@zintrust/core 0.1.11 → 0.1.13
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 +1 -1
- package/src/cache/Cache.d.ts.map +1 -1
- package/src/cache/Cache.js +3 -0
- package/src/cache/drivers/KVRemoteDriver.d.ts +11 -0
- package/src/cache/drivers/KVRemoteDriver.d.ts.map +1 -0
- package/src/cache/drivers/KVRemoteDriver.js +70 -0
- package/src/common/RemoteSignedJson.d.ts +21 -0
- package/src/common/RemoteSignedJson.d.ts.map +1 -0
- package/src/common/RemoteSignedJson.js +86 -0
- package/src/config/cache.d.ts +4 -0
- package/src/config/cache.d.ts.map +1 -1
- package/src/config/cache.js +4 -0
- package/src/config/env.d.ts +10 -0
- package/src/config/env.d.ts.map +1 -1
- package/src/config/env.js +12 -0
- package/src/config/index.d.ts +4 -0
- package/src/config/index.d.ts.map +1 -1
- package/src/config/type.d.ts +6 -1
- package/src/config/type.d.ts.map +1 -1
- package/src/index.d.ts +72 -1
- package/src/index.d.ts.map +1 -1
- package/src/index.js +26 -1
- package/src/node.d.ts +5 -1
- package/src/node.d.ts.map +1 -1
- package/src/node.js +5 -1
- package/src/orm/Database.d.ts.map +1 -1
- package/src/orm/Database.js +3 -0
- package/src/orm/DatabaseAdapter.d.ts +1 -1
- package/src/orm/DatabaseAdapter.d.ts.map +1 -1
- package/src/orm/adapters/D1RemoteAdapter.d.ts +11 -0
- package/src/orm/adapters/D1RemoteAdapter.d.ts.map +1 -0
- package/src/orm/adapters/D1RemoteAdapter.js +162 -0
- package/src/runtime/PluginRegistry.d.ts.map +1 -1
- package/src/runtime/PluginRegistry.js +124 -56
- package/src/security/SignedRequest.d.ts +53 -0
- package/src/security/SignedRequest.d.ts.map +1 -0
- package/src/security/SignedRequest.js +172 -0
- package/src/templates/project/basic/config/cache.ts.tpl +4 -0
- package/src/templates/project/basic/config/env.ts.tpl +15 -0
- package/src/templates/project/basic/config/type.ts.tpl +11 -5
- package/src/tools/mail/Mail.d.ts.map +1 -1
- package/src/tools/mail/Mail.js +4 -22
- package/src/tools/storage/StorageDriverRegistry.d.ts +16 -0
- package/src/tools/storage/StorageDriverRegistry.d.ts.map +1 -0
- package/src/tools/storage/StorageDriverRegistry.js +20 -0
- package/src/tools/storage/index.d.ts +1 -5
- package/src/tools/storage/index.d.ts.map +1 -1
- package/src/tools/storage/index.js +22 -17
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { ErrorFactory } from '../exceptions/ZintrustError.js';
|
|
2
|
+
const getHeader = (headers, name) => {
|
|
3
|
+
if (typeof headers.get === 'function') {
|
|
4
|
+
const value = headers.get(name);
|
|
5
|
+
return value ?? undefined;
|
|
6
|
+
}
|
|
7
|
+
return headers[name];
|
|
8
|
+
};
|
|
9
|
+
const timingSafeEquals = (a, b) => {
|
|
10
|
+
if (a.length !== b.length)
|
|
11
|
+
return false;
|
|
12
|
+
let result = 0;
|
|
13
|
+
for (let i = 0; i < a.length; i++) {
|
|
14
|
+
result |= (a.codePointAt(i) ?? 0) ^ (b.codePointAt(i) ?? 0);
|
|
15
|
+
}
|
|
16
|
+
return result === 0;
|
|
17
|
+
};
|
|
18
|
+
const getCrypto = () => {
|
|
19
|
+
if (typeof crypto === 'undefined' || crypto.subtle === undefined) {
|
|
20
|
+
// Some runtimes (or test environments) may not expose WebCrypto.
|
|
21
|
+
// Keep this as a typed Zintrust error to satisfy lint rules.
|
|
22
|
+
throw ErrorFactory.createSecurityError('WebCrypto is not available in this runtime');
|
|
23
|
+
}
|
|
24
|
+
return crypto;
|
|
25
|
+
};
|
|
26
|
+
const getSubtle = () => getCrypto().subtle;
|
|
27
|
+
const toBytes = (data) => {
|
|
28
|
+
const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
|
|
29
|
+
return new Uint8Array(bytes);
|
|
30
|
+
};
|
|
31
|
+
const toHex = (bytes) => {
|
|
32
|
+
const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
33
|
+
let out = '';
|
|
34
|
+
for (const element of view) {
|
|
35
|
+
out += element.toString(16).padStart(2, '0');
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
};
|
|
39
|
+
const sha256Hex = async (data) => {
|
|
40
|
+
const digest = await getSubtle().digest('SHA-256', toBytes(data));
|
|
41
|
+
return toHex(digest);
|
|
42
|
+
};
|
|
43
|
+
const canonicalString = (params) => {
|
|
44
|
+
const u = typeof params.url === 'string' ? new URL(params.url) : params.url;
|
|
45
|
+
const method = params.method.toUpperCase();
|
|
46
|
+
// Keep URL pieces aligned with plan: pathname + search (including leading '?' or '').
|
|
47
|
+
return [
|
|
48
|
+
method,
|
|
49
|
+
u.pathname,
|
|
50
|
+
u.search,
|
|
51
|
+
String(params.timestampMs),
|
|
52
|
+
params.nonce,
|
|
53
|
+
params.bodySha256Hex,
|
|
54
|
+
].join('\n');
|
|
55
|
+
};
|
|
56
|
+
export const SignedRequest = Object.freeze({
|
|
57
|
+
sha256Hex,
|
|
58
|
+
canonicalString,
|
|
59
|
+
async createHeaders(params) {
|
|
60
|
+
const timestampMs = params.timestampMs ?? Date.now();
|
|
61
|
+
const webCrypto = getCrypto();
|
|
62
|
+
const nonce = params.nonce ??
|
|
63
|
+
(typeof webCrypto.randomUUID === 'function'
|
|
64
|
+
? webCrypto.randomUUID()
|
|
65
|
+
: toHex(webCrypto.getRandomValues(new Uint8Array(16))));
|
|
66
|
+
const body = params.body ?? '';
|
|
67
|
+
const bodySha = await sha256Hex(body);
|
|
68
|
+
const canonical = canonicalString({
|
|
69
|
+
method: params.method,
|
|
70
|
+
url: params.url,
|
|
71
|
+
timestampMs,
|
|
72
|
+
nonce,
|
|
73
|
+
bodySha256Hex: bodySha,
|
|
74
|
+
});
|
|
75
|
+
const subtle = getSubtle();
|
|
76
|
+
const key = await subtle.importKey('raw', toBytes(params.secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
77
|
+
const signatureBytes = await subtle.sign('HMAC', key, toBytes(canonical));
|
|
78
|
+
const signature = toHex(signatureBytes);
|
|
79
|
+
return {
|
|
80
|
+
'x-zt-key-id': params.keyId,
|
|
81
|
+
'x-zt-timestamp': String(timestampMs),
|
|
82
|
+
'x-zt-nonce': nonce,
|
|
83
|
+
'x-zt-body-sha256': bodySha,
|
|
84
|
+
'x-zt-signature': signature,
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
async verify(params) {
|
|
88
|
+
const parsed = parseAndValidateHeaders(params.headers);
|
|
89
|
+
if (parsed.ok === false)
|
|
90
|
+
return parsed;
|
|
91
|
+
const { keyId, timestampMs, nonce, bodySha, signature } = parsed;
|
|
92
|
+
const nowMs = params.nowMs ?? Date.now();
|
|
93
|
+
const windowMs = params.windowMs ?? 60_000;
|
|
94
|
+
const windowCheck = validateTimestampWindow({ nowMs, timestampMs, windowMs });
|
|
95
|
+
if (windowCheck.ok === false)
|
|
96
|
+
return windowCheck;
|
|
97
|
+
const bodyCheck = await validateBodyHash({ body: params.body ?? '', bodyShaHeader: bodySha });
|
|
98
|
+
if (bodyCheck.ok === false)
|
|
99
|
+
return bodyCheck;
|
|
100
|
+
const secret = await params.getSecretForKeyId(keyId);
|
|
101
|
+
if (secret === undefined || secret.trim() === '') {
|
|
102
|
+
return { ok: false, code: 'UNKNOWN_KEY', message: 'Unknown key id' };
|
|
103
|
+
}
|
|
104
|
+
const sigCheck = await validateSignature({
|
|
105
|
+
method: params.method,
|
|
106
|
+
url: params.url,
|
|
107
|
+
timestampMs,
|
|
108
|
+
nonce,
|
|
109
|
+
bodySha,
|
|
110
|
+
signature,
|
|
111
|
+
secret,
|
|
112
|
+
});
|
|
113
|
+
if (sigCheck.ok === false)
|
|
114
|
+
return sigCheck;
|
|
115
|
+
if (params.verifyNonce !== undefined) {
|
|
116
|
+
const ok = await params.verifyNonce(keyId, nonce, windowMs);
|
|
117
|
+
if (ok === false) {
|
|
118
|
+
return { ok: false, code: 'REPLAYED', message: 'Nonce replayed or rejected' };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { ok: true, keyId, timestampMs, nonce };
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
const parseAndValidateHeaders = (headers) => {
|
|
125
|
+
const keyId = getHeader(headers, 'x-zt-key-id');
|
|
126
|
+
const ts = getHeader(headers, 'x-zt-timestamp');
|
|
127
|
+
const nonce = getHeader(headers, 'x-zt-nonce');
|
|
128
|
+
const bodySha = getHeader(headers, 'x-zt-body-sha256');
|
|
129
|
+
const signature = getHeader(headers, 'x-zt-signature');
|
|
130
|
+
if (keyId === undefined ||
|
|
131
|
+
ts === undefined ||
|
|
132
|
+
nonce === undefined ||
|
|
133
|
+
bodySha === undefined ||
|
|
134
|
+
signature === undefined) {
|
|
135
|
+
return { ok: false, code: 'MISSING_HEADER', message: 'Missing required signing headers' };
|
|
136
|
+
}
|
|
137
|
+
const timestampMs = Number.parseInt(ts, 10);
|
|
138
|
+
if (!Number.isFinite(timestampMs)) {
|
|
139
|
+
return { ok: false, code: 'INVALID_TIMESTAMP', message: 'Invalid x-zt-timestamp' };
|
|
140
|
+
}
|
|
141
|
+
return { ok: true, keyId, timestampMs, nonce, bodySha, signature };
|
|
142
|
+
};
|
|
143
|
+
const validateTimestampWindow = (params) => {
|
|
144
|
+
if (Math.abs(params.nowMs - params.timestampMs) > params.windowMs) {
|
|
145
|
+
return { ok: false, code: 'EXPIRED', message: 'Request timestamp outside allowed window' };
|
|
146
|
+
}
|
|
147
|
+
return { ok: true };
|
|
148
|
+
};
|
|
149
|
+
const validateBodyHash = async (params) => {
|
|
150
|
+
const computedBodySha = await sha256Hex(params.body);
|
|
151
|
+
if (!timingSafeEquals(computedBodySha, params.bodyShaHeader)) {
|
|
152
|
+
return { ok: false, code: 'INVALID_BODY_SHA', message: 'Body hash mismatch' };
|
|
153
|
+
}
|
|
154
|
+
return { ok: true };
|
|
155
|
+
};
|
|
156
|
+
const validateSignature = async (params) => {
|
|
157
|
+
const subtle = getSubtle();
|
|
158
|
+
const canonical = canonicalString({
|
|
159
|
+
method: params.method,
|
|
160
|
+
url: params.url,
|
|
161
|
+
timestampMs: params.timestampMs,
|
|
162
|
+
nonce: params.nonce,
|
|
163
|
+
bodySha256Hex: params.bodySha,
|
|
164
|
+
});
|
|
165
|
+
const key = await subtle.importKey('raw', toBytes(params.secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
166
|
+
const expectedBytes = await subtle.sign('HMAC', key, toBytes(canonical));
|
|
167
|
+
const expected = toHex(expectedBytes);
|
|
168
|
+
if (!timingSafeEquals(expected, params.signature)) {
|
|
169
|
+
return { ok: false, code: 'INVALID_SIGNATURE', message: 'Invalid signature' };
|
|
170
|
+
}
|
|
171
|
+
return { ok: true };
|
|
172
|
+
};
|
|
@@ -79,6 +79,21 @@ export const Env = Object.freeze({
|
|
|
79
79
|
D1_DATABASE_ID: get('D1_DATABASE_ID'),
|
|
80
80
|
KV_NAMESPACE_ID: get('KV_NAMESPACE_ID'),
|
|
81
81
|
|
|
82
|
+
// Cloudflare proxy services (D1/KV outside Cloudflare)
|
|
83
|
+
D1_REMOTE_URL: get('D1_REMOTE_URL', ''),
|
|
84
|
+
D1_REMOTE_KEY_ID: get('D1_REMOTE_KEY_ID', ''),
|
|
85
|
+
D1_REMOTE_SECRET: get('D1_REMOTE_SECRET', ''),
|
|
86
|
+
D1_REMOTE_MODE: get('D1_REMOTE_MODE', 'registry'),
|
|
87
|
+
|
|
88
|
+
KV_REMOTE_URL: get('KV_REMOTE_URL', ''),
|
|
89
|
+
KV_REMOTE_KEY_ID: get('KV_REMOTE_KEY_ID', ''),
|
|
90
|
+
KV_REMOTE_SECRET: get('KV_REMOTE_SECRET', ''),
|
|
91
|
+
KV_REMOTE_NAMESPACE: get('KV_REMOTE_NAMESPACE', ''),
|
|
92
|
+
|
|
93
|
+
// Proxy client tuning
|
|
94
|
+
ZT_PROXY_SIGNING_WINDOW_MS: getInt('ZT_PROXY_SIGNING_WINDOW_MS', 60000),
|
|
95
|
+
ZT_PROXY_TIMEOUT_MS: getInt('ZT_PROXY_TIMEOUT_MS', 30000),
|
|
96
|
+
|
|
82
97
|
// Cache
|
|
83
98
|
CACHE_DRIVER: get('CACHE_DRIVER', 'memory'),
|
|
84
99
|
REDIS_HOST: get('REDIS_HOST', 'localhost'),
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { Middleware } from '@zintrust/core';
|
|
2
|
-
|
|
3
1
|
import { Env } from './env';
|
|
2
|
+
import type { Middleware as MiddlewareFn } from '../middleware/MiddlewareStack';
|
|
4
3
|
|
|
5
4
|
export type Environment =
|
|
6
5
|
| 'development'
|
|
@@ -203,8 +202,8 @@ export type NotificationProviders = {
|
|
|
203
202
|
};
|
|
204
203
|
|
|
205
204
|
export type MiddlewareConfigType = {
|
|
206
|
-
global:
|
|
207
|
-
route: Record<string,
|
|
205
|
+
global: MiddlewareFn[];
|
|
206
|
+
route: Record<string, MiddlewareFn>;
|
|
208
207
|
};
|
|
209
208
|
|
|
210
209
|
export type MailDriverName = 'disabled' | 'sendgrid' | 'smtp' | 'ses' | 'mailgun' | 'nodemailer';
|
|
@@ -371,17 +370,24 @@ export type KvCacheDriverConfig = {
|
|
|
371
370
|
ttl: number;
|
|
372
371
|
};
|
|
373
372
|
|
|
373
|
+
export type KvRemoteCacheDriverConfig = {
|
|
374
|
+
driver: 'kv-remote';
|
|
375
|
+
ttl: number;
|
|
376
|
+
};
|
|
377
|
+
|
|
374
378
|
export type CacheDriverConfig =
|
|
375
379
|
| MemoryCacheDriverConfig
|
|
376
380
|
| RedisCacheDriverConfig
|
|
377
381
|
| MongoCacheDriverConfig
|
|
378
|
-
| KvCacheDriverConfig
|
|
382
|
+
| KvCacheDriverConfig
|
|
383
|
+
| KvRemoteCacheDriverConfig;
|
|
379
384
|
|
|
380
385
|
export type CacheDrivers = {
|
|
381
386
|
memory: MemoryCacheDriverConfig;
|
|
382
387
|
redis: RedisCacheDriverConfig;
|
|
383
388
|
mongodb: MongoCacheDriverConfig;
|
|
384
389
|
kv: KvCacheDriverConfig;
|
|
390
|
+
'kv-remote': KvRemoteCacheDriverConfig;
|
|
385
391
|
};
|
|
386
392
|
|
|
387
393
|
export type CacheConfigInput = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Mail.d.ts","sourceRoot":"","sources":["../../../../src/tools/mail/Mail.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Mail.d.ts","sourceRoot":"","sources":["../../../../src/tools/mail/Mail.ts"],"names":[],"mappings":"AAMA,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAI7E,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,GAAG,YAAY,CAAC;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAuHF,eAAO,MAAM,IAAI;gBACG,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;EAqBzD,CAAC;AAEH,eAAe,IAAI,CAAC"}
|
package/src/tools/mail/Mail.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { mailConfig } from '../../config/mail.js';
|
|
2
2
|
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
|
|
3
|
-
import { MailgunDriver } from './drivers/Mailgun.js';
|
|
4
|
-
import { SendGridDriver } from './drivers/SendGrid.js';
|
|
5
3
|
import { SesDriver } from './drivers/Ses.js';
|
|
6
|
-
import { SmtpDriver } from './drivers/Smtp.js';
|
|
7
4
|
import { resolveAttachments } from './attachments.js';
|
|
8
5
|
import { MailDriverRegistry } from './MailDriverRegistry.js';
|
|
9
6
|
import { Storage } from '../storage/index.js';
|
|
@@ -55,29 +52,11 @@ const createStorageWrapper = () => ({
|
|
|
55
52
|
},
|
|
56
53
|
});
|
|
57
54
|
const sendWithDriver = async (driver, message) => {
|
|
58
|
-
if (driver.driver === 'sendgrid') {
|
|
59
|
-
const result = await SendGridDriver.send({ apiKey: driver.apiKey }, message);
|
|
60
|
-
return { ok: result.ok, driver: 'sendgrid', messageId: result.messageId };
|
|
61
|
-
}
|
|
62
|
-
if (driver.driver === 'mailgun') {
|
|
63
|
-
const result = await MailgunDriver.send({ apiKey: driver.apiKey, domain: driver.domain, baseUrl: driver.baseUrl }, message);
|
|
64
|
-
return { ok: result.ok, driver: 'mailgun', messageId: result.messageId };
|
|
65
|
-
}
|
|
66
|
-
if (driver.driver === 'smtp') {
|
|
67
|
-
const result = await SmtpDriver.send({
|
|
68
|
-
host: driver.host,
|
|
69
|
-
port: driver.port,
|
|
70
|
-
username: driver.username,
|
|
71
|
-
password: driver.password,
|
|
72
|
-
secure: driver.secure,
|
|
73
|
-
}, message);
|
|
74
|
-
return { ok: result.ok, driver: 'smtp', messageId: result.messageId };
|
|
75
|
-
}
|
|
76
55
|
if (driver.driver === 'ses') {
|
|
77
56
|
const result = await SesDriver.send({ region: driver.region }, message);
|
|
78
57
|
return { ok: result.ok, driver: 'ses', messageId: result.messageId };
|
|
79
58
|
}
|
|
80
|
-
//
|
|
59
|
+
// Drivers resolve via MailDriverRegistry (external packages)
|
|
81
60
|
const external = MailDriverRegistry.get(driver.driver);
|
|
82
61
|
if (external !== undefined) {
|
|
83
62
|
const result = await external(driver, message);
|
|
@@ -87,6 +66,9 @@ const sendWithDriver = async (driver, message) => {
|
|
|
87
66
|
messageId: typeof result?.messageId === 'string' ? result.messageId : undefined,
|
|
88
67
|
};
|
|
89
68
|
}
|
|
69
|
+
if (driver.driver === 'sendgrid' || driver.driver === 'mailgun' || driver.driver === 'smtp') {
|
|
70
|
+
throw ErrorFactory.createConfigError(`Mail driver not registered: ${driver.driver} (run \`zin add mail:${driver.driver}\` / \`npm i @zintrust/mail-${driver.driver}\`)`);
|
|
71
|
+
}
|
|
90
72
|
// Config exists for future drivers, but implementations are intentionally CLI/runtime-safe and added incrementally.
|
|
91
73
|
{
|
|
92
74
|
const err = ErrorFactory.createConfigError(`Mail driver not implemented: ${mailConfig.default}`);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type StorageDriverEntry = {
|
|
2
|
+
driver: unknown;
|
|
3
|
+
normalize?: (raw: Record<string, unknown>) => Record<string, unknown>;
|
|
4
|
+
};
|
|
5
|
+
declare function register(driverName: string, entry: StorageDriverEntry): void;
|
|
6
|
+
declare function get(driverName: string): StorageDriverEntry | undefined;
|
|
7
|
+
declare function has(driverName: string): boolean;
|
|
8
|
+
declare function list(): string[];
|
|
9
|
+
export declare const StorageDriverRegistry: Readonly<{
|
|
10
|
+
register: typeof register;
|
|
11
|
+
get: typeof get;
|
|
12
|
+
has: typeof has;
|
|
13
|
+
list: typeof list;
|
|
14
|
+
}>;
|
|
15
|
+
export default StorageDriverRegistry;
|
|
16
|
+
//# sourceMappingURL=StorageDriverRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorageDriverRegistry.d.ts","sourceRoot":"","sources":["../../../../src/tools/storage/StorageDriverRegistry.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvE,CAAC;AAIF,iBAAS,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAErE;AAED,iBAAS,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAE/D;AAED,iBAAS,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAExC;AAED,iBAAS,IAAI,IAAI,MAAM,EAAE,CAExB;AAED,eAAO,MAAM,qBAAqB;;;;;EAKhC,CAAC;AAEH,eAAe,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const registry = new Map();
|
|
2
|
+
function register(driverName, entry) {
|
|
3
|
+
registry.set(String(driverName).trim().toLowerCase(), entry);
|
|
4
|
+
}
|
|
5
|
+
function get(driverName) {
|
|
6
|
+
return registry.get(String(driverName).trim().toLowerCase());
|
|
7
|
+
}
|
|
8
|
+
function has(driverName) {
|
|
9
|
+
return registry.has(String(driverName).trim().toLowerCase());
|
|
10
|
+
}
|
|
11
|
+
function list() {
|
|
12
|
+
return Array.from(registry.keys()).sort((a, b) => a.localeCompare(b));
|
|
13
|
+
}
|
|
14
|
+
export const StorageDriverRegistry = Object.freeze({
|
|
15
|
+
register,
|
|
16
|
+
get,
|
|
17
|
+
has,
|
|
18
|
+
list,
|
|
19
|
+
});
|
|
20
|
+
export default StorageDriverRegistry;
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import { GcsDriver } from '../../tools/storage/drivers/Gcs';
|
|
2
|
-
import { LocalDriver } from './drivers/Local';
|
|
3
|
-
import { R2Driver } from './drivers/R2';
|
|
4
|
-
import { S3Driver } from './drivers/S3';
|
|
5
1
|
export type DiskName = 'local' | 's3' | 'gcs' | 'r2';
|
|
6
2
|
export type StorageDisk = {
|
|
7
|
-
driver:
|
|
3
|
+
driver: unknown;
|
|
8
4
|
config: unknown;
|
|
9
5
|
};
|
|
10
6
|
type TempUrlOptions = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/tools/storage/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/tools/storage/index.ts"],"names":[],"mappings":"AAKA,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;AAsCrE,eAAO,MAAM,OAAO;mBACH,MAAM,GAAG,WAAW;cAqCnB,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"}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { GcsDriver } from '../../tools/storage/drivers/Gcs.js';
|
|
2
1
|
import { storageConfig } from '../../config/storage.js';
|
|
3
2
|
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
|
|
4
3
|
import { LocalDriver } from './drivers/Local.js';
|
|
5
|
-
import {
|
|
6
|
-
import { S3Driver } from './drivers/S3.js';
|
|
4
|
+
import { StorageDriverRegistry } from './StorageDriverRegistry.js';
|
|
7
5
|
const normalizers = {
|
|
8
6
|
local: (raw) => ({
|
|
9
7
|
root: String(raw['root'] ?? ''),
|
|
@@ -43,18 +41,25 @@ export const Storage = Object.freeze({
|
|
|
43
41
|
const config = drivers[diskName];
|
|
44
42
|
if (config === undefined)
|
|
45
43
|
throw ErrorFactory.createValidationError('Storage: unknown disk', { disk: diskName });
|
|
46
|
-
const driverName = String(config['driver'] ?? '')
|
|
47
|
-
|
|
44
|
+
const driverName = String(config['driver'] ?? '')
|
|
45
|
+
.trim()
|
|
46
|
+
.toLowerCase();
|
|
47
|
+
if (driverName === 'local') {
|
|
48
48
|
return { driver: LocalDriver, config: normalizeDiskConfig('local', config) };
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
}
|
|
50
|
+
const entry = StorageDriverRegistry.get(driverName);
|
|
51
|
+
if (entry === undefined) {
|
|
52
|
+
if (driverName === 's3' || driverName === 'r2' || driverName === 'gcs') {
|
|
53
|
+
throw ErrorFactory.createConfigError(`Storage driver not registered: ${driverName} (run \`zin add storage:${driverName}\` / \`npm i @zintrust/storage-${driverName}\`)`);
|
|
54
|
+
}
|
|
55
|
+
throw ErrorFactory.createValidationError('Storage: unsupported disk driver', {
|
|
56
|
+
driver: config['driver'],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const normalizedConfig = typeof entry.normalize === 'function'
|
|
60
|
+
? entry.normalize(config)
|
|
61
|
+
: normalizeDiskConfig(driverName, config);
|
|
62
|
+
return { driver: entry.driver, config: normalizedConfig };
|
|
58
63
|
},
|
|
59
64
|
async put(disk, path, contents) {
|
|
60
65
|
const d = Storage.getDisk(disk);
|
|
@@ -77,14 +82,14 @@ export const Storage = Object.freeze({
|
|
|
77
82
|
const driver = d.driver;
|
|
78
83
|
if (typeof driver.exists !== 'function')
|
|
79
84
|
return true;
|
|
80
|
-
return Boolean(await
|
|
85
|
+
return Boolean(await driver.exists(d.config, path));
|
|
81
86
|
},
|
|
82
87
|
async delete(disk, path) {
|
|
83
88
|
const d = Storage.getDisk(disk);
|
|
84
89
|
const driver = d.driver;
|
|
85
90
|
if (typeof driver.delete !== 'function')
|
|
86
91
|
return;
|
|
87
|
-
await
|
|
92
|
+
await driver.delete(d.config, path);
|
|
88
93
|
},
|
|
89
94
|
url(disk, path) {
|
|
90
95
|
const d = Storage.getDisk(disk);
|
|
@@ -99,7 +104,7 @@ export const Storage = Object.freeze({
|
|
|
99
104
|
const d = Storage.getDisk(disk);
|
|
100
105
|
const driver = d.driver;
|
|
101
106
|
if (typeof driver.tempUrl === 'function') {
|
|
102
|
-
return
|
|
107
|
+
return driver.tempUrl(d.config, path, options);
|
|
103
108
|
}
|
|
104
109
|
const url = typeof driver.url === 'function' ? driver.url(d.config, path) : undefined;
|
|
105
110
|
if (typeof url !== 'string' || url.trim() === '') {
|