okmodel 0.5.1 → 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.
- package/README.md +100 -1116
- package/dist/adapters/capabilities.d.ts +3 -0
- package/dist/adapters/failure.d.ts +13 -2
- package/dist/adapters/lazy-errors.d.ts +22 -0
- package/dist/adapters/pg/bunsql-extra.d.ts +54 -3
- package/dist/adapters/pg/bunsql.d.ts +14 -1
- package/dist/adapters/pg/cursor.d.ts +17 -0
- package/dist/adapters/pg/no-inflight-timeout.d.ts +20 -0
- package/dist/adapters/pg/nodepostgres-extra.d.ts +22 -1
- package/dist/adapters/pg/nodepostgres.d.ts +12 -0
- package/dist/adapters/pg/pglite-wire.d.ts +24 -0
- package/dist/adapters/pg/pglite.d.ts +10 -0
- package/dist/adapters/pg/postgres-extra.d.ts +41 -3
- package/dist/adapters/pg/reserved.d.ts +42 -0
- package/dist/adapters/pg/session.d.ts +0 -18
- package/dist/contracts/catalog/document.d.ts +19 -5
- package/dist/contracts/catalog/object.d.ts +2 -1
- package/dist/contracts/catalog/policy.d.ts +48 -0
- package/dist/contracts/catalog/types.d.ts +25 -1
- package/dist/contracts/catalog/view.d.ts +2 -0
- package/dist/contracts/connection.d.ts +7 -6
- package/dist/contracts/internal.js +1 -1
- package/dist/contracts/location.d.ts +10 -0
- package/dist/dialects/pg/column.d.ts +13 -2
- package/dist/dialects/pg/ddl.d.ts +37 -1
- package/dist/dialects/pg/emit.d.ts +19 -1
- package/dist/dialects/pg/errors.d.ts +32 -1
- package/dist/dialects/pg/ext/citext.js +1 -1
- package/dist/dialects/pg/ext/index.js +1 -1
- package/dist/dialects/pg/ext/pg-trgm.js +1 -1
- package/dist/dialects/pg/filter-parse.d.ts +5 -4
- package/dist/dialects/pg/floor.d.ts +68 -0
- package/dist/dialects/pg/fn/index.js +2 -2
- package/dist/dialects/pg/index.d.ts +8 -2
- package/dist/dialects/pg/index.js +67 -171
- package/dist/dialects/pg/introspect.d.ts +7 -0
- package/dist/dialects/pg/keys.d.ts +11 -8
- package/dist/dialects/pg/model.d.ts +47 -2
- package/dist/dialects/pg/morph.d.ts +43 -0
- package/dist/dialects/pg/operator-fit.d.ts +2 -2
- package/dist/dialects/pg/operators.d.ts +14 -7
- package/dist/dialects/pg/ops/bucket.d.ts +65 -0
- package/dist/dialects/pg/ops/inc.d.ts +2 -2
- package/dist/dialects/pg/ops/matches.d.ts +9 -12
- package/dist/dialects/pg/ops/relevance.d.ts +13 -0
- package/dist/dialects/pg/ops/search-query.d.ts +22 -0
- package/dist/dialects/pg/ops/window.d.ts +103 -0
- package/dist/dialects/pg/owner.d.ts +42 -0
- package/dist/dialects/pg/path-endpoint.d.ts +20 -0
- package/dist/dialects/pg/preset.d.ts +1 -1
- package/dist/dialects/pg/relations.d.ts +100 -3
- package/dist/dialects/pg/role/check.d.ts +3 -0
- package/dist/dialects/pg/role/index.d.ts +71 -6
- package/dist/dialects/pg/role/sql.d.ts +11 -0
- package/dist/dialects/pg/schema.d.ts +33 -3
- package/dist/dialects/pg/table.d.ts +25 -3
- package/dist/dialects/pg/tenancy.d.ts +45 -3
- package/dist/dialects/pg/view/client.d.ts +19 -1
- package/dist/dialects/pg/view/index.d.ts +4 -0
- package/dist/dialects/pg/view/index.js +21 -178
- package/dist/dialects/pg/view/refresh.d.ts +24 -0
- package/dist/runtime/aggregate.d.ts +8 -2
- package/dist/runtime/batch.d.ts +1 -1
- package/dist/runtime/bucket-group.d.ts +70 -0
- package/dist/runtime/conflict.d.ts +24 -6
- package/dist/runtime/drift.d.ts +9 -0
- package/dist/runtime/include.d.ts +0 -8
- package/dist/runtime/inflight.d.ts +14 -0
- package/dist/runtime/page.d.ts +4 -1
- package/dist/runtime/pg/bun.js +12 -5
- package/dist/runtime/pg/pg.js +5 -5
- package/dist/runtime/pg/pglite.d.ts +1 -0
- package/dist/runtime/pg/pglite.js +9 -8
- package/dist/runtime/pg/postgresjs.js +5 -5
- package/dist/runtime/plan.d.ts +113 -15
- package/dist/runtime/predicate.d.ts +19 -0
- package/dist/runtime/read-stream.d.ts +13 -16
- package/dist/runtime/result-name.d.ts +17 -0
- package/dist/runtime/safety/index.js +1 -1
- package/dist/runtime/server-check.d.ts +3 -3
- package/dist/runtime/sort-bind.d.ts +21 -0
- package/dist/runtime/sort.d.ts +56 -0
- package/dist/runtime/sync.d.ts +70 -0
- package/dist/runtime/tenancy/composite.d.ts +26 -0
- package/dist/runtime/tenancy/context.d.ts +16 -0
- package/dist/runtime/tenancy/index.d.ts +9 -5
- package/dist/runtime/tenancy/index.js +1511 -38
- package/dist/runtime/tenancy/path.d.ts +87 -0
- package/dist/runtime/tenancy/rls.d.ts +67 -0
- package/dist/runtime/tenancy/via.d.ts +22 -0
- package/dist/runtime/text-search.d.ts +20 -0
- package/dist/runtime/traits/index.d.ts +80 -0
- package/dist/runtime/traits/index.js +187 -17
- package/dist/runtime/tree.d.ts +38 -0
- package/dist/runtime/types.d.ts +231 -23
- package/dist/runtime/validate/index.js +4 -4
- package/dist/runtime/version.d.ts +39 -0
- package/dist/runtime/window.d.ts +21 -0
- package/dist/runtime/write.d.ts +55 -3
- package/dist/shared/{krxx77g0.js → 14vx95q4.js} +12 -18
- package/dist/shared/1est4ezg.js +313 -0
- package/dist/shared/{4az561fn.js → 1qa0qajz.js} +8 -23
- package/dist/shared/1w508jbr.js +1611 -0
- package/dist/shared/2c13q96h.js +18 -0
- package/dist/shared/2d3xq4ps.js +9 -0
- package/dist/shared/2shdbn8q.js +23 -0
- package/dist/shared/{t4tbsy9h.js → 358zsv4r.js} +1 -1
- package/dist/shared/3v7t1yrv.js +208 -0
- package/dist/shared/5e3tf63j.js +220 -0
- package/dist/shared/5fe3kbsh.js +10 -0
- package/dist/shared/5v3xf80k.js +37 -0
- package/dist/shared/{51mvgj8w.js → 63qtkrca.js} +130 -3
- package/dist/shared/{gbtcc2d7.js → 67txcrcw.js} +62 -66
- package/dist/shared/79dt02vc.js +12 -0
- package/dist/shared/{v36c5g6p.js → 7k9grvf6.js} +407 -14
- package/dist/shared/7kms6fvj.js +20 -0
- package/dist/shared/7mnme5tn.js +8 -0
- package/dist/shared/7n7ddwt4.js +6 -0
- package/dist/shared/{87jvvhnt.js → 81ebdx9p.js} +48 -68
- package/dist/shared/{szm0cksa.js → 8skf0cam.js} +34 -6
- package/dist/shared/{gjthds9j.js → 9628a9xt.js} +1 -1
- package/dist/shared/9a4ae4p8.js +31 -0
- package/dist/shared/{8xsbagad.js → 9s0ypp87.js} +88 -7
- package/dist/shared/9y3mmmh1.js +436 -0
- package/dist/shared/ab9gmbwy.js +213 -0
- package/dist/shared/{sycnx8gt.js → ax7qv55k.js} +18 -8
- package/dist/shared/{f5cw5q5r.js → aytqh5y2.js} +6 -4
- package/dist/shared/{fancm5pt.js → b5pftnn2.js} +138 -15
- package/dist/shared/{efmy2gcg.js → b76b8d95.js} +2 -2
- package/dist/shared/bdk4sty6.js +58 -0
- package/dist/shared/{32zqc1dw.js → bg3rym3g.js} +63 -32
- package/dist/shared/{0t22wwh1.js → bm22fvzr.js} +16 -14
- package/dist/shared/bz84tkzm.js +342 -0
- package/dist/shared/{31vm914h.js → cm3w0ywp.js} +15 -16
- package/dist/shared/cn957f8d.js +179 -0
- package/dist/shared/{pf0d8f77.js → cnkky55j.js} +25 -14
- package/dist/shared/{5fqx9yh4.js → dk8v79p3.js} +3 -1
- package/dist/shared/{nkaxvt9q.js → dkxgzaqd.js} +224 -19
- package/dist/shared/dzj09cye.js +10 -0
- package/dist/shared/{4m9fn52z.js → ed0b8k4s.js} +1 -1
- package/dist/shared/{z2rnngj4.js → ek2wt2dz.js} +8 -5
- package/dist/shared/exn3sd90.js +359 -0
- package/dist/shared/g6evj1j3.js +66 -0
- package/dist/shared/ga9j92qj.js +25 -0
- package/dist/shared/{qv2ydck9.js → gs74dbj5.js} +651 -76
- package/dist/shared/gzaveazc.js +285 -0
- package/dist/shared/{8rsy3hxz.js → h15jx9bc.js} +1 -1
- package/dist/shared/{dyj86cmj.js → hyh73qk6.js} +3 -3
- package/dist/shared/je4axnm8.js +22 -0
- package/dist/shared/{ycatkxmq.js → jgqj11w4.js} +1 -1
- package/dist/shared/{6qd27mx0.js → jvd0mqag.js} +5 -5
- package/dist/shared/jznfax0y.js +155 -0
- package/dist/shared/{cgbamx3e.js → kb2sdhfb.js} +7 -4
- package/dist/shared/{ps7a6h14.js → kr0jzbw2.js} +12 -8
- package/dist/shared/{janx09m5.js → ktz42yez.js} +20 -8
- package/dist/shared/{gde73es6.js → m6p12kbr.js} +56 -55
- package/dist/shared/{r6nwkxnt.js → m7n9h2a5.js} +4 -4
- package/dist/shared/{672t5vym.js → mss6ctgv.js} +2 -2
- package/dist/shared/{nxdp2ze3.js → mtaszv5a.js} +144 -12
- package/dist/shared/n816peh1.js +45 -0
- package/dist/shared/nbw1z4yd.js +18 -0
- package/dist/shared/{cctw17hj.js → p6qc5gcp.js} +38 -34
- package/dist/shared/{6rk2gen5.js → pbp1ss85.js} +20 -17
- package/dist/shared/pt94kz7f.js +6 -0
- package/dist/shared/{wq9n6pwr.js → pz6rj8ay.js} +82 -9
- package/dist/shared/q5c6ngn3.js +13 -0
- package/dist/shared/{8bwbwy8h.js → qq3899ht.js} +128 -18
- package/dist/shared/{8ktwd3rh.js → r3j8s5y0.js} +1 -1
- package/dist/shared/rhs3xe4k.js +7 -0
- package/dist/shared/{ntneex2a.js → rpycarcr.js} +6 -6
- package/dist/shared/{sdc7r2ww.js → rs73fca9.js} +8 -8
- package/dist/shared/{tm5ab8we.js → sfz1ksn6.js} +7 -4
- package/dist/shared/{6mnp99hx.js → svncdeh5.js} +16 -12
- package/dist/shared/{hf22e0wt.js → tbrvgw6z.js} +62 -42
- package/dist/shared/te5as7z7.js +622 -0
- package/dist/shared/{ga3sfy93.js → tsvww22q.js} +535 -200
- package/dist/shared/{47x8x9ze.js → ttsqgp4p.js} +20 -13
- package/dist/shared/{x72jb7ad.js → v8g1vgv8.js} +1 -1
- package/dist/shared/vb6qfyve.js +152 -0
- package/dist/shared/{r2sp8hrc.js → vczs8gd4.js} +22 -13
- package/dist/shared/{1g0drakv.js → vqrjpcqn.js} +145 -3
- package/dist/shared/{2pqx26h8.js → w72kgg4q.js} +31 -22
- package/dist/shared/{96qcj21c.js → w79hsckn.js} +16 -51
- package/dist/shared/wa07eas9.js +730 -0
- package/dist/shared/{k8v5v5wj.js → x933k9a3.js} +75 -34
- package/dist/shared/{cr74e96e.js → xhfsfkgk.js} +62 -28
- package/dist/shared/xjk7hnn5.js +272 -0
- package/dist/shared/{5enne4ma.js → xnxsed62.js} +66 -113
- package/dist/shared/xqve9598.js +53 -0
- package/dist/shared/{9x25fxhc.js → y34a8714.js} +1 -1
- package/dist/shared/{k7dwkhvq.js → y6jdds7g.js} +4 -1
- package/dist/shared/y6njgyda.js +249 -0
- package/dist/shared/{c9z5g0fm.js → ywr8hmey.js} +5 -2
- package/dist/shared/{g7ez901n.js → ywspezzx.js} +1 -1
- package/dist/shared/z7s6fpxv.js +62 -0
- package/dist/shared/{n7mcc1gk.js → zchaghkf.js} +4 -3
- package/dist/shared/{9m80mkac.js → ze4hxsmy.js} +1 -1
- package/dist/shared/zm0ptry0.js +40 -0
- package/dist/shared/{nsva2a6b.js → znm0bpp5.js} +3 -0
- package/dist/shared/zp9fmend.js +145 -0
- package/dist/shared/{y005b761.js → zy8r7qvz.js} +6 -2
- package/dist/tooling/cli.js +41 -15
- package/dist/tooling/migrate/apply.d.ts +14 -0
- package/dist/tooling/migrate/check.d.ts +3 -4
- package/dist/tooling/migrate/classify.d.ts +4 -0
- package/dist/tooling/migrate/config.d.ts +3 -2
- package/dist/tooling/migrate/engine.d.ts +3 -2
- package/dist/tooling/migrate/files.d.ts +11 -1
- package/dist/tooling/migrate/index.js +14 -14
- package/dist/tooling/migrate/lint.d.ts +1 -1
- package/dist/tooling/migrate/lost.d.ts +28 -0
- package/dist/tooling/migrate/paths.d.ts +14 -0
- package/dist/tooling/migrate/plan.d.ts +6 -3
- package/dist/tooling/migrate/runner.d.ts +25 -0
- package/dist/tooling/migrate/sandbox.d.ts +57 -0
- package/dist/tooling/migrate/sql-lex.d.ts +20 -3
- package/dist/tooling/migrate/text.d.ts +42 -0
- package/dist/tooling/node-version.d.ts +21 -0
- package/dist/tooling/testing/facts.d.ts +33 -5
- package/dist/tooling/testing/index.d.ts +1 -1
- package/dist/tooling/testing/index.js +3 -3
- package/dist/tooling/testing/isolation.d.ts +11 -5
- package/package.json +8 -3
- package/dist/shared/7m88x3e9.js +0 -45
- package/dist/shared/7zwwyh0v.js +0 -448
- package/dist/shared/aj2qxyhj.js +0 -90
- package/dist/shared/caw8b9d8.js +0 -204
- package/dist/shared/jwtxdj06.js +0 -76
- package/dist/shared/kfjp0gck.js +0 -212
- package/dist/shared/q58y17hy.js +0 -21
- package/dist/shared/qm7cdtvr.js +0 -912
- package/dist/shared/r8x5qhd7.js +0 -86
- package/dist/shared/zqrnhwsq.js +0 -31
|
@@ -9,51 +9,75 @@ import {
|
|
|
9
9
|
serializeCatalog2,
|
|
10
10
|
catalogHash2,
|
|
11
11
|
startupCatalog2,
|
|
12
|
+
catalogFileText2,
|
|
12
13
|
parseCatalog2
|
|
13
|
-
} from "./
|
|
14
|
+
} from "./qq3899ht.js";
|
|
14
15
|
import {
|
|
15
16
|
open2
|
|
16
|
-
} from "./
|
|
17
|
+
} from "./14vx95q4.js";
|
|
17
18
|
import {
|
|
19
|
+
sqlStatements,
|
|
20
|
+
transactionControl,
|
|
18
21
|
canonicalTypeName,
|
|
22
|
+
prepareMigrationText,
|
|
23
|
+
assertMigrationSteps,
|
|
24
|
+
columnPrivilege,
|
|
19
25
|
batchSizeField,
|
|
20
26
|
aboutBatchCount,
|
|
21
|
-
sqlStatements,
|
|
22
27
|
parseReplace,
|
|
23
28
|
planMigration,
|
|
24
29
|
formatPlan,
|
|
25
30
|
parsePlan,
|
|
26
31
|
staleRenames,
|
|
32
|
+
sessionFailure,
|
|
33
|
+
finishQuiet,
|
|
27
34
|
assertTargetPolicy,
|
|
28
35
|
assertTargetAlias,
|
|
29
36
|
listTargets,
|
|
30
37
|
selectTarget
|
|
31
|
-
} from "./
|
|
38
|
+
} from "./tsvww22q.js";
|
|
32
39
|
import {
|
|
33
40
|
contributedFields
|
|
34
41
|
} from "./n0dgs0pb.js";
|
|
35
42
|
import {
|
|
36
43
|
assertValidation,
|
|
37
44
|
writeWouldValidate
|
|
38
|
-
} from "./
|
|
45
|
+
} from "./ze4hxsmy.js";
|
|
46
|
+
import {
|
|
47
|
+
tableOwner,
|
|
48
|
+
stampOwnership
|
|
49
|
+
} from "./xqve9598.js";
|
|
50
|
+
import {
|
|
51
|
+
POSTGRES_FLOOR,
|
|
52
|
+
uuidv7NeedsPostgres
|
|
53
|
+
} from "./ga9j92qj.js";
|
|
39
54
|
import {
|
|
40
55
|
catalog,
|
|
41
56
|
isDomain
|
|
42
|
-
} from "./
|
|
57
|
+
} from "./ywspezzx.js";
|
|
43
58
|
import {
|
|
44
59
|
snakeCase,
|
|
45
60
|
emittedTypeName
|
|
46
|
-
} from "./
|
|
61
|
+
} from "./rpycarcr.js";
|
|
47
62
|
import {
|
|
48
63
|
ColumnBuilder2
|
|
49
|
-
} from "./
|
|
64
|
+
} from "./ywr8hmey.js";
|
|
50
65
|
import {
|
|
51
66
|
staticNamespace2
|
|
52
|
-
} from "./
|
|
67
|
+
} from "./358zsv4r.js";
|
|
68
|
+
import {
|
|
69
|
+
sha2562
|
|
70
|
+
} from "./nfzc5z0w.js";
|
|
53
71
|
import {
|
|
54
72
|
OkmError2
|
|
55
73
|
} from "./52szzakm.js";
|
|
56
74
|
|
|
75
|
+
// src/tooling/migrate/paths.ts
|
|
76
|
+
import { resolve } from "node:path";
|
|
77
|
+
function projectPath(cwd, ...parts) {
|
|
78
|
+
return resolve(cwd, ...parts);
|
|
79
|
+
}
|
|
80
|
+
|
|
57
81
|
// src/tooling/errors/registry.ts
|
|
58
82
|
var ERROR_DOCS = [
|
|
59
83
|
{
|
|
@@ -173,8 +197,8 @@ var ERROR_DOCS = [
|
|
|
173
197
|
{
|
|
174
198
|
code: "OKM1110",
|
|
175
199
|
title: "Engine version",
|
|
176
|
-
summary: "
|
|
177
|
-
fix: "Raise
|
|
200
|
+
summary: "A feature needs a newer PostgreSQL than minPostgres allows. The message names the feature, the version it needs, and this schema's minimum.",
|
|
201
|
+
fix: "Raise minPostgres, or stop using the feature. The error names both versions."
|
|
178
202
|
},
|
|
179
203
|
{
|
|
180
204
|
code: "OKM1111",
|
|
@@ -210,7 +234,7 @@ var ERROR_DOCS = [
|
|
|
210
234
|
code: "OKM1124",
|
|
211
235
|
title: "Operator mismatch",
|
|
212
236
|
summary: "An operator does not apply to the column type.",
|
|
213
|
-
fix: "Use an operator the error lists for that column type.
|
|
237
|
+
fix: "Use an operator the error lists for that column type. Search needs a column built for search, not a plain text column."
|
|
214
238
|
},
|
|
215
239
|
{
|
|
216
240
|
code: "OKM1130",
|
|
@@ -494,6 +518,18 @@ var ERROR_DOCS = [
|
|
|
494
518
|
summary: "The last migration's catalog does not match the schema.",
|
|
495
519
|
fix: "Run okm generate."
|
|
496
520
|
},
|
|
521
|
+
{
|
|
522
|
+
code: "OKM1550",
|
|
523
|
+
title: "Row-level security weakened",
|
|
524
|
+
summary: "A statement disables row-level security, drops or widens a policy, grants BYPASSRLS, or changes the owner of a tenant table.",
|
|
525
|
+
fix: "Remove the statement, or allow OKM1550 with a reason when the change is reviewed."
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
code: "OKM1551",
|
|
529
|
+
title: "Transaction control in a step",
|
|
530
|
+
summary: "A step contains COMMIT, ROLLBACK, or other transaction control, including inside a DO body. A function or procedure body is not this.",
|
|
531
|
+
fix: "Remove the transaction control. okm migrate check replays one transaction and rolls it back. Apply accepts the step only with -- okm-allow OKM1551: reason."
|
|
532
|
+
},
|
|
497
533
|
{
|
|
498
534
|
code: "OKM1601",
|
|
499
535
|
title: "Stale typed SQL",
|
|
@@ -533,8 +569,14 @@ var ERROR_DOCS = [
|
|
|
533
569
|
{
|
|
534
570
|
code: "OKM1707",
|
|
535
571
|
title: "RLS role",
|
|
536
|
-
summary: "
|
|
537
|
-
fix: "Connect as a role that is
|
|
572
|
+
summary: "A scoped call under rls ran as a role that owns the tenant tables, is a superuser, or has BYPASSRLS. The reason field says which. The message names the endpoint.",
|
|
573
|
+
fix: "Connect as a role that is not the table owner, a superuser, or BYPASSRLS. okm seed under rlsTenancy runs as the application role. The owner gets this error."
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
code: "OKM1708",
|
|
577
|
+
title: "RLS policy",
|
|
578
|
+
summary: "A scoped call under rls found a tenant table whose row-level security is missing, not forced, or whose policies are not the ones okm generate emits. The message names the table and the endpoint.",
|
|
579
|
+
fix: "Restore the policies okm generate emits for this rls schema."
|
|
538
580
|
},
|
|
539
581
|
{
|
|
540
582
|
code: "OKM1801",
|
|
@@ -545,14 +587,14 @@ var ERROR_DOCS = [
|
|
|
545
587
|
{
|
|
546
588
|
code: "OKM1802",
|
|
547
589
|
title: "Server version",
|
|
548
|
-
summary: "The connected server
|
|
549
|
-
fix: "Upgrade the server, or lower
|
|
590
|
+
summary: "The connected server is older than this schema's minPostgres.",
|
|
591
|
+
fix: "Upgrade the server, or lower minPostgres to a major the server meets."
|
|
550
592
|
},
|
|
551
593
|
{
|
|
552
594
|
code: "OKM1803",
|
|
553
595
|
title: "Postgres floor",
|
|
554
|
-
summary:
|
|
555
|
-
fix:
|
|
596
|
+
summary: `The connected server is older than PostgreSQL ${String(POSTGRES_FLOOR)}.`,
|
|
597
|
+
fix: `Upgrade the server to PostgreSQL ${String(POSTGRES_FLOOR)} or higher.`
|
|
556
598
|
},
|
|
557
599
|
{
|
|
558
600
|
code: "OKM1810",
|
|
@@ -569,8 +611,8 @@ var ERROR_DOCS = [
|
|
|
569
611
|
{
|
|
570
612
|
code: "OKM1812",
|
|
571
613
|
title: "Feature version",
|
|
572
|
-
summary: "A feature needs a newer
|
|
573
|
-
fix:
|
|
614
|
+
summary: "A feature needs a newer PostgreSQL than minPostgres, or an extension version the server does not have. The message names the feature and the version.",
|
|
615
|
+
fix: `Raise minPostgres, or avoid the feature. ${uuidv7NeedsPostgres()} An extension pin names the version to install. Core features are never gated on an extension.`
|
|
574
616
|
},
|
|
575
617
|
{
|
|
576
618
|
code: "OKM1813",
|
|
@@ -617,7 +659,7 @@ var ERROR_DOCS = [
|
|
|
617
659
|
{
|
|
618
660
|
code: "OKM1825",
|
|
619
661
|
title: "Missing privilege",
|
|
620
|
-
summary: "
|
|
662
|
+
summary: "A role lacks a privilege on a managed object.",
|
|
621
663
|
fix: "Grant the privilege, or change the role. okm doctor reports the object and the privilege."
|
|
622
664
|
},
|
|
623
665
|
{
|
|
@@ -712,9 +754,53 @@ function indexDocs() {
|
|
|
712
754
|
return index;
|
|
713
755
|
}
|
|
714
756
|
|
|
715
|
-
// src/tooling/migrate/
|
|
757
|
+
// src/tooling/migrate/files.ts
|
|
716
758
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
717
759
|
import { join } from "node:path";
|
|
760
|
+
function loadMigrations(directory) {
|
|
761
|
+
if (!existsSync(directory))
|
|
762
|
+
return [];
|
|
763
|
+
const names = readdirSync(directory).filter((file) => file.endsWith(".sql")).sort();
|
|
764
|
+
const migrations = [];
|
|
765
|
+
for (const file of names) {
|
|
766
|
+
const id = file.slice(0, -".sql".length);
|
|
767
|
+
const catalogPath = join(directory, `${id}.catalog.json`);
|
|
768
|
+
if (!existsSync(catalogPath)) {
|
|
769
|
+
throw new OkmError2("invalid", `Migration ${id} is missing its catalog snapshot.`, {
|
|
770
|
+
fix: {
|
|
771
|
+
summary: "Generate the migration again so the SQL and the catalog are written together."
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
const plan = readMigrationPlan(join(directory, file));
|
|
776
|
+
const stored = parseCatalog2(readFileSync(catalogPath, "utf8"));
|
|
777
|
+
const startup = startupCatalog2(stored);
|
|
778
|
+
const hash = catalogHash2(startup);
|
|
779
|
+
migrations.push(startup === stored ? { id, catalogHash: hash, steps: plan.steps } : { id, catalogHash: hash, legacyHash: catalogHash2(stored), steps: plan.steps });
|
|
780
|
+
}
|
|
781
|
+
return migrations;
|
|
782
|
+
}
|
|
783
|
+
function readMigrationPlan(path) {
|
|
784
|
+
const text = prepareMigrationText(readFileSync(path, "utf8"), path);
|
|
785
|
+
const plan = parsePlan(text);
|
|
786
|
+
assertMigrationSteps(text, plan.steps.length, path);
|
|
787
|
+
return plan;
|
|
788
|
+
}
|
|
789
|
+
function loadHeadSnapshot(directory) {
|
|
790
|
+
const migrations = loadMigrations(directory);
|
|
791
|
+
const last = migrations.at(-1);
|
|
792
|
+
if (last === undefined)
|
|
793
|
+
return;
|
|
794
|
+
return {
|
|
795
|
+
id: last.id,
|
|
796
|
+
catalogHash: last.catalogHash,
|
|
797
|
+
catalog: parseCatalog2(readFileSync(join(directory, `${last.id}.catalog.json`), "utf8"))
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// src/tooling/migrate/lint.ts
|
|
802
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
803
|
+
import { join as join2 } from "node:path";
|
|
718
804
|
|
|
719
805
|
// src/tooling/migrate/lint-rules.ts
|
|
720
806
|
var INTEGER_WIDTH = {
|
|
@@ -751,7 +837,12 @@ var STEP_RULES = [
|
|
|
751
837
|
rule("OKM1536", "locking", "error", "Adding a foreign key without NOT VALID locks the table while it scans.", (input) => hit(foreignKeyWithoutNotValid(input), "adds a foreign key without NOT VALID")),
|
|
752
838
|
rule("OKM1537", "locking", "error", "SET NOT NULL locks the table while it scans for nulls.", (input) => hit(unsafeSetNotNull(input), "sets NOT NULL")),
|
|
753
839
|
rule("OKM1538", "locking", "warning", "A type change that rewrites the table locks it for the rewrite.", (input) => typeChange(input, "rewrite")),
|
|
754
|
-
rule("OKM1542", "data-dependent", "error", "A data statement outside backfill(), or DO or CALL, is not a migration step.", (input) => dataStatement(input.step))
|
|
840
|
+
rule("OKM1542", "data-dependent", "error", "A data statement outside backfill(), or DO or CALL, is not a migration step.", (input) => dataStatement(input.step)),
|
|
841
|
+
rule("OKM1550", "destructive", "error", "Disabling row-level security, dropping a policy, creating or altering one that is not the emitted tenant predicate, granting BYPASSRLS, or changing the owner of a tenant table removes the rlsTenancy backstop.", (input) => weakensRls(input)),
|
|
842
|
+
rule("OKM1551", "destructive", "error", "Transaction control inside a step commits or leaves the migration transaction.", (input) => {
|
|
843
|
+
const word = transactionControl(input.step.sql);
|
|
844
|
+
return word === undefined ? [] : [`contains ${word}`];
|
|
845
|
+
})
|
|
755
846
|
];
|
|
756
847
|
var COLUMN_RULES = [
|
|
757
848
|
{
|
|
@@ -810,6 +901,146 @@ function normalized(sql) {
|
|
|
810
901
|
}
|
|
811
902
|
return out;
|
|
812
903
|
}
|
|
904
|
+
function weakensRls(input) {
|
|
905
|
+
const text = normalized(input.step.sql);
|
|
906
|
+
if (/\bdisable row level security\b/.test(text))
|
|
907
|
+
return ["disables row level security"];
|
|
908
|
+
if (/\bno force row level security\b/.test(text))
|
|
909
|
+
return ["stops forcing row level security"];
|
|
910
|
+
if (/^drop policy\b/.test(text))
|
|
911
|
+
return ["drops a policy"];
|
|
912
|
+
const drifted = policyDrift(input, text);
|
|
913
|
+
if (drifted !== undefined)
|
|
914
|
+
return [drifted];
|
|
915
|
+
if (/^(?:alter|create) (?:role|user)\b/.test(text) && /(?<!no)bypassrls\b/.test(text)) {
|
|
916
|
+
return ["grants BYPASSRLS"];
|
|
917
|
+
}
|
|
918
|
+
if (changesTenantOwner(input, text))
|
|
919
|
+
return ["changes the owner of a tenant table"];
|
|
920
|
+
return [];
|
|
921
|
+
}
|
|
922
|
+
function policyDrift(input, text) {
|
|
923
|
+
const header = /^(create|alter) policy\s+(\S+)\s+on\s+(\S+)/.exec(text);
|
|
924
|
+
const kind = header?.[1];
|
|
925
|
+
const table = lastName(header?.[3]);
|
|
926
|
+
if (kind === undefined || table === undefined)
|
|
927
|
+
return;
|
|
928
|
+
if (!hasPolicy(input.before, table) && !hasPolicy(input.after, table))
|
|
929
|
+
return;
|
|
930
|
+
const name = lastName(header?.[2]) ?? "";
|
|
931
|
+
const using = clause(text, "using");
|
|
932
|
+
const check = clause(text, "with check");
|
|
933
|
+
if (isTrue(using) || isTrue(check)) {
|
|
934
|
+
return kind === "alter" ? "widens a policy to match every row" : "creates a policy that matches every row";
|
|
935
|
+
}
|
|
936
|
+
if (kind === "alter" && using === undefined && check === undefined)
|
|
937
|
+
return;
|
|
938
|
+
const tenant = policyExpression(input, table, `${table}_tenant`);
|
|
939
|
+
const unscoped = policyExpression(input, table, `${table}_unscoped_select`);
|
|
940
|
+
const emitted = !/\bas restrictive\b/.test(text) && (matchesEmitted(name, `${table}_tenant`, using, check, tenant) || matchesEmitted(name, `${table}_unscoped_select`, using, check, unscoped));
|
|
941
|
+
if (emitted)
|
|
942
|
+
return;
|
|
943
|
+
return kind === "alter" ? "widens a policy away from the tenant predicate" : "creates a policy that is not the tenant predicate";
|
|
944
|
+
}
|
|
945
|
+
function matchesEmitted(name, expectedName, using, check, expression) {
|
|
946
|
+
if (name !== expectedName || expression === undefined || using === undefined)
|
|
947
|
+
return false;
|
|
948
|
+
if (!sameExpression(using, expression))
|
|
949
|
+
return false;
|
|
950
|
+
return check === undefined || sameExpression(check, expression);
|
|
951
|
+
}
|
|
952
|
+
function sameExpression(found, expected) {
|
|
953
|
+
return found === expected || unwrap(found) === unwrap(expected);
|
|
954
|
+
}
|
|
955
|
+
function unwrap(expression) {
|
|
956
|
+
const body = expression.trim();
|
|
957
|
+
if (!body.startsWith("(") || !body.endsWith(")"))
|
|
958
|
+
return body;
|
|
959
|
+
let depth = 0;
|
|
960
|
+
for (let index = 0;index < body.length; index += 1) {
|
|
961
|
+
const char = body[index];
|
|
962
|
+
if (char === "(")
|
|
963
|
+
depth += 1;
|
|
964
|
+
else if (char === ")") {
|
|
965
|
+
depth -= 1;
|
|
966
|
+
if (depth === 0)
|
|
967
|
+
return index === body.length - 1 ? body.slice(1, -1).trim() : body;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return body;
|
|
971
|
+
}
|
|
972
|
+
function isTrue(expression) {
|
|
973
|
+
return expression !== undefined && expression.replaceAll(" ", "") === "(true)";
|
|
974
|
+
}
|
|
975
|
+
function clause(text, keyword) {
|
|
976
|
+
const needle = keyword === "using" ? /\busing\s*\(/g : /\bwith check\s*\(/g;
|
|
977
|
+
const found = needle.exec(text);
|
|
978
|
+
if (found === null)
|
|
979
|
+
return;
|
|
980
|
+
return parenthesized(text, found.index + found[0].length - 1);
|
|
981
|
+
}
|
|
982
|
+
function parenthesized(text, open) {
|
|
983
|
+
let depth = 0;
|
|
984
|
+
let quoted = false;
|
|
985
|
+
let single = false;
|
|
986
|
+
for (let index = open;index < text.length; index += 1) {
|
|
987
|
+
const char = text[index];
|
|
988
|
+
if (single) {
|
|
989
|
+
if (char === "'" && text[index + 1] === "'") {
|
|
990
|
+
index += 1;
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
if (char === "'")
|
|
994
|
+
single = false;
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
if (quoted) {
|
|
998
|
+
if (char === '"')
|
|
999
|
+
quoted = false;
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
if (char === "'") {
|
|
1003
|
+
single = true;
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
if (char === '"') {
|
|
1007
|
+
quoted = true;
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
if (char === "(")
|
|
1011
|
+
depth += 1;
|
|
1012
|
+
else if (char === ")") {
|
|
1013
|
+
depth -= 1;
|
|
1014
|
+
if (depth === 0)
|
|
1015
|
+
return text.slice(open, index + 1);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
function policyExpression(input, table, name) {
|
|
1021
|
+
const expression = expressionOf(input.after, table, name) ?? expressionOf(input.before, table, name);
|
|
1022
|
+
return expression === undefined ? undefined : normalized(expression);
|
|
1023
|
+
}
|
|
1024
|
+
function expressionOf(source, table, name) {
|
|
1025
|
+
for (const object of source.objects) {
|
|
1026
|
+
if (object.kind !== "policy")
|
|
1027
|
+
continue;
|
|
1028
|
+
if (object.identity.parent.name !== table || object.identity.name !== name)
|
|
1029
|
+
continue;
|
|
1030
|
+
return object.definition.expression;
|
|
1031
|
+
}
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
function changesTenantOwner(input, text) {
|
|
1035
|
+
const match = /^alter table\s+(\S+)\s+owner to\b/.exec(text);
|
|
1036
|
+
const table = lastName(match?.[1]);
|
|
1037
|
+
if (table === undefined)
|
|
1038
|
+
return false;
|
|
1039
|
+
return hasPolicy(input.before, table) || hasPolicy(input.after, table);
|
|
1040
|
+
}
|
|
1041
|
+
function hasPolicy(source, table) {
|
|
1042
|
+
return source.objects.some((object) => object.kind === "policy" && object.identity.parent.name === table);
|
|
1043
|
+
}
|
|
813
1044
|
function dataStatement(step) {
|
|
814
1045
|
const text = normalized(step.sql);
|
|
815
1046
|
if (/^(?:do|call)\b/.test(text))
|
|
@@ -1161,15 +1392,15 @@ function lintCatalog(source) {
|
|
|
1161
1392
|
return findings;
|
|
1162
1393
|
}
|
|
1163
1394
|
function lintMigrationDirectory(directory, pending) {
|
|
1164
|
-
if (!
|
|
1395
|
+
if (!existsSync2(directory))
|
|
1165
1396
|
return [];
|
|
1166
|
-
const names =
|
|
1397
|
+
const names = readdirSync2(directory).filter((file) => file.endsWith(".sql")).sort();
|
|
1167
1398
|
let before = catalog([]);
|
|
1168
1399
|
const findings = [];
|
|
1169
1400
|
for (const file of names) {
|
|
1170
1401
|
const id = file.slice(0, -".sql".length);
|
|
1171
|
-
const plan =
|
|
1172
|
-
const after = parseCatalog2(
|
|
1402
|
+
const plan = readMigrationPlan(join2(directory, file));
|
|
1403
|
+
const after = parseCatalog2(readFileSync2(join2(directory, `${id}.catalog.json`), "utf8"));
|
|
1173
1404
|
if (pending === undefined || pending.has(id)) {
|
|
1174
1405
|
for (const item of lintPlan(plan, before, after)) {
|
|
1175
1406
|
findings.push({ ...item, place: `${id} ${item.place}` });
|
|
@@ -1288,8 +1519,8 @@ function columnPlace(column) {
|
|
|
1288
1519
|
}
|
|
1289
1520
|
|
|
1290
1521
|
// src/tooling/migrate/project.ts
|
|
1291
|
-
import { existsSync as
|
|
1292
|
-
import { basename, join as
|
|
1522
|
+
import { existsSync as existsSync3, mkdirSync, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync, writeFileSync } from "node:fs";
|
|
1523
|
+
import { basename, dirname, join as join3 } from "node:path";
|
|
1293
1524
|
import { pathToFileURL } from "node:url";
|
|
1294
1525
|
|
|
1295
1526
|
// src/dialects/pg/declarations.ts
|
|
@@ -1331,7 +1562,7 @@ function sqlIdentifier(name, casing) {
|
|
|
1331
1562
|
}
|
|
1332
1563
|
|
|
1333
1564
|
// src/dialects/pg/emit.ts
|
|
1334
|
-
function emitRowTypes(source) {
|
|
1565
|
+
function emitRowTypes(source, jsonTypes) {
|
|
1335
1566
|
const lines = [
|
|
1336
1567
|
"/**",
|
|
1337
1568
|
" * Row types emitted from the schema.",
|
|
@@ -1349,13 +1580,13 @@ function emitRowTypes(source) {
|
|
|
1349
1580
|
inserts.push(` readonly ${key}: ${name}Insert;`);
|
|
1350
1581
|
updates.push(` readonly ${key}: ${name}Update;`);
|
|
1351
1582
|
lines.push(`export interface ${name} {`);
|
|
1352
|
-
lines.push(fields(item, "row", source));
|
|
1583
|
+
lines.push(fields(item, "row", source, jsonTypes));
|
|
1353
1584
|
lines.push("}", "");
|
|
1354
1585
|
lines.push(`export interface ${name}Insert {`);
|
|
1355
|
-
lines.push(fields(item, "insert", source));
|
|
1586
|
+
lines.push(fields(item, "insert", source, jsonTypes));
|
|
1356
1587
|
lines.push("}", "");
|
|
1357
1588
|
lines.push(`export interface ${name}Update {`);
|
|
1358
|
-
lines.push(fields(item, "update", source));
|
|
1589
|
+
lines.push(fields(item, "update", source, jsonTypes));
|
|
1359
1590
|
lines.push("}", "");
|
|
1360
1591
|
}
|
|
1361
1592
|
lines.push("export interface Rows {");
|
|
@@ -1388,7 +1619,74 @@ ${body}`;
|
|
|
1388
1619
|
function propertyName(name) {
|
|
1389
1620
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : JSON.stringify(name);
|
|
1390
1621
|
}
|
|
1391
|
-
function
|
|
1622
|
+
function jsonColumnTypes(source) {
|
|
1623
|
+
const text = source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\/\/[^\n]*/g, " ");
|
|
1624
|
+
const found = new Map;
|
|
1625
|
+
const pattern = /(?:\bt\s*\.\s*)?(jsonb|json)\s*</g;
|
|
1626
|
+
for (const match of text.matchAll(pattern)) {
|
|
1627
|
+
const open = (match.index ?? 0) + match[0].length - 1;
|
|
1628
|
+
const argument = readGeneric(text, open);
|
|
1629
|
+
const field = propertyBefore(text, match.index ?? 0);
|
|
1630
|
+
const table = tableBefore(text, match.index ?? 0);
|
|
1631
|
+
if (argument === undefined || field === undefined || table === undefined)
|
|
1632
|
+
continue;
|
|
1633
|
+
found.set(`${table}.${field}`, argument);
|
|
1634
|
+
}
|
|
1635
|
+
return found;
|
|
1636
|
+
}
|
|
1637
|
+
function readGeneric(source, open) {
|
|
1638
|
+
if (source[open] !== "<")
|
|
1639
|
+
return;
|
|
1640
|
+
let depth = 0;
|
|
1641
|
+
let quote;
|
|
1642
|
+
for (let index = open;index < source.length; index += 1) {
|
|
1643
|
+
const char = source[index];
|
|
1644
|
+
if (char === undefined)
|
|
1645
|
+
return;
|
|
1646
|
+
if (quote !== undefined) {
|
|
1647
|
+
if (char === "\\") {
|
|
1648
|
+
index += 1;
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
if (char === quote)
|
|
1652
|
+
quote = undefined;
|
|
1653
|
+
continue;
|
|
1654
|
+
}
|
|
1655
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
1656
|
+
quote = char;
|
|
1657
|
+
continue;
|
|
1658
|
+
}
|
|
1659
|
+
if (char === "<")
|
|
1660
|
+
depth += 1;
|
|
1661
|
+
else if (char === ">") {
|
|
1662
|
+
depth -= 1;
|
|
1663
|
+
if (depth === 0)
|
|
1664
|
+
return source.slice(open + 1, index).trim().replace(/\s+/g, " ");
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
function propertyBefore(source, index) {
|
|
1670
|
+
let cursor = index;
|
|
1671
|
+
while (cursor > 0 && /\s/.test(source[cursor - 1] ?? ""))
|
|
1672
|
+
cursor -= 1;
|
|
1673
|
+
if (source[cursor - 1] !== ":")
|
|
1674
|
+
return;
|
|
1675
|
+
cursor -= 1;
|
|
1676
|
+
while (cursor > 0 && /\s/.test(source[cursor - 1] ?? ""))
|
|
1677
|
+
cursor -= 1;
|
|
1678
|
+
const end = cursor;
|
|
1679
|
+
while (cursor > 0 && /[\w$]/.test(source[cursor - 1] ?? ""))
|
|
1680
|
+
cursor -= 1;
|
|
1681
|
+
const name = source.slice(cursor, end);
|
|
1682
|
+
return /^[$A-Za-z_][\w$]*$/.test(name) ? name : undefined;
|
|
1683
|
+
}
|
|
1684
|
+
function tableBefore(source, index) {
|
|
1685
|
+
const head = source.slice(0, index);
|
|
1686
|
+
const tables = [...head.matchAll(/table\(\s*["']([^"']+)["']/g)];
|
|
1687
|
+
return tables.at(-1)?.[1];
|
|
1688
|
+
}
|
|
1689
|
+
function fields(item, kind, source, jsonTypes) {
|
|
1392
1690
|
const printed = [];
|
|
1393
1691
|
const options = item.options;
|
|
1394
1692
|
const entries = Object.entries(item.columns);
|
|
@@ -1406,7 +1704,7 @@ function fields(item, kind, source) {
|
|
|
1406
1704
|
const state = builder.state;
|
|
1407
1705
|
if (kind === "row") {
|
|
1408
1706
|
if (!state.hidden) {
|
|
1409
|
-
printed.push(` readonly ${field}: ${valueType(state)};`);
|
|
1707
|
+
printed.push(` readonly ${field}: ${valueType(state, jsonTypes?.get(`${item.name}.${field}`))};`);
|
|
1410
1708
|
}
|
|
1411
1709
|
continue;
|
|
1412
1710
|
}
|
|
@@ -1415,7 +1713,7 @@ function fields(item, kind, source) {
|
|
|
1415
1713
|
continue;
|
|
1416
1714
|
}
|
|
1417
1715
|
const mode = kind === "update" ? "optional" : written;
|
|
1418
|
-
const label = valueType(state);
|
|
1716
|
+
const label = valueType(state, jsonTypes?.get(`${item.name}.${field}`));
|
|
1419
1717
|
printed.push(mode === "optional" ? ` readonly ${field}?: ${label};` : ` readonly ${field}: ${label};`);
|
|
1420
1718
|
}
|
|
1421
1719
|
return printed.join(`
|
|
@@ -1435,8 +1733,8 @@ function updateMode(state) {
|
|
|
1435
1733
|
return "omit";
|
|
1436
1734
|
return "optional";
|
|
1437
1735
|
}
|
|
1438
|
-
function valueType(state) {
|
|
1439
|
-
let label = scalarLabel(state);
|
|
1736
|
+
function valueType(state, jsonArg) {
|
|
1737
|
+
let label = scalarLabel(state, jsonArg);
|
|
1440
1738
|
if (state.dims > 0) {
|
|
1441
1739
|
for (let rank = 0;rank < state.dims; rank += 1) {
|
|
1442
1740
|
label = `readonly (${label})[]`;
|
|
@@ -1447,7 +1745,7 @@ function valueType(state) {
|
|
|
1447
1745
|
}
|
|
1448
1746
|
return label;
|
|
1449
1747
|
}
|
|
1450
|
-
function scalarLabel(state) {
|
|
1748
|
+
function scalarLabel(state, jsonArg) {
|
|
1451
1749
|
if (state.picklist !== undefined) {
|
|
1452
1750
|
return state.picklist.values.map((value) => JSON.stringify(value)).join(" | ");
|
|
1453
1751
|
}
|
|
@@ -1480,9 +1778,9 @@ function scalarLabel(state) {
|
|
|
1480
1778
|
case "bytea":
|
|
1481
1779
|
return "Uint8Array";
|
|
1482
1780
|
case "json":
|
|
1483
|
-
return "
|
|
1781
|
+
return `json<${jsonArg ?? "unknown"}>`;
|
|
1484
1782
|
case "jsonb":
|
|
1485
|
-
return "
|
|
1783
|
+
return `jsonb<${jsonArg ?? "unknown"}>`;
|
|
1486
1784
|
case "timestamptz":
|
|
1487
1785
|
return "Temporal.Instant";
|
|
1488
1786
|
case "timestamp":
|
|
@@ -1526,17 +1824,34 @@ function baseName(dataType) {
|
|
|
1526
1824
|
}
|
|
1527
1825
|
|
|
1528
1826
|
// src/dialects/pg/role/index.ts
|
|
1827
|
+
class RoleDeclarationError extends OkmError2 {
|
|
1828
|
+
reason;
|
|
1829
|
+
constructor(reason, message) {
|
|
1830
|
+
super("invalid", message, {
|
|
1831
|
+
kind: "invalid",
|
|
1832
|
+
fix: {
|
|
1833
|
+
summary: reason === "reporting_write" ? "Grant select, or use the application role for writes." : "Give the reporting role its own name."
|
|
1834
|
+
}
|
|
1835
|
+
});
|
|
1836
|
+
this.name = "OkmError";
|
|
1837
|
+
this.reason = reason;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1529
1840
|
var PROVENANCE = { origin: "file", name: "roles" };
|
|
1530
1841
|
var RELATION = ["SELECT", "INSERT", "UPDATE", "DELETE"];
|
|
1531
1842
|
var READ = ["SELECT"];
|
|
1532
1843
|
var ROUTINE = ["EXECUTE"];
|
|
1533
1844
|
var SEQUENCE = ["USAGE", "SELECT"];
|
|
1534
|
-
function attachRoles(source, input) {
|
|
1535
|
-
return catalog([...source.objects, ...contributeRoles(input, source.objects)]);
|
|
1845
|
+
function attachRoles(source, input, exposure) {
|
|
1846
|
+
return catalog([...source.objects, ...contributeRoles(input, source.objects, exposure)]);
|
|
1536
1847
|
}
|
|
1537
|
-
function contributeRoles(input, built) {
|
|
1848
|
+
function contributeRoles(input, built, exposure) {
|
|
1538
1849
|
const migration = requireName(input.migration, "migration");
|
|
1539
1850
|
const app = requireName(input.app, "app");
|
|
1851
|
+
const reporting = input.reporting === undefined ? undefined : requireName(input.reporting, "reporting");
|
|
1852
|
+
if (reporting !== undefined && (reporting === app || reporting === migration)) {
|
|
1853
|
+
throw new RoleDeclarationError("reporting_distinct", `Role ${reporting} cannot be the reporting role and also migration or app.`);
|
|
1854
|
+
}
|
|
1540
1855
|
const managed = new Map;
|
|
1541
1856
|
for (const role of input.managed ?? []) {
|
|
1542
1857
|
const name = requireName(role.name, "managed role");
|
|
@@ -1544,6 +1859,10 @@ function contributeRoles(input, built) {
|
|
|
1544
1859
|
}
|
|
1545
1860
|
const objects = [];
|
|
1546
1861
|
const names = new Set([migration, app, ...managed.keys()]);
|
|
1862
|
+
if (reporting !== undefined)
|
|
1863
|
+
names.add(reporting);
|
|
1864
|
+
for (const grant of input.columns ?? [])
|
|
1865
|
+
names.add(requireName(grant.role, "column grant role"));
|
|
1547
1866
|
for (const name of [...names].sort()) {
|
|
1548
1867
|
const declaration = managed.get(name);
|
|
1549
1868
|
objects.push(roleObject({
|
|
@@ -1558,8 +1877,10 @@ function contributeRoles(input, built) {
|
|
|
1558
1877
|
}
|
|
1559
1878
|
const roleIdentity = (name) => ({ kind: "role", name });
|
|
1560
1879
|
const namespaces = new Set;
|
|
1880
|
+
const exposed = indexExposure(exposure);
|
|
1881
|
+
const columnGrants = new Map;
|
|
1561
1882
|
for (const object of built) {
|
|
1562
|
-
if (object.owner === "ignored")
|
|
1883
|
+
if (object.owner === "ignored" || object.owner === "external")
|
|
1563
1884
|
continue;
|
|
1564
1885
|
const ref = grantRef(object);
|
|
1565
1886
|
if (ref === undefined)
|
|
@@ -1577,6 +1898,7 @@ function contributeRoles(input, built) {
|
|
|
1577
1898
|
}));
|
|
1578
1899
|
}
|
|
1579
1900
|
}
|
|
1901
|
+
const relations = built.filter((object) => object.owner !== "ignored" && object.owner !== "external" && (object.kind === "table" || object.kind === "view" || object.kind === "materializedView"));
|
|
1580
1902
|
const schemas = namespaces.size === 0 ? ["public"] : [...namespaces].sort();
|
|
1581
1903
|
for (const name of schemas) {
|
|
1582
1904
|
const namespace = staticNamespace2(name);
|
|
@@ -1597,6 +1919,45 @@ function contributeRoles(input, built) {
|
|
|
1597
1919
|
}));
|
|
1598
1920
|
}
|
|
1599
1921
|
}
|
|
1922
|
+
if (reporting !== undefined) {
|
|
1923
|
+
objects.push(grantObject({
|
|
1924
|
+
role: reporting,
|
|
1925
|
+
object: { kind: "namespace", namespace, name },
|
|
1926
|
+
privilege: "USAGE",
|
|
1927
|
+
provenance: PROVENANCE,
|
|
1928
|
+
dependencies: [roleIdentity(reporting)]
|
|
1929
|
+
}));
|
|
1930
|
+
objects.push(defaultPrivilegeObject({
|
|
1931
|
+
forRole: migration,
|
|
1932
|
+
namespace,
|
|
1933
|
+
objectKind: "table",
|
|
1934
|
+
grantee: reporting,
|
|
1935
|
+
privilege: "SELECT",
|
|
1936
|
+
provenance: PROVENANCE,
|
|
1937
|
+
dependencies: [roleIdentity(migration), roleIdentity(reporting)]
|
|
1938
|
+
}));
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
for (const grant of input.columns ?? []) {
|
|
1942
|
+
noteColumnGrant(columnGrants, grant, built, exposed, reporting);
|
|
1943
|
+
}
|
|
1944
|
+
if (reporting !== undefined) {
|
|
1945
|
+
for (const object of relations) {
|
|
1946
|
+
const ref = grantRef(object);
|
|
1947
|
+
if (ref === undefined)
|
|
1948
|
+
continue;
|
|
1949
|
+
noteReportingSelect(columnGrants, objects, reporting, object, ref, exposed, built);
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
for (const pending of [...columnGrants.values()].sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0)) {
|
|
1953
|
+
const target = pending.target;
|
|
1954
|
+
objects.push(grantObject({
|
|
1955
|
+
role: pending.role,
|
|
1956
|
+
object: pending.object,
|
|
1957
|
+
privilege: columnPrivilege(pending.privilege, [...pending.columns].sort()),
|
|
1958
|
+
provenance: PROVENANCE,
|
|
1959
|
+
dependencies: [roleIdentity(pending.role), target.identity]
|
|
1960
|
+
}));
|
|
1600
1961
|
}
|
|
1601
1962
|
return objects;
|
|
1602
1963
|
}
|
|
@@ -1638,6 +1999,137 @@ function privilegesFor(kind) {
|
|
|
1638
1999
|
function objectIdentity(object) {
|
|
1639
2000
|
return object.identity;
|
|
1640
2001
|
}
|
|
2002
|
+
function indexExposure(exposure) {
|
|
2003
|
+
const indexed = new Map;
|
|
2004
|
+
for (const table of exposure ?? [])
|
|
2005
|
+
indexed.set(table.table, table);
|
|
2006
|
+
return indexed;
|
|
2007
|
+
}
|
|
2008
|
+
function noteReportingSelect(pending, objects, role, target, ref, exposed, built) {
|
|
2009
|
+
const key = columnKey(role, ref, "SELECT");
|
|
2010
|
+
const declared = pending.get(key);
|
|
2011
|
+
if (ref.kind === "view" || ref.kind === "materializedView") {
|
|
2012
|
+
if (declared !== undefined)
|
|
2013
|
+
return;
|
|
2014
|
+
objects.push(selectGrant(role, ref, target));
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
const visible = visibleColumns(ref.name, exposed, built);
|
|
2018
|
+
const hidden = hiddenColumns(ref.name, exposed);
|
|
2019
|
+
if (hidden.size === 0) {
|
|
2020
|
+
pending.delete(key);
|
|
2021
|
+
objects.push(selectGrant(role, ref, target));
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
const columns = new Set(visible);
|
|
2025
|
+
for (const name of declared?.columns ?? [])
|
|
2026
|
+
columns.add(name);
|
|
2027
|
+
if (columns.size === 0) {
|
|
2028
|
+
pending.delete(key);
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
const next = declared ?? openColumnGrant(role, ref, "SELECT", target);
|
|
2032
|
+
next.columns.clear();
|
|
2033
|
+
for (const name of columns)
|
|
2034
|
+
next.columns.add(name);
|
|
2035
|
+
pending.set(key, next);
|
|
2036
|
+
}
|
|
2037
|
+
function selectGrant(role, ref, target) {
|
|
2038
|
+
return grantObject({
|
|
2039
|
+
role,
|
|
2040
|
+
object: ref,
|
|
2041
|
+
privilege: "SELECT",
|
|
2042
|
+
provenance: PROVENANCE,
|
|
2043
|
+
dependencies: [{ kind: "role", name: role }, target.identity]
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
function noteColumnGrant(pending, grant, built, exposed, reporting) {
|
|
2047
|
+
const role = requireName(grant.role, "column grant role");
|
|
2048
|
+
const target = findRelation(built, grant.on);
|
|
2049
|
+
if (target.owner === "ignored") {
|
|
2050
|
+
throw new OkmError2("invalid", `Column grant on ${grant.on} targets an ignored table.`, {
|
|
2051
|
+
fix: { summary: "Remove the grant, or mark the table external." }
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
const ref = grantRef(target);
|
|
2055
|
+
if (ref === undefined) {
|
|
2056
|
+
throw new OkmError2("OKM1020", `Column grant on ${grant.on} is not a table or a view.`, {
|
|
2057
|
+
fix: { summary: "Name a table, view, or materialized view in the schema." }
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
for (const privilege of ["select", "update"]) {
|
|
2061
|
+
const names = grant[privilege];
|
|
2062
|
+
if (names === undefined || names.length === 0)
|
|
2063
|
+
continue;
|
|
2064
|
+
if (role === reporting && privilege === "update") {
|
|
2065
|
+
throw new RoleDeclarationError("reporting_write", `Role ${role} is the reporting role and cannot be granted update.`);
|
|
2066
|
+
}
|
|
2067
|
+
const upper = privilege === "select" ? "SELECT" : "UPDATE";
|
|
2068
|
+
const resolved = names.map((name) => resolveColumn(target, built, ref.name, name, exposed));
|
|
2069
|
+
const current = pending.get(columnKey(role, ref, upper)) ?? openColumnGrant(role, ref, upper, target);
|
|
2070
|
+
for (const name of resolved)
|
|
2071
|
+
current.columns.add(name);
|
|
2072
|
+
pending.set(current.key, current);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
function openColumnGrant(role, object, privilege, target) {
|
|
2076
|
+
return {
|
|
2077
|
+
key: columnKey(role, object, privilege),
|
|
2078
|
+
role,
|
|
2079
|
+
privilege,
|
|
2080
|
+
object,
|
|
2081
|
+
target,
|
|
2082
|
+
columns: new Set
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
function columnKey(role, object, privilege) {
|
|
2086
|
+
const namespace = object.namespace.form === "static" ? object.namespace.name : object.namespace.pattern;
|
|
2087
|
+
return `${role}\x00${object.kind}\x00${namespace}\x00${object.name}\x00${privilege}`;
|
|
2088
|
+
}
|
|
2089
|
+
function findRelation(built, name) {
|
|
2090
|
+
const found = built.filter((object) => (object.kind === "table" || object.kind === "view" || object.kind === "materializedView") && object.identity.name === name);
|
|
2091
|
+
const target = found[0];
|
|
2092
|
+
if (target === undefined) {
|
|
2093
|
+
throw new OkmError2("OKM1020", `Column grant names ${name}, which is not in the schema.`, {
|
|
2094
|
+
fix: { summary: "Use the SQL name of a table, view, or materialized view." }
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
if (found.length > 1) {
|
|
2098
|
+
throw new OkmError2("OKM1020", `Column grant on ${name} matches more than one object.`, {
|
|
2099
|
+
fix: { summary: "Rename one of the objects so the grant names one." }
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
return target;
|
|
2103
|
+
}
|
|
2104
|
+
function resolveColumn(target, built, table, name, exposed) {
|
|
2105
|
+
const known = exposed.get(table);
|
|
2106
|
+
const fromField = known?.columns.find((column) => column.field === name || column.sql === name);
|
|
2107
|
+
if (fromField !== undefined)
|
|
2108
|
+
return fromField.sql;
|
|
2109
|
+
const sqlNames = columnNames(target, built, table);
|
|
2110
|
+
if (sqlNames.includes(name))
|
|
2111
|
+
return name;
|
|
2112
|
+
throw new OkmError2("OKM1020", `Column ${table}.${name} is not in the schema.`, {
|
|
2113
|
+
fix: { summary: "Name a column of that table or view." }
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
function visibleColumns(table, exposed, built) {
|
|
2117
|
+
const known = exposed.get(table);
|
|
2118
|
+
if (known !== undefined) {
|
|
2119
|
+
return known.columns.filter((column) => !column.hidden).map((column) => column.sql);
|
|
2120
|
+
}
|
|
2121
|
+
return built.flatMap((object) => object.kind === "column" && object.identity.parent.name === table ? [object.identity.name] : []);
|
|
2122
|
+
}
|
|
2123
|
+
function hiddenColumns(table, exposed) {
|
|
2124
|
+
const known = exposed.get(table);
|
|
2125
|
+
return new Set((known?.columns ?? []).filter((column) => column.hidden).map((column) => column.sql));
|
|
2126
|
+
}
|
|
2127
|
+
function columnNames(target, built, table) {
|
|
2128
|
+
if (target.kind === "view" || target.kind === "materializedView") {
|
|
2129
|
+
return target.definition.columns.map((column) => column.name);
|
|
2130
|
+
}
|
|
2131
|
+
return built.flatMap((object) => object.kind === "column" && object.identity.parent.name === table ? [object.identity.name] : []);
|
|
2132
|
+
}
|
|
1641
2133
|
|
|
1642
2134
|
// src/tooling/migrate/estimate.ts
|
|
1643
2135
|
var LARGE_TABLE_ROWS = 1e6;
|
|
@@ -1677,15 +2169,13 @@ async function readRowEstimates(url, schema, tables) {
|
|
|
1677
2169
|
return map;
|
|
1678
2170
|
} catch (error) {
|
|
1679
2171
|
if (began)
|
|
1680
|
-
await connection.execute("rollback")
|
|
1681
|
-
|
|
1682
|
-
});
|
|
1683
|
-
throw error;
|
|
2172
|
+
await finishQuiet(() => connection.execute("rollback"));
|
|
2173
|
+
throw sessionFailure(error) ?? error;
|
|
1684
2174
|
} finally {
|
|
1685
|
-
await connection.release();
|
|
2175
|
+
await finishQuiet(() => connection.release());
|
|
1686
2176
|
}
|
|
1687
2177
|
} finally {
|
|
1688
|
-
await pool.close();
|
|
2178
|
+
await finishQuiet(() => pool.close());
|
|
1689
2179
|
}
|
|
1690
2180
|
}
|
|
1691
2181
|
function annotateLock(step, estimates, aliases = new Map) {
|
|
@@ -1842,15 +2332,17 @@ async function buildProject(cwd) {
|
|
|
1842
2332
|
}
|
|
1843
2333
|
async function generateProject(cwd, name, flags) {
|
|
1844
2334
|
const prepared = await prepare(cwd, name, flags);
|
|
2335
|
+
if (name === "init")
|
|
2336
|
+
writeGitAttributes(cwd);
|
|
1845
2337
|
writeArtifact(cwd, prepared.config, prepared.built);
|
|
1846
2338
|
if (prepared.plan.steps.length === 0)
|
|
1847
2339
|
return;
|
|
1848
|
-
const directory =
|
|
2340
|
+
const directory = projectPath(cwd, prepared.config.migrations ?? "migrations");
|
|
1849
2341
|
mkdirSync(directory, { recursive: true });
|
|
1850
2342
|
const base = `${nextNumber(directory)}_${safeName(name)}`;
|
|
1851
|
-
const sqlPath =
|
|
2343
|
+
const sqlPath = join3(directory, `${base}.sql`);
|
|
1852
2344
|
writeFileSync(sqlPath, formatPlan(prepared.plan));
|
|
1853
|
-
writeFileSync(
|
|
2345
|
+
writeFileSync(join3(directory, `${base}.catalog.json`), serializeCatalog2(prepared.built.catalog));
|
|
1854
2346
|
return { path: sqlPath, findings: lintPrepared(prepared) };
|
|
1855
2347
|
}
|
|
1856
2348
|
async function planProject(cwd, name, flags, invoke) {
|
|
@@ -1873,9 +2365,9 @@ async function checkProject(cwd, flags) {
|
|
|
1873
2365
|
const targets = listTargets(opened.config);
|
|
1874
2366
|
assertTargetAlias(targets);
|
|
1875
2367
|
if (opened.config.tables !== undefined) {
|
|
1876
|
-
const directory =
|
|
1877
|
-
const files =
|
|
1878
|
-
const source =
|
|
2368
|
+
const directory = projectPath(cwd, opened.config.tables);
|
|
2369
|
+
const files = existsSync3(directory) ? readdirSync3(directory) : [];
|
|
2370
|
+
const source = readFileSync3(projectPath(cwd, opened.config.schema), "utf8");
|
|
1879
2371
|
const missing = unlistedTableFiles(source, files);
|
|
1880
2372
|
if (missing.length > 0) {
|
|
1881
2373
|
throw new OkmError2("OKM1024", `Table file not in schema: ${missing.join(", ")}.`, {
|
|
@@ -1894,7 +2386,7 @@ async function checkProject(cwd, flags) {
|
|
|
1894
2386
|
const target = selectTarget(opened.config, named);
|
|
1895
2387
|
assertTargetPolicy(target, "check", flags?.allowProtected ?? false);
|
|
1896
2388
|
assertTargetPolicy(target, "drift", flags?.allowProtected ?? false);
|
|
1897
|
-
const { assertAuthorDrift } = await import("./
|
|
2389
|
+
const { assertAuthorDrift } = await import("./jvd0mqag.js");
|
|
1898
2390
|
const allowPooler = flags?.allowPooler === true || opened.config.allowPooler === true;
|
|
1899
2391
|
await assertAuthorDrift(target.url, opened.built.catalog, opened.config.roles, allowPooler);
|
|
1900
2392
|
return findings;
|
|
@@ -1975,7 +2467,7 @@ async function openProject(cwd) {
|
|
|
1975
2467
|
const config = await loadConfig(cwd);
|
|
1976
2468
|
const built = withRoles(await loadSchema(cwd, config.schema), config);
|
|
1977
2469
|
const declarations = schemaDeclarations(built);
|
|
1978
|
-
const previous = readPrevious(
|
|
2470
|
+
const previous = readPrevious(projectPath(cwd, config.migrations ?? "migrations"));
|
|
1979
2471
|
const stale = staleRenames(previous?.catalog, declarations.renames);
|
|
1980
2472
|
if (stale.length > 0) {
|
|
1981
2473
|
throw new OkmError2("OKM1020", stale.join(" "), {
|
|
@@ -1990,25 +2482,108 @@ async function openProject(cwd) {
|
|
|
1990
2482
|
};
|
|
1991
2483
|
}
|
|
1992
2484
|
function withRoles(built, config) {
|
|
1993
|
-
|
|
2485
|
+
const marks = ownershipMarks(built);
|
|
2486
|
+
const stamped = marks.size === 0 ? built.catalog : stampOwnership(built.catalog, marks);
|
|
2487
|
+
const roles = config.roles;
|
|
2488
|
+
const catalog = roles === undefined ? stamped : attachRoles(stamped, roles, exposureFor(built, roles));
|
|
2489
|
+
if (catalog === built.catalog)
|
|
1994
2490
|
return built;
|
|
1995
|
-
return { ...built, catalog
|
|
2491
|
+
return { ...built, catalog };
|
|
2492
|
+
}
|
|
2493
|
+
function ownershipMarks(built) {
|
|
2494
|
+
const marks = new Map;
|
|
2495
|
+
const models = built.model;
|
|
2496
|
+
for (const table of built.tables) {
|
|
2497
|
+
const owner = tableOwner(table);
|
|
2498
|
+
if (owner === undefined)
|
|
2499
|
+
continue;
|
|
2500
|
+
const model = models[table.name];
|
|
2501
|
+
if (model === undefined)
|
|
2502
|
+
continue;
|
|
2503
|
+
marks.set(model.sql, owner);
|
|
2504
|
+
}
|
|
2505
|
+
return marks;
|
|
2506
|
+
}
|
|
2507
|
+
function exposureFor(built, roles) {
|
|
2508
|
+
if (roles.reporting === undefined && (roles.columns === undefined || roles.columns.length === 0)) {
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2511
|
+
return Object.values(built.model).map((model) => ({
|
|
2512
|
+
table: model.sql,
|
|
2513
|
+
columns: model.columns.map((column) => ({
|
|
2514
|
+
field: column.field,
|
|
2515
|
+
sql: column.sql,
|
|
2516
|
+
hidden: column.hidden === true || column.hidden === "filter"
|
|
2517
|
+
}))
|
|
2518
|
+
}));
|
|
2519
|
+
}
|
|
2520
|
+
var GIT_ATTRIBUTES = [
|
|
2521
|
+
"migrations/*.sql text eol=lf",
|
|
2522
|
+
"*.catalog.json text eol=lf",
|
|
2523
|
+
".okm/** text eol=lf",
|
|
2524
|
+
""
|
|
2525
|
+
].join(`
|
|
2526
|
+
`);
|
|
2527
|
+
function writeGitAttributes(cwd) {
|
|
2528
|
+
const path = projectPath(cwd, ".gitattributes");
|
|
2529
|
+
if (existsSync3(path))
|
|
2530
|
+
return;
|
|
2531
|
+
writeFileSync(path, GIT_ATTRIBUTES);
|
|
2532
|
+
}
|
|
2533
|
+
function typesText(cwd, spec, built) {
|
|
2534
|
+
return `${emitRowTypes(built, jsonColumnTypes(readSchemaSource(cwd, spec)))}${referenceAugmentation(built)}`;
|
|
2535
|
+
}
|
|
2536
|
+
function readSchemaSource(cwd, spec) {
|
|
2537
|
+
const parts = [];
|
|
2538
|
+
const seen = new Set;
|
|
2539
|
+
const visit = (file) => {
|
|
2540
|
+
if (seen.has(file) || !existsSync3(file))
|
|
2541
|
+
return;
|
|
2542
|
+
let stat;
|
|
2543
|
+
try {
|
|
2544
|
+
stat = statSync(file);
|
|
2545
|
+
} catch {
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
if (!stat.isFile())
|
|
2549
|
+
return;
|
|
2550
|
+
seen.add(file);
|
|
2551
|
+
const text = readFileSync3(file, "utf8");
|
|
2552
|
+
parts.push(text);
|
|
2553
|
+
for (const match of text.matchAll(/from\s+["'](\.[^"']+)["']/g)) {
|
|
2554
|
+
const rel = match[1];
|
|
2555
|
+
if (rel === undefined)
|
|
2556
|
+
continue;
|
|
2557
|
+
const base = join3(dirname(file), rel);
|
|
2558
|
+
const candidates = [base, `${base}.ts`, `${base}.tsx`, `${base}.mts`, join3(base, "index.ts")];
|
|
2559
|
+
for (const candidate of candidates) {
|
|
2560
|
+
if (existsSync3(candidate)) {
|
|
2561
|
+
visit(candidate);
|
|
2562
|
+
break;
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
};
|
|
2567
|
+
visit(projectPath(cwd, spec));
|
|
2568
|
+
return parts.join(`
|
|
2569
|
+
`);
|
|
1996
2570
|
}
|
|
1997
2571
|
function writeArtifact(cwd, config, built) {
|
|
1998
|
-
const directory =
|
|
2572
|
+
const directory = projectPath(cwd, config.out ?? ".okm");
|
|
1999
2573
|
mkdirSync(directory, { recursive: true });
|
|
2000
2574
|
const startup = startupCatalog2(built.catalog);
|
|
2001
|
-
|
|
2002
|
-
writeFileSync(
|
|
2575
|
+
const text = catalogFileText2(serializeCatalog2(startup));
|
|
2576
|
+
writeFileSync(join3(directory, "catalog.json"), text);
|
|
2577
|
+
writeFileSync(join3(directory, "catalog.hash"), `${sha2562(text)}
|
|
2003
2578
|
`);
|
|
2004
|
-
writeFileSync(
|
|
2579
|
+
writeFileSync(join3(directory, "types.d.ts"), typesText(cwd, config.schema, built));
|
|
2005
2580
|
return directory;
|
|
2006
2581
|
}
|
|
2007
2582
|
async function loadBuiltSchema(cwd, config) {
|
|
2008
2583
|
return withRoles(await loadSchema(cwd, config.schema), config);
|
|
2009
2584
|
}
|
|
2010
2585
|
async function loadConfig(cwd) {
|
|
2011
|
-
const path =
|
|
2586
|
+
const path = projectPath(cwd, "okmodel.config.ts");
|
|
2012
2587
|
const imported = await import(`${pathToFileURL(path).href}?okm=${Date.now()}`);
|
|
2013
2588
|
const record = isRecord(imported) ? imported : {};
|
|
2014
2589
|
const config = record.default ?? record.config;
|
|
@@ -2018,7 +2593,7 @@ async function loadConfig(cwd) {
|
|
|
2018
2593
|
return config;
|
|
2019
2594
|
}
|
|
2020
2595
|
async function loadSchema(cwd, spec) {
|
|
2021
|
-
const path =
|
|
2596
|
+
const path = projectPath(cwd, spec);
|
|
2022
2597
|
const imported = await import(`${pathToFileURL(path).href}?okm=${Date.now()}`);
|
|
2023
2598
|
const record = isRecord(imported) ? imported : {};
|
|
2024
2599
|
for (const value of Object.values(record)) {
|
|
@@ -2030,13 +2605,13 @@ async function loadSchema(cwd, spec) {
|
|
|
2030
2605
|
throw new OkmError2("invalid", `${spec} must export a schema().`);
|
|
2031
2606
|
}
|
|
2032
2607
|
function readPrevious(directory) {
|
|
2033
|
-
if (!
|
|
2608
|
+
if (!existsSync3(directory))
|
|
2034
2609
|
return;
|
|
2035
|
-
const catalogs =
|
|
2610
|
+
const catalogs = readdirSync3(directory).filter((file) => file.endsWith(".catalog.json")).sort();
|
|
2036
2611
|
const latest = catalogs.at(-1);
|
|
2037
2612
|
if (latest === undefined)
|
|
2038
2613
|
return;
|
|
2039
|
-
const text =
|
|
2614
|
+
const text = readFileSync3(join3(directory, latest), "utf8");
|
|
2040
2615
|
return { catalog: parseCatalog2(text) };
|
|
2041
2616
|
}
|
|
2042
2617
|
function referenceAugmentation(source) {
|
|
@@ -2061,8 +2636,8 @@ function referenceAugmentation(source) {
|
|
|
2061
2636
|
}
|
|
2062
2637
|
function nextNumber(directory) {
|
|
2063
2638
|
let max = 0;
|
|
2064
|
-
if (
|
|
2065
|
-
for (const file of
|
|
2639
|
+
if (existsSync3(directory)) {
|
|
2640
|
+
for (const file of readdirSync3(directory)) {
|
|
2066
2641
|
const match = /^(\d+)_/.exec(file);
|
|
2067
2642
|
const value = match?.[1];
|
|
2068
2643
|
if (value !== undefined)
|
|
@@ -2103,14 +2678,14 @@ function projectImportsValidate(cwd) {
|
|
|
2103
2678
|
function directoryImportsValidate(dir) {
|
|
2104
2679
|
let entries;
|
|
2105
2680
|
try {
|
|
2106
|
-
entries =
|
|
2681
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
2107
2682
|
} catch {
|
|
2108
2683
|
return false;
|
|
2109
2684
|
}
|
|
2110
2685
|
for (const entry of entries) {
|
|
2111
2686
|
if (entry.name.startsWith(".") || SKIP_SCAN.has(entry.name))
|
|
2112
2687
|
continue;
|
|
2113
|
-
const path =
|
|
2688
|
+
const path = join3(dir, entry.name);
|
|
2114
2689
|
if (entry.isDirectory()) {
|
|
2115
2690
|
if (directoryImportsValidate(path))
|
|
2116
2691
|
return true;
|
|
@@ -2118,7 +2693,7 @@ function directoryImportsValidate(dir) {
|
|
|
2118
2693
|
}
|
|
2119
2694
|
if (!/\.(?:ts|tsx|mts|cts)$/.test(entry.name))
|
|
2120
2695
|
continue;
|
|
2121
|
-
if (runtimeImportsValidate(
|
|
2696
|
+
if (runtimeImportsValidate(readFileSync3(path, "utf8")))
|
|
2122
2697
|
return true;
|
|
2123
2698
|
}
|
|
2124
2699
|
return false;
|
|
@@ -2140,4 +2715,4 @@ function runtimeImportsValidate(source) {
|
|
|
2140
2715
|
return false;
|
|
2141
2716
|
}
|
|
2142
2717
|
|
|
2143
|
-
export { errorDoc, lintMigrationDirectory, formatFindings, hasError, lintRefusal, buildProject, generateProject, planProject, checkProject, projectHead, openProject, loadBuiltSchema, loadConfig, loadSchema };
|
|
2718
|
+
export { projectPath, errorDoc, loadMigrations, readMigrationPlan, loadHeadSnapshot, lintMigrationDirectory, formatFindings, hasError, lintRefusal, buildProject, generateProject, planProject, checkProject, projectHead, openProject, loadBuiltSchema, loadConfig, loadSchema };
|