better-supabase 0.4.0 → 0.5.1

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 (180) hide show
  1. package/README.md +135 -16
  2. package/dist/actor-DaJjqD5R.d.ts +43 -0
  3. package/dist/channel-FQW-CnGR.d.ts +70 -0
  4. package/dist/cli/bin.js +1 -1
  5. package/dist/cli/codemod-DqV7612m.js +388 -0
  6. package/dist/cli/{config-D9R0o1LH.js → config-CPVpXP5I.js} +78 -8
  7. package/dist/cli/{config-SSKSTUl2.js → config-dtoekM0v.js} +1 -1
  8. package/dist/cli/{read-sets-CMa29t9a.js → diff-SjkO5rNr.js} +1 -220
  9. package/dist/cli/{dist-CFgGknmZ.js → dist-DBPdlPYK.js} +4 -4
  10. package/dist/cli/{dist-B1MvLv-M.js → dist-DreZ24Sz.js} +1 -1
  11. package/dist/cli/{dist-Du7VnEUG.js → dist-xn4wlboc.js} +112 -1
  12. package/dist/cli/{doctor-BOym3mbL.js → doctor-C_A-KEAz.js} +533 -50
  13. package/dist/cli/{env-BZ0uy8XK.js → env-DmKv_G4A.js} +1 -1
  14. package/dist/cli/gen-CIdSQZad.js +2 -0
  15. package/dist/cli/{gen-Ccn_p90L.js → gen-CjElD1Vw.js} +321 -88
  16. package/dist/cli/index.d.ts +1 -1
  17. package/dist/cli/index.js +4 -4
  18. package/dist/cli/{init-ClnxRYBF.js → init-DMa6M7hV.js} +2 -2
  19. package/dist/cli/{introspect-DxqfkVIv.js → introspect-BKlaIu_M.js} +2 -2
  20. package/dist/cli/{openapi-8WHjxiTJ.js → openapi-Cp5Fyta-.js} +1 -1
  21. package/dist/cli/read-sets-VQcPgxU9.js +436 -0
  22. package/dist/cli/{run-CP20LKk_.js → run-Bo_NLT5n.js} +16 -12
  23. package/dist/cli/{seed-5J6eu_Xg.js → seed-CUlqWh-8.js} +1 -1
  24. package/dist/cli/sql-CVx-1O8G.js +373 -0
  25. package/dist/cli/{version-5mW_8sY1.js → version-BCtlgDQx.js} +1 -1
  26. package/dist/client/index.d.ts +1 -1
  27. package/dist/client/index.js +3 -10
  28. package/dist/clone-BfgmLU90.js +21 -0
  29. package/dist/{compiler-DB3aSFOA.d.ts → compiler-BXH_aZ7N.d.ts} +2 -2
  30. package/dist/config/index.d.ts +4 -4
  31. package/dist/config/index.js +4 -4
  32. package/dist/{config-C1CYcm6o.d.ts → config-Bv7gvfzC.d.ts} +185 -11
  33. package/dist/{config-CbvFmGD8.js → config-DreERlKo.js} +1 -1
  34. package/dist/{define-CRS2MRV3.d.ts → define-Bp_BZa5h.d.ts} +11 -7
  35. package/dist/{define-Bh0AqTik.js → define-cNKzqwL_.js} +202 -16
  36. package/dist/edge/index.d.ts +16 -5
  37. package/dist/edge/index.js +10 -7
  38. package/dist/{entitlements-Dm-FHpNH.d.ts → entitlements-BiJZ4t49.d.ts} +1 -1
  39. package/dist/env/index.d.ts +2 -2
  40. package/dist/env/index.js +34 -1
  41. package/dist/{errors-L0xK41y_.js → errors-DtwXa6b8.js} +1 -0
  42. package/dist/events/index.d.ts +3 -2
  43. package/dist/events/index.js +65 -12
  44. package/dist/events-aIO7vsIk.d.ts +230 -0
  45. package/dist/{result-CqEsbgYs.d.ts → executor-BunPOO-k.d.ts} +200 -1
  46. package/dist/{executor-CZ7ImRiD.d.ts → executor-Cghdstwu.d.ts} +1 -2
  47. package/dist/{executor-CVt6dD0B.js → executor-XycESQPH.js} +3 -3
  48. package/dist/hono/index.d.ts +17 -6
  49. package/dist/hono/index.js +40 -5
  50. package/dist/impersonation-BgSHAYLf.js +133 -0
  51. package/dist/{resolve-BAqQcwMW.d.ts → impersonation-BmePsuxz.d.ts} +36 -6
  52. package/dist/{index-BhHEC04r.d.ts → index-C7f8cOBM.d.ts} +2 -2
  53. package/dist/{index-Dy6kuF0P.d.ts → index-CLg87OBW.d.ts} +6 -1
  54. package/dist/{index-Cf-D2jk1.d.ts → index-CWMDdEr4.d.ts} +2 -2
  55. package/dist/{index-D5IfUIDh.d.ts → index-DOESKG27.d.ts} +33 -8
  56. package/dist/{index-DFmOpBGC.d.ts → index-DgILJRXj.d.ts} +35 -9
  57. package/dist/{index-RlLXd8dL.d.ts → index-DmXSxbLi.d.ts} +4 -4
  58. package/dist/{index-BBdvo-5d.d.ts → index-Dp2VDwog.d.ts} +49 -6
  59. package/dist/{index-528Ugmyp.d.ts → index-SaTsEfGP.d.ts} +76 -8
  60. package/dist/{index-BAy4tsAP.d.ts → index-fag8VJZ2.d.ts} +3 -4
  61. package/dist/index.d.ts +30 -13
  62. package/dist/index.js +10 -9
  63. package/dist/jobs/index.d.ts +147 -101
  64. package/dist/jobs/index.js +744 -237
  65. package/dist/{json-schema-DP6V7cbr.js → json-schema-m1fiwKgN.js} +22 -1
  66. package/dist/kit-events-CTFDfSwb.js +60 -0
  67. package/dist/kit-transport-B82k1djG.d.ts +33 -0
  68. package/dist/kit-transport-DJlHQUBU.js +55 -0
  69. package/dist/{config-BpxDfnbg.js → kits-CGLLrNPd.js} +37 -9
  70. package/dist/list/index.d.ts +1 -1
  71. package/dist/{live-BHakSWWo.d.ts → live-D9W2M5aO.d.ts} +6 -5
  72. package/dist/mcp/index.d.ts +25 -8
  73. package/dist/mcp/index.js +23 -14
  74. package/dist/{mfa-C7MNQ1Ob.d.ts → mfa-Cu96oJdw.d.ts} +2 -16
  75. package/dist/next/index.d.ts +31 -10
  76. package/dist/next/index.js +136 -27
  77. package/dist/notifications/index.d.ts +162 -0
  78. package/dist/notifications/index.js +264 -0
  79. package/dist/openapi/index.d.ts +1 -1
  80. package/dist/openapi/index.js +2 -2
  81. package/dist/orgs/index.d.ts +132 -0
  82. package/dist/orgs/index.js +245 -0
  83. package/dist/orpc/index.d.ts +28 -8
  84. package/dist/orpc/index.js +16 -3
  85. package/dist/otel/index.d.ts +21 -2
  86. package/dist/otel/index.js +50 -9
  87. package/dist/permdock-sql-C2Inc6Xc.d.ts +29 -0
  88. package/dist/permdock-sql-DgOfeixi.js +51 -0
  89. package/dist/plugin-DwARMuQp.js +59 -0
  90. package/dist/{plugin-bNhvJwRh.d.ts → plugin-o144f322.d.ts} +17 -5
  91. package/dist/plugins/actor/index.d.ts +5 -2
  92. package/dist/plugins/actor/index.js +22 -8
  93. package/dist/plugins/rules/index.d.ts +2 -2
  94. package/dist/plugins/rules/index.js +15 -10
  95. package/dist/plugins/soft-delete/index.d.ts +3 -3
  96. package/dist/plugins/soft-delete/index.js +14 -5
  97. package/dist/plugins/tenant/index.d.ts +2 -2
  98. package/dist/plugins/tenant/index.js +38 -20
  99. package/dist/plugins/timestamps/index.d.ts +1 -1
  100. package/dist/plugins/timestamps/index.js +4 -3
  101. package/dist/plugins/validation/index.d.ts +2 -2
  102. package/dist/plugins/validation/index.js +26 -6
  103. package/dist/{pool-DFwEQwmN.d.ts → pool-C9voM4Kw.d.ts} +17 -6
  104. package/dist/postgres/index.d.ts +5 -5
  105. package/dist/postgres/index.js +20 -11
  106. package/dist/{postgrest-Bc-0Xw62.d.ts → postgrest-CruYKAvV.d.ts} +1 -1
  107. package/dist/{problem-BihjPR0t.js → problem-BW1KhzSG.js} +2 -1
  108. package/dist/{problem-U8UEKccv.d.ts → problem-Blxe8S31.d.ts} +1 -1
  109. package/dist/query/index.d.ts +2 -2
  110. package/dist/query/index.js +3 -3
  111. package/dist/{query-BMMSWbI7.js → query-CCbo44lG.js} +2 -2
  112. package/dist/queue-BYUCjr59.d.ts +252 -0
  113. package/dist/react/index.d.ts +8 -7
  114. package/dist/react/index.js +4 -3
  115. package/dist/react/server.d.ts +14 -3
  116. package/dist/react/server.js +10 -2
  117. package/dist/react/session.d.ts +7 -1
  118. package/dist/react/session.js +9 -1
  119. package/dist/{react-CQHMaESR.js → react-C_s7kiNz.js} +103 -11
  120. package/dist/{read-set-FWK8qQ58.js → read-set-DQFt1N_Y.js} +22 -1
  121. package/dist/realtime/index.d.ts +3 -3
  122. package/dist/realtime/index.js +5 -4
  123. package/dist/{refresh-BwYkyNOQ.d.ts → refresh-CFPX0Qft.d.ts} +1 -1
  124. package/dist/{resource-BJcaIfxi.d.ts → resource-DNRFP7KR.d.ts} +5 -5
  125. package/dist/{resource-My_y-Syh.js → resource-mqfJW4YF.js} +22 -7
  126. package/dist/respond-BR1fJWOB.d.ts +377 -0
  127. package/dist/{result-Bj6yt33D.js → result-BKiliPY7.js} +1 -1
  128. package/dist/server/index.d.ts +15 -12
  129. package/dist/server/index.js +11 -17
  130. package/dist/{server-BA75B0mT.js → server-B1mZ7CgQ.js} +259 -37
  131. package/dist/{shared-DnoCdMDr.js → shared-D1dRUrAg.js} +16 -2
  132. package/dist/signers-Cc6r98SW.d.ts +162 -0
  133. package/dist/{simplify-Cqvfr9dj.js → simplify-_0oUKqCd.js} +4 -115
  134. package/dist/sql/index.d.ts +283 -11
  135. package/dist/sql/index.js +8705 -775
  136. package/dist/{sql-B34HU2DI.js → sql-Coh6n4cC.js} +9 -2
  137. package/dist/ssr/index.d.ts +4 -4
  138. package/dist/ssr/index.js +1 -2
  139. package/dist/{standard-BRyBGTtp.js → standard-D7r6dMUu.js} +6 -3
  140. package/dist/{standard-BsaCYQJc.d.ts → standard-J4YgMOLm.d.ts} +1 -1
  141. package/dist/{stats-C_YxA2r1.js → stats-Bqob3WqT.js} +4 -4
  142. package/dist/storage/index.d.ts +4 -4
  143. package/dist/storage/index.js +155 -26
  144. package/dist/support-CEPwlw2G.d.ts +89 -0
  145. package/dist/support-view-B8SlToQX.d.ts +17 -0
  146. package/dist/support-view-g4YLBeqX.js +18 -0
  147. package/dist/support-wHnK6zp-.js +293 -0
  148. package/dist/{permdock-sql-C4KFzLc9.js → template-TxrGI5jR.js} +2 -31
  149. package/dist/{temporal-BwshQk0W.js → temporal-CVlIV2DZ.js} +17 -4
  150. package/dist/{temporal-required-CBNHT6ni.js → temporal-required-CAt4Dc9Z.js} +2 -2
  151. package/dist/testing/index.d.ts +132 -10
  152. package/dist/testing/index.js +476 -8
  153. package/dist/{types-DONv9SHN.d.ts → types-Dnf_wRDz.d.ts} +26 -3
  154. package/dist/user-change-CHFEUro1.js +49 -0
  155. package/dist/{webhooks-Bh_Jf_-l.js → verify-Ce49ETPz.js} +5 -5
  156. package/dist/{version-Bmf7_FzM.js → version-DOYTYwHP.js} +1 -1
  157. package/dist/{mfa-byfjruR9.js → view-Bn6HNKUe.js} +97 -98
  158. package/dist/{view-B64o81wp.d.ts → view-CbgObAeb.d.ts} +22 -31
  159. package/dist/webhooks/index.d.ts +135 -4
  160. package/dist/webhooks/index.js +588 -2
  161. package/dist/wire-D2dT86vi.js +35 -0
  162. package/package.json +27 -19
  163. package/schemas/config-v1.json +131 -1
  164. package/schemas/doctor-report-v1.json +11 -0
  165. package/skills/better-supabase/SKILL.md +9 -6
  166. package/skills/better-supabase/references/plugins.md +6 -3
  167. package/skills/better-supabase/references/troubleshooting.md +1 -0
  168. package/skills/better-supabase-api/SKILL.md +9 -2
  169. package/skills/better-supabase-api/references/adapters.md +11 -8
  170. package/skills/better-supabase-auth/SKILL.md +18 -7
  171. package/skills/better-supabase-auth/references/permdock.md +78 -11
  172. package/dist/cli/gen-D5cLCULx.js +0 -2
  173. package/dist/cli/sql-D4VbqmXi.js +0 -199
  174. package/dist/events-B21UOhvP.d.ts +0 -84
  175. package/dist/executor-B5IoUxLE.d.ts +0 -195
  176. package/dist/invalidate-CgxmRamI.js +0 -24
  177. package/dist/permdock-sql-DImJULY-.d.ts +0 -17
  178. package/dist/plugin-BqR6wKMB.js +0 -23
  179. package/dist/respond-DgIbdrt8.d.ts +0 -194
  180. package/dist/view-BrJI2LKp.js +0 -45
@@ -32,6 +32,18 @@ function scalarKind(column) {
32
32
  if (column.type === "record" || column.type === "void") return { kind: "unknown" };
33
33
  return { kind: "string" };
34
34
  }
35
+ /** App names of the columns that plugins fill from the table's flags. */
36
+ function managedColumns(table) {
37
+ const { timestamps, actor, softDelete } = table.flags;
38
+ return new Set([
39
+ timestamps?.createdAt,
40
+ timestamps?.updatedAt,
41
+ actor?.createdBy,
42
+ actor?.updatedBy,
43
+ actor?.impersonatedBy,
44
+ softDelete
45
+ ].filter((name) => name !== void 0));
46
+ }
35
47
  /**
36
48
  * The generated row type of a field (its element type for arrays). Storage
37
49
  * path columns validate as strings and are cast to their `StoragePath` brand.
@@ -43,6 +55,7 @@ function rowType(table, plan) {
43
55
  /** The fields a variant accepts; read-only columns are left out of writes. */
44
56
  function fieldsFor(table, variant, input) {
45
57
  const fields = [];
58
+ const managed = managedColumns(table);
46
59
  for (const [name, column] of Object.entries(table.columns)) {
47
60
  if (variant === "Insert" && (column.generated || column.insertable === false)) continue;
48
61
  if (variant === "Update" && (column.generated || column.updatable === false)) continue;
@@ -52,7 +65,8 @@ function fieldsFor(table, variant, input) {
52
65
  scalar: scalarKind(column),
53
66
  nullable: column.nullable,
54
67
  optional: variant === "Update" || variant === "Insert" && (column.nullable || column.hasDefault),
55
- customJson: Boolean(column.json && input.config.json[`${table.name}.${column.db}`])
68
+ customJson: Boolean(column.json && input.config.json[`${table.name}.${column.db}`]),
69
+ managed: managed.has(name)
56
70
  });
57
71
  }
58
72
  return fields;
@@ -130,6 +144,13 @@ function scalar(kind) {
130
144
  }
131
145
  }
132
146
  function property(plan) {
147
+ const schema = nullable(plan);
148
+ return plan.managed ? {
149
+ ...schema,
150
+ readOnly: true
151
+ } : schema;
152
+ }
153
+ function nullable(plan) {
133
154
  let schema = scalar(plan.scalar);
134
155
  if (plan.column.array) schema = {
135
156
  type: "array",
@@ -0,0 +1,60 @@
1
+ import { t as cloneValue } from "./clone-BfgmLU90.js";
2
+ import { n as nowInstant } from "./temporal-CVlIV2DZ.js";
3
+ //#region src/core/kit-events.ts
4
+ /**
5
+ * Fixed OpenTelemetry attribute names for kit work, so dashboards and
6
+ * queries can rely on them.
7
+ */
8
+ const KIT_ATTRIBUTES = {
9
+ event: "better_supabase.kit.event",
10
+ tenant: "better_supabase.tenant",
11
+ actor: "better_supabase.actor.id",
12
+ supportSessionId: "better_supabase.support.session_id",
13
+ orgId: "better_supabase.org.id",
14
+ invitationId: "better_supabase.invitation.id",
15
+ notificationType: "better_supabase.notification.type",
16
+ webhookEndpointId: "better_supabase.webhook.endpoint_id"
17
+ };
18
+ /** The `KIT_ATTRIBUTES` an event carries. */
19
+ function kitEventAttributes(event) {
20
+ const attributes = { [KIT_ATTRIBUTES.event]: event.type };
21
+ if (event.tenant) attributes[KIT_ATTRIBUTES.tenant] = event.tenant;
22
+ if (event.actorId) attributes[KIT_ATTRIBUTES.actor] = event.actorId;
23
+ const data = Object.fromEntries(Object.entries(event.data));
24
+ const add = (key, value) => {
25
+ if (typeof value === "string" && value.length > 0) attributes[key] = value;
26
+ };
27
+ add(KIT_ATTRIBUTES.supportSessionId, data["sessionId"]);
28
+ add(KIT_ATTRIBUTES.orgId, data["organizationId"]);
29
+ add(KIT_ATTRIBUTES.invitationId, data["invitationId"]);
30
+ add(KIT_ATTRIBUTES.notificationType, data["type"]);
31
+ add(KIT_ATTRIBUTES.webhookEndpointId, data["endpointId"]);
32
+ return attributes;
33
+ }
34
+ /**
35
+ * Emits a kit event on `events` with a copy of `data`. Does nothing without
36
+ * a `kit` handler, so kits call it unconditionally.
37
+ */
38
+ function emitKitEvent(events, type, data, meta = {}) {
39
+ if (!events.has("kit")) return;
40
+ const event = {
41
+ ...meta,
42
+ type,
43
+ data: cloneValue(data),
44
+ time: nowInstant()
45
+ };
46
+ events.emit("kit", event);
47
+ }
48
+ const matches = (pattern, type) => pattern.endsWith(".*") ? type.startsWith(pattern.slice(0, -1)) : pattern === type;
49
+ /**
50
+ * Listens to the kit events that match `pattern` (`'support.started'` or
51
+ * `'support.*'`). Returns a function that stops listening.
52
+ */
53
+ function onKitEvent(betterSupabase, pattern, handler) {
54
+ return betterSupabase.events.on("kit", (event) => {
55
+ if (!matches(pattern, event.type)) return;
56
+ handler(event);
57
+ });
58
+ }
59
+ //#endregion
60
+ export { onKitEvent as i, emitKitEvent as n, kitEventAttributes as r, KIT_ATTRIBUTES as t };
@@ -0,0 +1,33 @@
1
+ import { t as SqlClient } from "./executor-Cghdstwu.js";
2
+ //#region src/core/kit-transport.d.ts
3
+ /**
4
+ * Calls a kit function as the current user and resolves with its result.
5
+ * Rejects with the raw database error (a `pg` error or a PostgREST error
6
+ * object), which the kit maps to a `DbError`.
7
+ */
8
+ interface KitTransport {
9
+ call(schema: string, fn: string, args: Readonly<Record<string, unknown>>): Promise<unknown>;
10
+ }
11
+ /**
12
+ * Calls the functions over Postgres, as the user `client` runs as:
13
+ * `sqlTransport(postgres.asUser(ctx.auth.claims))`. Works for any schema.
14
+ */
15
+ declare function sqlTransport(client: SqlClient): KitTransport;
16
+ interface RpcResponse {
17
+ readonly data: unknown;
18
+ readonly error: unknown;
19
+ }
20
+ /** The part of a supabase-js client `rpcTransport` uses. */
21
+ interface RpcClient {
22
+ schema(name: string): {
23
+ rpc(fn: string, args: Record<string, unknown>): PromiseLike<RpcResponse>;
24
+ };
25
+ }
26
+ /**
27
+ * Calls the functions over the Data API with the user's session. The kit
28
+ * schema must be exposed (`[api] schemas` in `config.toml`), or the modules
29
+ * installed in `public` (`kits.<module>.schema`).
30
+ */
31
+ declare function rpcTransport(client: RpcClient): KitTransport;
32
+ //#endregion
33
+ export { sqlTransport as i, RpcClient as n, rpcTransport as r, KitTransport as t };
@@ -0,0 +1,55 @@
1
+ import { r as sqlIdent } from "./template-TxrGI5jR.js";
2
+ //#region src/core/kit-transport.ts
3
+ /**
4
+ * Calls the functions over Postgres, as the user `client` runs as:
5
+ * `sqlTransport(postgres.asUser(ctx.auth.claims))`. Works for any schema.
6
+ */
7
+ function sqlTransport(client) {
8
+ return { async call(schema, fn, args) {
9
+ const entries = Object.entries(args).filter(([, value]) => value !== void 0);
10
+ const params = entries.map(([, value]) => value !== null && typeof value === "object" && !Array.isArray(value) ? JSON.stringify(value) : value);
11
+ const list = entries.map(([name], index) => `${sqlIdent(name)} => $${String(index + 1)}`).join(", ");
12
+ const [row] = await client.queryRaw(`select ${sqlIdent(schema)}.${sqlIdent(fn)}(${list}) as value`, params);
13
+ return row?.value ?? null;
14
+ } };
15
+ }
16
+ /**
17
+ * Calls the functions over the Data API with the user's session. The kit
18
+ * schema must be exposed (`[api] schemas` in `config.toml`), or the modules
19
+ * installed in `public` (`kits.<module>.schema`).
20
+ */
21
+ function rpcTransport(client) {
22
+ return { async call(schema, fn, args) {
23
+ const defined = Object.fromEntries(Object.entries(args).filter(([, value]) => value !== void 0));
24
+ const { data, error } = await client.schema(schema).rpc(fn, defined);
25
+ if (error) {
26
+ const message = typeof error === "object" && "message" in error ? String(error.message) : String(error);
27
+ throw Object.assign(new Error(message), error);
28
+ }
29
+ return data;
30
+ } };
31
+ }
32
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
33
+ /** The Postgres or PostgREST error a transport rejected with, if it is one. */
34
+ function rawError(cause) {
35
+ if (!isRecord(cause)) return void 0;
36
+ const field = (key) => {
37
+ const value = cause[key];
38
+ return typeof value === "string" ? value : void 0;
39
+ };
40
+ if (field("code") === void 0) return void 0;
41
+ const raw = {};
42
+ for (const key of [
43
+ "message",
44
+ "code",
45
+ "details",
46
+ "hint",
47
+ "constraint"
48
+ ]) {
49
+ const value = field(key);
50
+ if (value !== void 0) raw[key] = value;
51
+ }
52
+ return raw;
53
+ }
54
+ //#endregion
55
+ export { rpcTransport as n, sqlTransport as r, rawError as t };
@@ -9,14 +9,16 @@ function resolveJsonSchema(source) {
9
9
  return jsonSchema.input({ target: "draft-2020-12" });
10
10
  }
11
11
  function entitlementsOf(config = {}) {
12
- const customer = config.customer ?? "organizations.stripe_customer_id";
13
- const dot = customer.lastIndexOf(".");
14
- if (dot <= 0) throw new TypeError(`entitlements.customer must be "table.column", got "${customer}"`);
12
+ const customer = config.customer;
13
+ const dot = customer?.lastIndexOf(".") ?? -1;
14
+ if (customer !== void 0 && dot <= 0) throw new TypeError(`entitlements.customer must be "table.column", got "${customer}"`);
15
15
  return {
16
- table: customer.slice(0, dot),
17
- column: customer.slice(dot + 1),
16
+ ...customer === void 0 ? {} : {
17
+ table: customer.slice(0, dot),
18
+ column: customer.slice(dot + 1)
19
+ },
18
20
  key: config.key ?? "id",
19
- permdock: config.permdock === false ? false : { scope: config.permdock?.scope ?? "organization" }
21
+ permdock: config.permdock === false ? false : config.permdock?.scope === void 0 ? {} : { scope: config.permdock.scope }
20
22
  };
21
23
  }
22
24
  function vectorSearchOf(config = {}) {
@@ -86,7 +88,8 @@ function resolveConfig(config, root) {
86
88
  tenant: pick(config.plugins?.tenant, { column: "organization_id" }),
87
89
  actor: pick(config.plugins?.actor, {
88
90
  createdBy: "created_by",
89
- updatedBy: "updated_by"
91
+ updatedBy: "updated_by",
92
+ impersonatedBy: "impersonated_by"
90
93
  })
91
94
  },
92
95
  claims: {
@@ -95,7 +98,10 @@ function resolveConfig(config, root) {
95
98
  },
96
99
  buckets: config.buckets ?? {},
97
100
  topics: config.topics ?? {},
98
- realtime: { tables: config.realtime?.tables ?? [] },
101
+ realtime: {
102
+ tables: config.realtime?.tables ?? [],
103
+ global: config.realtime?.global ?? []
104
+ },
99
105
  entitlements: entitlementsOf(config.entitlements),
100
106
  permdock: {
101
107
  manifest: config.permdock?.manifest ?? "permdock.manifest.json",
@@ -108,6 +114,7 @@ function resolveConfig(config, root) {
108
114
  testsDir: config.sql?.testsDir ?? "supabase/tests",
109
115
  kit: config.sql?.kit ?? []
110
116
  },
117
+ kits: config.kits ?? {},
111
118
  seed: {
112
119
  entry: config.seed?.entry ?? "supabase/seed.ts",
113
120
  output: config.seed?.output ?? "supabase/seeds/000_better_supabase.sql"
@@ -126,4 +133,25 @@ function resolveConfig(config, root) {
126
133
  };
127
134
  }
128
135
  //#endregion
129
- export { resolveJsonSchema as i, defineConfig as n, resolveConfig as r, CONFIG_SCHEMA_URL as t };
136
+ //#region src/config/kits.ts
137
+ /** URL tenancy: the tenant comes from the request (a slug), not a stored setting. */
138
+ const DEFAULT_ACTIVE_TENANT = "resolver";
139
+ const KIT_SCHEMA = "better_supabase";
140
+ function resolveKitModule(config = {}) {
141
+ return {
142
+ mode: config.mode ?? "managed",
143
+ schema: config.schema ?? KIT_SCHEMA,
144
+ tables: config.tables ?? {},
145
+ columns: config.columns ?? {},
146
+ ...config.idType === void 0 ? {} : { idType: config.idType },
147
+ permissions: config.permissions ?? {},
148
+ options: config.options ?? {},
149
+ hooks: {
150
+ schema: config.hooks?.schema ?? "public",
151
+ functions: config.hooks?.functions ?? {}
152
+ },
153
+ events: config.events ?? true
154
+ };
155
+ }
156
+ //#endregion
157
+ export { resolveConfig as a, defineConfig as i, resolveKitModule as n, resolveJsonSchema as o, CONFIG_SCHEMA_URL as r, DEFAULT_ACTIVE_TENANT as t };
@@ -1,2 +1,2 @@
1
- import { a as ListPage, c as ListParsers, d as ListQueryInput, f as ListSearchParams, g as defineListQuery, h as UNSET, i as ListExtra, l as ListQuery, m as ParserSpec, n as FacetInfo, o as ListPagination, p as OpenApiParameter, r as ListDefinition, s as ListParseResult, t as FacetCounts, u as ListQueryConfig } from "../index-BAy4tsAP.js";
1
+ import { a as ListPage, c as ListParsers, d as ListQueryInput, f as ListSearchParams, g as defineListQuery, h as UNSET, i as ListExtra, l as ListQuery, m as ParserSpec, n as FacetInfo, o as ListPagination, p as OpenApiParameter, r as ListDefinition, s as ListParseResult, t as FacetCounts, u as ListQueryConfig } from "../index-fag8VJZ2.js";
2
2
  export { type FacetCounts, type FacetInfo, type ListDefinition, type ListExtra, type ListPage, type ListPagination, type ListParseResult, type ListParsers, type ListQuery, type ListQueryConfig, type ListQueryInput, type ListSearchParams, type OpenApiParameter, type ParserSpec, UNSET, defineListQuery };
@@ -1,7 +1,7 @@
1
- import { A as SchemaMeta, N as TableKey, O as Row, n as AnyModels, t as AnyFunctions, x as PermdockTopicPolicy } from "./types-DONv9SHN.js";
2
- import { b as ValidationIssue, o as Result, p as DbError, t as AsyncResult } from "./result-CqEsbgYs.js";
3
- import { Z as QuerySpec, t as BetterSupabase } from "./define-CRS2MRV3.js";
4
- import { t as PermdockCatalog } from "./permdock-sql-DImJULY-.js";
1
+ import { P as TableKey, S as PermdockTopicPolicy, j as SchemaMeta, k as Row, n as AnyFunctions, r as AnyModels } from "./types-Dnf_wRDz.js";
2
+ import { _ as DbError, a as AsyncResult, u as Result, w as ValidationIssue } from "./executor-BunPOO-k.js";
3
+ import { Z as QuerySpec, t as BetterSupabase } from "./define-Bp_BZa5h.js";
4
+ import { n as PermdockCatalog } from "./permdock-sql-C2Inc6Xc.js";
5
5
  import { n as TemplateValues, t as TemplateParams } from "./template-mbvlysOo.js";
6
6
  import { RealtimeChannel, SupabaseClient } from "@supabase/supabase-js";
7
7
  import { StandardSchemaV1 } from "@standard-schema/spec";
@@ -41,7 +41,8 @@ interface TopicOptions<E extends EventSchemas> {
41
41
  readonly permdock?: PermdockTopicPolicy;
42
42
  /**
43
43
  * PermDock's `permissions.catalog.json`. With it, a `permdock` policy
44
- * naming a permission with `rowConditions: true` throws.
44
+ * naming a permission without `rowConditions: false` (including one the
45
+ * catalog doesn't list) throws.
45
46
  */
46
47
  readonly catalog?: PermdockCatalog;
47
48
  /** Let clients broadcast on the topic, not only receive. Defaults to `false`. */
@@ -1,7 +1,8 @@
1
- import { N as TableKey, n as AnyModels, t as AnyFunctions } from "../types-DONv9SHN.js";
2
- import { t as BetterSupabase } from "../define-CRS2MRV3.js";
3
- import { f as ServerContext, n as GuardOptions, p as ServerOptions, u as BetterServer } from "../respond-DgIbdrt8.js";
4
- import { a as ResourceRouteOptions } from "../resource-BJcaIfxi.js";
1
+ import { P as TableKey, n as AnyFunctions, r as AnyModels } from "../types-Dnf_wRDz.js";
2
+ import { t as BetterSupabase } from "../define-Bp_BZa5h.js";
3
+ import { f as ServerContext, n as GuardOptions, p as ServerOptions, u as BetterServer } from "../respond-BR1fJWOB.js";
4
+ import { i as ResourceOperation } from "../index-C7f8cOBM.js";
5
+ import { a as ResourceRouteOptions } from "../resource-DNRFP7KR.js";
5
6
  import { StandardSchemaV1 } from "@standard-schema/spec";
6
7
  //#region src/mcp/mcp.d.ts
7
8
  export declare const MCP_PROTOCOL_VERSION: string;
@@ -38,7 +39,7 @@ interface McpTool<M extends AnyModels, F extends AnyFunctions, E, C = unknown, P
38
39
  /** What the `authorize` and `visible` hooks see of a tool. */
39
40
  interface ToolRef {
40
41
  readonly info: ToolInfo;
41
- /** The tool's `meta`, e.g. a PermDock permission. `undefined` for table tools. */
42
+ /** The tool's `meta`, e.g. a PermDock permission. For table tools, the resource's `meta` for that operation. */
42
43
  readonly meta: unknown;
43
44
  }
44
45
  /** The outcome of `authorize`. `scopes` turns a refusal into an OAuth challenge. */
@@ -66,8 +67,17 @@ interface ToolDefinition<M extends AnyModels, F extends AnyFunctions, E, I, C =
66
67
  readonly meta?: unknown;
67
68
  run(args: I, ctx: ToolContext<M, F, E, C, P>): unknown;
68
69
  }
69
- type ToolResources<M extends AnyModels> = { readonly [T in TableKey<M>]?: ResourceRouteOptions<M, T> | true; };
70
- interface McpOptions<M extends AnyModels, F extends AnyFunctions, E, C = unknown, P = unknown> extends ServerOptions, GuardOptions {
70
+ /** A table's tools: the REST resource options, plus `meta` per operation for `authorize` and `visible`. */
71
+ type ToolResourceOptions<M extends AnyModels, T extends TableKey<M>> = ResourceRouteOptions<M, T> & {
72
+ /**
73
+ * Opaque data per operation, e.g. a PermDock permission, passed to
74
+ * `authorize` and `visible` as the table tool's `meta`. Never sent to
75
+ * clients. An operation without an entry gets `undefined`.
76
+ */
77
+ readonly meta?: { readonly [K in ResourceOperation]?: unknown; };
78
+ };
79
+ type ToolResources<M extends AnyModels> = { readonly [T in TableKey<M>]?: ToolResourceOptions<M, T> | true; };
80
+ interface McpOptions<M extends AnyModels, F extends AnyFunctions, E, C = unknown, P = unknown> extends ServerOptions, Omit<GuardOptions, "scopes"> {
71
81
  readonly name: string;
72
82
  readonly version: string;
73
83
  readonly title?: string;
@@ -88,7 +98,14 @@ interface McpOptions<M extends AnyModels, F extends AnyFunctions, E, C = unknown
88
98
  * `insufficient_scope` challenges. `offline_access` is left out: refresh
89
99
  * tokens are between the client and the authorization server.
90
100
  */
101
+ readonly advertisedScopes?: readonly string[];
102
+ /** @deprecated Use `advertisedScopes`; `requiredScopes` is what `guard` enforces. */
91
103
  readonly scopes?: readonly string[];
104
+ /**
105
+ * Scopes a delegated token (an OAuth client or an `act` chain) needs to call
106
+ * the server at all. A missing one answers 403 `insufficient_scope`.
107
+ */
108
+ readonly requiredScopes?: readonly string[];
92
109
  /** Origins allowed to call the server (DNS rebinding protection). Defaults to any. */
93
110
  readonly allowedOrigins?: readonly string[];
94
111
  /**
@@ -146,4 +163,4 @@ export declare function defineTool<M extends AnyModels, F extends AnyFunctions,
146
163
  */
147
164
  export declare function createMcp<M extends AnyModels, D, F extends AnyFunctions, E, C = unknown, P = unknown>(betterSupabase: BetterSupabase<M, D, F, E, C, P>, options: McpOptions<M, F, E, C, P>): BetterMcp<M, F, E, C, P>;
148
165
  //#endregion
149
- export type { BetterMcp, GuardOptions, McpOptions, McpTool, ResourceRouteOptions, ToolAnnotations, ToolContext, ToolDecision, ToolDefinition, ToolInfo, ToolRef, ToolResources, ToolResult };
166
+ export type { BetterMcp, GuardOptions, McpOptions, McpTool, ResourceRouteOptions, ToolAnnotations, ToolContext, ToolDecision, ToolDefinition, ToolInfo, ToolRef, ToolResourceOptions, ToolResources, ToolResult };
package/dist/mcp/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import { n as dbError } from "../errors-L0xK41y_.js";
1
+ import { n as dbError } from "../errors-DtwXa6b8.js";
2
2
  import { t as base64ToText } from "../base64-D2gsioCY.js";
3
- import { t as validate } from "../standard-BRyBGTtp.js";
4
- import { o as toProblem } from "../problem-BihjPR0t.js";
3
+ import { t as validate } from "../standard-D7r6dMUu.js";
4
+ import { a as problemResponse, o as toProblem } from "../problem-BW1KhzSG.js";
5
5
  import { t as SPEC_PINS } from "../spec-pins-C3oc-Tdp.js";
6
- import { t as buildJsonSchema } from "../json-schema-DP6V7cbr.js";
7
- import { a as guard, c as settle, i as defaultExpose, n as extendServer, r as withExtra, t as createServer } from "../server-BA75B0mT.js";
8
- import { t as defineResource } from "../resource-My_y-Syh.js";
6
+ import { t as buildJsonSchema } from "../json-schema-m1fiwKgN.js";
7
+ import { a as withExtra, d as guard, g as jsonResponse, h as jsonReplacer, m as settle, n as createServer, r as extendServer, u as defaultExpose } from "../server-B1mZ7CgQ.js";
8
+ import { t as defineResource } from "../resource-mqfJW4YF.js";
9
9
  import { fromSupabaseUrl, unauthorizedResponse } from "@supabase/server/oauth-protected-resource";
10
10
  //#region src/mcp/mcp.ts
11
11
  const MCP_PROTOCOL_VERSION = SPEC_PINS.mcp;
@@ -215,7 +215,7 @@ function textResult(value, isError = false) {
215
215
  return {
216
216
  content: [{
217
217
  type: "text",
218
- text: typeof value === "string" ? value : JSON.stringify(value ?? null)
218
+ text: typeof value === "string" ? value : JSON.stringify(value ?? null, jsonReplacer)
219
219
  }],
220
220
  ...structured ? { structuredContent: structured } : {},
221
221
  ...isError ? { isError: true } : {}
@@ -258,11 +258,14 @@ function createMcp(betterSupabase, options) {
258
258
  const isVisible = async (tool, ctx) => !options.visible || options.visible(ctx, tool);
259
259
  for (const [table, raw] of Object.entries(options.resources ?? {})) {
260
260
  if (!raw) continue;
261
- const resource = defineResource(betterSupabase, table, raw === true ? {} : raw);
261
+ const key = table;
262
+ const entry = raw;
263
+ const { meta, ...route } = entry === true ? {} : entry;
264
+ const resource = defineResource(betterSupabase, key, route);
262
265
  for (const { info, operation } of tableTools(resource, defs)) {
263
266
  const ref = {
264
267
  info,
265
- meta: void 0
268
+ meta: meta?.[operation]
266
269
  };
267
270
  register({
268
271
  ...ref,
@@ -338,7 +341,7 @@ function createMcp(betterSupabase, options) {
338
341
  const outcome = await invoke(name, args, ctx);
339
342
  return isRefusal(outcome) ? failure(dbError("forbidden", outcome.refusal)) : outcome;
340
343
  };
341
- const scopes = (options.scopes ?? []).filter((scope) => scope !== "offline_access");
344
+ const scopes = (options.advertisedScopes ?? options.scopes ?? []).filter((scope) => scope !== "offline_access");
342
345
  const serverInfo = {
343
346
  name: options.name,
344
347
  version: options.version,
@@ -362,6 +365,12 @@ function createMcp(betterSupabase, options) {
362
365
  response.headers.set("www-authenticate", challenge);
363
366
  return response;
364
367
  };
368
+ /** A missing second factor is no scope problem, and an outage is not the caller's fault. */
369
+ const refuse = (request, denied) => {
370
+ if (denied.kind === "unauthorized") return unauthorized(request);
371
+ if (denied.kind !== "forbidden") return problemResponse(denied, { expose });
372
+ return denied.code === "INSUFFICIENT_AAL" ? rpcError(null, INVALID_REQUEST, denied.message, 403) : forbidden(request, denied.message, denied.scopes);
373
+ };
365
374
  const unsupportedVersion = (id, requested) => rpcError(id, UNSUPPORTED_PROTOCOL_VERSION, "Unsupported protocol version", 400, {
366
375
  supported: [...SUPPORTED_VERSIONS],
367
376
  requested
@@ -391,9 +400,9 @@ function createMcp(betterSupabase, options) {
391
400
  });
392
401
  const header = request.headers.get("mcp-protocol-version");
393
402
  if (header && !SUPPORTED_VERSIONS.includes(header)) return unsupportedVersion(null, header);
394
- const ctx = await server.context(request);
395
- const denied = guard(ctx.auth, options.allow, options.aal);
396
- if (denied) return denied.kind === "unauthorized" ? unauthorized(request) : forbidden(request, denied.message);
403
+ const ctx = await server.context(request, { cookies: false });
404
+ const denied = guard(ctx.auth, options.allow, options.aal, options.requiredScopes);
405
+ if (denied) return refuse(request, denied);
397
406
  let message;
398
407
  try {
399
408
  message = await request.json();
@@ -412,7 +421,7 @@ function createMcp(betterSupabase, options) {
412
421
  request,
413
422
  signal: request.signal
414
423
  });
415
- const reply = (result) => Response.json({
424
+ const reply = (result) => jsonResponse({
416
425
  jsonrpc: "2.0",
417
426
  id: message.id,
418
427
  result: modern ? {
@@ -1,18 +1,4 @@
1
- import { p as DbError } from "./result-CqEsbgYs.js";
2
- //#region src/auth/impersonation.d.ts
3
- /** The admin acting as the user, from the RFC 8693 `act` claim. */
4
- interface Impersonator {
5
- readonly id: string;
6
- readonly reason?: string;
7
- }
8
- /** `actingAs(userId, claims, { actor, reason })`: who acts, and why (recorded by the audit module). */
9
- interface ImpersonationOptions {
10
- readonly actor: string;
11
- readonly reason: string;
12
- }
13
- /** The impersonator in `claims.act`, or `undefined` for a normal session. */
14
- declare function impersonatorOf(claims: Readonly<Record<string, unknown>>): Impersonator | undefined;
15
- //#endregion
1
+ import { _ as DbError } from "./executor-BunPOO-k.js";
16
2
  //#region src/auth/mfa.d.ts
17
3
  /** Authenticator assurance level: `aal2` once a second factor is verified in this session. */
18
4
  type Aal = "aal1" | "aal2";
@@ -41,4 +27,4 @@ declare function checkAal(auth: {
41
27
  readonly kind: string;
42
28
  }, required: Aal): DbError | undefined;
43
29
  //#endregion
44
- export { checkAal as a, impersonatorOf as c, amrOf as i, AmrEntry as n, ImpersonationOptions as o, aalOf as r, Impersonator as s, Aal as t };
30
+ export { checkAal as a, amrOf as i, AmrEntry as n, aalOf as r, Aal as t };
@@ -1,12 +1,13 @@
1
- import { n as AnyModels, t as AnyFunctions } from "../types-DONv9SHN.js";
2
- import { p as DbError } from "../result-CqEsbgYs.js";
3
- import { B as ReadSet, P as DbStats, Z as QuerySpec, l as CacheAdapter, t as BetterSupabase } from "../define-CRS2MRV3.js";
4
- import { n as AmrEntry, s as Impersonator, t as Aal } from "../mfa-C7MNQ1Ob.js";
5
- import { r as AuthState } from "../resolve-BAqQcwMW.js";
6
- import { d as ContextOptions, f as ServerContext, n as GuardOptions, p as ServerOptions, t as AuthKind, u as BetterServer } from "../respond-DgIbdrt8.js";
7
- import { n as toSession, t as AuthSession } from "../view-B64o81wp.js";
8
- import { r as LiveCountSeed, t as CountRunner } from "../live-BHakSWWo.js";
9
- import { n as MembershipClaim, r as hasEntitlement, t as EntitlementKey } from "../entitlements-Dm-FHpNH.js";
1
+ import { n as AnyFunctions, r as AnyModels } from "../types-Dnf_wRDz.js";
2
+ import { _ as DbError } from "../executor-BunPOO-k.js";
3
+ import { B as ReadSet, P as DbStats, Z as QuerySpec, l as CacheAdapter, t as BetterSupabase } from "../define-Bp_BZa5h.js";
4
+ import { n as Impersonator, o as AuthState } from "../impersonation-BmePsuxz.js";
5
+ import { C as SupportOptions, D as supportSessions, E as SupportStartRequest, T as SupportSessions, b as ActiveSupport, d as ContextOptions, f as ServerContext, n as GuardOptions, p as ServerOptions, t as AuthKind, u as BetterServer, w as SupportPolicy } from "../respond-BR1fJWOB.js";
6
+ import { n as AmrEntry, t as Aal } from "../mfa-Cu96oJdw.js";
7
+ import { n as toSession, t as AuthSession } from "../view-CbgObAeb.js";
8
+ import { r as LiveCountSeed, t as CountRunner } from "../live-D9W2M5aO.js";
9
+ import { n as supportOf, t as SupportView } from "../support-view-B8SlToQX.js";
10
+ import { n as MembershipClaim, r as hasEntitlement, t as EntitlementKey } from "../entitlements-BiJZ4t49.js";
10
11
  import { NextRequest } from "next/server.js";
11
12
  import { StandardSchemaV1 } from "@standard-schema/spec";
12
13
  //#region src/next/collector.d.ts
@@ -149,7 +150,27 @@ interface BetterNext<M extends AnyModels, F extends AnyFunctions, E, C = unknown
149
150
  * `expectDbBudget`. Answers 404 unless `debug` is on.
150
151
  */
151
152
  debugRoute(): (request: Request) => Promise<Response>;
153
+ /**
154
+ * Starts a support session for the signed-in admin and sets its cookie,
155
+ * so the admin's next requests render as the target. Call it from a
156
+ * server action.
157
+ */
158
+ startSupport(request: SupportStartRequest): Promise<ActionResult<SupportStarted>>;
159
+ /** Ends the admin's support session and clears its cookie. Call it from a server action. */
160
+ stopSupport(): Promise<ActionResult<{
161
+ readonly ended: boolean;
162
+ }>>;
163
+ }
164
+ /** What `startSupport` returns: plain data, safe across the action boundary. */
165
+ interface SupportStarted {
166
+ readonly sessionId: string;
167
+ readonly targetUserId: string;
168
+ readonly readOnly: boolean;
169
+ /** ISO 8601. */
170
+ readonly expiresAt: string;
152
171
  }
172
+ /** The tag `bs.cached()` adds to entries rendered in a support session. */
173
+ export declare function supportTag(sessionId: string): string;
153
174
  interface SessionStaleOptions {
154
175
  /** Below this, the result would not be prefetched anyway. Defaults to 30. */
155
176
  readonly min?: number;
@@ -223,4 +244,4 @@ export declare function shouldRefresh(request: Request): boolean;
223
244
  */
224
245
  export declare function createNext<M extends AnyModels, D, F extends AnyFunctions, E, C = unknown, P = unknown>(betterSupabase: BetterSupabase<M, D, F, E, C, P>, options?: NextOptions): BetterNext<M, F, E, C, P>;
225
246
  //#endregion
226
- export { type Aal, type ActionOptions, type ActionResult, type AmrEntry, type AuthKind, type AuthSession, type BetterNext, type CachedContext, type CachedOptions, type DbBudget, type DbStats, type EntitlementKey, type GuardOptions, type Impersonator, type LiveCountSeed, type MembershipClaim, type NextDebugOptions, type NextOptions, type ProxyOptions, type RequireAalOptions, type SessionStaleOptions, hasEntitlement, toSession };
247
+ export { type Aal, type ActionOptions, type ActionResult, type ActiveSupport, type AmrEntry, type AuthKind, type AuthSession, type BetterNext, type CachedContext, type CachedOptions, type DbBudget, type DbStats, type EntitlementKey, type GuardOptions, type Impersonator, type LiveCountSeed, type MembershipClaim, type NextDebugOptions, type NextOptions, type ProxyOptions, type RequireAalOptions, type SessionStaleOptions, type SupportOptions, type SupportPolicy, type SupportSessions, type SupportStartRequest, type SupportStarted, type SupportView, hasEntitlement, supportOf, supportSessions, toSession };