@forgeax/engine-assets-runtime 0.0.0-dev.8d955ade1c79
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 +491 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/aabb.unit.test.d.ts +2 -0
- package/dist/__tests__/aabb.unit.test.d.ts.map +1 -0
- package/dist/__tests__/artifact-integrity.unit.test.d.ts +2 -0
- package/dist/__tests__/artifact-integrity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/artifact-io.unit.test.d.ts +2 -0
- package/dist/__tests__/artifact-io.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-sdk.integration.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-sdk.integration.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-kind.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-kind.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-catalog-reconcile.integration.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-catalog-reconcile.integration.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-catalog-reconcile.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-catalog-reconcile.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-catalog.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-catalog.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry.pack-cache.test.d.ts +2 -0
- package/dist/__tests__/asset-registry.pack-cache.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-runtime-error-code-owner.test-d.d.ts +2 -0
- package/dist/__tests__/asset-runtime-error-code-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/catalog-replica.unit.test.d.ts +2 -0
- package/dist/__tests__/catalog-replica.unit.test.d.ts.map +1 -0
- package/dist/__tests__/catalog-source-v2.unit.test.d.ts +2 -0
- package/dist/__tests__/catalog-source-v2.unit.test.d.ts.map +1 -0
- package/dist/__tests__/catalog-source.unit.test.d.ts +2 -0
- package/dist/__tests__/catalog-source.unit.test.d.ts.map +1 -0
- package/dist/__tests__/catalog-subscription-order.unit.test.d.ts +2 -0
- package/dist/__tests__/catalog-subscription-order.unit.test.d.ts.map +1 -0
- package/dist/__tests__/catalog-subscription.unit.test.d.ts +2 -0
- package/dist/__tests__/catalog-subscription.unit.test.d.ts.map +1 -0
- package/dist/__tests__/decode-image-bytes-pass-through.test.d.ts +2 -0
- package/dist/__tests__/decode-image-bytes-pass-through.test.d.ts.map +1 -0
- package/dist/__tests__/decode-image-bytes.browser.test.d.ts +2 -0
- package/dist/__tests__/decode-image-bytes.browser.test.d.ts.map +1 -0
- package/dist/__tests__/decode-image-bytes.test-d.d.ts +2 -0
- package/dist/__tests__/decode-image-bytes.test-d.d.ts.map +1 -0
- package/dist/__tests__/decode-image-bytes.test.d.ts +2 -0
- package/dist/__tests__/decode-image-bytes.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__/dynamic-texture-store.unit.test.d.ts +2 -0
- package/dist/__tests__/dynamic-texture-store.unit.test.d.ts.map +1 -0
- package/dist/__tests__/errors-asset.unit.test.d.ts +2 -0
- package/dist/__tests__/errors-asset.unit.test.d.ts.map +1 -0
- package/dist/__tests__/inline-pack-loaders.unit.test.d.ts +2 -0
- package/dist/__tests__/inline-pack-loaders.unit.test.d.ts.map +1 -0
- package/dist/__tests__/instantiate-context.unit.test.d.ts +2 -0
- package/dist/__tests__/instantiate-context.unit.test.d.ts.map +1 -0
- package/dist/__tests__/instantiate-publication-fence.unit.test.d.ts +2 -0
- package/dist/__tests__/instantiate-publication-fence.unit.test.d.ts.map +1 -0
- package/dist/__tests__/instantiate.unit.test.d.ts +2 -0
- package/dist/__tests__/instantiate.unit.test.d.ts.map +1 -0
- package/dist/__tests__/loader-input-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/loader-input-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/loader-registry.unit.test.d.ts +2 -0
- package/dist/__tests__/loader-registry.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-dependency-invalidation.unit.test.d.ts +2 -0
- package/dist/__tests__/material-dependency-invalidation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-generation-cache.unit.test.d.ts +2 -0
- package/dist/__tests__/material-generation-cache.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-load-consumer.test-d.d.ts +2 -0
- package/dist/__tests__/material-load-consumer.test-d.d.ts.map +1 -0
- package/dist/__tests__/material-ready.integration.test.d.ts +2 -0
- package/dist/__tests__/material-ready.integration.test.d.ts.map +1 -0
- package/dist/__tests__/material-stale-generation.unit.test.d.ts +2 -0
- package/dist/__tests__/material-stale-generation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-morph-v3.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-morph-v3.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mipmap-generator.unit.test.d.ts +2 -0
- package/dist/__tests__/mipmap-generator.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pack-artifact-transcode-model-owner.test.d.ts +2 -0
- package/dist/__tests__/pack-artifact-transcode-model-owner.test.d.ts.map +1 -0
- package/dist/__tests__/pack-basis-load.integration.test.d.ts +2 -0
- package/dist/__tests__/pack-basis-load.integration.test.d.ts.map +1 -0
- package/dist/__tests__/payload-validate.unit.test.d.ts +2 -0
- package/dist/__tests__/payload-validate.unit.test.d.ts.map +1 -0
- package/dist/__tests__/plugin.test.d.ts +2 -0
- package/dist/__tests__/plugin.test.d.ts.map +1 -0
- package/dist/__tests__/registry-concurrency.bench.test.d.ts +2 -0
- package/dist/__tests__/registry-concurrency.bench.test.d.ts.map +1 -0
- package/dist/__tests__/registry-failure-retry.integration.test.d.ts +2 -0
- package/dist/__tests__/registry-failure-retry.integration.test.d.ts.map +1 -0
- package/dist/__tests__/registry-load-graph.integration.test.d.ts +2 -0
- package/dist/__tests__/registry-load-graph.integration.test.d.ts.map +1 -0
- package/dist/__tests__/registry-load-state.unit.test.d.ts +2 -0
- package/dist/__tests__/registry-load-state.unit.test.d.ts.map +1 -0
- package/dist/__tests__/registry-package-cache.unit.test.d.ts +2 -0
- package/dist/__tests__/registry-package-cache.unit.test.d.ts.map +1 -0
- package/dist/__tests__/registry-public-ready.unit.test.d.ts +2 -0
- package/dist/__tests__/registry-public-ready.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-asset-handle.unit.test.d.ts +2 -0
- package/dist/__tests__/resolve-asset-handle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scene-handle-fields.unit.test.d.ts +2 -0
- package/dist/__tests__/scene-handle-fields.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scene-payload.unit.test.d.ts +2 -0
- package/dist/__tests__/scene-payload.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scene-publication-fence.test.d.ts +2 -0
- package/dist/__tests__/scene-publication-fence.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-public-surface.test-d.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-public-surface.test-d.d.ts.map +1 -0
- package/dist/__tests__/static-rig.integration.test.d.ts +2 -0
- package/dist/__tests__/static-rig.integration.test.d.ts.map +1 -0
- package/dist/__tests__/validate-material.unit.test.d.ts +2 -0
- package/dist/__tests__/validate-material.unit.test.d.ts.map +1 -0
- package/dist/__tests__/video-pack-loader.integration.test.d.ts +2 -0
- package/dist/__tests__/video-pack-loader.integration.test.d.ts.map +1 -0
- package/dist/aabb.d.ts +3 -0
- package/dist/aabb.d.ts.map +1 -0
- package/dist/asset-kind.d.ts +4 -0
- package/dist/asset-kind.d.ts.map +1 -0
- package/dist/asset-registry.d.ts +554 -0
- package/dist/asset-registry.d.ts.map +1 -0
- package/dist/builtin-asset-registry.d.ts +22 -0
- package/dist/builtin-asset-registry.d.ts.map +1 -0
- package/dist/catalog-source.d.ts +24 -0
- package/dist/catalog-source.d.ts.map +1 -0
- package/dist/decode-image-bytes.d.ts +57 -0
- package/dist/decode-image-bytes.d.ts.map +1 -0
- package/dist/dynamic-texture-store.d.ts +89 -0
- package/dist/dynamic-texture-store.d.ts.map +1 -0
- package/dist/errors/asset.d.ts +201 -0
- package/dist/errors/asset.d.ts.map +1 -0
- package/dist/handles.d.ts +143 -0
- package/dist/handles.d.ts.map +1 -0
- package/dist/image-error.d.ts +6 -0
- package/dist/image-error.d.ts.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +8359 -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 +3 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.mjs +329 -0
- package/dist/internal.mjs.map +1 -0
- package/dist/loader-registry.d.ts +63 -0
- package/dist/loader-registry.d.ts.map +1 -0
- package/dist/loaders/inline-pack.d.ts +72 -0
- package/dist/loaders/inline-pack.d.ts.map +1 -0
- package/dist/loaders/mesh-bin.d.ts +21 -0
- package/dist/loaders/mesh-bin.d.ts.map +1 -0
- package/dist/loaders/pack-artifact.d.ts +20 -0
- package/dist/loaders/pack-artifact.d.ts.map +1 -0
- package/dist/material/dependency-graph.d.ts +9 -0
- package/dist/material/dependency-graph.d.ts.map +1 -0
- package/dist/material/generation-cache.d.ts +24 -0
- package/dist/material/generation-cache.d.ts.map +1 -0
- package/dist/material/inspection.d.ts +24 -0
- package/dist/material/inspection.d.ts.map +1 -0
- package/dist/material/loader.d.ts +59 -0
- package/dist/material/loader.d.ts.map +1 -0
- package/dist/material/runtime-shader.d.ts +14 -0
- package/dist/material/runtime-shader.d.ts.map +1 -0
- package/dist/mesh-bin.d.ts +3 -0
- package/dist/mesh-bin.d.ts.map +1 -0
- package/dist/mipmap-generator.d.ts +132 -0
- package/dist/mipmap-generator.d.ts.map +1 -0
- package/dist/payload-validate.d.ts +60 -0
- package/dist/payload-validate.d.ts.map +1 -0
- package/dist/plugin.d.ts +16 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/registry/artifact-io.d.ts +16 -0
- package/dist/registry/artifact-io.d.ts.map +1 -0
- package/dist/registry/asset-evidence.d.ts +13 -0
- package/dist/registry/asset-evidence.d.ts.map +1 -0
- package/dist/registry/catalog-state.d.ts +47 -0
- package/dist/registry/catalog-state.d.ts.map +1 -0
- package/dist/registry/catalog.d.ts +48 -0
- package/dist/registry/catalog.d.ts.map +1 -0
- package/dist/registry/instantiate.d.ts +153 -0
- package/dist/registry/instantiate.d.ts.map +1 -0
- package/dist/registry/load-by-guid.d.ts +247 -0
- package/dist/registry/load-by-guid.d.ts.map +1 -0
- package/dist/registry/load-state.d.ts +26 -0
- package/dist/registry/load-state.d.ts.map +1 -0
- package/dist/registry/runtime-projection.d.ts +16 -0
- package/dist/registry/runtime-projection.d.ts.map +1 -0
- package/dist/registry/scene-publication-fence.d.ts +38 -0
- package/dist/registry/scene-publication-fence.d.ts.map +1 -0
- package/dist/registry/validate-material.d.ts +77 -0
- package/dist/registry/validate-material.d.ts.map +1 -0
- package/dist/resolve-asset-handle.d.ts +43 -0
- package/dist/resolve-asset-handle.d.ts.map +1 -0
- package/dist/resolve-tileset-runtime.d.ts +23 -0
- package/dist/resolve-tileset-runtime.d.ts.map +1 -0
- package/dist/scene-handle-fields.d.ts +88 -0
- package/dist/scene-handle-fields.d.ts.map +1 -0
- package/dist/scene-payload.d.ts +15 -0
- package/dist/scene-payload.d.ts.map +1 -0
- package/dist/wire-default-loaders.d.ts +29 -0
- package/dist/wire-default-loaders.d.ts.map +1 -0
- package/package.json +76 -0
- package/src/__tests__/aabb.unit.test.ts +97 -0
- package/src/__tests__/artifact-integrity.unit.test.ts +99 -0
- package/src/__tests__/artifact-io.unit.test.ts +127 -0
- package/src/__tests__/asset-evidence-sdk.integration.test.ts +32 -0
- package/src/__tests__/asset-evidence.unit.test.ts +32 -0
- package/src/__tests__/asset-kind.unit.test.ts +14 -0
- package/src/__tests__/asset-registry-catalog-reconcile.integration.test.ts +74 -0
- package/src/__tests__/asset-registry-catalog-reconcile.unit.test.ts +89 -0
- package/src/__tests__/asset-registry-catalog.unit.test.ts +170 -0
- package/src/__tests__/asset-registry.pack-cache.test.ts +96 -0
- package/src/__tests__/asset-registry.unit.test.ts +511 -0
- package/src/__tests__/asset-runtime-error-code-owner.test-d.ts +63 -0
- package/src/__tests__/catalog-replica.unit.test.ts +196 -0
- package/src/__tests__/catalog-source-v2.unit.test.ts +158 -0
- package/src/__tests__/catalog-source.unit.test.ts +126 -0
- package/src/__tests__/catalog-subscription-order.unit.test.ts +65 -0
- package/src/__tests__/catalog-subscription.unit.test.ts +28 -0
- package/src/__tests__/decode-image-bytes-pass-through.test.ts +34 -0
- package/src/__tests__/decode-image-bytes.browser.test.ts +102 -0
- package/src/__tests__/decode-image-bytes.test-d.ts +108 -0
- package/src/__tests__/decode-image-bytes.test.ts +90 -0
- package/src/__tests__/decode-image-mime-owner.test.ts +38 -0
- package/src/__tests__/dynamic-texture-store.unit.test.ts +115 -0
- package/src/__tests__/errors-asset.unit.test.ts +92 -0
- package/src/__tests__/inline-pack-loaders.unit.test.ts +548 -0
- package/src/__tests__/instantiate-context.unit.test.ts +65 -0
- package/src/__tests__/instantiate-publication-fence.unit.test.ts +158 -0
- package/src/__tests__/instantiate.unit.test.ts +684 -0
- package/src/__tests__/loader-input-contract.unit.test.ts +89 -0
- package/src/__tests__/loader-registry.unit.test.ts +101 -0
- package/src/__tests__/material-dependency-invalidation.unit.test.ts +14 -0
- package/src/__tests__/material-generation-cache.unit.test.ts +46 -0
- package/src/__tests__/material-load-consumer.test-d.ts +42 -0
- package/src/__tests__/material-ready.integration.test.ts +508 -0
- package/src/__tests__/material-stale-generation.unit.test.ts +147 -0
- package/src/__tests__/mesh-bin-morph-v3.test.ts +22 -0
- package/src/__tests__/mesh-bin.unit.test.ts +152 -0
- package/src/__tests__/mipmap-generator.unit.test.ts +202 -0
- package/src/__tests__/pack-artifact-transcode-model-owner.test.ts +32 -0
- package/src/__tests__/pack-basis-load.integration.test.ts +168 -0
- package/src/__tests__/payload-validate.unit.test.ts +285 -0
- package/src/__tests__/plugin.test.ts +55 -0
- package/src/__tests__/registry-concurrency.bench.test.ts +40 -0
- package/src/__tests__/registry-failure-retry.integration.test.ts +69 -0
- package/src/__tests__/registry-load-graph.integration.test.ts +246 -0
- package/src/__tests__/registry-load-state.unit.test.ts +35 -0
- package/src/__tests__/registry-package-cache.unit.test.ts +53 -0
- package/src/__tests__/registry-public-ready.unit.test.ts +38 -0
- package/src/__tests__/resolve-asset-handle.unit.test.ts +238 -0
- package/src/__tests__/scene-handle-fields.unit.test.ts +129 -0
- package/src/__tests__/scene-payload.unit.test.ts +151 -0
- package/src/__tests__/scene-publication-fence.test.ts +129 -0
- package/src/__tests__/scriptable-pack-public-surface.test-d.ts +27 -0
- package/src/__tests__/static-rig.integration.test.ts +8 -0
- package/src/__tests__/validate-material.unit.test.ts +359 -0
- package/src/__tests__/video-pack-loader.integration.test.ts +83 -0
- package/src/aabb.ts +107 -0
- package/src/asset-kind.ts +6 -0
- package/src/asset-registry.ts +2045 -0
- package/src/builtin-asset-registry.ts +164 -0
- package/src/catalog-source.ts +89 -0
- package/src/decode-image-bytes.ts +126 -0
- package/src/dynamic-texture-store.ts +234 -0
- package/src/errors/asset.ts +347 -0
- package/src/handles.ts +186 -0
- package/src/image-error.ts +45 -0
- package/src/index.ts +189 -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 +6 -0
- package/src/loader-registry.ts +157 -0
- package/src/loaders/inline-pack.ts +837 -0
- package/src/loaders/mesh-bin.ts +304 -0
- package/src/loaders/pack-artifact.ts +665 -0
- package/src/material/dependency-graph.ts +46 -0
- package/src/material/generation-cache.ts +177 -0
- package/src/material/inspection.ts +46 -0
- package/src/material/loader.ts +326 -0
- package/src/material/runtime-shader.ts +133 -0
- package/src/mesh-bin.ts +5 -0
- package/src/mipmap-generator.ts +450 -0
- package/src/payload-validate.ts +573 -0
- package/src/plugin.ts +48 -0
- package/src/registry/artifact-io.ts +259 -0
- package/src/registry/asset-evidence.ts +51 -0
- package/src/registry/catalog-state.ts +409 -0
- package/src/registry/catalog.ts +369 -0
- package/src/registry/instantiate.ts +1056 -0
- package/src/registry/load-by-guid.ts +1657 -0
- package/src/registry/load-state.ts +115 -0
- package/src/registry/runtime-projection.ts +63 -0
- package/src/registry/scene-publication-fence.ts +256 -0
- package/src/registry/validate-material.ts +385 -0
- package/src/resolve-asset-handle.ts +147 -0
- package/src/resolve-tileset-runtime.ts +72 -0
- package/src/scene-handle-fields.ts +221 -0
- package/src/scene-payload.ts +207 -0
- package/src/wire-default-loaders.ts +79 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { ok, type Result } from '@forgeax/engine-rhi';
|
|
2
|
+
import type {
|
|
3
|
+
AssetError,
|
|
4
|
+
CatalogDelta,
|
|
5
|
+
CatalogDiagnostic,
|
|
6
|
+
CatalogEntry,
|
|
7
|
+
CatalogRevisionWindow,
|
|
8
|
+
ResourceRevision,
|
|
9
|
+
RuntimeAssetBinding,
|
|
10
|
+
} from '@forgeax/engine-types';
|
|
11
|
+
import type { CatalogSource } from '../catalog-source';
|
|
12
|
+
|
|
13
|
+
export interface CatalogReplicaSnapshot {
|
|
14
|
+
readonly version: number;
|
|
15
|
+
readonly entries: readonly CatalogEntry[];
|
|
16
|
+
readonly stale: boolean;
|
|
17
|
+
readonly diagnostics: readonly CatalogDiagnostic[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type SnapshotResult = Result<CatalogReplicaSnapshot, AssetError>;
|
|
21
|
+
type CatalogDeltaListener = (delta: CatalogDelta) => void;
|
|
22
|
+
|
|
23
|
+
function guidKey(guid: string): string {
|
|
24
|
+
return guid.toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function freezeEntry(entry: CatalogEntry): CatalogEntry {
|
|
28
|
+
return Object.freeze({ ...entry });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function freezeSnapshot(snapshot: CatalogReplicaSnapshot): CatalogReplicaSnapshot {
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
...snapshot,
|
|
34
|
+
entries: Object.freeze(snapshot.entries.map(freezeEntry)),
|
|
35
|
+
diagnostics: Object.freeze([...snapshot.diagnostics]),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function revisionIsOlder(
|
|
40
|
+
incoming: ResourceRevision | undefined,
|
|
41
|
+
current: ResourceRevision | undefined,
|
|
42
|
+
): boolean {
|
|
43
|
+
return (
|
|
44
|
+
incoming !== undefined && current !== undefined && incoming.observedAt < current.observedAt
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function sameRevision(left: ResourceRevision, right: ResourceRevision): boolean {
|
|
49
|
+
return (
|
|
50
|
+
left.digest === right.digest &&
|
|
51
|
+
left.observedAt === right.observedAt &&
|
|
52
|
+
left.rootId === right.rootId
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function sameValue(left: unknown, right: unknown): boolean {
|
|
57
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function diagnosticForGap(): CatalogDiagnostic {
|
|
61
|
+
return {
|
|
62
|
+
code: 'catalog-gap',
|
|
63
|
+
severity: 'blocking',
|
|
64
|
+
expected: 'a contiguous producer revision window',
|
|
65
|
+
hint: 'reconcile the catalog before consuming incremental changes',
|
|
66
|
+
authority: 'catalog',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function diagnosticForDegradedRows(): CatalogDiagnostic {
|
|
71
|
+
return {
|
|
72
|
+
code: 'catalog-degraded-rows',
|
|
73
|
+
severity: 'blocking',
|
|
74
|
+
expected: 'a degraded catalog delta to contain no identity-bearing rows',
|
|
75
|
+
hint: 'keep the last verified catalog and reconcile before applying a replacement',
|
|
76
|
+
authority: 'catalog',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function hasIdentityChanges(delta: CatalogDelta): boolean {
|
|
81
|
+
return delta.added.length > 0 || delta.changed.length > 0 || delta.removed.length > 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function revisionDiagnostic(
|
|
85
|
+
revisions: CatalogRevisionWindow,
|
|
86
|
+
hasChanges: boolean,
|
|
87
|
+
): CatalogDiagnostic | undefined {
|
|
88
|
+
const baselineByRoot = new Map(revisions.baseline.map((point) => [point.rootId, point]));
|
|
89
|
+
const currentByRoot = new Map(revisions.current.map((point) => [point.rootId, point]));
|
|
90
|
+
|
|
91
|
+
for (const point of revisions.current) {
|
|
92
|
+
const baseline = baselineByRoot.get(point.rootId);
|
|
93
|
+
if (baseline === undefined) {
|
|
94
|
+
return {
|
|
95
|
+
code: 'catalog-revision-conflict',
|
|
96
|
+
severity: 'blocking',
|
|
97
|
+
expected: 'every current root to have a verified baseline',
|
|
98
|
+
actual: point.rootId,
|
|
99
|
+
hint: 'restore the latest verified snapshot for this root before applying the delta',
|
|
100
|
+
authority: 'catalog',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (point.revision < baseline.revision) {
|
|
104
|
+
return {
|
|
105
|
+
code: 'catalog-revision-stale',
|
|
106
|
+
severity: 'blocking',
|
|
107
|
+
expected: 'current revision to be at least the verified baseline',
|
|
108
|
+
actual: `${baseline.revision} -> ${point.revision}`,
|
|
109
|
+
hint: 'discard the stale update and request a fresh catalog snapshot',
|
|
110
|
+
authority: 'catalog',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (point.revision === baseline.revision && hasChanges) {
|
|
114
|
+
return {
|
|
115
|
+
code: 'catalog-revision-conflict',
|
|
116
|
+
severity: 'blocking',
|
|
117
|
+
expected: 'a changed delta to advance the root revision',
|
|
118
|
+
actual: `${baseline.revision} -> ${point.revision}`,
|
|
119
|
+
hint: 'keep the verified baseline and serialize concurrent updates before retrying',
|
|
120
|
+
authority: 'catalog',
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (point.revision > baseline.revision + 1) {
|
|
124
|
+
return {
|
|
125
|
+
code: 'catalog-revision-conflict',
|
|
126
|
+
severity: 'blocking',
|
|
127
|
+
expected: 'the next revision to be exactly baseline + 1',
|
|
128
|
+
actual: `${baseline.revision} -> ${point.revision}`,
|
|
129
|
+
hint: 'request the missing revisions or rebuild from the latest verified snapshot',
|
|
130
|
+
authority: 'catalog',
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const point of revisions.baseline) {
|
|
136
|
+
if (!currentByRoot.has(point.rootId)) {
|
|
137
|
+
return {
|
|
138
|
+
code: 'catalog-revision-conflict',
|
|
139
|
+
severity: 'blocking',
|
|
140
|
+
expected: 'every baseline root to be present in the current revision set',
|
|
141
|
+
actual: point.rootId,
|
|
142
|
+
hint: 'do not apply a partial root set over the verified baseline',
|
|
143
|
+
authority: 'catalog',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function rowRevisionDiagnostic(
|
|
152
|
+
entries: ReadonlyMap<string, CatalogEntry>,
|
|
153
|
+
delta: CatalogDelta,
|
|
154
|
+
): CatalogDiagnostic | undefined {
|
|
155
|
+
for (const entry of [...delta.added, ...delta.changed]) {
|
|
156
|
+
const prior = entries.get(guidKey(entry.guid));
|
|
157
|
+
if (prior?.revision === undefined) continue;
|
|
158
|
+
if (prior !== undefined && sameValue(prior, entry)) continue;
|
|
159
|
+
if (entry.revision === undefined) {
|
|
160
|
+
return {
|
|
161
|
+
code: 'catalog-revision-conflict',
|
|
162
|
+
severity: 'blocking',
|
|
163
|
+
expected: 'a replacement row to carry a newer producer revision',
|
|
164
|
+
actual: `${prior.revision.rootId}@${prior.revision.observedAt} -> missing`,
|
|
165
|
+
hint: 'restore the producer revision before applying the catalog change',
|
|
166
|
+
authority: 'catalog',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (entry.revision.observedAt < prior.revision.observedAt) {
|
|
170
|
+
return {
|
|
171
|
+
code: 'catalog-revision-stale',
|
|
172
|
+
severity: 'blocking',
|
|
173
|
+
expected: 'a replacement row to carry a non-decreasing producer revision',
|
|
174
|
+
actual: `${prior.revision.observedAt} -> ${entry.revision.observedAt}`,
|
|
175
|
+
hint: 'discard the stale update and request a fresh catalog snapshot',
|
|
176
|
+
authority: 'catalog',
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (entry.revision.observedAt === prior.revision.observedAt) {
|
|
180
|
+
return {
|
|
181
|
+
code: 'catalog-revision-conflict',
|
|
182
|
+
severity: 'blocking',
|
|
183
|
+
expected: 'a changed row to advance its producer revision',
|
|
184
|
+
actual: sameRevision(prior.revision, entry.revision)
|
|
185
|
+
? `${prior.revision.observedAt} -> ${entry.revision.observedAt}`
|
|
186
|
+
: `${prior.revision.digest} -> ${entry.revision.digest}`,
|
|
187
|
+
hint: 'publish a new producer revision for changed payload bytes',
|
|
188
|
+
authority: 'catalog',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function diagnosticForScopeMismatch(): CatalogDiagnostic {
|
|
196
|
+
return {
|
|
197
|
+
code: 'catalog-scope-mismatch',
|
|
198
|
+
severity: 'blocking',
|
|
199
|
+
expected: 'catalog delta scopeId and generation to match the active runtime binding',
|
|
200
|
+
hint: 'discard the stale publication and reconcile the active runtime catalog',
|
|
201
|
+
authority: 'catalog',
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function appendDiagnostic(
|
|
206
|
+
diagnostics: readonly CatalogDiagnostic[],
|
|
207
|
+
incoming: readonly CatalogDiagnostic[] | undefined,
|
|
208
|
+
): readonly CatalogDiagnostic[] {
|
|
209
|
+
const next = [...diagnostics];
|
|
210
|
+
for (const diagnostic of incoming ?? []) {
|
|
211
|
+
if (!next.some((existing) => existing.code === diagnostic.code)) next.push(diagnostic);
|
|
212
|
+
}
|
|
213
|
+
return next;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* One authoritative catalog read model owned by AssetRegistry.
|
|
218
|
+
*
|
|
219
|
+
* The source subscription is installed before the first enumeration. Deltas
|
|
220
|
+
* observed during that baseline read are buffered and folded afterwards, so
|
|
221
|
+
* an early publication cannot disappear between the two operations.
|
|
222
|
+
*/
|
|
223
|
+
export class CatalogReplica {
|
|
224
|
+
private readonly source: CatalogSource;
|
|
225
|
+
private readonly listeners = new Set<CatalogDeltaListener>();
|
|
226
|
+
private readonly entries = new Map<string, CatalogEntry>();
|
|
227
|
+
private pendingBeforeBaseline: CatalogDelta[] = [];
|
|
228
|
+
private pendingDuringReconcile: CatalogDelta[] | undefined;
|
|
229
|
+
private unsubscribe: (() => void) | undefined;
|
|
230
|
+
private startPromise: Promise<SnapshotResult> | undefined;
|
|
231
|
+
private baselineReady = false;
|
|
232
|
+
private version = 0;
|
|
233
|
+
private stale = false;
|
|
234
|
+
private diagnostics: readonly CatalogDiagnostic[] = [];
|
|
235
|
+
private currentSnapshot: CatalogReplicaSnapshot;
|
|
236
|
+
private readonly expectedScope: Pick<RuntimeAssetBinding, 'scopeId' | 'generation'> | undefined;
|
|
237
|
+
|
|
238
|
+
constructor(source: CatalogSource) {
|
|
239
|
+
this.source = source;
|
|
240
|
+
this.expectedScope = source.expectedScope;
|
|
241
|
+
this.currentSnapshot = freezeSnapshot({
|
|
242
|
+
version: 0,
|
|
243
|
+
entries: [],
|
|
244
|
+
stale: false,
|
|
245
|
+
diagnostics: [],
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
subscribe(listener: CatalogDeltaListener): () => void {
|
|
250
|
+
this.listeners.add(listener);
|
|
251
|
+
return () => this.listeners.delete(listener);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
snapshot(): CatalogReplicaSnapshot {
|
|
255
|
+
return this.currentSnapshot;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
dispose(): void {
|
|
259
|
+
this.unsubscribe?.();
|
|
260
|
+
this.unsubscribe = undefined;
|
|
261
|
+
this.startPromise = undefined;
|
|
262
|
+
this.pendingBeforeBaseline = [];
|
|
263
|
+
this.pendingDuringReconcile = undefined;
|
|
264
|
+
this.listeners.clear();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async start(): Promise<SnapshotResult> {
|
|
268
|
+
if (this.startPromise !== undefined) return this.startPromise;
|
|
269
|
+
if (this.unsubscribe === undefined) {
|
|
270
|
+
this.unsubscribe = this.source.subscribe((delta) => this.receive(delta));
|
|
271
|
+
}
|
|
272
|
+
const promise = this.loadBaseline(false);
|
|
273
|
+
this.startPromise = promise;
|
|
274
|
+
void promise.then((result) => {
|
|
275
|
+
if (!result.ok) this.startPromise = undefined;
|
|
276
|
+
});
|
|
277
|
+
return promise;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async reconcile(): Promise<SnapshotResult> {
|
|
281
|
+
if (this.unsubscribe === undefined) {
|
|
282
|
+
this.unsubscribe = this.source.subscribe((delta) => this.receive(delta));
|
|
283
|
+
}
|
|
284
|
+
this.pendingDuringReconcile = [];
|
|
285
|
+
return this.loadBaseline(true);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private async loadBaseline(isReconcile: boolean): Promise<SnapshotResult> {
|
|
289
|
+
const result = await this.source.enumerate();
|
|
290
|
+
if (!result.ok) {
|
|
291
|
+
this.stale = true;
|
|
292
|
+
this.currentSnapshot = this.makeSnapshot();
|
|
293
|
+
if (!isReconcile) this.pendingBeforeBaseline = [];
|
|
294
|
+
this.pendingDuringReconcile = undefined;
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
this.entries.clear();
|
|
299
|
+
for (const entry of result.value) this.entries.set(guidKey(entry.guid), freezeEntry(entry));
|
|
300
|
+
this.stale = false;
|
|
301
|
+
this.diagnostics = [];
|
|
302
|
+
this.baselineReady = true;
|
|
303
|
+
const pending = isReconcile ? (this.pendingDuringReconcile ?? []) : this.pendingBeforeBaseline;
|
|
304
|
+
this.pendingBeforeBaseline = [];
|
|
305
|
+
this.pendingDuringReconcile = undefined;
|
|
306
|
+
for (const delta of pending) this.fold(delta, false);
|
|
307
|
+
this.currentSnapshot = this.makeSnapshot();
|
|
308
|
+
return ok(this.currentSnapshot);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private receive(delta: CatalogDelta): void {
|
|
312
|
+
if (!this.baselineReady) {
|
|
313
|
+
if (this.pendingDuringReconcile !== undefined) this.pendingDuringReconcile.push(delta);
|
|
314
|
+
else this.pendingBeforeBaseline.push(delta);
|
|
315
|
+
this.publish(this.safeDelta(delta));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (this.pendingDuringReconcile !== undefined) {
|
|
319
|
+
this.pendingDuringReconcile.push(delta);
|
|
320
|
+
this.publish(this.safeDelta(delta));
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
this.fold(delta, true);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private fold(delta: CatalogDelta, publish: boolean): void {
|
|
327
|
+
const safeDelta = this.safeDelta(delta);
|
|
328
|
+
const degraded = safeDelta.authority === 'degraded';
|
|
329
|
+
this.stale = this.stale || degraded;
|
|
330
|
+
this.diagnostics = appendDiagnostic(
|
|
331
|
+
this.diagnostics,
|
|
332
|
+
safeDelta.diagnostics?.some((diagnostic) => diagnostic.code === 'catalog-gap')
|
|
333
|
+
? [...(safeDelta.diagnostics ?? []), diagnosticForGap()]
|
|
334
|
+
: safeDelta.diagnostics,
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
if (!degraded) {
|
|
338
|
+
let changed = false;
|
|
339
|
+
for (const entry of [...safeDelta.added, ...safeDelta.changed]) {
|
|
340
|
+
const key = guidKey(entry.guid);
|
|
341
|
+
const prior = this.entries.get(key);
|
|
342
|
+
if (revisionIsOlder(entry.revision, prior?.revision)) continue;
|
|
343
|
+
if (prior !== undefined && sameValue(prior, entry)) continue;
|
|
344
|
+
this.entries.set(key, freezeEntry(entry));
|
|
345
|
+
changed = true;
|
|
346
|
+
}
|
|
347
|
+
for (const guid of safeDelta.removed) {
|
|
348
|
+
if (this.entries.delete(guidKey(guid))) changed = true;
|
|
349
|
+
}
|
|
350
|
+
if (changed) this.version += 1;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
this.currentSnapshot = this.makeSnapshot();
|
|
354
|
+
if (publish) {
|
|
355
|
+
this.publish(safeDelta);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private safeDelta(delta: CatalogDelta): CatalogDelta {
|
|
360
|
+
const diagnostic =
|
|
361
|
+
this.expectedScope !== undefined &&
|
|
362
|
+
(delta.scopeId !== this.expectedScope.scopeId ||
|
|
363
|
+
delta.generation !== this.expectedScope.generation)
|
|
364
|
+
? diagnosticForScopeMismatch()
|
|
365
|
+
: delta.revisions === undefined
|
|
366
|
+
? rowRevisionDiagnostic(this.entries, delta)
|
|
367
|
+
: (revisionDiagnostic(delta.revisions, hasIdentityChanges(delta)) ??
|
|
368
|
+
rowRevisionDiagnostic(this.entries, delta));
|
|
369
|
+
const degradedRows = delta.authority === 'degraded' && hasIdentityChanges(delta);
|
|
370
|
+
if (diagnostic === undefined && !degradedRows) return delta;
|
|
371
|
+
return {
|
|
372
|
+
...delta,
|
|
373
|
+
added: [],
|
|
374
|
+
changed: [],
|
|
375
|
+
removed: [],
|
|
376
|
+
authority: 'degraded',
|
|
377
|
+
diagnostics: appendDiagnostic(
|
|
378
|
+
[],
|
|
379
|
+
[
|
|
380
|
+
...(diagnostic === undefined ? [] : [diagnostic]),
|
|
381
|
+
...(degradedRows ? [diagnosticForDegradedRows()] : []),
|
|
382
|
+
],
|
|
383
|
+
),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private publish(delta: CatalogDelta): void {
|
|
388
|
+
for (const listener of [...this.listeners]) {
|
|
389
|
+
try {
|
|
390
|
+
listener(delta);
|
|
391
|
+
} catch {
|
|
392
|
+
// One observer cannot prevent the remaining catalog observers from seeing a fact.
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private makeSnapshot(): CatalogReplicaSnapshot {
|
|
398
|
+
const entries = [...this.entries.values()].sort((left, right) =>
|
|
399
|
+
guidKey(left.guid).localeCompare(guidKey(right.guid)),
|
|
400
|
+
);
|
|
401
|
+
this.currentSnapshot = freezeSnapshot({
|
|
402
|
+
version: this.version,
|
|
403
|
+
entries,
|
|
404
|
+
stale: this.stale,
|
|
405
|
+
diagnostics: this.diagnostics,
|
|
406
|
+
});
|
|
407
|
+
return this.currentSnapshot;
|
|
408
|
+
}
|
|
409
|
+
}
|