mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-22 19:53:48 +08:00
feat(infra): orm document mode support (#8390)
```
info: t.document({
id: f.primaryKey().string()
})
```
```
tables.info.create({ id: '', a: 1, b: 2 })
```
This commit is contained in:
@@ -0,0 +1,127 @@
|
|||||||
|
import {
|
||||||
|
beforeEach,
|
||||||
|
describe,
|
||||||
|
expect,
|
||||||
|
test as vitest,
|
||||||
|
type TestAPI,
|
||||||
|
} from 'vitest';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createORMClient,
|
||||||
|
type DBSchemaBuilder,
|
||||||
|
f,
|
||||||
|
MemoryORMAdapter,
|
||||||
|
t,
|
||||||
|
Table,
|
||||||
|
} from '../';
|
||||||
|
|
||||||
|
const TEST_SCHEMA = {
|
||||||
|
docProperties: t.document({
|
||||||
|
docId: f.string().primaryKey(),
|
||||||
|
}),
|
||||||
|
} satisfies DBSchemaBuilder;
|
||||||
|
|
||||||
|
const ORMClient = createORMClient(TEST_SCHEMA);
|
||||||
|
|
||||||
|
type Context = {
|
||||||
|
client: InstanceType<typeof ORMClient>;
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach<Context>(async t => {
|
||||||
|
t.client = new ORMClient(new MemoryORMAdapter());
|
||||||
|
});
|
||||||
|
|
||||||
|
const test = vitest as TestAPI<Context>;
|
||||||
|
|
||||||
|
describe('ORM entity CRUD', () => {
|
||||||
|
test('still have type check', t => {
|
||||||
|
const { client } = t;
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
// @ts-expect-error type test
|
||||||
|
client.docProperties.create({
|
||||||
|
// docId missed
|
||||||
|
prop1: 'prop1:value',
|
||||||
|
prop2: 'prop2:value',
|
||||||
|
})
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to create ORM client', t => {
|
||||||
|
const { client } = t;
|
||||||
|
|
||||||
|
expect(client.docProperties instanceof Table).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to create entity', async t => {
|
||||||
|
const { client } = t;
|
||||||
|
|
||||||
|
const doc = client.docProperties.create({
|
||||||
|
docId: '1',
|
||||||
|
prop1: 'prop1:value',
|
||||||
|
prop2: 'prop2:value',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(doc.docId).toBe('1');
|
||||||
|
expect(doc.prop1).toBe('prop1:value');
|
||||||
|
expect(doc.prop2).toBe('prop2:value');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to read entity', async t => {
|
||||||
|
const { client } = t;
|
||||||
|
|
||||||
|
const doc = client.docProperties.create({
|
||||||
|
docId: '1',
|
||||||
|
prop1: 'prop1:value',
|
||||||
|
prop2: 'prop2:value',
|
||||||
|
});
|
||||||
|
|
||||||
|
const doc2 = client.docProperties.get(doc.docId);
|
||||||
|
|
||||||
|
expect(doc2).toStrictEqual(doc);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to update entity', async t => {
|
||||||
|
const { client } = t;
|
||||||
|
|
||||||
|
const doc = client.docProperties.create({
|
||||||
|
docId: '1',
|
||||||
|
prop1: 'prop1:value',
|
||||||
|
prop2: 'prop2:value',
|
||||||
|
});
|
||||||
|
|
||||||
|
client.docProperties.update(doc.docId, {
|
||||||
|
prop1: 'prop1:value2',
|
||||||
|
prop3: 'prop3:value',
|
||||||
|
prop4: null,
|
||||||
|
prop5: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const doc2 = client.docProperties.get(doc.docId);
|
||||||
|
|
||||||
|
expect(doc2).toStrictEqual({
|
||||||
|
docId: '1',
|
||||||
|
prop1: 'prop1:value2',
|
||||||
|
prop2: 'prop2:value',
|
||||||
|
prop3: 'prop3:value',
|
||||||
|
prop4: null,
|
||||||
|
prop5: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to delete entity', async t => {
|
||||||
|
const { client } = t;
|
||||||
|
|
||||||
|
const doc = client.docProperties.create({
|
||||||
|
docId: '1',
|
||||||
|
prop1: 'prop1:value',
|
||||||
|
prop2: 'prop2:value',
|
||||||
|
});
|
||||||
|
|
||||||
|
client.docProperties.delete(doc.docId);
|
||||||
|
|
||||||
|
const doc2 = client.docProperties.get(doc.docId);
|
||||||
|
|
||||||
|
expect(doc2).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { beforeEach, describe, expect, test as t, type TestAPI } from 'vitest';
|
import {
|
||||||
|
beforeEach,
|
||||||
|
describe,
|
||||||
|
expect,
|
||||||
|
test as vitest,
|
||||||
|
type TestAPI,
|
||||||
|
} from 'vitest';
|
||||||
import { Doc } from 'yjs';
|
import { Doc } from 'yjs';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -8,6 +14,7 @@ import {
|
|||||||
type DocProvider,
|
type DocProvider,
|
||||||
type Entity,
|
type Entity,
|
||||||
f,
|
f,
|
||||||
|
t,
|
||||||
Table,
|
Table,
|
||||||
YjsDBAdapter,
|
YjsDBAdapter,
|
||||||
} from '../';
|
} from '../';
|
||||||
@@ -28,6 +35,9 @@ const TEST_SCHEMA = {
|
|||||||
name: f.string(),
|
name: f.string(),
|
||||||
email: f.string().optional(),
|
email: f.string().optional(),
|
||||||
},
|
},
|
||||||
|
userInfo: t.document({
|
||||||
|
userId: f.number().primaryKey(),
|
||||||
|
}),
|
||||||
} satisfies DBSchemaBuilder;
|
} satisfies DBSchemaBuilder;
|
||||||
|
|
||||||
const docProvider: DocProvider = {
|
const docProvider: DocProvider = {
|
||||||
@@ -45,7 +55,7 @@ beforeEach<Context>(async t => {
|
|||||||
t.client = new Client(new YjsDBAdapter(TEST_SCHEMA, docProvider));
|
t.client = new Client(new YjsDBAdapter(TEST_SCHEMA, docProvider));
|
||||||
});
|
});
|
||||||
|
|
||||||
const test = t as TestAPI<Context>;
|
const test = vitest as TestAPI<Context>;
|
||||||
|
|
||||||
describe('ORM entity CRUD', () => {
|
describe('ORM entity CRUD', () => {
|
||||||
test('should be able to create ORM client', t => {
|
test('should be able to create ORM client', t => {
|
||||||
@@ -404,4 +414,71 @@ describe('ORM entity CRUD', () => {
|
|||||||
expect(found).toEqual([]);
|
expect(found).toEqual([]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should be able to create document entity', t => {
|
||||||
|
const { client } = t;
|
||||||
|
|
||||||
|
const doc = client.userInfo.create({
|
||||||
|
userId: 1,
|
||||||
|
avatar: 'avatar.jpg',
|
||||||
|
address: '123 Main St',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(doc.userId).toBe(1);
|
||||||
|
expect(doc.avatar).toBe('avatar.jpg');
|
||||||
|
expect(doc.address).toBe('123 Main St');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to read document entity', t => {
|
||||||
|
const { client } = t;
|
||||||
|
|
||||||
|
const doc = client.userInfo.create({
|
||||||
|
userId: 1,
|
||||||
|
avatar: 'avatar.jpg',
|
||||||
|
address: '123 Main St',
|
||||||
|
});
|
||||||
|
|
||||||
|
const doc2 = client.userInfo.get(1);
|
||||||
|
|
||||||
|
expect(doc2).toStrictEqual(doc);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to update document entity', t => {
|
||||||
|
const { client } = t;
|
||||||
|
|
||||||
|
const doc = client.userInfo.create({
|
||||||
|
userId: 1,
|
||||||
|
avatar: 'avatar.jpg',
|
||||||
|
address: '123 Main St',
|
||||||
|
});
|
||||||
|
|
||||||
|
client.userInfo.update(doc.userId, {
|
||||||
|
avatar: 'avatar2.jpg',
|
||||||
|
city: 'New York',
|
||||||
|
});
|
||||||
|
|
||||||
|
const doc2 = client.userInfo.get(1);
|
||||||
|
|
||||||
|
expect(doc2).toStrictEqual({
|
||||||
|
userId: 1,
|
||||||
|
avatar: 'avatar2.jpg',
|
||||||
|
address: '123 Main St',
|
||||||
|
city: 'New York',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to delete document entity', t => {
|
||||||
|
const { client } = t;
|
||||||
|
|
||||||
|
const doc = client.userInfo.create({
|
||||||
|
userId: 1,
|
||||||
|
avatar: 'avatar.jpg',
|
||||||
|
address: '123 Main St',
|
||||||
|
});
|
||||||
|
|
||||||
|
client.userInfo.delete(doc.userId);
|
||||||
|
|
||||||
|
const doc2 = client.userInfo.get(1);
|
||||||
|
expect(doc2).toBe(null);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ export class YjsTableAdapter implements TableAdapter {
|
|||||||
if (select === 'key') {
|
if (select === 'key') {
|
||||||
return this.keyof(record);
|
return this.keyof(record);
|
||||||
} else if (select === '*') {
|
} else if (select === '*') {
|
||||||
selectedFields = this.fields;
|
return this.toObject(record);
|
||||||
} else {
|
} else {
|
||||||
selectedFields = select;
|
selectedFields = select;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ export type TableSchemaBuilder = Record<
|
|||||||
string,
|
string,
|
||||||
FieldSchemaBuilder<any, boolean>
|
FieldSchemaBuilder<any, boolean>
|
||||||
>;
|
>;
|
||||||
|
export type DocumentTableSchemaBuilder = TableSchemaBuilder & {
|
||||||
|
__document: FieldSchemaBuilder<boolean, true, false>;
|
||||||
|
};
|
||||||
|
|
||||||
export type DBSchemaBuilder = Record<string, TableSchemaBuilder>;
|
export type DBSchemaBuilder = Record<string, TableSchemaBuilder>;
|
||||||
|
|
||||||
export class FieldSchemaBuilder<
|
export class FieldSchemaBuilder<
|
||||||
@@ -53,3 +57,12 @@ export const f = {
|
|||||||
boolean: () => new FieldSchemaBuilder<boolean>('boolean'),
|
boolean: () => new FieldSchemaBuilder<boolean>('boolean'),
|
||||||
json: <T = any>() => new FieldSchemaBuilder<T>('json'),
|
json: <T = any>() => new FieldSchemaBuilder<T>('json'),
|
||||||
} satisfies Record<FieldType, () => FieldSchemaBuilder<any>>;
|
} satisfies Record<FieldType, () => FieldSchemaBuilder<any>>;
|
||||||
|
|
||||||
|
export const t = {
|
||||||
|
document: <T extends TableSchemaBuilder>(schema: T) => {
|
||||||
|
return {
|
||||||
|
...schema,
|
||||||
|
__document: new FieldSchemaBuilder<boolean>('boolean').optional(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Observable, shareReplay } from 'rxjs';
|
|||||||
import type { DBAdapter, TableAdapter } from './adapters';
|
import type { DBAdapter, TableAdapter } from './adapters';
|
||||||
import type {
|
import type {
|
||||||
DBSchemaBuilder,
|
DBSchemaBuilder,
|
||||||
|
DocumentTableSchemaBuilder,
|
||||||
FieldSchemaBuilder,
|
FieldSchemaBuilder,
|
||||||
TableSchema,
|
TableSchema,
|
||||||
TableSchemaBuilder,
|
TableSchemaBuilder,
|
||||||
@@ -17,72 +18,115 @@ type Pretty<T> = T extends any
|
|||||||
}
|
}
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
|
// filter out all fields starting with `__`
|
||||||
|
type TableDefinedFieldNames<T extends TableSchemaBuilder> = keyof {
|
||||||
|
[K in keyof T as K extends `__${string}` ? never : K]: T[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
type Typeof<F extends FieldSchemaBuilder> =
|
||||||
|
F extends FieldSchemaBuilder<infer Type> ? Type : never;
|
||||||
|
|
||||||
type RequiredFields<T extends TableSchemaBuilder> = {
|
type RequiredFields<T extends TableSchemaBuilder> = {
|
||||||
[K in keyof T as T[K] extends FieldSchemaBuilder<any, infer Optional>
|
[K in TableDefinedFieldNames<T> as T[K] extends FieldSchemaBuilder<
|
||||||
|
any,
|
||||||
|
infer Optional
|
||||||
|
>
|
||||||
? Optional extends false
|
? Optional extends false
|
||||||
? K
|
? K
|
||||||
: never
|
: never
|
||||||
: never]: T[K] extends FieldSchemaBuilder<infer Type> ? Type : never;
|
: never]: Typeof<T[K]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type OptionalFields<T extends TableSchemaBuilder> = {
|
type OptionalFields<T extends TableSchemaBuilder> = {
|
||||||
[K in keyof T as T[K] extends FieldSchemaBuilder<any, infer Optional>
|
[K in TableDefinedFieldNames<T> as T[K] extends FieldSchemaBuilder<
|
||||||
|
any,
|
||||||
|
infer Optional
|
||||||
|
>
|
||||||
? Optional extends true
|
? Optional extends true
|
||||||
? K
|
? K
|
||||||
: never
|
: never
|
||||||
: never]?: T[K] extends FieldSchemaBuilder<infer Type>
|
: never]?: Typeof<T[K]> | null;
|
||||||
? Type | null
|
|
||||||
: never;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type PrimaryKeyField<T extends TableSchemaBuilder> = {
|
type PrimaryKeyField<T extends TableSchemaBuilder> = {
|
||||||
[K in keyof T]: T[K] extends FieldSchemaBuilder<any, any, infer PrimaryKey>
|
[K in TableDefinedFieldNames<T>]: T[K] extends FieldSchemaBuilder<
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
infer PrimaryKey
|
||||||
|
>
|
||||||
? PrimaryKey extends true
|
? PrimaryKey extends true
|
||||||
? K
|
? K
|
||||||
: never
|
: never
|
||||||
: never;
|
: never;
|
||||||
}[keyof T];
|
}[TableDefinedFieldNames<T>];
|
||||||
|
|
||||||
export type NonPrimaryKeyFields<T extends TableSchemaBuilder> = {
|
type TableDefinedEntity<T extends TableSchemaBuilder> = Pretty<
|
||||||
[K in keyof T]: T[K] extends FieldSchemaBuilder<any, any, infer PrimaryKey>
|
RequiredFields<T> &
|
||||||
|
OptionalFields<T> & {
|
||||||
|
[PrimaryKey in PrimaryKeyField<T>]: Typeof<T[PrimaryKey]>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
type MaybeDocumentEntityWrapper<Schema, Ty> =
|
||||||
|
Schema extends DocumentTableSchemaBuilder
|
||||||
|
? Ty & {
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
: Ty;
|
||||||
|
|
||||||
|
type NonPrimaryKeyFieldNames<T extends TableSchemaBuilder> = {
|
||||||
|
[K in TableDefinedFieldNames<T>]: T[K] extends FieldSchemaBuilder<
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
infer PrimaryKey
|
||||||
|
>
|
||||||
? PrimaryKey extends false
|
? PrimaryKey extends false
|
||||||
? K
|
? K
|
||||||
: never
|
: never
|
||||||
: never;
|
: never;
|
||||||
}[keyof T];
|
}[TableDefinedFieldNames<T>];
|
||||||
|
|
||||||
export type PrimaryKeyFieldType<T extends TableSchemaBuilder> =
|
// CRUD api types
|
||||||
T[PrimaryKeyField<T>] extends FieldSchemaBuilder<infer Type>
|
export type PrimaryKeyFieldType<T extends TableSchemaBuilder> = Typeof<
|
||||||
? Type extends Key
|
T[PrimaryKeyField<T>]
|
||||||
? Type
|
>;
|
||||||
: never
|
|
||||||
: never;
|
|
||||||
|
|
||||||
export type CreateEntityInput<T extends TableSchemaBuilder> = Pretty<
|
export type CreateEntityInput<T extends TableSchemaBuilder> = Pretty<
|
||||||
RequiredFields<T> & OptionalFields<T>
|
MaybeDocumentEntityWrapper<T, RequiredFields<T> & OptionalFields<T>>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
// @TODO(@forehalo): return value need to be specified with `Default` inference
|
// @TODO(@forehalo): return value need to be specified with `Default` inference
|
||||||
export type Entity<T extends TableSchemaBuilder> = Pretty<
|
export type Entity<T extends TableSchemaBuilder> = Pretty<
|
||||||
CreateEntityInput<T> & {
|
MaybeDocumentEntityWrapper<T, TableDefinedEntity<T>>
|
||||||
[key in PrimaryKeyField<T>]: PrimaryKeyFieldType<T>;
|
|
||||||
}
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type UpdateEntityInput<T extends TableSchemaBuilder> = Pretty<{
|
export type UpdateEntityInput<T extends TableSchemaBuilder> = Pretty<
|
||||||
[key in NonPrimaryKeyFields<T>]?: key extends keyof Entity<T>
|
MaybeDocumentEntityWrapper<
|
||||||
? Entity<T>[key]
|
T,
|
||||||
: never;
|
{
|
||||||
}>;
|
[key in NonPrimaryKeyFieldNames<T>]?: key extends keyof TableDefinedEntity<T>
|
||||||
|
? TableDefinedEntity<T>[key]
|
||||||
|
: never;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
|
||||||
export type FindEntityInput<T extends TableSchemaBuilder> = Pretty<{
|
export type FindEntityInput<T extends TableSchemaBuilder> = Pretty<
|
||||||
[key in keyof T]?: key extends keyof Entity<T> ? Entity<T>[key] : never;
|
MaybeDocumentEntityWrapper<
|
||||||
}>;
|
T,
|
||||||
|
{
|
||||||
|
[key in TableDefinedFieldNames<T>]?: key extends keyof TableDefinedEntity<T>
|
||||||
|
? TableDefinedEntity<T>[key]
|
||||||
|
: never;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
|
||||||
export class Table<T extends TableSchemaBuilder> {
|
export class Table<T extends TableSchemaBuilder> {
|
||||||
readonly schema: TableSchema;
|
readonly schema: TableSchema = {};
|
||||||
readonly keyField: string = '';
|
readonly keyField: string = '';
|
||||||
private readonly adapter: TableAdapter;
|
private readonly adapter: TableAdapter;
|
||||||
|
public readonly isDocumentTable: boolean = false;
|
||||||
|
|
||||||
private readonly subscribedKeys: Map<Key, Observable<any>> = new Map();
|
private readonly subscribedKeys: Map<Key, Observable<any>> = new Map();
|
||||||
|
|
||||||
@@ -92,17 +136,20 @@ export class Table<T extends TableSchemaBuilder> {
|
|||||||
private readonly opts: TableOptions
|
private readonly opts: TableOptions
|
||||||
) {
|
) {
|
||||||
this.adapter = db.table(name) as any;
|
this.adapter = db.table(name) as any;
|
||||||
this.schema = Object.entries(this.opts.schema).reduce(
|
for (const [fieldName, fieldBuilder] of Object.entries(this.opts.schema)) {
|
||||||
(acc, [fieldName, fieldBuilder]) => {
|
// handle internal fields
|
||||||
acc[fieldName] = fieldBuilder.schema;
|
if (fieldName.startsWith('__')) {
|
||||||
if (fieldBuilder.schema.isPrimaryKey) {
|
if (fieldName === '__document') {
|
||||||
// @ts-expect-error still in constructor
|
this.isDocumentTable = true;
|
||||||
this.keyField = fieldName;
|
|
||||||
}
|
}
|
||||||
return acc;
|
continue;
|
||||||
},
|
}
|
||||||
{} as TableSchema
|
|
||||||
);
|
this.schema[fieldName] = fieldBuilder.schema;
|
||||||
|
if (fieldBuilder.schema.isPrimaryKey) {
|
||||||
|
this.keyField = fieldName;
|
||||||
|
}
|
||||||
|
}
|
||||||
this.adapter.setup({ ...opts, keyField: this.keyField });
|
this.adapter.setup({ ...opts, keyField: this.keyField });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +176,7 @@ export class Table<T extends TableSchemaBuilder> {
|
|||||||
validators.validateCreateEntityData(this, data);
|
validators.validateCreateEntityData(this, data);
|
||||||
|
|
||||||
return this.adapter.insert({
|
return this.adapter.insert({
|
||||||
data: data,
|
data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,32 +52,33 @@ export const dataValidators = {
|
|||||||
validate(table, data) {
|
validate(table, data) {
|
||||||
for (const key in data) {
|
for (const key in data) {
|
||||||
const field = table.schema[key];
|
const field = table.schema[key];
|
||||||
if (!field) {
|
if (field) {
|
||||||
throw new Error(
|
const val = data[key];
|
||||||
`[Table(${table.name})]: Field '${key}' is not defined but set in entity.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const val = data[key];
|
if (val === undefined) {
|
||||||
|
delete data[key];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (val === undefined) {
|
if (val === null) {
|
||||||
delete data[key];
|
if (!field.optional) {
|
||||||
continue;
|
throw new Error(
|
||||||
}
|
`[Table(${table.name})]: Field '${key}' is required but not set.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (val === null) {
|
const typeGet = inputType(val);
|
||||||
if (!field.optional) {
|
if (!typeMatches(field.type, typeGet)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`[Table(${table.name})]: Field '${key}' is required but not set.`
|
`[Table(${table.name})]: Field '${key}' type mismatch. Expected ${field.type} got ${typeGet}.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
continue;
|
} else if (!table.isDocumentTable) {
|
||||||
}
|
// strict check field existence for normal table
|
||||||
|
|
||||||
const typeGet = inputType(val);
|
|
||||||
if (!typeMatches(field.type, typeGet)) {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`[Table(${table.name})]: Field '${key}' type mismatch. Expected ${field.type} got ${typeGet}.`
|
`[Table(${table.name})]: Field '${key}' is not defined but set in entity.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,33 +87,35 @@ export const dataValidators = {
|
|||||||
DataTypeShouldExactlyMatch: {
|
DataTypeShouldExactlyMatch: {
|
||||||
validate(table, data) {
|
validate(table, data) {
|
||||||
const keys: Set<string> = new Set();
|
const keys: Set<string> = new Set();
|
||||||
|
|
||||||
for (const key in data) {
|
for (const key in data) {
|
||||||
const field = table.schema[key];
|
const field = table.schema[key];
|
||||||
if (!field) {
|
if (field) {
|
||||||
|
const val = data[key];
|
||||||
|
|
||||||
|
if (val === undefined || val === null) {
|
||||||
|
if (!field.optional) {
|
||||||
|
throw new Error(
|
||||||
|
`[Table(${table.name})]: Field '${key}' is required but not set.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeGet = inputType(val);
|
||||||
|
if (!typeMatches(field.type, typeGet)) {
|
||||||
|
throw new Error(
|
||||||
|
`[Table(${table.name})]: Field '${key}' type mismatch. Expected type '${field.type}' but got '${typeGet}'.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
keys.add(key);
|
||||||
|
} else if (!table.isDocumentTable) {
|
||||||
|
// strict check field existence for normal table
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`[Table(${table.name})]: Field '${key}' is not defined but set in entity.`
|
`[Table(${table.name})]: Field '${key}' is not defined but set in entity.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const val = data[key];
|
|
||||||
|
|
||||||
if (val === undefined || val === null) {
|
|
||||||
if (!field.optional) {
|
|
||||||
throw new Error(
|
|
||||||
`[Table(${table.name})]: Field '${key}' is required but not set.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const typeGet = inputType(val);
|
|
||||||
if (!typeMatches(field.type, typeGet)) {
|
|
||||||
throw new Error(
|
|
||||||
`[Table(${table.name})]: Field '${key}' type mismatch. Expected type '${field.type}' but got '${typeGet}'.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
keys.add(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in table.schema) {
|
for (const key in table.schema) {
|
||||||
|
|||||||
Reference in New Issue
Block a user