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.
- package/README.md +34 -31
- package/dist/base64-CQeB1eSf.js +42 -0
- package/dist/claims-CoTobNC-.js +25 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +4 -14
- package/dist/{compiler-DL18ukPe.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-BD6wKthp.d.ts → config-C1CYcm6o.d.ts} +75 -54
- package/dist/{config-D-aR2ZoN.js → config-DOsGYSiT.js} +14 -136
- package/dist/{define-QGkq04oe.js → define-DF341GDj.js} +133 -110
- package/dist/{define-CTfWw-SS.d.ts → define-DUbydmJa.d.ts} +32 -14
- package/dist/edge/index.d.ts +4 -4
- package/dist/edge/index.js +4 -4
- package/dist/entitlements-CTEBm7IB.js +20 -0
- package/dist/entitlements-DvJXoI02.d.ts +42 -0
- 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-2OJsVG_d.d.ts → events-Do8jXh4D.d.ts} +3 -3
- package/dist/{executor-Bje4suQF.d.ts → executor-B26lgbHD.d.ts} +2 -2
- package/dist/{executor-Ceq60hPM.js → executor-C57jx2pn.js} +7 -2
- package/dist/{executor-Bazhgsgq.d.ts → executor-DynmIWhq.d.ts} +2 -2
- package/dist/guards-BnzDvFrz.js +10 -0
- package/dist/hono/index.d.ts +5 -5
- package/dist/hono/index.js +4 -4
- package/dist/{index-Hf8PP9sP.d.ts → index-BNsABq7P.d.ts} +17 -13
- package/dist/{index-JXHBuS53.d.ts → index-CADlNHK-.d.ts} +27 -14
- package/dist/{index-Nxo3TJcm.d.ts → index-CTTMS6_1.d.ts} +27 -8
- package/dist/index-Cf-D2jk1.d.ts +50 -0
- package/dist/{index-Dup-nSe9.d.ts → index-CriFFJ_p.d.ts} +4 -4
- package/dist/{index-DQVvHzjW.d.ts → index-GV77NpRP.d.ts} +5 -4
- package/dist/{index-Sw43J8HG.d.ts → index-UJ-LUxtI.d.ts} +6 -6
- package/dist/{index-cEeweTeM.d.ts → index-kpgopLNv.d.ts} +4 -3
- package/dist/{index-DRid0qIv.d.ts → index-qriRM9cZ.d.ts} +7 -2
- package/dist/index.d.ts +13 -13
- package/dist/index.js +18 -8
- package/dist/jobs/index.d.ts +25 -24
- package/dist/jobs/index.js +19 -12
- package/dist/{json-schema-BbwO2mZg.js → json-schema--aOXAGfU.js} +9 -3
- 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-6b9i72Ux.d.ts → live-QE3UPbGm.d.ts} +27 -6
- package/dist/logger-glM28NVN.js +21 -0
- package/dist/mcp/index.d.ts +59 -15
- package/dist/mcp/index.js +229 -67
- package/dist/{mfa-CpogP66z.js → mfa-BxoR1RkN.js} +97 -22
- package/dist/{mfa-TVia554c.d.ts → mfa-x3V3vGh-.d.ts} +1 -1
- package/dist/next/index.d.ts +51 -38
- package/dist/next/index.js +31 -25
- package/dist/openapi/index.d.ts +1 -1
- package/dist/openapi/index.js +58 -35
- package/dist/orpc/index.d.ts +6 -6
- package/dist/orpc/index.js +3 -3
- package/dist/otel/index.d.ts +3 -3
- package/dist/otel/index.js +5 -3
- package/dist/{template-BeOGnynM.js → permdock-sql-Bta_G0v-.js} +32 -3
- package/dist/permdock-sql-DImJULY-.d.ts +17 -0
- package/dist/{plugin-HvGqCurB.d.ts → plugin-DVRB8qNE.d.ts} +5 -4
- package/dist/plugins/actor/index.d.ts +1 -1
- package/dist/plugins/rules/index.d.ts +4 -3
- package/dist/plugins/rules/index.js +8 -15
- 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 +10 -4
- package/dist/plugins/tenant/index.js +15 -13
- 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-Dm-vhY6R.d.ts → postgrest-DmBlevh-.d.ts} +1 -1
- package/dist/{problem-DPu6Fh-t.js → problem-CGt1ADix.js} +14 -8
- 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 +3 -3
- package/dist/react/server.d.ts +5 -5
- 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-CrOvrnIP.js → react-DNBYNazu.js} +28 -23
- package/dist/{read-set-DhX2d04c.js → read-set-D0qBcboM.js} +3 -3
- package/dist/realtime/index.d.ts +4 -2
- package/dist/realtime/index.js +30 -14
- package/dist/{resolve-DW8h9NPm.d.ts → resolve-BRciX2jK.d.ts} +25 -9
- package/dist/{resource-SkPTRJDD.d.ts → resource-CwVgiI5q.d.ts} +16 -7
- package/dist/{resource-CYiu6Ygi.js → resource-D30rp0GN.js} +38 -16
- package/dist/{respond-4-dhoV7x.js → respond-C44EZAJo.js} +38 -19
- package/dist/{respond-PoTpy5DT.d.ts → respond-DAkPIdTH.d.ts} +23 -17
- 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-oHiDKRVQ.js → simplify-LakqK1vH.js} +22 -28
- package/dist/{spec-pins-t6sPaHvl.js → spec-pins-C3oc-Tdp.js} +1 -1
- package/dist/{spec-pins-CJPVghHy.d.ts → spec-pins-CqorWDnO.d.ts} +1 -1
- package/dist/sql/index.d.ts +139 -0
- package/dist/sql/index.js +1983 -0
- package/dist/{sql-M8l3fnS0.js → sql-BIgk12ew.js} +72 -16
- package/dist/ssr/index.d.ts +5 -5
- package/dist/ssr/index.js +3 -3
- package/dist/{standard-CyhRzctb.d.ts → standard-eudBiRk1.d.ts} +1 -1
- package/dist/{stats-3iy8u3_P.js → stats-D78UQ8Yu.js} +8 -7
- package/dist/storage/index.d.ts +4 -2
- 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 +49 -16
- package/dist/testing/index.js +233 -66
- package/dist/{types-C2fslP1z.d.ts → types-DONv9SHN.d.ts} +68 -12
- package/dist/{version-Cqq1m3UY.js → version-BfpG2gks.js} +1 -1
- package/dist/view-3Po_PVlr.d.ts +79 -0
- package/dist/{view-B6F9QbG-.js → view-wZ-9qIUz.js} +12 -2
- package/dist/webhooks/index.d.ts +19 -17
- package/dist/webhooks/index.js +1 -1
- package/dist/{webhooks-DbMTJJX2.js → webhooks-CAIFQSTM.js} +12 -10
- package/package.json +30 -30
- package/schemas/config-v1.json +80 -5
- package/schemas/doctor-report-v1.json +18 -1
- package/schemas/snapshot-v2.json +39 -0
- package/skills/better-supabase/SKILL.md +23 -11
- package/skills/better-supabase/references/plugins.md +28 -16
- package/skills/better-supabase/references/troubleshooting.md +15 -15
- package/skills/better-supabase-api/SKILL.md +9 -9
- package/skills/better-supabase-api/references/adapters.md +30 -25
- package/skills/better-supabase-testing/SKILL.md +15 -11
- package/bin/better-supabase.js +0 -3
- 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-CoByWYja.js +0 -6472
- package/dist/entitlements-BBg61DQZ.d.ts +0 -20
- package/dist/entitlements-BC_khwRy.js +0 -17
- package/dist/env-BEToab0I.js +0 -204
- package/dist/events-BuzTgn2D.js +0 -135
- package/dist/pool-BFeqzwQS.d.ts +0 -37
- package/dist/seed-CzecODcJ.js +0 -101
- package/dist/session-C3osL21k.d.ts +0 -21
- package/dist/session-CD-3orO_.js +0 -19
- package/dist/storage-BWXVUXGk.js +0 -461
- package/dist/view-B0ltUmTi.d.ts +0 -43
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { t as AuthSession } from "./view-3Po_PVlr.js";
|
|
2
|
+
//#region src/auth/entitlements.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* One entry of the `memberships` claim, in PermDock's shape. PermDock strips
|
|
5
|
+
* `null`s, so optional fields are absent rather than `null`.
|
|
6
|
+
*/
|
|
7
|
+
interface MembershipClaim {
|
|
8
|
+
/** The scope the membership is in, e.g. `tenant` or `organization`. */
|
|
9
|
+
readonly scope: string;
|
|
10
|
+
readonly id: string;
|
|
11
|
+
/** Parent scopes, e.g. `{ organization: '...' }` for a project membership. */
|
|
12
|
+
readonly within?: Readonly<Record<string, string>>;
|
|
13
|
+
readonly roles: readonly string[];
|
|
14
|
+
readonly via?: string;
|
|
15
|
+
/** Unix seconds. */
|
|
16
|
+
readonly expiresAt?: number;
|
|
17
|
+
readonly grantedBy?: string;
|
|
18
|
+
readonly reason?: string;
|
|
19
|
+
readonly member?: {
|
|
20
|
+
readonly group: string;
|
|
21
|
+
};
|
|
22
|
+
readonly managedBy?: "idp";
|
|
23
|
+
/** Seats, not plan features: those are in the `features` claim. */
|
|
24
|
+
readonly entitlements?: readonly string[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Entitlement keys of the `features` claim: the literal union when
|
|
28
|
+
* `sb.claims(schema)` declares one (`z.enum([...])`), otherwise `string`.
|
|
29
|
+
*/
|
|
30
|
+
type EntitlementKey<C> = C extends {
|
|
31
|
+
readonly features?: infer F;
|
|
32
|
+
} ? NonNullable<F> extends Readonly<Record<string, readonly (infer K)[]>> ? K extends string ? K : string : string : string;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the session's plan-features claim grants `key` in `tenantId`.
|
|
35
|
+
* `claim` is `config.claims.features` (default `features`), the claim
|
|
36
|
+
* `better_supabase.feature_claims()` fills. The claim is as fresh as the
|
|
37
|
+
* token: enforce it in RLS with `better_supabase.has_entitlement()`, and
|
|
38
|
+
* refresh the session after checkout.
|
|
39
|
+
*/
|
|
40
|
+
declare function hasEntitlement<C>(session: AuthSession<C>, tenantId: string, key: EntitlementKey<C>, claim?: string): boolean;
|
|
41
|
+
//#endregion
|
|
42
|
+
export { MembershipClaim as n, hasEntitlement as r, EntitlementKey as t };
|
package/dist/env/index.js
CHANGED
|
@@ -1,2 +1,204 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/env/index.ts
|
|
2
|
+
const PREFIXES = [
|
|
3
|
+
"",
|
|
4
|
+
"NEXT_PUBLIC_",
|
|
5
|
+
"VITE_",
|
|
6
|
+
"PUBLIC_",
|
|
7
|
+
"EXPO_PUBLIC_",
|
|
8
|
+
"NUXT_PUBLIC_"
|
|
9
|
+
];
|
|
10
|
+
const ENV_VARIABLES = {
|
|
11
|
+
url: PREFIXES.map((prefix) => `${prefix}SUPABASE_URL`),
|
|
12
|
+
publishableKey: PREFIXES.flatMap((prefix) => [`${prefix}SUPABASE_PUBLISHABLE_KEY`, `${prefix}SUPABASE_PUBLISHABLE_DEFAULT_KEY`]),
|
|
13
|
+
secretKey: ["SUPABASE_SECRET_KEY"],
|
|
14
|
+
dbUrl: ["SUPABASE_DB_URL", "DATABASE_URL"],
|
|
15
|
+
jwksUrl: ["SUPABASE_JWKS_URL"],
|
|
16
|
+
readUrl: ["SUPABASE_READ_URL"]
|
|
17
|
+
};
|
|
18
|
+
const LOOPBACK = /^(localhost|127(?:\.\d{1,3}){3}|\[::1\])$/;
|
|
19
|
+
const HOSTED = /^([a-z0-9]{20})\.supabase\.co$/;
|
|
20
|
+
var EnvValidationError = class extends Error {
|
|
21
|
+
name = "EnvValidationError";
|
|
22
|
+
issues;
|
|
23
|
+
constructor(issues) {
|
|
24
|
+
super(`Invalid Supabase environment:\n${issues.map((issue) => ` - ${issue.variables[0] ?? "env"}: ${issue.message}`).join("\n")}`);
|
|
25
|
+
this.issues = issues;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function first(source, names) {
|
|
29
|
+
for (const name of names) {
|
|
30
|
+
const value = source[name]?.trim();
|
|
31
|
+
if (value) return value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** `SUPABASE_*_KEYS` JSON objects, as `@supabase/server` reads them. */
|
|
35
|
+
function keySet(source, name, kind, issues) {
|
|
36
|
+
const raw = source[name];
|
|
37
|
+
if (!raw) return void 0;
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = JSON.parse(raw);
|
|
41
|
+
} catch {
|
|
42
|
+
parsed = void 0;
|
|
43
|
+
}
|
|
44
|
+
const entries = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? Object.entries(parsed) : [];
|
|
45
|
+
if (entries.length === 0 || entries.some(([, value]) => typeof value !== "string")) {
|
|
46
|
+
issues.push({
|
|
47
|
+
variables: [name],
|
|
48
|
+
message: "must be a JSON object of key names to keys"
|
|
49
|
+
});
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const keys = {};
|
|
53
|
+
for (const [key, value] of entries) if (checkKey(value, kind, [name], issues)) keys[key] = value;
|
|
54
|
+
return keys;
|
|
55
|
+
}
|
|
56
|
+
function preferred(keys) {
|
|
57
|
+
if (!keys) return void 0;
|
|
58
|
+
return keys["default"] ?? Object.values(keys)[0];
|
|
59
|
+
}
|
|
60
|
+
function checkUrl(value, variables, issues) {
|
|
61
|
+
let url;
|
|
62
|
+
try {
|
|
63
|
+
url = new URL(value);
|
|
64
|
+
} catch {
|
|
65
|
+
issues.push({
|
|
66
|
+
variables,
|
|
67
|
+
message: "is not a valid URL"
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (url.protocol === "https:") return url;
|
|
72
|
+
if (url.protocol === "http:" && LOOPBACK.test(url.hostname)) return url;
|
|
73
|
+
issues.push({
|
|
74
|
+
variables,
|
|
75
|
+
message: "must use https (http is only allowed for localhost / 127.0.0.1 / [::1])"
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function checkKey(value, kind, variables, issues) {
|
|
79
|
+
if (value.startsWith(`sb_${kind}_`)) return true;
|
|
80
|
+
if (value.startsWith("eyJ")) issues.push({
|
|
81
|
+
variables,
|
|
82
|
+
message: `is a legacy JWT key; use the new sb_${kind}_ key (Project Settings > API Keys)`
|
|
83
|
+
});
|
|
84
|
+
else if (value.startsWith(kind === "publishable" ? "sb_secret_" : "sb_publishable_")) issues.push({
|
|
85
|
+
variables,
|
|
86
|
+
message: kind === "publishable" ? "holds a secret key; never expose sb_secret_ keys as publishable" : "holds a publishable key, not a secret key"
|
|
87
|
+
});
|
|
88
|
+
else issues.push({
|
|
89
|
+
variables,
|
|
90
|
+
message: `must start with sb_${kind}_`
|
|
91
|
+
});
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Reads and validates Supabase settings. Accepts the `SUPABASE_`,
|
|
96
|
+
* `NEXT_PUBLIC_`, `VITE_`, `PUBLIC_`, `EXPO_PUBLIC_` and `NUXT_PUBLIC_`
|
|
97
|
+
* spellings. Issues name variables, never values.
|
|
98
|
+
*/
|
|
99
|
+
function parseEnv(source, options = {}) {
|
|
100
|
+
const issues = [];
|
|
101
|
+
const required = new Set(options.require ?? []);
|
|
102
|
+
const rawUrl = first(source, ENV_VARIABLES.url);
|
|
103
|
+
const url = rawUrl ? checkUrl(rawUrl, ENV_VARIABLES.url, issues) : void 0;
|
|
104
|
+
if (!rawUrl) issues.push({
|
|
105
|
+
variables: ENV_VARIABLES.url,
|
|
106
|
+
message: "is not set"
|
|
107
|
+
});
|
|
108
|
+
const publishableKeys = keySet(source, "SUPABASE_PUBLISHABLE_KEYS", "publishable", issues);
|
|
109
|
+
const singlePublishable = first(source, ENV_VARIABLES.publishableKey);
|
|
110
|
+
const publishableKey = singlePublishable ?? preferred(publishableKeys);
|
|
111
|
+
if (!publishableKey) issues.push({
|
|
112
|
+
variables: ENV_VARIABLES.publishableKey,
|
|
113
|
+
message: "is not set"
|
|
114
|
+
});
|
|
115
|
+
else if (singlePublishable) checkKey(publishableKey, "publishable", ENV_VARIABLES.publishableKey, issues);
|
|
116
|
+
const namedSecrets = keySet(source, "SUPABASE_SECRET_KEYS", "secret", issues);
|
|
117
|
+
const singleSecret = first(source, ENV_VARIABLES.secretKey);
|
|
118
|
+
const secretKey = singleSecret ?? preferred(namedSecrets);
|
|
119
|
+
if (singleSecret) checkKey(singleSecret, "secret", ENV_VARIABLES.secretKey, issues);
|
|
120
|
+
else if (!secretKey && required.has("secretKey")) issues.push({
|
|
121
|
+
variables: ENV_VARIABLES.secretKey,
|
|
122
|
+
message: "is not set"
|
|
123
|
+
});
|
|
124
|
+
const dbUrl = first(source, ENV_VARIABLES.dbUrl);
|
|
125
|
+
if (dbUrl && !/^postgres(ql)?:\/\//.test(dbUrl)) issues.push({
|
|
126
|
+
variables: ENV_VARIABLES.dbUrl,
|
|
127
|
+
message: "must be a postgres:// connection string"
|
|
128
|
+
});
|
|
129
|
+
else if (!dbUrl && required.has("dbUrl")) issues.push({
|
|
130
|
+
variables: ENV_VARIABLES.dbUrl,
|
|
131
|
+
message: "is not set"
|
|
132
|
+
});
|
|
133
|
+
const rawJwks = first(source, ENV_VARIABLES.jwksUrl);
|
|
134
|
+
const jwksOverride = rawJwks ? checkUrl(rawJwks, ENV_VARIABLES.jwksUrl, issues) : void 0;
|
|
135
|
+
const rawRead = first(source, ENV_VARIABLES.readUrl);
|
|
136
|
+
const readUrl = rawRead ? checkUrl(rawRead, ENV_VARIABLES.readUrl, issues) : void 0;
|
|
137
|
+
if (issues.length > 0 || !url || !publishableKey) return {
|
|
138
|
+
ok: false,
|
|
139
|
+
issues
|
|
140
|
+
};
|
|
141
|
+
const base = url.href.replace(/\/+$/, "");
|
|
142
|
+
const projectRef = HOSTED.exec(url.hostname)?.[1];
|
|
143
|
+
return {
|
|
144
|
+
ok: true,
|
|
145
|
+
env: {
|
|
146
|
+
url: base,
|
|
147
|
+
publishableKey,
|
|
148
|
+
jwksUrl: jwksOverride ?? new URL(`${base}/auth/v1/.well-known/jwks.json`),
|
|
149
|
+
...secretKey ? {
|
|
150
|
+
secretKey,
|
|
151
|
+
secretKeys: {
|
|
152
|
+
...namedSecrets,
|
|
153
|
+
default: secretKey
|
|
154
|
+
}
|
|
155
|
+
} : {},
|
|
156
|
+
...dbUrl ? { dbUrl } : {},
|
|
157
|
+
...projectRef ? { projectRef } : {},
|
|
158
|
+
...readUrl ? { readUrl: readUrl.href.replace(/\/+$/, "") } : {}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function processEnv() {
|
|
163
|
+
return globalThis.process?.env ?? {};
|
|
164
|
+
}
|
|
165
|
+
/** Like `parseEnv`, but throws `EnvValidationError`. Reads `process.env` by default. */
|
|
166
|
+
function loadEnv(source = processEnv(), options = {}) {
|
|
167
|
+
const result = parseEnv(source, options);
|
|
168
|
+
if (!result.ok) throw new EnvValidationError(result.issues);
|
|
169
|
+
return result.env;
|
|
170
|
+
}
|
|
171
|
+
/** The env validator as a Standard Schema, for t3-env, framework config or any validator slot. */
|
|
172
|
+
function envSchema(options = {}) {
|
|
173
|
+
return { "~standard": {
|
|
174
|
+
version: 1,
|
|
175
|
+
vendor: "better-supabase",
|
|
176
|
+
validate(value) {
|
|
177
|
+
if (typeof value !== "object" || value === null) return { issues: [{ message: "Expected an environment object" }] };
|
|
178
|
+
const result = parseEnv(value, options);
|
|
179
|
+
if (result.ok) return { value: result.env };
|
|
180
|
+
return { issues: result.issues.map((issue) => ({
|
|
181
|
+
message: `${issue.variables[0] ?? "env"} ${issue.message}`,
|
|
182
|
+
path: issue.variables.slice(0, 1)
|
|
183
|
+
})) };
|
|
184
|
+
}
|
|
185
|
+
} };
|
|
186
|
+
}
|
|
187
|
+
/** The parts that are safe in client bundles. */
|
|
188
|
+
function publicEnv(env) {
|
|
189
|
+
return {
|
|
190
|
+
url: env.url,
|
|
191
|
+
publishableKey: env.publishableKey
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/** Converts to `@supabase/server`'s `SupabaseEnv` (the `env` option of `withSupabase`). */
|
|
195
|
+
function toServerEnv(env) {
|
|
196
|
+
return {
|
|
197
|
+
url: env.url,
|
|
198
|
+
publishableKeys: { default: env.publishableKey },
|
|
199
|
+
secretKeys: env.secretKeys ?? (env.secretKey ? { default: env.secretKey } : {}),
|
|
200
|
+
jwks: env.jwksUrl
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
2
204
|
export { ENV_VARIABLES, EnvValidationError, envSchema, loadEnv, parseEnv, publicEnv, toServerEnv };
|
package/dist/events/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as HttpMode, c as RowEventData, d as httpSink, f as isCloudEvent, i as ForwardOptions, l as forwardMutations, m as toHttp, n as CloudEventOptions, o as HttpSinkOptions, p as toCloudEvents, r as EventSink, s as ROW_EVENT_TYPES, t as CloudEvent, u as fromHttp } from "../index-
|
|
1
|
+
import { a as HttpMode, c as RowEventData, d as httpSink, f as isCloudEvent, i as ForwardOptions, l as forwardMutations, m as toHttp, n as CloudEventOptions, o as HttpSinkOptions, p as toCloudEvents, r as EventSink, s as ROW_EVENT_TYPES, t as CloudEvent, u as fromHttp } from "../index-GV77NpRP.js";
|
|
2
2
|
export { CloudEvent, CloudEventOptions, EventSink, ForwardOptions, HttpMode, HttpSinkOptions, ROW_EVENT_TYPES, RowEventData, forwardMutations, fromHttp, httpSink, isCloudEvent, toCloudEvents, toHttp };
|
package/dist/events/index.js
CHANGED
|
@@ -1,2 +1,140 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as nowInstant } from "../temporal-BwshQk0W.js";
|
|
2
|
+
//#region src/events/index.ts
|
|
3
|
+
const ROW_EVENT_TYPES = {
|
|
4
|
+
insert: "dev.better-supabase.row.created",
|
|
5
|
+
upsert: "dev.better-supabase.row.upserted",
|
|
6
|
+
update: "dev.better-supabase.row.updated",
|
|
7
|
+
delete: "dev.better-supabase.row.deleted"
|
|
8
|
+
};
|
|
9
|
+
function subjectOf(meta, table, row) {
|
|
10
|
+
const key = meta?.tables[table]?.primaryKey;
|
|
11
|
+
if (!key || key.length === 0) return void 0;
|
|
12
|
+
const values = key.map((column) => row[column]);
|
|
13
|
+
if (values.some((value) => value === void 0 || value === null)) return void 0;
|
|
14
|
+
return `${table}/${values.map((value) => encodeURIComponent(String(value))).join(",")}`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* One CloudEvent per mutated row: `dev.better-supabase.row.created` with the
|
|
18
|
+
* app-cased row as `data`, the primary key as `subject`, the tenant as
|
|
19
|
+
* `partitionkey` and the actor as `actorid`.
|
|
20
|
+
*/
|
|
21
|
+
function toCloudEvents(notice, options) {
|
|
22
|
+
const type = options.typePrefix ? `${options.typePrefix}.row.${ROW_EVENT_TYPES[notice.kind].split(".").at(-1)}` : ROW_EVENT_TYPES[notice.kind];
|
|
23
|
+
const time = (options.now ?? nowInstant)().toString();
|
|
24
|
+
return notice.rows.map((row) => {
|
|
25
|
+
const subject = subjectOf(options.meta, notice.table, row);
|
|
26
|
+
return {
|
|
27
|
+
specversion: "1.0",
|
|
28
|
+
id: options.id?.() ?? crypto.randomUUID(),
|
|
29
|
+
source: options.source,
|
|
30
|
+
type,
|
|
31
|
+
...subject ? { subject } : {},
|
|
32
|
+
time,
|
|
33
|
+
datacontenttype: "application/json",
|
|
34
|
+
data: {
|
|
35
|
+
table: notice.table,
|
|
36
|
+
row
|
|
37
|
+
},
|
|
38
|
+
...notice.context.tenant ? { partitionkey: notice.context.tenant } : {},
|
|
39
|
+
...notice.context.actor?.id ? { actorid: notice.context.actor.id } : {}
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Sends a CloudEvent for every mutation to `sink`. Returns a function that
|
|
45
|
+
* stops forwarding. Use an outbox (SQL kit) when events must not be lost.
|
|
46
|
+
*/
|
|
47
|
+
function forwardMutations(sb, sink, options) {
|
|
48
|
+
return sb.events.on("mutation", (notice) => {
|
|
49
|
+
if (options.filter && !options.filter(notice)) return;
|
|
50
|
+
const events = toCloudEvents(notice, {
|
|
51
|
+
...options,
|
|
52
|
+
meta: sb.meta
|
|
53
|
+
});
|
|
54
|
+
if (events.length === 0) return;
|
|
55
|
+
const report = (error) => {
|
|
56
|
+
(options.onError ?? ((cause) => {
|
|
57
|
+
sb.events.logger.error("event sink failed", { cause });
|
|
58
|
+
}))(error, events);
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
Promise.resolve(sink.send(events)).catch(report);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
report(error);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const STRUCTURED = "application/cloudevents+json";
|
|
68
|
+
const BATCH = "application/cloudevents-batch+json";
|
|
69
|
+
/** The HTTP protocol binding: headers and body for one event (or a batch). */
|
|
70
|
+
function toHttp(events, mode = "structured") {
|
|
71
|
+
const list = Array.isArray(events) ? events : [events];
|
|
72
|
+
switch (mode) {
|
|
73
|
+
case "batch": return [{
|
|
74
|
+
headers: { "content-type": BATCH },
|
|
75
|
+
body: JSON.stringify(list)
|
|
76
|
+
}];
|
|
77
|
+
case "structured": return list.map((event) => ({
|
|
78
|
+
headers: { "content-type": STRUCTURED },
|
|
79
|
+
body: JSON.stringify(event)
|
|
80
|
+
}));
|
|
81
|
+
case "binary": return list.map((event) => {
|
|
82
|
+
const headers = {};
|
|
83
|
+
for (const [key, value] of Object.entries(event)) {
|
|
84
|
+
if (key === "data" || key === "datacontenttype" || value === void 0) continue;
|
|
85
|
+
headers[`ce-${key}`] = encodeURIComponent(String(value)).replaceAll("%20", " ");
|
|
86
|
+
}
|
|
87
|
+
headers["content-type"] = event.datacontenttype ?? "application/json";
|
|
88
|
+
return {
|
|
89
|
+
headers,
|
|
90
|
+
body: event.data === void 0 ? "" : JSON.stringify(event.data)
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
default: throw new TypeError(`Unknown CloudEvents mode ${String(mode)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function isCloudEvent(value) {
|
|
97
|
+
if (typeof value !== "object" || value === null) return false;
|
|
98
|
+
const event = value;
|
|
99
|
+
return event["specversion"] === "1.0" && typeof event["id"] === "string" && typeof event["source"] === "string" && typeof event["type"] === "string";
|
|
100
|
+
}
|
|
101
|
+
/** Reads CloudEvents from a request in any of the three HTTP modes. */
|
|
102
|
+
async function fromHttp(request) {
|
|
103
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
104
|
+
const text = await request.text();
|
|
105
|
+
if (contentType.startsWith(BATCH)) {
|
|
106
|
+
const parsed = JSON.parse(text);
|
|
107
|
+
if (!Array.isArray(parsed) || !parsed.every(isCloudEvent)) throw new TypeError("Invalid CloudEvents batch");
|
|
108
|
+
return parsed;
|
|
109
|
+
}
|
|
110
|
+
if (contentType.startsWith(STRUCTURED)) {
|
|
111
|
+
const parsed = JSON.parse(text);
|
|
112
|
+
if (!isCloudEvent(parsed)) throw new TypeError("Invalid CloudEvent");
|
|
113
|
+
return [parsed];
|
|
114
|
+
}
|
|
115
|
+
const event = {};
|
|
116
|
+
for (const [key, value] of request.headers) if (key.startsWith("ce-")) event[key.slice(3)] = decodeURIComponent(value);
|
|
117
|
+
if (contentType) event["datacontenttype"] = contentType;
|
|
118
|
+
if (text) event["data"] = contentType.includes("json") ? JSON.parse(text) : text;
|
|
119
|
+
if (!isCloudEvent(event)) throw new TypeError("Request is not a CloudEvent");
|
|
120
|
+
return [event];
|
|
121
|
+
}
|
|
122
|
+
/** POSTs events to `url`. Non-2xx responses reject. */
|
|
123
|
+
function httpSink(url, options = {}) {
|
|
124
|
+
const send = options.fetch ?? fetch;
|
|
125
|
+
return { async send(events) {
|
|
126
|
+
for (const message of toHttp(events, options.mode ?? "batch")) {
|
|
127
|
+
const response = await send(url, {
|
|
128
|
+
method: "POST",
|
|
129
|
+
headers: {
|
|
130
|
+
...options.headers,
|
|
131
|
+
...message.headers
|
|
132
|
+
},
|
|
133
|
+
body: message.body
|
|
134
|
+
});
|
|
135
|
+
if (!response.ok) throw new Error(`Event sink responded ${String(response.status)}`);
|
|
136
|
+
}
|
|
137
|
+
} };
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
2
140
|
export { ROW_EVENT_TYPES, forwardMutations, fromHttp, httpSink, isCloudEvent, toCloudEvents, toHttp };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { g as Operation } from "./executor-
|
|
2
|
-
import { p as DbError } from "./result-
|
|
3
|
-
import { h as RequestContext, l as MutationKind } from "./plugin-
|
|
1
|
+
import { g as Operation } from "./executor-DynmIWhq.js";
|
|
2
|
+
import { p as DbError } from "./result-c2OYgCV2.js";
|
|
3
|
+
import { h as RequestContext, l as MutationKind } from "./plugin-DVRB8qNE.js";
|
|
4
4
|
//#region src/core/logger.d.ts
|
|
5
5
|
type LogFields = Readonly<Record<string, unknown>>;
|
|
6
6
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { r as Executor } from "./executor-
|
|
2
|
-
import { y as RawDbError } from "./result-
|
|
1
|
+
import { r as Executor } from "./executor-DynmIWhq.js";
|
|
2
|
+
import { y as RawDbError } from "./result-c2OYgCV2.js";
|
|
3
3
|
//#region src/postgres/executor.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Anything that runs parameterized SQL: `ctx.postgres` from
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c as mapDbError, n as dbError } from "./errors-Bl3rkOzo.js";
|
|
2
2
|
import { i as ok, n as err, o as toDbError } from "./result-DWXatkd6.js";
|
|
3
|
-
import { t as compileSql } from "./sql-
|
|
3
|
+
import { t as compileSql } from "./sql-BIgk12ew.js";
|
|
4
4
|
//#region src/postgres/executor.ts
|
|
5
5
|
function isPgError(value) {
|
|
6
6
|
return typeof value === "object" && value !== null && typeof value.code === "string";
|
|
@@ -25,6 +25,11 @@ async function run(client, plan, op) {
|
|
|
25
25
|
const [first] = await client.queryRaw(plan.count.text, [...plan.count.params]);
|
|
26
26
|
count = first?.count ?? 0;
|
|
27
27
|
} else if (op.kind !== "select") count = rows.length;
|
|
28
|
+
for (const next of plan.chunks ?? []) {
|
|
29
|
+
const more = await run(client, next, op);
|
|
30
|
+
rows.push(...more.rows);
|
|
31
|
+
count = (count ?? 0) + (more.count ?? 0);
|
|
32
|
+
}
|
|
28
33
|
return {
|
|
29
34
|
rows,
|
|
30
35
|
count
|
|
@@ -74,7 +79,7 @@ async function executeOn(client, op, context) {
|
|
|
74
79
|
if (context.signal?.aborted) return err(dbError("aborted", "The request was aborted"));
|
|
75
80
|
let result;
|
|
76
81
|
try {
|
|
77
|
-
result = await run(client, plan, op);
|
|
82
|
+
result = plan.chunks && client.transaction ? await client.transaction((scoped) => run(scoped, plan, op)) : await run(client, plan, op);
|
|
78
83
|
} catch (cause) {
|
|
79
84
|
const raw = fromPgError(cause);
|
|
80
85
|
return err(raw ? mapDbError(raw, context.errorMappers) : toDbError(cause));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { o as Result, v as ErrorMapper } from "./result-
|
|
1
|
+
import { P as TableMeta, T as RelationMeta, c as Codec } from "./types-DONv9SHN.js";
|
|
2
|
+
import { o as Result, v as ErrorMapper } from "./result-c2OYgCV2.js";
|
|
3
3
|
//#region src/ir/types.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* The query IR. Every repository call becomes one of these operations; the
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/core/guards.ts
|
|
2
|
+
/**
|
|
3
|
+
* `Array.isArray` narrows `unknown` and readonly arrays to `any[]`; this keeps
|
|
4
|
+
* the element type (or `unknown`).
|
|
5
|
+
*/
|
|
6
|
+
function isList(value) {
|
|
7
|
+
return Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { isList as t };
|
package/dist/hono/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as BetterSupabase } from "../define-
|
|
3
|
-
import { r as AuthState } from "../resolve-
|
|
4
|
-
import { d as ServerContext, f as ServerOptions, l as BetterServer, n as GuardOptions } from "../respond-
|
|
5
|
-
import {
|
|
1
|
+
import { N as TableKey, n as AnyModels, t as AnyFunctions } from "../types-DONv9SHN.js";
|
|
2
|
+
import { t as BetterSupabase } from "../define-DUbydmJa.js";
|
|
3
|
+
import { r as AuthState } from "../resolve-BRciX2jK.js";
|
|
4
|
+
import { d as ServerContext, f as ServerOptions, l as BetterServer, n as GuardOptions } from "../respond-DAkPIdTH.js";
|
|
5
|
+
import { a as ResourceRouteOptions } from "../resource-CwVgiI5q.js";
|
|
6
6
|
import { Context, ErrorHandler, Hono, MiddlewareHandler } from "hono";
|
|
7
7
|
//#region src/hono/index.d.ts
|
|
8
8
|
/** Hono `Env` with the request's better-supabase context in `c.var`. */
|
package/dist/hono/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as dbError, r as dbErrorOf } from "../errors-Bl3rkOzo.js";
|
|
2
|
-
import { a as problemResponse } from "../problem-
|
|
3
|
-
import { i as respond, n as guard, o as createServer, s as extendServer, t as defaultExpose } from "../respond-
|
|
4
|
-
import { t as defineResource } from "../resource-
|
|
2
|
+
import { a as problemResponse } from "../problem-CGt1ADix.js";
|
|
3
|
+
import { i as respond, n as guard, o as createServer, s as extendServer, t as defaultExpose } from "../respond-C44EZAJo.js";
|
|
4
|
+
import { t as defineResource } from "../resource-D30rp0GN.js";
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
//#region src/hono/index.ts
|
|
7
7
|
function contextOf(c) {
|
|
@@ -31,7 +31,7 @@ function createHono(sb, options = {}) {
|
|
|
31
31
|
middleware(middlewareOptions = {}) {
|
|
32
32
|
return async (c, next) => {
|
|
33
33
|
const ctx = await server.context(c.req.raw, { refresh: middlewareOptions.refresh ?? false });
|
|
34
|
-
const denied = guard(ctx.auth, middlewareOptions.allow, middlewareOptions.aal);
|
|
34
|
+
const denied = guard(ctx.auth, middlewareOptions.allow, middlewareOptions.aal, middlewareOptions.scopes);
|
|
35
35
|
if (denied) return ctx.resolution.apply(problemResponse(denied, {
|
|
36
36
|
instance: new URL(c.req.url).pathname,
|
|
37
37
|
expose
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { p as DbError } from "./result-
|
|
2
|
-
import { Z as QuerySpec } from "./define-
|
|
3
|
-
import { i as BrowserAuth, t as AuthSnapshot } from "./index-
|
|
1
|
+
import { p as DbError } from "./result-c2OYgCV2.js";
|
|
2
|
+
import { Z as QuerySpec } from "./define-DUbydmJa.js";
|
|
3
|
+
import { i as BrowserAuth, t as AuthSnapshot } from "./index-UJ-LUxtI.js";
|
|
4
4
|
import { n as TemplateValues } from "./template-mbvlysOo.js";
|
|
5
|
-
import { t as AuthSession } from "./view-
|
|
6
|
-
import { a as LiveSource, g as Topic, h as SubscriptionStatus, p as SubscribeOptions, r as LiveCountSeed, u as EventSchemas, v as TopicHandlers, y as TopicMessage } from "./live-
|
|
7
|
-
import "./entitlements-
|
|
8
|
-
import "./session
|
|
5
|
+
import { t as AuthSession } from "./view-3Po_PVlr.js";
|
|
6
|
+
import { a as LiveSource, g as Topic, h as SubscriptionStatus, p as SubscribeOptions, r as LiveCountSeed, u as EventSchemas, v as TopicHandlers, y as TopicMessage } from "./live-QE3UPbGm.js";
|
|
7
|
+
import "./entitlements-DvJXoI02.js";
|
|
8
|
+
import "./react/session.js";
|
|
9
9
|
import { SupabaseClient } from "@supabase/supabase-js";
|
|
10
10
|
import { ReactNode } from "react";
|
|
11
11
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
12
12
|
import { QueryClient } from "@tanstack/query-core";
|
|
13
|
-
//#region src/react/
|
|
13
|
+
//#region src/react/hooks.d.ts
|
|
14
14
|
/** The parts of `createBrowser()` the provider needs. */
|
|
15
15
|
interface BrowserLike {
|
|
16
16
|
readonly sb: LiveSource;
|
|
@@ -37,13 +37,17 @@ interface BetterHooks<B extends BrowserLike> {
|
|
|
37
37
|
readonly useQueries: () => B["queries"];
|
|
38
38
|
readonly useSupabase: () => SupabaseClient;
|
|
39
39
|
readonly useAuth: () => AuthSnapshot;
|
|
40
|
-
/** `useSession()`
|
|
41
|
-
readonly useSession: () => AuthSession<ClaimsOf<B>>;
|
|
40
|
+
/** `useSession()` typed by the browser's `sb.claims(schema)` and `sb.userMetadata(schema)`. */
|
|
41
|
+
readonly useSession: () => AuthSession<ClaimsOf<B>, ProfileOf<B>>;
|
|
42
42
|
}
|
|
43
43
|
/** The claims type of a browser's `sb.claims(schema)`, `unknown` without one. */
|
|
44
44
|
type ClaimsOf<B extends BrowserLike> = B["sb"] extends {
|
|
45
45
|
readonly claimsSchema: StandardSchemaV1<unknown, infer C> | undefined;
|
|
46
46
|
} ? C : unknown;
|
|
47
|
+
/** The profile type of a browser's `sb.userMetadata(schema)`, `unknown` without one. */
|
|
48
|
+
type ProfileOf<B extends BrowserLike> = B["sb"] extends {
|
|
49
|
+
readonly userMetadataSchema: StandardSchemaV1<unknown, infer P> | undefined;
|
|
50
|
+
} ? P : unknown;
|
|
47
51
|
/**
|
|
48
52
|
* Hooks typed for your schema.
|
|
49
53
|
*
|
|
@@ -71,8 +75,8 @@ interface BroadcastOptions extends Omit<SubscribeOptions, "onStatus"> {
|
|
|
71
75
|
declare function useBroadcast<P extends string, E extends EventSchemas>(topic: Topic<P, E>, values: TemplateValues<P> | null | undefined, handlers?: TopicHandlers<E>, options?: BroadcastOptions): SubscriptionStatus;
|
|
72
76
|
interface LiveQueryHookOptions {
|
|
73
77
|
/**
|
|
74
|
-
* Tenant for tenant-scoped tables. Defaults to the `
|
|
75
|
-
* (top-level or in `app_metadata`).
|
|
78
|
+
* Tenant for tenant-scoped tables. Defaults to the `config.claims.tenant`
|
|
79
|
+
* claim (`tenant_id`, top-level or in `app_metadata`).
|
|
76
80
|
*/
|
|
77
81
|
readonly tenant?: string;
|
|
78
82
|
/** Defaults to 100 ms. */
|
|
@@ -113,4 +117,4 @@ interface LiveCount {
|
|
|
113
117
|
*/
|
|
114
118
|
declare function useLiveCount(source: QuerySpec<string, "count", number> | LiveCountSeed | null | undefined, options?: LiveCountHookOptions): LiveCount;
|
|
115
119
|
//#endregion
|
|
116
|
-
export { BrowserLike as a, LiveCountHookOptions as c,
|
|
120
|
+
export { BrowserLike as a, LiveCountHookOptions as c, createHooks as d, useAuth as f, useSupabase as g, useLiveQuery as h, BroadcastOptions as i, LiveQueryHookOptions as l, useLiveCount as m, BetterSupabaseProvider as n, ClaimsOf as o, useBroadcast as p, BetterSupabaseProviderProps as r, LiveCount as s, BetterHooks as t, ProfileOf as u };
|