@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.
Files changed (33) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/cli/commands/tailordb/migrate/bundler.d.mts +4 -1
  3. package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +6 -0
  4. package/dist/cli/lib.mjs +1 -1
  5. package/dist/cli/lib.mjs.map +1 -1
  6. package/dist/cli/main.mjs +46 -46
  7. package/dist/cli/main.mjs.map +1 -1
  8. package/dist/completion/zsh-worker.zsh +1 -1
  9. package/dist/kysely/index.d.mts +47 -15
  10. package/dist/kysely/index.mjs +1 -1
  11. package/dist/kysely/index.mjs.map +1 -1
  12. package/dist/kysely-type-RDX59E1j.mjs +48 -0
  13. package/dist/kysely-type-RDX59E1j.mjs.map +1 -0
  14. package/dist/plugin/builtin/kysely-type/index.d.mts +11 -0
  15. package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
  16. package/dist/{register-ts-hook-DPAW0Z4M.mjs → register-ts-hook-BuwkLuxC.mjs} +87 -86
  17. package/dist/register-ts-hook-BuwkLuxC.mjs.map +1 -0
  18. package/dist/runtime/index.d.mts +8 -0
  19. package/dist/vitest/index.d.mts +3 -2
  20. package/dist/vitest/index.mjs +1 -1
  21. package/dist/vitest/index.mjs.map +1 -1
  22. package/dist/vitest/mocks/tailordb-pglite.d.mts +1 -0
  23. package/dist/vitest/pglite-kysely.d.mts +14 -1
  24. package/dist/vitest/pglite-temporal.d.mts +9 -0
  25. package/docs/services/resolver.md +1 -1
  26. package/docs/services/tailordb-migration.md +4 -2
  27. package/docs/testing.md +2 -1
  28. package/package.json +1 -1
  29. package/dist/kysely-type-C-iyFFH7.mjs +0 -43
  30. package/dist/kysely-type-C-iyFFH7.mjs.map +0 -1
  31. package/dist/register-ts-hook-DPAW0Z4M.mjs.map +0 -1
  32. package/dist/tailordb-ddl-DqInYupv.mjs +0 -7
  33. 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
@@ -0,0 +1,9 @@
1
+ //#region src/vitest/pglite-temporal.d.ts
2
+ /** Name and Postgres type OID of a column PGlite returned. */
3
+ export interface PGliteField {
4
+ /** Column name */
5
+ name: string;
6
+ /** Postgres type OID */
7
+ dataTypeID: number;
8
+ }
9
+ //#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 `6` and reads versions `1` through `6`. 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.
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailor-platform/sdk",
3
- "version": "2.25.0",
3
+ "version": "2.26.0",
4
4
  "description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -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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"}