@zintrust/core 0.4.31 → 0.4.33

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/README.md CHANGED
@@ -30,6 +30,21 @@ If you’re targeting a different runtime:
30
30
 
31
31
  The canonical CLI is `zin`. `z` is a shorthand alias.
32
32
 
33
+ ## Reuse The Stock Bootstrap
34
+
35
+ If your project needs a `src/boot/bootstrap.ts` entry for Docker, Node, or custom startup layout, you do not need to copy the full ZinTrust bootstrap implementation into your app.
36
+
37
+ Use a thin bootstrap file that delegates to the published core boot entrypoint:
38
+
39
+ ```typescript
40
+ // src/boot/bootstrap.ts
41
+ import '@zintrust/core/boot';
42
+
43
+ export {};
44
+ ```
45
+
46
+ Use a full custom bootstrap only when you intentionally need behavior different from the stock ZinTrust bootstrap. If you replace it completely, you are also responsible for running any worker startup lifecycle that your app depends on.
47
+
33
48
  ## Install adapters (database/cache/etc.)
34
49
 
35
50
  ZinTrust keeps the core package minimal. Integrations like database drivers are installed explicitly via adapter packages.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zintrust/core",
3
- "version": "0.4.31",
3
+ "version": "0.4.33",
4
4
  "description": "Production-grade TypeScript backend framework for JavaScript",
5
5
  "homepage": "https://zintrust.com",
6
6
  "repository": {
@@ -22,6 +22,10 @@
22
22
  "types": "./src/start.d.ts",
23
23
  "import": "./src/start.js"
24
24
  },
25
+ "./boot": {
26
+ "types": "./src/boot.d.ts",
27
+ "import": "./src/boot.js"
28
+ },
25
29
  "./cli": {
26
30
  "types": "./src/cli.d.ts",
27
31
  "import": "./src/cli.js"
package/src/boot.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import './boot/bootstrap';
2
+ export {};
3
+ //# sourceMappingURL=boot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boot.d.ts","sourceRoot":"","sources":["../../src/boot.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,CAAC;AAEzB,OAAO,EAAE,CAAC"}
package/src/boot.js ADDED
@@ -0,0 +1 @@
1
+ import './boot/bootstrap.js';
@@ -1 +1 @@
1
- {"version":3,"file":"D1ProxyCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/D1ProxyCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AA6PrE,eAAO,MAAM,cAAc;cACf,YAAY;EAwCtB,CAAC;AAEH,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"D1ProxyCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/D1ProxyCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAmHrD,eAAO,MAAM,cAAc;cACf,YAAY;EAkBtB,CAAC;AAEH,eAAe,cAAc,CAAC"}
@@ -1,12 +1,7 @@
1
- import { BaseCommand } from '../BaseCommand.js';
2
- import { maybeRunProxyWatchMode } from '../commands/ProxyCommandUtils.js';
3
- import { SpawnUtil } from '../utils/spawn.js';
1
+ import { findQuotedValue, trimNonEmptyOption } from '../commands/ProxyScaffoldUtils.js';
2
+ import { addWranglerProxyBaseOptions, createWranglerProxyCommand, } from '../commands/WranglerProxyCommandUtils.js';
4
3
  import { Env } from '../../config/env.js';
5
4
  import { Logger } from '../../config/logger.js';
6
- import { ErrorFactory } from '../../exceptions/ZintrustError.js';
7
- import { isNonEmptyString } from '../../helper/index.js';
8
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from '../../node-singletons/fs.js';
9
- import { join } from '../../node-singletons/path.js';
10
5
  const DEFAULT_CONFIG = 'wrangler.jsonc';
11
6
  const DEFAULT_COMPATIBILITY_DATE = '2026-03-12';
12
7
  const DEFAULT_BINDING = 'ZIN_DB';
@@ -14,76 +9,25 @@ const DEFAULT_DATABASE_NAME = 'd1-proxy-db';
14
9
  const DEFAULT_DATABASE_ID = '<your-d1-database-id>';
15
10
  const DEFAULT_MIGRATIONS_DIR = 'database/migrations/d1';
16
11
  const DEFAULT_ENTRY_FILE = 'src/proxy/d1/ZintrustD1Proxy.ts';
12
+ const DEFAULT_ROUTE_PATTERN = 'd1-proxy.example.com';
17
13
  const CORE_PROXY_MODULE = ['@zintrust', 'core', 'proxy'].join('/');
18
- const trimOption = (value) => {
19
- if (!isNonEmptyString(value))
20
- return undefined;
21
- const trimmed = value.trim();
22
- return trimmed.length > 0 ? trimmed : undefined;
23
- };
24
- const resolveConfigPath = (raw) => {
25
- const trimmed = trimOption(raw);
26
- return trimmed ?? DEFAULT_CONFIG;
27
- };
28
- const isJsonWhitespace = (char) => {
29
- return char === ' ' || char === '\n' || char === '\r' || char === '\t';
30
- };
31
- const findJsonKeyValueStart = (content, key) => {
32
- const keyPosition = content.indexOf(`"${key}"`);
33
- if (keyPosition < 0)
34
- return -1;
35
- let cursor = keyPosition + key.length + 2;
36
- while (isJsonWhitespace(content[cursor]))
37
- cursor++;
38
- if (content[cursor] !== ':')
39
- return -1;
40
- cursor++;
41
- while (isJsonWhitespace(content[cursor]))
42
- cursor++;
43
- return cursor;
44
- };
45
- const findQuotedValue = (content, key) => {
46
- const valueStart = findJsonKeyValueStart(content, key);
47
- if (valueStart < 0 || content[valueStart] !== '"')
48
- return undefined;
49
- const valueEnd = content.indexOf('"', valueStart + 1);
50
- if (valueEnd < 0)
51
- return undefined;
52
- return trimOption(content.slice(valueStart + 1, valueEnd));
53
- };
54
- const hasEnvBlock = (content, envName) => {
55
- const valueStart = findJsonKeyValueStart(content, envName);
56
- return valueStart >= 0 && content[valueStart] === '{';
57
- };
58
- const findEnvObjectStart = (content) => {
59
- const valueStart = findJsonKeyValueStart(content, 'env');
60
- if (valueStart < 0 || content[valueStart] !== '{')
61
- return -1;
62
- return valueStart;
63
- };
64
- const isObjectEffectivelyEmpty = (content, objectStart) => {
65
- let cursor = objectStart + 1;
66
- while (isJsonWhitespace(content[cursor]))
67
- cursor++;
68
- return content[cursor] === '}';
69
- };
70
14
  const resolveConfigValues = (content, options) => {
71
15
  const fileContent = content ?? '';
72
16
  return {
73
- binding: trimOption(options.binding) ??
74
- trimOption(Env.get('D1_BINDING', '')) ??
17
+ binding: trimNonEmptyOption(options.binding) ??
18
+ trimNonEmptyOption(Env.get('D1_BINDING', '')) ??
75
19
  findQuotedValue(fileContent, 'D1_BINDING') ??
76
20
  findQuotedValue(fileContent, 'binding') ??
77
21
  DEFAULT_BINDING,
78
- databaseName: trimOption(options.databaseName) ??
79
- trimOption(Env.get('D1_DATABASE_NAME', '')) ??
22
+ databaseName: trimNonEmptyOption(options.databaseName) ??
23
+ trimNonEmptyOption(Env.get('D1_DATABASE_NAME', '')) ??
80
24
  findQuotedValue(fileContent, 'database_name') ??
81
25
  DEFAULT_DATABASE_NAME,
82
- databaseId: trimOption(options.databaseId) ??
83
- trimOption(Env.get('D1_DATABASE_ID', '')) ??
26
+ databaseId: trimNonEmptyOption(options.databaseId) ??
27
+ trimNonEmptyOption(Env.get('D1_DATABASE_ID', '')) ??
84
28
  findQuotedValue(fileContent, 'database_id') ??
85
29
  DEFAULT_DATABASE_ID,
86
- migrationsDir: trimOption(options.migrationsDir) ??
30
+ migrationsDir: trimNonEmptyOption(options.migrationsDir) ??
87
31
  findQuotedValue(fileContent, 'migrations_dir') ??
88
32
  DEFAULT_MIGRATIONS_DIR,
89
33
  };
@@ -111,81 +55,19 @@ const renderD1ProxyEnvBlock = (values) => {
111
55
  ` "database_id": "${values.databaseId}",`,
112
56
  ` "migrations_dir": "${values.migrationsDir}"`,
113
57
  ' }',
114
- ' ]',
58
+ ' ],',
59
+ ' // Add routes here when ready:',
60
+ ` // "routes": [{ "pattern": "${DEFAULT_ROUTE_PATTERN}", "custom_domain": true }]`,
115
61
  ' }',
116
62
  ].join('\n');
117
63
  };
118
- const renderDefaultWranglerConfig = (values) => {
119
- return [
120
- '{',
121
- ' "name": "zintrust-api",',
122
- ' "main": "./src/functions/cloudflare.ts",',
123
- ` "compatibility_date": "${DEFAULT_COMPATIBILITY_DATE}",`,
124
- ' "compatibility_flags": ["nodejs_compat"],',
125
- ' "env": {',
126
- renderD1ProxyEnvBlock(values),
127
- ' }',
128
- '}',
129
- '',
130
- ].join('\n');
131
- };
132
- const ensureProxyEntrypoint = (cwd) => {
133
- const entryFilePath = join(cwd, DEFAULT_ENTRY_FILE);
134
- if (existsSync(entryFilePath)) {
135
- return { created: false, entryFilePath };
136
- }
137
- mkdirSync(join(cwd, 'src/proxy/d1'), { recursive: true });
138
- writeFileSync(entryFilePath, [
139
- `export { ZintrustD1Proxy } from '${CORE_PROXY_MODULE}';`,
140
- `export { ZintrustD1Proxy as default } from '${CORE_PROXY_MODULE}';`,
141
- '',
142
- ].join('\n'), 'utf-8');
143
- return { created: true, entryFilePath };
144
- };
145
- const injectEnvBlock = (content, block) => {
146
- if (hasEnvBlock(content, 'd1-proxy'))
147
- return content;
148
- const envObjectStart = findEnvObjectStart(content);
149
- if (envObjectStart >= 0 && isObjectEffectivelyEmpty(content, envObjectStart)) {
150
- const closingBraceIndex = content.indexOf('}', envObjectStart);
151
- if (closingBraceIndex >= 0) {
152
- return `${content.slice(0, envObjectStart)}{\n${block}\n }${content.slice(closingBraceIndex + 1)}`;
153
- }
154
- }
155
- if (envObjectStart >= 0) {
156
- return `${content.slice(0, envObjectStart + 1)}\n${block},${content.slice(envObjectStart + 1)}`;
157
- }
158
- const closingIndex = content.lastIndexOf('}');
159
- if (closingIndex < 0) {
160
- throw ErrorFactory.createCliError('Invalid wrangler.jsonc: missing closing brace.');
161
- }
162
- const before = content.slice(0, closingIndex).trimEnd();
163
- const suffix = before.endsWith('{') ? '\n' : ',\n';
164
- return `${before}${suffix} "env": {\n${block}\n }\n}\n`;
165
- };
166
- const ensureWranglerConfig = (configPath, options) => {
167
- if (!existsSync(configPath)) {
168
- const values = resolveConfigValues(undefined, options);
169
- writeFileSync(configPath, renderDefaultWranglerConfig(values), 'utf-8');
170
- return { createdFile: true, insertedEnv: true, values };
171
- }
172
- const content = readFileSync(configPath, 'utf-8');
173
- const values = resolveConfigValues(content, options);
174
- const next = injectEnvBlock(content, renderD1ProxyEnvBlock(values));
175
- if (next !== content) {
176
- writeFileSync(configPath, next, 'utf-8');
177
- return { createdFile: false, insertedEnv: true, values };
178
- }
179
- return { createdFile: false, insertedEnv: false, values };
180
- };
181
64
  const warnOnPlaceholderDatabaseId = (values) => {
182
65
  if (values.databaseId !== DEFAULT_DATABASE_ID)
183
66
  return;
184
67
  Logger.warn('Could not resolve a D1 database id automatically. Update wrangler.jsonc or pass --database-id before relying on the generated d1-proxy environment.');
185
68
  };
186
69
  const addOptions = (command) => {
187
- command.option('-c, --config <path>', 'Wrangler config file', DEFAULT_CONFIG);
188
- command.option('--watch', 'Auto-restart proxy on file changes');
70
+ addWranglerProxyBaseOptions(command, DEFAULT_CONFIG);
189
71
  command.option('--binding <name>', 'D1 binding name', DEFAULT_BINDING);
190
72
  command.option('--database-name <name>', 'Cloudflare D1 database name');
191
73
  command.option('--database-id <id>', 'Cloudflare D1 database id');
@@ -193,37 +75,20 @@ const addOptions = (command) => {
193
75
  };
194
76
  export const D1ProxyCommand = Object.freeze({
195
77
  create() {
196
- return BaseCommand.create({
78
+ return createWranglerProxyCommand({
197
79
  name: 'proxy:d1',
198
80
  aliases: ['d1:proxy'],
199
81
  description: 'Start the local Cloudflare D1 proxy Worker via Wrangler and scaffold env.d1-proxy in wrangler.jsonc when missing',
82
+ envName: 'd1-proxy',
83
+ defaultConfig: DEFAULT_CONFIG,
84
+ compatibilityDate: DEFAULT_COMPATIBILITY_DATE,
85
+ entryFile: DEFAULT_ENTRY_FILE,
86
+ exportName: 'ZintrustD1Proxy',
87
+ moduleSpecifier: CORE_PROXY_MODULE,
200
88
  addOptions,
201
- execute: async (options) => {
202
- await maybeRunProxyWatchMode(options.watch);
203
- const cwd = process.cwd();
204
- const entrypoint = ensureProxyEntrypoint(cwd);
205
- const configPath = join(cwd, resolveConfigPath(options.config));
206
- const result = ensureWranglerConfig(configPath, options);
207
- if (entrypoint.created) {
208
- Logger.info(`Created ${entrypoint.entryFilePath} from @zintrust/core proxy entrypoint.`);
209
- }
210
- if (result.createdFile) {
211
- Logger.info(`Created ${configPath} with a default d1-proxy environment.`);
212
- }
213
- else if (result.insertedEnv) {
214
- Logger.info(`Added env.d1-proxy to ${configPath}.`);
215
- }
216
- warnOnPlaceholderDatabaseId(result.values);
217
- const exitCode = await SpawnUtil.spawnAndWait({
218
- command: 'wrangler',
219
- args: ['dev', '--config', configPath, '--env', 'd1-proxy'],
220
- env: process.env,
221
- forwardSignals: false,
222
- });
223
- if (exitCode !== 0) {
224
- process.exit(exitCode);
225
- }
226
- },
89
+ resolveValues: resolveConfigValues,
90
+ renderEnvBlock: renderD1ProxyEnvBlock,
91
+ afterConfigResolved: warnOnPlaceholderDatabaseId,
227
92
  });
228
93
  },
229
94
  });
@@ -1 +1 @@
1
- {"version":3,"file":"KvProxyCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/KvProxyCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AA0OrE,eAAO,MAAM,cAAc;cACf,YAAY;EAwCtB,CAAC;AAEH,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"KvProxyCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/KvProxyCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAmGrD,eAAO,MAAM,cAAc;cACf,YAAY;EAkBtB,CAAC;AAEH,eAAe,cAAc,CAAC"}
@@ -1,83 +1,30 @@
1
- import { BaseCommand } from '../BaseCommand.js';
2
- import { maybeRunProxyWatchMode } from '../commands/ProxyCommandUtils.js';
3
- import { SpawnUtil } from '../utils/spawn.js';
1
+ import { findQuotedValue, trimNonEmptyOption } from '../commands/ProxyScaffoldUtils.js';
2
+ import { addWranglerProxyBaseOptions, createWranglerProxyCommand, } from '../commands/WranglerProxyCommandUtils.js';
4
3
  import { Env } from '../../config/env.js';
5
4
  import { Logger } from '../../config/logger.js';
6
- import { ErrorFactory } from '../../exceptions/ZintrustError.js';
7
- import { isNonEmptyString } from '../../helper/index.js';
8
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from '../../node-singletons/fs.js';
9
- import { join } from '../../node-singletons/path.js';
10
5
  const DEFAULT_CONFIG = 'wrangler.jsonc';
11
6
  const DEFAULT_COMPATIBILITY_DATE = '2026-03-12';
12
7
  const DEFAULT_BINDING = 'ZIN_KV';
13
8
  const DEFAULT_NAMESPACE_ID = '<your-kv-namespace-id>';
14
9
  const DEFAULT_ENTRY_FILE = 'src/proxy/kv/ZintrustKvProxy.ts';
10
+ const DEFAULT_ROUTE_PATTERN = 'kv-proxy.example.com';
15
11
  const CORE_PROXY_MODULE = ['@zintrust', 'core', 'proxy'].join('/');
16
- const trimOption = (value) => {
17
- if (!isNonEmptyString(value))
18
- return undefined;
19
- const trimmed = value.trim();
20
- return trimmed.length > 0 ? trimmed : undefined;
21
- };
22
- const resolveConfigPath = (raw) => trimOption(raw) ?? DEFAULT_CONFIG;
23
- const isJsonWhitespace = (char) => {
24
- return char === ' ' || char === '\n' || char === '\r' || char === '\t';
25
- };
26
- const findJsonKeyValueStart = (content, key) => {
27
- const keyPosition = content.indexOf(`"${key}"`);
28
- if (keyPosition < 0)
29
- return -1;
30
- let cursor = keyPosition + key.length + 2;
31
- while (isJsonWhitespace(content[cursor]))
32
- cursor++;
33
- if (content[cursor] !== ':')
34
- return -1;
35
- cursor++;
36
- while (isJsonWhitespace(content[cursor]))
37
- cursor++;
38
- return cursor;
39
- };
40
- const findQuotedValue = (content, key) => {
41
- const valueStart = findJsonKeyValueStart(content, key);
42
- if (valueStart < 0 || content[valueStart] !== '"')
43
- return undefined;
44
- const valueEnd = content.indexOf('"', valueStart + 1);
45
- if (valueEnd < 0)
46
- return undefined;
47
- return trimOption(content.slice(valueStart + 1, valueEnd));
48
- };
49
- const hasEnvBlock = (content, envName) => {
50
- const valueStart = findJsonKeyValueStart(content, envName);
51
- return valueStart >= 0 && content[valueStart] === '{';
52
- };
53
- const findEnvObjectStart = (content) => {
54
- const valueStart = findJsonKeyValueStart(content, 'env');
55
- if (valueStart < 0 || content[valueStart] !== '{')
56
- return -1;
57
- return valueStart;
58
- };
59
- const isObjectEffectivelyEmpty = (content, objectStart) => {
60
- let cursor = objectStart + 1;
61
- while (isJsonWhitespace(content[cursor]))
62
- cursor++;
63
- return content[cursor] === '}';
64
- };
65
12
  const resolveConfigValues = (content, options) => {
66
13
  const fileContent = content ?? '';
67
- const namespaceId = trimOption(options.namespaceId) ??
68
- trimOption(Env.get('KV_NAMESPACE_ID', '')) ??
14
+ const namespaceId = trimNonEmptyOption(options.namespaceId) ??
15
+ trimNonEmptyOption(Env.get('KV_NAMESPACE_ID', '')) ??
69
16
  findQuotedValue(fileContent, 'preview_id') ??
70
17
  findQuotedValue(fileContent, 'id') ??
71
18
  DEFAULT_NAMESPACE_ID;
72
19
  return {
73
- binding: trimOption(options.binding) ??
74
- trimOption(Env.get('KV_NAMESPACE', '')) ??
20
+ binding: trimNonEmptyOption(options.binding) ??
21
+ trimNonEmptyOption(Env.get('KV_NAMESPACE', '')) ??
75
22
  findQuotedValue(fileContent, 'KV_NAMESPACE') ??
76
23
  findQuotedValue(fileContent, 'binding') ??
77
24
  DEFAULT_BINDING,
78
25
  namespaceId,
79
- previewId: trimOption(options.previewId) ??
80
- trimOption(Env.get('KV_NAMESPACE_PREVIEW_ID', '')) ??
26
+ previewId: trimNonEmptyOption(options.previewId) ??
27
+ trimNonEmptyOption(Env.get('KV_NAMESPACE_PREVIEW_ID', '')) ??
81
28
  findQuotedValue(fileContent, 'preview_id') ??
82
29
  namespaceId,
83
30
  };
@@ -100,118 +47,39 @@ const renderKvProxyEnvBlock = (values) => {
100
47
  ` "preview_id": "${values.previewId}",`,
101
48
  ' "remote": false',
102
49
  ' }',
103
- ' ]',
50
+ ' ],',
51
+ ' // Add routes here when ready:',
52
+ ` // "routes": [{ "pattern": "${DEFAULT_ROUTE_PATTERN}", "custom_domain": true }]`,
104
53
  ' }',
105
54
  ].join('\n');
106
55
  };
107
- const renderDefaultWranglerConfig = (values) => {
108
- return [
109
- '{',
110
- ' "name": "zintrust-api",',
111
- ' "main": "./src/functions/cloudflare.ts",',
112
- ` "compatibility_date": "${DEFAULT_COMPATIBILITY_DATE}",`,
113
- ' "compatibility_flags": ["nodejs_compat"],',
114
- ' "env": {',
115
- renderKvProxyEnvBlock(values),
116
- ' }',
117
- '}',
118
- '',
119
- ].join('\n');
120
- };
121
- const ensureProxyEntrypoint = (cwd) => {
122
- const entryFilePath = join(cwd, DEFAULT_ENTRY_FILE);
123
- if (existsSync(entryFilePath)) {
124
- return { created: false, entryFilePath };
125
- }
126
- mkdirSync(join(cwd, 'src/proxy/kv'), { recursive: true });
127
- writeFileSync(entryFilePath, [
128
- `export { ZintrustKvProxy } from '${CORE_PROXY_MODULE}';`,
129
- `export { ZintrustKvProxy as default } from '${CORE_PROXY_MODULE}';`,
130
- '',
131
- ].join('\n'), 'utf-8');
132
- return { created: true, entryFilePath };
133
- };
134
- const injectEnvBlock = (content, block) => {
135
- if (hasEnvBlock(content, 'kv-proxy'))
136
- return content;
137
- const envObjectStart = findEnvObjectStart(content);
138
- if (envObjectStart >= 0 && isObjectEffectivelyEmpty(content, envObjectStart)) {
139
- const closingBraceIndex = content.indexOf('}', envObjectStart);
140
- if (closingBraceIndex >= 0) {
141
- return `${content.slice(0, envObjectStart)}{\n${block}\n }${content.slice(closingBraceIndex + 1)}`;
142
- }
143
- }
144
- if (envObjectStart >= 0) {
145
- return `${content.slice(0, envObjectStart + 1)}\n${block},${content.slice(envObjectStart + 1)}`;
146
- }
147
- const closingIndex = content.lastIndexOf('}');
148
- if (closingIndex < 0) {
149
- throw ErrorFactory.createCliError('Invalid wrangler.jsonc: missing closing brace.');
150
- }
151
- const before = content.slice(0, closingIndex).trimEnd();
152
- const suffix = before.endsWith('{') ? '\n' : ',\n';
153
- return `${before}${suffix} "env": {\n${block}\n }\n}\n`;
154
- };
155
- const ensureWranglerConfig = (configPath, options) => {
156
- if (!existsSync(configPath)) {
157
- const values = resolveConfigValues(undefined, options);
158
- writeFileSync(configPath, renderDefaultWranglerConfig(values), 'utf-8');
159
- return { createdFile: true, insertedEnv: true, values };
160
- }
161
- const content = readFileSync(configPath, 'utf-8');
162
- const values = resolveConfigValues(content, options);
163
- const next = injectEnvBlock(content, renderKvProxyEnvBlock(values));
164
- if (next !== content) {
165
- writeFileSync(configPath, next, 'utf-8');
166
- return { createdFile: false, insertedEnv: true, values };
167
- }
168
- return { createdFile: false, insertedEnv: false, values };
169
- };
170
56
  const warnOnPlaceholderNamespaceId = (values) => {
171
57
  if (values.namespaceId !== DEFAULT_NAMESPACE_ID)
172
58
  return;
173
59
  Logger.warn('Could not resolve a KV namespace id automatically. Update wrangler.jsonc or pass --namespace-id before relying on the generated kv-proxy environment.');
174
60
  };
175
61
  const addOptions = (command) => {
176
- command.option('-c, --config <path>', 'Wrangler config file', DEFAULT_CONFIG);
177
- command.option('--watch', 'Auto-restart proxy on file changes');
62
+ addWranglerProxyBaseOptions(command, DEFAULT_CONFIG);
178
63
  command.option('--binding <name>', 'KV binding name', DEFAULT_BINDING);
179
64
  command.option('--namespace-id <id>', 'Cloudflare KV namespace id');
180
65
  command.option('--preview-id <id>', 'Cloudflare KV preview namespace id');
181
66
  };
182
67
  export const KvProxyCommand = Object.freeze({
183
68
  create() {
184
- return BaseCommand.create({
69
+ return createWranglerProxyCommand({
185
70
  name: 'proxy:kv',
186
71
  aliases: ['kv:proxy'],
187
72
  description: 'Start the local Cloudflare KV proxy Worker via Wrangler and scaffold env.kv-proxy in wrangler.jsonc when missing',
73
+ envName: 'kv-proxy',
74
+ defaultConfig: DEFAULT_CONFIG,
75
+ compatibilityDate: DEFAULT_COMPATIBILITY_DATE,
76
+ entryFile: DEFAULT_ENTRY_FILE,
77
+ exportName: 'ZintrustKvProxy',
78
+ moduleSpecifier: CORE_PROXY_MODULE,
188
79
  addOptions,
189
- execute: async (options) => {
190
- await maybeRunProxyWatchMode(options.watch);
191
- const cwd = process.cwd();
192
- const entrypoint = ensureProxyEntrypoint(cwd);
193
- const configPath = join(cwd, resolveConfigPath(options.config));
194
- const result = ensureWranglerConfig(configPath, options);
195
- if (entrypoint.created) {
196
- Logger.info(`Created ${entrypoint.entryFilePath} from @zintrust/core proxy entrypoint.`);
197
- }
198
- if (result.createdFile) {
199
- Logger.info(`Created ${configPath} with a default kv-proxy environment.`);
200
- }
201
- else if (result.insertedEnv) {
202
- Logger.info(`Added env.kv-proxy to ${configPath}.`);
203
- }
204
- warnOnPlaceholderNamespaceId(result.values);
205
- const exitCode = await SpawnUtil.spawnAndWait({
206
- command: 'wrangler',
207
- args: ['dev', '--config', configPath, '--env', 'kv-proxy'],
208
- env: process.env,
209
- forwardSignals: false,
210
- });
211
- if (exitCode !== 0) {
212
- process.exit(exitCode);
213
- }
214
- },
80
+ resolveValues: resolveConfigValues,
81
+ renderEnvBlock: renderKvProxyEnvBlock,
82
+ afterConfigResolved: warnOnPlaceholderNamespaceId,
215
83
  });
216
84
  },
217
85
  });
@@ -0,0 +1,31 @@
1
+ type EnsureProxyEntrypointOptions = {
2
+ cwd: string;
3
+ entryFile: string;
4
+ exportName: string;
5
+ moduleSpecifier: string;
6
+ };
7
+ type EnsureWranglerConfigOptions<TValues, TOptions> = {
8
+ configPath: string;
9
+ options: TOptions;
10
+ envName: string;
11
+ resolveValues: (content: string | undefined, options: TOptions) => TValues;
12
+ renderEnvBlock: (values: TValues) => string;
13
+ compatibilityDate: string;
14
+ };
15
+ type EnsureWranglerConfigResult<TValues> = {
16
+ createdFile: boolean;
17
+ insertedEnv: boolean;
18
+ values: TValues;
19
+ };
20
+ export declare const trimNonEmptyOption: (value: string | undefined) => string | undefined;
21
+ export declare const resolveConfigPath: (raw: string | undefined, fallback?: string) => string;
22
+ export declare const findQuotedValue: (content: string, key: string) => string | undefined;
23
+ export declare const injectEnvBlock: (content: string, envName: string, block: string) => string;
24
+ export declare const renderDefaultWranglerConfig: (envBlock: string, compatibilityDate: string) => string;
25
+ export declare const ensureProxyEntrypoint: (options: EnsureProxyEntrypointOptions) => {
26
+ created: boolean;
27
+ entryFilePath: string;
28
+ };
29
+ export declare const ensureWranglerConfig: <TValues, TOptions>(options: EnsureWranglerConfigOptions<TValues, TOptions>) => EnsureWranglerConfigResult<TValues>;
30
+ export {};
31
+ //# sourceMappingURL=ProxyScaffoldUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProxyScaffoldUtils.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/ProxyScaffoldUtils.ts"],"names":[],"mappings":"AAKA,KAAK,4BAA4B,GAAG;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,KAAK,2BAA2B,CAAC,OAAO,EAAE,QAAQ,IAAI;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,QAAQ,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC;IAC3E,cAAc,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,MAAM,CAAC;IAC5C,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,KAAK,0BAA0B,CAAC,OAAO,IAAI;IACzC,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,SAIvE,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,GAAG,SAAS,EAAE,iBAA2B,KAAG,MAExF,CAAC;AAmBF,eAAO,MAAM,eAAe,GAAI,SAAS,MAAM,EAAE,KAAK,MAAM,KAAG,MAAM,GAAG,SAQvE,CAAC;AAmBF,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,EAAE,SAAS,MAAM,EAAE,OAAO,MAAM,KAAG,MAuBhF,CAAC;AAEF,eAAO,MAAM,2BAA2B,GACtC,UAAU,MAAM,EAChB,mBAAmB,MAAM,KACxB,MAaF,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,SAAS,4BAA4B,KACpC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAoB3C,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,OAAO,EAAE,QAAQ,EACpD,SAAS,2BAA2B,CAAC,OAAO,EAAE,QAAQ,CAAC,KACtD,0BAA0B,CAAC,OAAO,CAqBpC,CAAC"}
@@ -0,0 +1,120 @@
1
+ import { ErrorFactory } from '../../exceptions/ZintrustError.js';
2
+ import { isNonEmptyString } from '../../helper/index.js';
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from '../../node-singletons/fs.js';
4
+ import { join } from '../../node-singletons/path.js';
5
+ export const trimNonEmptyOption = (value) => {
6
+ if (!isNonEmptyString(value))
7
+ return undefined;
8
+ const trimmed = value.trim();
9
+ return trimmed.length > 0 ? trimmed : undefined;
10
+ };
11
+ export const resolveConfigPath = (raw, fallback = 'wrangler.jsonc') => {
12
+ return trimNonEmptyOption(raw) ?? fallback;
13
+ };
14
+ const isJsonWhitespace = (char) => {
15
+ return char === ' ' || char === '\n' || char === '\r' || char === '\t';
16
+ };
17
+ const findJsonKeyValueStart = (content, key) => {
18
+ const keyPosition = content.indexOf(`"${key}"`);
19
+ if (keyPosition < 0)
20
+ return -1;
21
+ let cursor = keyPosition + key.length + 2;
22
+ while (isJsonWhitespace(content[cursor]))
23
+ cursor++;
24
+ if (content[cursor] !== ':')
25
+ return -1;
26
+ cursor++;
27
+ while (isJsonWhitespace(content[cursor]))
28
+ cursor++;
29
+ return cursor;
30
+ };
31
+ export const findQuotedValue = (content, key) => {
32
+ const valueStart = findJsonKeyValueStart(content, key);
33
+ if (valueStart < 0 || content[valueStart] !== '"')
34
+ return undefined;
35
+ const valueEnd = content.indexOf('"', valueStart + 1);
36
+ if (valueEnd < 0)
37
+ return undefined;
38
+ return trimNonEmptyOption(content.slice(valueStart + 1, valueEnd));
39
+ };
40
+ const hasEnvBlock = (content, envName) => {
41
+ const valueStart = findJsonKeyValueStart(content, envName);
42
+ return valueStart >= 0 && content[valueStart] === '{';
43
+ };
44
+ const findEnvObjectStart = (content) => {
45
+ const valueStart = findJsonKeyValueStart(content, 'env');
46
+ if (valueStart < 0 || content[valueStart] !== '{')
47
+ return -1;
48
+ return valueStart;
49
+ };
50
+ const isObjectEffectivelyEmpty = (content, objectStart) => {
51
+ let cursor = objectStart + 1;
52
+ while (isJsonWhitespace(content[cursor]))
53
+ cursor++;
54
+ return content[cursor] === '}';
55
+ };
56
+ export const injectEnvBlock = (content, envName, block) => {
57
+ if (hasEnvBlock(content, envName))
58
+ return content;
59
+ const envObjectStart = findEnvObjectStart(content);
60
+ if (envObjectStart >= 0 && isObjectEffectivelyEmpty(content, envObjectStart)) {
61
+ const closingBraceIndex = content.indexOf('}', envObjectStart);
62
+ if (closingBraceIndex >= 0) {
63
+ return `${content.slice(0, envObjectStart)}{\n${block}\n }${content.slice(closingBraceIndex + 1)}`;
64
+ }
65
+ }
66
+ if (envObjectStart >= 0) {
67
+ return `${content.slice(0, envObjectStart + 1)}\n${block},${content.slice(envObjectStart + 1)}`;
68
+ }
69
+ const closingIndex = content.lastIndexOf('}');
70
+ if (closingIndex < 0) {
71
+ throw ErrorFactory.createCliError('Invalid wrangler.jsonc: missing closing brace.');
72
+ }
73
+ const before = content.slice(0, closingIndex).trimEnd();
74
+ const suffix = before.endsWith('{') ? '\n' : ',\n';
75
+ return `${before}${suffix} "env": {\n${block}\n }\n}\n`;
76
+ };
77
+ export const renderDefaultWranglerConfig = (envBlock, compatibilityDate) => {
78
+ return [
79
+ '{',
80
+ ' "name": "zintrust-api",',
81
+ ' "main": "./src/functions/cloudflare.ts",',
82
+ ` "compatibility_date": "${compatibilityDate}",`,
83
+ ' "compatibility_flags": ["nodejs_compat"],',
84
+ ' "env": {',
85
+ envBlock,
86
+ ' }',
87
+ '}',
88
+ '',
89
+ ].join('\n');
90
+ };
91
+ export const ensureProxyEntrypoint = (options) => {
92
+ const entryFilePath = join(options.cwd, options.entryFile);
93
+ if (existsSync(entryFilePath)) {
94
+ return { created: false, entryFilePath };
95
+ }
96
+ const lastSlashIndex = entryFilePath.lastIndexOf('/');
97
+ const entryDir = lastSlashIndex > 0 ? entryFilePath.slice(0, lastSlashIndex) : options.cwd;
98
+ mkdirSync(entryDir, { recursive: true });
99
+ writeFileSync(entryFilePath, [
100
+ `export { ${options.exportName} } from '${options.moduleSpecifier}';`,
101
+ `export { ${options.exportName} as default } from '${options.moduleSpecifier}';`,
102
+ '',
103
+ ].join('\n'), 'utf-8');
104
+ return { created: true, entryFilePath };
105
+ };
106
+ export const ensureWranglerConfig = (options) => {
107
+ if (!existsSync(options.configPath)) {
108
+ const values = options.resolveValues(undefined, options.options);
109
+ writeFileSync(options.configPath, renderDefaultWranglerConfig(options.renderEnvBlock(values), options.compatibilityDate), 'utf-8');
110
+ return { createdFile: true, insertedEnv: true, values };
111
+ }
112
+ const content = readFileSync(options.configPath, 'utf-8');
113
+ const values = options.resolveValues(content, options.options);
114
+ const next = injectEnvBlock(content, options.envName, options.renderEnvBlock(values));
115
+ if (next !== content) {
116
+ writeFileSync(options.configPath, next, 'utf-8');
117
+ return { createdFile: false, insertedEnv: true, values };
118
+ }
119
+ return { createdFile: false, insertedEnv: false, values };
120
+ };
@@ -0,0 +1,26 @@
1
+ import type { CommandOptions, IBaseCommand } from '../BaseCommand';
2
+ import type { Command } from 'commander';
3
+ export type WranglerProxyCommandOptions = CommandOptions & {
4
+ config?: string;
5
+ port?: string;
6
+ watch?: boolean;
7
+ };
8
+ type CreateWranglerProxyCommandInput<TValues, TOptions extends WranglerProxyCommandOptions> = {
9
+ name: string;
10
+ aliases: string[];
11
+ description: string;
12
+ envName: string;
13
+ defaultConfig: string;
14
+ compatibilityDate: string;
15
+ entryFile: string;
16
+ exportName: string;
17
+ moduleSpecifier: string;
18
+ addOptions: (command: Command) => void;
19
+ resolveValues: (content: string | undefined, options: TOptions) => TValues;
20
+ renderEnvBlock: (values: TValues) => string;
21
+ afterConfigResolved?: (values: TValues) => void;
22
+ };
23
+ export declare const addWranglerProxyBaseOptions: (command: Command, defaultConfig: string) => void;
24
+ export declare const createWranglerProxyCommand: <TValues, TOptions extends WranglerProxyCommandOptions>(input: CreateWranglerProxyCommandInput<TValues, TOptions>) => IBaseCommand;
25
+ export {};
26
+ //# sourceMappingURL=WranglerProxyCommandUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WranglerProxyCommandUtils.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/WranglerProxyCommandUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAWrE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,MAAM,MAAM,2BAA2B,GAAG,cAAc,GAAG;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,KAAK,+BAA+B,CAAC,OAAO,EAAE,QAAQ,SAAS,2BAA2B,IAAI;IAC5F,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC;IAC3E,cAAc,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,MAAM,CAAC;IAC5C,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CACjD,CAAC;AAEF,eAAO,MAAM,2BAA2B,GAAI,SAAS,OAAO,EAAE,eAAe,MAAM,KAAG,IAIrF,CAAC;AAEF,eAAO,MAAM,0BAA0B,GAAI,OAAO,EAAE,QAAQ,SAAS,2BAA2B,EAC9F,OAAO,+BAA+B,CAAC,OAAO,EAAE,QAAQ,CAAC,KACxD,YA0DF,CAAC"}
@@ -0,0 +1,63 @@
1
+ import { BaseCommand } from '../BaseCommand.js';
2
+ import { maybeRunProxyWatchMode, parseIntOption } from '../commands/ProxyCommandUtils.js';
3
+ import { ensureProxyEntrypoint, ensureWranglerConfig, resolveConfigPath, } from '../commands/ProxyScaffoldUtils.js';
4
+ import { SpawnUtil } from '../utils/spawn.js';
5
+ import { Logger } from '../../config/logger.js';
6
+ import { join } from '../../node-singletons/path.js';
7
+ export const addWranglerProxyBaseOptions = (command, defaultConfig) => {
8
+ command.option('-c, --config <path>', 'Wrangler config file', defaultConfig);
9
+ command.option('--port <port>', 'Local Wrangler dev port');
10
+ command.option('--watch', 'Auto-restart proxy on file changes');
11
+ };
12
+ export const createWranglerProxyCommand = (input) => {
13
+ return BaseCommand.create({
14
+ name: input.name,
15
+ aliases: input.aliases,
16
+ description: input.description,
17
+ addOptions: input.addOptions,
18
+ execute: async (options) => {
19
+ const typedOptions = options;
20
+ await maybeRunProxyWatchMode(typedOptions.watch);
21
+ const port = parseIntOption(typedOptions.port, 'port');
22
+ const cwd = process.cwd();
23
+ const entrypoint = ensureProxyEntrypoint({
24
+ cwd,
25
+ entryFile: input.entryFile,
26
+ exportName: input.exportName,
27
+ moduleSpecifier: input.moduleSpecifier,
28
+ });
29
+ const configPath = join(cwd, resolveConfigPath(typedOptions.config, input.defaultConfig));
30
+ const result = ensureWranglerConfig({
31
+ configPath,
32
+ options: typedOptions,
33
+ envName: input.envName,
34
+ resolveValues: input.resolveValues,
35
+ renderEnvBlock: input.renderEnvBlock,
36
+ compatibilityDate: input.compatibilityDate,
37
+ });
38
+ if (entrypoint.created) {
39
+ Logger.info(`Created ${entrypoint.entryFilePath} from @zintrust/core proxy entrypoint.`);
40
+ }
41
+ if (result.createdFile) {
42
+ Logger.info(`Created ${configPath} with a default ${input.envName} environment.`);
43
+ }
44
+ else if (result.insertedEnv) {
45
+ Logger.info(`Added env.${input.envName} to ${configPath}.`);
46
+ }
47
+ input.afterConfigResolved?.(result.values);
48
+ const args = ['dev', '--config', configPath, '--env', input.envName];
49
+ if (port !== undefined) {
50
+ args.push('--port', String(port));
51
+ }
52
+ const exitCode = await SpawnUtil.spawnAndWait({
53
+ command: 'wrangler',
54
+ args,
55
+ env: process.env,
56
+ forwardSignals: false,
57
+ });
58
+ if (exitCode !== 0) {
59
+ process.exit(exitCode);
60
+ }
61
+ },
62
+ });
63
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"GovernanceScaffolder.d.ts","sourceRoot":"","sources":["../../../../src/cli/scaffolding/GovernanceScaffolder.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAoBH,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AA8UH,eAAO,MAAM,oBAAoB;0BAEhB,MAAM,YACV,yBAAyB,GACjC,OAAO,CAAC,wBAAwB,CAAC;EA+DpC,CAAC;AAEH,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"GovernanceScaffolder.d.ts","sourceRoot":"","sources":["../../../../src/cli/scaffolding/GovernanceScaffolder.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAqBH,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAsSH,eAAO,MAAM,oBAAoB;0BAEhB,MAAM,YACV,yBAAyB,GACjC,OAAO,CAAC,wBAAwB,CAAC;EA+DpC,CAAC;AAEH,eAAe,oBAAoB,CAAC"}
@@ -6,6 +6,7 @@
6
6
  * - Architecture tests (Vitest)
7
7
  */
8
8
  import { FileGenerator } from '../scaffolding/FileGenerator.js';
9
+ import { toCompatibleGovernanceVersion } from '../scaffolding/ScaffoldingVersionUtils.js';
9
10
  import { VersionChecker } from '../services/VersionChecker.js';
10
11
  import { SpawnUtil } from '../utils/spawn.js';
11
12
  import { resolvePackageManager } from '../../common/index.js';
@@ -50,53 +51,15 @@ const ensureDevDependency = (devDependencies, name, version) => {
50
51
  devDependencies[name] = version;
51
52
  }
52
53
  };
53
- const extractMajorMinorVersion = (value) => {
54
- const trimmed = value.trim();
55
- let start = -1;
56
- for (let index = 0; index < trimmed.length; index++) {
57
- const char = trimmed[index];
58
- if (char !== undefined && char >= '0' && char <= '9') {
59
- start = index;
60
- break;
61
- }
62
- }
63
- if (start < 0)
64
- return undefined;
65
- let end = start;
66
- while (end < trimmed.length) {
67
- const char = trimmed[end];
68
- const isDigit = char !== undefined && char >= '0' && char <= '9';
69
- if (!isDigit && char !== '.')
70
- break;
71
- end++;
72
- }
73
- const parts = trimmed.slice(start, end).split('.');
74
- if (parts.length < 3)
75
- return undefined;
76
- const [major, minor, patch] = parts;
77
- if (!major || !minor || !patch)
78
- return undefined;
79
- return { major, minor };
80
- };
81
- const toCompatibleGovernanceVersion = (value) => {
82
- const parsed = extractMajorMinorVersion(value);
83
- if (parsed === undefined)
84
- return undefined;
85
- return `^${parsed.major}.${parsed.minor}.0`;
86
- };
87
54
  const inferGovernanceVersion = (pkg) => {
88
55
  const deps = getStringRecord(pkg.dependencies);
89
56
  const core = deps?.['@zintrust/core'];
90
57
  if (typeof core === 'string' && core.trim() !== '') {
91
- const compatibleFromCore = toCompatibleGovernanceVersion(core);
92
- if (compatibleFromCore !== undefined)
93
- return compatibleFromCore;
58
+ return toCompatibleGovernanceVersion(core);
94
59
  }
95
60
  const currentVersion = VersionChecker.getCurrentVersion().trim();
96
61
  if (currentVersion !== '' && currentVersion !== '0.0.0') {
97
- const compatibleFromCli = toCompatibleGovernanceVersion(currentVersion);
98
- if (compatibleFromCli !== undefined)
99
- return compatibleFromCli;
62
+ return toCompatibleGovernanceVersion(currentVersion);
100
63
  }
101
64
  return '^0.4.0';
102
65
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectScaffolder.d.ts","sourceRoot":"","sources":["../../../../src/cli/scaffolding/ProjectScaffolder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GAAG,sBAAsB,CAAC;AAEpD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,cAAc,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACtD,YAAY,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,eAAe,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAAC;IACpE,cAAc,IAAI,MAAM,CAAC;IACzB,sBAAsB,IAAI,OAAO,CAAC;IAClC,iBAAiB,IAAI,MAAM,CAAC;IAC5B,WAAW,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAAC;IACtD,gBAAgB,IAAI,OAAO,CAAC;IAC5B,aAAa,IAAI,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;CAC3E;AA0fD,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAEhD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAsBrE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG;IAChE,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAsBA;AA8ID;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,GAAE,MAAsB,GAAG,kBAAkB,CAsB/F;AAED,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAEhC;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;EAM5B,CAAC"}
1
+ {"version":3,"file":"ProjectScaffolder.d.ts","sourceRoot":"","sources":["../../../../src/cli/scaffolding/ProjectScaffolder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAYH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GAAG,sBAAsB,CAAC;AAEpD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,cAAc,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACtD,YAAY,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,eAAe,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAAC;IACpE,cAAc,IAAI,MAAM,CAAC;IACzB,sBAAsB,IAAI,OAAO,CAAC;IAClC,iBAAiB,IAAI,MAAM,CAAC;IAC5B,WAAW,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAAC;IACtD,gBAAgB,IAAI,OAAO,CAAC;IAC5B,aAAa,IAAI,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;CAC3E;AAodD,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAEhD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAsBrE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG;IAChE,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAsBA;AA8ID;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,GAAE,MAAsB,GAAG,kBAAkB,CAsB/F;AAED,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAEhC;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;EAM5B,CAAC"}
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { EnvFileBackfill } from '../env/EnvFileBackfill.js';
6
6
  import { EnvData } from '../scaffolding/env.js';
7
+ import { toCompatibleGovernanceVersion } from '../scaffolding/ScaffoldingVersionUtils.js';
7
8
  import { Logger } from '../../config/logger.js';
8
9
  import { ErrorFactory } from '../../exceptions/ZintrustError.js';
9
10
  import { randomBytes } from '../../node-singletons/crypto.js';
@@ -20,40 +21,6 @@ const loadCoreVersion = () => {
20
21
  return '0.0.0';
21
22
  }
22
23
  };
23
- const extractMajorMinorVersion = (value) => {
24
- const trimmed = value.trim();
25
- let start = -1;
26
- for (let index = 0; index < trimmed.length; index++) {
27
- const char = trimmed[index];
28
- if (char !== undefined && char >= '0' && char <= '9') {
29
- start = index;
30
- break;
31
- }
32
- }
33
- if (start < 0)
34
- return undefined;
35
- let end = start;
36
- while (end < trimmed.length) {
37
- const char = trimmed[end];
38
- const isDigit = char !== undefined && char >= '0' && char <= '9';
39
- if (!isDigit && char !== '.')
40
- break;
41
- end++;
42
- }
43
- const parts = trimmed.slice(start, end).split('.');
44
- if (parts.length < 3)
45
- return undefined;
46
- const [major, minor, patch] = parts;
47
- if (!major || !minor || !patch)
48
- return undefined;
49
- return { major, minor };
50
- };
51
- const toCompatibleGovernanceVersion = (version) => {
52
- const parsed = extractMajorMinorVersion(version);
53
- if (parsed !== undefined)
54
- return `^${parsed.major}.${parsed.minor}.0`;
55
- return '^0.4.0';
56
- };
57
24
  const createDirectories = (projectPath, directories) => {
58
25
  let count = 0;
59
26
  if (!fs.existsSync(projectPath)) {
@@ -0,0 +1,6 @@
1
+ export declare const extractMajorMinorVersion: (value: string) => {
2
+ major: string;
3
+ minor: string;
4
+ } | undefined;
5
+ export declare const toCompatibleGovernanceVersion: (value: string, fallback?: string) => string;
6
+ //# sourceMappingURL=ScaffoldingVersionUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScaffoldingVersionUtils.d.ts","sourceRoot":"","sources":["../../../../src/cli/scaffolding/ScaffoldingVersionUtils.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,wBAAwB,GACnC,OAAO,MAAM,KACZ;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SA6BrC,CAAC;AAEF,eAAO,MAAM,6BAA6B,GAAI,OAAO,MAAM,EAAE,iBAAmB,KAAG,MAKlF,CAAC"}
@@ -0,0 +1,34 @@
1
+ export const extractMajorMinorVersion = (value) => {
2
+ const trimmed = value.trim();
3
+ let start = -1;
4
+ for (let index = 0; index < trimmed.length; index++) {
5
+ const char = trimmed[index];
6
+ if (char !== undefined && char >= '0' && char <= '9') {
7
+ start = index;
8
+ break;
9
+ }
10
+ }
11
+ if (start < 0)
12
+ return undefined;
13
+ let end = start;
14
+ while (end < trimmed.length) {
15
+ const char = trimmed[end];
16
+ const isDigit = char !== undefined && char >= '0' && char <= '9';
17
+ if (!isDigit && char !== '.')
18
+ break;
19
+ end++;
20
+ }
21
+ const parts = trimmed.slice(start, end).split('.');
22
+ if (parts.length < 3)
23
+ return undefined;
24
+ const [major, minor, patch] = parts;
25
+ if (!major || !minor || !patch)
26
+ return undefined;
27
+ return { major, minor };
28
+ };
29
+ export const toCompatibleGovernanceVersion = (value, fallback = '^0.4.0') => {
30
+ const parsed = extractMajorMinorVersion(value);
31
+ if (parsed === undefined)
32
+ return fallback;
33
+ return `^${parsed.major}.${parsed.minor}.0`;
34
+ };
@@ -1,6 +1,6 @@
1
1
  import type { MiddlewareConfigType } from './type';
2
- import type { Middleware } from '../middleware/MiddlewareStack';
3
2
  import type { MiddlewareFailureResponder } from '../middleware/MiddlewareFailureResponder';
3
+ import type { Middleware } from '../middleware/MiddlewareStack';
4
4
  type SharedMiddlewares = {
5
5
  log: Middleware;
6
6
  error: Middleware;
@@ -68,7 +68,10 @@ export declare const MiddlewareKeys: Readonly<{
68
68
  validateUserUpdate: true;
69
69
  validateUserFill: true;
70
70
  }>;
71
- export type MiddlewareKey = keyof typeof MiddlewareKeys;
71
+ export type ParameterizedRateLimitMiddlewareKey = `rateLimit:${number}:${number}`;
72
+ type StaticMiddlewareKey = keyof typeof MiddlewareKeys;
73
+ export type MiddlewareKey = StaticMiddlewareKey | ParameterizedRateLimitMiddlewareKey;
74
+ export declare const isKnownMiddlewareName: (value: string) => value is MiddlewareKey;
72
75
  export declare function createMiddlewareConfig(): MiddlewareConfigType;
73
76
  /**
74
77
  * Clear the middleware configuration cache
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/config/middleware.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAUzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAK9D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,wCAAwC,CAAC;AAgCzF,KAAK,iBAAiB,GAAG;IACvB,GAAG,EAAE,UAAU,CAAC;IAChB,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,UAAU,CAAC;IACrB,SAAS,EAAE,UAAU,CAAC;IACtB,YAAY,EAAE,UAAU,CAAC;IACzB,aAAa,EAAE,UAAU,CAAC;IAC1B,aAAa,EAAE,UAAU,CAAC;IAC1B,qBAAqB,EAAE,UAAU,CAAC;IAClC,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,GAAG,EAAE,UAAU,CAAC;IAChB,WAAW,EAAE,UAAU,CAAC;IACxB,aAAa,EAAE,UAAU,CAAC;IAC1B,gBAAgB,EAAE,UAAU,CAAC;IAC7B,iBAAiB,EAAE,UAAU,CAAC;IAC9B,kBAAkB,EAAE,UAAU,CAAC;IAC/B,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAAC;AAEF,KAAK,sBAAsB,GAAG,MAAM,iBAAiB,CAAC;AACtD,KAAK,yBAAyB,GAAG,OAAO,CACtC,MAAM,CAAC,sBAAsB,EAAE,0BAA0B,CAAC,CAC3D,CAAC;AAEF,eAAO,MAAM,cAAc;;;;;CAKjB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACjC,aAAa,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,aAAa,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,qBAAqB,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1E,UAAU,CAAC,EAAE,yBAAyB,CAAC;IACvC,MAAM,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACpC,CAAC;AAEF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;EAkBuB,CAAC;AAEnD,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,cAAc,CAAC;AA2RxD,wBAAgB,sBAAsB,IAAI,oBAAoB,CA0C7D;AAID;;;GAGG;AACH,eAAO,MAAM,0BAA0B,QAAO,IAE7C,CAAC;AAwBF,eAAO,MAAM,gBAAgB,EAAE,oBAY7B,CAAC;AAEH,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/config/middleware.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAUzD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,wCAAwC,CAAC;AACzF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAoC9D,KAAK,iBAAiB,GAAG;IACvB,GAAG,EAAE,UAAU,CAAC;IAChB,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,UAAU,CAAC;IACrB,SAAS,EAAE,UAAU,CAAC;IACtB,YAAY,EAAE,UAAU,CAAC;IACzB,aAAa,EAAE,UAAU,CAAC;IAC1B,aAAa,EAAE,UAAU,CAAC;IAC1B,qBAAqB,EAAE,UAAU,CAAC;IAClC,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,GAAG,EAAE,UAAU,CAAC;IAChB,WAAW,EAAE,UAAU,CAAC;IACxB,aAAa,EAAE,UAAU,CAAC;IAC1B,gBAAgB,EAAE,UAAU,CAAC;IAC7B,iBAAiB,EAAE,UAAU,CAAC;IAC9B,kBAAkB,EAAE,UAAU,CAAC;IAC/B,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAAC;AAEF,KAAK,sBAAsB,GAAG,MAAM,iBAAiB,CAAC;AACtD,KAAK,yBAAyB,GAAG,OAAO,CACtC,MAAM,CAAC,sBAAsB,EAAE,0BAA0B,CAAC,CAC3D,CAAC;AAEF,eAAO,MAAM,cAAc;;;;;CAKjB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACjC,aAAa,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,aAAa,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,qBAAqB,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1E,UAAU,CAAC,EAAE,yBAAyB,CAAC;IACvC,MAAM,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACpC,CAAC;AAEF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;EAkBuB,CAAC;AAEnD,MAAM,MAAM,mCAAmC,GAAG,aAAa,MAAM,IAAI,MAAM,EAAE,CAAC;AAElF,KAAK,mBAAmB,GAAG,MAAM,OAAO,cAAc,CAAC;AAEvD,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAAG,mCAAmC,CAAC;AAyBtF,eAAO,MAAM,qBAAqB,GAAI,OAAO,MAAM,KAAG,KAAK,IAAI,aAI9D,CAAC;AAuUF,wBAAgB,sBAAsB,IAAI,oBAAoB,CA4C7D;AAID;;;GAGG;AACH,eAAO,MAAM,0BAA0B,QAAO,IAE7C,CAAC;AAwBF,eAAO,MAAM,gBAAgB,EAAE,oBAY7B,CAAC;AAEH,eAAe,gBAAgB,CAAC"}
@@ -40,6 +40,24 @@ export const MiddlewareKeys = Object.freeze({
40
40
  validateUserUpdate: true,
41
41
  validateUserFill: true,
42
42
  });
43
+ const parseParameterizedRateLimitKey = (value) => {
44
+ const match = /^rateLimit:(\d+):(\d+(?:\.\d+)?)$/.exec(value.trim());
45
+ if (!match)
46
+ return undefined;
47
+ const max = Number.parseInt(match[1] ?? '', 10);
48
+ const windowMinutes = Number.parseFloat(match[2] ?? '');
49
+ if (!Number.isInteger(max) || max < 1)
50
+ return undefined;
51
+ if (!Number.isFinite(windowMinutes) || windowMinutes <= 0)
52
+ return undefined;
53
+ const windowMs = Math.round(windowMinutes * 60_000);
54
+ if (!Number.isFinite(windowMs) || windowMs < 1)
55
+ return undefined;
56
+ return Object.freeze({ max, windowMs });
57
+ };
58
+ export const isKnownMiddlewareName = (value) => {
59
+ return (Object.hasOwn(MiddlewareKeys, value) || parseParameterizedRateLimitKey(value) !== undefined);
60
+ };
43
61
  // Ensure `ValidationMiddleware` is strongly typed here to avoid `error`-typed values
44
62
  // triggering `@typescript-eslint/no-unsafe-*` rules.
45
63
  const Validation = ValidationMiddleware;
@@ -207,6 +225,42 @@ const applySharedMiddlewareOverrides = (shared, projectRoute) => {
207
225
  }
208
226
  return Object.freeze(overridden);
209
227
  };
228
+ const createRouteMiddlewareRegistry = (resolvedShared, projectRoute, responders) => {
229
+ const target = Object.freeze({
230
+ ...resolvedShared,
231
+ ...projectRoute,
232
+ });
233
+ const dynamicRateLimits = new Map();
234
+ return new Proxy(target, {
235
+ get(currentTarget, prop, receiver) {
236
+ if (typeof prop !== 'string') {
237
+ return Reflect.get(currentTarget, prop, receiver);
238
+ }
239
+ const resolved = currentTarget[prop];
240
+ if (typeof resolved === 'function')
241
+ return resolved;
242
+ const cached = dynamicRateLimits.get(prop);
243
+ if (cached !== undefined)
244
+ return cached;
245
+ const parsed = parseParameterizedRateLimitKey(prop);
246
+ if (parsed === undefined)
247
+ return undefined;
248
+ const middleware = RateLimiter.create({
249
+ max: parsed.max,
250
+ windowMs: parsed.windowMs,
251
+ onFailure: responders.rateLimit,
252
+ });
253
+ dynamicRateLimits.set(prop, middleware);
254
+ return middleware;
255
+ },
256
+ ownKeys(currentTarget) {
257
+ return Reflect.ownKeys(currentTarget);
258
+ },
259
+ getOwnPropertyDescriptor(currentTarget, prop) {
260
+ return Reflect.getOwnPropertyDescriptor(currentTarget, prop);
261
+ },
262
+ });
263
+ };
210
264
  export function createMiddlewareConfig() {
211
265
  const loadMiddlewareConfig = StartupConfigFileRegistry.get(StartupConfigFile.Middleware) ?? {};
212
266
  const skipPathsFromEnv = Env.get('CSRF_SKIP_PATHS', '')
@@ -224,6 +278,7 @@ export function createMiddlewareConfig() {
224
278
  const projectGlobal = resolveProjectGlobalMiddlewares(effectiveMiddlewareConfig);
225
279
  const projectRoute = resolveProjectRouteMiddlewares(effectiveMiddlewareConfig);
226
280
  const resolvedShared = applySharedMiddlewareOverrides(shared, projectRoute);
281
+ const routeMiddlewareRegistry = createRouteMiddlewareRegistry(resolvedShared, projectRoute, responders);
227
282
  const middlewareConfigObj = {
228
283
  global: [
229
284
  resolvedShared.log,
@@ -236,10 +291,7 @@ export function createMiddlewareConfig() {
236
291
  resolvedShared.sanitizeBody,
237
292
  ...projectGlobal,
238
293
  ],
239
- route: {
240
- ...resolvedShared,
241
- ...projectRoute,
242
- },
294
+ route: routeMiddlewareRegistry,
243
295
  };
244
296
  return Object.freeze(middlewareConfigObj);
245
297
  }
package/src/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
- * @zintrust/core v0.4.31
2
+ * @zintrust/core v0.4.33
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-29T12:34:52.815Z
8
+ * Built: 2026-03-29T18:23:05.480Z
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-03-29T12:34:52.781Z'; // Replaced during build
24
+ export const ZINTRUST_BUILD_DATE = '2026-03-29T18:23:05.446Z'; // 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';
@@ -89,7 +89,7 @@ const verifySignedRequest = async (request, env, bodyBytes) => {
89
89
  body: bodyBytes,
90
90
  headers: request.headers,
91
91
  windowMs,
92
- getSecretForKeyId: async (_keyId) => secret,
92
+ getSecretForKeyId: (_keyId) => secret,
93
93
  verifyNonce: env.ZT_NONCES === undefined
94
94
  ? undefined
95
95
  : async (keyId, nonce, ttlMs) => verifyNonceKv(env.ZT_NONCES, keyId, nonce, ttlMs),
@@ -0,0 +1,3 @@
1
+ import '@zintrust/core/boot';
2
+
3
+ export {};