overmux 0.0.8 → 0.0.10
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 +21 -0
- package/dist/bin.js +59 -21
- package/dist/bin.js.map +1 -1
- package/dist/docs/100-introduction/300-why-i-built-overmux.md +1 -1
- package/dist/docs/200-getting-started/200-install-overmux-desktop.md +13 -0
- package/dist/docs/400-reference/500-server/100-resources.md +25 -0
- package/dist/docs/400-reference/500-server/500-api.md +4 -2
- package/dist/docs/400-reference/600-client/010-commands.md +0 -29
- package/dist/docs/400-reference/600-client/030-native-web-view.md +166 -0
- package/dist/docs/400-reference/600-client/300-tech-stack-recommendations.md +1 -0
- package/dist/docs/400-reference/700-cli/600-docs.md +1 -1
- package/dist/exports/client.d.ts +44 -8
- package/dist/exports/client.d.ts.map +1 -1
- package/dist/exports/client.js +212 -40
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/{index-Cz3xkCa4.d.ts → index-Dk11nKIv.d.ts} +2 -2
- package/dist/exports/index-Dk11nKIv.d.ts.map +1 -0
- package/dist/exports/index.d.ts +1 -1
- package/dist/exports/index.js +1 -1
- package/dist/exports/index.js.map +1 -1
- package/dist/internal/server/coordinator/server-child.js +75 -22
- package/dist/internal/server/coordinator/server-child.js.map +1 -1
- package/docs/100-introduction/300-why-i-built-overmux.md +1 -1
- package/docs/200-getting-started/200-install-overmux-desktop.md +13 -0
- package/docs/400-reference/500-server/100-resources.md +25 -0
- package/docs/400-reference/500-server/500-api.md +4 -2
- package/docs/400-reference/600-client/010-commands.md +0 -29
- package/docs/400-reference/600-client/030-native-web-view.md +166 -0
- package/docs/400-reference/600-client/300-tech-stack-recommendations.md +1 -0
- package/docs/400-reference/700-cli/600-docs.md +1 -1
- package/package.json +2 -2
- package/src/internal/client/client-definition.ts +9 -13
- package/src/internal/client/host/desktop-host.ts +28 -2
- package/src/internal/client/native-web-view.tsx +158 -0
- package/src/internal/client/overmux-react.ts +121 -27
- package/src/internal/client/shortcuts.tsx +17 -31
- package/src/internal/server/runtime/runtime-resources.ts +99 -20
- package/src/internal/server/websocket-connection.ts +27 -2
- package/src/public/client.ts +5 -0
- package/src/public/config.ts +1 -1
- package/dist/exports/index-Cz3xkCa4.d.ts.map +0 -1
|
@@ -127,13 +127,131 @@ const useCanonicalInput = (input: unknown) => {
|
|
|
127
127
|
return useMemo(() => (key ? JSON.parse(key) : undefined), [key]);
|
|
128
128
|
};
|
|
129
129
|
|
|
130
|
+
// Query identity shares pending work across observers without retaining unused cache entries.
|
|
131
|
+
const pendingResourceInvalidations = new WeakSet<object>();
|
|
132
|
+
|
|
133
|
+
type ResourceQuery = {
|
|
134
|
+
queryClient: ReturnType<typeof useQueryClient>;
|
|
135
|
+
queryKey: readonly unknown[];
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const invalidateResource = ({ queryClient, queryKey }: ResourceQuery) => {
|
|
139
|
+
const query = queryClient.getQueryCache().find({ exact: true, queryKey });
|
|
140
|
+
|
|
141
|
+
if (query && query.state.fetchStatus !== "idle") {
|
|
142
|
+
// TanStack coalesces initial-read invalidations into that same read. Queue a
|
|
143
|
+
// follow-up instead, without cancelling reads or overlooking same-turn changes.
|
|
144
|
+
pendingResourceInvalidations.add(query);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (query) {
|
|
149
|
+
pendingResourceInvalidations.delete(query);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
void queryClient.invalidateQueries({ exact: true, queryKey });
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const flushResourceInvalidation = ({
|
|
156
|
+
queryClient,
|
|
157
|
+
queryKey,
|
|
158
|
+
}: ResourceQuery) => {
|
|
159
|
+
const query = queryClient.getQueryCache().find({ exact: true, queryKey });
|
|
160
|
+
|
|
161
|
+
if (!query || query.state.fetchStatus !== "idle") {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// All observers see settlement. Consuming the shared flag before starting a read
|
|
166
|
+
// ensures their callbacks cannot keep triggering each other's follow-up reads.
|
|
167
|
+
if (pendingResourceInvalidations.delete(query)) {
|
|
168
|
+
void queryClient.invalidateQueries({ exact: true, queryKey });
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const useResourceInvalidation = ({
|
|
173
|
+
queryKey,
|
|
174
|
+
skipped,
|
|
175
|
+
}: {
|
|
176
|
+
queryKey: readonly [id: string, input: unknown];
|
|
177
|
+
skipped: boolean;
|
|
178
|
+
}) => {
|
|
179
|
+
const [id, canonicalInput] = queryKey;
|
|
180
|
+
const { manifest, overmuxServerApi } = useRuntime();
|
|
181
|
+
const queryClient = useQueryClient();
|
|
182
|
+
const invalidate = useCallback(
|
|
183
|
+
() => invalidateResource({ queryClient, queryKey }),
|
|
184
|
+
[queryClient, queryKey],
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
if (skipped) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let active = true;
|
|
193
|
+
const cache = queryClient.getQueryCache();
|
|
194
|
+
const observedQuery = cache.find({ exact: true, queryKey });
|
|
195
|
+
|
|
196
|
+
// Fast reads can enter and leave fetching between React renders. Observe cache
|
|
197
|
+
// settlement directly, then defer the follow-up outside TanStack's notification.
|
|
198
|
+
const unsubscribe = cache.subscribe((event) => {
|
|
199
|
+
if (
|
|
200
|
+
event.type === "updated" &&
|
|
201
|
+
event.query === observedQuery &&
|
|
202
|
+
event.query.state.fetchStatus === "idle" &&
|
|
203
|
+
pendingResourceInvalidations.has(event.query)
|
|
204
|
+
) {
|
|
205
|
+
queueMicrotask(() => {
|
|
206
|
+
if (active) {
|
|
207
|
+
flushResourceInvalidation({ queryClient, queryKey });
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
flushResourceInvalidation({ queryClient, queryKey });
|
|
214
|
+
return () => {
|
|
215
|
+
active = false;
|
|
216
|
+
unsubscribe();
|
|
217
|
+
};
|
|
218
|
+
}, [queryClient, queryKey, skipped]);
|
|
219
|
+
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
if (skipped || !manifest.resources.includes(id)) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let active = true;
|
|
226
|
+
const unsubscribe = overmuxServerApi.subscribeResource({
|
|
227
|
+
id,
|
|
228
|
+
input: canonicalInput,
|
|
229
|
+
onError: () => active && invalidate(),
|
|
230
|
+
onInvalidate: () => active && invalidate(),
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
return () => {
|
|
234
|
+
active = false;
|
|
235
|
+
unsubscribe();
|
|
236
|
+
};
|
|
237
|
+
}, [
|
|
238
|
+
manifest.resources,
|
|
239
|
+
canonicalInput,
|
|
240
|
+
id,
|
|
241
|
+
invalidate,
|
|
242
|
+
overmuxServerApi,
|
|
243
|
+
skipped,
|
|
244
|
+
]);
|
|
245
|
+
|
|
246
|
+
return invalidate;
|
|
247
|
+
};
|
|
248
|
+
|
|
130
249
|
const useResourceById = <TOutput>(
|
|
131
250
|
id: string,
|
|
132
251
|
input: unknown,
|
|
133
252
|
skipped: boolean,
|
|
134
253
|
): ResourceResult<TOutput> | undefined => {
|
|
135
|
-
const {
|
|
136
|
-
const queryClient = useQueryClient();
|
|
254
|
+
const { overmuxServerApi } = useRuntime();
|
|
137
255
|
const canonicalInput = useCanonicalInput(input);
|
|
138
256
|
const queryKey = useMemo(
|
|
139
257
|
() => [id, canonicalInput] as const,
|
|
@@ -148,31 +266,7 @@ const useResourceById = <TOutput>(
|
|
|
148
266
|
}) as Promise<TOutput>,
|
|
149
267
|
queryKey,
|
|
150
268
|
});
|
|
151
|
-
|
|
152
|
-
() =>
|
|
153
|
-
!skipped && manifest.resources.includes(id)
|
|
154
|
-
? overmuxServerApi.subscribeResource({
|
|
155
|
-
id,
|
|
156
|
-
input: canonicalInput,
|
|
157
|
-
onError: () =>
|
|
158
|
-
void queryClient.invalidateQueries({ exact: true, queryKey }),
|
|
159
|
-
onInvalidate: () =>
|
|
160
|
-
void queryClient.invalidateQueries({ exact: true, queryKey }),
|
|
161
|
-
})
|
|
162
|
-
: undefined,
|
|
163
|
-
[
|
|
164
|
-
manifest.resources,
|
|
165
|
-
canonicalInput,
|
|
166
|
-
id,
|
|
167
|
-
overmuxServerApi,
|
|
168
|
-
queryClient,
|
|
169
|
-
queryKey,
|
|
170
|
-
skipped,
|
|
171
|
-
],
|
|
172
|
-
);
|
|
173
|
-
const refetch = useCallback(() => {
|
|
174
|
-
void queryClient.invalidateQueries({ exact: true, queryKey });
|
|
175
|
-
}, [queryClient, queryKey]);
|
|
269
|
+
const refetch = useResourceInvalidation({ queryKey, skipped });
|
|
176
270
|
if (skipped) {
|
|
177
271
|
return undefined;
|
|
178
272
|
}
|
|
@@ -8,9 +8,12 @@ import {
|
|
|
8
8
|
type RefObject,
|
|
9
9
|
} from "react";
|
|
10
10
|
import {
|
|
11
|
+
keyBindingSequence,
|
|
11
12
|
matchesKeyBinding,
|
|
12
|
-
|
|
13
|
+
matchesShortcutBindingPrefix,
|
|
14
|
+
normalizedShortcutBinding,
|
|
13
15
|
type KeyBinding,
|
|
16
|
+
type ShortcutBinding,
|
|
14
17
|
} from "@overmux/keybindings";
|
|
15
18
|
|
|
16
19
|
import {
|
|
@@ -19,7 +22,6 @@ import {
|
|
|
19
22
|
shortcutMediaQueries,
|
|
20
23
|
type ClientAppDefinition,
|
|
21
24
|
type CommandHandle,
|
|
22
|
-
type ShortcutBinding,
|
|
23
25
|
} from "./client-definition";
|
|
24
26
|
import {
|
|
25
27
|
CommandsContext,
|
|
@@ -29,25 +31,6 @@ import {
|
|
|
29
31
|
type RegisteredCommand,
|
|
30
32
|
} from "./commands";
|
|
31
33
|
|
|
32
|
-
const bindingSequence = (binding: ShortcutBinding): KeyBinding[] =>
|
|
33
|
-
typeof binding === "string" ? [binding] : [...binding];
|
|
34
|
-
|
|
35
|
-
const normalizedBinding = (binding: ShortcutBinding): string[] =>
|
|
36
|
-
bindingSequence(binding).map((key) => platformBinding(key));
|
|
37
|
-
|
|
38
|
-
const sequenceMatches = (
|
|
39
|
-
binding: ShortcutBinding,
|
|
40
|
-
pending: string[],
|
|
41
|
-
event: KeyboardEvent,
|
|
42
|
-
) => {
|
|
43
|
-
const sequence = normalizedBinding(binding);
|
|
44
|
-
return (
|
|
45
|
-
sequence.length > pending.length &&
|
|
46
|
-
pending.every((key, index) => sequence[index] === key) &&
|
|
47
|
-
matchesKeyBinding(sequence[pending.length] as KeyBinding, event)
|
|
48
|
-
);
|
|
49
|
-
};
|
|
50
|
-
|
|
51
34
|
const isTextInput = (element: Element | null) =>
|
|
52
35
|
element instanceof HTMLInputElement ||
|
|
53
36
|
element instanceof HTMLSelectElement ||
|
|
@@ -67,7 +50,7 @@ const acceptsTextInput = (bindings: readonly ShortcutBinding[]) => {
|
|
|
67
50
|
return true;
|
|
68
51
|
}
|
|
69
52
|
return bindings.some((binding) => {
|
|
70
|
-
const sequence =
|
|
53
|
+
const sequence = keyBindingSequence(binding);
|
|
71
54
|
return (
|
|
72
55
|
sequence.length > 1 ||
|
|
73
56
|
sequence[0]?.startsWith("F") ||
|
|
@@ -260,12 +243,12 @@ export const ShortcutHost = ({
|
|
|
260
243
|
window.addEventListener("focusin", refreshFocus);
|
|
261
244
|
return () => window.removeEventListener("focusin", refreshFocus);
|
|
262
245
|
}, []);
|
|
246
|
+
const shortcuts = flattenShortcuts(
|
|
247
|
+
definition,
|
|
248
|
+
(media) => window.matchMedia(media).matches,
|
|
249
|
+
);
|
|
263
250
|
useEffect(() => {
|
|
264
|
-
|
|
265
|
-
definition,
|
|
266
|
-
(media) => window.matchMedia(media).matches,
|
|
267
|
-
);
|
|
268
|
-
let pending: string[] = [];
|
|
251
|
+
let pending: KeyBinding[] = [];
|
|
269
252
|
let pendingEvents: KeyboardEvent[] = [];
|
|
270
253
|
let pendingTarget: ShortcutInputTarget | undefined;
|
|
271
254
|
let timeout: number | undefined;
|
|
@@ -302,8 +285,11 @@ export const ShortcutHost = ({
|
|
|
302
285
|
shortcut.bindings
|
|
303
286
|
.filter(
|
|
304
287
|
(binding) =>
|
|
305
|
-
|
|
306
|
-
|
|
288
|
+
matchesShortcutBindingPrefix({
|
|
289
|
+
binding,
|
|
290
|
+
input: event,
|
|
291
|
+
pending,
|
|
292
|
+
}) && canRunShortcut(shortcut, event, registrations),
|
|
307
293
|
)
|
|
308
294
|
.map((binding) => ({ binding, shortcut })),
|
|
309
295
|
);
|
|
@@ -317,7 +303,7 @@ export const ShortcutHost = ({
|
|
|
317
303
|
}
|
|
318
304
|
const complete = matches.find(
|
|
319
305
|
({ binding }) =>
|
|
320
|
-
|
|
306
|
+
normalizedShortcutBinding(binding).length === pending.length + 1,
|
|
321
307
|
);
|
|
322
308
|
if (complete) {
|
|
323
309
|
clearPending();
|
|
@@ -336,7 +322,7 @@ export const ShortcutHost = ({
|
|
|
336
322
|
}
|
|
337
323
|
pending = [
|
|
338
324
|
...pending,
|
|
339
|
-
|
|
325
|
+
normalizedShortcutBinding(matches[0]!.binding)[pending.length]!,
|
|
340
326
|
];
|
|
341
327
|
pendingEvents = [...pendingEvents, event];
|
|
342
328
|
event.preventDefault();
|
|
@@ -19,7 +19,7 @@ export type RuntimeResource = {
|
|
|
19
19
|
input: unknown,
|
|
20
20
|
listener: () => void,
|
|
21
21
|
signal?: AbortSignal,
|
|
22
|
-
) => RuntimeDisposer
|
|
22
|
+
) => Promise<RuntimeDisposer>;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
export type RuntimeResourceDefinitions = Readonly<
|
|
@@ -47,6 +47,51 @@ type CreateRuntimeResourcesOptions = {
|
|
|
47
47
|
lifecycle: RuntimeLifecycle;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
const createSubscriptionLifetime = ({
|
|
51
|
+
entry,
|
|
52
|
+
invalidationListeners,
|
|
53
|
+
lifecycle,
|
|
54
|
+
signal,
|
|
55
|
+
}: {
|
|
56
|
+
entry: InvalidationListener;
|
|
57
|
+
invalidationListeners: Set<InvalidationListener>;
|
|
58
|
+
lifecycle: RuntimeLifecycle;
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
}) => {
|
|
61
|
+
const controller = new AbortController();
|
|
62
|
+
const subscriptionSignal = AbortSignal.any([
|
|
63
|
+
lifecycle.requestSignal(signal),
|
|
64
|
+
controller.signal,
|
|
65
|
+
]);
|
|
66
|
+
subscriptionSignal.throwIfAborted();
|
|
67
|
+
const registeredEntry = {
|
|
68
|
+
...entry,
|
|
69
|
+
listener: () => {
|
|
70
|
+
if (!subscriptionSignal.aborted) {
|
|
71
|
+
entry.listener();
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
const disposers: RuntimeDisposer[] = [];
|
|
76
|
+
// Own the lifetime before invoking userland, which can invalidate and
|
|
77
|
+
// trigger unsubscribe synchronously, or await setup past cancellation.
|
|
78
|
+
const release = lifecycle.track(async () => {
|
|
79
|
+
invalidationListeners.delete(registeredEntry);
|
|
80
|
+
await lifecycle.disposeAll(disposers);
|
|
81
|
+
}, subscriptionSignal);
|
|
82
|
+
const dispose = () => {
|
|
83
|
+
controller.abort(new Error("Resource subscription closed"));
|
|
84
|
+
return release();
|
|
85
|
+
};
|
|
86
|
+
invalidationListeners.add(registeredEntry);
|
|
87
|
+
return {
|
|
88
|
+
dispose,
|
|
89
|
+
disposers,
|
|
90
|
+
refresh: registeredEntry.listener,
|
|
91
|
+
signal: subscriptionSignal,
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
50
95
|
export const createRuntimeResources = ({
|
|
51
96
|
definitions,
|
|
52
97
|
instance,
|
|
@@ -119,13 +164,14 @@ export const createRuntimeResources = ({
|
|
|
119
164
|
return definition.contract.output.parse(rawOutput);
|
|
120
165
|
};
|
|
121
166
|
|
|
122
|
-
const
|
|
167
|
+
const activateSubscriptions = async (
|
|
123
168
|
id: string,
|
|
124
169
|
rawInput: unknown,
|
|
125
|
-
signal: AbortSignal
|
|
170
|
+
signal: AbortSignal,
|
|
126
171
|
activated: Set<string>,
|
|
127
172
|
disposers: RuntimeDisposer[],
|
|
128
|
-
) => {
|
|
173
|
+
): Promise<void> => {
|
|
174
|
+
signal.throwIfAborted();
|
|
129
175
|
if (activated.has(id)) {
|
|
130
176
|
return;
|
|
131
177
|
}
|
|
@@ -133,44 +179,77 @@ export const createRuntimeResources = ({
|
|
|
133
179
|
const definition = definitions[id]!;
|
|
134
180
|
const input = definition.contract.input.parse(rawInput);
|
|
135
181
|
if (definition.kind === "subscription") {
|
|
136
|
-
const dispose = definition.subscribe(
|
|
182
|
+
const dispose = await definition.subscribe(
|
|
137
183
|
input,
|
|
138
|
-
() =>
|
|
184
|
+
() => {
|
|
185
|
+
if (!signal.aborted) {
|
|
186
|
+
invalidateResource(id, input);
|
|
187
|
+
}
|
|
188
|
+
},
|
|
139
189
|
context(signal),
|
|
140
190
|
);
|
|
141
191
|
if (typeof dispose !== "function") {
|
|
142
192
|
throw new Error(`Resource ${id} subscribe must return a disposer`);
|
|
143
193
|
}
|
|
194
|
+
// Cancellation releases acquired resources without waiting for pending
|
|
195
|
+
// userland setup. A cleanup returned after cancellation is released here.
|
|
196
|
+
if (signal.aborted) {
|
|
197
|
+
await dispose();
|
|
198
|
+
signal.throwIfAborted();
|
|
199
|
+
}
|
|
144
200
|
disposers.push(dispose);
|
|
145
201
|
return;
|
|
146
202
|
}
|
|
147
203
|
if (definition.kind === "derived") {
|
|
148
|
-
graph.dependencies.get(id)
|
|
149
|
-
|
|
150
|
-
|
|
204
|
+
for (const dependencyId of graph.dependencies.get(id) ?? []) {
|
|
205
|
+
await activateSubscriptions(
|
|
206
|
+
dependencyId,
|
|
207
|
+
rawInput,
|
|
208
|
+
signal,
|
|
209
|
+
activated,
|
|
210
|
+
disposers,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
151
213
|
}
|
|
152
214
|
};
|
|
153
215
|
|
|
154
216
|
Object.keys(definitions).forEach((name) => {
|
|
155
217
|
resources.set(name, {
|
|
156
218
|
read: (input, signal) => read(name, input, signal),
|
|
157
|
-
subscribe: (rawInput, listener, signal) => {
|
|
219
|
+
subscribe: async (rawInput, listener, signal) => {
|
|
158
220
|
lifecycle.requestSignal(signal).throwIfAborted();
|
|
159
221
|
const input = definitions[name]!.contract.input.parse(rawInput);
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
222
|
+
const subscription = createSubscriptionLifetime({
|
|
223
|
+
entry: { input, listener, resourceId: name },
|
|
224
|
+
invalidationListeners,
|
|
225
|
+
lifecycle,
|
|
226
|
+
signal,
|
|
227
|
+
});
|
|
163
228
|
try {
|
|
164
|
-
|
|
229
|
+
await activateSubscriptions(
|
|
230
|
+
name,
|
|
231
|
+
rawInput,
|
|
232
|
+
subscription.signal,
|
|
233
|
+
new Set(),
|
|
234
|
+
subscription.disposers,
|
|
235
|
+
);
|
|
236
|
+
subscription.signal.throwIfAborted();
|
|
237
|
+
// Refresh only this subscriber to cover changes during setup. Returning
|
|
238
|
+
// cleanup does not imply an external event source is already ready.
|
|
239
|
+
subscription.refresh();
|
|
240
|
+
subscription.signal.throwIfAborted();
|
|
241
|
+
return subscription.dispose;
|
|
165
242
|
} catch (cause) {
|
|
166
|
-
|
|
167
|
-
|
|
243
|
+
try {
|
|
244
|
+
await subscription.dispose();
|
|
245
|
+
} catch (cleanupError) {
|
|
246
|
+
throw new AggregateError(
|
|
247
|
+
[cause, cleanupError],
|
|
248
|
+
"Resource subscription setup and cleanup failed",
|
|
249
|
+
);
|
|
250
|
+
}
|
|
168
251
|
throw cause;
|
|
169
252
|
}
|
|
170
|
-
return lifecycle.track(async () => {
|
|
171
|
-
invalidationListeners.delete(entry);
|
|
172
|
-
await lifecycle.disposeAll(watcherDisposers);
|
|
173
|
-
}, signal);
|
|
174
253
|
},
|
|
175
254
|
});
|
|
176
255
|
});
|
|
@@ -301,7 +301,7 @@ const subscribeResource = (
|
|
|
301
301
|
state.subscriptions.set(message.subscriptionId, entry);
|
|
302
302
|
const signal = AbortSignal.any([operationSignal, controller.signal]);
|
|
303
303
|
try {
|
|
304
|
-
|
|
304
|
+
const dispose = await resource.subscribe(
|
|
305
305
|
message.input,
|
|
306
306
|
() =>
|
|
307
307
|
void send(state, {
|
|
@@ -310,8 +310,33 @@ const subscribeResource = (
|
|
|
310
310
|
}),
|
|
311
311
|
signal,
|
|
312
312
|
);
|
|
313
|
+
// While setup awaits, the client can unsubscribe and reuse this ID.
|
|
314
|
+
// Compare entry identity so late setup cannot attach to its replacement.
|
|
315
|
+
// Core makes cleanup safe to call again if cancellation already released it.
|
|
316
|
+
if (state.subscriptions.get(message.subscriptionId) === entry) {
|
|
317
|
+
entry.dispose = dispose;
|
|
318
|
+
} else {
|
|
319
|
+
await dispose();
|
|
320
|
+
}
|
|
313
321
|
} catch (cause) {
|
|
314
|
-
|
|
322
|
+
// A cancelled setup may settle after its ID has been reused. Do not
|
|
323
|
+
// remove the replacement or deliver the old failure to its client.
|
|
324
|
+
if (signal.aborted) {
|
|
325
|
+
// Ignore expected cancellation, but log other late failures,
|
|
326
|
+
// including cleanup errors, without sending them to the client.
|
|
327
|
+
if (cause !== signal.reason) {
|
|
328
|
+
state.serverLogger?.log({
|
|
329
|
+
correlationId: identity.operationId,
|
|
330
|
+
details: { ...identity, ...diagnosticErrorDetails(cause) },
|
|
331
|
+
event: "resource-subscription-cancelled-error",
|
|
332
|
+
level: "error",
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (state.subscriptions.get(message.subscriptionId) === entry) {
|
|
338
|
+
await disposeSubscription(state, message.subscriptionId);
|
|
339
|
+
}
|
|
315
340
|
throw cause;
|
|
316
341
|
}
|
|
317
342
|
});
|
package/src/public/client.ts
CHANGED
|
@@ -14,6 +14,11 @@ export {
|
|
|
14
14
|
type StreamResult,
|
|
15
15
|
} from "../internal/client/overmux-react";
|
|
16
16
|
export { OvermuxHost } from "../internal/client/host/overmux-host";
|
|
17
|
+
export { NativeWebView } from "../internal/client/native-web-view";
|
|
18
|
+
export type {
|
|
19
|
+
NativeWebViewProps,
|
|
20
|
+
NativeWebViewLoadError,
|
|
21
|
+
} from "../internal/client/native-web-view";
|
|
17
22
|
export {
|
|
18
23
|
OvermuxPortal,
|
|
19
24
|
OvermuxThemeScope,
|
package/src/public/config.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-Cz3xkCa4.d.ts","names":[],"sources":["../../../shared/dist/index.d.ts","../../../ai-context/dist/index.d.ts","../../src/public/ai-context.ts","../../src/public/notifications.ts","../../src/public/contracts.ts","../../src/public/config.ts"],"mappings":"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"}
|