@zintrust/core 0.1.41 → 0.1.42

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 (193) hide show
  1. package/package.json +17 -1
  2. package/src/boot/bootstrap.js +27 -11
  3. package/src/boot/registry/runtime.d.ts.map +1 -1
  4. package/src/boot/registry/runtime.js +11 -0
  5. package/src/cli/CLI.d.ts.map +1 -1
  6. package/src/cli/CLI.js +12 -0
  7. package/src/cli/commands/ConfigCommand.d.ts.map +1 -1
  8. package/src/cli/commands/ConfigCommand.js +3 -5
  9. package/src/cli/commands/D1LearnCommand.d.ts +9 -0
  10. package/src/cli/commands/D1LearnCommand.d.ts.map +1 -0
  11. package/src/cli/commands/D1LearnCommand.js +143 -0
  12. package/src/cli/commands/D1MigrateCommand.d.ts.map +1 -1
  13. package/src/cli/commands/D1MigrateCommand.js +55 -16
  14. package/src/cli/commands/InitContainerCommand.d.ts.map +1 -1
  15. package/src/cli/commands/InitContainerCommand.js +21 -6
  16. package/src/cli/commands/InitEcosystemCommand.d.ts +6 -0
  17. package/src/cli/commands/InitEcosystemCommand.d.ts.map +1 -0
  18. package/src/cli/commands/InitEcosystemCommand.js +51 -0
  19. package/src/cli/commands/MigrateCommand.d.ts.map +1 -1
  20. package/src/cli/commands/MigrateCommand.js +78 -36
  21. package/src/cli/commands/MigrateWorkerCommand.d.ts.map +1 -1
  22. package/src/cli/commands/MigrateWorkerCommand.js +36 -2
  23. package/src/cli/commands/PutCommand.d.ts +6 -0
  24. package/src/cli/commands/PutCommand.d.ts.map +1 -0
  25. package/src/cli/commands/PutCommand.js +173 -0
  26. package/src/cli/commands/QueueRecoveryCommand.d.ts.map +1 -1
  27. package/src/cli/commands/QueueRecoveryCommand.js +113 -14
  28. package/src/cli/commands/ScheduleListCommand.d.ts +6 -0
  29. package/src/cli/commands/ScheduleListCommand.d.ts.map +1 -0
  30. package/src/cli/commands/ScheduleListCommand.js +62 -0
  31. package/src/cli/commands/ScheduleRunCommand.d.ts +6 -0
  32. package/src/cli/commands/ScheduleRunCommand.d.ts.map +1 -0
  33. package/src/cli/commands/ScheduleRunCommand.js +32 -0
  34. package/src/cli/commands/ScheduleStartCommand.d.ts +6 -0
  35. package/src/cli/commands/ScheduleStartCommand.d.ts.map +1 -0
  36. package/src/cli/commands/ScheduleStartCommand.js +40 -0
  37. package/src/cli/commands/SecretsCommand.d.ts.map +1 -1
  38. package/src/cli/commands/SecretsCommand.js +2 -2
  39. package/src/cli/commands/schedule/ScheduleCliSupport.d.ts +6 -0
  40. package/src/cli/commands/schedule/ScheduleCliSupport.d.ts.map +1 -0
  41. package/src/cli/commands/schedule/ScheduleCliSupport.js +55 -0
  42. package/src/cli/config/ConfigManager.d.ts.map +1 -1
  43. package/src/cli/config/ConfigManager.js +8 -1
  44. package/src/cli/d1/D1SqlMigrations.d.ts.map +1 -1
  45. package/src/cli/d1/D1SqlMigrations.js +11 -1
  46. package/src/cli/d1/WranglerConfig.d.ts.map +1 -1
  47. package/src/cli/d1/WranglerConfig.js +34 -2
  48. package/src/cli/services/VersionChecker.d.ts.map +1 -1
  49. package/src/cli/services/VersionChecker.js +5 -1
  50. package/src/cli/utils/DatabaseCliUtils.d.ts.map +1 -1
  51. package/src/cli/utils/DatabaseCliUtils.js +6 -1
  52. package/src/cli/utils/EnvFileLoader.d.ts.map +1 -1
  53. package/src/cli/utils/EnvFileLoader.js +33 -14
  54. package/src/cli.d.ts +5 -0
  55. package/src/cli.d.ts.map +1 -0
  56. package/src/cli.js +4 -0
  57. package/src/collections/index.d.ts +2 -2
  58. package/src/collections/index.d.ts.map +1 -1
  59. package/src/collections/index.js +1 -1
  60. package/src/common/RemoteSignedJson.d.ts.map +1 -1
  61. package/src/common/RemoteSignedJson.js +49 -23
  62. package/src/common/utility.d.ts.map +1 -1
  63. package/src/common/utility.js +2 -6
  64. package/src/config/cloudflare.d.ts.map +1 -1
  65. package/src/config/cloudflare.js +19 -8
  66. package/src/config/env.js +2 -2
  67. package/src/helper/index.d.ts +225 -0
  68. package/src/helper/index.d.ts.map +1 -0
  69. package/src/helper/index.js +347 -0
  70. package/src/index.d.ts +3 -6
  71. package/src/index.d.ts.map +1 -1
  72. package/src/index.js +7 -9
  73. package/src/migrations/MigrationDiscovery.d.ts.map +1 -1
  74. package/src/migrations/MigrationDiscovery.js +2 -1
  75. package/src/orm/DatabaseAdapter.d.ts +1 -0
  76. package/src/orm/DatabaseAdapter.d.ts.map +1 -1
  77. package/src/orm/SchemaStatemenWriter.d.ts +15 -0
  78. package/src/orm/SchemaStatemenWriter.d.ts.map +1 -0
  79. package/src/orm/SchemaStatemenWriter.js +78 -0
  80. package/src/orm/adapters/D1Adapter.d.ts.map +1 -1
  81. package/src/orm/adapters/D1Adapter.js +52 -2
  82. package/src/orm/adapters/D1RemoteAdapter.d.ts.map +1 -1
  83. package/src/orm/adapters/D1RemoteAdapter.js +137 -89
  84. package/src/orm/adapters/MySQLProxyAdapter.d.ts.map +1 -1
  85. package/src/orm/adapters/MySQLProxyAdapter.js +100 -81
  86. package/src/orm/adapters/PostgreSQLProxyAdapter.d.ts.map +1 -1
  87. package/src/orm/adapters/PostgreSQLProxyAdapter.js +26 -10
  88. package/src/orm/adapters/SqlProxyAdapterUtils.d.ts.map +1 -1
  89. package/src/orm/adapters/SqlProxyAdapterUtils.js +2 -1
  90. package/src/orm/adapters/SqlProxyRegistryMode.d.ts +12 -0
  91. package/src/orm/adapters/SqlProxyRegistryMode.d.ts.map +1 -0
  92. package/src/orm/adapters/SqlProxyRegistryMode.js +24 -0
  93. package/src/orm/adapters/SqlServerProxyAdapter.d.ts +3 -0
  94. package/src/orm/adapters/SqlServerProxyAdapter.d.ts.map +1 -1
  95. package/src/orm/adapters/SqlServerProxyAdapter.js +125 -117
  96. package/src/orm/migrations/MigrationStore.js +1 -1
  97. package/src/proxy/ProxyRequestParsing.d.ts +9 -0
  98. package/src/proxy/ProxyRequestParsing.d.ts.map +1 -0
  99. package/src/proxy/ProxyRequestParsing.js +16 -0
  100. package/src/proxy/RequestValidator.d.ts.map +1 -1
  101. package/src/proxy/RequestValidator.js +2 -1
  102. package/src/proxy/SigningService.js +2 -2
  103. package/src/proxy/SqlProxyDbOverrides.d.ts +17 -0
  104. package/src/proxy/SqlProxyDbOverrides.d.ts.map +1 -0
  105. package/src/proxy/SqlProxyDbOverrides.js +1 -0
  106. package/src/proxy/SqlProxyServerDeps.d.ts +12 -0
  107. package/src/proxy/SqlProxyServerDeps.d.ts.map +1 -0
  108. package/src/proxy/SqlProxyServerDeps.js +9 -0
  109. package/src/proxy/StatementPayloadValidator.d.ts +13 -0
  110. package/src/proxy/StatementPayloadValidator.d.ts.map +1 -0
  111. package/src/proxy/StatementPayloadValidator.js +18 -0
  112. package/src/proxy/StatementRegistryLoader.d.ts +2 -0
  113. package/src/proxy/StatementRegistryLoader.d.ts.map +1 -0
  114. package/src/proxy/StatementRegistryLoader.js +36 -0
  115. package/src/proxy/StatementRegistryResolver.d.ts +15 -0
  116. package/src/proxy/StatementRegistryResolver.d.ts.map +1 -0
  117. package/src/proxy/StatementRegistryResolver.js +34 -0
  118. package/src/proxy/d1/ZintrustD1Proxy.d.ts +2 -1
  119. package/src/proxy/d1/ZintrustD1Proxy.d.ts.map +1 -1
  120. package/src/proxy/d1/ZintrustD1Proxy.js +2 -1
  121. package/src/proxy/isMutatingSql.d.ts +2 -0
  122. package/src/proxy/isMutatingSql.d.ts.map +1 -0
  123. package/src/proxy/isMutatingSql.js +12 -0
  124. package/src/proxy/kv/ZintrustKvProxy.d.ts +2 -1
  125. package/src/proxy/kv/ZintrustKvProxy.d.ts.map +1 -1
  126. package/src/proxy/kv/ZintrustKvProxy.js +2 -1
  127. package/src/proxy/mysql/MySqlProxyServer.d.ts +2 -8
  128. package/src/proxy/mysql/MySqlProxyServer.d.ts.map +1 -1
  129. package/src/proxy/mysql/MySqlProxyServer.js +84 -51
  130. package/src/proxy/postgres/PostgresProxyServer.d.ts +2 -8
  131. package/src/proxy/postgres/PostgresProxyServer.d.ts.map +1 -1
  132. package/src/proxy/postgres/PostgresProxyServer.js +86 -48
  133. package/src/proxy/smtp/SmtpProxyServer.d.ts.map +1 -1
  134. package/src/proxy/smtp/SmtpProxyServer.js +6 -5
  135. package/src/proxy/sqlserver/SqlServerProxyServer.d.ts +2 -8
  136. package/src/proxy/sqlserver/SqlServerProxyServer.d.ts.map +1 -1
  137. package/src/proxy/sqlserver/SqlServerProxyServer.js +84 -49
  138. package/src/proxy.d.ts +4 -0
  139. package/src/proxy.d.ts.map +1 -0
  140. package/src/proxy.js +3 -0
  141. package/src/scheduler/Schedule.d.ts +36 -0
  142. package/src/scheduler/Schedule.d.ts.map +1 -0
  143. package/src/scheduler/Schedule.js +197 -0
  144. package/src/scheduler/ScheduleHttpGateway.d.ts +8 -0
  145. package/src/scheduler/ScheduleHttpGateway.d.ts.map +1 -0
  146. package/src/scheduler/ScheduleHttpGateway.js +196 -0
  147. package/src/scheduler/ScheduleRunner.d.ts +6 -0
  148. package/src/scheduler/ScheduleRunner.d.ts.map +1 -1
  149. package/src/scheduler/ScheduleRunner.js +166 -29
  150. package/src/scheduler/SchedulerRuntime.d.ts +15 -0
  151. package/src/scheduler/SchedulerRuntime.d.ts.map +1 -0
  152. package/src/scheduler/SchedulerRuntime.js +79 -0
  153. package/src/scheduler/cron/Cron.d.ts +19 -0
  154. package/src/scheduler/cron/Cron.d.ts.map +1 -0
  155. package/src/scheduler/cron/Cron.js +200 -0
  156. package/src/scheduler/leader/SchedulerLeader.d.ts +14 -0
  157. package/src/scheduler/leader/SchedulerLeader.d.ts.map +1 -0
  158. package/src/scheduler/leader/SchedulerLeader.js +187 -0
  159. package/src/scheduler/state/ScheduleStateStore.d.ts +27 -0
  160. package/src/scheduler/state/ScheduleStateStore.d.ts.map +1 -0
  161. package/src/scheduler/state/ScheduleStateStore.js +27 -0
  162. package/src/scheduler/types.d.ts +10 -0
  163. package/src/scheduler/types.d.ts.map +1 -1
  164. package/src/schedules/index.d.ts +1 -0
  165. package/src/schedules/index.d.ts.map +1 -1
  166. package/src/schedules/index.js +1 -0
  167. package/src/schedules/job-tracking-cleanup.d.ts +4 -0
  168. package/src/schedules/job-tracking-cleanup.d.ts.map +1 -0
  169. package/src/schedules/job-tracking-cleanup.js +116 -0
  170. package/src/schedules/log-cleanup.d.ts +1 -2
  171. package/src/schedules/log-cleanup.d.ts.map +1 -1
  172. package/src/schedules/log-cleanup.js +12 -15
  173. package/src/security/Sanitizer.d.ts.map +1 -1
  174. package/src/security/Sanitizer.js +1 -9
  175. package/src/security/SignedRequest.d.ts.map +1 -1
  176. package/src/security/SignedRequest.js +2 -2
  177. package/src/templates/docker/docker-compose.ecosystem.yml.tpl +301 -0
  178. package/src/templates/docker/docker-compose.schedules.yml.tpl +84 -0
  179. package/src/templates/project/basic/app/Schedules/index.ts.tpl +0 -0
  180. package/src/templates/project/basic/config/database.ts.tpl +1 -1
  181. package/src/toolkit/Secrets/Manifest.d.ts.map +1 -1
  182. package/src/toolkit/Secrets/Manifest.js +5 -7
  183. package/src/tools/mail/drivers/Smtp.d.ts.map +1 -1
  184. package/src/tools/mail/drivers/Smtp.js +7 -1
  185. package/src/tools/queue/JobReconciliationRunner.d.ts.map +1 -1
  186. package/src/tools/queue/JobReconciliationRunner.js +7 -39
  187. package/src/tools/queue/JobRecoveryDaemon.d.ts.map +1 -1
  188. package/src/tools/queue/JobRecoveryDaemon.js +116 -18
  189. package/src/tools/queue/JobStateTracker.d.ts +10 -1
  190. package/src/tools/queue/JobStateTracker.d.ts.map +1 -1
  191. package/src/tools/queue/JobStateTracker.js +24 -2
  192. package/src/tools/queue/JobStateTrackerDbPersistence.d.ts.map +1 -1
  193. package/src/tools/queue/JobStateTrackerDbPersistence.js +93 -2
@@ -8,6 +8,7 @@ import { WranglerConfig } from '../d1/WranglerConfig.js';
8
8
  import { WranglerD1 } from '../d1/WranglerD1.js';
9
9
  import { PromptHelper } from '../PromptHelper.js';
10
10
  import { confirmProductionRun, mapConnectionToOrmConfig, parseRollbackSteps, } from '../utils/DatabaseCliUtils.js';
11
+ import { EnvFileLoader } from '../utils/EnvFileLoader.js';
11
12
  import { readEnvString } from '../../common/ExternalServiceUtils.js';
12
13
  import { databaseConfig } from '../../config/database.js';
13
14
  import { ErrorFactory } from '../../exceptions/ZintrustError.js';
@@ -52,7 +53,7 @@ const getServiceArgs = (options) => {
52
53
  return { service: serviceArg, includeGlobal };
53
54
  };
54
55
  const isDestructiveAction = (options) => options['fresh'] === true || options['reset'] === true || options['rollback'] === true;
55
- const isD1Driver = (driver) => driver === 'd1' || driver === 'd1-remote';
56
+ const isWranglerD1Driver = (driver) => driver === 'd1';
56
57
  const describeTargetDatabase = (conn) => {
57
58
  switch (conn.driver) {
58
59
  case 'sqlite':
@@ -189,33 +190,47 @@ const runD1Actions = async (params) => {
189
190
  cmd.info(output);
190
191
  cmd.success('D1 migrations completed successfully');
191
192
  };
192
- const processConnection = async (conn, options, cmd, interactive) => {
193
- // Avoid confusion: `--database` is a D1-only option (Wrangler binding name), not DB_DATABASE.
194
- if (isD1Driver(conn.driver)) {
193
+ const logOptionsForConn = (cmd, conn, options) => {
194
+ if (isWranglerD1Driver(conn.driver)) {
195
195
  cmd.debug(`Migrate command executed with options: ${JSON.stringify(options)}`);
196
+ return;
196
197
  }
197
- else {
198
- const optionsForLog = { ...options };
199
- delete optionsForLog['database'];
200
- delete optionsForLog['local'];
201
- delete optionsForLog['remote'];
202
- cmd.debug(`Migrate command executed with options: ${JSON.stringify(optionsForLog)}`);
198
+ const optionsForLog = { ...options };
199
+ delete optionsForLog['database'];
200
+ delete optionsForLog['local'];
201
+ delete optionsForLog['remote'];
202
+ cmd.debug(`Migrate command executed with options: ${JSON.stringify(optionsForLog)}`);
203
+ };
204
+ const warnIfAdapterMissing = (cmd, conn) => {
205
+ if (conn.driver === 'mysql' && DatabaseAdapterRegistry.get('mysql') === undefined) {
206
+ cmd.warn('MySQL adapter is not installed/registered; migrations may not hit a real MySQL DB.');
207
+ cmd.warn('Install via `zin plugin install adapter:mysql` (or `zin add db:mysql`).');
208
+ cmd.debug('[debug] Expected a side-effect import in src/zintrust.plugins.ts like: import "@zintrust/db-mysql/register";');
203
209
  }
204
- const { globalDir, extension, separateTracking } = getMigrationDirs();
205
- const { service, includeGlobal } = getServiceArgs(options);
206
- if (isD1Driver(conn.driver)) {
207
- await runD1Actions({
208
- options,
209
- cmd,
210
- projectRoot: process.cwd(),
211
- globalDir,
212
- extension,
213
- separateTracking,
214
- service,
215
- includeGlobal,
216
- });
217
- return;
210
+ if (conn.driver === 'postgresql' && DatabaseAdapterRegistry.get('postgresql') === undefined) {
211
+ cmd.warn('PostgreSQL adapter is not installed/registered; migrations may not hit a real PostgreSQL DB.');
212
+ cmd.warn('Install via `zin plugin install adapter:postgres` (or `zin add db:postgres`).');
218
213
  }
214
+ };
215
+ const withD1RemoteSqlMode = async (fn) => {
216
+ if (typeof process === 'undefined' || process.env === undefined)
217
+ return fn();
218
+ const prev = process.env['D1_REMOTE_MODE'];
219
+ process.env['D1_REMOTE_MODE'] = 'sql';
220
+ try {
221
+ return await fn();
222
+ }
223
+ finally {
224
+ if (prev === undefined) {
225
+ delete process.env['D1_REMOTE_MODE'];
226
+ }
227
+ else {
228
+ process.env['D1_REMOTE_MODE'] = prev;
229
+ }
230
+ }
231
+ };
232
+ const runOrmMigrationsForConn = async (params) => {
233
+ const { conn, options, cmd, interactive, globalDir, extension, separateTracking, service, includeGlobal, } = params;
219
234
  const ormConfig = mapConnectionToOrmConfig(conn);
220
235
  const destructive = isDestructiveAction(options);
221
236
  const force = options['force'] === true;
@@ -231,18 +246,7 @@ const processConnection = async (conn, options, cmd, interactive) => {
231
246
  cmd.info(`[i] Target database: ${describeTargetDatabase(conn)}`);
232
247
  cmd.info('[i] Migration tracking table: migrations');
233
248
  cmd.debug(`[debug] Registered database adapters: ${DatabaseAdapterRegistry.list().join(', ')}`);
234
- // Warn only when the adapter truly isn't registered.
235
- // Adapters are registered via side-effect imports in src/zintrust.plugins.ts
236
- // (generated by `zin plugin install`) and are loaded at CLI startup.
237
- if (conn.driver === 'mysql' && DatabaseAdapterRegistry.get('mysql') === undefined) {
238
- cmd.warn('MySQL adapter is not installed/registered; migrations may not hit a real MySQL DB.');
239
- cmd.warn('Install via `zin plugin install adapter:mysql` (or `zin add db:mysql`).');
240
- cmd.debug('[debug] Expected a side-effect import in src/zintrust.plugins.ts like: import "@zintrust/db-mysql/register";');
241
- }
242
- if (conn.driver === 'postgresql' && DatabaseAdapterRegistry.get('postgresql') === undefined) {
243
- cmd.warn('PostgreSQL adapter is not installed/registered; migrations may not hit a real PostgreSQL DB.');
244
- cmd.warn('Install via `zin plugin install adapter:postgres` (or `zin add db:postgres`).');
245
- }
249
+ warnIfAdapterMissing(cmd, conn);
246
250
  const db = Database.create(ormConfig);
247
251
  await db.connect();
248
252
  try {
@@ -261,7 +265,45 @@ const processConnection = async (conn, options, cmd, interactive) => {
261
265
  await db.disconnect();
262
266
  }
263
267
  };
268
+ const processConnection = async (conn, options, cmd, interactive) => {
269
+ // Avoid confusion: `--database` is a D1-only option (Wrangler binding name), not DB_DATABASE.
270
+ logOptionsForConn(cmd, conn, options);
271
+ const { globalDir, extension, separateTracking } = getMigrationDirs();
272
+ const { service, includeGlobal } = getServiceArgs(options);
273
+ if (isWranglerD1Driver(conn.driver)) {
274
+ await runD1Actions({
275
+ options,
276
+ cmd,
277
+ projectRoot: process.cwd(),
278
+ globalDir,
279
+ extension,
280
+ separateTracking,
281
+ service,
282
+ includeGlobal,
283
+ });
284
+ return;
285
+ }
286
+ const run = async () => runOrmMigrationsForConn({
287
+ conn,
288
+ options,
289
+ cmd,
290
+ interactive,
291
+ globalDir,
292
+ extension,
293
+ separateTracking,
294
+ service,
295
+ includeGlobal,
296
+ });
297
+ if (conn.driver === 'd1-remote') {
298
+ cmd.info('[i] d1-remote migrations: using SQL mode automatically for this CLI run');
299
+ await withD1RemoteSqlMode(run);
300
+ return;
301
+ }
302
+ await run();
303
+ };
264
304
  const executeMigrate = async (options, cmd) => {
305
+ // Ensure .env overlays are loaded for CLI runs (needed for d1-remote signing fallbacks).
306
+ EnvFileLoader.ensureLoaded();
265
307
  const interactive = getInteractive(options);
266
308
  const targets = [];
267
309
  if (options['all'] === true) {
@@ -1 +1 @@
1
- {"version":3,"file":"MigrateWorkerCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/MigrateWorkerCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAqKrE,eAAO,MAAM,oBAAoB;cACrB,YAAY;EAUtB,CAAC"}
1
+ {"version":3,"file":"MigrateWorkerCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/MigrateWorkerCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAiNrE,eAAO,MAAM,oBAAoB;cACrB,YAAY;EAUtB,CAAC"}
@@ -9,6 +9,19 @@ import { Migrator } from '../../migrations/Migrator.js';
9
9
  import * as path from '../../node-singletons/path.js';
10
10
  import { Database } from '../../orm/Database.js';
11
11
  import { DatabaseAdapterRegistry } from '../../orm/DatabaseAdapterRegistry.js';
12
+ const isBuiltInDriver = (driver) => driver === 'sqlite' ||
13
+ driver === 'mysql' ||
14
+ driver === 'postgresql' ||
15
+ driver === 'sqlserver' ||
16
+ driver === 'd1' ||
17
+ driver === 'd1-remote';
18
+ const getWorkerPersistenceConnectionName = () => {
19
+ if (typeof process === 'undefined')
20
+ return 'default';
21
+ const raw = process.env?.['WORKER_PERSISTENCE_DB_CONNECTION'];
22
+ const value = typeof raw === 'string' ? raw.trim() : '';
23
+ return value.length > 0 ? value : 'default';
24
+ };
12
25
  const addOptions = (command) => {
13
26
  command
14
27
  .option('--status', 'Display migration status (applied, pending, failed)')
@@ -80,10 +93,16 @@ const runForConnection = async (conn, options, cmd, interactive) => {
80
93
  });
81
94
  if (!proceed)
82
95
  return;
83
- if (!DatabaseAdapterRegistry.has(conn.driver)) {
96
+ if (!isBuiltInDriver(conn.driver) && !DatabaseAdapterRegistry.has(conn.driver)) {
84
97
  cmd.warn(`Missing adapter for driver: ${conn.driver}`);
85
98
  cmd.warn(`Install via 'zin plugin install adapter:${conn.driver}' (or 'zin add db:${conn.driver}').`);
86
99
  }
100
+ const previousD1RemoteMode = typeof process === 'undefined' ? undefined : process.env['D1_REMOTE_MODE'];
101
+ if (conn.driver === 'd1-remote' && typeof process !== 'undefined') {
102
+ // Important: set BEFORE Database.create() so D1RemoteAdapter is constructed in SQL mode.
103
+ // Registry mode uses /zin/d1/statement, which is not appropriate for migrations.
104
+ process.env['D1_REMOTE_MODE'] = 'sql';
105
+ }
87
106
  const ormConfig = mapConnectionToOrmConfig(conn);
88
107
  const db = Database.create(ormConfig);
89
108
  await db.connect();
@@ -98,6 +117,14 @@ const runForConnection = async (conn, options, cmd, interactive) => {
98
117
  await runActions(migrator, options, cmd, conn.driver);
99
118
  }
100
119
  finally {
120
+ if (conn.driver === 'd1-remote' && typeof process !== 'undefined') {
121
+ if (previousD1RemoteMode === undefined) {
122
+ delete process.env['D1_REMOTE_MODE'];
123
+ }
124
+ else {
125
+ process.env['D1_REMOTE_MODE'] = previousD1RemoteMode;
126
+ }
127
+ }
101
128
  await db.disconnect();
102
129
  }
103
130
  };
@@ -110,7 +137,14 @@ const executeMigrateWorker = async (options, cmd) => {
110
137
  }
111
138
  }
112
139
  else {
113
- targets.push({ name: 'default', config: databaseConfig.getConnection() });
140
+ const selected = getWorkerPersistenceConnectionName();
141
+ const hasSelected = selected.length > 0;
142
+ const connections = databaseConfig.connections;
143
+ const configured = hasSelected ? connections[selected] : undefined;
144
+ targets.push({
145
+ name: hasSelected ? selected : 'default',
146
+ config: configured ?? databaseConfig.getConnection(),
147
+ });
114
148
  }
115
149
  let sequence = Promise.resolve();
116
150
  for (const { name, config } of targets) {
@@ -0,0 +1,6 @@
1
+ import { type IBaseCommand } from '../BaseCommand';
2
+ export declare const PutCommand: Readonly<{
3
+ create(): IBaseCommand;
4
+ }>;
5
+ export default PutCommand;
6
+ //# sourceMappingURL=PutCommand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PutCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/PutCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAmNvF,eAAO,MAAM,UAAU;cACX,YAAY;EAWtB,CAAC;AAEH,eAAe,UAAU,CAAC"}
@@ -0,0 +1,173 @@
1
+ import { BaseCommand } from '../BaseCommand.js';
2
+ import { resolveNpmPath } from '../../common/index.js';
3
+ import { appConfig } from '../../config/app.js';
4
+ import { ErrorFactory } from '../../exceptions/ZintrustError.js';
5
+ import { execFileSync } from '../../node-singletons/child-process.js';
6
+ import { existsSync, readFileSync } from '../../node-singletons/fs.js';
7
+ import * as path from '../../node-singletons/path.js';
8
+ import { EnvFile } from '../../toolkit/Secrets/EnvFile.js';
9
+ const toStringArray = (value) => {
10
+ if (typeof value === 'string')
11
+ return [value];
12
+ if (!Array.isArray(value))
13
+ return [];
14
+ return value.filter((v) => typeof v === 'string');
15
+ };
16
+ const uniq = (items) => {
17
+ const seen = new Set();
18
+ const out = [];
19
+ for (const item of items) {
20
+ const normalized = item.trim();
21
+ if (normalized === '' || seen.has(normalized))
22
+ continue;
23
+ seen.add(normalized);
24
+ out.push(normalized);
25
+ }
26
+ return out;
27
+ };
28
+ const readZintrustConfig = (cwd) => {
29
+ const filePath = path.join(cwd, '.zintrust.json');
30
+ if (!existsSync(filePath)) {
31
+ return {};
32
+ }
33
+ try {
34
+ const raw = readFileSync(filePath, 'utf8');
35
+ const parsed = JSON.parse(raw);
36
+ return typeof parsed === 'object' && parsed !== null ? parsed : {};
37
+ }
38
+ catch (error) {
39
+ throw ErrorFactory.createCliError('Failed to parse .zintrust.json', error);
40
+ }
41
+ };
42
+ const getConfigArray = (config, key) => {
43
+ const raw = config[key];
44
+ if (!Array.isArray(raw))
45
+ return [];
46
+ return uniq(raw.filter((item) => typeof item === 'string'));
47
+ };
48
+ const resolveConfigGroups = (options) => {
49
+ return uniq(toStringArray(options.var));
50
+ };
51
+ const resolveWranglerEnvs = (options) => {
52
+ const requested = uniq(toStringArray(options.wg));
53
+ if (requested.length === 0)
54
+ return ['worker'];
55
+ return requested;
56
+ };
57
+ const parseEnvPath = (options) => {
58
+ const direct = options['env_path'];
59
+ if (typeof direct === 'string' && direct.trim() !== '')
60
+ return direct;
61
+ return '.env';
62
+ };
63
+ const resolveValue = (key, envMap) => {
64
+ const fromFile = envMap[key];
65
+ const fromProcess = process.env[key];
66
+ return fromFile ?? fromProcess ?? '';
67
+ };
68
+ const getPutTimeoutMs = () => {
69
+ const raw = process.env['ZT_PUT_TIMEOUT_MS'];
70
+ if (typeof raw !== 'string')
71
+ return 120000;
72
+ const parsed = Number.parseInt(raw, 10);
73
+ if (!Number.isFinite(parsed) || parsed <= 0)
74
+ return 120000;
75
+ return parsed;
76
+ };
77
+ const putSecret = (wranglerEnv, key, value) => {
78
+ const npmPath = resolveNpmPath();
79
+ execFileSync(npmPath, ['exec', '--yes', '--', 'wrangler', 'secret', 'put', key, '--env', wranglerEnv], {
80
+ stdio: ['pipe', 'inherit', 'inherit'],
81
+ input: value,
82
+ encoding: 'utf8',
83
+ timeout: getPutTimeoutMs(),
84
+ killSignal: 'SIGTERM',
85
+ env: appConfig.getSafeEnv(),
86
+ });
87
+ };
88
+ const addOptions = (command) => {
89
+ command
90
+ .argument('[provider]', 'Secret provider (cloudflare)', 'cloudflare')
91
+ .option('--wg <env...>', 'Wrangler environment target(s), e.g. d1-proxy kv-proxy')
92
+ .option('--var <configKey...>', 'Config array key(s) from .zintrust.json (e.g. d1_env kv_env)')
93
+ .option('--env_path <path>', 'Path to env file used as source values', '.env')
94
+ .option('--dry-run', 'Show what would be uploaded without calling wrangler');
95
+ };
96
+ const ensureCloudflareProvider = (providerRaw) => {
97
+ if (providerRaw.toLowerCase() === 'cloudflare')
98
+ return;
99
+ throw ErrorFactory.createCliError('Only cloudflare provider is supported for `zin put`');
100
+ };
101
+ const resolveSelectedKeys = (cmd, config, options) => {
102
+ const configGroups = resolveConfigGroups(options);
103
+ if (configGroups.length === 0) {
104
+ throw ErrorFactory.createCliError('No config groups selected. Use --var <group>.');
105
+ }
106
+ const selectedKeys = uniq(configGroups.flatMap((groupKey) => {
107
+ const keys = getConfigArray(config, groupKey);
108
+ if (keys.length === 0) {
109
+ cmd.warn(`Group \`${groupKey}\` is missing or empty in .zintrust.json`);
110
+ }
111
+ return keys;
112
+ }));
113
+ if (selectedKeys.length === 0) {
114
+ throw ErrorFactory.createCliError('No secret keys resolved from selected groups.');
115
+ }
116
+ return selectedKeys;
117
+ };
118
+ const getFailureReason = (error) => error instanceof Error ? error.message : String(error);
119
+ const reportResult = (cmd, pushed, failures) => {
120
+ cmd.success(`Cloudflare secrets report: pushed=${pushed}, failed=${failures.length}`);
121
+ for (const item of failures) {
122
+ cmd.warn(`${item.key} -> ${item.wranglerEnv}: ${item.reason}`);
123
+ }
124
+ };
125
+ const processPut = (cmd, wranglerEnvs, selectedKeys, envMap, dryRun) => {
126
+ let pushed = 0;
127
+ const failures = [];
128
+ for (const wranglerEnv of wranglerEnvs) {
129
+ for (const key of selectedKeys) {
130
+ const value = resolveValue(key, envMap);
131
+ if (value.trim() === '') {
132
+ failures.push({ wranglerEnv, key, reason: 'empty value' });
133
+ continue;
134
+ }
135
+ try {
136
+ if (!dryRun) {
137
+ cmd.info(`putting ${key} -> ${wranglerEnv}...`);
138
+ putSecret(wranglerEnv, key, value);
139
+ }
140
+ pushed += 1;
141
+ cmd.info(`${dryRun ? '[dry-run] ' : ''}put ${key} -> ${wranglerEnv}`);
142
+ }
143
+ catch (error) {
144
+ failures.push({ wranglerEnv, key, reason: getFailureReason(error) });
145
+ }
146
+ }
147
+ }
148
+ return { pushed, failures };
149
+ };
150
+ const execute = async (cmd, options) => {
151
+ ensureCloudflareProvider(String(options.args?.[0] ?? 'cloudflare'));
152
+ const cwd = process.cwd();
153
+ const config = readZintrustConfig(cwd);
154
+ const selectedKeys = resolveSelectedKeys(cmd, config, options);
155
+ const envFilePath = parseEnvPath(options);
156
+ const envMap = await EnvFile.read({ cwd, path: envFilePath });
157
+ const wranglerEnvs = resolveWranglerEnvs(options);
158
+ const dryRun = options.dryRun === true;
159
+ const result = processPut(cmd, wranglerEnvs, selectedKeys, envMap, dryRun);
160
+ reportResult(cmd, result.pushed, result.failures);
161
+ };
162
+ export const PutCommand = Object.freeze({
163
+ create() {
164
+ const cmd = BaseCommand.create({
165
+ name: 'put',
166
+ description: 'Put secrets to Cloudflare with dynamic groups from .zintrust.json',
167
+ addOptions,
168
+ execute: async (options) => execute(cmd, options),
169
+ });
170
+ return cmd;
171
+ },
172
+ });
173
+ export default PutCommand;
@@ -1 +1 @@
1
- {"version":3,"file":"QueueRecoveryCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/QueueRecoveryCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAwoBrE,eAAO,MAAM,oBAAoB;cACrB,YAAY;EAsBtB,CAAC;AAEH,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"QueueRecoveryCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/QueueRecoveryCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAwvBrE,eAAO,MAAM,oBAAoB;cACrB,YAAY;EAsBtB,CAAC;AAEH,eAAe,oBAAoB,CAAC"}
@@ -1,8 +1,10 @@
1
1
  import { BaseCommand } from '../BaseCommand.js';
2
+ import { databaseConfig } from '../../config/database.js';
2
3
  import { Logger } from '../../config/logger.js';
3
4
  import { queueConfig } from '../../config/queue.js';
4
5
  import { ErrorFactory } from '../../exceptions/ZintrustError.js';
5
- import { useDatabase } from '../../orm/Database.js';
6
+ import { resetDatabase, useDatabase } from '../../orm/Database.js';
7
+ import { registerDatabasesFromRuntimeConfig } from '../../orm/DatabaseRuntimeRegistration.js';
6
8
  import { JobRecoveryDaemon } from '../../tools/queue/JobRecoveryDaemon.js';
7
9
  import { JobStateTracker } from '../../tools/queue/JobStateTracker.js';
8
10
  import { Queue } from '../../tools/queue/Queue.js';
@@ -57,6 +59,7 @@ const normalizeStatus = (value) => {
57
59
  const statuses = [
58
60
  'pending',
59
61
  'active',
62
+ 'enqueued',
60
63
  'completed',
61
64
  'failed',
62
65
  'stalled',
@@ -98,17 +101,13 @@ const toRecordFromPersisted = (row) => {
98
101
  };
99
102
  };
100
103
  const getRecoverableStatuses = () => {
101
- return new Set([
102
- 'pending_recovery',
103
- 'timeout',
104
- 'stalled',
105
- 'dead_letter',
106
- ]);
104
+ return new Set(['pending_recovery']);
107
105
  };
108
106
  const getAllStatuses = () => {
109
107
  return new Set([
110
108
  'pending',
111
109
  'active',
110
+ 'enqueued',
112
111
  'completed',
113
112
  'failed',
114
113
  'stalled',
@@ -355,8 +354,35 @@ const runRecoveryOnce = async () => {
355
354
  const result = await JobRecoveryDaemon.runOnce();
356
355
  Logger.info('Queue recovery run completed', result);
357
356
  };
357
+ const isDuplicateJobIdError = (error) => {
358
+ const message = error instanceof Error ? error.message : String(error);
359
+ const normalized = message.toLowerCase();
360
+ return normalized.includes('jobid') && normalized.includes('already exists');
361
+ };
362
+ const validatePushable = (record) => {
363
+ if (record.status === 'enqueued') {
364
+ Logger.info('Job is already enqueued; nothing to push', {
365
+ jobId: record.jobId,
366
+ queueName: record.queueName,
367
+ });
368
+ return { ok: false };
369
+ }
370
+ if (record.status !== 'pending_recovery') {
371
+ Logger.error(`Refusing to push job in status ${record.status}. Only pending_recovery can be pushed.`);
372
+ if (typeof process !== 'undefined')
373
+ process.exitCode = 1;
374
+ return { ok: false };
375
+ }
376
+ return { ok: true };
377
+ };
358
378
  const runPushForJob = async (record, options) => {
359
- const payload = toSafeObject(record.payload);
379
+ if (!validatePushable(record).ok)
380
+ return;
381
+ const basePayload = toSafeObject(record.payload);
382
+ const hasPayload = record.payload !== undefined &&
383
+ record.payload !== null &&
384
+ typeof record.payload === 'object' &&
385
+ Object.keys(basePayload).length > 0;
360
386
  if (options.dryRun) {
361
387
  Logger.info('Dry-run: skipping enqueue for target job', {
362
388
  jobId: record.jobId,
@@ -365,12 +391,36 @@ const runPushForJob = async (record, options) => {
365
391
  });
366
392
  return;
367
393
  }
368
- const replayJobId = await Queue.enqueue(record.queueName, payload);
369
- await JobStateTracker.markedRecovered({
394
+ if (!hasPayload) {
395
+ Logger.error(`Cannot push job because payload is missing in tracker/persistence store: ${record.jobId}. ` +
396
+ 'Rehydrate payload_json first (or use policy recovery states) before pushing.');
397
+ if (typeof process !== 'undefined')
398
+ process.exitCode = 1;
399
+ return;
400
+ }
401
+ const payload = {
402
+ ...basePayload,
403
+ uniqueId: record.jobId,
404
+ attempts: typeof record.maxAttempts === 'number' ? record.maxAttempts : 3,
405
+ _currentAttempts: Math.max(0, Math.floor(record.attempts ?? 0)),
406
+ timestamp: Date.now(),
407
+ };
408
+ let replayJobId;
409
+ try {
410
+ replayJobId = await Queue.enqueue(record.queueName, payload, 'default');
411
+ }
412
+ catch (error) {
413
+ if (isDuplicateJobIdError(error)) {
414
+ replayJobId = record.jobId;
415
+ }
416
+ else {
417
+ throw error;
418
+ }
419
+ }
420
+ await JobStateTracker.handedOffToQueue({
370
421
  queueName: record.queueName,
371
422
  jobId: record.jobId,
372
423
  reason: `CLI pushed job as ${replayJobId}`,
373
- retryAt: new Date().toISOString(),
374
424
  });
375
425
  Logger.info('Target job pushed to queue', {
376
426
  originalJobId: record.jobId,
@@ -378,6 +428,21 @@ const runPushForJob = async (record, options) => {
378
428
  queueName: record.queueName,
379
429
  });
380
430
  };
431
+ const isTestRuntime = () => (process.env['NODE_ENV'] ?? '').trim().toLowerCase() === 'test' || Boolean(process.env['VITEST']);
432
+ const cleanupOneOffCli = async () => {
433
+ try {
434
+ QueueReliabilityOrchestrator.stop();
435
+ }
436
+ catch {
437
+ // ignore
438
+ }
439
+ try {
440
+ await resetDatabase();
441
+ }
442
+ catch {
443
+ // ignore
444
+ }
445
+ };
381
446
  const runRecoverOneForJob = async (record, options) => {
382
447
  if (options.dryRun) {
383
448
  Logger.info('Dry-run: skipping policy recovery for target job', {
@@ -456,10 +521,23 @@ const runTargetedRecovery = async (resolved) => {
456
521
  return;
457
522
  const record = await findRecord(resolved.jobId, resolved.queueName, resolved.allowDbLookup);
458
523
  if (record === null) {
459
- throw ErrorFactory.createConfigError(`Job not found in tracker${resolved.allowDbLookup ? ' or persistence store' : ''}: ${resolved.jobId}`);
524
+ Logger.error(`Job not found in tracker${resolved.allowDbLookup ? ' or persistence store' : ''}: ${resolved.jobId}`);
525
+ if (typeof process !== 'undefined')
526
+ process.exitCode = 1;
527
+ return;
528
+ }
529
+ if (record.status === 'enqueued' && !resolved.push) {
530
+ Logger.info('Job already enqueued; nothing to recover', {
531
+ jobId: record.jobId,
532
+ queueName: record.queueName,
533
+ });
534
+ return;
460
535
  }
461
536
  if (!resolved.push && !getRecoverableStatuses().has(record.status)) {
462
- throw ErrorFactory.createConfigError(`Job status is not recoverable via policy runner: ${record.status}. Use --push to force requeue.`);
537
+ Logger.error(`Job status is not recoverable via policy runner: ${record.status}. Use --push to force requeue.`);
538
+ if (typeof process !== 'undefined')
539
+ process.exitCode = 1;
540
+ return;
463
541
  }
464
542
  if (resolved.push) {
465
543
  await runPushForJob(record, { dryRun: resolved.dryRun });
@@ -480,11 +558,32 @@ const executeQueueRecovery = async (options) => {
480
558
  const resolved = resolveExecutionOptions(options);
481
559
  if (await maybeRunListMode(resolved))
482
560
  return;
483
- await registerQueuesFromRuntimeConfig(queueConfig);
561
+ // Ensure DB connections exist for optional persistence lookups.
562
+ // (CLI commands don't run full app bootstrap.)
563
+ registerDatabasesFromRuntimeConfig(databaseConfig);
564
+ const envDefault = (process.env['QUEUE_DRIVER'] ?? '').toString().trim().toLowerCase();
565
+ const cliQueueConfig = {
566
+ ...queueConfig,
567
+ default: (envDefault.length > 0
568
+ ? envDefault
569
+ : queueConfig.default),
570
+ };
571
+ await registerQueuesFromRuntimeConfig(cliQueueConfig);
572
+ if (!resolved.start) {
573
+ // Queue registration may auto-start orchestrator when JOB_RELIABILITY_AUTOSTART=true.
574
+ // For one-off CLI runs, stop it to avoid noisy intervals.
575
+ QueueReliabilityOrchestrator.stop();
576
+ }
484
577
  if (await maybeRunDefaultRecovery(resolved))
485
578
  return;
486
579
  await runTargetedRecovery(resolved);
487
580
  await finalizeRecoveryExecution(resolved);
581
+ if (!resolved.start) {
582
+ await cleanupOneOffCli();
583
+ if (!isTestRuntime() && typeof process !== 'undefined') {
584
+ process.exit(process.exitCode ?? 0);
585
+ }
586
+ }
488
587
  };
489
588
  export const QueueRecoveryCommand = Object.freeze({
490
589
  create() {
@@ -0,0 +1,6 @@
1
+ import type { IBaseCommand } from '../BaseCommand';
2
+ export declare const ScheduleListCommand: Readonly<{
3
+ create(): IBaseCommand;
4
+ }>;
5
+ export default ScheduleListCommand;
6
+ //# sourceMappingURL=ScheduleListCommand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScheduleListCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/ScheduleListCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAsErE,eAAO,MAAM,mBAAmB;cACpB,YAAY;EAUtB,CAAC;AAEH,eAAe,mBAAmB,CAAC"}