@ai-matrx/data 0.22.32 → 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/dist/db.js CHANGED
@@ -66,18 +66,18 @@ async function readAllRows(query, opts) {
66
66
  const maxRows = opts.maxRows ?? DEFAULT_MAX_ROWS;
67
67
  const rows = [];
68
68
  let total = null;
69
- for (let from = 0; ; from += pageSize) {
70
- const res = await query({ from, to: from + pageSize - 1 });
69
+ for (let from2 = 0; ; from2 += pageSize) {
70
+ const res = await query({ from: from2, to: from2 + pageSize - 1 });
71
71
  if (res.error) {
72
72
  throw new Error(
73
73
  `readAllRows(${opts.label}): query failed \u2014 ${res.error.message}`
74
74
  );
75
75
  }
76
- const page = res.data ?? [];
77
- rows.push(...page);
76
+ const page2 = res.data ?? [];
77
+ rows.push(...page2);
78
78
  if (typeof res.count === "number") total = res.count;
79
79
  if (total === null) {
80
- if (page.length < pageSize) return rows;
80
+ if (page2.length < pageSize) return rows;
81
81
  throw new IncompleteReadError(
82
82
  opts.label,
83
83
  rows.length,
@@ -86,7 +86,7 @@ async function readAllRows(query, opts) {
86
86
  );
87
87
  }
88
88
  if (rows.length >= total) break;
89
- if (page.length === 0) {
89
+ if (page2.length === 0) {
90
90
  throw new IncompleteReadError(
91
91
  opts.label,
92
92
  rows.length,
@@ -129,7 +129,7 @@ async function readAllRowsRest(opts) {
129
129
  const endpoint = `${opts.url.replace(/\/$/, "")}/rest/v1/${opts.path}`;
130
130
  const rows = [];
131
131
  let total = null;
132
- for (let from = 0; ; from += pageSize) {
132
+ for (let from2 = 0; ; from2 += pageSize) {
133
133
  const res = await fetch(endpoint, {
134
134
  headers: {
135
135
  apikey: opts.key,
@@ -140,7 +140,7 @@ async function readAllRowsRest(opts) {
140
140
  // no total, which is unverifiable and therefore refused below. This is
141
141
  // the REST twin of supabase-js's `{ count: "exact" }`.
142
142
  Prefer: "count=exact",
143
- Range: `${from}-${from + pageSize - 1}`,
143
+ Range: `${from2}-${from2 + pageSize - 1}`,
144
144
  "Range-Unit": "items"
145
145
  }
146
146
  });
@@ -149,12 +149,12 @@ async function readAllRowsRest(opts) {
149
149
  `readAllRowsRest(${opts.label}): HTTP ${res.status} \u2014 ${await res.text()}`
150
150
  );
151
151
  }
152
- const page = await res.json();
153
- rows.push(...page);
152
+ const page2 = await res.json();
153
+ rows.push(...page2);
154
154
  const totalPart = res.headers.get("content-range")?.split("/")[1];
155
155
  if (totalPart && totalPart !== "*") total = Number(totalPart);
156
156
  if (total === null) {
157
- if (page.length < pageSize) return rows;
157
+ if (page2.length < pageSize) return rows;
158
158
  throw new IncompleteReadError(
159
159
  opts.label,
160
160
  rows.length,
@@ -163,7 +163,7 @@ async function readAllRowsRest(opts) {
163
163
  );
164
164
  }
165
165
  if (rows.length >= total) break;
166
- if (page.length === 0) {
166
+ if (page2.length === 0) {
167
167
  throw new IncompleteReadError(
168
168
  opts.label,
169
169
  rows.length,
@@ -299,9 +299,9 @@ function belongsToStorageKey(name, storageKey) {
299
299
  if (!name.startsWith(`${storageKey}.`)) return false;
300
300
  return /^(0|[1-9][0-9]*)$/.test(name.slice(storageKey.length + 1));
301
301
  }
302
- function renamedCookie(cookie, from, to) {
303
- if (cookie.name === from) return { ...cookie, name: to };
304
- const prefix = `${from}.`;
302
+ function renamedCookie(cookie, from2, to) {
303
+ if (cookie.name === from2) return { ...cookie, name: to };
304
+ const prefix = `${from2}.`;
305
305
  if (!cookie.name.startsWith(prefix)) return null;
306
306
  const chunk = cookie.name.slice(prefix.length);
307
307
  if (!/^(0|[1-9][0-9]*)$/.test(chunk)) return null;
@@ -894,40 +894,261 @@ function recordUnavailableMessage(entity, reason) {
894
894
  function isRecordUnavailableError(value) {
895
895
  return value instanceof RecordUnavailableError;
896
896
  }
897
+
898
+ // src/db/doors.ts
899
+ function credentialsOf(client) {
900
+ return {
901
+ async get() {
902
+ if (!client.auth) return null;
903
+ const { data } = await client.auth.getSession();
904
+ return data.session ? { kind: "user", accessToken: data.session.access_token } : null;
905
+ },
906
+ onChange(cb) {
907
+ if (!client.auth) return () => {
908
+ };
909
+ const { data } = client.auth.onAuthStateChange(() => cb());
910
+ return () => data.subscription.unsubscribe();
911
+ }
912
+ };
913
+ }
914
+ function createDb(options) {
915
+ const { client, organization } = options;
916
+ return {
917
+ client,
918
+ role: "user",
919
+ activeOrganization: () => organization?.() ?? null,
920
+ credentials: credentialsOf(client)
921
+ };
922
+ }
923
+ function createServiceDb(options) {
924
+ if (typeof globalThis.window !== "undefined") {
925
+ throw new DoorRefusedError("createServiceDb", "the service role never runs in a browser");
926
+ }
927
+ return { client: options.client, role: "service", activeOrganization: () => null, credentials: credentialsOf(options.client) };
928
+ }
929
+ var DoorRefusedError = class extends Error {
930
+ constructor(door, reason) {
931
+ super(`${door}: ${reason}`);
932
+ this.door = door;
933
+ this.reason = reason;
934
+ this.name = "DoorRefusedError";
935
+ }
936
+ door;
937
+ reason;
938
+ };
939
+ var DatabaseError = class extends Error {
940
+ constructor(door, cause) {
941
+ super(`${door}: ${cause.message}${cause.code ? ` (${cause.code})` : ""}`);
942
+ this.door = door;
943
+ this.cause = cause;
944
+ this.name = "DatabaseError";
945
+ }
946
+ door;
947
+ cause;
948
+ };
949
+ var CLIENT_DOORS = {
950
+ entity: ["read", "insert", "update", "remove", "archive"],
951
+ detail: ["read", "insert", "update", "remove", "archive"],
952
+ ledger: ["read", "insert"],
953
+ reference: ["read"],
954
+ system: ["read"],
955
+ restricted: [],
956
+ deprecated: []
957
+ };
958
+ var qualified = (t) => `${t.schema}.${t.name}`;
959
+ function assertDoor(db, table, op) {
960
+ const door = `${op} ${qualified(table)}`;
961
+ if (table.type === "deprecated") throw new DoorRefusedError(door, "the table is deprecated \u2014 no door opens on it");
962
+ if (table.kind !== "table" && op !== "read") throw new DoorRefusedError(door, `a ${table.kind.replace("_", " ")} is read-only`);
963
+ if (db.role === "service") return;
964
+ if (table.type && !CLIENT_DOORS[table.type].includes(op))
965
+ throw new DoorRefusedError(door, `a ${table.type} table opens only ${CLIENT_DOORS[table.type].join(", ") || "service"} doors to a client`);
966
+ if (op !== "read" && table.readOnly) throw new DoorRefusedError(door, "the table is read-only to clients (client_read_only)");
967
+ if (op === "remove" && table.deletesRefused) throw new DoorRefusedError(door, "clients may not delete from this table (client_deletes_refused)");
968
+ }
969
+ function check(door, res) {
970
+ if (res.error) throw new DatabaseError(door, res.error);
971
+ return res.data;
972
+ }
973
+ var from = (db, t) => db.client.schema(t.schema).from(t.name);
974
+ function byPk(q, t, id) {
975
+ if (t.pk.length === 1) return q.eq(t.pk[0], id);
976
+ const key = id;
977
+ return t.pk.reduce((acc, col) => acc.eq(col, key[col]), q);
978
+ }
979
+ async function read(db, table, id, columns = "*") {
980
+ assertDoor(db, table, "read");
981
+ return check(`read ${qualified(table)}`, await byPk(from(db, table).select(columns), table, id).maybeSingle());
982
+ }
983
+ async function listAll(db, table, where, opts = {}) {
984
+ assertDoor(db, table, "read");
985
+ const order = opts.orderBy ?? table.pk[0] ?? "id";
986
+ return readAllRows(
987
+ ({ from: lo, to: hi }) => {
988
+ let q = from(db, table).select(opts.columns ?? "*", { count: "exact" });
989
+ if (where) q = where(q);
990
+ return q.order(order, { ascending: true }).range(lo, hi);
991
+ },
992
+ {
993
+ label: `listAll ${qualified(table)}`,
994
+ ...opts.pageSize !== void 0 ? { pageSize: opts.pageSize } : {},
995
+ ...opts.maxRows !== void 0 ? { maxRows: opts.maxRows } : {}
996
+ }
997
+ );
998
+ }
999
+ async function page(db, table, window2) {
1000
+ assertDoor(db, table, "read");
1001
+ let q = from(db, table).select(window2.columns ?? "*", { count: "exact" });
1002
+ if (window2.where) q = window2.where(q);
1003
+ const res = await q.order(window2.orderBy ?? table.pk[0] ?? "id", { ascending: window2.ascending ?? true }).range(window2.from, window2.to);
1004
+ return { rows: check(`page ${qualified(table)}`, res) ?? [], total: res.count ?? null };
1005
+ }
1006
+ async function count(db, table, where) {
1007
+ assertDoor(db, table, "read");
1008
+ let q = from(db, table).select("*", { count: "exact", head: true });
1009
+ if (where) q = where(q);
1010
+ const res = await q;
1011
+ check(`count ${qualified(table)}`, res);
1012
+ return res.count ?? 0;
1013
+ }
1014
+ function withOrganization(db, table, values) {
1015
+ const door = `insert ${qualified(table)}`;
1016
+ const org = table.org;
1017
+ if (org.rule === "parent" && values[org.via] != null) {
1018
+ return values;
1019
+ }
1020
+ if (org.rule === "binding" || org.rule === "parent") {
1021
+ if (values.organization_id != null) return values;
1022
+ const active = db.activeOrganization();
1023
+ if (!active) {
1024
+ throw new DoorRefusedError(
1025
+ door,
1026
+ db.role === "service" ? "a service insert names its organization_id" : "no organization: pass organization_id or bind createDb({ organization })"
1027
+ );
1028
+ }
1029
+ return { ...values, organization_id: active };
1030
+ }
1031
+ return values;
1032
+ }
1033
+ async function insert(db, table, values) {
1034
+ assertDoor(db, table, "insert");
1035
+ const many = Array.isArray(values);
1036
+ const rows = (many ? values : [values]).map((v) => withOrganization(db, table, v));
1037
+ const res = await from(db, table).insert(rows).select("*");
1038
+ const stored = check(`insert ${qualified(table)}`, res);
1039
+ return many ? stored : stored[0];
1040
+ }
1041
+ async function update(db, table, id, patch, guard) {
1042
+ assertDoor(db, table, "update");
1043
+ const door = `update ${qualified(table)}`;
1044
+ const values = patch;
1045
+ if (db.role === "user") {
1046
+ const blocked = table.readOnlyColumns.filter((c) => c in values);
1047
+ if (blocked.length) throw new DoorRefusedError(door, `read-only to clients: ${blocked.join(", ")}`);
1048
+ }
1049
+ if (table.versioned && guard && "expectedVersion" in guard) {
1050
+ return guardedUpdate({
1051
+ expectedVersion: guard.expectedVersion,
1052
+ applyUpdate: ({ expectedVersion, nextVersion }) => byPk(from(db, table).update({ ...values, version: nextVersion }), table, id).eq("version", expectedVersion).select("*").maybeSingle(),
1053
+ fetchCurrent: () => byPk(from(db, table).select("*"), table, id).maybeSingle()
1054
+ });
1055
+ }
1056
+ if (table.versioned && !(guard && "overwrite" in guard && guard.overwrite)) {
1057
+ throw new DoorRefusedError(door, "a versioned table takes { expectedVersion } or { overwrite: true }");
1058
+ }
1059
+ const row = check(door, await byPk(from(db, table).update(values), table, id).select("*").maybeSingle());
1060
+ return row ? { status: "saved", row } : { status: "not_found" };
1061
+ }
1062
+ async function remove(db, table, id) {
1063
+ assertDoor(db, table, "remove");
1064
+ const door = `remove ${qualified(table)}`;
1065
+ if (table.softDelete) {
1066
+ const row = check(door, await byPk(from(db, table).update({ deleted_at: (/* @__PURE__ */ new Date()).toISOString() }), table, id).select("*").maybeSingle());
1067
+ return row ? "soft_deleted" : "not_found";
1068
+ }
1069
+ if (table.archive) throw new DoorRefusedError(door, "this table archives instead of deleting \u2014 use archive()");
1070
+ const rows = check(door, await byPk(from(db, table).delete(), table, id).select("*"));
1071
+ return rows && rows.length ? "deleted" : "not_found";
1072
+ }
1073
+ async function archive(db, table, id, opts = {}) {
1074
+ assertDoor(db, table, "archive");
1075
+ const door = `${opts.restore ? "restore" : "archive"} ${qualified(table)}`;
1076
+ let values;
1077
+ if (table.archive === "archived_at") values = { archived_at: opts.restore ? null : (/* @__PURE__ */ new Date()).toISOString() };
1078
+ else if (table.archive === "is_archived") values = { is_archived: !opts.restore };
1079
+ else if (table.softDelete && opts.restore) values = { deleted_at: null };
1080
+ else throw new DoorRefusedError(door, "the table has no archive column");
1081
+ return check(door, await byPk(from(db, table).update(values), table, id).select("*").maybeSingle());
1082
+ }
1083
+ var restore = (db, table, id) => archive(db, table, id, { restore: true });
1084
+ async function call(db, fn, args) {
1085
+ const door = `call ${fn.schema}.${fn.name}`;
1086
+ const role = db.role === "service" ? "service_role" : "authenticated";
1087
+ if (!fn.roles.includes(role) && !(role === "authenticated" && fn.roles.includes("anon"))) {
1088
+ throw new DoorRefusedError(door, `not executable by ${role} (executable by: ${fn.roles.join(", ") || "nobody"})`);
1089
+ }
1090
+ return check(door, await db.client.schema(fn.schema).rpc(fn.name, args));
1091
+ }
1092
+ function tableByName(index, name) {
1093
+ const t = index[name];
1094
+ if (!t) throw new DoorRefusedError(`tableByName ${name}`, "no such table in the database description (schema-qualify it: schema.table)");
1095
+ return t;
1096
+ }
1097
+ function changesOf(table) {
1098
+ if (!table.realtime) throw new DoorRefusedError(`changesOf ${qualified(table)}`, "the table is not in the supabase_realtime publication");
1099
+ return { schema: table.schema, table: table.name };
1100
+ }
897
1101
  export {
898
1102
  BulkWriteError,
899
1103
  DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE,
900
1104
  DEFAULT_ACTIVE_ORG_COOKIE_NAME,
901
1105
  DEFAULT_AUTH_COOKIE_NAME,
1106
+ DatabaseError,
1107
+ DoorRefusedError,
902
1108
  IncompleteReadError,
903
1109
  RecordUnavailableError,
904
1110
  SessionUnavailableError,
905
1111
  WriteDidNotLandError,
906
1112
  WriteRefusedError,
1113
+ archive,
907
1114
  asJsonObject,
1115
+ assertDoor,
908
1116
  assertWriteLanded,
1117
+ call,
1118
+ changesOf,
1119
+ count,
909
1120
  createActiveOrgCookie,
910
1121
  createAuthCookie,
1122
+ createDb,
911
1123
  createOAuthRefreshFetch,
1124
+ createServiceDb,
912
1125
  createSessionRetry,
913
1126
  decodeActiveOrgSelection,
914
1127
  decodeOAuthRefreshClaims,
915
1128
  describeWriteFailure,
916
1129
  encodeActiveOrgSelection,
917
1130
  guardedUpdate,
1131
+ insert,
918
1132
  isMissingSessionError,
919
1133
  isRecordUnavailableError,
1134
+ listAll,
920
1135
  mergeJsonColumn,
1136
+ page,
921
1137
  parseCookieHeader,
922
1138
  personSentence,
1139
+ read,
923
1140
  readAllRows,
924
1141
  readAllRowsRest,
925
1142
  recordUnavailableMessage,
1143
+ remove,
1144
+ restore,
926
1145
  serverMessageFromBody,
1146
+ tableByName,
927
1147
  tryReadAllRows,
928
1148
  tryReadAllRowsRest,
929
1149
  tryWriteOne,
930
1150
  uniqueChannelTopic,
1151
+ update,
931
1152
  writeFailureStatus,
932
1153
  writeOne,
933
1154
  writeOneRow