@machinemetrics/mm-erp-sdk 0.9.6-beta.0 → 0.9.6-beta.1
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/utils/local-data-store/sdk-runtime-state-db.js +28 -25
- package/dist/utils/local-data-store/sdk-runtime-state-db.js.map +1 -1
- package/package.json +1 -1
- package/src/utils/local-data-store/sdk-runtime-state-db.test.ts +106 -0
- package/src/utils/local-data-store/sdk-runtime-state-db.ts +32 -32
|
@@ -23,17 +23,25 @@ function getDb() {
|
|
|
23
23
|
if (!db) {
|
|
24
24
|
const filename = dbPath();
|
|
25
25
|
ensureDbDirectory(filename);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const instance = new Database(filename);
|
|
27
|
+
instance.pragma("journal_mode = WAL");
|
|
28
|
+
instance.pragma("busy_timeout = 5000");
|
|
29
|
+
instance.exec(`
|
|
30
30
|
CREATE TABLE IF NOT EXISTS ${TABLE} (
|
|
31
31
|
key TEXT PRIMARY KEY,
|
|
32
32
|
value TEXT NOT NULL,
|
|
33
33
|
updated_at TEXT NOT NULL
|
|
34
34
|
);
|
|
35
35
|
`);
|
|
36
|
-
|
|
36
|
+
db = instance;
|
|
37
|
+
try {
|
|
38
|
+
importLegacyJobStateFileOnce();
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
instance.close();
|
|
42
|
+
db = null;
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
37
45
|
}
|
|
38
46
|
return db;
|
|
39
47
|
}
|
|
@@ -54,27 +62,22 @@ function importLegacyJobStateFileOnce() {
|
|
|
54
62
|
if (readRaw(LEGACY_IMPORT_FLAG_KEY)) {
|
|
55
63
|
return;
|
|
56
64
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
writeRaw("databaseLock", JSON.stringify(data.databaseLock));
|
|
73
|
-
}
|
|
65
|
+
if (fs.existsSync(LEGACY_JOB_STATE_FILE)) {
|
|
66
|
+
const raw = fs.readFileSync(LEGACY_JOB_STATE_FILE, "utf-8");
|
|
67
|
+
const data = JSON.parse(raw);
|
|
68
|
+
if (data.companyInfo && !readRaw("companyInfo")) {
|
|
69
|
+
writeRaw("companyInfo", JSON.stringify(data.companyInfo));
|
|
70
|
+
}
|
|
71
|
+
if (data.mmApiToken && !readRaw("mmApiToken")) {
|
|
72
|
+
writeRaw("mmApiToken", JSON.stringify(data.mmApiToken));
|
|
73
|
+
}
|
|
74
|
+
if (data.initialLoadComplete !== undefined &&
|
|
75
|
+
!readRaw("initialLoadComplete")) {
|
|
76
|
+
writeRaw("initialLoadComplete", JSON.stringify(data.initialLoadComplete));
|
|
77
|
+
}
|
|
78
|
+
if (data.databaseLock && !readRaw("databaseLock")) {
|
|
79
|
+
writeRaw("databaseLock", JSON.stringify(data.databaseLock));
|
|
74
80
|
}
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
console.error(`Failed to import legacy job state from ${LEGACY_JOB_STATE_FILE}:`, error);
|
|
78
81
|
}
|
|
79
82
|
writeRaw(LEGACY_IMPORT_FLAG_KEY, JSON.stringify(true));
|
|
80
83
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk-runtime-state-db.js","sourceRoot":"","sources":["../../../src/utils/local-data-store/sdk-runtime-state-db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAEtC,MAAM,KAAK,GAAG,mBAAmB,CAAC;AAClC,MAAM,qBAAqB,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;AAClE,MAAM,sBAAsB,GAAG,yBAAyB,CAAC;AAEzD,IAAI,EAAE,GAA6B,IAAI,CAAC;AAExC,SAAS,MAAM;IACb,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,iBAAiB,CAAC;AACzD,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAgB;IACzC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACvD,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACvD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,KAAK;IACZ,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC;QAC1B,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAC5B,
|
|
1
|
+
{"version":3,"file":"sdk-runtime-state-db.js","sourceRoot":"","sources":["../../../src/utils/local-data-store/sdk-runtime-state-db.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import {
|
|
6
|
+
getRuntimeStateJson,
|
|
7
|
+
resetRuntimeStateDbForTests,
|
|
8
|
+
} from "./sdk-runtime-state-db.js";
|
|
9
|
+
|
|
10
|
+
const LEGACY_JOB_STATE_FILE = path.join("/tmp", "job-state.json");
|
|
11
|
+
|
|
12
|
+
function mockLegacyFileExists(exists: boolean): void {
|
|
13
|
+
const realExistsSync = fs.existsSync.bind(fs);
|
|
14
|
+
vi.spyOn(fs, "existsSync").mockImplementation((filePath) => {
|
|
15
|
+
if (path.resolve(String(filePath)) === LEGACY_JOB_STATE_FILE) {
|
|
16
|
+
return exists;
|
|
17
|
+
}
|
|
18
|
+
return realExistsSync(filePath);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function mockLegacyFileContents(contents: string): void {
|
|
23
|
+
mockLegacyFileExists(true);
|
|
24
|
+
const realReadFileSync = fs.readFileSync.bind(fs);
|
|
25
|
+
vi.spyOn(fs, "readFileSync").mockImplementation((filePath, encoding) => {
|
|
26
|
+
if (path.resolve(String(filePath)) === LEGACY_JOB_STATE_FILE) {
|
|
27
|
+
return contents;
|
|
28
|
+
}
|
|
29
|
+
return realReadFileSync(filePath, encoding);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("sdk-runtime-state-db legacy import", () => {
|
|
34
|
+
let sqlitePath: string;
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
sqlitePath = path.join(
|
|
38
|
+
os.tmpdir(),
|
|
39
|
+
`sdk-runtime-state-${process.pid}-${Date.now()}.sqlite3`
|
|
40
|
+
);
|
|
41
|
+
process.env.SQLITE_DB_PATH = sqlitePath;
|
|
42
|
+
resetRuntimeStateDbForTests();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
resetRuntimeStateDbForTests();
|
|
47
|
+
delete process.env.SQLITE_DB_PATH;
|
|
48
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
49
|
+
try {
|
|
50
|
+
fs.rmSync(`${sqlitePath}${suffix}`);
|
|
51
|
+
} catch {
|
|
52
|
+
// ignore
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
vi.restoreAllMocks();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("marks import complete when the legacy file is absent", () => {
|
|
59
|
+
mockLegacyFileExists(false);
|
|
60
|
+
|
|
61
|
+
getRuntimeStateJson("companyInfo");
|
|
62
|
+
|
|
63
|
+
expect(getRuntimeStateJson<boolean>("_legacyJobStateImported")).toBe(true);
|
|
64
|
+
expect(getRuntimeStateJson("companyInfo")).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("imports legacy keys when the legacy file is valid", () => {
|
|
68
|
+
mockLegacyFileContents(
|
|
69
|
+
JSON.stringify({
|
|
70
|
+
companyInfo: { timezone: "US/Central" },
|
|
71
|
+
mmApiToken: { token: "abc", expiresAt: "2099-01-01T00:00:00.000Z" },
|
|
72
|
+
initialLoadComplete: true,
|
|
73
|
+
databaseLock: { holder: "worker-1" },
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(getRuntimeStateJson<{ timezone: string }>("companyInfo")).toEqual({
|
|
78
|
+
timezone: "US/Central",
|
|
79
|
+
});
|
|
80
|
+
expect(getRuntimeStateJson<{ token: string }>("mmApiToken")).toEqual({
|
|
81
|
+
token: "abc",
|
|
82
|
+
expiresAt: "2099-01-01T00:00:00.000Z",
|
|
83
|
+
});
|
|
84
|
+
expect(getRuntimeStateJson<boolean>("initialLoadComplete")).toBe(true);
|
|
85
|
+
expect(getRuntimeStateJson<{ holder: string }>("databaseLock")).toEqual({
|
|
86
|
+
holder: "worker-1",
|
|
87
|
+
});
|
|
88
|
+
expect(getRuntimeStateJson<boolean>("_legacyJobStateImported")).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("fails fast on corrupt legacy JSON and retries import on next access", () => {
|
|
92
|
+
mockLegacyFileContents("{ invalid json");
|
|
93
|
+
|
|
94
|
+
expect(() => getRuntimeStateJson("companyInfo")).toThrow(SyntaxError);
|
|
95
|
+
|
|
96
|
+
vi.restoreAllMocks();
|
|
97
|
+
mockLegacyFileContents(
|
|
98
|
+
JSON.stringify({ companyInfo: { timezone: "US/Eastern" } })
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
expect(getRuntimeStateJson<{ timezone: string }>("companyInfo")).toEqual({
|
|
102
|
+
timezone: "US/Eastern",
|
|
103
|
+
});
|
|
104
|
+
expect(getRuntimeStateJson<boolean>("_legacyJobStateImported")).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -27,17 +27,24 @@ function getDb(): Database.Database {
|
|
|
27
27
|
if (!db) {
|
|
28
28
|
const filename = dbPath();
|
|
29
29
|
ensureDbDirectory(filename);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
const instance = new Database(filename);
|
|
31
|
+
instance.pragma("journal_mode = WAL");
|
|
32
|
+
instance.pragma("busy_timeout = 5000");
|
|
33
|
+
instance.exec(`
|
|
34
34
|
CREATE TABLE IF NOT EXISTS ${TABLE} (
|
|
35
35
|
key TEXT PRIMARY KEY,
|
|
36
36
|
value TEXT NOT NULL,
|
|
37
37
|
updated_at TEXT NOT NULL
|
|
38
38
|
);
|
|
39
39
|
`);
|
|
40
|
-
|
|
40
|
+
db = instance;
|
|
41
|
+
try {
|
|
42
|
+
importLegacyJobStateFileOnce();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
instance.close();
|
|
45
|
+
db = null;
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
41
48
|
}
|
|
42
49
|
return db;
|
|
43
50
|
}
|
|
@@ -64,35 +71,28 @@ function importLegacyJobStateFileOnce(): void {
|
|
|
64
71
|
return;
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const data = JSON.parse(raw) as Record<string, unknown>;
|
|
74
|
+
if (fs.existsSync(LEGACY_JOB_STATE_FILE)) {
|
|
75
|
+
const raw = fs.readFileSync(LEGACY_JOB_STATE_FILE, "utf-8");
|
|
76
|
+
const data = JSON.parse(raw) as Record<string, unknown>;
|
|
71
77
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
78
|
+
if (data.companyInfo && !readRaw("companyInfo")) {
|
|
79
|
+
writeRaw("companyInfo", JSON.stringify(data.companyInfo));
|
|
80
|
+
}
|
|
81
|
+
if (data.mmApiToken && !readRaw("mmApiToken")) {
|
|
82
|
+
writeRaw("mmApiToken", JSON.stringify(data.mmApiToken));
|
|
83
|
+
}
|
|
84
|
+
if (
|
|
85
|
+
data.initialLoadComplete !== undefined &&
|
|
86
|
+
!readRaw("initialLoadComplete")
|
|
87
|
+
) {
|
|
88
|
+
writeRaw(
|
|
89
|
+
"initialLoadComplete",
|
|
90
|
+
JSON.stringify(data.initialLoadComplete)
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (data.databaseLock && !readRaw("databaseLock")) {
|
|
94
|
+
writeRaw("databaseLock", JSON.stringify(data.databaseLock));
|
|
90
95
|
}
|
|
91
|
-
} catch (error) {
|
|
92
|
-
console.error(
|
|
93
|
-
`Failed to import legacy job state from ${LEGACY_JOB_STATE_FILE}:`,
|
|
94
|
-
error
|
|
95
|
-
);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
writeRaw(LEGACY_IMPORT_FLAG_KEY, JSON.stringify(true));
|