@svadmin/supabase 0.13.9 → 0.15.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.
@@ -0,0 +1,28 @@
1
+ import {
2
+ createPermissionAccessControlProvider,
3
+ setAccessControlProvider,
4
+ type AccessControlProvider,
5
+ type PermissionProjectionOptions,
6
+ type PermissionSnapshot,
7
+ } from '@svadmin/core';
8
+
9
+ export type SupabasePermissionSnapshot = PermissionSnapshot;
10
+ export type SupabasePermissionAdapterOptions = PermissionProjectionOptions;
11
+
12
+ /** Convert a backend-verified Supabase/SupAuth snapshot into the UI hint provider. */
13
+ export function createSupabasePermissionAccessControlProvider(
14
+ snapshot: unknown,
15
+ options: SupabasePermissionAdapterOptions = {},
16
+ ): AccessControlProvider {
17
+ return createPermissionAccessControlProvider(snapshot, options);
18
+ }
19
+
20
+ /** Register the adapter in one step; the result remains a UI-only projection. */
21
+ export function registerSupabasePermissionAccessControl(
22
+ snapshot: unknown,
23
+ options: SupabasePermissionAdapterOptions = {},
24
+ ): AccessControlProvider {
25
+ const provider = createSupabasePermissionAccessControlProvider(snapshot, options);
26
+ setAccessControlProvider(provider);
27
+ return provider;
28
+ }
package/src/rpc.ts CHANGED
@@ -1,10 +1,9 @@
1
- // Supabase RPC Helper — Type-safe and Ergonomic PostgreSQL Function Execution
2
- import type { SupabaseClient } from '@supabase/supabase-js';
1
+ import { executeSupabaseRpc, inputObject } from './sdk-boundary';
3
2
 
4
3
  export interface SupabaseRpcOptions {
5
4
  /** Database schema where the RPC function resides (defaults to client schema or 'public') */
6
5
  schema?: string;
7
- /** When true, do not return result rows (sets Prefer: return=minimal) */
6
+ /** Use HEAD and omit result rows. */
8
7
  head?: boolean;
9
8
  /** Count algorithm to use */
10
9
  count?: 'exact' | 'planned' | 'estimated';
@@ -21,11 +20,11 @@ export interface SupabaseRpcClient {
21
20
  * @param options Additional Supabase RPC options (schema, head, count, get)
22
21
  * @returns The returned data from the RPC call
23
22
  */
24
- call: <TResult = unknown, TArgs extends Record<string, unknown> = Record<string, unknown>>(
23
+ call: (
25
24
  functionName: string,
26
- args?: TArgs,
25
+ args?: Record<string, unknown>,
27
26
  options?: SupabaseRpcOptions
28
- ) => Promise<TResult>;
27
+ ) => Promise<unknown>;
29
28
  }
30
29
 
31
30
  /**
@@ -35,35 +34,18 @@ export interface SupabaseRpcClient {
35
34
  * @param defaultOptions Default options applied to all RPC calls
36
35
  */
37
36
  export function createSupabaseRpc(
38
- client: SupabaseClient,
37
+ client: unknown,
39
38
  defaultOptions: SupabaseRpcOptions = {}
40
39
  ): SupabaseRpcClient {
40
+ inputObject(client, 'Supabase client');
41
41
  return {
42
- async call<TResult = unknown, TArgs extends Record<string, unknown> = Record<string, unknown>>(
42
+ async call(
43
43
  functionName: string,
44
- args?: TArgs,
44
+ args?: Record<string, unknown>,
45
45
  options: SupabaseRpcOptions = {}
46
- ): Promise<TResult> {
46
+ ): Promise<unknown> {
47
47
  const mergedOptions = { ...defaultOptions, ...options };
48
- const targetClient = mergedOptions.schema ? client.schema(mergedOptions.schema) : client;
49
-
50
- const { data, error } = await targetClient.rpc(
51
- functionName,
52
- args ?? ({} as TArgs),
53
- {
54
- head: mergedOptions.head,
55
- count: mergedOptions.count,
56
- get: mergedOptions.get,
57
- }
58
- );
59
-
60
- if (error) {
61
- throw new Error(
62
- `[svadmin/supabase] RPC function "${functionName}" failed: ${error.message}`
63
- );
64
- }
65
-
66
- return data as TResult;
48
+ return executeSupabaseRpc(client, functionName, args === undefined ? {} : args, mergedOptions);
67
49
  },
68
50
  };
69
51
  }
@@ -0,0 +1,79 @@
1
+ import { definedOptions } from '@svadmin/core/options';
2
+ import { decodeCustomResult, rejectProviderResponse } from '@svadmin/core/schema';
3
+
4
+ export function isObject(value: unknown): value is Record<string, unknown> {
5
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
6
+ }
7
+
8
+ export function inputObject(value: unknown, label: string): Record<string, unknown> {
9
+ if (!isObject(value)) throw new TypeError(`${label} must be an object`);
10
+ return value;
11
+ }
12
+
13
+ export function inputRecord(value: unknown, label: string): Record<string, unknown> {
14
+ const record = inputObject(value, label);
15
+ const prototype: unknown = Object.getPrototypeOf(record);
16
+ if (prototype !== null && prototype !== Object.prototype) throw new TypeError(`${label} must be a plain record`);
17
+ return record;
18
+ }
19
+
20
+ function metadataValue(meta: object | undefined, key: string): unknown {
21
+ return meta === undefined ? undefined : Reflect.get(meta, key);
22
+ }
23
+
24
+ export function optionalString(meta: object | undefined, key: string): string | undefined {
25
+ const value = metadataValue(meta, key);
26
+ if (value === undefined) return undefined;
27
+ if (typeof value !== 'string' || !value.trim()) throw new TypeError(`Supabase ${key} must be a nonempty string`);
28
+ return value;
29
+ }
30
+
31
+ export function optionalBoolean(meta: object | undefined, key: string): boolean | undefined {
32
+ const value = metadataValue(meta, key);
33
+ if (value === undefined) return undefined;
34
+ if (typeof value !== 'boolean') throw new TypeError(`Supabase ${key} must be a boolean`);
35
+ return value;
36
+ }
37
+
38
+ export function optionalCount(meta: object | undefined): 'exact' | 'planned' | 'estimated' | undefined {
39
+ const value = metadataValue(meta, 'count');
40
+ if (value === undefined || value === 'exact' || value === 'planned' || value === 'estimated') return value;
41
+ throw new TypeError('Supabase count must be exact, planned, or estimated');
42
+ }
43
+
44
+ type SdkMethod = 'schema' | 'from' | 'rpc' | 'invoke' | 'select' | 'insert' | 'update' | 'delete' | 'eq' | 'order';
45
+
46
+ /** Dynamic SDK calls remain unknown until their response envelope is checked. */
47
+ export function callSdk(target: unknown, name: SdkMethod, args: readonly unknown[]): unknown {
48
+ if (!isObject(target)) throw new TypeError('Invalid Supabase client or query builder');
49
+ const method = target[name];
50
+ if (typeof method !== 'function') throw new TypeError(`Supabase SDK method ${name} is unavailable`);
51
+ return Reflect.apply(method, target, args);
52
+ }
53
+
54
+ export async function sdkData(request: unknown, operation: string, write: boolean): Promise<unknown> {
55
+ const result: unknown = await request;
56
+ if (!isObject(result) || !Object.hasOwn(result, 'error')) return rejectProviderResponse(write);
57
+ const error = result['error'];
58
+ if (error !== null) {
59
+ if (!isObject(error) || typeof error['message'] !== 'string') return rejectProviderResponse(write);
60
+ throw new Error(`[svadmin/supabase] ${operation} failed: ${error['message']}`);
61
+ }
62
+ return decodeCustomResult(result, write).data;
63
+ }
64
+
65
+ export async function executeSupabaseRpc(
66
+ client: unknown, functionName: string, args: unknown, options: object,
67
+ ): Promise<unknown> {
68
+ if (typeof functionName !== 'string' || !functionName.trim()) throw new TypeError('Supabase RPC name cannot be empty');
69
+ const schema = optionalString(options, 'schema');
70
+ const rpcOptions = definedOptions({
71
+ head: optionalBoolean(options, 'head'),
72
+ get: optionalBoolean(options, 'get'),
73
+ count: optionalCount(options),
74
+ });
75
+ const variables = inputRecord(args, 'Supabase RPC arguments');
76
+ const target = schema ? callSdk(client, 'schema', [schema]) : client;
77
+ return sdkData(callSdk(target, 'rpc', [functionName, variables, rpcOptions]),
78
+ `RPC function "${functionName}"`, !(rpcOptions.get || rpcOptions.head));
79
+ }