@adobe/data 0.9.54 → 0.9.55
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/dist/cache/blob-store.browser.test.d.ts +1 -0
- package/dist/cache/blob-store.browser.test.js +122 -0
- package/dist/cache/blob-store.browser.test.js.map +1 -0
- package/dist/cache/data-cache.browser.test.d.ts +1 -0
- package/dist/cache/data-cache.browser.test.js +41 -0
- package/dist/cache/data-cache.browser.test.js.map +1 -0
- package/dist/cache/expiring-data-cache.browser.test.d.ts +1 -0
- package/dist/cache/expiring-data-cache.browser.test.js +42 -0
- package/dist/cache/expiring-data-cache.browser.test.js.map +1 -0
- package/dist/cache/functions/functions.browser.test.d.ts +1 -0
- package/dist/cache/functions/functions.browser.test.js +72 -0
- package/dist/cache/functions/functions.browser.test.js.map +1 -0
- package/dist/ecs/database/public/create-database-scheduler.browser.test.d.ts +1 -0
- package/dist/ecs/database/public/create-database-scheduler.browser.test.js +67 -0
- package/dist/ecs/database/public/create-database-scheduler.browser.test.js.map +1 -0
- package/dist/ecs/database/transactional-store/coalesce-actions.js +15 -41
- package/dist/ecs/database/transactional-store/coalesce-actions.js.map +1 -1
- package/dist/ecs/database/transactional-store/coalesce-actions.test.js +30 -1
- package/dist/ecs/database/transactional-store/coalesce-actions.test.js.map +1 -1
- package/dist/ecs/persistence-service/create-storage-persistence-service.browser.test.d.ts +1 -0
- package/dist/ecs/persistence-service/create-storage-persistence-service.browser.test.js +52 -0
- package/dist/ecs/persistence-service/create-storage-persistence-service.browser.test.js.map +1 -0
- package/dist/functions/serialization/serialize-to-storage.browser.test.d.ts +1 -0
- package/dist/functions/serialization/serialize-to-storage.browser.test.js +161 -0
- package/dist/functions/serialization/serialize-to-storage.browser.test.js.map +1 -0
- package/dist/internal/typed-array/get-byte-size.d.ts +1 -1
- package/dist/math/aabb/face/all.d.ts +2 -0
- package/dist/math/aabb/face/all.js +10 -0
- package/dist/math/aabb/face/all.js.map +1 -0
- package/dist/math/aabb/face/face.d.ts +4 -0
- package/dist/math/aabb/face/face.js +3 -0
- package/dist/math/aabb/face/face.js.map +1 -0
- package/dist/math/aabb/face/from-position.d.ts +9 -0
- package/dist/math/aabb/face/from-position.js +32 -0
- package/dist/math/aabb/face/from-position.js.map +1 -0
- package/dist/math/aabb/face/from-position.test.d.ts +1 -0
- package/dist/math/aabb/face/from-position.test.js +21 -0
- package/dist/math/aabb/face/from-position.test.js.map +1 -0
- package/dist/math/aabb/face/get-adjacent.d.ts +3 -0
- package/dist/math/aabb/face/get-adjacent.js +24 -0
- package/dist/math/aabb/face/get-adjacent.js.map +1 -0
- package/dist/math/aabb/face/get-adjacent.test.d.ts +1 -0
- package/dist/math/aabb/face/get-adjacent.test.js +27 -0
- package/dist/math/aabb/face/get-adjacent.test.js.map +1 -0
- package/dist/math/aabb/face/get-faces.d.ts +3 -0
- package/dist/math/aabb/face/get-faces.js +11 -0
- package/dist/math/aabb/face/get-faces.js.map +1 -0
- package/dist/math/aabb/face/get-faces.test.d.ts +1 -0
- package/dist/math/aabb/face/get-faces.test.js +16 -0
- package/dist/math/aabb/face/get-faces.test.js.map +1 -0
- package/dist/math/aabb/face/get-name.d.ts +3 -0
- package/dist/math/aabb/face/get-name.js +7 -0
- package/dist/math/aabb/face/get-name.js.map +1 -0
- package/dist/math/aabb/face/get-name.test.d.ts +1 -0
- package/dist/math/aabb/face/get-name.test.js +13 -0
- package/dist/math/aabb/face/get-name.test.js.map +1 -0
- package/dist/math/aabb/face/get-normal.d.ts +4 -0
- package/dist/math/aabb/face/get-normal.js +11 -0
- package/dist/math/aabb/face/get-normal.js.map +1 -0
- package/dist/math/aabb/face/get-normal.test.d.ts +1 -0
- package/dist/math/aabb/face/get-normal.test.js +13 -0
- package/dist/math/aabb/face/get-normal.test.js.map +1 -0
- package/dist/math/aabb/face/get-opposite.d.ts +3 -0
- package/dist/math/aabb/face/get-opposite.js +27 -0
- package/dist/math/aabb/face/get-opposite.js.map +1 -0
- package/dist/math/aabb/face/get-opposite.test.d.ts +1 -0
- package/dist/math/aabb/face/get-opposite.test.js +20 -0
- package/dist/math/aabb/face/get-opposite.test.js.map +1 -0
- package/dist/math/aabb/face/internals.d.ts +6 -0
- package/dist/math/aabb/face/internals.js +34 -0
- package/dist/math/aabb/face/internals.js.map +1 -0
- package/dist/math/aabb/face/is-adjacent.d.ts +3 -0
- package/dist/math/aabb/face/is-adjacent.js +7 -0
- package/dist/math/aabb/face/is-adjacent.js.map +1 -0
- package/dist/math/aabb/face/is-adjacent.test.d.ts +1 -0
- package/dist/math/aabb/face/is-adjacent.test.js +15 -0
- package/dist/math/aabb/face/is-adjacent.test.js.map +1 -0
- package/dist/math/aabb/face/is-opposite.d.ts +3 -0
- package/dist/math/aabb/face/is-opposite.js +7 -0
- package/dist/math/aabb/face/is-opposite.js.map +1 -0
- package/dist/math/aabb/face/is-opposite.test.d.ts +1 -0
- package/dist/math/aabb/face/is-opposite.test.js +15 -0
- package/dist/math/aabb/face/is-opposite.test.js.map +1 -0
- package/dist/math/aabb/face/neg-x.d.ts +2 -0
- package/dist/math/aabb/face/neg-x.js +4 -0
- package/dist/math/aabb/face/neg-x.js.map +1 -0
- package/dist/math/aabb/face/neg-y.d.ts +2 -0
- package/dist/math/aabb/face/neg-y.js +4 -0
- package/dist/math/aabb/face/neg-y.js.map +1 -0
- package/dist/math/aabb/face/neg-z.d.ts +2 -0
- package/dist/math/aabb/face/neg-z.js +4 -0
- package/dist/math/aabb/face/neg-z.js.map +1 -0
- package/dist/math/aabb/face/none.d.ts +2 -0
- package/dist/math/aabb/face/none.js +4 -0
- package/dist/math/aabb/face/none.js.map +1 -0
- package/dist/math/aabb/face/pos-x.d.ts +2 -0
- package/dist/math/aabb/face/pos-x.js +4 -0
- package/dist/math/aabb/face/pos-x.js.map +1 -0
- package/dist/math/aabb/face/pos-y.d.ts +2 -0
- package/dist/math/aabb/face/pos-y.js +4 -0
- package/dist/math/aabb/face/pos-y.js.map +1 -0
- package/dist/math/aabb/face/pos-z.d.ts +2 -0
- package/dist/math/aabb/face/pos-z.js +4 -0
- package/dist/math/aabb/face/pos-z.js.map +1 -0
- package/dist/math/aabb/face/public.d.ts +16 -1
- package/dist/math/aabb/face/public.js +16 -1
- package/dist/math/aabb/face/public.js.map +1 -1
- package/dist/math/aabb/public.d.ts +1 -1
- package/dist/math/aabb/public.js +1 -1
- package/dist/math/aabb/public.js.map +1 -1
- package/dist/service/agentic-service/action.d.ts +8 -4
- package/dist/service/agentic-service/create.d.ts +5 -1
- package/dist/service/agentic-service/create.js +1 -1
- package/dist/service/agentic-service/create.js.map +1 -1
- package/dist/service/agentic-service/create.test.js +1 -1
- package/dist/service/agentic-service/create.test.js.map +1 -1
- package/dist/service/agentic-service/create.type-test.js +23 -0
- package/dist/service/agentic-service/create.type-test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/references/data-lit/package.json +1 -1
- package/references/data-lit-tictactoe/package.json +1 -1
- package/references/data-react/package.json +1 -1
- package/references/data-react-hello/package.json +1 -1
- package/references/data-react-pixie/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
3
|
+
import { createBlobStore } from "./blob-store.js";
|
|
4
|
+
describe("blobStore", () => {
|
|
5
|
+
// Mock URL.createObjectURL and URL.revokeObjectURL
|
|
6
|
+
const mockCreateObjectURL = vi.fn((blob) => `blob:${Math.random()}`);
|
|
7
|
+
const mockRevokeObjectURL = vi.fn();
|
|
8
|
+
let testBlobStore;
|
|
9
|
+
let createdUrl;
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
// Setup URL mock functions
|
|
12
|
+
const originalCreateObjectURL = URL.createObjectURL;
|
|
13
|
+
const originalRevokeObjectURL = URL.revokeObjectURL;
|
|
14
|
+
// Ensure we create a non-http URL
|
|
15
|
+
mockCreateObjectURL.mockImplementation((blob) => {
|
|
16
|
+
createdUrl = `blob:${Math.random()}`;
|
|
17
|
+
return createdUrl;
|
|
18
|
+
});
|
|
19
|
+
URL.createObjectURL = mockCreateObjectURL;
|
|
20
|
+
URL.revokeObjectURL = mockRevokeObjectURL;
|
|
21
|
+
// Clear mock call history
|
|
22
|
+
mockCreateObjectURL.mockClear();
|
|
23
|
+
mockRevokeObjectURL.mockClear();
|
|
24
|
+
testBlobStore = await createBlobStore();
|
|
25
|
+
return () => {
|
|
26
|
+
URL.createObjectURL = originalCreateObjectURL;
|
|
27
|
+
URL.revokeObjectURL = originalRevokeObjectURL;
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
// Clean up any remaining borrowed URLs
|
|
32
|
+
mockCreateObjectURL.mockReset();
|
|
33
|
+
mockRevokeObjectURL.mockReset();
|
|
34
|
+
});
|
|
35
|
+
describe("URL reference counting", () => {
|
|
36
|
+
it("should reuse the same URL for multiple borrows of the same blob", async () => {
|
|
37
|
+
// Create a test blob
|
|
38
|
+
const testBlob = new Blob(["test"], { type: "text/plain" });
|
|
39
|
+
const blobRef = await testBlobStore.getRef(testBlob);
|
|
40
|
+
// Borrow the URL multiple times
|
|
41
|
+
const url1 = await testBlobStore.borrowUrl(blobRef);
|
|
42
|
+
const url2 = await testBlobStore.borrowUrl(blobRef);
|
|
43
|
+
const url3 = await testBlobStore.borrowUrl(blobRef);
|
|
44
|
+
expect(url1).toBeTruthy();
|
|
45
|
+
expect(url1).toBe(url2);
|
|
46
|
+
expect(url2).toBe(url3);
|
|
47
|
+
expect(url1).toMatch(/^blob:/); // Verify it's a blob URL
|
|
48
|
+
// createObjectURL should only be called once
|
|
49
|
+
expect(mockCreateObjectURL).toHaveBeenCalledTimes(1);
|
|
50
|
+
// Clean up
|
|
51
|
+
testBlobStore.returnUrl(url1);
|
|
52
|
+
testBlobStore.returnUrl(url2);
|
|
53
|
+
testBlobStore.returnUrl(url3);
|
|
54
|
+
});
|
|
55
|
+
it("should only revoke URL when all references are returned", async () => {
|
|
56
|
+
const testBlob = new Blob(["test"], { type: "text/plain" });
|
|
57
|
+
const blobRef = await testBlobStore.getRef(testBlob);
|
|
58
|
+
// Borrow the URL three times
|
|
59
|
+
const url1 = await testBlobStore.borrowUrl(blobRef);
|
|
60
|
+
const url2 = await testBlobStore.borrowUrl(blobRef);
|
|
61
|
+
const url3 = await testBlobStore.borrowUrl(blobRef);
|
|
62
|
+
expect(url1).toMatch(/^blob:/); // Verify it's a blob URL
|
|
63
|
+
// Return URLs one by one
|
|
64
|
+
testBlobStore.returnUrl(url1);
|
|
65
|
+
expect(mockRevokeObjectURL).not.toHaveBeenCalled();
|
|
66
|
+
testBlobStore.returnUrl(url2);
|
|
67
|
+
expect(mockRevokeObjectURL).not.toHaveBeenCalled();
|
|
68
|
+
testBlobStore.returnUrl(url3);
|
|
69
|
+
expect(mockRevokeObjectURL).toHaveBeenCalledTimes(1);
|
|
70
|
+
expect(mockRevokeObjectURL).toHaveBeenCalledWith(createdUrl);
|
|
71
|
+
});
|
|
72
|
+
it("should handle remote URLs correctly", async () => {
|
|
73
|
+
const remoteRef = testBlobStore.createRemoteBlobRef("http://example.com/image.jpg");
|
|
74
|
+
// Borrow remote URL multiple times
|
|
75
|
+
const url1 = await testBlobStore.borrowUrl(remoteRef);
|
|
76
|
+
const url2 = await testBlobStore.borrowUrl(remoteRef);
|
|
77
|
+
expect(url1).toBe("http://example.com/image.jpg");
|
|
78
|
+
expect(url2).toBe("http://example.com/image.jpg");
|
|
79
|
+
expect(mockCreateObjectURL).not.toHaveBeenCalled();
|
|
80
|
+
// Return URLs
|
|
81
|
+
testBlobStore.returnUrl(url1);
|
|
82
|
+
testBlobStore.returnUrl(url2);
|
|
83
|
+
// Should not revoke remote URLs
|
|
84
|
+
expect(mockRevokeObjectURL).not.toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
it("should handle null inputs gracefully", async () => {
|
|
87
|
+
const nullUrl = await testBlobStore.borrowUrl(null);
|
|
88
|
+
expect(nullUrl).toBeNull();
|
|
89
|
+
// Should not throw when returning null
|
|
90
|
+
expect(() => testBlobStore.returnUrl(null)).not.toThrow();
|
|
91
|
+
});
|
|
92
|
+
it('demonstrates race condition in borrowUrl', async () => {
|
|
93
|
+
const store = testBlobStore;
|
|
94
|
+
const testBlob = new Blob(['test data'], { type: 'text/plain' });
|
|
95
|
+
const blobRef = await store.getRef(testBlob);
|
|
96
|
+
// Create an artificially delayed getBlob to simulate network latency
|
|
97
|
+
const originalGetBlob = store.getBlob;
|
|
98
|
+
store.getBlob = async (ref) => {
|
|
99
|
+
await new Promise(resolve => setTimeout(resolve, 50)); // Add delay
|
|
100
|
+
return originalGetBlob(ref);
|
|
101
|
+
};
|
|
102
|
+
// Make two parallel borrowUrl calls
|
|
103
|
+
const [url1, url2] = await Promise.all([
|
|
104
|
+
store.borrowUrl(blobRef),
|
|
105
|
+
store.borrowUrl(blobRef)
|
|
106
|
+
]);
|
|
107
|
+
expect(url1).toBe(url2); // Same URL should be returned
|
|
108
|
+
// Return one of the URLs
|
|
109
|
+
store.returnUrl(url1);
|
|
110
|
+
// Verify that the borrow count is still 1 since we borrowed twice and returned once
|
|
111
|
+
expect(store._testGetBorrowCount(blobRef)).toBe(1);
|
|
112
|
+
// Return the second URL
|
|
113
|
+
store.returnUrl(url2);
|
|
114
|
+
// Verify the borrow count is now 0
|
|
115
|
+
expect(store._testGetBorrowCount(blobRef)).toBe(0);
|
|
116
|
+
// Verify the URL was revoked
|
|
117
|
+
expect(mockRevokeObjectURL).toHaveBeenCalledTimes(1);
|
|
118
|
+
expect(mockRevokeObjectURL).toHaveBeenCalledWith(url1);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
//# sourceMappingURL=blob-store.browser.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blob-store.browser.test.js","sourceRoot":"","sources":["../../src/cache/blob-store.browser.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import { dataCache, getDataCache } from "./data-cache.js";
|
|
3
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
4
|
+
describe("DataCache", () => {
|
|
5
|
+
beforeAll(() => { });
|
|
6
|
+
it("dataCache is not a top-level await", () => {
|
|
7
|
+
expect(typeof dataCache.then === "undefined");
|
|
8
|
+
});
|
|
9
|
+
it("dataCache is lazily initialized and supports put/match/delete directly", async () => {
|
|
10
|
+
const key = { test: "lazy-init", n: Date.now() };
|
|
11
|
+
const value = { hello: "world" };
|
|
12
|
+
await dataCache.put(key, value);
|
|
13
|
+
expect(await dataCache.match(key)).toEqual(value);
|
|
14
|
+
await dataCache.delete(key);
|
|
15
|
+
expect(await dataCache.match(key)).toBeUndefined();
|
|
16
|
+
});
|
|
17
|
+
it("should allow storing and retrieving values in namespaced/versioned caches", async () => {
|
|
18
|
+
// get a namespaced cache
|
|
19
|
+
const cache = await getDataCache("test:v1");
|
|
20
|
+
// create some keys and values to store
|
|
21
|
+
const key = { foo: 1, bar: [{ baz: 12 }] };
|
|
22
|
+
const valueIn = { alpha: "bet", bar: ["a", "b"] };
|
|
23
|
+
// store a key/value pair
|
|
24
|
+
await cache.put(key, valueIn);
|
|
25
|
+
// retrieve the value
|
|
26
|
+
const valueOut = await cache.match(key);
|
|
27
|
+
// output is NOT identical
|
|
28
|
+
expect(valueOut !== valueIn).toBe(true);
|
|
29
|
+
// output IS structurally equal
|
|
30
|
+
expect(valueOut).toEqual(valueIn);
|
|
31
|
+
// output from structurally equivalent key is the same
|
|
32
|
+
expect(await cache.match(JSON.parse(JSON.stringify(key)))).toEqual(valueIn);
|
|
33
|
+
// output from equivalent cache (constructed with same name/version) is the same.
|
|
34
|
+
expect(await (await getDataCache("test:v1")).match(key)).toEqual(valueIn);
|
|
35
|
+
// output from different named cache is undefined
|
|
36
|
+
expect(await (await getDataCache("test2:v1")).match(key)).toEqual(undefined);
|
|
37
|
+
// output from different cache version is undefined
|
|
38
|
+
expect(await (await getDataCache("test:v2")).match(key)).toEqual(undefined);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=data-cache.browser.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-cache.browser.test.js","sourceRoot":"","sources":["../../src/cache/data-cache.browser.test.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEzD,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,SAAS,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACpB,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,OAAQ,SAAiB,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wEAAwE,EAAE,KAAK,IAAI,EAAE;QACtF,MAAM,GAAG,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QAEjC,MAAM,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAChC,MAAM,CAAC,MAAM,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAElD,MAAM,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,CAAC,MAAM,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2EAA2E,EAAE,KAAK,IAAI,EAAE;QACzF,0BAA0B;QAC1B,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC,CAAC;QAE5C,wCAAwC;QACxC,MAAM,GAAG,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;QAC3C,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;QAElD,0BAA0B;QAC1B,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAE9B,sBAAsB;QACtB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAExC,2BAA2B;QAC3B,MAAM,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAExC,gCAAgC;QAChC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAElC,uDAAuD;QACvD,MAAM,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAE5E,kFAAkF;QAClF,MAAM,CAAC,MAAM,CAAC,MAAM,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAE1E,kDAAkD;QAClD,MAAM,CAAC,MAAM,CAAC,MAAM,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAC/D,SAAS,CACV,CAAC;QAEF,oDAAoD;QACpD,MAAM,CAAC,MAAM,CAAC,MAAM,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import { createExpiringDataCache } from "./expiring-data-cache.js"; // Adjust the import path as necessary
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { createDataCache } from "./data-cache.js";
|
|
5
|
+
import { getManagedPersistentCache } from "./get-persistent-cache.js";
|
|
6
|
+
describe("ExpiringDataCache", () => {
|
|
7
|
+
it("should store and retrieve an item before it expires", async () => {
|
|
8
|
+
const time = Date.now();
|
|
9
|
+
let timePassed = 0;
|
|
10
|
+
const getTime = () => time + timePassed;
|
|
11
|
+
const cache = createExpiringDataCache(createDataCache(await getManagedPersistentCache("expiring-data-cache-test", {
|
|
12
|
+
maximumMemoryEntries: 10,
|
|
13
|
+
maximumStorageEntries: 100,
|
|
14
|
+
})), getTime);
|
|
15
|
+
cache.put("key1", "value1", { maximumDuration: 1000 }); // Expires in 1000ms
|
|
16
|
+
expect(await cache.match("key1")).toBe("value1");
|
|
17
|
+
timePassed = 500;
|
|
18
|
+
expect(await cache.match("key1")).toBe("value1");
|
|
19
|
+
timePassed = 999;
|
|
20
|
+
expect(await cache.match("key1")).toBe("value1");
|
|
21
|
+
timePassed = 1000;
|
|
22
|
+
expect(await cache.match("key1")).toBeUndefined();
|
|
23
|
+
timePassed = 1001;
|
|
24
|
+
expect(await cache.match("key1")).toBeUndefined();
|
|
25
|
+
timePassed = 2000;
|
|
26
|
+
expect(await cache.match("key1")).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
it("should immediately expire items when maximumDuration is 0", async () => {
|
|
29
|
+
const time = Date.now();
|
|
30
|
+
let timePassed = 0;
|
|
31
|
+
const getTime = () => time + timePassed;
|
|
32
|
+
const cache = createExpiringDataCache(createDataCache(await getManagedPersistentCache("expiring-data-cache-test", {
|
|
33
|
+
maximumMemoryEntries: 10,
|
|
34
|
+
maximumStorageEntries: 100,
|
|
35
|
+
})), getTime);
|
|
36
|
+
cache.put("key1", "value1", { maximumDuration: 0 }); // Expires immediately
|
|
37
|
+
expect(await cache.match("key1")).toBeUndefined();
|
|
38
|
+
timePassed = 1000;
|
|
39
|
+
expect(await cache.match("key1")).toBeUndefined();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
//# sourceMappingURL=expiring-data-cache.browser.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expiring-data-cache.browser.test.js","sourceRoot":"","sources":["../../src/cache/expiring-data-cache.browser.test.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC,CAAC,sCAAsC;AAC1G,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACxB,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,GAAG,UAAU,CAAC;QACxC,MAAM,KAAK,GAAG,uBAAuB,CACnC,eAAe,CACb,MAAM,yBAAyB,CAAC,0BAA0B,EAAE;YAC1D,oBAAoB,EAAE,EAAE;YACxB,qBAAqB,EAAE,GAAG;SAC3B,CAAC,CACH,EACD,OAAO,CACR,CAAC;QAEF,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,oBAAoB;QAC5E,MAAM,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEjD,UAAU,GAAG,GAAG,CAAC;QACjB,MAAM,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEjD,UAAU,GAAG,GAAG,CAAC;QACjB,MAAM,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEjD,UAAU,GAAG,IAAI,CAAC;QAClB,MAAM,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAElD,UAAU,GAAG,IAAI,CAAC;QAClB,MAAM,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAElD,UAAU,GAAG,IAAI,CAAC;QAClB,MAAM,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACxB,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,GAAG,UAAU,CAAC;QACxC,MAAM,KAAK,GAAG,uBAAuB,CACnC,eAAe,CACb,MAAM,yBAAyB,CAAC,0BAA0B,EAAE;YAC1D,oBAAoB,EAAE,EAAE;YACxB,qBAAqB,EAAE,GAAG;SAC3B,CAAC,CACH,EACD,OAAO,CACR,CAAC;QAEF,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,sBAAsB;QAC3E,MAAM,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAElD,UAAU,GAAG,IAAI,CAAC;QAClB,MAAM,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import { memoize } from "./memoize.js";
|
|
3
|
+
import { preventParallelExecution } from "./prevent-parallel-execution.js";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
describe("Function", () => {
|
|
6
|
+
describe("prevent parallel execution", () => {
|
|
7
|
+
it("should block parallel execution and return same promise", async () => {
|
|
8
|
+
let addCallCount = 0;
|
|
9
|
+
async function add(a, b) {
|
|
10
|
+
addCallCount++;
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
setTimeout(() => {
|
|
13
|
+
resolve(a + b);
|
|
14
|
+
}, 100);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
async function testAddBlocksParallelExecution(addFn) {
|
|
18
|
+
addCallCount = 0;
|
|
19
|
+
const promises = [addFn(1, 2), addFn(1, 2), addFn(1, 2)];
|
|
20
|
+
const results = await Promise.all(promises);
|
|
21
|
+
expect(results).toEqual([3, 3, 3]);
|
|
22
|
+
}
|
|
23
|
+
const addFn = preventParallelExecution(add);
|
|
24
|
+
await testAddBlocksParallelExecution(addFn);
|
|
25
|
+
// after all have completed... then another can execute
|
|
26
|
+
addCallCount = 0;
|
|
27
|
+
expect(await addFn(1, 2)).toEqual(3);
|
|
28
|
+
expect(addCallCount).toEqual(1);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe("memoize", () => {
|
|
32
|
+
it("should block parallel execution and return same promise", async () => {
|
|
33
|
+
let addCallCount = 0;
|
|
34
|
+
async function add(a, b) {
|
|
35
|
+
addCallCount++;
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
setTimeout(() => {
|
|
38
|
+
resolve(a + b);
|
|
39
|
+
}, 100);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async function testAddBlocksParallelExecution(addFn) {
|
|
43
|
+
addCallCount = 0;
|
|
44
|
+
const promises = [addFn(1, 2), addFn(1, 2), addFn(1, 2)];
|
|
45
|
+
const results = await Promise.all(promises);
|
|
46
|
+
expect(results).toEqual([3, 3, 3]);
|
|
47
|
+
}
|
|
48
|
+
// now memoize the addFn and verify that it is cached.
|
|
49
|
+
const addFn = memoize("addFn-v1", add);
|
|
50
|
+
await testAddBlocksParallelExecution(addFn);
|
|
51
|
+
// after all have completed... then another can execute
|
|
52
|
+
addCallCount = 0;
|
|
53
|
+
expect(await addFn(1, 2)).toEqual(3);
|
|
54
|
+
// with memoize, the count should be 0 because we get a cached result.
|
|
55
|
+
expect(addCallCount).toEqual(0);
|
|
56
|
+
});
|
|
57
|
+
it("should reuse cached result for repeated object references", () => {
|
|
58
|
+
let callCount = 0;
|
|
59
|
+
const multiply = memoize(({ value }) => {
|
|
60
|
+
callCount++;
|
|
61
|
+
return value * 2;
|
|
62
|
+
});
|
|
63
|
+
const input = { value: 2 };
|
|
64
|
+
expect(multiply(input)).toEqual(4);
|
|
65
|
+
expect(multiply(input)).toEqual(4);
|
|
66
|
+
expect(callCount).toEqual(1);
|
|
67
|
+
expect(multiply({ value: 2 })).toEqual(4);
|
|
68
|
+
expect(callCount).toEqual(2);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=functions.browser.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"functions.browser.test.js","sourceRoot":"","sources":["../../../src/cache/functions/functions.browser.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
3
|
+
import { Database } from "../database.js";
|
|
4
|
+
import { scheduler } from "../../plugins/scheduler/scheduler.js";
|
|
5
|
+
describe("createDatabase with scheduler plugin extension", () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.useFakeTimers();
|
|
8
|
+
});
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
vi.restoreAllMocks();
|
|
11
|
+
});
|
|
12
|
+
it("should have all systems in functions when scheduler executes", async () => {
|
|
13
|
+
// This test reproduces the bug: when a plugin extends scheduler,
|
|
14
|
+
// the scheduler's executeFrame is called immediately during system creation,
|
|
15
|
+
// but db.system.functions is still empty at that point because it's only
|
|
16
|
+
// populated after all systems are created. This causes the error:
|
|
17
|
+
// "db.system.functions[name] is not a function"
|
|
18
|
+
const basePlugin = scheduler;
|
|
19
|
+
const extendedPlugin = Database.Plugin.create({
|
|
20
|
+
extends: basePlugin,
|
|
21
|
+
transactions: {
|
|
22
|
+
stopScheduler(store) {
|
|
23
|
+
store.resources.schedulerState = "disposed";
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
systems: {
|
|
27
|
+
testSystem: {
|
|
28
|
+
create: (db) => () => {
|
|
29
|
+
// Test system
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
// The bug: Database.create should not throw even though scheduler
|
|
35
|
+
// executeFrame is called during system initialization
|
|
36
|
+
const db = Database.create(extendedPlugin);
|
|
37
|
+
// Verify all systems are in functions (this should pass after fix)
|
|
38
|
+
const allSystemNames = new Set();
|
|
39
|
+
for (const tier of db.system.order) {
|
|
40
|
+
for (const name of tier) {
|
|
41
|
+
allSystemNames.add(name);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Check that all systems in order exist in functions
|
|
45
|
+
for (const name of allSystemNames) {
|
|
46
|
+
expect(db.system.functions[name]).toBeDefined();
|
|
47
|
+
expect(typeof db.system.functions[name]).toBe("function");
|
|
48
|
+
}
|
|
49
|
+
// Also verify the reverse: all functions are in the order
|
|
50
|
+
const functionNames = new Set(Object.keys(db.system.functions));
|
|
51
|
+
const orderNames = new Set();
|
|
52
|
+
for (const tier of db.system.order) {
|
|
53
|
+
for (const name of tier) {
|
|
54
|
+
orderNames.add(name);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// All functions should be in order
|
|
58
|
+
for (const name of functionNames) {
|
|
59
|
+
expect(orderNames.has(name)).toBe(true);
|
|
60
|
+
}
|
|
61
|
+
// Stop the scheduler
|
|
62
|
+
db.transactions.stopScheduler();
|
|
63
|
+
// Advance timers to let any pending RAF callbacks execute
|
|
64
|
+
await vi.runAllTimersAsync();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=create-database-scheduler.browser.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-database-scheduler.browser.test.js","sourceRoot":"","sources":["../../../../src/ecs/database/public/create-database-scheduler.browser.test.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AAEjE,QAAQ,CAAC,gDAAgD,EAAE,GAAG,EAAE;IAC5D,UAAU,CAAC,GAAG,EAAE;QACZ,EAAE,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACX,EAAE,CAAC,eAAe,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC1E,iEAAiE;QACjE,6EAA6E;QAC7E,yEAAyE;QACzE,kEAAkE;QAClE,gDAAgD;QAEhD,MAAM,UAAU,GAAG,SAAS,CAAC;QAE7B,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;YAC1C,OAAO,EAAE,UAAU;YACnB,YAAY,EAAE;gBACV,aAAa,CAAC,KAAK;oBACf,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,UAAU,CAAC;gBAChD,CAAC;aACJ;YACD,OAAO,EAAE;gBACL,UAAU,EAAE;oBACR,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE;wBACjB,cAAc;oBAClB,CAAC;iBACJ;aACJ;SACJ,CAAC,CAAC;QAEH,kEAAkE;QAClE,sDAAsD;QACtD,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QAE3C,mEAAmE;QACnE,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;QACzC,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACjC,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;gBACtB,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC7B,CAAC;QACL,CAAC;QAED,qDAAqD;QACrD,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;YAChC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,IAAwC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YACpF,MAAM,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,IAAwC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAClG,CAAC;QAED,0DAA0D;QAC1D,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;QAChE,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;QACrC,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACjC,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;gBACtB,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACzB,CAAC;QACL,CAAC;QAED,mCAAmC;QACnC,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YAC/B,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,CAAC;QAED,qBAAqB;QACrB,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,CAAC;QAEhC,0DAA0D;QAC1D,MAAM,EAAE,CAAC,iBAAiB,EAAE,CAAC;IACjC,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -18,18 +18,15 @@ export function shouldCoalesceTransactions(previous, current) {
|
|
|
18
18
|
export function coalesceWriteOperations(operations) {
|
|
19
19
|
if (operations.length <= 1)
|
|
20
20
|
return operations;
|
|
21
|
-
// Simple approach: just merge consecutive update operations on the same entity
|
|
22
21
|
const result = [];
|
|
22
|
+
const deletedEntities = new Set();
|
|
23
23
|
let i = 0;
|
|
24
24
|
while (i < operations.length) {
|
|
25
25
|
const current = operations[i];
|
|
26
|
-
// Look ahead to see if we can merge with next operations
|
|
27
26
|
if (current.type === "update") {
|
|
28
27
|
const mergedValues = { ...current.values };
|
|
29
28
|
let j = i + 1;
|
|
30
|
-
|
|
31
|
-
while (j < operations.length &&
|
|
32
|
-
operations[j].type === "update") {
|
|
29
|
+
while (j < operations.length && operations[j].type === "update") {
|
|
33
30
|
const nextOp = operations[j];
|
|
34
31
|
if (nextOp.type === "update" && nextOp.entity === current.entity) {
|
|
35
32
|
Object.assign(mergedValues, nextOp.values);
|
|
@@ -39,49 +36,26 @@ export function coalesceWriteOperations(operations) {
|
|
|
39
36
|
break;
|
|
40
37
|
}
|
|
41
38
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
result.push({
|
|
45
|
-
type: "update",
|
|
46
|
-
entity: current.entity,
|
|
47
|
-
values: mergedValues
|
|
48
|
-
});
|
|
49
|
-
i = j;
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
// No merging possible
|
|
53
|
-
result.push(current);
|
|
54
|
-
i++;
|
|
55
|
-
}
|
|
39
|
+
result.push(j > i + 1 ? { type: "update", entity: current.entity, values: mergedValues } : current);
|
|
40
|
+
i = j > i + 1 ? j : i + 1;
|
|
56
41
|
}
|
|
57
|
-
else if (current.type === "
|
|
58
|
-
|
|
59
|
-
// to verify the update is on the same entity
|
|
42
|
+
else if (current.type === "delete") {
|
|
43
|
+
deletedEntities.add(current.entity);
|
|
60
44
|
result.push(current);
|
|
61
45
|
i++;
|
|
62
46
|
}
|
|
63
47
|
else {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// Remove any preceding update operations on the same entity
|
|
67
|
-
const deleteEntity = current.entity;
|
|
68
|
-
for (let k = result.length - 1; k >= 0; k--) {
|
|
69
|
-
const op = result[k];
|
|
70
|
-
if (op.type === "update" && op.entity === deleteEntity) {
|
|
71
|
-
result.splice(k, 1);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
// Add the delete operation
|
|
75
|
-
result.push(current);
|
|
76
|
-
i++;
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
result.push(current);
|
|
80
|
-
i++;
|
|
81
|
-
}
|
|
48
|
+
result.push(current);
|
|
49
|
+
i++;
|
|
82
50
|
}
|
|
83
51
|
}
|
|
84
|
-
|
|
52
|
+
if (deletedEntities.size === 0)
|
|
53
|
+
return result;
|
|
54
|
+
// Single O(N) pass to strip updates for entities that were later deleted.
|
|
55
|
+
// An update-after-delete for the same entity is impossible in this codebase
|
|
56
|
+
// (updateEntity throws "Entity not found" after a delete), so filtering all
|
|
57
|
+
// updates whose entity appears in the delete set is semantics-preserving.
|
|
58
|
+
return result.filter(op => !(op.type === "update" && deletedEntities.has(op.entity)));
|
|
85
59
|
}
|
|
86
60
|
/**
|
|
87
61
|
* Coalesces two adjacent transaction results into a single combined transaction.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coalesce-actions.js","sourceRoot":"","sources":["../../../../src/ecs/database/transactional-store/coalesce-actions.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAGvD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CACtC,QAAgC,EAChC,OAA+B;IAE/B,OAAO,CAAC,CAAC,QAAQ,CAAC,QAAQ;QACtB,CAAC,CAAC,OAAO,CAAC,QAAQ;QAClB,OAAO,CAAC,QAAQ,CAAC,QAAQ,KAAK,KAAK;QACnC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,KAAK,KAAK;QACpC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACtE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,uBAAuB,CAAC,UAA4C;IAChF,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,UAAU,CAAC;IAE9C
|
|
1
|
+
{"version":3,"file":"coalesce-actions.js","sourceRoot":"","sources":["../../../../src/ecs/database/transactional-store/coalesce-actions.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
2
|
import { describe, it, expect } from "vitest";
|
|
3
|
-
import { shouldCoalesceTransactions, coalesceTransactions } from "./coalesce-actions.js";
|
|
3
|
+
import { shouldCoalesceTransactions, coalesceTransactions, coalesceWriteOperations } from "./coalesce-actions.js";
|
|
4
4
|
describe("shouldCoalesceTransactions", () => {
|
|
5
5
|
it("should return true for actions with same coalesce values", () => {
|
|
6
6
|
const previous = {
|
|
@@ -438,4 +438,33 @@ describe("coalesceTransactions", () => {
|
|
|
438
438
|
expect(result.redo[1]).toEqual({ type: "update", entity: 456, values: { position: { y: 2 } } });
|
|
439
439
|
});
|
|
440
440
|
});
|
|
441
|
+
describe("coalesceWriteOperations performance", () => {
|
|
442
|
+
it("should run in linear time (not quadratic) for N delete operations", () => {
|
|
443
|
+
// N delete ops produce O(N²) work in the buggy implementation because each
|
|
444
|
+
// delete scans all previously-built results. At N=25_000 the quadratic path
|
|
445
|
+
// takes ~450ms; the linear fix takes <20ms.
|
|
446
|
+
const N = 25_000;
|
|
447
|
+
const ops = Array.from({ length: N }, (_, i) => ({
|
|
448
|
+
type: "delete",
|
|
449
|
+
entity: i,
|
|
450
|
+
}));
|
|
451
|
+
const start = performance.now();
|
|
452
|
+
coalesceWriteOperations(ops);
|
|
453
|
+
const elapsed = performance.now() - start;
|
|
454
|
+
// 100ms budget: well above the linear path (~20ms) but well below the
|
|
455
|
+
// quadratic path (~450ms), making the regression unambiguous.
|
|
456
|
+
expect(elapsed).toBeLessThan(100);
|
|
457
|
+
});
|
|
458
|
+
it("should strip preceding updates for deleted entities", () => {
|
|
459
|
+
const ops = [
|
|
460
|
+
{ type: "update", entity: 1, values: { x: 1 } },
|
|
461
|
+
{ type: "update", entity: 2, values: { y: 2 } },
|
|
462
|
+
{ type: "delete", entity: 1 },
|
|
463
|
+
];
|
|
464
|
+
const result = coalesceWriteOperations(ops);
|
|
465
|
+
expect(result).toHaveLength(2);
|
|
466
|
+
expect(result[0]).toEqual({ type: "update", entity: 2, values: { y: 2 } });
|
|
467
|
+
expect(result[1]).toEqual({ type: "delete", entity: 1 });
|
|
468
|
+
});
|
|
469
|
+
});
|
|
441
470
|
//# sourceMappingURL=coalesce-actions.test.js.map
|