okmodel 0.6.0 → 0.7.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 (157) hide show
  1. package/README.md +100 -1131
  2. package/dist/adapters/capabilities.d.ts +3 -0
  3. package/dist/adapters/failure.d.ts +13 -2
  4. package/dist/adapters/lazy-errors.d.ts +22 -0
  5. package/dist/adapters/pg/bunsql-extra.d.ts +54 -3
  6. package/dist/adapters/pg/bunsql.d.ts +14 -1
  7. package/dist/adapters/pg/cursor.d.ts +17 -0
  8. package/dist/adapters/pg/no-inflight-timeout.d.ts +11 -8
  9. package/dist/adapters/pg/nodepostgres-extra.d.ts +22 -1
  10. package/dist/adapters/pg/nodepostgres.d.ts +12 -0
  11. package/dist/adapters/pg/pglite-wire.d.ts +24 -0
  12. package/dist/adapters/pg/pglite.d.ts +10 -0
  13. package/dist/adapters/pg/postgres-extra.d.ts +27 -11
  14. package/dist/adapters/pg/reserved.d.ts +42 -0
  15. package/dist/adapters/pg/session.d.ts +0 -18
  16. package/dist/contracts/connection.d.ts +7 -6
  17. package/dist/dialects/pg/floor.d.ts +68 -0
  18. package/dist/dialects/pg/index.d.ts +6 -1
  19. package/dist/dialects/pg/index.js +49 -159
  20. package/dist/dialects/pg/keys.d.ts +11 -8
  21. package/dist/dialects/pg/model.d.ts +40 -0
  22. package/dist/dialects/pg/morph.d.ts +43 -0
  23. package/dist/dialects/pg/operator-fit.d.ts +2 -2
  24. package/dist/dialects/pg/operators.d.ts +14 -7
  25. package/dist/dialects/pg/ops/bucket.d.ts +65 -0
  26. package/dist/dialects/pg/ops/inc.d.ts +2 -2
  27. package/dist/dialects/pg/ops/matches.d.ts +9 -12
  28. package/dist/dialects/pg/ops/relevance.d.ts +13 -0
  29. package/dist/dialects/pg/ops/search-query.d.ts +22 -0
  30. package/dist/dialects/pg/ops/window.d.ts +103 -0
  31. package/dist/dialects/pg/preset.d.ts +1 -1
  32. package/dist/dialects/pg/relations.d.ts +100 -3
  33. package/dist/dialects/pg/schema.d.ts +31 -3
  34. package/dist/dialects/pg/view/index.js +1 -1
  35. package/dist/runtime/aggregate.d.ts +8 -2
  36. package/dist/runtime/bucket-group.d.ts +70 -0
  37. package/dist/runtime/conflict.d.ts +22 -4
  38. package/dist/runtime/include.d.ts +0 -8
  39. package/dist/runtime/inflight.d.ts +1 -1
  40. package/dist/runtime/page.d.ts +4 -1
  41. package/dist/runtime/pg/bun.js +12 -6
  42. package/dist/runtime/pg/pg.js +5 -5
  43. package/dist/runtime/pg/pglite.d.ts +1 -0
  44. package/dist/runtime/pg/pglite.js +9 -9
  45. package/dist/runtime/pg/postgresjs.js +5 -5
  46. package/dist/runtime/plan.d.ts +95 -5
  47. package/dist/runtime/read-stream.d.ts +13 -16
  48. package/dist/runtime/result-name.d.ts +17 -0
  49. package/dist/runtime/server-check.d.ts +3 -3
  50. package/dist/runtime/sort-bind.d.ts +21 -0
  51. package/dist/runtime/sort.d.ts +56 -0
  52. package/dist/runtime/sync.d.ts +70 -0
  53. package/dist/runtime/tenancy/index.js +7 -5
  54. package/dist/runtime/text-search.d.ts +20 -0
  55. package/dist/runtime/traits/index.d.ts +80 -0
  56. package/dist/runtime/traits/index.js +175 -11
  57. package/dist/runtime/tree.d.ts +38 -0
  58. package/dist/runtime/types.d.ts +218 -22
  59. package/dist/runtime/validate/index.js +2 -2
  60. package/dist/runtime/version.d.ts +39 -0
  61. package/dist/runtime/window.d.ts +21 -0
  62. package/dist/runtime/write.d.ts +54 -2
  63. package/dist/shared/{wkme6tep.js → 14vx95q4.js} +8 -16
  64. package/dist/shared/1est4ezg.js +313 -0
  65. package/dist/shared/{4az561fn.js → 1qa0qajz.js} +8 -23
  66. package/dist/shared/{2cg3wnxc.js → 1w508jbr.js} +593 -130
  67. package/dist/shared/2c13q96h.js +18 -0
  68. package/dist/shared/{b4v57v3x.js → 2d3xq4ps.js} +2 -2
  69. package/dist/shared/{1nw1f3vs.js → 2shdbn8q.js} +7 -7
  70. package/dist/shared/3v7t1yrv.js +208 -0
  71. package/dist/shared/{x3jc53kg.js → 5e3tf63j.js} +2 -2
  72. package/dist/shared/5fe3kbsh.js +10 -0
  73. package/dist/shared/5v3xf80k.js +37 -0
  74. package/dist/shared/{c58secgj.js → 63qtkrca.js} +38 -4
  75. package/dist/shared/{2z7wv5tj.js → 67txcrcw.js} +55 -34
  76. package/dist/shared/79dt02vc.js +12 -0
  77. package/dist/shared/{8x72s86k.js → 7k9grvf6.js} +10 -4
  78. package/dist/shared/7kms6fvj.js +20 -0
  79. package/dist/shared/7mnme5tn.js +8 -0
  80. package/dist/shared/7n7ddwt4.js +6 -0
  81. package/dist/shared/{5j36z8dn.js → 81ebdx9p.js} +39 -70
  82. package/dist/shared/{knmes2kh.js → 8skf0cam.js} +1 -1
  83. package/dist/shared/9a4ae4p8.js +31 -0
  84. package/dist/shared/9y3mmmh1.js +436 -0
  85. package/dist/shared/ab9gmbwy.js +213 -0
  86. package/dist/shared/{f0f1n281.js → ax7qv55k.js} +2 -2
  87. package/dist/shared/{g90m9b0g.js → aytqh5y2.js} +6 -5
  88. package/dist/shared/{vjpnzwjz.js → b5pftnn2.js} +1 -1
  89. package/dist/shared/{ag4c7516.js → b76b8d95.js} +2 -2
  90. package/dist/shared/{vmfkhszw.js → bg3rym3g.js} +33 -26
  91. package/dist/shared/{mb2ycs1p.js → bm22fvzr.js} +15 -15
  92. package/dist/shared/{q3j1tq7a.js → bz84tkzm.js} +148 -23
  93. package/dist/shared/{31vm914h.js → cm3w0ywp.js} +15 -16
  94. package/dist/shared/cn957f8d.js +179 -0
  95. package/dist/shared/{gwn6v667.js → cnkky55j.js} +25 -14
  96. package/dist/shared/{ys0xzyaj.js → dkxgzaqd.js} +224 -19
  97. package/dist/shared/dzj09cye.js +10 -0
  98. package/dist/shared/exn3sd90.js +359 -0
  99. package/dist/shared/g6evj1j3.js +66 -0
  100. package/dist/shared/ga9j92qj.js +25 -0
  101. package/dist/shared/{t3g0k88x.js → gs74dbj5.js} +16 -12
  102. package/dist/shared/gzaveazc.js +285 -0
  103. package/dist/shared/{2jd1sn0e.js → hyh73qk6.js} +1 -1
  104. package/dist/shared/{r1fdd0f6.js → je4axnm8.js} +5 -2
  105. package/dist/shared/{1f0xa3cb.js → jvd0mqag.js} +3 -3
  106. package/dist/shared/jznfax0y.js +155 -0
  107. package/dist/shared/{cgbamx3e.js → kb2sdhfb.js} +7 -4
  108. package/dist/shared/{wt5rzv1h.js → kr0jzbw2.js} +5 -5
  109. package/dist/shared/{p6bjzq9q.js → ktz42yez.js} +12 -4
  110. package/dist/shared/{p248q79y.js → m6p12kbr.js} +2 -2
  111. package/dist/shared/{r6nwkxnt.js → m7n9h2a5.js} +4 -4
  112. package/dist/shared/{672t5vym.js → mss6ctgv.js} +2 -2
  113. package/dist/shared/{929h19xp.js → mtaszv5a.js} +40 -10
  114. package/dist/shared/{b3awxn7h.js → n816peh1.js} +1 -1
  115. package/dist/shared/nbw1z4yd.js +18 -0
  116. package/dist/shared/{nx9hh281.js → p6qc5gcp.js} +12 -11
  117. package/dist/shared/{pjr6xrm5.js → pbp1ss85.js} +20 -17
  118. package/dist/shared/pt94kz7f.js +6 -0
  119. package/dist/shared/q5c6ngn3.js +13 -0
  120. package/dist/shared/{xq0mvhts.js → rs73fca9.js} +4 -4
  121. package/dist/shared/{r11n8grd.js → sfz1ksn6.js} +7 -4
  122. package/dist/shared/{wynzeggs.js → svncdeh5.js} +6 -6
  123. package/dist/shared/{j60w0dvr.js → tbrvgw6z.js} +57 -38
  124. package/dist/shared/te5as7z7.js +622 -0
  125. package/dist/shared/{ttmg09vd.js → tsvww22q.js} +1 -1
  126. package/dist/shared/{y2t3jktd.js → ttsqgp4p.js} +19 -12
  127. package/dist/shared/{x72jb7ad.js → v8g1vgv8.js} +1 -1
  128. package/dist/shared/{sbdme3vy.js → vb6qfyve.js} +60 -10
  129. package/dist/shared/{5d8t84hg.js → vczs8gd4.js} +14 -14
  130. package/dist/shared/{5yv4k6a6.js → vqrjpcqn.js} +144 -2
  131. package/dist/shared/{3fqwmr09.js → w72kgg4q.js} +4 -4
  132. package/dist/shared/{1z04fa85.js → w79hsckn.js} +12 -57
  133. package/dist/shared/{09p049xa.js → wa07eas9.js} +42 -14
  134. package/dist/shared/{32cnm7kn.js → x933k9a3.js} +74 -38
  135. package/dist/shared/{n076ydsc.js → xhfsfkgk.js} +62 -28
  136. package/dist/shared/xjk7hnn5.js +272 -0
  137. package/dist/shared/{hazqqecq.js → xnxsed62.js} +50 -21
  138. package/dist/shared/y6njgyda.js +249 -0
  139. package/dist/shared/{8etn7d8w.js → z7s6fpxv.js} +1 -1
  140. package/dist/shared/{n7mcc1gk.js → zchaghkf.js} +4 -3
  141. package/dist/shared/zm0ptry0.js +40 -0
  142. package/dist/shared/{nsva2a6b.js → znm0bpp5.js} +3 -0
  143. package/dist/shared/zp9fmend.js +145 -0
  144. package/dist/tooling/cli.js +11 -11
  145. package/dist/tooling/migrate/engine.d.ts +3 -2
  146. package/dist/tooling/migrate/index.js +7 -7
  147. package/dist/tooling/testing/index.js +1 -1
  148. package/package.json +4 -2
  149. package/dist/shared/3qnhc7w5.js +0 -38
  150. package/dist/shared/7m88x3e9.js +0 -45
  151. package/dist/shared/8dqhth8c.js +0 -12
  152. package/dist/shared/d5azex6s.js +0 -6
  153. package/dist/shared/j3rkk835.js +0 -99
  154. package/dist/shared/k68eedmy.js +0 -85
  155. package/dist/shared/venqrjna.js +0 -216
  156. package/dist/shared/wxr8cwns.js +0 -6
  157. package/dist/shared/zm0mafk4.js +0 -92
@@ -8,7 +8,7 @@ import { type QueryCode } from "../contracts/error.js";
8
8
  import { type OperatorName } from "../dialects/pg/operators.js";
9
9
  import { type ColumnModel, type QuerySchema, type RelationModel, type TableModel } from "../dialects/pg/model.js";
10
10
  /** A read the caller asked for. */
11
- export type ReadOp = "find" | "one" | "count" | "exists" | "aggregate";
11
+ export type ReadOp = "find" | "one" | "count" | "exists" | "aggregate" | "descendants" | "ancestors";
12
12
  /** Routing constraint chosen before execute. */
13
13
  export type RouteConstraint = "auto" | "primary" | "replica";
14
14
  /**
@@ -25,7 +25,7 @@ export type CallScope = {
25
25
  /** Writes one more `where` condition after the tenant and active-set predicates. */
26
26
  export type Keyset = (sink: Sink, alias: string) => void;
27
27
  /** Writes the whole statement of a read the planner does not know. */
28
- export type ReadBuild = (schema: QuerySchema, table: Indexed, sink: Sink, outputs: Built | undefined, call: ReadCall) => void;
28
+ export type ReadBuild = (schema: QuerySchema, table: Indexed, sink: Sink, outputs: Built | undefined, call: ReadCall, hooks?: IncludeHooks) => void;
29
29
  /** One read call. `all` is the `.all(reason)` escape for an unbounded read. */
30
30
  export type ReadCall = {
31
31
  readonly op: ReadOp;
@@ -35,6 +35,8 @@ export type ReadCall = {
35
35
  readonly orderBy?: unknown;
36
36
  readonly limit?: unknown;
37
37
  readonly include?: unknown;
38
+ /** `find` and `count`. Rewritten to `matches` and `relevance` before SQL. */
39
+ readonly search?: unknown;
38
40
  readonly all?: string;
39
41
  /** The `after` cursor of a `page`. The condition joins the others with `and`. */
40
42
  readonly after?: Keyset;
@@ -47,6 +49,15 @@ export type ReadCall = {
47
49
  readonly timeout?: number;
48
50
  /** `"primary"` or `"replica"`. Absent means the router classifies the statement. */
49
51
  readonly route?: "primary" | "replica";
52
+ /**
53
+ * Endpoint a `.stream()` already chose.
54
+ *
55
+ * The first batch leaves `name` empty and the router fills it. Later batches
56
+ * pass the same object so they stay on that endpoint.
57
+ */
58
+ readonly pin?: {
59
+ name?: string;
60
+ };
50
61
  /** Set by `for()` or `unscoped()`. Absent on a schema with no tenancy. */
51
62
  readonly scope?: CallScope;
52
63
  /** Inspect lines from the tenancy object. Absent when the schema has no tenancy. */
@@ -95,6 +106,11 @@ export type ScalarOut = {
95
106
  readonly at: number;
96
107
  readonly decode: ((wire: string) => unknown) | undefined;
97
108
  };
109
+ /** One target inside a morph include's JSON value. */
110
+ export type MorphOut = {
111
+ readonly type: string;
112
+ readonly fields: readonly ScalarOut[];
113
+ };
98
114
  /** A nested include. `at` is a root column for `one` and the JSON column for `many`. */
99
115
  export type IncludeOut = {
100
116
  readonly name: string;
@@ -102,6 +118,15 @@ export type IncludeOut = {
102
118
  readonly at: number;
103
119
  readonly fields: readonly ScalarOut[];
104
120
  readonly nested: readonly IncludeOut[];
121
+ /**
122
+ * Set when the include is a morph.
123
+ *
124
+ * The column at `at` is one JSON array, `[type, ...fields]`, or null.
125
+ */
126
+ readonly morph?: {
127
+ readonly typeKey: string;
128
+ readonly targets: readonly MorphOut[];
129
+ };
105
130
  };
106
131
  /** Columns and includes in statement order. */
107
132
  export type Outputs = {
@@ -114,6 +139,13 @@ export type IncludeHooks = {
114
139
  read(schema: QuerySchema, table: Indexed, include: unknown, all: string | undefined): readonly ParsedInclude[];
115
140
  select(include: ParsedInclude, alias: string, sink: Sink, outputs: Built | undefined, column: number): number;
116
141
  join(schema: QuerySchema, include: ParsedInclude, sink: Sink, parentAlias: string, alias: string, depth: number): void;
142
+ /**
143
+ * Reports whether an include's `where` or `orderBy` needs the operator compiler.
144
+ *
145
+ * The root check does not walk includes. This does, and only after the
146
+ * include planner has loaded.
147
+ */
148
+ needsOperator(schema: QuerySchema, call: ReadCall): boolean;
117
149
  };
118
150
  export type Built = {
119
151
  fields: ScalarOut[];
@@ -157,8 +189,13 @@ export type Sink = {
157
189
  * @returns Names in a stable order
158
190
  */
159
191
  export declare function readOptionNames(op: ReadOp): readonly string[];
160
- /** 0.2 operator SQL. Absent until that module has been imported. */
161
- type OperatorEmit = (column: ColumnModel, ref: string, name: OperatorName, value: unknown, sink: Sink) => void;
192
+ /**
193
+ * 0.2 operator SQL. Absent until that module has been imported.
194
+ *
195
+ * `order` is set when the operator is an `orderBy` value. `relevance` writes
196
+ * its direction there; the same operator in `where` is refused by that module.
197
+ */
198
+ type OperatorEmit = (column: ColumnModel, ref: string, name: OperatorName, value: unknown, sink: Sink, order?: true) => void;
162
199
  /**
163
200
  * Installs the 0.2 operator compiler.
164
201
  *
@@ -177,6 +214,16 @@ export declare function installOperatorSql(emit: OperatorEmit): void;
177
214
  * @returns `true` when {@link installOperatorSql} has not run and the read needs it
178
215
  */
179
216
  export declare function readNeedsOperatorSql(schema: QuerySchema, call: ReadCall): boolean;
217
+ /**
218
+ * Reports whether `orderBy` holds a `relevance` value.
219
+ *
220
+ * Called before the operator compiler has loaded. The include planner uses it
221
+ * for a related table.
222
+ *
223
+ * @param orderBy - The caller's order
224
+ * @returns `true` when a value is `relevance`
225
+ */
226
+ export declare function orderHasRelevance(orderBy: unknown): boolean;
180
227
  /**
181
228
  * Writes `<table> <alias> where <link to parent>` for a to-many relation.
182
229
  *
@@ -184,6 +231,18 @@ export declare function readNeedsOperatorSql(schema: QuerySchema, call: ReadCall
184
231
  * The caller adds the related table's own predicates after it.
185
232
  */
186
233
  export declare function emitRelationFrom(schema: QuerySchema, relation: RelationModel, child: Indexed, sink: Sink, parent: string, alias: string, depth: number): void;
234
+ /**
235
+ * Reports whether a filter needs the operator compiler.
236
+ *
237
+ * Equality and text patterns do not. The include planner calls this for a
238
+ * related table.
239
+ *
240
+ * @param table - The table the filter reads
241
+ * @param value - A `where` value
242
+ * @param key - The field, when `value` is that field's operand
243
+ * @returns `true` when the operator compiler has to load
244
+ */
245
+ export declare function needsOperatorSql(table: Indexed, value: unknown, key?: string): boolean;
187
246
  /**
188
247
  * Runs registered safety rules before planning.
189
248
  *
@@ -259,6 +318,15 @@ export declare function emitPrimaryOrder(table: Indexed, sink: Sink, alias: stri
259
318
  * @param column - The field the caller named
260
319
  */
261
320
  export declare function refuseHidden(column: ColumnModel): void;
321
+ export declare function emitLimit(call: ReadCall, sink: Sink): void;
322
+ /** One target branch of a morph include. */
323
+ export type ParsedMorphTarget = {
324
+ readonly type: string;
325
+ readonly table: string;
326
+ readonly sql: string;
327
+ readonly keySql: string;
328
+ readonly columns: readonly ColumnModel[];
329
+ };
262
330
  export type ParsedInclude = {
263
331
  readonly name: string;
264
332
  readonly relation: RelationModel;
@@ -270,13 +338,35 @@ export type ParsedInclude = {
270
338
  /** Index in the projected list that is null when the related row is missing. */
271
339
  readonly anchor: number;
272
340
  readonly nested: readonly ParsedInclude[];
341
+ /**
342
+ * Set when the include is a morph.
343
+ *
344
+ * Absent on every other include, so a normal include does not carry it.
345
+ */
346
+ readonly morphTo?: {
347
+ readonly typeKey: string;
348
+ readonly typeSql: string;
349
+ readonly idSql: string;
350
+ readonly targets: readonly ParsedMorphTarget[];
351
+ };
273
352
  };
274
353
  export declare function selectedColumns(table: Indexed, select: unknown): readonly ColumnModel[];
275
354
  export declare function parseOrder(field: string, value: unknown): {
276
355
  readonly dir: "asc" | "desc";
277
356
  readonly nulls: "first" | "last";
278
357
  };
279
- export declare function rejectKeys(value: Readonly<Record<string, unknown>>, accepted: readonly string[], where: string): void;
358
+ /**
359
+ * Refuses a key that `accepted` does not list.
360
+ *
361
+ * A read passes `search`. `one` and `exists` then say to use `find` or
362
+ * `count`, instead of the generic option list.
363
+ *
364
+ * @param value - Caller options
365
+ * @param accepted - Names this call allows
366
+ * @param where - The call, named in the error
367
+ * @param search - When set, a rejected `search` key uses the read message
368
+ */
369
+ export declare function rejectKeys(value: Readonly<Record<string, unknown>>, accepted: readonly string[], where: string, search?: true): void;
280
370
  export declare function readLimit(value: unknown): number;
281
371
  /**
282
372
  * Lets an object through only when the column's codec takes it (OKM1121).
@@ -1,26 +1,23 @@
1
1
  /**
2
- * Server-side read cursor.
2
+ * Keyset batches for `.stream()` (spec §10).
3
3
  *
4
4
  * Loaded the first time `.stream()` is iterated. A find that is only awaited
5
- * does not load it.
5
+ * does not load it. Each batch is one finished `page` query: fetch, yield,
6
+ * then fetch the next. A `break` does not hold a connection. A server cursor
7
+ * is not used, so the driver `stream` flag is not read.
6
8
  */
7
- import type { DriverPool } from "../contracts/driver.js";
8
- import { OkmError, type ErrorStatuses } from "../contracts/error.js";
9
- import { type Plan, type ReadCall } from "./plan.js";
9
+ import { OkmError } from "../contracts/error.js";
10
+ import { type Mods, type Session } from "./client.js";
11
+ import { type ArchiveView, type ReadCall } from "./plan.js";
10
12
  /**
11
- * Yields decoded rows from the driver's stream.
13
+ * Yields decoded rows, one keyset batch at a time.
12
14
  *
13
15
  * @param session - Connected pool and HTTP statuses
14
- * @param call - The read being streamed
15
- * @param prepare - Compiles the statement on first use
16
+ * @param call - The find being streamed
17
+ * @param mods - `.all` reason and presets
18
+ * @param view - Archive visibility
19
+ * @param batchOptions - `{ batch }`, or omitted for {@link DEFAULT_BATCH}
16
20
  * @param log - Attaches the session logger and returns the error
17
21
  * @returns One row at a time
18
22
  */
19
- export declare function streamRows(session: {
20
- readonly connected: Promise<void>;
21
- readonly pool: DriverPool;
22
- readonly http: ErrorStatuses | undefined;
23
- }, call: ReadCall, prepare: () => Promise<{
24
- readonly plan: Plan;
25
- readonly params: readonly (string | null)[];
26
- }>, log: (error: OkmError) => OkmError): AsyncIterable<unknown>;
23
+ export declare function streamRows(session: Session, call: ReadCall, mods: Mods, view: ArchiveView | undefined, batchOptions: object | undefined, log: (error: OkmError) => OkmError): AsyncIterable<unknown>;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Result keys a caller chooses.
3
+ *
4
+ * Loaded with the bucket chunk and the window chunk (D258). The startup
5
+ * graph does not import it.
6
+ */
7
+ /**
8
+ * Refuses a result key that would shadow or replace a prototype property.
9
+ *
10
+ * Assigning `__proto__` replaces that object's prototype when the value is an
11
+ * object. The other names shadow methods on the row. `then` makes the row a
12
+ * thenable. The check runs before any SQL.
13
+ *
14
+ * @param name - The result key
15
+ * @param where - The call, such as `rowNumber as` or `bucket as`
16
+ */
17
+ export declare function refusePrototypeKey(name: string, where: string): void;
@@ -17,11 +17,11 @@ export declare const versionQuery = "select current_setting('server_version_num'
17
17
  /**
18
18
  * Accepts the `server_version_num` row, or throws OKM1801, OKM1520, OKM1802, or OKM1803.
19
19
  *
20
- * A declared `requires` is the minimum, including a major below 15 when the
21
- * schema names it on purpose. With no `requires`, the floor is 15.
20
+ * A declared floor is the minimum. With no floor, the minimum is 15.
21
+ * A major below 15 is refused when the schema is built.
22
22
  *
23
23
  * @param pool - Connection that ran the version query
24
- * @param schema - Schema whose `requires` is the declared minimum
24
+ * @param schema - Schema whose floor is the declared minimum
25
25
  * @param rows - `server_version_num`, `version()`, and the `okm_meta` hash
26
26
  * @param http - Status overrides from `connect`
27
27
  * @param requireMeta - When true, a missing hash is OKM1520
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Unique index `sortable()` adds.
3
+ *
4
+ * Tenancy has already widened uniques (D168), so this index names the tenant
5
+ * keys itself. The move and insert SQL load later, on the first call.
6
+ */
7
+ import type { AnyTable } from "../dialects/pg/table.js";
8
+ /**
9
+ * Adds the position unique index on tables this trait applies to.
10
+ *
11
+ * A path table is refused: the tenant key is not a column, so the index could
12
+ * not keep two tenants from sharing a position.
13
+ *
14
+ * @param self - The trait object `schema()` stored
15
+ * @param tables - Tables after the tenancy rewrite
16
+ * @param casing - Schema casing
17
+ * @param schemaTraits - Traits from `schema()`, when any were passed
18
+ * @param group - Field that splits the order, when `sortable` was given one
19
+ * @returns Tables with the index on the ones this trait applies to
20
+ */
21
+ export declare function rewriteSortable(self: object, tables: readonly AnyTable[], casing: "snake" | undefined, schemaTraits: readonly object[] | undefined, group: string | undefined): readonly AnyTable[];
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Position assignment and `move`.
3
+ *
4
+ * Loaded on the first insert or move of a `sortable()` table. Positions are
5
+ * dense positive integers, unique inside the group (and the tenant). The
6
+ * advisory lock is its own statement. The next statement reads positions.
7
+ * Under READ COMMITTED a statement's snapshot is taken when it starts, which
8
+ * is before a lock in that same statement is granted, so a commit that landed
9
+ * while this transaction waited would be invisible.
10
+ */
11
+ import type { Statement } from "../contracts/driver.js";
12
+ import type { ColumnModel, QuerySchema } from "../dialects/pg/model.js";
13
+ import { type ArchiveView, type CallScope, type Indexed } from "./plan.js";
14
+ import { type RunHost } from "./tx.js";
15
+ type Host = RunHost & {
16
+ readonly schema: QuerySchema;
17
+ readonly scope?: CallScope;
18
+ readonly connected?: Promise<void>;
19
+ };
20
+ /**
21
+ * Inserts rows at the end of their group.
22
+ *
23
+ * The lock statement runs first. The insert then takes `max(position) + n`
24
+ * from a snapshot taken after that lock is held. A writer that bypasses these
25
+ * statements can still race; the unique index rejects a duplicate.
26
+ *
27
+ * @param table - Target table
28
+ * @param columns - Columns the insert already decided to write
29
+ * @param rows - Stamped rows, in call order
30
+ * @param group - Group field, when the trait has one
31
+ * @param tenantFields - Tenant fields stored on this table
32
+ * @returns The lock, then the insert without `returning`
33
+ */
34
+ export declare function insertAtEnd(table: Indexed, columns: readonly ColumnModel[], rows: readonly Record<string, unknown>[], group: string | undefined, tenantFields: readonly string[] | undefined): {
35
+ readonly lock: Statement;
36
+ readonly text: string;
37
+ readonly params: (string | null)[];
38
+ };
39
+ /**
40
+ * Moves one row before or after another in the same group.
41
+ *
42
+ * The result is `{ position }` after the move. A missing id is `not_found`.
43
+ * An anchor in another group is OKM1120 and writes nothing. The same id on
44
+ * both sides leaves the row where it is.
45
+ *
46
+ * @param host - Pool and schema
47
+ * @param tableName - Table the trait is on
48
+ * @param id - Primary key of the row to move, without the tenant key
49
+ * @param place - `{ before }` or `{ after }`, the anchor's primary key
50
+ * @param view - Archive visibility, when the handle narrowed it
51
+ * @returns The row's position after the move
52
+ */
53
+ export declare function move(host: Host, tableName: string, id: unknown, place: unknown, view: ArchiveView | undefined): Promise<{
54
+ readonly position: number;
55
+ }>;
56
+ export {};
@@ -0,0 +1,70 @@
1
+ /**
2
+ * `sync` (spec §11, §13 tool 10).
3
+ *
4
+ * One transaction sets a to-many relation to an exact set of ids. The first
5
+ * statement locks the parent row. The write is a second statement, so a sync
6
+ * that waited for that lock sees the links the previous sync committed. A join table
7
+ * gains and loses link rows. A one-to-many sets or clears a nullable foreign
8
+ * key and leaves the related row in place. A link that would have to delete
9
+ * that row is refused. Duplicate ids count once. An id that is not in the
10
+ * related table, or a parent id that is not in this table, writes nothing and
11
+ * throws `not_found`. The parent row is locked, so two syncs of the same id
12
+ * run one after the other.
13
+ *
14
+ * Loaded on the first call.
15
+ */
16
+ import type { Statement } from "../contracts/driver.js";
17
+ import type { PresetUse } from "../dialects/pg/model.js";
18
+ import { type Mods as ReadMods, type Session } from "./client.js";
19
+ import { type ArchiveView } from "./plan.js";
20
+ type Mods = {
21
+ readonly expect?: number;
22
+ readonly archive?: ArchiveView | undefined;
23
+ readonly uses?: PresetUse | undefined;
24
+ };
25
+ /**
26
+ * Runs one `sync`.
27
+ *
28
+ * @param session - Client session
29
+ * @param table - Table that owns the relation
30
+ * @param id - Primary key of that row
31
+ * @param relation - To-many relation name
32
+ * @param ids - Related primary keys the relation should hold
33
+ * @param options - `expect`, `signal`, and `timeout`
34
+ * @param mods - Chained `expect`, archive visibility, and presets
35
+ * @returns The ids whose links were added or removed
36
+ */
37
+ export declare function execute(session: Session, table: string, id: unknown, relation: unknown, ids: unknown, options: object | undefined, mods: Mods): Promise<{
38
+ readonly added: readonly unknown[];
39
+ readonly removed: readonly unknown[];
40
+ }>;
41
+ /**
42
+ * The statement `sync` would run.
43
+ *
44
+ * @param session - Client session
45
+ * @param table - Table that owns the relation
46
+ * @param id - Primary key of that row
47
+ * @param relation - To-many relation name
48
+ * @param ids - Related primary keys the relation should hold
49
+ * @param options - `expect`, `signal`, and `timeout`
50
+ * @param mods - Chained `expect`, archive visibility, and presets
51
+ * @returns One statement
52
+ */
53
+ export declare function explain(session: Session, table: string, id: unknown, relation: unknown, ids: unknown, options: object | undefined, mods: Mods): Promise<{
54
+ readonly statements: readonly Statement[];
55
+ }>;
56
+ /**
57
+ * The handle for one `sync`.
58
+ *
59
+ * Await runs the change. `sql` shows the lock and the write. `expect` lives on
60
+ * the outer handle, which calls this again with `mods.expect` set.
61
+ *
62
+ * @param session - Client session
63
+ * @param table - Table that owns the relation
64
+ * @param input - `id`, `relation`, `ids`, and `options`
65
+ * @param mods - Presets, plus `expect` when the outer handle chained it
66
+ * @param view - Archive view. The same value a read on this handle uses
67
+ * @returns A handle. The value is `{ added, removed }`
68
+ */
69
+ export declare function build(session: Session, table: string, input: object, mods: ReadMods, view: ArchiveView | undefined): Promise<unknown> & Record<string, unknown>;
70
+ export {};
@@ -8,10 +8,10 @@ import {
8
8
  } from "../../shared/bdk4sty6.js";
9
9
  import {
10
10
  isRelationCall
11
- } from "../../shared/k68eedmy.js";
11
+ } from "../../shared/exn3sd90.js";
12
12
  import {
13
13
  uuid2
14
- } from "../../shared/2jd1sn0e.js";
14
+ } from "../../shared/hyh73qk6.js";
15
15
  import {
16
16
  index2,
17
17
  snakeCase
@@ -28,7 +28,7 @@ import {
28
28
  } from "../../shared/94ez8kzh.js";
29
29
  import {
30
30
  quote
31
- } from "../../shared/2z7wv5tj.js";
31
+ } from "../../shared/67txcrcw.js";
32
32
  import {
33
33
  assertIdentifier2
34
34
  } from "../../shared/nfzc5z0w.js";
@@ -233,7 +233,8 @@ function rewriteTenant(keys, keyColumns, item, tables, kind, tenants, exemptions
233
233
  ...options,
234
234
  ...primaryKey !== undefined ? { primaryKey } : {},
235
235
  ...Object.keys(unique).length > 0 ? { unique } : {},
236
- ...indexes !== undefined ? { indexes } : {}
236
+ ...indexes !== undefined ? { indexes } : {},
237
+ tenantKeys: keys
237
238
  }
238
239
  };
239
240
  return declared !== undefined && declared.length > 0 ? markPathEndpoint(rewritten) : rewritten;
@@ -1667,7 +1668,8 @@ function rewriteTenant2(key, column, item, tables, kind, tenants, exemptions, ex
1667
1668
  ...options,
1668
1669
  ...primaryKey !== undefined ? { primaryKey } : {},
1669
1670
  ...Object.keys(unique).length > 0 ? { unique } : {},
1670
- ...indexes !== undefined ? { indexes } : {}
1671
+ ...indexes !== undefined ? { indexes } : {},
1672
+ tenantKeys: [key]
1671
1673
  }
1672
1674
  };
1673
1675
  return noted !== undefined && noted.length > 0 ? markPathEndpoint(rewritten) : rewritten;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `search` on `find` and `count`.
3
+ *
4
+ * Loaded on the first call that passes `search`. It rewrites the call into
5
+ * `matches` and, when `find` has no `orderBy`, `relevance` plus the primary
6
+ * key. The SQL compiler is the one those helpers already use.
7
+ */
8
+ import type { QuerySchema } from "../dialects/pg/model.js";
9
+ import { type ReadCall } from "./plan.js";
10
+ /**
11
+ * Rewrites `search` into `matches` and `relevance`.
12
+ *
13
+ * `count` keeps the caller's order, which it does not emit. An explicit
14
+ * `orderBy` on `find` replaces the automatic ranking.
15
+ *
16
+ * @param schema - Connected schema
17
+ * @param call - The read, with `search` set
18
+ * @returns The same read, without `search`
19
+ */
20
+ export declare function applyTextSearch(schema: QuerySchema, call: ReadCall): ReadCall;
@@ -5,6 +5,7 @@
5
5
  * does not import it does not load these columns.
6
6
  */
7
7
  import { type PresetMap } from "../../dialects/pg/preset.js";
8
+ import type { QuerySchema, SchemaHookCtx } from "../../dialects/pg/model.js";
8
9
  import type { AnyTable, RowFrom } from "../../dialects/pg/table.js";
9
10
  import { attachArchive } from "../archive-handle.js";
10
11
  import { type Trait, type TraitContext, type TraitModel } from "../../dialects/pg/trait.js";
@@ -131,3 +132,82 @@ export declare function archivable(options?: ArchivableOptions): {
131
132
  rewrite(tables: readonly AnyTable[], casing: "snake" | undefined, schemaTraits: readonly object[] | undefined): readonly AnyTable[];
132
133
  hook: typeof attachArchive;
133
134
  };
135
+ /**
136
+ * Adds `version` and checks it on every update.
137
+ *
138
+ * The column starts at 1. An update passes the version it read and the write
139
+ * bumps it. A stale version is category `conflict` and changes nothing.
140
+ * Input cannot set `version`.
141
+ *
142
+ * @returns The versioned trait
143
+ */
144
+ export declare function versioned(): {
145
+ readonly name: string;
146
+ readonly fields: {
147
+ readonly version: import("../../dialects/pg/column.js").ColumnBuilder<number, import("../../dialects/pg/column.js").FlagTrue<import("../../dialects/pg/column.js").FlagTrue<import("../../dialects/pg/column.js").PlainFlags, "hasDefault">, "guarded">>;
148
+ };
149
+ readonly touch?: readonly "version"[];
150
+ readonly sealed?: readonly "version"[];
151
+ apply(model: TraitModel, ctx: TraitContext): void;
152
+ contribute(peers: unknown, built: unknown): readonly unknown[];
153
+ };
154
+ /**
155
+ * Adds `position` and `move`.
156
+ *
157
+ * New rows go at the end of the group. `move` places a row before or after
158
+ * another in that group. `find` and `page` order by `position` when the
159
+ * caller does not pass `orderBy`. Input cannot set `position`.
160
+ *
161
+ * @param groupBy - Column that splits the order. Omit it for one list
162
+ * @returns The sortable trait
163
+ */
164
+ export declare function sortable(groupBy?: string): {
165
+ name: string;
166
+ fields: {
167
+ readonly position: import("../../dialects/pg/column.js").ColumnBuilder<number, import("../../dialects/pg/column.js").FlagTrue<import("../../dialects/pg/column.js").PlainFlags, "guarded">>;
168
+ };
169
+ touch?: readonly "position"[];
170
+ sealed?: readonly "position"[];
171
+ contribute(peers: unknown, built: unknown): readonly unknown[];
172
+ group?: string;
173
+ "~sort": true;
174
+ apply(model: TraitModel, ctx: TraitContext): void;
175
+ /**
176
+ * Adds the unique position index after tenancy has widened uniques.
177
+ *
178
+ * @param tables - Tables after the tenancy rewrite
179
+ * @param casing - Schema casing
180
+ * @param schemaTraits - Traits from `schema()`, when any were passed
181
+ * @returns The rewritten tables
182
+ */
183
+ rewrite(tables: readonly AnyTable[], casing: "snake" | undefined, schemaTraits: readonly object[] | undefined): readonly AnyTable[];
184
+ hook: typeof attachSort;
185
+ };
186
+ /**
187
+ * Adds `move` and the default position order on a sortable table.
188
+ *
189
+ * @param target - The table handle being built
190
+ * @param ctx - Table name and the session
191
+ */
192
+ declare function attachSort(target: Record<string, unknown>, ctx: SchemaHookCtx): void;
193
+ declare module "../types.js" {
194
+ interface TableExtras<S extends QuerySchema, K extends keyof S["~byName"] & string> {
195
+ /**
196
+ * Moves one row before or after another in the same group.
197
+ *
198
+ * The result is the row's position after the move. A missing id is
199
+ * `not_found`. An anchor in another group is OKM1120.
200
+ *
201
+ * @param id - Primary key of the row to move, without the tenant key
202
+ * @param place - `{ before }` or `{ after }`, the anchor's primary key
203
+ * @returns The position after the move
204
+ */
205
+ move(id: unknown, place: {
206
+ readonly before: unknown;
207
+ } | {
208
+ readonly after: unknown;
209
+ }): Promise<{
210
+ readonly position: number;
211
+ }>;
212
+ }
213
+ }