overmux 0.0.3 → 0.0.5
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 +44 -0
- package/README.md +2 -2
- package/dist/auth/.vite/manifest.json +2 -2
- package/dist/auth/assets/{index-Bx8JhwVi.css → index-BM9gETvx.css} +1 -1
- package/dist/auth/assets/{index-CHIE7qYj.js → index-DabAfcs1.js} +1 -1
- package/dist/auth/index.html +2 -2
- package/dist/bin.js +219 -126
- package/dist/bin.js.map +1 -1
- package/dist/docs/000-index.md +2 -4
- package/dist/docs/100-introduction/200-how-overmux-works.md +126 -2
- package/{docs/100-introduction/300-why-overmux.md → dist/docs/100-introduction/300-why-i-built-overmux.md} +1 -1
- package/dist/docs/200-getting-started/100-install-and-run-overmux.md +2 -2
- package/dist/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +3 -3
- package/dist/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +2 -2
- package/dist/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +2 -2
- package/dist/docs/400-reference/100-project-structure.md +83 -0
- package/dist/docs/400-reference/200-configuration.md +257 -0
- package/dist/docs/400-reference/300-storage-locations.md +46 -0
- package/dist/docs/400-reference/400-authentication-and-security.md +37 -0
- package/dist/docs/400-reference/500-server/000-index.md +13 -0
- package/dist/docs/400-reference/500-server/100-resources.md +191 -0
- package/dist/docs/400-reference/500-server/200-operations.md +111 -0
- package/dist/docs/400-reference/500-server/300-streams.md +140 -0
- package/dist/docs/400-reference/500-server/400-notifications.md +32 -0
- package/dist/docs/400-reference/500-server/500-api.md +118 -0
- package/dist/docs/400-reference/600-client/000-index.md +9 -0
- package/dist/docs/400-reference/600-client/100-api.md +370 -0
- package/dist/docs/400-reference/600-client/200-theming.md +94 -0
- package/dist/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
- package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/050-init.md +2 -4
- package/dist/docs/400-reference/{400-cli → 700-cli}/100-serve.md +9 -4
- package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/200-auth.md +1 -4
- package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/300-call.md +1 -4
- package/dist/docs/400-reference/700-cli/350-instance.md +21 -0
- package/dist/docs/400-reference/700-cli/600-docs.md +128 -0
- package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/700-desktop.md +1 -3
- package/dist/docs/500-hosted-pages.md +0 -1
- package/dist/exports/client.d.ts +19 -15
- package/dist/exports/client.d.ts.map +1 -1
- package/dist/exports/client.js +240 -58
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/{index-D3_Dpy_T.d.ts → index-Cz3xkCa4.d.ts} +62 -36
- package/dist/exports/index-Cz3xkCa4.d.ts.map +1 -0
- package/dist/exports/index.d.ts +2 -2
- package/dist/exports/index.js +19 -5
- package/dist/exports/index.js.map +1 -1
- package/dist/exports/{notifications-kDp16bU_.js → notifications-BFAD3QQl.js} +34 -4
- package/dist/exports/notifications-BFAD3QQl.js.map +1 -0
- package/dist/exports/server.d.ts +1 -44
- package/dist/exports/server.d.ts.map +1 -1
- package/dist/exports/server.js +8 -1018
- package/dist/exports/server.js.map +1 -1
- package/dist/internal/server/coordinator/server-child.js +153 -85
- package/dist/internal/server/coordinator/server-child.js.map +1 -1
- package/docs/000-index.md +2 -4
- package/docs/100-introduction/200-how-overmux-works.md +126 -2
- package/{dist/docs/100-introduction/300-why-overmux.md → docs/100-introduction/300-why-i-built-overmux.md} +1 -1
- package/docs/200-getting-started/100-install-and-run-overmux.md +2 -2
- package/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +3 -3
- package/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +2 -2
- package/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +2 -2
- package/docs/400-reference/100-project-structure.md +83 -0
- package/docs/400-reference/200-configuration.md +257 -0
- package/docs/400-reference/300-storage-locations.md +46 -0
- package/docs/400-reference/400-authentication-and-security.md +37 -0
- package/docs/400-reference/500-server/000-index.md +13 -0
- package/docs/400-reference/500-server/100-resources.md +191 -0
- package/docs/400-reference/500-server/200-operations.md +111 -0
- package/docs/400-reference/500-server/300-streams.md +140 -0
- package/docs/400-reference/500-server/400-notifications.md +32 -0
- package/docs/400-reference/500-server/500-api.md +118 -0
- package/docs/400-reference/600-client/000-index.md +9 -0
- package/docs/400-reference/600-client/100-api.md +370 -0
- package/docs/400-reference/600-client/200-theming.md +94 -0
- package/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
- package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/050-init.md +2 -4
- package/docs/400-reference/{400-cli → 700-cli}/100-serve.md +9 -4
- package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/200-auth.md +1 -4
- package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/300-call.md +1 -4
- package/docs/400-reference/700-cli/350-instance.md +21 -0
- package/docs/400-reference/700-cli/600-docs.md +128 -0
- package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/700-desktop.md +1 -3
- package/docs/500-hosted-pages.md +0 -1
- package/package.json +3 -2
- package/src/internal/cli/app.ts +5 -5
- package/src/internal/cli/commands/docs-ai-context.ts +33 -0
- package/src/internal/cli/commands/docs.ts +19 -2
- package/src/internal/cli/commands/init-template.ts +1 -1
- package/src/internal/cli/commands/instance.ts +58 -0
- package/src/internal/cli/commands/serve.ts +3 -0
- package/src/internal/cli/login.ts +9 -9
- package/src/internal/cli/parse-port.ts +2 -1
- package/src/internal/client/auth/auth-shell.css +17 -1
- package/src/internal/client/auth/auth-shell.tsx +2 -11
- package/src/internal/client/auth/login-code-input.tsx +140 -0
- package/src/internal/client/browser-api.ts +17 -3
- package/src/internal/client/client-definition.ts +6 -8
- package/src/internal/client/host/deep-link-navigation.ts +75 -0
- package/src/internal/client/host/desktop-host.ts +3 -0
- package/src/internal/client/host/environment.ts +67 -0
- package/src/internal/client/host/instance-forwarding.ts +21 -0
- package/src/internal/client/host/overmux-host.tsx +17 -0
- package/src/internal/client/index.ts +0 -6
- package/src/internal/client/overmux-react.ts +95 -42
- package/src/internal/client/websocket.ts +32 -12
- package/src/internal/server/auth/auth-service.ts +2 -2
- package/src/internal/server/auth/instance-control.ts +89 -17
- package/src/internal/server/coordinator/ipc-protocol.ts +0 -1
- package/src/internal/server/runtime/create-runtime.ts +15 -2
- package/src/internal/server/runtime/runtime-instance.ts +47 -0
- package/src/internal/server/runtime/runtime-operations.ts +9 -3
- package/src/internal/server/runtime/runtime-resources.ts +15 -32
- package/src/internal/server/runtime/runtime-streams.ts +5 -6
- package/src/internal/server/server-logger.ts +5 -10
- package/src/internal/server/server-startup-options.ts +14 -14
- package/src/internal/server/start-application-server.ts +24 -19
- package/src/internal/server/websocket-server.ts +7 -0
- package/src/internal/shared/index.ts +2 -0
- package/src/internal/shared/protocol.ts +10 -1
- package/src/public/ai-context.ts +7 -29
- package/src/public/client.ts +1 -6
- package/src/public/config.ts +163 -31
- package/src/public/index.ts +2 -0
- package/src/public/server.ts +1 -16
- package/dist/docs/100-introduction/100-what-is-overmux.md +0 -7
- package/dist/docs/300-fundamentals/100-project-structure.md +0 -23
- package/dist/docs/300-fundamentals/200-configuration.md +0 -3
- package/dist/docs/300-fundamentals/300-theming.md +0 -3
- package/dist/docs/300-fundamentals/400-server.md +0 -3
- package/dist/docs/300-fundamentals/500-client.md +0 -3
- package/dist/docs/300-fundamentals/600-operations.md +0 -3
- package/dist/docs/300-fundamentals/700-resources.md +0 -3
- package/dist/docs/300-fundamentals/800-streams.md +0 -3
- package/dist/docs/300-fundamentals/900-authentication-and-security.md +0 -3
- package/dist/docs/400-reference/100-configuration.md +0 -3
- package/dist/docs/400-reference/200-server-api.md +0 -3
- package/dist/docs/400-reference/300-client-api.md +0 -20
- package/dist/docs/400-reference/400-cli/400-check.md +0 -20
- package/dist/docs/400-reference/400-cli/500-ai-context.md +0 -102
- package/dist/docs/400-reference/400-cli/600-docs.md +0 -23
- package/dist/exports/index-D3_Dpy_T.d.ts.map +0 -1
- package/dist/exports/notifications-kDp16bU_.js.map +0 -1
- package/docs/100-introduction/100-what-is-overmux.md +0 -7
- package/docs/300-fundamentals/100-project-structure.md +0 -23
- package/docs/300-fundamentals/200-configuration.md +0 -3
- package/docs/300-fundamentals/300-theming.md +0 -3
- package/docs/300-fundamentals/400-server.md +0 -3
- package/docs/300-fundamentals/500-client.md +0 -3
- package/docs/300-fundamentals/600-operations.md +0 -3
- package/docs/300-fundamentals/700-resources.md +0 -3
- package/docs/300-fundamentals/800-streams.md +0 -3
- package/docs/300-fundamentals/900-authentication-and-security.md +0 -3
- package/docs/400-reference/100-configuration.md +0 -3
- package/docs/400-reference/200-server-api.md +0 -3
- package/docs/400-reference/300-client-api.md +0 -20
- package/docs/400-reference/400-cli/400-check.md +0 -20
- package/docs/400-reference/400-cli/500-ai-context.md +0 -102
- package/docs/400-reference/400-cli/600-docs.md +0 -23
- package/src/internal/cli/commands/ai.ts +0 -44
package/dist/exports/server.js
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import { realpath } from "node:fs/promises";
|
|
2
1
|
import { isIP } from "node:net";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
|
+
import "jiti";
|
|
4
|
+
import { z } from "zod";
|
|
6
5
|
import { homedir } from "node:os";
|
|
7
|
-
import ts from "typescript";
|
|
8
|
-
import { isDeepStrictEqual } from "node:util";
|
|
9
|
-
import { fork } from "node:child_process";
|
|
10
|
-
import { fileURLToPath } from "node:url";
|
|
11
|
-
import { watch } from "node:fs";
|
|
12
6
|
//#region ../shared/dist/node/index.js
|
|
13
7
|
const durationPattern = /^([1-9]\d*)(ms|s|m|h|d)$/;
|
|
14
8
|
const millisecondsByUnit = {
|
|
@@ -26,6 +20,7 @@ const durationToMilliseconds = (duration) => {
|
|
|
26
20
|
return Number.isSafeInteger(milliseconds) ? milliseconds : void 0;
|
|
27
21
|
};
|
|
28
22
|
const aiContextSnippets = ["package-source", "tech-stack-recommendations"];
|
|
23
|
+
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");
|
|
29
24
|
const zodTypeSchema = z.custom((value) => value instanceof z.ZodType, "Expected a Zod schema");
|
|
30
25
|
const functionSchema = z.custom((value) => typeof value === "function", "Expected a function");
|
|
31
26
|
const contractRuntimeSchema = z.object({
|
|
@@ -74,12 +69,11 @@ const loopbackPeerSchema = z.string().refine((value) => isIP(value) !== 0 && (va
|
|
|
74
69
|
const authConfigRuntimeSchema = z.object({
|
|
75
70
|
mode: z.literal("cli-login"),
|
|
76
71
|
origins: z.array(exactOriginSchema).min(1).refine((origins) => new Set(origins).size === origins.length, "Expected unique origins").optional(),
|
|
77
|
-
sessionLifetime: z.union([z.literal("
|
|
72
|
+
sessionLifetime: z.union([z.literal("forever"), authDurationSchema]).default("forever"),
|
|
78
73
|
trustedProxyPeer: loopbackPeerSchema.optional()
|
|
79
74
|
}).strict();
|
|
80
75
|
const serverConfigRuntimeSchema = z.object({
|
|
81
76
|
host: z.string().min(1).optional(),
|
|
82
|
-
logFile: z.string().min(1).optional(),
|
|
83
77
|
port: z.number().int().min(1).max(65535).optional(),
|
|
84
78
|
productionWebAssetsDir: z.string().min(1).optional(),
|
|
85
79
|
watch: z.boolean().optional()
|
|
@@ -89,51 +83,14 @@ const serverDefinitionRuntimeSchema = z.object({
|
|
|
89
83
|
resources: z.record(z.string(), resourceDefinitionRuntimeSchema),
|
|
90
84
|
streams: z.record(z.string(), streamHandlerRuntimeSchema).optional()
|
|
91
85
|
});
|
|
92
|
-
|
|
86
|
+
serverConfigRuntimeSchema.extend({
|
|
93
87
|
aiContextSnippets: z.array(z.enum(aiContextSnippets)).optional(),
|
|
94
88
|
auth: authConfigRuntimeSchema,
|
|
95
89
|
debug: z.boolean().optional(),
|
|
90
|
+
instanceId: z.union([instanceIdSchema, z.custom((value) => typeof value === "function", "Expected an instance ID or a function of the bound port")]).optional(),
|
|
96
91
|
server: serverDefinitionRuntimeSchema,
|
|
97
92
|
vite: z.string().min(1).optional()
|
|
98
93
|
});
|
|
99
|
-
const resolveLogicalPath = async (path) => {
|
|
100
|
-
const logicalPath = resolve(path);
|
|
101
|
-
return {
|
|
102
|
-
logicalPath,
|
|
103
|
-
realPath: await realpath(logicalPath)
|
|
104
|
-
};
|
|
105
|
-
};
|
|
106
|
-
const resolveOvermuxConfigPaths = async (configPath) => {
|
|
107
|
-
const { logicalPath, realPath } = await resolveLogicalPath(configPath);
|
|
108
|
-
return {
|
|
109
|
-
logicalApplicationRoot: dirname(logicalPath),
|
|
110
|
-
logicalConfigPath: logicalPath,
|
|
111
|
-
realApplicationRoot: dirname(realPath),
|
|
112
|
-
realConfigPath: realPath
|
|
113
|
-
};
|
|
114
|
-
};
|
|
115
|
-
const unwrapDefaultExport = (module) => {
|
|
116
|
-
if (typeof module === "object" && module !== null && "default" in module) return module.default;
|
|
117
|
-
return module;
|
|
118
|
-
};
|
|
119
|
-
const loadOvermuxConfig = async ({ aliases = {}, configPath }) => {
|
|
120
|
-
const paths = await resolveOvermuxConfigPaths(configPath);
|
|
121
|
-
const module = await createJiti(paths.logicalConfigPath, {
|
|
122
|
-
alias: aliases,
|
|
123
|
-
fsCache: true,
|
|
124
|
-
jsx: {
|
|
125
|
-
importSource: "react",
|
|
126
|
-
runtime: "automatic"
|
|
127
|
-
},
|
|
128
|
-
moduleCache: false,
|
|
129
|
-
sourceMaps: true,
|
|
130
|
-
tsconfigPaths: true
|
|
131
|
-
}).import(paths.logicalConfigPath);
|
|
132
|
-
return {
|
|
133
|
-
config: configDefinitionRuntimeSchema.parse(unwrapDefaultExport(module)),
|
|
134
|
-
paths
|
|
135
|
-
};
|
|
136
|
-
};
|
|
137
94
|
const xdgDirectory = (configured, fallback) => configured && isAbsolute(configured) ? configured : fallback;
|
|
138
95
|
const overmuxDirectory = (root) => join(root, "overmux");
|
|
139
96
|
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 } = {}) => {
|
|
@@ -148,973 +105,6 @@ const getOvermuxPaths = ({ environment = process.env, homeDir = homedir(), xdgCa
|
|
|
148
105
|
};
|
|
149
106
|
};
|
|
150
107
|
//#endregion
|
|
151
|
-
|
|
152
|
-
const deliveryKeySchema = z.string().min(1).max(1024).regex(/^[\w-]+$/u);
|
|
153
|
-
const backgroundNotificationSubscriptionSchema = z.object({
|
|
154
|
-
endpoint: z.url().max(4096).refine((endpoint) => {
|
|
155
|
-
const url = new URL(endpoint);
|
|
156
|
-
return url.protocol === "https:" && !url.username && !url.password;
|
|
157
|
-
}),
|
|
158
|
-
expirationTime: z.number().int().nonnegative().nullable(),
|
|
159
|
-
keys: z.object({
|
|
160
|
-
auth: deliveryKeySchema,
|
|
161
|
-
p256dh: deliveryKeySchema
|
|
162
|
-
}).strict()
|
|
163
|
-
}).strict();
|
|
164
|
-
z.object({ publicKey: deliveryKeySchema }).strict();
|
|
165
|
-
z.object({ endpoint: backgroundNotificationSubscriptionSchema.shape.endpoint }).strict();
|
|
166
|
-
const safeRelativeNotificationLink = (link) => {
|
|
167
|
-
let decoded;
|
|
168
|
-
try {
|
|
169
|
-
decoded = decodeURIComponent(link);
|
|
170
|
-
} catch {
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
173
|
-
return !decoded.startsWith("//") && !decoded.includes("\\") && ![...decoded].some((character) => {
|
|
174
|
-
const codePoint = character.codePointAt(0) ?? 0;
|
|
175
|
-
return codePoint < 32 || codePoint === 127;
|
|
176
|
-
});
|
|
177
|
-
};
|
|
178
|
-
const notificationLinkSchema = z.union([z.string().max(4096).startsWith("/").refine(safeRelativeNotificationLink), z.string().max(4096).url().refine((link) => {
|
|
179
|
-
if (!URL.canParse(link)) return false;
|
|
180
|
-
const url = new URL(link);
|
|
181
|
-
return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password;
|
|
182
|
-
})]);
|
|
183
|
-
const notificationSchema = z.object({
|
|
184
|
-
body: z.string().max(4096).optional(),
|
|
185
|
-
open: z.object({ link: notificationLinkSchema }).strict().optional(),
|
|
186
|
-
title: z.string().min(1).max(4096)
|
|
187
|
-
}).strict();
|
|
188
|
-
z.object({
|
|
189
|
-
debug: z.boolean(),
|
|
190
|
-
operations: z.array(z.string().min(1)),
|
|
191
|
-
protocolVersion: z.literal(9),
|
|
192
|
-
resources: z.array(z.string().min(1)),
|
|
193
|
-
streams: z.array(z.string().min(1))
|
|
194
|
-
});
|
|
195
|
-
const updateAvailableEventSchema = z.object({ type: z.literal("update-available") });
|
|
196
|
-
const restartingEventSchema = z.object({ type: z.literal("restarting") });
|
|
197
|
-
z.discriminatedUnion("type", [updateAvailableEventSchema, restartingEventSchema]);
|
|
198
|
-
const notificationEventSchema = z.object({
|
|
199
|
-
notification: notificationSchema,
|
|
200
|
-
type: z.literal("notification")
|
|
201
|
-
}).strict();
|
|
202
|
-
const operationIdSchema = z.string().min(1).max(256);
|
|
203
|
-
const registrationNameSchema = z.string().min(1).max(256);
|
|
204
|
-
const resourceReadMessageSchema = z.object({
|
|
205
|
-
input: z.unknown().optional(),
|
|
206
|
-
operationId: operationIdSchema,
|
|
207
|
-
resourceName: registrationNameSchema,
|
|
208
|
-
type: z.literal("resource-read")
|
|
209
|
-
});
|
|
210
|
-
const resourceSubscribeMessageSchema = z.object({
|
|
211
|
-
input: z.unknown().optional(),
|
|
212
|
-
operationId: operationIdSchema,
|
|
213
|
-
resourceName: registrationNameSchema,
|
|
214
|
-
subscriptionId: operationIdSchema,
|
|
215
|
-
type: z.literal("resource-subscribe")
|
|
216
|
-
});
|
|
217
|
-
const resourceUnsubscribeMessageSchema = z.object({
|
|
218
|
-
subscriptionId: operationIdSchema,
|
|
219
|
-
type: z.literal("resource-unsubscribe")
|
|
220
|
-
});
|
|
221
|
-
const streamOpenMessageSchema = z.object({
|
|
222
|
-
input: z.unknown().optional(),
|
|
223
|
-
operationId: operationIdSchema,
|
|
224
|
-
streamId: operationIdSchema,
|
|
225
|
-
streamName: registrationNameSchema,
|
|
226
|
-
type: z.literal("stream-open")
|
|
227
|
-
});
|
|
228
|
-
const streamMessageSchema = z.object({
|
|
229
|
-
message: z.unknown(),
|
|
230
|
-
streamId: operationIdSchema,
|
|
231
|
-
type: z.literal("stream-message")
|
|
232
|
-
});
|
|
233
|
-
const streamCloseMessageSchema = z.object({
|
|
234
|
-
streamId: operationIdSchema,
|
|
235
|
-
type: z.literal("stream-close")
|
|
236
|
-
});
|
|
237
|
-
const clientDiagnosticEventSchema = z.object({
|
|
238
|
-
arguments: z.string().max(16100),
|
|
239
|
-
level: z.enum([
|
|
240
|
-
"log",
|
|
241
|
-
"info",
|
|
242
|
-
"warn",
|
|
243
|
-
"error",
|
|
244
|
-
"debug"
|
|
245
|
-
]),
|
|
246
|
-
message: z.string().max(4100),
|
|
247
|
-
stack: z.string().max(8100).optional(),
|
|
248
|
-
timestamp: z.iso.datetime(),
|
|
249
|
-
type: z.literal("client-diagnostic"),
|
|
250
|
-
url: z.string().max(2100).optional()
|
|
251
|
-
});
|
|
252
|
-
z.discriminatedUnion("type", [
|
|
253
|
-
resourceReadMessageSchema,
|
|
254
|
-
resourceSubscribeMessageSchema,
|
|
255
|
-
resourceUnsubscribeMessageSchema,
|
|
256
|
-
streamOpenMessageSchema,
|
|
257
|
-
streamMessageSchema,
|
|
258
|
-
streamCloseMessageSchema,
|
|
259
|
-
clientDiagnosticEventSchema
|
|
260
|
-
]);
|
|
261
|
-
const resourceResultMessageSchema = z.object({
|
|
262
|
-
operationId: operationIdSchema,
|
|
263
|
-
output: z.unknown(),
|
|
264
|
-
type: z.literal("resource-result")
|
|
265
|
-
});
|
|
266
|
-
const resourceInvalidatedMessageSchema = z.object({
|
|
267
|
-
subscriptionId: operationIdSchema,
|
|
268
|
-
type: z.literal("resource-invalidated")
|
|
269
|
-
});
|
|
270
|
-
const streamOpenedMessageSchema = z.object({
|
|
271
|
-
operationId: operationIdSchema,
|
|
272
|
-
streamId: operationIdSchema,
|
|
273
|
-
type: z.literal("stream-opened")
|
|
274
|
-
});
|
|
275
|
-
const streamOutputMessageSchema = z.object({
|
|
276
|
-
message: z.unknown(),
|
|
277
|
-
streamId: operationIdSchema,
|
|
278
|
-
type: z.literal("stream-output")
|
|
279
|
-
});
|
|
280
|
-
const streamClosedMessageSchema = z.object({
|
|
281
|
-
streamId: operationIdSchema,
|
|
282
|
-
type: z.literal("stream-closed")
|
|
283
|
-
});
|
|
284
|
-
const protocolErrorMessageSchema = z.object({
|
|
285
|
-
code: z.enum([
|
|
286
|
-
"bad-request",
|
|
287
|
-
"conflict",
|
|
288
|
-
"internal",
|
|
289
|
-
"not-found"
|
|
290
|
-
]),
|
|
291
|
-
message: z.string(),
|
|
292
|
-
operationId: operationIdSchema.optional(),
|
|
293
|
-
streamId: operationIdSchema.optional(),
|
|
294
|
-
subscriptionId: operationIdSchema.optional(),
|
|
295
|
-
type: z.literal("error")
|
|
296
|
-
});
|
|
297
|
-
z.discriminatedUnion("type", [
|
|
298
|
-
resourceResultMessageSchema,
|
|
299
|
-
resourceInvalidatedMessageSchema,
|
|
300
|
-
streamOpenedMessageSchema,
|
|
301
|
-
streamOutputMessageSchema,
|
|
302
|
-
streamClosedMessageSchema,
|
|
303
|
-
protocolErrorMessageSchema,
|
|
304
|
-
updateAvailableEventSchema,
|
|
305
|
-
restartingEventSchema,
|
|
306
|
-
notificationEventSchema
|
|
307
|
-
]);
|
|
308
|
-
//#endregion
|
|
309
|
-
//#region src/internal/server/runtime/runtime-lifecycle.ts
|
|
310
|
-
const asError = (cause) => cause instanceof Error ? cause : new Error(String(cause));
|
|
311
|
-
const disposeAll = async (disposers) => {
|
|
312
|
-
const errors = (await Promise.allSettled([...disposers].map(async (dispose) => dispose()))).flatMap((result) => result.status === "rejected" ? [asError(result.reason)] : []);
|
|
313
|
-
if (errors.length) throw new AggregateError(errors, "Runtime disposal failed");
|
|
314
|
-
};
|
|
315
|
-
const createRuntimeLifecycle = (preparationSignal) => {
|
|
316
|
-
const controller = new AbortController();
|
|
317
|
-
const activeDisposers = /* @__PURE__ */ new Set();
|
|
318
|
-
let disposal;
|
|
319
|
-
const abortPreparation = () => controller.abort(preparationSignal?.reason);
|
|
320
|
-
preparationSignal?.addEventListener("abort", abortPreparation, { once: true });
|
|
321
|
-
if (preparationSignal?.aborted) abortPreparation();
|
|
322
|
-
const completePreparation = () => {
|
|
323
|
-
preparationSignal?.removeEventListener("abort", abortPreparation);
|
|
324
|
-
};
|
|
325
|
-
const requestSignal = (signal) => signal ? AbortSignal.any([controller.signal, signal]) : controller.signal;
|
|
326
|
-
const track = (dispose, signal) => {
|
|
327
|
-
let pending;
|
|
328
|
-
const disposeOnce = () => {
|
|
329
|
-
if (pending) return pending;
|
|
330
|
-
controller.signal.removeEventListener("abort", onAbort);
|
|
331
|
-
signal?.removeEventListener("abort", onAbort);
|
|
332
|
-
activeDisposers.delete(disposeOnce);
|
|
333
|
-
pending = Promise.resolve().then(dispose);
|
|
334
|
-
return pending;
|
|
335
|
-
};
|
|
336
|
-
const onAbort = () => {
|
|
337
|
-
disposeOnce().catch(() => void 0);
|
|
338
|
-
};
|
|
339
|
-
activeDisposers.add(disposeOnce);
|
|
340
|
-
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
341
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
342
|
-
if (controller.signal.aborted || signal?.aborted) onAbort();
|
|
343
|
-
return disposeOnce;
|
|
344
|
-
};
|
|
345
|
-
const dispose = (reason = /* @__PURE__ */ new Error("Runtime disposed")) => {
|
|
346
|
-
if (disposal) return disposal;
|
|
347
|
-
completePreparation();
|
|
348
|
-
const pending = [...activeDisposers];
|
|
349
|
-
activeDisposers.clear();
|
|
350
|
-
controller.abort(reason);
|
|
351
|
-
disposal = disposeAll(pending);
|
|
352
|
-
return disposal;
|
|
353
|
-
};
|
|
354
|
-
return {
|
|
355
|
-
completePreparation,
|
|
356
|
-
dispose,
|
|
357
|
-
disposeAll,
|
|
358
|
-
requestSignal,
|
|
359
|
-
signal: controller.signal,
|
|
360
|
-
track
|
|
361
|
-
};
|
|
362
|
-
};
|
|
363
|
-
//#endregion
|
|
364
|
-
//#region src/internal/server/runtime/runtime-operations.ts
|
|
365
|
-
var OperationValidationError = class extends Error {
|
|
366
|
-
phase;
|
|
367
|
-
constructor(phase, cause) {
|
|
368
|
-
super(cause.message, { cause });
|
|
369
|
-
this.phase = phase;
|
|
370
|
-
}
|
|
371
|
-
};
|
|
372
|
-
const createRuntimeOperations = ({ context, definitions, notifications }) => {
|
|
373
|
-
const operations = /* @__PURE__ */ new Map();
|
|
374
|
-
Object.entries(definitions).forEach(([name, definition]) => {
|
|
375
|
-
if (!name) throw new Error("Operation names must not be empty");
|
|
376
|
-
operations.set(name, {
|
|
377
|
-
execute: async (rawInput, signal) => {
|
|
378
|
-
const operationContext = context(signal);
|
|
379
|
-
operationContext.signal.throwIfAborted();
|
|
380
|
-
const input = definition.input.safeParse(rawInput);
|
|
381
|
-
if (!input.success) throw new OperationValidationError("input", input.error);
|
|
382
|
-
const rawOutput = await definition.handle(input.data, {
|
|
383
|
-
...operationContext,
|
|
384
|
-
notifications
|
|
385
|
-
});
|
|
386
|
-
operationContext.signal.throwIfAborted();
|
|
387
|
-
const output = definition.output.safeParse(rawOutput);
|
|
388
|
-
if (!output.success) throw new OperationValidationError("output", output.error);
|
|
389
|
-
return output.data;
|
|
390
|
-
},
|
|
391
|
-
returnsVoid: definition.output instanceof z.ZodVoid
|
|
392
|
-
});
|
|
393
|
-
});
|
|
394
|
-
return {
|
|
395
|
-
get: (name) => operations.get(name),
|
|
396
|
-
names: [...operations.keys()]
|
|
397
|
-
};
|
|
398
|
-
};
|
|
399
|
-
z.void();
|
|
400
|
-
const jsonValueSchema = z.lazy(() => z.union([
|
|
401
|
-
z.string(),
|
|
402
|
-
z.number().finite(),
|
|
403
|
-
z.boolean(),
|
|
404
|
-
z.null(),
|
|
405
|
-
z.array(jsonValueSchema),
|
|
406
|
-
z.record(z.string(), jsonValueSchema)
|
|
407
|
-
]));
|
|
408
|
-
//#endregion
|
|
409
|
-
//#region src/internal/server/runtime/resource-dependency-graph.ts
|
|
410
|
-
const emptyGraphFor = (resourceIds) => new Map(resourceIds.map((resourceId) => [resourceId, /* @__PURE__ */ new Set()]));
|
|
411
|
-
const visitDependencies = ({ dependencies, path, resourceId, visited, visiting }) => {
|
|
412
|
-
if (visiting.has(resourceId)) throw new Error(`Resource dependency cycle: ${[...path, resourceId].join(" -> ")}`);
|
|
413
|
-
if (visited.has(resourceId)) return;
|
|
414
|
-
visiting.add(resourceId);
|
|
415
|
-
dependencies.get(resourceId)?.forEach((dependencyId) => {
|
|
416
|
-
visitDependencies({
|
|
417
|
-
dependencies,
|
|
418
|
-
path: [...path, resourceId],
|
|
419
|
-
resourceId: dependencyId,
|
|
420
|
-
visited,
|
|
421
|
-
visiting
|
|
422
|
-
});
|
|
423
|
-
});
|
|
424
|
-
visiting.delete(resourceId);
|
|
425
|
-
visited.add(resourceId);
|
|
426
|
-
};
|
|
427
|
-
const assertAcyclic = (dependencies) => {
|
|
428
|
-
const visited = /* @__PURE__ */ new Set();
|
|
429
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
430
|
-
dependencies.forEach((_, resourceId) => {
|
|
431
|
-
visitDependencies({
|
|
432
|
-
dependencies,
|
|
433
|
-
path: [],
|
|
434
|
-
resourceId,
|
|
435
|
-
visited,
|
|
436
|
-
visiting
|
|
437
|
-
});
|
|
438
|
-
});
|
|
439
|
-
};
|
|
440
|
-
const createResourceDependencyGraph = (resources) => {
|
|
441
|
-
const resourceIds = Object.keys(resources);
|
|
442
|
-
if (resourceIds.some((resourceId) => !resourceId)) throw new Error("Resource IDs must not be empty");
|
|
443
|
-
const dependencies = emptyGraphFor(resourceIds);
|
|
444
|
-
const dependants = emptyGraphFor(resourceIds);
|
|
445
|
-
Object.entries(resources).forEach(([resourceId, resource]) => {
|
|
446
|
-
if (resource.kind !== "derived") return;
|
|
447
|
-
Object.entries(resource.dependencies).forEach(([dependencyName, dependencyId]) => {
|
|
448
|
-
if (!dependencyName) throw new Error(`Resource ${resourceId} has an empty dependency name`);
|
|
449
|
-
if (!dependencyId) throw new Error(`Resource ${resourceId} has an empty dependency ID`);
|
|
450
|
-
if (!dependencies.has(dependencyId)) throw new Error(`Unknown resource dependency: ${dependencyId}`);
|
|
451
|
-
dependencies.get(resourceId)?.add(dependencyId);
|
|
452
|
-
dependants.get(dependencyId)?.add(resourceId);
|
|
453
|
-
});
|
|
454
|
-
});
|
|
455
|
-
assertAcyclic(dependencies);
|
|
456
|
-
return Object.freeze({
|
|
457
|
-
dependencies,
|
|
458
|
-
dependants
|
|
459
|
-
});
|
|
460
|
-
};
|
|
461
|
-
const collectInvalidatedResourceIds = (graph, resourceId, invalidated) => {
|
|
462
|
-
if (invalidated.has(resourceId)) return;
|
|
463
|
-
invalidated.add(resourceId);
|
|
464
|
-
graph.dependants.get(resourceId)?.forEach((dependantId) => {
|
|
465
|
-
collectInvalidatedResourceIds(graph, dependantId, invalidated);
|
|
466
|
-
});
|
|
467
|
-
};
|
|
468
|
-
const getInvalidatedResourceIds = (graph, resourceId) => {
|
|
469
|
-
const invalidated = /* @__PURE__ */ new Set();
|
|
470
|
-
collectInvalidatedResourceIds(graph, resourceId, invalidated);
|
|
471
|
-
return invalidated;
|
|
472
|
-
};
|
|
473
|
-
//#endregion
|
|
474
|
-
//#region src/internal/server/runtime/runtime-resources.ts
|
|
475
|
-
const createRuntimeResources = ({ definitions, lifecycle }) => {
|
|
476
|
-
const graph = createResourceDependencyGraph(definitions);
|
|
477
|
-
const invalidationListeners = /* @__PURE__ */ new Set();
|
|
478
|
-
const contracts = /* @__PURE__ */ new Map();
|
|
479
|
-
const resources = /* @__PURE__ */ new Map();
|
|
480
|
-
Object.entries(definitions).forEach(([id, definition]) => {
|
|
481
|
-
contracts.set(definition.contract, [...contracts.get(definition.contract) ?? [], id]);
|
|
482
|
-
});
|
|
483
|
-
const invalidateResource = (id, input, allInputs = false) => {
|
|
484
|
-
if (lifecycle.signal.aborted) return;
|
|
485
|
-
const ids = getInvalidatedResourceIds(graph, id);
|
|
486
|
-
invalidationListeners.forEach((entry) => {
|
|
487
|
-
if (ids.has(entry.resourceId) && (allInputs || isDeepStrictEqual(entry.input, input))) entry.listener();
|
|
488
|
-
});
|
|
489
|
-
};
|
|
490
|
-
const invalidate = (contract, input) => {
|
|
491
|
-
const ids = contracts.get(contract) ?? Object.entries(definitions).filter(([, definition]) => definition.contract.input === contract.input && definition.contract.output === contract.output).map(([id]) => id);
|
|
492
|
-
if (!ids.length) throw new Error("Cannot invalidate a resource outside this runtime");
|
|
493
|
-
ids.forEach((id) => {
|
|
494
|
-
const definition = definitions[id];
|
|
495
|
-
const parsedInput = input === void 0 ? void 0 : definition.contract.input.parse(input);
|
|
496
|
-
invalidateResource(id, parsedInput, input === void 0);
|
|
497
|
-
});
|
|
498
|
-
};
|
|
499
|
-
const context = (signal) => ({
|
|
500
|
-
invalidate,
|
|
501
|
-
signal: lifecycle.requestSignal(signal)
|
|
502
|
-
});
|
|
503
|
-
const read = async (id, rawInput, signal) => {
|
|
504
|
-
const definition = definitions[id];
|
|
505
|
-
const operationSignal = lifecycle.requestSignal(signal);
|
|
506
|
-
operationSignal.throwIfAborted();
|
|
507
|
-
const input = definition.contract.input.parse(rawInput);
|
|
508
|
-
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));
|
|
509
|
-
operationSignal.throwIfAborted();
|
|
510
|
-
return definition.contract.output.parse(rawOutput);
|
|
511
|
-
};
|
|
512
|
-
const activateWatchers = (id, rawInput, signal, activated, disposers) => {
|
|
513
|
-
if (activated.has(id)) return;
|
|
514
|
-
activated.add(id);
|
|
515
|
-
const definition = definitions[id];
|
|
516
|
-
const input = definition.contract.input.parse(rawInput);
|
|
517
|
-
if (definition.kind === "subscription") {
|
|
518
|
-
const dispose = definition.subscribe(input, () => invalidateResource(id, input), context(signal));
|
|
519
|
-
if (typeof dispose !== "function") throw new Error(`Resource ${id} subscribe must return a disposer`);
|
|
520
|
-
disposers.push(dispose);
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
if (definition.kind === "derived") graph.dependencies.get(id)?.forEach((dependencyId) => {
|
|
524
|
-
activateWatchers(dependencyId, rawInput, signal, activated, disposers);
|
|
525
|
-
});
|
|
526
|
-
};
|
|
527
|
-
Object.keys(definitions).forEach((name) => {
|
|
528
|
-
resources.set(name, {
|
|
529
|
-
read: (input, signal) => read(name, input, signal),
|
|
530
|
-
subscribe: (rawInput, listener, signal) => {
|
|
531
|
-
lifecycle.requestSignal(signal).throwIfAborted();
|
|
532
|
-
const entry = {
|
|
533
|
-
input: definitions[name].contract.input.parse(rawInput),
|
|
534
|
-
listener,
|
|
535
|
-
resourceId: name
|
|
536
|
-
};
|
|
537
|
-
const watcherDisposers = [];
|
|
538
|
-
invalidationListeners.add(entry);
|
|
539
|
-
try {
|
|
540
|
-
activateWatchers(name, rawInput, signal, /* @__PURE__ */ new Set(), watcherDisposers);
|
|
541
|
-
} catch (cause) {
|
|
542
|
-
invalidationListeners.delete(entry);
|
|
543
|
-
lifecycle.disposeAll(watcherDisposers).catch(() => void 0);
|
|
544
|
-
throw cause;
|
|
545
|
-
}
|
|
546
|
-
return lifecycle.track(async () => {
|
|
547
|
-
invalidationListeners.delete(entry);
|
|
548
|
-
await lifecycle.disposeAll(watcherDisposers);
|
|
549
|
-
}, signal);
|
|
550
|
-
}
|
|
551
|
-
});
|
|
552
|
-
});
|
|
553
|
-
return {
|
|
554
|
-
context,
|
|
555
|
-
get: (name) => resources.get(name),
|
|
556
|
-
invalidate,
|
|
557
|
-
names: [...resources.keys()]
|
|
558
|
-
};
|
|
559
|
-
};
|
|
560
|
-
//#endregion
|
|
561
|
-
//#region src/internal/server/runtime/runtime-streams.ts
|
|
562
|
-
const createRuntimeStreams = ({ context, definitions, lifecycle }) => {
|
|
563
|
-
const streams = /* @__PURE__ */ new Map();
|
|
564
|
-
Object.entries(definitions).forEach(([name, definition]) => {
|
|
565
|
-
if (!name) throw new Error("Stream names must not be empty");
|
|
566
|
-
streams.set(name, { open: async (rawInput, emit, requestSignal, onError) => {
|
|
567
|
-
const sessionController = new AbortController();
|
|
568
|
-
const sessionSignal = requestSignal ? AbortSignal.any([requestSignal, sessionController.signal]) : sessionController.signal;
|
|
569
|
-
const operationSignal = lifecycle.requestSignal(sessionSignal);
|
|
570
|
-
operationSignal.throwIfAborted();
|
|
571
|
-
const input = definition.contract.input.parse(rawInput);
|
|
572
|
-
let acceptingMessages = true;
|
|
573
|
-
let failed = false;
|
|
574
|
-
let failure;
|
|
575
|
-
let opened = false;
|
|
576
|
-
const fail = (cause) => {
|
|
577
|
-
if (failed || operationSignal.aborted) return;
|
|
578
|
-
failed = true;
|
|
579
|
-
failure = cause;
|
|
580
|
-
acceptingMessages = false;
|
|
581
|
-
sessionController.abort(cause);
|
|
582
|
-
if (opened) onError?.(cause);
|
|
583
|
-
};
|
|
584
|
-
const session = await definition.open(input, {
|
|
585
|
-
...context(sessionSignal),
|
|
586
|
-
emit: (message) => {
|
|
587
|
-
if (operationSignal.aborted) return;
|
|
588
|
-
try {
|
|
589
|
-
emit(definition.contract.serverMessage.parse(message));
|
|
590
|
-
} catch (cause) {
|
|
591
|
-
fail(cause);
|
|
592
|
-
}
|
|
593
|
-
},
|
|
594
|
-
fail,
|
|
595
|
-
signal: operationSignal
|
|
596
|
-
});
|
|
597
|
-
opened = true;
|
|
598
|
-
if (failed || operationSignal.aborted) {
|
|
599
|
-
await session.dispose?.();
|
|
600
|
-
operationSignal.throwIfAborted();
|
|
601
|
-
throw failure;
|
|
602
|
-
}
|
|
603
|
-
let messages = Promise.resolve();
|
|
604
|
-
const dispose = lifecycle.track(async () => {
|
|
605
|
-
acceptingMessages = false;
|
|
606
|
-
sessionController.abort(/* @__PURE__ */ new Error("Stream session disposed"));
|
|
607
|
-
await messages.catch(() => void 0);
|
|
608
|
-
await session.dispose?.();
|
|
609
|
-
}, requestSignal);
|
|
610
|
-
return {
|
|
611
|
-
dispose: async () => dispose(),
|
|
612
|
-
send: async (rawMessage) => {
|
|
613
|
-
if (!acceptingMessages) throw new Error("Stream session is closed");
|
|
614
|
-
const message = definition.contract.clientMessage.parse(rawMessage);
|
|
615
|
-
messages = messages.then(async () => {
|
|
616
|
-
operationSignal.throwIfAborted();
|
|
617
|
-
await session.onMessage?.(message);
|
|
618
|
-
operationSignal.throwIfAborted();
|
|
619
|
-
});
|
|
620
|
-
return messages;
|
|
621
|
-
}
|
|
622
|
-
};
|
|
623
|
-
} });
|
|
624
|
-
});
|
|
625
|
-
return {
|
|
626
|
-
get: (name) => streams.get(name),
|
|
627
|
-
names: [...streams.keys()]
|
|
628
|
-
};
|
|
629
|
-
};
|
|
630
|
-
//#endregion
|
|
631
|
-
//#region src/internal/server/runtime/create-runtime.ts
|
|
632
|
-
const createRuntime = async ({ config, debug = false, notifications = { send: async () => void 0 }, signal }) => {
|
|
633
|
-
const lifecycle = createRuntimeLifecycle(signal);
|
|
634
|
-
try {
|
|
635
|
-
const resources = createRuntimeResources({
|
|
636
|
-
definitions: config.server.resources ?? {},
|
|
637
|
-
lifecycle
|
|
638
|
-
});
|
|
639
|
-
const context = resources.context;
|
|
640
|
-
const operations = createRuntimeOperations({
|
|
641
|
-
context,
|
|
642
|
-
definitions: config.server.operations ?? {},
|
|
643
|
-
notifications
|
|
644
|
-
});
|
|
645
|
-
const streams = createRuntimeStreams({
|
|
646
|
-
context,
|
|
647
|
-
definitions: config.server.streams ?? {},
|
|
648
|
-
lifecycle
|
|
649
|
-
});
|
|
650
|
-
lifecycle.signal.throwIfAborted();
|
|
651
|
-
lifecycle.completePreparation();
|
|
652
|
-
return {
|
|
653
|
-
manifest: {
|
|
654
|
-
debug,
|
|
655
|
-
operations: [...operations.names],
|
|
656
|
-
protocolVersion: 9,
|
|
657
|
-
resources: [...resources.names],
|
|
658
|
-
streams: [...streams.names]
|
|
659
|
-
},
|
|
660
|
-
dispose: lifecycle.dispose,
|
|
661
|
-
getOperation: operations.get,
|
|
662
|
-
getResource: resources.get,
|
|
663
|
-
getStream: streams.get
|
|
664
|
-
};
|
|
665
|
-
} catch (cause) {
|
|
666
|
-
await lifecycle.dispose(cause).catch(() => void 0);
|
|
667
|
-
throw cause;
|
|
668
|
-
}
|
|
669
|
-
};
|
|
670
|
-
//#endregion
|
|
671
|
-
//#region src/internal/server/check-overmux.ts
|
|
672
|
-
const loadCompilerOptions = (configPath) => {
|
|
673
|
-
const tsconfig = ts.findConfigFile(dirname(configPath), ts.sys.fileExists);
|
|
674
|
-
if (!tsconfig) return {
|
|
675
|
-
allowImportingTsExtensions: true,
|
|
676
|
-
allowJs: false,
|
|
677
|
-
esModuleInterop: true,
|
|
678
|
-
jsx: ts.JsxEmit.ReactJSX,
|
|
679
|
-
module: ts.ModuleKind.ESNext,
|
|
680
|
-
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
681
|
-
noEmit: true,
|
|
682
|
-
skipLibCheck: true,
|
|
683
|
-
strict: true,
|
|
684
|
-
target: ts.ScriptTarget.ES2022
|
|
685
|
-
};
|
|
686
|
-
const loaded = ts.readConfigFile(tsconfig, ts.sys.readFile);
|
|
687
|
-
if (loaded.error) throw new Error(ts.flattenDiagnosticMessageText(loaded.error.messageText, "\n"));
|
|
688
|
-
return {
|
|
689
|
-
...ts.parseJsonConfigFileContent(loaded.config, ts.sys, dirname(tsconfig)).options,
|
|
690
|
-
allowImportingTsExtensions: true,
|
|
691
|
-
noEmit: true
|
|
692
|
-
};
|
|
693
|
-
};
|
|
694
|
-
const diagnosticOf = (diagnostic) => {
|
|
695
|
-
const position = diagnostic.file && diagnostic.start !== void 0 ? diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start) : void 0;
|
|
696
|
-
return {
|
|
697
|
-
code: diagnostic.code,
|
|
698
|
-
column: position ? position.character + 1 : void 0,
|
|
699
|
-
file: diagnostic.file?.fileName,
|
|
700
|
-
line: position ? position.line + 1 : void 0,
|
|
701
|
-
message: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
|
|
702
|
-
phase: "types"
|
|
703
|
-
};
|
|
704
|
-
};
|
|
705
|
-
const kindOf = (fileName, configPath) => {
|
|
706
|
-
if (resolve(fileName) === resolve(configPath) || fileName.match(/\.server\.[cm]?[jt]sx?$/)) return "server";
|
|
707
|
-
if (fileName.match(/\.ui\.[cm]?[jt]sx?$/)) return "client";
|
|
708
|
-
if (fileName.match(/\.shared\.[cm]?[jt]sx?$/)) return "shared";
|
|
709
|
-
};
|
|
710
|
-
const kindOfSpecifier = (specifier) => {
|
|
711
|
-
if (specifier === "overmux/client" || specifier.endsWith("/react")) return "client";
|
|
712
|
-
if (specifier === "overmux/server" || specifier.endsWith("/server") || specifier.startsWith("node:")) return "server";
|
|
713
|
-
if (specifier === "overmux" || specifier.endsWith("/shared")) return "shared";
|
|
714
|
-
};
|
|
715
|
-
const isTypeOnlyReference = (node) => {
|
|
716
|
-
if (ts.isExportDeclaration(node)) return Boolean(node.isTypeOnly || node.exportClause && ts.isNamedExports(node.exportClause) && node.exportClause.elements.every((element) => element.isTypeOnly));
|
|
717
|
-
const clause = node.importClause;
|
|
718
|
-
if (!clause || clause.name) return Boolean(clause?.isTypeOnly);
|
|
719
|
-
return Boolean(clause.isTypeOnly || clause.namedBindings && ts.isNamedImports(clause.namedBindings) && clause.namedBindings.elements.every((element) => element.isTypeOnly));
|
|
720
|
-
};
|
|
721
|
-
const resolvedImports = (program, source) => {
|
|
722
|
-
const imports = [];
|
|
723
|
-
source.forEachChild((node) => {
|
|
724
|
-
if (!ts.isImportDeclaration(node) && !ts.isExportDeclaration(node) || !node.moduleSpecifier || !ts.isStringLiteral(node.moduleSpecifier) || isTypeOnlyReference(node)) return;
|
|
725
|
-
const specifier = node.moduleSpecifier.text;
|
|
726
|
-
const resolved = ts.resolveModuleName(specifier, source.fileName, program.getCompilerOptions(), ts.sys).resolvedModule?.resolvedFileName;
|
|
727
|
-
const target = resolved ? program.getSourceFile(resolved) : void 0;
|
|
728
|
-
const kind = kindOfSpecifier(specifier);
|
|
729
|
-
if (kind || target && !target.isDeclarationFile) imports.push({
|
|
730
|
-
kind,
|
|
731
|
-
node,
|
|
732
|
-
source: target?.isDeclarationFile ? void 0 : target
|
|
733
|
-
});
|
|
734
|
-
});
|
|
735
|
-
return imports;
|
|
736
|
-
};
|
|
737
|
-
const invalidImport = (source, target) => source === "server" && target === "client" || source === "client" && target === "server" || source === "shared" && target !== void 0 && target !== "shared";
|
|
738
|
-
const visitImportGraph = ({ configPath, diagnostics, kind, program, seen, source }) => {
|
|
739
|
-
const visitKey = `${kind}:${source.fileName}`;
|
|
740
|
-
if (seen.has(visitKey)) return;
|
|
741
|
-
seen.add(visitKey);
|
|
742
|
-
resolvedImports(program, source).forEach(({ kind: importKind, node, source: target }) => {
|
|
743
|
-
const declaredKind = importKind ?? (target ? kindOf(target.fileName, configPath) : void 0);
|
|
744
|
-
if (invalidImport(kind, declaredKind)) {
|
|
745
|
-
const position = source.getLineAndCharacterOfPosition(node.getStart());
|
|
746
|
-
diagnostics.push({
|
|
747
|
-
code: "OVERMUX_IMPORT_BOUNDARY",
|
|
748
|
-
column: position.character + 1,
|
|
749
|
-
file: source.fileName,
|
|
750
|
-
line: position.line + 1,
|
|
751
|
-
message: `${kind} modules may not import ${declaredKind} modules`,
|
|
752
|
-
phase: "imports"
|
|
753
|
-
});
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
if (target) visitImportGraph({
|
|
757
|
-
configPath,
|
|
758
|
-
diagnostics,
|
|
759
|
-
kind: declaredKind === "shared" ? "shared" : kind,
|
|
760
|
-
program,
|
|
761
|
-
seen,
|
|
762
|
-
source: target
|
|
763
|
-
});
|
|
764
|
-
});
|
|
765
|
-
};
|
|
766
|
-
const importBoundaryDiagnostics = (program, configPath) => {
|
|
767
|
-
const diagnostics = [];
|
|
768
|
-
const seen = /* @__PURE__ */ new Set();
|
|
769
|
-
program.getSourceFiles().forEach((source) => {
|
|
770
|
-
const kind = kindOf(source.fileName, configPath);
|
|
771
|
-
if (kind && !source.isDeclarationFile) visitImportGraph({
|
|
772
|
-
configPath,
|
|
773
|
-
diagnostics,
|
|
774
|
-
kind,
|
|
775
|
-
program,
|
|
776
|
-
seen,
|
|
777
|
-
source
|
|
778
|
-
});
|
|
779
|
-
});
|
|
780
|
-
return diagnostics;
|
|
781
|
-
};
|
|
782
|
-
const runtimeDiagnostics = (cause) => {
|
|
783
|
-
if (cause instanceof ZodError) return cause.issues.map((issue) => ({
|
|
784
|
-
message: issue.message,
|
|
785
|
-
path: issue.path.length ? issue.path.join(".") : void 0,
|
|
786
|
-
phase: "runtime"
|
|
787
|
-
}));
|
|
788
|
-
return [{
|
|
789
|
-
message: cause instanceof Error ? cause.message : String(cause),
|
|
790
|
-
phase: "runtime"
|
|
791
|
-
}];
|
|
792
|
-
};
|
|
793
|
-
const typecheck = async ({ aliases = {}, configPath, typeAliases }) => {
|
|
794
|
-
const options = loadCompilerOptions(configPath);
|
|
795
|
-
const aliasesForTypes = typeAliases ?? Object.fromEntries(Object.entries(aliases).filter(([key]) => key === "overmux" || key.startsWith("overmux/")));
|
|
796
|
-
const absoluteAliases = Object.fromEntries(Object.entries(aliasesForTypes).map(([key, value]) => [key, [isAbsolute(value) ? value : resolve(value)]]));
|
|
797
|
-
const program = ts.createProgram({
|
|
798
|
-
options: {
|
|
799
|
-
...options,
|
|
800
|
-
baseUrl: options.baseUrl ?? "/",
|
|
801
|
-
paths: {
|
|
802
|
-
...options.paths,
|
|
803
|
-
...absoluteAliases
|
|
804
|
-
}
|
|
805
|
-
},
|
|
806
|
-
rootNames: [configPath]
|
|
807
|
-
});
|
|
808
|
-
return {
|
|
809
|
-
diagnostics: [...ts.getPreEmitDiagnostics(program).map(diagnosticOf), ...importBoundaryDiagnostics(program, configPath)],
|
|
810
|
-
files: program.getSourceFiles().filter((file) => !file.isDeclarationFile && !file.fileName.includes("node_modules")).length
|
|
811
|
-
};
|
|
812
|
-
};
|
|
813
|
-
const checkOvermux = async (options) => {
|
|
814
|
-
const configPath = resolve(options.configPath);
|
|
815
|
-
let checked;
|
|
816
|
-
try {
|
|
817
|
-
checked = await typecheck({
|
|
818
|
-
...options,
|
|
819
|
-
configPath
|
|
820
|
-
});
|
|
821
|
-
} catch (cause) {
|
|
822
|
-
return {
|
|
823
|
-
config: configPath,
|
|
824
|
-
diagnostics: [{
|
|
825
|
-
message: cause instanceof Error ? cause.message : String(cause),
|
|
826
|
-
phase: "config"
|
|
827
|
-
}],
|
|
828
|
-
files: 0,
|
|
829
|
-
ok: false
|
|
830
|
-
};
|
|
831
|
-
}
|
|
832
|
-
if (checked.diagnostics.length || options.typesOnly) return {
|
|
833
|
-
config: configPath,
|
|
834
|
-
diagnostics: checked.diagnostics,
|
|
835
|
-
files: checked.files,
|
|
836
|
-
ok: checked.diagnostics.length === 0
|
|
837
|
-
};
|
|
838
|
-
let runtime;
|
|
839
|
-
try {
|
|
840
|
-
const { config } = await loadOvermuxConfig({
|
|
841
|
-
aliases: options.aliases,
|
|
842
|
-
configPath
|
|
843
|
-
});
|
|
844
|
-
runtime = await createRuntime({ config });
|
|
845
|
-
return {
|
|
846
|
-
operations: runtime.manifest.operations.length,
|
|
847
|
-
config: configPath,
|
|
848
|
-
diagnostics: [],
|
|
849
|
-
files: checked.files,
|
|
850
|
-
ok: true
|
|
851
|
-
};
|
|
852
|
-
} catch (cause) {
|
|
853
|
-
return {
|
|
854
|
-
config: configPath,
|
|
855
|
-
diagnostics: runtimeDiagnostics(cause),
|
|
856
|
-
files: checked.files,
|
|
857
|
-
ok: false
|
|
858
|
-
};
|
|
859
|
-
} finally {
|
|
860
|
-
await runtime?.dispose();
|
|
861
|
-
}
|
|
862
|
-
};
|
|
863
|
-
//#endregion
|
|
864
|
-
//#region src/internal/server/server-logger.ts
|
|
865
|
-
const errorMessage = (cause) => cause instanceof Error ? cause.message : String(cause);
|
|
866
|
-
//#endregion
|
|
867
|
-
//#region src/internal/server/coordinator/ipc-protocol.ts
|
|
868
|
-
const childOptionsSchema = z.object({
|
|
869
|
-
configAliases: z.record(z.string(), z.string()).optional(),
|
|
870
|
-
configPath: z.string(),
|
|
871
|
-
debug: z.boolean().optional(),
|
|
872
|
-
developmentWebTarget: z.url().optional(),
|
|
873
|
-
host: z.string().optional(),
|
|
874
|
-
logFile: z.string().optional(),
|
|
875
|
-
port: z.number().int().min(0).max(65535).optional(),
|
|
876
|
-
productionWebAssetsDir: z.string().optional(),
|
|
877
|
-
watch: z.boolean().optional()
|
|
878
|
-
});
|
|
879
|
-
z.discriminatedUnion("type", [
|
|
880
|
-
z.object({
|
|
881
|
-
options: childOptionsSchema,
|
|
882
|
-
type: z.literal("start")
|
|
883
|
-
}),
|
|
884
|
-
z.object({ type: z.literal("update-available") }),
|
|
885
|
-
z.object({ type: z.literal("stop") })
|
|
886
|
-
]);
|
|
887
|
-
const childMessageSchema = z.discriminatedUnion("type", [
|
|
888
|
-
z.object({
|
|
889
|
-
host: z.string(),
|
|
890
|
-
port: z.number().int().min(1).max(65535),
|
|
891
|
-
type: z.literal("ready"),
|
|
892
|
-
url: z.string(),
|
|
893
|
-
watch: z.boolean()
|
|
894
|
-
}),
|
|
895
|
-
z.object({ type: z.literal("restart-requested") }),
|
|
896
|
-
z.object({
|
|
897
|
-
message: z.string(),
|
|
898
|
-
type: z.literal("startup-failed")
|
|
899
|
-
})
|
|
900
|
-
]);
|
|
901
|
-
const parseChildMessage = (value) => childMessageSchema.parse(value);
|
|
902
|
-
//#endregion
|
|
903
|
-
//#region src/internal/server/coordinator/update-watcher.ts
|
|
904
|
-
const isRelevantUpdatePath = (path) => {
|
|
905
|
-
if (path.split(/[\\/]/).some((segment) => segment.toLowerCase() === "node_modules")) return false;
|
|
906
|
-
return /\.(?:js|jsx|mjs|cjs|ts|tsx|mts|cts|json|css)$/i.test(path);
|
|
907
|
-
};
|
|
908
|
-
const startUpdateWatcher = ({ configPath, onUpdate }) => {
|
|
909
|
-
let closed = false;
|
|
910
|
-
let timeout;
|
|
911
|
-
const watcher = watch(dirname(configPath), { recursive: true }, (_event, fileName) => {
|
|
912
|
-
if (closed || !fileName || !isRelevantUpdatePath(fileName.toString())) return;
|
|
913
|
-
if (timeout) clearTimeout(timeout);
|
|
914
|
-
timeout = setTimeout(onUpdate, 100);
|
|
915
|
-
});
|
|
916
|
-
return { close: () => {
|
|
917
|
-
if (closed) return;
|
|
918
|
-
closed = true;
|
|
919
|
-
if (timeout) clearTimeout(timeout);
|
|
920
|
-
watcher.close();
|
|
921
|
-
} };
|
|
922
|
-
};
|
|
923
|
-
//#endregion
|
|
924
|
-
//#region src/internal/server/coordinator/server-coordinator.ts
|
|
925
|
-
const send = (child, message, onError) => {
|
|
926
|
-
if (!child.connected) {
|
|
927
|
-
onError?.(/* @__PURE__ */ new Error("Server child IPC channel is closed"));
|
|
928
|
-
return;
|
|
929
|
-
}
|
|
930
|
-
try {
|
|
931
|
-
child.send(message, (cause) => {
|
|
932
|
-
if (cause) onError?.(cause);
|
|
933
|
-
});
|
|
934
|
-
} catch (cause) {
|
|
935
|
-
onError?.(cause instanceof Error ? cause : new Error(String(cause)));
|
|
936
|
-
}
|
|
937
|
-
};
|
|
938
|
-
const stopChild = (child) => new Promise((resolve) => {
|
|
939
|
-
let timeout;
|
|
940
|
-
const finish = () => {
|
|
941
|
-
child.off("exit", finish);
|
|
942
|
-
clearTimeout(timeout);
|
|
943
|
-
resolve();
|
|
944
|
-
};
|
|
945
|
-
child.once("exit", finish);
|
|
946
|
-
if (child.exitCode !== null || child.signalCode !== null) {
|
|
947
|
-
finish();
|
|
948
|
-
return;
|
|
949
|
-
}
|
|
950
|
-
timeout = setTimeout(() => {
|
|
951
|
-
if (!child.kill("SIGKILL")) finish();
|
|
952
|
-
}, 5e3);
|
|
953
|
-
send(child, { type: "stop" });
|
|
954
|
-
});
|
|
955
|
-
const serverChildPath = () => fileURLToPath(import.meta.resolve("#server-child"));
|
|
956
|
-
const startChild = (options, onRestartRequested) => new Promise((resolve, reject) => {
|
|
957
|
-
const child = fork(serverChildPath(), [], {
|
|
958
|
-
serialization: "json",
|
|
959
|
-
stdio: [
|
|
960
|
-
"inherit",
|
|
961
|
-
"inherit",
|
|
962
|
-
"inherit",
|
|
963
|
-
"ipc"
|
|
964
|
-
]
|
|
965
|
-
});
|
|
966
|
-
let settled = false;
|
|
967
|
-
const fail = (cause) => {
|
|
968
|
-
if (settled) return;
|
|
969
|
-
settled = true;
|
|
970
|
-
if (child.exitCode === null && child.signalCode === null) child.kill();
|
|
971
|
-
reject(cause instanceof Error ? cause : new Error(String(cause)));
|
|
972
|
-
};
|
|
973
|
-
child.on("error", fail);
|
|
974
|
-
child.on("exit", (code, signal) => {
|
|
975
|
-
fail(/* @__PURE__ */ new Error(`Server child exited before readiness (${signal ?? code ?? "unknown"})`));
|
|
976
|
-
});
|
|
977
|
-
child.on("message", (raw) => {
|
|
978
|
-
let message;
|
|
979
|
-
try {
|
|
980
|
-
message = parseChildMessage(raw);
|
|
981
|
-
} catch (cause) {
|
|
982
|
-
fail(cause);
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
if (message.type === "restart-requested") {
|
|
986
|
-
if (settled) onRestartRequested();
|
|
987
|
-
return;
|
|
988
|
-
}
|
|
989
|
-
if (message.type === "startup-failed") {
|
|
990
|
-
fail(new Error(message.message));
|
|
991
|
-
return;
|
|
992
|
-
}
|
|
993
|
-
if (!settled) {
|
|
994
|
-
settled = true;
|
|
995
|
-
resolve({
|
|
996
|
-
host: message.host,
|
|
997
|
-
port: message.port,
|
|
998
|
-
process: child,
|
|
999
|
-
url: message.url,
|
|
1000
|
-
watch: message.watch
|
|
1001
|
-
});
|
|
1002
|
-
}
|
|
1003
|
-
});
|
|
1004
|
-
send(child, {
|
|
1005
|
-
options,
|
|
1006
|
-
type: "start"
|
|
1007
|
-
}, fail);
|
|
1008
|
-
});
|
|
1009
|
-
const startServerCoordinator = async (initialOptions) => {
|
|
1010
|
-
let child;
|
|
1011
|
-
let closed = false;
|
|
1012
|
-
let closePromise;
|
|
1013
|
-
let restartPromise;
|
|
1014
|
-
let updateDuringRestart = false;
|
|
1015
|
-
let startupAddress;
|
|
1016
|
-
let watcher;
|
|
1017
|
-
const adoptChild = (next) => {
|
|
1018
|
-
child = next;
|
|
1019
|
-
next.process.once("exit", () => {
|
|
1020
|
-
if (child?.process === next.process) child = void 0;
|
|
1021
|
-
});
|
|
1022
|
-
};
|
|
1023
|
-
const restart = () => {
|
|
1024
|
-
if (closed || restartPromise) return restartPromise ?? Promise.resolve();
|
|
1025
|
-
restartPromise = (async () => {
|
|
1026
|
-
const previous = child;
|
|
1027
|
-
child = void 0;
|
|
1028
|
-
if (previous) await stopChild(previous.process);
|
|
1029
|
-
if (closed || !startupAddress) return;
|
|
1030
|
-
try {
|
|
1031
|
-
const replacement = await startChild({
|
|
1032
|
-
...initialOptions,
|
|
1033
|
-
host: startupAddress.host,
|
|
1034
|
-
port: startupAddress.port
|
|
1035
|
-
}, () => void restart());
|
|
1036
|
-
if (closed) {
|
|
1037
|
-
await stopChild(replacement.process);
|
|
1038
|
-
return;
|
|
1039
|
-
}
|
|
1040
|
-
adoptChild(replacement);
|
|
1041
|
-
if (updateDuringRestart) {
|
|
1042
|
-
updateDuringRestart = false;
|
|
1043
|
-
send(replacement.process, { type: "update-available" });
|
|
1044
|
-
}
|
|
1045
|
-
} catch (cause) {
|
|
1046
|
-
process.stderr.write(`[overmux] replacement child failed: ${errorMessage(cause)}\n`);
|
|
1047
|
-
}
|
|
1048
|
-
})().finally(() => {
|
|
1049
|
-
const shouldRetry = updateDuringRestart && !child && !closed;
|
|
1050
|
-
updateDuringRestart = false;
|
|
1051
|
-
restartPromise = void 0;
|
|
1052
|
-
if (shouldRetry) restart();
|
|
1053
|
-
});
|
|
1054
|
-
return restartPromise;
|
|
1055
|
-
};
|
|
1056
|
-
const onUpdate = () => {
|
|
1057
|
-
if (closed) return;
|
|
1058
|
-
if (restartPromise) {
|
|
1059
|
-
updateDuringRestart = true;
|
|
1060
|
-
return;
|
|
1061
|
-
}
|
|
1062
|
-
if (child) {
|
|
1063
|
-
send(child.process, { type: "update-available" });
|
|
1064
|
-
return;
|
|
1065
|
-
}
|
|
1066
|
-
restart();
|
|
1067
|
-
};
|
|
1068
|
-
const initial = await startChild(initialOptions, () => void restart());
|
|
1069
|
-
startupAddress = {
|
|
1070
|
-
host: initial.host,
|
|
1071
|
-
port: initial.port
|
|
1072
|
-
};
|
|
1073
|
-
adoptChild(initial);
|
|
1074
|
-
try {
|
|
1075
|
-
if (initial.watch) watcher = startUpdateWatcher({
|
|
1076
|
-
configPath: initialOptions.configPath,
|
|
1077
|
-
onUpdate
|
|
1078
|
-
});
|
|
1079
|
-
} catch (cause) {
|
|
1080
|
-
await stopChild(initial.process);
|
|
1081
|
-
throw cause;
|
|
1082
|
-
}
|
|
1083
|
-
const close = () => {
|
|
1084
|
-
if (closePromise) return closePromise;
|
|
1085
|
-
closed = true;
|
|
1086
|
-
closePromise = (async () => {
|
|
1087
|
-
process.off("SIGINT", onSignal);
|
|
1088
|
-
process.off("SIGTERM", onSignal);
|
|
1089
|
-
watcher?.close();
|
|
1090
|
-
const active = child;
|
|
1091
|
-
child = void 0;
|
|
1092
|
-
if (active) await stopChild(active.process);
|
|
1093
|
-
await restartPromise;
|
|
1094
|
-
})();
|
|
1095
|
-
return closePromise;
|
|
1096
|
-
};
|
|
1097
|
-
const onSignal = () => {
|
|
1098
|
-
close().catch((cause) => {
|
|
1099
|
-
process.stderr.write(`[overmux] coordinator shutdown failed: ${errorMessage(cause)}\n`);
|
|
1100
|
-
});
|
|
1101
|
-
};
|
|
1102
|
-
process.once("SIGINT", onSignal);
|
|
1103
|
-
process.once("SIGTERM", onSignal);
|
|
1104
|
-
return {
|
|
1105
|
-
announceUpdateAvailable: onUpdate,
|
|
1106
|
-
close,
|
|
1107
|
-
port: initial.port,
|
|
1108
|
-
url: initial.url
|
|
1109
|
-
};
|
|
1110
|
-
};
|
|
1111
|
-
//#endregion
|
|
1112
|
-
//#region src/internal/server/start-overmux-server.ts
|
|
1113
|
-
const startOvermuxServer = async (options) => startServerCoordinator({
|
|
1114
|
-
...options,
|
|
1115
|
-
configPath: resolve(options.configPath)
|
|
1116
|
-
});
|
|
1117
|
-
//#endregion
|
|
1118
|
-
export { checkOvermux, getOvermuxPaths, startOvermuxServer };
|
|
108
|
+
export { getOvermuxPaths };
|
|
1119
109
|
|
|
1120
110
|
//# sourceMappingURL=server.js.map
|