@tailor-platform/sdk 2.29.2 → 2.32.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 +70 -0
- package/dist/ai/index.d.mts +112 -0
- package/dist/ai/index.mjs +2 -0
- package/dist/ai/index.mjs.map +1 -0
- package/dist/{application-Dm_21z-i.mjs → application-C2bPmmbh.mjs} +1 -1
- package/dist/{application-D8jruzwo.mjs → application-D0xNJbag.mjs} +32 -32
- package/dist/{application-D8jruzwo.mjs.map → application-D0xNJbag.mjs.map} +1 -1
- package/dist/cli/commands/machineuser/list.d.mts +2 -1
- package/dist/cli/lib.mjs +1 -1
- package/dist/cli/main.mjs +17 -17
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/shared/error-json.d.mts +2 -0
- package/dist/completion/zsh-worker.zsh +6 -2
- package/dist/{crashreport-BAG8c7hB.mjs → crashreport-BCIy33Cz.mjs} +2 -2
- package/dist/{crashreport-BAG8c7hB.mjs.map → crashreport-BCIy33Cz.mjs.map} +1 -1
- package/dist/crashreport-IN6H7GX3.mjs +1 -0
- package/dist/errors-BOF68LJ5.mjs +5 -0
- package/dist/errors-BOF68LJ5.mjs.map +1 -0
- package/dist/{kysely-type-BWMfV301.mjs → kysely-type-CBwsdx16.mjs} +6 -3
- package/dist/kysely-type-CBwsdx16.mjs.map +1 -0
- package/dist/{logger-DuaT2zY3.mjs → logger-RgtbhJ83.mjs} +3 -3
- package/dist/logger-RgtbhJ83.mjs.map +1 -0
- package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
- package/dist/plugin/builtin/seed/index.mjs +1 -1
- package/dist/register-ts-hook-_dHIREtU.mjs +1050 -0
- package/dist/register-ts-hook-_dHIREtU.mjs.map +1 -0
- package/dist/seed/idp-user-password.d.mts +15 -0
- package/dist/seed/index.d.mts +19 -1
- package/dist/seed/index.mjs +6 -6
- package/dist/seed/index.mjs.map +1 -1
- package/dist/{seed-C3P_T_Eh.mjs → seed-Cn-3JApy.mjs} +12 -8
- package/dist/seed-Cn-3JApy.mjs.map +1 -0
- package/dist/{service-l_tEI8sS.mjs → service-9SBPNpK8.mjs} +2 -2
- package/dist/{service-l_tEI8sS.mjs.map → service-9SBPNpK8.mjs.map} +1 -1
- package/dist/{service-b1g22NN9.mjs → service-9rwi_I4d.mjs} +2 -2
- package/dist/{service-b1g22NN9.mjs.map → service-9rwi_I4d.mjs.map} +1 -1
- package/dist/service-Ca1WwyIt.mjs +1 -0
- package/dist/telemetry-DfT2UfI4.mjs +2 -0
- package/dist/telemetry-DfT2UfI4.mjs.map +1 -0
- package/dist/{telemetry-DpkFGBmu.mjs → telemetry-nqOtDWNO.mjs} +1 -1
- package/docs/ai.md +92 -0
- package/docs/cli/application.md +4 -0
- package/docs/cli/auth.md +4 -0
- package/docs/cli/completion.md +4 -0
- package/docs/cli/crashreport.md +4 -0
- package/docs/cli/data.md +4 -0
- package/docs/cli/executor.md +4 -0
- package/docs/cli/function.md +4 -0
- package/docs/cli/organization.md +4 -0
- package/docs/cli/plugin.md +4 -0
- package/docs/cli/query.md +4 -0
- package/docs/cli/secret.md +4 -0
- package/docs/cli/skills.md +4 -0
- package/docs/cli/staticwebsite.md +4 -0
- package/docs/cli/tailordb.md +21 -15
- package/docs/cli/upgrade.md +4 -0
- package/docs/cli/user.md +4 -0
- package/docs/cli/workflow.md +4 -0
- package/docs/cli/workspace.md +5 -1
- package/docs/cli-reference.md +4 -0
- package/docs/configuration.md +4 -0
- package/docs/data-clone.md +4 -0
- package/docs/github-actions.md +108 -11
- package/docs/migration/v2.md +4 -0
- package/docs/multi-environment.md +4 -0
- package/docs/plugin/custom.md +4 -0
- package/docs/plugin/frontend.md +4 -0
- package/docs/plugin/index.md +4 -0
- package/docs/quickstart.md +4 -0
- package/docs/runtime.md +4 -0
- package/docs/services/aigateway.md +4 -0
- package/docs/services/auth.md +4 -0
- package/docs/services/executor.md +4 -0
- package/docs/services/http-adapter.md +4 -0
- package/docs/services/idp.md +4 -0
- package/docs/services/resolver.md +4 -0
- package/docs/services/secret.md +4 -0
- package/docs/services/staticwebsite.md +4 -0
- package/docs/services/tailordb/index.md +4 -0
- package/docs/services/tailordb/migration.md +49 -31
- package/docs/services/workflow.md +4 -0
- package/docs/testing.md +6 -1
- package/package.json +16 -9
- package/dist/crashreport-BcXuocwI.mjs +0 -1
- package/dist/errors-D3qpNHR6.mjs +0 -7
- package/dist/errors-D3qpNHR6.mjs.map +0 -1
- package/dist/kysely-type-BWMfV301.mjs.map +0 -1
- package/dist/logger-DuaT2zY3.mjs.map +0 -1
- package/dist/register-ts-hook-B5ZbbJwS.mjs +0 -1012
- package/dist/register-ts-hook-B5ZbbJwS.mjs.map +0 -1
- package/dist/seed-C3P_T_Eh.mjs.map +0 -1
- package/dist/service-C_CogLQr.mjs +0 -1
- package/dist/telemetry-Xk7J829P.mjs +0 -2
- package/dist/telemetry-Xk7J829P.mjs.map +0 -1
- package/docs/migration/v3.md +0 -277
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Build custom GraphQL queries and mutations in TypeScript with typed input and output schemas, validation, TailorDB access, and permissions."
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Resolver
|
|
2
6
|
|
|
3
7
|
Resolvers are custom GraphQL endpoints with business logic that execute on the Tailor Platform.
|
package/docs/services/secret.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Store API keys, tokens, and other credentials in Secret Manager vaults, and read them at runtime from executors and workflows."
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Secret Manager
|
|
2
6
|
|
|
3
7
|
Secret Manager provides secure storage for sensitive values like API keys, tokens, and credentials that your application needs at runtime.
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Host static web applications on the Tailor Platform with defineStaticWebSite, including type-safe URL references, IP restrictions, and custom domains."
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Static Website
|
|
2
6
|
|
|
3
7
|
Static Website is a service for hosting static web applications on the Tailor Platform.
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Define TailorDB tables in TypeScript with field types, relations, permissions, hooks, and validations, and get a generated GraphQL API."
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# TailorDB
|
|
2
6
|
|
|
3
7
|
TailorDB is a type-safe database service for defining and managing data models on the Tailor Platform.
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Generate, test, and apply TailorDB schema migrations with optional data migration scripts, and recover from failed migrations."
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# TailorDB Migrations
|
|
2
6
|
|
|
3
7
|
> **Beta:** The `tailordb migration` command and the migration runtime are beta features. They may introduce breaking changes in future releases. The CLI emits a beta warning on every invocation.
|
|
@@ -169,9 +173,17 @@ During deploy, the pre-migration phase keeps the old field and adds the new fiel
|
|
|
169
173
|
|
|
170
174
|
If you decline the prompt (or confirm the removal with `--drop`), the change stays a plain removal + addition with the usual data-loss warning.
|
|
171
175
|
|
|
176
|
+
#### Changing nested member constraints
|
|
177
|
+
|
|
178
|
+
Adding a required member, making an existing member required, or removing enum values inside a nested field requires a migration script, including inside arrays of objects. During Pre-migration, added or newly required members are optional and removed enum values remain allowed. Post-migration enforces the target constraints after your script fills missing values or replaces removed enum values. A newly added optional object may keep required children: those children are required only when the object is present.
|
|
179
|
+
|
|
180
|
+
The generated script includes a `TODO` for each nested constraint change. Replace these with updates to the containing nested field, preserving its other members and handling objects inside arrays. A confirmed member rename generates its copy step alongside these TODOs.
|
|
181
|
+
|
|
182
|
+
Changing a nested member's type, array-ness, decimal scale, or foreign key target is unsupported in a single migration. Use separate migrations to add a member with the target shape, copy or transform values in a custom script, and then remove the old member.
|
|
183
|
+
|
|
172
184
|
#### Renaming a member inside a nested field
|
|
173
185
|
|
|
174
|
-
Members inside a **nested field** (`db.object(...)`) are detected the same way: when `migration generate` finds a member removed from a nested field and a compatible member added under the same parent, it asks whether the member was renamed. Two members qualify only when copying the value preserves it exactly, because
|
|
186
|
+
Members inside a **nested field** (`db.object(...)`) are detected the same way: when `migration generate` finds a member removed from a nested field and a compatible member added under the same parent, it asks whether the member was renamed. Two members qualify only when copying the value preserves it exactly, because a rename copies existing values without transforming them: the type, array-ness, requiredness, foreign key target, decimal scale, hooks, and validations must match (index, unique, and vector may differ, as for a top-level rename), enum values may be added but not removed, an object-typed member must keep the same members recursively, and serial members never qualify.
|
|
175
187
|
|
|
176
188
|
```
|
|
177
189
|
? User.address.zip was removed and zipCode was added with a compatible type. Was it renamed to zipCode? (Y/n)
|
|
@@ -195,7 +207,7 @@ Warning: data loss possible:
|
|
|
195
207
|
- User.address.zip: Nested member removed (existing values will no longer be accessible through the schema). Possibly renamed to zipCode: confirm it with --rename "User.address.zip:<newName>" to scaffold a copy script, or keep the removal and copy the values yourself
|
|
196
208
|
```
|
|
197
209
|
|
|
198
|
-
The pre-migration phase keeps a removed member on the nested field until the script finishes, exactly like a removed top-level field, so a custom script can still read it. Nested fields reach the script as objects. Renaming a nested member cannot be combined in one migration with renaming its table or the nested field itself, and an object-typed member cannot be renamed in the same migration as one of its own members; split such changes into separate migrations (rename the object first, then its member).
|
|
210
|
+
The pre-migration phase keeps a removed member on the nested field until the script finishes, exactly like a removed top-level field, so a custom script can still read it. Nested fields reach the script as objects. Renaming a nested member requires every ancestor, including the top-level nested field, to keep its nested type and array-ness. It cannot be combined in one migration with renaming its table or the nested field itself, and an object-typed member cannot be renamed in the same migration as one of its own members; split such changes into separate migrations (rename the object first, then its member).
|
|
199
211
|
|
|
200
212
|
### Renaming a table
|
|
201
213
|
|
|
@@ -461,33 +473,35 @@ Tables that the failed migration removes are deleted with their data under `--co
|
|
|
461
473
|
|
|
462
474
|
## Supported Schema Changes
|
|
463
475
|
|
|
464
|
-
| Change Type
|
|
465
|
-
|
|
|
466
|
-
| Add optional field
|
|
467
|
-
| Add required field
|
|
468
|
-
| Remove field
|
|
469
|
-
| Rename field
|
|
470
|
-
| Remove nested member
|
|
471
|
-
|
|
|
472
|
-
| Change
|
|
473
|
-
|
|
|
474
|
-
|
|
|
475
|
-
|
|
|
476
|
-
|
|
|
477
|
-
|
|
|
478
|
-
|
|
|
479
|
-
| Remove
|
|
480
|
-
|
|
|
481
|
-
|
|
|
482
|
-
|
|
|
483
|
-
| Add
|
|
484
|
-
| Remove
|
|
485
|
-
|
|
|
486
|
-
|
|
|
487
|
-
|
|
|
488
|
-
| Change
|
|
489
|
-
| Change
|
|
490
|
-
| Change
|
|
476
|
+
| Change Type | Breaking? | Migration Script? | Notes |
|
|
477
|
+
| ----------------------------------------------------------------------------- | --------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
478
|
+
| Add optional field | No | No | Schema change only |
|
|
479
|
+
| Add required field | Yes | Yes | Script populates default values |
|
|
480
|
+
| Remove field | No | Optional | Warning tier — no script is auto-generated, but you can add one with `tailordb migration script` to preserve or clear data before the field leaves the active schema. The field stays readable from `migrate.ts` during Pre-migration. |
|
|
481
|
+
| Rename field | Yes | Yes | Confirmed interactively at generate time or via `--rename "Table.oldField:newField"` — see [Renaming a field](#renaming-a-field). Auto-generated script copies values from the old field to the new one; both fields coexist during Pre-migration. |
|
|
482
|
+
| Remove nested member | No | Optional | Warning tier — see [Renaming a member inside a nested field](#renaming-a-member-inside-a-nested-field). The member stays readable from `migrate.ts` during Pre-migration. |
|
|
483
|
+
| Add required nested member / make member required / remove nested enum values | Yes | Yes | Pre-migration relaxes the member constraints. Complete the generated TODOs to transform the nested values before Post-migration. |
|
|
484
|
+
| Change nested member type / array-ness / decimal scale / foreign key target | Rejected | — | Add a new member, transform and copy values, then remove the old member in separate migrations. |
|
|
485
|
+
| Rename nested member | Yes | Yes | Confirmed interactively at generate time or via `--rename "Table.field.oldMember:newMember"`. Auto-generated script rewrites each row's nested value; the old member stays readable and the new member is optional during Pre-migration. |
|
|
486
|
+
| Change optional → required | Yes | Yes | Script sets defaults for null values |
|
|
487
|
+
| Change required → optional | No | No | Schema change only |
|
|
488
|
+
| Add index (non-unique) | No | No | Schema change only |
|
|
489
|
+
| Add unique index | Yes | Yes | Script must resolve duplicate value combinations across the index fields |
|
|
490
|
+
| Change unique index fields | Yes | Yes | Treated like adding a new unique constraint over the new field set |
|
|
491
|
+
| Remove index | No | No | Schema change only (removing the unique constraint from an index is also non-breaking) |
|
|
492
|
+
| Add unique constraint | Yes | Yes | Script must resolve duplicate values |
|
|
493
|
+
| Remove unique constraint | No | No | Schema change only |
|
|
494
|
+
| Change decimal scale | Yes | Yes | Auto-generated script re-saves existing rows under the new scale. Decreasing scale rounds values half-up and can lose precision. If the same change adds a unique constraint, duplicate handling runs after re-saving. |
|
|
495
|
+
| Add enum value | No | No | Schema change only |
|
|
496
|
+
| Remove enum value | Yes | Yes | Script migrates records with removed values |
|
|
497
|
+
| Add table | No | No | Schema change only |
|
|
498
|
+
| Remove table | No | Optional | Warning tier — no script is auto-generated, but you can add one with `tailordb migration script` to preserve data before the table leaves the active schema. The table stays readable from `migrate.ts` during Pre-migration. |
|
|
499
|
+
| Rename table | Yes | Yes | Confirmed interactively at generate time or via `--rename "OldTable:NewTable"` — see [Renaming a table](#renaming-a-table). Auto-generated script copies all rows preserving ids; both tables coexist until post-migration cleanup. |
|
|
500
|
+
| Change foreign key target table | Yes | Yes | Script updates references to the new target |
|
|
501
|
+
| Change field type (verified pair) | Yes | Yes | In-place for the pairs listed under [Field type changes](#field-type-changes); review the generated normalization scaffold and customize it only when existing values need transformation |
|
|
502
|
+
| Change field type (other pair) | Yes | Yes | Two migrations, generated together after you confirm — see [Converting a field type](#converting-a-field-type). Edit the conversion in the first; the second needs no changes. |
|
|
503
|
+
| Change array → single value | - | - | **Not supported** — see [Converting a field type](#converting-a-field-type) |
|
|
504
|
+
| Change single value → array | Yes | Yes | Two migrations, generated together after you confirm — see [Converting a field type](#converting-a-field-type). Each stored value becomes a one-element array, so the conversion needs no edits. |
|
|
491
505
|
|
|
492
506
|
### Field type changes
|
|
493
507
|
|
|
@@ -607,13 +621,15 @@ Some changes are still rejected and need a temporary field you add yourself —
|
|
|
607
621
|
The command performs the following sequence:
|
|
608
622
|
|
|
609
623
|
1. Reads each migration-enabled namespace's `sdk-migration` checkpoint from the source workspace and reconstructs that exact snapshot from local migration history. A deployed namespace that has no checkpoint is treated as migration 0, as `tailor deploy` does, but only when its schema matches the `0000` snapshot; otherwise the command fails and lists the differences. A namespace that was never deployed also fails.
|
|
610
|
-
2. Creates a temporary workspace in the same region
|
|
624
|
+
2. Creates a temporary workspace in the same region as the source, unless `--target-workspace-id` names an existing throwaway workspace. By default it is created in the source's organization and folder. `--organization-id` places it at the root of that organization, and `--folder-id` places it in that folder (together with `--organization-id` when the folder is in another organization or the source is not in an organization). With `--target-workspace-id`, the given values must match where that workspace already is.
|
|
611
625
|
3. Deploys the checkpoint snapshots and writes their checkpoint labels.
|
|
612
626
|
4. Loads fixture data or clones source records.
|
|
613
627
|
5. Runs the normal deployment pipeline, including every pending pre-migration, `migrate.ts`, and post-migration phase.
|
|
614
628
|
6. Optionally runs an assertion script against the migrated data.
|
|
615
629
|
7. Deletes an automatically-created workspace after success or failure.
|
|
616
630
|
|
|
631
|
+
Right after creating an automatically-created workspace (step 2), the command records a 24-hour expiry on it. If the run is interrupted before step 7 — for example, a CI job is cancelled or reaches its time limit — the command cannot delete the workspace, so it stays. Once the 24 hours have passed, `tailor workspace prune --expired` (with a location option such as `--organization-root`, `--folder-id`, or `--personal`) deletes it. A workspace given with `--target-workspace-id` is not given an expiry. If recording the expiry fails, the command warns and continues the test.
|
|
632
|
+
|
|
617
633
|
Both the pre-migration and final TailorDB schemas come from committed migration snapshots. Ungenerated changes in the current table definitions are not included in the rehearsal.
|
|
618
634
|
|
|
619
635
|
Executors are omitted from the baseline deployment so loading fixture or cloned records cannot trigger current event handlers against the older schema. Auth user profiles are also deferred until the pending migrations finish, while configured machine users remain available to run seed and migration scripts. The final deployment restores the configured executors and user profiles. Static websites are deployed so configuration references to their URLs resolve, but their workspace-bound custom domains are omitted from migration-test deployments.
|
|
@@ -663,6 +679,8 @@ To inspect the result after a run, pass `--keep` so the automatically created wo
|
|
|
663
679
|
tailor tailordb migration test --keep
|
|
664
680
|
```
|
|
665
681
|
|
|
682
|
+
A kept workspace records no expiry unless you also pass `--keep-ttl <age>` (for example `--keep-ttl 7d`), so `tailor workspace prune --expired` does not delete it.
|
|
683
|
+
|
|
666
684
|
Alternatively, provide an empty designated throwaway workspace. This mode never deletes the target and requires explicit acknowledgment:
|
|
667
685
|
|
|
668
686
|
```bash
|
|
@@ -681,7 +699,7 @@ When you run `tailor deploy`, the SDK detects pending migrations (anything past
|
|
|
681
699
|
|
|
682
700
|
For each pending migration:
|
|
683
701
|
|
|
684
|
-
1. **Pre-migration**: Schema changes that would be breaking are applied in a relaxed form first. A verified in-place field type change keeps its complete previous field contract until Post-migration, including field and table-level hooks or validators changed by the same migration. Newly-required fields are added as optional; fields whose `optional → required` transition is breaking are temporarily kept optional. Fields that are being removed in this migration are temporarily kept on the table so that `migrate.ts` can still read them (for example, to `innerJoin` through a foreign key that is about to be dropped); members removed from a nested field are kept the same way, and the new member of a confirmed nested rename is added as optional and non-unique. For a renamed field, the old field is kept and the new field is added with its constraints relaxed, so the script can read the old field and write the new one. For a renamed table, the new table is created with its full constraints while the old table stays on the namespace until post-migration cleanup, so the script can copy rows between them. Breaking table-level index changes are relaxed the same way: a newly-added unique index is withheld, and an index gaining a unique constraint (or a unique index changing its field set) keeps its previous definition, so `migrate.ts` can resolve duplicates first. Non-breaking changes that are part of the same migration are also applied here.
|
|
702
|
+
1. **Pre-migration**: Schema changes that would be breaking are applied in a relaxed form first. A verified in-place field type change keeps its complete previous field contract until Post-migration, including field and table-level hooks or validators changed by the same migration. Newly-required fields are added as optional; fields whose `optional → required` transition is breaking are temporarily kept optional. Fields that are being removed in this migration are temporarily kept on the table so that `migrate.ts` can still read them (for example, to `innerJoin` through a foreign key that is about to be dropped); members removed from a nested field are kept the same way, and the new member of a confirmed nested rename is added as optional and non-unique. Added or newly required nested members are optional and removed nested enum values remain allowed until Post-migration. For a renamed field, the old field is kept and the new field is added with its constraints relaxed, so the script can read the old field and write the new one. For a renamed table, the new table is created with its full constraints while the old table stays on the namespace until post-migration cleanup, so the script can copy rows between them. Breaking table-level index changes are relaxed the same way: a newly-added unique index is withheld, and an index gaining a unique constraint (or a unique index changing its field set) keeps its previous definition, so `migrate.ts` can resolve duplicates first. Non-breaking changes that are part of the same migration are also applied here.
|
|
685
703
|
2. **Script execution**: If `migrate.ts` exists on disk for this migration, it is bundled and runs on the platform as the configured machine user inside a transaction — or, for a script that exports [`steps`](#splitting-a-migration-into-steps), one transaction per step. The script is hard-required for breaking changes (`diff.requiresMigrationScript`) — deploy fails if the file is missing, unless a `--no-script` acknowledgment was recorded (see [Breaking changes without a script](#breaking-changes-without-a-script)). It is also executed when present for warning-tier diffs — see [Warnings and optional migration scripts](#warnings-and-optional-migration-scripts).
|
|
686
704
|
3. **Post-migration schema**: Required constraints and the target field definitions are applied. Do not assume that removing a field clears its underlying stored JSON value.
|
|
687
705
|
4. **Checkpoint and cleanup**: The `sdk-migration` label is bumped to this migration's number, then removed GQL permissions and tables — including the old table left behind by a rename — are deleted. Advancing the checkpoint first prevents a failed checkpoint write from requiring the SDK to recreate irreversibly deleted records.
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Orchestrate multi-step jobs with durable workflows, including job dependencies, wait points, retry and concurrency policies, and starting workflows from resolvers."
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Workflow
|
|
2
6
|
|
|
3
7
|
Workflows orchestrate multiple jobs that can depend on each other, enabling complex multi-step operations with durable execution.
|
package/docs/testing.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Test SDK applications with Vitest: unit-test resolvers, workflow jobs, and executors with platform API mocks, and run end-to-end tests against a deployed workspace."
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Testing Guide
|
|
2
6
|
|
|
3
7
|
Tailor Platform SDK applications are tested with [Vitest](https://vitest.dev/) at two layers:
|
|
@@ -205,11 +209,12 @@ test("upserts against real rows", async () => {
|
|
|
205
209
|
});
|
|
206
210
|
```
|
|
207
211
|
|
|
208
|
-
The generated columns follow the Kysely types, not TailorDB's storage: `text` for string and enum fields, `timestamptz` for datetime, `date` and `time` for date and time, `numeric` for decimal, rounded to the configured scale and read back with exactly that many fractional digits, `jsonb` for nested objects (and arrays of them), Postgres arrays for other array fields. `id` is a generated `uuid` primary key, `.unique()` fields and unique `.indexes()` are enforced, so `ON CONFLICT` upserts behave, and `.default()` values become column defaults (`"now"` becomes the current time). `.serial()` fields are assigned by the database from the configured `start`, `maxValue`, and format. Relations are not enforced.
|
|
212
|
+
The generated columns follow the Kysely types, not TailorDB's storage: `text` for string and enum fields, `timestamptz` for datetime, `date` and `time` for date and time, `numeric` for decimal, rounded to the configured scale and read back with exactly that many fractional digits, `jsonb` for nested objects (and arrays of them), Postgres arrays for other array fields. `id` is a generated `uuid` primary key, `.unique()` fields and unique `.indexes()` are enforced, so `ON CONFLICT` upserts behave, and `.default()` values become column defaults (`"now"` becomes the current time). `.serial()` fields are assigned by the database from the configured `start`, `maxValue`, and format. Relations are enforced as the platform does: a row that points at a parent that does not exist is rejected with an error containing `violates foreign key constraint`, so create parent rows before their children. This includes relations on array fields. A relation declared on a field inside a nested object is not enforced, as on the platform.
|
|
209
213
|
|
|
210
214
|
What the script cannot reproduce:
|
|
211
215
|
|
|
212
216
|
- 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.
|
|
217
|
+
- Relations on array fields, and relations from a field whose type differs from the field it points at (such as a `string` field related to `id`), are checked only when a row is written. Deleting a parent that such a row still points at is rejected by the platform but not by PGlite, and changing the parent's `id` is not checked either, so a test must not expect PGlite to reject them.
|
|
213
218
|
- 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
219
|
- `gapless` is not reproduced: the numbers come from a sequence, so an insert that fails still uses up its number.
|
|
215
220
|
- 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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tailor-platform/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.32.0",
|
|
4
4
|
"description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -82,6 +82,11 @@
|
|
|
82
82
|
"import": "./dist/seed/index.mjs",
|
|
83
83
|
"default": "./dist/seed/index.mjs"
|
|
84
84
|
},
|
|
85
|
+
"./ai": {
|
|
86
|
+
"types": "./dist/ai/index.d.mts",
|
|
87
|
+
"import": "./dist/ai/index.mjs",
|
|
88
|
+
"default": "./dist/ai/index.mjs"
|
|
89
|
+
},
|
|
85
90
|
"./vitest": {
|
|
86
91
|
"types": "./dist/vitest/index.d.mts",
|
|
87
92
|
"import": "./dist/vitest/index.mjs",
|
|
@@ -160,9 +165,10 @@
|
|
|
160
165
|
"@jridgewell/trace-mapping": "0.3.31",
|
|
161
166
|
"@napi-rs/keyring": "2.1.1",
|
|
162
167
|
"@opentelemetry/api": "1.9.1",
|
|
168
|
+
"@opentelemetry/context-async-hooks": "2.12.0",
|
|
163
169
|
"@opentelemetry/exporter-trace-otlp-proto": "0.223.0",
|
|
164
170
|
"@opentelemetry/resources": "2.12.0",
|
|
165
|
-
"@opentelemetry/sdk-trace
|
|
171
|
+
"@opentelemetry/sdk-trace": "2.12.0",
|
|
166
172
|
"@opentelemetry/semantic-conventions": "1.43.0",
|
|
167
173
|
"@oxc-project/types": "0.154.0",
|
|
168
174
|
"@politty/zod": "0.5.2",
|
|
@@ -182,8 +188,8 @@
|
|
|
182
188
|
"globals": "17.13.0",
|
|
183
189
|
"graphql": "17.0.2",
|
|
184
190
|
"inflection": "3.0.2",
|
|
185
|
-
"kysely": "0.29.
|
|
186
|
-
"lines-db": "0.
|
|
191
|
+
"kysely": "0.29.7",
|
|
192
|
+
"lines-db": "0.18.0",
|
|
187
193
|
"mime-types": "3.0.2",
|
|
188
194
|
"open": "11.0.4",
|
|
189
195
|
"oxc-parser": "0.154.0",
|
|
@@ -203,21 +209,22 @@
|
|
|
203
209
|
"zod": "4.6.7"
|
|
204
210
|
},
|
|
205
211
|
"devDependencies": {
|
|
212
|
+
"@ark/attest": "0.57.0",
|
|
206
213
|
"@electric-sql/pglite": "0.5.10",
|
|
207
|
-
"@opentelemetry/sdk-trace-base": "2.12.0",
|
|
208
214
|
"@tailor-platform/shared": "^0.0.0",
|
|
209
215
|
"@tailor-platform/tailor-proto": "^0.0.1",
|
|
210
216
|
"@types/mime-types": "3.0.1",
|
|
211
217
|
"@types/node": "24.19.2",
|
|
212
218
|
"@types/semver": "7.8.0",
|
|
213
|
-
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
214
219
|
"@vitest/coverage-v8": "5.0.5",
|
|
215
|
-
"eslint-plugin-zod": "5.
|
|
220
|
+
"eslint-plugin-zod": "5.2.0",
|
|
216
221
|
"oxfmt": "0.73.0",
|
|
217
222
|
"oxlint": "1.88.0",
|
|
218
223
|
"oxlint-tsgolint": "7.0.2004",
|
|
219
224
|
"sonda": "0.14.0",
|
|
220
225
|
"tsdown": "0.23.1",
|
|
226
|
+
"tsrs": "0.16.0-ts7.1.0-dev.20260929",
|
|
227
|
+
"tsx": "4.23.15",
|
|
221
228
|
"typescript": "6.0.3",
|
|
222
229
|
"vitest": "5.0.5",
|
|
223
230
|
"zinfer": "0.4.7"
|
|
@@ -256,8 +263,8 @@
|
|
|
256
263
|
"check:import-cycles": "node --experimental-strip-types scripts/check-import-cycles.ts",
|
|
257
264
|
"lint:fix": "oxlint --type-aware . --fix",
|
|
258
265
|
"typecheck": "tsc --noEmit",
|
|
259
|
-
"typecheck:
|
|
260
|
-
"perf:
|
|
266
|
+
"typecheck:rs": "node scripts/typecheck-native.ts",
|
|
267
|
+
"perf:attest": "tsx scripts/perf/attest/run.ts",
|
|
261
268
|
"perf:runtime": "bash scripts/perf/runtime-perf.sh",
|
|
262
269
|
"postinstall": "node postinstall.mjs",
|
|
263
270
|
"measure:bundle": "node ../../scripts/measure-bundle-size-sonda.js",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export{n as reportCrash}from"./crashreport-BAG8c7hB.mjs";
|
package/dist/errors-D3qpNHR6.mjs
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import{c as e}from"./logger-DuaT2zY3.mjs";const t=/^[A-Za-z0-9_./:=@+-]+$/,n=/^[A-Za-z0-9_./:=@+\\-]+$/,r=/[%$"`“”„\p{Cc}]|\\$|^~(?:[\\/]|$)/u,i=/"|\s.*\\$|^~(?:[\\/]|$)/;function quotePosixArg(e){return t.test(e)?e:`'${e.replaceAll(`'`,`'"'"'`)}'`}function isPowerShellBare(e){return n.test(e)&&!/^(@|[+-]?\.?\d)/.test(e)&&e!==`--`&&!/^-[^-].*[.:]/.test(e)}function quotePowerShellArg(e){return isPowerShellBare(e)?e:`'${e.replace(/['‘’‚‛]/g,`$&$&`)}'`}function quoteCmdArg(e){if(n.test(e)&&!e.endsWith(`\\`))return e;let t=`"`,r=0;for(let n of e)n===`\\`?r++:(t+=n===`"`?`${`\\`.repeat(r*2)}""`:`${`\\`.repeat(r)}${n===`%`?`%%cd:~,%`:n}`,r=0);return`${t}${`\\`.repeat(r*2)}"`}function formatWindowsCommandLines(e){if(e.every(e=>isPowerShellBare(e)||e!==``&&!r.test(e)))return{kind:`shared`,commandLine:e.map(e=>isPowerShellBare(e)?e:`"${e}"`).join(` `)};let t=e.map(quoteCmdArg).join(` `);return{kind:`perShell`,powershell:e.some(e=>e===``||i.test(e))?`cmd /d /s /c ${quotePowerShellArg(t)}`:e.map(quotePowerShellArg).join(` `),cmd:t}}function formatShellCommandLines(e){return process.platform===`win32`?formatWindowsCommandLines(e):{kind:`shared`,commandLine:e.map(quotePosixArg).join(` `)}}function formatCommandHint(e){let t=formatShellCommandLines([e.command,...e.args]);return t.kind===`shared`?`\`${t.commandLine}\``:`\`${t.powershell}\` in PowerShell or \`${t.cmd}\` in cmd.exe`}function formatError(t){let n=[e.error(`Error${t.code?` [${t.code}]`:``}: ${t.message}`)];return t.details&&n.push(`\n ${e.dim(`Details:`)} ${t.details.split(`
|
|
2
|
-
`).join(`
|
|
3
|
-
`)}`),t.suggestion&&n.push(`\n ${e.info(`Suggestion:`)} ${t.suggestion.split(`
|
|
4
|
-
`).join(`
|
|
5
|
-
`)}`),t.command&&n.push(`\n ${e.dim(`Help:`)} Run \`tailor ${t.command} --help\` for usage information.`),t.next&&n.push(`\n ${e.info(`Next:`)} Run ${formatCommandHint(t.next)}.`),n.join(``)}function CLIError(e){let t=Error(e.message,e.cause===void 0?void 0:{cause:e.cause});return t.name=`CLIError`,t.code=e.code,t.details=e.details,t.suggestion=e.suggestion,t.command=e.command,t.next=e.next,t.context=e.context,t.format=()=>formatError(t),t}function isCLIError(e){return e instanceof Error&&e.name===`CLIError`}function errorSummary(e){return isCLIError(e)?[e.message,e.details,e.suggestion].filter(Boolean).join(`
|
|
6
|
-
`):e instanceof Error?e.message:String(e)}function internalError(e,t){return Error(e,t)}function toError(e){return e instanceof Error?e:Error(String(e),{cause:e})}const a=/does not provide an export named '(?!default')([^']+)'/;function typeOnlyImportHint(e){if(!(e instanceof SyntaxError))return;let t=a.exec(e.message)?.[1];if(t)return`If '${t}' is a type, import it with \`import type\` (or the inline \`type\` modifier). The CLI runs TypeScript by stripping types from each file in isolation, so type-only exports do not exist at runtime. Set "verbatimModuleSyntax": true in tsconfig.json to catch this at typecheck.`}export{isCLIError as a,internalError as i,errorSummary as n,toError as o,formatCommandHint as r,typeOnlyImportHint as s,CLIError as t};
|
|
7
|
-
//# sourceMappingURL=errors-D3qpNHR6.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors-D3qpNHR6.mjs","names":["styles","POSIX_BARE_ARG","CMD_BARE_ARG","WINDOWS_DOUBLE_QUOTE_UNSAFE","POWERSHELL_VERSION_SENSITIVE","value","quoted","backslashes","char","argv","cmd","commandLines","action","parts","error","options","message","MISSING_NAMED_EXPORT_PATTERN","name"],"sources":["../src/cli/shared/shell-quote.ts","../src/cli/shared/errors.ts"],"sourcesContent":["/**\n * Command lines for a suggested command: one line when every supported shell reads it the same\n * way, otherwise one line per Windows shell.\n */\nexport type ShellCommandLines =\n | { kind: \"shared\"; commandLine: string }\n | { kind: \"perShell\"; powershell: string; cmd: string };\n\nconst POSIX_BARE_ARG = /^[A-Za-z0-9_./:=@+-]+$/;\nconst CMD_BARE_ARG = /^[A-Za-z0-9_./:=@+\\\\-]+$/;\n// PowerShell expands `$` and backtick escapes inside double quotes and reads `“ ” „` as `\"`,\n// cmd.exe expands `%` even inside quotes, a `\"` or a trailing backslash changes where the\n// quoted argument ends, and a leading `~` is version-sensitive (below).\nconst WINDOWS_DOUBLE_QUOTE_UNSAFE = /[%$\"`“”„\\p{Cc}]|\\\\$|^~(?:[\\\\/]|$)/u;\n// PowerShell versions pass these to programs differently: Windows PowerShell 5.1 and 7.3+\n// disagree on quotes and trailing backslashes, and 7.6+ expands a leading `~` inside the `.ps1`\n// shim however it was quoted.\nconst POWERSHELL_VERSION_SENSITIVE = /\"|\\s.*\\\\$|^~(?:[\\\\/]|$)/;\n\nfunction quotePosixArg(value: string): string {\n if (POSIX_BARE_ARG.test(value)) return value;\n return `'${value.replaceAll(\"'\", `'\"'\"'`)}'`;\n}\n\nfunction isPowerShellBare(value: string): boolean {\n // A leading `@` splats, a lone `--` ends parameters, a leading digit reads as a number, and a\n // single-dash token with `.` or `:` reads as parameter syntax.\n return (\n CMD_BARE_ARG.test(value) &&\n !/^(@|[+-]?\\.?\\d)/.test(value) &&\n value !== \"--\" &&\n !/^-[^-].*[.:]/.test(value)\n );\n}\n\nfunction quotePowerShellArg(value: string): string {\n if (isPowerShellBare(value)) return value;\n return `'${value.replace(/['‘’‚‛]/g, \"$&$&\")}'`;\n}\n\nfunction quoteCmdArg(value: string): string {\n // Quoting a trailing backslash keeps a command routed through `cmd /c` from ending in one,\n // which PowerShell 7 doubles when it quotes the line.\n if (CMD_BARE_ARG.test(value) && !value.endsWith(\"\\\\\")) return value;\n let quoted = '\"';\n let backslashes = 0;\n for (const char of value) {\n if (char === \"\\\\\") {\n backslashes++;\n continue;\n }\n if (char === '\"') {\n quoted += `${\"\\\\\".repeat(backslashes * 2)}\"\"`;\n } else {\n // `%%cd:~,%` reads back as a literal `%` without cmd.exe expanding a `%NAME%` around it.\n quoted += `${\"\\\\\".repeat(backslashes)}${char === \"%\" ? \"%%cd:~,%\" : char}`;\n }\n backslashes = 0;\n }\n return `${quoted}${\"\\\\\".repeat(backslashes * 2)}\"`;\n}\n\nfunction formatWindowsCommandLines(argv: readonly string[]): ShellCommandLines {\n if (\n argv.every(\n (value) =>\n isPowerShellBare(value) || (value !== \"\" && !WINDOWS_DOUBLE_QUOTE_UNSAFE.test(value)),\n )\n ) {\n return {\n kind: \"shared\",\n commandLine: argv.map((value) => (isPowerShellBare(value) ? value : `\"${value}\"`)).join(\" \"),\n };\n }\n const cmd = argv.map(quoteCmdArg).join(\" \");\n // Both PowerShell versions pass a `cmd` argument the same legacy way, so routing the cmd.exe\n // line through `cmd /c` keeps the arguments identical where their native quoting differs.\n const powershell = argv.some((value) => value === \"\" || POWERSHELL_VERSION_SENSITIVE.test(value))\n ? `cmd /d /s /c ${quotePowerShellArg(cmd)}`\n : argv.map(quotePowerShellArg).join(\" \");\n return { kind: \"perShell\", powershell, cmd };\n}\n\n/**\n * Render an argv array as command lines that the current platform's shells run with every\n * argument delivered unchanged\n * @param {readonly string[]} argv - Executable name followed by its arguments\n * @returns {ShellCommandLines} One shared command line, or one per Windows shell\n */\nexport function formatShellCommandLines(argv: readonly string[]): ShellCommandLines {\n if (process.platform === \"win32\") return formatWindowsCommandLines(argv);\n return { kind: \"shared\", commandLine: argv.map(quotePosixArg).join(\" \") };\n}\n","import { styles } from \"./logger\";\nimport { formatShellCommandLines } from \"./shell-quote\";\nimport type { Jsonifiable } from \"type-fest\";\n\n/**\n * Options for creating a CLI error\n */\ninterface CLIErrorOptions {\n /** Stable machine-readable failure code, such as `WORKSPACE_NOT_FOUND`. */\n code: string;\n message: string;\n details?: string;\n suggestion?: string;\n command?: string;\n next?: CLIErrorNextAction;\n context?: Readonly<Record<string, Jsonifiable | undefined>>;\n cause?: unknown;\n}\n\nexport type CLIErrorNextAction = {\n /** Executable name, such as `tailor`. */\n command: string;\n /** Arguments passed directly to the executable. */\n args: readonly string[];\n};\n\n/**\n * CLI error interface with formatted output\n */\nexport interface CLIError extends Error {\n readonly code: string;\n readonly details?: string;\n readonly suggestion?: string;\n readonly command?: string;\n readonly next?: CLIErrorNextAction;\n readonly context?: Readonly<Record<string, Jsonifiable | undefined>>;\n format(): string;\n}\n\ntype CLIErrorInternal = Error & {\n code: string;\n details?: string;\n suggestion?: string;\n command?: string;\n next?: CLIErrorNextAction;\n context?: Readonly<Record<string, Jsonifiable | undefined>>;\n format(): string;\n};\n\n/**\n * Render a command as a user-facing hint that can be copied into the current platform's shells:\n * one backticked command line, or on Windows, when no single quoting suits both shells, a\n * PowerShell and a cmd.exe line (`` `…` in PowerShell or `…` in cmd.exe ``)\n * @param {CLIErrorNextAction} action - Executable and arguments to suggest\n * @returns {string} The command part of the hint, to follow a verb such as \"Run\"\n */\nexport function formatCommandHint(action: CLIErrorNextAction): string {\n const commandLines = formatShellCommandLines([action.command, ...action.args]);\n if (commandLines.kind === \"shared\") {\n return `\\`${commandLines.commandLine}\\``;\n }\n return `\\`${commandLines.powershell}\\` in PowerShell or \\`${commandLines.cmd}\\` in cmd.exe`;\n}\n\n/**\n * Format CLI error for output\n * @param error - CLIError instance to format\n * @returns Formatted error message\n */\nfunction formatError(error: CLIError): string {\n const parts: string[] = [\n styles.error(`Error${error.code ? ` [${error.code}]` : \"\"}: ${error.message}`),\n ];\n\n if (error.details) {\n parts.push(`\\n ${styles.dim(\"Details:\")} ${error.details.split(\"\\n\").join(\"\\n \")}`);\n }\n\n if (error.suggestion) {\n parts.push(`\\n ${styles.info(\"Suggestion:\")} ${error.suggestion.split(\"\\n\").join(\"\\n \")}`);\n }\n\n if (error.command) {\n parts.push(\n `\\n ${styles.dim(\"Help:\")} Run \\`tailor ${error.command} --help\\` for usage information.`,\n );\n }\n\n if (error.next) {\n parts.push(`\\n ${styles.info(\"Next:\")} Run ${formatCommandHint(error.next)}.`);\n }\n\n return parts.join(\"\");\n}\n\n/**\n * Create a CLI error with formatted output\n * @param options - Options to construct a CLIError\n * @returns Constructed CLIError instance\n */\nexport function CLIError(options: CLIErrorOptions): CLIError {\n const error = new Error(\n options.message,\n options.cause === undefined ? undefined : { cause: options.cause },\n ) as CLIErrorInternal;\n error.name = \"CLIError\";\n error.code = options.code;\n error.details = options.details;\n error.suggestion = options.suggestion;\n error.command = options.command;\n error.next = options.next;\n error.context = options.context;\n error.format = () => formatError(error);\n return error;\n}\n\n/**\n * Type guard to check if an error is a CLIError\n * @param error - Error to check\n * @returns True if the error is a CLIError\n */\nexport function isCLIError(error: unknown): error is CLIError {\n return error instanceof Error && error.name === \"CLIError\";\n}\n\n/**\n * Summarize a failure as plain text for reports that carry a single error string.\n * CLI errors contribute their details and suggestion so the remediation survives.\n * @param error - Failure to summarize\n * @returns Message followed by any details and suggestion, one per line\n */\nexport function errorSummary(error: unknown): string {\n if (isCLIError(error)) {\n return [error.message, error.details, error.suggestion].filter(Boolean).join(\"\\n\");\n }\n return error instanceof Error ? error.message : String(error);\n}\n\n/**\n * Create an error for an SDK invariant violation that no user action can fix.\n * The result is a plain Error, so JSON output reports it as `UNEXPECTED_ERROR`\n * and crash reporting treats it like any other unexpected failure.\n * @param message - Description of the violated invariant\n * @param options - Standard Error options, such as `cause`\n * @returns Plain Error instance\n */\nexport function internalError(message: string, options?: ErrorOptions): Error {\n return new Error(message, options);\n}\n\n/**\n * Convert a caught value into an Error, keeping Error instances as-is\n * @param value - Caught value\n * @returns The value itself when it is an Error, otherwise an Error of its string form with the value as `cause`\n */\nexport function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value), { cause: value });\n}\n\nconst MISSING_NAMED_EXPORT_PATTERN = /does not provide an export named '(?!default')([^']+)'/;\n\n/**\n * Suggest `import type` when importing user code fails on a missing named\n * export. The CLI strips types from each file in isolation, so a type-only\n * export does not exist at runtime and a plain import of it fails to link.\n * @param error - Error thrown while importing user modules\n * @returns Suggestion text, or undefined when the error is not that failure\n */\nexport function typeOnlyImportHint(error: unknown): string | undefined {\n if (!(error instanceof SyntaxError)) return undefined;\n const name = MISSING_NAMED_EXPORT_PATTERN.exec(error.message)?.[1];\n if (!name) return undefined;\n return (\n `If '${name}' is a type, import it with \\`import type\\` (or the inline \\`type\\` modifier). ` +\n \"The CLI runs TypeScript by stripping types from each file in isolation, so type-only exports do not exist at runtime. \" +\n 'Set \"verbatimModuleSyntax\": true in tsconfig.json to catch this at typecheck.'\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"kysely-type-BWMfV301.mjs","names":["multiline","utf8","fieldType","name","hash","byte","value","suffix","tableName","memberName","kind","head","base","field","pgType","label","CURRENT_TIME_EXPRESSIONS","currentTime","v","groups","format","conversion","fieldName","range","serial","SERIAL_CONVERSIONS","nextval","sequence","number","width","zeroPad","prefix","column","parts","hasDefault","filledOnCreate","tableIdentifier","table","sequences","ownerships","columns","indexes","index","tables","statement","entries","namespaces","namespace","options","type","temporal","COLUMN_TYPE_ALIASES","a","b","usedUtilityTypes","allowedValues","fieldConfig","fields","fieldResults","config","result","aggregatedUtilityTypes","acc","fieldTypes","r","obj","hasOptionalFields","hasGeneratedFields","nested","baseTypeResult","isArray","isNullable","isColumnTypeBase","finalType","fieldLines","parsedField","namespaceData","globalUsedUtilityTypes","ns","utilityTypeImports","types","line","KyselyGeneratorID","DEFAULT_KYSELY_TYPES_DIST_PATH","ctx","pgliteSchemaPath","resolve","distPath","allNamespaceData","typeMetadataList","metadata","files","content"],"sources":["../src/utils/tailordb-ddl.ts","../src/plugin/builtin/kysely-type/pglite-schema.ts","../src/utils/field-column-type.ts","../src/plugin/builtin/kysely-type/type-processor.ts","../src/plugin/builtin/kysely-type/index.ts"],"sourcesContent":["/**\n * PostgreSQL DDL derived from TailorDB table definitions, for creating the\n * tables a PGlite-backed test needs. The columns match the flat shape the\n * Kysely type generators emit, not TailorDB's storage layout.\n */\n\n/** The field shape the DDL generator reads; parsed and snapshot field configs are both assignable. */\nexport interface DDLFieldConfig {\n type: string;\n required?: boolean;\n array?: boolean;\n unique?: boolean;\n serial?: { start: number; maxValue?: number; format?: string };\n scale?: number;\n default?: unknown;\n optionalOnCreate?: boolean;\n hooks?: { create?: unknown; update?: unknown };\n fields?: Record<string, DDLFieldConfig>;\n}\n\n/** A table to emit DDL for. */\nexport interface DDLTableConfig {\n name: string;\n fields: Record<string, DDLFieldConfig>;\n indexes?: Record<string, { fields: string[]; unique?: boolean }>;\n}\n\nconst MAX_IDENTIFIER_BYTES = 63;\n// The platform rounds decimals to their scale (6 unless configured); numeric\n// needs a precision to carry a scale, and the maximum leaves it unconstrained.\nconst DECIMAL_PRECISION = 1000;\nconst DEFAULT_DECIMAL_SCALE = 6;\nconst utf8 = new TextEncoder();\n\n/**\n * Map a TailorDB field type to the PostgreSQL column type PGlite tests use for it.\n * @param fieldType - TailorDB field type name\n * @returns The PostgreSQL type name\n * @throws If the type is not a TailorDB field type\n */\nexport function mapFieldTypeToPostgresType(fieldType: string): string {\n switch (fieldType) {\n case \"uuid\":\n return \"uuid\";\n case \"string\":\n case \"enum\":\n return \"text\";\n case \"boolean\":\n case \"bool\":\n return \"boolean\";\n case \"integer\":\n return \"integer\";\n case \"float\":\n return \"double precision\";\n case \"decimal\":\n return \"numeric\";\n case \"date\":\n return \"date\";\n case \"datetime\":\n return \"timestamptz\";\n case \"time\":\n return \"time\";\n case \"nested\":\n return \"jsonb\";\n default:\n throw new Error(`Field type \"${fieldType}\" has no PostgreSQL column type.`);\n }\n}\n\nfunction identifier(name: string): string {\n return `\"${name.replaceAll('\"', '\"\"')}\"`;\n}\n\nfunction fnv1aHex(value: string): string {\n let hash = 0x811c9dc5;\n for (const byte of utf8.encode(value)) {\n hash = Math.imul(hash ^ byte, 0x01000193) >>> 0;\n }\n return hash.toString(16).padStart(8, \"0\");\n}\n\nfunction derivedIdentifier(tableName: string, memberName: string, kind: \"seq\" | \"idx\"): string {\n const suffix = `_${fnv1aHex(JSON.stringify([tableName, memberName, kind]))}_${kind}`;\n let head = `${tableName}_${memberName}`;\n while (utf8.encode(head + suffix).length > MAX_IDENTIFIER_BYTES) head = head.slice(0, -1);\n return identifier(head + suffix);\n}\n\nfunction stringLiteral(value: string): string {\n return `'${value.replaceAll(\"'\", \"''\")}'`;\n}\n\nfunction columnType(field: DDLFieldConfig): string {\n const base =\n field.type === \"decimal\"\n ? `numeric(${DECIMAL_PRECISION}, ${field.scale ?? DEFAULT_DECIMAL_SCALE})`\n : mapFieldTypeToPostgresType(field.type);\n return field.array && field.type !== \"nested\" ? `${base}[]` : base;\n}\n\nfunction scalarLiteral(value: unknown, field: DDLFieldConfig, label: string): string {\n const pgType = mapFieldTypeToPostgresType(field.type);\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) {\n throw new Error(`Default of field ${label} is an invalid Date.`);\n }\n return `${stringLiteral(value.toISOString())}::${pgType}`;\n }\n switch (typeof value) {\n case \"string\":\n return pgType === \"text\" ? stringLiteral(value) : `${stringLiteral(value)}::${pgType}`;\n case \"number\":\n if (!Number.isFinite(value)) {\n throw new Error(`Default of field ${label} is not a finite number.`);\n }\n return String(value);\n case \"boolean\":\n return value ? \"TRUE\" : \"FALSE\";\n default:\n throw new Error(`Default of field ${label} cannot be rendered as a SQL literal.`);\n }\n}\n\nconst CURRENT_TIME_EXPRESSIONS = new Map([\n [\"datetime\", \"now()\"],\n [\"date\", \"CURRENT_DATE\"],\n [\"time\", \"LOCALTIME\"],\n]);\n\nfunction defaultExpression(field: DDLFieldConfig, label: string): string {\n const value = field.default;\n const currentTime = CURRENT_TIME_EXPRESSIONS.get(field.type);\n if (value === \"now\" && currentTime !== undefined) {\n return currentTime;\n }\n if (field.array) {\n if (!Array.isArray(value)) {\n throw new Error(`Default of array field ${label} must be an array.`);\n }\n const pgType = columnType(field);\n if (value.length === 0) return `'{}'::${pgType}`;\n return `ARRAY[${value.map((v) => scalarLiteral(v, field, label)).join(\", \")}]::${pgType}`;\n }\n return scalarLiteral(value, field, label);\n}\n\ninterface SerialFormat {\n prefix: string;\n width: number;\n zeroPad: boolean;\n conversion: \"d\" | \"x\" | \"X\";\n suffix: string;\n}\n\n// printf-style with exactly one conversion specifier; octal has no Postgres\n// formatting function to stand in for it.\nfunction parseSerialFormat(format: string, label: string): SerialFormat {\n const match =\n /^(?<prefix>[^%]*)%(?<zero>0?)(?<width>\\d*)(?<conversion>[a-zA-Z])(?<suffix>[^%]*)$/.exec(\n format,\n );\n const groups = match?.groups;\n const conversion = groups?.conversion;\n if (!groups || (conversion !== \"d\" && conversion !== \"x\" && conversion !== \"X\")) {\n throw new Error(\n `Serial format \"${format}\" of field ${label} is not supported; use a single %d, %x, or %X specifier with an optional width (e.g. \"INV-%05d\").`,\n );\n }\n return {\n prefix: groups.prefix ?? \"\",\n zeroPad: groups.zero === \"0\",\n width: groups.width ? Number(groups.width) : 0,\n conversion,\n suffix: groups.suffix ?? \"\",\n };\n}\n\nfunction sequenceName(tableName: string, fieldName: string): string {\n return derivedIdentifier(tableName, fieldName, \"seq\");\n}\n\n// A sequence's minimum defaults to 1, which rejects a start of 0.\nfunction sequenceRange(serial: NonNullable<DDLFieldConfig[\"serial\"]>): string {\n const range = [`START WITH ${serial.start}`, `MINVALUE ${serial.start}`];\n if (serial.maxValue !== undefined) range.push(`MAXVALUE ${serial.maxValue}`);\n return range.join(\" \");\n}\n\nconst SERIAL_CONVERSIONS: Record<SerialFormat[\"conversion\"], (nextval: string) => string> = {\n d: (nextval) => nextval,\n x: (nextval) => `to_hex(${nextval})`,\n X: (nextval) => `upper(to_hex(${nextval}))`,\n};\n\nfunction serialStringDefault(sequence: string, format: string | undefined, label: string): string {\n const nextval = `nextval(${stringLiteral(sequence)})`;\n if (format === undefined) return `(${nextval}::text)`;\n const { prefix, width, zeroPad, conversion, suffix } = parseSerialFormat(format, label);\n const number = SERIAL_CONVERSIONS[conversion](nextval);\n let value = conversion === \"d\" ? `${number}::text` : number;\n if (width > 0) {\n value = `format('%${width}s', ${number})`;\n if (zeroPad) value = `translate(${value}, ' ', '0')`;\n }\n const parts = [\n ...(prefix ? [stringLiteral(prefix)] : []),\n value,\n ...(suffix ? [stringLiteral(suffix)] : []),\n ];\n return `(${parts.join(\" || \")})`;\n}\n\nfunction columnDefinition(tableName: string, fieldName: string, field: DDLFieldConfig): string {\n const column = identifier(fieldName);\n const label = `${identifier(tableName)}.${column}`;\n const parts = [column, columnType(field)];\n\n if (field.serial) {\n if (field.type === \"integer\") {\n parts.push(`GENERATED BY DEFAULT AS IDENTITY (${sequenceRange(field.serial)})`);\n if (field.unique) parts.push(\"UNIQUE\");\n return parts.join(\" \");\n }\n if (field.required) parts.push(\"NOT NULL\");\n if (field.unique) parts.push(\"UNIQUE\");\n parts.push(\n `DEFAULT ${serialStringDefault(sequenceName(tableName, fieldName), field.serial.format, label)}`,\n );\n return parts.join(\" \");\n }\n\n const hasDefault = field.default !== undefined;\n const filledOnCreate = field.hooks?.create !== undefined || field.optionalOnCreate === true;\n if (field.required && (hasDefault || !filledOnCreate)) parts.push(\"NOT NULL\");\n if (field.unique) parts.push(\"UNIQUE\");\n if (hasDefault) parts.push(`DEFAULT ${defaultExpression(field, label)}`);\n return parts.join(\" \");\n}\n\n/**\n * DDL statements that create one table: any sequences its string serial\n * fields draw from, the table, the sequences' ownership of their columns,\n * then its unique indexes. Every statement is `IF NOT EXISTS` or otherwise\n * idempotent, so re-applying the script on a database that already has the\n * table is a no-op. Owning a sequence makes `DROP TABLE` drop it and\n * `TRUNCATE ... RESTART IDENTITY` reset it to the configured start. Index\n * names end in `_idx` so they cannot take the `<table>_<column>_key` name\n * Postgres gives a UNIQUE column, which `IF NOT EXISTS` would otherwise\n * silently skip.\n * @param table - Table name, fields, and indexes\n * @returns Statements in execution order, without trailing semicolons\n * @throws If a field type, default, or serial format cannot be expressed\n */\nexport function generateTableDDL(table: DDLTableConfig): string[] {\n const tableIdentifier = identifier(table.name);\n const sequences: string[] = [];\n const ownerships: string[] = [];\n const columns = [` \"id\" uuid PRIMARY KEY DEFAULT gen_random_uuid()`];\n\n for (const [fieldName, field] of Object.entries(table.fields)) {\n if (fieldName === \"id\") continue;\n if (field.serial && field.type !== \"integer\") {\n const sequence = sequenceName(table.name, fieldName);\n sequences.push(`CREATE SEQUENCE IF NOT EXISTS ${sequence} ${sequenceRange(field.serial)}`);\n ownerships.push(\n `ALTER SEQUENCE ${sequence} OWNED BY ${tableIdentifier}.${identifier(fieldName)}`,\n );\n }\n columns.push(` ${columnDefinition(table.name, fieldName, field)}`);\n }\n\n const indexes = Object.entries(table.indexes ?? {})\n .filter(([, index]) => index.unique)\n .map(\n ([name, index]) =>\n `CREATE UNIQUE INDEX IF NOT EXISTS ${derivedIdentifier(table.name, name, \"idx\")} ON ${tableIdentifier} (${index.fields.map(identifier).join(\", \")})`,\n );\n\n return [\n ...sequences,\n `CREATE TABLE IF NOT EXISTS ${tableIdentifier} (\\n${columns.join(\",\\n\")}\\n)`,\n ...ownerships,\n ...indexes,\n ];\n}\n\n/**\n * A single SQL script creating every given table, for `pglite.exec()`.\n * @param tables - Tables in the order to create them\n * @returns The script, empty when there are no tables\n */\nexport function generateSchemaDDL(tables: readonly DDLTableConfig[]): string {\n return tables\n .map((table) =>\n generateTableDDL(table)\n .map((statement) => `${statement};`)\n .join(\"\\n\"),\n )\n .join(\"\\n\\n\");\n}\n\n/** One namespace's tables, as the schema module groups them. */\nexport interface PgliteSchemaNamespace {\n namespace: string;\n tables: readonly DDLTableConfig[];\n}\n\n/** Options for {@link generatePgliteSchemaModule}. */\nexport interface PgliteSchemaModuleOptions {\n /** Named in the generated header as the producer users must not edit around. */\n generatedBy: string;\n}\n\n// A template literal keeps line feeds but folds a carriage return into one.\nfunction templateLiteral(value: string): string {\n const escaped = value\n .replaceAll(\"\\\\\", \"\\\\\\\\\")\n .replaceAll(\"`\", \"\\\\`\")\n .replaceAll(\"${\", \"\\\\${\")\n .replaceAll(\"\\r\", \"\\\\r\");\n return `\\`${escaped}\\``;\n}\n\n/**\n * Render the module that exports each namespace's `CREATE TABLE` script for\n * `pglite.exec()`.\n * @param namespaces - Namespaces with the tables to create\n * @param options - Header wording\n * @returns TypeScript source of the schema module\n */\nexport function generatePgliteSchemaModule(\n namespaces: readonly PgliteSchemaNamespace[],\n options: PgliteSchemaModuleOptions,\n): string {\n const entries = namespaces.map(\n ({ namespace, tables }) =>\n ` ${JSON.stringify(namespace)}: ${templateLiteral(generateSchemaDDL(tables))},`,\n );\n return [\n \"/**\",\n \" * Auto-generated PGlite schema for TailorDB tables.\",\n \" * Create a namespace's tables in a test with `pglite.exec(pgliteSchema[namespace])`.\",\n \" *\",\n ` * DO NOT EDIT - This file is auto-generated by ${options.generatedBy}.`,\n \" */\",\n \"\",\n \"export const pgliteSchema = {\",\n ...entries,\n \"} as const;\",\n \"\",\n ].join(\"\\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","/** The Kysely column type a TailorDB field maps to, before array/null modifiers. */\nexport type FieldColumnType =\n | \"string\"\n | \"number\"\n | \"boolean\"\n | \"Timestamp\"\n | \"TemporalDate\"\n | \"TemporalInstant\"\n | \"TemporalTime\";\n\n/**\n * Map a scalar TailorDB field type to the column type generated code uses for it.\n *\n * With `temporal: false` (the default), `date`/`datetime` resolve to `Timestamp` and\n * `time` to `string`, because that's what the function runtime hands back for them by\n * default. With `temporal: true`, `date`/`datetime`/`time` resolve to their\n * `Temporal.PlainDate`/`Temporal.Instant`/`Temporal.PlainTime` counterparts instead,\n * matching a `getDB` created with `createGetDB({ temporal: true })`.\n *\n * `enum` and `nested` carry their own shape, so each generator resolves them\n * before reaching here; passing either is a caller bug rather than a `string`\n * column.\n * @param fieldType - TailorDB scalar field type name\n * @param temporal - Whether to map date/datetime/time fields to their Temporal column\n * types instead of their `Date`/`string` defaults\n * @returns The column type for the field, defaulting to `string`\n * @throws If given `enum` or `nested`\n */\nexport function mapFieldTypeToColumnType(fieldType: string, temporal = false): FieldColumnType {\n switch (fieldType) {\n case \"uuid\":\n case \"string\":\n case \"decimal\":\n return \"string\";\n case \"integer\":\n case \"float\":\n return \"number\";\n case \"date\":\n return temporal ? \"TemporalDate\" : \"Timestamp\";\n case \"datetime\":\n return temporal ? \"TemporalInstant\" : \"Timestamp\";\n case \"time\":\n return temporal ? \"TemporalTime\" : \"string\";\n case \"bool\":\n case \"boolean\":\n return \"boolean\";\n case \"enum\":\n case \"nested\":\n throw new Error(\n `Field type \"${fieldType}\" has no scalar column type; resolve it before mapping.`,\n );\n default:\n return \"string\";\n }\n}\n\n/** The select and write types a `ColumnType`-shaped alias expands to. */\nexport type ColumnTypeAliasExpansion = {\n /** Type the alias reads back as. */\n readonly select: string;\n /** Type the alias accepts on insert and update. */\n readonly write: string;\n};\n\n/**\n * Column types whose alias expands to a `ColumnType`, mapped to that expansion.\n *\n * Kysely only unwraps a `ColumnType` at the top level of a table property, so an\n * array of one of these stays wrapped in `ArrayColumnType<...>` rather than taking\n * a `[]` suffix, which would nest the `ColumnType` out of Kysely's reach. Where a\n * generator has to spell out the slots itself, it reads the expansion from here\n * rather than inlining the alias, which would nest just the same.\n *\n * Both type generators track alias usage per alias to decide which declarations to\n * emit, so adding an entry here also means teaching them to report the new alias.\n */\nexport const COLUMN_TYPE_ALIASES: ReadonlyMap<string, ColumnTypeAliasExpansion> = new Map<\n FieldColumnType,\n ColumnTypeAliasExpansion\n>([\n [\"Timestamp\", { select: \"Date\", write: \"Date | string\" }],\n [\"TemporalDate\", { select: \"Temporal.PlainDate\", write: \"Temporal.PlainDate | string\" }],\n [\"TemporalInstant\", { select: \"Temporal.Instant\", write: \"Temporal.Instant | string\" }],\n [\"TemporalTime\", { select: \"Temporal.PlainTime\", write: \"Temporal.PlainTime | string\" }],\n]);\n","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 {\n Timestamp: false,\n TemporalDate: false,\n TemporalInstant: false,\n TemporalTime: false,\n Serial: false,\n ObjectColumnType: false,\n ArrayColumnType: false,\n };\n}\n\nfunction mergeUsedUtilityTypes(a: UsedUtilityTypes, b: UsedUtilityTypes): UsedUtilityTypes {\n return {\n Timestamp: a.Timestamp || b.Timestamp,\n TemporalDate: a.TemporalDate || b.TemporalDate,\n TemporalInstant: a.TemporalInstant || b.TemporalInstant,\n TemporalTime: a.TemporalTime || b.TemporalTime,\n Serial: a.Serial || b.Serial,\n ObjectColumnType: a.ObjectColumnType || b.ObjectColumnType,\n ArrayColumnType: a.ArrayColumnType || b.ArrayColumnType,\n };\n}\n\n/**\n * Whether a utility-type usage record uses any `ColumnType`-shaped alias.\n * @param usedUtilityTypes - Utility-type usage record to check\n * @returns Whether any `ColumnType`-shaped alias is used\n */\nfunction usesColumnTypeAlias(usedUtilityTypes: UsedUtilityTypes): boolean {\n return (\n usedUtilityTypes.Timestamp ||\n usedUtilityTypes.TemporalDate ||\n usedUtilityTypes.TemporalInstant ||\n usedUtilityTypes.TemporalTime\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 * @param temporal - Whether date/datetime/time fields resolve to their Temporal column\n * types instead of their `Date`/`string` defaults\n * @returns The nested type with used utility types\n */\nfunction getNestedType(fieldConfig: KyselyFieldConfig, temporal: boolean): 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, temporal, true);\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 (usesColumnTypeAlias(aggregatedUtilityTypes) || 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 * @param temporal - Whether date/datetime/time fields resolve to their Temporal column\n * types instead of their `Date`/`string` defaults\n * @param nested - Whether the field is inside an object\n * @returns The base type with used utility types\n */\nfunction getBaseType(\n fieldConfig: KyselyFieldConfig,\n temporal: boolean,\n nested: boolean,\n): 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, temporal);\n }\n\n const type = mapFieldTypeToColumnType(fieldType, temporal && !(nested && fieldType === \"time\"));\n switch (type) {\n case \"Timestamp\":\n usedUtilityTypes.Timestamp = true;\n break;\n case \"TemporalDate\":\n usedUtilityTypes.TemporalDate = true;\n break;\n case \"TemporalInstant\":\n usedUtilityTypes.TemporalInstant = true;\n break;\n case \"TemporalTime\":\n usedUtilityTypes.TemporalTime = true;\n break;\n case \"string\":\n case \"number\":\n case \"boolean\":\n break;\n }\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 * @param temporal - Whether date/datetime/time fields resolve to their Temporal column\n * types instead of their `Date`/`string` defaults\n * @param nested - Whether the field is inside an object\n * @returns The complete field type with used utility types\n */\nfunction generateFieldType(\n fieldConfig: KyselyFieldConfig,\n temporal: boolean,\n nested = false,\n): FieldTypeResult {\n const baseTypeResult = getBaseType(fieldConfig, temporal, nested);\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 * @param temporal - Whether date/datetime/time fields resolve to their Temporal column\n * types instead of their `Date`/`string` defaults\n * @returns The type definition and used utility types\n */\nfunction generateTableInterface(\n name: string,\n fields: Record<string, KyselyFieldConfig>,\n temporal: boolean,\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, temporal),\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 * @param temporal - Whether date/datetime/time fields resolve to their Temporal column\n * types instead of their `Date`/`string` defaults. Defaults to `false`.\n * @returns Generated Kysely type metadata\n */\nexport function processKyselyFields(\n name: string,\n fields: Record<string, KyselyFieldConfig>,\n temporal = false,\n): KyselyTypeMetadata {\n const result = generateTableInterface(name, fields, temporal);\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 * @param temporal - Whether date/datetime/time fields resolve to their Temporal column\n * types instead of their `Date`/`string` defaults. Defaults to `false`.\n * @returns Generated Kysely type metadata\n */\nexport async function processKyselyType(\n type: TailorDBType,\n temporal = false,\n): 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 temporal,\n );\n}\n\n/**\n * Generate unified types file from multiple namespaces.\n * @param namespaceData - Namespace metadata\n * @param temporal - Whether `kyselyTypePlugin` was configured with `{ temporal: true }`;\n * passed to `createGetDB` so the generated `getDB` always reads back the Temporal values\n * the tables above are typed with. Defaults to `false`.\n * @returns Generated types file contents\n */\nexport function generateUnifiedKyselyTypes(\n namespaceData: KyselyNamespaceMetadata[],\n temporal = false,\n): 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.TemporalDate) {\n utilityTypeImports.push(\"type TemporalDate\");\n }\n if (globalUsedUtilityTypes.TemporalInstant) {\n utilityTypeImports.push(\"type TemporalInstant\");\n }\n if (globalUsedUtilityTypes.TemporalTime) {\n utilityTypeImports.push(\"type TemporalTime\");\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>(${temporal ? \"{ temporal: true }\" : \"\"});\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 { 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 * When `true`, generated tables map date/datetime fields and top-level time fields to\n * `Temporal.PlainDate`/`Temporal.Instant`/`Temporal.PlainTime` columns instead of\n * their `Date`/`string` defaults, and the generated `getDB` reads them back as those\n * Temporal values. Nested time fields remain strings. Defaults to `false`.\n */\n temporal?: boolean;\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 * @param options.temporal - When `true`, generated tables map date/datetime fields and\n * top-level time fields to their `Temporal.PlainDate`/`Temporal.Instant`/\n * `Temporal.PlainTime` counterparts instead of `Date`/`string`. Nested time fields\n * remain strings. Defaults to `false`.\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, temporal } = 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, temporal);\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, temporal);\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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"}
|