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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as invalidRequest, n as simplifyOrFalse } from "./simplify-
|
|
1
|
+
import { a as invalidRequest, n as simplifyOrFalse } from "./simplify-_0oUKqCd.js";
|
|
2
2
|
import { t as lookupOf } from "./lookup-BaP5bI27.js";
|
|
3
3
|
//#region src/compile/sql.ts
|
|
4
4
|
const MAX_PARAMS = 65535;
|
|
@@ -291,10 +291,17 @@ function insertPlan(compiler, from, alias, op, columns) {
|
|
|
291
291
|
if (op.onConflict) {
|
|
292
292
|
const target = `(${op.onConflict.columns.map(quoteIdent).join(", ")})`;
|
|
293
293
|
const updates = columns.map((column) => `${quoteIdent(column)} = excluded.${quoteIdent(column)}`);
|
|
294
|
-
conflict = op.onConflict.action === "ignore" || updates.length === 0 ? ` on conflict ${target} do nothing` : ` on conflict ${target} do update set ${updates.join(", ")}`;
|
|
294
|
+
conflict = op.onConflict.action === "ignore" || updates.length === 0 ? ` on conflict ${target} do nothing` : ` on conflict ${target} do update set ${updates.join(", ")}${tenantGuard(op, alias)}`;
|
|
295
295
|
}
|
|
296
296
|
return mutation(`insert into ${from} ${body}${conflict}${returningRows(compiler, op, alias)}`, op.returning !== void 0, compiler.params);
|
|
297
297
|
}
|
|
298
|
+
/** An upsert never updates a row that belongs to another tenant. */
|
|
299
|
+
function tenantGuard(op, alias) {
|
|
300
|
+
const app = op.table.flags.tenant;
|
|
301
|
+
const column = app === void 0 ? void 0 : op.table.columns[app]?.db;
|
|
302
|
+
if (column === void 0) return "";
|
|
303
|
+
return ` where ${alias}.${quoteIdent(column)} = excluded.${quoteIdent(column)}`;
|
|
304
|
+
}
|
|
298
305
|
function mutation(statement, returning, params) {
|
|
299
306
|
if (returning) return {
|
|
300
307
|
rows: {
|
package/dist/ssr/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as toSession, t as AuthSession } from "../view-
|
|
3
|
-
import { n as MembershipClaim, r as hasEntitlement, t as EntitlementKey } from "../entitlements-
|
|
4
|
-
import { n as RefreshOutcome, r as refreshSession, t as RefreshOptions } from "../refresh-
|
|
1
|
+
import { C as writeSession, S as sessionCookieName, _ as StoredSession, a as AuthResolver, b as readSession, c as ResolveAuthOptions, f as resolveAuth, g as CookieWrite, h as CookieRecord, i as AuthResolution, m as CookieOptions, o as AuthState, p as AUTH_CACHE_HEADERS, u as authContext, v as applyCookieWrites, x as serializeCookie, y as parseCookies } from "../impersonation-BmePsuxz.js";
|
|
2
|
+
import { n as toSession, t as AuthSession } from "../view-CbgObAeb.js";
|
|
3
|
+
import { n as MembershipClaim, r as hasEntitlement, t as EntitlementKey } from "../entitlements-BiJZ4t49.js";
|
|
4
|
+
import { n as RefreshOutcome, r as refreshSession, t as RefreshOptions } from "../refresh-CFPX0Qft.js";
|
|
5
5
|
export { AUTH_CACHE_HEADERS, type AuthResolution, type AuthResolver, type AuthSession, type AuthState, type CookieOptions, type CookieRecord, type CookieWrite, type EntitlementKey, type MembershipClaim, type RefreshOptions, type RefreshOutcome, type ResolveAuthOptions, type StoredSession, applyCookieWrites, authContext, hasEntitlement, parseCookies, readSession, refreshSession, resolveAuth, serializeCookie, sessionCookieName, toSession, writeSession };
|
package/dist/ssr/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import { t as hasEntitlement } from "../entitlements-CTEBm7IB.js";
|
|
2
|
-
import {
|
|
3
|
-
import { t as toSession } from "../view-BrJI2LKp.js";
|
|
2
|
+
import { _ as writeSession, d as AUTH_CACHE_HEADERS, f as applyCookieWrites, g as sessionCookieName, h as serializeCookie, m as readSession, n as toSession, o as authContext, p as parseCookies, u as resolveAuth, v as refreshSession } from "../view-Bn6HNKUe.js";
|
|
4
3
|
export { AUTH_CACHE_HEADERS, applyCookieWrites, authContext, hasEntitlement, parseCookies, readSession, refreshSession, resolveAuth, serializeCookie, sessionCookieName, toSession, writeSession };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as dbError } from "./errors-
|
|
2
|
-
import { i as ok, n as err } from "./result-
|
|
1
|
+
import { n as dbError } from "./errors-DtwXa6b8.js";
|
|
2
|
+
import { i as ok, n as err } from "./result-BKiliPY7.js";
|
|
3
3
|
//#region src/core/standard.ts
|
|
4
4
|
/** Validates with any Standard Schema (zod, valibot, arktype, ...). */
|
|
5
5
|
async function validate(schema, value, label = "value") {
|
|
@@ -10,7 +10,10 @@ async function validate(schema, value, label = "value") {
|
|
|
10
10
|
}
|
|
11
11
|
function toIssues(issues) {
|
|
12
12
|
return issues.map((issue) => {
|
|
13
|
-
const path = issue.path?.map((segment) =>
|
|
13
|
+
const path = issue.path?.map((segment) => {
|
|
14
|
+
const key = typeof segment === "object" ? segment.key : segment;
|
|
15
|
+
return typeof key === "symbol" ? String(key) : key;
|
|
16
|
+
});
|
|
14
17
|
return path && path.length > 0 ? {
|
|
15
18
|
message: issue.message,
|
|
16
19
|
path
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { u as Result } from "./executor-BunPOO-k.js";
|
|
2
2
|
import { StandardSchemaV1 as StandardSchemaV1$1 } from "@standard-schema/spec";
|
|
3
3
|
//#region src/core/standard.d.ts
|
|
4
4
|
/** Validates with any Standard Schema (zod, valibot, arktype, ...). */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { c as mapDbError, n as dbError } from "./errors-
|
|
2
|
-
import { a as invalidRequest, n as simplifyOrFalse } from "./simplify-
|
|
3
|
-
import { i as temporalText } from "./temporal-
|
|
4
|
-
import { i as ok, n as err, o as toDbError } from "./result-
|
|
1
|
+
import { c as mapDbError, n as dbError } from "./errors-DtwXa6b8.js";
|
|
2
|
+
import { a as invalidRequest, n as simplifyOrFalse } from "./simplify-_0oUKqCd.js";
|
|
3
|
+
import { i as temporalText } from "./temporal-CVlIV2DZ.js";
|
|
4
|
+
import { i as ok, n as err, o as toDbError } from "./result-BKiliPY7.js";
|
|
5
5
|
//#region src/compile/postgrest.ts
|
|
6
6
|
function scalar(value) {
|
|
7
7
|
if (typeof value === "string") return value;
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as AccessBucketPolicy, x as PermdockBucketPolicy } from "../types-Dnf_wRDz.js";
|
|
2
|
+
import { n as PermdockCatalog } from "../permdock-sql-C2Inc6Xc.js";
|
|
2
3
|
import { n as StoragePath, t as PathIn } from "../path-Cv__-OTj.js";
|
|
3
|
-
import { t as PermdockCatalog } from "../permdock-sql-DImJULY-.js";
|
|
4
4
|
import { n as TemplateValues, t as TemplateParams } from "../template-mbvlysOo.js";
|
|
5
|
-
import { C as
|
|
6
|
-
export { type ActualBucket, type Bucket, type BucketClient, type BucketConfig, type BucketConnectOptions, type BucketDrift, type BucketPolicy, type ObjectTarget, type PathIn, type PermdockBucketPolicy, type PermdockCatalog, type ReplaceOptions, type ReplaceResult, type Reservation, type StorageClient, type StoragePath, type StoredObject, type SweepOptions, type SweepResult, TTL, type TemplateParams, type TemplateValues, type TransformOptions, type TtlPreset, type UploadBody, type UploadOptions, type UrlOptions, defineBucket, fromStorageError, parseSize };
|
|
5
|
+
import { A as parseSize, C as TransformOptions, D as UrlOptions, E as UploadOptions, O as defineBucket, S as TTL, T as UploadBody, _ as Reservation, a as avatarBucket, b as SweepOptions, c as Bucket, d as BucketConnectOptions, f as BucketDrift, g as ReplaceResult, h as ReplaceOptions, i as OrgLogoBucketOptions, k as fromStorageError, l as BucketClient, m as ObjectTarget, n as IMAGE_TYPES, o as orgLogoBucket, p as BucketPolicy, r as ImageBucketOptions, s as ActualBucket, t as AvatarBucketOptions, u as BucketConfig, v as StorageClient, w as TtlPreset, x as SweepResult, y as StoredObject } from "../index-SaTsEfGP.js";
|
|
6
|
+
export { type AccessBucketPolicy, type ActualBucket, type AvatarBucketOptions, type Bucket, type BucketClient, type BucketConfig, type BucketConnectOptions, type BucketDrift, type BucketPolicy, IMAGE_TYPES, type ImageBucketOptions, type ObjectTarget, type OrgLogoBucketOptions, type PathIn, type PermdockBucketPolicy, type PermdockCatalog, type ReplaceOptions, type ReplaceResult, type Reservation, type StorageClient, type StoragePath, type StoredObject, type SweepOptions, type SweepResult, TTL, type TemplateParams, type TemplateValues, type TransformOptions, type TtlPreset, type UploadBody, type UploadOptions, type UrlOptions, avatarBucket, defineBucket, fromStorageError, orgLogoBucket, parseSize };
|
package/dist/storage/index.js
CHANGED
|
@@ -1,9 +1,82 @@
|
|
|
1
|
-
import { n as dbError, o as isDbError, t as DbException } from "../errors-
|
|
2
|
-
import { r as optionalTemporal } from "../temporal-
|
|
3
|
-
import { n as temporalMissing } from "../temporal-required-
|
|
4
|
-
import { i as ok, n as err, o as toDbError, t as AsyncResult } from "../result-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { n as dbError, o as isDbError, t as DbException } from "../errors-DtwXa6b8.js";
|
|
2
|
+
import { 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, o as toDbError, t as AsyncResult } from "../result-BKiliPY7.js";
|
|
5
|
+
import { o as spansAllTenants, s as tenantOf } from "../plugin-DwARMuQp.js";
|
|
6
|
+
import { i as tenantClaimPaths, n as claimAt } from "../claims-BAf6c7is.js";
|
|
7
|
+
import { i as sqlString, n as slug, r as sqlIdent, t as parseTemplate } from "../template-TxrGI5jR.js";
|
|
8
|
+
import { n as permdockCheck } from "../permdock-sql-DgOfeixi.js";
|
|
9
|
+
//#region src/storage/policy.ts
|
|
10
|
+
/**
|
|
11
|
+
* The checks of a PermDock or access contract policy, or `undefined` for a
|
|
12
|
+
* named policy. `segmentOf` returns the tenant segment, or throws when the
|
|
13
|
+
* path has none.
|
|
14
|
+
*/
|
|
15
|
+
function policyChecks(config, policy, segmentOf) {
|
|
16
|
+
if (typeof policy === "string") return void 0;
|
|
17
|
+
let check;
|
|
18
|
+
let keys;
|
|
19
|
+
if ("permdock" in policy) {
|
|
20
|
+
const where = `defineBucket(${config.id})`;
|
|
21
|
+
const id = policy.scope === "global" ? void 0 : `split_part(name, '/', ${String(policy.segment ?? segmentOf("PermDock"))})`;
|
|
22
|
+
check = (key) => permdockCheck(where, policy, key, id, config.catalog);
|
|
23
|
+
keys = policy.permdock;
|
|
24
|
+
} else {
|
|
25
|
+
const segment = policy.segment ?? (policy.scope === "platform" ? void 0 : segmentOf("permission"));
|
|
26
|
+
check = (key) => segment === void 0 ? `(select better_supabase.is_platform(${sqlString(key)}))` : `split_part(name, '/', ${String(segment)}) in (select t::text from better_supabase.tenant_ids_with(${sqlString(key)}) t)`;
|
|
27
|
+
keys = policy.access;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
read: check(keys.read),
|
|
31
|
+
...keys.list === void 0 ? {} : { list: check(keys.list) },
|
|
32
|
+
write: check(keys.write),
|
|
33
|
+
delete: check(keys.delete ?? keys.write)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/storage/tenant-scope.ts
|
|
38
|
+
const forbidden = (bucket, message) => new DbException(dbError("forbidden", message, { table: bucket }));
|
|
39
|
+
function resolveTenant(options, claim) {
|
|
40
|
+
const { context } = options;
|
|
41
|
+
if (options.tenant !== void 0) return options.tenant;
|
|
42
|
+
if (!context) return void 0;
|
|
43
|
+
const tenant = tenantOf(context);
|
|
44
|
+
if (tenant !== void 0) return tenant;
|
|
45
|
+
const paths = typeof claim === "string" ? [claim] : claim ?? tenantClaimPaths();
|
|
46
|
+
for (const path of paths) {
|
|
47
|
+
const value = claimAt(context.claims, path);
|
|
48
|
+
if (value !== void 0) return value;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The guard for a client of bucket `bucket` whose template holds the tenant
|
|
53
|
+
* in `param`, or `undefined` when nothing is checked (no tenant config,
|
|
54
|
+
* `allTenants`, or an all-tenants context).
|
|
55
|
+
*/
|
|
56
|
+
function tenantGuard(bucket, param, claim, match, options) {
|
|
57
|
+
const { context } = options;
|
|
58
|
+
if (param === void 0 || options.allTenants === true || context && spansAllTenants(context)) return;
|
|
59
|
+
const tenant = resolveTenant(options, claim);
|
|
60
|
+
const tenantOrThrow = () => {
|
|
61
|
+
if (tenant !== void 0) return tenant;
|
|
62
|
+
throw forbidden(bucket, `No tenant for bucket "${bucket}": pass { context } or { tenant } to connect(), or { allTenants: true }`);
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
param,
|
|
66
|
+
path(path) {
|
|
67
|
+
const owner = tenantOrThrow();
|
|
68
|
+
if (String(match(path)?.[param]) !== owner) throw forbidden(bucket, `Path "${path}" belongs to another tenant`);
|
|
69
|
+
return path;
|
|
70
|
+
},
|
|
71
|
+
within(within) {
|
|
72
|
+
const owner = tenantOrThrow();
|
|
73
|
+
const given = within?.[param];
|
|
74
|
+
if (given !== void 0 && String(given) !== owner) throw forbidden(bucket, "Listing belongs to another tenant");
|
|
75
|
+
return owner;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
7
80
|
//#region src/storage/bucket.ts
|
|
8
81
|
/** Storage operations that list objects; every other `select` is a read. */
|
|
9
82
|
const LIST_OPERATIONS = [
|
|
@@ -123,7 +196,10 @@ function defineBucket(config) {
|
|
|
123
196
|
if (index === void 0) throw new TypeError(`defineBucket: a ${kind} policy needs {${param}} as a whole path segment in "${config.path}"`);
|
|
124
197
|
return index;
|
|
125
198
|
}
|
|
126
|
-
const
|
|
199
|
+
const tenantParam = config.tenant ? config.tenant.param ?? "orgId" : void 0;
|
|
200
|
+
if (tenantParam !== void 0 && !template.params.includes(tenantParam)) throw new TypeError(`defineBucket: tenant.param {${tenantParam}} is not in "${config.path}"`);
|
|
201
|
+
const tenantClaim = config.tenant?.claim;
|
|
202
|
+
const mode = typeof policy === "string" ? policy : "permdock" in policy ? "permdock" : "access";
|
|
127
203
|
const accessCheck = (() => {
|
|
128
204
|
switch (mode) {
|
|
129
205
|
case "tenant": {
|
|
@@ -137,23 +213,12 @@ function defineBucket(config) {
|
|
|
137
213
|
}
|
|
138
214
|
case "public":
|
|
139
215
|
case "none":
|
|
140
|
-
case "permdock":
|
|
216
|
+
case "permdock":
|
|
217
|
+
case "access": return;
|
|
141
218
|
default: throw new TypeError(`defineBucket: unknown policy "${String(mode)}"`);
|
|
142
219
|
}
|
|
143
220
|
})();
|
|
144
|
-
const permdock = (() =>
|
|
145
|
-
if (typeof policy === "string") return void 0;
|
|
146
|
-
const where = `defineBucket(${config.id})`;
|
|
147
|
-
const id = policy.scope === "global" ? void 0 : `split_part(name, '/', ${String(policy.segment ?? segmentFor(config.tenant?.param ?? "orgId", "PermDock"))})`;
|
|
148
|
-
const check = (key) => permdockCheck(where, policy, key, id, config.catalog);
|
|
149
|
-
const keys = policy.permdock;
|
|
150
|
-
return {
|
|
151
|
-
read: check(keys.read),
|
|
152
|
-
...keys.list === void 0 ? {} : { list: check(keys.list) },
|
|
153
|
-
write: check(keys.write),
|
|
154
|
-
delete: check(keys.delete ?? keys.write)
|
|
155
|
-
};
|
|
156
|
-
})();
|
|
221
|
+
const permdock = policyChecks(config, policy, (kind) => segmentFor(config.tenant?.param ?? "orgId", kind));
|
|
157
222
|
const resolve = (target) => {
|
|
158
223
|
if (typeof target !== "string") return template.build(target);
|
|
159
224
|
if (!template.match(target)) throw new DbException(dbError("invalid_input", `Path "${target}" does not match "${config.path}"`));
|
|
@@ -168,6 +233,7 @@ function defineBucket(config) {
|
|
|
168
233
|
template: config.path,
|
|
169
234
|
params: template.params,
|
|
170
235
|
owner: policy === "owner" ? config.owner?.param ?? "userId" : template.params.includes("userId") ? "userId" : void 0,
|
|
236
|
+
tenant: tenantParam,
|
|
171
237
|
public: config.public ?? false,
|
|
172
238
|
policy,
|
|
173
239
|
fileSizeLimit,
|
|
@@ -217,7 +283,8 @@ function defineBucket(config) {
|
|
|
217
283
|
using,
|
|
218
284
|
void 0
|
|
219
285
|
]);
|
|
220
|
-
} else if (policy === "public")
|
|
286
|
+
} else if (policy === "public" && bucket.public) lines.push("-- Public URLs serve objects without a policy; a select policy would also let anyone list them.");
|
|
287
|
+
else if (policy === "public") policies.push([
|
|
221
288
|
"select",
|
|
222
289
|
"select",
|
|
223
290
|
"anon, authenticated",
|
|
@@ -309,7 +376,7 @@ function defineBucket(config) {
|
|
|
309
376
|
});
|
|
310
377
|
return issues;
|
|
311
378
|
},
|
|
312
|
-
connect: (client, options) => connectBucket(bucket, client, resolve, options)
|
|
379
|
+
connect: (client, options = {}) => connectBucket(bucket, client, resolve, options, tenantGuard(bucket.id, tenantParam, tenantClaim, (path) => bucket.match(path), options))
|
|
313
380
|
};
|
|
314
381
|
return bucket;
|
|
315
382
|
}
|
|
@@ -328,7 +395,15 @@ function ttlSeconds(ttl) {
|
|
|
328
395
|
function isErrorResult(value) {
|
|
329
396
|
return typeof value === "object" && value !== null && value.ok === false && "error" in value;
|
|
330
397
|
}
|
|
331
|
-
function connectBucket(bucket, client,
|
|
398
|
+
function connectBucket(bucket, client, resolveTarget, connectOptions, guard) {
|
|
399
|
+
const resolve = (target) => {
|
|
400
|
+
const path = resolveTarget(target);
|
|
401
|
+
return guard ? guard.path(path) : path;
|
|
402
|
+
};
|
|
403
|
+
const scopedWithin = (within) => guard ? {
|
|
404
|
+
...within,
|
|
405
|
+
[guard.param]: guard.within(within)
|
|
406
|
+
} : within ?? {};
|
|
332
407
|
const api = () => client.storage.from(bucket.id);
|
|
333
408
|
const signed = connectOptions.cacheSignedUrls ? /* @__PURE__ */ new Map() : void 0;
|
|
334
409
|
const run = (fn) => AsyncResult.from(async () => {
|
|
@@ -391,7 +466,7 @@ function connectBucket(bucket, client, resolve, connectOptions = {}) {
|
|
|
391
466
|
};
|
|
392
467
|
const list = (within, options) => AsyncResult.from(async () => {
|
|
393
468
|
const out = [];
|
|
394
|
-
await walk(bucket.prefix(within
|
|
469
|
+
await walk(bucket.prefix(scopedWithin(within)), options?.signal, out);
|
|
395
470
|
return ok(out);
|
|
396
471
|
}).mapError((error) => ({
|
|
397
472
|
...error,
|
|
@@ -423,6 +498,13 @@ function connectBucket(bucket, client, resolve, connectOptions = {}) {
|
|
|
423
498
|
});
|
|
424
499
|
return {
|
|
425
500
|
bucket,
|
|
501
|
+
path: (target) => {
|
|
502
|
+
try {
|
|
503
|
+
return ok(resolve(target));
|
|
504
|
+
} catch (cause) {
|
|
505
|
+
return err(toDbError(cause));
|
|
506
|
+
}
|
|
507
|
+
},
|
|
426
508
|
upload,
|
|
427
509
|
download: (target, options) => AsyncResult.from(async () => {
|
|
428
510
|
const path = resolve(target);
|
|
@@ -546,4 +628,51 @@ function connectBucket(bucket, client, resolve, connectOptions = {}) {
|
|
|
546
628
|
};
|
|
547
629
|
}
|
|
548
630
|
//#endregion
|
|
549
|
-
|
|
631
|
+
//#region src/storage/presets.ts
|
|
632
|
+
/** The image types browsers display everywhere. */
|
|
633
|
+
const IMAGE_TYPES = [
|
|
634
|
+
"image/png",
|
|
635
|
+
"image/jpeg",
|
|
636
|
+
"image/webp",
|
|
637
|
+
"image/gif",
|
|
638
|
+
"image/avif"
|
|
639
|
+
];
|
|
640
|
+
const AVATAR_PATH = "{userId}/avatar-{version}.{ext}";
|
|
641
|
+
const LOGO_PATH = "{orgId}/logo-{version}.{ext}";
|
|
642
|
+
/**
|
|
643
|
+
* A bucket for profile pictures: `{userId}/avatar-{version}.{ext}`, public,
|
|
644
|
+
* 2 MiB of images, written only by their owner (the `owner` policy).
|
|
645
|
+
*/
|
|
646
|
+
function avatarBucket(options = {}) {
|
|
647
|
+
return defineBucket({
|
|
648
|
+
id: options.id ?? "avatars",
|
|
649
|
+
path: options.path ?? AVATAR_PATH,
|
|
650
|
+
public: options.public ?? true,
|
|
651
|
+
fileSizeLimit: options.fileSizeLimit ?? "2MiB",
|
|
652
|
+
allowedMimeTypes: options.allowedMimeTypes ?? IMAGE_TYPES,
|
|
653
|
+
policy: options.policy ?? "owner"
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* A bucket for organization logos: `{orgId}/logo-{version}.{ext}`, public,
|
|
658
|
+
* 2 MiB of images, written by members with the update permission through
|
|
659
|
+
* the access contract (`tenant_ids_with`), so it follows the app's roles.
|
|
660
|
+
*/
|
|
661
|
+
function orgLogoBucket(options = {}) {
|
|
662
|
+
const permission = options.permission ?? "organization.update";
|
|
663
|
+
const tenant = options.tenant === false ? void 0 : options.tenant ?? { param: "orgId" };
|
|
664
|
+
return defineBucket({
|
|
665
|
+
id: options.id ?? "organization-logos",
|
|
666
|
+
path: options.path ?? LOGO_PATH,
|
|
667
|
+
public: options.public ?? true,
|
|
668
|
+
fileSizeLimit: options.fileSizeLimit ?? "2MiB",
|
|
669
|
+
allowedMimeTypes: options.allowedMimeTypes ?? IMAGE_TYPES,
|
|
670
|
+
policy: options.policy ?? { access: {
|
|
671
|
+
read: permission,
|
|
672
|
+
write: permission
|
|
673
|
+
} },
|
|
674
|
+
...tenant ? { tenant } : {}
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
//#endregion
|
|
678
|
+
export { IMAGE_TYPES, TTL, avatarBucket, defineBucket, fromStorageError, orgLogoBucket, parseSize };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/// <reference lib="esnext.temporal" />
|
|
2
|
+
import { t as BetterPostgres } from "./pool-C9voM4Kw.js";
|
|
3
|
+
//#region src/auth/support.d.ts
|
|
4
|
+
type SupportEndedBy = "admin" | "expired" | "revoked";
|
|
5
|
+
/** A support session: a platform admin viewing the app as `targetUserId`. */
|
|
6
|
+
interface SupportSession {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly adminId: string;
|
|
9
|
+
readonly targetUserId: string;
|
|
10
|
+
readonly reason: string;
|
|
11
|
+
/** Writes fail while it is true (`begin read only`). */
|
|
12
|
+
readonly readOnly: boolean;
|
|
13
|
+
readonly tenant?: string;
|
|
14
|
+
readonly startedAt: Temporal.Instant;
|
|
15
|
+
readonly expiresAt: Temporal.Instant;
|
|
16
|
+
readonly endedAt?: Temporal.Instant;
|
|
17
|
+
readonly endedBy?: SupportEndedBy;
|
|
18
|
+
/** App data, such as a ticket id. */
|
|
19
|
+
readonly metadata: Readonly<Record<string, unknown>>;
|
|
20
|
+
}
|
|
21
|
+
interface SupportStartInput {
|
|
22
|
+
readonly adminId: string;
|
|
23
|
+
/**
|
|
24
|
+
* The admin's verified claims. The SQL store runs `start_support_session`
|
|
25
|
+
* with them, so `is_platform()` checks the admin in the database too.
|
|
26
|
+
*/
|
|
27
|
+
readonly adminClaims: Readonly<Record<string, unknown>>;
|
|
28
|
+
readonly targetUserId: string;
|
|
29
|
+
readonly reason: string;
|
|
30
|
+
readonly ttlSeconds: number;
|
|
31
|
+
readonly readOnly: boolean;
|
|
32
|
+
readonly tenant?: string;
|
|
33
|
+
readonly metadata: Readonly<Record<string, unknown>>;
|
|
34
|
+
}
|
|
35
|
+
/** A caller's verified claims, which the store runs a call with. */
|
|
36
|
+
type SupportCallerClaims = Readonly<Record<string, unknown>>;
|
|
37
|
+
interface SupportListFilter {
|
|
38
|
+
readonly adminId?: string;
|
|
39
|
+
readonly targetUserId?: string;
|
|
40
|
+
/** `true`: running sessions only; `false`: ended or expired ones only. */
|
|
41
|
+
readonly active?: boolean;
|
|
42
|
+
/** Defaults to 50. */
|
|
43
|
+
readonly limit?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Where support sessions live. `sqlSupportStore` uses the `support-sessions`
|
|
47
|
+
* SQL kit module; implement this for another store and check it with
|
|
48
|
+
* `testSupportSessionStore` from `better-supabase/testing`.
|
|
49
|
+
*/
|
|
50
|
+
interface SupportSessionStore {
|
|
51
|
+
readonly apiVersion: 1;
|
|
52
|
+
readonly name: string;
|
|
53
|
+
/** Starts a session and ends the admin's previous one. Rejects when the store refuses. */
|
|
54
|
+
start(input: SupportStartInput): Promise<SupportSession>;
|
|
55
|
+
/**
|
|
56
|
+
* The admin's session while it is running; `undefined` once ended or
|
|
57
|
+
* expired. With the admin's `claims`, the store also checks that the admin
|
|
58
|
+
* may still start sessions.
|
|
59
|
+
*/
|
|
60
|
+
get(sessionId: string, adminId: string, claims?: SupportCallerClaims): Promise<SupportSession | undefined>;
|
|
61
|
+
/**
|
|
62
|
+
* `false` when it was not running. With `claims`, it runs as that caller:
|
|
63
|
+
* the session's admin, or staff with the revoke permission.
|
|
64
|
+
*/
|
|
65
|
+
end(sessionId: string, endedBy: SupportEndedBy, claims?: SupportCallerClaims): Promise<boolean>;
|
|
66
|
+
/** With `claims`, only what that caller may view. */
|
|
67
|
+
list(filter?: SupportListFilter, claims?: SupportCallerClaims): Promise<readonly SupportSession[]>;
|
|
68
|
+
/** The claims `targetUserId` would get, when the store can build them. */
|
|
69
|
+
claims?(targetUserId: string): Promise<Readonly<Record<string, unknown>>>;
|
|
70
|
+
}
|
|
71
|
+
interface SqlSupportStoreOptions {
|
|
72
|
+
/** `kits.support-sessions.schema`. Defaults to `better_supabase`. */
|
|
73
|
+
readonly schema?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Support sessions in the `support-sessions` SQL kit module. `start` runs
|
|
77
|
+
* as the admin, so the module's `is_platform()` gate applies; the reads run
|
|
78
|
+
* on the admin connection.
|
|
79
|
+
*/
|
|
80
|
+
declare function sqlSupportStore(postgres: Pick<BetterPostgres, "admin" | "asUser">, options?: SqlSupportStoreOptions): SupportSessionStore;
|
|
81
|
+
/**
|
|
82
|
+
* The claims a support session runs with: the target's, plus `act` naming
|
|
83
|
+
* the admin, the reason and the session (the audit module records all three),
|
|
84
|
+
* marked `kind: "support"` so `actorOf` never reads it as an OAuth client.
|
|
85
|
+
* `role` falls back to `authenticated`; the admin's claims never leak in.
|
|
86
|
+
*/
|
|
87
|
+
declare function supportClaims(session: Pick<SupportSession, "id" | "adminId" | "targetUserId" | "reason" | "readOnly">, target: Readonly<Record<string, unknown>>): Record<string, unknown>;
|
|
88
|
+
//#endregion
|
|
89
|
+
export { SupportSession as a, sqlSupportStore as c, SupportListFilter as i, supportClaims as l, SupportCallerClaims as n, SupportSessionStore as o, SupportEndedBy as r, SupportStartInput as s, SqlSupportStoreOptions as t };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { t as AuthSession } from "./view-CbgObAeb.js";
|
|
2
|
+
//#region src/auth/support-view.d.ts
|
|
3
|
+
/** A running support session as the target's view shows it, for a banner. */
|
|
4
|
+
interface SupportView {
|
|
5
|
+
readonly sessionId: string;
|
|
6
|
+
/** The admin viewing the app. */
|
|
7
|
+
readonly adminId: string;
|
|
8
|
+
readonly targetUserId: string;
|
|
9
|
+
readonly reason?: string;
|
|
10
|
+
readonly readOnly: boolean;
|
|
11
|
+
/** Seconds since epoch. */
|
|
12
|
+
readonly expiresAt: number | null;
|
|
13
|
+
}
|
|
14
|
+
/** The support session behind `session`, or `undefined` outside one. */
|
|
15
|
+
declare function supportOf(session: AuthSession): SupportView | undefined;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { supportOf as n, SupportView as t };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/auth/support-view.ts
|
|
2
|
+
/** The support session behind `session`, or `undefined` outside one. */
|
|
3
|
+
function supportOf(session) {
|
|
4
|
+
if (session.kind !== "user") return;
|
|
5
|
+
const { impersonator } = session;
|
|
6
|
+
const sessionId = impersonator?.sessionId;
|
|
7
|
+
if (!impersonator || !sessionId) return;
|
|
8
|
+
return {
|
|
9
|
+
sessionId,
|
|
10
|
+
adminId: impersonator.id,
|
|
11
|
+
targetUserId: session.user.id,
|
|
12
|
+
...impersonator.reason ? { reason: impersonator.reason } : {},
|
|
13
|
+
readOnly: impersonator.readOnly ?? true,
|
|
14
|
+
expiresAt: session.expiresAt
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { supportOf as t };
|