@meetploy/emulator 1.0.0
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/dist/bundler/bundler.d.ts +9 -0
- package/dist/bundler/bundler.js +174 -0
- package/dist/bundler/bundler.js.map +1 -0
- package/dist/bundler/watcher.d.ts +5 -0
- package/dist/bundler/watcher.js +65 -0
- package/dist/bundler/watcher.js.map +1 -0
- package/dist/config/ploy-config.d.ts +13 -0
- package/dist/config/ploy-config.js +40 -0
- package/dist/config/ploy-config.js.map +1 -0
- package/dist/config/workerd-config.d.ts +10 -0
- package/dist/config/workerd-config.js +49 -0
- package/dist/config/workerd-config.js.map +1 -0
- package/dist/emulator.d.ts +27 -0
- package/dist/emulator.js +247 -0
- package/dist/emulator.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/db-runtime.d.ts +1 -0
- package/dist/runtime/db-runtime.js +142 -0
- package/dist/runtime/db-runtime.js.map +1 -0
- package/dist/runtime/queue-runtime.d.ts +1 -0
- package/dist/runtime/queue-runtime.js +60 -0
- package/dist/runtime/queue-runtime.js.map +1 -0
- package/dist/runtime/workflow-runtime.d.ts +1 -0
- package/dist/runtime/workflow-runtime.js +206 -0
- package/dist/runtime/workflow-runtime.js.map +1 -0
- package/dist/services/db-service.d.ts +22 -0
- package/dist/services/db-service.js +103 -0
- package/dist/services/db-service.js.map +1 -0
- package/dist/services/mock-server.d.ts +10 -0
- package/dist/services/mock-server.js +46 -0
- package/dist/services/mock-server.js.map +1 -0
- package/dist/services/queue-service.d.ts +64 -0
- package/dist/services/queue-service.js +198 -0
- package/dist/services/queue-service.js.map +1 -0
- package/dist/services/workflow-service.d.ts +108 -0
- package/dist/services/workflow-service.js +217 -0
- package/dist/services/workflow-service.js.map +1 -0
- package/dist/utils/logger.d.ts +5 -0
- package/dist/utils/logger.js +30 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/paths.d.ts +6 -0
- package/dist/utils/paths.js +29 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/sqlite.d.ts +7 -0
- package/dist/utils/sqlite.js +82 -0
- package/dist/utils/sqlite.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PloyConfig } from "../config/ploy-config.js";
|
|
2
|
+
export interface BundlerOptions {
|
|
3
|
+
projectDir: string;
|
|
4
|
+
tempDir: string;
|
|
5
|
+
entryPoint: string;
|
|
6
|
+
config: PloyConfig;
|
|
7
|
+
mockServiceUrl: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function bundleWorker(options: BundlerOptions): Promise<string>;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { build } from "esbuild";
|
|
4
|
+
import { DB_RUNTIME_CODE } from "../runtime/db-runtime.js";
|
|
5
|
+
import { QUEUE_RUNTIME_CODE } from "../runtime/queue-runtime.js";
|
|
6
|
+
import { WORKFLOW_RUNTIME_CODE } from "../runtime/workflow-runtime.js";
|
|
7
|
+
function generateWrapperCode(config, mockServiceUrl) {
|
|
8
|
+
const imports = [];
|
|
9
|
+
const bindings = [];
|
|
10
|
+
if (config.db) {
|
|
11
|
+
imports.push('import { initializeDB } from "__ploy_db_runtime__";');
|
|
12
|
+
for (const [bindingName, dbName] of Object.entries(config.db)) {
|
|
13
|
+
bindings.push(` ${bindingName}: initializeDB("${dbName}", "${mockServiceUrl}/db"),`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (config.queue) {
|
|
17
|
+
imports.push('import { initializeQueue } from "__ploy_queue_runtime__";');
|
|
18
|
+
for (const [bindingName, queueName] of Object.entries(config.queue)) {
|
|
19
|
+
bindings.push(` ${bindingName}: initializeQueue("${queueName}", "${mockServiceUrl}"),`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (config.workflow) {
|
|
23
|
+
imports.push('import { initializeWorkflow, createStepContext, executeWorkflow } from "__ploy_workflow_runtime__";');
|
|
24
|
+
for (const [bindingName, workflowName] of Object.entries(config.workflow)) {
|
|
25
|
+
bindings.push(` ${bindingName}: initializeWorkflow("${workflowName}", "${mockServiceUrl}"),`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
imports.push('import userWorker from "__ploy_user_worker__";');
|
|
29
|
+
const workflowHandlerCode = config.workflow
|
|
30
|
+
? `
|
|
31
|
+
// Handle workflow execution requests
|
|
32
|
+
if (request.headers.get("X-Ploy-Workflow-Execution") === "true") {
|
|
33
|
+
const workflowName = request.headers.get("X-Ploy-Workflow-Name");
|
|
34
|
+
const executionId = request.headers.get("X-Ploy-Execution-Id");
|
|
35
|
+
|
|
36
|
+
if (workflowName && executionId && userWorker.workflows && userWorker.workflows[workflowName]) {
|
|
37
|
+
const input = await request.json();
|
|
38
|
+
try {
|
|
39
|
+
await executeWorkflow(
|
|
40
|
+
executionId,
|
|
41
|
+
userWorker.workflows[workflowName],
|
|
42
|
+
input,
|
|
43
|
+
injectedEnv,
|
|
44
|
+
"${mockServiceUrl}"
|
|
45
|
+
);
|
|
46
|
+
return new Response(JSON.stringify({ success: true }), {
|
|
47
|
+
headers: { "Content-Type": "application/json" }
|
|
48
|
+
});
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return new Response(JSON.stringify({ success: false, error: String(error) }), {
|
|
51
|
+
status: 500,
|
|
52
|
+
headers: { "Content-Type": "application/json" }
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}`
|
|
57
|
+
: "";
|
|
58
|
+
const queueHandlerCode = config.queue
|
|
59
|
+
? `
|
|
60
|
+
// Handle queue message delivery
|
|
61
|
+
if (request.headers.get("X-Ploy-Queue-Delivery") === "true") {
|
|
62
|
+
const queueName = request.headers.get("X-Ploy-Queue-Name");
|
|
63
|
+
const messageId = request.headers.get("X-Ploy-Message-Id");
|
|
64
|
+
const deliveryId = request.headers.get("X-Ploy-Delivery-Id");
|
|
65
|
+
const attempt = parseInt(request.headers.get("X-Ploy-Message-Attempt") || "1", 10);
|
|
66
|
+
|
|
67
|
+
if (queueName && messageId && userWorker.message) {
|
|
68
|
+
const payload = await request.json();
|
|
69
|
+
try {
|
|
70
|
+
await userWorker.message({
|
|
71
|
+
id: messageId,
|
|
72
|
+
queueName,
|
|
73
|
+
payload,
|
|
74
|
+
attempt,
|
|
75
|
+
timestamp: Date.now()
|
|
76
|
+
}, injectedEnv, ctx);
|
|
77
|
+
return new Response(JSON.stringify({ success: true }), {
|
|
78
|
+
headers: { "Content-Type": "application/json" }
|
|
79
|
+
});
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return new Response(JSON.stringify({ success: false, error: String(error) }), {
|
|
82
|
+
status: 500,
|
|
83
|
+
headers: { "Content-Type": "application/json" }
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}`
|
|
88
|
+
: "";
|
|
89
|
+
return `${imports.join("\n")}
|
|
90
|
+
|
|
91
|
+
const ployBindings = {
|
|
92
|
+
${bindings.join("\n")}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export default {
|
|
96
|
+
async fetch(request, env, ctx) {
|
|
97
|
+
const injectedEnv = { ...env, ...ployBindings };
|
|
98
|
+
${workflowHandlerCode}
|
|
99
|
+
${queueHandlerCode}
|
|
100
|
+
|
|
101
|
+
if (userWorker.fetch) {
|
|
102
|
+
return userWorker.fetch(request, injectedEnv, ctx);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return new Response("Worker has no fetch handler", { status: 500 });
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
async scheduled(event, env, ctx) {
|
|
109
|
+
if (userWorker.scheduled) {
|
|
110
|
+
const injectedEnv = { ...env, ...ployBindings };
|
|
111
|
+
return userWorker.scheduled(event, injectedEnv, ctx);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
117
|
+
function createRuntimePlugin(_config) {
|
|
118
|
+
return {
|
|
119
|
+
name: "ploy-runtime",
|
|
120
|
+
setup(build) {
|
|
121
|
+
build.onResolve({ filter: /^__ploy_db_runtime__$/ }, () => ({
|
|
122
|
+
path: "__ploy_db_runtime__",
|
|
123
|
+
namespace: "ploy-runtime",
|
|
124
|
+
}));
|
|
125
|
+
build.onResolve({ filter: /^__ploy_queue_runtime__$/ }, () => ({
|
|
126
|
+
path: "__ploy_queue_runtime__",
|
|
127
|
+
namespace: "ploy-runtime",
|
|
128
|
+
}));
|
|
129
|
+
build.onResolve({ filter: /^__ploy_workflow_runtime__$/ }, () => ({
|
|
130
|
+
path: "__ploy_workflow_runtime__",
|
|
131
|
+
namespace: "ploy-runtime",
|
|
132
|
+
}));
|
|
133
|
+
build.onLoad({ filter: /^__ploy_db_runtime__$/, namespace: "ploy-runtime" }, () => ({
|
|
134
|
+
contents: DB_RUNTIME_CODE,
|
|
135
|
+
loader: "ts",
|
|
136
|
+
}));
|
|
137
|
+
build.onLoad({ filter: /^__ploy_queue_runtime__$/, namespace: "ploy-runtime" }, () => ({
|
|
138
|
+
contents: QUEUE_RUNTIME_CODE,
|
|
139
|
+
loader: "ts",
|
|
140
|
+
}));
|
|
141
|
+
build.onLoad({ filter: /^__ploy_workflow_runtime__$/, namespace: "ploy-runtime" }, () => ({
|
|
142
|
+
contents: WORKFLOW_RUNTIME_CODE,
|
|
143
|
+
loader: "ts",
|
|
144
|
+
}));
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export async function bundleWorker(options) {
|
|
149
|
+
const { projectDir, tempDir, entryPoint, config, mockServiceUrl } = options;
|
|
150
|
+
const wrapperCode = generateWrapperCode(config, mockServiceUrl);
|
|
151
|
+
const wrapperPath = join(tempDir, "wrapper.ts");
|
|
152
|
+
writeFileSync(wrapperPath, wrapperCode);
|
|
153
|
+
const bundlePath = join(tempDir, "worker.bundle.js");
|
|
154
|
+
const buildOptions = {
|
|
155
|
+
entryPoints: [wrapperPath],
|
|
156
|
+
bundle: true,
|
|
157
|
+
format: "esm",
|
|
158
|
+
platform: "neutral",
|
|
159
|
+
target: "es2022",
|
|
160
|
+
outfile: bundlePath,
|
|
161
|
+
minify: false,
|
|
162
|
+
sourcemap: false,
|
|
163
|
+
external: ["cloudflare:*"],
|
|
164
|
+
alias: {
|
|
165
|
+
__ploy_user_worker__: entryPoint,
|
|
166
|
+
},
|
|
167
|
+
plugins: [createRuntimePlugin(config)],
|
|
168
|
+
absWorkingDir: projectDir,
|
|
169
|
+
logLevel: "warning",
|
|
170
|
+
};
|
|
171
|
+
await build(buildOptions);
|
|
172
|
+
return bundlePath;
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=bundler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundler.js","sourceRoot":"","sources":["../../src/bundler/bundler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { watch } from "chokidar";
|
|
2
|
+
export function createFileWatcher(srcDir, onRebuild) {
|
|
3
|
+
let watcher = null;
|
|
4
|
+
let debounceTimer = null;
|
|
5
|
+
let isRebuilding = false;
|
|
6
|
+
function shouldRebuild(filePath) {
|
|
7
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs"];
|
|
8
|
+
return extensions.some((ext) => filePath.endsWith(ext));
|
|
9
|
+
}
|
|
10
|
+
function scheduleRebuild() {
|
|
11
|
+
if (debounceTimer) {
|
|
12
|
+
clearTimeout(debounceTimer);
|
|
13
|
+
}
|
|
14
|
+
debounceTimer = setTimeout(async () => {
|
|
15
|
+
if (isRebuilding) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
isRebuilding = true;
|
|
19
|
+
try {
|
|
20
|
+
await onRebuild();
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
isRebuilding = false;
|
|
24
|
+
}
|
|
25
|
+
}, 100);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
start() {
|
|
29
|
+
if (watcher) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
watcher = watch(srcDir, {
|
|
33
|
+
persistent: true,
|
|
34
|
+
ignoreInitial: true,
|
|
35
|
+
ignored: ["**/node_modules/**", "**/dist/**", "**/.ploy/**", "**/.*"],
|
|
36
|
+
});
|
|
37
|
+
watcher.on("change", (filePath) => {
|
|
38
|
+
if (shouldRebuild(filePath)) {
|
|
39
|
+
scheduleRebuild();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
watcher.on("add", (filePath) => {
|
|
43
|
+
if (shouldRebuild(filePath)) {
|
|
44
|
+
scheduleRebuild();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
watcher.on("unlink", (filePath) => {
|
|
48
|
+
if (shouldRebuild(filePath)) {
|
|
49
|
+
scheduleRebuild();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
stop() {
|
|
54
|
+
if (debounceTimer) {
|
|
55
|
+
clearTimeout(debounceTimer);
|
|
56
|
+
debounceTimer = null;
|
|
57
|
+
}
|
|
58
|
+
if (watcher) {
|
|
59
|
+
watcher.close();
|
|
60
|
+
watcher = null;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../src/bundler/watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAkB,MAAM,UAAU,CAAC;AAOjD,MAAM,UAAU,iBAAiB,CAChC,MAAc,EACd,SAA8B;IAE9B,IAAI,OAAO,GAAqB,IAAI,CAAC;IACrC,IAAI,aAAa,GAAyC,IAAI,CAAC;IAC/D,IAAI,YAAY,GAAG,KAAK,CAAC;IAEzB,SAAS,aAAa,CAAC,QAAgB;QACtC,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAClE,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,SAAS,eAAe;QACvB,IAAI,aAAa,EAAE,CAAC;YACnB,YAAY,CAAC,aAAa,CAAC,CAAC;QAC7B,CAAC;QAED,aAAa,GAAG,UAAU,CAAC,KAAK,IAAI,EAAE;YACrC,IAAI,YAAY,EAAE,CAAC;gBAClB,OAAO;YACR,CAAC;YAED,YAAY,GAAG,IAAI,CAAC;YACpB,IAAI,CAAC;gBACJ,MAAM,SAAS,EAAE,CAAC;YACnB,CAAC;oBAAS,CAAC;gBACV,YAAY,GAAG,KAAK,CAAC;YACtB,CAAC;QACF,CAAC,EAAE,GAAG,CAAC,CAAC;IACT,CAAC;IAED,OAAO;QACN,KAAK;YACJ,IAAI,OAAO,EAAE,CAAC;gBACb,OAAO;YACR,CAAC;YAED,OAAO,GAAG,KAAK,CAAC,MAAM,EAAE;gBACvB,UAAU,EAAE,IAAI;gBAChB,aAAa,EAAE,IAAI;gBACnB,OAAO,EAAE,CAAC,oBAAoB,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,CAAC;aACrE,CAAC,CAAC;YAEH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE;gBACjC,IAAI,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC7B,eAAe,EAAE,CAAC;gBACnB,CAAC;YACF,CAAC,CAAC,CAAC;YAEH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,EAAE;gBAC9B,IAAI,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC7B,eAAe,EAAE,CAAC;gBACnB,CAAC;YACF,CAAC,CAAC,CAAC;YAEH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE;gBACjC,IAAI,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC7B,eAAe,EAAE,CAAC;gBACnB,CAAC;YACF,CAAC,CAAC,CAAC;QACJ,CAAC;QAED,IAAI;YACH,IAAI,aAAa,EAAE,CAAC;gBACnB,YAAY,CAAC,aAAa,CAAC,CAAC;gBAC5B,aAAa,GAAG,IAAI,CAAC;YACtB,CAAC;YAED,IAAI,OAAO,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,EAAE,CAAC;gBAChB,OAAO,GAAG,IAAI,CAAC;YAChB,CAAC;QACF,CAAC;KACD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface PloyConfig {
|
|
2
|
+
kind: "dynamic" | "worker";
|
|
3
|
+
build?: string;
|
|
4
|
+
out?: string;
|
|
5
|
+
main?: string;
|
|
6
|
+
db?: Record<string, string>;
|
|
7
|
+
queue?: Record<string, string>;
|
|
8
|
+
workflow?: Record<string, string>;
|
|
9
|
+
ai?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function readPloyConfig(projectDir: string, configPath?: string): PloyConfig;
|
|
12
|
+
export declare function getWorkerEntryPoint(projectDir: string, config: PloyConfig): string;
|
|
13
|
+
export declare function hasBindings(config: PloyConfig): boolean;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parse } from "yaml";
|
|
4
|
+
export function readPloyConfig(projectDir, configPath) {
|
|
5
|
+
const configFile = configPath || "ploy.yaml";
|
|
6
|
+
const fullPath = join(projectDir, configFile);
|
|
7
|
+
if (!existsSync(fullPath)) {
|
|
8
|
+
throw new Error(`Config file not found: ${fullPath}`);
|
|
9
|
+
}
|
|
10
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
11
|
+
const config = parse(content);
|
|
12
|
+
if (!config.kind) {
|
|
13
|
+
throw new Error(`Missing required field 'kind' in ${configFile}`);
|
|
14
|
+
}
|
|
15
|
+
if (config.kind !== "dynamic" && config.kind !== "worker") {
|
|
16
|
+
throw new Error(`Invalid kind '${config.kind}' in ${configFile}. Must be 'dynamic' or 'worker'`);
|
|
17
|
+
}
|
|
18
|
+
return config;
|
|
19
|
+
}
|
|
20
|
+
export function getWorkerEntryPoint(projectDir, config) {
|
|
21
|
+
if (config.main) {
|
|
22
|
+
return join(projectDir, config.main);
|
|
23
|
+
}
|
|
24
|
+
const defaultPaths = [
|
|
25
|
+
join(projectDir, "index.ts"),
|
|
26
|
+
join(projectDir, "index.js"),
|
|
27
|
+
join(projectDir, "src", "index.ts"),
|
|
28
|
+
join(projectDir, "src", "index.js"),
|
|
29
|
+
];
|
|
30
|
+
for (const path of defaultPaths) {
|
|
31
|
+
if (existsSync(path)) {
|
|
32
|
+
return path;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
throw new Error("Could not find worker entry point. Specify 'main' in ploy.yaml");
|
|
36
|
+
}
|
|
37
|
+
export function hasBindings(config) {
|
|
38
|
+
return !!(config.db || config.queue || config.workflow || config.ai);
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=ploy-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ploy-config.js","sourceRoot":"","sources":["../../src/config/ploy-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAa7B,MAAM,UAAU,cAAc,CAC7B,UAAkB,EAClB,UAAmB;IAEnB,MAAM,UAAU,GAAG,UAAU,IAAI,WAAW,CAAC;IAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAE9C,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,0BAA0B,QAAQ,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAe,CAAC;IAE5C,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,oCAAoC,UAAU,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CACd,iBAAiB,MAAM,CAAC,IAAI,QAAQ,UAAU,iCAAiC,CAC/E,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAED,MAAM,UAAU,mBAAmB,CAClC,UAAkB,EAClB,MAAkB;IAElB,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IAGD,MAAM,YAAY,GAAG;QACpB,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC;QAC5B,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC;QAC5B,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,UAAU,CAAC;QACnC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,UAAU,CAAC;KACnC,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QACjC,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IAED,MAAM,IAAI,KAAK,CACd,gEAAgE,CAChE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAkB;IAC7C,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PloyConfig } from "./ploy-config.js";
|
|
2
|
+
export interface WorkerdConfigOptions {
|
|
3
|
+
tempDir: string;
|
|
4
|
+
bundlePath: string;
|
|
5
|
+
port: number;
|
|
6
|
+
mockServicePort: number;
|
|
7
|
+
config: PloyConfig;
|
|
8
|
+
}
|
|
9
|
+
export declare function generateWorkerdConfig(options: WorkerdConfigOptions): string;
|
|
10
|
+
export declare function writeWorkerdConfig(options: WorkerdConfigOptions): string;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
export function generateWorkerdConfig(options) {
|
|
4
|
+
const { port, mockServicePort } = options;
|
|
5
|
+
const services = [
|
|
6
|
+
'(name = "main", worker = .worker)',
|
|
7
|
+
`(name = "mock", external = (address = "localhost:${mockServicePort}", http = ()))`,
|
|
8
|
+
'(name = "internet", network = (allow = ["public", "private", "local"], tlsOptions = (trustBrowserCas = true)))',
|
|
9
|
+
];
|
|
10
|
+
const bindings = [
|
|
11
|
+
'(name = "mock", service = "mock")',
|
|
12
|
+
'(name = "internet", service = "internet")',
|
|
13
|
+
];
|
|
14
|
+
const configContent = `using Workerd = import "/workerd/workerd.capnp";
|
|
15
|
+
|
|
16
|
+
const config :Workerd.Config = (
|
|
17
|
+
services = [
|
|
18
|
+
${services.join(",\n ")}
|
|
19
|
+
],
|
|
20
|
+
sockets = [
|
|
21
|
+
(name = "http", address = "*:${port}", http = (), service = "main")
|
|
22
|
+
]
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const worker :Workerd.Worker = (
|
|
26
|
+
modules = [
|
|
27
|
+
(name = "worker.js", esModule = embed "worker.bundle.js")
|
|
28
|
+
],
|
|
29
|
+
compatibilityDate = "2025-09-15",
|
|
30
|
+
compatibilityFlags = [
|
|
31
|
+
"experimental",
|
|
32
|
+
"nodejs_compat",
|
|
33
|
+
"nodejs_als"
|
|
34
|
+
],
|
|
35
|
+
globalOutbound = "internet",
|
|
36
|
+
bindings = [
|
|
37
|
+
${bindings.join(",\n ")}
|
|
38
|
+
]
|
|
39
|
+
);
|
|
40
|
+
`;
|
|
41
|
+
return configContent;
|
|
42
|
+
}
|
|
43
|
+
export function writeWorkerdConfig(options) {
|
|
44
|
+
const configContent = generateWorkerdConfig(options);
|
|
45
|
+
const configPath = join(options.tempDir, "workerd.capnp");
|
|
46
|
+
writeFileSync(configPath, configContent);
|
|
47
|
+
return configPath;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=workerd-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workerd-config.js","sourceRoot":"","sources":["../../src/config/workerd-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAYjC,MAAM,UAAU,qBAAqB,CAAC,OAA6B;IAClE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC;IAE1C,MAAM,QAAQ,GAAG;QAChB,mCAAmC;QACnC,oDAAoD,eAAe,gBAAgB;QACnF,gHAAgH;KAChH,CAAC;IAEF,MAAM,QAAQ,GAAG;QAChB,mCAAmC;QACnC,2CAA2C;KAC3C,CAAC;IAEF,MAAM,aAAa,GAAG;;;;MAIjB,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC;;;mCAGK,IAAI;;;;;;;;;;;;;;;;MAgBjC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC;;;CAG7B,CAAC;IAED,OAAO,aAAa,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAA6B;IAC/D,MAAM,aAAa,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACrD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;IAC1D,aAAa,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;IACzC,OAAO,UAAU,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface EmulatorOptions {
|
|
2
|
+
port?: number;
|
|
3
|
+
host?: string;
|
|
4
|
+
configPath?: string;
|
|
5
|
+
watch?: boolean;
|
|
6
|
+
verbose?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare class EmulatorServer {
|
|
9
|
+
private options;
|
|
10
|
+
private projectDir;
|
|
11
|
+
private tempDir;
|
|
12
|
+
private config;
|
|
13
|
+
private dbManager;
|
|
14
|
+
private mockServer;
|
|
15
|
+
private workerdProcess;
|
|
16
|
+
private fileWatcher;
|
|
17
|
+
private queueProcessor;
|
|
18
|
+
constructor(options?: EmulatorOptions);
|
|
19
|
+
start(): Promise<void>;
|
|
20
|
+
private bundle;
|
|
21
|
+
private startWorkerd;
|
|
22
|
+
private findWorkerdBinary;
|
|
23
|
+
private getSrcDir;
|
|
24
|
+
private setupSignalHandlers;
|
|
25
|
+
stop(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export declare function startEmulator(options?: EmulatorOptions): Promise<EmulatorServer>;
|
package/dist/emulator.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { bundleWorker } from "./bundler/bundler.js";
|
|
6
|
+
import { createFileWatcher } from "./bundler/watcher.js";
|
|
7
|
+
import { readPloyConfig, getWorkerEntryPoint, } from "./config/ploy-config.js";
|
|
8
|
+
import { writeWorkerdConfig } from "./config/workerd-config.js";
|
|
9
|
+
import { startMockServer } from "./services/mock-server.js";
|
|
10
|
+
import { createQueueProcessor, } from "./services/queue-service.js";
|
|
11
|
+
import * as logger from "./utils/logger.js";
|
|
12
|
+
import { ensureTempDir, ensureDataDir } from "./utils/paths.js";
|
|
13
|
+
import { initializeDatabases } from "./utils/sqlite.js";
|
|
14
|
+
export class EmulatorServer {
|
|
15
|
+
options;
|
|
16
|
+
projectDir;
|
|
17
|
+
tempDir = "";
|
|
18
|
+
config = null;
|
|
19
|
+
dbManager = null;
|
|
20
|
+
mockServer = null;
|
|
21
|
+
workerdProcess = null;
|
|
22
|
+
fileWatcher = null;
|
|
23
|
+
queueProcessor = null;
|
|
24
|
+
constructor(options = {}) {
|
|
25
|
+
this.options = {
|
|
26
|
+
port: options.port ?? 8787,
|
|
27
|
+
host: options.host ?? "localhost",
|
|
28
|
+
configPath: options.configPath ?? "ploy.yaml",
|
|
29
|
+
watch: options.watch ?? true,
|
|
30
|
+
verbose: options.verbose ?? false,
|
|
31
|
+
};
|
|
32
|
+
this.projectDir = process.cwd();
|
|
33
|
+
}
|
|
34
|
+
async start() {
|
|
35
|
+
logger.log("Starting Ploy emulator...");
|
|
36
|
+
try {
|
|
37
|
+
this.config = readPloyConfig(this.projectDir, this.options.configPath);
|
|
38
|
+
logger.debug(`Loaded config: ${JSON.stringify(this.config)}`, this.options.verbose);
|
|
39
|
+
this.tempDir = ensureTempDir(this.projectDir);
|
|
40
|
+
ensureDataDir(this.projectDir);
|
|
41
|
+
logger.debug(`Temp dir: ${this.tempDir}`, this.options.verbose);
|
|
42
|
+
this.dbManager = initializeDatabases(this.projectDir);
|
|
43
|
+
logger.debug("Initialized databases", this.options.verbose);
|
|
44
|
+
const workerUrl = `http://${this.options.host}:${this.options.port}`;
|
|
45
|
+
this.mockServer = await startMockServer(this.dbManager, this.config, {
|
|
46
|
+
workerUrl,
|
|
47
|
+
});
|
|
48
|
+
logger.debug(`Mock server started on port ${this.mockServer.port}`, this.options.verbose);
|
|
49
|
+
const mockServiceUrl = `http://localhost:${this.mockServer.port}`;
|
|
50
|
+
const entryPoint = getWorkerEntryPoint(this.projectDir, this.config);
|
|
51
|
+
logger.debug(`Entry point: ${entryPoint}`, this.options.verbose);
|
|
52
|
+
await this.bundle(entryPoint, mockServiceUrl);
|
|
53
|
+
const workerdConfigPath = writeWorkerdConfig({
|
|
54
|
+
tempDir: this.tempDir,
|
|
55
|
+
bundlePath: join(this.tempDir, "worker.bundle.js"),
|
|
56
|
+
port: this.options.port,
|
|
57
|
+
mockServicePort: this.mockServer.port,
|
|
58
|
+
config: this.config,
|
|
59
|
+
});
|
|
60
|
+
await this.startWorkerd(workerdConfigPath);
|
|
61
|
+
if (this.options.watch) {
|
|
62
|
+
const srcDir = this.getSrcDir();
|
|
63
|
+
this.fileWatcher = createFileWatcher(srcDir, async () => {
|
|
64
|
+
logger.log("Changes detected, rebuilding...");
|
|
65
|
+
try {
|
|
66
|
+
await this.bundle(entryPoint, mockServiceUrl);
|
|
67
|
+
logger.success("Rebuild complete");
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
logger.error(`Rebuild failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
this.fileWatcher.start();
|
|
74
|
+
logger.debug(`Watching ${srcDir} for changes`, this.options.verbose);
|
|
75
|
+
}
|
|
76
|
+
if (this.config.queue) {
|
|
77
|
+
const workerUrl = `http://${this.options.host}:${this.options.port}`;
|
|
78
|
+
this.queueProcessor = createQueueProcessor(this.dbManager.emulatorDb, this.config.queue, workerUrl);
|
|
79
|
+
this.queueProcessor.start();
|
|
80
|
+
logger.debug("Queue processor started", this.options.verbose);
|
|
81
|
+
}
|
|
82
|
+
logger.success(`Emulator running at http://${this.options.host}:${this.options.port}`);
|
|
83
|
+
if (this.config.db) {
|
|
84
|
+
logger.log(` DB bindings: ${Object.keys(this.config.db).join(", ")}`);
|
|
85
|
+
}
|
|
86
|
+
if (this.config.queue) {
|
|
87
|
+
logger.log(` Queue bindings: ${Object.keys(this.config.queue).join(", ")}`);
|
|
88
|
+
}
|
|
89
|
+
if (this.config.workflow) {
|
|
90
|
+
logger.log(` Workflow bindings: ${Object.keys(this.config.workflow).join(", ")}`);
|
|
91
|
+
}
|
|
92
|
+
this.setupSignalHandlers();
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
logger.error(`Failed to start emulator: ${err instanceof Error ? err.message : String(err)}`);
|
|
96
|
+
await this.stop();
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async bundle(entryPoint, mockServiceUrl) {
|
|
101
|
+
if (!this.config) {
|
|
102
|
+
throw new Error("Config not loaded");
|
|
103
|
+
}
|
|
104
|
+
await bundleWorker({
|
|
105
|
+
projectDir: this.projectDir,
|
|
106
|
+
tempDir: this.tempDir,
|
|
107
|
+
entryPoint,
|
|
108
|
+
config: this.config,
|
|
109
|
+
mockServiceUrl,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async startWorkerd(configPath) {
|
|
113
|
+
const args = ["serve", "--experimental", "--verbose", configPath];
|
|
114
|
+
if (this.options.watch) {
|
|
115
|
+
args.push("--watch");
|
|
116
|
+
}
|
|
117
|
+
const workerdBin = this.findWorkerdBinary();
|
|
118
|
+
logger.log(`[ploy] Using workerd binary: ${workerdBin}`);
|
|
119
|
+
logger.debug(`Starting workerd: ${workerdBin} ${args.join(" ")}`, this.options.verbose);
|
|
120
|
+
return await new Promise((resolve, reject) => {
|
|
121
|
+
const workerdBinDir = dirname(workerdBin);
|
|
122
|
+
this.workerdProcess = spawn(workerdBin, args, {
|
|
123
|
+
cwd: this.tempDir,
|
|
124
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
125
|
+
shell: true,
|
|
126
|
+
env: {
|
|
127
|
+
...process.env,
|
|
128
|
+
PATH: `${workerdBinDir}:${process.env.PATH || ""}`,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
let started = false;
|
|
132
|
+
let stderrOutput = "";
|
|
133
|
+
this.workerdProcess.stdout?.on("data", (data) => {
|
|
134
|
+
const output = data.toString();
|
|
135
|
+
if (this.options.verbose) {
|
|
136
|
+
process.stdout.write(output);
|
|
137
|
+
}
|
|
138
|
+
if (!started &&
|
|
139
|
+
(output.includes("Listening") || output.includes("running"))) {
|
|
140
|
+
started = true;
|
|
141
|
+
resolve();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
this.workerdProcess.stderr?.on("data", (data) => {
|
|
145
|
+
const output = data.toString();
|
|
146
|
+
stderrOutput += output;
|
|
147
|
+
logger.log(`[workerd stderr] ${output.trim()}`);
|
|
148
|
+
if (output.includes("error") || output.includes("Error")) {
|
|
149
|
+
logger.error(output.trim());
|
|
150
|
+
}
|
|
151
|
+
else if (this.options.verbose) {
|
|
152
|
+
process.stderr.write(output);
|
|
153
|
+
}
|
|
154
|
+
if (!started && output.includes("Listening")) {
|
|
155
|
+
started = true;
|
|
156
|
+
resolve();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
this.workerdProcess.on("error", (err) => {
|
|
160
|
+
logger.error(`workerd error: ${err.message}`);
|
|
161
|
+
if (!started) {
|
|
162
|
+
reject(err);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
this.workerdProcess.on("exit", (code) => {
|
|
166
|
+
if (code !== 0 && code !== null) {
|
|
167
|
+
logger.error(`workerd exited with code ${code}`);
|
|
168
|
+
if (stderrOutput) {
|
|
169
|
+
logger.error(`workerd stderr: ${stderrOutput.trim()}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (!started) {
|
|
173
|
+
reject(new Error(`workerd exited with code ${code}`));
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
setTimeout(() => {
|
|
177
|
+
if (!started) {
|
|
178
|
+
started = true;
|
|
179
|
+
resolve();
|
|
180
|
+
}
|
|
181
|
+
}, 2000);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
findWorkerdBinary() {
|
|
185
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
186
|
+
const __dirname = dirname(__filename);
|
|
187
|
+
const emulatorPkgDir = join(__dirname, "..");
|
|
188
|
+
const possiblePaths = [
|
|
189
|
+
join(emulatorPkgDir, "node_modules", ".bin", "workerd"),
|
|
190
|
+
join(this.projectDir, "node_modules", ".bin", "workerd"),
|
|
191
|
+
"workerd",
|
|
192
|
+
];
|
|
193
|
+
for (const p of possiblePaths) {
|
|
194
|
+
if (p === "workerd" || existsSync(p)) {
|
|
195
|
+
return p;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return "workerd";
|
|
199
|
+
}
|
|
200
|
+
getSrcDir() {
|
|
201
|
+
const possibleDirs = [join(this.projectDir, "src"), this.projectDir];
|
|
202
|
+
for (const dir of possibleDirs) {
|
|
203
|
+
if (existsSync(dir)) {
|
|
204
|
+
return dir;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return this.projectDir;
|
|
208
|
+
}
|
|
209
|
+
setupSignalHandlers() {
|
|
210
|
+
const handler = async () => {
|
|
211
|
+
logger.log("\nShutting down...");
|
|
212
|
+
await this.stop();
|
|
213
|
+
process.exit(0);
|
|
214
|
+
};
|
|
215
|
+
process.on("SIGINT", handler);
|
|
216
|
+
process.on("SIGTERM", handler);
|
|
217
|
+
}
|
|
218
|
+
async stop() {
|
|
219
|
+
if (this.queueProcessor) {
|
|
220
|
+
this.queueProcessor.stop();
|
|
221
|
+
this.queueProcessor = null;
|
|
222
|
+
}
|
|
223
|
+
if (this.fileWatcher) {
|
|
224
|
+
this.fileWatcher.stop();
|
|
225
|
+
this.fileWatcher = null;
|
|
226
|
+
}
|
|
227
|
+
if (this.workerdProcess) {
|
|
228
|
+
this.workerdProcess.kill("SIGTERM");
|
|
229
|
+
this.workerdProcess = null;
|
|
230
|
+
}
|
|
231
|
+
if (this.mockServer) {
|
|
232
|
+
await this.mockServer.close();
|
|
233
|
+
this.mockServer = null;
|
|
234
|
+
}
|
|
235
|
+
if (this.dbManager) {
|
|
236
|
+
this.dbManager.close();
|
|
237
|
+
this.dbManager = null;
|
|
238
|
+
}
|
|
239
|
+
logger.log("Emulator stopped");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
export async function startEmulator(options = {}) {
|
|
243
|
+
const emulator = new EmulatorServer(options);
|
|
244
|
+
await emulator.start();
|
|
245
|
+
return emulator;
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=emulator.js.map
|