tina4-nodejs 3.0.0-rc.2
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/BENCHMARK_REPORT.md +96 -0
- package/CARBONAH.md +140 -0
- package/CLAUDE.md +599 -0
- package/COMPARISON.md +194 -0
- package/README.md +595 -0
- package/package.json +59 -0
- package/packages/cli/src/bin.ts +110 -0
- package/packages/cli/src/commands/init.ts +194 -0
- package/packages/cli/src/commands/migrate.ts +96 -0
- package/packages/cli/src/commands/migrateCreate.ts +59 -0
- package/packages/cli/src/commands/routes.ts +61 -0
- package/packages/cli/src/commands/serve.ts +58 -0
- package/packages/cli/src/commands/test.ts +83 -0
- package/packages/core/gallery/auth/meta.json +1 -0
- package/packages/core/gallery/auth/src/routes/api/gallery/auth/login/post.ts +22 -0
- package/packages/core/gallery/auth/src/routes/api/gallery/auth/verify/get.ts +16 -0
- package/packages/core/gallery/auth/src/routes/gallery/auth/get.ts +97 -0
- package/packages/core/gallery/database/meta.json +1 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/notes/get.ts +13 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/notes/post.ts +17 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/tables/get.ts +23 -0
- package/packages/core/gallery/error-overlay/meta.json +1 -0
- package/packages/core/gallery/error-overlay/src/routes/api/gallery/crash/get.ts +17 -0
- package/packages/core/gallery/orm/meta.json +1 -0
- package/packages/core/gallery/orm/src/routes/api/gallery/products/get.ts +12 -0
- package/packages/core/gallery/orm/src/routes/api/gallery/products/post.ts +7 -0
- package/packages/core/gallery/queue/meta.json +1 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/produce/post.ts +16 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/status/get.ts +10 -0
- package/packages/core/gallery/rest-api/meta.json +1 -0
- package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/get.ts +6 -0
- package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/post.ts +7 -0
- package/packages/core/gallery/templates/meta.json +1 -0
- package/packages/core/gallery/templates/src/routes/gallery/page/get.ts +15 -0
- package/packages/core/gallery/templates/src/templates/gallery_page.twig +257 -0
- package/packages/core/public/css/tina4.css +2463 -0
- package/packages/core/public/css/tina4.min.css +1 -0
- package/packages/core/public/favicon.ico +0 -0
- package/packages/core/public/images/logo.svg +5 -0
- package/packages/core/public/images/tina4-logo-icon.webp +0 -0
- package/packages/core/public/js/frond.min.js +420 -0
- package/packages/core/public/js/tina4-dev-admin.min.js +327 -0
- package/packages/core/public/js/tina4.min.js +93 -0
- package/packages/core/public/swagger/index.html +90 -0
- package/packages/core/public/swagger/oauth2-redirect.html +63 -0
- package/packages/core/src/ai.ts +359 -0
- package/packages/core/src/api.ts +248 -0
- package/packages/core/src/auth.ts +287 -0
- package/packages/core/src/cache.ts +121 -0
- package/packages/core/src/constants.ts +48 -0
- package/packages/core/src/container.ts +90 -0
- package/packages/core/src/devAdmin.ts +2024 -0
- package/packages/core/src/devMailbox.ts +316 -0
- package/packages/core/src/dotenv.ts +172 -0
- package/packages/core/src/errorOverlay.test.ts +122 -0
- package/packages/core/src/errorOverlay.ts +278 -0
- package/packages/core/src/events.ts +112 -0
- package/packages/core/src/fakeData.ts +309 -0
- package/packages/core/src/graphql.ts +812 -0
- package/packages/core/src/health.ts +31 -0
- package/packages/core/src/htmlElement.ts +172 -0
- package/packages/core/src/i18n.ts +136 -0
- package/packages/core/src/index.ts +88 -0
- package/packages/core/src/logger.ts +226 -0
- package/packages/core/src/messenger.ts +822 -0
- package/packages/core/src/middleware.ts +138 -0
- package/packages/core/src/queue.ts +481 -0
- package/packages/core/src/queueBackends/kafkaBackend.ts +348 -0
- package/packages/core/src/queueBackends/rabbitmqBackend.ts +479 -0
- package/packages/core/src/rateLimiter.ts +107 -0
- package/packages/core/src/request.ts +189 -0
- package/packages/core/src/response.ts +146 -0
- package/packages/core/src/routeDiscovery.ts +87 -0
- package/packages/core/src/router.ts +398 -0
- package/packages/core/src/scss.ts +366 -0
- package/packages/core/src/server.ts +610 -0
- package/packages/core/src/service.ts +380 -0
- package/packages/core/src/session.ts +480 -0
- package/packages/core/src/sessionHandlers/mongoHandler.ts +286 -0
- package/packages/core/src/sessionHandlers/valkeyHandler.ts +184 -0
- package/packages/core/src/static.ts +58 -0
- package/packages/core/src/testing.ts +233 -0
- package/packages/core/src/types.ts +98 -0
- package/packages/core/src/watcher.ts +37 -0
- package/packages/core/src/websocket.ts +408 -0
- package/packages/core/src/wsdl.ts +546 -0
- package/packages/core/templates/errors/302.twig +14 -0
- package/packages/core/templates/errors/401.twig +9 -0
- package/packages/core/templates/errors/403.twig +29 -0
- package/packages/core/templates/errors/404.twig +29 -0
- package/packages/core/templates/errors/500.twig +38 -0
- package/packages/core/templates/errors/502.twig +9 -0
- package/packages/core/templates/errors/503.twig +12 -0
- package/packages/core/templates/errors/base.twig +37 -0
- package/packages/frond/src/engine.ts +1475 -0
- package/packages/frond/src/index.ts +2 -0
- package/packages/orm/src/adapters/firebird.ts +455 -0
- package/packages/orm/src/adapters/mssql.ts +440 -0
- package/packages/orm/src/adapters/mysql.ts +355 -0
- package/packages/orm/src/adapters/postgres.ts +362 -0
- package/packages/orm/src/adapters/sqlite.ts +270 -0
- package/packages/orm/src/autoCrud.ts +231 -0
- package/packages/orm/src/baseModel.ts +536 -0
- package/packages/orm/src/database.ts +321 -0
- package/packages/orm/src/fakeData.ts +118 -0
- package/packages/orm/src/index.ts +49 -0
- package/packages/orm/src/migration.ts +392 -0
- package/packages/orm/src/model.ts +56 -0
- package/packages/orm/src/query.ts +113 -0
- package/packages/orm/src/seeder.ts +120 -0
- package/packages/orm/src/sqlTranslation.ts +272 -0
- package/packages/orm/src/types.ts +110 -0
- package/packages/orm/src/validation.ts +93 -0
- package/packages/swagger/src/generator.ts +189 -0
- package/packages/swagger/src/index.ts +2 -0
- package/packages/swagger/src/ui.ts +48 -0
- package/skills/tina4-developer.skill +0 -0
- package/skills/tina4-js.skill +0 -0
- package/skills/tina4-maintainer.skill +0 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Runner — in-process background services using Node.js timers.
|
|
3
|
+
* Zero dependencies. Supports cron timing, simple intervals, and daemon mode.
|
|
4
|
+
*/
|
|
5
|
+
import { readdirSync, statSync, watchFile, unwatchFile } from "node:fs";
|
|
6
|
+
import { join, extname } from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
|
|
9
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
export interface ServiceOptions {
|
|
12
|
+
timing?: string; // cron: "*/5 * * * *"
|
|
13
|
+
daemon?: boolean; // continuous mode
|
|
14
|
+
interval?: number; // simple interval in seconds (alternative to cron)
|
|
15
|
+
maxRetries?: number; // restart on crash, default 3
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ServiceContext {
|
|
19
|
+
running: boolean;
|
|
20
|
+
lastRun: Date | null;
|
|
21
|
+
name: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type ServiceHandler = (context: ServiceContext) => Promise<void> | void;
|
|
25
|
+
|
|
26
|
+
export interface ServiceInfo {
|
|
27
|
+
name: string;
|
|
28
|
+
options: ServiceOptions;
|
|
29
|
+
running: boolean;
|
|
30
|
+
lastRun: Date | null;
|
|
31
|
+
retries: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─── Internal state ──────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
interface RegisteredService {
|
|
37
|
+
name: string;
|
|
38
|
+
handler: ServiceHandler;
|
|
39
|
+
options: ServiceOptions;
|
|
40
|
+
context: ServiceContext;
|
|
41
|
+
timerId: ReturnType<typeof setInterval> | null;
|
|
42
|
+
retries: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const registry = new Map<string, RegisteredService>();
|
|
46
|
+
const watchedFiles = new Set<string>();
|
|
47
|
+
|
|
48
|
+
// ─── Cron parser ─────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Parse a single cron field and check if the given value matches.
|
|
52
|
+
* Supports: * (every), N/n (step), N,N,N (list), N-N (range), plain number.
|
|
53
|
+
*/
|
|
54
|
+
export function matchCronField(field: string, value: number): boolean {
|
|
55
|
+
// wildcard
|
|
56
|
+
if (field === "*") return true;
|
|
57
|
+
|
|
58
|
+
// step: */5 or N/5
|
|
59
|
+
if (field.includes("/")) {
|
|
60
|
+
const [base, stepStr] = field.split("/");
|
|
61
|
+
const step = parseInt(stepStr, 10);
|
|
62
|
+
if (isNaN(step) || step <= 0) return false;
|
|
63
|
+
if (base === "*") return value % step === 0;
|
|
64
|
+
const start = parseInt(base, 10);
|
|
65
|
+
if (isNaN(start)) return false;
|
|
66
|
+
return value >= start && (value - start) % step === 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// list: 1,15,30
|
|
70
|
+
if (field.includes(",")) {
|
|
71
|
+
return field.split(",").some((part) => matchCronField(part.trim(), value));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// range: 1-5
|
|
75
|
+
if (field.includes("-")) {
|
|
76
|
+
const [loStr, hiStr] = field.split("-");
|
|
77
|
+
const lo = parseInt(loStr, 10);
|
|
78
|
+
const hi = parseInt(hiStr, 10);
|
|
79
|
+
if (isNaN(lo) || isNaN(hi)) return false;
|
|
80
|
+
return value >= lo && value <= hi;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// exact number
|
|
84
|
+
return parseInt(field, 10) === value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Check whether a Date matches a 5-field cron expression.
|
|
89
|
+
* Fields: minute hour dayOfMonth month dayOfWeek
|
|
90
|
+
*/
|
|
91
|
+
export function matchesCron(expression: string, date: Date): boolean {
|
|
92
|
+
const fields = expression.trim().split(/\s+/);
|
|
93
|
+
if (fields.length !== 5) return false;
|
|
94
|
+
|
|
95
|
+
const minute = date.getMinutes();
|
|
96
|
+
const hour = date.getHours();
|
|
97
|
+
const dayOfMonth = date.getDate();
|
|
98
|
+
const month = date.getMonth() + 1; // JS months are 0-based
|
|
99
|
+
const dayOfWeek = date.getDay(); // 0 = Sunday
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
matchCronField(fields[0], minute) &&
|
|
103
|
+
matchCronField(fields[1], hour) &&
|
|
104
|
+
matchCronField(fields[2], dayOfMonth) &&
|
|
105
|
+
matchCronField(fields[3], month) &&
|
|
106
|
+
matchCronField(fields[4], dayOfWeek)
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ─── Execution helpers ───────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
async function executeHandler(svc: RegisteredService): Promise<void> {
|
|
113
|
+
try {
|
|
114
|
+
await svc.handler(svc.context);
|
|
115
|
+
svc.context.lastRun = new Date();
|
|
116
|
+
svc.retries = 0; // reset retries on success
|
|
117
|
+
} catch (err) {
|
|
118
|
+
const maxRetries = svc.options.maxRetries ?? 3;
|
|
119
|
+
svc.retries++;
|
|
120
|
+
if (svc.retries >= maxRetries) {
|
|
121
|
+
svc.context.running = false;
|
|
122
|
+
if (svc.timerId) {
|
|
123
|
+
clearInterval(svc.timerId);
|
|
124
|
+
svc.timerId = null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function startCronService(svc: RegisteredService): void {
|
|
131
|
+
const checkIntervalMs = parseInt(
|
|
132
|
+
process.env.TINA4_SERVICE_INTERVAL ?? "1000",
|
|
133
|
+
10,
|
|
134
|
+
);
|
|
135
|
+
let lastMinuteRun = -1;
|
|
136
|
+
|
|
137
|
+
svc.timerId = setInterval(() => {
|
|
138
|
+
if (!svc.context.running) return;
|
|
139
|
+
const now = new Date();
|
|
140
|
+
const currentMinute = now.getMinutes();
|
|
141
|
+
// Avoid running the same minute twice
|
|
142
|
+
if (currentMinute === lastMinuteRun) return;
|
|
143
|
+
if (matchesCron(svc.options.timing!, now)) {
|
|
144
|
+
lastMinuteRun = currentMinute;
|
|
145
|
+
executeHandler(svc);
|
|
146
|
+
}
|
|
147
|
+
}, checkIntervalMs);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function startIntervalService(svc: RegisteredService): void {
|
|
151
|
+
const intervalMs = (svc.options.interval ?? 60) * 1000;
|
|
152
|
+
svc.timerId = setInterval(() => {
|
|
153
|
+
if (!svc.context.running) return;
|
|
154
|
+
executeHandler(svc);
|
|
155
|
+
}, intervalMs);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function startDaemonService(svc: RegisteredService): void {
|
|
159
|
+
// Daemon runs once; the handler manages its own loop using context.running
|
|
160
|
+
executeHandler(svc);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ─── ServiceRunner ───────────────────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
export class ServiceRunner {
|
|
166
|
+
/**
|
|
167
|
+
* Register a service with a handler and options.
|
|
168
|
+
*/
|
|
169
|
+
static register(
|
|
170
|
+
name: string,
|
|
171
|
+
handler: ServiceHandler,
|
|
172
|
+
options: ServiceOptions = {},
|
|
173
|
+
): void {
|
|
174
|
+
const context: ServiceContext = {
|
|
175
|
+
running: false,
|
|
176
|
+
lastRun: null,
|
|
177
|
+
name,
|
|
178
|
+
};
|
|
179
|
+
registry.set(name, {
|
|
180
|
+
name,
|
|
181
|
+
handler,
|
|
182
|
+
options,
|
|
183
|
+
context,
|
|
184
|
+
timerId: null,
|
|
185
|
+
retries: 0,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Discover services from a directory. Each file should export
|
|
191
|
+
* { name, handler, timing?, interval?, daemon?, maxRetries? }.
|
|
192
|
+
*/
|
|
193
|
+
static async discover(serviceDir?: string): Promise<ServiceInfo[]> {
|
|
194
|
+
const dir =
|
|
195
|
+
serviceDir ?? process.env.TINA4_SERVICE_DIR ?? "src/services";
|
|
196
|
+
const discovered: ServiceInfo[] = [];
|
|
197
|
+
|
|
198
|
+
let entries: string[];
|
|
199
|
+
try {
|
|
200
|
+
entries = readdirSync(dir);
|
|
201
|
+
} catch {
|
|
202
|
+
return discovered;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (const entry of entries) {
|
|
206
|
+
const ext = extname(entry);
|
|
207
|
+
if (ext !== ".ts" && ext !== ".js") continue;
|
|
208
|
+
|
|
209
|
+
const fullPath = join(dir, entry);
|
|
210
|
+
const stat = statSync(fullPath);
|
|
211
|
+
if (!stat.isFile()) continue;
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const fileUrl = pathToFileURL(fullPath).href;
|
|
215
|
+
const mod = await import(fileUrl);
|
|
216
|
+
const exp = mod.default ?? mod;
|
|
217
|
+
|
|
218
|
+
if (exp.name && typeof exp.handler === "function") {
|
|
219
|
+
const opts: ServiceOptions = {
|
|
220
|
+
timing: exp.timing,
|
|
221
|
+
daemon: exp.daemon,
|
|
222
|
+
interval: exp.interval,
|
|
223
|
+
maxRetries: exp.maxRetries,
|
|
224
|
+
};
|
|
225
|
+
ServiceRunner.register(exp.name, exp.handler, opts);
|
|
226
|
+
discovered.push({
|
|
227
|
+
name: exp.name,
|
|
228
|
+
options: opts,
|
|
229
|
+
running: false,
|
|
230
|
+
lastRun: null,
|
|
231
|
+
retries: 0,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
// skip files that fail to import
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return discovered;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Start all registered services, or a specific one by name.
|
|
244
|
+
*/
|
|
245
|
+
static start(name?: string): void {
|
|
246
|
+
const targets = name
|
|
247
|
+
? [registry.get(name)].filter(Boolean) as RegisteredService[]
|
|
248
|
+
: Array.from(registry.values());
|
|
249
|
+
|
|
250
|
+
for (const svc of targets) {
|
|
251
|
+
if (svc.context.running) continue;
|
|
252
|
+
svc.context.running = true;
|
|
253
|
+
svc.retries = 0;
|
|
254
|
+
|
|
255
|
+
if (svc.options.daemon) {
|
|
256
|
+
startDaemonService(svc);
|
|
257
|
+
} else if (svc.options.timing) {
|
|
258
|
+
startCronService(svc);
|
|
259
|
+
} else if (svc.options.interval != null) {
|
|
260
|
+
startIntervalService(svc);
|
|
261
|
+
} else {
|
|
262
|
+
// Default: run once immediately
|
|
263
|
+
executeHandler(svc);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Stop all running services, or a specific one by name.
|
|
270
|
+
*/
|
|
271
|
+
static stop(name?: string): void {
|
|
272
|
+
const targets = name
|
|
273
|
+
? [registry.get(name)].filter(Boolean) as RegisteredService[]
|
|
274
|
+
: Array.from(registry.values());
|
|
275
|
+
|
|
276
|
+
for (const svc of targets) {
|
|
277
|
+
svc.context.running = false;
|
|
278
|
+
if (svc.timerId) {
|
|
279
|
+
clearInterval(svc.timerId);
|
|
280
|
+
svc.timerId = null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* List all registered services with their current state.
|
|
287
|
+
*/
|
|
288
|
+
static list(): ServiceInfo[] {
|
|
289
|
+
return Array.from(registry.values()).map((svc) => ({
|
|
290
|
+
name: svc.name,
|
|
291
|
+
options: svc.options,
|
|
292
|
+
running: svc.context.running,
|
|
293
|
+
lastRun: svc.context.lastRun,
|
|
294
|
+
retries: svc.retries,
|
|
295
|
+
}));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Check if a specific service is running.
|
|
300
|
+
*/
|
|
301
|
+
static isRunning(name: string): boolean {
|
|
302
|
+
const svc = registry.get(name);
|
|
303
|
+
return svc?.context.running ?? false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Remove a service from the registry (stops it first if running).
|
|
308
|
+
*/
|
|
309
|
+
static remove(name: string): boolean {
|
|
310
|
+
const svc = registry.get(name);
|
|
311
|
+
if (!svc) return false;
|
|
312
|
+
ServiceRunner.stop(name);
|
|
313
|
+
return registry.delete(name);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Clear all registered services (stops them all first).
|
|
318
|
+
*/
|
|
319
|
+
static clear(): void {
|
|
320
|
+
ServiceRunner.stop();
|
|
321
|
+
registry.clear();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Watch service files for changes and hot-reload in dev mode.
|
|
326
|
+
*/
|
|
327
|
+
static watch(serviceDir?: string): void {
|
|
328
|
+
const dir =
|
|
329
|
+
serviceDir ?? process.env.TINA4_SERVICE_DIR ?? "src/services";
|
|
330
|
+
|
|
331
|
+
let entries: string[];
|
|
332
|
+
try {
|
|
333
|
+
entries = readdirSync(dir);
|
|
334
|
+
} catch {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
for (const entry of entries) {
|
|
339
|
+
const ext = extname(entry);
|
|
340
|
+
if (ext !== ".ts" && ext !== ".js") continue;
|
|
341
|
+
const fullPath = join(dir, entry);
|
|
342
|
+
if (watchedFiles.has(fullPath)) continue;
|
|
343
|
+
watchedFiles.add(fullPath);
|
|
344
|
+
|
|
345
|
+
watchFile(fullPath, { interval: 1000 }, async () => {
|
|
346
|
+
// Re-discover and restart
|
|
347
|
+
const fileUrl = pathToFileURL(fullPath).href;
|
|
348
|
+
try {
|
|
349
|
+
// Bust module cache by appending timestamp
|
|
350
|
+
const mod = await import(fileUrl + "?t=" + Date.now());
|
|
351
|
+
const exp = mod.default ?? mod;
|
|
352
|
+
if (exp.name && typeof exp.handler === "function") {
|
|
353
|
+
ServiceRunner.stop(exp.name);
|
|
354
|
+
ServiceRunner.remove(exp.name);
|
|
355
|
+
const opts: ServiceOptions = {
|
|
356
|
+
timing: exp.timing,
|
|
357
|
+
daemon: exp.daemon,
|
|
358
|
+
interval: exp.interval,
|
|
359
|
+
maxRetries: exp.maxRetries,
|
|
360
|
+
};
|
|
361
|
+
ServiceRunner.register(exp.name, exp.handler, opts);
|
|
362
|
+
ServiceRunner.start(exp.name);
|
|
363
|
+
}
|
|
364
|
+
} catch {
|
|
365
|
+
// skip
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Stop watching service files.
|
|
373
|
+
*/
|
|
374
|
+
static unwatch(): void {
|
|
375
|
+
for (const filePath of watchedFiles) {
|
|
376
|
+
unwatchFile(filePath);
|
|
377
|
+
}
|
|
378
|
+
watchedFiles.clear();
|
|
379
|
+
}
|
|
380
|
+
}
|