@ai-matrx/content-ir-react 0.9.3 → 0.11.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/CHANGELOG.md +70 -0
- package/dist/db-component.d.cts +8 -8
- package/dist/db-component.d.ts +8 -8
- package/dist/directive-host-DAb7Keg8.d.cts +63 -0
- package/dist/directive-host-DAb7Keg8.d.ts +63 -0
- package/dist/directives.cjs +382 -0
- package/dist/directives.cjs.map +1 -0
- package/dist/directives.d.cts +70 -0
- package/dist/directives.d.ts +70 -0
- package/dist/directives.js +369 -0
- package/dist/directives.js.map +1 -0
- package/dist/{host-types-CDcLW0TD.d.cts → host-types-C8jR31Wk.d.cts} +9 -0
- package/dist/{host-types-CpItjcYh.d.ts → host-types-IUEDmso7.d.ts} +9 -0
- package/dist/host.cjs +14 -4
- package/dist/host.cjs.map +1 -1
- package/dist/host.d.cts +5 -4
- package/dist/host.d.ts +5 -4
- package/dist/host.js +14 -4
- package/dist/host.js.map +1 -1
- package/dist/index.cjs +456 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -21
- package/dist/index.d.ts +85 -21
- package/dist/index.js +454 -31
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +401 -46
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +11 -10
- package/dist/react.d.ts +11 -10
- package/dist/react.js +398 -29
- package/dist/react.js.map +1 -1
- package/dist/resolver.cjs +19 -1
- package/dist/resolver.cjs.map +1 -1
- package/dist/resolver.d.cts +3 -0
- package/dist/resolver.d.ts +3 -0
- package/dist/resolver.js +19 -1
- package/dist/resolver.js.map +1 -1
- package/dist/{route-QL2Vqf2e.d.cts → route-Bu85A41G.d.cts} +83 -3
- package/dist/{route-PUuEuEug.d.ts → route-DoFoDKBn.d.ts} +83 -3
- package/dist/route.cjs +14 -0
- package/dist/route.cjs.map +1 -1
- package/dist/route.d.cts +1 -1
- package/dist/route.d.ts +1 -1
- package/dist/route.js +15 -0
- package/dist/route.js.map +1 -1
- package/package.json +15 -2
package/dist/resolver.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../resolver.ts","../host/error-report.ts","../resolver/component-resolver.ts"],"sourcesContent":["/**\n * `@ai-matrx/content-ir-react/resolver` — (kind, platform, role) → component:\n * the `kind_component` row vocabulary and the `ComponentResolver` (compiled\n * floor, DB override, granular per-kind repaint, cold-fetch dedupe,\n * warm/refresh with loud recovery). Pure TS — no React runtime; deliberately\n * unstamped so server graphs can import it.\n */\nexport * from \"./resolver/component-resolution.types\";\nexport * from \"./resolver/component-resolver\";\n","/**\n * THE SCREAM SEAM.\n *\n * Every recovery path in this package is loud: a defective component row, a\n * failed resolver load, a kind whose component throws on a provisional value.\n * The package refuses to own where those screams land — matrx-frontend routes\n * them to the Error Inspector, the dashboard to its own log, the extension to\n * its background page. Each host binds one function.\n *\n * A host that binds a no-op has chosen silence, which is a defect in itself;\n * {@link consoleErrorReporter} is the honest default.\n */\n\nexport interface ContentIrErrorReport {\n /** Always `\"content-ir\"` — hosts key their capture stores on it. */\n source: \"content-ir\";\n message: string;\n name?: string;\n stack?: string;\n /** Free-form grouping key (the host's `relation`), e.g. `\"partial-kind\"`. */\n relation?: string;\n raw?: unknown;\n}\n\nexport type ContentIrErrorReporter = (report: ContentIrErrorReport) => void;\n\n/** The default when a host binds nothing: never silent. */\nexport const consoleErrorReporter: ContentIrErrorReporter = (report) => {\n console.error(`[content-ir] ${report.message}`, report.raw ?? \"\");\n};\n","/**\n * The Shape System component RESOLVER (rulings R1 + R6): (kind, platform,\n * role) → component, with two tiers:\n *\n * - eager: a COMPILED bootstrap the host ships in its bundle — the\n * trusted-at-boot floor, available at import so the render seam can gate\n * synchronously from the first streamed byte.\n * - warm: one `content_ir.kind_component` list fetch per app session\n * (`ensureWarm`). A DB row for a (kind, platform, role) OVERRIDES the\n * compiled entry once warm; the compiled floor keeps answering until then\n * (and forever, on DB failure).\n *\n * Resolution is SYNCHRONOUS (the render seam calls it per block); the only\n * async work is the loading, off the render path.\n *\n * WHAT MOVED AND WHY. This class was matrx-frontend's `ComponentRegistry`. It\n * is a large part of why a second UI could not render a kind: every tier rule,\n * every repaint counter, every dedupe latch lived in one app. Nothing about it\n * is Next.js, Redux, or Supabase — the only host-specific parts were the two\n * loaders and the error sink, which are now constructor arguments.\n */\n\nimport type { ContentIrErrorReporter } from \"../host/error-report\";\nimport { consoleErrorReporter } from \"../host/error-report\";\nimport type {\n ComponentResolution,\n ComponentResolutionSource,\n ComponentRole,\n KindComponentRow,\n SystemComponentEntry,\n} from \"./component-resolution.types\";\n\nexport interface ComponentResolverOptions {\n /**\n * The compiled bootstrap, as a THUNK resolved on first use: a host's system\n * component table is often mid-initialization when this module evaluates\n * (matrx-frontend's registry cluster has a deliberate import cycle).\n */\n compiledEntries?: () => SystemComponentEntry[];\n /** The warm/refresh list load — every `kind_component` row the user may see. */\n loadAll?: () => Promise<KindComponentRow[]>;\n /** The eager single-kind load fired the moment a kind is identified mid-stream. */\n loadForKind?: (kind: string, platform: string) => Promise<KindComponentRow[]>;\n /** Where recovery screams land. Defaults to `console.error`, never silence. */\n reportError?: ContentIrErrorReporter;\n /** Clock seam — the refresh rate limiter. Defaults to `Date.now`. */\n now?: () => number;\n}\n\n/** Unit separator — a character no kind slug, platform, or role can contain. */\nconst KEY_SEPARATOR = String.fromCharCode(1);\n\nfunction keyOf(kind: string, platform: string, role: string): string {\n return `${kind}${KEY_SEPARATOR}${platform}${KEY_SEPARATOR}${role}`;\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** Error-shaped fields, spread-safe under `exactOptionalPropertyTypes`. */\nfunction errorFields(error: unknown): { name?: string; stack?: string } {\n if (!(error instanceof Error)) return {};\n return {\n name: error.name,\n ...(error.stack === undefined ? {} : { stack: error.stack }),\n };\n}\n\nexport class ComponentResolver implements ComponentResolutionSource {\n private compiled: Map<string, SystemComponentEntry> | null = null;\n private readonly db = new Map<string, KindComponentRow>();\n private warmPromise: Promise<void> | null = null;\n private warmFailureLogged = false;\n /**\n * True once a list load has RUN TO COMPLETION (success or failure), or once\n * any rows have been ingested by any tier. Until then `resolve()` answering\n * null means \"I have not looked yet\" — never \"no such component\". See THE\n * COLD-VERDICT RULE on `ComponentResolutionSource.hasSettled`.\n */\n private settled = false;\n /**\n * When the last successful wholesale refresh landed, or null for \"never\".\n * Explicitly nullable rather than 0: with a host-supplied clock that starts\n * near zero, a `0` sentinel makes the FIRST refresh look rate-limited and\n * silently skip.\n */\n private lastRefreshAt: number | null = null;\n private refreshPromise: Promise<void> | null = null;\n private readonly listeners = new Set<() => void>();\n /**\n * Cold single-kind fetch dedupe (streaming eager path). In-flight is keyed\n * by (kind, platform) — the fetch unit; misses are keyed by (kind, platform,\n * role) so a miss on web/output never suppresses other roles, and CLEARED on\n * every wholesale refresh (a component created mid-session becomes eagerly\n * fetchable again — misses are cheap to re-verify).\n */\n private readonly coldInFlight = new Set<string>();\n private readonly coldMisses = new Set<string>();\n /** Monotonic db-tier version — the repaint hook's snapshot key. */\n private version = 0;\n /** Per-kind versions + listeners (granular repaint) + wholesale epoch. */\n private readonly kindVersions = new Map<string, number>();\n private readonly kindListeners = new Map<string, Set<() => void>>();\n private epoch = 0;\n\n private readonly reportError: ContentIrErrorReporter;\n private readonly now: () => number;\n\n private readonly options: ComponentResolverOptions;\n\n // Explicit field, not a parameter property: consumers compile this source\n // directly, and a strict host (the dashboard) sets `erasableSyntaxOnly`,\n // under which parameter properties are a hard error.\n constructor(options: ComponentResolverOptions = {}) {\n this.options = options;\n this.reportError = options.reportError ?? consoleErrorReporter;\n this.now = options.now ?? (() => Date.now());\n }\n\n private compiledMap(): Map<string, SystemComponentEntry> {\n if (!this.compiled) {\n this.compiled = new Map();\n for (const entry of this.options.compiledEntries?.() ?? []) {\n this.compiled.set(keyOf(entry.kind, entry.platform, entry.role), entry);\n }\n }\n return this.compiled;\n }\n\n /**\n * Synchronous resolve — the render seam's per-block call. DB override first\n * (once warm), compiled floor second, null for unknown.\n */\n resolve(\n kind: string,\n platform: string,\n role: ComponentRole,\n ): ComponentResolution | null {\n const key = keyOf(kind, platform, role);\n\n const dbRow = this.db.get(key);\n if (dbRow) {\n return {\n componentKey: dbRow.componentKey,\n source: dbRow.source,\n config: dbRow.config,\n isActive: dbRow.isActive,\n resolvedBy: \"db\",\n componentSource: dbRow.componentSource,\n propsTransform: dbRow.propsTransform,\n hasComponentSource:\n dbRow.hasComponentSource ??\n Boolean(dbRow.componentSource && dbRow.componentSource.trim()),\n pinnedKindVersion: dbRow.pinnedKindVersion,\n updatedAt: dbRow.updatedAt,\n createdBy: dbRow.createdBy,\n };\n }\n\n const compiledEntry = this.compiledMap().get(key);\n if (compiledEntry) {\n return {\n componentKey: compiledEntry.componentKey,\n source: compiledEntry.source,\n config: compiledEntry.config,\n isActive: true, // trusted at bootstrap (R6)\n resolvedBy: \"compiled\",\n componentSource: null,\n propsTransform: null,\n hasComponentSource: false,\n pinnedKindVersion: null,\n updatedAt: null,\n createdBy: null,\n };\n }\n\n return null;\n }\n\n /** R6 floor check: compiled-bootstrap membership = always render-trusted. */\n hasCompiled(kind: string, platform: string, role: ComponentRole): boolean {\n return this.compiledMap().has(keyOf(kind, platform, role));\n }\n\n /**\n * Pure ingest — the warm landing point and the unit-test seam. First row per\n * key wins: rows arrive is_default-first / sort_order-asc from the source.\n */\n ingestDbRows(rows: readonly KindComponentRow[]): void {\n const changedKinds = new Set<string>();\n for (const row of rows) {\n const key = keyOf(row.kind, row.platform, row.role);\n if (!this.db.has(key)) {\n this.db.set(key, row);\n changedKinds.add(row.kind);\n }\n }\n // Warm/cold ingest is a resolver-tier change the render seam must see — a\n // db component landing AFTER a region finalized re-runs the route via the\n // repaint hook. Per-kind bumps keep the repaint granular.\n if (changedKinds.size > 0) {\n for (const kind of changedKinds) this.bumpKind(kind);\n this.notifyChanged();\n }\n }\n\n /**\n * Refresh landing point: REPLACE the db tier wholesale (same\n * first-row-per-key contract as {@link ingestDbRows}) so edits, deletions,\n * and is_active flips all take effect. Always notifies.\n */\n replaceDbRows(rows: readonly KindComponentRow[]): void {\n // A wholesale replace IS a completed list load — the refresh landing\n // point, and the seam tests use to declare \"the list loaded\". Settling\n // here keeps THE COLD-VERDICT RULE from holding blocks forever in any\n // host that warms through refresh() rather than ensureWarm().\n this.settled = true;\n this.db.clear();\n // Wholesale invalidation: every recorded miss is stale (a component\n // created mid-session must become eagerly fetchable again).\n this.coldMisses.clear();\n for (const row of rows) {\n const key = keyOf(row.kind, row.platform, row.role);\n if (!this.db.has(key)) this.db.set(key, row);\n }\n // A wholesale replace ALWAYS notifies (deletions/flips count too), and\n // bumps the epoch so EVERY per-kind subscriber re-snapshots.\n this.epoch += 1;\n for (const set of this.kindListeners.values()) {\n for (const listener of set) listener();\n }\n this.notifyChanged();\n }\n\n getVersion(): number {\n return this.version;\n }\n\n getKindVersion(kind: string): number {\n return this.epoch + (this.kindVersions.get(kind) ?? 0);\n }\n\n subscribeKind(kind: string, listener: () => void): () => void {\n const set = this.kindListeners.get(kind) ?? new Set<() => void>();\n set.add(listener);\n this.kindListeners.set(kind, set);\n return () => {\n set.delete(listener);\n if (set.size === 0) this.kindListeners.delete(kind);\n };\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private bumpKind(kind: string): void {\n this.kindVersions.set(kind, (this.kindVersions.get(kind) ?? 0) + 1);\n const listeners = this.kindListeners.get(kind);\n if (listeners) for (const listener of listeners) listener();\n }\n\n private notifyChanged(): void {\n this.version += 1;\n for (const listener of this.listeners) listener();\n }\n\n /**\n * A warm metadata shell is resolved enough for routing, but not for the\n * host's db-component renderer. Its body must still come from the cold\n * single-kind loader.\n */\n private needsColdFetch(\n kind: string,\n platform: string,\n role: ComponentRole,\n ): boolean {\n const resolution = this.resolve(kind, platform, role);\n return (\n resolution === null ||\n (resolution.resolvedBy === \"db\" &&\n resolution.hasComponentSource &&\n resolution.componentSource === null)\n );\n }\n\n /**\n * Cold rows are the authoritative heavy form of warm metadata shells. Keep\n * the same first-row-per-key winner contract, but replace an existing shell\n * so `componentSource` / `propsTransform` can land and repaint the kind.\n */\n private ingestColdRows(rows: readonly KindComponentRow[]): void {\n const winners = new Map<string, KindComponentRow>();\n for (const row of rows) {\n const key = keyOf(row.kind, row.platform, row.role);\n if (!winners.has(key)) winners.set(key, row);\n }\n if (winners.size === 0) return;\n\n const changedKinds = new Set<string>();\n for (const [key, row] of winners) {\n this.db.set(key, row);\n changedKinds.add(row.kind);\n }\n for (const kind of changedKinds) this.bumpKind(kind);\n this.notifyChanged();\n }\n\n /**\n * The eager lightweight single-kind fetch (streaming path): the moment a\n * cloud kind is identified mid-stream, pull ONLY that kind's resolver rows\n * and ingest them so {@link resolve} can answer before — or shortly after —\n * the region completes. Deduped in-flight and by known-miss. Fire-and-forget;\n * failures are loud (the warm list remains the backstop).\n */\n requestComponent(kind: string, platform: string, role: ComponentRole): void {\n const loadForKind = this.options.loadForKind;\n if (!loadForKind) return;\n if (!this.needsColdFetch(kind, platform, role)) return;\n const missKey = keyOf(kind, platform, role);\n const flightKey = `${kind}${KEY_SEPARATOR}${platform}`;\n if (this.coldInFlight.has(flightKey) || this.coldMisses.has(missKey)) {\n return;\n }\n this.coldInFlight.add(flightKey);\n\n void (async () => {\n try {\n this.ingestColdRows(await loadForKind(kind, platform));\n // Record the miss for exactly the requested (kind, platform, role) —\n // rows may exist for OTHER roles on this platform; those must not be\n // suppressed, and this one must not be re-fetched until a refresh.\n if (this.needsColdFetch(kind, platform, role)) {\n this.coldMisses.add(missKey);\n }\n } catch (error) {\n this.reportError({\n source: \"content-ir\",\n message: `component-resolver cold fetch failed for \"${kind}\": ${describe(error)}`,\n ...errorFields(error),\n raw: error,\n });\n } finally {\n this.coldInFlight.delete(flightKey);\n }\n })();\n }\n\n /**\n * Refresh-on-view: re-fetch the warm list and REPLACE the db tier, so an\n * edited `source='db'` component (its `updated_at` bump re-keys the host's\n * compile cache) renders fresh on the next view. Deduped in-flight and\n * rate-limited by `maxAgeMs` (default 10s) — mounting several previews costs\n * one fetch. Server-side edits do NOT push to open clients; the contract is\n * refresh-on-view via this call.\n */\n refresh(maxAgeMs = 10_000): Promise<void> {\n const loadAll = this.options.loadAll;\n if (!loadAll) return Promise.resolve();\n if (this.refreshPromise) return this.refreshPromise;\n if (\n this.lastRefreshAt !== null &&\n this.now() - this.lastRefreshAt < maxAgeMs\n ) {\n return Promise.resolve();\n }\n // Any refresh intent invalidates recorded misses immediately (also cleared\n // in replaceDbRows when the fetch lands) — cheap to re-verify.\n this.coldMisses.clear();\n\n this.refreshPromise = loadAll()\n .then((rows) => {\n this.lastRefreshAt = this.now();\n this.replaceDbRows(rows);\n })\n .catch((error: unknown) => {\n // Loud recovery: the current tier keeps answering; a refresh failure is\n // a real defect (same posture as the warm loader).\n this.reportError({\n source: \"content-ir\",\n message: `component-resolver refresh failed (current resolver tier still serving): ${describe(error)}`,\n ...errorFields(error),\n raw: error,\n });\n })\n .finally(() => {\n this.refreshPromise = null;\n });\n return this.refreshPromise;\n }\n\n /** One list fetch per app session; failed loads retry on the next call. */\n ensureWarm(): Promise<void> {\n const loadAll = this.options.loadAll;\n if (!loadAll) return Promise.resolve();\n if (!this.warmPromise) {\n this.warmPromise = loadAll()\n .then((rows) => {\n this.ingestDbRows(rows);\n this.settled = true;\n })\n .catch((error: unknown) => {\n // Loud recovery: the compiled floor keeps rendering, but a warm\n // failure is a real defect — one console scream (first failure) plus\n // a structured report per attempt, then retryable.\n const message = `component-resolver warm load failed (compiled bootstrap still serving): ${describe(error)}`;\n if (!this.warmFailureLogged) {\n this.warmFailureLogged = true;\n console.error(`[content-ir] ${message}`);\n }\n this.reportError({\n source: \"content-ir\",\n message,\n ...errorFields(error),\n raw: error,\n });\n // A FAILED load still settles: the compiled floor becomes the\n // honest answer, and holding every block in limbo forever would be\n // a worse failure than a visible one. The retry stays available.\n this.settled = true;\n this.warmPromise = null;\n });\n }\n return this.warmPromise;\n }\n\n /** @see ComponentResolutionSource.hasSettled */\n hasSettled(): boolean {\n // No list loader configured = nothing to wait for: the compiled floor IS\n // the complete truth for this resolver, so it is settled from birth. (A\n // host that never wired a loader must not have every block held forever.)\n if (!this.options.loadAll) return true;\n return this.settled || this.db.size > 0;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../resolver.ts","../host/error-report.ts","../resolver/component-resolver.ts"],"sourcesContent":["/**\n * `@ai-matrx/content-ir-react/resolver` — (kind, platform, role) → component:\n * the `kind_component` row vocabulary and the `ComponentResolver` (compiled\n * floor, DB override, granular per-kind repaint, cold-fetch dedupe,\n * warm/refresh with loud recovery). Pure TS — no React runtime; deliberately\n * unstamped so server graphs can import it.\n */\nexport * from \"./resolver/component-resolution.types\";\nexport * from \"./resolver/component-resolver\";\n","/**\n * THE SCREAM SEAM.\n *\n * Every recovery path in this package is loud: a defective component row, a\n * failed resolver load, a kind whose component throws on a provisional value.\n * The package refuses to own where those screams land — matrx-frontend routes\n * them to the Error Inspector, the dashboard to its own log, the extension to\n * its background page. Each host binds one function.\n *\n * A host that binds a no-op has chosen silence, which is a defect in itself;\n * {@link consoleErrorReporter} is the honest default.\n */\n\nexport interface ContentIrErrorReport {\n /** Always `\"content-ir\"` — hosts key their capture stores on it. */\n source: \"content-ir\";\n message: string;\n name?: string;\n stack?: string;\n /** Free-form grouping key (the host's `relation`), e.g. `\"partial-kind\"`. */\n relation?: string;\n raw?: unknown;\n}\n\nexport type ContentIrErrorReporter = (report: ContentIrErrorReport) => void;\n\n/** The default when a host binds nothing: never silent. */\nexport const consoleErrorReporter: ContentIrErrorReporter = (report) => {\n console.error(`[content-ir] ${report.message}`, report.raw ?? \"\");\n};\n","/**\n * The Shape System component RESOLVER (rulings R1 + R6): (kind, platform,\n * role) → component, with two tiers:\n *\n * - eager: a COMPILED bootstrap the host ships in its bundle — the\n * trusted-at-boot floor, available at import so the render seam can gate\n * synchronously from the first streamed byte.\n * - warm: one `content_ir.kind_component` list fetch per app session\n * (`ensureWarm`). A DB row for a (kind, platform, role) OVERRIDES the\n * compiled entry once warm; the compiled floor keeps answering until then\n * (and forever, on DB failure).\n *\n * Resolution is SYNCHRONOUS (the render seam calls it per block); the only\n * async work is the loading, off the render path.\n *\n * WHAT MOVED AND WHY. This class was matrx-frontend's `ComponentRegistry`. It\n * is a large part of why a second UI could not render a kind: every tier rule,\n * every repaint counter, every dedupe latch lived in one app. Nothing about it\n * is Next.js, Redux, or Supabase — the only host-specific parts were the two\n * loaders and the error sink, which are now constructor arguments.\n */\n\nimport type { ContentIrErrorReporter } from \"../host/error-report\";\nimport { consoleErrorReporter } from \"../host/error-report\";\nimport type {\n ComponentResolution,\n ComponentResolutionSource,\n ComponentRole,\n KindComponentRow,\n SystemComponentEntry,\n} from \"./component-resolution.types\";\n\nexport interface ComponentResolverOptions {\n /**\n * The compiled bootstrap, as a THUNK resolved on first use: a host's system\n * component table is often mid-initialization when this module evaluates\n * (matrx-frontend's registry cluster has a deliberate import cycle).\n */\n compiledEntries?: () => SystemComponentEntry[];\n /** The warm/refresh list load — every `kind_component` row the user may see. */\n loadAll?: () => Promise<KindComponentRow[]>;\n /** The eager single-kind load fired the moment a kind is identified mid-stream. */\n loadForKind?: (kind: string, platform: string) => Promise<KindComponentRow[]>;\n /** Where recovery screams land. Defaults to `console.error`, never silence. */\n reportError?: ContentIrErrorReporter;\n /** Clock seam — the refresh rate limiter. Defaults to `Date.now`. */\n now?: () => number;\n}\n\n/** Unit separator — a character no kind slug, platform, or role can contain. */\nconst KEY_SEPARATOR = String.fromCharCode(1);\n\nfunction keyOf(kind: string, platform: string, role: string): string {\n return `${kind}${KEY_SEPARATOR}${platform}${KEY_SEPARATOR}${role}`;\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** Error-shaped fields, spread-safe under `exactOptionalPropertyTypes`. */\nfunction errorFields(error: unknown): { name?: string; stack?: string } {\n if (!(error instanceof Error)) return {};\n return {\n name: error.name,\n ...(error.stack === undefined ? {} : { stack: error.stack }),\n };\n}\n\n/**\n * Warm-load retry backoff. Sized for the auth-hydration race: a fresh tab's\n * first list load can run before the Supabase session restores and fail with\n * a permission error; the session is typically live within a second or two,\n * so the first retry usually completes the load. After the last delay the\n * resolver settles on whatever it has — see the failure branch in ensureWarm.\n */\nconst WARM_RETRY_DELAYS_MS: readonly number[] = [1_000, 5_000, 15_000];\n\nexport class ComponentResolver implements ComponentResolutionSource {\n private compiled: Map<string, SystemComponentEntry> | null = null;\n private readonly db = new Map<string, KindComponentRow>();\n private warmPromise: Promise<void> | null = null;\n private warmFailureLogged = false;\n /** Consecutive warm-load failures — indexes WARM_RETRY_DELAYS_MS. */\n private warmFailures = 0;\n private warmRetryTimer: ReturnType<typeof setTimeout> | null = null;\n /**\n * True once a list load has RUN TO COMPLETION (success or failure), or once\n * any rows have been ingested by any tier. Until then `resolve()` answering\n * null means \"I have not looked yet\" — never \"no such component\". See THE\n * COLD-VERDICT RULE on `ComponentResolutionSource.hasSettled`.\n */\n private settled = false;\n /**\n * When the last successful wholesale refresh landed, or null for \"never\".\n * Explicitly nullable rather than 0: with a host-supplied clock that starts\n * near zero, a `0` sentinel makes the FIRST refresh look rate-limited and\n * silently skip.\n */\n private lastRefreshAt: number | null = null;\n private refreshPromise: Promise<void> | null = null;\n private readonly listeners = new Set<() => void>();\n /**\n * Cold single-kind fetch dedupe (streaming eager path). In-flight is keyed\n * by (kind, platform) — the fetch unit; misses are keyed by (kind, platform,\n * role) so a miss on web/output never suppresses other roles, and CLEARED on\n * every wholesale refresh (a component created mid-session becomes eagerly\n * fetchable again — misses are cheap to re-verify).\n */\n private readonly coldInFlight = new Set<string>();\n private readonly coldMisses = new Set<string>();\n /** Monotonic db-tier version — the repaint hook's snapshot key. */\n private version = 0;\n /** Per-kind versions + listeners (granular repaint) + wholesale epoch. */\n private readonly kindVersions = new Map<string, number>();\n private readonly kindListeners = new Map<string, Set<() => void>>();\n private epoch = 0;\n\n private readonly reportError: ContentIrErrorReporter;\n private readonly now: () => number;\n\n private readonly options: ComponentResolverOptions;\n\n // Explicit field, not a parameter property: consumers compile this source\n // directly, and a strict host (the dashboard) sets `erasableSyntaxOnly`,\n // under which parameter properties are a hard error.\n constructor(options: ComponentResolverOptions = {}) {\n this.options = options;\n this.reportError = options.reportError ?? consoleErrorReporter;\n this.now = options.now ?? (() => Date.now());\n }\n\n private compiledMap(): Map<string, SystemComponentEntry> {\n if (!this.compiled) {\n this.compiled = new Map();\n for (const entry of this.options.compiledEntries?.() ?? []) {\n this.compiled.set(keyOf(entry.kind, entry.platform, entry.role), entry);\n }\n }\n return this.compiled;\n }\n\n /**\n * Synchronous resolve — the render seam's per-block call. DB override first\n * (once warm), compiled floor second, null for unknown.\n */\n resolve(\n kind: string,\n platform: string,\n role: ComponentRole,\n ): ComponentResolution | null {\n const key = keyOf(kind, platform, role);\n\n const dbRow = this.db.get(key);\n if (dbRow) {\n return {\n componentKey: dbRow.componentKey,\n source: dbRow.source,\n config: dbRow.config,\n isActive: dbRow.isActive,\n resolvedBy: \"db\",\n componentSource: dbRow.componentSource,\n propsTransform: dbRow.propsTransform,\n hasComponentSource:\n dbRow.hasComponentSource ??\n Boolean(dbRow.componentSource && dbRow.componentSource.trim()),\n pinnedKindVersion: dbRow.pinnedKindVersion,\n updatedAt: dbRow.updatedAt,\n createdBy: dbRow.createdBy,\n };\n }\n\n const compiledEntry = this.compiledMap().get(key);\n if (compiledEntry) {\n return {\n componentKey: compiledEntry.componentKey,\n source: compiledEntry.source,\n config: compiledEntry.config,\n isActive: true, // trusted at bootstrap (R6)\n resolvedBy: \"compiled\",\n componentSource: null,\n propsTransform: null,\n hasComponentSource: false,\n pinnedKindVersion: null,\n updatedAt: null,\n createdBy: null,\n };\n }\n\n return null;\n }\n\n /** R6 floor check: compiled-bootstrap membership = always render-trusted. */\n hasCompiled(kind: string, platform: string, role: ComponentRole): boolean {\n return this.compiledMap().has(keyOf(kind, platform, role));\n }\n\n /**\n * Pure ingest — the warm landing point and the unit-test seam. First row per\n * key wins: rows arrive is_default-first / sort_order-asc from the source.\n */\n ingestDbRows(rows: readonly KindComponentRow[]): void {\n const changedKinds = new Set<string>();\n for (const row of rows) {\n const key = keyOf(row.kind, row.platform, row.role);\n if (!this.db.has(key)) {\n this.db.set(key, row);\n changedKinds.add(row.kind);\n }\n }\n // Warm/cold ingest is a resolver-tier change the render seam must see — a\n // db component landing AFTER a region finalized re-runs the route via the\n // repaint hook. Per-kind bumps keep the repaint granular.\n if (changedKinds.size > 0) {\n for (const kind of changedKinds) this.bumpKind(kind);\n this.notifyChanged();\n }\n }\n\n /**\n * Refresh landing point: REPLACE the db tier wholesale (same\n * first-row-per-key contract as {@link ingestDbRows}) so edits, deletions,\n * and is_active flips all take effect. Always notifies.\n */\n replaceDbRows(rows: readonly KindComponentRow[]): void {\n // A wholesale replace IS a completed list load — the refresh landing\n // point, and the seam tests use to declare \"the list loaded\". Settling\n // here keeps THE COLD-VERDICT RULE from holding blocks forever in any\n // host that warms through refresh() rather than ensureWarm().\n this.settled = true;\n this.db.clear();\n // Wholesale invalidation: every recorded miss is stale (a component\n // created mid-session must become eagerly fetchable again).\n this.coldMisses.clear();\n for (const row of rows) {\n const key = keyOf(row.kind, row.platform, row.role);\n if (!this.db.has(key)) this.db.set(key, row);\n }\n // A wholesale replace ALWAYS notifies (deletions/flips count too), and\n // bumps the epoch so EVERY per-kind subscriber re-snapshots.\n this.epoch += 1;\n for (const set of this.kindListeners.values()) {\n for (const listener of set) listener();\n }\n this.notifyChanged();\n }\n\n getVersion(): number {\n return this.version;\n }\n\n getKindVersion(kind: string): number {\n return this.epoch + (this.kindVersions.get(kind) ?? 0);\n }\n\n subscribeKind(kind: string, listener: () => void): () => void {\n const set = this.kindListeners.get(kind) ?? new Set<() => void>();\n set.add(listener);\n this.kindListeners.set(kind, set);\n return () => {\n set.delete(listener);\n if (set.size === 0) this.kindListeners.delete(kind);\n };\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private bumpKind(kind: string): void {\n this.kindVersions.set(kind, (this.kindVersions.get(kind) ?? 0) + 1);\n const listeners = this.kindListeners.get(kind);\n if (listeners) for (const listener of listeners) listener();\n }\n\n private notifyChanged(): void {\n this.version += 1;\n for (const listener of this.listeners) listener();\n }\n\n /**\n * A warm metadata shell is resolved enough for routing, but not for the\n * host's db-component renderer. Its body must still come from the cold\n * single-kind loader.\n */\n private needsColdFetch(\n kind: string,\n platform: string,\n role: ComponentRole,\n ): boolean {\n const resolution = this.resolve(kind, platform, role);\n return (\n resolution === null ||\n (resolution.resolvedBy === \"db\" &&\n resolution.hasComponentSource &&\n resolution.componentSource === null)\n );\n }\n\n /**\n * Cold rows are the authoritative heavy form of warm metadata shells. Keep\n * the same first-row-per-key winner contract, but replace an existing shell\n * so `componentSource` / `propsTransform` can land and repaint the kind.\n */\n private ingestColdRows(rows: readonly KindComponentRow[]): void {\n const winners = new Map<string, KindComponentRow>();\n for (const row of rows) {\n const key = keyOf(row.kind, row.platform, row.role);\n if (!winners.has(key)) winners.set(key, row);\n }\n if (winners.size === 0) return;\n\n const changedKinds = new Set<string>();\n for (const [key, row] of winners) {\n this.db.set(key, row);\n changedKinds.add(row.kind);\n }\n for (const kind of changedKinds) this.bumpKind(kind);\n this.notifyChanged();\n }\n\n /**\n * The eager lightweight single-kind fetch (streaming path): the moment a\n * cloud kind is identified mid-stream, pull ONLY that kind's resolver rows\n * and ingest them so {@link resolve} can answer before — or shortly after —\n * the region completes. Deduped in-flight and by known-miss. Fire-and-forget;\n * failures are loud (the warm list remains the backstop).\n */\n requestComponent(kind: string, platform: string, role: ComponentRole): void {\n const loadForKind = this.options.loadForKind;\n if (!loadForKind) return;\n if (!this.needsColdFetch(kind, platform, role)) return;\n const missKey = keyOf(kind, platform, role);\n const flightKey = `${kind}${KEY_SEPARATOR}${platform}`;\n if (this.coldInFlight.has(flightKey) || this.coldMisses.has(missKey)) {\n return;\n }\n this.coldInFlight.add(flightKey);\n\n void (async () => {\n try {\n this.ingestColdRows(await loadForKind(kind, platform));\n // Record the miss for exactly the requested (kind, platform, role) —\n // rows may exist for OTHER roles on this platform; those must not be\n // suppressed, and this one must not be re-fetched until a refresh.\n if (this.needsColdFetch(kind, platform, role)) {\n this.coldMisses.add(missKey);\n }\n } catch (error) {\n this.reportError({\n source: \"content-ir\",\n message: `component-resolver cold fetch failed for \"${kind}\": ${describe(error)}`,\n ...errorFields(error),\n raw: error,\n });\n } finally {\n this.coldInFlight.delete(flightKey);\n }\n })();\n }\n\n /**\n * Refresh-on-view: re-fetch the warm list and REPLACE the db tier, so an\n * edited `source='db'` component (its `updated_at` bump re-keys the host's\n * compile cache) renders fresh on the next view. Deduped in-flight and\n * rate-limited by `maxAgeMs` (default 10s) — mounting several previews costs\n * one fetch. Server-side edits do NOT push to open clients; the contract is\n * refresh-on-view via this call.\n */\n refresh(maxAgeMs = 10_000): Promise<void> {\n const loadAll = this.options.loadAll;\n if (!loadAll) return Promise.resolve();\n if (this.refreshPromise) return this.refreshPromise;\n if (\n this.lastRefreshAt !== null &&\n this.now() - this.lastRefreshAt < maxAgeMs\n ) {\n return Promise.resolve();\n }\n // Any refresh intent invalidates recorded misses immediately (also cleared\n // in replaceDbRows when the fetch lands) — cheap to re-verify.\n this.coldMisses.clear();\n\n this.refreshPromise = loadAll()\n .then((rows) => {\n this.lastRefreshAt = this.now();\n this.replaceDbRows(rows);\n })\n .catch((error: unknown) => {\n // Loud recovery: the current tier keeps answering; a refresh failure is\n // a real defect (same posture as the warm loader).\n this.reportError({\n source: \"content-ir\",\n message: `component-resolver refresh failed (current resolver tier still serving): ${describe(error)}`,\n ...errorFields(error),\n raw: error,\n });\n })\n .finally(() => {\n this.refreshPromise = null;\n });\n return this.refreshPromise;\n }\n\n /** One list fetch per app session; failed loads retry on the next call. */\n ensureWarm(): Promise<void> {\n const loadAll = this.options.loadAll;\n if (!loadAll) return Promise.resolve();\n if (!this.warmPromise) {\n this.warmPromise = loadAll()\n .then((rows) => {\n this.ingestDbRows(rows);\n this.settled = true;\n this.warmFailures = 0;\n if (this.warmRetryTimer) {\n clearTimeout(this.warmRetryTimer);\n this.warmRetryTimer = null;\n }\n })\n .catch((error: unknown) => {\n // Loud recovery: the compiled floor keeps rendering, but a warm\n // failure is a real defect — one console scream (first failure) plus\n // a structured report per attempt, then retryable.\n const message = `component-resolver warm load failed (compiled bootstrap still serving): ${describe(error)}`;\n if (!this.warmFailureLogged) {\n this.warmFailureLogged = true;\n console.error(`[content-ir] ${message}`);\n }\n this.reportError({\n source: \"content-ir\",\n message,\n ...errorFields(error),\n raw: error,\n });\n this.warmPromise = null;\n // 🚨 A FAILED load is NOT truth (2026-08-31). The 0.9.0 posture —\n // \"a failed load still settles\" — turned the auth-hydration race\n // into a lie: a fresh tab fires this load BEFORE the Supabase\n // session hydrates, the read runs as `anon`, RLS answers\n // 42501 permission denied, and settling on that failure declared an\n // EMPTY resolver to be the loaded state of the world. Every kind on\n // the page then routed to the generic floor as \"unregistered\" —\n // while the same rows were one authenticated retry away. So: retry\n // with backoff, and settle only when retries are exhausted (the\n // backend really is down; the generic floor + the per-attempt\n // scream above are then the honest, announced stand-in).\n this.warmFailures += 1;\n const delay =\n WARM_RETRY_DELAYS_MS[this.warmFailures - 1] ?? null;\n if (delay === null) {\n this.settled = true;\n } else if (!this.warmRetryTimer) {\n this.warmRetryTimer = setTimeout(() => {\n this.warmRetryTimer = null;\n void this.ensureWarm();\n }, delay);\n }\n });\n }\n return this.warmPromise;\n }\n\n /** @see ComponentResolutionSource.hasSettled */\n hasSettled(): boolean {\n // No list loader configured = nothing to wait for: the compiled floor IS\n // the complete truth for this resolver, so it is settled from birth. (A\n // host that never wired a loader must not have every block held forever.)\n if (!this.options.loadAll) return true;\n return this.settled || this.db.size > 0;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/resolver.d.cts
CHANGED
|
@@ -44,6 +44,9 @@ declare class ComponentResolver implements ComponentResolutionSource {
|
|
|
44
44
|
private readonly db;
|
|
45
45
|
private warmPromise;
|
|
46
46
|
private warmFailureLogged;
|
|
47
|
+
/** Consecutive warm-load failures — indexes WARM_RETRY_DELAYS_MS. */
|
|
48
|
+
private warmFailures;
|
|
49
|
+
private warmRetryTimer;
|
|
47
50
|
/**
|
|
48
51
|
* True once a list load has RUN TO COMPLETION (success or failure), or once
|
|
49
52
|
* any rows have been ingested by any tier. Until then `resolve()` answering
|
package/dist/resolver.d.ts
CHANGED
|
@@ -44,6 +44,9 @@ declare class ComponentResolver implements ComponentResolutionSource {
|
|
|
44
44
|
private readonly db;
|
|
45
45
|
private warmPromise;
|
|
46
46
|
private warmFailureLogged;
|
|
47
|
+
/** Consecutive warm-load failures — indexes WARM_RETRY_DELAYS_MS. */
|
|
48
|
+
private warmFailures;
|
|
49
|
+
private warmRetryTimer;
|
|
47
50
|
/**
|
|
48
51
|
* True once a list load has RUN TO COMPLETION (success or failure), or once
|
|
49
52
|
* any rows have been ingested by any tier. Until then `resolve()` answering
|
package/dist/resolver.js
CHANGED
|
@@ -18,11 +18,15 @@ function errorFields(error) {
|
|
|
18
18
|
...error.stack === void 0 ? {} : { stack: error.stack }
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
+
var WARM_RETRY_DELAYS_MS = [1e3, 5e3, 15e3];
|
|
21
22
|
var ComponentResolver = class {
|
|
22
23
|
compiled = null;
|
|
23
24
|
db = /* @__PURE__ */ new Map();
|
|
24
25
|
warmPromise = null;
|
|
25
26
|
warmFailureLogged = false;
|
|
27
|
+
/** Consecutive warm-load failures — indexes WARM_RETRY_DELAYS_MS. */
|
|
28
|
+
warmFailures = 0;
|
|
29
|
+
warmRetryTimer = null;
|
|
26
30
|
/**
|
|
27
31
|
* True once a list load has RUN TO COMPLETION (success or failure), or once
|
|
28
32
|
* any rows have been ingested by any tier. Until then `resolve()` answering
|
|
@@ -289,6 +293,11 @@ var ComponentResolver = class {
|
|
|
289
293
|
this.warmPromise = loadAll().then((rows) => {
|
|
290
294
|
this.ingestDbRows(rows);
|
|
291
295
|
this.settled = true;
|
|
296
|
+
this.warmFailures = 0;
|
|
297
|
+
if (this.warmRetryTimer) {
|
|
298
|
+
clearTimeout(this.warmRetryTimer);
|
|
299
|
+
this.warmRetryTimer = null;
|
|
300
|
+
}
|
|
292
301
|
}).catch((error) => {
|
|
293
302
|
const message = `component-resolver warm load failed (compiled bootstrap still serving): ${describe(error)}`;
|
|
294
303
|
if (!this.warmFailureLogged) {
|
|
@@ -301,8 +310,17 @@ var ComponentResolver = class {
|
|
|
301
310
|
...errorFields(error),
|
|
302
311
|
raw: error
|
|
303
312
|
});
|
|
304
|
-
this.settled = true;
|
|
305
313
|
this.warmPromise = null;
|
|
314
|
+
this.warmFailures += 1;
|
|
315
|
+
const delay = WARM_RETRY_DELAYS_MS[this.warmFailures - 1] ?? null;
|
|
316
|
+
if (delay === null) {
|
|
317
|
+
this.settled = true;
|
|
318
|
+
} else if (!this.warmRetryTimer) {
|
|
319
|
+
this.warmRetryTimer = setTimeout(() => {
|
|
320
|
+
this.warmRetryTimer = null;
|
|
321
|
+
void this.ensureWarm();
|
|
322
|
+
}, delay);
|
|
323
|
+
}
|
|
306
324
|
});
|
|
307
325
|
}
|
|
308
326
|
return this.warmPromise;
|
package/dist/resolver.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../host/error-report.ts","../resolver/component-resolver.ts"],"sourcesContent":["/**\n * THE SCREAM SEAM.\n *\n * Every recovery path in this package is loud: a defective component row, a\n * failed resolver load, a kind whose component throws on a provisional value.\n * The package refuses to own where those screams land — matrx-frontend routes\n * them to the Error Inspector, the dashboard to its own log, the extension to\n * its background page. Each host binds one function.\n *\n * A host that binds a no-op has chosen silence, which is a defect in itself;\n * {@link consoleErrorReporter} is the honest default.\n */\n\nexport interface ContentIrErrorReport {\n /** Always `\"content-ir\"` — hosts key their capture stores on it. */\n source: \"content-ir\";\n message: string;\n name?: string;\n stack?: string;\n /** Free-form grouping key (the host's `relation`), e.g. `\"partial-kind\"`. */\n relation?: string;\n raw?: unknown;\n}\n\nexport type ContentIrErrorReporter = (report: ContentIrErrorReport) => void;\n\n/** The default when a host binds nothing: never silent. */\nexport const consoleErrorReporter: ContentIrErrorReporter = (report) => {\n console.error(`[content-ir] ${report.message}`, report.raw ?? \"\");\n};\n","/**\n * The Shape System component RESOLVER (rulings R1 + R6): (kind, platform,\n * role) → component, with two tiers:\n *\n * - eager: a COMPILED bootstrap the host ships in its bundle — the\n * trusted-at-boot floor, available at import so the render seam can gate\n * synchronously from the first streamed byte.\n * - warm: one `content_ir.kind_component` list fetch per app session\n * (`ensureWarm`). A DB row for a (kind, platform, role) OVERRIDES the\n * compiled entry once warm; the compiled floor keeps answering until then\n * (and forever, on DB failure).\n *\n * Resolution is SYNCHRONOUS (the render seam calls it per block); the only\n * async work is the loading, off the render path.\n *\n * WHAT MOVED AND WHY. This class was matrx-frontend's `ComponentRegistry`. It\n * is a large part of why a second UI could not render a kind: every tier rule,\n * every repaint counter, every dedupe latch lived in one app. Nothing about it\n * is Next.js, Redux, or Supabase — the only host-specific parts were the two\n * loaders and the error sink, which are now constructor arguments.\n */\n\nimport type { ContentIrErrorReporter } from \"../host/error-report\";\nimport { consoleErrorReporter } from \"../host/error-report\";\nimport type {\n ComponentResolution,\n ComponentResolutionSource,\n ComponentRole,\n KindComponentRow,\n SystemComponentEntry,\n} from \"./component-resolution.types\";\n\nexport interface ComponentResolverOptions {\n /**\n * The compiled bootstrap, as a THUNK resolved on first use: a host's system\n * component table is often mid-initialization when this module evaluates\n * (matrx-frontend's registry cluster has a deliberate import cycle).\n */\n compiledEntries?: () => SystemComponentEntry[];\n /** The warm/refresh list load — every `kind_component` row the user may see. */\n loadAll?: () => Promise<KindComponentRow[]>;\n /** The eager single-kind load fired the moment a kind is identified mid-stream. */\n loadForKind?: (kind: string, platform: string) => Promise<KindComponentRow[]>;\n /** Where recovery screams land. Defaults to `console.error`, never silence. */\n reportError?: ContentIrErrorReporter;\n /** Clock seam — the refresh rate limiter. Defaults to `Date.now`. */\n now?: () => number;\n}\n\n/** Unit separator — a character no kind slug, platform, or role can contain. */\nconst KEY_SEPARATOR = String.fromCharCode(1);\n\nfunction keyOf(kind: string, platform: string, role: string): string {\n return `${kind}${KEY_SEPARATOR}${platform}${KEY_SEPARATOR}${role}`;\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** Error-shaped fields, spread-safe under `exactOptionalPropertyTypes`. */\nfunction errorFields(error: unknown): { name?: string; stack?: string } {\n if (!(error instanceof Error)) return {};\n return {\n name: error.name,\n ...(error.stack === undefined ? {} : { stack: error.stack }),\n };\n}\n\nexport class ComponentResolver implements ComponentResolutionSource {\n private compiled: Map<string, SystemComponentEntry> | null = null;\n private readonly db = new Map<string, KindComponentRow>();\n private warmPromise: Promise<void> | null = null;\n private warmFailureLogged = false;\n /**\n * True once a list load has RUN TO COMPLETION (success or failure), or once\n * any rows have been ingested by any tier. Until then `resolve()` answering\n * null means \"I have not looked yet\" — never \"no such component\". See THE\n * COLD-VERDICT RULE on `ComponentResolutionSource.hasSettled`.\n */\n private settled = false;\n /**\n * When the last successful wholesale refresh landed, or null for \"never\".\n * Explicitly nullable rather than 0: with a host-supplied clock that starts\n * near zero, a `0` sentinel makes the FIRST refresh look rate-limited and\n * silently skip.\n */\n private lastRefreshAt: number | null = null;\n private refreshPromise: Promise<void> | null = null;\n private readonly listeners = new Set<() => void>();\n /**\n * Cold single-kind fetch dedupe (streaming eager path). In-flight is keyed\n * by (kind, platform) — the fetch unit; misses are keyed by (kind, platform,\n * role) so a miss on web/output never suppresses other roles, and CLEARED on\n * every wholesale refresh (a component created mid-session becomes eagerly\n * fetchable again — misses are cheap to re-verify).\n */\n private readonly coldInFlight = new Set<string>();\n private readonly coldMisses = new Set<string>();\n /** Monotonic db-tier version — the repaint hook's snapshot key. */\n private version = 0;\n /** Per-kind versions + listeners (granular repaint) + wholesale epoch. */\n private readonly kindVersions = new Map<string, number>();\n private readonly kindListeners = new Map<string, Set<() => void>>();\n private epoch = 0;\n\n private readonly reportError: ContentIrErrorReporter;\n private readonly now: () => number;\n\n private readonly options: ComponentResolverOptions;\n\n // Explicit field, not a parameter property: consumers compile this source\n // directly, and a strict host (the dashboard) sets `erasableSyntaxOnly`,\n // under which parameter properties are a hard error.\n constructor(options: ComponentResolverOptions = {}) {\n this.options = options;\n this.reportError = options.reportError ?? consoleErrorReporter;\n this.now = options.now ?? (() => Date.now());\n }\n\n private compiledMap(): Map<string, SystemComponentEntry> {\n if (!this.compiled) {\n this.compiled = new Map();\n for (const entry of this.options.compiledEntries?.() ?? []) {\n this.compiled.set(keyOf(entry.kind, entry.platform, entry.role), entry);\n }\n }\n return this.compiled;\n }\n\n /**\n * Synchronous resolve — the render seam's per-block call. DB override first\n * (once warm), compiled floor second, null for unknown.\n */\n resolve(\n kind: string,\n platform: string,\n role: ComponentRole,\n ): ComponentResolution | null {\n const key = keyOf(kind, platform, role);\n\n const dbRow = this.db.get(key);\n if (dbRow) {\n return {\n componentKey: dbRow.componentKey,\n source: dbRow.source,\n config: dbRow.config,\n isActive: dbRow.isActive,\n resolvedBy: \"db\",\n componentSource: dbRow.componentSource,\n propsTransform: dbRow.propsTransform,\n hasComponentSource:\n dbRow.hasComponentSource ??\n Boolean(dbRow.componentSource && dbRow.componentSource.trim()),\n pinnedKindVersion: dbRow.pinnedKindVersion,\n updatedAt: dbRow.updatedAt,\n createdBy: dbRow.createdBy,\n };\n }\n\n const compiledEntry = this.compiledMap().get(key);\n if (compiledEntry) {\n return {\n componentKey: compiledEntry.componentKey,\n source: compiledEntry.source,\n config: compiledEntry.config,\n isActive: true, // trusted at bootstrap (R6)\n resolvedBy: \"compiled\",\n componentSource: null,\n propsTransform: null,\n hasComponentSource: false,\n pinnedKindVersion: null,\n updatedAt: null,\n createdBy: null,\n };\n }\n\n return null;\n }\n\n /** R6 floor check: compiled-bootstrap membership = always render-trusted. */\n hasCompiled(kind: string, platform: string, role: ComponentRole): boolean {\n return this.compiledMap().has(keyOf(kind, platform, role));\n }\n\n /**\n * Pure ingest — the warm landing point and the unit-test seam. First row per\n * key wins: rows arrive is_default-first / sort_order-asc from the source.\n */\n ingestDbRows(rows: readonly KindComponentRow[]): void {\n const changedKinds = new Set<string>();\n for (const row of rows) {\n const key = keyOf(row.kind, row.platform, row.role);\n if (!this.db.has(key)) {\n this.db.set(key, row);\n changedKinds.add(row.kind);\n }\n }\n // Warm/cold ingest is a resolver-tier change the render seam must see — a\n // db component landing AFTER a region finalized re-runs the route via the\n // repaint hook. Per-kind bumps keep the repaint granular.\n if (changedKinds.size > 0) {\n for (const kind of changedKinds) this.bumpKind(kind);\n this.notifyChanged();\n }\n }\n\n /**\n * Refresh landing point: REPLACE the db tier wholesale (same\n * first-row-per-key contract as {@link ingestDbRows}) so edits, deletions,\n * and is_active flips all take effect. Always notifies.\n */\n replaceDbRows(rows: readonly KindComponentRow[]): void {\n // A wholesale replace IS a completed list load — the refresh landing\n // point, and the seam tests use to declare \"the list loaded\". Settling\n // here keeps THE COLD-VERDICT RULE from holding blocks forever in any\n // host that warms through refresh() rather than ensureWarm().\n this.settled = true;\n this.db.clear();\n // Wholesale invalidation: every recorded miss is stale (a component\n // created mid-session must become eagerly fetchable again).\n this.coldMisses.clear();\n for (const row of rows) {\n const key = keyOf(row.kind, row.platform, row.role);\n if (!this.db.has(key)) this.db.set(key, row);\n }\n // A wholesale replace ALWAYS notifies (deletions/flips count too), and\n // bumps the epoch so EVERY per-kind subscriber re-snapshots.\n this.epoch += 1;\n for (const set of this.kindListeners.values()) {\n for (const listener of set) listener();\n }\n this.notifyChanged();\n }\n\n getVersion(): number {\n return this.version;\n }\n\n getKindVersion(kind: string): number {\n return this.epoch + (this.kindVersions.get(kind) ?? 0);\n }\n\n subscribeKind(kind: string, listener: () => void): () => void {\n const set = this.kindListeners.get(kind) ?? new Set<() => void>();\n set.add(listener);\n this.kindListeners.set(kind, set);\n return () => {\n set.delete(listener);\n if (set.size === 0) this.kindListeners.delete(kind);\n };\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private bumpKind(kind: string): void {\n this.kindVersions.set(kind, (this.kindVersions.get(kind) ?? 0) + 1);\n const listeners = this.kindListeners.get(kind);\n if (listeners) for (const listener of listeners) listener();\n }\n\n private notifyChanged(): void {\n this.version += 1;\n for (const listener of this.listeners) listener();\n }\n\n /**\n * A warm metadata shell is resolved enough for routing, but not for the\n * host's db-component renderer. Its body must still come from the cold\n * single-kind loader.\n */\n private needsColdFetch(\n kind: string,\n platform: string,\n role: ComponentRole,\n ): boolean {\n const resolution = this.resolve(kind, platform, role);\n return (\n resolution === null ||\n (resolution.resolvedBy === \"db\" &&\n resolution.hasComponentSource &&\n resolution.componentSource === null)\n );\n }\n\n /**\n * Cold rows are the authoritative heavy form of warm metadata shells. Keep\n * the same first-row-per-key winner contract, but replace an existing shell\n * so `componentSource` / `propsTransform` can land and repaint the kind.\n */\n private ingestColdRows(rows: readonly KindComponentRow[]): void {\n const winners = new Map<string, KindComponentRow>();\n for (const row of rows) {\n const key = keyOf(row.kind, row.platform, row.role);\n if (!winners.has(key)) winners.set(key, row);\n }\n if (winners.size === 0) return;\n\n const changedKinds = new Set<string>();\n for (const [key, row] of winners) {\n this.db.set(key, row);\n changedKinds.add(row.kind);\n }\n for (const kind of changedKinds) this.bumpKind(kind);\n this.notifyChanged();\n }\n\n /**\n * The eager lightweight single-kind fetch (streaming path): the moment a\n * cloud kind is identified mid-stream, pull ONLY that kind's resolver rows\n * and ingest them so {@link resolve} can answer before — or shortly after —\n * the region completes. Deduped in-flight and by known-miss. Fire-and-forget;\n * failures are loud (the warm list remains the backstop).\n */\n requestComponent(kind: string, platform: string, role: ComponentRole): void {\n const loadForKind = this.options.loadForKind;\n if (!loadForKind) return;\n if (!this.needsColdFetch(kind, platform, role)) return;\n const missKey = keyOf(kind, platform, role);\n const flightKey = `${kind}${KEY_SEPARATOR}${platform}`;\n if (this.coldInFlight.has(flightKey) || this.coldMisses.has(missKey)) {\n return;\n }\n this.coldInFlight.add(flightKey);\n\n void (async () => {\n try {\n this.ingestColdRows(await loadForKind(kind, platform));\n // Record the miss for exactly the requested (kind, platform, role) —\n // rows may exist for OTHER roles on this platform; those must not be\n // suppressed, and this one must not be re-fetched until a refresh.\n if (this.needsColdFetch(kind, platform, role)) {\n this.coldMisses.add(missKey);\n }\n } catch (error) {\n this.reportError({\n source: \"content-ir\",\n message: `component-resolver cold fetch failed for \"${kind}\": ${describe(error)}`,\n ...errorFields(error),\n raw: error,\n });\n } finally {\n this.coldInFlight.delete(flightKey);\n }\n })();\n }\n\n /**\n * Refresh-on-view: re-fetch the warm list and REPLACE the db tier, so an\n * edited `source='db'` component (its `updated_at` bump re-keys the host's\n * compile cache) renders fresh on the next view. Deduped in-flight and\n * rate-limited by `maxAgeMs` (default 10s) — mounting several previews costs\n * one fetch. Server-side edits do NOT push to open clients; the contract is\n * refresh-on-view via this call.\n */\n refresh(maxAgeMs = 10_000): Promise<void> {\n const loadAll = this.options.loadAll;\n if (!loadAll) return Promise.resolve();\n if (this.refreshPromise) return this.refreshPromise;\n if (\n this.lastRefreshAt !== null &&\n this.now() - this.lastRefreshAt < maxAgeMs\n ) {\n return Promise.resolve();\n }\n // Any refresh intent invalidates recorded misses immediately (also cleared\n // in replaceDbRows when the fetch lands) — cheap to re-verify.\n this.coldMisses.clear();\n\n this.refreshPromise = loadAll()\n .then((rows) => {\n this.lastRefreshAt = this.now();\n this.replaceDbRows(rows);\n })\n .catch((error: unknown) => {\n // Loud recovery: the current tier keeps answering; a refresh failure is\n // a real defect (same posture as the warm loader).\n this.reportError({\n source: \"content-ir\",\n message: `component-resolver refresh failed (current resolver tier still serving): ${describe(error)}`,\n ...errorFields(error),\n raw: error,\n });\n })\n .finally(() => {\n this.refreshPromise = null;\n });\n return this.refreshPromise;\n }\n\n /** One list fetch per app session; failed loads retry on the next call. */\n ensureWarm(): Promise<void> {\n const loadAll = this.options.loadAll;\n if (!loadAll) return Promise.resolve();\n if (!this.warmPromise) {\n this.warmPromise = loadAll()\n .then((rows) => {\n this.ingestDbRows(rows);\n this.settled = true;\n })\n .catch((error: unknown) => {\n // Loud recovery: the compiled floor keeps rendering, but a warm\n // failure is a real defect — one console scream (first failure) plus\n // a structured report per attempt, then retryable.\n const message = `component-resolver warm load failed (compiled bootstrap still serving): ${describe(error)}`;\n if (!this.warmFailureLogged) {\n this.warmFailureLogged = true;\n console.error(`[content-ir] ${message}`);\n }\n this.reportError({\n source: \"content-ir\",\n message,\n ...errorFields(error),\n raw: error,\n });\n // A FAILED load still settles: the compiled floor becomes the\n // honest answer, and holding every block in limbo forever would be\n // a worse failure than a visible one. The retry stays available.\n this.settled = true;\n this.warmPromise = null;\n });\n }\n return this.warmPromise;\n }\n\n /** @see ComponentResolutionSource.hasSettled */\n hasSettled(): boolean {\n // No list loader configured = nothing to wait for: the compiled floor IS\n // the complete truth for this resolver, so it is settled from birth. (A\n // host that never wired a loader must not have every block held forever.)\n if (!this.options.loadAll) return true;\n return this.settled || this.db.size > 0;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../host/error-report.ts","../resolver/component-resolver.ts"],"sourcesContent":["/**\n * THE SCREAM SEAM.\n *\n * Every recovery path in this package is loud: a defective component row, a\n * failed resolver load, a kind whose component throws on a provisional value.\n * The package refuses to own where those screams land — matrx-frontend routes\n * them to the Error Inspector, the dashboard to its own log, the extension to\n * its background page. Each host binds one function.\n *\n * A host that binds a no-op has chosen silence, which is a defect in itself;\n * {@link consoleErrorReporter} is the honest default.\n */\n\nexport interface ContentIrErrorReport {\n /** Always `\"content-ir\"` — hosts key their capture stores on it. */\n source: \"content-ir\";\n message: string;\n name?: string;\n stack?: string;\n /** Free-form grouping key (the host's `relation`), e.g. `\"partial-kind\"`. */\n relation?: string;\n raw?: unknown;\n}\n\nexport type ContentIrErrorReporter = (report: ContentIrErrorReport) => void;\n\n/** The default when a host binds nothing: never silent. */\nexport const consoleErrorReporter: ContentIrErrorReporter = (report) => {\n console.error(`[content-ir] ${report.message}`, report.raw ?? \"\");\n};\n","/**\n * The Shape System component RESOLVER (rulings R1 + R6): (kind, platform,\n * role) → component, with two tiers:\n *\n * - eager: a COMPILED bootstrap the host ships in its bundle — the\n * trusted-at-boot floor, available at import so the render seam can gate\n * synchronously from the first streamed byte.\n * - warm: one `content_ir.kind_component` list fetch per app session\n * (`ensureWarm`). A DB row for a (kind, platform, role) OVERRIDES the\n * compiled entry once warm; the compiled floor keeps answering until then\n * (and forever, on DB failure).\n *\n * Resolution is SYNCHRONOUS (the render seam calls it per block); the only\n * async work is the loading, off the render path.\n *\n * WHAT MOVED AND WHY. This class was matrx-frontend's `ComponentRegistry`. It\n * is a large part of why a second UI could not render a kind: every tier rule,\n * every repaint counter, every dedupe latch lived in one app. Nothing about it\n * is Next.js, Redux, or Supabase — the only host-specific parts were the two\n * loaders and the error sink, which are now constructor arguments.\n */\n\nimport type { ContentIrErrorReporter } from \"../host/error-report\";\nimport { consoleErrorReporter } from \"../host/error-report\";\nimport type {\n ComponentResolution,\n ComponentResolutionSource,\n ComponentRole,\n KindComponentRow,\n SystemComponentEntry,\n} from \"./component-resolution.types\";\n\nexport interface ComponentResolverOptions {\n /**\n * The compiled bootstrap, as a THUNK resolved on first use: a host's system\n * component table is often mid-initialization when this module evaluates\n * (matrx-frontend's registry cluster has a deliberate import cycle).\n */\n compiledEntries?: () => SystemComponentEntry[];\n /** The warm/refresh list load — every `kind_component` row the user may see. */\n loadAll?: () => Promise<KindComponentRow[]>;\n /** The eager single-kind load fired the moment a kind is identified mid-stream. */\n loadForKind?: (kind: string, platform: string) => Promise<KindComponentRow[]>;\n /** Where recovery screams land. Defaults to `console.error`, never silence. */\n reportError?: ContentIrErrorReporter;\n /** Clock seam — the refresh rate limiter. Defaults to `Date.now`. */\n now?: () => number;\n}\n\n/** Unit separator — a character no kind slug, platform, or role can contain. */\nconst KEY_SEPARATOR = String.fromCharCode(1);\n\nfunction keyOf(kind: string, platform: string, role: string): string {\n return `${kind}${KEY_SEPARATOR}${platform}${KEY_SEPARATOR}${role}`;\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** Error-shaped fields, spread-safe under `exactOptionalPropertyTypes`. */\nfunction errorFields(error: unknown): { name?: string; stack?: string } {\n if (!(error instanceof Error)) return {};\n return {\n name: error.name,\n ...(error.stack === undefined ? {} : { stack: error.stack }),\n };\n}\n\n/**\n * Warm-load retry backoff. Sized for the auth-hydration race: a fresh tab's\n * first list load can run before the Supabase session restores and fail with\n * a permission error; the session is typically live within a second or two,\n * so the first retry usually completes the load. After the last delay the\n * resolver settles on whatever it has — see the failure branch in ensureWarm.\n */\nconst WARM_RETRY_DELAYS_MS: readonly number[] = [1_000, 5_000, 15_000];\n\nexport class ComponentResolver implements ComponentResolutionSource {\n private compiled: Map<string, SystemComponentEntry> | null = null;\n private readonly db = new Map<string, KindComponentRow>();\n private warmPromise: Promise<void> | null = null;\n private warmFailureLogged = false;\n /** Consecutive warm-load failures — indexes WARM_RETRY_DELAYS_MS. */\n private warmFailures = 0;\n private warmRetryTimer: ReturnType<typeof setTimeout> | null = null;\n /**\n * True once a list load has RUN TO COMPLETION (success or failure), or once\n * any rows have been ingested by any tier. Until then `resolve()` answering\n * null means \"I have not looked yet\" — never \"no such component\". See THE\n * COLD-VERDICT RULE on `ComponentResolutionSource.hasSettled`.\n */\n private settled = false;\n /**\n * When the last successful wholesale refresh landed, or null for \"never\".\n * Explicitly nullable rather than 0: with a host-supplied clock that starts\n * near zero, a `0` sentinel makes the FIRST refresh look rate-limited and\n * silently skip.\n */\n private lastRefreshAt: number | null = null;\n private refreshPromise: Promise<void> | null = null;\n private readonly listeners = new Set<() => void>();\n /**\n * Cold single-kind fetch dedupe (streaming eager path). In-flight is keyed\n * by (kind, platform) — the fetch unit; misses are keyed by (kind, platform,\n * role) so a miss on web/output never suppresses other roles, and CLEARED on\n * every wholesale refresh (a component created mid-session becomes eagerly\n * fetchable again — misses are cheap to re-verify).\n */\n private readonly coldInFlight = new Set<string>();\n private readonly coldMisses = new Set<string>();\n /** Monotonic db-tier version — the repaint hook's snapshot key. */\n private version = 0;\n /** Per-kind versions + listeners (granular repaint) + wholesale epoch. */\n private readonly kindVersions = new Map<string, number>();\n private readonly kindListeners = new Map<string, Set<() => void>>();\n private epoch = 0;\n\n private readonly reportError: ContentIrErrorReporter;\n private readonly now: () => number;\n\n private readonly options: ComponentResolverOptions;\n\n // Explicit field, not a parameter property: consumers compile this source\n // directly, and a strict host (the dashboard) sets `erasableSyntaxOnly`,\n // under which parameter properties are a hard error.\n constructor(options: ComponentResolverOptions = {}) {\n this.options = options;\n this.reportError = options.reportError ?? consoleErrorReporter;\n this.now = options.now ?? (() => Date.now());\n }\n\n private compiledMap(): Map<string, SystemComponentEntry> {\n if (!this.compiled) {\n this.compiled = new Map();\n for (const entry of this.options.compiledEntries?.() ?? []) {\n this.compiled.set(keyOf(entry.kind, entry.platform, entry.role), entry);\n }\n }\n return this.compiled;\n }\n\n /**\n * Synchronous resolve — the render seam's per-block call. DB override first\n * (once warm), compiled floor second, null for unknown.\n */\n resolve(\n kind: string,\n platform: string,\n role: ComponentRole,\n ): ComponentResolution | null {\n const key = keyOf(kind, platform, role);\n\n const dbRow = this.db.get(key);\n if (dbRow) {\n return {\n componentKey: dbRow.componentKey,\n source: dbRow.source,\n config: dbRow.config,\n isActive: dbRow.isActive,\n resolvedBy: \"db\",\n componentSource: dbRow.componentSource,\n propsTransform: dbRow.propsTransform,\n hasComponentSource:\n dbRow.hasComponentSource ??\n Boolean(dbRow.componentSource && dbRow.componentSource.trim()),\n pinnedKindVersion: dbRow.pinnedKindVersion,\n updatedAt: dbRow.updatedAt,\n createdBy: dbRow.createdBy,\n };\n }\n\n const compiledEntry = this.compiledMap().get(key);\n if (compiledEntry) {\n return {\n componentKey: compiledEntry.componentKey,\n source: compiledEntry.source,\n config: compiledEntry.config,\n isActive: true, // trusted at bootstrap (R6)\n resolvedBy: \"compiled\",\n componentSource: null,\n propsTransform: null,\n hasComponentSource: false,\n pinnedKindVersion: null,\n updatedAt: null,\n createdBy: null,\n };\n }\n\n return null;\n }\n\n /** R6 floor check: compiled-bootstrap membership = always render-trusted. */\n hasCompiled(kind: string, platform: string, role: ComponentRole): boolean {\n return this.compiledMap().has(keyOf(kind, platform, role));\n }\n\n /**\n * Pure ingest — the warm landing point and the unit-test seam. First row per\n * key wins: rows arrive is_default-first / sort_order-asc from the source.\n */\n ingestDbRows(rows: readonly KindComponentRow[]): void {\n const changedKinds = new Set<string>();\n for (const row of rows) {\n const key = keyOf(row.kind, row.platform, row.role);\n if (!this.db.has(key)) {\n this.db.set(key, row);\n changedKinds.add(row.kind);\n }\n }\n // Warm/cold ingest is a resolver-tier change the render seam must see — a\n // db component landing AFTER a region finalized re-runs the route via the\n // repaint hook. Per-kind bumps keep the repaint granular.\n if (changedKinds.size > 0) {\n for (const kind of changedKinds) this.bumpKind(kind);\n this.notifyChanged();\n }\n }\n\n /**\n * Refresh landing point: REPLACE the db tier wholesale (same\n * first-row-per-key contract as {@link ingestDbRows}) so edits, deletions,\n * and is_active flips all take effect. Always notifies.\n */\n replaceDbRows(rows: readonly KindComponentRow[]): void {\n // A wholesale replace IS a completed list load — the refresh landing\n // point, and the seam tests use to declare \"the list loaded\". Settling\n // here keeps THE COLD-VERDICT RULE from holding blocks forever in any\n // host that warms through refresh() rather than ensureWarm().\n this.settled = true;\n this.db.clear();\n // Wholesale invalidation: every recorded miss is stale (a component\n // created mid-session must become eagerly fetchable again).\n this.coldMisses.clear();\n for (const row of rows) {\n const key = keyOf(row.kind, row.platform, row.role);\n if (!this.db.has(key)) this.db.set(key, row);\n }\n // A wholesale replace ALWAYS notifies (deletions/flips count too), and\n // bumps the epoch so EVERY per-kind subscriber re-snapshots.\n this.epoch += 1;\n for (const set of this.kindListeners.values()) {\n for (const listener of set) listener();\n }\n this.notifyChanged();\n }\n\n getVersion(): number {\n return this.version;\n }\n\n getKindVersion(kind: string): number {\n return this.epoch + (this.kindVersions.get(kind) ?? 0);\n }\n\n subscribeKind(kind: string, listener: () => void): () => void {\n const set = this.kindListeners.get(kind) ?? new Set<() => void>();\n set.add(listener);\n this.kindListeners.set(kind, set);\n return () => {\n set.delete(listener);\n if (set.size === 0) this.kindListeners.delete(kind);\n };\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private bumpKind(kind: string): void {\n this.kindVersions.set(kind, (this.kindVersions.get(kind) ?? 0) + 1);\n const listeners = this.kindListeners.get(kind);\n if (listeners) for (const listener of listeners) listener();\n }\n\n private notifyChanged(): void {\n this.version += 1;\n for (const listener of this.listeners) listener();\n }\n\n /**\n * A warm metadata shell is resolved enough for routing, but not for the\n * host's db-component renderer. Its body must still come from the cold\n * single-kind loader.\n */\n private needsColdFetch(\n kind: string,\n platform: string,\n role: ComponentRole,\n ): boolean {\n const resolution = this.resolve(kind, platform, role);\n return (\n resolution === null ||\n (resolution.resolvedBy === \"db\" &&\n resolution.hasComponentSource &&\n resolution.componentSource === null)\n );\n }\n\n /**\n * Cold rows are the authoritative heavy form of warm metadata shells. Keep\n * the same first-row-per-key winner contract, but replace an existing shell\n * so `componentSource` / `propsTransform` can land and repaint the kind.\n */\n private ingestColdRows(rows: readonly KindComponentRow[]): void {\n const winners = new Map<string, KindComponentRow>();\n for (const row of rows) {\n const key = keyOf(row.kind, row.platform, row.role);\n if (!winners.has(key)) winners.set(key, row);\n }\n if (winners.size === 0) return;\n\n const changedKinds = new Set<string>();\n for (const [key, row] of winners) {\n this.db.set(key, row);\n changedKinds.add(row.kind);\n }\n for (const kind of changedKinds) this.bumpKind(kind);\n this.notifyChanged();\n }\n\n /**\n * The eager lightweight single-kind fetch (streaming path): the moment a\n * cloud kind is identified mid-stream, pull ONLY that kind's resolver rows\n * and ingest them so {@link resolve} can answer before — or shortly after —\n * the region completes. Deduped in-flight and by known-miss. Fire-and-forget;\n * failures are loud (the warm list remains the backstop).\n */\n requestComponent(kind: string, platform: string, role: ComponentRole): void {\n const loadForKind = this.options.loadForKind;\n if (!loadForKind) return;\n if (!this.needsColdFetch(kind, platform, role)) return;\n const missKey = keyOf(kind, platform, role);\n const flightKey = `${kind}${KEY_SEPARATOR}${platform}`;\n if (this.coldInFlight.has(flightKey) || this.coldMisses.has(missKey)) {\n return;\n }\n this.coldInFlight.add(flightKey);\n\n void (async () => {\n try {\n this.ingestColdRows(await loadForKind(kind, platform));\n // Record the miss for exactly the requested (kind, platform, role) —\n // rows may exist for OTHER roles on this platform; those must not be\n // suppressed, and this one must not be re-fetched until a refresh.\n if (this.needsColdFetch(kind, platform, role)) {\n this.coldMisses.add(missKey);\n }\n } catch (error) {\n this.reportError({\n source: \"content-ir\",\n message: `component-resolver cold fetch failed for \"${kind}\": ${describe(error)}`,\n ...errorFields(error),\n raw: error,\n });\n } finally {\n this.coldInFlight.delete(flightKey);\n }\n })();\n }\n\n /**\n * Refresh-on-view: re-fetch the warm list and REPLACE the db tier, so an\n * edited `source='db'` component (its `updated_at` bump re-keys the host's\n * compile cache) renders fresh on the next view. Deduped in-flight and\n * rate-limited by `maxAgeMs` (default 10s) — mounting several previews costs\n * one fetch. Server-side edits do NOT push to open clients; the contract is\n * refresh-on-view via this call.\n */\n refresh(maxAgeMs = 10_000): Promise<void> {\n const loadAll = this.options.loadAll;\n if (!loadAll) return Promise.resolve();\n if (this.refreshPromise) return this.refreshPromise;\n if (\n this.lastRefreshAt !== null &&\n this.now() - this.lastRefreshAt < maxAgeMs\n ) {\n return Promise.resolve();\n }\n // Any refresh intent invalidates recorded misses immediately (also cleared\n // in replaceDbRows when the fetch lands) — cheap to re-verify.\n this.coldMisses.clear();\n\n this.refreshPromise = loadAll()\n .then((rows) => {\n this.lastRefreshAt = this.now();\n this.replaceDbRows(rows);\n })\n .catch((error: unknown) => {\n // Loud recovery: the current tier keeps answering; a refresh failure is\n // a real defect (same posture as the warm loader).\n this.reportError({\n source: \"content-ir\",\n message: `component-resolver refresh failed (current resolver tier still serving): ${describe(error)}`,\n ...errorFields(error),\n raw: error,\n });\n })\n .finally(() => {\n this.refreshPromise = null;\n });\n return this.refreshPromise;\n }\n\n /** One list fetch per app session; failed loads retry on the next call. */\n ensureWarm(): Promise<void> {\n const loadAll = this.options.loadAll;\n if (!loadAll) return Promise.resolve();\n if (!this.warmPromise) {\n this.warmPromise = loadAll()\n .then((rows) => {\n this.ingestDbRows(rows);\n this.settled = true;\n this.warmFailures = 0;\n if (this.warmRetryTimer) {\n clearTimeout(this.warmRetryTimer);\n this.warmRetryTimer = null;\n }\n })\n .catch((error: unknown) => {\n // Loud recovery: the compiled floor keeps rendering, but a warm\n // failure is a real defect — one console scream (first failure) plus\n // a structured report per attempt, then retryable.\n const message = `component-resolver warm load failed (compiled bootstrap still serving): ${describe(error)}`;\n if (!this.warmFailureLogged) {\n this.warmFailureLogged = true;\n console.error(`[content-ir] ${message}`);\n }\n this.reportError({\n source: \"content-ir\",\n message,\n ...errorFields(error),\n raw: error,\n });\n this.warmPromise = null;\n // 🚨 A FAILED load is NOT truth (2026-08-31). The 0.9.0 posture —\n // \"a failed load still settles\" — turned the auth-hydration race\n // into a lie: a fresh tab fires this load BEFORE the Supabase\n // session hydrates, the read runs as `anon`, RLS answers\n // 42501 permission denied, and settling on that failure declared an\n // EMPTY resolver to be the loaded state of the world. Every kind on\n // the page then routed to the generic floor as \"unregistered\" —\n // while the same rows were one authenticated retry away. So: retry\n // with backoff, and settle only when retries are exhausted (the\n // backend really is down; the generic floor + the per-attempt\n // scream above are then the honest, announced stand-in).\n this.warmFailures += 1;\n const delay =\n WARM_RETRY_DELAYS_MS[this.warmFailures - 1] ?? null;\n if (delay === null) {\n this.settled = true;\n } else if (!this.warmRetryTimer) {\n this.warmRetryTimer = setTimeout(() => {\n this.warmRetryTimer = null;\n void this.ensureWarm();\n }, delay);\n }\n });\n }\n return this.warmPromise;\n }\n\n /** @see ComponentResolutionSource.hasSettled */\n hasSettled(): boolean {\n // No list loader configured = nothing to wait for: the compiled floor IS\n // the complete truth for this resolver, so it is settled from birth. (A\n // host that never wired a loader must not have every block held forever.)\n if (!this.options.loadAll) return true;\n return this.settled || this.db.size > 0;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,65 @@
|
|
|
1
1
|
import { C as ComponentResolutionSource, b as ContentIrErrorReporter, a as ComponentResolution } from './component-resolution.types-DLO2w-0n.cjs';
|
|
2
|
+
import * as react from 'react';
|
|
2
3
|
import { ReactNode } from 'react';
|
|
3
|
-
import { KindDefinition, CanonicalBlockIR, PartialKindEvent } from '@ai-matrx/content-ir';
|
|
4
|
+
import { DecodedDirective, DirectiveNounCatalog, DirectiveItemKindLookup, KindDefinition, CanonicalBlockIR, PartialKindEvent } from '@ai-matrx/content-ir';
|
|
5
|
+
|
|
6
|
+
interface DirectiveApplyResult {
|
|
7
|
+
applied: number;
|
|
8
|
+
failed: number;
|
|
9
|
+
}
|
|
10
|
+
interface DirectiveOpenItemOptions {
|
|
11
|
+
/** The item, `__kind`-stamped when the shape has an item kind. */
|
|
12
|
+
item: Record<string, unknown>;
|
|
13
|
+
/** The content-IR kind this item IS, or null when the shape has none. */
|
|
14
|
+
itemKind: string | null;
|
|
15
|
+
/** What the item is called — the same title the card shows. */
|
|
16
|
+
title: string;
|
|
17
|
+
/** "Run Create agent definition · item 1 of 1". */
|
|
18
|
+
subtitle: string | null;
|
|
19
|
+
directive: DecodedDirective;
|
|
20
|
+
index: number;
|
|
21
|
+
total: number;
|
|
22
|
+
}
|
|
23
|
+
interface DirectiveCopyProps {
|
|
24
|
+
label: string;
|
|
25
|
+
/** The value to copy — the whole shell or one item. */
|
|
26
|
+
value: unknown;
|
|
27
|
+
/** The item's kind, when copying an item. */
|
|
28
|
+
kind?: string | null;
|
|
29
|
+
size: "icon" | "xs";
|
|
30
|
+
}
|
|
31
|
+
interface DirectiveHost {
|
|
32
|
+
/**
|
|
33
|
+
* Apply a side-effect directive as the user (the server re-validates every
|
|
34
|
+
* item against the registered model and runs the ONE handler; idempotent).
|
|
35
|
+
* Reject with an Error whose message is safe to show.
|
|
36
|
+
*/
|
|
37
|
+
confirm?: (shell: {
|
|
38
|
+
__kind: string;
|
|
39
|
+
items: Record<string, unknown>[];
|
|
40
|
+
}) => Promise<DirectiveApplyResult>;
|
|
41
|
+
/** Open ONE item to read it properly before approving the write. */
|
|
42
|
+
openItem?: (options: DirectiveOpenItemOptions) => void;
|
|
43
|
+
/** Host copy controls. */
|
|
44
|
+
renderCopy?: (props: DirectiveCopyProps) => ReactNode;
|
|
45
|
+
/** The mirrored noun catalog (label / family / title column). */
|
|
46
|
+
nouns?: DirectiveNounCatalog;
|
|
47
|
+
/** slug → the kind ONE item is (THE DIRECTIVE⇄KIND SEAM), or null. */
|
|
48
|
+
itemKind?: DirectiveItemKindLookup;
|
|
49
|
+
/** Where a decode failure or an unhonourable shell is reported. Default: console.error. */
|
|
50
|
+
reportError?: (message: string) => void;
|
|
51
|
+
}
|
|
52
|
+
interface DirectiveHostProviderProps {
|
|
53
|
+
host: DirectiveHost;
|
|
54
|
+
children: ReactNode;
|
|
55
|
+
}
|
|
56
|
+
declare function DirectiveHostProvider({ host, children }: DirectiveHostProviderProps): react.JSX.Element;
|
|
57
|
+
/**
|
|
58
|
+
* The wired directive host. Unlike the render host this does NOT throw when
|
|
59
|
+
* unwired: a directive still renders (named, itemised, copy-less, apply-less)
|
|
60
|
+
* — degrading the chrome is honest, hiding the content is not.
|
|
61
|
+
*/
|
|
62
|
+
declare function useDirectiveHost(): DirectiveHost;
|
|
4
63
|
|
|
5
64
|
/**
|
|
6
65
|
* THE HOST CONTRACT — everything this package refuses to decide.
|
|
@@ -113,6 +172,14 @@ interface ContentIrHost extends ContentIrRenderSeams {
|
|
|
113
172
|
* host that lies here renders the wrong component everywhere.
|
|
114
173
|
*/
|
|
115
174
|
platform: string;
|
|
175
|
+
/**
|
|
176
|
+
* The Kind Directives seams (apply / open item / copy / noun catalog / item
|
|
177
|
+
* kind). Optional: a host without them still renders every directive card,
|
|
178
|
+
* apply-less and copy-less — honest chrome, never hidden content. Provided
|
|
179
|
+
* to the directive tier by `ContentIrRenderProvider`; a host that has not
|
|
180
|
+
* adopted the render provider mounts `DirectiveHostProvider` directly.
|
|
181
|
+
*/
|
|
182
|
+
directives?: DirectiveHost;
|
|
116
183
|
}
|
|
117
184
|
/**
|
|
118
185
|
* The pure route's dependencies — a strict subset of {@link ContentIrHost},
|
|
@@ -180,6 +247,19 @@ declare const GENERIC_STRUCTURED_COMPONENT_KEY: "generic_structured";
|
|
|
180
247
|
* falls to that host's unknown-type handling, which is honest.
|
|
181
248
|
*/
|
|
182
249
|
declare const DB_KIND_COMPONENT_KEY: "db_kind_component";
|
|
250
|
+
/**
|
|
251
|
+
* THE DIRECTIVE TIER. A block whose root `__kind` is a reserved directive
|
|
252
|
+
* slug (`directive_v…`) is typed to this block type and drawn by
|
|
253
|
+
* `DirectiveRender` (`@ai-matrx/content-ir-react/directives`) — BEFORE the
|
|
254
|
+
* component resolver is consulted. A directive is a kind whose display is the
|
|
255
|
+
* directive card (the item inside it renders through the item's own kind —
|
|
256
|
+
* THE DIRECTIVE⇄KIND SEAM), so no `kind_component` row exists or is needed,
|
|
257
|
+
* and the "unregistered" / "no-component" floors must never claim one.
|
|
258
|
+
* `"matrx"` is the block type matrx-frontend has dispatched directive fences
|
|
259
|
+
* to since the envelope era; every host's dispatch table maps it to
|
|
260
|
+
* `DirectiveRender`.
|
|
261
|
+
*/
|
|
262
|
+
declare const DIRECTIVE_BLOCK_TYPE: "matrx";
|
|
183
263
|
/** Test-only reset of the once-per-kind console latch. */
|
|
184
264
|
declare function resetSourcelessDbRowReports(): void;
|
|
185
265
|
/** Why a block landed on the generic viewer instead of a real renderer. */
|
|
@@ -221,7 +301,7 @@ type GenericFallbackReason =
|
|
|
221
301
|
*/
|
|
222
302
|
| "unregistered";
|
|
223
303
|
interface IrRouteMarker {
|
|
224
|
-
by: ComponentResolution["resolvedBy"] | "generic";
|
|
304
|
+
by: ComponentResolution["resolvedBy"] | "generic" | "directive";
|
|
225
305
|
key: string;
|
|
226
306
|
/**
|
|
227
307
|
* Only on the generic fallback: the shape is NOT render-trusted, so the
|
|
@@ -435,4 +515,4 @@ interface KindRouteBinding {
|
|
|
435
515
|
/** Bind the pure routes to one host's env + owned block types. */
|
|
436
516
|
declare function createKindRouteBinding(env: KindRouteEnv, options?: KindRouteOptions): KindRouteBinding;
|
|
437
517
|
|
|
438
|
-
export { type ContentIrHost as C, DB_KIND_COMPONENT_KEY as D, GENERIC_STRUCTURED_COMPONENT_KEY as G, IR_PROVISIONAL_KEY as I, type KindRouteEnv as K, type PartialRenderOptions as P, type StructuredValueRenderProps as S, type KindDefinitionSource as a, type ContentIrRenderSeams as b,
|
|
518
|
+
export { resetSourcelessDbRowReports as A, resolveAnnouncedKindLoading as B, type ContentIrHost as C, DB_KIND_COMPONENT_KEY as D, resolveProvisionalKindRender as E, routeEnvOf as F, GENERIC_STRUCTURED_COMPONENT_KEY as G, useDirectiveHost as H, IR_PROVISIONAL_KEY as I, type KindRouteEnv as K, type PartialRenderOptions as P, type StructuredValueRenderProps as S, type KindDefinitionSource as a, type ContentIrRenderSeams as b, DIRECTIVE_BLOCK_TYPE as c, type DirectiveApplyResult as d, type DirectiveCopyProps as e, type DirectiveHost as f, DirectiveHostProvider as g, type DirectiveHostProviderProps as h, type DirectiveOpenItemOptions as i, type GenericFallbackReason as j, IR_ROUTE_KEY as k, type IrRenderBlock as l, type IrRoutableBlock as m, type IrRouteMarker as n, type KindRouteBinding as o, type KindRouteOptions as p, type ProvisionalKindRender as q, applyIrKindRoute as r, createKindRouteBinding as s, envelopeFromPartialKind as t, isPartialReadyKind as u, isProvisionalBlock as v, kindServerDataFromStoredValue as w, markKindPartialUnsafe as x, readIrRouteMarker as y, resetPartialUnsafeKinds as z };
|
|
@@ -1,6 +1,65 @@
|
|
|
1
1
|
import { C as ComponentResolutionSource, b as ContentIrErrorReporter, a as ComponentResolution } from './component-resolution.types-DLO2w-0n.js';
|
|
2
|
+
import * as react from 'react';
|
|
2
3
|
import { ReactNode } from 'react';
|
|
3
|
-
import { KindDefinition, CanonicalBlockIR, PartialKindEvent } from '@ai-matrx/content-ir';
|
|
4
|
+
import { DecodedDirective, DirectiveNounCatalog, DirectiveItemKindLookup, KindDefinition, CanonicalBlockIR, PartialKindEvent } from '@ai-matrx/content-ir';
|
|
5
|
+
|
|
6
|
+
interface DirectiveApplyResult {
|
|
7
|
+
applied: number;
|
|
8
|
+
failed: number;
|
|
9
|
+
}
|
|
10
|
+
interface DirectiveOpenItemOptions {
|
|
11
|
+
/** The item, `__kind`-stamped when the shape has an item kind. */
|
|
12
|
+
item: Record<string, unknown>;
|
|
13
|
+
/** The content-IR kind this item IS, or null when the shape has none. */
|
|
14
|
+
itemKind: string | null;
|
|
15
|
+
/** What the item is called — the same title the card shows. */
|
|
16
|
+
title: string;
|
|
17
|
+
/** "Run Create agent definition · item 1 of 1". */
|
|
18
|
+
subtitle: string | null;
|
|
19
|
+
directive: DecodedDirective;
|
|
20
|
+
index: number;
|
|
21
|
+
total: number;
|
|
22
|
+
}
|
|
23
|
+
interface DirectiveCopyProps {
|
|
24
|
+
label: string;
|
|
25
|
+
/** The value to copy — the whole shell or one item. */
|
|
26
|
+
value: unknown;
|
|
27
|
+
/** The item's kind, when copying an item. */
|
|
28
|
+
kind?: string | null;
|
|
29
|
+
size: "icon" | "xs";
|
|
30
|
+
}
|
|
31
|
+
interface DirectiveHost {
|
|
32
|
+
/**
|
|
33
|
+
* Apply a side-effect directive as the user (the server re-validates every
|
|
34
|
+
* item against the registered model and runs the ONE handler; idempotent).
|
|
35
|
+
* Reject with an Error whose message is safe to show.
|
|
36
|
+
*/
|
|
37
|
+
confirm?: (shell: {
|
|
38
|
+
__kind: string;
|
|
39
|
+
items: Record<string, unknown>[];
|
|
40
|
+
}) => Promise<DirectiveApplyResult>;
|
|
41
|
+
/** Open ONE item to read it properly before approving the write. */
|
|
42
|
+
openItem?: (options: DirectiveOpenItemOptions) => void;
|
|
43
|
+
/** Host copy controls. */
|
|
44
|
+
renderCopy?: (props: DirectiveCopyProps) => ReactNode;
|
|
45
|
+
/** The mirrored noun catalog (label / family / title column). */
|
|
46
|
+
nouns?: DirectiveNounCatalog;
|
|
47
|
+
/** slug → the kind ONE item is (THE DIRECTIVE⇄KIND SEAM), or null. */
|
|
48
|
+
itemKind?: DirectiveItemKindLookup;
|
|
49
|
+
/** Where a decode failure or an unhonourable shell is reported. Default: console.error. */
|
|
50
|
+
reportError?: (message: string) => void;
|
|
51
|
+
}
|
|
52
|
+
interface DirectiveHostProviderProps {
|
|
53
|
+
host: DirectiveHost;
|
|
54
|
+
children: ReactNode;
|
|
55
|
+
}
|
|
56
|
+
declare function DirectiveHostProvider({ host, children }: DirectiveHostProviderProps): react.JSX.Element;
|
|
57
|
+
/**
|
|
58
|
+
* The wired directive host. Unlike the render host this does NOT throw when
|
|
59
|
+
* unwired: a directive still renders (named, itemised, copy-less, apply-less)
|
|
60
|
+
* — degrading the chrome is honest, hiding the content is not.
|
|
61
|
+
*/
|
|
62
|
+
declare function useDirectiveHost(): DirectiveHost;
|
|
4
63
|
|
|
5
64
|
/**
|
|
6
65
|
* THE HOST CONTRACT — everything this package refuses to decide.
|
|
@@ -113,6 +172,14 @@ interface ContentIrHost extends ContentIrRenderSeams {
|
|
|
113
172
|
* host that lies here renders the wrong component everywhere.
|
|
114
173
|
*/
|
|
115
174
|
platform: string;
|
|
175
|
+
/**
|
|
176
|
+
* The Kind Directives seams (apply / open item / copy / noun catalog / item
|
|
177
|
+
* kind). Optional: a host without them still renders every directive card,
|
|
178
|
+
* apply-less and copy-less — honest chrome, never hidden content. Provided
|
|
179
|
+
* to the directive tier by `ContentIrRenderProvider`; a host that has not
|
|
180
|
+
* adopted the render provider mounts `DirectiveHostProvider` directly.
|
|
181
|
+
*/
|
|
182
|
+
directives?: DirectiveHost;
|
|
116
183
|
}
|
|
117
184
|
/**
|
|
118
185
|
* The pure route's dependencies — a strict subset of {@link ContentIrHost},
|
|
@@ -180,6 +247,19 @@ declare const GENERIC_STRUCTURED_COMPONENT_KEY: "generic_structured";
|
|
|
180
247
|
* falls to that host's unknown-type handling, which is honest.
|
|
181
248
|
*/
|
|
182
249
|
declare const DB_KIND_COMPONENT_KEY: "db_kind_component";
|
|
250
|
+
/**
|
|
251
|
+
* THE DIRECTIVE TIER. A block whose root `__kind` is a reserved directive
|
|
252
|
+
* slug (`directive_v…`) is typed to this block type and drawn by
|
|
253
|
+
* `DirectiveRender` (`@ai-matrx/content-ir-react/directives`) — BEFORE the
|
|
254
|
+
* component resolver is consulted. A directive is a kind whose display is the
|
|
255
|
+
* directive card (the item inside it renders through the item's own kind —
|
|
256
|
+
* THE DIRECTIVE⇄KIND SEAM), so no `kind_component` row exists or is needed,
|
|
257
|
+
* and the "unregistered" / "no-component" floors must never claim one.
|
|
258
|
+
* `"matrx"` is the block type matrx-frontend has dispatched directive fences
|
|
259
|
+
* to since the envelope era; every host's dispatch table maps it to
|
|
260
|
+
* `DirectiveRender`.
|
|
261
|
+
*/
|
|
262
|
+
declare const DIRECTIVE_BLOCK_TYPE: "matrx";
|
|
183
263
|
/** Test-only reset of the once-per-kind console latch. */
|
|
184
264
|
declare function resetSourcelessDbRowReports(): void;
|
|
185
265
|
/** Why a block landed on the generic viewer instead of a real renderer. */
|
|
@@ -221,7 +301,7 @@ type GenericFallbackReason =
|
|
|
221
301
|
*/
|
|
222
302
|
| "unregistered";
|
|
223
303
|
interface IrRouteMarker {
|
|
224
|
-
by: ComponentResolution["resolvedBy"] | "generic";
|
|
304
|
+
by: ComponentResolution["resolvedBy"] | "generic" | "directive";
|
|
225
305
|
key: string;
|
|
226
306
|
/**
|
|
227
307
|
* Only on the generic fallback: the shape is NOT render-trusted, so the
|
|
@@ -435,4 +515,4 @@ interface KindRouteBinding {
|
|
|
435
515
|
/** Bind the pure routes to one host's env + owned block types. */
|
|
436
516
|
declare function createKindRouteBinding(env: KindRouteEnv, options?: KindRouteOptions): KindRouteBinding;
|
|
437
517
|
|
|
438
|
-
export { type ContentIrHost as C, DB_KIND_COMPONENT_KEY as D, GENERIC_STRUCTURED_COMPONENT_KEY as G, IR_PROVISIONAL_KEY as I, type KindRouteEnv as K, type PartialRenderOptions as P, type StructuredValueRenderProps as S, type KindDefinitionSource as a, type ContentIrRenderSeams as b,
|
|
518
|
+
export { resetSourcelessDbRowReports as A, resolveAnnouncedKindLoading as B, type ContentIrHost as C, DB_KIND_COMPONENT_KEY as D, resolveProvisionalKindRender as E, routeEnvOf as F, GENERIC_STRUCTURED_COMPONENT_KEY as G, useDirectiveHost as H, IR_PROVISIONAL_KEY as I, type KindRouteEnv as K, type PartialRenderOptions as P, type StructuredValueRenderProps as S, type KindDefinitionSource as a, type ContentIrRenderSeams as b, DIRECTIVE_BLOCK_TYPE as c, type DirectiveApplyResult as d, type DirectiveCopyProps as e, type DirectiveHost as f, DirectiveHostProvider as g, type DirectiveHostProviderProps as h, type DirectiveOpenItemOptions as i, type GenericFallbackReason as j, IR_ROUTE_KEY as k, type IrRenderBlock as l, type IrRoutableBlock as m, type IrRouteMarker as n, type KindRouteBinding as o, type KindRouteOptions as p, type ProvisionalKindRender as q, applyIrKindRoute as r, createKindRouteBinding as s, envelopeFromPartialKind as t, isPartialReadyKind as u, isProvisionalBlock as v, kindServerDataFromStoredValue as w, markKindPartialUnsafe as x, readIrRouteMarker as y, resetPartialUnsafeKinds as z };
|
package/dist/route.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var route_exports = {};
|
|
22
22
|
__export(route_exports, {
|
|
23
23
|
DB_KIND_COMPONENT_KEY: () => DB_KIND_COMPONENT_KEY,
|
|
24
|
+
DIRECTIVE_BLOCK_TYPE: () => DIRECTIVE_BLOCK_TYPE,
|
|
24
25
|
GENERIC_STRUCTURED_COMPONENT_KEY: () => GENERIC_STRUCTURED_COMPONENT_KEY,
|
|
25
26
|
IR_PROVISIONAL_KEY: () => IR_PROVISIONAL_KEY,
|
|
26
27
|
IR_ROUTE_KEY: () => IR_ROUTE_KEY,
|
|
@@ -44,6 +45,7 @@ var import_content_ir = require("@ai-matrx/content-ir");
|
|
|
44
45
|
var IR_ROUTE_KEY = "__ir_route";
|
|
45
46
|
var GENERIC_STRUCTURED_COMPONENT_KEY = "generic_structured";
|
|
46
47
|
var DB_KIND_COMPONENT_KEY = "db_kind_component";
|
|
48
|
+
var DIRECTIVE_BLOCK_TYPE = "matrx";
|
|
47
49
|
function isDbSourceResolution(resolution) {
|
|
48
50
|
return resolution !== null && resolution.resolvedBy === "db" && resolution.source === "db" && resolution.isActive;
|
|
49
51
|
}
|
|
@@ -121,6 +123,18 @@ function applyIrKindRoute(block, env, options) {
|
|
|
121
123
|
const kind = envelope.root.kind;
|
|
122
124
|
if (!kind) return block;
|
|
123
125
|
if (envelope.root.kindState === "pending_schema") return block;
|
|
126
|
+
if ((0, import_content_ir.isReservedDirectiveSlug)(kind)) {
|
|
127
|
+
if (block.type === DIRECTIVE_BLOCK_TYPE) return block;
|
|
128
|
+
return {
|
|
129
|
+
...block,
|
|
130
|
+
type: DIRECTIVE_BLOCK_TYPE,
|
|
131
|
+
serverData: void 0,
|
|
132
|
+
metadata: {
|
|
133
|
+
...block.metadata ?? {},
|
|
134
|
+
[IR_ROUTE_KEY]: { by: "directive", key: DIRECTIVE_BLOCK_TYPE }
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
124
138
|
const def = env.kinds.getDefinition(kind);
|
|
125
139
|
const resolution = env.components.resolve(kind, env.platform, "output");
|
|
126
140
|
const componentsSettled = env.components.hasSettled?.() ?? true;
|