better-supabase 0.0.0 → 0.1.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 (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -1
  3. package/bin/better-supabase.js +3 -0
  4. package/dist/casing-da0uRTqt.js +14 -0
  5. package/dist/cli/bin.d.ts +1 -0
  6. package/dist/cli/bin.js +15 -0
  7. package/dist/cli/index.d.ts +226 -0
  8. package/dist/cli/index.js +3 -0
  9. package/dist/cli-CoByWYja.js +6472 -0
  10. package/dist/client/index.d.ts +2 -0
  11. package/dist/client/index.js +112 -0
  12. package/dist/compiler-DL18ukPe.d.ts +14 -0
  13. package/dist/config/index.d.ts +3 -0
  14. package/dist/config/index.js +3 -0
  15. package/dist/config-D-aR2ZoN.js +276 -0
  16. package/dist/define-CTfWw-SS.d.ts +662 -0
  17. package/dist/define-QGkq04oe.js +1068 -0
  18. package/dist/edge/index.d.ts +43 -0
  19. package/dist/edge/index.js +103 -0
  20. package/dist/entitlements-BBg61DQZ.d.ts +20 -0
  21. package/dist/entitlements-BC_khwRy.js +17 -0
  22. package/dist/env/index.d.ts +2 -0
  23. package/dist/env/index.js +2 -0
  24. package/dist/env-BEToab0I.js +204 -0
  25. package/dist/errors-Bl3rkOzo.js +196 -0
  26. package/dist/events/index.d.ts +2 -0
  27. package/dist/events/index.js +2 -0
  28. package/dist/events-2OJsVG_d.d.ts +84 -0
  29. package/dist/events-BuzTgn2D.js +135 -0
  30. package/dist/executor-Bazhgsgq.d.ts +195 -0
  31. package/dist/executor-Bje4suQF.d.ts +25 -0
  32. package/dist/executor-Ceq60hPM.js +89 -0
  33. package/dist/hono/index.d.ts +45 -0
  34. package/dist/hono/index.js +70 -0
  35. package/dist/index-BD6wKthp.d.ts +597 -0
  36. package/dist/index-DQVvHzjW.d.ts +74 -0
  37. package/dist/index-DRid0qIv.d.ts +76 -0
  38. package/dist/index-Dup-nSe9.d.ts +117 -0
  39. package/dist/index-Dy6kuF0P.d.ts +71 -0
  40. package/dist/index-Hf8PP9sP.d.ts +116 -0
  41. package/dist/index-JXHBuS53.d.ts +144 -0
  42. package/dist/index-Nxo3TJcm.d.ts +208 -0
  43. package/dist/index-Sw43J8HG.d.ts +62 -0
  44. package/dist/index-cEeweTeM.d.ts +38 -0
  45. package/dist/index.d.ts +110 -0
  46. package/dist/index.js +42 -0
  47. package/dist/invalidate-DbxuL2jL.js +18 -0
  48. package/dist/jobs/index.d.ts +173 -0
  49. package/dist/jobs/index.js +477 -0
  50. package/dist/json-schema-BbwO2mZg.js +181 -0
  51. package/dist/lint/index.d.ts +75 -0
  52. package/dist/lint/index.js +167 -0
  53. package/dist/list/index.d.ts +2 -0
  54. package/dist/list/index.js +410 -0
  55. package/dist/live-6b9i72Ux.d.ts +180 -0
  56. package/dist/live-D8rV1Ldw.js +168 -0
  57. package/dist/mcp/index.d.ts +105 -0
  58. package/dist/mcp/index.js +330 -0
  59. package/dist/mfa-CpogP66z.js +521 -0
  60. package/dist/mfa-TVia554c.d.ts +44 -0
  61. package/dist/next/image/index.d.ts +34 -0
  62. package/dist/next/image/index.js +40 -0
  63. package/dist/next/index.d.ts +210 -0
  64. package/dist/next/index.js +426 -0
  65. package/dist/openapi/index.d.ts +2 -0
  66. package/dist/openapi/index.js +332 -0
  67. package/dist/orpc/index.d.ts +46 -0
  68. package/dist/orpc/index.js +50 -0
  69. package/dist/otel/index.d.ts +40 -0
  70. package/dist/otel/index.js +168 -0
  71. package/dist/path-Cv__-OTj.d.ts +17 -0
  72. package/dist/plugin-BqR6wKMB.js +23 -0
  73. package/dist/plugin-HvGqCurB.d.ts +118 -0
  74. package/dist/plugins/actor/index.d.ts +13 -0
  75. package/dist/plugins/actor/index.js +43 -0
  76. package/dist/plugins/rules/index.d.ts +88 -0
  77. package/dist/plugins/rules/index.js +174 -0
  78. package/dist/plugins/soft-delete/index.d.ts +38 -0
  79. package/dist/plugins/soft-delete/index.js +69 -0
  80. package/dist/plugins/tenant/index.d.ts +43 -0
  81. package/dist/plugins/tenant/index.js +73 -0
  82. package/dist/plugins/timestamps/index.d.ts +9 -0
  83. package/dist/plugins/timestamps/index.js +37 -0
  84. package/dist/plugins/validation/index.d.ts +24 -0
  85. package/dist/plugins/validation/index.js +59 -0
  86. package/dist/pool-BFeqzwQS.d.ts +37 -0
  87. package/dist/postgres/index.d.ts +28 -0
  88. package/dist/postgres/index.js +79 -0
  89. package/dist/postgrest-Dm-vhY6R.d.ts +43 -0
  90. package/dist/problem-DPu6Fh-t.js +97 -0
  91. package/dist/problem-DlgB0lYl.d.ts +47 -0
  92. package/dist/query/index.d.ts +2 -0
  93. package/dist/query/index.js +3 -0
  94. package/dist/query-ChvDbsDE.js +186 -0
  95. package/dist/react/index.d.ts +7 -0
  96. package/dist/react/index.js +5 -0
  97. package/dist/react/server.d.ts +13 -0
  98. package/dist/react/server.js +25 -0
  99. package/dist/react/session.d.ts +2 -0
  100. package/dist/react/session.js +3 -0
  101. package/dist/react-CrOvrnIP.js +241 -0
  102. package/dist/read-set-DhX2d04c.js +216 -0
  103. package/dist/realtime/index.d.ts +3 -0
  104. package/dist/realtime/index.js +253 -0
  105. package/dist/resolve-DW8h9NPm.d.ts +155 -0
  106. package/dist/resource-CYiu6Ygi.js +175 -0
  107. package/dist/resource-SkPTRJDD.d.ts +65 -0
  108. package/dist/respond-4-dhoV7x.js +380 -0
  109. package/dist/respond-PoTpy5DT.d.ts +169 -0
  110. package/dist/result-CTkCZ6JA.d.ts +198 -0
  111. package/dist/result-DWXatkd6.js +103 -0
  112. package/dist/scope-CDHcS7dE.js +90 -0
  113. package/dist/seed-CzecODcJ.js +101 -0
  114. package/dist/server/index.d.ts +42 -0
  115. package/dist/server/index.js +68 -0
  116. package/dist/session-C3osL21k.d.ts +21 -0
  117. package/dist/session-CD-3orO_.js +19 -0
  118. package/dist/shared-_6-uPL7E.js +57 -0
  119. package/dist/simplify-oHiDKRVQ.js +665 -0
  120. package/dist/spec-pins-CJPVghHy.d.ts +20 -0
  121. package/dist/spec-pins-t6sPaHvl.js +20 -0
  122. package/dist/sql-M8l3fnS0.js +260 -0
  123. package/dist/ssr/index.d.ts +5 -0
  124. package/dist/ssr/index.js +4 -0
  125. package/dist/standard-CyhRzctb.d.ts +7 -0
  126. package/dist/standard-FUi3Z3-7.js +21 -0
  127. package/dist/stats-3iy8u3_P.js +566 -0
  128. package/dist/storage/index.d.ts +4 -0
  129. package/dist/storage/index.js +2 -0
  130. package/dist/storage-BWXVUXGk.js +461 -0
  131. package/dist/tables-rZxRxNPW.js +59 -0
  132. package/dist/template-BeOGnynM.js +93 -0
  133. package/dist/template-mbvlysOo.d.ts +6 -0
  134. package/dist/testing/index.d.ts +294 -0
  135. package/dist/testing/index.js +751 -0
  136. package/dist/types-6cYK9pbJ.js +29 -0
  137. package/dist/types-C2fslP1z.d.ts +195 -0
  138. package/dist/version-Cqq1m3UY.js +4 -0
  139. package/dist/view-B0ltUmTi.d.ts +43 -0
  140. package/dist/view-B6F9QbG-.js +35 -0
  141. package/dist/webhooks/index.d.ts +152 -0
  142. package/dist/webhooks/index.js +2 -0
  143. package/dist/webhooks-DbMTJJX2.js +139 -0
  144. package/package.json +246 -17
  145. package/schemas/config-v1.json +328 -0
  146. package/schemas/doctor-report-v1.json +105 -0
  147. package/schemas/snapshot-v2.json +1002 -0
  148. package/skills/better-supabase/SKILL.md +84 -0
  149. package/skills/better-supabase/references/plugins.md +56 -0
  150. package/skills/better-supabase/references/troubleshooting.md +39 -0
  151. package/skills/better-supabase-api/SKILL.md +67 -0
  152. package/skills/better-supabase-api/references/adapters.md +108 -0
  153. package/skills/better-supabase-testing/SKILL.md +65 -0
  154. package/index.js +0 -1
@@ -0,0 +1,332 @@
1
+ import { n as PROBLEM_TYPE_BASE, t as PROBLEM_CONTENT_TYPE } from "../problem-DPu6Fh-t.js";
2
+ import { t as SPEC_PINS } from "../spec-pins-t6sPaHvl.js";
3
+ import { t as buildJsonSchema } from "../json-schema-BbwO2mZg.js";
4
+ //#region src/openapi/index.ts
5
+ /** JSON Schema for RFC 9457 Problem Details as better-supabase sends them. */
6
+ const PROBLEM_SCHEMA = {
7
+ type: "object",
8
+ required: [
9
+ "type",
10
+ "title",
11
+ "status"
12
+ ],
13
+ properties: {
14
+ type: {
15
+ type: "string",
16
+ format: "uri-reference",
17
+ examples: [`${PROBLEM_TYPE_BASE}not-found`]
18
+ },
19
+ title: { type: "string" },
20
+ status: {
21
+ type: "integer",
22
+ minimum: 100,
23
+ maximum: 599
24
+ },
25
+ detail: { type: "string" },
26
+ instance: { type: "string" },
27
+ kind: { type: "string" },
28
+ code: { type: "string" },
29
+ hint: { type: "string" },
30
+ constraint: { type: "string" },
31
+ column: { type: "string" },
32
+ columns: {
33
+ type: "array",
34
+ items: { type: "string" }
35
+ },
36
+ issues: {
37
+ type: "array",
38
+ items: {
39
+ type: "object",
40
+ required: ["message"],
41
+ properties: {
42
+ message: { type: "string" },
43
+ path: {
44
+ type: "array",
45
+ items: { type: ["string", "integer"] }
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ };
52
+ const ERRORS = [
53
+ [
54
+ "BadRequest",
55
+ 400,
56
+ "The request is invalid."
57
+ ],
58
+ [
59
+ "Unauthorized",
60
+ 401,
61
+ "Missing or invalid credentials."
62
+ ],
63
+ [
64
+ "Forbidden",
65
+ 403,
66
+ "Row-level security or a plugin denied the request."
67
+ ],
68
+ [
69
+ "NotFound",
70
+ 404,
71
+ "No row matches."
72
+ ],
73
+ [
74
+ "Conflict",
75
+ 409,
76
+ "A unique or foreign key constraint failed."
77
+ ],
78
+ [
79
+ "Unprocessable",
80
+ 422,
81
+ "Validation or a check constraint failed."
82
+ ]
83
+ ];
84
+ const pascal = (value) => value.replace(/(^|[_-])(\w)/g, (_, _sep, char) => char.toUpperCase());
85
+ const ref = (name) => ({ $ref: `#/components/schemas/${name}` });
86
+ const response = (name) => ({ $ref: `#/components/responses/${name}` });
87
+ function errorResponses(codes) {
88
+ const out = {};
89
+ for (const [name, status] of ERRORS) if (codes.includes(status)) out[String(status)] = response(name);
90
+ return out;
91
+ }
92
+ function jsonBody(schema, description) {
93
+ return {
94
+ description,
95
+ content: { "application/json": { schema } }
96
+ };
97
+ }
98
+ function pageParameters() {
99
+ return [{
100
+ name: "page",
101
+ in: "query",
102
+ required: false,
103
+ description: "1-based page number.",
104
+ schema: {
105
+ type: "integer",
106
+ minimum: 1,
107
+ default: 1
108
+ }
109
+ }, {
110
+ name: "size",
111
+ in: "query",
112
+ required: false,
113
+ description: "Page size.",
114
+ schema: {
115
+ type: "integer",
116
+ minimum: 1,
117
+ maximum: 200,
118
+ default: 50
119
+ }
120
+ }];
121
+ }
122
+ /**
123
+ * An OpenAPI 3.1 document for REST resources over your tables, with schemas
124
+ * from the generated metadata, list-query parameters, Supabase security
125
+ * schemes and Problem Details errors.
126
+ *
127
+ * ```ts
128
+ * const doc = createOpenApi(sb, {
129
+ * info: { title: 'CRM API', version: '1.0.0' },
130
+ * resources: { customers: { list: customerList }, notes: { operations: ['list', 'get'] } },
131
+ * });
132
+ * ```
133
+ */
134
+ function createOpenApi(sb, options) {
135
+ const defs = buildJsonSchema({
136
+ meta: sb.meta,
137
+ config: { json: options.json ?? {} }
138
+ })["$defs"];
139
+ const schemas = { Problem: PROBLEM_SCHEMA };
140
+ const paths = {};
141
+ const tags = [];
142
+ const base = (options.basePath ?? "").replace(/\/$/, "");
143
+ const security = options.security ?? ["bearer"];
144
+ for (const [key, raw] of Object.entries(options.resources)) {
145
+ const table = sb.meta.tables[key];
146
+ if (!table) throw new TypeError(`createOpenApi: unknown table "${key}"`);
147
+ const resource = raw === true ? {} : raw;
148
+ const operations = resource.operations ?? (table.kind === "view" ? ["list", "get"] : [
149
+ "list",
150
+ "get",
151
+ "create",
152
+ "update",
153
+ "delete"
154
+ ]);
155
+ const name = pascal(key);
156
+ const tag = resource.tag ?? key;
157
+ if (!tags.some((entry) => entry.name === tag)) tags.push({
158
+ name: tag,
159
+ ...resource.description ? { description: resource.description } : {}
160
+ });
161
+ const extensions = { "x-better-supabase-table": `${table.schema}.${table.name}` };
162
+ for (const variant of [
163
+ "Row",
164
+ "Insert",
165
+ "Update"
166
+ ]) {
167
+ const schema = defs[`${key}${variant}`];
168
+ if (schema) schemas[`${name}${variant}`] = schema;
169
+ }
170
+ schemas[`${name}Page`] = {
171
+ type: "object",
172
+ required: ["items", "page"],
173
+ properties: {
174
+ items: {
175
+ type: "array",
176
+ items: ref(`${name}Row`)
177
+ },
178
+ page: {
179
+ type: "object",
180
+ required: [
181
+ "number",
182
+ "size",
183
+ "total",
184
+ "pages",
185
+ "hasMore"
186
+ ],
187
+ properties: {
188
+ number: { type: "integer" },
189
+ size: { type: "integer" },
190
+ total: { type: ["integer", "null"] },
191
+ pages: { type: ["integer", "null"] },
192
+ hasMore: { type: "boolean" }
193
+ }
194
+ }
195
+ }
196
+ };
197
+ const collection = `${base}${resource.path ?? `/${key}`}`;
198
+ const keyColumn = table.primaryKey.length === 1 ? table.primaryKey[0] : void 0;
199
+ const idSchema = keyColumn ? (defs[`${key}Row`]?.["properties"])?.[keyColumn] ?? { type: "string" } : void 0;
200
+ const item = keyColumn ? `${collection}/{${keyColumn}}` : void 0;
201
+ const idParameter = keyColumn ? [{
202
+ name: keyColumn,
203
+ in: "path",
204
+ required: true,
205
+ schema: idSchema
206
+ }] : [];
207
+ const op = (operation, summary, body) => ({
208
+ operationId: `${operation}${name}`,
209
+ tags: [tag],
210
+ summary,
211
+ ...extensions,
212
+ "x-better-supabase-operation": operation,
213
+ ...body
214
+ });
215
+ for (const operation of operations) switch (operation) {
216
+ case "list":
217
+ (paths[collection] ??= {})["get"] = op("list", `List ${key}`, {
218
+ parameters: resource.list?.openapi ?? pageParameters(),
219
+ responses: {
220
+ "200": jsonBody(ref(`${name}Page`), `A page of ${key}.`),
221
+ ...errorResponses([
222
+ 400,
223
+ 401,
224
+ 403
225
+ ])
226
+ }
227
+ });
228
+ break;
229
+ case "create":
230
+ (paths[collection] ??= {})["post"] = op("create", `Create a ${key} row`, {
231
+ requestBody: {
232
+ required: true,
233
+ content: { "application/json": { schema: ref(`${name}Insert`) } }
234
+ },
235
+ responses: {
236
+ "201": jsonBody(ref(`${name}Row`), "Created."),
237
+ ...errorResponses([
238
+ 400,
239
+ 401,
240
+ 403,
241
+ 409,
242
+ 422
243
+ ])
244
+ }
245
+ });
246
+ break;
247
+ case "get":
248
+ case "update":
249
+ case "delete": {
250
+ if (!item) break;
251
+ const path = paths[item] ??= { parameters: idParameter };
252
+ if (operation === "get") path["get"] = op("get", `Get a ${key} row`, { responses: {
253
+ "200": jsonBody(ref(`${name}Row`), "The row."),
254
+ ...errorResponses([
255
+ 401,
256
+ 403,
257
+ 404
258
+ ])
259
+ } });
260
+ else if (operation === "update") path["patch"] = op("update", `Update a ${key} row`, {
261
+ requestBody: {
262
+ required: true,
263
+ content: { "application/json": { schema: ref(`${name}Update`) } }
264
+ },
265
+ responses: {
266
+ "200": jsonBody(ref(`${name}Row`), "Updated."),
267
+ ...errorResponses([
268
+ 400,
269
+ 401,
270
+ 403,
271
+ 404,
272
+ 409,
273
+ 422
274
+ ])
275
+ }
276
+ });
277
+ else path["delete"] = op("delete", `Delete a ${key} row`, { responses: {
278
+ "204": { description: "Deleted." },
279
+ ...errorResponses([
280
+ 401,
281
+ 403,
282
+ 404,
283
+ 409
284
+ ])
285
+ } });
286
+ break;
287
+ }
288
+ default: throw new TypeError(`createOpenApi: unknown operation "${String(operation)}"`);
289
+ }
290
+ }
291
+ const securitySchemes = {};
292
+ if (security.includes("bearer")) securitySchemes["supabaseJwt"] = {
293
+ type: "http",
294
+ scheme: "bearer",
295
+ bearerFormat: "JWT",
296
+ description: "A Supabase access token. Requests run as that user under row-level security."
297
+ };
298
+ if (security.includes("oauth2")) {
299
+ if (!options.supabaseUrl) throw new TypeError("createOpenApi: oauth2 security needs supabaseUrl");
300
+ const auth = `${options.supabaseUrl.replace(/\/$/, "")}/auth/v1`;
301
+ securitySchemes["supabaseOAuth"] = {
302
+ type: "oauth2",
303
+ description: "The Supabase OAuth 2.1 server.",
304
+ flows: { authorizationCode: {
305
+ authorizationUrl: `${auth}/oauth/authorize`,
306
+ tokenUrl: `${auth}/oauth/token`,
307
+ scopes: {}
308
+ } }
309
+ };
310
+ }
311
+ const responses = {};
312
+ for (const [name, , description] of ERRORS) responses[name] = {
313
+ description,
314
+ content: { [PROBLEM_CONTENT_TYPE]: { schema: ref("Problem") } }
315
+ };
316
+ return {
317
+ openapi: SPEC_PINS.openapi,
318
+ info: options.info,
319
+ jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema",
320
+ ...options.servers ? { servers: options.servers } : {},
321
+ tags,
322
+ paths,
323
+ components: {
324
+ schemas,
325
+ responses,
326
+ securitySchemes
327
+ },
328
+ security: Object.keys(securitySchemes).map((name) => ({ [name]: [] }))
329
+ };
330
+ }
331
+ //#endregion
332
+ export { PROBLEM_SCHEMA, createOpenApi };
@@ -0,0 +1,46 @@
1
+ import { n as AnyModels, t as AnyFunctions } from "../types-C2fslP1z.js";
2
+ import { o as Result, p as DbError } from "../result-CTkCZ6JA.js";
3
+ import { t as BetterSupabase } from "../define-CTfWw-SS.js";
4
+ import { r as AuthState } from "../resolve-DW8h9NPm.js";
5
+ import { d as ServerContext, f as ServerOptions, l as BetterServer, n as GuardOptions } from "../respond-PoTpy5DT.js";
6
+ import { r as ProblemDetails } from "../problem-DlgB0lYl.js";
7
+ import { DecoratedMiddleware, ORPCError } from "@orpc/server";
8
+ //#region src/orpc/index.d.ts
9
+ /** Initial context: pass `{ context: { request } }` to the oRPC handler. */
10
+ export interface OrpcRequestContext {
11
+ readonly request: Request;
12
+ }
13
+ /** What `middleware()` adds to `context`. */
14
+ export interface OrpcContext<M extends AnyModels, F extends AnyFunctions, E> {
15
+ readonly bs: ServerContext<M, F, E>;
16
+ readonly db: ServerContext<M, F, E>["db"];
17
+ readonly auth: AuthState;
18
+ }
19
+ export interface OrpcOptions extends ServerOptions {
20
+ /** Include internal error messages. Defaults to `NODE_ENV === 'development'`. */
21
+ readonly exposeErrors?: boolean;
22
+ }
23
+ export interface MiddlewareOptions extends GuardOptions {
24
+ /** Refresh an expired cookie session. Pair with `ctx.bs.resolution.apply` in the handler adapter. */
25
+ readonly refresh?: boolean;
26
+ }
27
+ export type OrpcMiddleware<M extends AnyModels, F extends AnyFunctions, E> = DecoratedMiddleware<OrpcRequestContext, OrpcContext<M, F, E>, unknown, unknown, Record<never, never>>;
28
+ export interface BetterOrpc<M extends AnyModels, F extends AnyFunctions, E> extends BetterServer<M, F, E> {
29
+ /**
30
+ * Resolves the caller, enforces `allow` and adds `context.db`, `context.auth`
31
+ * and `context.bs`. Thrown `DbException`s, and errors caused by a
32
+ * `DbError`, become `ORPCError`s.
33
+ */
34
+ middleware(options?: MiddlewareOptions): OrpcMiddleware<M, F, E>;
35
+ /** A `Result` (or `AsyncResult`) as data, throwing an `ORPCError` on failure. */
36
+ unwrap<T>(value: Result<T> | PromiseLike<Result<T>>): Promise<T>;
37
+ /** Plain values pass through; thrown `DbException`s become `ORPCError`s. */
38
+ unwrap<T>(value: T | PromiseLike<T>): Promise<T>;
39
+ toORPCError(error: DbError): ORPCError<string, ProblemDetails>;
40
+ }
41
+ /** oRPC code for a `DbError`, from its HTTP status. */
42
+ export declare function orpcCode(error: DbError): string;
43
+ /** oRPC integration: middleware adding the caller's repositories to `context`. */
44
+ export declare function createOrpc<M extends AnyModels, D, F extends AnyFunctions, E>(sb: BetterSupabase<M, D, F, E>, options?: OrpcOptions): BetterOrpc<M, F, E>;
45
+ //#endregion
46
+ export type { GuardOptions };
@@ -0,0 +1,50 @@
1
+ import { r as dbErrorOf } from "../errors-Bl3rkOzo.js";
2
+ import { o as toProblem } from "../problem-DPu6Fh-t.js";
3
+ import { a as settle, n as guard, o as createServer, s as extendServer, t as defaultExpose } from "../respond-4-dhoV7x.js";
4
+ import { COMMON_ERROR_STATUS_MAP, ORPCError, os } from "@orpc/server";
5
+ //#region src/orpc/index.ts
6
+ const CODES = new Map(Object.entries(COMMON_ERROR_STATUS_MAP).map(([code, status]) => [status, code]));
7
+ /** oRPC code for a `DbError`, from its HTTP status. */
8
+ function orpcCode(error) {
9
+ return CODES.get(error.status) ?? (error.status >= 500 ? "INTERNAL_SERVER_ERROR" : "BAD_REQUEST");
10
+ }
11
+ /** oRPC integration: middleware adding the caller's repositories to `context`. */
12
+ function createOrpc(sb, options = {}) {
13
+ const server = createServer(sb, options);
14
+ const expose = options.exposeErrors ?? defaultExpose();
15
+ const toORPCError = (error) => {
16
+ const problem = toProblem(error, { expose });
17
+ return new ORPCError(orpcCode(error), {
18
+ message: problem.detail ?? problem.title,
19
+ data: problem
20
+ });
21
+ };
22
+ return extendServer(server, {
23
+ toORPCError,
24
+ middleware(middlewareOptions = {}) {
25
+ return os.$context().middleware(async ({ context, next }) => {
26
+ const ctx = await server.context(context.request, { refresh: middlewareOptions.refresh ?? false });
27
+ const denied = guard(ctx.auth, middlewareOptions.allow, middlewareOptions.aal);
28
+ if (denied) throw toORPCError(denied);
29
+ try {
30
+ return await next({ context: {
31
+ bs: ctx,
32
+ db: ctx.db,
33
+ auth: ctx.auth
34
+ } });
35
+ } catch (cause) {
36
+ const thrown = dbErrorOf(cause);
37
+ if (thrown) throw toORPCError(thrown);
38
+ throw cause;
39
+ }
40
+ });
41
+ },
42
+ async unwrap(value) {
43
+ const settled = await settle(() => value);
44
+ if (!settled.ok) throw toORPCError(settled.error);
45
+ return settled.data;
46
+ }
47
+ });
48
+ }
49
+ //#endregion
50
+ export { createOrpc, orpcCode };
@@ -0,0 +1,40 @@
1
+ import { a as EventHub } from "../events-2OJsVG_d.js";
2
+ import { f as Plugin } from "../plugin-HvGqCurB.js";
3
+ import { t as SPEC_PINS } from "../spec-pins-CJPVghHy.js";
4
+ import { Attributes, Meter, Tracer } from "@opentelemetry/api";
5
+ //#region src/otel/index.d.ts
6
+ export declare const INSTRUMENTATION_NAME = "better-supabase";
7
+ /** OpenTelemetry semantic conventions version the attributes follow. */
8
+ export declare const SEMCONV_VERSION: typeof SPEC_PINS.otelSemconv;
9
+ export interface OtelOptions {
10
+ readonly tracer?: Tracer;
11
+ readonly meter?: Meter;
12
+ /** Extra attributes on every span, e.g. `{ 'service.namespace': 'crm' }`. */
13
+ readonly attributes?: Attributes;
14
+ /** Set `false` to skip the duration histogram. */
15
+ readonly metrics?: boolean;
16
+ }
17
+ /**
18
+ * Database client spans and metrics for every repository operation, using
19
+ * the OpenTelemetry database conventions. Filter values and row data are
20
+ * never recorded; `db.query.summary` is `SELECT customers`.
21
+ *
22
+ * ```ts
23
+ * const sb = defineSupabase(schema).use(otel());
24
+ * ```
25
+ */
26
+ export declare function otel(options?: OtelOptions): Plugin<"otel">;
27
+ /**
28
+ * Spans for auth resolution and session refreshes, from `sb.events`.
29
+ * Returns a function that stops listening.
30
+ */
31
+ export declare function traceAuth(sb: {
32
+ readonly events: EventHub;
33
+ }, options?: Pick<OtelOptions, "tracer" | "attributes">): () => void;
34
+ /**
35
+ * A `fetch` that adds W3C `traceparent`/`tracestate` from the active
36
+ * context. Pass it to `createClient(url, key, { global: { fetch } })` so
37
+ * PostgREST, Auth and Storage calls join the trace.
38
+ */
39
+ export declare function tracedFetch(base?: typeof fetch): typeof fetch;
40
+ //#endregion
@@ -0,0 +1,168 @@
1
+ import { n as definePlugin } from "../plugin-BqR6wKMB.js";
2
+ import { t as SPEC_PINS } from "../spec-pins-t6sPaHvl.js";
3
+ import "../version-Cqq1m3UY.js";
4
+ import { SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
5
+ //#region src/otel/index.ts
6
+ const INSTRUMENTATION_NAME = "better-supabase";
7
+ /** OpenTelemetry semantic conventions version the attributes follow. */
8
+ const SEMCONV_VERSION = SPEC_PINS.otelSemconv;
9
+ const OPERATION_NAME = {
10
+ select: "SELECT",
11
+ insert: "INSERT",
12
+ update: "UPDATE",
13
+ delete: "DELETE"
14
+ };
15
+ function operationName(op) {
16
+ if (op.kind === "insert" && op.onConflict?.action === "update") return "UPSERT";
17
+ if (op.kind === "select" && op.head) return "COUNT";
18
+ return OPERATION_NAME[op.kind];
19
+ }
20
+ function systemOf(executor) {
21
+ return executor.name === "postgrest" || executor.name === "postgres" ? "postgresql" : executor.name;
22
+ }
23
+ /**
24
+ * Database client spans and metrics for every repository operation, using
25
+ * the OpenTelemetry database conventions. Filter values and row data are
26
+ * never recorded; `db.query.summary` is `SELECT customers`.
27
+ *
28
+ * ```ts
29
+ * const sb = defineSupabase(schema).use(otel());
30
+ * ```
31
+ */
32
+ function otel(options = {}) {
33
+ const tracer = options.tracer ?? trace.getTracer("better-supabase", "0.0.0");
34
+ let histogram;
35
+ if (options.metrics !== false) histogram = (options.meter ?? metrics.getMeter("better-supabase", "0.0.0")).createHistogram("db.client.operation.duration", {
36
+ unit: "s",
37
+ description: "Duration of database client operations.",
38
+ advice: { explicitBucketBoundaries: [
39
+ .001,
40
+ .005,
41
+ .01,
42
+ .05,
43
+ .1,
44
+ .5,
45
+ 1,
46
+ 5,
47
+ 10
48
+ ] }
49
+ });
50
+ return definePlugin({
51
+ name: "otel",
52
+ enforce: "post",
53
+ wrapExecutor: (executor) => ({
54
+ ...executor,
55
+ name: executor.name,
56
+ execute(op, executeContext) {
57
+ const operation = operationName(op);
58
+ const summary = `${operation} ${op.table.name}`;
59
+ const attributes = {
60
+ ...options.attributes,
61
+ "db.system.name": systemOf(executor),
62
+ "db.namespace": op.table.schema,
63
+ "db.collection.name": op.table.name,
64
+ "db.operation.name": operation,
65
+ "db.query.summary": summary,
66
+ "better_supabase.executor": executor.name
67
+ };
68
+ const started = performance.now();
69
+ return tracer.startActiveSpan(summary, {
70
+ kind: SpanKind.CLIENT,
71
+ attributes
72
+ }, async (span) => {
73
+ const finish = (errorType) => {
74
+ const metricAttributes = {
75
+ "db.system.name": attributes["db.system.name"],
76
+ "db.collection.name": op.table.name,
77
+ "db.operation.name": operation,
78
+ ...errorType ? { "error.type": errorType } : {}
79
+ };
80
+ histogram?.record((performance.now() - started) / 1e3, metricAttributes);
81
+ span.end();
82
+ };
83
+ try {
84
+ const result = await executor.execute(op, executeContext);
85
+ if (result.ok) {
86
+ span.setAttribute("db.response.returned_rows", result.data.rows.length);
87
+ finish();
88
+ } else {
89
+ span.setAttributes({
90
+ "error.type": result.error.kind,
91
+ ...result.error.code ? { "db.response.status_code": result.error.code } : {}
92
+ });
93
+ span.setStatus({
94
+ code: SpanStatusCode.ERROR,
95
+ message: result.error.message
96
+ });
97
+ finish(result.error.kind);
98
+ }
99
+ return result;
100
+ } catch (cause) {
101
+ const error = cause instanceof Error ? cause : new Error(String(cause));
102
+ span.recordException(error);
103
+ span.setAttribute("error.type", error.name);
104
+ span.setStatus({
105
+ code: SpanStatusCode.ERROR,
106
+ message: error.message
107
+ });
108
+ finish(error.name);
109
+ throw cause;
110
+ }
111
+ });
112
+ }
113
+ })
114
+ });
115
+ }
116
+ /**
117
+ * Spans for auth resolution and session refreshes, from `sb.events`.
118
+ * Returns a function that stops listening.
119
+ */
120
+ function traceAuth(sb, options = {}) {
121
+ const tracer = options.tracer ?? trace.getTracer("better-supabase", "0.0.0");
122
+ const offAuth = sb.events.on("auth", (event) => {
123
+ const span = tracer.startSpan("auth.resolve", {
124
+ kind: SpanKind.INTERNAL,
125
+ attributes: {
126
+ ...options.attributes,
127
+ "better_supabase.auth.source": event.source,
128
+ ...event.userId ? { "enduser.id": event.userId } : {}
129
+ }
130
+ });
131
+ if (!event.ok) span.setStatus({ code: SpanStatusCode.ERROR });
132
+ span.end();
133
+ });
134
+ const offRefresh = sb.events.on("refresh", (event) => {
135
+ const end = Date.now();
136
+ const span = tracer.startSpan("auth.refresh", {
137
+ kind: SpanKind.CLIENT,
138
+ startTime: end - event.durationMs,
139
+ attributes: {
140
+ ...options.attributes,
141
+ "better_supabase.refresh.shared": event.shared
142
+ }
143
+ });
144
+ if (!event.ok) span.setStatus({ code: SpanStatusCode.ERROR });
145
+ span.end(end);
146
+ });
147
+ return () => {
148
+ offAuth();
149
+ offRefresh();
150
+ };
151
+ }
152
+ /**
153
+ * A `fetch` that adds W3C `traceparent`/`tracestate` from the active
154
+ * context. Pass it to `createClient(url, key, { global: { fetch } })` so
155
+ * PostgREST, Auth and Storage calls join the trace.
156
+ */
157
+ function tracedFetch(base = fetch) {
158
+ return (input, init) => {
159
+ const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
160
+ propagation.inject(context.active(), headers, { set: (carrier, key, value) => carrier.set(key, value) });
161
+ return base(input, {
162
+ ...init,
163
+ headers
164
+ });
165
+ };
166
+ }
167
+ //#endregion
168
+ export { INSTRUMENTATION_NAME, SEMCONV_VERSION, otel, traceAuth, tracedFetch };
@@ -0,0 +1,17 @@
1
+ //#region src/storage/path.d.ts
2
+ declare const bucketOf: unique symbol;
3
+ /**
4
+ * An object path in bucket `B`, e.g. `org-1/cust-1/logo/3.webp`. Returned by
5
+ * `bucket.path()` and `upload()`, and the type of columns listed in the
6
+ * `storagePaths` config. Store paths, not URLs: signed URLs expire and public
7
+ * URLs pin the project host.
8
+ */
9
+ type StoragePath<B extends string = string> = string & {
10
+ readonly [bucketOf]: B;
11
+ };
12
+ /** A plain string or a `StoragePath` of bucket `B`; paths of other buckets are rejected. */
13
+ type PathIn<B extends string> = string & {
14
+ readonly [bucketOf]?: B;
15
+ };
16
+ //#endregion
17
+ export { StoragePath as n, PathIn as t };
@@ -0,0 +1,23 @@
1
+ //#region src/core/plugin.ts
2
+ const PLUGIN_API_VERSION = 1;
3
+ const RANK = {
4
+ pre: 0,
5
+ normal: 1,
6
+ post: 2
7
+ };
8
+ /** Plugins in hook order: `pre`, then unmarked in `use()` order, then `post`. */
9
+ function orderPlugins(plugins) {
10
+ return plugins.map((plugin, index) => ({
11
+ plugin,
12
+ index
13
+ })).sort((a, b) => RANK[a.plugin.enforce ?? "normal"] - RANK[b.plugin.enforce ?? "normal"] || a.index - b.index).map(({ plugin }) => plugin);
14
+ }
15
+ /** Identity helper that fixes `apiVersion` and infers the plugin types. */
16
+ function definePlugin(plugin) {
17
+ return {
18
+ ...plugin,
19
+ apiVersion: 1
20
+ };
21
+ }
22
+ //#endregion
23
+ export { definePlugin as n, orderPlugins as r, PLUGIN_API_VERSION as t };