better-supabase 0.4.0 → 0.5.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 (179) hide show
  1. package/README.md +2 -0
  2. package/dist/channel-FQW-CnGR.d.ts +70 -0
  3. package/dist/cli/bin.js +1 -1
  4. package/dist/cli/codemod-DqV7612m.js +388 -0
  5. package/dist/cli/{config-D9R0o1LH.js → config-CPVpXP5I.js} +78 -8
  6. package/dist/cli/{config-SSKSTUl2.js → config-dtoekM0v.js} +1 -1
  7. package/dist/cli/{read-sets-CMa29t9a.js → diff-SjkO5rNr.js} +1 -220
  8. package/dist/cli/{dist-CFgGknmZ.js → dist-DBPdlPYK.js} +4 -4
  9. package/dist/cli/{dist-B1MvLv-M.js → dist-DreZ24Sz.js} +1 -1
  10. package/dist/cli/{dist-Du7VnEUG.js → dist-xn4wlboc.js} +112 -1
  11. package/dist/cli/{doctor-BOym3mbL.js → doctor-L7Z8mYTx.js} +531 -50
  12. package/dist/cli/{env-BZ0uy8XK.js → env-DmKv_G4A.js} +1 -1
  13. package/dist/cli/gen-BJc9vdyu.js +2 -0
  14. package/dist/cli/{gen-Ccn_p90L.js → gen-C2osRcr5.js} +283 -87
  15. package/dist/cli/index.d.ts +1 -1
  16. package/dist/cli/index.js +4 -4
  17. package/dist/cli/{init-ClnxRYBF.js → init-Saf5-R9T.js} +2 -2
  18. package/dist/cli/{introspect-DxqfkVIv.js → introspect--H7oGmcd.js} +2 -2
  19. package/dist/cli/{openapi-8WHjxiTJ.js → openapi-Cp5Fyta-.js} +1 -1
  20. package/dist/cli/read-sets-BfMQcKy8.js +289 -0
  21. package/dist/cli/{run-CP20LKk_.js → run-BKR2W_79.js} +16 -12
  22. package/dist/cli/{seed-5J6eu_Xg.js → seed-CUlqWh-8.js} +1 -1
  23. package/dist/cli/sql-DzPbfngx.js +348 -0
  24. package/dist/cli/{version-5mW_8sY1.js → version-BDJuO0UR.js} +1 -1
  25. package/dist/client/index.d.ts +1 -1
  26. package/dist/client/index.js +3 -10
  27. package/dist/clone-BfgmLU90.js +21 -0
  28. package/dist/{compiler-DB3aSFOA.d.ts → compiler-BXH_aZ7N.d.ts} +2 -2
  29. package/dist/config/index.d.ts +4 -4
  30. package/dist/config/index.js +4 -4
  31. package/dist/{config-CbvFmGD8.js → config-DreERlKo.js} +1 -1
  32. package/dist/{config-C1CYcm6o.d.ts → config-T_ejwyd3.d.ts} +176 -11
  33. package/dist/{define-CRS2MRV3.d.ts → define-Bp_BZa5h.d.ts} +11 -7
  34. package/dist/{define-Bh0AqTik.js → define-cNKzqwL_.js} +202 -16
  35. package/dist/edge/index.d.ts +16 -5
  36. package/dist/edge/index.js +10 -7
  37. package/dist/{entitlements-Dm-FHpNH.d.ts → entitlements-DUxU4oHe.d.ts} +1 -1
  38. package/dist/env/index.d.ts +2 -2
  39. package/dist/env/index.js +34 -1
  40. package/dist/{errors-L0xK41y_.js → errors-DtwXa6b8.js} +1 -0
  41. package/dist/events/index.d.ts +3 -2
  42. package/dist/events/index.js +65 -12
  43. package/dist/events-aIO7vsIk.d.ts +230 -0
  44. package/dist/{result-CqEsbgYs.d.ts → executor-BunPOO-k.d.ts} +200 -1
  45. package/dist/{executor-CZ7ImRiD.d.ts → executor-Cghdstwu.d.ts} +1 -2
  46. package/dist/{executor-CVt6dD0B.js → executor-XycESQPH.js} +3 -3
  47. package/dist/hono/index.d.ts +17 -6
  48. package/dist/hono/index.js +40 -5
  49. package/dist/impersonation-VfaGc4Q_.js +41 -0
  50. package/dist/{index-BBdvo-5d.d.ts → index-BGdgBPdf.d.ts} +49 -6
  51. package/dist/{index-BhHEC04r.d.ts → index-C7f8cOBM.d.ts} +2 -2
  52. package/dist/{index-Dy6kuF0P.d.ts → index-CLg87OBW.d.ts} +6 -1
  53. package/dist/{index-D5IfUIDh.d.ts → index-DOESKG27.d.ts} +33 -8
  54. package/dist/{index-DFmOpBGC.d.ts → index-DgILJRXj.d.ts} +35 -9
  55. package/dist/{index-RlLXd8dL.d.ts → index-DmXSxbLi.d.ts} +4 -4
  56. package/dist/{index-Cf-D2jk1.d.ts → index-Dxn2a5cw.d.ts} +2 -2
  57. package/dist/{index-528Ugmyp.d.ts → index-SaTsEfGP.d.ts} +76 -8
  58. package/dist/{index-BAy4tsAP.d.ts → index-fag8VJZ2.d.ts} +3 -4
  59. package/dist/index.d.ts +30 -13
  60. package/dist/index.js +10 -9
  61. package/dist/jobs/index.d.ts +147 -101
  62. package/dist/jobs/index.js +744 -237
  63. package/dist/{json-schema-DP6V7cbr.js → json-schema-m1fiwKgN.js} +22 -1
  64. package/dist/kit-events-CTFDfSwb.js +60 -0
  65. package/dist/kit-transport-B82k1djG.d.ts +33 -0
  66. package/dist/kit-transport-DJlHQUBU.js +55 -0
  67. package/dist/{config-BpxDfnbg.js → kits-CGLLrNPd.js} +37 -9
  68. package/dist/list/index.d.ts +1 -1
  69. package/dist/{live-BHakSWWo.d.ts → live-D9W2M5aO.d.ts} +6 -5
  70. package/dist/mcp/index.d.ts +25 -8
  71. package/dist/mcp/index.js +23 -14
  72. package/dist/{mfa-C7MNQ1Ob.d.ts → mfa-CusDgPaD.d.ts} +5 -1
  73. package/dist/next/index.d.ts +31 -10
  74. package/dist/next/index.js +136 -27
  75. package/dist/notifications/index.d.ts +162 -0
  76. package/dist/notifications/index.js +264 -0
  77. package/dist/openapi/index.d.ts +1 -1
  78. package/dist/openapi/index.js +2 -2
  79. package/dist/orgs/index.d.ts +132 -0
  80. package/dist/orgs/index.js +245 -0
  81. package/dist/orpc/index.d.ts +28 -8
  82. package/dist/orpc/index.js +16 -3
  83. package/dist/otel/index.d.ts +21 -2
  84. package/dist/otel/index.js +50 -9
  85. package/dist/permdock-sql-C2Inc6Xc.d.ts +29 -0
  86. package/dist/permdock-sql-DgOfeixi.js +51 -0
  87. package/dist/plugin-DwARMuQp.js +59 -0
  88. package/dist/{plugin-bNhvJwRh.d.ts → plugin-o144f322.d.ts} +17 -5
  89. package/dist/plugins/actor/index.d.ts +5 -2
  90. package/dist/plugins/actor/index.js +22 -8
  91. package/dist/plugins/rules/index.d.ts +2 -2
  92. package/dist/plugins/rules/index.js +15 -10
  93. package/dist/plugins/soft-delete/index.d.ts +3 -3
  94. package/dist/plugins/soft-delete/index.js +14 -5
  95. package/dist/plugins/tenant/index.d.ts +2 -2
  96. package/dist/plugins/tenant/index.js +38 -20
  97. package/dist/plugins/timestamps/index.d.ts +1 -1
  98. package/dist/plugins/timestamps/index.js +4 -3
  99. package/dist/plugins/validation/index.d.ts +2 -2
  100. package/dist/plugins/validation/index.js +26 -6
  101. package/dist/{pool-DFwEQwmN.d.ts → pool-C9voM4Kw.d.ts} +17 -6
  102. package/dist/postgres/index.d.ts +5 -5
  103. package/dist/postgres/index.js +20 -11
  104. package/dist/{postgrest-Bc-0Xw62.d.ts → postgrest-CruYKAvV.d.ts} +1 -1
  105. package/dist/{problem-BihjPR0t.js → problem-BW1KhzSG.js} +2 -1
  106. package/dist/{problem-U8UEKccv.d.ts → problem-Blxe8S31.d.ts} +1 -1
  107. package/dist/query/index.d.ts +2 -2
  108. package/dist/query/index.js +3 -3
  109. package/dist/{query-BMMSWbI7.js → query-CCbo44lG.js} +2 -2
  110. package/dist/queue-BYUCjr59.d.ts +252 -0
  111. package/dist/react/index.d.ts +8 -7
  112. package/dist/react/index.js +4 -3
  113. package/dist/react/server.d.ts +14 -3
  114. package/dist/react/server.js +10 -2
  115. package/dist/react/session.d.ts +7 -1
  116. package/dist/react/session.js +9 -1
  117. package/dist/{react-CQHMaESR.js → react-C_s7kiNz.js} +103 -11
  118. package/dist/{read-set-FWK8qQ58.js → read-set-DQFt1N_Y.js} +22 -1
  119. package/dist/realtime/index.d.ts +3 -3
  120. package/dist/realtime/index.js +5 -4
  121. package/dist/{refresh-BwYkyNOQ.d.ts → refresh-BTrQ_ZTp.d.ts} +1 -1
  122. package/dist/{resolve-BAqQcwMW.d.ts → resolve-C15TJq77.d.ts} +11 -5
  123. package/dist/{resource-My_y-Syh.js → resource-CRxxwkQq.js} +22 -7
  124. package/dist/{resource-BJcaIfxi.d.ts → resource-D8iqWMr-.d.ts} +5 -5
  125. package/dist/respond-D2OQl62-.d.ts +371 -0
  126. package/dist/{result-Bj6yt33D.js → result-BKiliPY7.js} +1 -1
  127. package/dist/server/index.d.ts +14 -12
  128. package/dist/server/index.js +11 -17
  129. package/dist/{server-BA75B0mT.js → server-Csx2sRjg.js} +251 -34
  130. package/dist/{shared-DnoCdMDr.js → shared-D1dRUrAg.js} +16 -2
  131. package/dist/signers-Cc6r98SW.d.ts +162 -0
  132. package/dist/{simplify-Cqvfr9dj.js → simplify-_0oUKqCd.js} +4 -115
  133. package/dist/sql/index.d.ts +247 -10
  134. package/dist/sql/index.js +8590 -779
  135. package/dist/{sql-B34HU2DI.js → sql-Coh6n4cC.js} +9 -2
  136. package/dist/ssr/index.d.ts +4 -4
  137. package/dist/ssr/index.js +1 -2
  138. package/dist/{standard-BRyBGTtp.js → standard-D7r6dMUu.js} +6 -3
  139. package/dist/{standard-BsaCYQJc.d.ts → standard-J4YgMOLm.d.ts} +1 -1
  140. package/dist/{stats-C_YxA2r1.js → stats-Bqob3WqT.js} +4 -4
  141. package/dist/storage/index.d.ts +4 -4
  142. package/dist/storage/index.js +155 -26
  143. package/dist/support-DT-OhSxD.d.ts +88 -0
  144. package/dist/support-Dk6vdmEw.js +291 -0
  145. package/dist/support-view-Da5peKFg.d.ts +17 -0
  146. package/dist/support-view-g4YLBeqX.js +18 -0
  147. package/dist/{permdock-sql-C4KFzLc9.js → template-TxrGI5jR.js} +2 -31
  148. package/dist/{temporal-BwshQk0W.js → temporal-CVlIV2DZ.js} +17 -4
  149. package/dist/{temporal-required-CBNHT6ni.js → temporal-required-CAt4Dc9Z.js} +2 -2
  150. package/dist/testing/index.d.ts +100 -10
  151. package/dist/testing/index.js +336 -8
  152. package/dist/{types-DONv9SHN.d.ts → types-Dnf_wRDz.d.ts} +26 -3
  153. package/dist/user-change-CHFEUro1.js +49 -0
  154. package/dist/{webhooks-Bh_Jf_-l.js → verify-Ce49ETPz.js} +5 -5
  155. package/dist/{version-Bmf7_FzM.js → version-DM_Roffb.js} +1 -1
  156. package/dist/{view-B64o81wp.d.ts → view-BEf0diC5.d.ts} +8 -3
  157. package/dist/{mfa-byfjruR9.js → view-Bq64ki8-.js} +95 -38
  158. package/dist/webhooks/index.d.ts +135 -4
  159. package/dist/webhooks/index.js +588 -2
  160. package/dist/wire-D2dT86vi.js +35 -0
  161. package/package.json +16 -8
  162. package/schemas/config-v1.json +131 -1
  163. package/schemas/doctor-report-v1.json +10 -0
  164. package/skills/better-supabase/SKILL.md +9 -6
  165. package/skills/better-supabase/references/plugins.md +6 -3
  166. package/skills/better-supabase/references/troubleshooting.md +1 -0
  167. package/skills/better-supabase-api/SKILL.md +9 -2
  168. package/skills/better-supabase-api/references/adapters.md +11 -8
  169. package/skills/better-supabase-auth/SKILL.md +3 -0
  170. package/skills/better-supabase-auth/references/permdock.md +56 -11
  171. package/dist/cli/gen-D5cLCULx.js +0 -2
  172. package/dist/cli/sql-D4VbqmXi.js +0 -199
  173. package/dist/events-B21UOhvP.d.ts +0 -84
  174. package/dist/executor-B5IoUxLE.d.ts +0 -195
  175. package/dist/invalidate-CgxmRamI.js +0 -24
  176. package/dist/permdock-sql-DImJULY-.d.ts +0 -17
  177. package/dist/plugin-BqR6wKMB.js +0 -23
  178. package/dist/respond-DgIbdrt8.d.ts +0 -194
  179. package/dist/view-BrJI2LKp.js +0 -45
@@ -1,17 +1,18 @@
1
- import { n as dbError } from "../errors-L0xK41y_.js";
2
- import { n as StatsRecorder, t as EMPTY_STATS } from "../stats-C_YxA2r1.js";
3
- import { o as isReadSet } from "../read-set-FWK8qQ58.js";
4
- import { t as validate } from "../standard-BRyBGTtp.js";
5
- import { a as problemResponse } from "../problem-BihjPR0t.js";
1
+ import { n as dbError } from "../errors-DtwXa6b8.js";
2
+ import { n as StatsRecorder, t as EMPTY_STATS } from "../stats-Bqob3WqT.js";
3
+ import { o as isReadSet } from "../read-set-DQFt1N_Y.js";
4
+ import { t as validate } from "../standard-D7r6dMUu.js";
5
+ import { a as problemResponse } from "../problem-BW1KhzSG.js";
6
6
  import { t as isList } from "../guards-BnzDvFrz.js";
7
+ import { t as supportOf } from "../support-view-g4YLBeqX.js";
7
8
  import { t as hasEntitlement } from "../entitlements-CTEBm7IB.js";
8
- import { a as guard, c as settle, d as primaryCookie, i as defaultExpose, n as extendServer, r as withExtra, s as respond, t as createServer, u as PRIMARY_COOKIE } from "../server-BA75B0mT.js";
9
- import { r as checkAal } from "../mfa-byfjruR9.js";
10
- import { t as toSession } from "../view-BrJI2LKp.js";
9
+ import { a as withExtra, b as withPrimaryPin, d as guard, m as settle, n as createServer, p as respond, r as extendServer, u as defaultExpose, v as PRIMARY_COOKIE, y as primaryCookieOptions } from "../server-Csx2sRjg.js";
10
+ import { a as checkAal, n as toSession } from "../view-Bq64ki8-.js";
11
+ import { t as supportSessions } from "../support-Dk6vdmEw.js";
11
12
  import { cache } from "react";
12
13
  import { cacheLife, cacheTag, revalidateTag, updateTag } from "next/cache.js";
13
14
  import { cookies, headers } from "next/headers.js";
14
- import { NextResponse } from "next/server.js";
15
+ import { NextResponse, after } from "next/server.js";
15
16
  //#region src/next/collector.ts
16
17
  /** Forwarded by the proxy so every scope of one render shares an id. */
17
18
  const REQUEST_ID_HEADER = "x-bs-request-id";
@@ -145,6 +146,10 @@ function serverTimingValue(metrics) {
145
146
  //#region src/next/create.ts
146
147
  /** Response header with the URL of the request's totals on `bs.debugRoute()`. */
147
148
  const STATS_URL_HEADER = "x-bs-stats";
149
+ /** The tag `bs.cached()` adds to entries rendered in a support session. */
150
+ function supportTag(sessionId) {
151
+ return `bs:support:${sessionId}`;
152
+ }
148
153
  /**
149
154
  * `cacheLife` `stale` seconds for a view of `session`: `max`, but never past
150
155
  * the token's expiry, and at least `min`.
@@ -256,6 +261,16 @@ function createNext(betterSupabase, options = {}) {
256
261
  const id = collector && request.headers.get("x-bs-request-id");
257
262
  return id ? { stats: collector.recorderFor(id) } : {};
258
263
  };
264
+ /** Keeps the function alive for event sink sends the handler started. */
265
+ const flushAfter = () => {
266
+ const { events } = betterSupabase;
267
+ if (!events.pending) return;
268
+ try {
269
+ after(() => events.settled());
270
+ } catch (cause) {
271
+ events.logger.warn("after() is unavailable; event sends may be cut off", { cause });
272
+ }
273
+ };
259
274
  const incomingRequest = async () => new Request("http://next.local/", { headers: new Headers(await headers()) });
260
275
  /** One token verification per render scope backs `context()`, `session()` and `cached()`. */
261
276
  const incoming = cache(async () => {
@@ -267,14 +282,27 @@ function createNext(betterSupabase, options = {}) {
267
282
  });
268
283
  const current = cache(async () => {
269
284
  const { request, resolution } = await incoming();
270
- return base.contextFromResolution(resolution, request, statsFor(request));
285
+ const [tenant, support] = await Promise.all([options.tenant?.(request, resolution.auth), base.support.current(request, resolution.auth)]);
286
+ return base.contextFromResolution(resolution, request, {
287
+ ...statsFor(request),
288
+ ...tenant === void 0 ? {} : { tenant },
289
+ ...support ? { support } : {}
290
+ });
271
291
  });
272
292
  const context = (request, contextOptions) => request ? base.context(request, contextOptions) : current();
273
- const session = cache(async () => toSession((await incoming()).resolution.auth));
293
+ /** In a support session, the target's view: `impersonator` names the admin. */
294
+ const session = cache(async () => toSession(options.support ? (await current()).auth : (await incoming()).resolution.auth));
295
+ const supportCookieName = options.support?.options.cookie?.name ?? "bs-support";
296
+ const supportCookieOptions = {
297
+ httpOnly: true,
298
+ sameSite: "lax",
299
+ path: options.support?.options.cookie?.path ?? "/",
300
+ secure: options.support?.options.cookie?.secure ?? true
301
+ };
274
302
  const contextForSession = async (view, { token }) => {
275
303
  const request = new Request("http://next.local/", { headers: token ? { authorization: `Bearer ${token}` } : {} });
276
304
  const ctx = await base.context(request);
277
- if (ctx.auth.kind === "user" ? view.kind === "user" && view.user.id === ctx.auth.user.id : view.kind !== "user") return ctx;
305
+ if (ctx.auth.kind === "user" ? view.kind === "user" && view.user.id === ctx.auth.user.id : view.kind === ctx.auth.kind) return ctx;
278
306
  return base.contextFor({
279
307
  kind: "invalid",
280
308
  reason: "token",
@@ -300,7 +328,11 @@ function createNext(betterSupabase, options = {}) {
300
328
  ...life?.revalidate === void 0 ? {} : { revalidate: life.revalidate },
301
329
  ...life?.expire === void 0 ? {} : { expire: life.expire }
302
330
  });
303
- const tags = [...view.kind === "user" ? [sessionTag(view.user.id)] : [], ...cachedOptions.tags ?? []];
331
+ const tags = [
332
+ ...view.kind === "user" ? [sessionTag(view.user.id)] : [],
333
+ ...ctx.support ? [supportTag(ctx.support.session.id)] : [],
334
+ ...cachedOptions.tags ?? []
335
+ ];
304
336
  if (tags.length > 0) cacheTag(...tags);
305
337
  return withExtra(ctx, { session: view });
306
338
  },
@@ -357,13 +389,21 @@ function createNext(betterSupabase, options = {}) {
357
389
  expose
358
390
  });
359
391
  const params = await segment.params;
360
- const response = await respond(() => handler(request, withExtra(ctx, { params })), {
361
- instance,
362
- expose
363
- });
364
- if (ctx.replica?.wrote) try {
365
- response.headers.append("set-cookie", primaryCookie(pinMs));
366
- } catch {}
392
+ let response;
393
+ try {
394
+ response = await respond(() => handler(request, withExtra(ctx, { params })), {
395
+ instance,
396
+ expose
397
+ });
398
+ } catch (cause) {
399
+ rethrowNextControlFlow(cause);
400
+ response = problemResponse(dbError("unexpected", expose && cause instanceof Error ? cause.message : "Internal server error"), {
401
+ instance,
402
+ expose
403
+ });
404
+ }
405
+ response = withPrimaryPin(response, ctx.replica, pinMs);
406
+ flushAfter();
367
407
  if (collector) try {
368
408
  response.headers.set(statsHeader, formatStats(ctx.stats()));
369
409
  } catch {}
@@ -404,12 +444,8 @@ function createNext(betterSupabase, options = {}) {
404
444
  parsed = checked.data;
405
445
  }
406
446
  const settled = await settle(() => fn(parsed, ctx));
407
- if (ctx.replica?.wrote) (await cookies()).set(PRIMARY_COOKIE, String(Date.now() + pinMs), {
408
- path: "/",
409
- maxAge: Math.max(1, Math.ceil(pinMs / 1e3)),
410
- httpOnly: true,
411
- sameSite: "lax"
412
- });
447
+ flushAfter();
448
+ if (ctx.replica?.wrote) (await cookies()).set(PRIMARY_COOKIE, String(Date.now() + pinMs), primaryCookieOptions(pinMs));
413
449
  return settled.ok ? {
414
450
  ok: true,
415
451
  data: settled.data,
@@ -421,6 +457,59 @@ function createNext(betterSupabase, options = {}) {
421
457
  };
422
458
  };
423
459
  },
460
+ async startSupport(request) {
461
+ const { resolution } = await incoming();
462
+ const started = await base.support.start(resolution.auth, request);
463
+ flushAfter();
464
+ if (!started.ok) return {
465
+ ok: false,
466
+ data: null,
467
+ error: started.error
468
+ };
469
+ const { session: support } = started.data;
470
+ (await cookies()).set(supportCookieName, support.id, {
471
+ ...supportCookieOptions,
472
+ maxAge: Math.max(0, Math.floor((support.expiresAt.epochMilliseconds - Date.now()) / 1e3))
473
+ });
474
+ if (resolution.auth.kind === "user") invalidate(sessionTag(resolution.auth.user.id));
475
+ return {
476
+ ok: true,
477
+ data: {
478
+ sessionId: support.id,
479
+ targetUserId: support.targetUserId,
480
+ readOnly: support.readOnly,
481
+ expiresAt: support.expiresAt.toString()
482
+ },
483
+ error: null
484
+ };
485
+ },
486
+ async stopSupport() {
487
+ const { request, resolution } = await incoming();
488
+ const jar = await cookies();
489
+ const id = base.support.sessionIdOf(request);
490
+ jar.delete({
491
+ name: supportCookieName,
492
+ path: supportCookieOptions.path
493
+ });
494
+ if (!id) return {
495
+ ok: true,
496
+ data: { ended: false },
497
+ error: null
498
+ };
499
+ const stopped = await base.support.stop(resolution.auth, id);
500
+ flushAfter();
501
+ invalidate(supportTag(id));
502
+ if (resolution.auth.kind === "user") invalidate(sessionTag(resolution.auth.user.id));
503
+ return stopped.ok ? {
504
+ ok: true,
505
+ data: { ended: stopped.data.ended },
506
+ error: null
507
+ } : {
508
+ ok: false,
509
+ data: null,
510
+ error: stopped.error
511
+ };
512
+ },
424
513
  cacheTag(table, id) {
425
514
  cacheTag(tagFor(table), ...id === void 0 ? [] : [tagFor(table, id)]);
426
515
  },
@@ -436,5 +525,25 @@ function createNext(betterSupabase, options = {}) {
436
525
  }
437
526
  });
438
527
  }
528
+ const NEXT_DIGESTS = /* @__PURE__ */ new Set([
529
+ "NEXT_REDIRECT",
530
+ "NEXT_HTTP_ERROR_FALLBACK",
531
+ "DYNAMIC_SERVER_USAGE",
532
+ "BAILOUT_TO_CLIENT_SIDE_RENDERING",
533
+ "HANGING_PROMISE_REJECTION",
534
+ "NEXT_PRERENDER_INTERRUPTED"
535
+ ]);
536
+ const REACT_POSTPONE = Symbol.for("react.postpone");
537
+ /**
538
+ * `unstable_rethrow` without `next/navigation`: that entry loads the client
539
+ * router context, which Turbopack can't resolve inside route handlers. The
540
+ * digests are the ones Next 16 checks.
541
+ */
542
+ function rethrowNextControlFlow(error) {
543
+ if (typeof error !== "object" || error === null) return;
544
+ const digest = "digest" in error ? error.digest : void 0;
545
+ if (typeof digest === "string" && NEXT_DIGESTS.has(digest.split(";", 1)[0] ?? "") || "$$typeof" in error && error.$$typeof === REACT_POSTPONE || error instanceof Error && error.message.includes("needs to bail out of prerendering")) throw error;
546
+ if ("cause" in error) rethrowNextControlFlow(error.cause);
547
+ }
439
548
  //#endregion
440
- export { REQUEST_ID_HEADER, STATS_URL_HEADER, createNext, hasEntitlement, nextCache, requireAal, sessionStale, sessionTag, shouldRefresh, tagFor, toSession };
549
+ export { REQUEST_ID_HEADER, STATS_URL_HEADER, createNext, hasEntitlement, nextCache, requireAal, sessionStale, sessionTag, shouldRefresh, supportOf, supportSessions, supportTag, tagFor, toSession };
@@ -0,0 +1,162 @@
1
+ /// <reference lib="esnext.temporal" />
2
+ import { S as ErrorMapper, a as AsyncResult } from "../executor-BunPOO-k.js";
3
+ import { a as EventHub } from "../events-aIO7vsIk.js";
4
+ import { g as RequestContext } from "../plugin-o144f322.js";
5
+ import { i as sqlTransport, n as RpcClient, r as rpcTransport, t as KitTransport } from "../kit-transport-B82k1djG.js";
6
+ import { a as NotificationSubject, i as NotificationItem, n as NotificationMessage, o as RenderedText, r as NotificationSendResult, t as NotificationChannel } from "../channel-FQW-CnGR.js";
7
+ import { StandardSchemaV1 } from "@standard-schema/spec";
8
+ //#region src/notifications/notifications.d.ts
9
+ /** Notification type to the Standard Schema of its `data`. */
10
+ type NotificationTypes = Readonly<Record<string, StandardSchemaV1>>;
11
+ type TypeName<K extends NotificationTypes> = keyof K & string;
12
+ interface SendInput<D = unknown> {
13
+ /** Who gets it. Watchers of `subject` and the `notification_audience` hook add more. */
14
+ readonly recipients?: readonly string[];
15
+ /** The organization; required when an authenticated user sends. */
16
+ readonly tenant?: string;
17
+ readonly subject?: NotificationSubject;
18
+ readonly summary?: string;
19
+ readonly actionPath?: string;
20
+ /** One of `kits.notifications.options.priorities`. Defaults to `normal`. */
21
+ readonly priority?: string;
22
+ readonly data: D;
23
+ /** With a key, sending again (per tenant) returns the first notification. */
24
+ readonly key?: string;
25
+ /** Defaults to `kits.notifications.options.channels`. */
26
+ readonly channels?: readonly string[];
27
+ /** Also notify the user who caused it. Defaults to `false`. */
28
+ readonly includeActor?: boolean;
29
+ /**
30
+ * `participating` (default) reaches subject watchers at both levels;
31
+ * `all` is routine activity that only `all` watchers get.
32
+ */
33
+ readonly activity?: "participating" | "all";
34
+ /** Create it already resolved, e.g. for a record of a finished approval. */
35
+ readonly resolved?: boolean;
36
+ /** The actor, when the service sends on a user's behalf. */
37
+ readonly actorId?: string;
38
+ }
39
+ interface ListOptions<K extends string = string> {
40
+ readonly tenant?: string;
41
+ readonly status?: "all" | "unread" | "read" | "unresolved";
42
+ readonly types?: readonly K[];
43
+ /**
44
+ * Page: only notifications older than this. Pass the last item of the
45
+ * previous page; a bare instant skips items created at that same instant.
46
+ */
47
+ readonly before?: Temporal.Instant | Pick<NotificationItem, "createdAt" | "id">;
48
+ /** Up to 200. Defaults to 50. */
49
+ readonly limit?: number;
50
+ /** Passed to `render`. */
51
+ readonly locale?: string;
52
+ }
53
+ interface NotificationCounts {
54
+ readonly unread: number;
55
+ /** Unresolved notifications of the `actionable` types. */
56
+ readonly actionable: number;
57
+ }
58
+ type SubscriptionLevel = "participating" | "all" | "ignore";
59
+ interface DeliverOptions {
60
+ /** Channel names to work on. Defaults to every channel in `channels`. */
61
+ readonly channels?: readonly string[];
62
+ /** Deliveries per claim. Defaults to 50. */
63
+ readonly batch?: number;
64
+ /** How long a claimed delivery waits before another worker retries it. */
65
+ readonly lease?: string;
66
+ /**
67
+ * Tries before a delivery is `failed`. Defaults to 5. Retries wait a random
68
+ * time up to 30 seconds, doubling per attempt to an hour.
69
+ */
70
+ readonly maxAttempts?: number;
71
+ readonly budgetMs?: number;
72
+ }
73
+ interface DeliverResult {
74
+ readonly sent: number;
75
+ readonly skipped: number;
76
+ /** Deliveries that threw; retried later unless they ran out of attempts. */
77
+ readonly failed: number;
78
+ }
79
+ interface Rendered<K extends string = string, D = unknown> extends NotificationItem<K, D> {
80
+ /** What `render` returned. Absent without a `render` option. */
81
+ readonly text?: RenderedText;
82
+ }
83
+ interface NotificationsOptions<K extends NotificationTypes> {
84
+ /** `sqlTransport(postgres.asUser(claims))`, or over `postgres.admin` for the service. */
85
+ readonly transport: KitTransport;
86
+ /** Each type's `data` schema: `send` validates against it. */
87
+ readonly types: K;
88
+ /** `kits.notifications.schema`. Defaults to `better_supabase`. */
89
+ readonly schema?: string;
90
+ /** Turns a notification into text at read time, in the reader's locale. */
91
+ readonly render?: (item: NotificationItem, context: {
92
+ readonly locale?: string;
93
+ }) => RenderedText;
94
+ /** Types that wait for an action; `counts().actionable` counts them. */
95
+ readonly actionable?: readonly TypeName<K>[];
96
+ /** Senders for the non-`in_app` channels, used by `deliver()`. */
97
+ readonly channels?: readonly NotificationChannel[];
98
+ /** Runs after a notification is stored, e.g. to call Next's `updateTag`. */
99
+ readonly onSent?: (sent: {
100
+ readonly id: string;
101
+ readonly type: string;
102
+ readonly tenant: string | null;
103
+ readonly recipients: readonly string[];
104
+ }) => void | Promise<void>;
105
+ /** `betterSupabase.events`, for `notification.*` kit events. */
106
+ readonly events?: EventHub;
107
+ readonly actorId?: string;
108
+ readonly context?: RequestContext;
109
+ readonly errorMappers?: readonly ErrorMapper[];
110
+ }
111
+ /**
112
+ * The `notifications` SQL kit module as typed calls. `send` validates the
113
+ * data against its type's schema; the database checks the send permission,
114
+ * leaves out the actor and non-members and applies subscriptions and
115
+ * preferences.
116
+ */
117
+ interface Notifications<K extends NotificationTypes> {
118
+ /** The notification id, or `null` when nobody was left to notify. */
119
+ send<N extends TypeName<K>>(type: N, input: SendInput<StandardSchemaV1.InferInput<K[N]>>): AsyncResult<string | null>;
120
+ list(options?: ListOptions<TypeName<K>>): AsyncResult<readonly Rendered<TypeName<K>>[]>;
121
+ counts(options?: {
122
+ readonly tenant?: string;
123
+ }): AsyncResult<NotificationCounts>;
124
+ /** Marks the given notifications, or all of them, read. */
125
+ markRead(options?: {
126
+ readonly ids?: readonly string[];
127
+ readonly tenant?: string;
128
+ }): AsyncResult<number>;
129
+ dismiss(ids: readonly string[]): AsyncResult<number>;
130
+ /** Resolves every recipient's notification of a type about a subject. */
131
+ resolve(input: {
132
+ readonly type: TypeName<K>;
133
+ readonly subject: NotificationSubject;
134
+ readonly tenant?: string;
135
+ }): AsyncResult<number>;
136
+ /** Sets the user's level for a subject; `null` removes it. */
137
+ subscribe(input: {
138
+ readonly subject: NotificationSubject;
139
+ readonly level: SubscriptionLevel | null;
140
+ readonly tenant?: string;
141
+ /** Another member; only the service can set it. */
142
+ readonly userId?: string;
143
+ }): AsyncResult<void>;
144
+ /** Turns a type (`*` for all) on or off on a channel; `null` removes it. */
145
+ setPreference(input: {
146
+ readonly type: TypeName<K> | "*";
147
+ readonly channel: string;
148
+ readonly enabled: boolean | null;
149
+ /** One organization, or everywhere when omitted. */
150
+ readonly tenant?: string;
151
+ }): AsyncResult<void>;
152
+ /** Sends pending deliveries through `channels`. Run it from a cron or a job. */
153
+ deliver(options?: DeliverOptions): Promise<DeliverResult>;
154
+ /**
155
+ * Deletes up to `batch` (10,000) notifications older than `olderThan`
156
+ * (`90 days`) with their recipients and deliveries. Service only.
157
+ */
158
+ purge(olderThan?: string, batch?: number): AsyncResult<number>;
159
+ }
160
+ export declare function createNotifications<const K extends NotificationTypes>(options: NotificationsOptions<K>): Notifications<K>;
161
+ //#endregion
162
+ export { type DeliverOptions, type DeliverResult, type KitTransport, type ListOptions, type NotificationChannel, type NotificationCounts, type NotificationItem, type NotificationMessage, type NotificationSendResult, type NotificationSubject, type NotificationTypes, type Notifications, type NotificationsOptions, type Rendered, type RenderedText, type RpcClient, type SendInput, type SubscriptionLevel, rpcTransport, sqlTransport };
@@ -0,0 +1,264 @@
1
+ import { c as mapDbError, n as dbError } from "../errors-DtwXa6b8.js";
2
+ import { t as temporal } from "../temporal-required-CAt4Dc9Z.js";
3
+ import { i as ok, n as err, o as toDbError, t as AsyncResult } from "../result-BKiliPY7.js";
4
+ import { t as validate } from "../standard-D7r6dMUu.js";
5
+ import { n as emitKitEvent } from "../kit-events-CTFDfSwb.js";
6
+ import { n as rpcTransport, r as sqlTransport, t as rawError } from "../kit-transport-DJlHQUBU.js";
7
+ //#region src/notifications/notifications.ts
8
+ const DEFAULT_SCHEMA = "better_supabase";
9
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
10
+ const textOf = (value) => value === null || value === void 0 ? null : String(value);
11
+ const instantOf = (value) => typeof value === "string" ? temporal().Instant.from(value) : null;
12
+ function toItem(row) {
13
+ const type = textOf(row["subject_type"]);
14
+ const id = textOf(row["subject_id"]);
15
+ const label = textOf(row["subject_label"]);
16
+ return {
17
+ id: String(row["id"]),
18
+ eventId: String(row["event_id"]),
19
+ type: String(row["type"]),
20
+ data: row["data"] ?? {},
21
+ tenant: textOf(row["tenant"]),
22
+ actorId: textOf(row["actor_id"]),
23
+ subject: type === null || id === null ? null : {
24
+ type,
25
+ id,
26
+ ...label === null ? {} : { label }
27
+ },
28
+ summary: textOf(row["summary"]),
29
+ actionPath: textOf(row["action_path"]),
30
+ priority: textOf(row["priority"]),
31
+ createdAt: instantOf(row["created_at"]) ?? temporal().Now.instant(),
32
+ readAt: instantOf(row["read_at"]),
33
+ resolvedAt: instantOf(row["resolved_at"])
34
+ };
35
+ }
36
+ function cursorOf(before) {
37
+ if (before === void 0) return {
38
+ before: null,
39
+ before_id: null
40
+ };
41
+ if ("epochNanoseconds" in before) return {
42
+ before: before.toString(),
43
+ before_id: null
44
+ };
45
+ return {
46
+ before: before.createdAt.toString(),
47
+ before_id: before.id
48
+ };
49
+ }
50
+ const errorText = (cause) => cause instanceof Error ? cause.message : String(cause);
51
+ function createNotifications(options) {
52
+ const { transport } = options;
53
+ const schema = options.schema ?? DEFAULT_SCHEMA;
54
+ const mappers = options.errorMappers ?? [];
55
+ const call = async (fn, args) => transport.call(schema, fn, args);
56
+ function run(fn, args, then) {
57
+ return AsyncResult.from(async () => {
58
+ let value;
59
+ try {
60
+ value = await call(fn, args);
61
+ } catch (cause) {
62
+ const raw = rawError(cause);
63
+ return err(raw ? mapDbError(raw, mappers) : toDbError(cause));
64
+ }
65
+ return ok(await then(value));
66
+ });
67
+ }
68
+ function emit(type, data, tenant) {
69
+ if (!options.events) return;
70
+ emitKitEvent(options.events, type, data, {
71
+ subject: `notifications/${data.notificationId}`,
72
+ ...tenant ? { tenant } : {},
73
+ ...options.actorId ? { actorId: options.actorId } : {},
74
+ ...options.context ? { context: options.context } : {}
75
+ });
76
+ }
77
+ const render = (item, locale) => {
78
+ if (!options.render) return item;
79
+ return {
80
+ ...item,
81
+ text: options.render(item, locale === void 0 ? {} : { locale })
82
+ };
83
+ };
84
+ async function deliverOne(channel, row, maxAttempts) {
85
+ const notification = isRecord(row["notification"]) ? toItem(row["notification"]) : void 0;
86
+ if (!notification) throw new TypeError("claim returned no notification");
87
+ const attempts = Number(row["attempts"] ?? 1);
88
+ const message = {
89
+ deliveryId: String(row["delivery_id"]),
90
+ channel: channel.name,
91
+ attempts,
92
+ userId: String(row["user_id"]),
93
+ email: textOf(row["email"]),
94
+ notification,
95
+ ...options.render ? { text: options.render(notification, {}) } : {}
96
+ };
97
+ try {
98
+ const result = await channel.send(message);
99
+ const status = result?.status ?? "sent";
100
+ await call("complete_notification_delivery", {
101
+ delivery: message.deliveryId,
102
+ status,
103
+ provider: result?.provider ?? null,
104
+ provider_message_id: result?.providerMessageId ?? null
105
+ });
106
+ if (status === "sent") emit("notification.delivered", {
107
+ notificationId: notification.eventId,
108
+ type: notification.type,
109
+ channel: channel.name
110
+ }, notification.tenant);
111
+ return status;
112
+ } catch (cause) {
113
+ const error = errorText(cause);
114
+ await call("complete_notification_delivery", {
115
+ delivery: message.deliveryId,
116
+ status: "pending",
117
+ error,
118
+ max_attempts: maxAttempts
119
+ });
120
+ emit("notification.failed", {
121
+ notificationId: notification.eventId,
122
+ type: notification.type,
123
+ channel: channel.name,
124
+ error
125
+ }, notification.tenant);
126
+ return "failed";
127
+ }
128
+ }
129
+ return {
130
+ send(type, input) {
131
+ return AsyncResult.from(async () => {
132
+ const schemaOf = Object.hasOwn(options.types, type) ? options.types[type] : void 0;
133
+ if (schemaOf === void 0) return err(dbError("validation", `Unknown notification type "${type}"`, {
134
+ issues: [{
135
+ path: ["type"],
136
+ message: `Unknown type "${type}"`
137
+ }],
138
+ hint: "NOTIFICATION_TYPE_UNKNOWN"
139
+ }));
140
+ const data = await validate(schemaOf, input.data, `${type} data`);
141
+ if (!data.ok) return err(data.error);
142
+ const sent = await run("notify", { notification: {
143
+ type,
144
+ data: data.data,
145
+ recipients: input.recipients ?? [],
146
+ tenant: input.tenant,
147
+ subject_type: input.subject?.type,
148
+ subject_id: input.subject?.id,
149
+ subject_label: input.subject?.label,
150
+ summary: input.summary,
151
+ action_path: input.actionPath,
152
+ priority: input.priority,
153
+ key: input.key,
154
+ channels: input.channels,
155
+ include_actor: input.includeActor,
156
+ activity: input.activity,
157
+ resolved: input.resolved,
158
+ actor: input.actorId
159
+ } }, (value) => value === null ? null : String(value));
160
+ if (!sent.ok || sent.data === null) return sent;
161
+ const recipients = input.recipients ?? [];
162
+ emit("notification.created", {
163
+ notificationId: sent.data,
164
+ type,
165
+ recipientIds: recipients
166
+ }, input.tenant ?? null);
167
+ if (options.onSent) try {
168
+ await options.onSent({
169
+ id: sent.data,
170
+ type,
171
+ tenant: input.tenant ?? null,
172
+ recipients
173
+ });
174
+ } catch (cause) {
175
+ return err(toDbError(cause));
176
+ }
177
+ return sent;
178
+ });
179
+ },
180
+ list(listOptions = {}) {
181
+ return run("list_notifications", {
182
+ tenant: listOptions.tenant ?? null,
183
+ status: listOptions.status ?? "all",
184
+ types: listOptions.types ?? null,
185
+ ...cursorOf(listOptions.before),
186
+ max_items: listOptions.limit ?? 50
187
+ }, (value) => (Array.isArray(value) ? value : []).filter(isRecord).map((row) => render(toItem(row), listOptions.locale)));
188
+ },
189
+ counts(countOptions = {}) {
190
+ return run("notification_counts", {
191
+ tenant: countOptions.tenant ?? null,
192
+ actionable: options.actionable ?? []
193
+ }, (value) => ({
194
+ unread: Number(isRecord(value) ? value["unread"] ?? 0 : 0),
195
+ actionable: Number(isRecord(value) ? value["actionable"] ?? 0 : 0)
196
+ }));
197
+ },
198
+ markRead(readOptions = {}) {
199
+ return run("mark_notifications_read", {
200
+ ids: readOptions.ids ?? null,
201
+ tenant: readOptions.tenant ?? null
202
+ }, Number);
203
+ },
204
+ dismiss(ids) {
205
+ return run("dismiss_notifications", { ids }, Number);
206
+ },
207
+ resolve(input) {
208
+ return run("resolve_notifications", {
209
+ type: input.type,
210
+ subject_type: input.subject.type,
211
+ subject_id: input.subject.id,
212
+ tenant: input.tenant ?? null
213
+ }, Number);
214
+ },
215
+ subscribe(input) {
216
+ return run("set_notification_subscription", {
217
+ subject_type: input.subject.type,
218
+ subject_id: input.subject.id,
219
+ level: input.level,
220
+ tenant: input.tenant ?? null,
221
+ member: input.userId ?? null
222
+ }, () => void 0);
223
+ },
224
+ setPreference(input) {
225
+ return run("set_notification_preference", {
226
+ type: input.type,
227
+ channel: input.channel,
228
+ enabled: input.enabled,
229
+ tenant: input.tenant ?? null
230
+ }, () => void 0);
231
+ },
232
+ async deliver(deliverOptions = {}) {
233
+ const wanted = deliverOptions.channels;
234
+ const channels = (options.channels ?? []).filter((channel) => !wanted || wanted.includes(channel.name));
235
+ const deadline = deliverOptions.budgetMs === void 0 ? Number.POSITIVE_INFINITY : Date.now() + deliverOptions.budgetMs;
236
+ const batch = deliverOptions.batch ?? 50;
237
+ const counts = {
238
+ sent: 0,
239
+ skipped: 0,
240
+ failed: 0
241
+ };
242
+ for (const channel of channels) while (Date.now() < deadline) {
243
+ const claimed = await call("claim_notification_deliveries", {
244
+ channel: channel.name,
245
+ max_items: batch,
246
+ lease: deliverOptions.lease ?? "5 minutes",
247
+ max_attempts: deliverOptions.maxAttempts ?? 5
248
+ });
249
+ const rows = (Array.isArray(claimed) ? claimed : []).filter(isRecord);
250
+ for (const row of rows) counts[await deliverOne(channel, row, deliverOptions.maxAttempts ?? 5)] += 1;
251
+ if (rows.length < batch) break;
252
+ }
253
+ return counts;
254
+ },
255
+ purge(olderThan, batch) {
256
+ return run("purge_notifications", {
257
+ older_than: olderThan ?? null,
258
+ batch: batch ?? null
259
+ }, Number);
260
+ }
261
+ };
262
+ }
263
+ //#endregion
264
+ export { createNotifications, rpcTransport, sqlTransport };
@@ -1,2 +1,2 @@
1
- import { a as ResourceOptions, i as ResourceOperation, n as OpenApiOptions, o as createOpenApi, r as PROBLEM_SCHEMA, t as OpenApiDocument } from "../index-BhHEC04r.js";
1
+ import { a as ResourceOptions, i as ResourceOperation, n as OpenApiOptions, o as createOpenApi, r as PROBLEM_SCHEMA, t as OpenApiDocument } from "../index-C7f8cOBM.js";
2
2
  export { OpenApiDocument, OpenApiOptions, PROBLEM_SCHEMA, ResourceOperation, ResourceOptions, createOpenApi };
@@ -1,6 +1,6 @@
1
- import { n as PROBLEM_TYPE_BASE, t as PROBLEM_CONTENT_TYPE } from "../problem-BihjPR0t.js";
1
+ import { n as PROBLEM_TYPE_BASE, t as PROBLEM_CONTENT_TYPE } from "../problem-BW1KhzSG.js";
2
2
  import { t as SPEC_PINS } from "../spec-pins-C3oc-Tdp.js";
3
- import { t as buildJsonSchema } from "../json-schema-DP6V7cbr.js";
3
+ import { t as buildJsonSchema } from "../json-schema-m1fiwKgN.js";
4
4
  //#region src/openapi/index.ts
5
5
  /** JSON Schema for RFC 9457 Problem Details as better-supabase sends them. */
6
6
  const PROBLEM_SCHEMA = {