@forgeax/engine-import 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 +226 -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__/material-import-product.unit.test.d.ts +2 -0
- package/dist/__tests__/material-import-product.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__/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-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/import-product.d.ts +36 -0
- package/dist/import-product.d.ts.map +1 -0
- package/dist/import-runner.d.ts +133 -0
- package/dist/import-runner.d.ts.map +1 -0
- package/dist/importer-registry.d.ts +40 -0
- package/dist/importer-registry.d.ts.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +1607 -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/scriptable-pack-output-producers.d.ts +6 -0
- package/dist/scriptable-pack-output-producers.d.ts.map +1 -0
- package/dist/scriptable-pack-staged-snapshot.d.ts +22 -0
- package/dist/scriptable-pack-staged-snapshot.d.ts.map +1 -0
- package/dist/scriptable-pack.d.ts +153 -0
- package/dist/scriptable-pack.d.ts.map +1 -0
- package/package.json +63 -0
- package/src/__tests__/decode-image-owner.test-d.ts +53 -0
- package/src/__tests__/import-contract-migration.test.ts +190 -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 +100 -0
- package/src/__tests__/import.unit.test.ts +429 -0
- package/src/__tests__/material-import-product.unit.test.ts +56 -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__/scriptable-pack-output-producers.unit.test.ts +190 -0
- package/src/__tests__/scriptable-pack-production-producers.unit.test.ts +429 -0
- package/src/__tests__/scriptable-pack-staged-snapshot.unit.test.ts +258 -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/import-product.ts +195 -0
- package/src/import-runner.ts +720 -0
- package/src/importer-registry.ts +80 -0
- package/src/index.ts +103 -0
- package/src/mesh-bin.ts +229 -0
- package/src/scriptable-pack-output-producers.ts +466 -0
- package/src/scriptable-pack-staged-snapshot.ts +161 -0
- package/src/scriptable-pack.ts +615 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ArtifactDescriptor,
|
|
3
|
+
AssetRef,
|
|
4
|
+
CookProduct,
|
|
5
|
+
ImportedArtifactBody,
|
|
6
|
+
ImportedAsset,
|
|
7
|
+
ImportProduct,
|
|
8
|
+
MaterialAsset,
|
|
9
|
+
Result,
|
|
10
|
+
} from '@forgeax/engine-types';
|
|
11
|
+
import { err, ok } from '@forgeax/engine-types';
|
|
12
|
+
|
|
13
|
+
export interface MaterialImportRefs {
|
|
14
|
+
readonly parent: readonly string[];
|
|
15
|
+
readonly textures: readonly string[];
|
|
16
|
+
readonly samplers: readonly string[];
|
|
17
|
+
readonly modules: readonly string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MaterialSourceEvidence {
|
|
21
|
+
readonly inputFingerprint: string;
|
|
22
|
+
readonly importerVersion: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface MaterialImportProductInput {
|
|
26
|
+
readonly guid: string;
|
|
27
|
+
readonly sourcePath: string;
|
|
28
|
+
readonly material: MaterialAsset;
|
|
29
|
+
readonly refs: MaterialImportRefs;
|
|
30
|
+
readonly sourceEvidence: MaterialSourceEvidence;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface MaterialImportProduct {
|
|
34
|
+
readonly asset: ImportedAsset<MaterialAsset>;
|
|
35
|
+
readonly sourcePath: string;
|
|
36
|
+
readonly sourceEvidence: MaterialSourceEvidence;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface MaterialImportProductError {
|
|
40
|
+
readonly code: 'material-import-product-invalid';
|
|
41
|
+
readonly expected: string;
|
|
42
|
+
readonly hint: string;
|
|
43
|
+
readonly detail: { readonly field: string };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function invalid(field: string): Result<never, MaterialImportProductError> {
|
|
47
|
+
return err({
|
|
48
|
+
code: 'material-import-product-invalid',
|
|
49
|
+
expected: 'an authored MaterialAsset with complete dependency references',
|
|
50
|
+
hint: 'declare every material dependency before the product enters the cook pipeline',
|
|
51
|
+
detail: { field },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function distinct(values: readonly string[]): readonly string[] {
|
|
56
|
+
return [...new Set(values)];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function createMaterialImportProduct(
|
|
60
|
+
input: MaterialImportProductInput,
|
|
61
|
+
): Result<MaterialImportProduct, MaterialImportProductError> {
|
|
62
|
+
if (!input.guid || !input.sourcePath) return invalid('guid/sourcePath');
|
|
63
|
+
if (input.material.kind !== 'material') return invalid('material.kind');
|
|
64
|
+
if (!input.sourceEvidence.inputFingerprint || !input.sourceEvidence.importerVersion) {
|
|
65
|
+
return invalid('sourceEvidence');
|
|
66
|
+
}
|
|
67
|
+
const refs = distinct([
|
|
68
|
+
...input.refs.parent,
|
|
69
|
+
...input.refs.textures,
|
|
70
|
+
...input.refs.samplers,
|
|
71
|
+
...input.refs.modules,
|
|
72
|
+
]);
|
|
73
|
+
return ok({
|
|
74
|
+
sourcePath: input.sourcePath,
|
|
75
|
+
sourceEvidence: input.sourceEvidence,
|
|
76
|
+
asset: {
|
|
77
|
+
guid: input.guid,
|
|
78
|
+
kind: 'material',
|
|
79
|
+
payload: input.material,
|
|
80
|
+
refs: refs.map((guid): AssetRef => ({ guid })),
|
|
81
|
+
artifacts: {},
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function materialImportProductReady(
|
|
87
|
+
product: MaterialImportProduct,
|
|
88
|
+
availableRefs: ReadonlySet<string>,
|
|
89
|
+
): boolean {
|
|
90
|
+
return product.asset.refs.every((reference) => availableRefs.has(reference.guid));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function sha256Hex(bytes: Uint8Array): Promise<string> {
|
|
94
|
+
const subtle = globalThis.crypto?.subtle;
|
|
95
|
+
if (subtle === undefined) throw new Error('Web Crypto API is required for importer digests');
|
|
96
|
+
const owned = new Uint8Array(bytes.byteLength);
|
|
97
|
+
owned.set(bytes);
|
|
98
|
+
const digest = await subtle.digest('SHA-256', owned.buffer);
|
|
99
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function artifactDigest(bytes: Uint8Array): Promise<string> {
|
|
103
|
+
return `sha256:${await sha256Hex(bytes)}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function concatBytes(chunks: readonly (Uint8Array | string)[]): Uint8Array {
|
|
107
|
+
const encoder = new TextEncoder();
|
|
108
|
+
const encoded = chunks.map((chunk) =>
|
|
109
|
+
typeof chunk === 'string' ? encoder.encode(chunk) : chunk,
|
|
110
|
+
);
|
|
111
|
+
const totalLength = encoded.reduce((total, chunk) => total + chunk.byteLength, 0);
|
|
112
|
+
const bytes = new Uint8Array(totalLength);
|
|
113
|
+
let offset = 0;
|
|
114
|
+
for (const chunk of encoded) {
|
|
115
|
+
bytes.set(chunk, offset);
|
|
116
|
+
offset += chunk.byteLength;
|
|
117
|
+
}
|
|
118
|
+
return bytes;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function productDigest(
|
|
122
|
+
asset: ImportedAsset<unknown>,
|
|
123
|
+
artifacts: Readonly<Record<string, ArtifactDescriptor>>,
|
|
124
|
+
): Promise<string> {
|
|
125
|
+
const chunks: (Uint8Array | string)[] = [
|
|
126
|
+
JSON.stringify(digestPayload(asset)),
|
|
127
|
+
JSON.stringify(asset.refs.map((ref) => ref.guid)),
|
|
128
|
+
JSON.stringify(artifacts),
|
|
129
|
+
];
|
|
130
|
+
for (const [key, artifact] of Object.entries(asset.artifacts).sort(([left], [right]) =>
|
|
131
|
+
left.localeCompare(right),
|
|
132
|
+
)) {
|
|
133
|
+
chunks.push(key, artifact.bytes);
|
|
134
|
+
}
|
|
135
|
+
return `sha256:${await sha256Hex(concatBytes(chunks))}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function artifactDescriptors(
|
|
139
|
+
artifacts: Readonly<Record<string, ImportedArtifactBody>>,
|
|
140
|
+
): Promise<Readonly<Record<string, ArtifactDescriptor>>> {
|
|
141
|
+
const entries: [string, ArtifactDescriptor][] = [];
|
|
142
|
+
for (const [path, artifact] of Object.entries(artifacts)) {
|
|
143
|
+
entries.push([
|
|
144
|
+
path,
|
|
145
|
+
{
|
|
146
|
+
path,
|
|
147
|
+
mediaType: artifact.mediaType,
|
|
148
|
+
byteLength: artifact.bytes.byteLength,
|
|
149
|
+
integrity: { algorithm: 'sha256' as const, digest: await artifactDigest(artifact.bytes) },
|
|
150
|
+
...(artifact.assetCodec === undefined ? {} : { assetCodec: artifact.assetCodec }),
|
|
151
|
+
},
|
|
152
|
+
]);
|
|
153
|
+
}
|
|
154
|
+
return Object.fromEntries(entries);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function digestPayload(asset: ImportedAsset<unknown>): unknown {
|
|
158
|
+
if (Object.keys(asset.artifacts).length === 0) return asset.payload;
|
|
159
|
+
if (asset.kind === 'mesh') return { kind: 'mesh' };
|
|
160
|
+
if (asset.kind === 'texture' || asset.kind === 'equirect') {
|
|
161
|
+
const payload = asset.payload as Record<string, unknown>;
|
|
162
|
+
const { data: _runtimeBytes, ...metadata } = payload;
|
|
163
|
+
return metadata;
|
|
164
|
+
}
|
|
165
|
+
return asset.payload;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Convert each importer asset into the shared completed producer product. */
|
|
169
|
+
export function finalizeImportProducts(
|
|
170
|
+
product: ImportProduct<unknown>,
|
|
171
|
+
inputFingerprint: string,
|
|
172
|
+
): Promise<readonly CookProduct[]> {
|
|
173
|
+
return (async () => {
|
|
174
|
+
const products: CookProduct[] = [];
|
|
175
|
+
for (const asset of product.assets) {
|
|
176
|
+
const artifacts = await artifactDescriptors(asset.artifacts);
|
|
177
|
+
const digest = await productDigest(asset, artifacts);
|
|
178
|
+
products.push({
|
|
179
|
+
guid: asset.guid,
|
|
180
|
+
payload: asset.payload,
|
|
181
|
+
refs: asset.refs.map((ref) => ref.guid),
|
|
182
|
+
artifacts,
|
|
183
|
+
digest,
|
|
184
|
+
receipt: {
|
|
185
|
+
guid: asset.guid,
|
|
186
|
+
origin: 'sourceMeta' as const,
|
|
187
|
+
status: 'succeeded' as const,
|
|
188
|
+
inputFingerprint,
|
|
189
|
+
outputDigest: digest,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return products;
|
|
194
|
+
})();
|
|
195
|
+
}
|