@thomasfosterau/effect-drizzle-ext 0.0.0 → 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 +102 -4
  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,189 @@
1
+ import { PgTransactionConfig } from "drizzle-orm/pg-core";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Context from "effect/Context";
4
+ import { SqlError } from "effect/sql/SqlError";
5
+ /**
6
+ * The five read-side members {@link ReadHandle} narrows a handle to, as a
7
+ * constraint.
8
+ *
9
+ * `Pick<T, K>` requires `K extends keyof T`, which an unconstrained type
10
+ * parameter cannot satisfy; this interface is what lets {@link ReadHandle}
11
+ * name the five keys structurally instead. Members are typed `unknown` on
12
+ * purpose — the constraint asserts only that a handle HAS a read surface,
13
+ * never what it looks like, so the driver keeps full control of the
14
+ * signatures a consumer sees.
15
+ */
16
+ export interface ReadSurface {
17
+ readonly select: unknown;
18
+ readonly selectDistinct: unknown;
19
+ readonly selectDistinctOn: unknown;
20
+ readonly with: unknown;
21
+ readonly $count: unknown;
22
+ }
23
+ /**
24
+ * The read surface shared by an Effect-native Drizzle database and its
25
+ * transaction: every way of starting a SELECT, and nothing that writes.
26
+ *
27
+ * A read may run either side of a transaction boundary, so the value
28
+ * {@link DatabaseServices.readHandle} yields has to be the part both handles
29
+ * agree on. Narrowing to it is a `Pick`, never a cast: both handles satisfy
30
+ * it structurally, and the driver's own signatures survive the narrowing.
31
+ */
32
+ export type ReadHandle<Db extends ReadSurface> = Pick<Db, "select" | "selectDistinct" | "selectDistinctOn" | "with" | "$count">;
33
+ /**
34
+ * The one method {@link DatabaseServices.transact} needs from a handle: open
35
+ * a transaction, run an Effect against it, commit on success and roll back on
36
+ * failure.
37
+ *
38
+ * Both the database and the transaction satisfy this — a transaction's own
39
+ * `transaction` opens a SAVEPOINT — which is what makes nesting expressible
40
+ * without a second type.
41
+ *
42
+ * `config` is optional here and absent from a transaction handle's own
43
+ * signature; a method that ignores it stays assignable, so one interface
44
+ * covers both.
45
+ */
46
+ export interface TransactionalDatabase<Tx> {
47
+ readonly transaction: <A, E, R>(f: (tx: Tx) => Effect.Effect<A, E, R>, config?: PgTransactionConfig) => Effect.Effect<A, E | SqlError, R>;
48
+ }
49
+ /**
50
+ * The services one application's database handles are reached through: the
51
+ * two service keys, the write boundary, and the read-side resolver.
52
+ *
53
+ * The identifier of each key is the **key string itself**, deliberately: an
54
+ * application writes `type Database = typeof services.Database.Identifier`
55
+ * and gets a requirement it can name in its own signatures, the way a
56
+ * class-style `Context.Service` gives the class. Two factories called with
57
+ * different keys therefore produce services that are distinct to the type
58
+ * checker as well as at runtime.
59
+ */
60
+ export interface DatabaseServices<Db, Tx extends ReadSurface, Key extends string> {
61
+ /**
62
+ * The canonical database service: the Effect-native Drizzle client, keyed
63
+ * by the string the factory was given. Queries yielded from it are Effects
64
+ * executed against whatever pool the layer providing it holds open.
65
+ */
66
+ readonly Database: Context.Service<Key, Db>;
67
+ /**
68
+ * The ambient transaction handle for the unit of work in flight.
69
+ *
70
+ * Providing it is {@link transact}'s job; an operation that needs the
71
+ * handle yields this key instead of taking a `tx` parameter, so "must run
72
+ * inside a transaction" lands in the `R` channel where the compiler checks
73
+ * it rather than in a parameter a caller can satisfy with the wrong handle.
74
+ *
75
+ * The payload is NOT interchangeable with {@link Database}. A transaction
76
+ * handle is the CONCRETE transaction the driver passed to the callback,
77
+ * whereas the `Database` payload can be a proxy the consuming application
78
+ * wrapped around its client (website #593 depends on that difference for
79
+ * subquery recursion). Read this key as "the open transaction", not as
80
+ * "permission to write".
81
+ *
82
+ * The key string is the `Database` key plus `/CurrentTransaction`, so the
83
+ * pair is legible in a context dump and two applications composed into one
84
+ * process never collide.
85
+ */
86
+ readonly CurrentTransaction: Context.Service<`${Key}/CurrentTransaction`, Tx>;
87
+ /**
88
+ * Runs `body` inside a database transaction, providing the handle as
89
+ * {@link CurrentTransaction}.
90
+ *
91
+ * Replaces the hand-rolled `db.transaction((tx) => …)` boundary at every
92
+ * call site: the handle is yielded here, the driver's callback parameter is
93
+ * discharged into the context, and `CurrentTransaction` leaves the
94
+ * requirement channel while everything else in it is preserved.
95
+ *
96
+ * **Always-open, never join-if-present.** Calling `transact` while a
97
+ * transaction is already open deliberately opens a NESTED one (a SAVEPOINT
98
+ * on the same connection), exactly as the hand-rolled `db.transaction` it
99
+ * replaces did. A "join the ambient transaction if there is one" variant is
100
+ * easy to write and is the wrong call here: it would silently delete every
101
+ * SAVEPOINT the calling code currently takes, changing rollback granularity
102
+ * without changing a line of that code. If you want to reuse the open
103
+ * transaction, yield {@link CurrentTransaction} directly and do not open
104
+ * another. Adjudicated on website #1883 (owner, 2026-09-07, condition 3) —
105
+ * please do not "improve" this into a join-if-present helper.
106
+ *
107
+ * The nested boundary is opened on the ambient handle
108
+ * (`tx.transaction(…)`), not on the client. Some drivers pin the open
109
+ * connection to the fiber and would nest either way; a driver that does not
110
+ * would hand `db.transaction` a SECOND pooled connection, making the
111
+ * "nested" transaction an independent one that cannot see its parent's
112
+ * uncommitted writes and does not roll back with it. Going through the
113
+ * ambient handle is a SAVEPOINT on every driver.
114
+ */
115
+ readonly transact: <A, E, R>(body: Effect.Effect<A, E, R>) => Effect.Effect<A, E | SqlError, Exclude<R, `${Key}/CurrentTransaction`> | Key>;
116
+ /**
117
+ * The handle a **read** should use: the ambient {@link CurrentTransaction}
118
+ * when one is open, the {@link Database} client otherwise.
119
+ *
120
+ * Reads sit either side of a boundary. A snapshot reconstruction runs with
121
+ * no transaction in sight and also runs inside a restore's; a
122
+ * carry-forward read happens once on a plain GET and again inside the
123
+ * update. Threading a handle through those is what keeps a
124
+ * `Pick<Tx, "select">` parameter alive after the services have removed
125
+ * every other one — functions carrying a parameter purely because they are
126
+ * honest about being callable from both sides.
127
+ *
128
+ * Preferring the transaction is also the safer default, not merely the
129
+ * convenient one. A read issued against `Database` from inside an open
130
+ * transaction takes its own pooled connection, so it is not guaranteed to
131
+ * see that transaction's uncommitted writes — a carry-forward read that
132
+ * missed the row the same unit of work had just written would be a silent
133
+ * wrong-answer bug. Reading through the open transaction removes the
134
+ * question. (A test harness that pins the pool to one connection cannot
135
+ * demonstrate the difference; the reasoning is about the pooled production
136
+ * path.)
137
+ *
138
+ * This is deliberately **not** what {@link transact} does for writes.
139
+ * There, joining an ambient transaction would delete a SAVEPOINT and change
140
+ * rollback granularity, which is why it is always-open (website #1883,
141
+ * owner, 2026-09-07). A read opens nothing, so there is no boundary to lose
142
+ * — the asymmetry is the point, not an inconsistency.
143
+ *
144
+ * Not for a caller that needs a handle which is concretely NOT the
145
+ * consuming application's proxy (website #593). This falls back to exactly
146
+ * that proxy when no transaction is open, so such a caller must keep
147
+ * opening one of its own.
148
+ *
149
+ * The requirement channel says {@link Database} either way:
150
+ * `Effect.serviceOption` does not surface `CurrentTransaction` in `R`, so
151
+ * the preference is invisible to the type checker and is pinned by a test
152
+ * instead.
153
+ */
154
+ readonly readHandle: Effect.Effect<ReadHandle<Tx>, never, Key>;
155
+ }
156
+ /**
157
+ * Builds one application's {@link DatabaseServices} around its own database
158
+ * and transaction types.
159
+ *
160
+ * Curried in two stages because TypeScript has no partial type-argument
161
+ * inference: the first call fixes the driver's handle types (which a consumer
162
+ * always writes out — they carry its relations graph), the second infers the
163
+ * key string literal from the argument. Call it once per database, at module
164
+ * scope, and export what it returns; calling it twice with the same key
165
+ * produces services that share a runtime slot.
166
+ *
167
+ * @example
168
+ * ```ts
169
+ * import { makeDatabaseServices } from "@thomasfosterau/effect-drizzle-ext/database";
170
+ * // Any driver's handle types fit; these are the `@effect/sql-pg` binding's.
171
+ * import type { PgDatabaseClient, PgTransaction } from "@thomasfosterau/effect-drizzle-ext-pg";
172
+ *
173
+ * const relations = {}; // the application's drizzle relations graph
174
+ * type AppDatabase = PgDatabaseClient<typeof relations>;
175
+ * type AppTransaction = PgTransaction<typeof relations>;
176
+ *
177
+ * const services = makeDatabaseServices<AppDatabase, AppTransaction>()({
178
+ * key: "Database",
179
+ * });
180
+ * export const { Database, CurrentTransaction, transact, readHandle } = services;
181
+ * export type Database = typeof services.Database.Identifier; // "Database"
182
+ *
183
+ * console.log(Database.key); // "Database"
184
+ * ```
185
+ */
186
+ export declare const makeDatabaseServices: <Db extends TransactionalDatabase<Tx> & ReadHandle<Tx>, Tx extends ReadSurface & TransactionalDatabase<Tx>>() => <const Key extends string>(options: {
187
+ readonly key: Key;
188
+ }) => DatabaseServices<Db, Tx, Key>;
189
+ //# sourceMappingURL=database.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.d.ts","names":[],"sources":["../src/database.ts"],"mappings":";;;;;;;;;;;;;;;iBAiDiB;WACN;WACA;WACA;WACA;WACA;;;;;;;;;;;YAYC,WAAW,WAAW,eAAe,KAC/C;;;;;;;;;;;;;;iBAiBe,sBAAsB;WAC5B,cAAc,GAAG,GAAG,GAC3B,IAAI,IAAI,OAAO,OAAO,OAAO,GAAG,GAAG,IACnC,SAAS,wBACN,OAAO,OAAO,GAAG,IAAI,UAAU;;;;;;;;;;;;;iBAcrB,iBAAiB,IAAI,WAAW,aAAa;;;;;;WAMnD,UAAU,QAAQ,QAAQ,KAAK;;;;;;;;;;;;;;;;;;;;WAqB/B,oBAAoB,QAAQ,WAAW,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WA8BjE,WAAW,GAAG,GAAG,GACxB,MAAM,OAAO,OAAO,GAAG,GAAG,OACvB,OAAO,OAAO,GAAG,IAAI,UAAU,QAAQ,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAwCrE,YAAY,OAAO,OAAO,WAAW,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAiC/C,uBAET,WAAW,sBAAsB,MAAM,WAAW,KAClD,WAAW,cAAc,sBAAsB,iBAE1C,oBAAkB;WAAsB,KAAK;MAAQ,iBAAiB,IAAI,IAAI"}
@@ -0,0 +1,51 @@
1
+ import * as Option from "effect/Option";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Context from "effect/Context";
4
+ // src/database.ts
5
+ /**
6
+ * Builds one application's {@link DatabaseServices} around its own database
7
+ * and transaction types.
8
+ *
9
+ * Curried in two stages because TypeScript has no partial type-argument
10
+ * inference: the first call fixes the driver's handle types (which a consumer
11
+ * always writes out — they carry its relations graph), the second infers the
12
+ * key string literal from the argument. Call it once per database, at module
13
+ * scope, and export what it returns; calling it twice with the same key
14
+ * produces services that share a runtime slot.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { makeDatabaseServices } from "@thomasfosterau/effect-drizzle-ext/database";
19
+ * // Any driver's handle types fit; these are the `@effect/sql-pg` binding's.
20
+ * import type { PgDatabaseClient, PgTransaction } from "@thomasfosterau/effect-drizzle-ext-pg";
21
+ *
22
+ * const relations = {}; // the application's drizzle relations graph
23
+ * type AppDatabase = PgDatabaseClient<typeof relations>;
24
+ * type AppTransaction = PgTransaction<typeof relations>;
25
+ *
26
+ * const services = makeDatabaseServices<AppDatabase, AppTransaction>()({
27
+ * key: "Database",
28
+ * });
29
+ * export const { Database, CurrentTransaction, transact, readHandle } = services;
30
+ * export type Database = typeof services.Database.Identifier; // "Database"
31
+ *
32
+ * console.log(Database.key); // "Database"
33
+ * ```
34
+ */
35
+ const makeDatabaseServices = () => (options) => {
36
+ const Database = Context.Service(options.key);
37
+ const CurrentTransaction = Context.Service(`${options.key}/CurrentTransaction`);
38
+ /** Widens either handle to the read surface without a cast — both satisfy it structurally. */
39
+ const asReadHandle = (handle) => handle;
40
+ /** Opens the body's boundary on `handle`, discharging the new transaction into the context. */
41
+ const openOn = (handle, body) => handle.transaction((tx) => Effect.provideService(body, CurrentTransaction, tx));
42
+ return {
43
+ Database,
44
+ CurrentTransaction,
45
+ transact: (body) => Effect.flatMap(Effect.serviceOption(CurrentTransaction), (open) => Option.isSome(open) ? openOn(open.value, body) : Effect.flatMap(Database, (db) => openOn(db, body))),
46
+ readHandle: Effect.flatMap(Effect.serviceOption(CurrentTransaction), (open) => Option.isSome(open) ? Effect.succeed(asReadHandle(open.value)) : Effect.map(Database, asReadHandle))
47
+ };
48
+ };
49
+ export { makeDatabaseServices };
50
+
51
+ //# sourceMappingURL=database.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.js","names":[],"sources":["../src/database.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0OA,MAAa,8BAKgB,YAAkE;CAC3F,MAAM,WAAW,QAAQ,QAAiB,QAAQ,GAAG;CACrD,MAAM,qBAAqB,QAAQ,QACjC,GAAG,QAAQ,IAAI,oBACjB;;CAGA,MAAM,gBAAgB,WAA2C;;CAGjE,MAAM,UACJ,QACA,SAEA,OAAO,aAAa,OAAO,OAAO,eAAe,MAAM,oBAAoB,EAAE,CAAC;CAEhF,OAAO;EACL;EACA;EACA,WACE,SAEA,OAAO,QAAQ,OAAO,cAAc,kBAAkB,IAAI,SACxD,OAAO,OAAO,IAAI,IACd,OAAO,KAAK,OAAO,IAAI,IACvB,OAAO,QAAQ,WAAW,OAAO,OAAO,IAAI,IAAI,CAAC,CACvD;EACF,YAAY,OAAO,QAAQ,OAAO,cAAc,kBAAkB,IAAI,SACpE,OAAO,OAAO,IAAI,IACd,OAAO,QAAQ,aAAa,KAAK,KAAK,CAAC,IACvC,OAAO,IAAI,UAAU,YAAY,CACvC;CACF;AACF"}
@@ -0,0 +1,97 @@
1
+ import { L as DerivationError, i as DbColumn, l as FieldColumnsMetaOf, n as ColumnBounds, o as DbColumnReference } from "./index-CFhgcF4N.js";
2
+ import * as Schema from "effect/Schema";
3
+ import { ColumnBuilderBase } from "drizzle-orm";
4
+ import { AnyPgColumn, ExtraConfigColumn, PgTableExtraConfigValue } from "drizzle-orm/pg-core";
5
+ /**
6
+ * A registry mapping column kinds to Drizzle column builders — one builder per
7
+ * kind, taking the column name and returning a fresh (nullable) builder. The
8
+ * consumer supplies this; the package provides no fixed set.
9
+ */
10
+ export type ColumnBuilders<Kind extends string = string> = Record<Kind, (column: string) => ColumnBuilderBase>;
11
+ /** The (nullable) builder a kind produces in registry `B`. */
12
+ type Builder<B extends ColumnBuilders, P extends keyof B> = ReturnType<B[P]>;
13
+ /** The `.notNull()` form of a builder. */
14
+ type NotNull<X> = X extends {
15
+ notNull(): infer R;
16
+ } ? R : X;
17
+ /**
18
+ * The Drizzle column builder a single db-column annotation produces against
19
+ * registry `B`. Total over any `M` (a widened `unknown` metadata falls back to
20
+ * the base builder type), computing the precise builder type for a concrete
21
+ * `{ pg, notNull }` metadata.
22
+ *
23
+ * Exported so a projection-specific derivation (the `./jsonapi/derive` entry)
24
+ * can name this type without restating it.
25
+ */
26
+ export type BuilderFor<M, B extends ColumnBuilders> = M extends {
27
+ readonly pg: infer P;
28
+ readonly notNull: infer N;
29
+ } ? P extends keyof B ? N extends true ? NotNull<Builder<B, P>> : Builder<B, P> : ColumnBuilderBase : ColumnBuilderBase;
30
+ /**
31
+ * The Drizzle column builders a db-column metadata map produces against
32
+ * registry `B`, keyed and typed per entry — the type-level counterpart of the
33
+ * builder loop {@link makeDeriveColumns} returns.
34
+ */
35
+ export type DerivedColumns<Meta, B extends ColumnBuilders> = { readonly [K in keyof Meta]: BuilderFor<Meta[K], B>; };
36
+ /**
37
+ * Optional hooks for {@link makeDeriveColumns}. `resolveReference` turns a
38
+ * column's {@link DbColumnReference} name pair into the Drizzle column thunk
39
+ * `.references()` needs — the seam that lets FK-backed attributes derive their
40
+ * column instead of being hand-authored. Without it, a `references` annotation
41
+ * is a configuration error (throws), so a consumer only wires it when it
42
+ * declares FK columns. The `./references` entry ships one implementation.
43
+ */
44
+ export interface DeriveOptions {
45
+ readonly resolveReference?: ((reference: DbColumnReference) => () => AnyPgColumn) | undefined;
46
+ }
47
+ /**
48
+ * The derivation pair {@link makeDeriveColumns} returns for builder registry
49
+ * `B`: `fromMeta` over an already-projected metadata map, `fromFields` over a
50
+ * `Schema.Struct`'s fields. Both throw {@link DerivationError} at module load
51
+ * on a misdeclared column.
52
+ */
53
+ export interface DeriveColumns<B extends ColumnBuilders> {
54
+ /** Derive the builders an already-projected metadata map declares. */
55
+ readonly fromMeta: <Meta extends Record<string, DbColumn>>(metas: Meta) => DerivedColumns<Meta, B>;
56
+ /** Derive the builders a `Schema.Struct`'s annotated fields declare. */
57
+ readonly fromFields: <F extends Schema.Struct.Fields>(fields: F) => DerivedColumns<FieldColumnsMetaOf<F>, B>;
58
+ }
59
+ /**
60
+ * Build a derivation bound to a specific builder registry. The returned pair
61
+ * interprets per-column `dbColumn` metadata into the Drizzle column builders a
62
+ * table factory's `columns` expects:
63
+ *
64
+ * - `fromFields` takes a `Schema.Struct`'s fields directly (peeling the
65
+ * `optional` / `NullOr` wrappers via {@link fieldColumns});
66
+ * - `fromMeta` takes an already-projected metadata map, for a projection this
67
+ * entry knows nothing about (the `./jsonapi/derive` entry is one).
68
+ */
69
+ export declare const makeDeriveColumns: <B extends ColumnBuilders>(builders: B, options?: DeriveOptions) => DeriveColumns<B>;
70
+ /**
71
+ * Derive the SQL `CHECK` constraints an already-projected bounds map implies —
72
+ * the third leg of annotation-driven columns, after the column itself and the
73
+ * read-view selection. Emits one Drizzle `check()` per bounded column, named
74
+ * `${tableName}_${column}_${suffix}` (see {@link boundSuffix} for the canonical
75
+ * suffixes) unless the column annotation pins a `checkName`. A nullable
76
+ * column's expression is guarded with `col IS NULL OR …`, matching the
77
+ * hand-authored precedents.
78
+ *
79
+ * Returns a callback shaped for a table factory's `extraConstraints` seam: it
80
+ * receives the table's columns keyed by **attribute** name (not SQL column
81
+ * name). A bounded attribute whose column is absent from the table is a
82
+ * configuration error ({@link DerivationError}) — bounds on a column the table
83
+ * doesn't carry cannot be enforced.
84
+ */
85
+ export declare const deriveCheckConstraintsFrom: (bounds: Record<string, ColumnBounds>, tableName: string) => (table: Record<string, ExtraConfigColumn>) => PgTableExtraConfigValue[];
86
+ /**
87
+ * {@link deriveCheckConstraintsFrom} over a `Schema.Struct`'s fields: the
88
+ * bounds a model's `Schema.isGreaterThanOrEqualTo` / `isBetween` checks declare
89
+ * ({@link fieldColumnBounds}) become the table's CHECK constraints. One
90
+ * declaration drives wire validation and the database constraint.
91
+ *
92
+ * The projection is deferred to the returned callback, so this composes inside
93
+ * a `pgTable(...)` call whose fields are still being assembled.
94
+ */
95
+ export declare const deriveFieldCheckConstraints: (fields: Schema.Struct.Fields, tableName: string) => (table: Record<string, ExtraConfigColumn>) => PgTableExtraConfigValue[];
96
+ export { DerivationError };
97
+ //# sourceMappingURL=derive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derive.d.ts","names":[],"sources":["../src/derive.ts"],"mappings":";;;;;;;;;YAmEY,eAAe,gCAAgC,OACzD,OACC,mBAAmB;;KAIjB,QAAQ,UAAU,gBAAgB,gBAAgB,KAAK,WAAW,EAAE;;KAGpE,QAAQ,KAAK;EAAY,iBAAiB;IAAM,IAAI;;;;;;;;;;YAW7C,WAAW,GAAG,UAAU,kBAAkB;WAC3C,UAAU;WACV,eAAe;IAEtB,gBAAgB,IACd,iBACE,QAAQ,QAAQ,GAAG,MACnB,QAAQ,GAAG,KACb,oBACF;;;;;;YAOQ,eAAe,MAAM,UAAU,8BAC/B,WAAW,OAAO,WAAW,KAAK,IAAI;;;;;;;;;iBA0BjC;WACN,qBAAqB,WAAW,4BAA4B;;;;;;;;iBA0BtD,cAAc,UAAU;;WAE9B,WAAW,aAAa,eAAe,WAC9C,OAAO,SACJ,eAAe,MAAM;;WAEjB,aAAa,UAAU,OAAO,OAAO,QAC5C,QAAQ,MACL,eAAe,mBAAmB,IAAI;;;;;;;;;;;;qBAahC,oBAAqB,UAAU,gBAAc,UAC9C,GAAC,UACF,kBACR,cAAc;;;;;;;;;;;;;;;;qBAwGJ,6BAA0B,QAC5B,eAAe,eAAa,uBAAmB,OAChD,eAAe,uBAAqB;;;;;;;;;;qBA6BjC,8BAA2B,QAC7B,OAAO,OAAO,QAAM,uBAAmB,OACxC,eAAe,uBAAqB"}
package/dist/derive.js ADDED
@@ -0,0 +1,142 @@
1
+ import { c as fieldColumnBounds, h as DerivationError, l as fieldColumns } from "./src-CBeuhy5b.js";
2
+ import * as Arr from "effect/Array";
3
+ import * as R from "effect/Record";
4
+ import "effect/Schema";
5
+ import { sql } from "drizzle-orm";
6
+ import { check } from "drizzle-orm/pg-core";
7
+ // src/derive.ts
8
+ /**
9
+ * @copyright Thomas Foster
10
+ *
11
+ * **Effect Schema → Drizzle derivation: the derivation entry (Drizzle).**
12
+ *
13
+ * Reads the {@link DbColumn} metadata a schema declaration carries (via
14
+ * {@link fieldColumns} from the {@link ./index.ts} annotation entry, or via any
15
+ * other projection that yields a `Record<string, DbColumn>`) and emits the
16
+ * matching Drizzle column builders. Depends on Drizzle, so a model / client
17
+ * layer importing only the annotation entry never pulls the ORM.
18
+ *
19
+ * **Extensible column kinds.** The kind → builder mapping is *injected*, not
20
+ * baked in: a consumer passes a builder registry (a record from its own kind
21
+ * union to a Drizzle column builder) to {@link makeDeriveColumns}, and the
22
+ * derivation preserves each column's precise builder type — so the derived
23
+ * column map keeps `$inferSelect` typed — by reading the type-level phantom
24
+ * `dbColumn` stamps alongside the runtime annotation.
25
+ *
26
+ * **Two entry shapes, one interpreter.** {@link makeDeriveColumns} returns both
27
+ * `fromFields` (a `Schema.Struct`'s fields, the generic source) and `fromMeta`
28
+ * (an already-projected metadata map, the seam the `./jsonapi/derive` entry and
29
+ * any other projection build on). They share one builder loop.
30
+ *
31
+ * **Failures here are THROWN, not Effect failures.** Everything in this module
32
+ * runs at *module load*, while a consumer's table modules are being evaluated —
33
+ * before any Effect runtime exists to run a failure in, and before any query
34
+ * can be issued. A misdeclared table must fail at `import`, not at first
35
+ * insert, so a missing builder / a missing reference hook / bounds on a column
36
+ * the table does not carry throw {@link DerivationError}. Runtime query
37
+ * failures are the opposite and are never thrown: they travel in the Effect
38
+ * error channel as the tagged errors of the `./errors` entry.
39
+ *
40
+ * @module derive
41
+ */
42
+ /**
43
+ * Build a derivation bound to a specific builder registry. The returned pair
44
+ * interprets per-column `dbColumn` metadata into the Drizzle column builders a
45
+ * table factory's `columns` expects:
46
+ *
47
+ * - `fromFields` takes a `Schema.Struct`'s fields directly (peeling the
48
+ * `optional` / `NullOr` wrappers via {@link fieldColumns});
49
+ * - `fromMeta` takes an already-projected metadata map, for a projection this
50
+ * entry knows nothing about (the `./jsonapi/derive` entry is one).
51
+ */
52
+ const makeDeriveColumns = (builders, options = {}) => {
53
+ const buildColumn = (meta) => {
54
+ const build = builders[meta.pg];
55
+ if (build === void 0) throw new DerivationError({ message: `no builder registered for column kind '${meta.pg}'` });
56
+ let column = build(meta.column);
57
+ if (meta.notNull) column = column.notNull();
58
+ if (meta.default !== void 0) column = column.default(meta.default);
59
+ if (meta.references !== void 0) {
60
+ if (options.resolveReference === void 0) throw new DerivationError({ message: `column '${meta.column}' declares a reference but no resolveReference hook was provided` });
61
+ const target = options.resolveReference(meta.references);
62
+ column = column.references(target, {
63
+ onDelete: meta.references.onDelete,
64
+ onUpdate: meta.references.onUpdate
65
+ });
66
+ }
67
+ return column;
68
+ };
69
+ const buildColumns = (metas) => R.fromEntries(Arr.map(R.toEntries(metas), ([name, meta]) => [name, buildColumn(meta)]));
70
+ return {
71
+ fromMeta: (metas) => buildColumns(metas),
72
+ fromFields: (fields) => buildColumns(fieldColumns(fields))
73
+ };
74
+ };
75
+ /** A number rendered inline into the CHECK expression (never a bind parameter). */
76
+ const literal = (value) => sql.raw(String(value));
77
+ /**
78
+ * The canonical constraint-name suffix a set of bounds earns: `range` when
79
+ * bounded on both sides; `non_negative` for the sole inclusive-minimum-of-`0`
80
+ * case (the established naming for count/duration columns); `min` / `max` for
81
+ * the remaining one-sided bounds.
82
+ */
83
+ const boundSuffix = (bounds) => {
84
+ const hasMin = bounds.minimum !== void 0 || bounds.exclusiveMinimum !== void 0;
85
+ const hasMax = bounds.maximum !== void 0 || bounds.exclusiveMaximum !== void 0;
86
+ if (hasMin && hasMax) return "range";
87
+ if (!hasMin) return "max";
88
+ return bounds.minimum === 0 ? "non_negative" : "min";
89
+ };
90
+ /** The per-side comparisons for a bound set (exclusive bounds win their side). */
91
+ const comparisonParts = (column, bounds) => {
92
+ const { minimum, maximum, exclusiveMinimum, exclusiveMaximum } = bounds;
93
+ const parts = [];
94
+ if (exclusiveMinimum !== void 0) parts.push(sql`${column} > ${literal(exclusiveMinimum)}`);
95
+ else if (minimum !== void 0) parts.push(sql`${column} >= ${literal(minimum)}`);
96
+ if (exclusiveMaximum !== void 0) parts.push(sql`${column} < ${literal(exclusiveMaximum)}`);
97
+ else if (maximum !== void 0) parts.push(sql`${column} <= ${literal(maximum)}`);
98
+ return parts;
99
+ };
100
+ /**
101
+ * The bound expression for one column (without the nullability guard): a
102
+ * both-sides-inclusive bound renders as `col BETWEEN min AND max` — matching
103
+ * the hand-authored year-range constraints in the consuming applications — and
104
+ * anything else as the {@link comparisonParts} joined with `AND`.
105
+ */
106
+ const boundExpression = (column, bounds) => bounds.minimum !== void 0 && bounds.maximum !== void 0 ? sql`${column} BETWEEN ${literal(bounds.minimum)} AND ${literal(bounds.maximum)}` : sql.join(comparisonParts(column, bounds), sql` AND `);
107
+ /**
108
+ * Derive the SQL `CHECK` constraints an already-projected bounds map implies —
109
+ * the third leg of annotation-driven columns, after the column itself and the
110
+ * read-view selection. Emits one Drizzle `check()` per bounded column, named
111
+ * `${tableName}_${column}_${suffix}` (see {@link boundSuffix} for the canonical
112
+ * suffixes) unless the column annotation pins a `checkName`. A nullable
113
+ * column's expression is guarded with `col IS NULL OR …`, matching the
114
+ * hand-authored precedents.
115
+ *
116
+ * Returns a callback shaped for a table factory's `extraConstraints` seam: it
117
+ * receives the table's columns keyed by **attribute** name (not SQL column
118
+ * name). A bounded attribute whose column is absent from the table is a
119
+ * configuration error ({@link DerivationError}) — bounds on a column the table
120
+ * doesn't carry cannot be enforced.
121
+ */
122
+ const deriveCheckConstraintsFrom = (bounds, tableName) => (table) => Arr.map(R.toEntries(bounds), ([attribute, columnBounds]) => {
123
+ const column = table[attribute];
124
+ if (column === void 0) throw new DerivationError({ message: `attribute '${attribute}' declares numeric bounds but table '${tableName}' has no column for it` });
125
+ const expression = boundExpression(column, columnBounds);
126
+ const guarded = columnBounds.column.notNull ? expression : sql`${column} IS NULL OR ${expression}`;
127
+ const name = columnBounds.column.checkName ?? `${tableName}_${columnBounds.column.column}_${boundSuffix(columnBounds)}`;
128
+ return check(name, guarded);
129
+ });
130
+ /**
131
+ * {@link deriveCheckConstraintsFrom} over a `Schema.Struct`'s fields: the
132
+ * bounds a model's `Schema.isGreaterThanOrEqualTo` / `isBetween` checks declare
133
+ * ({@link fieldColumnBounds}) become the table's CHECK constraints. One
134
+ * declaration drives wire validation and the database constraint.
135
+ *
136
+ * The projection is deferred to the returned callback, so this composes inside
137
+ * a `pgTable(...)` call whose fields are still being assembled.
138
+ */
139
+ const deriveFieldCheckConstraints = (fields, tableName) => (table) => deriveCheckConstraintsFrom(fieldColumnBounds(fields), tableName)(table);
140
+ export { DerivationError, deriveCheckConstraintsFrom, deriveFieldCheckConstraints, makeDeriveColumns };
141
+
142
+ //# sourceMappingURL=derive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derive.js","names":[],"sources":["../src/derive.ts"],"mappings":"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"}
@@ -0,0 +1,157 @@
1
+ import * as Option from "effect/Option";
2
+ import * as Schema from "effect/Schema";
3
+ import * as Effect from "effect/Effect";
4
+ /**
5
+ * The fields of a Postgres error response this library reads, as recovered from
6
+ * wherever the driver's error sits in an error graph. `code` is the SQLSTATE and
7
+ * is always present (finding it is what identifies the node); every other
8
+ * fact is `Option` because Postgres populates it per error class — a `23502`
9
+ * carries `column` and `table` but no `constraint`, a `23503` carries
10
+ * `constraint`, `table` and `detail`.
11
+ */
12
+ export interface PgErrorFacts {
13
+ /** The five-character SQLSTATE, e.g. `23505`. */
14
+ readonly code: string;
15
+ /** The driver's message, or `""` when the node carries none. */
16
+ readonly message: string;
17
+ /** The violated constraint's name, for the constraint classes that name one. */
18
+ readonly constraint: Option.Option<string>;
19
+ /** The table the statement touched. */
20
+ readonly table: Option.Option<string>;
21
+ /** The column at fault, populated for NOT NULL violations. */
22
+ readonly column: Option.Option<string>;
23
+ /** Postgres' `DETAIL` line, which names the conflicting key values. */
24
+ readonly detail: Option.Option<string>;
25
+ /** The schema the table lives in. */
26
+ readonly schema: Option.Option<string>;
27
+ }
28
+ /**
29
+ * The driver's Postgres error facts, recovered from anywhere in `error`'s graph:
30
+ * a bare Postgres error, a bare `SqlError` (whose `reason` holds the driver
31
+ * error as its `cause`), or drizzle's `EffectDrizzleQueryError` wrapping an
32
+ * Effect `Cause` that holds the `SqlError`. `Option.none()` when the graph
33
+ * holds no node declaring a SQLSTATE, including for a non-object input.
34
+ *
35
+ * The walk is bounded (depth 8) and cycle-guarded, so a self-referential or
36
+ * pathologically deep graph terminates.
37
+ */
38
+ export declare const findPgError: (error: unknown) => Option.Option<PgErrorFacts>;
39
+ declare const UniqueViolation_base: Schema.Class<UniqueViolation, Schema.TaggedStruct<"UniqueViolation", {
40
+ readonly constraint: Schema.Option<Schema.String>;
41
+ readonly table: Schema.Option<Schema.String>;
42
+ readonly detail: Schema.Option<Schema.String>;
43
+ readonly cause: Schema.Defect;
44
+ }>, import("effect/Cause").YieldableError>;
45
+ /**
46
+ * SQLSTATE `23505`: a unique or primary key constraint was violated.
47
+ * `cause` is the value classification started from, so the wrapping graph
48
+ * (drizzle's query text and params, the `SqlError`) survives the recovery.
49
+ */
50
+ export declare class UniqueViolation extends UniqueViolation_base {}
51
+ declare const ForeignKeyViolation_base: Schema.Class<ForeignKeyViolation, Schema.TaggedStruct<"ForeignKeyViolation", {
52
+ readonly constraint: Schema.Option<Schema.String>;
53
+ readonly table: Schema.Option<Schema.String>;
54
+ readonly detail: Schema.Option<Schema.String>;
55
+ readonly cause: Schema.Defect;
56
+ }>, import("effect/Cause").YieldableError>;
57
+ /** SQLSTATE `23503`: a foreign key constraint was violated. `cause` as in {@link UniqueViolation}. */
58
+ export declare class ForeignKeyViolation extends ForeignKeyViolation_base {}
59
+ declare const NotNullViolation_base: Schema.Class<NotNullViolation, Schema.TaggedStruct<"NotNullViolation", {
60
+ readonly column: Schema.Option<Schema.String>;
61
+ readonly table: Schema.Option<Schema.String>;
62
+ readonly cause: Schema.Defect;
63
+ }>, import("effect/Cause").YieldableError>;
64
+ /** SQLSTATE `23502`: a NOT NULL constraint was violated. `column` names the offending column. */
65
+ export declare class NotNullViolation extends NotNullViolation_base {}
66
+ declare const CheckViolation_base: Schema.Class<CheckViolation, Schema.TaggedStruct<"CheckViolation", {
67
+ readonly constraint: Schema.Option<Schema.String>;
68
+ readonly table: Schema.Option<Schema.String>;
69
+ readonly cause: Schema.Defect;
70
+ }>, import("effect/Cause").YieldableError>;
71
+ /** SQLSTATE `23514`: a CHECK constraint was violated — including the bounds checks `./derive` writes. */
72
+ export declare class CheckViolation extends CheckViolation_base {}
73
+ declare const ExclusionViolation_base: Schema.Class<ExclusionViolation, Schema.TaggedStruct<"ExclusionViolation", {
74
+ readonly constraint: Schema.Option<Schema.String>;
75
+ readonly table: Schema.Option<Schema.String>;
76
+ readonly cause: Schema.Defect;
77
+ }>, import("effect/Cause").YieldableError>;
78
+ /** SQLSTATE `23P01`: an EXCLUDE constraint was violated. */
79
+ export declare class ExclusionViolation extends ExclusionViolation_base {}
80
+ declare const SerializationFailure_base: Schema.Class<SerializationFailure, Schema.TaggedStruct<"SerializationFailure", {
81
+ readonly cause: Schema.Defect;
82
+ }>, import("effect/Cause").YieldableError>;
83
+ /**
84
+ * SQLSTATE `40001`: the transaction lost a serialization conflict. Retryable:
85
+ * the same transaction re-run from the top may commit.
86
+ */
87
+ export declare class SerializationFailure extends SerializationFailure_base {}
88
+ declare const DeadlockDetected_base: Schema.Class<DeadlockDetected, Schema.TaggedStruct<"DeadlockDetected", {
89
+ readonly cause: Schema.Defect;
90
+ }>, import("effect/Cause").YieldableError>;
91
+ /**
92
+ * SQLSTATE `40P01`: the transaction was chosen as a deadlock victim.
93
+ * Retryable, like {@link SerializationFailure}.
94
+ */
95
+ export declare class DeadlockDetected extends DeadlockDetected_base {}
96
+ /**
97
+ * The integrity-constraint family (`23xxx`): a failure the *statement* caused,
98
+ * which a caller can map to a domain conflict. Retrying it unchanged fails
99
+ * again.
100
+ */
101
+ export type ConstraintViolation = UniqueViolation | ForeignKeyViolation | NotNullViolation | CheckViolation | ExclusionViolation;
102
+ /**
103
+ * Every failure this module recognises: a {@link ConstraintViolation}, or one
104
+ * of the two retryable transaction failures.
105
+ */
106
+ export type ClassifiedError = ConstraintViolation | SerializationFailure | DeadlockDetected;
107
+ /**
108
+ * Classifies any value into one of the seven tagged errors, by finding the
109
+ * driver's Postgres error in its graph ({@link findPgError}) and dispatching on the
110
+ * SQLSTATE. `Option.none()` when the graph holds no driver error, or holds one
111
+ * whose SQLSTATE is outside the classified families (a syntax error, a
112
+ * connection failure — `SqlError.reason` already covers those well).
113
+ */
114
+ export declare const classifyError: (error: unknown) => Option.Option<ClassifiedError>;
115
+ /**
116
+ * True when `error` is (or wraps) a unique-constraint violation.
117
+ *
118
+ * Kept as its own predicate, with its own walk, for adoption parity: this is
119
+ * the name and the semantics the consuming applications already call, and the
120
+ * semantics include a fallback the typed classification deliberately does not
121
+ * have — matching the stable Postgres message fragment when no `code` survived the
122
+ * wrapping. It also matches a `23505` declared under a `code` that is not
123
+ * SQLSTATE-shaped, which {@link classifyError} skips.
124
+ */
125
+ export declare const isUniqueViolation: (error: unknown) => boolean;
126
+ /**
127
+ * True when `error` is (or wraps) an integrity-constraint violation — one of
128
+ * the five `23xxx` classes {@link classifyError} recognises.
129
+ */
130
+ export declare const isConstraintViolation: (error: unknown) => boolean;
131
+ /**
132
+ * True when `error` is (or wraps) a transaction failure that re-running the
133
+ * whole transaction may clear: a serialization failure (`40001`) or a deadlock
134
+ * (`40P01`). Retrying a {@link ConstraintViolation} is pointless; retrying
135
+ * these is the documented remedy.
136
+ */
137
+ export declare const isRetryableTransactionFailure: (error: unknown) => boolean;
138
+ /**
139
+ * Recovers from a constraint violation in the error channel, leaving every
140
+ * other failure untouched — the failure is re-failed unchanged, with its
141
+ * original type, so a handler for one Postgres conflict does not swallow the
142
+ * rest of the channel.
143
+ *
144
+ * Data-last only: `self.pipe(catchConstraintViolation(handler))`.
145
+ */
146
+ export declare const catchConstraintViolation: <A2, E2, R2>(f: (violation: ConstraintViolation) => Effect.Effect<A2, E2, R2>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | A2, E | E2, R | R2>;
147
+ /**
148
+ * {@link catchConstraintViolation} narrowed to `23505`: recovers only a unique
149
+ * violation, passing everything else — including other constraint violations —
150
+ * through unchanged. This is the duplicate-key conflict a consumer maps to a
151
+ * domain "already exists".
152
+ *
153
+ * Data-last only: `self.pipe(catchUniqueViolation(handler))`.
154
+ */
155
+ export declare const catchUniqueViolation: <A2, E2, R2>(f: (violation: UniqueViolation) => Effect.Effect<A2, E2, R2>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | A2, E | E2, R | R2>;
156
+ export {};
157
+ //# sourceMappingURL=errors.d.ts.map