@forgeax/engine-pack 0.1.7 → 0.1.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgeax/engine-pack",
3
- "version": "0.1.7",
3
+ "version": "0.1.19",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -108,7 +108,7 @@
108
108
  "ajv": "^8.20.0",
109
109
  "ajv-formats": "^3.0.1",
110
110
  "tsup": "^8.3.5",
111
- "vitest": "4.0.18"
111
+ "vitest": "4.1.11"
112
112
  },
113
113
  "forgeax": {
114
114
  "metrics": {
@@ -136,7 +136,7 @@
136
136
  }
137
137
  },
138
138
  "dependencies": {
139
- "@forgeax/engine-types": "0.1.7",
139
+ "@forgeax/engine-types": "0.1.19",
140
140
  "@noble/hashes": "^2.2.0",
141
141
  "fast-glob": "^3.3.3",
142
142
  "typescript": "^5.8.3",
@@ -25,6 +25,50 @@ describe('material cook dependency closure', () => {
25
25
  });
26
26
  });
27
27
 
28
+ it('collects transmission and thickness texture dependencies', () => {
29
+ expect(
30
+ collectMaterialCookRefs({
31
+ parameters: [
32
+ { name: 'transmissionTexture', type: 'texture' },
33
+ { name: 'thicknessTexture', type: 'texture' },
34
+ ],
35
+ values: {
36
+ transmissionTexture: { texture: 'texture/transmission', sampler: 'sampler/linear' },
37
+ thicknessTexture: { texture: 'texture/thickness', sampler: 'sampler/linear' },
38
+ },
39
+ }),
40
+ ).toEqual({
41
+ parent: [],
42
+ textures: ['texture/thickness', 'texture/transmission'],
43
+ samplers: ['sampler/linear'],
44
+ modules: [],
45
+ });
46
+ });
47
+
48
+ it('keeps authored Standard values with both texture dependency edges', () => {
49
+ const refs = collectMaterialCookRefs({
50
+ passes: [{ name: 'Forward', program: { module: 'forgeax::default-standard-pbr' } }],
51
+ parameters: [
52
+ { name: 'transmissionTexture', type: 'texture' },
53
+ { name: 'thicknessTexture', type: 'texture' },
54
+ ],
55
+ values: {
56
+ transmission: 0.8,
57
+ ior: 1.45,
58
+ thickness: 0.25,
59
+ transmissionTexture: { texture: 'texture/transmission', sampler: 'sampler/linear' },
60
+ thicknessTexture: { texture: 'texture/thickness', sampler: 'sampler/linear' },
61
+ },
62
+ });
63
+
64
+ expect(refs).toEqual({
65
+ parent: [],
66
+ textures: ['texture/thickness', 'texture/transmission'],
67
+ samplers: ['sampler/linear'],
68
+ modules: ['forgeax::default-standard-pbr'],
69
+ });
70
+ });
71
+
28
72
  it('collects string shorthands only from declared texture parameters', () => {
29
73
  expect(
30
74
  collectMaterialCookRefs({
package/src/build.ts CHANGED
@@ -33,6 +33,7 @@ export {
33
33
  type CatalogBuildResult,
34
34
  type CatalogImporterPolicy,
35
35
  type CatalogProducerVisibility,
36
+ catalogSourcePathFor,
36
37
  metaPathForGuid,
37
38
  } from './catalog-builder.js';
38
39
  export { calculateCatalogDelta } from './catalog-delta.js';
@@ -1,4 +1,4 @@
1
- import { relative } from 'node:path';
1
+ import { isAbsolute, relative } from 'node:path';
2
2
  import type { PackIndexEntry, ProviderProvenance } from '@forgeax/engine-types';
3
3
  import {
4
4
  findReservedAssetKindConflict,
@@ -75,12 +75,38 @@ export interface CatalogBuildProjectionOptions {
75
75
  readonly scanOptions?: ScanOptions;
76
76
  readonly importerPolicy: (importer: string) => CatalogImporterPolicy;
77
77
  readonly visibility?: CatalogProducerVisibility;
78
+ /**
79
+ * Project physical source paths into a stable host-owned logical identity.
80
+ * Scanner/declaration maps retain physical paths for I/O; only Catalog rows
81
+ * use this projection so symlink farms cannot change publication identity.
82
+ */
83
+ readonly sourceIdentityFor?: (sourcePath: string) => string;
78
84
  }
79
85
 
80
86
  function sourcePathFor(cwd: string, path: string): string {
81
87
  return relative(cwd, path).replace(/\\/g, '/');
82
88
  }
83
89
 
90
+ /**
91
+ * Project one physical source path into the catalog's browser-safe logical
92
+ * locator. Physical paths remain the scanner/declaration keys; only catalog
93
+ * rows use this projection.
94
+ */
95
+ export function catalogSourcePathFor(
96
+ cwd: string,
97
+ path: string,
98
+ sourceIdentityFor: CatalogBuildProjectionOptions['sourceIdentityFor'],
99
+ ): string {
100
+ const projected = sourceIdentityFor?.(path);
101
+ // A host may intentionally leave paths outside its logical roots untouched.
102
+ // Keep the catalog's existing cwd-relative URL contract for that fallback;
103
+ // an absolute physical path is a valid dependency identity, but not a
104
+ // browser-served catalog locator.
105
+ const catalogPath =
106
+ projected === undefined || isAbsolute(projected) ? sourcePathFor(cwd, path) : projected;
107
+ return catalogPath.replaceAll('\\', '/').replace(/^\.\//, '');
108
+ }
109
+
84
110
  export function metaPathForGuid(
85
111
  declarations: ReadonlyMap<string, CatalogImportMeta>,
86
112
  guid: string,
@@ -115,6 +141,7 @@ function projectMeta(
115
141
  assetPaths: Record<string, string>,
116
142
  importerPolicy: (importer: string) => CatalogImporterPolicy,
117
143
  sourceDeclaration: Extract<ScanSourceDeclaration, { readonly format: 'meta.json' }>,
144
+ sourceIdentityFor: CatalogBuildProjectionOptions['sourceIdentityFor'],
118
145
  ): {
119
146
  readonly entries: PackIndexEntry[];
120
147
  readonly declaration?: CatalogImportMeta;
@@ -136,7 +163,7 @@ function projectMeta(
136
163
  },
137
164
  };
138
165
  }
139
- const sourcePath = sourcePathFor(cwd, resolved.value);
166
+ const sourcePath = catalogSourcePathFor(cwd, resolved.value, sourceIdentityFor);
140
167
  const packageUrl = `${base}/__forgeax-ddc/${meta.subAssets[0]?.guid?.toLowerCase() ?? 'pack'}.pack.json`;
141
168
  const declaration: CatalogImportMeta = {
142
169
  importer: meta.importer,
@@ -184,6 +211,7 @@ function projectSource(
184
211
  visibility: CatalogBuildProjectionOptions['visibility'],
185
212
  sourceDeclaration: ScanSourceDeclaration,
186
213
  declarations: Map<string, CatalogImportMeta>,
214
+ sourceIdentityFor: CatalogBuildProjectionOptions['sourceIdentityFor'],
187
215
  ): { readonly entries: PackIndexEntry[]; readonly error?: CatalogBuildError } {
188
216
  if (sourceDeclaration.format === 'pack.ts') {
189
217
  const meta = { ...sourceDeclaration.meta, source: sourcePathFor(cwd, path) };
@@ -193,7 +221,7 @@ function projectSource(
193
221
  : {
194
222
  entries: projectExternalCatalogEntries(
195
223
  meta,
196
- sourcePathFor(cwd, path),
224
+ catalogSourcePathFor(cwd, path, sourceIdentityFor),
197
225
  `${base}/__forgeax-ddc/${anchorGuid}.pack.json`,
198
226
  meta.subAssets,
199
227
  (output) => deriveAssetName(path, meta.subAssets.length, output.name),
@@ -212,12 +240,20 @@ function projectSource(
212
240
  ) {
213
241
  return { entries: [] };
214
242
  }
215
- const projected = projectMeta(path, cwd, base, assetPaths, importerPolicy, sourceDeclaration);
243
+ const projected = projectMeta(
244
+ path,
245
+ cwd,
246
+ base,
247
+ assetPaths,
248
+ importerPolicy,
249
+ sourceDeclaration,
250
+ sourceIdentityFor,
251
+ );
216
252
  if (projected.declaration !== undefined) declarations.set(path, projected.declaration);
217
253
  return projected;
218
254
  }
219
255
  const parsed = sourceDeclaration.value;
220
- const sourcePath = sourcePathFor(cwd, path);
256
+ const sourcePath = catalogSourcePathFor(cwd, path, sourceIdentityFor);
221
257
  const provenance =
222
258
  parsed.provenance ??
223
259
  ({ provider: 'pack', version: parsed.schemaVersion } satisfies ProviderProvenance);
@@ -297,6 +333,7 @@ export async function buildCatalogProjection(
297
333
  options.visibility,
298
334
  sourceDeclaration,
299
335
  declarations,
336
+ options.sourceIdentityFor,
300
337
  );
301
338
  entries.push(...projected.entries);
302
339
  if (projected.error !== undefined) errors.push(projected.error);
@@ -670,7 +670,10 @@ export async function loadScriptablePack(
670
670
  sourcePath: string,
671
671
  options: LoadScriptablePackOptions = {},
672
672
  ): Promise<Result<Readonly<ScriptablePackDefinition>, ScriptablePackError>> {
673
- const timeoutMs = options.timeoutMs ?? 5_000;
673
+ // Module initialization includes transpiling the trusted TypeScript source
674
+ // closure in a fresh Worker. A cold SDK workspace can legitimately exceed
675
+ // the build execution budget before any authored build() code has run.
676
+ const timeoutMs = options.timeoutMs ?? 15_000;
674
677
  const buildTimeoutMs = options.buildTimeoutMs ?? 5_000;
675
678
  const executor = options.executor ?? new WorkerScriptablePackExecutor();
676
679
  let disposeReason: 'complete' | 'timeout' | 'failure' | undefined;