@forgeax/engine-pack 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 +370 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/ai-recovery.unit.test.d.ts +2 -0
- package/dist/__tests__/ai-recovery.unit.test.d.ts.map +1 -0
- package/dist/__tests__/artifact-path.test.d.ts +2 -0
- package/dist/__tests__/artifact-path.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-error-hints.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-error-hints.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-lifecycle.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-lifecycle.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-schema.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-schema.test.d.ts.map +1 -0
- package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts +2 -0
- package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts.map +1 -0
- package/dist/__tests__/atlas-error-code-owner.test-d.d.ts +2 -0
- package/dist/__tests__/atlas-error-code-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/cli-asset-evidence.integration.test.d.ts +2 -0
- package/dist/__tests__/cli-asset-evidence.integration.test.d.ts.map +1 -0
- package/dist/__tests__/cli.unit.test.d.ts +2 -0
- package/dist/__tests__/cli.unit.test.d.ts.map +1 -0
- package/dist/__tests__/evidence-source-inventory.unit.test.d.ts +2 -0
- package/dist/__tests__/evidence-source-inventory.unit.test.d.ts.map +1 -0
- package/dist/__tests__/guid-collision.unit.test.d.ts +2 -0
- package/dist/__tests__/guid-collision.unit.test.d.ts.map +1 -0
- package/dist/__tests__/guid-validator-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/guid-validator-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/load-asset-config.test.d.ts +2 -0
- package/dist/__tests__/load-asset-config.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-dependencies.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-dependencies.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-receipt.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-receipt.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-schema.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-schema.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/meta-schema-optional.test.d.ts +2 -0
- package/dist/__tests__/meta-schema-optional.test.d.ts.map +1 -0
- package/dist/__tests__/meta-source-overrides.unit.test.d.ts +2 -0
- package/dist/__tests__/meta-source-overrides.unit.test.d.ts.map +1 -0
- package/dist/__tests__/native-cooker-registry.test.d.ts +2 -0
- package/dist/__tests__/native-cooker-registry.test.d.ts.map +1 -0
- package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts +2 -0
- package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/offline-evidence.integration.test.d.ts +2 -0
- package/dist/__tests__/offline-evidence.integration.test.d.ts.map +1 -0
- package/dist/__tests__/pack-v2-fixture-migration.test.d.ts +2 -0
- package/dist/__tests__/pack-v2-fixture-migration.test.d.ts.map +1 -0
- package/dist/__tests__/pack-v2-schema.test.d.ts +2 -0
- package/dist/__tests__/pack-v2-schema.test.d.ts.map +1 -0
- package/dist/__tests__/pack.unit.test.d.ts +2 -0
- package/dist/__tests__/pack.unit.test.d.ts.map +1 -0
- package/dist/__tests__/producer-contract-schema.unit.test.d.ts +2 -0
- package/dist/__tests__/producer-contract-schema.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-asset-source.test.d.ts +2 -0
- package/dist/__tests__/resolve-asset-source.test.d.ts.map +1 -0
- package/dist/__tests__/runtime-projection.unit.test.d.ts +2 -0
- package/dist/__tests__/runtime-projection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-inventory.test.d.ts +2 -0
- package/dist/__tests__/scanner-inventory.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-mount-cycle.test.d.ts +2 -0
- package/dist/__tests__/scanner-mount-cycle.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack.test-d.d.ts +2 -0
- package/dist/__tests__/scriptable-pack.test-d.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack.unit.test.d.ts.map +1 -0
- package/dist/__tests__/topology-actions.unit.test.d.ts +2 -0
- package/dist/__tests__/topology-actions.unit.test.d.ts.map +1 -0
- package/dist/__tests__/topology.unit.test.d.ts +2 -0
- package/dist/__tests__/topology.unit.test.d.ts.map +1 -0
- package/dist/artifact-path.d.ts +9 -0
- package/dist/artifact-path.d.ts.map +1 -0
- package/dist/atlas/run-atlas.d.ts +8 -0
- package/dist/atlas/run-atlas.d.ts.map +1 -0
- package/dist/atlas/shelf-pack.d.ts +55 -0
- package/dist/atlas/shelf-pack.d.ts.map +1 -0
- package/dist/bridge.d.ts +2 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.mjs +3 -0
- package/dist/bridge.mjs.map +1 -0
- package/dist/builtin.d.ts +24 -0
- package/dist/builtin.d.ts.map +1 -0
- package/dist/cli-asset.d.ts +22 -0
- package/dist/cli-asset.d.ts.map +1 -0
- package/dist/cli-asset.mjs +9950 -0
- package/dist/cli-asset.mjs.map +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.mjs +29 -0
- package/dist/config.mjs.map +1 -0
- package/dist/deriveAssetName.d.ts +15 -0
- package/dist/deriveAssetName.d.ts.map +1 -0
- package/dist/errors.d.ts +30 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.mjs +19 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/evidence/material-cook.d.ts +70 -0
- package/dist/evidence/material-cook.d.ts.map +1 -0
- package/dist/evidence/offline-evidence.d.ts +25 -0
- package/dist/evidence/offline-evidence.d.ts.map +1 -0
- package/dist/evidence/source-inventory.d.ts +29 -0
- package/dist/evidence/source-inventory.d.ts.map +1 -0
- package/dist/guid.d.ts +45 -0
- package/dist/guid.d.ts.map +1 -0
- package/dist/guid.mjs +94 -0
- package/dist/guid.mjs.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +8475 -0
- package/dist/index.mjs.map +1 -0
- package/dist/material/artifact-writer.d.ts +14 -0
- package/dist/material/artifact-writer.d.ts.map +1 -0
- package/dist/mesh-bin-contract.d.ts +36 -0
- package/dist/mesh-bin-contract.d.ts.map +1 -0
- package/dist/name.mjs +22 -0
- package/dist/name.mjs.map +1 -0
- package/dist/native-cooker-registry.d.ts +28 -0
- package/dist/native-cooker-registry.d.ts.map +1 -0
- package/dist/native-cooker.d.ts +3 -0
- package/dist/native-cooker.d.ts.map +1 -0
- package/dist/native-cooker.mjs +105 -0
- package/dist/native-cooker.mjs.map +1 -0
- package/dist/producer-contract.d.ts +19 -0
- package/dist/producer-contract.d.ts.map +1 -0
- package/dist/resolve-asset-source.d.ts +4 -0
- package/dist/resolve-asset-source.d.ts.map +1 -0
- package/dist/resolve-asset-source.mjs +99 -0
- package/dist/resolve-asset-source.mjs.map +1 -0
- package/dist/runtime-projection.d.ts +14 -0
- package/dist/runtime-projection.d.ts.map +1 -0
- package/dist/scanner.d.ts +80 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.mjs +9015 -0
- package/dist/scanner.mjs.map +1 -0
- package/dist/schema-compiled.d.ts +12 -0
- package/dist/schema-compiled.d.ts.map +1 -0
- package/dist/schema.mjs +7369 -0
- package/dist/schema.mjs.map +1 -0
- package/dist/scriptable-pack-node.d.ts +67 -0
- package/dist/scriptable-pack-node.d.ts.map +1 -0
- package/dist/scriptable-pack-node.mjs +1590 -0
- package/dist/scriptable-pack-node.mjs.map +1 -0
- package/dist/scriptable-pack-worker.d.ts +2 -0
- package/dist/scriptable-pack-worker.d.ts.map +1 -0
- package/dist/scriptable-pack-worker.mjs +223 -0
- package/dist/scriptable-pack-worker.mjs.map +1 -0
- package/dist/scriptable-pack.d.ts +525 -0
- package/dist/scriptable-pack.d.ts.map +1 -0
- package/dist/scriptable-pack.mjs +583 -0
- package/dist/scriptable-pack.mjs.map +1 -0
- package/dist/topology.d.ts +10 -0
- package/dist/topology.d.ts.map +1 -0
- package/package.json +129 -0
- package/schema/meta.schema.json +173 -0
- package/schema/pack.schema.json +232 -0
- package/src/__tests__/ai-recovery.unit.test.ts +114 -0
- package/src/__tests__/artifact-path.test.ts +41 -0
- package/src/__tests__/asset-evidence-error-hints.test.ts +46 -0
- package/src/__tests__/asset-evidence-lifecycle.test.ts +104 -0
- package/src/__tests__/asset-evidence-schema.test.ts +86 -0
- package/src/__tests__/atlas-cli-region-mismatch.test.ts +134 -0
- package/src/__tests__/atlas-error-code-owner.test-d.ts +23 -0
- package/src/__tests__/cli-asset-evidence.integration.test.ts +172 -0
- package/src/__tests__/cli.unit.test.ts +448 -0
- package/src/__tests__/evidence-source-inventory.unit.test.ts +37 -0
- package/src/__tests__/guid-collision.unit.test.ts +112 -0
- package/src/__tests__/guid-validator-surface.unit.test.ts +20 -0
- package/src/__tests__/load-asset-config.test.ts +121 -0
- package/src/__tests__/material-cook-dependencies.unit.test.ts +45 -0
- package/src/__tests__/material-cook-receipt.unit.test.ts +91 -0
- package/src/__tests__/material-cook-schema.unit.test.ts +98 -0
- package/src/__tests__/mesh-bin-consumer-surface.unit.test.ts +21 -0
- package/src/__tests__/mesh-bin-contract.unit.test.ts +32 -0
- package/src/__tests__/meta-schema-optional.test.ts +116 -0
- package/src/__tests__/meta-source-overrides.unit.test.ts +71 -0
- package/src/__tests__/native-cooker-registry.test.ts +64 -0
- package/src/__tests__/offline-evidence-artifact-verification-owner.test-d.ts +64 -0
- package/src/__tests__/offline-evidence.integration.test.ts +106 -0
- package/src/__tests__/pack-v2-fixture-migration.test.ts +64 -0
- package/src/__tests__/pack-v2-schema.test.ts +142 -0
- package/src/__tests__/pack.unit.test.ts +2218 -0
- package/src/__tests__/producer-contract-schema.unit.test.ts +92 -0
- package/src/__tests__/resolve-asset-source.test.ts +121 -0
- package/src/__tests__/runtime-projection.unit.test.ts +87 -0
- package/src/__tests__/scanner-inventory.test.ts +72 -0
- package/src/__tests__/scanner-mount-cycle.test.ts +232 -0
- package/src/__tests__/scriptable-pack-cli.integration.test.ts +47 -0
- package/src/__tests__/scriptable-pack-ddc-key.unit.test.ts +63 -0
- package/src/__tests__/scriptable-pack.test-d.ts +131 -0
- package/src/__tests__/scriptable-pack.unit.test.ts +747 -0
- package/src/__tests__/topology-actions.unit.test.ts +94 -0
- package/src/__tests__/topology.unit.test.ts +85 -0
- package/src/artifact-path.ts +69 -0
- package/src/atlas/run-atlas.ts +358 -0
- package/src/atlas/shelf-pack.ts +176 -0
- package/src/atlas/upng-ambient.d.ts +13 -0
- package/src/bridge.ts +2 -0
- package/src/builtin.ts +79 -0
- package/src/cli-asset.ts +590 -0
- package/src/config.ts +36 -0
- package/src/deriveAssetName.ts +42 -0
- package/src/errors.ts +47 -0
- package/src/evidence/material-cook.ts +332 -0
- package/src/evidence/offline-evidence.ts +90 -0
- package/src/evidence/source-inventory.ts +193 -0
- package/src/guid.ts +99 -0
- package/src/index.ts +104 -0
- package/src/material/artifact-writer.ts +26 -0
- package/src/mesh-bin-contract.ts +150 -0
- package/src/native-cooker-registry.ts +163 -0
- package/src/native-cooker.ts +2 -0
- package/src/producer-contract.ts +277 -0
- package/src/resolve-asset-source.ts +93 -0
- package/src/runtime-projection.ts +100 -0
- package/src/scanner.ts +716 -0
- package/src/schema/material-cook.schema.json +79 -0
- package/src/schema-compiled.ts +238 -0
- package/src/scriptable-pack-node.ts +1390 -0
- package/src/scriptable-pack-worker.ts +306 -0
- package/src/scriptable-pack.ts +884 -0
- package/src/topology.ts +156 -0
package/src/guid.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { uuidv7obj } from 'uuidv7';
|
|
2
|
+
import { PackError } from './errors.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 16-byte UUID branded ABI. Prevents assignment from plain Uint8Array or string.
|
|
6
|
+
* Brand field mirrors the declaration in @forgeax/engine-types for cross-package
|
|
7
|
+
* structural compatibility (both use __guidBrand: 'AssetGuid').
|
|
8
|
+
*/
|
|
9
|
+
export type AssetGuid = Uint8Array & { readonly __guidBrand: 'AssetGuid' };
|
|
10
|
+
|
|
11
|
+
/** Minimal Result alias for this module (structurally compatible with ScanResult). */
|
|
12
|
+
export type GuidResult<T, E> =
|
|
13
|
+
| { readonly ok: true; readonly value: T }
|
|
14
|
+
| { readonly ok: false; readonly error: E };
|
|
15
|
+
|
|
16
|
+
function brand(bytes: Uint8Array): AssetGuid {
|
|
17
|
+
return bytes as AssetGuid;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const HEX_BYTE = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
|
|
21
|
+
|
|
22
|
+
function bytesToDashForm(bytes: Uint8Array): string {
|
|
23
|
+
const h = HEX_BYTE;
|
|
24
|
+
// biome-ignore lint/style/noNonNullAssertion: length guaranteed 16
|
|
25
|
+
return `${h[bytes[0]!]}${h[bytes[1]!]}${h[bytes[2]!]}${h[bytes[3]!]}-${h[bytes[4]!]}${h[bytes[5]!]}-${h[bytes[6]!]}${h[bytes[7]!]}-${h[bytes[8]!]}${h[bytes[9]!]}-${h[bytes[10]!]}${h[bytes[11]!]}${h[bytes[12]!]}${h[bytes[13]!]}${h[bytes[14]!]}${h[bytes[15]!]}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
29
|
+
|
|
30
|
+
export function isValidAssetGuidString(value: unknown): value is string {
|
|
31
|
+
return typeof value === 'string' && UUID_RE.test(value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function dashFormToBytes(dashForm: string): Uint8Array {
|
|
35
|
+
const hex = dashForm.replace(/-/g, '');
|
|
36
|
+
const bytes = new Uint8Array(16);
|
|
37
|
+
for (let i = 0; i < 16; i++) {
|
|
38
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
39
|
+
}
|
|
40
|
+
return bytes;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Utilities for the AssetGuid branded 16-byte UUID type.
|
|
45
|
+
* Declare the type via `import type { AssetGuid } from '@forgeax/engine-pack/guid'`
|
|
46
|
+
* or `import type { AssetGuid } from '@forgeax/engine-types'`.
|
|
47
|
+
*/
|
|
48
|
+
export const AssetGuid = {
|
|
49
|
+
/**
|
|
50
|
+
* Parse a 36-char RFC 4122 dash-form UUID string into an AssetGuid.
|
|
51
|
+
* Returns Ok(AssetGuid) on success or Err(PackError) with code 'pack-guid-malformed' on failure.
|
|
52
|
+
* Never throws for expected failures (requirements §4.2 / §14 / charter proposition 4).
|
|
53
|
+
*/
|
|
54
|
+
parse(dashForm: string): GuidResult<AssetGuid, PackError> {
|
|
55
|
+
if (!isValidAssetGuidString(dashForm)) {
|
|
56
|
+
return {
|
|
57
|
+
ok: false,
|
|
58
|
+
error: new PackError({
|
|
59
|
+
code: 'pack-guid-malformed',
|
|
60
|
+
expected: '36-char RFC 4122 dash-form UUID',
|
|
61
|
+
hint: 'use AssetGuid.random() or a UUIDv7 generator; all GUID fields must be 36-char RFC 4122 dash-form',
|
|
62
|
+
detail: {
|
|
63
|
+
raw: dashForm,
|
|
64
|
+
reason: 'expected 36-char RFC 4122 dash-form UUID',
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return { ok: true, value: brand(dashFormToBytes(dashForm)) };
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Format an AssetGuid as a 36-char RFC 4122 lowercase dash-form string.
|
|
74
|
+
*/
|
|
75
|
+
format(guid: AssetGuid): string {
|
|
76
|
+
return bytesToDashForm(guid);
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Test byte-by-byte equality between two AssetGuids.
|
|
81
|
+
*/
|
|
82
|
+
equals(a: AssetGuid, b: AssetGuid): boolean {
|
|
83
|
+
for (let i = 0; i < 16; i++) {
|
|
84
|
+
if (a[i] !== b[i]) return false;
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Mint a new time-ordered UUIDv7 as an AssetGuid.
|
|
91
|
+
* Works in both Node.js and browser environments.
|
|
92
|
+
*/
|
|
93
|
+
random(): AssetGuid {
|
|
94
|
+
const uuid = uuidv7obj();
|
|
95
|
+
const bytes = new Uint8Array(16);
|
|
96
|
+
bytes.set(uuid.bytes);
|
|
97
|
+
return brand(bytes);
|
|
98
|
+
},
|
|
99
|
+
} as const;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// @forgeax/engine-pack
|
|
2
|
+
// Disk schema, GUID tools, scanner, fail-fast error chain, and CLI bin.
|
|
3
|
+
// Entry subpaths: . / schema / guid / errors / bridge / scanner
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
type CookedMaterialRecord,
|
|
7
|
+
collectMaterialCookRefs,
|
|
8
|
+
createMaterialArtifactDigest,
|
|
9
|
+
type MaterialCookArtifact,
|
|
10
|
+
type MaterialCookIdentityExpectation,
|
|
11
|
+
type MaterialCookReceipt,
|
|
12
|
+
type MaterialCookRecordError,
|
|
13
|
+
type MaterialCookRefs,
|
|
14
|
+
projectCookedMaterialRecord,
|
|
15
|
+
serializeCookedMaterialRecord,
|
|
16
|
+
serializeMaterialCookReceipt,
|
|
17
|
+
validateCookedMaterialRecord,
|
|
18
|
+
validateMaterialCookReceipt,
|
|
19
|
+
} from './evidence/material-cook.js';
|
|
20
|
+
export { buildOfflineAssetEvidence, packageVerification } from './evidence/offline-evidence.js';
|
|
21
|
+
export {
|
|
22
|
+
decodeMeshBinHeader,
|
|
23
|
+
MESH_BIN_DIGEST_BYTES,
|
|
24
|
+
MESH_BIN_HEADER_V4_BYTES,
|
|
25
|
+
MESH_BIN_PROJECTION_VERSION,
|
|
26
|
+
MESH_BIN_VERSION,
|
|
27
|
+
type MeshBinContractError,
|
|
28
|
+
type MeshBinHeaderResult,
|
|
29
|
+
type MeshBinHeaderV4,
|
|
30
|
+
writeMeshBinHeader,
|
|
31
|
+
} from './mesh-bin-contract.js';
|
|
32
|
+
export { validateProducerContract, validateProducerOutputs } from './producer-contract.js';
|
|
33
|
+
export {
|
|
34
|
+
type AssetReader,
|
|
35
|
+
isScriptablePackAssetKind,
|
|
36
|
+
projectScriptablePackMeta,
|
|
37
|
+
SCRIPTABLE_PACK_ASSET_KINDS,
|
|
38
|
+
type ScriptablePackAssetDeclaration,
|
|
39
|
+
type ScriptablePackAssetDeclarations,
|
|
40
|
+
type ScriptablePackAssetFor,
|
|
41
|
+
type ScriptablePackAssetKind,
|
|
42
|
+
type ScriptablePackDefinition,
|
|
43
|
+
type ScriptablePackError,
|
|
44
|
+
type ScriptablePackExternalAssets,
|
|
45
|
+
type ScriptablePackMetaJson,
|
|
46
|
+
type ScriptablePackOutputs,
|
|
47
|
+
type ScriptablePackPublicationEnvelope,
|
|
48
|
+
type ScriptablePackReadError,
|
|
49
|
+
validateScriptablePackDefinition,
|
|
50
|
+
} from './scriptable-pack.js';
|
|
51
|
+
export { calculateTopologyDiff, diffTopology } from './topology.js';
|
|
52
|
+
|
|
53
|
+
import { err, ok, type PackV2, type PackV2Error, type Result } from '@forgeax/engine-types';
|
|
54
|
+
|
|
55
|
+
export type { ArtifactPathContext } from './artifact-path.js';
|
|
56
|
+
export { validateArtifactPath } from './artifact-path.js';
|
|
57
|
+
|
|
58
|
+
import { validatePackV2 } from './schema-compiled.js';
|
|
59
|
+
|
|
60
|
+
export type {
|
|
61
|
+
AnimationClip,
|
|
62
|
+
AnimationGraph,
|
|
63
|
+
Asset,
|
|
64
|
+
AudioClipAsset,
|
|
65
|
+
EquirectAsset,
|
|
66
|
+
FontAsset,
|
|
67
|
+
MaterialAsset,
|
|
68
|
+
MeshAsset,
|
|
69
|
+
PackV2,
|
|
70
|
+
PackV2Error,
|
|
71
|
+
ParticleEffectAsset,
|
|
72
|
+
RenderPipelineAsset,
|
|
73
|
+
SamplerAsset,
|
|
74
|
+
SceneAsset,
|
|
75
|
+
SkeletonAsset,
|
|
76
|
+
SkinAsset,
|
|
77
|
+
TextureAsset,
|
|
78
|
+
TilesetAsset,
|
|
79
|
+
VideoAsset,
|
|
80
|
+
} from '@forgeax/engine-types';
|
|
81
|
+
export {
|
|
82
|
+
type MaterialArtifactWriteInput,
|
|
83
|
+
type MaterialArtifactWriteResult,
|
|
84
|
+
writeMaterialArtifact,
|
|
85
|
+
} from './material/artifact-writer.js';
|
|
86
|
+
export {
|
|
87
|
+
projectRuntimePack,
|
|
88
|
+
type RuntimeAssetProjectionInput,
|
|
89
|
+
type RuntimePackProjectionInput,
|
|
90
|
+
} from './runtime-projection.js';
|
|
91
|
+
export { validateMeta, validatePack, validatePackV2 } from './schema-compiled.js';
|
|
92
|
+
|
|
93
|
+
export function parsePackV2(value: unknown): Result<PackV2, PackV2Error> {
|
|
94
|
+
if (!validatePackV2(value)) {
|
|
95
|
+
return err({
|
|
96
|
+
code: 'pack-v2-envelope-invalid',
|
|
97
|
+
expected: 'a Pack v2 envelope with unique asset GUIDs and valid descriptors',
|
|
98
|
+
hint: 'validate the pack against packages/pack/schema/pack.schema.json and re-cook it',
|
|
99
|
+
detail: { observed: 'invalid pack', expected: 'schemaVersion 2.0.0' },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return ok(value);
|
|
104
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createMaterialArtifactDigest } from '../evidence/material-cook.js';
|
|
2
|
+
|
|
3
|
+
export interface MaterialArtifactWriteInput {
|
|
4
|
+
readonly key: string;
|
|
5
|
+
readonly bytes: Uint8Array;
|
|
6
|
+
readonly mediaType?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface MaterialArtifactWriteResult {
|
|
10
|
+
readonly key: string;
|
|
11
|
+
readonly bytes: Uint8Array;
|
|
12
|
+
readonly mediaType: string;
|
|
13
|
+
readonly digest: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Pack owns the immutable cooked material artifact tuple. */
|
|
17
|
+
export function writeMaterialArtifact(
|
|
18
|
+
input: MaterialArtifactWriteInput,
|
|
19
|
+
): MaterialArtifactWriteResult {
|
|
20
|
+
return {
|
|
21
|
+
key: input.key,
|
|
22
|
+
bytes: input.bytes.slice(),
|
|
23
|
+
mediaType: input.mediaType ?? 'application/octet-stream',
|
|
24
|
+
digest: createMaterialArtifactDigest(input.bytes),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/** Canonical mesh binary wire facts shared by build-time and runtime owners. */
|
|
2
|
+
|
|
3
|
+
export const MESH_BIN_VERSION = 4 as const;
|
|
4
|
+
export const MESH_BIN_PROJECTION_VERSION = 1 as const;
|
|
5
|
+
export const MESH_BIN_HEADER_V4_BYTES = 80;
|
|
6
|
+
export const MESH_BIN_DIGEST_BYTES = 32;
|
|
7
|
+
|
|
8
|
+
export interface MeshBinHeaderV4 {
|
|
9
|
+
readonly version: 4;
|
|
10
|
+
readonly projectionVersion: 1;
|
|
11
|
+
readonly mask: number;
|
|
12
|
+
readonly digest: string;
|
|
13
|
+
readonly stride: number;
|
|
14
|
+
readonly vertexCount: number;
|
|
15
|
+
readonly vertexBytes: number;
|
|
16
|
+
readonly indexCount: number;
|
|
17
|
+
readonly indexWidth: 0 | 2 | 4;
|
|
18
|
+
readonly indexBytes: number;
|
|
19
|
+
readonly jsonBytes: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface MeshBinContractError {
|
|
23
|
+
readonly code:
|
|
24
|
+
| 'mesh-bin-header-truncated'
|
|
25
|
+
| 'mesh-bin-version-unsupported'
|
|
26
|
+
| 'mesh-bin-header-invalid';
|
|
27
|
+
readonly subject: 'mesh-bin';
|
|
28
|
+
readonly sourceKey: string;
|
|
29
|
+
readonly expected: string;
|
|
30
|
+
readonly actual: string;
|
|
31
|
+
readonly recovery: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type MeshBinHeaderResult =
|
|
35
|
+
| { readonly ok: true; readonly value: MeshBinHeaderV4 }
|
|
36
|
+
| { readonly ok: false; readonly error: MeshBinContractError };
|
|
37
|
+
|
|
38
|
+
function failure(
|
|
39
|
+
code: MeshBinContractError['code'],
|
|
40
|
+
sourceKey: string,
|
|
41
|
+
expected: string,
|
|
42
|
+
actual: string,
|
|
43
|
+
): MeshBinHeaderResult {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
error: {
|
|
47
|
+
code,
|
|
48
|
+
subject: 'mesh-bin',
|
|
49
|
+
sourceKey,
|
|
50
|
+
expected,
|
|
51
|
+
actual,
|
|
52
|
+
recovery: 're-cook the source with its Meta sidecar through the build-time importer',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function digestBytes(digest: string): Uint8Array {
|
|
58
|
+
const bytes = new Uint8Array(MESH_BIN_DIGEST_BYTES);
|
|
59
|
+
bytes.set(new TextEncoder().encode(digest).subarray(0, MESH_BIN_DIGEST_BYTES));
|
|
60
|
+
return bytes;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function readDigest(bytes: Uint8Array): string {
|
|
64
|
+
return new TextDecoder().decode(bytes).replace(/\0+$/u, '');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function writeMeshBinHeader(header: MeshBinHeaderV4, out: Uint8Array): void {
|
|
68
|
+
if (out.byteLength < MESH_BIN_HEADER_V4_BYTES) {
|
|
69
|
+
throw new RangeError('mesh-bin v4 header output is truncated');
|
|
70
|
+
}
|
|
71
|
+
const view = new DataView(out.buffer, out.byteOffset, out.byteLength);
|
|
72
|
+
view.setUint32(0, header.version, true);
|
|
73
|
+
view.setUint32(4, header.projectionVersion, true);
|
|
74
|
+
view.setUint32(8, header.mask, true);
|
|
75
|
+
view.setUint32(12, header.stride, true);
|
|
76
|
+
view.setUint32(16, header.vertexCount, true);
|
|
77
|
+
view.setUint32(20, header.vertexBytes, true);
|
|
78
|
+
view.setUint32(24, header.indexCount, true);
|
|
79
|
+
view.setUint32(28, header.indexWidth, true);
|
|
80
|
+
view.setUint32(32, header.indexBytes, true);
|
|
81
|
+
view.setUint32(36, header.jsonBytes, true);
|
|
82
|
+
out.set(digestBytes(header.digest), 48);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function decodeMeshBinHeader(
|
|
86
|
+
bytes: Uint8Array,
|
|
87
|
+
sourceKey = '<unknown mesh source>',
|
|
88
|
+
): MeshBinHeaderResult {
|
|
89
|
+
if (bytes.byteLength < MESH_BIN_HEADER_V4_BYTES) {
|
|
90
|
+
return failure(
|
|
91
|
+
'mesh-bin-header-truncated',
|
|
92
|
+
sourceKey,
|
|
93
|
+
`mesh-bin v4 header (${MESH_BIN_HEADER_V4_BYTES} bytes)`,
|
|
94
|
+
`${bytes.byteLength} bytes`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
98
|
+
const version = view.getUint32(0, true);
|
|
99
|
+
if (version !== MESH_BIN_VERSION) {
|
|
100
|
+
return failure('mesh-bin-version-unsupported', sourceKey, 'mesh-bin v4', `version ${version}`);
|
|
101
|
+
}
|
|
102
|
+
const projectionVersion = view.getUint32(4, true);
|
|
103
|
+
const mask = view.getUint32(8, true);
|
|
104
|
+
const stride = view.getUint32(12, true);
|
|
105
|
+
const vertexCount = view.getUint32(16, true);
|
|
106
|
+
const vertexBytes = view.getUint32(20, true);
|
|
107
|
+
const indexCount = view.getUint32(24, true);
|
|
108
|
+
const indexWidth = view.getUint32(28, true);
|
|
109
|
+
const indexBytes = view.getUint32(32, true);
|
|
110
|
+
const jsonBytes = view.getUint32(36, true);
|
|
111
|
+
const digest = readDigest(bytes.subarray(48, 48 + MESH_BIN_DIGEST_BYTES));
|
|
112
|
+
if (
|
|
113
|
+
projectionVersion !== MESH_BIN_PROJECTION_VERSION ||
|
|
114
|
+
mask === 0 ||
|
|
115
|
+
stride === 0 ||
|
|
116
|
+
!Number.isSafeInteger(vertexCount) ||
|
|
117
|
+
!Number.isSafeInteger(vertexBytes) ||
|
|
118
|
+
!Number.isSafeInteger(indexCount) ||
|
|
119
|
+
!Number.isSafeInteger(indexBytes) ||
|
|
120
|
+
!Number.isSafeInteger(jsonBytes) ||
|
|
121
|
+
(indexCount === 0 && indexWidth !== 0) ||
|
|
122
|
+
(indexCount > 0 && indexWidth !== 2 && indexWidth !== 4) ||
|
|
123
|
+
indexBytes !== indexCount * indexWidth ||
|
|
124
|
+
vertexBytes !== vertexCount * stride ||
|
|
125
|
+
digest.length === 0
|
|
126
|
+
) {
|
|
127
|
+
return failure(
|
|
128
|
+
'mesh-bin-header-invalid',
|
|
129
|
+
sourceKey,
|
|
130
|
+
'safe v4 projection, stride, cardinality, and payload byte lengths',
|
|
131
|
+
`projection=${projectionVersion}; mask=${mask}; stride=${stride}; vertexBytes=${vertexBytes}; indexBytes=${indexBytes}; digest=${digest}`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
ok: true,
|
|
136
|
+
value: {
|
|
137
|
+
version: 4,
|
|
138
|
+
projectionVersion: 1,
|
|
139
|
+
mask,
|
|
140
|
+
digest,
|
|
141
|
+
stride,
|
|
142
|
+
vertexCount,
|
|
143
|
+
vertexBytes,
|
|
144
|
+
indexCount,
|
|
145
|
+
indexWidth: indexWidth as 0 | 2 | 4,
|
|
146
|
+
indexBytes,
|
|
147
|
+
jsonBytes,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import type {
|
|
3
|
+
ArtifactDescriptor,
|
|
4
|
+
AssetCodec,
|
|
5
|
+
AssetStageError,
|
|
6
|
+
CookProduct,
|
|
7
|
+
Result,
|
|
8
|
+
} from '@forgeax/engine-types';
|
|
9
|
+
import { err, ok } from '@forgeax/engine-types';
|
|
10
|
+
|
|
11
|
+
export interface NativeCookArtifact {
|
|
12
|
+
readonly mediaType: string;
|
|
13
|
+
readonly assetCodec?: AssetCodec;
|
|
14
|
+
readonly bytes: Uint8Array;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface NativeCookDraft<P = unknown> {
|
|
18
|
+
readonly guid: string;
|
|
19
|
+
readonly payload: P;
|
|
20
|
+
readonly refs: readonly string[];
|
|
21
|
+
readonly artifacts: Readonly<Record<string, NativeCookArtifact>>;
|
|
22
|
+
readonly inputFingerprint: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface NativeCooker<P = unknown, I = unknown> {
|
|
26
|
+
readonly key: string;
|
|
27
|
+
readonly discover?: (input: unknown) => I | Promise<I>;
|
|
28
|
+
cook(input: I): NativeCookDraft<P> | Promise<NativeCookDraft<P>>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function canonicalize(value: unknown): unknown {
|
|
32
|
+
if (value instanceof Uint8Array) return Buffer.from(value).toString('base64');
|
|
33
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
34
|
+
if (typeof value !== 'object' || value === null) return value;
|
|
35
|
+
return Object.fromEntries(
|
|
36
|
+
Object.entries(value)
|
|
37
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
38
|
+
.map(([key, nested]) => [key, canonicalize(nested)]),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function digestDraft<P>(draft: NativeCookDraft<P>): string {
|
|
43
|
+
const bytes = JSON.stringify(
|
|
44
|
+
canonicalize({
|
|
45
|
+
guid: draft.guid,
|
|
46
|
+
payload: draft.payload,
|
|
47
|
+
refs: draft.refs,
|
|
48
|
+
artifacts: draft.artifacts,
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type NativeCookErrorDetail = Extract<
|
|
55
|
+
AssetStageError['detail'],
|
|
56
|
+
{ readonly guid: string; readonly producer: string }
|
|
57
|
+
>;
|
|
58
|
+
|
|
59
|
+
function failure(key: string, detail: NativeCookErrorDetail): Result<never, AssetStageError> {
|
|
60
|
+
return err({
|
|
61
|
+
stage: 'native-cook',
|
|
62
|
+
code: 'native-cook-failed',
|
|
63
|
+
expected: 'a registered native cooker to produce a complete draft',
|
|
64
|
+
hint: 'register the cooker or repair its draft, then rerun recovery',
|
|
65
|
+
detail,
|
|
66
|
+
recovery: { action: `rerun native cooker ${key}`, retryable: true },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Injectable build-time table for native authored Pack producers. */
|
|
71
|
+
export class NativeCookerRegistry {
|
|
72
|
+
private readonly cookers = new Map<string, NativeCooker>();
|
|
73
|
+
|
|
74
|
+
register(cooker: NativeCooker): void {
|
|
75
|
+
if (typeof cooker.key !== 'string' || cooker.key.length === 0) {
|
|
76
|
+
throw new TypeError('NativeCookerRegistry.register: key must be a non-empty string');
|
|
77
|
+
}
|
|
78
|
+
if (typeof cooker.cook !== 'function') {
|
|
79
|
+
throw new TypeError(`NativeCookerRegistry.register: cooker ${cooker.key} must expose cook`);
|
|
80
|
+
}
|
|
81
|
+
this.cookers.set(cooker.key, cooker);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get(key: string): NativeCooker | undefined {
|
|
85
|
+
return this.cookers.get(key);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
registeredCookers(): readonly string[] {
|
|
89
|
+
return [...this.cookers.keys()];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async runDraft<P = unknown, I = unknown>(
|
|
93
|
+
key: string,
|
|
94
|
+
input: I,
|
|
95
|
+
): Promise<Result<NativeCookDraft<P>, AssetStageError>> {
|
|
96
|
+
const cooker = this.cookers.get(key) as NativeCooker<P, I> | undefined;
|
|
97
|
+
if (cooker === undefined) {
|
|
98
|
+
return failure(key, { guid: 'unknown', producer: key });
|
|
99
|
+
}
|
|
100
|
+
let draft: NativeCookDraft<P>;
|
|
101
|
+
try {
|
|
102
|
+
const typedInput = cooker.discover === undefined ? input : await cooker.discover(input);
|
|
103
|
+
draft = await cooker.cook(typedInput);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return failure(key, { guid: 'unknown', producer: String(error) });
|
|
106
|
+
}
|
|
107
|
+
if (
|
|
108
|
+
draft.guid.length === 0 ||
|
|
109
|
+
draft.inputFingerprint.length === 0 ||
|
|
110
|
+
!Array.isArray(draft.refs) ||
|
|
111
|
+
typeof draft.payload !== 'object' ||
|
|
112
|
+
draft.payload === null ||
|
|
113
|
+
typeof draft.artifacts !== 'object' ||
|
|
114
|
+
draft.artifacts === null ||
|
|
115
|
+
Object.entries(draft.artifacts).some(
|
|
116
|
+
([, artifact]) =>
|
|
117
|
+
artifact === null ||
|
|
118
|
+
typeof artifact !== 'object' ||
|
|
119
|
+
typeof artifact.mediaType !== 'string' ||
|
|
120
|
+
artifact.mediaType.length === 0 ||
|
|
121
|
+
!(artifact.bytes instanceof Uint8Array),
|
|
122
|
+
)
|
|
123
|
+
) {
|
|
124
|
+
return failure(key, { guid: draft.guid || 'unknown', producer: key });
|
|
125
|
+
}
|
|
126
|
+
return ok(draft);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async run<P = unknown, I = unknown>(
|
|
130
|
+
key: string,
|
|
131
|
+
input: I,
|
|
132
|
+
): Promise<Result<CookProduct<P>, AssetStageError>> {
|
|
133
|
+
const draftResult = await this.runDraft<P, I>(key, input);
|
|
134
|
+
if (!draftResult.ok) return draftResult;
|
|
135
|
+
const draft = draftResult.value;
|
|
136
|
+
const digest = digestDraft(draft);
|
|
137
|
+
const artifacts: Record<string, ArtifactDescriptor> = {};
|
|
138
|
+
for (const [path, artifact] of Object.entries(draft.artifacts)) {
|
|
139
|
+
artifacts[path] = {
|
|
140
|
+
path,
|
|
141
|
+
mediaType: artifact.mediaType,
|
|
142
|
+
...(artifact.assetCodec === undefined ? {} : { assetCodec: artifact.assetCodec }),
|
|
143
|
+
byteLength: artifact.bytes.byteLength,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const receipt = {
|
|
147
|
+
guid: draft.guid,
|
|
148
|
+
origin: 'authoredPack' as const,
|
|
149
|
+
status: 'succeeded' as const,
|
|
150
|
+
inputFingerprint: draft.inputFingerprint,
|
|
151
|
+
outputDigest: digest,
|
|
152
|
+
};
|
|
153
|
+
const product: CookProduct<P> = {
|
|
154
|
+
guid: draft.guid,
|
|
155
|
+
payload: draft.payload,
|
|
156
|
+
refs: draft.refs,
|
|
157
|
+
artifacts,
|
|
158
|
+
digest,
|
|
159
|
+
receipt,
|
|
160
|
+
};
|
|
161
|
+
return ok(product);
|
|
162
|
+
}
|
|
163
|
+
}
|