@ai-matrx/content-ir-react 0.1.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 +37 -0
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/index.cjs +769 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +782 -0
- package/dist/index.d.ts +782 -0
- package/dist/index.js +732 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var contentIr = require('@ai-matrx/content-ir');
|
|
6
|
+
|
|
7
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
+
|
|
9
|
+
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
10
|
+
|
|
11
|
+
// host/error-report.ts
|
|
12
|
+
var consoleErrorReporter = (report) => {
|
|
13
|
+
console.error(`[content-ir] ${report.message}`, report.raw ?? "");
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// host/host-types.ts
|
|
17
|
+
function routeEnvOf(host) {
|
|
18
|
+
return {
|
|
19
|
+
kinds: host.kinds,
|
|
20
|
+
components: host.components,
|
|
21
|
+
reportError: host.reportError,
|
|
22
|
+
platform: host.platform
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
var ContentIrHostContext = React.createContext(null);
|
|
26
|
+
function ContentIrRenderProvider({
|
|
27
|
+
host,
|
|
28
|
+
children
|
|
29
|
+
}) {
|
|
30
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ContentIrHostContext.Provider, { value: host, children });
|
|
31
|
+
}
|
|
32
|
+
function useContentIrHostOrNull() {
|
|
33
|
+
return React.useContext(ContentIrHostContext);
|
|
34
|
+
}
|
|
35
|
+
function useContentIrHost() {
|
|
36
|
+
const host = useContentIrHostOrNull();
|
|
37
|
+
if (!host) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
"[content-ir-react] No ContentIrRenderProvider above this component. Mount one at the app root with your host adapter (kind definitions, component resolver, renderBlock, renderValue, reportError)."
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return host;
|
|
43
|
+
}
|
|
44
|
+
function useKindRouteEnv() {
|
|
45
|
+
const host = useContentIrHost();
|
|
46
|
+
return React.useMemo(() => routeEnvOf(host), [host]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// resolver/component-resolver.ts
|
|
50
|
+
var KEY_SEPARATOR = String.fromCharCode(1);
|
|
51
|
+
function keyOf(kind, platform, role) {
|
|
52
|
+
return `${kind}${KEY_SEPARATOR}${platform}${KEY_SEPARATOR}${role}`;
|
|
53
|
+
}
|
|
54
|
+
function describe(error) {
|
|
55
|
+
return error instanceof Error ? error.message : String(error);
|
|
56
|
+
}
|
|
57
|
+
function errorFields(error) {
|
|
58
|
+
if (!(error instanceof Error)) return {};
|
|
59
|
+
return {
|
|
60
|
+
name: error.name,
|
|
61
|
+
...error.stack === void 0 ? {} : { stack: error.stack }
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
var ComponentResolver = class {
|
|
65
|
+
compiled = null;
|
|
66
|
+
db = /* @__PURE__ */ new Map();
|
|
67
|
+
warmPromise = null;
|
|
68
|
+
warmFailureLogged = false;
|
|
69
|
+
/**
|
|
70
|
+
* When the last successful wholesale refresh landed, or null for "never".
|
|
71
|
+
* Explicitly nullable rather than 0: with a host-supplied clock that starts
|
|
72
|
+
* near zero, a `0` sentinel makes the FIRST refresh look rate-limited and
|
|
73
|
+
* silently skip.
|
|
74
|
+
*/
|
|
75
|
+
lastRefreshAt = null;
|
|
76
|
+
refreshPromise = null;
|
|
77
|
+
listeners = /* @__PURE__ */ new Set();
|
|
78
|
+
/**
|
|
79
|
+
* Cold single-kind fetch dedupe (streaming eager path). In-flight is keyed
|
|
80
|
+
* by (kind, platform) — the fetch unit; misses are keyed by (kind, platform,
|
|
81
|
+
* role) so a miss on web/output never suppresses other roles, and CLEARED on
|
|
82
|
+
* every wholesale refresh (a component created mid-session becomes eagerly
|
|
83
|
+
* fetchable again — misses are cheap to re-verify).
|
|
84
|
+
*/
|
|
85
|
+
coldInFlight = /* @__PURE__ */ new Set();
|
|
86
|
+
coldMisses = /* @__PURE__ */ new Set();
|
|
87
|
+
/** Monotonic db-tier version — the repaint hook's snapshot key. */
|
|
88
|
+
version = 0;
|
|
89
|
+
/** Per-kind versions + listeners (granular repaint) + wholesale epoch. */
|
|
90
|
+
kindVersions = /* @__PURE__ */ new Map();
|
|
91
|
+
kindListeners = /* @__PURE__ */ new Map();
|
|
92
|
+
epoch = 0;
|
|
93
|
+
reportError;
|
|
94
|
+
now;
|
|
95
|
+
options;
|
|
96
|
+
// Explicit field, not a parameter property: consumers compile this source
|
|
97
|
+
// directly, and a strict host (the dashboard) sets `erasableSyntaxOnly`,
|
|
98
|
+
// under which parameter properties are a hard error.
|
|
99
|
+
constructor(options = {}) {
|
|
100
|
+
this.options = options;
|
|
101
|
+
this.reportError = options.reportError ?? consoleErrorReporter;
|
|
102
|
+
this.now = options.now ?? (() => Date.now());
|
|
103
|
+
}
|
|
104
|
+
compiledMap() {
|
|
105
|
+
if (!this.compiled) {
|
|
106
|
+
this.compiled = /* @__PURE__ */ new Map();
|
|
107
|
+
for (const entry of this.options.compiledEntries?.() ?? []) {
|
|
108
|
+
this.compiled.set(keyOf(entry.kind, entry.platform, entry.role), entry);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return this.compiled;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Synchronous resolve — the render seam's per-block call. DB override first
|
|
115
|
+
* (once warm), compiled floor second, null for unknown.
|
|
116
|
+
*/
|
|
117
|
+
resolve(kind, platform, role) {
|
|
118
|
+
const key = keyOf(kind, platform, role);
|
|
119
|
+
const dbRow = this.db.get(key);
|
|
120
|
+
if (dbRow) {
|
|
121
|
+
return {
|
|
122
|
+
componentKey: dbRow.componentKey,
|
|
123
|
+
source: dbRow.source,
|
|
124
|
+
config: dbRow.config,
|
|
125
|
+
isActive: dbRow.isActive,
|
|
126
|
+
resolvedBy: "db",
|
|
127
|
+
componentSource: dbRow.componentSource,
|
|
128
|
+
propsTransform: dbRow.propsTransform,
|
|
129
|
+
pinnedKindVersion: dbRow.pinnedKindVersion,
|
|
130
|
+
updatedAt: dbRow.updatedAt,
|
|
131
|
+
createdBy: dbRow.createdBy
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const compiledEntry = this.compiledMap().get(key);
|
|
135
|
+
if (compiledEntry) {
|
|
136
|
+
return {
|
|
137
|
+
componentKey: compiledEntry.componentKey,
|
|
138
|
+
source: compiledEntry.source,
|
|
139
|
+
config: compiledEntry.config,
|
|
140
|
+
isActive: true,
|
|
141
|
+
// trusted at bootstrap (R6)
|
|
142
|
+
resolvedBy: "compiled",
|
|
143
|
+
componentSource: null,
|
|
144
|
+
propsTransform: null,
|
|
145
|
+
pinnedKindVersion: null,
|
|
146
|
+
updatedAt: null,
|
|
147
|
+
createdBy: null
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
/** R6 floor check: compiled-bootstrap membership = always render-trusted. */
|
|
153
|
+
hasCompiled(kind, platform, role) {
|
|
154
|
+
return this.compiledMap().has(keyOf(kind, platform, role));
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Pure ingest — the warm landing point and the unit-test seam. First row per
|
|
158
|
+
* key wins: rows arrive is_default-first / sort_order-asc from the source.
|
|
159
|
+
*/
|
|
160
|
+
ingestDbRows(rows) {
|
|
161
|
+
const changedKinds = /* @__PURE__ */ new Set();
|
|
162
|
+
for (const row of rows) {
|
|
163
|
+
const key = keyOf(row.kind, row.platform, row.role);
|
|
164
|
+
if (!this.db.has(key)) {
|
|
165
|
+
this.db.set(key, row);
|
|
166
|
+
changedKinds.add(row.kind);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (changedKinds.size > 0) {
|
|
170
|
+
for (const kind of changedKinds) this.bumpKind(kind);
|
|
171
|
+
this.notifyChanged();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Refresh landing point: REPLACE the db tier wholesale (same
|
|
176
|
+
* first-row-per-key contract as {@link ingestDbRows}) so edits, deletions,
|
|
177
|
+
* and is_active flips all take effect. Always notifies.
|
|
178
|
+
*/
|
|
179
|
+
replaceDbRows(rows) {
|
|
180
|
+
this.db.clear();
|
|
181
|
+
this.coldMisses.clear();
|
|
182
|
+
for (const row of rows) {
|
|
183
|
+
const key = keyOf(row.kind, row.platform, row.role);
|
|
184
|
+
if (!this.db.has(key)) this.db.set(key, row);
|
|
185
|
+
}
|
|
186
|
+
this.epoch += 1;
|
|
187
|
+
for (const set of this.kindListeners.values()) {
|
|
188
|
+
for (const listener of set) listener();
|
|
189
|
+
}
|
|
190
|
+
this.notifyChanged();
|
|
191
|
+
}
|
|
192
|
+
getVersion() {
|
|
193
|
+
return this.version;
|
|
194
|
+
}
|
|
195
|
+
getKindVersion(kind) {
|
|
196
|
+
return this.epoch + (this.kindVersions.get(kind) ?? 0);
|
|
197
|
+
}
|
|
198
|
+
subscribeKind(kind, listener) {
|
|
199
|
+
const set = this.kindListeners.get(kind) ?? /* @__PURE__ */ new Set();
|
|
200
|
+
set.add(listener);
|
|
201
|
+
this.kindListeners.set(kind, set);
|
|
202
|
+
return () => {
|
|
203
|
+
set.delete(listener);
|
|
204
|
+
if (set.size === 0) this.kindListeners.delete(kind);
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
subscribe(listener) {
|
|
208
|
+
this.listeners.add(listener);
|
|
209
|
+
return () => {
|
|
210
|
+
this.listeners.delete(listener);
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
bumpKind(kind) {
|
|
214
|
+
this.kindVersions.set(kind, (this.kindVersions.get(kind) ?? 0) + 1);
|
|
215
|
+
const listeners = this.kindListeners.get(kind);
|
|
216
|
+
if (listeners) for (const listener of listeners) listener();
|
|
217
|
+
}
|
|
218
|
+
notifyChanged() {
|
|
219
|
+
this.version += 1;
|
|
220
|
+
for (const listener of this.listeners) listener();
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The eager lightweight single-kind fetch (streaming path): the moment a
|
|
224
|
+
* cloud kind is identified mid-stream, pull ONLY that kind's resolver rows
|
|
225
|
+
* and ingest them so {@link resolve} can answer before — or shortly after —
|
|
226
|
+
* the region completes. Deduped in-flight and by known-miss. Fire-and-forget;
|
|
227
|
+
* failures are loud (the warm list remains the backstop).
|
|
228
|
+
*/
|
|
229
|
+
requestComponent(kind, platform, role) {
|
|
230
|
+
const loadForKind = this.options.loadForKind;
|
|
231
|
+
if (!loadForKind) return;
|
|
232
|
+
if (this.resolve(kind, platform, role)) return;
|
|
233
|
+
const missKey = keyOf(kind, platform, role);
|
|
234
|
+
const flightKey = `${kind}${KEY_SEPARATOR}${platform}`;
|
|
235
|
+
if (this.coldInFlight.has(flightKey) || this.coldMisses.has(missKey)) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
this.coldInFlight.add(flightKey);
|
|
239
|
+
void (async () => {
|
|
240
|
+
try {
|
|
241
|
+
this.ingestDbRows(await loadForKind(kind, platform));
|
|
242
|
+
if (!this.resolve(kind, platform, role)) this.coldMisses.add(missKey);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
this.reportError({
|
|
245
|
+
source: "content-ir",
|
|
246
|
+
message: `component-resolver cold fetch failed for "${kind}": ${describe(error)}`,
|
|
247
|
+
...errorFields(error),
|
|
248
|
+
raw: error
|
|
249
|
+
});
|
|
250
|
+
} finally {
|
|
251
|
+
this.coldInFlight.delete(flightKey);
|
|
252
|
+
}
|
|
253
|
+
})();
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Refresh-on-view: re-fetch the warm list and REPLACE the db tier, so an
|
|
257
|
+
* edited `source='db'` component (its `updated_at` bump re-keys the host's
|
|
258
|
+
* compile cache) renders fresh on the next view. Deduped in-flight and
|
|
259
|
+
* rate-limited by `maxAgeMs` (default 10s) — mounting several previews costs
|
|
260
|
+
* one fetch. Server-side edits do NOT push to open clients; the contract is
|
|
261
|
+
* refresh-on-view via this call.
|
|
262
|
+
*/
|
|
263
|
+
refresh(maxAgeMs = 1e4) {
|
|
264
|
+
const loadAll = this.options.loadAll;
|
|
265
|
+
if (!loadAll) return Promise.resolve();
|
|
266
|
+
if (this.refreshPromise) return this.refreshPromise;
|
|
267
|
+
if (this.lastRefreshAt !== null && this.now() - this.lastRefreshAt < maxAgeMs) {
|
|
268
|
+
return Promise.resolve();
|
|
269
|
+
}
|
|
270
|
+
this.coldMisses.clear();
|
|
271
|
+
this.refreshPromise = loadAll().then((rows) => {
|
|
272
|
+
this.lastRefreshAt = this.now();
|
|
273
|
+
this.replaceDbRows(rows);
|
|
274
|
+
}).catch((error) => {
|
|
275
|
+
this.reportError({
|
|
276
|
+
source: "content-ir",
|
|
277
|
+
message: `component-resolver refresh failed (current resolver tier still serving): ${describe(error)}`,
|
|
278
|
+
...errorFields(error),
|
|
279
|
+
raw: error
|
|
280
|
+
});
|
|
281
|
+
}).finally(() => {
|
|
282
|
+
this.refreshPromise = null;
|
|
283
|
+
});
|
|
284
|
+
return this.refreshPromise;
|
|
285
|
+
}
|
|
286
|
+
/** One list fetch per app session; failed loads retry on the next call. */
|
|
287
|
+
ensureWarm() {
|
|
288
|
+
const loadAll = this.options.loadAll;
|
|
289
|
+
if (!loadAll) return Promise.resolve();
|
|
290
|
+
if (!this.warmPromise) {
|
|
291
|
+
this.warmPromise = loadAll().then((rows) => {
|
|
292
|
+
this.ingestDbRows(rows);
|
|
293
|
+
}).catch((error) => {
|
|
294
|
+
const message = `component-resolver warm load failed (compiled bootstrap still serving): ${describe(error)}`;
|
|
295
|
+
if (!this.warmFailureLogged) {
|
|
296
|
+
this.warmFailureLogged = true;
|
|
297
|
+
console.error(`[content-ir] ${message}`);
|
|
298
|
+
}
|
|
299
|
+
this.reportError({
|
|
300
|
+
source: "content-ir",
|
|
301
|
+
message,
|
|
302
|
+
...errorFields(error),
|
|
303
|
+
raw: error
|
|
304
|
+
});
|
|
305
|
+
this.warmPromise = null;
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return this.warmPromise;
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
var IR_ROUTE_KEY = "__ir_route";
|
|
312
|
+
var GENERIC_STRUCTURED_COMPONENT_KEY = "generic_structured";
|
|
313
|
+
var DB_KIND_COMPONENT_KEY = "db_kind_component";
|
|
314
|
+
function isDbSourceResolution(resolution) {
|
|
315
|
+
return resolution !== null && resolution.resolvedBy === "db" && resolution.source === "db" && resolution.isActive;
|
|
316
|
+
}
|
|
317
|
+
var reportedSourcelessDbRows = /* @__PURE__ */ new Set();
|
|
318
|
+
function reportDbRowWithoutSource(kind, env) {
|
|
319
|
+
const message = `[content-ir] kind_component for "${kind}" declares source='db' + is_active but has NO component_source \u2014 data defect; falling through to bundled rendering.`;
|
|
320
|
+
if (!reportedSourcelessDbRows.has(kind)) {
|
|
321
|
+
reportedSourcelessDbRows.add(kind);
|
|
322
|
+
console.error(message);
|
|
323
|
+
}
|
|
324
|
+
env.reportError({ source: "content-ir", message, raw: { kind } });
|
|
325
|
+
}
|
|
326
|
+
function resetSourcelessDbRowReports() {
|
|
327
|
+
reportedSourcelessDbRows.clear();
|
|
328
|
+
}
|
|
329
|
+
function routeToDbComponent(block, kind, resolution, env) {
|
|
330
|
+
if (!isDbSourceResolution(resolution)) return null;
|
|
331
|
+
if (!resolution.componentSource || !resolution.componentSource.trim()) {
|
|
332
|
+
reportDbRowWithoutSource(kind, env);
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
if (block.type === DB_KIND_COMPONENT_KEY) return block;
|
|
336
|
+
return {
|
|
337
|
+
...block,
|
|
338
|
+
type: DB_KIND_COMPONENT_KEY,
|
|
339
|
+
// The compiled/sandboxed component reads the envelope, never the raw
|
|
340
|
+
// region's annotation serverData (same poison rule as bridged kinds).
|
|
341
|
+
serverData: void 0,
|
|
342
|
+
metadata: withRouteMarker(block.metadata, resolution)
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function isRecord(value) {
|
|
346
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
347
|
+
}
|
|
348
|
+
function isRouteMarker(value) {
|
|
349
|
+
return isRecord(value) && typeof value.by === "string" && typeof value.key === "string";
|
|
350
|
+
}
|
|
351
|
+
function readIrRouteMarker(metadata) {
|
|
352
|
+
const candidate = metadata?.[IR_ROUTE_KEY];
|
|
353
|
+
return isRouteMarker(candidate) ? candidate : null;
|
|
354
|
+
}
|
|
355
|
+
function withRouteMarker(metadata, resolution) {
|
|
356
|
+
return {
|
|
357
|
+
...metadata,
|
|
358
|
+
[IR_ROUTE_KEY]: {
|
|
359
|
+
by: resolution.resolvedBy,
|
|
360
|
+
key: resolution.componentKey
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
function routeToGeneric(block, reason) {
|
|
365
|
+
if (block.type === GENERIC_STRUCTURED_COMPONENT_KEY) return block;
|
|
366
|
+
return {
|
|
367
|
+
...block,
|
|
368
|
+
type: GENERIC_STRUCTURED_COMPONENT_KEY,
|
|
369
|
+
serverData: void 0,
|
|
370
|
+
metadata: {
|
|
371
|
+
...block.metadata,
|
|
372
|
+
[IR_ROUTE_KEY]: {
|
|
373
|
+
by: "generic",
|
|
374
|
+
key: GENERIC_STRUCTURED_COMPONENT_KEY,
|
|
375
|
+
unverified: true,
|
|
376
|
+
reason
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function applyIrKindRoute(block, env, options) {
|
|
382
|
+
if (options?.ownedTypes?.includes(block.type)) return block;
|
|
383
|
+
const envelope = contentIr.readEnvelope(block.metadata);
|
|
384
|
+
if (!envelope) return block;
|
|
385
|
+
const kind = envelope.root.kind;
|
|
386
|
+
if (!kind) return block;
|
|
387
|
+
if (envelope.root.kindState === "pending_schema") return block;
|
|
388
|
+
const def = env.kinds.getDefinition(kind);
|
|
389
|
+
const resolution = env.components.resolve(kind, env.platform, "output");
|
|
390
|
+
const dbRouted = routeToDbComponent(block, kind, resolution, env);
|
|
391
|
+
if (dbRouted) return dbRouted;
|
|
392
|
+
if (def?.legacyBlockType) {
|
|
393
|
+
if (block.type === def.legacyBlockType && block.serverData) return block;
|
|
394
|
+
const serverData = def.toLegacyServerData?.(envelope);
|
|
395
|
+
if (block.type === def.legacyBlockType && serverData === void 0) {
|
|
396
|
+
return block;
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
...block,
|
|
400
|
+
type: def.legacyBlockType,
|
|
401
|
+
serverData,
|
|
402
|
+
...resolution ? { metadata: withRouteMarker(block.metadata, resolution) } : null
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
if (resolution?.isActive) {
|
|
406
|
+
if (block.type === resolution.componentKey) return block;
|
|
407
|
+
return {
|
|
408
|
+
...block,
|
|
409
|
+
type: resolution.componentKey,
|
|
410
|
+
// No compiled bridge exists — the routed component parses `content`
|
|
411
|
+
// itself; the raw region's annotation serverData is CLEARED.
|
|
412
|
+
serverData: void 0,
|
|
413
|
+
metadata: withRouteMarker(block.metadata, resolution)
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
if (def) {
|
|
417
|
+
return routeToGeneric(block, resolution ? "inactive" : "no-component");
|
|
418
|
+
}
|
|
419
|
+
return block;
|
|
420
|
+
}
|
|
421
|
+
function kindServerDataFromStoredValue(value, env) {
|
|
422
|
+
if (!isRecord(value)) return null;
|
|
423
|
+
const kind = contentIr.readObjectKind(value);
|
|
424
|
+
if (!kind) return null;
|
|
425
|
+
const def = env.kinds.getDefinition(kind);
|
|
426
|
+
if (!def?.toLegacyServerData) return null;
|
|
427
|
+
return def.toLegacyServerData(contentIr.envelopeFromCompleteValue(value, kind)) ?? null;
|
|
428
|
+
}
|
|
429
|
+
var IR_PROVISIONAL_KEY = "__ir_provisional";
|
|
430
|
+
function isProvisionalBlock(metadata) {
|
|
431
|
+
return metadata?.[IR_PROVISIONAL_KEY] === true;
|
|
432
|
+
}
|
|
433
|
+
function envelopeFromPartialKind(event) {
|
|
434
|
+
return {
|
|
435
|
+
v: contentIr.IR_VERSION,
|
|
436
|
+
// The partial channel is only ever produced by the Python detector; the
|
|
437
|
+
// envelope's `engine` union has no third member and inventing one would
|
|
438
|
+
// break every existing reader.
|
|
439
|
+
engine: "py-block-detector",
|
|
440
|
+
fingerprint: event.fingerprint,
|
|
441
|
+
root: event.root
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
var partialUnsafeKinds = /* @__PURE__ */ new Set();
|
|
445
|
+
function markKindPartialUnsafe(kind) {
|
|
446
|
+
partialUnsafeKinds.add(kind);
|
|
447
|
+
}
|
|
448
|
+
function resetPartialUnsafeKinds() {
|
|
449
|
+
partialUnsafeKinds.clear();
|
|
450
|
+
}
|
|
451
|
+
function isPartialReadyKind(kind, env) {
|
|
452
|
+
if (!kind || partialUnsafeKinds.has(kind)) return false;
|
|
453
|
+
return env.kinds.getDefinition(kind)?.partialReady === true;
|
|
454
|
+
}
|
|
455
|
+
function resolveAnnouncedKindLoading(block, options) {
|
|
456
|
+
if (options?.streamActive === false) return null;
|
|
457
|
+
const event = contentIr.readPartialKindEvent(block.metadata);
|
|
458
|
+
if (!contentIr.isProvisionalKind(event)) return null;
|
|
459
|
+
const kind = event.root.kind;
|
|
460
|
+
if (!kind) return null;
|
|
461
|
+
const verified = contentIr.readEnvelope(block.metadata);
|
|
462
|
+
if (verified && verified.root.status === "complete") return null;
|
|
463
|
+
return { kind, envelope: envelopeFromPartialKind(event) };
|
|
464
|
+
}
|
|
465
|
+
function resolveProvisionalKindRender(block, env, options) {
|
|
466
|
+
if (options?.streamActive === false) return null;
|
|
467
|
+
const event = contentIr.readPartialKindEvent(block.metadata);
|
|
468
|
+
if (!contentIr.isProvisionalKind(event)) return null;
|
|
469
|
+
const kind = event.root.kind;
|
|
470
|
+
if (!isPartialReadyKind(kind, env)) return null;
|
|
471
|
+
const verified = contentIr.readEnvelope(block.metadata);
|
|
472
|
+
if (verified && verified.root.status === "complete") return null;
|
|
473
|
+
const { [contentIr.IR_PARTIAL_KEY]: _partial, ...rest } = block.metadata ?? {};
|
|
474
|
+
const envelope = envelopeFromPartialKind(event);
|
|
475
|
+
const provisionalBlock = {
|
|
476
|
+
...block,
|
|
477
|
+
// The raw region annotation (`{ language: "json" }`) is not kind data —
|
|
478
|
+
// same poison rule the verified route follows.
|
|
479
|
+
serverData: void 0,
|
|
480
|
+
metadata: {
|
|
481
|
+
...rest,
|
|
482
|
+
[contentIr.IR_ENVELOPE_KEY]: envelope,
|
|
483
|
+
[IR_PROVISIONAL_KEY]: true
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
const routed = applyIrKindRoute(provisionalBlock, env, options);
|
|
487
|
+
if (routed === provisionalBlock) return null;
|
|
488
|
+
if (env.kinds.getDefinition(kind)?.toLegacyServerData && !routed.serverData) {
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
return { block: routed, kind, seq: event.seq, envelope };
|
|
492
|
+
}
|
|
493
|
+
var noopSubscribe = () => () => {
|
|
494
|
+
};
|
|
495
|
+
var zero = () => 0;
|
|
496
|
+
function useContentIrKindVersion(kind, sources) {
|
|
497
|
+
const host = useContentIrHostOrNull();
|
|
498
|
+
const resolved = sources ?? host;
|
|
499
|
+
if (kind && !resolved) {
|
|
500
|
+
throw new Error(
|
|
501
|
+
"[content-ir-react] useContentIrKindVersion needs either a ContentIrRenderProvider above it or explicit `sources`."
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
const subscribe = React.useCallback(
|
|
505
|
+
(onStoreChange) => {
|
|
506
|
+
if (!kind || !resolved) return noopSubscribe();
|
|
507
|
+
const unsubKinds = resolved.kinds.subscribeKind(kind, onStoreChange);
|
|
508
|
+
const unsubComponents = resolved.components.subscribeKind(
|
|
509
|
+
kind,
|
|
510
|
+
onStoreChange
|
|
511
|
+
);
|
|
512
|
+
return () => {
|
|
513
|
+
unsubKinds();
|
|
514
|
+
unsubComponents();
|
|
515
|
+
};
|
|
516
|
+
},
|
|
517
|
+
[resolved, kind]
|
|
518
|
+
);
|
|
519
|
+
const getSnapshot = React.useCallback(
|
|
520
|
+
() => kind && resolved ? (
|
|
521
|
+
// Both counters are monotonic, so the sum is monotonic — a change in
|
|
522
|
+
// either registry produces a new snapshot value for this kind.
|
|
523
|
+
resolved.kinds.getKindVersion(kind) + resolved.components.getKindVersion(kind)
|
|
524
|
+
) : 0,
|
|
525
|
+
[resolved, kind]
|
|
526
|
+
);
|
|
527
|
+
return React.useSyncExternalStore(subscribe, getSnapshot, zero);
|
|
528
|
+
}
|
|
529
|
+
function isRecordValue(value) {
|
|
530
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
531
|
+
}
|
|
532
|
+
function kindIsRoutable(kind, host) {
|
|
533
|
+
if (host.kinds.getDefinition(kind)?.legacyBlockType) return true;
|
|
534
|
+
return Boolean(
|
|
535
|
+
host.components.resolve(kind, host.platform, "output")?.isActive
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
function KindInstanceRender({
|
|
539
|
+
kind,
|
|
540
|
+
value,
|
|
541
|
+
showRoutingNote = true,
|
|
542
|
+
unroutableFallback,
|
|
543
|
+
variant = "card",
|
|
544
|
+
className
|
|
545
|
+
}) {
|
|
546
|
+
const host = useContentIrHost();
|
|
547
|
+
const [routingStatus, setRoutingStatus] = React.useState(
|
|
548
|
+
() => kindIsRoutable(kind, host) ? "routable" : "checking"
|
|
549
|
+
);
|
|
550
|
+
React.useEffect(() => {
|
|
551
|
+
let cancelled = false;
|
|
552
|
+
let warmed = false;
|
|
553
|
+
const syncRoutingStatus = () => {
|
|
554
|
+
if (cancelled) return;
|
|
555
|
+
setRoutingStatus(
|
|
556
|
+
kindIsRoutable(kind, host) ? "routable" : warmed ? "unroutable" : "checking"
|
|
557
|
+
);
|
|
558
|
+
};
|
|
559
|
+
const unsubscribe = host.components.subscribe(syncRoutingStatus);
|
|
560
|
+
syncRoutingStatus();
|
|
561
|
+
host.components.requestComponent(kind, host.platform, "output");
|
|
562
|
+
const wasAlreadyWarm = host.components.getVersion() > 0;
|
|
563
|
+
void Promise.allSettled([
|
|
564
|
+
host.kinds.ensureWarm(),
|
|
565
|
+
host.components.ensureWarm()
|
|
566
|
+
]).then(async () => {
|
|
567
|
+
warmed = true;
|
|
568
|
+
syncRoutingStatus();
|
|
569
|
+
if (wasAlreadyWarm) await host.components.refresh();
|
|
570
|
+
syncRoutingStatus();
|
|
571
|
+
});
|
|
572
|
+
return () => {
|
|
573
|
+
cancelled = true;
|
|
574
|
+
unsubscribe();
|
|
575
|
+
};
|
|
576
|
+
}, [host, kind]);
|
|
577
|
+
const block = isRecordValue(value) ? {
|
|
578
|
+
type: "code",
|
|
579
|
+
content: JSON.stringify(value, null, 2),
|
|
580
|
+
language: "json",
|
|
581
|
+
metadata: {
|
|
582
|
+
[contentIr.IR_ENVELOPE_KEY]: contentIr.envelopeFromCompleteValue(value, kind)
|
|
583
|
+
}
|
|
584
|
+
} : null;
|
|
585
|
+
const frameClass = variant === "bare" ? void 0 : "rounded-md border border-border bg-card p-3";
|
|
586
|
+
if (routingStatus === "unroutable" && unroutableFallback !== void 0) {
|
|
587
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: unroutableFallback });
|
|
588
|
+
}
|
|
589
|
+
const onTheFloor = block === null || routingStatus === "unroutable";
|
|
590
|
+
const notice = "This shape has no custom component yet, so it renders through the universal viewer \u2014 exactly what production shows today.";
|
|
591
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: className ? `space-y-3 ${className}` : "space-y-3", children: [
|
|
592
|
+
showRoutingNote && routingStatus === "unroutable" ? host.renderNotice?.(notice) ?? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-md border border-amber-500/30 bg-amber-500/5 px-3 py-2 text-xs text-amber-800 dark:text-amber-200", children: notice }) : null,
|
|
593
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: frameClass, children: onTheFloor ? host.renderValue({ value, kind }) : host.renderBlock(block) })
|
|
594
|
+
] });
|
|
595
|
+
}
|
|
596
|
+
var ProvisionalKindBoundaryInner = class extends React__default.default.Component {
|
|
597
|
+
state = { failed: false };
|
|
598
|
+
static getDerivedStateFromError() {
|
|
599
|
+
return { failed: true };
|
|
600
|
+
}
|
|
601
|
+
componentDidCatch(error, info) {
|
|
602
|
+
const { kind, reportError } = this.props;
|
|
603
|
+
markKindPartialUnsafe(kind);
|
|
604
|
+
reportError({
|
|
605
|
+
source: "content-ir",
|
|
606
|
+
message: `kind "${kind}" declares partialReady but its component threw on a provisional value \u2014 provisional rendering disabled for this kind (falling back to its loading skeleton). Fix the component or drop partialReady.`,
|
|
607
|
+
name: error.name,
|
|
608
|
+
...error.stack === void 0 ? {} : { stack: error.stack },
|
|
609
|
+
relation: "partial-kind",
|
|
610
|
+
raw: { kind, componentStack: info.componentStack }
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
render() {
|
|
614
|
+
if (this.state.failed) return this.props.fallback;
|
|
615
|
+
return this.props.children;
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
function ProvisionalKindBoundary(props) {
|
|
619
|
+
const host = useContentIrHost();
|
|
620
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ProvisionalKindBoundaryInner, { ...props, reportError: host.reportError });
|
|
621
|
+
}
|
|
622
|
+
function ProvisionalKindFrame({ children }) {
|
|
623
|
+
const host = useContentIrHost();
|
|
624
|
+
const label = "Still arriving";
|
|
625
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", "aria-busy": "true", children: [
|
|
626
|
+
children,
|
|
627
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "pointer-events-none absolute -top-2 right-4 z-10 select-none rounded-full bg-background px-1.5 leading-4", children: host.renderShimmer?.(label) ?? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground", children: label }) })
|
|
628
|
+
] });
|
|
629
|
+
}
|
|
630
|
+
function readStructuredValue(content, metadata) {
|
|
631
|
+
const envelope = contentIr.readEnvelope(metadata);
|
|
632
|
+
if (envelope) {
|
|
633
|
+
return { value: contentIr.reconstructRegionValue(envelope), recovered: true };
|
|
634
|
+
}
|
|
635
|
+
try {
|
|
636
|
+
return { value: JSON.parse(content), recovered: true };
|
|
637
|
+
} catch {
|
|
638
|
+
return { value: null, recovered: false };
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
function GenericStructuredView({
|
|
642
|
+
content,
|
|
643
|
+
metadata,
|
|
644
|
+
streamingIndicator,
|
|
645
|
+
className
|
|
646
|
+
}) {
|
|
647
|
+
const host = useContentIrHost();
|
|
648
|
+
const envelope = contentIr.readEnvelope(metadata);
|
|
649
|
+
const status = envelope?.root.status ?? "complete";
|
|
650
|
+
const { value, recovered } = readStructuredValue(content, metadata);
|
|
651
|
+
const kind = envelope?.root.kind ?? (typeof value === "object" && value !== null && !Array.isArray(value) ? contentIr.readObjectKind(value) : null) ?? "";
|
|
652
|
+
const marker = readIrRouteMarker(metadata);
|
|
653
|
+
const note = marker?.reason === "inactive" ? "a custom view is registered but held inactive" : "no custom view yet";
|
|
654
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: className ? `my-2 min-w-0 ${className}` : "my-2 min-w-0", children: [
|
|
655
|
+
status === "streaming" ? streamingIndicator ?? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 text-xs text-muted-foreground", children: "Still arriving\u2026" }) : null,
|
|
656
|
+
recovered ? host.renderValue({ value, ...kind ? { kind } : {}, note }) : (
|
|
657
|
+
// Zero-data-loss backstop: the region never parsed, so show the source
|
|
658
|
+
// verbatim rather than swallowing it.
|
|
659
|
+
/* @__PURE__ */ jsxRuntime.jsx("pre", { className: "max-h-96 overflow-auto font-mono text-xs leading-relaxed text-muted-foreground", children: content })
|
|
660
|
+
)
|
|
661
|
+
] });
|
|
662
|
+
}
|
|
663
|
+
function isRecord2(value) {
|
|
664
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
665
|
+
}
|
|
666
|
+
function readWrapperFrom(serverData, read) {
|
|
667
|
+
if (!isRecord2(serverData)) return null;
|
|
668
|
+
return read(serverData.wrapper);
|
|
669
|
+
}
|
|
670
|
+
function DelegatedOutput({
|
|
671
|
+
output,
|
|
672
|
+
declaredKind,
|
|
673
|
+
fallback,
|
|
674
|
+
emptyLabel = "This step ran, and handed its result to the next one."
|
|
675
|
+
}) {
|
|
676
|
+
const host = useContentIrHost();
|
|
677
|
+
if (output === null || output === void 0) {
|
|
678
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: emptyLabel });
|
|
679
|
+
}
|
|
680
|
+
if (!isRecord2(output)) {
|
|
681
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: host.renderValue({ value: output }) });
|
|
682
|
+
}
|
|
683
|
+
const kind = contentIr.readObjectKind(output) ?? declaredKind;
|
|
684
|
+
if (!kind) {
|
|
685
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback?.(output) ?? host.renderValue({ value: output }) });
|
|
686
|
+
}
|
|
687
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
688
|
+
KindInstanceRender,
|
|
689
|
+
{
|
|
690
|
+
kind,
|
|
691
|
+
value: output,
|
|
692
|
+
showRoutingNote: false,
|
|
693
|
+
variant: "bare",
|
|
694
|
+
...fallback === void 0 ? {} : { unroutableFallback: fallback(output) }
|
|
695
|
+
}
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
function NodeOutcomeView({ serverData, fallback }) {
|
|
699
|
+
const wrapper = readWrapperFrom(
|
|
700
|
+
serverData,
|
|
701
|
+
contentIr.readNodeOutcomeValue
|
|
702
|
+
);
|
|
703
|
+
if (!wrapper) return null;
|
|
704
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
705
|
+
DelegatedOutput,
|
|
706
|
+
{
|
|
707
|
+
output: wrapper.output,
|
|
708
|
+
declaredKind: wrapper.output_kind,
|
|
709
|
+
...fallback === void 0 ? {} : { fallback }
|
|
710
|
+
}
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
function RunResultView({ serverData, fallback }) {
|
|
714
|
+
const wrapper = readWrapperFrom(
|
|
715
|
+
serverData,
|
|
716
|
+
contentIr.readRunResultValue
|
|
717
|
+
);
|
|
718
|
+
if (!wrapper) return null;
|
|
719
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: wrapper.outputs.length > 0 ? wrapper.outputs.map((outcome) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
720
|
+
NodeOutcomeView,
|
|
721
|
+
{
|
|
722
|
+
serverData: { wrapper: outcome },
|
|
723
|
+
...fallback === void 0 ? {} : { fallback }
|
|
724
|
+
},
|
|
725
|
+
`${outcome.node_id}:${outcome.attempt}`
|
|
726
|
+
)) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
727
|
+
DelegatedOutput,
|
|
728
|
+
{
|
|
729
|
+
output: wrapper.output,
|
|
730
|
+
declaredKind: wrapper.output_kind,
|
|
731
|
+
...fallback === void 0 ? {} : { fallback }
|
|
732
|
+
}
|
|
733
|
+
) });
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
exports.ComponentResolver = ComponentResolver;
|
|
737
|
+
exports.ContentIrRenderProvider = ContentIrRenderProvider;
|
|
738
|
+
exports.DB_KIND_COMPONENT_KEY = DB_KIND_COMPONENT_KEY;
|
|
739
|
+
exports.DelegatedOutput = DelegatedOutput;
|
|
740
|
+
exports.GENERIC_STRUCTURED_COMPONENT_KEY = GENERIC_STRUCTURED_COMPONENT_KEY;
|
|
741
|
+
exports.GenericStructuredView = GenericStructuredView;
|
|
742
|
+
exports.IR_PROVISIONAL_KEY = IR_PROVISIONAL_KEY;
|
|
743
|
+
exports.IR_ROUTE_KEY = IR_ROUTE_KEY;
|
|
744
|
+
exports.KindInstanceRender = KindInstanceRender;
|
|
745
|
+
exports.NodeOutcomeView = NodeOutcomeView;
|
|
746
|
+
exports.ProvisionalKindBoundary = ProvisionalKindBoundary;
|
|
747
|
+
exports.ProvisionalKindFrame = ProvisionalKindFrame;
|
|
748
|
+
exports.RunResultView = RunResultView;
|
|
749
|
+
exports.applyIrKindRoute = applyIrKindRoute;
|
|
750
|
+
exports.consoleErrorReporter = consoleErrorReporter;
|
|
751
|
+
exports.envelopeFromPartialKind = envelopeFromPartialKind;
|
|
752
|
+
exports.isPartialReadyKind = isPartialReadyKind;
|
|
753
|
+
exports.isProvisionalBlock = isProvisionalBlock;
|
|
754
|
+
exports.isRecordValue = isRecordValue;
|
|
755
|
+
exports.kindIsRoutable = kindIsRoutable;
|
|
756
|
+
exports.kindServerDataFromStoredValue = kindServerDataFromStoredValue;
|
|
757
|
+
exports.markKindPartialUnsafe = markKindPartialUnsafe;
|
|
758
|
+
exports.readIrRouteMarker = readIrRouteMarker;
|
|
759
|
+
exports.resetPartialUnsafeKinds = resetPartialUnsafeKinds;
|
|
760
|
+
exports.resetSourcelessDbRowReports = resetSourcelessDbRowReports;
|
|
761
|
+
exports.resolveAnnouncedKindLoading = resolveAnnouncedKindLoading;
|
|
762
|
+
exports.resolveProvisionalKindRender = resolveProvisionalKindRender;
|
|
763
|
+
exports.routeEnvOf = routeEnvOf;
|
|
764
|
+
exports.useContentIrHost = useContentIrHost;
|
|
765
|
+
exports.useContentIrHostOrNull = useContentIrHostOrNull;
|
|
766
|
+
exports.useContentIrKindVersion = useContentIrKindVersion;
|
|
767
|
+
exports.useKindRouteEnv = useKindRouteEnv;
|
|
768
|
+
//# sourceMappingURL=index.cjs.map
|
|
769
|
+
//# sourceMappingURL=index.cjs.map
|