@elaraai/e3-core 1.0.16 → 1.0.18

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.
Files changed (28) hide show
  1. package/dist/src/records.d.ts +10 -0
  2. package/dist/src/records.d.ts.map +1 -1
  3. package/dist/src/records.js +37 -3
  4. package/dist/src/records.js.map +1 -1
  5. package/dist/src/records.spec.js +108 -1
  6. package/dist/src/records.spec.js.map +1 -1
  7. package/dist/src/storage/local/LocalDatasetRefStore.d.ts.map +1 -1
  8. package/dist/src/storage/local/LocalDatasetRefStore.js +2 -52
  9. package/dist/src/storage/local/LocalDatasetRefStore.js.map +1 -1
  10. package/dist/src/storage/local/LocalRefStore.d.ts.map +1 -1
  11. package/dist/src/storage/local/LocalRefStore.js +23 -18
  12. package/dist/src/storage/local/LocalRefStore.js.map +1 -1
  13. package/dist/src/storage/local/LocalRefStore.spec.d.ts +6 -0
  14. package/dist/src/storage/local/LocalRefStore.spec.d.ts.map +1 -0
  15. package/dist/src/storage/local/LocalRefStore.spec.js +159 -0
  16. package/dist/src/storage/local/LocalRefStore.spec.js.map +1 -0
  17. package/dist/src/storage/local/gc.d.ts.map +1 -1
  18. package/dist/src/storage/local/gc.js +62 -0
  19. package/dist/src/storage/local/gc.js.map +1 -1
  20. package/dist/src/storage/local/localHelpers.d.ts +40 -0
  21. package/dist/src/storage/local/localHelpers.d.ts.map +1 -1
  22. package/dist/src/storage/local/localHelpers.js +77 -0
  23. package/dist/src/storage/local/localHelpers.js.map +1 -1
  24. package/dist/src/storage/local/localHelpers.spec.d.ts +6 -0
  25. package/dist/src/storage/local/localHelpers.spec.d.ts.map +1 -0
  26. package/dist/src/storage/local/localHelpers.spec.js +83 -0
  27. package/dist/src/storage/local/localHelpers.spec.js.map +1 -0
  28. package/package.json +4 -4
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Licensed under BSL 1.1. See LICENSE for details.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=localHelpers.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localHelpers.spec.d.ts","sourceRoot":"","sources":["../../../../src/storage/local/localHelpers.spec.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Licensed under BSL 1.1. See LICENSE for details.
4
+ */
5
+ /**
6
+ * Tests for the local atomic-write primitive.
7
+ *
8
+ * `atomicWriteFile` is the shared stage-and-rename helper behind every mutable
9
+ * ref/state file (execution status, dataflow runs, workspace state, dataset
10
+ * refs). The contract it guarantees — and the property a bare `fs.writeFile`
11
+ * violates — is that a concurrent reader of a path being overwritten in place
12
+ * never observes a truncated/empty file.
13
+ */
14
+ import { describe, it, beforeEach, afterEach } from 'node:test';
15
+ import assert from 'node:assert';
16
+ import * as fs from 'node:fs/promises';
17
+ import { join } from 'node:path';
18
+ import { atomicWriteFile } from './localHelpers.js';
19
+ import { createTempDir, removeTempDir } from '../../test-helpers.js';
20
+ describe('atomicWriteFile', () => {
21
+ let dir;
22
+ beforeEach(() => { dir = createTempDir(); });
23
+ afterEach(() => { removeTempDir(dir); });
24
+ // POSIX-only. This reproduces the O_TRUNC torn-read race a bare fs.writeFile
25
+ // exposes (reader sees a 0-byte/partial file). On Windows rename-over-an-open
26
+ // file is a sharing violation (handled by renameWithRetry), not a torn read,
27
+ // and a tight raw-byte reader there starves the rename via libuv-threadpool
28
+ // ordering rather than exposing any real bug. Windows concurrent-read atomicity
29
+ // is covered end-to-end by the LocalRefStore execution/dataflow tests, which
30
+ // read+decode (a natural file-closed gap). Matches the repo's existing
31
+ // Windows-skip pattern for concurrency specs (e.g. runDetached.spec.ts).
32
+ it('a concurrent reader never observes a torn or empty file while the path is overwritten in place', { skip: process.platform === 'win32' }, async () => {
33
+ const target = join(dir, 'status.beast2');
34
+ // Distinctly-sized multi-chunk payloads so each write spans multiple write()
35
+ // calls — this widens the truncation window a bare fs.writeFile would expose,
36
+ // making a torn read overwhelmingly likely if atomicity regresses. Kept
37
+ // moderate so a concurrent reader on Windows doesn't hold the file long
38
+ // enough to starve the writer's rename.
39
+ const payloadA = Buffer.alloc(48 * 1024, 0xab);
40
+ const payloadB = Buffer.alloc(32 * 1024, 0xcd);
41
+ await atomicWriteFile(target, payloadA); // seed so the file always exists
42
+ let stop = false;
43
+ const reader = (async () => {
44
+ const torn = [];
45
+ while (!stop) {
46
+ const data = await fs.readFile(target);
47
+ const isA = data.length === payloadA.length && data[0] === 0xab && data[data.length - 1] === 0xab;
48
+ const isB = data.length === payloadB.length && data[0] === 0xcd && data[data.length - 1] === 0xcd;
49
+ if (!isA && !isB)
50
+ torn.push(data.length);
51
+ // Yield between reads. A tight read loop holds the file open ~continuously,
52
+ // which on Windows starves the writer's rename (EPERM); a real poller reads
53
+ // periodically. This still overlaps writes often enough to catch a torn read.
54
+ await new Promise((r) => setTimeout(r, 1));
55
+ }
56
+ return torn;
57
+ })();
58
+ try {
59
+ for (let i = 0; i < 200; i++) {
60
+ await atomicWriteFile(target, i % 2 === 0 ? payloadB : payloadA);
61
+ }
62
+ }
63
+ finally {
64
+ stop = true; // always release the reader, even if a write throws, or it spins forever
65
+ }
66
+ const torn = await reader;
67
+ assert.deepStrictEqual(torn, [], `reader observed ${torn.length} torn read(s); sizes: ${torn.slice(0, 5).join(',')}`);
68
+ });
69
+ it('leaves no .partial staging files behind after sequential overwrites', async () => {
70
+ const target = join(dir, 'sub', 'a.beast2');
71
+ await atomicWriteFile(target, Buffer.from('hello'));
72
+ await atomicWriteFile(target, Buffer.from('world'));
73
+ const entries = await fs.readdir(join(dir, 'sub'));
74
+ assert.deepStrictEqual(entries, ['a.beast2'], 'only the destination remains; staging files are renamed away');
75
+ assert.strictEqual((await fs.readFile(target)).toString(), 'world');
76
+ });
77
+ it('creates parent directories as needed', async () => {
78
+ const target = join(dir, 'deep', 'nested', 'path', 'x.beast2');
79
+ await atomicWriteFile(target, Buffer.from('ok'));
80
+ assert.strictEqual((await fs.readFile(target)).toString(), 'ok');
81
+ });
82
+ });
83
+ //# sourceMappingURL=localHelpers.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localHelpers.spec.js","sourceRoot":"","sources":["../../../../src/storage/local/localHelpers.spec.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elaraai/e3-core",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "type": "module",
5
5
  "description": "East Execution Engine Core - Programmatic API for e3 repository operations",
6
6
  "main": "dist/src/index.js",
@@ -37,11 +37,11 @@
37
37
  "cross-spawn": "^7.0.6",
38
38
  "yauzl": "^3.2.0",
39
39
  "yazl": "^2.5.1",
40
- "@elaraai/e3": "1.0.16",
41
- "@elaraai/e3-types": "1.0.16"
40
+ "@elaraai/e3-types": "1.0.18",
41
+ "@elaraai/e3": "1.0.18"
42
42
  },
43
43
  "peerDependencies": {
44
- "@elaraai/east": "1.0.16"
44
+ "@elaraai/east": "1.0.18"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/cross-spawn": "^6.0.6",