@selvajs/supabase-provider 0.17.0-beta.4 → 0.18.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 +26 -39
- package/dist/auth/SupabaseAuthProvider.d.ts +37 -82
- package/dist/auth/SupabaseAuthProvider.d.ts.map +1 -1
- package/dist/auth/SupabaseAuthProvider.js +62 -114
- package/dist/auth/SupabaseAuthProvider.js.map +1 -1
- package/dist/cli/sync-migrations.js +4 -6
- package/dist/cli/sync-migrations.js.map +1 -1
- package/dist/data/SupabaseAuditQuery.d.ts +3 -3
- package/dist/data/SupabaseAuditQuery.d.ts.map +1 -1
- package/dist/data/SupabaseAuditQuery.js +8 -14
- package/dist/data/SupabaseAuditQuery.js.map +1 -1
- package/dist/data/SupabaseComputeServerStore.d.ts +7 -13
- package/dist/data/SupabaseComputeServerStore.d.ts.map +1 -1
- package/dist/data/SupabaseComputeServerStore.js +15 -39
- package/dist/data/SupabaseComputeServerStore.js.map +1 -1
- package/dist/data/SupabaseDataProvider.d.ts +30 -46
- package/dist/data/SupabaseDataProvider.d.ts.map +1 -1
- package/dist/data/SupabaseDataProvider.js +39 -66
- package/dist/data/SupabaseDataProvider.js.map +1 -1
- package/dist/data/SupabaseDefinitionStore.d.ts +3 -10
- package/dist/data/SupabaseDefinitionStore.d.ts.map +1 -1
- package/dist/data/SupabaseDefinitionStore.js +29 -39
- package/dist/data/SupabaseDefinitionStore.js.map +1 -1
- package/dist/data/SupabaseInviteStore.d.ts +6 -6
- package/dist/data/SupabaseInviteStore.d.ts.map +1 -1
- package/dist/data/SupabaseInviteStore.js +16 -15
- package/dist/data/SupabaseInviteStore.js.map +1 -1
- package/dist/data/SupabaseOrgStore.d.ts +0 -4
- package/dist/data/SupabaseOrgStore.d.ts.map +1 -1
- package/dist/data/SupabaseOrgStore.js +19 -29
- package/dist/data/SupabaseOrgStore.js.map +1 -1
- package/dist/data/SupabaseProjectStore.d.ts +4 -8
- package/dist/data/SupabaseProjectStore.d.ts.map +1 -1
- package/dist/data/SupabaseProjectStore.js +15 -26
- package/dist/data/SupabaseProjectStore.js.map +1 -1
- package/dist/data/SupabaseShareLinkStore.d.ts +4 -9
- package/dist/data/SupabaseShareLinkStore.d.ts.map +1 -1
- package/dist/data/SupabaseShareLinkStore.js +9 -18
- package/dist/data/SupabaseShareLinkStore.js.map +1 -1
- package/dist/data/SupabaseSolveMetricSink.d.ts +13 -14
- package/dist/data/SupabaseSolveMetricSink.d.ts.map +1 -1
- package/dist/data/SupabaseSolveMetricSink.js +13 -14
- package/dist/data/SupabaseSolveMetricSink.js.map +1 -1
- package/dist/data/client.d.ts +25 -54
- package/dist/data/client.d.ts.map +1 -1
- package/dist/data/client.js +6 -14
- package/dist/data/client.js.map +1 -1
- package/dist/data/errors.d.ts.map +1 -1
- package/dist/data/errors.js.map +1 -1
- package/dist/data/migrationHead.d.ts +4 -11
- package/dist/data/migrationHead.d.ts.map +1 -1
- package/dist/data/migrationHead.js +4 -11
- package/dist/data/migrationHead.js.map +1 -1
- package/dist/data/pagination.d.ts +5 -16
- package/dist/data/pagination.d.ts.map +1 -1
- package/dist/data/pagination.js +5 -16
- package/dist/data/pagination.js.map +1 -1
- package/dist/data/query.d.ts +2 -14
- package/dist/data/query.d.ts.map +1 -1
- package/dist/data/query.js +1 -9
- package/dist/data/query.js.map +1 -1
- package/dist/data/rowStamp.d.ts +8 -24
- package/dist/data/rowStamp.d.ts.map +1 -1
- package/dist/data/rowStamp.js +8 -24
- package/dist/data/rowStamp.js.map +1 -1
- package/dist/permissions/SupabasePlatformPermissionStore.d.ts +8 -10
- package/dist/permissions/SupabasePlatformPermissionStore.d.ts.map +1 -1
- package/dist/permissions/SupabasePlatformPermissionStore.js +9 -15
- package/dist/permissions/SupabasePlatformPermissionStore.js.map +1 -1
- package/dist/storage/SupabaseStorageProvider.d.ts +14 -35
- package/dist/storage/SupabaseStorageProvider.d.ts.map +1 -1
- package/dist/storage/SupabaseStorageProvider.js +10 -25
- package/dist/storage/SupabaseStorageProvider.js.map +1 -1
- package/dist/userProfile/SupabaseUserProfileProvider.d.ts.map +1 -1
- package/dist/userProfile/SupabaseUserProfileProvider.js +8 -15
- package/dist/userProfile/SupabaseUserProfileProvider.js.map +1 -1
- package/package.json +6 -6
- package/supabase/migrations/20260817120000_invite_platform_permissions.sql +16 -0
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Compared against `selva.migration_head()` (a SECURITY DEFINER function over
|
|
6
|
-
* `supabase_migrations.schema_migrations`) in
|
|
7
|
-
* `SupabaseDataProvider.verifySchemaVersion` to drive the boot-time app↔DB
|
|
8
|
-
* schema handshake (audit O3).
|
|
9
|
-
*
|
|
10
|
-
* MUST be bumped whenever a migration is added — `migration-head.test.ts`
|
|
11
|
-
* asserts this constant matches the migrations directory, so CI catches drift.
|
|
2
|
+
* Timestamp prefix of the newest file in `supabase/migrations/`. Bump this
|
|
3
|
+
* whenever a migration is added — `migration-head.test.ts` asserts it
|
|
4
|
+
* matches the migrations directory, so CI catches drift.
|
|
12
5
|
*/
|
|
13
|
-
export const EXPECTED_MIGRATION_HEAD = '
|
|
6
|
+
export const EXPECTED_MIGRATION_HEAD = '20260817120000';
|
|
14
7
|
//# sourceMappingURL=migrationHead.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrationHead.js","sourceRoot":"","sources":["../../src/data/migrationHead.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"migrationHead.js","sourceRoot":"","sources":["../../src/data/migrationHead.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,gBAAgB,CAAC"}
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* an offset as the cursor — simple, correct for stable sorts, and good
|
|
7
|
-
* enough for the volumes Selva deals with (admin/definition lists are
|
|
8
|
-
* bounded; nothing is infinite scroll over millions of rows).
|
|
9
|
-
*
|
|
10
|
-
* Keyset pagination would be faster at scale if it ever matters.
|
|
2
|
+
* `ListOptions.cursor` is meant to be opaque; here it's just an offset
|
|
3
|
+
* encoded as a string. Simple and correct for stable sorts, fine at Selva's
|
|
4
|
+
* scale (bounded admin/definition lists, not infinite scroll over millions
|
|
5
|
+
* of rows). Keyset pagination would be faster if that ever changes.
|
|
11
6
|
*/
|
|
12
7
|
import type { ListOptions } from '@selvajs/platform';
|
|
13
8
|
export interface RangeSpec {
|
|
@@ -15,16 +10,10 @@ export interface RangeSpec {
|
|
|
15
10
|
to: number;
|
|
16
11
|
limit: number;
|
|
17
12
|
}
|
|
18
|
-
/**
|
|
13
|
+
/** Returns 0 for a missing or invalid cursor. */
|
|
19
14
|
export declare function decodeCursor(cursor: string | undefined): number;
|
|
20
15
|
export declare function encodeCursor(offset: number): string;
|
|
21
|
-
/** Clamp limit + compute the PostgREST `range(from, to)` bounds. */
|
|
22
16
|
export declare function toRange(opts: Pick<ListOptions, 'limit' | 'cursor'> | undefined): RangeSpec;
|
|
23
|
-
/**
|
|
24
|
-
* Build the `nextCursor` from the count returned by PostgREST. Returns
|
|
25
|
-
* undefined when there is no next page.
|
|
26
|
-
*/
|
|
27
17
|
export declare function nextCursorFromRange(range: RangeSpec, returnedCount: number, totalCount: number | null | undefined): string | undefined;
|
|
28
|
-
/** Map `ListOptions.orderBy` to a Postgres column name. */
|
|
29
18
|
export declare function orderColumn(orderBy: ListOptions['orderBy'] | undefined): string;
|
|
30
19
|
//# sourceMappingURL=pagination.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../../src/data/pagination.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../../src/data/pagination.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACd;AAED,iDAAiD;AACjD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAI/D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,QAAQ,CAAC,GAAG,SAAS,GAAG,SAAS,CAI1F;AAED,wBAAgB,mBAAmB,CAClC,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACnC,MAAM,GAAG,SAAS,CAOpB;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,GAAG,SAAS,GAAG,MAAM,CAU/E"}
|
package/dist/data/pagination.js
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* an offset as the cursor — simple, correct for stable sorts, and good
|
|
7
|
-
* enough for the volumes Selva deals with (admin/definition lists are
|
|
8
|
-
* bounded; nothing is infinite scroll over millions of rows).
|
|
9
|
-
*
|
|
10
|
-
* Keyset pagination would be faster at scale if it ever matters.
|
|
2
|
+
* `ListOptions.cursor` is meant to be opaque; here it's just an offset
|
|
3
|
+
* encoded as a string. Simple and correct for stable sorts, fine at Selva's
|
|
4
|
+
* scale (bounded admin/definition lists, not infinite scroll over millions
|
|
5
|
+
* of rows). Keyset pagination would be faster if that ever changes.
|
|
11
6
|
*/
|
|
12
7
|
import { DEFAULT_PAGE_LIMIT, MAX_PAGE_LIMIT } from '@selvajs/platform';
|
|
13
|
-
/**
|
|
8
|
+
/** Returns 0 for a missing or invalid cursor. */
|
|
14
9
|
export function decodeCursor(cursor) {
|
|
15
10
|
if (!cursor)
|
|
16
11
|
return 0;
|
|
@@ -20,16 +15,11 @@ export function decodeCursor(cursor) {
|
|
|
20
15
|
export function encodeCursor(offset) {
|
|
21
16
|
return String(offset);
|
|
22
17
|
}
|
|
23
|
-
/** Clamp limit + compute the PostgREST `range(from, to)` bounds. */
|
|
24
18
|
export function toRange(opts) {
|
|
25
19
|
const limit = Math.min(Math.max(opts?.limit ?? DEFAULT_PAGE_LIMIT, 1), MAX_PAGE_LIMIT);
|
|
26
20
|
const from = decodeCursor(opts?.cursor);
|
|
27
21
|
return { from, to: from + limit - 1, limit };
|
|
28
22
|
}
|
|
29
|
-
/**
|
|
30
|
-
* Build the `nextCursor` from the count returned by PostgREST. Returns
|
|
31
|
-
* undefined when there is no next page.
|
|
32
|
-
*/
|
|
33
23
|
export function nextCursorFromRange(range, returnedCount, totalCount) {
|
|
34
24
|
const consumed = range.from + returnedCount;
|
|
35
25
|
if (totalCount != null) {
|
|
@@ -38,7 +28,6 @@ export function nextCursorFromRange(range, returnedCount, totalCount) {
|
|
|
38
28
|
// Without total, we can only infer "there might be more" if we got a full page.
|
|
39
29
|
return returnedCount >= range.limit ? encodeCursor(consumed) : undefined;
|
|
40
30
|
}
|
|
41
|
-
/** Map `ListOptions.orderBy` to a Postgres column name. */
|
|
42
31
|
export function orderColumn(orderBy) {
|
|
43
32
|
switch (orderBy) {
|
|
44
33
|
case 'name':
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pagination.js","sourceRoot":"","sources":["../../src/data/pagination.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"pagination.js","sourceRoot":"","sources":["../../src/data/pagination.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AASvE,iDAAiD;AACjD,MAAM,UAAU,YAAY,CAAC,MAA0B;IACtD,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IACtB,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC/B,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc;IAC1C,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,IAAuD;IAC9E,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,IAAI,kBAAkB,EAAE,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;IACvF,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,GAAG,KAAK,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,mBAAmB,CAClC,KAAgB,EAChB,aAAqB,EACrB,UAAqC;IAErC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5C,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;QACxB,OAAO,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACnE,CAAC;IACD,gFAAgF;IAChF,OAAO,aAAa,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAA2C;IACtE,QAAQ,OAAO,EAAE,CAAC;QACjB,KAAK,MAAM;YACV,OAAO,MAAM,CAAC;QACf,KAAK,WAAW;YACf,OAAO,YAAY,CAAC;QACrB,KAAK,WAAW,CAAC;QACjB;YACC,OAAO,YAAY,CAAC;IACtB,CAAC;AACF,CAAC"}
|
package/dist/data/query.d.ts
CHANGED
|
@@ -1,20 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Small query-builder helpers shared across Supabase stores.
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* The slice of a PostgREST filter builder these helpers touch: an `.is()`
|
|
6
|
-
* method that returns the same builder for chaining. Generic over the builder
|
|
7
|
-
* type so the caller's concrete builder (and its row generic) flows through.
|
|
8
|
-
*/
|
|
1
|
+
/** Minimal PostgREST filter-builder slice these helpers need, generic so the caller's row type flows through. */
|
|
9
2
|
interface DeletableQuery {
|
|
10
3
|
is(column: string, value: null): this;
|
|
11
4
|
}
|
|
12
|
-
/**
|
|
13
|
-
* Apply the soft-delete filter `deleted_at IS NULL`. The `.is('deleted_at', null)`
|
|
14
|
-
* idiom is repeated across every store that soft-deletes; this names it.
|
|
15
|
-
*
|
|
16
|
-
* let q = notDeleted(client.from('projects').select(COLS));
|
|
17
|
-
*/
|
|
5
|
+
/** `deleted_at IS NULL`, named once instead of repeated across every soft-deleting store. */
|
|
18
6
|
export declare function notDeleted<Q extends DeletableQuery>(query: Q): Q;
|
|
19
7
|
export {};
|
|
20
8
|
//# sourceMappingURL=query.d.ts.map
|
package/dist/data/query.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/data/query.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/data/query.ts"],"names":[],"mappings":"AAAA,iHAAiH;AACjH,UAAU,cAAc;IACvB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,IAAI,CAAC;CACtC;AAED,6FAA6F;AAC7F,wBAAgB,UAAU,CAAC,CAAC,SAAS,cAAc,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAEhE"}
|
package/dist/data/query.js
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Small query-builder helpers shared across Supabase stores.
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* Apply the soft-delete filter `deleted_at IS NULL`. The `.is('deleted_at', null)`
|
|
6
|
-
* idiom is repeated across every store that soft-deletes; this names it.
|
|
7
|
-
*
|
|
8
|
-
* let q = notDeleted(client.from('projects').select(COLS));
|
|
9
|
-
*/
|
|
1
|
+
/** `deleted_at IS NULL`, named once instead of repeated across every soft-deleting store. */
|
|
10
2
|
export function notDeleted(query) {
|
|
11
3
|
return query.is('deleted_at', null);
|
|
12
4
|
}
|
package/dist/data/query.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/data/query.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/data/query.ts"],"names":[],"mappings":"AAKA,6FAA6F;AAC7F,MAAM,UAAU,UAAU,CAA2B,KAAQ;IAC5D,OAAO,KAAK,CAAC,EAAE,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC"}
|
package/dist/data/rowStamp.d.ts
CHANGED
|
@@ -1,35 +1,19 @@
|
|
|
1
1
|
import type { RequestContext } from '@selvajs/platform';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Translates platform's camelCase audit fields (`updatedAt`/`updatedBy`) to
|
|
4
|
+
* the snake_case columns every Supabase row uses.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* `updated_by` is written only when `ctx.userId` is set. On system contexts
|
|
7
|
+
* `auditUpdate`/`auditSoftDelete` fall back to `''`, and the column FKs
|
|
8
|
+
* `auth.users(id)` — writing `''` violates the FK — so it's omitted instead,
|
|
9
|
+
* leaving the existing value untouched.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* it's the empty string, and the column FKs `auth.users(id)` with
|
|
13
|
-
* `ON DELETE SET NULL` — writing `''` would violate the FK, so it's omitted
|
|
14
|
-
* and the existing value is left untouched.
|
|
15
|
-
* - Plain updates DON'T stamp `updated_at`: a per-table `updated_at` trigger
|
|
16
|
-
* already sets it. Only the soft-delete stamp writes `updated_at` explicitly,
|
|
17
|
-
* so it matches `deleted_at` (a single timestamp for the deletion event).
|
|
18
|
-
*/
|
|
19
|
-
/**
|
|
20
|
-
* Stamp for a plain UPDATE: `{ updated_by }` when the context carries a user,
|
|
21
|
-
* `{}` otherwise (the DB trigger handles `updated_at`). Spread into the row:
|
|
22
|
-
* `const row = { ...patch, ...stampUpdate(ctx) }`.
|
|
11
|
+
* Plain updates don't stamp `updated_at`: a per-table trigger already sets
|
|
12
|
+
* it. Only soft-delete sets `updated_at` explicitly, to match `deleted_at`.
|
|
23
13
|
*/
|
|
24
14
|
export declare function stampUpdate(ctx: RequestContext): {
|
|
25
15
|
updated_by?: string;
|
|
26
16
|
};
|
|
27
|
-
/**
|
|
28
|
-
* Stamp for a soft-delete: `{ deleted_at, updated_at, updated_by? }`. `updated_at`
|
|
29
|
-
* is set explicitly to equal `deleted_at`; `updated_by` follows the same
|
|
30
|
-
* omit-when-empty rule as {@link stampUpdate}. `fallbackActor` defaults to
|
|
31
|
-
* `ctx.userId` (matching the engine stores' `auditSoftDelete(ctx, ctx.userId)`).
|
|
32
|
-
*/
|
|
33
17
|
export declare function stampSoftDelete(ctx: RequestContext, fallbackActor?: string | null | undefined): {
|
|
34
18
|
deleted_at: string;
|
|
35
19
|
updated_at: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rowStamp.d.ts","sourceRoot":"","sources":["../../src/data/rowStamp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD
|
|
1
|
+
{"version":3,"file":"rowStamp.d.ts","sourceRoot":"","sources":["../../src/data/rowStamp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD;;;;;;;;;;;GAWG;AAEH,wBAAgB,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAExE;AAED,wBAAgB,eAAe,CAC9B,GAAG,EAAE,cAAc,EACnB,aAAa,GAAE,MAAM,GAAG,IAAI,GAAG,SAAsB,GACnD;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAQjE"}
|
package/dist/data/rowStamp.js
CHANGED
|
@@ -1,35 +1,19 @@
|
|
|
1
1
|
import { auditSoftDelete } from '@selvajs/platform';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Translates platform's camelCase audit fields (`updatedAt`/`updatedBy`) to
|
|
4
|
+
* the snake_case columns every Supabase row uses.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* `updated_by` is written only when `ctx.userId` is set. On system contexts
|
|
7
|
+
* `auditUpdate`/`auditSoftDelete` fall back to `''`, and the column FKs
|
|
8
|
+
* `auth.users(id)` — writing `''` violates the FK — so it's omitted instead,
|
|
9
|
+
* leaving the existing value untouched.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* it's the empty string, and the column FKs `auth.users(id)` with
|
|
13
|
-
* `ON DELETE SET NULL` — writing `''` would violate the FK, so it's omitted
|
|
14
|
-
* and the existing value is left untouched.
|
|
15
|
-
* - Plain updates DON'T stamp `updated_at`: a per-table `updated_at` trigger
|
|
16
|
-
* already sets it. Only the soft-delete stamp writes `updated_at` explicitly,
|
|
17
|
-
* so it matches `deleted_at` (a single timestamp for the deletion event).
|
|
18
|
-
*/
|
|
19
|
-
/**
|
|
20
|
-
* Stamp for a plain UPDATE: `{ updated_by }` when the context carries a user,
|
|
21
|
-
* `{}` otherwise (the DB trigger handles `updated_at`). Spread into the row:
|
|
22
|
-
* `const row = { ...patch, ...stampUpdate(ctx) }`.
|
|
11
|
+
* Plain updates don't stamp `updated_at`: a per-table trigger already sets
|
|
12
|
+
* it. Only soft-delete sets `updated_at` explicitly, to match `deleted_at`.
|
|
23
13
|
*/
|
|
24
14
|
export function stampUpdate(ctx) {
|
|
25
15
|
return ctx.userId ? { updated_by: ctx.userId } : {};
|
|
26
16
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Stamp for a soft-delete: `{ deleted_at, updated_at, updated_by? }`. `updated_at`
|
|
29
|
-
* is set explicitly to equal `deleted_at`; `updated_by` follows the same
|
|
30
|
-
* omit-when-empty rule as {@link stampUpdate}. `fallbackActor` defaults to
|
|
31
|
-
* `ctx.userId` (matching the engine stores' `auditSoftDelete(ctx, ctx.userId)`).
|
|
32
|
-
*/
|
|
33
17
|
export function stampSoftDelete(ctx, fallbackActor = ctx.userId) {
|
|
34
18
|
const stamp = auditSoftDelete(ctx, fallbackActor);
|
|
35
19
|
const row = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rowStamp.js","sourceRoot":"","sources":["../../src/data/rowStamp.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD
|
|
1
|
+
{"version":3,"file":"rowStamp.js","sourceRoot":"","sources":["../../src/data/rowStamp.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;;;;;;;GAWG;AAEH,MAAM,UAAU,WAAW,CAAC,GAAmB;IAC9C,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,eAAe,CAC9B,GAAmB,EACnB,gBAA2C,GAAG,CAAC,MAAM;IAErD,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IAClD,MAAM,GAAG,GAAoE;QAC5E,UAAU,EAAE,KAAK,CAAC,SAAS;QAC3B,UAAU,EAAE,KAAK,CAAC,SAAS;KAC3B,CAAC;IACF,IAAI,KAAK,CAAC,SAAS;QAAE,GAAG,CAAC,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC;IACtD,OAAO,GAAG,CAAC;AACZ,CAAC"}
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import type { IPlatformPermissionStore, PlatformPermission, RequestContext, UserManagementResult } from '@selvajs/platform';
|
|
2
2
|
import type { ClientBundle } from '../data/client.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* `
|
|
6
|
-
* `
|
|
7
|
-
* `
|
|
8
|
-
* since RLS on `user_profiles` is coarse-grained for cross-user reads.
|
|
4
|
+
* Reads and writes `public.user_profiles.platform_permissions` (auto-seeded
|
|
5
|
+
* by the `handle_new_auth_user` trigger). Service-role throughout — RLS on
|
|
6
|
+
* `user_profiles` is too coarse for cross-user reads, so `assertAdmin` /
|
|
7
|
+
* `assertCanRead` enforce the auth boundary here instead.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* semantics) instead of cross-referencing the auth admin API per candidate.
|
|
9
|
+
* `user_profiles.disabled` mirrors `auth.users.user_metadata.disabled` via
|
|
10
|
+
* the `sync_auth_user_disabled` trigger, so admin-count queries can exclude
|
|
11
|
+
* disabled admins with a single indexed query instead of cross-referencing
|
|
12
|
+
* the auth admin API per candidate.
|
|
15
13
|
*/
|
|
16
14
|
export declare class SupabasePlatformPermissionStore implements IPlatformPermissionStore {
|
|
17
15
|
private readonly clients;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SupabasePlatformPermissionStore.d.ts","sourceRoot":"","sources":["../../src/permissions/SupabasePlatformPermissionStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,wBAAwB,EACxB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD
|
|
1
|
+
{"version":3,"file":"SupabasePlatformPermissionStore.d.ts","sourceRoot":"","sources":["../../src/permissions/SupabasePlatformPermissionStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,wBAAwB,EACxB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,qBAAa,+BAAgC,YAAW,wBAAwB;IACnE,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,YAAY;IAElD,OAAO,CAAC,MAAM;IAIR,MAAM,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAW1E,WAAW,CAChB,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,SAAS,MAAM,EAAE,GACxB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAevC,GAAG,CACR,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,SAAS,kBAAkB,EAAE,GACxC,OAAO,CAAC,oBAAoB,CAAC;IA0B1B,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;IAUxD,4BAA4B,CAAC,IAAI,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAIlF,uBAAuB;CAUrC"}
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { ProviderError, hasPermission, ALL_PLATFORM_PERMISSIONS } from '@selvajs/platform';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* `
|
|
5
|
-
* `
|
|
6
|
-
* `
|
|
7
|
-
* since RLS on `user_profiles` is coarse-grained for cross-user reads.
|
|
3
|
+
* Reads and writes `public.user_profiles.platform_permissions` (auto-seeded
|
|
4
|
+
* by the `handle_new_auth_user` trigger). Service-role throughout — RLS on
|
|
5
|
+
* `user_profiles` is too coarse for cross-user reads, so `assertAdmin` /
|
|
6
|
+
* `assertCanRead` enforce the auth boundary here instead.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* semantics) instead of cross-referencing the auth admin API per candidate.
|
|
8
|
+
* `user_profiles.disabled` mirrors `auth.users.user_metadata.disabled` via
|
|
9
|
+
* the `sync_auth_user_disabled` trigger, so admin-count queries can exclude
|
|
10
|
+
* disabled admins with a single indexed query instead of cross-referencing
|
|
11
|
+
* the auth admin API per candidate.
|
|
14
12
|
*/
|
|
15
13
|
export class SupabasePlatformPermissionStore {
|
|
16
14
|
clients;
|
|
@@ -49,9 +47,6 @@ export class SupabasePlatformPermissionStore {
|
|
|
49
47
|
}
|
|
50
48
|
async set(ctx, userId, permissions) {
|
|
51
49
|
assertAdmin(ctx);
|
|
52
|
-
// 1. Confirm the target exists in the auth backend (the FK + trigger
|
|
53
|
-
// means the user_profiles row should exist post-signup; if not,
|
|
54
|
-
// treat as not_found rather than auto-creating).
|
|
55
50
|
const { data: existing, error: fetchError } = await this.client()
|
|
56
51
|
.from('user_profiles')
|
|
57
52
|
.select('platform_permissions')
|
|
@@ -61,7 +56,7 @@ export class SupabasePlatformPermissionStore {
|
|
|
61
56
|
throw mapError(fetchError);
|
|
62
57
|
if (!existing)
|
|
63
58
|
return 'not_found';
|
|
64
|
-
//
|
|
59
|
+
// Refuse to drop the last enabled instance_admin.
|
|
65
60
|
const wasAdmin = (existing.platform_permissions ?? []).includes('instance_admin');
|
|
66
61
|
const willBeAdmin = permissions.includes('instance_admin');
|
|
67
62
|
if (wasAdmin && !willBeAdmin) {
|
|
@@ -69,7 +64,6 @@ export class SupabasePlatformPermissionStore {
|
|
|
69
64
|
if (others === 0)
|
|
70
65
|
return 'last_admin';
|
|
71
66
|
}
|
|
72
|
-
// 3. Apply.
|
|
73
67
|
const { error } = await this.client()
|
|
74
68
|
.from('user_profiles')
|
|
75
69
|
.update({ platform_permissions: [...permissions] })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SupabasePlatformPermissionStore.js","sourceRoot":"","sources":["../../src/permissions/SupabasePlatformPermissionStore.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAG3F
|
|
1
|
+
{"version":3,"file":"SupabasePlatformPermissionStore.js","sourceRoot":"","sources":["../../src/permissions/SupabasePlatformPermissionStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,39 +1,25 @@
|
|
|
1
1
|
import type { IStorageProvider } from '@selvajs/platform/storage';
|
|
2
2
|
/**
|
|
3
|
-
* Storage backend for Selva on Supabase Storage.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* - {privateBucket} (private): .gh / .ghx source files plus every asset
|
|
9
|
-
* class that isn't `public` (definition covers, org private files).
|
|
10
|
-
* `getPublicUrl` returns the authenticated proxy URL
|
|
11
|
-
* (`${privateUrlPrefix}/{path}`) — the consuming app must have a route at
|
|
12
|
-
* that prefix that authenticates the request and streams the bytes via
|
|
13
|
-
* `get()`.
|
|
14
|
-
*
|
|
15
|
-
* Routing is derived from the extension and `classifyAssetPath`, so callers
|
|
16
|
-
* building paths via `definitionPaths` never have to think about buckets.
|
|
3
|
+
* Storage backend for Selva on Supabase Storage. Routes each path to one of
|
|
4
|
+
* two buckets: `publicBucket` (CDN-readable, `getPublicUrl` returns the
|
|
5
|
+
* direct CDN URL) or `privateBucket` (`getPublicUrl` returns
|
|
6
|
+
* `${privateUrlPrefix}/{path}` — the consuming app must route that prefix
|
|
7
|
+
* through an auth check and `get()`).
|
|
17
8
|
*/
|
|
18
9
|
export interface SupabaseStorageProviderConfig {
|
|
19
10
|
/** Supabase project URL (e.g. http://127.0.0.1:54321 for local stack). */
|
|
20
11
|
supabaseUrl: string;
|
|
21
12
|
/**
|
|
22
|
-
* Service-role key.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
13
|
+
* Service-role key. The provider runs server-side and must bypass RLS
|
|
14
|
+
* for server-internal actions (admin uploads, janitor cleanup) — never
|
|
15
|
+
* used from the browser.
|
|
25
16
|
*/
|
|
26
17
|
serviceRoleKey: string;
|
|
27
18
|
/** Bucket for CDN-readable objects. Default: "selva-public". */
|
|
28
19
|
publicBucket?: string;
|
|
29
20
|
/** Bucket for authenticated-only objects. Default: "selva-private". */
|
|
30
21
|
privateBucket?: string;
|
|
31
|
-
/**
|
|
32
|
-
* URL prefix the app exposes for authenticated file downloads. Used by
|
|
33
|
-
* `getPublicUrl` for private-bucket paths. Default: "/api/files".
|
|
34
|
-
* The matching SvelteKit route is expected to call `get()` and stream
|
|
35
|
-
* bytes back after an auth check.
|
|
36
|
-
*/
|
|
22
|
+
/** URL prefix for authenticated file downloads, used by `getPublicUrl`. Default: "/api/files". */
|
|
37
23
|
privateUrlPrefix?: string;
|
|
38
24
|
}
|
|
39
25
|
export declare class SupabaseStorageProvider implements IStorageProvider {
|
|
@@ -45,14 +31,10 @@ export declare class SupabaseStorageProvider implements IStorageProvider {
|
|
|
45
31
|
static fromEnv(env: Record<string, string | undefined>): SupabaseStorageProvider;
|
|
46
32
|
constructor(config: SupabaseStorageProviderConfig);
|
|
47
33
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* e.g. `orgs/{id}/private/*` pricing sheets are members-only and must
|
|
53
|
-
* never land in the CDN-readable bucket. Definition covers classify as
|
|
54
|
-
* `project`-visible and are private for the same reason.
|
|
55
|
-
* Everything else (branding, archives, thumbnails) is public.
|
|
34
|
+
* `.gh`/`.ghx` source files are always private, regardless of what
|
|
35
|
+
* `classifyAssetPath` says — confidentiality by extension means a
|
|
36
|
+
* path-scheme rename can't silently move sources into the public bucket.
|
|
37
|
+
* Everything else routes by asset class.
|
|
56
38
|
*/
|
|
57
39
|
private bucketFor;
|
|
58
40
|
get(storagePath: string): Promise<Uint8Array | null>;
|
|
@@ -60,10 +42,7 @@ export declare class SupabaseStorageProvider implements IStorageProvider {
|
|
|
60
42
|
delete(storagePath: string): Promise<void>;
|
|
61
43
|
deletePrefix(prefix: string): Promise<void>;
|
|
62
44
|
private deletePrefixInBucket;
|
|
63
|
-
/**
|
|
64
|
-
* Recursively list every key under a prefix. `storage-js`'s `list` API
|
|
65
|
-
* only returns the immediate children of a "folder", so we DFS.
|
|
66
|
-
*/
|
|
45
|
+
/** storage-js's `list` only returns immediate children of a "folder", so DFS to get everything under `prefix`. */
|
|
67
46
|
private listAllKeys;
|
|
68
47
|
getPublicUrl(storagePath: string): string;
|
|
69
48
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SupabaseStorageProvider.d.ts","sourceRoot":"","sources":["../../src/storage/SupabaseStorageProvider.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE
|
|
1
|
+
{"version":3,"file":"SupabaseStorageProvider.d.ts","sourceRoot":"","sources":["../../src/storage/SupabaseStorageProvider.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE;;;;;;GAMG;AACH,MAAM,WAAW,6BAA6B;IAC7C,0EAA0E;IAC1E,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kGAAkG;IAClG,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,uBAAwB,YAAW,gBAAgB;IAC/D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAE1C,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,uBAAuB;gBAcpE,MAAM,EAAE,6BAA6B;IAUjD;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAOX,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAapD,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc/E,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM1C,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAQnC,oBAAoB;IAYlC,kHAAkH;YACpG,WAAW;IAuBzB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;CAMzC"}
|
|
@@ -27,19 +27,14 @@ export class SupabaseStorageProvider {
|
|
|
27
27
|
});
|
|
28
28
|
this.publicBucket = config.publicBucket ?? 'selva-public';
|
|
29
29
|
this.privateBucket = config.privateBucket ?? 'selva-private';
|
|
30
|
-
// Direct CDN URL format: {supabaseUrl}/storage/v1/object/public/{bucket}/{path}
|
|
31
30
|
this.publicBaseUrl = `${config.supabaseUrl.replace(/\/$/, '')}/storage/v1/object/public/${this.publicBucket}`;
|
|
32
31
|
this.privateUrlPrefix = (config.privateUrlPrefix ?? '/api/files').replace(/\/$/, '');
|
|
33
32
|
}
|
|
34
33
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* e.g. `orgs/{id}/private/*` pricing sheets are members-only and must
|
|
40
|
-
* never land in the CDN-readable bucket. Definition covers classify as
|
|
41
|
-
* `project`-visible and are private for the same reason.
|
|
42
|
-
* Everything else (branding, archives, thumbnails) is public.
|
|
34
|
+
* `.gh`/`.ghx` source files are always private, regardless of what
|
|
35
|
+
* `classifyAssetPath` says — confidentiality by extension means a
|
|
36
|
+
* path-scheme rename can't silently move sources into the public bucket.
|
|
37
|
+
* Everything else routes by asset class.
|
|
43
38
|
*/
|
|
44
39
|
bucketFor(storagePath) {
|
|
45
40
|
if (/\.(gh|ghx)$/i.test(storagePath))
|
|
@@ -53,8 +48,7 @@ export class SupabaseStorageProvider {
|
|
|
53
48
|
const bucket = this.bucketFor(storagePath);
|
|
54
49
|
const { data, error } = await this.client.storage.from(bucket).download(storagePath);
|
|
55
50
|
if (error) {
|
|
56
|
-
// storage-js
|
|
57
|
-
// Normalize any "not found" to null — we don't have a stable status code.
|
|
51
|
+
// storage-js has no stable status code for 404 — match the message instead.
|
|
58
52
|
const msg = error.message ?? '';
|
|
59
53
|
if (/not found/i.test(msg) || /no such key/i.test(msg))
|
|
60
54
|
return null;
|
|
@@ -64,16 +58,13 @@ export class SupabaseStorageProvider {
|
|
|
64
58
|
return new Uint8Array(buffer);
|
|
65
59
|
}
|
|
66
60
|
async put(storagePath, data, contentType) {
|
|
67
|
-
// Normalize images through the shared helper so this provider produces
|
|
68
|
-
// the same bytes / extensions as LocalStorageProvider for the same input.
|
|
69
61
|
const transcoded = await transcodeImageIfNeeded(data, contentType, storagePath);
|
|
70
62
|
const bucket = this.bucketFor(transcoded.path);
|
|
71
63
|
const { error } = await this.client.storage
|
|
72
64
|
.from(bucket)
|
|
73
65
|
.upload(transcoded.path, transcoded.data, {
|
|
74
66
|
contentType: transcoded.contentType,
|
|
75
|
-
// Objects are content-addressed
|
|
76
|
-
// immutable, so let CDN/browser cache them for a year.
|
|
67
|
+
// Objects are content-addressed and immutable — cache for a year.
|
|
77
68
|
cacheControl: '31536000',
|
|
78
69
|
upsert: true
|
|
79
70
|
});
|
|
@@ -83,13 +74,11 @@ export class SupabaseStorageProvider {
|
|
|
83
74
|
async delete(storagePath) {
|
|
84
75
|
const bucket = this.bucketFor(storagePath);
|
|
85
76
|
const { error } = await this.client.storage.from(bucket).remove([storagePath]);
|
|
86
|
-
// remove() does not error for missing paths — it just returns an empty array.
|
|
87
77
|
if (error)
|
|
88
78
|
throw error;
|
|
89
79
|
}
|
|
90
80
|
async deletePrefix(prefix) {
|
|
91
|
-
//
|
|
92
|
-
// Scoped by the trailing slash in the prefix, so siblings aren't touched.
|
|
81
|
+
// A definition's GUID prefix may have files in either bucket.
|
|
93
82
|
await Promise.all([
|
|
94
83
|
this.deletePrefixInBucket(this.publicBucket, prefix),
|
|
95
84
|
this.deletePrefixInBucket(this.privateBucket, prefix)
|
|
@@ -100,7 +89,7 @@ export class SupabaseStorageProvider {
|
|
|
100
89
|
const keys = await this.listAllKeys(bucket, normalized);
|
|
101
90
|
if (keys.length === 0)
|
|
102
91
|
return;
|
|
103
|
-
//
|
|
92
|
+
// remove() caps out at 1000 paths per call.
|
|
104
93
|
for (let i = 0; i < keys.length; i += 1000) {
|
|
105
94
|
const slice = keys.slice(i, i + 1000);
|
|
106
95
|
const { error } = await this.client.storage.from(bucket).remove(slice);
|
|
@@ -108,10 +97,7 @@ export class SupabaseStorageProvider {
|
|
|
108
97
|
throw error;
|
|
109
98
|
}
|
|
110
99
|
}
|
|
111
|
-
/**
|
|
112
|
-
* Recursively list every key under a prefix. `storage-js`'s `list` API
|
|
113
|
-
* only returns the immediate children of a "folder", so we DFS.
|
|
114
|
-
*/
|
|
100
|
+
/** storage-js's `list` only returns immediate children of a "folder", so DFS to get everything under `prefix`. */
|
|
115
101
|
async listAllKeys(bucket, prefix) {
|
|
116
102
|
const out = [];
|
|
117
103
|
const stack = [prefix];
|
|
@@ -125,7 +111,7 @@ export class SupabaseStorageProvider {
|
|
|
125
111
|
throw error;
|
|
126
112
|
for (const entry of data ?? []) {
|
|
127
113
|
const full = dir ? `${dir}/${entry.name}` : entry.name;
|
|
128
|
-
//
|
|
114
|
+
// storage-js marks folder entries with id === null.
|
|
129
115
|
if (entry.id === null) {
|
|
130
116
|
stack.push(full);
|
|
131
117
|
}
|
|
@@ -140,7 +126,6 @@ export class SupabaseStorageProvider {
|
|
|
140
126
|
if (this.bucketFor(storagePath) === this.publicBucket) {
|
|
141
127
|
return `${this.publicBaseUrl}/${storagePath}`;
|
|
142
128
|
}
|
|
143
|
-
// Private files: the app's authenticated proxy route. See `privateUrlPrefix`.
|
|
144
129
|
return `${this.privateUrlPrefix}/${storagePath}`;
|
|
145
130
|
}
|
|
146
131
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SupabaseStorageProvider.js","sourceRoot":"","sources":["../../src/storage/SupabaseStorageProvider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"SupabaseStorageProvider.js","sourceRoot":"","sources":["../../src/storage/SupabaseStorageProvider.ts"],"names":[],"mappings":"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"}
|