@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.
- package/CHANGELOG.md +86 -0
- package/README.md +48 -2
- package/dist/domain/entity.d.ts +37 -6
- package/dist/domain/entity.d.ts.map +1 -1
- package/dist/domain/entity.js +9 -7
- package/dist/domain/entity.js.map +1 -1
- package/dist/domain/index.d.ts +4 -4
- package/dist/domain/index.d.ts.map +1 -1
- package/dist/domain/index.js +4 -4
- package/dist/domain/index.js.map +1 -1
- package/dist/flags/index.d.ts +5 -4
- package/dist/flags/index.d.ts.map +1 -1
- package/dist/flags/index.js +10 -9
- package/dist/flags/index.js.map +1 -1
- package/dist/idempotency/index.d.ts +11 -1
- package/dist/idempotency/index.d.ts.map +1 -1
- package/dist/idempotency/index.js +4 -3
- package/dist/idempotency/index.js.map +1 -1
- package/dist/jobs/index.d.ts +35 -6
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js +82 -11
- package/dist/jobs/index.js.map +1 -1
- package/dist/memo/index.d.ts +107 -0
- package/dist/memo/index.d.ts.map +1 -0
- package/dist/memo/index.js +205 -0
- package/dist/memo/index.js.map +1 -0
- package/dist/outbox/index.d.ts +20 -2
- package/dist/outbox/index.d.ts.map +1 -1
- package/dist/outbox/index.js +23 -8
- package/dist/outbox/index.js.map +1 -1
- package/dist/ports/events.d.ts +2 -2
- package/dist/providers/provider.d.ts +10 -2
- package/dist/providers/provider.d.ts.map +1 -1
- package/dist/providers/provider.js.map +1 -1
- package/dist/search/index.d.ts +2 -1
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js +1 -0
- package/dist/search/index.js.map +1 -1
- package/dist/server/providers/loadProviderConfig.d.ts.map +1 -1
- package/dist/server/providers/loadProviderConfig.js +15 -2
- package/dist/server/providers/loadProviderConfig.js.map +1 -1
- package/dist/server/server.d.ts +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +43 -3
- package/dist/server/server.js.map +1 -1
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +12 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/uploads/index.d.ts +111 -2
- package/dist/uploads/index.d.ts.map +1 -1
- package/dist/uploads/index.js +124 -61
- package/dist/uploads/index.js.map +1 -1
- package/dist/webhooks/index.d.ts +1 -0
- package/dist/webhooks/index.d.ts.map +1 -1
- package/dist/webhooks/index.js +1 -0
- package/dist/webhooks/index.js.map +1 -1
- package/package.json +5 -1
- package/src/domain/entity.ts +61 -13
- package/src/domain/index.ts +8 -7
- package/src/flags/index.ts +23 -19
- package/src/idempotency/index.ts +17 -3
- package/src/jobs/index.ts +131 -16
- package/src/memo/index.ts +310 -0
- package/src/outbox/index.ts +52 -8
- package/src/ports/events.ts +2 -2
- package/src/providers/provider.ts +11 -2
- package/src/search/index.ts +3 -1
- package/src/server/providers/loadProviderConfig.ts +19 -2
- package/src/server/server.ts +73 -18
- package/src/testing/index.ts +19 -3
- package/src/uploads/index.ts +174 -62
- package/src/webhooks/index.ts +2 -0
- package/src/domain/events.ts +0 -59
package/src/testing/index.ts
CHANGED
|
@@ -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
|
|
1068
|
+
config,
|
|
1053
1069
|
createServiceContext,
|
|
1054
1070
|
});
|
|
1055
1071
|
const ports: Record<string, unknown> = { ...basePorts, ...result.ports };
|
package/src/uploads/index.ts
CHANGED
|
@@ -452,14 +452,19 @@ export type UploadErrorCode =
|
|
|
452
452
|
| "INVALID_UPLOAD_BODY";
|
|
453
453
|
|
|
454
454
|
/**
|
|
455
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|
package/src/webhooks/index.ts
CHANGED
|
@@ -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,
|
package/src/domain/events.ts
DELETED
|
@@ -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
|
-
}
|