@forgeax/engine-import 0.1.31 → 0.1.33
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source-package-publication.d.ts","sourceRoot":"","sources":["../src/source-package-publication.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"source-package-publication.d.ts","sourceRoot":"","sources":["../src/source-package-publication.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/engine-import",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"LICENSE"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@forgeax/engine-ddc": "0.1.
|
|
31
|
-
"@forgeax/engine-ecs": "0.1.
|
|
32
|
-
"@forgeax/engine-geometry": "0.1.
|
|
33
|
-
"@forgeax/engine-pack": "0.1.
|
|
34
|
-
"@forgeax/engine-scene": "0.1.
|
|
35
|
-
"@forgeax/engine-shader": "0.1.
|
|
36
|
-
"@forgeax/engine-types": "0.1.
|
|
30
|
+
"@forgeax/engine-ddc": "0.1.33",
|
|
31
|
+
"@forgeax/engine-ecs": "0.1.33",
|
|
32
|
+
"@forgeax/engine-geometry": "0.1.33",
|
|
33
|
+
"@forgeax/engine-pack": "0.1.33",
|
|
34
|
+
"@forgeax/engine-scene": "0.1.33",
|
|
35
|
+
"@forgeax/engine-shader": "0.1.33",
|
|
36
|
+
"@forgeax/engine-types": "0.1.33",
|
|
37
37
|
"@noble/hashes": "^2.2.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { mkdtemp, readdir, readFile, rm } from 'node:fs/promises';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import { DdcEntryStore } from '@forgeax/engine-ddc';
|
|
5
|
+
import { DdcEntryStore, ddcOutputDigest } from '@forgeax/engine-ddc';
|
|
6
6
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
7
7
|
import {
|
|
8
8
|
commitImportPublication,
|
|
@@ -160,6 +160,145 @@ describe('source package publication concurrency', () => {
|
|
|
160
160
|
});
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
+
it('reuses the runtime Pack body when its semantic output is unchanged', async () => {
|
|
164
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-source-publication-'));
|
|
165
|
+
roots.push(root);
|
|
166
|
+
const pack = {
|
|
167
|
+
...(input(root).pack as {
|
|
168
|
+
readonly assets: readonly unknown[];
|
|
169
|
+
readonly schemaVersion: '2.0.0';
|
|
170
|
+
}),
|
|
171
|
+
kind: 'internal-text-package' as const,
|
|
172
|
+
scopeId: 'editor-main',
|
|
173
|
+
generation: 17,
|
|
174
|
+
digest: 'sha256:semantic',
|
|
175
|
+
outputSetDigest: 'sha256:outputs',
|
|
176
|
+
};
|
|
177
|
+
const staged = await stageImportPublication({ ...input(root), pack });
|
|
178
|
+
expect(staged.ok).toBe(true);
|
|
179
|
+
if (!staged.ok) return;
|
|
180
|
+
await expect(commitImportPublication(staged.candidate)).resolves.toMatchObject({ ok: true });
|
|
181
|
+
|
|
182
|
+
const entry = await new DdcEntryStore(root).read(KEY);
|
|
183
|
+
expect(entry?.payload).toEqual({
|
|
184
|
+
schemaVersion: '2.0.0',
|
|
185
|
+
kind: 'internal-text-package',
|
|
186
|
+
assets: (pack as unknown as { readonly assets: unknown }).assets,
|
|
187
|
+
digest: pack.digest,
|
|
188
|
+
outputSetDigest: pack.outputSetDigest,
|
|
189
|
+
});
|
|
190
|
+
expect(entry?.receipt.publicationGeneration).toBe(pack.generation);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('keeps fresh DDC payload bytes independent from publication generation', async () => {
|
|
194
|
+
const firstRoot = await mkdtemp(join(tmpdir(), 'forgeax-source-publication-'));
|
|
195
|
+
const secondRoot = await mkdtemp(join(tmpdir(), 'forgeax-source-publication-'));
|
|
196
|
+
roots.push(firstRoot, secondRoot);
|
|
197
|
+
const basePack = input(firstRoot).pack as Record<string, unknown>;
|
|
198
|
+
const firstPack = {
|
|
199
|
+
...basePack,
|
|
200
|
+
kind: 'internal-text-package' as const,
|
|
201
|
+
scopeId: 'first-scope',
|
|
202
|
+
generation: 1114181571,
|
|
203
|
+
digest: 'sha256:semantic',
|
|
204
|
+
outputSetDigest: 'sha256:outputs',
|
|
205
|
+
};
|
|
206
|
+
const secondPack = { ...firstPack, scopeId: 'second-scope', generation: 2018541951 };
|
|
207
|
+
|
|
208
|
+
const first = await stageImportPublication({ ...input(firstRoot), pack: firstPack });
|
|
209
|
+
const second = await stageImportPublication({ ...input(secondRoot), pack: secondPack });
|
|
210
|
+
expect(first.ok).toBe(true);
|
|
211
|
+
expect(second.ok).toBe(true);
|
|
212
|
+
if (!first.ok || !second.ok) return;
|
|
213
|
+
await expect(commitImportPublication(first.candidate)).resolves.toMatchObject({ ok: true });
|
|
214
|
+
await expect(commitImportPublication(second.candidate)).resolves.toMatchObject({ ok: true });
|
|
215
|
+
|
|
216
|
+
const firstEntry = await new DdcEntryStore(firstRoot).read(KEY);
|
|
217
|
+
const secondEntry = await new DdcEntryStore(secondRoot).read(KEY);
|
|
218
|
+
expect(firstEntry?.payload).toEqual(secondEntry?.payload);
|
|
219
|
+
expect(firstEntry?.payload).not.toHaveProperty('scopeId');
|
|
220
|
+
expect(firstEntry?.payload).not.toHaveProperty('generation');
|
|
221
|
+
expect(firstEntry?.receipt.publicationGeneration).toBe(firstPack.generation);
|
|
222
|
+
expect(secondEntry?.receipt.publicationGeneration).toBe(secondPack.generation);
|
|
223
|
+
expect(firstEntry?.receipt.outputDigest).toBe(secondEntry?.receipt.outputDigest);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('accepts concurrent semantic candidates with different publication generations', async () => {
|
|
227
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-source-publication-'));
|
|
228
|
+
roots.push(root);
|
|
229
|
+
const basePack = input(root).pack as Record<string, unknown>;
|
|
230
|
+
const firstPack = {
|
|
231
|
+
...basePack,
|
|
232
|
+
kind: 'internal-text-package' as const,
|
|
233
|
+
scopeId: 'first-scope',
|
|
234
|
+
generation: 1114181571,
|
|
235
|
+
digest: 'sha256:semantic',
|
|
236
|
+
outputSetDigest: 'sha256:outputs',
|
|
237
|
+
};
|
|
238
|
+
const secondPack = { ...firstPack, scopeId: 'second-scope', generation: 2018541951 };
|
|
239
|
+
const first = await stageImportPublication({ ...input(root), pack: firstPack });
|
|
240
|
+
const second = await stageImportPublication({ ...input(root), pack: secondPack });
|
|
241
|
+
expect(first.ok).toBe(true);
|
|
242
|
+
expect(second.ok).toBe(true);
|
|
243
|
+
if (!first.ok || !second.ok) return;
|
|
244
|
+
|
|
245
|
+
const winner = await commitImportPublication(second.candidate);
|
|
246
|
+
const loser = await commitImportPublication(first.candidate);
|
|
247
|
+
expect(winner).toMatchObject({ ok: true });
|
|
248
|
+
expect(loser).toMatchObject({ ok: true });
|
|
249
|
+
await expect(new DdcEntryStore(root).read(KEY)).resolves.toMatchObject({
|
|
250
|
+
payload: expect.not.objectContaining({
|
|
251
|
+
scopeId: expect.anything(),
|
|
252
|
+
generation: expect.anything(),
|
|
253
|
+
}),
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('reuses a legacy runtime payload when only its publication generation changes', async () => {
|
|
258
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-source-publication-'));
|
|
259
|
+
roots.push(root);
|
|
260
|
+
const oldPack = {
|
|
261
|
+
...(input(root).pack as {
|
|
262
|
+
readonly assets: readonly unknown[];
|
|
263
|
+
readonly schemaVersion: '2.0.0';
|
|
264
|
+
}),
|
|
265
|
+
kind: 'internal-text-package' as const,
|
|
266
|
+
scopeId: 'old-scope',
|
|
267
|
+
generation: 1114181571,
|
|
268
|
+
digest: 'sha256:semantic',
|
|
269
|
+
outputSetDigest: 'sha256:outputs',
|
|
270
|
+
};
|
|
271
|
+
await expect(
|
|
272
|
+
new DdcEntryStore(root).write({
|
|
273
|
+
key: KEY,
|
|
274
|
+
guid: GUID,
|
|
275
|
+
payload: oldPack,
|
|
276
|
+
refs: [],
|
|
277
|
+
artifacts: {},
|
|
278
|
+
receipt: {
|
|
279
|
+
guid: GUID,
|
|
280
|
+
key: KEY,
|
|
281
|
+
producer: 'engine-import/source-package-publication',
|
|
282
|
+
inputFingerprint: KEY,
|
|
283
|
+
outputDigest: ddcOutputDigest({
|
|
284
|
+
guid: GUID,
|
|
285
|
+
payload: oldPack,
|
|
286
|
+
refs: [],
|
|
287
|
+
artifacts: {},
|
|
288
|
+
}),
|
|
289
|
+
},
|
|
290
|
+
}),
|
|
291
|
+
).resolves.toMatchObject({ result: 'published' });
|
|
292
|
+
|
|
293
|
+
const nextPack = { ...oldPack, scopeId: 'new-scope', generation: 2018541951 };
|
|
294
|
+
const second = await stageImportPublication({ ...input(root), pack: nextPack });
|
|
295
|
+
expect(second.ok).toBe(true);
|
|
296
|
+
if (!second.ok) return;
|
|
297
|
+
await expect(commitImportPublication(second.candidate)).resolves.toMatchObject({ ok: true });
|
|
298
|
+
|
|
299
|
+
await expect(new DdcEntryStore(root).read(KEY)).resolves.toMatchObject({ payload: oldPack });
|
|
300
|
+
});
|
|
301
|
+
|
|
163
302
|
it.each([
|
|
164
303
|
{
|
|
165
304
|
label: 'missing body',
|
|
@@ -4,6 +4,7 @@ import { dirname } from 'node:path';
|
|
|
4
4
|
import {
|
|
5
5
|
type DdcArtifact,
|
|
6
6
|
type DdcEntry,
|
|
7
|
+
DdcEntryStore,
|
|
7
8
|
type DdcGenerationEntryCandidate,
|
|
8
9
|
DdcGenerationSession,
|
|
9
10
|
type DdcHead,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
ddcOutputDigest,
|
|
12
13
|
} from '@forgeax/engine-ddc';
|
|
13
14
|
import { canonicalDdcJson } from '@forgeax/engine-ddc/key';
|
|
15
|
+
import { stripRuntimePackLifecycle } from '@forgeax/engine-pack/build';
|
|
14
16
|
import {
|
|
15
17
|
type CatalogEntry,
|
|
16
18
|
err,
|
|
@@ -36,6 +38,11 @@ interface StagedSourcePackageDdc extends DdcGenerationEntryCandidate {
|
|
|
36
38
|
readonly context: SourcePackageErrorContext;
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
interface ImmutablePackSelection {
|
|
42
|
+
readonly payload: unknown;
|
|
43
|
+
readonly publicationGeneration?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
39
46
|
export interface ImportPublicationInput {
|
|
40
47
|
readonly root: string;
|
|
41
48
|
readonly guid: string;
|
|
@@ -138,6 +145,43 @@ async function stageSourcePackageDdc(
|
|
|
138
145
|
}
|
|
139
146
|
}
|
|
140
147
|
|
|
148
|
+
/**
|
|
149
|
+
* DDC identity is semantic Pack content. Fresh entries keep the publication
|
|
150
|
+
* generation in receipt metadata; an existing matching entry reuses its exact
|
|
151
|
+
* body so a generation change remains an idempotent cache hit.
|
|
152
|
+
*/
|
|
153
|
+
async function immutablePackPayload(
|
|
154
|
+
input: ImportPublicationInput,
|
|
155
|
+
): Promise<ImmutablePackSelection> {
|
|
156
|
+
const semantic = stripRuntimePackLifecycle(input.pack);
|
|
157
|
+
const existing = await new DdcEntryStore(input.root).read(input.desiredKey);
|
|
158
|
+
if (
|
|
159
|
+
existing !== null &&
|
|
160
|
+
existing.guid.toLowerCase() === input.guid.toLowerCase() &&
|
|
161
|
+
canonicalDdcJson(stripRuntimePackLifecycle(existing.payload)) === canonicalDdcJson(semantic)
|
|
162
|
+
) {
|
|
163
|
+
return {
|
|
164
|
+
payload: existing.payload,
|
|
165
|
+
...(existing.receipt.publicationGeneration === undefined
|
|
166
|
+
? {}
|
|
167
|
+
: { publicationGeneration: existing.receipt.publicationGeneration }),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const publicationGeneration =
|
|
171
|
+
input.pack !== null &&
|
|
172
|
+
typeof input.pack === 'object' &&
|
|
173
|
+
!Array.isArray(input.pack) &&
|
|
174
|
+
typeof (input.pack as { readonly generation?: unknown }).generation === 'number' &&
|
|
175
|
+
Number.isSafeInteger((input.pack as { readonly generation: number }).generation) &&
|
|
176
|
+
(input.pack as { readonly generation: number }).generation > 0
|
|
177
|
+
? (input.pack as { readonly generation: number }).generation
|
|
178
|
+
: undefined;
|
|
179
|
+
return {
|
|
180
|
+
payload: semantic,
|
|
181
|
+
...(publicationGeneration === undefined ? {} : { publicationGeneration }),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
141
185
|
async function commitSourcePackageDdc(
|
|
142
186
|
candidate: StagedSourcePackageDdc,
|
|
143
187
|
): Promise<Result<DdcHead, SourcePackageError>> {
|
|
@@ -469,12 +513,14 @@ export async function stageImportPublication(
|
|
|
469
513
|
};
|
|
470
514
|
}
|
|
471
515
|
const artifacts = validatedArtifacts.value;
|
|
516
|
+
const selected = await immutablePackPayload(input);
|
|
517
|
+
const { payload } = selected;
|
|
472
518
|
const staged = await stageSourcePackageDdc({
|
|
473
519
|
root: input.root,
|
|
474
520
|
entry: {
|
|
475
521
|
key: input.desiredKey,
|
|
476
522
|
guid: input.guid,
|
|
477
|
-
payload
|
|
523
|
+
payload,
|
|
478
524
|
refs: [],
|
|
479
525
|
artifacts,
|
|
480
526
|
receipt: {
|
|
@@ -482,9 +528,12 @@ export async function stageImportPublication(
|
|
|
482
528
|
key: input.desiredKey,
|
|
483
529
|
producer: 'engine-import/source-package-publication',
|
|
484
530
|
inputFingerprint: input.desiredKey,
|
|
531
|
+
...(selected.publicationGeneration === undefined
|
|
532
|
+
? {}
|
|
533
|
+
: { publicationGeneration: selected.publicationGeneration }),
|
|
485
534
|
outputDigest: ddcOutputDigest({
|
|
486
535
|
guid: input.guid,
|
|
487
|
-
payload
|
|
536
|
+
payload,
|
|
488
537
|
refs: [],
|
|
489
538
|
artifacts,
|
|
490
539
|
}),
|