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.
- package/README.md +34 -31
- package/dist/base64-CQeB1eSf.js +42 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +4 -14
- package/dist/{compiler-CZ9nIuqy.d.ts → compiler-2zqGzxVZ.d.ts} +2 -2
- package/dist/config/index.d.ts +4 -3
- package/dist/config/index.js +5 -3
- package/dist/config-Be4GwsqI.js +129 -0
- package/dist/{index-DOsjFvMf.d.ts → config-C1CYcm6o.d.ts} +49 -49
- package/dist/{config-ClDXmVtY.js → config-DOsGYSiT.js} +14 -137
- package/dist/{define-BXMYD9AY.js → define-DF341GDj.js} +71 -51
- package/dist/{define-e3zI4_4g.d.ts → define-DUbydmJa.d.ts} +10 -9
- package/dist/edge/index.d.ts +4 -4
- package/dist/edge/index.js +4 -4
- package/dist/{entitlements-Dng-ctuT.d.ts → entitlements-DvJXoI02.d.ts} +1 -1
- package/dist/env/index.js +203 -1
- package/dist/events/index.d.ts +1 -1
- package/dist/events/index.js +139 -1
- package/dist/{events-DOt3nSO-.d.ts → events-Do8jXh4D.d.ts} +3 -3
- package/dist/{executor-BYXQ9AGn.d.ts → executor-B26lgbHD.d.ts} +2 -2
- package/dist/{executor-DeppvirK.js → executor-C57jx2pn.js} +7 -2
- package/dist/{executor-CGdNQ27g.d.ts → executor-DynmIWhq.d.ts} +2 -2
- package/dist/hono/index.d.ts +5 -5
- package/dist/hono/index.js +4 -4
- package/dist/{index-CBDufH1M.d.ts → index-BNsABq7P.d.ts} +8 -8
- package/dist/{index-Bw1bwtC0.d.ts → index-CADlNHK-.d.ts} +27 -14
- package/dist/{index-DuP14VHo.d.ts → index-CTTMS6_1.d.ts} +19 -8
- package/dist/index-Cf-D2jk1.d.ts +50 -0
- package/dist/{index-K-3WM2Hp.d.ts → index-CriFFJ_p.d.ts} +4 -4
- package/dist/{index-Ciu5Xlml.d.ts → index-GV77NpRP.d.ts} +5 -4
- package/dist/{index-D4v7sncu.d.ts → index-UJ-LUxtI.d.ts} +3 -3
- package/dist/{index-CG69s15v.d.ts → index-kpgopLNv.d.ts} +4 -3
- package/dist/{index-DbxFr7XG.d.ts → index-qriRM9cZ.d.ts} +7 -2
- package/dist/index.d.ts +12 -12
- package/dist/index.js +16 -7
- package/dist/jobs/index.d.ts +25 -24
- package/dist/jobs/index.js +16 -10
- package/dist/{json-schema-BpYqkCKf.js → json-schema--aOXAGfU.js} +7 -2
- package/dist/lint/index.js +1 -1
- package/dist/list/index.d.ts +2 -2
- package/dist/list/index.js +128 -93
- package/dist/{live-DJyZMD6w.d.ts → live-QE3UPbGm.d.ts} +16 -6
- package/dist/mcp/index.d.ts +24 -17
- package/dist/mcp/index.js +41 -12
- package/dist/{mfa-CpLfJSde.js → mfa-BxoR1RkN.js} +74 -19
- package/dist/{mfa-TVia554c.d.ts → mfa-x3V3vGh-.d.ts} +1 -1
- package/dist/next/index.d.ts +39 -26
- package/dist/next/index.js +28 -23
- package/dist/openapi/index.d.ts +1 -1
- package/dist/openapi/index.js +57 -34
- package/dist/orpc/index.d.ts +6 -6
- package/dist/orpc/index.js +3 -3
- package/dist/otel/index.d.ts +2 -2
- package/dist/otel/index.js +4 -2
- package/dist/{permdock-sql-CghlHPuH.js → permdock-sql-Bta_G0v-.js} +11 -6
- package/dist/permdock-sql-DImJULY-.d.ts +17 -0
- package/dist/{plugin-C4TR9J0Z.d.ts → plugin-DVRB8qNE.d.ts} +5 -4
- package/dist/plugins/actor/index.d.ts +1 -1
- package/dist/plugins/rules/index.d.ts +2 -2
- package/dist/plugins/rules/index.js +4 -4
- package/dist/plugins/soft-delete/index.d.ts +3 -3
- package/dist/plugins/soft-delete/index.js +2 -2
- package/dist/plugins/tenant/index.d.ts +2 -2
- package/dist/plugins/timestamps/index.d.ts +1 -1
- package/dist/plugins/timestamps/index.js +1 -1
- package/dist/plugins/validation/index.d.ts +2 -2
- package/dist/pool-B9qsLyFR.d.ts +71 -0
- package/dist/postgres/index.d.ts +10 -5
- package/dist/postgres/index.js +36 -16
- package/dist/{postgrest-cAEQ_IQN.d.ts → postgrest-DmBlevh-.d.ts} +1 -1
- package/dist/{problem-BTkTtRV9.js → problem-CGt1ADix.js} +10 -4
- package/dist/{problem-DlgB0lYl.d.ts → problem-Z2RTQjZD.d.ts} +6 -4
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +1 -1
- package/dist/{query-ChvDbsDE.js → query-BdKLTPlj.js} +2 -2
- package/dist/react/index.d.ts +7 -7
- package/dist/react/index.js +2 -2
- package/dist/react/server.d.ts +3 -3
- package/dist/react/server.js +1 -1
- package/dist/react/session.d.ts +21 -2
- package/dist/react/session.js +19 -1
- package/dist/{react-C83BdBGt.js → react-DNBYNazu.js} +20 -16
- package/dist/{read-set-4lI3W_gZ.js → read-set-D0qBcboM.js} +3 -3
- package/dist/realtime/index.d.ts +4 -3
- package/dist/realtime/index.js +7 -6
- package/dist/{resolve-CjHILk2w.d.ts → resolve-BRciX2jK.d.ts} +6 -5
- package/dist/{resource-CgZa3YG_.d.ts → resource-CwVgiI5q.d.ts} +16 -7
- package/dist/{resource-BHEWmQJK.js → resource-D30rp0GN.js} +38 -16
- package/dist/{respond-DMcbQwfN.js → respond-C44EZAJo.js} +35 -19
- package/dist/{respond-Dne1ARSa.d.ts → respond-DAkPIdTH.d.ts} +15 -9
- package/dist/{result-CTkCZ6JA.d.ts → result-c2OYgCV2.d.ts} +13 -8
- package/dist/server/index.d.ts +35 -10
- package/dist/server/index.js +38 -9
- package/dist/{simplify-bwJpLnaS.js → simplify-LakqK1vH.js} +10 -16
- package/dist/sql/index.d.ts +139 -0
- package/dist/sql/index.js +1983 -0
- package/dist/{sql-DTjA1LPj.js → sql-BIgk12ew.js} +72 -16
- package/dist/ssr/index.d.ts +4 -4
- package/dist/ssr/index.js +2 -2
- package/dist/{standard-CyhRzctb.d.ts → standard-eudBiRk1.d.ts} +1 -1
- package/dist/{stats-C5W-rBfR.js → stats-D78UQ8Yu.js} +8 -7
- package/dist/storage/index.d.ts +4 -3
- package/dist/storage/index.js +530 -1
- package/dist/{tables-rZxRxNPW.js → tables-Dhha3BXM.js} +5 -5
- package/dist/temporal-BwshQk0W.js +27 -0
- package/dist/temporal-required-CMxeSVPo.js +19 -0
- package/dist/testing/index.d.ts +13 -11
- package/dist/testing/index.js +128 -29
- package/dist/{types-C83v5SQz.d.ts → types-DONv9SHN.d.ts} +14 -11
- package/dist/{version-Cqq1m3UY.js → version-BfpG2gks.js} +1 -1
- package/dist/{view-D1s0wa0T.d.ts → view-3Po_PVlr.d.ts} +34 -4
- package/dist/{view-aOYizTeE.js → view-wZ-9qIUz.js} +11 -2
- package/dist/webhooks/index.d.ts +18 -16
- package/dist/webhooks/index.js +1 -1
- package/dist/{webhooks-DMipjhSP.js → webhooks-CAIFQSTM.js} +11 -9
- package/package.json +24 -28
- package/schemas/config-v1.json +31 -1
- package/schemas/doctor-report-v1.json +17 -1
- package/schemas/snapshot-v2.json +35 -0
- package/skills/better-supabase/SKILL.md +23 -11
- package/skills/better-supabase/references/plugins.md +21 -18
- package/skills/better-supabase/references/troubleshooting.md +15 -15
- package/skills/better-supabase-api/SKILL.md +7 -7
- package/skills/better-supabase-api/references/adapters.md +30 -25
- package/skills/better-supabase-testing/SKILL.md +11 -8
- package/bin/better-supabase.js +0 -4
- package/dist/casing-da0uRTqt.js +0 -14
- package/dist/cli/bin.d.ts +0 -1
- package/dist/cli/bin.js +0 -15
- package/dist/cli/index.d.ts +0 -226
- package/dist/cli/index.js +0 -3
- package/dist/cli-DrSgDOQC.js +0 -6646
- package/dist/env-BEToab0I.js +0 -204
- package/dist/events-BuzTgn2D.js +0 -135
- package/dist/pool-R2m_DkXi.d.ts +0 -37
- package/dist/seed-CzecODcJ.js +0 -101
- package/dist/session-C3LxnQ0e.d.ts +0 -22
- package/dist/session-CqEwzVup.js +0 -20
- package/dist/storage-DuGn38fr.js +0 -527
package/dist/testing/index.js
CHANGED
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
import { n as dbError } from "../errors-Bl3rkOzo.js";
|
|
2
|
+
import { i as temporalText } from "../temporal-BwshQk0W.js";
|
|
3
|
+
import { t as temporal } from "../temporal-required-CMxeSVPo.js";
|
|
2
4
|
import { i as ok } from "../result-DWXatkd6.js";
|
|
3
|
-
import { t as BetterSupabase } from "../define-
|
|
5
|
+
import { t as BetterSupabase } from "../define-DF341GDj.js";
|
|
6
|
+
import { a as toBase64Url, r as fromBase64, t as base64ToText } from "../base64-CQeB1eSf.js";
|
|
7
|
+
import { r as resolveConfig } from "../config-Be4GwsqI.js";
|
|
4
8
|
import { t as isList } from "../guards-BnzDvFrz.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { o as toCloudEvents } from "../events-BuzTgn2D.js";
|
|
9
|
-
import { n as isSeed, t as defineSeed } from "../seed-CzecODcJ.js";
|
|
9
|
+
import { ENV_VARIABLES } from "../env/index.js";
|
|
10
|
+
import { n as postgresExecutor } from "../executor-C57jx2pn.js";
|
|
11
|
+
import { toCloudEvents } from "../events/index.js";
|
|
10
12
|
import { createClient } from "@supabase/supabase-js";
|
|
11
13
|
import { existsSync } from "node:fs";
|
|
12
14
|
import { readFile } from "node:fs/promises";
|
|
13
15
|
import { dirname, join, resolve } from "node:path";
|
|
14
16
|
//#region src/testing/jwt.ts
|
|
15
17
|
const encoder = new TextEncoder();
|
|
16
|
-
function base64url(input) {
|
|
17
|
-
const bytes = typeof input === "string" ? encoder.encode(input) : input;
|
|
18
|
-
let binary = "";
|
|
19
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
20
|
-
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
21
|
-
}
|
|
22
18
|
/**
|
|
23
19
|
* Signs an HS256 access token the local Supabase stack accepts. Test-only:
|
|
24
20
|
* production tokens come from Supabase Auth.
|
|
@@ -34,21 +30,17 @@ async function signTestJwt(secret, claims) {
|
|
|
34
30
|
exp: now + expiresIn,
|
|
35
31
|
...rest
|
|
36
32
|
};
|
|
37
|
-
const header =
|
|
33
|
+
const header = toBase64Url(JSON.stringify({
|
|
38
34
|
alg: "HS256",
|
|
39
35
|
typ: "JWT"
|
|
40
36
|
}));
|
|
41
|
-
const body =
|
|
37
|
+
const body = toBase64Url(JSON.stringify(payload));
|
|
42
38
|
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), {
|
|
43
39
|
name: "HMAC",
|
|
44
40
|
hash: "SHA-256"
|
|
45
41
|
}, false, ["sign"]);
|
|
46
42
|
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(`${header}.${body}`));
|
|
47
|
-
return `${header}.${body}.${
|
|
48
|
-
}
|
|
49
|
-
function base64urlDecode(input) {
|
|
50
|
-
const binary = atob(input.replaceAll("-", "+").replaceAll("_", "/"));
|
|
51
|
-
return Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
43
|
+
return `${header}.${body}.${toBase64Url(new Uint8Array(signature))}`;
|
|
52
44
|
}
|
|
53
45
|
/**
|
|
54
46
|
* Accepts `signTestJwt` tokens as users, for API servers tested against the
|
|
@@ -65,9 +57,9 @@ function localAuth(secret) {
|
|
|
65
57
|
async resolve(request) {
|
|
66
58
|
const token = /^Bearer (.+)$/i.exec(request.headers.get("authorization") ?? "")?.[1];
|
|
67
59
|
const [header, body, signature] = token?.split(".") ?? [];
|
|
68
|
-
if (!token || !header || !body || !signature) return
|
|
69
|
-
if (!await crypto.subtle.verify("HMAC", await key,
|
|
70
|
-
const claims = JSON.parse(
|
|
60
|
+
if (!token || !header || !body || !signature) return;
|
|
61
|
+
if (!await crypto.subtle.verify("HMAC", await key, fromBase64(signature), encoder.encode(`${header}.${body}`)).catch(() => false)) return;
|
|
62
|
+
const claims = JSON.parse(base64ToText(body));
|
|
71
63
|
if (typeof claims.exp === "number" && claims.exp * 1e3 < Date.now()) return {
|
|
72
64
|
kind: "invalid",
|
|
73
65
|
error: dbError("unauthorized", "The token has expired")
|
|
@@ -98,17 +90,17 @@ async function signEs256(key, kid, claims) {
|
|
|
98
90
|
exp: now + expiresIn,
|
|
99
91
|
...rest
|
|
100
92
|
};
|
|
101
|
-
const header =
|
|
93
|
+
const header = toBase64Url(JSON.stringify({
|
|
102
94
|
alg: "ES256",
|
|
103
95
|
typ: "JWT",
|
|
104
96
|
kid
|
|
105
97
|
}));
|
|
106
|
-
const body =
|
|
98
|
+
const body = toBase64Url(JSON.stringify(payload));
|
|
107
99
|
const signature = await crypto.subtle.sign({
|
|
108
100
|
name: "ECDSA",
|
|
109
101
|
hash: "SHA-256"
|
|
110
102
|
}, key, encoder.encode(`${header}.${body}`));
|
|
111
|
-
return `${header}.${body}.${
|
|
103
|
+
return `${header}.${body}.${toBase64Url(new Uint8Array(signature))}`;
|
|
112
104
|
}
|
|
113
105
|
/**
|
|
114
106
|
* Signs an ES256 access token with a private JWK, the way the local stack
|
|
@@ -247,7 +239,9 @@ function settle(promise, ms) {
|
|
|
247
239
|
let timer;
|
|
248
240
|
return Promise.race([promise, new Promise((done) => {
|
|
249
241
|
timer = setTimeout(done, ms);
|
|
250
|
-
})]).finally(() =>
|
|
242
|
+
})]).finally(() => {
|
|
243
|
+
clearTimeout(timer);
|
|
244
|
+
});
|
|
251
245
|
}
|
|
252
246
|
/**
|
|
253
247
|
* Measures every document and RSC navigation response during `during` and
|
|
@@ -367,7 +361,9 @@ function tableKey(sb, table) {
|
|
|
367
361
|
if (!key || !(key in sb.meta.tables)) throw new TypeError(`better-supabase: unknown table "${String(key)}"`);
|
|
368
362
|
return key;
|
|
369
363
|
}
|
|
370
|
-
const hasName = (subject) => ["has a name", () =>
|
|
364
|
+
const hasName = (subject) => ["has a name", () => {
|
|
365
|
+
expect(typeof subject.name === "string" && subject.name.length > 0, "name must be a non-empty string");
|
|
366
|
+
}];
|
|
371
367
|
/**
|
|
372
368
|
* Proves an `Executor` meets the contract: rows keyed by the selection's
|
|
373
369
|
* aliases, counts, `aborted` errors, failures returned as `Result`s rather
|
|
@@ -655,12 +651,14 @@ function testPlugin(plugin, options) {
|
|
|
655
651
|
schema: sb.meta,
|
|
656
652
|
context,
|
|
657
653
|
options: {},
|
|
658
|
-
now: () =>
|
|
654
|
+
now: () => temporal().Instant.fromEpochMilliseconds(0)
|
|
659
655
|
});
|
|
660
656
|
const create = options.create;
|
|
661
657
|
return conform(`Plugin "${plugin.name}"`, [
|
|
662
658
|
hasName(plugin),
|
|
663
|
-
["targets plugin API v1", () =>
|
|
659
|
+
["targets plugin API v1", () => {
|
|
660
|
+
expect(plugin.apiVersion === 1, `apiVersion is ${String(plugin.apiVersion)}`);
|
|
661
|
+
}],
|
|
664
662
|
["installs and builds repositories", () => {
|
|
665
663
|
const db = sb.use(plugin).connect(recorder([]), context);
|
|
666
664
|
for (const key of Object.keys(sb.meta.tables)) expect(db[key], `db.${key} is missing`);
|
|
@@ -816,4 +814,105 @@ async function expectTenantIsolation(sb, options) {
|
|
|
816
814
|
}
|
|
817
815
|
}
|
|
818
816
|
//#endregion
|
|
817
|
+
//#region src/testing/seed.ts
|
|
818
|
+
const SEED = Symbol.for("better-supabase.seed");
|
|
819
|
+
function isSeed(value) {
|
|
820
|
+
return typeof value === "object" && value !== null && SEED in value;
|
|
821
|
+
}
|
|
822
|
+
const quote = (text) => `'${text.replaceAll("'", "''")}'`;
|
|
823
|
+
function arrayLiteral(values) {
|
|
824
|
+
return `{${values.map((value) => {
|
|
825
|
+
if (value === null) return "NULL";
|
|
826
|
+
if (Array.isArray(value)) return arrayLiteral(value);
|
|
827
|
+
return `"${(temporalText(value) ?? String(value)).replaceAll(/[\\"]/g, "\\$&")}"`;
|
|
828
|
+
}).join(",")}}`;
|
|
829
|
+
}
|
|
830
|
+
function literal(value, column, where) {
|
|
831
|
+
if (value === null) return "null";
|
|
832
|
+
if (column.json) return quote(JSON.stringify(value));
|
|
833
|
+
if (Array.isArray(value)) return quote(arrayLiteral(value));
|
|
834
|
+
const temporalValue = temporalText(value);
|
|
835
|
+
if (temporalValue !== void 0) return quote(temporalValue);
|
|
836
|
+
const kind = typeof value;
|
|
837
|
+
switch (kind) {
|
|
838
|
+
case "string": return quote(value);
|
|
839
|
+
case "boolean":
|
|
840
|
+
case "bigint": return String(value);
|
|
841
|
+
case "number":
|
|
842
|
+
if (!Number.isFinite(value)) throw new TypeError(`${where}: ${String(value)} is not a finite number`);
|
|
843
|
+
return String(value);
|
|
844
|
+
case "object":
|
|
845
|
+
case "function":
|
|
846
|
+
case "symbol":
|
|
847
|
+
case "undefined": throw new TypeError(`${where}: unsupported value of type ${kind}`);
|
|
848
|
+
default: throw new TypeError(`${where}: unsupported value of type ${String(kind)}`);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
const ident = (name) => `"${name.replaceAll("\"", "\"\"")}"`;
|
|
852
|
+
/** Parents first (by forward relations), otherwise in the order given. */
|
|
853
|
+
function order(meta, keys) {
|
|
854
|
+
const sorted = [];
|
|
855
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
856
|
+
const visit = (key) => {
|
|
857
|
+
if (sorted.includes(key) || visiting.has(key)) return;
|
|
858
|
+
visiting.add(key);
|
|
859
|
+
for (const relation of Object.values(meta.tables[key]?.relations ?? {})) if (relation.direction === "forward" && relation.table !== key && keys.includes(relation.table)) visit(relation.table);
|
|
860
|
+
visiting.delete(key);
|
|
861
|
+
sorted.push(key);
|
|
862
|
+
};
|
|
863
|
+
for (const key of keys) visit(key);
|
|
864
|
+
return sorted;
|
|
865
|
+
}
|
|
866
|
+
function statement(table, rows) {
|
|
867
|
+
const entries = Object.entries(rows);
|
|
868
|
+
if (entries.length === 0) return void 0;
|
|
869
|
+
const columns = [];
|
|
870
|
+
for (const [, row] of entries) for (const [key, value] of Object.entries(row)) {
|
|
871
|
+
if (value === void 0 || columns.includes(key)) continue;
|
|
872
|
+
const column = table.columns[key];
|
|
873
|
+
if (!column) throw new TypeError(`seed ${table.key}: unknown column "${key}"`);
|
|
874
|
+
if (column.generated) throw new TypeError(`seed ${table.key}: "${key}" is a generated column`);
|
|
875
|
+
columns.push(key);
|
|
876
|
+
}
|
|
877
|
+
const values = entries.map(([name, row]) => {
|
|
878
|
+
return ` (${columns.map((key) => row[key] === void 0 ? "default" : literal(row[key], table.columns[key], `seed ${table.key}.${name}.${key}`)).join(", ")})`;
|
|
879
|
+
});
|
|
880
|
+
return `insert into ${`${ident(table.schema)}.${ident(table.name)}`} (${columns.map((key) => ident(table.columns[key].db)).join(", ")}) values\n${values.join(",\n")}\non conflict do nothing;`;
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Typed fixtures shared by `supabase/seeds` and your tests. Rows use app
|
|
884
|
+
* casing and Insert types; `better-supabase seed` renders them to SQL.
|
|
885
|
+
*
|
|
886
|
+
* ```ts
|
|
887
|
+
* export const seed = defineSeed(sb, {
|
|
888
|
+
* organizations: { acme: { id: ACME, name: 'Acme' } },
|
|
889
|
+
* customers: { first: { organizationId: ACME, name: 'First customer' } },
|
|
890
|
+
* });
|
|
891
|
+
* ```
|
|
892
|
+
*/
|
|
893
|
+
function defineSeed(sb, fixtures) {
|
|
894
|
+
const meta = sb.meta;
|
|
895
|
+
const statements = () => {
|
|
896
|
+
const keys = Object.keys(fixtures).filter((key) => fixtures[key] !== void 0);
|
|
897
|
+
for (const key of keys) {
|
|
898
|
+
const table = meta.tables[key];
|
|
899
|
+
if (!table) throw new TypeError(`seed: unknown table "${key}"`);
|
|
900
|
+
if (table.kind === "view") throw new TypeError(`seed: "${key}" is a view`);
|
|
901
|
+
}
|
|
902
|
+
return order(meta, keys).flatMap((key) => {
|
|
903
|
+
const sql = statement(meta.tables[key], fixtures[key]);
|
|
904
|
+
return sql ? [sql] : [];
|
|
905
|
+
});
|
|
906
|
+
};
|
|
907
|
+
return {
|
|
908
|
+
[SEED]: true,
|
|
909
|
+
rows: fixtures,
|
|
910
|
+
statements,
|
|
911
|
+
sql: () => statements().join("\n\n"),
|
|
912
|
+
async insert(sql) {
|
|
913
|
+
for (const text of statements()) await sql.queryRaw(text);
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
//#endregion
|
|
819
918
|
export { ConformanceError, LOCAL_JWT_SECRET, asUser, createTestSigner, defineSeed, expectDbBudget, expectTenantIsolation, isSeed, localAuth, localSigningKey, signLocalJwt, signTestJwt, signTestJwtWithKey, testAuthResolver, testCacheAdapter, testEventSink, testExecutor, testGenerator, testPlugin };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference lib="esnext.temporal" />
|
|
1
2
|
//#region src/schema/types.d.ts
|
|
2
3
|
type Casing = "snake" | "camel";
|
|
3
4
|
/** Runtime metadata for one column. Keyed by the app (cased) column name. */
|
|
@@ -27,10 +28,12 @@ interface ColumnMeta {
|
|
|
27
28
|
readonly storage?: string;
|
|
28
29
|
}
|
|
29
30
|
/**
|
|
30
|
-
* Runtime representation for a column: `
|
|
31
|
-
* `
|
|
31
|
+
* Runtime representation for a column: `instant` decodes `timestamptz` to
|
|
32
|
+
* `Temporal.Instant`, `plainDateTime` decodes `timestamp` to
|
|
33
|
+
* `Temporal.PlainDateTime`, and `bigint` and `string` read the exact text of
|
|
34
|
+
* `int8`/`numeric` values.
|
|
32
35
|
*/
|
|
33
|
-
type Codec = "
|
|
36
|
+
type Codec = "instant" | "plainDateTime" | "bigint" | "string";
|
|
34
37
|
interface RelationMeta {
|
|
35
38
|
/** App key of the target table. */
|
|
36
39
|
readonly table: string;
|
|
@@ -165,19 +168,19 @@ interface BucketMeta {
|
|
|
165
168
|
readonly fileSizeLimit?: string;
|
|
166
169
|
readonly allowedMimeTypes?: readonly string[];
|
|
167
170
|
}
|
|
168
|
-
interface
|
|
171
|
+
interface RelationTypes {
|
|
169
172
|
readonly table: string;
|
|
170
173
|
readonly kind: "one" | "many";
|
|
171
174
|
readonly nullable: boolean;
|
|
172
175
|
}
|
|
173
|
-
interface
|
|
176
|
+
interface ModelFlags {
|
|
174
177
|
readonly softDelete?: string;
|
|
175
178
|
readonly timestamps?: true;
|
|
176
179
|
readonly tenant?: string;
|
|
177
180
|
readonly actor?: true;
|
|
178
181
|
readonly version?: string;
|
|
179
182
|
}
|
|
180
|
-
interface
|
|
183
|
+
interface ModelTypes {
|
|
181
184
|
readonly Row: object;
|
|
182
185
|
readonly Insert: object;
|
|
183
186
|
readonly Update: object;
|
|
@@ -188,17 +191,17 @@ interface ModelShape {
|
|
|
188
191
|
readonly Checks?: string;
|
|
189
192
|
/** Foreign key constraint names on this table. */
|
|
190
193
|
readonly ForeignKeys?: string;
|
|
191
|
-
readonly Flags:
|
|
194
|
+
readonly Flags: ModelFlags;
|
|
192
195
|
}
|
|
193
196
|
type AnyModels = {
|
|
194
|
-
readonly [table: string]:
|
|
197
|
+
readonly [table: string]: ModelTypes;
|
|
195
198
|
};
|
|
196
|
-
type
|
|
199
|
+
type FunctionTypes = {
|
|
197
200
|
readonly Args: object;
|
|
198
201
|
readonly Returns: unknown;
|
|
199
202
|
};
|
|
200
203
|
type AnyFunctions = {
|
|
201
|
-
readonly [name: string]:
|
|
204
|
+
readonly [name: string]: FunctionTypes;
|
|
202
205
|
};
|
|
203
206
|
/**
|
|
204
207
|
* Carries the model types next to the runtime metadata. Created by
|
|
@@ -245,4 +248,4 @@ type IsUnion<T, U = T> = T extends unknown ? [U] extends [T] ? false : true : ne
|
|
|
245
248
|
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>];
|
|
246
249
|
type Simplify<T> = { [K in keyof T]: T[K]; } & {};
|
|
247
250
|
//#endregion
|
|
248
|
-
export { SchemaMeta as A, PrimaryKeyValue as C, Relations as D,
|
|
251
|
+
export { SchemaMeta as A, PrimaryKeyValue as C, Relations as D, RelationTypes as E, UniqueConstraint as F, UniqueKeyName as I, UniqueWhere as L, TableFlags as M, TableKey as N, Row as O, TableMeta as P, Update as R, PrimaryKeyColumn as S, RelationMeta as T, ModelFlags as _, Casing as a, PermdockBucketPolicy as b, Codec as c, Flags as d, ForeignKeyConstraint as f, Insert as g, FunctionsOf as h, BucketPolicyName as i, Simplify as j, Schema as k, ColumnMeta as l, FunctionTypes as m, AnyModels as n, CheckConstraint as o, FunctionMeta as p, BucketMeta as r, ClaimsMeta as s, AnyFunctions as t, DatabaseOf as u, ModelTypes as v, RealtimeTableMeta as w, PermdockTopicPolicy as x, ModelsOf as y };
|
|
@@ -1,7 +1,30 @@
|
|
|
1
|
-
import { p as DbError } from "./result-
|
|
2
|
-
import { n as AmrEntry, s as Impersonator, t as Aal } from "./mfa-
|
|
3
|
-
import { i as InvalidReason, r as AuthState } from "./resolve-
|
|
1
|
+
import { p as DbError } from "./result-c2OYgCV2.js";
|
|
2
|
+
import { n as AmrEntry, s as Impersonator, t as Aal } from "./mfa-x3V3vGh-.js";
|
|
3
|
+
import { i as InvalidReason, r as AuthState } from "./resolve-BRciX2jK.js";
|
|
4
4
|
import { JWTClaims, UserClaims } from "@supabase/server";
|
|
5
|
+
//#region src/auth/actor.d.ts
|
|
6
|
+
/** One level of an RFC 8693 `act` chain: who acts, and who acted before it. */
|
|
7
|
+
interface ActClaim {
|
|
8
|
+
readonly sub: string;
|
|
9
|
+
readonly act?: ActClaim;
|
|
10
|
+
readonly [claim: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The app acting for the user: the outermost `sub` of an RFC 8693 `act`
|
|
14
|
+
* chain (prior actors stay on `chain`), else the OAuth `client_id` of a
|
|
15
|
+
* Supabase OAuth server token.
|
|
16
|
+
*/
|
|
17
|
+
interface SessionActor {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly kind: "oauth-client";
|
|
20
|
+
readonly chain?: ActClaim;
|
|
21
|
+
}
|
|
22
|
+
/** What the user delegated to the actor: the token's OAuth `scope`. */
|
|
23
|
+
interface SessionDelegation {
|
|
24
|
+
readonly scopes: readonly string[];
|
|
25
|
+
readonly chain?: ActClaim;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
5
28
|
//#region src/auth/view.d.ts
|
|
6
29
|
/**
|
|
7
30
|
* The verified caller as plain data: no token, no clients. Safe to return
|
|
@@ -29,6 +52,13 @@ type AuthSession<C = unknown, P = unknown> = {
|
|
|
29
52
|
readonly amr: readonly AmrEntry[];
|
|
30
53
|
/** Set when an admin acts as this user (the `act` claim), for a banner. */
|
|
31
54
|
readonly impersonator?: Impersonator;
|
|
55
|
+
/**
|
|
56
|
+
* The OAuth client or agent acting for the user (`client_id`, or the
|
|
57
|
+
* outermost `sub` of the `act` chain), as PermDock's `actorOf` reads it.
|
|
58
|
+
*/
|
|
59
|
+
readonly actor?: SessionActor;
|
|
60
|
+
/** The scopes the user granted `actor`, plus its `act` chain. Set only with `actor`. */
|
|
61
|
+
readonly delegation?: SessionDelegation;
|
|
32
62
|
} | {
|
|
33
63
|
readonly kind: "service";
|
|
34
64
|
readonly keyName: string;
|
|
@@ -46,4 +76,4 @@ type AnonReason = Extract<AuthState, {
|
|
|
46
76
|
/** Drops the token from an `AuthState`, leaving only serializable fields. */
|
|
47
77
|
declare function toSession<C, P>(auth: AuthState<C, P>): AuthSession<C, P>;
|
|
48
78
|
//#endregion
|
|
49
|
-
export { toSession as n, AuthSession as t };
|
|
79
|
+
export { SessionDelegation as a, SessionActor as i, toSession as n, ActClaim as r, AuthSession as t };
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import { g as
|
|
1
|
+
import { g as delegationOf, h as actorOf, n as amrOf, t as aalOf, v as impersonatorOf } from "./mfa-BxoR1RkN.js";
|
|
2
2
|
//#region src/auth/view.ts
|
|
3
3
|
/** Drops the token from an `AuthState`, leaving only serializable fields. */
|
|
4
4
|
function toSession(auth) {
|
|
5
5
|
switch (auth.kind) {
|
|
6
6
|
case "user": {
|
|
7
7
|
const impersonator = impersonatorOf(auth.claims);
|
|
8
|
+
const outcome = actorOf(auth.claims);
|
|
9
|
+
const actor = outcome.ok ? outcome.actor : void 0;
|
|
10
|
+
const scopes = actor ? delegationOf(auth.claims)?.scopes : void 0;
|
|
11
|
+
const delegation = actor && (scopes || actor.chain) ? {
|
|
12
|
+
scopes: scopes ?? [],
|
|
13
|
+
...actor.chain ? { chain: actor.chain } : {}
|
|
14
|
+
} : void 0;
|
|
8
15
|
return {
|
|
9
16
|
kind: "user",
|
|
10
17
|
user: auth.user,
|
|
@@ -13,7 +20,9 @@ function toSession(auth) {
|
|
|
13
20
|
...auth.profile === void 0 ? {} : { profile: auth.profile },
|
|
14
21
|
aal: aalOf(auth.claims),
|
|
15
22
|
amr: amrOf(auth.claims),
|
|
16
|
-
...impersonator ? { impersonator } : {}
|
|
23
|
+
...impersonator ? { impersonator } : {},
|
|
24
|
+
...actor ? { actor } : {},
|
|
25
|
+
...delegation ? { delegation } : {}
|
|
17
26
|
};
|
|
18
27
|
}
|
|
19
28
|
case "service": return {
|
package/dist/webhooks/index.d.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/// <reference lib="esnext.temporal" />
|
|
2
|
+
import { N as TableKey, O as Row, n as AnyModels, t as AnyFunctions } from "../types-DONv9SHN.js";
|
|
3
|
+
import { o as Result } from "../result-c2OYgCV2.js";
|
|
4
|
+
import { t as BetterSupabase } from "../define-DUbydmJa.js";
|
|
5
|
+
//#region src/webhooks/verify.d.ts
|
|
6
|
+
interface VerifiedWebhook<T = unknown> {
|
|
6
7
|
readonly id: string;
|
|
7
|
-
readonly timestamp:
|
|
8
|
+
readonly timestamp: Temporal.Instant;
|
|
8
9
|
readonly payload: T;
|
|
9
10
|
readonly body: string;
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
+
interface VerifyOptions {
|
|
12
13
|
/** Allowed clock skew in seconds. Defaults to 300. */
|
|
13
14
|
readonly tolerance?: number;
|
|
14
|
-
readonly now?: () =>
|
|
15
|
+
readonly now?: () => Temporal.Instant;
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
+
type WebhookInput = Request | {
|
|
17
18
|
readonly headers: Headers | Readonly<Record<string, string>>;
|
|
18
19
|
readonly body: string;
|
|
19
20
|
};
|
|
@@ -23,7 +24,7 @@ export declare function timingSafeEqual(a: string, b: string): boolean;
|
|
|
23
24
|
export declare function signWebhook(secret: string, message: {
|
|
24
25
|
readonly id: string;
|
|
25
26
|
readonly body: string;
|
|
26
|
-
readonly timestamp?:
|
|
27
|
+
readonly timestamp?: Temporal.Instant;
|
|
27
28
|
}): Promise<Record<string, string>>;
|
|
28
29
|
/**
|
|
29
30
|
* Verifies a Standard Webhooks signature (`webhook-id`, `webhook-timestamp`,
|
|
@@ -41,13 +42,13 @@ interface HookUser {
|
|
|
41
42
|
readonly user_metadata?: Readonly<Record<string, unknown>>;
|
|
42
43
|
readonly [key: string]: unknown;
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
+
interface AuthHookError {
|
|
45
46
|
readonly error: {
|
|
46
47
|
readonly http_code: number;
|
|
47
48
|
readonly message: string;
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
+
interface AuthHooks {
|
|
51
52
|
custom_access_token: {
|
|
52
53
|
input: {
|
|
53
54
|
readonly user_id: string;
|
|
@@ -116,7 +117,7 @@ export interface AuthHooks {
|
|
|
116
117
|
output: Record<never, never>;
|
|
117
118
|
};
|
|
118
119
|
}
|
|
119
|
-
|
|
120
|
+
type AuthHookKind = keyof AuthHooks;
|
|
120
121
|
/** Rejects a hook with a status and message Supabase Auth passes to the client. */
|
|
121
122
|
export declare function hookError(status: number, message: string): AuthHookError;
|
|
122
123
|
/**
|
|
@@ -130,14 +131,14 @@ export declare function hookError(status: number, message: string): AuthHookErro
|
|
|
130
131
|
* ```
|
|
131
132
|
*/
|
|
132
133
|
export declare function authHook<K extends AuthHookKind>(_kind: K, secret: string | readonly string[], handler: (payload: AuthHooks[K]["input"], webhook: VerifiedWebhook<AuthHooks[K]["input"]>) => AuthHooks[K]["output"] | AuthHookError | Promise<AuthHooks[K]["output"] | AuthHookError>, options?: VerifyOptions): (request: Request) => Promise<Response>;
|
|
133
|
-
|
|
134
|
+
interface DatabaseWebhookPayload {
|
|
134
135
|
readonly type: "INSERT" | "UPDATE" | "DELETE";
|
|
135
136
|
readonly table: string;
|
|
136
137
|
readonly schema: string;
|
|
137
138
|
readonly record: Readonly<Record<string, unknown>> | null;
|
|
138
139
|
readonly old_record: Readonly<Record<string, unknown>> | null;
|
|
139
140
|
}
|
|
140
|
-
|
|
141
|
+
interface DatabaseChange<R> {
|
|
141
142
|
readonly type: "INSERT" | "UPDATE" | "DELETE";
|
|
142
143
|
readonly table: string;
|
|
143
144
|
readonly record: R | null;
|
|
@@ -149,4 +150,5 @@ export declare function isDatabaseWebhook(value: unknown): value is DatabaseWebh
|
|
|
149
150
|
* Returns `null` for other tables or payloads that aren't database webhooks.
|
|
150
151
|
*/
|
|
151
152
|
export declare function databaseChange<M extends AnyModels, D, F extends AnyFunctions, E, T extends TableKey<M>>(sb: BetterSupabase<M, D, F, E>, table: T, payload: unknown): DatabaseChange<Row<M, T>> | null;
|
|
152
|
-
//#endregion
|
|
153
|
+
//#endregion
|
|
154
|
+
export type { AuthHookError, AuthHookKind, AuthHooks, DatabaseChange, DatabaseWebhookPayload, VerifiedWebhook, VerifyOptions, WebhookInput };
|
package/dist/webhooks/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as signWebhook, c as verifyWebhook, i as isDatabaseWebhook, n as databaseChange, o as timingSafeEqual, r as hookError, s as verifySharedSecret, t as authHook } from "../webhooks-
|
|
1
|
+
import { a as signWebhook, c as verifyWebhook, i as isDatabaseWebhook, n as databaseChange, o as timingSafeEqual, r as hookError, s as verifySharedSecret, t as authHook } from "../webhooks-CAIFQSTM.js";
|
|
2
2
|
export { authHook, databaseChange, hookError, isDatabaseWebhook, signWebhook, timingSafeEqual, verifySharedSecret, verifyWebhook };
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { n as dbError } from "./errors-Bl3rkOzo.js";
|
|
2
|
+
import { n as nowInstant, r as optionalTemporal } from "./temporal-BwshQk0W.js";
|
|
3
|
+
import { n as temporalMissing } from "./temporal-required-CMxeSVPo.js";
|
|
2
4
|
import { i as ok, n as err } from "./result-DWXatkd6.js";
|
|
5
|
+
import { i as toBase64, r as fromBase64 } from "./base64-CQeB1eSf.js";
|
|
3
6
|
import { s as toApp } from "./shared-_6-uPL7E.js";
|
|
4
|
-
//#region src/webhooks/
|
|
7
|
+
//#region src/webhooks/verify.ts
|
|
5
8
|
const encoder = new TextEncoder();
|
|
6
9
|
/** Accepts `whsec_…`, `v1,whsec_…` (Supabase Auth hooks) or raw base64. */
|
|
7
10
|
function secretBytes(secret) {
|
|
8
11
|
const raw = secret.replace(/^v1,/, "").replace(/^whsec_/, "");
|
|
9
12
|
try {
|
|
10
|
-
return
|
|
13
|
+
return fromBase64(raw);
|
|
11
14
|
} catch {
|
|
12
15
|
throw new TypeError("Webhook secret is not valid base64");
|
|
13
16
|
}
|
|
@@ -17,10 +20,7 @@ async function signature(secret, content) {
|
|
|
17
20
|
name: "HMAC",
|
|
18
21
|
hash: "SHA-256"
|
|
19
22
|
}, false, ["sign"]);
|
|
20
|
-
|
|
21
|
-
let binary = "";
|
|
22
|
-
for (const byte of mac) binary += String.fromCharCode(byte);
|
|
23
|
-
return btoa(binary);
|
|
23
|
+
return toBase64(new Uint8Array(await crypto.subtle.sign("HMAC", key, encoder.encode(content))));
|
|
24
24
|
}
|
|
25
25
|
/** Constant-time string comparison. */
|
|
26
26
|
function timingSafeEqual(a, b) {
|
|
@@ -37,7 +37,7 @@ function header(headers, name) {
|
|
|
37
37
|
const unauthorized = (code, message) => dbError("unauthorized", message, { code });
|
|
38
38
|
/** Headers for a signed Standard Webhooks request. */
|
|
39
39
|
async function signWebhook(secret, message) {
|
|
40
|
-
const timestamp = String(Math.floor((message.timestamp ??
|
|
40
|
+
const timestamp = String(Math.floor((message.timestamp ?? nowInstant()).epochMilliseconds / 1e3));
|
|
41
41
|
return {
|
|
42
42
|
"webhook-id": message.id,
|
|
43
43
|
"webhook-timestamp": timestamp,
|
|
@@ -58,7 +58,9 @@ async function verifyWebhook(input, secrets, options = {}) {
|
|
|
58
58
|
if (!id || !timestamp || !signatures) return err(unauthorized("WEBHOOK_MISSING_HEADERS", "Missing webhook signature headers"));
|
|
59
59
|
const seconds = Number(timestamp);
|
|
60
60
|
if (!/^\d+$/.test(timestamp)) return err(unauthorized("WEBHOOK_INVALID_TIMESTAMP", "Invalid webhook timestamp"));
|
|
61
|
-
const
|
|
61
|
+
const namespace = optionalTemporal();
|
|
62
|
+
if (namespace === void 0) return err(temporalMissing());
|
|
63
|
+
const now = (options.now?.() ?? namespace.Now.instant()).epochMilliseconds / 1e3;
|
|
62
64
|
const tolerance = options.tolerance ?? 300;
|
|
63
65
|
if (seconds < now - tolerance) return err(unauthorized("WEBHOOK_TIMESTAMP_TOO_OLD", "Webhook timestamp is too old"));
|
|
64
66
|
if (seconds > now + tolerance) return err(unauthorized("WEBHOOK_TIMESTAMP_TOO_NEW", "Webhook timestamp is in the future"));
|
|
@@ -73,7 +75,7 @@ async function verifyWebhook(input, secrets, options = {}) {
|
|
|
73
75
|
try {
|
|
74
76
|
return ok({
|
|
75
77
|
id,
|
|
76
|
-
timestamp:
|
|
78
|
+
timestamp: namespace.Instant.fromEpochMilliseconds(seconds * 1e3),
|
|
77
79
|
payload: JSON.parse(body),
|
|
78
80
|
body
|
|
79
81
|
});
|