@machinemetrics/mm-erp-sdk 0.9.5 → 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.
Files changed (31) hide show
  1. package/dist/migrations/20260617160000_create_sdk_runtime_state_table.d.ts +4 -0
  2. package/dist/migrations/20260617160000_create_sdk_runtime_state_table.d.ts.map +1 -0
  3. package/dist/migrations/20260617160000_create_sdk_runtime_state_table.js +11 -0
  4. package/dist/migrations/20260617160000_create_sdk_runtime_state_table.js.map +1 -0
  5. package/dist/services/mm-api-service/company-info.d.ts.map +1 -1
  6. package/dist/services/mm-api-service/company-info.js +4 -5
  7. package/dist/services/mm-api-service/company-info.js.map +1 -1
  8. package/dist/services/mm-api-service/token-mgr.d.ts +1 -1
  9. package/dist/services/mm-api-service/token-mgr.js +1 -1
  10. package/dist/utils/application-initializer.js +1 -1
  11. package/dist/utils/application-initializer.js.map +1 -1
  12. package/dist/utils/local-data-store/database-lock.d.ts.map +1 -1
  13. package/dist/utils/local-data-store/database-lock.js +32 -41
  14. package/dist/utils/local-data-store/database-lock.js.map +1 -1
  15. package/dist/utils/local-data-store/jobs-shared-data.d.ts +1 -11
  16. package/dist/utils/local-data-store/jobs-shared-data.d.ts.map +1 -1
  17. package/dist/utils/local-data-store/jobs-shared-data.js +13 -71
  18. package/dist/utils/local-data-store/jobs-shared-data.js.map +1 -1
  19. package/dist/utils/local-data-store/sdk-runtime-state-db.d.ts +15 -0
  20. package/dist/utils/local-data-store/sdk-runtime-state-db.d.ts.map +1 -0
  21. package/dist/utils/local-data-store/sdk-runtime-state-db.js +123 -0
  22. package/dist/utils/local-data-store/sdk-runtime-state-db.js.map +1 -0
  23. package/package.json +5 -2
  24. package/src/migrations/20260617160000_create_sdk_runtime_state_table.ts +13 -0
  25. package/src/services/mm-api-service/company-info.ts +4 -5
  26. package/src/services/mm-api-service/token-mgr.ts +1 -1
  27. package/src/utils/application-initializer.ts +1 -1
  28. package/src/utils/local-data-store/database-lock.ts +38 -47
  29. package/src/utils/local-data-store/jobs-shared-data.ts +16 -76
  30. package/src/utils/local-data-store/sdk-runtime-state-db.test.ts +106 -0
  31. package/src/utils/local-data-store/sdk-runtime-state-db.ts +141 -0
@@ -0,0 +1,4 @@
1
+ import type { Knex } from "knex";
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
4
+ //# sourceMappingURL=20260617160000_create_sdk_runtime_state_table.d.ts.map
@@ -0,0 +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,CAMlD;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpD"}
@@ -0,0 +1,11 @@
1
+ export async function up(knex) {
2
+ await knex.schema.createTable("sdk_runtime_state", (table) => {
3
+ table.string("key").primary();
4
+ table.text("value").notNullable();
5
+ table.string("updated_at").notNullable();
6
+ });
7
+ }
8
+ export async function down(knex) {
9
+ await knex.schema.dropTable("sdk_runtime_state");
10
+ }
11
+ //# sourceMappingURL=20260617160000_create_sdk_runtime_state_table.js.map
@@ -0,0 +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 +1 @@
1
- {"version":3,"file":"company-info.d.ts","sourceRoot":"","sources":["../../../src/services/mm-api-service/company-info.ts"],"names":[],"mappings":"AA8BA,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CACzB;AA4BD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,QAAa,OAAO,CAAC,WAAW,CAsDrE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QAAa,OAAO,CAAC,WAAW,CAS1D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAC5B,SAAQ,uBAGP,KACA,OAAO,CAAC,IAAI,CAkBd,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,WAAW,CAElD;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,CAEpD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;GAGG;AACH,wBAAgB,mCAAmC,IAAI,MAAM,CAG5D;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,IAAI,MAAM,CAErD;AAED,4FAA4F;AAC5F,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD;AAED,qFAAqF;AACrF,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C"}
1
+ {"version":3,"file":"company-info.d.ts","sourceRoot":"","sources":["../../../src/services/mm-api-service/company-info.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CACzB;AA4BD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,QAAa,OAAO,CAAC,WAAW,CAsDrE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QAAa,OAAO,CAAC,WAAW,CAS1D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAC5B,SAAQ,uBAGP,KACA,OAAO,CAAC,IAAI,CAkBd,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,WAAW,CAElD;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,CAEpD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;GAGG;AACH,wBAAgB,mCAAmC,IAAI,MAAM,CAG5D;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,IAAI,MAAM,CAErD;AAED,4FAA4F;AAC5F,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD;AAED,qFAAqF;AACrF,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C"}
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * Company info from MM (`/accounts/current`) is loaded during SDK startup
3
3
  * (`ApplicationInitializer.initialize` → {@link ensureCompanyInfo}), written to shared
4
- * job-state, and read by synchronous {@link getCachedCompanyInfo} and related getters.
4
+ * SQLite runtime state, and read by synchronous {@link getCachedCompanyInfo} and related getters.
5
5
  *
6
- * Persisted data is scoped to whatever filesystem sees the job-state file (default under `/tmp`):
7
- * e.g. one container’s filesystem unless that path is mounted shared. Company fields are treated
6
+ * Persisted data lives in `sdk_runtime_state` (same SQLite file as `SQLITE_DB_PATH`). Company fields are treated
8
7
  * as stable for the deployment; refreshing them if MM changes is the SDK’s responsibility when we
9
8
  * add that behavior—not connector boilerplate.
10
9
  *
@@ -32,7 +31,7 @@ function hydrateCacheFromDiskIfPresent() {
32
31
  return null;
33
32
  }
34
33
  /**
35
- * Resolves company info for synchronous readers: in-memory, then persisted job-state.
34
+ * Resolves company info for synchronous readers: in-memory, then persisted SQLite runtime state.
36
35
  * @throws Error if neither is available (initialization / API fetch has not populated data).
37
36
  */
38
37
  function resolveCompanyInfoOrThrow() {
@@ -43,7 +42,7 @@ function resolveCompanyInfoOrThrow() {
43
42
  if (fromDisk) {
44
43
  return fromDisk;
45
44
  }
46
- throw new Error("Company info is not available: run ApplicationInitializer.initialize() or ensureCompanyInfo() in a process with API access, or ensure job-state.json contains companyInfo from a prior fetch");
45
+ throw new Error("Company info is not available: run ApplicationInitializer.initialize() or ensureCompanyInfo() in a process with API access, or ensure sdk_runtime_state contains companyInfo from a prior fetch");
47
46
  }
48
47
  /**
49
48
  * Fetches company info from MM, updates in-memory cache and persisted job-state.
@@ -1 +1 @@
1
- {"version":3,"file":"company-info.js","sourceRoot":"","sources":["../../../src/services/mm-api-service/company-info.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"company-info.js","sourceRoot":"","sources":["../../../src/services/mm-api-service/company-info.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Manages the MM API token with caching and expiration checking
3
- * Tokens are cached across jobs and job instances in local-data-store/jobs-shared-data.ts
3
+ * Tokens are cached across jobs and job instances in SQLite runtime state (jobs-shared-data.ts).
4
4
  * @jest-environment node
5
5
  */
6
6
  export declare class MMTokenManager {
@@ -4,7 +4,7 @@ import { CoreConfiguration } from "../data-sync-service/configuration-manager.js
4
4
  import { logger } from "../reporting-service/index.js";
5
5
  /**
6
6
  * Manages the MM API token with caching and expiration checking
7
- * Tokens are cached across jobs and job instances in local-data-store/jobs-shared-data.ts
7
+ * Tokens are cached across jobs and job instances in SQLite runtime state (jobs-shared-data.ts).
8
8
  * @jest-environment node
9
9
  */
10
10
  export class MMTokenManager {
@@ -26,7 +26,7 @@ export class ApplicationInitializer {
26
26
  logger.info("Running database migrations...");
27
27
  await ApplicationInitializer.runMigrations();
28
28
  logger.info("Database migrations completed successfully");
29
- // Loads company info from MM into memory + job-state (timezone, locationRef, companyId).
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();
32
32
  logger.info("\n================================APPLICATION INITIALIZATION COMPLETED================================\n");
@@ -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,kCAAkC;YAClC,MAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;YACrD,MAAM,iBAAiB,CAAC,mBAAmB,EAAE,CAAC;YAC9C,MAAM,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;YAE9D,qDAAqD;YACrD,MAAM,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;YAC9C,MAAM,sBAAsB,CAAC,aAAa,EAAE,CAAC;YAC7C,MAAM,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;YAE1D,yFAAyF;YACzF,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"}
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,kCAAkC;YAClC,MAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;YACrD,MAAM,iBAAiB,CAAC,mBAAmB,EAAE,CAAC;YAC9C,MAAM,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;YAE9D,qDAAqD;YACrD,MAAM,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;YAC9C,MAAM,sBAAsB,CAAC,aAAa,EAAE,CAAC;YAC7C,MAAM,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;YAE1D,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"}
@@ -1 +1 @@
1
- {"version":3,"file":"database-lock.d.ts","sourceRoot":"","sources":["../../../src/utils/local-data-store/database-lock.ts"],"names":[],"mappings":"AAKA,UAAU,YAAY;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,QAAO,YASlC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,aAAa,MAAM,KAAG,OAmBzD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,aAAa,MAAM,KAAG,OAmBzD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,QAAO,OAGtC,CAAC"}
1
+ {"version":3,"file":"database-lock.d.ts","sourceRoot":"","sources":["../../../src/utils/local-data-store/database-lock.ts"],"names":[],"mappings":"AAQA,UAAU,YAAY;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAYD;;;GAGG;AACH,eAAO,MAAM,eAAe,QAAO,YAElC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,aAAa,MAAM,KAAG,OAczD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,aAAa,MAAM,KAAG,OAUzD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,QAAO,OAEtC,CAAC"}
@@ -1,15 +1,19 @@
1
- import { readDatabaseLockState, writeDatabaseLockState, } from "./jobs-shared-data.js";
1
+ import { getRuntimeStateJson, setRuntimeStateJson, withImmediateTransaction, } from "./sdk-runtime-state-db.js";
2
+ const DATABASE_LOCK_KEY = "databaseLock";
3
+ const defaultLock = () => ({
4
+ isLocked: false,
5
+ lockedBy: "",
6
+ lockedAt: null,
7
+ });
8
+ function readLock() {
9
+ return getRuntimeStateJson(DATABASE_LOCK_KEY) ?? defaultLock();
10
+ }
2
11
  /**
3
12
  * Gets the current database lock state
4
13
  * @returns The current database lock state
5
14
  */
6
15
  export const getDatabaseLock = () => {
7
- const data = readDatabaseLockState();
8
- return (data.databaseLock ?? {
9
- isLocked: false,
10
- lockedBy: "",
11
- lockedAt: null,
12
- });
16
+ return readLock();
13
17
  };
14
18
  /**
15
19
  * Attempts to acquire the database lock
@@ -17,22 +21,18 @@ export const getDatabaseLock = () => {
17
21
  * @returns true if lock was acquired, false if database is already locked
18
22
  */
19
23
  export const acquireDatabaseLock = (processName) => {
20
- const data = readDatabaseLockState();
21
- const currentLock = data.databaseLock ?? {
22
- isLocked: false,
23
- lockedBy: "",
24
- lockedAt: null,
25
- };
26
- if (currentLock.isLocked) {
27
- return false;
28
- }
29
- data.databaseLock = {
30
- isLocked: true,
31
- lockedBy: processName,
32
- lockedAt: new Date().toISOString(),
33
- };
34
- writeDatabaseLockState(data);
35
- return true;
24
+ return withImmediateTransaction(() => {
25
+ const currentLock = readLock();
26
+ if (currentLock.isLocked) {
27
+ return false;
28
+ }
29
+ setRuntimeStateJson(DATABASE_LOCK_KEY, {
30
+ isLocked: true,
31
+ lockedBy: processName,
32
+ lockedAt: new Date().toISOString(),
33
+ });
34
+ return true;
35
+ });
36
36
  };
37
37
  /**
38
38
  * Releases the database lock
@@ -40,29 +40,20 @@ export const acquireDatabaseLock = (processName) => {
40
40
  * @returns true if lock was released, false if process doesn't own the lock
41
41
  */
42
42
  export const releaseDatabaseLock = (processName) => {
43
- const data = readDatabaseLockState();
44
- const currentLock = data.databaseLock ?? {
45
- isLocked: false,
46
- lockedBy: "",
47
- lockedAt: null,
48
- };
49
- if (!currentLock.isLocked || currentLock.lockedBy !== processName) {
50
- return false;
51
- }
52
- data.databaseLock = {
53
- isLocked: false,
54
- lockedBy: "",
55
- lockedAt: null,
56
- };
57
- writeDatabaseLockState(data);
58
- return true;
43
+ return withImmediateTransaction(() => {
44
+ const currentLock = readLock();
45
+ if (!currentLock.isLocked || currentLock.lockedBy !== processName) {
46
+ return false;
47
+ }
48
+ setRuntimeStateJson(DATABASE_LOCK_KEY, defaultLock());
49
+ return true;
50
+ });
59
51
  };
60
52
  /**
61
53
  * Checks if the database is available for use
62
54
  * @returns true if database is available, false if locked
63
55
  */
64
56
  export const isDatabaseAvailable = () => {
65
- const lock = getDatabaseLock();
66
- return !lock.isLocked;
57
+ return !readLock().isLocked;
67
58
  };
68
59
  //# sourceMappingURL=database-lock.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"database-lock.js","sourceRoot":"","sources":["../../../src/utils/local-data-store/database-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAQ/B;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAiB,EAAE;IAChD,MAAM,IAAI,GAAG,qBAAqB,EAAE,CAAC;IACrC,OAAO,CACJ,IAAI,CAAC,YAA6B,IAAI;QACrC,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,EAAE;QACZ,QAAQ,EAAE,IAAI;KACf,CACF,CAAC;AACJ,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,WAAmB,EAAW,EAAE;IAClE,MAAM,IAAI,GAAG,qBAAqB,EAAE,CAAC;IACrC,MAAM,WAAW,GAAI,IAAI,CAAC,YAA6B,IAAI;QACzD,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,EAAE;QACZ,QAAQ,EAAE,IAAI;KACf,CAAC;IAEF,IAAI,WAAW,CAAC,QAAQ,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC,YAAY,GAAG;QAClB,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,WAAW;QACrB,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACnC,CAAC;IACF,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,WAAmB,EAAW,EAAE;IAClE,MAAM,IAAI,GAAG,qBAAqB,EAAE,CAAC;IACrC,MAAM,WAAW,GAAI,IAAI,CAAC,YAA6B,IAAI;QACzD,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,EAAE;QACZ,QAAQ,EAAE,IAAI;KACf,CAAC;IAEF,IAAI,CAAC,WAAW,CAAC,QAAQ,IAAI,WAAW,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;QAClE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC,YAAY,GAAG;QAClB,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,EAAE;QACZ,QAAQ,EAAE,IAAI;KACf,CAAC;IACF,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAY,EAAE;IAC/C,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;IAC/B,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC;AACxB,CAAC,CAAC"}
1
+ {"version":3,"file":"database-lock.js","sourceRoot":"","sources":["../../../src/utils/local-data-store/database-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,2BAA2B,CAAC;AAEnC,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAQzC,MAAM,WAAW,GAAG,GAAiB,EAAE,CAAC,CAAC;IACvC,QAAQ,EAAE,KAAK;IACf,QAAQ,EAAE,EAAE;IACZ,QAAQ,EAAE,IAAI;CACf,CAAC,CAAC;AAEH,SAAS,QAAQ;IACf,OAAO,mBAAmB,CAAe,iBAAiB,CAAC,IAAI,WAAW,EAAE,CAAC;AAC/E,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAiB,EAAE;IAChD,OAAO,QAAQ,EAAE,CAAC;AACpB,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,WAAmB,EAAW,EAAE;IAClE,OAAO,wBAAwB,CAAC,GAAG,EAAE;QACnC,MAAM,WAAW,GAAG,QAAQ,EAAE,CAAC;QAC/B,IAAI,WAAW,CAAC,QAAQ,EAAE,CAAC;YACzB,OAAO,KAAK,CAAC;QACf,CAAC;QAED,mBAAmB,CAAC,iBAAiB,EAAE;YACrC,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,WAAW;YACrB,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACnC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,WAAmB,EAAW,EAAE;IAClE,OAAO,wBAAwB,CAAC,GAAG,EAAE;QACnC,MAAM,WAAW,GAAG,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC,WAAW,CAAC,QAAQ,IAAI,WAAW,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;YAClE,OAAO,KAAK,CAAC;QACf,CAAC;QAED,mBAAmB,CAAC,iBAAiB,EAAE,WAAW,EAAE,CAAC,CAAC;QACtD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAY,EAAE;IAC/C,OAAO,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC;AAC9B,CAAC,CAAC"}
@@ -4,19 +4,9 @@ import type { CompanyInfo } from "../../services/mm-api-service/company-info.js"
4
4
  */
5
5
  export declare const readPersistedCompanyInfo: () => CompanyInfo | null;
6
6
  /**
7
- * Persists company info for other processes on the same host (same job-state file).
7
+ * Persists company info for other processes sharing the same SQLite database file.
8
8
  */
9
9
  export declare const writePersistedCompanyInfo: (info: CompanyInfo) => void;
10
- /**
11
- * Reads the database lock state from the shared storage file
12
- * @returns The data stored in the file
13
- */
14
- export declare const readDatabaseLockState: () => Record<string, unknown>;
15
- /**
16
- * Writes the database lock state to the shared storage file
17
- * @param data The lock state data to write
18
- */
19
- export declare const writeDatabaseLockState: (data: Record<string, unknown>) => void;
20
10
  export declare const getInitialLoadComplete: () => boolean;
21
11
  export declare const setInitialLoadComplete: (complete: boolean) => void;
22
12
  interface CachedToken {
@@ -1 +1 @@
1
- {"version":3,"file":"jobs-shared-data.d.ts","sourceRoot":"","sources":["../../../src/utils/local-data-store/jobs-shared-data.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+CAA+C,CAAC;AA2DjF;;GAEG;AACH,eAAO,MAAM,wBAAwB,QAAO,WAAW,GAAG,IAIzD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,yBAAyB,GAAI,MAAM,WAAW,KAAG,IAI7D,CAAC;AAGF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAE9D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,IAEtE,CAAC;AAGF,eAAO,MAAM,sBAAsB,QAAO,OAGzC,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,UAAU,OAAO,KAAG,IAI1D,CAAC;AAEF,UAAU,WAAW;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QAAO,WAAW,GAAG,IAGjD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,WAAW,WAAW,KAAG,IAIzD,CAAC"}
1
+ {"version":3,"file":"jobs-shared-data.d.ts","sourceRoot":"","sources":["../../../src/utils/local-data-store/jobs-shared-data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+CAA+C,CAAC;AA6BjF;;GAEG;AACH,eAAO,MAAM,wBAAwB,QAAO,WAAW,GAAG,IAGzD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,yBAAyB,GAAI,MAAM,WAAW,KAAG,IAE7D,CAAC;AAEF,eAAO,MAAM,sBAAsB,QAAO,OAEzC,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,UAAU,OAAO,KAAG,IAE1D,CAAC;AAEF,UAAU,WAAW;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QAAO,WAAW,GAAG,IAEjD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,WAAW,WAAW,KAAG,IAEzD,CAAC"}
@@ -1,13 +1,12 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { mkdirSync } from "fs";
1
+ import { getRuntimeStateJson, setRuntimeStateJson, } from "./sdk-runtime-state-db.js";
4
2
  /**
5
- * This file contains the logic for storing and retrieving data from the job state file.
6
- * It is used to store data that is shared between jobs, and (more importantly) across job instances.
7
- * Company info (timezone, locationRef, companyId) is persisted here so processes that share this
8
- * file path (same host /tmp, or a shared volume) can read it after SDK init; offset is not stored.
3
+ * Cross-process runtime state shared between Bree workers (company info, token cache,
4
+ * initial-load flag). Stored in SQLite (`sdk_runtime_state`) keyed by SQLITE_DB_PATH so
5
+ * each key is updated independently under WAL no whole-file read-modify-write races.
9
6
  */
10
7
  const COMPANY_INFO_STORAGE_KEY = "companyInfo";
8
+ const INITIAL_LOAD_COMPLETE_KEY = "initialLoadComplete";
9
+ const MM_API_TOKEN_KEY = "mmApiToken";
11
10
  function isPersistedCompanyInfo(value) {
12
11
  if (!value || typeof value !== "object") {
13
12
  return false;
@@ -18,94 +17,37 @@ function isPersistedCompanyInfo(value) {
18
17
  typeof o.locationRef === "string" &&
19
18
  typeof o.companyId === "string");
20
19
  }
21
- const STORAGE_FILE = path.join("/tmp", "job-state.json");
22
- // Ensure parent directory exists
23
- const parentDir = path.dirname(STORAGE_FILE);
24
- try {
25
- mkdirSync(parentDir, { recursive: true });
26
- }
27
- catch (error) {
28
- if (error.code !== "EEXIST") {
29
- throw error;
30
- }
31
- }
32
- //#region Non-exported functions
33
- const ensureStorageFile = () => {
34
- if (!fs.existsSync(STORAGE_FILE)) {
35
- fs.writeFileSync(STORAGE_FILE, JSON.stringify({}), "utf-8");
36
- }
37
- };
38
- const readStorage = () => {
39
- ensureStorageFile();
40
- try {
41
- return JSON.parse(fs.readFileSync(STORAGE_FILE, "utf-8"));
42
- }
43
- catch (error) {
44
- console.error(`Failed to read storage from ${STORAGE_FILE}:`, error);
45
- return {};
46
- }
47
- };
48
- const writeStorage = (data) => {
49
- ensureStorageFile();
50
- fs.writeFileSync(STORAGE_FILE, JSON.stringify(data, null, 2), "utf-8");
51
- };
52
- //#endregion
53
20
  /**
54
21
  * Reads company info written by initialization (or a prior API fetch) for use across processes.
55
22
  */
56
23
  export const readPersistedCompanyInfo = () => {
57
- const data = readStorage();
58
- const raw = data[COMPANY_INFO_STORAGE_KEY];
24
+ const raw = getRuntimeStateJson(COMPANY_INFO_STORAGE_KEY);
59
25
  return isPersistedCompanyInfo(raw) ? raw : null;
60
26
  };
61
27
  /**
62
- * Persists company info for other processes on the same host (same job-state file).
28
+ * Persists company info for other processes sharing the same SQLite database file.
63
29
  */
64
30
  export const writePersistedCompanyInfo = (info) => {
65
- const data = readStorage();
66
- data[COMPANY_INFO_STORAGE_KEY] = info;
67
- writeStorage(data);
68
- };
69
- //#region Database lock storage functions
70
- /**
71
- * Reads the database lock state from the shared storage file
72
- * @returns The data stored in the file
73
- */
74
- export const readDatabaseLockState = () => {
75
- return readStorage();
76
- };
77
- /**
78
- * Writes the database lock state to the shared storage file
79
- * @param data The lock state data to write
80
- */
81
- export const writeDatabaseLockState = (data) => {
82
- writeStorage(data);
31
+ setRuntimeStateJson(COMPANY_INFO_STORAGE_KEY, info);
83
32
  };
84
- //#endregion
85
33
  export const getInitialLoadComplete = () => {
86
- const data = readStorage();
87
- return data.initialLoadComplete ?? false;
34
+ return getRuntimeStateJson(INITIAL_LOAD_COMPLETE_KEY) ?? false;
88
35
  };
89
36
  export const setInitialLoadComplete = (complete) => {
90
- const data = readStorage();
91
- data.initialLoadComplete = complete;
92
- writeStorage(data);
37
+ setRuntimeStateJson(INITIAL_LOAD_COMPLETE_KEY, complete);
93
38
  };
94
39
  /**
95
40
  * Gets the cached MM API token and its expiration
96
41
  * @returns The cached token and expiration or null if not found
97
42
  */
98
43
  export const getCachedMMToken = () => {
99
- const data = readStorage();
100
- return data.mmApiToken ?? null;
44
+ return getRuntimeStateJson(MM_API_TOKEN_KEY);
101
45
  };
102
46
  /**
103
47
  * Sets the MM API token and its expiration in the cache
104
48
  * @param tokenData The token and expiration to cache
105
49
  */
106
50
  export const setCachedMMToken = (tokenData) => {
107
- const data = readStorage();
108
- data.mmApiToken = tokenData;
109
- writeStorage(data);
51
+ setRuntimeStateJson(MM_API_TOKEN_KEY, tokenData);
110
52
  };
111
53
  //# sourceMappingURL=jobs-shared-data.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"jobs-shared-data.js","sourceRoot":"","sources":["../../../src/utils/local-data-store/jobs-shared-data.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"jobs-shared-data.js","sourceRoot":"","sources":["../../../src/utils/local-data-store/jobs-shared-data.ts"],"names":[],"mappings":"AACA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,2BAA2B,CAAC;AAEnC;;;;GAIG;AAEH,MAAM,wBAAwB,GAAG,aAAa,CAAC;AAC/C,MAAM,yBAAyB,GAAG,qBAAqB,CAAC;AACxD,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAEtC,SAAS,sBAAsB,CAAC,KAAc;IAC5C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACxC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,CAAC,GAAG,KAAgC,CAAC;IAC3C,OAAO,CACL,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ;QAC9B,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;QACrB,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ;QACjC,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,CAChC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAuB,EAAE;IAC/D,MAAM,GAAG,GAAG,mBAAmB,CAAU,wBAAwB,CAAC,CAAC;IACnE,OAAO,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AAClD,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,IAAiB,EAAQ,EAAE;IACnE,mBAAmB,CAAC,wBAAwB,EAAE,IAAI,CAAC,CAAC;AACtD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAY,EAAE;IAClD,OAAO,mBAAmB,CAAU,yBAAyB,CAAC,IAAI,KAAK,CAAC;AAC1E,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,QAAiB,EAAQ,EAAE;IAChE,mBAAmB,CAAC,yBAAyB,EAAE,QAAQ,CAAC,CAAC;AAC3D,CAAC,CAAC;AAOF;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAuB,EAAE;IACvD,OAAO,mBAAmB,CAAc,gBAAgB,CAAC,CAAC;AAC5D,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,SAAsB,EAAQ,EAAE;IAC/D,mBAAmB,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC;AACnD,CAAC,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Reads a JSON value for `key`, or null when unset.
3
+ */
4
+ export declare function getRuntimeStateJson<T>(key: string): T | null;
5
+ /**
6
+ * Upserts a JSON-serializable value for `key` without touching other keys.
7
+ */
8
+ export declare function setRuntimeStateJson(key: string, value: unknown): void;
9
+ /**
10
+ * Runs `fn` inside BEGIN IMMEDIATE so cross-process writers serialize safely.
11
+ */
12
+ export declare function withImmediateTransaction<T>(fn: () => T): T;
13
+ /** Visible for integration tests that need an isolated database file. */
14
+ export declare function resetRuntimeStateDbForTests(): void;
15
+ //# sourceMappingURL=sdk-runtime-state-db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk-runtime-state-db.d.ts","sourceRoot":"","sources":["../../../src/utils/local-data-store/sdk-runtime-state-db.ts"],"names":[],"mappings":"AAoGA;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAM5D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAErE;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAW1D;AAED,yEAAyE;AACzE,wBAAgB,2BAA2B,IAAI,IAAI,CAKlD"}
@@ -0,0 +1,123 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import Database from "better-sqlite3";
4
+ const TABLE = "sdk_runtime_state";
5
+ const LEGACY_JOB_STATE_FILE = path.join("/tmp", "job-state.json");
6
+ const LEGACY_IMPORT_FLAG_KEY = "_legacyJobStateImported";
7
+ let db = null;
8
+ function dbPath() {
9
+ return process.env.SQLITE_DB_PATH || "./local.sqlite3";
10
+ }
11
+ function ensureDbDirectory(filename) {
12
+ const parentDir = path.dirname(path.resolve(filename));
13
+ try {
14
+ fs.mkdirSync(parentDir, { recursive: true });
15
+ }
16
+ catch (error) {
17
+ if (error.code !== "EEXIST") {
18
+ throw error;
19
+ }
20
+ }
21
+ }
22
+ function getDb() {
23
+ if (!db) {
24
+ const filename = dbPath();
25
+ ensureDbDirectory(filename);
26
+ const instance = new Database(filename);
27
+ instance.pragma("journal_mode = WAL");
28
+ instance.pragma("busy_timeout = 5000");
29
+ instance.exec(`
30
+ CREATE TABLE IF NOT EXISTS ${TABLE} (
31
+ key TEXT PRIMARY KEY,
32
+ value TEXT NOT NULL,
33
+ updated_at TEXT NOT NULL
34
+ );
35
+ `);
36
+ db = instance;
37
+ try {
38
+ importLegacyJobStateFileOnce();
39
+ }
40
+ catch (error) {
41
+ instance.close();
42
+ db = null;
43
+ throw error;
44
+ }
45
+ }
46
+ return db;
47
+ }
48
+ function readRaw(key) {
49
+ const row = getDb()
50
+ .prepare(`SELECT value FROM ${TABLE} WHERE key = ?`)
51
+ .get(key);
52
+ return row?.value ?? null;
53
+ }
54
+ function writeRaw(key, value) {
55
+ const updatedAt = new Date().toISOString();
56
+ getDb()
57
+ .prepare(`INSERT INTO ${TABLE} (key, value, updated_at) VALUES (?, ?, ?)
58
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`)
59
+ .run(key, value, updatedAt);
60
+ }
61
+ function importLegacyJobStateFileOnce() {
62
+ if (readRaw(LEGACY_IMPORT_FLAG_KEY)) {
63
+ return;
64
+ }
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));
80
+ }
81
+ }
82
+ writeRaw(LEGACY_IMPORT_FLAG_KEY, JSON.stringify(true));
83
+ }
84
+ /**
85
+ * Reads a JSON value for `key`, or null when unset.
86
+ */
87
+ export function getRuntimeStateJson(key) {
88
+ const raw = readRaw(key);
89
+ if (raw === null) {
90
+ return null;
91
+ }
92
+ return JSON.parse(raw);
93
+ }
94
+ /**
95
+ * Upserts a JSON-serializable value for `key` without touching other keys.
96
+ */
97
+ export function setRuntimeStateJson(key, value) {
98
+ writeRaw(key, JSON.stringify(value));
99
+ }
100
+ /**
101
+ * Runs `fn` inside BEGIN IMMEDIATE so cross-process writers serialize safely.
102
+ */
103
+ export function withImmediateTransaction(fn) {
104
+ const database = getDb();
105
+ database.prepare("BEGIN IMMEDIATE").run();
106
+ try {
107
+ const result = fn();
108
+ database.prepare("COMMIT").run();
109
+ return result;
110
+ }
111
+ catch (error) {
112
+ database.prepare("ROLLBACK").run();
113
+ throw error;
114
+ }
115
+ }
116
+ /** Visible for integration tests that need an isolated database file. */
117
+ export function resetRuntimeStateDbForTests() {
118
+ if (db) {
119
+ db.close();
120
+ db = null;
121
+ }
122
+ }
123
+ //# sourceMappingURL=sdk-runtime-state-db.js.map
@@ -0,0 +1 @@
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,7 +1,7 @@
1
1
  {
2
2
  "name": "@machinemetrics/mm-erp-sdk",
3
3
  "description": "A library for syncing data between MachineMetrics and ERP systems",
4
- "version": "0.9.5",
4
+ "version": "0.9.6-beta.1",
5
5
  "license": "MIT",
6
6
  "author": "machinemetrics",
7
7
  "main": "dist/index.js",
@@ -19,7 +19,8 @@
19
19
  "start": "tsc --project tsconfig.build.json --watch",
20
20
  "type-check": "tsc --noEmit",
21
21
  "test": "vitest run",
22
- "test:watch": "vitest"
22
+ "test:watch": "vitest",
23
+ "test:concurrency": "RUN_STATE_CONCURRENCY_IT=1 vitest run test/concurrency"
23
24
  },
24
25
  "dependencies": {
25
26
  "@azure/msal-node": "^2.12.0",
@@ -44,6 +45,8 @@
44
45
  "odbc": "^2.4.8"
45
46
  },
46
47
  "devDependencies": {
48
+ "@rollup/rollup-linux-x64-gnu": "^4.62.0",
49
+ "@types/better-sqlite3": "^7.6.13",
47
50
  "@types/json-stable-stringify": "^1.1.0",
48
51
  "@types/lodash": "^4.17.10",
49
52
  "@types/luxon": "^3.7.1",
@@ -0,0 +1,13 @@
1
+ import type { Knex } from "knex";
2
+
3
+ export async function up(knex: Knex): Promise<void> {
4
+ await knex.schema.createTable("sdk_runtime_state", (table) => {
5
+ table.string("key").primary();
6
+ table.text("value").notNullable();
7
+ table.string("updated_at").notNullable();
8
+ });
9
+ }
10
+
11
+ export async function down(knex: Knex): Promise<void> {
12
+ await knex.schema.dropTable("sdk_runtime_state");
13
+ }
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * Company info from MM (`/accounts/current`) is loaded during SDK startup
3
3
  * (`ApplicationInitializer.initialize` → {@link ensureCompanyInfo}), written to shared
4
- * job-state, and read by synchronous {@link getCachedCompanyInfo} and related getters.
4
+ * SQLite runtime state, and read by synchronous {@link getCachedCompanyInfo} and related getters.
5
5
  *
6
- * Persisted data is scoped to whatever filesystem sees the job-state file (default under `/tmp`):
7
- * e.g. one container’s filesystem unless that path is mounted shared. Company fields are treated
6
+ * Persisted data lives in `sdk_runtime_state` (same SQLite file as `SQLITE_DB_PATH`). Company fields are treated
8
7
  * as stable for the deployment; refreshing them if MM changes is the SDK’s responsibility when we
9
8
  * add that behavior—not connector boilerplate.
10
9
  *
@@ -49,7 +48,7 @@ function hydrateCacheFromDiskIfPresent(): CompanyInfo | null {
49
48
  }
50
49
 
51
50
  /**
52
- * Resolves company info for synchronous readers: in-memory, then persisted job-state.
51
+ * Resolves company info for synchronous readers: in-memory, then persisted SQLite runtime state.
53
52
  * @throws Error if neither is available (initialization / API fetch has not populated data).
54
53
  */
55
54
  function resolveCompanyInfoOrThrow(): CompanyInfo {
@@ -61,7 +60,7 @@ function resolveCompanyInfoOrThrow(): CompanyInfo {
61
60
  return fromDisk;
62
61
  }
63
62
  throw new Error(
64
- "Company info is not available: run ApplicationInitializer.initialize() or ensureCompanyInfo() in a process with API access, or ensure job-state.json contains companyInfo from a prior fetch"
63
+ "Company info is not available: run ApplicationInitializer.initialize() or ensureCompanyInfo() in a process with API access, or ensure sdk_runtime_state contains companyInfo from a prior fetch"
65
64
  );
66
65
  }
67
66
 
@@ -8,7 +8,7 @@ import { logger } from "../reporting-service/index.js";
8
8
 
9
9
  /**
10
10
  * Manages the MM API token with caching and expiration checking
11
- * Tokens are cached across jobs and job instances in local-data-store/jobs-shared-data.ts
11
+ * Tokens are cached across jobs and job instances in SQLite runtime state (jobs-shared-data.ts).
12
12
  * @jest-environment node
13
13
  */
14
14
  export class MMTokenManager {
@@ -33,7 +33,7 @@ export class ApplicationInitializer {
33
33
  await ApplicationInitializer.runMigrations();
34
34
  logger.info("Database migrations completed successfully");
35
35
 
36
- // Loads company info from MM into memory + job-state (timezone, locationRef, companyId).
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();
39
39
 
@@ -1,7 +1,10 @@
1
1
  import {
2
- readDatabaseLockState,
3
- writeDatabaseLockState,
4
- } from "./jobs-shared-data.js";
2
+ getRuntimeStateJson,
3
+ setRuntimeStateJson,
4
+ withImmediateTransaction,
5
+ } from "./sdk-runtime-state-db.js";
6
+
7
+ const DATABASE_LOCK_KEY = "databaseLock";
5
8
 
6
9
  interface DatabaseLock {
7
10
  isLocked: boolean;
@@ -9,19 +12,22 @@ interface DatabaseLock {
9
12
  lockedAt: string | null;
10
13
  }
11
14
 
15
+ const defaultLock = (): DatabaseLock => ({
16
+ isLocked: false,
17
+ lockedBy: "",
18
+ lockedAt: null,
19
+ });
20
+
21
+ function readLock(): DatabaseLock {
22
+ return getRuntimeStateJson<DatabaseLock>(DATABASE_LOCK_KEY) ?? defaultLock();
23
+ }
24
+
12
25
  /**
13
26
  * Gets the current database lock state
14
27
  * @returns The current database lock state
15
28
  */
16
29
  export const getDatabaseLock = (): DatabaseLock => {
17
- const data = readDatabaseLockState();
18
- return (
19
- (data.databaseLock as DatabaseLock) ?? {
20
- isLocked: false,
21
- lockedBy: "",
22
- lockedAt: null,
23
- }
24
- );
30
+ return readLock();
25
31
  };
26
32
 
27
33
  /**
@@ -30,24 +36,19 @@ export const getDatabaseLock = (): DatabaseLock => {
30
36
  * @returns true if lock was acquired, false if database is already locked
31
37
  */
32
38
  export const acquireDatabaseLock = (processName: string): boolean => {
33
- const data = readDatabaseLockState();
34
- const currentLock = (data.databaseLock as DatabaseLock) ?? {
35
- isLocked: false,
36
- lockedBy: "",
37
- lockedAt: null,
38
- };
39
-
40
- if (currentLock.isLocked) {
41
- return false;
42
- }
39
+ return withImmediateTransaction(() => {
40
+ const currentLock = readLock();
41
+ if (currentLock.isLocked) {
42
+ return false;
43
+ }
43
44
 
44
- data.databaseLock = {
45
- isLocked: true,
46
- lockedBy: processName,
47
- lockedAt: new Date().toISOString(),
48
- };
49
- writeDatabaseLockState(data);
50
- return true;
45
+ setRuntimeStateJson(DATABASE_LOCK_KEY, {
46
+ isLocked: true,
47
+ lockedBy: processName,
48
+ lockedAt: new Date().toISOString(),
49
+ });
50
+ return true;
51
+ });
51
52
  };
52
53
 
53
54
  /**
@@ -56,24 +57,15 @@ export const acquireDatabaseLock = (processName: string): boolean => {
56
57
  * @returns true if lock was released, false if process doesn't own the lock
57
58
  */
58
59
  export const releaseDatabaseLock = (processName: string): boolean => {
59
- const data = readDatabaseLockState();
60
- const currentLock = (data.databaseLock as DatabaseLock) ?? {
61
- isLocked: false,
62
- lockedBy: "",
63
- lockedAt: null,
64
- };
65
-
66
- if (!currentLock.isLocked || currentLock.lockedBy !== processName) {
67
- return false;
68
- }
60
+ return withImmediateTransaction(() => {
61
+ const currentLock = readLock();
62
+ if (!currentLock.isLocked || currentLock.lockedBy !== processName) {
63
+ return false;
64
+ }
69
65
 
70
- data.databaseLock = {
71
- isLocked: false,
72
- lockedBy: "",
73
- lockedAt: null,
74
- };
75
- writeDatabaseLockState(data);
76
- return true;
66
+ setRuntimeStateJson(DATABASE_LOCK_KEY, defaultLock());
67
+ return true;
68
+ });
77
69
  };
78
70
 
79
71
  /**
@@ -81,6 +73,5 @@ export const releaseDatabaseLock = (processName: string): boolean => {
81
73
  * @returns true if database is available, false if locked
82
74
  */
83
75
  export const isDatabaseAvailable = (): boolean => {
84
- const lock = getDatabaseLock();
85
- return !lock.isLocked;
76
+ return !readLock().isLocked;
86
77
  };
@@ -1,16 +1,18 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { mkdirSync } from "fs";
4
1
  import type { CompanyInfo } from "../../services/mm-api-service/company-info.js";
2
+ import {
3
+ getRuntimeStateJson,
4
+ setRuntimeStateJson,
5
+ } from "./sdk-runtime-state-db.js";
5
6
 
6
7
  /**
7
- * This file contains the logic for storing and retrieving data from the job state file.
8
- * It is used to store data that is shared between jobs, and (more importantly) across job instances.
9
- * Company info (timezone, locationRef, companyId) is persisted here so processes that share this
10
- * file path (same host /tmp, or a shared volume) can read it after SDK init; offset is not stored.
8
+ * Cross-process runtime state shared between Bree workers (company info, token cache,
9
+ * initial-load flag). Stored in SQLite (`sdk_runtime_state`) keyed by SQLITE_DB_PATH so
10
+ * each key is updated independently under WAL no whole-file read-modify-write races.
11
11
  */
12
12
 
13
13
  const COMPANY_INFO_STORAGE_KEY = "companyInfo";
14
+ const INITIAL_LOAD_COMPLETE_KEY = "initialLoadComplete";
15
+ const MM_API_TOKEN_KEY = "mmApiToken";
14
16
 
15
17
  function isPersistedCompanyInfo(value: unknown): value is CompanyInfo {
16
18
  if (!value || typeof value !== "object") {
@@ -25,86 +27,27 @@ function isPersistedCompanyInfo(value: unknown): value is CompanyInfo {
25
27
  );
26
28
  }
27
29
 
28
- const STORAGE_FILE = path.join("/tmp", "job-state.json");
29
-
30
- // Ensure parent directory exists
31
- const parentDir = path.dirname(STORAGE_FILE);
32
- try {
33
- mkdirSync(parentDir, { recursive: true });
34
- } catch (error) {
35
- if ((error as NodeJS.ErrnoException).code !== "EEXIST") {
36
- throw error;
37
- }
38
- }
39
-
40
- //#region Non-exported functions
41
- const ensureStorageFile = () => {
42
- if (!fs.existsSync(STORAGE_FILE)) {
43
- fs.writeFileSync(STORAGE_FILE, JSON.stringify({}), "utf-8");
44
- }
45
- };
46
-
47
- const readStorage = (): Record<string, unknown> => {
48
- ensureStorageFile();
49
- try {
50
- return JSON.parse(fs.readFileSync(STORAGE_FILE, "utf-8"));
51
- } catch (error) {
52
- console.error(`Failed to read storage from ${STORAGE_FILE}:`, error);
53
- return {};
54
- }
55
- };
56
-
57
- const writeStorage = (data: Record<string, unknown>): void => {
58
- ensureStorageFile();
59
- fs.writeFileSync(STORAGE_FILE, JSON.stringify(data, null, 2), "utf-8");
60
- };
61
- //#endregion
62
-
63
30
  /**
64
31
  * Reads company info written by initialization (or a prior API fetch) for use across processes.
65
32
  */
66
33
  export const readPersistedCompanyInfo = (): CompanyInfo | null => {
67
- const data = readStorage();
68
- const raw = data[COMPANY_INFO_STORAGE_KEY];
34
+ const raw = getRuntimeStateJson<unknown>(COMPANY_INFO_STORAGE_KEY);
69
35
  return isPersistedCompanyInfo(raw) ? raw : null;
70
36
  };
71
37
 
72
38
  /**
73
- * Persists company info for other processes on the same host (same job-state file).
39
+ * Persists company info for other processes sharing the same SQLite database file.
74
40
  */
75
41
  export const writePersistedCompanyInfo = (info: CompanyInfo): void => {
76
- const data = readStorage();
77
- data[COMPANY_INFO_STORAGE_KEY] = info;
78
- writeStorage(data);
79
- };
80
-
81
- //#region Database lock storage functions
82
- /**
83
- * Reads the database lock state from the shared storage file
84
- * @returns The data stored in the file
85
- */
86
- export const readDatabaseLockState = (): Record<string, unknown> => {
87
- return readStorage();
88
- };
89
-
90
- /**
91
- * Writes the database lock state to the shared storage file
92
- * @param data The lock state data to write
93
- */
94
- export const writeDatabaseLockState = (data: Record<string, unknown>): void => {
95
- writeStorage(data);
42
+ setRuntimeStateJson(COMPANY_INFO_STORAGE_KEY, info);
96
43
  };
97
- //#endregion
98
44
 
99
45
  export const getInitialLoadComplete = (): boolean => {
100
- const data = readStorage();
101
- return (data.initialLoadComplete as boolean) ?? false;
46
+ return getRuntimeStateJson<boolean>(INITIAL_LOAD_COMPLETE_KEY) ?? false;
102
47
  };
103
48
 
104
49
  export const setInitialLoadComplete = (complete: boolean): void => {
105
- const data = readStorage();
106
- data.initialLoadComplete = complete;
107
- writeStorage(data);
50
+ setRuntimeStateJson(INITIAL_LOAD_COMPLETE_KEY, complete);
108
51
  };
109
52
 
110
53
  interface CachedToken {
@@ -117,8 +60,7 @@ interface CachedToken {
117
60
  * @returns The cached token and expiration or null if not found
118
61
  */
119
62
  export const getCachedMMToken = (): CachedToken | null => {
120
- const data = readStorage();
121
- return (data.mmApiToken as CachedToken) ?? null;
63
+ return getRuntimeStateJson<CachedToken>(MM_API_TOKEN_KEY);
122
64
  };
123
65
 
124
66
  /**
@@ -126,7 +68,5 @@ export const getCachedMMToken = (): CachedToken | null => {
126
68
  * @param tokenData The token and expiration to cache
127
69
  */
128
70
  export const setCachedMMToken = (tokenData: CachedToken): void => {
129
- const data = readStorage();
130
- data.mmApiToken = tokenData;
131
- writeStorage(data);
71
+ setRuntimeStateJson(MM_API_TOKEN_KEY, tokenData);
132
72
  };
@@ -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
+ });
@@ -0,0 +1,141 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import Database from "better-sqlite3";
4
+
5
+ const TABLE = "sdk_runtime_state";
6
+ const LEGACY_JOB_STATE_FILE = path.join("/tmp", "job-state.json");
7
+ const LEGACY_IMPORT_FLAG_KEY = "_legacyJobStateImported";
8
+
9
+ let db: Database.Database | null = null;
10
+
11
+ function dbPath(): string {
12
+ return process.env.SQLITE_DB_PATH || "./local.sqlite3";
13
+ }
14
+
15
+ function ensureDbDirectory(filename: string): void {
16
+ const parentDir = path.dirname(path.resolve(filename));
17
+ try {
18
+ fs.mkdirSync(parentDir, { recursive: true });
19
+ } catch (error) {
20
+ if ((error as NodeJS.ErrnoException).code !== "EEXIST") {
21
+ throw error;
22
+ }
23
+ }
24
+ }
25
+
26
+ function getDb(): Database.Database {
27
+ if (!db) {
28
+ const filename = dbPath();
29
+ ensureDbDirectory(filename);
30
+ const instance = new Database(filename);
31
+ instance.pragma("journal_mode = WAL");
32
+ instance.pragma("busy_timeout = 5000");
33
+ instance.exec(`
34
+ CREATE TABLE IF NOT EXISTS ${TABLE} (
35
+ key TEXT PRIMARY KEY,
36
+ value TEXT NOT NULL,
37
+ updated_at TEXT NOT NULL
38
+ );
39
+ `);
40
+ db = instance;
41
+ try {
42
+ importLegacyJobStateFileOnce();
43
+ } catch (error) {
44
+ instance.close();
45
+ db = null;
46
+ throw error;
47
+ }
48
+ }
49
+ return db;
50
+ }
51
+
52
+ function readRaw(key: string): string | null {
53
+ const row = getDb()
54
+ .prepare(`SELECT value FROM ${TABLE} WHERE key = ?`)
55
+ .get(key) as { value: string } | undefined;
56
+ return row?.value ?? null;
57
+ }
58
+
59
+ function writeRaw(key: string, value: string): void {
60
+ const updatedAt = new Date().toISOString();
61
+ getDb()
62
+ .prepare(
63
+ `INSERT INTO ${TABLE} (key, value, updated_at) VALUES (?, ?, ?)
64
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
65
+ )
66
+ .run(key, value, updatedAt);
67
+ }
68
+
69
+ function importLegacyJobStateFileOnce(): void {
70
+ if (readRaw(LEGACY_IMPORT_FLAG_KEY)) {
71
+ return;
72
+ }
73
+
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>;
77
+
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));
95
+ }
96
+ }
97
+
98
+ writeRaw(LEGACY_IMPORT_FLAG_KEY, JSON.stringify(true));
99
+ }
100
+
101
+ /**
102
+ * Reads a JSON value for `key`, or null when unset.
103
+ */
104
+ export function getRuntimeStateJson<T>(key: string): T | null {
105
+ const raw = readRaw(key);
106
+ if (raw === null) {
107
+ return null;
108
+ }
109
+ return JSON.parse(raw) as T;
110
+ }
111
+
112
+ /**
113
+ * Upserts a JSON-serializable value for `key` without touching other keys.
114
+ */
115
+ export function setRuntimeStateJson(key: string, value: unknown): void {
116
+ writeRaw(key, JSON.stringify(value));
117
+ }
118
+
119
+ /**
120
+ * Runs `fn` inside BEGIN IMMEDIATE so cross-process writers serialize safely.
121
+ */
122
+ export function withImmediateTransaction<T>(fn: () => T): T {
123
+ const database = getDb();
124
+ database.prepare("BEGIN IMMEDIATE").run();
125
+ try {
126
+ const result = fn();
127
+ database.prepare("COMMIT").run();
128
+ return result;
129
+ } catch (error) {
130
+ database.prepare("ROLLBACK").run();
131
+ throw error;
132
+ }
133
+ }
134
+
135
+ /** Visible for integration tests that need an isolated database file. */
136
+ export function resetRuntimeStateDbForTests(): void {
137
+ if (db) {
138
+ db.close();
139
+ db = null;
140
+ }
141
+ }