mirror of
https://github.com/logto-io/logto.git
synced 2025-01-27 21:39:16 -05:00
Merge pull request #4636 from logto-io/gao-optimize-schema-types
refactor(schemas,shared)!: optimize schema types
This commit is contained in:
commit
b4655b4e0f
17 changed files with 149 additions and 95 deletions
|
@ -65,7 +65,7 @@ export const createPoolAndDatabaseIfNeeded = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
export const insertInto = <T extends SchemaLike>(object: T, table: string) => {
|
||||
export const insertInto = <T extends SchemaLike<string>>(object: T, table: string) => {
|
||||
const keys = Object.keys(object);
|
||||
|
||||
return sql`
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
import { type GeneratedSchema, type SchemaLike } from '@logto/schemas';
|
||||
import {
|
||||
type FieldIdentifiers,
|
||||
conditionalSql,
|
||||
convertToIdentifiers,
|
||||
manyRows,
|
||||
} from '@logto/shared';
|
||||
import { conditionalSql, convertToIdentifiers, manyRows } from '@logto/shared';
|
||||
import { sql, type CommonQueryMethods } from 'slonik';
|
||||
|
||||
export const buildFindAllEntitiesWithPool =
|
||||
(pool: CommonQueryMethods) =>
|
||||
<CreateSchema extends SchemaLike, Schema extends CreateSchema>(
|
||||
schema: GeneratedSchema<CreateSchema, Schema>,
|
||||
<
|
||||
Keys extends string,
|
||||
CreateSchema extends Partial<SchemaLike<Keys>>,
|
||||
Schema extends SchemaLike<Keys>,
|
||||
>(
|
||||
schema: GeneratedSchema<Keys, CreateSchema, Schema>,
|
||||
orderBy?: Array<{
|
||||
field: keyof FieldIdentifiers<keyof GeneratedSchema<CreateSchema, Schema>['fields']>;
|
||||
field: Keys;
|
||||
order: 'asc' | 'desc';
|
||||
}>
|
||||
) => {
|
||||
|
|
|
@ -7,10 +7,16 @@ import RequestError from '#src/errors/RequestError/index.js';
|
|||
import assertThat from '#src/utils/assert-that.js';
|
||||
import { isKeyOf } from '#src/utils/schema.js';
|
||||
|
||||
type WithId<Key> = Key | 'id';
|
||||
|
||||
export const buildFindEntityByIdWithPool =
|
||||
(pool: CommonQueryMethods) =>
|
||||
<CreateSchema extends SchemaLike, Schema extends CreateSchema>(
|
||||
schema: GeneratedSchema<CreateSchema, Schema & { id: string }>
|
||||
<
|
||||
Key extends string,
|
||||
CreateSchema extends Partial<SchemaLike<WithId<Key>>>,
|
||||
Schema extends SchemaLike<WithId<Key>>,
|
||||
>(
|
||||
schema: GeneratedSchema<WithId<Key>, CreateSchema, Schema>
|
||||
) => {
|
||||
const { table, fields } = convertToIdentifiers(schema);
|
||||
const isKeyOfSchema = isKeyOf(schema);
|
||||
|
|
|
@ -40,20 +40,32 @@ type InsertIntoConfig = {
|
|||
};
|
||||
|
||||
type BuildInsertInto = {
|
||||
<CreateSchema extends SchemaLike, Schema extends CreateSchema>(
|
||||
{ fieldKeys, ...rest }: GeneratedSchema<CreateSchema, Schema>,
|
||||
<
|
||||
Key extends string,
|
||||
CreateSchema extends Partial<SchemaLike<Key>>,
|
||||
Schema extends SchemaLike<Key>,
|
||||
>(
|
||||
{ fieldKeys, ...rest }: GeneratedSchema<Key, CreateSchema, Schema>,
|
||||
config: InsertIntoConfigReturning
|
||||
): (data: OmitAutoSetFields<CreateSchema>) => Promise<Schema>;
|
||||
<CreateSchema extends SchemaLike, Schema extends CreateSchema>(
|
||||
{ fieldKeys, ...rest }: GeneratedSchema<CreateSchema, Schema>,
|
||||
<
|
||||
Key extends string,
|
||||
CreateSchema extends Partial<SchemaLike<Key>>,
|
||||
Schema extends SchemaLike<Key>,
|
||||
>(
|
||||
{ fieldKeys, ...rest }: GeneratedSchema<Key, CreateSchema, Schema>,
|
||||
config?: InsertIntoConfig
|
||||
): (data: OmitAutoSetFields<CreateSchema>) => Promise<void>;
|
||||
};
|
||||
|
||||
export const buildInsertIntoWithPool =
|
||||
(pool: CommonQueryMethods): BuildInsertInto =>
|
||||
<CreateSchema extends SchemaLike, Schema extends CreateSchema>(
|
||||
schema: GeneratedSchema<CreateSchema, Schema>,
|
||||
<
|
||||
Key extends string,
|
||||
CreateSchema extends Partial<SchemaLike<Key>>,
|
||||
Schema extends SchemaLike<Key>,
|
||||
>(
|
||||
schema: GeneratedSchema<Key, CreateSchema, Schema>,
|
||||
config?: InsertIntoConfig | InsertIntoConfigReturning
|
||||
) => {
|
||||
const { fieldKeys, ...rest } = schema;
|
||||
|
@ -88,7 +100,7 @@ export const buildInsertIntoWithPool =
|
|||
${conditionalSql(returning, () => sql`returning *`)}
|
||||
`);
|
||||
|
||||
assertThat(!returning || entry, new InsertionError<CreateSchema, Schema>(schema, data));
|
||||
assertThat(!returning || entry, new InsertionError<Key, CreateSchema, Schema>(schema, data));
|
||||
|
||||
return entry;
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { CreateUser, User } from '@logto/schemas';
|
||||
import type { CreateUser, UserKeys } from '@logto/schemas';
|
||||
import { Users, Applications } from '@logto/schemas';
|
||||
import type { UpdateWhereData } from '@logto/shared';
|
||||
|
||||
|
@ -97,7 +97,7 @@ describe('buildUpdateWhere()', () => {
|
|||
const pool = createTestPool('update "users"\nset "username"=$1\nwhere "id"=$2\nreturning *');
|
||||
|
||||
const updateWhere = buildUpdateWhereWithPool(pool)(Users, true);
|
||||
const updateWhereData: UpdateWhereData<User> = {
|
||||
const updateWhereData: UpdateWhereData<UserKeys, UserKeys> = {
|
||||
set: { username: '123' },
|
||||
where: { id: 'foo' },
|
||||
jsonbMode: 'merge',
|
||||
|
@ -114,7 +114,7 @@ describe('buildUpdateWhere()', () => {
|
|||
);
|
||||
|
||||
const updateWhere = buildUpdateWhereWithPool(pool)(Users, true);
|
||||
const updateWhereData: UpdateWhereData<User> = {
|
||||
const updateWhereData: UpdateWhereData<UserKeys, UserKeys> = {
|
||||
set: { username: '123' },
|
||||
where: { username: 'foo' },
|
||||
jsonbMode: 'merge',
|
||||
|
|
|
@ -11,25 +11,44 @@ import assertThat from '#src/utils/assert-that.js';
|
|||
import { isKeyOf } from '#src/utils/schema.js';
|
||||
|
||||
type BuildUpdateWhere = {
|
||||
<CreateSchema extends SchemaLike, Schema extends CreateSchema>(
|
||||
schema: GeneratedSchema<CreateSchema, Schema>,
|
||||
<
|
||||
Key extends string,
|
||||
CreateSchema extends Partial<SchemaLike<Key>>,
|
||||
Schema extends SchemaLike<Key>,
|
||||
>(
|
||||
schema: GeneratedSchema<Key, CreateSchema, Schema>,
|
||||
returning: true
|
||||
): (data: UpdateWhereData<Schema>) => Promise<Schema>;
|
||||
<CreateSchema extends SchemaLike, Schema extends CreateSchema>(
|
||||
schema: GeneratedSchema<CreateSchema, Schema>,
|
||||
): <SetKey extends Key, WhereKey extends Key>(
|
||||
data: UpdateWhereData<SetKey, WhereKey>
|
||||
) => Promise<Schema>;
|
||||
<
|
||||
Key extends string,
|
||||
CreateSchema extends Partial<SchemaLike<Key>>,
|
||||
Schema extends SchemaLike<Key>,
|
||||
>(
|
||||
schema: GeneratedSchema<Key, CreateSchema, Schema>,
|
||||
returning?: false
|
||||
): (data: UpdateWhereData<Schema>) => Promise<void>;
|
||||
): <SetKey extends Key, WhereKey extends Key>(
|
||||
data: UpdateWhereData<SetKey, WhereKey>
|
||||
) => Promise<void>;
|
||||
};
|
||||
|
||||
export const buildUpdateWhereWithPool =
|
||||
(pool: CommonQueryMethods): BuildUpdateWhere =>
|
||||
<CreateSchema extends SchemaLike, Schema extends CreateSchema>(
|
||||
schema: GeneratedSchema<CreateSchema, Schema>,
|
||||
<
|
||||
Key extends string,
|
||||
CreateSchema extends Partial<SchemaLike<Key>>,
|
||||
Schema extends SchemaLike<Key>,
|
||||
>(
|
||||
schema: GeneratedSchema<Key, CreateSchema, Schema>,
|
||||
returning = false
|
||||
) => {
|
||||
const { table, fields } = convertToIdentifiers(schema);
|
||||
const isKeyOfSchema = isKeyOf(schema);
|
||||
const connectKeyValueWithEqualSign = (data: Partial<Schema>, jsonbMode: 'replace' | 'merge') =>
|
||||
const connectKeyValueWithEqualSign = <ConnectKey extends Key>(
|
||||
data: Partial<SchemaLike<ConnectKey>>,
|
||||
jsonbMode: 'replace' | 'merge'
|
||||
) =>
|
||||
Object.entries<SchemaValue>(data)
|
||||
.map(([key, value]) => {
|
||||
if (!isKeyOfSchema(key) || value === undefined) {
|
||||
|
@ -57,7 +76,11 @@ export const buildUpdateWhereWithPool =
|
|||
})
|
||||
.filter((value): value is Truthy<typeof value> => notFalsy(value));
|
||||
|
||||
return async ({ set, where, jsonbMode }: UpdateWhereData<Schema>) => {
|
||||
return async <SetKey extends Key, WhereKey extends Key>({
|
||||
set,
|
||||
where,
|
||||
jsonbMode,
|
||||
}: UpdateWhereData<SetKey, WhereKey>) => {
|
||||
const {
|
||||
rows: [data],
|
||||
} = await pool.query<Schema>(sql`
|
||||
|
|
|
@ -12,23 +12,27 @@ export class DeletionError extends SlonikError {
|
|||
}
|
||||
|
||||
export class UpdateError<
|
||||
CreateSchema extends SchemaLike,
|
||||
Schema extends CreateSchema,
|
||||
Key extends string,
|
||||
CreateSchema extends Partial<SchemaLike<Key>>,
|
||||
Schema extends SchemaLike<Key>,
|
||||
SetKey extends Key,
|
||||
WhereKey extends Key,
|
||||
> extends SlonikError {
|
||||
public constructor(
|
||||
public readonly schema: GeneratedSchema<CreateSchema, Schema>,
|
||||
public readonly detail: Partial<UpdateWhereData<Schema>>
|
||||
public readonly schema: GeneratedSchema<Key, CreateSchema, Schema>,
|
||||
public readonly detail: Partial<UpdateWhereData<SetKey, WhereKey>>
|
||||
) {
|
||||
super('Resource not found.');
|
||||
}
|
||||
}
|
||||
|
||||
export class InsertionError<
|
||||
CreateSchema extends SchemaLike,
|
||||
Schema extends CreateSchema,
|
||||
Key extends string,
|
||||
CreateSchema extends Partial<SchemaLike<Key>>,
|
||||
Schema extends SchemaLike<Key>,
|
||||
> extends SlonikError {
|
||||
public constructor(
|
||||
public readonly schema: GeneratedSchema<CreateSchema, Schema>,
|
||||
public readonly schema: GeneratedSchema<Key, CreateSchema, Schema>,
|
||||
public readonly detail?: OmitAutoSetFields<CreateSchema>
|
||||
) {
|
||||
super('Create Error.');
|
||||
|
|
|
@ -37,9 +37,9 @@ export default function koaSlonikErrorHandler<StateT, ContextT>(): Middleware<St
|
|||
throw new RequestError({
|
||||
code: 'entity.create_failed',
|
||||
status: 422,
|
||||
// Assert generic type of the Class instance
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
name: (error as InsertionError<SchemaLike, SchemaLike>).schema.tableSingular,
|
||||
// eslint-disable-next-line no-restricted-syntax -- assert generic type of the Class instance
|
||||
name: (error as InsertionError<string, SchemaLike<string>, SchemaLike<string>>).schema
|
||||
.tableSingular,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -47,9 +47,10 @@ export default function koaSlonikErrorHandler<StateT, ContextT>(): Middleware<St
|
|||
throw new RequestError({
|
||||
code: 'entity.not_exists',
|
||||
status: 404,
|
||||
// Assert generic type of the Class instance
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
name: (error as UpdateError<SchemaLike, SchemaLike>).schema.tableSingular,
|
||||
name:
|
||||
// eslint-disable-next-line no-restricted-syntax -- assert generic type of the Class instance
|
||||
(error as UpdateError<string, SchemaLike<string>, SchemaLike<string>, string, string>)
|
||||
.schema.tableSingular,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import { type CreateOrganization, type Organization, Organizations } from '@logto/schemas';
|
||||
import {
|
||||
type CreateOrganization,
|
||||
type Organization,
|
||||
Organizations,
|
||||
type OrganizationKeys,
|
||||
} from '@logto/schemas';
|
||||
import { type OmitAutoSetFields } from '@logto/shared';
|
||||
|
||||
import { type Pagination } from '#src/middleware/koa-pagination.js';
|
||||
|
@ -11,7 +16,7 @@ type PostSchema = Omit<OmitAutoSetFields<CreateOrganization>, 'id'>;
|
|||
type PatchSchema = Partial<Omit<OmitAutoSetFields<Organization>, 'id'>>;
|
||||
|
||||
class OrganizationActions
|
||||
implements SchemaActions<CreateOrganization, Organization, PostSchema, PatchSchema>
|
||||
implements SchemaActions<OrganizationKeys, Organization, PostSchema, PatchSchema>
|
||||
{
|
||||
postGuard = Organizations.createGuard.omit({ id: true, createdAt: true });
|
||||
patchGuard = Organizations.guard.omit({ id: true, createdAt: true }).partial();
|
||||
|
|
|
@ -17,7 +17,7 @@ type Schema = {
|
|||
};
|
||||
|
||||
describe('SchemaRouter', () => {
|
||||
const schema: GeneratedSchema<CreateSchema, Schema> = {
|
||||
const schema: GeneratedSchema<'id', CreateSchema, Schema> = {
|
||||
table: 'test_table',
|
||||
tableSingular: 'test_table',
|
||||
fields: {
|
||||
|
@ -28,7 +28,7 @@ describe('SchemaRouter', () => {
|
|||
guard: z.object({ id: z.string() }),
|
||||
};
|
||||
const entities = [{ id: 'test' }, { id: 'test2' }] as const satisfies readonly Schema[];
|
||||
const actions: SchemaActions<CreateSchema, Schema, CreateSchema, CreateSchema> = {
|
||||
const actions: SchemaActions<'id', Schema, CreateSchema, CreateSchema> = {
|
||||
get: jest.fn().mockResolvedValue([entities.length, entities]),
|
||||
getById: jest.fn(async (id) => {
|
||||
const entity = entities.find((entity) => entity.id === id);
|
||||
|
|
|
@ -10,8 +10,8 @@ import koaPagination, { type Pagination } from '#src/middleware/koa-pagination.j
|
|||
* necessary functions to handle the CRUD operations for a schema.
|
||||
*/
|
||||
export abstract class SchemaActions<
|
||||
CreateSchema extends SchemaLike,
|
||||
Schema extends CreateSchema,
|
||||
Key extends string,
|
||||
Schema extends SchemaLike<Key>,
|
||||
PostSchema extends Partial<Schema>,
|
||||
PatchSchema extends Partial<Schema>,
|
||||
> {
|
||||
|
@ -91,16 +91,17 @@ export abstract class SchemaActions<
|
|||
* @see {@link SchemaActions} for the `actions` configuration.
|
||||
*/
|
||||
export default class SchemaRouter<
|
||||
CreateSchema extends SchemaLike,
|
||||
Schema extends CreateSchema,
|
||||
Key extends string,
|
||||
CreateSchema extends Partial<SchemaLike<Key>>,
|
||||
Schema extends SchemaLike<Key>,
|
||||
PostSchema extends Partial<Schema> = Partial<Schema>,
|
||||
PatchSchema extends Partial<Schema> = Partial<Schema>,
|
||||
StateT = unknown,
|
||||
CustomT extends IRouterParamContext = IRouterParamContext,
|
||||
> extends Router<StateT, CustomT> {
|
||||
constructor(
|
||||
public readonly schema: GeneratedSchema<CreateSchema, Schema>,
|
||||
public readonly actions: SchemaActions<CreateSchema, Schema, PostSchema, PatchSchema>
|
||||
public readonly schema: GeneratedSchema<Key, CreateSchema, Schema>,
|
||||
public readonly actions: SchemaActions<Key, Schema, PostSchema, PatchSchema>
|
||||
) {
|
||||
super({ prefix: '/' + schema.table.replaceAll('_', '-') });
|
||||
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import type { GeneratedSchema, SchemaLike } from '@logto/schemas';
|
||||
|
||||
export const isKeyOf =
|
||||
<CreateSchema extends SchemaLike, Schema extends CreateSchema>({
|
||||
<
|
||||
Key extends string,
|
||||
CreateSchema extends Partial<SchemaLike<Key>>,
|
||||
Schema extends SchemaLike<Key>,
|
||||
>({
|
||||
fieldKeys,
|
||||
}: GeneratedSchema<CreateSchema, Schema>) =>
|
||||
(key: string): key is keyof Schema extends string ? keyof Schema : never =>
|
||||
fieldKeys.includes(key);
|
||||
}: GeneratedSchema<Key, CreateSchema, Schema>) =>
|
||||
(key: string): key is Key =>
|
||||
// eslint-disable-next-line no-restricted-syntax -- the quickest way to check
|
||||
fieldKeys.includes(key as Key);
|
||||
|
|
|
@ -26,17 +26,16 @@ export type Guard<T extends Record<string, unknown>> = ZodObject<
|
|||
>;
|
||||
|
||||
export type GeneratedSchema<
|
||||
CreateSchema extends SchemaLike,
|
||||
Schema extends CreateSchema,
|
||||
> = keyof Schema extends string
|
||||
? Readonly<{
|
||||
table: string;
|
||||
tableSingular: string;
|
||||
fields: {
|
||||
[key in keyof Required<Schema>]: string;
|
||||
};
|
||||
fieldKeys: ReadonlyArray<keyof Schema>;
|
||||
createGuard: CreateGuard<CreateSchema>;
|
||||
guard: Guard<Schema>;
|
||||
}>
|
||||
: never;
|
||||
Key extends string,
|
||||
CreateSchema extends Partial<SchemaLike<Key>>,
|
||||
Schema extends SchemaLike<Key>,
|
||||
> = Readonly<{
|
||||
table: string;
|
||||
tableSingular: string;
|
||||
fields: {
|
||||
[key in Key]: string;
|
||||
};
|
||||
fieldKeys: readonly Key[];
|
||||
createGuard: CreateGuard<CreateSchema>;
|
||||
guard: Guard<Schema>;
|
||||
}>;
|
||||
|
|
|
@ -68,6 +68,10 @@ export const generateSchema = ({ name, comments, fields }: TableWithType) => {
|
|||
),
|
||||
'};',
|
||||
'',
|
||||
`export type ${modelName}Keys = ${fields
|
||||
.map(({ name }) => `'${camelcase(name)}'`)
|
||||
.join(' | ')};`,
|
||||
'',
|
||||
`const createGuard: CreateGuard<${databaseEntryType}> = z.object({`,
|
||||
|
||||
...fields.map(
|
||||
|
@ -122,7 +126,7 @@ export const generateSchema = ({ name, comments, fields }: TableWithType) => {
|
|||
'',
|
||||
`export const ${camelcase(name, {
|
||||
pascalCase: true,
|
||||
})}: GeneratedSchema<${databaseEntryType}, ${modelName}> = Object.freeze({`,
|
||||
})}: GeneratedSchema<${modelName}Keys, ${databaseEntryType}, ${modelName}> = Object.freeze({`,
|
||||
` table: '${name}',`,
|
||||
` tableSingular: '${pluralize(name, 1)}',`,
|
||||
' fields: {',
|
||||
|
|
|
@ -81,7 +81,7 @@ describe('convertToIdentifiers()', () => {
|
|||
fooBar: 'foo_bar',
|
||||
baz: 'baz',
|
||||
};
|
||||
const data: Table = { table, fields };
|
||||
const data: Table<string> = { table, fields };
|
||||
|
||||
it('converts table to correct identifiers', () => {
|
||||
expect(convertToIdentifiers(data)).toEqual({
|
||||
|
|
|
@ -68,16 +68,20 @@ export const convertToPrimitiveOrSql = (
|
|||
throw new Error(`Cannot convert ${key} to primitive`);
|
||||
};
|
||||
|
||||
export const convertToIdentifiers = <T extends Table>({ table, fields }: T, withPrefix = false) => {
|
||||
const fieldsIdentifiers = Object.entries<string>(fields).map<
|
||||
[keyof T['fields'], IdentifierSqlToken]
|
||||
>(([key, value]) => [key, sql.identifier(withPrefix ? [table, value] : [value])]);
|
||||
export const convertToIdentifiers = <Key extends string>(
|
||||
{ table, fields }: Table<Key>,
|
||||
withPrefix = false
|
||||
) => {
|
||||
const fieldsIdentifiers = Object.entries<string>(fields).map<[Key, IdentifierSqlToken]>(
|
||||
// eslint-disable-next-line no-restricted-syntax -- Object.entries can only return string keys
|
||||
([key, value]) => [key as Key, sql.identifier(withPrefix ? [table, value] : [value])]
|
||||
);
|
||||
|
||||
return {
|
||||
table: sql.identifier([table]),
|
||||
// Key value inferred from the original fields directly
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
fields: Object.fromEntries(fieldsIdentifiers) as FieldIdentifiers<keyof T['fields']>,
|
||||
fields: Object.fromEntries(fieldsIdentifiers) as FieldIdentifiers<Key>,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -2,28 +2,19 @@ import type { IdentifierSqlToken } from 'slonik';
|
|||
|
||||
export type SchemaValuePrimitive = string | number | boolean | undefined;
|
||||
export type SchemaValue = SchemaValuePrimitive | Record<string, unknown> | unknown[] | null;
|
||||
export type SchemaLike<Key extends string = string> = {
|
||||
export type SchemaLike<Key extends string> = {
|
||||
[key in Key]: SchemaValue;
|
||||
};
|
||||
|
||||
export type Table = { table: string; fields: Record<string, string> };
|
||||
export type FieldIdentifiers<Key extends string | number | symbol> = {
|
||||
export type Table<Keys extends string> = { table: string; fields: Record<Keys, string> };
|
||||
export type FieldIdentifiers<Key extends string> = {
|
||||
[key in Key]: IdentifierSqlToken;
|
||||
};
|
||||
|
||||
export type OrderDirection = 'asc' | 'desc';
|
||||
|
||||
export type OrderBy<Schema extends SchemaLike> = Partial<Record<keyof Schema, OrderDirection>>;
|
||||
|
||||
export type FindManyData<Schema extends SchemaLike> = {
|
||||
where?: Partial<Schema>;
|
||||
orderBy?: OrderBy<Schema>;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
};
|
||||
|
||||
export type UpdateWhereData<Schema extends SchemaLike> = {
|
||||
set: Partial<Schema>;
|
||||
where: Partial<Schema>;
|
||||
export type UpdateWhereData<SetKey extends string, WhereKey extends string> = {
|
||||
set: Partial<SchemaLike<SetKey>>;
|
||||
where: Partial<SchemaLike<WhereKey>>;
|
||||
jsonbMode: 'replace' | 'merge';
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue