@immediately-run/sdk 0.15.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 +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +2 -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 +3 -1
- package/dist/mounts.d.ts +3 -1
- 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.map +1 -1
- package/dist/onFsChange.d.cts +1 -0
- package/dist/onFsChange.d.ts +1 -0
- package/dist/onFsChange.js.map +1 -1
- 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":"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 };
|
package/dist/routing.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
1
2
|
import { use, useContext } from "react";
|
|
2
3
|
import { sendMessage } from "./sandboxUtils";
|
|
3
4
|
import { TinkerableContext } from "./TinkerableContext";
|
|
5
|
+
import { matchRoute } from "./routeMatch";
|
|
4
6
|
import { constructUrl, isAbsolutePath, parseTarget } from "./urlUtils";
|
|
5
7
|
import { joinPaths } from "./pathUtils";
|
|
6
8
|
const useTinkerableLink = (newSandboxLocation) => {
|
|
@@ -16,29 +18,42 @@ const useTinkerableLink = (newSandboxLocation) => {
|
|
|
16
18
|
const applyRoutingRule = (routingSpec, navigationState) => {
|
|
17
19
|
const { sandboxPath } = navigationState;
|
|
18
20
|
for (const routingRule of routingSpec.routes) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
} else {
|
|
24
|
-
const match = sandboxPath.match(routingRule.pattern);
|
|
25
|
-
if (routingRule.pattern.test(sandboxPath)) {
|
|
26
|
-
return {
|
|
27
|
-
routingRule,
|
|
28
|
-
pathParameters: match?.groups
|
|
29
|
-
};
|
|
30
|
-
}
|
|
21
|
+
const pathParameters = matchRoute(routingRule.pattern, sandboxPath);
|
|
22
|
+
if (pathParameters) {
|
|
23
|
+
return { routingRule, pathParameters };
|
|
31
24
|
}
|
|
32
25
|
}
|
|
33
26
|
return void 0;
|
|
34
27
|
};
|
|
28
|
+
const renderRoute = (routingRule, params) => {
|
|
29
|
+
if (routingRule.component) {
|
|
30
|
+
const Component = routingRule.component;
|
|
31
|
+
return /* @__PURE__ */ jsx(Component, { params });
|
|
32
|
+
}
|
|
33
|
+
return routingRule.element ?? routingRule.reactNode ?? null;
|
|
34
|
+
};
|
|
35
35
|
const Router = () => {
|
|
36
36
|
const context = useContext(TinkerableContext);
|
|
37
|
-
const { navigationState: { routingRule } } = context;
|
|
37
|
+
const { navigationState: { routingRule, pathParameters } } = context;
|
|
38
38
|
if (!routingRule) {
|
|
39
39
|
throw new Error(`No route registered for path ${context.navigationState.sandboxPath}!`);
|
|
40
40
|
}
|
|
41
|
-
return routingRule
|
|
41
|
+
return renderRoute(routingRule, pathParameters ?? {});
|
|
42
|
+
};
|
|
43
|
+
const useRouteParams = () => use(TinkerableContext).navigationState.pathParameters ?? {};
|
|
44
|
+
const useRoute = () => {
|
|
45
|
+
const { navigationState } = use(TinkerableContext);
|
|
46
|
+
const { routingRule, pathParameters, sandboxPath, mode, provider, namespace, repository, ref } = navigationState;
|
|
47
|
+
return {
|
|
48
|
+
name: routingRule?.name,
|
|
49
|
+
params: pathParameters ?? {},
|
|
50
|
+
sandboxPath,
|
|
51
|
+
mode,
|
|
52
|
+
provider,
|
|
53
|
+
namespace,
|
|
54
|
+
repository,
|
|
55
|
+
ref
|
|
56
|
+
};
|
|
42
57
|
};
|
|
43
58
|
const navigate = (target) => {
|
|
44
59
|
console.log(`[Sandbox] Navigating to ${target}`);
|
|
@@ -52,6 +67,9 @@ export {
|
|
|
52
67
|
Router,
|
|
53
68
|
applyRoutingRule,
|
|
54
69
|
navigate,
|
|
70
|
+
renderRoute,
|
|
71
|
+
useRoute,
|
|
72
|
+
useRouteParams,
|
|
55
73
|
useTinkerableLink
|
|
56
74
|
};
|
|
57
75
|
//# sourceMappingURL=routing.js.map
|
package/dist/routing.js.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":"AA8CW;AA7CX,SAAS,KAAK,kBAAkB;AAEhC,SAAS,mBAAmB;AAC5B,SAA0B,yBAAyB;AAEnD,SAAS,kBAAkB;AAC3B,SAAS,cAAc,gBAAgB,mBAAmB;AAC1D,SAAS,iBAAiB;AAUnB,MAAM,oBAAoB,CAAC,uBAA+B;AAC/D,QAAM,EAAE,WAAW,iBAAiB,WAAW,IAAI,IAAI,iBAAiB;AACxE,MAAI,qBAAqB,YAAY,oBAAoB,UAAU;AACnE,MAAI,CAAC,eAAe,kBAAkB,GAAG;AACvC,uBAAmB,cAAc,UAAU,WAAW,aAAa,kBAAkB;AAAA,EACvF,OAAO;AACL,uBAAmB,cAAc;AAAA,EACnC;AACA,SAAO,aAAa,WAAW,kBAAkB;AACnD;AAIO,MAAM,mBAAmB,CAAC,aAAyB,oBAAqE;AAC7H,QAAM,EAAE,YAAY,IAAI;AACxB,aAAW,eAAe,YAAY,QAAQ;AAC5C,UAAM,iBAAiB,WAAW,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,oBAAC,aAAU,QAAgB;AAAA,EACpC;AACA,SAAO,YAAY,WAAW,YAAY,aAAa;AACzD;AAGO,MAAM,SAAS,MAAM;AAC1B,QAAM,UAAU,WAAW,iBAAiB;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,MAC3B,IAAI,iBAAiB,EAAE,gBAAgB,kBAAkB,CAAC;AAOtD,MAAM,WAAW,MAAM;AAC5B,QAAM,EAAE,gBAAgB,IAAI,IAAI,iBAAiB;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,cAAY,aAAa;AAAA,IACvB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,EACX,CAAC;AACH;","names":[]}
|
package/dist/runtime.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["// Runtime discovery + version handshake (SDK_PACKAGING_SPEC §4/§6).\n//\n// Today the SDK reaches the host through the INJECTED sandbox services\n// (`module.evaluation.module.bundler.*`, see sandboxUtils). The packaging migration\n// makes the SDK an app-pinnable npm dependency that finds the runtime through a\n// stable, versioned global the sandbox publishes BEFORE evaluating app code:\n//\n// globalThis.__immediatelyRun__ = { runtimeVersion, protocolVersion, transport }\n//\n// Phase 1 (behind a flag, injection still active): the SDK can READ that global\n// when present (else fall back to injection), and ANNOUNCE its own version +\n// protocol so the host can record + version-check it (§6/T45). The transport itself\n// is unchanged here — this only wires the discovery + handshake fields so the check\n// exists when app-pinned versions become real.\nimport { sendMessage, addListener } from './sandboxUtils';\nimport { SDK_VERSION } from './version';\n\n// `getHostRuntime` + `ImmediatelyRunGlobal` live in the leaf `hostRuntime` module\n// (imports nothing) and are re-exported here for a stable public API. This breaks\n// the sandboxUtils↔runtime import cycle: sandboxUtils reads `getHostRuntime` from\n// the leaf, while runtime still imports sandboxUtils for the handshake — one\n// direction only, no cycle.\nexport { getHostRuntime } from './hostRuntime';\nexport type { ImmediatelyRunGlobal } from './hostRuntime';\n\n/** The wire protocol (postMessage envelope / channels / methods) THIS SDK speaks.\n * Additive-only (§9); bump only for a backwards-compatible extension. */\nexport const SDK_PROTOCOL_VERSION = '1.0.0';\n\n/** This SDK's package version, baked from package.json at build (SP2-6,\n * `scripts/gen-version.mjs`). Re-exported so the public surface is unchanged\n * (`@immediately-run/sdk` → `SDK_VERSION`); imported above for the handshake. */\nexport { SDK_VERSION };\n\n/** This SDK's handshake payload — the version + protocol the host records + checks\n * against `HOST_PROTOCOL_VERSION` (§6/T45). */\nexport interface SdkHandshake {\n sdkVersion: string;\n protocolVersion: string;\n}\nexport const sdkHandshake = (): SdkHandshake => ({\n sdkVersion: SDK_VERSION,\n protocolVersion: SDK_PROTOCOL_VERSION,\n});\n\n/**\n * Announce this SDK's version to the host (§6). Sends `sdk-handshake` eagerly\n * (best-effort — the host may already be listening) AND replies to a host\n * `request-handshake` (the robust path, mirroring the other `request-*` pulls).\n * Idempotent; safe to call more than once. Returns an unsubscribe fn.\n */\nexport function announceHandshake(): () => void {\n const send = () => {\n try {\n sendMessage('sdk-handshake', sdkHandshake() as unknown as Record<string, unknown>);\n } catch {\n /* transport not ready yet — the request-handshake reply covers it */\n }\n };\n send();\n return addListener('request-handshake', send);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,0BAAyC;AACzC,qBAA4B;AAO5B,yBAA+B;AAKxB,MAAM,uBAAuB;
|
|
1
|
+
{"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["// Runtime discovery + version handshake (SDK_PACKAGING_SPEC §4/§6).\n//\n// Today the SDK reaches the host through the INJECTED sandbox services\n// (`module.evaluation.module.bundler.*`, see sandboxUtils). The packaging migration\n// makes the SDK an app-pinnable npm dependency that finds the runtime through a\n// stable, versioned global the sandbox publishes BEFORE evaluating app code:\n//\n// globalThis.__immediatelyRun__ = { runtimeVersion, protocolVersion, transport }\n//\n// Phase 1 (behind a flag, injection still active): the SDK can READ that global\n// when present (else fall back to injection), and ANNOUNCE its own version +\n// protocol so the host can record + version-check it (§6/T45). The transport itself\n// is unchanged here — this only wires the discovery + handshake fields so the check\n// exists when app-pinned versions become real.\nimport { sendMessage, addListener } from './sandboxUtils';\nimport { SDK_VERSION } from './version';\n\n// `getHostRuntime` + `ImmediatelyRunGlobal` live in the leaf `hostRuntime` module\n// (imports nothing) and are re-exported here for a stable public API. This breaks\n// the sandboxUtils↔runtime import cycle: sandboxUtils reads `getHostRuntime` from\n// the leaf, while runtime still imports sandboxUtils for the handshake — one\n// direction only, no cycle.\nexport { getHostRuntime } from './hostRuntime';\nexport type { ImmediatelyRunGlobal } from './hostRuntime';\n\n/** The wire protocol (postMessage envelope / channels / methods) THIS SDK speaks.\n * Additive-only (§9); bump only for a backwards-compatible extension. */\nexport const SDK_PROTOCOL_VERSION = '1.0.0';\n\n/** This SDK's package version, baked from package.json at build (SP2-6,\n * `scripts/gen-version.mjs`). Re-exported so the public surface is unchanged\n * (`@immediately-run/sdk` → `SDK_VERSION`); imported above for the handshake. */\nexport { SDK_VERSION };\n\n/** This SDK's handshake payload — the version + protocol the host records + checks\n * against `HOST_PROTOCOL_VERSION` (§6/T45). */\nexport interface SdkHandshake {\n sdkVersion: string;\n protocolVersion: string;\n}\n/** Build this SDK's handshake payload (version + protocol) for the host to record. */\nexport const sdkHandshake = (): SdkHandshake => ({\n sdkVersion: SDK_VERSION,\n protocolVersion: SDK_PROTOCOL_VERSION,\n});\n\n/**\n * Announce this SDK's version to the host (§6). Sends `sdk-handshake` eagerly\n * (best-effort — the host may already be listening) AND replies to a host\n * `request-handshake` (the robust path, mirroring the other `request-*` pulls).\n * Idempotent; safe to call more than once. Returns an unsubscribe fn.\n */\nexport function announceHandshake(): () => void {\n const send = () => {\n try {\n sendMessage('sdk-handshake', sdkHandshake() as unknown as Record<string, unknown>);\n } catch {\n /* transport not ready yet — the request-handshake reply covers it */\n }\n };\n send();\n return addListener('request-handshake', send);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,0BAAyC;AACzC,qBAA4B;AAO5B,yBAA+B;AAKxB,MAAM,uBAAuB;AAc7B,MAAM,eAAe,OAAqB;AAAA,EAC/C,YAAY;AAAA,EACZ,iBAAiB;AACnB;AAQO,SAAS,oBAAgC;AAC9C,QAAM,OAAO,MAAM;AACjB,QAAI;AACF,2CAAY,iBAAiB,aAAa,CAAuC;AAAA,IACnF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,OAAK;AACL,aAAO,iCAAY,qBAAqB,IAAI;AAC9C;","names":[]}
|