better-supabase 0.0.0 → 0.1.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.
Files changed (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -1
  3. package/bin/better-supabase.js +3 -0
  4. package/dist/casing-da0uRTqt.js +14 -0
  5. package/dist/cli/bin.d.ts +1 -0
  6. package/dist/cli/bin.js +15 -0
  7. package/dist/cli/index.d.ts +226 -0
  8. package/dist/cli/index.js +3 -0
  9. package/dist/cli-CoByWYja.js +6472 -0
  10. package/dist/client/index.d.ts +2 -0
  11. package/dist/client/index.js +112 -0
  12. package/dist/compiler-DL18ukPe.d.ts +14 -0
  13. package/dist/config/index.d.ts +3 -0
  14. package/dist/config/index.js +3 -0
  15. package/dist/config-D-aR2ZoN.js +276 -0
  16. package/dist/define-CTfWw-SS.d.ts +662 -0
  17. package/dist/define-QGkq04oe.js +1068 -0
  18. package/dist/edge/index.d.ts +43 -0
  19. package/dist/edge/index.js +103 -0
  20. package/dist/entitlements-BBg61DQZ.d.ts +20 -0
  21. package/dist/entitlements-BC_khwRy.js +17 -0
  22. package/dist/env/index.d.ts +2 -0
  23. package/dist/env/index.js +2 -0
  24. package/dist/env-BEToab0I.js +204 -0
  25. package/dist/errors-Bl3rkOzo.js +196 -0
  26. package/dist/events/index.d.ts +2 -0
  27. package/dist/events/index.js +2 -0
  28. package/dist/events-2OJsVG_d.d.ts +84 -0
  29. package/dist/events-BuzTgn2D.js +135 -0
  30. package/dist/executor-Bazhgsgq.d.ts +195 -0
  31. package/dist/executor-Bje4suQF.d.ts +25 -0
  32. package/dist/executor-Ceq60hPM.js +89 -0
  33. package/dist/hono/index.d.ts +45 -0
  34. package/dist/hono/index.js +70 -0
  35. package/dist/index-BD6wKthp.d.ts +597 -0
  36. package/dist/index-DQVvHzjW.d.ts +74 -0
  37. package/dist/index-DRid0qIv.d.ts +76 -0
  38. package/dist/index-Dup-nSe9.d.ts +117 -0
  39. package/dist/index-Dy6kuF0P.d.ts +71 -0
  40. package/dist/index-Hf8PP9sP.d.ts +116 -0
  41. package/dist/index-JXHBuS53.d.ts +144 -0
  42. package/dist/index-Nxo3TJcm.d.ts +208 -0
  43. package/dist/index-Sw43J8HG.d.ts +62 -0
  44. package/dist/index-cEeweTeM.d.ts +38 -0
  45. package/dist/index.d.ts +110 -0
  46. package/dist/index.js +42 -0
  47. package/dist/invalidate-DbxuL2jL.js +18 -0
  48. package/dist/jobs/index.d.ts +173 -0
  49. package/dist/jobs/index.js +477 -0
  50. package/dist/json-schema-BbwO2mZg.js +181 -0
  51. package/dist/lint/index.d.ts +75 -0
  52. package/dist/lint/index.js +167 -0
  53. package/dist/list/index.d.ts +2 -0
  54. package/dist/list/index.js +410 -0
  55. package/dist/live-6b9i72Ux.d.ts +180 -0
  56. package/dist/live-D8rV1Ldw.js +168 -0
  57. package/dist/mcp/index.d.ts +105 -0
  58. package/dist/mcp/index.js +330 -0
  59. package/dist/mfa-CpogP66z.js +521 -0
  60. package/dist/mfa-TVia554c.d.ts +44 -0
  61. package/dist/next/image/index.d.ts +34 -0
  62. package/dist/next/image/index.js +40 -0
  63. package/dist/next/index.d.ts +210 -0
  64. package/dist/next/index.js +426 -0
  65. package/dist/openapi/index.d.ts +2 -0
  66. package/dist/openapi/index.js +332 -0
  67. package/dist/orpc/index.d.ts +46 -0
  68. package/dist/orpc/index.js +50 -0
  69. package/dist/otel/index.d.ts +40 -0
  70. package/dist/otel/index.js +168 -0
  71. package/dist/path-Cv__-OTj.d.ts +17 -0
  72. package/dist/plugin-BqR6wKMB.js +23 -0
  73. package/dist/plugin-HvGqCurB.d.ts +118 -0
  74. package/dist/plugins/actor/index.d.ts +13 -0
  75. package/dist/plugins/actor/index.js +43 -0
  76. package/dist/plugins/rules/index.d.ts +88 -0
  77. package/dist/plugins/rules/index.js +174 -0
  78. package/dist/plugins/soft-delete/index.d.ts +38 -0
  79. package/dist/plugins/soft-delete/index.js +69 -0
  80. package/dist/plugins/tenant/index.d.ts +43 -0
  81. package/dist/plugins/tenant/index.js +73 -0
  82. package/dist/plugins/timestamps/index.d.ts +9 -0
  83. package/dist/plugins/timestamps/index.js +37 -0
  84. package/dist/plugins/validation/index.d.ts +24 -0
  85. package/dist/plugins/validation/index.js +59 -0
  86. package/dist/pool-BFeqzwQS.d.ts +37 -0
  87. package/dist/postgres/index.d.ts +28 -0
  88. package/dist/postgres/index.js +79 -0
  89. package/dist/postgrest-Dm-vhY6R.d.ts +43 -0
  90. package/dist/problem-DPu6Fh-t.js +97 -0
  91. package/dist/problem-DlgB0lYl.d.ts +47 -0
  92. package/dist/query/index.d.ts +2 -0
  93. package/dist/query/index.js +3 -0
  94. package/dist/query-ChvDbsDE.js +186 -0
  95. package/dist/react/index.d.ts +7 -0
  96. package/dist/react/index.js +5 -0
  97. package/dist/react/server.d.ts +13 -0
  98. package/dist/react/server.js +25 -0
  99. package/dist/react/session.d.ts +2 -0
  100. package/dist/react/session.js +3 -0
  101. package/dist/react-CrOvrnIP.js +241 -0
  102. package/dist/read-set-DhX2d04c.js +216 -0
  103. package/dist/realtime/index.d.ts +3 -0
  104. package/dist/realtime/index.js +253 -0
  105. package/dist/resolve-DW8h9NPm.d.ts +155 -0
  106. package/dist/resource-CYiu6Ygi.js +175 -0
  107. package/dist/resource-SkPTRJDD.d.ts +65 -0
  108. package/dist/respond-4-dhoV7x.js +380 -0
  109. package/dist/respond-PoTpy5DT.d.ts +169 -0
  110. package/dist/result-CTkCZ6JA.d.ts +198 -0
  111. package/dist/result-DWXatkd6.js +103 -0
  112. package/dist/scope-CDHcS7dE.js +90 -0
  113. package/dist/seed-CzecODcJ.js +101 -0
  114. package/dist/server/index.d.ts +42 -0
  115. package/dist/server/index.js +68 -0
  116. package/dist/session-C3osL21k.d.ts +21 -0
  117. package/dist/session-CD-3orO_.js +19 -0
  118. package/dist/shared-_6-uPL7E.js +57 -0
  119. package/dist/simplify-oHiDKRVQ.js +665 -0
  120. package/dist/spec-pins-CJPVghHy.d.ts +20 -0
  121. package/dist/spec-pins-t6sPaHvl.js +20 -0
  122. package/dist/sql-M8l3fnS0.js +260 -0
  123. package/dist/ssr/index.d.ts +5 -0
  124. package/dist/ssr/index.js +4 -0
  125. package/dist/standard-CyhRzctb.d.ts +7 -0
  126. package/dist/standard-FUi3Z3-7.js +21 -0
  127. package/dist/stats-3iy8u3_P.js +566 -0
  128. package/dist/storage/index.d.ts +4 -0
  129. package/dist/storage/index.js +2 -0
  130. package/dist/storage-BWXVUXGk.js +461 -0
  131. package/dist/tables-rZxRxNPW.js +59 -0
  132. package/dist/template-BeOGnynM.js +93 -0
  133. package/dist/template-mbvlysOo.d.ts +6 -0
  134. package/dist/testing/index.d.ts +294 -0
  135. package/dist/testing/index.js +751 -0
  136. package/dist/types-6cYK9pbJ.js +29 -0
  137. package/dist/types-C2fslP1z.d.ts +195 -0
  138. package/dist/version-Cqq1m3UY.js +4 -0
  139. package/dist/view-B0ltUmTi.d.ts +43 -0
  140. package/dist/view-B6F9QbG-.js +35 -0
  141. package/dist/webhooks/index.d.ts +152 -0
  142. package/dist/webhooks/index.js +2 -0
  143. package/dist/webhooks-DbMTJJX2.js +139 -0
  144. package/package.json +246 -17
  145. package/schemas/config-v1.json +328 -0
  146. package/schemas/doctor-report-v1.json +105 -0
  147. package/schemas/snapshot-v2.json +1002 -0
  148. package/skills/better-supabase/SKILL.md +84 -0
  149. package/skills/better-supabase/references/plugins.md +56 -0
  150. package/skills/better-supabase/references/troubleshooting.md +39 -0
  151. package/skills/better-supabase-api/SKILL.md +67 -0
  152. package/skills/better-supabase-api/references/adapters.md +108 -0
  153. package/skills/better-supabase-testing/SKILL.md +65 -0
  154. package/index.js +0 -1
@@ -0,0 +1,1068 @@
1
+ import { n as dbError, t as DbException } from "./errors-Bl3rkOzo.js";
2
+ import { a as invalidRequest, c as needsDecoding, o as decodeRows, r as IrBuilder, s as encodeValue } from "./simplify-oHiDKRVQ.js";
3
+ import { i as or, t as and } from "./types-6cYK9pbJ.js";
4
+ import { i as ok, n as err, o as toDbError, s as withErrorMapper, t as AsyncResult } from "./result-DWXatkd6.js";
5
+ import { t as invalidationTargets } from "./tables-rZxRxNPW.js";
6
+ import { r as orderPlugins } from "./plugin-BqR6wKMB.js";
7
+ import { a as postgrestExecutor, n as StatsRecorder, r as recordStats } from "./stats-3iy8u3_P.js";
8
+ import { d as isQuerySpec, f as specTables, n as checkParams, o as isReadSet, r as containsPlaceholder, t as bindParams, u as createSpecs } from "./read-set-DhX2d04c.js";
9
+ import { t as validate } from "./standard-FUi3Z3-7.js";
10
+ //#region src/core/batch.ts
11
+ /**
12
+ * Holds operations until each of `readers` has either submitted one or
13
+ * finished, then runs the held ones through `batch` together. A reader that
14
+ * runs a second operation joins the next batch.
15
+ */
16
+ function batchingExecutor(base, readers) {
17
+ let running = readers;
18
+ const queue = [];
19
+ const flush = () => {
20
+ if (queue.length === 0 || queue.length < running) return;
21
+ const items = queue.splice(0);
22
+ base.batch(items.map((item) => item.op), items[0].context).then((results) => {
23
+ items.forEach((item, index) => {
24
+ item.resolve(results[index] ?? err(toDbError(/* @__PURE__ */ new Error(`Executor "${base.name}" returned too few results`))));
25
+ });
26
+ }, (cause) => {
27
+ for (const item of items) item.resolve(err(toDbError(cause)));
28
+ });
29
+ };
30
+ return {
31
+ executor: {
32
+ name: base.name,
33
+ execute: (op, context) => new Promise((resolve) => {
34
+ queue.push({
35
+ op,
36
+ context,
37
+ resolve
38
+ });
39
+ flush();
40
+ }),
41
+ ...base.rpc ? { rpc: base.rpc.bind(base) } : {}
42
+ },
43
+ done: () => {
44
+ running -= 1;
45
+ flush();
46
+ }
47
+ };
48
+ }
49
+ //#endregion
50
+ //#region src/core/cache.ts
51
+ /** Primary key of a row as a string, or `undefined` when it isn't returned. */
52
+ function rowKey(table, row) {
53
+ if (table.primaryKey.length === 0) return void 0;
54
+ const parts = table.primaryKey.map((column) => row[column]);
55
+ if (parts.some((part) => part === void 0 || part === null)) return void 0;
56
+ return parts.map(String).join(",");
57
+ }
58
+ function cacheTargetOf(meta, notice) {
59
+ const table = meta.tables[notice.table];
60
+ const ids = table ? [...new Set(notice.rows.flatMap((row) => rowKey(table, row) ?? []))] : [];
61
+ const tenant = notice.context.tenant;
62
+ return {
63
+ table: notice.table,
64
+ tables: notice.kind === "delete" ? invalidationTargets(meta, notice.table) : [notice.table],
65
+ ids,
66
+ ...tenant === void 0 ? {} : { tenant }
67
+ };
68
+ }
69
+ /** One target per table an RPC changes; RPCs report no row ids. */
70
+ function rpcCacheTargets(meta, notice) {
71
+ const tenant = notice.context.tenant;
72
+ return notice.invalidates.map((table) => ({
73
+ table,
74
+ tables: invalidationTargets(meta, table),
75
+ ids: [],
76
+ ...tenant === void 0 ? {} : { tenant }
77
+ }));
78
+ }
79
+ /** Records invalidations. For tests and for checking what a mutation touches. */
80
+ function memoryCache() {
81
+ const invalidated = [];
82
+ return {
83
+ name: "memory",
84
+ invalidated,
85
+ invalidate: (target) => {
86
+ invalidated.push(target);
87
+ },
88
+ clear: () => {
89
+ invalidated.length = 0;
90
+ }
91
+ };
92
+ }
93
+ //#endregion
94
+ //#region src/core/logger.ts
95
+ const log = (method) => (message, fields) => {
96
+ if (fields === void 0) console[method](`better-supabase: ${message}`);
97
+ else console[method](`better-supabase: ${message}`, fields);
98
+ };
99
+ /** The default: `console`, with a `better-supabase:` prefix. */
100
+ const consoleLogger = {
101
+ debug: log("debug"),
102
+ info: log("info"),
103
+ warn: log("warn"),
104
+ error: log("error")
105
+ };
106
+ const noop = () => {};
107
+ const silentLogger = {
108
+ debug: noop,
109
+ info: noop,
110
+ warn: noop,
111
+ error: noop
112
+ };
113
+ //#endregion
114
+ //#region src/core/events.ts
115
+ /**
116
+ * Observers only: a handler that throws is reported to the logger and never
117
+ * changes a result.
118
+ */
119
+ var EventHub = class {
120
+ #handlers = /* @__PURE__ */ new Map();
121
+ logger;
122
+ constructor(logger = consoleLogger) {
123
+ this.logger = logger;
124
+ }
125
+ on(name, handler) {
126
+ let set = this.#handlers.get(name);
127
+ if (!set) {
128
+ set = /* @__PURE__ */ new Set();
129
+ this.#handlers.set(name, set);
130
+ }
131
+ set.add(handler);
132
+ return () => {
133
+ set.delete(handler);
134
+ };
135
+ }
136
+ has(name) {
137
+ return (this.#handlers.get(name)?.size ?? 0) > 0;
138
+ }
139
+ emit(name, event) {
140
+ const set = this.#handlers.get(name);
141
+ if (!set) return;
142
+ for (const handler of set) try {
143
+ handler(event);
144
+ } catch (cause) {
145
+ this.logger.error(`"${name}" handler threw`, { cause });
146
+ }
147
+ }
148
+ };
149
+ //#endregion
150
+ //#region src/core/cursor.ts
151
+ const encoder = new TextEncoder();
152
+ const decoder = new TextDecoder();
153
+ function toBase64Url(bytes) {
154
+ let binary = "";
155
+ for (const byte of bytes) binary += String.fromCharCode(byte);
156
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
157
+ }
158
+ function fromBase64Url(text) {
159
+ const base64 = text.replace(/-/g, "+").replace(/_/g, "/");
160
+ const binary = atob(base64 + "=".repeat((4 - base64.length % 4) % 4));
161
+ const bytes = new Uint8Array(binary.length);
162
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
163
+ return bytes;
164
+ }
165
+ /** Opaque, URL-safe keyset cursor. */
166
+ function encodeCursor(values) {
167
+ return toBase64Url(encoder.encode(JSON.stringify(values)));
168
+ }
169
+ function decodeCursor(cursor) {
170
+ try {
171
+ const parsed = JSON.parse(decoder.decode(fromBase64Url(cursor)));
172
+ return Array.isArray(parsed) ? parsed : void 0;
173
+ } catch {
174
+ return;
175
+ }
176
+ }
177
+ //#endregion
178
+ //#region src/core/repository.ts
179
+ const KNOWN_KEYS = /* @__PURE__ */ new Set([
180
+ "select",
181
+ "include",
182
+ "where",
183
+ "orderBy",
184
+ "limit",
185
+ "offset",
186
+ "signal",
187
+ "returning",
188
+ "expect",
189
+ "onConflict",
190
+ "ignoreDuplicates",
191
+ "page",
192
+ "size",
193
+ "count",
194
+ "after",
195
+ "mode",
196
+ "data",
197
+ "groupBy",
198
+ "_count",
199
+ "_sum",
200
+ "_avg",
201
+ "_min",
202
+ "_max"
203
+ ]);
204
+ function optionsOf(args) {
205
+ if (!args) return {};
206
+ const options = {};
207
+ for (const [key, value] of Object.entries(args)) if (!KNOWN_KEYS.has(key)) options[key] = value;
208
+ return options;
209
+ }
210
+ function signalOf(args) {
211
+ const signal = args?.signal;
212
+ return signal instanceof AbortSignal ? signal : void 0;
213
+ }
214
+ function mutationKind(op) {
215
+ if (op.kind === "insert") return op.onConflict ? "upsert" : "insert";
216
+ return op.kind;
217
+ }
218
+ /** Runs operations through plugins, the executor and the event hub. */
219
+ var OperationRunner = class {
220
+ runtime;
221
+ constructor(runtime) {
222
+ this.runtime = runtime;
223
+ }
224
+ hookArgs(table, options) {
225
+ return {
226
+ table,
227
+ schema: this.runtime.meta,
228
+ context: this.runtime.context,
229
+ options,
230
+ now: this.runtime.now
231
+ };
232
+ }
233
+ async run(op, options, signal) {
234
+ const { runtime } = this;
235
+ const hook = this.hookArgs(op.table, options);
236
+ let current = op;
237
+ try {
238
+ for (const plugin of runtime.plugins) if (plugin.transformQuery) current = plugin.transformQuery(current, hook);
239
+ if (current.kind !== "select") {
240
+ let mutation = current;
241
+ for (const plugin of runtime.plugins) if (plugin.beforeMutation) mutation = await plugin.beforeMutation(mutation, hook);
242
+ current = mutation;
243
+ }
244
+ } catch (cause) {
245
+ if (cause instanceof DbException) return this.fail(op.table, cause.error);
246
+ throw cause;
247
+ }
248
+ const started = performance.now();
249
+ const context = signal ? {
250
+ signal,
251
+ errorMappers: runtime.errorMappers
252
+ } : { errorMappers: runtime.errorMappers };
253
+ const executed = await runtime.executor.execute(current, context);
254
+ const selection = current.kind === "select" ? current.selection : current.returning;
255
+ const result = executed.ok && selection && needsDecoding(selection) ? ok({
256
+ ...executed.data,
257
+ rows: decodeRows(selection, executed.data.rows)
258
+ }) : executed;
259
+ const rows = result.ok ? result.data.rows.length : 0;
260
+ const truncated = current.kind === "select" && current.limit === void 0 && current.single === void 0 && !current.head && rows >= runtime.maxRows;
261
+ if (truncated && !runtime.truncatedTables.has(op.table.key)) {
262
+ runtime.truncatedTables.add(op.table.key);
263
+ runtime.events.logger.warn(`a read on ${op.table.key} returned ${rows} rows, the maxRows cap; add a limit or use paginate()`, {
264
+ table: op.table.key,
265
+ maxRows: runtime.maxRows
266
+ });
267
+ }
268
+ if (runtime.events.has("query")) runtime.events.emit("query", {
269
+ table: op.table.key,
270
+ operation: current.kind,
271
+ ok: result.ok,
272
+ durationMs: performance.now() - started,
273
+ rows,
274
+ truncated
275
+ });
276
+ if (!result.ok) return this.fail(op.table, result.error);
277
+ if (current.kind !== "select") await this.afterMutation(current, result.data);
278
+ return result;
279
+ }
280
+ async afterMutation(op, data) {
281
+ const { runtime } = this;
282
+ const event = {
283
+ table: op.table,
284
+ kind: mutationKind(op),
285
+ rows: data.rows,
286
+ context: runtime.context
287
+ };
288
+ for (const plugin of runtime.plugins) {
289
+ if (!plugin.afterMutation) continue;
290
+ try {
291
+ await plugin.afterMutation(event);
292
+ } catch (cause) {
293
+ runtime.events.logger.error(`plugin "${plugin.name}" afterMutation threw`, { cause });
294
+ }
295
+ }
296
+ runtime.events.emit("mutation", {
297
+ table: op.table.key,
298
+ kind: event.kind,
299
+ rows: event.rows,
300
+ context: event.context
301
+ });
302
+ }
303
+ fail(table, error) {
304
+ const withTable = error.table ? error : {
305
+ ...error,
306
+ table: table.key
307
+ };
308
+ this.runtime.events.emit("error", {
309
+ table: table.key,
310
+ error: withTable
311
+ });
312
+ return err(withTable);
313
+ }
314
+ };
315
+ /** Creates the runtime repository object for one table. */
316
+ function createRepository(runner, table) {
317
+ const { builder } = runner.runtime;
318
+ const selection = (args) => builder.selection(table, args?.select, args?.include);
319
+ const selectOp = (args, patch = {}) => ({
320
+ kind: "select",
321
+ table,
322
+ selection: selection(args),
323
+ where: builder.where(table, args?.where),
324
+ orderBy: builder.orderBy(table, args?.orderBy),
325
+ limit: typeof args?.limit === "number" ? args.limit : void 0,
326
+ offset: typeof args?.offset === "number" ? args.offset : void 0,
327
+ count: void 0,
328
+ head: false,
329
+ single: void 0,
330
+ ...patch
331
+ });
332
+ const returning = (args) => args?.returning === false ? void 0 : selection(args);
333
+ const run = (op, args) => runner.run(op, optionsOf(args), signalOf(args));
334
+ const notFound = () => runner.fail(table, dbError("not_found", `No ${table.key} row matched`));
335
+ const base = {
336
+ $table: table.key,
337
+ $meta: table,
338
+ findMany(args) {
339
+ return AsyncResult.from(async () => {
340
+ const op = selectOp(args);
341
+ const ordered = op.orderBy.length > 0 || table.primaryKey.length === 0 ? op : {
342
+ ...op,
343
+ orderBy: table.primaryKey.map((column) => ({
344
+ column,
345
+ direction: "asc"
346
+ }))
347
+ };
348
+ const result = await run(ordered, args);
349
+ return result.ok ? ok(result.data.rows) : result;
350
+ });
351
+ },
352
+ findFirst(args) {
353
+ return AsyncResult.from(async () => {
354
+ const result = await run(selectOp(args, { limit: 1 }), args);
355
+ return result.ok ? ok(result.data.rows[0] ?? null) : result;
356
+ });
357
+ },
358
+ findUnique(args) {
359
+ return AsyncResult.from(async () => {
360
+ const op = selectOp(args, {
361
+ where: builder.uniqueKey(table, args.where),
362
+ limit: 1
363
+ });
364
+ const result = await run(op, args);
365
+ return result.ok ? ok(result.data.rows[0] ?? null) : result;
366
+ });
367
+ },
368
+ findById(id, args) {
369
+ return AsyncResult.from(async () => {
370
+ const op = selectOp(args, {
371
+ where: builder.primaryKey(table, id),
372
+ limit: 1
373
+ });
374
+ const result = await run(op, args);
375
+ if (!result.ok) return result;
376
+ const row = result.data.rows[0];
377
+ return row ? ok(row) : notFound();
378
+ });
379
+ },
380
+ count(args) {
381
+ return AsyncResult.from(async () => {
382
+ const mode = args?.mode;
383
+ const op = selectOp({ where: args?.where }, {
384
+ selection: {
385
+ columns: [],
386
+ includes: []
387
+ },
388
+ count: mode === "planned" || mode === "estimated" ? mode : "exact",
389
+ head: true
390
+ });
391
+ const result = await run(op, args);
392
+ return result.ok ? ok(result.data.count ?? 0) : result;
393
+ });
394
+ },
395
+ aggregate(args) {
396
+ return AsyncResult.from(async () => {
397
+ const aggregation = builder.aggregation(table, args);
398
+ const orderBy = builder.orderBy(table, args.orderBy);
399
+ const grouped = new Set(aggregation.columns.map((c) => c.column));
400
+ const loose = orderBy.find((term) => !grouped.has(term.column));
401
+ if (loose) return runner.fail(table, dbError("invalid_request", `aggregate on "${table.key}" can only sort by groupBy columns, not "${loose.column}"`));
402
+ const op = selectOp({
403
+ where: args.where,
404
+ limit: args.limit,
405
+ offset: args.offset
406
+ }, {
407
+ selection: aggregation,
408
+ orderBy
409
+ });
410
+ const result = await run(op, args);
411
+ if (!result.ok) return result;
412
+ if (args.groupBy !== void 0) return ok(result.data.rows);
413
+ return ok(result.data.rows[0] ?? decodeRows(aggregation, [{}])[0] ?? {});
414
+ });
415
+ },
416
+ exists(args) {
417
+ return AsyncResult.from(async () => {
418
+ const columns = table.primaryKey.slice(0, 1).map((alias) => builder.selectColumn(table, alias));
419
+ const op = selectOp({ where: args?.where }, {
420
+ selection: {
421
+ columns,
422
+ includes: []
423
+ },
424
+ limit: 1
425
+ });
426
+ const result = await run(op, args);
427
+ return result.ok ? ok(result.data.rows.length > 0) : result;
428
+ });
429
+ },
430
+ paginate(args) {
431
+ return AsyncResult.from(async () => "after" in args ? cursorPage(args) : offsetPage(args));
432
+ },
433
+ create(data, args) {
434
+ return AsyncResult.from(async () => {
435
+ const op = insertOp([data], args, void 0);
436
+ const result = await run(op, args);
437
+ if (!result.ok) return result;
438
+ return ok(args?.returning === false ? null : result.data.rows[0] ?? null);
439
+ });
440
+ },
441
+ createMany(rows, args) {
442
+ return AsyncResult.from(async () => {
443
+ if (rows.length === 0) return ok(args?.returning === false ? { count: 0 } : []);
444
+ const result = await run(insertOp(rows, args, void 0), args);
445
+ if (!result.ok) return result;
446
+ return ok(args?.returning === false ? { count: result.data.count ?? rows.length } : result.data.rows);
447
+ });
448
+ },
449
+ update(id, patch, args) {
450
+ return AsyncResult.from(async () => {
451
+ const key = builder.primaryKey(table, id);
452
+ const expect = args?.expect ? builder.where(table, args.expect) : void 0;
453
+ const op = {
454
+ kind: "update",
455
+ table,
456
+ set: builder.row(table, patch, "update"),
457
+ where: and(key, expect),
458
+ returning: returning(args)
459
+ };
460
+ const result = await run(op, args);
461
+ if (!result.ok) return result;
462
+ if ((op.returning ? result.data.rows.length : result.data.count ?? 0) === 0) {
463
+ if (!expect) return notFound();
464
+ const exists = await run(selectOp(void 0, {
465
+ where: key,
466
+ selection: {
467
+ columns: [],
468
+ includes: []
469
+ },
470
+ count: "exact",
471
+ head: true
472
+ }), args);
473
+ if (exists.ok && (exists.data.count ?? 0) > 0) return runner.fail(table, dbError("stale", `The ${table.key} row changed since it was read`));
474
+ return notFound();
475
+ }
476
+ return ok(op.returning ? result.data.rows[0] ?? null : null);
477
+ });
478
+ },
479
+ updateMany(args) {
480
+ return AsyncResult.from(async () => {
481
+ const op = {
482
+ kind: "update",
483
+ table,
484
+ set: builder.row(table, args.data, "update"),
485
+ where: builder.where(table, args.where),
486
+ returning: void 0
487
+ };
488
+ const result = await run(op, args);
489
+ return result.ok ? ok({ count: result.data.count ?? 0 }) : result;
490
+ });
491
+ },
492
+ upsert(data, args) {
493
+ return AsyncResult.from(async () => {
494
+ const op = insertOp([data], args, conflictColumns(args?.onConflict));
495
+ const result = await run(op, args);
496
+ if (!result.ok) return result;
497
+ return ok(args?.returning === false ? null : result.data.rows[0] ?? null);
498
+ });
499
+ },
500
+ upsertMany(rows, args) {
501
+ return AsyncResult.from(async () => {
502
+ if (rows.length === 0) return ok(args?.returning === false ? { count: 0 } : []);
503
+ const op = insertOp(rows, args, conflictColumns(args?.onConflict));
504
+ const result = await run(op, args);
505
+ if (!result.ok) return result;
506
+ return ok(args?.returning === false ? { count: result.data.count ?? rows.length } : result.data.rows);
507
+ });
508
+ },
509
+ delete(id, args) {
510
+ return AsyncResult.from(async () => {
511
+ const op = {
512
+ kind: "delete",
513
+ table,
514
+ where: builder.primaryKey(table, id),
515
+ returning: builder.selection(table, table.primaryKey, void 0)
516
+ };
517
+ const result = await run(op, args);
518
+ if (!result.ok) return result;
519
+ return (result.data.count ?? result.data.rows.length) === 0 ? notFound() : ok(void 0);
520
+ });
521
+ },
522
+ deleteMany(args) {
523
+ return AsyncResult.from(async () => {
524
+ const where = builder.where(table, args.where);
525
+ if (!where) return runner.fail(table, dbError("invalid_request", "deleteMany needs a non-empty \"where\""));
526
+ const result = await run({
527
+ kind: "delete",
528
+ table,
529
+ where,
530
+ returning: void 0
531
+ }, args);
532
+ return result.ok ? ok({ count: result.data.count ?? 0 }) : result;
533
+ });
534
+ }
535
+ };
536
+ function insertOp(rows, args, conflict) {
537
+ return {
538
+ kind: "insert",
539
+ table,
540
+ rows: rows.map((row) => builder.row(table, row)),
541
+ returning: returning(args),
542
+ onConflict: conflict ? {
543
+ columns: conflict,
544
+ action: args?.ignoreDuplicates === true ? "ignore" : "update"
545
+ } : void 0,
546
+ defaultToNull: false
547
+ };
548
+ }
549
+ function conflictColumns(target) {
550
+ if (target === void 0 || target === "primaryKey") return table.primaryKey.map((name) => builder.column(table, name));
551
+ if (typeof target === "string") {
552
+ const columns = table.uniqueKeys[target];
553
+ if (!columns) invalidRequest(`Unknown unique key "${target}" on "${table.key}"`, table.key);
554
+ return columns.map((name) => builder.column(table, name));
555
+ }
556
+ if (Array.isArray(target)) return target.map((name) => builder.column(table, String(name)));
557
+ return invalidRequest(`Invalid onConflict on "${table.key}"`, table.key);
558
+ }
559
+ async function offsetPage(args) {
560
+ const size = Number(args.size);
561
+ if (!Number.isInteger(size) || size < 1) return runner.fail(table, dbError("invalid_request", "\"size\" must be a positive integer"));
562
+ const number = args.page === void 0 ? 1 : Number(args.page);
563
+ if (!Number.isInteger(number) || number < 1) return runner.fail(table, dbError("invalid_request", "\"page\" must be a positive integer"));
564
+ const count = args.count;
565
+ const op = selectOp(args, {
566
+ limit: size + 1,
567
+ offset: (number - 1) * size,
568
+ count: count === "exact" || count === "planned" || count === "estimated" ? count : void 0
569
+ });
570
+ const result = await run(op, args);
571
+ if (!result.ok) return result;
572
+ const rows = result.data.rows;
573
+ const total = result.data.count;
574
+ return ok({
575
+ items: rows.slice(0, size),
576
+ page: {
577
+ number,
578
+ size,
579
+ total,
580
+ pages: total === null ? null : Math.max(1, Math.ceil(total / size)),
581
+ hasMore: rows.length > size
582
+ }
583
+ });
584
+ }
585
+ async function cursorPage(args) {
586
+ const size = Number(args.size);
587
+ if (!Number.isInteger(size) || size < 1) return runner.fail(table, dbError("invalid_request", "\"size\" must be a positive integer"));
588
+ const orderBy = withTieBreaker(builder.orderBy(table, args.orderBy));
589
+ const { selection: withSort, added } = ensureColumns(selection(args), orderBy);
590
+ let after;
591
+ if (typeof args.after === "string") {
592
+ const values = decodeCursor(args.after);
593
+ if (!values || values.length !== orderBy.length) return runner.fail(table, dbError("invalid_request", "Invalid cursor"));
594
+ after = keysetCondition(orderBy, values);
595
+ }
596
+ const op = selectOp(args, {
597
+ selection: withSort,
598
+ where: and(builder.where(table, args.where), after),
599
+ orderBy,
600
+ limit: size + 1,
601
+ offset: void 0
602
+ });
603
+ const result = await run(op, args);
604
+ if (!result.ok) return result;
605
+ const rows = result.data.rows.slice(0, size);
606
+ const hasMore = result.data.rows.length > size;
607
+ const last = rows.at(-1);
608
+ const nextCursor = hasMore && last ? encodeCursor(orderBy.map((term) => encodeValue(last[aliasOf(withSort, term.column)]))) : null;
609
+ return ok({
610
+ items: added.length > 0 ? rows.map((row) => strip(row, added)) : rows,
611
+ nextCursor,
612
+ hasMore
613
+ });
614
+ }
615
+ function withTieBreaker(orderBy) {
616
+ const terms = [...orderBy];
617
+ for (const name of table.primaryKey) {
618
+ const column = builder.column(table, name);
619
+ if (!terms.some((term) => term.column === column)) terms.push({
620
+ column,
621
+ direction: terms[0]?.direction ?? "asc"
622
+ });
623
+ }
624
+ return terms;
625
+ }
626
+ function ensureColumns(base, orderBy) {
627
+ const columns = [...base.columns];
628
+ const added = [];
629
+ for (const term of orderBy) {
630
+ if (columns.some((entry) => entry.column === term.column)) continue;
631
+ const alias = Object.entries(table.columns).find(([, meta]) => meta.db === term.column)?.[0] ?? term.column;
632
+ columns.push({
633
+ alias,
634
+ column: term.column
635
+ });
636
+ added.push(alias);
637
+ }
638
+ return {
639
+ selection: {
640
+ columns,
641
+ includes: base.includes
642
+ },
643
+ added
644
+ };
645
+ }
646
+ return base;
647
+ }
648
+ function aliasOf(selection, column) {
649
+ return selection.columns.find((entry) => entry.column === column)?.alias ?? column;
650
+ }
651
+ function strip(row, keys) {
652
+ const copy = { ...row };
653
+ for (const key of keys) delete copy[key];
654
+ return copy;
655
+ }
656
+ /** `(a, b) > (x, y)` for mixed directions, as an OR of ANDs. */
657
+ function keysetCondition(orderBy, values) {
658
+ const branches = orderBy.map((term, index) => {
659
+ const equal = orderBy.slice(0, index).map((prev, prevIndex) => ({
660
+ kind: "column",
661
+ column: prev.column,
662
+ op: "eq",
663
+ value: values[prevIndex]
664
+ }));
665
+ const step = {
666
+ kind: "column",
667
+ column: term.column,
668
+ op: term.direction === "asc" ? "gt" : "lt",
669
+ value: values[index]
670
+ };
671
+ return and(...equal, step) ?? step;
672
+ });
673
+ return or(...branches);
674
+ }
675
+ //#endregion
676
+ //#region src/core/search.ts
677
+ /** pgvector's text form, or `undefined` for anything that isn't a finite vector. */
678
+ function vectorLiteral(vector) {
679
+ if (typeof vector === "string") return /^\[[^\]]+\]$/.test(vector.trim()) ? vector.trim() : void 0;
680
+ if (vector.length === 0 || !vector.every(Number.isFinite)) return void 0;
681
+ return `[${vector.join(",")}]`;
682
+ }
683
+ //#endregion
684
+ //#region src/core/define.ts
685
+ function isExecutor(value) {
686
+ return typeof value === "object" && value !== null && typeof value.execute === "function" && typeof value.name === "string";
687
+ }
688
+ /**
689
+ * The isomorphic definition of your data layer: schema plus plugins. Holds no
690
+ * secrets and no connection; `connect()` binds it to a client per request.
691
+ */
692
+ var BetterSupabase = class BetterSupabase {
693
+ schema;
694
+ plugins;
695
+ events;
696
+ options;
697
+ constructor(schema, plugins = [], options = {}, events = new EventHub(options.logger)) {
698
+ this.schema = schema;
699
+ this.plugins = orderPlugins(plugins);
700
+ this.options = options;
701
+ this.events = events;
702
+ }
703
+ get meta() {
704
+ return this.schema.meta;
705
+ }
706
+ /** The schema set by `claims()`; its output types `session.claims`. */
707
+ get claimsSchema() {
708
+ return this.options.claims;
709
+ }
710
+ /**
711
+ * Returns a new instance whose servers validate verified JWT claims (custom
712
+ * access token hook output) with `schema`, and whose sessions are typed by it.
713
+ * A token that fails it resolves to `{ kind: 'invalid', reason: 'claims' }`.
714
+ *
715
+ * ```ts
716
+ * const sb = defineSupabase(schema).claims(z.object({ tenant_id: z.uuid() }));
717
+ * ```
718
+ */
719
+ claims(schema) {
720
+ return new BetterSupabase(this.schema, this.plugins, {
721
+ ...this.options,
722
+ claims: schema
723
+ }, this.events);
724
+ }
725
+ /**
726
+ * Returns a new instance whose `.orThrow()` throws `mapper(error)` instead
727
+ * of a `DbException`, and whose results `toBetterResult` maps the same way.
728
+ * Results themselves keep their `DbError`.
729
+ *
730
+ * ```ts
731
+ * const sb = defineSupabase(schema).mapError((error) => new AppError(error));
732
+ * ```
733
+ */
734
+ mapError(mapper) {
735
+ return new BetterSupabase(this.schema, this.plugins, {
736
+ ...this.options,
737
+ throwAs: mapper
738
+ }, this.events);
739
+ }
740
+ #specs;
741
+ #truncatedTables = /* @__PURE__ */ new Set();
742
+ /**
743
+ * Builds serializable `QuerySpec`s with the same arguments and result types
744
+ * as the repository: `sb.spec.customers.findMany({ select: ['id'] })`.
745
+ */
746
+ get spec() {
747
+ this.#specs ??= createSpecs(this.meta);
748
+ return this.#specs;
749
+ }
750
+ /** App keys of every table a spec reads; mutations of any of them make it stale. */
751
+ tablesOf(spec) {
752
+ return specTables(this.meta, spec);
753
+ }
754
+ /** Returns a new instance with the plugin appended. */
755
+ use(plugin) {
756
+ if (plugin.apiVersion !== 1) throw new TypeError(`better-supabase: plugin "${plugin.name}" targets plugin API v${String(plugin.apiVersion)}; this version supports v1`);
757
+ if (this.plugins.some((existing) => existing.name === plugin.name)) throw new TypeError(`better-supabase: plugin "${plugin.name}" is already installed`);
758
+ return new BetterSupabase(this.schema, [...this.plugins, plugin], this.options, this.events);
759
+ }
760
+ /**
761
+ * Declares the tables an RPC writes, so cache adapters and live queries
762
+ * invalidate them after it succeeds.
763
+ *
764
+ * ```ts
765
+ * sb.defineRpc('archive_customer', { invalidates: ['customers', 'notes'] });
766
+ * ```
767
+ */
768
+ defineRpc(name, options) {
769
+ for (const table of options.invalidates) if (!this.meta.tables[table]) throw new TypeError(`better-supabase: defineRpc("${name}") invalidates unknown table "${table}"`);
770
+ return new BetterSupabase(this.schema, this.plugins, {
771
+ ...this.options,
772
+ rpc: {
773
+ ...this.options.rpc,
774
+ [name]: options
775
+ }
776
+ }, this.events);
777
+ }
778
+ /** Observes queries, mutations, errors, auth and refreshes. */
779
+ on(name, handler) {
780
+ return this.events.on(name, handler);
781
+ }
782
+ /**
783
+ * Invalidates `adapter` after every mutation in this runtime. Returns a
784
+ * function that detaches it. Adapter failures are logged, never thrown.
785
+ */
786
+ cache(adapter) {
787
+ const report = (cause, table) => {
788
+ this.events.logger.error(`cache adapter "${adapter.name}" failed`, {
789
+ cause,
790
+ table
791
+ });
792
+ };
793
+ const invalidate = (target) => {
794
+ try {
795
+ const pending = adapter.invalidate(target);
796
+ if (pending) pending.catch((cause) => report(cause, target.table));
797
+ } catch (cause) {
798
+ report(cause, target.table);
799
+ }
800
+ };
801
+ const offMutation = this.on("mutation", (notice) => {
802
+ invalidate(cacheTargetOf(this.meta, notice));
803
+ });
804
+ const offRpc = this.on("rpc", (notice) => {
805
+ for (const target of rpcCacheTargets(this.meta, notice)) invalidate(target);
806
+ });
807
+ return () => {
808
+ offMutation();
809
+ offRpc();
810
+ };
811
+ }
812
+ connect(source, context = {}, options = {}) {
813
+ const client = isExecutor(source) ? void 0 : source;
814
+ const base = options.executor ?? (isExecutor(source) ? source : postgrestExecutor(source));
815
+ const recorder = new StatsRecorder(options.stats);
816
+ return this.#db(client, base, context, this.plugins, recorder);
817
+ }
818
+ #db(client, base, context, plugins, recorder, events = this.events) {
819
+ let executor = base;
820
+ for (const plugin of plugins) if (plugin.wrapExecutor) executor = plugin.wrapExecutor(executor);
821
+ executor = recordStats(executor, recorder);
822
+ const errorMappers = [...this.options.errors ?? [], ...plugins.flatMap((plugin) => plugin.mapError ? [plugin.mapError] : [])];
823
+ const runner = new OperationRunner({
824
+ meta: this.meta,
825
+ builder: new IrBuilder(this.meta),
826
+ executor,
827
+ plugins,
828
+ context,
829
+ events,
830
+ errorMappers,
831
+ now: this.options.now ?? (() => /* @__PURE__ */ new Date()),
832
+ maxRows: this.options.maxRows ?? 1e3,
833
+ truncatedTables: this.#truncatedTables
834
+ });
835
+ const db = {
836
+ $client: client,
837
+ $executor: executor,
838
+ $context: context,
839
+ $rpc: (name, ...rest) => rpc(executor, errorMappers, name, rest).map((data) => {
840
+ const registered = this.options.rpc?.[name];
841
+ if (registered) this.events.emit("rpc", {
842
+ name,
843
+ invalidates: registered.invalidates,
844
+ context
845
+ });
846
+ return data;
847
+ }),
848
+ $with: (extra) => this.#db(client, base, {
849
+ ...context,
850
+ ...extra
851
+ }, plugins, recorder),
852
+ $withoutPlugins: () => this.#db(client, base, context, [], recorder),
853
+ $stats: () => recorder.snapshot(),
854
+ $table: (name) => {
855
+ if (!Object.hasOwn(this.meta.tables, name)) throw new TypeError(`better-supabase: unknown table "${name}". Known: ${Object.keys(this.meta.tables).join(", ")}`);
856
+ return db[name];
857
+ },
858
+ $run: (spec, options) => runSpec(db, spec, options?.signal),
859
+ $many: (target, ...rest) => {
860
+ if (isReadSet(target)) {
861
+ const [values, options] = rest;
862
+ return readSet(target, values, options?.signal);
863
+ }
864
+ const [options] = rest;
865
+ if (!Array.isArray(target)) return AsyncResult.err(dbError("invalid_request", "db.$many() expects an array of specs or a read set"));
866
+ return many(target, options?.signal, true);
867
+ },
868
+ $search: (name, args) => AsyncResult.from(async () => {
869
+ const table = Object.hasOwn(this.meta.tables, name) ? this.meta.tables[name] : void 0;
870
+ if (!table) return err(dbError("invalid_request", `db.$search(): unknown table "${name}"`));
871
+ if (!base.functionSources) return err(dbError("invalid_request", `db.$search() reads from a function; the ${base.name} executor doesn't support that`, { table: table.key }));
872
+ const query = vectorLiteral(args.vector);
873
+ const k = args.k ?? 10;
874
+ if (query === void 0 || !Number.isInteger(k) || k < 1) return err(dbError("invalid_input", "db.$search() needs a vector of finite numbers and a positive integer k", { table: table.key }));
875
+ const { builder } = runner.runtime;
876
+ const op = {
877
+ kind: "select",
878
+ table,
879
+ selection: builder.selection(table, args.select, args.include),
880
+ where: builder.where(table, args.where),
881
+ orderBy: [],
882
+ limit: k,
883
+ offset: void 0,
884
+ count: void 0,
885
+ head: false,
886
+ single: void 0,
887
+ source: {
888
+ schema: table.schema,
889
+ name: `search_${table.name}`,
890
+ args: {
891
+ query,
892
+ k
893
+ }
894
+ }
895
+ };
896
+ const result = await runner.run(op, {}, args.signal);
897
+ if (result.ok) return ok(result.data.rows);
898
+ return result.error.code === "PGRST202" || result.error.code === "42883" ? err({
899
+ ...result.error,
900
+ hint: `Add "${table.schema}.${table.name}" to vectorSearch in better-supabase.config.ts and run \`better-supabase sql sync\`.`
901
+ }) : result;
902
+ })
903
+ };
904
+ const many = (specs, signal, usePlugins) => AsyncResult.from(async () => {
905
+ const invalid = specs.findIndex((spec) => !isQuerySpec(spec));
906
+ if (invalid !== -1) return err(dbError("invalid_request", `db.$many() entry ${invalid} is not a QuerySpec from sb.spec`));
907
+ const active = usePlugins ? plugins : [];
908
+ const batch = base.batch?.bind(base);
909
+ const batching = batch && specs.length > 1 ? batchingExecutor({
910
+ ...base,
911
+ batch
912
+ }, specs.length) : void 0;
913
+ const target = batching ? this.#db(client, batching.executor, context, active, recorder) : usePlugins ? db : this.#db(client, base, context, [], recorder);
914
+ const results = await Promise.all(specs.map(async (spec) => {
915
+ try {
916
+ return await runSpec(target, spec, signal);
917
+ } finally {
918
+ batching?.done();
919
+ }
920
+ }));
921
+ const data = [];
922
+ for (const result of results) {
923
+ if (!result.ok) return result;
924
+ data.push(result.data);
925
+ }
926
+ return ok(data);
927
+ });
928
+ const readSet = (set, values, signal) => AsyncResult.from(async () => {
929
+ const problems = checkParams(set, values);
930
+ if (problems.length > 0) return err(dbError("invalid_request", `db.$many(${set.name}): ${problems.join(", ")}`));
931
+ const keys = Object.keys(set.specs);
932
+ const bound = bindParams(set, values ?? {});
933
+ if (base.batch || !executor.rpc) {
934
+ const result = await many(keys.map((key) => bound[key]), signal, false);
935
+ if (!result.ok) return result;
936
+ return ok(Object.fromEntries(keys.map((key, i) => [key, result.data[i]])));
937
+ }
938
+ const called = await executor.rpc(set.functionName, { p: values ?? {} }, {
939
+ schema: "public",
940
+ get: true,
941
+ errorMappers,
942
+ ...signal ? { signal } : {}
943
+ });
944
+ if (!called.ok) return called;
945
+ return this.#decodeReadSet(client, context, set, bound, called.data);
946
+ });
947
+ const throwAs = this.options.throwAs;
948
+ if (throwAs) for (const name of [
949
+ "$rpc",
950
+ "$run",
951
+ "$many",
952
+ "$search"
953
+ ]) db[name] = mapThrows(db[name], throwAs);
954
+ for (const [key, table] of Object.entries(this.meta.tables)) {
955
+ let repository;
956
+ Object.defineProperty(db, key, {
957
+ enumerable: true,
958
+ get: () => {
959
+ if (!repository) {
960
+ repository = this.#repository(runner, key, table, plugins);
961
+ if (throwAs) {
962
+ for (const [name, method] of Object.entries(repository)) if (typeof method === "function" && name !== "extend") repository[name] = mapThrows(method, throwAs);
963
+ }
964
+ }
965
+ return repository;
966
+ }
967
+ });
968
+ }
969
+ return db;
970
+ }
971
+ /**
972
+ * Decodes what a read-set function returned (`{ [key]: { rows, count } }`)
973
+ * by running each spec against those rows, so casing, codecs and
974
+ * aggregates come out exactly as from `db.$run`.
975
+ */
976
+ async #decodeReadSet(client, context, set, specs, data) {
977
+ const payload = data ?? {};
978
+ const out = {};
979
+ for (const [key, spec] of Object.entries(specs)) {
980
+ const entry = payload[key];
981
+ if (!entry) return err(dbError("unexpected", `${set.functionName}() returned no "${key}". Run \`better-supabase gen\` and migrate.`));
982
+ const rows = entry.rows ?? [];
983
+ const result = await runSpec(this.#db(client, {
984
+ name: "read-set",
985
+ execute: async (op) => {
986
+ if (op.kind === "select" && op.single) {
987
+ if (rows.length > 1) return err(dbError("multiple_rows", `Expected one ${op.table.key} row`));
988
+ if (rows.length === 0 && op.single === "one") return err(dbError("not_found", `No ${op.table.key} row matched`));
989
+ }
990
+ return ok({
991
+ rows,
992
+ count: entry.count ?? null
993
+ });
994
+ }
995
+ }, context, [], new StatsRecorder(), new EventHub(this.events.logger)), spec, void 0);
996
+ if (!result.ok) return result;
997
+ out[key] = result.data;
998
+ }
999
+ return ok(out);
1000
+ }
1001
+ #repository(runner, key, table, plugins) {
1002
+ const repository = createRepository(runner, table);
1003
+ for (const plugin of plugins) {
1004
+ const methods = plugin.repository?.({
1005
+ table,
1006
+ base: repository
1007
+ });
1008
+ if (!methods) continue;
1009
+ for (const [name, method] of Object.entries(methods)) {
1010
+ if (name in repository) throw new TypeError(`better-supabase: plugin "${plugin.name}" redefines "${key}.${name}"`);
1011
+ repository[name] = method;
1012
+ }
1013
+ }
1014
+ repository.extend = (build) => ({
1015
+ ...repository,
1016
+ ...build(repository)
1017
+ });
1018
+ return repository;
1019
+ }
1020
+ };
1021
+ function mapThrows(method, throwAs) {
1022
+ return function(...args) {
1023
+ const value = method.apply(this, args);
1024
+ return value instanceof AsyncResult ? withErrorMapper(value, throwAs) : value;
1025
+ };
1026
+ }
1027
+ function runSpec(db, spec, signal) {
1028
+ if (!isQuerySpec(spec)) return AsyncResult.err(dbError("invalid_request", "db.$run() expects a QuerySpec from sb.spec"));
1029
+ const method = db[spec.table]?.[spec.method];
1030
+ if (!method) return AsyncResult.err(dbError("invalid_request", `Unknown table "${spec.table}" in QuerySpec`));
1031
+ if (containsPlaceholder(spec.args)) return AsyncResult.err(dbError("invalid_request", "This spec comes from a read set and still holds placeholders; run it with db.$many(readSet, params)"));
1032
+ const args = [...spec.args];
1033
+ if (signal) {
1034
+ const index = spec.method === "findById" ? 1 : 0;
1035
+ args[index] = {
1036
+ ...args[index],
1037
+ signal
1038
+ };
1039
+ }
1040
+ return method(...args);
1041
+ }
1042
+ function rpc(executor, errorMappers, name, rest) {
1043
+ const [args, options] = rest;
1044
+ return AsyncResult.from(async () => {
1045
+ if (!executor.rpc) return err(dbError("invalid_request", `Executor "${executor.name}" does not support rpc()`));
1046
+ const context = {
1047
+ schema: options?.schema ?? "public",
1048
+ errorMappers,
1049
+ ...options?.signal ? { signal: options.signal } : {}
1050
+ };
1051
+ const result = await executor.rpc(name, args ?? {}, context);
1052
+ if (!result.ok || !options?.returns) return result;
1053
+ const checked = await validate(options.returns, result.data, `${name}() result`);
1054
+ return checked.ok ? ok(checked.data) : checked;
1055
+ });
1056
+ }
1057
+ /**
1058
+ * Creates the data-layer definition from the generated `schema`.
1059
+ *
1060
+ * ```ts
1061
+ * export const sb = defineSupabase(schema).use(timestamps()).use(softDelete());
1062
+ * ```
1063
+ */
1064
+ function defineSupabase(schema, options) {
1065
+ return new BetterSupabase(schema, [], options);
1066
+ }
1067
+ //#endregion
1068
+ export { EventHub as a, cacheTargetOf as c, rpcCacheTargets as d, encodeCursor as i, memoryCache as l, defineSupabase as n, consoleLogger as o, decodeCursor as r, silentLogger as s, BetterSupabase as t, rowKey as u };