stratal 0.0.14 → 0.0.16
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/dist/application-zG8b-pol.d.mts +116 -0
- package/dist/application-zG8b-pol.d.mts.map +1 -0
- package/dist/{base-email.provider-bzdAYp8Z.mjs → base-email.provider-Cuw4OAB0.mjs} +1 -1
- package/dist/{base-email.provider-bzdAYp8Z.mjs.map → base-email.provider-Cuw4OAB0.mjs.map} +1 -1
- package/dist/bin/cloudflare-workers-loader.mjs +34 -0
- package/dist/bin/cloudflare-workers-loader.mjs.map +1 -0
- package/dist/bin/quarry.mjs +168 -0
- package/dist/bin/quarry.mjs.map +1 -0
- package/dist/cache/index.d.mts +2 -2
- package/dist/cache/index.mjs +11 -6
- package/dist/cache/index.mjs.map +1 -1
- package/dist/colors-DJaRDXoS.mjs +16 -0
- package/dist/colors-DJaRDXoS.mjs.map +1 -0
- package/dist/command-B-QH-Vu3.d.mts +120 -0
- package/dist/command-B-QH-Vu3.d.mts.map +1 -0
- package/dist/command-BvCOD6df.mjs +193 -0
- package/dist/command-BvCOD6df.mjs.map +1 -0
- package/dist/config/index.d.mts +2 -2
- package/dist/config/index.mjs +11 -6
- package/dist/config/index.mjs.map +1 -1
- package/dist/cron/index.d.mts +1 -1
- package/dist/cron/index.mjs +4 -3
- package/dist/{cron-manager-CpS_hrDD.mjs → cron-manager-DR7fiG6o.mjs} +3 -3
- package/dist/{cron-manager-CpS_hrDD.mjs.map → cron-manager-DR7fiG6o.mjs.map} +1 -1
- package/dist/decorate-D5j-d9_z.mjs +171 -0
- package/dist/decorate-D5j-d9_z.mjs.map +1 -0
- package/dist/di/index.d.mts +1 -1
- package/dist/di/index.mjs +3 -2
- package/dist/email/index.d.mts +3 -3
- package/dist/email/index.mjs +17 -11
- package/dist/email/index.mjs.map +1 -1
- package/dist/{en-C9U5-ETs.mjs → en-DaewN8hc.mjs} +3 -1
- package/dist/en-DaewN8hc.mjs.map +1 -0
- package/dist/errors/index.d.mts +1 -1
- package/dist/errors/index.mjs +3 -2
- package/dist/{errors-BRJgVd5-.mjs → errors-CtCi1wn6.mjs} +6 -3
- package/dist/errors-CtCi1wn6.mjs.map +1 -0
- package/dist/errors-H3TZnVeX.mjs +67 -0
- package/dist/errors-H3TZnVeX.mjs.map +1 -0
- package/dist/events/index.d.mts +2 -2
- package/dist/events/index.mjs +3 -2
- package/dist/{events-CQyvSyrQ.mjs → events-CXl-o1Ad.mjs} +3 -2
- package/dist/{events-CQyvSyrQ.mjs.map → events-CXl-o1Ad.mjs.map} +1 -1
- package/dist/{gateway-context-D7TFPLi5.mjs → gateway-context-BkZ4UKaX.mjs} +4 -4
- package/dist/{gateway-context-D7TFPLi5.mjs.map → gateway-context-BkZ4UKaX.mjs.map} +1 -1
- package/dist/guards/index.d.mts +3 -3
- package/dist/guards/index.mjs +1 -1
- package/dist/{guards-B5o618bL.mjs → guards-DUk_Kzst.mjs} +1 -1
- package/dist/{guards-B5o618bL.mjs.map → guards-DUk_Kzst.mjs.map} +1 -1
- package/dist/i18n/index.d.mts +2 -2
- package/dist/i18n/index.mjs +15 -10
- package/dist/i18n/messages/en/index.d.mts +1 -1
- package/dist/i18n/messages/en/index.mjs +1 -1
- package/dist/i18n/validation/index.d.mts +1 -1
- package/dist/i18n/validation/index.mjs +1 -1
- package/dist/{i18n.module-C9wQr_2k.mjs → i18n.module-W8OJxg3d.mjs} +10 -11
- package/dist/i18n.module-W8OJxg3d.mjs.map +1 -0
- package/dist/{index-C9bIk5tt.d.mts → index-BJWm863C.d.mts} +9 -6
- package/dist/index-BJWm863C.d.mts.map +1 -0
- package/dist/{index-zKURVFOC.d.mts → index-D9iYu2Yc.d.mts} +3 -3
- package/dist/{index-zKURVFOC.d.mts.map → index-D9iYu2Yc.d.mts.map} +1 -1
- package/dist/{index-Dl4RvzNp.d.mts → index-DVhdhLvE.d.mts} +2 -2
- package/dist/{index-Dl4RvzNp.d.mts.map → index-DVhdhLvE.d.mts.map} +1 -1
- package/dist/{index-BWEwA_XK.d.mts → index-D_w_Rmtd.d.mts} +3 -1
- package/dist/{index-BWEwA_XK.d.mts.map → index-D_w_Rmtd.d.mts.map} +1 -1
- package/dist/{index-3TtGtYlJ.d.mts → index-Dp6A5ywM.d.mts} +1 -1
- package/dist/{index-3TtGtYlJ.d.mts.map → index-Dp6A5ywM.d.mts.map} +1 -1
- package/dist/index.d.mts +3 -106
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +20 -13
- package/dist/is-command-BfCgWAcQ.mjs +14 -0
- package/dist/is-command-BfCgWAcQ.mjs.map +1 -0
- package/dist/is-seeder-CebjZCDn.mjs +28 -0
- package/dist/is-seeder-CebjZCDn.mjs.map +1 -0
- package/dist/logger/index.d.mts +1 -1
- package/dist/logger/index.mjs +2 -1
- package/dist/{logger-Bg-CuidS.mjs → logger-BR1-s1Um.mjs} +4 -169
- package/dist/logger-BR1-s1Um.mjs.map +1 -0
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +5 -4
- package/dist/{middleware-B3tx1u1K.mjs → middleware-C0Ebzswy.mjs} +3 -3
- package/dist/{middleware-B3tx1u1K.mjs.map → middleware-C0Ebzswy.mjs.map} +1 -1
- package/dist/module/index.d.mts +21 -3
- package/dist/module/index.d.mts.map +1 -1
- package/dist/module/index.mjs +11 -6
- package/dist/{module-Dvzm4dhS.mjs → module-BgdxxzBe.mjs} +44 -5
- package/dist/module-BgdxxzBe.mjs.map +1 -0
- package/dist/openapi/index.d.mts +3 -3
- package/dist/openapi/index.mjs +15 -10
- package/dist/quarry/index.d.mts +120 -0
- package/dist/quarry/index.d.mts.map +1 -0
- package/dist/quarry/index.mjs +7 -0
- package/dist/quarry-registry-DCwqVcRp.mjs +310 -0
- package/dist/quarry-registry-DCwqVcRp.mjs.map +1 -0
- package/dist/queue/index.d.mts +1 -1
- package/dist/queue/index.mjs +12 -7
- package/dist/queue/index.mjs.map +1 -1
- package/dist/{queue.module-ZqaZ2iY0.mjs → queue.module-BZvmeAMj.mjs} +4 -4
- package/dist/{queue.module-ZqaZ2iY0.mjs.map → queue.module-BZvmeAMj.mjs.map} +1 -1
- package/dist/{resend.provider-BFGt6fS4.mjs → resend.provider-BCCACQAU.mjs} +5 -4
- package/dist/{resend.provider-BFGt6fS4.mjs.map → resend.provider-BCCACQAU.mjs.map} +1 -1
- package/dist/router/index.d.mts +1 -1
- package/dist/router/index.mjs +15 -10
- package/dist/{router-context-DlTxpJUG.mjs → router-context-BEJe9HEB.mjs} +2 -2
- package/dist/{router-context-DlTxpJUG.mjs.map → router-context-BEJe9HEB.mjs.map} +1 -1
- package/dist/s3-storage.provider-BLlzQYiJ.mjs +336 -0
- package/dist/s3-storage.provider-BLlzQYiJ.mjs.map +1 -0
- package/dist/seeder/index.d.mts +77 -0
- package/dist/seeder/index.d.mts.map +1 -0
- package/dist/seeder/index.mjs +8 -0
- package/dist/seeder-Cupi5jl-.mjs +132 -0
- package/dist/seeder-Cupi5jl-.mjs.map +1 -0
- package/dist/{smtp.provider-BYY-AdmU.mjs → smtp.provider-B8XtOcHU.mjs} +5 -4
- package/dist/{smtp.provider-BYY-AdmU.mjs.map → smtp.provider-B8XtOcHU.mjs.map} +1 -1
- package/dist/storage/index.d.mts +6 -272
- package/dist/storage/index.d.mts.map +1 -1
- package/dist/storage/index.mjs +15 -9
- package/dist/{storage-dgi7MG6z.mjs → storage-By_ow2o_.mjs} +35 -411
- package/dist/storage-By_ow2o_.mjs.map +1 -0
- package/dist/{stratal-D4MS_7pI.mjs → stratal-CE0iTz4f.mjs} +44 -9
- package/dist/stratal-CE0iTz4f.mjs.map +1 -0
- package/dist/types-CLhOhYsQ.d.mts +64 -0
- package/dist/types-CLhOhYsQ.d.mts.map +1 -0
- package/dist/{types-JUIHSW_a.d.mts → types-DahElfUw.d.mts} +1 -1
- package/dist/types-DahElfUw.d.mts.map +1 -0
- package/dist/usage-generator-C9hWziY4.mjs +158 -0
- package/dist/usage-generator-C9hWziY4.mjs.map +1 -0
- package/dist/{validation-DA5nptIp.mjs → validation-Bh875Lyg.mjs} +1 -1
- package/dist/{validation-DA5nptIp.mjs.map → validation-Bh875Lyg.mjs.map} +1 -1
- package/dist/websocket/index.d.mts +2 -2
- package/dist/websocket/index.mjs +5 -4
- package/dist/workers/index.d.mts +1 -1
- package/dist/workers/index.mjs +20 -13
- package/dist/workers/index.mjs.map +1 -1
- package/package.json +17 -7
- package/dist/en-C9U5-ETs.mjs.map +0 -1
- package/dist/errors-BRJgVd5-.mjs.map +0 -1
- package/dist/i18n.module-C9wQr_2k.mjs.map +0 -1
- package/dist/index-C9bIk5tt.d.mts.map +0 -1
- package/dist/logger-Bg-CuidS.mjs.map +0 -1
- package/dist/module-Dvzm4dhS.mjs.map +0 -1
- package/dist/storage-dgi7MG6z.mjs.map +0 -1
- package/dist/stratal-D4MS_7pI.mjs.map +0 -1
- package/dist/types-JUIHSW_a.d.mts.map +0 -1
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import "./errors-CtCi1wn6.mjs";
|
|
2
|
+
import "./decorate-D5j-d9_z.mjs";
|
|
3
|
+
import "./logger-BR1-s1Um.mjs";
|
|
4
|
+
import { t as StorageResponseBodyMissingError } from "./errors-H3TZnVeX.mjs";
|
|
5
|
+
import { DOMParser } from "@xmldom/xmldom";
|
|
6
|
+
import { AbortMultipartUploadCommand, CompleteMultipartUploadCommand, CreateMultipartUploadCommand, DeleteObjectCommand, DeleteObjectsCommand, GetObjectCommand, HeadObjectCommand, ListMultipartUploadsCommand, ListPartsCommand, PutObjectCommand, S3Client, UploadPartCommand } from "@aws-sdk/client-s3";
|
|
7
|
+
import { Upload } from "@aws-sdk/lib-storage";
|
|
8
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
9
|
+
//#region src/storage/dom.polyfill.ts
|
|
10
|
+
/**
|
|
11
|
+
* DOM polyfills for Cloudflare Workers
|
|
12
|
+
*
|
|
13
|
+
* AWS SDK v3 uses DOMParser and Node for XML parsing, which are not available in Workers.
|
|
14
|
+
* This module must be imported BEFORE any AWS SDK imports (including transitive).
|
|
15
|
+
*
|
|
16
|
+
* @see https://github.com/aws/aws-sdk-js-v3/issues/7375
|
|
17
|
+
*/
|
|
18
|
+
globalThis.DOMParser = DOMParser;
|
|
19
|
+
if (typeof globalThis.Node === "undefined") globalThis.Node = {
|
|
20
|
+
ELEMENT_NODE: 1,
|
|
21
|
+
ATTRIBUTE_NODE: 2,
|
|
22
|
+
TEXT_NODE: 3,
|
|
23
|
+
CDATA_SECTION_NODE: 4,
|
|
24
|
+
ENTITY_REFERENCE_NODE: 5,
|
|
25
|
+
ENTITY_NODE: 6,
|
|
26
|
+
PROCESSING_INSTRUCTION_NODE: 7,
|
|
27
|
+
COMMENT_NODE: 8,
|
|
28
|
+
DOCUMENT_NODE: 9,
|
|
29
|
+
DOCUMENT_TYPE_NODE: 10,
|
|
30
|
+
DOCUMENT_FRAGMENT_NODE: 11,
|
|
31
|
+
NOTATION_NODE: 12
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/storage/providers/s3-storage.provider.ts
|
|
35
|
+
/**
|
|
36
|
+
* S3 Storage Provider
|
|
37
|
+
* Implements storage operations using AWS SDK for S3-compatible storage
|
|
38
|
+
* Works with AWS S3, Cloudflare R2, MinIO, and other S3-compatible services
|
|
39
|
+
*
|
|
40
|
+
* Implements IS3MultipartProvider for multipart upload support needed by TUS
|
|
41
|
+
*/
|
|
42
|
+
var S3StorageProvider = class {
|
|
43
|
+
client;
|
|
44
|
+
bucket;
|
|
45
|
+
disk;
|
|
46
|
+
constructor(config) {
|
|
47
|
+
this.client = new S3Client({
|
|
48
|
+
region: config.region || "auto",
|
|
49
|
+
endpoint: config.endpoint,
|
|
50
|
+
credentials: {
|
|
51
|
+
accessKeyId: config.accessKeyId,
|
|
52
|
+
secretAccessKey: config.secretAccessKey
|
|
53
|
+
},
|
|
54
|
+
forcePathStyle: true
|
|
55
|
+
});
|
|
56
|
+
this.bucket = config.bucket;
|
|
57
|
+
this.disk = config.disk;
|
|
58
|
+
}
|
|
59
|
+
async upload(body, path, options) {
|
|
60
|
+
const command = new PutObjectCommand({
|
|
61
|
+
Bucket: this.bucket,
|
|
62
|
+
Key: path,
|
|
63
|
+
Body: body,
|
|
64
|
+
ContentType: options.mimeType,
|
|
65
|
+
ContentLength: options.size,
|
|
66
|
+
Metadata: options.metadata,
|
|
67
|
+
Tagging: options.tagging
|
|
68
|
+
});
|
|
69
|
+
await this.client.send(command);
|
|
70
|
+
return {
|
|
71
|
+
path,
|
|
72
|
+
disk: this.disk,
|
|
73
|
+
fullPath: `${this.bucket}/${path}`,
|
|
74
|
+
size: options.size,
|
|
75
|
+
mimeType: options.mimeType ?? "application/octet-stream",
|
|
76
|
+
uploadedAt: /* @__PURE__ */ new Date()
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async download(path) {
|
|
80
|
+
const command = new GetObjectCommand({
|
|
81
|
+
Bucket: this.bucket,
|
|
82
|
+
Key: path
|
|
83
|
+
});
|
|
84
|
+
const response = await this.client.send(command);
|
|
85
|
+
if (!response.Body) throw new StorageResponseBodyMissingError(path);
|
|
86
|
+
return {
|
|
87
|
+
toStream: () => response.Body?.transformToWebStream(),
|
|
88
|
+
contentType: response.ContentType ?? "application/octet-stream",
|
|
89
|
+
size: response.ContentLength ?? 0,
|
|
90
|
+
metadata: response.Metadata,
|
|
91
|
+
toString: () => response.Body?.transformToString(),
|
|
92
|
+
toArrayBuffer: () => response.Body?.transformToByteArray()
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async delete(path) {
|
|
96
|
+
const command = new DeleteObjectCommand({
|
|
97
|
+
Bucket: this.bucket,
|
|
98
|
+
Key: path
|
|
99
|
+
});
|
|
100
|
+
await this.client.send(command);
|
|
101
|
+
}
|
|
102
|
+
async exists(path) {
|
|
103
|
+
try {
|
|
104
|
+
const command = new HeadObjectCommand({
|
|
105
|
+
Bucket: this.bucket,
|
|
106
|
+
Key: path
|
|
107
|
+
});
|
|
108
|
+
await this.client.send(command);
|
|
109
|
+
return true;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async getPresignedUrl(path, method, expiresIn) {
|
|
115
|
+
let command;
|
|
116
|
+
switch (method) {
|
|
117
|
+
case "GET":
|
|
118
|
+
command = new GetObjectCommand({
|
|
119
|
+
Bucket: this.bucket,
|
|
120
|
+
Key: path
|
|
121
|
+
});
|
|
122
|
+
break;
|
|
123
|
+
case "PUT":
|
|
124
|
+
command = new PutObjectCommand({
|
|
125
|
+
Bucket: this.bucket,
|
|
126
|
+
Key: path
|
|
127
|
+
});
|
|
128
|
+
break;
|
|
129
|
+
case "DELETE":
|
|
130
|
+
command = new DeleteObjectCommand({
|
|
131
|
+
Bucket: this.bucket,
|
|
132
|
+
Key: path
|
|
133
|
+
});
|
|
134
|
+
break;
|
|
135
|
+
case "HEAD":
|
|
136
|
+
command = new HeadObjectCommand({
|
|
137
|
+
Bucket: this.bucket,
|
|
138
|
+
Key: path
|
|
139
|
+
});
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
url: await getSignedUrl(this.client, command, { expiresIn }),
|
|
144
|
+
expiresIn,
|
|
145
|
+
expiresAt: new Date(Date.now() + expiresIn * 1e3),
|
|
146
|
+
method
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Chunked upload for streaming data without known size
|
|
151
|
+
* Uses @aws-sdk/lib-storage for multipart upload handling
|
|
152
|
+
*
|
|
153
|
+
* Benefits:
|
|
154
|
+
* - Handles unknown Content-Length automatically
|
|
155
|
+
* - Automatic retry on transient failures
|
|
156
|
+
* - Cleanup of partial uploads on error
|
|
157
|
+
* - Works with S3-compatible storage (R2, MinIO, RustFS)
|
|
158
|
+
*
|
|
159
|
+
* @param body - Content to upload (stream or buffer)
|
|
160
|
+
* @param path - Full path including disk root
|
|
161
|
+
* @param options - Upload options (mimeType required, size optional)
|
|
162
|
+
* @returns Upload result with metadata
|
|
163
|
+
*/
|
|
164
|
+
async chunkedUpload(body, path, options) {
|
|
165
|
+
await new Upload({
|
|
166
|
+
client: this.client,
|
|
167
|
+
params: {
|
|
168
|
+
Bucket: this.bucket,
|
|
169
|
+
Key: path,
|
|
170
|
+
Body: body,
|
|
171
|
+
ContentType: options.mimeType
|
|
172
|
+
},
|
|
173
|
+
queueSize: 4,
|
|
174
|
+
partSize: 5 * 1024 * 1024,
|
|
175
|
+
leavePartsOnError: false
|
|
176
|
+
}).done();
|
|
177
|
+
const headResponse = await this.client.send(new HeadObjectCommand({
|
|
178
|
+
Bucket: this.bucket,
|
|
179
|
+
Key: path
|
|
180
|
+
}));
|
|
181
|
+
return {
|
|
182
|
+
path,
|
|
183
|
+
disk: this.disk,
|
|
184
|
+
fullPath: `${this.bucket}/${path}`,
|
|
185
|
+
size: headResponse.ContentLength ?? options.size ?? 0,
|
|
186
|
+
mimeType: options.mimeType ?? "application/octet-stream",
|
|
187
|
+
uploadedAt: /* @__PURE__ */ new Date()
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Get the bucket name
|
|
192
|
+
*/
|
|
193
|
+
getBucket() {
|
|
194
|
+
return this.bucket;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get object metadata without downloading the body
|
|
198
|
+
*/
|
|
199
|
+
async headObject(key) {
|
|
200
|
+
const response = await this.client.send(new HeadObjectCommand({
|
|
201
|
+
Bucket: this.bucket,
|
|
202
|
+
Key: key
|
|
203
|
+
}));
|
|
204
|
+
return {
|
|
205
|
+
size: response.ContentLength ?? 0,
|
|
206
|
+
contentType: response.ContentType,
|
|
207
|
+
metadata: response.Metadata
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Delete multiple objects in a single request
|
|
212
|
+
*/
|
|
213
|
+
async deleteObjects(keys) {
|
|
214
|
+
if (keys.length === 0) return {
|
|
215
|
+
deleted: 0,
|
|
216
|
+
errors: []
|
|
217
|
+
};
|
|
218
|
+
const response = await this.client.send(new DeleteObjectsCommand({
|
|
219
|
+
Bucket: this.bucket,
|
|
220
|
+
Delete: { Objects: keys.map((key) => ({ Key: key })) }
|
|
221
|
+
}));
|
|
222
|
+
return {
|
|
223
|
+
deleted: response.Deleted?.length ?? 0,
|
|
224
|
+
errors: (response.Errors ?? []).map((e) => ({
|
|
225
|
+
key: e.Key ?? "",
|
|
226
|
+
code: e.Code ?? "Unknown",
|
|
227
|
+
message: e.Message ?? "Unknown error"
|
|
228
|
+
}))
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Create a multipart upload
|
|
233
|
+
*/
|
|
234
|
+
async createMultipartUpload(key, options) {
|
|
235
|
+
const response = await this.client.send(new CreateMultipartUploadCommand({
|
|
236
|
+
Bucket: this.bucket,
|
|
237
|
+
Key: key,
|
|
238
|
+
ContentType: options?.contentType,
|
|
239
|
+
CacheControl: options?.cacheControl,
|
|
240
|
+
Metadata: options?.metadata,
|
|
241
|
+
Tagging: options?.tagging
|
|
242
|
+
}));
|
|
243
|
+
return {
|
|
244
|
+
uploadId: response.UploadId ?? "",
|
|
245
|
+
key: response.Key ?? ""
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Upload a part to an existing multipart upload
|
|
250
|
+
*/
|
|
251
|
+
async uploadPart(key, uploadId, partNumber, body) {
|
|
252
|
+
return {
|
|
253
|
+
etag: (await this.client.send(new UploadPartCommand({
|
|
254
|
+
Bucket: this.bucket,
|
|
255
|
+
Key: key,
|
|
256
|
+
UploadId: uploadId,
|
|
257
|
+
PartNumber: partNumber,
|
|
258
|
+
Body: body,
|
|
259
|
+
ContentLength: body.length
|
|
260
|
+
}))).ETag ?? "",
|
|
261
|
+
partNumber
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Complete a multipart upload
|
|
266
|
+
*/
|
|
267
|
+
async completeMultipartUpload(key, uploadId, parts) {
|
|
268
|
+
const response = await this.client.send(new CompleteMultipartUploadCommand({
|
|
269
|
+
Bucket: this.bucket,
|
|
270
|
+
Key: key,
|
|
271
|
+
UploadId: uploadId,
|
|
272
|
+
MultipartUpload: { Parts: parts.map((p) => ({
|
|
273
|
+
ETag: p.etag,
|
|
274
|
+
PartNumber: p.partNumber
|
|
275
|
+
})) }
|
|
276
|
+
}));
|
|
277
|
+
return {
|
|
278
|
+
location: response.Location,
|
|
279
|
+
key: response.Key ?? ""
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Abort a multipart upload
|
|
284
|
+
*/
|
|
285
|
+
async abortMultipartUpload(key, uploadId) {
|
|
286
|
+
await this.client.send(new AbortMultipartUploadCommand({
|
|
287
|
+
Bucket: this.bucket,
|
|
288
|
+
Key: key,
|
|
289
|
+
UploadId: uploadId
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* List parts of a multipart upload
|
|
294
|
+
*/
|
|
295
|
+
async listParts(key, uploadId, partNumberMarker) {
|
|
296
|
+
const response = await this.client.send(new ListPartsCommand({
|
|
297
|
+
Bucket: this.bucket,
|
|
298
|
+
Key: key,
|
|
299
|
+
UploadId: uploadId,
|
|
300
|
+
PartNumberMarker: partNumberMarker
|
|
301
|
+
}));
|
|
302
|
+
return {
|
|
303
|
+
parts: (response.Parts ?? []).map((p) => ({
|
|
304
|
+
partNumber: p.PartNumber ?? 0,
|
|
305
|
+
etag: p.ETag ?? "",
|
|
306
|
+
size: p.Size ?? 0
|
|
307
|
+
})),
|
|
308
|
+
isTruncated: response.IsTruncated ?? false,
|
|
309
|
+
nextPartNumberMarker: response.NextPartNumberMarker?.toString()
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* List all in-progress multipart uploads
|
|
314
|
+
*/
|
|
315
|
+
async listMultipartUploads(keyMarker, uploadIdMarker) {
|
|
316
|
+
const response = await this.client.send(new ListMultipartUploadsCommand({
|
|
317
|
+
Bucket: this.bucket,
|
|
318
|
+
KeyMarker: keyMarker,
|
|
319
|
+
UploadIdMarker: uploadIdMarker
|
|
320
|
+
}));
|
|
321
|
+
return {
|
|
322
|
+
uploads: (response.Uploads ?? []).map((u) => ({
|
|
323
|
+
key: u.Key ?? "",
|
|
324
|
+
uploadId: u.UploadId ?? "",
|
|
325
|
+
initiated: u.Initiated
|
|
326
|
+
})),
|
|
327
|
+
isTruncated: response.IsTruncated ?? false,
|
|
328
|
+
nextKeyMarker: response.NextKeyMarker,
|
|
329
|
+
nextUploadIdMarker: response.NextUploadIdMarker
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
//#endregion
|
|
334
|
+
export { S3StorageProvider };
|
|
335
|
+
|
|
336
|
+
//# sourceMappingURL=s3-storage.provider-BLlzQYiJ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-storage.provider-BLlzQYiJ.mjs","names":[],"sources":["../src/storage/dom.polyfill.ts","../src/storage/providers/s3-storage.provider.ts"],"sourcesContent":["/**\n * DOM polyfills for Cloudflare Workers\n *\n * AWS SDK v3 uses DOMParser and Node for XML parsing, which are not available in Workers.\n * This module must be imported BEFORE any AWS SDK imports (including transitive).\n *\n * @see https://github.com/aws/aws-sdk-js-v3/issues/7375\n */\nimport { DOMParser } from '@xmldom/xmldom'\n\n// DOMParser polyfill for XML response parsing\nglobalThis.DOMParser = DOMParser\n\n// Node interface polyfill with DOM node type constants\n// Required by AWS SDK for XML response parsing\nif (typeof globalThis.Node === 'undefined') {\n globalThis.Node = {\n ELEMENT_NODE: 1,\n ATTRIBUTE_NODE: 2,\n TEXT_NODE: 3,\n CDATA_SECTION_NODE: 4,\n ENTITY_REFERENCE_NODE: 5,\n ENTITY_NODE: 6,\n PROCESSING_INSTRUCTION_NODE: 7,\n COMMENT_NODE: 8,\n DOCUMENT_NODE: 9,\n DOCUMENT_TYPE_NODE: 10,\n DOCUMENT_FRAGMENT_NODE: 11,\n NOTATION_NODE: 12,\n } as unknown as typeof Node\n}\n","import '../dom.polyfill'\n\nimport {\n AbortMultipartUploadCommand,\n CompleteMultipartUploadCommand,\n CreateMultipartUploadCommand,\n DeleteObjectCommand,\n DeleteObjectsCommand,\n GetObjectCommand,\n HeadObjectCommand,\n ListMultipartUploadsCommand,\n ListPartsCommand,\n PutObjectCommand,\n S3Client,\n UploadPartCommand\n} from '@aws-sdk/client-s3'\nimport { Upload } from '@aws-sdk/lib-storage'\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner'\nimport type { StorageEntry } from '../types'\nimport type { DownloadResult, PresignedUrlResult, UploadOptions, UploadResult } from '../contracts'\nimport { StorageResponseBodyMissingError } from '../errors'\nimport type {\n CompletedPart,\n CompleteMultipartResult,\n CreateMultipartOptions,\n CreateMultipartResult,\n DeleteObjectsResult,\n HeadObjectResult,\n IS3MultipartProvider,\n ListMultipartUploadsResult,\n ListPartsResult,\n UploadPartResult,\n} from './s3-multipart-provider.interface'\nimport type { StreamingBlobPayloadInputTypes } from './storage-provider.interface'\n\n/**\n * S3 Storage Provider\n * Implements storage operations using AWS SDK for S3-compatible storage\n * Works with AWS S3, Cloudflare R2, MinIO, and other S3-compatible services\n *\n * Implements IS3MultipartProvider for multipart upload support needed by TUS\n */\nexport class S3StorageProvider implements IS3MultipartProvider {\n private readonly client: S3Client\n private readonly bucket: string\n private readonly disk: string\n\n constructor(config: StorageEntry) {\n // Configure S3Client for S3-compatible storage\n this.client = new S3Client({\n region: config.region || 'auto',\n endpoint: config.endpoint,\n credentials: {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n },\n forcePathStyle: true,\n })\n\n this.bucket = config.bucket\n this.disk = config.disk\n }\n\n async upload(\n body: StreamingBlobPayloadInputTypes,\n path: string,\n options: UploadOptions\n ): Promise<UploadResult> {\n const command = new PutObjectCommand({\n Bucket: this.bucket,\n Key: path,\n Body: body,\n ContentType: options.mimeType,\n ContentLength: options.size,\n Metadata: options.metadata,\n Tagging: options.tagging,\n })\n\n await this.client.send(command)\n\n return {\n path,\n disk: this.disk,\n fullPath: `${this.bucket}/${path}`,\n size: options.size,\n mimeType: options.mimeType ?? 'application/octet-stream',\n uploadedAt: new Date(),\n }\n }\n\n async download(path: string): Promise<DownloadResult> {\n const command = new GetObjectCommand({\n Bucket: this.bucket,\n Key: path,\n })\n\n const response = await this.client.send(command)\n\n if (!response.Body) {\n throw new StorageResponseBodyMissingError(path)\n }\n\n return {\n toStream: () => response.Body?.transformToWebStream(),\n contentType: response.ContentType ?? 'application/octet-stream',\n size: response.ContentLength ?? 0,\n metadata: response.Metadata,\n toString: () => response.Body?.transformToString(),\n toArrayBuffer: () => response.Body?.transformToByteArray(),\n }\n }\n\n async delete(path: string): Promise<void> {\n const command = new DeleteObjectCommand({\n Bucket: this.bucket,\n Key: path,\n })\n\n await this.client.send(command)\n }\n\n async exists(path: string): Promise<boolean> {\n try {\n const command = new HeadObjectCommand({\n Bucket: this.bucket,\n Key: path,\n })\n\n await this.client.send(command)\n return true\n } catch {\n // HeadObject throws error if file doesn't exist\n return false\n }\n }\n\n async getPresignedUrl(\n path: string,\n method: 'GET' | 'PUT' | 'DELETE' | 'HEAD',\n expiresIn: number\n ): Promise<PresignedUrlResult> {\n // Select appropriate command based on method\n let command\n switch (method) {\n case 'GET':\n command = new GetObjectCommand({ Bucket: this.bucket, Key: path })\n break\n case 'PUT':\n command = new PutObjectCommand({ Bucket: this.bucket, Key: path })\n break\n case 'DELETE':\n command = new DeleteObjectCommand({ Bucket: this.bucket, Key: path })\n break\n case 'HEAD':\n command = new HeadObjectCommand({ Bucket: this.bucket, Key: path })\n break\n }\n\n const url = await getSignedUrl(this.client, command as GetObjectCommand, { expiresIn })\n\n return {\n url,\n expiresIn,\n expiresAt: new Date(Date.now() + expiresIn * 1000),\n method,\n }\n }\n\n /**\n * Chunked upload for streaming data without known size\n * Uses @aws-sdk/lib-storage for multipart upload handling\n *\n * Benefits:\n * - Handles unknown Content-Length automatically\n * - Automatic retry on transient failures\n * - Cleanup of partial uploads on error\n * - Works with S3-compatible storage (R2, MinIO, RustFS)\n *\n * @param body - Content to upload (stream or buffer)\n * @param path - Full path including disk root\n * @param options - Upload options (mimeType required, size optional)\n * @returns Upload result with metadata\n */\n async chunkedUpload(\n body: StreamingBlobPayloadInputTypes,\n path: string,\n options: Omit<UploadOptions, 'size'> & { size?: number }\n ): Promise<UploadResult> {\n const upload = new Upload({\n client: this.client,\n params: {\n Bucket: this.bucket,\n Key: path,\n Body: body,\n ContentType: options.mimeType,\n },\n // Concurrency configuration\n queueSize: 4,\n // Part size: 5MB minimum for S3\n partSize: 5 * 1024 * 1024,\n // Don't leave orphaned parts on error\n leavePartsOnError: false,\n })\n\n await upload.done()\n\n // Get the actual uploaded size via HeadObject\n const headResponse = await this.client.send(\n new HeadObjectCommand({\n Bucket: this.bucket,\n Key: path,\n })\n )\n\n return {\n path,\n disk: this.disk,\n fullPath: `${this.bucket}/${path}`,\n size: headResponse.ContentLength ?? options.size ?? 0,\n mimeType: options.mimeType ?? 'application/octet-stream',\n uploadedAt: new Date(),\n }\n }\n\n // ============================================\n // IS3MultipartProvider - Multipart upload methods\n // ============================================\n\n /**\n * Get the bucket name\n */\n getBucket(): string {\n return this.bucket\n }\n\n /**\n * Get object metadata without downloading the body\n */\n async headObject(key: string): Promise<HeadObjectResult | null> {\n const response = await this.client.send(\n new HeadObjectCommand({\n Bucket: this.bucket,\n Key: key,\n })\n )\n return {\n size: response.ContentLength ?? 0,\n contentType: response.ContentType,\n metadata: response.Metadata,\n }\n }\n\n /**\n * Delete multiple objects in a single request\n */\n async deleteObjects(keys: string[]): Promise<DeleteObjectsResult> {\n if (keys.length === 0) {\n return { deleted: 0, errors: [] }\n }\n\n const response = await this.client.send(\n new DeleteObjectsCommand({\n Bucket: this.bucket,\n Delete: {\n Objects: keys.map((key) => ({ Key: key })),\n },\n })\n )\n\n return {\n deleted: response.Deleted?.length ?? 0,\n errors: (response.Errors ?? []).map((e) => ({\n key: e.Key ?? '',\n code: e.Code ?? 'Unknown',\n message: e.Message ?? 'Unknown error',\n })),\n }\n }\n\n /**\n * Create a multipart upload\n */\n async createMultipartUpload(\n key: string,\n options?: CreateMultipartOptions\n ): Promise<CreateMultipartResult> {\n const response = await this.client.send(\n new CreateMultipartUploadCommand({\n Bucket: this.bucket,\n Key: key,\n ContentType: options?.contentType,\n CacheControl: options?.cacheControl,\n Metadata: options?.metadata,\n Tagging: options?.tagging,\n })\n )\n\n return {\n uploadId: response.UploadId ?? '',\n key: response.Key ?? '',\n }\n }\n\n /**\n * Upload a part to an existing multipart upload\n */\n async uploadPart(\n key: string,\n uploadId: string,\n partNumber: number,\n body: Uint8Array\n ): Promise<UploadPartResult> {\n const response = await this.client.send(\n new UploadPartCommand({\n Bucket: this.bucket,\n Key: key,\n UploadId: uploadId,\n PartNumber: partNumber,\n Body: body,\n ContentLength: body.length,\n })\n )\n\n return {\n etag: response.ETag ?? '',\n partNumber,\n }\n }\n\n /**\n * Complete a multipart upload\n */\n async completeMultipartUpload(\n key: string,\n uploadId: string,\n parts: CompletedPart[]\n ): Promise<CompleteMultipartResult> {\n const response = await this.client.send(\n new CompleteMultipartUploadCommand({\n Bucket: this.bucket,\n Key: key,\n UploadId: uploadId,\n MultipartUpload: {\n Parts: parts.map((p) => ({\n ETag: p.etag,\n PartNumber: p.partNumber,\n })),\n },\n })\n )\n\n return {\n location: response.Location,\n key: response.Key ?? '',\n }\n }\n\n /**\n * Abort a multipart upload\n */\n async abortMultipartUpload(key: string, uploadId: string): Promise<void> {\n await this.client.send(\n new AbortMultipartUploadCommand({\n Bucket: this.bucket,\n Key: key,\n UploadId: uploadId,\n })\n )\n }\n\n /**\n * List parts of a multipart upload\n */\n async listParts(\n key: string,\n uploadId: string,\n partNumberMarker?: string\n ): Promise<ListPartsResult> {\n const response = await this.client.send(\n new ListPartsCommand({\n Bucket: this.bucket,\n Key: key,\n UploadId: uploadId,\n PartNumberMarker: partNumberMarker,\n })\n )\n\n return {\n parts: (response.Parts ?? []).map((p) => ({\n partNumber: p.PartNumber ?? 0,\n etag: p.ETag ?? '',\n size: p.Size ?? 0,\n })),\n isTruncated: response.IsTruncated ?? false,\n nextPartNumberMarker: response.NextPartNumberMarker?.toString(),\n }\n }\n\n /**\n * List all in-progress multipart uploads\n */\n async listMultipartUploads(\n keyMarker?: string,\n uploadIdMarker?: string\n ): Promise<ListMultipartUploadsResult> {\n const response = await this.client.send(\n new ListMultipartUploadsCommand({\n Bucket: this.bucket,\n KeyMarker: keyMarker,\n UploadIdMarker: uploadIdMarker,\n })\n )\n\n return {\n uploads: (response.Uploads ?? []).map((u) => ({\n key: u.Key ?? '',\n uploadId: u.UploadId ?? '',\n initiated: u.Initiated,\n })),\n isTruncated: response.IsTruncated ?? false,\n nextKeyMarker: response.NextKeyMarker,\n nextUploadIdMarker: response.NextUploadIdMarker,\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { $t as Container, s as ApplicationError } from "../index-BJWm863C.mjs";
|
|
2
|
+
import { t as Constructor } from "../types-DahElfUw.mjs";
|
|
3
|
+
import { t as Application } from "../application-zG8b-pol.mjs";
|
|
4
|
+
import { t as Command } from "../command-B-QH-Vu3.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/seeder/seeder.d.ts
|
|
7
|
+
declare const SEEDER_INTERNALS: unique symbol;
|
|
8
|
+
interface SeederContext {
|
|
9
|
+
run(SeederClass: Constructor<Seeder>): Promise<void>;
|
|
10
|
+
container: Container | null;
|
|
11
|
+
}
|
|
12
|
+
declare abstract class Seeder {
|
|
13
|
+
[SEEDER_INTERNALS]: SeederContext;
|
|
14
|
+
abstract run(): Promise<void>;
|
|
15
|
+
/** Call another seeder (like Laravel's $this->call()) */
|
|
16
|
+
protected call(SeederClass: Constructor<Seeder>): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/seeder/seeder-registry.d.ts
|
|
20
|
+
declare const SEEDER_TOKENS: {
|
|
21
|
+
readonly SeederRegistry: symbol;
|
|
22
|
+
};
|
|
23
|
+
declare class SeederRegistry {
|
|
24
|
+
private app;
|
|
25
|
+
private seeders;
|
|
26
|
+
private nameIndex;
|
|
27
|
+
constructor(app: Application);
|
|
28
|
+
register(SeederClass: Constructor<Seeder>): void;
|
|
29
|
+
run(SeederClass: Constructor<Seeder>, options?: {
|
|
30
|
+
container?: Container;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
runAll(options?: {
|
|
33
|
+
container?: Container;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
find(name: string): Constructor<Seeder> | undefined;
|
|
36
|
+
has(SeederClass: Constructor<Seeder>): boolean;
|
|
37
|
+
list(): {
|
|
38
|
+
className: string;
|
|
39
|
+
}[];
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/seeder/commands/db-seed-list.command.d.ts
|
|
43
|
+
declare class DbSeedListCommand extends Command {
|
|
44
|
+
private seeders;
|
|
45
|
+
static command: string;
|
|
46
|
+
static description: string;
|
|
47
|
+
constructor(seeders: SeederRegistry);
|
|
48
|
+
handle(): undefined | number;
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/seeder/commands/db-seed.command.d.ts
|
|
52
|
+
declare class DbSeedCommand extends Command {
|
|
53
|
+
private seeders;
|
|
54
|
+
static command: string;
|
|
55
|
+
static description: string;
|
|
56
|
+
constructor(seeders: SeederRegistry);
|
|
57
|
+
handle(): Promise<number | undefined>;
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/seeder/errors.d.ts
|
|
61
|
+
declare class SeederNotRegisteredError extends ApplicationError {
|
|
62
|
+
constructor(name: string);
|
|
63
|
+
}
|
|
64
|
+
declare class SeederNameCollisionError extends ApplicationError {
|
|
65
|
+
constructor(name: string);
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/seeder/is-seeder.d.ts
|
|
69
|
+
/**
|
|
70
|
+
* Check if a class is a Seeder (extends Seeder base class).
|
|
71
|
+
*
|
|
72
|
+
* Used by ModuleRegistry for auto-discovery from providers.
|
|
73
|
+
*/
|
|
74
|
+
declare function isSeeder(target: Constructor): boolean;
|
|
75
|
+
//#endregion
|
|
76
|
+
export { DbSeedCommand, DbSeedListCommand, SEEDER_INTERNALS, SEEDER_TOKENS, Seeder, type SeederContext, SeederNameCollisionError, SeederNotRegisteredError, SeederRegistry, isSeeder };
|
|
77
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/seeder/seeder.ts","../../src/seeder/seeder-registry.ts","../../src/seeder/commands/db-seed-list.command.ts","../../src/seeder/commands/db-seed.command.ts","../../src/seeder/errors.ts","../../src/seeder/is-seeder.ts"],"mappings":";;;;;;cAGa,gBAAA;AAAA,UAEI,aAAA;EACf,GAAA,CAAI,WAAA,EAAa,WAAA,CAAY,MAAA,IAAU,OAAA;EACvC,SAAA,EAAW,SAAA;AAAA;AAAA,uBAGS,MAAA;EAAA,CACnB,gBAAA,GAAmB,aAAA;EAAA,SAKX,GAAA,CAAA,GAAO,OAAA;EAbwC;EAAA,UAgBxC,IAAA,CAAK,WAAA,EAAa,WAAA,CAAY,MAAA,IAAU,OAAA;AAAA;;;cCb7C,aAAA;EAAA,SAEH,cAAA;AAAA;AAAA,cAEG,cAAA;EAAA,QAIS,GAAA;EAAA,QAHZ,OAAA;EAAA,QACA,SAAA;cAEY,GAAA,EAAK,WAAA;EAEzB,QAAA,CAAS,WAAA,EAAa,WAAA,CAAY,MAAA;EAS5B,GAAA,CAAI,WAAA,EAAa,WAAA,CAAY,MAAA,GAAS,OAAA;IAAY,SAAA,GAAY,SAAA;EAAA,IAAc,OAAA;EAsB5E,MAAA,CAAO,OAAA;IAAY,SAAA,GAAY,SAAA;EAAA,IAAc,OAAA;EAMnD,IAAA,CAAK,IAAA,WAAe,WAAA,CAAY,MAAA;EAIhC,GAAA,CAAI,WAAA,EAAa,WAAA,CAAY,MAAA;EAI7B,IAAA,CAAA;IAAU,SAAA;EAAA;AAAA;;;cCzDC,iBAAA,SAA0B,OAAA;EAAA,QAIqB,OAAA;EAAA,OAHnD,OAAA;EAAA,OACA,WAAA;cAEmD,OAAA,EAAS,cAAA;EAInE,MAAA,CAAA;AAAA;;;cCRW,aAAA,SAAsB,OAAA;EAAA,QAIyB,OAAA;EAAA,OAHnD,OAAA;EAAA,OACA,WAAA;cAEmD,OAAA,EAAS,cAAA;EAI7D,MAAA,CAAA,GAAU,OAAA;AAAA;;;cCVL,wBAAA,SAAiC,gBAAA;cAChC,IAAA;AAAA;AAAA,cASD,wBAAA,SAAiC,gBAAA;cAChC,IAAA;AAAA;;;;;;;;iBCLE,QAAA,CAAS,MAAA,EAAQ,WAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "../errors-CtCi1wn6.mjs";
|
|
2
|
+
import "../decorate-D5j-d9_z.mjs";
|
|
3
|
+
import "../logger-BR1-s1Um.mjs";
|
|
4
|
+
import "../colors-DJaRDXoS.mjs";
|
|
5
|
+
import "../command-BvCOD6df.mjs";
|
|
6
|
+
import { n as SEEDER_INTERNALS, r as Seeder, t as isSeeder } from "../is-seeder-CebjZCDn.mjs";
|
|
7
|
+
import { a as SeederNameCollisionError, i as SeederRegistry, n as DbSeedListCommand, o as SeederNotRegisteredError, r as SEEDER_TOKENS, t as DbSeedCommand } from "../seeder-Cupi5jl-.mjs";
|
|
8
|
+
export { DbSeedCommand, DbSeedListCommand, SEEDER_INTERNALS, SEEDER_TOKENS, Seeder, SeederNameCollisionError, SeederNotRegisteredError, SeederRegistry, isSeeder };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { S as ApplicationError, b as ERROR_CODES } from "./errors-CtCi1wn6.mjs";
|
|
2
|
+
import { n as __decorateParam, r as __decorateMetadata, t as __decorate } from "./decorate-D5j-d9_z.mjs";
|
|
3
|
+
import { t as Command } from "./command-BvCOD6df.mjs";
|
|
4
|
+
import { n as SEEDER_INTERNALS } from "./is-seeder-CebjZCDn.mjs";
|
|
5
|
+
import { inject } from "tsyringe";
|
|
6
|
+
//#region src/seeder/errors.ts
|
|
7
|
+
var SeederNotRegisteredError = class extends ApplicationError {
|
|
8
|
+
constructor(name) {
|
|
9
|
+
super("errors.seederNotRegistered", ERROR_CODES.SYSTEM.SEEDER_NOT_REGISTERED, { name });
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var SeederNameCollisionError = class extends ApplicationError {
|
|
13
|
+
constructor(name) {
|
|
14
|
+
super("errors.seederNameCollision", ERROR_CODES.SYSTEM.SEEDER_NAME_COLLISION, { name });
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/seeder/seeder-registry.ts
|
|
19
|
+
const SEEDER_TOKENS = { SeederRegistry: Symbol.for("stratal:seeders:registry") };
|
|
20
|
+
var SeederRegistry = class {
|
|
21
|
+
seeders = /* @__PURE__ */ new Set();
|
|
22
|
+
nameIndex = /* @__PURE__ */ new Map();
|
|
23
|
+
constructor(app) {
|
|
24
|
+
this.app = app;
|
|
25
|
+
}
|
|
26
|
+
register(SeederClass) {
|
|
27
|
+
const existing = this.nameIndex.get(SeederClass.name);
|
|
28
|
+
if (existing && existing !== SeederClass) throw new SeederNameCollisionError(SeederClass.name);
|
|
29
|
+
this.seeders.add(SeederClass);
|
|
30
|
+
this.nameIndex.set(SeederClass.name, SeederClass);
|
|
31
|
+
}
|
|
32
|
+
async run(SeederClass, options) {
|
|
33
|
+
if (!this.seeders.has(SeederClass)) throw new SeederNotRegisteredError(SeederClass.name);
|
|
34
|
+
const execute = async (container) => {
|
|
35
|
+
const seeder = container.resolve(SeederClass);
|
|
36
|
+
seeder[SEEDER_INTERNALS] = {
|
|
37
|
+
run: (cls) => this.run(cls, { container }),
|
|
38
|
+
container
|
|
39
|
+
};
|
|
40
|
+
await seeder.run();
|
|
41
|
+
};
|
|
42
|
+
if (options?.container) await execute(options.container);
|
|
43
|
+
else {
|
|
44
|
+
const mockContext = this.app.createMockRouterContext("en");
|
|
45
|
+
await this.app.container.runInRequestScope(mockContext, execute);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async runAll(options) {
|
|
49
|
+
for (const SeederClass of this.seeders) await this.run(SeederClass, options);
|
|
50
|
+
}
|
|
51
|
+
find(name) {
|
|
52
|
+
return this.nameIndex.get(name);
|
|
53
|
+
}
|
|
54
|
+
has(SeederClass) {
|
|
55
|
+
return this.seeders.has(SeederClass);
|
|
56
|
+
}
|
|
57
|
+
list() {
|
|
58
|
+
return [...this.seeders].map((cls) => ({ className: cls.name }));
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/seeder/commands/db-seed-list.command.ts
|
|
63
|
+
let DbSeedListCommand = class DbSeedListCommand extends Command {
|
|
64
|
+
static command = "db:seed:list";
|
|
65
|
+
static description = "List available database seeders";
|
|
66
|
+
constructor(seeders) {
|
|
67
|
+
super();
|
|
68
|
+
this.seeders = seeders;
|
|
69
|
+
}
|
|
70
|
+
handle() {
|
|
71
|
+
const list = this.seeders.list();
|
|
72
|
+
if (list.length === 0) {
|
|
73
|
+
this.info("No seeders found");
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
this.table(["Class"], list.map((s) => [s.className]));
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
DbSeedListCommand = __decorate([__decorateParam(0, inject(SEEDER_TOKENS.SeederRegistry)), __decorateMetadata("design:paramtypes", [Object])], DbSeedListCommand);
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/seeder/commands/db-seed.command.ts
|
|
82
|
+
let DbSeedCommand = class DbSeedCommand extends Command {
|
|
83
|
+
static command = "db:seed {name? : Seeder class name} {--a|all : Run all seeders} {--dry-run : Preview without executing}";
|
|
84
|
+
static description = "Run database seeders";
|
|
85
|
+
constructor(seeders) {
|
|
86
|
+
super();
|
|
87
|
+
this.seeders = seeders;
|
|
88
|
+
}
|
|
89
|
+
async handle() {
|
|
90
|
+
const name = this.string("name");
|
|
91
|
+
const all = this.boolean("all");
|
|
92
|
+
const dryRun = this.boolean("dry-run");
|
|
93
|
+
if (name && all) this.warn(`Ignoring "${name}" because --all takes precedence`);
|
|
94
|
+
if (!name && !all) {
|
|
95
|
+
this.fail("Specify a seeder class name or use --all");
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
if (dryRun) {
|
|
99
|
+
const list = this.seeders.list();
|
|
100
|
+
if (all) {
|
|
101
|
+
this.info("Dry run — would execute:");
|
|
102
|
+
for (const s of list) this.info(` ${s.className}`);
|
|
103
|
+
} else {
|
|
104
|
+
const SeederClass = this.seeders.find(name);
|
|
105
|
+
if (!SeederClass) {
|
|
106
|
+
this.fail(`Seeder "${name}" not found`);
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
this.info(`Dry run — would execute: ${SeederClass.name}`);
|
|
110
|
+
}
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
if (all) {
|
|
114
|
+
await this.seeders.runAll();
|
|
115
|
+
this.success("All seeders completed");
|
|
116
|
+
} else {
|
|
117
|
+
const SeederClass = this.seeders.find(name);
|
|
118
|
+
if (!SeederClass) {
|
|
119
|
+
this.fail(`Seeder "${name}" not found`);
|
|
120
|
+
return 1;
|
|
121
|
+
}
|
|
122
|
+
await this.seeders.run(SeederClass);
|
|
123
|
+
this.success(`Seeder "${name}" completed`);
|
|
124
|
+
}
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
DbSeedCommand = __decorate([__decorateParam(0, inject(SEEDER_TOKENS.SeederRegistry)), __decorateMetadata("design:paramtypes", [Object])], DbSeedCommand);
|
|
129
|
+
//#endregion
|
|
130
|
+
export { SeederNameCollisionError as a, SeederRegistry as i, DbSeedListCommand as n, SeederNotRegisteredError as o, SEEDER_TOKENS as r, DbSeedCommand as t };
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=seeder-Cupi5jl-.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seeder-Cupi5jl-.mjs","names":[],"sources":["../src/seeder/errors.ts","../src/seeder/seeder-registry.ts","../src/seeder/commands/db-seed-list.command.ts","../src/seeder/commands/db-seed.command.ts"],"sourcesContent":["import { ApplicationError, ERROR_CODES } from '../errors'\n\nexport class SeederNotRegisteredError extends ApplicationError {\n constructor(name: string) {\n super(\n 'errors.seederNotRegistered',\n ERROR_CODES.SYSTEM.SEEDER_NOT_REGISTERED,\n { name },\n )\n }\n}\n\nexport class SeederNameCollisionError extends ApplicationError {\n constructor(name: string) {\n super(\n 'errors.seederNameCollision',\n ERROR_CODES.SYSTEM.SEEDER_NAME_COLLISION,\n { name },\n )\n }\n}\n","import type { Application } from '../application'\nimport type { Container } from '../di/container'\nimport type { Constructor } from '../types'\nimport { SeederNameCollisionError, SeederNotRegisteredError } from './errors'\nimport { type Seeder, SEEDER_INTERNALS } from './seeder'\n\nexport const SEEDER_TOKENS = {\n SeederRegistry: Symbol.for('stratal:seeders:registry'),\n} as const\n\nexport class SeederRegistry {\n private seeders = new Set<Constructor<Seeder>>()\n private nameIndex = new Map<string, Constructor<Seeder>>()\n\n constructor(private app: Application) { }\n\n register(SeederClass: Constructor<Seeder>): void {\n const existing = this.nameIndex.get(SeederClass.name)\n if (existing && existing !== SeederClass) {\n throw new SeederNameCollisionError(SeederClass.name)\n }\n this.seeders.add(SeederClass)\n this.nameIndex.set(SeederClass.name, SeederClass)\n }\n\n async run(SeederClass: Constructor<Seeder>, options?: { container?: Container }): Promise<void> {\n if (!this.seeders.has(SeederClass)) {\n throw new SeederNotRegisteredError(SeederClass.name)\n }\n\n const execute = async (container: Container) => {\n const seeder = container.resolve<Seeder>(SeederClass)\n seeder[SEEDER_INTERNALS] = {\n run: (cls) => this.run(cls, { container }),\n container,\n }\n await seeder.run()\n }\n\n if (options?.container) {\n await execute(options.container)\n } else {\n const mockContext = this.app.createMockRouterContext('en')\n await this.app.container.runInRequestScope(mockContext, execute)\n }\n }\n\n async runAll(options?: { container?: Container }): Promise<void> {\n for (const SeederClass of this.seeders) {\n await this.run(SeederClass, options)\n }\n }\n\n find(name: string): Constructor<Seeder> | undefined {\n return this.nameIndex.get(name)\n }\n\n has(SeederClass: Constructor<Seeder>): boolean {\n return this.seeders.has(SeederClass)\n }\n\n list(): { className: string }[] {\n return [...this.seeders].map(cls => ({ className: cls.name }))\n }\n}\n","import { inject } from 'tsyringe'\nimport { Command } from '../../quarry/command'\nimport { type SeederRegistry, SEEDER_TOKENS } from '../seeder-registry'\n\nexport class DbSeedListCommand extends Command {\n static command = 'db:seed:list'\n static description = 'List available database seeders'\n\n constructor(@inject(SEEDER_TOKENS.SeederRegistry) private seeders: SeederRegistry) {\n super()\n }\n\n handle(): undefined | number {\n const list = this.seeders.list()\n if (list.length === 0) {\n this.info('No seeders found')\n return 0\n }\n this.table(['Class'], list.map(s => [s.className]))\n\n return undefined\n }\n}\n","import { inject } from 'tsyringe'\nimport { Command } from '../../quarry/command'\nimport { type SeederRegistry, SEEDER_TOKENS } from '../seeder-registry'\n\nexport class DbSeedCommand extends Command {\n static command = 'db:seed {name? : Seeder class name} {--a|all : Run all seeders} {--dry-run : Preview without executing}'\n static description = 'Run database seeders'\n\n constructor(@inject(SEEDER_TOKENS.SeederRegistry) private seeders: SeederRegistry) {\n super()\n }\n\n async handle(): Promise<number | undefined> {\n const name = this.string('name')\n const all = this.boolean('all')\n const dryRun = this.boolean('dry-run')\n\n if (name && all) {\n this.warn(`Ignoring \"${name}\" because --all takes precedence`)\n }\n\n if (!name && !all) {\n this.fail('Specify a seeder class name or use --all')\n return 1\n }\n\n if (dryRun) {\n const list = this.seeders.list()\n if (all) {\n this.info('Dry run — would execute:')\n for (const s of list) {\n this.info(` ${s.className}`)\n }\n } else {\n const SeederClass = this.seeders.find(name)\n if (!SeederClass) {\n this.fail(`Seeder \"${name}\" not found`)\n return 1\n }\n this.info(`Dry run — would execute: ${SeederClass.name}`)\n }\n return 0\n }\n\n if (all) {\n await this.seeders.runAll()\n this.success('All seeders completed')\n } else {\n const SeederClass = this.seeders.find(name)\n if (!SeederClass) {\n this.fail(`Seeder \"${name}\" not found`)\n return 1\n }\n await this.seeders.run(SeederClass)\n this.success(`Seeder \"${name}\" completed`)\n }\n\n return 0\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import "./errors-
|
|
2
|
-
import "./
|
|
1
|
+
import "./errors-CtCi1wn6.mjs";
|
|
2
|
+
import "./decorate-D5j-d9_z.mjs";
|
|
3
|
+
import "./logger-BR1-s1Um.mjs";
|
|
3
4
|
import { EmailSmtpConnectionFailedError, SmtpConfigurationMissingError, SmtpHostMissingError } from "./email/index.mjs";
|
|
4
|
-
import { t as BaseEmailProvider } from "./base-email.provider-
|
|
5
|
+
import { t as BaseEmailProvider } from "./base-email.provider-Cuw4OAB0.mjs";
|
|
5
6
|
import * as nodemailer from "nodemailer";
|
|
6
7
|
import { Readable } from "stream";
|
|
7
8
|
//#region src/email/providers/smtp.provider.ts
|
|
@@ -74,4 +75,4 @@ var SmtpProvider = class extends BaseEmailProvider {
|
|
|
74
75
|
//#endregion
|
|
75
76
|
export { SmtpProvider };
|
|
76
77
|
|
|
77
|
-
//# sourceMappingURL=smtp.provider-
|
|
78
|
+
//# sourceMappingURL=smtp.provider-B8XtOcHU.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"smtp.provider-
|
|
1
|
+
{"version":3,"file":"smtp.provider-B8XtOcHU.mjs","names":[],"sources":["../src/email/providers/smtp.provider.ts"],"sourcesContent":["import type { Transporter } from 'nodemailer'\nimport * as nodemailer from 'nodemailer'\nimport type SMTPTransport from 'nodemailer/lib/smtp-transport'\nimport { Readable } from 'stream'\nimport type { ResolvedEmailAttachment, ResolvedEmailMessage } from '../contracts'\nimport type { EmailModuleOptions } from '../email.module'\nimport { EmailSmtpConnectionFailedError, SmtpConfigurationMissingError, SmtpHostMissingError } from '../errors'\nimport { BaseEmailProvider } from './base-email.provider'\nimport type { EmailSendResult } from './email-provider.interface'\n\n/**\n * SMTP Email Provider\n *\n * Implementation of IEmailProvider using SMTP/nodemailer\n * Supports any SMTP server configured via SMTP_URL\n */\nexport class SmtpProvider extends BaseEmailProvider {\n private readonly transporter: Transporter<SMTPTransport.SentMessageInfo>\n private readonly defaultFrom: { name: string; email: string }\n\n constructor(\n private readonly options: EmailModuleOptions\n ) {\n super()\n\n // Validate SMTP configuration\n if (!this.options.smtp) {\n throw new SmtpConfigurationMissingError()\n }\n\n if (!this.options.smtp.host) {\n throw new SmtpHostMissingError()\n }\n\n // Create nodemailer transporter\n this.transporter = nodemailer.createTransport({\n host: this.options.smtp.host,\n port: this.options.smtp.port,\n secure: this.options.smtp.secure,\n auth: this.options.smtp.username && this.options.smtp.password\n ? {\n user: this.options.smtp.username,\n pass: this.options.smtp.password,\n }\n : undefined,\n })\n\n this.defaultFrom = this.options.from\n }\n\n async send(message: ResolvedEmailMessage): Promise<EmailSendResult> {\n try {\n const from = message.from\n ? `${message.from.name} <${message.from.email}>`\n : `${this.defaultFrom.name} <${this.defaultFrom.email}>`\n\n const info = await this.transporter.sendMail({\n from,\n to: Array.isArray(message.to) ? message.to.join(', ') : message.to,\n subject: message.subject,\n html: message.html,\n text: message.text,\n replyTo: message.replyTo,\n cc: message.cc?.join(', '),\n bcc: message.bcc?.join(', '),\n attachments: message.attachments?.map(attachment => ({\n filename: attachment.filename,\n content: this.toNodeStream(attachment.content),\n contentType: attachment.contentType,\n })),\n })\n\n return {\n messageId: info.messageId,\n accepted: true,\n metadata: {\n provider: 'smtp',\n response: info.response,\n },\n }\n } catch {\n throw new EmailSmtpConnectionFailedError(\n this.options.smtp?.host ?? '',\n this.options.smtp?.port ?? 587\n )\n }\n }\n\n /**\n * Convert attachment content to Node.js stream format\n *\n * Nodemailer expects Node.js Readable streams, not web ReadableStream.\n * Buffer is passed through as-is since nodemailer supports it directly.\n */\n private toNodeStream(content: ResolvedEmailAttachment['content']): Buffer | Readable {\n if (Buffer.isBuffer(content)) {\n return content\n }\n // Convert web ReadableStream to Node.js Readable\n return Readable.fromWeb(content as Parameters<typeof Readable.fromWeb>[0])\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAgBA,IAAa,eAAb,cAAkC,kBAAkB;CAClD;CACA;CAEA,YACE,SACA;AACA,SAAO;AAFU,OAAA,UAAA;AAKjB,MAAI,CAAC,KAAK,QAAQ,KAChB,OAAM,IAAI,+BAA+B;AAG3C,MAAI,CAAC,KAAK,QAAQ,KAAK,KACrB,OAAM,IAAI,sBAAsB;AAIlC,OAAK,cAAc,WAAW,gBAAgB;GAC5C,MAAM,KAAK,QAAQ,KAAK;GACxB,MAAM,KAAK,QAAQ,KAAK;GACxB,QAAQ,KAAK,QAAQ,KAAK;GAC1B,MAAM,KAAK,QAAQ,KAAK,YAAY,KAAK,QAAQ,KAAK,WAClD;IACA,MAAM,KAAK,QAAQ,KAAK;IACxB,MAAM,KAAK,QAAQ,KAAK;IACzB,GACC,KAAA;GACL,CAAC;AAEF,OAAK,cAAc,KAAK,QAAQ;;CAGlC,MAAM,KAAK,SAAyD;AAClE,MAAI;GACF,MAAM,OAAO,QAAQ,OACjB,GAAG,QAAQ,KAAK,KAAK,IAAI,QAAQ,KAAK,MAAM,KAC5C,GAAG,KAAK,YAAY,KAAK,IAAI,KAAK,YAAY,MAAM;GAExD,MAAM,OAAO,MAAM,KAAK,YAAY,SAAS;IAC3C;IACA,IAAI,MAAM,QAAQ,QAAQ,GAAG,GAAG,QAAQ,GAAG,KAAK,KAAK,GAAG,QAAQ;IAChE,SAAS,QAAQ;IACjB,MAAM,QAAQ;IACd,MAAM,QAAQ;IACd,SAAS,QAAQ;IACjB,IAAI,QAAQ,IAAI,KAAK,KAAK;IAC1B,KAAK,QAAQ,KAAK,KAAK,KAAK;IAC5B,aAAa,QAAQ,aAAa,KAAI,gBAAe;KACnD,UAAU,WAAW;KACrB,SAAS,KAAK,aAAa,WAAW,QAAQ;KAC9C,aAAa,WAAW;KACzB,EAAE;IACJ,CAAC;AAEF,UAAO;IACL,WAAW,KAAK;IAChB,UAAU;IACV,UAAU;KACR,UAAU;KACV,UAAU,KAAK;KAChB;IACF;UACK;AACN,SAAM,IAAI,+BACR,KAAK,QAAQ,MAAM,QAAQ,IAC3B,KAAK,QAAQ,MAAM,QAAQ,IAC5B;;;;;;;;;CAUL,aAAqB,SAAgE;AACnF,MAAI,OAAO,SAAS,QAAQ,CAC1B,QAAO;AAGT,SAAO,SAAS,QAAQ,QAAkD"}
|