okmodel 0.5.0 → 0.6.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 (169) hide show
  1. package/README.md +36 -18
  2. package/dist/adapters/pg/no-inflight-timeout.d.ts +17 -0
  3. package/dist/adapters/pg/postgres-extra.d.ts +22 -0
  4. package/dist/contracts/catalog/document.d.ts +19 -5
  5. package/dist/contracts/catalog/object.d.ts +2 -1
  6. package/dist/contracts/catalog/policy.d.ts +48 -0
  7. package/dist/contracts/catalog/types.d.ts +25 -1
  8. package/dist/contracts/catalog/view.d.ts +2 -0
  9. package/dist/contracts/internal.js +1 -1
  10. package/dist/contracts/location.d.ts +10 -0
  11. package/dist/dialects/pg/column.d.ts +13 -2
  12. package/dist/dialects/pg/ddl.d.ts +37 -1
  13. package/dist/dialects/pg/emit.d.ts +19 -1
  14. package/dist/dialects/pg/errors.d.ts +32 -1
  15. package/dist/dialects/pg/ext/citext.js +1 -1
  16. package/dist/dialects/pg/ext/index.js +1 -1
  17. package/dist/dialects/pg/ext/pg-trgm.js +1 -1
  18. package/dist/dialects/pg/filter-parse.d.ts +10 -3
  19. package/dist/dialects/pg/fn/index.js +10 -2
  20. package/dist/dialects/pg/index.d.ts +2 -1
  21. package/dist/dialects/pg/index.js +29 -17
  22. package/dist/dialects/pg/introspect.d.ts +7 -0
  23. package/dist/dialects/pg/model.d.ts +7 -2
  24. package/dist/dialects/pg/ops/or.d.ts +1 -1
  25. package/dist/dialects/pg/owner.d.ts +42 -0
  26. package/dist/dialects/pg/path-endpoint.d.ts +20 -0
  27. package/dist/dialects/pg/role/check.d.ts +3 -0
  28. package/dist/dialects/pg/role/index.d.ts +71 -6
  29. package/dist/dialects/pg/role/sql.d.ts +11 -0
  30. package/dist/dialects/pg/schema.d.ts +2 -0
  31. package/dist/dialects/pg/table.d.ts +25 -3
  32. package/dist/dialects/pg/temporal.d.ts +5 -76
  33. package/dist/dialects/pg/tenancy.d.ts +45 -3
  34. package/dist/dialects/pg/view/client.d.ts +19 -1
  35. package/dist/dialects/pg/view/index.d.ts +4 -0
  36. package/dist/dialects/pg/view/index.js +21 -178
  37. package/dist/dialects/pg/view/refresh.d.ts +24 -0
  38. package/dist/runtime/batch.d.ts +1 -1
  39. package/dist/runtime/client.d.ts +6 -1
  40. package/dist/runtime/conflict.d.ts +2 -1
  41. package/dist/runtime/drift.d.ts +9 -0
  42. package/dist/runtime/inflight.d.ts +14 -0
  43. package/dist/runtime/pg/bun.js +6 -5
  44. package/dist/runtime/pg/pg.js +5 -5
  45. package/dist/runtime/pg/pglite.js +6 -5
  46. package/dist/runtime/pg/postgresjs.js +5 -5
  47. package/dist/runtime/plan.d.ts +22 -1
  48. package/dist/runtime/predicate.d.ts +19 -0
  49. package/dist/runtime/safety/index.js +1 -1
  50. package/dist/runtime/tenancy/composite.d.ts +26 -0
  51. package/dist/runtime/tenancy/context.d.ts +16 -0
  52. package/dist/runtime/tenancy/index.d.ts +9 -5
  53. package/dist/runtime/tenancy/index.js +1508 -37
  54. package/dist/runtime/tenancy/path.d.ts +87 -0
  55. package/dist/runtime/tenancy/rls.d.ts +67 -0
  56. package/dist/runtime/tenancy/via.d.ts +22 -0
  57. package/dist/runtime/traits/index.js +17 -11
  58. package/dist/runtime/types.d.ts +13 -1
  59. package/dist/runtime/validate/index.js +4 -4
  60. package/dist/runtime/write.d.ts +1 -1
  61. package/dist/shared/09p049xa.js +702 -0
  62. package/dist/shared/{ykv7wh5b.js → 1f0xa3cb.js} +5 -5
  63. package/dist/shared/1nw1f3vs.js +23 -0
  64. package/dist/shared/{6p5k20sm.js → 1z04fa85.js} +21 -11
  65. package/dist/shared/{p7pfx624.js → 2cg3wnxc.js} +314 -65
  66. package/dist/shared/{dyj86cmj.js → 2jd1sn0e.js} +2 -2
  67. package/dist/shared/{qgvt8nx1.js → 2z7wv5tj.js} +21 -5
  68. package/dist/shared/{kyxb62mc.js → 31vm914h.js} +9 -1
  69. package/dist/shared/{sf8m6tfy.js → 32cnm7kn.js} +8 -3
  70. package/dist/shared/{t4tbsy9h.js → 358zsv4r.js} +1 -1
  71. package/dist/shared/{jdgqqx31.js → 3fqwmr09.js} +38 -14
  72. package/dist/shared/{922qbgnw.js → 3qnhc7w5.js} +16 -9
  73. package/dist/shared/{zvbjc949.js → 4az561fn.js} +1 -1
  74. package/dist/shared/{k3ted12y.js → 54tqkxja.js} +20 -2
  75. package/dist/shared/{k7cc9nzk.js → 5d8t84hg.js} +15 -3
  76. package/dist/shared/{fa9vt245.js → 5j36z8dn.js} +17 -6
  77. package/dist/shared/{1g0drakv.js → 5yv4k6a6.js} +1 -1
  78. package/dist/shared/8dqhth8c.js +12 -0
  79. package/dist/shared/8etn7d8w.js +62 -0
  80. package/dist/shared/{g516zdsb.js → 8x72s86k.js} +476 -30
  81. package/dist/shared/{nkc9sfqb.js → 929h19xp.js} +145 -7
  82. package/dist/shared/{gjthds9j.js → 9628a9xt.js} +1 -1
  83. package/dist/shared/{8xsbagad.js → 9s0ypp87.js} +88 -7
  84. package/dist/shared/{b066g80h.js → ag4c7516.js} +2 -2
  85. package/dist/shared/b3awxn7h.js +45 -0
  86. package/dist/shared/b4v57v3x.js +9 -0
  87. package/dist/shared/bdk4sty6.js +58 -0
  88. package/dist/shared/{s6gqfn5j.js → c58secgj.js} +125 -8
  89. package/dist/shared/{rrstp3kb.js → cgbamx3e.js} +2 -2
  90. package/dist/shared/d5azex6s.js +6 -0
  91. package/dist/shared/{5fqx9yh4.js → dk8v79p3.js} +3 -1
  92. package/dist/shared/{4m9fn52z.js → ed0b8k4s.js} +1 -1
  93. package/dist/shared/{z2rnngj4.js → ek2wt2dz.js} +8 -5
  94. package/dist/shared/{wxn4b7pp.js → f0f1n281.js} +59 -12
  95. package/dist/shared/{gdh9mrtp.js → g90m9b0g.js} +3 -2
  96. package/dist/shared/{tbszx8mz.js → gwn6v667.js} +2 -2
  97. package/dist/shared/{c8aghh05.js → h15jx9bc.js} +8 -2
  98. package/dist/shared/{3p4b1fg0.js → hazqqecq.js} +17 -93
  99. package/dist/shared/{4ef7mmqf.js → j3rkk835.js} +34 -11
  100. package/dist/shared/{bt9sfq0z.js → j60w0dvr.js} +56 -21
  101. package/dist/shared/{ycatkxmq.js → jgqj11w4.js} +1 -1
  102. package/dist/shared/k68eedmy.js +85 -0
  103. package/dist/shared/{szm0cksa.js → knmes2kh.js} +34 -6
  104. package/dist/shared/{ckjbyrt9.js → mb2ycs1p.js} +7 -9
  105. package/dist/shared/{tq9afp8d.js → n076ydsc.js} +1 -1
  106. package/dist/shared/{xhh1srw3.js → nx9hh281.js} +50 -32
  107. package/dist/shared/{kdkxh3kb.js → p248q79y.js} +58 -59
  108. package/dist/shared/{990a291j.js → p6bjzq9q.js} +14 -9
  109. package/dist/shared/{h4nqvjkp.js → pjr6xrm5.js} +2 -2
  110. package/dist/shared/pz6rj8ay.js +212 -0
  111. package/dist/shared/{5vdm4sgp.js → q3j1tq7a.js} +36 -23
  112. package/dist/shared/{8bwbwy8h.js → qq3899ht.js} +128 -18
  113. package/dist/shared/{dxjp14rk.js → r11n8grd.js} +2 -2
  114. package/dist/shared/r1fdd0f6.js +19 -0
  115. package/dist/shared/{8ktwd3rh.js → r3j8s5y0.js} +1 -1
  116. package/dist/shared/rhs3xe4k.js +7 -0
  117. package/dist/shared/{jsd07prz.js → rpycarcr.js} +6 -6
  118. package/dist/shared/{xsq7v6wp.js → sbdme3vy.js} +14 -2
  119. package/dist/shared/{xdpj5kkm.js → t3g0k88x.js} +775 -264
  120. package/dist/shared/{ayz64vzj.js → ttmg09vd.js} +708 -19
  121. package/dist/shared/{r6pj4fq2.js → venqrjna.js} +8 -4
  122. package/dist/shared/{fancm5pt.js → vjpnzwjz.js} +138 -15
  123. package/dist/shared/{9tgmr1gh.js → vmfkhszw.js} +44 -9
  124. package/dist/shared/{wqem4jed.js → wkme6tep.js} +11 -9
  125. package/dist/shared/{qy9rdq0g.js → wt5rzv1h.js} +15 -11
  126. package/dist/shared/wxr8cwns.js +6 -0
  127. package/dist/shared/{bzsjtd7c.js → wynzeggs.js} +19 -14
  128. package/dist/shared/x3jc53kg.js +220 -0
  129. package/dist/shared/{7p2wbz7f.js → x72jb7ad.js} +1 -1
  130. package/dist/shared/{fyqkk1m0.js → xq0mvhts.js} +10 -9
  131. package/dist/shared/xqve9598.js +53 -0
  132. package/dist/shared/{47xzb9fc.js → y2t3jktd.js} +2 -2
  133. package/dist/shared/{9x25fxhc.js → y34a8714.js} +1 -1
  134. package/dist/shared/{k7dwkhvq.js → y6jdds7g.js} +4 -1
  135. package/dist/shared/{h8ne16fq.js → ys0xzyaj.js} +1 -1
  136. package/dist/shared/{c9z5g0fm.js → ywr8hmey.js} +5 -2
  137. package/dist/shared/{g7ez901n.js → ywspezzx.js} +1 -1
  138. package/dist/shared/{9m80mkac.js → ze4hxsmy.js} +1 -1
  139. package/dist/shared/{fb00y8fp.js → zm0mafk4.js} +12 -6
  140. package/dist/shared/{y005b761.js → zy8r7qvz.js} +6 -2
  141. package/dist/tooling/cli.js +97 -24
  142. package/dist/tooling/migrate/apply.d.ts +14 -0
  143. package/dist/tooling/migrate/check.d.ts +3 -4
  144. package/dist/tooling/migrate/classify.d.ts +4 -0
  145. package/dist/tooling/migrate/commands.d.ts +1 -2
  146. package/dist/tooling/migrate/config.d.ts +3 -2
  147. package/dist/tooling/migrate/drift.d.ts +1 -1
  148. package/dist/tooling/migrate/estimate.d.ts +14 -1
  149. package/dist/tooling/migrate/files.d.ts +11 -1
  150. package/dist/tooling/migrate/index.js +17 -16
  151. package/dist/tooling/migrate/lint.d.ts +4 -4
  152. package/dist/tooling/migrate/lost.d.ts +28 -0
  153. package/dist/tooling/migrate/paths.d.ts +14 -0
  154. package/dist/tooling/migrate/plan.d.ts +10 -3
  155. package/dist/tooling/migrate/policy.d.ts +11 -0
  156. package/dist/tooling/migrate/runner.d.ts +25 -0
  157. package/dist/tooling/migrate/sandbox.d.ts +57 -0
  158. package/dist/tooling/migrate/sql-lex.d.ts +39 -0
  159. package/dist/tooling/migrate/text.d.ts +42 -0
  160. package/dist/tooling/node-version.d.ts +21 -0
  161. package/dist/tooling/testing/factories.d.ts +10 -0
  162. package/dist/tooling/testing/facts.d.ts +33 -5
  163. package/dist/tooling/testing/index.d.ts +1 -1
  164. package/dist/tooling/testing/index.js +3 -3
  165. package/dist/tooling/testing/isolation.d.ts +15 -8
  166. package/package.json +10 -6
  167. package/dist/shared/0v1g424p.js +0 -26
  168. package/dist/shared/r73pt594.js +0 -426
  169. package/dist/shared/rasvvzgz.js +0 -21
@@ -1,10 +1,13 @@
1
1
  import {
2
2
  functionObject,
3
3
  triggerObject
4
- } from "../../../shared/9x25fxhc.js";
4
+ } from "../../../shared/y34a8714.js";
5
5
  import {
6
6
  staticNamespace2
7
- } from "../../../shared/t4tbsy9h.js";
7
+ } from "../../../shared/358zsv4r.js";
8
+ import {
9
+ assertIdentifier2
10
+ } from "../../../shared/nfzc5z0w.js";
8
11
  import {
9
12
  OkmError2
10
13
  } from "../../../shared/52szzakm.js";
@@ -26,6 +29,11 @@ function fn(name, options) {
26
29
  fix: { summary: "Set search_path on the function to the schemas it is allowed to see." }
27
30
  });
28
31
  }
32
+ if (options.searchPath !== undefined && options.searchPath.trim().length > 0) {
33
+ for (const part of options.searchPath.split(",")) {
34
+ assertIdentifier2(part.trim(), `function ${name} search_path`);
35
+ }
36
+ }
29
37
  const argTypes = (options.arguments ?? []).map((argument) => argument.type);
30
38
  const schema = options.schema ?? "public";
31
39
  return {
@@ -5,7 +5,8 @@
5
5
  * Each builder is a separate export so a bundle can keep only the ones it calls.
6
6
  */
7
7
  export { schema, type BuiltSchema, type SchemaCodecs, type SchemaInput, type SchemaRequires, } from "./schema.js";
8
- export { index, sql, table, type AnyTable, type ColumnHandle, type IndexCall, type SqlText, type SqlValue, type Table, type TableOptions, } from "./table.js";
8
+ export { withOwner, type DeclaredOwner } from "./owner.js";
9
+ export { index, sql, table, type AnyTable, type ColumnHandle, type IndexCall, type SqlText, type SqlValue, type FilterOperator, type Table, type TableOptions, } from "./table.js";
9
10
  export type { PresetQuery } from "./preset.js";
10
11
  export { custom } from "./custom.js";
11
12
  export { ColumnBuilder } from "./column.js";
@@ -2,7 +2,10 @@ import {
2
2
  jsonReplacer2,
3
3
  json2,
4
4
  jsonb2
5
- } from "../../shared/gjthds9j.js";
5
+ } from "../../shared/9628a9xt.js";
6
+ import {
7
+ withOwner2
8
+ } from "../../shared/xqve9598.js";
6
9
  import {
7
10
  encodeInstant,
8
11
  decodeInstant,
@@ -14,25 +17,22 @@ import {
14
17
  time2,
15
18
  timetz2,
16
19
  interval2
17
- } from "../../shared/1g0drakv.js";
20
+ } from "../../shared/5yv4k6a6.js";
18
21
  import {
19
- one2,
20
- many2,
21
- manyThrough2,
22
22
  schema2
23
- } from "../../shared/3p4b1fg0.js";
23
+ } from "../../shared/hazqqecq.js";
24
24
  import {
25
25
  domainType
26
- } from "../../shared/g7ez901n.js";
26
+ } from "../../shared/ywspezzx.js";
27
+ import {
28
+ one2,
29
+ many2,
30
+ manyThrough2
31
+ } from "../../shared/k68eedmy.js";
27
32
  import {
28
33
  id2,
29
34
  uuid2
30
- } from "../../shared/dyj86cmj.js";
31
- import {
32
- table2,
33
- index2,
34
- sql2
35
- } from "../../shared/jsd07prz.js";
35
+ } from "../../shared/2jd1sn0e.js";
36
36
  import {
37
37
  text2,
38
38
  varchar2,
@@ -40,13 +40,18 @@ import {
40
40
  citext2,
41
41
  encodeText,
42
42
  decodeText
43
- } from "../../shared/4m9fn52z.js";
43
+ } from "../../shared/ed0b8k4s.js";
44
+ import {
45
+ table2,
46
+ index2,
47
+ sql2
48
+ } from "../../shared/rpycarcr.js";
44
49
  import {
45
50
  ColumnBuilder2,
46
51
  required,
47
52
  openColumn,
48
53
  formatType
49
- } from "../../shared/c9z5g0fm.js";
54
+ } from "../../shared/ywr8hmey.js";
50
55
  import {
51
56
  definition,
52
57
  rejected
@@ -54,11 +59,12 @@ import {
54
59
  import {
55
60
  sameNamespace2,
56
61
  compareText
57
- } from "../../shared/t4tbsy9h.js";
62
+ } from "../../shared/358zsv4r.js";
58
63
  import {
59
64
  tag
60
65
  } from "../../shared/cn0n68ft.js";
61
66
  import {
67
+ OkmError2,
62
68
  catalogError2
63
69
  } from "../../shared/52szzakm.js";
64
70
  var __defProp = Object.defineProperty;
@@ -909,6 +915,11 @@ function notIn(values) {
909
915
  }
910
916
  // src/dialects/pg/ops/or.ts
911
917
  function or(branches) {
918
+ if (arguments.length !== 1 || !Array.isArray(branches)) {
919
+ throw new OkmError2("OKM1121", "Pass an array: or([a, b]).", {
920
+ fix: { summary: "Pass an array: or([a, b])." }
921
+ });
922
+ }
912
923
  return tag("or", branches);
913
924
  }
914
925
  // src/dialects/pg/ops/overlaps.ts
@@ -998,5 +1009,6 @@ export {
998
1009
  tstzrange,
999
1010
  tsvector,
1000
1011
  uuid2 as uuid,
1001
- varchar2 as varchar
1012
+ varchar2 as varchar,
1013
+ withOwner2 as withOwner
1002
1014
  };
@@ -15,6 +15,13 @@ export type IntrospectOptions = {
15
15
  * When omitted, roles, grants, and default privileges are not read.
16
16
  */
17
17
  readonly managedRoles?: readonly string[];
18
+ /**
19
+ * Read-only role.
20
+ *
21
+ * Schema `USAGE` is read for this role only. Other roles keep the schema
22
+ * privileges they already have.
23
+ */
24
+ readonly reportingRole?: string;
18
25
  };
19
26
  /** A connection that can run one query. Parameters are `$1`, `$2`, … */
20
27
  export type CatalogQuery = {
@@ -21,8 +21,13 @@ export type ColumnModel = {
21
21
  * call a codec it does not need.
22
22
  */
23
23
  readonly decode: ((wire: string) => unknown) | undefined;
24
- /** Excluded from a default select. Named selects still return it. Includes never return it. */
25
- readonly hidden: boolean;
24
+ /**
25
+ * Excluded from a default select. Named selects still return it. Includes never return it.
26
+ *
27
+ * `"filter"` is still excluded, and `where` and `orderBy` may name it.
28
+ * `true` is excluded and refused there (OKM1120).
29
+ */
30
+ readonly hidden: boolean | "filter";
26
31
  /**
27
32
  * Redacted in logs, errors, and `inspect()`.
28
33
  *
@@ -3,7 +3,7 @@ import { type Or } from "../operators.js";
3
3
  * Disjunction of where objects.
4
4
  *
5
5
  * @typeParam V - One branch
6
- * @param branches - Where objects, or-ed
6
+ * @param branches - Where objects, or-ed. One array: `or([a, b])`
7
7
  * @returns A tagged operator
8
8
  */
9
9
  export declare function or<const V>(branches: readonly V[]): Or<V>;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Table ownership marks (`external` and `ignored`).
3
+ *
4
+ * The mark lives on the table value, not in `table()` options. `schema()` does
5
+ * not read it, so a featureless app does not load this module. Tooling stamps
6
+ * the catalog before it plans.
7
+ */
8
+ import type { Catalog, Owner } from "../../contracts/catalog/types.js";
9
+ import type { AnyTable } from "./table.js";
10
+ /** Owners a declaration may set. `managed` stays the default. */
11
+ export type DeclaredOwner = Exclude<Owner, "managed">;
12
+ /**
13
+ * Marks a table `external` or `ignored`.
14
+ *
15
+ * `external` is known and queryable, and a plan never creates, alters, or
16
+ * drops it. `ignored` is invisible to diffs, plans, and `okm check`. The
17
+ * table stays on the schema, so the client can still name it.
18
+ *
19
+ * @param table - A `table()` value
20
+ * @param owner - `external` or `ignored`
21
+ * @returns The same table
22
+ */
23
+ export declare function withOwner<T extends AnyTable>(table: T, owner: DeclaredOwner): T;
24
+ /**
25
+ * Reads the mark {@link withOwner} stored.
26
+ *
27
+ * @param table - A table value
28
+ * @returns The mark, or `undefined` when the table is managed
29
+ */
30
+ export declare function tableOwner(table: object): DeclaredOwner | undefined;
31
+ /**
32
+ * Copies `owner` onto a table and the objects that belong to it.
33
+ *
34
+ * Columns, indexes, constraints, triggers, policies, and sequences that
35
+ * depend on the table follow the table. A catalog with no marks is returned
36
+ * as itself.
37
+ *
38
+ * @param source - Catalog from `schema()`
39
+ * @param marks - SQL table name to owner
40
+ * @returns The catalog planning and drift should read
41
+ */
42
+ export declare function stampOwnership(source: Catalog, marks: ReadonlyMap<string, DeclaredOwner>): Catalog;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Marks the table a path ends on.
3
+ *
4
+ * The symbol lives here so a column-only app and a featureless app do not
5
+ * load it. `archivable()` reads it. Compile hashes named fields, so the mark
6
+ * stays out of the catalog.
7
+ */
8
+ /**
9
+ * Property set on the tenant table a path ends on.
10
+ *
11
+ * An options spread does not copy it. The rewrite sets it on the table object.
12
+ */
13
+ export declare const pathEndpoint: unique symbol;
14
+ /**
15
+ * Records that `table` is the end of a path.
16
+ *
17
+ * @param table - The rewritten tenant table
18
+ * @returns The same table, marked
19
+ */
20
+ export declare function markPathEndpoint<T extends object>(table: T): T;
@@ -23,6 +23,9 @@ export declare function assertRoleHealth(runner: Runner, roles: RolesInput, obje
23
23
  /**
24
24
  * Refuses to apply role changes when the acting role lacks `CREATEROLE`.
25
25
  *
26
+ * A superuser can create roles when `rolcreaterole` is false, so `rolsuper`
27
+ * satisfies the same check.
28
+ *
26
29
  * @param runner - Database connection
27
30
  * @param role - The role that will run `CREATE ROLE` or `ALTER ROLE`
28
31
  */
@@ -1,11 +1,12 @@
1
1
  /**
2
- * `roles: { migration, app }`.
2
+ * `roles: { migration, app, reporting? }`.
3
3
  *
4
4
  * The object contributes role, grant, and default-privilege records. `schema()`
5
5
  * does not import this module. Tooling calls {@link attachRoles} when the
6
6
  * config names roles.
7
7
  */
8
8
  import type { Catalog, CatalogObject } from "../../../contracts/catalog/types.js";
9
+ import { OkmError } from "../../../contracts/error.js";
9
10
  /** A role the plan creates and alters. It is never dropped. */
10
11
  export type ManagedRole = {
11
12
  readonly name: string;
@@ -14,37 +15,101 @@ export type ManagedRole = {
14
15
  /** `INHERIT` when not `false`. */
15
16
  readonly inherit?: boolean;
16
17
  };
18
+ /**
19
+ * One column grant.
20
+ *
21
+ * `on` is the table, view, or materialized view name. `select` and `update`
22
+ * name columns. A hidden column is included only when it is listed here.
23
+ */
24
+ export type ColumnGrantInput = {
25
+ /** Role that receives the privilege. */
26
+ readonly role: string;
27
+ /** Table, view, or materialized view. */
28
+ readonly on: string;
29
+ /** Columns granted `SELECT`. */
30
+ readonly select?: readonly string[];
31
+ /** Columns granted `UPDATE`. */
32
+ readonly update?: readonly string[];
33
+ };
34
+ /**
35
+ * Why a `roles` declaration was refused.
36
+ *
37
+ * `reporting_distinct` means the reporting role is the migration or
38
+ * application role. `reporting_write` means that role was granted a write.
39
+ */
40
+ export type RoleDeclarationReason = "reporting_distinct" | "reporting_write";
41
+ /**
42
+ * A `roles` declaration this version refuses.
43
+ *
44
+ * The kind stays `invalid`. No new OKM code: OKM1825 is a missing privilege
45
+ * at doctor time, and OKM1060 is a column definition.
46
+ */
47
+ export declare class RoleDeclarationError extends OkmError {
48
+ /** Which rule refused the declaration. */
49
+ readonly reason: RoleDeclarationReason;
50
+ /**
51
+ * @param reason - Which rule failed
52
+ * @param message - What the author should change
53
+ */
54
+ constructor(reason: RoleDeclarationReason, message: string);
55
+ }
56
+ /**
57
+ * Columns of one table, for the reporting role's hidden-column rule.
58
+ *
59
+ * `hidden` is true for `.hidden()` and for `hidden({ filterable: true })`.
60
+ */
61
+ export type TableExposure = {
62
+ /** SQL table name. */
63
+ readonly table: string;
64
+ /** Field name and SQL name. `hidden` is left out of the reporting role. */
65
+ readonly columns: readonly {
66
+ readonly field: string;
67
+ readonly sql: string;
68
+ readonly hidden: boolean;
69
+ }[];
70
+ };
17
71
  /**
18
72
  * Roles named by `defineConfig`.
19
73
  *
20
74
  * A string is external: it must already exist. A name listed in `managed` is
21
- * created when `pg_roles` does not have it.
75
+ * created when `pg_roles` does not have it. `reporting` receives select only.
22
76
  */
23
77
  export type RolesInput = {
24
78
  /** Role that runs migrations. */
25
79
  readonly migration: string;
26
80
  /** Role the application connects as. */
27
81
  readonly app: string;
82
+ /**
83
+ * Read-only role.
84
+ *
85
+ * Select on tables, views, and materialized views, usage on schemas, and
86
+ * default privileges for later tables. No write and no execute.
87
+ */
88
+ readonly reporting?: string;
28
89
  /** Roles this catalog creates and alters. */
29
90
  readonly managed?: readonly ManagedRole[];
91
+ /** Column `SELECT` or `UPDATE` grants. */
92
+ readonly columns?: readonly ColumnGrantInput[];
30
93
  };
31
94
  /**
32
95
  * Adds role, grant, and default-privilege objects to a catalog.
33
96
  *
34
97
  * Grants and default privileges are for {@link RolesInput.app} on every
35
- * managed table, view, materialized view, sequence, and function. Fine-grained
36
- * grants are not accepted here.
98
+ * managed table, view, materialized view, sequence, and function. Column
99
+ * grants and the reporting role are added when `input` names them.
37
100
  *
38
101
  * @param source - Catalog from `schema()`
39
102
  * @param input - Migration role, application role, and managed roles
103
+ * @param exposure - Hidden columns. Omitted when no table hides one
40
104
  * @returns A catalog that includes those objects
41
105
  */
42
- export declare function attachRoles(source: Catalog, input: RolesInput): Catalog;
106
+ export declare function attachRoles(source: Catalog, input: RolesInput, exposure?: readonly TableExposure[]): Catalog;
43
107
  /**
44
108
  * Catalog records for one `roles` configuration.
45
109
  *
46
110
  * @param input - Migration role, application role, and managed roles
47
111
  * @param built - Objects already in the catalog
112
+ * @param exposure - Hidden columns. Omitted when no table hides one
48
113
  * @returns Role, grant, and default-privilege records
49
114
  */
50
- export declare function contributeRoles(input: RolesInput, built: readonly CatalogObject[]): readonly CatalogObject[];
115
+ export declare function contributeRoles(input: RolesInput, built: readonly CatalogObject[], exposure?: readonly TableExposure[]): readonly CatalogObject[];
@@ -19,6 +19,17 @@ export type PrivilegeStatement = {
19
19
  readonly sql: string;
20
20
  readonly kind: "revoke" | "revoke-default" | "create-role" | "alter-role" | "grant-default" | "grant";
21
21
  };
22
+ /**
23
+ * Privilege name for a column grant.
24
+ *
25
+ * Columns are sorted so the identity is stable. The catalog stores this
26
+ * string. It does not grow the grant type.
27
+ *
28
+ * @param name - `SELECT` or `UPDATE`
29
+ * @param columns - SQL column names
30
+ * @returns `SELECT(email,name)`
31
+ */
32
+ export declare function columnPrivilege(name: string, columns: readonly string[]): string;
22
33
  /**
23
34
  * Diffs role, grant, and default-privilege objects into SQL.
24
35
  *
@@ -114,6 +114,8 @@ export type TenancySchema<S, TTables extends readonly AnyTable[], TTenancy> = TT
114
114
  readonly "~byName": {
115
115
  readonly [T in TTables[number] as T["~name"]]: T extends {
116
116
  readonly "~global": string;
117
+ } ? T : T extends {
118
+ readonly "~path": string;
117
119
  } ? T : T & {
118
120
  readonly "~row": RowFrom<TenantFields<TTenancy["key"]>>;
119
121
  readonly "~insert": InsertFrom<TenantFields<TTenancy["key"]>>;
@@ -154,7 +154,8 @@ export type Table<TName extends string, TColumns, TRelations extends Readonly<Re
154
154
  * Allowlisted client filters.
155
155
  *
156
156
  * A hidden field in `allow`, `sort`, or `relations` throws OKM1123 here.
157
- * `parse` loads the parser on first use.
157
+ * `parse` loads the parser on first use. An operator outside
158
+ * {@link FilterOperator} is not assignable.
158
159
  *
159
160
  * @param spec - Fields, operators, and sorts the caller may send
160
161
  * @returns A parser for one request object
@@ -163,10 +164,23 @@ export type Table<TName extends string, TColumns, TRelations extends Readonly<Re
163
164
  parse(query: unknown): Promise<ParsedFilters>;
164
165
  };
165
166
  };
167
+ /**
168
+ * Operators `filters().parse` applies.
169
+ *
170
+ * `path`, `matches`, `similar`, `wordSimilar`, boolean trees, relation
171
+ * quantifiers, and write operators are not in this list. A caller cannot
172
+ * name them in `allow`.
173
+ */
174
+ export type FilterOperator = "eq" | "in" | "inList" | "notIn" | "lt" | "lte" | "gt" | "gte" | "between" | "startsWith" | "endsWith" | "contains" | "like" | "ilike" | "not" | "containedBy" | "overlaps" | "hasKey" | "hasAnyKey";
166
175
  /** What {@link Table.filters} accepts. */
167
176
  export type FilterSpec = {
168
- readonly allow?: Readonly<Record<string, readonly string[]>>;
177
+ readonly allow?: Readonly<Record<string, readonly FilterOperator[]>>;
169
178
  readonly sort?: readonly string[];
179
+ /**
180
+ * Relation name to the fields a caller may filter.
181
+ *
182
+ * `parse` turns `{ list: { name: { startsWith: "a" } } }` into `has`.
183
+ */
170
184
  readonly relations?: Readonly<Record<string, readonly string[]>>;
171
185
  };
172
186
  /** `where` and `orderBy` a caller can spread into `find`. */
@@ -266,7 +280,7 @@ type PrimaryOf<TOptions> = TOptions extends {
266
280
  * @typeParam TColumns - Column builders
267
281
  * @typeParam TOptions - Options argument
268
282
  */
269
- type DeclaredTable<TName extends string, TColumns, TOptions> = Table<TName, TColumns, RelationsOf<TOptions>, PrimaryOf<TOptions>, NoInfer<TOptions>> & TraitShapes<TColumns, TOptions> & ArchiveFlag<TOptions> & PresetFlag<TOptions> & OmitFlag<TOptions> & GlobalFlag<TOptions>;
283
+ type DeclaredTable<TName extends string, TColumns, TOptions> = Table<TName, TColumns, RelationsOf<TOptions>, PrimaryOf<TOptions>, NoInfer<TOptions>> & TraitShapes<TColumns, TOptions> & ArchiveFlag<TOptions> & PresetFlag<TOptions> & OmitFlag<TOptions> & GlobalFlag<TOptions> & PathFlag<TOptions>;
270
284
  type ArchiveFlag<TOptions> = TOptions extends {
271
285
  readonly traits: infer TTraits;
272
286
  } ? HasArchive<TTraits> : unknown;
@@ -291,6 +305,14 @@ type GlobalFlag<TOptions> = TOptions extends {
291
305
  } ? {
292
306
  readonly "~global": TReason;
293
307
  } : unknown;
308
+ /** A path table keeps its own columns. The schema type does not add the tenant key. */
309
+ type PathFlag<TOptions> = TOptions extends {
310
+ readonly tenancy: {
311
+ readonly via: infer TVia extends string;
312
+ };
313
+ } ? {
314
+ readonly "~path": TVia;
315
+ } : unknown;
294
316
  type TraitShapes<TColumns, TOptions> = TOptions extends {
295
317
  readonly traits: infer TTraits;
296
318
  } ? TTraits extends readonly {
@@ -114,100 +114,29 @@ export declare function decodeDuration(wire: string): Temporal.Duration;
114
114
  */
115
115
  export declare function intervalFields(fields: string): string;
116
116
  /**
117
- * Temporal is in the runtime (Node and Bun) and not in this repository's
118
- * TypeScript libs. These declarations name the methods the codecs call.
117
+ * Empty names so `Temporal.Instant` and the other codec types merge with
118
+ * TypeScript's Temporal lib and with `temporal-polyfill`. Method signatures
119
+ * live in `temporal.local.d.ts` for this repository's typecheck only. They
120
+ * are not shipped: a second `toString` or `var Temporal` conflicts with those
121
+ * libraries (QA-L2).
119
122
  */
120
123
  declare global {
121
124
  /** Instant, plain date-time, date, time, and duration. */
122
125
  namespace Temporal {
123
126
  /** UTC instant. */
124
127
  interface Instant {
125
- /**
126
- * @param options - Fractional digits to keep
127
- * @returns ISO-8601 text
128
- */
129
- toString(options?: {
130
- readonly fractionalSecondDigits?: number;
131
- }): string;
132
128
  }
133
129
  /** Date and time without a zone. */
134
130
  interface PlainDateTime {
135
- /**
136
- * @param options - Fractional digits to keep
137
- * @returns ISO-8601 text
138
- */
139
- toString(options?: {
140
- readonly fractionalSecondDigits?: number;
141
- }): string;
142
131
  }
143
132
  /** Calendar date. */
144
133
  interface PlainDate {
145
- /**
146
- * @returns `YYYY-MM-DD`
147
- */
148
- toString(): string;
149
- /**
150
- * @param duration - Days to add
151
- * @returns A later or earlier date
152
- */
153
- add(duration: {
154
- readonly days: number;
155
- }): Temporal.PlainDate;
156
134
  }
157
135
  /** Time of day. */
158
136
  interface PlainTime {
159
- /**
160
- * @param options - Fractional digits to keep
161
- * @returns Time text
162
- */
163
- toString(options?: {
164
- readonly fractionalSecondDigits?: number;
165
- }): string;
166
137
  }
167
138
  /** ISO-8601 duration. */
168
139
  interface Duration {
169
- /**
170
- * @returns ISO-8601 duration text
171
- */
172
- toString(): string;
173
140
  }
174
141
  }
175
- /** Temporal constructors used by the codecs. */
176
- var Temporal: {
177
- readonly Instant: {
178
- /**
179
- * @param item - ISO-8601 instant
180
- * @returns An instant
181
- */
182
- from(item: string): Temporal.Instant;
183
- };
184
- readonly PlainDateTime: {
185
- /**
186
- * @param item - ISO-8601 date-time
187
- * @returns A plain date-time
188
- */
189
- from(item: string): Temporal.PlainDateTime;
190
- };
191
- readonly PlainDate: {
192
- /**
193
- * @param item - `YYYY-MM-DD`
194
- * @returns A date
195
- */
196
- from(item: string): Temporal.PlainDate;
197
- };
198
- readonly PlainTime: {
199
- /**
200
- * @param item - Time text
201
- * @returns A time
202
- */
203
- from(item: string): Temporal.PlainTime;
204
- };
205
- readonly Duration: {
206
- /**
207
- * @param item - ISO-8601 duration, or numeric fields
208
- * @returns A duration
209
- */
210
- from(item: string | Readonly<Record<string, number>>): Temporal.Duration;
211
- };
212
- };
213
142
  }
@@ -16,9 +16,15 @@ import type { AnyTable } from "./table.js";
16
16
  export type TenantFields<Key extends string> = {
17
17
  readonly [K in Key]: ColumnBuilder<string, FlagTrue<PlainFlags, "guarded">>;
18
18
  };
19
- /** One tenant value, or a reason for leaving the scope. The value is not a cache key. */
19
+ /**
20
+ * One tenant value, or a reason for leaving the scope. The value is not a cache key.
21
+ *
22
+ * `values` is set when the tenant is several keys. `value` stays the first key,
23
+ * which is what a single-key scope already stored.
24
+ */
20
25
  export type TenantCall = {
21
26
  readonly value: string;
27
+ readonly values?: Readonly<Record<string, string>>;
22
28
  } | {
23
29
  readonly unscoped: string;
24
30
  };
@@ -49,10 +55,45 @@ export type TenancyRule = {
49
55
  */
50
56
  export type ColumnTenancy = {
51
57
  readonly key: string;
58
+ /** Every tenant field, in declaration order. One entry for column tenancy. */
59
+ readonly keys?: readonly string[];
52
60
  readonly type: "uuid";
53
- readonly strategy: "column";
61
+ readonly strategy: string;
54
62
  /** Tables with the tenant column, widened uniques, and composite references. */
55
63
  rewrite(tables: readonly AnyTable[]): readonly AnyTable[];
64
+ /** Path table: isolation follows a relation instead of a tenant column. */
65
+ isPath?(table: string): boolean;
66
+ /**
67
+ * Steps from a path table to the tenant table.
68
+ *
69
+ * Absent when `table` has its own tenant column.
70
+ */
71
+ pathOf?(table: string): readonly {
72
+ readonly child: string;
73
+ readonly parent: string;
74
+ readonly localField: string;
75
+ readonly remoteField: string;
76
+ }[] | undefined;
77
+ /**
78
+ * Writes a locking `EXISTS` for an insert into a path table.
79
+ *
80
+ * @returns `false` when `table` is not a path table
81
+ */
82
+ lockPredicate?(input: {
83
+ readonly table: string;
84
+ readonly fieldSql: (field: string) => string | undefined;
85
+ readonly encode: ((value: unknown) => string) | undefined;
86
+ readonly alias: string;
87
+ readonly appended: boolean;
88
+ readonly scope: TenantCall | undefined;
89
+ readonly sink: TenancyText;
90
+ }): boolean;
91
+ /**
92
+ * Checks a path update's new parent in the same statement.
93
+ *
94
+ * A column-tenant table ignores it.
95
+ */
96
+ noteParent?(table: string, set: Readonly<Record<string, unknown>>, sink: TenancyText, scope: TenantCall | undefined, fieldSql: (field: string) => string | undefined): void;
56
97
  /** The guarded uuid column, for emitted row types. */
57
98
  column(): object;
58
99
  predicate(input: {
@@ -95,7 +136,8 @@ export type ColumnTenancy = {
95
136
  /**
96
137
  * Reads `schema({ tenancy })`.
97
138
  *
98
- * Omitted stays `undefined`. Anything else must be `columnTenancy()`.
139
+ * Omitted stays `undefined`. Anything else must carry `rewrite`. The strategy
140
+ * name stays on the object from `okmodel/tenancy`. This file does not list them.
99
141
  *
100
142
  * @param value - The option the caller passed
101
143
  * @returns The tenancy object, or `undefined`