@zintrust/core 0.4.33 → 0.4.36
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 +4 -4
- package/src/cache/drivers/RedisDriver.d.ts.map +1 -1
- package/src/cache/drivers/RedisDriver.js +1 -1
- package/src/cli/OptionalCliExtensions.d.ts.map +1 -1
- package/src/cli/OptionalCliExtensions.js +1 -0
- package/src/cli/commands/WorkerCommands.d.ts +2 -1
- package/src/cli/commands/WorkerCommands.d.ts.map +1 -1
- package/src/cli/commands/WorkerCommands.js +111 -62
- package/src/cli/services/WorkerStartupDiagnostics.d.ts +41 -0
- package/src/cli/services/WorkerStartupDiagnostics.d.ts.map +1 -0
- package/src/cli/services/WorkerStartupDiagnostics.js +348 -0
- package/src/cli/utils/DistPackager.d.ts.map +1 -1
- package/src/cli/utils/DistPackager.js +22 -18
- package/src/config/queue.d.ts.map +1 -1
- package/src/config/queue.js +5 -3
- package/src/config/workers.d.ts +2 -1
- package/src/config/workers.d.ts.map +1 -1
- package/src/config/workers.js +6 -1
- package/src/index.d.ts +11 -11
- package/src/index.d.ts.map +1 -1
- package/src/index.js +13 -13
- package/src/proxy/CloudflareProxyShared.d.ts +50 -0
- package/src/proxy/CloudflareProxyShared.d.ts.map +1 -0
- package/src/proxy/CloudflareProxyShared.js +117 -0
- package/src/proxy/d1/ZintrustD1Proxy.d.ts.map +1 -1
- package/src/proxy/d1/ZintrustD1Proxy.js +49 -158
- package/src/proxy/kv/ZintrustKvProxy.d.ts.map +1 -1
- package/src/proxy/kv/ZintrustKvProxy.js +41 -142
- package/src/security/CsrfTokenManager.d.ts.map +1 -1
- package/src/security/CsrfTokenManager.js +1 -1
- package/src/security/JwtSessions.d.ts.map +1 -1
- package/src/security/JwtSessions.js +1 -1
- package/src/security/TokenRevocation.d.ts.map +1 -1
- package/src/security/TokenRevocation.js +1 -1
- package/src/tools/queue/LockProvider.d.ts.map +1 -1
- package/src/tools/queue/LockProvider.js +1 -1
- package/src/tools/redis/RedisTransport.d.ts +34 -0
- package/src/tools/redis/RedisTransport.d.ts.map +1 -0
- package/src/tools/redis/RedisTransport.js +251 -0
package/src/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @zintrust/core v0.4.
|
|
2
|
+
* @zintrust/core v0.4.36
|
|
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-03-
|
|
8
|
+
* Built: 2026-03-30T17:16:50.879Z
|
|
9
9
|
* Node: >=20.0.0
|
|
10
10
|
* License: MIT
|
|
11
11
|
*
|
|
@@ -21,13 +21,13 @@
|
|
|
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-03-
|
|
24
|
+
export const ZINTRUST_BUILD_DATE = '2026-03-30T17:16:50.845Z'; // 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';
|
|
28
28
|
export { Server } from './boot/Server.js';
|
|
29
29
|
export { ServiceContainer } from './container/ServiceContainer.js';
|
|
30
|
-
export {
|
|
30
|
+
export { createPaginator, getNextPageUrl, getPrevPageUrl, Paginator } from './database/Paginator.js';
|
|
31
31
|
export { Controller } from './http/Controller.js';
|
|
32
32
|
export { FileUpload } from './http/FileUpload.js';
|
|
33
33
|
export { Kernel } from './http/Kernel.js';
|
|
@@ -39,7 +39,7 @@ export { MultipartParserRegistry } from './http/parsers/MultipartParserRegistry.
|
|
|
39
39
|
export { Request } from './http/Request.js';
|
|
40
40
|
export { RequestContext } from './http/RequestContext.js';
|
|
41
41
|
export { Response } from './http/Response.js';
|
|
42
|
-
export {
|
|
42
|
+
export { getValidatedBody, getValidatedHeaders, getValidatedParams, getValidatedQuery, hasValidatedBody, requireValidatedBody, ValidationHelper, } from './http/ValidationHelper.js';
|
|
43
43
|
export { CsrfMiddleware } from './middleware/CsrfMiddleware.js';
|
|
44
44
|
export { ErrorHandlerMiddleware } from './middleware/ErrorHandlerMiddleware.js';
|
|
45
45
|
export { LoggingMiddleware } from './middleware/LoggingMiddleware.js';
|
|
@@ -64,13 +64,13 @@ export { Schema as MigrationSchema } from './migrations/schema/index.js';
|
|
|
64
64
|
// Adapter registry (for external adapter packages)
|
|
65
65
|
export { OpenApiGenerator } from './openapi/OpenApiGenerator.js';
|
|
66
66
|
export { Router } from './routes/Router.js';
|
|
67
|
-
export {
|
|
67
|
+
export { normalizeRouteMeta, RouteRegistry } from './routes/RouteRegistry.js';
|
|
68
68
|
export { DatabaseAdapterRegistry } from './orm/DatabaseAdapterRegistry.js';
|
|
69
69
|
// Common
|
|
70
|
-
export {
|
|
70
|
+
export { generateSecureJobId, generateUuid, getString, Utilities, } from './common/utility.js';
|
|
71
71
|
export { delay, ensureDirSafe } from './common/index.js';
|
|
72
72
|
// Collections
|
|
73
|
-
export {
|
|
73
|
+
export { collect, Collection } from './collections/index.js';
|
|
74
74
|
// HTTP Client
|
|
75
75
|
export { HttpClient } from './tools/http/Http.js';
|
|
76
76
|
// Profiling
|
|
@@ -94,14 +94,14 @@ export { Hash } from './security/Hash.js';
|
|
|
94
94
|
export { JwtManager } from './security/JwtManager.js';
|
|
95
95
|
export { JwtSessions } from './security/JwtSessions.js';
|
|
96
96
|
export { PasswordResetTokenBroker } from './security/PasswordResetTokenBroker.js';
|
|
97
|
-
export {
|
|
97
|
+
export { createSanitizer, Sanitizer } from './security/Sanitizer.js';
|
|
98
98
|
export { TokenRevocation } from './security/TokenRevocation.js';
|
|
99
99
|
export { Xss } from './security/Xss.js';
|
|
100
100
|
export { XssProtection } from './security/XssProtection.js';
|
|
101
101
|
// Exceptions
|
|
102
102
|
export { ErrorFactory } from './exceptions/ZintrustError.js';
|
|
103
103
|
// Runtime services
|
|
104
|
-
export {
|
|
104
|
+
export { detectCloudflareWorkers, detectRuntimePlatform, RUNTIME_PLATFORM, RuntimeServices, } from './runtime/RuntimeServices.js';
|
|
105
105
|
// Events
|
|
106
106
|
export { EventDispatcher } from './events/EventDispatcher.js';
|
|
107
107
|
// Sessions
|
|
@@ -132,7 +132,7 @@ export { ServiceAuthMiddleware } from './microservices/ServiceAuthMiddleware.js'
|
|
|
132
132
|
export { HealthCheckHandler, ServiceHealthMonitor } from './microservices/ServiceHealthMonitor.js';
|
|
133
133
|
export { getServiceId, isCanonicalServiceId, normalizeServiceManifest, } from './microservices/ServiceManifest.js';
|
|
134
134
|
export { ProjectRuntime } from './runtime/ProjectRuntime.js';
|
|
135
|
-
export {
|
|
135
|
+
export { clearMiddlewareConfigCache, middlewareConfig, MiddlewareKeys } from './config/middleware.js';
|
|
136
136
|
export { createBaseDrivers, queueConfig } from './config/queue.js';
|
|
137
137
|
export { default as broadcastConfig } from './config/broadcast.js';
|
|
138
138
|
export { default as notificationConfig } from './config/notification.js';
|
|
@@ -143,7 +143,7 @@ export { startupConfig } from './config/startup.js';
|
|
|
143
143
|
export { Constants, DEFAULTS, ENV_KEYS, HTTP_HEADERS, MIME_TYPES } from './config/constants.js';
|
|
144
144
|
export { FeatureFlags } from './config/features.js';
|
|
145
145
|
export { Cloudflare } from './config/cloudflare.js';
|
|
146
|
-
export {
|
|
146
|
+
export { getDatabaseCredentials, getJwtSecrets, SECRETS, SecretsManager, } from './config/SecretsManager.js';
|
|
147
147
|
// Config (validation)
|
|
148
148
|
export { StartupConfigValidator } from './config/StartupConfigValidator.js';
|
|
149
149
|
export { Mail } from './tools/mail/index.js';
|
|
@@ -225,7 +225,7 @@ export { ZintrustLang } from './lang/lang.js';
|
|
|
225
225
|
// Workers config
|
|
226
226
|
export { createRedisConnection, workersConfig } from './config/workers.js';
|
|
227
227
|
// Redis config key - Singleton exports
|
|
228
|
-
export {
|
|
228
|
+
export { createRedisKey, extractOriginalKey, getBullMQSafeQueueName, getPrefix, isAppKey, RedisKeys, } from './tools/redis/RedisKeyManager.js';
|
|
229
229
|
export { CloudflareSocket } from './sockets/CloudflareSocket.js';
|
|
230
230
|
export { detectRuntime } from './runtime/detectRuntime.js';
|
|
231
231
|
// NOTE: Node-only exports (like FileLogWriter, process) are intentionally not
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type ProxyNoncePutOptions = {
|
|
2
|
+
expirationTtl?: number;
|
|
3
|
+
};
|
|
4
|
+
export type ProxyNonceNamespace = {
|
|
5
|
+
get: (key: string) => Promise<string | null>;
|
|
6
|
+
put: (key: string, value: string, options?: ProxyNoncePutOptions) => Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
type SignedRequestOptions = Readonly<{
|
|
9
|
+
secretEnvVar: string;
|
|
10
|
+
missingSecretStatus: number;
|
|
11
|
+
missingSecretMessage: string;
|
|
12
|
+
defaultSigningWindowMs: number;
|
|
13
|
+
}>;
|
|
14
|
+
type ReadAndVerifyJsonOptions = SignedRequestOptions & Readonly<{
|
|
15
|
+
defaultMaxBodyBytes: number;
|
|
16
|
+
}>;
|
|
17
|
+
type ProxyRequestEnv = object;
|
|
18
|
+
export declare const json: (status: number, body: unknown) => Response;
|
|
19
|
+
export declare const toErrorResponse: (status: number, code: string, message: string) => Response;
|
|
20
|
+
export declare const getEnvInt: (env: ProxyRequestEnv, name: string, fallback: number) => number;
|
|
21
|
+
export declare const normalizeBindingName: (value: unknown) => string | null;
|
|
22
|
+
export declare const readBodyBytes: (request: Request, maxBytes: number) => Promise<{
|
|
23
|
+
ok: true;
|
|
24
|
+
bytes: Uint8Array;
|
|
25
|
+
text: string;
|
|
26
|
+
} | {
|
|
27
|
+
ok: false;
|
|
28
|
+
response: Response;
|
|
29
|
+
}>;
|
|
30
|
+
export declare const parseOptionalJson: (text: string) => {
|
|
31
|
+
ok: true;
|
|
32
|
+
payload: Record<string, unknown> | null;
|
|
33
|
+
} | {
|
|
34
|
+
ok: false;
|
|
35
|
+
response: Response;
|
|
36
|
+
};
|
|
37
|
+
export declare const verifyNonceKv: (kv: ProxyNonceNamespace, keyId: string, nonce: string, ttlMs: number) => Promise<boolean>;
|
|
38
|
+
export declare const verifySignedRequest: (request: Request, env: ProxyRequestEnv, bodyBytes: Uint8Array, options: SignedRequestOptions) => Promise<Response | {
|
|
39
|
+
ok: true;
|
|
40
|
+
}>;
|
|
41
|
+
export declare const readAndVerifyJson: (request: Request, env: ProxyRequestEnv, options: ReadAndVerifyJsonOptions) => Promise<{
|
|
42
|
+
ok: true;
|
|
43
|
+
payload: Record<string, unknown> | null;
|
|
44
|
+
bodyBytes: Uint8Array;
|
|
45
|
+
} | {
|
|
46
|
+
ok: false;
|
|
47
|
+
response: Response;
|
|
48
|
+
}>;
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=CloudflareProxyShared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CloudflareProxyShared.d.ts","sourceRoot":"","sources":["../../../src/proxy/CloudflareProxyShared.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,oBAAoB,GAAG;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACpF,CAAC;AAEF,KAAK,oBAAoB,GAAG,QAAQ,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,sBAAsB,EAAE,MAAM,CAAC;CAChC,CAAC,CAAC;AAEH,KAAK,wBAAwB,GAAG,oBAAoB,GAClD,QAAQ,CAAC;IACP,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CAAC,CAAC;AAEL,KAAK,eAAe,GAAG,MAAM,CAAC;AAE9B,eAAO,MAAM,IAAI,GAAI,QAAQ,MAAM,EAAE,MAAM,OAAO,KAAG,QAQpD,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,EAAE,MAAM,MAAM,EAAE,SAAS,MAAM,KAAG,QAG/E,CAAC;AAMF,eAAO,MAAM,SAAS,GAAI,KAAK,eAAe,EAAE,MAAM,MAAM,EAAE,UAAU,MAAM,KAAG,MAKhF,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,OAAO,OAAO,KAAG,MAAM,GAAG,IAI9D,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,SAAS,OAAO,EAChB,UAAU,MAAM,KACf,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAY3F,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,MAAM,MAAM,KACX;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAezF,CAAC;AAcF,eAAO,MAAM,aAAa,GACxB,IAAI,mBAAmB,EACvB,OAAO,MAAM,EACb,OAAO,MAAM,EACb,OAAO,MAAM,KACZ,OAAO,CAAC,OAAO,CAOjB,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,SAAS,OAAO,EAChB,KAAK,eAAe,EACpB,WAAW,UAAU,EACrB,SAAS,oBAAoB,KAC5B,OAAO,CAAC,QAAQ,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,CA+BjC,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,SAAS,OAAO,EAChB,KAAK,eAAe,EACpB,SAAS,wBAAwB,KAChC,OAAO,CACN;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,UAAU,CAAA;CAAE,GAC5E;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAapC,CAAC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { isString } from '../helper/index.js';
|
|
2
|
+
import { ErrorHandler } from './ErrorHandler.js';
|
|
3
|
+
import { RequestValidator } from './RequestValidator.js';
|
|
4
|
+
import { SigningService } from './SigningService.js';
|
|
5
|
+
export const json = (status, body) => {
|
|
6
|
+
return new Response(JSON.stringify(body), {
|
|
7
|
+
status,
|
|
8
|
+
headers: {
|
|
9
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
10
|
+
'Cache-Control': 'no-store',
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
export const toErrorResponse = (status, code, message) => {
|
|
15
|
+
const error = ErrorHandler.toProxyError(status, code, message);
|
|
16
|
+
return json(error.status, error.body);
|
|
17
|
+
};
|
|
18
|
+
const getEnvValue = (env, name) => {
|
|
19
|
+
return env[name];
|
|
20
|
+
};
|
|
21
|
+
export const getEnvInt = (env, name, fallback) => {
|
|
22
|
+
const raw = getEnvValue(env, name);
|
|
23
|
+
if (!isString(raw))
|
|
24
|
+
return fallback;
|
|
25
|
+
const parsed = Number.parseInt(raw, 10);
|
|
26
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
27
|
+
};
|
|
28
|
+
export const normalizeBindingName = (value) => {
|
|
29
|
+
if (!isString(value))
|
|
30
|
+
return null;
|
|
31
|
+
const trimmed = value.trim();
|
|
32
|
+
return trimmed === '' ? null : trimmed;
|
|
33
|
+
};
|
|
34
|
+
export const readBodyBytes = async (request, maxBytes) => {
|
|
35
|
+
const buf = await request.arrayBuffer();
|
|
36
|
+
if (buf.byteLength > maxBytes) {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
response: toErrorResponse(413, 'PAYLOAD_TOO_LARGE', 'Body too large'),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const bytes = new Uint8Array(buf);
|
|
43
|
+
const text = new TextDecoder().decode(bytes);
|
|
44
|
+
return { ok: true, bytes, text };
|
|
45
|
+
};
|
|
46
|
+
export const parseOptionalJson = (text) => {
|
|
47
|
+
if (text.trim() === '')
|
|
48
|
+
return { ok: true, payload: null };
|
|
49
|
+
const parsed = RequestValidator.parseJson(text);
|
|
50
|
+
if (!parsed.ok) {
|
|
51
|
+
let message = parsed.error.message;
|
|
52
|
+
if (parsed.error.code === 'INVALID_JSON') {
|
|
53
|
+
message = 'Invalid JSON body';
|
|
54
|
+
}
|
|
55
|
+
else if (parsed.error.code === 'VALIDATION_ERROR') {
|
|
56
|
+
message = 'Invalid body';
|
|
57
|
+
}
|
|
58
|
+
return { ok: false, response: toErrorResponse(400, parsed.error.code, message) };
|
|
59
|
+
}
|
|
60
|
+
return { ok: true, payload: parsed.value };
|
|
61
|
+
};
|
|
62
|
+
const loadSigningSecret = (env, secretEnvVar) => {
|
|
63
|
+
const directValue = getEnvValue(env, secretEnvVar);
|
|
64
|
+
const direct = isString(directValue) ? directValue.trim() : '';
|
|
65
|
+
if (direct !== '')
|
|
66
|
+
return direct;
|
|
67
|
+
const fallbackValue = getEnvValue(env, 'APP_KEY');
|
|
68
|
+
const fallback = isString(fallbackValue) ? fallbackValue.trim() : '';
|
|
69
|
+
if (fallback !== '')
|
|
70
|
+
return fallback;
|
|
71
|
+
return null;
|
|
72
|
+
};
|
|
73
|
+
export const verifyNonceKv = async (kv, keyId, nonce, ttlMs) => {
|
|
74
|
+
const ttlSeconds = Math.max(1, Math.ceil(ttlMs / 1000));
|
|
75
|
+
const storageKey = `nonce:${keyId}:${nonce}`;
|
|
76
|
+
const existing = await kv.get(storageKey);
|
|
77
|
+
if (existing !== null)
|
|
78
|
+
return false;
|
|
79
|
+
await kv.put(storageKey, '1', { expirationTtl: ttlSeconds });
|
|
80
|
+
return true;
|
|
81
|
+
};
|
|
82
|
+
export const verifySignedRequest = async (request, env, bodyBytes, options) => {
|
|
83
|
+
const secret = loadSigningSecret(env, options.secretEnvVar);
|
|
84
|
+
if (secret === null) {
|
|
85
|
+
return toErrorResponse(options.missingSecretStatus, 'CONFIG_ERROR', options.missingSecretMessage);
|
|
86
|
+
}
|
|
87
|
+
const windowMs = getEnvInt(env, 'ZT_PROXY_SIGNING_WINDOW_MS', options.defaultSigningWindowMs);
|
|
88
|
+
const nonceStore = getEnvValue(env, 'ZT_NONCES');
|
|
89
|
+
const verifyResult = await SigningService.verifyWithKeyProvider({
|
|
90
|
+
method: request.method,
|
|
91
|
+
url: request.url,
|
|
92
|
+
body: bodyBytes,
|
|
93
|
+
headers: request.headers,
|
|
94
|
+
windowMs,
|
|
95
|
+
getSecretForKeyId: (_keyId) => secret,
|
|
96
|
+
verifyNonce: nonceStore === undefined || nonceStore === null
|
|
97
|
+
? undefined
|
|
98
|
+
: async (keyId, nonce, ttlMs) => verifyNonceKv(nonceStore, keyId, nonce, ttlMs),
|
|
99
|
+
});
|
|
100
|
+
if (!verifyResult.ok) {
|
|
101
|
+
return toErrorResponse(verifyResult.status, verifyResult.code, verifyResult.message);
|
|
102
|
+
}
|
|
103
|
+
return { ok: true };
|
|
104
|
+
};
|
|
105
|
+
export const readAndVerifyJson = async (request, env, options) => {
|
|
106
|
+
const maxBodyBytes = getEnvInt(env, 'ZT_MAX_BODY_BYTES', options.defaultMaxBodyBytes);
|
|
107
|
+
const bodyResult = await readBodyBytes(request, maxBodyBytes);
|
|
108
|
+
if (!bodyResult.ok)
|
|
109
|
+
return { ok: false, response: bodyResult.response };
|
|
110
|
+
const auth = await verifySignedRequest(request, env, bodyResult.bytes, options);
|
|
111
|
+
if (auth instanceof Response)
|
|
112
|
+
return { ok: false, response: auth };
|
|
113
|
+
const parsed = parseOptionalJson(bodyResult.text);
|
|
114
|
+
if (!parsed.ok)
|
|
115
|
+
return { ok: false, response: parsed.response };
|
|
116
|
+
return { ok: true, payload: parsed.payload, bodyBytes: bodyResult.bytes };
|
|
117
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ZintrustD1Proxy.d.ts","sourceRoot":"","sources":["../../../../src/proxy/d1/ZintrustD1Proxy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ZintrustD1Proxy.d.ts","sourceRoot":"","sources":["../../../../src/proxy/d1/ZintrustD1Proxy.ts"],"names":[],"mappings":"AAWA,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;AA6QF,eAAO,MAAM,eAAe;;;mBAGL,OAAO,OAAO,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC;EAqB5D,CAAC;AAEH,eAAe,eAAe,CAAC"}
|
|
@@ -1,30 +1,11 @@
|
|
|
1
1
|
import { Logger } from '../../config/logger.js';
|
|
2
2
|
import { isArray, isObject, isString } from '../../helper/index.js';
|
|
3
|
-
import {
|
|
3
|
+
import { getEnvInt, json, normalizeBindingName, readAndVerifyJson, toErrorResponse, } from '../CloudflareProxyShared.js';
|
|
4
4
|
import { RequestValidator } from '../RequestValidator.js';
|
|
5
|
-
import { SigningService } from '../SigningService.js';
|
|
6
5
|
const DEFAULT_SIGNING_WINDOW_MS = 60_000;
|
|
7
6
|
const DEFAULT_MAX_BODY_BYTES = 128 * 1024;
|
|
8
7
|
const DEFAULT_MAX_SQL_BYTES = 32 * 1024;
|
|
9
8
|
const DEFAULT_MAX_PARAMS = 256;
|
|
10
|
-
const json = (status, body) => new Response(JSON.stringify(body), {
|
|
11
|
-
status,
|
|
12
|
-
headers: {
|
|
13
|
-
'Content-Type': 'application/json; charset=utf-8',
|
|
14
|
-
'Cache-Control': 'no-store',
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
const toErrorResponse = (status, code, message) => {
|
|
18
|
-
const error = ErrorHandler.toProxyError(status, code, message);
|
|
19
|
-
return json(error.status, error.body);
|
|
20
|
-
};
|
|
21
|
-
const getEnvInt = (env, name, fallback) => {
|
|
22
|
-
const raw = env[name];
|
|
23
|
-
if (!isString(raw))
|
|
24
|
-
return fallback;
|
|
25
|
-
const parsed = Number.parseInt(raw, 10);
|
|
26
|
-
return Number.isFinite(parsed) ? parsed : fallback;
|
|
27
|
-
};
|
|
28
9
|
const isDebugEnabled = (env) => {
|
|
29
10
|
const raw = env.ZT_PROXY_DEBUG;
|
|
30
11
|
if (!isString(raw))
|
|
@@ -52,12 +33,6 @@ const logProxyError = (env, context, error) => {
|
|
|
52
33
|
message: safeErrorMessage(error).slice(0, 800),
|
|
53
34
|
});
|
|
54
35
|
};
|
|
55
|
-
const normalizeBindingName = (value) => {
|
|
56
|
-
if (!isString(value))
|
|
57
|
-
return null;
|
|
58
|
-
const trimmed = value.trim();
|
|
59
|
-
return trimmed === '' ? null : trimmed;
|
|
60
|
-
};
|
|
61
36
|
const resolveD1Binding = (env) => {
|
|
62
37
|
const candidates = ['DB', 'zintrust_db', normalizeBindingName(env.D1_BINDING)].filter((value, index, values) => isString(value) && value.trim() !== '' && values.indexOf(value) === index);
|
|
63
38
|
const record = env;
|
|
@@ -69,43 +44,6 @@ const resolveD1Binding = (env) => {
|
|
|
69
44
|
}
|
|
70
45
|
return null;
|
|
71
46
|
};
|
|
72
|
-
const readBodyBytes = async (request, maxBytes) => {
|
|
73
|
-
const buf = await request.arrayBuffer();
|
|
74
|
-
if (buf.byteLength > maxBytes) {
|
|
75
|
-
return {
|
|
76
|
-
ok: false,
|
|
77
|
-
response: toErrorResponse(413, 'PAYLOAD_TOO_LARGE', 'Body too large'),
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
const bytes = new Uint8Array(buf);
|
|
81
|
-
const text = new TextDecoder().decode(bytes);
|
|
82
|
-
return { ok: true, bytes, text };
|
|
83
|
-
};
|
|
84
|
-
const parseOptionalJson = (text) => {
|
|
85
|
-
if (text.trim() === '')
|
|
86
|
-
return { ok: true, payload: null };
|
|
87
|
-
const parsed = RequestValidator.parseJson(text);
|
|
88
|
-
if (!parsed.ok) {
|
|
89
|
-
let message = parsed.error.message;
|
|
90
|
-
if (parsed.error.code === 'INVALID_JSON') {
|
|
91
|
-
message = 'Invalid JSON body';
|
|
92
|
-
}
|
|
93
|
-
else if (parsed.error.code === 'VALIDATION_ERROR') {
|
|
94
|
-
message = 'Invalid body';
|
|
95
|
-
}
|
|
96
|
-
return { ok: false, response: toErrorResponse(400, parsed.error.code, message) };
|
|
97
|
-
}
|
|
98
|
-
return { ok: true, payload: parsed.value };
|
|
99
|
-
};
|
|
100
|
-
const loadSigningSecret = (env) => {
|
|
101
|
-
const direct = isString(env.D1_REMOTE_SECRET) ? env.D1_REMOTE_SECRET.trim() : '';
|
|
102
|
-
if (direct !== '')
|
|
103
|
-
return direct;
|
|
104
|
-
const fallback = isString(env.APP_KEY) ? env.APP_KEY.trim() : '';
|
|
105
|
-
if (fallback !== '')
|
|
106
|
-
return fallback;
|
|
107
|
-
return null;
|
|
108
|
-
};
|
|
109
47
|
const loadStatements = (env) => {
|
|
110
48
|
const raw = env.ZT_D1_STATEMENTS_JSON;
|
|
111
49
|
if (!isString(raw) || raw.trim() === '')
|
|
@@ -130,37 +68,6 @@ const isMutatingSql = (sql) => {
|
|
|
130
68
|
normalized.startsWith('alter') ||
|
|
131
69
|
normalized.startsWith('replace'));
|
|
132
70
|
};
|
|
133
|
-
const verifyNonceKv = async (kv, keyId, nonce, ttlMs) => {
|
|
134
|
-
const ttlSeconds = Math.max(1, Math.ceil(ttlMs / 1000));
|
|
135
|
-
const storageKey = `nonce:${keyId}:${nonce}`;
|
|
136
|
-
const existing = await kv.get(storageKey);
|
|
137
|
-
if (existing !== null)
|
|
138
|
-
return false;
|
|
139
|
-
await kv.put(storageKey, '1', { expirationTtl: ttlSeconds });
|
|
140
|
-
return true;
|
|
141
|
-
};
|
|
142
|
-
const verifySignedRequest = async (request, env, bodyBytes) => {
|
|
143
|
-
const secret = loadSigningSecret(env);
|
|
144
|
-
if (secret === null) {
|
|
145
|
-
return toErrorResponse(401, 'CONFIG_ERROR', 'Missing signing secret (D1_REMOTE_SECRET or APP_KEY)');
|
|
146
|
-
}
|
|
147
|
-
const windowMs = getEnvInt(env, 'ZT_PROXY_SIGNING_WINDOW_MS', DEFAULT_SIGNING_WINDOW_MS);
|
|
148
|
-
const verifyResult = await SigningService.verifyWithKeyProvider({
|
|
149
|
-
method: request.method,
|
|
150
|
-
url: request.url,
|
|
151
|
-
body: bodyBytes,
|
|
152
|
-
headers: request.headers,
|
|
153
|
-
windowMs,
|
|
154
|
-
getSecretForKeyId: (_keyId) => secret,
|
|
155
|
-
verifyNonce: env.ZT_NONCES === undefined
|
|
156
|
-
? undefined
|
|
157
|
-
: async (keyId, nonce, ttlMs) => verifyNonceKv(env.ZT_NONCES, keyId, nonce, ttlMs),
|
|
158
|
-
});
|
|
159
|
-
if (!verifyResult.ok) {
|
|
160
|
-
return toErrorResponse(verifyResult.status, verifyResult.code, verifyResult.message);
|
|
161
|
-
}
|
|
162
|
-
return { ok: true };
|
|
163
|
-
};
|
|
164
71
|
const requireDb = (env) => {
|
|
165
72
|
const db = resolveD1Binding(env);
|
|
166
73
|
if (db === null) {
|
|
@@ -197,36 +104,41 @@ const enforceSqlLimits = (env, sql, params) => {
|
|
|
197
104
|
}
|
|
198
105
|
return null;
|
|
199
106
|
};
|
|
200
|
-
const
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
107
|
+
const resolveDbRequest = async (request, env) => {
|
|
108
|
+
const check = await readAndVerifyJson(request, env, {
|
|
109
|
+
secretEnvVar: 'D1_REMOTE_SECRET',
|
|
110
|
+
missingSecretStatus: 401,
|
|
111
|
+
missingSecretMessage: 'Missing signing secret (D1_REMOTE_SECRET or APP_KEY)',
|
|
112
|
+
defaultSigningWindowMs: DEFAULT_SIGNING_WINDOW_MS,
|
|
113
|
+
defaultMaxBodyBytes: DEFAULT_MAX_BODY_BYTES,
|
|
114
|
+
});
|
|
115
|
+
if (!check.ok)
|
|
116
|
+
return { ok: false, response: check.response };
|
|
117
|
+
const db = requireDb(env);
|
|
118
|
+
if (db instanceof Response)
|
|
119
|
+
return { ok: false, response: db };
|
|
120
|
+
return { ok: true, db, payload: check.payload };
|
|
121
|
+
};
|
|
122
|
+
const resolveSqlRequest = async (request, env) => {
|
|
123
|
+
const resolved = await resolveDbRequest(request, env);
|
|
124
|
+
if (!resolved.ok)
|
|
125
|
+
return { ok: false, response: resolved.response };
|
|
126
|
+
const parsed = parseSqlPayload(resolved.payload);
|
|
209
127
|
if (!parsed.ok)
|
|
210
128
|
return { ok: false, response: parsed.response };
|
|
211
|
-
|
|
129
|
+
const limit = enforceSqlLimits(env, parsed.sql, parsed.params);
|
|
130
|
+
if (limit !== null)
|
|
131
|
+
return { ok: false, response: limit };
|
|
132
|
+
return { ok: true, db: resolved.db, sql: parsed.sql, params: parsed.params };
|
|
212
133
|
};
|
|
213
134
|
const handleQuery = async (request, env) => {
|
|
214
135
|
try {
|
|
215
|
-
const
|
|
216
|
-
if (!
|
|
217
|
-
return
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const parsed = parseSqlPayload(check.payload);
|
|
222
|
-
if (!parsed.ok)
|
|
223
|
-
return parsed.response;
|
|
224
|
-
const limit = enforceSqlLimits(env, parsed.sql, parsed.params);
|
|
225
|
-
if (limit !== null)
|
|
226
|
-
return limit;
|
|
227
|
-
const result = await db
|
|
228
|
-
.prepare(parsed.sql)
|
|
229
|
-
.bind(...parsed.params)
|
|
136
|
+
const resolved = await resolveSqlRequest(request, env);
|
|
137
|
+
if (!resolved.ok)
|
|
138
|
+
return resolved.response;
|
|
139
|
+
const result = await resolved.db
|
|
140
|
+
.prepare(resolved.sql)
|
|
141
|
+
.bind(...resolved.params)
|
|
230
142
|
.all();
|
|
231
143
|
const rows = result.results ?? [];
|
|
232
144
|
return json(200, { rows, rowCount: rows.length });
|
|
@@ -238,21 +150,12 @@ const handleQuery = async (request, env) => {
|
|
|
238
150
|
};
|
|
239
151
|
const handleQueryOne = async (request, env) => {
|
|
240
152
|
try {
|
|
241
|
-
const
|
|
242
|
-
if (!
|
|
243
|
-
return
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const parsed = parseSqlPayload(check.payload);
|
|
248
|
-
if (!parsed.ok)
|
|
249
|
-
return parsed.response;
|
|
250
|
-
const limit = enforceSqlLimits(env, parsed.sql, parsed.params);
|
|
251
|
-
if (limit !== null)
|
|
252
|
-
return limit;
|
|
253
|
-
const row = await db
|
|
254
|
-
.prepare(parsed.sql)
|
|
255
|
-
.bind(...parsed.params)
|
|
153
|
+
const resolved = await resolveSqlRequest(request, env);
|
|
154
|
+
if (!resolved.ok)
|
|
155
|
+
return resolved.response;
|
|
156
|
+
const row = await resolved.db
|
|
157
|
+
.prepare(resolved.sql)
|
|
158
|
+
.bind(...resolved.params)
|
|
256
159
|
.first();
|
|
257
160
|
return json(200, { row: row ?? null });
|
|
258
161
|
}
|
|
@@ -263,21 +166,12 @@ const handleQueryOne = async (request, env) => {
|
|
|
263
166
|
};
|
|
264
167
|
const handleExec = async (request, env) => {
|
|
265
168
|
try {
|
|
266
|
-
const
|
|
267
|
-
if (!
|
|
268
|
-
return
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const parsed = parseSqlPayload(check.payload);
|
|
273
|
-
if (!parsed.ok)
|
|
274
|
-
return parsed.response;
|
|
275
|
-
const limit = enforceSqlLimits(env, parsed.sql, parsed.params);
|
|
276
|
-
if (limit !== null)
|
|
277
|
-
return limit;
|
|
278
|
-
const out = await db
|
|
279
|
-
.prepare(parsed.sql)
|
|
280
|
-
.bind(...parsed.params)
|
|
169
|
+
const resolved = await resolveSqlRequest(request, env);
|
|
170
|
+
if (!resolved.ok)
|
|
171
|
+
return resolved.response;
|
|
172
|
+
const out = await resolved.db
|
|
173
|
+
.prepare(resolved.sql)
|
|
174
|
+
.bind(...resolved.params)
|
|
281
175
|
.run();
|
|
282
176
|
return json(200, { ok: true, meta: out.meta });
|
|
283
177
|
}
|
|
@@ -302,17 +196,14 @@ const parseStatementPayload = (payload) => {
|
|
|
302
196
|
};
|
|
303
197
|
const handleStatement = async (request, env) => {
|
|
304
198
|
try {
|
|
305
|
-
const
|
|
306
|
-
if (!
|
|
307
|
-
return
|
|
308
|
-
const db = requireDb(env);
|
|
309
|
-
if (db instanceof Response)
|
|
310
|
-
return db;
|
|
199
|
+
const resolved = await resolveDbRequest(request, env);
|
|
200
|
+
if (!resolved.ok)
|
|
201
|
+
return resolved.response;
|
|
311
202
|
const statements = loadStatements(env);
|
|
312
203
|
if (statements === null) {
|
|
313
204
|
return toErrorResponse(400, 'CONFIG_ERROR', 'Missing or invalid ZT_D1_STATEMENTS_JSON');
|
|
314
205
|
}
|
|
315
|
-
const parsed = parseStatementPayload(
|
|
206
|
+
const parsed = parseStatementPayload(resolved.payload);
|
|
316
207
|
if (!parsed.ok)
|
|
317
208
|
return parsed.response;
|
|
318
209
|
const sql = statements[parsed.statementId];
|
|
@@ -320,13 +211,13 @@ const handleStatement = async (request, env) => {
|
|
|
320
211
|
return toErrorResponse(404, 'NOT_FOUND', 'Unknown statementId');
|
|
321
212
|
}
|
|
322
213
|
if (isMutatingSql(sql)) {
|
|
323
|
-
const out = await db
|
|
214
|
+
const out = await resolved.db
|
|
324
215
|
.prepare(sql)
|
|
325
216
|
.bind(...parsed.params)
|
|
326
217
|
.run();
|
|
327
218
|
return json(200, { ok: true, meta: out.meta });
|
|
328
219
|
}
|
|
329
|
-
const out = await db
|
|
220
|
+
const out = await resolved.db
|
|
330
221
|
.prepare(sql)
|
|
331
222
|
.bind(...parsed.params)
|
|
332
223
|
.all();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ZintrustKvProxy.d.ts","sourceRoot":"","sources":["../../../../src/proxy/kv/ZintrustKvProxy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ZintrustKvProxy.d.ts","sourceRoot":"","sources":["../../../../src/proxy/kv/ZintrustKvProxy.ts"],"names":[],"mappings":"AAUA,KAAK,qBAAqB,GAAG;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC;AAEjD,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,OAAO,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;IACpF,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,EAAE,CAAC,OAAO,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CAChG,CAAC;AAEF,KAAK,KAAK,GAAG;IACX,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAwOF,eAAO,MAAM,eAAe;;;mBAGL,OAAO,OAAO,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC;EAqB5D,CAAC;AAEH,eAAe,eAAe,CAAC"}
|