@mirrorstack-ai/app-module-client 0.5.1

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.
Files changed (70) hide show
  1. package/CHANGELOG.md +152 -0
  2. package/LICENSE +202 -0
  3. package/README.md +488 -0
  4. package/dist/base-url.d.ts +39 -0
  5. package/dist/base-url.js +51 -0
  6. package/dist/base-url.js.map +1 -0
  7. package/dist/client.d.ts +56 -0
  8. package/dist/client.js +85 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/error.d.ts +29 -0
  11. package/dist/error.js +100 -0
  12. package/dist/error.js.map +1 -0
  13. package/dist/index.d.ts +6 -0
  14. package/dist/index.js +6 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/next/auth-routes.d.ts +82 -0
  17. package/dist/next/auth-routes.js +164 -0
  18. package/dist/next/auth-routes.js.map +1 -0
  19. package/dist/next/index.d.ts +12 -0
  20. package/dist/next/index.js +13 -0
  21. package/dist/next/index.js.map +1 -0
  22. package/dist/next/module-proxy-routes.d.ts +81 -0
  23. package/dist/next/module-proxy-routes.js +77 -0
  24. package/dist/next/module-proxy-routes.js.map +1 -0
  25. package/dist/plugin.d.ts +39 -0
  26. package/dist/plugin.js +39 -0
  27. package/dist/plugin.js.map +1 -0
  28. package/dist/response.d.ts +13 -0
  29. package/dist/response.js +80 -0
  30. package/dist/response.js.map +1 -0
  31. package/dist/server/index.d.ts +7 -0
  32. package/dist/server/index.js +8 -0
  33. package/dist/server/index.js.map +1 -0
  34. package/dist/server/member-sessions.d.ts +80 -0
  35. package/dist/server/member-sessions.js +162 -0
  36. package/dist/server/member-sessions.js.map +1 -0
  37. package/dist/transport.d.ts +98 -0
  38. package/dist/transport.js +299 -0
  39. package/dist/transport.js.map +1 -0
  40. package/dist/web/cache.d.ts +33 -0
  41. package/dist/web/cache.js +130 -0
  42. package/dist/web/cache.js.map +1 -0
  43. package/dist/web/component-mount.d.ts +23 -0
  44. package/dist/web/component-mount.js +111 -0
  45. package/dist/web/component-mount.js.map +1 -0
  46. package/dist/web/index.d.ts +6 -0
  47. package/dist/web/index.js +7 -0
  48. package/dist/web/index.js.map +1 -0
  49. package/dist/web/localized-text.d.ts +10 -0
  50. package/dist/web/localized-text.js +36 -0
  51. package/dist/web/localized-text.js.map +1 -0
  52. package/dist/web/react.d.ts +18 -0
  53. package/dist/web/react.js +111 -0
  54. package/dist/web/react.js.map +1 -0
  55. package/dist/web/runtime.d.ts +60 -0
  56. package/dist/web/runtime.js +73 -0
  57. package/dist/web/runtime.js.map +1 -0
  58. package/dist/web/subpath.d.ts +22 -0
  59. package/dist/web/subpath.js +50 -0
  60. package/dist/web/subpath.js.map +1 -0
  61. package/dist/web/types.d.ts +129 -0
  62. package/dist/web/types.js +2 -0
  63. package/dist/web/types.js.map +1 -0
  64. package/dist/web/use-now.d.ts +7 -0
  65. package/dist/web/use-now.js +22 -0
  66. package/dist/web/use-now.js.map +1 -0
  67. package/dist/web/use-platform-unsaved-state.d.ts +10 -0
  68. package/dist/web/use-platform-unsaved-state.js +35 -0
  69. package/dist/web/use-platform-unsaved-state.js.map +1 -0
  70. package/package.json +88 -0
@@ -0,0 +1,130 @@
1
+ const DEFAULT_MAX_AGE_MS = 30_000;
2
+ const DEFAULT_MAX_ENTRIES = 64;
3
+ /** Returns true when an error represents an aborted browser request. */
4
+ export function isAbortError(error) {
5
+ return error instanceof DOMException && error.name === "AbortError";
6
+ }
7
+ /**
8
+ * Creates an isolated text cache for one module mount.
9
+ *
10
+ * The cache stores response text so each consumer can parse its own object. It
11
+ * never uses browser storage and shares no state with another cache instance.
12
+ */
13
+ export function createModuleTextCache(options = {}) {
14
+ const maxAgeMs = nonNegative(options.maxAgeMs ?? DEFAULT_MAX_AGE_MS, "maxAgeMs");
15
+ const maxEntries = positiveInteger(options.maxEntries ?? DEFAULT_MAX_ENTRIES, "maxEntries");
16
+ const now = options.now ?? Date.now;
17
+ const entries = new Map();
18
+ const inflight = new Map();
19
+ function remember(key, text) {
20
+ entries.delete(key);
21
+ entries.set(key, { text, storedAt: now() });
22
+ while (entries.size > maxEntries) {
23
+ const oldest = entries.keys().next();
24
+ if (oldest.done)
25
+ return;
26
+ entries.delete(oldest.value);
27
+ }
28
+ }
29
+ function attach(key, shared, signal) {
30
+ if (signal?.aborted === true)
31
+ return Promise.reject(abortError());
32
+ shared.waiters += 1;
33
+ if (signal === undefined) {
34
+ return shared.promise.finally(() => {
35
+ shared.waiters -= 1;
36
+ });
37
+ }
38
+ let released = false;
39
+ const release = () => {
40
+ if (released)
41
+ return;
42
+ released = true;
43
+ shared.waiters -= 1;
44
+ if (shared.waiters === 0 && inflight.get(key) === shared) {
45
+ inflight.delete(key);
46
+ shared.controller.abort();
47
+ }
48
+ };
49
+ return new Promise((resolve, reject) => {
50
+ const onAbort = () => {
51
+ release();
52
+ reject(abortError());
53
+ };
54
+ signal.addEventListener("abort", onAbort, { once: true });
55
+ shared.promise.then((text) => {
56
+ signal.removeEventListener("abort", onAbort);
57
+ release();
58
+ resolve(text);
59
+ }, (error) => {
60
+ signal.removeEventListener("abort", onAbort);
61
+ release();
62
+ reject(error);
63
+ });
64
+ });
65
+ }
66
+ async function cachedText(key, fetchText, readOptions = {}) {
67
+ if (readOptions.signal?.aborted === true)
68
+ return Promise.reject(abortError());
69
+ const requestedMaxAge = nonNegative(readOptions.maxAgeMs ?? maxAgeMs, "options.maxAgeMs");
70
+ const hit = entries.get(key);
71
+ if (hit !== undefined && requestedMaxAge > 0 && now() - hit.storedAt < requestedMaxAge) {
72
+ return hit.text;
73
+ }
74
+ const live = inflight.get(key);
75
+ if (live !== undefined)
76
+ return attach(key, live, readOptions.signal);
77
+ const controller = new AbortController();
78
+ const shared = {
79
+ controller,
80
+ promise: Promise.resolve(""),
81
+ waiters: 0,
82
+ };
83
+ const request = fetchText(controller.signal).then((text) => {
84
+ if (inflight.get(key) === shared)
85
+ inflight.delete(key);
86
+ remember(key, text);
87
+ return text;
88
+ }, (error) => {
89
+ if (inflight.get(key) === shared)
90
+ inflight.delete(key);
91
+ throw error;
92
+ });
93
+ Object.assign(shared, { promise: request });
94
+ inflight.set(key, shared);
95
+ return attach(key, shared, readOptions.signal);
96
+ }
97
+ function invalidate(prefix) {
98
+ if (prefix === undefined) {
99
+ entries.clear();
100
+ return;
101
+ }
102
+ for (const key of entries.keys()) {
103
+ if (key.startsWith(prefix))
104
+ entries.delete(key);
105
+ }
106
+ }
107
+ function clear() {
108
+ entries.clear();
109
+ for (const pending of inflight.values())
110
+ pending.controller.abort();
111
+ inflight.clear();
112
+ }
113
+ return Object.freeze({ cachedText, invalidate, clear });
114
+ }
115
+ function abortError() {
116
+ return new DOMException("The operation was aborted.", "AbortError");
117
+ }
118
+ function nonNegative(value, name) {
119
+ if (!Number.isFinite(value) || value < 0) {
120
+ throw new TypeError(`${name} must be a finite non-negative number`);
121
+ }
122
+ return value;
123
+ }
124
+ function positiveInteger(value, name) {
125
+ if (!Number.isSafeInteger(value) || value <= 0) {
126
+ throw new TypeError(`${name} must be a positive integer`);
127
+ }
128
+ return value;
129
+ }
130
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/web/cache.ts"],"names":[],"mappings":"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","sourcesContent":["const DEFAULT_MAX_AGE_MS = 30_000;\nconst DEFAULT_MAX_ENTRIES = 64;\n\ninterface Entry {\n readonly text: string;\n readonly storedAt: number;\n}\n\ninterface Inflight {\n readonly promise: Promise<string>;\n readonly controller: AbortController;\n waiters: number;\n}\n\n/** Configuration for one mount-scoped text cache. */\nexport interface ModuleTextCacheOptions {\n /** Default freshness window for cached responses. */\n readonly maxAgeMs?: number;\n /** Maximum number of completed responses retained by this instance. */\n readonly maxEntries?: number;\n /** Clock override for deterministic tests. */\n readonly now?: () => number;\n}\n\n/** Options for one cached read. */\nexport interface CachedTextOptions {\n readonly signal?: AbortSignal;\n /** Overrides the instance freshness window; zero forces a network read. */\n readonly maxAgeMs?: number;\n}\n\n/** A request cache owned and disposed by one mounted module instance. */\nexport interface ModuleTextCache {\n /** Returns a fresh cached response or coalesces an equivalent live request. */\n cachedText(\n key: string,\n fetchText: (signal: AbortSignal) => Promise<string>,\n options?: CachedTextOptions,\n ): Promise<string>;\n\n /** Removes completed entries, optionally restricted to a key prefix. */\n invalidate(prefix?: string): void;\n\n /** Removes completed entries and aborts every live request owned by this cache. */\n clear(): void;\n}\n\n/** Returns true when an error represents an aborted browser request. */\nexport function isAbortError(error: unknown): boolean {\n return error instanceof DOMException && error.name === \"AbortError\";\n}\n\n/**\n * Creates an isolated text cache for one module mount.\n *\n * The cache stores response text so each consumer can parse its own object. It\n * never uses browser storage and shares no state with another cache instance.\n */\nexport function createModuleTextCache(\n options: ModuleTextCacheOptions = {},\n): ModuleTextCache {\n const maxAgeMs = nonNegative(options.maxAgeMs ?? DEFAULT_MAX_AGE_MS, \"maxAgeMs\");\n const maxEntries = positiveInteger(options.maxEntries ?? DEFAULT_MAX_ENTRIES, \"maxEntries\");\n const now = options.now ?? Date.now;\n const entries = new Map<string, Entry>();\n const inflight = new Map<string, Inflight>();\n\n function remember(key: string, text: string): void {\n entries.delete(key);\n entries.set(key, { text, storedAt: now() });\n while (entries.size > maxEntries) {\n const oldest = entries.keys().next();\n if (oldest.done) return;\n entries.delete(oldest.value);\n }\n }\n\n function attach(key: string, shared: Inflight, signal?: AbortSignal): Promise<string> {\n if (signal?.aborted === true) return Promise.reject(abortError());\n\n shared.waiters += 1;\n if (signal === undefined) {\n return shared.promise.finally(() => {\n shared.waiters -= 1;\n });\n }\n\n let released = false;\n const release = () => {\n if (released) return;\n released = true;\n shared.waiters -= 1;\n if (shared.waiters === 0 && inflight.get(key) === shared) {\n inflight.delete(key);\n shared.controller.abort();\n }\n };\n\n return new Promise<string>((resolve, reject) => {\n const onAbort = () => {\n release();\n reject(abortError());\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n shared.promise.then(\n (text) => {\n signal.removeEventListener(\"abort\", onAbort);\n release();\n resolve(text);\n },\n (error: unknown) => {\n signal.removeEventListener(\"abort\", onAbort);\n release();\n reject(error);\n },\n );\n });\n }\n\n async function cachedText(\n key: string,\n fetchText: (signal: AbortSignal) => Promise<string>,\n readOptions: CachedTextOptions = {},\n ): Promise<string> {\n if (readOptions.signal?.aborted === true) return Promise.reject(abortError());\n const requestedMaxAge = nonNegative(\n readOptions.maxAgeMs ?? maxAgeMs,\n \"options.maxAgeMs\",\n );\n const hit = entries.get(key);\n if (hit !== undefined && requestedMaxAge > 0 && now() - hit.storedAt < requestedMaxAge) {\n return hit.text;\n }\n\n const live = inflight.get(key);\n if (live !== undefined) return attach(key, live, readOptions.signal);\n\n const controller = new AbortController();\n const shared = {\n controller,\n promise: Promise.resolve(\"\"),\n waiters: 0,\n } satisfies Inflight;\n const request = fetchText(controller.signal).then(\n (text) => {\n if (inflight.get(key) === shared) inflight.delete(key);\n remember(key, text);\n return text;\n },\n (error: unknown) => {\n if (inflight.get(key) === shared) inflight.delete(key);\n throw error;\n },\n );\n Object.assign(shared, { promise: request });\n inflight.set(key, shared);\n return attach(key, shared, readOptions.signal);\n }\n\n function invalidate(prefix?: string): void {\n if (prefix === undefined) {\n entries.clear();\n return;\n }\n for (const key of entries.keys()) {\n if (key.startsWith(prefix)) entries.delete(key);\n }\n }\n\n function clear(): void {\n entries.clear();\n for (const pending of inflight.values()) pending.controller.abort();\n inflight.clear();\n }\n\n return Object.freeze({ cachedText, invalidate, clear });\n}\n\nfunction abortError(): DOMException {\n return new DOMException(\"The operation was aborted.\", \"AbortError\");\n}\n\nfunction nonNegative(value: number, name: string): number {\n if (!Number.isFinite(value) || value < 0) {\n throw new TypeError(`${name} must be a finite non-negative number`);\n }\n return value;\n}\n\nfunction positiveInteger(value: number, name: string): number {\n if (!Number.isSafeInteger(value) || value <= 0) {\n throw new TypeError(`${name} must be a positive integer`);\n }\n return value;\n}\n"]}
@@ -0,0 +1,23 @@
1
+ /** Lifecycle state of one component contributed by another installed module. */
2
+ export type ModuleComponentMountStatus = "mounting" | "ready" | "unavailable" | "disposed";
3
+ /** Mount operation invoked with a child owned by the target's document. */
4
+ export type ModuleComponentMountOperation = (target: HTMLElement) => (() => void) | PromiseLike<() => void>;
5
+ /** Observable lifecycle for one cross-module component mount. */
6
+ export interface ModuleComponentMount {
7
+ /** Returns the stable current lifecycle state. */
8
+ getSnapshot(): ModuleComponentMountStatus;
9
+ /** Returns the mount failure retained for diagnostics, when unavailable. */
10
+ getError(): unknown;
11
+ /** Subscribes to lifecycle state changes. */
12
+ subscribe(listener: () => void): () => void;
13
+ /** Releases the contributed component and removes its document-owned child. */
14
+ dispose(): void;
15
+ }
16
+ /**
17
+ * Mounts a contributed component into an isolated child of the supplied target.
18
+ *
19
+ * The returned observable reports asynchronous readiness without coupling the
20
+ * lifecycle to React. A late successful mount is immediately disposed when its
21
+ * owner has already gone away.
22
+ */
23
+ export declare function mountModuleComponent(target: HTMLElement, mount: ModuleComponentMountOperation): ModuleComponentMount;
@@ -0,0 +1,111 @@
1
+ function promiseLike(value) {
2
+ return (value !== null
3
+ && (typeof value === "object" || typeof value === "function")
4
+ && typeof value.then === "function");
5
+ }
6
+ /**
7
+ * Mounts a contributed component into an isolated child of the supplied target.
8
+ *
9
+ * The returned observable reports asynchronous readiness without coupling the
10
+ * lifecycle to React. A late successful mount is immediately disposed when its
11
+ * owner has already gone away.
12
+ */
13
+ export function mountModuleComponent(target, mount) {
14
+ const child = target.ownerDocument.createElement("div");
15
+ target.replaceChildren(child);
16
+ let status = "mounting";
17
+ let error;
18
+ let cleanup;
19
+ const listeners = new Set();
20
+ const publish = (next) => {
21
+ if (status === next)
22
+ return;
23
+ status = next;
24
+ for (const listener of listeners) {
25
+ try {
26
+ listener();
27
+ }
28
+ catch {
29
+ // Observer failures must not interrupt mount cleanup or state changes.
30
+ }
31
+ }
32
+ };
33
+ const unavailable = (cause) => {
34
+ if (status === "disposed") {
35
+ child.remove();
36
+ return;
37
+ }
38
+ error = cause;
39
+ child.remove();
40
+ publish("unavailable");
41
+ };
42
+ const mounted = (dispose) => {
43
+ if (typeof dispose !== "function") {
44
+ unavailable(new TypeError("module component mount must return a cleanup function"));
45
+ return;
46
+ }
47
+ if (status === "disposed") {
48
+ try {
49
+ dispose();
50
+ }
51
+ catch (cause) {
52
+ error = cause;
53
+ }
54
+ finally {
55
+ child.remove();
56
+ }
57
+ return;
58
+ }
59
+ cleanup = dispose;
60
+ publish("ready");
61
+ };
62
+ const controller = Object.freeze({
63
+ getSnapshot: () => status,
64
+ getError: () => error,
65
+ subscribe(listener) {
66
+ if (status === "disposed")
67
+ return () => { };
68
+ listeners.add(listener);
69
+ return () => listeners.delete(listener);
70
+ },
71
+ dispose() {
72
+ if (status === "disposed")
73
+ return;
74
+ publish("disposed");
75
+ listeners.clear();
76
+ const dispose = cleanup;
77
+ cleanup = undefined;
78
+ try {
79
+ dispose?.();
80
+ }
81
+ catch (cause) {
82
+ error = cause;
83
+ throw cause;
84
+ }
85
+ finally {
86
+ child.remove();
87
+ }
88
+ },
89
+ });
90
+ let result;
91
+ try {
92
+ result = mount(child);
93
+ }
94
+ catch (cause) {
95
+ unavailable(cause);
96
+ return controller;
97
+ }
98
+ try {
99
+ if (promiseLike(result)) {
100
+ void Promise.resolve(result).then(mounted, unavailable);
101
+ }
102
+ else {
103
+ mounted(result);
104
+ }
105
+ }
106
+ catch (cause) {
107
+ unavailable(cause);
108
+ }
109
+ return controller;
110
+ }
111
+ //# sourceMappingURL=component-mount.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-mount.js","sourceRoot":"","sources":["../../src/web/component-mount.ts"],"names":[],"mappings":"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","sourcesContent":["/** Lifecycle state of one component contributed by another installed module. */\nexport type ModuleComponentMountStatus =\n | \"mounting\"\n | \"ready\"\n | \"unavailable\"\n | \"disposed\";\n\n/** Mount operation invoked with a child owned by the target's document. */\nexport type ModuleComponentMountOperation = (\n target: HTMLElement,\n) => (() => void) | PromiseLike<() => void>;\n\n/** Observable lifecycle for one cross-module component mount. */\nexport interface ModuleComponentMount {\n /** Returns the stable current lifecycle state. */\n getSnapshot(): ModuleComponentMountStatus;\n /** Returns the mount failure retained for diagnostics, when unavailable. */\n getError(): unknown;\n /** Subscribes to lifecycle state changes. */\n subscribe(listener: () => void): () => void;\n /** Releases the contributed component and removes its document-owned child. */\n dispose(): void;\n}\n\nfunction promiseLike(value: unknown): value is PromiseLike<unknown> {\n return (\n value !== null\n && (typeof value === \"object\" || typeof value === \"function\")\n && typeof (value as PromiseLike<unknown>).then === \"function\"\n );\n}\n\n/**\n * Mounts a contributed component into an isolated child of the supplied target.\n *\n * The returned observable reports asynchronous readiness without coupling the\n * lifecycle to React. A late successful mount is immediately disposed when its\n * owner has already gone away.\n */\nexport function mountModuleComponent(\n target: HTMLElement,\n mount: ModuleComponentMountOperation,\n): ModuleComponentMount {\n const child = target.ownerDocument.createElement(\"div\");\n target.replaceChildren(child);\n\n let status: ModuleComponentMountStatus = \"mounting\";\n let error: unknown;\n let cleanup: (() => void) | undefined;\n const listeners = new Set<() => void>();\n\n const publish = (next: ModuleComponentMountStatus) => {\n if (status === next) return;\n status = next;\n for (const listener of listeners) {\n try {\n listener();\n } catch {\n // Observer failures must not interrupt mount cleanup or state changes.\n }\n }\n };\n const unavailable = (cause: unknown) => {\n if (status === \"disposed\") {\n child.remove();\n return;\n }\n error = cause;\n child.remove();\n publish(\"unavailable\");\n };\n const mounted = (dispose: unknown) => {\n if (typeof dispose !== \"function\") {\n unavailable(new TypeError(\"module component mount must return a cleanup function\"));\n return;\n }\n if (status === \"disposed\") {\n try {\n dispose();\n } catch (cause) {\n error = cause;\n } finally {\n child.remove();\n }\n return;\n }\n cleanup = dispose as () => void;\n publish(\"ready\");\n };\n\n const controller: ModuleComponentMount = Object.freeze({\n getSnapshot: () => status,\n getError: () => error,\n subscribe(listener: () => void) {\n if (status === \"disposed\") return () => {};\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n dispose() {\n if (status === \"disposed\") return;\n publish(\"disposed\");\n listeners.clear();\n const dispose = cleanup;\n cleanup = undefined;\n try {\n dispose?.();\n } catch (cause) {\n error = cause;\n throw cause;\n } finally {\n child.remove();\n }\n },\n });\n\n let result: ReturnType<ModuleComponentMountOperation>;\n try {\n result = mount(child);\n } catch (cause) {\n unavailable(cause);\n return controller;\n }\n\n try {\n if (promiseLike(result)) {\n void Promise.resolve(result).then(mounted, unavailable);\n } else {\n mounted(result);\n }\n } catch (cause) {\n unavailable(cause);\n }\n return controller;\n}\n"]}
@@ -0,0 +1,6 @@
1
+ export * from "./cache.js";
2
+ export * from "./component-mount.js";
3
+ export * from "./localized-text.js";
4
+ export * from "./runtime.js";
5
+ export * from "./subpath.js";
6
+ export * from "./types.js";
@@ -0,0 +1,7 @@
1
+ export * from "./cache.js";
2
+ export * from "./component-mount.js";
3
+ export * from "./localized-text.js";
4
+ export * from "./runtime.js";
5
+ export * from "./subpath.js";
6
+ export * from "./types.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/web/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC","sourcesContent":["export * from \"./cache.js\";\nexport * from \"./component-mount.js\";\nexport * from \"./localized-text.js\";\nexport * from \"./runtime.js\";\nexport * from \"./subpath.js\";\nexport * from \"./types.js\";\n"]}
@@ -0,0 +1,10 @@
1
+ /** Text authored either as a literal or as locale-keyed translations. */
2
+ export type LocalizedText = string | Readonly<Record<string, string>>;
3
+ /**
4
+ * Resolves authored text without modifying the selected translation.
5
+ *
6
+ * Selection prefers an exact locale (case-insensitively), then another entry
7
+ * with the same primary language, then the exact fallback locale, and finally
8
+ * the first non-blank translation.
9
+ */
10
+ export declare function resolveLocalizedText(value: LocalizedText | null | undefined, locale?: string, fallbackLocale?: string): string;
@@ -0,0 +1,36 @@
1
+ function available(value) {
2
+ return typeof value === "string" && value.trim() !== "";
3
+ }
4
+ function exact(entries, locale) {
5
+ if (locale === undefined || locale === "")
6
+ return undefined;
7
+ const normalized = locale.toLowerCase();
8
+ return entries.find(([key, text]) => key.toLowerCase() === normalized && available(text))?.[1];
9
+ }
10
+ /**
11
+ * Resolves authored text without modifying the selected translation.
12
+ *
13
+ * Selection prefers an exact locale (case-insensitively), then another entry
14
+ * with the same primary language, then the exact fallback locale, and finally
15
+ * the first non-blank translation.
16
+ */
17
+ export function resolveLocalizedText(value, locale, fallbackLocale = "en-US") {
18
+ if (typeof value === "string")
19
+ return available(value) ? value : "";
20
+ if (value === null || value === undefined)
21
+ return "";
22
+ const entries = Object.entries(value);
23
+ const exactLocale = exact(entries, locale);
24
+ if (exactLocale !== undefined)
25
+ return exactLocale;
26
+ const primaryLanguage = locale?.split("-")[0]?.toLowerCase();
27
+ if (primaryLanguage !== undefined && primaryLanguage !== "") {
28
+ const sameLanguage = entries.find(([key, text]) => key.split("-")[0]?.toLowerCase() === primaryLanguage && available(text))?.[1];
29
+ if (sameLanguage !== undefined)
30
+ return sameLanguage;
31
+ }
32
+ return exact(entries, fallbackLocale)
33
+ ?? entries.find(([, text]) => available(text))?.[1]
34
+ ?? "";
35
+ }
36
+ //# sourceMappingURL=localized-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localized-text.js","sourceRoot":"","sources":["../../src/web/localized-text.ts"],"names":[],"mappings":"AAGA,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;AAC1D,CAAC;AAED,SAAS,KAAK,CACZ,OAAiD,EACjD,MAA0B;IAE1B,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IAC5D,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IACxC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,CAClC,GAAG,CAAC,WAAW,EAAE,KAAK,UAAU,IAAI,SAAS,CAAC,IAAI,CAAC,CACpD,EAAE,CAAC,CAAC,CAAC,CAAC;AACT,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAuC,EACvC,MAAe,EACf,cAAc,GAAG,OAAO;IAExB,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACpE,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IAErD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACtC,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC3C,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,WAAW,CAAC;IAElD,MAAM,eAAe,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;IAC7D,IAAI,eAAe,KAAK,SAAS,IAAI,eAAe,KAAK,EAAE,EAAE,CAAC;QAC5D,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,CAChD,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,eAAe,IAAI,SAAS,CAAC,IAAI,CAAC,CACxE,EAAE,CAAC,CAAC,CAAC,CAAC;QACP,IAAI,YAAY,KAAK,SAAS;YAAE,OAAO,YAAY,CAAC;IACtD,CAAC;IAED,OAAO,KAAK,CAAC,OAAO,EAAE,cAAc,CAAC;WAChC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;WAChD,EAAE,CAAC;AACV,CAAC","sourcesContent":["/** Text authored either as a literal or as locale-keyed translations. */\nexport type LocalizedText = string | Readonly<Record<string, string>>;\n\nfunction available(value: unknown): value is string {\n return typeof value === \"string\" && value.trim() !== \"\";\n}\n\nfunction exact(\n entries: ReadonlyArray<readonly [string, string]>,\n locale: string | undefined,\n): string | undefined {\n if (locale === undefined || locale === \"\") return undefined;\n const normalized = locale.toLowerCase();\n return entries.find(([key, text]) =>\n key.toLowerCase() === normalized && available(text)\n )?.[1];\n}\n\n/**\n * Resolves authored text without modifying the selected translation.\n *\n * Selection prefers an exact locale (case-insensitively), then another entry\n * with the same primary language, then the exact fallback locale, and finally\n * the first non-blank translation.\n */\nexport function resolveLocalizedText(\n value: LocalizedText | null | undefined,\n locale?: string,\n fallbackLocale = \"en-US\",\n): string {\n if (typeof value === \"string\") return available(value) ? value : \"\";\n if (value === null || value === undefined) return \"\";\n\n const entries = Object.entries(value);\n const exactLocale = exact(entries, locale);\n if (exactLocale !== undefined) return exactLocale;\n\n const primaryLanguage = locale?.split(\"-\")[0]?.toLowerCase();\n if (primaryLanguage !== undefined && primaryLanguage !== \"\") {\n const sameLanguage = entries.find(([key, text]) =>\n key.split(\"-\")[0]?.toLowerCase() === primaryLanguage && available(text)\n )?.[1];\n if (sameLanguage !== undefined) return sameLanguage;\n }\n\n return exact(entries, fallbackLocale)\n ?? entries.find(([, text]) => available(text))?.[1]\n ?? \"\";\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import { type ReactElement } from "react";
2
+ export { usePlatformUnsavedState } from "./use-platform-unsaved-state.js";
3
+ export { useNow } from "./use-now.js";
4
+ /** Configuration for mounting one React-owned module surface. */
5
+ export interface MountReactSurfaceOptions {
6
+ /** Catalog slug written to the host element for module-scoped styling. */
7
+ moduleSlug: string;
8
+ /** Compiled module CSS retained once per document while any mount uses it. */
9
+ styles?: string;
10
+ /** React content owned by the mounted module. */
11
+ element: ReactElement;
12
+ /** Releases mount-local resources after React unmounts. */
13
+ dispose?: () => void;
14
+ }
15
+ /**
16
+ * Mounts one isolated React surface and returns an idempotent cleanup function.
17
+ */
18
+ export declare function mountReactSurface(target: HTMLElement, options: MountReactSurfaceOptions): () => void;
@@ -0,0 +1,111 @@
1
+ import { StrictMode, createElement } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ export { usePlatformUnsavedState } from "./use-platform-unsaved-state.js";
4
+ export { useNow } from "./use-now.js";
5
+ const stylesheetsByDocument = new WeakMap();
6
+ function retainStylesheet(document, styles) {
7
+ let stylesheets = stylesheetsByDocument.get(document);
8
+ if (stylesheets === undefined) {
9
+ stylesheets = new Map();
10
+ stylesheetsByDocument.set(document, stylesheets);
11
+ }
12
+ let registration = stylesheets.get(styles);
13
+ if (registration === undefined) {
14
+ const element = document.createElement("style");
15
+ element.textContent = styles;
16
+ document.head.appendChild(element);
17
+ registration = { element, references: 0 };
18
+ stylesheets.set(styles, registration);
19
+ }
20
+ registration.references += 1;
21
+ let released = false;
22
+ return () => {
23
+ if (released)
24
+ return;
25
+ released = true;
26
+ registration.references -= 1;
27
+ if (registration.references !== 0)
28
+ return;
29
+ registration.element.remove();
30
+ stylesheets.delete(styles);
31
+ if (stylesheets.size === 0) {
32
+ stylesheetsByDocument.delete(document);
33
+ }
34
+ };
35
+ }
36
+ /**
37
+ * Mounts one isolated React surface and returns an idempotent cleanup function.
38
+ */
39
+ export function mountReactSurface(target, options) {
40
+ const hadMount = target.hasAttribute("data-ms-mount");
41
+ const previousMount = target.getAttribute("data-ms-mount");
42
+ const restoreMount = () => {
43
+ if (hadMount && previousMount !== null) {
44
+ target.setAttribute("data-ms-mount", previousMount);
45
+ }
46
+ else {
47
+ target.removeAttribute("data-ms-mount");
48
+ }
49
+ };
50
+ target.setAttribute("data-ms-mount", options.moduleSlug);
51
+ let root;
52
+ let releaseStylesheet;
53
+ try {
54
+ if (options.styles !== undefined) {
55
+ releaseStylesheet = retainStylesheet(target.ownerDocument, options.styles);
56
+ }
57
+ root = createRoot(target);
58
+ root.render(createElement(StrictMode, null, options.element));
59
+ }
60
+ catch (error) {
61
+ try {
62
+ root?.unmount();
63
+ }
64
+ catch {
65
+ // Preserve the original mount error after best-effort cleanup.
66
+ }
67
+ try {
68
+ options.dispose?.();
69
+ }
70
+ catch {
71
+ // Preserve the original mount error after best-effort cleanup.
72
+ }
73
+ try {
74
+ releaseStylesheet?.();
75
+ }
76
+ catch {
77
+ // Preserve the original mount error after best-effort cleanup.
78
+ }
79
+ try {
80
+ restoreMount();
81
+ }
82
+ catch {
83
+ // Preserve the original mount error after best-effort cleanup.
84
+ }
85
+ throw error;
86
+ }
87
+ const mountedRoot = root;
88
+ let cleaned = false;
89
+ return () => {
90
+ if (cleaned)
91
+ return;
92
+ cleaned = true;
93
+ try {
94
+ mountedRoot.unmount();
95
+ }
96
+ finally {
97
+ try {
98
+ options.dispose?.();
99
+ }
100
+ finally {
101
+ try {
102
+ releaseStylesheet?.();
103
+ }
104
+ finally {
105
+ restoreMount();
106
+ }
107
+ }
108
+ }
109
+ };
110
+ }
111
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/web/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAqB,MAAM,OAAO,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAOtC,MAAM,qBAAqB,GAAG,IAAI,OAAO,EAGtC,CAAC;AAEJ,SAAS,gBAAgB,CAAC,QAAkB,EAAE,MAAc;IAC1D,IAAI,WAAW,GAAG,qBAAqB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,WAAW,GAAG,IAAI,GAAG,EAAE,CAAC;QACxB,qBAAqB,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACnD,CAAC;IAED,IAAI,YAAY,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3C,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAChD,OAAO,CAAC,WAAW,GAAG,MAAM,CAAC;QAC7B,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QACnC,YAAY,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;QAC1C,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACxC,CAAC;IACD,YAAY,CAAC,UAAU,IAAI,CAAC,CAAC;IAE7B,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,OAAO,GAAG,EAAE;QACV,IAAI,QAAQ;YAAE,OAAO;QACrB,QAAQ,GAAG,IAAI,CAAC;QAChB,YAAY,CAAC,UAAU,IAAI,CAAC,CAAC;QAC7B,IAAI,YAAY,CAAC,UAAU,KAAK,CAAC;YAAE,OAAO;QAE1C,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QAC9B,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3B,IAAI,WAAW,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAC3B,qBAAqB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACzC,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAcD;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAmB,EACnB,OAAiC;IAEjC,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;IACtD,MAAM,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;IAC3D,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,IAAI,QAAQ,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;YACvC,MAAM,CAAC,YAAY,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;QACtD,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,eAAe,CAAC,eAAe,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC,CAAC;IACF,MAAM,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAEzD,IAAI,IAA+C,CAAC;IACpD,IAAI,iBAA2C,CAAC;IAChD,IAAI,CAAC;QACH,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YACjC,iBAAiB,GAAG,gBAAgB,CAAC,MAAM,CAAC,aAAa,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QAC7E,CAAC;QACD,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IAChE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC;YACH,IAAI,EAAE,OAAO,EAAE,CAAC;QAClB,CAAC;QAAC,MAAM,CAAC;YACP,+DAA+D;QACjE,CAAC;QACD,IAAI,CAAC;YACH,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,+DAA+D;QACjE,CAAC;QACD,IAAI,CAAC;YACH,iBAAiB,EAAE,EAAE,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,+DAA+D;QACjE,CAAC;QACD,IAAI,CAAC;YACH,YAAY,EAAE,CAAC;QACjB,CAAC;QAAC,MAAM,CAAC;YACP,+DAA+D;QACjE,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,MAAM,WAAW,GAAG,IAAI,CAAC;IAEzB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,OAAO,GAAG,EAAE;QACV,IAAI,OAAO;YAAE,OAAO;QACpB,OAAO,GAAG,IAAI,CAAC;QACf,IAAI,CAAC;YACH,WAAW,CAAC,OAAO,EAAE,CAAC;QACxB,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC;gBACH,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;YACtB,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC;oBACH,iBAAiB,EAAE,EAAE,CAAC;gBACxB,CAAC;wBAAS,CAAC;oBACT,YAAY,EAAE,CAAC;gBACjB,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { StrictMode, createElement, type ReactElement } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nexport { usePlatformUnsavedState } from \"./use-platform-unsaved-state.js\";\nexport { useNow } from \"./use-now.js\";\n\ninterface StylesheetRegistration {\n element: HTMLStyleElement;\n references: number;\n}\n\nconst stylesheetsByDocument = new WeakMap<\n Document,\n Map<string, StylesheetRegistration>\n>();\n\nfunction retainStylesheet(document: Document, styles: string): () => void {\n let stylesheets = stylesheetsByDocument.get(document);\n if (stylesheets === undefined) {\n stylesheets = new Map();\n stylesheetsByDocument.set(document, stylesheets);\n }\n\n let registration = stylesheets.get(styles);\n if (registration === undefined) {\n const element = document.createElement(\"style\");\n element.textContent = styles;\n document.head.appendChild(element);\n registration = { element, references: 0 };\n stylesheets.set(styles, registration);\n }\n registration.references += 1;\n\n let released = false;\n return () => {\n if (released) return;\n released = true;\n registration.references -= 1;\n if (registration.references !== 0) return;\n\n registration.element.remove();\n stylesheets.delete(styles);\n if (stylesheets.size === 0) {\n stylesheetsByDocument.delete(document);\n }\n };\n}\n\n/** Configuration for mounting one React-owned module surface. */\nexport interface MountReactSurfaceOptions {\n /** Catalog slug written to the host element for module-scoped styling. */\n moduleSlug: string;\n /** Compiled module CSS retained once per document while any mount uses it. */\n styles?: string;\n /** React content owned by the mounted module. */\n element: ReactElement;\n /** Releases mount-local resources after React unmounts. */\n dispose?: () => void;\n}\n\n/**\n * Mounts one isolated React surface and returns an idempotent cleanup function.\n */\nexport function mountReactSurface(\n target: HTMLElement,\n options: MountReactSurfaceOptions,\n): () => void {\n const hadMount = target.hasAttribute(\"data-ms-mount\");\n const previousMount = target.getAttribute(\"data-ms-mount\");\n const restoreMount = () => {\n if (hadMount && previousMount !== null) {\n target.setAttribute(\"data-ms-mount\", previousMount);\n } else {\n target.removeAttribute(\"data-ms-mount\");\n }\n };\n target.setAttribute(\"data-ms-mount\", options.moduleSlug);\n\n let root: ReturnType<typeof createRoot> | undefined;\n let releaseStylesheet: (() => void) | undefined;\n try {\n if (options.styles !== undefined) {\n releaseStylesheet = retainStylesheet(target.ownerDocument, options.styles);\n }\n root = createRoot(target);\n root.render(createElement(StrictMode, null, options.element));\n } catch (error) {\n try {\n root?.unmount();\n } catch {\n // Preserve the original mount error after best-effort cleanup.\n }\n try {\n options.dispose?.();\n } catch {\n // Preserve the original mount error after best-effort cleanup.\n }\n try {\n releaseStylesheet?.();\n } catch {\n // Preserve the original mount error after best-effort cleanup.\n }\n try {\n restoreMount();\n } catch {\n // Preserve the original mount error after best-effort cleanup.\n }\n throw error;\n }\n const mountedRoot = root;\n\n let cleaned = false;\n return () => {\n if (cleaned) return;\n cleaned = true;\n try {\n mountedRoot.unmount();\n } finally {\n try {\n options.dispose?.();\n } finally {\n try {\n releaseStylesheet?.();\n } finally {\n restoreMount();\n }\n }\n }\n };\n}\n"]}
@@ -0,0 +1,60 @@
1
+ import { type ModuleClientContext } from "../plugin.js";
2
+ import type { PlatformFetch } from "./types.js";
3
+ /** Configuration owned by the host at module mount time. */
4
+ export interface CreateModuleWebTransportsOptions {
5
+ /** Canonical 1-16 character catalog slug or UUID used for routing and diagnostics. */
6
+ readonly moduleRef: string;
7
+ /** Dispatch root for this mounted module. Empty means same-origin. */
8
+ readonly apiBase?: string;
9
+ /** Authenticated fetch capability supplied by the platform host. */
10
+ readonly fetch?: PlatformFetch;
11
+ /** Maximum bytes parsed from response bodies. Defaults to one mebibyte. */
12
+ readonly maxResponseBytes?: number;
13
+ }
14
+ /** Public-root and platform-scoped transports for one mounted module. */
15
+ export type ModuleWebTransports = ModuleClientContext;
16
+ export declare function createModuleWebTransports(options: CreateModuleWebTransportsOptions): ModuleWebTransports;
17
+ /**
18
+ * Options for an API request made through the v0.1.0 compatibility transport.
19
+ *
20
+ * @deprecated Prefer the complete scoped request options exposed by
21
+ * `createModuleWebTransports()`.
22
+ */
23
+ export interface ModuleWebRequestOptions {
24
+ body?: unknown;
25
+ headers?: HeadersInit;
26
+ query?: Record<string, boolean | number | string | null | undefined>;
27
+ signal?: AbortSignal;
28
+ }
29
+ /**
30
+ * Configuration for the v0.1.0 compatibility transport.
31
+ *
32
+ * `appId` remains accepted for source compatibility but is informational.
33
+ * It is never converted into a trusted `X-MS-App-ID` browser header.
34
+ *
35
+ * @deprecated Prefer {@link CreateModuleWebTransportsOptions}.
36
+ */
37
+ export interface CreateModuleWebTransportOptions {
38
+ moduleRef: string;
39
+ apiBase?: string;
40
+ appId?: string;
41
+ fetch?: PlatformFetch;
42
+ }
43
+ /**
44
+ * Direct v0.1.0 module transport for public-root and platform-scoped routes.
45
+ *
46
+ * @deprecated Prefer {@link ModuleWebTransports}.
47
+ */
48
+ export interface ModuleWebTransport {
49
+ /** Sends a request and parses a successful JSON body. */
50
+ request<T>(method: string, route: string, options?: ModuleWebRequestOptions): Promise<T>;
51
+ /** Sends a request and returns its successful body as text. */
52
+ text(method: string, route: string, options?: ModuleWebRequestOptions): Promise<string>;
53
+ }
54
+ /**
55
+ * Creates the direct route-dispatching transport released in v0.1.0.
56
+ *
57
+ * @deprecated Prefer {@link createModuleWebTransports}, which keeps public and
58
+ * platform routes structurally separate and exposes the full scoped contract.
59
+ */
60
+ export declare function createModuleWebTransport(options: CreateModuleWebTransportOptions): ModuleWebTransport;