better-supabase 0.2.0 → 0.3.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 (139) hide show
  1. package/README.md +34 -31
  2. package/dist/base64-CQeB1eSf.js +42 -0
  3. package/dist/client/index.d.ts +1 -1
  4. package/dist/client/index.js +4 -14
  5. package/dist/{compiler-CZ9nIuqy.d.ts → compiler-2zqGzxVZ.d.ts} +2 -2
  6. package/dist/config/index.d.ts +4 -3
  7. package/dist/config/index.js +5 -3
  8. package/dist/config-Be4GwsqI.js +129 -0
  9. package/dist/{index-DOsjFvMf.d.ts → config-C1CYcm6o.d.ts} +49 -49
  10. package/dist/{config-ClDXmVtY.js → config-DOsGYSiT.js} +14 -137
  11. package/dist/{define-BXMYD9AY.js → define-DF341GDj.js} +71 -51
  12. package/dist/{define-e3zI4_4g.d.ts → define-DUbydmJa.d.ts} +10 -9
  13. package/dist/edge/index.d.ts +4 -4
  14. package/dist/edge/index.js +4 -4
  15. package/dist/{entitlements-Dng-ctuT.d.ts → entitlements-DvJXoI02.d.ts} +1 -1
  16. package/dist/env/index.js +203 -1
  17. package/dist/events/index.d.ts +1 -1
  18. package/dist/events/index.js +139 -1
  19. package/dist/{events-DOt3nSO-.d.ts → events-Do8jXh4D.d.ts} +3 -3
  20. package/dist/{executor-BYXQ9AGn.d.ts → executor-B26lgbHD.d.ts} +2 -2
  21. package/dist/{executor-DeppvirK.js → executor-C57jx2pn.js} +7 -2
  22. package/dist/{executor-CGdNQ27g.d.ts → executor-DynmIWhq.d.ts} +2 -2
  23. package/dist/hono/index.d.ts +5 -5
  24. package/dist/hono/index.js +4 -4
  25. package/dist/{index-CBDufH1M.d.ts → index-BNsABq7P.d.ts} +8 -8
  26. package/dist/{index-Bw1bwtC0.d.ts → index-CADlNHK-.d.ts} +27 -14
  27. package/dist/{index-DuP14VHo.d.ts → index-CTTMS6_1.d.ts} +19 -8
  28. package/dist/index-Cf-D2jk1.d.ts +50 -0
  29. package/dist/{index-K-3WM2Hp.d.ts → index-CriFFJ_p.d.ts} +4 -4
  30. package/dist/{index-Ciu5Xlml.d.ts → index-GV77NpRP.d.ts} +5 -4
  31. package/dist/{index-D4v7sncu.d.ts → index-UJ-LUxtI.d.ts} +3 -3
  32. package/dist/{index-CG69s15v.d.ts → index-kpgopLNv.d.ts} +4 -3
  33. package/dist/{index-DbxFr7XG.d.ts → index-qriRM9cZ.d.ts} +7 -2
  34. package/dist/index.d.ts +12 -12
  35. package/dist/index.js +16 -7
  36. package/dist/jobs/index.d.ts +25 -24
  37. package/dist/jobs/index.js +16 -10
  38. package/dist/{json-schema-BpYqkCKf.js → json-schema--aOXAGfU.js} +7 -2
  39. package/dist/lint/index.js +1 -1
  40. package/dist/list/index.d.ts +2 -2
  41. package/dist/list/index.js +128 -93
  42. package/dist/{live-DJyZMD6w.d.ts → live-QE3UPbGm.d.ts} +16 -6
  43. package/dist/mcp/index.d.ts +24 -17
  44. package/dist/mcp/index.js +41 -12
  45. package/dist/{mfa-CpLfJSde.js → mfa-BxoR1RkN.js} +74 -19
  46. package/dist/{mfa-TVia554c.d.ts → mfa-x3V3vGh-.d.ts} +1 -1
  47. package/dist/next/index.d.ts +39 -26
  48. package/dist/next/index.js +28 -23
  49. package/dist/openapi/index.d.ts +1 -1
  50. package/dist/openapi/index.js +57 -34
  51. package/dist/orpc/index.d.ts +6 -6
  52. package/dist/orpc/index.js +3 -3
  53. package/dist/otel/index.d.ts +2 -2
  54. package/dist/otel/index.js +4 -2
  55. package/dist/{permdock-sql-CghlHPuH.js → permdock-sql-Bta_G0v-.js} +11 -6
  56. package/dist/permdock-sql-DImJULY-.d.ts +17 -0
  57. package/dist/{plugin-C4TR9J0Z.d.ts → plugin-DVRB8qNE.d.ts} +5 -4
  58. package/dist/plugins/actor/index.d.ts +1 -1
  59. package/dist/plugins/rules/index.d.ts +2 -2
  60. package/dist/plugins/rules/index.js +4 -4
  61. package/dist/plugins/soft-delete/index.d.ts +3 -3
  62. package/dist/plugins/soft-delete/index.js +2 -2
  63. package/dist/plugins/tenant/index.d.ts +2 -2
  64. package/dist/plugins/timestamps/index.d.ts +1 -1
  65. package/dist/plugins/timestamps/index.js +1 -1
  66. package/dist/plugins/validation/index.d.ts +2 -2
  67. package/dist/pool-B9qsLyFR.d.ts +71 -0
  68. package/dist/postgres/index.d.ts +10 -5
  69. package/dist/postgres/index.js +36 -16
  70. package/dist/{postgrest-cAEQ_IQN.d.ts → postgrest-DmBlevh-.d.ts} +1 -1
  71. package/dist/{problem-BTkTtRV9.js → problem-CGt1ADix.js} +10 -4
  72. package/dist/{problem-DlgB0lYl.d.ts → problem-Z2RTQjZD.d.ts} +6 -4
  73. package/dist/query/index.d.ts +2 -2
  74. package/dist/query/index.js +1 -1
  75. package/dist/{query-ChvDbsDE.js → query-BdKLTPlj.js} +2 -2
  76. package/dist/react/index.d.ts +7 -7
  77. package/dist/react/index.js +2 -2
  78. package/dist/react/server.d.ts +3 -3
  79. package/dist/react/server.js +1 -1
  80. package/dist/react/session.d.ts +21 -2
  81. package/dist/react/session.js +19 -1
  82. package/dist/{react-C83BdBGt.js → react-DNBYNazu.js} +20 -16
  83. package/dist/{read-set-4lI3W_gZ.js → read-set-D0qBcboM.js} +3 -3
  84. package/dist/realtime/index.d.ts +4 -3
  85. package/dist/realtime/index.js +7 -6
  86. package/dist/{resolve-CjHILk2w.d.ts → resolve-BRciX2jK.d.ts} +6 -5
  87. package/dist/{resource-CgZa3YG_.d.ts → resource-CwVgiI5q.d.ts} +16 -7
  88. package/dist/{resource-BHEWmQJK.js → resource-D30rp0GN.js} +38 -16
  89. package/dist/{respond-DMcbQwfN.js → respond-C44EZAJo.js} +35 -19
  90. package/dist/{respond-Dne1ARSa.d.ts → respond-DAkPIdTH.d.ts} +15 -9
  91. package/dist/{result-CTkCZ6JA.d.ts → result-c2OYgCV2.d.ts} +13 -8
  92. package/dist/server/index.d.ts +35 -10
  93. package/dist/server/index.js +38 -9
  94. package/dist/{simplify-bwJpLnaS.js → simplify-LakqK1vH.js} +10 -16
  95. package/dist/sql/index.d.ts +139 -0
  96. package/dist/sql/index.js +1983 -0
  97. package/dist/{sql-DTjA1LPj.js → sql-BIgk12ew.js} +72 -16
  98. package/dist/ssr/index.d.ts +4 -4
  99. package/dist/ssr/index.js +2 -2
  100. package/dist/{standard-CyhRzctb.d.ts → standard-eudBiRk1.d.ts} +1 -1
  101. package/dist/{stats-C5W-rBfR.js → stats-D78UQ8Yu.js} +8 -7
  102. package/dist/storage/index.d.ts +4 -3
  103. package/dist/storage/index.js +530 -1
  104. package/dist/{tables-rZxRxNPW.js → tables-Dhha3BXM.js} +5 -5
  105. package/dist/temporal-BwshQk0W.js +27 -0
  106. package/dist/temporal-required-CMxeSVPo.js +19 -0
  107. package/dist/testing/index.d.ts +13 -11
  108. package/dist/testing/index.js +128 -29
  109. package/dist/{types-C83v5SQz.d.ts → types-DONv9SHN.d.ts} +14 -11
  110. package/dist/{version-Cqq1m3UY.js → version-BfpG2gks.js} +1 -1
  111. package/dist/{view-D1s0wa0T.d.ts → view-3Po_PVlr.d.ts} +34 -4
  112. package/dist/{view-aOYizTeE.js → view-wZ-9qIUz.js} +11 -2
  113. package/dist/webhooks/index.d.ts +18 -16
  114. package/dist/webhooks/index.js +1 -1
  115. package/dist/{webhooks-DMipjhSP.js → webhooks-CAIFQSTM.js} +11 -9
  116. package/package.json +24 -28
  117. package/schemas/config-v1.json +31 -1
  118. package/schemas/doctor-report-v1.json +17 -1
  119. package/schemas/snapshot-v2.json +35 -0
  120. package/skills/better-supabase/SKILL.md +23 -11
  121. package/skills/better-supabase/references/plugins.md +21 -18
  122. package/skills/better-supabase/references/troubleshooting.md +15 -15
  123. package/skills/better-supabase-api/SKILL.md +7 -7
  124. package/skills/better-supabase-api/references/adapters.md +30 -25
  125. package/skills/better-supabase-testing/SKILL.md +11 -8
  126. package/bin/better-supabase.js +0 -4
  127. package/dist/casing-da0uRTqt.js +0 -14
  128. package/dist/cli/bin.d.ts +0 -1
  129. package/dist/cli/bin.js +0 -15
  130. package/dist/cli/index.d.ts +0 -226
  131. package/dist/cli/index.js +0 -3
  132. package/dist/cli-DrSgDOQC.js +0 -6646
  133. package/dist/env-BEToab0I.js +0 -204
  134. package/dist/events-BuzTgn2D.js +0 -135
  135. package/dist/pool-R2m_DkXi.d.ts +0 -37
  136. package/dist/seed-CzecODcJ.js +0 -101
  137. package/dist/session-C3LxnQ0e.d.ts +0 -22
  138. package/dist/session-CqEwzVup.js +0 -20
  139. package/dist/storage-DuGn38fr.js +0 -527
package/dist/env/index.js CHANGED
@@ -1,2 +1,204 @@
1
- import { a as parseEnv, i as loadEnv, n as EnvValidationError, o as publicEnv, r as envSchema, s as toServerEnv, t as ENV_VARIABLES } from "../env-BEToab0I.js";
1
+ //#region src/env/index.ts
2
+ const PREFIXES = [
3
+ "",
4
+ "NEXT_PUBLIC_",
5
+ "VITE_",
6
+ "PUBLIC_",
7
+ "EXPO_PUBLIC_",
8
+ "NUXT_PUBLIC_"
9
+ ];
10
+ const ENV_VARIABLES = {
11
+ url: PREFIXES.map((prefix) => `${prefix}SUPABASE_URL`),
12
+ publishableKey: PREFIXES.flatMap((prefix) => [`${prefix}SUPABASE_PUBLISHABLE_KEY`, `${prefix}SUPABASE_PUBLISHABLE_DEFAULT_KEY`]),
13
+ secretKey: ["SUPABASE_SECRET_KEY"],
14
+ dbUrl: ["SUPABASE_DB_URL", "DATABASE_URL"],
15
+ jwksUrl: ["SUPABASE_JWKS_URL"],
16
+ readUrl: ["SUPABASE_READ_URL"]
17
+ };
18
+ const LOOPBACK = /^(localhost|127(?:\.\d{1,3}){3}|\[::1\])$/;
19
+ const HOSTED = /^([a-z0-9]{20})\.supabase\.co$/;
20
+ var EnvValidationError = class extends Error {
21
+ name = "EnvValidationError";
22
+ issues;
23
+ constructor(issues) {
24
+ super(`Invalid Supabase environment:\n${issues.map((issue) => ` - ${issue.variables[0] ?? "env"}: ${issue.message}`).join("\n")}`);
25
+ this.issues = issues;
26
+ }
27
+ };
28
+ function first(source, names) {
29
+ for (const name of names) {
30
+ const value = source[name]?.trim();
31
+ if (value) return value;
32
+ }
33
+ }
34
+ /** `SUPABASE_*_KEYS` JSON objects, as `@supabase/server` reads them. */
35
+ function keySet(source, name, kind, issues) {
36
+ const raw = source[name];
37
+ if (!raw) return void 0;
38
+ let parsed;
39
+ try {
40
+ parsed = JSON.parse(raw);
41
+ } catch {
42
+ parsed = void 0;
43
+ }
44
+ const entries = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? Object.entries(parsed) : [];
45
+ if (entries.length === 0 || entries.some(([, value]) => typeof value !== "string")) {
46
+ issues.push({
47
+ variables: [name],
48
+ message: "must be a JSON object of key names to keys"
49
+ });
50
+ return;
51
+ }
52
+ const keys = {};
53
+ for (const [key, value] of entries) if (checkKey(value, kind, [name], issues)) keys[key] = value;
54
+ return keys;
55
+ }
56
+ function preferred(keys) {
57
+ if (!keys) return void 0;
58
+ return keys["default"] ?? Object.values(keys)[0];
59
+ }
60
+ function checkUrl(value, variables, issues) {
61
+ let url;
62
+ try {
63
+ url = new URL(value);
64
+ } catch {
65
+ issues.push({
66
+ variables,
67
+ message: "is not a valid URL"
68
+ });
69
+ return;
70
+ }
71
+ if (url.protocol === "https:") return url;
72
+ if (url.protocol === "http:" && LOOPBACK.test(url.hostname)) return url;
73
+ issues.push({
74
+ variables,
75
+ message: "must use https (http is only allowed for localhost / 127.0.0.1 / [::1])"
76
+ });
77
+ }
78
+ function checkKey(value, kind, variables, issues) {
79
+ if (value.startsWith(`sb_${kind}_`)) return true;
80
+ if (value.startsWith("eyJ")) issues.push({
81
+ variables,
82
+ message: `is a legacy JWT key; use the new sb_${kind}_ key (Project Settings > API Keys)`
83
+ });
84
+ else if (value.startsWith(kind === "publishable" ? "sb_secret_" : "sb_publishable_")) issues.push({
85
+ variables,
86
+ message: kind === "publishable" ? "holds a secret key; never expose sb_secret_ keys as publishable" : "holds a publishable key, not a secret key"
87
+ });
88
+ else issues.push({
89
+ variables,
90
+ message: `must start with sb_${kind}_`
91
+ });
92
+ return false;
93
+ }
94
+ /**
95
+ * Reads and validates Supabase settings. Accepts the `SUPABASE_`,
96
+ * `NEXT_PUBLIC_`, `VITE_`, `PUBLIC_`, `EXPO_PUBLIC_` and `NUXT_PUBLIC_`
97
+ * spellings. Issues name variables, never values.
98
+ */
99
+ function parseEnv(source, options = {}) {
100
+ const issues = [];
101
+ const required = new Set(options.require ?? []);
102
+ const rawUrl = first(source, ENV_VARIABLES.url);
103
+ const url = rawUrl ? checkUrl(rawUrl, ENV_VARIABLES.url, issues) : void 0;
104
+ if (!rawUrl) issues.push({
105
+ variables: ENV_VARIABLES.url,
106
+ message: "is not set"
107
+ });
108
+ const publishableKeys = keySet(source, "SUPABASE_PUBLISHABLE_KEYS", "publishable", issues);
109
+ const singlePublishable = first(source, ENV_VARIABLES.publishableKey);
110
+ const publishableKey = singlePublishable ?? preferred(publishableKeys);
111
+ if (!publishableKey) issues.push({
112
+ variables: ENV_VARIABLES.publishableKey,
113
+ message: "is not set"
114
+ });
115
+ else if (singlePublishable) checkKey(publishableKey, "publishable", ENV_VARIABLES.publishableKey, issues);
116
+ const namedSecrets = keySet(source, "SUPABASE_SECRET_KEYS", "secret", issues);
117
+ const singleSecret = first(source, ENV_VARIABLES.secretKey);
118
+ const secretKey = singleSecret ?? preferred(namedSecrets);
119
+ if (singleSecret) checkKey(singleSecret, "secret", ENV_VARIABLES.secretKey, issues);
120
+ else if (!secretKey && required.has("secretKey")) issues.push({
121
+ variables: ENV_VARIABLES.secretKey,
122
+ message: "is not set"
123
+ });
124
+ const dbUrl = first(source, ENV_VARIABLES.dbUrl);
125
+ if (dbUrl && !/^postgres(ql)?:\/\//.test(dbUrl)) issues.push({
126
+ variables: ENV_VARIABLES.dbUrl,
127
+ message: "must be a postgres:// connection string"
128
+ });
129
+ else if (!dbUrl && required.has("dbUrl")) issues.push({
130
+ variables: ENV_VARIABLES.dbUrl,
131
+ message: "is not set"
132
+ });
133
+ const rawJwks = first(source, ENV_VARIABLES.jwksUrl);
134
+ const jwksOverride = rawJwks ? checkUrl(rawJwks, ENV_VARIABLES.jwksUrl, issues) : void 0;
135
+ const rawRead = first(source, ENV_VARIABLES.readUrl);
136
+ const readUrl = rawRead ? checkUrl(rawRead, ENV_VARIABLES.readUrl, issues) : void 0;
137
+ if (issues.length > 0 || !url || !publishableKey) return {
138
+ ok: false,
139
+ issues
140
+ };
141
+ const base = url.href.replace(/\/+$/, "");
142
+ const projectRef = HOSTED.exec(url.hostname)?.[1];
143
+ return {
144
+ ok: true,
145
+ env: {
146
+ url: base,
147
+ publishableKey,
148
+ jwksUrl: jwksOverride ?? new URL(`${base}/auth/v1/.well-known/jwks.json`),
149
+ ...secretKey ? {
150
+ secretKey,
151
+ secretKeys: {
152
+ ...namedSecrets,
153
+ default: secretKey
154
+ }
155
+ } : {},
156
+ ...dbUrl ? { dbUrl } : {},
157
+ ...projectRef ? { projectRef } : {},
158
+ ...readUrl ? { readUrl: readUrl.href.replace(/\/+$/, "") } : {}
159
+ }
160
+ };
161
+ }
162
+ function processEnv() {
163
+ return globalThis.process?.env ?? {};
164
+ }
165
+ /** Like `parseEnv`, but throws `EnvValidationError`. Reads `process.env` by default. */
166
+ function loadEnv(source = processEnv(), options = {}) {
167
+ const result = parseEnv(source, options);
168
+ if (!result.ok) throw new EnvValidationError(result.issues);
169
+ return result.env;
170
+ }
171
+ /** The env validator as a Standard Schema, for t3-env, framework config or any validator slot. */
172
+ function envSchema(options = {}) {
173
+ return { "~standard": {
174
+ version: 1,
175
+ vendor: "better-supabase",
176
+ validate(value) {
177
+ if (typeof value !== "object" || value === null) return { issues: [{ message: "Expected an environment object" }] };
178
+ const result = parseEnv(value, options);
179
+ if (result.ok) return { value: result.env };
180
+ return { issues: result.issues.map((issue) => ({
181
+ message: `${issue.variables[0] ?? "env"} ${issue.message}`,
182
+ path: issue.variables.slice(0, 1)
183
+ })) };
184
+ }
185
+ } };
186
+ }
187
+ /** The parts that are safe in client bundles. */
188
+ function publicEnv(env) {
189
+ return {
190
+ url: env.url,
191
+ publishableKey: env.publishableKey
192
+ };
193
+ }
194
+ /** Converts to `@supabase/server`'s `SupabaseEnv` (the `env` option of `withSupabase`). */
195
+ function toServerEnv(env) {
196
+ return {
197
+ url: env.url,
198
+ publishableKeys: { default: env.publishableKey },
199
+ secretKeys: env.secretKeys ?? (env.secretKey ? { default: env.secretKey } : {}),
200
+ jwks: env.jwksUrl
201
+ };
202
+ }
203
+ //#endregion
2
204
  export { ENV_VARIABLES, EnvValidationError, envSchema, loadEnv, parseEnv, publicEnv, toServerEnv };
@@ -1,2 +1,2 @@
1
- import { a as HttpMode, c as RowEventData, d as httpSink, f as isCloudEvent, i as ForwardOptions, l as forwardMutations, m as toHttp, n as CloudEventOptions, o as HttpSinkOptions, p as toCloudEvents, r as EventSink, s as ROW_EVENT_TYPES, t as CloudEvent, u as fromHttp } from "../index-Ciu5Xlml.js";
1
+ import { a as HttpMode, c as RowEventData, d as httpSink, f as isCloudEvent, i as ForwardOptions, l as forwardMutations, m as toHttp, n as CloudEventOptions, o as HttpSinkOptions, p as toCloudEvents, r as EventSink, s as ROW_EVENT_TYPES, t as CloudEvent, u as fromHttp } from "../index-GV77NpRP.js";
2
2
  export { CloudEvent, CloudEventOptions, EventSink, ForwardOptions, HttpMode, HttpSinkOptions, ROW_EVENT_TYPES, RowEventData, forwardMutations, fromHttp, httpSink, isCloudEvent, toCloudEvents, toHttp };
@@ -1,2 +1,140 @@
1
- import { a as isCloudEvent, i as httpSink, n as forwardMutations, o as toCloudEvents, r as fromHttp, s as toHttp, t as ROW_EVENT_TYPES } from "../events-BuzTgn2D.js";
1
+ import { n as nowInstant } from "../temporal-BwshQk0W.js";
2
+ //#region src/events/index.ts
3
+ const ROW_EVENT_TYPES = {
4
+ insert: "dev.better-supabase.row.created",
5
+ upsert: "dev.better-supabase.row.upserted",
6
+ update: "dev.better-supabase.row.updated",
7
+ delete: "dev.better-supabase.row.deleted"
8
+ };
9
+ function subjectOf(meta, table, row) {
10
+ const key = meta?.tables[table]?.primaryKey;
11
+ if (!key || key.length === 0) return void 0;
12
+ const values = key.map((column) => row[column]);
13
+ if (values.some((value) => value === void 0 || value === null)) return void 0;
14
+ return `${table}/${values.map((value) => encodeURIComponent(String(value))).join(",")}`;
15
+ }
16
+ /**
17
+ * One CloudEvent per mutated row: `dev.better-supabase.row.created` with the
18
+ * app-cased row as `data`, the primary key as `subject`, the tenant as
19
+ * `partitionkey` and the actor as `actorid`.
20
+ */
21
+ function toCloudEvents(notice, options) {
22
+ const type = options.typePrefix ? `${options.typePrefix}.row.${ROW_EVENT_TYPES[notice.kind].split(".").at(-1)}` : ROW_EVENT_TYPES[notice.kind];
23
+ const time = (options.now ?? nowInstant)().toString();
24
+ return notice.rows.map((row) => {
25
+ const subject = subjectOf(options.meta, notice.table, row);
26
+ return {
27
+ specversion: "1.0",
28
+ id: options.id?.() ?? crypto.randomUUID(),
29
+ source: options.source,
30
+ type,
31
+ ...subject ? { subject } : {},
32
+ time,
33
+ datacontenttype: "application/json",
34
+ data: {
35
+ table: notice.table,
36
+ row
37
+ },
38
+ ...notice.context.tenant ? { partitionkey: notice.context.tenant } : {},
39
+ ...notice.context.actor?.id ? { actorid: notice.context.actor.id } : {}
40
+ };
41
+ });
42
+ }
43
+ /**
44
+ * Sends a CloudEvent for every mutation to `sink`. Returns a function that
45
+ * stops forwarding. Use an outbox (SQL kit) when events must not be lost.
46
+ */
47
+ function forwardMutations(sb, sink, options) {
48
+ return sb.events.on("mutation", (notice) => {
49
+ if (options.filter && !options.filter(notice)) return;
50
+ const events = toCloudEvents(notice, {
51
+ ...options,
52
+ meta: sb.meta
53
+ });
54
+ if (events.length === 0) return;
55
+ const report = (error) => {
56
+ (options.onError ?? ((cause) => {
57
+ sb.events.logger.error("event sink failed", { cause });
58
+ }))(error, events);
59
+ };
60
+ try {
61
+ Promise.resolve(sink.send(events)).catch(report);
62
+ } catch (error) {
63
+ report(error);
64
+ }
65
+ });
66
+ }
67
+ const STRUCTURED = "application/cloudevents+json";
68
+ const BATCH = "application/cloudevents-batch+json";
69
+ /** The HTTP protocol binding: headers and body for one event (or a batch). */
70
+ function toHttp(events, mode = "structured") {
71
+ const list = Array.isArray(events) ? events : [events];
72
+ switch (mode) {
73
+ case "batch": return [{
74
+ headers: { "content-type": BATCH },
75
+ body: JSON.stringify(list)
76
+ }];
77
+ case "structured": return list.map((event) => ({
78
+ headers: { "content-type": STRUCTURED },
79
+ body: JSON.stringify(event)
80
+ }));
81
+ case "binary": return list.map((event) => {
82
+ const headers = {};
83
+ for (const [key, value] of Object.entries(event)) {
84
+ if (key === "data" || key === "datacontenttype" || value === void 0) continue;
85
+ headers[`ce-${key}`] = encodeURIComponent(String(value)).replaceAll("%20", " ");
86
+ }
87
+ headers["content-type"] = event.datacontenttype ?? "application/json";
88
+ return {
89
+ headers,
90
+ body: event.data === void 0 ? "" : JSON.stringify(event.data)
91
+ };
92
+ });
93
+ default: throw new TypeError(`Unknown CloudEvents mode ${String(mode)}`);
94
+ }
95
+ }
96
+ function isCloudEvent(value) {
97
+ if (typeof value !== "object" || value === null) return false;
98
+ const event = value;
99
+ return event["specversion"] === "1.0" && typeof event["id"] === "string" && typeof event["source"] === "string" && typeof event["type"] === "string";
100
+ }
101
+ /** Reads CloudEvents from a request in any of the three HTTP modes. */
102
+ async function fromHttp(request) {
103
+ const contentType = request.headers.get("content-type") ?? "";
104
+ const text = await request.text();
105
+ if (contentType.startsWith(BATCH)) {
106
+ const parsed = JSON.parse(text);
107
+ if (!Array.isArray(parsed) || !parsed.every(isCloudEvent)) throw new TypeError("Invalid CloudEvents batch");
108
+ return parsed;
109
+ }
110
+ if (contentType.startsWith(STRUCTURED)) {
111
+ const parsed = JSON.parse(text);
112
+ if (!isCloudEvent(parsed)) throw new TypeError("Invalid CloudEvent");
113
+ return [parsed];
114
+ }
115
+ const event = {};
116
+ for (const [key, value] of request.headers) if (key.startsWith("ce-")) event[key.slice(3)] = decodeURIComponent(value);
117
+ if (contentType) event["datacontenttype"] = contentType;
118
+ if (text) event["data"] = contentType.includes("json") ? JSON.parse(text) : text;
119
+ if (!isCloudEvent(event)) throw new TypeError("Request is not a CloudEvent");
120
+ return [event];
121
+ }
122
+ /** POSTs events to `url`. Non-2xx responses reject. */
123
+ function httpSink(url, options = {}) {
124
+ const send = options.fetch ?? fetch;
125
+ return { async send(events) {
126
+ for (const message of toHttp(events, options.mode ?? "batch")) {
127
+ const response = await send(url, {
128
+ method: "POST",
129
+ headers: {
130
+ ...options.headers,
131
+ ...message.headers
132
+ },
133
+ body: message.body
134
+ });
135
+ if (!response.ok) throw new Error(`Event sink responded ${String(response.status)}`);
136
+ }
137
+ } };
138
+ }
139
+ //#endregion
2
140
  export { ROW_EVENT_TYPES, forwardMutations, fromHttp, httpSink, isCloudEvent, toCloudEvents, toHttp };
@@ -1,6 +1,6 @@
1
- import { g as Operation } from "./executor-CGdNQ27g.js";
2
- import { p as DbError } from "./result-CTkCZ6JA.js";
3
- import { h as RequestContext, l as MutationKind } from "./plugin-C4TR9J0Z.js";
1
+ import { g as Operation } from "./executor-DynmIWhq.js";
2
+ import { p as DbError } from "./result-c2OYgCV2.js";
3
+ import { h as RequestContext, l as MutationKind } from "./plugin-DVRB8qNE.js";
4
4
  //#region src/core/logger.d.ts
5
5
  type LogFields = Readonly<Record<string, unknown>>;
6
6
  /**
@@ -1,5 +1,5 @@
1
- import { r as Executor } from "./executor-CGdNQ27g.js";
2
- import { y as RawDbError } from "./result-CTkCZ6JA.js";
1
+ import { r as Executor } from "./executor-DynmIWhq.js";
2
+ import { y as RawDbError } from "./result-c2OYgCV2.js";
3
3
  //#region src/postgres/executor.d.ts
4
4
  /**
5
5
  * Anything that runs parameterized SQL: `ctx.postgres` from
@@ -1,6 +1,6 @@
1
1
  import { c as mapDbError, n as dbError } from "./errors-Bl3rkOzo.js";
2
2
  import { i as ok, n as err, o as toDbError } from "./result-DWXatkd6.js";
3
- import { t as compileSql } from "./sql-DTjA1LPj.js";
3
+ import { t as compileSql } from "./sql-BIgk12ew.js";
4
4
  //#region src/postgres/executor.ts
5
5
  function isPgError(value) {
6
6
  return typeof value === "object" && value !== null && typeof value.code === "string";
@@ -25,6 +25,11 @@ async function run(client, plan, op) {
25
25
  const [first] = await client.queryRaw(plan.count.text, [...plan.count.params]);
26
26
  count = first?.count ?? 0;
27
27
  } else if (op.kind !== "select") count = rows.length;
28
+ for (const next of plan.chunks ?? []) {
29
+ const more = await run(client, next, op);
30
+ rows.push(...more.rows);
31
+ count = (count ?? 0) + (more.count ?? 0);
32
+ }
28
33
  return {
29
34
  rows,
30
35
  count
@@ -74,7 +79,7 @@ async function executeOn(client, op, context) {
74
79
  if (context.signal?.aborted) return err(dbError("aborted", "The request was aborted"));
75
80
  let result;
76
81
  try {
77
- result = await run(client, plan, op);
82
+ result = plan.chunks && client.transaction ? await client.transaction((scoped) => run(scoped, plan, op)) : await run(client, plan, op);
78
83
  } catch (cause) {
79
84
  const raw = fromPgError(cause);
80
85
  return err(raw ? mapDbError(raw, context.errorMappers) : toDbError(cause));
@@ -1,5 +1,5 @@
1
- import { P as TableMeta, T as RelationMeta, c as Codec } from "./types-C83v5SQz.js";
2
- import { o as Result, v as ErrorMapper } from "./result-CTkCZ6JA.js";
1
+ import { P as TableMeta, T as RelationMeta, c as Codec } from "./types-DONv9SHN.js";
2
+ import { o as Result, v as ErrorMapper } from "./result-c2OYgCV2.js";
3
3
  //#region src/ir/types.d.ts
4
4
  /**
5
5
  * The query IR. Every repository call becomes one of these operations; the
@@ -1,8 +1,8 @@
1
- import { N as TableKey, n as AnyModels, t as AnyFunctions } from "../types-C83v5SQz.js";
2
- import { t as BetterSupabase } from "../define-e3zI4_4g.js";
3
- import { r as AuthState } from "../resolve-CjHILk2w.js";
4
- import { d as ServerContext, f as ServerOptions, l as BetterServer, n as GuardOptions } from "../respond-Dne1ARSa.js";
5
- import { i as ResourceRouteOptions } from "../resource-CgZa3YG_.js";
1
+ import { N as TableKey, n as AnyModels, t as AnyFunctions } from "../types-DONv9SHN.js";
2
+ import { t as BetterSupabase } from "../define-DUbydmJa.js";
3
+ import { r as AuthState } from "../resolve-BRciX2jK.js";
4
+ import { d as ServerContext, f as ServerOptions, l as BetterServer, n as GuardOptions } from "../respond-DAkPIdTH.js";
5
+ import { a as ResourceRouteOptions } from "../resource-CwVgiI5q.js";
6
6
  import { Context, ErrorHandler, Hono, MiddlewareHandler } from "hono";
7
7
  //#region src/hono/index.d.ts
8
8
  /** Hono `Env` with the request's better-supabase context in `c.var`. */
@@ -1,7 +1,7 @@
1
1
  import { n as dbError, r as dbErrorOf } from "../errors-Bl3rkOzo.js";
2
- import { a as problemResponse } from "../problem-BTkTtRV9.js";
3
- import { i as respond, n as guard, o as createServer, s as extendServer, t as defaultExpose } from "../respond-DMcbQwfN.js";
4
- import { t as defineResource } from "../resource-BHEWmQJK.js";
2
+ import { a as problemResponse } from "../problem-CGt1ADix.js";
3
+ import { i as respond, n as guard, o as createServer, s as extendServer, t as defaultExpose } from "../respond-C44EZAJo.js";
4
+ import { t as defineResource } from "../resource-D30rp0GN.js";
5
5
  import { Hono } from "hono";
6
6
  //#region src/hono/index.ts
7
7
  function contextOf(c) {
@@ -31,7 +31,7 @@ function createHono(sb, options = {}) {
31
31
  middleware(middlewareOptions = {}) {
32
32
  return async (c, next) => {
33
33
  const ctx = await server.context(c.req.raw, { refresh: middlewareOptions.refresh ?? false });
34
- const denied = guard(ctx.auth, middlewareOptions.allow, middlewareOptions.aal);
34
+ const denied = guard(ctx.auth, middlewareOptions.allow, middlewareOptions.aal, middlewareOptions.scopes);
35
35
  if (denied) return ctx.resolution.apply(problemResponse(denied, {
36
36
  instance: new URL(c.req.url).pathname,
37
37
  expose
@@ -1,16 +1,16 @@
1
- import { p as DbError } from "./result-CTkCZ6JA.js";
2
- import { Z as QuerySpec } from "./define-e3zI4_4g.js";
3
- import { i as BrowserAuth, t as AuthSnapshot } from "./index-D4v7sncu.js";
1
+ import { p as DbError } from "./result-c2OYgCV2.js";
2
+ import { Z as QuerySpec } from "./define-DUbydmJa.js";
3
+ import { i as BrowserAuth, t as AuthSnapshot } from "./index-UJ-LUxtI.js";
4
4
  import { n as TemplateValues } from "./template-mbvlysOo.js";
5
- import { t as AuthSession } from "./view-D1s0wa0T.js";
6
- import { a as LiveSource, g as Topic, h as SubscriptionStatus, p as SubscribeOptions, r as LiveCountSeed, u as EventSchemas, v as TopicHandlers, y as TopicMessage } from "./live-DJyZMD6w.js";
7
- import "./entitlements-Dng-ctuT.js";
8
- import "./session-C3LxnQ0e.js";
5
+ import { t as AuthSession } from "./view-3Po_PVlr.js";
6
+ import { a as LiveSource, g as Topic, h as SubscriptionStatus, p as SubscribeOptions, r as LiveCountSeed, u as EventSchemas, v as TopicHandlers, y as TopicMessage } from "./live-QE3UPbGm.js";
7
+ import "./entitlements-DvJXoI02.js";
8
+ import "./react/session.js";
9
9
  import { SupabaseClient } from "@supabase/supabase-js";
10
10
  import { ReactNode } from "react";
11
11
  import { StandardSchemaV1 } from "@standard-schema/spec";
12
12
  import { QueryClient } from "@tanstack/query-core";
13
- //#region src/react/index.d.ts
13
+ //#region src/react/hooks.d.ts
14
14
  /** The parts of `createBrowser()` the provider needs. */
15
15
  interface BrowserLike {
16
16
  readonly sb: LiveSource;
@@ -1,14 +1,16 @@
1
- import { N as TableKey, O as Row, n as AnyModels, t as AnyFunctions } from "./types-C83v5SQz.js";
2
- import { l as CountMode } from "./executor-CGdNQ27g.js";
3
- import { b as ValidationIssue, t as AsyncResult } from "./result-CTkCZ6JA.js";
4
- import { C as FindExt, Ct as WhereInput, O as RepositoryOf, T as OffsetPageArgs, dt as IncludeArg, gt as OrderByArg, t as BetterSupabase, vt as Payload, w as OffsetPage, xt as SelectArg } from "./define-e3zI4_4g.js";
1
+ import { N as TableKey, O as Row, n as AnyModels, t as AnyFunctions } from "./types-DONv9SHN.js";
2
+ import { l as CountMode } from "./executor-DynmIWhq.js";
3
+ import { b as ValidationIssue, t as AsyncResult } from "./result-c2OYgCV2.js";
4
+ import { C as FindExt, Ct as WhereInput, O as RepositoryOf, T as OffsetPageArgs, _ as CursorPage, dt as IncludeArg, gt as OrderByArg, t as BetterSupabase, v as CursorPageArgs, vt as Payload, w as OffsetPage, xt as SelectArg } from "./define-DUbydmJa.js";
5
5
  import { StandardSchemaV1 } from "@standard-schema/spec";
6
- //#region src/list/index.d.ts
6
+ //#region src/list/list-query.d.ts
7
7
  /** Facet value meaning "not set": filters with `is null`. */
8
8
  declare const UNSET = "__unset__";
9
9
  type Column<M extends AnyModels, T extends keyof M> = Extract<keyof Row<M, T>, string>;
10
10
  type TextColumn<M extends AnyModels, T extends keyof M> = { [K in Column<M, T>]: [NonNullable<Row<M, T>[K]>] extends [string] ? K : never; }[Column<M, T>];
11
- interface ListQueryConfig<M extends AnyModels, T extends keyof M, S extends string, F extends string, C extends boolean = boolean> {
11
+ /** `offset` pages by number; `cursor` continues after the last row (keyset). */
12
+ type ListPagination = "offset" | "cursor";
13
+ interface ListQueryConfig<M extends AnyModels, T extends keyof M, S extends string, F extends string, C extends boolean = boolean, P extends ListPagination = ListPagination> {
12
14
  /**
13
15
  * Also count rows per facet value, with one grouped aggregate that runs
14
16
  * next to the page: 2 calls, 1 wave. Needs PostgREST aggregates.
@@ -26,6 +28,12 @@ interface ListQueryConfig<M extends AnyModels, T extends keyof M, S extends stri
26
28
  /** Named orderings. Add a unique column last for stable pages. */
27
29
  readonly sorts: { readonly [K in S]: OrderByArg<M, T>; };
28
30
  readonly defaultSort: NoInfer<S>;
31
+ /**
32
+ * `cursor` takes an `after` cursor instead of `page` and returns
33
+ * `nextCursor`: the cost of a page stays the same however deep it is, and
34
+ * rows inserted meanwhile don't shift it. Defaults to `offset`.
35
+ */
36
+ readonly pagination?: P;
29
37
  /** Defaults to 50. */
30
38
  readonly pageSize?: number;
31
39
  /** Defaults to 200. */
@@ -36,8 +44,10 @@ interface ListQueryConfig<M extends AnyModels, T extends keyof M, S extends stri
36
44
  interface ListQuery<S extends string, F extends string> {
37
45
  readonly q?: string;
38
46
  readonly sort: S;
39
- /** 1-based. */
47
+ /** 1-based. Always 1 with cursor pagination. */
40
48
  readonly page: number;
49
+ /** Cursor pagination: the `nextCursor` of the previous page. */
50
+ readonly after?: string;
41
51
  readonly size: number;
42
52
  readonly facets: { readonly [K in F]?: readonly string[]; };
43
53
  }
@@ -46,6 +56,7 @@ interface ListQueryInput<S extends string, F extends string> {
46
56
  readonly q?: string;
47
57
  readonly sort?: S;
48
58
  readonly page?: number;
59
+ readonly after?: string;
49
60
  readonly size?: number;
50
61
  readonly facets?: { readonly [K in F]?: readonly string[]; };
51
62
  }
@@ -83,13 +94,14 @@ interface FacetInfo {
83
94
  /** Rows per facet value, keyed by facet and then by value (`UNSET` for empty). */
84
95
  type FacetCounts<F extends string> = { readonly [K in F]: Readonly<Record<string, number>>; };
85
96
  /** A page of a list; with `facetCounts: true`, also the counts per facet value. */
86
- type ListPage<R, F extends string, C extends boolean> = OffsetPage<R> & ([C] extends [true] ? {
97
+ type ListPage<R, F extends string, C extends boolean, P extends ListPagination = "offset"> = ([P] extends ["cursor"] ? CursorPage<R> : OffsetPage<R>) & ([C] extends [true] ? {
87
98
  readonly facetCounts: FacetCounts<F>;
88
99
  } : [C] extends [false] ? unknown : {
89
100
  readonly facetCounts?: FacetCounts<F>;
90
101
  });
91
- interface ListDefinition<M extends AnyModels, T extends TableKey<M>, E, S extends string, F extends string, C extends boolean = false> {
102
+ interface ListDefinition<M extends AnyModels, T extends TableKey<M>, E, S extends string, F extends string, C extends boolean = false, P extends ListPagination = "offset"> {
92
103
  readonly table: T;
104
+ readonly pagination: P;
93
105
  readonly defaults: ListQuery<S, F>;
94
106
  readonly facets: readonly FacetInfo[];
95
107
  /** Validates typed input or URL params. Also a Standard Schema (`schema`). */
@@ -100,13 +112,13 @@ interface ListDefinition<M extends AnyModels, T extends TableKey<M>, E, S extend
100
112
  /** `{ parse, serialize }` per URL key, for routers and nuqs (`list.nuqs(createParser)`). */
101
113
  readonly parsers: ListParsers<S, F>;
102
114
  nuqs<P>(createParser: (spec: ParserSpec<unknown>) => P): { readonly [K in keyof ListParsers<S, F>]: P; };
103
- /** Repository arguments: `where`, `orderBy`, `page`, `size`, `count`. */
104
- args(query: ListQuery<S, F>): OffsetPageArgs<M, T>;
115
+ /** Repository arguments: `where`, `orderBy`, `size`, and `page` and `count` or `after`. */
116
+ args(query: ListQuery<S, F>): [P] extends ["cursor"] ? CursorPageArgs<M, T> : OffsetPageArgs<M, T>;
105
117
  /**
106
118
  * Runs the list against a table, merging your `select`, `include`, `where`
107
119
  * and `count`. One request, plus one for `facetCounts`, always one wave.
108
120
  */
109
- run<const A extends ListExtra<M, T> & FindExt<E, M, T>>(db: { readonly [K in T]: RepositoryOf<M, K, E>; }, query: ListQuery<S, F>, extra?: A): AsyncResult<ListPage<Payload<M, T, A>, F, C>>;
121
+ run<const A extends ListExtra<M, T> & FindExt<E, M, T>>(db: { readonly [K in T]: RepositoryOf<M, K, E>; }, query: ListQuery<S, F>, extra?: A): AsyncResult<ListPage<Payload<M, T, A>, F, C, P>>;
110
122
  /** OpenAPI 3.1 query parameters. */
111
123
  readonly openapi: readonly OpenApiParameter[];
112
124
  /** JSON Schema of the typed input, e.g. for MCP tool arguments. */
@@ -123,6 +135,7 @@ type ListParsers<S extends string, F extends string> = {
123
135
  readonly q: ParserSpec<string>;
124
136
  readonly sort: ParserSpec<S>;
125
137
  readonly page: ParserSpec<number>;
138
+ readonly after: ParserSpec<string>;
126
139
  readonly size: ParserSpec<number>;
127
140
  } & { readonly [K in F]: ParserSpec<readonly string[]>; };
128
141
  /**
@@ -139,6 +152,6 @@ type ListParsers<S extends string, F extends string> = {
139
152
  * });
140
153
  * ```
141
154
  */
142
- declare function defineListQuery<M extends AnyModels, D, Fn extends AnyFunctions, E, T extends TableKey<M>, const S extends string, const F extends string = never, const C extends boolean = false>(sb: BetterSupabase<M, D, Fn, E>, table: T, config: ListQueryConfig<M, T, S, F, C>): ListDefinition<M, T, E, S, F, C>;
155
+ declare function defineListQuery<M extends AnyModels, D, Fn extends AnyFunctions, E, T extends TableKey<M>, const S extends string, const F extends string = never, const C extends boolean = false, const P extends ListPagination = "offset">(sb: BetterSupabase<M, D, Fn, E>, table: T, config: ListQueryConfig<M, T, S, F, C, P>): ListDefinition<M, T, E, S, F, C, P>;
143
156
  //#endregion
144
- export { ListPage as a, ListQuery as c, ListSearchParams as d, OpenApiParameter as f, defineListQuery as h, ListExtra as i, ListQueryConfig as l, UNSET as m, FacetInfo as n, ListParseResult as o, ParserSpec as p, ListDefinition as r, ListParsers as s, FacetCounts as t, ListQueryInput as u };
157
+ export { ListPage as a, ListParsers as c, ListQueryInput as d, ListSearchParams as f, defineListQuery as g, UNSET as h, ListExtra as i, ListQuery as l, ParserSpec as m, FacetInfo as n, ListPagination as o, OpenApiParameter as p, ListDefinition as r, ListParseResult as s, FacetCounts as t, ListQueryConfig as u };
@@ -1,9 +1,11 @@
1
- import { b as PermdockBucketPolicy, i as BucketPolicyName } from "./types-C83v5SQz.js";
2
- import { p as DbError, t as AsyncResult } from "./result-CTkCZ6JA.js";
1
+ /// <reference lib="esnext.temporal" />
2
+ import { b as PermdockBucketPolicy, i as BucketPolicyName } from "./types-DONv9SHN.js";
3
+ import { p as DbError, t as AsyncResult } from "./result-c2OYgCV2.js";
3
4
  import { n as StoragePath, t as PathIn } from "./path-Cv__-OTj.js";
5
+ import { t as PermdockCatalog } from "./permdock-sql-DImJULY-.js";
4
6
  import { n as TemplateValues, t as TemplateParams } from "./template-mbvlysOo.js";
5
7
  import { SupabaseClient } from "@supabase/supabase-js";
6
- //#region src/storage/index.d.ts
8
+ //#region src/storage/bucket.d.ts
7
9
  type BucketPolicy = BucketPolicyName | PermdockBucketPolicy;
8
10
  interface BucketConfig<P extends string = string, Id extends string = string> {
9
11
  /** Bucket id, e.g. `customer-logos`. */
@@ -20,10 +22,16 @@ interface BucketConfig<P extends string = string, Id extends string = string> {
20
22
  * The helpers check role and scope only. Use `permdock` just for
21
23
  * permissions whose grants have no row conditions beyond the scope: for a
22
24
  * permission with row conditions (e.g. `ownerId = principal.id`) the bucket
23
- * grants every object in the scope. Leave those to the policies
24
- * `permdock rls generate` writes.
25
+ * would grant every object in the scope. Pass `catalog` to refuse those
26
+ * keys here; `better-supabase doctor` (BS214) refuses them from the
27
+ * catalog file.
25
28
  */
26
29
  readonly policy?: BucketPolicy;
30
+ /**
31
+ * PermDock's `permissions.catalog.json`. With it, a `permdock` policy
32
+ * naming a permission with `rowConditions: true` throws.
33
+ */
34
+ readonly catalog?: PermdockCatalog;
27
35
  /** `'5MiB'`, `'500KB'` or bytes. */
28
36
  readonly fileSizeLimit?: string | number;
29
37
  /** `['image/png', 'image/*']`. */
@@ -105,14 +113,17 @@ interface ReplaceResult<Id extends string = string> {
105
113
  interface SweepOptions<P extends string> {
106
114
  /** Only look under the path prefix these values fill. */
107
115
  readonly within?: Partial<TemplateValues<P>>;
108
- /** Minimum age in milliseconds, or a cutoff date. */
109
- readonly olderThan: number | Date;
116
+ /**
117
+ * A cutoff instant, or a minimum age. A duration counts a day as 24 hours
118
+ * and cannot use months or years.
119
+ */
120
+ readonly olderThan: Temporal.Instant | Temporal.Duration;
110
121
  /** Returns which of `paths` are still referenced. */
111
122
  readonly referenced: (paths: readonly string[]) => PromiseLike<Iterable<string>> | Iterable<string>;
112
123
  readonly dryRun?: boolean;
113
124
  readonly batchSize?: number;
114
125
  readonly signal?: AbortSignal;
115
- readonly now?: () => number;
126
+ readonly now?: () => Temporal.Instant;
116
127
  }
117
128
  interface SweepResult {
118
129
  readonly scanned: number;