@ai-matrx/data 0.22.33 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/bin/direct-db.mjs +80 -0
- package/bin/emit.mjs +355 -0
- package/bin/generate.mjs +196 -0
- package/bin/matrx-data.mjs +47 -0
- package/dist/db.cjs +240 -19
- package/dist/db.cjs.map +1 -1
- package/dist/db.d.cts +220 -3
- package/dist/db.d.ts +220 -3
- package/dist/db.js +236 -15
- package/dist/db.js.map +1 -1
- package/package.json +6 -2
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* matrx-data — @ai-matrx/data's build-time CLI.
|
|
4
|
+
*
|
|
5
|
+
* matrx-data emit [--out <dir>] [--check] live database → the database description (read-only)
|
|
6
|
+
* matrx-data generate --from <dir> --out <dir> [--schemas a,b] description → typed doors in THIS app
|
|
7
|
+
*
|
|
8
|
+
* The package ships the runtime and this CLI; each app generates its own doors
|
|
9
|
+
* locally (Prisma's model), so a new column never waits on a package release.
|
|
10
|
+
*/
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
|
|
13
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
14
|
+
const flag = (name) => {
|
|
15
|
+
const i = rest.indexOf(name);
|
|
16
|
+
return i >= 0 ? rest[i + 1] : undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
async function main() {
|
|
20
|
+
if (command === "emit") {
|
|
21
|
+
const { emit } = await import("./emit.mjs");
|
|
22
|
+
return emit({ out: path.resolve(flag("--out") ?? "db-contract"), check: rest.includes("--check") });
|
|
23
|
+
}
|
|
24
|
+
if (command === "generate") {
|
|
25
|
+
const { generate } = await import("./generate.mjs");
|
|
26
|
+
const from = flag("--from");
|
|
27
|
+
const out = flag("--out");
|
|
28
|
+
if (!from || !out) {
|
|
29
|
+
console.error("matrx-data generate: --from <description dir> and --out <dir> are required");
|
|
30
|
+
return 2;
|
|
31
|
+
}
|
|
32
|
+
return generate({
|
|
33
|
+
from: path.resolve(from),
|
|
34
|
+
out: path.resolve(out),
|
|
35
|
+
schemas: flag("--schemas")?.split(",").map((s) => s.trim()).filter(Boolean),
|
|
36
|
+
check: rest.includes("--check"),
|
|
37
|
+
importFrom: flag("--import-from") ?? "@ai-matrx/data/db",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
console.error("usage: matrx-data emit [--out <dir>] [--check] | matrx-data generate --from <dir> --out <dir> [--schemas a,b] [--check]");
|
|
41
|
+
return 2;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
main().then((code) => process.exit(code ?? 0), (err) => {
|
|
45
|
+
console.error(`matrx-data ${command ?? ""}: ${err?.message ?? err}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|
package/dist/db.cjs
CHANGED
|
@@ -7,11 +7,11 @@ var __export = (target, all) => {
|
|
|
7
7
|
for (var name in all)
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
|
-
var __copyProps = (to,
|
|
11
|
-
if (
|
|
12
|
-
for (let key of __getOwnPropNames(
|
|
10
|
+
var __copyProps = (to, from2, except, desc) => {
|
|
11
|
+
if (from2 && typeof from2 === "object" || typeof from2 === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from2))
|
|
13
13
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () =>
|
|
14
|
+
__defProp(to, key, { get: () => from2[key], enumerable: !(desc = __getOwnPropDesc(from2, key)) || desc.enumerable });
|
|
15
15
|
}
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
@@ -24,35 +24,52 @@ __export(db_exports, {
|
|
|
24
24
|
DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE: () => DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE,
|
|
25
25
|
DEFAULT_ACTIVE_ORG_COOKIE_NAME: () => DEFAULT_ACTIVE_ORG_COOKIE_NAME,
|
|
26
26
|
DEFAULT_AUTH_COOKIE_NAME: () => DEFAULT_AUTH_COOKIE_NAME,
|
|
27
|
+
DatabaseError: () => DatabaseError,
|
|
28
|
+
DoorRefusedError: () => DoorRefusedError,
|
|
27
29
|
IncompleteReadError: () => IncompleteReadError,
|
|
28
30
|
RecordUnavailableError: () => RecordUnavailableError,
|
|
29
31
|
SessionUnavailableError: () => SessionUnavailableError,
|
|
30
32
|
WriteDidNotLandError: () => WriteDidNotLandError,
|
|
31
33
|
WriteRefusedError: () => WriteRefusedError,
|
|
34
|
+
archive: () => archive,
|
|
32
35
|
asJsonObject: () => asJsonObject,
|
|
36
|
+
assertDoor: () => assertDoor,
|
|
33
37
|
assertWriteLanded: () => assertWriteLanded,
|
|
38
|
+
call: () => call,
|
|
39
|
+
changesOf: () => changesOf,
|
|
40
|
+
count: () => count,
|
|
34
41
|
createActiveOrgCookie: () => createActiveOrgCookie,
|
|
35
42
|
createAuthCookie: () => createAuthCookie,
|
|
43
|
+
createDb: () => createDb,
|
|
36
44
|
createOAuthRefreshFetch: () => createOAuthRefreshFetch,
|
|
45
|
+
createServiceDb: () => createServiceDb,
|
|
37
46
|
createSessionRetry: () => createSessionRetry,
|
|
38
47
|
decodeActiveOrgSelection: () => decodeActiveOrgSelection,
|
|
39
48
|
decodeOAuthRefreshClaims: () => decodeOAuthRefreshClaims,
|
|
40
49
|
describeWriteFailure: () => describeWriteFailure,
|
|
41
50
|
encodeActiveOrgSelection: () => encodeActiveOrgSelection,
|
|
42
51
|
guardedUpdate: () => guardedUpdate,
|
|
52
|
+
insert: () => insert,
|
|
43
53
|
isMissingSessionError: () => isMissingSessionError,
|
|
44
54
|
isRecordUnavailableError: () => isRecordUnavailableError,
|
|
55
|
+
listAll: () => listAll,
|
|
45
56
|
mergeJsonColumn: () => mergeJsonColumn,
|
|
57
|
+
page: () => page,
|
|
46
58
|
parseCookieHeader: () => parseCookieHeader,
|
|
47
59
|
personSentence: () => personSentence,
|
|
60
|
+
read: () => read,
|
|
48
61
|
readAllRows: () => readAllRows,
|
|
49
62
|
readAllRowsRest: () => readAllRowsRest,
|
|
50
63
|
recordUnavailableMessage: () => recordUnavailableMessage,
|
|
64
|
+
remove: () => remove,
|
|
65
|
+
restore: () => restore,
|
|
51
66
|
serverMessageFromBody: () => serverMessageFromBody,
|
|
67
|
+
tableByName: () => tableByName,
|
|
52
68
|
tryReadAllRows: () => tryReadAllRows,
|
|
53
69
|
tryReadAllRowsRest: () => tryReadAllRowsRest,
|
|
54
70
|
tryWriteOne: () => tryWriteOne,
|
|
55
71
|
uniqueChannelTopic: () => import_realtime.uniqueChannelTopic,
|
|
72
|
+
update: () => update,
|
|
56
73
|
writeFailureStatus: () => writeFailureStatus,
|
|
57
74
|
writeOne: () => writeOne,
|
|
58
75
|
writeOneRow: () => writeOneRow
|
|
@@ -127,18 +144,18 @@ async function readAllRows(query, opts) {
|
|
|
127
144
|
const maxRows = opts.maxRows ?? DEFAULT_MAX_ROWS;
|
|
128
145
|
const rows = [];
|
|
129
146
|
let total = null;
|
|
130
|
-
for (let
|
|
131
|
-
const res = await query({ from, to:
|
|
147
|
+
for (let from2 = 0; ; from2 += pageSize) {
|
|
148
|
+
const res = await query({ from: from2, to: from2 + pageSize - 1 });
|
|
132
149
|
if (res.error) {
|
|
133
150
|
throw new Error(
|
|
134
151
|
`readAllRows(${opts.label}): query failed \u2014 ${res.error.message}`
|
|
135
152
|
);
|
|
136
153
|
}
|
|
137
|
-
const
|
|
138
|
-
rows.push(...
|
|
154
|
+
const page2 = res.data ?? [];
|
|
155
|
+
rows.push(...page2);
|
|
139
156
|
if (typeof res.count === "number") total = res.count;
|
|
140
157
|
if (total === null) {
|
|
141
|
-
if (
|
|
158
|
+
if (page2.length < pageSize) return rows;
|
|
142
159
|
throw new IncompleteReadError(
|
|
143
160
|
opts.label,
|
|
144
161
|
rows.length,
|
|
@@ -147,7 +164,7 @@ async function readAllRows(query, opts) {
|
|
|
147
164
|
);
|
|
148
165
|
}
|
|
149
166
|
if (rows.length >= total) break;
|
|
150
|
-
if (
|
|
167
|
+
if (page2.length === 0) {
|
|
151
168
|
throw new IncompleteReadError(
|
|
152
169
|
opts.label,
|
|
153
170
|
rows.length,
|
|
@@ -190,7 +207,7 @@ async function readAllRowsRest(opts) {
|
|
|
190
207
|
const endpoint = `${opts.url.replace(/\/$/, "")}/rest/v1/${opts.path}`;
|
|
191
208
|
const rows = [];
|
|
192
209
|
let total = null;
|
|
193
|
-
for (let
|
|
210
|
+
for (let from2 = 0; ; from2 += pageSize) {
|
|
194
211
|
const res = await fetch(endpoint, {
|
|
195
212
|
headers: {
|
|
196
213
|
apikey: opts.key,
|
|
@@ -201,7 +218,7 @@ async function readAllRowsRest(opts) {
|
|
|
201
218
|
// no total, which is unverifiable and therefore refused below. This is
|
|
202
219
|
// the REST twin of supabase-js's `{ count: "exact" }`.
|
|
203
220
|
Prefer: "count=exact",
|
|
204
|
-
Range: `${
|
|
221
|
+
Range: `${from2}-${from2 + pageSize - 1}`,
|
|
205
222
|
"Range-Unit": "items"
|
|
206
223
|
}
|
|
207
224
|
});
|
|
@@ -210,12 +227,12 @@ async function readAllRowsRest(opts) {
|
|
|
210
227
|
`readAllRowsRest(${opts.label}): HTTP ${res.status} \u2014 ${await res.text()}`
|
|
211
228
|
);
|
|
212
229
|
}
|
|
213
|
-
const
|
|
214
|
-
rows.push(...
|
|
230
|
+
const page2 = await res.json();
|
|
231
|
+
rows.push(...page2);
|
|
215
232
|
const totalPart = res.headers.get("content-range")?.split("/")[1];
|
|
216
233
|
if (totalPart && totalPart !== "*") total = Number(totalPart);
|
|
217
234
|
if (total === null) {
|
|
218
|
-
if (
|
|
235
|
+
if (page2.length < pageSize) return rows;
|
|
219
236
|
throw new IncompleteReadError(
|
|
220
237
|
opts.label,
|
|
221
238
|
rows.length,
|
|
@@ -224,7 +241,7 @@ async function readAllRowsRest(opts) {
|
|
|
224
241
|
);
|
|
225
242
|
}
|
|
226
243
|
if (rows.length >= total) break;
|
|
227
|
-
if (
|
|
244
|
+
if (page2.length === 0) {
|
|
228
245
|
throw new IncompleteReadError(
|
|
229
246
|
opts.label,
|
|
230
247
|
rows.length,
|
|
@@ -360,9 +377,9 @@ function belongsToStorageKey(name, storageKey) {
|
|
|
360
377
|
if (!name.startsWith(`${storageKey}.`)) return false;
|
|
361
378
|
return /^(0|[1-9][0-9]*)$/.test(name.slice(storageKey.length + 1));
|
|
362
379
|
}
|
|
363
|
-
function renamedCookie(cookie,
|
|
364
|
-
if (cookie.name ===
|
|
365
|
-
const prefix = `${
|
|
380
|
+
function renamedCookie(cookie, from2, to) {
|
|
381
|
+
if (cookie.name === from2) return { ...cookie, name: to };
|
|
382
|
+
const prefix = `${from2}.`;
|
|
366
383
|
if (!cookie.name.startsWith(prefix)) return null;
|
|
367
384
|
const chunk = cookie.name.slice(prefix.length);
|
|
368
385
|
if (!/^(0|[1-9][0-9]*)$/.test(chunk)) return null;
|
|
@@ -955,4 +972,208 @@ function recordUnavailableMessage(entity, reason) {
|
|
|
955
972
|
function isRecordUnavailableError(value) {
|
|
956
973
|
return value instanceof RecordUnavailableError;
|
|
957
974
|
}
|
|
975
|
+
|
|
976
|
+
// src/db/doors.ts
|
|
977
|
+
function credentialsOf(client) {
|
|
978
|
+
return {
|
|
979
|
+
async get() {
|
|
980
|
+
if (!client.auth) return null;
|
|
981
|
+
const { data } = await client.auth.getSession();
|
|
982
|
+
return data.session ? { kind: "user", accessToken: data.session.access_token } : null;
|
|
983
|
+
},
|
|
984
|
+
onChange(cb) {
|
|
985
|
+
if (!client.auth) return () => {
|
|
986
|
+
};
|
|
987
|
+
const { data } = client.auth.onAuthStateChange(() => cb());
|
|
988
|
+
return () => data.subscription.unsubscribe();
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
function createDb(options) {
|
|
993
|
+
const { client, organization } = options;
|
|
994
|
+
return {
|
|
995
|
+
client,
|
|
996
|
+
role: "user",
|
|
997
|
+
activeOrganization: () => organization?.() ?? null,
|
|
998
|
+
credentials: credentialsOf(client)
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function createServiceDb(options) {
|
|
1002
|
+
if (typeof globalThis.window !== "undefined") {
|
|
1003
|
+
throw new DoorRefusedError("createServiceDb", "the service role never runs in a browser");
|
|
1004
|
+
}
|
|
1005
|
+
return { client: options.client, role: "service", activeOrganization: () => null, credentials: credentialsOf(options.client) };
|
|
1006
|
+
}
|
|
1007
|
+
var DoorRefusedError = class extends Error {
|
|
1008
|
+
constructor(door, reason) {
|
|
1009
|
+
super(`${door}: ${reason}`);
|
|
1010
|
+
this.door = door;
|
|
1011
|
+
this.reason = reason;
|
|
1012
|
+
this.name = "DoorRefusedError";
|
|
1013
|
+
}
|
|
1014
|
+
door;
|
|
1015
|
+
reason;
|
|
1016
|
+
};
|
|
1017
|
+
var DatabaseError = class extends Error {
|
|
1018
|
+
constructor(door, cause) {
|
|
1019
|
+
super(`${door}: ${cause.message}${cause.code ? ` (${cause.code})` : ""}`);
|
|
1020
|
+
this.door = door;
|
|
1021
|
+
this.cause = cause;
|
|
1022
|
+
this.name = "DatabaseError";
|
|
1023
|
+
}
|
|
1024
|
+
door;
|
|
1025
|
+
cause;
|
|
1026
|
+
};
|
|
1027
|
+
var CLIENT_DOORS = {
|
|
1028
|
+
entity: ["read", "insert", "update", "remove", "archive"],
|
|
1029
|
+
detail: ["read", "insert", "update", "remove", "archive"],
|
|
1030
|
+
ledger: ["read", "insert"],
|
|
1031
|
+
reference: ["read"],
|
|
1032
|
+
system: ["read"],
|
|
1033
|
+
restricted: [],
|
|
1034
|
+
deprecated: []
|
|
1035
|
+
};
|
|
1036
|
+
var qualified = (t) => `${t.schema}.${t.name}`;
|
|
1037
|
+
function assertDoor(db, table, op) {
|
|
1038
|
+
const door = `${op} ${qualified(table)}`;
|
|
1039
|
+
if (table.type === "deprecated") throw new DoorRefusedError(door, "the table is deprecated \u2014 no door opens on it");
|
|
1040
|
+
if (table.kind !== "table" && op !== "read") throw new DoorRefusedError(door, `a ${table.kind.replace("_", " ")} is read-only`);
|
|
1041
|
+
if (db.role === "service") return;
|
|
1042
|
+
if (table.type && !CLIENT_DOORS[table.type].includes(op))
|
|
1043
|
+
throw new DoorRefusedError(door, `a ${table.type} table opens only ${CLIENT_DOORS[table.type].join(", ") || "service"} doors to a client`);
|
|
1044
|
+
if (op !== "read" && table.readOnly) throw new DoorRefusedError(door, "the table is read-only to clients (client_read_only)");
|
|
1045
|
+
if (op === "remove" && table.deletesRefused) throw new DoorRefusedError(door, "clients may not delete from this table (client_deletes_refused)");
|
|
1046
|
+
}
|
|
1047
|
+
function check(door, res) {
|
|
1048
|
+
if (res.error) throw new DatabaseError(door, res.error);
|
|
1049
|
+
return res.data;
|
|
1050
|
+
}
|
|
1051
|
+
var from = (db, t) => db.client.schema(t.schema).from(t.name);
|
|
1052
|
+
function byPk(q, t, id) {
|
|
1053
|
+
if (t.pk.length === 1) return q.eq(t.pk[0], id);
|
|
1054
|
+
const key = id;
|
|
1055
|
+
return t.pk.reduce((acc, col) => acc.eq(col, key[col]), q);
|
|
1056
|
+
}
|
|
1057
|
+
async function read(db, table, id, columns = "*") {
|
|
1058
|
+
assertDoor(db, table, "read");
|
|
1059
|
+
return check(`read ${qualified(table)}`, await byPk(from(db, table).select(columns), table, id).maybeSingle());
|
|
1060
|
+
}
|
|
1061
|
+
async function listAll(db, table, where, opts = {}) {
|
|
1062
|
+
assertDoor(db, table, "read");
|
|
1063
|
+
const order = opts.orderBy ?? table.pk[0] ?? "id";
|
|
1064
|
+
return readAllRows(
|
|
1065
|
+
({ from: lo, to: hi }) => {
|
|
1066
|
+
let q = from(db, table).select(opts.columns ?? "*", { count: "exact" });
|
|
1067
|
+
if (where) q = where(q);
|
|
1068
|
+
return q.order(order, { ascending: true }).range(lo, hi);
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
label: `listAll ${qualified(table)}`,
|
|
1072
|
+
...opts.pageSize !== void 0 ? { pageSize: opts.pageSize } : {},
|
|
1073
|
+
...opts.maxRows !== void 0 ? { maxRows: opts.maxRows } : {}
|
|
1074
|
+
}
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
async function page(db, table, window2) {
|
|
1078
|
+
assertDoor(db, table, "read");
|
|
1079
|
+
let q = from(db, table).select(window2.columns ?? "*", { count: "exact" });
|
|
1080
|
+
if (window2.where) q = window2.where(q);
|
|
1081
|
+
const res = await q.order(window2.orderBy ?? table.pk[0] ?? "id", { ascending: window2.ascending ?? true }).range(window2.from, window2.to);
|
|
1082
|
+
return { rows: check(`page ${qualified(table)}`, res) ?? [], total: res.count ?? null };
|
|
1083
|
+
}
|
|
1084
|
+
async function count(db, table, where) {
|
|
1085
|
+
assertDoor(db, table, "read");
|
|
1086
|
+
let q = from(db, table).select("*", { count: "exact", head: true });
|
|
1087
|
+
if (where) q = where(q);
|
|
1088
|
+
const res = await q;
|
|
1089
|
+
check(`count ${qualified(table)}`, res);
|
|
1090
|
+
return res.count ?? 0;
|
|
1091
|
+
}
|
|
1092
|
+
function withOrganization(db, table, values) {
|
|
1093
|
+
const door = `insert ${qualified(table)}`;
|
|
1094
|
+
const org = table.org;
|
|
1095
|
+
if (org.rule === "parent" && values[org.via] != null) {
|
|
1096
|
+
return values;
|
|
1097
|
+
}
|
|
1098
|
+
if (org.rule === "binding" || org.rule === "parent") {
|
|
1099
|
+
if (values.organization_id != null) return values;
|
|
1100
|
+
const active = db.activeOrganization();
|
|
1101
|
+
if (!active) {
|
|
1102
|
+
throw new DoorRefusedError(
|
|
1103
|
+
door,
|
|
1104
|
+
db.role === "service" ? "a service insert names its organization_id" : "no organization: pass organization_id or bind createDb({ organization })"
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
return { ...values, organization_id: active };
|
|
1108
|
+
}
|
|
1109
|
+
return values;
|
|
1110
|
+
}
|
|
1111
|
+
async function insert(db, table, values) {
|
|
1112
|
+
assertDoor(db, table, "insert");
|
|
1113
|
+
const many = Array.isArray(values);
|
|
1114
|
+
const rows = (many ? values : [values]).map((v) => withOrganization(db, table, v));
|
|
1115
|
+
const res = await from(db, table).insert(rows).select("*");
|
|
1116
|
+
const stored = check(`insert ${qualified(table)}`, res);
|
|
1117
|
+
return many ? stored : stored[0];
|
|
1118
|
+
}
|
|
1119
|
+
async function update(db, table, id, patch, guard) {
|
|
1120
|
+
assertDoor(db, table, "update");
|
|
1121
|
+
const door = `update ${qualified(table)}`;
|
|
1122
|
+
const values = patch;
|
|
1123
|
+
if (db.role === "user") {
|
|
1124
|
+
const blocked = table.readOnlyColumns.filter((c) => c in values);
|
|
1125
|
+
if (blocked.length) throw new DoorRefusedError(door, `read-only to clients: ${blocked.join(", ")}`);
|
|
1126
|
+
}
|
|
1127
|
+
if (table.versioned && guard && "expectedVersion" in guard) {
|
|
1128
|
+
return guardedUpdate({
|
|
1129
|
+
expectedVersion: guard.expectedVersion,
|
|
1130
|
+
applyUpdate: ({ expectedVersion, nextVersion }) => byPk(from(db, table).update({ ...values, version: nextVersion }), table, id).eq("version", expectedVersion).select("*").maybeSingle(),
|
|
1131
|
+
fetchCurrent: () => byPk(from(db, table).select("*"), table, id).maybeSingle()
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
if (table.versioned && !(guard && "overwrite" in guard && guard.overwrite)) {
|
|
1135
|
+
throw new DoorRefusedError(door, "a versioned table takes { expectedVersion } or { overwrite: true }");
|
|
1136
|
+
}
|
|
1137
|
+
const row = check(door, await byPk(from(db, table).update(values), table, id).select("*").maybeSingle());
|
|
1138
|
+
return row ? { status: "saved", row } : { status: "not_found" };
|
|
1139
|
+
}
|
|
1140
|
+
async function remove(db, table, id) {
|
|
1141
|
+
assertDoor(db, table, "remove");
|
|
1142
|
+
const door = `remove ${qualified(table)}`;
|
|
1143
|
+
if (table.softDelete) {
|
|
1144
|
+
const row = check(door, await byPk(from(db, table).update({ deleted_at: (/* @__PURE__ */ new Date()).toISOString() }), table, id).select("*").maybeSingle());
|
|
1145
|
+
return row ? "soft_deleted" : "not_found";
|
|
1146
|
+
}
|
|
1147
|
+
if (table.archive) throw new DoorRefusedError(door, "this table archives instead of deleting \u2014 use archive()");
|
|
1148
|
+
const rows = check(door, await byPk(from(db, table).delete(), table, id).select("*"));
|
|
1149
|
+
return rows && rows.length ? "deleted" : "not_found";
|
|
1150
|
+
}
|
|
1151
|
+
async function archive(db, table, id, opts = {}) {
|
|
1152
|
+
assertDoor(db, table, "archive");
|
|
1153
|
+
const door = `${opts.restore ? "restore" : "archive"} ${qualified(table)}`;
|
|
1154
|
+
let values;
|
|
1155
|
+
if (table.archive === "archived_at") values = { archived_at: opts.restore ? null : (/* @__PURE__ */ new Date()).toISOString() };
|
|
1156
|
+
else if (table.archive === "is_archived") values = { is_archived: !opts.restore };
|
|
1157
|
+
else if (table.softDelete && opts.restore) values = { deleted_at: null };
|
|
1158
|
+
else throw new DoorRefusedError(door, "the table has no archive column");
|
|
1159
|
+
return check(door, await byPk(from(db, table).update(values), table, id).select("*").maybeSingle());
|
|
1160
|
+
}
|
|
1161
|
+
var restore = (db, table, id) => archive(db, table, id, { restore: true });
|
|
1162
|
+
async function call(db, fn, args) {
|
|
1163
|
+
const door = `call ${fn.schema}.${fn.name}`;
|
|
1164
|
+
const role = db.role === "service" ? "service_role" : "authenticated";
|
|
1165
|
+
if (!fn.roles.includes(role) && !(role === "authenticated" && fn.roles.includes("anon"))) {
|
|
1166
|
+
throw new DoorRefusedError(door, `not executable by ${role} (executable by: ${fn.roles.join(", ") || "nobody"})`);
|
|
1167
|
+
}
|
|
1168
|
+
return check(door, await db.client.schema(fn.schema).rpc(fn.name, args));
|
|
1169
|
+
}
|
|
1170
|
+
function tableByName(index, name) {
|
|
1171
|
+
const t = index[name];
|
|
1172
|
+
if (!t) throw new DoorRefusedError(`tableByName ${name}`, "no such table in the database description (schema-qualify it: schema.table)");
|
|
1173
|
+
return t;
|
|
1174
|
+
}
|
|
1175
|
+
function changesOf(table) {
|
|
1176
|
+
if (!table.realtime) throw new DoorRefusedError(`changesOf ${qualified(table)}`, "the table is not in the supabase_realtime publication");
|
|
1177
|
+
return { schema: table.schema, table: table.name };
|
|
1178
|
+
}
|
|
958
1179
|
//# sourceMappingURL=db.cjs.map
|