mirror of
https://github.com/bknd-io/bknd/
synced 2026-08-03 16:46:00 +00:00
fixes
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
import type { CreateUserPayload } from "auth/AppAuth";
|
||||
import { $console } from "core";
|
||||
import { $console } from "core/console";
|
||||
import { Event } from "core/events";
|
||||
import type { em as prototypeEm } from "data/prototype";
|
||||
import { Connection } from "data/connection/Connection";
|
||||
|
||||
@@ -34,7 +34,7 @@ export class PasswordStrategy extends Strategy<typeof schema> {
|
||||
private getPayloadSchema() {
|
||||
return s.object({
|
||||
email: s.string({
|
||||
pattern: "^[\\w-\\.\\+_]+@([\\w-]+\\.)+[\\w-]{2,4}$",
|
||||
pattern: /^[\w-\.\+_]+@([\w-]+\.)+[\w-]{2,4}$/,
|
||||
}),
|
||||
password: s.string({
|
||||
minLength: 8, // @todo: this should be configurable
|
||||
|
||||
@@ -26,14 +26,13 @@ export class SchemaObject<Schema extends TSchema = TSchema> {
|
||||
initial?: Partial<s.Static<Schema>>,
|
||||
private options?: SchemaObjectOptions<Schema>,
|
||||
) {
|
||||
this._default = _schema.template() as any;
|
||||
this._value = initial
|
||||
? parse(_schema, structuredClone(initial as any), {
|
||||
withDefaults: true,
|
||||
forceParse: this.isForceParse(),
|
||||
skipMark: this.isForceParse(),
|
||||
})
|
||||
: (this._default as any);
|
||||
this._default = _schema.template({}, { withOptional: true }) as any;
|
||||
this._value = parse(_schema, structuredClone(initial ?? {}), {
|
||||
withDefaults: true,
|
||||
withExtendedDefaults: true,
|
||||
forceParse: this.isForceParse(),
|
||||
skipMark: this.isForceParse(),
|
||||
});
|
||||
this._config = Object.freeze(this._value);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ export class InvalidSchemaError extends Error {
|
||||
|
||||
export type ParseOptions = {
|
||||
withDefaults?: boolean;
|
||||
withExtendedDefaults?: boolean;
|
||||
coerce?: boolean;
|
||||
clone?: boolean;
|
||||
skipMark?: boolean; // @todo: do something with this
|
||||
@@ -57,8 +58,11 @@ export function parse<S extends s.Schema, Options extends ParseOptions = ParseOp
|
||||
): Options extends { coerce: true } ? s.StaticCoerced<S> : s.Static<S> {
|
||||
const schema = (opts?.clone ? cloneSchema(_schema as any) : _schema) as s.Schema;
|
||||
let value = opts?.coerce !== false ? schema.coerce(v) : v;
|
||||
if (opts?.withDefaults) {
|
||||
value = schema.template(value, { withOptional: true });
|
||||
if (opts?.withDefaults !== false) {
|
||||
value = schema.template(value, {
|
||||
withOptional: true,
|
||||
withExtendedOptional: opts?.withExtendedDefaults ?? false,
|
||||
});
|
||||
}
|
||||
|
||||
const result = schema.validate(value, {
|
||||
|
||||
@@ -120,7 +120,7 @@ export class Repository<TBD extends object = DefaultDB, TB extends keyof TBD = a
|
||||
if (options.where) {
|
||||
// @todo: auto-alias base entity when using joins! otherwise "id" is ambiguous
|
||||
const aliases = [entity.name];
|
||||
if (validated.join.length > 0) {
|
||||
if (validated.join?.length > 0) {
|
||||
aliases.push(...JoinBuilder.getJoinedEntityNames(this.em, entity, validated.join));
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ export class InvalidFieldConfigException extends Exception {
|
||||
) {
|
||||
console.error("InvalidFieldConfigException", {
|
||||
given,
|
||||
error: error.firstToString(),
|
||||
error: error.first(),
|
||||
});
|
||||
super(`Invalid Field config given for field "${field.name}": ${error.firstToString()}`);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ import { s } from "core/object/schema";
|
||||
|
||||
export const booleanFieldConfigSchema = s
|
||||
.strictObject({
|
||||
default_value: s.boolean({ default: false }),
|
||||
//default_value: s.boolean({ default: false }),
|
||||
default_value: s.boolean(),
|
||||
...omitKeys(baseFieldConfigSchema.properties, ["default_value"]),
|
||||
})
|
||||
.partial();
|
||||
|
||||
@@ -7,11 +7,11 @@ import { s } from "core/object/schema";
|
||||
|
||||
export const dateFieldConfigSchema = s
|
||||
.strictObject({
|
||||
type: s.string({ enum: ["date", "datetime", "week"], default: "date" }),
|
||||
type: s.string({ enum: ["date", "datetime", "week"] }),
|
||||
timezone: s.string(),
|
||||
min_date: s.string(),
|
||||
max_date: s.string(),
|
||||
...omitKeys(baseFieldConfigSchema.properties, ["default_value"]),
|
||||
...baseFieldConfigSchema.properties,
|
||||
})
|
||||
.partial();
|
||||
|
||||
|
||||
@@ -27,26 +27,16 @@ export const baseFieldConfigSchema = s
|
||||
.strictObject({
|
||||
label: s.string(),
|
||||
description: s.string(),
|
||||
required: s.boolean({ default: DEFAULT_REQUIRED }),
|
||||
fillable: s.anyOf(
|
||||
[
|
||||
s.boolean({ title: "Boolean", default: DEFAULT_FILLABLE }),
|
||||
s.array(s.string({ enum: ActionContext }), { title: "Context", uniqueItems: true }),
|
||||
],
|
||||
{
|
||||
default: DEFAULT_FILLABLE,
|
||||
},
|
||||
),
|
||||
hidden: s.anyOf(
|
||||
[
|
||||
s.boolean({ title: "Boolean", default: DEFAULT_HIDDEN }),
|
||||
// @todo: tmp workaround
|
||||
s.array(s.string({ enum: TmpContext }), { title: "Context", uniqueItems: true }),
|
||||
],
|
||||
{
|
||||
default: DEFAULT_HIDDEN,
|
||||
},
|
||||
),
|
||||
required: s.boolean(),
|
||||
fillable: s.anyOf([
|
||||
s.boolean({ title: "Boolean" }),
|
||||
s.array(s.string({ enum: ActionContext }), { title: "Context", uniqueItems: true }),
|
||||
]),
|
||||
hidden: s.anyOf([
|
||||
s.boolean({ title: "Boolean" }),
|
||||
// @todo: tmp workaround
|
||||
s.array(s.string({ enum: TmpContext }), { title: "Context", uniqueItems: true }),
|
||||
]),
|
||||
// if field is virtual, it will not call transformPersist & transformRetrieve
|
||||
virtual: s.boolean(),
|
||||
default_value: s.any(),
|
||||
@@ -100,7 +90,9 @@ export abstract class Field<
|
||||
name: this.name,
|
||||
type: "text",
|
||||
nullable: true,
|
||||
dflt: this.getDefault(),
|
||||
// see field-test-suite.ts:41
|
||||
dflt: undefined,
|
||||
//dflt: this.getDefault(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -116,14 +108,14 @@ export abstract class Field<
|
||||
if (Array.isArray(this.config.fillable)) {
|
||||
return context ? this.config.fillable.includes(context) : DEFAULT_FILLABLE;
|
||||
}
|
||||
return !!this.config.fillable;
|
||||
return this.config.fillable ?? DEFAULT_FILLABLE;
|
||||
}
|
||||
|
||||
isHidden(context?: TmpActionAndRenderContext): boolean {
|
||||
if (Array.isArray(this.config.hidden)) {
|
||||
return context ? this.config.hidden.includes(context as any) : DEFAULT_HIDDEN;
|
||||
}
|
||||
return this.config.hidden ?? false;
|
||||
return this.config.hidden ?? DEFAULT_HIDDEN;
|
||||
}
|
||||
|
||||
isRequired(): boolean {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type Schema as JsonSchema, Validator } from "@cfworker/json-schema";
|
||||
import { FromSchema, objectToJsLiteral, omitKeys } from "core/utils";
|
||||
import { objectToJsLiteral } from "core/utils";
|
||||
import type { EntityManager } from "data";
|
||||
import { TransformPersistFailedException } from "../errors";
|
||||
import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field";
|
||||
@@ -8,10 +8,10 @@ import { s } from "core/object/schema";
|
||||
|
||||
export const jsonSchemaFieldConfigSchema = s
|
||||
.strictObject({
|
||||
schema: s.any({ type: "object", default: {} }),
|
||||
ui_schema: s.any({ type: "object", default: {} }),
|
||||
schema: s.any({ type: "object" }),
|
||||
ui_schema: s.any({ type: "object" }),
|
||||
default_from_schema: s.boolean(),
|
||||
...omitKeys(baseFieldConfigSchema.properties, ["default_value"]),
|
||||
...baseFieldConfigSchema.properties,
|
||||
})
|
||||
.partial();
|
||||
|
||||
@@ -26,7 +26,7 @@ export class JsonSchemaField<
|
||||
|
||||
constructor(name: string, config: Partial<JsonSchemaFieldConfig>) {
|
||||
super(name, config);
|
||||
this.validator = new Validator(this.getJsonSchema());
|
||||
this.validator = new Validator({ ...this.getJsonSchema() });
|
||||
}
|
||||
|
||||
protected getSchema() {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { omitKeys, uuidv7 } from "core/utils";
|
||||
import { Field, baseFieldConfigSchema } from "./Field";
|
||||
import type { TFieldTSType } from "data/entities/EntityTypescript";
|
||||
import { s } from "core/object/schema";
|
||||
import type { FieldSpec } from "data/connection/Connection";
|
||||
|
||||
export const primaryFieldTypes = ["integer", "uuid"] as const;
|
||||
export type TPrimaryFieldFormat = (typeof primaryFieldTypes)[number];
|
||||
@@ -26,7 +25,7 @@ export class PrimaryField<Required extends true | false = false> extends Field<
|
||||
override readonly type = "primary";
|
||||
|
||||
constructor(name: string = config.data.default_primary_field, cfg?: PrimaryFieldConfig) {
|
||||
super(name, { fillable: false, required: false, ...cfg });
|
||||
super(name, { ...cfg, fillable: false, required: false });
|
||||
}
|
||||
|
||||
override isRequired(): boolean {
|
||||
|
||||
@@ -10,8 +10,8 @@ export const textFieldConfigSchema = s
|
||||
minLength: s.number(),
|
||||
maxLength: s.number(),
|
||||
pattern: s.string(),
|
||||
html_config: s.object({
|
||||
element: s.string({ default: "input" }),
|
||||
html_config: s.partialObject({
|
||||
element: s.string(),
|
||||
props: s.record(s.anyOf([s.string({ title: "String" }), s.number({ title: "Number" })])),
|
||||
}),
|
||||
...omitKeys(baseFieldConfigSchema.properties, ["default_value"]),
|
||||
|
||||
@@ -50,7 +50,7 @@ export function fieldTestSuite(
|
||||
expect(noConfigField.hasDefault()).toBe(false);
|
||||
expect(noConfigField.getDefault()).toBeUndefined();
|
||||
expect(dflt.hasDefault()).toBe(true);
|
||||
expect(dflt.getDefault()).toBe(config.defaultValue);
|
||||
expect(dflt.getDefault()).toEqual(config.defaultValue);
|
||||
});
|
||||
|
||||
test("isFillable", async () => {
|
||||
@@ -98,9 +98,6 @@ export function fieldTestSuite(
|
||||
test("toJSON", async () => {
|
||||
const _config = {
|
||||
..._requiredConfig,
|
||||
fillable: true,
|
||||
required: false,
|
||||
hidden: false,
|
||||
};
|
||||
|
||||
function fieldJson(field: Field) {
|
||||
@@ -118,7 +115,10 @@ export function fieldTestSuite(
|
||||
|
||||
expect(fieldJson(fillable)).toEqual({
|
||||
type: noConfigField.type,
|
||||
config: _config,
|
||||
config: {
|
||||
..._config,
|
||||
fillable: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(fieldJson(required)).toEqual({
|
||||
|
||||
@@ -141,9 +141,12 @@ export const repoQuery = s.recursive((self) =>
|
||||
.partial(),
|
||||
);
|
||||
export const getRepoQueryTemplate = () =>
|
||||
repoQuery.template({
|
||||
withOptional: true,
|
||||
}) as Required<RepoQuery>;
|
||||
repoQuery.template(
|
||||
{},
|
||||
{
|
||||
withOptional: true,
|
||||
},
|
||||
) as Required<RepoQuery>;
|
||||
|
||||
export type RepoQueryIn = {
|
||||
limit?: number;
|
||||
|
||||
@@ -17,8 +17,7 @@ export type TaskResult<Output = any> = {
|
||||
|
||||
export type TaskRenderProps<T extends Task = Task> = any;
|
||||
|
||||
// @todo: CURRENT WORKAROUND
|
||||
export const dynamic = <S extends s.Schema>(a: S, b?: any) => null as unknown as S;
|
||||
export const dynamic = <S extends s.Schema>(a: S, b?: any) => a;
|
||||
|
||||
/* export function dynamic<Type extends TSchema>(
|
||||
type: Type,
|
||||
|
||||
@@ -23,7 +23,8 @@ declare module "core" {
|
||||
}
|
||||
}
|
||||
|
||||
export class AppMedia extends Module<TAppMediaConfig> {
|
||||
// @todo: current workaround to make it all required
|
||||
export class AppMedia extends Module<Required<TAppMediaConfig>> {
|
||||
private _storage?: Storage;
|
||||
|
||||
override async build() {
|
||||
|
||||
@@ -23,21 +23,46 @@ export function buildMediaSchema() {
|
||||
);
|
||||
});
|
||||
|
||||
return s.strictObject({
|
||||
enabled: s.boolean({ default: false }),
|
||||
basepath: s.string({ default: "/api/media" }),
|
||||
entity_name: s.string({ default: "media" }),
|
||||
storage: s.strictObject(
|
||||
return s
|
||||
.strictObject(
|
||||
{
|
||||
body_max_size: s.number({
|
||||
description: "Max size of the body in bytes. Leave blank for unlimited.",
|
||||
}),
|
||||
enabled: s.boolean({ default: false }),
|
||||
basepath: s.string({ default: "/api/media" }),
|
||||
entity_name: s.string({ default: "media" }),
|
||||
storage: s
|
||||
.strictObject({
|
||||
body_max_size: s.number({
|
||||
description: "Max size of the body in bytes. Leave blank for unlimited.",
|
||||
}),
|
||||
})
|
||||
.partial(),
|
||||
adapter: s.anyOf(Object.values(adapterSchemaObject)),
|
||||
},
|
||||
{ default: {} },
|
||||
),
|
||||
adapter: s.anyOf(Object.values(adapterSchemaObject)).optional(),
|
||||
});
|
||||
{
|
||||
default: {},
|
||||
},
|
||||
)
|
||||
.partial();
|
||||
}
|
||||
|
||||
export const mediaConfigSchema = buildMediaSchema();
|
||||
export type TAppMediaConfig = s.Static<typeof mediaConfigSchema>;
|
||||
export type TAppMediaConfig2 = s.ObjectDefaults<(typeof mediaConfigSchema)["properties"]>;
|
||||
|
||||
const schema = s.strictObject(
|
||||
{
|
||||
enabled: s.boolean({ default: false }),
|
||||
basepath: s.string({ default: "/api/media" }),
|
||||
entity_name: s.string({ default: "media" }),
|
||||
storage: s
|
||||
.strictObject({
|
||||
body_max_size: s.number({
|
||||
description: "Max size of the body in bytes. Leave blank for unlimited.",
|
||||
}),
|
||||
})
|
||||
.partial(),
|
||||
},
|
||||
{
|
||||
default: {},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -25,10 +25,10 @@ export type { ModuleBuildContext };
|
||||
|
||||
export const MODULES = {
|
||||
server: AppServer,
|
||||
data: AppData, // @todo:
|
||||
data: AppData,
|
||||
auth: AppAuth,
|
||||
media: AppMedia,
|
||||
flows: AppFlows, // @todo:
|
||||
flows: AppFlows,
|
||||
} as const;
|
||||
|
||||
// get names of MODULES as an array
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { JSONSchema7 } from "json-schema";
|
||||
import { cloneDeep, omit, pick } from "lodash-es";
|
||||
import type { s } from "core/object/schema";
|
||||
import { omitKeys } from "core/utils";
|
||||
|
||||
export function extractSchema<
|
||||
Schema extends s.ObjectSchema,
|
||||
@@ -25,10 +25,10 @@ export function extractSchema<
|
||||
return [{ ...schema.toJSON() }, config, {} as any];
|
||||
}
|
||||
|
||||
const newSchema = cloneDeep(schema);
|
||||
const newSchema = JSON.parse(JSON.stringify(schema));
|
||||
const updated = {
|
||||
...newSchema.toJSON(),
|
||||
properties: omit(newSchema.properties, keys),
|
||||
...newSchema,
|
||||
properties: omitKeys(newSchema.properties, keys),
|
||||
};
|
||||
if (updated.required) {
|
||||
updated.required = updated.required.filter((key) => !keys.includes(key as any));
|
||||
@@ -44,7 +44,7 @@ export function extractSchema<
|
||||
};
|
||||
}
|
||||
|
||||
const reducedConfig = omit(config, keys) as any;
|
||||
const reducedConfig = omitKeys(config, keys as string[]) as any;
|
||||
|
||||
return [updated, reducedConfig, extracted];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user