@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.
Files changed (48) hide show
  1. package/package.json +1 -1
  2. package/src/cache/Cache.d.ts.map +1 -1
  3. package/src/cache/Cache.js +3 -0
  4. package/src/cache/drivers/KVRemoteDriver.d.ts +11 -0
  5. package/src/cache/drivers/KVRemoteDriver.d.ts.map +1 -0
  6. package/src/cache/drivers/KVRemoteDriver.js +70 -0
  7. package/src/common/RemoteSignedJson.d.ts +21 -0
  8. package/src/common/RemoteSignedJson.d.ts.map +1 -0
  9. package/src/common/RemoteSignedJson.js +86 -0
  10. package/src/config/cache.d.ts +4 -0
  11. package/src/config/cache.d.ts.map +1 -1
  12. package/src/config/cache.js +4 -0
  13. package/src/config/env.d.ts +10 -0
  14. package/src/config/env.d.ts.map +1 -1
  15. package/src/config/env.js +12 -0
  16. package/src/config/index.d.ts +4 -0
  17. package/src/config/index.d.ts.map +1 -1
  18. package/src/config/type.d.ts +6 -1
  19. package/src/config/type.d.ts.map +1 -1
  20. package/src/index.d.ts +72 -1
  21. package/src/index.d.ts.map +1 -1
  22. package/src/index.js +26 -1
  23. package/src/node.d.ts +5 -1
  24. package/src/node.d.ts.map +1 -1
  25. package/src/node.js +5 -1
  26. package/src/orm/Database.d.ts.map +1 -1
  27. package/src/orm/Database.js +3 -0
  28. package/src/orm/DatabaseAdapter.d.ts +1 -1
  29. package/src/orm/DatabaseAdapter.d.ts.map +1 -1
  30. package/src/orm/adapters/D1RemoteAdapter.d.ts +11 -0
  31. package/src/orm/adapters/D1RemoteAdapter.d.ts.map +1 -0
  32. package/src/orm/adapters/D1RemoteAdapter.js +162 -0
  33. package/src/runtime/PluginRegistry.d.ts.map +1 -1
  34. package/src/runtime/PluginRegistry.js +124 -56
  35. package/src/security/SignedRequest.d.ts +53 -0
  36. package/src/security/SignedRequest.d.ts.map +1 -0
  37. package/src/security/SignedRequest.js +172 -0
  38. package/src/templates/project/basic/config/cache.ts.tpl +4 -0
  39. package/src/templates/project/basic/config/env.ts.tpl +15 -0
  40. package/src/templates/project/basic/config/type.ts.tpl +11 -5
  41. package/src/tools/mail/Mail.d.ts.map +1 -1
  42. package/src/tools/mail/Mail.js +4 -22
  43. package/src/tools/storage/StorageDriverRegistry.d.ts +16 -0
  44. package/src/tools/storage/StorageDriverRegistry.d.ts.map +1 -0
  45. package/src/tools/storage/StorageDriverRegistry.js +20 -0
  46. package/src/tools/storage/index.d.ts +1 -5
  47. package/src/tools/storage/index.d.ts.map +1 -1
  48. 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
+ };
@@ -48,6 +48,10 @@ const cacheConfigObj = {
48
48
  driver: 'kv' as const,
49
49
  ttl: Env.getInt('CACHE_KV_TTL', 3600),
50
50
  },
51
+ 'kv-remote': {
52
+ driver: 'kv-remote' as const,
53
+ ttl: Env.getInt('CACHE_KV_TTL', 3600),
54
+ },
51
55
  },
52
56
 
53
57
  /**
@@ -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: Middleware[];
207
- route: Record<string, Middleware>;
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":"AAQA,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;AA6IF,eAAO,MAAM,IAAI;gBACG,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;EAqBzD,CAAC;AAEH,eAAe,IAAI,CAAC"}
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"}
@@ -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
- // External drivers can register via MailDriverRegistry (e.g., nodemailer)
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: typeof LocalDriver | typeof S3Driver | typeof R2Driver | typeof GcsDriver;
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":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAGxD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,CAAC;AAErD,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,OAAO,WAAW,GAAG,OAAO,QAAQ,GAAG,OAAO,QAAQ,GAAG,OAAO,SAAS,CAAC;IAClF,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;cAqBnB,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
+ {"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 { R2Driver } from './drivers/R2.js';
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
- if (driverName === 'local')
44
+ const driverName = String(config['driver'] ?? '')
45
+ .trim()
46
+ .toLowerCase();
47
+ if (driverName === 'local') {
48
48
  return { driver: LocalDriver, config: normalizeDiskConfig('local', config) };
49
- if (driverName === 's3')
50
- return { driver: S3Driver, config: normalizeDiskConfig('s3', config) };
51
- if (driverName === 'r2')
52
- return { driver: R2Driver, config: normalizeDiskConfig('r2', config) };
53
- if (driverName === 'gcs')
54
- return { driver: GcsDriver, config: normalizeDiskConfig('gcs', config) };
55
- throw ErrorFactory.createValidationError('Storage: unsupported disk driver', {
56
- driver: config['driver'],
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 Promise.resolve(driver.exists(d.config, path)));
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 Promise.resolve(driver.delete(d.config, path));
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 Promise.resolve(driver.tempUrl(d.config, path, options));
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() === '') {