devflare 1.0.0-next.34 → 1.0.0-next.36
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/LICENSE +21 -0
- package/LLM.md +2 -0
- package/README.md +5 -4
- package/bin/devflare.js +1 -1
- package/dist/{account-p9eywryw.js → account-4tgh03a8.js} +4 -6
- package/dist/bridge/client.d.ts +1 -1
- package/dist/bridge/v2/control-messages.d.ts +1 -6
- package/dist/bridge/v2/control-messages.d.ts.map +1 -1
- package/dist/bridge/v2/wire.d.ts +4 -4
- package/dist/bridge/v2/wire.d.ts.map +1 -1
- package/dist/browser.js +2340 -19
- package/dist/{build-8bn7frg8.js → build-rh7ghrgw.js} +7 -10
- package/dist/cli/index.js +1377 -6
- package/dist/cloudflare/index.js +2 -3
- package/dist/config-entry.js +1 -9
- package/dist/{config-gphd6wx7.js → config-ghk2bxj3.js} +3 -6
- package/dist/{deploy-vpcp41fw.js → deploy-6hzv76sh.js} +13 -17
- package/dist/{dev-9scy9gap.js → dev-mkhjdwh7.js} +971 -73
- package/dist/{doctor-swfeeqny.js → doctor-0ng5qrr1.js} +2 -3
- package/dist/{index-e7xn30bh.js → index-0cc05hzv.js} +1 -1
- package/dist/{index-t0r7evpw.js → index-293aqfy4.js} +7 -9
- package/dist/{index-3t0e51a0.js → index-2yv7w548.js} +1 -1
- package/dist/{index-ksqth51q.js → index-a8pnvg3r.js} +1 -1
- package/dist/{index-bhxj7ff6.js → index-e251zhns.js} +943 -117
- package/dist/{index-2xz4etye.js → index-hvtd936d.js} +3 -0
- package/dist/{index-13sfensw.js → index-kcp7y951.js} +2 -13
- package/dist/{index-w8c8tjc5.js → index-mek9msfv.js} +1 -4
- package/dist/{index-dem45895.js → index-mjvybn39.js} +1 -1
- package/dist/{index-x0wpeb3x.js → index-phcrvs6d.js} +112 -2
- package/dist/{index-b20xx2vk.js → index-se4kw8tm.js} +4 -6
- package/dist/{index-pd97hk2j.js → index-x8s9rwfh.js} +466 -19
- package/dist/index.js +1 -42
- package/dist/{login-7vyzeqy4.js → login-4h1sfsed.js} +4 -6
- package/dist/{previews-jfbectpc.js → previews-qr7c5j8h.js} +10 -14
- package/dist/{productions-49ct4kjm.js → productions-xpmd6bw8.js} +6 -9
- package/dist/{remote-6876k6mz.js → remote-kqcvhnfx.js} +100 -5
- package/dist/runtime/index.js +0 -63
- package/dist/{secrets-9ne7armr.js → secrets-6wbdsdek.js} +3 -5
- package/dist/sveltekit/index.js +0 -263
- package/dist/test/index.js +2 -4626
- package/dist/{types-zekmbebk.js → types-ahnnpphq.js} +6 -8
- package/dist/vite/index.js +7 -9
- package/dist/{worker-706g8qeh.js → worker-1yg72jxg.js} +6 -9
- package/package.json +5 -3
- package/dist/index-1trss579.js +0 -2299
- package/dist/index-34dejneh.js +0 -214
- package/dist/index-3z20d3kd.js +0 -1380
- package/dist/index-4xmtkg9g.js +0 -109
- package/dist/index-62b3gt2g.js +0 -12
- package/dist/index-97rfy7n2.js +0 -413
- package/dist/index-c4zd39tc.js +0 -155
- package/dist/index-dgx495pv.js +0 -1036
- package/dist/index-dm9q84c7.js +0 -360
- package/dist/index-pmnb7eke.js +0 -109
- package/dist/index-t23wq5js.js +0 -111
- package/dist/index-wxdcrzcp.js +0 -475
- package/dist/index-x2fd1361.js +0 -133
- package/dist/index-za0r01bx.js +0 -588
package/dist/browser.js
CHANGED
|
@@ -1,28 +1,2349 @@
|
|
|
1
|
-
import {
|
|
2
|
-
workerName
|
|
3
|
-
} from "./index-62b3gt2g.js";
|
|
4
|
-
import {
|
|
5
|
-
env,
|
|
6
|
-
vars
|
|
7
|
-
} from "./index-t23wq5js.js";
|
|
8
1
|
import {
|
|
9
2
|
durableObject,
|
|
10
3
|
getDurableObjectOptions
|
|
11
4
|
} from "./index-y7w3x9p1.js";
|
|
12
|
-
import {
|
|
13
|
-
BridgeClient,
|
|
14
|
-
createEnvProxy,
|
|
15
|
-
getClient,
|
|
16
|
-
initEnv,
|
|
17
|
-
setBindingHints
|
|
18
|
-
} from "./index-1trss579.js";
|
|
19
|
-
import"./index-n4xkaymm.js";
|
|
20
|
-
import {
|
|
21
|
-
defineConfig,
|
|
22
|
-
ref
|
|
23
|
-
} from "./index-34dejneh.js";
|
|
24
5
|
import"./index-37x76zdn.js";
|
|
25
6
|
|
|
7
|
+
// src/config/define.ts
|
|
8
|
+
function defineConfig(config) {
|
|
9
|
+
if (typeof config === "function") {
|
|
10
|
+
const result = config();
|
|
11
|
+
if (result instanceof Promise) {
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
return config;
|
|
17
|
+
}
|
|
18
|
+
// src/config/ref.ts
|
|
19
|
+
var resolvedCache = new WeakMap;
|
|
20
|
+
var pendingResolutions = new WeakMap;
|
|
21
|
+
var PENDING_REF_VALUE = "<pending>";
|
|
22
|
+
function extractConfigPathFromImportFn(fn) {
|
|
23
|
+
let source;
|
|
24
|
+
try {
|
|
25
|
+
source = Function.prototype.toString.call(fn);
|
|
26
|
+
} catch {
|
|
27
|
+
return PENDING_REF_VALUE;
|
|
28
|
+
}
|
|
29
|
+
if (!/import\s*\(/.test(source)) {
|
|
30
|
+
return PENDING_REF_VALUE;
|
|
31
|
+
}
|
|
32
|
+
const match = source.match(/import\s*\(\s*(['"`])([^'"`]+)\1\s*\)/);
|
|
33
|
+
const raw = match?.[2];
|
|
34
|
+
if (!raw || raw.length === 0) {
|
|
35
|
+
throw new Error("ref() could not extract a config path from the import function source. " + "The specifier must be a static string literal — dynamic or computed " + "specifiers (e.g. template literals with expressions) are not supported. " + "If this input has been minified, pass an unminified config source.");
|
|
36
|
+
}
|
|
37
|
+
if (match?.[1] === "`" && /\$\{/.test(raw)) {
|
|
38
|
+
throw new Error("ref() import specifier is a template literal with an embedded expression. " + "The specifier must be a static string literal so the config path can " + "be resolved ahead of time.");
|
|
39
|
+
}
|
|
40
|
+
if (raw.length < 2 && !/[./]/.test(raw)) {
|
|
41
|
+
throw new Error(`ref() extracted a suspiciously short config path (${JSON.stringify(raw)}). ` + "This usually indicates a minified bundle where the original specifier " + "was rewritten. Pass an unminified config source.");
|
|
42
|
+
}
|
|
43
|
+
return raw;
|
|
44
|
+
}
|
|
45
|
+
function ref(nameOrImport, maybeImport) {
|
|
46
|
+
const nameOverride = typeof nameOrImport === "string" ? nameOrImport : undefined;
|
|
47
|
+
let importFn;
|
|
48
|
+
if (typeof nameOrImport === "function") {
|
|
49
|
+
importFn = nameOrImport;
|
|
50
|
+
} else if (typeof maybeImport === "function") {
|
|
51
|
+
importFn = maybeImport;
|
|
52
|
+
}
|
|
53
|
+
if (!importFn) {
|
|
54
|
+
throw new Error("ref() requires an import function");
|
|
55
|
+
}
|
|
56
|
+
const resolvedImportFn = importFn;
|
|
57
|
+
const configPath = extractConfigPathFromImportFn(resolvedImportFn);
|
|
58
|
+
const doBindingCache = new Map;
|
|
59
|
+
async function doResolve() {
|
|
60
|
+
const cached = resolvedCache.get(proxy);
|
|
61
|
+
if (cached)
|
|
62
|
+
return cached;
|
|
63
|
+
const pending = pendingResolutions.get(proxy);
|
|
64
|
+
if (pending)
|
|
65
|
+
return pending;
|
|
66
|
+
const promise = (async () => {
|
|
67
|
+
const module = await resolvedImportFn();
|
|
68
|
+
const config = "default" in module ? module.default : module;
|
|
69
|
+
if (!config.name && !nameOverride) {
|
|
70
|
+
throw new Error('Referenced config must have a "name" property');
|
|
71
|
+
}
|
|
72
|
+
const resolved = {
|
|
73
|
+
name: nameOverride ?? config.name,
|
|
74
|
+
config,
|
|
75
|
+
configPath
|
|
76
|
+
};
|
|
77
|
+
resolvedCache.set(proxy, resolved);
|
|
78
|
+
return resolved;
|
|
79
|
+
})();
|
|
80
|
+
const trackedPromise = promise.finally(() => {
|
|
81
|
+
pendingResolutions.delete(proxy);
|
|
82
|
+
});
|
|
83
|
+
pendingResolutions.set(proxy, trackedPromise);
|
|
84
|
+
return trackedPromise;
|
|
85
|
+
}
|
|
86
|
+
function getResolved() {
|
|
87
|
+
const cached = resolvedCache.get(proxy);
|
|
88
|
+
if (cached)
|
|
89
|
+
return cached;
|
|
90
|
+
throw new Error("ref() not yet resolved. Call ref().resolve() first, or use top-level await " + "in your config file to resolve all refs before exporting.");
|
|
91
|
+
}
|
|
92
|
+
function createWorkerBinding(entrypoint) {
|
|
93
|
+
return {
|
|
94
|
+
get service() {
|
|
95
|
+
const cached = resolvedCache.get(proxy);
|
|
96
|
+
if (cached)
|
|
97
|
+
return cached.name;
|
|
98
|
+
if (nameOverride)
|
|
99
|
+
return nameOverride;
|
|
100
|
+
return PENDING_REF_VALUE;
|
|
101
|
+
},
|
|
102
|
+
entrypoint,
|
|
103
|
+
__ref: proxy
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const workerAccessor = new Proxy((entrypoint) => createWorkerBinding(entrypoint), {
|
|
107
|
+
get(target, prop) {
|
|
108
|
+
if (prop === "service") {
|
|
109
|
+
const cached = resolvedCache.get(proxy);
|
|
110
|
+
if (cached)
|
|
111
|
+
return cached.name;
|
|
112
|
+
if (nameOverride)
|
|
113
|
+
return nameOverride;
|
|
114
|
+
return PENDING_REF_VALUE;
|
|
115
|
+
}
|
|
116
|
+
if (prop === "entrypoint")
|
|
117
|
+
return;
|
|
118
|
+
if (prop === "__ref")
|
|
119
|
+
return proxy;
|
|
120
|
+
return Reflect.get(target, prop);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
function createDOBinding(bindingName) {
|
|
124
|
+
const cachedBinding = doBindingCache.get(bindingName);
|
|
125
|
+
if (cachedBinding) {
|
|
126
|
+
return cachedBinding;
|
|
127
|
+
}
|
|
128
|
+
const doBinding = {
|
|
129
|
+
get className() {
|
|
130
|
+
const cached = resolvedCache.get(proxy);
|
|
131
|
+
if (cached?.config.bindings?.durableObjects) {
|
|
132
|
+
const doBindings = cached.config.bindings.durableObjects;
|
|
133
|
+
const doConfig = doBindings[bindingName];
|
|
134
|
+
if (typeof doConfig === "string") {
|
|
135
|
+
return doConfig;
|
|
136
|
+
}
|
|
137
|
+
if (doConfig && typeof doConfig === "object" && "className" in doConfig) {
|
|
138
|
+
return doConfig.className;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return PENDING_REF_VALUE;
|
|
142
|
+
},
|
|
143
|
+
get scriptName() {
|
|
144
|
+
const cached = resolvedCache.get(proxy);
|
|
145
|
+
if (cached)
|
|
146
|
+
return cached.name;
|
|
147
|
+
if (nameOverride)
|
|
148
|
+
return nameOverride;
|
|
149
|
+
return PENDING_REF_VALUE;
|
|
150
|
+
},
|
|
151
|
+
kind: "cross-worker",
|
|
152
|
+
__ref: proxy
|
|
153
|
+
};
|
|
154
|
+
doBindingCache.set(bindingName, doBinding);
|
|
155
|
+
return doBinding;
|
|
156
|
+
}
|
|
157
|
+
const knownProps = new Set([
|
|
158
|
+
"name",
|
|
159
|
+
"config",
|
|
160
|
+
"configPath",
|
|
161
|
+
"worker",
|
|
162
|
+
"__import",
|
|
163
|
+
"__nameOverride",
|
|
164
|
+
"resolve",
|
|
165
|
+
"then"
|
|
166
|
+
]);
|
|
167
|
+
const proxyTarget = {
|
|
168
|
+
get name() {
|
|
169
|
+
return getResolved().name;
|
|
170
|
+
},
|
|
171
|
+
get config() {
|
|
172
|
+
return getResolved().config;
|
|
173
|
+
},
|
|
174
|
+
configPath,
|
|
175
|
+
worker: workerAccessor,
|
|
176
|
+
__import: resolvedImportFn,
|
|
177
|
+
__nameOverride: nameOverride,
|
|
178
|
+
resolve: doResolve
|
|
179
|
+
};
|
|
180
|
+
const proxy = new Proxy(proxyTarget, {
|
|
181
|
+
get(target, prop) {
|
|
182
|
+
if (typeof prop === "string" && knownProps.has(prop)) {
|
|
183
|
+
return Reflect.get(target, prop);
|
|
184
|
+
}
|
|
185
|
+
if (typeof prop === "symbol") {
|
|
186
|
+
return Reflect.get(target, prop);
|
|
187
|
+
}
|
|
188
|
+
if (typeof prop === "string" && /^[A-Z][A-Z0-9_]*$/.test(prop)) {
|
|
189
|
+
const cached = resolvedCache.get(proxy);
|
|
190
|
+
if (cached) {
|
|
191
|
+
const doBindings = cached.config.bindings?.durableObjects;
|
|
192
|
+
if (!doBindings || !(prop in doBindings)) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return createDOBinding(prop);
|
|
197
|
+
}
|
|
198
|
+
return Reflect.get(target, prop);
|
|
199
|
+
},
|
|
200
|
+
has(target, prop) {
|
|
201
|
+
if (typeof prop === "string") {
|
|
202
|
+
if (knownProps.has(prop))
|
|
203
|
+
return true;
|
|
204
|
+
if (/^[A-Z][A-Z0-9_]*$/.test(prop)) {
|
|
205
|
+
const cached = resolvedCache.get(proxy);
|
|
206
|
+
if (cached) {
|
|
207
|
+
const doBindings = cached.config.bindings?.durableObjects;
|
|
208
|
+
return !!doBindings && prop in doBindings;
|
|
209
|
+
}
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return Reflect.has(target, prop);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
return proxy;
|
|
217
|
+
}
|
|
218
|
+
// src/workerName.ts
|
|
219
|
+
var workerName = (() => {
|
|
220
|
+
if (typeof __DEVFLARE_WORKER_NAME__ !== "undefined") {
|
|
221
|
+
return __DEVFLARE_WORKER_NAME__;
|
|
222
|
+
}
|
|
223
|
+
if (typeof process !== "undefined" && process.env?.DEVFLARE_WORKER_NAME) {
|
|
224
|
+
return process.env.DEVFLARE_WORKER_NAME;
|
|
225
|
+
}
|
|
226
|
+
return "unknown";
|
|
227
|
+
})();
|
|
228
|
+
// src/bridge/log.ts
|
|
229
|
+
var isDebugEnabled = () => {
|
|
230
|
+
try {
|
|
231
|
+
const env = globalThis.process?.env;
|
|
232
|
+
return Boolean(env?.DEVFLARE_DEBUG_BRIDGE);
|
|
233
|
+
} catch {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
var bridgeLog = {
|
|
238
|
+
warn(message, error) {
|
|
239
|
+
if (!isDebugEnabled())
|
|
240
|
+
return;
|
|
241
|
+
console.warn(`[devflare:bridge] ${message}`, error);
|
|
242
|
+
},
|
|
243
|
+
debug(message, error) {
|
|
244
|
+
if (!isDebugEnabled())
|
|
245
|
+
return;
|
|
246
|
+
console.debug(`[devflare:bridge] ${message}`, error);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// src/bridge/v2/frames.ts
|
|
251
|
+
var TRANSPORT_V2_PROTOCOL_VERSION = 2;
|
|
252
|
+
var TRANSPORT_V2_UNSUPPORTED_VERSION_CLOSE_CODE = 4001;
|
|
253
|
+
var TransportV2BinaryKind = {
|
|
254
|
+
StreamChunk: 1,
|
|
255
|
+
WsData: 2,
|
|
256
|
+
BodyChunk: 3
|
|
257
|
+
};
|
|
258
|
+
var TransportV2BinaryFlags = {
|
|
259
|
+
FIN: 1,
|
|
260
|
+
TEXT: 2,
|
|
261
|
+
ABORT: 4
|
|
262
|
+
};
|
|
263
|
+
var TRANSPORT_V2_BINARY_HEADER_SIZE = 10;
|
|
264
|
+
function decodeTransportV2BinaryFrame(frame) {
|
|
265
|
+
if (frame.byteLength < TRANSPORT_V2_BINARY_HEADER_SIZE) {
|
|
266
|
+
throw new Error(`Invalid transport v2 binary frame: too short (${frame.byteLength} bytes, need at least ${TRANSPORT_V2_BINARY_HEADER_SIZE})`);
|
|
267
|
+
}
|
|
268
|
+
const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
|
|
269
|
+
const kind = view.getUint8(0);
|
|
270
|
+
if (kind !== TransportV2BinaryKind.StreamChunk && kind !== TransportV2BinaryKind.WsData && kind !== TransportV2BinaryKind.BodyChunk) {
|
|
271
|
+
throw new Error(`Invalid transport v2 binary frame: unknown kind ${kind}`);
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
kind,
|
|
275
|
+
id: view.getUint32(1, true),
|
|
276
|
+
seq: view.getUint32(5, true),
|
|
277
|
+
flags: view.getUint8(9),
|
|
278
|
+
payload: frame.subarray(TRANSPORT_V2_BINARY_HEADER_SIZE)
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function transportV2IsFin(flags) {
|
|
282
|
+
return (flags & TransportV2BinaryFlags.FIN) !== 0;
|
|
283
|
+
}
|
|
284
|
+
function transportV2IsAbort(flags) {
|
|
285
|
+
return (flags & TransportV2BinaryFlags.ABORT) !== 0;
|
|
286
|
+
}
|
|
287
|
+
var KNOWN_V2_CONTROL_TYPES = new Set(["hello", "welcome", "body.open", "body.end", "body.abort"]);
|
|
288
|
+
function parseTransportV2ControlMsg(data) {
|
|
289
|
+
const msg = JSON.parse(data);
|
|
290
|
+
if (typeof msg !== "object" || msg === null || !("t" in msg)) {
|
|
291
|
+
throw new Error("Invalid transport v2 control message: missing type field");
|
|
292
|
+
}
|
|
293
|
+
if (!KNOWN_V2_CONTROL_TYPES.has(msg.t)) {
|
|
294
|
+
throw new Error(`Invalid transport v2 control message: unknown type "${msg.t}"`);
|
|
295
|
+
}
|
|
296
|
+
if (msg.t === "hello" || msg.t === "welcome") {
|
|
297
|
+
if (msg.protocolVersion !== TRANSPORT_V2_PROTOCOL_VERSION) {
|
|
298
|
+
throw new Error(`Invalid transport v2 ${msg.t}: protocolVersion ${msg.protocolVersion} != ${TRANSPORT_V2_PROTOCOL_VERSION}`);
|
|
299
|
+
}
|
|
300
|
+
if (!Array.isArray(msg.capabilities)) {
|
|
301
|
+
throw new Error(`Invalid transport v2 ${msg.t}: capabilities must be an array`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return msg;
|
|
305
|
+
}
|
|
306
|
+
function stringifyTransportV2ControlMsg(msg) {
|
|
307
|
+
return JSON.stringify(msg);
|
|
308
|
+
}
|
|
309
|
+
function negotiateTransportV2Capabilities(supported, advertised) {
|
|
310
|
+
const supportedSet = new Set(supported);
|
|
311
|
+
const intersection = advertised.filter((cap) => supportedSet.has(cap));
|
|
312
|
+
return [...new Set(intersection)].sort();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/bridge/v2/body-streams.ts
|
|
316
|
+
var TRANSPORT_V2_DEFAULT_BODY_CHUNK_SIZE = 256 * 1024;
|
|
317
|
+
class TransportV2BodyReaderRegistry {
|
|
318
|
+
#streams = new Map;
|
|
319
|
+
getOrOpen(bid) {
|
|
320
|
+
const existing = this.#streams.get(bid);
|
|
321
|
+
if (existing !== undefined)
|
|
322
|
+
return existing.stream;
|
|
323
|
+
return this.open(bid);
|
|
324
|
+
}
|
|
325
|
+
open(bid) {
|
|
326
|
+
if (this.#streams.has(bid)) {
|
|
327
|
+
throw new Error(`v2 body reader already registered for bid ${bid}`);
|
|
328
|
+
}
|
|
329
|
+
const state = {
|
|
330
|
+
controller: null,
|
|
331
|
+
stream: null,
|
|
332
|
+
closed: false
|
|
333
|
+
};
|
|
334
|
+
state.stream = new ReadableStream({
|
|
335
|
+
start: (controller) => {
|
|
336
|
+
state.controller = controller;
|
|
337
|
+
},
|
|
338
|
+
cancel: () => {
|
|
339
|
+
state.closed = true;
|
|
340
|
+
this.#streams.delete(bid);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
this.#streams.set(bid, state);
|
|
344
|
+
return state.stream;
|
|
345
|
+
}
|
|
346
|
+
pushChunk(frame) {
|
|
347
|
+
if (frame.kind !== TransportV2BinaryKind.BodyChunk) {
|
|
348
|
+
throw new Error(`v2 body reader received non-BodyChunk frame (kind=${frame.kind})`);
|
|
349
|
+
}
|
|
350
|
+
const state = this.#streams.get(frame.id);
|
|
351
|
+
if (state === undefined || state.closed)
|
|
352
|
+
return;
|
|
353
|
+
const isFin = transportV2IsFin(frame.flags);
|
|
354
|
+
const isAbort = transportV2IsAbort(frame.flags);
|
|
355
|
+
if (isAbort) {
|
|
356
|
+
state.closed = true;
|
|
357
|
+
state.controller?.error(new Error(`v2 body stream ${frame.id} aborted by writer`));
|
|
358
|
+
this.#streams.delete(frame.id);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (frame.payload.byteLength > 0) {
|
|
362
|
+
state.controller?.enqueue(new Uint8Array(frame.payload));
|
|
363
|
+
}
|
|
364
|
+
if (isFin) {
|
|
365
|
+
state.closed = true;
|
|
366
|
+
state.controller?.close();
|
|
367
|
+
this.#streams.delete(frame.id);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
end(bid) {
|
|
371
|
+
const state = this.#streams.get(bid);
|
|
372
|
+
if (state === undefined || state.closed)
|
|
373
|
+
return;
|
|
374
|
+
state.closed = true;
|
|
375
|
+
state.controller?.close();
|
|
376
|
+
this.#streams.delete(bid);
|
|
377
|
+
}
|
|
378
|
+
abort(bid, reason) {
|
|
379
|
+
const state = this.#streams.get(bid);
|
|
380
|
+
if (state === undefined || state.closed)
|
|
381
|
+
return;
|
|
382
|
+
state.closed = true;
|
|
383
|
+
state.controller?.error(new Error(reason ?? `v2 body stream ${bid} aborted`));
|
|
384
|
+
this.#streams.delete(bid);
|
|
385
|
+
}
|
|
386
|
+
get size() {
|
|
387
|
+
return this.#streams.size;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// src/bridge/v2/codec.ts
|
|
392
|
+
class TransportV2Codec {
|
|
393
|
+
bodyReaders = new TransportV2BodyReaderRegistry;
|
|
394
|
+
#socket;
|
|
395
|
+
#capabilities;
|
|
396
|
+
#options;
|
|
397
|
+
#handshakeResolver = null;
|
|
398
|
+
#handshakePromise;
|
|
399
|
+
#sentHello = false;
|
|
400
|
+
#receivedHello = false;
|
|
401
|
+
#receivedWelcome = false;
|
|
402
|
+
#negotiated = null;
|
|
403
|
+
#pendingRpc = new Map;
|
|
404
|
+
#nextBid = 1;
|
|
405
|
+
#nextRpcId = 1;
|
|
406
|
+
#closed = false;
|
|
407
|
+
constructor(socket, options = {}) {
|
|
408
|
+
this.#socket = socket;
|
|
409
|
+
this.#capabilities = options.capabilities ?? [];
|
|
410
|
+
this.#options = options;
|
|
411
|
+
this.#handshakePromise = new Promise((resolve, reject) => {
|
|
412
|
+
this.#handshakeResolver = { resolve, reject };
|
|
413
|
+
});
|
|
414
|
+
socket.onmessage = (event) => this.#onMessage(event);
|
|
415
|
+
socket.onclose = () => this.#onClose();
|
|
416
|
+
socket.onerror = (event) => this.#onError(event.error);
|
|
417
|
+
}
|
|
418
|
+
allocateBid() {
|
|
419
|
+
return this.#nextBid++;
|
|
420
|
+
}
|
|
421
|
+
allocateRpcId() {
|
|
422
|
+
return `v2_rpc_${this.#nextRpcId++}`;
|
|
423
|
+
}
|
|
424
|
+
sendHello() {
|
|
425
|
+
if (this.#sentHello)
|
|
426
|
+
return;
|
|
427
|
+
this.#sentHello = true;
|
|
428
|
+
const hello = {
|
|
429
|
+
t: "hello",
|
|
430
|
+
protocolVersion: TRANSPORT_V2_PROTOCOL_VERSION,
|
|
431
|
+
capabilities: [...this.#capabilities]
|
|
432
|
+
};
|
|
433
|
+
this.#socket.send(stringifyTransportV2ControlMsg(hello));
|
|
434
|
+
}
|
|
435
|
+
get handshake() {
|
|
436
|
+
return this.#handshakePromise;
|
|
437
|
+
}
|
|
438
|
+
get negotiated() {
|
|
439
|
+
return this.#negotiated;
|
|
440
|
+
}
|
|
441
|
+
sendText(message) {
|
|
442
|
+
if (this.#closed) {
|
|
443
|
+
throw new Error("cannot send on a closed v2 codec");
|
|
444
|
+
}
|
|
445
|
+
this.#socket.send(message);
|
|
446
|
+
}
|
|
447
|
+
sendBinary(frame) {
|
|
448
|
+
if (this.#closed) {
|
|
449
|
+
throw new Error("cannot send on a closed v2 codec");
|
|
450
|
+
}
|
|
451
|
+
this.#socket.send(frame);
|
|
452
|
+
}
|
|
453
|
+
call(method, params = []) {
|
|
454
|
+
const id = this.allocateRpcId();
|
|
455
|
+
const call = { t: "rpc.call", id, method, params };
|
|
456
|
+
const promise = new Promise((resolve, reject) => {
|
|
457
|
+
this.#pendingRpc.set(id, { resolve, reject });
|
|
458
|
+
});
|
|
459
|
+
this.sendText(JSON.stringify(call));
|
|
460
|
+
return promise;
|
|
461
|
+
}
|
|
462
|
+
respondOk(id, result) {
|
|
463
|
+
const reply = { t: "rpc.ok", id, result };
|
|
464
|
+
this.sendText(JSON.stringify(reply));
|
|
465
|
+
}
|
|
466
|
+
respondErr(id, error) {
|
|
467
|
+
const reply = { t: "rpc.err", id, error };
|
|
468
|
+
this.sendText(JSON.stringify(reply));
|
|
469
|
+
}
|
|
470
|
+
sendWireError(err) {
|
|
471
|
+
const frame = { t: "error", ...err };
|
|
472
|
+
this.sendText(JSON.stringify(frame));
|
|
473
|
+
}
|
|
474
|
+
openBodyReader(bid) {
|
|
475
|
+
return this.bodyReaders.getOrOpen(bid);
|
|
476
|
+
}
|
|
477
|
+
setRpcCallHandler(handler) {
|
|
478
|
+
this.#options = { ...this.#options, onRpcCall: handler };
|
|
479
|
+
}
|
|
480
|
+
close(code, reason) {
|
|
481
|
+
if (this.#closed)
|
|
482
|
+
return;
|
|
483
|
+
this.#closed = true;
|
|
484
|
+
this.#failPending(new Error("v2 transport closed"));
|
|
485
|
+
this.#socket.close(code, reason);
|
|
486
|
+
}
|
|
487
|
+
get isClosed() {
|
|
488
|
+
return this.#closed;
|
|
489
|
+
}
|
|
490
|
+
#onMessage(event) {
|
|
491
|
+
const { data } = event;
|
|
492
|
+
if (typeof data === "string") {
|
|
493
|
+
this.#onText(data);
|
|
494
|
+
} else if (data instanceof Uint8Array) {
|
|
495
|
+
this.#onBinary(data);
|
|
496
|
+
} else if (data instanceof ArrayBuffer) {
|
|
497
|
+
this.#onBinary(new Uint8Array(data));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
#onText(data) {
|
|
501
|
+
let msg;
|
|
502
|
+
try {
|
|
503
|
+
msg = parseTransportV2ControlMsg(data);
|
|
504
|
+
} catch {
|
|
505
|
+
const rpc = tryParseRpcMsg(data);
|
|
506
|
+
if (rpc !== null) {
|
|
507
|
+
this.#onRpcMessage(rpc);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const wireErr = tryParseWireError(data);
|
|
511
|
+
if (wireErr !== null) {
|
|
512
|
+
this.#options.onWireError?.(wireErr);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
this.#options.onUnknownControl?.(data);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
switch (msg.t) {
|
|
519
|
+
case "hello":
|
|
520
|
+
this.#onHello(msg);
|
|
521
|
+
break;
|
|
522
|
+
case "welcome":
|
|
523
|
+
this.#onWelcome(msg);
|
|
524
|
+
break;
|
|
525
|
+
case "body.open":
|
|
526
|
+
this.bodyReaders.getOrOpen(msg.bid);
|
|
527
|
+
break;
|
|
528
|
+
case "body.end":
|
|
529
|
+
this.bodyReaders.end(msg.bid);
|
|
530
|
+
break;
|
|
531
|
+
case "body.abort":
|
|
532
|
+
this.bodyReaders.abort(msg.bid, msg.error);
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
#onBinary(data) {
|
|
537
|
+
let frame;
|
|
538
|
+
try {
|
|
539
|
+
frame = decodeTransportV2BinaryFrame(data);
|
|
540
|
+
} catch {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
if (frame.kind === TransportV2BinaryKind.BodyChunk) {
|
|
544
|
+
this.bodyReaders.pushChunk(frame);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
this.#options.onUnknownBinary?.(frame);
|
|
548
|
+
}
|
|
549
|
+
#onRpcMessage(msg) {
|
|
550
|
+
switch (msg.t) {
|
|
551
|
+
case "rpc.call":
|
|
552
|
+
this.#options.onRpcCall?.(msg);
|
|
553
|
+
break;
|
|
554
|
+
case "rpc.ok": {
|
|
555
|
+
const pending = this.#pendingRpc.get(msg.id);
|
|
556
|
+
if (pending !== undefined) {
|
|
557
|
+
this.#pendingRpc.delete(msg.id);
|
|
558
|
+
pending.resolve(msg.result);
|
|
559
|
+
}
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
case "rpc.err": {
|
|
563
|
+
const pending = this.#pendingRpc.get(msg.id);
|
|
564
|
+
if (pending !== undefined) {
|
|
565
|
+
this.#pendingRpc.delete(msg.id);
|
|
566
|
+
const err = new Error(msg.error.message);
|
|
567
|
+
Object.assign(err, { code: msg.error.code, details: msg.error.details });
|
|
568
|
+
pending.reject(err);
|
|
569
|
+
}
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
#onHello(msg) {
|
|
575
|
+
if (this.#receivedHello) {
|
|
576
|
+
this.close(TRANSPORT_V2_UNSUPPORTED_VERSION_CLOSE_CODE, "duplicate hello");
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
this.#receivedHello = true;
|
|
580
|
+
const negotiated = negotiateTransportV2Capabilities(this.#capabilities, msg.capabilities);
|
|
581
|
+
const welcome = {
|
|
582
|
+
t: "welcome",
|
|
583
|
+
protocolVersion: TRANSPORT_V2_PROTOCOL_VERSION,
|
|
584
|
+
capabilities: negotiated
|
|
585
|
+
};
|
|
586
|
+
this.#socket.send(stringifyTransportV2ControlMsg(welcome));
|
|
587
|
+
this.#completeHandshake({
|
|
588
|
+
protocolVersion: TRANSPORT_V2_PROTOCOL_VERSION,
|
|
589
|
+
capabilities: negotiated
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
#onWelcome(msg) {
|
|
593
|
+
if (this.#receivedWelcome)
|
|
594
|
+
return;
|
|
595
|
+
this.#receivedWelcome = true;
|
|
596
|
+
this.#completeHandshake({
|
|
597
|
+
protocolVersion: TRANSPORT_V2_PROTOCOL_VERSION,
|
|
598
|
+
capabilities: msg.capabilities
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
#completeHandshake(result) {
|
|
602
|
+
if (this.#negotiated !== null)
|
|
603
|
+
return;
|
|
604
|
+
this.#negotiated = result;
|
|
605
|
+
this.#handshakeResolver?.resolve(result);
|
|
606
|
+
this.#handshakeResolver = null;
|
|
607
|
+
}
|
|
608
|
+
#onClose() {
|
|
609
|
+
if (this.#closed) {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
this.#closed = true;
|
|
613
|
+
this.#failPending(new Error("v2 transport closed"));
|
|
614
|
+
}
|
|
615
|
+
#onError(error) {
|
|
616
|
+
const wrapped = error instanceof Error ? error : new Error(String(error));
|
|
617
|
+
this.#failPending(wrapped);
|
|
618
|
+
}
|
|
619
|
+
#failPending(error) {
|
|
620
|
+
const resolver = this.#handshakeResolver;
|
|
621
|
+
if (resolver !== null) {
|
|
622
|
+
this.#handshakeResolver = null;
|
|
623
|
+
resolver.reject(error);
|
|
624
|
+
}
|
|
625
|
+
for (const pending of this.#pendingRpc.values()) {
|
|
626
|
+
pending.reject(error);
|
|
627
|
+
}
|
|
628
|
+
this.#pendingRpc.clear();
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
function tryParseRpcMsg(data) {
|
|
632
|
+
let parsed;
|
|
633
|
+
try {
|
|
634
|
+
parsed = JSON.parse(data);
|
|
635
|
+
} catch {
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
if (typeof parsed !== "object" || parsed === null || !("t" in parsed))
|
|
639
|
+
return null;
|
|
640
|
+
const t = parsed.t;
|
|
641
|
+
if (t === "rpc.call" || t === "rpc.ok" || t === "rpc.err") {
|
|
642
|
+
return parsed;
|
|
643
|
+
}
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
function tryParseWireError(data) {
|
|
647
|
+
let parsed;
|
|
648
|
+
try {
|
|
649
|
+
parsed = JSON.parse(data);
|
|
650
|
+
} catch {
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
if (typeof parsed !== "object" || parsed === null || !("t" in parsed))
|
|
654
|
+
return null;
|
|
655
|
+
const candidate = parsed;
|
|
656
|
+
if (candidate.t !== "error")
|
|
657
|
+
return null;
|
|
658
|
+
const scope = candidate.scope;
|
|
659
|
+
if (scope !== "transport" && scope !== "rpc" && scope !== "stream" && scope !== "ws")
|
|
660
|
+
return null;
|
|
661
|
+
const err = candidate.error;
|
|
662
|
+
if (!err || typeof err.code !== "string" || typeof err.message !== "string")
|
|
663
|
+
return null;
|
|
664
|
+
return parsed;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// src/bridge/v2/wire.ts
|
|
668
|
+
var BinaryKind = {
|
|
669
|
+
StreamChunk: 1,
|
|
670
|
+
WsData: 2
|
|
671
|
+
};
|
|
672
|
+
var BinaryFlags = {
|
|
673
|
+
FIN: 1,
|
|
674
|
+
TEXT: 2
|
|
675
|
+
};
|
|
676
|
+
var BINARY_HEADER_SIZE = 10;
|
|
677
|
+
function encodeBinaryFrame(kind, id, seq, flags, payload) {
|
|
678
|
+
const frame = new Uint8Array(BINARY_HEADER_SIZE + payload.byteLength);
|
|
679
|
+
const view = new DataView(frame.buffer);
|
|
680
|
+
view.setUint8(0, kind);
|
|
681
|
+
view.setUint32(1, id, true);
|
|
682
|
+
view.setUint32(5, seq, true);
|
|
683
|
+
view.setUint8(9, flags);
|
|
684
|
+
frame.set(payload, BINARY_HEADER_SIZE);
|
|
685
|
+
return frame;
|
|
686
|
+
}
|
|
687
|
+
function parseJsonMsg(data) {
|
|
688
|
+
const msg = JSON.parse(data);
|
|
689
|
+
if (typeof msg !== "object" || msg === null || !("t" in msg)) {
|
|
690
|
+
throw new Error("Invalid message: missing type field");
|
|
691
|
+
}
|
|
692
|
+
return msg;
|
|
693
|
+
}
|
|
694
|
+
function stringifyJsonMsg(msg) {
|
|
695
|
+
return JSON.stringify(msg);
|
|
696
|
+
}
|
|
697
|
+
var streamIdCounter = 0;
|
|
698
|
+
var wsIdCounter = 0;
|
|
699
|
+
function nextStreamId() {
|
|
700
|
+
return ++streamIdCounter;
|
|
701
|
+
}
|
|
702
|
+
function nextWsId() {
|
|
703
|
+
return ++wsIdCounter;
|
|
704
|
+
}
|
|
705
|
+
var DEFAULT_CHUNK_SIZE = 256 * 1024;
|
|
706
|
+
var HTTP_TRANSFER_THRESHOLD = 512 * 1024;
|
|
707
|
+
var DEFAULT_BRIDGE_PORT = 8787;
|
|
708
|
+
|
|
709
|
+
// src/bridge/v2/value-serialization.ts
|
|
710
|
+
async function serializeRequest(request, options) {
|
|
711
|
+
const streams = [];
|
|
712
|
+
const threshold = options?.httpThreshold ?? HTTP_TRANSFER_THRESHOLD;
|
|
713
|
+
const headers = [];
|
|
714
|
+
request.headers.forEach((value, key) => {
|
|
715
|
+
headers.push([key, value]);
|
|
716
|
+
});
|
|
717
|
+
let body = null;
|
|
718
|
+
if (request.body) {
|
|
719
|
+
const bytes = await request.arrayBuffer();
|
|
720
|
+
if (bytes.byteLength > threshold) {
|
|
721
|
+
throw new Error("Request body exceeds the bridge inline limit (~512 KB) and large request-body streaming is not yet supported over the local bridge. " + "Send the payload in smaller chunks or via an R2 binding for now.");
|
|
722
|
+
}
|
|
723
|
+
if (bytes.byteLength > 0) {
|
|
724
|
+
body = { type: "bytes", data: base64Encode(new Uint8Array(bytes)) };
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return {
|
|
728
|
+
serialized: {
|
|
729
|
+
url: request.url,
|
|
730
|
+
method: request.method,
|
|
731
|
+
headers,
|
|
732
|
+
body,
|
|
733
|
+
redirect: request.redirect
|
|
734
|
+
},
|
|
735
|
+
streams
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function deserializeRequest(serialized, getStream) {
|
|
739
|
+
let body = null;
|
|
740
|
+
if (serialized.body) {
|
|
741
|
+
switch (serialized.body.type) {
|
|
742
|
+
case "bytes":
|
|
743
|
+
body = base64Decode(serialized.body.data);
|
|
744
|
+
break;
|
|
745
|
+
case "stream":
|
|
746
|
+
if (getStream) {
|
|
747
|
+
body = getStream(serialized.body.sid) ?? null;
|
|
748
|
+
}
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return new Request(serialized.url, {
|
|
753
|
+
method: serialized.method,
|
|
754
|
+
headers: serialized.headers,
|
|
755
|
+
body,
|
|
756
|
+
redirect: serialized.redirect
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
async function serializeResponse(response, options) {
|
|
760
|
+
const streams = [];
|
|
761
|
+
const threshold = options?.httpThreshold ?? HTTP_TRANSFER_THRESHOLD;
|
|
762
|
+
const headers = [];
|
|
763
|
+
response.headers.forEach((value, key) => {
|
|
764
|
+
headers.push([key, value]);
|
|
765
|
+
});
|
|
766
|
+
let body = null;
|
|
767
|
+
if (response.body) {
|
|
768
|
+
const bytes = await response.arrayBuffer();
|
|
769
|
+
if (bytes.byteLength > threshold) {
|
|
770
|
+
const sid = nextStreamId();
|
|
771
|
+
const payload = new Uint8Array(bytes);
|
|
772
|
+
const frameSize = HTTP_TRANSFER_THRESHOLD;
|
|
773
|
+
const stream = new ReadableStream({
|
|
774
|
+
start(controller) {
|
|
775
|
+
for (let offset = 0;offset < payload.byteLength; offset += frameSize) {
|
|
776
|
+
controller.enqueue(payload.slice(offset, Math.min(offset + frameSize, payload.byteLength)));
|
|
777
|
+
}
|
|
778
|
+
controller.close();
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
streams.push({ sid, stream });
|
|
782
|
+
body = { type: "stream", sid };
|
|
783
|
+
} else if (bytes.byteLength > 0) {
|
|
784
|
+
body = { type: "bytes", data: base64Encode(new Uint8Array(bytes)) };
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return {
|
|
788
|
+
serialized: {
|
|
789
|
+
status: response.status,
|
|
790
|
+
statusText: response.statusText,
|
|
791
|
+
headers,
|
|
792
|
+
body
|
|
793
|
+
},
|
|
794
|
+
streams
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
function deserializeResponse(serialized, getStream) {
|
|
798
|
+
let body = null;
|
|
799
|
+
if (serialized.body) {
|
|
800
|
+
switch (serialized.body.type) {
|
|
801
|
+
case "bytes":
|
|
802
|
+
body = base64Decode(serialized.body.data);
|
|
803
|
+
break;
|
|
804
|
+
case "stream":
|
|
805
|
+
if (getStream) {
|
|
806
|
+
body = getStream(serialized.body.sid) ?? null;
|
|
807
|
+
}
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
return new Response(body, {
|
|
812
|
+
status: serialized.status,
|
|
813
|
+
statusText: serialized.statusText,
|
|
814
|
+
headers: serialized.headers
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
async function serializeValue(value, options) {
|
|
818
|
+
const streams = [];
|
|
819
|
+
const result = await serializeValueInternal(value, streams, options);
|
|
820
|
+
return { value: result, streams };
|
|
821
|
+
}
|
|
822
|
+
async function serializeValueInternal(value, streams, options) {
|
|
823
|
+
if (value === null || value === undefined) {
|
|
824
|
+
return value;
|
|
825
|
+
}
|
|
826
|
+
if (value instanceof Request) {
|
|
827
|
+
const { serialized, streams: reqStreams } = await serializeRequest(value, options);
|
|
828
|
+
streams.push(...reqStreams);
|
|
829
|
+
return { __type: "Request", ...serialized };
|
|
830
|
+
}
|
|
831
|
+
if (value instanceof Response) {
|
|
832
|
+
const { serialized, streams: resStreams } = await serializeResponse(value, options);
|
|
833
|
+
streams.push(...resStreams);
|
|
834
|
+
return { __type: "Response", ...serialized };
|
|
835
|
+
}
|
|
836
|
+
if (value instanceof ReadableStream) {
|
|
837
|
+
const sid = nextStreamId();
|
|
838
|
+
streams.push({ sid, stream: value });
|
|
839
|
+
return { __type: "ReadableStream", sid };
|
|
840
|
+
}
|
|
841
|
+
if (value instanceof Uint8Array) {
|
|
842
|
+
return { __type: "Uint8Array", data: base64Encode(value) };
|
|
843
|
+
}
|
|
844
|
+
if (value instanceof ArrayBuffer) {
|
|
845
|
+
return { __type: "ArrayBuffer", data: base64Encode(new Uint8Array(value)) };
|
|
846
|
+
}
|
|
847
|
+
if (value instanceof Date) {
|
|
848
|
+
return { __devflare: "date", iso: value.toISOString() };
|
|
849
|
+
}
|
|
850
|
+
if (value instanceof URL) {
|
|
851
|
+
return { __devflare: "url", href: value.href };
|
|
852
|
+
}
|
|
853
|
+
if (value instanceof Error) {
|
|
854
|
+
const encoded = {
|
|
855
|
+
__devflare: "error",
|
|
856
|
+
name: value.name,
|
|
857
|
+
message: value.message
|
|
858
|
+
};
|
|
859
|
+
if (value.stack)
|
|
860
|
+
encoded.stack = value.stack;
|
|
861
|
+
return encoded;
|
|
862
|
+
}
|
|
863
|
+
if (value instanceof Map) {
|
|
864
|
+
const entries = [];
|
|
865
|
+
for (const [k, v] of value.entries()) {
|
|
866
|
+
entries.push([
|
|
867
|
+
await serializeValueInternal(k, streams, options),
|
|
868
|
+
await serializeValueInternal(v, streams, options)
|
|
869
|
+
]);
|
|
870
|
+
}
|
|
871
|
+
return { __devflare: "map", entries };
|
|
872
|
+
}
|
|
873
|
+
if (value instanceof Set) {
|
|
874
|
+
const values = [];
|
|
875
|
+
for (const v of value.values()) {
|
|
876
|
+
values.push(await serializeValueInternal(v, streams, options));
|
|
877
|
+
}
|
|
878
|
+
return { __devflare: "set", values };
|
|
879
|
+
}
|
|
880
|
+
if (Array.isArray(value)) {
|
|
881
|
+
return Promise.all(value.map((v) => serializeValueInternal(v, streams, options)));
|
|
882
|
+
}
|
|
883
|
+
if (typeof value === "object") {
|
|
884
|
+
const result = {};
|
|
885
|
+
for (const [k, v] of Object.entries(value)) {
|
|
886
|
+
result[k] = await serializeValueInternal(v, streams, options);
|
|
887
|
+
}
|
|
888
|
+
return result;
|
|
889
|
+
}
|
|
890
|
+
return value;
|
|
891
|
+
}
|
|
892
|
+
function deserializeValue(value, getStream) {
|
|
893
|
+
if (value === null || value === undefined) {
|
|
894
|
+
return value;
|
|
895
|
+
}
|
|
896
|
+
if (typeof value === "object" && value !== null) {
|
|
897
|
+
const obj = value;
|
|
898
|
+
if (typeof obj.__devflare === "string") {
|
|
899
|
+
switch (obj.__devflare) {
|
|
900
|
+
case "date":
|
|
901
|
+
return new Date(obj.iso);
|
|
902
|
+
case "url":
|
|
903
|
+
return new URL(obj.href);
|
|
904
|
+
case "error": {
|
|
905
|
+
const err = new Error(obj.message);
|
|
906
|
+
if (typeof obj.name === "string")
|
|
907
|
+
err.name = obj.name;
|
|
908
|
+
if (typeof obj.stack === "string")
|
|
909
|
+
err.stack = obj.stack;
|
|
910
|
+
return err;
|
|
911
|
+
}
|
|
912
|
+
case "map": {
|
|
913
|
+
const entries = obj.entries ?? [];
|
|
914
|
+
const map = new Map;
|
|
915
|
+
for (const [k, v] of entries) {
|
|
916
|
+
map.set(deserializeValue(k, getStream), deserializeValue(v, getStream));
|
|
917
|
+
}
|
|
918
|
+
return map;
|
|
919
|
+
}
|
|
920
|
+
case "set": {
|
|
921
|
+
const values = obj.values ?? [];
|
|
922
|
+
const set = new Set;
|
|
923
|
+
for (const v of values) {
|
|
924
|
+
set.add(deserializeValue(v, getStream));
|
|
925
|
+
}
|
|
926
|
+
return set;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
if (obj.__type === "Request") {
|
|
931
|
+
return deserializeRequest(obj, getStream);
|
|
932
|
+
}
|
|
933
|
+
if (obj.__type === "Response") {
|
|
934
|
+
return deserializeResponse(obj, getStream);
|
|
935
|
+
}
|
|
936
|
+
if (obj.__type === "ReadableStream") {
|
|
937
|
+
const sid = obj.sid;
|
|
938
|
+
return getStream?.(sid) ?? null;
|
|
939
|
+
}
|
|
940
|
+
if (obj.__type === "Uint8Array") {
|
|
941
|
+
return base64Decode(obj.data);
|
|
942
|
+
}
|
|
943
|
+
if (obj.__type === "ArrayBuffer") {
|
|
944
|
+
return base64Decode(obj.data).buffer;
|
|
945
|
+
}
|
|
946
|
+
if (obj.__type === "R2Object") {
|
|
947
|
+
return deserializeR2Object(obj);
|
|
948
|
+
}
|
|
949
|
+
if (obj.__type === "R2ObjectBody") {
|
|
950
|
+
return deserializeR2ObjectBody(obj);
|
|
951
|
+
}
|
|
952
|
+
if (Array.isArray(value)) {
|
|
953
|
+
return value.map((v) => deserializeValue(v, getStream));
|
|
954
|
+
}
|
|
955
|
+
const result = {};
|
|
956
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
957
|
+
result[k] = deserializeValue(v, getStream);
|
|
958
|
+
}
|
|
959
|
+
return result;
|
|
960
|
+
}
|
|
961
|
+
return value;
|
|
962
|
+
}
|
|
963
|
+
function applySerializedHttpMetadata(headers, httpMetadata) {
|
|
964
|
+
if (httpMetadata?.contentType) {
|
|
965
|
+
headers.set("Content-Type", httpMetadata.contentType);
|
|
966
|
+
}
|
|
967
|
+
if (httpMetadata?.contentLanguage) {
|
|
968
|
+
headers.set("Content-Language", httpMetadata.contentLanguage);
|
|
969
|
+
}
|
|
970
|
+
if (httpMetadata?.contentDisposition) {
|
|
971
|
+
headers.set("Content-Disposition", httpMetadata.contentDisposition);
|
|
972
|
+
}
|
|
973
|
+
if (httpMetadata?.contentEncoding) {
|
|
974
|
+
headers.set("Content-Encoding", httpMetadata.contentEncoding);
|
|
975
|
+
}
|
|
976
|
+
if (httpMetadata?.cacheControl) {
|
|
977
|
+
headers.set("Cache-Control", httpMetadata.cacheControl);
|
|
978
|
+
}
|
|
979
|
+
if (httpMetadata?.cacheExpiry) {
|
|
980
|
+
headers.set("Expires", new Date(httpMetadata.cacheExpiry).toUTCString());
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
function createSerializedR2Metadata(serialized) {
|
|
984
|
+
return {
|
|
985
|
+
key: serialized.key,
|
|
986
|
+
version: serialized.version,
|
|
987
|
+
size: serialized.size,
|
|
988
|
+
etag: serialized.etag,
|
|
989
|
+
httpEtag: serialized.httpEtag,
|
|
990
|
+
checksums: serialized.checksums,
|
|
991
|
+
uploaded: serialized.uploaded ? new Date(serialized.uploaded) : new Date,
|
|
992
|
+
httpMetadata: serialized.httpMetadata,
|
|
993
|
+
customMetadata: serialized.customMetadata,
|
|
994
|
+
range: serialized.range,
|
|
995
|
+
storageClass: serialized.storageClass,
|
|
996
|
+
writeHttpMetadata(headers) {
|
|
997
|
+
applySerializedHttpMetadata(headers, serialized.httpMetadata);
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function deserializeR2Object(obj) {
|
|
1002
|
+
const serialized = obj;
|
|
1003
|
+
return {
|
|
1004
|
+
...createSerializedR2Metadata(serialized)
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
function deserializeR2ObjectBody(obj) {
|
|
1008
|
+
const serialized = obj;
|
|
1009
|
+
const bodyBytes = serialized.bodyData ? base64Decode(serialized.bodyData) : new Uint8Array(0);
|
|
1010
|
+
const r2ObjectBody = {
|
|
1011
|
+
...createSerializedR2Metadata(serialized),
|
|
1012
|
+
body: new ReadableStream({
|
|
1013
|
+
start(controller) {
|
|
1014
|
+
controller.enqueue(bodyBytes);
|
|
1015
|
+
controller.close();
|
|
1016
|
+
}
|
|
1017
|
+
}),
|
|
1018
|
+
bodyUsed: false,
|
|
1019
|
+
async arrayBuffer() {
|
|
1020
|
+
const copy = new Uint8Array(bodyBytes.byteLength);
|
|
1021
|
+
copy.set(bodyBytes);
|
|
1022
|
+
return copy.buffer;
|
|
1023
|
+
},
|
|
1024
|
+
async text() {
|
|
1025
|
+
return new TextDecoder().decode(bodyBytes);
|
|
1026
|
+
},
|
|
1027
|
+
async json() {
|
|
1028
|
+
const text = new TextDecoder().decode(bodyBytes);
|
|
1029
|
+
return JSON.parse(text);
|
|
1030
|
+
},
|
|
1031
|
+
async blob() {
|
|
1032
|
+
const contentType = serialized.httpMetadata?.contentType || "application/octet-stream";
|
|
1033
|
+
const buffer = bodyBytes.buffer.slice(bodyBytes.byteOffset, bodyBytes.byteOffset + bodyBytes.byteLength);
|
|
1034
|
+
return new Blob([buffer], { type: contentType });
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
return r2ObjectBody;
|
|
1038
|
+
}
|
|
1039
|
+
function base64Encode(bytes) {
|
|
1040
|
+
if (typeof Buffer !== "undefined") {
|
|
1041
|
+
return Buffer.from(bytes).toString("base64");
|
|
1042
|
+
}
|
|
1043
|
+
let binary = "";
|
|
1044
|
+
for (let i = 0;i < bytes.byteLength; i++) {
|
|
1045
|
+
binary += String.fromCharCode(bytes[i]);
|
|
1046
|
+
}
|
|
1047
|
+
return btoa(binary);
|
|
1048
|
+
}
|
|
1049
|
+
function base64Decode(str) {
|
|
1050
|
+
if (typeof Buffer !== "undefined") {
|
|
1051
|
+
return new Uint8Array(Buffer.from(str, "base64"));
|
|
1052
|
+
}
|
|
1053
|
+
const binary = atob(str);
|
|
1054
|
+
const bytes = new Uint8Array(binary.length);
|
|
1055
|
+
for (let i = 0;i < binary.length; i++) {
|
|
1056
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1057
|
+
}
|
|
1058
|
+
return bytes;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// src/bridge/client.ts
|
|
1062
|
+
class BridgeWsAdapter {
|
|
1063
|
+
onmessage = null;
|
|
1064
|
+
onclose = null;
|
|
1065
|
+
onerror = null;
|
|
1066
|
+
#ws;
|
|
1067
|
+
constructor(ws) {
|
|
1068
|
+
this.#ws = ws;
|
|
1069
|
+
}
|
|
1070
|
+
send(data) {
|
|
1071
|
+
this.#ws.send(data);
|
|
1072
|
+
}
|
|
1073
|
+
close(code, reason) {
|
|
1074
|
+
this.#ws.close(code, reason);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
var BRIDGE_CLIENT_CAPABILITIES = ["streams", "ws-relay", "http-transfer"];
|
|
1078
|
+
var wsPackageConstructorPromise = null;
|
|
1079
|
+
async function importWsPackageConstructor() {
|
|
1080
|
+
if (!wsPackageConstructorPromise) {
|
|
1081
|
+
wsPackageConstructorPromise = (async () => {
|
|
1082
|
+
const dynamicImport = new Function("specifier", ["return ", "import", "(specifier)"].join(""));
|
|
1083
|
+
const wsModule = await dynamicImport("ws");
|
|
1084
|
+
const defaultExport = wsModule.default;
|
|
1085
|
+
const constructor = wsModule.WebSocket ?? (typeof defaultExport === "object" && defaultExport !== null ? defaultExport.WebSocket : undefined) ?? defaultExport;
|
|
1086
|
+
if (typeof constructor !== "function") {
|
|
1087
|
+
throw new Error("Could not load a WebSocket client implementation from the ws package");
|
|
1088
|
+
}
|
|
1089
|
+
return constructor;
|
|
1090
|
+
})();
|
|
1091
|
+
}
|
|
1092
|
+
return wsPackageConstructorPromise;
|
|
1093
|
+
}
|
|
1094
|
+
function getRuntimeWebSocketConstructor(runtimeWebSocket = globalThis.WebSocket) {
|
|
1095
|
+
if (typeof runtimeWebSocket === "function") {
|
|
1096
|
+
return runtimeWebSocket;
|
|
1097
|
+
}
|
|
1098
|
+
return null;
|
|
1099
|
+
}
|
|
1100
|
+
class BridgeClient {
|
|
1101
|
+
ws = null;
|
|
1102
|
+
codec = null;
|
|
1103
|
+
adapter = null;
|
|
1104
|
+
url;
|
|
1105
|
+
autoReconnect;
|
|
1106
|
+
reconnectDelay;
|
|
1107
|
+
connectTimeout;
|
|
1108
|
+
activeStreams = new Map;
|
|
1109
|
+
wsProxies = new Map;
|
|
1110
|
+
pendingWsOpens = new Map;
|
|
1111
|
+
outgoingStreams = new Map;
|
|
1112
|
+
eventListeners = new Map;
|
|
1113
|
+
connectPromise = null;
|
|
1114
|
+
isConnected = false;
|
|
1115
|
+
constructor(options = {}) {
|
|
1116
|
+
this.url = options.url ?? `ws://localhost:${DEFAULT_BRIDGE_PORT}`;
|
|
1117
|
+
this.autoReconnect = options.autoReconnect ?? true;
|
|
1118
|
+
this.reconnectDelay = options.reconnectDelay ?? 1000;
|
|
1119
|
+
this.connectTimeout = options.connectTimeout ?? 5000;
|
|
1120
|
+
}
|
|
1121
|
+
getUrl() {
|
|
1122
|
+
return this.url;
|
|
1123
|
+
}
|
|
1124
|
+
getHttpUrl() {
|
|
1125
|
+
return this.url.replace(/^ws:\/\//, "http://").replace(/^wss:\/\//, "https://");
|
|
1126
|
+
}
|
|
1127
|
+
async connect() {
|
|
1128
|
+
if (this.isConnected)
|
|
1129
|
+
return;
|
|
1130
|
+
if (this.connectPromise)
|
|
1131
|
+
return this.connectPromise;
|
|
1132
|
+
const WebSocketCtor = getRuntimeWebSocketConstructor();
|
|
1133
|
+
const promise = WebSocketCtor ? this.openConnection(WebSocketCtor) : this.openConnectionWithPackageFallback();
|
|
1134
|
+
this.connectPromise = promise;
|
|
1135
|
+
promise.catch(() => {
|
|
1136
|
+
if (this.connectPromise === promise) {
|
|
1137
|
+
this.connectPromise = null;
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
return promise;
|
|
1141
|
+
}
|
|
1142
|
+
async openConnectionWithPackageFallback() {
|
|
1143
|
+
const WebSocketCtor = await importWsPackageConstructor();
|
|
1144
|
+
return this.openConnection(WebSocketCtor);
|
|
1145
|
+
}
|
|
1146
|
+
openConnection(WebSocketCtor) {
|
|
1147
|
+
return new Promise((resolve, reject) => {
|
|
1148
|
+
const timeout = setTimeout(() => {
|
|
1149
|
+
reject(new Error(`Connection timeout: ${this.url}`));
|
|
1150
|
+
this.ws?.close();
|
|
1151
|
+
}, this.connectTimeout);
|
|
1152
|
+
try {
|
|
1153
|
+
this.ws = new WebSocketCtor(this.url);
|
|
1154
|
+
this.ws.binaryType = "arraybuffer";
|
|
1155
|
+
const adapter = new BridgeWsAdapter(this.ws);
|
|
1156
|
+
this.adapter = adapter;
|
|
1157
|
+
this.ws.onopen = () => {
|
|
1158
|
+
clearTimeout(timeout);
|
|
1159
|
+
this.codec = new TransportV2Codec(adapter, {
|
|
1160
|
+
capabilities: [...BRIDGE_CLIENT_CAPABILITIES],
|
|
1161
|
+
onUnknownControl: (data) => this.handleJsonMessage(data),
|
|
1162
|
+
onUnknownBinary: (frame) => this.handleV2BinaryFrame(frame)
|
|
1163
|
+
});
|
|
1164
|
+
this.codec.sendHello();
|
|
1165
|
+
this.codec.handshake.catch(() => {});
|
|
1166
|
+
this.isConnected = true;
|
|
1167
|
+
this.connectPromise = null;
|
|
1168
|
+
resolve();
|
|
1169
|
+
};
|
|
1170
|
+
this.ws.onerror = (event) => {
|
|
1171
|
+
clearTimeout(timeout);
|
|
1172
|
+
this.connectPromise = null;
|
|
1173
|
+
adapter.onerror?.({ error: event.error });
|
|
1174
|
+
reject(new Error("WebSocket connection failed"));
|
|
1175
|
+
};
|
|
1176
|
+
this.ws.onclose = (event) => {
|
|
1177
|
+
adapter.onclose?.({
|
|
1178
|
+
code: event?.code ?? 1006,
|
|
1179
|
+
reason: event?.reason ?? ""
|
|
1180
|
+
});
|
|
1181
|
+
this.handleDisconnect();
|
|
1182
|
+
};
|
|
1183
|
+
this.ws.onmessage = (event) => {
|
|
1184
|
+
adapter.onmessage?.({ data: event.data });
|
|
1185
|
+
};
|
|
1186
|
+
} catch (error) {
|
|
1187
|
+
clearTimeout(timeout);
|
|
1188
|
+
this.connectPromise = null;
|
|
1189
|
+
reject(error);
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
disconnect() {
|
|
1194
|
+
this.autoReconnect = false;
|
|
1195
|
+
this.codec?.close();
|
|
1196
|
+
this.codec = null;
|
|
1197
|
+
this.adapter = null;
|
|
1198
|
+
this.ws?.close();
|
|
1199
|
+
this.ws = null;
|
|
1200
|
+
this.isConnected = false;
|
|
1201
|
+
this.cleanupPending(new Error("Bridge disconnected"));
|
|
1202
|
+
this.eventListeners.clear();
|
|
1203
|
+
}
|
|
1204
|
+
close() {
|
|
1205
|
+
this.disconnect();
|
|
1206
|
+
}
|
|
1207
|
+
get connected() {
|
|
1208
|
+
return this.isConnected;
|
|
1209
|
+
}
|
|
1210
|
+
handleDisconnect() {
|
|
1211
|
+
this.isConnected = false;
|
|
1212
|
+
this.codec?.close();
|
|
1213
|
+
this.codec = null;
|
|
1214
|
+
this.adapter = null;
|
|
1215
|
+
this.ws = null;
|
|
1216
|
+
this.cleanupPending(new Error("Bridge disconnected"));
|
|
1217
|
+
if (this.autoReconnect) {
|
|
1218
|
+
setTimeout(() => {
|
|
1219
|
+
this.connect().catch((error) => {
|
|
1220
|
+
bridgeLog.warn("auto-reconnect attempt failed", error);
|
|
1221
|
+
});
|
|
1222
|
+
}, this.reconnectDelay);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
cleanupPending(error) {
|
|
1226
|
+
for (const pending of this.pendingWsOpens.values()) {
|
|
1227
|
+
pending.reject(error);
|
|
1228
|
+
}
|
|
1229
|
+
this.pendingWsOpens.clear();
|
|
1230
|
+
for (const stream of this.activeStreams.values()) {
|
|
1231
|
+
stream.closed = true;
|
|
1232
|
+
if (stream.pendingPull) {
|
|
1233
|
+
stream.pendingPull.reject(error);
|
|
1234
|
+
stream.pendingPull = null;
|
|
1235
|
+
}
|
|
1236
|
+
try {
|
|
1237
|
+
stream.controller.error(error);
|
|
1238
|
+
} catch {}
|
|
1239
|
+
}
|
|
1240
|
+
this.activeStreams.clear();
|
|
1241
|
+
for (const proxy of this.wsProxies.values()) {
|
|
1242
|
+
try {
|
|
1243
|
+
proxy.onClose(1006, error.message);
|
|
1244
|
+
} catch {}
|
|
1245
|
+
}
|
|
1246
|
+
this.wsProxies.clear();
|
|
1247
|
+
this.outgoingStreams.clear();
|
|
1248
|
+
}
|
|
1249
|
+
async call(method, params, timeoutMs = 30000) {
|
|
1250
|
+
await this.ensureConnected();
|
|
1251
|
+
const codec = this.codec;
|
|
1252
|
+
if (!codec) {
|
|
1253
|
+
throw new Error("Bridge disconnected");
|
|
1254
|
+
}
|
|
1255
|
+
const { value: serializedParams, streams } = await serializeValue(params);
|
|
1256
|
+
for (const streamRef of streams) {
|
|
1257
|
+
this.outgoingStreams.set(streamRef.sid, streamRef);
|
|
1258
|
+
}
|
|
1259
|
+
const callPromise = codec.call(method, serializedParams);
|
|
1260
|
+
let timeoutHandle = null;
|
|
1261
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1262
|
+
timeoutHandle = setTimeout(() => {
|
|
1263
|
+
reject(new Error(`RPC timeout: ${method}`));
|
|
1264
|
+
}, timeoutMs);
|
|
1265
|
+
});
|
|
1266
|
+
try {
|
|
1267
|
+
const rawResult = await Promise.race([callPromise, timeoutPromise]);
|
|
1268
|
+
return deserializeValue(rawResult, (sid) => this.createReadableStream(sid));
|
|
1269
|
+
} catch (error) {
|
|
1270
|
+
if (!this.isConnected && error instanceof Error && /v2 transport closed|transport/.test(error.message)) {
|
|
1271
|
+
throw new Error("Bridge disconnected");
|
|
1272
|
+
}
|
|
1273
|
+
throw error;
|
|
1274
|
+
} finally {
|
|
1275
|
+
if (timeoutHandle !== null)
|
|
1276
|
+
clearTimeout(timeoutHandle);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
on(topic, cb) {
|
|
1280
|
+
let set = this.eventListeners.get(topic);
|
|
1281
|
+
if (!set) {
|
|
1282
|
+
set = new Set;
|
|
1283
|
+
this.eventListeners.set(topic, set);
|
|
1284
|
+
}
|
|
1285
|
+
set.add(cb);
|
|
1286
|
+
return () => {
|
|
1287
|
+
const current = this.eventListeners.get(topic);
|
|
1288
|
+
if (!current)
|
|
1289
|
+
return;
|
|
1290
|
+
current.delete(cb);
|
|
1291
|
+
if (current.size === 0)
|
|
1292
|
+
this.eventListeners.delete(topic);
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
async createWsProxy(binding, id, url, headers) {
|
|
1296
|
+
await this.ensureConnected();
|
|
1297
|
+
const wid = nextWsId();
|
|
1298
|
+
const proxy = {
|
|
1299
|
+
clientWs: null,
|
|
1300
|
+
onMessage: () => {},
|
|
1301
|
+
onClose: () => {}
|
|
1302
|
+
};
|
|
1303
|
+
this.wsProxies.set(wid, proxy);
|
|
1304
|
+
const openedPromise = new Promise((resolve, reject) => {
|
|
1305
|
+
this.pendingWsOpens.set(wid, { resolve, reject });
|
|
1306
|
+
});
|
|
1307
|
+
const msg = {
|
|
1308
|
+
t: "ws.open",
|
|
1309
|
+
wid,
|
|
1310
|
+
target: { binding, id, url, headers }
|
|
1311
|
+
};
|
|
1312
|
+
this.send(msg);
|
|
1313
|
+
try {
|
|
1314
|
+
await openedPromise;
|
|
1315
|
+
} catch (error) {
|
|
1316
|
+
this.wsProxies.delete(wid);
|
|
1317
|
+
throw error;
|
|
1318
|
+
}
|
|
1319
|
+
return {
|
|
1320
|
+
wid,
|
|
1321
|
+
send: (data) => {
|
|
1322
|
+
const payload = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
1323
|
+
const flags = typeof data === "string" ? BinaryFlags.TEXT : 0;
|
|
1324
|
+
const frame = encodeBinaryFrame(BinaryKind.WsData, wid, 0, flags, payload);
|
|
1325
|
+
this.sendBinary(frame);
|
|
1326
|
+
},
|
|
1327
|
+
close: (code, reason) => {
|
|
1328
|
+
const closeMsg = { t: "ws.close", wid, code, reason };
|
|
1329
|
+
this.send(closeMsg);
|
|
1330
|
+
this.wsProxies.delete(wid);
|
|
1331
|
+
},
|
|
1332
|
+
onMessage: (handler) => {
|
|
1333
|
+
proxy.onMessage = handler;
|
|
1334
|
+
},
|
|
1335
|
+
onClose: (handler) => {
|
|
1336
|
+
proxy.onClose = handler;
|
|
1337
|
+
}
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
createReadableStream(sid) {
|
|
1341
|
+
return new ReadableStream({
|
|
1342
|
+
start: (controller) => {
|
|
1343
|
+
this.activeStreams.set(sid, {
|
|
1344
|
+
controller,
|
|
1345
|
+
buffer: [],
|
|
1346
|
+
creditRemaining: 0,
|
|
1347
|
+
pendingPull: null,
|
|
1348
|
+
ended: false,
|
|
1349
|
+
closed: false
|
|
1350
|
+
});
|
|
1351
|
+
},
|
|
1352
|
+
pull: async (controller) => {
|
|
1353
|
+
const stream = this.activeStreams.get(sid);
|
|
1354
|
+
if (!stream || stream.closed)
|
|
1355
|
+
return;
|
|
1356
|
+
if (stream.buffer.length > 0) {
|
|
1357
|
+
const chunk = stream.buffer.shift();
|
|
1358
|
+
controller.enqueue(chunk);
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
if (stream.ended) {
|
|
1362
|
+
controller.close();
|
|
1363
|
+
this.activeStreams.delete(sid);
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
const pullMsg = {
|
|
1367
|
+
t: "stream.pull",
|
|
1368
|
+
sid,
|
|
1369
|
+
creditBytes: DEFAULT_CHUNK_SIZE * 4
|
|
1370
|
+
};
|
|
1371
|
+
this.send(pullMsg);
|
|
1372
|
+
await new Promise((resolve, reject) => {
|
|
1373
|
+
stream.pendingPull = { resolve, reject };
|
|
1374
|
+
});
|
|
1375
|
+
stream.pendingPull = null;
|
|
1376
|
+
if (stream.closed)
|
|
1377
|
+
return;
|
|
1378
|
+
if (stream.buffer.length > 0) {
|
|
1379
|
+
const chunk = stream.buffer.shift();
|
|
1380
|
+
controller.enqueue(chunk);
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
if (stream.ended) {
|
|
1384
|
+
controller.close();
|
|
1385
|
+
this.activeStreams.delete(sid);
|
|
1386
|
+
}
|
|
1387
|
+
},
|
|
1388
|
+
cancel: () => {
|
|
1389
|
+
const stream = this.activeStreams.get(sid);
|
|
1390
|
+
if (stream) {
|
|
1391
|
+
stream.closed = true;
|
|
1392
|
+
if (stream.pendingPull) {
|
|
1393
|
+
stream.pendingPull.reject(new Error("Stream cancelled"));
|
|
1394
|
+
stream.pendingPull = null;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
this.activeStreams.delete(sid);
|
|
1398
|
+
}
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
handleJsonMessage(data) {
|
|
1402
|
+
try {
|
|
1403
|
+
const msg = parseJsonMsg(data);
|
|
1404
|
+
switch (msg.t) {
|
|
1405
|
+
case "event":
|
|
1406
|
+
this.handleEvent(msg);
|
|
1407
|
+
break;
|
|
1408
|
+
case "stream.pull":
|
|
1409
|
+
this.handleStreamPull(msg);
|
|
1410
|
+
break;
|
|
1411
|
+
case "stream.end":
|
|
1412
|
+
this.handleStreamEnd(msg);
|
|
1413
|
+
break;
|
|
1414
|
+
case "stream.abort":
|
|
1415
|
+
this.handleStreamAbort(msg);
|
|
1416
|
+
break;
|
|
1417
|
+
case "ws.opened":
|
|
1418
|
+
this.handleWsOpened(msg);
|
|
1419
|
+
break;
|
|
1420
|
+
case "ws.close":
|
|
1421
|
+
this.handleWsClose(msg);
|
|
1422
|
+
break;
|
|
1423
|
+
}
|
|
1424
|
+
} catch (error) {
|
|
1425
|
+
console.error("[devflare bridge client] parse error:", data, error);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
handleV2BinaryFrame(frame) {
|
|
1429
|
+
switch (frame.kind) {
|
|
1430
|
+
case BinaryKind.StreamChunk:
|
|
1431
|
+
this.handleStreamChunk(frame);
|
|
1432
|
+
break;
|
|
1433
|
+
case BinaryKind.WsData:
|
|
1434
|
+
this.handleWsData(frame);
|
|
1435
|
+
break;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
handleEvent(msg) {
|
|
1439
|
+
this.dispatchEvent(this.eventListeners.get(msg.topic), msg.data, msg.topic);
|
|
1440
|
+
this.dispatchEvent(this.eventListeners.get("*"), msg.data, msg.topic);
|
|
1441
|
+
}
|
|
1442
|
+
dispatchEvent(listeners, data, topic) {
|
|
1443
|
+
if (!listeners)
|
|
1444
|
+
return;
|
|
1445
|
+
for (const cb of [...listeners]) {
|
|
1446
|
+
try {
|
|
1447
|
+
cb(data);
|
|
1448
|
+
} catch (error) {
|
|
1449
|
+
bridgeLog.warn(`event listener for topic "${topic}" threw`, error);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
handleStreamPull(msg) {
|
|
1454
|
+
const streamRef = this.outgoingStreams.get(msg.sid);
|
|
1455
|
+
if (!streamRef)
|
|
1456
|
+
return;
|
|
1457
|
+
this.pumpStream(streamRef, msg.creditBytes);
|
|
1458
|
+
}
|
|
1459
|
+
async pumpStream(streamRef, creditBytes) {
|
|
1460
|
+
const reader = streamRef.stream.getReader();
|
|
1461
|
+
let sent = 0;
|
|
1462
|
+
let seq = 0;
|
|
1463
|
+
try {
|
|
1464
|
+
while (sent < creditBytes) {
|
|
1465
|
+
const { done, value } = await reader.read();
|
|
1466
|
+
if (done) {
|
|
1467
|
+
this.send({ t: "stream.end", sid: streamRef.sid });
|
|
1468
|
+
this.outgoingStreams.delete(streamRef.sid);
|
|
1469
|
+
break;
|
|
1470
|
+
}
|
|
1471
|
+
if (value) {
|
|
1472
|
+
const frame = encodeBinaryFrame(BinaryKind.StreamChunk, streamRef.sid, seq++, 0, value);
|
|
1473
|
+
this.sendBinary(frame);
|
|
1474
|
+
sent += value.byteLength;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
} catch (error) {
|
|
1478
|
+
this.send({
|
|
1479
|
+
t: "stream.abort",
|
|
1480
|
+
sid: streamRef.sid,
|
|
1481
|
+
error: String(error)
|
|
1482
|
+
});
|
|
1483
|
+
this.outgoingStreams.delete(streamRef.sid);
|
|
1484
|
+
} finally {
|
|
1485
|
+
reader.releaseLock();
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
handleStreamChunk(decoded) {
|
|
1489
|
+
const stream = this.activeStreams.get(decoded.id);
|
|
1490
|
+
if (!stream || stream.closed)
|
|
1491
|
+
return;
|
|
1492
|
+
stream.buffer.push(decoded.payload);
|
|
1493
|
+
if (stream.pendingPull) {
|
|
1494
|
+
const pending = stream.pendingPull;
|
|
1495
|
+
stream.pendingPull = null;
|
|
1496
|
+
pending.resolve();
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
handleStreamEnd(msg) {
|
|
1500
|
+
const stream = this.activeStreams.get(msg.sid);
|
|
1501
|
+
if (!stream)
|
|
1502
|
+
return;
|
|
1503
|
+
stream.ended = true;
|
|
1504
|
+
if (stream.pendingPull) {
|
|
1505
|
+
const pending = stream.pendingPull;
|
|
1506
|
+
stream.pendingPull = null;
|
|
1507
|
+
pending.resolve();
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
handleStreamAbort(msg) {
|
|
1511
|
+
const stream = this.activeStreams.get(msg.sid);
|
|
1512
|
+
if (!stream)
|
|
1513
|
+
return;
|
|
1514
|
+
const err = new Error(msg.error ?? "Stream aborted");
|
|
1515
|
+
stream.closed = true;
|
|
1516
|
+
if (stream.pendingPull) {
|
|
1517
|
+
const pending = stream.pendingPull;
|
|
1518
|
+
stream.pendingPull = null;
|
|
1519
|
+
pending.reject(err);
|
|
1520
|
+
}
|
|
1521
|
+
try {
|
|
1522
|
+
stream.controller.error(err);
|
|
1523
|
+
} catch {}
|
|
1524
|
+
this.activeStreams.delete(msg.sid);
|
|
1525
|
+
}
|
|
1526
|
+
handleWsData(decoded) {
|
|
1527
|
+
const proxy = this.wsProxies.get(decoded.id);
|
|
1528
|
+
if (!proxy)
|
|
1529
|
+
return;
|
|
1530
|
+
const isText = (decoded.flags & BinaryFlags.TEXT) !== 0;
|
|
1531
|
+
const data = isText ? new TextDecoder().decode(decoded.payload) : decoded.payload;
|
|
1532
|
+
proxy.onMessage(data);
|
|
1533
|
+
}
|
|
1534
|
+
handleWsClose(msg) {
|
|
1535
|
+
const proxy = this.wsProxies.get(msg.wid);
|
|
1536
|
+
if (!proxy)
|
|
1537
|
+
return;
|
|
1538
|
+
proxy.onClose(msg.code, msg.reason);
|
|
1539
|
+
this.wsProxies.delete(msg.wid);
|
|
1540
|
+
}
|
|
1541
|
+
handleWsOpened(msg) {
|
|
1542
|
+
const pending = this.pendingWsOpens.get(msg.wid);
|
|
1543
|
+
if (!pending)
|
|
1544
|
+
return;
|
|
1545
|
+
this.pendingWsOpens.delete(msg.wid);
|
|
1546
|
+
pending.resolve();
|
|
1547
|
+
}
|
|
1548
|
+
async ensureConnected() {
|
|
1549
|
+
if (!this.isConnected) {
|
|
1550
|
+
await this.connect();
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
send(msg) {
|
|
1554
|
+
const codec = this.codec;
|
|
1555
|
+
if (!codec || !this.isConnected) {
|
|
1556
|
+
throw new Error("Not connected to bridge");
|
|
1557
|
+
}
|
|
1558
|
+
codec.sendText(stringifyJsonMsg(msg));
|
|
1559
|
+
}
|
|
1560
|
+
sendBinary(frame) {
|
|
1561
|
+
const codec = this.codec;
|
|
1562
|
+
if (!codec || !this.isConnected) {
|
|
1563
|
+
throw new Error("Not connected to bridge");
|
|
1564
|
+
}
|
|
1565
|
+
codec.sendBinary(frame);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
var defaultClient = null;
|
|
1569
|
+
function getClient(options) {
|
|
1570
|
+
if (!defaultClient) {
|
|
1571
|
+
defaultClient = new BridgeClient(options);
|
|
1572
|
+
}
|
|
1573
|
+
return defaultClient;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// src/bridge/proxy.ts
|
|
1577
|
+
function createKVProxy(client, bindingName) {
|
|
1578
|
+
return {
|
|
1579
|
+
async get(key, options) {
|
|
1580
|
+
return client.call(`${bindingName}.kv.get`, [key, options]);
|
|
1581
|
+
},
|
|
1582
|
+
async put(key, value, options) {
|
|
1583
|
+
await client.call(`${bindingName}.kv.put`, [key, value, options]);
|
|
1584
|
+
},
|
|
1585
|
+
async delete(key) {
|
|
1586
|
+
await client.call(`${bindingName}.kv.delete`, [key]);
|
|
1587
|
+
},
|
|
1588
|
+
async list(options) {
|
|
1589
|
+
return client.call(`${bindingName}.kv.list`, [options]);
|
|
1590
|
+
},
|
|
1591
|
+
async getWithMetadata(key, options) {
|
|
1592
|
+
return client.call(`${bindingName}.kv.getWithMetadata`, [key, options]);
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
function createR2Proxy(client, bindingName) {
|
|
1597
|
+
return {
|
|
1598
|
+
async head(key) {
|
|
1599
|
+
return client.call(`${bindingName}.r2.head`, [key]);
|
|
1600
|
+
},
|
|
1601
|
+
async get(key, options) {
|
|
1602
|
+
return client.call(`${bindingName}.r2.get`, [key, options]);
|
|
1603
|
+
},
|
|
1604
|
+
async put(key, value, options) {
|
|
1605
|
+
const size = getValueSize(value);
|
|
1606
|
+
if (size > HTTP_TRANSFER_THRESHOLD) {
|
|
1607
|
+
const transferId = `${bindingName}:${key}`;
|
|
1608
|
+
const httpUrl = client.getHttpUrl();
|
|
1609
|
+
const transferUrl = httpUrl.replace(/\/$/, "") + `/_devflare/transfer/${encodeURIComponent(transferId)}`;
|
|
1610
|
+
const response = await fetch(transferUrl, {
|
|
1611
|
+
method: "PUT",
|
|
1612
|
+
body: value,
|
|
1613
|
+
headers: {
|
|
1614
|
+
...options?.httpMetadata?.contentType ? { "Content-Type": options.httpMetadata.contentType } : {}
|
|
1615
|
+
}
|
|
1616
|
+
});
|
|
1617
|
+
if (!response.ok) {
|
|
1618
|
+
const error = await response.text();
|
|
1619
|
+
throw new Error(`HTTP transfer failed: ${error}`);
|
|
1620
|
+
}
|
|
1621
|
+
const serialized = await response.json();
|
|
1622
|
+
return deserializeValue(serialized);
|
|
1623
|
+
}
|
|
1624
|
+
return client.call(`${bindingName}.r2.put`, [key, value, options]);
|
|
1625
|
+
},
|
|
1626
|
+
async delete(keys) {
|
|
1627
|
+
await client.call(`${bindingName}.r2.delete`, [keys]);
|
|
1628
|
+
},
|
|
1629
|
+
async list(options) {
|
|
1630
|
+
return client.call(`${bindingName}.r2.list`, [options]);
|
|
1631
|
+
},
|
|
1632
|
+
async createMultipartUpload(key, options) {
|
|
1633
|
+
return client.call(`${bindingName}.r2.createMultipartUpload`, [
|
|
1634
|
+
key,
|
|
1635
|
+
options
|
|
1636
|
+
]);
|
|
1637
|
+
},
|
|
1638
|
+
async resumeMultipartUpload(key, uploadId) {
|
|
1639
|
+
return client.call(`${bindingName}.r2.resumeMultipartUpload`, [
|
|
1640
|
+
key,
|
|
1641
|
+
uploadId
|
|
1642
|
+
]);
|
|
1643
|
+
}
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
function getValueSize(value) {
|
|
1647
|
+
if (value instanceof Blob)
|
|
1648
|
+
return value.size;
|
|
1649
|
+
if (value instanceof ArrayBuffer)
|
|
1650
|
+
return value.byteLength;
|
|
1651
|
+
if (value instanceof Uint8Array)
|
|
1652
|
+
return value.byteLength;
|
|
1653
|
+
if (typeof value === "string")
|
|
1654
|
+
return new TextEncoder().encode(value).byteLength;
|
|
1655
|
+
if (value instanceof ReadableStream)
|
|
1656
|
+
return Number.POSITIVE_INFINITY;
|
|
1657
|
+
return 0;
|
|
1658
|
+
}
|
|
1659
|
+
function createD1Proxy(client, bindingName) {
|
|
1660
|
+
return {
|
|
1661
|
+
prepare(sql) {
|
|
1662
|
+
return createD1StatementProxy(client, bindingName, sql, []);
|
|
1663
|
+
},
|
|
1664
|
+
async batch(statements) {
|
|
1665
|
+
const serialized = statements.map((stmt) => {
|
|
1666
|
+
const s = stmt;
|
|
1667
|
+
return { sql: s._sql, bindings: s._bindings };
|
|
1668
|
+
});
|
|
1669
|
+
return client.call(`${bindingName}.d1.batch`, [serialized]);
|
|
1670
|
+
},
|
|
1671
|
+
async exec(sql) {
|
|
1672
|
+
return client.call(`${bindingName}.d1.exec`, [sql]);
|
|
1673
|
+
},
|
|
1674
|
+
async dump() {
|
|
1675
|
+
return client.call(`${bindingName}.d1.dump`, []);
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
function createD1StatementProxy(client, bindingName, sql, bindings) {
|
|
1680
|
+
const stmt = {
|
|
1681
|
+
_sql: sql,
|
|
1682
|
+
_bindings: bindings,
|
|
1683
|
+
bind(...values) {
|
|
1684
|
+
return createD1StatementProxy(client, bindingName, sql, values);
|
|
1685
|
+
},
|
|
1686
|
+
async first(column) {
|
|
1687
|
+
return client.call(`${bindingName}.d1.stmt.first`, [sql, ...bindings, column]);
|
|
1688
|
+
},
|
|
1689
|
+
async all() {
|
|
1690
|
+
return client.call(`${bindingName}.d1.stmt.all`, [sql, ...bindings]);
|
|
1691
|
+
},
|
|
1692
|
+
async run() {
|
|
1693
|
+
return client.call(`${bindingName}.d1.stmt.run`, [sql, ...bindings]);
|
|
1694
|
+
},
|
|
1695
|
+
async raw(options) {
|
|
1696
|
+
return client.call(`${bindingName}.d1.stmt.raw`, [sql, ...bindings, options]);
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1699
|
+
return stmt;
|
|
1700
|
+
}
|
|
1701
|
+
function createDOProxy(client, bindingName, proxyOptions = {}) {
|
|
1702
|
+
return {
|
|
1703
|
+
idFromName(name) {
|
|
1704
|
+
return createDOIdProxy(client, bindingName, {
|
|
1705
|
+
type: "name",
|
|
1706
|
+
value: name,
|
|
1707
|
+
jurisdiction: proxyOptions.jurisdiction
|
|
1708
|
+
});
|
|
1709
|
+
},
|
|
1710
|
+
idFromString(hexId) {
|
|
1711
|
+
return createDOIdProxy(client, bindingName, {
|
|
1712
|
+
type: "hex",
|
|
1713
|
+
value: hexId,
|
|
1714
|
+
jurisdiction: proxyOptions.jurisdiction
|
|
1715
|
+
});
|
|
1716
|
+
},
|
|
1717
|
+
newUniqueId(options) {
|
|
1718
|
+
const tempId = crypto.randomUUID().replace(/-/g, "");
|
|
1719
|
+
return createDOIdProxy(client, bindingName, {
|
|
1720
|
+
type: "unique",
|
|
1721
|
+
value: tempId,
|
|
1722
|
+
options,
|
|
1723
|
+
jurisdiction: proxyOptions.jurisdiction
|
|
1724
|
+
});
|
|
1725
|
+
},
|
|
1726
|
+
get(id) {
|
|
1727
|
+
const idProxy = id;
|
|
1728
|
+
return createDOStubProxy(client, bindingName, idProxy._idInfo, proxyOptions);
|
|
1729
|
+
},
|
|
1730
|
+
getByName(name) {
|
|
1731
|
+
const id = this.idFromName(name);
|
|
1732
|
+
return this.get(id);
|
|
1733
|
+
},
|
|
1734
|
+
jurisdiction(jurisdiction) {
|
|
1735
|
+
return createDOProxy(client, bindingName, { ...proxyOptions, jurisdiction });
|
|
1736
|
+
}
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
function createDOIdProxy(client, bindingName, idInfo) {
|
|
1740
|
+
return {
|
|
1741
|
+
_idInfo: idInfo,
|
|
1742
|
+
toString() {
|
|
1743
|
+
if (idInfo.type === "hex")
|
|
1744
|
+
return idInfo.value;
|
|
1745
|
+
return `${idInfo.type}:${idInfo.value}`;
|
|
1746
|
+
},
|
|
1747
|
+
equals(other) {
|
|
1748
|
+
return this.toString() === other.toString();
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
function createDOStubProxy(client, bindingName, idInfo, proxyOptions = {}) {
|
|
1753
|
+
const { transformResult } = proxyOptions;
|
|
1754
|
+
let resolvedId = null;
|
|
1755
|
+
const resolveId = async () => {
|
|
1756
|
+
if (resolvedId)
|
|
1757
|
+
return resolvedId;
|
|
1758
|
+
switch (idInfo.type) {
|
|
1759
|
+
case "name":
|
|
1760
|
+
resolvedId = await client.call(`${bindingName}.do.idFromName`, [
|
|
1761
|
+
idInfo.value,
|
|
1762
|
+
idInfo.jurisdiction
|
|
1763
|
+
]);
|
|
1764
|
+
break;
|
|
1765
|
+
case "hex":
|
|
1766
|
+
resolvedId = { __type: "DOId", hex: idInfo.value };
|
|
1767
|
+
break;
|
|
1768
|
+
case "unique":
|
|
1769
|
+
resolvedId = await client.call(`${bindingName}.do.newUniqueId`, [
|
|
1770
|
+
idInfo.options,
|
|
1771
|
+
idInfo.jurisdiction
|
|
1772
|
+
]);
|
|
1773
|
+
break;
|
|
1774
|
+
}
|
|
1775
|
+
return resolvedId;
|
|
1776
|
+
};
|
|
1777
|
+
const stubBase = {
|
|
1778
|
+
async fetch(input, init) {
|
|
1779
|
+
const id = await resolveId();
|
|
1780
|
+
const request = input instanceof Request ? input : new Request(input, init);
|
|
1781
|
+
const { serialized } = await serializeRequest(request);
|
|
1782
|
+
const result = await client.call(`${bindingName}.do.fetch`, [bindingName, id, serialized]);
|
|
1783
|
+
return deserializeResponse(result);
|
|
1784
|
+
},
|
|
1785
|
+
async connect(url, options) {
|
|
1786
|
+
const id = await resolveId();
|
|
1787
|
+
const headersList = [];
|
|
1788
|
+
if (options?.headers) {
|
|
1789
|
+
const headers = new Headers(options.headers);
|
|
1790
|
+
headers.forEach((value, key) => {
|
|
1791
|
+
headersList.push([key, value]);
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
const wsProxy = await client.createWsProxy(bindingName, id.hex, url, headersList);
|
|
1795
|
+
let readController = null;
|
|
1796
|
+
const readable = new ReadableStream({
|
|
1797
|
+
start(controller) {
|
|
1798
|
+
readController = controller;
|
|
1799
|
+
},
|
|
1800
|
+
cancel() {
|
|
1801
|
+
wsProxy.close();
|
|
1802
|
+
}
|
|
1803
|
+
});
|
|
1804
|
+
wsProxy.onMessage((data) => {
|
|
1805
|
+
if (readController) {
|
|
1806
|
+
const chunk = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
1807
|
+
readController.enqueue(chunk);
|
|
1808
|
+
}
|
|
1809
|
+
});
|
|
1810
|
+
wsProxy.onClose((code, reason) => {
|
|
1811
|
+
if (readController) {
|
|
1812
|
+
readController.close();
|
|
1813
|
+
}
|
|
1814
|
+
});
|
|
1815
|
+
const writable = new WritableStream({
|
|
1816
|
+
write(chunk) {
|
|
1817
|
+
wsProxy.send(chunk);
|
|
1818
|
+
},
|
|
1819
|
+
close() {
|
|
1820
|
+
wsProxy.close(1000, "Normal closure");
|
|
1821
|
+
},
|
|
1822
|
+
abort(reason) {
|
|
1823
|
+
wsProxy.close(1001, reason?.toString() ?? "Aborted");
|
|
1824
|
+
}
|
|
1825
|
+
});
|
|
1826
|
+
return {
|
|
1827
|
+
readable,
|
|
1828
|
+
writable,
|
|
1829
|
+
get opened() {
|
|
1830
|
+
return Promise.resolve({
|
|
1831
|
+
remoteAddress: "127.0.0.1",
|
|
1832
|
+
localAddress: "127.0.0.1"
|
|
1833
|
+
});
|
|
1834
|
+
},
|
|
1835
|
+
get closed() {
|
|
1836
|
+
return new Promise((resolve) => {
|
|
1837
|
+
wsProxy.onClose(() => resolve());
|
|
1838
|
+
});
|
|
1839
|
+
},
|
|
1840
|
+
close() {
|
|
1841
|
+
wsProxy.close(1000, "Normal closure");
|
|
1842
|
+
return Promise.resolve();
|
|
1843
|
+
},
|
|
1844
|
+
startTls() {
|
|
1845
|
+
throw new Error("startTls() is not supported on a Durable Object WebSocket connection. " + "StartTLS applies to raw TCP sockets (cloudflare:sockets connect()), not " + "WebSocket-tunneled DO connections, whose transport is already secured by the connection itself.");
|
|
1846
|
+
}
|
|
1847
|
+
};
|
|
1848
|
+
},
|
|
1849
|
+
get id() {
|
|
1850
|
+
return createDOIdProxy(client, bindingName, idInfo);
|
|
1851
|
+
},
|
|
1852
|
+
get name() {
|
|
1853
|
+
return idInfo.type === "name" ? idInfo.value : undefined;
|
|
1854
|
+
}
|
|
1855
|
+
};
|
|
1856
|
+
return new Proxy(stubBase, {
|
|
1857
|
+
get(target, prop) {
|
|
1858
|
+
if (prop in target) {
|
|
1859
|
+
return target[prop];
|
|
1860
|
+
}
|
|
1861
|
+
if (typeof prop === "symbol") {
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
if (prop === "then" || prop === "catch" || prop === "finally") {
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
return async (...args) => {
|
|
1868
|
+
const id = await resolveId();
|
|
1869
|
+
let result = await client.call(`${bindingName}.do.rpc`, [bindingName, id, prop, args]);
|
|
1870
|
+
if (transformResult) {
|
|
1871
|
+
result = transformResult(result);
|
|
1872
|
+
}
|
|
1873
|
+
return result;
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
function isResponseLike(value) {
|
|
1879
|
+
return value instanceof Response;
|
|
1880
|
+
}
|
|
1881
|
+
function createServiceProxy(client, bindingName, proxyOptions = {}) {
|
|
1882
|
+
const { transformResult } = proxyOptions;
|
|
1883
|
+
const serviceBase = {
|
|
1884
|
+
async fetch(input, init) {
|
|
1885
|
+
const request = input instanceof Request ? input : new Request(input, init);
|
|
1886
|
+
const { serialized } = await serializeRequest(request);
|
|
1887
|
+
const result = await client.call(`${bindingName}.service.fetch`, [serialized]);
|
|
1888
|
+
if (isResponseLike(result)) {
|
|
1889
|
+
return result;
|
|
1890
|
+
}
|
|
1891
|
+
return deserializeResponse(result);
|
|
1892
|
+
}
|
|
1893
|
+
};
|
|
1894
|
+
return new Proxy(serviceBase, {
|
|
1895
|
+
get(target, prop) {
|
|
1896
|
+
if (typeof prop !== "string") {
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1899
|
+
if (prop in target) {
|
|
1900
|
+
return target[prop];
|
|
1901
|
+
}
|
|
1902
|
+
if (prop === "then" || prop === "catch" || prop === "finally") {
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
if (prop === "toString") {
|
|
1906
|
+
return () => `[DevflareServiceBinding ${bindingName}]`;
|
|
1907
|
+
}
|
|
1908
|
+
return async (...args) => {
|
|
1909
|
+
let result = await client.call(`${bindingName}.service.rpc`, [prop, args]);
|
|
1910
|
+
if (transformResult) {
|
|
1911
|
+
result = transformResult(result);
|
|
1912
|
+
}
|
|
1913
|
+
return result;
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
var EMPTY_QUEUE_METRICS = {
|
|
1919
|
+
backlogCount: 0,
|
|
1920
|
+
backlogBytes: 0
|
|
1921
|
+
};
|
|
1922
|
+
function createQueueResponse() {
|
|
1923
|
+
return { metadata: { metrics: EMPTY_QUEUE_METRICS } };
|
|
1924
|
+
}
|
|
1925
|
+
function createQueueProxy(client, bindingName) {
|
|
1926
|
+
return {
|
|
1927
|
+
async metrics() {
|
|
1928
|
+
return EMPTY_QUEUE_METRICS;
|
|
1929
|
+
},
|
|
1930
|
+
async send(message, options) {
|
|
1931
|
+
await client.call(`${bindingName}.queue.send`, [message, options]);
|
|
1932
|
+
return createQueueResponse();
|
|
1933
|
+
},
|
|
1934
|
+
async sendBatch(messages, options) {
|
|
1935
|
+
await client.call(`${bindingName}.queue.sendBatch`, [messages, options]);
|
|
1936
|
+
return createQueueResponse();
|
|
1937
|
+
}
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
function createAIProxy(client, bindingName) {
|
|
1941
|
+
return {
|
|
1942
|
+
async run(model, inputs, options) {
|
|
1943
|
+
return client.call(`${bindingName}.ai.run`, [model, inputs, options]);
|
|
1944
|
+
}
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
function createSendEmailProxy(client, bindingName) {
|
|
1948
|
+
return {
|
|
1949
|
+
async send(message) {
|
|
1950
|
+
return client.call(`${bindingName}.email.send`, [message]);
|
|
1951
|
+
}
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
function createWorkflowInstanceProxy(client, bindingName, id) {
|
|
1955
|
+
return {
|
|
1956
|
+
id,
|
|
1957
|
+
async pause() {
|
|
1958
|
+
await client.call(`${bindingName}.workflow.pause`, [id]);
|
|
1959
|
+
},
|
|
1960
|
+
async resume() {
|
|
1961
|
+
await client.call(`${bindingName}.workflow.resume`, [id]);
|
|
1962
|
+
},
|
|
1963
|
+
async terminate() {
|
|
1964
|
+
await client.call(`${bindingName}.workflow.terminate`, [id]);
|
|
1965
|
+
},
|
|
1966
|
+
async restart() {
|
|
1967
|
+
await client.call(`${bindingName}.workflow.restart`, [id]);
|
|
1968
|
+
},
|
|
1969
|
+
async status() {
|
|
1970
|
+
return client.call(`${bindingName}.workflow.status`, [id]);
|
|
1971
|
+
},
|
|
1972
|
+
async sendEvent(event) {
|
|
1973
|
+
await client.call(`${bindingName}.workflow.sendEvent`, [id, event]);
|
|
1974
|
+
}
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
function createWorkflowProxy(client, bindingName) {
|
|
1978
|
+
const toInstance = (value) => {
|
|
1979
|
+
const id = value?.id;
|
|
1980
|
+
if (typeof id !== "string") {
|
|
1981
|
+
throw new Error(`Workflow ${bindingName} returned an instance without a string id.`);
|
|
1982
|
+
}
|
|
1983
|
+
return createWorkflowInstanceProxy(client, bindingName, id);
|
|
1984
|
+
};
|
|
1985
|
+
return {
|
|
1986
|
+
async create(options) {
|
|
1987
|
+
return toInstance(await client.call(`${bindingName}.workflow.create`, [options]));
|
|
1988
|
+
},
|
|
1989
|
+
async get(id) {
|
|
1990
|
+
return toInstance(await client.call(`${bindingName}.workflow.get`, [id]));
|
|
1991
|
+
}
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
var globalBindingHints = {};
|
|
1995
|
+
function createEnvProxy(options = {}) {
|
|
1996
|
+
const client = options.client ?? getClient();
|
|
1997
|
+
const bindingProxies = new Map;
|
|
1998
|
+
const doProxyOptions = { transformResult: options.transformResult };
|
|
1999
|
+
const serviceProxyOptions = { transformResult: options.transformResult };
|
|
2000
|
+
const strict = options.strict === true;
|
|
2001
|
+
const hints = { ...globalBindingHints, ...options.hints };
|
|
2002
|
+
return new Proxy({}, {
|
|
2003
|
+
get(target, prop) {
|
|
2004
|
+
if (typeof prop !== "string")
|
|
2005
|
+
return;
|
|
2006
|
+
if (bindingProxies.has(prop)) {
|
|
2007
|
+
return bindingProxies.get(prop);
|
|
2008
|
+
}
|
|
2009
|
+
const hint = hints[prop];
|
|
2010
|
+
if (!hint && strict) {
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
let proxy;
|
|
2014
|
+
switch (hint) {
|
|
2015
|
+
case "kv":
|
|
2016
|
+
proxy = createKVProxy(client, prop);
|
|
2017
|
+
break;
|
|
2018
|
+
case "r2":
|
|
2019
|
+
proxy = createR2Proxy(client, prop);
|
|
2020
|
+
break;
|
|
2021
|
+
case "d1":
|
|
2022
|
+
proxy = createD1Proxy(client, prop);
|
|
2023
|
+
break;
|
|
2024
|
+
case "do":
|
|
2025
|
+
proxy = createDOProxy(client, prop, doProxyOptions);
|
|
2026
|
+
break;
|
|
2027
|
+
case "queue":
|
|
2028
|
+
proxy = createQueueProxy(client, prop);
|
|
2029
|
+
break;
|
|
2030
|
+
case "ai":
|
|
2031
|
+
proxy = createAIProxy(client, prop);
|
|
2032
|
+
break;
|
|
2033
|
+
case "service":
|
|
2034
|
+
proxy = createServiceProxy(client, prop, serviceProxyOptions);
|
|
2035
|
+
break;
|
|
2036
|
+
case "sendEmail":
|
|
2037
|
+
proxy = createSendEmailProxy(client, prop);
|
|
2038
|
+
break;
|
|
2039
|
+
case "workflow":
|
|
2040
|
+
proxy = createWorkflowProxy(client, prop);
|
|
2041
|
+
break;
|
|
2042
|
+
case "secret":
|
|
2043
|
+
case "var":
|
|
2044
|
+
proxy = createSimpleBindingProxy(client, prop);
|
|
2045
|
+
break;
|
|
2046
|
+
default:
|
|
2047
|
+
proxy = createGenericBindingProxy(client, prop);
|
|
2048
|
+
}
|
|
2049
|
+
bindingProxies.set(prop, proxy);
|
|
2050
|
+
return proxy;
|
|
2051
|
+
},
|
|
2052
|
+
has(target, prop) {
|
|
2053
|
+
return typeof prop === "string" && (!strict || (prop in hints));
|
|
2054
|
+
},
|
|
2055
|
+
ownKeys() {
|
|
2056
|
+
return Object.keys(hints);
|
|
2057
|
+
},
|
|
2058
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
2059
|
+
if (typeof prop === "string" && (!strict || (prop in hints))) {
|
|
2060
|
+
return { configurable: true, enumerable: true, writable: false };
|
|
2061
|
+
}
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
function createGenericBindingProxy(client, bindingName) {
|
|
2067
|
+
return new Proxy({}, {
|
|
2068
|
+
get(target, prop) {
|
|
2069
|
+
if (typeof prop !== "string")
|
|
2070
|
+
return;
|
|
2071
|
+
if (["get", "put", "delete", "list", "getWithMetadata"].includes(prop)) {
|
|
2072
|
+
return createKVProxy(client, bindingName)[prop];
|
|
2073
|
+
}
|
|
2074
|
+
if (["idFromName", "idFromString", "newUniqueId", "get"].includes(prop)) {
|
|
2075
|
+
return createDOProxy(client, bindingName)[prop];
|
|
2076
|
+
}
|
|
2077
|
+
if (["prepare", "batch", "exec", "dump"].includes(prop)) {
|
|
2078
|
+
return createD1Proxy(client, bindingName)[prop];
|
|
2079
|
+
}
|
|
2080
|
+
if (["head"].includes(prop)) {
|
|
2081
|
+
return createR2Proxy(client, bindingName)[prop];
|
|
2082
|
+
}
|
|
2083
|
+
return async (...args) => {
|
|
2084
|
+
return client.call(`${bindingName}.${prop}`, args);
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
});
|
|
2088
|
+
}
|
|
2089
|
+
function createSimpleBindingProxy(client, bindingName) {
|
|
2090
|
+
let cachedValue;
|
|
2091
|
+
let fetched = false;
|
|
2092
|
+
let pendingValue = null;
|
|
2093
|
+
const loadValue = () => {
|
|
2094
|
+
if (fetched) {
|
|
2095
|
+
return Promise.resolve(cachedValue);
|
|
2096
|
+
}
|
|
2097
|
+
if (!pendingValue) {
|
|
2098
|
+
pendingValue = client.call(`${bindingName}.var.value`, []).then((value) => {
|
|
2099
|
+
cachedValue = value;
|
|
2100
|
+
fetched = true;
|
|
2101
|
+
return value;
|
|
2102
|
+
}).catch((error) => {
|
|
2103
|
+
pendingValue = null;
|
|
2104
|
+
throw error;
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2107
|
+
return pendingValue;
|
|
2108
|
+
};
|
|
2109
|
+
return {
|
|
2110
|
+
then(resolve, reject) {
|
|
2111
|
+
return loadValue().then(resolve ?? undefined, reject ?? undefined);
|
|
2112
|
+
},
|
|
2113
|
+
toString() {
|
|
2114
|
+
if (!fetched)
|
|
2115
|
+
throw new Error(`Binding ${bindingName} not yet fetched. Use await.`);
|
|
2116
|
+
return String(cachedValue);
|
|
2117
|
+
}
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
var globalEnvProxy = null;
|
|
2121
|
+
var bridgeEnv = new Proxy({}, {
|
|
2122
|
+
get(target, prop) {
|
|
2123
|
+
if (!globalEnvProxy) {
|
|
2124
|
+
globalEnvProxy = createEnvProxy({ lazy: true });
|
|
2125
|
+
}
|
|
2126
|
+
return globalEnvProxy[prop];
|
|
2127
|
+
}
|
|
2128
|
+
});
|
|
2129
|
+
function initEnv(options = {}) {
|
|
2130
|
+
globalEnvProxy = createEnvProxy(options);
|
|
2131
|
+
return globalEnvProxy;
|
|
2132
|
+
}
|
|
2133
|
+
function setBindingHints(hints) {
|
|
2134
|
+
globalBindingHints = { ...globalBindingHints, ...hints };
|
|
2135
|
+
globalEnvProxy = null;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
// src/runtime/context.ts
|
|
2139
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2140
|
+
|
|
2141
|
+
// src/runtime/validation.ts
|
|
2142
|
+
class ContextAccessError extends Error {
|
|
2143
|
+
contextName;
|
|
2144
|
+
propertyName;
|
|
2145
|
+
constructor(contextName, propertyName, message) {
|
|
2146
|
+
super(message ?? `Cannot access ${contextName}.${propertyName} outside of an active Devflare handler trail.
|
|
2147
|
+
|
|
2148
|
+
` + `This typically happens when:
|
|
2149
|
+
` + ` 1. Accessing ${contextName} at module top-level (during import)
|
|
2150
|
+
` + ` 2. Accessing ${contextName} in a callback that runs after the handler ends
|
|
2151
|
+
` + ` 3. Accessing ${contextName} in a setTimeout/setInterval callback
|
|
2152
|
+
|
|
2153
|
+
` + `Move the access inside your handler function or middleware.`);
|
|
2154
|
+
this.name = "ContextAccessError";
|
|
2155
|
+
this.contextName = contextName;
|
|
2156
|
+
this.propertyName = propertyName;
|
|
2157
|
+
}
|
|
2158
|
+
static contextUnavailable(message) {
|
|
2159
|
+
return new ContextAccessError("context", "<unavailable>", message ?? `Context not available. Devflare uses AsyncLocalStorage to carry the active event through fetch, queue, scheduled, email, tail, and Durable Object handler call chains.
|
|
2160
|
+
|
|
2161
|
+
` + `This usually means one of:
|
|
2162
|
+
|
|
2163
|
+
` + `1. Accessing context at module top-level (runs at cold start, not per-request)
|
|
2164
|
+
` + `2. Accessing context in setTimeout/setInterval callbacks
|
|
2165
|
+
` + `3. Missing 'nodejs_compat' compatibility flag in your worker config
|
|
2166
|
+
|
|
2167
|
+
` + `Fix: Move the access inside your handler, middleware, or a helper called from that handler trail.
|
|
2168
|
+
` + `Learn more: https://devflare.dev/docs/context-errors`);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
// src/runtime/context.ts
|
|
2173
|
+
var storage = new AsyncLocalStorage;
|
|
2174
|
+
function getContextOrNull() {
|
|
2175
|
+
const context = storage.getStore();
|
|
2176
|
+
return context ?? null;
|
|
2177
|
+
}
|
|
2178
|
+
function getEventContextOrNull() {
|
|
2179
|
+
return getContextOrNull()?.event ?? null;
|
|
2180
|
+
}
|
|
2181
|
+
function createEventAccessor(name, matcher) {
|
|
2182
|
+
const accessor = () => {
|
|
2183
|
+
const currentEvent = getEventContextOrNull();
|
|
2184
|
+
if (!currentEvent) {
|
|
2185
|
+
throw ContextAccessError.contextUnavailable();
|
|
2186
|
+
}
|
|
2187
|
+
if (!matcher(currentEvent)) {
|
|
2188
|
+
throw ContextAccessError.contextUnavailable(`${name} is not available in the current '${currentEvent.type}' context.
|
|
2189
|
+
|
|
2190
|
+
Devflare stores event objects in AsyncLocalStorage so helpers called within a handler can reach the active event.
|
|
2191
|
+
Use ${name}.safe() to return null instead of throwing, or call the getter that matches the active surface.`);
|
|
2192
|
+
}
|
|
2193
|
+
return currentEvent;
|
|
2194
|
+
};
|
|
2195
|
+
accessor.safe = () => {
|
|
2196
|
+
const currentEvent = getEventContextOrNull();
|
|
2197
|
+
return currentEvent && matcher(currentEvent) ? currentEvent : null;
|
|
2198
|
+
};
|
|
2199
|
+
return accessor;
|
|
2200
|
+
}
|
|
2201
|
+
function isFetchEvent(event) {
|
|
2202
|
+
return event.type === "fetch" && event.request instanceof Request;
|
|
2203
|
+
}
|
|
2204
|
+
function isQueueEvent(event) {
|
|
2205
|
+
return event.type === "queue" && "batch" in event;
|
|
2206
|
+
}
|
|
2207
|
+
function isScheduledEvent(event) {
|
|
2208
|
+
return event.type === "scheduled" && "controller" in event;
|
|
2209
|
+
}
|
|
2210
|
+
function isEmailEvent(event) {
|
|
2211
|
+
return event.type === "email" && "message" in event;
|
|
2212
|
+
}
|
|
2213
|
+
function isTailEvent(event) {
|
|
2214
|
+
return event.type === "tail" && Array.isArray(event) && "events" in event;
|
|
2215
|
+
}
|
|
2216
|
+
function isDurableObjectEvent(event) {
|
|
2217
|
+
return event.type.startsWith("durable-object-") && "state" in event;
|
|
2218
|
+
}
|
|
2219
|
+
function isDurableObjectFetchEvent(event) {
|
|
2220
|
+
return event.type === "durable-object-fetch" && event.request instanceof Request;
|
|
2221
|
+
}
|
|
2222
|
+
function isDurableObjectAlarmEvent(event) {
|
|
2223
|
+
return event.type === "durable-object-alarm" && "state" in event;
|
|
2224
|
+
}
|
|
2225
|
+
function isDurableObjectWebSocketMessageEvent(event) {
|
|
2226
|
+
return event.type === "durable-object-websocket-message" && "ws" in event && "message" in event;
|
|
2227
|
+
}
|
|
2228
|
+
function isDurableObjectWebSocketCloseEvent(event) {
|
|
2229
|
+
return event.type === "durable-object-websocket-close" && "ws" in event && "code" in event;
|
|
2230
|
+
}
|
|
2231
|
+
function isDurableObjectWebSocketErrorEvent(event) {
|
|
2232
|
+
return event.type === "durable-object-websocket-error" && "ws" in event && "error" in event;
|
|
2233
|
+
}
|
|
2234
|
+
var getFetchEvent = createEventAccessor("getFetchEvent()", isFetchEvent);
|
|
2235
|
+
var getQueueEvent = createEventAccessor("getQueueEvent()", isQueueEvent);
|
|
2236
|
+
var getScheduledEvent = createEventAccessor("getScheduledEvent()", isScheduledEvent);
|
|
2237
|
+
var getEmailEvent = createEventAccessor("getEmailEvent()", isEmailEvent);
|
|
2238
|
+
var getTailEvent = createEventAccessor("getTailEvent()", isTailEvent);
|
|
2239
|
+
var getDurableObjectEvent = createEventAccessor("getDurableObjectEvent()", isDurableObjectEvent);
|
|
2240
|
+
var getDurableObjectFetchEvent = createEventAccessor("getDurableObjectFetchEvent()", isDurableObjectFetchEvent);
|
|
2241
|
+
var getDurableObjectAlarmEvent = createEventAccessor("getDurableObjectAlarmEvent()", isDurableObjectAlarmEvent);
|
|
2242
|
+
var getDurableObjectWebSocketMessageEvent = createEventAccessor("getDurableObjectWebSocketMessageEvent()", isDurableObjectWebSocketMessageEvent);
|
|
2243
|
+
var getDurableObjectWebSocketCloseEvent = createEventAccessor("getDurableObjectWebSocketCloseEvent()", isDurableObjectWebSocketCloseEvent);
|
|
2244
|
+
var getDurableObjectWebSocketErrorEvent = createEventAccessor("getDurableObjectWebSocketErrorEvent()", isDurableObjectWebSocketErrorEvent);
|
|
2245
|
+
|
|
2246
|
+
// src/env.ts
|
|
2247
|
+
var testContextEnv = null;
|
|
2248
|
+
var testContextDispose = null;
|
|
2249
|
+
function __clearTestContext() {
|
|
2250
|
+
testContextEnv = null;
|
|
2251
|
+
testContextDispose = null;
|
|
2252
|
+
}
|
|
2253
|
+
var env = new Proxy({}, {
|
|
2254
|
+
get(_target, prop) {
|
|
2255
|
+
if (prop === "dispose") {
|
|
2256
|
+
return async () => {
|
|
2257
|
+
if (testContextDispose) {
|
|
2258
|
+
await testContextDispose();
|
|
2259
|
+
__clearTestContext();
|
|
2260
|
+
}
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2263
|
+
const ctx = getContextOrNull();
|
|
2264
|
+
if (ctx?.env) {
|
|
2265
|
+
return ctx.env[prop];
|
|
2266
|
+
}
|
|
2267
|
+
if (testContextEnv) {
|
|
2268
|
+
return testContextEnv[prop];
|
|
2269
|
+
}
|
|
2270
|
+
return bridgeEnv[prop];
|
|
2271
|
+
},
|
|
2272
|
+
has(_target, prop) {
|
|
2273
|
+
if (prop === "dispose")
|
|
2274
|
+
return true;
|
|
2275
|
+
const ctx = getContextOrNull();
|
|
2276
|
+
if (ctx?.env) {
|
|
2277
|
+
return prop in ctx.env;
|
|
2278
|
+
}
|
|
2279
|
+
if (testContextEnv) {
|
|
2280
|
+
return prop in testContextEnv;
|
|
2281
|
+
}
|
|
2282
|
+
return prop in bridgeEnv;
|
|
2283
|
+
},
|
|
2284
|
+
ownKeys(_target) {
|
|
2285
|
+
const ctx = getContextOrNull();
|
|
2286
|
+
if (ctx?.env) {
|
|
2287
|
+
return Reflect.ownKeys(ctx.env);
|
|
2288
|
+
}
|
|
2289
|
+
if (testContextEnv) {
|
|
2290
|
+
return Reflect.ownKeys(testContextEnv);
|
|
2291
|
+
}
|
|
2292
|
+
return Reflect.ownKeys(bridgeEnv);
|
|
2293
|
+
},
|
|
2294
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
2295
|
+
if (prop === "dispose") {
|
|
2296
|
+
return { configurable: true, enumerable: false, writable: false };
|
|
2297
|
+
}
|
|
2298
|
+
const ctx = getContextOrNull();
|
|
2299
|
+
const source = ctx?.env ?? testContextEnv ?? bridgeEnv;
|
|
2300
|
+
return Reflect.getOwnPropertyDescriptor(source, prop);
|
|
2301
|
+
}
|
|
2302
|
+
});
|
|
2303
|
+
var vars = new Proxy({}, {
|
|
2304
|
+
get(_target, prop) {
|
|
2305
|
+
const ctx = getContextOrNull();
|
|
2306
|
+
if (ctx?.env) {
|
|
2307
|
+
return ctx.env[prop];
|
|
2308
|
+
}
|
|
2309
|
+
if (testContextEnv) {
|
|
2310
|
+
return testContextEnv[prop];
|
|
2311
|
+
}
|
|
2312
|
+
return bridgeEnv[prop];
|
|
2313
|
+
},
|
|
2314
|
+
has(_target, prop) {
|
|
2315
|
+
const ctx = getContextOrNull();
|
|
2316
|
+
if (ctx?.env) {
|
|
2317
|
+
return prop in ctx.env;
|
|
2318
|
+
}
|
|
2319
|
+
if (testContextEnv) {
|
|
2320
|
+
return prop in testContextEnv;
|
|
2321
|
+
}
|
|
2322
|
+
return prop in bridgeEnv;
|
|
2323
|
+
},
|
|
2324
|
+
ownKeys(_target) {
|
|
2325
|
+
const ctx = getContextOrNull();
|
|
2326
|
+
if (ctx?.env) {
|
|
2327
|
+
return Reflect.ownKeys(ctx.env);
|
|
2328
|
+
}
|
|
2329
|
+
if (testContextEnv) {
|
|
2330
|
+
return Reflect.ownKeys(testContextEnv);
|
|
2331
|
+
}
|
|
2332
|
+
return Reflect.ownKeys(bridgeEnv);
|
|
2333
|
+
},
|
|
2334
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
2335
|
+
const ctx = getContextOrNull();
|
|
2336
|
+
const source = ctx?.env ?? testContextEnv ?? bridgeEnv;
|
|
2337
|
+
return Reflect.getOwnPropertyDescriptor(source, prop);
|
|
2338
|
+
},
|
|
2339
|
+
set(_target, prop) {
|
|
2340
|
+
throw new TypeError(`Cannot assign to '${String(prop)}' on 'vars' because it is read-only.`);
|
|
2341
|
+
},
|
|
2342
|
+
deleteProperty(_target, prop) {
|
|
2343
|
+
throw new TypeError(`Cannot delete property '${String(prop)}' from 'vars' because it is read-only.`);
|
|
2344
|
+
}
|
|
2345
|
+
});
|
|
2346
|
+
|
|
26
2347
|
// src/browser.ts
|
|
27
2348
|
function createUnsupportedApiError(name) {
|
|
28
2349
|
return new Error(`${name} is not available in browser environments; import from devflare/test or devflare/runtime in a worker/Node context instead.`);
|