okmodel 0.1.1 → 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 +32 -16
- 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 +376 -27
- 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/{xqbhg6cn.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/{x5x59srr.js → 4jzzghr6.js} +416 -83
- package/dist/shared/{ycwz42az.js → 52szzakm.js} +6 -26
- package/dist/shared/{05w44d8g.js → 5z4q2zda.js} +567 -18
- 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/{523ft3e8.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/{kprcwaha.js → eaxwrgby.js} +4 -3
- package/dist/shared/{dkn3y60a.js → ecdzej21.js} +76 -112
- package/dist/shared/{hadrfb9h.js → edwqqryd.js} +17 -14
- package/dist/shared/{m3ypagh5.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/{yd1kzwn2.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/{jjb7yht9.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/{rgs0st28.js → xrfnq5k8.js} +237 -434
- 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 +41 -4
- package/dist/shared/gk7cs7sp.js +0 -566
|
@@ -1,67 +1,168 @@
|
|
|
1
1
|
import {
|
|
2
|
+
assertOperatorFits,
|
|
3
|
+
textArray,
|
|
4
|
+
arrayElementType
|
|
5
|
+
} from "./zt8y0wzb.js";
|
|
6
|
+
import {
|
|
7
|
+
runWrite
|
|
8
|
+
} from "./n4jqhpqz.js";
|
|
9
|
+
import {
|
|
10
|
+
stack
|
|
11
|
+
} from "./672t5vym.js";
|
|
12
|
+
import {
|
|
13
|
+
touchFields,
|
|
14
|
+
fieldSealed,
|
|
15
|
+
sealingTrait
|
|
16
|
+
} from "./7m88x3e9.js";
|
|
17
|
+
import {
|
|
18
|
+
writeWouldValidate
|
|
19
|
+
} from "./3a5ne750.js";
|
|
20
|
+
import {
|
|
21
|
+
encodeJson
|
|
22
|
+
} from "./yw5c610b.js";
|
|
23
|
+
import {
|
|
24
|
+
withRowFilters,
|
|
25
|
+
archiveRules,
|
|
2
26
|
emitWhere,
|
|
3
27
|
selectedColumns,
|
|
4
28
|
rejectKeys,
|
|
29
|
+
takeObject,
|
|
5
30
|
projectExpr,
|
|
6
31
|
decodeRow,
|
|
7
32
|
indexes,
|
|
8
33
|
quote,
|
|
9
34
|
isRecord,
|
|
35
|
+
registerFailFix,
|
|
10
36
|
fail
|
|
11
|
-
} from "./
|
|
37
|
+
} from "./xrfnq5k8.js";
|
|
38
|
+
import {
|
|
39
|
+
safetyInstalled,
|
|
40
|
+
runSafety
|
|
41
|
+
} from "./a5sxz52y.js";
|
|
12
42
|
import {
|
|
13
43
|
isOperator,
|
|
14
44
|
operatorName,
|
|
15
45
|
operatorValue
|
|
16
46
|
} from "./cn0n68ft.js";
|
|
17
47
|
import {
|
|
18
|
-
OkmError2
|
|
19
|
-
|
|
20
|
-
} from "./ycwz42az.js";
|
|
21
|
-
|
|
22
|
-
// src/runtime/tx.ts
|
|
23
|
-
async function runWrite(pool, statements, options) {
|
|
24
|
-
if (statements.length === 0)
|
|
25
|
-
return [];
|
|
26
|
-
const first = statements[0];
|
|
27
|
-
if (statements.length === 1 && first !== undefined) {
|
|
28
|
-
return [await pool.execute(first.text, first.params, options)];
|
|
29
|
-
}
|
|
30
|
-
return pool.batch(statements, options);
|
|
31
|
-
}
|
|
48
|
+
OkmError2
|
|
49
|
+
} from "./52szzakm.js";
|
|
32
50
|
|
|
33
51
|
// src/runtime/write.ts
|
|
52
|
+
registerFailFix("OKM1102", "Pass where, or call .all(reason) to match every row.");
|
|
53
|
+
registerFailFix("OKM1190", "Remove the guarded field. Input cannot set it.");
|
|
34
54
|
var WRITE_PARAM_BUDGET = 2048;
|
|
35
|
-
var
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
55
|
+
var REDACTED = "[redacted]";
|
|
56
|
+
function encodeColumn(column, value) {
|
|
57
|
+
if (!column.hidden && !column.sensitive)
|
|
58
|
+
return column.encode(value);
|
|
59
|
+
try {
|
|
60
|
+
return column.encode(value);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error instanceof OkmError2) {
|
|
63
|
+
throw new OkmError2(error.code, `Field ${column.field} was rejected.`, {
|
|
64
|
+
kind: error.kind,
|
|
65
|
+
fix: error.fix
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function requireValidationImport(schema, op, tableName, options) {
|
|
72
|
+
if (op === "delete" || validationRegistered(schema))
|
|
73
|
+
return;
|
|
74
|
+
const stored = schema;
|
|
75
|
+
const source = stored.tables?.find((item) => item.name === tableName);
|
|
76
|
+
if (source === undefined || !writeWouldValidate(stored.validation, source, options))
|
|
77
|
+
return;
|
|
78
|
+
const { refuseMissingValidation } = await import("./83eaefwr.js");
|
|
79
|
+
refuseMissingValidation();
|
|
80
|
+
}
|
|
81
|
+
function validationRegistered(schema) {
|
|
82
|
+
const hooks = schema.hooks;
|
|
83
|
+
if (hooks === undefined)
|
|
84
|
+
return false;
|
|
85
|
+
for (const hook of hooks) {
|
|
86
|
+
if (hook["~v"] === 1)
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
var INSERT_OPTIONS = [
|
|
92
|
+
"allow",
|
|
93
|
+
"expect",
|
|
94
|
+
"onConflict",
|
|
95
|
+
"returning",
|
|
96
|
+
"signal",
|
|
97
|
+
"timeout",
|
|
98
|
+
"validate"
|
|
99
|
+
];
|
|
100
|
+
var FILTER_OPTIONS = ["allow", "expect", "returning", "signal", "timeout", "validate"];
|
|
101
|
+
var NO_ALLOW = new Set;
|
|
102
|
+
async function executeWrite(host, op, table, input, options, mods, scope, plan) {
|
|
103
|
+
const prepared = await prepareWrite(host, op, table, input, options, mods, scope);
|
|
104
|
+
if (plan !== undefined)
|
|
105
|
+
return prepared;
|
|
106
|
+
if (prepared.statements.length === 0)
|
|
107
|
+
return prepared.finish([]);
|
|
108
|
+
const results = await runWrite(host, prepared.statements, prepared.options);
|
|
109
|
+
return prepared.finish(results);
|
|
110
|
+
}
|
|
111
|
+
async function prepareWrite(host, op, table, input, options, mods, scope) {
|
|
112
|
+
try {
|
|
113
|
+
const planned = await planWrite(host.schema, host.generators, op, table, input, options, mods, scope);
|
|
114
|
+
return {
|
|
115
|
+
statements: planned.statements,
|
|
116
|
+
options: callOptions(options),
|
|
117
|
+
...planned.expect !== undefined ? { checked: "expect" } : {},
|
|
118
|
+
finish: (results) => finish(results, planned, table)
|
|
119
|
+
};
|
|
120
|
+
} catch (error) {
|
|
121
|
+
throw scrubWrite(host.schema, table, input, error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function explainWrite(schema, op, table, input, options, mods, generators, scope) {
|
|
125
|
+
try {
|
|
126
|
+
const planned = await planWrite(schema, generators, op, table, input, options, mods, scope);
|
|
127
|
+
return { statements: planned.statements };
|
|
128
|
+
} catch (error) {
|
|
129
|
+
throw scrubWrite(schema, table, input, error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function planWrite(schema, generators, op, tableName, input, options, mods, scope) {
|
|
133
|
+
await requireValidationImport(schema, op, tableName, options);
|
|
49
134
|
const table = indexes(schema).get(tableName);
|
|
50
135
|
if (table === undefined) {
|
|
51
136
|
fail("OKM1120", `Table ${tableName} is not in the schema.`);
|
|
52
137
|
}
|
|
138
|
+
if (mods.uses !== undefined && op !== "insert") {
|
|
139
|
+
const { resolve } = await import("./r6nwkxnt.js");
|
|
140
|
+
mods = { ...mods, presets: resolve(schema, tableName, mods.uses).wheres };
|
|
141
|
+
}
|
|
53
142
|
const record = isRecord(options) ? options : {};
|
|
54
143
|
rejectKeys(record, op === "insert" ? INSERT_OPTIONS : FILTER_OPTIONS, op);
|
|
55
|
-
if (op === "insert")
|
|
56
|
-
return planInsert(table, input, record, mods);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
144
|
+
if (op === "insert") {
|
|
145
|
+
return planInsert(table, input, record, mods, generators, scope, schema.tenancy);
|
|
146
|
+
}
|
|
147
|
+
if (op === "update") {
|
|
148
|
+
return withRowFilters(scope, mods.archive, () => {
|
|
149
|
+
noteArchive(table, mods.archive);
|
|
150
|
+
return planUpdate(schema, table, input, record, mods);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return withRowFilters(scope, mods.archive, () => {
|
|
154
|
+
noteArchive(table, mods.archive);
|
|
155
|
+
return planDelete(schema, table, input, record, mods);
|
|
156
|
+
});
|
|
60
157
|
}
|
|
61
|
-
async function planInsert(table, input, options, mods) {
|
|
158
|
+
async function planInsert(table, input, options, mods, generators, scope, tenancy) {
|
|
159
|
+
const allow = allowSet(table, options.allow);
|
|
160
|
+
noteGuarded(table, allow, "insert");
|
|
62
161
|
const many = Array.isArray(input);
|
|
63
|
-
const rows = (many ? input : [input]).map((row) => insertRow(table, row));
|
|
64
|
-
|
|
162
|
+
const rows = (many ? input : [input]).map((row) => insertRow(table, row, allow, tenancy));
|
|
163
|
+
tenancy?.stamp(table.model.name, rows, scope);
|
|
164
|
+
fillInsert(table, rows, generators);
|
|
165
|
+
const columns = writtenColumns(table, rows, allow, tenancy?.key);
|
|
65
166
|
const returning = selectedColumns(table, options.returning);
|
|
66
167
|
const outputs = outputsOf(returning);
|
|
67
168
|
const clause = returningClause(returning, undefined);
|
|
@@ -78,9 +179,9 @@ async function planInsert(table, input, options, mods) {
|
|
|
78
179
|
keyFields: undefined
|
|
79
180
|
};
|
|
80
181
|
}
|
|
81
|
-
const conflictMod = options.onConflict === undefined || options.onConflict === "error" ? undefined : await import("./
|
|
82
|
-
const conflict = conflictMod?.readConflict(table, options.onConflict);
|
|
83
|
-
const chunks = chunkRows(rows.map((row) => cellsFor(
|
|
182
|
+
const conflictMod = options.onConflict === undefined || options.onConflict === "error" ? undefined : await import("./jns2wx4t.js");
|
|
183
|
+
const conflict = conflictMod?.readConflict(table, options.onConflict, allow);
|
|
184
|
+
const chunks = chunkRows(rows.map((row) => cellsFor(columns, row)));
|
|
84
185
|
const statements = [];
|
|
85
186
|
const keys = [];
|
|
86
187
|
let offset = 0;
|
|
@@ -107,11 +208,16 @@ async function planInsert(table, input, options, mods) {
|
|
|
107
208
|
};
|
|
108
209
|
}
|
|
109
210
|
function planUpdate(schema, table, input, options, mods) {
|
|
211
|
+
const allow = allowSet(table, options.allow);
|
|
212
|
+
noteGuarded(table, allow, "update");
|
|
110
213
|
const returning = returningOption(table, options.returning);
|
|
111
214
|
const outputs = outputsOf(returning);
|
|
112
215
|
const expect = expectOf(options, mods);
|
|
113
216
|
if (Array.isArray(input)) {
|
|
114
|
-
const rows = input.map((item) =>
|
|
217
|
+
const rows = input.map((item) => {
|
|
218
|
+
const row = updateItem(table, item, allow, schema.tenancy);
|
|
219
|
+
return { ...row, where: stack(row.where, mods.presets) };
|
|
220
|
+
});
|
|
115
221
|
return {
|
|
116
222
|
statements: updateList(schema, table, rows, returning),
|
|
117
223
|
outputs,
|
|
@@ -123,7 +229,7 @@ function planUpdate(schema, table, input, options, mods) {
|
|
|
123
229
|
}
|
|
124
230
|
if (!isRecord(input))
|
|
125
231
|
fail("OKM1121", "update expects { where, set } or a list of rows.");
|
|
126
|
-
const set = writeSet(table, input.set);
|
|
232
|
+
const set = writeSet(table, input.set, allow, schema.tenancy);
|
|
127
233
|
requireFilter(table, input.where, mods, "update");
|
|
128
234
|
if (Object.keys(set).length === 0)
|
|
129
235
|
fail("OKM1120", "update set is empty.");
|
|
@@ -132,7 +238,7 @@ function planUpdate(schema, table, input, options, mods) {
|
|
|
132
238
|
sql.text(quote(table.model.sql));
|
|
133
239
|
sql.text(" as t set ");
|
|
134
240
|
emitSet(sql, table, set, "t");
|
|
135
|
-
emitFilter(schema, table, input.where, sql, "t");
|
|
241
|
+
emitFilter(schema, table, stack(input.where, mods.presets), sql, "t");
|
|
136
242
|
sql.text(returningClause(returning, "t"));
|
|
137
243
|
return {
|
|
138
244
|
statements: [sql.statement()],
|
|
@@ -153,7 +259,7 @@ function planDelete(schema, table, input, options, mods) {
|
|
|
153
259
|
sql.text("delete from ");
|
|
154
260
|
sql.text(quote(table.model.sql));
|
|
155
261
|
sql.text(" as t");
|
|
156
|
-
emitFilter(schema, table, input.where, sql, "t");
|
|
262
|
+
emitFilter(schema, table, stack(input.where, mods.presets), sql, "t");
|
|
157
263
|
sql.text(returningClause(returning, "t"));
|
|
158
264
|
return {
|
|
159
265
|
statements: [sql.statement()],
|
|
@@ -276,6 +382,7 @@ function updateSlice(schema, table, rows, returning) {
|
|
|
276
382
|
sql.text(quote(column.sql));
|
|
277
383
|
sql.text(" end");
|
|
278
384
|
}
|
|
385
|
+
emitTouch(sql, table, fields.length > 0);
|
|
279
386
|
sql.text(" where ");
|
|
280
387
|
for (let index = 0;index < rows.length; index += 1) {
|
|
281
388
|
const row = rows[index];
|
|
@@ -292,41 +399,102 @@ function updateSlice(schema, table, rows, returning) {
|
|
|
292
399
|
}
|
|
293
400
|
function emitSet(sql, table, set, alias) {
|
|
294
401
|
const keys = Object.keys(set);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (key === undefined)
|
|
298
|
-
continue;
|
|
402
|
+
let wrote = false;
|
|
403
|
+
for (const key of keys) {
|
|
299
404
|
const column = table.columns.get(key);
|
|
300
405
|
if (column === undefined)
|
|
301
406
|
continue;
|
|
302
|
-
if (
|
|
407
|
+
if (wrote)
|
|
303
408
|
sql.text(", ");
|
|
409
|
+
wrote = true;
|
|
304
410
|
sql.text(quote(column.sql));
|
|
305
411
|
sql.text(" = ");
|
|
306
412
|
emitValue(sql, column, set[key], alias);
|
|
307
413
|
}
|
|
414
|
+
wrote = emitTouch(sql, table, wrote);
|
|
415
|
+
}
|
|
416
|
+
function emitTouch(sql, table, wrote) {
|
|
417
|
+
const touch = touchFields(table.model);
|
|
418
|
+
if (touch === undefined)
|
|
419
|
+
return wrote;
|
|
420
|
+
for (const field of touch) {
|
|
421
|
+
const column = table.columns.get(field);
|
|
422
|
+
if (column === undefined)
|
|
423
|
+
continue;
|
|
424
|
+
if (wrote)
|
|
425
|
+
sql.text(", ");
|
|
426
|
+
wrote = true;
|
|
427
|
+
sql.text(quote(column.sql));
|
|
428
|
+
sql.text(" = now()");
|
|
429
|
+
}
|
|
430
|
+
return wrote;
|
|
308
431
|
}
|
|
309
432
|
function emitValue(sql, column, value, alias) {
|
|
310
433
|
if (isOperator(value)) {
|
|
311
|
-
|
|
312
|
-
|
|
434
|
+
const name = operatorName(value);
|
|
435
|
+
if (name === "inc") {
|
|
436
|
+
const amount = operatorValue(value);
|
|
437
|
+
if (typeof amount === "object" && amount !== null) {
|
|
438
|
+
fail("OKM1121", `inc on ${column.field} needs a value.`);
|
|
439
|
+
}
|
|
440
|
+
sql.text(`${alias}.${quote(column.sql)} + `);
|
|
441
|
+
sql.param(encodeColumn(column, amount), column.dataType);
|
|
442
|
+
return;
|
|
313
443
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
444
|
+
if (name === "json.set") {
|
|
445
|
+
emitJsonSet(sql, column, value, alias);
|
|
446
|
+
return;
|
|
317
447
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
448
|
+
if (name === "arr.append" || name === "arr.remove") {
|
|
449
|
+
emitArrayWrite(sql, column, name, value, alias);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
fail("OKM1121", `Field ${column.field} received an operator. set accepts a value, inc, set, or arr.`);
|
|
321
453
|
}
|
|
322
454
|
if (value === null) {
|
|
323
455
|
sql.text(`null::${column.dataType}`);
|
|
324
456
|
return;
|
|
325
457
|
}
|
|
326
|
-
if (typeof value === "object")
|
|
327
|
-
|
|
458
|
+
if (typeof value === "object")
|
|
459
|
+
takeObject(column, value);
|
|
460
|
+
sql.param(encodeColumn(column, value), column.dataType);
|
|
461
|
+
}
|
|
462
|
+
function emitJsonSet(sql, column, value, alias) {
|
|
463
|
+
assertOperatorFits(column, "json.set");
|
|
464
|
+
if (!isOperator(value))
|
|
465
|
+
fail("OKM1121", `json.set on ${column.field} needs a path and a value.`);
|
|
466
|
+
const stored = operatorValue(value);
|
|
467
|
+
if (!isRecord(stored) || !Array.isArray(stored.path)) {
|
|
468
|
+
fail("OKM1121", `json.set on ${column.field} needs a path and a value.`);
|
|
469
|
+
}
|
|
470
|
+
const path = stored.path;
|
|
471
|
+
if (path.length === 0 || path.some((item) => typeof item !== "string")) {
|
|
472
|
+
fail("OKM1121", `json.set on ${column.field} needs a list of segments.`);
|
|
328
473
|
}
|
|
329
|
-
|
|
474
|
+
const encoded = encodeJson(stored.value);
|
|
475
|
+
const ref = `${alias}.${quote(column.sql)}`;
|
|
476
|
+
const target = column.dataType === "json" ? `${ref}::jsonb` : ref;
|
|
477
|
+
sql.text(`jsonb_set(${target}, `);
|
|
478
|
+
sql.param(textArray(path, `path on ${column.field}`), "text[]");
|
|
479
|
+
sql.text(", ");
|
|
480
|
+
sql.param(encoded, "jsonb");
|
|
481
|
+
sql.text(")");
|
|
482
|
+
if (column.dataType === "json")
|
|
483
|
+
sql.text("::json");
|
|
484
|
+
}
|
|
485
|
+
function emitArrayWrite(sql, column, name, value, alias) {
|
|
486
|
+
assertOperatorFits(column, name);
|
|
487
|
+
const encode = column.elementEncode;
|
|
488
|
+
if (encode === undefined) {
|
|
489
|
+
fail("OKM1124", `Operator ${name} does not apply to ${column.dataType} column ${column.field}. Accepted operators: eq, not, lt, lte, gt, gte, between, inList, notIn, inc.`);
|
|
490
|
+
}
|
|
491
|
+
if (!isOperator(value)) {
|
|
492
|
+
fail("OKM1121", `${name} on ${column.field} needs an element.`);
|
|
493
|
+
}
|
|
494
|
+
const fn = name === "arr.append" ? "array_append" : "array_remove";
|
|
495
|
+
sql.text(`${fn}(${alias}.${quote(column.sql)}, `);
|
|
496
|
+
sql.param(encode(operatorValue(value)), arrayElementType(column.dataType, column));
|
|
497
|
+
sql.text(")");
|
|
330
498
|
}
|
|
331
499
|
function emitFilter(schema, table, where, sql, alias) {
|
|
332
500
|
emitWhere(schema, table, where, sql.sink(), alias, 0);
|
|
@@ -379,7 +547,7 @@ class Sql {
|
|
|
379
547
|
return this.params.length === 0 ? { text: this.parts.join("") } : { text: this.parts.join(""), params: this.params };
|
|
380
548
|
}
|
|
381
549
|
}
|
|
382
|
-
function insertRow(table, value) {
|
|
550
|
+
function insertRow(table, value, allow, tenancy) {
|
|
383
551
|
if (!isRecord(value))
|
|
384
552
|
fail("OKM1121", "insert expects an object or a list of objects.");
|
|
385
553
|
const row = {};
|
|
@@ -387,27 +555,55 @@ function insertRow(table, value) {
|
|
|
387
555
|
const column = table.columns.get(key);
|
|
388
556
|
if (column === undefined)
|
|
389
557
|
continue;
|
|
390
|
-
|
|
391
|
-
|
|
558
|
+
tenancy?.guard(table.model.name, key, "insert");
|
|
559
|
+
if (!writable(table, column, allow))
|
|
560
|
+
refuse(table, column);
|
|
392
561
|
if (value[key] !== undefined)
|
|
393
562
|
row[key] = value[key];
|
|
394
563
|
}
|
|
395
564
|
return row;
|
|
396
565
|
}
|
|
397
|
-
function cellsFor(
|
|
566
|
+
function cellsFor(columns, row) {
|
|
398
567
|
return columns.map((column) => {
|
|
399
568
|
if (!Object.hasOwn(row, column.field))
|
|
400
569
|
return { kind: "default" };
|
|
401
570
|
const value = row[column.field];
|
|
402
571
|
if (value === null)
|
|
403
572
|
return { kind: "null", dataType: column.dataType };
|
|
404
|
-
if (typeof value === "object")
|
|
405
|
-
|
|
406
|
-
}
|
|
407
|
-
return { kind: "value", wire: column.encode(value), dataType: column.dataType };
|
|
573
|
+
if (typeof value === "object")
|
|
574
|
+
takeObject(column, value);
|
|
575
|
+
return { kind: "value", wire: encodeColumn(column, value), dataType: column.dataType };
|
|
408
576
|
});
|
|
409
577
|
}
|
|
410
|
-
function
|
|
578
|
+
function fillInsert(table, rows, generators) {
|
|
579
|
+
const fills = [];
|
|
580
|
+
for (const column of table.model.columns) {
|
|
581
|
+
if (column.fill !== undefined)
|
|
582
|
+
fills.push(column);
|
|
583
|
+
}
|
|
584
|
+
if (fills.length === 0)
|
|
585
|
+
return;
|
|
586
|
+
for (const row of rows) {
|
|
587
|
+
for (const column of fills) {
|
|
588
|
+
if (Object.hasOwn(row, column.field))
|
|
589
|
+
continue;
|
|
590
|
+
const fill = column.fill;
|
|
591
|
+
if (fill === undefined)
|
|
592
|
+
continue;
|
|
593
|
+
row[column.field] = nextFill(fill, generators);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
function nextFill(fill, generators) {
|
|
598
|
+
if (fill.name === "uuidv4" && generators?.uuidv4 !== undefined)
|
|
599
|
+
return generators.uuidv4();
|
|
600
|
+
if (fill.name === "uuidv7" && generators?.uuidv7 !== undefined)
|
|
601
|
+
return generators.uuidv7();
|
|
602
|
+
if (fill.name === "okid" && generators?.okid !== undefined)
|
|
603
|
+
return generators.okid();
|
|
604
|
+
return fill.call();
|
|
605
|
+
}
|
|
606
|
+
function writtenColumns(table, rows, allow, tenantKey) {
|
|
411
607
|
const present = new Set;
|
|
412
608
|
for (const row of rows) {
|
|
413
609
|
for (const key of Object.keys(row))
|
|
@@ -415,12 +611,13 @@ function writtenColumns(table, rows) {
|
|
|
415
611
|
}
|
|
416
612
|
const columns = [];
|
|
417
613
|
for (const column of table.model.columns) {
|
|
418
|
-
if (column.
|
|
614
|
+
if (present.has(column.field) && (writable(table, column, allow) || column.fill !== undefined || column.field === tenantKey)) {
|
|
419
615
|
columns.push(column);
|
|
616
|
+
}
|
|
420
617
|
}
|
|
421
618
|
return columns;
|
|
422
619
|
}
|
|
423
|
-
function writeSet(table, value) {
|
|
620
|
+
function writeSet(table, value, allow, tenancy) {
|
|
424
621
|
if (!isRecord(value))
|
|
425
622
|
fail("OKM1121", "set must be an object of fields.");
|
|
426
623
|
const set = {};
|
|
@@ -428,19 +625,19 @@ function writeSet(table, value) {
|
|
|
428
625
|
if (value[key] === undefined)
|
|
429
626
|
continue;
|
|
430
627
|
const column = table.columns.get(key);
|
|
431
|
-
if (column === undefined)
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
if (
|
|
435
|
-
refuse(table
|
|
628
|
+
if (column === undefined)
|
|
629
|
+
continue;
|
|
630
|
+
tenancy?.guard(table.model.name, key, "update");
|
|
631
|
+
if (column.guardUpdate || !writable(table, column, allow))
|
|
632
|
+
refuse(table, column);
|
|
436
633
|
set[key] = value[key];
|
|
437
634
|
}
|
|
438
635
|
return set;
|
|
439
636
|
}
|
|
440
|
-
function updateItem(table, value) {
|
|
637
|
+
function updateItem(table, value, allow, tenancy) {
|
|
441
638
|
if (!isRecord(value))
|
|
442
639
|
fail("OKM1121", "update expects { where, set } or a list of rows.");
|
|
443
|
-
const set = writeSet(table, value.set);
|
|
640
|
+
const set = writeSet(table, value.set, allow, tenancy);
|
|
444
641
|
if (value.where !== undefined)
|
|
445
642
|
return { where: value.where, set };
|
|
446
643
|
if (value.id !== undefined)
|
|
@@ -491,9 +688,7 @@ function keyWire(columns, row) {
|
|
|
491
688
|
const value = row[column.field];
|
|
492
689
|
if (value === undefined || value === null)
|
|
493
690
|
return null;
|
|
494
|
-
|
|
495
|
-
return null;
|
|
496
|
-
return column.encode(value);
|
|
691
|
+
return encodeColumn(column, value);
|
|
497
692
|
});
|
|
498
693
|
}
|
|
499
694
|
function returningOption(table, value) {
|
|
@@ -554,10 +749,148 @@ function callOptions(options) {
|
|
|
554
749
|
};
|
|
555
750
|
}
|
|
556
751
|
function refuse(table, column) {
|
|
752
|
+
if (fieldSealed(table.model, column.field)) {
|
|
753
|
+
fail("OKM1190", `Field ${table.model.name}.${column.field} is set by a trait. Input cannot set it.`);
|
|
754
|
+
}
|
|
755
|
+
if (column.guardUpdate) {
|
|
756
|
+
fail("OKM1190", `Field ${table.model.name}.${column.field} is a primary key. Input cannot change it.`);
|
|
757
|
+
}
|
|
557
758
|
if (column.guarded) {
|
|
558
|
-
fail("OKM1190", `Field ${table}.${column.field} is guarded. Input cannot set it.`);
|
|
759
|
+
fail("OKM1190", `Field ${table.model.name}.${column.field} is guarded. Input cannot set it.`);
|
|
760
|
+
}
|
|
761
|
+
fail("OKM1120", `Field ${table.model.name}.${column.field} cannot be written.`);
|
|
762
|
+
}
|
|
763
|
+
function writable(table, column, allow) {
|
|
764
|
+
if (fieldSealed(table.model, column.field))
|
|
765
|
+
return false;
|
|
766
|
+
if (column.writable)
|
|
767
|
+
return true;
|
|
768
|
+
return column.guarded && allow.has(column.field);
|
|
769
|
+
}
|
|
770
|
+
function allowSet(table, value) {
|
|
771
|
+
if (value === undefined)
|
|
772
|
+
return NO_ALLOW;
|
|
773
|
+
if (!Array.isArray(value))
|
|
774
|
+
fail("OKM1120", "allow must be a list of field names.");
|
|
775
|
+
const set = new Set;
|
|
776
|
+
for (const name of value) {
|
|
777
|
+
if (typeof name !== "string" || !table.columns.has(name)) {
|
|
778
|
+
fail("OKM1120", "allow must name fields on the table.");
|
|
779
|
+
}
|
|
780
|
+
set.add(name);
|
|
781
|
+
}
|
|
782
|
+
return set;
|
|
783
|
+
}
|
|
784
|
+
function noteArchive(table, view) {
|
|
785
|
+
if (!safetyInstalled())
|
|
786
|
+
return;
|
|
787
|
+
const rules = archiveRules(table.model, view);
|
|
788
|
+
if (rules === undefined)
|
|
789
|
+
return;
|
|
790
|
+
runSafety(rules, undefined);
|
|
791
|
+
}
|
|
792
|
+
function noteGuarded(table, allow, op) {
|
|
793
|
+
if (!safetyInstalled())
|
|
794
|
+
return;
|
|
795
|
+
const contributions = [];
|
|
796
|
+
const hatches = [];
|
|
797
|
+
const touch = touchFields(table.model);
|
|
798
|
+
const source = table.model.source;
|
|
799
|
+
for (const column of table.model.columns) {
|
|
800
|
+
const traitName = sealingTrait(table.model, column.field);
|
|
801
|
+
if (traitName !== undefined) {
|
|
802
|
+
const sets = op === "insert" || touch?.includes(column.field) === true;
|
|
803
|
+
contributions.push({
|
|
804
|
+
rule: traitName,
|
|
805
|
+
contribution: sets ? `${traitName} ${table.model.name}.${column.field} set by ${traitName}` : `${traitName} ${table.model.name}.${column.field} kept`,
|
|
806
|
+
provenance: "planner",
|
|
807
|
+
...source !== undefined ? { source } : {}
|
|
808
|
+
});
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
if (!column.guarded)
|
|
812
|
+
continue;
|
|
813
|
+
const name = `${table.model.name}.${column.field}`;
|
|
814
|
+
if (allow.has(column.field)) {
|
|
815
|
+
contributions.push({
|
|
816
|
+
rule: "guarded",
|
|
817
|
+
contribution: `guarded ${name} allowed`,
|
|
818
|
+
provenance: "allow"
|
|
819
|
+
});
|
|
820
|
+
hatches.push({ name: "allow", reason: name });
|
|
821
|
+
} else {
|
|
822
|
+
contributions.push({
|
|
823
|
+
rule: "guarded",
|
|
824
|
+
contribution: `guarded ${name} absent`,
|
|
825
|
+
provenance: "planner"
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
if (contributions.length === 0)
|
|
830
|
+
return;
|
|
831
|
+
runSafety(contributions, hatches.length === 0 ? undefined : hatches);
|
|
832
|
+
}
|
|
833
|
+
function scrubWrite(schema, tableName, input, error) {
|
|
834
|
+
if (!(error instanceof OkmError2))
|
|
835
|
+
return error;
|
|
836
|
+
const table = indexes(schema).get(tableName);
|
|
837
|
+
if (table === undefined || table.model.conceal !== true)
|
|
838
|
+
return error;
|
|
839
|
+
const secrets = [];
|
|
840
|
+
const names = new Set;
|
|
841
|
+
for (const column of table.model.columns) {
|
|
842
|
+
if (column.hidden || column.sensitive)
|
|
843
|
+
names.add(column.field);
|
|
844
|
+
}
|
|
845
|
+
collectSecrets(input, names, secrets);
|
|
846
|
+
if (secrets.length === 0)
|
|
847
|
+
return error;
|
|
848
|
+
let message = error.message;
|
|
849
|
+
for (const secret of secrets) {
|
|
850
|
+
if (secret.length < 4 || !message.includes(secret))
|
|
851
|
+
continue;
|
|
852
|
+
message = message.replaceAll(secret, REDACTED);
|
|
559
853
|
}
|
|
560
|
-
|
|
854
|
+
const issues = error.issues === undefined ? undefined : redactIssues(error.issues, secrets);
|
|
855
|
+
if (message === error.message && issues === undefined)
|
|
856
|
+
return error;
|
|
857
|
+
return new OkmError2(error.code, message, {
|
|
858
|
+
kind: error.kind,
|
|
859
|
+
...error.table !== undefined ? { table: error.table } : {},
|
|
860
|
+
...error.columns.length > 0 ? { columns: error.columns } : {},
|
|
861
|
+
...error.issues !== undefined ? { issues: issues ?? error.issues } : {},
|
|
862
|
+
fix: error.fix
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
function redactIssues(issues, secrets) {
|
|
866
|
+
let changed = false;
|
|
867
|
+
const next = issues.map((issue) => {
|
|
868
|
+
let message = issue.message;
|
|
869
|
+
for (const secret of secrets) {
|
|
870
|
+
if (secret.length < 4 || !message.includes(secret))
|
|
871
|
+
continue;
|
|
872
|
+
message = message.replaceAll(secret, REDACTED);
|
|
873
|
+
changed = true;
|
|
874
|
+
}
|
|
875
|
+
return message === issue.message ? issue : { path: issue.path, message };
|
|
876
|
+
});
|
|
877
|
+
return changed ? next : undefined;
|
|
878
|
+
}
|
|
879
|
+
function collectSecrets(value, names, found, key) {
|
|
880
|
+
if (typeof value === "string") {
|
|
881
|
+
if (key !== undefined && names.has(key))
|
|
882
|
+
found.push(value);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
if (Array.isArray(value)) {
|
|
886
|
+
for (const item of value)
|
|
887
|
+
collectSecrets(item, names, found);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
if (!isRecord(value))
|
|
891
|
+
return;
|
|
892
|
+
for (const child of Object.keys(value))
|
|
893
|
+
collectSecrets(value[child], names, found, child);
|
|
561
894
|
}
|
|
562
895
|
export {
|
|
563
896
|
WRITE_PARAM_BUDGET,
|