overmux 0.0.10 → 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 +22 -0
- package/dist/bin.js +145 -58
- package/dist/bin.js.map +1 -1
- package/dist/docs/400-reference/200-configuration.md +54 -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/400-notifications.md +4 -0
- 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 +55 -14
- 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 +616 -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/THIRD_PARTY_NOTICES.md +30 -0
- package/dist/push/badge.png +0 -0
- package/dist/push/icon.png +0 -0
- package/dist/push/sw.js +88 -0
- package/dist/push/sw.js.map +1 -0
- package/dist/worker-protocol-hXs4FXCH.js +638 -0
- package/dist/worker-protocol-hXs4FXCH.js.map +1 -0
- package/docs/400-reference/200-configuration.md +54 -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/400-notifications.md +4 -0
- package/docs/400-reference/500-server/500-api.md +26 -5
- package/package.json +4 -3
- package/src/internal/client/background-notifications.ts +3 -1
- package/src/internal/client/host/hosted-pages/browser-notification-settings.tsx +8 -3
- package/src/internal/client/host/overmux-host.tsx +4 -8
- package/src/internal/client/push-assets/badge.png +0 -0
- package/src/internal/client/push-assets/icon.png +0 -0
- package/src/internal/client/push-service-worker.ts +118 -0
- package/src/internal/client/register-push-worker.ts +63 -0
- 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/http/routes.ts +47 -2
- 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/internal/shared/routes.ts +2 -0
- 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 { 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
|
};
|
|
@@ -1574,6 +1107,8 @@ const createRuntimeManifestHandler = ({ runtime }) => (context) => {
|
|
|
1574
1107
|
};
|
|
1575
1108
|
//#endregion
|
|
1576
1109
|
//#region src/internal/shared/routes.ts
|
|
1110
|
+
const pushNotificationIconPath = "/_overmux/push/icon.png";
|
|
1111
|
+
const pushNotificationBadgePath = "/_overmux/push/badge.png";
|
|
1577
1112
|
const runtimeWebSocketPath = "/api/socket";
|
|
1578
1113
|
const viteInfrastructurePath = "/_overmux/vite/*";
|
|
1579
1114
|
//#endregion
|
|
@@ -1608,6 +1143,17 @@ const registerHttpRoutes = ({ app, auth, authAssetsDirectory, backgroundNotifica
|
|
|
1608
1143
|
path: "*",
|
|
1609
1144
|
handler: createRequestLoggingMiddleware({ serverLogger })
|
|
1610
1145
|
},
|
|
1146
|
+
...[pushNotificationIconPath, pushNotificationBadgePath].map((path) => ({
|
|
1147
|
+
method: "GET",
|
|
1148
|
+
path,
|
|
1149
|
+
handler: async (context) => {
|
|
1150
|
+
const asset = await readFile(fileURLToPath(new URL(`../push/${basename(path)}`, import.meta.resolve("overmux"))));
|
|
1151
|
+
context.header("Cache-Control", "public, max-age=86400");
|
|
1152
|
+
context.header("Content-Type", "image/png");
|
|
1153
|
+
context.header("X-Content-Type-Options", "nosniff");
|
|
1154
|
+
return context.body(new Uint8Array(asset));
|
|
1155
|
+
}
|
|
1156
|
+
})),
|
|
1611
1157
|
{
|
|
1612
1158
|
method: "GET",
|
|
1613
1159
|
path: "/_overmux/auth-shell/*",
|
|
@@ -1639,6 +1185,17 @@ const registerHttpRoutes = ({ app, auth, authAssetsDirectory, backgroundNotifica
|
|
|
1639
1185
|
path: "*",
|
|
1640
1186
|
handler: authentication.authenticate
|
|
1641
1187
|
},
|
|
1188
|
+
{
|
|
1189
|
+
method: "GET",
|
|
1190
|
+
path: "/sw.js",
|
|
1191
|
+
handler: async (context) => {
|
|
1192
|
+
const script = await readFile(fileURLToPath(new URL("../push/sw.js", import.meta.resolve("overmux"))), "utf8");
|
|
1193
|
+
context.header("Cache-Control", "no-cache");
|
|
1194
|
+
context.header("Content-Type", "text/javascript; charset=UTF-8");
|
|
1195
|
+
context.header("X-Content-Type-Options", "nosniff");
|
|
1196
|
+
return context.body(script);
|
|
1197
|
+
}
|
|
1198
|
+
},
|
|
1642
1199
|
{
|
|
1643
1200
|
method: "POST",
|
|
1644
1201
|
path: "/api/auth/logout",
|
|
@@ -1746,13 +1303,6 @@ const createHttpApp = ({ auth, authAssetsDirectory, backgroundNotifications, dev
|
|
|
1746
1303
|
//#endregion
|
|
1747
1304
|
//#region src/internal/server/websocket-connection.ts
|
|
1748
1305
|
const maxPendingStreamOutputBytes = 1048576;
|
|
1749
|
-
var OperationError = class extends Error {
|
|
1750
|
-
code;
|
|
1751
|
-
constructor(code, message) {
|
|
1752
|
-
super(message);
|
|
1753
|
-
this.code = code;
|
|
1754
|
-
}
|
|
1755
|
-
};
|
|
1756
1306
|
const bytesOf = (data) => {
|
|
1757
1307
|
if (Array.isArray(data)) return Buffer.concat(data);
|
|
1758
1308
|
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
@@ -1769,7 +1319,7 @@ const operationIdOf = (value) => {
|
|
|
1769
1319
|
return typeof operationId === "string" && operationId.length <= 256 ? operationId : void 0;
|
|
1770
1320
|
};
|
|
1771
1321
|
const errorDetails = (cause) => {
|
|
1772
|
-
if (cause instanceof
|
|
1322
|
+
if (cause instanceof ProtocolError) return {
|
|
1773
1323
|
code: cause.code,
|
|
1774
1324
|
message: cause.message
|
|
1775
1325
|
};
|
|
@@ -1809,6 +1359,14 @@ const sendError = (state, cause, operationId, identity) => {
|
|
|
1809
1359
|
type: "error"
|
|
1810
1360
|
});
|
|
1811
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
|
+
};
|
|
1812
1370
|
const disposeSubscription = async (state, subscriptionId) => {
|
|
1813
1371
|
const entry = state.subscriptions.get(subscriptionId);
|
|
1814
1372
|
if (!entry) return;
|
|
@@ -1827,11 +1385,11 @@ const disposeStream = async (state, streamId, notify = true) => {
|
|
|
1827
1385
|
}
|
|
1828
1386
|
state.streams.delete(streamId);
|
|
1829
1387
|
entry.controller.abort(/* @__PURE__ */ new Error("Stream closed"));
|
|
1830
|
-
await entry.session?.dispose();
|
|
1831
1388
|
if (notify) send$1(state, {
|
|
1832
1389
|
streamId,
|
|
1833
1390
|
type: "stream-closed"
|
|
1834
1391
|
});
|
|
1392
|
+
await entry.session?.dispose();
|
|
1835
1393
|
};
|
|
1836
1394
|
const disposeConnectionOperations = async (state, notify) => {
|
|
1837
1395
|
const operationControllers = [...state.operations.values()];
|
|
@@ -1843,7 +1401,7 @@ const disposeConnectionOperations = async (state, notify) => {
|
|
|
1843
1401
|
};
|
|
1844
1402
|
const runOperation = (state, identity, operation) => {
|
|
1845
1403
|
if (state.operations.has(identity.operationId)) {
|
|
1846
|
-
sendError(state, new
|
|
1404
|
+
sendError(state, new ProtocolError("conflict", "Operation ID is already active"), identity.operationId, identity);
|
|
1847
1405
|
return;
|
|
1848
1406
|
}
|
|
1849
1407
|
const controller = new AbortController();
|
|
@@ -1852,14 +1410,16 @@ const runOperation = (state, identity, operation) => {
|
|
|
1852
1410
|
state.serverLogger?.log({
|
|
1853
1411
|
correlationId: identity.operationId,
|
|
1854
1412
|
details: identity,
|
|
1855
|
-
event: "operation-start"
|
|
1413
|
+
event: "operation-start",
|
|
1414
|
+
level: "debug"
|
|
1856
1415
|
});
|
|
1857
1416
|
operation(controller.signal).then(() => {
|
|
1858
1417
|
state.serverLogger?.log({
|
|
1859
1418
|
correlationId: identity.operationId,
|
|
1860
1419
|
details: identity,
|
|
1861
1420
|
durationMs: Date.now() - startedAt,
|
|
1862
|
-
event: "operation-complete"
|
|
1421
|
+
event: "operation-complete",
|
|
1422
|
+
level: "debug"
|
|
1863
1423
|
});
|
|
1864
1424
|
}).catch((cause) => {
|
|
1865
1425
|
if (!controller.signal.aborted && !state.closed) {
|
|
@@ -1887,8 +1447,11 @@ const readResource = (state, message) => {
|
|
|
1887
1447
|
requestedName: message.resourceName
|
|
1888
1448
|
};
|
|
1889
1449
|
runOperation(state, identity, async (signal) => {
|
|
1890
|
-
if (!resource) throw new
|
|
1891
|
-
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
|
+
});
|
|
1892
1455
|
send$1(state, {
|
|
1893
1456
|
operationId: message.operationId,
|
|
1894
1457
|
output,
|
|
@@ -1904,20 +1467,31 @@ const subscribeResource = (state, message) => {
|
|
|
1904
1467
|
requestedName: message.resourceName
|
|
1905
1468
|
};
|
|
1906
1469
|
if (state.subscriptions.has(message.subscriptionId)) {
|
|
1907
|
-
sendError(state, new
|
|
1470
|
+
sendError(state, new ProtocolError("conflict", "Subscription ID is already active"), message.operationId, identity);
|
|
1908
1471
|
return;
|
|
1909
1472
|
}
|
|
1910
1473
|
runOperation(state, identity, async (operationSignal) => {
|
|
1911
|
-
if (!resource) throw new
|
|
1474
|
+
if (!resource) throw new ProtocolError("not-found", "Resource not found");
|
|
1912
1475
|
const controller = new AbortController();
|
|
1913
1476
|
const entry = { controller };
|
|
1914
1477
|
state.subscriptions.set(message.subscriptionId, entry);
|
|
1915
1478
|
const signal = AbortSignal.any([operationSignal, controller.signal]);
|
|
1916
1479
|
try {
|
|
1917
|
-
const dispose = await resource.subscribe(message.input, () =>
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
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
|
+
});
|
|
1921
1495
|
if (state.subscriptions.get(message.subscriptionId) === entry) entry.dispose = dispose;
|
|
1922
1496
|
else await dispose();
|
|
1923
1497
|
} catch (cause) {
|
|
@@ -1946,11 +1520,11 @@ const openStream = (state, message) => {
|
|
|
1946
1520
|
requestedName: message.streamName
|
|
1947
1521
|
};
|
|
1948
1522
|
if (state.streams.has(message.streamId)) {
|
|
1949
|
-
sendError(state, new
|
|
1523
|
+
sendError(state, new ProtocolError("conflict", "Stream ID is already active"), message.operationId, identity);
|
|
1950
1524
|
return;
|
|
1951
1525
|
}
|
|
1952
1526
|
runOperation(state, identity, async (operationSignal) => {
|
|
1953
|
-
if (!stream) throw new
|
|
1527
|
+
if (!stream) throw new ProtocolError("not-found", "Stream not found");
|
|
1954
1528
|
const controller = new AbortController();
|
|
1955
1529
|
const entry = {
|
|
1956
1530
|
controller,
|
|
@@ -1960,8 +1534,10 @@ const openStream = (state, message) => {
|
|
|
1960
1534
|
const pending = [];
|
|
1961
1535
|
let pendingBytes = 0;
|
|
1962
1536
|
let opened = false;
|
|
1537
|
+
const signal = AbortSignal.any([operationSignal, controller.signal]);
|
|
1963
1538
|
try {
|
|
1964
|
-
|
|
1539
|
+
const session = await stream.open(message.input, (output) => {
|
|
1540
|
+
if (signal.aborted || state.streams.get(message.streamId) !== entry) return;
|
|
1965
1541
|
if (opened) {
|
|
1966
1542
|
if (!send$1(state, {
|
|
1967
1543
|
message: output,
|
|
@@ -1973,12 +1549,22 @@ const openStream = (state, message) => {
|
|
|
1973
1549
|
pendingBytes += frameLength(output);
|
|
1974
1550
|
if (pendingBytes > maxPendingStreamOutputBytes) throw new Error("Stream produced too much output while opening");
|
|
1975
1551
|
pending.push(output);
|
|
1976
|
-
},
|
|
1552
|
+
}, signal, (cause) => {
|
|
1553
|
+
if (signal.aborted || state.streams.get(message.streamId) !== entry) return;
|
|
1977
1554
|
sendError(state, cause, message.streamId, identity);
|
|
1978
1555
|
disposeStream(state, message.streamId).catch((disposeCause) => {
|
|
1979
|
-
|
|
1556
|
+
reportCleanupFailure(state, disposeCause);
|
|
1980
1557
|
});
|
|
1558
|
+
}, {
|
|
1559
|
+
correlationId: message.operationId,
|
|
1560
|
+
connectionId: state.connectionId,
|
|
1561
|
+
streamId: message.streamId
|
|
1981
1562
|
});
|
|
1563
|
+
if (signal.aborted || state.streams.get(message.streamId) !== entry) {
|
|
1564
|
+
await session.dispose();
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
entry.session = session;
|
|
1982
1568
|
send$1(state, {
|
|
1983
1569
|
operationId: message.operationId,
|
|
1984
1570
|
streamId: message.streamId,
|
|
@@ -1991,6 +1577,7 @@ const openStream = (state, message) => {
|
|
|
1991
1577
|
type: "stream-output"
|
|
1992
1578
|
})) throw new Error("Stream output could not be delivered");
|
|
1993
1579
|
} catch (cause) {
|
|
1580
|
+
if (signal.aborted || state.streams.get(message.streamId) !== entry) return;
|
|
1994
1581
|
await disposeStream(state, message.streamId, false);
|
|
1995
1582
|
throw cause;
|
|
1996
1583
|
}
|
|
@@ -2004,18 +1591,19 @@ const sendStreamMessage = (state, message) => {
|
|
|
2004
1591
|
operationType: message.type
|
|
2005
1592
|
};
|
|
2006
1593
|
if (!entry?.session) {
|
|
2007
|
-
sendError(state, new
|
|
1594
|
+
sendError(state, new ProtocolError("not-found", "Stream not found"), message.streamId, identity);
|
|
2008
1595
|
return;
|
|
2009
1596
|
}
|
|
2010
1597
|
entry.session.send(message.message).catch((cause) => {
|
|
1598
|
+
if (entry.controller.signal.aborted || state.streams.get(message.streamId) !== entry) return;
|
|
2011
1599
|
sendError(state, cause, message.streamId, identity);
|
|
2012
1600
|
disposeStream(state, message.streamId).catch((disposeCause) => {
|
|
2013
|
-
|
|
1601
|
+
reportCleanupFailure(state, disposeCause);
|
|
2014
1602
|
});
|
|
2015
1603
|
});
|
|
2016
1604
|
};
|
|
2017
1605
|
const logClientDiagnostic = (state, message) => {
|
|
2018
|
-
if (
|
|
1606
|
+
if (state.serverLogger?.logLevel !== "debug") return;
|
|
2019
1607
|
state.serverLogger.log({
|
|
2020
1608
|
details: {
|
|
2021
1609
|
arguments: message.arguments,
|
|
@@ -2044,7 +1632,7 @@ const handleMessage = (state, message) => {
|
|
|
2044
1632
|
}
|
|
2045
1633
|
if (message.type === "resource-unsubscribe") {
|
|
2046
1634
|
disposeSubscription(state, message.subscriptionId).catch((cause) => {
|
|
2047
|
-
|
|
1635
|
+
reportCleanupFailure(state, cause);
|
|
2048
1636
|
});
|
|
2049
1637
|
return;
|
|
2050
1638
|
}
|
|
@@ -2054,7 +1642,7 @@ const handleMessage = (state, message) => {
|
|
|
2054
1642
|
}
|
|
2055
1643
|
if (message.type === "stream-close") {
|
|
2056
1644
|
disposeStream(state, message.streamId).catch((cause) => {
|
|
2057
|
-
|
|
1645
|
+
reportCleanupFailure(state, cause);
|
|
2058
1646
|
});
|
|
2059
1647
|
return;
|
|
2060
1648
|
}
|
|
@@ -2091,7 +1679,8 @@ const disposeConnection = async (state) => {
|
|
|
2091
1679
|
state.closed = true;
|
|
2092
1680
|
state.serverLogger?.log({
|
|
2093
1681
|
correlationId: state.connectionId,
|
|
2094
|
-
event: "websocket-close"
|
|
1682
|
+
event: "websocket-close",
|
|
1683
|
+
level: "debug"
|
|
2095
1684
|
});
|
|
2096
1685
|
await disposeConnectionOperations(state, false);
|
|
2097
1686
|
};
|
|
@@ -2113,7 +1702,8 @@ const attachWebSocketConnection = ({ runtime, serverLogger, socket }) => {
|
|
|
2113
1702
|
});
|
|
2114
1703
|
serverLogger?.log({
|
|
2115
1704
|
correlationId: state.connectionId,
|
|
2116
|
-
event: "websocket-open"
|
|
1705
|
+
event: "websocket-open",
|
|
1706
|
+
level: "debug"
|
|
2117
1707
|
});
|
|
2118
1708
|
socket.on("message", (data, isBinary) => handleFrame(state, data, isBinary));
|
|
2119
1709
|
socket.once("close", (code, reason) => {
|
|
@@ -2123,7 +1713,8 @@ const attachWebSocketConnection = ({ runtime, serverLogger, socket }) => {
|
|
|
2123
1713
|
code,
|
|
2124
1714
|
reason: reason?.toString() ?? ""
|
|
2125
1715
|
},
|
|
2126
|
-
event: "websocket-close-event"
|
|
1716
|
+
event: "websocket-close-event",
|
|
1717
|
+
level: "debug"
|
|
2127
1718
|
});
|
|
2128
1719
|
disposeConnection(state);
|
|
2129
1720
|
});
|
|
@@ -2312,7 +1903,8 @@ const createWebSocketServer = ({ auth, developmentWebTarget, runtime, server, se
|
|
|
2312
1903
|
const correlationId = serverLogger?.id();
|
|
2313
1904
|
serverLogger?.log({
|
|
2314
1905
|
correlationId,
|
|
2315
|
-
event: "websocket-connect"
|
|
1906
|
+
event: "websocket-connect",
|
|
1907
|
+
level: "debug"
|
|
2316
1908
|
});
|
|
2317
1909
|
const path = requestPath(request);
|
|
2318
1910
|
const isRuntimeSocket = path === runtimeWebSocketPath;
|
|
@@ -2615,15 +2207,6 @@ const createFileBackgroundNotificationStore = ({ environment = process.env, home
|
|
|
2615
2207
|
}
|
|
2616
2208
|
};
|
|
2617
2209
|
};
|
|
2618
|
-
z.void();
|
|
2619
|
-
const jsonValueSchema = z.lazy(() => z.union([
|
|
2620
|
-
z.string(),
|
|
2621
|
-
z.number().finite(),
|
|
2622
|
-
z.boolean(),
|
|
2623
|
-
z.null(),
|
|
2624
|
-
z.array(jsonValueSchema),
|
|
2625
|
-
z.record(z.string(), jsonValueSchema)
|
|
2626
|
-
]));
|
|
2627
2210
|
//#endregion
|
|
2628
2211
|
//#region src/internal/server/notifications/notification-service.ts
|
|
2629
2212
|
const deliverSafely = async ({ deliver, event, logger }) => {
|
|
@@ -2661,389 +2244,537 @@ const createNotificationService = ({ backgroundNotifications, logger }) => {
|
|
|
2661
2244
|
};
|
|
2662
2245
|
};
|
|
2663
2246
|
//#endregion
|
|
2664
|
-
//#region src/internal/server/runtime/
|
|
2665
|
-
const
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
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();
|
|
2671
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();
|
|
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 });
|
|
2672
2334
|
return {
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
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
|
+
};
|
|
2346
|
+
},
|
|
2347
|
+
opened: () => {
|
|
2348
|
+
if (state.status === "opening") state = { status: "open" };
|
|
2349
|
+
assertOpen();
|
|
2676
2350
|
},
|
|
2677
|
-
|
|
2678
|
-
if (
|
|
2351
|
+
deliver: (value) => {
|
|
2352
|
+
if (state.status !== "opening" && state.status !== "open") return;
|
|
2679
2353
|
try {
|
|
2680
|
-
|
|
2354
|
+
options.onEvent(value);
|
|
2681
2355
|
} catch (cause) {
|
|
2682
|
-
|
|
2356
|
+
fail(asError(cause));
|
|
2683
2357
|
}
|
|
2684
|
-
|
|
2358
|
+
},
|
|
2359
|
+
terminate: (cause) => {
|
|
2360
|
+
if (state.status === "closed") return;
|
|
2361
|
+
if (state.status === "closing") {
|
|
2362
|
+
finishClose(cause);
|
|
2363
|
+
return;
|
|
2364
|
+
}
|
|
2365
|
+
detach();
|
|
2366
|
+
state = {
|
|
2367
|
+
status: "closed",
|
|
2368
|
+
completion: Promise.resolve(),
|
|
2369
|
+
failure: cause
|
|
2370
|
+
};
|
|
2371
|
+
release();
|
|
2372
|
+
contain(() => options.onError?.(cause));
|
|
2685
2373
|
}
|
|
2686
2374
|
};
|
|
2687
2375
|
};
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
const
|
|
2691
|
-
const
|
|
2692
|
-
const
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
if (pending) return pending;
|
|
2710
|
-
controller.signal.removeEventListener("abort", onAbort);
|
|
2711
|
-
signal?.removeEventListener("abort", onAbort);
|
|
2712
|
-
activeDisposers.delete(disposeOnce);
|
|
2713
|
-
pending = Promise.resolve().then(dispose);
|
|
2714
|
-
return pending;
|
|
2715
|
-
};
|
|
2716
|
-
const onAbort = () => {
|
|
2717
|
-
disposeOnce().catch(() => void 0);
|
|
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
|
|
2718
2397
|
};
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
activeDisposers.clear();
|
|
2730
|
-
controller.abort(reason);
|
|
2731
|
-
disposal = disposeAll(pending);
|
|
2732
|
-
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;
|
|
2733
2408
|
};
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
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
|
+
}
|
|
2741
2416
|
};
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
const
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
path: [...path, resourceId],
|
|
2754
|
-
resourceId: dependencyId,
|
|
2755
|
-
visited,
|
|
2756
|
-
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
|
|
2757
2428
|
});
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
const
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
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
|
+
}
|
|
2772
2467
|
});
|
|
2773
|
-
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
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)
|
|
2788
2482
|
});
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
}
|
|
2803
|
-
const
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
return invalidated;
|
|
2807
|
-
};
|
|
2808
|
-
//#endregion
|
|
2809
|
-
//#region src/internal/server/runtime/runtime-resources.ts
|
|
2810
|
-
const createSubscriptionLifetime = ({ entry, invalidationListeners, lifecycle, signal }) => {
|
|
2811
|
-
const controller = new AbortController();
|
|
2812
|
-
const subscriptionSignal = AbortSignal.any([lifecycle.requestSignal(signal), controller.signal]);
|
|
2813
|
-
subscriptionSignal.throwIfAborted();
|
|
2814
|
-
const registeredEntry = {
|
|
2815
|
-
...entry,
|
|
2816
|
-
listener: () => {
|
|
2817
|
-
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;
|
|
2818
2500
|
}
|
|
2819
2501
|
};
|
|
2820
|
-
const
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
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);
|
|
2828
2521
|
};
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
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
|
+
}
|
|
2847
2552
|
};
|
|
2848
|
-
const
|
|
2849
|
-
if (
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
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
|
+
}
|
|
2853
2566
|
};
|
|
2854
|
-
const
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
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;
|
|
2588
|
+
};
|
|
2589
|
+
worker.on("message", receive);
|
|
2590
|
+
worker.on("error", (cause) => {
|
|
2591
|
+
terminate(cause);
|
|
2858
2592
|
});
|
|
2859
|
-
|
|
2860
|
-
const
|
|
2861
|
-
|
|
2862
|
-
operationSignal.throwIfAborted();
|
|
2863
|
-
const input = definition.contract.input.parse(rawInput);
|
|
2864
|
-
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));
|
|
2865
|
-
operationSignal.throwIfAborted();
|
|
2866
|
-
return definition.contract.output.parse(rawOutput);
|
|
2867
|
-
};
|
|
2868
|
-
const activateSubscriptions = async (id, rawInput, signal, activated, disposers) => {
|
|
2869
|
-
signal.throwIfAborted();
|
|
2870
|
-
if (activated.has(id)) return;
|
|
2871
|
-
activated.add(id);
|
|
2872
|
-
const definition = definitions[id];
|
|
2873
|
-
const input = definition.contract.input.parse(rawInput);
|
|
2874
|
-
if (definition.kind === "subscription") {
|
|
2875
|
-
const dispose = await definition.subscribe(input, () => {
|
|
2876
|
-
if (!signal.aborted) invalidateResource(id, input);
|
|
2877
|
-
}, context(signal));
|
|
2878
|
-
if (typeof dispose !== "function") throw new Error(`Resource ${id} subscribe must return a disposer`);
|
|
2879
|
-
if (signal.aborted) {
|
|
2880
|
-
await dispose();
|
|
2881
|
-
signal.throwIfAborted();
|
|
2882
|
-
}
|
|
2883
|
-
disposers.push(dispose);
|
|
2884
|
-
return;
|
|
2885
|
-
}
|
|
2886
|
-
if (definition.kind === "derived") for (const dependencyId of graph.dependencies.get(id) ?? []) await activateSubscriptions(dependencyId, rawInput, signal, activated, disposers);
|
|
2887
|
-
};
|
|
2888
|
-
Object.keys(definitions).forEach((name) => {
|
|
2889
|
-
resources.set(name, {
|
|
2890
|
-
read: (input, signal) => read(name, input, signal),
|
|
2891
|
-
subscribe: async (rawInput, listener, signal) => {
|
|
2892
|
-
lifecycle.requestSignal(signal).throwIfAborted();
|
|
2893
|
-
const input = definitions[name].contract.input.parse(rawInput);
|
|
2894
|
-
const subscription = createSubscriptionLifetime({
|
|
2895
|
-
entry: {
|
|
2896
|
-
input,
|
|
2897
|
-
listener,
|
|
2898
|
-
resourceId: name
|
|
2899
|
-
},
|
|
2900
|
-
invalidationListeners,
|
|
2901
|
-
lifecycle,
|
|
2902
|
-
signal
|
|
2903
|
-
});
|
|
2904
|
-
try {
|
|
2905
|
-
await activateSubscriptions(name, rawInput, subscription.signal, /* @__PURE__ */ new Set(), subscription.disposers);
|
|
2906
|
-
subscription.signal.throwIfAborted();
|
|
2907
|
-
subscription.refresh();
|
|
2908
|
-
subscription.signal.throwIfAborted();
|
|
2909
|
-
return subscription.dispose;
|
|
2910
|
-
} catch (cause) {
|
|
2911
|
-
try {
|
|
2912
|
-
await subscription.dispose();
|
|
2913
|
-
} catch (cleanupError) {
|
|
2914
|
-
throw new AggregateError([cause, cleanupError], "Resource subscription setup and cleanup failed");
|
|
2915
|
-
}
|
|
2916
|
-
throw cause;
|
|
2917
|
-
}
|
|
2918
|
-
}
|
|
2919
|
-
});
|
|
2593
|
+
worker.on("exit", (code) => {
|
|
2594
|
+
const phase = state.status === "starting" ? " during startup" : "";
|
|
2595
|
+
terminate(/* @__PURE__ */ new Error(`Worker ${options.name} exited${phase} (${code})`));
|
|
2920
2596
|
});
|
|
2597
|
+
postControl(options.init);
|
|
2921
2598
|
return {
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2599
|
+
ready: startup.promise,
|
|
2600
|
+
request,
|
|
2601
|
+
openSession,
|
|
2602
|
+
stop,
|
|
2603
|
+
terminate
|
|
2926
2604
|
};
|
|
2927
2605
|
};
|
|
2928
2606
|
//#endregion
|
|
2929
|
-
//#region src/internal/server/runtime/
|
|
2930
|
-
const
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
await session.dispose?.();
|
|
2968
|
-
operationSignal.throwIfAborted();
|
|
2969
|
-
throw failure;
|
|
2970
|
-
}
|
|
2971
|
-
let messages = Promise.resolve();
|
|
2972
|
-
const dispose = lifecycle.track(async () => {
|
|
2973
|
-
acceptingMessages = false;
|
|
2974
|
-
sessionController.abort(/* @__PURE__ */ new Error("Stream session disposed"));
|
|
2975
|
-
await messages.catch(() => void 0);
|
|
2976
|
-
await session.dispose?.();
|
|
2977
|
-
}, requestSignal);
|
|
2978
|
-
return {
|
|
2979
|
-
dispose: async () => dispose(),
|
|
2980
|
-
send: async (rawMessage) => {
|
|
2981
|
-
if (!acceptingMessages) throw new Error("Stream session is closed");
|
|
2982
|
-
const message = definition.contract.clientMessage.parse(rawMessage);
|
|
2983
|
-
messages = messages.then(async () => {
|
|
2984
|
-
operationSignal.throwIfAborted();
|
|
2985
|
-
await session.onMessage?.(message);
|
|
2986
|
-
operationSignal.throwIfAborted();
|
|
2987
|
-
});
|
|
2988
|
-
return messages;
|
|
2989
|
-
}
|
|
2990
|
-
};
|
|
2991
|
-
} });
|
|
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
|
|
2992
2645
|
});
|
|
2993
2646
|
return {
|
|
2994
|
-
|
|
2995
|
-
|
|
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
|
+
}
|
|
2996
2656
|
};
|
|
2997
|
-
};
|
|
2657
|
+
} });
|
|
2998
2658
|
//#endregion
|
|
2999
|
-
//#region src/internal/server/runtime/
|
|
3000
|
-
const
|
|
3001
|
-
|
|
3002
|
-
|
|
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
|
|
2683
|
+
});
|
|
2684
|
+
workers.push(connection);
|
|
2685
|
+
return connection;
|
|
2686
|
+
};
|
|
2687
|
+
const loadWorkerConfig = async (options) => {
|
|
2688
|
+
const workers = [];
|
|
3003
2689
|
try {
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
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);
|
|
2715
|
+
return {
|
|
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
|
+
}
|
|
2734
|
+
};
|
|
2735
|
+
};
|
|
2736
|
+
const createWorkerRuntime = async (input) => {
|
|
2737
|
+
const options = resolveOptions(input);
|
|
2738
|
+
const workers = [];
|
|
2739
|
+
let disposal;
|
|
2740
|
+
try {
|
|
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))]));
|
|
3029
2749
|
return {
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
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
|
+
}
|
|
3037
2767
|
};
|
|
3038
2768
|
} catch (cause) {
|
|
3039
|
-
|
|
2769
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
2770
|
+
await Promise.all(workers.map((worker) => worker.terminate(error)));
|
|
3040
2771
|
throw cause;
|
|
3041
2772
|
}
|
|
3042
2773
|
};
|
|
3043
2774
|
//#endregion
|
|
3044
2775
|
//#region src/internal/server/server-startup-options.ts
|
|
3045
2776
|
const serverStartupOverridesRuntimeSchema = serverConfigRuntimeSchema.omit({ port: true }).extend({
|
|
3046
|
-
|
|
2777
|
+
logLevel: logLevelSchema.optional(),
|
|
3047
2778
|
port: z.number().int().min(0).max(65535).optional()
|
|
3048
2779
|
});
|
|
3049
2780
|
const resolveServerStartupOptions = ({ config, configPath, overrides = {} }) => {
|
|
@@ -3051,7 +2782,7 @@ const resolveServerStartupOptions = ({ config, configPath, overrides = {} }) =>
|
|
|
3051
2782
|
const configuredWebAssetsDir = config.productionWebAssetsDir ? resolve(dirname(configPath), config.productionWebAssetsDir) : void 0;
|
|
3052
2783
|
const productionWebAssetsDir = validatedOverrides.productionWebAssetsDir ? resolve(validatedOverrides.productionWebAssetsDir) : configuredWebAssetsDir;
|
|
3053
2784
|
return {
|
|
3054
|
-
|
|
2785
|
+
logLevel: validatedOverrides.logLevel ?? config.logLevel ?? "info",
|
|
3055
2786
|
host: validatedOverrides.host ?? config.host ?? "localhost",
|
|
3056
2787
|
port: validatedOverrides.port ?? config.port ?? 4242,
|
|
3057
2788
|
...productionWebAssetsDir === void 0 ? {} : { productionWebAssetsDir },
|
|
@@ -3113,7 +2844,7 @@ const registerProcessErrorLogging = (logger) => {
|
|
|
3113
2844
|
};
|
|
3114
2845
|
};
|
|
3115
2846
|
const loadServerSettings = async ({ configPath, options }) => {
|
|
3116
|
-
const { config } = await
|
|
2847
|
+
const { config } = await loadWorkerConfig({
|
|
3117
2848
|
aliases: options.configAliases,
|
|
3118
2849
|
configPath
|
|
3119
2850
|
});
|
|
@@ -3137,7 +2868,7 @@ const createAuthOrigins = ({ configuredOrigins, startup }) => {
|
|
|
3137
2868
|
get: () => values
|
|
3138
2869
|
};
|
|
3139
2870
|
};
|
|
3140
|
-
const createApplicationServices = async ({ config, logger, resources, startup }) => {
|
|
2871
|
+
const createApplicationServices = async ({ aliases, config, configPath, logger, resources, startup }) => {
|
|
3141
2872
|
const authOrigins = createAuthOrigins({
|
|
3142
2873
|
configuredOrigins: config.auth.origins,
|
|
3143
2874
|
startup
|
|
@@ -3166,10 +2897,11 @@ const createApplicationServices = async ({ config, logger, resources, startup })
|
|
|
3166
2897
|
backgroundNotifications,
|
|
3167
2898
|
logger
|
|
3168
2899
|
});
|
|
3169
|
-
const runtime = await
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
notifications
|
|
2900
|
+
const runtime = await createWorkerRuntime({
|
|
2901
|
+
configPath,
|
|
2902
|
+
serverLogger: logger,
|
|
2903
|
+
notifications,
|
|
2904
|
+
aliases
|
|
3173
2905
|
});
|
|
3174
2906
|
resources.add(runtime.dispose);
|
|
3175
2907
|
return {
|
|
@@ -3203,7 +2935,7 @@ const startApplicationTransport = async ({ auth, authOrigins, authService, backg
|
|
|
3203
2935
|
});
|
|
3204
2936
|
resources.add(() => closeNodeServer(server, runtime.dispose));
|
|
3205
2937
|
const address = await getServerAddress(server);
|
|
3206
|
-
runtime.establishInstance(address.port);
|
|
2938
|
+
await runtime.establishInstance(address.port);
|
|
3207
2939
|
const urlHost = startup.host.includes(":") ? `[${startup.host}]` : startup.host;
|
|
3208
2940
|
const localUrl = `http://${urlHost}:${address.port}`;
|
|
3209
2941
|
const controlApiHost = startup.host === "0.0.0.0" ? "127.0.0.1" : startup.host === "::" ? "[::1]" : urlHost;
|
|
@@ -3249,25 +2981,26 @@ const failApplicationServerStartup = async (cause, resources) => {
|
|
|
3249
2981
|
throw cause;
|
|
3250
2982
|
};
|
|
3251
2983
|
const startApplicationServer = async ({ onRestartRequested, options }) => {
|
|
2984
|
+
const configPath = resolve(options.configPath);
|
|
2985
|
+
const { config, startup } = await loadServerSettings({
|
|
2986
|
+
configPath,
|
|
2987
|
+
options
|
|
2988
|
+
});
|
|
3252
2989
|
const logger = createServerLogger({
|
|
3253
|
-
|
|
2990
|
+
logLevel: startup.logLevel,
|
|
3254
2991
|
logFile: getServerLogFile()
|
|
3255
2992
|
});
|
|
3256
2993
|
const resources = createResources();
|
|
3257
2994
|
resources.add(registerProcessErrorLogging(logger));
|
|
3258
2995
|
try {
|
|
3259
|
-
const configPath = resolve(options.configPath);
|
|
3260
2996
|
logger.log({
|
|
3261
2997
|
details: { configPath },
|
|
3262
2998
|
event: "server-start"
|
|
3263
2999
|
});
|
|
3264
|
-
const { config, startup } = await loadServerSettings({
|
|
3265
|
-
configPath,
|
|
3266
|
-
options
|
|
3267
|
-
});
|
|
3268
|
-
logger.setEnabled(startup.debug);
|
|
3269
3000
|
const { auth, authOrigins, authService, backgroundNotifications, notifications, runtime } = await createApplicationServices({
|
|
3001
|
+
aliases: options.configAliases,
|
|
3270
3002
|
config,
|
|
3003
|
+
configPath,
|
|
3271
3004
|
logger,
|
|
3272
3005
|
resources,
|
|
3273
3006
|
startup
|
|
@@ -3306,7 +3039,7 @@ const startApplicationServer = async ({ onRestartRequested, options }) => {
|
|
|
3306
3039
|
const childOptionsSchema = z.object({
|
|
3307
3040
|
configAliases: z.record(z.string(), z.string()).optional(),
|
|
3308
3041
|
configPath: z.string(),
|
|
3309
|
-
|
|
3042
|
+
logLevel: logLevelSchema.optional(),
|
|
3310
3043
|
developmentWebTarget: z.url().optional(),
|
|
3311
3044
|
host: z.string().optional(),
|
|
3312
3045
|
port: z.number().int().min(0).max(65535).optional(),
|