@tailor-platform/sdk 2.25.0 → 2.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/cli/commands/tailordb/migrate/bundler.d.mts +4 -1
- package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +6 -0
- package/dist/cli/lib.mjs +1 -1
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/main.mjs +46 -46
- package/dist/cli/main.mjs.map +1 -1
- package/dist/completion/zsh-worker.zsh +1 -1
- package/dist/kysely/index.d.mts +47 -15
- package/dist/kysely/index.mjs +1 -1
- package/dist/kysely/index.mjs.map +1 -1
- package/dist/kysely-type-RDX59E1j.mjs +48 -0
- package/dist/kysely-type-RDX59E1j.mjs.map +1 -0
- package/dist/plugin/builtin/kysely-type/index.d.mts +11 -0
- package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
- package/dist/{register-ts-hook-DPAW0Z4M.mjs → register-ts-hook-BuwkLuxC.mjs} +87 -86
- package/dist/register-ts-hook-BuwkLuxC.mjs.map +1 -0
- package/dist/runtime/index.d.mts +8 -0
- package/dist/vitest/index.d.mts +3 -2
- package/dist/vitest/index.mjs +1 -1
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/mocks/tailordb-pglite.d.mts +1 -0
- package/dist/vitest/pglite-kysely.d.mts +14 -1
- package/dist/vitest/pglite-temporal.d.mts +9 -0
- package/docs/services/resolver.md +1 -1
- package/docs/services/tailordb-migration.md +4 -2
- package/docs/testing.md +2 -1
- package/package.json +1 -1
- package/dist/kysely-type-C-iyFFH7.mjs +0 -43
- package/dist/kysely-type-C-iyFFH7.mjs.map +0 -1
- package/dist/register-ts-hook-DPAW0Z4M.mjs.map +0 -1
- package/dist/tailordb-ddl-DqInYupv.mjs +0 -7
- package/dist/tailordb-ddl-DqInYupv.mjs.map +0 -1
|
@@ -62,6 +62,7 @@ export declare function mockTailordbWithPGlite(options: MockTailordbPGliteOption
|
|
|
62
62
|
/** The mock `tailordb.Client` constructor (`vi.fn`). */
|
|
63
63
|
Client: import("vitest").Mock<(this: any, config?: {
|
|
64
64
|
namespace?: string;
|
|
65
|
+
temporal?: boolean;
|
|
65
66
|
}) => void>;
|
|
66
67
|
/**
|
|
67
68
|
* All queries executed on the PGlite instances, in order, with the
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PGliteField } from "./pglite-temporal.mjs";
|
|
1
2
|
import { ColumnType, Kysely } from "kysely";
|
|
2
3
|
//#region src/vitest/pglite-kysely.d.ts
|
|
3
4
|
/** Result of a {@link PGliteClient.query} call. */
|
|
@@ -10,6 +11,8 @@ export interface PGliteQueryResult {
|
|
|
10
11
|
command?: string;
|
|
11
12
|
/** Row count reported alongside the command tag. */
|
|
12
13
|
rowCount?: number;
|
|
14
|
+
/** Name and Postgres type OID of each returned column. */
|
|
15
|
+
fields?: PGliteField[];
|
|
13
16
|
}
|
|
14
17
|
/**
|
|
15
18
|
* The subset of a `@electric-sql/pglite` `PGlite` instance used by
|
|
@@ -43,6 +46,15 @@ type UnmigratedColumn<C> = C extends ColumnType<infer S, infer I, infer U> ? Col
|
|
|
43
46
|
* ```
|
|
44
47
|
*/
|
|
45
48
|
export type Unmigrated<DB> = { [T in keyof DB]: { [C in keyof DB[T]]: UnmigratedColumn<DB[T][C]>; }; };
|
|
49
|
+
/** Options for {@link createKyselyPGlite}. */
|
|
50
|
+
export interface CreateKyselyPGliteOptions {
|
|
51
|
+
/**
|
|
52
|
+
* Read date/datetime/time columns back as `Temporal.PlainDate`/`Temporal.Instant`/
|
|
53
|
+
* `Temporal.PlainTime`, as a migration whose `db.ts` uses Temporal column types sees
|
|
54
|
+
* them on the platform. Defaults to `false`.
|
|
55
|
+
*/
|
|
56
|
+
temporal?: boolean;
|
|
57
|
+
}
|
|
46
58
|
/**
|
|
47
59
|
* Create a Kysely instance backed by a PGlite in-memory Postgres, for
|
|
48
60
|
* executing a migration script's queries against real data in tests.
|
|
@@ -54,6 +66,7 @@ export type Unmigrated<DB> = { [T in keyof DB]: { [C in keyof DB[T]]: Unmigrated
|
|
|
54
66
|
* statement passing here can still be rejected by the platform; keep a
|
|
55
67
|
* statement-level test (see `createKyselyMock`) alongside.
|
|
56
68
|
* @param client - A `PGlite` instance from `@electric-sql/pglite`
|
|
69
|
+
* @param options - Adapter options
|
|
57
70
|
* @returns A Kysely instance that executes queries on the client and closes it on `destroy()`
|
|
58
71
|
* @example
|
|
59
72
|
* ```typescript
|
|
@@ -68,5 +81,5 @@ export type Unmigrated<DB> = { [T in keyof DB]: { [C in keyof DB[T]]: Unmigrated
|
|
|
68
81
|
* await db.transaction().execute((trx) => main(trx));
|
|
69
82
|
* ```
|
|
70
83
|
*/
|
|
71
|
-
export declare function createKyselyPGlite<DB = Record<string, never>>(client: PGliteClient): Kysely<DB>;
|
|
84
|
+
export declare function createKyselyPGlite<DB = Record<string, never>>(client: PGliteClient, options?: CreateKyselyPGliteOptions): Kysely<DB>;
|
|
72
85
|
//#endregion
|
|
@@ -336,7 +336,7 @@ Define actual resolver logic in the `body` function. Function arguments include:
|
|
|
336
336
|
|
|
337
337
|
### Using Kysely for Database Access
|
|
338
338
|
|
|
339
|
-
If you're generating Kysely types with `kyselyTypePlugin`, you can use `getDB` to execute typed queries:
|
|
339
|
+
If you're generating Kysely types with `kyselyTypePlugin`, you can use `getDB` to execute typed queries. By default, `date`/`datetime`/`time` columns come back as `Date`/`string`; configure `kyselyTypePlugin({ temporal: true })` to get `Temporal.PlainDate`/`Temporal.Instant` for date/datetime fields and `Temporal.PlainTime` for top-level time fields. Time fields inside nested objects remain strings.
|
|
340
340
|
|
|
341
341
|
```typescript
|
|
342
342
|
import { getDB } from "../generated/tailordb";
|
|
@@ -92,7 +92,7 @@ A typical change cycle:
|
|
|
92
92
|
DB types: ./migrations/0001/db.ts
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
If `EDITOR` or `VISUAL` is set, `migrate.ts` opens automatically.
|
|
95
|
+
If `EDITOR` or `VISUAL` is set, `migrate.ts` opens automatically unless JSON output is enabled.
|
|
96
96
|
|
|
97
97
|
3. **Edit `migrate.ts`** to populate data for the new required field:
|
|
98
98
|
|
|
@@ -282,7 +282,7 @@ export default defineConfig({
|
|
|
282
282
|
|
|
283
283
|
### Migration file format compatibility
|
|
284
284
|
|
|
285
|
-
Migration files are versioned independently of the SDK package. This SDK writes format version `
|
|
285
|
+
Migration files are versioned independently of the SDK package. This SDK writes format version `7` and reads versions `1` through `7`. It normalizes supported older formats in memory; it never rewrites applied migration files on disk. Format version `6` records renames of members inside nested fields (`memberRenames`); older SDK versions refuse to read it rather than deploying such a migration without the copy step. Format version `7` records whether a migration's `db.ts` uses Temporal column types (`temporal`); older SDK versions refuse to read it rather than running the script with `Date` values.
|
|
286
286
|
|
|
287
287
|
Supported histories also preserve the behavior of field hooks and validators saved by older SDKs, including access to the record and boolean validators with a separate error message. Legacy update hooks retain existing values for omitted fields; explicitly supplied values, including `null`, take precedence. This applies to both snapshots and diffs, including nested fields. Your existing migration files can remain as generated.
|
|
288
288
|
|
|
@@ -898,6 +898,8 @@ describe("0005 add required email", () => {
|
|
|
898
898
|
```
|
|
899
899
|
|
|
900
900
|
Pass nested field values as JavaScript objects or arrays of objects, without `JSON.stringify`.
|
|
901
|
+
|
|
902
|
+
When the migration was generated with `kyselyTypePlugin({ temporal: true })`, its `db.ts` types date and datetime fields as `Temporal.PlainDate` and `Temporal.Instant`, and top-level time fields as `Temporal.PlainTime`; nested time fields remain strings. Deploy runs the script with those values. Create the instance with `createKyselyPGlite<Unmigrated<Database>>(pglite, { temporal: true })` so PGlite reads top-level temporal columns back the same way; the scaffold adds the option for such migrations. Run the test in the [`tailor-runtime` Vitest environment](../testing.md#temporal), which provides `Temporal`. A migration generated before `temporal` was enabled still runs with `Date` values on deploy, so its test leaves the option out.
|
|
901
903
|
Generated migration types use `Record<string, unknown>` for each nested object so scripts can
|
|
902
904
|
work with both old and new members during a migration; narrow member values before using them.
|
|
903
905
|
|
package/docs/testing.md
CHANGED
|
@@ -211,7 +211,7 @@ What the script cannot reproduce:
|
|
|
211
211
|
|
|
212
212
|
- Hooks, validations, and permissions do not run. A required field whose value only its own field-level create hook supplies is created nullable, so inserts that omit it succeed; give it a `.default()` if the test reads it back. A field filled by a table-level hook stays `NOT NULL`, as its Kysely type still requires it on insert.
|
|
213
213
|
- Serial formats are reproduced for a single `%d`, `%x`, or `%X` specifier with an optional zero-padded width; an octal `%o` format fails generation with an error naming the field.
|
|
214
|
-
- A datetime inside a nested object reads back as a string from `jsonb`, not a `Date
|
|
214
|
+
- A date or datetime inside a nested object reads back as a string from `jsonb`, not a `Date`, or a `Temporal.PlainDate` or `Temporal.Instant` under `kyselyTypePlugin({ temporal: true })`, although the platform returns those types.
|
|
215
215
|
- On a persistent PGlite (`dataDir`), tables created by an earlier run are kept as they were; drop them or start from an empty directory after changing a table definition.
|
|
216
216
|
|
|
217
217
|
To hand-write DDL instead — for a table not in the schema, or to add a constraint — run your own statements after the script, or without it.
|
|
@@ -219,6 +219,7 @@ To hand-write DDL instead — for a table not in the schema, or to add a constra
|
|
|
219
219
|
- The PGlite instance is yours: the mock never closes it, so close it in `afterAll`. Reuse one instance across a suite — creating one per test is slow.
|
|
220
220
|
- Pass the same instance under several namespaces to drive them against one shared database. Two namespaces with a same-named table cannot share one instance, because the second script leaves the first table as it is.
|
|
221
221
|
- Seed through `getDB` itself. When a column type rejects a value that only the test must stage, use `createKyselyPGlite<Unmigrated<...>>(pglite)` instead — see [Testing Migrations Locally](./services/tailordb-migration.md#testing-migrations-locally). This only affects test setup; it cannot supply a `.serial()` value for an insert issued by the code under test.
|
|
222
|
+
- With `kyselyTypePlugin({ temporal: true })`, the generated `getDB` reads top-level date, datetime, and time columns back from PGlite as `Temporal.PlainDate`, `Temporal.Instant`, and `Temporal.PlainTime`, as it does on the platform. Values inside nested objects stay strings in PGlite (see above); on the platform, nested date/datetime fields are Temporal values while nested time fields remain strings.
|
|
222
223
|
- Transactions on a shared instance are serialized: while one is open, queries from other `getDB` instances wait. Do not use `test.concurrent` with a shared instance, and do not query the same instance through a second `getDB` from inside a transaction — that waits on itself.
|
|
223
224
|
- PGlite runs full PostgreSQL while TailorDB supports a subset of it, and TailorDB hooks, validations, and permissions do not run here — a test passing on PGlite can still behave differently on the platform. Keep [`mockTailordb`](#tailordb-mock) or [`createKyselyMock`](#kysely-layer-mock-createkyselymock) tests for query shape and error paths, and E2E tests for platform behavior.
|
|
224
225
|
|
package/package.json
CHANGED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import{t as e}from"./multiline-EyzjEwn9.mjs";import{n as t,r as n,t as r}from"./tailordb-ddl-DqInYupv.mjs";import{resolve as i}from"pathe";function emptyUsedUtilityTypes(){return{Timestamp:!1,Serial:!1,ObjectColumnType:!1,ArrayColumnType:!1}}function mergeUsedUtilityTypes(e,t){return{Timestamp:e.Timestamp||t.Timestamp,Serial:e.Serial||t.Serial,ObjectColumnType:e.ObjectColumnType||t.ObjectColumnType,ArrayColumnType:e.ArrayColumnType||t.ArrayColumnType}}function getEnumType(e){let t=e.allowedValues;return t&&Array.isArray(t)?t.map(e=>`"${typeof e==`string`?e:e.value}"`).join(` | `):`string`}function getNestedType(e){let t=e.fields;if(!t||typeof t!=`object`)return{type:`string`,usedUtilityTypes:emptyUsedUtilityTypes()};let n=Object.entries(t).map(([e,t])=>{let n=generateFieldType(t);return{fieldType:`${e}${t.required===!0?``:`?`}: ${n.type}`,usedUtilityTypes:n.usedUtilityTypes}}),r=n.reduce((e,t)=>mergeUsedUtilityTypes(e,t.usedUtilityTypes),emptyUsedUtilityTypes()),i=n.map(e=>e.fieldType),a=`{\n ${i.join(`;
|
|
2
|
-
`)}${i.length>0?`;`:``}\n}`,o=Object.values(t).some(e=>e.required!==!0),s=Object.values(t).some(e=>e.hooks?.create||e.default!==void 0||e.optionalOnCreate===!0);return r.Timestamp||o||s?{type:`ObjectColumnType<${a}>`,usedUtilityTypes:{...r,ObjectColumnType:!0}}:{type:a,usedUtilityTypes:r}}function getBaseType(e){let t=e.type,r=emptyUsedUtilityTypes();if(t===`enum`)return{type:getEnumType(e),usedUtilityTypes:r};if(t===`nested`)return getNestedType(e);let i=n(t);return r.Timestamp=i===`Timestamp`,{type:i,usedUtilityTypes:r}}function generateFieldType(e){let n=getBaseType(e),r={...n.usedUtilityTypes},i=e.array===!0,a=e.required!==!0,o=t.has(n.type),s=n.type;return i&&(o||s.startsWith(`ObjectColumnType<`)?(s=`ArrayColumnType<${n.type}>`,r.ArrayColumnType=!0):s=e.type===`enum`?`(${n.type})[]`:`${n.type}[]`),a&&(s=`${s} | null`),e.serial&&(r.Serial=!0,s=`Serial<${s}>`),(e.hooks?.create||e.default!==void 0||e.optionalOnCreate===!0)&&(s=`Generated<${s}>`),{type:s,usedUtilityTypes:r}}function generateTableInterface(t,n){let r=Object.entries(n).filter(([e])=>e!==`id`).map(([e,t])=>({fieldName:e,...generateFieldType(t)})),i=[`id: Generated<string>;`,...r.map(e=>`${e.fieldName}: ${e.type};`)],a=r.reduce((e,t)=>mergeUsedUtilityTypes(e,t.usedUtilityTypes),emptyUsedUtilityTypes());return{typeDef:e`
|
|
3
|
-
${t}: {
|
|
4
|
-
${i.join(`
|
|
5
|
-
`)}
|
|
6
|
-
}
|
|
7
|
-
`,usedUtilityTypes:a}}function processKyselyFields(e,t){let n=generateTableInterface(e,t);return{name:e,typeDef:n.typeDef,usedUtilityTypes:n.usedUtilityTypes}}async function processKyselyType(e){return processKyselyFields(e.name,Object.fromEntries(Object.entries(e.fields).map(([e,t])=>[e,t.config])))}function generateUnifiedKyselyTypes(t){if(t.length===0)return``;let n=t.flatMap(e=>e.types).reduce((e,t)=>mergeUsedUtilityTypes(e,t.usedUtilityTypes),emptyUsedUtilityTypes()),r=[`type Generated`];return n.Timestamp&&r.push(`type Timestamp`),n.ObjectColumnType&&r.push(`type ObjectColumnType`),n.ArrayColumnType&&r.push(`type ArrayColumnType`),n.Serial&&r.push(`type Serial`),[e`
|
|
8
|
-
import {
|
|
9
|
-
createGetDB,
|
|
10
|
-
${r.join(`,
|
|
11
|
-
`)},
|
|
12
|
-
type NamespaceDB,
|
|
13
|
-
type NamespaceInsertable,
|
|
14
|
-
type NamespaceSelectable,
|
|
15
|
-
type NamespaceTable,
|
|
16
|
-
type NamespaceTableName,
|
|
17
|
-
type NamespaceTransaction,
|
|
18
|
-
type NamespaceUpdateable,
|
|
19
|
-
} from "@tailor-platform/sdk/kysely";
|
|
20
|
-
`,`export interface Namespace {\n${t.map(({namespace:e,types:t})=>` "${e}": {\n${t.map(e=>e.typeDef.split(`
|
|
21
|
-
`).map(e=>e.trim()?` ${e}`:``).join(`
|
|
22
|
-
`)).join(`
|
|
23
|
-
|
|
24
|
-
`)}\n }`).join(`,
|
|
25
|
-
`)}\n}`,e`
|
|
26
|
-
export const getDB = createGetDB<Namespace>();
|
|
27
|
-
|
|
28
|
-
export type DB<N extends keyof Namespace = keyof Namespace> = NamespaceDB<Namespace, N>;
|
|
29
|
-
`,e`
|
|
30
|
-
export type Transaction<K extends keyof Namespace | DB = keyof Namespace> =
|
|
31
|
-
NamespaceTransaction<Namespace, K>;
|
|
32
|
-
|
|
33
|
-
type TableName = NamespaceTableName<Namespace>;
|
|
34
|
-
export type Table<T extends TableName> = NamespaceTable<Namespace, T>;
|
|
35
|
-
|
|
36
|
-
export type Insertable<T extends TableName> = NamespaceInsertable<Namespace, T>;
|
|
37
|
-
export type Selectable<T extends TableName> = NamespaceSelectable<Namespace, T>;
|
|
38
|
-
export type Updateable<T extends TableName> = NamespaceUpdateable<Namespace, T>;
|
|
39
|
-
`].join(`
|
|
40
|
-
|
|
41
|
-
`)+`
|
|
42
|
-
`}function toDDLTables(e){return Object.values(e).map(e=>({name:e.name,fields:Object.fromEntries(Object.entries(e.fields).map(([e,t])=>[e,t.config])),indexes:e.indexes}))}function generatePGliteSchemaModule(e){return r(e.map(({namespace:e,tables:t})=>({namespace:e,tables:toDDLTables(t)})),{generatedBy:`the kysely-type plugin`})}const a=`@tailor-platform/kysely-type`,o=`./generated/tailordb.ts`;function kyselyTypePlugin(e){return{id:a,description:`Generates Kysely type definitions for TailorDB tables`,pluginConfig:e,async onTailorDBReady(e){let{distPath:t,pgliteSchemaPath:n}=e.pluginConfig;if(n&&i(t)===i(n))throw Error(`distPath and pgliteSchemaPath must resolve to different files.`);let r=[];for(let t of e.tailordb){let e=[];for(let n of Object.values(t.tables)){let t=await processKyselyType(n);e.push(t)}e.length!==0&&r.push({namespace:t.namespace,types:e})}let a=[];if(r.length>0){let t=generateUnifiedKyselyTypes(r);a.push({path:e.pluginConfig.distPath,content:t}),e.pluginConfig.pgliteSchemaPath&&a.push({path:e.pluginConfig.pgliteSchemaPath,content:generatePGliteSchemaModule(e.tailordb.filter(e=>Object.keys(e.tables).length>0))})}return{files:a}}}}export{processKyselyFields as a,generateUnifiedKyselyTypes as i,a as n,kyselyTypePlugin as r,o as t};
|
|
43
|
-
//# sourceMappingURL=kysely-type-C-iyFFH7.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"kysely-type-C-iyFFH7.mjs","names":["multiline","COLUMN_TYPE_ALIASES","mapFieldTypeToColumnType","generatePgliteSchemaModule","a","b","allowedValues","fieldConfig","v","fields","fieldResults","fieldName","config","result","aggregatedUtilityTypes","acc","fieldTypes","r","obj","hasOptionalFields","hasGeneratedFields","fieldType","usedUtilityTypes","type","baseTypeResult","isArray","isNullable","isColumnTypeBase","finalType","fieldLines","name","parsedField","namespaceData","globalUsedUtilityTypes","ns","utilityTypeImports","namespace","types","line","tables","field","namespaces","KyselyGeneratorID","DEFAULT_KYSELY_TYPES_DIST_PATH","options","ctx","pgliteSchemaPath","resolve","distPath","allNamespaceData","typeMetadataList","metadata","files","content"],"sources":["../src/plugin/builtin/kysely-type/type-processor.ts","../src/plugin/builtin/kysely-type/pglite-schema.ts","../src/plugin/builtin/kysely-type/index.ts"],"sourcesContent":["import { COLUMN_TYPE_ALIASES, mapFieldTypeToColumnType } from \"#/utils/field-column-type\";\nimport multiline from \"#/utils/multiline\";\nimport {\n type KyselyFieldConfig,\n type KyselyNamespaceMetadata,\n type KyselyTypeMetadata,\n type UsedUtilityTypes,\n} from \"./types\";\nimport type { TailorDBType } from \"#/parser/service/tailordb/types\";\n\ntype FieldTypeResult = {\n type: string;\n usedUtilityTypes: UsedUtilityTypes;\n};\n\nfunction emptyUsedUtilityTypes(): UsedUtilityTypes {\n return { Timestamp: false, Serial: false, ObjectColumnType: false, ArrayColumnType: false };\n}\n\nfunction mergeUsedUtilityTypes(a: UsedUtilityTypes, b: UsedUtilityTypes): UsedUtilityTypes {\n return {\n Timestamp: a.Timestamp || b.Timestamp,\n Serial: a.Serial || b.Serial,\n ObjectColumnType: a.ObjectColumnType || b.ObjectColumnType,\n ArrayColumnType: a.ArrayColumnType || b.ArrayColumnType,\n };\n}\n\n/**\n * Get the enum type definition.\n * @param fieldConfig - The field configuration\n * @returns The enum type as a string union\n */\nfunction getEnumType(fieldConfig: KyselyFieldConfig): string {\n const allowedValues = fieldConfig.allowedValues;\n\n if (allowedValues && Array.isArray(allowedValues)) {\n return allowedValues\n .map((v: string | { value: string }) => {\n const value = typeof v === \"string\" ? v : v.value;\n return `\"${value}\"`;\n })\n .join(\" | \");\n }\n return \"string\";\n}\n\n/**\n * Get the nested object type definition.\n * @param fieldConfig - The field configuration\n * @returns The nested type with used utility types\n */\nfunction getNestedType(fieldConfig: KyselyFieldConfig): FieldTypeResult {\n const fields = fieldConfig.fields;\n if (!fields || typeof fields !== \"object\") {\n return {\n type: \"string\",\n usedUtilityTypes: emptyUsedUtilityTypes(),\n };\n }\n\n const fieldResults = Object.entries(fields).map(([fieldName, config]) => {\n const result = generateFieldType(config);\n const optional = config.required !== true ? \"?\" : \"\";\n return {\n fieldType: `${fieldName}${optional}: ${result.type}`,\n usedUtilityTypes: result.usedUtilityTypes,\n };\n });\n\n const aggregatedUtilityTypes = fieldResults.reduce(\n (acc, result) => mergeUsedUtilityTypes(acc, result.usedUtilityTypes),\n emptyUsedUtilityTypes(),\n );\n\n const fieldTypes = fieldResults.map((r) => r.fieldType);\n const obj = `{\\n ${fieldTypes.join(\";\\n \")}${fieldTypes.length > 0 ? \";\" : \"\"}\\n}`;\n\n const hasOptionalFields = Object.values(fields).some((config) => config.required !== true);\n const hasGeneratedFields = Object.values(fields).some(\n (config) =>\n config.hooks?.create || config.default !== undefined || config.optionalOnCreate === true,\n );\n if (aggregatedUtilityTypes.Timestamp || hasOptionalFields || hasGeneratedFields) {\n return {\n type: `ObjectColumnType<${obj}>`,\n usedUtilityTypes: { ...aggregatedUtilityTypes, ObjectColumnType: true },\n };\n }\n return { type: obj, usedUtilityTypes: aggregatedUtilityTypes };\n}\n\n/**\n * Get the base Kysely type for a field (without array/null modifiers).\n * @param fieldConfig - The field configuration\n * @returns The base type with used utility types\n */\nfunction getBaseType(fieldConfig: KyselyFieldConfig): FieldTypeResult {\n const fieldType = fieldConfig.type;\n const usedUtilityTypes = emptyUsedUtilityTypes();\n\n if (fieldType === \"enum\") {\n return { type: getEnumType(fieldConfig), usedUtilityTypes };\n }\n if (fieldType === \"nested\") {\n return getNestedType(fieldConfig);\n }\n\n const type = mapFieldTypeToColumnType(fieldType);\n usedUtilityTypes.Timestamp = type === \"Timestamp\";\n\n return { type, usedUtilityTypes };\n}\n\n/**\n * Generate the complete field type including array and null modifiers.\n * @param fieldConfig - The field configuration\n * @returns The complete field type with used utility types\n */\nfunction generateFieldType(fieldConfig: KyselyFieldConfig): FieldTypeResult {\n const baseTypeResult = getBaseType(fieldConfig);\n const usedUtilityTypes = { ...baseTypeResult.usedUtilityTypes };\n\n const isArray = fieldConfig.array === true;\n const isNullable = fieldConfig.required !== true;\n\n // A ColumnType-shaped alias and ObjectColumnType cannot be wrapped with [] for\n // arrays, because Kysely only resolves ColumnType at the top-level table\n // property. Use ArrayColumnType to keep the ColumnType at the top level.\n const isColumnTypeBase = COLUMN_TYPE_ALIASES.has(baseTypeResult.type);\n\n let finalType = baseTypeResult.type;\n if (isArray) {\n if (isColumnTypeBase || finalType.startsWith(\"ObjectColumnType<\")) {\n finalType = `ArrayColumnType<${baseTypeResult.type}>`;\n usedUtilityTypes.ArrayColumnType = true;\n } else {\n const needsParens = fieldConfig.type === \"enum\";\n finalType = needsParens ? `(${baseTypeResult.type})[]` : `${baseTypeResult.type}[]`;\n }\n }\n if (isNullable) {\n finalType = `${finalType} | null`;\n }\n\n if (fieldConfig.serial) {\n usedUtilityTypes.Serial = true;\n finalType = `Serial<${finalType}>`;\n }\n if (\n fieldConfig.hooks?.create ||\n fieldConfig.default !== undefined ||\n fieldConfig.optionalOnCreate === true\n ) {\n finalType = `Generated<${finalType}>`;\n }\n\n return { type: finalType, usedUtilityTypes };\n}\n\n/**\n * Generate the table interface.\n * @param name - Table name\n * @param fields - Field configurations keyed by field name\n * @returns The type definition and used utility types\n */\nfunction generateTableInterface(\n name: string,\n fields: Record<string, KyselyFieldConfig>,\n): {\n typeDef: string;\n usedUtilityTypes: UsedUtilityTypes;\n} {\n const fieldEntries = Object.entries(fields).filter(([fieldName]) => fieldName !== \"id\");\n\n const fieldResults = fieldEntries.map(([fieldName, fieldConfig]) => ({\n fieldName,\n ...generateFieldType(fieldConfig),\n }));\n\n const fieldLines = [\n \"id: Generated<string>;\",\n ...fieldResults.map((result) => `${result.fieldName}: ${result.type};`),\n ];\n\n const aggregatedUtilityTypes = fieldResults.reduce(\n (acc, result) => mergeUsedUtilityTypes(acc, result.usedUtilityTypes),\n emptyUsedUtilityTypes(),\n );\n\n const typeDef = multiline /* ts */ `\n ${name}: {\n ${fieldLines.join(\"\\n\")}\n }\n `;\n\n return { typeDef, usedUtilityTypes: aggregatedUtilityTypes };\n}\n\n/**\n * Generate KyselyTypeMetadata from field configurations.\n * @param name - Table name\n * @param fields - Field configurations keyed by field name\n * @returns Generated Kysely type metadata\n */\nexport function processKyselyFields(\n name: string,\n fields: Record<string, KyselyFieldConfig>,\n): KyselyTypeMetadata {\n const result = generateTableInterface(name, fields);\n\n return {\n name,\n typeDef: result.typeDef,\n usedUtilityTypes: result.usedUtilityTypes,\n };\n}\n\n/**\n * Convert a TailorDBType into KyselyTypeMetadata.\n * @param type - Parsed TailorDB table\n * @returns Generated Kysely type metadata\n */\nexport async function processKyselyType(type: TailorDBType): Promise<KyselyTypeMetadata> {\n return processKyselyFields(\n type.name,\n Object.fromEntries(\n Object.entries(type.fields).map(([fieldName, parsedField]) => [\n fieldName,\n parsedField.config,\n ]),\n ),\n );\n}\n\n/**\n * Generate unified types file from multiple namespaces.\n * @param namespaceData - Namespace metadata\n * @returns Generated types file contents\n */\nexport function generateUnifiedKyselyTypes(namespaceData: KyselyNamespaceMetadata[]): string {\n if (namespaceData.length === 0) {\n return \"\";\n }\n\n // Aggregate used utility types from all namespaces\n const globalUsedUtilityTypes = namespaceData\n .flatMap((ns) => ns.types)\n .reduce(\n (acc, type) => mergeUsedUtilityTypes(acc, type.usedUtilityTypes),\n emptyUsedUtilityTypes(),\n );\n\n const utilityTypeImports: string[] = [\"type Generated\"];\n if (globalUsedUtilityTypes.Timestamp) {\n utilityTypeImports.push(\"type Timestamp\");\n }\n if (globalUsedUtilityTypes.ObjectColumnType) {\n utilityTypeImports.push(\"type ObjectColumnType\");\n }\n if (globalUsedUtilityTypes.ArrayColumnType) {\n utilityTypeImports.push(\"type ArrayColumnType\");\n }\n if (globalUsedUtilityTypes.Serial) {\n utilityTypeImports.push(\"type Serial\");\n }\n\n const importsSection = multiline /* ts */ `\n import {\n createGetDB,\n ${utilityTypeImports.join(\",\\n\")},\n type NamespaceDB,\n type NamespaceInsertable,\n type NamespaceSelectable,\n type NamespaceTable,\n type NamespaceTableName,\n type NamespaceTransaction,\n type NamespaceUpdateable,\n } from \"@tailor-platform/sdk/kysely\";\n `;\n\n // Generate Namespace interface with multiple namespaces\n const namespaceInterfaces = namespaceData\n .map(({ namespace, types }) => {\n const typeDefsWithIndent = types\n .map((type) => {\n return type.typeDef\n .split(\"\\n\")\n .map((line) => (line.trim() ? ` ${line}` : \"\"))\n .join(\"\\n\");\n })\n .join(\"\\n\\n\");\n\n return ` \"${namespace}\": {\\n${typeDefsWithIndent}\\n }`;\n })\n .join(\",\\n\");\n\n const namespaceInterface = `export interface Namespace {\\n${namespaceInterfaces}\\n}`;\n\n const getDBFunction = multiline /* ts */ `\n export const getDB = createGetDB<Namespace>();\n\n export type DB<N extends keyof Namespace = keyof Namespace> = NamespaceDB<Namespace, N>;\n `;\n\n const utilityTypeExports = multiline /* ts */ `\n export type Transaction<K extends keyof Namespace | DB = keyof Namespace> =\n NamespaceTransaction<Namespace, K>;\n\n type TableName = NamespaceTableName<Namespace>;\n export type Table<T extends TableName> = NamespaceTable<Namespace, T>;\n\n export type Insertable<T extends TableName> = NamespaceInsertable<Namespace, T>;\n export type Selectable<T extends TableName> = NamespaceSelectable<Namespace, T>;\n export type Updateable<T extends TableName> = NamespaceUpdateable<Namespace, T>;\n `;\n\n return (\n [importsSection, namespaceInterface, getDBFunction, utilityTypeExports].join(\"\\n\\n\") + \"\\n\"\n );\n}\n","import { generatePgliteSchemaModule, type DDLTableConfig } from \"#/utils/tailordb-ddl\";\nimport type { TailorDBType } from \"#/parser/service/tailordb/types\";\n\n/** Tables of one namespace, as the schema module groups them. */\nexport interface PGliteSchemaNamespace {\n namespace: string;\n tables: Record<string, TailorDBType>;\n}\n\n/**\n * Reduce parsed tables to what the DDL generator reads.\n * @param tables - Parsed TailorDB tables keyed by name\n * @returns DDL table configs in the same order\n */\nexport function toDDLTables(tables: Record<string, TailorDBType>): DDLTableConfig[] {\n return Object.values(tables).map((type) => ({\n name: type.name,\n fields: Object.fromEntries(\n Object.entries(type.fields).map(([fieldName, field]) => [fieldName, field.config]),\n ),\n indexes: type.indexes,\n }));\n}\n\n/**\n * Render the module that exports each namespace's `CREATE TABLE` script for\n * `pglite.exec()`.\n * @param namespaces - Namespaces with their parsed tables\n * @returns TypeScript source of the schema module\n */\nexport function generatePGliteSchemaModule(namespaces: readonly PGliteSchemaNamespace[]): string {\n return generatePgliteSchemaModule(\n namespaces.map(({ namespace, tables }) => ({ namespace, tables: toDDLTables(tables) })),\n { generatedBy: \"the kysely-type plugin\" },\n );\n}\n","import { resolve } from \"pathe\";\nimport { generatePGliteSchemaModule } from \"./pglite-schema\";\nimport { processKyselyType, generateUnifiedKyselyTypes } from \"./type-processor\";\nimport type { Plugin, GeneratorResult, TailorDBReadyContext } from \"#/plugin/types\";\nimport type { KyselyTypeMetadata, KyselyNamespaceMetadata } from \"./types\";\n\n/** Unique identifier for the Kysely type generator plugin. */\nexport const KyselyGeneratorID = \"@tailor-platform/kysely-type\";\n\ntype KyselyTypePluginOptions = {\n distPath: string;\n pgliteSchemaPath?: string;\n};\n\n// Register this plugin's config type under its own id, via the package's\n// real public specifier, so callers can resolve it type-safely from a\n// `Plugin[]` array (see plugin/get-plugin-config.ts's resolvePluginConfig)\n// without importing KyselyTypePluginOptions, which stays unexported.\ndeclare module \"@tailor-platform/sdk/plugin\" {\n interface PluginConfigRegistry {\n \"@tailor-platform/kysely-type\": KyselyTypePluginOptions;\n }\n}\n\n/** Conventional output path used when `kyselyTypePlugin` has no `distPath` configured. */\nexport const DEFAULT_KYSELY_TYPES_DIST_PATH = \"./generated/tailordb.ts\";\n\n/**\n * Plugin that generates Kysely type definitions for TailorDB tables.\n * @param options - Plugin options\n * @param options.distPath - Output file path for generated types\n * @param options.pgliteSchemaPath - Output file path for the PGlite `CREATE TABLE` script module; omit to skip it\n * @returns Plugin instance with onTailorDBReady hook\n */\nexport function kyselyTypePlugin(\n options: KyselyTypePluginOptions,\n): Plugin<unknown, KyselyTypePluginOptions> {\n return {\n id: KyselyGeneratorID,\n description: \"Generates Kysely type definitions for TailorDB tables\",\n pluginConfig: options,\n\n async onTailorDBReady(\n ctx: TailorDBReadyContext<KyselyTypePluginOptions>,\n ): Promise<GeneratorResult> {\n const { distPath, pgliteSchemaPath } = ctx.pluginConfig;\n if (pgliteSchemaPath && resolve(distPath) === resolve(pgliteSchemaPath)) {\n throw new Error(\"distPath and pgliteSchemaPath must resolve to different files.\");\n }\n\n const allNamespaceData: KyselyNamespaceMetadata[] = [];\n\n for (const ns of ctx.tailordb) {\n const typeMetadataList: KyselyTypeMetadata[] = [];\n\n for (const type of Object.values(ns.tables)) {\n const metadata = await processKyselyType(type);\n typeMetadataList.push(metadata);\n }\n\n if (typeMetadataList.length === 0) continue;\n\n allNamespaceData.push({\n namespace: ns.namespace,\n types: typeMetadataList,\n });\n }\n\n const files: GeneratorResult[\"files\"] = [];\n if (allNamespaceData.length > 0) {\n const content = generateUnifiedKyselyTypes(allNamespaceData);\n files.push({\n path: ctx.pluginConfig.distPath,\n content,\n });\n if (ctx.pluginConfig.pgliteSchemaPath) {\n files.push({\n path: ctx.pluginConfig.pgliteSchemaPath,\n content: generatePGliteSchemaModule(\n ctx.tailordb.filter((ns) => Object.keys(ns.tables).length > 0),\n ),\n });\n }\n }\n\n return { files };\n },\n };\n}\n"],"mappings":"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"}
|