overmux 0.0.11 → 0.0.12
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 +12 -0
- package/dist/bin.js +145 -58
- package/dist/bin.js.map +1 -1
- package/dist/docs/400-reference/200-configuration.md +19 -4
- package/dist/docs/400-reference/500-server/000-index.md +4 -0
- package/dist/docs/400-reference/500-server/100-resources.md +5 -0
- package/dist/docs/400-reference/500-server/200-operations.md +1 -0
- package/dist/docs/400-reference/500-server/300-streams.md +7 -1
- package/dist/docs/400-reference/500-server/500-api.md +26 -5
- package/dist/exports/client.d.ts +1 -1
- package/dist/exports/client.d.ts.map +1 -1
- package/dist/exports/client.js +6 -7
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/{index-Dk11nKIv.d.ts → index-C-8Kzrnc.d.ts} +23 -8
- package/dist/exports/index-C-8Kzrnc.d.ts.map +1 -0
- package/dist/exports/index.d.ts +2 -2
- package/dist/exports/index.js +1 -1
- package/dist/exports/index.js.map +1 -1
- package/dist/exports/{notifications-BFAD3QQl.js → notifications-BBKvKZgI.js} +8 -2
- package/dist/exports/{notifications-BFAD3QQl.js.map → notifications-BBKvKZgI.js.map} +1 -1
- package/dist/exports/server.d.ts.map +1 -1
- package/dist/exports/server.js +8 -2
- package/dist/exports/server.js.map +1 -1
- package/dist/internal/server/coordinator/server-child.js +592 -883
- package/dist/internal/server/coordinator/server-child.js.map +1 -1
- package/dist/internal/server/worker-entry.d.ts +1 -0
- package/dist/internal/server/worker-entry.js +728 -0
- package/dist/internal/server/worker-entry.js.map +1 -0
- package/dist/push/sw.js +4 -4
- package/dist/push/sw.js.map +1 -1
- package/dist/worker-protocol-hXs4FXCH.js +638 -0
- package/dist/worker-protocol-hXs4FXCH.js.map +1 -0
- package/docs/400-reference/200-configuration.md +19 -4
- package/docs/400-reference/500-server/000-index.md +4 -0
- package/docs/400-reference/500-server/100-resources.md +5 -0
- package/docs/400-reference/500-server/200-operations.md +1 -0
- package/docs/400-reference/500-server/300-streams.md +7 -1
- package/docs/400-reference/500-server/500-api.md +26 -5
- package/package.json +3 -2
- package/src/internal/client/host/overmux-host.tsx +4 -8
- package/src/internal/server/coordinator/ipc-protocol.ts +2 -1
- package/src/internal/server/http/operation-handler.ts +3 -0
- package/src/internal/server/http/request-logging.ts +1 -1
- package/src/internal/server/protocol-error.ts +19 -0
- package/src/internal/server/runtime/create-runtime.ts +11 -5
- package/src/internal/server/runtime/handler-logger.ts +54 -0
- package/src/internal/server/runtime/runtime-operations.ts +23 -5
- package/src/internal/server/runtime/runtime-resources.ts +158 -88
- package/src/internal/server/runtime/runtime-streams.ts +24 -8
- package/src/internal/server/runtime/worker-capabilities.ts +67 -0
- package/src/internal/server/runtime/worker-connection.ts +549 -0
- package/src/internal/server/runtime/worker-protocol.ts +173 -0
- package/src/internal/server/runtime/worker-runtime.ts +214 -0
- package/src/internal/server/server-logger.ts +25 -37
- package/src/internal/server/server-startup-options.ts +7 -6
- package/src/internal/server/start-application-server.ts +22 -18
- package/src/internal/server/start-overmux-server.ts +2 -2
- package/src/internal/server/websocket-connection.ts +97 -32
- package/src/internal/server/websocket-server.ts +5 -1
- package/src/internal/server/worker-entry.ts +504 -0
- package/src/internal/shared/protocol.ts +3 -3
- package/src/public/config.ts +15 -23
- package/src/public/handler-logger.ts +7 -0
- package/src/public/index.ts +3 -1
- package/dist/exports/index-Dk11nKIv.d.ts.map +0 -1
|
@@ -1,16 +1,16 @@
|
|
|
1
|
+
import { A as overmuxLogoutPath, C as serverProtocolMessageSchema, D as instanceIdSchema, E as backgroundNotificationSubscriptionSchema, F as serverConfigRuntimeSchema, M as authDurationSchema, N as getOvermuxPaths, O as logLevelSchema, S as notificationEventSchema, T as backgroundNotificationDisableRequestSchema, b as decodeProtocolFrame, c as defaultInstanceId, g as getServerLogFile, h as errorMessage, i as postWorkerMessage, j as overmuxSettingsPath, k as notificationSchema, l as ProtocolError, m as errorDetails$1, n as deserializeWorkerError, o as workerLimits, p as createServerLogger, r as encodeWorkerMessage, s as createRuntimeInstance, t as decodeWorkerMessage, u as OperationValidationError, w as webSocketCloseCode, x as encodeProtocolFrame, y as clientProtocolMessageSchema } from "../../../worker-protocol-hXs4FXCH.js";
|
|
1
2
|
import { serve } from "@hono/node-server";
|
|
2
|
-
import { chmod, link, mkdir, open, readFile, realpath, rename, rm, stat } from "node:fs/promises";
|
|
3
|
-
import { createServer, isIP } from "node:net";
|
|
4
|
-
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
|
-
import { createJiti } from "jiti";
|
|
6
|
-
import { ZodError, z } from "zod";
|
|
7
|
-
import { homedir, hostname } from "node:os";
|
|
8
3
|
import { once } from "node:events";
|
|
4
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
9
5
|
import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
6
|
+
import { chmod, link, mkdir, open, readFile, rename, rm, stat } from "node:fs/promises";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { createServer } from "node:net";
|
|
9
|
+
import { ZodError, z } from "zod";
|
|
10
10
|
import { generateCookie } from "hono/cookie";
|
|
11
11
|
import { bodyLimit } from "hono/body-limit";
|
|
12
12
|
import { parse } from "hono/utils/cookie";
|
|
13
|
-
import {
|
|
13
|
+
import { constants, readFileSync } from "node:fs";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { Hono } from "hono";
|
|
16
16
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
@@ -18,146 +18,7 @@ import { proxyFetch, proxyUpgrade } from "httpxy";
|
|
|
18
18
|
import { WebSocket, WebSocketServer } from "ws";
|
|
19
19
|
import webPush from "web-push";
|
|
20
20
|
import { isDeepStrictEqual } from "node:util";
|
|
21
|
-
|
|
22
|
-
const durationPattern$1 = /^([1-9]\d*)(ms|s|m|h|d)$/;
|
|
23
|
-
const millisecondsByUnit$1 = {
|
|
24
|
-
d: 1440 * 60 * 1e3,
|
|
25
|
-
h: 3600 * 1e3,
|
|
26
|
-
m: 60 * 1e3,
|
|
27
|
-
ms: 1,
|
|
28
|
-
s: 1e3
|
|
29
|
-
};
|
|
30
|
-
const durationToMilliseconds$1 = (duration) => {
|
|
31
|
-
const match = durationPattern$1.exec(duration);
|
|
32
|
-
if (!match) return;
|
|
33
|
-
const unit = match[2];
|
|
34
|
-
const milliseconds = Number(match[1]) * millisecondsByUnit$1[unit];
|
|
35
|
-
return Number.isSafeInteger(milliseconds) ? milliseconds : void 0;
|
|
36
|
-
};
|
|
37
|
-
const aiContextSnippets = ["package-source", "tech-stack-recommendations"];
|
|
38
|
-
const instanceIdSchema$1 = z.string().max(253).regex(/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?![\s\S])/, "Expected a lowercase instance ID with only ASCII letters, digits, internal dots and hyphens");
|
|
39
|
-
const zodTypeSchema = z.custom((value) => value instanceof z.ZodType, "Expected a Zod schema");
|
|
40
|
-
const functionSchema = z.custom((value) => typeof value === "function", "Expected a function");
|
|
41
|
-
const contractRuntimeSchema = z.object({
|
|
42
|
-
input: zodTypeSchema,
|
|
43
|
-
output: zodTypeSchema
|
|
44
|
-
});
|
|
45
|
-
const operationRuntimeSchema = z.object({
|
|
46
|
-
handle: functionSchema,
|
|
47
|
-
input: zodTypeSchema,
|
|
48
|
-
output: zodTypeSchema
|
|
49
|
-
});
|
|
50
|
-
const streamHandlerRuntimeSchema = z.object({
|
|
51
|
-
contract: z.object({
|
|
52
|
-
clientMessage: zodTypeSchema,
|
|
53
|
-
input: zodTypeSchema,
|
|
54
|
-
serverMessage: zodTypeSchema
|
|
55
|
-
}),
|
|
56
|
-
open: functionSchema
|
|
57
|
-
});
|
|
58
|
-
const resourceDefinitionRuntimeSchema = z.discriminatedUnion("kind", [
|
|
59
|
-
z.object({
|
|
60
|
-
contract: contractRuntimeSchema,
|
|
61
|
-
kind: z.literal("query"),
|
|
62
|
-
read: functionSchema
|
|
63
|
-
}),
|
|
64
|
-
z.object({
|
|
65
|
-
contract: contractRuntimeSchema,
|
|
66
|
-
kind: z.literal("subscription"),
|
|
67
|
-
read: functionSchema,
|
|
68
|
-
subscribe: functionSchema
|
|
69
|
-
}),
|
|
70
|
-
z.object({
|
|
71
|
-
combine: functionSchema,
|
|
72
|
-
contract: contractRuntimeSchema,
|
|
73
|
-
dependencies: z.record(z.string(), z.string()),
|
|
74
|
-
kind: z.literal("derived")
|
|
75
|
-
})
|
|
76
|
-
]);
|
|
77
|
-
const authDurationSchema = z.string().regex(/^[1-9]\d*[mhd]$/, "Expected a positive duration such as 30d").refine((value) => durationToMilliseconds$1(value) !== void 0, "Duration is too large");
|
|
78
|
-
const isLoopbackHostname$1 = (hostname) => hostname === "localhost" || hostname === "[::1]" || /^127(?:\.\d{1,3}){3}$/.test(hostname);
|
|
79
|
-
const exactOriginSchema = z.url().refine((value) => new URL(value).origin === value, "Expected an origin").refine((value) => {
|
|
80
|
-
const url = new URL(value);
|
|
81
|
-
return url.protocol === "https:" || url.protocol === "http:" && isLoopbackHostname$1(url.hostname);
|
|
82
|
-
}, "Non-loopback Overmux origins must use HTTPS");
|
|
83
|
-
const loopbackPeerSchema = z.string().refine((value) => isIP(value) !== 0 && (value === "::1" || value.startsWith("127.")), "Expected a loopback IP address");
|
|
84
|
-
const authConfigRuntimeSchema = z.object({
|
|
85
|
-
mode: z.literal("cli-login"),
|
|
86
|
-
origins: z.array(exactOriginSchema).min(1).refine((origins) => new Set(origins).size === origins.length, "Expected unique origins").optional(),
|
|
87
|
-
sessionLifetime: z.union([z.literal("forever"), authDurationSchema]).default("forever"),
|
|
88
|
-
trustedProxyPeer: loopbackPeerSchema.optional()
|
|
89
|
-
}).strict();
|
|
90
|
-
const serverConfigRuntimeSchema = z.object({
|
|
91
|
-
host: z.string().min(1).optional(),
|
|
92
|
-
port: z.number().int().min(1).max(65535).optional(),
|
|
93
|
-
productionWebAssetsDir: z.string().min(1).optional(),
|
|
94
|
-
watch: z.boolean().optional()
|
|
95
|
-
});
|
|
96
|
-
const serverDefinitionRuntimeSchema = z.object({
|
|
97
|
-
operations: z.record(z.string(), operationRuntimeSchema).optional(),
|
|
98
|
-
resources: z.record(z.string(), resourceDefinitionRuntimeSchema),
|
|
99
|
-
streams: z.record(z.string(), streamHandlerRuntimeSchema).optional()
|
|
100
|
-
});
|
|
101
|
-
const configDefinitionRuntimeSchema = serverConfigRuntimeSchema.extend({
|
|
102
|
-
aiContextSnippets: z.array(z.enum(aiContextSnippets)).optional(),
|
|
103
|
-
auth: authConfigRuntimeSchema,
|
|
104
|
-
debug: z.boolean().optional(),
|
|
105
|
-
instanceId: z.union([instanceIdSchema$1, z.custom((value) => typeof value === "function", "Expected an instance ID or a function of the bound port")]).optional(),
|
|
106
|
-
server: serverDefinitionRuntimeSchema,
|
|
107
|
-
vite: z.string().min(1).optional()
|
|
108
|
-
});
|
|
109
|
-
const resolveLogicalPath = async (path) => {
|
|
110
|
-
const logicalPath = resolve(path);
|
|
111
|
-
return {
|
|
112
|
-
logicalPath,
|
|
113
|
-
realPath: await realpath(logicalPath)
|
|
114
|
-
};
|
|
115
|
-
};
|
|
116
|
-
const resolveOvermuxConfigPaths = async (configPath) => {
|
|
117
|
-
const { logicalPath, realPath } = await resolveLogicalPath(configPath);
|
|
118
|
-
return {
|
|
119
|
-
logicalApplicationRoot: dirname(logicalPath),
|
|
120
|
-
logicalConfigPath: logicalPath,
|
|
121
|
-
realApplicationRoot: dirname(realPath),
|
|
122
|
-
realConfigPath: realPath
|
|
123
|
-
};
|
|
124
|
-
};
|
|
125
|
-
const unwrapDefaultExport = (module) => {
|
|
126
|
-
if (typeof module === "object" && module !== null && "default" in module) return module.default;
|
|
127
|
-
return module;
|
|
128
|
-
};
|
|
129
|
-
const loadOvermuxConfig = async ({ aliases = {}, configPath }) => {
|
|
130
|
-
const paths = await resolveOvermuxConfigPaths(configPath);
|
|
131
|
-
const module = await createJiti(paths.logicalConfigPath, {
|
|
132
|
-
alias: aliases,
|
|
133
|
-
fsCache: true,
|
|
134
|
-
jsx: {
|
|
135
|
-
importSource: "react",
|
|
136
|
-
runtime: "automatic"
|
|
137
|
-
},
|
|
138
|
-
moduleCache: false,
|
|
139
|
-
sourceMaps: true,
|
|
140
|
-
tsconfigPaths: true
|
|
141
|
-
}).import(paths.logicalConfigPath);
|
|
142
|
-
return {
|
|
143
|
-
config: configDefinitionRuntimeSchema.parse(unwrapDefaultExport(module)),
|
|
144
|
-
paths
|
|
145
|
-
};
|
|
146
|
-
};
|
|
147
|
-
const xdgDirectory = (configured, fallback) => configured && isAbsolute(configured) ? configured : fallback;
|
|
148
|
-
const overmuxDirectory = (root) => join(root, "overmux");
|
|
149
|
-
const getOvermuxPaths = ({ environment = process.env, homeDir = homedir(), xdgCacheHome = environment.XDG_CACHE_HOME, xdgConfigHome = environment.XDG_CONFIG_HOME, xdgDataHome = environment.XDG_DATA_HOME, xdgRuntimeDir = environment.XDG_RUNTIME_DIR, xdgStateHome = environment.XDG_STATE_HOME } = {}) => {
|
|
150
|
-
if (!isAbsolute(homeDir)) throw new TypeError("The home directory must be absolute.");
|
|
151
|
-
const runtimeDir = xdgRuntimeDir && isAbsolute(xdgRuntimeDir) ? overmuxDirectory(xdgRuntimeDir) : void 0;
|
|
152
|
-
return {
|
|
153
|
-
cacheDir: overmuxDirectory(xdgDirectory(xdgCacheHome, join(homeDir, ".cache"))),
|
|
154
|
-
configDir: overmuxDirectory(xdgDirectory(xdgConfigHome, join(homeDir, ".config"))),
|
|
155
|
-
dataDir: overmuxDirectory(xdgDirectory(xdgDataHome, join(homeDir, ".local", "share"))),
|
|
156
|
-
...runtimeDir ? { runtimeDir } : {},
|
|
157
|
-
stateDir: overmuxDirectory(xdgDirectory(xdgStateHome, join(homeDir, ".local", "state")))
|
|
158
|
-
};
|
|
159
|
-
};
|
|
160
|
-
//#endregion
|
|
21
|
+
import { Worker } from "node:worker_threads";
|
|
161
22
|
//#region ../lib/dist/index.js
|
|
162
23
|
const durationPattern = /^([1-9]\d*)(ms|s|m|h|d)$/;
|
|
163
24
|
const millisecondsByUnit = {
|
|
@@ -845,37 +706,6 @@ const createHttpAuthenticationHandlers = ({ auth, authAssetsDirectory }) => {
|
|
|
845
706
|
};
|
|
846
707
|
};
|
|
847
708
|
//#endregion
|
|
848
|
-
//#region ../shared/dist/index.js
|
|
849
|
-
const overmuxSettingsPath = "/_overmux/settings";
|
|
850
|
-
const overmuxLogoutPath = "/_overmux/logout";
|
|
851
|
-
const safeRelativeNotificationLink = (link) => {
|
|
852
|
-
let decoded;
|
|
853
|
-
try {
|
|
854
|
-
decoded = decodeURIComponent(link);
|
|
855
|
-
} catch {
|
|
856
|
-
return false;
|
|
857
|
-
}
|
|
858
|
-
return !decoded.startsWith("//") && !decoded.includes("\\") && ![...decoded].some((character) => {
|
|
859
|
-
const codePoint = character.codePointAt(0) ?? 0;
|
|
860
|
-
return codePoint < 32 || codePoint === 127;
|
|
861
|
-
});
|
|
862
|
-
};
|
|
863
|
-
const notificationLinkSchema = z.union([z.string().max(4096).startsWith("/").refine(safeRelativeNotificationLink), z.string().max(4096).url().refine((link) => {
|
|
864
|
-
if (!URL.canParse(link)) return false;
|
|
865
|
-
const url = new URL(link);
|
|
866
|
-
return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password;
|
|
867
|
-
})]);
|
|
868
|
-
const notificationSchema = z.object({
|
|
869
|
-
body: z.string().max(4096).optional(),
|
|
870
|
-
open: z.object({ link: notificationLinkSchema }).strict().optional(),
|
|
871
|
-
title: z.string().min(1).max(4096)
|
|
872
|
-
}).strict();
|
|
873
|
-
const instanceIdSchema = z.string().max(253).regex(/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?![\s\S])/, "Expected a lowercase instance ID with only ASCII letters, digits, internal dots and hyphens");
|
|
874
|
-
const createInstanceIdentity = (instanceId) => ({
|
|
875
|
-
instanceId: instanceIdSchema.parse(instanceId),
|
|
876
|
-
deepLinkPrefix: `overmux://${instanceId}`
|
|
877
|
-
});
|
|
878
|
-
//#endregion
|
|
879
709
|
//#region src/internal/server/auth/instance-control.ts
|
|
880
710
|
const maxControlMessageBytes = 16384;
|
|
881
711
|
const controlRequestSchema = z.discriminatedUnion("type", [
|
|
@@ -1098,214 +928,6 @@ const startInstanceControl = async ({ auth, apiUrl, instanceId, port, url }) =>
|
|
|
1098
928
|
return { close };
|
|
1099
929
|
};
|
|
1100
930
|
//#endregion
|
|
1101
|
-
//#region src/internal/shared/background-notifications.ts
|
|
1102
|
-
const deliveryKeySchema = z.string().min(1).max(1024).regex(/^[\w-]+$/u);
|
|
1103
|
-
const backgroundNotificationSubscriptionSchema = z.object({
|
|
1104
|
-
endpoint: z.url().max(4096).refine((endpoint) => {
|
|
1105
|
-
const url = new URL(endpoint);
|
|
1106
|
-
return url.protocol === "https:" && !url.username && !url.password;
|
|
1107
|
-
}),
|
|
1108
|
-
expirationTime: z.number().int().nonnegative().nullable(),
|
|
1109
|
-
keys: z.object({
|
|
1110
|
-
auth: deliveryKeySchema,
|
|
1111
|
-
p256dh: deliveryKeySchema
|
|
1112
|
-
}).strict()
|
|
1113
|
-
}).strict();
|
|
1114
|
-
z.object({ publicKey: deliveryKeySchema }).strict();
|
|
1115
|
-
const backgroundNotificationDisableRequestSchema = z.object({ endpoint: backgroundNotificationSubscriptionSchema.shape.endpoint }).strict();
|
|
1116
|
-
const webSocketCloseCode = {
|
|
1117
|
-
authenticationRevoked: 4001,
|
|
1118
|
-
authenticationUnavailable: 4002
|
|
1119
|
-
};
|
|
1120
|
-
z.object({
|
|
1121
|
-
debug: z.boolean(),
|
|
1122
|
-
operations: z.array(z.string().min(1)),
|
|
1123
|
-
protocolVersion: z.literal(10),
|
|
1124
|
-
resources: z.array(z.string().min(1)),
|
|
1125
|
-
streams: z.array(z.string().min(1))
|
|
1126
|
-
});
|
|
1127
|
-
const updateAvailableEventSchema = z.object({ type: z.literal("update-available") });
|
|
1128
|
-
const restartingEventSchema = z.object({ type: z.literal("restarting") });
|
|
1129
|
-
z.discriminatedUnion("type", [updateAvailableEventSchema, restartingEventSchema]);
|
|
1130
|
-
const notificationEventSchema = z.object({
|
|
1131
|
-
notification: notificationSchema,
|
|
1132
|
-
type: z.literal("notification")
|
|
1133
|
-
}).strict();
|
|
1134
|
-
const operationIdSchema = z.string().min(1).max(256);
|
|
1135
|
-
const registrationNameSchema = z.string().min(1).max(256);
|
|
1136
|
-
const resourceReadMessageSchema = z.object({
|
|
1137
|
-
input: z.unknown().optional(),
|
|
1138
|
-
operationId: operationIdSchema,
|
|
1139
|
-
resourceName: registrationNameSchema,
|
|
1140
|
-
type: z.literal("resource-read")
|
|
1141
|
-
});
|
|
1142
|
-
const resourceSubscribeMessageSchema = z.object({
|
|
1143
|
-
input: z.unknown().optional(),
|
|
1144
|
-
operationId: operationIdSchema,
|
|
1145
|
-
resourceName: registrationNameSchema,
|
|
1146
|
-
subscriptionId: operationIdSchema,
|
|
1147
|
-
type: z.literal("resource-subscribe")
|
|
1148
|
-
});
|
|
1149
|
-
const resourceUnsubscribeMessageSchema = z.object({
|
|
1150
|
-
subscriptionId: operationIdSchema,
|
|
1151
|
-
type: z.literal("resource-unsubscribe")
|
|
1152
|
-
});
|
|
1153
|
-
const streamOpenMessageSchema = z.object({
|
|
1154
|
-
input: z.unknown().optional(),
|
|
1155
|
-
operationId: operationIdSchema,
|
|
1156
|
-
streamId: operationIdSchema,
|
|
1157
|
-
streamName: registrationNameSchema,
|
|
1158
|
-
type: z.literal("stream-open")
|
|
1159
|
-
});
|
|
1160
|
-
const streamMessageSchema = z.object({
|
|
1161
|
-
message: z.unknown(),
|
|
1162
|
-
streamId: operationIdSchema,
|
|
1163
|
-
type: z.literal("stream-message")
|
|
1164
|
-
});
|
|
1165
|
-
const streamCloseMessageSchema = z.object({
|
|
1166
|
-
streamId: operationIdSchema,
|
|
1167
|
-
type: z.literal("stream-close")
|
|
1168
|
-
});
|
|
1169
|
-
const clientDiagnosticEventSchema = z.object({
|
|
1170
|
-
arguments: z.string().max(16100),
|
|
1171
|
-
level: z.enum([
|
|
1172
|
-
"log",
|
|
1173
|
-
"info",
|
|
1174
|
-
"warn",
|
|
1175
|
-
"error",
|
|
1176
|
-
"debug"
|
|
1177
|
-
]),
|
|
1178
|
-
message: z.string().max(4100),
|
|
1179
|
-
stack: z.string().max(8100).optional(),
|
|
1180
|
-
timestamp: z.iso.datetime(),
|
|
1181
|
-
type: z.literal("client-diagnostic"),
|
|
1182
|
-
url: z.string().max(2100).optional()
|
|
1183
|
-
});
|
|
1184
|
-
const clientProtocolMessageSchema = z.discriminatedUnion("type", [
|
|
1185
|
-
resourceReadMessageSchema,
|
|
1186
|
-
resourceSubscribeMessageSchema,
|
|
1187
|
-
resourceUnsubscribeMessageSchema,
|
|
1188
|
-
streamOpenMessageSchema,
|
|
1189
|
-
streamMessageSchema,
|
|
1190
|
-
streamCloseMessageSchema,
|
|
1191
|
-
clientDiagnosticEventSchema
|
|
1192
|
-
]);
|
|
1193
|
-
const resourceResultMessageSchema = z.object({
|
|
1194
|
-
operationId: operationIdSchema,
|
|
1195
|
-
output: z.unknown(),
|
|
1196
|
-
type: z.literal("resource-result")
|
|
1197
|
-
});
|
|
1198
|
-
const resourceInvalidatedMessageSchema = z.object({
|
|
1199
|
-
subscriptionId: operationIdSchema,
|
|
1200
|
-
type: z.literal("resource-invalidated")
|
|
1201
|
-
});
|
|
1202
|
-
const streamOpenedMessageSchema = z.object({
|
|
1203
|
-
operationId: operationIdSchema,
|
|
1204
|
-
streamId: operationIdSchema,
|
|
1205
|
-
type: z.literal("stream-opened")
|
|
1206
|
-
});
|
|
1207
|
-
const streamOutputMessageSchema = z.object({
|
|
1208
|
-
message: z.unknown(),
|
|
1209
|
-
streamId: operationIdSchema,
|
|
1210
|
-
type: z.literal("stream-output")
|
|
1211
|
-
});
|
|
1212
|
-
const streamClosedMessageSchema = z.object({
|
|
1213
|
-
streamId: operationIdSchema,
|
|
1214
|
-
type: z.literal("stream-closed")
|
|
1215
|
-
});
|
|
1216
|
-
const protocolErrorMessageSchema = z.object({
|
|
1217
|
-
code: z.enum([
|
|
1218
|
-
"bad-request",
|
|
1219
|
-
"conflict",
|
|
1220
|
-
"internal",
|
|
1221
|
-
"not-found"
|
|
1222
|
-
]),
|
|
1223
|
-
message: z.string(),
|
|
1224
|
-
operationId: operationIdSchema.optional(),
|
|
1225
|
-
streamId: operationIdSchema.optional(),
|
|
1226
|
-
subscriptionId: operationIdSchema.optional(),
|
|
1227
|
-
type: z.literal("error")
|
|
1228
|
-
});
|
|
1229
|
-
const serverInfoMessageSchema = z.object({
|
|
1230
|
-
type: z.literal("server-info"),
|
|
1231
|
-
instanceId: instanceIdSchema
|
|
1232
|
-
}).strict();
|
|
1233
|
-
const serverProtocolMessageSchema = z.discriminatedUnion("type", [
|
|
1234
|
-
serverInfoMessageSchema,
|
|
1235
|
-
resourceResultMessageSchema,
|
|
1236
|
-
resourceInvalidatedMessageSchema,
|
|
1237
|
-
streamOpenedMessageSchema,
|
|
1238
|
-
streamOutputMessageSchema,
|
|
1239
|
-
streamClosedMessageSchema,
|
|
1240
|
-
protocolErrorMessageSchema,
|
|
1241
|
-
updateAvailableEventSchema,
|
|
1242
|
-
restartingEventSchema,
|
|
1243
|
-
notificationEventSchema
|
|
1244
|
-
]);
|
|
1245
|
-
const byteMarker = "$overmuxBytes";
|
|
1246
|
-
const decodeValue = (value) => {
|
|
1247
|
-
if (Array.isArray(value)) return value.map(decodeValue);
|
|
1248
|
-
if (typeof value !== "object" || value === null) return value;
|
|
1249
|
-
const record = value;
|
|
1250
|
-
if (Object.keys(record).length === 1 && Array.isArray(record[byteMarker]) && record[byteMarker].every((item) => Number.isInteger(item) && item >= 0 && item <= 255)) return Uint8Array.from(record[byteMarker]);
|
|
1251
|
-
return Object.fromEntries(Object.entries(record).map(([key, item]) => [key, decodeValue(item)]));
|
|
1252
|
-
};
|
|
1253
|
-
const binaryMarker = "$overmuxBinary";
|
|
1254
|
-
const binaryMagic = 1330468865;
|
|
1255
|
-
const encodeBinaryValue = (value, payloads, offset) => {
|
|
1256
|
-
if (value instanceof Uint8Array) {
|
|
1257
|
-
const marker = { [binaryMarker]: [offset.value, value.byteLength] };
|
|
1258
|
-
payloads.push(value);
|
|
1259
|
-
offset.value += value.byteLength;
|
|
1260
|
-
return marker;
|
|
1261
|
-
}
|
|
1262
|
-
if (Array.isArray(value)) return value.map((item) => encodeBinaryValue(item, payloads, offset));
|
|
1263
|
-
if (typeof value === "object" && value !== null) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, encodeBinaryValue(item, payloads, offset)]));
|
|
1264
|
-
return value;
|
|
1265
|
-
};
|
|
1266
|
-
const decodeBinaryValue = (value, payload) => {
|
|
1267
|
-
if (Array.isArray(value)) return value.map((item) => decodeBinaryValue(item, payload));
|
|
1268
|
-
if (typeof value !== "object" || value === null) return value;
|
|
1269
|
-
const record = value;
|
|
1270
|
-
const marker = record[binaryMarker];
|
|
1271
|
-
if (Object.keys(record).length === 1 && Array.isArray(marker) && marker.length === 2 && marker.every((item) => Number.isInteger(item) && item >= 0)) {
|
|
1272
|
-
const [offset, length] = marker;
|
|
1273
|
-
if (offset + length > payload.byteLength) throw new Error("Invalid Overmux binary payload range");
|
|
1274
|
-
return payload.slice(offset, offset + length);
|
|
1275
|
-
}
|
|
1276
|
-
return Object.fromEntries(Object.entries(record).map(([key, item]) => [key, decodeBinaryValue(item, payload)]));
|
|
1277
|
-
};
|
|
1278
|
-
const decodeProtocolMessage = (message) => decodeValue(JSON.parse(message));
|
|
1279
|
-
const encodeProtocolFrame = (message) => {
|
|
1280
|
-
const payloads = [];
|
|
1281
|
-
const encoded = encodeBinaryValue(message, payloads, { value: 0 });
|
|
1282
|
-
if (!payloads.length) return JSON.stringify(encoded);
|
|
1283
|
-
const header = new TextEncoder().encode(JSON.stringify(encoded));
|
|
1284
|
-
const payloadLength = payloads.reduce((total, payload) => total + payload.byteLength, 0);
|
|
1285
|
-
const frame = new Uint8Array(8 + header.byteLength + payloadLength);
|
|
1286
|
-
const view = new DataView(frame.buffer);
|
|
1287
|
-
view.setUint32(0, binaryMagic);
|
|
1288
|
-
view.setUint32(4, header.byteLength);
|
|
1289
|
-
frame.set(header, 8);
|
|
1290
|
-
let offset = 8 + header.byteLength;
|
|
1291
|
-
payloads.forEach((payload) => {
|
|
1292
|
-
frame.set(payload, offset);
|
|
1293
|
-
offset += payload.byteLength;
|
|
1294
|
-
});
|
|
1295
|
-
return frame;
|
|
1296
|
-
};
|
|
1297
|
-
const decodeProtocolFrame = (frame) => {
|
|
1298
|
-
if (typeof frame === "string") return decodeProtocolMessage(frame);
|
|
1299
|
-
const bytes = frame instanceof Uint8Array ? frame : new Uint8Array(frame, 0, frame.byteLength);
|
|
1300
|
-
if (bytes.byteLength < 8) throw new Error("Invalid Overmux binary frame");
|
|
1301
|
-
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1302
|
-
if (view.getUint32(0) !== binaryMagic) throw new Error("Invalid Overmux binary frame magic");
|
|
1303
|
-
const headerLength = view.getUint32(4);
|
|
1304
|
-
if (8 + headerLength > bytes.byteLength) throw new Error("Invalid Overmux binary frame header");
|
|
1305
|
-
const header = JSON.parse(new TextDecoder().decode(bytes.subarray(8, 8 + headerLength)));
|
|
1306
|
-
return decodeBinaryValue(header, bytes.subarray(8 + headerLength));
|
|
1307
|
-
};
|
|
1308
|
-
//#endregion
|
|
1309
931
|
//#region src/internal/server/http/background-notification-handlers.ts
|
|
1310
932
|
const requestLimit = 16384;
|
|
1311
933
|
const parseJson = async (request, schema) => {
|
|
@@ -1339,61 +961,6 @@ const createBackgroundNotificationHandlers = ({ backgroundNotifications }) => ({
|
|
|
1339
961
|
}
|
|
1340
962
|
});
|
|
1341
963
|
//#endregion
|
|
1342
|
-
//#region src/internal/server/server-logger.ts
|
|
1343
|
-
const lifecycleEvents = /* @__PURE__ */ new Set([
|
|
1344
|
-
"server-start",
|
|
1345
|
-
"server-stop",
|
|
1346
|
-
"websocket-close",
|
|
1347
|
-
"websocket-close-event",
|
|
1348
|
-
"websocket-connect",
|
|
1349
|
-
"websocket-open"
|
|
1350
|
-
]);
|
|
1351
|
-
const getServerLogFile = ({ homeDir, xdgStateHome } = {}) => join(getOvermuxPaths({
|
|
1352
|
-
homeDir,
|
|
1353
|
-
xdgStateHome
|
|
1354
|
-
}).stateDir, "overmux.log");
|
|
1355
|
-
const safeJson = (value) => {
|
|
1356
|
-
try {
|
|
1357
|
-
return JSON.stringify(value);
|
|
1358
|
-
} catch {
|
|
1359
|
-
return JSON.stringify({
|
|
1360
|
-
event: "log-serialization-error",
|
|
1361
|
-
level: "error",
|
|
1362
|
-
source: "server",
|
|
1363
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1364
|
-
});
|
|
1365
|
-
}
|
|
1366
|
-
};
|
|
1367
|
-
const createServerLogger = ({ enabled, logFile }) => {
|
|
1368
|
-
let debugEnabled = enabled;
|
|
1369
|
-
return {
|
|
1370
|
-
get enabled() {
|
|
1371
|
-
return debugEnabled;
|
|
1372
|
-
},
|
|
1373
|
-
id: randomUUID,
|
|
1374
|
-
log: (entry) => {
|
|
1375
|
-
if (!debugEnabled && entry.level !== "error" && !lifecycleEvents.has(entry.event)) return;
|
|
1376
|
-
const record = safeJson({
|
|
1377
|
-
timestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1378
|
-
level: entry.level ?? "info",
|
|
1379
|
-
source: entry.source ?? "server",
|
|
1380
|
-
...entry
|
|
1381
|
-
});
|
|
1382
|
-
mkdirSync(dirname(logFile), { recursive: true });
|
|
1383
|
-
appendFileSync(logFile, `${record}\n`, "utf8");
|
|
1384
|
-
process.stderr.write(`[overmux:debug] ${record}\n`);
|
|
1385
|
-
},
|
|
1386
|
-
setEnabled: (nextEnabled) => {
|
|
1387
|
-
debugEnabled = nextEnabled;
|
|
1388
|
-
}
|
|
1389
|
-
};
|
|
1390
|
-
};
|
|
1391
|
-
const errorMessage = (cause) => cause instanceof Error ? cause.message : String(cause);
|
|
1392
|
-
const errorDetails$1 = (cause) => ({
|
|
1393
|
-
message: errorMessage(cause),
|
|
1394
|
-
...cause instanceof Error && cause.stack ? { stack: cause.stack } : {}
|
|
1395
|
-
});
|
|
1396
|
-
//#endregion
|
|
1397
964
|
//#region src/internal/server/http/development-web-backend.ts
|
|
1398
965
|
const createForwardingHeaders = ({ origin, remoteAddress, request }) => {
|
|
1399
966
|
const headers = new Headers(request.headers);
|
|
@@ -1449,42 +1016,6 @@ const createRestartHandler = ({ onRestartRequested, shutdown }) => (context) =>
|
|
|
1449
1016
|
return response;
|
|
1450
1017
|
};
|
|
1451
1018
|
//#endregion
|
|
1452
|
-
//#region src/internal/server/runtime/runtime-operations.ts
|
|
1453
|
-
var OperationValidationError = class extends Error {
|
|
1454
|
-
phase;
|
|
1455
|
-
constructor(phase, cause) {
|
|
1456
|
-
super(cause.message, { cause });
|
|
1457
|
-
this.phase = phase;
|
|
1458
|
-
}
|
|
1459
|
-
};
|
|
1460
|
-
const createRuntimeOperations = ({ context, definitions, notifications }) => {
|
|
1461
|
-
const operations = /* @__PURE__ */ new Map();
|
|
1462
|
-
Object.entries(definitions).forEach(([name, definition]) => {
|
|
1463
|
-
if (!name) throw new Error("Operation names must not be empty");
|
|
1464
|
-
operations.set(name, {
|
|
1465
|
-
execute: async (rawInput, signal) => {
|
|
1466
|
-
const operationContext = context(signal);
|
|
1467
|
-
operationContext.signal.throwIfAborted();
|
|
1468
|
-
const input = definition.input.safeParse(rawInput);
|
|
1469
|
-
if (!input.success) throw new OperationValidationError("input", input.error);
|
|
1470
|
-
const rawOutput = await definition.handle(input.data, {
|
|
1471
|
-
...operationContext,
|
|
1472
|
-
notifications
|
|
1473
|
-
});
|
|
1474
|
-
operationContext.signal.throwIfAborted();
|
|
1475
|
-
const output = definition.output.safeParse(rawOutput);
|
|
1476
|
-
if (!output.success) throw new OperationValidationError("output", output.error);
|
|
1477
|
-
return output.data;
|
|
1478
|
-
},
|
|
1479
|
-
returnsVoid: definition.output instanceof z.ZodVoid
|
|
1480
|
-
});
|
|
1481
|
-
});
|
|
1482
|
-
return {
|
|
1483
|
-
get: (name) => operations.get(name),
|
|
1484
|
-
names: [...operations.keys()]
|
|
1485
|
-
};
|
|
1486
|
-
};
|
|
1487
|
-
//#endregion
|
|
1488
1019
|
//#region src/internal/server/http/operation-handler.ts
|
|
1489
1020
|
const maxOperationRequestBytes = 1048576;
|
|
1490
1021
|
const parseOperationRequest = async (request) => {
|
|
@@ -1521,15 +1052,17 @@ const createOperationHandler = ({ runtime, serverLogger }) => async (context) =>
|
|
|
1521
1052
|
serverLogger?.log({
|
|
1522
1053
|
correlationId,
|
|
1523
1054
|
details: { registeredName: context.req.param("name") },
|
|
1524
|
-
event: "operation-start"
|
|
1055
|
+
event: "operation-start",
|
|
1056
|
+
level: "debug"
|
|
1525
1057
|
});
|
|
1526
1058
|
try {
|
|
1527
|
-
const output = await operation.execute(parsed.input, context.req.raw.signal);
|
|
1059
|
+
const output = await operation.execute(parsed.input, context.req.raw.signal, { correlationId });
|
|
1528
1060
|
serverLogger?.log({
|
|
1529
1061
|
correlationId,
|
|
1530
1062
|
details: { registeredName: context.req.param("name") },
|
|
1531
1063
|
durationMs: Date.now() - startedAt,
|
|
1532
|
-
event: "operation-complete"
|
|
1064
|
+
event: "operation-complete",
|
|
1065
|
+
level: "debug"
|
|
1533
1066
|
});
|
|
1534
1067
|
return operation.returnsVoid ? new Response(void 0, { status: 204 }) : context.json(output);
|
|
1535
1068
|
} catch (cause) {
|
|
@@ -1562,7 +1095,7 @@ const createRequestLoggingMiddleware = ({ serverLogger }) => async (context, nex
|
|
|
1562
1095
|
},
|
|
1563
1096
|
durationMs: Date.now() - startedAt,
|
|
1564
1097
|
event: failed ? "http-request-failure" : "http-request",
|
|
1565
|
-
level: failed ? "error" :
|
|
1098
|
+
level: failed ? "error" : "debug",
|
|
1566
1099
|
message: context.error?.message
|
|
1567
1100
|
});
|
|
1568
1101
|
};
|
|
@@ -1770,13 +1303,6 @@ const createHttpApp = ({ auth, authAssetsDirectory, backgroundNotifications, dev
|
|
|
1770
1303
|
//#endregion
|
|
1771
1304
|
//#region src/internal/server/websocket-connection.ts
|
|
1772
1305
|
const maxPendingStreamOutputBytes = 1048576;
|
|
1773
|
-
var OperationError = class extends Error {
|
|
1774
|
-
code;
|
|
1775
|
-
constructor(code, message) {
|
|
1776
|
-
super(message);
|
|
1777
|
-
this.code = code;
|
|
1778
|
-
}
|
|
1779
|
-
};
|
|
1780
1306
|
const bytesOf = (data) => {
|
|
1781
1307
|
if (Array.isArray(data)) return Buffer.concat(data);
|
|
1782
1308
|
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
@@ -1793,7 +1319,7 @@ const operationIdOf = (value) => {
|
|
|
1793
1319
|
return typeof operationId === "string" && operationId.length <= 256 ? operationId : void 0;
|
|
1794
1320
|
};
|
|
1795
1321
|
const errorDetails = (cause) => {
|
|
1796
|
-
if (cause instanceof
|
|
1322
|
+
if (cause instanceof ProtocolError) return {
|
|
1797
1323
|
code: cause.code,
|
|
1798
1324
|
message: cause.message
|
|
1799
1325
|
};
|
|
@@ -1833,6 +1359,14 @@ const sendError = (state, cause, operationId, identity) => {
|
|
|
1833
1359
|
type: "error"
|
|
1834
1360
|
});
|
|
1835
1361
|
};
|
|
1362
|
+
const reportCleanupFailure = (state, cause) => {
|
|
1363
|
+
state.serverLogger?.log({
|
|
1364
|
+
correlationId: state.connectionId,
|
|
1365
|
+
details: errorDetails$1(cause),
|
|
1366
|
+
event: "protocol-cleanup-error",
|
|
1367
|
+
level: "error"
|
|
1368
|
+
});
|
|
1369
|
+
};
|
|
1836
1370
|
const disposeSubscription = async (state, subscriptionId) => {
|
|
1837
1371
|
const entry = state.subscriptions.get(subscriptionId);
|
|
1838
1372
|
if (!entry) return;
|
|
@@ -1851,11 +1385,11 @@ const disposeStream = async (state, streamId, notify = true) => {
|
|
|
1851
1385
|
}
|
|
1852
1386
|
state.streams.delete(streamId);
|
|
1853
1387
|
entry.controller.abort(/* @__PURE__ */ new Error("Stream closed"));
|
|
1854
|
-
await entry.session?.dispose();
|
|
1855
1388
|
if (notify) send$1(state, {
|
|
1856
1389
|
streamId,
|
|
1857
1390
|
type: "stream-closed"
|
|
1858
1391
|
});
|
|
1392
|
+
await entry.session?.dispose();
|
|
1859
1393
|
};
|
|
1860
1394
|
const disposeConnectionOperations = async (state, notify) => {
|
|
1861
1395
|
const operationControllers = [...state.operations.values()];
|
|
@@ -1867,7 +1401,7 @@ const disposeConnectionOperations = async (state, notify) => {
|
|
|
1867
1401
|
};
|
|
1868
1402
|
const runOperation = (state, identity, operation) => {
|
|
1869
1403
|
if (state.operations.has(identity.operationId)) {
|
|
1870
|
-
sendError(state, new
|
|
1404
|
+
sendError(state, new ProtocolError("conflict", "Operation ID is already active"), identity.operationId, identity);
|
|
1871
1405
|
return;
|
|
1872
1406
|
}
|
|
1873
1407
|
const controller = new AbortController();
|
|
@@ -1876,14 +1410,16 @@ const runOperation = (state, identity, operation) => {
|
|
|
1876
1410
|
state.serverLogger?.log({
|
|
1877
1411
|
correlationId: identity.operationId,
|
|
1878
1412
|
details: identity,
|
|
1879
|
-
event: "operation-start"
|
|
1413
|
+
event: "operation-start",
|
|
1414
|
+
level: "debug"
|
|
1880
1415
|
});
|
|
1881
1416
|
operation(controller.signal).then(() => {
|
|
1882
1417
|
state.serverLogger?.log({
|
|
1883
1418
|
correlationId: identity.operationId,
|
|
1884
1419
|
details: identity,
|
|
1885
1420
|
durationMs: Date.now() - startedAt,
|
|
1886
|
-
event: "operation-complete"
|
|
1421
|
+
event: "operation-complete",
|
|
1422
|
+
level: "debug"
|
|
1887
1423
|
});
|
|
1888
1424
|
}).catch((cause) => {
|
|
1889
1425
|
if (!controller.signal.aborted && !state.closed) {
|
|
@@ -1911,8 +1447,11 @@ const readResource = (state, message) => {
|
|
|
1911
1447
|
requestedName: message.resourceName
|
|
1912
1448
|
};
|
|
1913
1449
|
runOperation(state, identity, async (signal) => {
|
|
1914
|
-
if (!resource) throw new
|
|
1915
|
-
const output = await resource.read(message.input, signal
|
|
1450
|
+
if (!resource) throw new ProtocolError("not-found", "Resource not found");
|
|
1451
|
+
const output = await resource.read(message.input, signal, {
|
|
1452
|
+
correlationId: message.operationId,
|
|
1453
|
+
connectionId: state.connectionId
|
|
1454
|
+
});
|
|
1916
1455
|
send$1(state, {
|
|
1917
1456
|
operationId: message.operationId,
|
|
1918
1457
|
output,
|
|
@@ -1928,20 +1467,31 @@ const subscribeResource = (state, message) => {
|
|
|
1928
1467
|
requestedName: message.resourceName
|
|
1929
1468
|
};
|
|
1930
1469
|
if (state.subscriptions.has(message.subscriptionId)) {
|
|
1931
|
-
sendError(state, new
|
|
1470
|
+
sendError(state, new ProtocolError("conflict", "Subscription ID is already active"), message.operationId, identity);
|
|
1932
1471
|
return;
|
|
1933
1472
|
}
|
|
1934
1473
|
runOperation(state, identity, async (operationSignal) => {
|
|
1935
|
-
if (!resource) throw new
|
|
1474
|
+
if (!resource) throw new ProtocolError("not-found", "Resource not found");
|
|
1936
1475
|
const controller = new AbortController();
|
|
1937
1476
|
const entry = { controller };
|
|
1938
1477
|
state.subscriptions.set(message.subscriptionId, entry);
|
|
1939
1478
|
const signal = AbortSignal.any([operationSignal, controller.signal]);
|
|
1940
1479
|
try {
|
|
1941
|
-
const dispose = await resource.subscribe(message.input, () =>
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1480
|
+
const dispose = await resource.subscribe(message.input, () => {
|
|
1481
|
+
if (signal.aborted || state.subscriptions.get(message.subscriptionId) !== entry) return;
|
|
1482
|
+
send$1(state, {
|
|
1483
|
+
subscriptionId: message.subscriptionId,
|
|
1484
|
+
type: "resource-invalidated"
|
|
1485
|
+
});
|
|
1486
|
+
}, signal, (cause) => {
|
|
1487
|
+
if (signal.aborted || state.subscriptions.get(message.subscriptionId) !== entry) return;
|
|
1488
|
+
sendError(state, cause, message.subscriptionId, identity);
|
|
1489
|
+
disposeSubscription(state, message.subscriptionId).catch((disposeCause) => sendError(state, disposeCause, message.subscriptionId, identity));
|
|
1490
|
+
}, {
|
|
1491
|
+
correlationId: message.operationId,
|
|
1492
|
+
connectionId: state.connectionId,
|
|
1493
|
+
subscriptionId: message.subscriptionId
|
|
1494
|
+
});
|
|
1945
1495
|
if (state.subscriptions.get(message.subscriptionId) === entry) entry.dispose = dispose;
|
|
1946
1496
|
else await dispose();
|
|
1947
1497
|
} catch (cause) {
|
|
@@ -1970,11 +1520,11 @@ const openStream = (state, message) => {
|
|
|
1970
1520
|
requestedName: message.streamName
|
|
1971
1521
|
};
|
|
1972
1522
|
if (state.streams.has(message.streamId)) {
|
|
1973
|
-
sendError(state, new
|
|
1523
|
+
sendError(state, new ProtocolError("conflict", "Stream ID is already active"), message.operationId, identity);
|
|
1974
1524
|
return;
|
|
1975
1525
|
}
|
|
1976
1526
|
runOperation(state, identity, async (operationSignal) => {
|
|
1977
|
-
if (!stream) throw new
|
|
1527
|
+
if (!stream) throw new ProtocolError("not-found", "Stream not found");
|
|
1978
1528
|
const controller = new AbortController();
|
|
1979
1529
|
const entry = {
|
|
1980
1530
|
controller,
|
|
@@ -1984,8 +1534,10 @@ const openStream = (state, message) => {
|
|
|
1984
1534
|
const pending = [];
|
|
1985
1535
|
let pendingBytes = 0;
|
|
1986
1536
|
let opened = false;
|
|
1537
|
+
const signal = AbortSignal.any([operationSignal, controller.signal]);
|
|
1987
1538
|
try {
|
|
1988
|
-
|
|
1539
|
+
const session = await stream.open(message.input, (output) => {
|
|
1540
|
+
if (signal.aborted || state.streams.get(message.streamId) !== entry) return;
|
|
1989
1541
|
if (opened) {
|
|
1990
1542
|
if (!send$1(state, {
|
|
1991
1543
|
message: output,
|
|
@@ -1997,12 +1549,22 @@ const openStream = (state, message) => {
|
|
|
1997
1549
|
pendingBytes += frameLength(output);
|
|
1998
1550
|
if (pendingBytes > maxPendingStreamOutputBytes) throw new Error("Stream produced too much output while opening");
|
|
1999
1551
|
pending.push(output);
|
|
2000
|
-
},
|
|
1552
|
+
}, signal, (cause) => {
|
|
1553
|
+
if (signal.aborted || state.streams.get(message.streamId) !== entry) return;
|
|
2001
1554
|
sendError(state, cause, message.streamId, identity);
|
|
2002
1555
|
disposeStream(state, message.streamId).catch((disposeCause) => {
|
|
2003
|
-
|
|
1556
|
+
reportCleanupFailure(state, disposeCause);
|
|
2004
1557
|
});
|
|
1558
|
+
}, {
|
|
1559
|
+
correlationId: message.operationId,
|
|
1560
|
+
connectionId: state.connectionId,
|
|
1561
|
+
streamId: message.streamId
|
|
2005
1562
|
});
|
|
1563
|
+
if (signal.aborted || state.streams.get(message.streamId) !== entry) {
|
|
1564
|
+
await session.dispose();
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
entry.session = session;
|
|
2006
1568
|
send$1(state, {
|
|
2007
1569
|
operationId: message.operationId,
|
|
2008
1570
|
streamId: message.streamId,
|
|
@@ -2015,6 +1577,7 @@ const openStream = (state, message) => {
|
|
|
2015
1577
|
type: "stream-output"
|
|
2016
1578
|
})) throw new Error("Stream output could not be delivered");
|
|
2017
1579
|
} catch (cause) {
|
|
1580
|
+
if (signal.aborted || state.streams.get(message.streamId) !== entry) return;
|
|
2018
1581
|
await disposeStream(state, message.streamId, false);
|
|
2019
1582
|
throw cause;
|
|
2020
1583
|
}
|
|
@@ -2028,18 +1591,19 @@ const sendStreamMessage = (state, message) => {
|
|
|
2028
1591
|
operationType: message.type
|
|
2029
1592
|
};
|
|
2030
1593
|
if (!entry?.session) {
|
|
2031
|
-
sendError(state, new
|
|
1594
|
+
sendError(state, new ProtocolError("not-found", "Stream not found"), message.streamId, identity);
|
|
2032
1595
|
return;
|
|
2033
1596
|
}
|
|
2034
1597
|
entry.session.send(message.message).catch((cause) => {
|
|
1598
|
+
if (entry.controller.signal.aborted || state.streams.get(message.streamId) !== entry) return;
|
|
2035
1599
|
sendError(state, cause, message.streamId, identity);
|
|
2036
1600
|
disposeStream(state, message.streamId).catch((disposeCause) => {
|
|
2037
|
-
|
|
1601
|
+
reportCleanupFailure(state, disposeCause);
|
|
2038
1602
|
});
|
|
2039
1603
|
});
|
|
2040
1604
|
};
|
|
2041
1605
|
const logClientDiagnostic = (state, message) => {
|
|
2042
|
-
if (
|
|
1606
|
+
if (state.serverLogger?.logLevel !== "debug") return;
|
|
2043
1607
|
state.serverLogger.log({
|
|
2044
1608
|
details: {
|
|
2045
1609
|
arguments: message.arguments,
|
|
@@ -2068,7 +1632,7 @@ const handleMessage = (state, message) => {
|
|
|
2068
1632
|
}
|
|
2069
1633
|
if (message.type === "resource-unsubscribe") {
|
|
2070
1634
|
disposeSubscription(state, message.subscriptionId).catch((cause) => {
|
|
2071
|
-
|
|
1635
|
+
reportCleanupFailure(state, cause);
|
|
2072
1636
|
});
|
|
2073
1637
|
return;
|
|
2074
1638
|
}
|
|
@@ -2078,7 +1642,7 @@ const handleMessage = (state, message) => {
|
|
|
2078
1642
|
}
|
|
2079
1643
|
if (message.type === "stream-close") {
|
|
2080
1644
|
disposeStream(state, message.streamId).catch((cause) => {
|
|
2081
|
-
|
|
1645
|
+
reportCleanupFailure(state, cause);
|
|
2082
1646
|
});
|
|
2083
1647
|
return;
|
|
2084
1648
|
}
|
|
@@ -2115,7 +1679,8 @@ const disposeConnection = async (state) => {
|
|
|
2115
1679
|
state.closed = true;
|
|
2116
1680
|
state.serverLogger?.log({
|
|
2117
1681
|
correlationId: state.connectionId,
|
|
2118
|
-
event: "websocket-close"
|
|
1682
|
+
event: "websocket-close",
|
|
1683
|
+
level: "debug"
|
|
2119
1684
|
});
|
|
2120
1685
|
await disposeConnectionOperations(state, false);
|
|
2121
1686
|
};
|
|
@@ -2137,7 +1702,8 @@ const attachWebSocketConnection = ({ runtime, serverLogger, socket }) => {
|
|
|
2137
1702
|
});
|
|
2138
1703
|
serverLogger?.log({
|
|
2139
1704
|
correlationId: state.connectionId,
|
|
2140
|
-
event: "websocket-open"
|
|
1705
|
+
event: "websocket-open",
|
|
1706
|
+
level: "debug"
|
|
2141
1707
|
});
|
|
2142
1708
|
socket.on("message", (data, isBinary) => handleFrame(state, data, isBinary));
|
|
2143
1709
|
socket.once("close", (code, reason) => {
|
|
@@ -2147,7 +1713,8 @@ const attachWebSocketConnection = ({ runtime, serverLogger, socket }) => {
|
|
|
2147
1713
|
code,
|
|
2148
1714
|
reason: reason?.toString() ?? ""
|
|
2149
1715
|
},
|
|
2150
|
-
event: "websocket-close-event"
|
|
1716
|
+
event: "websocket-close-event",
|
|
1717
|
+
level: "debug"
|
|
2151
1718
|
});
|
|
2152
1719
|
disposeConnection(state);
|
|
2153
1720
|
});
|
|
@@ -2336,7 +1903,8 @@ const createWebSocketServer = ({ auth, developmentWebTarget, runtime, server, se
|
|
|
2336
1903
|
const correlationId = serverLogger?.id();
|
|
2337
1904
|
serverLogger?.log({
|
|
2338
1905
|
correlationId,
|
|
2339
|
-
event: "websocket-connect"
|
|
1906
|
+
event: "websocket-connect",
|
|
1907
|
+
level: "debug"
|
|
2340
1908
|
});
|
|
2341
1909
|
const path = requestPath(request);
|
|
2342
1910
|
const isRuntimeSocket = path === runtimeWebSocketPath;
|
|
@@ -2639,15 +2207,6 @@ const createFileBackgroundNotificationStore = ({ environment = process.env, home
|
|
|
2639
2207
|
}
|
|
2640
2208
|
};
|
|
2641
2209
|
};
|
|
2642
|
-
z.void();
|
|
2643
|
-
const jsonValueSchema = z.lazy(() => z.union([
|
|
2644
|
-
z.string(),
|
|
2645
|
-
z.number().finite(),
|
|
2646
|
-
z.boolean(),
|
|
2647
|
-
z.null(),
|
|
2648
|
-
z.array(jsonValueSchema),
|
|
2649
|
-
z.record(z.string(), jsonValueSchema)
|
|
2650
|
-
]));
|
|
2651
2210
|
//#endregion
|
|
2652
2211
|
//#region src/internal/server/notifications/notification-service.ts
|
|
2653
2212
|
const deliverSafely = async ({ deliver, event, logger }) => {
|
|
@@ -2685,389 +2244,537 @@ const createNotificationService = ({ backgroundNotifications, logger }) => {
|
|
|
2685
2244
|
};
|
|
2686
2245
|
};
|
|
2687
2246
|
//#endregion
|
|
2688
|
-
//#region src/internal/server/runtime/
|
|
2689
|
-
const
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2247
|
+
//#region src/internal/server/runtime/worker-connection.ts
|
|
2248
|
+
const deliverNotification = async (message, notifications, postControl) => {
|
|
2249
|
+
await Promise.resolve();
|
|
2250
|
+
try {
|
|
2251
|
+
await notifications.send(message.notification);
|
|
2252
|
+
postControl({
|
|
2253
|
+
type: "notification-result",
|
|
2254
|
+
id: message.id,
|
|
2255
|
+
ok: true
|
|
2256
|
+
});
|
|
2257
|
+
} catch (cause) {
|
|
2258
|
+
postControl({
|
|
2259
|
+
type: "notification-result",
|
|
2260
|
+
id: message.id,
|
|
2261
|
+
ok: false,
|
|
2262
|
+
error: String(cause).slice(0, 8192)
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
};
|
|
2266
|
+
let sequence = 0;
|
|
2267
|
+
const nextId = () => ++sequence;
|
|
2268
|
+
const asError = (cause) => cause instanceof Error ? cause : new Error(String(cause));
|
|
2269
|
+
const contain = (callback) => {
|
|
2270
|
+
try {
|
|
2271
|
+
callback();
|
|
2272
|
+
} catch {}
|
|
2273
|
+
};
|
|
2274
|
+
const settleCaller = (pending, result) => {
|
|
2275
|
+
if (pending.caller.status === "settled") return;
|
|
2276
|
+
const { settle } = pending.caller;
|
|
2277
|
+
pending.caller = { status: "settled" };
|
|
2278
|
+
settle(result);
|
|
2279
|
+
};
|
|
2280
|
+
const ownSession = ({ id, options, postClose, release }) => {
|
|
2281
|
+
let state = {
|
|
2282
|
+
status: "opening",
|
|
2283
|
+
admission: "local"
|
|
2284
|
+
};
|
|
2285
|
+
const detach = () => options.signal?.removeEventListener("abort", abort);
|
|
2286
|
+
const close = (failure) => {
|
|
2287
|
+
if (state.status === "closed" || state.status === "closing") return state.completion;
|
|
2288
|
+
detach();
|
|
2289
|
+
if (state.status === "opening" && state.admission === "local") {
|
|
2290
|
+
state = {
|
|
2291
|
+
status: "closed",
|
|
2292
|
+
completion: Promise.resolve(),
|
|
2293
|
+
failure
|
|
2294
|
+
};
|
|
2295
|
+
release();
|
|
2296
|
+
return state.completion;
|
|
2297
|
+
}
|
|
2298
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
2299
|
+
state = {
|
|
2300
|
+
status: "closing",
|
|
2301
|
+
completion: promise,
|
|
2302
|
+
resolve,
|
|
2303
|
+
reject,
|
|
2304
|
+
failure
|
|
2305
|
+
};
|
|
2306
|
+
promise.catch(() => void 0);
|
|
2307
|
+
postClose();
|
|
2308
|
+
return promise;
|
|
2309
|
+
};
|
|
2310
|
+
const abort = () => {
|
|
2311
|
+
close();
|
|
2312
|
+
};
|
|
2313
|
+
const fail = (cause) => {
|
|
2314
|
+
if (state.status === "closing" || state.status === "closed") return;
|
|
2315
|
+
close(cause);
|
|
2316
|
+
contain(() => options.onError?.(cause));
|
|
2317
|
+
};
|
|
2318
|
+
const finishClose = (cause) => {
|
|
2319
|
+
if (state.status !== "closing") return;
|
|
2320
|
+
const closing = state;
|
|
2321
|
+
state = {
|
|
2322
|
+
status: "closed",
|
|
2323
|
+
completion: closing.completion,
|
|
2324
|
+
failure: closing.failure ?? cause
|
|
2325
|
+
};
|
|
2326
|
+
release();
|
|
2327
|
+
cause ? closing.reject(cause) : closing.resolve();
|
|
2695
2328
|
};
|
|
2329
|
+
const assertOpen = () => {
|
|
2330
|
+
if (state.status === "opening") throw new Error("Session is still opening");
|
|
2331
|
+
if (state.status === "closing" || state.status === "closed") throw state.failure ?? /* @__PURE__ */ new Error(options.action === "stream-open" ? "Stream is closed" : "Subscription is closed");
|
|
2332
|
+
};
|
|
2333
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
2696
2334
|
return {
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2335
|
+
id,
|
|
2336
|
+
close: () => close(),
|
|
2337
|
+
assertOpen,
|
|
2338
|
+
fail,
|
|
2339
|
+
finishClose,
|
|
2340
|
+
getFailure: () => state.status === "closing" || state.status === "closed" ? state.failure : void 0,
|
|
2341
|
+
markSent: () => {
|
|
2342
|
+
if (state.status === "opening") state = {
|
|
2343
|
+
status: "opening",
|
|
2344
|
+
admission: "remote"
|
|
2345
|
+
};
|
|
2700
2346
|
},
|
|
2701
|
-
|
|
2702
|
-
if (
|
|
2347
|
+
opened: () => {
|
|
2348
|
+
if (state.status === "opening") state = { status: "open" };
|
|
2349
|
+
assertOpen();
|
|
2350
|
+
},
|
|
2351
|
+
deliver: (value) => {
|
|
2352
|
+
if (state.status !== "opening" && state.status !== "open") return;
|
|
2703
2353
|
try {
|
|
2704
|
-
|
|
2354
|
+
options.onEvent(value);
|
|
2705
2355
|
} catch (cause) {
|
|
2706
|
-
|
|
2356
|
+
fail(asError(cause));
|
|
2357
|
+
}
|
|
2358
|
+
},
|
|
2359
|
+
terminate: (cause) => {
|
|
2360
|
+
if (state.status === "closed") return;
|
|
2361
|
+
if (state.status === "closing") {
|
|
2362
|
+
finishClose(cause);
|
|
2363
|
+
return;
|
|
2707
2364
|
}
|
|
2708
|
-
|
|
2365
|
+
detach();
|
|
2366
|
+
state = {
|
|
2367
|
+
status: "closed",
|
|
2368
|
+
completion: Promise.resolve(),
|
|
2369
|
+
failure: cause
|
|
2370
|
+
};
|
|
2371
|
+
release();
|
|
2372
|
+
contain(() => options.onError?.(cause));
|
|
2709
2373
|
}
|
|
2710
2374
|
};
|
|
2711
2375
|
};
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
const
|
|
2715
|
-
const
|
|
2716
|
-
const
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
if (pending) return pending;
|
|
2734
|
-
controller.signal.removeEventListener("abort", onAbort);
|
|
2735
|
-
signal?.removeEventListener("abort", onAbort);
|
|
2736
|
-
activeDisposers.delete(disposeOnce);
|
|
2737
|
-
pending = Promise.resolve().then(dispose);
|
|
2738
|
-
return pending;
|
|
2376
|
+
const createWorkerConnection = (options) => {
|
|
2377
|
+
const worker = new Worker(new URL(import.meta.resolve("#worker-entry")));
|
|
2378
|
+
const startup = Promise.withResolvers();
|
|
2379
|
+
const pending = /* @__PURE__ */ new Map();
|
|
2380
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
2381
|
+
let pendingBytes = 0;
|
|
2382
|
+
let state = {
|
|
2383
|
+
status: "starting",
|
|
2384
|
+
timer: setTimeout(() => {
|
|
2385
|
+
terminate(/* @__PURE__ */ new Error(`Worker ${options.name} startup deadline exceeded`));
|
|
2386
|
+
}, options.startupMs)
|
|
2387
|
+
};
|
|
2388
|
+
const terminate = (cause) => {
|
|
2389
|
+
if (state.status === "stopped") return state.completion;
|
|
2390
|
+
const previous = state;
|
|
2391
|
+
if (previous.status === "starting" || previous.status === "stopping") clearTimeout(previous.timer);
|
|
2392
|
+
const completion = worker.terminate().then(() => void 0);
|
|
2393
|
+
state = {
|
|
2394
|
+
status: "stopped",
|
|
2395
|
+
cause,
|
|
2396
|
+
completion
|
|
2739
2397
|
};
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
if (disposal) return disposal;
|
|
2751
|
-
completePreparation();
|
|
2752
|
-
const pending = [...activeDisposers];
|
|
2753
|
-
activeDisposers.clear();
|
|
2754
|
-
controller.abort(reason);
|
|
2755
|
-
disposal = disposeAll(pending);
|
|
2756
|
-
return disposal;
|
|
2398
|
+
if (previous.status === "starting") startup.reject(cause);
|
|
2399
|
+
if (previous.status === "stopping") completion.then(previous.finish, previous.finish);
|
|
2400
|
+
pending.forEach((request) => settleCaller(request, {
|
|
2401
|
+
ok: false,
|
|
2402
|
+
error: cause
|
|
2403
|
+
}));
|
|
2404
|
+
pending.clear();
|
|
2405
|
+
pendingBytes = 0;
|
|
2406
|
+
sessions.forEach((session) => session.terminate(cause));
|
|
2407
|
+
return completion;
|
|
2757
2408
|
};
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2409
|
+
const postControl = (message) => {
|
|
2410
|
+
if (state.status === "stopped") return;
|
|
2411
|
+
try {
|
|
2412
|
+
postWorkerMessage(worker, encodeWorkerMessage(message));
|
|
2413
|
+
} catch (cause) {
|
|
2414
|
+
terminate(asError(cause));
|
|
2415
|
+
}
|
|
2765
2416
|
};
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
const
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
path: [...path, resourceId],
|
|
2778
|
-
resourceId: dependencyId,
|
|
2779
|
-
visited,
|
|
2780
|
-
visiting
|
|
2417
|
+
const requireReady = () => {
|
|
2418
|
+
if (state.status === "stopping" || state.status === "stopped") throw state.cause;
|
|
2419
|
+
if (state.status !== "ready") throw new Error(`Worker ${options.name} is not ready`);
|
|
2420
|
+
};
|
|
2421
|
+
const request = async (message, signal, onSent) => {
|
|
2422
|
+
signal?.throwIfAborted();
|
|
2423
|
+
requireReady();
|
|
2424
|
+
const id = nextId();
|
|
2425
|
+
const packet = encodeWorkerMessage({
|
|
2426
|
+
...message,
|
|
2427
|
+
id
|
|
2781
2428
|
});
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
const
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2429
|
+
signal?.throwIfAborted();
|
|
2430
|
+
const bytes = packet.byteLength;
|
|
2431
|
+
if (pending.size >= workerLimits.pendingRequests || pendingBytes + bytes > workerLimits.pendingBytes) throw new Error(`Worker ${options.name} request queue is full or payload exceeds 1 MiB`);
|
|
2432
|
+
return new Promise((resolve, reject) => {
|
|
2433
|
+
const abort = () => {
|
|
2434
|
+
settleCaller(owned, {
|
|
2435
|
+
ok: false,
|
|
2436
|
+
error: signal?.reason
|
|
2437
|
+
});
|
|
2438
|
+
postControl({
|
|
2439
|
+
type: "cancel",
|
|
2440
|
+
requestId: id
|
|
2441
|
+
});
|
|
2442
|
+
};
|
|
2443
|
+
const owned = {
|
|
2444
|
+
bytes,
|
|
2445
|
+
caller: {
|
|
2446
|
+
status: "waiting",
|
|
2447
|
+
settle: (result) => {
|
|
2448
|
+
signal?.removeEventListener("abort", abort);
|
|
2449
|
+
result.ok ? resolve(result.value) : reject(result.error);
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
};
|
|
2453
|
+
pending.set(id, owned);
|
|
2454
|
+
pendingBytes += bytes;
|
|
2455
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
2456
|
+
try {
|
|
2457
|
+
postWorkerMessage(worker, packet);
|
|
2458
|
+
onSent?.();
|
|
2459
|
+
} catch (cause) {
|
|
2460
|
+
pending.delete(id);
|
|
2461
|
+
pendingBytes -= bytes;
|
|
2462
|
+
settleCaller(owned, {
|
|
2463
|
+
ok: false,
|
|
2464
|
+
error: cause
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2796
2467
|
});
|
|
2797
|
-
}
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
dependants.get(dependencyId)?.add(resourceId);
|
|
2468
|
+
};
|
|
2469
|
+
const openSession = async (sessionOptions) => {
|
|
2470
|
+
sessionOptions.signal?.throwIfAborted();
|
|
2471
|
+
requireReady();
|
|
2472
|
+
if (sessions.size >= workerLimits.lifetimes) throw new Error("Worker lifetime limit reached");
|
|
2473
|
+
const id = nextId();
|
|
2474
|
+
const session = ownSession({
|
|
2475
|
+
id,
|
|
2476
|
+
options: sessionOptions,
|
|
2477
|
+
postClose: () => postControl({
|
|
2478
|
+
type: "close",
|
|
2479
|
+
sessionId: id
|
|
2480
|
+
}),
|
|
2481
|
+
release: () => sessions.delete(id)
|
|
2812
2482
|
});
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
}
|
|
2827
|
-
const
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
return invalidated;
|
|
2831
|
-
};
|
|
2832
|
-
//#endregion
|
|
2833
|
-
//#region src/internal/server/runtime/runtime-resources.ts
|
|
2834
|
-
const createSubscriptionLifetime = ({ entry, invalidationListeners, lifecycle, signal }) => {
|
|
2835
|
-
const controller = new AbortController();
|
|
2836
|
-
const subscriptionSignal = AbortSignal.any([lifecycle.requestSignal(signal), controller.signal]);
|
|
2837
|
-
subscriptionSignal.throwIfAborted();
|
|
2838
|
-
const registeredEntry = {
|
|
2839
|
-
...entry,
|
|
2840
|
-
listener: () => {
|
|
2841
|
-
if (!subscriptionSignal.aborted) entry.listener();
|
|
2483
|
+
sessions.set(id, session);
|
|
2484
|
+
const { action, name, input, signal, correlation } = sessionOptions;
|
|
2485
|
+
try {
|
|
2486
|
+
await request({
|
|
2487
|
+
action,
|
|
2488
|
+
name,
|
|
2489
|
+
input,
|
|
2490
|
+
correlation,
|
|
2491
|
+
sessionId: id
|
|
2492
|
+
}, signal, session.markSent);
|
|
2493
|
+
signal?.throwIfAborted();
|
|
2494
|
+
session.opened();
|
|
2495
|
+
return session;
|
|
2496
|
+
} catch (cause) {
|
|
2497
|
+
const failure = session.getFailure();
|
|
2498
|
+
session.close();
|
|
2499
|
+
throw failure ?? cause;
|
|
2842
2500
|
}
|
|
2843
2501
|
};
|
|
2844
|
-
const
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2502
|
+
const handleReady = (message) => {
|
|
2503
|
+
if (state.status !== "starting") throw new Error("Unexpected worker ready message");
|
|
2504
|
+
clearTimeout(state.timer);
|
|
2505
|
+
state = { status: "ready" };
|
|
2506
|
+
startup.resolve(message);
|
|
2507
|
+
};
|
|
2508
|
+
const handleRequestReply = (message) => {
|
|
2509
|
+
const result = message.ok ? {
|
|
2510
|
+
ok: true,
|
|
2511
|
+
value: message.value
|
|
2512
|
+
} : {
|
|
2513
|
+
ok: false,
|
|
2514
|
+
error: deserializeWorkerError(message.error)
|
|
2515
|
+
};
|
|
2516
|
+
const owned = pending.get(message.id);
|
|
2517
|
+
if (!owned) return;
|
|
2518
|
+
pending.delete(message.id);
|
|
2519
|
+
pendingBytes -= owned.bytes;
|
|
2520
|
+
settleCaller(owned, result);
|
|
2852
2521
|
};
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2522
|
+
const handleDelivery = (message) => {
|
|
2523
|
+
try {
|
|
2524
|
+
if (message.type === "reply") handleRequestReply(message);
|
|
2525
|
+
if (message.type === "closed") {
|
|
2526
|
+
const error = message.error === void 0 ? void 0 : deserializeWorkerError(message.error);
|
|
2527
|
+
sessions.get(message.sessionId)?.finishClose(error);
|
|
2528
|
+
}
|
|
2529
|
+
if (message.type === "invalidate") sessions.get(message.sessionId)?.deliver(void 0);
|
|
2530
|
+
if (message.type === "stream-output") sessions.get(message.sessionId)?.deliver(message.message);
|
|
2531
|
+
if (message.type === "stream-error") {
|
|
2532
|
+
const error = deserializeWorkerError(message.error);
|
|
2533
|
+
sessions.get(message.sessionId)?.fail(error);
|
|
2534
|
+
}
|
|
2535
|
+
if (message.type === "log") contain(() => {
|
|
2536
|
+
const entry = JSON.parse(message.entry);
|
|
2537
|
+
options.serverLogger?.log({
|
|
2538
|
+
...entry,
|
|
2539
|
+
details: {
|
|
2540
|
+
...entry.details,
|
|
2541
|
+
worker: options.name
|
|
2542
|
+
}
|
|
2543
|
+
});
|
|
2544
|
+
});
|
|
2545
|
+
if (message.type === "notification") deliverNotification(message, options.notifications, postControl);
|
|
2546
|
+
} finally {
|
|
2547
|
+
postControl({
|
|
2548
|
+
type: "ack",
|
|
2549
|
+
deliveryId: message.deliveryId
|
|
2550
|
+
});
|
|
2551
|
+
}
|
|
2552
|
+
};
|
|
2553
|
+
const receive = (packet) => {
|
|
2554
|
+
if (state.status === "stopped") return;
|
|
2555
|
+
try {
|
|
2556
|
+
const message = decodeWorkerMessage(packet);
|
|
2557
|
+
if (message.type === "ready") handleReady(message);
|
|
2558
|
+
else if (message.type === "fatal") terminate(deserializeWorkerError(message.error));
|
|
2559
|
+
else if (message.type === "disposed") {
|
|
2560
|
+
if (state.status !== "stopping") throw new Error("Unexpected worker disposed message");
|
|
2561
|
+
terminate(state.cause);
|
|
2562
|
+
} else handleDelivery(message);
|
|
2563
|
+
} catch (cause) {
|
|
2564
|
+
terminate(asError(cause));
|
|
2565
|
+
}
|
|
2871
2566
|
};
|
|
2872
|
-
const
|
|
2873
|
-
if (
|
|
2874
|
-
const
|
|
2875
|
-
|
|
2876
|
-
|
|
2567
|
+
const stop = () => {
|
|
2568
|
+
if (state.status === "stopped" || state.status === "stopping") return state.completion;
|
|
2569
|
+
const cause = /* @__PURE__ */ new Error("Worker runtime disposed");
|
|
2570
|
+
if (state.status === "starting") return terminate(cause);
|
|
2571
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
2572
|
+
state = {
|
|
2573
|
+
status: "stopping",
|
|
2574
|
+
cause,
|
|
2575
|
+
completion: promise,
|
|
2576
|
+
finish: resolve,
|
|
2577
|
+
timer: setTimeout(() => {
|
|
2578
|
+
terminate(cause);
|
|
2579
|
+
}, options.shutdownMs)
|
|
2580
|
+
};
|
|
2581
|
+
pending.forEach((owned) => settleCaller(owned, {
|
|
2582
|
+
ok: false,
|
|
2583
|
+
error: cause
|
|
2584
|
+
}));
|
|
2585
|
+
sessions.forEach((session) => session.fail(cause));
|
|
2586
|
+
postControl({ type: "shutdown" });
|
|
2587
|
+
return promise;
|
|
2877
2588
|
};
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
signal: lifecycle.requestSignal(signal)
|
|
2589
|
+
worker.on("message", receive);
|
|
2590
|
+
worker.on("error", (cause) => {
|
|
2591
|
+
terminate(cause);
|
|
2882
2592
|
});
|
|
2883
|
-
|
|
2884
|
-
const
|
|
2885
|
-
|
|
2886
|
-
operationSignal.throwIfAborted();
|
|
2887
|
-
const input = definition.contract.input.parse(rawInput);
|
|
2888
|
-
const rawOutput = definition.kind === "derived" ? definition.combine(Object.fromEntries(await Promise.all(Object.entries(definition.dependencies).map(async ([name, dependencyId]) => [name, await read(dependencyId, rawInput, signal)])))) : await definition.read(input, context(signal));
|
|
2889
|
-
operationSignal.throwIfAborted();
|
|
2890
|
-
return definition.contract.output.parse(rawOutput);
|
|
2891
|
-
};
|
|
2892
|
-
const activateSubscriptions = async (id, rawInput, signal, activated, disposers) => {
|
|
2893
|
-
signal.throwIfAborted();
|
|
2894
|
-
if (activated.has(id)) return;
|
|
2895
|
-
activated.add(id);
|
|
2896
|
-
const definition = definitions[id];
|
|
2897
|
-
const input = definition.contract.input.parse(rawInput);
|
|
2898
|
-
if (definition.kind === "subscription") {
|
|
2899
|
-
const dispose = await definition.subscribe(input, () => {
|
|
2900
|
-
if (!signal.aborted) invalidateResource(id, input);
|
|
2901
|
-
}, context(signal));
|
|
2902
|
-
if (typeof dispose !== "function") throw new Error(`Resource ${id} subscribe must return a disposer`);
|
|
2903
|
-
if (signal.aborted) {
|
|
2904
|
-
await dispose();
|
|
2905
|
-
signal.throwIfAborted();
|
|
2906
|
-
}
|
|
2907
|
-
disposers.push(dispose);
|
|
2908
|
-
return;
|
|
2909
|
-
}
|
|
2910
|
-
if (definition.kind === "derived") for (const dependencyId of graph.dependencies.get(id) ?? []) await activateSubscriptions(dependencyId, rawInput, signal, activated, disposers);
|
|
2911
|
-
};
|
|
2912
|
-
Object.keys(definitions).forEach((name) => {
|
|
2913
|
-
resources.set(name, {
|
|
2914
|
-
read: (input, signal) => read(name, input, signal),
|
|
2915
|
-
subscribe: async (rawInput, listener, signal) => {
|
|
2916
|
-
lifecycle.requestSignal(signal).throwIfAborted();
|
|
2917
|
-
const input = definitions[name].contract.input.parse(rawInput);
|
|
2918
|
-
const subscription = createSubscriptionLifetime({
|
|
2919
|
-
entry: {
|
|
2920
|
-
input,
|
|
2921
|
-
listener,
|
|
2922
|
-
resourceId: name
|
|
2923
|
-
},
|
|
2924
|
-
invalidationListeners,
|
|
2925
|
-
lifecycle,
|
|
2926
|
-
signal
|
|
2927
|
-
});
|
|
2928
|
-
try {
|
|
2929
|
-
await activateSubscriptions(name, rawInput, subscription.signal, /* @__PURE__ */ new Set(), subscription.disposers);
|
|
2930
|
-
subscription.signal.throwIfAborted();
|
|
2931
|
-
subscription.refresh();
|
|
2932
|
-
subscription.signal.throwIfAborted();
|
|
2933
|
-
return subscription.dispose;
|
|
2934
|
-
} catch (cause) {
|
|
2935
|
-
try {
|
|
2936
|
-
await subscription.dispose();
|
|
2937
|
-
} catch (cleanupError) {
|
|
2938
|
-
throw new AggregateError([cause, cleanupError], "Resource subscription setup and cleanup failed");
|
|
2939
|
-
}
|
|
2940
|
-
throw cause;
|
|
2941
|
-
}
|
|
2942
|
-
}
|
|
2943
|
-
});
|
|
2593
|
+
worker.on("exit", (code) => {
|
|
2594
|
+
const phase = state.status === "starting" ? " during startup" : "";
|
|
2595
|
+
terminate(/* @__PURE__ */ new Error(`Worker ${options.name} exited${phase} (${code})`));
|
|
2944
2596
|
});
|
|
2597
|
+
postControl(options.init);
|
|
2945
2598
|
return {
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2599
|
+
ready: startup.promise,
|
|
2600
|
+
request,
|
|
2601
|
+
openSession,
|
|
2602
|
+
stop,
|
|
2603
|
+
terminate
|
|
2950
2604
|
};
|
|
2951
2605
|
};
|
|
2952
2606
|
//#endregion
|
|
2953
|
-
//#region src/internal/server/runtime/
|
|
2954
|
-
const
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
2607
|
+
//#region src/internal/server/runtime/worker-capabilities.ts
|
|
2608
|
+
const createWorkerResource = (connection, name) => ({
|
|
2609
|
+
read: (input, signal, correlation) => connection.request({
|
|
2610
|
+
action: "resource-read",
|
|
2611
|
+
input,
|
|
2612
|
+
name,
|
|
2613
|
+
correlation
|
|
2614
|
+
}, signal),
|
|
2615
|
+
subscribe: async (input, listener, signal, onError, correlation) => {
|
|
2616
|
+
return (await connection.openSession({
|
|
2617
|
+
action: "resource-subscribe",
|
|
2618
|
+
name,
|
|
2619
|
+
input,
|
|
2620
|
+
signal,
|
|
2621
|
+
onEvent: listener,
|
|
2622
|
+
onError,
|
|
2623
|
+
correlation
|
|
2624
|
+
})).close;
|
|
2625
|
+
}
|
|
2626
|
+
});
|
|
2627
|
+
const createWorkerOperation = (connection, name, returnsVoid) => ({
|
|
2628
|
+
execute: (input, signal, correlation) => connection.request({
|
|
2629
|
+
action: "operation",
|
|
2630
|
+
input,
|
|
2631
|
+
name,
|
|
2632
|
+
correlation
|
|
2633
|
+
}, signal),
|
|
2634
|
+
returnsVoid
|
|
2635
|
+
});
|
|
2636
|
+
const createWorkerStream = (connection, name) => ({ open: async (input, emit, signal, onError, correlation) => {
|
|
2637
|
+
const session = await connection.openSession({
|
|
2638
|
+
action: "stream-open",
|
|
2639
|
+
name,
|
|
2640
|
+
input,
|
|
2641
|
+
signal,
|
|
2642
|
+
onEvent: emit,
|
|
2643
|
+
onError,
|
|
2644
|
+
correlation
|
|
2645
|
+
});
|
|
2646
|
+
return {
|
|
2647
|
+
dispose: session.close,
|
|
2648
|
+
send: async (message) => {
|
|
2649
|
+
session.assertOpen();
|
|
2650
|
+
await connection.request({
|
|
2651
|
+
action: "stream-message",
|
|
2652
|
+
message,
|
|
2653
|
+
sessionId: session.id
|
|
2654
|
+
}, signal);
|
|
2655
|
+
}
|
|
2656
|
+
};
|
|
2657
|
+
} });
|
|
2658
|
+
//#endregion
|
|
2659
|
+
//#region src/internal/server/runtime/worker-runtime.ts
|
|
2660
|
+
const silentNotifications = { send: async () => void 0 };
|
|
2661
|
+
const resolveOptions = (options) => ({
|
|
2662
|
+
...options,
|
|
2663
|
+
notifications: options.notifications ?? silentNotifications,
|
|
2664
|
+
aliases: options.aliases ?? {},
|
|
2665
|
+
startupMs: options.startupMs ?? workerLimits.startupMs,
|
|
2666
|
+
shutdownMs: options.shutdownMs ?? workerLimits.shutdownMs
|
|
2667
|
+
});
|
|
2668
|
+
const launchWorker = (selection, options, workers) => {
|
|
2669
|
+
const connection = createWorkerConnection({
|
|
2670
|
+
name: selection.name,
|
|
2671
|
+
init: {
|
|
2672
|
+
type: "init",
|
|
2673
|
+
configPath: options.configPath,
|
|
2674
|
+
aliases: options.aliases,
|
|
2675
|
+
logLevel: options.serverLogger?.logLevel,
|
|
2676
|
+
role: selection.role,
|
|
2677
|
+
stream: selection.stream
|
|
2678
|
+
},
|
|
2679
|
+
notifications: options.notifications,
|
|
2680
|
+
serverLogger: options.serverLogger,
|
|
2681
|
+
startupMs: options.startupMs,
|
|
2682
|
+
shutdownMs: options.shutdownMs
|
|
3016
2683
|
});
|
|
2684
|
+
workers.push(connection);
|
|
2685
|
+
return connection;
|
|
2686
|
+
};
|
|
2687
|
+
const loadWorkerConfig = async (options) => {
|
|
2688
|
+
const workers = [];
|
|
2689
|
+
try {
|
|
2690
|
+
return { config: (await launchWorker({
|
|
2691
|
+
role: "metadata",
|
|
2692
|
+
name: "config"
|
|
2693
|
+
}, resolveOptions(options), workers).ready).settings };
|
|
2694
|
+
} finally {
|
|
2695
|
+
await Promise.all(workers.map((worker) => worker.terminate(/* @__PURE__ */ new Error("Config inspection finished"))));
|
|
2696
|
+
}
|
|
2697
|
+
};
|
|
2698
|
+
const startStreams = async (ready, options, workers) => {
|
|
2699
|
+
const streams = /* @__PURE__ */ new Map();
|
|
2700
|
+
const names = ready.manifest.streams;
|
|
2701
|
+
for (let offset = 0; offset < names.length; offset += 4) await Promise.all(names.slice(offset, offset + 4).map(async (name) => {
|
|
2702
|
+
const owner = launchWorker({
|
|
2703
|
+
role: "stream",
|
|
2704
|
+
stream: name,
|
|
2705
|
+
name: `stream:${name}`
|
|
2706
|
+
}, options, workers);
|
|
2707
|
+
const streamReady = await owner.ready;
|
|
2708
|
+
if (JSON.stringify(streamReady.manifest) !== JSON.stringify(ready.manifest)) throw new Error("Server definitions changed between worker imports");
|
|
2709
|
+
streams.set(name, owner);
|
|
2710
|
+
}));
|
|
2711
|
+
return streams;
|
|
2712
|
+
};
|
|
2713
|
+
const createWorkerIdentity = ({ application, streams, startupMs }) => {
|
|
2714
|
+
let instance = createRuntimeInstance(defaultInstanceId);
|
|
3017
2715
|
return {
|
|
3018
|
-
|
|
3019
|
-
|
|
2716
|
+
instance: {
|
|
2717
|
+
getInstanceId: () => instance.context.getInstanceId(),
|
|
2718
|
+
getDeepLinkPrefix: () => instance.context.getDeepLinkPrefix()
|
|
2719
|
+
},
|
|
2720
|
+
establishInstance: async (port) => {
|
|
2721
|
+
const signal = AbortSignal.timeout(startupMs);
|
|
2722
|
+
const identity = await application.request({
|
|
2723
|
+
action: "initialize-instance-identity",
|
|
2724
|
+
port
|
|
2725
|
+
}, signal);
|
|
2726
|
+
await Promise.all([...streams.values()].map((stream) => stream.request({
|
|
2727
|
+
action: "initialize-instance-identity",
|
|
2728
|
+
port,
|
|
2729
|
+
instanceId: identity.instanceId
|
|
2730
|
+
}, signal)));
|
|
2731
|
+
instance = createRuntimeInstance(identity.instanceId);
|
|
2732
|
+
return instance.establish(port);
|
|
2733
|
+
}
|
|
3020
2734
|
};
|
|
3021
2735
|
};
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
const
|
|
3025
|
-
|
|
3026
|
-
const instance = createRuntimeInstance(config.instanceId ?? defaultInstanceId);
|
|
2736
|
+
const createWorkerRuntime = async (input) => {
|
|
2737
|
+
const options = resolveOptions(input);
|
|
2738
|
+
const workers = [];
|
|
2739
|
+
let disposal;
|
|
3027
2740
|
try {
|
|
3028
|
-
const
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
const
|
|
3034
|
-
const
|
|
3035
|
-
|
|
3036
|
-
definitions: config.server.operations ?? {},
|
|
3037
|
-
notifications
|
|
3038
|
-
});
|
|
3039
|
-
const streams = createRuntimeStreams({
|
|
3040
|
-
context,
|
|
3041
|
-
definitions: config.server.streams ?? {},
|
|
3042
|
-
lifecycle
|
|
3043
|
-
});
|
|
3044
|
-
lifecycle.signal.throwIfAborted();
|
|
3045
|
-
lifecycle.completePreparation();
|
|
3046
|
-
const manifest = {
|
|
3047
|
-
debug,
|
|
3048
|
-
operations: [...operations.names],
|
|
3049
|
-
protocolVersion: 10,
|
|
3050
|
-
resources: [...resources.names],
|
|
3051
|
-
streams: [...streams.names]
|
|
3052
|
-
};
|
|
2741
|
+
const application = launchWorker({
|
|
2742
|
+
role: "application",
|
|
2743
|
+
name: "resources/operations"
|
|
2744
|
+
}, options, workers);
|
|
2745
|
+
const ready = await application.ready;
|
|
2746
|
+
const streams = await startStreams(ready, options, workers);
|
|
2747
|
+
const resources = new Map(ready.manifest.resources.map((name) => [name, createWorkerResource(application, name)]));
|
|
2748
|
+
const operations = new Map(ready.manifest.operations.map((name) => [name, createWorkerOperation(application, name, ready.voidOperations.includes(name))]));
|
|
3053
2749
|
return {
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
2750
|
+
settings: ready.settings,
|
|
2751
|
+
...createWorkerIdentity({
|
|
2752
|
+
application,
|
|
2753
|
+
streams,
|
|
2754
|
+
startupMs: options.startupMs
|
|
2755
|
+
}),
|
|
2756
|
+
manifest: ready.manifest,
|
|
2757
|
+
dispose: () => {
|
|
2758
|
+
disposal = disposal ?? Promise.all(workers.map((worker) => worker.stop())).then(() => void 0);
|
|
2759
|
+
return disposal;
|
|
2760
|
+
},
|
|
2761
|
+
getOperation: (name) => operations.get(name),
|
|
2762
|
+
getResource: (name) => resources.get(name),
|
|
2763
|
+
getStream: (name) => {
|
|
2764
|
+
const owner = streams.get(name);
|
|
2765
|
+
return owner ? createWorkerStream(owner, name) : void 0;
|
|
2766
|
+
}
|
|
3061
2767
|
};
|
|
3062
2768
|
} catch (cause) {
|
|
3063
|
-
|
|
2769
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
2770
|
+
await Promise.all(workers.map((worker) => worker.terminate(error)));
|
|
3064
2771
|
throw cause;
|
|
3065
2772
|
}
|
|
3066
2773
|
};
|
|
3067
2774
|
//#endregion
|
|
3068
2775
|
//#region src/internal/server/server-startup-options.ts
|
|
3069
2776
|
const serverStartupOverridesRuntimeSchema = serverConfigRuntimeSchema.omit({ port: true }).extend({
|
|
3070
|
-
|
|
2777
|
+
logLevel: logLevelSchema.optional(),
|
|
3071
2778
|
port: z.number().int().min(0).max(65535).optional()
|
|
3072
2779
|
});
|
|
3073
2780
|
const resolveServerStartupOptions = ({ config, configPath, overrides = {} }) => {
|
|
@@ -3075,7 +2782,7 @@ const resolveServerStartupOptions = ({ config, configPath, overrides = {} }) =>
|
|
|
3075
2782
|
const configuredWebAssetsDir = config.productionWebAssetsDir ? resolve(dirname(configPath), config.productionWebAssetsDir) : void 0;
|
|
3076
2783
|
const productionWebAssetsDir = validatedOverrides.productionWebAssetsDir ? resolve(validatedOverrides.productionWebAssetsDir) : configuredWebAssetsDir;
|
|
3077
2784
|
return {
|
|
3078
|
-
|
|
2785
|
+
logLevel: validatedOverrides.logLevel ?? config.logLevel ?? "info",
|
|
3079
2786
|
host: validatedOverrides.host ?? config.host ?? "localhost",
|
|
3080
2787
|
port: validatedOverrides.port ?? config.port ?? 4242,
|
|
3081
2788
|
...productionWebAssetsDir === void 0 ? {} : { productionWebAssetsDir },
|
|
@@ -3137,7 +2844,7 @@ const registerProcessErrorLogging = (logger) => {
|
|
|
3137
2844
|
};
|
|
3138
2845
|
};
|
|
3139
2846
|
const loadServerSettings = async ({ configPath, options }) => {
|
|
3140
|
-
const { config } = await
|
|
2847
|
+
const { config } = await loadWorkerConfig({
|
|
3141
2848
|
aliases: options.configAliases,
|
|
3142
2849
|
configPath
|
|
3143
2850
|
});
|
|
@@ -3161,7 +2868,7 @@ const createAuthOrigins = ({ configuredOrigins, startup }) => {
|
|
|
3161
2868
|
get: () => values
|
|
3162
2869
|
};
|
|
3163
2870
|
};
|
|
3164
|
-
const createApplicationServices = async ({ config, logger, resources, startup }) => {
|
|
2871
|
+
const createApplicationServices = async ({ aliases, config, configPath, logger, resources, startup }) => {
|
|
3165
2872
|
const authOrigins = createAuthOrigins({
|
|
3166
2873
|
configuredOrigins: config.auth.origins,
|
|
3167
2874
|
startup
|
|
@@ -3190,10 +2897,11 @@ const createApplicationServices = async ({ config, logger, resources, startup })
|
|
|
3190
2897
|
backgroundNotifications,
|
|
3191
2898
|
logger
|
|
3192
2899
|
});
|
|
3193
|
-
const runtime = await
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
notifications
|
|
2900
|
+
const runtime = await createWorkerRuntime({
|
|
2901
|
+
configPath,
|
|
2902
|
+
serverLogger: logger,
|
|
2903
|
+
notifications,
|
|
2904
|
+
aliases
|
|
3197
2905
|
});
|
|
3198
2906
|
resources.add(runtime.dispose);
|
|
3199
2907
|
return {
|
|
@@ -3227,7 +2935,7 @@ const startApplicationTransport = async ({ auth, authOrigins, authService, backg
|
|
|
3227
2935
|
});
|
|
3228
2936
|
resources.add(() => closeNodeServer(server, runtime.dispose));
|
|
3229
2937
|
const address = await getServerAddress(server);
|
|
3230
|
-
runtime.establishInstance(address.port);
|
|
2938
|
+
await runtime.establishInstance(address.port);
|
|
3231
2939
|
const urlHost = startup.host.includes(":") ? `[${startup.host}]` : startup.host;
|
|
3232
2940
|
const localUrl = `http://${urlHost}:${address.port}`;
|
|
3233
2941
|
const controlApiHost = startup.host === "0.0.0.0" ? "127.0.0.1" : startup.host === "::" ? "[::1]" : urlHost;
|
|
@@ -3273,25 +2981,26 @@ const failApplicationServerStartup = async (cause, resources) => {
|
|
|
3273
2981
|
throw cause;
|
|
3274
2982
|
};
|
|
3275
2983
|
const startApplicationServer = async ({ onRestartRequested, options }) => {
|
|
2984
|
+
const configPath = resolve(options.configPath);
|
|
2985
|
+
const { config, startup } = await loadServerSettings({
|
|
2986
|
+
configPath,
|
|
2987
|
+
options
|
|
2988
|
+
});
|
|
3276
2989
|
const logger = createServerLogger({
|
|
3277
|
-
|
|
2990
|
+
logLevel: startup.logLevel,
|
|
3278
2991
|
logFile: getServerLogFile()
|
|
3279
2992
|
});
|
|
3280
2993
|
const resources = createResources();
|
|
3281
2994
|
resources.add(registerProcessErrorLogging(logger));
|
|
3282
2995
|
try {
|
|
3283
|
-
const configPath = resolve(options.configPath);
|
|
3284
2996
|
logger.log({
|
|
3285
2997
|
details: { configPath },
|
|
3286
2998
|
event: "server-start"
|
|
3287
2999
|
});
|
|
3288
|
-
const { config, startup } = await loadServerSettings({
|
|
3289
|
-
configPath,
|
|
3290
|
-
options
|
|
3291
|
-
});
|
|
3292
|
-
logger.setEnabled(startup.debug);
|
|
3293
3000
|
const { auth, authOrigins, authService, backgroundNotifications, notifications, runtime } = await createApplicationServices({
|
|
3001
|
+
aliases: options.configAliases,
|
|
3294
3002
|
config,
|
|
3003
|
+
configPath,
|
|
3295
3004
|
logger,
|
|
3296
3005
|
resources,
|
|
3297
3006
|
startup
|
|
@@ -3330,7 +3039,7 @@ const startApplicationServer = async ({ onRestartRequested, options }) => {
|
|
|
3330
3039
|
const childOptionsSchema = z.object({
|
|
3331
3040
|
configAliases: z.record(z.string(), z.string()).optional(),
|
|
3332
3041
|
configPath: z.string(),
|
|
3333
|
-
|
|
3042
|
+
logLevel: logLevelSchema.optional(),
|
|
3334
3043
|
developmentWebTarget: z.url().optional(),
|
|
3335
3044
|
host: z.string().optional(),
|
|
3336
3045
|
port: z.number().int().min(0).max(65535).optional(),
|