@ekrooh/bare 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,512 +1 @@
1
- import { TextDecoder as TextDecoder$1, TextEncoder as TextEncoder$1 } from "bare-encoding";
2
- //#region core/messages/constants.ts
3
- const VERSION = 1;
4
- const MessageType = { ENVELOPE: 1 };
5
- /** Canonical error codes. Everything the framework throws uses these; peers
6
- * may carry arbitrary codes over the wire, and consumers may return app-scoped
7
- * codes (e.g. `app.photos/not-found`) which are preserved verbatim. Keep
8
- * `android/src/main/.../ErrorCodes.kt` in sync with the canonical union. */
9
- const ErrorCode = {
10
- UNSUPPORTED_CAPABILITY: "UNSUPPORTED_CAPABILITY",
11
- UNSUPPORTED_EVENT: "UNSUPPORTED_EVENT",
12
- HOST_ERROR: "HOST_ERROR",
13
- TRANSPORT_ERROR: "TRANSPORT_ERROR",
14
- PLUGIN_ERROR: "PLUGIN_ERROR",
15
- INVALID_RESPONSE: "INVALID_RESPONSE",
16
- FRAME_TOO_LARGE: "FRAME_TOO_LARGE",
17
- FRAME_INVALID: "FRAME_INVALID",
18
- TIMEOUT: "TIMEOUT"
19
- };
20
- /** A JSON header must fit a 16-bit big-endian length field. */
21
- const MAX_HEADER_BYTES = 65535;
22
- /** Upper bound on one encoded frame. Large binary transfers are a plugin-level
23
- * concern (out-of-band handles), not an in-band payload. */
24
- const MAX_FRAME_BYTES = 16777216;
25
- //#endregion
26
- //#region core/messages/types.ts
27
- var CoreError = class extends Error {
28
- code;
29
- constructor(code, message) {
30
- super(message);
31
- this.name = "CoreError";
32
- this.code = code;
33
- }
34
- };
35
- //#endregion
36
- //#region core/messages/errors.ts
37
- /** Success half of an `Either` tuple. */
38
- function ok(result) {
39
- return [null, result];
40
- }
41
- /** Failure half of an `Either` tuple. Accepts a canonical error code or any
42
- * app-scoped code (`app.photos/not-found`) — the code is preserved verbatim. */
43
- function err(code, message) {
44
- return [new CoreError(code, message), null];
45
- }
46
- /** Convenience for constructing a `CoreError` from a possibly-non-canonical
47
- * wire code: preserves known codes, maps anything else to `PLUGIN_ERROR`.
48
- * Framework callers that want app codes preserved construct the error
49
- * directly instead. */
50
- function coerceErrorCode(code) {
51
- if (code && code in ErrorCode) return code;
52
- return ErrorCode.PLUGIN_ERROR;
53
- }
54
- //#endregion
55
- //#region core/messages/plugin-kernel.ts
56
- function createPluginRegistry() {
57
- const plugins = /* @__PURE__ */ new Map();
58
- return {
59
- register(plugin) {
60
- if (!plugin.id.includes(".")) throw new Error(`plugin id must be namespaced (example: "vendor.plugin"), got "${plugin.id}"`);
61
- if (plugins.has(plugin.id)) throw new Error(`plugin id "${plugin.id}" is already registered`);
62
- plugins.set(plugin.id, plugin);
63
- },
64
- resolve(pluginId, runtime) {
65
- return plugins.get(pluginId)?.runtimes[runtime];
66
- },
67
- get(pluginId) {
68
- return plugins.get(pluginId);
69
- },
70
- listCapabilities(runtime) {
71
- const rows = [];
72
- for (const plugin of plugins.values()) {
73
- const runtimes = Object.keys(plugin.runtimes);
74
- if (runtime && !runtimes.includes(runtime)) continue;
75
- rows.push({
76
- pluginId: plugin.id,
77
- capabilities: plugin.capabilities ?? [],
78
- events: plugin.events ?? [],
79
- runtimes
80
- });
81
- }
82
- return rows;
83
- }
84
- };
85
- }
86
- /** Whether the router may synthesize an UNSUPPORTED_EVENT response. Events are
87
- * only gated when the manifest declares them; undeclared plugins opt out of
88
- * validation for backward compatibility. */
89
- function declaresEvent(plugin, event) {
90
- return plugin?.events ? plugin.events.includes(event) : true;
91
- }
92
- function createPluginRouter(registry, runtime, options) {
93
- const logger = options?.logger ?? console;
94
- return { async route(header, payload) {
95
- const plugin = isPluginEnvelopeHeader(header) ? registry.get(header.pluginId) : void 0;
96
- if (isPluginEnvelopeHeader(header)) {
97
- if (!declaresEvent(plugin, header.event)) {
98
- const error = new CoreError(ErrorCode.UNSUPPORTED_EVENT, `Unsupported event ${header.pluginId}.${header.event} on ${runtime}`);
99
- if (isPluginDispatchHeader(header)) logger.warn(error.message);
100
- return invokeErrorResponse(header, error);
101
- }
102
- }
103
- if (isPluginDispatchHeader(header)) {
104
- const runtimeAdapter = registry.resolve(header.pluginId, runtime);
105
- if (!runtimeAdapter?.dispatch) {
106
- const error = new CoreError(ErrorCode.UNSUPPORTED_CAPABILITY, `Unsupported capability ${header.pluginId}.${header.event} on ${runtime}`);
107
- logger.warn(error.message);
108
- return invokeErrorResponse(header, error);
109
- }
110
- try {
111
- await runtimeAdapter.dispatch(header.event, header.args, {
112
- runtime,
113
- payload
114
- });
115
- return null;
116
- } catch (e) {
117
- const error = new CoreError(ErrorCode.PLUGIN_ERROR, e instanceof Error ? e.message : String(e));
118
- logger.error(`dispatch ${header.pluginId}.${header.event} failed: ${error.message}`);
119
- return invokeErrorResponse(header, error);
120
- }
121
- }
122
- if (isPluginInvokeRequestHeader(header)) {
123
- const runtimeAdapter = registry.resolve(header.pluginId, runtime);
124
- if (runtimeAdapter?.invoke) try {
125
- const [error, okResult] = await runtimeAdapter.invoke(header.event, header.args, {
126
- runtime,
127
- payload
128
- });
129
- return {
130
- type: "INVOKE_RESPONSE",
131
- pluginId: header.pluginId,
132
- event: header.event,
133
- requestId: header.requestId,
134
- result: okResult ?? void 0,
135
- error: error ? {
136
- code: error.code,
137
- message: error.message
138
- } : void 0
139
- };
140
- } catch (e) {
141
- const error = new CoreError(ErrorCode.PLUGIN_ERROR, e instanceof Error ? e.message : String(e));
142
- logger.error(`invoke ${header.pluginId}.${header.event} failed: ${error.message}`);
143
- return invokeErrorResponse(header, error);
144
- }
145
- if (header.requestId && options?.delegateToHost) try {
146
- const delegated = await options.delegateToHost(header, payload);
147
- if (delegated) return delegated;
148
- } catch (e) {
149
- return invokeErrorResponse(header, new CoreError(ErrorCode.HOST_ERROR, e instanceof Error ? e.message : String(e)));
150
- }
151
- return invokeErrorResponse(header, new CoreError(ErrorCode.UNSUPPORTED_CAPABILITY, `Unsupported capability ${header.pluginId}.${header.event} on ${runtime}`));
152
- }
153
- return null;
154
- } };
155
- }
156
- function createPluginBus(messenger) {
157
- return {
158
- dispatch(envelope) {
159
- return messenger.dispatch({
160
- type: "DISPATCH",
161
- pluginId: envelope.pluginId,
162
- event: envelope.event,
163
- args: envelope.args
164
- });
165
- },
166
- async invoke(envelope) {
167
- const response = await messenger.invoke({
168
- type: "INVOKE_REQUEST",
169
- pluginId: envelope.pluginId,
170
- event: envelope.event,
171
- args: envelope.args
172
- }, envelope.payload ?? null, envelope.timeoutMs);
173
- if (isPluginInvokeResponseHeader(response)) {
174
- if (response.error) return [new CoreError(response.error.code, String(response.error.message ?? "Plugin invoke failed")), null];
175
- return [null, response.result];
176
- }
177
- return [new CoreError(ErrorCode.INVALID_RESPONSE, `Unexpected response type ${response.type}`), null];
178
- }
179
- };
180
- }
181
- function invokeErrorResponse(header, error) {
182
- return {
183
- type: "INVOKE_RESPONSE",
184
- pluginId: header.pluginId,
185
- event: header.event,
186
- requestId: header.requestId,
187
- error: {
188
- code: error.code,
189
- message: error.message
190
- }
191
- };
192
- }
193
- function isPluginDispatchHeader(header) {
194
- return header.type === "DISPATCH" && typeof header.pluginId === "string" && typeof header.event === "string";
195
- }
196
- function isPluginEnvelopeHeader(header) {
197
- return isPluginDispatchHeader(header) || isPluginInvokeRequestHeader(header);
198
- }
199
- function isPluginInvokeRequestHeader(header) {
200
- return header.type === "INVOKE_REQUEST" && typeof header.pluginId === "string" && typeof header.event === "string";
201
- }
202
- function isPluginInvokeResponseHeader(header) {
203
- return header.type === "INVOKE_RESPONSE" && typeof header.pluginId === "string" && typeof header.event === "string";
204
- }
205
- //#endregion
206
- //#region core/messages/rpc-messenger.ts
207
- function createProtocolMessenger(send) {
208
- const pending = /* @__PURE__ */ new Map();
209
- return {
210
- dispatch(request, payload) {
211
- const requestWithId = withRequestId(request);
212
- send(requestWithId, payload);
213
- return requestWithId.requestId;
214
- },
215
- invoke(request, payload, timeoutMs = 5e3) {
216
- const requestWithId = withRequestId(request);
217
- const { requestId } = requestWithId;
218
- return new Promise((resolvePromise, rejectPromise) => {
219
- const timer = setTimeout(() => {
220
- pending.delete(requestId);
221
- rejectPromise(/* @__PURE__ */ new Error(`invoke timeout for ${request.type} (${requestId})`));
222
- }, timeoutMs);
223
- pending.set(requestId, {
224
- resolve: resolvePromise,
225
- reject: rejectPromise,
226
- timer
227
- });
228
- send(requestWithId, payload);
229
- });
230
- },
231
- handleIncoming(header) {
232
- if (!header.requestId) return;
233
- const pendingCall = pending.get(header.requestId);
234
- if (!pendingCall) return;
235
- clearTimeout(pendingCall.timer);
236
- pending.delete(header.requestId);
237
- pendingCall.resolve(header);
238
- }
239
- };
240
- }
241
- function createRequestId() {
242
- return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
243
- }
244
- function withRequestId(request) {
245
- return {
246
- ...request,
247
- requestId: createRequestId()
248
- };
249
- }
250
- //#endregion
251
- //#region core/messages/wire-codec.ts
252
- var MessageProtocol = class {
253
- encodeStr;
254
- decodeStr;
255
- allowUnknownTypes;
256
- maxFrameBytes;
257
- constructor(options) {
258
- this.encodeStr = options?.encode || ((str) => new TextEncoder().encode(str));
259
- this.decodeStr = options?.decode || ((bytes) => new TextDecoder().decode(bytes));
260
- this.allowUnknownTypes = options?.allowUnknownTypes ?? false;
261
- this.maxFrameBytes = options?.maxFrameBytes ?? 16777216;
262
- }
263
- encode(type, header, payload) {
264
- const headerJson = JSON.stringify(header);
265
- const headerBytes = this.encodeStr(headerJson);
266
- if (headerBytes.byteLength > 65535) throw new Error(`Header too large: ${headerBytes.byteLength} bytes, maximum is ${MAX_HEADER_BYTES}`);
267
- let payloadBytes;
268
- if (payload == null) payloadBytes = /* @__PURE__ */ new Uint8Array(0);
269
- else if (typeof payload === "string") payloadBytes = this.encodeStr(payload);
270
- else if (payload instanceof ArrayBuffer) payloadBytes = new Uint8Array(payload);
271
- else payloadBytes = payload;
272
- const hLen = headerBytes.byteLength;
273
- const pLen = payloadBytes.byteLength;
274
- const totalLength = 4 + hLen + pLen;
275
- if (totalLength > this.maxFrameBytes) throw new Error(`Frame too large: ${totalLength} bytes, maximum is ${this.maxFrameBytes}`);
276
- let buffer;
277
- if (typeof Buffer !== "undefined" && typeof Buffer.allocUnsafe === "function") buffer = Buffer.allocUnsafe(totalLength);
278
- else buffer = new Uint8Array(totalLength);
279
- buffer[0] = 1;
280
- buffer[1] = type;
281
- buffer[2] = hLen >> 8 & 255;
282
- buffer[3] = hLen & 255;
283
- buffer.set(headerBytes, 4);
284
- if (pLen > 0) buffer.set(payloadBytes, 4 + hLen);
285
- return buffer;
286
- }
287
- decode(data) {
288
- const view = data instanceof Uint8Array ? data : new Uint8Array(data);
289
- const byteLength = view.byteLength;
290
- if (byteLength < 4) throw new Error(`Message too short: ${byteLength} bytes`);
291
- if (byteLength > this.maxFrameBytes) throw new Error(`Frame too large: ${byteLength} bytes, maximum is ${this.maxFrameBytes}`);
292
- const version = view[0];
293
- if (version !== 1) throw new Error(`Unsupported version: ${version}. Expected 1`);
294
- const type = view[1];
295
- if (!this.allowUnknownTypes && !isKnownMessageType(type)) throw new Error(`Unsupported message type: ${type}`);
296
- const headerLen = view[2] << 8 | view[3];
297
- if (byteLength < 4 + headerLen) throw new Error(`Message too short for header: ${byteLength} bytes, expected at least ${4 + headerLen}`);
298
- return {
299
- type,
300
- header: parseAndValidateHeader(this.decodeStr(view.subarray(4, 4 + headerLen))),
301
- payload: view.subarray(4 + headerLen)
302
- };
303
- }
304
- };
305
- function isKnownMessageType(type) {
306
- return Object.values(MessageType).includes(type);
307
- }
308
- function parseAndValidateHeader(headerJson) {
309
- const parsed = JSON.parse(headerJson);
310
- if (!parsed || typeof parsed !== "object") throw new Error("Invalid header: expected object");
311
- const header = parsed;
312
- switch (header.type) {
313
- case "DISPATCH":
314
- if (isValidPluginHeaderFields(header)) return mergeHeader(header, {
315
- type: "DISPATCH",
316
- pluginId: header.pluginId,
317
- event: header.event,
318
- requestId: asOptionalString(header.requestId),
319
- args: asOptionalRecord(header.args)
320
- });
321
- break;
322
- case "INVOKE_REQUEST":
323
- if (isValidPluginHeaderFields(header)) return mergeHeader(header, {
324
- type: "INVOKE_REQUEST",
325
- pluginId: header.pluginId,
326
- event: header.event,
327
- requestId: asOptionalString(header.requestId),
328
- args: asOptionalRecord(header.args)
329
- });
330
- break;
331
- case "INVOKE_RESPONSE":
332
- if (isValidPluginHeaderFields(header)) return mergeHeader(header, {
333
- type: "INVOKE_RESPONSE",
334
- pluginId: header.pluginId,
335
- event: header.event,
336
- requestId: asOptionalString(header.requestId),
337
- result: header.result,
338
- error: parseCoreErrorWire(header.error)
339
- });
340
- break;
341
- case "HOST_CAPABILITIES_QUERY":
342
- if (typeof header.requestId === "string") return mergeHeader(header, {
343
- type: "HOST_CAPABILITIES_QUERY",
344
- requestId: header.requestId
345
- });
346
- break;
347
- case "HOST_CAPABILITIES_RESPONSE":
348
- if (typeof header.requestId === "string") return mergeHeader(header, {
349
- type: "HOST_CAPABILITIES_RESPONSE",
350
- requestId: header.requestId,
351
- capabilities: parseCapabilityDescriptors(header.capabilities)
352
- });
353
- break;
354
- case "HOST_INVOKE_REQUEST":
355
- if (typeof header.requestId === "string" && typeof header.pluginId === "string" && typeof header.event === "string") return mergeHeader(header, {
356
- type: "HOST_INVOKE_REQUEST",
357
- requestId: header.requestId,
358
- pluginId: header.pluginId,
359
- event: header.event,
360
- args: asOptionalRecord(header.args)
361
- });
362
- break;
363
- case "HOST_INVOKE_RESPONSE": if (typeof header.requestId === "string" && typeof header.pluginId === "string" && typeof header.event === "string") return mergeHeader(header, {
364
- type: "HOST_INVOKE_RESPONSE",
365
- requestId: header.requestId,
366
- pluginId: header.pluginId,
367
- event: header.event,
368
- result: header.result,
369
- error: parseCoreErrorWire(header.error)
370
- });
371
- }
372
- throw new Error(`Unsupported header type: ${String(header.type)}`);
373
- }
374
- /** Validated known fields win, unknown fields pass through untouched so
375
- * forward-compatible peers can relay envelopes without losing data. */
376
- function mergeHeader(parsed, known) {
377
- return {
378
- ...parsed,
379
- ...known
380
- };
381
- }
382
- function isValidPluginHeaderFields(header) {
383
- return typeof header.pluginId === "string" && typeof header.event === "string";
384
- }
385
- function asOptionalString(value) {
386
- return typeof value === "string" ? value : void 0;
387
- }
388
- function asOptionalRecord(value) {
389
- return value && typeof value === "object" ? value : void 0;
390
- }
391
- function parseCoreErrorWire(value) {
392
- if (!value || typeof value !== "object") return void 0;
393
- const candidate = value;
394
- if (typeof candidate.code !== "string" || typeof candidate.message !== "string") return void 0;
395
- return {
396
- code: candidate.code,
397
- message: candidate.message
398
- };
399
- }
400
- const RUNTIME_TARGETS = /* @__PURE__ */ new Set([
401
- "web",
402
- "android",
403
- "ios",
404
- "bare"
405
- ]);
406
- function parseRuntimeTarget(value) {
407
- if (typeof value !== "string") return null;
408
- return RUNTIME_TARGETS.has(value) ? value : null;
409
- }
410
- function parseCapabilityDescriptors(value) {
411
- if (!Array.isArray(value)) return [];
412
- const out = [];
413
- for (const row of value) {
414
- if (!row || typeof row !== "object") continue;
415
- const r = row;
416
- if (typeof r.pluginId !== "string") continue;
417
- const events = Array.isArray(r.events) ? r.events.filter((e) => typeof e === "string") : [];
418
- const caps = Array.isArray(r.capabilities) ? r.capabilities.filter((c) => typeof c === "string") : [];
419
- const runtimes = [];
420
- if (Array.isArray(r.runtimes)) for (const rt of r.runtimes) {
421
- const parsed = parseRuntimeTarget(rt);
422
- if (parsed) runtimes.push(parsed);
423
- }
424
- out.push({
425
- pluginId: r.pluginId,
426
- capabilities: caps,
427
- events,
428
- runtimes
429
- });
430
- }
431
- return out;
432
- }
433
- //#endregion
434
- //#region core/messages/define-plugin.ts
435
- /**
436
- * Builds a {@link PluginManifest} from an event spec table. The `events` list
437
- * is derived from the specs and handlers are dispatched by event name — no
438
- * hand-written if-chains. Handlers receive typed args. Plugins with no local
439
- * handlers (host-delegated, e.g. `core.permissions`) pass no `invoke`/`dispatch`.
440
- */
441
- function definePlugin(id, specs, options = {}) {
442
- const invokeByEvent = /* @__PURE__ */ new Map();
443
- const dispatchByEvent = /* @__PURE__ */ new Map();
444
- const events = [];
445
- for (const key of Object.keys(specs)) {
446
- const spec = specs[key];
447
- if (spec.pluginId !== id) throw new Error(`event spec ${spec.name} belongs to ${spec.pluginId}, not ${id}`);
448
- events.push(spec.name);
449
- const invoker = options.invoke?.[key];
450
- if (invoker) invokeByEvent.set(spec.name, invoker);
451
- const dispatcher = options.dispatch?.[key];
452
- if (dispatcher) dispatchByEvent.set(spec.name, dispatcher);
453
- }
454
- const adapter = {};
455
- if (invokeByEvent.size > 0) adapter.invoke = (event, args, context) => {
456
- const handler = invokeByEvent.get(event);
457
- if (!handler) throw new Error(`Unhandled invoke event ${event}`);
458
- return handler(args, context);
459
- };
460
- if (dispatchByEvent.size > 0) adapter.dispatch = (event, args, context) => {
461
- const handler = dispatchByEvent.get(event);
462
- if (!handler) throw new Error(`Unhandled dispatch event ${event}`);
463
- return handler(args, context);
464
- };
465
- const runtimes = {};
466
- if (invokeByEvent.size > 0 || dispatchByEvent.size > 0) runtimes.bare = adapter;
467
- return {
468
- id,
469
- capabilities: options.capabilities,
470
- events,
471
- runtimes
472
- };
473
- }
474
- /** Typed builder: an invoke envelope for a spec's event. */
475
- function invokeEvent(spec, args, payload, timeoutMs) {
476
- return {
477
- kind: "invoke",
478
- pluginId: spec.pluginId,
479
- event: spec.name,
480
- args,
481
- payload,
482
- timeoutMs
483
- };
484
- }
485
- /** Typed builder: a dispatch envelope for a spec's event. */
486
- function dispatchEvent(spec, args, payload) {
487
- return {
488
- kind: "dispatch",
489
- pluginId: spec.pluginId,
490
- event: spec.name,
491
- args,
492
- payload
493
- };
494
- }
495
- //#endregion
496
- //#region core/messages/create-bare-runtime-context.ts
497
- function createBareRuntimeContext(plugins = [], routerOptions) {
498
- const runtime = "bare";
499
- const protocol = new MessageProtocol({
500
- encode: (str) => new TextEncoder$1().encode(str),
501
- decode: (bytes) => new TextDecoder$1().decode(bytes)
502
- });
503
- const pluginRegistry = createPluginRegistry();
504
- for (const plugin of plugins) pluginRegistry.register(plugin);
505
- return {
506
- protocol,
507
- pluginRegistry,
508
- pluginRouter: createPluginRouter(pluginRegistry, runtime, routerOptions)
509
- };
510
- }
511
- //#endregion
512
- export { CoreError, ErrorCode, MAX_FRAME_BYTES, MAX_HEADER_BYTES, MessageProtocol, MessageType, VERSION, coerceErrorCode, createBareRuntimeContext, createPluginBus, createPluginRegistry, createPluginRouter, createProtocolMessenger, definePlugin, dispatchEvent, err, invokeEvent, ok };
1
+ export { h as CoreError, g as ErrorCode, _ as MAX_FRAME_BYTES, v as MAX_HEADER_BYTES, o as MessageProtocol, y as MessageType, b as VERSION, f as coerceErrorCode, n as createBareRuntimeContext, l as createPluginBus, u as createPluginRegistry, d as createPluginRouter, s as createProtocolMessenger, r as definePlugin, i as dispatchEvent, p as err, t as installWebConsoleCapture, a as invokeEvent, m as ok } from "./messages-BbpF3wNH.mjs";