better-supabase 0.1.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 (149) hide show
  1. package/README.md +34 -31
  2. package/dist/base64-CQeB1eSf.js +42 -0
  3. package/dist/claims-CoTobNC-.js +25 -0
  4. package/dist/client/index.d.ts +1 -1
  5. package/dist/client/index.js +4 -14
  6. package/dist/{compiler-DL18ukPe.d.ts → compiler-2zqGzxVZ.d.ts} +2 -2
  7. package/dist/config/index.d.ts +4 -3
  8. package/dist/config/index.js +5 -3
  9. package/dist/config-Be4GwsqI.js +129 -0
  10. package/dist/{index-BD6wKthp.d.ts → config-C1CYcm6o.d.ts} +75 -54
  11. package/dist/{config-D-aR2ZoN.js → config-DOsGYSiT.js} +14 -136
  12. package/dist/{define-QGkq04oe.js → define-DF341GDj.js} +133 -110
  13. package/dist/{define-CTfWw-SS.d.ts → define-DUbydmJa.d.ts} +32 -14
  14. package/dist/edge/index.d.ts +4 -4
  15. package/dist/edge/index.js +4 -4
  16. package/dist/entitlements-CTEBm7IB.js +20 -0
  17. package/dist/entitlements-DvJXoI02.d.ts +42 -0
  18. package/dist/env/index.js +203 -1
  19. package/dist/events/index.d.ts +1 -1
  20. package/dist/events/index.js +139 -1
  21. package/dist/{events-2OJsVG_d.d.ts → events-Do8jXh4D.d.ts} +3 -3
  22. package/dist/{executor-Bje4suQF.d.ts → executor-B26lgbHD.d.ts} +2 -2
  23. package/dist/{executor-Ceq60hPM.js → executor-C57jx2pn.js} +7 -2
  24. package/dist/{executor-Bazhgsgq.d.ts → executor-DynmIWhq.d.ts} +2 -2
  25. package/dist/guards-BnzDvFrz.js +10 -0
  26. package/dist/hono/index.d.ts +5 -5
  27. package/dist/hono/index.js +4 -4
  28. package/dist/{index-Hf8PP9sP.d.ts → index-BNsABq7P.d.ts} +17 -13
  29. package/dist/{index-JXHBuS53.d.ts → index-CADlNHK-.d.ts} +27 -14
  30. package/dist/{index-Nxo3TJcm.d.ts → index-CTTMS6_1.d.ts} +27 -8
  31. package/dist/index-Cf-D2jk1.d.ts +50 -0
  32. package/dist/{index-Dup-nSe9.d.ts → index-CriFFJ_p.d.ts} +4 -4
  33. package/dist/{index-DQVvHzjW.d.ts → index-GV77NpRP.d.ts} +5 -4
  34. package/dist/{index-Sw43J8HG.d.ts → index-UJ-LUxtI.d.ts} +6 -6
  35. package/dist/{index-cEeweTeM.d.ts → index-kpgopLNv.d.ts} +4 -3
  36. package/dist/{index-DRid0qIv.d.ts → index-qriRM9cZ.d.ts} +7 -2
  37. package/dist/index.d.ts +13 -13
  38. package/dist/index.js +18 -8
  39. package/dist/jobs/index.d.ts +25 -24
  40. package/dist/jobs/index.js +19 -12
  41. package/dist/{json-schema-BbwO2mZg.js → json-schema--aOXAGfU.js} +9 -3
  42. package/dist/lint/index.js +1 -1
  43. package/dist/list/index.d.ts +2 -2
  44. package/dist/list/index.js +128 -93
  45. package/dist/{live-6b9i72Ux.d.ts → live-QE3UPbGm.d.ts} +27 -6
  46. package/dist/logger-glM28NVN.js +21 -0
  47. package/dist/mcp/index.d.ts +59 -15
  48. package/dist/mcp/index.js +229 -67
  49. package/dist/{mfa-CpogP66z.js → mfa-BxoR1RkN.js} +97 -22
  50. package/dist/{mfa-TVia554c.d.ts → mfa-x3V3vGh-.d.ts} +1 -1
  51. package/dist/next/index.d.ts +51 -38
  52. package/dist/next/index.js +31 -25
  53. package/dist/openapi/index.d.ts +1 -1
  54. package/dist/openapi/index.js +58 -35
  55. package/dist/orpc/index.d.ts +6 -6
  56. package/dist/orpc/index.js +3 -3
  57. package/dist/otel/index.d.ts +3 -3
  58. package/dist/otel/index.js +5 -3
  59. package/dist/{template-BeOGnynM.js → permdock-sql-Bta_G0v-.js} +32 -3
  60. package/dist/permdock-sql-DImJULY-.d.ts +17 -0
  61. package/dist/{plugin-HvGqCurB.d.ts → plugin-DVRB8qNE.d.ts} +5 -4
  62. package/dist/plugins/actor/index.d.ts +1 -1
  63. package/dist/plugins/rules/index.d.ts +4 -3
  64. package/dist/plugins/rules/index.js +8 -15
  65. package/dist/plugins/soft-delete/index.d.ts +3 -3
  66. package/dist/plugins/soft-delete/index.js +2 -2
  67. package/dist/plugins/tenant/index.d.ts +10 -4
  68. package/dist/plugins/tenant/index.js +15 -13
  69. package/dist/plugins/timestamps/index.d.ts +1 -1
  70. package/dist/plugins/timestamps/index.js +1 -1
  71. package/dist/plugins/validation/index.d.ts +2 -2
  72. package/dist/pool-B9qsLyFR.d.ts +71 -0
  73. package/dist/postgres/index.d.ts +10 -5
  74. package/dist/postgres/index.js +36 -16
  75. package/dist/{postgrest-Dm-vhY6R.d.ts → postgrest-DmBlevh-.d.ts} +1 -1
  76. package/dist/{problem-DPu6Fh-t.js → problem-CGt1ADix.js} +14 -8
  77. package/dist/{problem-DlgB0lYl.d.ts → problem-Z2RTQjZD.d.ts} +6 -4
  78. package/dist/query/index.d.ts +2 -2
  79. package/dist/query/index.js +1 -1
  80. package/dist/{query-ChvDbsDE.js → query-BdKLTPlj.js} +2 -2
  81. package/dist/react/index.d.ts +7 -7
  82. package/dist/react/index.js +3 -3
  83. package/dist/react/server.d.ts +5 -5
  84. package/dist/react/server.js +1 -1
  85. package/dist/react/session.d.ts +21 -2
  86. package/dist/react/session.js +19 -1
  87. package/dist/{react-CrOvrnIP.js → react-DNBYNazu.js} +28 -23
  88. package/dist/{read-set-DhX2d04c.js → read-set-D0qBcboM.js} +3 -3
  89. package/dist/realtime/index.d.ts +4 -2
  90. package/dist/realtime/index.js +30 -14
  91. package/dist/{resolve-DW8h9NPm.d.ts → resolve-BRciX2jK.d.ts} +25 -9
  92. package/dist/{resource-SkPTRJDD.d.ts → resource-CwVgiI5q.d.ts} +16 -7
  93. package/dist/{resource-CYiu6Ygi.js → resource-D30rp0GN.js} +38 -16
  94. package/dist/{respond-4-dhoV7x.js → respond-C44EZAJo.js} +38 -19
  95. package/dist/{respond-PoTpy5DT.d.ts → respond-DAkPIdTH.d.ts} +23 -17
  96. package/dist/{result-CTkCZ6JA.d.ts → result-c2OYgCV2.d.ts} +13 -8
  97. package/dist/server/index.d.ts +35 -10
  98. package/dist/server/index.js +38 -9
  99. package/dist/{simplify-oHiDKRVQ.js → simplify-LakqK1vH.js} +22 -28
  100. package/dist/{spec-pins-t6sPaHvl.js → spec-pins-C3oc-Tdp.js} +1 -1
  101. package/dist/{spec-pins-CJPVghHy.d.ts → spec-pins-CqorWDnO.d.ts} +1 -1
  102. package/dist/sql/index.d.ts +139 -0
  103. package/dist/sql/index.js +1983 -0
  104. package/dist/{sql-M8l3fnS0.js → sql-BIgk12ew.js} +72 -16
  105. package/dist/ssr/index.d.ts +5 -5
  106. package/dist/ssr/index.js +3 -3
  107. package/dist/{standard-CyhRzctb.d.ts → standard-eudBiRk1.d.ts} +1 -1
  108. package/dist/{stats-3iy8u3_P.js → stats-D78UQ8Yu.js} +8 -7
  109. package/dist/storage/index.d.ts +4 -2
  110. package/dist/storage/index.js +530 -1
  111. package/dist/{tables-rZxRxNPW.js → tables-Dhha3BXM.js} +5 -5
  112. package/dist/temporal-BwshQk0W.js +27 -0
  113. package/dist/temporal-required-CMxeSVPo.js +19 -0
  114. package/dist/testing/index.d.ts +49 -16
  115. package/dist/testing/index.js +233 -66
  116. package/dist/{types-C2fslP1z.d.ts → types-DONv9SHN.d.ts} +68 -12
  117. package/dist/{version-Cqq1m3UY.js → version-BfpG2gks.js} +1 -1
  118. package/dist/view-3Po_PVlr.d.ts +79 -0
  119. package/dist/{view-B6F9QbG-.js → view-wZ-9qIUz.js} +12 -2
  120. package/dist/webhooks/index.d.ts +19 -17
  121. package/dist/webhooks/index.js +1 -1
  122. package/dist/{webhooks-DbMTJJX2.js → webhooks-CAIFQSTM.js} +12 -10
  123. package/package.json +30 -30
  124. package/schemas/config-v1.json +80 -5
  125. package/schemas/doctor-report-v1.json +18 -1
  126. package/schemas/snapshot-v2.json +39 -0
  127. package/skills/better-supabase/SKILL.md +23 -11
  128. package/skills/better-supabase/references/plugins.md +28 -16
  129. package/skills/better-supabase/references/troubleshooting.md +15 -15
  130. package/skills/better-supabase-api/SKILL.md +9 -9
  131. package/skills/better-supabase-api/references/adapters.md +30 -25
  132. package/skills/better-supabase-testing/SKILL.md +15 -11
  133. package/bin/better-supabase.js +0 -3
  134. package/dist/casing-da0uRTqt.js +0 -14
  135. package/dist/cli/bin.d.ts +0 -1
  136. package/dist/cli/bin.js +0 -15
  137. package/dist/cli/index.d.ts +0 -226
  138. package/dist/cli/index.js +0 -3
  139. package/dist/cli-CoByWYja.js +0 -6472
  140. package/dist/entitlements-BBg61DQZ.d.ts +0 -20
  141. package/dist/entitlements-BC_khwRy.js +0 -17
  142. package/dist/env-BEToab0I.js +0 -204
  143. package/dist/events-BuzTgn2D.js +0 -135
  144. package/dist/pool-BFeqzwQS.d.ts +0 -37
  145. package/dist/seed-CzecODcJ.js +0 -101
  146. package/dist/session-C3osL21k.d.ts +0 -21
  147. package/dist/session-CD-3orO_.js +0 -19
  148. package/dist/storage-BWXVUXGk.js +0 -461
  149. package/dist/view-B0ltUmTi.d.ts +0 -43
@@ -1,10 +1,12 @@
1
1
  import { c as mapDbError, n as dbError, t as DbException } from "../errors-Bl3rkOzo.js";
2
+ import { n as nowInstant } from "../temporal-BwshQk0W.js";
3
+ import { t as temporal } from "../temporal-required-CMxeSVPo.js";
2
4
  import { i as ok, n as err, o as toDbError, t as AsyncResult } from "../result-DWXatkd6.js";
3
5
  import { t as validate } from "../standard-FUi3Z3-7.js";
4
- import { a as problemResponse } from "../problem-DPu6Fh-t.js";
5
- import { t as fromPgError } from "../executor-Ceq60hPM.js";
6
- import { c as verifyWebhook } from "../webhooks-DbMTJJX2.js";
7
- //#region src/jobs/index.ts
6
+ import { a as problemResponse } from "../problem-CGt1ADix.js";
7
+ import { t as fromPgError } from "../executor-C57jx2pn.js";
8
+ import { c as verifyWebhook } from "../webhooks-CAIFQSTM.js";
9
+ //#region src/jobs/jobs.ts
8
10
  function run(fn) {
9
11
  return AsyncResult.from(async () => {
10
12
  try {
@@ -22,7 +24,10 @@ function workerId() {
22
24
  return `worker-${crypto.randomUUID().slice(0, 8)}`;
23
25
  }
24
26
  const sleep = (ms, signal) => new Promise((resolve) => {
25
- if (signal?.aborted) return resolve();
27
+ if (signal?.aborted) {
28
+ resolve();
29
+ return;
30
+ }
26
31
  const timer = setTimeout(done, ms);
27
32
  function done() {
28
33
  clearTimeout(timer);
@@ -102,7 +107,7 @@ function postgrestTransport(client) {
102
107
  const call = async (fn, args) => {
103
108
  const { data, error } = await client.schema("pgmq_public").rpc(fn, args);
104
109
  if (error) {
105
- const message = typeof error === "object" && error !== null && "message" in error ? String(error.message) : String(error);
110
+ const message = typeof error === "object" && "message" in error ? String(error.message) : String(error);
106
111
  throw new Error(`pgmq_public.${fn}: ${message}`, { cause: error });
107
112
  }
108
113
  return data;
@@ -149,7 +154,8 @@ function postgrestTransport(client) {
149
154
  unschedule: () => sqlOnly("unschedule")
150
155
  };
151
156
  }
152
- const toDate = (value) => value instanceof Date ? value : new Date(value);
157
+ /** pgmq and the inbox return `timestamptz` as text over RPC and as `Date` from `pg`. */
158
+ const toInstant = (value) => value instanceof Date ? temporal().Instant.fromEpochMilliseconds(value.getTime()) : temporal().Instant.from(value);
153
159
  function toJob(queue, row) {
154
160
  const message = row.message ?? {};
155
161
  return {
@@ -158,8 +164,8 @@ function toJob(queue, row) {
158
164
  payload: message.payload,
159
165
  attempts: row.attempts,
160
166
  maxAttempts: message.max_attempts ?? 5,
161
- enqueuedAt: toDate(row.enqueued_at),
162
- visibleUntil: toDate(row.visible_until),
167
+ enqueuedAt: toInstant(row.enqueued_at),
168
+ visibleUntil: toInstant(row.visible_until),
163
169
  lastError: message.last_error ?? null
164
170
  };
165
171
  }
@@ -254,7 +260,7 @@ function createJobs(source, queues) {
254
260
  return AsyncResult.from(async () => {
255
261
  const valid = await validate(schemaOf(queue), payload, "payload");
256
262
  if (!valid.ok) return valid;
257
- const delay = enqueueOptions.delay ?? (enqueueOptions.runAt ? Math.max(0, Math.ceil((enqueueOptions.runAt.getTime() - Date.now()) / 1e3)) : 0);
263
+ const delay = enqueueOptions.delay ?? (enqueueOptions.runAt ? Math.max(0, Math.ceil((enqueueOptions.runAt.epochMilliseconds - nowInstant().epochMilliseconds) / 1e3)) : 0);
258
264
  return run(() => transport.send(queue, valid.data, delay, enqueueOptions.maxAttempts ?? 5, enqueueOptions.dedupeKey));
259
265
  });
260
266
  },
@@ -435,7 +441,7 @@ function createInbox(sql, options) {
435
441
  payload: row.payload,
436
442
  headers: row.headers,
437
443
  attempts: row.attempts,
438
- receivedAt: toDate(row.received_at)
444
+ receivedAt: toInstant(row.received_at)
439
445
  };
440
446
  try {
441
447
  const outcome = await handler(message);
@@ -458,7 +464,8 @@ function createInbox(sql, options) {
458
464
  /** The Stripe event sent when a customer's active entitlements change. */
459
465
  const ENTITLEMENTS_UPDATED = "entitlements.active_entitlement_summary.updated";
460
466
  function stripeCustomerOf(payload) {
461
- const customer = (payload?.data?.object)?.customer;
467
+ if (typeof payload !== "object" || payload === null) return void 0;
468
+ const customer = (payload.data?.object)?.customer;
462
469
  if (typeof customer === "string") return customer;
463
470
  const id = customer?.id;
464
471
  return typeof id === "string" ? id : void 0;
@@ -1,3 +1,4 @@
1
+ import { t as isList } from "./guards-BnzDvFrz.js";
1
2
  //#region src/generators/shared.ts
2
3
  const INTEGER = /* @__PURE__ */ new Set([
3
4
  "int2",
@@ -18,7 +19,8 @@ function scalarKind(column) {
18
19
  values: column.enum
19
20
  };
20
21
  if (column.json) return { kind: "json" };
21
- if (column.codec === "date") return { kind: "dateObject" };
22
+ if (column.codec === "instant") return { kind: "instant" };
23
+ if (column.codec === "plainDateTime") return { kind: "plainDateTime" };
22
24
  if (column.codec === "bigint") return { kind: "bigint" };
23
25
  if (column.codec === "string") return { kind: "string" };
24
26
  if (column.type === "uuid") return { kind: "uuid" };
@@ -106,10 +108,14 @@ function scalar(kind) {
106
108
  type: "string",
107
109
  format: "date"
108
110
  };
109
- case "dateObject": return {
111
+ case "instant": return {
110
112
  type: "string",
111
113
  format: "date-time"
112
114
  };
115
+ case "plainDateTime": return {
116
+ type: "string",
117
+ pattern: "^\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?$"
118
+ };
113
119
  case "bigint": return {
114
120
  type: "string",
115
121
  pattern: "^-?\\d+$"
@@ -132,7 +138,7 @@ function property(plan) {
132
138
  if (!plan.nullable) return schema;
133
139
  if (Object.keys(schema).length === 0) return schema;
134
140
  const { enum: values, ...rest } = schema;
135
- if (Array.isArray(values)) return {
141
+ if (isList(values)) return {
136
142
  ...rest,
137
143
  type: ["string", "null"],
138
144
  enum: [...values, null]
@@ -16,7 +16,7 @@ function receiverName(node) {
16
16
  return receiver.property.name;
17
17
  }
18
18
  /** Matches `order_items`, `orderItems` and `public.order_items` alike. */
19
- const tableKey = (name) => (name.split(".").at(-1) ?? name).replace(/_/g, "").toLowerCase();
19
+ const tableKey = (name) => (name.split(".").at(-1) ?? name).replaceAll("_", "").toLowerCase();
20
20
  function keyName(property) {
21
21
  if (property.computed) return void 0;
22
22
  if (property.key.type === "Identifier") return property.key.name;
@@ -1,2 +1,2 @@
1
- import { a as ListPage, c as ListQuery, d as ListSearchParams, f as OpenApiParameter, h as defineListQuery, i as ListExtra, l as ListQueryConfig, m as UNSET, n as FacetInfo, o as ListParseResult, p as ParserSpec, r as ListDefinition, s as ListParsers, t as FacetCounts, u as ListQueryInput } from "../index-JXHBuS53.js";
2
- export { FacetCounts, FacetInfo, ListDefinition, ListExtra, ListPage, ListParseResult, ListParsers, ListQuery, ListQueryConfig, ListQueryInput, ListSearchParams, OpenApiParameter, ParserSpec, UNSET, defineListQuery };
1
+ import { a as ListPage, c as ListParsers, d as ListQueryInput, f as ListSearchParams, g as defineListQuery, h as UNSET, i as ListExtra, l as ListQuery, m as ParserSpec, n as FacetInfo, o as ListPagination, p as OpenApiParameter, r as ListDefinition, s as ListParseResult, t as FacetCounts, u as ListQueryConfig } from "../index-CADlNHK-.js";
2
+ export { type FacetCounts, type FacetInfo, type ListDefinition, type ListExtra, type ListPage, type ListPagination, type ListParseResult, type ListParsers, type ListQuery, type ListQueryConfig, type ListQueryInput, type ListSearchParams, type OpenApiParameter, type ParserSpec, UNSET, defineListQuery };
@@ -1,10 +1,11 @@
1
- //#region src/list/index.ts
1
+ //#region src/list/list-query.ts
2
2
  /** Facet value meaning "not set": filters with `is null`. */
3
3
  const UNSET = "__unset__";
4
4
  const RESERVED = /* @__PURE__ */ new Set([
5
5
  "q",
6
6
  "sort",
7
7
  "page",
8
+ "after",
8
9
  "size"
9
10
  ]);
10
11
  function first(value) {
@@ -13,8 +14,9 @@ function first(value) {
13
14
  function all(value) {
14
15
  return (typeof value === "string" ? [value] : [...value ?? []]).flatMap((entry) => entry.split(",")).map((entry) => entry.trim()).filter(Boolean);
15
16
  }
17
+ const isParamValue = (value) => value === void 0 || typeof value === "string" || Array.isArray(value) && value.every((entry) => typeof entry === "string");
16
18
  function isSearchParams(value) {
17
- return value instanceof URLSearchParams || !("facets" in value && typeof value.facets === "object");
19
+ return value instanceof URLSearchParams || !("facets" in value && typeof value.facets === "object") && Object.values(value).every(isParamValue);
18
20
  }
19
21
  function readRecord(input) {
20
22
  if (!(input instanceof URLSearchParams)) return input;
@@ -47,6 +49,7 @@ function defineListQuery(sb, table, config) {
47
49
  const pageSize = config.pageSize ?? 50;
48
50
  const maxPageSize = config.maxPageSize ?? 200;
49
51
  const maxSearch = config.maxSearchLength ?? 200;
52
+ const cursor = config.pagination === "cursor";
50
53
  const sortKeys = Object.keys(config.sorts);
51
54
  const facetEntries = Object.entries(config.facets ?? {});
52
55
  for (const [key, column] of facetEntries) {
@@ -96,6 +99,18 @@ function defineListQuery(sb, table, config) {
96
99
  message: "Must be a whole number of at least 1",
97
100
  path: ["page"]
98
101
  });
102
+ else if (cursor && page !== 1) issues.push({
103
+ message: "This list pages with `after`, not `page`",
104
+ path: ["page"]
105
+ });
106
+ let after;
107
+ if (raw.after !== void 0 && raw.after !== "") {
108
+ if (cursor && typeof raw.after === "string") after = raw.after;
109
+ else issues.push({
110
+ message: cursor ? "Must be text" : "This list pages with `page`, not `after`",
111
+ path: ["after"]
112
+ });
113
+ }
99
114
  const size = raw.size === void 0 || raw.size === "" ? pageSize : toInt(raw.size);
100
115
  if (size === void 0 || !Number.isInteger(size) || size < 1 || size > maxPageSize) issues.push({
101
116
  message: `Must be a whole number from 1 to ${String(maxPageSize)}`,
@@ -121,6 +136,7 @@ function defineListQuery(sb, table, config) {
121
136
  ...q ? { q } : {},
122
137
  sort,
123
138
  page: page ?? 1,
139
+ ...after ? { after } : {},
124
140
  size: size ?? pageSize,
125
141
  facets: facetValues
126
142
  }
@@ -143,6 +159,7 @@ function defineListQuery(sb, table, config) {
143
159
  q: first(record["q"]),
144
160
  sort: first(record["sort"]),
145
161
  page: first(record["page"]),
162
+ after: first(record["after"]),
146
163
  size: first(record["size"]),
147
164
  facets: facetValues
148
165
  });
@@ -160,6 +177,7 @@ function defineListQuery(sb, table, config) {
160
177
  q: typed.q,
161
178
  sort: typed.sort,
162
179
  page: typed.page,
180
+ after: typed.after,
163
181
  size: typed.size,
164
182
  facets: rawFacets
165
183
  });
@@ -195,12 +213,15 @@ function defineListQuery(sb, table, config) {
195
213
  }
196
214
  function args(query) {
197
215
  const filter = where(query);
216
+ const paging = cursor ? { after: query.after ?? null } : {
217
+ page: query.page,
218
+ count: config.count ?? "exact"
219
+ };
198
220
  return {
199
221
  ...filter ? { where: filter } : {},
200
222
  orderBy: config.sorts[query.sort],
201
- page: query.page,
202
223
  size: query.size,
203
- count: config.count ?? "exact"
224
+ ...paging
204
225
  };
205
226
  }
206
227
  /** Whether a group's value passes the facet's selected values. */
@@ -249,6 +270,10 @@ function defineListQuery(sb, table, config) {
249
270
  serialize: (value) => value
250
271
  },
251
272
  page: intParser,
273
+ after: {
274
+ parse: (value) => value || null,
275
+ serialize: (value) => value
276
+ },
252
277
  size: intParser,
253
278
  ...Object.fromEntries(facets.map((facet) => [facet.key, listParser]))
254
279
  };
@@ -261,8 +286,102 @@ function defineListQuery(sb, table, config) {
261
286
  uniqueItems: true
262
287
  });
263
288
  const searchDescription = config.search ? Array.isArray(config.search) ? `Case-insensitive search in ${config.search.join(", ")}.` : "Full-text search (web search syntax)." : void 0;
289
+ const openapi = [
290
+ ...searchDescription ? [{
291
+ name: "q",
292
+ in: "query",
293
+ required: false,
294
+ description: searchDescription,
295
+ schema: {
296
+ type: "string",
297
+ maxLength: maxSearch
298
+ }
299
+ }] : [],
300
+ {
301
+ name: "sort",
302
+ in: "query",
303
+ required: false,
304
+ description: `Ordering. Default \`${config.defaultSort}\`.`,
305
+ schema: {
306
+ type: "string",
307
+ enum: sortKeys,
308
+ default: config.defaultSort
309
+ }
310
+ },
311
+ cursor ? {
312
+ name: "after",
313
+ in: "query",
314
+ required: false,
315
+ description: "`nextCursor` of the previous page. Leave it out for the first page.",
316
+ schema: { type: "string" }
317
+ } : {
318
+ name: "page",
319
+ in: "query",
320
+ required: false,
321
+ description: "1-based page number.",
322
+ schema: {
323
+ type: "integer",
324
+ minimum: 1,
325
+ default: 1
326
+ }
327
+ },
328
+ {
329
+ name: "size",
330
+ in: "query",
331
+ required: false,
332
+ description: "Page size.",
333
+ schema: {
334
+ type: "integer",
335
+ minimum: 1,
336
+ maximum: maxPageSize,
337
+ default: pageSize
338
+ }
339
+ },
340
+ ...facets.map((facet) => ({
341
+ name: facet.key,
342
+ in: "query",
343
+ required: false,
344
+ description: `Filter on ${facet.column}. Comma-separated; any value matches.${facet.nullable ? ` \`${UNSET}\` matches empty.` : ""}`,
345
+ schema: facetSchema(facet),
346
+ style: "form",
347
+ explode: false
348
+ }))
349
+ ];
350
+ const jsonSchema = {
351
+ type: "object",
352
+ additionalProperties: false,
353
+ properties: {
354
+ ...searchDescription ? { q: {
355
+ type: "string",
356
+ maxLength: maxSearch,
357
+ description: searchDescription
358
+ } } : {},
359
+ sort: {
360
+ type: "string",
361
+ enum: sortKeys,
362
+ default: config.defaultSort
363
+ },
364
+ ...cursor ? { after: { type: "string" } } : { page: {
365
+ type: "integer",
366
+ minimum: 1,
367
+ default: 1
368
+ } },
369
+ size: {
370
+ type: "integer",
371
+ minimum: 1,
372
+ maximum: maxPageSize,
373
+ default: pageSize
374
+ },
375
+ ...facets.length > 0 ? { facets: {
376
+ type: "object",
377
+ additionalProperties: false,
378
+ properties: Object.fromEntries(facets.map((facet) => [facet.key, facetSchema(facet)]))
379
+ } } : {}
380
+ }
381
+ };
264
382
  return {
265
383
  table,
384
+ pagination: config.pagination ?? "offset",
266
385
  defaults,
267
386
  facets,
268
387
  parse,
@@ -279,7 +398,8 @@ function defineListQuery(sb, table, config) {
279
398
  if (query.q) params.set("q", query.q);
280
399
  if (query.sort && query.sort !== config.defaultSort) params.set("sort", query.sort);
281
400
  if (query.page && query.page !== 1) params.set("page", String(query.page));
282
- if (query.size && query.size !== pageSize) params.set("size", String(query.size));
401
+ if (query.after) params.set("after", query.after);
402
+ if (query.size !== void 0 && query.size > 0 && query.size !== pageSize) params.set("size", String(query.size));
283
403
  for (const facet of facets) {
284
404
  const values = query.facets?.[facet.key];
285
405
  if (values && values.length > 0) params.set(facet.key, values.join(","));
@@ -297,7 +417,7 @@ function defineListQuery(sb, table, config) {
297
417
  const page = repository.paginate({
298
418
  ...extra,
299
419
  ...base,
300
- count: count ?? base["count"],
420
+ ...cursor ? {} : { count: count ?? base["count"] },
301
421
  ...combined ? { where: combined } : {}
302
422
  });
303
423
  if (!config.facetCounts) return page;
@@ -317,93 +437,8 @@ function defineListQuery(sb, table, config) {
317
437
  facetCounts: marginals(rows, query)
318
438
  })));
319
439
  },
320
- openapi: [
321
- ...searchDescription ? [{
322
- name: "q",
323
- in: "query",
324
- required: false,
325
- description: searchDescription,
326
- schema: {
327
- type: "string",
328
- maxLength: maxSearch
329
- }
330
- }] : [],
331
- {
332
- name: "sort",
333
- in: "query",
334
- required: false,
335
- description: `Ordering. Default \`${config.defaultSort}\`.`,
336
- schema: {
337
- type: "string",
338
- enum: sortKeys,
339
- default: config.defaultSort
340
- }
341
- },
342
- {
343
- name: "page",
344
- in: "query",
345
- required: false,
346
- description: "1-based page number.",
347
- schema: {
348
- type: "integer",
349
- minimum: 1,
350
- default: 1
351
- }
352
- },
353
- {
354
- name: "size",
355
- in: "query",
356
- required: false,
357
- description: "Page size.",
358
- schema: {
359
- type: "integer",
360
- minimum: 1,
361
- maximum: maxPageSize,
362
- default: pageSize
363
- }
364
- },
365
- ...facets.map((facet) => ({
366
- name: facet.key,
367
- in: "query",
368
- required: false,
369
- description: `Filter on ${facet.column}. Comma-separated; any value matches.${facet.nullable ? ` \`${UNSET}\` matches empty.` : ""}`,
370
- schema: facetSchema(facet),
371
- style: "form",
372
- explode: false
373
- }))
374
- ],
375
- jsonSchema: {
376
- type: "object",
377
- additionalProperties: false,
378
- properties: {
379
- ...searchDescription ? { q: {
380
- type: "string",
381
- maxLength: maxSearch,
382
- description: searchDescription
383
- } } : {},
384
- sort: {
385
- type: "string",
386
- enum: sortKeys,
387
- default: config.defaultSort
388
- },
389
- page: {
390
- type: "integer",
391
- minimum: 1,
392
- default: 1
393
- },
394
- size: {
395
- type: "integer",
396
- minimum: 1,
397
- maximum: maxPageSize,
398
- default: pageSize
399
- },
400
- ...facets.length > 0 ? { facets: {
401
- type: "object",
402
- additionalProperties: false,
403
- properties: Object.fromEntries(facets.map((facet) => [facet.key, facetSchema(facet)]))
404
- } } : {}
405
- }
406
- }
440
+ openapi,
441
+ jsonSchema
407
442
  };
408
443
  }
409
444
  //#endregion
@@ -1,10 +1,11 @@
1
- import { E as SchemaMeta, k as TableKey, n as AnyModels, t as AnyFunctions, w as Row } from "./types-C2fslP1z.js";
2
- import { b as ValidationIssue, o as Result, p as DbError, t as AsyncResult } from "./result-CTkCZ6JA.js";
3
- import { Z as QuerySpec, t as BetterSupabase } from "./define-CTfWw-SS.js";
1
+ import { A as SchemaMeta, N as TableKey, O as Row, n as AnyModels, t as AnyFunctions, x as PermdockTopicPolicy } from "./types-DONv9SHN.js";
2
+ import { b as ValidationIssue, o as Result, p as DbError, t as AsyncResult } from "./result-c2OYgCV2.js";
3
+ import { Z as QuerySpec, t as BetterSupabase } from "./define-DUbydmJa.js";
4
+ import { t as PermdockCatalog } from "./permdock-sql-DImJULY-.js";
4
5
  import { n as TemplateValues, t as TemplateParams } from "./template-mbvlysOo.js";
5
6
  import { RealtimeChannel, SupabaseClient } from "@supabase/supabase-js";
6
7
  import { StandardSchemaV1 } from "@standard-schema/spec";
7
- //#region src/realtime/index.d.ts
8
+ //#region src/realtime/topic.d.ts
8
9
  type EventSchemas = Readonly<Record<string, StandardSchemaV1>>;
9
10
  interface TopicOptions<E extends EventSchemas> {
10
11
  /** Name used for generated policies and functions. Defaults to the template's literal parts. */
@@ -15,7 +16,7 @@ interface TopicOptions<E extends EventSchemas> {
15
16
  readonly private?: boolean;
16
17
  /**
17
18
  * Tenant check for the generated policy. Defaults to `{orgId}` against
18
- * `org_id` / `app_metadata.org_id` when the template has `{orgId}`.
19
+ * `tenant_id` / `app_metadata.tenant_id` when the template has `{orgId}`.
19
20
  */
20
21
  readonly tenant?: false | {
21
22
  readonly param?: string;
@@ -26,6 +27,23 @@ interface TopicOptions<E extends EventSchemas> {
26
27
  readonly owner?: false | {
27
28
  readonly param?: string;
28
29
  };
30
+ /**
31
+ * Authorize with PermDock's SQL helpers instead of the tenant claim:
32
+ * `permitted_<scope>_ids(receive)` on the scope segment (or
33
+ * `permdock_has` for `scope: 'global'`), and `send` for broadcasting.
34
+ *
35
+ * The helpers check role and scope only. Use this just for permissions
36
+ * whose grants have no row conditions beyond the scope: for a permission
37
+ * with row conditions (e.g. `authorId = principal.id`) every member of the
38
+ * scope could join. Pass `catalog` to refuse those keys here;
39
+ * `better-supabase doctor` (BS214) refuses them from the catalog file.
40
+ */
41
+ readonly permdock?: PermdockTopicPolicy;
42
+ /**
43
+ * PermDock's `permissions.catalog.json`. With it, a `permdock` policy
44
+ * naming a permission with `rowConditions: true` throws.
45
+ */
46
+ readonly catalog?: PermdockCatalog;
29
47
  /** Let clients broadcast on the topic, not only receive. Defaults to `false`. */
30
48
  readonly send?: boolean;
31
49
  /** Also authorize presence. Defaults to `false`. */
@@ -66,7 +84,10 @@ interface TriggerOptions<M extends AnyModels, T extends keyof M, P extends strin
66
84
  readonly values: { readonly [K in TemplateParams<P>]: Extract<keyof Row<M, T>, string>; };
67
85
  /** Defaults to all three. */
68
86
  readonly events?: readonly ("insert" | "update" | "delete")[];
69
- /** Schema for the generated function. Defaults to `public`. */
87
+ /**
88
+ * Schema for the generated function. Defaults to `better_supabase`, which
89
+ * the Data API does not expose.
90
+ */
70
91
  readonly functionSchema?: string;
71
92
  }
72
93
  interface RowChange<R> {
@@ -0,0 +1,21 @@
1
+ //#region src/core/logger.ts
2
+ const log = (method) => (message, fields) => {
3
+ if (fields === void 0) console[method](`better-supabase: ${message}`);
4
+ else console[method](`better-supabase: ${message}`, fields);
5
+ };
6
+ /** The default: `console`, with a `better-supabase:` prefix. */
7
+ const consoleLogger = {
8
+ debug: log("debug"),
9
+ info: log("info"),
10
+ warn: log("warn"),
11
+ error: log("error")
12
+ };
13
+ const noop = () => {};
14
+ const silentLogger = {
15
+ debug: noop,
16
+ info: noop,
17
+ warn: noop,
18
+ error: noop
19
+ };
20
+ //#endregion
21
+ export { silentLogger as n, consoleLogger as t };
@@ -1,13 +1,13 @@
1
- import { k as TableKey, n as AnyModels, t as AnyFunctions } from "../types-C2fslP1z.js";
2
- import { t as BetterSupabase } from "../define-CTfWw-SS.js";
3
- import { d as ServerContext, f as ServerOptions, l as BetterServer, n as GuardOptions } from "../respond-PoTpy5DT.js";
4
- import { i as ResourceRouteOptions } from "../resource-SkPTRJDD.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 { d as ServerContext, f as ServerOptions, l as BetterServer, n as GuardOptions } from "../respond-DAkPIdTH.js";
4
+ import { a as ResourceRouteOptions } from "../resource-CwVgiI5q.js";
5
5
  import { StandardSchemaV1 } from "@standard-schema/spec";
6
- //#region src/mcp/index.d.ts
6
+ //#region src/mcp/mcp.d.ts
7
7
  export declare const MCP_PROTOCOL_VERSION: string;
8
8
  type Json = Readonly<Record<string, unknown>>;
9
9
  /** MCP tool annotations (hints for clients, not guarantees). */
10
- export interface ToolAnnotations {
10
+ interface ToolAnnotations {
11
11
  readonly title?: string;
12
12
  readonly readOnlyHint?: boolean;
13
13
  readonly destructiveHint?: boolean;
@@ -15,7 +15,7 @@ export interface ToolAnnotations {
15
15
  readonly openWorldHint?: boolean;
16
16
  }
17
17
  /** A tool as listed by `tools/list`. */
18
- export interface ToolInfo {
18
+ interface ToolInfo {
19
19
  readonly name: string;
20
20
  readonly title?: string;
21
21
  readonly description?: string;
@@ -23,17 +23,34 @@ export interface ToolInfo {
23
23
  readonly outputSchema?: Json;
24
24
  readonly annotations?: ToolAnnotations;
25
25
  }
26
- export interface ToolContext<M extends AnyModels, F extends AnyFunctions, E> extends ServerContext<M, F, E> {
26
+ interface ToolContext<M extends AnyModels, F extends AnyFunctions, E> extends ServerContext<M, F, E> {
27
27
  readonly request: Request;
28
28
  readonly signal: AbortSignal;
29
29
  }
30
- export interface McpTool<M extends AnyModels, F extends AnyFunctions, E> {
30
+ interface McpTool<M extends AnyModels, F extends AnyFunctions, E> {
31
31
  readonly info: ToolInfo;
32
32
  /** Validates arguments; its output is what `run` receives. */
33
33
  readonly input?: StandardSchemaV1;
34
+ /** Opaque data for the `authorize` and `visible` hooks. Never sent to clients. */
35
+ readonly meta?: unknown;
34
36
  run(args: never, ctx: ToolContext<M, F, E>): unknown;
35
37
  }
36
- export interface ToolDefinition<M extends AnyModels, F extends AnyFunctions, E, I> {
38
+ /** What the `authorize` and `visible` hooks see of a tool. */
39
+ interface ToolRef {
40
+ readonly info: ToolInfo;
41
+ /** The tool's `meta`, e.g. a PermDock permission. `undefined` for table tools. */
42
+ readonly meta: unknown;
43
+ }
44
+ /** The outcome of `authorize`. `scopes` turns a refusal into an OAuth challenge. */
45
+ type ToolDecision = {
46
+ readonly allowed: true;
47
+ } | {
48
+ readonly allowed: false;
49
+ readonly reason?: string;
50
+ /** OAuth scopes the call needs, sent in an `insufficient_scope` challenge. */
51
+ readonly scopes?: readonly string[];
52
+ };
53
+ interface ToolDefinition<M extends AnyModels, F extends AnyFunctions, E, I> {
37
54
  readonly name: string;
38
55
  readonly title?: string;
39
56
  readonly description: string;
@@ -45,10 +62,12 @@ export interface ToolDefinition<M extends AnyModels, F extends AnyFunctions, E,
45
62
  readonly inputSchema?: Json;
46
63
  readonly outputSchema?: Json;
47
64
  readonly annotations?: ToolAnnotations;
65
+ /** Opaque data for the `authorize` and `visible` hooks. Never sent to clients. */
66
+ readonly meta?: unknown;
48
67
  run(args: I, ctx: ToolContext<M, F, E>): unknown;
49
68
  }
50
- export type ToolResources<M extends AnyModels> = { readonly [T in TableKey<M>]?: ResourceRouteOptions<M, T> | true; };
51
- export interface McpOptions<M extends AnyModels, F extends AnyFunctions, E> extends ServerOptions, GuardOptions {
69
+ type ToolResources<M extends AnyModels> = { readonly [T in TableKey<M>]?: ResourceRouteOptions<M, T> | true; };
70
+ interface McpOptions<M extends AnyModels, F extends AnyFunctions, E> extends ServerOptions, GuardOptions {
52
71
  readonly name: string;
53
72
  readonly version: string;
54
73
  readonly title?: string;
@@ -64,14 +83,39 @@ export interface McpOptions<M extends AnyModels, F extends AnyFunctions, E> exte
64
83
  readonly resource?: string | ((request: Request) => string);
65
84
  /** Defaults to Supabase Auth of `env.url` (`<url>/auth/v1`). */
66
85
  readonly authorizationServers?: readonly string[];
86
+ /**
87
+ * Scopes the tools need, published as RFC 9728 `scopes_supported` and in
88
+ * `insufficient_scope` challenges. `offline_access` is left out: refresh
89
+ * tokens are between the client and the authorization server.
90
+ */
91
+ readonly scopes?: readonly string[];
67
92
  /** Origins allowed to call the server (DNS rebinding protection). Defaults to any. */
68
93
  readonly allowedOrigins?: readonly string[];
94
+ /**
95
+ * Hostnames the `Host` header may name, without the port (DNS rebinding
96
+ * protection). List the production, preview and local hosts. Defaults to any.
97
+ */
98
+ readonly allowedHosts?: readonly string[];
99
+ /** A page that explains how to connect, published as RFC 9728 `resource_documentation`. */
100
+ readonly resourceDocumentation?: string;
69
101
  /** Custom jsonb types, as in `createOpenApi`. */
70
102
  readonly json?: Readonly<Record<string, unknown>>;
71
103
  /** Include internal error messages in tool results. Defaults to `NODE_ENV === 'development'`. */
72
104
  readonly exposeErrors?: boolean;
105
+ /**
106
+ * Decides each `tools/call` after the arguments are validated and before
107
+ * `run`. `args` are the validated arguments (raw for table tools, which
108
+ * validate in the repository). A refusal is a tool error; a refusal with
109
+ * `scopes` is a 403 `insufficient_scope` challenge naming them.
110
+ */
111
+ readonly authorize?: (ctx: ToolContext<M, F, E>, tool: ToolRef, args: unknown) => ToolDecision | Promise<ToolDecision>;
112
+ /**
113
+ * Whether the caller sees a tool in `tools/list`. A hidden tool is called
114
+ * like an unknown one. Lists are cached per token (`cacheScope: 'private'`).
115
+ */
116
+ readonly visible?: (ctx: ToolContext<M, F, E>, tool: ToolRef) => boolean | Promise<boolean>;
73
117
  }
74
- export interface BetterMcp<M extends AnyModels, F extends AnyFunctions, E> extends BetterServer<M, F, E> {
118
+ interface BetterMcp<M extends AnyModels, F extends AnyFunctions, E> extends BetterServer<M, F, E> {
75
119
  /** Everything in one handler: the MCP endpoint and `/.well-known/oauth-protected-resource`. */
76
120
  readonly fetch: (request: Request) => Promise<Response>;
77
121
  /** The Streamable HTTP endpoint (POST JSON-RPC). */
@@ -84,7 +128,7 @@ export interface BetterMcp<M extends AnyModels, F extends AnyFunctions, E> exten
84
128
  /** Calls a tool directly, e.g. from tests or another transport. */
85
129
  call(name: string, args: unknown, ctx: ToolContext<M, F, E>): Promise<ToolResult>;
86
130
  }
87
- export interface ToolResult {
131
+ interface ToolResult {
88
132
  readonly content: readonly {
89
133
  readonly type: "text";
90
134
  readonly text: string;
@@ -102,4 +146,4 @@ export declare function defineTool<M extends AnyModels, F extends AnyFunctions,
102
146
  */
103
147
  export declare function createMcp<M extends AnyModels, D, F extends AnyFunctions, E>(sb: BetterSupabase<M, D, F, E>, options: McpOptions<M, F, E>): BetterMcp<M, F, E>;
104
148
  //#endregion
105
- export type { GuardOptions, ResourceRouteOptions };
149
+ export type { BetterMcp, GuardOptions, McpOptions, McpTool, ResourceRouteOptions, ToolAnnotations, ToolContext, ToolDecision, ToolDefinition, ToolInfo, ToolRef, ToolResources, ToolResult };