better-supabase 0.2.0 → 0.4.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 (190) hide show
  1. package/README.md +37 -33
  2. package/dist/base64-D2gsioCY.js +42 -0
  3. package/dist/claims-BAf6c7is.js +48 -0
  4. package/dist/cli/_format-Cax5EsM9.js +101 -0
  5. package/dist/cli/bin.js +39 -6
  6. package/dist/cli/command-CK14fP91.js +502 -0
  7. package/dist/cli/compare-BmIWVEui.js +9 -0
  8. package/dist/cli/config-D9R0o1LH.js +1895 -0
  9. package/dist/cli/config-SSKSTUl2.js +2 -0
  10. package/dist/cli/db-DOBPt660.js +52 -0
  11. package/dist/cli/dist-B1MvLv-M.js +129 -0
  12. package/dist/cli/dist-CFgGknmZ.js +1112 -0
  13. package/dist/cli/dist-Du7VnEUG.js +621 -0
  14. package/dist/cli/doctor-BOym3mbL.js +2382 -0
  15. package/dist/cli/env-BZ0uy8XK.js +108 -0
  16. package/dist/cli/env-jIFUADLY.js +511 -0
  17. package/dist/cli/errors-4v4DTtda.js +56 -0
  18. package/dist/cli/fastest-levenshtein-CFN-lNDv.js +120 -0
  19. package/dist/cli/gen-Ccn_p90L.js +2008 -0
  20. package/dist/cli/gen-D5cLCULx.js +2 -0
  21. package/dist/cli/index.d.ts +295 -28
  22. package/dist/cli/index.js +184 -3
  23. package/dist/cli/init-ClnxRYBF.js +550 -0
  24. package/dist/cli/introspect-DxqfkVIv.js +81 -0
  25. package/dist/cli/io-k41_z-jT.js +24 -0
  26. package/dist/cli/json5-ChRycquj.js +787 -0
  27. package/dist/cli/jsonc-BUniUpOk.js +2 -0
  28. package/dist/cli/jsonc-D5Fu67jw.js +45 -0
  29. package/dist/cli/keys-ByKHnvP9.js +83 -0
  30. package/dist/cli/ohash-D7bG_EOb.js +277 -0
  31. package/dist/cli/openapi-8WHjxiTJ.js +66 -0
  32. package/dist/cli/project-B_GeXyHh.js +70 -0
  33. package/dist/cli/prompts-BNcNfAve.js +1142 -0
  34. package/dist/cli/prompts-BTYQGunc.js +2 -0
  35. package/dist/cli/read-sets-CMa29t9a.js +971 -0
  36. package/dist/cli/rolldown-runtime-BPOCksWG.js +24 -0
  37. package/dist/cli/run-CP20LKk_.js +347 -0
  38. package/dist/cli/seed-5J6eu_Xg.js +98 -0
  39. package/dist/cli/skills-LJJIhLFR.js +137 -0
  40. package/dist/cli/sql-D4VbqmXi.js +199 -0
  41. package/dist/cli/style-C0NFdJRL.js +29 -0
  42. package/dist/cli/toml-DB-MNnFs.js +424 -0
  43. package/dist/{version-Cqq1m3UY.js → cli/version-5mW_8sY1.js} +1 -1
  44. package/dist/cli/yaml-CYCbJ6et.js +2252 -0
  45. package/dist/client/index.d.ts +2 -2
  46. package/dist/client/index.js +15 -25
  47. package/dist/{compiler-CZ9nIuqy.d.ts → compiler-DB3aSFOA.d.ts} +2 -2
  48. package/dist/config/index.d.ts +4 -3
  49. package/dist/config/index.js +5 -3
  50. package/dist/config-BpxDfnbg.js +129 -0
  51. package/dist/{index-DOsjFvMf.d.ts → config-C1CYcm6o.d.ts} +49 -49
  52. package/dist/{config-ClDXmVtY.js → config-CbvFmGD8.js} +14 -137
  53. package/dist/{define-BXMYD9AY.js → define-Bh0AqTik.js} +221 -120
  54. package/dist/{define-e3zI4_4g.d.ts → define-CRS2MRV3.d.ts} +36 -31
  55. package/dist/edge/index.d.ts +9 -13
  56. package/dist/edge/index.js +51 -25
  57. package/dist/{entitlements-Dng-ctuT.d.ts → entitlements-Dm-FHpNH.d.ts} +2 -2
  58. package/dist/env/index.js +203 -1
  59. package/dist/{errors-Bl3rkOzo.js → errors-L0xK41y_.js} +1 -1
  60. package/dist/events/index.d.ts +1 -1
  61. package/dist/events/index.js +139 -1
  62. package/dist/{events-DOt3nSO-.d.ts → events-B21UOhvP.d.ts} +4 -4
  63. package/dist/{executor-CGdNQ27g.d.ts → executor-B5IoUxLE.d.ts} +2 -2
  64. package/dist/{executor-DeppvirK.js → executor-CVt6dD0B.js} +9 -4
  65. package/dist/{executor-BYXQ9AGn.d.ts → executor-CZ7ImRiD.d.ts} +2 -2
  66. package/dist/hono/index.d.ts +17 -24
  67. package/dist/hono/index.js +11 -11
  68. package/dist/{index-DuP14VHo.d.ts → index-528Ugmyp.d.ts} +30 -10
  69. package/dist/{index-Bw1bwtC0.d.ts → index-BAy4tsAP.d.ts} +28 -15
  70. package/dist/{index-CBDufH1M.d.ts → index-BBdvo-5d.d.ts} +24 -26
  71. package/dist/{index-DbxFr7XG.d.ts → index-BhHEC04r.d.ts} +9 -4
  72. package/dist/index-Cf-D2jk1.d.ts +50 -0
  73. package/dist/{index-Ciu5Xlml.d.ts → index-D5IfUIDh.d.ts} +6 -5
  74. package/dist/{index-K-3WM2Hp.d.ts → index-DFmOpBGC.d.ts} +13 -13
  75. package/dist/{index-D4v7sncu.d.ts → index-RlLXd8dL.d.ts} +13 -13
  76. package/dist/index.d.ts +17 -17
  77. package/dist/index.js +23 -14
  78. package/dist/{invalidate-DbxuL2jL.js → invalidate-CgxmRamI.js} +7 -1
  79. package/dist/jobs/index.d.ts +33 -26
  80. package/dist/jobs/index.js +44 -22
  81. package/dist/{json-schema-BpYqkCKf.js → json-schema-DP6V7cbr.js} +8 -3
  82. package/dist/lint/index.js +1 -1
  83. package/dist/list/index.d.ts +2 -2
  84. package/dist/list/index.js +131 -96
  85. package/dist/{live-DJyZMD6w.d.ts → live-BHakSWWo.d.ts} +26 -16
  86. package/dist/{live-D8rV1Ldw.js → live-CdJBOc4f.js} +13 -9
  87. package/dist/lookup-BaP5bI27.js +18 -0
  88. package/dist/mcp/index.d.ts +34 -27
  89. package/dist/mcp/index.js +59 -25
  90. package/dist/{mfa-TVia554c.d.ts → mfa-C7MNQ1Ob.d.ts} +1 -1
  91. package/dist/{mfa-CpLfJSde.js → mfa-byfjruR9.js} +190 -46
  92. package/dist/next/image/index.js +6 -3
  93. package/dist/next/index.d.ts +64 -48
  94. package/dist/next/index.js +63 -50
  95. package/dist/openapi/index.d.ts +1 -1
  96. package/dist/openapi/index.js +61 -38
  97. package/dist/orpc/index.d.ts +18 -21
  98. package/dist/orpc/index.js +12 -12
  99. package/dist/otel/index.d.ts +5 -5
  100. package/dist/otel/index.js +84 -63
  101. package/dist/{permdock-sql-CghlHPuH.js → permdock-sql-C4KFzLc9.js} +12 -7
  102. package/dist/permdock-sql-DImJULY-.d.ts +17 -0
  103. package/dist/{plugin-C4TR9J0Z.d.ts → plugin-bNhvJwRh.d.ts} +5 -4
  104. package/dist/plugins/actor/index.d.ts +1 -1
  105. package/dist/plugins/actor/index.js +1 -1
  106. package/dist/plugins/rules/index.d.ts +2 -2
  107. package/dist/plugins/rules/index.js +46 -16
  108. package/dist/plugins/soft-delete/index.d.ts +3 -3
  109. package/dist/plugins/soft-delete/index.js +4 -4
  110. package/dist/plugins/tenant/index.d.ts +3 -3
  111. package/dist/plugins/tenant/index.js +10 -4
  112. package/dist/plugins/timestamps/index.d.ts +1 -1
  113. package/dist/plugins/timestamps/index.js +2 -2
  114. package/dist/plugins/validation/index.d.ts +2 -2
  115. package/dist/plugins/validation/index.js +3 -3
  116. package/dist/pool-DFwEQwmN.d.ts +78 -0
  117. package/dist/postgres/index.d.ts +10 -5
  118. package/dist/postgres/index.js +37 -16
  119. package/dist/{postgrest-cAEQ_IQN.d.ts → postgrest-Bc-0Xw62.d.ts} +1 -1
  120. package/dist/{problem-BTkTtRV9.js → problem-BihjPR0t.js} +11 -5
  121. package/dist/{problem-DlgB0lYl.d.ts → problem-U8UEKccv.d.ts} +6 -4
  122. package/dist/query/index.d.ts +2 -2
  123. package/dist/query/index.js +2 -2
  124. package/dist/{query-ChvDbsDE.js → query-BMMSWbI7.js} +19 -19
  125. package/dist/react/index.d.ts +7 -7
  126. package/dist/react/index.js +2 -2
  127. package/dist/react/server.d.ts +6 -6
  128. package/dist/react/server.js +3 -3
  129. package/dist/react/session.d.ts +21 -2
  130. package/dist/react/session.js +19 -1
  131. package/dist/{react-C83BdBGt.js → react-CQHMaESR.js} +59 -55
  132. package/dist/{read-set-4lI3W_gZ.js → read-set-FWK8qQ58.js} +51 -16
  133. package/dist/realtime/index.d.ts +4 -3
  134. package/dist/realtime/index.js +19 -17
  135. package/dist/{index-CG69s15v.d.ts → refresh-BwYkyNOQ.d.ts} +7 -3
  136. package/dist/{resolve-CjHILk2w.d.ts → resolve-BAqQcwMW.d.ts} +17 -11
  137. package/dist/{resource-CgZa3YG_.d.ts → resource-BJcaIfxi.d.ts} +17 -8
  138. package/dist/{resource-BHEWmQJK.js → resource-My_y-Syh.js} +43 -21
  139. package/dist/{respond-Dne1ARSa.d.ts → respond-DgIbdrt8.d.ts} +39 -14
  140. package/dist/{result-DWXatkd6.js → result-Bj6yt33D.js} +2 -2
  141. package/dist/{result-CTkCZ6JA.d.ts → result-CqEsbgYs.d.ts} +16 -11
  142. package/dist/server/index.d.ts +38 -13
  143. package/dist/server/index.js +43 -14
  144. package/dist/{respond-DMcbQwfN.js → server-BA75B0mT.js} +151 -96
  145. package/dist/{shared-_6-uPL7E.js → shared-DnoCdMDr.js} +4 -3
  146. package/dist/{simplify-bwJpLnaS.js → simplify-Cqvfr9dj.js} +76 -39
  147. package/dist/sql/index.d.ts +139 -0
  148. package/dist/sql/index.js +1983 -0
  149. package/dist/{sql-DTjA1LPj.js → sql-B34HU2DI.js} +74 -17
  150. package/dist/ssr/index.d.ts +4 -4
  151. package/dist/ssr/index.js +2 -2
  152. package/dist/{standard-FUi3Z3-7.js → standard-BRyBGTtp.js} +2 -2
  153. package/dist/{standard-CyhRzctb.d.ts → standard-BsaCYQJc.d.ts} +1 -1
  154. package/dist/{stats-C5W-rBfR.js → stats-C_YxA2r1.js} +40 -14
  155. package/dist/storage/index.d.ts +4 -3
  156. package/dist/storage/index.js +548 -1
  157. package/dist/{tables-rZxRxNPW.js → tables-Cb-3_VWc.js} +19 -5
  158. package/dist/temporal-BwshQk0W.js +27 -0
  159. package/dist/temporal-required-CBNHT6ni.js +19 -0
  160. package/dist/testing/index.d.ts +25 -24
  161. package/dist/testing/index.js +165 -66
  162. package/dist/{types-C83v5SQz.d.ts → types-DONv9SHN.d.ts} +14 -11
  163. package/dist/version-Bmf7_FzM.js +4 -0
  164. package/dist/{view-D1s0wa0T.d.ts → view-B64o81wp.d.ts} +36 -6
  165. package/dist/{view-aOYizTeE.js → view-BrJI2LKp.js} +11 -2
  166. package/dist/webhooks/index.d.ts +19 -17
  167. package/dist/webhooks/index.js +1 -1
  168. package/dist/{webhooks-DMipjhSP.js → webhooks-Bh_Jf_-l.js} +45 -24
  169. package/package.json +50 -22
  170. package/schemas/config-v1.json +31 -1
  171. package/schemas/doctor-report-v1.json +17 -1
  172. package/schemas/snapshot-v2.json +35 -0
  173. package/skills/better-supabase/SKILL.md +77 -18
  174. package/skills/better-supabase/references/plugins.md +21 -22
  175. package/skills/better-supabase/references/troubleshooting.md +38 -18
  176. package/skills/better-supabase-api/SKILL.md +66 -18
  177. package/skills/better-supabase-api/references/adapters.md +64 -41
  178. package/skills/better-supabase-auth/SKILL.md +146 -0
  179. package/skills/better-supabase-auth/references/permdock.md +55 -0
  180. package/skills/better-supabase-testing/SKILL.md +68 -11
  181. package/dist/casing-da0uRTqt.js +0 -14
  182. package/dist/claims-CoTobNC-.js +0 -25
  183. package/dist/cli-DrSgDOQC.js +0 -6646
  184. package/dist/env-BEToab0I.js +0 -204
  185. package/dist/events-BuzTgn2D.js +0 -135
  186. package/dist/pool-R2m_DkXi.d.ts +0 -37
  187. package/dist/seed-CzecODcJ.js +0 -101
  188. package/dist/session-C3LxnQ0e.d.ts +0 -22
  189. package/dist/session-CqEwzVup.js +0 -20
  190. package/dist/storage-DuGn38fr.js +0 -527
@@ -1,13 +1,16 @@
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-bwJpLnaS.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";
1
+ import { n as dbError, t as DbException } from "./errors-L0xK41y_.js";
2
+ import { a as invalidRequest, c as needsDecoding, o as decodeRows, r as builderFor, s as encodeValue } from "./simplify-Cqvfr9dj.js";
3
+ import { t as lookupOf } from "./lookup-BaP5bI27.js";
4
+ import { n as nowInstant } from "./temporal-BwshQk0W.js";
5
+ import { i as or, n as column, r as not, t as and } from "./types-6cYK9pbJ.js";
6
+ import { i as ok, n as err, o as toDbError, s as withErrorMapper, t as AsyncResult } from "./result-Bj6yt33D.js";
7
+ import { t as invalidationTargets } from "./tables-Cb-3_VWc.js";
6
8
  import { t as consoleLogger } from "./logger-glM28NVN.js";
7
9
  import { r as orderPlugins } from "./plugin-BqR6wKMB.js";
8
- import { a as postgrestExecutor, n as StatsRecorder, r as recordStats } from "./stats-C5W-rBfR.js";
9
- 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-4lI3W_gZ.js";
10
- import { t as validate } from "./standard-FUi3Z3-7.js";
10
+ import { a as postgrestExecutor, n as StatsRecorder, r as recordStats } from "./stats-C_YxA2r1.js";
11
+ 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-FWK8qQ58.js";
12
+ import { a as toBase64Url, t as base64ToText } from "./base64-D2gsioCY.js";
13
+ import { t as validate } from "./standard-BRyBGTtp.js";
11
14
  //#region src/core/batch.ts
12
15
  /**
13
16
  * Holds operations until each of `readers` has either submitted one or
@@ -93,12 +96,18 @@ function memoryCache() {
93
96
  }
94
97
  //#endregion
95
98
  //#region src/core/events.ts
99
+ /** More handlers than this for one event usually means a missing unsubscribe. */
100
+ const HANDLER_WARNING = 50;
101
+ function isDevelopment() {
102
+ return typeof process !== "undefined" && process.env["NODE_ENV"] !== "production";
103
+ }
96
104
  /**
97
105
  * Observers only: a handler that throws is reported to the logger and never
98
106
  * changes a result.
99
107
  */
100
108
  var EventHub = class {
101
109
  #handlers = /* @__PURE__ */ new Map();
110
+ #warned = /* @__PURE__ */ new Set();
102
111
  logger;
103
112
  constructor(logger = consoleLogger) {
104
113
  this.logger = logger;
@@ -110,6 +119,10 @@ var EventHub = class {
110
119
  this.#handlers.set(name, set);
111
120
  }
112
121
  set.add(handler);
122
+ if (set.size > HANDLER_WARNING && !this.#warned.has(name) && isDevelopment()) {
123
+ this.#warned.add(name);
124
+ this.logger.warn(`${set.size} "${name}" handlers are registered; call the function on() returns to remove one you no longer need`);
125
+ }
113
126
  return () => {
114
127
  set.delete(handler);
115
128
  };
@@ -129,27 +142,13 @@ var EventHub = class {
129
142
  };
130
143
  //#endregion
131
144
  //#region src/core/cursor.ts
132
- const encoder = new TextEncoder();
133
- const decoder = new TextDecoder();
134
- function toBase64Url(bytes) {
135
- let binary = "";
136
- for (const byte of bytes) binary += String.fromCharCode(byte);
137
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
138
- }
139
- function fromBase64Url(text) {
140
- const base64 = text.replace(/-/g, "+").replace(/_/g, "/");
141
- const binary = atob(base64 + "=".repeat((4 - base64.length % 4) % 4));
142
- const bytes = new Uint8Array(binary.length);
143
- for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
144
- return bytes;
145
- }
146
145
  /** Opaque, URL-safe keyset cursor. */
147
146
  function encodeCursor(values) {
148
- return toBase64Url(encoder.encode(JSON.stringify(values)));
147
+ return toBase64Url(JSON.stringify(values));
149
148
  }
150
149
  function decodeCursor(cursor) {
151
150
  try {
152
- const parsed = JSON.parse(decoder.decode(fromBase64Url(cursor)));
151
+ const parsed = JSON.parse(base64ToText(cursor));
153
152
  return Array.isArray(parsed) ? parsed : void 0;
154
153
  } catch {
155
154
  return;
@@ -182,11 +181,28 @@ const KNOWN_KEYS = /* @__PURE__ */ new Set([
182
181
  "_min",
183
182
  "_max"
184
183
  ]);
184
+ const NO_OPTIONS = Object.freeze({});
185
185
  function optionsOf(args) {
186
- if (!args) return {};
187
- const options = {};
188
- for (const [key, value] of Object.entries(args)) if (!KNOWN_KEYS.has(key)) options[key] = value;
189
- return options;
186
+ if (!args) return NO_OPTIONS;
187
+ let options;
188
+ for (const key in args) if (!KNOWN_KEYS.has(key) && Object.hasOwn(args, key)) (options ??= {})[key] = args[key];
189
+ return options ?? NO_OPTIONS;
190
+ }
191
+ const hooksByPlugins = /* @__PURE__ */ new WeakMap();
192
+ function hooksOf(plugins) {
193
+ let hooks = hooksByPlugins.get(plugins);
194
+ if (!hooks) {
195
+ hooks = {
196
+ transformQuery: plugins.filter((plugin) => plugin.transformQuery),
197
+ beforeMutation: plugins.filter((plugin) => plugin.beforeMutation),
198
+ afterMutation: plugins.filter((plugin) => plugin.afterMutation)
199
+ };
200
+ hooksByPlugins.set(plugins, hooks);
201
+ }
202
+ return hooks;
203
+ }
204
+ function isThenable(value) {
205
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
190
206
  }
191
207
  function signalOf(args) {
192
208
  const signal = args?.["signal"];
@@ -199,8 +215,10 @@ function mutationKind(op) {
199
215
  /** Runs operations through plugins, the executor and the event hub. */
200
216
  var OperationRunner = class {
201
217
  runtime;
218
+ #hooks;
202
219
  constructor(runtime) {
203
220
  this.runtime = runtime;
221
+ this.#hooks = hooksOf(runtime.plugins);
204
222
  }
205
223
  hookArgs(table, options) {
206
224
  return {
@@ -213,30 +231,43 @@ var OperationRunner = class {
213
231
  }
214
232
  async run(op, options, signal) {
215
233
  const { runtime } = this;
216
- const hook = this.hookArgs(op.table, options);
234
+ const hooks = this.#hooks;
217
235
  let current = op;
218
- try {
219
- for (const plugin of runtime.plugins) if (plugin.transformQuery) current = plugin.transformQuery(current, hook);
220
- if (current.kind !== "select") {
221
- let mutation = current;
222
- for (const plugin of runtime.plugins) if (plugin.beforeMutation) mutation = await plugin.beforeMutation(mutation, hook);
223
- current = mutation;
236
+ if (hooks.transformQuery.length > 0 || hooks.beforeMutation.length > 0) {
237
+ const hook = this.hookArgs(op.table, options);
238
+ try {
239
+ for (const plugin of hooks.transformQuery) current = plugin.transformQuery(current, hook);
240
+ if (current.kind !== "select") {
241
+ let mutation = current;
242
+ for (const plugin of hooks.beforeMutation) {
243
+ const next = plugin.beforeMutation(mutation, hook);
244
+ mutation = isThenable(next) ? await next : next;
245
+ }
246
+ current = mutation;
247
+ }
248
+ } catch (cause) {
249
+ if (cause instanceof DbException) return this.fail(op.table, cause.error);
250
+ throw cause;
224
251
  }
225
- } catch (cause) {
226
- if (cause instanceof DbException) return this.fail(op.table, cause.error);
227
- throw cause;
228
252
  }
229
- const started = performance.now();
253
+ const timed = runtime.events.has("query");
254
+ const started = timed ? performance.now() : 0;
230
255
  const context = signal ? {
231
256
  signal,
232
257
  errorMappers: runtime.errorMappers
233
258
  } : { errorMappers: runtime.errorMappers };
234
259
  const executed = await runtime.executor.execute(current, context);
235
260
  const selection = current.kind === "select" ? current.selection : current.returning;
236
- const result = executed.ok && selection && needsDecoding(selection) ? ok({
237
- ...executed.data,
238
- rows: decodeRows(selection, executed.data.rows)
239
- }) : executed;
261
+ let result = executed;
262
+ if (executed.ok && selection && needsDecoding(selection)) try {
263
+ result = ok({
264
+ ...executed.data,
265
+ rows: decodeRows(selection, executed.data.rows, true)
266
+ });
267
+ } catch (cause) {
268
+ if (cause instanceof DbException) return this.fail(op.table, cause.error);
269
+ throw cause;
270
+ }
240
271
  const rows = result.ok ? result.data.rows.length : 0;
241
272
  const truncated = current.kind === "select" && current.limit === void 0 && current.single === void 0 && !current.head && rows >= runtime.maxRows;
242
273
  if (truncated && !runtime.truncatedTables.has(op.table.key)) {
@@ -246,7 +277,7 @@ var OperationRunner = class {
246
277
  maxRows: runtime.maxRows
247
278
  });
248
279
  }
249
- if (runtime.events.has("query")) runtime.events.emit("query", {
280
+ if (timed) runtime.events.emit("query", {
250
281
  table: op.table.key,
251
282
  operation: current.kind,
252
283
  ok: result.ok,
@@ -260,21 +291,22 @@ var OperationRunner = class {
260
291
  }
261
292
  async afterMutation(op, data) {
262
293
  const { runtime } = this;
294
+ const plugins = this.#hooks.afterMutation;
295
+ const listening = runtime.events.has("mutation");
296
+ if (plugins.length === 0 && !listening) return;
263
297
  const event = {
264
298
  table: op.table,
265
299
  kind: mutationKind(op),
266
300
  rows: data.rows,
267
301
  context: runtime.context
268
302
  };
269
- for (const plugin of runtime.plugins) {
270
- if (!plugin.afterMutation) continue;
271
- try {
272
- await plugin.afterMutation(event);
273
- } catch (cause) {
274
- runtime.events.logger.error(`plugin "${plugin.name}" afterMutation threw`, { cause });
275
- }
303
+ for (const plugin of plugins) try {
304
+ const pending = plugin.afterMutation(event);
305
+ if (isThenable(pending)) await pending;
306
+ } catch (cause) {
307
+ runtime.events.logger.error(`plugin "${plugin.name}" afterMutation threw`, { cause });
276
308
  }
277
- runtime.events.emit("mutation", {
309
+ if (listening) runtime.events.emit("mutation", {
278
310
  table: op.table.key,
279
311
  kind: event.kind,
280
312
  rows: event.rows,
@@ -286,7 +318,7 @@ var OperationRunner = class {
286
318
  ...error,
287
319
  table: table.key
288
320
  };
289
- this.runtime.events.emit("error", {
321
+ if (this.runtime.events.has("error")) this.runtime.events.emit("error", {
290
322
  table: table.key,
291
323
  error: withTable
292
324
  });
@@ -312,19 +344,22 @@ function createRepository(runner, table) {
312
344
  });
313
345
  const returning = (args) => args?.["returning"] === false ? void 0 : selection(args);
314
346
  const run = (op, args) => runner.run(op, optionsOf(args), signalOf(args));
347
+ let primaryOrder;
348
+ /** `primaryKey` holds app names; order terms take database names. */
349
+ const defaultOrder = () => primaryOrder ??= table.primaryKey.map((name) => ({
350
+ column: builder.column(table, name),
351
+ direction: "asc"
352
+ }));
315
353
  const notFound = () => runner.fail(table, dbError("not_found", `No ${table.key} row matched`));
316
354
  const base = {
317
- $table: table.key,
355
+ $tableName: table.key,
318
356
  $meta: table,
319
357
  findMany(args) {
320
358
  return AsyncResult.from(async () => {
321
359
  const op = selectOp(args);
322
360
  const ordered = op.orderBy.length > 0 || table.primaryKey.length === 0 ? op : {
323
361
  ...op,
324
- orderBy: table.primaryKey.map((column) => ({
325
- column,
326
- direction: "asc"
327
- }))
362
+ orderBy: defaultOrder()
328
363
  };
329
364
  const result = await run(ordered, args);
330
365
  return result.ok ? ok(result.data.rows) : result;
@@ -515,10 +550,11 @@ function createRepository(runner, table) {
515
550
  }
516
551
  };
517
552
  function insertOp(rows, args, conflict) {
553
+ const built = rows.map((row) => builder.row(table, row));
518
554
  return {
519
555
  kind: "insert",
520
556
  table,
521
- rows: rows.map((row) => builder.row(table, row)),
557
+ rows: conflict && built.length > 1 ? byKey(built, conflict) : built,
522
558
  returning: returning(args),
523
559
  onConflict: conflict ? {
524
560
  columns: conflict,
@@ -572,7 +608,8 @@ function createRepository(runner, table) {
572
608
  if (typeof args["after"] === "string") {
573
609
  const values = decodeCursor(args["after"]);
574
610
  if (!values || values.length !== orderBy.length) return runner.fail(table, dbError("invalid_request", "Invalid cursor"));
575
- after = keysetCondition(orderBy, values);
611
+ const { byDb } = lookupOf(table);
612
+ after = keysetCondition(orderBy, values, (name) => byDb.get(name)?.[1].nullable ?? true);
576
613
  }
577
614
  const op = selectOp(args, {
578
615
  selection: withSort,
@@ -586,7 +623,8 @@ function createRepository(runner, table) {
586
623
  const rows = result.data.rows.slice(0, size);
587
624
  const hasMore = result.data.rows.length > size;
588
625
  const last = rows.at(-1);
589
- const nextCursor = hasMore && last ? encodeCursor(orderBy.map((term) => encodeValue(last[aliasOf(withSort, term.column)]))) : null;
626
+ const aliases = new Map(withSort.columns.map((entry) => [entry.column, entry.alias]));
627
+ const nextCursor = hasMore && last ? encodeCursor(orderBy.map((term) => encodeValue(last[aliases.get(term.column) ?? term.column]))) : null;
590
628
  return ok({
591
629
  items: added.length > 0 ? rows.map((row) => strip(row, added)) : rows,
592
630
  nextCursor,
@@ -606,14 +644,17 @@ function createRepository(runner, table) {
606
644
  }
607
645
  function ensureColumns(base, orderBy) {
608
646
  const columns = [...base.columns];
647
+ const selected = new Set(columns.map((entry) => entry.column));
648
+ const { byDb } = lookupOf(table);
609
649
  const added = [];
610
650
  for (const term of orderBy) {
611
- if (columns.some((entry) => entry.column === term.column)) continue;
612
- const alias = Object.entries(table.columns).find(([, meta]) => meta.db === term.column)?.[0] ?? term.column;
651
+ if (selected.has(term.column)) continue;
652
+ const alias = byDb.get(term.column)?.[0] ?? term.column;
613
653
  columns.push({
614
654
  alias,
615
655
  column: term.column
616
656
  });
657
+ selected.add(term.column);
617
658
  added.push(alias);
618
659
  }
619
660
  return {
@@ -626,32 +667,60 @@ function createRepository(runner, table) {
626
667
  }
627
668
  return base;
628
669
  }
629
- function aliasOf(selection, column) {
630
- return selection.columns.find((entry) => entry.column === column)?.alias ?? column;
631
- }
632
670
  function strip(row, keys) {
633
- const copy = { ...row };
634
- for (const key of keys) delete copy[key];
671
+ const copy = {};
672
+ for (const key in row) if (!keys.includes(key) && Object.hasOwn(row, key)) copy[key] = row[key];
635
673
  return copy;
636
674
  }
637
- /** `(a, b) > (x, y)` for mixed directions, as an OR of ANDs. */
638
- function keysetCondition(orderBy, values) {
639
- const branches = orderBy.map((term, index) => {
640
- const equal = orderBy.slice(0, index).map((prev, prevIndex) => ({
641
- kind: "column",
642
- column: prev.column,
643
- op: "eq",
644
- value: values[prevIndex]
645
- }));
646
- const step = {
647
- kind: "column",
648
- column: term.column,
649
- op: term.direction === "asc" ? "gt" : "lt",
650
- value: values[index]
651
- };
652
- return and(...equal, step) ?? step;
675
+ /**
676
+ * Rows in conflict-key order, so concurrent upserts lock the same rows in
677
+ * the same order instead of deadlocking.
678
+ */
679
+ function byKey(rows, columns) {
680
+ const compare = (left, right) => {
681
+ if (left === right) return 0;
682
+ if (left === null || left === void 0) return 1;
683
+ if (right === null || right === void 0) return -1;
684
+ if (typeof left === "number" && typeof right === "number") return left - right;
685
+ if (typeof left === "bigint" && typeof right === "bigint") return left < right ? -1 : 1;
686
+ const a = String(left);
687
+ const b = String(right);
688
+ return a < b ? -1 : a > b ? 1 : 0;
689
+ };
690
+ const keyed = rows.map((row) => ({
691
+ row,
692
+ key: columns.map((column) => {
693
+ const value = row[column];
694
+ return typeof value === "object" && value !== null ? String(value) : value;
695
+ })
696
+ }));
697
+ keyed.sort((left, right) => {
698
+ for (let index = 0; index < columns.length; index++) {
699
+ const order = compare(left.key[index], right.key[index]);
700
+ if (order !== 0) return order;
701
+ }
702
+ return 0;
703
+ });
704
+ return keyed.map((entry) => entry.row);
705
+ }
706
+ /**
707
+ * The rows after the cursor `values` in `orderBy` order, as an OR of ANDs:
708
+ * `a > x or (a = x and b > y)`. The SQL compiler sends that shape as the row
709
+ * comparison `(a, b) > (x, y)`. Nulls sort where Postgres puts them: last
710
+ * for `asc`, first for `desc`, unless the term says otherwise.
711
+ */
712
+ function keysetCondition(orderBy, values, nullable) {
713
+ const isNull = (name) => column(name, "is", null);
714
+ const branches = orderBy.flatMap((term, index) => {
715
+ const value = values[index];
716
+ const nullsFirst = (term.nulls ?? (term.direction === "desc" ? "first" : "last")) === "first";
717
+ const after = column(term.column, term.direction === "asc" ? "gt" : "lt", value);
718
+ const step = value === null ? nullsFirst ? not(isNull(term.column)) : void 0 : nullsFirst || !nullable(term.column) ? after : or(after, isNull(term.column));
719
+ if (!step) return [];
720
+ const equal = orderBy.slice(0, index).map((prev, prevIndex) => values[prevIndex] === null ? isNull(prev.column) : column(prev.column, "eq", values[prevIndex]));
721
+ return [and(...equal, step) ?? step];
653
722
  });
654
- return or(...branches);
723
+ return branches.length > 0 ? or(...branches) : column(orderBy[0]?.column ?? "", "in", []);
655
724
  }
656
725
  //#endregion
657
726
  //#region src/core/search.ts
@@ -698,7 +767,7 @@ var BetterSupabase = class BetterSupabase {
698
767
  * A token that fails it resolves to `{ kind: 'invalid', reason: 'claims' }`.
699
768
  *
700
769
  * ```ts
701
- * const sb = defineSupabase(schema).claims(z.object({ tenant_id: z.uuid() }));
770
+ * const betterSupabase = defineSupabase(schema).claims(z.object({ tenant_id: z.uuid() }));
702
771
  * ```
703
772
  */
704
773
  claims(schema) {
@@ -716,7 +785,7 @@ var BetterSupabase = class BetterSupabase {
716
785
  * the verified claims, never from `profile`.
717
786
  *
718
787
  * ```ts
719
- * const sb = defineSupabase(schema).userMetadata(z.object({ display_name: z.string() }));
788
+ * const betterSupabase = defineSupabase(schema).userMetadata(z.object({ display_name: z.string() }));
720
789
  * ```
721
790
  */
722
791
  userMetadata(schema) {
@@ -731,7 +800,7 @@ var BetterSupabase = class BetterSupabase {
731
800
  * Results themselves keep their `DbError`.
732
801
  *
733
802
  * ```ts
734
- * const sb = defineSupabase(schema).mapError((error) => new AppError(error));
803
+ * const betterSupabase = defineSupabase(schema).mapError((error) => new AppError(error));
735
804
  * ```
736
805
  */
737
806
  mapError(mapper) {
@@ -742,9 +811,14 @@ var BetterSupabase = class BetterSupabase {
742
811
  }
743
812
  #specs;
744
813
  #truncatedTables = /* @__PURE__ */ new Set();
814
+ /** A hub without listeners, for decoding read-set results. */
815
+ #decodeEvents;
816
+ /** One getter per table, shared by every db this instance connects. */
817
+ #tables;
818
+ #errorMappers = /* @__PURE__ */ new WeakMap();
745
819
  /**
746
820
  * Builds serializable `QuerySpec`s with the same arguments and result types
747
- * as the repository: `sb.spec.customers.findMany({ select: ['id'] })`.
821
+ * as the repository: `betterSupabase.spec.customers.findMany({ select: ['id'] })`.
748
822
  */
749
823
  get spec() {
750
824
  this.#specs ??= createSpecs(this.meta);
@@ -765,7 +839,7 @@ var BetterSupabase = class BetterSupabase {
765
839
  * invalidate them after it succeeds.
766
840
  *
767
841
  * ```ts
768
- * sb.defineRpc('archive_customer', { invalidates: ['customers', 'notes'] });
842
+ * betterSupabase.defineRpc('archive_customer', { invalidates: ['customers', 'notes'] });
769
843
  * ```
770
844
  */
771
845
  defineRpc(name, options) {
@@ -796,7 +870,9 @@ var BetterSupabase = class BetterSupabase {
796
870
  const invalidate = (target) => {
797
871
  try {
798
872
  const pending = adapter.invalidate(target);
799
- if (pending) pending.catch((cause) => report(cause, target.table));
873
+ if (pending) pending.catch((cause) => {
874
+ report(cause, target.table);
875
+ });
800
876
  } catch (cause) {
801
877
  report(cause, target.table);
802
878
  }
@@ -822,20 +898,26 @@ var BetterSupabase = class BetterSupabase {
822
898
  let executor = base;
823
899
  for (const plugin of plugins) if (plugin.wrapExecutor) executor = plugin.wrapExecutor(executor);
824
900
  executor = recordStats(executor, recorder);
825
- const errorMappers = [...this.options.errors ?? [], ...plugins.flatMap((plugin) => plugin.mapError ? [plugin.mapError] : [])];
901
+ const errorMappers = this.#errorMappersFor(plugins);
826
902
  const runner = new OperationRunner({
827
903
  meta: this.meta,
828
- builder: new IrBuilder(this.meta),
904
+ builder: builderFor(this.meta),
829
905
  executor,
830
906
  plugins,
831
907
  context,
832
908
  events,
833
909
  errorMappers,
834
- now: this.options.now ?? (() => /* @__PURE__ */ new Date()),
910
+ now: this.options.now ?? nowInstant,
835
911
  maxRows: this.options.maxRows ?? 1e3,
836
912
  truncatedTables: this.#truncatedTables
837
913
  });
838
- const db = {
914
+ const db = Object.create(this.#tablePrototype());
915
+ dbStates.set(db, {
916
+ runner,
917
+ plugins,
918
+ repositories: /* @__PURE__ */ new Map()
919
+ });
920
+ Object.assign(db, {
839
921
  $client: client,
840
922
  $executor: executor,
841
923
  $context: context,
@@ -903,10 +985,10 @@ var BetterSupabase = class BetterSupabase {
903
985
  hint: `Add "${table.schema}.${table.name}" to vectorSearch in better-supabase.config.ts and run \`better-supabase sql sync\`.`
904
986
  }) : result;
905
987
  })
906
- };
988
+ });
907
989
  const many = (specs, signal, usePlugins) => AsyncResult.from(async () => {
908
990
  const invalid = specs.findIndex((spec) => !isQuerySpec(spec));
909
- if (invalid !== -1) return err(dbError("invalid_request", `db.$many() entry ${invalid} is not a QuerySpec from sb.spec`));
991
+ if (invalid !== -1) return err(dbError("invalid_request", `db.$many() entry ${invalid} is not a QuerySpec from betterSupabase.spec`));
910
992
  const active = usePlugins ? plugins : [];
911
993
  const batch = base.batch?.bind(base);
912
994
  const batching = batch && specs.length > 1 ? batchingExecutor({
@@ -954,21 +1036,6 @@ var BetterSupabase = class BetterSupabase {
954
1036
  "$many",
955
1037
  "$search"
956
1038
  ]) db[name] = mapThrows(db[name], throwAs);
957
- for (const [key, table] of Object.entries(this.meta.tables)) {
958
- let repository;
959
- Object.defineProperty(db, key, {
960
- enumerable: true,
961
- get: () => {
962
- if (!repository) {
963
- repository = this.#repository(runner, key, table, plugins);
964
- if (throwAs) {
965
- for (const [name, method] of Object.entries(repository)) if (typeof method === "function" && name !== "extend") repository[name] = mapThrows(method, throwAs);
966
- }
967
- }
968
- return repository;
969
- }
970
- });
971
- }
972
1039
  return db;
973
1040
  }
974
1041
  /**
@@ -985,22 +1052,55 @@ var BetterSupabase = class BetterSupabase {
985
1052
  const rows = entry.rows ?? [];
986
1053
  const result = await runSpec(this.#db(client, {
987
1054
  name: "read-set",
988
- execute: async (op) => {
1055
+ execute: (op) => {
989
1056
  if (op.kind === "select" && op.single) {
990
- if (rows.length > 1) return err(dbError("multiple_rows", `Expected one ${op.table.key} row`));
991
- if (rows.length === 0 && op.single === "one") return err(dbError("not_found", `No ${op.table.key} row matched`));
1057
+ if (rows.length > 1) return Promise.resolve(err(dbError("multiple_rows", `Expected one ${op.table.key} row`)));
1058
+ if (rows.length === 0 && op.single === "one") return Promise.resolve(err(dbError("not_found", `No ${op.table.key} row matched`)));
992
1059
  }
993
- return ok({
1060
+ return Promise.resolve(ok({
994
1061
  rows,
995
1062
  count: entry.count ?? null
996
- });
1063
+ }));
997
1064
  }
998
- }, context, [], new StatsRecorder(), new EventHub(this.events.logger)), spec, void 0);
1065
+ }, context, [], new StatsRecorder(), this.#decodeEvents ??= new EventHub(this.events.logger)), spec, void 0);
999
1066
  if (!result.ok) return result;
1000
1067
  out[key] = result.data;
1001
1068
  }
1002
1069
  return ok(out);
1003
1070
  }
1071
+ #errorMappersFor(plugins) {
1072
+ let mappers = this.#errorMappers.get(plugins);
1073
+ if (!mappers) {
1074
+ mappers = [...this.options.errors ?? [], ...plugins.flatMap((plugin) => plugin.mapError ? [plugin.mapError] : [])];
1075
+ this.#errorMappers.set(plugins, mappers);
1076
+ }
1077
+ return mappers;
1078
+ }
1079
+ #tablePrototype() {
1080
+ if (this.#tables) return this.#tables;
1081
+ const tables = {};
1082
+ const throwAs = this.options.throwAs;
1083
+ const build = (state, key) => {
1084
+ let repository = state.repositories.get(key);
1085
+ if (repository) return repository;
1086
+ const table = this.meta.tables[key];
1087
+ repository = this.#repository(state.runner, key, table, state.plugins);
1088
+ if (throwAs) {
1089
+ for (const [name, method] of Object.entries(repository)) if (typeof method === "function" && name !== "extend") repository[name] = mapThrows(method, throwAs);
1090
+ }
1091
+ state.repositories.set(key, repository);
1092
+ return repository;
1093
+ };
1094
+ for (const key of Object.keys(this.meta.tables)) Object.defineProperty(tables, key, {
1095
+ enumerable: true,
1096
+ get() {
1097
+ const state = dbStates.get(this);
1098
+ return state ? build(state, key) : void 0;
1099
+ }
1100
+ });
1101
+ this.#tables = tables;
1102
+ return tables;
1103
+ }
1004
1104
  #repository(runner, key, table, plugins) {
1005
1105
  const repository = createRepository(runner, table);
1006
1106
  for (const plugin of plugins) {
@@ -1021,6 +1121,7 @@ var BetterSupabase = class BetterSupabase {
1021
1121
  return repository;
1022
1122
  }
1023
1123
  };
1124
+ const dbStates = /* @__PURE__ */ new WeakMap();
1024
1125
  function mapThrows(method, throwAs) {
1025
1126
  return function(...args) {
1026
1127
  const value = method.apply(this, args);
@@ -1028,7 +1129,7 @@ function mapThrows(method, throwAs) {
1028
1129
  };
1029
1130
  }
1030
1131
  function runSpec(db, spec, signal) {
1031
- if (!isQuerySpec(spec)) return AsyncResult.err(dbError("invalid_request", "db.$run() expects a QuerySpec from sb.spec"));
1132
+ if (!isQuerySpec(spec)) return AsyncResult.err(dbError("invalid_request", "db.$run() expects a QuerySpec from betterSupabase.spec"));
1032
1133
  const method = db[spec.table]?.[spec.method];
1033
1134
  if (!method) return AsyncResult.err(dbError("invalid_request", `Unknown table "${spec.table}" in QuerySpec`));
1034
1135
  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)"));
@@ -1061,7 +1162,7 @@ function rpc(executor, errorMappers, name, rest) {
1061
1162
  * Creates the data-layer definition from the generated `schema`.
1062
1163
  *
1063
1164
  * ```ts
1064
- * export const sb = defineSupabase(schema).use(timestamps()).use(softDelete());
1165
+ * export const betterSupabase = defineSupabase(schema).use(timestamps()).use(softDelete());
1065
1166
  * ```
1066
1167
  */
1067
1168
  function defineSupabase(schema, options) {