@forgeax/engine-import 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 +227 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/decode-image-owner.test-d.d.ts +2 -0
- package/dist/__tests__/decode-image-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/import-contract-migration.test.d.ts +2 -0
- package/dist/__tests__/import-contract-migration.test.d.ts.map +1 -0
- package/dist/__tests__/import-dependencies.test.d.ts +2 -0
- package/dist/__tests__/import-dependencies.test.d.ts.map +1 -0
- package/dist/__tests__/import-finalizer-digest-retry.test.d.ts +2 -0
- package/dist/__tests__/import-finalizer-digest-retry.test.d.ts.map +1 -0
- package/dist/__tests__/import-local-artifacts.test.d.ts +2 -0
- package/dist/__tests__/import-local-artifacts.test.d.ts.map +1 -0
- package/dist/__tests__/import.unit.test.d.ts +2 -0
- package/dist/__tests__/import.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-contract.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-contract.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.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin.test.d.ts.map +1 -0
- package/dist/__tests__/owner-chain.integration.test.d.ts +2 -0
- package/dist/__tests__/owner-chain.integration.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-host.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-host.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-output-producers.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-output-producers.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-product.contract.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-product.contract.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-production-producers.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-production-producers.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-staged-snapshot.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-staged-snapshot.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack.integration.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack.integration.test.d.ts.map +1 -0
- package/dist/__tests__/source-overrides-import-context.unit.test.d.ts +2 -0
- package/dist/__tests__/source-overrides-import-context.unit.test.d.ts.map +1 -0
- package/dist/__tests__/source-package-publication.integration.test.d.ts +2 -0
- package/dist/__tests__/source-package-publication.integration.test.d.ts.map +1 -0
- package/dist/browser.d.ts +5 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.mjs +782 -0
- package/dist/browser.mjs.map +1 -0
- package/dist/build-production.d.ts +48 -0
- package/dist/build-production.d.ts.map +1 -0
- package/dist/catalog-importer-policy.d.ts +13 -0
- package/dist/catalog-importer-policy.d.ts.map +1 -0
- package/dist/catalog-inventory.d.ts +4 -0
- package/dist/catalog-inventory.d.ts.map +1 -0
- package/dist/import-product.d.ts +27 -0
- package/dist/import-product.d.ts.map +1 -0
- package/dist/import-runner.d.ts +136 -0
- package/dist/import-runner.d.ts.map +1 -0
- package/dist/importer-registry.d.ts +48 -0
- package/dist/importer-registry.d.ts.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +2849 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mesh-bin.d.ts +19 -0
- package/dist/mesh-bin.d.ts.map +1 -0
- package/dist/mesh-bin.mjs +182 -0
- package/dist/mesh-bin.mjs.map +1 -0
- package/dist/pack-projection.d.ts +5 -0
- package/dist/pack-projection.d.ts.map +1 -0
- package/dist/scriptable-pack-host.d.ts +72 -0
- package/dist/scriptable-pack-host.d.ts.map +1 -0
- package/dist/scriptable-pack-output-producers.d.ts +7 -0
- package/dist/scriptable-pack-output-producers.d.ts.map +1 -0
- package/dist/scriptable-pack-staged-snapshot.d.ts +21 -0
- package/dist/scriptable-pack-staged-snapshot.d.ts.map +1 -0
- package/dist/scriptable-pack.d.ts +141 -0
- package/dist/scriptable-pack.d.ts.map +1 -0
- package/dist/scriptable-source-package.d.ts +14 -0
- package/dist/scriptable-source-package.d.ts.map +1 -0
- package/dist/source-package-errors.d.ts +52 -0
- package/dist/source-package-errors.d.ts.map +1 -0
- package/dist/source-package-publication.d.ts +63 -0
- package/dist/source-package-publication.d.ts.map +1 -0
- package/dist/source-package.d.ts +60 -0
- package/dist/source-package.d.ts.map +1 -0
- package/package.json +69 -0
- package/src/__tests__/decode-image-owner.test-d.ts +53 -0
- package/src/__tests__/import-contract-migration.test.ts +223 -0
- package/src/__tests__/import-dependencies.test.ts +65 -0
- package/src/__tests__/import-finalizer-digest-retry.test.ts +113 -0
- package/src/__tests__/import-local-artifacts.test.ts +102 -0
- package/src/__tests__/import.unit.test.ts +429 -0
- package/src/__tests__/mesh-bin-contract.test.ts +95 -0
- package/src/__tests__/mesh-bin-morph-v3.test.ts +44 -0
- package/src/__tests__/mesh-bin.test.ts +102 -0
- package/src/__tests__/owner-chain.integration.test.ts +37 -0
- package/src/__tests__/scriptable-pack-host.unit.test.ts +86 -0
- package/src/__tests__/scriptable-pack-output-producers.unit.test.ts +190 -0
- package/src/__tests__/scriptable-pack-product.contract.test.ts +47 -0
- package/src/__tests__/scriptable-pack-production-producers.unit.test.ts +488 -0
- package/src/__tests__/scriptable-pack-staged-snapshot.unit.test.ts +290 -0
- package/src/__tests__/scriptable-pack.integration.test.ts +435 -0
- package/src/__tests__/source-overrides-import-context.unit.test.ts +92 -0
- package/src/__tests__/source-package-publication.integration.test.ts +75 -0
- package/src/browser.ts +14 -0
- package/src/build-production.ts +493 -0
- package/src/catalog-importer-policy.ts +58 -0
- package/src/catalog-inventory.ts +24 -0
- package/src/import-product.ts +163 -0
- package/src/import-runner.ts +749 -0
- package/src/importer-registry.ts +103 -0
- package/src/index.ts +175 -0
- package/src/mesh-bin.ts +229 -0
- package/src/pack-projection.ts +21 -0
- package/src/scriptable-pack-host.ts +544 -0
- package/src/scriptable-pack-output-producers.ts +456 -0
- package/src/scriptable-pack-staged-snapshot.ts +172 -0
- package/src/scriptable-pack.ts +626 -0
- package/src/scriptable-source-package.ts +88 -0
- package/src/source-package-errors.ts +201 -0
- package/src/source-package-publication.ts +326 -0
- package/src/source-package.ts +187 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type DdcGenerationEntryCandidate, DdcGenerationSession, type DdcHead } from '@forgeax/engine-ddc';
|
|
2
|
+
import { type CatalogEntry, type ResourceRevision } from '@forgeax/engine-types';
|
|
3
|
+
import { type SourcePackageError, type SourcePackageErrorContext } from './source-package-errors.js';
|
|
4
|
+
interface StagedSourcePackageDdc extends DdcGenerationEntryCandidate {
|
|
5
|
+
readonly root: string;
|
|
6
|
+
readonly session: DdcGenerationSession;
|
|
7
|
+
readonly context: SourcePackageErrorContext;
|
|
8
|
+
}
|
|
9
|
+
export interface ImportPublicationInput {
|
|
10
|
+
readonly root: string;
|
|
11
|
+
readonly guid: string;
|
|
12
|
+
readonly desiredKey: string;
|
|
13
|
+
readonly pack: unknown;
|
|
14
|
+
readonly previousCatalog: readonly CatalogEntry[];
|
|
15
|
+
readonly nextCatalog: readonly CatalogEntry[];
|
|
16
|
+
readonly publishedGuids: readonly string[];
|
|
17
|
+
readonly transport?: {
|
|
18
|
+
readonly path: string;
|
|
19
|
+
readonly body: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export interface ImportPublicationError {
|
|
23
|
+
readonly code: SourcePackageError['code'];
|
|
24
|
+
readonly expected: string;
|
|
25
|
+
readonly hint: string;
|
|
26
|
+
readonly detail: string;
|
|
27
|
+
readonly diagnostic: SourcePackageError;
|
|
28
|
+
}
|
|
29
|
+
export type ImportPublicationResult = {
|
|
30
|
+
readonly ok: true;
|
|
31
|
+
readonly key: string;
|
|
32
|
+
readonly head: DdcHead;
|
|
33
|
+
readonly catalog: readonly CatalogEntry[];
|
|
34
|
+
readonly revision: ResourceRevision;
|
|
35
|
+
readonly transportPersisted: boolean;
|
|
36
|
+
} | {
|
|
37
|
+
readonly ok: false;
|
|
38
|
+
readonly error: ImportPublicationError;
|
|
39
|
+
readonly head: DdcHead;
|
|
40
|
+
};
|
|
41
|
+
export interface StagedImportPublication {
|
|
42
|
+
readonly input: ImportPublicationInput;
|
|
43
|
+
readonly ddc: StagedSourcePackageDdc;
|
|
44
|
+
readonly key: string;
|
|
45
|
+
readonly head: DdcHead;
|
|
46
|
+
readonly catalog: readonly CatalogEntry[];
|
|
47
|
+
readonly revision: ResourceRevision;
|
|
48
|
+
}
|
|
49
|
+
export type StagedImportPublicationResult = {
|
|
50
|
+
readonly ok: true;
|
|
51
|
+
readonly candidate: StagedImportPublication;
|
|
52
|
+
} | {
|
|
53
|
+
readonly ok: false;
|
|
54
|
+
readonly error: ImportPublicationError;
|
|
55
|
+
readonly head: DdcHead;
|
|
56
|
+
};
|
|
57
|
+
export declare function publishImportPublication(input: ImportPublicationInput): Promise<ImportPublicationResult>;
|
|
58
|
+
export declare function stageImportPublication(input: ImportPublicationInput): Promise<StagedImportPublicationResult>;
|
|
59
|
+
export declare function commitImportPublication(candidate: StagedImportPublication): Promise<ImportPublicationResult>;
|
|
60
|
+
export declare function discardImportPublication(candidate: StagedImportPublication): Promise<void>;
|
|
61
|
+
export declare function restoreImportPublication(candidate: StagedImportPublication): Promise<void>;
|
|
62
|
+
export {};
|
|
63
|
+
//# sourceMappingURL=source-package-publication.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-package-publication.d.ts","sourceRoot":"","sources":["../src/source-package-publication.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ImportedAsset, ImportProduct, ImportProductFinalizeOptions, ImportProductFinalizeResult, Result } from '@forgeax/engine-types';
|
|
2
|
+
import type { ImportRunnerFs, RunImportMeta } from './import-runner.js';
|
|
3
|
+
import type { ImporterRegistry } from './importer-registry.js';
|
|
4
|
+
export type ProducerReadiness = 'before-consume' | 'on-demand';
|
|
5
|
+
export interface ProducerReadinessError {
|
|
6
|
+
readonly code: 'producer-readiness-invalid';
|
|
7
|
+
readonly expected: "'before-consume' or 'on-demand'";
|
|
8
|
+
readonly hint: 'set producerReadiness to before-consume or on-demand';
|
|
9
|
+
readonly detail: {
|
|
10
|
+
readonly value: unknown;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export type ProducerReadinessResult = {
|
|
14
|
+
readonly ok: true;
|
|
15
|
+
readonly value: ProducerReadiness;
|
|
16
|
+
} | {
|
|
17
|
+
readonly ok: false;
|
|
18
|
+
readonly error: ProducerReadinessError;
|
|
19
|
+
};
|
|
20
|
+
export declare function parseProducerReadiness(value: unknown): ProducerReadinessResult;
|
|
21
|
+
export interface SourcePackageProducerInput {
|
|
22
|
+
readonly meta: RunImportMeta;
|
|
23
|
+
readonly registry: ImporterRegistry;
|
|
24
|
+
readonly fs: ImportRunnerFs;
|
|
25
|
+
}
|
|
26
|
+
export interface SourcePackageClosureDetail {
|
|
27
|
+
readonly stage: 'closure';
|
|
28
|
+
readonly declaredGuids: readonly string[];
|
|
29
|
+
readonly producedGuids: readonly string[];
|
|
30
|
+
readonly missingGuids: readonly string[];
|
|
31
|
+
readonly unexpectedGuids: readonly string[];
|
|
32
|
+
readonly duplicateGuids: readonly string[];
|
|
33
|
+
}
|
|
34
|
+
export interface SourcePackageClosureError {
|
|
35
|
+
readonly code: 'source-package-guid-closure-mismatch';
|
|
36
|
+
readonly expected: string;
|
|
37
|
+
readonly hint: string;
|
|
38
|
+
readonly detail: SourcePackageClosureDetail;
|
|
39
|
+
}
|
|
40
|
+
export interface SourcePackageProduct {
|
|
41
|
+
readonly anchorGuid: string;
|
|
42
|
+
readonly declaredGuids: readonly string[];
|
|
43
|
+
readonly product: ImportProduct<unknown>;
|
|
44
|
+
}
|
|
45
|
+
type SourcePackageFinalizeError = Extract<ImportProductFinalizeResult, {
|
|
46
|
+
readonly ok: false;
|
|
47
|
+
}>['error'];
|
|
48
|
+
/** Apply an importer-owned transport finalizer without teaching the adapter its kind. */
|
|
49
|
+
export declare function finalizeSourcePackage(product: ImportProduct<unknown>, finalizer: ((product: ImportProduct<unknown>, options: ImportProductFinalizeOptions) => ImportProductFinalizeResult) | undefined, artifactUrl: ImportProductFinalizeOptions['artifactUrl']): Result<ImportProduct<unknown>, SourcePackageFinalizeError>;
|
|
50
|
+
export type SourcePackageProducerResult = {
|
|
51
|
+
readonly ok: true;
|
|
52
|
+
readonly value: SourcePackageProduct;
|
|
53
|
+
} | {
|
|
54
|
+
readonly ok: false;
|
|
55
|
+
readonly error: SourcePackageClosureError;
|
|
56
|
+
};
|
|
57
|
+
export declare function produceSourcePackage(input: SourcePackageProducerInput): Promise<SourcePackageProducerResult>;
|
|
58
|
+
export declare function sourcePackageAssetsByGuid(source: SourcePackageProduct): ReadonlyMap<string, ImportedAsset<unknown>>;
|
|
59
|
+
export {};
|
|
60
|
+
//# sourceMappingURL=source-package.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-package.d.ts","sourceRoot":"","sources":["../src/source-package.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forgeax/engine-import",
|
|
3
|
+
"version": "0.0.0-dev.8d955ade1c79",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"description": "Build-time asset import runner + ImporterRegistry (the build-time half of the import/load split). Dispatches a *.meta.json sidecar by its top-level `importer` string key to a registered Importer, enforces the GUID import-stable iron law, and writes the DDC (.pack.json / .bin). Build-time only — never bundled into the player runtime (AC-06). Mirrors the LoaderRegistry (runtime) + Console Registry register/get/fail-fast injection shape.",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"browser": "./dist/browser.mjs",
|
|
13
|
+
"import": "./dist/index.mjs"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json",
|
|
16
|
+
"./mesh-bin": {
|
|
17
|
+
"types": "./dist/mesh-bin.d.ts",
|
|
18
|
+
"import": "./dist/mesh-bin.mjs"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"main": "./dist/index.mjs",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"src",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@forgeax/engine-ddc": "0.0.0-dev.8d955ade1c79",
|
|
31
|
+
"@forgeax/engine-ecs": "0.0.0-dev.8d955ade1c79",
|
|
32
|
+
"@forgeax/engine-geometry": "0.0.0-dev.8d955ade1c79",
|
|
33
|
+
"@forgeax/engine-pack": "0.0.0-dev.8d955ade1c79",
|
|
34
|
+
"@forgeax/engine-scene": "0.0.0-dev.8d955ade1c79",
|
|
35
|
+
"@forgeax/engine-types": "0.0.0-dev.8d955ade1c79",
|
|
36
|
+
"@noble/hashes": "^2.2.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.14.0"
|
|
40
|
+
},
|
|
41
|
+
"forgeax": {
|
|
42
|
+
"metrics": {
|
|
43
|
+
"bundle-size": {
|
|
44
|
+
"enabled": false,
|
|
45
|
+
"reason": "build-time-only package (import runner + ImporterRegistry); never enters the player runtime bundle (AC-06), so no shipped-size budget applies"
|
|
46
|
+
},
|
|
47
|
+
"fps": {
|
|
48
|
+
"enabled": false,
|
|
49
|
+
"reason": "build-time package; no runtime canvas or frame loop"
|
|
50
|
+
},
|
|
51
|
+
"bench": {
|
|
52
|
+
"enabled": false,
|
|
53
|
+
"reason": "import runner is one-shot build-time work (not a hot path); no perf-critical bench in this package"
|
|
54
|
+
},
|
|
55
|
+
"gate": {
|
|
56
|
+
"enabled": false,
|
|
57
|
+
"reason": "no standalone binary gate; package-level gates declared in CI yaml directly"
|
|
58
|
+
},
|
|
59
|
+
"spike-report": {
|
|
60
|
+
"enabled": false,
|
|
61
|
+
"reason": "not a spike package"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "tsup",
|
|
67
|
+
"test": "vitest run"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import type { ImportContext, ImportRunnerFs as PublicImportRunnerFs } from '@forgeax/engine-import';
|
|
3
|
+
import { describe, expect, expectTypeOf, it } from 'vitest';
|
|
4
|
+
import type { ImportRunnerFs } from '../import-runner.js';
|
|
5
|
+
|
|
6
|
+
type DecodeImage = ImportContext['decodeImage'];
|
|
7
|
+
type RunnerDecodeImage = NonNullable<ImportRunnerFs['decodeImage']>;
|
|
8
|
+
type DecodeImageMime = Parameters<DecodeImage>[1];
|
|
9
|
+
type DecodeImageResult = Awaited<ReturnType<DecodeImage>>;
|
|
10
|
+
|
|
11
|
+
const readSource: ImportRunnerFs['readSource'] = async () => ({
|
|
12
|
+
ok: true,
|
|
13
|
+
value: new Uint8Array(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
declare const hostDecodeImage: DecodeImage;
|
|
17
|
+
|
|
18
|
+
const runnerSource = readFileSync(new URL('../import-runner.ts', import.meta.url), 'utf8');
|
|
19
|
+
|
|
20
|
+
function assertOptionalHostInjection(): void {
|
|
21
|
+
const withoutHostDecoder = {
|
|
22
|
+
readSource,
|
|
23
|
+
} satisfies PublicImportRunnerFs;
|
|
24
|
+
|
|
25
|
+
const withHostDecoder = {
|
|
26
|
+
readSource,
|
|
27
|
+
decodeImage: hostDecodeImage,
|
|
28
|
+
} satisfies PublicImportRunnerFs;
|
|
29
|
+
|
|
30
|
+
void withoutHostDecoder;
|
|
31
|
+
void withHostDecoder;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
void assertOptionalHostInjection;
|
|
35
|
+
|
|
36
|
+
describe('ImportRunnerFs decodeImage owner', () => {
|
|
37
|
+
it('derives the public callback and result envelope from ImportContext', () => {
|
|
38
|
+
expectTypeOf<RunnerDecodeImage>().toEqualTypeOf<DecodeImage>();
|
|
39
|
+
expectTypeOf<PublicImportRunnerFs>().toEqualTypeOf<ImportRunnerFs>();
|
|
40
|
+
expectTypeOf<Awaited<ReturnType<RunnerDecodeImage>>>().toEqualTypeOf<DecodeImageResult>();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('preserves the PNG/JPEG/TGA input contract and optional host injection', () => {
|
|
44
|
+
expectTypeOf<DecodeImageMime>().toEqualTypeOf<'image/png' | 'image/jpeg' | 'image/x-tga'>();
|
|
45
|
+
expectTypeOf<ImportRunnerFs['decodeImage']>().toEqualTypeOf<DecodeImage | undefined>();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('keeps both runner views derived from the authority', () => {
|
|
49
|
+
expect(runnerSource).toContain("decodeImage?: ImportContext['decodeImage'];");
|
|
50
|
+
expect(runnerSource).toContain("const decodeImage: ImportContext['decodeImage'] =");
|
|
51
|
+
expect(runnerSource).not.toContain("mimeType: 'image/png' | 'image/jpeg'");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import type { Asset, AssetRelation, ImportedAsset, ImportResult } from '@forgeax/engine-types';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { createImportProduct } from '../import-product.js';
|
|
6
|
+
import { type RunImportMeta, runImport } from '../import-runner.js';
|
|
7
|
+
import { ImporterRegistry } from '../importer-registry.js';
|
|
8
|
+
|
|
9
|
+
const GUID = '019e2cc6-0c86-79da-aa76-b0984c86d45c';
|
|
10
|
+
const DEPENDENCY_GUID = '019e2cc6-0c86-79da-aa76-b0984c86d45d';
|
|
11
|
+
const MESH = {
|
|
12
|
+
kind: 'mesh',
|
|
13
|
+
vertices: new Float32Array(),
|
|
14
|
+
indices: new Uint16Array(),
|
|
15
|
+
attributes: {},
|
|
16
|
+
} as unknown as Asset;
|
|
17
|
+
|
|
18
|
+
const relation: AssetRelation = {
|
|
19
|
+
from: { type: 'asset', id: GUID },
|
|
20
|
+
to: { type: 'asset', id: DEPENDENCY_GUID },
|
|
21
|
+
type: 'references',
|
|
22
|
+
provenance: { provider: 'fixture-producer', version: '2.0.0' },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function registry(): ImporterRegistry {
|
|
26
|
+
const registry = new ImporterRegistry();
|
|
27
|
+
registry.register({
|
|
28
|
+
key: 'fixture',
|
|
29
|
+
import: async (): Promise<ImportResult> => ({
|
|
30
|
+
ok: true,
|
|
31
|
+
value: {
|
|
32
|
+
assets: [
|
|
33
|
+
{
|
|
34
|
+
guid: GUID,
|
|
35
|
+
kind: 'mesh',
|
|
36
|
+
payload: MESH,
|
|
37
|
+
refs: [{ guid: DEPENDENCY_GUID }],
|
|
38
|
+
artifacts: {},
|
|
39
|
+
} satisfies ImportedAsset,
|
|
40
|
+
],
|
|
41
|
+
sourceDependencies: [],
|
|
42
|
+
},
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
return registry;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readFs() {
|
|
49
|
+
return {
|
|
50
|
+
readSource: async () => ({ ok: true as const, value: new Uint8Array([1]) }),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function multiMeta(
|
|
55
|
+
overrides: readonly (Partial<RunImportMeta['subAssets'][number]> | undefined)[] = [],
|
|
56
|
+
): RunImportMeta {
|
|
57
|
+
const entries = [
|
|
58
|
+
{ guid: GUID, sourceKey: 'scene/main', sourceIndex: 0, kind: 'mesh' },
|
|
59
|
+
{ guid: DEPENDENCY_GUID, sourceKey: 'scene/detail', sourceIndex: 1, kind: 'mesh' },
|
|
60
|
+
];
|
|
61
|
+
return {
|
|
62
|
+
importer: 'fixture',
|
|
63
|
+
source: 'models/fixture.glb',
|
|
64
|
+
subAssets: entries.map((entry, index) => ({ ...entry, ...(overrides[index] ?? {}) })),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function meta(facts: Record<string, unknown> = {}): RunImportMeta {
|
|
69
|
+
return {
|
|
70
|
+
importer: 'fixture',
|
|
71
|
+
source: 'models/fixture.glb',
|
|
72
|
+
...facts,
|
|
73
|
+
subAssets: [
|
|
74
|
+
{
|
|
75
|
+
guid: GUID,
|
|
76
|
+
sourceKey: 'scene/main',
|
|
77
|
+
sourceIndex: 0,
|
|
78
|
+
kind: 'mesh',
|
|
79
|
+
relations: [relation],
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
} as unknown as RunImportMeta;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
describe('import runner producer fact propagation', () => {
|
|
86
|
+
it('rejects sourceIndex-only declarations before reading source or invoking importer', async () => {
|
|
87
|
+
let reads = 0;
|
|
88
|
+
const keyedMeta = multiMeta();
|
|
89
|
+
const sourceIndexOnlyMeta: RunImportMeta = {
|
|
90
|
+
...keyedMeta,
|
|
91
|
+
subAssets: keyedMeta.subAssets.map(
|
|
92
|
+
({ sourceKey: _sourceKey, ...declaration }) => declaration,
|
|
93
|
+
),
|
|
94
|
+
};
|
|
95
|
+
const result = await runImport(sourceIndexOnlyMeta, registry(), {
|
|
96
|
+
readSource: async () => {
|
|
97
|
+
reads += 1;
|
|
98
|
+
return { ok: true as const, value: new Uint8Array([1]) };
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expect(result.ok).toBe(false);
|
|
103
|
+
if (!result.ok) {
|
|
104
|
+
expect(result.error.code).toBe('source-validation-failed');
|
|
105
|
+
expect(result.error.detail).toMatchObject({
|
|
106
|
+
diagnostics: expect.arrayContaining([
|
|
107
|
+
expect.objectContaining({ code: 'source-key-required' }),
|
|
108
|
+
]),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
expect(reads).toBe(0);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('rejects duplicate, empty, and malformed sourceKey before Meta publication', async () => {
|
|
115
|
+
for (const override of [
|
|
116
|
+
{ sourceKey: 'scene/main' },
|
|
117
|
+
{ sourceKey: ' ' },
|
|
118
|
+
{ sourceKey: 7 as unknown as string },
|
|
119
|
+
]) {
|
|
120
|
+
const result = await runImport(multiMeta([undefined, override]), registry(), readFs());
|
|
121
|
+
expect(result.ok).toBe(false);
|
|
122
|
+
if (!result.ok) expect(result.error.code).toBe('source-validation-failed');
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('preserves complete producer facts on the DDC row matched by GUID', async () => {
|
|
127
|
+
const facts = {
|
|
128
|
+
packageId: 'package/fixture',
|
|
129
|
+
provenance: { provider: 'fixture-producer', version: '2.0.0', source: 'fixture.glb' },
|
|
130
|
+
revision: { digest: 'sha256:fixture', observedAt: 42, rootId: 'root-fixture' },
|
|
131
|
+
diagnostics: [
|
|
132
|
+
{
|
|
133
|
+
code: 'fixture-warning',
|
|
134
|
+
severity: 'warning',
|
|
135
|
+
hint: 'reimport the fixture when its source changes',
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
};
|
|
139
|
+
const result = await runImport(meta(facts), registry(), readFs());
|
|
140
|
+
|
|
141
|
+
expect(result.ok).toBe(true);
|
|
142
|
+
if (!result.ok || 'skipped' in result.value) return;
|
|
143
|
+
expect(result.value.pack).toMatchObject(facts);
|
|
144
|
+
expect(result.value.pack.assets).toEqual([
|
|
145
|
+
expect.objectContaining({
|
|
146
|
+
guid: GUID,
|
|
147
|
+
sourceKey: 'scene/main',
|
|
148
|
+
sourceIndex: 0,
|
|
149
|
+
relations: [relation],
|
|
150
|
+
}),
|
|
151
|
+
]);
|
|
152
|
+
expect(result.value.cookProducts).toEqual([
|
|
153
|
+
expect.objectContaining({
|
|
154
|
+
guid: GUID,
|
|
155
|
+
payload: MESH,
|
|
156
|
+
refs: [DEPENDENCY_GUID],
|
|
157
|
+
artifacts: {},
|
|
158
|
+
receipt: expect.objectContaining({
|
|
159
|
+
guid: GUID,
|
|
160
|
+
origin: 'sourceMeta',
|
|
161
|
+
status: 'succeeded',
|
|
162
|
+
}),
|
|
163
|
+
}),
|
|
164
|
+
]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('keeps optional producer facts absent when the declaration has no evidence', async () => {
|
|
168
|
+
const result = await runImport(meta(), registry(), readFs());
|
|
169
|
+
|
|
170
|
+
expect(result.ok).toBe(true);
|
|
171
|
+
if (!result.ok || 'skipped' in result.value) return;
|
|
172
|
+
expect(result.value.pack).not.toHaveProperty('packageId');
|
|
173
|
+
expect(result.value.pack).not.toHaveProperty('provenance');
|
|
174
|
+
expect(result.value.pack).not.toHaveProperty('revision');
|
|
175
|
+
expect(result.value.pack).not.toHaveProperty('diagnostics');
|
|
176
|
+
expect(result.value.pack.assets[0]).toMatchObject({
|
|
177
|
+
guid: GUID,
|
|
178
|
+
sourceKey: 'scene/main',
|
|
179
|
+
sourceIndex: 0,
|
|
180
|
+
relations: [relation],
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('removes package-global artifact and kind-specific bin contracts from the runner', () => {
|
|
185
|
+
const source = readFileSync(resolve(import.meta.dirname, '..', 'import-runner.ts'), 'utf8');
|
|
186
|
+
expect(source).not.toContain('RunImportOk.bins');
|
|
187
|
+
expect(source).not.toContain('readonly bins');
|
|
188
|
+
expect(source).not.toContain("a.kind === 'texture'");
|
|
189
|
+
expect(source).not.toContain("a.kind === 'mesh'");
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('engine-import terminal product contract', () => {
|
|
194
|
+
it('projects refs, artifacts, receipts, diagnostics, and source identity together', () => {
|
|
195
|
+
const result = createImportProduct({
|
|
196
|
+
assets: [
|
|
197
|
+
{
|
|
198
|
+
guid: '019e3969-1d48-7c3b-ac24-6d68f457065f',
|
|
199
|
+
kind: 'texture',
|
|
200
|
+
payload: { kind: 'texture', width: 1, height: 1 },
|
|
201
|
+
refs: [],
|
|
202
|
+
artifacts: {},
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
sourceDependencies: ['hero.png'],
|
|
206
|
+
refs: [],
|
|
207
|
+
artifacts: {},
|
|
208
|
+
receipts: [],
|
|
209
|
+
diagnostics: [],
|
|
210
|
+
sourceRevision: 'sha256:hero',
|
|
211
|
+
sourceKey: 'hero/albedo',
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
expect(result.ok).toBe(true);
|
|
215
|
+
if (!result.ok) return;
|
|
216
|
+
expect(result.value.sourceRevision).toBe('sha256:hero');
|
|
217
|
+
expect(result.value.sourceKey).toBe('hero/albedo');
|
|
218
|
+
expect(result.value).toHaveProperty('refs');
|
|
219
|
+
expect(result.value).toHaveProperty('artifacts');
|
|
220
|
+
expect(result.value).toHaveProperty('receipts');
|
|
221
|
+
expect(result.value).toHaveProperty('diagnostics');
|
|
222
|
+
});
|
|
223
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Asset, ImportContext, Importer, ImportResult } from '@forgeax/engine-types';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { type RunImportMeta, runImport } from '../import-runner.js';
|
|
4
|
+
import { ImporterRegistry } from '../importer-registry.js';
|
|
5
|
+
|
|
6
|
+
const GUID = '019e2cc6-0c86-79da-aa76-b0984c86d45c';
|
|
7
|
+
const MESH = {
|
|
8
|
+
kind: 'mesh',
|
|
9
|
+
vertices: new Float32Array(),
|
|
10
|
+
indices: new Uint16Array(),
|
|
11
|
+
attributes: {},
|
|
12
|
+
} as unknown as Asset;
|
|
13
|
+
|
|
14
|
+
function meta(): RunImportMeta {
|
|
15
|
+
return {
|
|
16
|
+
importer: 'fixture',
|
|
17
|
+
source: 'ui/main.html',
|
|
18
|
+
subAssets: [{ guid: GUID, sourceIndex: 0, kind: 'mesh' }],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('import runner dependency observation', () => {
|
|
23
|
+
it('records normalized source and sibling reads exactly once, including retries', async () => {
|
|
24
|
+
const reads: string[] = [];
|
|
25
|
+
const importer: Importer = {
|
|
26
|
+
key: 'fixture',
|
|
27
|
+
async import(ctx: ImportContext): Promise<ImportResult> {
|
|
28
|
+
await ctx.readSibling('./main.css');
|
|
29
|
+
const missing = await ctx.readSibling('icons/missing.svg');
|
|
30
|
+
expect(missing.ok).toBe(false);
|
|
31
|
+
await ctx.readSibling('icons/missing.svg');
|
|
32
|
+
return {
|
|
33
|
+
ok: true,
|
|
34
|
+
value: {
|
|
35
|
+
assets: [{ guid: GUID, kind: 'mesh', payload: MESH, refs: [], artifacts: {} }],
|
|
36
|
+
sourceDependencies: [],
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
const registry = new ImporterRegistry();
|
|
42
|
+
registry.register(importer);
|
|
43
|
+
const result = await runImport(meta(), registry, {
|
|
44
|
+
readSource: async (source) => {
|
|
45
|
+
reads.push(source);
|
|
46
|
+
if (source.endsWith('missing.svg')) return { ok: false, error: new Error('ENOENT') };
|
|
47
|
+
return { ok: true, value: new Uint8Array([1]) };
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
expect(result.ok).toBe(true);
|
|
51
|
+
if (result.ok && 'product' in result.value) {
|
|
52
|
+
expect(result.value.product.sourceDependencies).toEqual([
|
|
53
|
+
'ui/main.html',
|
|
54
|
+
'ui/main.css',
|
|
55
|
+
'ui/icons/missing.svg',
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
expect(reads).toEqual([
|
|
59
|
+
'ui/main.html',
|
|
60
|
+
'ui/./main.css',
|
|
61
|
+
'ui/icons/missing.svg',
|
|
62
|
+
'ui/icons/missing.svg',
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { ImportContext, ImportResult } from '@forgeax/engine-types';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { type RunImportMeta, runImport } from '../import-runner.js';
|
|
4
|
+
import { ImporterRegistry } from '../importer-registry.js';
|
|
5
|
+
|
|
6
|
+
const GUID = '019f0000-0000-7000-8000-000000000071';
|
|
7
|
+
const SOURCE = 'assets/m71.fixture';
|
|
8
|
+
|
|
9
|
+
const meta: RunImportMeta = {
|
|
10
|
+
importer: 'm71-fixture',
|
|
11
|
+
source: SOURCE,
|
|
12
|
+
subAssets: [{ guid: GUID, sourceIndex: 0, sourceKey: 'fixture:main', kind: 'reel-game-blob' }],
|
|
13
|
+
importSettings: { revision: 'm71' },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function createRegistry(): ImporterRegistry {
|
|
17
|
+
const registry = new ImporterRegistry();
|
|
18
|
+
registry.register({
|
|
19
|
+
key: meta.importer,
|
|
20
|
+
import: async (ctx: ImportContext): Promise<ImportResult<unknown>> => {
|
|
21
|
+
const source = await ctx.readSource();
|
|
22
|
+
if (!source.ok) throw new Error('fixture source was not readable');
|
|
23
|
+
return {
|
|
24
|
+
ok: true,
|
|
25
|
+
value: {
|
|
26
|
+
assets: [
|
|
27
|
+
{
|
|
28
|
+
guid: GUID,
|
|
29
|
+
kind: 'reel-game-blob',
|
|
30
|
+
payload: { kind: 'reel-game-blob', title: 'M71 digest fixture' },
|
|
31
|
+
refs: [],
|
|
32
|
+
artifacts: {
|
|
33
|
+
body: {
|
|
34
|
+
mediaType: 'application/x-forgeax-m71',
|
|
35
|
+
bytes: new Uint8Array([11, 71, 1, 9]),
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
sourceDependencies: [],
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
return registry;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createFs(reads: { count: number }) {
|
|
49
|
+
const sourceBytes = new Uint8Array([77, 55, 49, 0]);
|
|
50
|
+
return {
|
|
51
|
+
readSource: async (path: string) => {
|
|
52
|
+
reads.count += 1;
|
|
53
|
+
return path === SOURCE
|
|
54
|
+
? { ok: true as const, value: sourceBytes }
|
|
55
|
+
: { ok: false as const, error: new Error(`unexpected source path ${path}`) };
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe('public import finalizer digest recovery', () => {
|
|
61
|
+
it('returns a structured finalization error and retries idempotently on the same registry', async () => {
|
|
62
|
+
const registry = createRegistry();
|
|
63
|
+
const reads = { count: 0 };
|
|
64
|
+
const fs = createFs(reads);
|
|
65
|
+
const subtle = globalThis.crypto?.subtle;
|
|
66
|
+
if (subtle === undefined) throw new Error('Web Crypto subtle is unavailable');
|
|
67
|
+
const originalDigest = subtle.digest;
|
|
68
|
+
const publishedDigests = new Set<string>();
|
|
69
|
+
|
|
70
|
+
subtle.digest = async () => {
|
|
71
|
+
throw new Error('M71 injected subtle.digest refusal');
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
let failed: Awaited<ReturnType<typeof runImport>>;
|
|
75
|
+
try {
|
|
76
|
+
failed = await runImport(meta, registry, fs);
|
|
77
|
+
} finally {
|
|
78
|
+
subtle.digest = originalDigest;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
expect(failed.ok).toBe(false);
|
|
82
|
+
expect(failed).not.toHaveProperty('value');
|
|
83
|
+
if (!failed.ok) {
|
|
84
|
+
expect(failed.error.code).toBe('import-internal-error');
|
|
85
|
+
expect(failed.error.expected).toContain('finalization');
|
|
86
|
+
expect(failed.error.detail).toEqual({
|
|
87
|
+
reason: expect.stringMatching(/finalization.*digest.*M71 injected subtle\.digest refusal/),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const repaired = await runImport(meta, registry, fs);
|
|
92
|
+
expect(repaired.ok).toBe(true);
|
|
93
|
+
if (!repaired.ok || 'skipped' in repaired.value)
|
|
94
|
+
throw new Error('repair must publish a product');
|
|
95
|
+
expect(repaired.value.cookProducts).toHaveLength(1);
|
|
96
|
+
expect(repaired.value.pack.assets).toHaveLength(1);
|
|
97
|
+
const repairedDigest = repaired.value.cookProducts[0]?.digest;
|
|
98
|
+
if (repairedDigest === undefined) throw new Error('repair must produce a digest');
|
|
99
|
+
publishedDigests.add(repairedDigest);
|
|
100
|
+
|
|
101
|
+
const third = await runImport(meta, registry, fs);
|
|
102
|
+
expect(third.ok).toBe(true);
|
|
103
|
+
if (!third.ok || 'skipped' in third.value)
|
|
104
|
+
throw new Error('idempotent run must publish a product');
|
|
105
|
+
expect(third.value.cookProducts[0]?.digest).toBe(repairedDigest);
|
|
106
|
+
expect(third.value.pack).toEqual(repaired.value.pack);
|
|
107
|
+
publishedDigests.add(third.value.cookProducts[0]?.digest ?? '');
|
|
108
|
+
|
|
109
|
+
expect(publishedDigests.size).toBe(1);
|
|
110
|
+
expect(registry.registeredImporters()).toEqual([meta.importer]);
|
|
111
|
+
expect(reads.count).toBe(6);
|
|
112
|
+
});
|
|
113
|
+
});
|