@svadmin/supabase 0.13.5 → 0.13.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/supabase",
3
- "version": "0.13.5",
3
+ "version": "0.13.7",
4
4
  "description": "Supabase adapters — DataProvider, AuthProvider, LiveProvider, AuditHandler",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -24,7 +24,7 @@
24
24
  }
25
25
  },
26
26
  "peerDependencies": {
27
- "@svadmin/core": "^0.48.0",
27
+ "@svadmin/core": "^0.49.0",
28
28
  "@supabase/supabase-js": "^2.112.4",
29
29
  "@refinedev/supabase": "^6.0.2",
30
30
  "@supacloud/js": "^0.23.1"
@@ -55,9 +55,11 @@
55
55
  }
56
56
  },
57
57
  "dependencies": {
58
- "@svadmin/refine-adapter": "^0.10.12"
58
+ "@svadmin/refine-adapter": "^0.10.13"
59
59
  },
60
60
  "devDependencies": {
61
- "@supabase/supabase-js": "^2.112.4"
61
+ "@refinedev/supabase": "^6.0.2",
62
+ "@supabase/supabase-js": "^2.112.4",
63
+ "@supacloud/js": "^0.23.1"
62
64
  }
63
65
  }
package/src/index.ts CHANGED
@@ -21,7 +21,17 @@ export {
21
21
  } from './supacloud';
22
22
  export type {
23
23
  SupaCloudTaskClient,
24
+ SupaCloudTaskLegacyClient,
25
+ SupaCloudTaskSdkClient,
24
26
  SupaCloudTaskRecord,
27
+ SupaCloudSdkTaskRecord,
28
+ SupaCloudSdkTaskSubmitOptions,
29
+ SupaCloudTaskReceipt,
30
+ SupaCloudTaskSnapshot,
31
+ SupaCloudTaskSubscribeState,
32
+ SupaCloudTaskSubscribeOptions,
33
+ SupaCloudSdkTaskWaitOptions,
34
+ SupaCloudSdkTaskClient,
25
35
  CreateSupaCloudTaskProviderOptions,
26
36
  CreateSupaCloudTaskLiveProviderOptions,
27
37
  } from './supacloud';
package/src/supacloud.ts CHANGED
@@ -13,38 +13,119 @@ export interface SupaCloudTaskRecord extends TaskRecord {
13
13
  [key: string]: unknown;
14
14
  }
15
15
 
16
- export interface SupaCloudTaskReceipt<TTask extends SupaCloudTaskRecord = SupaCloudTaskRecord> {
16
+ export interface SupaCloudSdkTaskRecord {
17
+ id: string;
18
+ status: string;
19
+ progress?: number | null;
20
+ error?: string | null;
21
+ error_message?: string | null;
22
+ updatedAt?: string | null;
23
+ updated_at?: string | null;
24
+ [key: string]: unknown;
25
+ }
26
+
27
+ export interface SupaCloudTaskSnapshot {
28
+ id: string;
29
+ status: string;
30
+ progress?: number | null;
31
+ error?: string | null;
32
+ updatedAt?: string | null;
33
+ raw: unknown;
34
+ }
35
+
36
+ export type SupaCloudTaskSubscribeState = 'connecting' | 'realtime' | 'polling' | 'closed';
37
+
38
+ export interface SupaCloudTaskSubscribeOptions<TTask = SupaCloudTaskSnapshot> {
39
+ pollingIntervalMs?: number;
40
+ realtimeTimeoutMs?: number;
41
+ reconcileIntervalMs?: number;
42
+ onUpdate: (task: TTask) => void;
43
+ onStateChange?: (state: SupaCloudTaskSubscribeState, details?: { error?: unknown }) => void;
44
+ onError?: (error: unknown) => void;
45
+ stopOnTerminal?: boolean;
46
+ }
47
+
48
+ export interface SupaCloudSdkTaskWaitOptions {
49
+ intervalMs?: number;
50
+ signal?: AbortSignal;
51
+ }
52
+
53
+ export interface SupaCloudSdkTaskSubmitOptions {
54
+ body?: string
55
+ | Blob
56
+ | ArrayBuffer
57
+ | FormData
58
+ | File
59
+ | ReadableStream<Uint8Array>
60
+ | Record<string, unknown>;
61
+ headers?: Record<string, string>;
62
+ retries?: number;
63
+ timeoutSec?: number;
64
+ idempotencyKey?: string;
65
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
66
+ correlationId?: string;
67
+ businessTaskId?: string;
68
+ metadata?: Record<string, unknown>;
69
+ }
70
+
71
+ export interface SupaCloudTaskReceipt<
72
+ TTask = SupaCloudTaskRecord,
73
+ TUpdate = TTask,
74
+ TWaitOptions = unknown,
75
+ > {
17
76
  id?: string;
18
77
  taskId?: string;
19
- wait(options?: unknown): Promise<TTask>;
78
+ status?: string;
79
+ wait(options?: TWaitOptions): Promise<TTask>;
80
+ get?(): Promise<TTask>;
20
81
  cancel?(): Promise<unknown>;
21
82
  retry?(): Promise<unknown>;
22
83
  subscribe?(
23
- options: {
24
- onUpdate: (task: TTask) => void;
25
- onStateChange?: (state: unknown, details?: { error?: unknown }) => void;
26
- onError?: (error: unknown) => void;
27
- stopOnTerminal?: boolean;
28
- }
29
- ): TaskSubscription | (() => void) | undefined;
84
+ options: Omit<SupaCloudTaskSubscribeOptions<TUpdate>, 'onUpdate'> & {
85
+ onUpdate: (task: TUpdate) => void;
86
+ },
87
+ ): TaskSubscription | (() => void);
30
88
  }
31
89
 
32
- export interface SupaCloudTaskLegacyClient<TTask extends SupaCloudTaskRecord = SupaCloudTaskRecord> {
33
- submit(taskName: string, options?: SubmitTaskOptions): Promise<TaskHandle<TTask>>;
90
+ /** Structural shape of `@supacloud/js` 0.23.x task clients. */
91
+ export interface SupaCloudSdkTaskClient<
92
+ TTask = SupaCloudSdkTaskRecord,
93
+ TUpdate = SupaCloudTaskSnapshot,
94
+ > {
95
+ submit(
96
+ taskName: string,
97
+ options?: SupaCloudSdkTaskSubmitOptions,
98
+ ): Promise<SupaCloudTaskReceipt<TTask, TUpdate, SupaCloudSdkTaskWaitOptions>>;
34
99
  get(taskId: string): Promise<TTask>;
35
- list?(params?: Record<string, unknown>): Promise<TTask[] | { data?: TTask[] }>;
36
- listDlq?(params?: Record<string, unknown>): Promise<TTask[] | { data?: TTask[] }>;
100
+ list?(params?: {
101
+ status?: string | string[];
102
+ taskType?: string | string[];
103
+ functionSlug?: string;
104
+ dlq?: boolean;
105
+ limit?: number;
106
+ }): Promise<TTask[]>;
107
+ listDlq?(limit?: number): Promise<TTask[]>;
37
108
  cancel?(taskId: string): Promise<unknown>;
38
109
  retry?(taskId: string): Promise<unknown>;
39
110
  subscribe?(
40
111
  taskId: string,
41
- callback: (task: TTask) => void
42
- ): TaskSubscription | (() => void) | undefined;
112
+ options: Omit<SupaCloudTaskSubscribeOptions<TUpdate>, 'onUpdate'> & {
113
+ onUpdate: (task: TUpdate) => void;
114
+ },
115
+ ): TaskSubscription | (() => void);
43
116
  }
44
117
 
45
- export interface SupaCloudTaskSdkClient<TTask extends SupaCloudTaskRecord = SupaCloudTaskRecord> {
118
+ /** Structural shape of a root `createSupaCloudClient()` task result. */
119
+ export interface SupaCloudTaskSdkClient<
120
+ TTask = SupaCloudTaskRecord,
121
+ TUpdate = TTask,
122
+ TWaitOptions = unknown,
123
+ > {
46
124
  tasks: {
47
- submit(taskName: string, options?: Record<string, unknown>): Promise<SupaCloudTaskReceipt<TTask>>;
125
+ submit(
126
+ taskName: string,
127
+ options?: SupaCloudSdkTaskSubmitOptions,
128
+ ): Promise<SupaCloudTaskReceipt<TTask, TUpdate, TWaitOptions>>;
48
129
  get(taskId: string): Promise<TTask>;
49
130
  list?(params?: Record<string, unknown>): Promise<TTask[]>;
50
131
  listDlq?(limit?: number): Promise<TTask[]>;
@@ -52,236 +133,353 @@ export interface SupaCloudTaskSdkClient<TTask extends SupaCloudTaskRecord = Supa
52
133
  retry?(taskId: string): Promise<unknown>;
53
134
  subscribe?(
54
135
  taskId: string,
55
- options: {
56
- onUpdate: (task: TTask) => void;
57
- onStateChange?: (state: unknown, details?: { error?: unknown }) => void;
58
- onError?: (error: unknown) => void;
59
- stopOnTerminal?: boolean;
60
- }
61
- ): TaskSubscription | (() => void) | undefined;
136
+ options: Omit<SupaCloudTaskSubscribeOptions<TUpdate>, 'onUpdate'> & {
137
+ onUpdate: (task: TUpdate) => void;
138
+ },
139
+ ): TaskSubscription | (() => void);
62
140
  };
63
141
  }
64
142
 
143
+ type CurrentSupaCloudSdkRootClient = SupaCloudTaskSdkClient<
144
+ SupaCloudSdkTaskRecord,
145
+ SupaCloudTaskSnapshot,
146
+ SupaCloudSdkTaskWaitOptions
147
+ >;
148
+
149
+ export interface SupaCloudTaskLegacyClient<TTask extends SupaCloudTaskRecord = SupaCloudTaskRecord> {
150
+ submit(taskName: string, options?: SubmitTaskOptions): Promise<TaskHandle<TTask>>;
151
+ get(taskId: string): Promise<TTask>;
152
+ list?(params?: Record<string, unknown>): Promise<TTask[] | { data?: TTask[] }>;
153
+ listDlq?(params?: Record<string, unknown>): Promise<TTask[] | { data?: TTask[] }>;
154
+ cancel?(taskId: string): Promise<unknown>;
155
+ retry?(taskId: string): Promise<unknown>;
156
+ subscribe?(
157
+ taskId: string,
158
+ callback: (task: TTask) => void
159
+ ): TaskSubscription | (() => void) | undefined;
160
+ }
161
+
162
+ /** Supported task-client shapes: the legacy root methods or a root SDK client. */
65
163
  export type SupaCloudTaskClient<TTask extends SupaCloudTaskRecord = SupaCloudTaskRecord> =
66
164
  | SupaCloudTaskLegacyClient<TTask>
67
- | SupaCloudTaskSdkClient<TTask>;
165
+ | SupaCloudTaskSdkClient<TTask>
166
+ | CurrentSupaCloudSdkRootClient;
68
167
 
69
- export interface CreateSupaCloudTaskProviderOptions<
70
- TTask extends SupaCloudTaskRecord = SupaCloudTaskRecord,
71
- > {
72
- supacloud: SupaCloudTaskClient<TTask>;
168
+ interface LegacyTaskProviderOptions<TTask extends SupaCloudTaskRecord> {
169
+ supacloud: SupaCloudTaskLegacyClient<TTask>;
170
+ clientKind?: 'legacy';
73
171
  }
74
172
 
75
- export interface CreateSupaCloudTaskLiveProviderOptions<
173
+ interface SdkTaskProviderOptions<TTask extends SupaCloudTaskRecord> {
174
+ supacloud:
175
+ | SupaCloudSdkTaskClient
176
+ | SupaCloudTaskSdkClient<TTask>
177
+ | CurrentSupaCloudSdkRootClient;
178
+ clientKind: 'sdk';
179
+ }
180
+
181
+ interface AutoSdkTaskProviderOptions<TTask extends SupaCloudTaskRecord> {
182
+ supacloud: SupaCloudTaskSdkClient<TTask> | CurrentSupaCloudSdkRootClient;
183
+ clientKind?: undefined;
184
+ }
185
+
186
+ export type CreateSupaCloudTaskProviderOptions<
76
187
  TTask extends SupaCloudTaskRecord = SupaCloudTaskRecord,
77
- > {
188
+ > = LegacyTaskProviderOptions<TTask>
189
+ | SdkTaskProviderOptions<TTask>
190
+ | AutoSdkTaskProviderOptions<TTask>;
191
+
192
+ interface LegacyTaskLiveProviderOptions<TTask extends SupaCloudTaskRecord> {
193
+ supacloud: Pick<SupaCloudTaskLegacyClient<TTask>, 'subscribe'>;
194
+ clientKind?: 'legacy';
195
+ resource?: string;
196
+ mapTaskToEvent?: (task: TTask, resource: string) => LiveEvent;
197
+ }
198
+
199
+ interface SdkTaskLiveProviderOptions<TTask extends SupaCloudTaskRecord> {
78
200
  supacloud:
79
- | Pick<SupaCloudTaskLegacyClient<TTask>, 'subscribe'>
80
- | SupaCloudTaskSdkClient<TTask>;
201
+ | Pick<SupaCloudSdkTaskClient, 'subscribe'>
202
+ | SupaCloudTaskSdkClient<TTask>
203
+ | CurrentSupaCloudSdkRootClient;
204
+ clientKind: 'sdk';
81
205
  resource?: string;
82
206
  mapTaskToEvent?: (task: TTask, resource: string) => LiveEvent;
83
207
  }
84
208
 
85
- function normalizeTaskSubscription(
86
- subscription: TaskSubscription | (() => void) | undefined,
87
- ): (() => void) | undefined {
209
+ interface AutoSdkTaskLiveProviderOptions<TTask extends SupaCloudTaskRecord> {
210
+ supacloud: SupaCloudTaskSdkClient<TTask> | CurrentSupaCloudSdkRootClient;
211
+ clientKind?: undefined;
212
+ resource?: string;
213
+ mapTaskToEvent?: (task: TTask, resource: string) => LiveEvent;
214
+ }
215
+
216
+ export type CreateSupaCloudTaskLiveProviderOptions<
217
+ TTask extends SupaCloudTaskRecord = SupaCloudTaskRecord,
218
+ > = LegacyTaskLiveProviderOptions<TTask>
219
+ | SdkTaskLiveProviderOptions<TTask>
220
+ | AutoSdkTaskLiveProviderOptions<TTask>;
221
+
222
+ function normalizeTaskSubscription(subscription: unknown): (() => void) | undefined {
88
223
  if (!subscription) return undefined;
89
- if (typeof subscription === 'function') return subscription;
90
- if (typeof subscription.unsubscribe === 'function') {
91
- return () => subscription.unsubscribe();
224
+ if (typeof subscription === 'function') return subscription as () => void;
225
+ if (
226
+ typeof subscription === 'object'
227
+ && subscription !== null
228
+ && 'unsubscribe' in subscription
229
+ && typeof subscription.unsubscribe === 'function'
230
+ ) {
231
+ const unsubscribe = (subscription as { unsubscribe: () => void }).unsubscribe;
232
+ return () => unsubscribe.call(subscription);
92
233
  }
93
234
  return undefined;
94
235
  }
95
236
 
96
- function normalizeTaskHandle<TTask extends SupaCloudTaskRecord>(
97
- taskHandle: SupaCloudTaskReceipt<TTask>,
98
- ): TaskHandle<TTask> {
99
- if (!taskHandle || typeof taskHandle.wait !== 'function') {
100
- throw new Error('[svadmin/supabase] SupaCloud client returned an invalid task receipt');
237
+ function readSdkTaskSubscription(subscription: unknown, operation: string): () => void {
238
+ const unsubscribe = normalizeTaskSubscription(subscription);
239
+ if (!unsubscribe) {
240
+ throw invalidSdkResponse(operation, 'an invalid task subscription');
101
241
  }
242
+ return unsubscribe;
243
+ }
102
244
 
103
- const normalized: TaskHandle<TTask> = {
104
- wait: () => taskHandle.wait(),
105
- };
106
-
107
- const id = 'taskId' in taskHandle && typeof taskHandle.taskId === 'string'
108
- ? taskHandle.taskId
109
- : typeof taskHandle.id === 'string'
110
- ? taskHandle.id
111
- : undefined;
112
-
113
- if (id) {
114
- normalized.id = id;
245
+ function normalizeLegacyTaskList<TTask extends SupaCloudTaskRecord>(
246
+ payload: unknown,
247
+ context: string,
248
+ ): TaskListResult<TTask> {
249
+ if (Array.isArray(payload)) {
250
+ return { data: payload as TTask[], total: payload.length };
115
251
  }
116
-
117
- const cancel = taskHandle.cancel;
118
- if (typeof cancel === 'function') {
119
- normalized.cancel = () => cancel.call(taskHandle);
252
+ if (isObjectRecord(payload) && Array.isArray(payload.data)) {
253
+ return { data: payload.data as TTask[], total: payload.data.length };
120
254
  }
255
+ throw new Error(`[svadmin/supabase] ${context} returned an invalid task list response`);
256
+ }
121
257
 
122
- const retry = taskHandle.retry;
123
- if (typeof retry === 'function') {
124
- normalized.retry = () => retry.call(taskHandle);
125
- }
258
+ function invalidSdkResponse(operation: string, reason: string): TypeError {
259
+ return new TypeError(`[svadmin/supabase] SupaCloud SDK ${operation} returned ${reason}`);
260
+ }
126
261
 
127
- const subscribe = taskHandle.subscribe;
128
- if (typeof subscribe === 'function') {
129
- normalized.subscribe = (callback) =>
130
- normalizeTaskSubscription(
131
- subscribe.call(taskHandle, {
132
- onUpdate: (task) => callback(task),
133
- }),
134
- );
135
- }
262
+ function isObjectRecord(payload: unknown): payload is Record<string, unknown> {
263
+ return typeof payload === 'object' && payload !== null && !Array.isArray(payload);
264
+ }
136
265
 
137
- return normalized;
266
+ function isSdkRootClient(
267
+ payload: unknown,
268
+ ): payload is SupaCloudTaskSdkClient {
269
+ return isObjectRecord(payload) && isObjectRecord(payload.tasks);
138
270
  }
139
271
 
140
- function mapSubmitOptionsToSdk(
141
- submitOptions?: SubmitTaskOptions,
142
- ): Record<string, unknown> | undefined {
143
- if (!submitOptions) return undefined;
272
+ interface ResolvedSdkTaskClient {
273
+ submit(taskName: string, options?: SupaCloudSdkTaskSubmitOptions): Promise<unknown>;
274
+ get(taskId: string): Promise<unknown>;
275
+ list?(params?: Record<string, unknown>): Promise<unknown>;
276
+ listDlq?(limit?: number): Promise<unknown>;
277
+ cancel?(taskId: string): Promise<unknown>;
278
+ retry?(taskId: string): Promise<unknown>;
279
+ subscribe?(
280
+ taskId: string,
281
+ options: Omit<SupaCloudTaskSubscribeOptions<unknown>, 'onUpdate'> & {
282
+ onUpdate: (task: unknown) => void;
283
+ },
284
+ ): unknown;
285
+ }
144
286
 
145
- const sdkOptions: Record<string, unknown> = {};
287
+ function resolveSdkTaskClient(payload: unknown): ResolvedSdkTaskClient {
288
+ const client = isSdkRootClient(payload) ? payload.tasks : payload;
289
+ return client as ResolvedSdkTaskClient;
290
+ }
146
291
 
147
- if (submitOptions.body !== undefined) sdkOptions.body = submitOptions.body;
148
- if (submitOptions.headers !== undefined) sdkOptions.headers = submitOptions.headers;
149
- if (submitOptions.idempotencyKey !== undefined) sdkOptions.idempotencyKey = submitOptions.idempotencyKey;
150
- if (submitOptions.meta !== undefined) sdkOptions.metadata = submitOptions.meta;
292
+ function nonEmptyString(candidate: unknown): string | undefined {
293
+ return typeof candidate === 'string' && candidate.trim().length > 0 ? candidate : undefined;
294
+ }
151
295
 
152
- return sdkOptions;
296
+ function readSdkTaskId(
297
+ taskPayload: Record<string, unknown>,
298
+ rawTask: Record<string, unknown>,
299
+ operation: string,
300
+ ): string {
301
+ const taskId = nonEmptyString(taskPayload.id) ?? nonEmptyString(rawTask.id);
302
+ if (!taskId) throw invalidSdkResponse(operation, 'a task without a non-empty id');
303
+ return taskId;
153
304
  }
154
305
 
155
- function normalizeTaskList<TTask extends SupaCloudTaskRecord>(
156
- value: TTask[] | { data?: TTask[] },
157
- context: string,
158
- ): TaskListResult<TTask> {
159
- if (Array.isArray(value)) {
160
- return {
161
- data: value,
162
- total: value.length,
163
- };
306
+ function readSdkTaskStatus(
307
+ taskPayload: Record<string, unknown>,
308
+ rawTask: Record<string, unknown>,
309
+ operation: string,
310
+ ): string {
311
+ const taskStatus = nonEmptyString(taskPayload.status) ?? nonEmptyString(rawTask.status);
312
+ if (!taskStatus) throw invalidSdkResponse(operation, 'a task without a non-empty status');
313
+ return taskStatus;
314
+ }
315
+
316
+ function validateSdkProgress(
317
+ taskPayload: Record<string, unknown>,
318
+ operation: string,
319
+ ): void {
320
+ if (
321
+ taskPayload.progress !== undefined
322
+ && taskPayload.progress !== null
323
+ && (typeof taskPayload.progress !== 'number' || !Number.isFinite(taskPayload.progress))
324
+ ) {
325
+ throw invalidSdkResponse(operation, 'a task with invalid progress');
164
326
  }
327
+ }
165
328
 
166
- if (value && typeof value === 'object' && Array.isArray(value.data)) {
167
- return {
168
- data: value.data,
169
- total: value.data.length,
170
- };
329
+ function validateSdkError(
330
+ taskPayload: Record<string, unknown>,
331
+ operation: string,
332
+ ): void {
333
+ const errorPayload = taskPayload.error ?? taskPayload.error_message;
334
+ if (errorPayload !== undefined && errorPayload !== null && typeof errorPayload !== 'string') {
335
+ throw invalidSdkResponse(operation, 'a task with invalid error');
171
336
  }
337
+ }
172
338
 
173
- throw new Error(`[svadmin/supabase] ${context} returned an invalid task list response`);
339
+ function validateSdkUpdatedAt(
340
+ taskPayload: Record<string, unknown>,
341
+ operation: string,
342
+ ): void {
343
+ const updatedAtPayload = taskPayload.updatedAt ?? taskPayload.updated_at;
344
+ if (updatedAtPayload !== undefined && updatedAtPayload !== null && typeof updatedAtPayload !== 'string') {
345
+ throw invalidSdkResponse(operation, 'a task with invalid updatedAt');
346
+ }
174
347
  }
175
348
 
176
- function getTaskBridge<TTask extends SupaCloudTaskRecord>(
177
- supacloud: SupaCloudTaskClient<TTask>,
178
- ): SupaCloudTaskLegacyClient<TTask> {
179
- if ('tasks' in supacloud && supacloud.tasks) {
180
- const sdk = supacloud.tasks;
349
+ function validateOptionalSdkTaskFields(
350
+ taskPayload: Record<string, unknown>,
351
+ operation: string,
352
+ ): void {
353
+ validateSdkProgress(taskPayload, operation);
354
+ validateSdkError(taskPayload, operation);
355
+ validateSdkUpdatedAt(taskPayload, operation);
356
+ }
181
357
 
182
- return {
183
- async submit(taskName, submitOptions) {
184
- const receipt = await sdk.submit(taskName, mapSubmitOptionsToSdk(submitOptions));
185
- return normalizeTaskHandle(receipt);
186
- },
187
- get(taskId) {
188
- return sdk.get(taskId);
189
- },
190
- async list(params) {
191
- if (!sdk.list) {
192
- throw new Error('[svadmin/supabase] SupaCloud SDK client does not implement tasks.list');
193
- }
194
- return normalizeTaskList(await sdk.list(params), 'tasks.list');
195
- },
196
- async listDlq(params) {
197
- if (!sdk.listDlq) {
198
- throw new Error('[svadmin/supabase] SupaCloud SDK client does not implement tasks.listDlq');
199
- }
200
- const limit = typeof params?.limit === 'number' ? params.limit : undefined;
201
- return normalizeTaskList(await sdk.listDlq(limit), 'tasks.listDlq');
202
- },
203
- cancel(taskId) {
204
- if (!sdk.cancel) {
205
- throw new Error('[svadmin/supabase] SupaCloud SDK client does not implement tasks.cancel');
206
- }
207
- return sdk.cancel(taskId);
208
- },
209
- retry(taskId) {
210
- if (!sdk.retry) {
211
- throw new Error('[svadmin/supabase] SupaCloud SDK client does not implement tasks.retry');
212
- }
213
- return sdk.retry(taskId);
214
- },
215
- subscribe(taskId, callback) {
216
- if (!sdk.subscribe) {
217
- throw new Error('[svadmin/supabase] SupaCloud SDK client does not implement tasks.subscribe');
218
- }
219
- return sdk.subscribe(taskId, {
220
- onUpdate: callback,
221
- });
222
- },
223
- };
358
+ function readSdkTask<TTask extends SupaCloudTaskRecord>(
359
+ taskPayload: unknown,
360
+ operation: string,
361
+ ): TTask {
362
+ if (!isObjectRecord(taskPayload)) {
363
+ throw invalidSdkResponse(operation, 'a non-object task payload');
364
+ }
365
+ validateOptionalSdkTaskFields(taskPayload, operation);
366
+
367
+ const rawTask = isObjectRecord(taskPayload.raw) ? taskPayload.raw : taskPayload;
368
+ if (rawTask !== taskPayload) validateOptionalSdkTaskFields(rawTask, operation);
369
+ const taskId = readSdkTaskId(taskPayload, rawTask, operation);
370
+ const taskStatus = readSdkTaskStatus(taskPayload, rawTask, operation);
371
+
372
+ const normalizedTask: Record<string, unknown> = { ...rawTask, id: taskId, status: taskStatus };
373
+ const taskProgress = taskPayload.progress ?? rawTask.progress;
374
+ if (typeof taskProgress === 'number') normalizedTask.progress = taskProgress;
375
+ else delete normalizedTask.progress;
376
+ if (taskPayload.error === null || typeof taskPayload.error === 'string') {
377
+ normalizedTask.error = taskPayload.error;
378
+ }
379
+ if (taskPayload.updatedAt === null || typeof taskPayload.updatedAt === 'string') {
380
+ normalizedTask.updatedAt = taskPayload.updatedAt;
224
381
  }
382
+ return normalizedTask as TTask;
383
+ }
225
384
 
226
- return supacloud as SupaCloudTaskLegacyClient<TTask>;
385
+ function readSdkTaskList<TTask extends SupaCloudTaskRecord>(
386
+ taskListPayload: unknown,
387
+ operation: string,
388
+ ): TaskListResult<TTask> {
389
+ if (!Array.isArray(taskListPayload)) {
390
+ throw invalidSdkResponse(operation, 'a non-array task list');
391
+ }
392
+ const tasks = taskListPayload.map((task) => readSdkTask<TTask>(task, operation));
393
+ return { data: tasks, total: tasks.length };
227
394
  }
228
395
 
229
- function getTaskSubscribeBridge<TTask extends SupaCloudTaskRecord>(
230
- supacloud:
231
- | Pick<SupaCloudTaskLegacyClient<TTask>, 'subscribe'>
232
- | SupaCloudTaskSdkClient<TTask>,
233
- ): Pick<SupaCloudTaskLegacyClient<TTask>, 'subscribe'> {
234
- if ('tasks' in supacloud && supacloud.tasks) {
235
- return {
236
- subscribe(taskId, callback) {
237
- if (!supacloud.tasks.subscribe) {
238
- throw new Error('[svadmin/supabase] SupaCloud SDK client does not implement tasks.subscribe');
239
- }
240
-
241
- return supacloud.tasks.subscribe(taskId, {
242
- onUpdate: callback,
243
- });
244
- },
245
- };
396
+ function assertOptionalReceiptMethod(
397
+ receipt: Record<string, unknown>,
398
+ method: 'cancel' | 'retry' | 'subscribe',
399
+ ): void {
400
+ if (receipt[method] !== undefined && typeof receipt[method] !== 'function') {
401
+ throw invalidSdkResponse('submit', `a receipt with invalid ${method}`);
246
402
  }
403
+ }
247
404
 
248
- return supacloud as Pick<SupaCloudTaskLegacyClient<TTask>, 'subscribe'>;
405
+ function readSdkReceipt(receiptPayload: unknown): SupaCloudTaskReceipt {
406
+ if (!isObjectRecord(receiptPayload)) {
407
+ throw invalidSdkResponse('submit', 'a non-object receipt');
408
+ }
409
+ if (!nonEmptyString(receiptPayload.taskId) || typeof receiptPayload.wait !== 'function') {
410
+ throw invalidSdkResponse('submit', 'a receipt without a non-empty taskId and wait method');
411
+ }
412
+ assertOptionalReceiptMethod(receiptPayload, 'cancel');
413
+ assertOptionalReceiptMethod(receiptPayload, 'retry');
414
+ assertOptionalReceiptMethod(receiptPayload, 'subscribe');
415
+ return receiptPayload as unknown as SupaCloudTaskReceipt;
249
416
  }
250
417
 
251
- function defaultMapTaskToEvent<TTask extends SupaCloudTaskRecord>(
252
- task: TTask,
253
- resource: string,
254
- ): LiveEvent {
418
+ function adaptSdkReceipt<TTask extends SupaCloudTaskRecord>(
419
+ receipt: SupaCloudTaskReceipt,
420
+ ): TaskHandle<TTask> {
421
+ const wait = receipt.wait;
422
+ const taskHandle: TaskHandle<TTask> = {
423
+ id: receipt.taskId,
424
+ wait: async () => readSdkTask<TTask>(
425
+ await wait.call(receipt),
426
+ 'receipt.wait',
427
+ ),
428
+ };
429
+
430
+ const cancel = receipt.cancel;
431
+ if (cancel) taskHandle.cancel = () => cancel.call(receipt);
432
+ const retry = receipt.retry;
433
+ if (retry) taskHandle.retry = () => retry.call(receipt);
434
+ const subscribe = receipt.subscribe;
435
+ if (subscribe) {
436
+ taskHandle.subscribe = (callback) => readSdkTaskSubscription(
437
+ subscribe.call(receipt, {
438
+ onUpdate: (task) => callback(readSdkTask<TTask>(task, 'receipt.subscribe')),
439
+ }),
440
+ 'receipt.subscribe',
441
+ );
442
+ }
443
+ return taskHandle;
444
+ }
445
+
446
+ function toSdkSubmitOptions(
447
+ submitOptions?: SubmitTaskOptions,
448
+ ): SupaCloudSdkTaskSubmitOptions | undefined {
449
+ if (!submitOptions) return undefined;
450
+ const { meta, ...sharedOptions } = submitOptions;
255
451
  return {
256
- type: 'UPDATE',
257
- resource,
258
- payload: task as Record<string, unknown>,
452
+ ...sharedOptions,
453
+ ...(meta === undefined ? {} : { metadata: meta }),
259
454
  };
260
455
  }
261
456
 
262
- export function createSupaCloudTaskProvider<
263
- TTask extends SupaCloudTaskRecord = SupaCloudTaskRecord,
264
- >(options: CreateSupaCloudTaskProviderOptions<TTask>): TaskProvider<TTask> {
265
- const supacloud = getTaskBridge(options.supacloud);
457
+ function readSdkDlqLimit(params?: Record<string, unknown>): number | undefined {
458
+ const limit = params?.limit;
459
+ if (limit === undefined) return undefined;
460
+ if (typeof limit !== 'number' || !Number.isFinite(limit)) {
461
+ throw new TypeError('[svadmin/supabase] SupaCloud SDK tasks.listDlq requires a finite numeric limit');
462
+ }
463
+ return limit;
464
+ }
266
465
 
466
+ function createLegacyTaskProvider<TTask extends SupaCloudTaskRecord>(
467
+ supacloud: SupaCloudTaskLegacyClient<TTask>,
468
+ ): TaskProvider<TTask> {
267
469
  return {
268
- submit(taskName, submitOptions) {
269
- return supacloud.submit(taskName, submitOptions);
270
- },
271
- get(taskId) {
272
- return supacloud.get(taskId);
273
- },
470
+ submit: (taskName, submitOptions) => supacloud.submit(taskName, submitOptions),
471
+ get: (taskId) => supacloud.get(taskId),
274
472
  async list(params) {
275
473
  if (!supacloud.list) {
276
474
  throw new Error('[svadmin/supabase] SupaCloud client does not implement tasks.list');
277
475
  }
278
- return normalizeTaskList(await supacloud.list(params), 'tasks.list');
476
+ return normalizeLegacyTaskList(await supacloud.list(params), 'tasks.list');
279
477
  },
280
478
  async listDlq(params) {
281
479
  if (!supacloud.listDlq) {
282
480
  throw new Error('[svadmin/supabase] SupaCloud client does not implement tasks.listDlq');
283
481
  }
284
- return normalizeTaskList(await supacloud.listDlq(params), 'tasks.listDlq');
482
+ return normalizeLegacyTaskList(await supacloud.listDlq(params), 'tasks.listDlq');
285
483
  },
286
484
  cancel(taskId) {
287
485
  if (!supacloud.cancel) {
@@ -304,29 +502,159 @@ export function createSupaCloudTaskProvider<
304
502
  };
305
503
  }
306
504
 
307
- export function createSupaCloudTaskLiveProvider<
308
- TTask extends SupaCloudTaskRecord = SupaCloudTaskRecord,
309
- >(options: CreateSupaCloudTaskLiveProviderOptions<TTask>): LiveProvider {
310
- const supacloud = getTaskSubscribeBridge(options.supacloud);
505
+ function createSdkTaskProvider<TTask extends SupaCloudTaskRecord>(
506
+ supacloud: unknown,
507
+ ): TaskProvider<TTask> {
508
+ const sdk = resolveSdkTaskClient(supacloud);
509
+ return {
510
+ async submit(taskName, submitOptions) {
511
+ const receipt = readSdkReceipt(await sdk.submit(
512
+ taskName,
513
+ toSdkSubmitOptions(submitOptions),
514
+ ));
515
+ return adaptSdkReceipt<TTask>(receipt);
516
+ },
517
+ async get(taskId) {
518
+ return readSdkTask<TTask>(await sdk.get(taskId), 'tasks.get');
519
+ },
520
+ async list(params) {
521
+ if (!sdk.list) {
522
+ throw new Error('[svadmin/supabase] SupaCloud SDK client does not implement tasks.list');
523
+ }
524
+ return readSdkTaskList<TTask>(await sdk.list(params), 'tasks.list');
525
+ },
526
+ async listDlq(params) {
527
+ if (!sdk.listDlq) {
528
+ throw new Error('[svadmin/supabase] SupaCloud SDK client does not implement tasks.listDlq');
529
+ }
530
+ return readSdkTaskList<TTask>(
531
+ await sdk.listDlq(readSdkDlqLimit(params)),
532
+ 'tasks.listDlq',
533
+ );
534
+ },
535
+ cancel(taskId) {
536
+ if (!sdk.cancel) {
537
+ throw new Error('[svadmin/supabase] SupaCloud SDK client does not implement tasks.cancel');
538
+ }
539
+ return sdk.cancel(taskId);
540
+ },
541
+ retry(taskId) {
542
+ if (!sdk.retry) {
543
+ throw new Error('[svadmin/supabase] SupaCloud SDK client does not implement tasks.retry');
544
+ }
545
+ return sdk.retry(taskId);
546
+ },
547
+ subscribe(taskId, callback) {
548
+ if (!sdk.subscribe) {
549
+ throw new Error('[svadmin/supabase] SupaCloud SDK client does not implement tasks.subscribe');
550
+ }
551
+ return readSdkTaskSubscription(
552
+ sdk.subscribe(taskId, {
553
+ onUpdate: (task) => callback(readSdkTask<TTask>(task, 'tasks.subscribe')),
554
+ }),
555
+ 'tasks.subscribe',
556
+ );
557
+ },
558
+ };
559
+ }
560
+
561
+ function defaultMapTaskToEvent<TTask extends SupaCloudTaskRecord>(
562
+ task: TTask,
563
+ resource: string,
564
+ ): LiveEvent {
565
+ return {
566
+ type: 'UPDATE',
567
+ resource,
568
+ payload: task as Record<string, unknown>,
569
+ };
570
+ }
571
+
572
+ function requiredLiveTaskId(liveParams?: Record<string, unknown>): string {
573
+ const taskId = nonEmptyString(liveParams?.taskId);
574
+ if (!taskId) {
575
+ throw new Error('[svadmin/supabase] createSupaCloudTaskLiveProvider requires liveParams.taskId');
576
+ }
577
+ return taskId;
578
+ }
579
+
580
+ function createLegacyTaskLiveProvider<TTask extends SupaCloudTaskRecord>(
581
+ options: LegacyTaskLiveProviderOptions<TTask>,
582
+ ): LiveProvider {
311
583
  const resourceName = options.resource ?? 'tasks';
312
584
  const mapTaskToEvent = options.mapTaskToEvent ?? defaultMapTaskToEvent<TTask>;
313
-
314
585
  return {
315
586
  subscribe({ resource, liveParams, callback }) {
316
- if (!supacloud.subscribe) {
587
+ if (!options.supacloud.subscribe) {
317
588
  throw new Error('[svadmin/supabase] SupaCloud client does not implement tasks.subscribe');
318
589
  }
319
-
320
- const taskId = typeof liveParams?.taskId === 'string' ? liveParams.taskId : undefined;
321
- if (!taskId) {
322
- throw new Error('[svadmin/supabase] createSupaCloudTaskLiveProvider requires liveParams.taskId');
323
- }
324
-
325
- const unsubscribe = supacloud.subscribe(taskId, (task) => {
590
+ const taskId = requiredLiveTaskId(liveParams);
591
+ const subscription = options.supacloud.subscribe(taskId, (task) => {
326
592
  callback(mapTaskToEvent(task, resource || resourceName));
327
593
  });
594
+ return normalizeTaskSubscription(subscription) ?? (() => {});
595
+ },
596
+ };
597
+ }
328
598
 
329
- return normalizeTaskSubscription(unsubscribe) ?? (() => {});
599
+ function createSdkTaskLiveProvider<TTask extends SupaCloudTaskRecord>(
600
+ options: {
601
+ supacloud: unknown;
602
+ resource?: string;
603
+ mapTaskToEvent?: (task: TTask, resource: string) => LiveEvent;
604
+ },
605
+ ): LiveProvider {
606
+ const sdk = resolveSdkTaskClient(options.supacloud);
607
+ const resourceName = options.resource ?? 'tasks';
608
+ const mapTaskToEvent = options.mapTaskToEvent ?? defaultMapTaskToEvent<TTask>;
609
+ return {
610
+ subscribe({ resource, liveParams, callback }) {
611
+ if (!sdk.subscribe) {
612
+ throw new Error('[svadmin/supabase] SupaCloud SDK client does not implement tasks.subscribe');
613
+ }
614
+ const taskId = requiredLiveTaskId(liveParams);
615
+ return readSdkTaskSubscription(
616
+ sdk.subscribe(taskId, {
617
+ onUpdate: (task) => callback(mapTaskToEvent(
618
+ readSdkTask<TTask>(task, 'tasks.subscribe'),
619
+ resource || resourceName,
620
+ )),
621
+ }),
622
+ 'tasks.subscribe',
623
+ );
330
624
  },
331
625
  };
332
626
  }
627
+
628
+ export function createSupaCloudTaskProvider<
629
+ TTask extends SupaCloudTaskRecord = SupaCloudTaskRecord,
630
+ >(options: CreateSupaCloudTaskProviderOptions<TTask>): TaskProvider<TTask> {
631
+ if (options.clientKind === 'legacy') {
632
+ return createLegacyTaskProvider(options.supacloud);
633
+ }
634
+ if (options.clientKind === 'sdk') {
635
+ return createSdkTaskProvider<TTask>(options.supacloud);
636
+ }
637
+ if (isSdkRootClient(options.supacloud)) {
638
+ return createSdkTaskProvider<TTask>(options.supacloud);
639
+ }
640
+ return createLegacyTaskProvider(options.supacloud as SupaCloudTaskLegacyClient<TTask>);
641
+ }
642
+
643
+ export function createSupaCloudTaskLiveProvider<
644
+ TTask extends SupaCloudTaskRecord = SupaCloudTaskRecord,
645
+ >(options: CreateSupaCloudTaskLiveProviderOptions<TTask>): LiveProvider {
646
+ if (options.clientKind === 'legacy') {
647
+ return createLegacyTaskLiveProvider(options);
648
+ }
649
+ if (options.clientKind === 'sdk') {
650
+ return createSdkTaskLiveProvider(options);
651
+ }
652
+ if (isSdkRootClient(options.supacloud)) {
653
+ return createSdkTaskLiveProvider({
654
+ supacloud: options.supacloud,
655
+ resource: options.resource,
656
+ mapTaskToEvent: options.mapTaskToEvent,
657
+ });
658
+ }
659
+ return createLegacyTaskLiveProvider(options as LegacyTaskLiveProviderOptions<TTask>);
660
+ }