@donkeylabs/server 2.0.21 → 2.0.23
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/docs/workflows.md +107 -7
- package/package.json +2 -2
- package/src/admin/dashboard.ts +74 -3
- package/src/admin/routes.ts +62 -0
- package/src/core/cron.ts +17 -10
- package/src/core/index.ts +23 -0
- package/src/core/jobs.ts +8 -2
- package/src/core/logger.ts +14 -0
- package/src/core/logs-adapter-kysely.ts +287 -0
- package/src/core/logs-transport.ts +83 -0
- package/src/core/logs.ts +398 -0
- package/src/core/subprocess-bootstrap.ts +241 -0
- package/src/core/workflow-executor.ts +50 -36
- package/src/core/workflow-socket.ts +1 -0
- package/src/core/workflows.test.ts +56 -0
- package/src/core/workflows.ts +350 -33
- package/src/core.ts +83 -3
- package/src/harness.ts +4 -0
- package/src/index.ts +10 -0
- package/src/server.ts +53 -5
- /package/{CLAUDE.md → agents.md} +0 -0
package/src/core.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { sql, type Kysely } from "kysely";
|
|
2
2
|
import { readdir } from "node:fs/promises";
|
|
3
|
-
import { join, dirname } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { join, dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import type { z } from "zod";
|
|
6
6
|
import type { Logger } from "./core/logger";
|
|
7
7
|
import type { Cache } from "./core/cache";
|
|
@@ -16,6 +16,33 @@ import type { Processes } from "./core/processes";
|
|
|
16
16
|
import type { Audit } from "./core/audit";
|
|
17
17
|
import type { WebSocketService } from "./core/websocket";
|
|
18
18
|
import type { Storage } from "./core/storage";
|
|
19
|
+
import type { Logs } from "./core/logs";
|
|
20
|
+
|
|
21
|
+
// ============================================
|
|
22
|
+
// Auto-detect caller module for plugin define()
|
|
23
|
+
// ============================================
|
|
24
|
+
|
|
25
|
+
const CORE_FILE = resolve(fileURLToPath(import.meta.url));
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Walk the call stack to find the file that invoked define().
|
|
29
|
+
* Returns a file:// URL string or undefined if detection fails.
|
|
30
|
+
* Skips frames originating from this file (core.ts).
|
|
31
|
+
*/
|
|
32
|
+
function captureCallerUrl(): string | undefined {
|
|
33
|
+
const stack = new Error().stack ?? "";
|
|
34
|
+
for (const line of stack.split("\n").slice(1)) {
|
|
35
|
+
const match = line.match(/at\s+(?:.*?\s+\(?)?([^\s():]+):\d+:\d+/);
|
|
36
|
+
if (match) {
|
|
37
|
+
let filePath = match[1];
|
|
38
|
+
if (filePath.startsWith("file://")) filePath = fileURLToPath(filePath);
|
|
39
|
+
if (filePath.startsWith("native")) continue;
|
|
40
|
+
filePath = resolve(filePath);
|
|
41
|
+
if (filePath !== CORE_FILE) return pathToFileURL(filePath).href;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
19
46
|
|
|
20
47
|
export interface PluginRegistry {}
|
|
21
48
|
|
|
@@ -109,6 +136,7 @@ export interface CoreServices {
|
|
|
109
136
|
audit: Audit;
|
|
110
137
|
websocket: WebSocketService;
|
|
111
138
|
storage: Storage;
|
|
139
|
+
logs: Logs;
|
|
112
140
|
}
|
|
113
141
|
|
|
114
142
|
/**
|
|
@@ -328,7 +356,7 @@ export class PluginBuilder<LocalSchema = {}> {
|
|
|
328
356
|
client?: ClientConfig;
|
|
329
357
|
customErrors?: CustomErrors;
|
|
330
358
|
} {
|
|
331
|
-
return config as any;
|
|
359
|
+
return { ...config, _modulePath: captureCallerUrl() } as any;
|
|
332
360
|
}
|
|
333
361
|
}
|
|
334
362
|
|
|
@@ -384,9 +412,11 @@ export class ConfiguredPluginBuilder<LocalSchema, Config> {
|
|
|
384
412
|
client?: ClientConfig;
|
|
385
413
|
customErrors?: CustomErrors;
|
|
386
414
|
}> {
|
|
415
|
+
const modulePath = captureCallerUrl();
|
|
387
416
|
const factory = (config: Config) => ({
|
|
388
417
|
...pluginDef,
|
|
389
418
|
_boundConfig: config,
|
|
419
|
+
_modulePath: modulePath,
|
|
390
420
|
});
|
|
391
421
|
return factory as any;
|
|
392
422
|
}
|
|
@@ -423,6 +453,8 @@ export type Plugin = {
|
|
|
423
453
|
service: (ctx: any) => any;
|
|
424
454
|
/** Called after service is created - use for registering crons, events, etc. */
|
|
425
455
|
init?: (ctx: any, service: any) => void | Promise<void>;
|
|
456
|
+
/** Auto-detected module path where the plugin was defined */
|
|
457
|
+
_modulePath?: string;
|
|
426
458
|
};
|
|
427
459
|
|
|
428
460
|
export type PluginWithConfig<Config = void> = Plugin & {
|
|
@@ -452,6 +484,54 @@ export class PluginManager {
|
|
|
452
484
|
return Array.from(this.plugins.values());
|
|
453
485
|
}
|
|
454
486
|
|
|
487
|
+
getPluginNames(): string[] {
|
|
488
|
+
return Array.from(this.plugins.keys());
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/** Returns { name: modulePath } for plugins that have a captured module path */
|
|
492
|
+
getPluginModulePaths(): Record<string, string> {
|
|
493
|
+
const result: Record<string, string> = {};
|
|
494
|
+
for (const [name, plugin] of this.plugins) {
|
|
495
|
+
if (plugin._modulePath) {
|
|
496
|
+
result[name] = plugin._modulePath;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return result;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** Returns { name: boundConfig } for configured plugins */
|
|
503
|
+
getPluginConfigs(): Record<string, any> {
|
|
504
|
+
const result: Record<string, any> = {};
|
|
505
|
+
for (const [name, plugin] of this.plugins) {
|
|
506
|
+
if ((plugin as ConfiguredPlugin)._boundConfig !== undefined) {
|
|
507
|
+
result[name] = (plugin as ConfiguredPlugin)._boundConfig;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return result;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/** Returns { name: [...deps] } for plugins with dependencies */
|
|
514
|
+
getPluginDependencies(): Record<string, string[]> {
|
|
515
|
+
const result: Record<string, string[]> = {};
|
|
516
|
+
for (const [name, plugin] of this.plugins) {
|
|
517
|
+
if (plugin.dependencies && plugin.dependencies.length > 0) {
|
|
518
|
+
result[name] = [...plugin.dependencies];
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return result;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/** Returns custom error definitions per plugin */
|
|
525
|
+
getPluginCustomErrors(): Record<string, Record<string, any>> {
|
|
526
|
+
const result: Record<string, Record<string, any>> = {};
|
|
527
|
+
for (const [name, plugin] of this.plugins) {
|
|
528
|
+
if (plugin.customErrors && Object.keys(plugin.customErrors).length > 0) {
|
|
529
|
+
result[name] = plugin.customErrors;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return result;
|
|
533
|
+
}
|
|
534
|
+
|
|
455
535
|
register(plugin: ConfiguredPlugin): void {
|
|
456
536
|
if (this.plugins.has(plugin.name)) {
|
|
457
537
|
throw new Error(`Plugin ${plugin.name} is already registered.`);
|
package/src/harness.ts
CHANGED
|
@@ -16,9 +16,11 @@ import {
|
|
|
16
16
|
createAudit,
|
|
17
17
|
createWebSocket,
|
|
18
18
|
createStorage,
|
|
19
|
+
createLogs,
|
|
19
20
|
KyselyJobAdapter,
|
|
20
21
|
KyselyWorkflowAdapter,
|
|
21
22
|
MemoryAuditAdapter,
|
|
23
|
+
MemoryLogsAdapter,
|
|
22
24
|
} from "./core/index";
|
|
23
25
|
import { AppServer, type ServerConfig } from "./server";
|
|
24
26
|
import type { IRouter, RouteDefinition } from "./router";
|
|
@@ -68,6 +70,7 @@ export async function createTestHarness(targetPlugin: Plugin, dependencies: Plug
|
|
|
68
70
|
const audit = createAudit({ adapter: new MemoryAuditAdapter() });
|
|
69
71
|
const websocket = createWebSocket();
|
|
70
72
|
const storage = createStorage(); // Uses memory adapter by default
|
|
73
|
+
const logs = createLogs({ adapter: new MemoryLogsAdapter(), events });
|
|
71
74
|
|
|
72
75
|
const core: CoreServices = {
|
|
73
76
|
db,
|
|
@@ -85,6 +88,7 @@ export async function createTestHarness(targetPlugin: Plugin, dependencies: Plug
|
|
|
85
88
|
audit,
|
|
86
89
|
websocket,
|
|
87
90
|
storage,
|
|
91
|
+
logs,
|
|
88
92
|
};
|
|
89
93
|
|
|
90
94
|
const manager = new PluginManager(core);
|
package/src/index.ts
CHANGED
|
@@ -99,6 +99,16 @@ export {
|
|
|
99
99
|
type ErrorFactories,
|
|
100
100
|
} from "./core/index";
|
|
101
101
|
|
|
102
|
+
// Logs (persistent logging)
|
|
103
|
+
export {
|
|
104
|
+
type Logs,
|
|
105
|
+
type LogSource,
|
|
106
|
+
type PersistentLogEntry,
|
|
107
|
+
type LogsQueryFilters,
|
|
108
|
+
type LogsConfig,
|
|
109
|
+
type LogsRetentionConfig,
|
|
110
|
+
} from "./core/logs";
|
|
111
|
+
|
|
102
112
|
// Workflows (step functions)
|
|
103
113
|
export {
|
|
104
114
|
workflow,
|
package/src/server.ts
CHANGED
|
@@ -19,12 +19,16 @@ import {
|
|
|
19
19
|
createAudit,
|
|
20
20
|
createWebSocket,
|
|
21
21
|
createStorage,
|
|
22
|
+
createLogs,
|
|
22
23
|
extractClientIP,
|
|
23
24
|
HttpError,
|
|
24
25
|
KyselyJobAdapter,
|
|
25
26
|
KyselyProcessAdapter,
|
|
26
27
|
KyselyWorkflowAdapter,
|
|
27
28
|
KyselyAuditAdapter,
|
|
29
|
+
KyselyLogsAdapter,
|
|
30
|
+
PersistentTransport,
|
|
31
|
+
ConsoleTransport,
|
|
28
32
|
type LoggerConfig,
|
|
29
33
|
type CacheConfig,
|
|
30
34
|
type EventsConfig,
|
|
@@ -38,6 +42,7 @@ import {
|
|
|
38
42
|
type AuditConfig,
|
|
39
43
|
type WebSocketConfig,
|
|
40
44
|
type StorageConfig,
|
|
45
|
+
type LogsConfig,
|
|
41
46
|
} from "./core/index";
|
|
42
47
|
import type { AdminConfig } from "./admin";
|
|
43
48
|
import { zodSchemaToTs } from "./generator/zod-to-ts";
|
|
@@ -80,6 +85,7 @@ export interface ServerConfig {
|
|
|
80
85
|
audit?: AuditConfig;
|
|
81
86
|
websocket?: WebSocketConfig;
|
|
82
87
|
storage?: StorageConfig;
|
|
88
|
+
logs?: LogsConfig;
|
|
83
89
|
/**
|
|
84
90
|
* Admin dashboard configuration.
|
|
85
91
|
* Automatically enabled in dev mode, disabled in production.
|
|
@@ -224,23 +230,52 @@ export class AppServer {
|
|
|
224
230
|
const useLegacy = options.useLegacyCoreDatabases ?? false;
|
|
225
231
|
|
|
226
232
|
// Initialize core services
|
|
227
|
-
|
|
233
|
+
// Order matters: events → logs → logger (with PersistentTransport) → cron/jobs (with logger)
|
|
228
234
|
const cache = createCache(options.cache);
|
|
229
235
|
const events = createEvents(options.events);
|
|
230
|
-
const cron = createCron(options.cron);
|
|
231
236
|
const sse = createSSE(options.sse);
|
|
232
237
|
const rateLimiter = createRateLimiter(options.rateLimiter);
|
|
233
238
|
const errors = createErrors(options.errors);
|
|
234
239
|
|
|
240
|
+
// Create logs service with its own database
|
|
241
|
+
const logsAdapter = options.logs?.adapter ?? new KyselyLogsAdapter({
|
|
242
|
+
dbPath: options.logs?.dbPath,
|
|
243
|
+
});
|
|
244
|
+
const logs = createLogs({
|
|
245
|
+
...options.logs,
|
|
246
|
+
adapter: logsAdapter,
|
|
247
|
+
events,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Create logger with both console and persistent transports
|
|
251
|
+
const persistentTransport = new PersistentTransport(logs, {
|
|
252
|
+
minLevel: options.logs?.minLevel,
|
|
253
|
+
});
|
|
254
|
+
const loggerTransports = [
|
|
255
|
+
new ConsoleTransport(options.logger?.format ?? "pretty"),
|
|
256
|
+
persistentTransport,
|
|
257
|
+
];
|
|
258
|
+
const logger = createLogger({
|
|
259
|
+
...options.logger,
|
|
260
|
+
transports: options.logger?.transports ?? loggerTransports,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Cron with logger for scoped logging
|
|
264
|
+
const cron = createCron({
|
|
265
|
+
...options.cron,
|
|
266
|
+
logger,
|
|
267
|
+
});
|
|
268
|
+
|
|
235
269
|
// Create adapters - use Kysely by default, or legacy SQLite if requested
|
|
236
270
|
const jobAdapter = options.jobs?.adapter ?? (useLegacy ? undefined : new KyselyJobAdapter(options.db));
|
|
237
271
|
const workflowAdapter = options.workflows?.adapter ?? (useLegacy ? undefined : new KyselyWorkflowAdapter(options.db));
|
|
238
272
|
const auditAdapter = options.audit?.adapter ?? new KyselyAuditAdapter(options.db);
|
|
239
273
|
|
|
240
|
-
// Jobs can emit events and use Kysely adapter
|
|
274
|
+
// Jobs can emit events and use Kysely adapter, with logger for scoped logging
|
|
241
275
|
const jobs = createJobs({
|
|
242
276
|
...options.jobs,
|
|
243
277
|
events,
|
|
278
|
+
logger,
|
|
244
279
|
adapter: jobAdapter,
|
|
245
280
|
// Disable built-in persistence when using Kysely adapter
|
|
246
281
|
persist: useLegacy ? options.jobs?.persist : false,
|
|
@@ -256,8 +291,6 @@ export class AppServer {
|
|
|
256
291
|
});
|
|
257
292
|
|
|
258
293
|
// Processes - still uses its own adapter pattern but can use Kysely
|
|
259
|
-
// Note: ProcessesImpl creates its own SqliteProcessAdapter internally
|
|
260
|
-
// For full Kysely support, we need to modify processes.ts
|
|
261
294
|
const processes = createProcesses({
|
|
262
295
|
...options.processes,
|
|
263
296
|
events,
|
|
@@ -287,6 +320,7 @@ export class AppServer {
|
|
|
287
320
|
audit,
|
|
288
321
|
websocket,
|
|
289
322
|
storage,
|
|
323
|
+
logs,
|
|
290
324
|
};
|
|
291
325
|
|
|
292
326
|
// Resolve circular dependency: workflows needs core for step handlers
|
|
@@ -989,10 +1023,20 @@ ${factoryFunction}
|
|
|
989
1023
|
// Pass plugins to workflows so handlers can access ctx.plugins
|
|
990
1024
|
this.coreServices.workflows.setPlugins(this.manager.getServices());
|
|
991
1025
|
|
|
1026
|
+
// Forward plugin metadata so isolated workflows can instantiate plugins locally
|
|
1027
|
+
this.coreServices.workflows.setPluginMetadata({
|
|
1028
|
+
names: this.manager.getPluginNames(),
|
|
1029
|
+
modulePaths: this.manager.getPluginModulePaths(),
|
|
1030
|
+
configs: this.manager.getPluginConfigs(),
|
|
1031
|
+
dependencies: this.manager.getPluginDependencies(),
|
|
1032
|
+
customErrors: this.manager.getPluginCustomErrors(),
|
|
1033
|
+
});
|
|
1034
|
+
|
|
992
1035
|
this.isInitialized = true;
|
|
993
1036
|
|
|
994
1037
|
this.coreServices.cron.start();
|
|
995
1038
|
this.coreServices.jobs.start();
|
|
1039
|
+
await this.coreServices.workflows.resolveDbPath();
|
|
996
1040
|
await this.coreServices.workflows.resume();
|
|
997
1041
|
this.coreServices.processes.start();
|
|
998
1042
|
logger.info("Background services started (cron, jobs, workflows, processes)");
|
|
@@ -1407,6 +1451,10 @@ ${factoryFunction}
|
|
|
1407
1451
|
// Run user shutdown handlers first (in reverse order - LIFO)
|
|
1408
1452
|
await this.runShutdownHandlers();
|
|
1409
1453
|
|
|
1454
|
+
// Flush and stop logs before other services shut down
|
|
1455
|
+
await this.coreServices.logs.flush();
|
|
1456
|
+
this.coreServices.logs.stop();
|
|
1457
|
+
|
|
1410
1458
|
// Stop SSE (closes all client connections)
|
|
1411
1459
|
this.coreServices.sse.shutdown();
|
|
1412
1460
|
|
/package/{CLAUDE.md → agents.md}
RENAMED
|
File without changes
|