@immediately-run/sdk 0.13.0 → 0.16.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/README.md +27 -3
- package/dist/MDXProvider.cjs.map +1 -1
- package/dist/MDXProvider.d.cts +4 -0
- package/dist/MDXProvider.d.ts +4 -0
- package/dist/MDXProvider.js.map +1 -1
- package/dist/RoutingSpec.cjs.map +1 -1
- package/dist/RoutingSpec.d.cts +20 -3
- package/dist/RoutingSpec.d.ts +20 -3
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +2 -0
- package/dist/auth.d.ts +2 -0
- package/dist/auth.js.map +1 -1
- package/dist/boot.cjs +17 -7
- package/dist/boot.cjs.map +1 -1
- package/dist/boot.d.cts +28 -4
- package/dist/boot.d.ts +28 -4
- package/dist/boot.js +16 -7
- package/dist/boot.js.map +1 -1
- package/dist/components/Include.cjs.map +1 -1
- package/dist/components/Include.d.cts +7 -0
- package/dist/components/Include.d.ts +7 -0
- package/dist/components/Include.js.map +1 -1
- package/dist/components/MDXComponents.cjs.map +1 -1
- package/dist/components/MDXComponents.d.cts +6 -0
- package/dist/components/MDXComponents.d.ts +6 -0
- package/dist/components/MDXComponents.js.map +1 -1
- package/dist/components/Routes.cjs +59 -0
- package/dist/components/Routes.cjs.map +1 -0
- package/dist/components/Routes.d.cts +34 -0
- package/dist/components/Routes.d.ts +34 -0
- package/dist/components/Routes.js +34 -0
- package/dist/components/Routes.js.map +1 -0
- package/dist/contribute.cjs.map +1 -1
- package/dist/contribute.d.cts +2 -0
- package/dist/contribute.d.ts +2 -0
- package/dist/contribute.js.map +1 -1
- package/dist/diagnostics.cjs.map +1 -1
- package/dist/diagnostics.d.cts +3 -0
- package/dist/diagnostics.d.ts +3 -0
- package/dist/diagnostics.js.map +1 -1
- package/dist/formFactor.cjs.map +1 -1
- package/dist/formFactor.d.cts +2 -0
- package/dist/formFactor.d.ts +2 -0
- package/dist/formFactor.js.map +1 -1
- package/dist/hooks.cjs +27 -28
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.cts +39 -4
- package/dist/hooks.d.ts +39 -4
- package/dist/hooks.js +27 -29
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +6 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -4
- package/dist/index.d.ts +7 -4
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/irMarkers.cjs.map +1 -1
- package/dist/irMarkers.d.cts +1 -0
- package/dist/irMarkers.d.ts +1 -0
- package/dist/irMarkers.js.map +1 -1
- package/dist/llm.cjs.map +1 -1
- package/dist/llm.d.cts +5 -0
- package/dist/llm.d.ts +5 -0
- package/dist/llm.js.map +1 -1
- package/dist/loading.cjs +186 -0
- package/dist/loading.cjs.map +1 -0
- package/dist/loading.d.cts +48 -0
- package/dist/loading.d.ts +48 -0
- package/dist/loading.js +162 -0
- package/dist/loading.js.map +1 -0
- package/dist/mounts.cjs.map +1 -1
- package/dist/mounts.d.cts +14 -2
- package/dist/mounts.d.ts +14 -2
- package/dist/mounts.js.map +1 -1
- package/dist/netFetch.cjs.map +1 -1
- package/dist/netFetch.d.cts +2 -0
- package/dist/netFetch.d.ts +2 -0
- package/dist/netFetch.js.map +1 -1
- package/dist/onFsChange.cjs +42 -0
- package/dist/onFsChange.cjs.map +1 -0
- package/dist/onFsChange.d.cts +23 -0
- package/dist/onFsChange.d.ts +23 -0
- package/dist/onFsChange.js +16 -0
- package/dist/onFsChange.js.map +1 -0
- package/dist/protocolStream.cjs.map +1 -1
- package/dist/protocolStream.d.cts +3 -0
- package/dist/protocolStream.d.ts +3 -0
- package/dist/protocolStream.js.map +1 -1
- package/dist/ready.cjs.map +1 -1
- package/dist/ready.d.cts +7 -0
- package/dist/ready.d.ts +7 -0
- package/dist/ready.js.map +1 -1
- package/dist/routeMatch.cjs +72 -0
- package/dist/routeMatch.cjs.map +1 -0
- package/dist/routeMatch.d.cts +19 -0
- package/dist/routeMatch.d.ts +19 -0
- package/dist/routeMatch.js +46 -0
- package/dist/routeMatch.js.map +1 -0
- package/dist/routing.cjs +35 -14
- package/dist/routing.cjs.map +1 -1
- package/dist/routing.d.cts +33 -4
- package/dist/routing.d.ts +33 -4
- package/dist/routing.js +32 -14
- package/dist/routing.js.map +1 -1
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.cts +1 -0
- package/dist/runtime.d.ts +1 -0
- package/dist/runtime.js.map +1 -1
- package/dist/sandboxTypes.cjs.map +1 -1
- package/dist/sandboxTypes.d.cts +30 -7
- package/dist/sandboxTypes.d.ts +30 -7
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +6 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/protocolStream.ts"],"sourcesContent":["// SDK-side consumer for the host streaming transport (UI_AS_APPS_SPEC §5.1).\n//\n// The host `pumpGenerator` emits, per request msgId, a run of `stream.event`\n// frames terminated by one `stream.done` (with the return value) or `stream.error`\n// frame. This reassembles that run into an AsyncGenerator: each `event` is a\n// `yield`, the `done` value is the generator's `return`, an `error` is a `throw`.\n//\n// `consumeStream` takes an injected `StreamTransport` so it's unit-tested with a\n// fake send/subscribe — no bundler. `protocolStream`/`contribute` below wire it to\n// the real sandbox messageBus via sandboxUtils.\nimport { addListener, sendMessage } from './sandboxUtils';\n\nexport type StreamFrame =\n | { kind: 'event'; value: unknown }\n | { kind: 'done'; value: unknown }\n | { kind: 'error'; code: string; message: string };\n\nexport interface StreamTransport {\n // Fire the request that starts the stream. The host replies with frames tagged\n // by the same `msgId`.\n send: (msg: { type: string; method: string; params: unknown[]; msgId: number; stream: true }) => void;\n // Subscribe to inbound frames for `type`; returns an unsubscribe.\n subscribe: (\n type: string,\n handler: (msg: { msgId?: number; stream?: StreamFrame }) => void\n ) => () => void;\n}\n\nexport class StreamError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = 'StreamError';\n this.code = code;\n }\n}\n\nlet streamCounter = 0;\nconst nextMsgId = (): number => {\n // Distinct from the bundler's own protocolRequest counter space is unnecessary —\n // frames are filtered by (type, msgId, stream) so a collision with a one-shot\n // reply (which has `result`, not `stream`) can't be misread.\n streamCounter = (streamCounter + 1) % Number.MAX_SAFE_INTEGER;\n return streamCounter;\n};\n\n/**\n * Drive one streamed request to completion over an injected transport.\n *\n * Yields each event value; returns the `done` value; throws `StreamError` on an\n * error frame. Always unsubscribes (via the generator's `finally`) so an early\n * `break` in the consumer doesn't leak the listener.\n */\nexport async function* consumeStream<T = unknown, R = unknown>(\n transport: StreamTransport,\n type: string,\n method: string,\n params: unknown[],\n msgId: number = nextMsgId()\n): AsyncGenerator<T, R, void> {\n const queue: StreamFrame[] = [];\n let wake: (() => void) | null = null;\n const push = (frame: StreamFrame) => {\n queue.push(frame);\n const w = wake;\n wake = null;\n w?.();\n };\n\n const unsubscribe = transport.subscribe(type, (msg) => {\n if (msg.msgId !== msgId || !msg.stream) return;\n push(msg.stream);\n });\n\n try {\n transport.send({ type, method, params, msgId, stream: true });\n while (true) {\n if (queue.length === 0) {\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n continue;\n }\n const frame = queue.shift() as StreamFrame;\n if (frame.kind === 'event') {\n yield frame.value as T;\n } else if (frame.kind === 'done') {\n return frame.value as R;\n } else {\n throw new StreamError(frame.code, frame.message);\n }\n }\n } finally {\n unsubscribe();\n }\n}\n\n// The real sandbox transport, built from the bundler messageBus helpers.\nconst bundlerTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) =>\n addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n};\n\n/**\n * Consume an elevated streaming protocol method from app code.\n *\n * `for await (const ev of protocolStream('protocol-contribute', 'run', [opts])) …`\n */\nexport function protocolStream<T = unknown, R = unknown>(\n protocolName: string,\n method: string,\n params: unknown[]\n): AsyncGenerator<T, R, void> {\n return consumeStream<T, R>(bundlerTransport, protocolName, method, params);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,0BAAyC;
|
|
1
|
+
{"version":3,"sources":["../src/protocolStream.ts"],"sourcesContent":["// SDK-side consumer for the host streaming transport (UI_AS_APPS_SPEC §5.1).\n//\n// The host `pumpGenerator` emits, per request msgId, a run of `stream.event`\n// frames terminated by one `stream.done` (with the return value) or `stream.error`\n// frame. This reassembles that run into an AsyncGenerator: each `event` is a\n// `yield`, the `done` value is the generator's `return`, an `error` is a `throw`.\n//\n// `consumeStream` takes an injected `StreamTransport` so it's unit-tested with a\n// fake send/subscribe — no bundler. `protocolStream`/`contribute` below wire it to\n// the real sandbox messageBus via sandboxUtils.\nimport { addListener, sendMessage } from './sandboxUtils';\n\n/** One frame of a host stream: an `event` value, the terminal `done` value, or an `error`. */\nexport type StreamFrame =\n | { kind: 'event'; value: unknown }\n | { kind: 'done'; value: unknown }\n | { kind: 'error'; code: string; message: string };\n\n/** The send/subscribe transport {@link consumeStream} drives (injected so it can be faked in tests). */\nexport interface StreamTransport {\n // Fire the request that starts the stream. The host replies with frames tagged\n // by the same `msgId`.\n send: (msg: { type: string; method: string; params: unknown[]; msgId: number; stream: true }) => void;\n // Subscribe to inbound frames for `type`; returns an unsubscribe.\n subscribe: (\n type: string,\n handler: (msg: { msgId?: number; stream?: StreamFrame }) => void\n ) => () => void;\n}\n\n/** Thrown when a stream ends in an `error` frame; carries the host's `code`. */\nexport class StreamError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = 'StreamError';\n this.code = code;\n }\n}\n\nlet streamCounter = 0;\nconst nextMsgId = (): number => {\n // Distinct from the bundler's own protocolRequest counter space is unnecessary —\n // frames are filtered by (type, msgId, stream) so a collision with a one-shot\n // reply (which has `result`, not `stream`) can't be misread.\n streamCounter = (streamCounter + 1) % Number.MAX_SAFE_INTEGER;\n return streamCounter;\n};\n\n/**\n * Drive one streamed request to completion over an injected transport.\n *\n * Yields each event value; returns the `done` value; throws `StreamError` on an\n * error frame. Always unsubscribes (via the generator's `finally`) so an early\n * `break` in the consumer doesn't leak the listener.\n */\nexport async function* consumeStream<T = unknown, R = unknown>(\n transport: StreamTransport,\n type: string,\n method: string,\n params: unknown[],\n msgId: number = nextMsgId()\n): AsyncGenerator<T, R, void> {\n const queue: StreamFrame[] = [];\n let wake: (() => void) | null = null;\n const push = (frame: StreamFrame) => {\n queue.push(frame);\n const w = wake;\n wake = null;\n w?.();\n };\n\n const unsubscribe = transport.subscribe(type, (msg) => {\n if (msg.msgId !== msgId || !msg.stream) return;\n push(msg.stream);\n });\n\n try {\n transport.send({ type, method, params, msgId, stream: true });\n while (true) {\n if (queue.length === 0) {\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n continue;\n }\n const frame = queue.shift() as StreamFrame;\n if (frame.kind === 'event') {\n yield frame.value as T;\n } else if (frame.kind === 'done') {\n return frame.value as R;\n } else {\n throw new StreamError(frame.code, frame.message);\n }\n }\n } finally {\n unsubscribe();\n }\n}\n\n// The real sandbox transport, built from the bundler messageBus helpers.\nconst bundlerTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) =>\n addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n};\n\n/**\n * Consume an elevated streaming protocol method from app code.\n *\n * `for await (const ev of protocolStream('protocol-contribute', 'run', [opts])) …`\n */\nexport function protocolStream<T = unknown, R = unknown>(\n protocolName: string,\n method: string,\n params: unknown[]\n): AsyncGenerator<T, R, void> {\n return consumeStream<T, R>(bundlerTransport, protocolName, method, params);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,0BAAyC;AAqBlC,MAAM,oBAAoB,MAAM;AAAA,EAErC,YAAY,MAAc,SAAiB;AACzC,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;AAEA,IAAI,gBAAgB;AACpB,MAAM,YAAY,MAAc;AAI9B,mBAAiB,gBAAgB,KAAK,OAAO;AAC7C,SAAO;AACT;AASA,gBAAuB,cACrB,WACA,MACA,QACA,QACA,QAAgB,UAAU,GACE;AAC5B,QAAM,QAAuB,CAAC;AAC9B,MAAI,OAA4B;AAChC,QAAM,OAAO,CAAC,UAAuB;AACnC,UAAM,KAAK,KAAK;AAChB,UAAM,IAAI;AACV,WAAO;AACP,QAAI;AAAA,EACN;AAEA,QAAM,cAAc,UAAU,UAAU,MAAM,CAAC,QAAQ;AACrD,QAAI,IAAI,UAAU,SAAS,CAAC,IAAI,OAAQ;AACxC,SAAK,IAAI,MAAM;AAAA,EACjB,CAAC;AAED,MAAI;AACF,cAAU,KAAK,EAAE,MAAM,QAAQ,QAAQ,OAAO,QAAQ,KAAK,CAAC;AAC5D,WAAO,MAAM;AACX,UAAI,MAAM,WAAW,GAAG;AACtB,cAAM,IAAI,QAAc,CAAC,YAAY;AACnC,iBAAO;AAAA,QACT,CAAC;AACD;AAAA,MACF;AACA,YAAM,QAAQ,MAAM,MAAM;AAC1B,UAAI,MAAM,SAAS,SAAS;AAC1B,cAAM,MAAM;AAAA,MACd,WAAW,MAAM,SAAS,QAAQ;AAChC,eAAO,MAAM;AAAA,MACf,OAAO;AACL,cAAM,IAAI,YAAY,MAAM,MAAM,MAAM,OAAO;AAAA,MACjD;AAAA,IACF;AAAA,EACF,UAAE;AACA,gBAAY;AAAA,EACd;AACF;AAGA,MAAM,mBAAoC;AAAA,EACxC,MAAM,CAAC,YAAQ,iCAAY,IAAI,MAAM,GAAyC;AAAA,EAC9E,WAAW,CAAC,MAAM,gBAChB,iCAAY,MAAM,CAAC,QAAQ,QAAQ,GAA+C,CAAC;AACvF;AAOO,SAAS,eACd,cACA,QACA,QAC4B;AAC5B,SAAO,cAAoB,kBAAkB,cAAc,QAAQ,MAAM;AAC3E;","names":[]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** One frame of a host stream: an `event` value, the terminal `done` value, or an `error`. */
|
|
1
2
|
type StreamFrame = {
|
|
2
3
|
kind: 'event';
|
|
3
4
|
value: unknown;
|
|
@@ -9,6 +10,7 @@ type StreamFrame = {
|
|
|
9
10
|
code: string;
|
|
10
11
|
message: string;
|
|
11
12
|
};
|
|
13
|
+
/** The send/subscribe transport {@link consumeStream} drives (injected so it can be faked in tests). */
|
|
12
14
|
interface StreamTransport {
|
|
13
15
|
send: (msg: {
|
|
14
16
|
type: string;
|
|
@@ -22,6 +24,7 @@ interface StreamTransport {
|
|
|
22
24
|
stream?: StreamFrame;
|
|
23
25
|
}) => void) => () => void;
|
|
24
26
|
}
|
|
27
|
+
/** Thrown when a stream ends in an `error` frame; carries the host's `code`. */
|
|
25
28
|
declare class StreamError extends Error {
|
|
26
29
|
code: string;
|
|
27
30
|
constructor(code: string, message: string);
|
package/dist/protocolStream.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** One frame of a host stream: an `event` value, the terminal `done` value, or an `error`. */
|
|
1
2
|
type StreamFrame = {
|
|
2
3
|
kind: 'event';
|
|
3
4
|
value: unknown;
|
|
@@ -9,6 +10,7 @@ type StreamFrame = {
|
|
|
9
10
|
code: string;
|
|
10
11
|
message: string;
|
|
11
12
|
};
|
|
13
|
+
/** The send/subscribe transport {@link consumeStream} drives (injected so it can be faked in tests). */
|
|
12
14
|
interface StreamTransport {
|
|
13
15
|
send: (msg: {
|
|
14
16
|
type: string;
|
|
@@ -22,6 +24,7 @@ interface StreamTransport {
|
|
|
22
24
|
stream?: StreamFrame;
|
|
23
25
|
}) => void) => () => void;
|
|
24
26
|
}
|
|
27
|
+
/** Thrown when a stream ends in an `error` frame; carries the host's `code`. */
|
|
25
28
|
declare class StreamError extends Error {
|
|
26
29
|
code: string;
|
|
27
30
|
constructor(code: string, message: string);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/protocolStream.ts"],"sourcesContent":["// SDK-side consumer for the host streaming transport (UI_AS_APPS_SPEC §5.1).\n//\n// The host `pumpGenerator` emits, per request msgId, a run of `stream.event`\n// frames terminated by one `stream.done` (with the return value) or `stream.error`\n// frame. This reassembles that run into an AsyncGenerator: each `event` is a\n// `yield`, the `done` value is the generator's `return`, an `error` is a `throw`.\n//\n// `consumeStream` takes an injected `StreamTransport` so it's unit-tested with a\n// fake send/subscribe — no bundler. `protocolStream`/`contribute` below wire it to\n// the real sandbox messageBus via sandboxUtils.\nimport { addListener, sendMessage } from './sandboxUtils';\n\nexport type StreamFrame =\n | { kind: 'event'; value: unknown }\n | { kind: 'done'; value: unknown }\n | { kind: 'error'; code: string; message: string };\n\nexport interface StreamTransport {\n // Fire the request that starts the stream. The host replies with frames tagged\n // by the same `msgId`.\n send: (msg: { type: string; method: string; params: unknown[]; msgId: number; stream: true }) => void;\n // Subscribe to inbound frames for `type`; returns an unsubscribe.\n subscribe: (\n type: string,\n handler: (msg: { msgId?: number; stream?: StreamFrame }) => void\n ) => () => void;\n}\n\nexport class StreamError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = 'StreamError';\n this.code = code;\n }\n}\n\nlet streamCounter = 0;\nconst nextMsgId = (): number => {\n // Distinct from the bundler's own protocolRequest counter space is unnecessary —\n // frames are filtered by (type, msgId, stream) so a collision with a one-shot\n // reply (which has `result`, not `stream`) can't be misread.\n streamCounter = (streamCounter + 1) % Number.MAX_SAFE_INTEGER;\n return streamCounter;\n};\n\n/**\n * Drive one streamed request to completion over an injected transport.\n *\n * Yields each event value; returns the `done` value; throws `StreamError` on an\n * error frame. Always unsubscribes (via the generator's `finally`) so an early\n * `break` in the consumer doesn't leak the listener.\n */\nexport async function* consumeStream<T = unknown, R = unknown>(\n transport: StreamTransport,\n type: string,\n method: string,\n params: unknown[],\n msgId: number = nextMsgId()\n): AsyncGenerator<T, R, void> {\n const queue: StreamFrame[] = [];\n let wake: (() => void) | null = null;\n const push = (frame: StreamFrame) => {\n queue.push(frame);\n const w = wake;\n wake = null;\n w?.();\n };\n\n const unsubscribe = transport.subscribe(type, (msg) => {\n if (msg.msgId !== msgId || !msg.stream) return;\n push(msg.stream);\n });\n\n try {\n transport.send({ type, method, params, msgId, stream: true });\n while (true) {\n if (queue.length === 0) {\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n continue;\n }\n const frame = queue.shift() as StreamFrame;\n if (frame.kind === 'event') {\n yield frame.value as T;\n } else if (frame.kind === 'done') {\n return frame.value as R;\n } else {\n throw new StreamError(frame.code, frame.message);\n }\n }\n } finally {\n unsubscribe();\n }\n}\n\n// The real sandbox transport, built from the bundler messageBus helpers.\nconst bundlerTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) =>\n addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n};\n\n/**\n * Consume an elevated streaming protocol method from app code.\n *\n * `for await (const ev of protocolStream('protocol-contribute', 'run', [opts])) …`\n */\nexport function protocolStream<T = unknown, R = unknown>(\n protocolName: string,\n method: string,\n params: unknown[]\n): AsyncGenerator<T, R, void> {\n return consumeStream<T, R>(bundlerTransport, protocolName, method, params);\n}\n"],"mappings":"AAUA,SAAS,aAAa,mBAAmB;
|
|
1
|
+
{"version":3,"sources":["../src/protocolStream.ts"],"sourcesContent":["// SDK-side consumer for the host streaming transport (UI_AS_APPS_SPEC §5.1).\n//\n// The host `pumpGenerator` emits, per request msgId, a run of `stream.event`\n// frames terminated by one `stream.done` (with the return value) or `stream.error`\n// frame. This reassembles that run into an AsyncGenerator: each `event` is a\n// `yield`, the `done` value is the generator's `return`, an `error` is a `throw`.\n//\n// `consumeStream` takes an injected `StreamTransport` so it's unit-tested with a\n// fake send/subscribe — no bundler. `protocolStream`/`contribute` below wire it to\n// the real sandbox messageBus via sandboxUtils.\nimport { addListener, sendMessage } from './sandboxUtils';\n\n/** One frame of a host stream: an `event` value, the terminal `done` value, or an `error`. */\nexport type StreamFrame =\n | { kind: 'event'; value: unknown }\n | { kind: 'done'; value: unknown }\n | { kind: 'error'; code: string; message: string };\n\n/** The send/subscribe transport {@link consumeStream} drives (injected so it can be faked in tests). */\nexport interface StreamTransport {\n // Fire the request that starts the stream. The host replies with frames tagged\n // by the same `msgId`.\n send: (msg: { type: string; method: string; params: unknown[]; msgId: number; stream: true }) => void;\n // Subscribe to inbound frames for `type`; returns an unsubscribe.\n subscribe: (\n type: string,\n handler: (msg: { msgId?: number; stream?: StreamFrame }) => void\n ) => () => void;\n}\n\n/** Thrown when a stream ends in an `error` frame; carries the host's `code`. */\nexport class StreamError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = 'StreamError';\n this.code = code;\n }\n}\n\nlet streamCounter = 0;\nconst nextMsgId = (): number => {\n // Distinct from the bundler's own protocolRequest counter space is unnecessary —\n // frames are filtered by (type, msgId, stream) so a collision with a one-shot\n // reply (which has `result`, not `stream`) can't be misread.\n streamCounter = (streamCounter + 1) % Number.MAX_SAFE_INTEGER;\n return streamCounter;\n};\n\n/**\n * Drive one streamed request to completion over an injected transport.\n *\n * Yields each event value; returns the `done` value; throws `StreamError` on an\n * error frame. Always unsubscribes (via the generator's `finally`) so an early\n * `break` in the consumer doesn't leak the listener.\n */\nexport async function* consumeStream<T = unknown, R = unknown>(\n transport: StreamTransport,\n type: string,\n method: string,\n params: unknown[],\n msgId: number = nextMsgId()\n): AsyncGenerator<T, R, void> {\n const queue: StreamFrame[] = [];\n let wake: (() => void) | null = null;\n const push = (frame: StreamFrame) => {\n queue.push(frame);\n const w = wake;\n wake = null;\n w?.();\n };\n\n const unsubscribe = transport.subscribe(type, (msg) => {\n if (msg.msgId !== msgId || !msg.stream) return;\n push(msg.stream);\n });\n\n try {\n transport.send({ type, method, params, msgId, stream: true });\n while (true) {\n if (queue.length === 0) {\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n continue;\n }\n const frame = queue.shift() as StreamFrame;\n if (frame.kind === 'event') {\n yield frame.value as T;\n } else if (frame.kind === 'done') {\n return frame.value as R;\n } else {\n throw new StreamError(frame.code, frame.message);\n }\n }\n } finally {\n unsubscribe();\n }\n}\n\n// The real sandbox transport, built from the bundler messageBus helpers.\nconst bundlerTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) =>\n addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n};\n\n/**\n * Consume an elevated streaming protocol method from app code.\n *\n * `for await (const ev of protocolStream('protocol-contribute', 'run', [opts])) …`\n */\nexport function protocolStream<T = unknown, R = unknown>(\n protocolName: string,\n method: string,\n params: unknown[]\n): AsyncGenerator<T, R, void> {\n return consumeStream<T, R>(bundlerTransport, protocolName, method, params);\n}\n"],"mappings":"AAUA,SAAS,aAAa,mBAAmB;AAqBlC,MAAM,oBAAoB,MAAM;AAAA,EAErC,YAAY,MAAc,SAAiB;AACzC,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;AAEA,IAAI,gBAAgB;AACpB,MAAM,YAAY,MAAc;AAI9B,mBAAiB,gBAAgB,KAAK,OAAO;AAC7C,SAAO;AACT;AASA,gBAAuB,cACrB,WACA,MACA,QACA,QACA,QAAgB,UAAU,GACE;AAC5B,QAAM,QAAuB,CAAC;AAC9B,MAAI,OAA4B;AAChC,QAAM,OAAO,CAAC,UAAuB;AACnC,UAAM,KAAK,KAAK;AAChB,UAAM,IAAI;AACV,WAAO;AACP,QAAI;AAAA,EACN;AAEA,QAAM,cAAc,UAAU,UAAU,MAAM,CAAC,QAAQ;AACrD,QAAI,IAAI,UAAU,SAAS,CAAC,IAAI,OAAQ;AACxC,SAAK,IAAI,MAAM;AAAA,EACjB,CAAC;AAED,MAAI;AACF,cAAU,KAAK,EAAE,MAAM,QAAQ,QAAQ,OAAO,QAAQ,KAAK,CAAC;AAC5D,WAAO,MAAM;AACX,UAAI,MAAM,WAAW,GAAG;AACtB,cAAM,IAAI,QAAc,CAAC,YAAY;AACnC,iBAAO;AAAA,QACT,CAAC;AACD;AAAA,MACF;AACA,YAAM,QAAQ,MAAM,MAAM;AAC1B,UAAI,MAAM,SAAS,SAAS;AAC1B,cAAM,MAAM;AAAA,MACd,WAAW,MAAM,SAAS,QAAQ;AAChC,eAAO,MAAM;AAAA,MACf,OAAO;AACL,cAAM,IAAI,YAAY,MAAM,MAAM,MAAM,OAAO;AAAA,MACjD;AAAA,IACF;AAAA,EACF,UAAE;AACA,gBAAY;AAAA,EACd;AACF;AAGA,MAAM,mBAAoC;AAAA,EACxC,MAAM,CAAC,QAAQ,YAAY,IAAI,MAAM,GAAyC;AAAA,EAC9E,WAAW,CAAC,MAAM,YAChB,YAAY,MAAM,CAAC,QAAQ,QAAQ,GAA+C,CAAC;AACvF;AAOO,SAAS,eACd,cACA,QACA,QAC4B;AAC5B,SAAO,cAAoB,kBAAkB,cAAc,QAAQ,MAAM;AAC3E;","names":[]}
|
package/dist/ready.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ready.ts"],"sourcesContent":["// The `ir.interactive` boot signal — the app-facing `reportReady()` / `onReady()` /\n// `getReadyState()` surface (LOAD_PROFILING_SPEC §3.1, R3-46). This closes the\n// \"existing SDK boot signal\" that `UI_AS_APPS_SPEC §6.2` referenced but never\n// defined.\n//\n// The runtime marks `ir.interactive` when the app's root render commits. An app\n// whose USEFULLY-interactive moment is later than first commit (e.g. after an\n// initial data load) calls `reportReady()` to DELAY the signal — which, per LP2-3,\n// can only ever push interactive later, never earlier than the commit (the host\n// resolves `max(commit, reportReady)`; see `resolveInteractive`). `onReady` /\n// `getReadyState` expose the report state in the same poll+subscribe shape as\n// `auth` / `mounts`.\n\nimport { sendMessage as defaultSend } from \"./sandboxUtils\";\n\nexport interface ReadyState {\n /** Whether the app has called `reportReady()`. */\n reported: boolean;\n /** The app-reported timestamp (`performance.now()`), if it has reported. */\n reportedAt?: number;\n}\n\ninterface ReadyDeps {\n send: (type: string, data?: Record<string, unknown>) => void;\n now: () => number;\n}\n\nconst realNow = (): number =>\n typeof performance !== \"undefined\" && typeof performance.now === \"function\"\n ? performance.now()\n : Date.now();\n\nconst defaultDeps: ReadyDeps = { send: defaultSend, now: realNow };\n\nlet deps: ReadyDeps = defaultDeps;\nlet state: ReadyState = { reported: false };\nconst listeners = new Set<(s: ReadyState) => void>();\n\n/**\n * Signal that the app is usefully interactive (e.g. after an initial data load).\n * IDEMPOTENT — only the FIRST call counts; later calls are ignored. Forwards the\n * report to the runtime (`ir-report-ready`) so the host can resolve\n * `ir.interactive = max(rootRenderCommit, reportedAt)` (LP2-3) — calling it before\n * the root render commits can only delay the signal, never advance it.\n */\nexport function reportReady(): void {\n if (state.reported) return;\n state = { reported: true, reportedAt: deps.now() };\n try {\n deps.send(\"ir-report-ready\", { at: state.reportedAt });\n } catch {\n /* transport not ready — the runtime still marks interactive at root commit */\n }\n for (const l of listeners) l(state);\n}\n\n/** Pollable snapshot of the report state. */\nexport function getReadyState(): ReadyState {\n return state;\n}\n\n/**\n * Subscribe to the ready signal. Invoked immediately with the current state (so a\n * late subscriber after `reportReady()` still fires) and again whenever it reports.\n * Returns an unsubscribe.\n */\nexport function onReady(listener: (s: ReadyState) => void): () => void {\n listeners.add(listener);\n listener(state);\n return () => {\n listeners.delete(listener);\n };\n}\n\n/** Test seam: override the transport/clock. */\nexport function __setReadyDeps(d: Partial<ReadyDeps>): void {\n deps = { ...defaultDeps, ...d };\n}\n\n/** Test seam: reset module state between cases. */\nexport function __resetReady(): void {\n deps = defaultDeps;\n state = { reported: false };\n listeners.clear();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,0BAA2C;
|
|
1
|
+
{"version":3,"sources":["../src/ready.ts"],"sourcesContent":["// The `ir.interactive` boot signal — the app-facing `reportReady()` / `onReady()` /\n// `getReadyState()` surface (LOAD_PROFILING_SPEC §3.1, R3-46). This closes the\n// \"existing SDK boot signal\" that `UI_AS_APPS_SPEC §6.2` referenced but never\n// defined.\n//\n// The runtime marks `ir.interactive` when the app's root render commits. An app\n// whose USEFULLY-interactive moment is later than first commit (e.g. after an\n// initial data load) calls `reportReady()` to DELAY the signal — which, per LP2-3,\n// can only ever push interactive later, never earlier than the commit (the host\n// resolves `max(commit, reportReady)`; see `resolveInteractive`). `onReady` /\n// `getReadyState` expose the report state in the same poll+subscribe shape as\n// `auth` / `mounts`.\n\nimport { sendMessage as defaultSend } from \"./sandboxUtils\";\n\n/** The app's `reportReady()` state, mirrored by {@link onReady}/{@link getReadyState}. */\nexport interface ReadyState {\n /** Whether the app has called `reportReady()`. */\n reported: boolean;\n /** The app-reported timestamp (`performance.now()`), if it has reported. */\n reportedAt?: number;\n}\n\ninterface ReadyDeps {\n send: (type: string, data?: Record<string, unknown>) => void;\n now: () => number;\n}\n\nconst realNow = (): number =>\n typeof performance !== \"undefined\" && typeof performance.now === \"function\"\n ? performance.now()\n : Date.now();\n\nconst defaultDeps: ReadyDeps = { send: defaultSend, now: realNow };\n\nlet deps: ReadyDeps = defaultDeps;\nlet state: ReadyState = { reported: false };\nconst listeners = new Set<(s: ReadyState) => void>();\n\n/**\n * Signal that the app is usefully interactive (e.g. after an initial data load).\n * IDEMPOTENT — only the FIRST call counts; later calls are ignored. Forwards the\n * report to the runtime (`ir-report-ready`) so the host can resolve\n * `ir.interactive = max(rootRenderCommit, reportedAt)` (LP2-3) — calling it before\n * the root render commits can only delay the signal, never advance it.\n *\n * UX contract (LOADING_UX_SPEC §9.1): calling this tells the host *\"keep your\n * loading skeleton up; I am not done yet\"* — the host holds the §3 reveal until\n * this call (or the load budget). Call it ONCE, when the first USEFULLY-interactive\n * frame is on screen — not at mount, and not after every async settle. An app that\n * never calls it reveals automatically at the root-render commit (the default path).\n */\nexport function reportReady(): void {\n if (state.reported) return;\n state = { reported: true, reportedAt: deps.now() };\n try {\n deps.send(\"ir-report-ready\", { at: state.reportedAt });\n } catch {\n /* transport not ready — the runtime still marks interactive at root commit */\n }\n for (const l of listeners) l(state);\n}\n\n/** Pollable snapshot of the report state. */\nexport function getReadyState(): ReadyState {\n return state;\n}\n\n/**\n * Subscribe to the ready signal. Invoked immediately with the current state (so a\n * late subscriber after `reportReady()` still fires) and again whenever it reports.\n * Returns an unsubscribe.\n */\nexport function onReady(listener: (s: ReadyState) => void): () => void {\n listeners.add(listener);\n listener(state);\n return () => {\n listeners.delete(listener);\n };\n}\n\n/** Test seam: override the transport/clock. */\nexport function __setReadyDeps(d: Partial<ReadyDeps>): void {\n deps = { ...defaultDeps, ...d };\n}\n\n/** Test seam: reset module state between cases. */\nexport function __resetReady(): void {\n deps = defaultDeps;\n state = { reported: false };\n listeners.clear();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,0BAA2C;AAe3C,MAAM,UAAU,MACd,OAAO,gBAAgB,eAAe,OAAO,YAAY,QAAQ,aAC7D,YAAY,IAAI,IAChB,KAAK,IAAI;AAEf,MAAM,cAAyB,EAAE,MAAM,oBAAAA,aAAa,KAAK,QAAQ;AAEjE,IAAI,OAAkB;AACtB,IAAI,QAAoB,EAAE,UAAU,MAAM;AAC1C,MAAM,YAAY,oBAAI,IAA6B;AAe5C,SAAS,cAAoB;AAClC,MAAI,MAAM,SAAU;AACpB,UAAQ,EAAE,UAAU,MAAM,YAAY,KAAK,IAAI,EAAE;AACjD,MAAI;AACF,SAAK,KAAK,mBAAmB,EAAE,IAAI,MAAM,WAAW,CAAC;AAAA,EACvD,QAAQ;AAAA,EAER;AACA,aAAW,KAAK,UAAW,GAAE,KAAK;AACpC;AAGO,SAAS,gBAA4B;AAC1C,SAAO;AACT;AAOO,SAAS,QAAQ,UAA+C;AACrE,YAAU,IAAI,QAAQ;AACtB,WAAS,KAAK;AACd,SAAO,MAAM;AACX,cAAU,OAAO,QAAQ;AAAA,EAC3B;AACF;AAGO,SAAS,eAAe,GAA6B;AAC1D,SAAO,EAAE,GAAG,aAAa,GAAG,EAAE;AAChC;AAGO,SAAS,eAAqB;AACnC,SAAO;AACP,UAAQ,EAAE,UAAU,MAAM;AAC1B,YAAU,MAAM;AAClB;","names":["defaultSend"]}
|
package/dist/ready.d.cts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** The app's `reportReady()` state, mirrored by {@link onReady}/{@link getReadyState}. */
|
|
1
2
|
interface ReadyState {
|
|
2
3
|
/** Whether the app has called `reportReady()`. */
|
|
3
4
|
reported: boolean;
|
|
@@ -14,6 +15,12 @@ interface ReadyDeps {
|
|
|
14
15
|
* report to the runtime (`ir-report-ready`) so the host can resolve
|
|
15
16
|
* `ir.interactive = max(rootRenderCommit, reportedAt)` (LP2-3) — calling it before
|
|
16
17
|
* the root render commits can only delay the signal, never advance it.
|
|
18
|
+
*
|
|
19
|
+
* UX contract (LOADING_UX_SPEC §9.1): calling this tells the host *"keep your
|
|
20
|
+
* loading skeleton up; I am not done yet"* — the host holds the §3 reveal until
|
|
21
|
+
* this call (or the load budget). Call it ONCE, when the first USEFULLY-interactive
|
|
22
|
+
* frame is on screen — not at mount, and not after every async settle. An app that
|
|
23
|
+
* never calls it reveals automatically at the root-render commit (the default path).
|
|
17
24
|
*/
|
|
18
25
|
declare function reportReady(): void;
|
|
19
26
|
/** Pollable snapshot of the report state. */
|
package/dist/ready.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** The app's `reportReady()` state, mirrored by {@link onReady}/{@link getReadyState}. */
|
|
1
2
|
interface ReadyState {
|
|
2
3
|
/** Whether the app has called `reportReady()`. */
|
|
3
4
|
reported: boolean;
|
|
@@ -14,6 +15,12 @@ interface ReadyDeps {
|
|
|
14
15
|
* report to the runtime (`ir-report-ready`) so the host can resolve
|
|
15
16
|
* `ir.interactive = max(rootRenderCommit, reportedAt)` (LP2-3) — calling it before
|
|
16
17
|
* the root render commits can only delay the signal, never advance it.
|
|
18
|
+
*
|
|
19
|
+
* UX contract (LOADING_UX_SPEC §9.1): calling this tells the host *"keep your
|
|
20
|
+
* loading skeleton up; I am not done yet"* — the host holds the §3 reveal until
|
|
21
|
+
* this call (or the load budget). Call it ONCE, when the first USEFULLY-interactive
|
|
22
|
+
* frame is on screen — not at mount, and not after every async settle. An app that
|
|
23
|
+
* never calls it reveals automatically at the root-render commit (the default path).
|
|
17
24
|
*/
|
|
18
25
|
declare function reportReady(): void;
|
|
19
26
|
/** Pollable snapshot of the report state. */
|
package/dist/ready.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ready.ts"],"sourcesContent":["// The `ir.interactive` boot signal — the app-facing `reportReady()` / `onReady()` /\n// `getReadyState()` surface (LOAD_PROFILING_SPEC §3.1, R3-46). This closes the\n// \"existing SDK boot signal\" that `UI_AS_APPS_SPEC §6.2` referenced but never\n// defined.\n//\n// The runtime marks `ir.interactive` when the app's root render commits. An app\n// whose USEFULLY-interactive moment is later than first commit (e.g. after an\n// initial data load) calls `reportReady()` to DELAY the signal — which, per LP2-3,\n// can only ever push interactive later, never earlier than the commit (the host\n// resolves `max(commit, reportReady)`; see `resolveInteractive`). `onReady` /\n// `getReadyState` expose the report state in the same poll+subscribe shape as\n// `auth` / `mounts`.\n\nimport { sendMessage as defaultSend } from \"./sandboxUtils\";\n\nexport interface ReadyState {\n /** Whether the app has called `reportReady()`. */\n reported: boolean;\n /** The app-reported timestamp (`performance.now()`), if it has reported. */\n reportedAt?: number;\n}\n\ninterface ReadyDeps {\n send: (type: string, data?: Record<string, unknown>) => void;\n now: () => number;\n}\n\nconst realNow = (): number =>\n typeof performance !== \"undefined\" && typeof performance.now === \"function\"\n ? performance.now()\n : Date.now();\n\nconst defaultDeps: ReadyDeps = { send: defaultSend, now: realNow };\n\nlet deps: ReadyDeps = defaultDeps;\nlet state: ReadyState = { reported: false };\nconst listeners = new Set<(s: ReadyState) => void>();\n\n/**\n * Signal that the app is usefully interactive (e.g. after an initial data load).\n * IDEMPOTENT — only the FIRST call counts; later calls are ignored. Forwards the\n * report to the runtime (`ir-report-ready`) so the host can resolve\n * `ir.interactive = max(rootRenderCommit, reportedAt)` (LP2-3) — calling it before\n * the root render commits can only delay the signal, never advance it.\n */\nexport function reportReady(): void {\n if (state.reported) return;\n state = { reported: true, reportedAt: deps.now() };\n try {\n deps.send(\"ir-report-ready\", { at: state.reportedAt });\n } catch {\n /* transport not ready — the runtime still marks interactive at root commit */\n }\n for (const l of listeners) l(state);\n}\n\n/** Pollable snapshot of the report state. */\nexport function getReadyState(): ReadyState {\n return state;\n}\n\n/**\n * Subscribe to the ready signal. Invoked immediately with the current state (so a\n * late subscriber after `reportReady()` still fires) and again whenever it reports.\n * Returns an unsubscribe.\n */\nexport function onReady(listener: (s: ReadyState) => void): () => void {\n listeners.add(listener);\n listener(state);\n return () => {\n listeners.delete(listener);\n };\n}\n\n/** Test seam: override the transport/clock. */\nexport function __setReadyDeps(d: Partial<ReadyDeps>): void {\n deps = { ...defaultDeps, ...d };\n}\n\n/** Test seam: reset module state between cases. */\nexport function __resetReady(): void {\n deps = defaultDeps;\n state = { reported: false };\n listeners.clear();\n}\n"],"mappings":"AAaA,SAAS,eAAe,mBAAmB;
|
|
1
|
+
{"version":3,"sources":["../src/ready.ts"],"sourcesContent":["// The `ir.interactive` boot signal — the app-facing `reportReady()` / `onReady()` /\n// `getReadyState()` surface (LOAD_PROFILING_SPEC §3.1, R3-46). This closes the\n// \"existing SDK boot signal\" that `UI_AS_APPS_SPEC §6.2` referenced but never\n// defined.\n//\n// The runtime marks `ir.interactive` when the app's root render commits. An app\n// whose USEFULLY-interactive moment is later than first commit (e.g. after an\n// initial data load) calls `reportReady()` to DELAY the signal — which, per LP2-3,\n// can only ever push interactive later, never earlier than the commit (the host\n// resolves `max(commit, reportReady)`; see `resolveInteractive`). `onReady` /\n// `getReadyState` expose the report state in the same poll+subscribe shape as\n// `auth` / `mounts`.\n\nimport { sendMessage as defaultSend } from \"./sandboxUtils\";\n\n/** The app's `reportReady()` state, mirrored by {@link onReady}/{@link getReadyState}. */\nexport interface ReadyState {\n /** Whether the app has called `reportReady()`. */\n reported: boolean;\n /** The app-reported timestamp (`performance.now()`), if it has reported. */\n reportedAt?: number;\n}\n\ninterface ReadyDeps {\n send: (type: string, data?: Record<string, unknown>) => void;\n now: () => number;\n}\n\nconst realNow = (): number =>\n typeof performance !== \"undefined\" && typeof performance.now === \"function\"\n ? performance.now()\n : Date.now();\n\nconst defaultDeps: ReadyDeps = { send: defaultSend, now: realNow };\n\nlet deps: ReadyDeps = defaultDeps;\nlet state: ReadyState = { reported: false };\nconst listeners = new Set<(s: ReadyState) => void>();\n\n/**\n * Signal that the app is usefully interactive (e.g. after an initial data load).\n * IDEMPOTENT — only the FIRST call counts; later calls are ignored. Forwards the\n * report to the runtime (`ir-report-ready`) so the host can resolve\n * `ir.interactive = max(rootRenderCommit, reportedAt)` (LP2-3) — calling it before\n * the root render commits can only delay the signal, never advance it.\n *\n * UX contract (LOADING_UX_SPEC §9.1): calling this tells the host *\"keep your\n * loading skeleton up; I am not done yet\"* — the host holds the §3 reveal until\n * this call (or the load budget). Call it ONCE, when the first USEFULLY-interactive\n * frame is on screen — not at mount, and not after every async settle. An app that\n * never calls it reveals automatically at the root-render commit (the default path).\n */\nexport function reportReady(): void {\n if (state.reported) return;\n state = { reported: true, reportedAt: deps.now() };\n try {\n deps.send(\"ir-report-ready\", { at: state.reportedAt });\n } catch {\n /* transport not ready — the runtime still marks interactive at root commit */\n }\n for (const l of listeners) l(state);\n}\n\n/** Pollable snapshot of the report state. */\nexport function getReadyState(): ReadyState {\n return state;\n}\n\n/**\n * Subscribe to the ready signal. Invoked immediately with the current state (so a\n * late subscriber after `reportReady()` still fires) and again whenever it reports.\n * Returns an unsubscribe.\n */\nexport function onReady(listener: (s: ReadyState) => void): () => void {\n listeners.add(listener);\n listener(state);\n return () => {\n listeners.delete(listener);\n };\n}\n\n/** Test seam: override the transport/clock. */\nexport function __setReadyDeps(d: Partial<ReadyDeps>): void {\n deps = { ...defaultDeps, ...d };\n}\n\n/** Test seam: reset module state between cases. */\nexport function __resetReady(): void {\n deps = defaultDeps;\n state = { reported: false };\n listeners.clear();\n}\n"],"mappings":"AAaA,SAAS,eAAe,mBAAmB;AAe3C,MAAM,UAAU,MACd,OAAO,gBAAgB,eAAe,OAAO,YAAY,QAAQ,aAC7D,YAAY,IAAI,IAChB,KAAK,IAAI;AAEf,MAAM,cAAyB,EAAE,MAAM,aAAa,KAAK,QAAQ;AAEjE,IAAI,OAAkB;AACtB,IAAI,QAAoB,EAAE,UAAU,MAAM;AAC1C,MAAM,YAAY,oBAAI,IAA6B;AAe5C,SAAS,cAAoB;AAClC,MAAI,MAAM,SAAU;AACpB,UAAQ,EAAE,UAAU,MAAM,YAAY,KAAK,IAAI,EAAE;AACjD,MAAI;AACF,SAAK,KAAK,mBAAmB,EAAE,IAAI,MAAM,WAAW,CAAC;AAAA,EACvD,QAAQ;AAAA,EAER;AACA,aAAW,KAAK,UAAW,GAAE,KAAK;AACpC;AAGO,SAAS,gBAA4B;AAC1C,SAAO;AACT;AAOO,SAAS,QAAQ,UAA+C;AACrE,YAAU,IAAI,QAAQ;AACtB,WAAS,KAAK;AACd,SAAO,MAAM;AACX,cAAU,OAAO,QAAQ;AAAA,EAC3B;AACF;AAGO,SAAS,eAAe,GAA6B;AAC1D,SAAO,EAAE,GAAG,aAAa,GAAG,EAAE;AAChC;AAGO,SAAS,eAAqB;AACnC,SAAO;AACP,UAAQ,EAAE,UAAU,MAAM;AAC1B,YAAU,MAAM;AAClB;","names":[]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var routeMatch_exports = {};
|
|
20
|
+
__export(routeMatch_exports, {
|
|
21
|
+
compileTemplate: () => compileTemplate,
|
|
22
|
+
matchRoute: () => matchRoute,
|
|
23
|
+
toRegExp: () => toRegExp
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(routeMatch_exports);
|
|
26
|
+
const escapeForRegexp = (str) => str.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");
|
|
27
|
+
const WILDCARD_GROUP = "wild";
|
|
28
|
+
const compileTemplate = (template) => {
|
|
29
|
+
const token = /(:[A-Za-z_][A-Za-z0-9_]*)|\*/g;
|
|
30
|
+
let src = "";
|
|
31
|
+
let last = 0;
|
|
32
|
+
let m;
|
|
33
|
+
while ((m = token.exec(template)) !== null) {
|
|
34
|
+
src += escapeForRegexp(template.slice(last, m.index));
|
|
35
|
+
src += m[1] ? `(?<${m[1].slice(1)}>[^/]+)` : `(?<${WILDCARD_GROUP}>.*)`;
|
|
36
|
+
last = m.index + m[0].length;
|
|
37
|
+
}
|
|
38
|
+
src += escapeForRegexp(template.slice(last));
|
|
39
|
+
return new RegExp(`^${src}$`);
|
|
40
|
+
};
|
|
41
|
+
const templateCache = /* @__PURE__ */ new Map();
|
|
42
|
+
const toRegExp = (pattern) => {
|
|
43
|
+
if (pattern instanceof RegExp) {
|
|
44
|
+
return pattern;
|
|
45
|
+
}
|
|
46
|
+
let compiled = templateCache.get(pattern);
|
|
47
|
+
if (!compiled) {
|
|
48
|
+
compiled = compileTemplate(pattern);
|
|
49
|
+
templateCache.set(pattern, compiled);
|
|
50
|
+
}
|
|
51
|
+
return compiled;
|
|
52
|
+
};
|
|
53
|
+
const matchRoute = (pattern, path) => {
|
|
54
|
+
const match = path.match(toRegExp(pattern));
|
|
55
|
+
if (!match) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const params = {};
|
|
59
|
+
for (const [key, value] of Object.entries(match.groups ?? {})) {
|
|
60
|
+
if (value !== void 0) {
|
|
61
|
+
params[key === WILDCARD_GROUP ? "*" : key] = value;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return params;
|
|
65
|
+
};
|
|
66
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
67
|
+
0 && (module.exports = {
|
|
68
|
+
compileTemplate,
|
|
69
|
+
matchRoute,
|
|
70
|
+
toRegExp
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=routeMatch.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/routeMatch.ts"],"sourcesContent":["import type { RouteParams } from './RoutingSpec';\n\n// from: https://stackoverflow.com/a/63838890\nconst escapeForRegexp = (str: string): string => str.replace(/[.*+\\-?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n// Internal group name standing in for a `*` wildcard (which is not a valid JS\n// regex group identifier); remapped to the `*` param key after matching.\nconst WILDCARD_GROUP = 'wild';\n\n/**\n * Compile a path template to an anchored RegExp with named groups:\n * `:name` → one non-slash segment `*` → the rest (greedy)\n * A template with neither token is a literal exact match. Raw RegExp patterns\n * never reach here — they are the escape hatch, used as authored.\n */\nexport const compileTemplate = (template: string): RegExp => {\n const token = /(:[A-Za-z_][A-Za-z0-9_]*)|\\*/g;\n let src = '';\n let last = 0;\n let m: RegExpExecArray | null;\n while ((m = token.exec(template)) !== null) {\n src += escapeForRegexp(template.slice(last, m.index));\n src += m[1] ? `(?<${m[1].slice(1)}>[^/]+)` : `(?<${WILDCARD_GROUP}>.*)`;\n last = m.index + m[0].length;\n }\n src += escapeForRegexp(template.slice(last));\n return new RegExp(`^${src}$`);\n};\n\nconst templateCache = new Map<string, RegExp>();\n\n/** Resolve a pattern to a RegExp: templates are compiled (and cached), RegExp passes through. */\nexport const toRegExp = (pattern: string | RegExp): RegExp => {\n if (pattern instanceof RegExp) {\n return pattern;\n }\n let compiled = templateCache.get(pattern);\n if (!compiled) {\n compiled = compileTemplate(pattern);\n templateCache.set(pattern, compiled);\n }\n return compiled;\n};\n\n/**\n * Match a `sandboxPath` against a route pattern. Returns the named params on a\n * match (the `*` wildcard surfaces under the `'*'` key), or `null` otherwise.\n */\nexport const matchRoute = (pattern: string | RegExp, path: string): RouteParams | null => {\n const match = path.match(toRegExp(pattern));\n if (!match) {\n return null;\n }\n const params: RouteParams = {};\n for (const [key, value] of Object.entries(match.groups ?? {})) {\n if (value !== undefined) {\n params[key === WILDCARD_GROUP ? '*' : key] = value;\n }\n }\n return params;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,MAAM,kBAAkB,CAAC,QAAwB,IAAI,QAAQ,yBAAyB,MAAM;AAI5F,MAAM,iBAAiB;AAQhB,MAAM,kBAAkB,CAAC,aAA6B;AAC3D,QAAM,QAAQ;AACd,MAAI,MAAM;AACV,MAAI,OAAO;AACX,MAAI;AACJ,UAAQ,IAAI,MAAM,KAAK,QAAQ,OAAO,MAAM;AAC1C,WAAO,gBAAgB,SAAS,MAAM,MAAM,EAAE,KAAK,CAAC;AACpD,WAAO,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,YAAY,MAAM,cAAc;AACjE,WAAO,EAAE,QAAQ,EAAE,CAAC,EAAE;AAAA,EACxB;AACA,SAAO,gBAAgB,SAAS,MAAM,IAAI,CAAC;AAC3C,SAAO,IAAI,OAAO,IAAI,GAAG,GAAG;AAC9B;AAEA,MAAM,gBAAgB,oBAAI,IAAoB;AAGvC,MAAM,WAAW,CAAC,YAAqC;AAC5D,MAAI,mBAAmB,QAAQ;AAC7B,WAAO;AAAA,EACT;AACA,MAAI,WAAW,cAAc,IAAI,OAAO;AACxC,MAAI,CAAC,UAAU;AACb,eAAW,gBAAgB,OAAO;AAClC,kBAAc,IAAI,SAAS,QAAQ;AAAA,EACrC;AACA,SAAO;AACT;AAMO,MAAM,aAAa,CAAC,SAA0B,SAAqC;AACxF,QAAM,QAAQ,KAAK,MAAM,SAAS,OAAO,CAAC;AAC1C,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AACA,QAAM,SAAsB,CAAC;AAC7B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,UAAU,CAAC,CAAC,GAAG;AAC7D,QAAI,UAAU,QAAW;AACvB,aAAO,QAAQ,iBAAiB,MAAM,GAAG,IAAI;AAAA,IAC/C;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RouteParams } from './RoutingSpec.cjs';
|
|
2
|
+
import 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Compile a path template to an anchored RegExp with named groups:
|
|
6
|
+
* `:name` → one non-slash segment `*` → the rest (greedy)
|
|
7
|
+
* A template with neither token is a literal exact match. Raw RegExp patterns
|
|
8
|
+
* never reach here — they are the escape hatch, used as authored.
|
|
9
|
+
*/
|
|
10
|
+
declare const compileTemplate: (template: string) => RegExp;
|
|
11
|
+
/** Resolve a pattern to a RegExp: templates are compiled (and cached), RegExp passes through. */
|
|
12
|
+
declare const toRegExp: (pattern: string | RegExp) => RegExp;
|
|
13
|
+
/**
|
|
14
|
+
* Match a `sandboxPath` against a route pattern. Returns the named params on a
|
|
15
|
+
* match (the `*` wildcard surfaces under the `'*'` key), or `null` otherwise.
|
|
16
|
+
*/
|
|
17
|
+
declare const matchRoute: (pattern: string | RegExp, path: string) => RouteParams | null;
|
|
18
|
+
|
|
19
|
+
export { compileTemplate, matchRoute, toRegExp };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RouteParams } from './RoutingSpec.js';
|
|
2
|
+
import 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Compile a path template to an anchored RegExp with named groups:
|
|
6
|
+
* `:name` → one non-slash segment `*` → the rest (greedy)
|
|
7
|
+
* A template with neither token is a literal exact match. Raw RegExp patterns
|
|
8
|
+
* never reach here — they are the escape hatch, used as authored.
|
|
9
|
+
*/
|
|
10
|
+
declare const compileTemplate: (template: string) => RegExp;
|
|
11
|
+
/** Resolve a pattern to a RegExp: templates are compiled (and cached), RegExp passes through. */
|
|
12
|
+
declare const toRegExp: (pattern: string | RegExp) => RegExp;
|
|
13
|
+
/**
|
|
14
|
+
* Match a `sandboxPath` against a route pattern. Returns the named params on a
|
|
15
|
+
* match (the `*` wildcard surfaces under the `'*'` key), or `null` otherwise.
|
|
16
|
+
*/
|
|
17
|
+
declare const matchRoute: (pattern: string | RegExp, path: string) => RouteParams | null;
|
|
18
|
+
|
|
19
|
+
export { compileTemplate, matchRoute, toRegExp };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const escapeForRegexp = (str) => str.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");
|
|
2
|
+
const WILDCARD_GROUP = "wild";
|
|
3
|
+
const compileTemplate = (template) => {
|
|
4
|
+
const token = /(:[A-Za-z_][A-Za-z0-9_]*)|\*/g;
|
|
5
|
+
let src = "";
|
|
6
|
+
let last = 0;
|
|
7
|
+
let m;
|
|
8
|
+
while ((m = token.exec(template)) !== null) {
|
|
9
|
+
src += escapeForRegexp(template.slice(last, m.index));
|
|
10
|
+
src += m[1] ? `(?<${m[1].slice(1)}>[^/]+)` : `(?<${WILDCARD_GROUP}>.*)`;
|
|
11
|
+
last = m.index + m[0].length;
|
|
12
|
+
}
|
|
13
|
+
src += escapeForRegexp(template.slice(last));
|
|
14
|
+
return new RegExp(`^${src}$`);
|
|
15
|
+
};
|
|
16
|
+
const templateCache = /* @__PURE__ */ new Map();
|
|
17
|
+
const toRegExp = (pattern) => {
|
|
18
|
+
if (pattern instanceof RegExp) {
|
|
19
|
+
return pattern;
|
|
20
|
+
}
|
|
21
|
+
let compiled = templateCache.get(pattern);
|
|
22
|
+
if (!compiled) {
|
|
23
|
+
compiled = compileTemplate(pattern);
|
|
24
|
+
templateCache.set(pattern, compiled);
|
|
25
|
+
}
|
|
26
|
+
return compiled;
|
|
27
|
+
};
|
|
28
|
+
const matchRoute = (pattern, path) => {
|
|
29
|
+
const match = path.match(toRegExp(pattern));
|
|
30
|
+
if (!match) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const params = {};
|
|
34
|
+
for (const [key, value] of Object.entries(match.groups ?? {})) {
|
|
35
|
+
if (value !== void 0) {
|
|
36
|
+
params[key === WILDCARD_GROUP ? "*" : key] = value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return params;
|
|
40
|
+
};
|
|
41
|
+
export {
|
|
42
|
+
compileTemplate,
|
|
43
|
+
matchRoute,
|
|
44
|
+
toRegExp
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=routeMatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/routeMatch.ts"],"sourcesContent":["import type { RouteParams } from './RoutingSpec';\n\n// from: https://stackoverflow.com/a/63838890\nconst escapeForRegexp = (str: string): string => str.replace(/[.*+\\-?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n// Internal group name standing in for a `*` wildcard (which is not a valid JS\n// regex group identifier); remapped to the `*` param key after matching.\nconst WILDCARD_GROUP = 'wild';\n\n/**\n * Compile a path template to an anchored RegExp with named groups:\n * `:name` → one non-slash segment `*` → the rest (greedy)\n * A template with neither token is a literal exact match. Raw RegExp patterns\n * never reach here — they are the escape hatch, used as authored.\n */\nexport const compileTemplate = (template: string): RegExp => {\n const token = /(:[A-Za-z_][A-Za-z0-9_]*)|\\*/g;\n let src = '';\n let last = 0;\n let m: RegExpExecArray | null;\n while ((m = token.exec(template)) !== null) {\n src += escapeForRegexp(template.slice(last, m.index));\n src += m[1] ? `(?<${m[1].slice(1)}>[^/]+)` : `(?<${WILDCARD_GROUP}>.*)`;\n last = m.index + m[0].length;\n }\n src += escapeForRegexp(template.slice(last));\n return new RegExp(`^${src}$`);\n};\n\nconst templateCache = new Map<string, RegExp>();\n\n/** Resolve a pattern to a RegExp: templates are compiled (and cached), RegExp passes through. */\nexport const toRegExp = (pattern: string | RegExp): RegExp => {\n if (pattern instanceof RegExp) {\n return pattern;\n }\n let compiled = templateCache.get(pattern);\n if (!compiled) {\n compiled = compileTemplate(pattern);\n templateCache.set(pattern, compiled);\n }\n return compiled;\n};\n\n/**\n * Match a `sandboxPath` against a route pattern. Returns the named params on a\n * match (the `*` wildcard surfaces under the `'*'` key), or `null` otherwise.\n */\nexport const matchRoute = (pattern: string | RegExp, path: string): RouteParams | null => {\n const match = path.match(toRegExp(pattern));\n if (!match) {\n return null;\n }\n const params: RouteParams = {};\n for (const [key, value] of Object.entries(match.groups ?? {})) {\n if (value !== undefined) {\n params[key === WILDCARD_GROUP ? '*' : key] = value;\n }\n }\n return params;\n};\n"],"mappings":"AAGA,MAAM,kBAAkB,CAAC,QAAwB,IAAI,QAAQ,yBAAyB,MAAM;AAI5F,MAAM,iBAAiB;AAQhB,MAAM,kBAAkB,CAAC,aAA6B;AAC3D,QAAM,QAAQ;AACd,MAAI,MAAM;AACV,MAAI,OAAO;AACX,MAAI;AACJ,UAAQ,IAAI,MAAM,KAAK,QAAQ,OAAO,MAAM;AAC1C,WAAO,gBAAgB,SAAS,MAAM,MAAM,EAAE,KAAK,CAAC;AACpD,WAAO,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,YAAY,MAAM,cAAc;AACjE,WAAO,EAAE,QAAQ,EAAE,CAAC,EAAE;AAAA,EACxB;AACA,SAAO,gBAAgB,SAAS,MAAM,IAAI,CAAC;AAC3C,SAAO,IAAI,OAAO,IAAI,GAAG,GAAG;AAC9B;AAEA,MAAM,gBAAgB,oBAAI,IAAoB;AAGvC,MAAM,WAAW,CAAC,YAAqC;AAC5D,MAAI,mBAAmB,QAAQ;AAC7B,WAAO;AAAA,EACT;AACA,MAAI,WAAW,cAAc,IAAI,OAAO;AACxC,MAAI,CAAC,UAAU;AACb,eAAW,gBAAgB,OAAO;AAClC,kBAAc,IAAI,SAAS,QAAQ;AAAA,EACrC;AACA,SAAO;AACT;AAMO,MAAM,aAAa,CAAC,SAA0B,SAAqC;AACxF,QAAM,QAAQ,KAAK,MAAM,SAAS,OAAO,CAAC;AAC1C,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AACA,QAAM,SAAsB,CAAC;AAC7B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,UAAU,CAAC,CAAC,GAAG;AAC7D,QAAI,UAAU,QAAW;AACvB,aAAO,QAAQ,iBAAiB,MAAM,GAAG,IAAI;AAAA,IAC/C;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
|
package/dist/routing.cjs
CHANGED
|
@@ -21,12 +21,17 @@ __export(routing_exports, {
|
|
|
21
21
|
Router: () => Router,
|
|
22
22
|
applyRoutingRule: () => applyRoutingRule,
|
|
23
23
|
navigate: () => navigate,
|
|
24
|
+
renderRoute: () => renderRoute,
|
|
25
|
+
useRoute: () => useRoute,
|
|
26
|
+
useRouteParams: () => useRouteParams,
|
|
24
27
|
useTinkerableLink: () => useTinkerableLink
|
|
25
28
|
});
|
|
26
29
|
module.exports = __toCommonJS(routing_exports);
|
|
30
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
27
31
|
var import_react = require("react");
|
|
28
32
|
var import_sandboxUtils = require("./sandboxUtils");
|
|
29
33
|
var import_TinkerableContext = require("./TinkerableContext");
|
|
34
|
+
var import_routeMatch = require("./routeMatch");
|
|
30
35
|
var import_urlUtils = require("./urlUtils");
|
|
31
36
|
var import_pathUtils = require("./pathUtils");
|
|
32
37
|
const useTinkerableLink = (newSandboxLocation) => {
|
|
@@ -42,29 +47,42 @@ const useTinkerableLink = (newSandboxLocation) => {
|
|
|
42
47
|
const applyRoutingRule = (routingSpec, navigationState) => {
|
|
43
48
|
const { sandboxPath } = navigationState;
|
|
44
49
|
for (const routingRule of routingSpec.routes) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
} else {
|
|
50
|
-
const match = sandboxPath.match(routingRule.pattern);
|
|
51
|
-
if (routingRule.pattern.test(sandboxPath)) {
|
|
52
|
-
return {
|
|
53
|
-
routingRule,
|
|
54
|
-
pathParameters: match?.groups
|
|
55
|
-
};
|
|
56
|
-
}
|
|
50
|
+
const pathParameters = (0, import_routeMatch.matchRoute)(routingRule.pattern, sandboxPath);
|
|
51
|
+
if (pathParameters) {
|
|
52
|
+
return { routingRule, pathParameters };
|
|
57
53
|
}
|
|
58
54
|
}
|
|
59
55
|
return void 0;
|
|
60
56
|
};
|
|
57
|
+
const renderRoute = (routingRule, params) => {
|
|
58
|
+
if (routingRule.component) {
|
|
59
|
+
const Component = routingRule.component;
|
|
60
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Component, { params });
|
|
61
|
+
}
|
|
62
|
+
return routingRule.element ?? routingRule.reactNode ?? null;
|
|
63
|
+
};
|
|
61
64
|
const Router = () => {
|
|
62
65
|
const context = (0, import_react.useContext)(import_TinkerableContext.TinkerableContext);
|
|
63
|
-
const { navigationState: { routingRule } } = context;
|
|
66
|
+
const { navigationState: { routingRule, pathParameters } } = context;
|
|
64
67
|
if (!routingRule) {
|
|
65
68
|
throw new Error(`No route registered for path ${context.navigationState.sandboxPath}!`);
|
|
66
69
|
}
|
|
67
|
-
return routingRule
|
|
70
|
+
return renderRoute(routingRule, pathParameters ?? {});
|
|
71
|
+
};
|
|
72
|
+
const useRouteParams = () => (0, import_react.use)(import_TinkerableContext.TinkerableContext).navigationState.pathParameters ?? {};
|
|
73
|
+
const useRoute = () => {
|
|
74
|
+
const { navigationState } = (0, import_react.use)(import_TinkerableContext.TinkerableContext);
|
|
75
|
+
const { routingRule, pathParameters, sandboxPath, mode, provider, namespace, repository, ref } = navigationState;
|
|
76
|
+
return {
|
|
77
|
+
name: routingRule?.name,
|
|
78
|
+
params: pathParameters ?? {},
|
|
79
|
+
sandboxPath,
|
|
80
|
+
mode,
|
|
81
|
+
provider,
|
|
82
|
+
namespace,
|
|
83
|
+
repository,
|
|
84
|
+
ref
|
|
85
|
+
};
|
|
68
86
|
};
|
|
69
87
|
const navigate = (target) => {
|
|
70
88
|
console.log(`[Sandbox] Navigating to ${target}`);
|
|
@@ -79,6 +97,9 @@ const navigate = (target) => {
|
|
|
79
97
|
Router,
|
|
80
98
|
applyRoutingRule,
|
|
81
99
|
navigate,
|
|
100
|
+
renderRoute,
|
|
101
|
+
useRoute,
|
|
102
|
+
useRouteParams,
|
|
82
103
|
useTinkerableLink
|
|
83
104
|
});
|
|
84
105
|
//# sourceMappingURL=routing.cjs.map
|
package/dist/routing.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/routing.tsx"],"sourcesContent":["import { use, useContext } from 'react';\n\nimport { sendMessage } from './sandboxUtils';\nimport { NavigationState, TinkerableContext } from './TinkerableContext';\nimport { RoutingRule, RoutingSpec } from './RoutingSpec';\nimport { constructUrl, isAbsolutePath, parseTarget } from './urlUtils';\nimport { joinPaths } from './pathUtils';\n\nexport type AppliedRoutingRule = {\n routingRule: RoutingRule,\n pathParameters?: Record<string, string>;\n}\n\nexport const useTinkerableLink = (newSandboxLocation: string) => {\n const { outerHref, navigationState: navigation } = use(TinkerableContext);\n let newNavigationState = parseTarget(newSandboxLocation, navigation);\n if (!isAbsolutePath(newSandboxLocation)) {\n newNavigationState.sandboxPath = joinPaths(navigation.sandboxPath, newSandboxLocation)\n } else {\n newNavigationState.sandboxPath = newSandboxLocation\n }\n return constructUrl(outerHref, newNavigationState);\n}\n\nexport const applyRoutingRule = (routingSpec:RoutingSpec, navigationState: NavigationState): AppliedRoutingRule | undefined => {\n const { sandboxPath } = navigationState;\n for (const routingRule of routingSpec.routes) {\n
|
|
1
|
+
{"version":3,"sources":["../src/routing.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { use, useContext } from 'react';\n\nimport { sendMessage } from './sandboxUtils';\nimport { NavigationState, TinkerableContext } from './TinkerableContext';\nimport { RouteParams, RoutingRule, RoutingSpec } from './RoutingSpec';\nimport { matchRoute } from './routeMatch';\nimport { constructUrl, isAbsolutePath, parseTarget } from './urlUtils';\nimport { joinPaths } from './pathUtils';\n\n/** The result of matching a path: the winning {@link RoutingRule} plus its captured params. */\nexport type AppliedRoutingRule = {\n routingRule: RoutingRule,\n pathParameters?: Record<string, string>;\n}\n\n/** Build the full outer href for an in-app target (absolute `sandboxPath` or a\n * path relative to the current route), e.g. for an `href` attribute. */\nexport const useTinkerableLink = (newSandboxLocation: string) => {\n const { outerHref, navigationState: navigation } = use(TinkerableContext);\n let newNavigationState = parseTarget(newSandboxLocation, navigation);\n if (!isAbsolutePath(newSandboxLocation)) {\n newNavigationState.sandboxPath = joinPaths(navigation.sandboxPath, newSandboxLocation)\n } else {\n newNavigationState.sandboxPath = newSandboxLocation\n }\n return constructUrl(outerHref, newNavigationState);\n}\n\n/** Find the first rule in `routingSpec` whose pattern matches the current\n * `sandboxPath`, returning it with the captured params (or `undefined`). */\nexport const applyRoutingRule = (routingSpec:RoutingSpec, navigationState: NavigationState): AppliedRoutingRule | undefined => {\n const { sandboxPath } = navigationState;\n for (const routingRule of routingSpec.routes) {\n const pathParameters = matchRoute(routingRule.pattern, sandboxPath);\n if (pathParameters) {\n return { routingRule, pathParameters };\n }\n }\n return undefined;\n}\n\n/** Render a matched rule, passing params to a `component` and falling back to `element`/`reactNode`. */\nexport const renderRoute = (routingRule: RoutingRule, params: RouteParams): ReactNode => {\n if (routingRule.component) {\n const Component = routingRule.component;\n return <Component params={params} />;\n }\n return routingRule.element ?? routingRule.reactNode ?? null;\n};\n\n/** Render the route matched for the current location (set up by `boot`'s route table). */\nexport const Router = () => {\n const context = useContext(TinkerableContext);\n const {navigationState: {routingRule, pathParameters}} = context;\n if (!routingRule) {\n // TODO: better error\n throw new Error(`No route registered for path ${context.navigationState.sandboxPath}!`);\n }\n\n return renderRoute(routingRule, pathParameters ?? {});\n};\n\n/** Read the current route's matched params (`:name` segments and the `*` wildcard). */\nexport const useRouteParams = <T extends RouteParams = RouteParams>(): T =>\n (use(TinkerableContext).navigationState.pathParameters ?? {}) as T;\n\n/**\n * Read the current route: the matched rule's `name`, its `params`, the app-owned\n * `sandboxPath`, and the read-only platform prefix fields (`mode`, `provider`,\n * `namespace`, `repository`, `ref`) — e.g. to tell `/edit` from `/present`.\n */\nexport const useRoute = () => {\n const { navigationState } = use(TinkerableContext);\n const { routingRule, pathParameters, sandboxPath, mode, provider, namespace, repository, ref } = navigationState;\n return {\n name: routingRule?.name,\n params: (pathParameters ?? {}) as RouteParams,\n sandboxPath,\n mode,\n provider,\n namespace,\n repository,\n ref,\n };\n};\n\n\n/**\n * Navigate within the app. Messages the host to update the URL; the host then\n * pushes the new href back, which drives the actual route change.\n */\nexport const navigate = (target: string) => {\n console.log(`[Sandbox] Navigating to ${target}`)\n sendMessage('urlchange', {\n url: target,\n back: false,\n forward: false,\n });\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8CW;AA7CX,mBAAgC;AAEhC,0BAA4B;AAC5B,+BAAmD;AAEnD,wBAA2B;AAC3B,sBAA0D;AAC1D,uBAA0B;AAUnB,MAAM,oBAAoB,CAAC,uBAA+B;AAC/D,QAAM,EAAE,WAAW,iBAAiB,WAAW,QAAI,kBAAI,0CAAiB;AACxE,MAAI,yBAAqB,6BAAY,oBAAoB,UAAU;AACnE,MAAI,KAAC,gCAAe,kBAAkB,GAAG;AACvC,uBAAmB,kBAAc,4BAAU,WAAW,aAAa,kBAAkB;AAAA,EACvF,OAAO;AACL,uBAAmB,cAAc;AAAA,EACnC;AACA,aAAO,8BAAa,WAAW,kBAAkB;AACnD;AAIO,MAAM,mBAAmB,CAAC,aAAyB,oBAAqE;AAC7H,QAAM,EAAE,YAAY,IAAI;AACxB,aAAW,eAAe,YAAY,QAAQ;AAC5C,UAAM,qBAAiB,8BAAW,YAAY,SAAS,WAAW;AAClE,QAAI,gBAAgB;AAClB,aAAO,EAAE,aAAa,eAAe;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AAGO,MAAM,cAAc,CAAC,aAA0B,WAAmC;AACvF,MAAI,YAAY,WAAW;AACzB,UAAM,YAAY,YAAY;AAC9B,WAAO,4CAAC,aAAU,QAAgB;AAAA,EACpC;AACA,SAAO,YAAY,WAAW,YAAY,aAAa;AACzD;AAGO,MAAM,SAAS,MAAM;AAC1B,QAAM,cAAU,yBAAW,0CAAiB;AAC5C,QAAM,EAAC,iBAAiB,EAAC,aAAa,eAAc,EAAC,IAAI;AACzD,MAAI,CAAC,aAAa;AAEhB,UAAM,IAAI,MAAM,gCAAgC,QAAQ,gBAAgB,WAAW,GAAG;AAAA,EACxF;AAEA,SAAO,YAAY,aAAa,kBAAkB,CAAC,CAAC;AACtD;AAGO,MAAM,iBAAiB,UAC3B,kBAAI,0CAAiB,EAAE,gBAAgB,kBAAkB,CAAC;AAOtD,MAAM,WAAW,MAAM;AAC5B,QAAM,EAAE,gBAAgB,QAAI,kBAAI,0CAAiB;AACjD,QAAM,EAAE,aAAa,gBAAgB,aAAa,MAAM,UAAU,WAAW,YAAY,IAAI,IAAI;AACjG,SAAO;AAAA,IACL,MAAM,aAAa;AAAA,IACnB,QAAS,kBAAkB,CAAC;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAOO,MAAM,WAAW,CAAC,WAAmB;AAC1C,UAAQ,IAAI,2BAA2B,MAAM,EAAE;AAC/C,uCAAY,aAAa;AAAA,IACvB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,EACX,CAAC;AACH;","names":[]}
|
package/dist/routing.d.cts
CHANGED
|
@@ -1,15 +1,44 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
2
|
import { NavigationState } from './TinkerableContext.cjs';
|
|
3
|
-
import { RoutingRule, RoutingSpec } from './RoutingSpec.cjs';
|
|
3
|
+
import { RoutingRule, RoutingSpec, RouteParams } from './RoutingSpec.cjs';
|
|
4
4
|
import './sandboxTypes.cjs';
|
|
5
5
|
|
|
6
|
+
/** The result of matching a path: the winning {@link RoutingRule} plus its captured params. */
|
|
6
7
|
type AppliedRoutingRule = {
|
|
7
8
|
routingRule: RoutingRule;
|
|
8
9
|
pathParameters?: Record<string, string>;
|
|
9
10
|
};
|
|
11
|
+
/** Build the full outer href for an in-app target (absolute `sandboxPath` or a
|
|
12
|
+
* path relative to the current route), e.g. for an `href` attribute. */
|
|
10
13
|
declare const useTinkerableLink: (newSandboxLocation: string) => string;
|
|
14
|
+
/** Find the first rule in `routingSpec` whose pattern matches the current
|
|
15
|
+
* `sandboxPath`, returning it with the captured params (or `undefined`). */
|
|
11
16
|
declare const applyRoutingRule: (routingSpec: RoutingSpec, navigationState: NavigationState) => AppliedRoutingRule | undefined;
|
|
12
|
-
|
|
17
|
+
/** Render a matched rule, passing params to a `component` and falling back to `element`/`reactNode`. */
|
|
18
|
+
declare const renderRoute: (routingRule: RoutingRule, params: RouteParams) => ReactNode;
|
|
19
|
+
/** Render the route matched for the current location (set up by `boot`'s route table). */
|
|
20
|
+
declare const Router: () => ReactNode;
|
|
21
|
+
/** Read the current route's matched params (`:name` segments and the `*` wildcard). */
|
|
22
|
+
declare const useRouteParams: <T extends RouteParams = RouteParams>() => T;
|
|
23
|
+
/**
|
|
24
|
+
* Read the current route: the matched rule's `name`, its `params`, the app-owned
|
|
25
|
+
* `sandboxPath`, and the read-only platform prefix fields (`mode`, `provider`,
|
|
26
|
+
* `namespace`, `repository`, `ref`) — e.g. to tell `/edit` from `/present`.
|
|
27
|
+
*/
|
|
28
|
+
declare const useRoute: () => {
|
|
29
|
+
name: string | undefined;
|
|
30
|
+
params: RouteParams;
|
|
31
|
+
sandboxPath: string;
|
|
32
|
+
mode: string;
|
|
33
|
+
provider: string;
|
|
34
|
+
namespace: string;
|
|
35
|
+
repository: string;
|
|
36
|
+
ref: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Navigate within the app. Messages the host to update the URL; the host then
|
|
40
|
+
* pushes the new href back, which drives the actual route change.
|
|
41
|
+
*/
|
|
13
42
|
declare const navigate: (target: string) => void;
|
|
14
43
|
|
|
15
|
-
export { type AppliedRoutingRule, Router, applyRoutingRule, navigate, useTinkerableLink };
|
|
44
|
+
export { type AppliedRoutingRule, Router, applyRoutingRule, navigate, renderRoute, useRoute, useRouteParams, useTinkerableLink };
|
package/dist/routing.d.ts
CHANGED
|
@@ -1,15 +1,44 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
2
|
import { NavigationState } from './TinkerableContext.js';
|
|
3
|
-
import { RoutingRule, RoutingSpec } from './RoutingSpec.js';
|
|
3
|
+
import { RoutingRule, RoutingSpec, RouteParams } from './RoutingSpec.js';
|
|
4
4
|
import './sandboxTypes.js';
|
|
5
5
|
|
|
6
|
+
/** The result of matching a path: the winning {@link RoutingRule} plus its captured params. */
|
|
6
7
|
type AppliedRoutingRule = {
|
|
7
8
|
routingRule: RoutingRule;
|
|
8
9
|
pathParameters?: Record<string, string>;
|
|
9
10
|
};
|
|
11
|
+
/** Build the full outer href for an in-app target (absolute `sandboxPath` or a
|
|
12
|
+
* path relative to the current route), e.g. for an `href` attribute. */
|
|
10
13
|
declare const useTinkerableLink: (newSandboxLocation: string) => string;
|
|
14
|
+
/** Find the first rule in `routingSpec` whose pattern matches the current
|
|
15
|
+
* `sandboxPath`, returning it with the captured params (or `undefined`). */
|
|
11
16
|
declare const applyRoutingRule: (routingSpec: RoutingSpec, navigationState: NavigationState) => AppliedRoutingRule | undefined;
|
|
12
|
-
|
|
17
|
+
/** Render a matched rule, passing params to a `component` and falling back to `element`/`reactNode`. */
|
|
18
|
+
declare const renderRoute: (routingRule: RoutingRule, params: RouteParams) => ReactNode;
|
|
19
|
+
/** Render the route matched for the current location (set up by `boot`'s route table). */
|
|
20
|
+
declare const Router: () => ReactNode;
|
|
21
|
+
/** Read the current route's matched params (`:name` segments and the `*` wildcard). */
|
|
22
|
+
declare const useRouteParams: <T extends RouteParams = RouteParams>() => T;
|
|
23
|
+
/**
|
|
24
|
+
* Read the current route: the matched rule's `name`, its `params`, the app-owned
|
|
25
|
+
* `sandboxPath`, and the read-only platform prefix fields (`mode`, `provider`,
|
|
26
|
+
* `namespace`, `repository`, `ref`) — e.g. to tell `/edit` from `/present`.
|
|
27
|
+
*/
|
|
28
|
+
declare const useRoute: () => {
|
|
29
|
+
name: string | undefined;
|
|
30
|
+
params: RouteParams;
|
|
31
|
+
sandboxPath: string;
|
|
32
|
+
mode: string;
|
|
33
|
+
provider: string;
|
|
34
|
+
namespace: string;
|
|
35
|
+
repository: string;
|
|
36
|
+
ref: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Navigate within the app. Messages the host to update the URL; the host then
|
|
40
|
+
* pushes the new href back, which drives the actual route change.
|
|
41
|
+
*/
|
|
13
42
|
declare const navigate: (target: string) => void;
|
|
14
43
|
|
|
15
|
-
export { type AppliedRoutingRule, Router, applyRoutingRule, navigate, useTinkerableLink };
|
|
44
|
+
export { type AppliedRoutingRule, Router, applyRoutingRule, navigate, renderRoute, useRoute, useRouteParams, useTinkerableLink };
|