@svadmin/supabase 0.13.6 → 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 +4 -2
- package/src/index.ts +10 -0
- package/src/supacloud.ts +526 -198
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/supabase",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.7",
|
|
4
4
|
"description": "Supabase adapters — DataProvider, AuthProvider, LiveProvider, AuditHandler",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -58,6 +58,8 @@
|
|
|
58
58
|
"@svadmin/refine-adapter": "^0.10.13"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@
|
|
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
|
|
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
|
-
|
|
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:
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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?:
|
|
36
|
-
|
|
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
|
-
|
|
42
|
-
|
|
112
|
+
options: Omit<SupaCloudTaskSubscribeOptions<TUpdate>, 'onUpdate'> & {
|
|
113
|
+
onUpdate: (task: TUpdate) => void;
|
|
114
|
+
},
|
|
115
|
+
): TaskSubscription | (() => void);
|
|
43
116
|
}
|
|
44
117
|
|
|
45
|
-
|
|
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(
|
|
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:
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
70
|
-
TTask
|
|
71
|
-
|
|
72
|
-
supacloud: SupaCloudTaskClient<TTask>;
|
|
168
|
+
interface LegacyTaskProviderOptions<TTask extends SupaCloudTaskRecord> {
|
|
169
|
+
supacloud: SupaCloudTaskLegacyClient<TTask>;
|
|
170
|
+
clientKind?: 'legacy';
|
|
73
171
|
}
|
|
74
172
|
|
|
75
|
-
|
|
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<
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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 (
|
|
91
|
-
|
|
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
|
|
97
|
-
|
|
98
|
-
)
|
|
99
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
258
|
+
function invalidSdkResponse(operation: string, reason: string): TypeError {
|
|
259
|
+
return new TypeError(`[svadmin/supabase] SupaCloud SDK ${operation} returned ${reason}`);
|
|
260
|
+
}
|
|
126
261
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
266
|
+
function isSdkRootClient(
|
|
267
|
+
payload: unknown,
|
|
268
|
+
): payload is SupaCloudTaskSdkClient {
|
|
269
|
+
return isObjectRecord(payload) && isObjectRecord(payload.tasks);
|
|
138
270
|
}
|
|
139
271
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
):
|
|
143
|
-
|
|
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
|
-
|
|
287
|
+
function resolveSdkTaskClient(payload: unknown): ResolvedSdkTaskClient {
|
|
288
|
+
const client = isSdkRootClient(payload) ? payload.tasks : payload;
|
|
289
|
+
return client as ResolvedSdkTaskClient;
|
|
290
|
+
}
|
|
146
291
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
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
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
payload: task as Record<string, unknown>,
|
|
452
|
+
...sharedOptions,
|
|
453
|
+
...(meta === undefined ? {} : { metadata: meta }),
|
|
259
454
|
};
|
|
260
455
|
}
|
|
261
456
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|