mirror of
https://github.com/logto-io/logto.git
synced 2025-03-17 22:31:28 -05:00
Merge pull request #82 from logto-io/gao-log-3
feat: `POST /applicaiton`
This commit is contained in:
commit
d861b7a623
20 changed files with 250 additions and 84 deletions
87
packages/core/src/database/insert.ts
Normal file
87
packages/core/src/database/insert.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
import assert from 'assert';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { SchemaLike, GeneratedSchema } from '@logto/schemas';
|
||||
import { DatabasePoolType, IdentifierSqlTokenType, sql } from 'slonik';
|
||||
import {
|
||||
conditionalSql,
|
||||
convertToIdentifiers,
|
||||
convertToPrimitive,
|
||||
excludeAutoSetFields,
|
||||
OmitAutoSetFields,
|
||||
} from './utils';
|
||||
|
||||
const setExcluded = (...fields: IdentifierSqlTokenType[]) =>
|
||||
sql.join(
|
||||
fields.map((field) => sql`${field}=excluded.${field}`),
|
||||
sql`, `
|
||||
);
|
||||
|
||||
type OnConflict = {
|
||||
fields: IdentifierSqlTokenType[];
|
||||
setExcludedFields: IdentifierSqlTokenType[];
|
||||
};
|
||||
|
||||
type InsertIntoConfigReturning = {
|
||||
returning: true;
|
||||
onConflict?: OnConflict;
|
||||
};
|
||||
|
||||
type InsertIntoConfig = {
|
||||
returning?: false;
|
||||
onConflict?: OnConflict;
|
||||
};
|
||||
|
||||
interface BuildInsertInto {
|
||||
<Schema extends SchemaLike<string>>(
|
||||
pool: DatabasePoolType,
|
||||
{ fieldKeys, ...rest }: GeneratedSchema<Schema>,
|
||||
config: InsertIntoConfigReturning
|
||||
): (data: OmitAutoSetFields<Schema>) => Promise<Schema>;
|
||||
<Schema extends SchemaLike<string>>(
|
||||
pool: DatabasePoolType,
|
||||
{ fieldKeys, ...rest }: GeneratedSchema<Schema>,
|
||||
config?: InsertIntoConfig
|
||||
): (data: OmitAutoSetFields<Schema>) => Promise<void>;
|
||||
}
|
||||
|
||||
export const buildInsertInto: BuildInsertInto = <Schema extends SchemaLike<string>>(
|
||||
pool: DatabasePoolType,
|
||||
{ fieldKeys, ...rest }: GeneratedSchema<Schema>,
|
||||
config?: InsertIntoConfig | InsertIntoConfigReturning
|
||||
) => {
|
||||
const { table, fields } = convertToIdentifiers(rest);
|
||||
const keys = excludeAutoSetFields(fieldKeys);
|
||||
const returning = Boolean(config?.returning);
|
||||
const onConflict = config?.onConflict;
|
||||
|
||||
return async (data: OmitAutoSetFields<Schema>): Promise<Schema | void> => {
|
||||
const result = await pool.query<Schema>(sql`
|
||||
insert into ${table} (${sql.join(
|
||||
keys.map((key) => fields[key]),
|
||||
sql`, `
|
||||
)})
|
||||
values (${sql.join(
|
||||
keys.map((key) => convertToPrimitive(data[key] ?? null)),
|
||||
sql`, `
|
||||
)})
|
||||
${conditionalSql(returning, () => sql`returning *`)}
|
||||
${conditionalSql(
|
||||
onConflict,
|
||||
({ fields, setExcludedFields }) => sql`
|
||||
on conflict (${sql.join(fields, sql`, `)}) do update
|
||||
set ${setExcluded(...setExcludedFields)}
|
||||
`
|
||||
)}
|
||||
`);
|
||||
|
||||
const {
|
||||
rows: [entry],
|
||||
} = result;
|
||||
|
||||
assert(
|
||||
!returning || entry,
|
||||
new RequestError({ code: 'entity.create_failed', name: rest.tableSingular })
|
||||
);
|
||||
return entry;
|
||||
};
|
||||
};
|
6
packages/core/src/database/types.ts
Normal file
6
packages/core/src/database/types.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { IdentifierSqlTokenType } from 'slonik';
|
||||
|
||||
export type Table = { table: string; fields: Record<string, string> };
|
||||
export type FieldIdentifiers<Key extends string | number | symbol> = {
|
||||
[key in Key]: IdentifierSqlTokenType;
|
||||
};
|
|
@ -1,13 +1,51 @@
|
|||
import { IdentifierSqlTokenType, sql } from 'slonik';
|
||||
import { Falsy, notFalsy } from '@logto/essentials';
|
||||
import { SchemaValuePrimitive, SchemaValue } from '@logto/schemas';
|
||||
import { sql, SqlSqlTokenType } from 'slonik';
|
||||
import { FieldIdentifiers, Table } from './types';
|
||||
|
||||
type Table = { table: string; fields: Record<string, string> };
|
||||
type FieldIdentifiers<Key extends string | number | symbol> = {
|
||||
[key in Key]: IdentifierSqlTokenType;
|
||||
export const conditionalSql = <T>(
|
||||
value: T,
|
||||
buildSql: (value: Exclude<T, Falsy>) => SqlSqlTokenType
|
||||
) => (notFalsy(value) ? buildSql(value) : sql``);
|
||||
|
||||
export const autoSetFields = Object.freeze(['createdAt', 'updatedAt'] as const);
|
||||
// `Except` type will require omit fields to be the key of given type
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export type OmitAutoSetFields<T> = Omit<T, typeof autoSetFields[number]>;
|
||||
export type ExcludeAutoSetFields<T> = Exclude<T, typeof autoSetFields[number]>;
|
||||
export const excludeAutoSetFields = <T extends string>(fields: readonly T[]) =>
|
||||
Object.freeze(
|
||||
fields.filter(
|
||||
(field): field is ExcludeAutoSetFields<T> =>
|
||||
!(autoSetFields as readonly string[]).includes(field)
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Note `undefined` is removed from the acceptable list,
|
||||
* since you should NOT call this function if ignoring the field is the desired behavior.
|
||||
* Calling this function with `null` means an explicit `null` setting in database is expected.
|
||||
* @param value The value to convert.
|
||||
* @returns A primitive that can be saved into database.
|
||||
*/
|
||||
export const convertToPrimitive = (
|
||||
value: NonNullable<SchemaValue> | null
|
||||
): NonNullable<SchemaValuePrimitive> | null => {
|
||||
if (value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new Error(`Cannot convert to primitive from ${typeof value}`);
|
||||
};
|
||||
|
||||
const convertToPrimitive = <T>(value: T) =>
|
||||
value !== null && typeof value === 'object' ? JSON.stringify(value) : value;
|
||||
|
||||
export const convertToIdentifiers = <T extends Table>(
|
||||
{ table, fields }: T,
|
||||
withPrefix = false
|
||||
|
@ -22,24 +60,3 @@ export const convertToIdentifiers = <T extends Table>(
|
|||
{} as FieldIdentifiers<keyof T['fields']>
|
||||
),
|
||||
});
|
||||
|
||||
export const insertInto = <Type, Key extends keyof Type = keyof Type>(
|
||||
table: IdentifierSqlTokenType,
|
||||
fields: FieldIdentifiers<Key>,
|
||||
fieldKeys: readonly Key[],
|
||||
value: { [key in Key]?: Type[key] }
|
||||
) => sql`
|
||||
insert into ${table} (${sql.join(
|
||||
fieldKeys.map((key) => fields[key]),
|
||||
sql`, `
|
||||
)})
|
||||
values (${sql.join(
|
||||
fieldKeys.map((key) => convertToPrimitive(value[key] ?? null)),
|
||||
sql`, `
|
||||
)})`;
|
||||
|
||||
export const setExcluded = (...fields: IdentifierSqlTokenType[]) =>
|
||||
sql.join(
|
||||
fields.map((field) => sql`${field}=excluded.${field}`),
|
||||
sql`, `
|
||||
);
|
||||
|
|
|
@ -10,8 +10,12 @@ export default class RequestError extends Error {
|
|||
data: unknown;
|
||||
|
||||
constructor(input: RequestErrorMetadata | LogtoErrorCode, data?: unknown) {
|
||||
const { code, status = 400 } = typeof input === 'string' ? { code: input } : input;
|
||||
const message = i18next.t<string, LogtoErrorI18nKey>(`errors:${code}`);
|
||||
const {
|
||||
code,
|
||||
status = 400,
|
||||
...interpolation
|
||||
} = typeof input === 'string' ? { code: input } : input;
|
||||
const message = i18next.t<string, LogtoErrorI18nKey>(`errors:${code}`, interpolation);
|
||||
|
||||
super(message);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
} from '@/queries/oidc-model-instance';
|
||||
import { findApplicationById } from '@/queries/application';
|
||||
import { ApplicationDBEntry } from '@logto/schemas';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export default function postgresAdapter(modelName: string): ReturnType<AdapterFactory> {
|
||||
if (modelName === 'Client') {
|
||||
|
@ -32,7 +33,13 @@ export default function postgresAdapter(modelName: string): ReturnType<AdapterFa
|
|||
}
|
||||
|
||||
return {
|
||||
upsert: async (id, payload, expiresIn) => upsertInstance(modelName, id, payload, expiresIn),
|
||||
upsert: async (id, payload, expiresIn) =>
|
||||
upsertInstance({
|
||||
modelName,
|
||||
id,
|
||||
payload,
|
||||
expiresAt: dayjs().add(expiresIn, 'second').unix(),
|
||||
}),
|
||||
find: async (id) => findPayloadById(modelName, id),
|
||||
findByUserCode: async (userCode) => findPayloadByPayloadField(modelName, 'userCode', userCode),
|
||||
findByUid: async (uid) => findPayloadByPayloadField(modelName, 'uid', uid),
|
||||
|
|
6
packages/core/src/oidc/utils.ts
Normal file
6
packages/core/src/oidc/utils.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { OidcClientMetadata } from '@logto/schemas';
|
||||
|
||||
export const generateOidcClientMetadata = (): OidcClientMetadata => ({
|
||||
redirect_uris: [],
|
||||
post_logout_redirect_uris: [],
|
||||
});
|
|
@ -1,3 +1,4 @@
|
|||
import { buildInsertInto } from '@/database/insert';
|
||||
import pool from '@/database/pool';
|
||||
import { convertToIdentifiers } from '@/database/utils';
|
||||
import { ApplicationDBEntry, Applications } from '@logto/schemas';
|
||||
|
@ -11,3 +12,7 @@ export const findApplicationById = async (id: string) =>
|
|||
from ${table}
|
||||
where ${fields.id}=${id}
|
||||
`);
|
||||
|
||||
export const insertApplication = buildInsertInto<ApplicationDBEntry>(pool, Applications, {
|
||||
returning: true,
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { buildInsertInto } from '@/database/insert';
|
||||
import pool from '@/database/pool';
|
||||
import { convertToIdentifiers, insertInto, setExcluded } from '@/database/utils';
|
||||
import { convertToIdentifiers } from '@/database/utils';
|
||||
import { conditional } from '@logto/essentials';
|
||||
import {
|
||||
OidcModelInstanceDBEntry,
|
||||
|
@ -22,30 +23,12 @@ const withConsumed = <T>(data: T, consumedAt?: number): WithConsumed<T> => ({
|
|||
const convertResult = (result: QueryResult | null) =>
|
||||
conditional(result && withConsumed(result.payload, result.consumedAt));
|
||||
|
||||
export const upsertInstance = async (
|
||||
modelName: string,
|
||||
id: string,
|
||||
payload: OidcModelInstancePayload,
|
||||
expiresIn: number
|
||||
) => {
|
||||
await pool.query(
|
||||
sql`
|
||||
${insertInto<OidcModelInstanceDBEntry>(
|
||||
table,
|
||||
fields,
|
||||
['modelName', 'id', 'payload', 'expiresAt'],
|
||||
{
|
||||
modelName,
|
||||
id,
|
||||
payload,
|
||||
expiresAt: dayjs().add(expiresIn, 'second').unix(),
|
||||
}
|
||||
)}
|
||||
on conflict (${fields.modelName}, ${fields.id}) do update
|
||||
set ${setExcluded(fields.payload, fields.expiresAt)}
|
||||
`
|
||||
);
|
||||
};
|
||||
export const upsertInstance = buildInsertInto<OidcModelInstanceDBEntry>(pool, OidcModelInstances, {
|
||||
onConflict: {
|
||||
fields: [fields.modelName, fields.id],
|
||||
setExcludedFields: [fields.payload, fields.expiresAt],
|
||||
},
|
||||
});
|
||||
|
||||
const findByModel = (modelName: string) => sql`
|
||||
select ${fields.payload}, ${fields.consumedAt}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { UserDBEntry, Users } from '@logto/schemas';
|
||||
import { sql } from 'slonik';
|
||||
import pool from '@/database/pool';
|
||||
import { convertToIdentifiers, insertInto } from '@/database/utils';
|
||||
import { convertToIdentifiers } from '@/database/utils';
|
||||
import { buildInsertInto } from '@/database/insert';
|
||||
|
||||
const { table, fields } = convertToIdentifiers(Users);
|
||||
|
||||
|
@ -33,5 +34,4 @@ export const hasUserWithId = async (id: string) =>
|
|||
where ${fields.id}=${id}
|
||||
`);
|
||||
|
||||
export const insertUser = async (user: UserDBEntry) =>
|
||||
pool.query(insertInto(table, fields, Users.fieldKeys, user));
|
||||
export const insertUser = buildInsertInto<UserDBEntry>(pool, Users, { returning: true });
|
||||
|
|
|
@ -2,6 +2,11 @@ import Router from 'koa-router';
|
|||
import { nativeEnum, object, string } from 'zod';
|
||||
import { ApplicationType } from '@logto/schemas';
|
||||
import koaGuard from '@/middleware/koa-guard';
|
||||
import { insertApplication } from '@/queries/application';
|
||||
import { buildIdGenerator } from '@/utils/id';
|
||||
import { generateOidcClientMetadata } from '@/oidc/utils';
|
||||
|
||||
const applicationId = buildIdGenerator(21);
|
||||
|
||||
export default function applicationRoutes<StateT, ContextT>(router: Router<StateT, ContextT>) {
|
||||
router.post(
|
||||
|
@ -15,7 +20,12 @@ export default function applicationRoutes<StateT, ContextT>(router: Router<State
|
|||
async (ctx, next) => {
|
||||
const { name, type } = ctx.guard.body;
|
||||
|
||||
ctx.body = { name, type };
|
||||
ctx.body = await insertApplication({
|
||||
id: applicationId(),
|
||||
type,
|
||||
name,
|
||||
oidcClientMetadata: generateOidcClientMetadata(),
|
||||
});
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
|
|
@ -2,13 +2,13 @@ import Router from 'koa-router';
|
|||
import { object, string } from 'zod';
|
||||
import { encryptPassword } from '@/utils/password';
|
||||
import { hasUser, hasUserWithId, insertUser } from '@/queries/user';
|
||||
import { customAlphabet, nanoid } from 'nanoid';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { PasswordEncryptionMethod } from '@logto/schemas';
|
||||
import koaGuard from '@/middleware/koa-guard';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { buildIdGenerator } from '@/utils/id';
|
||||
|
||||
const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
const userId = customAlphabet(alphabet, 12);
|
||||
const userId = buildIdGenerator(12);
|
||||
|
||||
const generateUserId = async (maxRetries = 500) => {
|
||||
for (let i = 0; i < maxRetries; ++i) {
|
||||
|
@ -48,15 +48,13 @@ export default function userRoutes(router: Router) {
|
|||
passwordEncryptionMethod
|
||||
);
|
||||
|
||||
await insertUser({
|
||||
ctx.body = await insertUser({
|
||||
id,
|
||||
username,
|
||||
passwordEncrypted,
|
||||
passwordEncryptionMethod,
|
||||
passwordEncryptionSalt,
|
||||
});
|
||||
|
||||
ctx.body = { id };
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
|
5
packages/core/src/utils/id.ts
Normal file
5
packages/core/src/utils/id.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { customAlphabet } from 'nanoid';
|
||||
|
||||
export const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
export const buildIdGenerator = (size: number) => customAlphabet(alphabet, size);
|
|
@ -37,6 +37,9 @@ const errors = {
|
|||
swagger: {
|
||||
invalid_zod_type: 'Invalid Zod type, please check route guard config.',
|
||||
},
|
||||
entity: {
|
||||
create_failed: 'Failed to create {{name}}.',
|
||||
},
|
||||
};
|
||||
|
||||
const en = Object.freeze({
|
||||
|
|
|
@ -39,6 +39,9 @@ const errors = {
|
|||
swagger: {
|
||||
invalid_zod_type: '无效的 Zod 类型,请检查路由 guard 配置。',
|
||||
},
|
||||
entity: {
|
||||
create_failed: '创建{{name}}失败。',
|
||||
},
|
||||
};
|
||||
|
||||
const zhCN: typeof en = Object.freeze({
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { LogtoErrorCode } from '@logto/phrases';
|
||||
|
||||
export type RequestErrorMetadata = {
|
||||
export type RequestErrorMetadata = Record<string, unknown> & {
|
||||
code: LogtoErrorCode;
|
||||
status?: number;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
|
||||
import { OidcClientMetadata } from '../foundations';
|
||||
import { OidcClientMetadata, GeneratedSchema } from '../foundations';
|
||||
|
||||
import { ApplicationType } from './custom-types';
|
||||
|
||||
|
@ -12,8 +12,9 @@ export type ApplicationDBEntry = {
|
|||
createdAt: number;
|
||||
};
|
||||
|
||||
export const Applications = Object.freeze({
|
||||
export const Applications: GeneratedSchema<ApplicationDBEntry> = Object.freeze({
|
||||
table: 'applications',
|
||||
tableSingular: 'application',
|
||||
fields: {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
|
@ -22,4 +23,4 @@ export const Applications = Object.freeze({
|
|||
createdAt: 'created_at',
|
||||
},
|
||||
fieldKeys: ['id', 'name', 'type', 'oidcClientMetadata', 'createdAt'],
|
||||
} as const);
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
|
||||
import { OidcModelInstancePayload } from '../foundations';
|
||||
import { OidcModelInstancePayload, GeneratedSchema } from '../foundations';
|
||||
|
||||
export type OidcModelInstanceDBEntry = {
|
||||
modelName: string;
|
||||
|
@ -10,8 +10,9 @@ export type OidcModelInstanceDBEntry = {
|
|||
consumedAt?: number;
|
||||
};
|
||||
|
||||
export const OidcModelInstances = Object.freeze({
|
||||
export const OidcModelInstances: GeneratedSchema<OidcModelInstanceDBEntry> = Object.freeze({
|
||||
table: 'oidc_model_instances',
|
||||
tableSingular: 'oidc_model_instance',
|
||||
fields: {
|
||||
modelName: 'model_name',
|
||||
id: 'id',
|
||||
|
@ -20,4 +21,4 @@ export const OidcModelInstances = Object.freeze({
|
|||
consumedAt: 'consumed_at',
|
||||
},
|
||||
fieldKeys: ['modelName', 'id', 'payload', 'expiresAt', 'consumedAt'],
|
||||
} as const);
|
||||
});
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
|
||||
import { GeneratedSchema } from '../foundations';
|
||||
|
||||
import { PasswordEncryptionMethod } from './custom-types';
|
||||
|
||||
export type UserDBEntry = {
|
||||
|
@ -12,8 +14,9 @@ export type UserDBEntry = {
|
|||
passwordEncryptionSalt?: string;
|
||||
};
|
||||
|
||||
export const Users = Object.freeze({
|
||||
export const Users: GeneratedSchema<UserDBEntry> = Object.freeze({
|
||||
table: 'users',
|
||||
tableSingular: 'user',
|
||||
fields: {
|
||||
id: 'id',
|
||||
username: 'username',
|
||||
|
@ -32,4 +35,4 @@ export const Users = Object.freeze({
|
|||
'passwordEncryptionMethod',
|
||||
'passwordEncryptionSalt',
|
||||
],
|
||||
} as const);
|
||||
});
|
||||
|
|
|
@ -1,5 +1,21 @@
|
|||
export type OidcModelInstancePayload = {
|
||||
[key: string]: unknown;
|
||||
export type SchemaValuePrimitive = string | number | boolean | undefined;
|
||||
export type SchemaValue = SchemaValuePrimitive | Record<string, unknown>;
|
||||
export type SchemaLike<Key extends string> = {
|
||||
[key in Key]: SchemaValue;
|
||||
};
|
||||
|
||||
export type GeneratedSchema<Schema extends SchemaLike<string>> = keyof Schema extends string
|
||||
? Readonly<{
|
||||
table: string;
|
||||
tableSingular: string;
|
||||
fields: {
|
||||
[key in keyof Schema]: string;
|
||||
};
|
||||
fieldKeys: ReadonlyArray<keyof Schema>;
|
||||
}>
|
||||
: never;
|
||||
|
||||
export type OidcModelInstancePayload = Record<string, unknown> & {
|
||||
userCode?: string;
|
||||
uid?: string;
|
||||
grantId?: string;
|
||||
|
|
|
@ -184,6 +184,10 @@ const generate = async () => {
|
|||
}),
|
||||
}));
|
||||
|
||||
if (tableWithTypes.length > 0) {
|
||||
tsTypes.push('GeneratedSchema');
|
||||
}
|
||||
|
||||
const importTsTypes = conditionalString(
|
||||
tsTypes.length > 0 &&
|
||||
[
|
||||
|
@ -211,9 +215,13 @@ const generate = async () => {
|
|||
importTsTypes +
|
||||
importTypes +
|
||||
tableWithTypes
|
||||
.map(({ name, fields }) =>
|
||||
[
|
||||
`export type ${pluralize(camelcase(name, { pascalCase: true }), 1)}DBEntry = {`,
|
||||
.map(({ name, fields }) => {
|
||||
const databaseEntryType = `${pluralize(
|
||||
camelcase(name, { pascalCase: true }),
|
||||
1
|
||||
)}DBEntry`;
|
||||
return [
|
||||
`export type ${databaseEntryType} = {`,
|
||||
...fields.map(
|
||||
({ name, type, isArray, required }) =>
|
||||
` ${camelcase(name)}${conditionalString(
|
||||
|
@ -222,17 +230,20 @@ const generate = async () => {
|
|||
),
|
||||
'};',
|
||||
'',
|
||||
`export const ${camelcase(name, { pascalCase: true })} = Object.freeze({`,
|
||||
`export const ${camelcase(name, {
|
||||
pascalCase: true,
|
||||
})}: GeneratedSchema<${databaseEntryType}> = Object.freeze({`,
|
||||
` table: '${name}',`,
|
||||
` tableSingular: '${pluralize(name, 1)}',`,
|
||||
' fields: {',
|
||||
...fields.map(({ name }) => ` ${camelcase(name)}: '${name}',`),
|
||||
' },',
|
||||
' fieldKeys: [',
|
||||
...fields.map(({ name }) => ` '${camelcase(name)}',`),
|
||||
' ],',
|
||||
'} as const);',
|
||||
].join('\n')
|
||||
)
|
||||
'});',
|
||||
].join('\n');
|
||||
})
|
||||
.join('\n') +
|
||||
'\n';
|
||||
await fs.writeFile(path.join(generatedDirectory, getOutputFileName(file) + '.ts'), content);
|
||||
|
|
Loading…
Add table
Reference in a new issue