@rawsql-ts/ztd-cli 0.16.0 → 0.19.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 (242) hide show
  1. package/README.md +532 -353
  2. package/package.json +35 -17
  3. package/templates/.editorconfig +16 -16
  4. package/templates/.prettierignore +2 -2
  5. package/templates/.prettierrc +24 -24
  6. package/templates/AGENTS.md +30 -325
  7. package/templates/CONTEXT.md +11 -0
  8. package/templates/CONTEXT.webapi.md +11 -0
  9. package/templates/DESIGN.md +17 -0
  10. package/templates/DEV_NOTES.md +14 -0
  11. package/templates/PROMPT_DOGFOOD.webapi.md +49 -0
  12. package/templates/README.md +46 -217
  13. package/templates/README.webapi.md +38 -0
  14. package/templates/scripts/local-source-guard.mjs +189 -0
  15. package/templates/src/AGENTS.md +26 -0
  16. package/templates/src/application/AGENTS.md +15 -0
  17. package/templates/src/application/README.md +6 -0
  18. package/templates/src/catalog/AGENTS.md +28 -0
  19. package/templates/src/catalog/runtime/AGENTS.md +28 -0
  20. package/templates/src/catalog/runtime/_coercions.local-source.ts +30 -0
  21. package/templates/src/catalog/runtime/_coercions.ts +30 -0
  22. package/templates/src/catalog/runtime/_smoke.runtime.ts +21 -0
  23. package/templates/src/catalog/specs/AGENTS.md +41 -0
  24. package/templates/src/catalog/specs/_smoke.spec.arktype.ts +21 -0
  25. package/templates/src/catalog/specs/_smoke.spec.zod.ts +20 -0
  26. package/templates/src/db/sql-client-adapters.ts +32 -0
  27. package/templates/src/db/sql-client.ts +24 -24
  28. package/templates/src/domain/AGENTS.md +15 -0
  29. package/templates/src/domain/README.md +6 -0
  30. package/templates/src/infrastructure/AGENTS.md +14 -0
  31. package/templates/src/infrastructure/README.md +6 -0
  32. package/templates/src/infrastructure/db/AGENTS.md +14 -0
  33. package/templates/src/infrastructure/db/sql-client-adapters.ts +34 -0
  34. package/templates/src/infrastructure/db/sql-client.ts +24 -0
  35. package/templates/src/infrastructure/persistence/AGENTS.md +18 -0
  36. package/templates/src/infrastructure/persistence/README.md +8 -0
  37. package/templates/src/infrastructure/persistence/repositories/AGENTS.md +17 -0
  38. package/templates/src/infrastructure/persistence/repositories/tables/AGENTS.md +20 -0
  39. package/templates/src/infrastructure/persistence/repositories/tables/README.md +5 -0
  40. package/templates/src/infrastructure/persistence/repositories/views/AGENTS.md +16 -0
  41. package/templates/src/infrastructure/persistence/repositories/views/README.md +5 -0
  42. package/templates/src/infrastructure/telemetry/AGENTS.md +14 -0
  43. package/templates/src/infrastructure/telemetry/consoleRepositoryTelemetry.ts +66 -0
  44. package/templates/src/infrastructure/telemetry/repositoryTelemetry.ts +26 -0
  45. package/templates/src/infrastructure/telemetry/types.ts +48 -0
  46. package/templates/src/jobs/AGENTS.md +25 -0
  47. package/templates/src/jobs/README.md +3 -0
  48. package/templates/src/local/sql-contract.ts +1 -0
  49. package/templates/src/presentation/AGENTS.md +15 -0
  50. package/templates/src/presentation/http/AGENTS.md +15 -0
  51. package/templates/src/presentation/http/README.md +6 -0
  52. package/templates/src/repositories/AGENTS.md +30 -0
  53. package/templates/src/repositories/tables/AGENTS.md +29 -0
  54. package/templates/src/repositories/tables/README.md +5 -0
  55. package/templates/src/repositories/views/AGENTS.md +25 -0
  56. package/templates/src/repositories/views/README.md +5 -0
  57. package/templates/src/sql/AGENTS.md +30 -0
  58. package/templates/src/sql/README.md +6 -0
  59. package/templates/tests/AGENTS.md +29 -169
  60. package/templates/tests/generated/AGENTS.md +23 -0
  61. package/templates/tests/smoke.test.ts +25 -0
  62. package/templates/tests/smoke.validation.test.ts +34 -0
  63. package/templates/tests/support/AGENTS.md +24 -0
  64. package/templates/tests/support/global-setup.ts +15 -30
  65. package/templates/tests/support/testkit-client.ts +14 -742
  66. package/templates/tests/support/testkit-client.webapi.ts +14 -0
  67. package/templates/tests/ztd-layout.generated.ts +6 -6
  68. package/templates/tsconfig.json +15 -8
  69. package/templates/vitest.config.ts +13 -13
  70. package/templates/ztd/AGENTS.md +23 -74
  71. package/templates/ztd/README.md +6 -15
  72. package/templates/ztd/ddl/AGENTS.md +26 -0
  73. package/templates/ztd/ddl/demo.sql +74 -0
  74. package/LICENSE +0 -21
  75. package/dist/commands/ddl.d.ts +0 -7
  76. package/dist/commands/ddl.js +0 -118
  77. package/dist/commands/ddl.js.map +0 -1
  78. package/dist/commands/diff.d.ts +0 -10
  79. package/dist/commands/diff.js +0 -38
  80. package/dist/commands/diff.js.map +0 -1
  81. package/dist/commands/genEntities.d.ts +0 -6
  82. package/dist/commands/genEntities.js +0 -50
  83. package/dist/commands/genEntities.js.map +0 -1
  84. package/dist/commands/init.d.ts +0 -66
  85. package/dist/commands/init.js +0 -838
  86. package/dist/commands/init.js.map +0 -1
  87. package/dist/commands/lint.d.ts +0 -59
  88. package/dist/commands/lint.js +0 -338
  89. package/dist/commands/lint.js.map +0 -1
  90. package/dist/commands/options.d.ts +0 -9
  91. package/dist/commands/options.js +0 -48
  92. package/dist/commands/options.js.map +0 -1
  93. package/dist/commands/pull.d.ts +0 -10
  94. package/dist/commands/pull.js +0 -105
  95. package/dist/commands/pull.js.map +0 -1
  96. package/dist/commands/ztdConfig.d.ts +0 -23
  97. package/dist/commands/ztdConfig.js +0 -202
  98. package/dist/commands/ztdConfig.js.map +0 -1
  99. package/dist/commands/ztdConfigCommand.d.ts +0 -5
  100. package/dist/commands/ztdConfigCommand.js +0 -157
  101. package/dist/commands/ztdConfigCommand.js.map +0 -1
  102. package/dist/index.d.ts +0 -2
  103. package/dist/index.js +0 -22
  104. package/dist/index.js.map +0 -1
  105. package/dist/utils/agents.d.ts +0 -1
  106. package/dist/utils/agents.js +0 -48
  107. package/dist/utils/agents.js.map +0 -1
  108. package/dist/utils/collectSqlFiles.d.ts +0 -9
  109. package/dist/utils/collectSqlFiles.js +0 -58
  110. package/dist/utils/collectSqlFiles.js.map +0 -1
  111. package/dist/utils/connectionSummary.d.ts +0 -3
  112. package/dist/utils/connectionSummary.js +0 -29
  113. package/dist/utils/connectionSummary.js.map +0 -1
  114. package/dist/utils/dbConnection.d.ts +0 -29
  115. package/dist/utils/dbConnection.js +0 -210
  116. package/dist/utils/dbConnection.js.map +0 -1
  117. package/dist/utils/fs.d.ts +0 -1
  118. package/dist/utils/fs.js +0 -12
  119. package/dist/utils/fs.js.map +0 -1
  120. package/dist/utils/normalizePulledSchema.d.ts +0 -12
  121. package/dist/utils/normalizePulledSchema.js +0 -213
  122. package/dist/utils/normalizePulledSchema.js.map +0 -1
  123. package/dist/utils/pgDump.d.ts +0 -11
  124. package/dist/utils/pgDump.js +0 -55
  125. package/dist/utils/pgDump.js.map +0 -1
  126. package/dist/utils/sqlLintHelpers.d.ts +0 -18
  127. package/dist/utils/sqlLintHelpers.js +0 -270
  128. package/dist/utils/sqlLintHelpers.js.map +0 -1
  129. package/dist/utils/typeMapper.d.ts +0 -4
  130. package/dist/utils/typeMapper.js +0 -76
  131. package/dist/utils/typeMapper.js.map +0 -1
  132. package/dist/utils/ztdProjectConfig.d.ts +0 -40
  133. package/dist/utils/ztdProjectConfig.js +0 -167
  134. package/dist/utils/ztdProjectConfig.js.map +0 -1
  135. package/templates/dist/drivers/pg-testkit/src/driver/PgTestkitClient.d.ts +0 -38
  136. package/templates/dist/drivers/pg-testkit/src/driver/PgTestkitClient.js +0 -117
  137. package/templates/dist/drivers/pg-testkit/src/driver/PgTestkitClient.js.map +0 -1
  138. package/templates/dist/drivers/pg-testkit/src/driver/createPgTestkitPool.d.ts +0 -4
  139. package/templates/dist/drivers/pg-testkit/src/driver/createPgTestkitPool.js +0 -71
  140. package/templates/dist/drivers/pg-testkit/src/driver/createPgTestkitPool.js.map +0 -1
  141. package/templates/dist/drivers/pg-testkit/src/index.d.ts +0 -5
  142. package/templates/dist/drivers/pg-testkit/src/index.js +0 -11
  143. package/templates/dist/drivers/pg-testkit/src/index.js.map +0 -1
  144. package/templates/dist/drivers/pg-testkit/src/proxy/wrapPgClient.d.ts +0 -3
  145. package/templates/dist/drivers/pg-testkit/src/proxy/wrapPgClient.js +0 -79
  146. package/templates/dist/drivers/pg-testkit/src/proxy/wrapPgClient.js.map +0 -1
  147. package/templates/dist/drivers/pg-testkit/src/types.d.ts +0 -69
  148. package/templates/dist/drivers/pg-testkit/src/types.js +0 -3
  149. package/templates/dist/drivers/pg-testkit/src/types.js.map +0 -1
  150. package/templates/dist/drivers/pg-testkit/src/utils/fixtureState.d.ts +0 -15
  151. package/templates/dist/drivers/pg-testkit/src/utils/fixtureState.js +0 -34
  152. package/templates/dist/drivers/pg-testkit/src/utils/fixtureState.js.map +0 -1
  153. package/templates/dist/drivers/pg-testkit/src/utils/fixtureValidation.d.ts +0 -12
  154. package/templates/dist/drivers/pg-testkit/src/utils/fixtureValidation.js +0 -53
  155. package/templates/dist/drivers/pg-testkit/src/utils/fixtureValidation.js.map +0 -1
  156. package/templates/dist/mapper-core/src/index.d.ts +0 -160
  157. package/templates/dist/mapper-core/src/index.js +0 -637
  158. package/templates/dist/mapper-core/src/index.js.map +0 -1
  159. package/templates/dist/testkit-core/src/errors/index.d.ts +0 -49
  160. package/templates/dist/testkit-core/src/errors/index.js +0 -111
  161. package/templates/dist/testkit-core/src/errors/index.js.map +0 -1
  162. package/templates/dist/testkit-core/src/fixtures/ColumnAffinity.d.ts +0 -5
  163. package/templates/dist/testkit-core/src/fixtures/ColumnAffinity.js +0 -29
  164. package/templates/dist/testkit-core/src/fixtures/ColumnAffinity.js.map +0 -1
  165. package/templates/dist/testkit-core/src/fixtures/DdlFixtureLoader.d.ts +0 -37
  166. package/templates/dist/testkit-core/src/fixtures/DdlFixtureLoader.js +0 -182
  167. package/templates/dist/testkit-core/src/fixtures/DdlFixtureLoader.js.map +0 -1
  168. package/templates/dist/testkit-core/src/fixtures/FixtureProvider.d.ts +0 -20
  169. package/templates/dist/testkit-core/src/fixtures/FixtureProvider.js +0 -121
  170. package/templates/dist/testkit-core/src/fixtures/FixtureProvider.js.map +0 -1
  171. package/templates/dist/testkit-core/src/fixtures/FixtureStore.d.ts +0 -51
  172. package/templates/dist/testkit-core/src/fixtures/FixtureStore.js +0 -199
  173. package/templates/dist/testkit-core/src/fixtures/FixtureStore.js.map +0 -1
  174. package/templates/dist/testkit-core/src/fixtures/TableDefinitionSchemaRegistry.d.ts +0 -10
  175. package/templates/dist/testkit-core/src/fixtures/TableDefinitionSchemaRegistry.js +0 -28
  176. package/templates/dist/testkit-core/src/fixtures/TableDefinitionSchemaRegistry.js.map +0 -1
  177. package/templates/dist/testkit-core/src/fixtures/TableNameResolver.d.ts +0 -18
  178. package/templates/dist/testkit-core/src/fixtures/TableNameResolver.js +0 -80
  179. package/templates/dist/testkit-core/src/fixtures/TableNameResolver.js.map +0 -1
  180. package/templates/dist/testkit-core/src/fixtures/ddlLint.d.ts +0 -59
  181. package/templates/dist/testkit-core/src/fixtures/ddlLint.js +0 -489
  182. package/templates/dist/testkit-core/src/fixtures/ddlLint.js.map +0 -1
  183. package/templates/dist/testkit-core/src/fixtures/naming.d.ts +0 -1
  184. package/templates/dist/testkit-core/src/fixtures/naming.js +0 -6
  185. package/templates/dist/testkit-core/src/fixtures/naming.js.map +0 -1
  186. package/templates/dist/testkit-core/src/index.d.ts +0 -17
  187. package/templates/dist/testkit-core/src/index.js +0 -47
  188. package/templates/dist/testkit-core/src/index.js.map +0 -1
  189. package/templates/dist/testkit-core/src/logger/NoopLogger.d.ts +0 -8
  190. package/templates/dist/testkit-core/src/logger/NoopLogger.js +0 -16
  191. package/templates/dist/testkit-core/src/logger/NoopLogger.js.map +0 -1
  192. package/templates/dist/testkit-core/src/provider/TestkitProvider.d.ts +0 -57
  193. package/templates/dist/testkit-core/src/provider/TestkitProvider.js +0 -149
  194. package/templates/dist/testkit-core/src/provider/TestkitProvider.js.map +0 -1
  195. package/templates/dist/testkit-core/src/rewriter/ResultSelectRewriter.d.ts +0 -43
  196. package/templates/dist/testkit-core/src/rewriter/ResultSelectRewriter.js +0 -473
  197. package/templates/dist/testkit-core/src/rewriter/ResultSelectRewriter.js.map +0 -1
  198. package/templates/dist/testkit-core/src/rewriter/SelectAnalyzer.d.ts +0 -9
  199. package/templates/dist/testkit-core/src/rewriter/SelectAnalyzer.js +0 -38
  200. package/templates/dist/testkit-core/src/rewriter/SelectAnalyzer.js.map +0 -1
  201. package/templates/dist/testkit-core/src/rewriter/SelectFixtureRewriter.d.ts +0 -42
  202. package/templates/dist/testkit-core/src/rewriter/SelectFixtureRewriter.js +0 -298
  203. package/templates/dist/testkit-core/src/rewriter/SelectFixtureRewriter.js.map +0 -1
  204. package/templates/dist/testkit-core/src/sql/SqliteValuesBuilder.d.ts +0 -12
  205. package/templates/dist/testkit-core/src/sql/SqliteValuesBuilder.js +0 -63
  206. package/templates/dist/testkit-core/src/sql/SqliteValuesBuilder.js.map +0 -1
  207. package/templates/dist/testkit-core/src/types/index.d.ts +0 -69
  208. package/templates/dist/testkit-core/src/types/index.js +0 -3
  209. package/templates/dist/testkit-core/src/types/index.js.map +0 -1
  210. package/templates/dist/testkit-core/src/utils/queryHelpers.d.ts +0 -28
  211. package/templates/dist/testkit-core/src/utils/queryHelpers.js +0 -81
  212. package/templates/dist/testkit-core/src/utils/queryHelpers.js.map +0 -1
  213. package/templates/dist/writer-core/src/index.d.ts +0 -34
  214. package/templates/dist/writer-core/src/index.js +0 -115
  215. package/templates/dist/writer-core/src/index.js.map +0 -1
  216. package/templates/dist/ztd-cli/templates/src/db/sql-client.d.ts +0 -20
  217. package/templates/dist/ztd-cli/templates/src/db/sql-client.js +0 -3
  218. package/templates/dist/ztd-cli/templates/src/db/sql-client.js.map +0 -1
  219. package/templates/dist/ztd-cli/templates/src/repositories/user-accounts.d.ts +0 -36
  220. package/templates/dist/ztd-cli/templates/src/repositories/user-accounts.js +0 -85
  221. package/templates/dist/ztd-cli/templates/src/repositories/user-accounts.js.map +0 -1
  222. package/templates/dist/ztd-cli/templates/tests/generated/ztd-row-map.generated.d.ts +0 -20
  223. package/templates/dist/ztd-cli/templates/tests/generated/ztd-row-map.generated.js +0 -33
  224. package/templates/dist/ztd-cli/templates/tests/generated/ztd-row-map.generated.js.map +0 -1
  225. package/templates/dist/ztd-cli/templates/tests/support/global-setup.d.ts +0 -10
  226. package/templates/dist/ztd-cli/templates/tests/support/global-setup.js +0 -29
  227. package/templates/dist/ztd-cli/templates/tests/support/global-setup.js.map +0 -1
  228. package/templates/dist/ztd-cli/templates/tests/support/testkit-client.d.ts +0 -66
  229. package/templates/dist/ztd-cli/templates/tests/support/testkit-client.js +0 -552
  230. package/templates/dist/ztd-cli/templates/tests/support/testkit-client.js.map +0 -1
  231. package/templates/dist/ztd-cli/templates/tests/user-profiles.test.d.ts +0 -1
  232. package/templates/dist/ztd-cli/templates/tests/user-profiles.test.js +0 -82
  233. package/templates/dist/ztd-cli/templates/tests/user-profiles.test.js.map +0 -1
  234. package/templates/dist/ztd-cli/templates/tests/writer-constraints.test.d.ts +0 -1
  235. package/templates/dist/ztd-cli/templates/tests/writer-constraints.test.js +0 -29
  236. package/templates/dist/ztd-cli/templates/tests/writer-constraints.test.js.map +0 -1
  237. package/templates/dist/ztd-cli/templates/tests/ztd-layout.generated.d.ts +0 -7
  238. package/templates/dist/ztd-cli/templates/tests/ztd-layout.generated.js +0 -10
  239. package/templates/dist/ztd-cli/templates/tests/ztd-layout.generated.js.map +0 -1
  240. package/templates/src/repositories/user-accounts.ts +0 -179
  241. package/templates/tests/user-profiles.test.ts +0 -161
  242. package/templates/tests/writer-constraints.test.ts +0 -32
@@ -1,217 +1,46 @@
1
- # Zero Table Dependency Project
2
-
3
- This project organizes all SQL‑related artifacts under the `ztd/` directory, separating concerns so both humans and AI can collaborate effectively without interfering with each other's responsibilities.
4
-
5
- ```
6
- /ztd
7
- /ddl
8
- *.sql <- schema definitions (required)
9
- README.md <- documentation for the layout
10
- AGENTS.md <- combined guidance for DDL
11
-
12
- /src <- application & repository code
13
- /tests <- ZTD tests, fixtures, generated maps
14
- ```
15
-
16
- Only `ztd/ddl` is part of the template contract. Do not create or assume other `ztd` subdirectories unless the project explicitly adds them.
17
-
18
- ## Generated files (important)
19
-
20
- `tests/generated/` is auto-generated and must never be committed to git.
21
- After cloning the repository (or in a clean environment), run (strongly recommended):
22
-
23
- ```bash
24
- npx ztd ztd-config
25
- ```
26
-
27
- If TypeScript reports missing modules or type errors because `tests/generated/` is missing, rerun `npx ztd ztd-config`.
28
-
29
- `tests/generated/ztd-layout.generated.ts` declares the directories above so the CLI and your tests always point at the intended files. The authoritative directory remains `ztd/ddl/`; do not read or assume additional `ztd` subdirectories.
30
-
31
- ---
32
-
33
- # Optional SqlClient seam
34
-
35
- If this project was initialized with `npx ztd init --with-sqlclient`, you'll also have `src/db/sql-client.ts`.
36
- It defines a minimal `SqlClient` interface that repositories can depend on:
37
-
38
- - Use it for tutorials and greenfield projects to keep repository SQL decoupled from drivers.
39
- - Skip it when you already have a database abstraction (Prisma, Drizzle, Kysely, custom adapters).
40
- - For `pg`, adapt `client.query(...)` so it returns a plain `T[]` row array that matches the interface.
41
- - Prefer a shared client per worker process so tests and scripts do not reconnect on every query.
42
- - Do not share a live connection across parallel workers; each worker should own its own shared client.
43
-
44
- Example (driver-agnostic):
45
-
46
- ```ts
47
- let sharedClient: SqlClient | undefined;
48
-
49
- export function getSqlClient(): SqlClient {
50
- if (!sharedClient) {
51
- // Create the client once using your chosen driver (pg, mysql, etc.).
52
- sharedClient = createSqlClientOnce();
53
- }
54
- return sharedClient;
55
- }
56
- ```
57
-
58
- ---
59
-
60
- # Mapper + writer sample
61
-
62
- - This scaffold already exposes `src/repositories/user-accounts.ts`, which maps `public.user_account` rows together with optional `public.user_profile` data through `@rawsql-ts/sql-contract/mapper` and emits insert/update/remove helpers via `@rawsql-ts/sql-contract/writer`.
63
- - The SQL used here is defined in `src/repositories/user-accounts.ts`; the template tests exercise that implementation and `ztd/ddl` is the authoritative source for every column and constraint.
64
- - Two template tests demonstrate how to run the stitch:
65
- - `tests/user-profiles.test.ts` seeds fixtures, executes the query through the mapper, and verifies the DTO shape.
66
- - `tests/writer-constraints.test.ts` reads `userAccountWriterColumnSets` plus `tests/generated/ztd-row-map.generated.ts` so writer callers stay within the approved column set when referencing `public.user_account`.
67
- - Regenerate `tests/generated/ztd-row-map.generated.ts` (`npx ztd ztd-config`) before running the example tests so the row map reflects any schema changes.
68
- - The example tests require a real PostgreSQL connection via `DATABASE_URL`; they automatically skip when the variable is missing so local tooling stays fast.
69
-
70
- # Principles
71
-
72
- ### 1. Humans own the *definitions*
73
- - DDL (physical schema)
74
- Only `ztd/ddl` is part of the template contract; other subdirectories should not be assumed.
75
-
76
- ### 2. AI owns the *implementation*
77
- - Repository SQL generation
78
- - Test fixture updates
79
- - Intermediate TypeScript structures
80
- - SQL rewriting, parameter binding, shape resolution
81
-
82
- ### 3. ZTD ensures these stay in sync
83
- ZTD acts as the consistency layer ensuring:
84
- - DDL → SQL shape consistency
85
- - Do not rely on other directories unless the project explicitly adds them.
86
-
87
- If any part diverges, ZTD tests fail deterministically.
88
-
89
- ---
90
-
91
- # Workflow Overview
92
-
93
- Different tasks start from different entry points. Choose the workflow that matches what you want to change.
94
-
95
- ---
96
-
97
- # Workflow A — Starting From *DDL Changes*
98
- (Adding tables/columns, changing constraints)
99
-
100
- 1. Edit files under `ztd/ddl/`.
101
- 2. Run:
102
-
103
- ```bash
104
- npx ztd ztd-config
105
- ```
106
-
107
- This regenerates `tests/generated/ztd-row-map.generated.ts` from the new schema.
108
-
109
- 3. Update repository SQL so it matches the new schema.
110
- 4. Update fixtures if shapes changed.
111
- 5. Run tests. Any schema mismatch will fail fast.
112
-
113
- **Flow:**
114
- **DDL -> repository SQL -> fixtures/tests -> application**
115
-
116
- ---
117
-
118
- # Workflow B — Starting From *Repository Interface Changes*
119
- (Adding a method, changing return types, etc.)
120
-
121
- 1. Modify the repository interface or class in `/src`.
122
- 2. Allow AI to generate the SQL needed to satisfy the interface.
123
- 3. If the query contradicts DDL, reconcile the authoritative definition before continuing.
124
- 4. Run ZTD tests to confirm logic is consistent.
125
- 5. Regenerate ZTD config if result shapes changed.
126
-
127
- **Flow:**
128
- **repository interface -> SQL -> tests**
129
-
130
- ---
131
-
132
- # Workflow C — Starting From Repository SQL Logic Changes
133
- (Fixing a bug, optimizing logic, rewriting a query)
134
-
135
- 1. Edit SQL inside the repository.
136
- 2. Run ZTD tests.
137
- 3. If the intended behavior changes, update the DDL before adjusting dependent logic and keep documentation aligned with the confirmed schema.
138
- 4. Update fixtures as necessary.
139
- 5. If SQL result shape changed, run:
140
-
141
- ```bash
142
- npx ztd ztd-config
143
- ```
144
-
145
- **Flow:**
146
- **SQL -> fixtures/tests**
147
-
148
- ---
149
-
150
- # Combined Real‑World Flow Examples
151
-
152
- - **Add a new contract status**
153
- DDL -> SQL -> config -> tests
154
-
155
- - **Add a new table**
156
- DDL -> config -> SQL -> fixtures -> tests
157
-
158
- - **Fix business logic**
159
- SQL -> tests
160
-
161
- ZTD ensures all changes converge into a consistent, validated workflow.
162
- ---
163
-
164
- # Human Responsibilities
165
-
166
- - Humans maintain:
167
- - Physical schema (`ddl`)
168
- - High‑level repository interfaces
169
- - Acceptance of AI-generated changes
170
-
171
- Humans decide “what is correct.”
172
-
173
- ---
174
-
175
- # AI Responsibilities
176
-
177
- AI must:
178
-
179
- - Use DDL as the **physical shape constraint** and primary source of truth.
180
- - Do not assume any additional `ztd` directories exist unless a human explicitly creates them.
181
- - Generate repository SQL consistent with DDL and the documented behavior.
182
- - Regenerate fixtures and tests as instructed.
183
- - Never modify `ztd/AGENTS.md` or `ztd/README.md` unless explicitly asked.
184
-
185
- AI decides “how to implement” within those constraints.
186
-
187
- ---
188
-
189
- # ZTD CLI Responsibilities
190
-
191
- ZTD CLI:
192
-
193
- - Parses DDL files to build accurate table/column shapes
194
- - Rewrites SQL with fixture-based CTE shadowing (via testkit adapters)
195
- - Generates `ztd-row-map.generated.ts`
196
- - Produces deterministic, parallelizable tests
197
-
198
- ZTD is the verification engine guaranteeing correctness.
199
-
200
- ## Traditional execution mode
201
-
202
- - Set `ZTD_EXECUTION_MODE=traditional` or pass `{ mode: 'traditional', traditional: { isolation: 'schema', cleanup: 'drop_schema' } }` when you need to run the tests against a real Postgres schema (locking, isolation, constraints). The helper still applies the DDL inside `ztd/ddl/`, loads the fixture rows into the schema, optionally executes `setupSql`, and carries out the chosen cleanup strategy (`drop_schema`, `custom_sql`, or `none`).
203
- - Use `isolation: 'none'` if you need to target a schema that is already defined or if your SQL embeds schema qualifiers explicitly.
204
-
205
- ---
206
-
207
- # Summary
208
-
209
- ZTD enables a workflow where **humans define meaning**, **AI writes implementation**, and **tests guarantee correctness**.
210
-
211
- The project layout and workflows above ensure long-term maintainability, clarity, and full reproducibility of SQL logic independent of physical database state.
212
-
213
- ## Recommended local verification
214
-
215
- - `npx ztd ztd-config` (Recommended)
216
- - `pnpm -C packages/ztd-cli test` (Recommended)
217
- - `templates/tests/user-profiles.test.ts` runs only when `DATABASE_URL` is configured; otherwise it skips automatically.
1
+ # Zero Table Dependency Project
2
+
3
+ This project uses Zero Table Dependency (ZTD) to keep SQL, DDL, and tests aligned.
4
+
5
+ Conceptual model:
6
+ - `ztd-cli` implicitly uses only `ZTD_TEST_DATABASE_URL`.
7
+ - `DATABASE_URL` and other runtime or deployment database settings are outside the ownership of `ztd-cli`.
8
+ - Any non-ZTD database target must be passed explicitly via `--url` or `--db-*`.
9
+ - `ztd-cli` may generate migration SQL artifacts, but it does not apply them.
10
+
11
+ Quick boundary table:
12
+ - `ZTD_TEST_DATABASE_URL`: owned by `ztd-cli` for ZTD tests and verification
13
+ - `DATABASE_URL`: runtime or deployment concern, not read automatically by `ztd-cli`
14
+ - `--url` / complete `--db-*`: explicit target inspection only
15
+
16
+ Key folders:
17
+ - ztd/ddl: schema files (source of truth)
18
+ - src: application SQL and repositories
19
+ - tests: ZTD tests and support
20
+
21
+ Next steps:
22
+ 1. Update `ztd/ddl/<schema>.sql` if needed.
23
+ 2. Run `pnpm exec ztd ztd-config` (or `npx ztd ztd-config` if you prefer npm-style invocation).
24
+ 3. Run `pnpm exec ztd model-gen --probe-mode ztd <sql-file> --out <spec-file>` when you want a QuerySpec scaffold from the local DDL snapshot.
25
+ 4. Wire repositories to `src/infrastructure/telemetry/repositoryTelemetry.ts` so application code can replace the default telemetry hook.
26
+ 5. Provide a SqlClient implementation.
27
+ 6. Run tests (`pnpm test` or `npx vitest run`).
28
+ 7. Use `ZTD_TEST_DATABASE_URL` for ZTD-owned test and verification workflows.
29
+ 8. Use `ztd model-gen --probe-mode live`, `ztd ddl pull`, or `ztd ddl diff` only for explicit target inspection by passing `--url` or a complete `--db-*` flag set.
30
+ 9. If you generate migration SQL artifacts, apply them with your deployment tooling instead of `ztd-cli`.
31
+
32
+ If this project was scaffolded with `ztd init --local-source-root <monorepo-root>`, first run `pnpm install` (or `pnpm install --ignore-workspace` when nested under another `pnpm-workspace.yaml`), then `pnpm typecheck`, then `pnpm test`, then `pnpm ztd ztd-config`. For generated QuerySpecs, prefer `pnpm ztd model-gen --probe-mode ztd --import-style relative` so imports keep using the local shim.
33
+
34
+ For schema-change impact checks, `npx ztd query uses` defaults to the `impact` view. Table add / column add checks usually work with the default scan, while table rename / column rename / column type change checks often benefit from `--exclude-generated` so review-only specs under `src/catalog/specs/generated` do not add noise. The flag is optional and does not change the default scan set.
35
+
36
+ If you later switch this scaffold into a layered WebAPI shape, add a prompt-dogfooding guide similar to `PROMPT_DOGFOOD.md` so generic transport requests can be checked for accidental ZTD leakage.
37
+
38
+ Examples:
39
+
40
+ ```bash
41
+ pnpm exec ztd query uses table public.sale_items --exclude-generated
42
+ pnpm exec ztd query uses table public.sale_lines --exclude-generated
43
+ pnpm exec ztd query uses column public.products.title --exclude-generated
44
+ pnpm exec ztd query uses column public.sale_items.quantity --exclude-generated
45
+ pnpm exec ztd query uses table public.sale_lines --view detail --exclude-generated
46
+ ```
@@ -0,0 +1,38 @@
1
+ # Zero Table Dependency WebAPI Project
2
+
3
+ This scaffold separates WebAPI concerns into explicit layers so transport and use-case work do not accidentally inherit persistence-specific rules.
4
+
5
+ Conceptual model:
6
+ - `ztd-cli` implicitly uses only `ZTD_TEST_DATABASE_URL`.
7
+ - Projects often also have a runtime or deployment database setting such as `DATABASE_URL`, but `ztd-cli` does not read it automatically.
8
+ - Any non-ZTD database target must be passed explicitly via `--url` or `--db-*`.
9
+ - `ztd-cli` may generate migration SQL artifacts, but it does not apply them.
10
+
11
+ Quick boundary table:
12
+ - `ZTD_TEST_DATABASE_URL`: owned by `ztd-cli` for ZTD tests and verification
13
+ - `DATABASE_URL`: runtime or deployment concern, not read automatically by `ztd-cli`
14
+ - `--url` / complete `--db-*`: explicit target inspection only
15
+
16
+ Key folders:
17
+ - `src/domain`: domain types and business rules with no direct ZTD dependency
18
+ - `src/application`: use cases and orchestration over domain-facing ports
19
+ - `src/presentation/http`: HTTP handlers, request parsing, and response shaping
20
+ - `src/infrastructure/persistence`: repositories, SQL assets, and QuerySpec wiring
21
+ - `src/sql`, `src/catalog`, `ztd/ddl`: ZTD-owned persistence assets
22
+ - `tests`: smoke tests and test support
23
+
24
+ Prompt dogfooding:
25
+ - See `PROMPT_DOGFOOD.md` when you want to verify that generic WebAPI requests stay out of persistence-specific ZTD guidance unless repository or SQL work is explicitly requested.
26
+
27
+ Next steps:
28
+ 1. Update `ztd/ddl/<schema>.sql` if needed.
29
+ 2. Run `pnpm exec ztd ztd-config` (or `npx ztd ztd-config` if you prefer npm-style invocation).
30
+ 3. Keep `src/domain`, `src/application`, and `src/presentation/http` free from direct SQL or DDL concerns.
31
+ 4. Use `pnpm exec ztd model-gen --probe-mode ztd <sql-file> --out <spec-file>` when you want a QuerySpec scaffold from the local DDL snapshot.
32
+ 5. Wire repositories to `src/infrastructure/telemetry/repositoryTelemetry.ts` only when you add SQL-backed repository classes.
33
+ 6. Provide a SqlClient implementation in `src/infrastructure/db`.
34
+ 7. Run tests (`pnpm test` or `npx vitest run`) with `ZTD_TEST_DATABASE_URL` when SQL-backed verification needs a managed test database.
35
+ 8. Treat `ddl pull` and `ddl diff` as explicit target inspection commands that require `--url` or a complete `--db-*` flag set.
36
+ 9. If you generate migration SQL artifacts, apply them outside `ztd-cli`.
37
+
38
+ If this project was scaffolded with `ztd init --local-source-root <monorepo-root>`, first run `pnpm install` (or `pnpm install --ignore-workspace` when nested under another `pnpm-workspace.yaml`), then `pnpm typecheck`, then `pnpm test`, then `pnpm ztd ztd-config`. For generated QuerySpecs, prefer `pnpm ztd model-gen --probe-mode ztd --import-style relative` so imports keep using the local shim.
@@ -0,0 +1,189 @@
1
+ import { createRequire } from 'node:module';
2
+ import { existsSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+
6
+ const projectRoot = process.cwd();
7
+ const command = process.argv[2];
8
+
9
+ if (command !== 'test' && command !== 'typecheck' && command !== 'ztd') {
10
+ console.error('local-source guard expects "test", "typecheck", or "ztd".');
11
+ process.exit(1);
12
+ }
13
+
14
+ if (command === 'ztd') {
15
+ const cliEntry = path.resolve(projectRoot, '__LOCAL_SOURCE_ZTD_CLI__');
16
+ const requestedSubcommand = process.argv.slice(3).join(' ').trim() || '(none)';
17
+ if (!existsSync(cliEntry)) {
18
+ console.error(
19
+ [
20
+ '[local-source guard] ztd cannot run against the local source checkout yet.',
21
+ '',
22
+ `What happened:`,
23
+ `- Requested subcommand: ${requestedSubcommand}`,
24
+ `- Project root: ${normalizePath(projectRoot)}`,
25
+ `- The local CLI entry was not found at ${normalizePath(cliEntry)}.`,
26
+ '',
27
+ 'Next steps:',
28
+ '1. Build the local CLI package (for example: pnpm --filter @rawsql-ts/ztd-cli build)',
29
+ '2. Confirm this scaffold still points at the intended rawsql-ts monorepo root',
30
+ '3. Re-run pnpm ztd <subcommand>'
31
+ ].join('\n')
32
+ );
33
+ process.exit(1);
34
+ }
35
+
36
+ const cliArgs = process.argv.slice(3);
37
+ const result = spawnSync(process.execPath, [cliEntry, ...cliArgs], {
38
+ cwd: projectRoot,
39
+ stdio: 'inherit',
40
+ shell: false
41
+ });
42
+
43
+ // Surface execution failures explicitly so local-source dogfooding does not
44
+ // collapse permission, spawn, and signal problems into the same exit code.
45
+ if (result.error) {
46
+ console.error('[local-source guard] Failed to launch the local ztd CLI entry.');
47
+ console.error(`- Message: ${result.error.message}`);
48
+ if (result.error.stack) {
49
+ console.error(result.error.stack);
50
+ }
51
+ process.exit(1);
52
+ }
53
+
54
+ if (result.signal) {
55
+ console.error('[local-source guard] The local ztd CLI entry was terminated by signal.');
56
+ console.error(`- Signal: ${result.signal}`);
57
+ process.exit(1);
58
+ }
59
+
60
+ process.exit(result.status ?? 1);
61
+ }
62
+
63
+ const workspaceRoot = findAncestorPnpmWorkspaceRoot(projectRoot);
64
+ const installCommand = workspaceRoot ? 'pnpm install --ignore-workspace' : 'pnpm install';
65
+ const rerunCommand = command === 'test' ? 'pnpm test' : 'pnpm typecheck';
66
+ const fallbackCommand = command === 'test' ? 'npx vitest run' : 'npx tsc --noEmit';
67
+ const binaryName = process.platform === 'win32'
68
+ ? command === 'test'
69
+ ? 'vitest.cmd'
70
+ : 'tsc.cmd'
71
+ : command === 'test'
72
+ ? 'vitest'
73
+ : 'tsc';
74
+ const binaryArgs = command === 'test' ? ['run'] : ['--noEmit'];
75
+ const binaryPath = path.join(projectRoot, 'node_modules', '.bin', binaryName);
76
+ const packageChecks = command === 'test'
77
+ ? ['vitest/package.json', 'zod/package.json', '@rawsql-ts/sql-contract/package.json']
78
+ : ['typescript/package.json', 'zod/package.json', '@rawsql-ts/sql-contract/package.json'];
79
+
80
+ const resolutionIssues = inspectResolution(packageChecks);
81
+ if (!existsSync(binaryPath) || resolutionIssues.length > 0) {
82
+ printGuidance({
83
+ command,
84
+ installCommand,
85
+ rerunCommand,
86
+ fallbackCommand,
87
+ workspaceRoot,
88
+ binaryPath,
89
+ resolutionIssues
90
+ });
91
+ process.exit(1);
92
+ }
93
+
94
+ const result = spawnSync(binaryPath, binaryArgs, {
95
+ cwd: projectRoot,
96
+ stdio: 'inherit',
97
+ shell: process.platform === 'win32'
98
+ });
99
+ process.exit(result.status ?? 1);
100
+
101
+ function inspectResolution(specifiers) {
102
+ const projectRequire = createRequire(path.join(projectRoot, 'package.json'));
103
+ const issues = [];
104
+
105
+ for (const specifier of specifiers) {
106
+ try {
107
+ const resolvedPath = specifier.endsWith('/package.json')
108
+ ? resolveInstalledPackageManifest(specifier)
109
+ : projectRequire.resolve(specifier);
110
+ if (!isInsideProject(resolvedPath)) {
111
+ issues.push(`${specifier} resolved outside this scaffold: ${normalizePath(resolvedPath)}`);
112
+ }
113
+ } catch {
114
+ issues.push(`${specifier} is not installed in this scaffold`);
115
+ }
116
+ }
117
+
118
+ return issues;
119
+ }
120
+
121
+ function resolveInstalledPackageManifest(specifier) {
122
+ const manifestRelativePath = path.join('node_modules', ...specifier.split('/'));
123
+ const manifestPath = path.join(projectRoot, manifestRelativePath);
124
+ if (!existsSync(manifestPath)) {
125
+ throw new Error(`${specifier} manifest is missing`);
126
+ }
127
+ return manifestPath;
128
+ }
129
+
130
+ function isInsideProject(filePath) {
131
+ const relativePath = path.relative(projectRoot, filePath);
132
+ return relativePath.length === 0 || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
133
+ }
134
+
135
+ function normalizePath(filePath) {
136
+ return filePath.replace(/\\/g, '/');
137
+ }
138
+
139
+ function findAncestorPnpmWorkspaceRoot(rootDir) {
140
+ let cursor = path.resolve(rootDir);
141
+ while (true) {
142
+ const parentDir = path.dirname(cursor);
143
+ if (parentDir === cursor) {
144
+ return null;
145
+ }
146
+ cursor = parentDir;
147
+ if (existsSync(path.join(cursor, 'pnpm-workspace.yaml'))) {
148
+ return cursor;
149
+ }
150
+ }
151
+ }
152
+
153
+ function printGuidance({
154
+ command,
155
+ installCommand,
156
+ rerunCommand,
157
+ fallbackCommand,
158
+ workspaceRoot,
159
+ binaryPath,
160
+ resolutionIssues
161
+ }) {
162
+ const commandLabel = command === 'test' ? 'pnpm test' : 'pnpm typecheck';
163
+ const lines = [
164
+ `[local-source guard] ${commandLabel} cannot run against this scaffold yet.`,
165
+ '',
166
+ 'What happened:',
167
+ `- The local binary was not found at ${normalizePath(binaryPath)} or required packages resolved outside this project.`,
168
+ ];
169
+
170
+ if (workspaceRoot) {
171
+ lines.push(`- This project sits under pnpm workspace ${normalizePath(workspaceRoot)}, so pnpm may still be using the parent workspace context.`);
172
+ }
173
+
174
+ if (resolutionIssues.length > 0) {
175
+ lines.push('- Resolution details:');
176
+ for (const issue of resolutionIssues) {
177
+ lines.push(` - ${issue}`);
178
+ }
179
+ }
180
+
181
+ lines.push(
182
+ '',
183
+ 'Next steps:',
184
+ `1. Run ${installCommand}`,
185
+ `2. Re-run ${rerunCommand}`,
186
+ `3. If pnpm still looks absorbed by the parent workspace, try ${fallbackCommand}`
187
+ );
188
+ console.error(lines.join('\n'));
189
+ }
@@ -0,0 +1,26 @@
1
+ # Package Scope
2
+ - Applies to `packages/ztd-cli/templates/src`.
3
+ - Governs runtime application code emitted by template generation across both generic and WebAPI-oriented layouts.
4
+
5
+ # Policy
6
+ ## REQUIRED
7
+ - Runtime code under `src/` MUST remain independent from `tests/` and `tests/generated/` imports.
8
+ - Runtime code MUST remain independent from ZTD internals.
9
+ - Runtime modules MUST use explicit contracts and deterministic failure surfaces.
10
+ - Domain, application, and presentation layers MUST stay free from direct SQL/DDL ownership when those directories exist.
11
+
12
+ ## ALLOWED
13
+ - Runtime modules MAY use project typecheck and filtered test commands for validation.
14
+
15
+ ## PROHIBITED
16
+ - Importing test-only helpers into runtime code.
17
+ - Treating generated artifacts as runtime dependencies.
18
+
19
+ # Mandatory Workflow
20
+ - Before committing changes under `src/`, run project typecheck and relevant tests.
21
+
22
+ # Hygiene
23
+ - Keep runtime modules explicit and small enough to preserve contract boundaries.
24
+
25
+ # References
26
+ - Parent policy: [../AGENTS.md](../AGENTS.md)
@@ -0,0 +1,15 @@
1
+ # Package Scope
2
+ - Applies to `packages/ztd-cli/templates/src/application`.
3
+ - Defines application-layer orchestration for WebAPI-oriented scaffolds.
4
+
5
+ # Policy
6
+ ## REQUIRED
7
+ - Application modules MUST orchestrate domain-facing ports and policies without embedding transport or SQL details.
8
+ - Validation and command handling MAY live here when it stays independent from persistence mechanics.
9
+
10
+ ## PROHIBITED
11
+ - Direct SQL file access.
12
+ - Direct dependence on DDL or QuerySpec implementation details.
13
+
14
+ # Mandatory Workflow
15
+ - Application changes MUST run the relevant typecheck and behavioral tests.
@@ -0,0 +1,6 @@
1
+ # Application
2
+
3
+ Use cases and orchestration live here.
4
+
5
+ - Coordinate domain behavior and repository ports here.
6
+ - Avoid direct ownership of SQL, DDL, and ZTD configuration in this layer.
@@ -0,0 +1,28 @@
1
+ # Package Scope
2
+ - Applies to `packages/ztd-cli/templates/src/catalog`.
3
+ - Defines runtime catalog contract boundaries between human-owned specs and runtime wiring.
4
+
5
+ # Policy
6
+ ## REQUIRED
7
+ - Catalog entrypoints MUST bind SQL assets, parameter contracts, output contracts, and runtime validation/mapping.
8
+ - `src/catalog/specs` MUST be treated as human-owned contracts.
9
+ - `src/catalog/runtime` MUST implement runtime wiring only.
10
+ - Code in `src/catalog` MUST remain independent from `tests`, `tests/generated`, and `ztd` imports.
11
+ - Each spec MUST be covered by tests for rewrite execution, mapping/validation outcomes, and output shape.
12
+
13
+ ## ALLOWED
14
+ - Runtime catalog code MAY add observability hooks where contract behavior is unchanged.
15
+
16
+ ## PROHIBITED
17
+ - Changing spec params/DTO contracts without explicit instruction.
18
+ - Runtime dependency on ZTD internals.
19
+
20
+ # Mandatory Workflow
21
+ - Catalog spec or runtime changes MUST run tests that exercise affected specs.
22
+
23
+ # Hygiene
24
+ - Preserve clear separation between `specs` and `runtime` responsibilities.
25
+
26
+ # References
27
+ - Specs contract: [./specs/AGENTS.md](./specs/AGENTS.md)
28
+ - Runtime contract: [./runtime/AGENTS.md](./runtime/AGENTS.md)
@@ -0,0 +1,28 @@
1
+ # Package Scope
2
+ - Applies to `packages/ztd-cli/templates/src/catalog/runtime`.
3
+ - Defines runtime validation, normalization, and row-to-DTO mapping behavior.
4
+
5
+ # Policy
6
+ ## REQUIRED
7
+ - Runtime MUST be the only layer that validates unknown input into typed params and validates output contracts.
8
+ - Runtime MUST map snake_case SQL rows into DTO objects using explicit normalization rules.
9
+ - Timestamp normalization MUST use `timestampFromDriver`.
10
+ - Numeric normalization rules MUST be explicit per contract.
11
+ - Runtime helpers (`ensure*`, `map*`) MUST validate before returning values.
12
+
13
+ ## ALLOWED
14
+ - Command outputs MAY be scalar identifiers or `void` when the contract output is non-DTO.
15
+
16
+ ## PROHIBITED
17
+ - Database I/O in runtime mapping modules.
18
+ - Importing from `tests/` or `tests/generated/`.
19
+ - Defining or editing human-owned contracts in runtime modules.
20
+
21
+ # Mandatory Workflow
22
+ - Runtime changes MUST run tests that cover normalization and validator failure paths.
23
+
24
+ # Hygiene
25
+ - Do not swallow validator errors or silently coerce unsupported values.
26
+
27
+ # References
28
+ - Parent catalog policy: [../AGENTS.md](../AGENTS.md)
@@ -0,0 +1,30 @@
1
+ // Runtime coercions run BEFORE validator schemas.
2
+ // See docs/recipes/mapping-vs-validation.md for pipeline details.
3
+ export function normalizeTimestamp(value: unknown, fieldName?: string): Date {
4
+ const fieldLabel = fieldName?.trim() ? ` for "${fieldName}"` : '';
5
+
6
+ // Preserve valid Date instances while rejecting invalid dates eagerly.
7
+ if (value instanceof Date) {
8
+ if (Number.isNaN(value.getTime())) {
9
+ throw new Error(`Invalid Date value${fieldLabel}.`);
10
+ }
11
+ return value;
12
+ }
13
+
14
+ // Parse driver-returned timestamp strings after trimming transport whitespace.
15
+ if (typeof value === 'string') {
16
+ const trimmed = value.trim();
17
+ if (!trimmed) {
18
+ throw new Error(`Expected a non-empty timestamp string${fieldLabel}.`);
19
+ }
20
+
21
+ const timestamp = Date.parse(trimmed);
22
+ if (Number.isNaN(timestamp)) {
23
+ throw new Error(`Invalid timestamp string${fieldLabel}: "${value}".`);
24
+ }
25
+ return new Date(timestamp);
26
+ }
27
+
28
+ const actualType = value === null ? 'null' : typeof value;
29
+ throw new Error(`Expected Date or timestamp string${fieldLabel}, received ${actualType}.`);
30
+ }
@@ -0,0 +1,30 @@
1
+ // Runtime coercions run BEFORE validator schemas.
2
+ // See docs/recipes/mapping-vs-validation.md for pipeline details.
3
+ export function normalizeTimestamp(value: unknown, fieldName?: string): Date {
4
+ const fieldLabel = fieldName?.trim() ? ` for "${fieldName}"` : '';
5
+
6
+ // Preserve valid Date instances while rejecting invalid dates eagerly.
7
+ if (value instanceof Date) {
8
+ if (Number.isNaN(value.getTime())) {
9
+ throw new Error(`Invalid Date value${fieldLabel}.`);
10
+ }
11
+ return value;
12
+ }
13
+
14
+ // Parse driver-returned timestamp strings after trimming transport whitespace.
15
+ if (typeof value === 'string') {
16
+ const trimmed = value.trim();
17
+ if (!trimmed) {
18
+ throw new Error(`Expected a non-empty timestamp string${fieldLabel}.`);
19
+ }
20
+
21
+ const timestamp = Date.parse(trimmed);
22
+ if (Number.isNaN(timestamp)) {
23
+ throw new Error(`Invalid timestamp string${fieldLabel}: "${value}".`);
24
+ }
25
+ return new Date(timestamp);
26
+ }
27
+
28
+ const actualType = value === null ? 'null' : typeof value;
29
+ throw new Error(`Expected Date or timestamp string${fieldLabel}, received ${actualType}.`);
30
+ }