@tailor-platform/sdk 2.4.0 → 2.5.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 (234) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/dist/aigateway-DsWDjzk4.mjs +2 -0
  3. package/dist/{aigateway-DR4Kwuik.mjs.map → aigateway-DsWDjzk4.mjs.map} +1 -1
  4. package/dist/application-DFOUovmN.mjs +1 -0
  5. package/dist/application-D_4vg1KR.mjs +192 -0
  6. package/dist/application-D_4vg1KR.mjs.map +1 -0
  7. package/dist/assert-WeXvmG4j.mjs +2 -0
  8. package/dist/{assert-DBxo8jPo.mjs.map → assert-WeXvmG4j.mjs.map} +1 -1
  9. package/dist/authconnection-CynFBIv8.mjs +2 -0
  10. package/dist/{authconnection-CZvi9ANm.mjs.map → authconnection-CynFBIv8.mjs.map} +1 -1
  11. package/dist/brand-C8nMKhJC.mjs +2 -0
  12. package/dist/{brand-Eo4pLXPJ.mjs.map → brand-C8nMKhJC.mjs.map} +1 -1
  13. package/dist/cli/commands/deploy/deploy.d.mts +2 -2
  14. package/dist/cli/commands/deploy/deployment-target.d.mts +2 -0
  15. package/dist/cli/commands/deploy/event-subscriptions.d.mts +3 -0
  16. package/dist/cli/commands/deploy/tailordb/apply.d.mts +3 -0
  17. package/dist/cli/commands/deploy/tailordb/index.d.mts +3 -10
  18. package/dist/cli/commands/deploy/tailordb/migration-validation.d.mts +5 -0
  19. package/dist/cli/commands/deploy/tailordb/plan.d.mts +8 -0
  20. package/dist/cli/commands/executor/jobs.d.mts +1 -1
  21. package/dist/cli/commands/generate/seed/bundler.d.mts +2 -2
  22. package/dist/cli/commands/show.d.mts +1 -1
  23. package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +11 -11
  24. package/dist/cli/commands/tailordb/migrate/generate.d.mts +4 -0
  25. package/dist/cli/commands/tailordb/migrate/rename-detection.d.mts +1 -1
  26. package/dist/cli/commands/tailordb/migrate/snapshot-comparison.d.mts +43 -0
  27. package/dist/cli/commands/tailordb/migrate/snapshot-files.d.mts +62 -0
  28. package/dist/cli/commands/tailordb/migrate/snapshot-local.d.mts +12 -0
  29. package/dist/cli/commands/tailordb/migrate/snapshot-migrations.d.mts +12 -0
  30. package/dist/cli/commands/tailordb/migrate/snapshot-remote.d.mts +1 -0
  31. package/dist/cli/commands/tailordb/migrate/snapshot-types.d.mts +3 -1
  32. package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +5 -117
  33. package/dist/cli/lib.d.mts +8 -4
  34. package/dist/cli/lib.mjs +1 -9
  35. package/dist/cli/lib.mjs.map +1 -1
  36. package/dist/cli/main.mjs +1170 -7754
  37. package/dist/cli/main.mjs.map +1 -1
  38. package/dist/cli/shared/seed-chunker.d.mts +5 -5
  39. package/dist/completion/zsh-worker.zsh +82 -9
  40. package/dist/configure/config/index.d.mts +1 -1
  41. package/dist/configure/config/types.d.mts +2 -2
  42. package/dist/configure/index.d.mts +48 -12
  43. package/dist/configure/index.mjs +1 -1267
  44. package/dist/configure/index.mjs.map +1 -1
  45. package/dist/configure/services/auth/types.d.mts +1 -1
  46. package/dist/configure/services/idp/types.d.mts +1 -1
  47. package/dist/configure/services/tailordb/schema.d.mts +4 -8
  48. package/dist/configure/services/tailordb/types.d.mts +1 -1
  49. package/dist/configure/services/workflow/execution-policy.d.mts +3 -3
  50. package/dist/configure/services/workflow/job.d.mts +12 -2
  51. package/dist/configure/types/field.types.d.mts +1 -1
  52. package/dist/configure/types/permission-operand.types.d.mts +1 -1
  53. package/dist/context-D0QjfxzD.mjs +2 -0
  54. package/dist/{context-DYQVYWqZ.mjs.map → context-D0QjfxzD.mjs.map} +1 -1
  55. package/dist/crashreport-B-_HTKLr.mjs +42 -0
  56. package/dist/{crashreport-Q0pDAlrj.mjs.map → crashreport-B-_HTKLr.mjs.map} +1 -1
  57. package/dist/crashreport-BmsRIdpy.mjs +1 -0
  58. package/dist/errors-DSEXKRVD.mjs +2 -0
  59. package/dist/errors-DSEXKRVD.mjs.map +1 -0
  60. package/dist/file-COPYfju_.mjs +2 -0
  61. package/dist/file-COPYfju_.mjs.map +1 -0
  62. package/dist/globals-CMHSnj4w.mjs +2 -0
  63. package/dist/{globals-B2nlxBrz.mjs.map → globals-CMHSnj4w.mjs.map} +1 -1
  64. package/dist/iconv-DlFMt2gW.mjs +2 -0
  65. package/dist/{iconv-QWaZh0TT.mjs.map → iconv-DlFMt2gW.mjs.map} +1 -1
  66. package/dist/idp-G_ojPBB5.mjs +2 -0
  67. package/dist/{idp-RA_swu-h.mjs.map → idp-G_ojPBB5.mjs.map} +1 -1
  68. package/dist/interceptor-DQg3cR_9.mjs +2 -0
  69. package/dist/{interceptor-e33JtsC-.mjs.map → interceptor-DQg3cR_9.mjs.map} +1 -1
  70. package/dist/kysely/index.mjs +1 -32
  71. package/dist/kysely/index.mjs.map +1 -1
  72. package/dist/kysely-type-BTi3BMtL.mjs +43 -0
  73. package/dist/kysely-type-BTi3BMtL.mjs.map +1 -0
  74. package/dist/logger-BcGy-u7G.mjs +9 -0
  75. package/dist/{logger-D0bEznPv.mjs.map → logger-BcGy-u7G.mjs.map} +1 -1
  76. package/dist/logger-DP2BjQ93.mjs +2 -0
  77. package/dist/{logger-BtX0suGY.mjs.map → logger-DP2BjQ93.mjs.map} +1 -1
  78. package/dist/multiline-EyzjEwn9.mjs +3 -0
  79. package/dist/{multiline-sfHpTZZK.mjs.map → multiline-EyzjEwn9.mjs.map} +1 -1
  80. package/dist/node-builtins-oBl7EbyV.mjs +2 -0
  81. package/dist/{node-builtins-CmaL2Cbq.mjs.map → node-builtins-oBl7EbyV.mjs.map} +1 -1
  82. package/dist/package-json-C690ceex.mjs +2 -0
  83. package/dist/{package-json-8b0O9TlX.mjs.map → package-json-C690ceex.mjs.map} +1 -1
  84. package/dist/package-json-CmqhDJ__.mjs +1 -0
  85. package/dist/parser/service/tailordb/type-source.d.mts +5 -5
  86. package/dist/parser/service/tailordb/types.d.mts +4 -4
  87. package/dist/platform-serialize-DkiTdHOt.mjs +2 -0
  88. package/dist/{platform-serialize-RoRtBS0v.mjs.map → platform-serialize-DkiTdHOt.mjs.map} +1 -1
  89. package/dist/plugin/builtin/enum-constants/index.mjs +5 -114
  90. package/dist/plugin/builtin/enum-constants/index.mjs.map +1 -1
  91. package/dist/plugin/builtin/file-utils/index.mjs +22 -104
  92. package/dist/plugin/builtin/file-utils/index.mjs.map +1 -1
  93. package/dist/plugin/builtin/kysely-type/index.d.mts +8 -1
  94. package/dist/plugin/builtin/kysely-type/index.mjs +1 -275
  95. package/dist/plugin/builtin/seed/index.d.mts +5 -0
  96. package/dist/plugin/builtin/seed/index.mjs +1 -3
  97. package/dist/plugin/builtin/seed/seed-type-processor.d.mts +0 -2
  98. package/dist/plugin/{get-generated-type.d.mts → get-generated-table.d.mts} +5 -5
  99. package/dist/plugin/index.d.mts +3 -2
  100. package/dist/plugin/index.mjs +1 -251
  101. package/dist/plugin/index.mjs.map +1 -1
  102. package/dist/plugin/types.d.mts +30 -23
  103. package/dist/plugin/with-context.d.mts +3 -3
  104. package/dist/register-ts-hook-Drnaproy.mjs +640 -0
  105. package/dist/register-ts-hook-Drnaproy.mjs.map +1 -0
  106. package/dist/registry-HlEaGvl5.mjs +2 -0
  107. package/dist/{registry-BIGVUrMB.mjs.map → registry-HlEaGvl5.mjs.map} +1 -1
  108. package/dist/repl-editor-BG1aDOfH.mjs +2 -0
  109. package/dist/{repl-editor-F9LotMNx.mjs.map → repl-editor-BG1aDOfH.mjs.map} +1 -1
  110. package/dist/runtime/aigateway.mjs +1 -3
  111. package/dist/runtime/authconnection.mjs +1 -3
  112. package/dist/runtime/context.mjs +1 -3
  113. package/dist/runtime/file.d.mts +21 -21
  114. package/dist/runtime/file.mjs +1 -3
  115. package/dist/runtime/globals.mjs +1 -1
  116. package/dist/runtime/iconv.mjs +1 -3
  117. package/dist/runtime/idp.mjs +1 -3
  118. package/dist/runtime/index.mjs +1 -11
  119. package/dist/runtime/logger.mjs +1 -3
  120. package/dist/runtime/secretmanager.mjs +1 -3
  121. package/dist/runtime/types.d.mts +1 -1
  122. package/dist/runtime/workflow.mjs +1 -3
  123. package/dist/schema-Ze_dI5VX.mjs +2 -0
  124. package/dist/schema-Ze_dI5VX.mjs.map +1 -0
  125. package/dist/secret-file-C9wp_FCX.mjs +2 -0
  126. package/dist/{secret-file-C3J4Gy8r.mjs.map → secret-file-C9wp_FCX.mjs.map} +1 -1
  127. package/dist/secretmanager-5olfnI1b.mjs +2 -0
  128. package/dist/{secretmanager-IY4UvinW.mjs.map → secretmanager-5olfnI1b.mjs.map} +1 -1
  129. package/dist/secretmanager-vHQoXdQz.mjs +2 -0
  130. package/dist/{secretmanager-CYlpffsz.mjs.map → secretmanager-vHQoXdQz.mjs.map} +1 -1
  131. package/dist/seed/index.mjs +8 -244
  132. package/dist/seed/index.mjs.map +1 -1
  133. package/dist/seed-DwqRFdqP.mjs +207 -0
  134. package/dist/seed-DwqRFdqP.mjs.map +1 -0
  135. package/dist/service-BVe9u2Rt.mjs +7 -0
  136. package/dist/service-BVe9u2Rt.mjs.map +1 -0
  137. package/dist/service-CpZELSBa.mjs +1 -0
  138. package/dist/service-DlgaUO4V.mjs +2 -0
  139. package/dist/{service-LTfp03ZZ.mjs.map → service-DlgaUO4V.mjs.map} +1 -1
  140. package/dist/service_pb-DGKCLuAs.mjs +2 -0
  141. package/dist/{service_pb-BEWj1pOV.mjs.map → service_pb-DGKCLuAs.mjs.map} +1 -1
  142. package/dist/service_pb-_aeFE-Nv.mjs +1 -0
  143. package/dist/telemetry-Bklv9kQY.mjs +2 -0
  144. package/dist/{telemetry-BRVdwh14.mjs.map → telemetry-Bklv9kQY.mjs.map} +1 -1
  145. package/dist/telemetry-DXHP3VVi.mjs +1 -0
  146. package/dist/type-source--ZNcV8RJ.mjs +2 -0
  147. package/dist/type-source--ZNcV8RJ.mjs.map +1 -0
  148. package/dist/types/executor.generated.d.mts +1 -1
  149. package/dist/user-agent-vdHYF3QL.mjs +2 -0
  150. package/dist/{user-agent-Bgsszb5I.mjs.map → user-agent-vdHYF3QL.mjs.map} +1 -1
  151. package/dist/utils/test/index.mjs +1 -109
  152. package/dist/utils/test/index.mjs.map +1 -1
  153. package/dist/vitest/environment.mjs +1 -41
  154. package/dist/vitest/environment.mjs.map +1 -1
  155. package/dist/vitest/index.mjs +1 -1986
  156. package/dist/vitest/index.mjs.map +1 -1
  157. package/dist/vitest/mocks/file.d.mts +8 -8
  158. package/dist/vitest/setup.mjs +1 -140
  159. package/dist/vitest/setup.mjs.map +1 -1
  160. package/dist/wait-point-invoker-eiP-IIux.mjs +2 -0
  161. package/dist/wait-point-invoker-eiP-IIux.mjs.map +1 -0
  162. package/dist/wait-point-registry-BrkwfyjS.mjs +2 -0
  163. package/dist/{wait-point-registry-TL99zotw.mjs.map → wait-point-registry-BrkwfyjS.mjs.map} +1 -1
  164. package/dist/workflow-Cs9ISw6j.mjs +2 -0
  165. package/dist/{workflow-Bamae_Yc.mjs.map → workflow-Cs9ISw6j.mjs.map} +1 -1
  166. package/dist/workspace_resource_pb--GeYQPXu.mjs +2 -0
  167. package/dist/{workspace_resource_pb-HNIL_Vfj.mjs.map → workspace_resource_pb--GeYQPXu.mjs.map} +1 -1
  168. package/docs/cli/function.md +66 -6
  169. package/docs/cli/setup.md +24 -14
  170. package/docs/cli/tailordb.md +2 -0
  171. package/docs/cli-reference.md +9 -8
  172. package/docs/github-actions.md +10 -9
  173. package/docs/migration/v2.md +13 -1
  174. package/docs/migration/v3.md +82 -0
  175. package/docs/plugin/custom.md +130 -85
  176. package/docs/plugin/index.md +3 -3
  177. package/docs/services/tailordb-migration.md +21 -7
  178. package/docs/services/workflow.md +10 -9
  179. package/package.json +11 -11
  180. package/postinstall.mjs +1 -1
  181. package/dist/aigateway-DR4Kwuik.mjs +0 -9
  182. package/dist/application-C8a0UJ-z.mjs +0 -6071
  183. package/dist/application-C8a0UJ-z.mjs.map +0 -1
  184. package/dist/application-D4zOiLLa.mjs +0 -3
  185. package/dist/assert-DBxo8jPo.mjs +0 -9
  186. package/dist/authconnection-CZvi9ANm.mjs +0 -9
  187. package/dist/brand-Eo4pLXPJ.mjs +0 -36
  188. package/dist/context-DYQVYWqZ.mjs +0 -23
  189. package/dist/crashreport-Cd4TqfZq.mjs +0 -3
  190. package/dist/crashreport-Q0pDAlrj.mjs +0 -427
  191. package/dist/errors-Bxut5_Sy.mjs +0 -89
  192. package/dist/errors-Bxut5_Sy.mjs.map +0 -1
  193. package/dist/file-BUcmVIH2.mjs +0 -58
  194. package/dist/file-BUcmVIH2.mjs.map +0 -1
  195. package/dist/globals-B2nlxBrz.mjs +0 -126
  196. package/dist/iconv-QWaZh0TT.mjs +0 -38
  197. package/dist/idp-RA_swu-h.mjs +0 -82
  198. package/dist/interceptor-e33JtsC-.mjs +0 -33
  199. package/dist/logger-BtX0suGY.mjs +0 -48
  200. package/dist/logger-D0bEznPv.mjs +0 -362
  201. package/dist/multiline-sfHpTZZK.mjs +0 -38
  202. package/dist/node-builtins-CmaL2Cbq.mjs +0 -36
  203. package/dist/package-json-8b0O9TlX.mjs +0 -17
  204. package/dist/package-json-Cv2Z-TqQ.mjs +0 -3
  205. package/dist/platform-serialize-RoRtBS0v.mjs +0 -46
  206. package/dist/plugin/builtin/kysely-type/index.mjs.map +0 -1
  207. package/dist/plugin/builtin/seed/types.d.mts +0 -2
  208. package/dist/register-ts-hook-Vqjza10T.mjs +0 -25679
  209. package/dist/register-ts-hook-Vqjza10T.mjs.map +0 -1
  210. package/dist/registry-BIGVUrMB.mjs +0 -54
  211. package/dist/repl-editor-F9LotMNx.mjs +0 -252
  212. package/dist/schema-D4TC4BC3.mjs +0 -640
  213. package/dist/schema-D4TC4BC3.mjs.map +0 -1
  214. package/dist/secret-file-C3J4Gy8r.mjs +0 -64
  215. package/dist/secretmanager-CYlpffsz.mjs +0 -13
  216. package/dist/secretmanager-IY4UvinW.mjs +0 -131
  217. package/dist/seed-Cibjmtry.mjs +0 -489
  218. package/dist/seed-Cibjmtry.mjs.map +0 -1
  219. package/dist/service-2grcAOCJ.mjs +0 -898
  220. package/dist/service-2grcAOCJ.mjs.map +0 -1
  221. package/dist/service-CaBPNDIZ.mjs +0 -3
  222. package/dist/service-LTfp03ZZ.mjs +0 -133
  223. package/dist/service_pb-BEWj1pOV.mjs +0 -167
  224. package/dist/service_pb-BjlhvK9E.mjs +0 -3
  225. package/dist/telemetry-BRVdwh14.mjs +0 -84
  226. package/dist/telemetry-CkbkeJxl.mjs +0 -3
  227. package/dist/type-source-DH_LH20p.mjs +0 -13
  228. package/dist/type-source-DH_LH20p.mjs.map +0 -1
  229. package/dist/user-agent-Bgsszb5I.mjs +0 -28
  230. package/dist/wait-point-invoker-__oE88_P.mjs +0 -148
  231. package/dist/wait-point-invoker-__oE88_P.mjs.map +0 -1
  232. package/dist/wait-point-registry-TL99zotw.mjs +0 -47
  233. package/dist/workflow-Bamae_Yc.mjs +0 -34
  234. package/dist/workspace_resource_pb-HNIL_Vfj.mjs +0 -860
@@ -1 +1 @@
1
- {"version":3,"file":"secretmanager-IY4UvinW.mjs","names":[],"sources":["../src/vitest/mocks/shared.ts","../src/vitest/mocks/secretmanager.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Shared helpers\n// ---------------------------------------------------------------------------\n\n// Attach a non-enumerable `Symbol.dispose` to a facade so it works with `using`.\nexport function withDispose<T extends object>(facade: T, dispose: () => void): T & Disposable {\n Object.defineProperty(facade, Symbol.dispose, {\n value: dispose,\n enumerable: false,\n writable: true,\n configurable: true,\n });\n return facade as T & Disposable;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function tailorRoot(): Record<string, any> {\n const g = globalThis as Record<string, unknown>;\n if (!g.tailor) {\n // Ensure the container (and the always-present context stub) exists even if\n // the base globals were not installed (e.g. a unit test that only acquires\n // a single mock without the tailor-runtime environment).\n g.tailor = { context: { getInvoker: () => null } };\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return g.tailor as Record<string, any>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function tailordbRoot(): Record<string, any> {\n const g = globalThis as Record<string, unknown>;\n if (!g.tailordb) {\n g.tailordb = {};\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return g.tailordb as Record<string, any>;\n}\n","import { vi } from \"vitest\";\nimport { assertDefined } from \"#/utils/assert\";\nimport { tailorRoot, withDispose } from \"./shared\";\n\ninterface SecretCall {\n method: \"getSecret\" | \"getSecrets\";\n vault: string;\n name?: string;\n names?: readonly string[];\n}\n\n/** Initial fixtures for a Secret Manager mock. */\nexport interface MockSecretmanagerOptions {\n /** Secrets to merge over fixtures inherited from the currently installed mock. */\n secrets?: Record<string, Record<string, string>>;\n}\n\n// ---------------------------------------------------------------------------\n// SecretManager Mock\n// ---------------------------------------------------------------------------\n\n// Hidden accessor key used to inherit the previous scope's secret store on\n// acquisition (so secrets seeded once outside tests — e.g. from tailor.config.ts\n// via setup.ts — remain visible) while still isolating per-test overrides.\nconst SECRET_STORE = Symbol(\"tailorSecretStore\");\n\n/**\n * Acquire a disposable mock for `tailor.secretmanager`. The secret store is\n * inherited (cloned) from the currently-installed mock on acquisition and\n * restored on dispose, so secrets seeded outside the test survive across\n * `using` scopes while per-test `setSecrets()` overrides stay isolated.\n * @param options - Initial Secret Manager fixtures\n * @returns Disposable SecretManager mock control object\n * @example\n * ```typescript\n * import { mockSecretmanager } from \"@tailor-platform/sdk/vitest\";\n *\n * test(\"reads secrets from vault\", async () => {\n * using sm = mockSecretmanager({ secrets: { \"my-vault\": { API_KEY: \"sk-123\" } } });\n * sm.setSecret(\"my-vault\", \"API_KEY\", \"replacement\");\n * // …\n * });\n * ```\n */\nexport function mockSecretmanager(options: MockSecretmanagerOptions = {}) {\n const root = tailorRoot();\n const prev = root.secretmanager;\n\n const holder: { store: Record<string, Record<string, string>> } = {\n // prior mock state may be absent\n // oxlint-disable-next-line typescript/no-unnecessary-condition\n store: structuredClone((prev?.[SECRET_STORE]?.store as typeof holder.store) ?? {}),\n };\n for (const [vault, secrets] of Object.entries(options.secrets ?? {})) {\n holder.store[vault] = { ...holder.store[vault], ...secrets };\n }\n\n async function defaultGetSecret(vault: string, name: string): Promise<string | undefined> {\n return holder.store[vault]?.[name];\n }\n\n async function defaultGetSecrets<const T extends readonly string[]>(\n vault: string,\n names: T,\n ): Promise<Partial<Record<T[number], string>>> {\n const vaultData = holder.store[vault] ?? {};\n const result: Record<string, string> = {};\n for (const name of names) {\n if (name in vaultData) {\n result[name] = assertDefined(vaultData[name], `vault entry missing for: ${name}`);\n }\n }\n return result as Partial<Record<T[number], string>>;\n }\n\n const getSecret = vi.fn(defaultGetSecret);\n const getSecrets = vi.fn(defaultGetSecrets);\n\n root.secretmanager = { getSecret, getSecrets, [SECRET_STORE]: holder };\n\n const facade = {\n /** The `getSecret` `vi.fn`. */\n getSecret,\n /** The `getSecrets` `vi.fn`. */\n getSecrets,\n\n setSecrets(secrets: Record<string, Record<string, string>>): void {\n holder.store = secrets;\n },\n\n setSecret(vault: string, name: string, value: string): void {\n holder.store = {\n ...holder.store,\n [vault]: { ...holder.store[vault], [name]: value },\n };\n },\n\n mergeSecrets(vault: string, secrets: Record<string, string>): void {\n holder.store = {\n ...holder.store,\n [vault]: { ...holder.store[vault], ...secrets },\n };\n },\n\n get calls(): SecretCall[] {\n // Merge both methods' calls back into chronological order via vi.fn's\n // global invocationCallOrder, so a test mixing getSecret/getSecrets sees\n // them in the order they actually ran (not all getSecret, then all getSecrets).\n const entries: { order: number; call: SecretCall }[] = [\n ...getSecret.mock.calls.map((args, i) => ({\n order: getSecret.mock.invocationCallOrder[i] ?? 0,\n call: { method: \"getSecret\" as const, vault: args[0] as string, name: args[1] as string },\n })),\n ...getSecrets.mock.calls.map((args, i) => ({\n order: getSecrets.mock.invocationCallOrder[i] ?? 0,\n call: {\n method: \"getSecrets\" as const,\n vault: args[0] as string,\n names: args[1] as readonly string[],\n },\n })),\n ];\n return entries.toSorted((a, b) => a.order - b.order).map((e) => e.call);\n },\n\n clear(): void {\n getSecret.mockClear();\n getSecrets.mockClear();\n },\n\n reset(): void {\n holder.store = {};\n getSecret.mockReset();\n getSecret.mockImplementation(defaultGetSecret);\n getSecrets.mockReset();\n getSecrets.mockImplementation(defaultGetSecrets);\n },\n };\n\n return withDispose(facade, () => {\n root.secretmanager = prev;\n });\n}\n"],"mappings":";;;;AAKA,SAAgB,YAA8B,QAAW,SAAqC;CAC5F,OAAO,eAAe,QAAQ,OAAO,SAAS;EAC5C,OAAO;EACP,YAAY;EACZ,UAAU;EACV,cAAc;CAChB,CAAC;CACD,OAAO;AACT;AAGA,SAAgB,aAAkC;CAChD,MAAM,IAAI;CACV,IAAI,CAAC,EAAE,QAIL,EAAE,SAAS,EAAE,SAAS,EAAE,kBAAkB,KAAK,EAAE;CAGnD,OAAO,EAAE;AACX;AAGA,SAAgB,eAAoC;CAClD,MAAM,IAAI;CACV,IAAI,CAAC,EAAE,UACL,EAAE,WAAW,CAAC;CAGhB,OAAO,EAAE;AACX;;;;ACZA,MAAM,eAAe,OAAO,mBAAmB;;;;;;;;;;;;;;;;;;;AAoB/C,SAAgB,kBAAkB,UAAoC,CAAC,GAAG;CACxE,MAAM,OAAO,WAAW;CACxB,MAAM,OAAO,KAAK;CAElB,MAAM,SAA4D,EAGhE,OAAO,gBAAiB,OAAO,aAAa,EAAE,SAAiC,CAAC,CAAC,EACnF;CACA,KAAK,MAAM,CAAC,OAAO,YAAY,OAAO,QAAQ,QAAQ,WAAW,CAAC,CAAC,GACjE,OAAO,MAAM,SAAS;EAAE,GAAG,OAAO,MAAM;EAAQ,GAAG;CAAQ;CAG7D,eAAe,iBAAiB,OAAe,MAA2C;EACxF,OAAO,OAAO,MAAM,MAAM,GAAG;CAC/B;CAEA,eAAe,kBACb,OACA,OAC6C;EAC7C,MAAM,YAAY,OAAO,MAAM,UAAU,CAAC;EAC1C,MAAM,SAAiC,CAAC;EACxC,KAAK,MAAM,QAAQ,OACjB,IAAI,QAAQ,WACV,OAAO,QAAQ,cAAc,UAAU,OAAO,4BAA4B,MAAM;EAGpF,OAAO;CACT;CAEA,MAAM,YAAY,GAAG,GAAG,gBAAgB;CACxC,MAAM,aAAa,GAAG,GAAG,iBAAiB;CAE1C,KAAK,gBAAgB;EAAE;EAAW;GAAa,eAAe;CAAO;CA6DrE,OAAO,YAAY;;EAzDjB;;EAEA;EAEA,WAAW,SAAuD;GAChE,OAAO,QAAQ;EACjB;EAEA,UAAU,OAAe,MAAc,OAAqB;GAC1D,OAAO,QAAQ;IACb,GAAG,OAAO;KACT,QAAQ;KAAE,GAAG,OAAO,MAAM;MAAS,OAAO;IAAM;GACnD;EACF;EAEA,aAAa,OAAe,SAAuC;GACjE,OAAO,QAAQ;IACb,GAAG,OAAO;KACT,QAAQ;KAAE,GAAG,OAAO,MAAM;KAAQ,GAAG;IAAQ;GAChD;EACF;EAEA,IAAI,QAAsB;GAkBxB,OAAO,CAbL,GAAG,UAAU,KAAK,MAAM,KAAK,MAAM,OAAO;IACxC,OAAO,UAAU,KAAK,oBAAoB,MAAM;IAChD,MAAM;KAAE,QAAQ;KAAsB,OAAO,KAAK;KAAc,MAAM,KAAK;IAAa;GAC1F,EAAE,GACF,GAAG,WAAW,KAAK,MAAM,KAAK,MAAM,OAAO;IACzC,OAAO,WAAW,KAAK,oBAAoB,MAAM;IACjD,MAAM;KACJ,QAAQ;KACR,OAAO,KAAK;KACZ,OAAO,KAAK;IACd;GACF,EAAE,CAES,CAAC,CAAC,UAAU,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,KAAK,MAAM,EAAE,IAAI;EACxE;EAEA,QAAc;GACZ,UAAU,UAAU;GACpB,WAAW,UAAU;EACvB;EAEA,QAAc;GACZ,OAAO,QAAQ,CAAC;GAChB,UAAU,UAAU;GACpB,UAAU,mBAAmB,gBAAgB;GAC7C,WAAW,UAAU;GACrB,WAAW,mBAAmB,iBAAiB;EACjD;CAGsB,SAAS;EAC/B,KAAK,gBAAgB;CACvB,CAAC;AACH"}
1
+ {"version":3,"file":"secretmanager-5olfnI1b.mjs","names":[],"sources":["../src/vitest/mocks/shared.ts","../src/vitest/mocks/secretmanager.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Shared helpers\n// ---------------------------------------------------------------------------\n\n// Attach a non-enumerable `Symbol.dispose` to a facade so it works with `using`.\nexport function withDispose<T extends object>(facade: T, dispose: () => void): T & Disposable {\n Object.defineProperty(facade, Symbol.dispose, {\n value: dispose,\n enumerable: false,\n writable: true,\n configurable: true,\n });\n return facade as T & Disposable;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function tailorRoot(): Record<string, any> {\n const g = globalThis as Record<string, unknown>;\n if (!g.tailor) {\n // Ensure the container (and the always-present context stub) exists even if\n // the base globals were not installed (e.g. a unit test that only acquires\n // a single mock without the tailor-runtime environment).\n g.tailor = { context: { getInvoker: () => null } };\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return g.tailor as Record<string, any>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function tailordbRoot(): Record<string, any> {\n const g = globalThis as Record<string, unknown>;\n if (!g.tailordb) {\n g.tailordb = {};\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return g.tailordb as Record<string, any>;\n}\n","import { vi } from \"vitest\";\nimport { assertDefined } from \"#/utils/assert\";\nimport { tailorRoot, withDispose } from \"./shared\";\n\ninterface SecretCall {\n method: \"getSecret\" | \"getSecrets\";\n vault: string;\n name?: string;\n names?: readonly string[];\n}\n\n/** Initial fixtures for a Secret Manager mock. */\nexport interface MockSecretmanagerOptions {\n /** Secrets to merge over fixtures inherited from the currently installed mock. */\n secrets?: Record<string, Record<string, string>>;\n}\n\n// ---------------------------------------------------------------------------\n// SecretManager Mock\n// ---------------------------------------------------------------------------\n\n// Hidden accessor key used to inherit the previous scope's secret store on\n// acquisition (so secrets seeded once outside tests — e.g. from tailor.config.ts\n// via setup.ts — remain visible) while still isolating per-test overrides.\nconst SECRET_STORE = Symbol(\"tailorSecretStore\");\n\n/**\n * Acquire a disposable mock for `tailor.secretmanager`. The secret store is\n * inherited (cloned) from the currently-installed mock on acquisition and\n * restored on dispose, so secrets seeded outside the test survive across\n * `using` scopes while per-test `setSecrets()` overrides stay isolated.\n * @param options - Initial Secret Manager fixtures\n * @returns Disposable SecretManager mock control object\n * @example\n * ```typescript\n * import { mockSecretmanager } from \"@tailor-platform/sdk/vitest\";\n *\n * test(\"reads secrets from vault\", async () => {\n * using sm = mockSecretmanager({ secrets: { \"my-vault\": { API_KEY: \"sk-123\" } } });\n * sm.setSecret(\"my-vault\", \"API_KEY\", \"replacement\");\n * // …\n * });\n * ```\n */\nexport function mockSecretmanager(options: MockSecretmanagerOptions = {}) {\n const root = tailorRoot();\n const prev = root.secretmanager;\n\n const holder: { store: Record<string, Record<string, string>> } = {\n // prior mock state may be absent\n // oxlint-disable-next-line typescript/no-unnecessary-condition\n store: structuredClone((prev?.[SECRET_STORE]?.store as typeof holder.store) ?? {}),\n };\n for (const [vault, secrets] of Object.entries(options.secrets ?? {})) {\n holder.store[vault] = { ...holder.store[vault], ...secrets };\n }\n\n async function defaultGetSecret(vault: string, name: string): Promise<string | undefined> {\n return holder.store[vault]?.[name];\n }\n\n async function defaultGetSecrets<const T extends readonly string[]>(\n vault: string,\n names: T,\n ): Promise<Partial<Record<T[number], string>>> {\n const vaultData = holder.store[vault] ?? {};\n const result: Record<string, string> = {};\n for (const name of names) {\n if (name in vaultData) {\n result[name] = assertDefined(vaultData[name], `vault entry missing for: ${name}`);\n }\n }\n return result as Partial<Record<T[number], string>>;\n }\n\n const getSecret = vi.fn(defaultGetSecret);\n const getSecrets = vi.fn(defaultGetSecrets);\n\n root.secretmanager = { getSecret, getSecrets, [SECRET_STORE]: holder };\n\n const facade = {\n /** The `getSecret` `vi.fn`. */\n getSecret,\n /** The `getSecrets` `vi.fn`. */\n getSecrets,\n\n setSecrets(secrets: Record<string, Record<string, string>>): void {\n holder.store = secrets;\n },\n\n setSecret(vault: string, name: string, value: string): void {\n holder.store = {\n ...holder.store,\n [vault]: { ...holder.store[vault], [name]: value },\n };\n },\n\n mergeSecrets(vault: string, secrets: Record<string, string>): void {\n holder.store = {\n ...holder.store,\n [vault]: { ...holder.store[vault], ...secrets },\n };\n },\n\n get calls(): SecretCall[] {\n // Merge both methods' calls back into chronological order via vi.fn's\n // global invocationCallOrder, so a test mixing getSecret/getSecrets sees\n // them in the order they actually ran (not all getSecret, then all getSecrets).\n const entries: { order: number; call: SecretCall }[] = [\n ...getSecret.mock.calls.map((args, i) => ({\n order: getSecret.mock.invocationCallOrder[i] ?? 0,\n call: { method: \"getSecret\" as const, vault: args[0], name: args[1] },\n })),\n ...getSecrets.mock.calls.map((args, i) => ({\n order: getSecrets.mock.invocationCallOrder[i] ?? 0,\n call: {\n method: \"getSecrets\" as const,\n vault: args[0],\n names: args[1],\n },\n })),\n ];\n return entries.toSorted((a, b) => a.order - b.order).map((e) => e.call);\n },\n\n clear(): void {\n getSecret.mockClear();\n getSecrets.mockClear();\n },\n\n reset(): void {\n holder.store = {};\n getSecret.mockReset();\n getSecret.mockImplementation(defaultGetSecret);\n getSecrets.mockReset();\n getSecrets.mockImplementation(defaultGetSecrets);\n },\n };\n\n return withDispose(facade, () => {\n root.secretmanager = prev;\n });\n}\n"],"mappings":"sEAKA,SAAgB,YAA8B,EAAW,EAAqC,CAO5F,OANA,OAAO,eAAe,EAAQ,OAAO,QAAS,CAC5C,MAAO,EACP,WAAY,GACZ,SAAU,GACV,aAAc,EAChB,CAAC,EACM,CACT,CAGA,SAAgB,YAAkC,CAChD,IAAM,EAAI,WAQV,MAPA,CAIE,EAAE,SAAS,CAAE,QAAS,CAAE,eAAkB,IAAK,CAAE,EAG5C,EAAE,MACX,CAGA,SAAgB,cAAoC,CAClD,IAAM,EAAI,WAKV,MAJA,CACE,EAAE,WAAW,CAAC,EAGT,EAAE,QACX,CCZA,MAAM,EAAe,OAAO,mBAAmB,EAoB/C,SAAgB,kBAAkB,EAAoC,CAAC,EAAG,CACxE,IAAM,EAAO,WAAW,EAClB,EAAO,EAAK,cAEZ,EAA4D,CAGhE,MAAO,gBAAiB,IAAO,EAAa,EAAE,OAAiC,CAAC,CAAC,CACnF,EACA,IAAK,GAAM,CAAC,EAAO,KAAY,OAAO,QAAQ,EAAQ,SAAW,CAAC,CAAC,EACjE,EAAO,MAAM,GAAS,CAAE,GAAG,EAAO,MAAM,GAAQ,GAAG,CAAQ,EAG7D,eAAe,iBAAiB,EAAe,EAA2C,CACxF,OAAO,EAAO,MAAM,EAAM,GAAG,EAC/B,CAEA,eAAe,kBACb,EACA,EAC6C,CAC7C,IAAM,EAAY,EAAO,MAAM,IAAU,CAAC,EACpC,EAAiC,CAAC,EACxC,IAAK,IAAM,KAAQ,EACb,KAAQ,IACV,EAAO,GAAQ,EAAc,EAAU,GAAO,4BAA4B,GAAM,GAGpF,OAAO,CACT,CAEA,IAAM,EAAY,EAAG,GAAG,gBAAgB,EAClC,EAAa,EAAG,GAAG,iBAAiB,EA+D1C,MA7DA,GAAK,cAAgB,CAAE,YAAW,cAAa,GAAe,CAAO,EA6D9D,YAAY,CAzDjB,YAEA,aAEA,WAAW,EAAuD,CAChE,EAAO,MAAQ,CACjB,EAEA,UAAU,EAAe,EAAc,EAAqB,CAC1D,EAAO,MAAQ,CACb,GAAG,EAAO,OACT,GAAQ,CAAE,GAAG,EAAO,MAAM,IAAS,GAAO,CAAM,CACnD,CACF,EAEA,aAAa,EAAe,EAAuC,CACjE,EAAO,MAAQ,CACb,GAAG,EAAO,OACT,GAAQ,CAAE,GAAG,EAAO,MAAM,GAAQ,GAAG,CAAQ,CAChD,CACF,EAEA,IAAI,OAAsB,CAkBxB,MAAO,CAbL,GAAG,EAAU,KAAK,MAAM,KAAK,EAAM,KAAO,CACxC,MAAO,EAAU,KAAK,oBAAoB,IAAM,EAChD,KAAM,CAAE,OAAQ,YAAsB,MAAO,EAAK,GAAI,KAAM,EAAK,EAAG,CACtE,EAAE,EACF,GAAG,EAAW,KAAK,MAAM,KAAK,EAAM,KAAO,CACzC,MAAO,EAAW,KAAK,oBAAoB,IAAM,EACjD,KAAM,CACJ,OAAQ,aACR,MAAO,EAAK,GACZ,MAAO,EAAK,EACd,CACF,EAAE,CAES,CAAC,CAAC,UAAU,EAAG,IAAM,EAAE,MAAQ,EAAE,KAAK,CAAC,CAAC,IAAK,GAAM,EAAE,IAAI,CACxE,EAEA,OAAc,CACZ,EAAU,UAAU,EACpB,EAAW,UAAU,CACvB,EAEA,OAAc,CACZ,EAAO,MAAQ,CAAC,EAChB,EAAU,UAAU,EACpB,EAAU,mBAAmB,gBAAgB,EAC7C,EAAW,UAAU,EACrB,EAAW,mBAAmB,iBAAiB,CACjD,CAGsB,MAAS,CAC/B,EAAK,cAAgB,CACvB,CAAC,CACH"}
@@ -0,0 +1,2 @@
1
+ const api=()=>globalThis.tailor.secretmanager,e={getSecrets:(...e)=>api().getSecrets(...e),getSecret:(...e)=>api().getSecret(...e)};export{e as t};
2
+ //# sourceMappingURL=secretmanager-vHQoXdQz.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"secretmanager-CYlpffsz.mjs","names":[],"sources":["../src/runtime/secretmanager.ts"],"sourcesContent":["/**\n * Secret manager utilities.\n *\n * Thin typed wrapper around the platform-provided `tailor.secretmanager` runtime API.\n * At runtime this delegates to `globalThis.tailor.secretmanager`. Use\n * `mockSecretmanager` from `@tailor-platform/sdk/vitest` to mock these calls\n * in unit tests.\n * @example\n * import { secretmanager } from \"@tailor-platform/sdk/runtime\";\n *\n * const apiKey = await secretmanager.getSecret(\"my-vault\", \"API_KEY\");\n * const all = await secretmanager.getSecrets(\"my-vault\", [\"A\", \"B\"] as const);\n */\n\n/**\n * Platform API surface for `tailor.secretmanager`. Describes the shape the\n * platform runtime injects on `globalThis.tailor.secretmanager`.\n */\nexport interface TailorSecretmanagerAPI {\n /**\n * Returns multiple secrets from a vault. Missing names are omitted from the result.\n * @param vault - Vault name\n * @param names - Secret names to fetch (use `as const` to narrow the result key)\n * @returns Partial record keyed by the requested names\n */\n getSecrets<const T extends readonly string[]>(\n vault: string,\n names: T,\n ): Promise<Partial<Record<T[number], string>>>;\n\n /**\n * Returns a single secret from a vault, or `undefined` when missing.\n * @param vault - Vault name\n * @param name - Secret name\n * @returns The secret value, or `undefined` if not present\n */\n getSecret(vault: string, name: string): Promise<string | undefined>;\n}\n\nconst api = (): TailorSecretmanagerAPI =>\n (globalThis as unknown as { tailor: { secretmanager: TailorSecretmanagerAPI } }).tailor\n .secretmanager;\n\nconst getSecrets: TailorSecretmanagerAPI[\"getSecrets\"] = (...args) => api().getSecrets(...args);\n\nconst getSecret: TailorSecretmanagerAPI[\"getSecret\"] = (...args) => api().getSecret(...args);\n\n/** Runtime wrapper namespace for `tailor.secretmanager`. */\nexport const secretmanager = {\n getSecrets,\n getSecret,\n} as const satisfies TailorSecretmanagerAPI;\n"],"mappings":";AAuCA,MAAM,YACH,WAAgF,OAC9E;AAEL,MAAM,cAAoD,GAAG,SAAS,IAAI,CAAC,CAAC,WAAW,GAAG,IAAI;AAE9F,MAAM,aAAkD,GAAG,SAAS,IAAI,CAAC,CAAC,UAAU,GAAG,IAAI;;AAG3F,MAAa,gBAAgB;CAC3B;CACA;AACF"}
1
+ {"version":3,"file":"secretmanager-vHQoXdQz.mjs","names":[],"sources":["../src/runtime/secretmanager.ts"],"sourcesContent":["/**\n * Secret manager utilities.\n *\n * Thin typed wrapper around the platform-provided `tailor.secretmanager` runtime API.\n * At runtime this delegates to `globalThis.tailor.secretmanager`. Use\n * `mockSecretmanager` from `@tailor-platform/sdk/vitest` to mock these calls\n * in unit tests.\n * @example\n * import { secretmanager } from \"@tailor-platform/sdk/runtime\";\n *\n * const apiKey = await secretmanager.getSecret(\"my-vault\", \"API_KEY\");\n * const all = await secretmanager.getSecrets(\"my-vault\", [\"A\", \"B\"] as const);\n */\n\n/**\n * Platform API surface for `tailor.secretmanager`. Describes the shape the\n * platform runtime injects on `globalThis.tailor.secretmanager`.\n */\nexport interface TailorSecretmanagerAPI {\n /**\n * Returns multiple secrets from a vault. Missing names are omitted from the result.\n * @param vault - Vault name\n * @param names - Secret names to fetch (use `as const` to narrow the result key)\n * @returns Partial record keyed by the requested names\n */\n getSecrets<const T extends readonly string[]>(\n vault: string,\n names: T,\n ): Promise<Partial<Record<T[number], string>>>;\n\n /**\n * Returns a single secret from a vault, or `undefined` when missing.\n * @param vault - Vault name\n * @param name - Secret name\n * @returns The secret value, or `undefined` if not present\n */\n getSecret(vault: string, name: string): Promise<string | undefined>;\n}\n\nconst api = (): TailorSecretmanagerAPI =>\n (globalThis as unknown as { tailor: { secretmanager: TailorSecretmanagerAPI } }).tailor\n .secretmanager;\n\nconst getSecrets: TailorSecretmanagerAPI[\"getSecrets\"] = (...args) => api().getSecrets(...args);\n\nconst getSecret: TailorSecretmanagerAPI[\"getSecret\"] = (...args) => api().getSecret(...args);\n\n/** Runtime wrapper namespace for `tailor.secretmanager`. */\nexport const secretmanager = {\n getSecrets,\n getSecret,\n} as const satisfies TailorSecretmanagerAPI;\n"],"mappings":"AAuCA,MAAM,QACH,WAAgF,OAC9E,cAOQ,EAAgB,CAC3B,YANwD,GAAG,IAAS,IAAI,CAAC,CAAC,WAAW,GAAG,CAAI,EAO5F,WALsD,GAAG,IAAS,IAAI,CAAC,CAAC,UAAU,GAAG,CAAI,CAM3F"}
@@ -1,245 +1,9 @@
1
- import { basename, dirname, join } from "pathe";
2
- import { pathToFileURL } from "node:url";
3
- import { readFile, readdir, stat, writeFile } from "node:fs/promises";
4
- import { ErrorFormatter, LinesDB, defineSchema, findSchemaFile } from "@toiroakr/lines-db";
5
-
6
- //#region src/seed/index.ts
7
- /**
8
- * Seed integration module for generated seed code.
9
- *
10
- * Re-exports `@toiroakr/lines-db` through a single import path
11
- * to avoid phantom dependency issues with pnpm, and provides
12
- * seed-specific utility functions used by the code generator.
13
- */
14
- /** Fields `fillSeedData` writes when the caller names none. */
15
- const DEFAULT_FILL_FIELDS = ["id"];
16
- async function resolveSeedDataTarget(resolvedPath) {
17
- const stats = await stat(resolvedPath);
18
- if (stats.isDirectory()) return { dataDir: resolvedPath };
19
- if (stats.isFile() && resolvedPath.endsWith(".jsonl")) return {
20
- dataDir: dirname(resolvedPath),
21
- tableName: basename(resolvedPath, ".jsonl")
22
- };
23
- throw new Error(`Invalid path: ${resolvedPath}. Must be a directory or .jsonl file.`);
24
- }
25
- async function listSeedTables(dataDir) {
26
- return (await readdir(dataDir)).filter((entry) => entry.endsWith(".jsonl")).map((entry) => basename(entry, ".jsonl")).toSorted();
27
- }
28
- function formatWarnings(warnings) {
29
- if (warnings.length === 0) return [];
30
- return [...warnings.map((warning) => `⚠ ${warning}`), ""];
31
- }
32
- function formatValidationErrors(errors, verbose) {
33
- const formatter = new ErrorFormatter({ verbose });
34
- const errorLines = [];
35
- const errorsByFile = /* @__PURE__ */ new Map();
36
- for (const error of errors) {
37
- const fileErrors = errorsByFile.get(error.file) || [];
38
- fileErrors.push(error);
39
- errorsByFile.set(error.file, fileErrors);
40
- }
41
- for (const [file, fileErrors] of errorsByFile) {
42
- errorLines.push(formatter.formatErrorHeader(fileErrors.length, file));
43
- errorLines.push("");
44
- const validationErrors = fileErrors.filter((e) => e.type !== "foreignKey" || !e.foreignKeyError);
45
- const foreignKeyErrors = fileErrors.filter((e) => e.type === "foreignKey" && e.foreignKeyError);
46
- if (validationErrors.length > 0) errorLines.push(formatter.formatValidationErrors(validationErrors.map((e) => ({
47
- file: e.file,
48
- rowIndex: e.rowIndex,
49
- issues: e.issues
50
- }))));
51
- for (const fkError of foreignKeyErrors) if (fkError.foreignKeyError) errorLines.push(formatter.formatForeignKeyError({
52
- file: fkError.file,
53
- rowIndex: fkError.rowIndex,
54
- column: fkError.foreignKeyError.column,
55
- value: fkError.foreignKeyError.value,
56
- referencedTable: fkError.foreignKeyError.referencedTable,
57
- referencedColumn: fkError.foreignKeyError.referencedColumn
58
- }));
59
- errorLines.push("");
60
- }
61
- return errorLines.join("\n");
62
- }
63
- /**
64
- * Validate JSONL seed data against schema definitions.
65
- * Resolves the given path (directory or `.jsonl` file), validates the rows it
66
- * holds, and returns formatted output and error messages.
67
- * @param options - Validation options including path and verbose flag
68
- * @returns Validation result with output messages and optional error details
69
- */
70
- async function validateSeedData(options) {
71
- const { path: resolvedPath, verbose = false } = options;
72
- const { dataDir, tableName } = await resolveSeedDataTarget(resolvedPath);
73
- const db = LinesDB.create({ dataDir });
74
- let result;
75
- try {
76
- result = await db.initialize({
77
- tableName,
78
- detailedValidate: true
79
- });
80
- } finally {
81
- await db.close();
82
- }
83
- const outputLines = formatWarnings(result.warnings);
84
- if (result.valid) {
85
- outputLines.push("✓ All records are valid");
86
- return {
87
- valid: true,
88
- output: outputLines.join("\n")
89
- };
90
- }
91
- return {
92
- valid: false,
93
- output: outputLines.join("\n"),
94
- error: formatValidationErrors(result.errors, verbose)
95
- };
96
- }
97
- function ownValue(row, field) {
98
- return Object.hasOwn(row, field) ? row[field] : void 0;
99
- }
100
- function setField(row, field, value) {
101
- Object.defineProperty(row, field, {
102
- value,
103
- enumerable: true,
104
- writable: true,
105
- configurable: true
106
- });
107
- }
108
- function isBlank(value) {
109
- if (value === void 0 || value === null) return true;
110
- if (typeof value !== "object" || Array.isArray(value)) return false;
111
- return Object.values(value).every(isBlank);
112
- }
113
- function splitLines(content) {
114
- if (content === "") return [];
115
- return content.split("\n").map((raw, index, all) => {
116
- const eol = index === all.length - 1 ? "" : "\n";
117
- const text = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
118
- const carriage = raw.endsWith("\r") ? "\r" : "";
119
- let row;
120
- if (text.trim() !== "") try {
121
- const parsed = JSON.parse(text);
122
- row = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
123
- } catch {
124
- row = void 0;
125
- }
126
- return {
127
- text,
128
- eol: `${carriage}${eol}`,
129
- row
130
- };
131
- });
132
- }
133
- function serializeRow(row, fieldOrder) {
134
- const rank = new Map(fieldOrder.map((field, index) => [field, index]));
135
- const rankOf = (key) => rank.get(key) ?? fieldOrder.length;
136
- return JSON.stringify(Object.fromEntries(Object.entries(row).toSorted(([a], [b]) => rankOf(a) - rankOf(b))));
137
- }
138
- async function loadSeedHook(dataDir, table) {
139
- const schemaPath = await findSchemaFile(dataDir, table);
140
- if (!schemaPath) return;
141
- const hook = (await import(pathToFileURL(schemaPath).href)).hook;
142
- if (typeof hook !== "function") throw new Error(`${schemaPath} does not export \`hook\`. Run \`tailor generate\` to regenerate the seed schema files.`);
143
- return hook;
144
- }
145
- /**
146
- * Fill in the values a record gets on create for the JSONL seed data rows that
147
- * are missing them, so a row can be referenced by `id` or carry a timestamp
148
- * before it is ever seeded.
149
- *
150
- * The values come from the table's own create-time behavior — its `id`, its field
151
- * defaults, and its create hooks — applied to each row on its own. Nothing is
152
- * validated, so a row can be filled while the data around it is still
153
- * incomplete: that is what lets you get the ids you need in order to write the
154
- * rows that reference them. Run `validateSeedData` when the data is ready.
155
- *
156
- * Only the named fields are written, and only into a row that has no value for
157
- * them, so a value already in the file is never replaced. A line that gains
158
- * nothing is written back exactly as it was, byte for byte; a line that does get
159
- * a value is re-serialized with its keys in the order the table declares its
160
- * fields, so a filled-in `id` lands at the front. A field the table gives no
161
- * value to — one it does not declare, or one the platform assigns such as a
162
- * serial field — is skipped, so one field list covers a whole data directory.
163
- *
164
- * The values are read from the schema files generated next to the data, and all
165
- * of them are read before anything is written: a file that predates the current
166
- * generator stops the run with nothing filled in anywhere.
167
- * @param options - Fill options including path and fields
168
- * @returns Which files received which fields
169
- */
170
- async function fillSeedData(options) {
171
- const { path: resolvedPath, fields = DEFAULT_FILL_FIELDS } = options;
172
- if (fields.length === 0) throw new Error("No fields to fill. Name at least one field.");
173
- const { dataDir, tableName } = await resolveSeedDataTarget(resolvedPath);
174
- const tables = tableName ? [tableName] : await listSeedTables(dataDir);
175
- const warnings = [];
176
- const filled = [];
177
- const producedFields = /* @__PURE__ */ new Set();
178
- const hooks = [];
179
- for (const table of tables) {
180
- const hook = await loadSeedHook(dataDir, table);
181
- if (!hook) {
182
- warnings.push(`No schema file for ${table}, so nothing can be filled in there`);
183
- continue;
184
- }
185
- hooks.push({
186
- table,
187
- hook
188
- });
189
- }
190
- const writes = [];
191
- for (const { table, hook } of hooks) {
192
- const file = join(dataDir, `${table}.jsonl`);
193
- const lines = splitLines(await readFile(file, "utf-8"));
194
- const written = /* @__PURE__ */ new Set();
195
- const unreadable = [];
196
- let count = 0;
197
- let fieldOrder = [];
198
- for (const [index, line] of lines.entries()) {
199
- const row = line.row;
200
- if (!row) {
201
- if (line.text.trim() !== "") unreadable.push(index + 1);
202
- continue;
203
- }
204
- const hooked = hook(row);
205
- fieldOrder = Object.keys(hooked);
206
- const gained = fields.filter((field) => {
207
- if (isBlank(ownValue(hooked, field))) return false;
208
- producedFields.add(field);
209
- return isBlank(ownValue(row, field));
210
- });
211
- if (gained.length === 0) continue;
212
- for (const field of gained) {
213
- setField(row, field, hooked[field]);
214
- written.add(field);
215
- }
216
- line.text = serializeRow(row, fieldOrder);
217
- count += 1;
218
- }
219
- if (unreadable.length > 0) warnings.push(`${file}: line(s) ${unreadable.join(", ")} are not JSON objects, so nothing was filled in there`);
220
- if (count === 0) continue;
221
- writes.push({
222
- file,
223
- content: lines.map((line) => `${line.text}${line.eol}`).join("")
224
- });
225
- filled.push({
226
- table,
227
- file,
228
- fields: [...written],
229
- count
230
- });
231
- }
232
- for (const { file, content } of writes) await writeFile(file, content);
233
- const unproducedFields = fields.filter((field) => !producedFields.has(field));
234
- if (tables.length > 0 && unproducedFields.length > 0) warnings.push(`No seed data produces a value for: ${unproducedFields.join(", ")}`);
235
- const outputLines = formatWarnings(warnings);
236
- outputLines.push(filled.length === 0 ? "✓ Nothing to fill" : filled.map(({ file, fields: tableFields, count }) => `\u2713 ${file}: filled ${tableFields.join(", ")} in ${count} row(s)`).join("\n"));
237
- return {
238
- output: outputLines.join("\n"),
239
- filled
240
- };
241
- }
242
-
243
- //#endregion
244
- export { defineSchema, fillSeedData, validateSeedData };
1
+ import{basename as e,dirname as t,join as n}from"pathe";import{pathToFileURL as r}from"node:url";import{readFile as i,readdir as a,stat as o,writeFile as s}from"node:fs/promises";import{ErrorFormatter as c,LinesDB as l,defineSchema as u,findSchemaFile as d}from"@toiroakr/lines-db";const f=[`id`];async function resolveSeedDataTarget(n){let r=await o(n);if(r.isDirectory())return{dataDir:n};if(r.isFile()&&n.endsWith(`.jsonl`))return{dataDir:t(n),tableName:e(n,`.jsonl`)};throw Error(`Invalid path: ${n}. Must be a directory or .jsonl file.`)}async function listSeedTables(t){return(await a(t)).filter(e=>e.endsWith(`.jsonl`)).map(t=>e(t,`.jsonl`)).toSorted()}function formatWarnings(e){return e.length===0?[]:[...e.map(e=>`⚠ ${e}`),``]}function formatValidationErrors(e,t){let n=new c({verbose:t}),r=[],i=new Map;for(let t of e){let e=i.get(t.file)||[];e.push(t),i.set(t.file,e)}for(let[e,t]of i){r.push(n.formatErrorHeader(t.length,e)),r.push(``);let i=t.filter(e=>e.type!==`foreignKey`||!e.foreignKeyError),a=t.filter(e=>e.type===`foreignKey`&&e.foreignKeyError);i.length>0&&r.push(n.formatValidationErrors(i.map(e=>({file:e.file,rowIndex:e.rowIndex,issues:e.issues}))));for(let e of a)e.foreignKeyError&&r.push(n.formatForeignKeyError({file:e.file,rowIndex:e.rowIndex,column:e.foreignKeyError.column,value:e.foreignKeyError.value,referencedTable:e.foreignKeyError.referencedTable,referencedColumn:e.foreignKeyError.referencedColumn}));r.push(``)}return r.join(`
2
+ `)}async function validateSeedData(e){let{path:t,verbose:n=!1}=e,{dataDir:r,tableName:i}=await resolveSeedDataTarget(t),a=l.create({dataDir:r}),o;try{o=await a.initialize({tableName:i,detailedValidate:!0})}finally{await a.close()}let s=formatWarnings(o.warnings);return o.valid?(s.push(`✓ All records are valid`),{valid:!0,output:s.join(`
3
+ `)}):{valid:!1,output:s.join(`
4
+ `),error:formatValidationErrors(o.errors,n)}}function ownValue(e,t){return Object.hasOwn(e,t)?e[t]:void 0}function setField(e,t,n){Object.defineProperty(e,t,{value:n,enumerable:!0,writable:!0,configurable:!0})}function isBlank(e){return e==null?!0:typeof e!=`object`||Array.isArray(e)?!1:Object.values(e).every(isBlank)}function splitLines(e){return e===``?[]:e.split(`
5
+ `).map((e,t,n)=>{let r=t===n.length-1?``:`
6
+ `,i=e.endsWith(`\r`)?e.slice(0,-1):e,a=e.endsWith(`\r`)?`\r`:``,o;if(i.trim()!==``)try{let e=JSON.parse(i);o=typeof e==`object`&&e&&!Array.isArray(e)?e:void 0}catch{o=void 0}return{text:i,eol:`${a}${r}`,row:o}})}function serializeRow(e,t){let n=new Map(t.map((e,t)=>[e,t])),rankOf=e=>n.get(e)??t.length;return JSON.stringify(Object.fromEntries(Object.entries(e).toSorted(([e],[t])=>rankOf(e)-rankOf(t))))}async function loadSeedHook(e,t){let n=await d(e,t);if(!n)return;let i=(await import(r(n).href)).hook;if(typeof i!=`function`)throw Error(`${n} does not export \`hook\`. Run \`tailor generate\` to regenerate the seed schema files.`);return i}async function fillSeedData(e){let{path:t,fields:r=f}=e;if(r.length===0)throw Error(`No fields to fill. Name at least one field.`);let{dataDir:a,tableName:o}=await resolveSeedDataTarget(t),c=o?[o]:await listSeedTables(a),l=[],u=[],d=new Set,p=[];for(let e of c){let t=await loadSeedHook(a,e);if(!t){l.push(`No schema file for ${e}, so nothing can be filled in there`);continue}p.push({table:e,hook:t})}let m=[];for(let{table:e,hook:t}of p){let o=n(a,`${e}.jsonl`),s=splitLines(await i(o,`utf-8`)),c=new Set,f=[],p=0,h=[];for(let[e,n]of s.entries()){let i=n.row;if(!i){n.text.trim()!==``&&f.push(e+1);continue}let a=t(i);h=Object.keys(a);let o=r.filter(e=>!isBlank(ownValue(a,e))&&(d.add(e),isBlank(ownValue(i,e))));if(o.length!==0){for(let e of o)setField(i,e,a[e]),c.add(e);n.text=serializeRow(i,h),p+=1}}f.length>0&&l.push(`${o}: line(s) ${f.join(`, `)} are not JSON objects, so nothing was filled in there`),p!==0&&(m.push({file:o,content:s.map(e=>`${e.text}${e.eol}`).join(``)}),u.push({table:e,file:o,fields:[...c],count:p}))}for(let{file:e,content:t}of m)await s(e,t);let h=r.filter(e=>!d.has(e));c.length>0&&h.length>0&&l.push(`No seed data produces a value for: ${h.join(`, `)}`);let g=formatWarnings(l);return g.push(u.length===0?`✓ Nothing to fill`:u.map(({file:e,fields:t,count:n})=>`\u2713 ${e}: filled ${t.join(`, `)} in ${n} row(s)`).join(`
7
+ `)),{output:g.join(`
8
+ `),filled:u}}export{u as defineSchema,fillSeedData,validateSeedData};
245
9
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/seed/index.ts"],"sourcesContent":["/**\n * Seed integration module for generated seed code.\n *\n * Re-exports `@toiroakr/lines-db` through a single import path\n * to avoid phantom dependency issues with pnpm, and provides\n * seed-specific utility functions used by the code generator.\n */\n\nimport { readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { pathToFileURL } from \"node:url\";\nimport { LinesDB, ErrorFormatter, findSchemaFile } from \"@toiroakr/lines-db\";\n// `pathe`, not `node:path`: the file paths reported back are printed and returned\n// to the caller, and these stay separator-stable across platforms.\nimport { basename, dirname, join } from \"pathe\";\nimport type { JsonObject, ValidationErrorDetail } from \"@toiroakr/lines-db\";\n\nexport { defineSchema } from \"@toiroakr/lines-db\";\nexport type { ForeignKeyDefinition, IndexDefinition } from \"@toiroakr/lines-db\";\n\n/** Fields `fillSeedData` writes when the caller names none. */\nconst DEFAULT_FILL_FIELDS = [\"id\"];\n\ntype SeedDataTarget = {\n dataDir: string;\n tableName?: string;\n};\n\ntype ValidateSeedDataOptions = {\n /** Resolved absolute path to a data directory or a .jsonl file */\n path: string;\n /** Show verbose error output */\n verbose?: boolean;\n};\n\ntype ValidateSeedResult =\n | { valid: true; output: string }\n | { valid: false; output: string; error: string };\n\n/**\n * A JSONL seed file that received values.\n */\ntype FilledSeedFile = {\n /** Name of the seeded table, matching the JSONL file name */\n table: string;\n /** Absolute path to the updated JSONL file */\n file: string;\n /** Fields that were written back to the file */\n fields: string[];\n /** How many rows were missing at least one of those fields */\n count: number;\n};\n\ntype FillSeedDataOptions = {\n /** Resolved absolute path to a data directory or a .jsonl file */\n path: string;\n /** Fields to fill. Defaults to `id`. */\n fields?: readonly string[];\n};\n\ntype FillSeedDataResult = {\n output: string;\n filled: FilledSeedFile[];\n};\n\nasync function resolveSeedDataTarget(resolvedPath: string): Promise<SeedDataTarget> {\n const stats = await stat(resolvedPath);\n if (stats.isDirectory()) {\n return { dataDir: resolvedPath };\n }\n if (stats.isFile() && resolvedPath.endsWith(\".jsonl\")) {\n return { dataDir: dirname(resolvedPath), tableName: basename(resolvedPath, \".jsonl\") };\n }\n throw new Error(`Invalid path: ${resolvedPath}. Must be a directory or .jsonl file.`);\n}\n\nasync function listSeedTables(dataDir: string): Promise<string[]> {\n const entries = await readdir(dataDir);\n return entries\n .filter((entry) => entry.endsWith(\".jsonl\"))\n .map((entry) => basename(entry, \".jsonl\"))\n .toSorted();\n}\n\nfunction formatWarnings(warnings: string[]): string[] {\n if (warnings.length === 0) {\n return [];\n }\n return [...warnings.map((warning) => `⚠ ${warning}`), \"\"];\n}\n\nfunction formatValidationErrors(errors: ValidationErrorDetail[], verbose: boolean): string {\n const formatter = new ErrorFormatter({ verbose });\n const errorLines: string[] = [];\n const errorsByFile = new Map<string, ValidationErrorDetail[]>();\n for (const error of errors) {\n const fileErrors = errorsByFile.get(error.file) || [];\n fileErrors.push(error);\n errorsByFile.set(error.file, fileErrors);\n }\n for (const [file, fileErrors] of errorsByFile) {\n errorLines.push(formatter.formatErrorHeader(fileErrors.length, file));\n errorLines.push(\"\");\n const validationErrors = fileErrors.filter(\n (e) => e.type !== \"foreignKey\" || !e.foreignKeyError,\n );\n const foreignKeyErrors = fileErrors.filter((e) => e.type === \"foreignKey\" && e.foreignKeyError);\n if (validationErrors.length > 0) {\n errorLines.push(\n formatter.formatValidationErrors(\n validationErrors.map((e) => ({\n file: e.file,\n rowIndex: e.rowIndex,\n issues: e.issues,\n })),\n ),\n );\n }\n for (const fkError of foreignKeyErrors) {\n if (fkError.foreignKeyError) {\n errorLines.push(\n formatter.formatForeignKeyError({\n file: fkError.file,\n rowIndex: fkError.rowIndex,\n column: fkError.foreignKeyError.column,\n value: fkError.foreignKeyError.value,\n referencedTable: fkError.foreignKeyError.referencedTable,\n referencedColumn: fkError.foreignKeyError.referencedColumn,\n }),\n );\n }\n }\n errorLines.push(\"\");\n }\n\n return errorLines.join(\"\\n\");\n}\n\n/**\n * Validate JSONL seed data against schema definitions.\n * Resolves the given path (directory or `.jsonl` file), validates the rows it\n * holds, and returns formatted output and error messages.\n * @param options - Validation options including path and verbose flag\n * @returns Validation result with output messages and optional error details\n */\nexport async function validateSeedData(\n options: ValidateSeedDataOptions,\n): Promise<ValidateSeedResult> {\n const { path: resolvedPath, verbose = false } = options;\n const { dataDir, tableName } = await resolveSeedDataTarget(resolvedPath);\n\n const db = LinesDB.create({ dataDir });\n let result;\n try {\n result = await db.initialize({ tableName, detailedValidate: true });\n } finally {\n await db.close();\n }\n\n const outputLines = formatWarnings(result.warnings);\n\n if (result.valid) {\n outputLines.push(\"✓ All records are valid\");\n return { valid: true, output: outputLines.join(\"\\n\") };\n }\n\n return {\n valid: false,\n output: outputLines.join(\"\\n\"),\n error: formatValidationErrors(result.errors, verbose),\n };\n}\n\n// A row's own value for a field, or undefined when the row has none.\nfunction ownValue(row: Record<string, unknown>, field: string): unknown {\n return Object.hasOwn(row, field) ? row[field] : undefined;\n}\n\n// Not `row[field] = value`: a field named `__proto__` goes through the inherited\n// setter, which leaves no own property for the serializer to read back.\nfunction setField(row: JsonObject, field: string, value: JsonObject[string]): void {\n Object.defineProperty(row, field, {\n value,\n enumerable: true,\n writable: true,\n configurable: true,\n });\n}\n\nfunction isBlank(value: unknown): boolean {\n if (value === undefined || value === null) {\n return true;\n }\n if (typeof value !== \"object\" || Array.isArray(value)) {\n return false;\n }\n // A nested field the row never had comes back as an object whose keys carry no\n // value of their own, and writing that into the line fills nothing in.\n return Object.values(value).every(isBlank);\n}\n\n/** A JSONL line, kept as text so an untouched line is written back verbatim. */\ntype SeedLine = {\n text: string;\n /** Line separator the file used after this line, kept so CRLF survives. */\n eol: string;\n row: JsonObject | undefined;\n};\n\nfunction splitLines(content: string): SeedLine[] {\n if (content === \"\") {\n return [];\n }\n return content.split(\"\\n\").map((raw, index, all) => {\n const eol = index === all.length - 1 ? \"\" : \"\\n\";\n const text = raw.endsWith(\"\\r\") ? raw.slice(0, -1) : raw;\n const carriage = raw.endsWith(\"\\r\") ? \"\\r\" : \"\";\n let row: JsonObject | undefined;\n if (text.trim() !== \"\") {\n try {\n const parsed: unknown = JSON.parse(text);\n row =\n parsed !== null && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? (parsed as JsonObject)\n : undefined;\n } catch {\n row = undefined;\n }\n }\n return { text, eol: `${carriage}${eol}`, row };\n });\n}\n\n// Keys go in the order the hook produced them, which is the order the type\n// declares its fields. Keys the type does not declare follow the declared ones.\nfunction serializeRow(row: JsonObject, fieldOrder: string[]): string {\n const rank = new Map(fieldOrder.map((field, index) => [field, index]));\n const rankOf = (key: string): number => rank.get(key) ?? fieldOrder.length;\n return JSON.stringify(\n Object.fromEntries(Object.entries(row).toSorted(([a], [b]) => rankOf(a) - rankOf(b))),\n );\n}\n\ntype SeedHook = (row: unknown) => Record<string, unknown>;\n\nasync function loadSeedHook(dataDir: string, table: string): Promise<SeedHook | undefined> {\n const schemaPath = await findSchemaFile(dataDir, table);\n if (!schemaPath) {\n return undefined;\n }\n const loaded: unknown = await import(pathToFileURL(schemaPath).href);\n const hook = (loaded as { hook?: unknown }).hook;\n if (typeof hook !== \"function\") {\n throw new Error(\n `${schemaPath} does not export \\`hook\\`. Run \\`tailor generate\\` to regenerate the seed schema files.`,\n );\n }\n return hook as SeedHook;\n}\n\n/**\n * Fill in the values a record gets on create for the JSONL seed data rows that\n * are missing them, so a row can be referenced by `id` or carry a timestamp\n * before it is ever seeded.\n *\n * The values come from the table's own create-time behavior — its `id`, its field\n * defaults, and its create hooks — applied to each row on its own. Nothing is\n * validated, so a row can be filled while the data around it is still\n * incomplete: that is what lets you get the ids you need in order to write the\n * rows that reference them. Run `validateSeedData` when the data is ready.\n *\n * Only the named fields are written, and only into a row that has no value for\n * them, so a value already in the file is never replaced. A line that gains\n * nothing is written back exactly as it was, byte for byte; a line that does get\n * a value is re-serialized with its keys in the order the table declares its\n * fields, so a filled-in `id` lands at the front. A field the table gives no\n * value to — one it does not declare, or one the platform assigns such as a\n * serial field — is skipped, so one field list covers a whole data directory.\n *\n * The values are read from the schema files generated next to the data, and all\n * of them are read before anything is written: a file that predates the current\n * generator stops the run with nothing filled in anywhere.\n * @param options - Fill options including path and fields\n * @returns Which files received which fields\n */\nexport async function fillSeedData(options: FillSeedDataOptions): Promise<FillSeedDataResult> {\n const { path: resolvedPath, fields = DEFAULT_FILL_FIELDS } = options;\n if (fields.length === 0) {\n throw new Error(\"No fields to fill. Name at least one field.\");\n }\n const { dataDir, tableName } = await resolveSeedDataTarget(resolvedPath);\n const tables = tableName ? [tableName] : await listSeedTables(dataDir);\n\n const warnings: string[] = [];\n const filled: FilledSeedFile[] = [];\n const producedFields = new Set<string>();\n\n // Every hook loads before anything is written, so a schema file that predates\n // `tailor generate` stops the run instead of leaving half the files filled.\n const hooks: { table: string; hook: SeedHook }[] = [];\n for (const table of tables) {\n const hook = await loadSeedHook(dataDir, table);\n if (!hook) {\n warnings.push(`No schema file for ${table}, so nothing can be filled in there`);\n continue;\n }\n hooks.push({ table, hook });\n }\n\n // Every line is decided before any file is written, so a hook that throws on\n // one table cannot leave another one already rewritten.\n const writes: { file: string; content: string }[] = [];\n for (const { table, hook } of hooks) {\n const file = join(dataDir, `${table}.jsonl`);\n const lines = splitLines(await readFile(file, \"utf-8\"));\n\n const written = new Set<string>();\n const unreadable: number[] = [];\n let count = 0;\n let fieldOrder: string[] = [];\n for (const [index, line] of lines.entries()) {\n const row = line.row;\n if (!row) {\n if (line.text.trim() !== \"\") {\n unreadable.push(index + 1);\n }\n continue;\n }\n const hooked = hook(row);\n fieldOrder = Object.keys(hooked);\n const gained = fields.filter((field) => {\n const value = ownValue(hooked, field);\n if (isBlank(value)) {\n return false;\n }\n producedFields.add(field);\n return isBlank(ownValue(row, field));\n });\n if (gained.length === 0) {\n continue;\n }\n for (const field of gained) {\n setField(row, field, hooked[field] as JsonObject[string]);\n written.add(field);\n }\n line.text = serializeRow(row, fieldOrder);\n count += 1;\n }\n\n if (unreadable.length > 0) {\n warnings.push(\n `${file}: line(s) ${unreadable.join(\", \")} are not JSON objects, so nothing was filled in there`,\n );\n }\n\n if (count === 0) {\n continue;\n }\n writes.push({ file, content: lines.map((line) => `${line.text}${line.eol}`).join(\"\") });\n filled.push({ table, file, fields: [...written], count });\n }\n\n for (const { file, content } of writes) {\n await writeFile(file, content);\n }\n\n const unproducedFields = fields.filter((field) => !producedFields.has(field));\n if (tables.length > 0 && unproducedFields.length > 0) {\n warnings.push(`No seed data produces a value for: ${unproducedFields.join(\", \")}`);\n }\n\n const outputLines = formatWarnings(warnings);\n outputLines.push(\n filled.length === 0\n ? \"\\u2713 Nothing to fill\"\n : filled\n .map(\n ({ file, fields: tableFields, count }) =>\n `\\u2713 ${file}: filled ${tableFields.join(\", \")} in ${count} row(s)`,\n )\n .join(\"\\n\"),\n );\n\n return { output: outputLines.join(\"\\n\"), filled };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/seed/index.ts"],"sourcesContent":["/**\n * Seed integration module for generated seed code.\n *\n * Re-exports `@toiroakr/lines-db` through a single import path\n * to avoid phantom dependency issues with pnpm, and provides\n * seed-specific utility functions used by the code generator.\n */\n\nimport { readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { pathToFileURL } from \"node:url\";\nimport { LinesDB, ErrorFormatter, findSchemaFile } from \"@toiroakr/lines-db\";\n// `pathe`, not `node:path`: the file paths reported back are printed and returned\n// to the caller, and these stay separator-stable across platforms.\nimport { basename, dirname, join } from \"pathe\";\nimport type { JsonObject, ValidationErrorDetail } from \"@toiroakr/lines-db\";\n\nexport { defineSchema } from \"@toiroakr/lines-db\";\nexport type { ForeignKeyDefinition, IndexDefinition } from \"@toiroakr/lines-db\";\n\n/** Fields `fillSeedData` writes when the caller names none. */\nconst DEFAULT_FILL_FIELDS = [\"id\"];\n\ntype SeedDataTarget = {\n dataDir: string;\n tableName?: string;\n};\n\ntype ValidateSeedDataOptions = {\n /** Resolved absolute path to a data directory or a .jsonl file */\n path: string;\n /** Show verbose error output */\n verbose?: boolean;\n};\n\ntype ValidateSeedResult =\n | { valid: true; output: string }\n | { valid: false; output: string; error: string };\n\n/**\n * A JSONL seed file that received values.\n */\ntype FilledSeedFile = {\n /** Name of the seeded table, matching the JSONL file name */\n table: string;\n /** Absolute path to the updated JSONL file */\n file: string;\n /** Fields that were written back to the file */\n fields: string[];\n /** How many rows were missing at least one of those fields */\n count: number;\n};\n\ntype FillSeedDataOptions = {\n /** Resolved absolute path to a data directory or a .jsonl file */\n path: string;\n /** Fields to fill. Defaults to `id`. */\n fields?: readonly string[];\n};\n\ntype FillSeedDataResult = {\n output: string;\n filled: FilledSeedFile[];\n};\n\nasync function resolveSeedDataTarget(resolvedPath: string): Promise<SeedDataTarget> {\n const stats = await stat(resolvedPath);\n if (stats.isDirectory()) {\n return { dataDir: resolvedPath };\n }\n if (stats.isFile() && resolvedPath.endsWith(\".jsonl\")) {\n return { dataDir: dirname(resolvedPath), tableName: basename(resolvedPath, \".jsonl\") };\n }\n throw new Error(`Invalid path: ${resolvedPath}. Must be a directory or .jsonl file.`);\n}\n\nasync function listSeedTables(dataDir: string): Promise<string[]> {\n const entries = await readdir(dataDir);\n return entries\n .filter((entry) => entry.endsWith(\".jsonl\"))\n .map((entry) => basename(entry, \".jsonl\"))\n .toSorted();\n}\n\nfunction formatWarnings(warnings: string[]): string[] {\n if (warnings.length === 0) {\n return [];\n }\n return [...warnings.map((warning) => `⚠ ${warning}`), \"\"];\n}\n\nfunction formatValidationErrors(errors: ValidationErrorDetail[], verbose: boolean): string {\n const formatter = new ErrorFormatter({ verbose });\n const errorLines: string[] = [];\n const errorsByFile = new Map<string, ValidationErrorDetail[]>();\n for (const error of errors) {\n const fileErrors = errorsByFile.get(error.file) || [];\n fileErrors.push(error);\n errorsByFile.set(error.file, fileErrors);\n }\n for (const [file, fileErrors] of errorsByFile) {\n errorLines.push(formatter.formatErrorHeader(fileErrors.length, file));\n errorLines.push(\"\");\n const validationErrors = fileErrors.filter(\n (e) => e.type !== \"foreignKey\" || !e.foreignKeyError,\n );\n const foreignKeyErrors = fileErrors.filter((e) => e.type === \"foreignKey\" && e.foreignKeyError);\n if (validationErrors.length > 0) {\n errorLines.push(\n formatter.formatValidationErrors(\n validationErrors.map((e) => ({\n file: e.file,\n rowIndex: e.rowIndex,\n issues: e.issues,\n })),\n ),\n );\n }\n for (const fkError of foreignKeyErrors) {\n if (fkError.foreignKeyError) {\n errorLines.push(\n formatter.formatForeignKeyError({\n file: fkError.file,\n rowIndex: fkError.rowIndex,\n column: fkError.foreignKeyError.column,\n value: fkError.foreignKeyError.value,\n referencedTable: fkError.foreignKeyError.referencedTable,\n referencedColumn: fkError.foreignKeyError.referencedColumn,\n }),\n );\n }\n }\n errorLines.push(\"\");\n }\n\n return errorLines.join(\"\\n\");\n}\n\n/**\n * Validate JSONL seed data against schema definitions.\n * Resolves the given path (directory or `.jsonl` file), validates the rows it\n * holds, and returns formatted output and error messages.\n * @param options - Validation options including path and verbose flag\n * @returns Validation result with output messages and optional error details\n */\nexport async function validateSeedData(\n options: ValidateSeedDataOptions,\n): Promise<ValidateSeedResult> {\n const { path: resolvedPath, verbose = false } = options;\n const { dataDir, tableName } = await resolveSeedDataTarget(resolvedPath);\n\n const db = LinesDB.create({ dataDir });\n let result;\n try {\n result = await db.initialize({ tableName, detailedValidate: true });\n } finally {\n await db.close();\n }\n\n const outputLines = formatWarnings(result.warnings);\n\n if (result.valid) {\n outputLines.push(\"✓ All records are valid\");\n return { valid: true, output: outputLines.join(\"\\n\") };\n }\n\n return {\n valid: false,\n output: outputLines.join(\"\\n\"),\n error: formatValidationErrors(result.errors, verbose),\n };\n}\n\n// A row's own value for a field, or undefined when the row has none.\nfunction ownValue(row: Record<string, unknown>, field: string): unknown {\n return Object.hasOwn(row, field) ? row[field] : undefined;\n}\n\n// Not `row[field] = value`: a field named `__proto__` goes through the inherited\n// setter, which leaves no own property for the serializer to read back.\nfunction setField(row: JsonObject, field: string, value: JsonObject[string]): void {\n Object.defineProperty(row, field, {\n value,\n enumerable: true,\n writable: true,\n configurable: true,\n });\n}\n\nfunction isBlank(value: unknown): boolean {\n if (value === undefined || value === null) {\n return true;\n }\n if (typeof value !== \"object\" || Array.isArray(value)) {\n return false;\n }\n // A nested field the row never had comes back as an object whose keys carry no\n // value of their own, and writing that into the line fills nothing in.\n return Object.values(value).every(isBlank);\n}\n\n/** A JSONL line, kept as text so an untouched line is written back verbatim. */\ntype SeedLine = {\n text: string;\n /** Line separator the file used after this line, kept so CRLF survives. */\n eol: string;\n row: JsonObject | undefined;\n};\n\nfunction splitLines(content: string): SeedLine[] {\n if (content === \"\") {\n return [];\n }\n return content.split(\"\\n\").map((raw, index, all) => {\n const eol = index === all.length - 1 ? \"\" : \"\\n\";\n const text = raw.endsWith(\"\\r\") ? raw.slice(0, -1) : raw;\n const carriage = raw.endsWith(\"\\r\") ? \"\\r\" : \"\";\n let row: JsonObject | undefined;\n if (text.trim() !== \"\") {\n try {\n const parsed: unknown = JSON.parse(text);\n row =\n parsed !== null && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? (parsed as JsonObject)\n : undefined;\n } catch {\n row = undefined;\n }\n }\n return { text, eol: `${carriage}${eol}`, row };\n });\n}\n\n// Keys go in the order the hook produced them, which is the order the table\n// declares its fields. Keys the table does not declare follow the declared ones.\nfunction serializeRow(row: JsonObject, fieldOrder: string[]): string {\n const rank = new Map(fieldOrder.map((field, index) => [field, index]));\n const rankOf = (key: string): number => rank.get(key) ?? fieldOrder.length;\n return JSON.stringify(\n Object.fromEntries(Object.entries(row).toSorted(([a], [b]) => rankOf(a) - rankOf(b))),\n );\n}\n\ntype SeedHook = (row: unknown) => Record<string, unknown>;\n\nasync function loadSeedHook(dataDir: string, table: string): Promise<SeedHook | undefined> {\n const schemaPath = await findSchemaFile(dataDir, table);\n if (!schemaPath) {\n return undefined;\n }\n const loaded: unknown = await import(pathToFileURL(schemaPath).href);\n const hook = (loaded as { hook?: unknown }).hook;\n if (typeof hook !== \"function\") {\n throw new Error(\n `${schemaPath} does not export \\`hook\\`. Run \\`tailor generate\\` to regenerate the seed schema files.`,\n );\n }\n return hook as SeedHook;\n}\n\n/**\n * Fill in the values a record gets on create for the JSONL seed data rows that\n * are missing them, so a row can be referenced by `id` or carry a timestamp\n * before it is ever seeded.\n *\n * The values come from the table's own create-time behavior — its `id`, its field\n * defaults, and its create hooks — applied to each row on its own. Nothing is\n * validated, so a row can be filled while the data around it is still\n * incomplete: that is what lets you get the ids you need in order to write the\n * rows that reference them. Run `validateSeedData` when the data is ready.\n *\n * Only the named fields are written, and only into a row that has no value for\n * them, so a value already in the file is never replaced. A line that gains\n * nothing is written back exactly as it was, byte for byte; a line that does get\n * a value is re-serialized with its keys in the order the table declares its\n * fields, so a filled-in `id` lands at the front. A field the table gives no\n * value to — one it does not declare, or one the platform assigns such as a\n * serial field — is skipped, so one field list covers a whole data directory.\n *\n * The values are read from the schema files generated next to the data, and all\n * of them are read before anything is written: a file that predates the current\n * generator stops the run with nothing filled in anywhere.\n * @param options - Fill options including path and fields\n * @returns Which files received which fields\n */\nexport async function fillSeedData(options: FillSeedDataOptions): Promise<FillSeedDataResult> {\n const { path: resolvedPath, fields = DEFAULT_FILL_FIELDS } = options;\n if (fields.length === 0) {\n throw new Error(\"No fields to fill. Name at least one field.\");\n }\n const { dataDir, tableName } = await resolveSeedDataTarget(resolvedPath);\n const tables = tableName ? [tableName] : await listSeedTables(dataDir);\n\n const warnings: string[] = [];\n const filled: FilledSeedFile[] = [];\n const producedFields = new Set<string>();\n\n // Every hook loads before anything is written, so a schema file that predates\n // `tailor generate` stops the run instead of leaving half the files filled.\n const hooks: { table: string; hook: SeedHook }[] = [];\n for (const table of tables) {\n const hook = await loadSeedHook(dataDir, table);\n if (!hook) {\n warnings.push(`No schema file for ${table}, so nothing can be filled in there`);\n continue;\n }\n hooks.push({ table, hook });\n }\n\n // Every line is decided before any file is written, so a hook that throws on\n // one table cannot leave another one already rewritten.\n const writes: { file: string; content: string }[] = [];\n for (const { table, hook } of hooks) {\n const file = join(dataDir, `${table}.jsonl`);\n const lines = splitLines(await readFile(file, \"utf-8\"));\n\n const written = new Set<string>();\n const unreadable: number[] = [];\n let count = 0;\n let fieldOrder: string[] = [];\n for (const [index, line] of lines.entries()) {\n const row = line.row;\n if (!row) {\n if (line.text.trim() !== \"\") {\n unreadable.push(index + 1);\n }\n continue;\n }\n const hooked = hook(row);\n fieldOrder = Object.keys(hooked);\n const gained = fields.filter((field) => {\n const value = ownValue(hooked, field);\n if (isBlank(value)) {\n return false;\n }\n producedFields.add(field);\n return isBlank(ownValue(row, field));\n });\n if (gained.length === 0) {\n continue;\n }\n for (const field of gained) {\n setField(row, field, hooked[field] as JsonObject[string]);\n written.add(field);\n }\n line.text = serializeRow(row, fieldOrder);\n count += 1;\n }\n\n if (unreadable.length > 0) {\n warnings.push(\n `${file}: line(s) ${unreadable.join(\", \")} are not JSON objects, so nothing was filled in there`,\n );\n }\n\n if (count === 0) {\n continue;\n }\n writes.push({ file, content: lines.map((line) => `${line.text}${line.eol}`).join(\"\") });\n filled.push({ table, file, fields: [...written], count });\n }\n\n for (const { file, content } of writes) {\n await writeFile(file, content);\n }\n\n const unproducedFields = fields.filter((field) => !producedFields.has(field));\n if (tables.length > 0 && unproducedFields.length > 0) {\n warnings.push(`No seed data produces a value for: ${unproducedFields.join(\", \")}`);\n }\n\n const outputLines = formatWarnings(warnings);\n outputLines.push(\n filled.length === 0\n ? \"\\u2713 Nothing to fill\"\n : filled\n .map(\n ({ file, fields: tableFields, count }) =>\n `\\u2713 ${file}: filled ${tableFields.join(\", \")} in ${count} row(s)`,\n )\n .join(\"\\n\"),\n );\n\n return { output: outputLines.join(\"\\n\"), filled };\n}\n"],"mappings":"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"}
@@ -0,0 +1,207 @@
1
+ import{t as e}from"./assert-WeXvmG4j.mjs";import{t}from"./multiline-EyzjEwn9.mjs";import{t as n}from"./type-source--ZNcV8RJ.mjs";import*as r from"pathe";function processIdpUser(e){if(e.idProvider?.kind!==`BuiltInIdP`||!e.userProfile)return;let{tableName:t,usernameField:n}=e.userProfile;return{name:`_User`,dependencies:[t],dataFile:`data/_User.jsonl`,idpNamespace:e.idProvider.namespace,schema:{usernameField:n,userTableName:t}}}function generateIdpSeedScriptCode(e){return t`
2
+ export async function main(input) {
3
+ const client = new tailor.idp.Client({ namespace: "${e}" });
4
+ const errors = [];
5
+ let processed = 0;
6
+ let created = 0;
7
+ let updated = 0;
8
+ let skipped = 0;
9
+ const upsert = input.upsert === true;
10
+ // Rows may arrive as one chunk of a larger dataset; report row numbers
11
+ // relative to the whole dataset so they match the source JSONL.
12
+ const offset = typeof input.offset === "number" ? input.offset : 0;
13
+ const total = typeof input.total === "number" ? input.total : input.users.length;
14
+
15
+ for (let i = 0; i < input.users.length; i++) {
16
+ try {
17
+ if (upsert) {
18
+ let existing;
19
+ let lookupError;
20
+ try {
21
+ existing = await client.userByName(input.users[i].name);
22
+ } catch (error) {
23
+ existing = undefined;
24
+ lookupError = error instanceof Error ? error.message : String(error);
25
+ }
26
+
27
+ if (existing) {
28
+ const { name, ...attributes } = input.users[i];
29
+ if (Object.keys(attributes).length === 0) {
30
+ skipped++;
31
+ } else {
32
+ await client.updateUser({ id: existing.id, ...attributes });
33
+ updated++;
34
+ }
35
+ } else {
36
+ try {
37
+ await client.createUser(input.users[i]);
38
+ created++;
39
+ } catch (error) {
40
+ const message = error instanceof Error ? error.message : String(error);
41
+ throw new Error(
42
+ lookupError ? \`create failed (\${message}); lookup failed (\${lookupError})\` : message,
43
+ );
44
+ }
45
+ }
46
+ } else {
47
+ await client.createUser(input.users[i]);
48
+ created++;
49
+ }
50
+ processed++;
51
+ console.log(\`[_User] \${offset + i + 1}/\${total}: \${input.users[i].name}\`);
52
+ } catch (error) {
53
+ const message = error instanceof Error ? error.message : String(error);
54
+ errors.push(\`Row \${offset + i + 1} (\${input.users[i].name}): \${message}\`);
55
+ console.error(\`[_User] Row \${offset + i + 1} failed: \${message}\`);
56
+ }
57
+ }
58
+
59
+ return {
60
+ success: errors.length === 0,
61
+ processed,
62
+ created,
63
+ updated,
64
+ skipped,
65
+ errors,
66
+ };
67
+ }
68
+ `}function generateIdpTruncateScriptCode(e){return t`
69
+ export async function main() {
70
+ const client = new tailor.idp.Client({ namespace: "${e}" });
71
+ const errors = [];
72
+ let deleted = 0;
73
+
74
+ // List all users with pagination
75
+ let after = undefined;
76
+ const allUsers = [];
77
+ do {
78
+ const response = await client.users(after ? { after } : undefined);
79
+ allUsers.push(...(response.users || []));
80
+ after = response.nextPageToken;
81
+ } while (after);
82
+
83
+ console.log(\`Found \${allUsers.length} IDP users to delete\`);
84
+
85
+ for (const user of allUsers) {
86
+ try {
87
+ await client.deleteUser(user.id);
88
+ deleted++;
89
+ console.log(\`[_User] Deleted \${deleted}/\${allUsers.length}: \${user.name}\`);
90
+ } catch (error) {
91
+ const message = error instanceof Error ? error.message : String(error);
92
+ errors.push(\`User \${user.id} (\${user.name}): \${message}\`);
93
+ console.error(\`[_User] Delete failed for \${user.name}: \${message}\`);
94
+ }
95
+ }
96
+
97
+ return {
98
+ success: errors.length === 0,
99
+ deleted,
100
+ total: allUsers.length,
101
+ errors,
102
+ };
103
+ }
104
+ `}function generateIdpUserSchemaFile(e){let{usernameField:n,userTableName:r,includeUserProfileFK:i=!0}=e,a=i?t`
105
+ primaryKey: "name",
106
+ indexes: [
107
+ { name: "_user_name_unique_idx", columns: ["name"], unique: true },
108
+ ],
109
+ foreignKeys: [
110
+ {
111
+ column: "name",
112
+ references: {
113
+ table: "${r}",
114
+ column: "${n}",
115
+ },
116
+ },
117
+ ],
118
+ `:t`
119
+ primaryKey: "name",
120
+ indexes: [
121
+ { name: "_user_name_unique_idx", columns: ["name"], unique: true },
122
+ ],
123
+ `;return t`
124
+ import { t } from "@tailor-platform/sdk";
125
+ import { defineSchema } from "@tailor-platform/sdk/seed";
126
+ import { createStandardSchema } from "@tailor-platform/sdk/test";
127
+
128
+ const schemaType = t.object({
129
+ name: t.string(),
130
+ password: t.string(),
131
+ });
132
+
133
+ // Simple identity hook for _User (no TailorDB backing table)
134
+ export const hook = <T>(data: unknown) => data as T;
135
+
136
+ export const schema = defineSchema(
137
+ createStandardSchema(schemaType, hook),
138
+ {
139
+ ${a}
140
+ }
141
+ );
142
+
143
+ `}function processLinesDb(e,t){if(n(t))return processLinesDbForPluginTable(e,t);if(!t.filePath)throw Error(`Missing source info for table ${e.name}`);if(!t.exportName)throw Error(`Missing export name for table ${e.name}`);let{optionalFields:r,omitFields:i,indexes:a,foreignKeys:o}=extractFieldMetadata(e);return{tableName:e.name,exportName:t.exportName,importPath:t.filePath,optionalFields:r,omitFields:i,foreignKeys:o,indexes:a}}function processLinesDbForPluginTable(e,t){let{optionalFields:n,omitFields:r,indexes:i,foreignKeys:a}=extractFieldMetadata(e);return{tableName:e.name,exportName:t.exportName,importPath:``,optionalFields:n,omitFields:r,foreignKeys:a,indexes:i,pluginSource:t}}function extractFieldMetadata(e){let t=[`id`],n=[],r=[],i=[];for(let[i,a]of Object.entries(e.fields))a.config.hooks?.create&&t.push(i),a.config.serial&&n.push(i),a.config.unique&&r.push({name:`${e.name.toLowerCase()}_${i}_unique_idx`,columns:[i],unique:!0});if(e.indexes)for(let[t,n]of Object.entries(e.indexes))r.push({name:t,columns:n.fields,unique:n.unique});for(let[t,n]of Object.entries(e.fields))n.relation&&i.push({column:t,references:{table:n.relation.targetType,column:n.relation.key}});return{optionalFields:t,omitFields:n,indexes:r,foreignKeys:i}}function generateSchemaOptions(e,t){let n=[];return e.length>0&&(n.push(`foreignKeys: [`),e.forEach(e=>{n.push(` ${JSON.stringify(e)},`)}),n.push(`],`)),t.length>0&&(n.push(`indexes: [`),t.forEach(e=>{n.push(` ${JSON.stringify(e)},`)}),n.push(`],`)),n.length>0?[`
144
+ {`,...n.map(e=>` ${e}`),` }`].join(`
145
+ `):``}function generateLinesDbSchemaFile(e,n){let{exportName:r,optionalFields:i,omitFields:a,foreignKeys:o,indexes:s}=e,c=t`
146
+ const schemaType = t.object({
147
+ ...${r}.pickFields(${JSON.stringify(i)}, { optional: true }),
148
+ ...${r}.omitFields(${JSON.stringify([...i,...a])}),
149
+ });
150
+ `,l=generateSchemaOptions(o,s);return t`
151
+ import { t } from "@tailor-platform/sdk";
152
+ import { defineSchema } from "@tailor-platform/sdk/seed";
153
+ import { createTailorDBHook, createStandardSchema } from "@tailor-platform/sdk/test";
154
+ import { ${r} } from "${n}";
155
+
156
+ ${c}
157
+
158
+ export const hook = createTailorDBHook(${r});
159
+
160
+ export const schema = defineSchema(
161
+ createStandardSchema(schemaType, hook, ${r}),${l}
162
+ );
163
+
164
+ `}function generateLinesDbSchemaFileWithPluginAPI(e,n){let{tableName:r,exportName:i,optionalFields:a,omitFields:o,foreignKeys:s,indexes:c,pluginSource:l}=e;if(!l)throw Error(`pluginSource is required for plugin-generated table "${r}"`);let{configImportPath:u,originalImportPath:d}=n,f=t`
165
+ const schemaType = t.object({
166
+ ...${i}.pickFields(${JSON.stringify(a)}, { optional: true }),
167
+ ...${i}.omitFields(${JSON.stringify([...a,...o])}),
168
+ });
169
+ `,p=generateSchemaOptions(s,c);if(l.originalExportName&&d&&l.generatedTableKind)return t`
170
+ import { join } from "node:path";
171
+ import { t } from "@tailor-platform/sdk";
172
+ import { getGeneratedTable } from "@tailor-platform/sdk/plugin";
173
+ import { defineSchema } from "@tailor-platform/sdk/seed";
174
+ import { createTailorDBHook, createStandardSchema } from "@tailor-platform/sdk/test";
175
+ import { ${l.originalExportName} } from "${d}";
176
+
177
+ const configPath = join(import.meta.dirname, "${u}");
178
+ const ${i} = await getGeneratedTable(configPath, "${l.pluginId}", ${l.originalExportName}, "${l.generatedTableKind}");
179
+
180
+ ${f}
181
+
182
+ export const hook = createTailorDBHook(${i});
183
+
184
+ export const schema = defineSchema(
185
+ createStandardSchema(schemaType, hook, ${i}),${p}
186
+ );
187
+
188
+ `;if(!l.generatedTableKind)throw Error(`Namespace plugin "${l.pluginId}" must provide generatedTableKind for table "${r}"`);return t`
189
+ import { join } from "node:path";
190
+ import { t } from "@tailor-platform/sdk";
191
+ import { getGeneratedTable } from "@tailor-platform/sdk/plugin";
192
+ import { defineSchema } from "@tailor-platform/sdk/seed";
193
+ import { createTailorDBHook, createStandardSchema } from "@tailor-platform/sdk/test";
194
+
195
+ const configPath = join(import.meta.dirname, "${u}");
196
+ const ${i} = await getGeneratedTable(configPath, "${l.pluginId}", null, "${l.generatedTableKind}");
197
+
198
+ ${f}
199
+
200
+ export const hook = createTailorDBHook(${i});
201
+
202
+ export const schema = defineSchema(
203
+ createStandardSchema(schemaType, hook, ${i}),${p}
204
+ );
205
+
206
+ `}const i=`@tailor-platform/seed`;function resolveIdpUserSyncFKs(e){return{emitUserToIdpFK:!(e?.userToIdp??!1),emitIdpToUserFK:!(e?.idpToUser??!1)}}function seedPlugin(t){return{id:i,description:`Generates seed data and schema files for the tailor seed CLI plugin`,pluginConfig:t,async onTailorDBReady(t){let n=[],i=t.auth?processIdpUser(t.auth)??null:null,a=resolveIdpUserSyncFKs(t.pluginConfig.disableIdpUserSync);for(let o of t.tailordb)for(let[s,c]of Object.entries(o.tables)){let l=processLinesDb(c,e(o.sourceInfo.get(s),`source info missing for table: ${s}`));a.emitUserToIdpFK&&i&&s===i.schema.userTableName&&l.foreignKeys.push({column:i.schema.usernameField,references:{table:`_User`,column:`name`}}),n.push({path:r.join(t.pluginConfig.distPath,`data`,`${l.tableName}.jsonl`),content:``,skipIfExists:!0});let u=r.join(t.pluginConfig.distPath,`data`,`${l.tableName}.schema.ts`);if(l.pluginSource&&l.pluginSource.pluginImportPath){let e;if(l.pluginSource.originalFilePath&&l.pluginSource.originalExportName){let t=r.relative(r.dirname(u),l.pluginSource.originalFilePath);e=t.replace(/\.ts$/,``).startsWith(`.`)?t.replace(/\.ts$/,``):`./${t.replace(/\.ts$/,``)}`}let i=generateLinesDbSchemaFileWithPluginAPI(l,{configImportPath:r.relative(r.dirname(u),t.configPath),originalImportPath:e});n.push({path:u,content:i})}else{let e=r.relative(r.dirname(u),l.importPath),t=generateLinesDbSchemaFile(l,e.replace(/\.ts$/,``).startsWith(`.`)?e.replace(/\.ts$/,``):`./${e.replace(/\.ts$/,``)}`);n.push({path:u,content:t})}}return i&&(n.push({path:r.join(t.pluginConfig.distPath,i.dataFile),content:``,skipIfExists:!0}),n.push({path:r.join(t.pluginConfig.distPath,`data`,`${i.name}.schema.ts`),content:generateIdpUserSchemaFile({usernameField:i.schema.usernameField,userTableName:i.schema.userTableName,includeUserProfileFK:a.emitIdpToUserFK})})),{files:n}}}}export{generateIdpTruncateScriptCode as a,generateIdpSeedScriptCode as i,seedPlugin as n,processIdpUser as o,processLinesDb as r,i as t};
207
+ //# sourceMappingURL=seed-DwqRFdqP.mjs.map