@machinemetrics/mm-erp-sdk 0.9.6-beta.0 → 0.9.6-beta.2
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/migrations/20260617160000_create_sdk_runtime_state_table.d.ts.map +1 -1
- package/dist/migrations/20260617160000_create_sdk_runtime_state_table.js +4 -0
- package/dist/migrations/20260617160000_create_sdk_runtime_state_table.js.map +1 -1
- package/dist/utils/application-initializer.js +5 -5
- package/dist/utils/application-initializer.js.map +1 -1
- 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/migrations/20260617160000_create_sdk_runtime_state_table.ts +4 -0
- package/src/utils/application-initializer.ts +6 -6
- 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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"20260617160000_create_sdk_runtime_state_table.d.ts","sourceRoot":"","sources":["../../src/migrations/20260617160000_create_sdk_runtime_state_table.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAEjC,wBAAsB,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"20260617160000_create_sdk_runtime_state_table.d.ts","sourceRoot":"","sources":["../../src/migrations/20260617160000_create_sdk_runtime_state_table.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAEjC,wBAAsB,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAUlD;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"20260617160000_create_sdk_runtime_state_table.js","sourceRoot":"","sources":["../../src/migrations/20260617160000_create_sdk_runtime_state_table.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC,IAAU;IACjC,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,mBAAmB,EAAE,CAAC,KAAK,EAAE,EAAE;QAC3D,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;QAClC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAU;IACnC,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;AACnD,CAAC"}
|
|
1
|
+
{"version":3,"file":"20260617160000_create_sdk_runtime_state_table.js","sourceRoot":"","sources":["../../src/migrations/20260617160000_create_sdk_runtime_state_table.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC,IAAU;IACjC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;IAC/D,IAAI,MAAM,EAAE,CAAC;QACX,OAAO;IACT,CAAC;IACD,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,mBAAmB,EAAE,CAAC,KAAK,EAAE,EAAE;QAC3D,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;QAClC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAU;IACnC,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;AACnD,CAAC"}
|
|
@@ -18,14 +18,14 @@ export class ApplicationInitializer {
|
|
|
18
18
|
// Load and validate core configuration
|
|
19
19
|
const coreConfig = CoreConfiguration.inst();
|
|
20
20
|
logger.info("Core Configuration loaded:", coreConfig.toSafeLogObject());
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
await SQLiteCoordinator.performStartupCheck();
|
|
24
|
-
logger.info("Database startup checks completed successfully");
|
|
25
|
-
// Run database migrations to ensure all tables exist
|
|
21
|
+
// Migrations first: runtime-state reads (startup lock check) use sdk_runtime_state,
|
|
22
|
+
// which must exist via migrate.latest before any lazy CREATE TABLE IF NOT EXISTS runs.
|
|
26
23
|
logger.info("Running database migrations...");
|
|
27
24
|
await ApplicationInitializer.runMigrations();
|
|
28
25
|
logger.info("Database migrations completed successfully");
|
|
26
|
+
logger.info("Performing database startup checks...");
|
|
27
|
+
await SQLiteCoordinator.performStartupCheck();
|
|
28
|
+
logger.info("Database startup checks completed successfully");
|
|
29
29
|
// Loads company info from MM into memory + SQLite runtime state (timezone, locationRef, companyId).
|
|
30
30
|
// Retries until success; failure aborts init. Confirms the MM API token works for /accounts/current.
|
|
31
31
|
await ensureCompanyInfo();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"application-initializer.js","sourceRoot":"","sources":["../../src/utils/application-initializer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,MAAM,EAAE,MAAM,yCAAyC,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wDAAwD,CAAC;AAC3F,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;AAC/E,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,MAAM,MAAM,gBAAgB,CAAC;AAEpC;;GAEG;AACH,MAAM,OAAO,sBAAsB;IACjC;;;OAGG;IACI,MAAM,CAAC,KAAK,CAAC,UAAU;QAC5B,IAAI,CAAC;YACH,MAAM,CAAC,IAAI,CACT,8FAA8F,CAC/F,CAAC;YAEF,uCAAuC;YACvC,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,EAAE,CAAC;YAC5C,MAAM,CAAC,IAAI,CAAC,4BAA4B,EAAE,UAAU,CAAC,eAAe,EAAE,CAAC,CAAC;YAExE,
|
|
1
|
+
{"version":3,"file":"application-initializer.js","sourceRoot":"","sources":["../../src/utils/application-initializer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,MAAM,EAAE,MAAM,yCAAyC,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wDAAwD,CAAC;AAC3F,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;AAC/E,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,MAAM,MAAM,gBAAgB,CAAC;AAEpC;;GAEG;AACH,MAAM,OAAO,sBAAsB;IACjC;;;OAGG;IACI,MAAM,CAAC,KAAK,CAAC,UAAU;QAC5B,IAAI,CAAC;YACH,MAAM,CAAC,IAAI,CACT,8FAA8F,CAC/F,CAAC;YAEF,uCAAuC;YACvC,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,EAAE,CAAC;YAC5C,MAAM,CAAC,IAAI,CAAC,4BAA4B,EAAE,UAAU,CAAC,eAAe,EAAE,CAAC,CAAC;YAExE,oFAAoF;YACpF,uFAAuF;YACvF,MAAM,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;YAC9C,MAAM,sBAAsB,CAAC,aAAa,EAAE,CAAC;YAC7C,MAAM,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;YAE1D,MAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;YACrD,MAAM,iBAAiB,CAAC,mBAAmB,EAAE,CAAC;YAC9C,MAAM,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;YAE9D,oGAAoG;YACpG,qGAAqG;YACrG,MAAM,iBAAiB,EAAE,CAAC;YAE1B,MAAM,CAAC,IAAI,CACT,0GAA0G,CAC3G,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,2CAA2C,EAAE,KAAK,CAAC,CAAC;YACjE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED;;OAEG;IACK,MAAM,CAAC,KAAK,CAAC,aAAa;QAChC,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC;YACjD,MAAM,KAAK,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC;QACrB,CAAC;IACH,CAAC;CACF"}
|
|
@@ -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
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Knex } from "knex";
|
|
2
2
|
|
|
3
3
|
export async function up(knex: Knex): Promise<void> {
|
|
4
|
+
const exists = await knex.schema.hasTable("sdk_runtime_state");
|
|
5
|
+
if (exists) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
4
8
|
await knex.schema.createTable("sdk_runtime_state", (table) => {
|
|
5
9
|
table.string("key").primary();
|
|
6
10
|
table.text("value").notNullable();
|
|
@@ -23,16 +23,16 @@ export class ApplicationInitializer {
|
|
|
23
23
|
const coreConfig = CoreConfiguration.inst();
|
|
24
24
|
logger.info("Core Configuration loaded:", coreConfig.toSafeLogObject());
|
|
25
25
|
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
await SQLiteCoordinator.performStartupCheck();
|
|
29
|
-
logger.info("Database startup checks completed successfully");
|
|
30
|
-
|
|
31
|
-
// Run database migrations to ensure all tables exist
|
|
26
|
+
// Migrations first: runtime-state reads (startup lock check) use sdk_runtime_state,
|
|
27
|
+
// which must exist via migrate.latest before any lazy CREATE TABLE IF NOT EXISTS runs.
|
|
32
28
|
logger.info("Running database migrations...");
|
|
33
29
|
await ApplicationInitializer.runMigrations();
|
|
34
30
|
logger.info("Database migrations completed successfully");
|
|
35
31
|
|
|
32
|
+
logger.info("Performing database startup checks...");
|
|
33
|
+
await SQLiteCoordinator.performStartupCheck();
|
|
34
|
+
logger.info("Database startup checks completed successfully");
|
|
35
|
+
|
|
36
36
|
// Loads company info from MM into memory + SQLite runtime state (timezone, locationRef, companyId).
|
|
37
37
|
// Retries until success; failure aborts init. Confirms the MM API token works for /accounts/current.
|
|
38
38
|
await ensureCompanyInfo();
|
|
@@ -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));
|