better-supabase 0.1.0 → 0.3.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 (149) hide show
  1. package/README.md +34 -31
  2. package/dist/base64-CQeB1eSf.js +42 -0
  3. package/dist/claims-CoTobNC-.js +25 -0
  4. package/dist/client/index.d.ts +1 -1
  5. package/dist/client/index.js +4 -14
  6. package/dist/{compiler-DL18ukPe.d.ts → compiler-2zqGzxVZ.d.ts} +2 -2
  7. package/dist/config/index.d.ts +4 -3
  8. package/dist/config/index.js +5 -3
  9. package/dist/config-Be4GwsqI.js +129 -0
  10. package/dist/{index-BD6wKthp.d.ts → config-C1CYcm6o.d.ts} +75 -54
  11. package/dist/{config-D-aR2ZoN.js → config-DOsGYSiT.js} +14 -136
  12. package/dist/{define-QGkq04oe.js → define-DF341GDj.js} +133 -110
  13. package/dist/{define-CTfWw-SS.d.ts → define-DUbydmJa.d.ts} +32 -14
  14. package/dist/edge/index.d.ts +4 -4
  15. package/dist/edge/index.js +4 -4
  16. package/dist/entitlements-CTEBm7IB.js +20 -0
  17. package/dist/entitlements-DvJXoI02.d.ts +42 -0
  18. package/dist/env/index.js +203 -1
  19. package/dist/events/index.d.ts +1 -1
  20. package/dist/events/index.js +139 -1
  21. package/dist/{events-2OJsVG_d.d.ts → events-Do8jXh4D.d.ts} +3 -3
  22. package/dist/{executor-Bje4suQF.d.ts → executor-B26lgbHD.d.ts} +2 -2
  23. package/dist/{executor-Ceq60hPM.js → executor-C57jx2pn.js} +7 -2
  24. package/dist/{executor-Bazhgsgq.d.ts → executor-DynmIWhq.d.ts} +2 -2
  25. package/dist/guards-BnzDvFrz.js +10 -0
  26. package/dist/hono/index.d.ts +5 -5
  27. package/dist/hono/index.js +4 -4
  28. package/dist/{index-Hf8PP9sP.d.ts → index-BNsABq7P.d.ts} +17 -13
  29. package/dist/{index-JXHBuS53.d.ts → index-CADlNHK-.d.ts} +27 -14
  30. package/dist/{index-Nxo3TJcm.d.ts → index-CTTMS6_1.d.ts} +27 -8
  31. package/dist/index-Cf-D2jk1.d.ts +50 -0
  32. package/dist/{index-Dup-nSe9.d.ts → index-CriFFJ_p.d.ts} +4 -4
  33. package/dist/{index-DQVvHzjW.d.ts → index-GV77NpRP.d.ts} +5 -4
  34. package/dist/{index-Sw43J8HG.d.ts → index-UJ-LUxtI.d.ts} +6 -6
  35. package/dist/{index-cEeweTeM.d.ts → index-kpgopLNv.d.ts} +4 -3
  36. package/dist/{index-DRid0qIv.d.ts → index-qriRM9cZ.d.ts} +7 -2
  37. package/dist/index.d.ts +13 -13
  38. package/dist/index.js +18 -8
  39. package/dist/jobs/index.d.ts +25 -24
  40. package/dist/jobs/index.js +19 -12
  41. package/dist/{json-schema-BbwO2mZg.js → json-schema--aOXAGfU.js} +9 -3
  42. package/dist/lint/index.js +1 -1
  43. package/dist/list/index.d.ts +2 -2
  44. package/dist/list/index.js +128 -93
  45. package/dist/{live-6b9i72Ux.d.ts → live-QE3UPbGm.d.ts} +27 -6
  46. package/dist/logger-glM28NVN.js +21 -0
  47. package/dist/mcp/index.d.ts +59 -15
  48. package/dist/mcp/index.js +229 -67
  49. package/dist/{mfa-CpogP66z.js → mfa-BxoR1RkN.js} +97 -22
  50. package/dist/{mfa-TVia554c.d.ts → mfa-x3V3vGh-.d.ts} +1 -1
  51. package/dist/next/index.d.ts +51 -38
  52. package/dist/next/index.js +31 -25
  53. package/dist/openapi/index.d.ts +1 -1
  54. package/dist/openapi/index.js +58 -35
  55. package/dist/orpc/index.d.ts +6 -6
  56. package/dist/orpc/index.js +3 -3
  57. package/dist/otel/index.d.ts +3 -3
  58. package/dist/otel/index.js +5 -3
  59. package/dist/{template-BeOGnynM.js → permdock-sql-Bta_G0v-.js} +32 -3
  60. package/dist/permdock-sql-DImJULY-.d.ts +17 -0
  61. package/dist/{plugin-HvGqCurB.d.ts → plugin-DVRB8qNE.d.ts} +5 -4
  62. package/dist/plugins/actor/index.d.ts +1 -1
  63. package/dist/plugins/rules/index.d.ts +4 -3
  64. package/dist/plugins/rules/index.js +8 -15
  65. package/dist/plugins/soft-delete/index.d.ts +3 -3
  66. package/dist/plugins/soft-delete/index.js +2 -2
  67. package/dist/plugins/tenant/index.d.ts +10 -4
  68. package/dist/plugins/tenant/index.js +15 -13
  69. package/dist/plugins/timestamps/index.d.ts +1 -1
  70. package/dist/plugins/timestamps/index.js +1 -1
  71. package/dist/plugins/validation/index.d.ts +2 -2
  72. package/dist/pool-B9qsLyFR.d.ts +71 -0
  73. package/dist/postgres/index.d.ts +10 -5
  74. package/dist/postgres/index.js +36 -16
  75. package/dist/{postgrest-Dm-vhY6R.d.ts → postgrest-DmBlevh-.d.ts} +1 -1
  76. package/dist/{problem-DPu6Fh-t.js → problem-CGt1ADix.js} +14 -8
  77. package/dist/{problem-DlgB0lYl.d.ts → problem-Z2RTQjZD.d.ts} +6 -4
  78. package/dist/query/index.d.ts +2 -2
  79. package/dist/query/index.js +1 -1
  80. package/dist/{query-ChvDbsDE.js → query-BdKLTPlj.js} +2 -2
  81. package/dist/react/index.d.ts +7 -7
  82. package/dist/react/index.js +3 -3
  83. package/dist/react/server.d.ts +5 -5
  84. package/dist/react/server.js +1 -1
  85. package/dist/react/session.d.ts +21 -2
  86. package/dist/react/session.js +19 -1
  87. package/dist/{react-CrOvrnIP.js → react-DNBYNazu.js} +28 -23
  88. package/dist/{read-set-DhX2d04c.js → read-set-D0qBcboM.js} +3 -3
  89. package/dist/realtime/index.d.ts +4 -2
  90. package/dist/realtime/index.js +30 -14
  91. package/dist/{resolve-DW8h9NPm.d.ts → resolve-BRciX2jK.d.ts} +25 -9
  92. package/dist/{resource-SkPTRJDD.d.ts → resource-CwVgiI5q.d.ts} +16 -7
  93. package/dist/{resource-CYiu6Ygi.js → resource-D30rp0GN.js} +38 -16
  94. package/dist/{respond-4-dhoV7x.js → respond-C44EZAJo.js} +38 -19
  95. package/dist/{respond-PoTpy5DT.d.ts → respond-DAkPIdTH.d.ts} +23 -17
  96. package/dist/{result-CTkCZ6JA.d.ts → result-c2OYgCV2.d.ts} +13 -8
  97. package/dist/server/index.d.ts +35 -10
  98. package/dist/server/index.js +38 -9
  99. package/dist/{simplify-oHiDKRVQ.js → simplify-LakqK1vH.js} +22 -28
  100. package/dist/{spec-pins-t6sPaHvl.js → spec-pins-C3oc-Tdp.js} +1 -1
  101. package/dist/{spec-pins-CJPVghHy.d.ts → spec-pins-CqorWDnO.d.ts} +1 -1
  102. package/dist/sql/index.d.ts +139 -0
  103. package/dist/sql/index.js +1983 -0
  104. package/dist/{sql-M8l3fnS0.js → sql-BIgk12ew.js} +72 -16
  105. package/dist/ssr/index.d.ts +5 -5
  106. package/dist/ssr/index.js +3 -3
  107. package/dist/{standard-CyhRzctb.d.ts → standard-eudBiRk1.d.ts} +1 -1
  108. package/dist/{stats-3iy8u3_P.js → stats-D78UQ8Yu.js} +8 -7
  109. package/dist/storage/index.d.ts +4 -2
  110. package/dist/storage/index.js +530 -1
  111. package/dist/{tables-rZxRxNPW.js → tables-Dhha3BXM.js} +5 -5
  112. package/dist/temporal-BwshQk0W.js +27 -0
  113. package/dist/temporal-required-CMxeSVPo.js +19 -0
  114. package/dist/testing/index.d.ts +49 -16
  115. package/dist/testing/index.js +233 -66
  116. package/dist/{types-C2fslP1z.d.ts → types-DONv9SHN.d.ts} +68 -12
  117. package/dist/{version-Cqq1m3UY.js → version-BfpG2gks.js} +1 -1
  118. package/dist/view-3Po_PVlr.d.ts +79 -0
  119. package/dist/{view-B6F9QbG-.js → view-wZ-9qIUz.js} +12 -2
  120. package/dist/webhooks/index.d.ts +19 -17
  121. package/dist/webhooks/index.js +1 -1
  122. package/dist/{webhooks-DbMTJJX2.js → webhooks-CAIFQSTM.js} +12 -10
  123. package/package.json +30 -30
  124. package/schemas/config-v1.json +80 -5
  125. package/schemas/doctor-report-v1.json +18 -1
  126. package/schemas/snapshot-v2.json +39 -0
  127. package/skills/better-supabase/SKILL.md +23 -11
  128. package/skills/better-supabase/references/plugins.md +28 -16
  129. package/skills/better-supabase/references/troubleshooting.md +15 -15
  130. package/skills/better-supabase-api/SKILL.md +9 -9
  131. package/skills/better-supabase-api/references/adapters.md +30 -25
  132. package/skills/better-supabase-testing/SKILL.md +15 -11
  133. package/bin/better-supabase.js +0 -3
  134. package/dist/casing-da0uRTqt.js +0 -14
  135. package/dist/cli/bin.d.ts +0 -1
  136. package/dist/cli/bin.js +0 -15
  137. package/dist/cli/index.d.ts +0 -226
  138. package/dist/cli/index.js +0 -3
  139. package/dist/cli-CoByWYja.js +0 -6472
  140. package/dist/entitlements-BBg61DQZ.d.ts +0 -20
  141. package/dist/entitlements-BC_khwRy.js +0 -17
  142. package/dist/env-BEToab0I.js +0 -204
  143. package/dist/events-BuzTgn2D.js +0 -135
  144. package/dist/pool-BFeqzwQS.d.ts +0 -37
  145. package/dist/seed-CzecODcJ.js +0 -101
  146. package/dist/session-C3osL21k.d.ts +0 -21
  147. package/dist/session-CD-3orO_.js +0 -19
  148. package/dist/storage-BWXVUXGk.js +0 -461
  149. package/dist/view-B0ltUmTi.d.ts +0 -43
@@ -1,11 +1,14 @@
1
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";
2
+ import { a as invalidRequest, c as needsDecoding, o as decodeRows, r as IrBuilder, s as encodeValue } from "./simplify-LakqK1vH.js";
3
+ import { n as nowInstant } from "./temporal-BwshQk0W.js";
4
+ import { i as or, n as column, r as not, t as and } from "./types-6cYK9pbJ.js";
4
5
  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 { t as invalidationTargets } from "./tables-Dhha3BXM.js";
7
+ import { t as consoleLogger } from "./logger-glM28NVN.js";
6
8
  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 { a as postgrestExecutor, n as StatsRecorder, r as recordStats } from "./stats-D78UQ8Yu.js";
10
+ 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-D0qBcboM.js";
11
+ import { a as toBase64Url, t as base64ToText } from "./base64-CQeB1eSf.js";
9
12
  import { t as validate } from "./standard-FUi3Z3-7.js";
10
13
  //#region src/core/batch.ts
11
14
  /**
@@ -91,26 +94,6 @@ function memoryCache() {
91
94
  };
92
95
  }
93
96
  //#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
97
  //#region src/core/events.ts
115
98
  /**
116
99
  * Observers only: a handler that throws is reported to the logger and never
@@ -148,27 +131,13 @@ var EventHub = class {
148
131
  };
149
132
  //#endregion
150
133
  //#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
134
  /** Opaque, URL-safe keyset cursor. */
166
135
  function encodeCursor(values) {
167
- return toBase64Url(encoder.encode(JSON.stringify(values)));
136
+ return toBase64Url(JSON.stringify(values));
168
137
  }
169
138
  function decodeCursor(cursor) {
170
139
  try {
171
- const parsed = JSON.parse(decoder.decode(fromBase64Url(cursor)));
140
+ const parsed = JSON.parse(base64ToText(cursor));
172
141
  return Array.isArray(parsed) ? parsed : void 0;
173
142
  } catch {
174
143
  return;
@@ -208,7 +177,7 @@ function optionsOf(args) {
208
177
  return options;
209
178
  }
210
179
  function signalOf(args) {
211
- const signal = args?.signal;
180
+ const signal = args?.["signal"];
212
181
  return signal instanceof AbortSignal ? signal : void 0;
213
182
  }
214
183
  function mutationKind(op) {
@@ -252,10 +221,16 @@ var OperationRunner = class {
252
221
  } : { errorMappers: runtime.errorMappers };
253
222
  const executed = await runtime.executor.execute(current, context);
254
223
  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;
224
+ let result = executed;
225
+ if (executed.ok && selection && needsDecoding(selection)) try {
226
+ result = ok({
227
+ ...executed.data,
228
+ rows: decodeRows(selection, executed.data.rows)
229
+ });
230
+ } catch (cause) {
231
+ if (cause instanceof DbException) return this.fail(op.table, cause.error);
232
+ throw cause;
233
+ }
259
234
  const rows = result.ok ? result.data.rows.length : 0;
260
235
  const truncated = current.kind === "select" && current.limit === void 0 && current.single === void 0 && !current.head && rows >= runtime.maxRows;
261
236
  if (truncated && !runtime.truncatedTables.has(op.table.key)) {
@@ -315,21 +290,21 @@ var OperationRunner = class {
315
290
  /** Creates the runtime repository object for one table. */
316
291
  function createRepository(runner, table) {
317
292
  const { builder } = runner.runtime;
318
- const selection = (args) => builder.selection(table, args?.select, args?.include);
293
+ const selection = (args) => builder.selection(table, args?.["select"], args?.["include"]);
319
294
  const selectOp = (args, patch = {}) => ({
320
295
  kind: "select",
321
296
  table,
322
297
  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,
298
+ where: builder.where(table, args?.["where"]),
299
+ orderBy: builder.orderBy(table, args?.["orderBy"]),
300
+ limit: typeof args?.["limit"] === "number" ? args["limit"] : void 0,
301
+ offset: typeof args?.["offset"] === "number" ? args["offset"] : void 0,
327
302
  count: void 0,
328
303
  head: false,
329
304
  single: void 0,
330
305
  ...patch
331
306
  });
332
- const returning = (args) => args?.returning === false ? void 0 : selection(args);
307
+ const returning = (args) => args?.["returning"] === false ? void 0 : selection(args);
333
308
  const run = (op, args) => runner.run(op, optionsOf(args), signalOf(args));
334
309
  const notFound = () => runner.fail(table, dbError("not_found", `No ${table.key} row matched`));
335
310
  const base = {
@@ -358,7 +333,7 @@ function createRepository(runner, table) {
358
333
  findUnique(args) {
359
334
  return AsyncResult.from(async () => {
360
335
  const op = selectOp(args, {
361
- where: builder.uniqueKey(table, args.where),
336
+ where: builder.uniqueKey(table, args["where"]),
362
337
  limit: 1
363
338
  });
364
339
  const result = await run(op, args);
@@ -379,8 +354,8 @@ function createRepository(runner, table) {
379
354
  },
380
355
  count(args) {
381
356
  return AsyncResult.from(async () => {
382
- const mode = args?.mode;
383
- const op = selectOp({ where: args?.where }, {
357
+ const mode = args?.["mode"];
358
+ const op = selectOp({ where: args?.["where"] }, {
384
359
  selection: {
385
360
  columns: [],
386
361
  includes: []
@@ -395,28 +370,28 @@ function createRepository(runner, table) {
395
370
  aggregate(args) {
396
371
  return AsyncResult.from(async () => {
397
372
  const aggregation = builder.aggregation(table, args);
398
- const orderBy = builder.orderBy(table, args.orderBy);
373
+ const orderBy = builder.orderBy(table, args["orderBy"]);
399
374
  const grouped = new Set(aggregation.columns.map((c) => c.column));
400
375
  const loose = orderBy.find((term) => !grouped.has(term.column));
401
376
  if (loose) return runner.fail(table, dbError("invalid_request", `aggregate on "${table.key}" can only sort by groupBy columns, not "${loose.column}"`));
402
377
  const op = selectOp({
403
- where: args.where,
404
- limit: args.limit,
405
- offset: args.offset
378
+ where: args["where"],
379
+ limit: args["limit"],
380
+ offset: args["offset"]
406
381
  }, {
407
382
  selection: aggregation,
408
383
  orderBy
409
384
  });
410
385
  const result = await run(op, args);
411
386
  if (!result.ok) return result;
412
- if (args.groupBy !== void 0) return ok(result.data.rows);
387
+ if (args["groupBy"] !== void 0) return ok(result.data.rows);
413
388
  return ok(result.data.rows[0] ?? decodeRows(aggregation, [{}])[0] ?? {});
414
389
  });
415
390
  },
416
391
  exists(args) {
417
392
  return AsyncResult.from(async () => {
418
393
  const columns = table.primaryKey.slice(0, 1).map((alias) => builder.selectColumn(table, alias));
419
- const op = selectOp({ where: args?.where }, {
394
+ const op = selectOp({ where: args?.["where"] }, {
420
395
  selection: {
421
396
  columns,
422
397
  includes: []
@@ -435,21 +410,21 @@ function createRepository(runner, table) {
435
410
  const op = insertOp([data], args, void 0);
436
411
  const result = await run(op, args);
437
412
  if (!result.ok) return result;
438
- return ok(args?.returning === false ? null : result.data.rows[0] ?? null);
413
+ return ok(args?.["returning"] === false ? null : result.data.rows[0] ?? null);
439
414
  });
440
415
  },
441
416
  createMany(rows, args) {
442
417
  return AsyncResult.from(async () => {
443
- if (rows.length === 0) return ok(args?.returning === false ? { count: 0 } : []);
418
+ if (rows.length === 0) return ok(args?.["returning"] === false ? { count: 0 } : []);
444
419
  const result = await run(insertOp(rows, args, void 0), args);
445
420
  if (!result.ok) return result;
446
- return ok(args?.returning === false ? { count: result.data.count ?? rows.length } : result.data.rows);
421
+ return ok(args?.["returning"] === false ? { count: result.data.count ?? rows.length } : result.data.rows);
447
422
  });
448
423
  },
449
424
  update(id, patch, args) {
450
425
  return AsyncResult.from(async () => {
451
426
  const key = builder.primaryKey(table, id);
452
- const expect = args?.expect ? builder.where(table, args.expect) : void 0;
427
+ const expect = args?.["expect"] ? builder.where(table, args["expect"]) : void 0;
453
428
  const op = {
454
429
  kind: "update",
455
430
  table,
@@ -481,8 +456,8 @@ function createRepository(runner, table) {
481
456
  const op = {
482
457
  kind: "update",
483
458
  table,
484
- set: builder.row(table, args.data, "update"),
485
- where: builder.where(table, args.where),
459
+ set: builder.row(table, args["data"], "update"),
460
+ where: builder.where(table, args["where"]),
486
461
  returning: void 0
487
462
  };
488
463
  const result = await run(op, args);
@@ -491,19 +466,19 @@ function createRepository(runner, table) {
491
466
  },
492
467
  upsert(data, args) {
493
468
  return AsyncResult.from(async () => {
494
- const op = insertOp([data], args, conflictColumns(args?.onConflict));
469
+ const op = insertOp([data], args, conflictColumns(args?.["onConflict"]));
495
470
  const result = await run(op, args);
496
471
  if (!result.ok) return result;
497
- return ok(args?.returning === false ? null : result.data.rows[0] ?? null);
472
+ return ok(args?.["returning"] === false ? null : result.data.rows[0] ?? null);
498
473
  });
499
474
  },
500
475
  upsertMany(rows, args) {
501
476
  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));
477
+ if (rows.length === 0) return ok(args?.["returning"] === false ? { count: 0 } : []);
478
+ const op = insertOp(rows, args, conflictColumns(args?.["onConflict"]));
504
479
  const result = await run(op, args);
505
480
  if (!result.ok) return result;
506
- return ok(args?.returning === false ? { count: result.data.count ?? rows.length } : result.data.rows);
481
+ return ok(args?.["returning"] === false ? { count: result.data.count ?? rows.length } : result.data.rows);
507
482
  });
508
483
  },
509
484
  delete(id, args) {
@@ -521,7 +496,7 @@ function createRepository(runner, table) {
521
496
  },
522
497
  deleteMany(args) {
523
498
  return AsyncResult.from(async () => {
524
- const where = builder.where(table, args.where);
499
+ const where = builder.where(table, args["where"]);
525
500
  if (!where) return runner.fail(table, dbError("invalid_request", "deleteMany needs a non-empty \"where\""));
526
501
  const result = await run({
527
502
  kind: "delete",
@@ -534,14 +509,15 @@ function createRepository(runner, table) {
534
509
  }
535
510
  };
536
511
  function insertOp(rows, args, conflict) {
512
+ const built = rows.map((row) => builder.row(table, row));
537
513
  return {
538
514
  kind: "insert",
539
515
  table,
540
- rows: rows.map((row) => builder.row(table, row)),
516
+ rows: conflict && built.length > 1 ? byKey(built, conflict) : built,
541
517
  returning: returning(args),
542
518
  onConflict: conflict ? {
543
519
  columns: conflict,
544
- action: args?.ignoreDuplicates === true ? "ignore" : "update"
520
+ action: args?.["ignoreDuplicates"] === true ? "ignore" : "update"
545
521
  } : void 0,
546
522
  defaultToNull: false
547
523
  };
@@ -557,11 +533,11 @@ function createRepository(runner, table) {
557
533
  return invalidRequest(`Invalid onConflict on "${table.key}"`, table.key);
558
534
  }
559
535
  async function offsetPage(args) {
560
- const size = Number(args.size);
536
+ const size = Number(args["size"]);
561
537
  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);
538
+ const number = args["page"] === void 0 ? 1 : Number(args["page"]);
563
539
  if (!Number.isInteger(number) || number < 1) return runner.fail(table, dbError("invalid_request", "\"page\" must be a positive integer"));
564
- const count = args.count;
540
+ const count = args["count"];
565
541
  const op = selectOp(args, {
566
542
  limit: size + 1,
567
543
  offset: (number - 1) * size,
@@ -583,19 +559,19 @@ function createRepository(runner, table) {
583
559
  });
584
560
  }
585
561
  async function cursorPage(args) {
586
- const size = Number(args.size);
562
+ const size = Number(args["size"]);
587
563
  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));
564
+ const orderBy = withTieBreaker(builder.orderBy(table, args["orderBy"]));
589
565
  const { selection: withSort, added } = ensureColumns(selection(args), orderBy);
590
566
  let after;
591
- if (typeof args.after === "string") {
592
- const values = decodeCursor(args.after);
567
+ if (typeof args["after"] === "string") {
568
+ const values = decodeCursor(args["after"]);
593
569
  if (!values || values.length !== orderBy.length) return runner.fail(table, dbError("invalid_request", "Invalid cursor"));
594
- after = keysetCondition(orderBy, values);
570
+ after = keysetCondition(orderBy, values, (name) => Object.values(table.columns).find((meta) => meta.db === name)?.nullable ?? true);
595
571
  }
596
572
  const op = selectOp(args, {
597
573
  selection: withSort,
598
- where: and(builder.where(table, args.where), after),
574
+ where: and(builder.where(table, args["where"]), after),
599
575
  orderBy,
600
576
  limit: size + 1,
601
577
  offset: void 0
@@ -653,24 +629,47 @@ function strip(row, keys) {
653
629
  for (const key of keys) delete copy[key];
654
630
  return copy;
655
631
  }
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;
632
+ /**
633
+ * Rows in conflict-key order, so concurrent upserts lock the same rows in
634
+ * the same order instead of deadlocking.
635
+ */
636
+ function byKey(rows, columns) {
637
+ const compare = (left, right) => {
638
+ if (left === right) return 0;
639
+ if (left === null || left === void 0) return 1;
640
+ if (right === null || right === void 0) return -1;
641
+ if (typeof left === "number" && typeof right === "number") return left - right;
642
+ if (typeof left === "bigint" && typeof right === "bigint") return left < right ? -1 : 1;
643
+ const a = String(left);
644
+ const b = String(right);
645
+ return a < b ? -1 : a > b ? 1 : 0;
646
+ };
647
+ return [...rows].sort((left, right) => {
648
+ for (const column of columns) {
649
+ const order = compare(left[column], right[column]);
650
+ if (order !== 0) return order;
651
+ }
652
+ return 0;
653
+ });
654
+ }
655
+ /**
656
+ * The rows after the cursor `values` in `orderBy` order, as an OR of ANDs:
657
+ * `a > x or (a = x and b > y)`. The SQL compiler sends that shape as the row
658
+ * comparison `(a, b) > (x, y)`. Nulls sort where Postgres puts them: last
659
+ * for `asc`, first for `desc`, unless the term says otherwise.
660
+ */
661
+ function keysetCondition(orderBy, values, nullable) {
662
+ const isNull = (name) => column(name, "is", null);
663
+ const branches = orderBy.flatMap((term, index) => {
664
+ const value = values[index];
665
+ const nullsFirst = (term.nulls ?? (term.direction === "desc" ? "first" : "last")) === "first";
666
+ const after = column(term.column, term.direction === "asc" ? "gt" : "lt", value);
667
+ const step = value === null ? nullsFirst ? not(isNull(term.column)) : void 0 : nullsFirst || !nullable(term.column) ? after : or(after, isNull(term.column));
668
+ if (!step) return [];
669
+ const equal = orderBy.slice(0, index).map((prev, prevIndex) => values[prevIndex] === null ? isNull(prev.column) : column(prev.column, "eq", values[prevIndex]));
670
+ return [and(...equal, step) ?? step];
672
671
  });
673
- return or(...branches);
672
+ return branches.length > 0 ? or(...branches) : column(orderBy[0]?.column ?? "", "in", []);
674
673
  }
675
674
  //#endregion
676
675
  //#region src/core/search.ts
@@ -707,6 +706,10 @@ var BetterSupabase = class BetterSupabase {
707
706
  get claimsSchema() {
708
707
  return this.options.claims;
709
708
  }
709
+ /** The schema set by `userMetadata()`; its output types `session.profile`. */
710
+ get userMetadataSchema() {
711
+ return this.options.userMetadata;
712
+ }
710
713
  /**
711
714
  * Returns a new instance whose servers validate verified JWT claims (custom
712
715
  * access token hook output) with `schema`, and whose sessions are typed by it.
@@ -723,6 +726,24 @@ var BetterSupabase = class BetterSupabase {
723
726
  }, this.events);
724
727
  }
725
728
  /**
729
+ * Returns a new instance whose sessions parse `user_metadata` with
730
+ * `schema` into a typed `session.profile`. Users can change their metadata
731
+ * with `auth.updateUser()`, so a failure only leaves `profile` undefined
732
+ * (with one warning), and no authorization code reads it. It is for
733
+ * display only: roles, memberships, the tenant and entitlements come from
734
+ * the verified claims, never from `profile`.
735
+ *
736
+ * ```ts
737
+ * const sb = defineSupabase(schema).userMetadata(z.object({ display_name: z.string() }));
738
+ * ```
739
+ */
740
+ userMetadata(schema) {
741
+ return new BetterSupabase(this.schema, this.plugins, {
742
+ ...this.options,
743
+ userMetadata: schema
744
+ }, this.events);
745
+ }
746
+ /**
726
747
  * Returns a new instance whose `.orThrow()` throws `mapper(error)` instead
727
748
  * of a `DbException`, and whose results `toBetterResult` maps the same way.
728
749
  * Results themselves keep their `DbError`.
@@ -793,7 +814,9 @@ var BetterSupabase = class BetterSupabase {
793
814
  const invalidate = (target) => {
794
815
  try {
795
816
  const pending = adapter.invalidate(target);
796
- if (pending) pending.catch((cause) => report(cause, target.table));
817
+ if (pending) pending.catch((cause) => {
818
+ report(cause, target.table);
819
+ });
797
820
  } catch (cause) {
798
821
  report(cause, target.table);
799
822
  }
@@ -828,7 +851,7 @@ var BetterSupabase = class BetterSupabase {
828
851
  context,
829
852
  events,
830
853
  errorMappers,
831
- now: this.options.now ?? (() => /* @__PURE__ */ new Date()),
854
+ now: this.options.now ?? nowInstant,
832
855
  maxRows: this.options.maxRows ?? 1e3,
833
856
  truncatedTables: this.#truncatedTables
834
857
  });
@@ -982,15 +1005,15 @@ var BetterSupabase = class BetterSupabase {
982
1005
  const rows = entry.rows ?? [];
983
1006
  const result = await runSpec(this.#db(client, {
984
1007
  name: "read-set",
985
- execute: async (op) => {
1008
+ execute: (op) => {
986
1009
  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`));
1010
+ if (rows.length > 1) return Promise.resolve(err(dbError("multiple_rows", `Expected one ${op.table.key} row`)));
1011
+ if (rows.length === 0 && op.single === "one") return Promise.resolve(err(dbError("not_found", `No ${op.table.key} row matched`)));
989
1012
  }
990
- return ok({
1013
+ return Promise.resolve(ok({
991
1014
  rows,
992
1015
  count: entry.count ?? null
993
- });
1016
+ }));
994
1017
  }
995
1018
  }, context, [], new StatsRecorder(), new EventHub(this.events.logger)), spec, void 0);
996
1019
  if (!result.ok) return result;
@@ -1011,7 +1034,7 @@ var BetterSupabase = class BetterSupabase {
1011
1034
  repository[name] = method;
1012
1035
  }
1013
1036
  }
1014
- repository.extend = (build) => ({
1037
+ repository["extend"] = (build) => ({
1015
1038
  ...repository,
1016
1039
  ...build(repository)
1017
1040
  });
@@ -1065,4 +1088,4 @@ function defineSupabase(schema, options) {
1065
1088
  return new BetterSupabase(schema, [], options);
1066
1089
  }
1067
1090
  //#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 };
1091
+ export { EventHub as a, rowKey as c, encodeCursor as i, rpcCacheTargets as l, defineSupabase as n, cacheTargetOf as o, decodeCursor as r, memoryCache as s, BetterSupabase as t };
@@ -1,10 +1,11 @@
1
- import { A as TableMeta, C as Relations, D as Simplify, E as SchemaMeta, M as UniqueKeyName, N as UniqueWhere, P as Update, S as RelationShape, T as Schema, h as Insert, k as TableKey, n as AnyModels, t as AnyFunctions, w as Row, y as PrimaryKeyValue } from "./types-C2fslP1z.js";
2
- import { g as Operation, l as CountMode, r as Executor } from "./executor-Bazhgsgq.js";
3
- import { p as DbError, s as ThrowMapper, t as AsyncResult, v as ErrorMapper } from "./result-CTkCZ6JA.js";
4
- import { a as EventHub, f as Logger, i as EventHandler, o as EventName, s as MutationNotice, u as RpcNotice } from "./events-2OJsVG_d.js";
5
- import { a as ExtensionOf, g as WithExtension, h as RequestContext, n as AnyPlugin, r as ApplyExtension } from "./plugin-HvGqCurB.js";
6
- import { t as StandardSchemaV1 } from "./standard-CyhRzctb.js";
7
- import { i as PostgrestPlan } from "./postgrest-Dm-vhY6R.js";
1
+ /// <reference lib="esnext.temporal" />
2
+ import { A as SchemaMeta, C as PrimaryKeyValue, D as Relations, E as RelationTypes, I as UniqueKeyName, L as UniqueWhere, N as TableKey, O as Row, P as TableMeta, R as Update, g as Insert, j as Simplify, k as Schema, n as AnyModels, t as AnyFunctions } from "./types-DONv9SHN.js";
3
+ import { g as Operation, l as CountMode, r as Executor } from "./executor-DynmIWhq.js";
4
+ import { p as DbError, s as ThrowMapper, t as AsyncResult, v as ErrorMapper } from "./result-c2OYgCV2.js";
5
+ import { a as EventHub, f as Logger, i as EventHandler, o as EventName, s as MutationNotice, u as RpcNotice } from "./events-Do8jXh4D.js";
6
+ import { a as ExtensionOf, g as WithExtension, h as RequestContext, n as AnyPlugin, r as ApplyExtension } from "./plugin-DVRB8qNE.js";
7
+ import { t as StandardSchemaV1 } from "./standard-eudBiRk1.js";
8
+ import { i as PostgrestPlan } from "./postgrest-DmBlevh-.js";
8
9
  //#region src/ir/args.d.ts
9
10
  interface BaseOps<V> {
10
11
  readonly eq?: V;
@@ -147,7 +148,7 @@ interface AggregateArgs<M extends AnyModels, T extends keyof M> {
147
148
  type SelectPart<M extends AnyModels, T extends keyof M, A> = A extends {
148
149
  readonly select: readonly (infer K)[];
149
150
  } ? Pick<Row<M, T>, Extract<K, keyof Row<M, T>>> : Row<M, T>;
150
- type RelationPayload<M extends AnyModels, R, V> = R extends RelationShape ? R["kind"] extends "many" ? Payload<M, Extract<R["table"], keyof M>, V extends true ? unknown : V>[] : R["nullable"] extends true ? Payload<M, Extract<R["table"], keyof M>, V extends true ? unknown : V> | null : Payload<M, Extract<R["table"], keyof M>, V extends true ? unknown : V> : never;
151
+ type RelationPayload<M extends AnyModels, R, V> = R extends RelationTypes ? R["kind"] extends "many" ? Payload<M, Extract<R["table"], keyof M>, V extends true ? unknown : V>[] : R["nullable"] extends true ? Payload<M, Extract<R["table"], keyof M>, V extends true ? unknown : V> | null : Payload<M, Extract<R["table"], keyof M>, V extends true ? unknown : V> : never;
151
152
  type IncludePart<M extends AnyModels, T extends keyof M, A> = A extends {
152
153
  readonly include: infer I;
153
154
  } ? { -readonly [R in keyof I & keyof Relations<M, T>]: RelationPayload<M, Relations<M, T>[R], I[R]>; } & CountPart<I> & ([keyof I & MeasureKey] extends [never] ? unknown : RelationMeasures<M, T, I>) : unknown;
@@ -551,7 +552,7 @@ declare function explainPostgrest(op: Operation): PostgrestPlan;
551
552
  //#region src/core/define.d.ts
552
553
  interface DefineSupabaseOptions {
553
554
  /** Clock used by plugins (timestamps, soft delete). */
554
- readonly now?: () => Date;
555
+ readonly now?: () => Temporal.Instant;
555
556
  /** Extra error mappers, run before plugin mappers. */
556
557
  readonly errors?: readonly ErrorMapper[];
557
558
  /** Receives errors from event handlers, hooks and cache adapters. Defaults to `console`. */
@@ -562,6 +563,8 @@ interface DefineSupabaseOptions {
562
563
  readonly throwAs?: ThrowMapper;
563
564
  /** Validates verified JWT claims on the server. Prefer `sb.claims()`, which also types them. */
564
565
  readonly claims?: StandardSchemaV1;
566
+ /** Parses `user_metadata` into `session.profile`. Prefer `sb.userMetadata()`, which also types it. */
567
+ readonly userMetadata?: StandardSchemaV1;
565
568
  /**
566
569
  * PostgREST's `db-max-rows`: the most rows one read returns. An unbounded
567
570
  * `findMany` that returns this many sets `truncated` on the `query` event
@@ -586,7 +589,7 @@ interface RpcDefinition {
586
589
  * The isomorphic definition of your data layer: schema plus plugins. Holds no
587
590
  * secrets and no connection; `connect()` binds it to a client per request.
588
591
  */
589
- declare class BetterSupabase<M extends AnyModels = AnyModels, D = unknown, F extends AnyFunctions = AnyFunctions, E = unknown, C = unknown> {
592
+ declare class BetterSupabase<M extends AnyModels = AnyModels, D = unknown, F extends AnyFunctions = AnyFunctions, E = unknown, C = unknown, P = unknown> {
590
593
  #private;
591
594
  readonly schema: Schema<M, D, F>;
592
595
  readonly plugins: readonly AnyPlugin[];
@@ -596,6 +599,8 @@ declare class BetterSupabase<M extends AnyModels = AnyModels, D = unknown, F ext
596
599
  get meta(): SchemaMeta;
597
600
  /** The schema set by `claims()`; its output types `session.claims`. */
598
601
  get claimsSchema(): StandardSchemaV1<unknown, C> | undefined;
602
+ /** The schema set by `userMetadata()`; its output types `session.profile`. */
603
+ get userMetadataSchema(): StandardSchemaV1<unknown, P> | undefined;
599
604
  /**
600
605
  * Returns a new instance whose servers validate verified JWT claims (custom
601
606
  * access token hook output) with `schema`, and whose sessions are typed by it.
@@ -605,7 +610,20 @@ declare class BetterSupabase<M extends AnyModels = AnyModels, D = unknown, F ext
605
610
  * const sb = defineSupabase(schema).claims(z.object({ tenant_id: z.uuid() }));
606
611
  * ```
607
612
  */
608
- claims<S extends StandardSchemaV1>(schema: S): BetterSupabase<M, D, F, E, StandardSchemaV1.InferOutput<S>>;
613
+ claims<S extends StandardSchemaV1>(schema: S): BetterSupabase<M, D, F, E, StandardSchemaV1.InferOutput<S>, P>;
614
+ /**
615
+ * Returns a new instance whose sessions parse `user_metadata` with
616
+ * `schema` into a typed `session.profile`. Users can change their metadata
617
+ * with `auth.updateUser()`, so a failure only leaves `profile` undefined
618
+ * (with one warning), and no authorization code reads it. It is for
619
+ * display only: roles, memberships, the tenant and entitlements come from
620
+ * the verified claims, never from `profile`.
621
+ *
622
+ * ```ts
623
+ * const sb = defineSupabase(schema).userMetadata(z.object({ display_name: z.string() }));
624
+ * ```
625
+ */
626
+ userMetadata<S extends StandardSchemaV1>(schema: S): BetterSupabase<M, D, F, E, C, StandardSchemaV1.InferOutput<S>>;
609
627
  /**
610
628
  * Returns a new instance whose `.orThrow()` throws `mapper(error)` instead
611
629
  * of a `DbException`, and whose results `toBetterResult` maps the same way.
@@ -615,7 +633,7 @@ declare class BetterSupabase<M extends AnyModels = AnyModels, D = unknown, F ext
615
633
  * const sb = defineSupabase(schema).mapError((error) => new AppError(error));
616
634
  * ```
617
635
  */
618
- mapError(mapper: (error: DbError) => unknown): BetterSupabase<M, D, F, E, C>;
636
+ mapError(mapper: (error: DbError) => unknown): BetterSupabase<M, D, F, E, C, P>;
619
637
  /**
620
638
  * Builds serializable `QuerySpec`s with the same arguments and result types
621
639
  * as the repository: `sb.spec.customers.findMany({ select: ['id'] })`.
@@ -624,7 +642,7 @@ declare class BetterSupabase<M extends AnyModels = AnyModels, D = unknown, F ext
624
642
  /** App keys of every table a spec reads; mutations of any of them make it stale. */
625
643
  tablesOf(spec: QuerySpec): string[];
626
644
  /** Returns a new instance with the plugin appended. */
627
- use<P extends AnyPlugin>(plugin: P): BetterSupabase<M, D, F, WithExtension<E, ExtensionOf<P>>, C>;
645
+ use<Q extends AnyPlugin>(plugin: Q): BetterSupabase<M, D, F, WithExtension<E, ExtensionOf<Q>>, C, P>;
628
646
  /**
629
647
  * Declares the tables an RPC writes, so cache adapters and live queries
630
648
  * invalidate them after it succeeds.
@@ -635,7 +653,7 @@ declare class BetterSupabase<M extends AnyModels = AnyModels, D = unknown, F ext
635
653
  */
636
654
  defineRpc(name: Extract<keyof F, string>, options: {
637
655
  readonly invalidates: readonly TableKey<M>[];
638
- }): BetterSupabase<M, D, F, E, C>;
656
+ }): BetterSupabase<M, D, F, E, C, P>;
639
657
  /** Observes queries, mutations, errors, auth and refreshes. */
640
658
  on<K extends EventName>(name: K, handler: EventHandler<K>): () => void;
641
659
  /**
@@ -1,7 +1,7 @@
1
- import { k as TableKey, n as AnyModels, t as AnyFunctions } from "../types-C2fslP1z.js";
2
- import { t as BetterSupabase } from "../define-CTfWw-SS.js";
3
- import { d as ServerContext, f as ServerOptions, l as BetterServer, n as GuardOptions } from "../respond-PoTpy5DT.js";
4
- import { i as ResourceRouteOptions } from "../resource-SkPTRJDD.js";
1
+ import { N as TableKey, n as AnyModels, t as AnyFunctions } from "../types-DONv9SHN.js";
2
+ import { t as BetterSupabase } from "../define-DUbydmJa.js";
3
+ import { d as ServerContext, f as ServerOptions, l as BetterServer, n as GuardOptions } from "../respond-DAkPIdTH.js";
4
+ import { a as ResourceRouteOptions } from "../resource-CwVgiI5q.js";
5
5
  //#region src/edge/index.d.ts
6
6
  export interface CorsOptions {
7
7
  /** Allowed origins, or `*`. Defaults to `*`. */
@@ -1,7 +1,7 @@
1
1
  import { n as dbError } from "../errors-Bl3rkOzo.js";
2
- import { a as problemResponse } from "../problem-DPu6Fh-t.js";
3
- import { i as respond, n as guard, o as createServer, s as extendServer, t as defaultExpose } from "../respond-4-dhoV7x.js";
4
- import { t as defineResource } from "../resource-CYiu6Ygi.js";
2
+ import { a as problemResponse } from "../problem-CGt1ADix.js";
3
+ import { i as respond, n as guard, o as createServer, s as extendServer, t as defaultExpose } from "../respond-C44EZAJo.js";
4
+ import { t as defineResource } from "../resource-D30rp0GN.js";
5
5
  //#region src/edge/index.ts
6
6
  const SUPABASE_CORS_HEADERS = [
7
7
  "authorization",
@@ -54,7 +54,7 @@ function createEdge(sb, options = {}) {
54
54
  });
55
55
  const ctx = await server.context(request, { refresh: handlerOptions.refresh ?? false });
56
56
  const instance = new URL(request.url).pathname;
57
- const denied = guard(ctx.auth, handlerOptions.allow, handlerOptions.aal);
57
+ const denied = guard(ctx.auth, handlerOptions.allow, handlerOptions.aal, handlerOptions.scopes);
58
58
  let response;
59
59
  try {
60
60
  response = denied ? problemResponse(denied, {
@@ -0,0 +1,20 @@
1
+ //#region src/auth/entitlements.ts
2
+ /**
3
+ * Whether the session's plan-features claim grants `key` in `tenantId`.
4
+ * `claim` is `config.claims.features` (default `features`), the claim
5
+ * `better_supabase.feature_claims()` fills. The claim is as fresh as the
6
+ * token: enforce it in RLS with `better_supabase.has_entitlement()`, and
7
+ * refresh the session after checkout.
8
+ */
9
+ function hasEntitlement(session, tenantId, key, claim = "features") {
10
+ if (session.kind !== "user") return false;
11
+ const claims = session.claims;
12
+ if (!isRecord(claims)) return false;
13
+ const features = claims[claim];
14
+ if (!isRecord(features) || !Object.hasOwn(features, tenantId)) return false;
15
+ const keys = features[tenantId];
16
+ return Array.isArray(keys) && keys.includes(key);
17
+ }
18
+ const isRecord = (value) => typeof value === "object" && value !== null;
19
+ //#endregion
20
+ export { hasEntitlement as t };