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
@@ -0,0 +1,132 @@
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
+ //#region src/orgs/orgs.d.ts
7
+ /**
8
+ * Organization columns by database name: `name`, `slug` and the columns in
9
+ * `kits.organizations.options.attributes`. Other keys are ignored.
10
+ */
11
+ interface OrgAttributes {
12
+ readonly name?: string;
13
+ readonly slug?: string;
14
+ readonly [column: string]: unknown;
15
+ }
16
+ interface CreateOrgOptions {
17
+ /** The owner, when the transport runs as the service role. */
18
+ readonly ownerId?: string;
19
+ }
20
+ type SlugProblem = "invalid" | "reserved" | "taken";
21
+ interface InviteRequest {
22
+ /** `null` invites to the platform (`kits.access.model: 'catalog'`). */
23
+ readonly organizationId: string | null;
24
+ readonly email: string;
25
+ /** A role name, or a role id or key under the catalog model. */
26
+ readonly role: string;
27
+ /** A Postgres interval such as `'3 days'`. Defaults to `options.validFor`. */
28
+ readonly validFor?: string;
29
+ /** Profile fields to fill in on sign-up. */
30
+ readonly prefill?: Readonly<Record<string, unknown>>;
31
+ }
32
+ interface Invitation {
33
+ readonly id: string;
34
+ readonly organizationId: string | null;
35
+ readonly email: string;
36
+ readonly role: string;
37
+ readonly expiresAt: Temporal.Instant;
38
+ readonly invitedBy?: string;
39
+ readonly prefill: Readonly<Record<string, unknown>>;
40
+ }
41
+ /** What `onInvite` receives: the place to send the email. */
42
+ interface InvitationSent {
43
+ readonly invitation: Invitation;
44
+ /** Put it in the accept link. It is never stored in plain text by default. */
45
+ readonly token: string;
46
+ readonly resent: boolean;
47
+ }
48
+ type InvitationStatus = "pending" | "accepted" | "declined" | "revoked" | "expired";
49
+ interface InvitationPreview {
50
+ readonly status: InvitationStatus;
51
+ readonly email: string;
52
+ readonly role: string;
53
+ readonly organizationId: string | null;
54
+ readonly expiresAt: Temporal.Instant;
55
+ /** The organization columns in `options.previewColumns`. */
56
+ readonly organization: Readonly<Record<string, unknown>> | null;
57
+ readonly prefill: Readonly<Record<string, unknown>>;
58
+ }
59
+ interface SwitchResult {
60
+ readonly organizationId: string;
61
+ /** The active tenant is a claim: refresh the session to get it. */
62
+ readonly refresh: boolean;
63
+ }
64
+ interface OrgsOptions {
65
+ /** `sqlTransport(postgres.asUser(claims))` or `rpcTransport(supabase)`. */
66
+ readonly transport: KitTransport;
67
+ /**
68
+ * The schema holding the functions: `kits.<module>.schema` with
69
+ * `sqlTransport`, the exposed API schema of wrappers with `rpcTransport`.
70
+ * One schema for both modules, or one per module. Defaults to
71
+ * `better_supabase`.
72
+ */
73
+ readonly schema?: string | {
74
+ readonly organizations?: string;
75
+ readonly invitations?: string;
76
+ };
77
+ /** `betterSupabase.events`, to emit `org.*` and `invitation.*` kit events. */
78
+ readonly events?: EventHub;
79
+ /** The user acting, for event metadata. */
80
+ readonly actorId?: string;
81
+ readonly context?: RequestContext;
82
+ /**
83
+ * Runs before an invitation is created, after the database checks the
84
+ * caller may invite. Return `false` to refuse (seat limits, plans).
85
+ */
86
+ readonly canInvite?: (request: InviteRequest) => boolean | Promise<boolean>;
87
+ /**
88
+ * Runs after an invitation is created or resent, to send the email. When
89
+ * it throws, the call returns an error and the invitation stays open:
90
+ * call `resendInvitation` to retry.
91
+ */
92
+ readonly onInvite?: (sent: InvitationSent) => void | Promise<void>;
93
+ /** Run before the built-in mapping, as in `betterSupabase.mapError()`. */
94
+ readonly errorMappers?: readonly ErrorMapper[];
95
+ }
96
+ /**
97
+ * The `organizations` and `invitations` SQL kit modules as typed calls. Each
98
+ * method returns an `AsyncResult`; database errors carry the kit's error
99
+ * code (`ORG_FORBIDDEN`, `INVITATION_INVALID`) as `hint`.
100
+ */
101
+ interface Orgs {
102
+ create(attributes: OrgAttributes, options?: CreateOrgOptions): AsyncResult<{
103
+ readonly id: string;
104
+ }>;
105
+ update(organizationId: string, attributes: OrgAttributes): AsyncResult<true>;
106
+ /** `false` when it did not exist (or was already soft-deleted). */
107
+ delete(organizationId: string): AsyncResult<boolean>;
108
+ /** Why a slug can't be used, or `undefined` when it can. */
109
+ slugProblem(slug: string, exceptOrganizationId?: string): AsyncResult<SlugProblem | undefined>;
110
+ updateMemberRole(organizationId: string, userId: string, role: string): AsyncResult<true>;
111
+ removeMember(organizationId: string, userId: string): AsyncResult<true>;
112
+ leave(organizationId: string): AsyncResult<true>;
113
+ /** `formerRole` defaults to `options.formerOwnerRole`. */
114
+ transferOwnership(organizationId: string, newOwnerId: string, formerRole?: string): AsyncResult<true>;
115
+ markUsed(organizationId: string): AsyncResult<boolean>;
116
+ switch(organizationId: string): AsyncResult<SwitchResult>;
117
+ invite(request: InviteRequest): AsyncResult<InvitationSent>;
118
+ resendInvitation(invitationId: string, validFor?: string): AsyncResult<InvitationSent>;
119
+ /** `false` when it was not open. */
120
+ revokeInvitation(invitationId: string): AsyncResult<boolean>;
121
+ declineInvitation(token: string): AsyncResult<boolean>;
122
+ /** `undefined` for an unknown token. Callable without a session. */
123
+ previewInvitation(token: string): AsyncResult<InvitationPreview | undefined>;
124
+ /** Returns the organization joined, `null` for a platform invitation. */
125
+ acceptInvitation(token: string): AsyncResult<{
126
+ readonly organizationId: string | null;
127
+ }>;
128
+ }
129
+ /** Calls the `organizations` and `invitations` kit functions. */
130
+ export declare function createOrgs(options: OrgsOptions): Orgs;
131
+ //#endregion
132
+ export { type CreateOrgOptions, type Invitation, type InvitationPreview, type InvitationSent, type InvitationStatus, type InviteRequest, type KitTransport, type OrgAttributes, type Orgs, type OrgsOptions, type RpcClient, type SlugProblem, type SwitchResult, rpcTransport, sqlTransport };
@@ -0,0 +1,245 @@
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 { n as emitKitEvent } from "../kit-events-CTFDfSwb.js";
5
+ import { n as rpcTransport, r as sqlTransport, t as rawError } from "../kit-transport-DJlHQUBU.js";
6
+ //#region src/orgs/orgs.ts
7
+ const DEFAULT_SCHEMA = "better_supabase";
8
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
9
+ const text = (value) => typeof value === "string" ? value : String(value);
10
+ const optionalText = (value) => value === null || value === void 0 ? null : text(value);
11
+ function invitationFrom(row) {
12
+ const invitedBy = optionalText(row["invited_by"]);
13
+ return {
14
+ id: text(row["id"]),
15
+ organizationId: optionalText(row["tenant"]),
16
+ email: text(row["email"]),
17
+ role: text(row["role"]),
18
+ expiresAt: temporal().Instant.from(text(row["expires_at"])),
19
+ ...invitedBy === null ? {} : { invitedBy },
20
+ prefill: isRecord(row["prefill"]) ? row["prefill"] : {}
21
+ };
22
+ }
23
+ const STATUSES = /* @__PURE__ */ new Set([
24
+ "pending",
25
+ "accepted",
26
+ "declined",
27
+ "revoked",
28
+ "expired"
29
+ ]);
30
+ function isStatus(value) {
31
+ return typeof value === "string" && STATUSES.has(value);
32
+ }
33
+ function previewFrom(row) {
34
+ const status = row["status"];
35
+ if (!isStatus(status)) throw new TypeError(`Unknown invitation status ${String(status)}`);
36
+ return {
37
+ status,
38
+ email: text(row["email"]),
39
+ role: text(row["role"]),
40
+ organizationId: optionalText(row["tenant"]),
41
+ expiresAt: temporal().Instant.from(text(row["expires_at"])),
42
+ organization: isRecord(row["organization"]) ? row["organization"] : null,
43
+ prefill: isRecord(row["prefill"]) ? row["prefill"] : {}
44
+ };
45
+ }
46
+ function recordOf(value, fn) {
47
+ if (!isRecord(value)) throw new TypeError(`${fn} returned ${JSON.stringify(value)}`);
48
+ return value;
49
+ }
50
+ /** Calls the `organizations` and `invitations` kit functions. */
51
+ function createOrgs(options) {
52
+ const { transport } = options;
53
+ const schemaOf = (module) => typeof options.schema === "string" ? options.schema : options.schema?.[module] ?? DEFAULT_SCHEMA;
54
+ const mappers = options.errorMappers ?? [];
55
+ function run(module, fn, args, then) {
56
+ return AsyncResult.from(async () => {
57
+ let value;
58
+ try {
59
+ value = await transport.call(schemaOf(module), fn, args);
60
+ } catch (cause) {
61
+ const raw = rawError(cause);
62
+ return err(raw ? mapDbError(raw, mappers) : toDbError(cause));
63
+ }
64
+ return ok(await then(value));
65
+ });
66
+ }
67
+ function emit(type, data, subject, tenant) {
68
+ if (!options.events) return;
69
+ emitKitEvent(options.events, type, data, {
70
+ subject,
71
+ ...tenant ? { tenant } : {},
72
+ ...options.actorId ? { actorId: options.actorId } : {},
73
+ ...options.context ? { context: options.context } : {}
74
+ });
75
+ }
76
+ const orgEvent = (type, data) => {
77
+ emit(type, data, `organizations/${data.organizationId}`, data.organizationId);
78
+ };
79
+ const invitationEvent = (type, data) => {
80
+ emit(type, data, `invitations/${data.invitationId}`, data.organizationId ?? null);
81
+ };
82
+ const sent = async (value, fn, resent) => {
83
+ const row = recordOf(value, fn);
84
+ const invitation = invitationFrom(row);
85
+ const result = {
86
+ invitation,
87
+ token: text(row["token"]),
88
+ resent
89
+ };
90
+ invitationEvent(resent ? "invitation.resent" : "invitation.created", {
91
+ invitationId: invitation.id,
92
+ ...invitation.organizationId === null ? {} : { organizationId: invitation.organizationId },
93
+ email: invitation.email,
94
+ role: invitation.role
95
+ });
96
+ await options.onInvite?.(result);
97
+ return result;
98
+ };
99
+ return {
100
+ create(attributes, createOptions) {
101
+ return run("organizations", "create_organization", { attrs: {
102
+ ...attributes,
103
+ ...createOptions?.ownerId ? { owner_id: createOptions.ownerId } : {}
104
+ } }, (value) => {
105
+ const id = text(value);
106
+ orgEvent("org.created", {
107
+ organizationId: id,
108
+ ...createOptions?.ownerId ?? options.actorId ? { userId: createOptions?.ownerId ?? options.actorId } : {}
109
+ });
110
+ return { id };
111
+ });
112
+ },
113
+ update(organizationId, attributes) {
114
+ return run("organizations", "update_organization", {
115
+ org: organizationId,
116
+ attrs: attributes
117
+ }, () => {
118
+ orgEvent("org.updated", { organizationId });
119
+ return true;
120
+ });
121
+ },
122
+ delete(organizationId) {
123
+ return run("organizations", "delete_organization", { org: organizationId }, (value) => {
124
+ if (value === true) orgEvent("org.deleted", { organizationId });
125
+ return value === true;
126
+ });
127
+ },
128
+ slugProblem(slug, exceptOrganizationId) {
129
+ return run("organizations", "organization_slug_problem", {
130
+ value: slug,
131
+ except_org: exceptOrganizationId
132
+ }, (value) => {
133
+ if (value === null) return;
134
+ if (value === "invalid" || value === "reserved" || value === "taken") return value;
135
+ throw new TypeError(`Unknown slug problem ${String(value)}`);
136
+ });
137
+ },
138
+ updateMemberRole(organizationId, userId, role) {
139
+ return run("organizations", "update_member_role", {
140
+ org: organizationId,
141
+ member: userId,
142
+ role
143
+ }, () => {
144
+ orgEvent("org.role_changed", {
145
+ organizationId,
146
+ userId,
147
+ role
148
+ });
149
+ return true;
150
+ });
151
+ },
152
+ removeMember(organizationId, userId) {
153
+ return run("organizations", "remove_member", {
154
+ org: organizationId,
155
+ member: userId
156
+ }, () => {
157
+ orgEvent("org.member_removed", {
158
+ organizationId,
159
+ userId
160
+ });
161
+ return true;
162
+ });
163
+ },
164
+ leave(organizationId) {
165
+ return run("organizations", "leave_organization", { org: organizationId }, () => {
166
+ orgEvent("org.member_left", {
167
+ organizationId,
168
+ ...options.actorId ? { userId: options.actorId } : {}
169
+ });
170
+ return true;
171
+ });
172
+ },
173
+ transferOwnership(organizationId, newOwnerId, formerRole) {
174
+ return run("organizations", "transfer_ownership", {
175
+ org: organizationId,
176
+ new_owner: newOwnerId,
177
+ former_role: formerRole
178
+ }, () => {
179
+ orgEvent("org.ownership_transferred", {
180
+ organizationId,
181
+ userId: newOwnerId
182
+ });
183
+ return true;
184
+ });
185
+ },
186
+ markUsed(organizationId) {
187
+ return run("organizations", "mark_used", { org: organizationId }, (value) => value === true);
188
+ },
189
+ switch(organizationId) {
190
+ return run("organizations", "switch_organization", { org: organizationId }, (value) => {
191
+ const row = recordOf(value, "switch_organization");
192
+ orgEvent("org.switched", {
193
+ organizationId,
194
+ ...options.actorId ? { userId: options.actorId } : {}
195
+ });
196
+ return {
197
+ organizationId: text(row["organization_id"]),
198
+ refresh: row["refresh"] === true
199
+ };
200
+ });
201
+ },
202
+ invite(request) {
203
+ return AsyncResult.from(async () => {
204
+ if (options.canInvite && !await options.canInvite(request)) return err(dbError("forbidden", "The invitation was refused", { hint: "INVITATION_FORBIDDEN" }));
205
+ return run("invitations", "invite_member", {
206
+ tenant: request.organizationId,
207
+ invitee_email: request.email,
208
+ invitee_role: request.role,
209
+ valid_for: request.validFor,
210
+ prefill: request.prefill
211
+ }, (value) => sent(value, "invite_member", false));
212
+ });
213
+ },
214
+ resendInvitation(invitationId, validFor) {
215
+ return run("invitations", "resend_invitation", {
216
+ invitation_id: invitationId,
217
+ valid_for: validFor
218
+ }, (value) => sent(value, "resend_invitation", true));
219
+ },
220
+ revokeInvitation(invitationId) {
221
+ return run("invitations", "revoke_invitation", { invitation_id: invitationId }, (value) => {
222
+ if (value === true) invitationEvent("invitation.revoked", { invitationId });
223
+ return value === true;
224
+ });
225
+ },
226
+ declineInvitation(token) {
227
+ return run("invitations", "decline_invitation", { token }, (value) => value === true);
228
+ },
229
+ previewInvitation(token) {
230
+ return run("invitations", "invitation_preview", { token }, (value) => value === null ? void 0 : previewFrom(recordOf(value, "invitation_preview")));
231
+ },
232
+ acceptInvitation(token) {
233
+ return run("invitations", "accept_invitation", { token }, (value) => {
234
+ const organizationId = optionalText(value);
235
+ if (organizationId !== null) orgEvent("org.member_added", {
236
+ organizationId,
237
+ ...options.actorId ? { userId: options.actorId } : {}
238
+ });
239
+ return { organizationId };
240
+ });
241
+ }
242
+ };
243
+ }
244
+ //#endregion
245
+ export { createOrgs, rpcTransport, sqlTransport };
@@ -1,9 +1,9 @@
1
- import { n as AnyModels, t as AnyFunctions } from "../types-DONv9SHN.js";
2
- import { o as Result, p as DbError } from "../result-CqEsbgYs.js";
3
- import { t as BetterSupabase } from "../define-CRS2MRV3.js";
4
- import { r as AuthState } from "../resolve-BAqQcwMW.js";
5
- import { f as ServerContext, n as GuardOptions, p as ServerOptions, r as MiddlewareOptions, u as BetterServer } from "../respond-DgIbdrt8.js";
6
- import { r as ProblemDetails } from "../problem-U8UEKccv.js";
1
+ import { n as AnyFunctions, r as AnyModels } from "../types-Dnf_wRDz.js";
2
+ import { _ as DbError, u as Result } from "../executor-BunPOO-k.js";
3
+ import { t as BetterSupabase } from "../define-Bp_BZa5h.js";
4
+ import { r as AuthState } from "../resolve-C15TJq77.js";
5
+ import { f as ServerContext, n as GuardOptions, p as ServerOptions, r as MiddlewareOptions, u as BetterServer } from "../respond-D2OQl62-.js";
6
+ import { r as ProblemDetails } from "../problem-Blxe8S31.js";
7
7
  import { DecoratedMiddleware, ORPCError } from "@orpc/server";
8
8
  //#region src/orpc/index.d.ts
9
9
  /** Initial context: pass `{ context: { request } }` to the oRPC handler. */
@@ -20,15 +20,35 @@ export interface OrpcOptions extends ServerOptions {
20
20
  /** Include internal error messages. Defaults to `NODE_ENV === 'development'`. */
21
21
  readonly exposeErrors?: boolean;
22
22
  }
23
+ /** The part of an oRPC fetch handler (`RPCHandler`, `OpenAPIHandler`) `fetchHandler` calls. */
24
+ export interface OrpcFetchHandler {
25
+ handle(request: Request, options: {
26
+ readonly prefix?: `/${string}`;
27
+ readonly context: OrpcRequestContext;
28
+ }): Promise<{
29
+ readonly matched: boolean;
30
+ readonly response?: Response | undefined;
31
+ }>;
32
+ }
33
+ export interface OrpcFetchOptions {
34
+ /** Path prefix the router is mounted under, e.g. `/rpc`. */
35
+ readonly prefix?: `/${string}`;
36
+ }
23
37
  export type OrpcMiddleware<M extends AnyModels, F extends AnyFunctions, E, C = unknown, P = unknown> = DecoratedMiddleware<OrpcRequestContext, OrpcContext<M, F, E, C, P>, unknown, unknown, Record<never, never>>;
24
38
  export interface BetterOrpc<M extends AnyModels, F extends AnyFunctions, E, C = unknown, P = unknown> extends BetterServer<M, F, E, C, P> {
25
39
  /**
26
40
  * Resolves the caller, enforces `allow` and adds `context.db`, `context.auth`
27
41
  * and `context.bs`. Thrown `DbException`s, and errors caused by a
28
- * `DbError`, become `ORPCError`s. With `refresh`, pair it with
29
- * `ctx.bs.resolution.apply` in the handler adapter.
42
+ * `DbError`, become `ORPCError`s. Serve the router with `fetchHandler` so
43
+ * refreshed session cookies and `bs-primary-until` reach the response.
30
44
  */
31
45
  middleware(options?: MiddlewareOptions): OrpcMiddleware<M, F, E, C, P>;
46
+ /**
47
+ * A fetch handler for an oRPC handler: passes `{ request }` as the initial
48
+ * context, answers 404 when no procedure matched, and applies the cookies
49
+ * of the request's context (`ctx.apply`).
50
+ */
51
+ fetchHandler(handler: OrpcFetchHandler, options?: OrpcFetchOptions): (request: Request) => Promise<Response>;
32
52
  /** A `Result` (or `AsyncResult`) as data, throwing an `ORPCError` on failure. */
33
53
  unwrap<T>(value: Result<T> | PromiseLike<Result<T>>): Promise<T>;
34
54
  /** Plain values pass through; thrown `DbException`s become `ORPCError`s. */
@@ -1,6 +1,6 @@
1
- import { r as dbErrorOf } from "../errors-L0xK41y_.js";
2
- import { o as toProblem } from "../problem-BihjPR0t.js";
3
- import { a as guard, c as settle, i as defaultExpose, n as extendServer, t as createServer } from "../server-BA75B0mT.js";
1
+ import { r as dbErrorOf } from "../errors-DtwXa6b8.js";
2
+ import { o as toProblem } from "../problem-BW1KhzSG.js";
3
+ import { d as guard, m as settle, n as createServer, r as extendServer, u as defaultExpose } from "../server-Csx2sRjg.js";
4
4
  import { COMMON_ERROR_STATUS_MAP, ORPCError, os } from "@orpc/server";
5
5
  //#region src/orpc/index.ts
6
6
  const CODES = /* @__PURE__ */ new Map(Object.entries(COMMON_ERROR_STATUS_MAP).map(([code, status]) => [status, code]));
@@ -12,6 +12,7 @@ function orpcCode(error) {
12
12
  function createOrpc(betterSupabase, options = {}) {
13
13
  const server = createServer(betterSupabase, options);
14
14
  const expose = options.exposeErrors ?? defaultExpose();
15
+ const contexts = /* @__PURE__ */ new WeakMap();
15
16
  const toOrpcError = (error) => {
16
17
  const problem = toProblem(error, { expose });
17
18
  return new ORPCError(orpcCode(error), {
@@ -26,6 +27,7 @@ function createOrpc(betterSupabase, options = {}) {
26
27
  const ctx = await server.context(context.request, { refresh: middlewareOptions.refresh ?? false });
27
28
  const denied = guard(ctx.auth, middlewareOptions.allow, middlewareOptions.aal, middlewareOptions.scopes);
28
29
  if (denied) throw toOrpcError(denied);
30
+ contexts.set(context.request, ctx);
29
31
  try {
30
32
  return await next({ context: {
31
33
  bs: ctx,
@@ -39,6 +41,17 @@ function createOrpc(betterSupabase, options = {}) {
39
41
  }
40
42
  });
41
43
  },
44
+ fetchHandler(handler, fetchOptions = {}) {
45
+ return async (request) => {
46
+ const { response } = await handler.handle(request, {
47
+ ...fetchOptions.prefix ? { prefix: fetchOptions.prefix } : {},
48
+ context: { request }
49
+ });
50
+ const ctx = contexts.get(request);
51
+ const answer = response ?? new Response("Not found", { status: 404 });
52
+ return ctx ? ctx.apply(answer) : answer;
53
+ };
54
+ },
42
55
  async unwrap(value) {
43
56
  const settled = await settle(() => value);
44
57
  if (!settled.ok) throw toOrpcError(settled.error);
@@ -1,5 +1,5 @@
1
- import { a as EventHub } from "../events-B21UOhvP.js";
2
- import { f as Plugin } from "../plugin-bNhvJwRh.js";
1
+ import { a as EventHub } from "../events-aIO7vsIk.js";
2
+ import { p as Plugin } from "../plugin-o144f322.js";
3
3
  import { t as SPEC_PINS } from "../spec-pins-CqorWDnO.js";
4
4
  import { Attributes, Meter, Tracer } from "@opentelemetry/api";
5
5
  //#region src/otel/index.d.ts
@@ -13,6 +13,16 @@ export interface OtelOptions {
13
13
  readonly attributes?: Attributes;
14
14
  /** Set `false` to skip the duration histogram. */
15
15
  readonly metrics?: boolean;
16
+ /**
17
+ * The database name in `db.namespace` (`{database}|{schema}`). Supabase
18
+ * names it `postgres`, the default for Postgres executors.
19
+ */
20
+ readonly database?: string;
21
+ /** `server.address` and `server.port` on spans and metrics, e.g. the project host. */
22
+ readonly server?: {
23
+ readonly address: string;
24
+ readonly port?: number;
25
+ };
16
26
  }
17
27
  /**
18
28
  * Database client spans and metrics for every repository operation, using
@@ -31,6 +41,15 @@ export declare function otel(options?: OtelOptions): Plugin<"otel">;
31
41
  export declare function traceAuth(betterSupabase: {
32
42
  readonly events: EventHub;
33
43
  }, options?: Pick<OtelOptions, "tracer" | "attributes">): () => void;
44
+ /**
45
+ * Records kit events (`support.started`, `webhook.failed`, ...) with the
46
+ * `KIT_ATTRIBUTES` names: as an event on the active span, or as a short
47
+ * `kit <type>` span when there is none. Failures (`*.failed`, `*.denied`)
48
+ * set an error status. Returns a function that stops listening.
49
+ */
50
+ export declare function traceKitEvents(betterSupabase: {
51
+ readonly events: EventHub;
52
+ }, options?: Pick<OtelOptions, "tracer" | "attributes">): () => void;
34
53
  /**
35
54
  * A `fetch` that adds W3C `traceparent`/`tracestate` from the active
36
55
  * context. Pass it to `createClient(url, key, { global: { fetch } })` so
@@ -1,11 +1,21 @@
1
- import { n as definePlugin } from "../plugin-BqR6wKMB.js";
1
+ import { r as definePlugin } from "../plugin-DwARMuQp.js";
2
2
  import { t as SPEC_PINS } from "../spec-pins-C3oc-Tdp.js";
3
- import "../version-Bmf7_FzM.js";
3
+ import { r as kitEventAttributes } from "../kit-events-CTFDfSwb.js";
4
+ import "../version-DM_Roffb.js";
4
5
  import { SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
5
6
  //#region src/otel/index.ts
6
7
  const INSTRUMENTATION_NAME = "better-supabase";
7
8
  /** OpenTelemetry semantic conventions version the attributes follow. */
8
9
  const SEMCONV_VERSION = SPEC_PINS.otelSemconv;
10
+ /** A five-character SQLSTATE; PostgREST's own codes (`PGRST116`) are not one. */
11
+ const SQLSTATE = /^[0-9A-Z]{5}$/;
12
+ function serverAttributes(server) {
13
+ if (!server) return {};
14
+ return server.port === void 0 ? { "server.address": server.address } : {
15
+ "server.address": server.address,
16
+ "server.port": server.port
17
+ };
18
+ }
9
19
  const OPERATION_NAME = {
10
20
  select: "SELECT",
11
21
  insert: "INSERT",
@@ -30,9 +40,9 @@ function systemOf(executor) {
30
40
  * ```
31
41
  */
32
42
  function otel(options = {}) {
33
- const tracer = options.tracer ?? trace.getTracer("better-supabase", "0.4.0");
43
+ const tracer = options.tracer ?? trace.getTracer("better-supabase", "0.5.0");
34
44
  let histogram;
35
- if (options.metrics !== false) histogram = (options.meter ?? metrics.getMeter("better-supabase", "0.4.0")).createHistogram("db.client.operation.duration", {
45
+ if (options.metrics !== false) histogram = (options.meter ?? metrics.getMeter("better-supabase", "0.5.0")).createHistogram("db.client.operation.duration", {
36
46
  unit: "s",
37
47
  description: "Duration of database client operations.",
38
48
  advice: { explicitBucketBoundaries: [
@@ -52,6 +62,8 @@ function otel(options = {}) {
52
62
  enforce: "post",
53
63
  wrapExecutor: (executor) => {
54
64
  const system = systemOf(executor);
65
+ const database = options.database ?? (system === "postgresql" ? "postgres" : void 0);
66
+ const server = serverAttributes(options.server);
55
67
  /** Span and metric attributes per table and operation, built once. */
56
68
  const described = /* @__PURE__ */ new Map();
57
69
  const describe = (op) => {
@@ -65,13 +77,15 @@ function otel(options = {}) {
65
77
  attributes: {
66
78
  ...options.attributes,
67
79
  "db.system.name": system,
68
- "db.namespace": op.table.schema,
80
+ "db.namespace": database ? `${database}|${op.table.schema}` : op.table.schema,
69
81
  "db.collection.name": op.table.name,
70
82
  "db.operation.name": operation,
71
83
  "db.query.summary": summary,
72
- "better_supabase.executor": executor.name
84
+ "better_supabase.executor": executor.name,
85
+ ...server
73
86
  },
74
87
  metric: {
88
+ ...server,
75
89
  "db.system.name": system,
76
90
  "db.collection.name": op.table.name,
77
91
  "db.operation.name": operation
@@ -106,7 +120,7 @@ function otel(options = {}) {
106
120
  } else {
107
121
  span.setAttributes({
108
122
  "error.type": result.error.kind,
109
- ...result.error.code ? { "db.response.status_code": result.error.code } : {}
123
+ ...result.error.code && SQLSTATE.test(result.error.code) ? { "db.response.status_code": result.error.code } : {}
110
124
  });
111
125
  span.setStatus({
112
126
  code: SpanStatusCode.ERROR,
@@ -137,7 +151,7 @@ function otel(options = {}) {
137
151
  * Returns a function that stops listening.
138
152
  */
139
153
  function traceAuth(betterSupabase, options = {}) {
140
- const tracer = options.tracer ?? trace.getTracer("better-supabase", "0.4.0");
154
+ const tracer = options.tracer ?? trace.getTracer("better-supabase", "0.5.0");
141
155
  const offAuth = betterSupabase.events.on("auth", (event) => {
142
156
  const span = tracer.startSpan("auth.resolve", {
143
157
  kind: SpanKind.INTERNAL,
@@ -169,6 +183,33 @@ function traceAuth(betterSupabase, options = {}) {
169
183
  };
170
184
  }
171
185
  /**
186
+ * Records kit events (`support.started`, `webhook.failed`, ...) with the
187
+ * `KIT_ATTRIBUTES` names: as an event on the active span, or as a short
188
+ * `kit <type>` span when there is none. Failures (`*.failed`, `*.denied`)
189
+ * set an error status. Returns a function that stops listening.
190
+ */
191
+ function traceKitEvents(betterSupabase, options = {}) {
192
+ const tracer = options.tracer ?? trace.getTracer("better-supabase", "0.5.0");
193
+ return betterSupabase.events.on("kit", (event) => {
194
+ const attributes = {
195
+ ...options.attributes,
196
+ ...kitEventAttributes(event)
197
+ };
198
+ const failed = event.type.endsWith(".failed") || event.type.endsWith(".denied");
199
+ const active = trace.getActiveSpan();
200
+ if (active) {
201
+ active.addEvent(event.type, attributes);
202
+ return;
203
+ }
204
+ const span = tracer.startSpan(`kit ${event.type}`, {
205
+ kind: SpanKind.INTERNAL,
206
+ attributes
207
+ });
208
+ if (failed) span.setStatus({ code: SpanStatusCode.ERROR });
209
+ span.end();
210
+ });
211
+ }
212
+ /**
172
213
  * A `fetch` that adds W3C `traceparent`/`tracestate` from the active
173
214
  * context. Pass it to `createClient(url, key, { global: { fetch } })` so
174
215
  * PostgREST, Auth and Storage calls join the trace.
@@ -186,4 +227,4 @@ function tracedFetch(base = fetch) {
186
227
  };
187
228
  }
188
229
  //#endregion
189
- export { INSTRUMENTATION_NAME, SEMCONV_VERSION, otel, traceAuth, tracedFetch };
230
+ export { INSTRUMENTATION_NAME, SEMCONV_VERSION, otel, traceAuth, traceKitEvents, tracedFetch };
@@ -0,0 +1,29 @@
1
+ //#region src/core/permdock-sql.d.ts
2
+ /** PermDock's default `rls.schema`, a schema the Data API does not expose. */
3
+ declare const PERMDOCK_SCHEMA = "permdock";
4
+ /**
5
+ * The part of PermDock's `permissions.catalog.json` the Storage and Realtime
6
+ * builders read: `import catalog from './permissions.catalog.json'`.
7
+ */
8
+ interface PermdockCatalog {
9
+ readonly permissions: readonly {
10
+ readonly key: string;
11
+ readonly rowConditions?: boolean;
12
+ /** The scope PermDock declares the permission at. */
13
+ readonly scope?: string;
14
+ }[];
15
+ }
16
+ /**
17
+ * What PermDock's catalog says about a key: `scope-only` (`rowConditions:
18
+ * false`), `row-conditions` (`true`), `no-flag` (an entry without a boolean
19
+ * flag, from an older `permdock catalog`) or `missing`. Only `scope-only` is
20
+ * safe for the SQL helpers.
21
+ */
22
+ type PermdockKeyStatus = "scope-only" | "row-conditions" | "no-flag" | "missing";
23
+ declare function permdockKeyStatus(catalog: PermdockCatalog, key: string): PermdockKeyStatus;
24
+ /** The keys a bucket or topic PermDock policy names. */
25
+ declare function permdockKeys(policy: {
26
+ readonly permdock: object;
27
+ }): string[];
28
+ //#endregion
29
+ export { permdockKeys as a, permdockKeyStatus as i, PermdockCatalog as n, PermdockKeyStatus as r, PERMDOCK_SCHEMA as t };