@beignet/core 0.0.30 → 0.0.32

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 (74) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +48 -2
  3. package/dist/domain/entity.d.ts +37 -6
  4. package/dist/domain/entity.d.ts.map +1 -1
  5. package/dist/domain/entity.js +9 -7
  6. package/dist/domain/entity.js.map +1 -1
  7. package/dist/domain/index.d.ts +4 -4
  8. package/dist/domain/index.d.ts.map +1 -1
  9. package/dist/domain/index.js +4 -4
  10. package/dist/domain/index.js.map +1 -1
  11. package/dist/flags/index.d.ts +5 -4
  12. package/dist/flags/index.d.ts.map +1 -1
  13. package/dist/flags/index.js +10 -9
  14. package/dist/flags/index.js.map +1 -1
  15. package/dist/idempotency/index.d.ts +11 -1
  16. package/dist/idempotency/index.d.ts.map +1 -1
  17. package/dist/idempotency/index.js +4 -3
  18. package/dist/idempotency/index.js.map +1 -1
  19. package/dist/jobs/index.d.ts +35 -6
  20. package/dist/jobs/index.d.ts.map +1 -1
  21. package/dist/jobs/index.js +82 -11
  22. package/dist/jobs/index.js.map +1 -1
  23. package/dist/memo/index.d.ts +107 -0
  24. package/dist/memo/index.d.ts.map +1 -0
  25. package/dist/memo/index.js +205 -0
  26. package/dist/memo/index.js.map +1 -0
  27. package/dist/outbox/index.d.ts +20 -2
  28. package/dist/outbox/index.d.ts.map +1 -1
  29. package/dist/outbox/index.js +23 -8
  30. package/dist/outbox/index.js.map +1 -1
  31. package/dist/ports/events.d.ts +2 -2
  32. package/dist/providers/provider.d.ts +10 -2
  33. package/dist/providers/provider.d.ts.map +1 -1
  34. package/dist/providers/provider.js.map +1 -1
  35. package/dist/search/index.d.ts +2 -1
  36. package/dist/search/index.d.ts.map +1 -1
  37. package/dist/search/index.js +1 -0
  38. package/dist/search/index.js.map +1 -1
  39. package/dist/server/providers/loadProviderConfig.d.ts.map +1 -1
  40. package/dist/server/providers/loadProviderConfig.js +15 -2
  41. package/dist/server/providers/loadProviderConfig.js.map +1 -1
  42. package/dist/server/server.d.ts +1 -1
  43. package/dist/server/server.d.ts.map +1 -1
  44. package/dist/server/server.js +43 -3
  45. package/dist/server/server.js.map +1 -1
  46. package/dist/testing/index.d.ts +6 -0
  47. package/dist/testing/index.d.ts.map +1 -1
  48. package/dist/testing/index.js +12 -3
  49. package/dist/testing/index.js.map +1 -1
  50. package/dist/uploads/index.d.ts +111 -2
  51. package/dist/uploads/index.d.ts.map +1 -1
  52. package/dist/uploads/index.js +124 -61
  53. package/dist/uploads/index.js.map +1 -1
  54. package/dist/webhooks/index.d.ts +1 -0
  55. package/dist/webhooks/index.d.ts.map +1 -1
  56. package/dist/webhooks/index.js +1 -0
  57. package/dist/webhooks/index.js.map +1 -1
  58. package/package.json +5 -1
  59. package/src/domain/entity.ts +61 -13
  60. package/src/domain/index.ts +8 -7
  61. package/src/flags/index.ts +23 -19
  62. package/src/idempotency/index.ts +17 -3
  63. package/src/jobs/index.ts +131 -16
  64. package/src/memo/index.ts +310 -0
  65. package/src/outbox/index.ts +52 -8
  66. package/src/ports/events.ts +2 -2
  67. package/src/providers/provider.ts +11 -2
  68. package/src/search/index.ts +3 -1
  69. package/src/server/providers/loadProviderConfig.ts +19 -2
  70. package/src/server/server.ts +73 -18
  71. package/src/testing/index.ts +19 -3
  72. package/src/uploads/index.ts +174 -62
  73. package/src/webhooks/index.ts +2 -0
  74. package/src/domain/events.ts +0 -59
@@ -66,6 +66,7 @@ import type {
66
66
  } from "../providers/provider.js";
67
67
  import { createMemorySearch, type MemorySearchPort } from "../search/index.js";
68
68
  import { createAmbientAuditLog } from "../server/audit-context.js";
69
+ import { loadProviderConfig } from "../server/providers/loadProviderConfig.js";
69
70
  import {
70
71
  clearActiveRequestContext,
71
72
  enterActiveRequestContext,
@@ -377,8 +378,10 @@ export function createTestPorts<
377
378
  const errorReporter = createMemoryErrorReporter();
378
379
  const flags = createMemoryFlags();
379
380
  const { jobs, dispatchedJobs } = createRecordingJobDispatcher();
380
- const idempotency = createMemoryIdempotencyStore();
381
381
  const ids = options.ids ?? createUuidIdGenerator();
382
+ const idempotency = createMemoryIdempotencyStore({
383
+ now: () => clock.now(),
384
+ });
382
385
  const logger = createNoopLogger();
383
386
  const locks = createMemoryLocks({
384
387
  now: () => clock.now(),
@@ -386,7 +389,10 @@ export function createTestPorts<
386
389
  });
387
390
  const mailer = createMemoryMailer();
388
391
  const notifications = createMemoryNotificationPort();
389
- const outbox = createMemoryOutbox();
392
+ const outbox = createMemoryOutbox({
393
+ id: () => ids.nextId(),
394
+ now: () => clock.now(),
395
+ });
390
396
  const payments = createMemoryPayments();
391
397
  const rateLimit = createMemoryRateLimiter();
392
398
  const search = createMemorySearch();
@@ -983,6 +989,12 @@ function createUnboundPortGuard<Ports extends AnyPorts>(ports: Ports): Ports {
983
989
  * Options for `installProviderForTest(...)`.
984
990
  */
985
991
  export interface InstallProviderForTestOptions {
992
+ /**
993
+ * Environment variables used to resolve the provider's config through the
994
+ * same loader the server uses (including the provider's `overrides`).
995
+ * Ignored when `config` is passed explicitly.
996
+ */
997
+ env?: Record<string, string | undefined>;
986
998
  /**
987
999
  * Base app ports visible to provider setup, such as `{ devtools }`.
988
1000
  */
@@ -1047,9 +1059,13 @@ export async function installProviderForTest(
1047
1059
  "Pass createServiceContext to installProviderForTest(...) when the test needs a service context.",
1048
1060
  );
1049
1061
  });
1062
+ const config =
1063
+ options.config !== undefined
1064
+ ? options.config
1065
+ : await loadProviderConfig(provider, options.env ?? {}, {});
1050
1066
  const result = await provider.setup({
1051
1067
  ports: basePorts,
1052
- config: options.config,
1068
+ config,
1053
1069
  createServiceContext,
1054
1070
  });
1055
1071
  const ports: Record<string, unknown> = { ...basePorts, ...result.ports };
@@ -452,14 +452,19 @@ export type UploadErrorCode =
452
452
  | "INVALID_UPLOAD_BODY";
453
453
 
454
454
  /**
455
- * Error thrown for expected upload failures.
455
+ * Base error for expected upload failures.
456
+ *
457
+ * Upload workflows throw one of the specific subclasses, such as
458
+ * {@link UploadNotFoundError} or {@link UnauthorizedUploadError}. Catch this
459
+ * base class to handle any expected upload failure; the upload router maps it
460
+ * to an HTTP response using `code`, `status`, and `details`.
456
461
  */
457
462
  export class UploadError extends Error {
458
463
  readonly code: UploadErrorCode;
459
464
  readonly status: number;
460
465
  readonly details?: unknown;
461
466
 
462
- constructor(args: {
467
+ protected constructor(args: {
463
468
  code: UploadErrorCode;
464
469
  message: string;
465
470
  status?: number;
@@ -473,6 +478,111 @@ export class UploadError extends Error {
473
478
  }
474
479
  }
475
480
 
481
+ /**
482
+ * Error thrown when an upload name is not registered on the router.
483
+ */
484
+ export class UploadNotFoundError extends UploadError {
485
+ declare readonly code: "UPLOAD_NOT_FOUND";
486
+
487
+ constructor(args: { message: string; details?: unknown }) {
488
+ super({ code: "UPLOAD_NOT_FOUND", status: 404, ...args });
489
+ this.name = "UploadNotFoundError";
490
+ }
491
+ }
492
+
493
+ /**
494
+ * Error thrown when an upload request targets an unknown upload action.
495
+ */
496
+ export class InvalidUploadActionError extends UploadError {
497
+ declare readonly code: "INVALID_UPLOAD_ACTION";
498
+
499
+ constructor(args: { message: string; details?: unknown }) {
500
+ super({ code: "INVALID_UPLOAD_ACTION", status: 400, ...args });
501
+ this.name = "InvalidUploadActionError";
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Error thrown when upload metadata fails schema validation.
507
+ */
508
+ export class InvalidUploadMetadataError extends UploadError {
509
+ declare readonly code: "INVALID_UPLOAD_METADATA";
510
+
511
+ constructor(args: { message: string; details?: unknown }) {
512
+ super({ code: "INVALID_UPLOAD_METADATA", status: 422, ...args });
513
+ this.name = "InvalidUploadMetadataError";
514
+ }
515
+ }
516
+
517
+ /**
518
+ * Error thrown when a file fails upload validation, such as file count,
519
+ * content type (415), size limits (413), or prepared-file mismatches.
520
+ */
521
+ export class InvalidUploadFileError extends UploadError {
522
+ declare readonly code: "INVALID_UPLOAD_FILE";
523
+
524
+ constructor(args: { message: string; details?: unknown; status?: number }) {
525
+ super({
526
+ code: "INVALID_UPLOAD_FILE",
527
+ message: args.message,
528
+ status: args.status ?? 422,
529
+ details: args.details,
530
+ });
531
+ this.name = "InvalidUploadFileError";
532
+ }
533
+ }
534
+
535
+ /**
536
+ * Error thrown when an upload is denied by authorize(...) or lacks
537
+ * authorization configuration.
538
+ */
539
+ export class UnauthorizedUploadError extends UploadError {
540
+ declare readonly code: "UNAUTHORIZED_UPLOAD";
541
+
542
+ constructor(args: { message: string; details?: unknown }) {
543
+ super({ code: "UNAUTHORIZED_UPLOAD", status: 403, ...args });
544
+ this.name = "UnauthorizedUploadError";
545
+ }
546
+ }
547
+
548
+ /**
549
+ * Error thrown when a completed upload references a stored object that does
550
+ * not exist.
551
+ */
552
+ export class UploadObjectNotFoundError extends UploadError {
553
+ declare readonly code: "UPLOAD_OBJECT_NOT_FOUND";
554
+
555
+ constructor(args: { message: string; details?: unknown }) {
556
+ super({ code: "UPLOAD_OBJECT_NOT_FOUND", status: 404, ...args });
557
+ this.name = "UploadObjectNotFoundError";
558
+ }
559
+ }
560
+
561
+ /**
562
+ * Error thrown when an upload request body exceeds the configured size limit.
563
+ */
564
+ export class UploadBodyTooLargeError extends UploadError {
565
+ declare readonly code: "UPLOAD_BODY_TOO_LARGE";
566
+
567
+ constructor(args: { message: string; details?: unknown }) {
568
+ super({ code: "UPLOAD_BODY_TOO_LARGE", status: 413, ...args });
569
+ this.name = "UploadBodyTooLargeError";
570
+ }
571
+ }
572
+
573
+ /**
574
+ * Error thrown when an upload request body cannot be parsed or has an
575
+ * invalid shape.
576
+ */
577
+ export class InvalidUploadBodyError extends UploadError {
578
+ declare readonly code: "INVALID_UPLOAD_BODY";
579
+
580
+ constructor(args: { message: string; details?: unknown }) {
581
+ super({ code: "INVALID_UPLOAD_BODY", status: 400, ...args });
582
+ this.name = "InvalidUploadBodyError";
583
+ }
584
+ }
585
+
476
586
  /**
477
587
  * Define a typed upload workflow.
478
588
  */
@@ -507,6 +617,47 @@ export function defineUpload<
507
617
  };
508
618
  }
509
619
 
620
+ /**
621
+ * Context-bound upload helper factory.
622
+ */
623
+ export interface Uploads<Ctx> {
624
+ /**
625
+ * Define an upload workflow with the bound context type.
626
+ */
627
+ defineUpload<
628
+ const Name extends string,
629
+ MetadataSchema extends StandardSchema,
630
+ Result = unknown,
631
+ >(
632
+ name: Name,
633
+ options: DefineUploadOptions<MetadataSchema, Ctx, Result>,
634
+ ): UploadDef<Name, MetadataSchema, Ctx, Result>;
635
+ }
636
+
637
+ /**
638
+ * Create upload helper methods bound to an application context type.
639
+ *
640
+ * Call it once in `lib/uploads.ts`:
641
+ *
642
+ * ```ts
643
+ * export const { defineUpload } = createUploads<AppContext>();
644
+ * ```
645
+ */
646
+ export function createUploads<Ctx>(): Uploads<Ctx> {
647
+ return {
648
+ defineUpload<
649
+ const Name extends string,
650
+ MetadataSchema extends StandardSchema,
651
+ Result = unknown,
652
+ >(
653
+ name: Name,
654
+ options: DefineUploadOptions<MetadataSchema, Ctx, Result>,
655
+ ): UploadDef<Name, MetadataSchema, Ctx, Result> {
656
+ return defineUpload(name, options);
657
+ },
658
+ };
659
+ }
660
+
510
661
  /**
511
662
  * Define a nested upload registry while preserving upload names and metadata
512
663
  * types for client code.
@@ -612,9 +763,7 @@ export function createUploadRouter<Ctx>(
612
763
  const registered = [...uploads.keys()]
613
764
  .map((registeredName) => `"${registeredName}"`)
614
765
  .join(", ");
615
- throw new UploadError({
616
- code: "UPLOAD_NOT_FOUND",
617
- status: 404,
766
+ throw new UploadNotFoundError({
618
767
  message: `Upload "${name}" is not registered. Registered uploads: ${registered || "none"}. Upload routes resolve the defineUpload(...) name, not the defineUploads({...}) registry key.`,
619
768
  });
620
769
  }
@@ -723,9 +872,7 @@ export function createUploadRouter<Ctx>(
723
872
  uploadId: file.uploadId,
724
873
  });
725
874
  if (file.key !== expectedKey) {
726
- throw new UploadError({
727
- code: "INVALID_UPLOAD_FILE",
728
- status: 422,
875
+ throw new InvalidUploadFileError({
729
876
  message: `Uploaded object key does not match upload "${upload.name}".`,
730
877
  details: {
731
878
  expectedKey,
@@ -735,9 +882,7 @@ export function createUploadRouter<Ctx>(
735
882
  }
736
883
  const object = await options.storage.stat(file.key);
737
884
  if (!object) {
738
- throw new UploadError({
739
- code: "UPLOAD_OBJECT_NOT_FOUND",
740
- status: 404,
885
+ throw new UploadObjectNotFoundError({
741
886
  message: `Uploaded object "${file.key}" was not found.`,
742
887
  });
743
888
  }
@@ -970,9 +1115,7 @@ function assertUploadContentLengthWithinLimit(
970
1115
  const actualBytes = Number(contentLength);
971
1116
  if (!Number.isFinite(actualBytes) || actualBytes < 0) return;
972
1117
  if (actualBytes > context.maxBytes) {
973
- throw new UploadError({
974
- code: "UPLOAD_BODY_TOO_LARGE",
975
- status: 413,
1118
+ throw new UploadBodyTooLargeError({
976
1119
  message: `Upload "${context.uploadName}" ${context.action} body is too large.`,
977
1120
  details: {
978
1121
  maxBytes: context.maxBytes,
@@ -996,9 +1139,7 @@ async function readLimitedRequestText(
996
1139
  const text = await request.text();
997
1140
  const actualBytes = new TextEncoder().encode(text).byteLength;
998
1141
  if (actualBytes > context.maxBytes) {
999
- throw new UploadError({
1000
- code: "UPLOAD_BODY_TOO_LARGE",
1001
- status: 413,
1142
+ throw new UploadBodyTooLargeError({
1002
1143
  message: `Upload "${context.uploadName}" ${context.action} body is too large.`,
1003
1144
  details: {
1004
1145
  maxBytes: context.maxBytes,
@@ -1020,9 +1161,7 @@ async function readLimitedRequestText(
1020
1161
  if (result.done) break;
1021
1162
  actualBytes += result.value.byteLength;
1022
1163
  if (actualBytes > context.maxBytes) {
1023
- throw new UploadError({
1024
- code: "UPLOAD_BODY_TOO_LARGE",
1025
- status: 413,
1164
+ throw new UploadBodyTooLargeError({
1026
1165
  message: `Upload "${context.uploadName}" ${context.action} body is too large.`,
1027
1166
  details: {
1028
1167
  maxBytes: context.maxBytes,
@@ -1052,9 +1191,7 @@ async function readJsonBody(
1052
1191
  return JSON.parse(await readLimitedRequestText(request, context));
1053
1192
  } catch (error) {
1054
1193
  if (error instanceof UploadError) throw error;
1055
- throw new UploadError({
1056
- code: "INVALID_UPLOAD_BODY",
1057
- status: 400,
1194
+ throw new InvalidUploadBodyError({
1058
1195
  message: `Upload "${context.uploadName}" ${context.action} body must be valid JSON.`,
1059
1196
  });
1060
1197
  }
@@ -1115,9 +1252,7 @@ function parsePrepareInput(
1115
1252
  requireCompletedFileFields: false,
1116
1253
  });
1117
1254
  if (issues.length > 0) {
1118
- throw new UploadError({
1119
- code: "INVALID_UPLOAD_BODY",
1120
- status: 400,
1255
+ throw new InvalidUploadBodyError({
1121
1256
  message: `Upload "${uploadName}" prepare body is invalid.`,
1122
1257
  details: { issues },
1123
1258
  });
@@ -1133,9 +1268,7 @@ function parseCompleteInput(
1133
1268
  requireCompletedFileFields: true,
1134
1269
  });
1135
1270
  if (issues.length > 0) {
1136
- throw new UploadError({
1137
- code: "INVALID_UPLOAD_BODY",
1138
- status: 400,
1271
+ throw new InvalidUploadBodyError({
1139
1272
  message: `Upload "${uploadName}" complete body is invalid.`,
1140
1273
  details: { issues },
1141
1274
  });
@@ -1150,9 +1283,7 @@ async function parseMetadata<U extends UploadDef>(
1150
1283
  const result = await upload.metadata["~standard"].validate(input);
1151
1284
 
1152
1285
  if (result.issues?.length) {
1153
- throw new UploadError({
1154
- code: "INVALID_UPLOAD_METADATA",
1155
- status: 422,
1286
+ throw new InvalidUploadMetadataError({
1156
1287
  message: `Invalid metadata for upload "${upload.name}".`,
1157
1288
  details: { issues: result.issues },
1158
1289
  });
@@ -1171,9 +1302,7 @@ function assertFiles(
1171
1302
  ): void {
1172
1303
  const maxFiles = upload.file.maxFiles ?? 1;
1173
1304
  if (files.length === 0 || files.length > maxFiles) {
1174
- throw new UploadError({
1175
- code: "INVALID_UPLOAD_FILE",
1176
- status: 422,
1305
+ throw new InvalidUploadFileError({
1177
1306
  message: `Upload "${upload.name}" requires between 1 and ${maxFiles} file${maxFiles === 1 ? "" : "s"}.`,
1178
1307
  details: { fileCount: files.length, maxFiles },
1179
1308
  });
@@ -1181,9 +1310,7 @@ function assertFiles(
1181
1310
 
1182
1311
  for (const file of files) {
1183
1312
  if (!file.name || !file.contentType || !Number.isFinite(file.size)) {
1184
- throw new UploadError({
1185
- code: "INVALID_UPLOAD_FILE",
1186
- status: 422,
1313
+ throw new InvalidUploadFileError({
1187
1314
  message: `Upload "${upload.name}" received invalid file metadata.`,
1188
1315
  details: { file },
1189
1316
  });
@@ -1193,8 +1320,7 @@ function assertFiles(
1193
1320
  upload.file.contentTypes?.length &&
1194
1321
  !upload.file.contentTypes.includes(file.contentType)
1195
1322
  ) {
1196
- throw new UploadError({
1197
- code: "INVALID_UPLOAD_FILE",
1323
+ throw new InvalidUploadFileError({
1198
1324
  status: 415,
1199
1325
  message: `Upload "${upload.name}" does not accept "${file.contentType}".`,
1200
1326
  details: {
@@ -1208,8 +1334,7 @@ function assertFiles(
1208
1334
  upload.file.maxSizeBytes !== undefined &&
1209
1335
  file.size > upload.file.maxSizeBytes
1210
1336
  ) {
1211
- throw new UploadError({
1212
- code: "INVALID_UPLOAD_FILE",
1337
+ throw new InvalidUploadFileError({
1213
1338
  status: 413,
1214
1339
  message: `Upload "${upload.name}" exceeds the maximum file size.`,
1215
1340
  details: {
@@ -1228,9 +1353,7 @@ async function assertAuthorized<Metadata, Ctx>(
1228
1353
  if (!upload.authorize) {
1229
1354
  if ((upload.access ?? "protected") === "public") return;
1230
1355
 
1231
- throw new UploadError({
1232
- code: "UNAUTHORIZED_UPLOAD",
1233
- status: 403,
1356
+ throw new UnauthorizedUploadError({
1234
1357
  message: `Upload "${upload.name}" must declare authorize(...) or set access: "public".`,
1235
1358
  });
1236
1359
  }
@@ -1246,9 +1369,7 @@ async function assertAuthorized<Metadata, Ctx>(
1246
1369
  result.allowed === false);
1247
1370
  if (!denied) return;
1248
1371
 
1249
- throw new UploadError({
1250
- code: "UNAUTHORIZED_UPLOAD",
1251
- status: 403,
1372
+ throw new UnauthorizedUploadError({
1252
1373
  message:
1253
1374
  typeof result === "object" && result?.reason
1254
1375
  ? result.reason
@@ -1267,8 +1388,7 @@ function assertStoredObject(
1267
1388
  upload.file.maxSizeBytes !== undefined &&
1268
1389
  object.size > upload.file.maxSizeBytes
1269
1390
  ) {
1270
- throw new UploadError({
1271
- code: "INVALID_UPLOAD_FILE",
1391
+ throw new InvalidUploadFileError({
1272
1392
  status: 413,
1273
1393
  message: `Uploaded object "${object.key}" exceeds the maximum file size.`,
1274
1394
  details: {
@@ -1279,9 +1399,7 @@ function assertStoredObject(
1279
1399
  }
1280
1400
 
1281
1401
  if (object.size !== file.size) {
1282
- throw new UploadError({
1283
- code: "INVALID_UPLOAD_FILE",
1284
- status: 422,
1402
+ throw new InvalidUploadFileError({
1285
1403
  message: `Uploaded object "${object.key}" size does not match the prepared file.`,
1286
1404
  details: {
1287
1405
  expected: file.size,
@@ -1295,9 +1413,7 @@ function assertStoredObject(
1295
1413
  file.contentType &&
1296
1414
  object.contentType !== file.contentType
1297
1415
  ) {
1298
- throw new UploadError({
1299
- code: "INVALID_UPLOAD_FILE",
1300
- status: 422,
1416
+ throw new InvalidUploadFileError({
1301
1417
  message: `Uploaded object "${object.key}" content type does not match the prepared file.`,
1302
1418
  details: {
1303
1419
  expected: file.contentType,
@@ -1314,9 +1430,7 @@ function metadataFromFormData(formData: FormData): unknown {
1314
1430
  try {
1315
1431
  return JSON.parse(metadata);
1316
1432
  } catch {
1317
- throw new UploadError({
1318
- code: "INVALID_UPLOAD_BODY",
1319
- status: 400,
1433
+ throw new InvalidUploadBodyError({
1320
1434
  message: "Upload metadata must be valid JSON.",
1321
1435
  });
1322
1436
  }
@@ -1326,9 +1440,7 @@ function filesFromFormData(formData: FormData): File[] {
1326
1440
  const values = [...formData.getAll("file"), ...formData.getAll("files")];
1327
1441
  const files = values.filter((value): value is File => value instanceof File);
1328
1442
  if (files.length === 0) {
1329
- throw new UploadError({
1330
- code: "INVALID_UPLOAD_BODY",
1331
- status: 400,
1443
+ throw new InvalidUploadBodyError({
1332
1444
  message: 'Multipart upload must include at least one "file" field.',
1333
1445
  });
1334
1446
  }
@@ -82,6 +82,7 @@ export interface WebhookDef<
82
82
  Name extends string = string,
83
83
  Events extends WebhookEventSchemas = WebhookEventSchemas,
84
84
  > {
85
+ kind: "webhook";
85
86
  name: Name;
86
87
  provider?: string;
87
88
  events: Events;
@@ -235,6 +236,7 @@ export function defineWebhook<
235
236
  if (!name) throw new WebhookOptionsError("Webhook name is required.");
236
237
 
237
238
  return {
239
+ kind: "webhook",
238
240
  name,
239
241
  provider: options.provider,
240
242
  events: (options.events ?? {}) as Events,
@@ -1,59 +0,0 @@
1
- import type {
2
- EventDef,
3
- InferEventPayload,
4
- StandardSchema,
5
- } from "../events/index.js";
6
- import { defineEvent } from "../events/index.js";
7
-
8
- /**
9
- * Domain event definition with a stable name and payload schema.
10
- */
11
- export type DomainEventDef<
12
- Name extends string = string,
13
- Payload extends StandardSchema = StandardSchema,
14
- > = EventDef<Name, Payload>;
15
-
16
- /**
17
- * Infer the payload type from a DomainEventDef.
18
- *
19
- * @example
20
- * ```ts
21
- * const UserRegistered = defineDomainEvent(
22
- * "user.registered",
23
- * z.object({
24
- * userId: z.string(),
25
- * email: z.string().email(),
26
- * })
27
- * );
28
- *
29
- * type Payload = InferEventPayload<typeof UserRegistered>;
30
- * // { userId: string; email: string }
31
- * ```
32
- */
33
- export type { InferEventPayload };
34
-
35
- /**
36
- * Create a new domain event definition.
37
- *
38
- * This is a domain-focused alias around `defineEvent(...)` for applications
39
- * that separate domain events from integration events.
40
- *
41
- * @example
42
- * ```ts
43
- * const UserRegistered = defineDomainEvent(
44
- * "user.registered",
45
- * z.object({
46
- * userId: z.string(),
47
- * email: z.string().email(),
48
- * })
49
- * );
50
- *
51
- * type UserRegistered = typeof UserRegistered;
52
- * ```
53
- */
54
- export function defineDomainEvent<
55
- Name extends string,
56
- Payload extends StandardSchema,
57
- >(name: Name, payload: Payload): DomainEventDef<Name, Payload> {
58
- return defineEvent(name, { payload });
59
- }