okmodel 0.1.0 → 0.2.1
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 +237 -19
- package/dist/adapters/capabilities.d.ts +34 -0
- package/dist/adapters/pg/bunsql-extra.d.ts +17 -0
- package/dist/adapters/pg/bunsql.d.ts +60 -0
- package/dist/adapters/pg/call.d.ts +2 -12
- package/dist/adapters/pg/nodepostgres-extra.d.ts +18 -0
- package/dist/adapters/pg/nodepostgres.d.ts +57 -0
- package/dist/adapters/pg/postgres-extra.d.ts +50 -1
- package/dist/adapters/pg/postgresjs.d.ts +0 -4
- package/dist/adapters/pg/session.d.ts +94 -0
- package/dist/adapters/pg/watch.d.ts +18 -0
- package/dist/contracts/catalog/object.d.ts +1 -0
- package/dist/contracts/catalog/types.d.ts +15 -8
- package/dist/contracts/connection.d.ts +13 -0
- package/dist/contracts/driver.d.ts +18 -5
- package/dist/contracts/error.d.ts +17 -1
- package/dist/contracts/generator.d.ts +63 -0
- package/dist/contracts/index.d.ts +2 -2
- package/dist/contracts/index.js +1 -1
- package/dist/contracts/internal.js +11 -9
- package/dist/contracts/location.d.ts +24 -0
- package/dist/contracts/rows.d.ts +15 -0
- package/dist/dialects/pg/archive-bind.d.ts +22 -0
- package/dist/dialects/pg/column.d.ts +126 -9
- package/dist/dialects/pg/compile.d.ts +1 -0
- package/dist/dialects/pg/custom.d.ts +6 -0
- package/dist/dialects/pg/filter-parse.d.ts +19 -0
- package/dist/dialects/pg/filters.d.ts +19 -0
- package/dist/dialects/pg/index.d.ts +11 -2
- package/dist/dialects/pg/index.js +159 -1063
- package/dist/dialects/pg/keys.d.ts +19 -3
- package/dist/dialects/pg/model.d.ts +223 -2
- package/dist/dialects/pg/operator-fit.d.ts +86 -0
- package/dist/dialects/pg/operators.d.ts +39 -1
- package/dist/dialects/pg/ops/arr-append.d.ts +9 -0
- package/dist/dialects/pg/ops/arr-ns.d.ts +7 -0
- package/dist/dialects/pg/ops/arr-remove.d.ts +9 -0
- package/dist/dialects/pg/ops/containedBy.d.ts +9 -0
- package/dist/dialects/pg/ops/contains.d.ts +8 -4
- package/dist/dialects/pg/ops/hasAnyKey.d.ts +8 -0
- package/dist/dialects/pg/ops/hasKey.d.ts +8 -0
- package/dist/dialects/pg/ops/json-ns.d.ts +6 -0
- package/dist/dialects/pg/ops/json-set.d.ts +12 -0
- package/dist/dialects/pg/ops/matches.d.ts +16 -0
- package/dist/dialects/pg/ops/overlaps.d.ts +9 -0
- package/dist/dialects/pg/ops/path.d.ts +13 -0
- package/dist/dialects/pg/preset.d.ts +76 -0
- package/dist/dialects/pg/relations.d.ts +59 -2
- package/dist/dialects/pg/schema.d.ts +109 -5
- package/dist/dialects/pg/table.d.ts +130 -11
- package/dist/dialects/pg/temporal.d.ts +9 -7
- package/dist/dialects/pg/tenancy.d.ts +94 -0
- package/dist/dialects/pg/through.d.ts +21 -0
- package/dist/dialects/pg/trait.d.ts +141 -0
- package/dist/dialects/pg/where.d.ts +37 -0
- package/dist/runtime/aggregate.d.ts +25 -0
- package/dist/runtime/archive-handle.d.ts +18 -0
- package/dist/runtime/archive-rules.d.ts +12 -0
- package/dist/runtime/archive.d.ts +73 -0
- package/dist/runtime/batch.d.ts +20 -0
- package/dist/runtime/client.d.ts +114 -5
- package/dist/runtime/conflict.d.ts +2 -1
- package/dist/runtime/exposure.d.ts +27 -0
- package/dist/runtime/hookm.d.ts +39 -0
- package/dist/runtime/ids/index.d.ts +11 -0
- package/dist/runtime/ids/index.js +193 -0
- package/dist/runtime/ids/okid-extended.d.ts +19 -0
- package/dist/runtime/ids/okid-generator.d.ts +21 -0
- package/dist/runtime/ids/okid-shared.d.ts +89 -0
- package/dist/runtime/ids/okid.d.ts +24 -0
- package/dist/runtime/ids/uuid.d.ts +21 -0
- package/dist/runtime/lock.d.ts +22 -0
- package/dist/runtime/operator-sql.d.ts +8 -0
- package/dist/runtime/page.d.ts +25 -0
- package/dist/runtime/pg/bun.d.ts +30 -0
- package/dist/runtime/pg/bun.js +52 -0
- package/dist/runtime/pg/pg.d.ts +29 -0
- package/dist/runtime/pg/pg.js +52 -0
- package/dist/runtime/pg/pglite.js +16 -17
- package/dist/runtime/pg/postgresjs.js +14 -24
- package/dist/runtime/plan.d.ts +146 -7
- package/dist/runtime/preset-stack.d.ts +17 -0
- package/dist/runtime/presets.d.ts +48 -0
- package/dist/runtime/read-stream.d.ts +26 -0
- package/dist/runtime/safety/index.d.ts +159 -0
- package/dist/runtime/safety/index.js +294 -0
- package/dist/runtime/safety-hook.d.ts +42 -0
- package/dist/runtime/server-check.d.ts +34 -0
- package/dist/runtime/tenancy/index.d.ts +37 -0
- package/dist/runtime/tenancy/index.js +365 -0
- package/dist/runtime/trait-read.d.ts +30 -0
- package/dist/runtime/traits/index.d.ts +128 -0
- package/dist/runtime/traits/index.js +509 -0
- package/dist/runtime/transaction.d.ts +19 -0
- package/dist/runtime/tx.d.ts +22 -7
- package/dist/runtime/types.d.ts +390 -25
- package/dist/runtime/validate/bind.d.ts +11 -0
- package/dist/runtime/validate/closed.d.ts +16 -0
- package/dist/runtime/validate/engine.d.ts +41 -0
- package/dist/runtime/validate/hook.d.ts +17 -0
- package/dist/runtime/validate/index.d.ts +21 -0
- package/dist/runtime/validate/index.js +175 -0
- package/dist/runtime/validate/load.d.ts +13 -0
- package/dist/runtime/validate/places.d.ts +41 -0
- package/dist/runtime/validate/rules.d.ts +108 -0
- package/dist/runtime/validate/surface.d.ts +102 -0
- package/dist/runtime/write.d.ts +30 -5
- package/dist/shared/{fgqgsb5z.js → 0qkxbm1f.js} +2 -2
- package/dist/shared/0v1g424p.js +26 -0
- package/dist/shared/10tjenmd.js +210 -0
- package/dist/shared/{tb4ze1tr.js → 1dp5knt7.js} +95 -10
- package/dist/shared/38crxbgn.js +229 -0
- package/dist/shared/3a5ne750.js +158 -0
- package/dist/shared/4b9da9d7.js +138 -0
- package/dist/shared/4e4c6vq2.js +62 -0
- package/dist/shared/{ckb1p0a4.js → 4jzzghr6.js} +416 -83
- package/dist/shared/{ycwz42az.js → 52szzakm.js} +6 -26
- package/dist/shared/{kynx7vbm.js → 5z4q2zda.js} +604 -21
- package/dist/shared/672t5vym.js +12 -0
- package/dist/shared/7m88x3e9.js +45 -0
- package/dist/shared/7mht49yd.js +1091 -0
- package/dist/shared/{mtkwv8wx.js → 7p2wbz7f.js} +1 -1
- package/dist/shared/83eaefwr.js +10 -0
- package/dist/shared/96ps4g1z.js +32 -0
- package/dist/shared/97g2jnrw.js +76 -0
- package/dist/shared/{xy79j5hq.js → 9k6vetvf.js} +25 -36
- package/dist/shared/{w8samvk1.js → a1gdp7j9.js} +1 -1
- package/dist/shared/a5sxz52y.js +13 -0
- package/dist/shared/{dynq16vb.js → b066g80h.js} +8 -6
- package/dist/shared/cstgkzdx.js +154 -0
- package/dist/shared/{ax16zpvm.js → d04j53v8.js} +6 -5
- package/dist/shared/{y9m15dfv.js → d2whjz4k.js} +15 -9
- package/dist/shared/dd4f8mnx.js +307 -0
- package/dist/shared/e6wg00wd.js +164 -0
- package/dist/shared/e7a268fy.js +195 -0
- package/dist/shared/{vkm3q4x1.js → eaxwrgby.js} +4 -3
- package/dist/shared/{7ck0dtry.js → ecdzej21.js} +160 -81
- package/dist/shared/{px90pf9p.js → edwqqryd.js} +21 -16
- package/dist/shared/{g2v6gnc8.js → eqmtn1bj.js} +57 -5
- package/dist/shared/g1b5zzj9.js +75 -0
- package/dist/shared/g9f0zqce.js +376 -0
- package/dist/shared/ge03bfca.js +64 -0
- package/dist/shared/{79ggp945.js → jns2wx4t.js} +55 -12
- package/dist/shared/k3ted12y.js +33 -0
- package/dist/shared/k7dwkhvq.js +31 -0
- package/dist/shared/{hd293j9g.js → kyxb62mc.js} +14 -2
- package/dist/shared/m9tv7xn7.js +404 -0
- package/dist/shared/mrq77sgw.js +55 -0
- package/dist/shared/mxmcdsh5.js +450 -0
- package/dist/shared/n442h54c.js +14 -0
- package/dist/shared/n47rd821.js +442 -0
- package/dist/shared/n4jqhpqz.js +27 -0
- package/dist/shared/{mkjmqjv4.js → nfzc5z0w.js} +2 -104
- package/dist/shared/nsva2a6b.js +42 -0
- package/dist/shared/ps43134h.js +69 -0
- package/dist/shared/{8b04d28c.js → py3nh7jy.js} +2 -2
- package/dist/shared/qa5dsc7z.js +86 -0
- package/dist/shared/{g5fhqmwe.js → qxa0yb9j.js} +6 -4
- package/dist/shared/r6nwkxnt.js +86 -0
- package/dist/shared/{4j72449d.js → rtnaqre0.js} +3 -3
- package/dist/shared/rwmrejjz.js +26 -0
- package/dist/shared/{zhc5vmgf.js → s6gqfn5j.js} +2 -2
- package/dist/shared/sacqha1y.js +70 -0
- package/dist/shared/{qk3hxfja.js → sfh4pk75.js} +5 -5
- package/dist/shared/{j5cn2963.js → t4tbsy9h.js} +13 -64
- package/dist/shared/{ngk8gj61.js → tbszx8mz.js} +15 -1
- package/dist/shared/v18jva5b.js +90 -0
- package/dist/shared/x9v1qgeb.js +201 -0
- package/dist/shared/xatfg0gn.js +193 -0
- package/dist/shared/xkz8g6by.js +225 -0
- package/dist/shared/{f5thc1s0.js → xrfnq5k8.js} +237 -418
- package/dist/shared/ytg3xf9p.js +149 -0
- package/dist/shared/yw5c610b.js +48 -0
- package/dist/shared/zd93g3rq.js +31 -0
- package/dist/shared/zt8y0wzb.js +132 -0
- package/dist/shared/{p0cbr3g2.js → zwmqxb0g.js} +1 -1
- package/dist/tooling/cli.js +14 -10
- package/dist/tooling/migrate/commands.d.ts +10 -0
- package/dist/tooling/migrate/engine.d.ts +36 -0
- package/package.json +56 -4
- package/dist/shared/gk7cs7sp.js +0 -566
|
@@ -0,0 +1,1091 @@
|
|
|
1
|
+
import {
|
|
2
|
+
encodeText,
|
|
3
|
+
decodeText
|
|
4
|
+
} from "./sacqha1y.js";
|
|
5
|
+
import {
|
|
6
|
+
catalog,
|
|
7
|
+
enumType,
|
|
8
|
+
sameEnumLabels
|
|
9
|
+
} from "./qxa0yb9j.js";
|
|
10
|
+
import {
|
|
11
|
+
snakeCase,
|
|
12
|
+
emittedTypeName,
|
|
13
|
+
rejectLater
|
|
14
|
+
} from "./x9v1qgeb.js";
|
|
15
|
+
import {
|
|
16
|
+
withLocation
|
|
17
|
+
} from "./k7dwkhvq.js";
|
|
18
|
+
import {
|
|
19
|
+
ColumnBuilder2,
|
|
20
|
+
formatType
|
|
21
|
+
} from "./dd4f8mnx.js";
|
|
22
|
+
import {
|
|
23
|
+
definition,
|
|
24
|
+
unavailable,
|
|
25
|
+
quoteLiteral
|
|
26
|
+
} from "./xatfg0gn.js";
|
|
27
|
+
import {
|
|
28
|
+
staticNamespace2,
|
|
29
|
+
table3,
|
|
30
|
+
column2,
|
|
31
|
+
index3,
|
|
32
|
+
constraint2,
|
|
33
|
+
sequence2,
|
|
34
|
+
compareText
|
|
35
|
+
} from "./t4tbsy9h.js";
|
|
36
|
+
import {
|
|
37
|
+
assertIdentifier2,
|
|
38
|
+
fitIdentifier2,
|
|
39
|
+
assertStoredText
|
|
40
|
+
} from "./nfzc5z0w.js";
|
|
41
|
+
import {
|
|
42
|
+
OkmError2,
|
|
43
|
+
catalogError2,
|
|
44
|
+
throwNamed2
|
|
45
|
+
} from "./52szzakm.js";
|
|
46
|
+
import {
|
|
47
|
+
readClientGenerator
|
|
48
|
+
} from "./rwmrejjz.js";
|
|
49
|
+
|
|
50
|
+
// src/dialects/pg/through.ts
|
|
51
|
+
var emit = (planner, schema, relation, child, sink, parent, alias, depth) => {
|
|
52
|
+
const through = relation.through;
|
|
53
|
+
const join = through === undefined ? undefined : planner.indexes(schema).get(through.table);
|
|
54
|
+
if (through === undefined || join === undefined) {
|
|
55
|
+
return catalogError2("OKM1020", `Relation ${relation.name} names a join table that is not in the schema.`);
|
|
56
|
+
}
|
|
57
|
+
const { quote } = planner;
|
|
58
|
+
const joinAlias = `j${alias}`;
|
|
59
|
+
sink.mark(`via:${through.table}`);
|
|
60
|
+
sink.text(`${quote(join.model.sql)} ${joinAlias} join ${quote(child.model.sql)} ${alias} on `);
|
|
61
|
+
for (let index = 0;index < through.local.length; index += 1) {
|
|
62
|
+
const local = through.local[index];
|
|
63
|
+
const remote = through.remote[index];
|
|
64
|
+
if (local === undefined || remote === undefined)
|
|
65
|
+
continue;
|
|
66
|
+
if (index > 0)
|
|
67
|
+
sink.text(" and ");
|
|
68
|
+
sink.text(`${alias}.${quote(remote)} = ${joinAlias}.${quote(local)}`);
|
|
69
|
+
}
|
|
70
|
+
sink.text(" where ");
|
|
71
|
+
planner.join(sink, parent, joinAlias, relation);
|
|
72
|
+
planner.where(schema, join, undefined, sink, joinAlias, depth + 1, true);
|
|
73
|
+
};
|
|
74
|
+
function resolveThrough(scope, table, through, from, to) {
|
|
75
|
+
for (const name of [table, through]) {
|
|
76
|
+
if (!scope.tables.includes(name)) {
|
|
77
|
+
throwNamed2("OKM1020", name, scope.tables, `Relation ${scope.owner}.${scope.name} names ${name}, which is not in the schema. Accepted names: ${scope.tables.join(", ")}.`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const toOwner = pick(scope, through, scope.owner, from, `${scope.owner}.${scope.name}`, "from");
|
|
81
|
+
const toTable = pick(scope, through, table, to, `${scope.owner}.${scope.name}`, "to");
|
|
82
|
+
if (toOwner === toTable) {
|
|
83
|
+
catalogError2("OKM1021", `Relation ${scope.owner}.${scope.name} uses ${toOwner.fromField} on ${through} for both ends. Pass from and to.`);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
name: scope.name,
|
|
87
|
+
kind: "many",
|
|
88
|
+
table,
|
|
89
|
+
local: toOwner.remote,
|
|
90
|
+
remote: toOwner.local,
|
|
91
|
+
through: { table: through, local: toTable.local, remote: toTable.remote, emit }
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function pick(scope, through, target, field, label, end) {
|
|
95
|
+
const matches = scope.edges.filter((edge) => edge.fromTable === through && edge.toTable === target && (field === undefined || edge.fromField === field));
|
|
96
|
+
const [only] = matches;
|
|
97
|
+
if (matches.length === 1 && only !== undefined)
|
|
98
|
+
return only;
|
|
99
|
+
const fields = matches.map((edge) => edge.fromField).sort();
|
|
100
|
+
catalogError2("OKM1021", `Relation ${label} through ${through} is ambiguous at ${end}. Accepted columns: ${fields.length === 0 ? "(none)" : fields.join(", ")}.`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/dialects/pg/relations.ts
|
|
104
|
+
function one2(table, field) {
|
|
105
|
+
return { kind: "one", table, field };
|
|
106
|
+
}
|
|
107
|
+
function many2(table, field) {
|
|
108
|
+
return { kind: "many", table, field };
|
|
109
|
+
}
|
|
110
|
+
function isRelationCall(value) {
|
|
111
|
+
if (typeof value !== "object" || value === null)
|
|
112
|
+
return false;
|
|
113
|
+
if (!("kind" in value) || !("table" in value))
|
|
114
|
+
return false;
|
|
115
|
+
const kind = value.kind;
|
|
116
|
+
return (kind === "one" || kind === "many") && typeof value.table === "string";
|
|
117
|
+
}
|
|
118
|
+
function manyThrough2(table, options) {
|
|
119
|
+
const { through, from, to } = options;
|
|
120
|
+
return {
|
|
121
|
+
kind: "many",
|
|
122
|
+
table,
|
|
123
|
+
field: undefined,
|
|
124
|
+
through,
|
|
125
|
+
resolve: (scope) => resolveThrough(scope, table, through, from, to)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/dialects/pg/compile.ts
|
|
130
|
+
function compileColumn(builder, input) {
|
|
131
|
+
const state = builder.state;
|
|
132
|
+
const name = state.sqlName ?? input.name;
|
|
133
|
+
const dependencies = [];
|
|
134
|
+
if (state.extension !== undefined) {
|
|
135
|
+
assertIdentifier2(state.extension, "extension");
|
|
136
|
+
dependencies.push({ kind: "extension", name: state.extension });
|
|
137
|
+
}
|
|
138
|
+
if (state.typeDependency !== undefined) {
|
|
139
|
+
assertIdentifier2(state.typeDependency, "type");
|
|
140
|
+
dependencies.push({
|
|
141
|
+
kind: "type",
|
|
142
|
+
namespace: input.parent.namespace,
|
|
143
|
+
name: state.typeDependency
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (state.renamedFrom !== undefined) {
|
|
147
|
+
assertIdentifier2(state.renamedFrom, "renamed column");
|
|
148
|
+
}
|
|
149
|
+
if (state.comment !== undefined) {
|
|
150
|
+
assertStoredText(state.comment, "column comment");
|
|
151
|
+
}
|
|
152
|
+
if (state.picklist !== undefined) {
|
|
153
|
+
for (const value of state.picklist.values) {
|
|
154
|
+
assertStoredText(value, "picklist value");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const built = column2({
|
|
158
|
+
parent: input.parent,
|
|
159
|
+
name,
|
|
160
|
+
dataType: formatType(state.baseType, state.dims),
|
|
161
|
+
nullable: state.nullable,
|
|
162
|
+
provenance: input.provenance,
|
|
163
|
+
...input.owner !== undefined ? { owner: input.owner } : {},
|
|
164
|
+
...state.defaultSql !== undefined ? { defaultExpression: state.defaultSql } : {},
|
|
165
|
+
...state.collation !== undefined ? { collation: state.collation } : {},
|
|
166
|
+
...state.identity !== undefined ? { identity: state.identity } : {},
|
|
167
|
+
...state.generated !== undefined ? { generated: state.generated } : {},
|
|
168
|
+
...dependencies.length > 0 || (input.dependencies?.length ?? 0) > 0 ? { dependencies: [...dependencies, ...input.dependencies ?? []] } : {}
|
|
169
|
+
});
|
|
170
|
+
const check = state.picklist !== undefined && state.picklist.check ? constraint2({
|
|
171
|
+
parent: input.parent,
|
|
172
|
+
constraintKind: "check",
|
|
173
|
+
columns: [name],
|
|
174
|
+
nameKey: name,
|
|
175
|
+
expression: picklistExpression(name, state.picklist.values),
|
|
176
|
+
provenance: input.provenance,
|
|
177
|
+
...input.owner !== undefined ? { owner: input.owner } : {}
|
|
178
|
+
}) : undefined;
|
|
179
|
+
const unique = state.unique !== undefined ? constraint2({
|
|
180
|
+
parent: input.parent,
|
|
181
|
+
constraintKind: "unique",
|
|
182
|
+
columns: [name],
|
|
183
|
+
nameKey: name,
|
|
184
|
+
provenance: input.provenance,
|
|
185
|
+
...input.owner !== undefined ? { owner: input.owner } : {}
|
|
186
|
+
}) : undefined;
|
|
187
|
+
return {
|
|
188
|
+
column: built,
|
|
189
|
+
guarded: state.guarded,
|
|
190
|
+
hidden: state.hidden,
|
|
191
|
+
...check !== undefined ? { check } : {},
|
|
192
|
+
...unique !== undefined ? { unique } : {},
|
|
193
|
+
...state.renamedFrom !== undefined ? { renamedFrom: state.renamedFrom } : {},
|
|
194
|
+
...state.comment !== undefined ? { comment: state.comment } : {},
|
|
195
|
+
...state.unique?.reason !== undefined ? { uniqueReason: state.unique.reason } : {},
|
|
196
|
+
...state.unique?.global !== undefined ? { uniqueGlobal: state.unique.global } : {},
|
|
197
|
+
...state.domain !== undefined ? { domain: state.domain } : {}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function picklistExpression(name, values) {
|
|
201
|
+
const list = values.map((value) => quoteLiteral(value)).join(", ");
|
|
202
|
+
return `(${name} IN (${list}))`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/dialects/pg/tenancy.ts
|
|
206
|
+
function readTenancy(value) {
|
|
207
|
+
if (value === undefined)
|
|
208
|
+
return;
|
|
209
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
210
|
+
definition("schema() tenancy must be columnTenancy() from okmodel/tenancy.");
|
|
211
|
+
}
|
|
212
|
+
const record = value;
|
|
213
|
+
if (record.strategy !== "column" || typeof record.rewrite !== "function") {
|
|
214
|
+
definition("schema() tenancy must be columnTenancy() from okmodel/tenancy.");
|
|
215
|
+
}
|
|
216
|
+
return value;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/dialects/pg/schema.ts
|
|
220
|
+
var SCHEMA_KNOWN = new Set([
|
|
221
|
+
"casing",
|
|
222
|
+
"codecs",
|
|
223
|
+
"defaults",
|
|
224
|
+
"requires",
|
|
225
|
+
"tables",
|
|
226
|
+
"tenancy",
|
|
227
|
+
"traits",
|
|
228
|
+
"types",
|
|
229
|
+
"validation"
|
|
230
|
+
]);
|
|
231
|
+
var SCHEMA_LATER = {
|
|
232
|
+
extensions: "0.3",
|
|
233
|
+
functions: "0.3",
|
|
234
|
+
triggers: "0.3",
|
|
235
|
+
views: "0.3"
|
|
236
|
+
};
|
|
237
|
+
var BIGINT_CODECS = new Set(["string", "number", "bigint"]);
|
|
238
|
+
var NUMERIC_CODECS = new Set(["string", "number"]);
|
|
239
|
+
function stage(jobs, build) {
|
|
240
|
+
jobs.push((objects) => {
|
|
241
|
+
objects.push(build());
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
function schema2(config) {
|
|
245
|
+
rejectLater(config, SCHEMA_KNOWN, SCHEMA_LATER, "schema()");
|
|
246
|
+
const schemaTraits = openSchemaTraits(config.traits);
|
|
247
|
+
const tenancy = readTenancy(config.tenancy);
|
|
248
|
+
if (!Array.isArray(config.tables)) {
|
|
249
|
+
definition("schema() needs a tables array.");
|
|
250
|
+
}
|
|
251
|
+
const casing = readCasing(config.casing);
|
|
252
|
+
const tables = rewritePass(config.tables, tenancy, schemaTraits, casing);
|
|
253
|
+
const hooks = collectHooks(tenancy, schemaTraits, config.tables);
|
|
254
|
+
const types = readTypes(config.types);
|
|
255
|
+
const codecs = readCodecs(config.codecs);
|
|
256
|
+
const requires = readRequires(config.requires);
|
|
257
|
+
const idDefault = readIdDefault(config.defaults);
|
|
258
|
+
const namespace = staticNamespace2("public");
|
|
259
|
+
const accepted = acceptTables(config.tables);
|
|
260
|
+
const jobs = [];
|
|
261
|
+
const prepared = [];
|
|
262
|
+
const sqlNames = new Set;
|
|
263
|
+
const enums = new Map;
|
|
264
|
+
for (const item of tables) {
|
|
265
|
+
const built = compileTable(item, namespace, casing, codecs, requires, idDefault, jobs, enums, schemaTraits);
|
|
266
|
+
if (sqlNames.has(built.sqlName)) {
|
|
267
|
+
catalogError2("OKM1023", `Table ${built.tsName} uses SQL name ${built.sqlName}, which another table already uses. Accepted names: ${list(accepted)}.`);
|
|
268
|
+
}
|
|
269
|
+
sqlNames.add(built.sqlName);
|
|
270
|
+
prepared.push(built);
|
|
271
|
+
}
|
|
272
|
+
const byName = new Map;
|
|
273
|
+
for (const item of prepared) {
|
|
274
|
+
byName.set(item.tsName, item);
|
|
275
|
+
}
|
|
276
|
+
const edges = [];
|
|
277
|
+
for (const item of prepared) {
|
|
278
|
+
compileForeignKeys(item, byName, accepted, jobs, edges);
|
|
279
|
+
}
|
|
280
|
+
const model = {};
|
|
281
|
+
for (const item of prepared) {
|
|
282
|
+
model[item.tsName] = tableModel(item, edges, byName, accepted);
|
|
283
|
+
}
|
|
284
|
+
let document;
|
|
285
|
+
return {
|
|
286
|
+
get catalog() {
|
|
287
|
+
if (document === undefined) {
|
|
288
|
+
const objects = [];
|
|
289
|
+
for (const job of jobs)
|
|
290
|
+
job(objects);
|
|
291
|
+
for (const [name, note] of [...enums.entries()].sort((left, right) => compareText(left[0], right[0]))) {
|
|
292
|
+
objects.push(enumType({
|
|
293
|
+
namespace,
|
|
294
|
+
name,
|
|
295
|
+
labels: note.labels,
|
|
296
|
+
provenance: note.provenance
|
|
297
|
+
}));
|
|
298
|
+
}
|
|
299
|
+
document = catalog(objects);
|
|
300
|
+
}
|
|
301
|
+
return document;
|
|
302
|
+
},
|
|
303
|
+
types,
|
|
304
|
+
casing,
|
|
305
|
+
codecs,
|
|
306
|
+
requires,
|
|
307
|
+
tables: config.tables,
|
|
308
|
+
model,
|
|
309
|
+
...schemaTraits !== undefined && schemaTraits.length > 0 ? { traits: schemaTraits } : {},
|
|
310
|
+
...tenancy !== undefined ? { tenancy } : {},
|
|
311
|
+
...hooks !== undefined ? { hooks } : {},
|
|
312
|
+
...config.validation !== undefined ? { validation: config.validation } : {}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function acceptTables(tables) {
|
|
316
|
+
const names = [];
|
|
317
|
+
const seen = new Set;
|
|
318
|
+
const emitted = new Map;
|
|
319
|
+
for (const item of tables) {
|
|
320
|
+
if (typeof item.name !== "string" || item.name.length === 0) {
|
|
321
|
+
definition("schema() tables need a name.");
|
|
322
|
+
}
|
|
323
|
+
if (seen.has(item.name)) {
|
|
324
|
+
catalogError2("OKM1023", withLocation(`Table ${item.name} is declared more than once. Accepted names: ${list([...names].sort())}.`, item.source));
|
|
325
|
+
}
|
|
326
|
+
seen.add(item.name);
|
|
327
|
+
names.push(item.name);
|
|
328
|
+
const typeName = emittedTypeName(item.name);
|
|
329
|
+
const previous = emitted.get(typeName);
|
|
330
|
+
if (previous !== undefined) {
|
|
331
|
+
catalogError2("OKM1023", `Table ${item.name} and ${previous} both emit ${typeName}. Accepted names: ${list([...names].sort())}.`);
|
|
332
|
+
}
|
|
333
|
+
emitted.set(typeName, item.name);
|
|
334
|
+
}
|
|
335
|
+
names.sort();
|
|
336
|
+
return names;
|
|
337
|
+
}
|
|
338
|
+
function noteEnum(notes, labels, name, table, provenance) {
|
|
339
|
+
const previous = notes.get(name);
|
|
340
|
+
if (previous !== undefined && !sameEnumLabels(previous.labels, labels)) {
|
|
341
|
+
catalogError2("OKM1020", `Enum ${name} lists ${previous.labels.join(", ")} on ${previous.table} and ${labels.join(", ")} on ${table}. One enum has one label list.`);
|
|
342
|
+
}
|
|
343
|
+
if (previous === undefined || compareText(table, previous.table) < 0) {
|
|
344
|
+
notes.set(name, { labels, table, provenance });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function rewritePass(tables, tenancy, schemaTraits, casing) {
|
|
348
|
+
let next = tenancy === undefined ? bareTables(tables) : tenancy.rewrite(tables);
|
|
349
|
+
const seen = new Set;
|
|
350
|
+
const pending = [];
|
|
351
|
+
const add = (value) => {
|
|
352
|
+
if (seen.has(value))
|
|
353
|
+
return;
|
|
354
|
+
if (typeof value.rewrite !== "function")
|
|
355
|
+
return;
|
|
356
|
+
seen.add(value);
|
|
357
|
+
pending.push(value);
|
|
358
|
+
};
|
|
359
|
+
if (schemaTraits !== undefined) {
|
|
360
|
+
for (const trait of schemaTraits)
|
|
361
|
+
add(trait);
|
|
362
|
+
}
|
|
363
|
+
for (const item of tables) {
|
|
364
|
+
const own = item.options?.traits;
|
|
365
|
+
if (!Array.isArray(own))
|
|
366
|
+
continue;
|
|
367
|
+
for (const trait of own) {
|
|
368
|
+
if (typeof trait === "object" && trait !== null)
|
|
369
|
+
add(trait);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
for (const trait of pending) {
|
|
373
|
+
const rewrite = trait.rewrite;
|
|
374
|
+
next = rewrite.call(trait, next, casing, schemaTraits);
|
|
375
|
+
}
|
|
376
|
+
return next;
|
|
377
|
+
}
|
|
378
|
+
var registeredHooks = [];
|
|
379
|
+
function addSchemaHook(hook) {
|
|
380
|
+
registeredHooks.push(hook);
|
|
381
|
+
}
|
|
382
|
+
function collectHooks(tenancy, schemaTraits, tables) {
|
|
383
|
+
const found = [];
|
|
384
|
+
const seen = new Set;
|
|
385
|
+
const add = (value) => {
|
|
386
|
+
if (seen.has(value))
|
|
387
|
+
return;
|
|
388
|
+
const hook = value.hook;
|
|
389
|
+
if (typeof hook !== "function")
|
|
390
|
+
return;
|
|
391
|
+
seen.add(value);
|
|
392
|
+
found.push(hook.bind(value));
|
|
393
|
+
};
|
|
394
|
+
if (tenancy !== undefined)
|
|
395
|
+
add(tenancy);
|
|
396
|
+
if (schemaTraits !== undefined) {
|
|
397
|
+
for (const trait of schemaTraits)
|
|
398
|
+
add(trait);
|
|
399
|
+
}
|
|
400
|
+
for (const item of tables) {
|
|
401
|
+
const own = item.options?.traits;
|
|
402
|
+
if (!Array.isArray(own))
|
|
403
|
+
continue;
|
|
404
|
+
for (const trait of own) {
|
|
405
|
+
if (typeof trait === "object" && trait !== null)
|
|
406
|
+
add(trait);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
for (const hook of registeredHooks)
|
|
410
|
+
found.push(hook);
|
|
411
|
+
return found.length === 0 ? undefined : found;
|
|
412
|
+
}
|
|
413
|
+
function archiveModel(item) {
|
|
414
|
+
const archive = item.archiveModel;
|
|
415
|
+
return archive === undefined ? undefined : { archive };
|
|
416
|
+
}
|
|
417
|
+
function bareTables(tables) {
|
|
418
|
+
for (const item of tables) {
|
|
419
|
+
const mark = item.options?.tenancy;
|
|
420
|
+
if (mark !== undefined) {
|
|
421
|
+
definition(`Table ${item.name} sets tenancy, and the schema does not.`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return tables;
|
|
425
|
+
}
|
|
426
|
+
function openSchemaTraits(value) {
|
|
427
|
+
if (value === undefined)
|
|
428
|
+
return;
|
|
429
|
+
if (!Array.isArray(value))
|
|
430
|
+
definition("schema() traits must be a list of traits.");
|
|
431
|
+
return value.length === 0 ? undefined : value;
|
|
432
|
+
}
|
|
433
|
+
function openTableTraits(table, columns, schemaTraits, options) {
|
|
434
|
+
let shared = schemaTraits;
|
|
435
|
+
const omit = options?.omitDefaults;
|
|
436
|
+
if (omit !== undefined) {
|
|
437
|
+
if (typeof omit !== "string" || omit.trim().length === 0) {
|
|
438
|
+
definition(`Table ${table} omitDefaults needs a reason.`);
|
|
439
|
+
}
|
|
440
|
+
shared = undefined;
|
|
441
|
+
}
|
|
442
|
+
const own = options?.traits;
|
|
443
|
+
if (own !== undefined && !Array.isArray(own)) {
|
|
444
|
+
definition(`Table ${table} traits must be a list of traits.`);
|
|
445
|
+
}
|
|
446
|
+
const local = Array.isArray(own) && own.length > 0 ? own : undefined;
|
|
447
|
+
if ((shared === undefined || shared.length === 0) && local === undefined)
|
|
448
|
+
return;
|
|
449
|
+
const traits = local === undefined ? shared : shared === undefined || shared.length === 0 ? local : [...shared, ...local];
|
|
450
|
+
if (traits === undefined)
|
|
451
|
+
return;
|
|
452
|
+
const model = { columns: { ...columns }, presets: options?.presets };
|
|
453
|
+
for (const trait of traits)
|
|
454
|
+
trait.apply(model, { table });
|
|
455
|
+
return { ...model, traits };
|
|
456
|
+
}
|
|
457
|
+
function compileTable(item, namespace, casing, codecs, requires, idDefault, jobs, enums, schemaTraits) {
|
|
458
|
+
const options = item.options;
|
|
459
|
+
const applied = openTableTraits(item.name, item.columns, schemaTraits, item.options);
|
|
460
|
+
readTableNames(item.name, options);
|
|
461
|
+
const sqlName = options?.sqlName ?? (casing === "snake" ? snakeCase(item.name) : item.name);
|
|
462
|
+
assertIdentifier2(sqlName, `table ${item.name}`);
|
|
463
|
+
const provenance = item.source === undefined ? { origin: "file", name: item.name } : { origin: "file", name: item.name, source: item.source };
|
|
464
|
+
const parent = { namespace, name: sqlName };
|
|
465
|
+
stage(jobs, () => table3({ namespace, name: sqlName, provenance }));
|
|
466
|
+
const columns = [];
|
|
467
|
+
const columnPrimary = [];
|
|
468
|
+
const byField = new Map;
|
|
469
|
+
const bySql = new Map;
|
|
470
|
+
const handles = {};
|
|
471
|
+
const sourceColumns = applied === undefined ? item.columns : applied.columns;
|
|
472
|
+
for (const [field, builder] of Object.entries(sourceColumns)) {
|
|
473
|
+
if (!(builder instanceof ColumnBuilder2)) {
|
|
474
|
+
definition(`Column ${item.name}.${field} must be a column builder.`);
|
|
475
|
+
}
|
|
476
|
+
const column = applySchemaId(builder, idDefault);
|
|
477
|
+
const columnSql = column.state.sqlName ?? (casing === "snake" ? snakeCase(field) : field);
|
|
478
|
+
assertCodec(item.name, field, column, codecs);
|
|
479
|
+
const mark = applied !== undefined ? builder.trait : undefined;
|
|
480
|
+
const traitName = typeof mark === "string" ? mark : undefined;
|
|
481
|
+
const columnProvenance = traitName === undefined ? provenance : {
|
|
482
|
+
origin: "trait",
|
|
483
|
+
name: traitName,
|
|
484
|
+
...provenance.source !== undefined ? { source: provenance.source } : {}
|
|
485
|
+
};
|
|
486
|
+
const identity = column.state.identity;
|
|
487
|
+
if (identity !== undefined) {
|
|
488
|
+
const seqName = fitIdentifier2(`${sqlName}_${columnSql}_seq`);
|
|
489
|
+
jobs.push((objects) => {
|
|
490
|
+
const seq = sequence2({
|
|
491
|
+
namespace,
|
|
492
|
+
name: seqName,
|
|
493
|
+
dataType: "bigint",
|
|
494
|
+
provenance,
|
|
495
|
+
dependencies: [{ kind: "table", namespace, name: sqlName }]
|
|
496
|
+
});
|
|
497
|
+
objects.push(seq);
|
|
498
|
+
pushCompiled(objects, column, {
|
|
499
|
+
parent,
|
|
500
|
+
name: columnSql,
|
|
501
|
+
provenance: columnProvenance,
|
|
502
|
+
dependencies: [seq.identity]
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
} else {
|
|
506
|
+
jobs.push((objects) => {
|
|
507
|
+
pushCompiled(objects, column, {
|
|
508
|
+
parent,
|
|
509
|
+
name: columnSql,
|
|
510
|
+
provenance: columnProvenance
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
const decode = column.state.decode;
|
|
515
|
+
const prepared = {
|
|
516
|
+
field,
|
|
517
|
+
sqlName: columnSql,
|
|
518
|
+
dataType: formatType(column.state.baseType, column.state.dims),
|
|
519
|
+
nullable: column.state.nullable,
|
|
520
|
+
references: column.state.references,
|
|
521
|
+
encode: column.state.encode,
|
|
522
|
+
decode: decode === decodeText ? undefined : decode,
|
|
523
|
+
hidden: column.state.hidden,
|
|
524
|
+
sensitive: column.state.sensitive,
|
|
525
|
+
guarded: column.state.guarded,
|
|
526
|
+
writable: column.state.guarded !== true && column.state.omitWrite !== true && column.state.generated === undefined,
|
|
527
|
+
unique: column.state.unique !== undefined,
|
|
528
|
+
clientDefault: column.state.clientDefault === undefined ? undefined : "client",
|
|
529
|
+
elementEncode: column.state.dims > 0 ? column.state.elementEncode : undefined,
|
|
530
|
+
fill: column.state.clientDefault,
|
|
531
|
+
accepts: column.state.accepts
|
|
532
|
+
};
|
|
533
|
+
columns.push(prepared);
|
|
534
|
+
byField.set(field, prepared);
|
|
535
|
+
bySql.set(prepared.sqlName, prepared);
|
|
536
|
+
handles[field] = { name: prepared.sqlName };
|
|
537
|
+
if (column.state.primaryKey) {
|
|
538
|
+
if (column.state.nullable) {
|
|
539
|
+
catalogError2("OKM1020", `Primary key ${item.name}.${field} is nullable.`);
|
|
540
|
+
}
|
|
541
|
+
columnPrimary.push(prepared.sqlName);
|
|
542
|
+
}
|
|
543
|
+
if (column.state.defaultSql === "uuidv7()") {
|
|
544
|
+
refuseUuidV7(item.name, field, requires);
|
|
545
|
+
}
|
|
546
|
+
const labels = column.state.enumLabels;
|
|
547
|
+
if (labels !== undefined) {
|
|
548
|
+
noteEnum(enums, labels, column.state.typeDependency ?? prepared.sqlName, sqlName, provenance);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const optionPrimary = readPrimaryKey(item.name, options, byField);
|
|
552
|
+
if (columnPrimary.length > 1) {
|
|
553
|
+
catalogError2("OKM1020", `Table ${item.name} has more than one primary key (${columnPrimary.join(", ")}). One primary key is accepted.`);
|
|
554
|
+
}
|
|
555
|
+
if (columnPrimary.length > 0 && optionPrimary.length > 0) {
|
|
556
|
+
catalogError2("OKM1020", `Table ${item.name} has two primary keys. One primary key is accepted.`);
|
|
557
|
+
}
|
|
558
|
+
const primary = optionPrimary.length > 0 ? optionPrimary : columnPrimary;
|
|
559
|
+
if (primary.length > 0) {
|
|
560
|
+
stage(jobs, () => constraint2({
|
|
561
|
+
parent,
|
|
562
|
+
constraintKind: "primaryKey",
|
|
563
|
+
columns: primary,
|
|
564
|
+
provenance
|
|
565
|
+
}));
|
|
566
|
+
}
|
|
567
|
+
if (options?.unique !== undefined) {
|
|
568
|
+
compileUniques(item.name, options, parent, provenance, byField, jobs);
|
|
569
|
+
}
|
|
570
|
+
if (options?.indexes !== undefined) {
|
|
571
|
+
compileIndexes(item.name, options, handles, parent, provenance, jobs);
|
|
572
|
+
}
|
|
573
|
+
if (options?.checks !== undefined) {
|
|
574
|
+
compileChecks(item.name, options, handles, parent, provenance, byField, jobs);
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
tsName: item.name,
|
|
578
|
+
sqlName,
|
|
579
|
+
parent,
|
|
580
|
+
provenance,
|
|
581
|
+
columns,
|
|
582
|
+
primary,
|
|
583
|
+
uniques: uniqueTargets(primary, columns, bySql, options, handles),
|
|
584
|
+
byField,
|
|
585
|
+
bySql,
|
|
586
|
+
relationOptions: relationInput(item.name, options),
|
|
587
|
+
...applied !== undefined ? { traits: applied.traits } : {},
|
|
588
|
+
presets: applied?.presets ?? options?.presets,
|
|
589
|
+
...archiveModel(item)
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function uniqueTargets(primary, columns, bySql, options, handles) {
|
|
593
|
+
const uniques = [];
|
|
594
|
+
if (primary.length > 0) {
|
|
595
|
+
const fields = [];
|
|
596
|
+
for (const sqlName of primary) {
|
|
597
|
+
const column = bySql.get(sqlName);
|
|
598
|
+
if (column !== undefined)
|
|
599
|
+
fields.push(column.field);
|
|
600
|
+
}
|
|
601
|
+
if (fields.length > 0)
|
|
602
|
+
uniques.push(fields);
|
|
603
|
+
}
|
|
604
|
+
for (const column of columns) {
|
|
605
|
+
if (column.unique)
|
|
606
|
+
uniques.push([column.field]);
|
|
607
|
+
}
|
|
608
|
+
const named = options?.unique;
|
|
609
|
+
if (named !== undefined) {
|
|
610
|
+
for (const fields of Object.values(named))
|
|
611
|
+
uniques.push([...fields]);
|
|
612
|
+
}
|
|
613
|
+
const build = options?.indexes;
|
|
614
|
+
if (build !== undefined) {
|
|
615
|
+
const calls = build(handles);
|
|
616
|
+
if (Array.isArray(calls)) {
|
|
617
|
+
for (const call of calls) {
|
|
618
|
+
if (call.isUnique !== true || call.predicate === undefined)
|
|
619
|
+
continue;
|
|
620
|
+
const fields = [];
|
|
621
|
+
let whole = true;
|
|
622
|
+
for (const sqlName of call.columns) {
|
|
623
|
+
const column = bySql.get(sqlName);
|
|
624
|
+
if (column === undefined) {
|
|
625
|
+
whole = false;
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
fields.push(column.field);
|
|
629
|
+
}
|
|
630
|
+
if (whole && fields.length > 0)
|
|
631
|
+
uniques.push(fields);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return uniques;
|
|
636
|
+
}
|
|
637
|
+
function relationInput(tableName, options) {
|
|
638
|
+
const relations = options?.relations;
|
|
639
|
+
if (relations === undefined)
|
|
640
|
+
return;
|
|
641
|
+
if (typeof relations !== "object" || relations === null || Array.isArray(relations)) {
|
|
642
|
+
definition(`Table ${tableName} relations must be an object of one() and many() calls.`);
|
|
643
|
+
}
|
|
644
|
+
return relations;
|
|
645
|
+
}
|
|
646
|
+
function pushCompiled(objects, column, input) {
|
|
647
|
+
const compiled = compileColumn(column, input);
|
|
648
|
+
objects.push(compiled.column);
|
|
649
|
+
if (compiled.check !== undefined)
|
|
650
|
+
objects.push(compiled.check);
|
|
651
|
+
if (compiled.unique !== undefined)
|
|
652
|
+
objects.push(compiled.unique);
|
|
653
|
+
}
|
|
654
|
+
function compileUniques(tableName, options, parent, provenance, byField, jobs) {
|
|
655
|
+
const unique = options?.unique;
|
|
656
|
+
if (unique === undefined) {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
const accepted = [...byField.keys()].sort();
|
|
660
|
+
for (const [name, fields] of Object.entries(unique)) {
|
|
661
|
+
if (fields.length === 0) {
|
|
662
|
+
definition(`Unique ${tableName}.${name} needs a column.`);
|
|
663
|
+
}
|
|
664
|
+
const columns = [];
|
|
665
|
+
for (const field of fields) {
|
|
666
|
+
const column = byField.get(field);
|
|
667
|
+
if (column === undefined) {
|
|
668
|
+
throwNamed2("OKM1020", field, accepted, `Unique ${tableName}.${name} names ${field}, which is not a column of ${tableName}. Accepted names: ${list(accepted)}.`);
|
|
669
|
+
}
|
|
670
|
+
columns.push(column.sqlName);
|
|
671
|
+
}
|
|
672
|
+
stage(jobs, () => constraint2({
|
|
673
|
+
parent,
|
|
674
|
+
constraintKind: "unique",
|
|
675
|
+
columns,
|
|
676
|
+
nameKey: name,
|
|
677
|
+
provenance
|
|
678
|
+
}));
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
function compileIndexes(tableName, options, handles, parent, provenance, jobs) {
|
|
682
|
+
const build = options?.indexes;
|
|
683
|
+
if (build === undefined) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
const calls = build(handles);
|
|
687
|
+
if (!Array.isArray(calls)) {
|
|
688
|
+
definition(`Table ${tableName} indexes must return an array.`);
|
|
689
|
+
}
|
|
690
|
+
for (const call of calls) {
|
|
691
|
+
const built = readIndex(tableName, call);
|
|
692
|
+
stage(jobs, () => index3({
|
|
693
|
+
parent,
|
|
694
|
+
columns: built.columns,
|
|
695
|
+
unique: built.isUnique === true,
|
|
696
|
+
nameKey: built.columns.join("_"),
|
|
697
|
+
...built.predicate !== undefined ? { predicate: built.predicate } : {},
|
|
698
|
+
provenance
|
|
699
|
+
}));
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function compileChecks(tableName, options, handles, parent, provenance, byField, jobs) {
|
|
703
|
+
const checks = options?.checks;
|
|
704
|
+
if (checks === undefined) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
for (const [name, build] of Object.entries(checks)) {
|
|
708
|
+
const used = [];
|
|
709
|
+
const value = build(track(handles, used));
|
|
710
|
+
const text = readCheck(tableName, name, value);
|
|
711
|
+
const columns = [];
|
|
712
|
+
for (const field of used) {
|
|
713
|
+
const column = byField.get(field);
|
|
714
|
+
if (column !== undefined) {
|
|
715
|
+
columns.push(column.sqlName);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
stage(jobs, () => constraint2({
|
|
719
|
+
parent,
|
|
720
|
+
constraintKind: "check",
|
|
721
|
+
columns,
|
|
722
|
+
nameKey: name,
|
|
723
|
+
expression: text,
|
|
724
|
+
provenance
|
|
725
|
+
}));
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
function compileForeignKeys(item, byName, accepted, jobs, edges) {
|
|
729
|
+
for (const column of item.columns) {
|
|
730
|
+
const reference = column.references;
|
|
731
|
+
if (reference === undefined) {
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
const target = byName.get(reference.table);
|
|
735
|
+
if (target === undefined) {
|
|
736
|
+
throwNamed2("OKM1020", reference.table, accepted, `Table ${reference.table} is not in the schema. Accepted names: ${list(accepted)}.`);
|
|
737
|
+
}
|
|
738
|
+
const localColumns = localKey(item, column, reference);
|
|
739
|
+
const targetColumns = resolveTarget(item, column, target, reference);
|
|
740
|
+
if (localColumns.length !== targetColumns.length) {
|
|
741
|
+
catalogError2("OKM1021", `Foreign key ${item.tsName}.${column.field} on ${target.tsName} has ${localColumns.length} local columns and ${targetColumns.length} target columns.`);
|
|
742
|
+
}
|
|
743
|
+
if (reference.onDelete === "set null" || reference.onUpdate === "set null") {
|
|
744
|
+
for (const local of localColumns) {
|
|
745
|
+
if (!local.nullable) {
|
|
746
|
+
definition(`Foreign key ${item.tsName}.${column.field} uses set null. ${item.tsName}.${local.field} must be nullable.`);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
const localNames = [];
|
|
751
|
+
const remoteNames = [];
|
|
752
|
+
for (let index = 0;index < localColumns.length; index += 1) {
|
|
753
|
+
const local = localColumns[index];
|
|
754
|
+
const remote = targetColumns[index];
|
|
755
|
+
if (local === undefined || remote === undefined)
|
|
756
|
+
continue;
|
|
757
|
+
if (local.dataType !== remote.dataType) {
|
|
758
|
+
catalogError2("OKM1022", `Foreign key ${item.tsName}.${local.field} is ${local.dataType} and ${target.tsName}.${remote.field} is ${remote.dataType}. Accepted type: ${remote.dataType}.`);
|
|
759
|
+
}
|
|
760
|
+
localNames.push(local.sqlName);
|
|
761
|
+
remoteNames.push(remote.sqlName);
|
|
762
|
+
}
|
|
763
|
+
stage(jobs, () => constraint2({
|
|
764
|
+
parent: item.parent,
|
|
765
|
+
constraintKind: "foreignKey",
|
|
766
|
+
columns: localNames,
|
|
767
|
+
nameKey: column.sqlName,
|
|
768
|
+
provenance: item.provenance,
|
|
769
|
+
references: {
|
|
770
|
+
parent: target.parent,
|
|
771
|
+
columns: remoteNames,
|
|
772
|
+
...reference.onDelete !== undefined ? { onDelete: reference.onDelete } : {},
|
|
773
|
+
...reference.onUpdate !== undefined ? { onUpdate: reference.onUpdate } : {}
|
|
774
|
+
}
|
|
775
|
+
}));
|
|
776
|
+
edges.push({
|
|
777
|
+
fromTable: item.tsName,
|
|
778
|
+
fromField: column.field,
|
|
779
|
+
toTable: target.tsName,
|
|
780
|
+
local: localNames,
|
|
781
|
+
remote: remoteNames
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
function localKey(item, column, reference) {
|
|
786
|
+
const along = reference.along;
|
|
787
|
+
if (along === undefined || along.length === 0)
|
|
788
|
+
return [column];
|
|
789
|
+
const accepted = [...item.byField.keys()].sort();
|
|
790
|
+
const columns = [column];
|
|
791
|
+
for (const name of along) {
|
|
792
|
+
const extra = item.byField.get(name) ?? item.bySql.get(name);
|
|
793
|
+
if (extra === undefined) {
|
|
794
|
+
throwNamed2("OKM1021", name, accepted, `Foreign key ${item.tsName}.${column.field} includes ${name}, which is not a column. Accepted columns: ${list(accepted)}.`);
|
|
795
|
+
}
|
|
796
|
+
columns.push(extra);
|
|
797
|
+
}
|
|
798
|
+
return columns;
|
|
799
|
+
}
|
|
800
|
+
function resolveTarget(item, column, target, reference) {
|
|
801
|
+
const accepted = [...target.byField.keys()].sort();
|
|
802
|
+
const named = reference.columns;
|
|
803
|
+
if (named !== undefined) {
|
|
804
|
+
const found = [];
|
|
805
|
+
for (const name of named) {
|
|
806
|
+
const match = target.byField.get(name) ?? target.bySql.get(name);
|
|
807
|
+
if (match === undefined) {
|
|
808
|
+
throwNamed2("OKM1021", name, accepted, `Foreign key ${item.tsName}.${column.field} references ${name} on ${target.tsName}, which is not a column. Accepted columns: ${list(accepted)}.`);
|
|
809
|
+
}
|
|
810
|
+
found.push(match);
|
|
811
|
+
}
|
|
812
|
+
if (found.length !== 1 && reference.along === undefined) {
|
|
813
|
+
catalogError2("OKM1021", `Foreign key ${item.tsName}.${column.field} on ${target.tsName} is ambiguous. Accepted columns: ${list(accepted)}.`);
|
|
814
|
+
}
|
|
815
|
+
return found;
|
|
816
|
+
}
|
|
817
|
+
if (target.primary.length === 0) {
|
|
818
|
+
catalogError2("OKM1021", `Foreign key ${item.tsName}.${column.field} on ${target.tsName} has no primary key. Accepted columns: ${list(accepted)}.`);
|
|
819
|
+
}
|
|
820
|
+
if (target.primary.length !== 1) {
|
|
821
|
+
catalogError2("OKM1021", `Foreign key ${item.tsName}.${column.field} on ${target.tsName} is ambiguous. Accepted columns: ${list(target.primary)}.`);
|
|
822
|
+
}
|
|
823
|
+
const sqlName = target.primary[0] ?? "";
|
|
824
|
+
const match = target.bySql.get(sqlName);
|
|
825
|
+
if (match === undefined) {
|
|
826
|
+
catalogError2("OKM1021", `Foreign key ${item.tsName}.${column.field} on ${target.tsName} is missing its target. Accepted columns: ${list(accepted)}.`);
|
|
827
|
+
}
|
|
828
|
+
return [match];
|
|
829
|
+
}
|
|
830
|
+
function assertCodec(tableName, field, builder, codecs) {
|
|
831
|
+
const base = builder.state.baseType;
|
|
832
|
+
if (base === "bigint" && codecs.bigint !== "string") {
|
|
833
|
+
const label = builder.state.typeLabel ?? "string";
|
|
834
|
+
if (label !== codecs.bigint) {
|
|
835
|
+
definition(`Schema codecs.bigint is ${codecs.bigint}, and ${tableName}.${field} was built as ${label}. Set as: "${codecs.bigint}" on that column.`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (base.startsWith("numeric") && codecs.numeric !== "string") {
|
|
839
|
+
const label = builder.state.typeLabel ?? "string";
|
|
840
|
+
if (label !== codecs.numeric) {
|
|
841
|
+
definition(`Schema codecs.numeric is ${codecs.numeric}, and ${tableName}.${field} was built as ${label}. Set as: "${codecs.numeric}" on that column.`);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function readTableNames(tableName, options) {
|
|
846
|
+
if (options?.sqlName !== undefined) {
|
|
847
|
+
if (options.sqlName.length === 0) {
|
|
848
|
+
definition(`Table ${tableName} sqlName must be a non-empty SQL identifier.`);
|
|
849
|
+
}
|
|
850
|
+
assertIdentifier2(options.sqlName, `table ${tableName} sqlName`);
|
|
851
|
+
}
|
|
852
|
+
if (options?.renamedFrom !== undefined) {
|
|
853
|
+
if (options.renamedFrom.length === 0) {
|
|
854
|
+
definition(`Table ${tableName} renamedFrom must be a non-empty previous name.`);
|
|
855
|
+
}
|
|
856
|
+
assertIdentifier2(options.renamedFrom, `table ${tableName} renamedFrom`);
|
|
857
|
+
}
|
|
858
|
+
if (options?.comment !== undefined) {
|
|
859
|
+
assertStoredText(options.comment, `table ${tableName} comment`);
|
|
860
|
+
if (options.comment.length === 0) {
|
|
861
|
+
definition(`Table ${tableName} comment must be non-empty.`);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
function readTypes(types) {
|
|
866
|
+
if (types === undefined) {
|
|
867
|
+
return "emitted";
|
|
868
|
+
}
|
|
869
|
+
if (types !== "emitted" && types !== "inferred") {
|
|
870
|
+
definition(`schema() types ${String(types)} must be emitted or inferred.`);
|
|
871
|
+
}
|
|
872
|
+
return types;
|
|
873
|
+
}
|
|
874
|
+
function readCasing(casing) {
|
|
875
|
+
if (casing === undefined) {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
if (casing !== "snake") {
|
|
879
|
+
definition(`schema() casing ${String(casing)} must be snake.`);
|
|
880
|
+
}
|
|
881
|
+
return casing;
|
|
882
|
+
}
|
|
883
|
+
function readCodecs(codecs) {
|
|
884
|
+
const bigint = codecs?.bigint ?? "string";
|
|
885
|
+
const numeric = codecs?.numeric ?? "string";
|
|
886
|
+
const timestamps = codecs?.timestamps ?? "temporal";
|
|
887
|
+
if (!BIGINT_CODECS.has(bigint)) {
|
|
888
|
+
definition(`schema() codecs.bigint ${String(bigint)} must be string, number, or bigint.`);
|
|
889
|
+
}
|
|
890
|
+
if (!NUMERIC_CODECS.has(numeric)) {
|
|
891
|
+
definition(`schema() codecs.numeric ${String(numeric)} must be string or number.`);
|
|
892
|
+
}
|
|
893
|
+
if (timestamps !== "temporal") {
|
|
894
|
+
definition(`schema() codecs.timestamps ${String(timestamps)} must be temporal.`);
|
|
895
|
+
}
|
|
896
|
+
return { bigint, numeric, timestamps };
|
|
897
|
+
}
|
|
898
|
+
function tableModel(item, edges, byName, accepted) {
|
|
899
|
+
const primary = [];
|
|
900
|
+
for (const sqlName of item.primary) {
|
|
901
|
+
const column = item.bySql.get(sqlName);
|
|
902
|
+
if (column !== undefined)
|
|
903
|
+
primary.push(column.field);
|
|
904
|
+
}
|
|
905
|
+
let conceal = false;
|
|
906
|
+
const columns = item.columns.map((column) => {
|
|
907
|
+
if (column.hidden || column.sensitive)
|
|
908
|
+
conceal = true;
|
|
909
|
+
return {
|
|
910
|
+
field: column.field,
|
|
911
|
+
sql: column.sqlName,
|
|
912
|
+
dataType: column.dataType,
|
|
913
|
+
encode: column.encode,
|
|
914
|
+
decode: column.decode,
|
|
915
|
+
hidden: column.hidden,
|
|
916
|
+
sensitive: column.sensitive,
|
|
917
|
+
guarded: column.guarded,
|
|
918
|
+
writable: column.writable,
|
|
919
|
+
guardUpdate: column.writable && primary.includes(column.field),
|
|
920
|
+
...column.fill !== undefined ? { fill: column.fill, clientDefault: "client" } : {},
|
|
921
|
+
...column.elementEncode !== undefined ? { elementEncode: column.elementEncode } : {},
|
|
922
|
+
accepts: column.accepts
|
|
923
|
+
};
|
|
924
|
+
});
|
|
925
|
+
return {
|
|
926
|
+
name: item.tsName,
|
|
927
|
+
sql: item.sqlName,
|
|
928
|
+
primary,
|
|
929
|
+
uniques: item.uniques,
|
|
930
|
+
columns,
|
|
931
|
+
relations: resolveRelations(item, edges, byName, accepted),
|
|
932
|
+
...conceal ? { conceal: true } : {},
|
|
933
|
+
...item.traits !== undefined ? { traits: item.traits } : {},
|
|
934
|
+
...item.provenance.source !== undefined ? { source: item.provenance.source } : {},
|
|
935
|
+
...item.archive !== undefined ? { archive: item.archive } : {},
|
|
936
|
+
...item.presets !== undefined ? { presets: item.presets } : {}
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
function resolveRelations(item, edges, byName, accepted) {
|
|
940
|
+
const raw = item.relationOptions;
|
|
941
|
+
if (raw === undefined)
|
|
942
|
+
return [];
|
|
943
|
+
const record = raw;
|
|
944
|
+
const names = Object.keys(record);
|
|
945
|
+
const resolved = [];
|
|
946
|
+
const seen = new Set;
|
|
947
|
+
for (const name of names) {
|
|
948
|
+
if (seen.has(name))
|
|
949
|
+
continue;
|
|
950
|
+
seen.add(name);
|
|
951
|
+
const call = record[name];
|
|
952
|
+
if (!isRelationCall(call)) {
|
|
953
|
+
unavailable(`Table ${item.tsName} relation ${name} is not available yet. one(), many(), and manyThrough() are accepted. morph arrives later.`);
|
|
954
|
+
}
|
|
955
|
+
const own = call.resolve;
|
|
956
|
+
if (own !== undefined) {
|
|
957
|
+
resolved.push(own({ owner: item.tsName, name, edges, tables: accepted }));
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
if (!byName.has(call.table)) {
|
|
961
|
+
throwNamed2("OKM1020", call.table, accepted, `Relation ${item.tsName}.${name} names ${call.table}, which is not in the schema. Accepted names: ${list(accepted)}.`);
|
|
962
|
+
}
|
|
963
|
+
const field = call.field;
|
|
964
|
+
const matches = call.kind === "one" ? edges.filter((edge) => edge.fromTable === item.tsName && edge.toTable === call.table && (field === undefined || edge.fromField === field)) : edges.filter((edge) => edge.toTable === item.tsName && edge.fromTable === call.table && (field === undefined || edge.fromField === field));
|
|
965
|
+
if (matches.length !== 1) {
|
|
966
|
+
const hint = matches.map((edge) => edge.fromField).sort();
|
|
967
|
+
const which = hint.length === 0 ? "none" : hint.join(", ");
|
|
968
|
+
catalogError2("OKM1021", `Relation ${item.tsName}.${name} on ${call.table} is ambiguous. Accepted columns: ${which}.`);
|
|
969
|
+
}
|
|
970
|
+
const edge = matches[0];
|
|
971
|
+
if (edge === undefined) {
|
|
972
|
+
catalogError2("OKM1021", `Relation ${item.tsName}.${name} on ${call.table} is ambiguous. Accepted columns: (none).`);
|
|
973
|
+
}
|
|
974
|
+
resolved.push(call.kind === "one" ? { name, kind: "one", table: call.table, local: edge.local, remote: edge.remote } : { name, kind: "many", table: call.table, local: edge.remote, remote: edge.local });
|
|
975
|
+
}
|
|
976
|
+
return resolved;
|
|
977
|
+
}
|
|
978
|
+
function readPrimaryKey(tableName, options, byField) {
|
|
979
|
+
const listed = options?.primaryKey;
|
|
980
|
+
if (listed === undefined)
|
|
981
|
+
return [];
|
|
982
|
+
if (!Array.isArray(listed) || listed.length === 0) {
|
|
983
|
+
definition(`Table ${tableName} primaryKey needs a column.`);
|
|
984
|
+
}
|
|
985
|
+
const columns = [];
|
|
986
|
+
const seen = new Set;
|
|
987
|
+
for (const field of listed) {
|
|
988
|
+
if (typeof field !== "string" || field.length === 0 || seen.has(field)) {
|
|
989
|
+
definition(`Table ${tableName} primaryKey repeats a column.`);
|
|
990
|
+
}
|
|
991
|
+
seen.add(field);
|
|
992
|
+
const column = byField.get(field);
|
|
993
|
+
if (column === undefined || column.nullable) {
|
|
994
|
+
catalogError2("OKM1020", `Primary key ${tableName}.${field} is missing or nullable.`);
|
|
995
|
+
}
|
|
996
|
+
columns.push(column.sqlName);
|
|
997
|
+
}
|
|
998
|
+
return columns;
|
|
999
|
+
}
|
|
1000
|
+
function applySchemaId(column, idDefault) {
|
|
1001
|
+
if (idDefault === undefined || column.state.idSource !== "implicit")
|
|
1002
|
+
return column;
|
|
1003
|
+
const fill = idDefault.kind === "client" ? idDefault.fill : undefined;
|
|
1004
|
+
const okid = fill?.name === "okid";
|
|
1005
|
+
return new ColumnBuilder2({
|
|
1006
|
+
...column.state,
|
|
1007
|
+
defaultSql: idDefault.kind === "database" ? idDefault.sql : undefined,
|
|
1008
|
+
clientDefault: fill,
|
|
1009
|
+
idSource: "column",
|
|
1010
|
+
...okid ? {
|
|
1011
|
+
baseType: "text",
|
|
1012
|
+
collation: "C",
|
|
1013
|
+
encode: encodeText,
|
|
1014
|
+
decode: decodeText,
|
|
1015
|
+
sqlForm: "quote"
|
|
1016
|
+
} : {}
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
function readIdDefault(defaults) {
|
|
1020
|
+
if (defaults === undefined)
|
|
1021
|
+
return;
|
|
1022
|
+
if (typeof defaults !== "object" || defaults === null) {
|
|
1023
|
+
definition("schema() defaults must be an object.");
|
|
1024
|
+
}
|
|
1025
|
+
for (const key of Object.keys(defaults)) {
|
|
1026
|
+
if (key !== "id") {
|
|
1027
|
+
definition(`schema() defaults.${key} is not a schema default. Accepted names: id.`);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
const id = defaults.id;
|
|
1031
|
+
if (id === undefined)
|
|
1032
|
+
return;
|
|
1033
|
+
if (id === "uuidv7")
|
|
1034
|
+
return { kind: "database", sql: "uuidv7()" };
|
|
1035
|
+
if (id === "uuidv4")
|
|
1036
|
+
return { kind: "database", sql: "gen_random_uuid()" };
|
|
1037
|
+
const fill = readClientGenerator(id);
|
|
1038
|
+
if (fill !== undefined)
|
|
1039
|
+
return { kind: "client", fill };
|
|
1040
|
+
definition('schema() defaults.id must be "uuidv7", "uuidv4", or a client generator.');
|
|
1041
|
+
}
|
|
1042
|
+
function refuseUuidV7(tableName, field, requires) {
|
|
1043
|
+
const declared = Number(/^>=(\d+)$/.exec(requires?.postgres ?? "")?.[1]);
|
|
1044
|
+
if (!(declared > 0) || declared >= 18)
|
|
1045
|
+
return;
|
|
1046
|
+
throw new OkmError2("OKM1812", `Column ${tableName}.${field} uses uuidv7(). Set defaults.id to "uuidv4" or a client generator.`, { fix: { summary: "Set defaults.id." } });
|
|
1047
|
+
}
|
|
1048
|
+
function readRequires(requires) {
|
|
1049
|
+
if (requires === undefined) {
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
const postgres = requires.postgres;
|
|
1053
|
+
if (postgres !== undefined && postgres.length === 0) {
|
|
1054
|
+
definition("schema() requires.postgres must be a version range such as >=17.");
|
|
1055
|
+
}
|
|
1056
|
+
return postgres === undefined ? {} : { postgres };
|
|
1057
|
+
}
|
|
1058
|
+
function readIndex(tableName, call) {
|
|
1059
|
+
if (call.columns.length === 0) {
|
|
1060
|
+
definition(`Index on ${tableName} needs a column.`);
|
|
1061
|
+
}
|
|
1062
|
+
for (const name of call.columns) {
|
|
1063
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
1064
|
+
definition(`Index on ${tableName} must be built from column handles.`);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return call;
|
|
1068
|
+
}
|
|
1069
|
+
function readCheck(tableName, name, value) {
|
|
1070
|
+
if (value === undefined || typeof value.text !== "string" || value.text.length === 0) {
|
|
1071
|
+
definition(`Check ${tableName}.${name} must return sql text.`);
|
|
1072
|
+
}
|
|
1073
|
+
assertStoredText(value.text, `check ${tableName}.${name}`);
|
|
1074
|
+
return value.text;
|
|
1075
|
+
}
|
|
1076
|
+
function track(handles, used) {
|
|
1077
|
+
return new Proxy(handles, {
|
|
1078
|
+
get(target, key) {
|
|
1079
|
+
if (typeof key === "string" && Object.hasOwn(target, key)) {
|
|
1080
|
+
used.push(key);
|
|
1081
|
+
return target[key];
|
|
1082
|
+
}
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
function list(names) {
|
|
1088
|
+
return names.length === 0 ? "(none)" : names.join(", ");
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
export { one2, many2, manyThrough2, schema2, addSchemaHook };
|