@ai-matrx/content-ir-react 0.12.7 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/dist/index.cjs +31 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +31 -2
- package/dist/index.js.map +1 -1
- package/dist/resolver.cjs +31 -2
- package/dist/resolver.cjs.map +1 -1
- package/dist/resolver.d.cts +10 -0
- package/dist/resolver.d.ts +10 -0
- package/dist/resolver.js +31 -2
- package/dist/resolver.js.map +1 -1
- package/package.json +1 -1
package/dist/resolver.cjs
CHANGED
|
@@ -153,6 +153,7 @@ var ComponentResolver = class {
|
|
|
153
153
|
* key wins: rows arrive is_default-first / sort_order-asc from the source.
|
|
154
154
|
*/
|
|
155
155
|
ingestDbRows(rows) {
|
|
156
|
+
const wasSettled = this.hasSettled();
|
|
156
157
|
const changedKinds = /* @__PURE__ */ new Set();
|
|
157
158
|
for (const row of rows) {
|
|
158
159
|
const key = keyOf(row.kind, row.platform, row.role);
|
|
@@ -161,11 +162,35 @@ var ComponentResolver = class {
|
|
|
161
162
|
changedKinds.add(row.kind);
|
|
162
163
|
}
|
|
163
164
|
}
|
|
165
|
+
if (!wasSettled && this.hasSettled()) {
|
|
166
|
+
this.notifySettled();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
164
169
|
if (changedKinds.size > 0) {
|
|
165
170
|
for (const kind of changedKinds) this.bumpKind(kind);
|
|
166
171
|
this.notifyChanged();
|
|
167
172
|
}
|
|
168
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Mark the tier settled. THE COLD-VERDICT RULE held every block whose kind
|
|
176
|
+
* the tier could not answer; settling changes that verdict for ALL of them,
|
|
177
|
+
* so the flip bumps the epoch and calls every per-kind listener (as a
|
|
178
|
+
* wholesale replace does). Before 0.13.0 a settle notified only kinds that
|
|
179
|
+
* gained rows, and a waiting block for any other kind kept its pre-settle
|
|
180
|
+
* rendering until something unrelated re-rendered it.
|
|
181
|
+
*/
|
|
182
|
+
markSettled() {
|
|
183
|
+
const wasSettled = this.hasSettled();
|
|
184
|
+
this.settled = true;
|
|
185
|
+
if (!wasSettled) this.notifySettled();
|
|
186
|
+
}
|
|
187
|
+
notifySettled() {
|
|
188
|
+
this.epoch += 1;
|
|
189
|
+
for (const set of this.kindListeners.values()) {
|
|
190
|
+
for (const listener of set) listener();
|
|
191
|
+
}
|
|
192
|
+
this.notifyChanged();
|
|
193
|
+
}
|
|
169
194
|
/**
|
|
170
195
|
* Refresh landing point: REPLACE the db tier wholesale (same
|
|
171
196
|
* first-row-per-key contract as {@link ingestDbRows}) so edits, deletions,
|
|
@@ -261,6 +286,7 @@ var ComponentResolver = class {
|
|
|
261
286
|
return;
|
|
262
287
|
}
|
|
263
288
|
this.coldInFlight.add(flightKey);
|
|
289
|
+
const versionBefore = this.kindVersions.get(kind) ?? 0;
|
|
264
290
|
void (async () => {
|
|
265
291
|
try {
|
|
266
292
|
this.ingestColdRows(await loadForKind(kind, platform));
|
|
@@ -276,6 +302,9 @@ var ComponentResolver = class {
|
|
|
276
302
|
});
|
|
277
303
|
} finally {
|
|
278
304
|
this.coldInFlight.delete(flightKey);
|
|
305
|
+
if ((this.kindVersions.get(kind) ?? 0) === versionBefore) {
|
|
306
|
+
this.bumpKind(kind);
|
|
307
|
+
}
|
|
279
308
|
}
|
|
280
309
|
})();
|
|
281
310
|
}
|
|
@@ -317,7 +346,7 @@ var ComponentResolver = class {
|
|
|
317
346
|
if (!this.warmPromise) {
|
|
318
347
|
this.warmPromise = loadAll().then((rows) => {
|
|
319
348
|
this.ingestDbRows(rows);
|
|
320
|
-
this.
|
|
349
|
+
this.markSettled();
|
|
321
350
|
this.warmFailures = 0;
|
|
322
351
|
if (this.warmRetryTimer) {
|
|
323
352
|
clearTimeout(this.warmRetryTimer);
|
|
@@ -335,7 +364,7 @@ var ComponentResolver = class {
|
|
|
335
364
|
this.warmFailures += 1;
|
|
336
365
|
const delay = WARM_RETRY_DELAYS_MS[this.warmFailures - 1] ?? null;
|
|
337
366
|
if (delay === null) {
|
|
338
|
-
this.
|
|
367
|
+
this.markSettled();
|
|
339
368
|
} else if (!this.warmRetryTimer) {
|
|
340
369
|
this.warmRetryTimer = setTimeout(() => {
|
|
341
370
|
this.warmRetryTimer = null;
|
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\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 /** 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 // The host owns presentation and severity through reportError. A\n // direct console.error here duplicates that report and prevents a\n // host from classifying a still-serving compiled fallback as a\n // recoverable warning.\n const message = `component-resolver warm load failed (compiled bootstrap still serving): ${describe(error)}`;\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
|
+
{"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 /** 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 wasSettled = this.hasSettled();\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 // The first rows settle the tier (hasSettled): every waiting kind hears it,\n // ONCE — the epoch covers the ingested kinds too.\n if (!wasSettled && this.hasSettled()) {\n this.notifySettled();\n return;\n }\n if (changedKinds.size > 0) {\n for (const kind of changedKinds) this.bumpKind(kind);\n this.notifyChanged();\n }\n }\n\n /**\n * Mark the tier settled. THE COLD-VERDICT RULE held every block whose kind\n * the tier could not answer; settling changes that verdict for ALL of them,\n * so the flip bumps the epoch and calls every per-kind listener (as a\n * wholesale replace does). Before 0.13.0 a settle notified only kinds that\n * gained rows, and a waiting block for any other kind kept its pre-settle\n * rendering until something unrelated re-rendered it.\n */\n private markSettled(): void {\n const wasSettled = this.hasSettled();\n this.settled = true;\n if (!wasSettled) this.notifySettled();\n }\n\n private notifySettled(): void {\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 /**\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 const versionBefore = this.kindVersions.get(kind) ?? 0;\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 // The kind's single-kind fetch answered (rows, a miss, or a failure):\n // a block waiting on THIS kind re-routes now. A landing already bumped\n // inside ingestColdRows; a miss or failure changed nothing in the\n // tier, but the waiting block must still be told to re-ask.\n if ((this.kindVersions.get(kind) ?? 0) === versionBefore) {\n this.bumpKind(kind);\n }\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.markSettled();\n this.warmFailures = 0;\n if (this.warmRetryTimer) {\n clearTimeout(this.warmRetryTimer);\n this.warmRetryTimer = null;\n }\n })\n .catch((error: unknown) => {\n // The host owns presentation and severity through reportError. A\n // direct console.error here duplicates that report and prevents a\n // host from classifying a still-serving compiled fallback as a\n // recoverable warning.\n const message = `component-resolver warm load failed (compiled bootstrap still serving): ${describe(error)}`;\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.markSettled();\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
|
@@ -94,6 +94,16 @@ declare class ComponentResolver implements ComponentResolutionSource {
|
|
|
94
94
|
* key wins: rows arrive is_default-first / sort_order-asc from the source.
|
|
95
95
|
*/
|
|
96
96
|
ingestDbRows(rows: readonly KindComponentRow[]): void;
|
|
97
|
+
/**
|
|
98
|
+
* Mark the tier settled. THE COLD-VERDICT RULE held every block whose kind
|
|
99
|
+
* the tier could not answer; settling changes that verdict for ALL of them,
|
|
100
|
+
* so the flip bumps the epoch and calls every per-kind listener (as a
|
|
101
|
+
* wholesale replace does). Before 0.13.0 a settle notified only kinds that
|
|
102
|
+
* gained rows, and a waiting block for any other kind kept its pre-settle
|
|
103
|
+
* rendering until something unrelated re-rendered it.
|
|
104
|
+
*/
|
|
105
|
+
private markSettled;
|
|
106
|
+
private notifySettled;
|
|
97
107
|
/**
|
|
98
108
|
* Refresh landing point: REPLACE the db tier wholesale (same
|
|
99
109
|
* first-row-per-key contract as {@link ingestDbRows}) so edits, deletions,
|
package/dist/resolver.d.ts
CHANGED
|
@@ -94,6 +94,16 @@ declare class ComponentResolver implements ComponentResolutionSource {
|
|
|
94
94
|
* key wins: rows arrive is_default-first / sort_order-asc from the source.
|
|
95
95
|
*/
|
|
96
96
|
ingestDbRows(rows: readonly KindComponentRow[]): void;
|
|
97
|
+
/**
|
|
98
|
+
* Mark the tier settled. THE COLD-VERDICT RULE held every block whose kind
|
|
99
|
+
* the tier could not answer; settling changes that verdict for ALL of them,
|
|
100
|
+
* so the flip bumps the epoch and calls every per-kind listener (as a
|
|
101
|
+
* wholesale replace does). Before 0.13.0 a settle notified only kinds that
|
|
102
|
+
* gained rows, and a waiting block for any other kind kept its pre-settle
|
|
103
|
+
* rendering until something unrelated re-rendered it.
|
|
104
|
+
*/
|
|
105
|
+
private markSettled;
|
|
106
|
+
private notifySettled;
|
|
97
107
|
/**
|
|
98
108
|
* Refresh landing point: REPLACE the db tier wholesale (same
|
|
99
109
|
* first-row-per-key contract as {@link ingestDbRows}) so edits, deletions,
|
package/dist/resolver.js
CHANGED
|
@@ -127,6 +127,7 @@ var ComponentResolver = class {
|
|
|
127
127
|
* key wins: rows arrive is_default-first / sort_order-asc from the source.
|
|
128
128
|
*/
|
|
129
129
|
ingestDbRows(rows) {
|
|
130
|
+
const wasSettled = this.hasSettled();
|
|
130
131
|
const changedKinds = /* @__PURE__ */ new Set();
|
|
131
132
|
for (const row of rows) {
|
|
132
133
|
const key = keyOf(row.kind, row.platform, row.role);
|
|
@@ -135,11 +136,35 @@ var ComponentResolver = class {
|
|
|
135
136
|
changedKinds.add(row.kind);
|
|
136
137
|
}
|
|
137
138
|
}
|
|
139
|
+
if (!wasSettled && this.hasSettled()) {
|
|
140
|
+
this.notifySettled();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
138
143
|
if (changedKinds.size > 0) {
|
|
139
144
|
for (const kind of changedKinds) this.bumpKind(kind);
|
|
140
145
|
this.notifyChanged();
|
|
141
146
|
}
|
|
142
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Mark the tier settled. THE COLD-VERDICT RULE held every block whose kind
|
|
150
|
+
* the tier could not answer; settling changes that verdict for ALL of them,
|
|
151
|
+
* so the flip bumps the epoch and calls every per-kind listener (as a
|
|
152
|
+
* wholesale replace does). Before 0.13.0 a settle notified only kinds that
|
|
153
|
+
* gained rows, and a waiting block for any other kind kept its pre-settle
|
|
154
|
+
* rendering until something unrelated re-rendered it.
|
|
155
|
+
*/
|
|
156
|
+
markSettled() {
|
|
157
|
+
const wasSettled = this.hasSettled();
|
|
158
|
+
this.settled = true;
|
|
159
|
+
if (!wasSettled) this.notifySettled();
|
|
160
|
+
}
|
|
161
|
+
notifySettled() {
|
|
162
|
+
this.epoch += 1;
|
|
163
|
+
for (const set of this.kindListeners.values()) {
|
|
164
|
+
for (const listener of set) listener();
|
|
165
|
+
}
|
|
166
|
+
this.notifyChanged();
|
|
167
|
+
}
|
|
143
168
|
/**
|
|
144
169
|
* Refresh landing point: REPLACE the db tier wholesale (same
|
|
145
170
|
* first-row-per-key contract as {@link ingestDbRows}) so edits, deletions,
|
|
@@ -235,6 +260,7 @@ var ComponentResolver = class {
|
|
|
235
260
|
return;
|
|
236
261
|
}
|
|
237
262
|
this.coldInFlight.add(flightKey);
|
|
263
|
+
const versionBefore = this.kindVersions.get(kind) ?? 0;
|
|
238
264
|
void (async () => {
|
|
239
265
|
try {
|
|
240
266
|
this.ingestColdRows(await loadForKind(kind, platform));
|
|
@@ -250,6 +276,9 @@ var ComponentResolver = class {
|
|
|
250
276
|
});
|
|
251
277
|
} finally {
|
|
252
278
|
this.coldInFlight.delete(flightKey);
|
|
279
|
+
if ((this.kindVersions.get(kind) ?? 0) === versionBefore) {
|
|
280
|
+
this.bumpKind(kind);
|
|
281
|
+
}
|
|
253
282
|
}
|
|
254
283
|
})();
|
|
255
284
|
}
|
|
@@ -291,7 +320,7 @@ var ComponentResolver = class {
|
|
|
291
320
|
if (!this.warmPromise) {
|
|
292
321
|
this.warmPromise = loadAll().then((rows) => {
|
|
293
322
|
this.ingestDbRows(rows);
|
|
294
|
-
this.
|
|
323
|
+
this.markSettled();
|
|
295
324
|
this.warmFailures = 0;
|
|
296
325
|
if (this.warmRetryTimer) {
|
|
297
326
|
clearTimeout(this.warmRetryTimer);
|
|
@@ -309,7 +338,7 @@ var ComponentResolver = class {
|
|
|
309
338
|
this.warmFailures += 1;
|
|
310
339
|
const delay = WARM_RETRY_DELAYS_MS[this.warmFailures - 1] ?? null;
|
|
311
340
|
if (delay === null) {
|
|
312
|
-
this.
|
|
341
|
+
this.markSettled();
|
|
313
342
|
} else if (!this.warmRetryTimer) {
|
|
314
343
|
this.warmRetryTimer = setTimeout(() => {
|
|
315
344
|
this.warmRetryTimer = null;
|
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\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 /** 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 // The host owns presentation and severity through reportError. A\n // direct console.error here duplicates that report and prevents a\n // host from classifying a still-serving compiled fallback as a\n // recoverable warning.\n const message = `component-resolver warm load failed (compiled bootstrap still serving): ${describe(error)}`;\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
|
+
{"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 /** 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 wasSettled = this.hasSettled();\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 // The first rows settle the tier (hasSettled): every waiting kind hears it,\n // ONCE — the epoch covers the ingested kinds too.\n if (!wasSettled && this.hasSettled()) {\n this.notifySettled();\n return;\n }\n if (changedKinds.size > 0) {\n for (const kind of changedKinds) this.bumpKind(kind);\n this.notifyChanged();\n }\n }\n\n /**\n * Mark the tier settled. THE COLD-VERDICT RULE held every block whose kind\n * the tier could not answer; settling changes that verdict for ALL of them,\n * so the flip bumps the epoch and calls every per-kind listener (as a\n * wholesale replace does). Before 0.13.0 a settle notified only kinds that\n * gained rows, and a waiting block for any other kind kept its pre-settle\n * rendering until something unrelated re-rendered it.\n */\n private markSettled(): void {\n const wasSettled = this.hasSettled();\n this.settled = true;\n if (!wasSettled) this.notifySettled();\n }\n\n private notifySettled(): void {\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 /**\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 const versionBefore = this.kindVersions.get(kind) ?? 0;\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 // The kind's single-kind fetch answered (rows, a miss, or a failure):\n // a block waiting on THIS kind re-routes now. A landing already bumped\n // inside ingestColdRows; a miss or failure changed nothing in the\n // tier, but the waiting block must still be told to re-ask.\n if ((this.kindVersions.get(kind) ?? 0) === versionBefore) {\n this.bumpKind(kind);\n }\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.markSettled();\n this.warmFailures = 0;\n if (this.warmRetryTimer) {\n clearTimeout(this.warmRetryTimer);\n this.warmRetryTimer = null;\n }\n })\n .catch((error: unknown) => {\n // The host owns presentation and severity through reportError. A\n // direct console.error here duplicates that report and prevents a\n // host from classifying a still-serving compiled fallback as a\n // recoverable warning.\n const message = `component-resolver warm load failed (compiled bootstrap still serving): ${describe(error)}`;\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.markSettled();\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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/content-ir-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "The shared AI Matrx Content IR RENDER layer: kind routing, the component resolver, provisional (streaming) rendering, the runtime-wrapper chrome, and the generic structured floor — as portable React.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|