@zintrust/core 0.4.24 → 0.4.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/boot/bootstrap.js +5 -0
- package/src/cli/commands/InitContainerCommand.d.ts.map +1 -1
- package/src/cli/commands/InitContainerCommand.js +144 -19
- package/src/cli/commands/MigrateWorkerCommand.d.ts.map +1 -1
- package/src/cli/commands/MigrateWorkerCommand.js +8 -2
- package/src/cli/commands/WorkerCommands.d.ts.map +1 -1
- package/src/cli/commands/WorkerCommands.js +47 -7
- package/src/index.js +3 -3
- package/src/runtime/WorkerProjectAutoImports.d.ts +14 -0
- package/src/runtime/WorkerProjectAutoImports.d.ts.map +1 -0
- package/src/runtime/WorkerProjectAutoImports.js +63 -0
- package/src/templates/project/basic/app/Workers/ExampleWorker.ts.tpl +17 -0
- package/src/templates/project/basic/package.json.tpl +1 -0
- package/src/templates/project/basic/src/zintrust.workers.ts.tpl +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.27",
|
|
4
4
|
"description": "Production-grade TypeScript backend framework for JavaScript",
|
|
5
5
|
"homepage": "https://zintrust.com",
|
|
6
6
|
"repository": {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@cloudflare/containers": "^0.1.1",
|
|
53
|
-
"@zintrust/workers": "^0.4.
|
|
53
|
+
"@zintrust/workers": "^0.4.25",
|
|
54
54
|
"bcryptjs": "^3.0.3",
|
|
55
55
|
"bullmq": "^5.71.0",
|
|
56
56
|
"chalk": "^5.6.2",
|
package/src/boot/bootstrap.js
CHANGED
|
@@ -10,6 +10,7 @@ import { Env } from '../config/env.js';
|
|
|
10
10
|
import { Logger } from '../config/logger.js';
|
|
11
11
|
import { ErrorFactory } from '../exceptions/ZintrustError.js';
|
|
12
12
|
import { ProjectRuntime } from '../runtime/ProjectRuntime.js';
|
|
13
|
+
import { WorkerProjectAutoImports } from '../runtime/WorkerProjectAutoImports.js';
|
|
13
14
|
import { loadWorkersModule } from '../runtime/WorkersModule.js';
|
|
14
15
|
let appInstance;
|
|
15
16
|
let serverInstance;
|
|
@@ -165,6 +166,10 @@ async function useWorkerStarter() {
|
|
|
165
166
|
// Initialize worker management system
|
|
166
167
|
let workerInit = null;
|
|
167
168
|
try {
|
|
169
|
+
const projectWorkerEntrypoint = await WorkerProjectAutoImports.tryImportProjectWorkerEntrypoint();
|
|
170
|
+
if (!projectWorkerEntrypoint.ok && projectWorkerEntrypoint.reason === 'import-failed') {
|
|
171
|
+
Logger.warn(`Project worker entrypoint import failed: ${projectWorkerEntrypoint.errorMessage ?? 'Unknown error'}`);
|
|
172
|
+
}
|
|
168
173
|
const workers = await loadWorkersModule();
|
|
169
174
|
if (workers?.WorkerInit !== undefined) {
|
|
170
175
|
workerInit = workers.WorkerInit;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InitContainerCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/InitContainerCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"InitContainerCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/InitContainerCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAqSlE,eAAO,MAAM,oBAAoB;cACrB,YAAY;EAqBtB,CAAC"}
|
|
@@ -7,12 +7,13 @@ const DOCKER_COMPOSE_WORKERS_TEMPLATE = `name: zintrust-workers
|
|
|
7
7
|
|
|
8
8
|
services:
|
|
9
9
|
# Workers/Jobs API Service (Port 7772)
|
|
10
|
-
# Exposes the Workers API to create/manage jobs
|
|
10
|
+
# Exposes the Workers API to create/manage jobs using the project worker overlay image.
|
|
11
11
|
workers-api:
|
|
12
|
-
image: \${WORKERS_IMAGE:-zintrust
|
|
12
|
+
image: \${WORKERS_IMAGE:-zintrust-workers-local:latest}
|
|
13
13
|
build:
|
|
14
14
|
context: .
|
|
15
|
-
dockerfile: Dockerfile
|
|
15
|
+
dockerfile: Dockerfile.workers
|
|
16
|
+
target: runtime
|
|
16
17
|
command: ["node", "--experimental-specifier-resolution=node", "dist/src/boot/bootstrap.js"]
|
|
17
18
|
environment:
|
|
18
19
|
# Runtime
|
|
@@ -26,7 +27,6 @@ services:
|
|
|
26
27
|
- APP_KEY=\${APP_KEY}
|
|
27
28
|
- ENCRYPTION_CIPHER=\${ENCRYPTION_CIPHER:-aes-256-cbc}
|
|
28
29
|
- LOG_LEVEL=\${LOG_LEVEL:-info}
|
|
29
|
-
- ZINTRUST_PROJECT_ROOT=/app/dist
|
|
30
30
|
|
|
31
31
|
# Workers & Queue
|
|
32
32
|
- WORKER_ENABLED=\${WORKER_ENABLED:-false}
|
|
@@ -96,23 +96,148 @@ services:
|
|
|
96
96
|
ports:
|
|
97
97
|
- '7772:7772'
|
|
98
98
|
|
|
99
|
+
# Dedicated background worker runner.
|
|
100
|
+
# Uses the same project overlay image but boots the worker target.
|
|
101
|
+
worker-runner:
|
|
102
|
+
image: \${WORKERS_RUNNER_IMAGE:-zintrust-workers-local:latest}
|
|
103
|
+
build:
|
|
104
|
+
context: .
|
|
105
|
+
dockerfile: Dockerfile.workers
|
|
106
|
+
target: worker
|
|
107
|
+
environment:
|
|
108
|
+
# Runtime
|
|
109
|
+
- NODE_ENV=\${NODE_ENV:-development}
|
|
110
|
+
- HOST=0.0.0.0
|
|
111
|
+
|
|
112
|
+
# Application
|
|
113
|
+
- APP_NAME=\${APP_NAME:-ZinTrust}
|
|
114
|
+
- APP_KEY=\${APP_KEY}
|
|
115
|
+
- ENCRYPTION_CIPHER=\${ENCRYPTION_CIPHER:-aes-256-cbc}
|
|
116
|
+
- LOG_LEVEL=\${LOG_LEVEL:-info}
|
|
117
|
+
|
|
118
|
+
# Workers & Queue
|
|
119
|
+
- DOCKER_WORKER=true
|
|
120
|
+
- WORKER_ENABLED=\${WORKER_ENABLED:-true}
|
|
121
|
+
- WORKER_AUTO_START=\${WORKER_AUTO_START:-true}
|
|
122
|
+
- QUEUE_ENABLED=true
|
|
123
|
+
- QUEUE_MONITOR_ENABLED=\${QUEUE_MONITOR_ENABLED:-false}
|
|
124
|
+
- QUEUE_MONITOR_MIDDLEWARE=\${QUEUE_MONITOR_MIDDLEWARE:-}
|
|
125
|
+
- WORKER_PERSISTENCE_DRIVER=\${WORKER_PERSISTENCE_DRIVER:-redis}
|
|
126
|
+
- WORKER_PERSISTENCE_DB_CONNECTION=\${WORKER_PERSISTENCE_DB_CONNECTION:-mysql}
|
|
127
|
+
- WORKER_PERSISTENCE_REDIS_KEY_PREFIX=\${WORKER_PERSISTENCE_REDIS_KEY_PREFIX}
|
|
128
|
+
- QUEUE_DRIVER=\${QUEUE_DRIVER:-redis}
|
|
129
|
+
- QUEUE_CONNECTION=\${QUEUE_CONNECTION:-redis}
|
|
130
|
+
- CACHE_DRIVER=\${CACHE_DRIVER:-redis}
|
|
131
|
+
|
|
132
|
+
# Redis
|
|
133
|
+
- REDIS_HOST=\${DOCKER_REDIS_HOST:-host.docker.internal}
|
|
134
|
+
- REDIS_PORT=\${REDIS_PORT:-6379}
|
|
135
|
+
- REDIS_PASSWORD=\${REDIS_PASSWORD}
|
|
136
|
+
- REDIS_QUEUE_DB=\${REDIS_QUEUE_DB:-1}
|
|
137
|
+
|
|
138
|
+
# Database
|
|
139
|
+
- DB_CONNECTION=\${DB_CONNECTION:-postgres}
|
|
140
|
+
- DB_HOST=\${DOCKER_DB_HOST:-host.docker.internal}
|
|
141
|
+
- DB_PORT=\${DB_PORT:-3306}
|
|
142
|
+
- DB_DATABASE=\${DB_DATABASE:-zintrust}
|
|
143
|
+
- DB_USERNAME=\${DB_USERNAME:-zintrust}
|
|
144
|
+
- DB_PASSWORD=\${DB_PASSWORD:-}
|
|
145
|
+
|
|
146
|
+
# SMTP Mail
|
|
147
|
+
- MAIL_DRIVER=\${MAIL_DRIVER:-smtp}
|
|
148
|
+
- MAIL_CONNECTION=\${MAIL_CONNECTION:-smtp}
|
|
149
|
+
- MAIL_HOST=\${MAIL_HOST}
|
|
150
|
+
- MAIL_PORT=\${MAIL_PORT:-587}
|
|
151
|
+
- MAIL_SECURE=\${MAIL_SECURE:-false}
|
|
152
|
+
- MAIL_USERNAME=\${MAIL_USERNAME}
|
|
153
|
+
- MAIL_PASSWORD=\${MAIL_PASSWORD}
|
|
154
|
+
- MAIL_FROM_ADDRESS=\${MAIL_FROM_ADDRESS}
|
|
155
|
+
- MAIL_FROM_NAME=\${MAIL_FROM_NAME:-ZinTrust}
|
|
156
|
+
|
|
157
|
+
# PostgreSQL
|
|
158
|
+
- DB_PORT_POSTGRESQL=\${DB_PORT_POSTGRESQL:-5432}
|
|
159
|
+
- DB_DATABASE_POSTGRESQL=\${DB_DATABASE_POSTGRESQL:-zintrust}
|
|
160
|
+
- DB_USERNAME_POSTGRESQL=\${DB_USERNAME_POSTGRESQL:-zintrust}
|
|
161
|
+
- DB_PASSWORD_POSTGRESQL=\${DB_PASSWORD_POSTGRESQL:-}
|
|
162
|
+
|
|
163
|
+
# MySQL
|
|
164
|
+
- DB_PORT_MYSQL=\${DB_PORT_MYSQL:-3306}
|
|
165
|
+
- DB_DATABASE_MYSQL=\${DB_DATABASE_MYSQL:-zintrust}
|
|
166
|
+
- DB_USERNAME_MYSQL=\${DB_USERNAME_MYSQL:-zintrust}
|
|
167
|
+
- DB_PASSWORD_MYSQL=\${DB_PASSWORD_MYSQL:-}
|
|
168
|
+
|
|
169
|
+
# Cloudflare D1
|
|
170
|
+
- D1_DATABASE_ID=\${D1_DATABASE_ID}
|
|
171
|
+
- D1_ACCOUNT_ID=\${D1_ACCOUNT_ID}
|
|
172
|
+
- D1_API_TOKEN=\${D1_API_TOKEN}
|
|
173
|
+
- D1_REMOTE_URL=\${D1_REMOTE_URL}
|
|
174
|
+
- D1_REMOTE_KEY_ID=\${D1_REMOTE_KEY_ID}
|
|
175
|
+
- D1_REMOTE_SECRET=\${D1_REMOTE_SECRET}
|
|
176
|
+
|
|
177
|
+
# Cloudflare KV
|
|
178
|
+
- KV_NAMESPACE_ID=\${KV_NAMESPACE_ID}
|
|
179
|
+
- KV_ACCOUNT_ID=\${KV_ACCOUNT_ID}
|
|
180
|
+
- KV_API_TOKEN=\${KV_API_TOKEN}
|
|
181
|
+
- KV_REMOTE_URL=\${KV_REMOTE_URL}
|
|
182
|
+
- KV_REMOTE_KEY_ID=\${KV_REMOTE_KEY_ID}
|
|
183
|
+
- KV_REMOTE_SECRET=\${KV_REMOTE_SECRET}
|
|
184
|
+
|
|
99
185
|
`;
|
|
100
|
-
const DOCKERFILE_TEMPLATE = String.raw
|
|
186
|
+
const DOCKERFILE_TEMPLATE = String.raw `# Multi-stage worker overlay image.
|
|
187
|
+
#
|
|
188
|
+
# This compiles the local ZinTrust app first, then copies only the compiled worker-related
|
|
189
|
+
# artifacts onto the published zintrust/zintrust runtime image.
|
|
190
|
+
|
|
191
|
+
FROM node:20-alpine AS project-build
|
|
192
|
+
|
|
193
|
+
WORKDIR /project
|
|
194
|
+
|
|
195
|
+
ENV NPM_CONFIG_CACHE=/root/.npm
|
|
196
|
+
ENV NPM_CONFIG_PREFER_OFFLINE=true
|
|
197
|
+
|
|
198
|
+
RUN apk upgrade --no-cache \
|
|
199
|
+
&& apk add --no-cache g++ git make python3
|
|
200
|
+
|
|
201
|
+
COPY package.json package-lock.json ./
|
|
202
|
+
|
|
203
|
+
RUN --mount=type=cache,target=/root/.npm,id=zintrust-worker-overlay-npm-cache,sharing=locked \
|
|
204
|
+
npm config set fetch-retries 5 \
|
|
205
|
+
&& npm config set fetch-retry-mintimeout 20000 \
|
|
206
|
+
&& npm config set fetch-retry-maxtimeout 120000 \
|
|
207
|
+
&& npm ci
|
|
208
|
+
|
|
209
|
+
COPY . .
|
|
210
|
+
|
|
211
|
+
# Fresh projects always ship npm run build; do not depend on framework-internal build variants.
|
|
212
|
+
RUN --mount=type=cache,target=/root/.npm,id=zintrust-worker-overlay-npm-cache,sharing=locked npm run build
|
|
213
|
+
|
|
214
|
+
FROM project-build AS worker-overlay
|
|
215
|
+
|
|
216
|
+
RUN set -eu; \
|
|
217
|
+
overlay_root=/overlay; \
|
|
218
|
+
mkdir -p "$overlay_root/dist"; \
|
|
219
|
+
if [ -d /project/dist/app ]; then cp -R /project/dist/app "$overlay_root/dist/app"; fi; \
|
|
220
|
+
mkdir -p "$overlay_root/dist/src"; \
|
|
221
|
+
if [ -f /project/dist/src/zintrust.workers.js ]; then cp /project/dist/src/zintrust.workers.js "$overlay_root/dist/src/zintrust.workers.js"; fi; \
|
|
222
|
+
if [ -f /project/dist/src/zintrust.workers.js.map ]; then cp /project/dist/src/zintrust.workers.js.map "$overlay_root/dist/src/zintrust.workers.js.map"; fi
|
|
223
|
+
|
|
224
|
+
FROM zintrust/zintrust:latest AS runtime
|
|
101
225
|
|
|
102
226
|
WORKDIR /app
|
|
103
227
|
|
|
104
|
-
|
|
105
|
-
ENV PORT=7772
|
|
106
|
-
ENV HOST=0.0.0.0
|
|
228
|
+
COPY --from=worker-overlay --chown=nodejs:nodejs /overlay/dist/ /app/dist/
|
|
107
229
|
|
|
108
|
-
|
|
230
|
+
FROM runtime AS worker
|
|
109
231
|
|
|
110
|
-
|
|
111
|
-
|
|
232
|
+
ENV DOCKER_WORKER=true
|
|
233
|
+
ENV WORKER_ENABLED=true
|
|
234
|
+
ENV WORKER_AUTO_START=true
|
|
235
|
+
ENV QUEUE_ENABLED=true
|
|
236
|
+
ENV PORT=0
|
|
112
237
|
|
|
113
|
-
|
|
238
|
+
HEALTHCHECK NONE
|
|
114
239
|
|
|
115
|
-
CMD ["node", "dist/
|
|
240
|
+
CMD ["node", "dist/bin/zin.js", "worker:start-all"]
|
|
116
241
|
`;
|
|
117
242
|
const backupSuffix = () => new Date().toISOString().replaceAll(/[:.]/g, '-');
|
|
118
243
|
const backupFileIfExists = (filePath) => {
|
|
@@ -138,19 +263,18 @@ async function writeDockerComposeFile(cwd) {
|
|
|
138
263
|
}
|
|
139
264
|
}
|
|
140
265
|
async function writeDockerfile(cwd) {
|
|
141
|
-
const dockerfilePath = join(cwd, 'Dockerfile');
|
|
266
|
+
const dockerfilePath = join(cwd, 'Dockerfile.workers');
|
|
142
267
|
let shouldWrite = true;
|
|
143
268
|
if (existsSync(dockerfilePath)) {
|
|
144
|
-
|
|
145
|
-
shouldWrite = await PromptHelper.confirm('Dockerfile already exists. Overwrite with standard worker configuration?', false);
|
|
269
|
+
shouldWrite = await PromptHelper.confirm('Dockerfile.workers already exists. Overwrite with the ZinTrust worker overlay image?', false);
|
|
146
270
|
}
|
|
147
271
|
if (shouldWrite) {
|
|
148
272
|
backupFileIfExists(dockerfilePath);
|
|
149
273
|
writeFileSync(dockerfilePath, DOCKERFILE_TEMPLATE);
|
|
150
|
-
Logger.info('✅ Created Dockerfile');
|
|
274
|
+
Logger.info('✅ Created Dockerfile.workers');
|
|
151
275
|
}
|
|
152
276
|
else {
|
|
153
|
-
Logger.info('Skipped Dockerfile');
|
|
277
|
+
Logger.info('Skipped Dockerfile.workers');
|
|
154
278
|
}
|
|
155
279
|
}
|
|
156
280
|
export const InitContainerCommand = Object.freeze({
|
|
@@ -165,7 +289,8 @@ export const InitContainerCommand = Object.freeze({
|
|
|
165
289
|
await writeDockerComposeFile(cwd);
|
|
166
290
|
await writeDockerfile(cwd);
|
|
167
291
|
Logger.info('✅ Container worker scaffolding complete.');
|
|
168
|
-
Logger.info('Run with: docker
|
|
292
|
+
Logger.info('Run with: docker compose -f docker-compose.workers.yml up');
|
|
293
|
+
Logger.info('Build worker runner with: docker build -f Dockerfile.workers --target worker .');
|
|
169
294
|
await Promise.resolve();
|
|
170
295
|
},
|
|
171
296
|
});
|
|
@@ -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;
|
|
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;AAuNrE,eAAO,MAAM,oBAAoB;cACrB,YAAY;EAUtB,CAAC"}
|
|
@@ -15,7 +15,12 @@ const isBuiltInDriver = (driver) => driver === 'sqlite' ||
|
|
|
15
15
|
driver === 'sqlserver' ||
|
|
16
16
|
driver === 'd1' ||
|
|
17
17
|
driver === 'd1-remote';
|
|
18
|
-
const getWorkerPersistenceConnectionName = () => {
|
|
18
|
+
const getWorkerPersistenceConnectionName = (optionValue) => {
|
|
19
|
+
if (typeof optionValue === 'string') {
|
|
20
|
+
const trimmed = optionValue.trim();
|
|
21
|
+
if (trimmed.length > 0)
|
|
22
|
+
return trimmed;
|
|
23
|
+
}
|
|
19
24
|
if (typeof process === 'undefined')
|
|
20
25
|
return 'default';
|
|
21
26
|
const raw = process.env?.['WORKER_PERSISTENCE_DB_CONNECTION'];
|
|
@@ -31,6 +36,7 @@ const addOptions = (command) => {
|
|
|
31
36
|
.option('--step <number>', 'Number of batches to rollback (use with --rollback)', '1')
|
|
32
37
|
.option('--force', 'Skip production confirmation (allow unsafe operations in production)')
|
|
33
38
|
.option('--all', 'Run migrations for all configured database connections')
|
|
39
|
+
.option('--connection <name>', 'Use a specific database connection for worker migrations')
|
|
34
40
|
.option('--no-interactive', 'Disable interactive prompts (useful for CI/CD)');
|
|
35
41
|
};
|
|
36
42
|
const getInteractive = (options) => options['interactive'] !== false;
|
|
@@ -137,7 +143,7 @@ const executeMigrateWorker = async (options, cmd) => {
|
|
|
137
143
|
}
|
|
138
144
|
}
|
|
139
145
|
else {
|
|
140
|
-
const selected = getWorkerPersistenceConnectionName();
|
|
146
|
+
const selected = getWorkerPersistenceConnectionName(options['connection']);
|
|
141
147
|
const hasSelected = selected.length > 0;
|
|
142
148
|
const connections = databaseConfig.connections;
|
|
143
149
|
const configured = hasSelected ? connections[selected] : undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkerCommands.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/WorkerCommands.ts"],"names":[],"mappings":"AACA;;;GAGG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"WorkerCommands.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/WorkerCommands.ts"],"names":[],"mappings":"AACA;;;GAGG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AA0lBrD;;GAEG;AACH,eAAO,MAAM,cAAc;mCAtaS,YAAY;qCA4CV,YAAY;oCAiDb,YAAY;uCAqHT,YAAY;mCAyFhB,YAAY;sCAgCT,YAAY;sCAgCZ,YAAY;CAmElD,CAAC"}
|
|
@@ -7,14 +7,26 @@ import { ErrorFactory } from '../../exceptions/ZintrustError.js';
|
|
|
7
7
|
import { BaseCommand } from '../BaseCommand.js';
|
|
8
8
|
import { Env } from '../../config/env.js';
|
|
9
9
|
import { Logger } from '../../config/logger.js';
|
|
10
|
+
import { WorkerProjectAutoImports } from '../../runtime/WorkerProjectAutoImports.js';
|
|
10
11
|
import { loadWorkersModule as loadWorkersRuntimeModule } from '../../runtime/WorkersModule.js';
|
|
11
12
|
// Lazy initialization to prevent temporal dead zone issues
|
|
12
13
|
let WorkerFactory;
|
|
13
14
|
let WorkerRegistry;
|
|
14
15
|
let HealthMonitor;
|
|
15
16
|
let ResourceMonitor;
|
|
17
|
+
let workerProjectEntrypointAttempted = false;
|
|
18
|
+
const ensureProjectWorkerEntrypointLoaded = async () => {
|
|
19
|
+
if (workerProjectEntrypointAttempted)
|
|
20
|
+
return;
|
|
21
|
+
workerProjectEntrypointAttempted = true;
|
|
22
|
+
const result = await WorkerProjectAutoImports.tryImportProjectWorkerEntrypoint();
|
|
23
|
+
if (!result.ok && result.reason === 'import-failed') {
|
|
24
|
+
Logger.warn(`Project worker entrypoint import failed: ${result.errorMessage ?? 'Unknown error'}`);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
16
27
|
const loadWorkersModule = async () => {
|
|
17
28
|
try {
|
|
29
|
+
await ensureProjectWorkerEntrypointLoaded();
|
|
18
30
|
return (await loadWorkersRuntimeModule());
|
|
19
31
|
}
|
|
20
32
|
catch (error) {
|
|
@@ -232,6 +244,32 @@ const isRegisteredWorkerRunning = async (workerLike) => {
|
|
|
232
244
|
const isPaused = typeof pausedFn === 'function' ? pausedFn() : false;
|
|
233
245
|
return isRunning && !isPaused;
|
|
234
246
|
};
|
|
247
|
+
const resolveAutoStartWorkerNames = async (factory, workersModule) => {
|
|
248
|
+
const persistedRecords = await factory.listPersistedRecords();
|
|
249
|
+
const fileRecords = typeof factory.listFileBackedRecords === 'function'
|
|
250
|
+
? await factory.listFileBackedRecords()
|
|
251
|
+
: [];
|
|
252
|
+
if (typeof workersModule.selectAutoStartNames === 'function') {
|
|
253
|
+
return workersModule.selectAutoStartNames(persistedRecords, fileRecords, Logger.warn);
|
|
254
|
+
}
|
|
255
|
+
const persistedNames = persistedRecords
|
|
256
|
+
.filter((record) => record.activeStatus !== false && record.autoStart === true)
|
|
257
|
+
.map((record) => record.name);
|
|
258
|
+
if (persistedNames.length > 0) {
|
|
259
|
+
return { names: persistedNames, source: 'persisted' };
|
|
260
|
+
}
|
|
261
|
+
const seen = new Set();
|
|
262
|
+
const fileNames = [];
|
|
263
|
+
for (const record of fileRecords) {
|
|
264
|
+
if (record.activeStatus === false || record.autoStart !== true)
|
|
265
|
+
continue;
|
|
266
|
+
if (seen.has(record.name))
|
|
267
|
+
continue;
|
|
268
|
+
seen.add(record.name);
|
|
269
|
+
fileNames.push(record.name);
|
|
270
|
+
}
|
|
271
|
+
return { names: fileNames, source: fileNames.length > 0 ? 'file' : 'none' };
|
|
272
|
+
};
|
|
235
273
|
/**
|
|
236
274
|
* Worker Start All Command
|
|
237
275
|
*/
|
|
@@ -244,17 +282,19 @@ const createWorkerStartAllCommand = () => {
|
|
|
244
282
|
return;
|
|
245
283
|
}
|
|
246
284
|
const factory = await getWorkerFactory();
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
const workers = autoStartRecords.map((record) => record.name);
|
|
285
|
+
const workersModule = await loadWorkersModule();
|
|
286
|
+
const { names: workers, source } = await resolveAutoStartWorkerNames(factory, workersModule);
|
|
250
287
|
if (workers.length === 0) {
|
|
251
|
-
Logger.info('No auto-start eligible persisted workers found.');
|
|
288
|
+
Logger.info('No auto-start eligible persisted or file-backed workers found.');
|
|
252
289
|
return;
|
|
253
290
|
}
|
|
254
|
-
|
|
255
|
-
|
|
291
|
+
let eligibleWorkers = workers;
|
|
292
|
+
if (source === 'persisted') {
|
|
293
|
+
const discoveredWorkers = await pollForPersistedWorkers(factory);
|
|
294
|
+
eligibleWorkers = workers.filter((name) => discoveredWorkers.includes(name));
|
|
295
|
+
}
|
|
256
296
|
if (eligibleWorkers.length === 0) {
|
|
257
|
-
Logger.info('No auto-start eligible workers are currently
|
|
297
|
+
Logger.info('No auto-start eligible workers are currently available.');
|
|
258
298
|
return;
|
|
259
299
|
}
|
|
260
300
|
const results = await Promise.all(eligibleWorkers.map(async (name) => {
|
package/src/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @zintrust/core v0.4.
|
|
2
|
+
* @zintrust/core v0.4.27
|
|
3
3
|
*
|
|
4
4
|
* ZinTrust Framework - Production-Grade TypeScript Backend
|
|
5
5
|
* Built for performance, type safety, and exceptional developer experience
|
|
6
6
|
*
|
|
7
7
|
* Build Information:
|
|
8
|
-
* Built: 2026-03-
|
|
8
|
+
* Built: 2026-03-28T14:15:33.465Z
|
|
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-
|
|
24
|
+
export const ZINTRUST_BUILD_DATE = '2026-03-28T14:15:33.427Z'; // 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';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type ImportResult = {
|
|
2
|
+
ok: true;
|
|
3
|
+
loadedPath: string;
|
|
4
|
+
} | {
|
|
5
|
+
ok: false;
|
|
6
|
+
loadedPath?: string;
|
|
7
|
+
reason: 'not-found' | 'import-failed';
|
|
8
|
+
errorMessage?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare const WorkerProjectAutoImports: Readonly<{
|
|
11
|
+
tryImportProjectWorkerEntrypoint(): Promise<ImportResult>;
|
|
12
|
+
}>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=WorkerProjectAutoImports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkerProjectAutoImports.d.ts","sourceRoot":"","sources":["../../../src/runtime/WorkerProjectAutoImports.ts"],"names":[],"mappings":"AAKA,KAAK,YAAY,GACb;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAChC;IACE,EAAE,EAAE,KAAK,CAAC;IACV,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,WAAW,GAAG,eAAe,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AA6BN,eAAO,MAAM,wBAAwB;wCACO,OAAO,CAAC,YAAY,CAAC;EA0C/D,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Logger } from '../config/logger.js';
|
|
2
|
+
import { existsSync } from '../node-singletons/fs.js';
|
|
3
|
+
import * as path from '../node-singletons/path.js';
|
|
4
|
+
import { pathToFileURL } from '../node-singletons/url.js';
|
|
5
|
+
const resolveProjectRoot = () => {
|
|
6
|
+
const projectRoot = String(process.env['ZINTRUST_PROJECT_ROOT'] ?? '').trim();
|
|
7
|
+
return projectRoot.length > 0 ? projectRoot : process.cwd();
|
|
8
|
+
};
|
|
9
|
+
const getCandidates = (projectRoot) => {
|
|
10
|
+
return [
|
|
11
|
+
path.join(projectRoot, 'src', 'zintrust.workers.ts'),
|
|
12
|
+
path.join(projectRoot, 'dist', 'src', 'zintrust.workers.js'),
|
|
13
|
+
path.join(projectRoot, 'src', 'zintrust.workers.js'),
|
|
14
|
+
];
|
|
15
|
+
};
|
|
16
|
+
const invokeWorkerEntrypoint = async (mod) => {
|
|
17
|
+
let registerWorkers = null;
|
|
18
|
+
if (typeof mod['registerWorkers'] === 'function') {
|
|
19
|
+
registerWorkers = mod['registerWorkers'];
|
|
20
|
+
}
|
|
21
|
+
else if (typeof mod['default'] === 'function') {
|
|
22
|
+
registerWorkers = mod['default'];
|
|
23
|
+
}
|
|
24
|
+
if (registerWorkers !== null) {
|
|
25
|
+
await registerWorkers();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
export const WorkerProjectAutoImports = Object.freeze({
|
|
29
|
+
async tryImportProjectWorkerEntrypoint() {
|
|
30
|
+
const candidates = getCandidates(resolveProjectRoot()).filter((candidate) => existsSync(candidate));
|
|
31
|
+
if (candidates.length === 0) {
|
|
32
|
+
return { ok: false, reason: 'not-found' };
|
|
33
|
+
}
|
|
34
|
+
let firstFailure = null;
|
|
35
|
+
for (const candidate of candidates) {
|
|
36
|
+
try {
|
|
37
|
+
// eslint-disable-next-line no-await-in-loop
|
|
38
|
+
const mod = (await import(pathToFileURL(candidate).href));
|
|
39
|
+
// eslint-disable-next-line no-await-in-loop
|
|
40
|
+
await invokeWorkerEntrypoint(mod);
|
|
41
|
+
return { ok: true, loadedPath: candidate };
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
45
|
+
Logger.debug('[workers] Project worker entrypoint import failed', {
|
|
46
|
+
candidate,
|
|
47
|
+
errorMessage,
|
|
48
|
+
});
|
|
49
|
+
firstFailure ??= {
|
|
50
|
+
ok: false,
|
|
51
|
+
loadedPath: candidate,
|
|
52
|
+
reason: 'import-failed',
|
|
53
|
+
errorMessage,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return (firstFailure ?? {
|
|
58
|
+
ok: false,
|
|
59
|
+
reason: 'import-failed',
|
|
60
|
+
errorMessage: 'All worker entrypoint candidates failed',
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Logger } from '@zintrust/core';
|
|
2
|
+
|
|
3
|
+
export const workerDefinition = Object.freeze({
|
|
4
|
+
name: 'example-worker',
|
|
5
|
+
queueName: 'example-worker',
|
|
6
|
+
version: '1.0.0',
|
|
7
|
+
autoStart: false,
|
|
8
|
+
activeStatus: true,
|
|
9
|
+
concurrency: 1,
|
|
10
|
+
processorSpec: 'app/Workers/ExampleWorker.ts',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export async function ZinTrustProcessor(payload: unknown): Promise<void> {
|
|
14
|
+
Logger.info('Example worker processed job', { payload });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default ZinTrustProcessor;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional ZinTrust worker bootstrap.
|
|
3
|
+
*
|
|
4
|
+
* Docker worker containers and worker CLI commands auto-load this file when it exists.
|
|
5
|
+
* For simple code-first workers that export `workerDefinition`, you usually do not need to
|
|
6
|
+
* update this file after scaffolding. Keep it for advanced pre-registration or grouped imports.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import '@app/Workers/ExampleWorker';
|
|
10
|
+
|
|
11
|
+
export const __zintrustGeneratedWorkerStub = 'zintrust.workers.ts';
|