@forgeax/engine-pack 0.1.3 → 0.1.6
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/README.md +52 -4
- package/dist/.tsbuildinfo +1 -1
- 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__/package-finalizer.contract.test.d.ts +2 -0
- package/dist/__tests__/package-finalizer.contract.test.d.ts.map +1 -0
- package/dist/__tests__/runtime.browser.test.d.ts +2 -0
- package/dist/__tests__/runtime.browser.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-inventory.contract.test.d.ts +2 -0
- package/dist/__tests__/scanner-inventory.contract.test.d.ts.map +1 -0
- package/dist/artifact-path.mjs +53 -0
- package/dist/artifact-path.mjs.map +1 -0
- package/dist/atlas/shelf-pack.d.ts.map +1 -1
- package/dist/build.d.ts +19 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.mjs +10168 -0
- package/dist/build.mjs.map +1 -0
- package/dist/builtin.d.ts +20 -0
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.mjs +141 -0
- package/dist/builtin.mjs.map +1 -0
- package/dist/catalog-builder.d.ts +43 -0
- package/dist/catalog-builder.d.ts.map +1 -0
- package/dist/catalog-delta.d.ts +11 -0
- package/dist/catalog-delta.d.ts.map +1 -0
- package/dist/catalog-projection.d.ts +91 -0
- package/dist/catalog-projection.d.ts.map +1 -0
- package/dist/cli-asset.mjs +231 -484
- package/dist/cli-asset.mjs.map +1 -1
- package/dist/index.mjs +83 -1
- package/dist/index.mjs.map +1 -1
- package/dist/material-cook.d.ts +2 -0
- package/dist/material-cook.d.ts.map +1 -0
- package/dist/material-cook.mjs +193 -0
- package/dist/material-cook.mjs.map +1 -0
- package/dist/mesh-bin-contract.mjs +96 -0
- package/dist/mesh-bin-contract.mjs.map +1 -0
- package/dist/native-cooker-registry.d.ts +19 -0
- package/dist/native-cooker-registry.d.ts.map +1 -1
- package/dist/native-cooker.mjs +37 -0
- package/dist/native-cooker.mjs.map +1 -1
- package/dist/package-finalizer.d.ts +121 -0
- package/dist/package-finalizer.d.ts.map +1 -0
- package/dist/resolve-asset-source.d.ts.map +1 -1
- package/dist/resolve-asset-source.mjs +16 -6
- package/dist/resolve-asset-source.mjs.map +1 -1
- package/dist/runtime-publication.d.ts +44 -0
- package/dist/runtime-publication.d.ts.map +1 -0
- package/dist/runtime.d.ts +8 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.mjs +7261 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/scanner.d.ts +87 -54
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.mjs +258 -454
- package/dist/scanner.mjs.map +1 -1
- package/dist/scriptable-pack-node.d.ts +3 -8
- package/dist/scriptable-pack-node.d.ts.map +1 -1
- package/dist/scriptable-pack-node.mjs +93 -9
- package/dist/scriptable-pack-node.mjs.map +1 -1
- package/dist/scriptable-pack-worker.mjs +1 -0
- package/dist/scriptable-pack-worker.mjs.map +1 -1
- package/dist/scriptable-pack.d.ts +33 -0
- package/dist/scriptable-pack.d.ts.map +1 -1
- package/dist/scriptable-pack.mjs +91 -2
- package/dist/scriptable-pack.mjs.map +1 -1
- package/dist/topology.d.ts +1 -1
- package/dist/topology.d.ts.map +1 -1
- package/package.json +27 -2
- package/src/__tests__/mesh-bin-consumer-surface.unit.test.ts +2 -1
- package/src/__tests__/owner-chain.integration.test.ts +78 -0
- package/src/__tests__/package-finalizer.contract.test.ts +105 -0
- package/src/__tests__/runtime.browser.test.ts +15 -0
- package/src/__tests__/scanner-inventory.contract.test.ts +167 -0
- package/src/__tests__/scanner-inventory.test.ts +16 -12
- package/src/__tests__/scriptable-pack.unit.test.ts +42 -0
- package/src/atlas/shelf-pack.ts +20 -24
- package/src/build.ts +139 -0
- package/src/builtin.ts +38 -0
- package/src/catalog-builder.ts +313 -0
- package/src/catalog-delta.ts +189 -0
- package/src/catalog-projection.ts +344 -0
- package/src/cli-asset.ts +1 -5
- package/src/index.ts +2 -2
- package/src/material-cook.ts +13 -0
- package/src/native-cooker-registry.ts +72 -0
- package/src/package-finalizer.ts +394 -0
- package/src/resolve-asset-source.ts +2 -7
- package/src/runtime-publication.ts +190 -0
- package/src/runtime.ts +23 -0
- package/src/scanner.ts +289 -290
- package/src/scriptable-pack-node.ts +27 -23
- package/src/scriptable-pack-worker.ts +2 -0
- package/src/scriptable-pack.ts +123 -1
- package/src/topology.ts +2 -1
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
type ScriptablePackDefinition,
|
|
23
23
|
type ScriptablePackError,
|
|
24
24
|
type ScriptablePackReadError,
|
|
25
|
+
type ScriptablePackSourceClosureEntry,
|
|
25
26
|
validateScriptablePackDefinition,
|
|
26
27
|
} from './scriptable-pack.js';
|
|
27
28
|
|
|
@@ -76,13 +77,17 @@ export async function readOptionalBuildCache<T>(cache: OptionalBuildCache<T>): P
|
|
|
76
77
|
return { value: await cache.coldCook(), fromCache: false };
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
readonly
|
|
82
|
-
|
|
80
|
+
export interface LoadScriptablePackOptions {
|
|
81
|
+
/** Maximum time allowed for module initialization in the isolated worker. */
|
|
82
|
+
readonly timeoutMs?: number;
|
|
83
|
+
/** Maximum time allowed for one definition.build(reader) in the isolated worker. */
|
|
84
|
+
readonly buildTimeoutMs?: number;
|
|
85
|
+
readonly executor?: ScriptablePackModuleExecutor;
|
|
86
|
+
/** Release the isolated loader after identity projection when build will not be called. */
|
|
87
|
+
readonly metadataOnly?: boolean;
|
|
83
88
|
}
|
|
84
89
|
|
|
85
|
-
async function
|
|
90
|
+
async function resolveRelativeImport(
|
|
86
91
|
sourcePath: string,
|
|
87
92
|
specifier: string,
|
|
88
93
|
): Promise<string | undefined> {
|
|
@@ -111,38 +116,33 @@ async function resolveRelativeSourceImport(
|
|
|
111
116
|
return undefined;
|
|
112
117
|
}
|
|
113
118
|
|
|
114
|
-
/**
|
|
119
|
+
/** Capture one immutable ScriptablePack module closure for inventory and production. */
|
|
115
120
|
export async function inventoryScriptablePackSource(
|
|
116
121
|
sourcePath: string,
|
|
122
|
+
initialSourceText?: string,
|
|
117
123
|
): Promise<readonly ScriptablePackSourceClosureEntry[]> {
|
|
118
|
-
const
|
|
124
|
+
const root = await realpath(sourcePath);
|
|
125
|
+
const pending = [root];
|
|
119
126
|
const seen = new Set<string>();
|
|
120
127
|
const entries: ScriptablePackSourceClosureEntry[] = [];
|
|
121
128
|
while (pending.length > 0) {
|
|
122
129
|
const path = pending.pop();
|
|
123
130
|
if (path === undefined || seen.has(path)) continue;
|
|
124
131
|
seen.add(path);
|
|
125
|
-
const bytes =
|
|
132
|
+
const bytes =
|
|
133
|
+
path === root && initialSourceText !== undefined
|
|
134
|
+
? new TextEncoder().encode(initialSourceText)
|
|
135
|
+
: await readFile(path);
|
|
126
136
|
entries.push({ path, digest: `sha256:${createHash('sha256').update(bytes).digest('hex')}` });
|
|
127
|
-
const
|
|
128
|
-
for (const imported of
|
|
129
|
-
const resolved = await
|
|
137
|
+
const source = new TextDecoder().decode(bytes);
|
|
138
|
+
for (const imported of ts.preProcessFile(source, true, true).importedFiles) {
|
|
139
|
+
const resolved = await resolveRelativeImport(path, imported.fileName);
|
|
130
140
|
if (resolved !== undefined) pending.push(await realpath(resolved));
|
|
131
141
|
}
|
|
132
142
|
}
|
|
133
143
|
return entries.sort((left, right) => left.path.localeCompare(right.path));
|
|
134
144
|
}
|
|
135
145
|
|
|
136
|
-
export interface LoadScriptablePackOptions {
|
|
137
|
-
/** Maximum time allowed for module initialization in the isolated worker. */
|
|
138
|
-
readonly timeoutMs?: number;
|
|
139
|
-
/** Maximum time allowed for one definition.build(reader) in the isolated worker. */
|
|
140
|
-
readonly buildTimeoutMs?: number;
|
|
141
|
-
readonly executor?: ScriptablePackModuleExecutor;
|
|
142
|
-
/** Release the isolated loader after identity projection when build will not be called. */
|
|
143
|
-
readonly metadataOnly?: boolean;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
146
|
interface StructuredFailure {
|
|
147
147
|
readonly name?: unknown;
|
|
148
148
|
readonly code?: unknown;
|
|
@@ -246,6 +246,10 @@ class WorkerScriptablePackExecutor implements ScriptablePackModuleExecutor {
|
|
|
246
246
|
if (message === null || typeof message !== 'object') return;
|
|
247
247
|
const value = message as Record<string, unknown>;
|
|
248
248
|
if (value.kind === 'loaded') {
|
|
249
|
+
// The scan keeps this executor alive for the later no-reopen build
|
|
250
|
+
// route, but an idle source worker must not keep a Vite/Vitest host
|
|
251
|
+
// process alive after its own work has settled.
|
|
252
|
+
worker.unref();
|
|
249
253
|
worker.off('message', onMessage);
|
|
250
254
|
resolveLoad({
|
|
251
255
|
default:
|
|
@@ -966,7 +970,7 @@ export default {
|
|
|
966
970
|
packageId: ${guidExpression(manifest.packageId)},
|
|
967
971
|
${manifest.name === undefined ? '' : `name: ${JSON.stringify(manifest.name)},\n `}assets,
|
|
968
972
|
externalAssets,
|
|
969
|
-
build: () => ok(Object.fromEntries(Object.entries(assets).map(([sourceKey, asset]) => [sourceKey, createOutput(asset.kind)])) as
|
|
973
|
+
build: () => ok(Object.fromEntries(Object.entries(assets).map(([sourceKey, asset]) => [sourceKey, createOutput(asset.kind)])) as Record<string, Asset>),
|
|
970
974
|
} satisfies ScriptablePackDefinition;
|
|
971
975
|
`;
|
|
972
976
|
}
|
|
@@ -1000,7 +1004,7 @@ function definitionFromCanonical(
|
|
|
1000
1004
|
...(manifest.name === undefined ? {} : { name: manifest.name }),
|
|
1001
1005
|
assets,
|
|
1002
1006
|
externalAssets,
|
|
1003
|
-
build: () => ok({} as never),
|
|
1007
|
+
build: () => ok({} as Record<string, never>),
|
|
1004
1008
|
});
|
|
1005
1009
|
}
|
|
1006
1010
|
|
|
@@ -21,6 +21,7 @@ type LoadedDefinition = {
|
|
|
21
21
|
readonly packageId?: unknown;
|
|
22
22
|
readonly name?: unknown;
|
|
23
23
|
readonly assets?: unknown;
|
|
24
|
+
readonly sceneComponents?: unknown;
|
|
24
25
|
readonly externalAssets?: unknown;
|
|
25
26
|
readonly build?: unknown;
|
|
26
27
|
};
|
|
@@ -232,6 +233,7 @@ async function loadDefinition(task: {
|
|
|
232
233
|
packageId: definition.packageId,
|
|
233
234
|
name: definition.name,
|
|
234
235
|
assets: definition.assets,
|
|
236
|
+
sceneComponents: definition.sceneComponents,
|
|
235
237
|
externalAssets: definition.externalAssets,
|
|
236
238
|
},
|
|
237
239
|
});
|
package/src/scriptable-pack.ts
CHANGED
|
@@ -25,6 +25,15 @@ export const SCRIPTABLE_PACK_ASSET_KINDS = [
|
|
|
25
25
|
'particle-effect',
|
|
26
26
|
] as const satisfies readonly ScriptablePackAssetKind[];
|
|
27
27
|
|
|
28
|
+
/** Machine-readable producer/loader floor owned by engine-pack. */
|
|
29
|
+
export const SCRIPTABLE_PACK_CAPABILITY_MANIFEST = {
|
|
30
|
+
assetKinds: SCRIPTABLE_PACK_ASSET_KINDS,
|
|
31
|
+
durablePayload: true,
|
|
32
|
+
refs: true,
|
|
33
|
+
artifacts: true,
|
|
34
|
+
hostCapabilities: ['audio-install', 'video-play', 'particle-execute'] as const,
|
|
35
|
+
} as const;
|
|
36
|
+
|
|
28
37
|
export function isScriptablePackAssetKind(value: string): value is ScriptablePackAssetKind {
|
|
29
38
|
return (SCRIPTABLE_PACK_ASSET_KINDS as readonly string[]).includes(value);
|
|
30
39
|
}
|
|
@@ -43,6 +52,43 @@ export type ScriptablePackAssetDeclarations = Readonly<
|
|
|
43
52
|
|
|
44
53
|
export type ScriptablePackExternalAssets = Readonly<Record<string, AssetGuid>>;
|
|
45
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Component-shaped input accepted by a ScriptablePack scene declaration.
|
|
57
|
+
* Engine component tokens satisfy this shape; plain `{ type }` rows keep the
|
|
58
|
+
* Pack contract independent of ECS and survive the isolated module worker.
|
|
59
|
+
*/
|
|
60
|
+
export interface ScriptablePackSceneComponentInput {
|
|
61
|
+
readonly name: string;
|
|
62
|
+
readonly fields: Readonly<Record<string, string | { readonly type: string }>>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Neutral scene schema projection owned by the Pack definition. */
|
|
66
|
+
export interface ScriptablePackSceneComponent {
|
|
67
|
+
readonly name: string;
|
|
68
|
+
readonly fields: Readonly<Record<string, string>>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function sceneComponentFieldType(value: unknown): string | undefined {
|
|
72
|
+
if (typeof value === 'string') return value;
|
|
73
|
+
if (isRecord(value) && typeof value.type === 'string') return value.type;
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Derive the frozen, ECS-neutral schema used by scene output producers. */
|
|
78
|
+
export function projectScriptablePackSceneComponents(
|
|
79
|
+
components: readonly ScriptablePackSceneComponentInput[] | undefined,
|
|
80
|
+
): readonly ScriptablePackSceneComponent[] {
|
|
81
|
+
return (components ?? []).map((component) => ({
|
|
82
|
+
name: component.name,
|
|
83
|
+
fields: Object.fromEntries(
|
|
84
|
+
Object.entries(component.fields).map(([fieldName, field]) => [
|
|
85
|
+
fieldName,
|
|
86
|
+
sceneComponentFieldType(field) as string,
|
|
87
|
+
]),
|
|
88
|
+
),
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
|
|
46
92
|
/** Pack-facing name for the shared Engine publication envelope. */
|
|
47
93
|
export type ScriptablePackPublicationEnvelope = AssetPublicationEnvelope;
|
|
48
94
|
|
|
@@ -96,6 +142,8 @@ export interface ScriptablePackDefinition<
|
|
|
96
142
|
readonly packageId: AssetGuid;
|
|
97
143
|
readonly name?: string;
|
|
98
144
|
readonly assets: TAssets;
|
|
145
|
+
/** Component tokens/schema rows used by scene ref externalization. */
|
|
146
|
+
readonly sceneComponents?: readonly ScriptablePackSceneComponentInput[];
|
|
99
147
|
readonly externalAssets: TExternal;
|
|
100
148
|
readonly build: (
|
|
101
149
|
assetReader: AssetReader,
|
|
@@ -104,6 +152,11 @@ export interface ScriptablePackDefinition<
|
|
|
104
152
|
| Promise<ScriptablePackBuildResult<TAssets, TError>>;
|
|
105
153
|
}
|
|
106
154
|
|
|
155
|
+
export interface ScriptablePackSourceClosureEntry {
|
|
156
|
+
readonly path: string;
|
|
157
|
+
readonly digest: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
107
160
|
export interface ScriptablePackMetaJson {
|
|
108
161
|
readonly schemaVersion: '1.0.0';
|
|
109
162
|
readonly kind: 'external-asset-package';
|
|
@@ -707,7 +760,7 @@ function invalid(
|
|
|
707
760
|
});
|
|
708
761
|
}
|
|
709
762
|
|
|
710
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
763
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
711
764
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
712
765
|
}
|
|
713
766
|
|
|
@@ -734,11 +787,23 @@ function freezeDefinition(
|
|
|
734
787
|
guid.slice() as AssetGuid,
|
|
735
788
|
]),
|
|
736
789
|
);
|
|
790
|
+
const sceneComponents =
|
|
791
|
+
definition.sceneComponents === undefined
|
|
792
|
+
? undefined
|
|
793
|
+
: Object.freeze(
|
|
794
|
+
projectScriptablePackSceneComponents(definition.sceneComponents).map((component) =>
|
|
795
|
+
Object.freeze({
|
|
796
|
+
...component,
|
|
797
|
+
fields: Object.freeze({ ...component.fields }),
|
|
798
|
+
}),
|
|
799
|
+
),
|
|
800
|
+
);
|
|
737
801
|
return Object.freeze({
|
|
738
802
|
...definition,
|
|
739
803
|
packageId: definition.packageId.slice() as AssetGuid,
|
|
740
804
|
assets: Object.freeze(assets),
|
|
741
805
|
externalAssets: Object.freeze(externalAssets),
|
|
806
|
+
...(sceneComponents === undefined ? {} : { sceneComponents }),
|
|
742
807
|
});
|
|
743
808
|
}
|
|
744
809
|
|
|
@@ -773,6 +838,63 @@ export function validateScriptablePackDefinition(
|
|
|
773
838
|
sourcePath,
|
|
774
839
|
);
|
|
775
840
|
}
|
|
841
|
+
if (value.sceneComponents !== undefined) {
|
|
842
|
+
if (!Array.isArray(value.sceneComponents)) {
|
|
843
|
+
return invalid(
|
|
844
|
+
'$.sceneComponents',
|
|
845
|
+
'an array of component tokens or neutral schema rows when present',
|
|
846
|
+
value.sceneComponents,
|
|
847
|
+
sourcePath,
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
const componentNames = new Set<string>();
|
|
851
|
+
for (const [componentIndex, component] of value.sceneComponents.entries()) {
|
|
852
|
+
if (!isRecord(component)) {
|
|
853
|
+
return invalid(
|
|
854
|
+
`$.sceneComponents[${componentIndex}]`,
|
|
855
|
+
'a component token or neutral schema row',
|
|
856
|
+
component,
|
|
857
|
+
sourcePath,
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
if (typeof component.name !== 'string' || component.name.trim().length === 0) {
|
|
861
|
+
return invalid(
|
|
862
|
+
`$.sceneComponents[${componentIndex}].name`,
|
|
863
|
+
'a non-empty component name',
|
|
864
|
+
component.name,
|
|
865
|
+
sourcePath,
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
if (componentNames.has(component.name)) {
|
|
869
|
+
return invalid(
|
|
870
|
+
`$.sceneComponents[${componentIndex}].name`,
|
|
871
|
+
'component names to be unique within one ScriptablePack',
|
|
872
|
+
component.name,
|
|
873
|
+
sourcePath,
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
componentNames.add(component.name);
|
|
877
|
+
if (!isRecord(component.fields)) {
|
|
878
|
+
return invalid(
|
|
879
|
+
`$.sceneComponents[${componentIndex}].fields`,
|
|
880
|
+
'a field-name to schema-type object',
|
|
881
|
+
component.fields,
|
|
882
|
+
sourcePath,
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
for (const [fieldName, field] of Object.entries(component.fields)) {
|
|
886
|
+
const type = sceneComponentFieldType(field);
|
|
887
|
+
if (fieldName.trim().length === 0 || type === undefined || type.trim().length === 0) {
|
|
888
|
+
return invalid(
|
|
889
|
+
`$.sceneComponents[${componentIndex}].fields`,
|
|
890
|
+
'field names with non-empty string schema types',
|
|
891
|
+
field,
|
|
892
|
+
sourcePath,
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
776
898
|
if (typeof value.build !== 'function') {
|
|
777
899
|
return invalid('$.build', 'a build(assetReader) function', value.build, sourcePath);
|
|
778
900
|
}
|
package/src/topology.ts
CHANGED
|
@@ -152,5 +152,6 @@ export function diffTopology(
|
|
|
152
152
|
return { preserved, added, removed, changedKind, ambiguous };
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
/** Alias named after the producer-facing contract wording. */
|
|
156
155
|
export const calculateTopologyDiff = diffTopology;
|
|
156
|
+
|
|
157
|
+
/** Alias named after the producer-facing contract wording. */
|