ocel 0.0.1-alpha.0 → 0.0.1
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/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +98 -0
- package/dist/binding/binding.d.ts +22 -0
- package/dist/binding/binding.js +60 -0
- package/dist/binding/unprovisioned.d.ts +17 -0
- package/dist/binding/unprovisioned.js +25 -0
- package/dist/bucket/bucket-client.d.ts +4 -0
- package/dist/bucket/bucket-client.js +5 -0
- package/dist/bucket/bucket-context.d.ts +8 -0
- package/dist/bucket/bucket-context.js +10 -0
- package/dist/bucket/bucket.d.ts +60 -0
- package/dist/bucket/bucket.js +91 -0
- package/dist/bucket/client.d.ts +49 -0
- package/dist/bucket/client.js +95 -0
- package/dist/bucket/errors.d.ts +12 -0
- package/dist/bucket/errors.js +20 -0
- package/dist/bucket/express.d.ts +10 -0
- package/dist/bucket/express.js +24 -0
- package/dist/bucket/hono.d.ts +18 -0
- package/dist/bucket/hono.js +13 -0
- package/dist/bucket/index.d.ts +8 -0
- package/dist/bucket/index.js +6 -0
- package/dist/bucket/keys.d.ts +5 -0
- package/dist/bucket/keys.js +24 -0
- package/dist/bucket/metadata.d.ts +6 -0
- package/dist/bucket/metadata.js +6 -0
- package/dist/bucket/next.d.ts +15 -0
- package/dist/bucket/next.js +11 -0
- package/dist/bucket/objects.d.ts +130 -0
- package/dist/bucket/objects.js +350 -0
- package/dist/bucket/route.d.ts +13 -0
- package/dist/bucket/route.js +247 -0
- package/dist/bucket/server-only.js +5 -0
- package/dist/bucket/types.d.ts +63 -0
- package/dist/bucket/uploader.d.ts +3 -0
- package/dist/bucket/uploader.js +6 -0
- package/dist/build-env.d.ts +17 -0
- package/dist/build-env.js +41 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.js +9 -0
- package/dist/declaration/callsite.d.ts +15 -0
- package/dist/declaration/callsite.js +43 -0
- package/dist/declaration/component.d.ts +4 -0
- package/dist/declaration/component.js +1 -0
- package/dist/declaration/defer.d.ts +5 -0
- package/dist/declaration/defer.js +9 -0
- package/dist/delivery/duration.d.ts +13 -0
- package/dist/delivery/duration.js +36 -0
- package/dist/delivery/lane.d.ts +4 -0
- package/dist/delivery/lane.js +15 -0
- package/dist/delivery/payload.d.ts +6 -0
- package/dist/delivery/payload.js +14 -0
- package/dist/delivery/retry.d.ts +16 -0
- package/dist/delivery/retry.js +10 -0
- package/dist/delivery/schema.d.ts +10 -0
- package/dist/delivery/schema.js +20 -0
- package/dist/dns.d.ts +11 -0
- package/dist/dns.js +4 -0
- package/dist/edge.d.ts +4 -0
- package/dist/edge.js +4 -0
- package/dist/env/access.d.ts +22 -0
- package/dist/env/access.js +49 -0
- package/dist/env/client.d.ts +5 -0
- package/dist/env/client.js +10 -0
- package/dist/env/declare.d.ts +2 -0
- package/dist/env/declare.js +83 -0
- package/dist/env/definition.d.ts +46 -0
- package/dist/env/definition.js +164 -0
- package/dist/env/deployment.d.ts +23 -0
- package/dist/env/deployment.js +13 -0
- package/dist/env/edge.d.ts +10 -0
- package/dist/env/edge.js +30 -0
- package/dist/env/errors.d.ts +9 -0
- package/dist/env/errors.js +9 -0
- package/dist/env/file.d.ts +1 -0
- package/dist/env/file.js +27 -0
- package/dist/env/index.d.ts +10 -0
- package/dist/env/index.js +63 -0
- package/dist/env/live.d.ts +3 -0
- package/dist/env/live.js +12 -0
- package/dist/env/schema.d.ts +27 -0
- package/dist/env/schema.js +43 -0
- package/dist/env/scope.d.ts +7 -0
- package/dist/env/scope.js +45 -0
- package/dist/env/standard.d.ts +10 -0
- package/dist/env/standard.js +21 -0
- package/dist/env/value.d.ts +5 -0
- package/dist/env/value.js +26 -0
- package/dist/env-source.d.ts +17 -0
- package/dist/env-source.js +16 -0
- package/dist/gen/proto/app/bucket/v1/bucket_pb.d.ts +911 -0
- package/dist/gen/proto/app/bucket/v1/bucket_pb.js +253 -0
- package/dist/gen/proto/app/realtime/v1/realtime_pb.d.ts +50 -0
- package/dist/gen/proto/app/realtime/v1/realtime_pb.js +23 -0
- package/dist/gen/proto/app/resources/v1/resources_pb.d.ts +554 -0
- package/dist/gen/proto/app/resources/v1/resources_pb.js +214 -0
- package/dist/gen/proto/app/resources/v1/variables_pb.d.ts +231 -0
- package/dist/gen/proto/app/resources/v1/variables_pb.js +101 -0
- package/dist/gen/proto/app/task/v1/task_pb.d.ts +498 -0
- package/dist/gen/proto/app/task/v1/task_pb.js +151 -0
- package/dist/gen/proto/app/topic/v1/topic_pb.d.ts +456 -0
- package/dist/gen/proto/app/topic/v1/topic_pb.js +125 -0
- package/dist/gen/proto/buf/validate/validate_pb.d.ts +4833 -0
- package/dist/gen/proto/buf/validate/validate_pb.js +356 -0
- package/dist/gen/proto/common/bindings/v1/bindings_pb.d.ts +378 -0
- package/dist/gen/proto/common/bindings/v1/bindings_pb.js +140 -0
- package/dist/generated/config.d.ts +428 -0
- package/dist/generated/config.js +2 -0
- package/dist/kv/entries.d.ts +193 -0
- package/dist/kv/entries.js +325 -0
- package/dist/kv/errors.d.ts +12 -0
- package/dist/kv/errors.js +14 -0
- package/dist/kv/index.d.ts +6 -0
- package/dist/kv/index.js +3 -0
- package/dist/kv/ioredis.d.ts +2 -0
- package/dist/kv/ioredis.js +20 -0
- package/dist/kv/kv.d.ts +84 -0
- package/dist/kv/kv.js +177 -0
- package/dist/kv/pattern.d.ts +27 -0
- package/dist/kv/pattern.js +73 -0
- package/dist/kv/ttl.d.ts +22 -0
- package/dist/kv/ttl.js +21 -0
- package/dist/postgres/index.d.ts +21 -2
- package/dist/postgres/index.js +73 -2
- package/dist/postgres/pg.d.ts +5 -4
- package/dist/postgres/pg.js +16 -12
- package/dist/providers/aws/dns.d.ts +11 -0
- package/dist/providers/aws/dns.js +4 -0
- package/dist/providers/aws/edge.d.ts +6 -0
- package/dist/providers/aws/edge.js +8 -0
- package/dist/providers/aws/index.d.ts +4 -0
- package/dist/providers/aws/index.js +4 -0
- package/dist/providers/gcp/edge.d.ts +14 -0
- package/dist/providers/gcp/edge.js +14 -0
- package/dist/providers/gcp/index.d.ts +9 -0
- package/dist/providers/gcp/index.js +9 -0
- package/dist/providers/vps/index.d.ts +4 -0
- package/dist/providers/vps/index.js +4 -0
- package/dist/realtime/client-error.d.ts +30 -0
- package/dist/realtime/client-error.js +17 -0
- package/dist/realtime/client-event-socket.d.ts +8 -0
- package/dist/realtime/client-event-socket.js +169 -0
- package/dist/realtime/client-transport.d.ts +29 -0
- package/dist/realtime/client-transport.js +13 -0
- package/dist/realtime/client.d.ts +150 -0
- package/dist/realtime/client.js +345 -0
- package/dist/realtime/express.d.ts +18 -0
- package/dist/realtime/express.js +83 -0
- package/dist/realtime/handler.d.ts +56 -0
- package/dist/realtime/handler.js +287 -0
- package/dist/realtime/hono.d.ts +19 -0
- package/dist/realtime/hono.js +24 -0
- package/dist/realtime/index.d.ts +2 -0
- package/dist/realtime/index.js +2 -0
- package/dist/realtime/next.d.ts +26 -0
- package/dist/realtime/next.js +16 -0
- package/dist/realtime/realtime.d.ts +181 -0
- package/dist/realtime/realtime.js +205 -0
- package/dist/realtime/token.d.ts +20 -0
- package/dist/realtime/token.js +45 -0
- package/dist/realtime/transport.d.ts +11 -0
- package/dist/realtime/transport.js +15 -0
- package/dist/realtime/wire.d.ts +30 -0
- package/dist/realtime/wire.js +104 -0
- package/dist/runtime/dev-server.d.ts +8 -0
- package/dist/runtime/dev-server.js +8 -0
- package/dist/runtime/rpc.d.ts +23 -0
- package/dist/runtime/rpc.js +24 -0
- package/dist/runtime/transport.d.ts +11 -0
- package/dist/runtime/transport.js +35 -0
- package/dist/task/errors.d.ts +7 -0
- package/dist/task/errors.js +7 -0
- package/dist/task/hooks.d.ts +57 -0
- package/dist/task/hooks.js +1 -0
- package/dist/task/index.d.ts +9 -0
- package/dist/task/index.js +4 -0
- package/dist/task/runs.d.ts +74 -0
- package/dist/task/runs.js +82 -0
- package/dist/task/task.d.ts +117 -0
- package/dist/task/task.js +109 -0
- package/dist/topic/dead-letters.d.ts +53 -0
- package/dist/topic/dead-letters.js +62 -0
- package/dist/topic/index.d.ts +8 -0
- package/dist/topic/index.js +4 -0
- package/dist/topic/topic.d.ts +85 -0
- package/dist/topic/topic.js +117 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/dist/worker/context.d.ts +36 -0
- package/dist/worker/context.js +1 -0
- package/dist/worker/deliver.d.ts +13 -0
- package/dist/worker/deliver.js +258 -0
- package/dist/worker/index.d.ts +4 -0
- package/dist/worker/index.js +2 -0
- package/dist/worker/registry.d.ts +28 -0
- package/dist/worker/registry.js +22 -0
- package/dist/worker/semaphore.d.ts +7 -0
- package/dist/worker/semaphore.js +36 -0
- package/dist/worker/worker.d.ts +21 -0
- package/dist/worker/worker.js +32 -0
- package/package.json +158 -57
- package/bin/run.js +0 -39
- package/dist/blob/bucket.d.ts +0 -55
- package/dist/blob/bucket.js +0 -65
- package/dist/blob/index.d.ts +0 -8
- package/dist/blob/index.js +0 -13
- package/dist/blob/integrations/client.d.ts +0 -25
- package/dist/blob/integrations/client.js +0 -71
- package/dist/blob/integrations/hono.d.ts +0 -31
- package/dist/blob/integrations/hono.js +0 -11
- package/dist/blob/mime-types.d.ts +0 -1
- package/dist/blob/parse.d.ts +0 -16
- package/dist/blob/parse.js +0 -69
- package/dist/blob/s3-listener.d.ts +0 -7
- package/dist/blob/s3-listener.js +0 -35
- package/dist/blob/types.d.ts +0 -128
- package/dist/blob/types.js +0 -1
- package/dist/blob/upload-error.d.ts +0 -35
- package/dist/blob/upload-error.js +0 -54
- package/dist/blob/uploader.d.ts +0 -67
- package/dist/blob/uploader.js +0 -323
- package/dist/internal/db/index.d.ts +0 -68
- package/dist/internal/db/index.js +0 -73
- package/dist/lambda/index.d.ts +0 -2
- package/dist/lambda/index.js +0 -4
- package/dist/lambda/integrations/express.d.ts +0 -3
- package/dist/lambda/integrations/express.js +0 -13
- package/dist/lambda/integrations/hono.d.ts +0 -3
- package/dist/lambda/integrations/hono.js +0 -13
- package/dist/lambda/lambda.d.ts +0 -43
- package/dist/lambda/lambda.js +0 -30
- package/dist/utils/component.d.ts +0 -3
- package/dist/utils/constants.d.ts +0 -2
- package/dist/utils/constants.js +0 -2
- package/dist/utils/nanoid.d.ts +0 -1
- package/dist/utils/nanoid.js +0 -6
- package/dist/utils/rpc.d.ts +0 -11
- package/dist/utils/rpc.js +0 -17
- package/dist/utils/stack.d.ts +0 -4
- package/dist/utils/stack.js +0 -23
- package/dist/workflow/index.d.ts +0 -3
- package/dist/workflow/index.js +0 -4
- package/dist/workflow/workflow.d.ts +0 -34
- package/dist/workflow/workflow.js +0 -146
- package/src/blob/bucket.ts +0 -92
- package/src/blob/index.ts +0 -34
- package/src/blob/integrations/client.ts +0 -127
- package/src/blob/integrations/hono.ts +0 -16
- package/src/blob/mime-types.ts +0 -68
- package/src/blob/parse.ts +0 -93
- package/src/blob/s3-listener.ts +0 -48
- package/src/blob/types.ts +0 -142
- package/src/blob/upload-error.ts +0 -67
- package/src/blob/uploader.ts +0 -426
- package/src/internal/db/index.ts +0 -78
- package/src/lambda/index.ts +0 -8
- package/src/lambda/integrations/express.ts +0 -25
- package/src/lambda/integrations/hono.ts +0 -25
- package/src/lambda/lambda.ts +0 -94
- package/src/postgres/index.ts +0 -5
- package/src/postgres/pg.ts +0 -33
- package/src/utils/component.ts +0 -3
- package/src/utils/constants.ts +0 -2
- package/src/utils/nanoid.ts +0 -9
- package/src/utils/rpc.ts +0 -28
- package/src/utils/stack.ts +0 -26
- package/src/workflow/index.ts +0 -9
- package/src/workflow/workflow.ts +0 -235
- /package/dist/{blob/mime-types.js → bucket/server-only.d.ts} +0 -0
- /package/dist/{utils/component.js → bucket/types.js} +0 -0
package/src/blob/types.ts
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import type { IncomingMessage } from "node:http";
|
|
2
|
-
import z, { type ZodType } from "zod";
|
|
3
|
-
import type { KnownMimeType } from "./mime-types";
|
|
4
|
-
import type { UploadError } from "./upload-error";
|
|
5
|
-
|
|
6
|
-
type SuggestedSize = "1KB" | "100KB" | "1MB" | "10MB" | "100MB" | "1GB";
|
|
7
|
-
export type FileSizeUnit = "B" | "KB" | "MB" | "GB";
|
|
8
|
-
export type UnitAutocomplete<TUnit extends string> = `${number}${TUnit}`;
|
|
9
|
-
export type FileSize =
|
|
10
|
-
| SuggestedSize
|
|
11
|
-
| UnitAutocomplete<FileSizeUnit>
|
|
12
|
-
| number
|
|
13
|
-
| (string & {});
|
|
14
|
-
|
|
15
|
-
export type StringWithAutocomplete<T extends string> = T | (string & {});
|
|
16
|
-
export type HandleUploadBody = PresignUploadBody | CallbackBody<any>;
|
|
17
|
-
export type HandleUploadResult = PresignUploadResponse;
|
|
18
|
-
|
|
19
|
-
export type PresignUploadBody = {
|
|
20
|
-
files: {
|
|
21
|
-
name: string;
|
|
22
|
-
size: number;
|
|
23
|
-
mimeType: string;
|
|
24
|
-
lastModified: number;
|
|
25
|
-
}[];
|
|
26
|
-
input?: Record<string, any>;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export type PresignUploadResponse = {
|
|
30
|
-
files: {
|
|
31
|
-
url: string;
|
|
32
|
-
key: string;
|
|
33
|
-
name: string;
|
|
34
|
-
}[];
|
|
35
|
-
sessionId: string;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export type CallbackBody<TMeta> = {
|
|
39
|
-
metadata: TMeta;
|
|
40
|
-
file: {
|
|
41
|
-
path: string;
|
|
42
|
-
contentDisposition: string;
|
|
43
|
-
contentType: string;
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export type UploadFileBody = {
|
|
48
|
-
key: string;
|
|
49
|
-
name: string;
|
|
50
|
-
size: number;
|
|
51
|
-
mimeType: string;
|
|
52
|
-
disposition: string;
|
|
53
|
-
lastModified: number;
|
|
54
|
-
metadata?: Record<string, string>;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export type PathConfig = {
|
|
58
|
-
prefix?: string;
|
|
59
|
-
suffix?: string;
|
|
60
|
-
randomSuffix?: boolean;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
export type PathContext<TMeta> = [TMeta] extends [never]
|
|
64
|
-
? {
|
|
65
|
-
/**
|
|
66
|
-
* File extension including the dot (e.g., .jpg, .png)
|
|
67
|
-
*/
|
|
68
|
-
ext: string;
|
|
69
|
-
/**
|
|
70
|
-
* File name without extension
|
|
71
|
-
*/
|
|
72
|
-
fileNameWithoutExt: string;
|
|
73
|
-
/**
|
|
74
|
-
* Original file name as uploaded by user
|
|
75
|
-
*/
|
|
76
|
-
fileNameWithExt: string;
|
|
77
|
-
}
|
|
78
|
-
: {
|
|
79
|
-
/**
|
|
80
|
-
* File extension including the dot (e.g., .jpg, .png)
|
|
81
|
-
*/
|
|
82
|
-
ext: string;
|
|
83
|
-
/**
|
|
84
|
-
* File name without extension
|
|
85
|
-
*/
|
|
86
|
-
fileNameWithoutExt: string;
|
|
87
|
-
/**
|
|
88
|
-
* Original file name as uploaded by user
|
|
89
|
-
*/
|
|
90
|
-
fileNameWithExt: string;
|
|
91
|
-
/**
|
|
92
|
-
* Any metadata returned by middleware function
|
|
93
|
-
*/
|
|
94
|
-
metadata: TMeta;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
export type PathGenerator<TMeta> = (ctx: PathContext<TMeta>) => string;
|
|
98
|
-
export type PathInput<TMeta> = PathConfig | PathGenerator<TMeta>;
|
|
99
|
-
|
|
100
|
-
export type TUploaderConfig<
|
|
101
|
-
TInput extends ZodType = never,
|
|
102
|
-
TMeta = never,
|
|
103
|
-
TReturn = unknown
|
|
104
|
-
> = {
|
|
105
|
-
path?: PathInput<TMeta>;
|
|
106
|
-
input?: TInput;
|
|
107
|
-
accept?: StringWithAutocomplete<KnownMimeType>[];
|
|
108
|
-
limits?: {
|
|
109
|
-
maxFileSize?: FileSize | ((ctx: { metadata?: TMeta }) => FileSize);
|
|
110
|
-
maxFileCount?: number | ((ctx: { metadata?: TMeta }) => number);
|
|
111
|
-
minFileCount?: number | ((ctx: { metadata?: TMeta }) => number);
|
|
112
|
-
};
|
|
113
|
-
contentDisposition?: "inline" | "attachment";
|
|
114
|
-
|
|
115
|
-
onBeforeUpload: (
|
|
116
|
-
opts: [TInput] extends [never]
|
|
117
|
-
? {
|
|
118
|
-
files: PresignUploadBody["files"];
|
|
119
|
-
req: Request | IncomingMessage;
|
|
120
|
-
}
|
|
121
|
-
: {
|
|
122
|
-
files: PresignUploadBody["files"];
|
|
123
|
-
input: z.infer<TInput>;
|
|
124
|
-
req: Request | IncomingMessage;
|
|
125
|
-
}
|
|
126
|
-
) => TMeta | Promise<TMeta>;
|
|
127
|
-
onUploadError?: (args: { error: UploadError }) => void | Promise<void>;
|
|
128
|
-
onUploadComplete?: (args: {
|
|
129
|
-
metadata: TMeta;
|
|
130
|
-
file: { path: string; contentDisposition: string; contentType: string };
|
|
131
|
-
}) => TReturn | Promise<TReturn>;
|
|
132
|
-
|
|
133
|
-
_def: {
|
|
134
|
-
meta: TMeta;
|
|
135
|
-
input: TInput;
|
|
136
|
-
output: TReturn;
|
|
137
|
-
};
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
export type TBucketConfig<TUploaderShape> = {
|
|
141
|
-
uploaders: TUploaderShape;
|
|
142
|
-
};
|
package/src/blob/upload-error.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
export class UploadError extends Error {
|
|
2
|
-
public readonly statusCode: number;
|
|
3
|
-
public readonly code: string;
|
|
4
|
-
|
|
5
|
-
constructor(
|
|
6
|
-
message: string,
|
|
7
|
-
statusCode = 500,
|
|
8
|
-
code = "INTERNAL_SERVER_ERROR"
|
|
9
|
-
) {
|
|
10
|
-
super(message);
|
|
11
|
-
this.statusCode = statusCode;
|
|
12
|
-
this.code = code;
|
|
13
|
-
|
|
14
|
-
// Essential for 'instanceof' to work correctly when extending built-in classes in TypeScript
|
|
15
|
-
Object.setPrototypeOf(this, UploadError.prototype);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// --- Static Factory Helpers (The DX Magic) ---
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* 400 Bad Request
|
|
22
|
-
* Use when the input is invalid (e.g. file too big, wrong extension)
|
|
23
|
-
*/
|
|
24
|
-
static badRequest(message = "Bad Request") {
|
|
25
|
-
return new UploadError(message, 400, "BAD_REQUEST");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 401 Unauthorized
|
|
30
|
-
* Use when the user is not logged in or missing headers
|
|
31
|
-
*/
|
|
32
|
-
static unauthorized(message = "Unauthorized") {
|
|
33
|
-
return new UploadError(message, 401, "UNAUTHORIZED");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* 403 Forbidden
|
|
38
|
-
* Use when the user is logged in but doesn't have permission (e.g. wrong plan)
|
|
39
|
-
*/
|
|
40
|
-
static forbidden(message = "Forbidden") {
|
|
41
|
-
return new UploadError(message, 403, "FORBIDDEN");
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 404 Not Found
|
|
46
|
-
* Use when a resource required for the upload is missing
|
|
47
|
-
*/
|
|
48
|
-
static notFound(message = "Not Found") {
|
|
49
|
-
return new UploadError(message, 404, "NOT_FOUND");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* 429 Too Many Requests
|
|
54
|
-
* Use for rate limiting logic
|
|
55
|
-
*/
|
|
56
|
-
static tooManyRequests(message = "Too many requests") {
|
|
57
|
-
return new UploadError(message, 429, "TOO_MANY_REQUESTS");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* 500 Internal Server Error
|
|
62
|
-
* Use for unexpected crashes or database failures
|
|
63
|
-
*/
|
|
64
|
-
static internal(message = "Internal Server Error") {
|
|
65
|
-
return new UploadError(message, 500, "INTERNAL_SERVER_ERROR");
|
|
66
|
-
}
|
|
67
|
-
}
|
package/src/blob/uploader.ts
DELETED
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
import slugify from "@sindresorhus/slugify";
|
|
2
|
-
import type {
|
|
3
|
-
CallbackBody,
|
|
4
|
-
HandleUploadBody,
|
|
5
|
-
PathConfig,
|
|
6
|
-
PathGenerator,
|
|
7
|
-
PresignUploadBody,
|
|
8
|
-
TUploaderConfig,
|
|
9
|
-
UploadFileBody,
|
|
10
|
-
} from "./types";
|
|
11
|
-
import { UploadError } from "./upload-error";
|
|
12
|
-
import type { IncomingMessage } from "node:http";
|
|
13
|
-
import path from "node:path";
|
|
14
|
-
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
15
|
-
import { getNanoid } from "../utils/nanoid";
|
|
16
|
-
import { UploadSession } from "../internal/db";
|
|
17
|
-
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
18
|
-
import type { ZodType } from "zod";
|
|
19
|
-
import { parseFileSize, parseReq } from "./parse";
|
|
20
|
-
import type { Bucket } from "./bucket";
|
|
21
|
-
|
|
22
|
-
export class Uploader<TMeta = unknown> {
|
|
23
|
-
private pathGenerator: PathGenerator<TMeta> | null = null;
|
|
24
|
-
|
|
25
|
-
constructor(
|
|
26
|
-
private bucket: Bucket<any>,
|
|
27
|
-
private name: string,
|
|
28
|
-
private config: TUploaderConfig<any, TMeta>
|
|
29
|
-
) {
|
|
30
|
-
const path = this.config.path;
|
|
31
|
-
if (path) {
|
|
32
|
-
if (typeof path === "function") {
|
|
33
|
-
this.pathGenerator = path;
|
|
34
|
-
} else {
|
|
35
|
-
this.pathGenerator = this.createConfigBasedGenerator(path);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async handleUpload({
|
|
41
|
-
action,
|
|
42
|
-
body,
|
|
43
|
-
request,
|
|
44
|
-
sessionId,
|
|
45
|
-
}: {
|
|
46
|
-
request: Request | IncomingMessage;
|
|
47
|
-
body: HandleUploadBody;
|
|
48
|
-
action: "presign" | "callback" | "poll";
|
|
49
|
-
sessionId?: string;
|
|
50
|
-
}) {
|
|
51
|
-
try {
|
|
52
|
-
const req = parseReq(request);
|
|
53
|
-
|
|
54
|
-
switch (action) {
|
|
55
|
-
case "presign": {
|
|
56
|
-
if (!("files" in body)) {
|
|
57
|
-
throw UploadError.badRequest("Invalid presign body");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const presignBody = body as PresignUploadBody;
|
|
61
|
-
const rawInput = presignBody.input || {};
|
|
62
|
-
const parser = this.config.input as ZodType | undefined;
|
|
63
|
-
|
|
64
|
-
let parsedInput: any;
|
|
65
|
-
|
|
66
|
-
// TODO: error handling
|
|
67
|
-
if (parser) {
|
|
68
|
-
const parsed = parser.safeParse(rawInput);
|
|
69
|
-
if (!parsed.success) {
|
|
70
|
-
throw UploadError.badRequest(parsed.error.issues[0]?.message);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
parsedInput = parsed.data;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const middleware = this.config.onBeforeUpload;
|
|
77
|
-
const customMeta = await middleware?.({
|
|
78
|
-
req: request,
|
|
79
|
-
input: parsedInput,
|
|
80
|
-
files: presignBody.files,
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const metadata = {
|
|
84
|
-
"x-ocel-path": req.path,
|
|
85
|
-
"x-ocel-uploader": this.name,
|
|
86
|
-
...(customMeta
|
|
87
|
-
? { "x-ocel-metadata": JSON.stringify(customMeta) }
|
|
88
|
-
: {}),
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
// TODO: validate files against uploader config (size, count, types, etc)
|
|
92
|
-
this.validateFiles(presignBody.files, customMeta);
|
|
93
|
-
|
|
94
|
-
const files = body.files.map((f) => {
|
|
95
|
-
const name = slugify(f.name, { preserveCharacters: ["."] });
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
...f,
|
|
99
|
-
key:
|
|
100
|
-
this.pathGenerator?.({
|
|
101
|
-
fileNameWithExt: name,
|
|
102
|
-
metadata: customMeta as any,
|
|
103
|
-
fileNameWithoutExt: name.replace(path.extname(name), ""),
|
|
104
|
-
ext: path.extname(name),
|
|
105
|
-
} as any) || name,
|
|
106
|
-
disposition: "inline",
|
|
107
|
-
metadata: {
|
|
108
|
-
...metadata,
|
|
109
|
-
"x-ocel-original-filename": f.name,
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
}) satisfies UploadFileBody[];
|
|
113
|
-
|
|
114
|
-
const provider = this.getProvider();
|
|
115
|
-
|
|
116
|
-
return provider.presign(files);
|
|
117
|
-
}
|
|
118
|
-
case "poll": {
|
|
119
|
-
if (!sessionId) {
|
|
120
|
-
throw UploadError.badRequest("sessionId is required for poll");
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const result = await UploadSession.query
|
|
124
|
-
.sessions({ sessionId })
|
|
125
|
-
.go();
|
|
126
|
-
|
|
127
|
-
const files = result.data.map((record) => {
|
|
128
|
-
let parsedMeta: TMeta = {} as TMeta;
|
|
129
|
-
try {
|
|
130
|
-
if (record.metadata) {
|
|
131
|
-
parsedMeta = JSON.parse(record.metadata) as TMeta;
|
|
132
|
-
}
|
|
133
|
-
} catch {
|
|
134
|
-
// ignore parse errors
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
fileKey: record.fileKey,
|
|
138
|
-
status: record.status,
|
|
139
|
-
file:
|
|
140
|
-
record.status === "SUCCESS"
|
|
141
|
-
? {
|
|
142
|
-
path: record.fileKey,
|
|
143
|
-
contentDisposition:
|
|
144
|
-
record.contentDisposition || "inline",
|
|
145
|
-
contentType: record.contentType,
|
|
146
|
-
}
|
|
147
|
-
: undefined,
|
|
148
|
-
metadata: parsedMeta,
|
|
149
|
-
};
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
const allComplete = files.every(
|
|
153
|
-
(f) => f.status === "SUCCESS" || f.status === "FAILED"
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
files,
|
|
158
|
-
completed: allComplete,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
case "callback": {
|
|
162
|
-
// TODO: validate callback body
|
|
163
|
-
if (!("metadata" in body)) {
|
|
164
|
-
throw UploadError.badRequest("Invalid callback body");
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const data = body as CallbackBody<TMeta>;
|
|
168
|
-
|
|
169
|
-
await this.config.onUploadComplete?.(data);
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
type: "UPLOAD_COMPLETED" as const,
|
|
173
|
-
response: "ok" as const,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
default:
|
|
177
|
-
throw UploadError.badRequest(`Unhandled action type: ${action}`);
|
|
178
|
-
}
|
|
179
|
-
} catch (err) {
|
|
180
|
-
if (err instanceof UploadError) {
|
|
181
|
-
this.config.onUploadError?.({ error: err });
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
error: {
|
|
185
|
-
message: err.message,
|
|
186
|
-
code: err.code, // e.g. "FORBIDDEN"
|
|
187
|
-
status: err.statusCode, // e.g. 403
|
|
188
|
-
},
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
error: {
|
|
194
|
-
message: "Internal Server Error",
|
|
195
|
-
code: "INTERNAL_SERVER_ERROR",
|
|
196
|
-
status: 500,
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
private startPolling(sessionId: string): void {
|
|
203
|
-
const POLL_INTERVAL_MS = 1000;
|
|
204
|
-
const MAX_DURATION_MS = 10 * 60 * 1000; // 10 minutes
|
|
205
|
-
const startTime = Date.now();
|
|
206
|
-
|
|
207
|
-
const tick = async (): Promise<boolean> => {
|
|
208
|
-
if (Date.now() - startTime > MAX_DURATION_MS) {
|
|
209
|
-
return true;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const result = await UploadSession.query
|
|
213
|
-
.sessions({ sessionId })
|
|
214
|
-
.go();
|
|
215
|
-
|
|
216
|
-
const allComplete = result.data.every(
|
|
217
|
-
(r) => r.status === "SUCCESS" || r.status === "FAILED"
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
if (!allComplete) {
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Session complete - invoke callbacks
|
|
225
|
-
for (const record of result.data) {
|
|
226
|
-
let parsedMeta: TMeta = {} as TMeta;
|
|
227
|
-
try {
|
|
228
|
-
if (record.metadata) {
|
|
229
|
-
parsedMeta = JSON.parse(record.metadata) as TMeta;
|
|
230
|
-
}
|
|
231
|
-
} catch {
|
|
232
|
-
// ignore
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (record.status === "SUCCESS") {
|
|
236
|
-
await this.config.onUploadComplete?.({
|
|
237
|
-
metadata: parsedMeta,
|
|
238
|
-
file: {
|
|
239
|
-
path: record.fileKey,
|
|
240
|
-
contentDisposition: record.contentDisposition || "inline",
|
|
241
|
-
contentType: record.contentType,
|
|
242
|
-
},
|
|
243
|
-
});
|
|
244
|
-
} else if (record.status === "FAILED" && this.config.onUploadError) {
|
|
245
|
-
this.config.onUploadError({
|
|
246
|
-
error: new UploadError(
|
|
247
|
-
`Upload failed for ${record.fileKey}`,
|
|
248
|
-
500,
|
|
249
|
-
"UPLOAD_FAILED"
|
|
250
|
-
),
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return true;
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
const intervalId = setInterval(async () => {
|
|
259
|
-
try {
|
|
260
|
-
const done = await tick();
|
|
261
|
-
if (done) {
|
|
262
|
-
clearInterval(intervalId);
|
|
263
|
-
}
|
|
264
|
-
} catch {
|
|
265
|
-
clearInterval(intervalId);
|
|
266
|
-
}
|
|
267
|
-
}, POLL_INTERVAL_MS);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
getProvider() {
|
|
271
|
-
const bucket = this.bucket;
|
|
272
|
-
const bucketId = bucket.__name();
|
|
273
|
-
|
|
274
|
-
return {
|
|
275
|
-
presign: async (files: UploadFileBody[]) => {
|
|
276
|
-
const s3Client = new S3Client();
|
|
277
|
-
const sessionId = getNanoid(8);
|
|
278
|
-
|
|
279
|
-
const presignedFiles = await Promise.all(
|
|
280
|
-
files.map(async (file) => {
|
|
281
|
-
const metadataWithSession = {
|
|
282
|
-
...file.metadata,
|
|
283
|
-
"x-ocel-session-id": sessionId,
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
await UploadSession.create({
|
|
287
|
-
sessionId,
|
|
288
|
-
bucketName: bucketId,
|
|
289
|
-
fileKey: file.key,
|
|
290
|
-
createdAt: new Date().toISOString(),
|
|
291
|
-
status: "PENDING",
|
|
292
|
-
contentType: file.mimeType,
|
|
293
|
-
contentDisposition: file.disposition,
|
|
294
|
-
fileSize: file.size,
|
|
295
|
-
metadata: file.metadata
|
|
296
|
-
? JSON.stringify(file.metadata)
|
|
297
|
-
: undefined,
|
|
298
|
-
}).go();
|
|
299
|
-
|
|
300
|
-
const url = await getSignedUrl(
|
|
301
|
-
s3Client,
|
|
302
|
-
new PutObjectCommand({
|
|
303
|
-
Key: file.key,
|
|
304
|
-
Bucket: bucketId,
|
|
305
|
-
ContentType: file.mimeType,
|
|
306
|
-
Metadata: metadataWithSession,
|
|
307
|
-
}),
|
|
308
|
-
{ expiresIn: 3600 }
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
return {
|
|
312
|
-
url,
|
|
313
|
-
key: file.key,
|
|
314
|
-
name: file.name,
|
|
315
|
-
};
|
|
316
|
-
})
|
|
317
|
-
);
|
|
318
|
-
|
|
319
|
-
this.startPolling(sessionId);
|
|
320
|
-
|
|
321
|
-
return {
|
|
322
|
-
files: presignedFiles,
|
|
323
|
-
sessionId,
|
|
324
|
-
};
|
|
325
|
-
},
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
createConfigBasedGenerator(config: PathConfig): PathGenerator<any> {
|
|
330
|
-
return (ctx) => {
|
|
331
|
-
let key = ctx.fileNameWithoutExt;
|
|
332
|
-
|
|
333
|
-
// random suffix
|
|
334
|
-
if (config.randomSuffix) {
|
|
335
|
-
key += `-${Math.random().toString(36).slice(2, 10)}`;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// static suffix
|
|
339
|
-
else if (config.suffix) {
|
|
340
|
-
key += `-${config.suffix}`;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// add extension
|
|
344
|
-
key += ctx.ext;
|
|
345
|
-
|
|
346
|
-
// prefix
|
|
347
|
-
if (config.prefix) {
|
|
348
|
-
return `${config.prefix.replace(/\/$/, "")}/${key}`;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return key;
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
private validateFiles(
|
|
356
|
-
files: PresignUploadBody["files"],
|
|
357
|
-
metadata?: TMeta
|
|
358
|
-
): void {
|
|
359
|
-
const { accept, limits } = this.config;
|
|
360
|
-
|
|
361
|
-
if (limits?.maxFileCount !== undefined) {
|
|
362
|
-
const max =
|
|
363
|
-
typeof limits.maxFileCount === "function"
|
|
364
|
-
? limits.maxFileCount({ metadata })
|
|
365
|
-
: limits.maxFileCount;
|
|
366
|
-
|
|
367
|
-
if (files.length > max) {
|
|
368
|
-
throw UploadError.badRequest(
|
|
369
|
-
`File limit exceeded. Maximum files allowed: ${max}.`
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
if (limits?.minFileCount !== undefined) {
|
|
374
|
-
const min =
|
|
375
|
-
typeof limits.minFileCount === "function"
|
|
376
|
-
? limits.minFileCount({ metadata })
|
|
377
|
-
: limits.minFileCount;
|
|
378
|
-
|
|
379
|
-
if (files.length < min) {
|
|
380
|
-
throw UploadError.badRequest(
|
|
381
|
-
`File limit not met. Minimum files required: ${min}.`
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// 2. Per-File Validation (Size and MIME Type)
|
|
387
|
-
for (const file of files) {
|
|
388
|
-
// MIME Type Validation
|
|
389
|
-
if (accept && accept.length > 0) {
|
|
390
|
-
const isAccepted = accept.some((allowedType) => {
|
|
391
|
-
// Simple check for wildcards (*/*) and direct matches (image/png)
|
|
392
|
-
if (allowedType.endsWith("/*")) {
|
|
393
|
-
const typeGroup = allowedType.slice(0, -2);
|
|
394
|
-
return file.mimeType.startsWith(typeGroup);
|
|
395
|
-
}
|
|
396
|
-
return file.mimeType === allowedType;
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
if (!isAccepted) {
|
|
400
|
-
throw UploadError.badRequest(
|
|
401
|
-
`File type not allowed: ${
|
|
402
|
-
file.mimeType
|
|
403
|
-
}. Must be one of: ${accept.join(", ")}.`
|
|
404
|
-
);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Max File Size Validation
|
|
409
|
-
if (limits?.maxFileSize !== undefined) {
|
|
410
|
-
const sizeValue =
|
|
411
|
-
typeof limits.maxFileSize === "function"
|
|
412
|
-
? limits.maxFileSize({ metadata })
|
|
413
|
-
: limits.maxFileSize;
|
|
414
|
-
|
|
415
|
-
const maxSizeInBytes = parseFileSize(sizeValue);
|
|
416
|
-
|
|
417
|
-
if (file.size > maxSizeInBytes) {
|
|
418
|
-
// You might want to convert maxSize from bytes to KB/MB for a better error message
|
|
419
|
-
throw UploadError.badRequest(
|
|
420
|
-
`File size too large. Maximum size: ${maxSizeInBytes} bytes.`
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
package/src/internal/db/index.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { Entity } from "electrodb";
|
|
2
|
-
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
3
|
-
|
|
4
|
-
const client = new DynamoDBClient({});
|
|
5
|
-
|
|
6
|
-
export const UploadSession = new Entity(
|
|
7
|
-
{
|
|
8
|
-
model: {
|
|
9
|
-
entity: "upload-session",
|
|
10
|
-
version: "1",
|
|
11
|
-
service: "blob",
|
|
12
|
-
},
|
|
13
|
-
attributes: {
|
|
14
|
-
sessionId: {
|
|
15
|
-
type: "string",
|
|
16
|
-
required: true,
|
|
17
|
-
},
|
|
18
|
-
bucketName: {
|
|
19
|
-
type: "string",
|
|
20
|
-
required: true,
|
|
21
|
-
},
|
|
22
|
-
fileKey: {
|
|
23
|
-
type: "string",
|
|
24
|
-
required: true,
|
|
25
|
-
},
|
|
26
|
-
contentType: {
|
|
27
|
-
type: "string",
|
|
28
|
-
required: true,
|
|
29
|
-
},
|
|
30
|
-
contentDisposition: {
|
|
31
|
-
type: "string",
|
|
32
|
-
required: false,
|
|
33
|
-
},
|
|
34
|
-
fileSize: {
|
|
35
|
-
type: "number",
|
|
36
|
-
required: true,
|
|
37
|
-
},
|
|
38
|
-
createdAt: {
|
|
39
|
-
type: "string",
|
|
40
|
-
required: true,
|
|
41
|
-
default: () => new Date().toISOString(),
|
|
42
|
-
},
|
|
43
|
-
updatedAt: {
|
|
44
|
-
type: "string",
|
|
45
|
-
required: true,
|
|
46
|
-
default: () => new Date().toISOString(),
|
|
47
|
-
set: () => new Date().toISOString(),
|
|
48
|
-
},
|
|
49
|
-
status: {
|
|
50
|
-
type: ["PENDING", "SUCCESS", "FAILED"] as const,
|
|
51
|
-
required: true,
|
|
52
|
-
default: "PENDING",
|
|
53
|
-
},
|
|
54
|
-
metadata: {
|
|
55
|
-
type: "string",
|
|
56
|
-
required: false,
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
indexes: {
|
|
60
|
-
sessions: {
|
|
61
|
-
pk: {
|
|
62
|
-
field: "pk",
|
|
63
|
-
composite: ["sessionId"],
|
|
64
|
-
template: "SESSION#${sessionId}",
|
|
65
|
-
},
|
|
66
|
-
sk: {
|
|
67
|
-
field: "sk",
|
|
68
|
-
composite: ["bucketName", "fileKey"],
|
|
69
|
-
template: "FILE#${bucketName}#${fileKey}",
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
client,
|
|
76
|
-
table: "OcelTable", // required for all ocel logic
|
|
77
|
-
}
|
|
78
|
-
);
|