@transloadit/convex 0.0.1 → 0.0.3

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.
Files changed (38) hide show
  1. package/README.md +162 -79
  2. package/dist/client/index.d.ts +92 -63
  3. package/dist/client/index.d.ts.map +1 -1
  4. package/dist/client/index.js +57 -30
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/client/types.d.ts.map +1 -1
  7. package/dist/component/_generated/component.d.ts +35 -15
  8. package/dist/component/_generated/component.d.ts.map +1 -1
  9. package/dist/component/apiUtils.d.ts +13 -4
  10. package/dist/component/apiUtils.d.ts.map +1 -1
  11. package/dist/component/apiUtils.js +22 -12
  12. package/dist/component/apiUtils.js.map +1 -1
  13. package/dist/component/lib.d.ts +69 -45
  14. package/dist/component/lib.d.ts.map +1 -1
  15. package/dist/component/lib.js +154 -77
  16. package/dist/component/lib.js.map +1 -1
  17. package/dist/component/schema.d.ts +9 -9
  18. package/dist/component/schema.js +3 -3
  19. package/dist/component/schema.js.map +1 -1
  20. package/dist/react/index.d.ts +23 -25
  21. package/dist/react/index.d.ts.map +1 -1
  22. package/dist/react/index.js +129 -88
  23. package/dist/react/index.js.map +1 -1
  24. package/dist/test/index.d.ts +65 -0
  25. package/dist/test/index.d.ts.map +1 -0
  26. package/dist/test/index.js +8 -0
  27. package/dist/test/index.js.map +1 -0
  28. package/package.json +27 -15
  29. package/src/client/index.ts +72 -35
  30. package/src/client/types.ts +12 -11
  31. package/src/component/_generated/component.ts +44 -13
  32. package/src/component/apiUtils.test.ts +29 -0
  33. package/src/component/apiUtils.ts +52 -26
  34. package/src/component/lib.test.ts +73 -3
  35. package/src/component/lib.ts +220 -97
  36. package/src/component/schema.ts +3 -3
  37. package/src/react/index.tsx +193 -150
  38. package/src/test/index.ts +10 -0
@@ -1,8 +1,10 @@
1
+ import type { AssemblyStatus } from "@transloadit/types/assemblyStatus";
2
+ import type { AssemblyInstructionsInput } from "@transloadit/types/template";
3
+ import { anyApi, type FunctionReference } from "convex/server";
1
4
  import { type Infer, v } from "convex/values";
2
- import { internal } from "./_generated/api.js";
3
- import type { Id } from "./_generated/dataModel.js";
4
5
  import {
5
6
  action,
7
+ internalAction,
6
8
  internalMutation,
7
9
  mutation,
8
10
  query,
@@ -16,6 +18,101 @@ import {
16
18
 
17
19
  const TRANSLOADIT_ASSEMBLY_URL = "https://api2.transloadit.com/assemblies";
18
20
 
21
+ type ProcessWebhookResult = {
22
+ assemblyId: string;
23
+ resultCount: number;
24
+ ok?: string;
25
+ status?: string;
26
+ };
27
+
28
+ type InternalApi = {
29
+ lib: {
30
+ upsertAssembly: FunctionReference<
31
+ "mutation",
32
+ "internal",
33
+ Record<string, unknown>,
34
+ unknown
35
+ >;
36
+ replaceResultsForAssembly: FunctionReference<
37
+ "mutation",
38
+ "internal",
39
+ Record<string, unknown>,
40
+ unknown
41
+ >;
42
+ processWebhook: FunctionReference<
43
+ "action",
44
+ "internal",
45
+ Record<string, unknown>,
46
+ ProcessWebhookResult
47
+ >;
48
+ };
49
+ };
50
+
51
+ const internal = anyApi as unknown as InternalApi;
52
+
53
+ const resolveAssemblyId = (payload: AssemblyStatus): string => {
54
+ if (typeof payload.assembly_id === "string") return payload.assembly_id;
55
+ if (typeof payload.assemblyId === "string") return payload.assemblyId;
56
+ return "";
57
+ };
58
+
59
+ const buildSignedAssemblyUrl = async (
60
+ assemblyId: string,
61
+ authKey: string,
62
+ authSecret: string,
63
+ ): Promise<string> => {
64
+ const params = JSON.stringify({
65
+ auth: {
66
+ key: authKey,
67
+ expires: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
68
+ },
69
+ });
70
+ const signature = await signTransloaditParams(params, authSecret);
71
+ const url = new URL(`${TRANSLOADIT_ASSEMBLY_URL}/${assemblyId}`);
72
+ url.searchParams.set("signature", signature);
73
+ url.searchParams.set("params", params);
74
+ return url.toString();
75
+ };
76
+
77
+ const applyAssemblyStatus = async (
78
+ ctx: Pick<import("./_generated/server.js").FunctionCtx, "runMutation">,
79
+ payload: AssemblyStatus,
80
+ ) => {
81
+ const assemblyId = resolveAssemblyId(payload);
82
+ if (!assemblyId) {
83
+ throw new Error("Webhook payload missing assembly_id");
84
+ }
85
+
86
+ const results = flattenResults(payload.results ?? undefined);
87
+
88
+ await ctx.runMutation(internal.lib.upsertAssembly, {
89
+ assemblyId,
90
+ status: typeof payload.ok === "string" ? payload.ok : undefined,
91
+ ok: typeof payload.ok === "string" ? payload.ok : undefined,
92
+ message: typeof payload.message === "string" ? payload.message : undefined,
93
+ templateId:
94
+ typeof payload.template_id === "string" ? payload.template_id : undefined,
95
+ notifyUrl:
96
+ typeof payload.notify_url === "string" ? payload.notify_url : undefined,
97
+ uploads: payload.uploads,
98
+ results: payload.results,
99
+ error: payload.error,
100
+ raw: payload,
101
+ });
102
+
103
+ await ctx.runMutation(internal.lib.replaceResultsForAssembly, {
104
+ assemblyId,
105
+ results,
106
+ });
107
+
108
+ return {
109
+ assemblyId,
110
+ resultCount: results.length,
111
+ ok: typeof payload.ok === "string" ? payload.ok : undefined,
112
+ status: typeof payload.ok === "string" ? payload.ok : undefined,
113
+ };
114
+ };
115
+
19
116
  export const vAssembly = v.object({
20
117
  _id: v.id("assemblies"),
21
118
  _creationTime: v.number(),
@@ -26,9 +123,9 @@ export const vAssembly = v.object({
26
123
  templateId: v.optional(v.string()),
27
124
  notifyUrl: v.optional(v.string()),
28
125
  numExpectedUploadFiles: v.optional(v.number()),
29
- fields: v.optional(v.any()),
30
- uploads: v.optional(v.any()),
31
- results: v.optional(v.any()),
126
+ fields: v.optional(v.record(v.string(), v.any())),
127
+ uploads: v.optional(v.array(v.any())),
128
+ results: v.optional(v.record(v.string(), v.array(v.any()))),
32
129
  error: v.optional(v.any()),
33
130
  raw: v.optional(v.any()),
34
131
  createdAt: v.number(),
@@ -61,12 +158,12 @@ export const vTransloaditConfig = v.object({
61
158
 
62
159
  const vAssemblyBaseArgs = {
63
160
  templateId: v.optional(v.string()),
64
- steps: v.optional(v.any()),
65
- fields: v.optional(v.any()),
161
+ steps: v.optional(v.record(v.string(), v.any())),
162
+ fields: v.optional(v.record(v.string(), v.any())),
66
163
  notifyUrl: v.optional(v.string()),
67
164
  numExpectedUploadFiles: v.optional(v.number()),
68
165
  expires: v.optional(v.string()),
69
- additionalParams: v.optional(v.any()),
166
+ additionalParams: v.optional(v.record(v.string(), v.any())),
70
167
  userId: v.optional(v.string()),
71
168
  };
72
169
 
@@ -79,9 +176,9 @@ export const upsertAssembly = internalMutation({
79
176
  templateId: v.optional(v.string()),
80
177
  notifyUrl: v.optional(v.string()),
81
178
  numExpectedUploadFiles: v.optional(v.number()),
82
- fields: v.optional(v.any()),
83
- uploads: v.optional(v.any()),
84
- results: v.optional(v.any()),
179
+ fields: v.optional(v.record(v.string(), v.any())),
180
+ uploads: v.optional(v.array(v.any())),
181
+ results: v.optional(v.record(v.string(), v.array(v.any()))),
85
182
  error: v.optional(v.any()),
86
183
  raw: v.optional(v.any()),
87
184
  userId: v.optional(v.string()),
@@ -189,10 +286,10 @@ export const createAssembly = action({
189
286
  const { paramsString, params } = buildTransloaditParams({
190
287
  authKey: args.config.authKey,
191
288
  templateId: args.templateId,
192
- steps: args.steps as Record<string, unknown> | undefined,
193
- fields: args.fields as Record<string, unknown> | undefined,
289
+ steps: args.steps as AssemblyInstructionsInput["steps"],
290
+ fields: args.fields as AssemblyInstructionsInput["fields"],
194
291
  notifyUrl: args.notifyUrl,
195
- numExpectedUploadFiles: args.numExpectedUploadFiles,
292
+ numExpectedUploadFiles: undefined,
196
293
  expires: args.expires,
197
294
  additionalParams: args.additionalParams as
198
295
  | Record<string, unknown>
@@ -207,6 +304,12 @@ export const createAssembly = action({
207
304
  const formData = new FormData();
208
305
  formData.append("params", paramsString);
209
306
  formData.append("signature", signature);
307
+ if (typeof args.numExpectedUploadFiles === "number") {
308
+ formData.append(
309
+ "tus_num_expected_upload_files",
310
+ String(args.numExpectedUploadFiles),
311
+ );
312
+ }
210
313
 
211
314
  const response = await fetch(TRANSLOADIT_ASSEMBLY_URL, {
212
315
  method: "POST",
@@ -251,72 +354,30 @@ export const createAssembly = action({
251
354
  },
252
355
  });
253
356
 
254
- export const generateUploadParams = action({
255
- args: {
256
- config: vTransloaditConfig,
257
- ...vAssemblyBaseArgs,
258
- },
259
- returns: v.object({
260
- params: v.string(),
261
- signature: v.string(),
262
- url: v.string(),
263
- }),
264
- handler: async (ctx, args) => {
265
- const { paramsString } = buildTransloaditParams({
266
- authKey: args.config.authKey,
267
- templateId: args.templateId,
268
- steps: args.steps as Record<string, unknown> | undefined,
269
- fields: args.fields as Record<string, unknown> | undefined,
270
- notifyUrl: args.notifyUrl,
271
- numExpectedUploadFiles: args.numExpectedUploadFiles,
272
- expires: args.expires,
273
- additionalParams: args.additionalParams as
274
- | Record<string, unknown>
275
- | undefined,
276
- });
277
-
278
- const signature = await signTransloaditParams(
279
- paramsString,
280
- args.config.authSecret,
281
- );
282
-
283
- return {
284
- params: paramsString,
285
- signature,
286
- url: TRANSLOADIT_ASSEMBLY_URL,
287
- };
288
- },
289
- });
357
+ const vWebhookArgs = {
358
+ payload: v.any(),
359
+ rawBody: v.optional(v.string()),
360
+ signature: v.optional(v.string()),
361
+ verifySignature: v.optional(v.boolean()),
362
+ authSecret: v.optional(v.string()),
363
+ };
290
364
 
291
- export const handleWebhook = action({
292
- args: {
293
- payload: v.any(),
294
- rawBody: v.optional(v.string()),
295
- signature: v.optional(v.string()),
296
- verifySignature: v.optional(v.boolean()),
297
- config: v.optional(
298
- v.object({
299
- authSecret: v.string(),
300
- }),
301
- ),
302
- },
365
+ export const processWebhook = internalAction({
366
+ args: vWebhookArgs,
303
367
  returns: v.object({
304
368
  assemblyId: v.string(),
305
369
  resultCount: v.number(),
370
+ ok: v.optional(v.string()),
371
+ status: v.optional(v.string()),
306
372
  }),
307
373
  handler: async (ctx, args) => {
308
374
  const rawBody = args.rawBody ?? JSON.stringify(args.payload ?? {});
309
375
  const shouldVerify = args.verifySignature ?? true;
310
- const authSecret =
311
- args.config?.authSecret ??
312
- process.env.TRANSLOADIT_AUTH_SECRET ??
313
- process.env.TRANSLOADIT_SECRET;
376
+ const authSecret = args.authSecret ?? process.env.TRANSLOADIT_SECRET;
314
377
 
315
378
  if (shouldVerify) {
316
379
  if (!authSecret) {
317
- throw new Error(
318
- "Missing TRANSLOADIT_AUTH_SECRET for webhook validation",
319
- );
380
+ throw new Error("Missing TRANSLOADIT_SECRET for webhook validation");
320
381
  }
321
382
  const verified = await verifyWebhookSignature({
322
383
  rawBody,
@@ -328,41 +389,103 @@ export const handleWebhook = action({
328
389
  }
329
390
  }
330
391
 
331
- const payload = args.payload as Record<string, unknown>;
332
- const assemblyId =
333
- typeof payload.assembly_id === "string"
334
- ? payload.assembly_id
335
- : typeof payload.assemblyId === "string"
336
- ? payload.assemblyId
337
- : "";
392
+ return applyAssemblyStatus(ctx, args.payload as AssemblyStatus);
393
+ },
394
+ });
338
395
 
396
+ export const handleWebhook = action({
397
+ args: {
398
+ ...vWebhookArgs,
399
+ config: v.optional(
400
+ v.object({
401
+ authSecret: v.string(),
402
+ }),
403
+ ),
404
+ },
405
+ returns: v.object({
406
+ assemblyId: v.string(),
407
+ resultCount: v.number(),
408
+ ok: v.optional(v.string()),
409
+ status: v.optional(v.string()),
410
+ }),
411
+ handler: async (ctx, args) => {
412
+ return ctx.runAction(internal.lib.processWebhook, {
413
+ payload: args.payload,
414
+ rawBody: args.rawBody,
415
+ signature: args.signature,
416
+ verifySignature: args.verifySignature,
417
+ authSecret: args.config?.authSecret,
418
+ });
419
+ },
420
+ });
421
+
422
+ export const queueWebhook = action({
423
+ args: {
424
+ ...vWebhookArgs,
425
+ config: v.optional(
426
+ v.object({
427
+ authSecret: v.string(),
428
+ }),
429
+ ),
430
+ },
431
+ returns: v.object({
432
+ assemblyId: v.string(),
433
+ queued: v.boolean(),
434
+ }),
435
+ handler: async (ctx, args) => {
436
+ const payload = args.payload as AssemblyStatus;
437
+ const assemblyId = resolveAssemblyId(payload);
339
438
  if (!assemblyId) {
340
439
  throw new Error("Webhook payload missing assembly_id");
341
440
  }
342
441
 
343
- const results = flattenResults(
344
- (payload.results as Record<string, Array<Record<string, unknown>>>) ??
345
- undefined,
346
- );
347
-
348
- await ctx.runMutation(internal.lib.upsertAssembly, {
349
- assemblyId,
350
- status: typeof payload.ok === "string" ? payload.ok : undefined,
351
- ok: typeof payload.ok === "string" ? payload.ok : undefined,
352
- message:
353
- typeof payload.message === "string" ? payload.message : undefined,
354
- uploads: payload.uploads,
355
- results: payload.results,
356
- error: payload.error,
357
- raw: payload,
442
+ await ctx.scheduler.runAfter(0, internal.lib.processWebhook, {
443
+ payload: args.payload,
444
+ rawBody: args.rawBody,
445
+ signature: args.signature,
446
+ verifySignature: args.verifySignature,
447
+ authSecret: args.config?.authSecret,
358
448
  });
359
449
 
360
- await ctx.runMutation(internal.lib.replaceResultsForAssembly, {
361
- assemblyId,
362
- results,
363
- });
450
+ return { assemblyId, queued: true };
451
+ },
452
+ });
453
+
454
+ export const refreshAssembly = action({
455
+ args: {
456
+ assemblyId: v.string(),
457
+ config: v.optional(
458
+ v.object({
459
+ authKey: v.string(),
460
+ authSecret: v.string(),
461
+ }),
462
+ ),
463
+ },
464
+ returns: v.object({
465
+ assemblyId: v.string(),
466
+ resultCount: v.number(),
467
+ ok: v.optional(v.string()),
468
+ status: v.optional(v.string()),
469
+ }),
470
+ handler: async (ctx, args) => {
471
+ const { assemblyId } = args;
472
+ const authKey = args.config?.authKey ?? process.env.TRANSLOADIT_KEY;
473
+ const authSecret =
474
+ args.config?.authSecret ?? process.env.TRANSLOADIT_SECRET;
475
+ const url =
476
+ authKey && authSecret
477
+ ? await buildSignedAssemblyUrl(assemblyId, authKey, authSecret)
478
+ : `${TRANSLOADIT_ASSEMBLY_URL}/${assemblyId}`;
479
+
480
+ const response = await fetch(url);
481
+ const payload = (await response.json()) as AssemblyStatus;
482
+ if (!response.ok) {
483
+ throw new Error(
484
+ `Transloadit status error ${response.status}: ${JSON.stringify(payload)}`,
485
+ );
486
+ }
364
487
 
365
- return { assemblyId, resultCount: results.length };
488
+ return applyAssemblyStatus(ctx, payload);
366
489
  },
367
490
  });
368
491
 
@@ -438,7 +561,7 @@ export const storeAssemblyMetadata = mutation({
438
561
  args: {
439
562
  assemblyId: v.string(),
440
563
  userId: v.optional(v.string()),
441
- fields: v.optional(v.any()),
564
+ fields: v.optional(v.record(v.string(), v.any())),
442
565
  },
443
566
  returns: v.union(vAssembly, v.null()),
444
567
  handler: async (ctx, args) => {
@@ -20,9 +20,9 @@ export default defineSchema({
20
20
  templateId: v.optional(v.string()),
21
21
  notifyUrl: v.optional(v.string()),
22
22
  numExpectedUploadFiles: v.optional(v.number()),
23
- fields: v.optional(v.any()),
24
- uploads: v.optional(v.any()),
25
- results: v.optional(v.any()),
23
+ fields: v.optional(v.record(v.string(), v.any())),
24
+ uploads: v.optional(v.array(v.any())),
25
+ results: v.optional(v.record(v.string(), v.array(v.any()))),
26
26
  error: v.optional(v.any()),
27
27
  raw: v.optional(v.any()),
28
28
  createdAt: v.number(),