@getvision/server 0.4.3-d4c761e-develop → 0.4.4-44d79d9-develop
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/CHANGELOG.md +8 -0
- package/dist/event-bus.d.ts +87 -0
- package/dist/event-bus.d.ts.map +1 -0
- package/dist/event-bus.js +265 -0
- package/dist/event-bus.js.map +10 -0
- package/dist/event-registry.d.ts +79 -0
- package/dist/event-registry.d.ts.map +1 -0
- package/dist/event-registry.js +93 -0
- package/dist/event-registry.js.map +10 -0
- package/{src/index.ts → dist/index.d.ts} +14 -28
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +10 -0
- package/dist/router.d.ts +16 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +117 -0
- package/dist/router.js.map +10 -0
- package/dist/service.d.ts +151 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +341 -0
- package/dist/service.js.map +10 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +9 -0
- package/dist/vision-app.d.ts +166 -0
- package/dist/vision-app.d.ts.map +1 -0
- package/dist/vision-app.js +611 -0
- package/dist/vision-app.js.map +10 -0
- package/dist/vision.d.ts +63 -0
- package/dist/vision.d.ts.map +1 -0
- package/dist/vision.js +223 -0
- package/dist/vision.js.map +10 -0
- package/package.json +13 -3
- package/.env.example +0 -3
- package/.eslintrc.cjs +0 -7
- package/.turbo/turbo-build.log +0 -1
- package/src/event-bus.ts +0 -409
- package/src/event-registry.ts +0 -158
- package/src/router.ts +0 -118
- package/src/service.ts +0 -618
- package/src/types.ts +0 -93
- package/src/vision-app.ts +0 -880
- package/src/vision.ts +0 -319
- package/tsconfig.json +0 -9
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/vision-app.ts
|
|
21
|
+
import { Hono } from "hono";
|
|
22
|
+
import { VisionCore, runInTraceContext } from "@getvision/core";
|
|
23
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
24
|
+
import { existsSync } from "fs";
|
|
25
|
+
import { spawn, spawnSync } from "child_process";
|
|
26
|
+
import { ServiceBuilder } from "./service";
|
|
27
|
+
import { EventBus } from "./event-bus";
|
|
28
|
+
import { eventRegistry } from "./event-registry";
|
|
29
|
+
var visionContext = new AsyncLocalStorage;
|
|
30
|
+
var GLOBAL_VISION_KEY = "__vision_instance_state";
|
|
31
|
+
if (!globalThis[GLOBAL_VISION_KEY]) {
|
|
32
|
+
globalThis[GLOBAL_VISION_KEY] = {
|
|
33
|
+
instance: null,
|
|
34
|
+
drizzleProcess: null
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function getGlobalState() {
|
|
38
|
+
return globalThis[GLOBAL_VISION_KEY];
|
|
39
|
+
}
|
|
40
|
+
async function cleanupVisionInstance(instance) {
|
|
41
|
+
const existing = instance._cleanupPromise;
|
|
42
|
+
if (existing)
|
|
43
|
+
return existing;
|
|
44
|
+
instance._cleanupPromise = (async () => {
|
|
45
|
+
const server = instance.bunServer;
|
|
46
|
+
const hasBunServer = server && typeof server.stop === "function";
|
|
47
|
+
try {
|
|
48
|
+
if (hasBunServer) {
|
|
49
|
+
server.stop();
|
|
50
|
+
}
|
|
51
|
+
if (globalThis.__vision_bun_server === server) {
|
|
52
|
+
globalThis.__vision_bun_server = undefined;
|
|
53
|
+
}
|
|
54
|
+
} catch {}
|
|
55
|
+
try {
|
|
56
|
+
stopDrizzleStudio({ log: false });
|
|
57
|
+
} catch {}
|
|
58
|
+
try {
|
|
59
|
+
await instance.eventBus?.close();
|
|
60
|
+
} catch {}
|
|
61
|
+
})();
|
|
62
|
+
return instance._cleanupPromise;
|
|
63
|
+
}
|
|
64
|
+
function deepMerge(target, source) {
|
|
65
|
+
const output = { ...target };
|
|
66
|
+
if (source && typeof source === "object") {
|
|
67
|
+
for (const key of Object.keys(source)) {
|
|
68
|
+
const srcVal = source[key];
|
|
69
|
+
const tgtVal = output[key];
|
|
70
|
+
if (srcVal && typeof srcVal === "object" && !Array.isArray(srcVal) && tgtVal && typeof tgtVal === "object" && !Array.isArray(tgtVal)) {
|
|
71
|
+
output[key] = deepMerge(tgtVal, srcVal);
|
|
72
|
+
} else {
|
|
73
|
+
output[key] = srcVal;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return output;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class Vision extends Hono {
|
|
81
|
+
visionCore;
|
|
82
|
+
eventBus;
|
|
83
|
+
config;
|
|
84
|
+
serviceBuilders = [];
|
|
85
|
+
bunServer;
|
|
86
|
+
signalHandler;
|
|
87
|
+
constructor(config) {
|
|
88
|
+
super();
|
|
89
|
+
const defaultConfig = {
|
|
90
|
+
service: {
|
|
91
|
+
name: "Vision SubApp"
|
|
92
|
+
},
|
|
93
|
+
vision: {
|
|
94
|
+
enabled: false,
|
|
95
|
+
port: 9500
|
|
96
|
+
},
|
|
97
|
+
pubsub: {},
|
|
98
|
+
routes: {
|
|
99
|
+
autodiscover: true,
|
|
100
|
+
dirs: ["app/routes"]
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
this.config = deepMerge(defaultConfig, config || {});
|
|
104
|
+
const visionEnabled = this.config.vision?.enabled !== false;
|
|
105
|
+
const visionPort = this.config.vision?.port ?? 9500;
|
|
106
|
+
if (visionEnabled) {
|
|
107
|
+
this.visionCore = new VisionCore({
|
|
108
|
+
port: visionPort,
|
|
109
|
+
maxTraces: this.config.vision?.maxTraces ?? 1000,
|
|
110
|
+
maxLogs: this.config.vision?.maxLogs ?? 1e4,
|
|
111
|
+
apiUrl: this.config.vision?.apiUrl
|
|
112
|
+
});
|
|
113
|
+
const drizzleInfo = detectDrizzle();
|
|
114
|
+
let drizzleStudioUrl;
|
|
115
|
+
if (drizzleInfo.detected) {
|
|
116
|
+
console.log(`\uD83D\uDDC4️ Drizzle detected (${drizzleInfo.configPath})`);
|
|
117
|
+
if (this.config.service.drizzle?.autoStart) {
|
|
118
|
+
const drizzlePort = this.config.service.drizzle.port || 4983;
|
|
119
|
+
const started = startDrizzleStudio(drizzlePort);
|
|
120
|
+
if (started) {
|
|
121
|
+
drizzleStudioUrl = "https://local.drizzle.studio";
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
console.log("\uD83D\uDCA1 Tip: Enable Drizzle Studio auto-start with drizzle: { autoStart: true }");
|
|
125
|
+
drizzleStudioUrl = "https://local.drizzle.studio";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const cleanIntegrations = Object.fromEntries(Object.entries(this.config.service.integrations || {}).filter(([_, v]) => v !== undefined));
|
|
129
|
+
this.visionCore.setAppStatus({
|
|
130
|
+
name: this.config.service.name,
|
|
131
|
+
version: this.config.service.version ?? "0.0.0",
|
|
132
|
+
description: this.config.service.description,
|
|
133
|
+
running: true,
|
|
134
|
+
pid: process.pid,
|
|
135
|
+
metadata: {
|
|
136
|
+
framework: "vision-server",
|
|
137
|
+
integrations: Object.keys(cleanIntegrations).length > 0 ? cleanIntegrations : undefined,
|
|
138
|
+
drizzle: drizzleInfo.detected ? {
|
|
139
|
+
detected: true,
|
|
140
|
+
configPath: drizzleInfo.configPath,
|
|
141
|
+
studioUrl: drizzleStudioUrl,
|
|
142
|
+
autoStarted: this.config.service.drizzle?.autoStart || false
|
|
143
|
+
} : {
|
|
144
|
+
detected: false,
|
|
145
|
+
configPath: undefined,
|
|
146
|
+
studioUrl: undefined,
|
|
147
|
+
autoStarted: false
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
} else {
|
|
152
|
+
this.visionCore = null;
|
|
153
|
+
}
|
|
154
|
+
this.eventBus = this.config.pubsub?.eventBus || new EventBus({
|
|
155
|
+
redis: this.config.pubsub?.redis,
|
|
156
|
+
devMode: this.config.pubsub?.devMode,
|
|
157
|
+
workerConcurrency: this.config.pubsub?.workerConcurrency,
|
|
158
|
+
queue: this.config.pubsub?.queue,
|
|
159
|
+
worker: this.config.pubsub?.worker,
|
|
160
|
+
queueEvents: this.config.pubsub?.queueEvents
|
|
161
|
+
});
|
|
162
|
+
if (visionEnabled) {
|
|
163
|
+
this.registerEventMethods();
|
|
164
|
+
}
|
|
165
|
+
if (visionEnabled) {
|
|
166
|
+
this.installVisionMiddleware();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
registerEventMethods() {
|
|
170
|
+
const server = this.visionCore.getServer();
|
|
171
|
+
server.registerMethod("events/list", async () => {
|
|
172
|
+
const events = eventRegistry.getAllEvents();
|
|
173
|
+
return events.map((event) => ({
|
|
174
|
+
name: event.name,
|
|
175
|
+
description: event.description,
|
|
176
|
+
icon: event.icon,
|
|
177
|
+
tags: event.tags,
|
|
178
|
+
handlers: event.handlers.length,
|
|
179
|
+
lastTriggered: event.lastTriggered,
|
|
180
|
+
totalCount: event.totalCount,
|
|
181
|
+
failedCount: event.failedCount
|
|
182
|
+
}));
|
|
183
|
+
});
|
|
184
|
+
server.registerMethod("cron/list", async () => {
|
|
185
|
+
const crons = eventRegistry.getAllCrons();
|
|
186
|
+
return crons.map((cron) => ({
|
|
187
|
+
name: cron.name,
|
|
188
|
+
schedule: cron.schedule,
|
|
189
|
+
description: cron.description,
|
|
190
|
+
icon: cron.icon,
|
|
191
|
+
tags: cron.tags,
|
|
192
|
+
lastRun: cron.lastRun,
|
|
193
|
+
nextRun: cron.nextRun,
|
|
194
|
+
totalRuns: cron.totalRuns,
|
|
195
|
+
failedRuns: cron.failedRuns
|
|
196
|
+
}));
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
installVisionMiddleware() {
|
|
200
|
+
const logging = this.config.vision?.logging !== false;
|
|
201
|
+
this.use("*", async (c, next) => {
|
|
202
|
+
if (c.req.method === "OPTIONS") {
|
|
203
|
+
return next();
|
|
204
|
+
}
|
|
205
|
+
const startTime = Date.now();
|
|
206
|
+
const trace = this.visionCore.createTrace(c.req.method, c.req.path);
|
|
207
|
+
return visionContext.run({
|
|
208
|
+
vision: this.visionCore,
|
|
209
|
+
traceId: trace.id,
|
|
210
|
+
rootSpanId: ""
|
|
211
|
+
}, async () => {
|
|
212
|
+
return runInTraceContext(trace.id, async () => {
|
|
213
|
+
const tracer = this.visionCore.getTracer();
|
|
214
|
+
const rootSpan = tracer.startSpan("http.request", trace.id);
|
|
215
|
+
const ctx = visionContext.getStore();
|
|
216
|
+
if (ctx) {
|
|
217
|
+
ctx.rootSpanId = rootSpan.id;
|
|
218
|
+
}
|
|
219
|
+
if (!c.span) {
|
|
220
|
+
c.addContext = (context) => {
|
|
221
|
+
const current = visionContext.getStore();
|
|
222
|
+
const currentTraceId = current?.traceId || trace.id;
|
|
223
|
+
const visionTrace = this.visionCore.getTraceStore().getTrace(currentTraceId);
|
|
224
|
+
if (visionTrace) {
|
|
225
|
+
visionTrace.metadata = { ...visionTrace.metadata || {}, ...context };
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
c.span = (name, attributes = {}, fn) => {
|
|
229
|
+
const current = visionContext.getStore();
|
|
230
|
+
const currentTraceId = current?.traceId || trace.id;
|
|
231
|
+
const currentRootSpanId = current?.rootSpanId || rootSpan.id;
|
|
232
|
+
const s = tracer.startSpan(name, currentTraceId, currentRootSpanId);
|
|
233
|
+
for (const [k, v] of Object.entries(attributes))
|
|
234
|
+
tracer.setAttribute(s.id, k, v);
|
|
235
|
+
try {
|
|
236
|
+
const result = fn ? fn() : undefined;
|
|
237
|
+
const completed = tracer.endSpan(s.id);
|
|
238
|
+
if (completed)
|
|
239
|
+
this.visionCore.getTraceStore().addSpan(currentTraceId, completed);
|
|
240
|
+
return result;
|
|
241
|
+
} catch (err) {
|
|
242
|
+
tracer.setAttribute(s.id, "error", true);
|
|
243
|
+
tracer.setAttribute(s.id, "error.message", err instanceof Error ? err.message : String(err));
|
|
244
|
+
const completed = tracer.endSpan(s.id);
|
|
245
|
+
if (completed)
|
|
246
|
+
this.visionCore.getTraceStore().addSpan(currentTraceId, completed);
|
|
247
|
+
throw err;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
tracer.setAttribute(rootSpan.id, "http.method", c.req.method);
|
|
252
|
+
tracer.setAttribute(rootSpan.id, "http.path", c.req.path);
|
|
253
|
+
tracer.setAttribute(rootSpan.id, "http.url", c.req.url);
|
|
254
|
+
const url = new URL(c.req.url);
|
|
255
|
+
if (url.search) {
|
|
256
|
+
tracer.setAttribute(rootSpan.id, "http.query", url.search);
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const rawReq = c.req.raw;
|
|
260
|
+
const headers = {};
|
|
261
|
+
rawReq.headers.forEach((v, k) => {
|
|
262
|
+
headers[k] = v;
|
|
263
|
+
});
|
|
264
|
+
const urlObj = new URL(c.req.url);
|
|
265
|
+
const query = {};
|
|
266
|
+
urlObj.searchParams.forEach((v, k) => {
|
|
267
|
+
query[k] = v;
|
|
268
|
+
});
|
|
269
|
+
let body = undefined;
|
|
270
|
+
const ct = headers["content-type"] || headers["Content-Type"];
|
|
271
|
+
if (ct && ct.includes("application/json")) {
|
|
272
|
+
try {
|
|
273
|
+
body = await rawReq.clone().json();
|
|
274
|
+
} catch {}
|
|
275
|
+
}
|
|
276
|
+
const sessionId = headers["x-vision-session"];
|
|
277
|
+
if (sessionId) {
|
|
278
|
+
tracer.setAttribute(rootSpan.id, "session.id", sessionId);
|
|
279
|
+
trace.metadata = { ...trace.metadata || {}, sessionId };
|
|
280
|
+
}
|
|
281
|
+
const requestMeta = {
|
|
282
|
+
method: c.req.method,
|
|
283
|
+
url: urlObj.pathname + (urlObj.search || ""),
|
|
284
|
+
headers,
|
|
285
|
+
query: Object.keys(query).length ? query : undefined,
|
|
286
|
+
body
|
|
287
|
+
};
|
|
288
|
+
tracer.setAttribute(rootSpan.id, "http.request", requestMeta);
|
|
289
|
+
trace.metadata = { ...trace.metadata || {}, request: requestMeta };
|
|
290
|
+
if (logging) {
|
|
291
|
+
const parts = [
|
|
292
|
+
`method=${c.req.method}`,
|
|
293
|
+
`path=${c.req.path}`
|
|
294
|
+
];
|
|
295
|
+
if (sessionId)
|
|
296
|
+
parts.push(`sessionId=${sessionId}`);
|
|
297
|
+
parts.push(`traceId=${trace.id}`);
|
|
298
|
+
console.info(`INF starting request ${parts.join(" ")}`);
|
|
299
|
+
}
|
|
300
|
+
await next();
|
|
301
|
+
tracer.setAttribute(rootSpan.id, "http.status_code", c.res.status);
|
|
302
|
+
const resHeaders = {};
|
|
303
|
+
c.res.headers?.forEach((v, k) => {
|
|
304
|
+
resHeaders[k] = v;
|
|
305
|
+
});
|
|
306
|
+
let respBody = undefined;
|
|
307
|
+
const resCt = c.res.headers?.get("content-type") || "";
|
|
308
|
+
try {
|
|
309
|
+
const clone = c.res.clone();
|
|
310
|
+
if (resCt.includes("application/json")) {
|
|
311
|
+
const txt = await clone.text();
|
|
312
|
+
if (txt && txt.length <= 65536) {
|
|
313
|
+
try {
|
|
314
|
+
respBody = JSON.parse(txt);
|
|
315
|
+
} catch {
|
|
316
|
+
respBody = txt;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} catch {}
|
|
321
|
+
const responseMeta = {
|
|
322
|
+
status: c.res.status,
|
|
323
|
+
headers: Object.keys(resHeaders).length ? resHeaders : undefined,
|
|
324
|
+
body: respBody
|
|
325
|
+
};
|
|
326
|
+
tracer.setAttribute(rootSpan.id, "http.response", responseMeta);
|
|
327
|
+
trace.metadata = { ...trace.metadata || {}, response: responseMeta };
|
|
328
|
+
} catch (error) {
|
|
329
|
+
tracer.addEvent(rootSpan.id, "error", {
|
|
330
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
331
|
+
stack: error instanceof Error ? error.stack : undefined
|
|
332
|
+
});
|
|
333
|
+
tracer.setAttribute(rootSpan.id, "error", true);
|
|
334
|
+
throw error;
|
|
335
|
+
} finally {
|
|
336
|
+
const completedSpan = tracer.endSpan(rootSpan.id);
|
|
337
|
+
if (completedSpan) {
|
|
338
|
+
this.visionCore.getTraceStore().addSpan(trace.id, completedSpan);
|
|
339
|
+
}
|
|
340
|
+
const duration = Date.now() - startTime;
|
|
341
|
+
this.visionCore.completeTrace(trace.id, c.res.status, duration);
|
|
342
|
+
c.header("X-Vision-Trace-Id", trace.id);
|
|
343
|
+
if (logging) {
|
|
344
|
+
console.info(`INF request completed code=${c.res.status} duration=${duration}ms method=${c.req.method} path=${c.req.path} traceId=${trace.id}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
service(name) {
|
|
352
|
+
const builder = new ServiceBuilder(name, this.eventBus, this.visionCore);
|
|
353
|
+
this.serviceBuilders.push(builder);
|
|
354
|
+
return builder;
|
|
355
|
+
}
|
|
356
|
+
getServiceSummaries() {
|
|
357
|
+
const summaries = [];
|
|
358
|
+
for (const builder of this.serviceBuilders) {
|
|
359
|
+
const name = builder.getDisplayName?.() ?? "Service";
|
|
360
|
+
const rawRoutes = builder.getRoutesMetadata?.();
|
|
361
|
+
if (!rawRoutes || !Array.isArray(rawRoutes))
|
|
362
|
+
continue;
|
|
363
|
+
const routes = rawRoutes.map((r) => ({
|
|
364
|
+
method: r.method,
|
|
365
|
+
path: r.path,
|
|
366
|
+
handler: name,
|
|
367
|
+
queryParams: r.queryParams,
|
|
368
|
+
requestBody: r.requestBody,
|
|
369
|
+
responseBody: r.responseBody
|
|
370
|
+
}));
|
|
371
|
+
summaries.push({ name, routes });
|
|
372
|
+
}
|
|
373
|
+
return summaries;
|
|
374
|
+
}
|
|
375
|
+
buildAllServices() {
|
|
376
|
+
const allServices = [];
|
|
377
|
+
for (const builder of this.serviceBuilders) {
|
|
378
|
+
builder.build(this, allServices);
|
|
379
|
+
}
|
|
380
|
+
return allServices;
|
|
381
|
+
}
|
|
382
|
+
getVision() {
|
|
383
|
+
return this.visionCore;
|
|
384
|
+
}
|
|
385
|
+
getEventBus() {
|
|
386
|
+
return this.eventBus;
|
|
387
|
+
}
|
|
388
|
+
async autoloadRoutes() {
|
|
389
|
+
const enabled = this.config.routes?.autodiscover !== false;
|
|
390
|
+
const dirs = this.config.routes?.dirs ?? ["app/routes"];
|
|
391
|
+
if (!enabled)
|
|
392
|
+
return [];
|
|
393
|
+
const existing = [];
|
|
394
|
+
for (const d of dirs) {
|
|
395
|
+
try {
|
|
396
|
+
if (existsSync(d))
|
|
397
|
+
existing.push(d);
|
|
398
|
+
} catch {}
|
|
399
|
+
}
|
|
400
|
+
if (existing.length === 0)
|
|
401
|
+
return [];
|
|
402
|
+
const { loadSubApps } = await import("./router");
|
|
403
|
+
let allSubAppSummaries = [];
|
|
404
|
+
for (const d of existing) {
|
|
405
|
+
try {
|
|
406
|
+
const summaries = await loadSubApps(this, d, this.eventBus);
|
|
407
|
+
allSubAppSummaries = allSubAppSummaries.concat(summaries);
|
|
408
|
+
} catch (e) {
|
|
409
|
+
console.error(`❌ Failed to load sub-apps from ${d}:`, e?.message || e);
|
|
410
|
+
if (e instanceof Error && e.stack) {
|
|
411
|
+
console.error("Stack:", e.stack);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return allSubAppSummaries;
|
|
416
|
+
}
|
|
417
|
+
async start(port = 3000, options) {
|
|
418
|
+
const { hostname, ...restOptions } = options || {};
|
|
419
|
+
const { fetch: _bf, port: _bp, ...bunRest } = restOptions;
|
|
420
|
+
const { fetch: _nf, port: _np, ...nodeRest } = restOptions;
|
|
421
|
+
const rootSummaries = this.buildAllServices();
|
|
422
|
+
const subAppSummaries = await this.autoloadRoutes();
|
|
423
|
+
const allServices = new Map;
|
|
424
|
+
for (const summary of rootSummaries || []) {
|
|
425
|
+
allServices.set(summary.name, { name: summary.name, routes: [...summary.routes] });
|
|
426
|
+
}
|
|
427
|
+
for (const summary of subAppSummaries || []) {
|
|
428
|
+
if (allServices.has(summary.name)) {
|
|
429
|
+
const existing = allServices.get(summary.name);
|
|
430
|
+
existing.routes.push(...summary.routes);
|
|
431
|
+
} else {
|
|
432
|
+
allServices.set(summary.name, { name: summary.name, routes: [...summary.routes] });
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (this.visionCore && allServices.size > 0) {
|
|
436
|
+
const servicesToRegister = Array.from(allServices.values());
|
|
437
|
+
this.visionCore.registerServices(servicesToRegister);
|
|
438
|
+
const flatRoutes = servicesToRegister.flatMap((s) => s.routes);
|
|
439
|
+
this.visionCore.registerRoutes(flatRoutes);
|
|
440
|
+
console.log(`✅ Registered ${servicesToRegister.length} total services (${flatRoutes.length} routes)`);
|
|
441
|
+
}
|
|
442
|
+
const state = getGlobalState();
|
|
443
|
+
if (state.instance && state.instance !== this) {
|
|
444
|
+
await cleanupVisionInstance(state.instance);
|
|
445
|
+
}
|
|
446
|
+
state.instance = this;
|
|
447
|
+
console.log(`\uD83D\uDE80 Starting ${this.config.service.name}...`);
|
|
448
|
+
console.log(`\uD83D\uDCE1 API Server: http://localhost:${port}`);
|
|
449
|
+
if (!this.signalHandler) {
|
|
450
|
+
this.signalHandler = async () => {
|
|
451
|
+
const s = getGlobalState();
|
|
452
|
+
if (s.instance) {
|
|
453
|
+
await cleanupVisionInstance(s.instance);
|
|
454
|
+
}
|
|
455
|
+
try {
|
|
456
|
+
process.exit(0);
|
|
457
|
+
} catch {}
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
const handleSignal = this.signalHandler;
|
|
461
|
+
process.removeListener("SIGINT", handleSignal);
|
|
462
|
+
process.removeListener("SIGTERM", handleSignal);
|
|
463
|
+
try {
|
|
464
|
+
process.removeListener("SIGQUIT", handleSignal);
|
|
465
|
+
} catch {}
|
|
466
|
+
process.on("SIGINT", handleSignal);
|
|
467
|
+
process.on("SIGTERM", handleSignal);
|
|
468
|
+
try {
|
|
469
|
+
process.on("SIGQUIT", handleSignal);
|
|
470
|
+
} catch {}
|
|
471
|
+
try {
|
|
472
|
+
const hot = import.meta?.hot;
|
|
473
|
+
if (hot && typeof hot.dispose === "function") {
|
|
474
|
+
hot.dispose(async () => {
|
|
475
|
+
console.log("♻️ Hot reload: reloading...");
|
|
476
|
+
process.off("SIGINT", handleSignal);
|
|
477
|
+
process.off("SIGTERM", handleSignal);
|
|
478
|
+
try {
|
|
479
|
+
process.off("SIGQUIT", handleSignal);
|
|
480
|
+
} catch {}
|
|
481
|
+
const s = getGlobalState();
|
|
482
|
+
await cleanupVisionInstance(this);
|
|
483
|
+
if (s.instance === this) {
|
|
484
|
+
s.instance = null;
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
} catch {}
|
|
489
|
+
if (typeof process !== "undefined" && process.versions?.bun) {
|
|
490
|
+
const BunServe = globalThis.Bun?.serve;
|
|
491
|
+
if (typeof BunServe === "function") {
|
|
492
|
+
try {
|
|
493
|
+
const existing = globalThis.__vision_bun_server;
|
|
494
|
+
if (existing && typeof existing.stop === "function") {
|
|
495
|
+
try {
|
|
496
|
+
existing.stop();
|
|
497
|
+
} catch {}
|
|
498
|
+
}
|
|
499
|
+
} catch {}
|
|
500
|
+
this.bunServer = BunServe({
|
|
501
|
+
...bunRest,
|
|
502
|
+
fetch: this.fetch.bind(this),
|
|
503
|
+
port,
|
|
504
|
+
hostname
|
|
505
|
+
});
|
|
506
|
+
try {
|
|
507
|
+
globalThis.__vision_bun_server = this.bunServer;
|
|
508
|
+
} catch {}
|
|
509
|
+
} else {
|
|
510
|
+
console.warn("Bun detected but Bun.serve is unavailable");
|
|
511
|
+
return this;
|
|
512
|
+
}
|
|
513
|
+
} else if (typeof process !== "undefined" && process.versions?.node) {
|
|
514
|
+
const { serve } = await import("@hono/node-server");
|
|
515
|
+
serve({
|
|
516
|
+
...nodeRest,
|
|
517
|
+
fetch: this.fetch.bind(this),
|
|
518
|
+
port,
|
|
519
|
+
hostname
|
|
520
|
+
});
|
|
521
|
+
} else {
|
|
522
|
+
console.log("⚠️ Use your runtime's serve function");
|
|
523
|
+
return this;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
setEventBus(eventBus) {
|
|
527
|
+
this.eventBus = eventBus;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function getVisionContext() {
|
|
531
|
+
return visionContext.getStore();
|
|
532
|
+
}
|
|
533
|
+
function detectDrizzle() {
|
|
534
|
+
const possiblePaths = [
|
|
535
|
+
"drizzle.config.ts",
|
|
536
|
+
"drizzle.config.js",
|
|
537
|
+
"drizzle.config.mjs"
|
|
538
|
+
];
|
|
539
|
+
for (const path of possiblePaths) {
|
|
540
|
+
if (existsSync(path)) {
|
|
541
|
+
return { detected: true, configPath: path };
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return { detected: false };
|
|
545
|
+
}
|
|
546
|
+
function startDrizzleStudio(port) {
|
|
547
|
+
const state = getGlobalState();
|
|
548
|
+
if (state.drizzleProcess) {
|
|
549
|
+
console.log("⚠️ Drizzle Studio is already running");
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
try {
|
|
553
|
+
if (process.platform === "win32") {
|
|
554
|
+
const res = spawnSync("powershell", ["-NoProfile", "-Command", `netstat -ano | Select-String -Pattern "LISTENING\\s+.*:${port}\\s"`], { encoding: "utf-8" });
|
|
555
|
+
if ((res.stdout || "").trim().length > 0) {
|
|
556
|
+
console.log(`⚠️ Drizzle Studio port ${port} already in use; assuming it is running. Skipping auto-start.`);
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
} else {
|
|
560
|
+
const res = spawnSync("lsof", ["-i", `tcp:${port}`, "-sTCP:LISTEN"], { encoding: "utf-8" });
|
|
561
|
+
if ((res.stdout || "").trim().length > 0) {
|
|
562
|
+
console.log(`⚠️ Drizzle Studio port ${port} already in use; assuming it is running. Skipping auto-start.`);
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
} catch {}
|
|
567
|
+
try {
|
|
568
|
+
const proc = spawn("npx", ["drizzle-kit", "studio", "--port", String(port), "--host", "0.0.0.0"], {
|
|
569
|
+
stdio: "inherit",
|
|
570
|
+
detached: false,
|
|
571
|
+
shell: process.platform === "win32"
|
|
572
|
+
});
|
|
573
|
+
state.drizzleProcess = proc;
|
|
574
|
+
proc.on("error", (error) => {
|
|
575
|
+
console.error("❌ Failed to start Drizzle Studio:", error.message);
|
|
576
|
+
});
|
|
577
|
+
proc.on("exit", (code) => {
|
|
578
|
+
if (code !== 0 && code !== null) {
|
|
579
|
+
console.error(`❌ Drizzle Studio exited with code ${code}`);
|
|
580
|
+
}
|
|
581
|
+
const s = getGlobalState();
|
|
582
|
+
if (s.drizzleProcess === proc) {
|
|
583
|
+
s.drizzleProcess = null;
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
console.log(`✅ Drizzle Studio: https://local.drizzle.studio`);
|
|
587
|
+
return true;
|
|
588
|
+
} catch (error) {
|
|
589
|
+
console.error("❌ Failed to start Drizzle Studio:", error);
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function stopDrizzleStudio(options) {
|
|
594
|
+
const state = getGlobalState();
|
|
595
|
+
if (state.drizzleProcess) {
|
|
596
|
+
state.drizzleProcess.removeAllListeners();
|
|
597
|
+
state.drizzleProcess.kill();
|
|
598
|
+
state.drizzleProcess = null;
|
|
599
|
+
if (options?.log !== false) {
|
|
600
|
+
console.log("\uD83D\uDED1 Drizzle Studio stopped");
|
|
601
|
+
}
|
|
602
|
+
return true;
|
|
603
|
+
}
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
export {
|
|
607
|
+
getVisionContext,
|
|
608
|
+
Vision
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
//# debugId=030980C43D326B6264756E2164756E21
|