@forgeax/engine-assets-runtime 0.1.2
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/LICENSE +202 -0
- package/README.md +213 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/asset-graph-red.integration.test.d.ts +2 -0
- package/dist/__tests__/asset-graph-red.integration.test.d.ts.map +1 -0
- package/dist/__tests__/asset-kind.test.d.ts +2 -0
- package/dist/__tests__/asset-kind.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-core.integration.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-core.integration.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-public-api.test-d.d.ts +2 -0
- package/dist/__tests__/asset-registry-public-api.test-d.d.ts.map +1 -0
- package/dist/__tests__/asset-runtime-core-lifecycle.integration.test.d.ts +2 -0
- package/dist/__tests__/asset-runtime-core-lifecycle.integration.test.d.ts.map +1 -0
- package/dist/__tests__/asset-runtime-snapshot.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-runtime-snapshot.unit.test.d.ts.map +1 -0
- package/dist/__tests__/catalog-session-red.unit.test.d.ts +2 -0
- package/dist/__tests__/catalog-session-red.unit.test.d.ts.map +1 -0
- package/dist/__tests__/decode-image-mime-owner.test.d.ts +2 -0
- package/dist/__tests__/decode-image-mime-owner.test.d.ts.map +1 -0
- package/dist/__tests__/registry-lifecycle-red.integration.test.d.ts +2 -0
- package/dist/__tests__/registry-lifecycle-red.integration.test.d.ts.map +1 -0
- package/dist/asset-kind.d.ts +4 -0
- package/dist/asset-kind.d.ts.map +1 -0
- package/dist/catalog-source.d.ts +23 -0
- package/dist/catalog-source.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +1240 -0
- package/dist/index.mjs.map +1 -0
- package/dist/internal/artifact-cache.d.ts +17 -0
- package/dist/internal/artifact-cache.d.ts.map +1 -0
- package/dist/internal/asset-graph.d.ts +70 -0
- package/dist/internal/asset-graph.d.ts.map +1 -0
- package/dist/internal/catalog-session.d.ts +60 -0
- package/dist/internal/catalog-session.d.ts.map +1 -0
- package/dist/internal/decoder-registry.d.ts +19 -0
- package/dist/internal/decoder-registry.d.ts.map +1 -0
- package/dist/internal/immutable-payload.d.ts +9 -0
- package/dist/internal/immutable-payload.d.ts.map +1 -0
- package/dist/internal/load-asset.d.ts +31 -0
- package/dist/internal/load-asset.d.ts.map +1 -0
- package/dist/internal/pack-reader.d.ts +18 -0
- package/dist/internal/pack-reader.d.ts.map +1 -0
- package/dist/internal/validate-runtime-row.d.ts +7 -0
- package/dist/internal/validate-runtime-row.d.ts.map +1 -0
- package/dist/internal.d.ts +2 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.mjs +15 -0
- package/dist/internal.mjs.map +1 -0
- package/package.json +63 -0
- package/src/__tests__/asset-graph-red.integration.test.ts +113 -0
- package/src/__tests__/asset-kind.test.ts +14 -0
- package/src/__tests__/asset-registry-core.integration.test.ts +161 -0
- package/src/__tests__/asset-registry-public-api.test-d.ts +31 -0
- package/src/__tests__/asset-runtime-core-lifecycle.integration.test.ts +79 -0
- package/src/__tests__/asset-runtime-snapshot.unit.test.ts +23 -0
- package/src/__tests__/catalog-session-red.unit.test.ts +276 -0
- package/src/__tests__/decode-image-mime-owner.test.ts +41 -0
- package/src/__tests__/registry-lifecycle-red.integration.test.ts +80 -0
- package/src/asset-kind.ts +6 -0
- package/src/catalog-source.ts +145 -0
- package/src/index.ts +22 -0
- package/src/internal/artifact-cache.ts +65 -0
- package/src/internal/asset-graph.ts +420 -0
- package/src/internal/catalog-session.ts +345 -0
- package/src/internal/decoder-registry.ts +175 -0
- package/src/internal/immutable-payload.ts +20 -0
- package/src/internal/load-asset.ts +254 -0
- package/src/internal/pack-reader.ts +183 -0
- package/src/internal/validate-runtime-row.ts +51 -0
- package/src/internal.ts +4 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1240 @@
|
|
|
1
|
+
import { ok, err, AssetError, ASSET_ERROR_HINTS } from '@forgeax/engine-types';
|
|
2
|
+
|
|
3
|
+
// src/asset-kind.ts
|
|
4
|
+
function defineAssetKind(kind) {
|
|
5
|
+
return Object.freeze({ kind });
|
|
6
|
+
}
|
|
7
|
+
function createCatalogSource(options) {
|
|
8
|
+
const entries = options.entries;
|
|
9
|
+
return {
|
|
10
|
+
async enumerate() {
|
|
11
|
+
if (entries !== void 0) {
|
|
12
|
+
if (options.expectedRevision === void 0) return ok(entries);
|
|
13
|
+
const actualRevisions = entries.flatMap(
|
|
14
|
+
(entry) => entry.revision === void 0 ? [] : [entry.revision]
|
|
15
|
+
);
|
|
16
|
+
const matches = actualRevisions.length > 0 && actualRevisions.every(
|
|
17
|
+
(revision) => revision.digest === options.expectedRevision?.digest && revision.observedAt === options.expectedRevision?.observedAt && revision.rootId === options.expectedRevision?.rootId
|
|
18
|
+
);
|
|
19
|
+
if (!matches) {
|
|
20
|
+
return err(
|
|
21
|
+
new AssetError({
|
|
22
|
+
code: "asset-parse-failed",
|
|
23
|
+
expected: "static catalog entries to carry the expected producer revision",
|
|
24
|
+
hint: "restore a verified catalog revision before applying the source",
|
|
25
|
+
detail: { expectedRevision: options.expectedRevision, actualRevisions }
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return ok(entries);
|
|
30
|
+
}
|
|
31
|
+
if (options.url === void 0) {
|
|
32
|
+
return err(
|
|
33
|
+
new AssetError({
|
|
34
|
+
code: "catalog-source-unconfigured",
|
|
35
|
+
expected: "a configured catalog source",
|
|
36
|
+
hint: ASSET_ERROR_HINTS["catalog-source-unconfigured"]
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const response = await (options.fetch ?? globalThis.fetch)(options.url);
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
return err(
|
|
43
|
+
new AssetError({
|
|
44
|
+
code: "asset-fetch-failed",
|
|
45
|
+
expected: "the configured Catalog URL to return HTTP 200",
|
|
46
|
+
hint: "verify the producer Catalog URL and republish the current snapshot",
|
|
47
|
+
detail: { sourcePath: options.url }
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
let raw;
|
|
52
|
+
try {
|
|
53
|
+
raw = await response.json();
|
|
54
|
+
} catch {
|
|
55
|
+
return err(
|
|
56
|
+
new AssetError({
|
|
57
|
+
code: "asset-parse-failed",
|
|
58
|
+
expected: "the configured Catalog URL to return JSON",
|
|
59
|
+
hint: "repair the producer Catalog before loading the current publication"
|
|
60
|
+
})
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const rows = scopedRows(raw, options.expectedScope);
|
|
64
|
+
if (!rows.ok) return rows;
|
|
65
|
+
const result = rows.value.map((row) => ({ ...row }));
|
|
66
|
+
return ok(result);
|
|
67
|
+
},
|
|
68
|
+
subscribe(listener) {
|
|
69
|
+
return options.subscribe?.(listener) ?? (() => {
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
...options.expectedScope === void 0 ? {} : { expectedScope: options.expectedScope }
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function scopedRows(raw, expectedScope) {
|
|
76
|
+
if (Array.isArray(raw)) return ok(raw);
|
|
77
|
+
if (raw === null || typeof raw !== "object") return parseRowsError("Catalog JSON rows");
|
|
78
|
+
const snapshot = raw;
|
|
79
|
+
if (!Array.isArray(snapshot.entries)) return parseRowsError("Catalog JSON entries");
|
|
80
|
+
if (expectedScope !== void 0) {
|
|
81
|
+
if (snapshot.scopeId !== expectedScope.scopeId || snapshot.generation !== expectedScope.generation || snapshot.authority !== "authoritative") {
|
|
82
|
+
return err(
|
|
83
|
+
new AssetError({
|
|
84
|
+
code: "asset-parse-failed",
|
|
85
|
+
expected: "the Catalog snapshot to match the active scope and generation",
|
|
86
|
+
hint: "restore the authoritative producer Catalog for the active runtime scope",
|
|
87
|
+
detail: {
|
|
88
|
+
expectedScope,
|
|
89
|
+
actualScope: { scopeId: snapshot.scopeId, generation: snapshot.generation }
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return ok(snapshot.entries);
|
|
96
|
+
}
|
|
97
|
+
function parseRowsError(expected) {
|
|
98
|
+
return err(
|
|
99
|
+
new AssetError({
|
|
100
|
+
code: "asset-parse-failed",
|
|
101
|
+
expected,
|
|
102
|
+
hint: "repair the producer Catalog before loading the current publication"
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/internal/artifact-cache.ts
|
|
108
|
+
var ArtifactCache = class {
|
|
109
|
+
values = /* @__PURE__ */ new Map();
|
|
110
|
+
pending = /* @__PURE__ */ new Map();
|
|
111
|
+
hits = 0;
|
|
112
|
+
misses = 0;
|
|
113
|
+
read(contentAddress, reader) {
|
|
114
|
+
const value = this.values.get(contentAddress);
|
|
115
|
+
if (value !== void 0) {
|
|
116
|
+
this.hits += 1;
|
|
117
|
+
return Promise.resolve({ ok: true, value: new Uint8Array(value) });
|
|
118
|
+
}
|
|
119
|
+
const pending = this.pending.get(contentAddress);
|
|
120
|
+
if (pending !== void 0) {
|
|
121
|
+
this.hits += 1;
|
|
122
|
+
return pending;
|
|
123
|
+
}
|
|
124
|
+
this.misses += 1;
|
|
125
|
+
const request = Promise.resolve().then(reader).then((result) => {
|
|
126
|
+
if (result.ok) this.values.set(contentAddress, new Uint8Array(result.value));
|
|
127
|
+
return result;
|
|
128
|
+
}).finally(() => {
|
|
129
|
+
if (this.pending.get(contentAddress) === request) this.pending.delete(contentAddress);
|
|
130
|
+
});
|
|
131
|
+
this.pending.set(contentAddress, request);
|
|
132
|
+
return request;
|
|
133
|
+
}
|
|
134
|
+
clear(contentAddress) {
|
|
135
|
+
if (contentAddress === void 0) {
|
|
136
|
+
this.values.clear();
|
|
137
|
+
this.pending.clear();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
this.values.delete(contentAddress);
|
|
141
|
+
this.pending.delete(contentAddress);
|
|
142
|
+
}
|
|
143
|
+
snapshot() {
|
|
144
|
+
return Object.freeze({
|
|
145
|
+
entries: this.values.size,
|
|
146
|
+
pending: this.pending.size,
|
|
147
|
+
hits: this.hits,
|
|
148
|
+
misses: this.misses
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// src/internal/immutable-payload.ts
|
|
154
|
+
function freezeRuntimePayload(value) {
|
|
155
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
156
|
+
return freeze(value, seen);
|
|
157
|
+
}
|
|
158
|
+
function freeze(value, seen) {
|
|
159
|
+
if (value === null || typeof value !== "object") return value;
|
|
160
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) return value;
|
|
161
|
+
if (seen.has(value)) return value;
|
|
162
|
+
seen.add(value);
|
|
163
|
+
for (const child of Object.values(value)) freeze(child, seen);
|
|
164
|
+
return Object.freeze(value);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/internal/asset-graph.ts
|
|
168
|
+
function cancelled(guid) {
|
|
169
|
+
return {
|
|
170
|
+
code: "asset-load-cancelled",
|
|
171
|
+
expected: "the request AbortSignal to remain live until asset closure completes",
|
|
172
|
+
hint: "retry with a live AbortSignal when the request is still needed",
|
|
173
|
+
detail: { guid }
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function disposed(scopeId = "asset-runtime") {
|
|
177
|
+
return {
|
|
178
|
+
code: "asset-runtime-disposed",
|
|
179
|
+
expected: "an active asset runtime scope",
|
|
180
|
+
hint: "obtain a new Registry from the current realm",
|
|
181
|
+
detail: { scopeId }
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function superseded(guid, generation) {
|
|
185
|
+
return {
|
|
186
|
+
code: "asset-superseded",
|
|
187
|
+
expected: "the load ticket to remain current until promotion",
|
|
188
|
+
hint: "load the current publication after the Catalog change",
|
|
189
|
+
detail: { guid, generation }
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function thrown(guid) {
|
|
193
|
+
return {
|
|
194
|
+
code: "asset-decode-failed",
|
|
195
|
+
expected: "the graph reader to return a Result",
|
|
196
|
+
hint: "repair the owner reader and retry the current publication",
|
|
197
|
+
detail: { guid, kind: "asset-graph-reader" }
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function freezeSnapshot(snapshot) {
|
|
201
|
+
return Object.freeze({
|
|
202
|
+
...snapshot,
|
|
203
|
+
ready: Object.freeze([...snapshot.ready]),
|
|
204
|
+
sccs: Object.freeze(snapshot.sccs.map((scc) => Object.freeze([...scc]))),
|
|
205
|
+
counters: Object.freeze({ ...snapshot.counters })
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
var AssetGraph = class {
|
|
209
|
+
read;
|
|
210
|
+
limit;
|
|
211
|
+
ready = /* @__PURE__ */ new Map();
|
|
212
|
+
forward = /* @__PURE__ */ new Map();
|
|
213
|
+
reverse = /* @__PURE__ */ new Map();
|
|
214
|
+
reads = /* @__PURE__ */ new Map();
|
|
215
|
+
requests = /* @__PURE__ */ new Map();
|
|
216
|
+
listeners = /* @__PURE__ */ new Set();
|
|
217
|
+
sccs = [];
|
|
218
|
+
activeReads = 0;
|
|
219
|
+
readWaiters = [];
|
|
220
|
+
disposed = false;
|
|
221
|
+
epoch = 0;
|
|
222
|
+
counters = {
|
|
223
|
+
loads: 0,
|
|
224
|
+
cacheHits: 0,
|
|
225
|
+
readErrors: 0,
|
|
226
|
+
noChange: 0,
|
|
227
|
+
listenerFailures: 0
|
|
228
|
+
};
|
|
229
|
+
currentSnapshot;
|
|
230
|
+
constructor(options) {
|
|
231
|
+
this.read = options.read;
|
|
232
|
+
this.limit = Math.max(1, Math.floor(options.maxConcurrentReads ?? 8));
|
|
233
|
+
this.currentSnapshot = freezeSnapshot({
|
|
234
|
+
epoch: 0,
|
|
235
|
+
ready: [],
|
|
236
|
+
pending: 0,
|
|
237
|
+
resources: 0,
|
|
238
|
+
sccs: [],
|
|
239
|
+
counters: this.counters
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
load(guid, signal = new AbortController().signal) {
|
|
243
|
+
const canonicalGuid = guid.toLowerCase();
|
|
244
|
+
if (this.disposed) return Promise.resolve(err(disposed()));
|
|
245
|
+
if (signal.aborted) return Promise.resolve(err(cancelled(canonicalGuid)));
|
|
246
|
+
const cached = this.ready.get(canonicalGuid);
|
|
247
|
+
if (cached !== void 0) {
|
|
248
|
+
this.counters = {
|
|
249
|
+
...this.counters,
|
|
250
|
+
cacheHits: this.counters.cacheHits + 1,
|
|
251
|
+
noChange: this.counters.noChange + 1
|
|
252
|
+
};
|
|
253
|
+
this.publish();
|
|
254
|
+
return Promise.resolve(ok(cached));
|
|
255
|
+
}
|
|
256
|
+
const existing = this.requests.get(canonicalGuid);
|
|
257
|
+
if (existing !== void 0) {
|
|
258
|
+
this.counters = { ...this.counters, cacheHits: this.counters.cacheHits + 1 };
|
|
259
|
+
return existing;
|
|
260
|
+
}
|
|
261
|
+
this.counters = { ...this.counters, loads: this.counters.loads + 1 };
|
|
262
|
+
const ticketEpoch = this.epoch;
|
|
263
|
+
const request = this.loadClosure(canonicalGuid, signal, ticketEpoch).finally(() => {
|
|
264
|
+
if (this.requests.get(canonicalGuid) === request) this.requests.delete(canonicalGuid);
|
|
265
|
+
this.publish();
|
|
266
|
+
});
|
|
267
|
+
this.requests.set(canonicalGuid, request);
|
|
268
|
+
this.publish();
|
|
269
|
+
return request;
|
|
270
|
+
}
|
|
271
|
+
invalidate(guid) {
|
|
272
|
+
const affected = this.collectAffected([guid.toLowerCase()]);
|
|
273
|
+
this.drop(affected);
|
|
274
|
+
this.epoch += 1;
|
|
275
|
+
this.publish();
|
|
276
|
+
return [...affected].sort();
|
|
277
|
+
}
|
|
278
|
+
invalidateForCatalogChange(guids) {
|
|
279
|
+
if (this.disposed) return;
|
|
280
|
+
const affected = guids === void 0 || guids.length === 0 ? /* @__PURE__ */ new Set([...this.ready.keys(), ...this.requests.keys(), ...this.reads.keys()]) : this.collectAffected(guids.map((item) => item.toLowerCase()));
|
|
281
|
+
this.drop(affected);
|
|
282
|
+
this.epoch += 1;
|
|
283
|
+
this.publish();
|
|
284
|
+
}
|
|
285
|
+
subscribe(listener) {
|
|
286
|
+
this.listeners.add(listener);
|
|
287
|
+
return () => this.listeners.delete(listener);
|
|
288
|
+
}
|
|
289
|
+
snapshot() {
|
|
290
|
+
return this.currentSnapshot;
|
|
291
|
+
}
|
|
292
|
+
lookup(guid) {
|
|
293
|
+
return this.ready.get(guid.toLowerCase())?.value;
|
|
294
|
+
}
|
|
295
|
+
guidOf(value) {
|
|
296
|
+
for (const [guid, entry] of this.ready) {
|
|
297
|
+
if (entry.value === value) return guid;
|
|
298
|
+
}
|
|
299
|
+
return void 0;
|
|
300
|
+
}
|
|
301
|
+
dispose() {
|
|
302
|
+
if (this.disposed) return;
|
|
303
|
+
this.disposed = true;
|
|
304
|
+
this.epoch += 1;
|
|
305
|
+
this.ready.clear();
|
|
306
|
+
this.forward.clear();
|
|
307
|
+
this.reverse.clear();
|
|
308
|
+
this.reads.clear();
|
|
309
|
+
const waiters = this.readWaiters;
|
|
310
|
+
this.readWaiters = [];
|
|
311
|
+
for (const resolve of waiters) resolve();
|
|
312
|
+
this.publish();
|
|
313
|
+
}
|
|
314
|
+
async loadClosure(root, signal, ticketEpoch) {
|
|
315
|
+
const values = /* @__PURE__ */ new Map();
|
|
316
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
317
|
+
const visited = /* @__PURE__ */ new Set();
|
|
318
|
+
const group = /* @__PURE__ */ new Set();
|
|
319
|
+
const result = await this.visit(root, signal, visiting, visited, values, group);
|
|
320
|
+
if (!result.ok) return result;
|
|
321
|
+
if (this.disposed) return err(disposed());
|
|
322
|
+
if (signal.aborted) return err(cancelled(root));
|
|
323
|
+
if (ticketEpoch !== this.epoch) return err(superseded(root, ticketEpoch));
|
|
324
|
+
for (const [guid, value] of values) this.promote(guid, value);
|
|
325
|
+
this.recordScc();
|
|
326
|
+
return ok(values.get(root));
|
|
327
|
+
}
|
|
328
|
+
async visit(guid, signal, visiting, visited, values, group) {
|
|
329
|
+
if (visiting.has(guid)) {
|
|
330
|
+
group.add(guid);
|
|
331
|
+
return ok({ value: void 0, refs: [] });
|
|
332
|
+
}
|
|
333
|
+
if (visited.has(guid)) return ok(values.get(guid));
|
|
334
|
+
const cached = this.ready.get(guid);
|
|
335
|
+
if (cached !== void 0) {
|
|
336
|
+
values.set(guid, cached);
|
|
337
|
+
visited.add(guid);
|
|
338
|
+
return ok(cached);
|
|
339
|
+
}
|
|
340
|
+
visiting.add(guid);
|
|
341
|
+
const read = await this.readOnce(guid, signal);
|
|
342
|
+
if (!read.ok) {
|
|
343
|
+
visiting.delete(guid);
|
|
344
|
+
return read;
|
|
345
|
+
}
|
|
346
|
+
values.set(guid, read.value);
|
|
347
|
+
group.add(guid);
|
|
348
|
+
this.link(guid, read.value.refs);
|
|
349
|
+
for (const ref of read.value.refs) {
|
|
350
|
+
const child = await this.visit(ref, signal, visiting, visited, values, group);
|
|
351
|
+
if (!child.ok) {
|
|
352
|
+
visiting.delete(guid);
|
|
353
|
+
return err({
|
|
354
|
+
code: "asset-dependency-failed",
|
|
355
|
+
expected: "every referenced asset to load successfully",
|
|
356
|
+
hint: "repair the dependency publication and retry the root asset",
|
|
357
|
+
detail: { guid, dependencyGuid: ref }
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
visiting.delete(guid);
|
|
362
|
+
visited.add(guid);
|
|
363
|
+
return ok(read.value);
|
|
364
|
+
}
|
|
365
|
+
readOnce(guid, signal) {
|
|
366
|
+
const existing = this.reads.get(guid);
|
|
367
|
+
if (existing !== void 0) return existing;
|
|
368
|
+
const request = this.withPermit(async () => {
|
|
369
|
+
if (this.disposed) return err(disposed());
|
|
370
|
+
if (signal.aborted) return err(cancelled(guid));
|
|
371
|
+
try {
|
|
372
|
+
const result = await this.read(guid, signal);
|
|
373
|
+
if (signal.aborted) return err(cancelled(guid));
|
|
374
|
+
if (!result.ok)
|
|
375
|
+
this.counters = { ...this.counters, readErrors: this.counters.readErrors + 1 };
|
|
376
|
+
return result;
|
|
377
|
+
} catch {
|
|
378
|
+
this.counters = { ...this.counters, readErrors: this.counters.readErrors + 1 };
|
|
379
|
+
return err(thrown(guid));
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
this.reads.set(guid, request);
|
|
383
|
+
void request.finally(() => {
|
|
384
|
+
if (this.reads.get(guid) === request) this.reads.delete(guid);
|
|
385
|
+
});
|
|
386
|
+
return request;
|
|
387
|
+
}
|
|
388
|
+
async withPermit(operation) {
|
|
389
|
+
if (this.activeReads >= this.limit) {
|
|
390
|
+
await new Promise((resolve) => this.readWaiters.push(resolve));
|
|
391
|
+
}
|
|
392
|
+
this.activeReads += 1;
|
|
393
|
+
try {
|
|
394
|
+
return await operation();
|
|
395
|
+
} finally {
|
|
396
|
+
this.activeReads -= 1;
|
|
397
|
+
this.readWaiters.shift()?.();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
link(guid, refs) {
|
|
401
|
+
const previous = this.forward.get(guid) ?? /* @__PURE__ */ new Set();
|
|
402
|
+
for (const ref of previous) this.reverse.get(ref)?.delete(guid);
|
|
403
|
+
const next = new Set(refs.map((ref) => ref.toLowerCase()));
|
|
404
|
+
this.forward.set(guid, next);
|
|
405
|
+
for (const ref of next) {
|
|
406
|
+
const dependents = this.reverse.get(ref) ?? /* @__PURE__ */ new Set();
|
|
407
|
+
dependents.add(guid);
|
|
408
|
+
this.reverse.set(ref, dependents);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
collectAffected(guids) {
|
|
412
|
+
const affected = /* @__PURE__ */ new Set();
|
|
413
|
+
const queue = [...guids];
|
|
414
|
+
while (queue.length > 0) {
|
|
415
|
+
const current = queue.shift();
|
|
416
|
+
if (current === void 0 || affected.has(current)) continue;
|
|
417
|
+
affected.add(current);
|
|
418
|
+
for (const dependent of this.reverse.get(current) ?? []) queue.push(dependent);
|
|
419
|
+
}
|
|
420
|
+
return affected;
|
|
421
|
+
}
|
|
422
|
+
drop(affected) {
|
|
423
|
+
for (const item of affected) {
|
|
424
|
+
for (const ref of this.forward.get(item) ?? []) this.reverse.get(ref)?.delete(item);
|
|
425
|
+
for (const dependent of this.reverse.get(item) ?? []) {
|
|
426
|
+
this.forward.get(dependent)?.delete(item);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
for (const item of affected) {
|
|
430
|
+
this.ready.delete(item);
|
|
431
|
+
this.requests.delete(item);
|
|
432
|
+
this.reads.delete(item);
|
|
433
|
+
this.forward.delete(item);
|
|
434
|
+
this.reverse.delete(item);
|
|
435
|
+
}
|
|
436
|
+
this.sccs.splice(0, this.sccs.length);
|
|
437
|
+
}
|
|
438
|
+
promote(guid, value) {
|
|
439
|
+
if (value === void 0) return;
|
|
440
|
+
this.ready.set(
|
|
441
|
+
guid,
|
|
442
|
+
Object.freeze({
|
|
443
|
+
...value,
|
|
444
|
+
value: freezeRuntimePayload(value.value),
|
|
445
|
+
refs: Object.freeze([...value.refs])
|
|
446
|
+
})
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
recordScc() {
|
|
450
|
+
const indexByGuid = /* @__PURE__ */ new Map();
|
|
451
|
+
const lowByGuid = /* @__PURE__ */ new Map();
|
|
452
|
+
const stack = [];
|
|
453
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
454
|
+
let nextIndex = 0;
|
|
455
|
+
const components = [];
|
|
456
|
+
const visit = (guid) => {
|
|
457
|
+
indexByGuid.set(guid, nextIndex);
|
|
458
|
+
lowByGuid.set(guid, nextIndex);
|
|
459
|
+
nextIndex += 1;
|
|
460
|
+
stack.push(guid);
|
|
461
|
+
onStack.add(guid);
|
|
462
|
+
for (const ref of this.forward.get(guid) ?? []) {
|
|
463
|
+
if (!indexByGuid.has(ref)) {
|
|
464
|
+
visit(ref);
|
|
465
|
+
lowByGuid.set(guid, Math.min(lowByGuid.get(guid) ?? 0, lowByGuid.get(ref) ?? 0));
|
|
466
|
+
} else if (onStack.has(ref)) {
|
|
467
|
+
lowByGuid.set(guid, Math.min(lowByGuid.get(guid) ?? 0, indexByGuid.get(ref) ?? 0));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (lowByGuid.get(guid) !== indexByGuid.get(guid)) return;
|
|
471
|
+
const component = [];
|
|
472
|
+
let member;
|
|
473
|
+
do {
|
|
474
|
+
member = stack.pop();
|
|
475
|
+
if (member === void 0) break;
|
|
476
|
+
onStack.delete(member);
|
|
477
|
+
component.push(member);
|
|
478
|
+
} while (member !== guid);
|
|
479
|
+
if (component.length > 1 || this.forward.get(guid)?.has(guid) === true)
|
|
480
|
+
components.push(component.sort());
|
|
481
|
+
};
|
|
482
|
+
for (const guid of this.forward.keys()) if (!indexByGuid.has(guid)) visit(guid);
|
|
483
|
+
this.sccs.splice(
|
|
484
|
+
0,
|
|
485
|
+
this.sccs.length,
|
|
486
|
+
...components.map((component) => Object.freeze(component))
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
publish() {
|
|
490
|
+
this.currentSnapshot = freezeSnapshot({
|
|
491
|
+
epoch: this.epoch,
|
|
492
|
+
ready: [...this.ready.keys()].sort(),
|
|
493
|
+
pending: this.requests.size + this.reads.size,
|
|
494
|
+
resources: this.ready.size,
|
|
495
|
+
sccs: this.sccs,
|
|
496
|
+
counters: this.counters
|
|
497
|
+
});
|
|
498
|
+
for (const listener of [...this.listeners]) {
|
|
499
|
+
try {
|
|
500
|
+
listener(this.currentSnapshot);
|
|
501
|
+
} catch {
|
|
502
|
+
this.counters = {
|
|
503
|
+
...this.counters,
|
|
504
|
+
listenerFailures: Math.min(1024, this.counters.listenerFailures + 1)
|
|
505
|
+
};
|
|
506
|
+
this.currentSnapshot = freezeSnapshot({
|
|
507
|
+
...this.currentSnapshot,
|
|
508
|
+
counters: this.counters
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
function invalidRow(guid, reason) {
|
|
515
|
+
return err({
|
|
516
|
+
code: "asset-package-invalid",
|
|
517
|
+
expected: "one complete current runtime Catalog row",
|
|
518
|
+
hint: "rebuild the producer Catalog and publish one Pack v2 tuple",
|
|
519
|
+
detail: { guid, reason }
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
function validateRuntimeRow(row) {
|
|
523
|
+
if (row === null || typeof row !== "object") return invalidRow("", "row");
|
|
524
|
+
const candidate = row;
|
|
525
|
+
const guid = typeof candidate.guid === "string" ? candidate.guid : "";
|
|
526
|
+
if (guid.trim().length === 0) return invalidRow(guid, "guid");
|
|
527
|
+
if (typeof candidate.kind !== "string" || candidate.kind.trim().length === 0)
|
|
528
|
+
return invalidRow(guid, "kind");
|
|
529
|
+
if (typeof candidate.packageUrl !== "string" || candidate.packageUrl.trim().length === 0)
|
|
530
|
+
return invalidRow(guid, "packageUrl");
|
|
531
|
+
if (typeof candidate.sourcePath !== "string" || candidate.sourcePath.trim().length === 0)
|
|
532
|
+
return invalidRow(guid, "sourcePath");
|
|
533
|
+
if (!Number.isSafeInteger(candidate.publication?.generation))
|
|
534
|
+
return invalidRow(guid, "publication generation");
|
|
535
|
+
if (candidate.publication === void 0) return invalidRow(guid, "publication");
|
|
536
|
+
const publication = candidate.publication;
|
|
537
|
+
if (publication.schemaVersion !== "asset-publication/1" || typeof publication.sourcePath !== "string" || publication.sourcePath.trim().length === 0 || typeof publication.sourceRevision !== "string" || publication.sourceRevision.trim().length === 0 || publication.generation < 0 || typeof publication.digest !== "string" || publication.digest.trim().length === 0 || typeof publication.outputSetDigest !== "string" || publication.outputSetDigest.trim().length === 0 || !Array.isArray(publication.outputs) || publication.receipt === null || typeof publication.receipt !== "object" || !Array.isArray(publication.externalEvidence)) {
|
|
538
|
+
return invalidRow(guid, "publication tuple");
|
|
539
|
+
}
|
|
540
|
+
return ok(Object.freeze({ ...candidate, guid, publication }));
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/internal/catalog-session.ts
|
|
544
|
+
function runtimeError(code, guid, detail) {
|
|
545
|
+
if (code === "catalog-discontinuous") {
|
|
546
|
+
return {
|
|
547
|
+
code,
|
|
548
|
+
expected: "an ordered catalog revision window",
|
|
549
|
+
hint: "reconcile the current Catalog before consuming this delta",
|
|
550
|
+
detail: {
|
|
551
|
+
scopeId: String(detail.scopeId ?? "unknown"),
|
|
552
|
+
expectedGeneration: Number(detail.expectedGeneration ?? 0),
|
|
553
|
+
actualGeneration: Number(detail.actualGeneration ?? 0)
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
code: "asset-package-invalid",
|
|
559
|
+
expected: "a verified Catalog source",
|
|
560
|
+
hint: "repair the producer Catalog and retry with the current publication",
|
|
561
|
+
detail: { guid, reason: String(detail.reason ?? "catalog source failed") }
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function freezeSnapshot2(snapshot) {
|
|
565
|
+
return Object.freeze({
|
|
566
|
+
...snapshot,
|
|
567
|
+
entries: Object.freeze([...snapshot.entries]),
|
|
568
|
+
changed: Object.freeze([...snapshot.changed]),
|
|
569
|
+
removed: Object.freeze([...snapshot.removed]),
|
|
570
|
+
diagnostics: Object.freeze([...snapshot.diagnostics])
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
function sameEntry(left, right) {
|
|
574
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
575
|
+
}
|
|
576
|
+
function key(guid) {
|
|
577
|
+
return guid.toLowerCase();
|
|
578
|
+
}
|
|
579
|
+
var CatalogSession = class {
|
|
580
|
+
source;
|
|
581
|
+
scopeId;
|
|
582
|
+
generation;
|
|
583
|
+
entries = /* @__PURE__ */ new Map();
|
|
584
|
+
listeners = /* @__PURE__ */ new Set();
|
|
585
|
+
unsubscribe;
|
|
586
|
+
baselinePromise;
|
|
587
|
+
reconcilePromise;
|
|
588
|
+
pending = [];
|
|
589
|
+
currentSnapshot;
|
|
590
|
+
revision;
|
|
591
|
+
diagnostics = [];
|
|
592
|
+
listenerFailures = 0;
|
|
593
|
+
changed = /* @__PURE__ */ new Set();
|
|
594
|
+
removed = /* @__PURE__ */ new Set();
|
|
595
|
+
epoch = 0;
|
|
596
|
+
stale = false;
|
|
597
|
+
staleGeneration = 0;
|
|
598
|
+
started = false;
|
|
599
|
+
disposed = false;
|
|
600
|
+
constructor(source, options = {}) {
|
|
601
|
+
this.source = source;
|
|
602
|
+
this.scopeId = options.scopeId ?? source.expectedScope?.scopeId ?? "asset-runtime";
|
|
603
|
+
this.generation = options.generation ?? source.expectedScope?.generation ?? 0;
|
|
604
|
+
this.currentSnapshot = freezeSnapshot2({
|
|
605
|
+
scopeId: this.scopeId,
|
|
606
|
+
generation: this.generation,
|
|
607
|
+
epoch: 0,
|
|
608
|
+
entries: [],
|
|
609
|
+
changed: [],
|
|
610
|
+
removed: [],
|
|
611
|
+
diagnostics: [],
|
|
612
|
+
listenerFailures: 0,
|
|
613
|
+
stale: false
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
start() {
|
|
617
|
+
if (this.baselinePromise !== void 0) return this.baselinePromise;
|
|
618
|
+
if (this.disposed) return Promise.resolve(err(this.disposedError()));
|
|
619
|
+
this.unsubscribe = this.source.subscribe((delta) => this.receive(delta));
|
|
620
|
+
const promise = this.source.enumerate().then((result) => {
|
|
621
|
+
if (!result.ok) {
|
|
622
|
+
this.markStale();
|
|
623
|
+
this.publish();
|
|
624
|
+
return err(runtimeError("asset-package-invalid", "", { reason: result.error.code }));
|
|
625
|
+
}
|
|
626
|
+
this.entries.clear();
|
|
627
|
+
for (const entry of result.value) {
|
|
628
|
+
const validated = validateRuntimeRow(entry);
|
|
629
|
+
if (!validated.ok) {
|
|
630
|
+
this.markStale();
|
|
631
|
+
this.publish();
|
|
632
|
+
return err(validated.error);
|
|
633
|
+
}
|
|
634
|
+
this.entries.set(key(validated.value.guid), validated.value);
|
|
635
|
+
}
|
|
636
|
+
this.started = true;
|
|
637
|
+
this.stale = false;
|
|
638
|
+
this.staleGeneration = this.generation;
|
|
639
|
+
this.diagnostics = [];
|
|
640
|
+
for (const delta of this.pending) this.fold(delta, false);
|
|
641
|
+
this.pending = [];
|
|
642
|
+
this.publish();
|
|
643
|
+
return ok(this.currentSnapshot);
|
|
644
|
+
}).catch((cause) => {
|
|
645
|
+
this.markStale();
|
|
646
|
+
this.addDiagnostic("catalog-degraded-rows", "Catalog enumeration must resolve a Result");
|
|
647
|
+
this.publish();
|
|
648
|
+
return err(
|
|
649
|
+
runtimeError("asset-package-invalid", "", {
|
|
650
|
+
reason: cause instanceof Error ? cause.message : String(cause)
|
|
651
|
+
})
|
|
652
|
+
);
|
|
653
|
+
});
|
|
654
|
+
this.baselinePromise = promise;
|
|
655
|
+
void promise.then(
|
|
656
|
+
(result) => {
|
|
657
|
+
if (!result.ok) this.baselinePromise = void 0;
|
|
658
|
+
},
|
|
659
|
+
() => {
|
|
660
|
+
this.baselinePromise = void 0;
|
|
661
|
+
}
|
|
662
|
+
);
|
|
663
|
+
return promise;
|
|
664
|
+
}
|
|
665
|
+
reconcile() {
|
|
666
|
+
if (this.disposed) return Promise.resolve(err(this.disposedError()));
|
|
667
|
+
if (this.reconcilePromise !== void 0) return this.reconcilePromise;
|
|
668
|
+
this.unsubscribe?.();
|
|
669
|
+
this.unsubscribe = void 0;
|
|
670
|
+
this.started = false;
|
|
671
|
+
this.pending = [];
|
|
672
|
+
this.baselinePromise = void 0;
|
|
673
|
+
this.epoch += 1;
|
|
674
|
+
const promise = this.start();
|
|
675
|
+
this.reconcilePromise = promise;
|
|
676
|
+
void promise.then(
|
|
677
|
+
() => {
|
|
678
|
+
if (this.reconcilePromise === promise) this.reconcilePromise = void 0;
|
|
679
|
+
},
|
|
680
|
+
() => {
|
|
681
|
+
if (this.reconcilePromise === promise) this.reconcilePromise = void 0;
|
|
682
|
+
}
|
|
683
|
+
);
|
|
684
|
+
return promise;
|
|
685
|
+
}
|
|
686
|
+
current(guid) {
|
|
687
|
+
return this.entries.get(key(guid));
|
|
688
|
+
}
|
|
689
|
+
snapshot() {
|
|
690
|
+
return this.currentSnapshot;
|
|
691
|
+
}
|
|
692
|
+
discontinuity() {
|
|
693
|
+
if (!this.stale) return void 0;
|
|
694
|
+
return {
|
|
695
|
+
code: "catalog-discontinuous",
|
|
696
|
+
expected: "an ordered, authoritative catalog revision window",
|
|
697
|
+
hint: "reconcile the Catalog source and retry the current publication",
|
|
698
|
+
detail: {
|
|
699
|
+
scopeId: this.scopeId,
|
|
700
|
+
expectedGeneration: this.generation,
|
|
701
|
+
actualGeneration: this.staleGeneration
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
subscribe(listener) {
|
|
706
|
+
this.listeners.add(listener);
|
|
707
|
+
return () => this.listeners.delete(listener);
|
|
708
|
+
}
|
|
709
|
+
dispose() {
|
|
710
|
+
if (this.disposed) return;
|
|
711
|
+
this.disposed = true;
|
|
712
|
+
this.unsubscribe?.();
|
|
713
|
+
this.unsubscribe = void 0;
|
|
714
|
+
this.pending = [];
|
|
715
|
+
this.listeners.clear();
|
|
716
|
+
}
|
|
717
|
+
receive(delta) {
|
|
718
|
+
if (this.disposed) return;
|
|
719
|
+
if (!this.started) {
|
|
720
|
+
this.pending.push(delta);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
this.fold(delta, true);
|
|
724
|
+
}
|
|
725
|
+
fold(delta, publish) {
|
|
726
|
+
if (delta.scopeId !== void 0 && delta.scopeId !== this.scopeId || delta.generation !== void 0 && delta.generation !== this.generation) {
|
|
727
|
+
this.markStale(delta.generation);
|
|
728
|
+
this.addDiagnostic("catalog-scope-mismatch", "delta scope does not match the session");
|
|
729
|
+
if (publish) this.publish();
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (delta.authority === "degraded") {
|
|
733
|
+
this.markStale(delta.generation);
|
|
734
|
+
this.addDiagnostic("catalog-degraded-rows", "degraded rows are not identity-bearing");
|
|
735
|
+
if (publish) this.publish();
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (delta.revisions !== void 0) {
|
|
739
|
+
const baseline = delta.revisions.baseline;
|
|
740
|
+
const current = delta.revisions.current;
|
|
741
|
+
const valid = baseline.length === current.length && current.every((point) => {
|
|
742
|
+
const prior = baseline.find((item) => item.rootId === point.rootId);
|
|
743
|
+
return prior !== void 0 && point.revision === prior.revision + 1;
|
|
744
|
+
});
|
|
745
|
+
if (!valid) {
|
|
746
|
+
this.markStale(delta.generation);
|
|
747
|
+
this.addDiagnostic("catalog-gap", "delta revision window is not contiguous");
|
|
748
|
+
if (publish) this.publish();
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
let changed = false;
|
|
753
|
+
for (const entry of [...delta.added, ...delta.changed]) {
|
|
754
|
+
const validated = validateRuntimeRow(entry);
|
|
755
|
+
if (!validated.ok) {
|
|
756
|
+
this.markStale(delta.generation);
|
|
757
|
+
this.addDiagnostic("catalog-degraded-rows", "delta contains an invalid runtime row");
|
|
758
|
+
if (publish) this.publish();
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
const entryKey = key(validated.value.guid);
|
|
762
|
+
const prior = this.entries.get(entryKey);
|
|
763
|
+
if (prior === void 0 || !sameEntry(prior, validated.value)) {
|
|
764
|
+
this.entries.set(entryKey, validated.value);
|
|
765
|
+
this.changed.add(entryKey);
|
|
766
|
+
changed = true;
|
|
767
|
+
if (validated.value.revision !== void 0) this.revision = validated.value.revision;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
for (const guid of delta.removed) {
|
|
771
|
+
const entryKey = key(guid);
|
|
772
|
+
if (this.entries.delete(entryKey)) {
|
|
773
|
+
this.removed.add(entryKey);
|
|
774
|
+
changed = true;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
if (changed) this.epoch += 1;
|
|
778
|
+
if (publish) this.publish();
|
|
779
|
+
}
|
|
780
|
+
addDiagnostic(code, expected) {
|
|
781
|
+
if (this.diagnostics.some((diagnostic) => diagnostic.code === code)) return;
|
|
782
|
+
this.diagnostics.push({
|
|
783
|
+
code,
|
|
784
|
+
severity: "blocking",
|
|
785
|
+
expected,
|
|
786
|
+
hint: "reconcile the Catalog before loading the affected publication",
|
|
787
|
+
authority: "catalog"
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
markStale(actualGeneration = this.generation) {
|
|
791
|
+
this.stale = true;
|
|
792
|
+
this.staleGeneration = actualGeneration;
|
|
793
|
+
this.epoch += 1;
|
|
794
|
+
}
|
|
795
|
+
publish() {
|
|
796
|
+
this.currentSnapshot = freezeSnapshot2({
|
|
797
|
+
scopeId: this.scopeId,
|
|
798
|
+
generation: this.generation,
|
|
799
|
+
epoch: this.epoch,
|
|
800
|
+
...this.revision === void 0 ? {} : { revision: this.revision },
|
|
801
|
+
entries: [...this.entries.values()].sort(
|
|
802
|
+
(left, right) => key(left.guid).localeCompare(key(right.guid))
|
|
803
|
+
),
|
|
804
|
+
changed: [...this.changed].sort(),
|
|
805
|
+
removed: [...this.removed].sort(),
|
|
806
|
+
diagnostics: this.diagnostics,
|
|
807
|
+
listenerFailures: this.listenerFailures,
|
|
808
|
+
stale: this.stale
|
|
809
|
+
});
|
|
810
|
+
this.changed.clear();
|
|
811
|
+
this.removed.clear();
|
|
812
|
+
for (const listener of [...this.listeners]) {
|
|
813
|
+
try {
|
|
814
|
+
listener(this.currentSnapshot);
|
|
815
|
+
} catch {
|
|
816
|
+
this.listenerFailures = Math.min(1024, this.listenerFailures + 1);
|
|
817
|
+
this.currentSnapshot = freezeSnapshot2({
|
|
818
|
+
...this.currentSnapshot,
|
|
819
|
+
listenerFailures: this.listenerFailures
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
disposedError() {
|
|
825
|
+
return runtimeError("asset-runtime-disposed", "", { scopeId: this.scopeId });
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
var DecoderRegistry = class {
|
|
829
|
+
dispatch = /* @__PURE__ */ new Map();
|
|
830
|
+
scopeId;
|
|
831
|
+
disposed = false;
|
|
832
|
+
constructor(options = {}) {
|
|
833
|
+
this.scopeId = options.scopeId ?? "asset-runtime";
|
|
834
|
+
}
|
|
835
|
+
install(kind, decoder) {
|
|
836
|
+
if (this.disposed) throw new TypeError("asset runtime decoder registry is disposed");
|
|
837
|
+
const normalizedDecoder = decoder;
|
|
838
|
+
const existing = this.dispatch.get(kind.kind);
|
|
839
|
+
if (existing !== void 0) {
|
|
840
|
+
if (existing.decoder !== normalizedDecoder) {
|
|
841
|
+
throw new TypeError(`duplicate decoder kind "${kind.kind}"`);
|
|
842
|
+
}
|
|
843
|
+
existing.references += 1;
|
|
844
|
+
return this.createLease(kind.kind, existing);
|
|
845
|
+
}
|
|
846
|
+
const identity = Symbol(kind.kind);
|
|
847
|
+
const entry = {
|
|
848
|
+
identity,
|
|
849
|
+
decoder: normalizedDecoder,
|
|
850
|
+
decode: (input) => decoder.decode(input),
|
|
851
|
+
references: 1
|
|
852
|
+
};
|
|
853
|
+
this.dispatch.set(kind.kind, entry);
|
|
854
|
+
return this.createLease(kind.kind, entry);
|
|
855
|
+
}
|
|
856
|
+
createLease(kind, entry) {
|
|
857
|
+
let released = false;
|
|
858
|
+
return {
|
|
859
|
+
kind,
|
|
860
|
+
dispose: () => {
|
|
861
|
+
if (released) return;
|
|
862
|
+
released = true;
|
|
863
|
+
entry.references -= 1;
|
|
864
|
+
if (entry.references === 0 && this.dispatch.get(kind)?.identity === entry.identity) {
|
|
865
|
+
this.dispatch.delete(kind);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
has(kind) {
|
|
871
|
+
return this.dispatch.has(kind.kind);
|
|
872
|
+
}
|
|
873
|
+
load(kind, input) {
|
|
874
|
+
return this.decode(kind, input);
|
|
875
|
+
}
|
|
876
|
+
loadByKind(kind, input) {
|
|
877
|
+
if (this.disposed) return Promise.resolve(err(disposedError(this.scopeId)));
|
|
878
|
+
const entry = this.dispatch.get(kind);
|
|
879
|
+
if (entry === void 0) {
|
|
880
|
+
return Promise.resolve(
|
|
881
|
+
err({
|
|
882
|
+
code: "asset-decoder-missing",
|
|
883
|
+
expected: `an active decoder for kind "${kind}"`,
|
|
884
|
+
hint: "install the owner decoder lease before loading this kind",
|
|
885
|
+
detail: { kind }
|
|
886
|
+
})
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
return this.decodeEntry(kind, entry, input);
|
|
890
|
+
}
|
|
891
|
+
async decode(kind, input) {
|
|
892
|
+
if (this.disposed) return err(disposedError(this.scopeId));
|
|
893
|
+
if (input.signal.aborted) return err(cancelledError(input.envelope.guid));
|
|
894
|
+
const entry = this.dispatch.get(kind.kind);
|
|
895
|
+
if (entry === void 0) {
|
|
896
|
+
return err({
|
|
897
|
+
code: "asset-decoder-missing",
|
|
898
|
+
expected: `an active decoder for kind "${kind.kind}"`,
|
|
899
|
+
hint: "install the owner decoder lease before loading this kind",
|
|
900
|
+
detail: { kind: kind.kind }
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
return await this.decodeEntry(kind.kind, entry, input);
|
|
904
|
+
}
|
|
905
|
+
async decodeEntry(kind, entry, input) {
|
|
906
|
+
if (this.disposed) return err(disposedError(this.scopeId));
|
|
907
|
+
if (input.signal.aborted) return err(cancelledError(input.envelope.guid));
|
|
908
|
+
try {
|
|
909
|
+
const result = await entry.decode(input);
|
|
910
|
+
if (this.disposed) return err(disposedError(this.scopeId));
|
|
911
|
+
if (this.dispatch.get(kind)?.identity !== entry.identity) {
|
|
912
|
+
return err(supersededError(input.envelope.guid, kind));
|
|
913
|
+
}
|
|
914
|
+
if (input.signal.aborted) return err(cancelledError(input.envelope.guid));
|
|
915
|
+
return result.ok ? ok(freezeRuntimePayload(result.value)) : result;
|
|
916
|
+
} catch {
|
|
917
|
+
return err({
|
|
918
|
+
code: "asset-decode-failed",
|
|
919
|
+
expected: `decoder for kind "${kind}" to return a Result`,
|
|
920
|
+
hint: "inspect the owner decoder and retry the current publication",
|
|
921
|
+
detail: { guid: input.envelope.guid, kind }
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
dispose() {
|
|
926
|
+
if (this.disposed) return;
|
|
927
|
+
this.disposed = true;
|
|
928
|
+
this.dispatch.clear();
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
function cancelledError(guid) {
|
|
932
|
+
return {
|
|
933
|
+
code: "asset-load-cancelled",
|
|
934
|
+
expected: "the request AbortSignal to remain live until decode completes",
|
|
935
|
+
hint: "retry with a live AbortSignal when the request is still needed",
|
|
936
|
+
detail: { guid }
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
function disposedError(scopeId) {
|
|
940
|
+
return {
|
|
941
|
+
code: "asset-runtime-disposed",
|
|
942
|
+
expected: "an active asset runtime decoder scope",
|
|
943
|
+
hint: "obtain a new Registry from the current realm",
|
|
944
|
+
detail: { scopeId }
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
function supersededError(guid, kind) {
|
|
948
|
+
return {
|
|
949
|
+
code: "asset-superseded",
|
|
950
|
+
expected: `the decoder lease for kind "${kind}" to remain current until decode completes`,
|
|
951
|
+
hint: "retry the current publication after reinstalling its owner decoder",
|
|
952
|
+
detail: { guid, generation: 0 }
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
function invalid(guid, reason) {
|
|
956
|
+
return err({
|
|
957
|
+
code: "asset-package-invalid",
|
|
958
|
+
expected: "a verified Pack v2 envelope with complete runtime artifacts",
|
|
959
|
+
hint: "re-cook the Pack v2 publication and retry the current tuple",
|
|
960
|
+
detail: { guid, reason }
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
function frozen(value) {
|
|
964
|
+
if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
|
|
965
|
+
Object.freeze(value);
|
|
966
|
+
for (const child of Object.values(value)) frozen(child);
|
|
967
|
+
}
|
|
968
|
+
return value;
|
|
969
|
+
}
|
|
970
|
+
function sameTuple(left, right) {
|
|
971
|
+
return left.scopeId === right.scopeId && left.generation === right.generation && left.digest === right.digest && left.outputSetDigest === right.outputSetDigest;
|
|
972
|
+
}
|
|
973
|
+
var PackReader = class {
|
|
974
|
+
fetcher;
|
|
975
|
+
verified = /* @__PURE__ */ new Map();
|
|
976
|
+
pending = /* @__PURE__ */ new Map();
|
|
977
|
+
constructor(options = {}) {
|
|
978
|
+
this.fetcher = options.fetcher?.bind(globalThis);
|
|
979
|
+
}
|
|
980
|
+
async read(packageUrl, expected, signal) {
|
|
981
|
+
if (signal.aborted) return err(this.cancelled(packageUrl));
|
|
982
|
+
const key2 = this.key(packageUrl, expected);
|
|
983
|
+
const cached = this.verified.get(key2);
|
|
984
|
+
if (cached !== void 0) return ok(cached);
|
|
985
|
+
const active = this.pending.get(key2);
|
|
986
|
+
if (active !== void 0) return active;
|
|
987
|
+
const request = this.fetchAndVerify(packageUrl, expected, signal);
|
|
988
|
+
this.pending.set(key2, request);
|
|
989
|
+
const result = await request;
|
|
990
|
+
this.pending.delete(key2);
|
|
991
|
+
if (result.ok) this.verified.set(key2, result.value);
|
|
992
|
+
return result;
|
|
993
|
+
}
|
|
994
|
+
async fetchAndVerify(packageUrl, expected, signal) {
|
|
995
|
+
let response;
|
|
996
|
+
try {
|
|
997
|
+
const fetcher = this.fetcher ?? globalThis.fetch.bind(globalThis);
|
|
998
|
+
response = await fetcher(packageUrl, { signal });
|
|
999
|
+
} catch (_cause) {
|
|
1000
|
+
if (signal.aborted) return err(this.cancelled(packageUrl));
|
|
1001
|
+
return err({
|
|
1002
|
+
code: "asset-fetch-failed",
|
|
1003
|
+
expected: "HTTP 200 for the current Pack URL",
|
|
1004
|
+
hint: "verify the package locator and republish the Pack",
|
|
1005
|
+
detail: { guid: packageUrl, packageUrl }
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
if (!response.ok) {
|
|
1009
|
+
return err({
|
|
1010
|
+
code: "asset-fetch-failed",
|
|
1011
|
+
expected: "HTTP 200 for the current Pack URL",
|
|
1012
|
+
hint: "verify the package locator and republish the Pack",
|
|
1013
|
+
detail: { guid: packageUrl, packageUrl }
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
let value;
|
|
1017
|
+
try {
|
|
1018
|
+
value = await response.json();
|
|
1019
|
+
} catch {
|
|
1020
|
+
return invalid(packageUrl, "invalid JSON");
|
|
1021
|
+
}
|
|
1022
|
+
return this.verify(value, expected);
|
|
1023
|
+
}
|
|
1024
|
+
key(packageUrl, tuple) {
|
|
1025
|
+
return `${packageUrl}\0${tuple.scopeId}\0${tuple.generation}\0${tuple.digest}\0${tuple.outputSetDigest}`;
|
|
1026
|
+
}
|
|
1027
|
+
verify(value, expected) {
|
|
1028
|
+
if (value === null || typeof value !== "object") return invalid("", "Pack is not an object");
|
|
1029
|
+
const pack = value;
|
|
1030
|
+
if (pack.schemaVersion !== "2.0.0" || pack.kind !== "internal-text-package") {
|
|
1031
|
+
return invalid("", "schemaVersion or kind");
|
|
1032
|
+
}
|
|
1033
|
+
if (typeof pack.scopeId !== "string" || !Number.isSafeInteger(pack.generation) || typeof pack.digest !== "string" || typeof pack.outputSetDigest !== "string" || !sameTuple(pack, expected)) {
|
|
1034
|
+
return invalid("", "publication tuple mismatch");
|
|
1035
|
+
}
|
|
1036
|
+
if (!Array.isArray(pack.assets)) return invalid("", "assets");
|
|
1037
|
+
const guids = /* @__PURE__ */ new Set();
|
|
1038
|
+
for (const raw of pack.assets) {
|
|
1039
|
+
const asset = raw;
|
|
1040
|
+
const guid = typeof asset.guid === "string" ? asset.guid : "";
|
|
1041
|
+
if (guid.length === 0 || guids.has(guid.toLowerCase()))
|
|
1042
|
+
return invalid(guid, "duplicate guid");
|
|
1043
|
+
guids.add(guid.toLowerCase());
|
|
1044
|
+
if (typeof asset.kind !== "string" || asset.payload === void 0 || !Array.isArray(asset.refs) || asset.refs.some((ref) => typeof ref !== "string") || asset.artifacts === null || typeof asset.artifacts !== "object") {
|
|
1045
|
+
return invalid(guid, "asset envelope fields");
|
|
1046
|
+
}
|
|
1047
|
+
for (const [artifactKey, descriptor] of Object.entries(asset.artifacts)) {
|
|
1048
|
+
if (!this.validArtifact(guid, artifactKey, descriptor)) {
|
|
1049
|
+
return invalid(guid, `artifact ${artifactKey}`);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
return ok(frozen(pack));
|
|
1054
|
+
}
|
|
1055
|
+
validArtifact(_guid, key2, value) {
|
|
1056
|
+
if (value === null || typeof value !== "object") return false;
|
|
1057
|
+
const descriptor = value;
|
|
1058
|
+
const integrity = descriptor.integrity;
|
|
1059
|
+
return key2.length > 0 && typeof descriptor.path === "string" && descriptor.path.length > 0 && typeof descriptor.mediaType === "string" && descriptor.mediaType.length > 0 && (descriptor.contentEncoding === "identity" || descriptor.contentEncoding === "zstd") && Number.isSafeInteger(descriptor.byteLength) && Number(descriptor.byteLength) >= 0 && integrity !== null && typeof integrity === "object" && integrity.algorithm === "sha256" && typeof integrity.digest === "string" && /^sha256:[0-9a-f]{64}$/i.test(String(integrity.digest));
|
|
1060
|
+
}
|
|
1061
|
+
cancelled(guid) {
|
|
1062
|
+
return {
|
|
1063
|
+
code: "asset-load-cancelled",
|
|
1064
|
+
expected: "the request AbortSignal to remain live while reading the Pack",
|
|
1065
|
+
hint: "retry with a live AbortSignal when the request is still needed",
|
|
1066
|
+
detail: { guid }
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
|
|
1071
|
+
// src/internal/load-asset.ts
|
|
1072
|
+
var REGISTRY_RESOLVER = /* @__PURE__ */ Symbol.for("forgeax.assets-runtime.registry-resolver");
|
|
1073
|
+
function missing(guid) {
|
|
1074
|
+
return {
|
|
1075
|
+
code: "asset-not-found",
|
|
1076
|
+
expected: `the current Catalog to contain GUID "${guid}"`,
|
|
1077
|
+
hint: "inspect the producer Catalog and rebuild the missing publication",
|
|
1078
|
+
detail: { guid }
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
function mismatch(guid, expectedKind, actualKind) {
|
|
1082
|
+
return {
|
|
1083
|
+
code: "asset-kind-mismatch",
|
|
1084
|
+
expected: `Catalog kind "${actualKind}" to match "${expectedKind}"`,
|
|
1085
|
+
hint: "pass the Catalog kind or the matching custom AssetKind token",
|
|
1086
|
+
detail: { guid, expectedKind, actualKind }
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
function artifactError(guid, reason) {
|
|
1090
|
+
return {
|
|
1091
|
+
code: "asset-integrity-failed",
|
|
1092
|
+
expected: "the verified artifact byte length and digest",
|
|
1093
|
+
hint: "verify the artifact digest and recook the Pack",
|
|
1094
|
+
detail: {
|
|
1095
|
+
guid,
|
|
1096
|
+
artifactKey: reason,
|
|
1097
|
+
expectedDigest: "descriptor integrity",
|
|
1098
|
+
actualDigest: reason
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
var ASSET_GUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1103
|
+
function invalidGuid(guid) {
|
|
1104
|
+
return {
|
|
1105
|
+
code: "asset-guid-invalid",
|
|
1106
|
+
expected: "a 36-character dash-form asset GUID",
|
|
1107
|
+
hint: "pass the producer GUID from the current Catalog row",
|
|
1108
|
+
detail: { guid }
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
function createAssetRegistry(options) {
|
|
1112
|
+
const session = new CatalogSession(options.catalog, options);
|
|
1113
|
+
const reader = new PackReader(options.fetcher === void 0 ? {} : { fetcher: options.fetcher });
|
|
1114
|
+
const cache = new ArtifactCache();
|
|
1115
|
+
const decoders = new DecoderRegistry(
|
|
1116
|
+
options.scopeId === void 0 ? {} : { scopeId: options.scopeId }
|
|
1117
|
+
);
|
|
1118
|
+
const graph = new AssetGraph({
|
|
1119
|
+
...options.maxConcurrentReads === void 0 ? {} : { maxConcurrentReads: options.maxConcurrentReads },
|
|
1120
|
+
read: async (guid, signal) => {
|
|
1121
|
+
const row = session.current(guid);
|
|
1122
|
+
if (row === void 0) return err(missing(guid));
|
|
1123
|
+
const validated = validateRuntimeRow(row);
|
|
1124
|
+
if (!validated.ok) return validated;
|
|
1125
|
+
const publication = validated.value.publication;
|
|
1126
|
+
const tuple = {
|
|
1127
|
+
scopeId: session.snapshot().scopeId,
|
|
1128
|
+
generation: publication.generation,
|
|
1129
|
+
digest: publication.digest,
|
|
1130
|
+
outputSetDigest: publication.outputSetDigest
|
|
1131
|
+
};
|
|
1132
|
+
const pack = await reader.read(row.packageUrl, tuple, signal);
|
|
1133
|
+
if (!pack.ok) return pack;
|
|
1134
|
+
const envelope = pack.value.assets.find(
|
|
1135
|
+
(asset) => asset.guid.toLowerCase() === guid.toLowerCase()
|
|
1136
|
+
);
|
|
1137
|
+
if (envelope === void 0) return err(missing(guid));
|
|
1138
|
+
const artifacts = {
|
|
1139
|
+
read: (descriptor) => {
|
|
1140
|
+
const integrity = descriptor.integrity;
|
|
1141
|
+
if (integrity === void 0)
|
|
1142
|
+
return Promise.resolve(err(artifactError(guid, descriptor.path)));
|
|
1143
|
+
const key2 = `${tuple.scopeId}:${tuple.generation}:${tuple.outputSetDigest}:${integrity.digest}`;
|
|
1144
|
+
return cache.read(key2, async () => {
|
|
1145
|
+
const url = resolveArtifactUrl(row.packageUrl, descriptor.path);
|
|
1146
|
+
let response;
|
|
1147
|
+
try {
|
|
1148
|
+
response = await (options.fetcher ?? globalThis.fetch)(url, { signal });
|
|
1149
|
+
} catch {
|
|
1150
|
+
return err(artifactError(guid, descriptor.path));
|
|
1151
|
+
}
|
|
1152
|
+
if (!response.ok) return err(artifactError(guid, descriptor.path));
|
|
1153
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
1154
|
+
if (descriptor.byteLength === void 0 || bytes.byteLength !== descriptor.byteLength) {
|
|
1155
|
+
return err(artifactError(guid, descriptor.path));
|
|
1156
|
+
}
|
|
1157
|
+
const actualDigest = await sha256(bytes);
|
|
1158
|
+
if (actualDigest !== descriptor.integrity?.digest.toLowerCase()) {
|
|
1159
|
+
return err(artifactError(guid, `${descriptor.path}:${actualDigest}`));
|
|
1160
|
+
}
|
|
1161
|
+
return ok(bytes);
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
const input = { envelope, artifacts, signal };
|
|
1166
|
+
const decoded = await decoders.loadByKind(envelope.kind, input);
|
|
1167
|
+
if (!decoded.ok) return decoded;
|
|
1168
|
+
return ok({ value: freezeRuntimePayload(decoded.value), refs: envelope.refs });
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
let catalogEpoch = session.snapshot().epoch;
|
|
1172
|
+
const unsubscribeCatalog = session.subscribe((snapshot) => {
|
|
1173
|
+
if (snapshot.epoch === catalogEpoch) return;
|
|
1174
|
+
catalogEpoch = snapshot.epoch;
|
|
1175
|
+
graph.invalidateForCatalogChange(
|
|
1176
|
+
snapshot.stale ? void 0 : [...snapshot.changed, ...snapshot.removed]
|
|
1177
|
+
);
|
|
1178
|
+
});
|
|
1179
|
+
const registry = {
|
|
1180
|
+
installDecoder: (kind, decoder) => decoders.install(kind, decoder),
|
|
1181
|
+
load: (async (guid, kind, loadOptions = {}) => {
|
|
1182
|
+
if (!ASSET_GUID_PATTERN.test(guid)) return err(invalidGuid(guid));
|
|
1183
|
+
const expectedKind = typeof kind === "string" ? kind : kind.kind;
|
|
1184
|
+
const started = await session.start();
|
|
1185
|
+
if (!started.ok) return err(started.error);
|
|
1186
|
+
if (session.snapshot().stale) {
|
|
1187
|
+
const reconciled = await session.reconcile();
|
|
1188
|
+
if (!reconciled.ok) return err(reconciled.error);
|
|
1189
|
+
const discontinuity = session.discontinuity();
|
|
1190
|
+
if (discontinuity !== void 0) return err(discontinuity);
|
|
1191
|
+
}
|
|
1192
|
+
const row = session.current(guid);
|
|
1193
|
+
if (row === void 0) return err(missing(guid));
|
|
1194
|
+
if (row.kind !== expectedKind) return err(mismatch(guid, expectedKind, row.kind));
|
|
1195
|
+
const result = await graph.load(guid, loadOptions.signal);
|
|
1196
|
+
return result.ok ? ok(result.value.value) : result;
|
|
1197
|
+
}),
|
|
1198
|
+
snapshot: () => graph.snapshot(),
|
|
1199
|
+
subscribe: (listener) => graph.subscribe(listener),
|
|
1200
|
+
dispose: () => {
|
|
1201
|
+
graph.dispose();
|
|
1202
|
+
unsubscribeCatalog();
|
|
1203
|
+
decoders.dispose();
|
|
1204
|
+
cache.clear();
|
|
1205
|
+
session.dispose();
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
const resolver = {
|
|
1209
|
+
get epoch() {
|
|
1210
|
+
return graph.snapshot().epoch;
|
|
1211
|
+
},
|
|
1212
|
+
lookup: (guid) => graph.lookup(guid),
|
|
1213
|
+
guidOf: (asset) => graph.guidOf(asset)
|
|
1214
|
+
};
|
|
1215
|
+
Object.defineProperty(registry, REGISTRY_RESOLVER, {
|
|
1216
|
+
value: resolver,
|
|
1217
|
+
enumerable: false
|
|
1218
|
+
});
|
|
1219
|
+
return registry;
|
|
1220
|
+
}
|
|
1221
|
+
async function sha256(bytes) {
|
|
1222
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
1223
|
+
return `sha256:${Array.from(
|
|
1224
|
+
new Uint8Array(digest),
|
|
1225
|
+
(byte) => byte.toString(16).padStart(2, "0")
|
|
1226
|
+
).join("")}`;
|
|
1227
|
+
}
|
|
1228
|
+
function resolveArtifactUrl(packageUrl, path) {
|
|
1229
|
+
const separator = packageUrl.lastIndexOf("/");
|
|
1230
|
+
const packageDirectory = separator < 0 ? "" : packageUrl.slice(0, separator + 1);
|
|
1231
|
+
try {
|
|
1232
|
+
return new URL(path, packageDirectory).toString();
|
|
1233
|
+
} catch {
|
|
1234
|
+
return `${packageDirectory}${path.replace(/^\//, "")}`;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
export { createAssetRegistry, createCatalogSource, defineAssetKind };
|
|
1239
|
+
//# sourceMappingURL=index.mjs.map
|
|
1240
|
+
//# sourceMappingURL=index.mjs.map
|