@milaboratories/pl-middle-layer 1.51.0 → 1.52.0
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/cfg_render/executor.cjs +2 -2
- package/dist/cfg_render/executor.js +1 -1
- package/dist/debug/index.cjs +4 -1
- package/dist/debug/index.cjs.map +1 -1
- package/dist/debug/index.js +4 -1
- package/dist/debug/index.js.map +1 -1
- package/dist/index.cjs +14 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/js_render/computable_context.cjs +12 -2
- package/dist/js_render/computable_context.cjs.map +1 -1
- package/dist/js_render/computable_context.js +12 -2
- package/dist/js_render/computable_context.js.map +1 -1
- package/dist/js_render/context.cjs +36 -3
- package/dist/js_render/context.cjs.map +1 -1
- package/dist/js_render/context.js +36 -3
- package/dist/js_render/context.js.map +1 -1
- package/dist/js_render/index.cjs +5 -1
- package/dist/js_render/index.cjs.map +1 -1
- package/dist/js_render/index.js +5 -1
- package/dist/js_render/index.js.map +1 -1
- package/dist/middle_layer/project.cjs +8 -5
- package/dist/middle_layer/project.cjs.map +1 -1
- package/dist/middle_layer/project.js +8 -5
- package/dist/middle_layer/project.js.map +1 -1
- package/dist/middle_layer/project_overview.cjs +28 -22
- package/dist/middle_layer/project_overview.cjs.map +1 -1
- package/dist/middle_layer/project_overview.js +28 -22
- package/dist/middle_layer/project_overview.js.map +1 -1
- package/dist/model/block_pack_spec.cjs.map +1 -1
- package/dist/model/block_pack_spec.d.ts +2 -2
- package/dist/model/block_pack_spec.js.map +1 -1
- package/dist/model/template_spec.d.ts +7 -2
- package/dist/mutator/block-pack/block_pack.cjs +20 -1
- package/dist/mutator/block-pack/block_pack.cjs.map +1 -1
- package/dist/mutator/block-pack/block_pack.d.ts +4 -0
- package/dist/mutator/block-pack/block_pack.js +19 -1
- package/dist/mutator/block-pack/block_pack.js.map +1 -1
- package/dist/mutator/template/template_cache.cjs +515 -0
- package/dist/mutator/template/template_cache.cjs.map +1 -0
- package/dist/mutator/template/template_cache.d.ts +78 -0
- package/dist/mutator/template/template_cache.js +502 -0
- package/dist/mutator/template/template_cache.js.map +1 -0
- package/dist/mutator/template/template_loading.cjs +3 -1
- package/dist/mutator/template/template_loading.cjs.map +1 -1
- package/dist/mutator/template/template_loading.js +3 -1
- package/dist/mutator/template/template_loading.js.map +1 -1
- package/package.json +11 -11
- package/src/debug/index.ts +6 -0
- package/src/index.ts +1 -0
- package/src/js_render/computable_context.ts +13 -2
- package/src/js_render/context.ts +58 -5
- package/src/js_render/index.ts +8 -1
- package/src/middle_layer/project.ts +12 -8
- package/src/middle_layer/project_overview.ts +6 -0
- package/src/model/block_pack_spec.ts +2 -2
- package/src/model/template_spec.ts +11 -1
- package/src/mutator/block-pack/block_pack.ts +35 -1
- package/src/mutator/template/template_cache.test.ts +373 -0
- package/src/mutator/template/template_cache.ts +763 -0
- package/src/mutator/template/template_loading.ts +3 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureResourceIdNotNull,
|
|
3
|
+
field,
|
|
4
|
+
isNotNullResourceId,
|
|
5
|
+
poll,
|
|
6
|
+
TestHelpers,
|
|
7
|
+
toGlobalResourceId,
|
|
8
|
+
} from "@milaboratories/pl-client";
|
|
9
|
+
import type { AnyResourceRef } from "@milaboratories/pl-client";
|
|
10
|
+
import { describe, expect, test } from "vitest";
|
|
11
|
+
import { TplSpecEnterExplicit, TplSpecSumExplicit } from "../../test/known_templates";
|
|
12
|
+
import {
|
|
13
|
+
ExplicitTemplateEnterNumbers,
|
|
14
|
+
ExplicitTemplateSumNumbers,
|
|
15
|
+
} from "../../test/explicit_templates";
|
|
16
|
+
import { loadTemplate } from "./template_loading";
|
|
17
|
+
import {
|
|
18
|
+
ACCESS_COUNT_KEY,
|
|
19
|
+
ACCESS_KEY_PREFIX,
|
|
20
|
+
cacheBlockPackTemplate,
|
|
21
|
+
dropTemplateCache,
|
|
22
|
+
flattenTemplateTree,
|
|
23
|
+
getOrCreateTemplateCache,
|
|
24
|
+
loadTemplateCached,
|
|
25
|
+
runGc,
|
|
26
|
+
TemplateCacheType,
|
|
27
|
+
} from "./template_cache";
|
|
28
|
+
import { parseTemplate } from "@milaboratories/pl-model-backend";
|
|
29
|
+
import type { BlockPackSpecPrepared } from "../../model";
|
|
30
|
+
|
|
31
|
+
function createTestCacheInTx(pl: Parameters<Parameters<typeof TestHelpers.withTempRoot>[0]>[0]) {
|
|
32
|
+
return pl.withWriteTx("createTestCache", async (tx) => {
|
|
33
|
+
const cache = tx.createStruct(TemplateCacheType);
|
|
34
|
+
// Attach to user root so it doesn't get GC'd
|
|
35
|
+
tx.createField(field(pl.clientRoot, "__testCache"), "Dynamic", cache);
|
|
36
|
+
tx.lock(cache);
|
|
37
|
+
await tx.commit();
|
|
38
|
+
return await cache.globalId;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── flattenTemplateTree ─────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
describe("flattenTemplateTree", () => {
|
|
45
|
+
test("produces nodes in topological order for V2 template", () => {
|
|
46
|
+
const data = parseTemplate(ExplicitTemplateEnterNumbers);
|
|
47
|
+
const nodes = flattenTemplateTree(data);
|
|
48
|
+
expect(nodes.length).toBeGreaterThan(0);
|
|
49
|
+
|
|
50
|
+
// All hashes are unique
|
|
51
|
+
const hashes = nodes.map((n) => n.hash);
|
|
52
|
+
expect(new Set(hashes).size).toBe(hashes.length);
|
|
53
|
+
|
|
54
|
+
// Every child hash references a node that appears earlier in the list
|
|
55
|
+
const seenHashes = new Set<string>();
|
|
56
|
+
for (const node of nodes) {
|
|
57
|
+
for (const ch of node.childHashes) {
|
|
58
|
+
expect(seenHashes.has(ch)).toBe(true);
|
|
59
|
+
}
|
|
60
|
+
seenHashes.add(node.hash);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("deterministic hashes for same content", () => {
|
|
65
|
+
const data = parseTemplate(ExplicitTemplateEnterNumbers);
|
|
66
|
+
const nodes1 = flattenTemplateTree(data);
|
|
67
|
+
const nodes2 = flattenTemplateTree(data);
|
|
68
|
+
expect(nodes1.map((n) => n.hash)).toStrictEqual(nodes2.map((n) => n.hash));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("different templates produce different root hashes", () => {
|
|
72
|
+
const dataEnter = parseTemplate(ExplicitTemplateEnterNumbers);
|
|
73
|
+
const dataSum = parseTemplate(ExplicitTemplateSumNumbers);
|
|
74
|
+
const nodesEnter = flattenTemplateTree(dataEnter);
|
|
75
|
+
const nodesSum = flattenTemplateTree(dataSum);
|
|
76
|
+
expect(nodesEnter[nodesEnter.length - 1].hash).not.toBe(nodesSum[nodesSum.length - 1].hash);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ─── getOrCreateTemplateCache / dropTemplateCache ────────────────────────────
|
|
81
|
+
|
|
82
|
+
describe("getOrCreateTemplateCache", () => {
|
|
83
|
+
test("creates cache on first call and reuses on second", async () => {
|
|
84
|
+
await TestHelpers.withTempRoot(async (pl) => {
|
|
85
|
+
const cacheId1 = await getOrCreateTemplateCache(pl);
|
|
86
|
+
const cacheId2 = await getOrCreateTemplateCache(pl);
|
|
87
|
+
expect(cacheId1).toBe(cacheId2);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("dropTemplateCache", () => {
|
|
93
|
+
test("drops cache and allows recreation", async () => {
|
|
94
|
+
await TestHelpers.withTempRoot(async (pl) => {
|
|
95
|
+
const cacheId1 = await getOrCreateTemplateCache(pl);
|
|
96
|
+
await dropTemplateCache(pl);
|
|
97
|
+
const cacheId2 = await getOrCreateTemplateCache(pl);
|
|
98
|
+
expect(cacheId1).not.toBe(cacheId2);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// ─── loadTemplateCached ──────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
describe("loadTemplateCached", () => {
|
|
106
|
+
test("cache miss then cache hit returns same ResourceId", async () => {
|
|
107
|
+
await TestHelpers.withTempRoot(async (pl) => {
|
|
108
|
+
const testCache = await createTestCacheInTx(pl);
|
|
109
|
+
|
|
110
|
+
const id1 = await loadTemplateCached(pl, TplSpecEnterExplicit, {
|
|
111
|
+
cacheResourceId: testCache,
|
|
112
|
+
});
|
|
113
|
+
const id2 = await loadTemplateCached(pl, TplSpecEnterExplicit, {
|
|
114
|
+
cacheResourceId: testCache,
|
|
115
|
+
});
|
|
116
|
+
expect(id1).toBe(id2);
|
|
117
|
+
});
|
|
118
|
+
}, 15000);
|
|
119
|
+
|
|
120
|
+
test("different templates get different ResourceIds", async () => {
|
|
121
|
+
await TestHelpers.withTempRoot(async (pl) => {
|
|
122
|
+
const testCache = await createTestCacheInTx(pl);
|
|
123
|
+
|
|
124
|
+
const id1 = await loadTemplateCached(pl, TplSpecEnterExplicit, {
|
|
125
|
+
cacheResourceId: testCache,
|
|
126
|
+
});
|
|
127
|
+
const id2 = await loadTemplateCached(pl, TplSpecSumExplicit, {
|
|
128
|
+
cacheResourceId: testCache,
|
|
129
|
+
});
|
|
130
|
+
expect(id1).not.toBe(id2);
|
|
131
|
+
});
|
|
132
|
+
}, 15000);
|
|
133
|
+
|
|
134
|
+
test("cached template can be used in a transaction via loadTemplate", async () => {
|
|
135
|
+
await TestHelpers.withTempRoot(async (pl) => {
|
|
136
|
+
const testCache = await createTestCacheInTx(pl);
|
|
137
|
+
|
|
138
|
+
const templateId = await loadTemplateCached(pl, TplSpecEnterExplicit, {
|
|
139
|
+
cacheResourceId: testCache,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Use the cached template in a write transaction
|
|
143
|
+
const resultId = await pl.withWriteTx("useCachedTemplate", async (tx) => {
|
|
144
|
+
const ref = loadTemplate(tx, { type: "cached", resourceId: templateId });
|
|
145
|
+
const holder = field(pl.clientRoot, "test_result");
|
|
146
|
+
tx.createField(holder, "Dynamic", ref);
|
|
147
|
+
await tx.commit();
|
|
148
|
+
return templateId;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Verify the field was set correctly
|
|
152
|
+
await pl.withReadTx("verify", async (tx) => {
|
|
153
|
+
const fd = await tx.getField(field(pl.clientRoot, "test_result"));
|
|
154
|
+
expect(ensureResourceIdNotNull(fd.value)).toBe(resultId);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}, 15000);
|
|
158
|
+
|
|
159
|
+
test("cached spec type is passed through without re-caching", async () => {
|
|
160
|
+
await TestHelpers.withTempRoot(async (pl) => {
|
|
161
|
+
const testCache = await createTestCacheInTx(pl);
|
|
162
|
+
|
|
163
|
+
const id1 = await loadTemplateCached(pl, TplSpecEnterExplicit, {
|
|
164
|
+
cacheResourceId: testCache,
|
|
165
|
+
});
|
|
166
|
+
// Pass cached spec directly
|
|
167
|
+
const id2 = await loadTemplateCached(
|
|
168
|
+
pl,
|
|
169
|
+
{ type: "cached", resourceId: id1 },
|
|
170
|
+
{ cacheResourceId: testCache },
|
|
171
|
+
);
|
|
172
|
+
expect(id1).toBe(id2);
|
|
173
|
+
});
|
|
174
|
+
}, 10000);
|
|
175
|
+
|
|
176
|
+
test("uses lazy cache initialization when no cacheResourceId provided", async () => {
|
|
177
|
+
await TestHelpers.withTempRoot(async (pl) => {
|
|
178
|
+
const id = await loadTemplateCached(pl, TplSpecEnterExplicit);
|
|
179
|
+
expect(id).toBeDefined();
|
|
180
|
+
|
|
181
|
+
const id2 = await loadTemplateCached(pl, TplSpecEnterExplicit);
|
|
182
|
+
expect(id).toBe(id2);
|
|
183
|
+
});
|
|
184
|
+
}, 15000);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// ─── cacheBlockPackTemplate ──────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
describe("cacheBlockPackTemplate", () => {
|
|
190
|
+
test("replaces prepared template with cached reference", async () => {
|
|
191
|
+
await TestHelpers.withTempRoot(async (pl) => {
|
|
192
|
+
const testCache = await createTestCacheInTx(pl);
|
|
193
|
+
|
|
194
|
+
const spec: BlockPackSpecPrepared = {
|
|
195
|
+
type: "prepared",
|
|
196
|
+
template: {
|
|
197
|
+
type: "prepared",
|
|
198
|
+
data: parseTemplate(ExplicitTemplateEnterNumbers),
|
|
199
|
+
},
|
|
200
|
+
config: { renderingMode: "Heavy" } as any,
|
|
201
|
+
frontend: { type: "url", url: "http://test" },
|
|
202
|
+
source: { type: "dev-v1", folder: "/test" },
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const cached = await cacheBlockPackTemplate(pl, spec, {
|
|
206
|
+
cacheResourceId: testCache,
|
|
207
|
+
});
|
|
208
|
+
expect(cached.template.type).toBe("cached");
|
|
209
|
+
expect(cached.type).toBe("prepared");
|
|
210
|
+
expect(cached.config).toBe(spec.config);
|
|
211
|
+
});
|
|
212
|
+
}, 15000);
|
|
213
|
+
|
|
214
|
+
test("returns already-cached spec unchanged", async () => {
|
|
215
|
+
await TestHelpers.withTempRoot(async (pl) => {
|
|
216
|
+
const testCache = await createTestCacheInTx(pl);
|
|
217
|
+
|
|
218
|
+
const templateId = await loadTemplateCached(pl, TplSpecEnterExplicit, {
|
|
219
|
+
cacheResourceId: testCache,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const spec: BlockPackSpecPrepared = {
|
|
223
|
+
type: "prepared",
|
|
224
|
+
template: { type: "cached", resourceId: templateId },
|
|
225
|
+
config: { renderingMode: "Heavy" } as any,
|
|
226
|
+
frontend: { type: "url", url: "http://test" },
|
|
227
|
+
source: { type: "dev-v1", folder: "/test" },
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const result = await cacheBlockPackTemplate(pl, spec, {
|
|
231
|
+
cacheResourceId: testCache,
|
|
232
|
+
});
|
|
233
|
+
expect(result).toBe(spec);
|
|
234
|
+
});
|
|
235
|
+
}, 15000);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// ─── Equivalence: cached vs legacy produce identical resources ───────────────
|
|
239
|
+
|
|
240
|
+
describe("template cache produces equivalent resources", () => {
|
|
241
|
+
test("cached and legacy templates deduplicate to same original (V2)", async () => {
|
|
242
|
+
await TestHelpers.withTempRoot(async (pl) => {
|
|
243
|
+
const testCache = await createTestCacheInTx(pl);
|
|
244
|
+
|
|
245
|
+
// Cached path
|
|
246
|
+
const cachedId = await loadTemplateCached(pl, TplSpecEnterExplicit, {
|
|
247
|
+
cacheResourceId: testCache,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Legacy path (inside a transaction)
|
|
251
|
+
const legacyId = await pl.withWriteTx("legacy", async (tx) => {
|
|
252
|
+
const ref = loadTemplate(tx, TplSpecEnterExplicit);
|
|
253
|
+
const holder = field(pl.clientRoot, "legacy_tpl");
|
|
254
|
+
tx.createField(holder, "Dynamic", ref);
|
|
255
|
+
await tx.commit();
|
|
256
|
+
return await toGlobalResourceId(ref as AnyResourceRef);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Wait for both to reach final state
|
|
260
|
+
const [cachedOriginal, legacyOriginal] = await poll(pl, async (a) => {
|
|
261
|
+
const cachedRes = await a.get(cachedId).then((r) => r.final());
|
|
262
|
+
const legacyRes = await a.get(legacyId).then((r) => r.final());
|
|
263
|
+
return [cachedRes.data.originalResourceId, legacyRes.data.originalResourceId] as const;
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// After dedup, both should resolve to the same canonical resource.
|
|
267
|
+
// Either one points to the other, or both point to a common original.
|
|
268
|
+
const resolvedCached = isNotNullResourceId(cachedOriginal) ? cachedOriginal : cachedId;
|
|
269
|
+
const resolvedLegacy = isNotNullResourceId(legacyOriginal) ? legacyOriginal : legacyId;
|
|
270
|
+
expect(resolvedCached).toBe(resolvedLegacy);
|
|
271
|
+
});
|
|
272
|
+
}, 30000);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// ─── Shared library dedup ────────────────────────────────────────────────────
|
|
276
|
+
|
|
277
|
+
describe("shared library dedup", () => {
|
|
278
|
+
test("two different templates sharing a library reuse the same cache entry", async () => {
|
|
279
|
+
await TestHelpers.withTempRoot(async (pl) => {
|
|
280
|
+
const testCache = await createTestCacheInTx(pl);
|
|
281
|
+
|
|
282
|
+
// Flatten both templates and find common hashes (shared libs)
|
|
283
|
+
const nodesEnter = flattenTemplateTree(parseTemplate(ExplicitTemplateEnterNumbers));
|
|
284
|
+
const nodesSum = flattenTemplateTree(parseTemplate(ExplicitTemplateSumNumbers));
|
|
285
|
+
const enterHashes = new Set(nodesEnter.map((n) => n.hash));
|
|
286
|
+
const sumHashes = new Set(nodesSum.map((n) => n.hash));
|
|
287
|
+
const sharedHashes = [...enterHashes].filter((h) => sumHashes.has(h));
|
|
288
|
+
|
|
289
|
+
// Cache both templates
|
|
290
|
+
await loadTemplateCached(pl, TplSpecEnterExplicit, { cacheResourceId: testCache });
|
|
291
|
+
await loadTemplateCached(pl, TplSpecSumExplicit, { cacheResourceId: testCache });
|
|
292
|
+
|
|
293
|
+
// Verify shared hashes exist as fields on the cache resource
|
|
294
|
+
if (sharedHashes.length > 0) {
|
|
295
|
+
await pl.withReadTx("checkShared", async (tx) => {
|
|
296
|
+
for (const hash of sharedHashes) {
|
|
297
|
+
const exists = await tx.fieldExists(field(testCache, hash));
|
|
298
|
+
expect(exists).toBe(true);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}, 15000);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// ─── GC ──────────────────────────────────────────────────────────────────────
|
|
307
|
+
|
|
308
|
+
describe("GC", () => {
|
|
309
|
+
test("evicts entries when cache exceeds max entries", async () => {
|
|
310
|
+
await TestHelpers.withTempRoot(async (pl) => {
|
|
311
|
+
const testCache = await createTestCacheInTx(pl);
|
|
312
|
+
|
|
313
|
+
// Cache enter-numbers template (~37 entries)
|
|
314
|
+
await loadTemplateCached(pl, TplSpecEnterExplicit, { cacheResourceId: testCache });
|
|
315
|
+
|
|
316
|
+
const enterNodes = flattenTemplateTree(parseTemplate(ExplicitTemplateEnterNumbers));
|
|
317
|
+
const allHashes = enterNodes.map((n) => n.hash);
|
|
318
|
+
const halfLen = Math.floor(allHashes.length / 2);
|
|
319
|
+
|
|
320
|
+
// Backdate the first half of entries (low timestamp = evicted first)
|
|
321
|
+
await pl.withWriteTx("backdate", async (tx) => {
|
|
322
|
+
for (let i = 0; i < halfLen; i++) {
|
|
323
|
+
tx.setKValue(testCache, ACCESS_KEY_PREFIX + allHashes[i], "1000");
|
|
324
|
+
}
|
|
325
|
+
await tx.commit();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Run GC with low max: keep only the fresh half
|
|
329
|
+
const evicted = await runGc(pl, testCache, allHashes.length - halfLen);
|
|
330
|
+
expect(evicted).toBe(true);
|
|
331
|
+
|
|
332
|
+
// Verify backdated entries were evicted, fresh entries survive
|
|
333
|
+
await pl.withReadTx("verify", async (tx) => {
|
|
334
|
+
for (let i = 0; i < halfLen; i++) {
|
|
335
|
+
const exists = await tx.fieldExists(field(testCache, allHashes[i]));
|
|
336
|
+
expect(exists).toBe(false);
|
|
337
|
+
}
|
|
338
|
+
for (let i = halfLen; i < allHashes.length; i++) {
|
|
339
|
+
const exists = await tx.fieldExists(field(testCache, allHashes[i]));
|
|
340
|
+
expect(exists).toBe(true);
|
|
341
|
+
}
|
|
342
|
+
// Counter should be reset
|
|
343
|
+
const count = await tx.getKValueStringIfExists(testCache, ACCESS_COUNT_KEY);
|
|
344
|
+
expect(count).toBe("0");
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
}, 15000);
|
|
348
|
+
|
|
349
|
+
test("does not evict when under max entries", async () => {
|
|
350
|
+
await TestHelpers.withTempRoot(async (pl) => {
|
|
351
|
+
const testCache = await createTestCacheInTx(pl);
|
|
352
|
+
|
|
353
|
+
// Cache a template (~37 entries)
|
|
354
|
+
await loadTemplateCached(pl, TplSpecEnterExplicit, { cacheResourceId: testCache });
|
|
355
|
+
|
|
356
|
+
const enterNodes = flattenTemplateTree(parseTemplate(ExplicitTemplateEnterNumbers));
|
|
357
|
+
|
|
358
|
+
// Run GC with high max — nothing should be evicted
|
|
359
|
+
const evicted = await runGc(pl, testCache, 100);
|
|
360
|
+
expect(evicted).toBe(false);
|
|
361
|
+
|
|
362
|
+
// All entries should survive, counter should still be reset
|
|
363
|
+
await pl.withReadTx("verify", async (tx) => {
|
|
364
|
+
for (const node of enterNodes) {
|
|
365
|
+
const exists = await tx.fieldExists(field(testCache, node.hash));
|
|
366
|
+
expect(exists).toBe(true);
|
|
367
|
+
}
|
|
368
|
+
const count = await tx.getKValueStringIfExists(testCache, ACCESS_COUNT_KEY);
|
|
369
|
+
expect(count).toBe("0");
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
}, 15000);
|
|
373
|
+
});
|