@donkeylabs/server 2.0.22 → 2.0.24
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/cron.md +28 -2
- package/docs/jobs.md +15 -0
- package/docs/workflows.md +60 -1
- package/package.json +1 -1
- package/src/core/cron.ts +50 -7
- package/src/core/index.ts +3 -0
- package/src/core/jobs.ts +42 -3
- package/src/core/subprocess-bootstrap.ts +241 -0
- package/src/core/workflow-executor.ts +48 -43
- package/src/core/workflow-socket.ts +1 -0
- package/src/core/workflow-state-machine.ts +34 -1
- package/src/core/workflows.ts +291 -11
- package/src/core.ts +81 -3
- package/src/server.ts +10 -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";
|
|
@@ -18,6 +18,32 @@ import type { WebSocketService } from "./core/websocket";
|
|
|
18
18
|
import type { Storage } from "./core/storage";
|
|
19
19
|
import type { Logs } from "./core/logs";
|
|
20
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
|
+
}
|
|
46
|
+
|
|
21
47
|
export interface PluginRegistry {}
|
|
22
48
|
|
|
23
49
|
export interface ClientConfig {
|
|
@@ -330,7 +356,7 @@ export class PluginBuilder<LocalSchema = {}> {
|
|
|
330
356
|
client?: ClientConfig;
|
|
331
357
|
customErrors?: CustomErrors;
|
|
332
358
|
} {
|
|
333
|
-
return config as any;
|
|
359
|
+
return { ...config, _modulePath: captureCallerUrl() } as any;
|
|
334
360
|
}
|
|
335
361
|
}
|
|
336
362
|
|
|
@@ -386,9 +412,11 @@ export class ConfiguredPluginBuilder<LocalSchema, Config> {
|
|
|
386
412
|
client?: ClientConfig;
|
|
387
413
|
customErrors?: CustomErrors;
|
|
388
414
|
}> {
|
|
415
|
+
const modulePath = captureCallerUrl();
|
|
389
416
|
const factory = (config: Config) => ({
|
|
390
417
|
...pluginDef,
|
|
391
418
|
_boundConfig: config,
|
|
419
|
+
_modulePath: modulePath,
|
|
392
420
|
});
|
|
393
421
|
return factory as any;
|
|
394
422
|
}
|
|
@@ -425,6 +453,8 @@ export type Plugin = {
|
|
|
425
453
|
service: (ctx: any) => any;
|
|
426
454
|
/** Called after service is created - use for registering crons, events, etc. */
|
|
427
455
|
init?: (ctx: any, service: any) => void | Promise<void>;
|
|
456
|
+
/** Auto-detected module path where the plugin was defined */
|
|
457
|
+
_modulePath?: string;
|
|
428
458
|
};
|
|
429
459
|
|
|
430
460
|
export type PluginWithConfig<Config = void> = Plugin & {
|
|
@@ -454,6 +484,54 @@ export class PluginManager {
|
|
|
454
484
|
return Array.from(this.plugins.values());
|
|
455
485
|
}
|
|
456
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
|
+
|
|
457
535
|
register(plugin: ConfiguredPlugin): void {
|
|
458
536
|
if (this.plugins.has(plugin.name)) {
|
|
459
537
|
throw new Error(`Plugin ${plugin.name} is already registered.`);
|
package/src/server.ts
CHANGED
|
@@ -264,6 +264,7 @@ export class AppServer {
|
|
|
264
264
|
const cron = createCron({
|
|
265
265
|
...options.cron,
|
|
266
266
|
logger,
|
|
267
|
+
events,
|
|
267
268
|
});
|
|
268
269
|
|
|
269
270
|
// Create adapters - use Kysely by default, or legacy SQLite if requested
|
|
@@ -1023,6 +1024,15 @@ ${factoryFunction}
|
|
|
1023
1024
|
// Pass plugins to workflows so handlers can access ctx.plugins
|
|
1024
1025
|
this.coreServices.workflows.setPlugins(this.manager.getServices());
|
|
1025
1026
|
|
|
1027
|
+
// Forward plugin metadata so isolated workflows can instantiate plugins locally
|
|
1028
|
+
this.coreServices.workflows.setPluginMetadata({
|
|
1029
|
+
names: this.manager.getPluginNames(),
|
|
1030
|
+
modulePaths: this.manager.getPluginModulePaths(),
|
|
1031
|
+
configs: this.manager.getPluginConfigs(),
|
|
1032
|
+
dependencies: this.manager.getPluginDependencies(),
|
|
1033
|
+
customErrors: this.manager.getPluginCustomErrors(),
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1026
1036
|
this.isInitialized = true;
|
|
1027
1037
|
|
|
1028
1038
|
this.coreServices.cron.start();
|