@transloadit/convex 0.0.2 → 0.0.4

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 (87) hide show
  1. package/README.md +184 -121
  2. package/dist/client/index.d.ts +100 -60
  3. package/dist/client/index.d.ts.map +1 -1
  4. package/dist/client/index.js +69 -31
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/component/_generated/api.d.ts +2 -2
  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/_generated/dataModel.d.ts +1 -1
  10. package/dist/component/_generated/server.d.ts +1 -1
  11. package/dist/component/apiUtils.d.ts +36 -7
  12. package/dist/component/apiUtils.d.ts.map +1 -1
  13. package/dist/component/apiUtils.js +60 -40
  14. package/dist/component/apiUtils.js.map +1 -1
  15. package/dist/component/lib.d.ts +71 -49
  16. package/dist/component/lib.d.ts.map +1 -1
  17. package/dist/component/lib.js +206 -73
  18. package/dist/component/lib.js.map +1 -1
  19. package/dist/component/schema.d.ts +11 -13
  20. package/dist/component/schema.d.ts.map +1 -1
  21. package/dist/component/schema.js +3 -10
  22. package/dist/component/schema.js.map +1 -1
  23. package/dist/debug/index.d.ts +19 -0
  24. package/dist/debug/index.d.ts.map +1 -0
  25. package/dist/debug/index.js +49 -0
  26. package/dist/debug/index.js.map +1 -0
  27. package/dist/react/index.d.ts +213 -17
  28. package/dist/react/index.d.ts.map +1 -1
  29. package/dist/react/index.js +726 -105
  30. package/dist/react/index.js.map +1 -1
  31. package/dist/shared/assemblyUrls.d.ts +10 -0
  32. package/dist/shared/assemblyUrls.d.ts.map +1 -0
  33. package/dist/shared/assemblyUrls.js +26 -0
  34. package/dist/shared/assemblyUrls.js.map +1 -0
  35. package/dist/shared/errors.d.ts +7 -0
  36. package/dist/shared/errors.d.ts.map +1 -0
  37. package/dist/shared/errors.js +10 -0
  38. package/dist/shared/errors.js.map +1 -0
  39. package/dist/shared/pollAssembly.d.ts +12 -0
  40. package/dist/shared/pollAssembly.d.ts.map +1 -0
  41. package/dist/shared/pollAssembly.js +50 -0
  42. package/dist/shared/pollAssembly.js.map +1 -0
  43. package/dist/shared/resultTypes.d.ts +37 -0
  44. package/dist/shared/resultTypes.d.ts.map +1 -0
  45. package/dist/shared/resultTypes.js +2 -0
  46. package/dist/shared/resultTypes.js.map +1 -0
  47. package/dist/shared/resultUtils.d.ts +4 -0
  48. package/dist/shared/resultUtils.d.ts.map +1 -0
  49. package/dist/shared/resultUtils.js +69 -0
  50. package/dist/shared/resultUtils.js.map +1 -0
  51. package/dist/shared/tusUpload.d.ts +13 -0
  52. package/dist/shared/tusUpload.d.ts.map +1 -0
  53. package/dist/shared/tusUpload.js +32 -0
  54. package/dist/shared/tusUpload.js.map +1 -0
  55. package/dist/test/index.d.ts +65 -0
  56. package/dist/test/index.d.ts.map +1 -0
  57. package/dist/test/index.js +8 -0
  58. package/dist/test/index.js.map +1 -0
  59. package/dist/test/nodeModules.d.ts +2 -0
  60. package/dist/test/nodeModules.d.ts.map +1 -0
  61. package/dist/test/nodeModules.js +19 -0
  62. package/dist/test/nodeModules.js.map +1 -0
  63. package/package.json +53 -15
  64. package/src/client/index.ts +141 -38
  65. package/src/component/_generated/api.ts +2 -2
  66. package/src/component/_generated/component.ts +44 -13
  67. package/src/component/_generated/dataModel.ts +1 -1
  68. package/src/component/_generated/server.ts +1 -1
  69. package/src/component/apiUtils.test.ts +195 -2
  70. package/src/component/apiUtils.ts +124 -66
  71. package/src/component/lib.test.ts +243 -7
  72. package/src/component/lib.ts +302 -90
  73. package/src/component/schema.ts +3 -13
  74. package/src/debug/index.ts +84 -0
  75. package/src/react/index.test.tsx +340 -0
  76. package/src/react/index.tsx +1105 -152
  77. package/src/react/uploadWithTus.test.tsx +192 -0
  78. package/src/shared/assemblyUrls.test.ts +71 -0
  79. package/src/shared/assemblyUrls.ts +59 -0
  80. package/src/shared/errors.ts +23 -0
  81. package/src/shared/pollAssembly.ts +65 -0
  82. package/src/shared/resultTypes.ts +44 -0
  83. package/src/shared/resultUtils.test.ts +29 -0
  84. package/src/shared/resultUtils.ts +71 -0
  85. package/src/shared/tusUpload.ts +59 -0
  86. package/src/test/index.ts +10 -0
  87. package/src/test/nodeModules.ts +19 -0
@@ -1,20 +1,141 @@
1
+ import type { AssemblyStatus } from "@transloadit/zod/v3/assemblyStatus";
2
+ import type { AssemblyInstructionsInput } from "@transloadit/zod/v3/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";
5
+ import { parseAssemblyStatus } from "../shared/assemblyUrls.ts";
6
+ import { transloaditError } from "../shared/errors.ts";
7
+ import { getResultUrl } from "../shared/resultUtils.ts";
3
8
  import {
4
9
  action,
10
+ internalAction,
5
11
  internalMutation,
6
12
  mutation,
7
13
  query,
8
- } from "./_generated/server.js";
14
+ } from "./_generated/server.ts";
9
15
  import {
10
16
  buildTransloaditParams,
11
17
  flattenResults,
12
18
  signTransloaditParams,
13
19
  verifyWebhookSignature,
14
- } from "./apiUtils.js";
20
+ } from "./apiUtils.ts";
15
21
 
16
22
  const TRANSLOADIT_ASSEMBLY_URL = "https://api2.transloadit.com/assemblies";
17
23
 
24
+ type ProcessWebhookResult = {
25
+ assemblyId: string;
26
+ resultCount: number;
27
+ ok?: string;
28
+ status?: string;
29
+ };
30
+
31
+ type InternalApi = {
32
+ lib: {
33
+ upsertAssembly: FunctionReference<
34
+ "mutation",
35
+ "internal",
36
+ Record<string, unknown>,
37
+ unknown
38
+ >;
39
+ replaceResultsForAssembly: FunctionReference<
40
+ "mutation",
41
+ "internal",
42
+ Record<string, unknown>,
43
+ unknown
44
+ >;
45
+ processWebhook: FunctionReference<
46
+ "action",
47
+ "internal",
48
+ Record<string, unknown>,
49
+ ProcessWebhookResult
50
+ >;
51
+ };
52
+ };
53
+
54
+ const internal = anyApi as unknown as InternalApi;
55
+
56
+ const resolveAssemblyId = (payload: AssemblyStatus): string => {
57
+ if (typeof payload.assembly_id === "string") return payload.assembly_id;
58
+ if (typeof payload.assemblyId === "string") return payload.assemblyId;
59
+ return "";
60
+ };
61
+
62
+ const parseAssemblyPayload = (payload: unknown): AssemblyStatus => {
63
+ const parsed = parseAssemblyStatus(payload);
64
+ if (!parsed) {
65
+ throw transloaditError("payload", "Invalid Transloadit payload");
66
+ }
67
+ return parsed;
68
+ };
69
+
70
+ const resolveWebhookRawBody = (args: {
71
+ payload: unknown;
72
+ rawBody?: string;
73
+ verifySignature?: boolean;
74
+ }) => {
75
+ if (typeof args.rawBody === "string") return args.rawBody;
76
+ if (args.verifySignature === false) {
77
+ return JSON.stringify(args.payload ?? {});
78
+ }
79
+ return null;
80
+ };
81
+
82
+ const buildSignedAssemblyUrl = async (
83
+ assemblyId: string,
84
+ authKey: string,
85
+ authSecret: string,
86
+ ): Promise<string> => {
87
+ const params = JSON.stringify({
88
+ auth: {
89
+ key: authKey,
90
+ expires: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
91
+ },
92
+ });
93
+ const signature = await signTransloaditParams(params, authSecret);
94
+ const url = new URL(`${TRANSLOADIT_ASSEMBLY_URL}/${assemblyId}`);
95
+ url.searchParams.set("signature", signature);
96
+ url.searchParams.set("params", params);
97
+ return url.toString();
98
+ };
99
+
100
+ const applyAssemblyStatus = async (
101
+ ctx: Pick<import("./_generated/server.ts").FunctionCtx, "runMutation">,
102
+ payload: AssemblyStatus,
103
+ ) => {
104
+ const assemblyId = resolveAssemblyId(payload);
105
+ if (!assemblyId) {
106
+ throw transloaditError("webhook", "Webhook payload missing assembly_id");
107
+ }
108
+
109
+ const results = flattenResults(payload.results ?? undefined);
110
+
111
+ await ctx.runMutation(internal.lib.upsertAssembly, {
112
+ assemblyId,
113
+ status: typeof payload.ok === "string" ? payload.ok : undefined,
114
+ ok: typeof payload.ok === "string" ? payload.ok : undefined,
115
+ message: typeof payload.message === "string" ? payload.message : undefined,
116
+ templateId:
117
+ typeof payload.template_id === "string" ? payload.template_id : undefined,
118
+ notifyUrl:
119
+ typeof payload.notify_url === "string" ? payload.notify_url : undefined,
120
+ uploads: payload.uploads,
121
+ results: payload.results,
122
+ error: payload.error,
123
+ raw: payload,
124
+ });
125
+
126
+ await ctx.runMutation(internal.lib.replaceResultsForAssembly, {
127
+ assemblyId,
128
+ results,
129
+ });
130
+
131
+ return {
132
+ assemblyId,
133
+ resultCount: results.length,
134
+ ok: typeof payload.ok === "string" ? payload.ok : undefined,
135
+ status: typeof payload.ok === "string" ? payload.ok : undefined,
136
+ };
137
+ };
138
+
18
139
  export const vAssembly = v.object({
19
140
  _id: v.id("assemblies"),
20
141
  _creationTime: v.number(),
@@ -25,9 +146,9 @@ export const vAssembly = v.object({
25
146
  templateId: v.optional(v.string()),
26
147
  notifyUrl: v.optional(v.string()),
27
148
  numExpectedUploadFiles: v.optional(v.number()),
28
- fields: v.optional(v.any()),
29
- uploads: v.optional(v.any()),
30
- results: v.optional(v.any()),
149
+ fields: v.optional(v.record(v.string(), v.any())),
150
+ uploads: v.optional(v.array(v.any())),
151
+ results: v.optional(v.record(v.string(), v.array(v.any()))),
31
152
  error: v.optional(v.any()),
32
153
  raw: v.optional(v.any()),
33
154
  createdAt: v.number(),
@@ -60,12 +181,12 @@ export const vTransloaditConfig = v.object({
60
181
 
61
182
  const vAssemblyBaseArgs = {
62
183
  templateId: v.optional(v.string()),
63
- steps: v.optional(v.any()),
64
- fields: v.optional(v.any()),
184
+ steps: v.optional(v.record(v.string(), v.any())),
185
+ fields: v.optional(v.record(v.string(), v.any())),
65
186
  notifyUrl: v.optional(v.string()),
66
187
  numExpectedUploadFiles: v.optional(v.number()),
67
188
  expires: v.optional(v.string()),
68
- additionalParams: v.optional(v.any()),
189
+ additionalParams: v.optional(v.record(v.string(), v.any())),
69
190
  userId: v.optional(v.string()),
70
191
  };
71
192
 
@@ -78,15 +199,18 @@ export const upsertAssembly = internalMutation({
78
199
  templateId: v.optional(v.string()),
79
200
  notifyUrl: v.optional(v.string()),
80
201
  numExpectedUploadFiles: v.optional(v.number()),
81
- fields: v.optional(v.any()),
82
- uploads: v.optional(v.any()),
83
- results: v.optional(v.any()),
202
+ fields: v.optional(v.record(v.string(), v.any())),
203
+ uploads: v.optional(v.array(v.any())),
204
+ results: v.optional(v.record(v.string(), v.array(v.any()))),
84
205
  error: v.optional(v.any()),
85
206
  raw: v.optional(v.any()),
86
207
  userId: v.optional(v.string()),
87
208
  },
88
209
  returns: v.id("assemblies"),
89
210
  handler: async (ctx, args) => {
211
+ // Note: we persist full `raw` + `results` for debugging/fidelity. Large
212
+ // assemblies can hit Convex document size limits; trim or externalize
213
+ // payloads if this becomes an issue for your workload.
90
214
  const existing = await ctx.db
91
215
  .query("assemblies")
92
216
  .withIndex("by_assemblyId", (q) => q.eq("assemblyId", args.assemblyId))
@@ -146,6 +270,10 @@ export const replaceResultsForAssembly = internalMutation({
146
270
  },
147
271
  returns: v.null(),
148
272
  handler: async (ctx, args) => {
273
+ // We store raw result payloads for fidelity. For very large assemblies,
274
+ // consider trimming or externalizing these fields to avoid size limits.
275
+ // This mutation replaces all results in one transaction; extremely large
276
+ // result sets may need batching or external storage to avoid Convex limits.
149
277
  const existingResults = await ctx.db
150
278
  .query("results")
151
279
  .withIndex("by_assemblyId", (q) => q.eq("assemblyId", args.assemblyId))
@@ -158,11 +286,12 @@ export const replaceResultsForAssembly = internalMutation({
158
286
  const now = Date.now();
159
287
  for (const entry of args.results) {
160
288
  const raw = entry.result as Record<string, unknown>;
289
+ const sslUrl = getResultUrl(entry.result);
161
290
  await ctx.db.insert("results", {
162
291
  assemblyId: args.assemblyId,
163
292
  stepName: entry.stepName,
164
293
  resultId: typeof raw.id === "string" ? raw.id : undefined,
165
- sslUrl: typeof raw.ssl_url === "string" ? raw.ssl_url : undefined,
294
+ sslUrl,
166
295
  name: typeof raw.name === "string" ? raw.name : undefined,
167
296
  size: typeof raw.size === "number" ? raw.size : undefined,
168
297
  mime: typeof raw.mime === "string" ? raw.mime : undefined,
@@ -188,10 +317,10 @@ export const createAssembly = action({
188
317
  const { paramsString, params } = buildTransloaditParams({
189
318
  authKey: args.config.authKey,
190
319
  templateId: args.templateId,
191
- steps: args.steps as Record<string, unknown> | undefined,
192
- fields: args.fields as Record<string, unknown> | undefined,
320
+ steps: args.steps as AssemblyInstructionsInput["steps"],
321
+ fields: args.fields as AssemblyInstructionsInput["fields"],
193
322
  notifyUrl: args.notifyUrl,
194
- numExpectedUploadFiles: args.numExpectedUploadFiles,
323
+ numExpectedUploadFiles: undefined,
195
324
  expires: args.expires,
196
325
  additionalParams: args.additionalParams as
197
326
  | Record<string, unknown>
@@ -206,6 +335,12 @@ export const createAssembly = action({
206
335
  const formData = new FormData();
207
336
  formData.append("params", paramsString);
208
337
  formData.append("signature", signature);
338
+ if (typeof args.numExpectedUploadFiles === "number") {
339
+ formData.append(
340
+ "tus_num_expected_upload_files",
341
+ String(args.numExpectedUploadFiles),
342
+ );
343
+ }
209
344
 
210
345
  const response = await fetch(TRANSLOADIT_ASSEMBLY_URL, {
211
346
  method: "POST",
@@ -214,8 +349,9 @@ export const createAssembly = action({
214
349
 
215
350
  const data = (await response.json()) as Record<string, unknown>;
216
351
  if (!response.ok) {
217
- throw new Error(
218
- `Transloadit error ${response.status}: ${JSON.stringify(data)}`,
352
+ throw transloaditError(
353
+ "createAssembly",
354
+ `HTTP ${response.status}: ${JSON.stringify(data)}`,
219
355
  );
220
356
  }
221
357
 
@@ -227,7 +363,10 @@ export const createAssembly = action({
227
363
  : "";
228
364
 
229
365
  if (!assemblyId) {
230
- throw new Error("Transloadit response missing assembly_id");
366
+ throw transloaditError(
367
+ "createAssembly",
368
+ "Transloadit response missing assembly_id",
369
+ );
231
370
  }
232
371
 
233
372
  await ctx.runMutation(internal.lib.upsertAssembly, {
@@ -250,49 +389,68 @@ export const createAssembly = action({
250
389
  },
251
390
  });
252
391
 
253
- export const generateUploadParams = action({
254
- args: {
255
- config: vTransloaditConfig,
256
- ...vAssemblyBaseArgs,
257
- },
392
+ const vWebhookArgs = {
393
+ payload: v.any(),
394
+ rawBody: v.optional(v.string()),
395
+ signature: v.optional(v.string()),
396
+ verifySignature: v.optional(v.boolean()),
397
+ authSecret: v.optional(v.string()),
398
+ };
399
+
400
+ const vPublicWebhookArgs = {
401
+ payload: v.any(),
402
+ rawBody: v.optional(v.string()),
403
+ signature: v.optional(v.string()),
404
+ verifySignature: v.optional(v.boolean()),
405
+ };
406
+
407
+ export const processWebhook = internalAction({
408
+ args: vWebhookArgs,
258
409
  returns: v.object({
259
- params: v.string(),
260
- signature: v.string(),
261
- url: v.string(),
410
+ assemblyId: v.string(),
411
+ resultCount: v.number(),
412
+ ok: v.optional(v.string()),
413
+ status: v.optional(v.string()),
262
414
  }),
263
- handler: async (_ctx, args) => {
264
- const { paramsString } = buildTransloaditParams({
265
- authKey: args.config.authKey,
266
- templateId: args.templateId,
267
- steps: args.steps as Record<string, unknown> | undefined,
268
- fields: args.fields as Record<string, unknown> | undefined,
269
- notifyUrl: args.notifyUrl,
270
- numExpectedUploadFiles: args.numExpectedUploadFiles,
271
- expires: args.expires,
272
- additionalParams: args.additionalParams as
273
- | Record<string, unknown>
274
- | undefined,
275
- });
415
+ handler: async (ctx, args) => {
416
+ const rawBody = resolveWebhookRawBody(args);
417
+ const shouldVerify = args.verifySignature ?? true;
418
+ const authSecret = args.authSecret ?? process.env.TRANSLOADIT_SECRET;
276
419
 
277
- const signature = await signTransloaditParams(
278
- paramsString,
279
- args.config.authSecret,
280
- );
420
+ if (shouldVerify) {
421
+ if (!rawBody) {
422
+ throw transloaditError(
423
+ "webhook",
424
+ "Missing rawBody for webhook verification",
425
+ );
426
+ }
427
+ if (!authSecret) {
428
+ throw transloaditError(
429
+ "webhook",
430
+ "Missing TRANSLOADIT_SECRET for webhook validation",
431
+ );
432
+ }
433
+ const verified = await verifyWebhookSignature({
434
+ rawBody,
435
+ signatureHeader: args.signature,
436
+ authSecret,
437
+ });
438
+ if (!verified) {
439
+ throw transloaditError(
440
+ "webhook",
441
+ "Invalid Transloadit webhook signature",
442
+ );
443
+ }
444
+ }
281
445
 
282
- return {
283
- params: paramsString,
284
- signature,
285
- url: TRANSLOADIT_ASSEMBLY_URL,
286
- };
446
+ const parsed = parseAssemblyPayload(args.payload);
447
+ return applyAssemblyStatus(ctx, parsed);
287
448
  },
288
449
  });
289
450
 
290
451
  export const handleWebhook = action({
291
452
  args: {
292
- payload: v.any(),
293
- rawBody: v.optional(v.string()),
294
- signature: v.optional(v.string()),
295
- verifySignature: v.optional(v.boolean()),
453
+ ...vPublicWebhookArgs,
296
454
  config: v.optional(
297
455
  v.object({
298
456
  authSecret: v.string(),
@@ -302,19 +460,51 @@ export const handleWebhook = action({
302
460
  returns: v.object({
303
461
  assemblyId: v.string(),
304
462
  resultCount: v.number(),
463
+ ok: v.optional(v.string()),
464
+ status: v.optional(v.string()),
305
465
  }),
306
466
  handler: async (ctx, args) => {
307
- const rawBody = args.rawBody ?? JSON.stringify(args.payload ?? {});
467
+ const verifySignature = args.verifySignature ?? true;
468
+ return ctx.runAction(internal.lib.processWebhook, {
469
+ payload: args.payload,
470
+ rawBody: args.rawBody,
471
+ signature: args.signature,
472
+ verifySignature,
473
+ authSecret: args.config?.authSecret,
474
+ });
475
+ },
476
+ });
477
+
478
+ export const queueWebhook = action({
479
+ args: {
480
+ ...vPublicWebhookArgs,
481
+ config: v.optional(
482
+ v.object({
483
+ authSecret: v.string(),
484
+ }),
485
+ ),
486
+ },
487
+ returns: v.object({
488
+ assemblyId: v.string(),
489
+ queued: v.boolean(),
490
+ }),
491
+ handler: async (ctx, args) => {
492
+ const rawBody = resolveWebhookRawBody(args);
308
493
  const shouldVerify = args.verifySignature ?? true;
309
494
  const authSecret =
310
- args.config?.authSecret ??
311
- process.env.TRANSLOADIT_AUTH_SECRET ??
312
- process.env.TRANSLOADIT_SECRET;
495
+ args.config?.authSecret ?? process.env.TRANSLOADIT_SECRET;
313
496
 
314
497
  if (shouldVerify) {
498
+ if (!rawBody) {
499
+ throw transloaditError(
500
+ "webhook",
501
+ "Missing rawBody for webhook verification",
502
+ );
503
+ }
315
504
  if (!authSecret) {
316
- throw new Error(
317
- "Missing TRANSLOADIT_AUTH_SECRET for webhook validation",
505
+ throw transloaditError(
506
+ "webhook",
507
+ "Missing TRANSLOADIT_SECRET for webhook validation",
318
508
  );
319
509
  }
320
510
  const verified = await verifyWebhookSignature({
@@ -323,45 +513,67 @@ export const handleWebhook = action({
323
513
  authSecret,
324
514
  });
325
515
  if (!verified) {
326
- throw new Error("Invalid Transloadit webhook signature");
516
+ throw transloaditError(
517
+ "webhook",
518
+ "Invalid Transloadit webhook signature",
519
+ );
327
520
  }
328
521
  }
329
522
 
330
- const payload = args.payload as Record<string, unknown>;
331
- const assemblyId =
332
- typeof payload.assembly_id === "string"
333
- ? payload.assembly_id
334
- : typeof payload.assemblyId === "string"
335
- ? payload.assemblyId
336
- : "";
337
-
523
+ const parsed = parseAssemblyPayload(args.payload);
524
+ const assemblyId = resolveAssemblyId(parsed);
338
525
  if (!assemblyId) {
339
- throw new Error("Webhook payload missing assembly_id");
526
+ throw transloaditError("webhook", "Webhook payload missing assembly_id");
340
527
  }
341
528
 
342
- const results = flattenResults(
343
- (payload.results as Record<string, Array<Record<string, unknown>>>) ??
344
- undefined,
345
- );
346
-
347
- await ctx.runMutation(internal.lib.upsertAssembly, {
348
- assemblyId,
349
- status: typeof payload.ok === "string" ? payload.ok : undefined,
350
- ok: typeof payload.ok === "string" ? payload.ok : undefined,
351
- message:
352
- typeof payload.message === "string" ? payload.message : undefined,
353
- uploads: payload.uploads,
354
- results: payload.results,
355
- error: payload.error,
356
- raw: payload,
529
+ await ctx.scheduler.runAfter(0, internal.lib.processWebhook, {
530
+ payload: parsed,
531
+ rawBody: args.rawBody,
532
+ signature: args.signature,
533
+ verifySignature: true,
534
+ authSecret: args.config?.authSecret,
357
535
  });
358
536
 
359
- await ctx.runMutation(internal.lib.replaceResultsForAssembly, {
360
- assemblyId,
361
- results,
362
- });
537
+ return { assemblyId, queued: true };
538
+ },
539
+ });
540
+
541
+ export const refreshAssembly = action({
542
+ args: {
543
+ assemblyId: v.string(),
544
+ config: v.optional(
545
+ v.object({
546
+ authKey: v.string(),
547
+ authSecret: v.string(),
548
+ }),
549
+ ),
550
+ },
551
+ returns: v.object({
552
+ assemblyId: v.string(),
553
+ resultCount: v.number(),
554
+ ok: v.optional(v.string()),
555
+ status: v.optional(v.string()),
556
+ }),
557
+ handler: async (ctx, args) => {
558
+ const { assemblyId } = args;
559
+ const authKey = args.config?.authKey ?? process.env.TRANSLOADIT_KEY;
560
+ const authSecret =
561
+ args.config?.authSecret ?? process.env.TRANSLOADIT_SECRET;
562
+ const url =
563
+ authKey && authSecret
564
+ ? await buildSignedAssemblyUrl(assemblyId, authKey, authSecret)
565
+ : `${TRANSLOADIT_ASSEMBLY_URL}/${assemblyId}`;
566
+
567
+ const response = await fetch(url);
568
+ const payload = parseAssemblyPayload(await response.json());
569
+ if (!response.ok) {
570
+ throw transloaditError(
571
+ "status",
572
+ `HTTP ${response.status}: ${JSON.stringify(payload)}`,
573
+ );
574
+ }
363
575
 
364
- return { assemblyId, resultCount: results.length };
576
+ return applyAssemblyStatus(ctx, payload);
365
577
  },
366
578
  });
367
579
 
@@ -437,7 +649,7 @@ export const storeAssemblyMetadata = mutation({
437
649
  args: {
438
650
  assemblyId: v.string(),
439
651
  userId: v.optional(v.string()),
440
- fields: v.optional(v.any()),
652
+ fields: v.optional(v.record(v.string(), v.any())),
441
653
  },
442
654
  returns: v.union(vAssembly, v.null()),
443
655
  handler: async (ctx, args) => {
@@ -1,16 +1,6 @@
1
1
  import { defineSchema, defineTable } from "convex/server";
2
2
  import { v } from "convex/values";
3
3
 
4
- export const assemblyStatusValues = [
5
- "ASSEMBLY_UPLOADING",
6
- "ASSEMBLY_EXECUTING",
7
- "ASSEMBLY_COMPLETED",
8
- "ASSEMBLY_CANCELED",
9
- "ASSEMBLY_FAILED",
10
- ] as const;
11
-
12
- export type AssemblyStatus = (typeof assemblyStatusValues)[number];
13
-
14
4
  export default defineSchema({
15
5
  assemblies: defineTable({
16
6
  assemblyId: v.string(),
@@ -20,9 +10,9 @@ export default defineSchema({
20
10
  templateId: v.optional(v.string()),
21
11
  notifyUrl: v.optional(v.string()),
22
12
  numExpectedUploadFiles: v.optional(v.number()),
23
- fields: v.optional(v.any()),
24
- uploads: v.optional(v.any()),
25
- results: v.optional(v.any()),
13
+ fields: v.optional(v.record(v.string(), v.any())),
14
+ uploads: v.optional(v.array(v.any())),
15
+ results: v.optional(v.record(v.string(), v.array(v.any()))),
26
16
  error: v.optional(v.any()),
27
17
  raw: v.optional(v.any()),
28
18
  createdAt: v.number(),
@@ -0,0 +1,84 @@
1
+ type ConsoleSink = Pick<Console, "log" | "info" | "warn" | "error">;
2
+
3
+ export type DebugLogger = {
4
+ enabled: boolean;
5
+ log: (message: string, meta?: Record<string, unknown>) => void;
6
+ info: (message: string, meta?: Record<string, unknown>) => void;
7
+ warn: (message: string, meta?: Record<string, unknown>) => void;
8
+ error: (message: string, meta?: Record<string, unknown>) => void;
9
+ event: (name: string, meta?: Record<string, unknown>) => void;
10
+ child: (namespace: string) => DebugLogger;
11
+ };
12
+
13
+ export type DebugLoggerOptions = {
14
+ namespace?: string;
15
+ enabled?: boolean;
16
+ sink?: ConsoleSink;
17
+ clock?: () => Date;
18
+ };
19
+
20
+ const resolveEnv = (): Record<string, string | undefined> => {
21
+ if (typeof process !== "undefined" && process.env) {
22
+ return process.env;
23
+ }
24
+ return {};
25
+ };
26
+
27
+ const parseEnabled = (value: string | undefined) => {
28
+ if (!value) return false;
29
+ const normalized = value.trim().toLowerCase();
30
+ return normalized === "1" || normalized === "true" || normalized === "yes";
31
+ };
32
+
33
+ const formatLine = (
34
+ timestamp: string,
35
+ prefix: string,
36
+ message: string,
37
+ meta?: Record<string, unknown>,
38
+ ) => {
39
+ if (!meta || Object.keys(meta).length === 0) {
40
+ return `${timestamp} ${prefix} ${message}`;
41
+ }
42
+ return `${timestamp} ${prefix} ${message} ${JSON.stringify(meta)}`;
43
+ };
44
+
45
+ export const createDebugLogger = (
46
+ options: DebugLoggerOptions = {},
47
+ ): DebugLogger => {
48
+ const env = resolveEnv();
49
+ const enabled =
50
+ options.enabled ??
51
+ (parseEnabled(env.TRANSLOADIT_DEBUG) ||
52
+ parseEnabled(env.CONVEX_TRANSLOADIT_DEBUG));
53
+ const namespace = options.namespace ?? "convex";
54
+ const prefix = `[transloadit:${namespace}]`;
55
+ const sink: ConsoleSink = options.sink ?? console;
56
+ const clock = options.clock ?? (() => new Date());
57
+
58
+ const emit = (
59
+ level: "log" | "info" | "warn" | "error",
60
+ message: string,
61
+ meta?: Record<string, unknown>,
62
+ ) => {
63
+ if (!enabled) return;
64
+ const line = formatLine(clock().toISOString(), prefix, message, meta);
65
+ sink[level](line);
66
+ };
67
+
68
+ const logger: DebugLogger = {
69
+ enabled,
70
+ log: (message, meta) => emit("log", message, meta),
71
+ info: (message, meta) => emit("info", message, meta),
72
+ warn: (message, meta) => emit("warn", message, meta),
73
+ error: (message, meta) => emit("error", message, meta),
74
+ event: (name, meta) => emit("info", `event:${name}`, meta),
75
+ child: (childNamespace) =>
76
+ createDebugLogger({
77
+ ...options,
78
+ namespace: `${namespace}:${childNamespace}`,
79
+ enabled,
80
+ }),
81
+ };
82
+
83
+ return logger;
84
+ };