better-supabase 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -16
- package/dist/actor-DaJjqD5R.d.ts +43 -0
- package/dist/channel-FQW-CnGR.d.ts +70 -0
- package/dist/cli/bin.js +1 -1
- package/dist/cli/codemod-DqV7612m.js +388 -0
- package/dist/cli/{config-D9R0o1LH.js → config-CPVpXP5I.js} +78 -8
- package/dist/cli/{config-SSKSTUl2.js → config-dtoekM0v.js} +1 -1
- package/dist/cli/{read-sets-CMa29t9a.js → diff-SjkO5rNr.js} +1 -220
- package/dist/cli/{dist-CFgGknmZ.js → dist-DBPdlPYK.js} +4 -4
- package/dist/cli/{dist-B1MvLv-M.js → dist-DreZ24Sz.js} +1 -1
- package/dist/cli/{dist-Du7VnEUG.js → dist-xn4wlboc.js} +112 -1
- package/dist/cli/{doctor-BOym3mbL.js → doctor-C_A-KEAz.js} +533 -50
- package/dist/cli/{env-BZ0uy8XK.js → env-DmKv_G4A.js} +1 -1
- package/dist/cli/gen-CIdSQZad.js +2 -0
- package/dist/cli/{gen-Ccn_p90L.js → gen-CjElD1Vw.js} +321 -88
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +4 -4
- package/dist/cli/{init-ClnxRYBF.js → init-DMa6M7hV.js} +2 -2
- package/dist/cli/{introspect-DxqfkVIv.js → introspect-BKlaIu_M.js} +2 -2
- package/dist/cli/{openapi-8WHjxiTJ.js → openapi-Cp5Fyta-.js} +1 -1
- package/dist/cli/read-sets-VQcPgxU9.js +436 -0
- package/dist/cli/{run-CP20LKk_.js → run-Bo_NLT5n.js} +16 -12
- package/dist/cli/{seed-5J6eu_Xg.js → seed-CUlqWh-8.js} +1 -1
- package/dist/cli/sql-CVx-1O8G.js +373 -0
- package/dist/cli/{version-5mW_8sY1.js → version-BCtlgDQx.js} +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +3 -10
- package/dist/clone-BfgmLU90.js +21 -0
- package/dist/{compiler-DB3aSFOA.d.ts → compiler-BXH_aZ7N.d.ts} +2 -2
- package/dist/config/index.d.ts +4 -4
- package/dist/config/index.js +4 -4
- package/dist/{config-C1CYcm6o.d.ts → config-Bv7gvfzC.d.ts} +185 -11
- package/dist/{config-CbvFmGD8.js → config-DreERlKo.js} +1 -1
- package/dist/{define-CRS2MRV3.d.ts → define-Bp_BZa5h.d.ts} +11 -7
- package/dist/{define-Bh0AqTik.js → define-cNKzqwL_.js} +202 -16
- package/dist/edge/index.d.ts +16 -5
- package/dist/edge/index.js +10 -7
- package/dist/{entitlements-Dm-FHpNH.d.ts → entitlements-BiJZ4t49.d.ts} +1 -1
- package/dist/env/index.d.ts +2 -2
- package/dist/env/index.js +34 -1
- package/dist/{errors-L0xK41y_.js → errors-DtwXa6b8.js} +1 -0
- package/dist/events/index.d.ts +3 -2
- package/dist/events/index.js +65 -12
- package/dist/events-aIO7vsIk.d.ts +230 -0
- package/dist/{result-CqEsbgYs.d.ts → executor-BunPOO-k.d.ts} +200 -1
- package/dist/{executor-CZ7ImRiD.d.ts → executor-Cghdstwu.d.ts} +1 -2
- package/dist/{executor-CVt6dD0B.js → executor-XycESQPH.js} +3 -3
- package/dist/hono/index.d.ts +17 -6
- package/dist/hono/index.js +40 -5
- package/dist/impersonation-BgSHAYLf.js +133 -0
- package/dist/{resolve-BAqQcwMW.d.ts → impersonation-BmePsuxz.d.ts} +36 -6
- package/dist/{index-BhHEC04r.d.ts → index-C7f8cOBM.d.ts} +2 -2
- package/dist/{index-Dy6kuF0P.d.ts → index-CLg87OBW.d.ts} +6 -1
- package/dist/{index-Cf-D2jk1.d.ts → index-CWMDdEr4.d.ts} +2 -2
- package/dist/{index-D5IfUIDh.d.ts → index-DOESKG27.d.ts} +33 -8
- package/dist/{index-DFmOpBGC.d.ts → index-DgILJRXj.d.ts} +35 -9
- package/dist/{index-RlLXd8dL.d.ts → index-DmXSxbLi.d.ts} +4 -4
- package/dist/{index-BBdvo-5d.d.ts → index-Dp2VDwog.d.ts} +49 -6
- package/dist/{index-528Ugmyp.d.ts → index-SaTsEfGP.d.ts} +76 -8
- package/dist/{index-BAy4tsAP.d.ts → index-fag8VJZ2.d.ts} +3 -4
- package/dist/index.d.ts +30 -13
- package/dist/index.js +10 -9
- package/dist/jobs/index.d.ts +147 -101
- package/dist/jobs/index.js +744 -237
- package/dist/{json-schema-DP6V7cbr.js → json-schema-m1fiwKgN.js} +22 -1
- package/dist/kit-events-CTFDfSwb.js +60 -0
- package/dist/kit-transport-B82k1djG.d.ts +33 -0
- package/dist/kit-transport-DJlHQUBU.js +55 -0
- package/dist/{config-BpxDfnbg.js → kits-CGLLrNPd.js} +37 -9
- package/dist/list/index.d.ts +1 -1
- package/dist/{live-BHakSWWo.d.ts → live-D9W2M5aO.d.ts} +6 -5
- package/dist/mcp/index.d.ts +25 -8
- package/dist/mcp/index.js +23 -14
- package/dist/{mfa-C7MNQ1Ob.d.ts → mfa-Cu96oJdw.d.ts} +2 -16
- package/dist/next/index.d.ts +31 -10
- package/dist/next/index.js +136 -27
- package/dist/notifications/index.d.ts +162 -0
- package/dist/notifications/index.js +264 -0
- package/dist/openapi/index.d.ts +1 -1
- package/dist/openapi/index.js +2 -2
- package/dist/orgs/index.d.ts +132 -0
- package/dist/orgs/index.js +245 -0
- package/dist/orpc/index.d.ts +28 -8
- package/dist/orpc/index.js +16 -3
- package/dist/otel/index.d.ts +21 -2
- package/dist/otel/index.js +50 -9
- package/dist/permdock-sql-C2Inc6Xc.d.ts +29 -0
- package/dist/permdock-sql-DgOfeixi.js +51 -0
- package/dist/plugin-DwARMuQp.js +59 -0
- package/dist/{plugin-bNhvJwRh.d.ts → plugin-o144f322.d.ts} +17 -5
- package/dist/plugins/actor/index.d.ts +5 -2
- package/dist/plugins/actor/index.js +22 -8
- package/dist/plugins/rules/index.d.ts +2 -2
- package/dist/plugins/rules/index.js +15 -10
- package/dist/plugins/soft-delete/index.d.ts +3 -3
- package/dist/plugins/soft-delete/index.js +14 -5
- package/dist/plugins/tenant/index.d.ts +2 -2
- package/dist/plugins/tenant/index.js +38 -20
- package/dist/plugins/timestamps/index.d.ts +1 -1
- package/dist/plugins/timestamps/index.js +4 -3
- package/dist/plugins/validation/index.d.ts +2 -2
- package/dist/plugins/validation/index.js +26 -6
- package/dist/{pool-DFwEQwmN.d.ts → pool-C9voM4Kw.d.ts} +17 -6
- package/dist/postgres/index.d.ts +5 -5
- package/dist/postgres/index.js +20 -11
- package/dist/{postgrest-Bc-0Xw62.d.ts → postgrest-CruYKAvV.d.ts} +1 -1
- package/dist/{problem-BihjPR0t.js → problem-BW1KhzSG.js} +2 -1
- package/dist/{problem-U8UEKccv.d.ts → problem-Blxe8S31.d.ts} +1 -1
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +3 -3
- package/dist/{query-BMMSWbI7.js → query-CCbo44lG.js} +2 -2
- package/dist/queue-BYUCjr59.d.ts +252 -0
- package/dist/react/index.d.ts +8 -7
- package/dist/react/index.js +4 -3
- package/dist/react/server.d.ts +14 -3
- package/dist/react/server.js +10 -2
- package/dist/react/session.d.ts +7 -1
- package/dist/react/session.js +9 -1
- package/dist/{react-CQHMaESR.js → react-C_s7kiNz.js} +103 -11
- package/dist/{read-set-FWK8qQ58.js → read-set-DQFt1N_Y.js} +22 -1
- package/dist/realtime/index.d.ts +3 -3
- package/dist/realtime/index.js +5 -4
- package/dist/{refresh-BwYkyNOQ.d.ts → refresh-CFPX0Qft.d.ts} +1 -1
- package/dist/{resource-BJcaIfxi.d.ts → resource-DNRFP7KR.d.ts} +5 -5
- package/dist/{resource-My_y-Syh.js → resource-mqfJW4YF.js} +22 -7
- package/dist/respond-BR1fJWOB.d.ts +377 -0
- package/dist/{result-Bj6yt33D.js → result-BKiliPY7.js} +1 -1
- package/dist/server/index.d.ts +15 -12
- package/dist/server/index.js +11 -17
- package/dist/{server-BA75B0mT.js → server-B1mZ7CgQ.js} +259 -37
- package/dist/{shared-DnoCdMDr.js → shared-D1dRUrAg.js} +16 -2
- package/dist/signers-Cc6r98SW.d.ts +162 -0
- package/dist/{simplify-Cqvfr9dj.js → simplify-_0oUKqCd.js} +4 -115
- package/dist/sql/index.d.ts +283 -11
- package/dist/sql/index.js +8705 -775
- package/dist/{sql-B34HU2DI.js → sql-Coh6n4cC.js} +9 -2
- package/dist/ssr/index.d.ts +4 -4
- package/dist/ssr/index.js +1 -2
- package/dist/{standard-BRyBGTtp.js → standard-D7r6dMUu.js} +6 -3
- package/dist/{standard-BsaCYQJc.d.ts → standard-J4YgMOLm.d.ts} +1 -1
- package/dist/{stats-C_YxA2r1.js → stats-Bqob3WqT.js} +4 -4
- package/dist/storage/index.d.ts +4 -4
- package/dist/storage/index.js +155 -26
- package/dist/support-CEPwlw2G.d.ts +89 -0
- package/dist/support-view-B8SlToQX.d.ts +17 -0
- package/dist/support-view-g4YLBeqX.js +18 -0
- package/dist/support-wHnK6zp-.js +293 -0
- package/dist/{permdock-sql-C4KFzLc9.js → template-TxrGI5jR.js} +2 -31
- package/dist/{temporal-BwshQk0W.js → temporal-CVlIV2DZ.js} +17 -4
- package/dist/{temporal-required-CBNHT6ni.js → temporal-required-CAt4Dc9Z.js} +2 -2
- package/dist/testing/index.d.ts +132 -10
- package/dist/testing/index.js +476 -8
- package/dist/{types-DONv9SHN.d.ts → types-Dnf_wRDz.d.ts} +26 -3
- package/dist/user-change-CHFEUro1.js +49 -0
- package/dist/{webhooks-Bh_Jf_-l.js → verify-Ce49ETPz.js} +5 -5
- package/dist/{version-Bmf7_FzM.js → version-DOYTYwHP.js} +1 -1
- package/dist/{mfa-byfjruR9.js → view-Bn6HNKUe.js} +97 -98
- package/dist/{view-B64o81wp.d.ts → view-CbgObAeb.d.ts} +22 -31
- package/dist/webhooks/index.d.ts +135 -4
- package/dist/webhooks/index.js +588 -2
- package/dist/wire-D2dT86vi.js +35 -0
- package/package.json +27 -19
- package/schemas/config-v1.json +131 -1
- package/schemas/doctor-report-v1.json +11 -0
- package/skills/better-supabase/SKILL.md +9 -6
- package/skills/better-supabase/references/plugins.md +6 -3
- package/skills/better-supabase/references/troubleshooting.md +1 -0
- package/skills/better-supabase-api/SKILL.md +9 -2
- package/skills/better-supabase-api/references/adapters.md +11 -8
- package/skills/better-supabase-auth/SKILL.md +18 -7
- package/skills/better-supabase-auth/references/permdock.md +78 -11
- package/dist/cli/gen-D5cLCULx.js +0 -2
- package/dist/cli/sql-D4VbqmXi.js +0 -199
- package/dist/events-B21UOhvP.d.ts +0 -84
- package/dist/executor-B5IoUxLE.d.ts +0 -195
- package/dist/invalidate-CgxmRamI.js +0 -24
- package/dist/permdock-sql-DImJULY-.d.ts +0 -17
- package/dist/plugin-BqR6wKMB.js +0 -23
- package/dist/respond-DgIbdrt8.d.ts +0 -194
- package/dist/view-BrJI2LKp.js +0 -45
package/dist/testing/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { n as dbError } from "../errors-
|
|
2
|
-
import { i as temporalText } from "../temporal-
|
|
3
|
-
import { t as temporal } from "../temporal-required-
|
|
4
|
-
import { i as ok } from "../result-
|
|
5
|
-
import { t as BetterSupabase } from "../define-
|
|
1
|
+
import { n as dbError } from "../errors-DtwXa6b8.js";
|
|
2
|
+
import { i as temporalText } from "../temporal-CVlIV2DZ.js";
|
|
3
|
+
import { t as temporal } from "../temporal-required-CAt4Dc9Z.js";
|
|
4
|
+
import { i as ok } from "../result-BKiliPY7.js";
|
|
5
|
+
import { t as BetterSupabase } from "../define-cNKzqwL_.js";
|
|
6
6
|
import { a as toBase64Url, r as fromBase64, t as base64ToText } from "../base64-D2gsioCY.js";
|
|
7
|
-
import {
|
|
7
|
+
import { a as resolveConfig } from "../kits-CGLLrNPd.js";
|
|
8
8
|
import { t as isList } from "../guards-BnzDvFrz.js";
|
|
9
9
|
import { ENV_VARIABLES } from "../env/index.js";
|
|
10
|
-
import { n as postgresExecutor } from "../executor-
|
|
10
|
+
import { n as postgresExecutor } from "../executor-XycESQPH.js";
|
|
11
11
|
import { toCloudEvents } from "../events/index.js";
|
|
12
12
|
import { createClient } from "@supabase/supabase-js";
|
|
13
13
|
import { existsSync } from "node:fs";
|
|
@@ -546,6 +546,147 @@ function testEventSink(sink, options = {}) {
|
|
|
546
546
|
}]
|
|
547
547
|
]);
|
|
548
548
|
}
|
|
549
|
+
function claimedJob(queue, row) {
|
|
550
|
+
const instant = temporal().Instant.fromEpochMilliseconds(0);
|
|
551
|
+
return {
|
|
552
|
+
id: Number(row.id),
|
|
553
|
+
queue,
|
|
554
|
+
payload: row.message?.payload,
|
|
555
|
+
attempts: row.attempts,
|
|
556
|
+
maxAttempts: row.message?.max_attempts ?? 5,
|
|
557
|
+
enqueuedAt: instant,
|
|
558
|
+
visibleUntil: instant,
|
|
559
|
+
lastError: row.message?.last_error ?? null,
|
|
560
|
+
context: {}
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Proves a `QueueBackend` round-trips payloads, hides claimed messages for
|
|
565
|
+
* their lease, counts attempts, retries and dead-letters on `fail`, and
|
|
566
|
+
* (when it has `leases`) rejects a stale attempt and extends a lease. It
|
|
567
|
+
* writes to `options.queue` on the backend's real store.
|
|
568
|
+
*/
|
|
569
|
+
function testQueueBackend(backend, options) {
|
|
570
|
+
const { queue } = options;
|
|
571
|
+
const claimOne = async () => {
|
|
572
|
+
const [row] = await backend.read(queue, 30, 1);
|
|
573
|
+
return row && claimedJob(queue, row);
|
|
574
|
+
};
|
|
575
|
+
return conform(`QueueBackend "${backend.name}"`, [
|
|
576
|
+
hasName(backend),
|
|
577
|
+
["has apiVersion 1", () => {
|
|
578
|
+
const version = backend.apiVersion;
|
|
579
|
+
expect(version === 1, "apiVersion must be 1");
|
|
580
|
+
}],
|
|
581
|
+
["round-trips a payload and leases it", async () => {
|
|
582
|
+
const id = await backend.send(queue, { n: 1 }, 0, 3, void 0);
|
|
583
|
+
expect(Number.isFinite(id), "send must return a numeric id");
|
|
584
|
+
const job = await claimOne();
|
|
585
|
+
expect(job?.id === id, "read must return the sent message");
|
|
586
|
+
expect(same(job.payload, { n: 1 }), "the payload must round-trip");
|
|
587
|
+
expect(job.attempts === 1, "the first claim must be attempt 1");
|
|
588
|
+
expect(job.maxAttempts === 3, "max_attempts must round-trip");
|
|
589
|
+
expect(await claimOne() === void 0, "a leased message must stay hidden from other reads");
|
|
590
|
+
expect(await backend.complete(job), "complete must return true");
|
|
591
|
+
}],
|
|
592
|
+
backend.leases && ["rejects a stale attempt and extends a lease", async () => {
|
|
593
|
+
await backend.send(queue, { n: 2 }, 0, 3, void 0);
|
|
594
|
+
const job = await claimOne();
|
|
595
|
+
expect(job, "read must return the sent message");
|
|
596
|
+
expect(!await backend.complete({
|
|
597
|
+
...job,
|
|
598
|
+
attempts: job.attempts + 1
|
|
599
|
+
}), "complete with another attempt must return false");
|
|
600
|
+
expect(await backend.extend(job, 60), "extend must return true");
|
|
601
|
+
expect(await backend.complete(job), "complete must return true");
|
|
602
|
+
}],
|
|
603
|
+
["retries, then dead-letters at max attempts", async () => {
|
|
604
|
+
await backend.send(queue, { n: 3 }, 0, 2, void 0);
|
|
605
|
+
const first = await claimOne();
|
|
606
|
+
expect(first, "read must return the sent message");
|
|
607
|
+
expect(await backend.fail(first, "boom", 0) === "queued", "fail before max_attempts must return queued");
|
|
608
|
+
const second = await claimOne();
|
|
609
|
+
expect(second?.attempts === 2, "a retried message must be attempt 2");
|
|
610
|
+
expect(await backend.fail(second, "boom", 0) === "dead", "fail at max_attempts must return dead");
|
|
611
|
+
expect(await claimOne() === void 0, "a dead message must not be claimed again");
|
|
612
|
+
}],
|
|
613
|
+
(options.dedupe ?? backend.leases) && ["returns the first id for a repeated dedupe key", async () => {
|
|
614
|
+
expect(await backend.send(queue, { n: 4 }, 0, 3, "same") === await backend.send(queue, { n: 4 }, 0, 3, "same"), "a waiting dedupe key must return its id");
|
|
615
|
+
const job = await claimOne();
|
|
616
|
+
expect(job, "read must return the sent message");
|
|
617
|
+
await backend.complete(job);
|
|
618
|
+
}]
|
|
619
|
+
]);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Proves a `SupportSessionStore` starts sessions with the input it was
|
|
623
|
+
* given, shows a running session only to its admin, ends the admin's
|
|
624
|
+
* previous session on start, ends a session once, and lists sessions.
|
|
625
|
+
* It writes sessions to the store; they are all ended when it returns.
|
|
626
|
+
*/
|
|
627
|
+
function testSupportSessionStore(store, options) {
|
|
628
|
+
const { admin } = options;
|
|
629
|
+
const [first, second] = options.targets;
|
|
630
|
+
const start = (targetUserId) => store.start({
|
|
631
|
+
adminId: admin.id,
|
|
632
|
+
adminClaims: admin.claims,
|
|
633
|
+
targetUserId,
|
|
634
|
+
reason: "conformance",
|
|
635
|
+
ttlSeconds: 600,
|
|
636
|
+
readOnly: true,
|
|
637
|
+
metadata: { ticket: "T-1" }
|
|
638
|
+
});
|
|
639
|
+
return conform(`SupportSessionStore "${store.name}"`, [
|
|
640
|
+
hasName(store),
|
|
641
|
+
["has apiVersion 1", () => {
|
|
642
|
+
const version = store.apiVersion;
|
|
643
|
+
expect(version === 1, "apiVersion must be 1");
|
|
644
|
+
}],
|
|
645
|
+
["starts a session with its input and shows it to its admin only", async () => {
|
|
646
|
+
const session = await start(first);
|
|
647
|
+
try {
|
|
648
|
+
expect(session.id.length > 0, "start must return an id");
|
|
649
|
+
expect(session.adminId === admin.id, "adminId must round-trip");
|
|
650
|
+
expect(session.targetUserId === first, "targetUserId must round-trip");
|
|
651
|
+
expect(session.reason === "conformance", "reason must round-trip");
|
|
652
|
+
expect(session.readOnly, "readOnly must round-trip");
|
|
653
|
+
expect(same(session.metadata, { ticket: "T-1" }), "metadata must round-trip");
|
|
654
|
+
const seconds = (session.expiresAt.epochMilliseconds - session.startedAt.epochMilliseconds) / 1e3;
|
|
655
|
+
expect(Math.abs(seconds - 600) < 5, "expiresAt must be ttlSeconds after startedAt");
|
|
656
|
+
expect((await store.get(session.id, admin.id))?.id === session.id, "get must return the running session");
|
|
657
|
+
expect(await store.get(session.id, first) === void 0, "get must hide a session from anyone but its admin");
|
|
658
|
+
} finally {
|
|
659
|
+
await store.end(session.id, "admin");
|
|
660
|
+
}
|
|
661
|
+
}],
|
|
662
|
+
["ends the admin's previous session on start", async () => {
|
|
663
|
+
const older = await start(first);
|
|
664
|
+
const newer = await start(second);
|
|
665
|
+
try {
|
|
666
|
+
expect(await store.get(older.id, admin.id) === void 0, "starting a session must end the admin's previous one");
|
|
667
|
+
expect((await store.get(newer.id, admin.id))?.id === newer.id, "the new session must be running");
|
|
668
|
+
} finally {
|
|
669
|
+
await store.end(newer.id, "admin");
|
|
670
|
+
}
|
|
671
|
+
}],
|
|
672
|
+
["ends a session once and lists it", async () => {
|
|
673
|
+
const session = await start(first);
|
|
674
|
+
expect(await store.end(session.id, "admin"), "end must return true");
|
|
675
|
+
expect(!await store.end(session.id, "admin"), "ending an ended session must return false");
|
|
676
|
+
expect(await store.get(session.id, admin.id) === void 0, "get must not return an ended session");
|
|
677
|
+
const entry = (await store.list({
|
|
678
|
+
adminId: admin.id,
|
|
679
|
+
active: false
|
|
680
|
+
})).find((item) => item.id === session.id);
|
|
681
|
+
expect(entry, "list must include the ended session");
|
|
682
|
+
expect(entry.endedAt !== void 0, "an ended session has endedAt");
|
|
683
|
+
expect((await store.list({
|
|
684
|
+
adminId: admin.id,
|
|
685
|
+
active: true
|
|
686
|
+
})).every((item) => item.id !== session.id), "list({ active: true }) must leave out ended sessions");
|
|
687
|
+
}]
|
|
688
|
+
]);
|
|
689
|
+
}
|
|
549
690
|
async function resolveSafely(resolver, request) {
|
|
550
691
|
try {
|
|
551
692
|
return await resolver.resolve(request);
|
|
@@ -678,6 +819,21 @@ function testPlugin(plugin, options) {
|
|
|
678
819
|
expect(first.kind === op.kind, "must return a mutation of the same kind");
|
|
679
820
|
expect(same(first, second), "two calls with the same input differ");
|
|
680
821
|
}],
|
|
822
|
+
plugin.afterMutation && create && ["afterMutation cannot change the result", async () => {
|
|
823
|
+
const row = {
|
|
824
|
+
marker: "conformance",
|
|
825
|
+
nested: { kept: true }
|
|
826
|
+
};
|
|
827
|
+
const result = await betterSupabase.use(plugin).connect({
|
|
828
|
+
name: "rows",
|
|
829
|
+
execute: () => Promise.resolve(ok({
|
|
830
|
+
rows: [structuredClone(row)],
|
|
831
|
+
count: 1
|
|
832
|
+
}))
|
|
833
|
+
}, context)[table]["create"](create);
|
|
834
|
+
expect(result.ok, "create failed");
|
|
835
|
+
expect(same(result.data, row), "afterMutation changed the returned row");
|
|
836
|
+
}],
|
|
681
837
|
plugin.wrapExecutor && ["wrapExecutor keeps results intact", async () => {
|
|
682
838
|
const op = await captureOp(betterSupabase, (db) => db[table]["findMany"]({ limit: 1 }));
|
|
683
839
|
const data = {
|
|
@@ -814,6 +970,146 @@ async function expectTenantIsolation(betterSupabase, options) {
|
|
|
814
970
|
}
|
|
815
971
|
}
|
|
816
972
|
//#endregion
|
|
973
|
+
//#region src/testing/claims-fixtures.ts
|
|
974
|
+
const base = {
|
|
975
|
+
sub: "6f1c2c1e-5d0a-4d9e-9a51-6b1f0e7c2a10",
|
|
976
|
+
aud: "authenticated",
|
|
977
|
+
role: "authenticated",
|
|
978
|
+
iss: "https://project.supabase.co/auth/v1",
|
|
979
|
+
aal: "aal1",
|
|
980
|
+
session_id: "b7a8f9c0-1111-4222-8333-944455556666",
|
|
981
|
+
is_anonymous: false
|
|
982
|
+
};
|
|
983
|
+
const admin = "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d";
|
|
984
|
+
const supportSessionId = "4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b";
|
|
985
|
+
const client = "5f0e4d3c-2b1a-4098-8776-655443322110";
|
|
986
|
+
const chain = {
|
|
987
|
+
sub: "agent-runner",
|
|
988
|
+
act: { sub: "mcp-client-42" }
|
|
989
|
+
};
|
|
990
|
+
/**
|
|
991
|
+
* `supportSession` and `supportSessionReadOnly` are what `supportClaims`
|
|
992
|
+
* mints (`act.kind: "support"`), `impersonation` what `actingAs` mints
|
|
993
|
+
* (`act.kind: "impersonation"`), `oauthClient` a Supabase OAuth server token
|
|
994
|
+
* and `agentChain` an RFC 8693 chain without `kind`.
|
|
995
|
+
*/
|
|
996
|
+
const supabaseClaimFixtures = {
|
|
997
|
+
supportSession: {
|
|
998
|
+
claims: {
|
|
999
|
+
...base,
|
|
1000
|
+
act: {
|
|
1001
|
+
kind: "support",
|
|
1002
|
+
sub: admin,
|
|
1003
|
+
reason: "ticket 42",
|
|
1004
|
+
session_id: supportSessionId,
|
|
1005
|
+
read_only: false
|
|
1006
|
+
}
|
|
1007
|
+
},
|
|
1008
|
+
expect: {
|
|
1009
|
+
actor: {
|
|
1010
|
+
kind: "support",
|
|
1011
|
+
id: admin,
|
|
1012
|
+
sessionId: supportSessionId,
|
|
1013
|
+
readOnly: false,
|
|
1014
|
+
reason: "ticket 42"
|
|
1015
|
+
},
|
|
1016
|
+
impersonator: {
|
|
1017
|
+
kind: "support",
|
|
1018
|
+
id: admin,
|
|
1019
|
+
sessionId: supportSessionId,
|
|
1020
|
+
readOnly: false,
|
|
1021
|
+
reason: "ticket 42"
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
},
|
|
1025
|
+
supportSessionReadOnly: {
|
|
1026
|
+
claims: {
|
|
1027
|
+
...base,
|
|
1028
|
+
act: {
|
|
1029
|
+
kind: "support",
|
|
1030
|
+
sub: admin,
|
|
1031
|
+
reason: "ticket 42",
|
|
1032
|
+
session_id: supportSessionId,
|
|
1033
|
+
read_only: true
|
|
1034
|
+
}
|
|
1035
|
+
},
|
|
1036
|
+
expect: {
|
|
1037
|
+
actor: {
|
|
1038
|
+
kind: "support",
|
|
1039
|
+
id: admin,
|
|
1040
|
+
sessionId: supportSessionId,
|
|
1041
|
+
readOnly: true,
|
|
1042
|
+
reason: "ticket 42"
|
|
1043
|
+
},
|
|
1044
|
+
impersonator: {
|
|
1045
|
+
kind: "support",
|
|
1046
|
+
id: admin,
|
|
1047
|
+
sessionId: supportSessionId,
|
|
1048
|
+
readOnly: true,
|
|
1049
|
+
reason: "ticket 42"
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
},
|
|
1053
|
+
impersonation: {
|
|
1054
|
+
claims: {
|
|
1055
|
+
...base,
|
|
1056
|
+
act: {
|
|
1057
|
+
kind: "impersonation",
|
|
1058
|
+
sub: admin,
|
|
1059
|
+
reason: "ticket 42"
|
|
1060
|
+
}
|
|
1061
|
+
},
|
|
1062
|
+
expect: {
|
|
1063
|
+
actor: {
|
|
1064
|
+
kind: "impersonation",
|
|
1065
|
+
id: admin,
|
|
1066
|
+
reason: "ticket 42"
|
|
1067
|
+
},
|
|
1068
|
+
impersonator: {
|
|
1069
|
+
kind: "impersonation",
|
|
1070
|
+
id: admin,
|
|
1071
|
+
reason: "ticket 42"
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
},
|
|
1075
|
+
oauthClient: {
|
|
1076
|
+
claims: {
|
|
1077
|
+
...base,
|
|
1078
|
+
client_id: client,
|
|
1079
|
+
scope: "openid email posts:read"
|
|
1080
|
+
},
|
|
1081
|
+
expect: {
|
|
1082
|
+
actor: {
|
|
1083
|
+
kind: "oauth-client",
|
|
1084
|
+
id: client
|
|
1085
|
+
},
|
|
1086
|
+
delegation: { scopes: [
|
|
1087
|
+
"openid",
|
|
1088
|
+
"email",
|
|
1089
|
+
"posts:read"
|
|
1090
|
+
] }
|
|
1091
|
+
}
|
|
1092
|
+
},
|
|
1093
|
+
agentChain: {
|
|
1094
|
+
claims: {
|
|
1095
|
+
...base,
|
|
1096
|
+
scope: "posts:read",
|
|
1097
|
+
act: chain
|
|
1098
|
+
},
|
|
1099
|
+
expect: {
|
|
1100
|
+
actor: {
|
|
1101
|
+
kind: "oauth-client",
|
|
1102
|
+
id: "agent-runner",
|
|
1103
|
+
chain
|
|
1104
|
+
},
|
|
1105
|
+
delegation: {
|
|
1106
|
+
scopes: ["posts:read"],
|
|
1107
|
+
chain
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
//#endregion
|
|
817
1113
|
//#region src/testing/seed.ts
|
|
818
1114
|
const SEED = Symbol.for("better-supabase.seed");
|
|
819
1115
|
function isSeed(value) {
|
|
@@ -915,4 +1211,176 @@ function defineSeed(betterSupabase, fixtures) {
|
|
|
915
1211
|
};
|
|
916
1212
|
}
|
|
917
1213
|
//#endregion
|
|
918
|
-
|
|
1214
|
+
//#region src/testing/notification-channel.ts
|
|
1215
|
+
const STATUSES = /* @__PURE__ */ new Set(["sent", "skipped"]);
|
|
1216
|
+
function sample(channel, email) {
|
|
1217
|
+
const createdAt = temporal().Instant.fromEpochMilliseconds(0);
|
|
1218
|
+
return Object.freeze({
|
|
1219
|
+
deliveryId: "00000000-0000-4000-8000-00000000c0f0",
|
|
1220
|
+
channel,
|
|
1221
|
+
attempts: 1,
|
|
1222
|
+
userId: "00000000-0000-4000-8000-0000000000aa",
|
|
1223
|
+
email,
|
|
1224
|
+
notification: Object.freeze({
|
|
1225
|
+
id: "00000000-0000-4000-8000-00000000c0f1",
|
|
1226
|
+
eventId: "00000000-0000-4000-8000-00000000c0f2",
|
|
1227
|
+
type: "conformance.check",
|
|
1228
|
+
data: Object.freeze({ title: "Conformance" }),
|
|
1229
|
+
tenant: null,
|
|
1230
|
+
actorId: null,
|
|
1231
|
+
subject: Object.freeze({
|
|
1232
|
+
type: "task",
|
|
1233
|
+
id: "42",
|
|
1234
|
+
label: "Conformance"
|
|
1235
|
+
}),
|
|
1236
|
+
summary: "Conformance check",
|
|
1237
|
+
actionPath: "/",
|
|
1238
|
+
priority: null,
|
|
1239
|
+
createdAt,
|
|
1240
|
+
readAt: null,
|
|
1241
|
+
resolvedAt: null
|
|
1242
|
+
}),
|
|
1243
|
+
text: Object.freeze({ title: "Conformance check" })
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Runs the `NotificationChannel` contract against `channel`: the API version,
|
|
1248
|
+
* a name, and one frozen sample message it must send without mutating.
|
|
1249
|
+
* Throws a `ConformanceError` listing every failed check.
|
|
1250
|
+
*/
|
|
1251
|
+
function testNotificationChannel(channel, options = {}) {
|
|
1252
|
+
const message = sample(channel.name, options.email ?? null);
|
|
1253
|
+
return conform(`NotificationChannel "${channel.name}"`, [
|
|
1254
|
+
["has apiVersion 1", () => {
|
|
1255
|
+
const version = channel.apiVersion;
|
|
1256
|
+
expect(version === 1, "apiVersion must be 1");
|
|
1257
|
+
}],
|
|
1258
|
+
["has a name", () => {
|
|
1259
|
+
expect(typeof channel.name === "string" && channel.name.length > 0, "name must be a non-empty string");
|
|
1260
|
+
}],
|
|
1261
|
+
["sends a message without mutating it and returns a valid result", async () => {
|
|
1262
|
+
const result = await channel.send(message);
|
|
1263
|
+
if (result === void 0) return;
|
|
1264
|
+
expect(result.status === void 0 || STATUSES.has(result.status), `status must be "sent" or "skipped", got ${String(result.status)}`);
|
|
1265
|
+
for (const key of ["provider", "providerMessageId"]) expect(result[key] === void 0 || typeof result[key] === "string", `${key} must be a string`);
|
|
1266
|
+
}],
|
|
1267
|
+
options.received && ["delivers the message", async () => {
|
|
1268
|
+
expect((await options.received()).includes(message.deliveryId), `delivery ${message.deliveryId} was not received`);
|
|
1269
|
+
}]
|
|
1270
|
+
]);
|
|
1271
|
+
}
|
|
1272
|
+
//#endregion
|
|
1273
|
+
//#region src/testing/webhooks.ts
|
|
1274
|
+
const SECRET = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
|
|
1275
|
+
function signInput(body, timestamp) {
|
|
1276
|
+
return Object.freeze({
|
|
1277
|
+
id: "msg_conformance",
|
|
1278
|
+
body,
|
|
1279
|
+
timestamp,
|
|
1280
|
+
secrets: Object.freeze([SECRET])
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
function isHeaders(value) {
|
|
1284
|
+
return typeof value === "object" && value !== null && Object.values(value).every((entry) => typeof entry === "string");
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Runs the `WebhookSigner` contract against `signer`: the API version, a
|
|
1288
|
+
* name, string headers that depend on the body and are stable for the same
|
|
1289
|
+
* input, and, with `verify`, a signature the receiver accepts.
|
|
1290
|
+
*/
|
|
1291
|
+
function testWebhookSigner(signer, options = {}) {
|
|
1292
|
+
const body = JSON.stringify({
|
|
1293
|
+
type: "conformance.check",
|
|
1294
|
+
data: { n: 1 }
|
|
1295
|
+
});
|
|
1296
|
+
const now = temporal().Now.instant();
|
|
1297
|
+
return conform(`WebhookSigner "${signer.name}"`, [
|
|
1298
|
+
["has apiVersion 1", () => {
|
|
1299
|
+
const version = signer.apiVersion;
|
|
1300
|
+
expect(version === 1, "apiVersion must be 1");
|
|
1301
|
+
}],
|
|
1302
|
+
["has a name", () => {
|
|
1303
|
+
expect(typeof signer.name === "string" && signer.name.length > 0, "name must be a non-empty string");
|
|
1304
|
+
}],
|
|
1305
|
+
["returns string headers, stable for the same input", async () => {
|
|
1306
|
+
const first = await signer.sign(signInput(body, now));
|
|
1307
|
+
const second = await signer.sign(signInput(body, now));
|
|
1308
|
+
expect(isHeaders(first), "sign must return a record of string headers");
|
|
1309
|
+
expect(isHeaders(first) && Object.keys(first).length > 0, "sign must return at least one header");
|
|
1310
|
+
expect(JSON.stringify(first) === JSON.stringify(second), "the same input must produce the same headers");
|
|
1311
|
+
}],
|
|
1312
|
+
["changes the headers when the body changes", async () => {
|
|
1313
|
+
const original = await signer.sign(signInput(body, now));
|
|
1314
|
+
const tampered = await signer.sign(signInput(`${body} `, now));
|
|
1315
|
+
expect(JSON.stringify(original) !== JSON.stringify(tampered), "the signature must cover the body");
|
|
1316
|
+
}],
|
|
1317
|
+
options.verify && ["produces a signature the receiver verifies", async () => {
|
|
1318
|
+
const headers = await signer.sign(signInput(body, now));
|
|
1319
|
+
expect(await options.verify({
|
|
1320
|
+
headers,
|
|
1321
|
+
body
|
|
1322
|
+
}, SECRET), "the receiver rejected the signature");
|
|
1323
|
+
}]
|
|
1324
|
+
]);
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Runs the `WebhookTransport` contract against `transport`: the API version,
|
|
1328
|
+
* a name, and one POST to `options.url` that returns an HTTP status and a
|
|
1329
|
+
* string body.
|
|
1330
|
+
*/
|
|
1331
|
+
function testWebhookTransport(transport, options) {
|
|
1332
|
+
const body = JSON.stringify({
|
|
1333
|
+
type: "conformance.check",
|
|
1334
|
+
id: crypto.randomUUID()
|
|
1335
|
+
});
|
|
1336
|
+
return conform(`WebhookTransport "${transport.name}"`, [
|
|
1337
|
+
["has apiVersion 1", () => {
|
|
1338
|
+
const version = transport.apiVersion;
|
|
1339
|
+
expect(version === 1, "apiVersion must be 1");
|
|
1340
|
+
}],
|
|
1341
|
+
["has a name", () => {
|
|
1342
|
+
expect(typeof transport.name === "string" && transport.name.length > 0, "name must be a non-empty string");
|
|
1343
|
+
}],
|
|
1344
|
+
["sends the request and returns a status and a body", async () => {
|
|
1345
|
+
const response = await transport.send(Object.freeze({
|
|
1346
|
+
url: options.url,
|
|
1347
|
+
headers: Object.freeze({ "content-type": "application/json" }),
|
|
1348
|
+
body
|
|
1349
|
+
}));
|
|
1350
|
+
expect(Number.isInteger(response.status) && response.status >= 100 && response.status <= 599, `status must be an HTTP status, got ${String(response.status)}`);
|
|
1351
|
+
const responseBody = response.body;
|
|
1352
|
+
expect(typeof responseBody === "string", "body must be a string");
|
|
1353
|
+
}],
|
|
1354
|
+
options.received && ["delivers the body", async () => {
|
|
1355
|
+
expect((await options.received()).includes(body), "the receiver did not get the body");
|
|
1356
|
+
}]
|
|
1357
|
+
]);
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Runs the `WebhookSecretStore` contract against `store`: the API version, a
|
|
1361
|
+
* list of strings, and, when it can rotate, a new secret listed first while
|
|
1362
|
+
* the previous one keeps signing.
|
|
1363
|
+
*/
|
|
1364
|
+
function testWebhookSecretStore(store, options) {
|
|
1365
|
+
return conform("WebhookSecretStore", [
|
|
1366
|
+
["has apiVersion 1", () => {
|
|
1367
|
+
const version = store.apiVersion;
|
|
1368
|
+
expect(version === 1, "apiVersion must be 1");
|
|
1369
|
+
}],
|
|
1370
|
+
["lists the secrets as strings", async () => {
|
|
1371
|
+
const secrets = await store.secrets(options.endpointId);
|
|
1372
|
+
expect(Array.isArray(secrets) && secrets.every((secret) => typeof secret === "string"), "secrets must return an array of strings");
|
|
1373
|
+
}],
|
|
1374
|
+
store.rotate && ["rotates to a new secret and keeps the previous one during the overlap", async () => {
|
|
1375
|
+
const first = await store.rotate(options.endpointId, { overlap: "1 hour" });
|
|
1376
|
+
const second = await store.rotate(options.endpointId, { overlap: "1 hour" });
|
|
1377
|
+
expect(typeof first === "string" && first.length > 0, "rotate must return the new secret");
|
|
1378
|
+
expect(first !== second, "each rotation must create a new secret");
|
|
1379
|
+
const secrets = await store.secrets(options.endpointId);
|
|
1380
|
+
expect(secrets[0] === second, "the newest secret must be listed first");
|
|
1381
|
+
expect(secrets.includes(first), "the previous secret must keep signing during the overlap");
|
|
1382
|
+
}]
|
|
1383
|
+
]);
|
|
1384
|
+
}
|
|
1385
|
+
//#endregion
|
|
1386
|
+
export { ConformanceError, LOCAL_JWT_SECRET, asUser, createTestSigner, defineSeed, expectDbBudget, expectTenantIsolation, isSeed, localAuth, localSigningKey, signLocalJwt, signTestJwt, signTestJwtWithKey, supabaseClaimFixtures, testAuthResolver, testCacheAdapter, testEventSink, testExecutor, testGenerator, testNotificationChannel, testPlugin, testQueueBackend, testSupportSessionStore, testWebhookSecretStore, testWebhookSigner, testWebhookTransport };
|
|
@@ -62,6 +62,8 @@ interface TableFlags {
|
|
|
62
62
|
readonly actor?: {
|
|
63
63
|
readonly createdBy?: string;
|
|
64
64
|
readonly updatedBy?: string;
|
|
65
|
+
/** The admin behind an impersonated write; `null` otherwise. */
|
|
66
|
+
readonly impersonatedBy?: string;
|
|
65
67
|
};
|
|
66
68
|
/** App column used for optimistic concurrency, usually `updatedAt`. */
|
|
67
69
|
readonly version?: string;
|
|
@@ -140,7 +142,7 @@ interface PermdockBucketPolicy {
|
|
|
140
142
|
readonly segment?: number;
|
|
141
143
|
/**
|
|
142
144
|
* Schema of the helpers `permdock rls generate` writes: PermDock's
|
|
143
|
-
* `rls.schema`. Defaults to `
|
|
145
|
+
* `rls.schema`. Defaults to `permdock`, PermDock's default.
|
|
144
146
|
*/
|
|
145
147
|
readonly schema?: string;
|
|
146
148
|
}
|
|
@@ -153,14 +155,35 @@ interface PermdockTopicPolicy {
|
|
|
153
155
|
readonly scope: string;
|
|
154
156
|
/** 1-based `:`-separated topic segment holding the scope id. Defaults to the `{orgId}` segment. */
|
|
155
157
|
readonly segment?: number;
|
|
158
|
+
/** Schema of PermDock's helpers (`rls.schema`). Defaults to `permdock`. */
|
|
156
159
|
readonly schema?: string;
|
|
157
160
|
}
|
|
158
161
|
type BucketPolicyName = "tenant" | "owner" | "public" | "none";
|
|
162
|
+
/**
|
|
163
|
+
* A policy through the SQL kit's [access contract](/docs/kits/access):
|
|
164
|
+
* `tenant_ids_with(permission)` for the tenant id in the path, or
|
|
165
|
+
* `is_platform(permission)`. Works with every access model.
|
|
166
|
+
*/
|
|
167
|
+
interface AccessBucketPolicy {
|
|
168
|
+
readonly access: {
|
|
169
|
+
/** Downloads, signed URLs, renders and metadata reads. */
|
|
170
|
+
readonly read: string;
|
|
171
|
+
/** Listing; without it `read` covers listing too. */
|
|
172
|
+
readonly list?: string;
|
|
173
|
+
/** Uploads, updates and moves; also deletes unless `delete` is set. */
|
|
174
|
+
readonly write: string;
|
|
175
|
+
readonly delete?: string;
|
|
176
|
+
};
|
|
177
|
+
/** `tenant` (default) checks the path's tenant segment; `platform` checks platform permissions. */
|
|
178
|
+
readonly scope?: "tenant" | "platform";
|
|
179
|
+
/** 1-based path segment holding the tenant id. Defaults to the `{orgId}` segment. */
|
|
180
|
+
readonly segment?: number;
|
|
181
|
+
}
|
|
159
182
|
interface BucketMeta {
|
|
160
183
|
readonly id: string;
|
|
161
184
|
readonly public: boolean;
|
|
162
185
|
readonly path: string;
|
|
163
|
-
readonly policy?: BucketPolicyName | PermdockBucketPolicy;
|
|
186
|
+
readonly policy?: BucketPolicyName | PermdockBucketPolicy | AccessBucketPolicy;
|
|
164
187
|
/** Claim paths for the tenant policy, when `config.claims.tenant` is not the default. */
|
|
165
188
|
readonly tenant?: {
|
|
166
189
|
readonly claim: readonly string[];
|
|
@@ -248,4 +271,4 @@ type IsUnion<T, U = T> = T extends unknown ? [U] extends [T] ? false : true : ne
|
|
|
248
271
|
type PrimaryKeyValue<M extends AnyModels, T extends keyof M> = true extends IsUnion<PrimaryKeyColumn<M, T>> ? { readonly [K in PrimaryKeyColumn<M, T>]: Row<M, T>[K]; } : Row<M, T>[PrimaryKeyColumn<M, T>];
|
|
249
272
|
type Simplify<T> = { [K in keyof T]: T[K]; } & {};
|
|
250
273
|
//#endregion
|
|
251
|
-
export {
|
|
274
|
+
export { Schema as A, PrimaryKeyColumn as C, RelationTypes as D, RelationMeta as E, TableMeta as F, UniqueConstraint as I, UniqueKeyName as L, Simplify as M, TableFlags as N, Relations as O, TableKey as P, UniqueWhere as R, PermdockTopicPolicy as S, RealtimeTableMeta as T, Insert as _, BucketPolicyName as a, ModelsOf as b, ClaimsMeta as c, DatabaseOf as d, Flags as f, FunctionsOf as g, FunctionTypes as h, BucketMeta as i, SchemaMeta as j, Row as k, Codec as l, FunctionMeta as m, AnyFunctions as n, Casing as o, ForeignKeyConstraint as p, AnyModels as r, CheckConstraint as s, AccessBucketPolicy as t, ColumnMeta as u, ModelFlags as v, PrimaryKeyValue as w, PermdockBucketPolicy as x, ModelTypes as y, Update as z };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/query/invalidate.ts
|
|
2
|
+
function isString(value) {
|
|
3
|
+
return typeof value === "string";
|
|
4
|
+
}
|
|
5
|
+
function queryTables(query) {
|
|
6
|
+
const tables = query.meta?.["bsTables"];
|
|
7
|
+
if (Array.isArray(tables)) {
|
|
8
|
+
const list = tables;
|
|
9
|
+
return list.every(isString) ? list : list.filter(isString);
|
|
10
|
+
}
|
|
11
|
+
const [prefix, table] = query.queryKey;
|
|
12
|
+
return prefix === "bs" && typeof table === "string" ? [table] : void 0;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Invalidates every query that read one of `tables`: better-supabase queries
|
|
16
|
+
* by their `meta.bsTables`, and other `['bs', table, ...]` keys by prefix.
|
|
17
|
+
*/
|
|
18
|
+
function invalidateTables(client, tables) {
|
|
19
|
+
if (tables.length === 0) return Promise.resolve();
|
|
20
|
+
const changed = new Set(tables);
|
|
21
|
+
return client.invalidateQueries({ predicate: (query) => queryTables(query)?.some((table) => changed.has(table)) ?? false });
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/query/user-change.ts
|
|
25
|
+
/**
|
|
26
|
+
* Removes every `['bs', ...]` query when the signed-in user changes or signs
|
|
27
|
+
* out, so the next user never sees the previous user's rows. Returns the
|
|
28
|
+
* function that stops listening. `BetterSupabaseProvider` calls it for React;
|
|
29
|
+
* call it yourself with Vue, Solid, Svelte or Angular Query.
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* const stop = clearOnUserChange(queryClient, bs.auth);
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
function clearOnUserChange(queryClient, auth) {
|
|
36
|
+
let user;
|
|
37
|
+
const check = () => {
|
|
38
|
+
const snapshot = auth.current();
|
|
39
|
+
if (snapshot.status === "loading") return;
|
|
40
|
+
const id = snapshot.user?.id ?? null;
|
|
41
|
+
if (user !== void 0 && user !== id) queryClient.removeQueries({ queryKey: ["bs"] });
|
|
42
|
+
user = id;
|
|
43
|
+
};
|
|
44
|
+
const stop = auth.subscribe(check);
|
|
45
|
+
check();
|
|
46
|
+
return stop;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { invalidateTables as n, clearOnUserChange as t };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { n as dbError } from "./errors-
|
|
2
|
-
import { n as nowInstant, r as optionalTemporal } from "./temporal-
|
|
3
|
-
import { n as temporalMissing } from "./temporal-required-
|
|
4
|
-
import { i as ok, n as err } from "./result-
|
|
1
|
+
import { n as dbError } from "./errors-DtwXa6b8.js";
|
|
2
|
+
import { n as nowInstant, r as optionalTemporal } from "./temporal-CVlIV2DZ.js";
|
|
3
|
+
import { n as temporalMissing } from "./temporal-required-CAt4Dc9Z.js";
|
|
4
|
+
import { i as ok, n as err } from "./result-BKiliPY7.js";
|
|
5
5
|
import { i as toBase64, r as fromBase64 } from "./base64-D2gsioCY.js";
|
|
6
|
-
import {
|
|
6
|
+
import { c as toApp } from "./shared-D1dRUrAg.js";
|
|
7
7
|
//#region src/webhooks/verify.ts
|
|
8
8
|
const encoder = /* @__PURE__ */ new TextEncoder();
|
|
9
9
|
/** Accepts `whsec_…`, `v1,whsec_…` (Supabase Auth hooks) or raw base64. */
|