@thomasfosterau/effect-drizzle-ext 0.0.0-stage → 0.1.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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +508 -2
  3. package/dist/columns.d.ts +113 -0
  4. package/dist/columns.d.ts.map +1 -0
  5. package/dist/columns.js +146 -0
  6. package/dist/columns.js.map +1 -0
  7. package/dist/constraints.d.ts +117 -0
  8. package/dist/constraints.d.ts.map +1 -0
  9. package/dist/constraints.js +93 -0
  10. package/dist/constraints.js.map +1 -0
  11. package/dist/database.d.ts +189 -0
  12. package/dist/database.d.ts.map +1 -0
  13. package/dist/database.js +51 -0
  14. package/dist/database.js.map +1 -0
  15. package/dist/derive.d.ts +97 -0
  16. package/dist/derive.d.ts.map +1 -0
  17. package/dist/derive.js +142 -0
  18. package/dist/derive.js.map +1 -0
  19. package/dist/errors.d.ts +157 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.js +287 -0
  22. package/dist/errors.js.map +1 -0
  23. package/dist/index-CFhgcF4N.d.ts +418 -0
  24. package/dist/index-CFhgcF4N.d.ts.map +1 -0
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.js +1 -0
  27. package/dist/jsonapi/derive.d.ts +28 -0
  28. package/dist/jsonapi/derive.d.ts.map +1 -0
  29. package/dist/jsonapi/derive.js +28 -0
  30. package/dist/jsonapi/derive.js.map +1 -0
  31. package/dist/jsonapi/index.d.ts +189 -0
  32. package/dist/jsonapi/index.d.ts.map +1 -0
  33. package/dist/jsonapi/index.js +164 -0
  34. package/dist/jsonapi/index.js.map +1 -0
  35. package/dist/query.d.ts +111 -0
  36. package/dist/query.d.ts.map +1 -0
  37. package/dist/query.js +142 -0
  38. package/dist/query.js.map +1 -0
  39. package/dist/references.d.ts +38 -0
  40. package/dist/references.d.ts.map +1 -0
  41. package/dist/references.js +60 -0
  42. package/dist/references.js.map +1 -0
  43. package/dist/sort.d.ts +129 -0
  44. package/dist/sort.d.ts.map +1 -0
  45. package/dist/sort.js +225 -0
  46. package/dist/sort.js.map +1 -0
  47. package/dist/src-CBeuhy5b.js +478 -0
  48. package/dist/src-CBeuhy5b.js.map +1 -0
  49. package/dist/update.d.ts +61 -0
  50. package/dist/update.d.ts.map +1 -0
  51. package/dist/update.js +64 -0
  52. package/dist/update.js.map +1 -0
  53. package/dist/versioned-chain.d.ts +524 -0
  54. package/dist/versioned-chain.d.ts.map +1 -0
  55. package/dist/versioned-chain.js +298 -0
  56. package/dist/versioned-chain.js.map +1 -0
  57. package/package.json +109 -3
  58. package/src/columns.ts +186 -0
  59. package/src/constraints.ts +225 -0
  60. package/src/database.ts +273 -0
  61. package/src/derivation-error.ts +27 -0
  62. package/src/derive.ts +320 -0
  63. package/src/errors.ts +466 -0
  64. package/src/index.ts +653 -0
  65. package/src/jsonapi/derive.ts +74 -0
  66. package/src/jsonapi/index.ts +355 -0
  67. package/src/query.ts +183 -0
  68. package/src/references.ts +103 -0
  69. package/src/sort.ts +368 -0
  70. package/src/tree.ts +367 -0
  71. package/src/update.ts +88 -0
  72. package/src/versioned-chain.ts +1027 -0
@@ -0,0 +1,524 @@
1
+ import { F as Tree } from "./index-CFhgcF4N.js";
2
+ import { ColumnBuilders, DeriveOptions, DerivedColumns } from "./derive.js";
3
+ import { ColumnBuilderBase, GetColumnData, SQL } from "drizzle-orm";
4
+ import { AnyPgColumn, ExtraConfigColumn, PgBigInt64Builder, PgBuildColumns, PgSchema, PgTable, PgTableExtraConfigValue, PgTableWithColumns, PgTextBuilder, PgTimestampBuilder, PgUUIDBuilder, PgViewWithSelection, SetHasDefault, SetIsPrimaryKey, SetNotNull } from "drizzle-orm/pg-core";
5
+ /** A parent version table contributes its `(id, version)` key as the FK target. */
6
+ type ParentVersionTable = PgTable & {
7
+ id: AnyPgColumn;
8
+ version: AnyPgColumn;
9
+ };
10
+ /** The stable row's `id` + `type` columns a discriminator hook constrains. */
11
+ export interface DiscriminatorColumns {
12
+ id: AnyPgColumn;
13
+ type: AnyPgColumn;
14
+ }
15
+ /**
16
+ * Declaration accepted by a {@link makeSubtypeTables} builder.
17
+ *
18
+ * **The optional members below are NOT migrated to `Option`, and must not be.**
19
+ * Two reasons, either sufficient:
20
+ *
21
+ * 1. This is the config a hierarchy hands the Drizzle table builder, and it is
22
+ * handed on unchanged to a consuming application's database package — a
23
+ * signature change here is a signature change for every consumer.
24
+ * 2. Every one of them is threaded straight into a Drizzle builder call
25
+ * (`bigint().default(versionDefault)`, `discriminator`, …). Drizzle reads
26
+ * plain `undefined` as "not supplied"; an `Option` would be an object it
27
+ * would try to emit as a value, producing wrong DDL that no type error and
28
+ * no unit test would catch.
29
+ *
30
+ * The explicit `| undefined` on each is likewise deliberate: a configuring
31
+ * layer can thread its own optional overrides through without per-property
32
+ * conditional spreads (`exactOptionalPropertyTypes`).
33
+ *
34
+ * Constraint and column names are canonical (`${name}_version_pkey`,
35
+ * `${name}_version_${parentVersion}_fk`, an `id` column): no deployed schema
36
+ * needs the historical overrides (effect-drizzle-ext#112, decision 4).
37
+ */
38
+ export interface SubtypeTablesConfig<TColumns extends Record<string, ColumnBuilderBase>, TDiscriminator> {
39
+ /** Leaf name; the version table is `${name}_version` (e.g. `video`). */
40
+ name: string;
41
+ /**
42
+ * The parent branch's stable `id` column the leaf's stable row references
43
+ * (cascade delete).
44
+ */
45
+ parentId: AnyPgColumn;
46
+ /**
47
+ * The parent branch's version table; the leaf version's `(id, version)`
48
+ * foreign-keys to its `(id, version)`. Its name seeds the FK constraint name
49
+ * (`${name}_version_${parentVersionName}_fk`).
50
+ */
51
+ parentVersion: ParentVersionTable;
52
+ /** The leaf's own domain columns (beyond the generated id/version). */
53
+ columns: TColumns;
54
+ /**
55
+ * Default for the `version` column — e.g. `0n` for branch-extending leaves
56
+ * whose first version row is written with an explicit `0`. Omitted leaves get
57
+ * a plain `NOT NULL` version.
58
+ */
59
+ versionDefault?: bigint | undefined;
60
+ /**
61
+ * Extra version-table constraints/indexes appended after the generated primary
62
+ * key and parent-version foreign key. The callback receives the version
63
+ * table's columns (including any generated column declared in `columns`).
64
+ */
65
+ extraConstraints?: ((table: Record<string, ExtraConfigColumn>) => PgTableExtraConfigValue[]) | undefined;
66
+ /**
67
+ * The discriminator declaration for this leaf's stable row. When present, the
68
+ * stable `type` column is made `NOT NULL` and the injected discriminator hook
69
+ * appends its constraints; when absent the stable row keeps a nullable `type`.
70
+ * Its shape is whatever the injected hook consumes.
71
+ */
72
+ discriminator?: TDiscriminator | undefined;
73
+ }
74
+ /**
75
+ * The stable identity row's generated column builders — `id` (the primary key
76
+ * that references the parent branch's stable `id`) and the denormalised leaf
77
+ * `type` discriminator.
78
+ *
79
+ * `type` is a plain {@link PgTextBuilder} rather than a union with its
80
+ * `.notNull()` form because the two reduce: `SetNotNull<T>` is `T & …`, so
81
+ * `SetNotNull<T> | T` collapses to `T` under union subtype reduction. This
82
+ * restates what the compiler already infers for the ternary in the body, it does
83
+ * not widen it — the `NOT NULL` a discriminator adds was never in the inferred
84
+ * type either.
85
+ */
86
+ type SubtypeStableColumnBuilders = {
87
+ id: SetIsPrimaryKey<PgUUIDBuilder>;
88
+ type: PgTextBuilder<[string, ...string[]]>;
89
+ };
90
+ /**
91
+ * The version row's column builders: the generated `(id, version)` key columns
92
+ * plus the leaf's own domain columns. `version` is a plain
93
+ * `SetNotNull<PgBigInt64Builder>` for the same subtype-reduction reason `type`
94
+ * is above — `bigint().notNull().default(…)` is `HasDefault<SetNotNull<…>>`, a
95
+ * subtype of the no-default branch, so the union the ternary produces has
96
+ * already collapsed.
97
+ *
98
+ * This alias is referenced **both** by {@link SubtypeTables} and by the explicit
99
+ * type arguments at the `schema.table` call that builds the table, and it has to
100
+ * be: see the note on that call.
101
+ */
102
+ type SubtypeVersionColumnBuilders<TColumns extends Record<string, ColumnBuilderBase>> = {
103
+ id: SetNotNull<PgUUIDBuilder>;
104
+ version: SetNotNull<PgBigInt64Builder>;
105
+ } & TColumns;
106
+ /**
107
+ * The `{ stable, version }` table pair a {@link makeSubtypeTables} builder
108
+ * returns.
109
+ *
110
+ * **Why this is written out rather than inferred.** Declaration emit cannot
111
+ * name the inferred form. `PgBuildColumns<TTableName, TColumnsMap>` is a
112
+ * homomorphic mapped type; when `TColumnsMap` is generic (it contains this
113
+ * builder's `TColumns`) the compiler cannot reduce it, and the declaration
114
+ * printer expands the mapped type's *body* while printing drizzle's own
115
+ * `TTableName` type parameter by name — a name that is not in scope in the
116
+ * emitted `.d.ts`. The result is an unbound type reference: `tsc` resolves it to
117
+ * the error type, `skipLibCheck: true` hides the `TS2304`, and every consumer
118
+ * reading these tables through the emitted declarations silently gets `any`
119
+ * where it should get the column's real type — a consuming application's
120
+ * database package degrading a table's `$inferSelect["id"]` from `string` to
121
+ * `any` with no type error anywhere in the chain (website #1579). This
122
+ * package's own `tests/dist-consumer` type-checks a consumer fixture against
123
+ * the emitted `dist/`, asserting the chain tables' row types precisely to
124
+ * catch this regression (an unbound name degrades them to `any`, and an
125
+ * `Equals<…>` assertion against `any` is false); it is the gate this module
126
+ * exists chiefly to satisfy.
127
+ *
128
+ * An explicit annotation is emitted verbatim as a type *reference*, so the
129
+ * printer never expands the mapped type and `TTableName` stays bound to
130
+ * drizzle's own declaration. Both `PgBuildColumns` instantiations below restate
131
+ * exactly the arguments the call sites already infer — the tables are still
132
+ * built by `schema.table(…)`, and this only names what it returns.
133
+ *
134
+ * Identical behaviour under TypeScript 6.0.3 and the 7.x tsgo build; this is a
135
+ * printer limitation in both, not a version-specific bug.
136
+ */
137
+ export interface SubtypeTables<TColumns extends Record<string, ColumnBuilderBase>> {
138
+ stable: PgTableWithColumns<{
139
+ name: string;
140
+ schema: string;
141
+ columns: PgBuildColumns<string, SubtypeStableColumnBuilders>;
142
+ dialect: "pg";
143
+ isAlias: false;
144
+ }>;
145
+ version: PgTableWithColumns<{
146
+ name: `${string}_version`;
147
+ schema: string;
148
+ columns: PgBuildColumns<`${string}_version`, SubtypeVersionColumnBuilders<TColumns>>;
149
+ dialect: "pg";
150
+ isAlias: false;
151
+ }>;
152
+ }
153
+ /**
154
+ * Build a `defineSubtypeTables` bound to a discriminator hook. The hook turns a
155
+ * leaf's `discriminator` declaration into the stable-table constraints that
156
+ * enforce it (e.g. a `UNIQUE(id, type)` + composite `(id, type)` → parent FK +
157
+ * per-level CHECK trio); a hierarchy without a discriminator passes a hook that
158
+ * is simply never invoked (its leaves omit `discriminator`).
159
+ *
160
+ * The returned builder emits a regular subtype's `{ stable, version }` table
161
+ * pair with the composite-`(id, version)` FK chain to its parent branch.
162
+ */
163
+ export declare const makeSubtypeTables: <TDiscriminator>(applyDiscriminator: (level: string, stable: DiscriminatorColumns, discriminator: TDiscriminator) => PgTableExtraConfigValue[]) => <TColumns extends Record<string, ColumnBuilderBase>>(schema: PgSchema, { name, parentId, parentVersion, columns, versionDefault, extraConstraints, discriminator }: SubtypeTablesConfig<TColumns, TDiscriminator>) => SubtypeTables<TColumns>;
164
+ /** A version table participates in the chain via its `(id, version)` key. */
165
+ interface VersionTable {
166
+ id: AnyPgColumn;
167
+ version: AnyPgColumn;
168
+ }
169
+ /** A stable identity table is joined on `id` alone. */
170
+ interface StableTable {
171
+ id: AnyPgColumn;
172
+ }
173
+ /** The explicit, projection-shaped column selection a leaf authors. */
174
+ type ViewColumns = Record<string, AnyPgColumn | SQL | SQL.Aliased>;
175
+ /** Resolve one selected field to its inferred (nullability-aware) row type. */
176
+ type ViewField<TField> = TField extends AnyPgColumn ? GetColumnData<TField> : TField extends SQL<infer U> ? U : TField extends SQL.Aliased<infer U> ? U : never;
177
+ /** Inferred `$inferSelect` row for an explicit {@link ViewColumns} map. */
178
+ type ViewRow<TColumns extends ViewColumns> = { [K in keyof TColumns]: ViewField<TColumns[K]>; };
179
+ /**
180
+ * The view type a {@link makeCurrentVersionView} builder returns: a real Drizzle
181
+ * view (usable in `from` / `join` and exposing the column proxies for
182
+ * `eq(view.col, …)`) with `$inferSelect` pinned to the explicit column map plus
183
+ * the injected always-merged columns `TMerge`.
184
+ *
185
+ * The selection is built directly from `columns` + `merge`, so this never
186
+ * depends on the in-body select's nullability/table-name generics — which
187
+ * collapse to `{ [x: string]: never }` because the join chain is assembled
188
+ * untyped.
189
+ */
190
+ type CurrentVersionViewResult<TColumns extends ViewColumns, TMerge extends ViewColumns> = Omit<PgViewWithSelection<{
191
+ name: string;
192
+ schema: string;
193
+ existing: false;
194
+ selectedFields: TColumns & TMerge;
195
+ isAlias: false;
196
+ }>, "$inferSelect"> & {
197
+ $inferSelect: ViewRow<TColumns & TMerge>;
198
+ };
199
+ /**
200
+ * Declaration accepted by a {@link makeCurrentVersionView} builder.
201
+ *
202
+ * **The optional members below are NOT migrated to `Option`, and must not be** —
203
+ * for the same two reasons as {@link SubtypeTablesConfig}: the interface is
204
+ * handed on unchanged to a consuming application's database package, and each
205
+ * member is spread straight into the generated Drizzle join chain
206
+ * (`for (const table of through ?? [])`), where `undefined` means "no such
207
+ * join" and an `Option` would be a truthy object.
208
+ */
209
+ export interface CurrentVersionViewConfig<TColumns extends ViewColumns> {
210
+ /** Leaf version table the view resolves to its latest row. */
211
+ leaf: VersionTable;
212
+ /**
213
+ * Intermediate version tables joined on the composite `(id, version)` key,
214
+ * ordered leaf → branch. The root version table is always appended after these
215
+ * by the builder. A **required fact table** (a 6NF satellite keyed
216
+ * `(id, version)` whose row must exist at every version) also joins here —
217
+ * the INNER JOIN doubles as its fail-closed witness.
218
+ */
219
+ through?: VersionTable[] | undefined;
220
+ /**
221
+ * **Optional fact tables** LEFT JOINed at the current `(id, version)`: 6NF
222
+ * satellites whose row-absence at a version encodes NULL (the fact-scoped
223
+ * version storage decision). Selected columns sourced from these must be
224
+ * projected nullability-aware (an `sql<T | null>` aliased expression), since
225
+ * the LEFT JOIN widens a NOT NULL fact column to nullable in the row.
226
+ */
227
+ optional?: VersionTable[] | undefined;
228
+ /** Stable identity tables joined on `id` (explicit `undefined` admitted, as above). */
229
+ on?: StableTable[] | undefined;
230
+ /** Explicit column selection, including `id`, `action`, and `timestamp`. */
231
+ columns: TColumns;
232
+ }
233
+ /** Injected dependencies for a {@link makeCurrentVersionView} builder. */
234
+ export interface CurrentVersionViewDeps<TRoot extends PgTable & VersionTable, TMerge extends ViewColumns> {
235
+ /**
236
+ * The root version table currency resolves from — a `MAX(version)` sub-select
237
+ * groups it by `id`, and the leaf / intermediate version tables INNER JOIN at
238
+ * that current `(id, version)`. Also joined for the current row's own columns
239
+ * (e.g. an `action` soft-delete gate and a `timestamp`).
240
+ */
241
+ rootVersion: TRoot;
242
+ /** The always-joined identity row (joined on `id`). */
243
+ identity: StableTable;
244
+ /**
245
+ * Columns merged into every view's selection (sourced from `identity`), so no
246
+ * leaf needs to name them — e.g. a shared `slug`.
247
+ *
248
+ * Passing a **function** instead of a plain object additionally INNER JOINs a
249
+ * `version = 0` alias of `rootVersion` — the node's birth row — and hands it
250
+ * to the callback, so a hierarchy can merge in a true `createdAt` sourced
251
+ * from the first version's own timestamp, distinct from the current
252
+ * version's. The plain-object form skips that join entirely, leaving the
253
+ * emitted view definition byte-identical to before: this matters for a root
254
+ * version table whose current-version view is itself joined **as a view** by
255
+ * another hand-written view, where changing its definition would require
256
+ * drizzle-kit to DROP it — and drizzle-kit doesn't topologically order
257
+ * view-on-view DROPs, so it fails against the still-dependent view.
258
+ *
259
+ * That hazard is confined to a hierarchy's own hand-written views layered on
260
+ * top: the join chain this builder generates only ever reaches version and
261
+ * stable tables (`leaf`, `through`, `optional`, `on`, `identity`,
262
+ * `rootVersion`), never a {@link CurrentVersionViewResult} this module
263
+ * itself returns. No view built by `makeCurrentVersionView` may select from
264
+ * another view built by `makeCurrentVersionView` — that is a library-level
265
+ * rule, not just this comment's observation.
266
+ */
267
+ merge: TMerge | ((versionZero: TRoot) => TMerge);
268
+ }
269
+ /**
270
+ * Build a `currentVersionView` bound to a root version table + identity row.
271
+ * `schema.view(name).as(...)` with the uniform join chain generated from
272
+ * {@link CurrentVersionViewConfig}.
273
+ *
274
+ * The joins only widen the `FROM` clause; they never change the explicit
275
+ * `columns` selection, so the view's `$inferSelect` is derived directly from
276
+ * `columns` + the injected `merge` via {@link CurrentVersionViewResult}. The
277
+ * runtime query is assembled untyped (the chain alternates join shapes), which
278
+ * is why the return type is asserted rather than inferred: letting `.as()` infer
279
+ * the selection through `.from(current)` collapses every column to `never`.
280
+ *
281
+ * Currency is resolved from `rootVersion` (fail-closed): a `MAX(version)`
282
+ * sub-select drives the FROM, and the leaf / intermediate version tables INNER
283
+ * JOIN at that current `(id, version)`. One row per node (the sub-select groups
284
+ * by id) landing on unique `(id, version)` PKs, so no `DISTINCT ON` / `ORDER BY`
285
+ * is needed. A leaf missing at the current version drops out of the INNER JOIN —
286
+ * the view yields no row.
287
+ */
288
+ export declare const makeCurrentVersionView: <TRoot extends PgTable & VersionTable, TMerge extends ViewColumns>({ rootVersion, identity, merge }: CurrentVersionViewDeps<TRoot, TMerge>) => <TColumns extends ViewColumns>(schema: PgSchema, name: string, { leaf, through, optional, on, columns }: CurrentVersionViewConfig<TColumns>) => CurrentVersionViewResult<TColumns, TMerge>;
289
+ /**
290
+ * The actions a version-chain row records by default: a chain appends a
291
+ * `(version, action, timestamp)` row per edit rather than rewriting in place,
292
+ * and a delete is a tombstone version.
293
+ */
294
+ export type VersionAction = "create" | "update" | "delete";
295
+ /**
296
+ * One entry in a node's append-only version chain — what a version-history
297
+ * listing returns per row. `Action` is the chain's own action vocabulary
298
+ * (default {@link VersionAction}).
299
+ */
300
+ export interface VersionChainEntry<Action extends string = VersionAction> {
301
+ /** The version number (0 is the create). */
302
+ readonly version: bigint;
303
+ /** The edit action this version recorded. */
304
+ readonly action: Action;
305
+ /** When the version was written. */
306
+ readonly timestamp: Date;
307
+ }
308
+ /**
309
+ * One version's reconstructed state — what a browse / compare surface shows,
310
+ * and what a restore re-applies as a new version. Every leaf-chain domain
311
+ * column at `(id, version)`, keyed by column name (leaf-most level wins on a
312
+ * shared name), plus the root chain's `action` / `timestamp`. Generated
313
+ * columns (a search vector) are opaque and belong outside `columns`.
314
+ *
315
+ * `Extra` carries the branch-level facts a hierarchy adds to a snapshot (a
316
+ * content branch's visibility and draft flag, say); the library knows none of
317
+ * them.
318
+ */
319
+ export type VersionSnapshot<Action extends string = VersionAction, Extra extends object = Record<never, never>> = VersionChainEntry<Action> & Extra & {
320
+ /** Every leaf-chain domain column at this version, keyed by column name. */
321
+ readonly columns: Readonly<Record<string, unknown>>;
322
+ };
323
+ /** The `createdAt` / `updatedAt` pair {@link versionTimestamps} returns. */
324
+ export interface VersionTimestamps {
325
+ /** The birth row's timestamp, or `updatedAt` when the view merges none in. */
326
+ readonly createdAt: Date;
327
+ /** The current version's `timestamp`. */
328
+ readonly updatedAt: Date;
329
+ }
330
+ /**
331
+ * The `createdAt` / `updatedAt` pair of a current-version row: `updatedAt` is
332
+ * the current version's `timestamp`; `createdAt` is the birth row's own
333
+ * timestamp where the view merges one in (a function `merge` of
334
+ * {@link makeCurrentVersionView}), and falls back to `updatedAt` for a view
335
+ * that carries none (website #1223).
336
+ */
337
+ export declare const versionTimestamps: (row: {
338
+ readonly timestamp: Date;
339
+ readonly createdAt?: Date | null | undefined;
340
+ }) => VersionTimestamps;
341
+ /** A builder map a table's `columns` takes. */
342
+ type ColumnMap = Record<string, ColumnBuilderBase>;
343
+ /** `T` as a {@link ColumnMap}, so a mapped column type satisfies the table constraint. */
344
+ type AsColumns<T> = T extends ColumnMap ? T : never;
345
+ /** The metadata map member `P` of level `K` in tree metadata `M`; `{}` when absent. */
346
+ type MetaOf<M, K, P extends "level" | "leaf"> = K extends keyof M ? M[K] extends { readonly [Q in P]: infer X; } ? X : Record<never, never> : Record<never, never>;
347
+ /** The hand-authored columns the hooks `H` give level `K`; `{}` when none. */
348
+ type HookColumnsOf<H, K> = K extends keyof H ? H[K] extends {
349
+ readonly columns?: infer C;
350
+ } ? Exclude<C, undefined> extends ColumnMap ? Exclude<C, undefined> : Record<never, never> : Record<never, never> : Record<never, never>;
351
+ /** Whether an entry declares the `"fact-scoped"` level kind. */
352
+ type IsFactScoped<E> = E extends {
353
+ readonly kind?: infer Kind;
354
+ } ? [Exclude<Kind, undefined>] extends ["fact-scoped"] ? true : false : false;
355
+ /** Level `K`'s own derived columns: its level columns, then its leaf's. */
356
+ type OwnColumns<M, K, B extends ColumnBuilders> = DerivedColumns<MetaOf<M, K, "level">, B> & DerivedColumns<MetaOf<M, K, "leaf">, B>;
357
+ /**
358
+ * The columns level `K`'s version table carries beyond `(id, version)`: its own
359
+ * derived columns (a fact-scoped level keeps them in fact tables instead), then
360
+ * the hand-authored hook columns.
361
+ */
362
+ export type TreeVersionColumns<S, M, B extends ColumnBuilders, H, K extends keyof S> = AsColumns<(IsFactScoped<S[K]> extends true ? Record<never, never> : OwnColumns<M, K, B>) & HookColumnsOf<H, K>>;
363
+ /** A fact table's column builders: the `(id, version)` key plus one fact. */
364
+ type FactColumnBuilders<TColumns extends ColumnMap> = {
365
+ id: SetNotNull<PgUUIDBuilder>;
366
+ version: SetNotNull<PgBigInt64Builder>;
367
+ } & TColumns;
368
+ /**
369
+ * One fact table of a fact-scoped level: `(id, version)` keyed, carrying one
370
+ * column, foreign-keyed to the level's version spine. Written out for the
371
+ * declaration printer, as {@link SubtypeTables} is.
372
+ */
373
+ export type FactTable<TColumns extends ColumnMap> = PgTableWithColumns<{
374
+ name: string;
375
+ schema: string;
376
+ columns: PgBuildColumns<string, FactColumnBuilders<TColumns>>;
377
+ dialect: "pg";
378
+ isAlias: false;
379
+ }>;
380
+ /** Every level's `{ stable, version }` pair, keyed by level name. */
381
+ export type TreeLevelTables<S, M, B extends ColumnBuilders, H> = { readonly [K in keyof S & string]: SubtypeTables<TreeVersionColumns<S, M, B, H, K>>; };
382
+ /** Every fact-scoped level's fact tables, keyed by level then field; `{}` for a plain level. */
383
+ export type TreeFactTables<S, M, B extends ColumnBuilders> = { readonly [K in keyof S & string]: IsFactScoped<S[K]> extends true ? { readonly [F in keyof OwnColumns<M, K, B>]: FactTable<AsColumns<Pick<OwnColumns<M, K, B>, F>>>; } : Record<never, never>; };
384
+ /** The root identity table's column builders: `id`, the leaf `type`, then the injected extras. */
385
+ type RootIdentityColumnBuilders<IE extends ColumnMap> = {
386
+ id: SetIsPrimaryKey<PgUUIDBuilder>;
387
+ type: SetNotNull<PgTextBuilder<[string, ...string[]]>>;
388
+ } & IE;
389
+ /**
390
+ * The root version table's column builders: the `(id, version)` key, `action`
391
+ * (the chain's only currency and tombstone state), `timestamp`, the actor,
392
+ * then the injected extras.
393
+ */
394
+ type RootVersionColumnBuilders<VE extends ColumnMap> = {
395
+ id: SetNotNull<PgUUIDBuilder>;
396
+ version: SetNotNull<PgBigInt64Builder>;
397
+ action: SetNotNull<PgTextBuilder<[string, ...string[]]>>;
398
+ timestamp: SetHasDefault<SetNotNull<PgTimestampBuilder>>;
399
+ actor: PgTextBuilder<[string, ...string[]]>;
400
+ } & VE;
401
+ /** The root `{ identity, version }` pair. Written out for the declaration printer. */
402
+ export interface TreeRootTables<IE extends ColumnMap, VE extends ColumnMap> {
403
+ identity: PgTableWithColumns<{
404
+ name: string;
405
+ schema: string;
406
+ columns: PgBuildColumns<string, RootIdentityColumnBuilders<IE>>;
407
+ dialect: "pg";
408
+ isAlias: false;
409
+ }>;
410
+ version: PgTableWithColumns<{
411
+ name: string;
412
+ schema: string;
413
+ columns: PgBuildColumns<string, RootVersionColumnBuilders<VE>>;
414
+ dialect: "pg";
415
+ isAlias: false;
416
+ }>;
417
+ }
418
+ /** Everything {@link makeTreeTables} derives from a tree. */
419
+ export interface TreeTables<S, M, B extends ColumnBuilders, H, IE extends ColumnMap, VE extends ColumnMap> {
420
+ /** The root identity and version tables. */
421
+ readonly root: TreeRootTables<IE, VE>;
422
+ /** Every level's stable + version pair, keyed by level name. */
423
+ readonly levels: TreeLevelTables<S, M, B, H>;
424
+ /** Every fact-scoped level's fact tables, keyed by level then field. */
425
+ readonly facts: TreeFactTables<S, M, B>;
426
+ }
427
+ /**
428
+ * Per-level hooks for what a model can't carry. Every member is threaded into
429
+ * a Drizzle builder call, so each is `?: T | undefined` (see
430
+ * {@link SubtypeTablesConfig}).
431
+ */
432
+ export interface TreeLevelHooks {
433
+ /** Hand-authored version-table columns (an FK backing a relationship, a generated column). */
434
+ readonly columns?: ColumnMap | undefined;
435
+ /** Extra version-table constraints and indexes, after the generated key and FK. */
436
+ readonly extraConstraints?: ((table: Record<string, ExtraConfigColumn>) => PgTableExtraConfigValue[]) | undefined;
437
+ /**
438
+ * A refinable branch: the `(id, type)` arc of this level and of every level
439
+ * below it gets `ON UPDATE CASCADE`, so flipping the root's `type` from a
440
+ * bare value to a concrete leaf cascades down the existing chain (website's
441
+ * entity refinement).
442
+ */
443
+ readonly refinable?: boolean | undefined;
444
+ }
445
+ /** The root tables' names and injected columns. */
446
+ export interface TreeRootConfig<IE extends ColumnMap, VE extends ColumnMap> {
447
+ /** The identity table's name; `node` when absent. */
448
+ readonly identity?: string | undefined;
449
+ /** The root version table's name; `${identity}_version` when absent. */
450
+ readonly version?: string | undefined;
451
+ /** Extra identity columns (website's `slug`). */
452
+ readonly identityColumns?: IE | undefined;
453
+ /** Extra root-version columns (pulpit's `proposed_by`). */
454
+ readonly versionColumns?: VE | undefined;
455
+ /** The actor column's name; `actor` when absent. */
456
+ readonly actorColumn?: string | undefined;
457
+ }
458
+ /** The configuration {@link makeTreeTables} takes. */
459
+ export interface TreeTablesConfig<B extends ColumnBuilders, H, IE extends ColumnMap, VE extends ColumnMap> {
460
+ /** The schema every table is created in. */
461
+ readonly schema: PgSchema;
462
+ /** The builder registry the `dbColumn` kinds resolve against. */
463
+ readonly builders: B;
464
+ /** Resolves a `references` annotation to its target column (see `./derive`). */
465
+ readonly resolveReference?: DeriveOptions["resolveReference"];
466
+ /** The root tables' names and injected columns. */
467
+ readonly root?: TreeRootConfig<IE, VE> | undefined;
468
+ /** Per-level hooks, keyed by level name. */
469
+ readonly levels?: H | undefined;
470
+ }
471
+ /**
472
+ * Derive every table and constraint of a tree in one call: the root identity
473
+ * table (`id`, `type`, extras; `UNIQUE (id, type)`) and root version table
474
+ * (`(id, version)` key, `action` checked to `create` / `update` / `delete`,
475
+ * `timestamp`, the actor, extras); then per level a stable table (`id` →
476
+ * parent `id`, cascade delete; `type` NOT NULL with `UNIQUE (id, type)`,
477
+ * `(id, type)` → parent `(id, type)`, and a CHECK narrowing `type` to the
478
+ * level's subtree leaves) and a version table (`(id, version)` key and
479
+ * `(id, version)` → parent version FK) carrying the level's level columns,
480
+ * its leaf's columns (both derived through `builders`) and its hook columns.
481
+ * A fact-scoped level's version table is a spine, with one
482
+ * `${level}_${column}` fact table per own column, keyed and foreign-keyed on
483
+ * `(id, version)` (cascade delete).
484
+ *
485
+ * Constraint names are canonical (`${table}_pkey`, `${table}_${parent}_fk`,
486
+ * `${level}_id_type_unique` / `_fk` / `_type_valid`); no version table carries
487
+ * `action`. Column, constraint and CHECK value order follow declaration order,
488
+ * so the same tree always generates the same migration. Throws
489
+ * `DerivationError` at module load for a column kind `builders` lacks or an
490
+ * unresolvable reference.
491
+ *
492
+ * Every table is typed from the tree's column phantom. The one type-erasure
493
+ * seam is inside: the level and fact records are assembled from the runtime
494
+ * tree, then named by {@link TreeTables}.
495
+ *
496
+ * @example
497
+ * ```ts
498
+ * import { dbColumn, defineTree } from "@thomasfosterau/effect-drizzle-ext";
499
+ * import { makeTreeTables } from "@thomasfosterau/effect-drizzle-ext/versioned-chain";
500
+ * import { getTableConfig, integer, pgSchema, text } from "drizzle-orm/pg-core";
501
+ * import * as Schema from "effect/Schema";
502
+ *
503
+ * const tree = defineTree({
504
+ * work: {
505
+ * columns: { title: dbColumn(Schema.String, { column: "title", pg: "text", notNull: true }) },
506
+ * },
507
+ * book: {
508
+ * parent: "work",
509
+ * leaf: { pages: dbColumn(Schema.Finite, { column: "pages", pg: "integer", notNull: false }) },
510
+ * },
511
+ * });
512
+ *
513
+ * const tables = makeTreeTables(tree, {
514
+ * schema: pgSchema("catalogue"),
515
+ * builders: { text: (c: string) => text(c), integer: (c: string) => integer(c) },
516
+ * });
517
+ *
518
+ * console.log(getTableConfig(tables.levels.book.version).columns.map((c) => c.name));
519
+ * // ["id", "version", "pages"]
520
+ * ```
521
+ */
522
+ export declare const makeTreeTables: <S, M, B extends ColumnBuilders, const H extends { readonly [K in keyof S]?: TreeLevelHooks; } = Record<never, never>, IE extends ColumnMap = Record<never, never>, VE extends ColumnMap = Record<never, never>>(tree: Tree<S, M>, config: TreeTablesConfig<B, H, IE, VE>) => TreeTables<S, M, B, H, IE, VE>;
523
+ export {};
524
+ //# sourceMappingURL=versioned-chain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"versioned-chain.d.ts","names":[],"sources":["../src/versioned-chain.ts"],"mappings":";;;;;KAoFK,qBAAqB;EAAY,IAAI;EAAa,SAAS;;;iBAG/C;EACf,IAAI;EACJ,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;iBA2BS,oBACf,iBAAiB,eAAe,oBAChC;;EAGA;;;;;EAKA,UAAU;;;;;;EAMV,eAAe;;EAEf,SAAS;;;;;;EAMT;;;;;;EAMA,qBACM,OAAO,eAAe,uBAAuB;;;;;;;EAQnD,gBAAgB;;;;;;;;;;;;;;KAgBb;EACH,IAAI,gBAAgB;EACpB,MAAM;;;;;;;;;;;;;;KAeH,6BAA6B,iBAAiB,eAAe;EAChE,IAAI,WAAW;EACf,SAAS,WAAW;IAClB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiCa,cAAc,iBAAiB,eAAe;EAC7D,QAAQ;IACN;IACA;IACA,SAAS,uBAAuB;IAChC;IACA;;EAEF,SAAS;IACP;IACA;IACA,SAAS,oCAAoC,6BAA6B;IAC1E;IACA;;;;;;;;;;;;;qBAcS,oBACV,gBAAc,qBAEX,eACA,QAAQ,sBACR,eAAe,mBACZ,+BAEN,iBAAiB,eAAe,oBAAkB,QACzC,YAAQ,MAAA,UAAA,eAAA,SAAA,gBAAA,kBAAA,iBASb,oBAAoB,UAAU,oBAChC,cAAc;;UAkET;EACR,IAAI;EACJ,SAAS;;;UAID;EACR,IAAI;;;KAID,cAAc,eAAe,cAAc,MAAM,IAAI;;KAGrD,UAAU,UAAU,eAAe,cACpC,cAAc,UACd,eAAe,UAAU,KACvB,IACA,eAAe,IAAI,cAAc,KAC/B;;KAIH,QAAQ,iBAAiB,kBAAkB,WAAW,WAAW,UAAU,SAAS;;;;;;;;;;;;KAapF,yBAAyB,iBAAiB,aAAa,eAAe,eAAe,KACxF;EACE;EACA;EACA;EACA,gBAAgB,WAAW;EAC3B;;EAIF,cAAc,QAAQ,WAAW;;;;;;;;;;;;iBAclB,yBAAyB,iBAAiB;;EAEzD,MAAM;;;;;;;;EAQN,UAAU;;;;;;;;EAQV,WAAW;;EAEX,KAAK;;EAEL,SAAS;;;iBAKM,uBACf,cAAc,UAAU,cACxB,eAAe;;;;;;;EAQf,aAAa;;EAEb,UAAU;;;;;;;;;;;;;;;;;;;;;;;;EAwBV,OAAO,WAAW,aAAa,UAAU;;;;;;;;;;;;;;;;;;;;;qBA2B9B,yBACV,cAAc,UAAU,cAAc,eAAe,eAAW,aAAA,UAAA,SAI9D,uBAAuB,OAAO,aAChC,iBAAiB,aAAW,QACnB,UAAQ,gBACJ,MAAA,SAAA,UAAA,IAAA,WAC6C,yBAAyB,cACjF,yBAAyB,UAAU;;;;;;YAmE5B;;;;;;iBAOK,kBAAkB,wBAAwB;;WAEhD;;WAEA,QAAQ;;WAER,WAAW;;;;;;;;;;;;;YAcV,gBACV,wBAAwB,eACxB,uBAAuB,wBACrB,kBAAkB,UACpB;;WAGW,SAAS,SAAS;;;iBAId;;WAEN,WAAW;;WAEX,WAAW;;;;;;;;;qBAWT,oBAAiB;WACnB,WAAW;WACX,YAAY;MACnB;;KASC,YAAY,eAAe;;KAG3B,UAAU,KAAK,UAAU,YAAY;;KAGrC,OAAO,GAAG,GAAG,8BAA8B,gBAAgB,IAC5D,EAAE,uBAAuB,KAAK,UAAU,OACtC,IACA,uBACF;;KAGC,cAAc,GAAG,KAAK,gBAAgB,IACvC,EAAE;WAAsB,gBAAgB;IACtC,QAAQ,sBAAsB,YAC5B,QAAQ,gBACR,uBACF,uBACF;;KAGC,aAAa,KAAK;WAAqB,aAAa;KACpD,QAAQ;;KAMR,WAAW,GAAG,GAAG,UAAU,kBAAkB,eAAe,OAAO,GAAG,aAAa,KACtF,eAAe,OAAO,GAAG,YAAY;;;;;;YAO3B,mBAAmB,GAAG,GAAG,UAAU,gBAAgB,GAAG,gBAAgB,KAAK,WACpF,aAAa,EAAE,mBAAmB,uBAAuB,WAAW,GAAG,GAAG,MACzE,cAAc,GAAG;;KAIhB,mBAAmB,iBAAiB;EACvC,IAAI,WAAW;EACf,SAAS,WAAW;IAClB;;;;;;YAOQ,UAAU,iBAAiB,aAAa;EAClD;EACA;EACA,SAAS,uBAAuB,mBAAmB;EACnD;EACA;;;YAIU,gBAAgB,GAAG,GAAG,UAAU,gBAAgB,iBAChD,WAAW,aAAa,cAAc,mBAAmB,GAAG,GAAG,GAAG,GAAG;;YAIrE,eAAe,GAAG,GAAG,UAAU,8BAC/B,WAAW,aAAa,aAAa,EAAE,+BAEjC,WAAW,WAAW,GAAG,GAAG,KAAK,UACzC,UAAU,KAAK,WAAW,GAAG,GAAG,IAAI,UAGxC;;KAID,2BAA2B,WAAW;EACzC,IAAI,gBAAgB;EACpB,MAAM,WAAW;IACf;;;;;;KAOC,0BAA0B,WAAW;EACxC,IAAI,WAAW;EACf,SAAS,WAAW;EACpB,QAAQ,WAAW;EACnB,WAAW,cAAc,WAAW;EACpC,OAAO;IACL;;iBAGa,eAAe,WAAW,WAAW,WAAW;EAC/D,UAAU;IACR;IACA;IACA,SAAS,uBAAuB,2BAA2B;IAC3D;IACA;;EAEF,SAAS;IACP;IACA;IACA,SAAS,uBAAuB,0BAA0B;IAC1D;IACA;;;;iBAKa,WACf,GACA,GACA,UAAU,gBACV,GACA,WAAW,WACX,WAAW;;WAGF,MAAM,eAAe,IAAI;;WAEzB,QAAQ,gBAAgB,GAAG,GAAG,GAAG;;WAEjC,OAAO,eAAe,GAAG,GAAG;;;;;;;iBAStB;;WAEN,UAAU;;WAEV,qBACH,OAAO,eAAe,uBAAuB;;;;;;;WAQ1C;;;iBAIM,eAAe,WAAW,WAAW,WAAW;;WAEtD;;WAEA;;WAEA,kBAAkB;;WAElB,iBAAiB;;WAEjB;;;iBAIM,iBACf,UAAU,gBACV,GACA,WAAW,WACX,WAAW;;WAGF,QAAQ;;WAER,UAAU;;WAEV,mBAAmB;;WAEnB,OAAO,eAAe,IAAI;;WAE1B,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAmEP,iBACX,GACA,GACA,UAAU,sBACJ,sBAAsB,WAAW,KAAK,oBAAmB,sBAC/D,WAAW,YAAY,sBACvB,WAAW,YAAY,sBAAoB,MAErC,KAAK,GAAG,IAAE,QACR,iBAAiB,GAAG,GAAG,IAAI,QAClC,WAAW,GAAG,GAAG,GAAG,GAAG,IAAI"}