@ekrooh/bare 0.1.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 ADDED
@@ -0,0 +1,512 @@
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 };
@@ -0,0 +1,125 @@
1
+ import { ErrorCode, definePlugin, err, ok } from "./index.mjs";
2
+ import { discoverySpecs } from "./plugins-discovery-events.mjs";
3
+ import { healthSpecs } from "./plugins-health-events.mjs";
4
+ import { mediaSpecs } from "./plugins-media-events.mjs";
5
+ import { permissionSpecs } from "./plugins-permissions-events.mjs";
6
+ //#region plugins/discovery/plugin.ts
7
+ function mergeCapabilityRows(a, b) {
8
+ const map = /* @__PURE__ */ new Map();
9
+ for (const row of [...a, ...b]) {
10
+ const existing = map.get(row.pluginId);
11
+ if (!existing) map.set(row.pluginId, {
12
+ pluginId: row.pluginId,
13
+ capabilities: [...row.capabilities],
14
+ events: [...row.events],
15
+ runtimes: [...row.runtimes]
16
+ });
17
+ else {
18
+ const ev = /* @__PURE__ */ new Set([...existing.events, ...row.events]);
19
+ const rt = /* @__PURE__ */ new Set([...existing.runtimes, ...row.runtimes]);
20
+ const caps = /* @__PURE__ */ new Set([...existing.capabilities, ...row.capabilities]);
21
+ map.set(row.pluginId, {
22
+ pluginId: row.pluginId,
23
+ capabilities: [...caps],
24
+ events: [...ev],
25
+ runtimes: [...rt]
26
+ });
27
+ }
28
+ }
29
+ return [...map.values()];
30
+ }
31
+ function createDiscoveryPlugin(deps) {
32
+ return definePlugin("core.discovery", discoverySpecs, {
33
+ capabilities: ["discovery"],
34
+ invoke: { list: async () => {
35
+ const merged = mergeCapabilityRows(deps.listBareCapabilities(), await deps.queryHostCapabilities());
36
+ return ok({
37
+ schemaVersion: 1,
38
+ capabilities: merged
39
+ });
40
+ } }
41
+ });
42
+ }
43
+ //#endregion
44
+ //#region plugins/health/plugin.ts
45
+ function createHealthPlugin() {
46
+ return definePlugin("core.health", healthSpecs, {
47
+ capabilities: ["health"],
48
+ invoke: {
49
+ ping: (args) => ok({
50
+ message: args?.message ?? "pong",
51
+ ts: Date.now()
52
+ }),
53
+ payloadEcho: (args, context) => ok({
54
+ label: args?.label ?? "payload",
55
+ payloadSize: context.payload.byteLength
56
+ }),
57
+ roundtrip: () => ok({
58
+ pong: true,
59
+ ts: Date.now()
60
+ })
61
+ }
62
+ });
63
+ }
64
+ //#endregion
65
+ //#region plugins/media/plugin.ts
66
+ function newRequestId() {
67
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
68
+ }
69
+ /**
70
+ * `vendor.media` — reference plugin demonstrating out-of-band binary transfer.
71
+ * The native host picks/captures a file (returns its path); the worklet mounts
72
+ * it on its loopback HTTP server and hands the web layer a plain URL. No media
73
+ * bytes ever cross the wire protocol or a WebView bridge, and the serving code
74
+ * is identical on every runtime.
75
+ */
76
+ function createMediaPlugin(deps) {
77
+ const serve = async (event, args) => {
78
+ const host = await deps.invokeOnHost({
79
+ type: "INVOKE_REQUEST",
80
+ pluginId: "vendor.media",
81
+ event,
82
+ requestId: newRequestId(),
83
+ args
84
+ }, /* @__PURE__ */ new Uint8Array(0));
85
+ if (!host) return err(ErrorCode.HOST_ERROR, `Host did not answer ${event}`);
86
+ if (host.error) return err(host.error.code || ErrorCode.HOST_ERROR, host.error.message ?? `Host failed ${event}`);
87
+ const path = host.result?.path;
88
+ if (!path) return err(ErrorCode.HOST_ERROR, `Host returned no path for ${event}`);
89
+ const id = `${args?.kind ?? "media"}-${newRequestId()}`;
90
+ deps.staticServer.mount(`/media/${id}`, path);
91
+ const url = await deps.staticServer.url(`/media/${id}`);
92
+ return ok({
93
+ url,
94
+ path
95
+ });
96
+ };
97
+ return definePlugin("vendor.media", mediaSpecs, {
98
+ capabilities: ["media"],
99
+ invoke: {
100
+ pick: async (args) => serve("media.pick", args),
101
+ capture: async (args) => serve("media.capture", args)
102
+ }
103
+ });
104
+ }
105
+ //#endregion
106
+ //#region plugins/permissions/plugin.ts
107
+ /**
108
+ * Declares host-only permission events. Handlers are registered on the Android host
109
+ * via {@code HostPluginRegistry}; the worklet delegates invokes here over IPC.
110
+ */
111
+ function createPermissionsPluginStub() {
112
+ return definePlugin("core.permissions", permissionSpecs, { capabilities: ["permissions"] });
113
+ }
114
+ //#endregion
115
+ //#region plugins/index.ts
116
+ function createDefaultPlugins(deps) {
117
+ return [
118
+ createHealthPlugin(),
119
+ createDiscoveryPlugin(deps),
120
+ createPermissionsPluginStub(),
121
+ createMediaPlugin(deps)
122
+ ];
123
+ }
124
+ //#endregion
125
+ export { createDefaultPlugins as t };
@@ -0,0 +1,21 @@
1
+ import { A as CapabilityDescriptor, B as InvokeEnvelope } from "./index-DjulYFJL.mjs";
2
+ //#region plugins/discovery/events.d.ts
3
+ type DiscoveryListResult = {
4
+ schemaVersion: 1;
5
+ capabilities: CapabilityDescriptor[];
6
+ };
7
+ declare const discoverySpecs: {
8
+ readonly list: {
9
+ readonly pluginId: 'core.discovery';
10
+ readonly name: 'discovery.list';
11
+ readonly args: Record<string, never>;
12
+ readonly result: DiscoveryListResult;
13
+ };
14
+ };
15
+ declare const discoveryEvents: {
16
+ discovery: {
17
+ list(): InvokeEnvelope<"discovery.list", Record<string, never>, DiscoveryListResult>;
18
+ };
19
+ };
20
+ //#endregion
21
+ export { DiscoveryListResult, discoveryEvents, discoverySpecs };
@@ -0,0 +1,13 @@
1
+ import { invokeEvent } from "./index.mjs";
2
+ //#region plugins/discovery/events.ts
3
+ const discoverySpecs = { list: {
4
+ pluginId: "core.discovery",
5
+ name: "discovery.list",
6
+ args: {},
7
+ result: {}
8
+ } };
9
+ const discoveryEvents = { discovery: { list() {
10
+ return invokeEvent(discoverySpecs.list, {});
11
+ } } };
12
+ //#endregion
13
+ export { discoveryEvents, discoverySpecs };