@camera.ui/transport 0.0.25 → 0.0.27
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/dist/effects/backgroundProbe.d.ts +2 -1
- package/dist/effects/workerBridge.d.ts +8 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +47 -6
- package/dist/transports/http.d.ts +4 -2
- package/dist/transports/http.js +2 -1
- package/dist/transports/nats.d.ts +9 -1
- package/dist/transports/nats.js +76 -7
- package/dist/transports/socketio.d.ts +9 -1
- package/dist/transports/socketio.js +60 -6
- package/dist/transports/ws.d.ts +8 -1
- package/dist/transports/ws.js +35 -5
- package/dist/worker/index.d.ts +2 -2
- package/dist/worker/mirror.d.ts +7 -0
- package/dist/worker/protocol.d.ts +14 -1
- package/dist/worker.js +39 -0
- package/package.json +10 -10
|
@@ -2,7 +2,7 @@ import { Kernel } from '../core/kernel.js';
|
|
|
2
2
|
import { ConnectionTarget, Endpoint, Tokens } from '../core/types.js';
|
|
3
3
|
import { TimeoutByModeFn } from '../race.js';
|
|
4
4
|
import { ProbeContext } from './probeLoop.js';
|
|
5
|
-
export type BackgroundProbeOutcome = 'swap' | 'same' | 'failed' | 'skipped';
|
|
5
|
+
export type BackgroundProbeOutcome = 'swap' | 'same' | 'kept' | 'failed' | 'skipped';
|
|
6
6
|
export interface BackgroundProbeOptions {
|
|
7
7
|
readonly kernel: Kernel;
|
|
8
8
|
readonly discover: (signal: AbortSignal) => Promise<readonly Endpoint[]>;
|
|
@@ -11,6 +11,7 @@ export interface BackgroundProbeOptions {
|
|
|
11
11
|
readonly timeoutByMode?: TimeoutByModeFn;
|
|
12
12
|
readonly prefer?: (endpoint: Endpoint) => boolean;
|
|
13
13
|
readonly preferGraceMs?: number;
|
|
14
|
+
readonly isCurrentHealthy?: () => boolean;
|
|
14
15
|
readonly onResult?: (outcome: BackgroundProbeOutcome, detail?: string) => void;
|
|
15
16
|
}
|
|
16
17
|
export interface BackgroundProbe {
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { Kernel } from '../core/kernel.js';
|
|
2
|
+
import { ConnectionTarget } from '../core/types.js';
|
|
2
3
|
import { WorkerHost } from '../worker/protocol.js';
|
|
3
4
|
export type Detach = () => void;
|
|
5
|
+
export interface BridgeResolveContext {
|
|
6
|
+
readonly target: ConnectionTarget;
|
|
7
|
+
readonly connId: string;
|
|
8
|
+
readonly defaultServers: readonly string[];
|
|
9
|
+
}
|
|
4
10
|
export interface WorkerBridgeOptions {
|
|
5
11
|
readonly kernel: Kernel;
|
|
6
|
-
readonly hosts: () => Iterable<WorkerHost>;
|
|
7
12
|
readonly listenForResyncRequests?: boolean;
|
|
13
|
+
readonly hosts: () => Iterable<WorkerHost>;
|
|
14
|
+
readonly resolveServers?: (ctx: BridgeResolveContext) => Promise<string[]>;
|
|
8
15
|
readonly onBroadcast?: (generation: number, hostCount: number) => void;
|
|
9
16
|
readonly onSyncHost?: (generation: number) => void;
|
|
10
17
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -28,10 +28,10 @@ export type { PresenceCallback, PresenceOptions, VisibilitySource } from './effe
|
|
|
28
28
|
export type { ProbeContext, ProbeFailure, ProbeFailureKind, ProbeLoopOptions } from './effects/probeLoop.js';
|
|
29
29
|
export type { Detach, RefreshReason, TokenLifecycle, TokenLifecycleOptions } from './effects/tokenLifecycle.js';
|
|
30
30
|
export type { TransportSyncOptions } from './effects/transportSync.js';
|
|
31
|
-
export type { WorkerBridge, WorkerBridgeOptions } from './effects/workerBridge.js';
|
|
31
|
+
export type { BridgeResolveContext, WorkerBridge, WorkerBridgeOptions } from './effects/workerBridge.js';
|
|
32
32
|
export type { ConnectionJournal, JournalEntry, JournalOptions } from './journal.js';
|
|
33
33
|
export type { RaceCandidate, RaceFirstOptions, RaceFirstResult, TimeoutByModeFn } from './race.js';
|
|
34
34
|
export type { ConnectionSignal, ConnectionSignalHandle, ConnectionSignalOptions } from './signal.js';
|
|
35
35
|
export type { CloseClass, CloseLike } from './transports/closeCodes.js';
|
|
36
36
|
export type { PerResourceTransport, Transport, TransportEvent, TransportEventHandler, TransportEventPayload } from './transports/contract.js';
|
|
37
|
-
export type { KernelSyncMessage, KernelSyncRequestMessage, MessageSource, WorkerHost, WorkerMessage } from './worker/protocol.js';
|
|
37
|
+
export type { KernelResolveReplyMessage, KernelResolveRequestMessage, KernelSyncMessage, KernelSyncRequestMessage, MessageSource, WorkerHost, WorkerMessage, } from './worker/protocol.js';
|
package/dist/index.js
CHANGED
|
@@ -331,7 +331,12 @@ function createBackgroundProbe(options) {
|
|
|
331
331
|
options.onResult?.("skipped", `phase=${phase.kind}`);
|
|
332
332
|
return "skipped";
|
|
333
333
|
}
|
|
334
|
-
const
|
|
334
|
+
const current = phase.target.endpoint;
|
|
335
|
+
const same = current.url === endpoint.url && current.mode === endpoint.mode;
|
|
336
|
+
if (!same && options.prefer && options.prefer(current) && !options.prefer(endpoint) && options.isCurrentHealthy?.() === true) {
|
|
337
|
+
options.onResult?.("kept", current.url);
|
|
338
|
+
return "kept";
|
|
339
|
+
}
|
|
335
340
|
options.kernel.dispatch({
|
|
336
341
|
type: "ENDPOINT_SWAP",
|
|
337
342
|
endpoint,
|
|
@@ -780,8 +785,10 @@ function attachProbeLoop(options) {
|
|
|
780
785
|
return tokens;
|
|
781
786
|
} catch (err) {
|
|
782
787
|
let finalErr = err;
|
|
783
|
-
if (signal.aborted && !ctrl.signal.aborted && !isProbeFailure(err))
|
|
784
|
-
|
|
788
|
+
if (signal.aborted && !ctrl.signal.aborted && !isProbeFailure(err)) {
|
|
789
|
+
if (signal.reason === "race-settled") finalErr = makeProbeFailure("transient", "superseded");
|
|
790
|
+
else finalErr = makeProbeFailure("transient", `timeout (${(options.timeoutByMode ?? ((m) => DEFAULT_RACE_TIMEOUT_BY_MODE[m] ?? 5e3))(endpoint.mode)}ms)`);
|
|
791
|
+
}
|
|
785
792
|
options.onProbeError?.(endpoint, finalErr);
|
|
786
793
|
throw finalErr;
|
|
787
794
|
}
|
|
@@ -1119,7 +1126,8 @@ function attachWorkerBridge(options) {
|
|
|
1119
1126
|
return {
|
|
1120
1127
|
type: "kernel-sync",
|
|
1121
1128
|
generation,
|
|
1122
|
-
phase
|
|
1129
|
+
phase,
|
|
1130
|
+
resolver: options.resolveServers !== void 0
|
|
1123
1131
|
};
|
|
1124
1132
|
}
|
|
1125
1133
|
function broadcast(phase) {
|
|
@@ -1153,13 +1161,46 @@ function attachWorkerBridge(options) {
|
|
|
1153
1161
|
});
|
|
1154
1162
|
}
|
|
1155
1163
|
}
|
|
1164
|
+
async function answerResolve(host, req) {
|
|
1165
|
+
const reply = (msg) => {
|
|
1166
|
+
try {
|
|
1167
|
+
host.postMessage({
|
|
1168
|
+
type: "kernel-resolve-reply",
|
|
1169
|
+
id: req.id,
|
|
1170
|
+
...msg
|
|
1171
|
+
});
|
|
1172
|
+
} catch (err) {
|
|
1173
|
+
log.warn("resolve reply postMessage failed", err);
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
if (!options.resolveServers) {
|
|
1177
|
+
reply({ servers: [...req.defaultServers] });
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
const phase = options.kernel.phase;
|
|
1181
|
+
if (phase.kind !== "online") {
|
|
1182
|
+
reply({ error: `kernel ${phase.kind}` });
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
try {
|
|
1186
|
+
reply({ servers: await options.resolveServers({
|
|
1187
|
+
target: phase.target,
|
|
1188
|
+
connId: req.connId,
|
|
1189
|
+
defaultServers: req.defaultServers
|
|
1190
|
+
}) });
|
|
1191
|
+
} catch (err) {
|
|
1192
|
+
reply({ error: err instanceof Error ? err.message : String(err) });
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1156
1195
|
function maybeAttachHostListener(host) {
|
|
1157
|
-
if (!options.listenForResyncRequests) return;
|
|
1196
|
+
if (!options.listenForResyncRequests && !options.resolveServers) return;
|
|
1158
1197
|
if (!host.addEventListener) return;
|
|
1159
1198
|
if (hostListenerCleanups.has(host)) return;
|
|
1160
1199
|
const listener = (event) => {
|
|
1161
1200
|
if (detached) return;
|
|
1162
|
-
|
|
1201
|
+
const msg = event.data;
|
|
1202
|
+
if (msg?.type === "kernel-sync-request" && options.listenForResyncRequests) syncOne(host);
|
|
1203
|
+
else if (msg?.type === "kernel-resolve-request") answerResolve(host, msg);
|
|
1163
1204
|
};
|
|
1164
1205
|
host.addEventListener("message", listener);
|
|
1165
1206
|
hostListenerCleanups.set(host, () => {
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { Logger } from '@camera.ui/logger';
|
|
2
|
-
import { AxiosInstance, AxiosRequestConfig } from 'axios';
|
|
3
|
-
import { TransportSpec } from '../core/types.js';
|
|
2
|
+
import { AxiosInstance, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios';
|
|
3
|
+
import { ConnectionTarget, TransportSpec } from '../core/types.js';
|
|
4
4
|
import { Transport } from './contract.js';
|
|
5
|
+
export type HttpAuthorizer = (config: InternalAxiosRequestConfig, target: ConnectionTarget) => void | Promise<void>;
|
|
5
6
|
export interface HttpTransportOptions {
|
|
6
7
|
readonly apiPrefix?: string;
|
|
7
8
|
readonly timeoutMs?: number;
|
|
8
9
|
readonly targetWaitMs?: number;
|
|
9
10
|
readonly spec?: Partial<TransportSpec>;
|
|
11
|
+
readonly authorize?: HttpAuthorizer;
|
|
10
12
|
readonly logger?: Logger;
|
|
11
13
|
}
|
|
12
14
|
export interface HttpTransport extends Transport {
|
package/dist/transports/http.js
CHANGED
|
@@ -57,7 +57,8 @@ function createHttpTransport(options = {}) {
|
|
|
57
57
|
const target = currentTarget;
|
|
58
58
|
if (!target) throw new axios.Cancel("http-transport: no target");
|
|
59
59
|
if (!config.baseURL) config.baseURL = `${target.endpoint.url}${apiPrefix}`;
|
|
60
|
-
|
|
60
|
+
if (options.authorize) await options.authorize(config, target);
|
|
61
|
+
else config.headers.set("Authorization", `Bearer ${target.tokens.access}`);
|
|
61
62
|
if (target.tokens.proxySession) config.headers.set("X-Proxy-Session", target.tokens.proxySession);
|
|
62
63
|
return config;
|
|
63
64
|
});
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { Logger } from '@camera.ui/logger';
|
|
2
2
|
import { RPCClient } from '@camera.ui/rpc';
|
|
3
|
-
import { TransportSpec, TransportStatus } from '../core/types.js';
|
|
3
|
+
import { ConnectionTarget, TransportSpec, TransportStatus } from '../core/types.js';
|
|
4
4
|
import { Transport, Unsubscribe } from './contract.js';
|
|
5
|
+
export interface ResolveServersContext {
|
|
6
|
+
readonly target: ConnectionTarget;
|
|
7
|
+
readonly connId: string;
|
|
8
|
+
readonly defaultServers: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
export type ServersResolver = (ctx: ResolveServersContext) => Promise<string[]>;
|
|
5
11
|
export interface NatsTransportOptions {
|
|
6
12
|
readonly spec?: Partial<TransportSpec>;
|
|
7
13
|
readonly proxyPath?: string;
|
|
14
|
+
readonly resolveRefreshMs?: number;
|
|
8
15
|
readonly clientName?: string;
|
|
9
16
|
readonly natsUser?: string;
|
|
10
17
|
readonly natsPassword?: string;
|
|
@@ -17,6 +24,7 @@ export interface NatsTransportOptions {
|
|
|
17
24
|
readonly pingTimeout?: number;
|
|
18
25
|
readonly maxPingOut?: number;
|
|
19
26
|
readonly logger?: Logger;
|
|
27
|
+
readonly resolveServers?: ServersResolver;
|
|
20
28
|
}
|
|
21
29
|
export type NatsClientListener = (client: RPCClient | null) => void;
|
|
22
30
|
export interface NatsTransport extends Transport {
|
package/dist/transports/nats.js
CHANGED
|
@@ -35,8 +35,11 @@ function createNatsTransport(options = {}) {
|
|
|
35
35
|
const pingTimeout = options.pingTimeout ?? 2e4;
|
|
36
36
|
const maxPingOut = options.maxPingOut ?? 1;
|
|
37
37
|
const logger = options.logger;
|
|
38
|
+
const resolveServers = options.resolveServers;
|
|
39
|
+
const resolveRefreshMs = options.resolveRefreshMs ?? 0;
|
|
38
40
|
const emitter = new TransportEmitter();
|
|
39
41
|
const clientListeners = /* @__PURE__ */ new Set();
|
|
42
|
+
let refreshTimer = null;
|
|
40
43
|
let proxy = null;
|
|
41
44
|
let currentTarget = null;
|
|
42
45
|
let status = { up: false };
|
|
@@ -55,6 +58,53 @@ function createNatsTransport(options = {}) {
|
|
|
55
58
|
if (connId) params.set("connId", connId);
|
|
56
59
|
return [`${wsProtocol}//${url.hostname}:${port}${prefix}${proxyPath}?${params.toString()}`];
|
|
57
60
|
}
|
|
61
|
+
async function serversFor(target) {
|
|
62
|
+
const defaultServers = buildServers(target);
|
|
63
|
+
if (!resolveServers) return defaultServers;
|
|
64
|
+
return resolveServers({
|
|
65
|
+
target,
|
|
66
|
+
connId: connId ?? "",
|
|
67
|
+
defaultServers
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async function refreshPool(p) {
|
|
71
|
+
const target = currentTarget;
|
|
72
|
+
if (!resolveServers || !target || disposed) return;
|
|
73
|
+
try {
|
|
74
|
+
const servers = await serversFor(target);
|
|
75
|
+
if (proxy === p && !disposed) p.setServers(servers);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
logger?.warn(`server refresh failed: ${stringifyError(err)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function stopRefresh() {
|
|
81
|
+
if (refreshTimer) {
|
|
82
|
+
clearInterval(refreshTimer);
|
|
83
|
+
refreshTimer = null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function startRefresh(p) {
|
|
87
|
+
stopRefresh();
|
|
88
|
+
if (!resolveServers || resolveRefreshMs <= 0) return;
|
|
89
|
+
refreshTimer = setInterval(() => void refreshPool(p), resolveRefreshMs);
|
|
90
|
+
}
|
|
91
|
+
async function resign(reason) {
|
|
92
|
+
const target = currentTarget;
|
|
93
|
+
if (!proxy || !target || disposed) return;
|
|
94
|
+
logger?.debug(`resign (${reason})`);
|
|
95
|
+
const epoch = ++applyEpoch;
|
|
96
|
+
stopStatusMonitor();
|
|
97
|
+
stopRefresh();
|
|
98
|
+
try {
|
|
99
|
+
proxy.abortClose();
|
|
100
|
+
} catch {}
|
|
101
|
+
proxy = null;
|
|
102
|
+
notifyClient();
|
|
103
|
+
markDown(`signature-expired: ${reason}`);
|
|
104
|
+
try {
|
|
105
|
+
await rebuildClient(target, epoch);
|
|
106
|
+
} catch {}
|
|
107
|
+
}
|
|
58
108
|
function notifyClient() {
|
|
59
109
|
const next = status.up ? proxy : null;
|
|
60
110
|
for (const fn of [...clientListeners]) try {
|
|
@@ -98,14 +148,13 @@ function createNatsTransport(options = {}) {
|
|
|
98
148
|
case "disconnect":
|
|
99
149
|
markDown("disconnect");
|
|
100
150
|
notifyClient();
|
|
151
|
+
if (resolveServers) refreshPool(p);
|
|
101
152
|
break;
|
|
102
153
|
case "staleConnection":
|
|
103
154
|
markDown("staleConnection");
|
|
104
155
|
notifyClient();
|
|
105
156
|
break;
|
|
106
|
-
case "error":
|
|
107
|
-
handleErrorEvent(event);
|
|
108
|
-
break;
|
|
157
|
+
case "error": handleErrorEvent(event);
|
|
109
158
|
}
|
|
110
159
|
}
|
|
111
160
|
})().catch(() => {});
|
|
@@ -121,6 +170,10 @@ function createNatsTransport(options = {}) {
|
|
|
121
170
|
message
|
|
122
171
|
} : message);
|
|
123
172
|
if (cls === "auth-expired") {
|
|
173
|
+
if (resolveServers) {
|
|
174
|
+
resign(message);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
124
177
|
emitter.emit("auth-error", { message });
|
|
125
178
|
return;
|
|
126
179
|
}
|
|
@@ -135,6 +188,7 @@ function createNatsTransport(options = {}) {
|
|
|
135
188
|
logger?.debug(`rebuildClient start (epoch=${epoch})`);
|
|
136
189
|
const t0 = Date.now();
|
|
137
190
|
stopStatusMonitor();
|
|
191
|
+
stopRefresh();
|
|
138
192
|
pendingConnectAbort?.abort();
|
|
139
193
|
if (proxy) {
|
|
140
194
|
try {
|
|
@@ -144,8 +198,19 @@ function createNatsTransport(options = {}) {
|
|
|
144
198
|
notifyClient();
|
|
145
199
|
}
|
|
146
200
|
connId = newConnId();
|
|
201
|
+
let servers;
|
|
202
|
+
try {
|
|
203
|
+
servers = await serversFor(target);
|
|
204
|
+
} catch (err) {
|
|
205
|
+
if (disposed || epoch !== applyEpoch) return;
|
|
206
|
+
const msg = stringifyError(err);
|
|
207
|
+
logger?.debug(`rebuildClient resolve FAILED: ${msg}`);
|
|
208
|
+
markDown(msg);
|
|
209
|
+
throw err;
|
|
210
|
+
}
|
|
211
|
+
if (disposed || epoch !== applyEpoch) return;
|
|
147
212
|
const next = createRPCClient({
|
|
148
|
-
servers
|
|
213
|
+
servers,
|
|
149
214
|
name: clientName,
|
|
150
215
|
connId,
|
|
151
216
|
auth: {
|
|
@@ -175,7 +240,7 @@ function createNatsTransport(options = {}) {
|
|
|
175
240
|
if (disposed || epoch !== applyEpoch) return;
|
|
176
241
|
const msg = err instanceof Error ? err.message : String(err);
|
|
177
242
|
logger?.debug(`rebuildClient connect FAILED after ${Date.now() - t0}ms: ${msg}`);
|
|
178
|
-
if (classifyClose(err instanceof Error ? err : msg) === "auth-expired") emitter.emit("auth-error", { message: msg });
|
|
243
|
+
if (!resolveServers && classifyClose(err instanceof Error ? err : msg) === "auth-expired") emitter.emit("auth-error", { message: msg });
|
|
179
244
|
markDown(msg);
|
|
180
245
|
throw err;
|
|
181
246
|
} finally {
|
|
@@ -192,6 +257,7 @@ function createNatsTransport(options = {}) {
|
|
|
192
257
|
markUp();
|
|
193
258
|
notifyClient();
|
|
194
259
|
startStatusMonitor(next);
|
|
260
|
+
startRefresh(next);
|
|
195
261
|
}
|
|
196
262
|
async function apply(target) {
|
|
197
263
|
if (disposed) throw new Error("nats-transport disposed");
|
|
@@ -201,6 +267,7 @@ function createNatsTransport(options = {}) {
|
|
|
201
267
|
currentTarget = target;
|
|
202
268
|
if (!target) {
|
|
203
269
|
stopStatusMonitor();
|
|
270
|
+
stopRefresh();
|
|
204
271
|
pendingConnectAbort?.abort();
|
|
205
272
|
pendingConnectAbort = null;
|
|
206
273
|
if (proxy) {
|
|
@@ -222,8 +289,9 @@ function createNatsTransport(options = {}) {
|
|
|
222
289
|
}
|
|
223
290
|
return;
|
|
224
291
|
}
|
|
225
|
-
const
|
|
226
|
-
|
|
292
|
+
const p = proxy;
|
|
293
|
+
const newServers = await serversFor(target);
|
|
294
|
+
if (proxy === p && epoch === applyEpoch) p.setServers(newServers);
|
|
227
295
|
}
|
|
228
296
|
function health() {
|
|
229
297
|
if (status.up && proxy?.isStale) return {
|
|
@@ -239,6 +307,7 @@ function createNatsTransport(options = {}) {
|
|
|
239
307
|
disposed = true;
|
|
240
308
|
applyEpoch++;
|
|
241
309
|
stopStatusMonitor();
|
|
310
|
+
stopRefresh();
|
|
242
311
|
pendingConnectAbort?.abort();
|
|
243
312
|
pendingConnectAbort = null;
|
|
244
313
|
if (proxy) {
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { Manager, Socket } from 'socket.io-client';
|
|
2
2
|
import { Logger } from '@camera.ui/logger';
|
|
3
|
-
import { TransportSpec, TransportStatus } from '../core/types.js';
|
|
3
|
+
import { ConnectionTarget, TransportSpec, TransportStatus } from '../core/types.js';
|
|
4
4
|
import { Transport } from './contract.js';
|
|
5
5
|
export type { Socket };
|
|
6
|
+
export interface ResolveSocketQueryContext {
|
|
7
|
+
readonly target: ConnectionTarget;
|
|
8
|
+
readonly path: string;
|
|
9
|
+
readonly query: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
export type SocketQueryResolver = (ctx: ResolveSocketQueryContext) => Promise<Record<string, string>>;
|
|
6
12
|
export interface SocketioTransportOptions {
|
|
7
13
|
readonly spec?: Partial<TransportSpec>;
|
|
8
14
|
readonly path?: string;
|
|
15
|
+
readonly resolveQuery?: SocketQueryResolver;
|
|
16
|
+
readonly resolveRefreshMs?: number;
|
|
9
17
|
readonly mainNamespace?: string;
|
|
10
18
|
readonly reconnection?: boolean;
|
|
11
19
|
readonly reconnectionDelay?: number;
|
|
@@ -19,10 +19,14 @@ function createSocketioTransport(options = {}) {
|
|
|
19
19
|
const reconnectionDelayMax = options.reconnectionDelayMax ?? 5e3;
|
|
20
20
|
const timeout = options.timeout ?? 2e4;
|
|
21
21
|
const logger = options.logger;
|
|
22
|
+
const resolveQuery = options.resolveQuery;
|
|
23
|
+
const resolveRefreshMs = options.resolveRefreshMs ?? 0;
|
|
22
24
|
const emitter = new TransportEmitter();
|
|
23
25
|
const sockets = /* @__PURE__ */ new Map();
|
|
24
26
|
let manager = null;
|
|
25
27
|
let currentTarget = null;
|
|
28
|
+
let currentQuery = {};
|
|
29
|
+
let refreshTimer = null;
|
|
26
30
|
let status = { up: false };
|
|
27
31
|
let disposed = false;
|
|
28
32
|
const staleAfterMs = options.staleAfterMs ?? 6e4;
|
|
@@ -43,6 +47,38 @@ function createSocketioTransport(options = {}) {
|
|
|
43
47
|
if (target.tokens.proxySession) q.session = target.tokens.proxySession;
|
|
44
48
|
return q;
|
|
45
49
|
}
|
|
50
|
+
async function queryFor(target) {
|
|
51
|
+
const base = buildQuery(target);
|
|
52
|
+
if (!resolveQuery) return base;
|
|
53
|
+
return resolveQuery({
|
|
54
|
+
target,
|
|
55
|
+
path: socketPath(target),
|
|
56
|
+
query: base
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async function refreshQuery() {
|
|
60
|
+
const target = currentTarget;
|
|
61
|
+
if (!resolveQuery || !target || disposed) return;
|
|
62
|
+
try {
|
|
63
|
+
const query = await queryFor(target);
|
|
64
|
+
if (currentTarget !== target || disposed) return;
|
|
65
|
+
currentQuery = query;
|
|
66
|
+
for (const sock of sockets.values()) sock.io.opts.query = query;
|
|
67
|
+
} catch (err) {
|
|
68
|
+
logger?.warn(`query refresh failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function stopRefresh() {
|
|
72
|
+
if (refreshTimer) {
|
|
73
|
+
clearInterval(refreshTimer);
|
|
74
|
+
refreshTimer = null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function startRefresh() {
|
|
78
|
+
stopRefresh();
|
|
79
|
+
if (!resolveQuery || resolveRefreshMs <= 0) return;
|
|
80
|
+
refreshTimer = setInterval(() => void refreshQuery(), resolveRefreshMs);
|
|
81
|
+
}
|
|
46
82
|
function markUp() {
|
|
47
83
|
if (status.up) return;
|
|
48
84
|
status = { up: true };
|
|
@@ -63,7 +99,10 @@ function createSocketioTransport(options = {}) {
|
|
|
63
99
|
}
|
|
64
100
|
function bindMainSocketEvents(socket) {
|
|
65
101
|
socket.on("connect", () => markUp());
|
|
66
|
-
socket.on("disconnect", (reason) =>
|
|
102
|
+
socket.on("disconnect", (reason) => {
|
|
103
|
+
markDown(reason);
|
|
104
|
+
if (resolveQuery) refreshQuery();
|
|
105
|
+
});
|
|
67
106
|
socket.on("connect_error", (err) => {
|
|
68
107
|
const msg = err?.message ?? "connect_error";
|
|
69
108
|
logger?.debug(`connect_error (main): ${msg}`);
|
|
@@ -87,10 +126,11 @@ function createSocketioTransport(options = {}) {
|
|
|
87
126
|
return `${new URL(target.endpoint.url).pathname.replace(/\/$/, "")}${path}`;
|
|
88
127
|
}
|
|
89
128
|
function openSocket(namespace, target) {
|
|
90
|
-
const
|
|
129
|
+
const url = `${socketOrigin(target)}${namespace}`;
|
|
130
|
+
const sock = io(url, {
|
|
91
131
|
path: socketPath(target),
|
|
92
132
|
auth: buildAuth(target),
|
|
93
|
-
query:
|
|
133
|
+
query: currentQuery,
|
|
94
134
|
reconnection,
|
|
95
135
|
reconnectionDelay,
|
|
96
136
|
reconnectionDelayMax,
|
|
@@ -101,8 +141,18 @@ function createSocketioTransport(options = {}) {
|
|
|
101
141
|
sockets.set(namespace, sock);
|
|
102
142
|
return sock;
|
|
103
143
|
}
|
|
104
|
-
function rebuildManager(target) {
|
|
144
|
+
async function rebuildManager(target) {
|
|
145
|
+
stopRefresh();
|
|
146
|
+
let query;
|
|
147
|
+
try {
|
|
148
|
+
query = await queryFor(target);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
markDown(err instanceof Error ? err.message : String(err));
|
|
151
|
+
throw err;
|
|
152
|
+
}
|
|
153
|
+
if (currentTarget !== target || disposed) return;
|
|
105
154
|
closeAllSockets();
|
|
155
|
+
currentQuery = query;
|
|
106
156
|
manager = new Manager(socketOrigin(target), {
|
|
107
157
|
path: socketPath(target),
|
|
108
158
|
autoConnect: false,
|
|
@@ -115,6 +165,7 @@ function createSocketioTransport(options = {}) {
|
|
|
115
165
|
});
|
|
116
166
|
const main = openSocket(mainNs, target);
|
|
117
167
|
bindMainSocketEvents(main);
|
|
168
|
+
startRefresh();
|
|
118
169
|
lastActivityAt = Date.now();
|
|
119
170
|
main.io.on("open", touchActivity);
|
|
120
171
|
main.io.on("ping", touchActivity);
|
|
@@ -145,12 +196,13 @@ function createSocketioTransport(options = {}) {
|
|
|
145
196
|
currentTarget = target;
|
|
146
197
|
if (!target) {
|
|
147
198
|
closeAllSockets();
|
|
199
|
+
stopRefresh();
|
|
148
200
|
lastActivityAt = 0;
|
|
149
201
|
markDown("detached");
|
|
150
202
|
return;
|
|
151
203
|
}
|
|
152
204
|
if (endpointChanged || !manager) {
|
|
153
|
-
rebuildManager(target);
|
|
205
|
+
await rebuildManager(target);
|
|
154
206
|
return;
|
|
155
207
|
}
|
|
156
208
|
rebindAuth(target);
|
|
@@ -168,6 +220,7 @@ function createSocketioTransport(options = {}) {
|
|
|
168
220
|
async function dispose() {
|
|
169
221
|
disposed = true;
|
|
170
222
|
closeAllSockets();
|
|
223
|
+
stopRefresh();
|
|
171
224
|
currentTarget = null;
|
|
172
225
|
status = { up: false };
|
|
173
226
|
emitter.clear();
|
|
@@ -176,7 +229,7 @@ function createSocketioTransport(options = {}) {
|
|
|
176
229
|
return sockets.get(namespace) ?? null;
|
|
177
230
|
}
|
|
178
231
|
function ensureSocket(namespace) {
|
|
179
|
-
if (!currentTarget) return null;
|
|
232
|
+
if (!currentTarget || !manager) return null;
|
|
180
233
|
let sock = sockets.get(namespace);
|
|
181
234
|
if (!sock) {
|
|
182
235
|
sock = openSocket(namespace, currentTarget);
|
|
@@ -203,6 +256,7 @@ function createSocketioTransport(options = {}) {
|
|
|
203
256
|
}
|
|
204
257
|
async function ensureAlive() {
|
|
205
258
|
if (disposed || !currentTarget) return health();
|
|
259
|
+
if (resolveQuery) await refreshQuery();
|
|
206
260
|
reviveDeadSockets();
|
|
207
261
|
return health();
|
|
208
262
|
}
|
package/dist/transports/ws.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Logger } from '@camera.ui/logger';
|
|
2
|
-
import { TransportSpec } from '../core/types.js';
|
|
2
|
+
import { ConnectionTarget, TransportSpec } from '../core/types.js';
|
|
3
3
|
import { PerResourceTransport, Unsubscribe } from './contract.js';
|
|
4
4
|
export interface WsHandleSpec {
|
|
5
5
|
readonly path: string;
|
|
@@ -23,12 +23,19 @@ export interface WsHandle {
|
|
|
23
23
|
on<E extends WsEvent>(event: E, handler: WsEventHandler<E>): Unsubscribe;
|
|
24
24
|
dispose(): void;
|
|
25
25
|
}
|
|
26
|
+
export interface ResolveWsUrlContext {
|
|
27
|
+
readonly target: ConnectionTarget;
|
|
28
|
+
readonly spec: WsHandleSpec;
|
|
29
|
+
readonly defaultUrl: string;
|
|
30
|
+
}
|
|
31
|
+
export type WsUrlResolver = (ctx: ResolveWsUrlContext) => Promise<string>;
|
|
26
32
|
export interface WsTransportOptions {
|
|
27
33
|
readonly spec?: Partial<TransportSpec>;
|
|
28
34
|
readonly webSocketCtor?: typeof WebSocket;
|
|
29
35
|
readonly tokenParam?: string;
|
|
30
36
|
readonly sessionParam?: string;
|
|
31
37
|
readonly logger?: Logger;
|
|
38
|
+
readonly resolveUrl?: WsUrlResolver;
|
|
32
39
|
}
|
|
33
40
|
export interface WsTransport extends PerResourceTransport<WsHandle, WsHandleSpec> {
|
|
34
41
|
readonly handleCount: number;
|
package/dist/transports/ws.js
CHANGED
|
@@ -9,6 +9,7 @@ var WS_SPEC = {
|
|
|
9
9
|
var WS_CLOSE_TARGET_CHANGED = 4e3;
|
|
10
10
|
var WS_CLOSE_DETACHED = 4001;
|
|
11
11
|
var WS_CLOSE_DISPOSED = 4002;
|
|
12
|
+
var WS_CLOSE_RESOLVE_FAILED = 4003;
|
|
12
13
|
function createWsTransport(options = {}) {
|
|
13
14
|
const spec = {
|
|
14
15
|
...WS_SPEC,
|
|
@@ -17,6 +18,7 @@ function createWsTransport(options = {}) {
|
|
|
17
18
|
const tokenParam = options.tokenParam ?? "token";
|
|
18
19
|
const sessionParam = options.sessionParam ?? "session";
|
|
19
20
|
const WsCtor = options.webSocketCtor ?? (typeof WebSocket !== "undefined" ? WebSocket : void 0);
|
|
21
|
+
const resolveUrl = options.resolveUrl;
|
|
20
22
|
const logger = options.logger;
|
|
21
23
|
const emitter = new TransportEmitter();
|
|
22
24
|
const handles = /* @__PURE__ */ new Set();
|
|
@@ -83,6 +85,7 @@ function createWsTransport(options = {}) {
|
|
|
83
85
|
};
|
|
84
86
|
}
|
|
85
87
|
function closeWs(handle, code, reason) {
|
|
88
|
+
handle.openSeq++;
|
|
86
89
|
const ws = handle.ws;
|
|
87
90
|
if (!ws) return;
|
|
88
91
|
const Ctor = ensureCtor();
|
|
@@ -105,16 +108,42 @@ function createWsTransport(options = {}) {
|
|
|
105
108
|
};
|
|
106
109
|
for (const fn of [...handle.listeners.close]) fn(info);
|
|
107
110
|
}
|
|
108
|
-
function
|
|
109
|
-
if (handle.disposed) return;
|
|
110
|
-
if (!currentTarget) return;
|
|
111
|
-
const url = buildUrl(currentTarget, handle.spec);
|
|
111
|
+
function connectWs(handle, url) {
|
|
112
112
|
const ws = new (ensureCtor())(url, handle.spec.protocols);
|
|
113
113
|
if (handle.spec.binaryType) ws.binaryType = handle.spec.binaryType;
|
|
114
114
|
handle.ws = ws;
|
|
115
115
|
handle.url = url;
|
|
116
116
|
bindWs(handle, ws);
|
|
117
117
|
}
|
|
118
|
+
function openWs(handle) {
|
|
119
|
+
if (handle.disposed) return;
|
|
120
|
+
if (!currentTarget) return;
|
|
121
|
+
const target = currentTarget;
|
|
122
|
+
const defaultUrl = buildUrl(target, handle.spec);
|
|
123
|
+
if (!resolveUrl) {
|
|
124
|
+
connectWs(handle, defaultUrl);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const seq = ++handle.openSeq;
|
|
128
|
+
resolveUrl({
|
|
129
|
+
target,
|
|
130
|
+
spec: handle.spec,
|
|
131
|
+
defaultUrl
|
|
132
|
+
}).then((url) => {
|
|
133
|
+
if (handle.disposed || seq !== handle.openSeq || currentTarget !== target) return;
|
|
134
|
+
connectWs(handle, url);
|
|
135
|
+
}, (err) => {
|
|
136
|
+
if (handle.disposed || seq !== handle.openSeq) return;
|
|
137
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
138
|
+
logger?.warn(`resolveUrl failed for ${handle.spec.path}: ${reason}`);
|
|
139
|
+
const info = {
|
|
140
|
+
code: WS_CLOSE_RESOLVE_FAILED,
|
|
141
|
+
reason: "resolve-failed",
|
|
142
|
+
wasClean: false
|
|
143
|
+
};
|
|
144
|
+
for (const fn of [...handle.listeners.close]) fn(info);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
118
147
|
function recycleAll(code, reason) {
|
|
119
148
|
const snapshot = [...handles];
|
|
120
149
|
logger?.debug(`recycleAll (${reason}) handles=${snapshot.length}`);
|
|
@@ -178,7 +207,8 @@ function createWsTransport(options = {}) {
|
|
|
178
207
|
},
|
|
179
208
|
ws: null,
|
|
180
209
|
url: null,
|
|
181
|
-
disposed: false
|
|
210
|
+
disposed: false,
|
|
211
|
+
openSeq: 0
|
|
182
212
|
};
|
|
183
213
|
function send(data) {
|
|
184
214
|
if (handle.disposed) throw new Error("ws-handle disposed");
|
package/dist/worker/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { createWorkerKernelMirror } from './mirror.js';
|
|
2
|
-
export type { MirrorListener, Unsubscribe, WorkerKernelMirror, WorkerKernelMirrorOptions } from './mirror.js';
|
|
3
|
-
export type { KernelSyncMessage, KernelSyncRequestMessage, MessageSource, WorkerHost, WorkerMessage } from './protocol.js';
|
|
2
|
+
export type { MirrorListener, MirrorResolveContext, Unsubscribe, WorkerKernelMirror, WorkerKernelMirrorOptions } from './mirror.js';
|
|
3
|
+
export type { KernelResolveReplyMessage, KernelResolveRequestMessage, KernelSyncMessage, KernelSyncRequestMessage, MessageSource, WorkerHost, WorkerMessage, } from './protocol.js';
|
package/dist/worker/mirror.d.ts
CHANGED
|
@@ -2,16 +2,23 @@ import { ConnectionPhase, ConnectionTarget } from '../core/types.js';
|
|
|
2
2
|
import { MessageSource } from './protocol.js';
|
|
3
3
|
export type Unsubscribe = () => void;
|
|
4
4
|
export type MirrorListener = (next: ConnectionPhase, prev: ConnectionPhase) => void;
|
|
5
|
+
export interface MirrorResolveContext {
|
|
6
|
+
readonly connId: string;
|
|
7
|
+
readonly defaultServers: readonly string[];
|
|
8
|
+
}
|
|
5
9
|
export interface WorkerKernelMirror {
|
|
6
10
|
readonly phase: ConnectionPhase;
|
|
7
11
|
readonly target: ConnectionTarget | null;
|
|
12
|
+
readonly hasResolver: boolean;
|
|
8
13
|
subscribe(listener: MirrorListener): Unsubscribe;
|
|
9
14
|
onRevalidate(listener: () => void): Unsubscribe;
|
|
10
15
|
requestSync(): void;
|
|
16
|
+
resolveServers(ctx: MirrorResolveContext): Promise<string[]>;
|
|
11
17
|
dispose(): void;
|
|
12
18
|
}
|
|
13
19
|
export interface WorkerKernelMirrorOptions {
|
|
14
20
|
readonly source: MessageSource;
|
|
15
21
|
readonly initial?: ConnectionPhase;
|
|
22
|
+
readonly resolveTimeoutMs?: number;
|
|
16
23
|
}
|
|
17
24
|
export declare function createWorkerKernelMirror(options: WorkerKernelMirrorOptions): WorkerKernelMirror;
|
|
@@ -3,6 +3,7 @@ export interface KernelSyncMessage {
|
|
|
3
3
|
readonly type: 'kernel-sync';
|
|
4
4
|
readonly generation: number;
|
|
5
5
|
readonly phase: ConnectionPhase;
|
|
6
|
+
readonly resolver?: boolean;
|
|
6
7
|
}
|
|
7
8
|
export interface KernelSyncRequestMessage {
|
|
8
9
|
readonly type: 'kernel-sync-request';
|
|
@@ -10,7 +11,19 @@ export interface KernelSyncRequestMessage {
|
|
|
10
11
|
export interface KernelRevalidateMessage {
|
|
11
12
|
readonly type: 'kernel-revalidate';
|
|
12
13
|
}
|
|
13
|
-
export
|
|
14
|
+
export interface KernelResolveRequestMessage {
|
|
15
|
+
readonly type: 'kernel-resolve-request';
|
|
16
|
+
readonly id: number;
|
|
17
|
+
readonly connId: string;
|
|
18
|
+
readonly defaultServers: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
export interface KernelResolveReplyMessage {
|
|
21
|
+
readonly type: 'kernel-resolve-reply';
|
|
22
|
+
readonly id: number;
|
|
23
|
+
readonly servers?: string[];
|
|
24
|
+
readonly error?: string;
|
|
25
|
+
}
|
|
26
|
+
export type WorkerMessage = KernelSyncMessage | KernelSyncRequestMessage | KernelRevalidateMessage | KernelResolveRequestMessage | KernelResolveReplyMessage;
|
|
14
27
|
export interface MessageSource {
|
|
15
28
|
postMessage(message: WorkerMessage): void;
|
|
16
29
|
addEventListener(type: 'message', listener: (event: MessageEvent<WorkerMessage>) => void): void;
|
package/dist/worker.js
CHANGED
|
@@ -2,13 +2,29 @@
|
|
|
2
2
|
function createWorkerKernelMirror(options) {
|
|
3
3
|
let current = options.initial ?? { kind: "idle" };
|
|
4
4
|
let lastGeneration = -1;
|
|
5
|
+
let hasResolver = false;
|
|
5
6
|
const listeners = /* @__PURE__ */ new Set();
|
|
6
7
|
const revalidateListeners = /* @__PURE__ */ new Set();
|
|
8
|
+
const pending = /* @__PURE__ */ new Map();
|
|
9
|
+
const resolveTimeoutMs = options.resolveTimeoutMs ?? 15e3;
|
|
10
|
+
let nextResolveId = 1;
|
|
7
11
|
let disposed = false;
|
|
12
|
+
function settleResolve(id, servers, error) {
|
|
13
|
+
const entry = pending.get(id);
|
|
14
|
+
if (!entry) return;
|
|
15
|
+
pending.delete(id);
|
|
16
|
+
clearTimeout(entry.timer);
|
|
17
|
+
if (error !== void 0 || !servers) entry.reject(new Error(error ?? "resolve failed"));
|
|
18
|
+
else entry.resolve(servers);
|
|
19
|
+
}
|
|
8
20
|
function handler(event) {
|
|
9
21
|
if (disposed) return;
|
|
10
22
|
const msg = event.data;
|
|
11
23
|
if (!msg) return;
|
|
24
|
+
if (msg.type === "kernel-resolve-reply") {
|
|
25
|
+
settleResolve(msg.id, msg.servers, msg.error);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
12
28
|
if (msg.type === "kernel-revalidate") {
|
|
13
29
|
for (const l of [...revalidateListeners]) try {
|
|
14
30
|
l();
|
|
@@ -18,6 +34,7 @@ function createWorkerKernelMirror(options) {
|
|
|
18
34
|
if (msg.type !== "kernel-sync") return;
|
|
19
35
|
if (msg.generation <= lastGeneration) return;
|
|
20
36
|
lastGeneration = msg.generation;
|
|
37
|
+
hasResolver = msg.resolver === true;
|
|
21
38
|
const prev = current;
|
|
22
39
|
current = msg.phase;
|
|
23
40
|
if (phaseEquals(prev, current)) return;
|
|
@@ -44,6 +61,9 @@ function createWorkerKernelMirror(options) {
|
|
|
44
61
|
get target() {
|
|
45
62
|
return targetOf(current);
|
|
46
63
|
},
|
|
64
|
+
get hasResolver() {
|
|
65
|
+
return hasResolver;
|
|
66
|
+
},
|
|
47
67
|
subscribe(listener) {
|
|
48
68
|
listeners.add(listener);
|
|
49
69
|
return () => {
|
|
@@ -60,11 +80,30 @@ function createWorkerKernelMirror(options) {
|
|
|
60
80
|
if (disposed) return;
|
|
61
81
|
options.source.postMessage({ type: "kernel-sync-request" });
|
|
62
82
|
},
|
|
83
|
+
resolveServers(ctx) {
|
|
84
|
+
if (disposed) return Promise.reject(/* @__PURE__ */ new Error("mirror disposed"));
|
|
85
|
+
const id = nextResolveId++;
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
const timer = setTimeout(() => settleResolve(id, void 0, "resolve timeout"), resolveTimeoutMs);
|
|
88
|
+
pending.set(id, {
|
|
89
|
+
resolve,
|
|
90
|
+
reject,
|
|
91
|
+
timer
|
|
92
|
+
});
|
|
93
|
+
options.source.postMessage({
|
|
94
|
+
type: "kernel-resolve-request",
|
|
95
|
+
id,
|
|
96
|
+
connId: ctx.connId,
|
|
97
|
+
defaultServers: [...ctx.defaultServers]
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
},
|
|
63
101
|
dispose() {
|
|
64
102
|
disposed = true;
|
|
65
103
|
options.source.removeEventListener("message", handler);
|
|
66
104
|
listeners.clear();
|
|
67
105
|
revalidateListeners.clear();
|
|
106
|
+
for (const id of [...pending.keys()]) settleResolve(id, void 0, "mirror disposed");
|
|
68
107
|
}
|
|
69
108
|
};
|
|
70
109
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camera.ui/transport",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.27",
|
|
4
4
|
"description": "camera.ui transport layer — framework-agnostic connection kernel, reducer state, pluggable transports (HTTP/WS/Socket.IO/NATS), lifecycle effects and worker bridge",
|
|
5
5
|
"author": "seydx (https://github.com/cameraui/clients)",
|
|
6
6
|
"type": "module",
|
|
@@ -56,26 +56,26 @@
|
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@camera.ui/logger": ">=0.0.3",
|
|
59
|
-
"@camera.ui/rpc": ">=1.0.
|
|
60
|
-
"axios": ">=1.
|
|
59
|
+
"@camera.ui/rpc": ">=1.0.11",
|
|
60
|
+
"axios": ">=1.20.0",
|
|
61
61
|
"socket.io-client": ">=4.8.3"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@eslint/js": "9.39.4",
|
|
65
65
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
66
|
-
"@typescript-eslint/parser": "^8.
|
|
67
|
-
"@vue/language-core": "^3.3.
|
|
66
|
+
"@typescript-eslint/parser": "^8.68.0",
|
|
67
|
+
"@vue/language-core": "^3.3.11",
|
|
68
68
|
"eslint": "9.39.2",
|
|
69
|
-
"globals": "^17.
|
|
69
|
+
"globals": "^17.11.0",
|
|
70
70
|
"jiti": "^2.7.0",
|
|
71
71
|
"prettier": "^3.9.6",
|
|
72
72
|
"rimraf": "^6.1.3",
|
|
73
73
|
"typescript": "5.9.3",
|
|
74
|
-
"typescript-eslint": "^8.
|
|
74
|
+
"typescript-eslint": "^8.68.0",
|
|
75
75
|
"unplugin-dts": "^1.0.3",
|
|
76
|
-
"updates": "^
|
|
77
|
-
"vite": "^8.
|
|
78
|
-
"vitest": "^4.1.
|
|
76
|
+
"updates": "^18.0.7",
|
|
77
|
+
"vite": "^8.2.2",
|
|
78
|
+
"vitest": "^4.1.11"
|
|
79
79
|
},
|
|
80
80
|
"overrides": {
|
|
81
81
|
"@vue/language-core": "$@vue/language-core"
|