From 198b8aba66ceab4f05e780ac4e95937a097f79b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 22:26:33 +0000 Subject: [PATCH 1/3] Initial plan From 4a35c80d2e5a405b113b31ffeaea7b7ae00b082e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 22:31:56 +0000 Subject: [PATCH 2/3] perf(vlocity-deploy): cache strict-order dependency status checks Co-authored-by: Codeneos <787686+Codeneos@users.noreply.github.com> --- .../src/__tests__/datapackDeployment.test.ts | 26 ++++++++++++++++++- .../vlocity-deploy/src/datapackDeployment.ts | 25 +++++++++++++++--- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/packages/vlocity-deploy/src/__tests__/datapackDeployment.test.ts b/packages/vlocity-deploy/src/__tests__/datapackDeployment.test.ts index eb872dac..3c628faf 100644 --- a/packages/vlocity-deploy/src/__tests__/datapackDeployment.test.ts +++ b/packages/vlocity-deploy/src/__tests__/datapackDeployment.test.ts @@ -212,6 +212,30 @@ describe('DatapackDeployment', () => { expect(deploySets[0].map(r => r.sourceKey)).toStrictEqual([ 'A/1', 'B/1', 'C/1', 'D/1']); expect(deploySets[1].map(r => r.sourceKey)).toStrictEqual([ 'A/2', 'B/2', 'C/2', 'D/2']); }); + it('with [strictOrder: true] should reuse datapack status lookups while selecting deployable records', () => { + // Arrange + const deployment = container.new(DatapackDeployment, { strictOrder: true }); + const parentB1 = mockDatapackRecord({ sourceKey: 'B/1', datapackKey: 'B' }); + const parentB2 = mockDatapackRecord({ sourceKey: 'B/2', datapackKey: 'B' }); + const childA1 = mockDatapackRecord({ sourceKey: 'A/1', datapackKey: 'A' }); + const childA2 = mockDatapackRecord({ sourceKey: 'A/2', datapackKey: 'A' }); + const childA3 = mockDatapackRecord({ sourceKey: 'A/3', datapackKey: 'A' }); + + childA1.addDependency(parentB1); + childA2.addDependency(parentB1); + childA3.addDependency(parentB2); + parentB1.updateStatus(DeploymentStatus.Deployed, 'ID'); + + deployment.add(parentB1, parentB2, childA1, childA2, childA3); + const getDatapackStatusSpy = jest.spyOn(deployment as any, 'getDatapackStatus'); + + // Act + const records = deployment['getDeployableRecords'](); + + // Assert + expect(records && [...records.values()].map(record => record.sourceKey)).toStrictEqual([ 'B/2' ]); + expect(getDatapackStatusSpy.mock.calls.filter(([datapackKey]) => datapackKey === 'B')).toHaveLength(1); + }); }); describe('hasCircularDependencies', () => { @@ -254,4 +278,4 @@ describe('DatapackDeployment', () => { expect(result).toStrictEqual([ 'A', 'C', 'B', 'A' ]); }); }); -}); \ No newline at end of file +}); diff --git a/packages/vlocity-deploy/src/datapackDeployment.ts b/packages/vlocity-deploy/src/datapackDeployment.ts index b5a9a95d..50c0463c 100644 --- a/packages/vlocity-deploy/src/datapackDeployment.ts +++ b/packages/vlocity-deploy/src/datapackDeployment.ts @@ -416,8 +416,10 @@ export class DatapackDeployment extends AsyncEventEmitter(); + const datapackStatusCache = new Map(); + const circularDependencyCache = new Map(); for (const record of this.records.values()) { - if (record.isPending && record.retryCount == 0 && !this.hasPendingDependencies(record)) { + if (record.isPending && record.retryCount == 0 && !this.hasPendingDependencies(record, datapackStatusCache, circularDependencyCache)) { records.set(record.sourceKey, record); } } @@ -456,7 +458,7 @@ export class DatapackDeployment extends AsyncEventEmitter(), circularDependencyCache = new Map()) : boolean { for(const key of record.getDependencySourceKeys()) { const dependentRecord = this.records.get(key); if (!dependentRecord) { @@ -469,9 +471,24 @@ export class DatapackDeployment extends AsyncEventEmitter { + const status = this.getDatapackStatus(cacheKey); + datapackStatusCache.set(cacheKey, status); + return status; + })(); if (dependencyStatus !== undefined && dependencyStatus < DeploymentStatus.Deployed) { - if (this.isCircularDatapackDependency(record.datapackKey, dependentRecord.datapackKey)) { + const circularDependencyKey = `${record.datapackKey}->${dependentRecord.datapackKey}`; + const isCircularDependency = circularDependencyCache.has(circularDependencyKey) + ? circularDependencyCache.get(circularDependencyKey)! + : (() => { + const result = this.isCircularDatapackDependency(record.datapackKey, dependentRecord.datapackKey); + circularDependencyCache.set(circularDependencyKey, result); + return result; + })(); + if (isCircularDependency) { this.reportWarning(record, `Circular datapack dependency: ${record.datapackKey}->${dependentRecord.datapackKey}->${record.datapackKey}`); continue; } From a40e22804568e05656025c1dc9dbb015f6ac8cad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 22:38:35 +0000 Subject: [PATCH 3/3] test(vlocity-deploy): cover strict-order dependency cache reuse Co-authored-by: Codeneos <787686+Codeneos@users.noreply.github.com> --- .../src/__tests__/datapackDeployment.test.ts | 7 +++++- .../vlocity-deploy/src/datapackDeployment.ts | 24 +++++++------------ 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/vlocity-deploy/src/__tests__/datapackDeployment.test.ts b/packages/vlocity-deploy/src/__tests__/datapackDeployment.test.ts index 3c628faf..d0ef3aea 100644 --- a/packages/vlocity-deploy/src/__tests__/datapackDeployment.test.ts +++ b/packages/vlocity-deploy/src/__tests__/datapackDeployment.test.ts @@ -228,13 +228,18 @@ describe('DatapackDeployment', () => { deployment.add(parentB1, parentB2, childA1, childA2, childA3); const getDatapackStatusSpy = jest.spyOn(deployment as any, 'getDatapackStatus'); + const isCircularDatapackDependencySpy = jest.spyOn(deployment as any, 'isCircularDatapackDependency'); // Act const records = deployment['getDeployableRecords'](); // Assert - expect(records && [...records.values()].map(record => record.sourceKey)).toStrictEqual([ 'B/2' ]); + if (!records) { + throw new Error('Expected deployment records to be returned'); + } + expect([...records.values()].map(record => record.sourceKey)).toStrictEqual([ 'B/2' ]); expect(getDatapackStatusSpy.mock.calls.filter(([datapackKey]) => datapackKey === 'B')).toHaveLength(1); + expect(isCircularDatapackDependencySpy.mock.calls.filter(([fromDatapackKey, toDatapackKey]) => fromDatapackKey === 'A' && toDatapackKey === 'B')).toHaveLength(1); }); }); diff --git a/packages/vlocity-deploy/src/datapackDeployment.ts b/packages/vlocity-deploy/src/datapackDeployment.ts index 50c0463c..cdde9f96 100644 --- a/packages/vlocity-deploy/src/datapackDeployment.ts +++ b/packages/vlocity-deploy/src/datapackDeployment.ts @@ -458,7 +458,7 @@ export class DatapackDeployment extends AsyncEventEmitter(), circularDependencyCache = new Map()) : boolean { + private hasPendingDependencies(record: DatapackDeploymentRecord, datapackStatusCache?: Map, circularDependencyCache?: Map) : boolean { for(const key of record.getDependencySourceKeys()) { const dependentRecord = this.records.get(key); if (!dependentRecord) { @@ -472,22 +472,14 @@ export class DatapackDeployment extends AsyncEventEmitter { - const status = this.getDatapackStatus(cacheKey); - datapackStatusCache.set(cacheKey, status); - return status; - })(); + const dependencyStatus = datapackStatusCache + ? mapGetOrCreate(datapackStatusCache, cacheKey, () => this.getDatapackStatus(cacheKey)) + : this.getDatapackStatus(cacheKey); if (dependencyStatus !== undefined && dependencyStatus < DeploymentStatus.Deployed) { - const circularDependencyKey = `${record.datapackKey}->${dependentRecord.datapackKey}`; - const isCircularDependency = circularDependencyCache.has(circularDependencyKey) - ? circularDependencyCache.get(circularDependencyKey)! - : (() => { - const result = this.isCircularDatapackDependency(record.datapackKey, dependentRecord.datapackKey); - circularDependencyCache.set(circularDependencyKey, result); - return result; - })(); + const circularDependencyKey = `${record.datapackKey}\u0000${dependentRecord.datapackKey}`; + const isCircularDependency = circularDependencyCache + ? mapGetOrCreate(circularDependencyCache, circularDependencyKey, () => this.isCircularDatapackDependency(record.datapackKey, dependentRecord.datapackKey)) + : this.isCircularDatapackDependency(record.datapackKey, dependentRecord.datapackKey); if (isCircularDependency) { this.reportWarning(record, `Circular datapack dependency: ${record.datapackKey}->${dependentRecord.datapackKey}->${record.datapackKey}`); continue;