@zintrust/core 2.5.9 → 2.7.2
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/package.json +16 -12
- package/src/cli/commands/D1MigrateCommand.d.ts.map +1 -1
- package/src/cli/commands/D1MigrateCommand.js +4 -2
- package/src/http/middleware/BodyParsingMiddleware.d.ts +28 -0
- package/src/http/middleware/BodyParsingMiddleware.d.ts.map +1 -1
- package/src/http/middleware/BodyParsingMiddleware.js +3 -3
- package/src/http/middleware/FileUploadMiddleware.d.ts.map +1 -1
- package/src/http/middleware/FileUploadMiddleware.js +138 -31
- package/src/http/parsers/MultipartParser.d.ts +3 -1
- package/src/http/parsers/MultipartParser.d.ts.map +1 -1
- package/src/http/parsers/MultipartParser.js +4 -2
- package/src/index.js +3 -3
- package/src/orm/QueryBuilder.d.ts.map +1 -1
- package/src/orm/QueryBuilder.js +22 -3
- package/src/proxy/d1/ZintrustD1Proxy.d.ts +7 -0
- package/src/proxy/d1/ZintrustD1Proxy.d.ts.map +1 -1
- package/src/proxy/d1/ZintrustD1Proxy.js +1 -1
- package/src/runtime/adapters/CloudflareAdapter.d.ts.map +1 -1
- package/src/runtime/adapters/CloudflareAdapter.js +24 -2
- package/src/runtime/adapters/DenoAdapter.js +1 -1
- package/src/tools/broadcast/Broadcast.d.ts.map +1 -1
- package/src/tools/broadcast/Broadcast.js +53 -3
- package/src/tools/storage/drivers/R2.d.ts +1 -0
- package/src/tools/storage/drivers/R2.d.ts.map +1 -1
- package/src/tools/storage/drivers/R2.js +10 -1
- package/src/tools/storage/drivers/S3.d.ts +1 -0
- package/src/tools/storage/drivers/S3.d.ts.map +1 -1
- package/src/tools/storage/drivers/S3.js +5 -0
- package/src/tools/storage/index.d.ts.map +1 -1
- package/src/tools/storage/index.js +1 -0
- package/src/worker-runtime/drain.d.ts +19 -0
- package/src/worker-runtime/drain.d.ts.map +1 -1
- package/src/worker-runtime/drain.js +21 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.2",
|
|
4
4
|
"description": "Production-grade TypeScript backend framework for JavaScript",
|
|
5
5
|
"homepage": "https://zintrust.com",
|
|
6
6
|
"repository": {
|
|
@@ -213,16 +213,16 @@
|
|
|
213
213
|
"./package.json": "./package.json"
|
|
214
214
|
},
|
|
215
215
|
"dependencies": {
|
|
216
|
-
"@cloudflare/containers": "^0.3.
|
|
216
|
+
"@cloudflare/containers": "^0.3.7",
|
|
217
217
|
"bcryptjs": "^3.0.3",
|
|
218
|
-
"bullmq": "^5.
|
|
218
|
+
"bullmq": "^5.79.0",
|
|
219
219
|
"chalk": "^5.6.2",
|
|
220
220
|
"commander": "^15.0.0",
|
|
221
221
|
"inquirer": "^14.0.1",
|
|
222
|
-
"ioredis": "^5.11.
|
|
222
|
+
"ioredis": "^5.11.1",
|
|
223
223
|
"jsonwebtoken": "^9.0.3",
|
|
224
|
-
"mysql2": "^3.22.
|
|
225
|
-
"pg": "^8.
|
|
224
|
+
"mysql2": "^3.22.5",
|
|
225
|
+
"pg": "^8.22.0",
|
|
226
226
|
"redis": "^6.0.0",
|
|
227
227
|
"tsx": "^4.22.4"
|
|
228
228
|
},
|
|
@@ -234,18 +234,21 @@
|
|
|
234
234
|
"node-forge": "^1.4.0",
|
|
235
235
|
"fast-uri": "3.1.2",
|
|
236
236
|
"fast-xml-parser": "5.8.0",
|
|
237
|
-
"
|
|
237
|
+
"@babel/core": "^7.29.7",
|
|
238
|
+
"protobufjs": "7.6.4",
|
|
238
239
|
"picomatch": "^4.0.4",
|
|
239
240
|
"cross-spawn": "^7.0.5",
|
|
240
241
|
"glob": "^11.1.0",
|
|
241
242
|
"minimatch": "^10.2.2",
|
|
242
243
|
"rollup": "^4.59.0",
|
|
243
|
-
"vite": "^8.0.
|
|
244
|
+
"vite": "^8.0.16",
|
|
244
245
|
"flatted": "^3.4.2",
|
|
245
246
|
"uuid": "14.0.0",
|
|
246
|
-
"ioredis": "^5.11.
|
|
247
|
+
"ioredis": "^5.11.1",
|
|
247
248
|
"axios": "^1.16.1",
|
|
248
|
-
"
|
|
249
|
+
"form-data": "^4.0.6",
|
|
250
|
+
"js-yaml": "^4.2.0",
|
|
251
|
+
"tmp": "^0.2.7",
|
|
249
252
|
"sonarqube-scanner": {
|
|
250
253
|
"axios": "^1.16.1"
|
|
251
254
|
},
|
|
@@ -256,7 +259,7 @@
|
|
|
256
259
|
"undici": "^6.24.0"
|
|
257
260
|
},
|
|
258
261
|
"miniflare": {
|
|
259
|
-
"undici": "^7.
|
|
262
|
+
"undici": "^7.28.0"
|
|
260
263
|
},
|
|
261
264
|
"http-proxy-agent": "7.0.2",
|
|
262
265
|
"retry-request": "8.0.2",
|
|
@@ -265,9 +268,10 @@
|
|
|
265
268
|
"braces": "^3.0.3",
|
|
266
269
|
"lodash": "^4.17.21",
|
|
267
270
|
"semver": "^7.6.3",
|
|
268
|
-
"ws": "^8.
|
|
271
|
+
"ws": "^8.21.0",
|
|
269
272
|
"micromatch": "^4.0.8",
|
|
270
273
|
"brace-expansion": "^5.0.6",
|
|
274
|
+
"esbuild": "0.28.1",
|
|
271
275
|
"qs": "^6.15.2"
|
|
272
276
|
},
|
|
273
277
|
"bin": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"D1MigrateCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/D1MigrateCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"D1MigrateCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/D1MigrateCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AA4IrE;;;GAGG;AAEH;;GAEG;AACH,eAAO,MAAM,gBAAgB;IAC3B;;OAEG;cACO,YAAY;EAgCtB,CAAC"}
|
|
@@ -37,8 +37,10 @@ const buildExecutionContext = (options) => {
|
|
|
37
37
|
const dbName = getDbName(projectRoot, options);
|
|
38
38
|
const env = typeof options['env'] === 'string' ? options['env'].trim() : undefined;
|
|
39
39
|
const config = (typeof options['config'] === 'string' ? options['config'].trim() : undefined) ??
|
|
40
|
-
(typeof options
|
|
41
|
-
|
|
40
|
+
(typeof options['wranglerConfig'] === 'string'
|
|
41
|
+
? options['wranglerConfig'].trim()
|
|
42
|
+
: undefined) ??
|
|
43
|
+
(typeof options['wc'] === 'string' ? options['wc'].trim() : undefined);
|
|
42
44
|
const migrationsRelDir = isWorkerCommand
|
|
43
45
|
? path.join('database', 'migrations', 'd1')
|
|
44
46
|
: WranglerConfig.getD1MigrationsDir(projectRoot, dbName);
|
|
@@ -2,7 +2,35 @@
|
|
|
2
2
|
* Content Type Detection & Body Parsing Middleware
|
|
3
3
|
* Detects content-type and parses non-JSON request bodies
|
|
4
4
|
*/
|
|
5
|
+
import type { IRequest } from '../Request';
|
|
5
6
|
import type { Middleware } from '../../middleware/MiddlewareStack';
|
|
7
|
+
type ReadBodyResult = {
|
|
8
|
+
ok: true;
|
|
9
|
+
bytes: Buffer;
|
|
10
|
+
text?: string;
|
|
11
|
+
} | {
|
|
12
|
+
ok: false;
|
|
13
|
+
statusCode: 400 | 413;
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Attempt to recover a JSON string that was stored as raw text or bytes
|
|
18
|
+
* due to a missing or incorrect Content-Type header.
|
|
19
|
+
*
|
|
20
|
+
* Only plain objects `{...}` are considered for recovery — arrays, strings,
|
|
21
|
+
* and primitives are left unchanged. This minimizes false positives for
|
|
22
|
+
* legitimate text or binary payloads that happen to start with `{`.
|
|
23
|
+
*/
|
|
24
|
+
export declare const tryRecoverTextJsonBody: (body: string) => string | Record<string, unknown>;
|
|
25
|
+
export declare const setRequestBody: (req: IRequest, rawResult: ReadBodyResult & {
|
|
26
|
+
ok: true;
|
|
27
|
+
}, contentType: string) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Attempt to parse a string as a JSON object.
|
|
30
|
+
* Returns the parsed object on success, or null if the string is not
|
|
31
|
+
* a valid JSON object literal (plain object `{...}`, not array or primitive).
|
|
32
|
+
*/
|
|
33
|
+
export declare const tryParseJsonObject: (text: string) => Record<string, unknown> | null;
|
|
6
34
|
/**
|
|
7
35
|
* Body parsing middleware
|
|
8
36
|
* Automatically detects content-type and parses non-JSON bodies
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BodyParsingMiddleware.d.ts","sourceRoot":"","sources":["../../../../src/http/middleware/BodyParsingMiddleware.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"BodyParsingMiddleware.d.ts","sourceRoot":"","sources":["../../../../src/http/middleware/BodyParsingMiddleware.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,KAAK,cAAc,GACf;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AA+J1D;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GAAI,MAAM,MAAM,KAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAapF,CAAC;AAEF,eAAO,MAAM,cAAc,GACzB,KAAK,QAAQ,EACb,WAAW,cAAc,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,EACxC,aAAa,MAAM,KAClB,IA4BF,CAAC;AA+CF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAY3E,CAAC;AAwGF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,UA4CnC,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
|
|
@@ -164,7 +164,7 @@ const parseJsonBody = (text, contentType, res) => {
|
|
|
164
164
|
* and primitives are left unchanged. This minimizes false positives for
|
|
165
165
|
* legitimate text or binary payloads that happen to start with `{`.
|
|
166
166
|
*/
|
|
167
|
-
const tryRecoverTextJsonBody = (body) => {
|
|
167
|
+
export const tryRecoverTextJsonBody = (body) => {
|
|
168
168
|
const trimmed = body.trim();
|
|
169
169
|
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
|
|
170
170
|
try {
|
|
@@ -179,7 +179,7 @@ const tryRecoverTextJsonBody = (body) => {
|
|
|
179
179
|
}
|
|
180
180
|
return body;
|
|
181
181
|
};
|
|
182
|
-
const setRequestBody = (req, rawResult, contentType) => {
|
|
182
|
+
export const setRequestBody = (req, rawResult, contentType) => {
|
|
183
183
|
const isUrlEncoded = contentType.includes('application/x-www-form-urlencoded');
|
|
184
184
|
const isText = contentType.startsWith('text/') || contentType.includes('application/xml');
|
|
185
185
|
const text = getTextFromRaw(rawResult);
|
|
@@ -243,7 +243,7 @@ const reuseExistingRawBody = (req, res, contentType, existingBytes, existingText
|
|
|
243
243
|
* Returns the parsed object on success, or null if the string is not
|
|
244
244
|
* a valid JSON object literal (plain object `{...}`, not array or primitive).
|
|
245
245
|
*/
|
|
246
|
-
const tryParseJsonObject = (text) => {
|
|
246
|
+
export const tryParseJsonObject = (text) => {
|
|
247
247
|
const trimmed = text.trim();
|
|
248
248
|
if (!trimmed.startsWith('{') || !trimmed.endsWith('}'))
|
|
249
249
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileUploadMiddleware.d.ts","sourceRoot":"","sources":["../../../../src/http/middleware/FileUploadMiddleware.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"FileUploadMiddleware.d.ts","sourceRoot":"","sources":["../../../../src/http/middleware/FileUploadMiddleware.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAqJ9D;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,UAsDlC,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
|
|
@@ -6,69 +6,176 @@ import { Env } from '../../config/env.js';
|
|
|
6
6
|
import { Logger } from '../../config/logger.js';
|
|
7
7
|
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
|
|
8
8
|
import { MultipartParser } from '../parsers/MultipartParser.js';
|
|
9
|
-
import { MultipartParserRegistry } from '../parsers/MultipartParserRegistry.js';
|
|
9
|
+
import { MultipartParserRegistry, } from '../parsers/MultipartParserRegistry.js';
|
|
10
10
|
/**
|
|
11
|
-
* Extract content-type header
|
|
11
|
+
* Extract the original content-type header value.
|
|
12
|
+
*
|
|
13
|
+
* The value is NOT lowercased: multipart boundaries are case-sensitive, so a
|
|
14
|
+
* browser boundary containing uppercase characters must be preserved verbatim.
|
|
15
|
+
* Only the multipart check (MultipartParser.isMultipart) is case-insensitive.
|
|
12
16
|
*/
|
|
13
17
|
const getContentType = (req) => {
|
|
14
18
|
const contentType = req.getHeader('content-type');
|
|
15
19
|
if (typeof contentType === 'string')
|
|
16
|
-
return contentType.
|
|
20
|
+
return contentType.trim();
|
|
21
|
+
if (Array.isArray(contentType) && typeof contentType[0] === 'string') {
|
|
22
|
+
return contentType[0].trim();
|
|
23
|
+
}
|
|
17
24
|
return '';
|
|
18
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* Convert a buffered request body (Buffer or string) to a Buffer, if present.
|
|
28
|
+
* Worker adapters often expose a buffered body rather than a Node stream.
|
|
29
|
+
*/
|
|
30
|
+
const getBufferedBody = (req) => {
|
|
31
|
+
const raw = req.getRaw();
|
|
32
|
+
const body = raw.body;
|
|
33
|
+
if (Buffer.isBuffer(body))
|
|
34
|
+
return body;
|
|
35
|
+
if (typeof body === 'string')
|
|
36
|
+
return Buffer.from(body);
|
|
37
|
+
return undefined;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A raw request is pipeable when it exposes a Node-style readable stream, which
|
|
41
|
+
* the registered streaming parser (e.g. busboy) consumes via `.pipe(...)`.
|
|
42
|
+
*/
|
|
43
|
+
const isPipeable = (req) => {
|
|
44
|
+
const raw = req.getRaw();
|
|
45
|
+
return typeof raw.pipe === 'function';
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Convert a bracket-notation field name to dotted notation.
|
|
49
|
+
* e.g. `doc[reg_cer]` -> `doc.reg_cer`, `doc[reg_cer][file]` -> `doc.reg_cer.file`.
|
|
50
|
+
* Empty brackets (`files[]`) collapse to the base name (`files`).
|
|
51
|
+
*/
|
|
52
|
+
const toDottedName = (name) => {
|
|
53
|
+
let result = '';
|
|
54
|
+
let cursor = 0;
|
|
55
|
+
while (cursor < name.length) {
|
|
56
|
+
const openBracket = name.indexOf('[', cursor);
|
|
57
|
+
if (openBracket === -1) {
|
|
58
|
+
result += name.slice(cursor);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
result += name.slice(cursor, openBracket);
|
|
62
|
+
const closeBracket = name.indexOf(']', openBracket + 1);
|
|
63
|
+
if (closeBracket === -1) {
|
|
64
|
+
result += name.slice(openBracket);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
const inner = name.slice(openBracket + 1, closeBracket);
|
|
68
|
+
if (inner !== '') {
|
|
69
|
+
result += `.${inner}`;
|
|
70
|
+
}
|
|
71
|
+
cursor = closeBracket + 1;
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Add dotted-notation aliases for any bracket-notation keys, without removing
|
|
77
|
+
* the original field names. Existing keys are never overwritten.
|
|
78
|
+
*/
|
|
79
|
+
const addDottedAliases = (record) => {
|
|
80
|
+
const out = { ...record };
|
|
81
|
+
for (const [key, value] of Object.entries(record)) {
|
|
82
|
+
if (!key.includes('['))
|
|
83
|
+
continue;
|
|
84
|
+
const dotted = toDottedName(key);
|
|
85
|
+
if (dotted !== '' && dotted !== key && !(dotted in out)) {
|
|
86
|
+
out[dotted] = value;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Parse a buffered multipart body using the built-in parser and the original
|
|
93
|
+
* (case-sensitive) boundary from the content-type header.
|
|
94
|
+
*/
|
|
95
|
+
const parseBufferedMultipart = (body, contentType) => {
|
|
96
|
+
const boundary = MultipartParser.getBoundary(contentType);
|
|
97
|
+
if (boundary === undefined || boundary === '') {
|
|
98
|
+
throw ErrorFactory.createValidationError('Multipart upload is missing a boundary.', {
|
|
99
|
+
contentType,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return MultipartParser.parse(body, boundary);
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Resolve parsed multipart data from the request.
|
|
106
|
+
*
|
|
107
|
+
* Prefers a buffered binary body (Worker adapters) parsed with the built-in
|
|
108
|
+
* parser. Falls back to a registered streaming parser only when the raw request
|
|
109
|
+
* is pipeable (Node-style request streams).
|
|
110
|
+
*/
|
|
111
|
+
const resolveParsedMultipart = async (req, contentType, limits) => {
|
|
112
|
+
const bufferedBody = getBufferedBody(req);
|
|
113
|
+
if (bufferedBody !== undefined) {
|
|
114
|
+
return parseBufferedMultipart(bufferedBody, contentType);
|
|
115
|
+
}
|
|
116
|
+
if (isPipeable(req)) {
|
|
117
|
+
const provider = MultipartParserRegistry.get();
|
|
118
|
+
if (provider === null) {
|
|
119
|
+
throw ErrorFactory.createConfigError('Multipart upload parser is not configured.', {
|
|
120
|
+
contentType,
|
|
121
|
+
hint: 'Install @zintrust/storage to enable multipart/form-data uploads.',
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return provider({ req: req.getRaw(), contentType, limits });
|
|
125
|
+
}
|
|
126
|
+
throw ErrorFactory.createValidationError('Multipart upload has no readable body.', {
|
|
127
|
+
contentType,
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
const isConfigError = (error) => typeof error === 'object' &&
|
|
131
|
+
error !== null &&
|
|
132
|
+
error.code === 'CONFIG_ERROR';
|
|
19
133
|
/**
|
|
20
134
|
* File upload middleware
|
|
21
135
|
* Automatically parses multipart/form-data and makes files available
|
|
22
136
|
*/
|
|
23
137
|
export const fileUploadMiddleware = async (req, _res, next) => {
|
|
24
138
|
const contentType = getContentType(req);
|
|
25
|
-
// Only process multipart/form-data requests
|
|
139
|
+
// Only process multipart/form-data requests (case-insensitive check).
|
|
26
140
|
if (!MultipartParser.isMultipart(contentType)) {
|
|
27
141
|
await next();
|
|
28
142
|
return;
|
|
29
143
|
}
|
|
30
|
-
// Phase 4 default behavior: multipart requires external streaming parser.
|
|
31
|
-
const provider = MultipartParserRegistry.get();
|
|
32
|
-
if (provider === null) {
|
|
33
|
-
throw ErrorFactory.createConfigError('Multipart upload parser is not configured.', {
|
|
34
|
-
contentType,
|
|
35
|
-
hint: 'Install @zintrust/storage to enable multipart/form-data uploads.',
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
144
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
maxFieldSizeBytes,
|
|
51
|
-
},
|
|
52
|
-
});
|
|
145
|
+
const limits = {
|
|
146
|
+
maxFileSizeBytes: Env.getInt('MAX_FILE_SIZE', 50 * 1024 * 1024),
|
|
147
|
+
maxFiles: Env.getInt('MAX_FILES', 20),
|
|
148
|
+
maxFields: Env.getInt('MAX_FIELDS', 200),
|
|
149
|
+
maxFieldSizeBytes: Env.getInt('MAX_FIELD_SIZE', 128 * 1024),
|
|
150
|
+
};
|
|
151
|
+
const parsed = await resolveParsedMultipart(req, contentType, limits);
|
|
152
|
+
// Add dotted aliases for bracket-notation names without dropping originals,
|
|
153
|
+
// so `doc[reg_cer]` is also addressable as `doc.reg_cer`.
|
|
154
|
+
const fields = addDottedAliases(parsed.fields);
|
|
155
|
+
const files = addDottedAliases(parsed.files);
|
|
53
156
|
const currentBody = req.getBody?.();
|
|
54
157
|
const updatedBody = typeof currentBody === 'object' && currentBody !== null ? currentBody : {};
|
|
55
158
|
// Merge fields directly into the request body for ergonomics.
|
|
56
159
|
// Files remain under __files for FileUpload helper compatibility.
|
|
57
160
|
req.setBody({
|
|
58
161
|
...updatedBody,
|
|
59
|
-
...
|
|
60
|
-
__files:
|
|
162
|
+
...fields,
|
|
163
|
+
__files: files,
|
|
61
164
|
});
|
|
62
165
|
if (Env.getBool('ZIN_DEBUG_FILE_UPLOAD', false)) {
|
|
63
166
|
Logger.debug('[File Upload] Successfully parsed multipart data', {
|
|
64
|
-
fieldsCount: Object.keys(
|
|
65
|
-
filesCount: Object.keys(
|
|
167
|
+
fieldsCount: Object.keys(fields).length,
|
|
168
|
+
filesCount: Object.keys(files).length,
|
|
66
169
|
});
|
|
67
170
|
}
|
|
68
171
|
}
|
|
69
172
|
catch (error) {
|
|
173
|
+
// A missing streaming parser is a hard configuration error (e.g. multipart
|
|
174
|
+
// arrives as a Node stream but @zintrust/storage is not installed): surface it.
|
|
175
|
+
if (isConfigError(error))
|
|
176
|
+
throw error;
|
|
177
|
+
// Other parse failures are non-fatal: log and continue with the unparsed body.
|
|
70
178
|
Logger.error('[File Upload] Error parsing multipart data', error);
|
|
71
|
-
// Continue with unparsed body
|
|
72
179
|
}
|
|
73
180
|
await next();
|
|
74
181
|
};
|
|
@@ -17,7 +17,9 @@ export declare const parseMultipartFormData: (body: Buffer | string, boundary: s
|
|
|
17
17
|
*/
|
|
18
18
|
export declare const MultipartParser: Readonly<{
|
|
19
19
|
/**
|
|
20
|
-
* Check if content-type is multipart/form-data
|
|
20
|
+
* Check if content-type is multipart/form-data.
|
|
21
|
+
* The check is case-insensitive so callers can preserve the original
|
|
22
|
+
* content-type value (and its case-sensitive boundary) for parsing.
|
|
21
23
|
*/
|
|
22
24
|
isMultipart(contentType: string): boolean;
|
|
23
25
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultipartParser.d.ts","sourceRoot":"","sources":["../../../../src/http/parsers/MultipartParser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,UAAU,mBAAmB;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;CACvC;AAwJD;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GACjC,MAAM,MAAM,GAAG,MAAM,EACrB,UAAU,MAAM,KACf,mBAaF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe;IAC1B
|
|
1
|
+
{"version":3,"file":"MultipartParser.d.ts","sourceRoot":"","sources":["../../../../src/http/parsers/MultipartParser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,UAAU,mBAAmB;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;CACvC;AAwJD;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GACjC,MAAM,MAAM,GAAG,MAAM,EACrB,UAAU,MAAM,KACf,mBAaF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe;IAC1B;;;;OAIG;6BACsB,MAAM,GAAG,OAAO;IAIzC;;OAEG;6BACsB,MAAM,GAAG,MAAM,GAAG,SAAS;IAIpD;;OAEG;gBACS,MAAM,GAAG,MAAM,YAAY,MAAM,GAAG,mBAAmB;EAGnE,CAAC;AAEH,eAAe,eAAe,CAAC"}
|
|
@@ -135,10 +135,12 @@ export const parseMultipartFormData = (body, boundary) => {
|
|
|
135
135
|
*/
|
|
136
136
|
export const MultipartParser = Object.freeze({
|
|
137
137
|
/**
|
|
138
|
-
* Check if content-type is multipart/form-data
|
|
138
|
+
* Check if content-type is multipart/form-data.
|
|
139
|
+
* The check is case-insensitive so callers can preserve the original
|
|
140
|
+
* content-type value (and its case-sensitive boundary) for parsing.
|
|
139
141
|
*/
|
|
140
142
|
isMultipart(contentType) {
|
|
141
|
-
return contentType.includes('multipart/form-data');
|
|
143
|
+
return contentType.toLowerCase().includes('multipart/form-data');
|
|
142
144
|
},
|
|
143
145
|
/**
|
|
144
146
|
* Get boundary from content-type header
|
package/src/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @zintrust/core v2.
|
|
2
|
+
* @zintrust/core v2.7.2
|
|
3
3
|
*
|
|
4
4
|
* ZinTrust Framework - Production-Grade TypeScript Backend
|
|
5
5
|
* Built for performance, type safety, and exceptional developer experience
|
|
6
6
|
*
|
|
7
7
|
* Build Information:
|
|
8
|
-
* Built: 2026-06-
|
|
8
|
+
* Built: 2026-06-19T10:18:51.959Z
|
|
9
9
|
* Node: >=20.0.0
|
|
10
10
|
* License: MIT
|
|
11
11
|
*
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* Available at runtime for debugging and health checks
|
|
22
22
|
*/
|
|
23
23
|
export const ZINTRUST_VERSION = '0.1.41';
|
|
24
|
-
export const ZINTRUST_BUILD_DATE = '2026-06-
|
|
24
|
+
export const ZINTRUST_BUILD_DATE = '2026-06-19T10:18:51.925Z'; // Replaced during build
|
|
25
25
|
export { Application } from './boot/Application.js';
|
|
26
26
|
export { AwsSigV4 } from './common/index.js';
|
|
27
27
|
export { SignedRequest } from './security/SignedRequest.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QueryBuilder.d.ts","sourceRoot":"","sources":["../../../src/orm/QueryBuilder.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"QueryBuilder.d.ts","sourceRoot":"","sources":["../../../src/orm/QueryBuilder.ts"],"names":[],"mappings":"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"}
|
package/src/orm/QueryBuilder.js
CHANGED
|
@@ -382,14 +382,30 @@ const buildLimitOffsetClause = (limit, offset) => {
|
|
|
382
382
|
}
|
|
383
383
|
return sql;
|
|
384
384
|
};
|
|
385
|
+
// Matches `left OP right` where both sides are identifier paths (e.g.
|
|
386
|
+
// `users.id = profiles.user_id`). Operator alternation lists the two-char
|
|
387
|
+
// operators before their single-char prefixes so `<=`/`>=`/`<>` win the match.
|
|
388
|
+
const JOIN_CONDITION = /^([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\s*(=|!=|<>|<=|>=|<|>)\s*([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)$/;
|
|
389
|
+
const buildJoinClause = (joins, dialect) => joins
|
|
390
|
+
.map(({ table, on, type }) => {
|
|
391
|
+
assertSafeIdentifierPath(table, 'join table');
|
|
392
|
+
const match = JOIN_CONDITION.exec(on.trim());
|
|
393
|
+
if (!match) {
|
|
394
|
+
throw ErrorFactory.createDatabaseError('Unsafe SQL join condition');
|
|
395
|
+
}
|
|
396
|
+
const [, left, op, right] = match;
|
|
397
|
+
return ` ${type} JOIN ${escapeIdentifier(table, dialect)} ON ${escapeIdentifier(left, dialect)} ${op} ${escapeIdentifier(right, dialect)}`;
|
|
398
|
+
})
|
|
399
|
+
.join('');
|
|
385
400
|
const buildSelectQuery = (state) => {
|
|
386
401
|
if (state.tableName.length > 0) {
|
|
387
402
|
assertSafeIdentifierPath(state.tableName, 'table name');
|
|
388
403
|
}
|
|
389
404
|
const columns = buildSelectClause(state.selectColumns, state.dialect);
|
|
390
405
|
const fromClause = state.tableName.length > 0 ? ` FROM ${escapeIdentifier(state.tableName, state.dialect)}` : '';
|
|
406
|
+
const joinClause = buildJoinClause(state.joins, state.dialect);
|
|
391
407
|
const where = compileWhere(getEffectiveWhereConditions(state), state.dialect);
|
|
392
|
-
const sql = `SELECT ${columns}${fromClause}${where.sql}${buildOrderByClause(state.orderByClauses, state.randomOrder, state.dialect)}${buildLimitOffsetClause(state.limitValue, state.offsetValue)}`;
|
|
408
|
+
const sql = `SELECT ${columns}${fromClause}${joinClause}${where.sql}${buildOrderByClause(state.orderByClauses, state.randomOrder, state.dialect)}${buildLimitOffsetClause(state.limitValue, state.offsetValue)}`;
|
|
393
409
|
return { sql, parameters: where.parameters };
|
|
394
410
|
};
|
|
395
411
|
const buildCountQuery = (state) => {
|
|
@@ -706,10 +722,13 @@ function attachSoftDeleteMethods(builder, state) {
|
|
|
706
722
|
}
|
|
707
723
|
function attachJoinOrderPagingMethods(builder, state) {
|
|
708
724
|
builder.join = (tableJoin, on) => {
|
|
709
|
-
state.joins.push({ table: tableJoin, on });
|
|
725
|
+
state.joins.push({ table: tableJoin, on, type: 'INNER' });
|
|
726
|
+
return builder;
|
|
727
|
+
};
|
|
728
|
+
builder.leftJoin = (tableJoin, on) => {
|
|
729
|
+
state.joins.push({ table: tableJoin, on, type: 'LEFT' });
|
|
710
730
|
return builder;
|
|
711
731
|
};
|
|
712
|
-
builder.leftJoin = (tableJoin, on) => builder.join(tableJoin, on);
|
|
713
732
|
builder.orderBy = (column, direction = 'ASC') => {
|
|
714
733
|
applyOrderByClause(state, column, direction);
|
|
715
734
|
return builder;
|
|
@@ -39,6 +39,13 @@ type D1Env = {
|
|
|
39
39
|
ZT_MAX_PARAMS?: string;
|
|
40
40
|
ZT_D1_STATEMENTS_JSON?: string;
|
|
41
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* Normalize bind parameters before passing to D1's bind().
|
|
44
|
+
*
|
|
45
|
+
* D1 rejects JS objects and needs integer-valued numbers serialized for TEXT
|
|
46
|
+
* column compatibility, Dates as ISO strings, while blobs must be untouched.
|
|
47
|
+
*/
|
|
48
|
+
export declare const normalizeParams: (params: unknown[]) => unknown[];
|
|
42
49
|
export declare const ZintrustD1Proxy: Readonly<{
|
|
43
50
|
_ZINTRUST_CLOUDFLARE_D1_PROXY_VERSION: "0.1.15";
|
|
44
51
|
_ZINTRUST_CLOUDFLARE_D1_PROXY_BUILD_DATE: "__BUILD_DATE__";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ZintrustD1Proxy.d.ts","sourceRoot":"","sources":["../../../../src/proxy/d1/ZintrustD1Proxy.ts"],"names":[],"mappings":"AAUA,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC;AAEjD,KAAK,qBAAqB,GAAG;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,GAAG,EAAE;QACH,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;QACtC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;QACrE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;QAChE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,GAAG,MAAM,GAAG,IAAI,CAAC,CAAC;KAChG,CAAC;IACF,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF,CAAC;AAEF,KAAK,WAAW,CAAC,CAAC,IAAI;IACpB,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;CACf,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,IAAI,EAAE,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,mBAAmB,CAAC;IACpD,GAAG,EAAE,CAAC,CAAC,GAAG,OAAO,OAAO,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,KAAK,EAAE,CAAC,CAAC,GAAG,OAAO,OAAO,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC5C,GAAG,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;CACjC,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,mBAAmB,CAAC;CAC/C,CAAC;AAEF,KAAK,KAAK,GAAG;IACX,EAAE,CAAC,EAAE,UAAU,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;
|
|
1
|
+
{"version":3,"file":"ZintrustD1Proxy.d.ts","sourceRoot":"","sources":["../../../../src/proxy/d1/ZintrustD1Proxy.ts"],"names":[],"mappings":"AAUA,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC;AAEjD,KAAK,qBAAqB,GAAG;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,GAAG,EAAE;QACH,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;QACtC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;QACrE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;QAChE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,GAAG,MAAM,GAAG,IAAI,CAAC,CAAC;KAChG,CAAC;IACF,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF,CAAC;AAEF,KAAK,WAAW,CAAC,CAAC,IAAI;IACpB,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;CACf,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,IAAI,EAAE,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,mBAAmB,CAAC;IACpD,GAAG,EAAE,CAAC,CAAC,GAAG,OAAO,OAAO,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,KAAK,EAAE,CAAC,CAAC,GAAG,OAAO,OAAO,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC5C,GAAG,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;CACjC,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,mBAAmB,CAAC;CAC/C,CAAC;AAEF,KAAK,KAAK,GAAG;IACX,EAAE,CAAC,EAAE,UAAU,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AA2LF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,OAAO,EAAE,KAAG,OAAO,EAe1D,CAAC;AAsJF,eAAO,MAAM,eAAe;;;mBAGL,OAAO,OAAO,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC;EAqB5D,CAAC;AAEH,eAAe,eAAe,CAAC"}
|
|
@@ -157,7 +157,7 @@ const resolveSqlRequest = async (request, env) => {
|
|
|
157
157
|
* D1 rejects JS objects and needs integer-valued numbers serialized for TEXT
|
|
158
158
|
* column compatibility, Dates as ISO strings, while blobs must be untouched.
|
|
159
159
|
*/
|
|
160
|
-
const normalizeParams = (params) => {
|
|
160
|
+
export const normalizeParams = (params) => {
|
|
161
161
|
if (!Array.isArray(params))
|
|
162
162
|
return params;
|
|
163
163
|
return params.map((value) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CloudflareAdapter.d.ts","sourceRoot":"","sources":["../../../../src/runtime/adapters/CloudflareAdapter.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,aAAa,EAGb,cAAc,EACf,MAAM,yBAAyB,CAAC;AAIjC;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;IAC5B;;OAEG;mBACY,aAAa,GAAG,cAAc;IA8D7C;;;OAGG;qBACc,OAAO;IAKxB;;;OAGG;qBACc,MAAM,GAAG,OAAO;EAIjC,CAAC;
|
|
1
|
+
{"version":3,"file":"CloudflareAdapter.d.ts","sourceRoot":"","sources":["../../../../src/runtime/adapters/CloudflareAdapter.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,aAAa,EAGb,cAAc,EACf,MAAM,yBAAyB,CAAC;AAIjC;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;IAC5B;;OAEG;mBACY,aAAa,GAAG,cAAc;IA8D7C;;;OAGG;qBACc,OAAO;IAKxB;;;OAGG;qBACc,MAAM,GAAG,OAAO;EAIjC,CAAC;AAyMH;;;;GAIG;AACH,MAAM,WAAW,iBAAkB,SAAQ,OAAO;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACjC,EAAE,CAAC,EAAE;QACH,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;CACH"}
|
|
@@ -74,6 +74,26 @@ export const CloudflareAdapter = Object.freeze({
|
|
|
74
74
|
return globalThis.env?.[namespace] ?? null;
|
|
75
75
|
},
|
|
76
76
|
});
|
|
77
|
+
/**
|
|
78
|
+
* Read the Cloudflare request body in a content-type aware way.
|
|
79
|
+
*
|
|
80
|
+
* Multipart bodies are read as a binary Buffer (via arrayBuffer) so uploaded
|
|
81
|
+
* files are not corrupted by text decoding. All other bodies are read as text,
|
|
82
|
+
* preserving the previous behavior for JSON / urlencoded / text payloads.
|
|
83
|
+
*/
|
|
84
|
+
async function readCloudflareRequestBody(request, platformRequest) {
|
|
85
|
+
if (request.method === 'GET' || request.method === 'HEAD')
|
|
86
|
+
return null;
|
|
87
|
+
const rawContentType = platformRequest.headers['content-type'];
|
|
88
|
+
const contentType = Array.isArray(rawContentType)
|
|
89
|
+
? (rawContentType[0] ?? '')
|
|
90
|
+
: (rawContentType ?? '');
|
|
91
|
+
if (contentType.toLowerCase().includes('multipart/form-data')) {
|
|
92
|
+
const arrayBuffer = await request.arrayBuffer();
|
|
93
|
+
return Buffer.from(arrayBuffer);
|
|
94
|
+
}
|
|
95
|
+
return request.text();
|
|
96
|
+
}
|
|
77
97
|
/**
|
|
78
98
|
* Handle Cloudflare request
|
|
79
99
|
*/
|
|
@@ -82,8 +102,10 @@ async function handleCloudflareRequest(adapter, config, logger, event) {
|
|
|
82
102
|
const request = event;
|
|
83
103
|
// Parse incoming request
|
|
84
104
|
const platformRequest = adapter.parseRequest(request);
|
|
85
|
-
// Read request body
|
|
86
|
-
|
|
105
|
+
// Read request body. Multipart bodies may contain binary bytes, so read them
|
|
106
|
+
// as an ArrayBuffer and keep a binary Buffer — converting to text first would
|
|
107
|
+
// corrupt uploaded files. Other content types remain text for downstream parsing.
|
|
108
|
+
const body = await readCloudflareRequestBody(request, platformRequest);
|
|
87
109
|
platformRequest.body = body;
|
|
88
110
|
// Create mock Node.js request/response objects
|
|
89
111
|
const { req, res, responseData } = createMockHttpObjects(platformRequest);
|
|
@@ -175,7 +175,7 @@ function formatDenoResponse(response) {
|
|
|
175
175
|
*/
|
|
176
176
|
function getDenoEnvironment() {
|
|
177
177
|
// @ts-ignore - Deno.env is available in Deno runtime
|
|
178
|
-
const env = (typeof Deno === 'undefined' ? {} : Deno.env.toObject?.() ?? {});
|
|
178
|
+
const env = (typeof Deno === 'undefined' ? {} : (Deno.env.toObject?.() ?? {}));
|
|
179
179
|
return {
|
|
180
180
|
nodeEnv: (env['DENO_ENV'] ?? 'production'),
|
|
181
181
|
runtime: 'deno',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Broadcast.d.ts","sourceRoot":"","sources":["../../../../src/tools/broadcast/Broadcast.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Broadcast.d.ts","sourceRoot":"","sources":["../../../../src/tools/broadcast/Broadcast.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAE/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,KAAK,WAAW,GAAG,QAAQ,CAAC;IAC1B,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1E,OAAO,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;CAC5E,CAAC,CAAC;AAEH,KAAK,qBAAqB,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,YAAY,CAAC;AAC9E,KAAK,qBAAqB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC1D,KAAK,kBAAkB,GAAG,eAAe,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAShE,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,KAAK,CAAC,EAAE,qBAAqB,CAAC;IAC9B,YAAY,CAAC,EAAE,qBAAqB,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC,CAAC;AA0BH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,EAAE,EAAE,IAAI,CAAC;IACT,SAAS,EAAE,kBAAkB,CAAC;IAC9B,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,0BAA0B,CAAC,QAAQ,CAAC,CAAC;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACpD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAC9B,CAAC,CAAC;AAo3BH,eAAO,MAAM,SAAS;mBACC,qBAAqB,GAAG,OAAO,CAAC,sBAAsB,CAAC;kBAIxD,MAAM,SAAS,MAAM,QAAQ,OAAO;0BAU5B,MAAM,SAAS,MAAM,QAAQ,OAAO;wBAKvD,qBAAqB,YACnB;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;4BAO1C,MAAM,SACR,MAAM,QACP,OAAO,YACJ;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;qBAKpC,MAAM;8BAES,qBAAqB;kCAEjB,MAAM,SAAS,MAAM,QAAQ,OAAO;;uBAKrD,MAAM,GAAG,WAAW;EAmBvC,CAAC;AAEH,eAAe,SAAS,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isArray, isNonEmptyString, isObject } from '../../helper/index.js';
|
|
2
|
+
import { delay } from '../../common/index.js';
|
|
2
3
|
import { InMemoryDriver } from './drivers/InMemory.js';
|
|
3
4
|
import { PusherDriver } from './drivers/Pusher.js';
|
|
4
5
|
import { RedisDriver } from './drivers/Redis.js';
|
|
@@ -15,6 +16,24 @@ const BROADCAST_CHANNEL_SCOPES = new Set([
|
|
|
15
16
|
]);
|
|
16
17
|
const INTERNAL_SOCKET_SECRET_HEADER = 'x-zintrust-socket-secret';
|
|
17
18
|
const DEFAULT_BROADCAST_EVENTS_PATH_TEMPLATE = '/apps/{appId}/events';
|
|
19
|
+
// A POST that returns one of these never completed at the application origin, so
|
|
20
|
+
// re-sending it cannot duplicate a delivered event. 520-524 are the edge->origin
|
|
21
|
+
// (e.g. Cloudflare) statuses: a 522 means the edge could not connect to the origin,
|
|
22
|
+
// not that the broadcast was processed. Genuine application responses (2xx and 4xx
|
|
23
|
+
// such as 401/403/422) reached the origin and are never retried.
|
|
24
|
+
const TRANSIENT_PUBLISH_STATUSES = new Set([
|
|
25
|
+
408, 425, 429, 500, 502, 503, 504, 520, 521, 522, 523, 524,
|
|
26
|
+
]);
|
|
27
|
+
const resolveInternalPublishRetryConfig = () => ({
|
|
28
|
+
maxAttempts: Math.max(1, Env.getInt('BROADCAST_INTERNAL_PUBLISH_RETRIES', 3)),
|
|
29
|
+
baseDelayMs: Math.max(0, Env.getInt('BROADCAST_INTERNAL_PUBLISH_RETRY_BASE_MS', 200)),
|
|
30
|
+
});
|
|
31
|
+
// exponential backoff + full jitter
|
|
32
|
+
const computeRetryDelayMs = (baseDelayMs, attempt) => {
|
|
33
|
+
const exponential = baseDelayMs * Math.pow(2, attempt - 1);
|
|
34
|
+
const jitter = baseDelayMs === 0 ? 0 : Math.floor(Math.random() * baseDelayMs);
|
|
35
|
+
return exponential + jitter;
|
|
36
|
+
};
|
|
18
37
|
const pickFirstNonEmpty = (...values) => {
|
|
19
38
|
for (const value of values) {
|
|
20
39
|
if (value.trim() !== '') {
|
|
@@ -224,7 +243,7 @@ const logTransportFallback = (transport, details) => {
|
|
|
224
243
|
...details,
|
|
225
244
|
});
|
|
226
245
|
};
|
|
227
|
-
const
|
|
246
|
+
const attemptInternalPublishEndpoint = async (endpoint, secret, payload) => {
|
|
228
247
|
try {
|
|
229
248
|
const response = await tracedFetch(endpoint, {
|
|
230
249
|
method: 'POST',
|
|
@@ -251,7 +270,12 @@ const requestInternalPublishEndpoint = async (endpoint, secret, payload) => {
|
|
|
251
270
|
status: response.status,
|
|
252
271
|
body: responseBody,
|
|
253
272
|
});
|
|
254
|
-
return {
|
|
273
|
+
return {
|
|
274
|
+
result: null,
|
|
275
|
+
error,
|
|
276
|
+
status: response.status,
|
|
277
|
+
transient: TRANSIENT_PUBLISH_STATUSES.has(response.status),
|
|
278
|
+
};
|
|
255
279
|
}
|
|
256
280
|
const resolvedChannels = isObject(responseBody) && isArray(responseBody['channels'])
|
|
257
281
|
? responseBody['channels'].filter(isNonEmptyString).map((channel) => channel.trim())
|
|
@@ -276,8 +300,34 @@ const requestInternalPublishEndpoint = async (endpoint, secret, payload) => {
|
|
|
276
300
|
endpoint,
|
|
277
301
|
error: describeError(error),
|
|
278
302
|
});
|
|
279
|
-
|
|
303
|
+
// A thrown error means the POST never completed at the application origin, so
|
|
304
|
+
// it is safe to retry.
|
|
305
|
+
return { result: null, error, transient: true };
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
const requestInternalPublishEndpoint = async (endpoint, secret, payload) => {
|
|
309
|
+
const { maxAttempts, baseDelayMs } = resolveInternalPublishRetryConfig();
|
|
310
|
+
let lastAttempt = { result: null };
|
|
311
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
312
|
+
lastAttempt = await attemptInternalPublishEndpoint(endpoint, secret, payload);
|
|
313
|
+
if (lastAttempt.result !== null) {
|
|
314
|
+
return lastAttempt;
|
|
315
|
+
}
|
|
316
|
+
// Non-transient failures (application 4xx, etc.) reached the origin; do not retry.
|
|
317
|
+
if (lastAttempt.transient !== true || attempt >= maxAttempts) {
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
const delayMs = computeRetryDelayMs(baseDelayMs, attempt);
|
|
321
|
+
Logger.warn('Broadcast internal publish retrying transient failure.', {
|
|
322
|
+
endpoint,
|
|
323
|
+
attempt,
|
|
324
|
+
maxAttempts,
|
|
325
|
+
delayMs,
|
|
326
|
+
status: lastAttempt.status,
|
|
327
|
+
});
|
|
328
|
+
await delay(delayMs);
|
|
280
329
|
}
|
|
330
|
+
return lastAttempt;
|
|
281
331
|
};
|
|
282
332
|
const tryInternalPublishEndpoints = async (endpoints, secret, payload, index = 0, lastError) => {
|
|
283
333
|
const endpoint = endpoints[index];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"R2.d.ts","sourceRoot":"","sources":["../../../../../src/tools/storage/drivers/R2.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AA6EF,eAAO,MAAM,QAAQ;kCAET,QAAQ,OACX,MAAM,YACD,OAAO,GAChB,OAAO,CAAC,qBAAqB,CAAC;uBAOvB,QAAQ,OACX,MAAM,YACD,MAAM,cACJ,MAAM,SACX,OAAO,YACJ,OAAO,GAChB,OAAO,CAAC,cAAc,CAAC;oCAOhB,QAAQ,OACX,MAAM,YACD,MAAM,iBACD,cAAc,EAAE,GAC9B,OAAO,CAAC,MAAM,CAAC;iCAOiB,QAAQ,OAAO,MAAM,YAAY,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;gBAMxE,QAAQ,OAAO,MAAM,WAAW,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"R2.d.ts","sourceRoot":"","sources":["../../../../../src/tools/storage/drivers/R2.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AA6EF,eAAO,MAAM,QAAQ;kCAET,QAAQ,OACX,MAAM,YACD,OAAO,GAChB,OAAO,CAAC,qBAAqB,CAAC;uBAOvB,QAAQ,OACX,MAAM,YACD,MAAM,cACJ,MAAM,SACX,OAAO,YACJ,OAAO,GAChB,OAAO,CAAC,cAAc,CAAC;oCAOhB,QAAQ,OACX,MAAM,YACD,MAAM,iBACD,cAAc,EAAE,GAC9B,OAAO,CAAC,MAAM,CAAC;iCAOiB,QAAQ,OAAO,MAAM,YAAY,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;gBAMxE,QAAQ,OAAO,MAAM,WAAW,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;gBA4BjE,QAAQ,OAAO,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;mBAmBpC,QAAQ,OAAO,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;mBAmBxC,QAAQ,OAAO,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;gBAmB9C,QAAQ,OAAO,MAAM,GAAG,MAAM;oBAchC,QAAQ,OACX,MAAM,YACD;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAA;KAAE,GACvD,MAAM;EAgBT,CAAC;AAEH,eAAe,QAAQ,CAAC"}
|
|
@@ -77,7 +77,10 @@ export const R2Driver = Object.freeze({
|
|
|
77
77
|
endpoint: config.endpoint,
|
|
78
78
|
usePathStyle: true,
|
|
79
79
|
};
|
|
80
|
-
|
|
80
|
+
await S3Driver.put(s3Config, key, content);
|
|
81
|
+
// Return the public-facing URL (custom domain when configured), not the
|
|
82
|
+
// path-style signing URL produced by S3Driver.put.
|
|
83
|
+
return R2Driver.url(config, key);
|
|
81
84
|
},
|
|
82
85
|
async get(config, key) {
|
|
83
86
|
if (hasWorkersBucketBinding(config)) {
|
|
@@ -128,6 +131,12 @@ export const R2Driver = Object.freeze({
|
|
|
128
131
|
return S3Driver.delete(s3Config, key);
|
|
129
132
|
},
|
|
130
133
|
url(config, key) {
|
|
134
|
+
// Prefer an explicit public base URL (custom domain CNAME'd to the bucket root):
|
|
135
|
+
// no bucket segment, since the domain already resolves to the bucket. The signing
|
|
136
|
+
// `endpoint` (used by tempUrl/put/get) stays path-style and independent.
|
|
137
|
+
const publicBase = config.url?.trim() ?? '';
|
|
138
|
+
if (publicBase !== '')
|
|
139
|
+
return `${publicBase.replace(/\/$/, '')}/${key}`;
|
|
131
140
|
if (config.endpoint !== undefined && config.endpoint.trim() !== '') {
|
|
132
141
|
return `${config.endpoint.replace(/\/$/, '')}/${config.bucket}/${key}`;
|
|
133
142
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"S3.d.ts","sourceRoot":"","sources":["../../../../../src/tools/storage/drivers/S3.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"S3.d.ts","sourceRoot":"","sources":["../../../../../src/tools/storage/drivers/S3.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IAIvB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,CAAC;AAwGF,eAAO,MAAM,QAAQ;gBACD,QAAQ,OAAO,MAAM,WAAW,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;gBAkDjE,QAAQ,OAAO,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;mBA8CpC,QAAQ,OAAO,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;mBAsCxC,QAAQ,OAAO,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;gBAwC9C,QAAQ,OAAO,MAAM,GAAG,MAAM;oBAYhC,QAAQ,OACX,MAAM,YACD;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAA;KAAE,GACvD,MAAM;EAqDT,CAAC;AAEH,eAAe,QAAQ,CAAC"}
|
|
@@ -209,6 +209,11 @@ export const S3Driver = Object.freeze({
|
|
|
209
209
|
}
|
|
210
210
|
},
|
|
211
211
|
url(config, key) {
|
|
212
|
+
// Prefer an explicit public base URL (custom domain mapped to the bucket root):
|
|
213
|
+
// no bucket segment, since the domain already resolves to the bucket.
|
|
214
|
+
const publicBase = config.url?.trim() ?? '';
|
|
215
|
+
if (publicBase !== '')
|
|
216
|
+
return `${publicBase.replace(/\/$/, '')}/${key}`;
|
|
212
217
|
const endpoint = config.endpoint?.trim() ?? '';
|
|
213
218
|
if (endpoint !== '')
|
|
214
219
|
return `${endpoint.replace(/\/$/, '')}/${config.bucket}/${key}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/tools/storage/index.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,CAAC;AAErD,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,KAAK,cAAc,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/tools/storage/index.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,CAAC;AAErD,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,KAAK,cAAc,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAA;CAAE,CAAC;AAwFrE,eAAO,MAAM,OAAO;mBACH,MAAM,GAAG,WAAW;cAuCnB,MAAM,GAAG,SAAS,QAAQ,MAAM,YAAY,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;cAW7E,MAAM,GAAG,SAAS,QAAQ,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;iBAW/C,MAAM,GAAG,SAAS,QAAQ,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;iBASnD,MAAM,GAAG,SAAS,QAAQ,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;cASzD,MAAM,GAAG,SAAS,QAAQ,MAAM,GAAG,MAAM;kBAY/B,MAAM,GAAG,SAAS,QAAQ,MAAM,YAAY,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;EAqBhG,CAAC;AAEH,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC"}
|
|
@@ -14,6 +14,7 @@ const normalizers = {
|
|
|
14
14
|
accessKeyId: String(raw['accessKeyId'] ?? raw['key'] ?? ''),
|
|
15
15
|
secretAccessKey: String(raw['secretAccessKey'] ?? raw['secret'] ?? ''),
|
|
16
16
|
endpoint: typeof raw['endpoint'] === 'string' ? raw['endpoint'] : undefined,
|
|
17
|
+
url: typeof raw['url'] === 'string' ? raw['url'] : undefined,
|
|
17
18
|
usePathStyle: Boolean(raw['usePathStyle'] ?? raw['usePathStyleUrl'] ?? false),
|
|
18
19
|
}),
|
|
19
20
|
r2: (raw) => ({
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { WorkerModule } from './processor-registry';
|
|
2
|
+
export declare const csvEnvSet: (key: string) => Set<string>;
|
|
3
|
+
type DrainTarget = {
|
|
4
|
+
queueName: string;
|
|
5
|
+
processorSpec: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Narrow drain targets by env allowlist/denylist:
|
|
9
|
+
* - `WORKER_DRAIN_QUEUES`: if set, only these queues are drained.
|
|
10
|
+
* - `WORKER_DRAIN_EXCLUDE_QUEUES`: these queues are always skipped.
|
|
11
|
+
*/
|
|
12
|
+
export declare const filterDrainTargetsByEnv: (targets: DrainTarget[]) => DrainTarget[];
|
|
2
13
|
export type AppWorkerDefinition = Readonly<{
|
|
3
14
|
name: string;
|
|
4
15
|
queueName: string;
|
|
@@ -8,6 +19,13 @@ export type AppWorkerDefinition = Readonly<{
|
|
|
8
19
|
concurrency: number;
|
|
9
20
|
processorSpec: string;
|
|
10
21
|
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve which queues this Worker should drain: app manifest workers that have a bundled
|
|
24
|
+
* processor and are not disabled. The backend lifecycle registry is authoritative for
|
|
25
|
+
* start/stop — a worker explicitly `stopped` there is skipped; otherwise the manifest's
|
|
26
|
+
* `autoStart` decides.
|
|
27
|
+
*/
|
|
28
|
+
export declare const resolveDrainTargets: (appWorkerDefinitions: AppWorkerDefinition[], workerModules: ReadonlyArray<WorkerModule>) => Promise<DrainTarget[]>;
|
|
11
29
|
/**
|
|
12
30
|
* Ensure a drain loop is running. Returns immediately. If a loop is already active this is
|
|
13
31
|
* a no-op (concurrent-PING guard). The returned promise resolves when the active loop ends
|
|
@@ -15,4 +33,5 @@ export type AppWorkerDefinition = Readonly<{
|
|
|
15
33
|
*/
|
|
16
34
|
export declare const ensureDraining: (appWorkerDefinitions: AppWorkerDefinition[], workerModules: ReadonlyArray<WorkerModule>) => Promise<void>;
|
|
17
35
|
export declare const isDraining: () => boolean;
|
|
36
|
+
export {};
|
|
18
37
|
//# sourceMappingURL=drain.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drain.d.ts","sourceRoot":"","sources":["../../../src/worker-runtime/drain.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"drain.d.ts","sourceRoot":"","sources":["../../../src/worker-runtime/drain.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAoBvE,eAAO,MAAM,SAAS,GAAI,KAAK,MAAM,KAAG,GAAG,CAAC,MAAM,CAM/C,CAAC;AAEJ,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAAI,SAAS,WAAW,EAAE,KAAG,WAAW,EAW3E,CAAC;AASF,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAC9B,sBAAsB,mBAAmB,EAAE,EAC3C,eAAe,aAAa,CAAC,YAAY,CAAC,KACzC,OAAO,CAAC,WAAW,EAAE,CA6BvB,CAAC;AA8HF;;;;GAIG;AACH,eAAO,MAAM,cAAc,GACzB,sBAAsB,mBAAmB,EAAE,EAC3C,eAAe,aAAa,CAAC,YAAY,CAAC,KACzC,OAAO,CAAC,IAAI,CAUd,CAAC;AAEF,eAAO,MAAM,UAAU,QAAO,OAA+B,CAAC"}
|
|
@@ -21,13 +21,32 @@ const intEnv = (key, fallback) => {
|
|
|
21
21
|
const parsed = Number.parseInt(Env.get(key, '').trim(), 10);
|
|
22
22
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
23
23
|
};
|
|
24
|
+
export const csvEnvSet = (key) => new Set(Env.get(key, '')
|
|
25
|
+
.split(',')
|
|
26
|
+
.map((value) => value.trim())
|
|
27
|
+
.filter((value) => value.length > 0));
|
|
28
|
+
/**
|
|
29
|
+
* Narrow drain targets by env allowlist/denylist:
|
|
30
|
+
* - `WORKER_DRAIN_QUEUES`: if set, only these queues are drained.
|
|
31
|
+
* - `WORKER_DRAIN_EXCLUDE_QUEUES`: these queues are always skipped.
|
|
32
|
+
*/
|
|
33
|
+
export const filterDrainTargetsByEnv = (targets) => {
|
|
34
|
+
const onlyQueues = csvEnvSet('WORKER_DRAIN_QUEUES');
|
|
35
|
+
const excludeQueues = csvEnvSet('WORKER_DRAIN_EXCLUDE_QUEUES');
|
|
36
|
+
return targets.filter((target) => {
|
|
37
|
+
if (onlyQueues.size > 0 && !onlyQueues.has(target.queueName)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return !excludeQueues.has(target.queueName);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
24
43
|
/**
|
|
25
44
|
* Resolve which queues this Worker should drain: app manifest workers that have a bundled
|
|
26
45
|
* processor and are not disabled. The backend lifecycle registry is authoritative for
|
|
27
46
|
* start/stop — a worker explicitly `stopped` there is skipped; otherwise the manifest's
|
|
28
47
|
* `autoStart` decides.
|
|
29
48
|
*/
|
|
30
|
-
const resolveDrainTargets = async (appWorkerDefinitions, workerModules) => {
|
|
49
|
+
export const resolveDrainTargets = async (appWorkerDefinitions, workerModules) => {
|
|
31
50
|
let statusByQueue = new Map();
|
|
32
51
|
try {
|
|
33
52
|
const registered = await listWorkers();
|
|
@@ -50,7 +69,7 @@ const resolveDrainTargets = async (appWorkerDefinitions, workerModules) => {
|
|
|
50
69
|
targets.push({ queueName: definition.queueName, processorSpec: definition.processorSpec });
|
|
51
70
|
}
|
|
52
71
|
}
|
|
53
|
-
return targets;
|
|
72
|
+
return filterDrainTargetsByEnv(targets);
|
|
54
73
|
};
|
|
55
74
|
const toJob = (queueName, pulled) => {
|
|
56
75
|
return {
|