hzl-core 1.5.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/dist/__tests__/backup/backup-restore.test.js +13 -12
  2. package/dist/__tests__/backup/backup-restore.test.js.map +1 -1
  3. package/dist/__tests__/backup/import-export.test.js +41 -39
  4. package/dist/__tests__/backup/import-export.test.js.map +1 -1
  5. package/dist/__tests__/concurrency/stress.test.js +2 -2
  6. package/dist/__tests__/concurrency/stress.test.js.map +1 -1
  7. package/dist/__tests__/concurrency/worker.js +3 -2
  8. package/dist/__tests__/concurrency/worker.js.map +1 -1
  9. package/dist/__tests__/migrations/upgrade.test.js +132 -117
  10. package/dist/__tests__/migrations/upgrade.test.js.map +1 -1
  11. package/dist/__tests__/projections/rebuild-equivalence.test.js +2 -2
  12. package/dist/__tests__/projections/rebuild-equivalence.test.js.map +1 -1
  13. package/dist/__tests__/properties/invariants.test.js +2 -2
  14. package/dist/__tests__/properties/invariants.test.js.map +1 -1
  15. package/dist/db/__tests__/append-only.test.d.ts +2 -0
  16. package/dist/db/__tests__/append-only.test.d.ts.map +1 -0
  17. package/dist/db/__tests__/append-only.test.js +46 -0
  18. package/dist/db/__tests__/append-only.test.js.map +1 -0
  19. package/dist/db/__tests__/datastore.test.d.ts +2 -0
  20. package/dist/db/__tests__/datastore.test.d.ts.map +1 -0
  21. package/dist/db/__tests__/datastore.test.js +68 -0
  22. package/dist/db/__tests__/datastore.test.js.map +1 -0
  23. package/dist/db/__tests__/libsql-compat.test.d.ts +2 -0
  24. package/dist/db/__tests__/libsql-compat.test.d.ts.map +1 -0
  25. package/dist/db/__tests__/libsql-compat.test.js +56 -0
  26. package/dist/db/__tests__/libsql-compat.test.js.map +1 -0
  27. package/dist/db/__tests__/lock.test.d.ts +2 -0
  28. package/dist/db/__tests__/lock.test.d.ts.map +1 -0
  29. package/dist/db/__tests__/lock.test.js +77 -0
  30. package/dist/db/__tests__/lock.test.js.map +1 -0
  31. package/dist/db/__tests__/meta.test.d.ts +2 -0
  32. package/dist/db/__tests__/meta.test.d.ts.map +1 -0
  33. package/dist/db/__tests__/meta.test.js +50 -0
  34. package/dist/db/__tests__/meta.test.js.map +1 -0
  35. package/dist/db/__tests__/migrations.test.d.ts +2 -0
  36. package/dist/db/__tests__/migrations.test.d.ts.map +1 -0
  37. package/dist/db/__tests__/migrations.test.js +57 -0
  38. package/dist/db/__tests__/migrations.test.js.map +1 -0
  39. package/dist/db/__tests__/sync-policy.test.d.ts +2 -0
  40. package/dist/db/__tests__/sync-policy.test.d.ts.map +1 -0
  41. package/dist/db/__tests__/sync-policy.test.js +118 -0
  42. package/dist/db/__tests__/sync-policy.test.js.map +1 -0
  43. package/dist/db/__tests__/types.test.d.ts +2 -0
  44. package/dist/db/__tests__/types.test.d.ts.map +1 -0
  45. package/dist/db/__tests__/types.test.js +91 -0
  46. package/dist/db/__tests__/types.test.js.map +1 -0
  47. package/dist/db/datastore.d.ts +16 -0
  48. package/dist/db/datastore.d.ts.map +1 -0
  49. package/dist/db/datastore.js +159 -0
  50. package/dist/db/datastore.js.map +1 -0
  51. package/dist/db/lock.d.ts +38 -0
  52. package/dist/db/lock.d.ts.map +1 -0
  53. package/dist/db/lock.js +114 -0
  54. package/dist/db/lock.js.map +1 -0
  55. package/dist/db/meta.d.ts +28 -0
  56. package/dist/db/meta.d.ts.map +1 -0
  57. package/dist/db/meta.js +95 -0
  58. package/dist/db/meta.js.map +1 -0
  59. package/dist/db/migrations.d.ts +23 -3
  60. package/dist/db/migrations.d.ts.map +1 -1
  61. package/dist/db/migrations.js +69 -99
  62. package/dist/db/migrations.js.map +1 -1
  63. package/dist/db/migrations.test.js +40 -105
  64. package/dist/db/migrations.test.js.map +1 -1
  65. package/dist/db/schema.d.ts +2 -1
  66. package/dist/db/schema.d.ts.map +1 -1
  67. package/dist/db/schema.js +52 -10
  68. package/dist/db/schema.js.map +1 -1
  69. package/dist/db/sync-policy.d.ts +21 -0
  70. package/dist/db/sync-policy.d.ts.map +1 -0
  71. package/dist/db/sync-policy.js +62 -0
  72. package/dist/db/sync-policy.js.map +1 -0
  73. package/dist/db/test-utils.d.ts +33 -0
  74. package/dist/db/test-utils.d.ts.map +1 -0
  75. package/dist/db/test-utils.js +58 -0
  76. package/dist/db/test-utils.js.map +1 -0
  77. package/dist/db/{connection.d.ts → transaction.d.ts} +2 -4
  78. package/dist/db/transaction.d.ts.map +1 -0
  79. package/dist/db/{connection.js → transaction.js} +1 -23
  80. package/dist/db/transaction.js.map +1 -0
  81. package/dist/db/types.d.ts +199 -0
  82. package/dist/db/types.d.ts.map +1 -0
  83. package/dist/db/types.js +43 -0
  84. package/dist/db/types.js.map +1 -0
  85. package/dist/events/store.d.ts +1 -1
  86. package/dist/events/store.d.ts.map +1 -1
  87. package/dist/events/store.test.js +2 -4
  88. package/dist/events/store.test.js.map +1 -1
  89. package/dist/index.d.ts +7 -2
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +6 -2
  92. package/dist/index.js.map +1 -1
  93. package/dist/index.test.js +13 -11
  94. package/dist/index.test.js.map +1 -1
  95. package/dist/projections/comments-checkpoints.d.ts +1 -1
  96. package/dist/projections/comments-checkpoints.d.ts.map +1 -1
  97. package/dist/projections/comments-checkpoints.test.js +3 -4
  98. package/dist/projections/comments-checkpoints.test.js.map +1 -1
  99. package/dist/projections/dependencies.d.ts +1 -1
  100. package/dist/projections/dependencies.d.ts.map +1 -1
  101. package/dist/projections/dependencies.test.js +3 -4
  102. package/dist/projections/dependencies.test.js.map +1 -1
  103. package/dist/projections/engine.d.ts +9 -2
  104. package/dist/projections/engine.d.ts.map +1 -1
  105. package/dist/projections/engine.js +22 -6
  106. package/dist/projections/engine.js.map +1 -1
  107. package/dist/projections/engine.test.js +3 -4
  108. package/dist/projections/engine.test.js.map +1 -1
  109. package/dist/projections/projects.d.ts +1 -1
  110. package/dist/projections/projects.d.ts.map +1 -1
  111. package/dist/projections/projects.test.js +2 -20
  112. package/dist/projections/projects.test.js.map +1 -1
  113. package/dist/projections/rebuild.d.ts +1 -1
  114. package/dist/projections/rebuild.d.ts.map +1 -1
  115. package/dist/projections/rebuild.test.js +3 -4
  116. package/dist/projections/rebuild.test.js.map +1 -1
  117. package/dist/projections/search.d.ts +1 -1
  118. package/dist/projections/search.d.ts.map +1 -1
  119. package/dist/projections/search.test.js +3 -4
  120. package/dist/projections/search.test.js.map +1 -1
  121. package/dist/projections/tags.d.ts +1 -1
  122. package/dist/projections/tags.d.ts.map +1 -1
  123. package/dist/projections/tags.test.js +3 -4
  124. package/dist/projections/tags.test.js.map +1 -1
  125. package/dist/projections/tasks-current.d.ts +1 -1
  126. package/dist/projections/tasks-current.d.ts.map +1 -1
  127. package/dist/projections/tasks-current.test.js +3 -4
  128. package/dist/projections/tasks-current.test.js.map +1 -1
  129. package/dist/projections/types.d.ts +1 -1
  130. package/dist/projections/types.d.ts.map +1 -1
  131. package/dist/services/backup-service.d.ts +2 -2
  132. package/dist/services/backup-service.d.ts.map +1 -1
  133. package/dist/services/backup-service.js +13 -3
  134. package/dist/services/backup-service.js.map +1 -1
  135. package/dist/services/project-service.d.ts +1 -1
  136. package/dist/services/project-service.d.ts.map +1 -1
  137. package/dist/services/project-service.js +1 -1
  138. package/dist/services/project-service.js.map +1 -1
  139. package/dist/services/project-service.test.js +2 -13
  140. package/dist/services/project-service.test.js.map +1 -1
  141. package/dist/services/search-service.d.ts +1 -1
  142. package/dist/services/search-service.d.ts.map +1 -1
  143. package/dist/services/search-service.test.js +3 -4
  144. package/dist/services/search-service.test.js.map +1 -1
  145. package/dist/services/task-service.d.ts +1 -1
  146. package/dist/services/task-service.d.ts.map +1 -1
  147. package/dist/services/task-service.js +1 -1
  148. package/dist/services/task-service.js.map +1 -1
  149. package/dist/services/task-service.test.js +3 -4
  150. package/dist/services/task-service.test.js.map +1 -1
  151. package/dist/services/validation-service.d.ts +1 -1
  152. package/dist/services/validation-service.d.ts.map +1 -1
  153. package/dist/services/validation-service.test.js +2 -4
  154. package/dist/services/validation-service.test.js.map +1 -1
  155. package/package.json +4 -4
  156. package/dist/db/connection.d.ts.map +0 -1
  157. package/dist/db/connection.js.map +0 -1
  158. package/dist/db/connection.test.d.ts +0 -2
  159. package/dist/db/connection.test.d.ts.map +0 -1
  160. package/dist/db/connection.test.js +0 -63
  161. package/dist/db/connection.test.js.map +0 -1
@@ -0,0 +1,16 @@
1
+ import Database from 'libsql';
2
+ import type { DbConfig, SyncStats } from './types.js';
3
+ export type ConnectionMode = 'local-only' | 'remote-replica' | 'offline-sync' | 'remote-only';
4
+ export interface Datastore {
5
+ eventsDb: Database.Database;
6
+ cacheDb: Database.Database;
7
+ mode: ConnectionMode;
8
+ syncUrl?: string;
9
+ instanceId: string;
10
+ deviceId: string;
11
+ syncAttempts: number[];
12
+ sync(): Promise<SyncStats>;
13
+ close(): void;
14
+ }
15
+ export declare function createDatastore(config: DbConfig): Datastore;
16
+ //# sourceMappingURL=datastore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datastore.d.ts","sourceRoot":"","sources":["../../src/db/datastore.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,QAAQ,CAAC;AAG9B,OAAO,KAAK,EAAE,QAAQ,EAAc,SAAS,EAAE,MAAM,YAAY,CAAC;AAKlE,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,gBAAgB,GAAG,cAAc,GAAG,aAAa,CAAC;AAE9F,MAAM,WAAW,SAAS;IACtB,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC;IAC5B,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC;IAC3B,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC;IAC3B,KAAK,IAAI,IAAI,CAAC;CACjB;AAiBD,wBAAgB,eAAe,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,CAuK3D"}
@@ -0,0 +1,159 @@
1
+ import Database from 'libsql';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { EVENTS_SCHEMA_V2, CACHE_SCHEMA_V1, PRAGMAS } from './schema.js';
5
+ import { generateId } from '../utils/id.js';
6
+ import { setInstanceId, getInstanceId, setDeviceId, getDeviceId, setLastSyncAttemptAt, setLastSyncAt, setLastSyncError, clearLastSyncError, clearDirtySince } from './meta.js';
7
+ function ensureDirectory(filePath) {
8
+ const dir = path.dirname(filePath);
9
+ if (!fs.existsSync(dir)) {
10
+ fs.mkdirSync(dir, { recursive: true });
11
+ }
12
+ }
13
+ function determineMode(config) {
14
+ const syncUrl = config.events?.syncUrl;
15
+ if (!syncUrl)
16
+ return 'local-only';
17
+ const syncMode = config.events?.syncMode ?? 'offline';
18
+ return syncMode === 'replica' ? 'remote-replica' : 'offline-sync';
19
+ }
20
+ export function createDatastore(config) {
21
+ const eventsPath = config.events?.path ?? ':memory:';
22
+ const cachePath = config.cache?.path ?? ':memory:';
23
+ const mode = determineMode(config);
24
+ // Ensure directories exist
25
+ if (eventsPath !== ':memory:')
26
+ ensureDirectory(eventsPath);
27
+ if (cachePath !== ':memory:')
28
+ ensureDirectory(cachePath);
29
+ const eventsOpts = {
30
+ syncUrl: config.events?.syncUrl,
31
+ authToken: config.events?.authToken,
32
+ encryptionKey: config.events?.encryptionKey,
33
+ timeout: config.timeoutSec,
34
+ // Use configured syncPeriod, default to 0 (disabled) for CLI use cases
35
+ syncPeriod: config.syncPeriod ?? 0,
36
+ };
37
+ // Create connections
38
+ const eventsDb = new Database(eventsPath, eventsOpts);
39
+ const cacheDb = new Database(cachePath, { timeout: config.timeoutSec });
40
+ // Set pragmas
41
+ cacheDb.exec(PRAGMAS);
42
+ try {
43
+ eventsDb.exec(PRAGMAS);
44
+ }
45
+ catch (err) {
46
+ // In sync mode (embedded replica), some pragmas like journal_mode might be restricted
47
+ // or handled by the engine. We ignore 'Sqlite3UnsupportedStatement' in this context.
48
+ // The library throws generic Error with message or name equivalent to the code.
49
+ const errorObj = err;
50
+ const isUnsupported = errorObj.code === 'Sqlite3UnsupportedStatement' ||
51
+ errorObj.message === 'Sqlite3UnsupportedStatement' ||
52
+ String(err).includes('Sqlite3UnsupportedStatement');
53
+ if (!isUnsupported) {
54
+ throw err;
55
+ }
56
+ }
57
+ // Initialize schemas
58
+ eventsDb.exec(EVENTS_SCHEMA_V2);
59
+ cacheDb.exec(CACHE_SCHEMA_V1);
60
+ // Ensure instance ID exists (generate if new database)
61
+ let instanceId = getInstanceId(eventsDb);
62
+ if (!instanceId) {
63
+ instanceId = generateId();
64
+ setInstanceId(eventsDb, instanceId);
65
+ }
66
+ // Ensure device ID exists (generate if new device)
67
+ let deviceId = getDeviceId(cacheDb);
68
+ if (!deviceId) {
69
+ deviceId = generateId();
70
+ setDeviceId(cacheDb, deviceId);
71
+ }
72
+ const datastore = {
73
+ eventsDb,
74
+ cacheDb,
75
+ mode,
76
+ syncUrl: config.events?.syncUrl,
77
+ instanceId,
78
+ deviceId,
79
+ // Rate limiting state
80
+ syncAttempts: [],
81
+ async sync() {
82
+ if (mode === 'local-only') {
83
+ return { attempted: false, success: true };
84
+ }
85
+ const now = Date.now();
86
+ const maxAttempts = config.sync?.maxSyncAttemptsPerMinute ?? 10;
87
+ const syncTimeoutMs = config.sync?.syncTimeoutMs ?? 30000;
88
+ // Rate limiting: track attempts in the last minute
89
+ this.syncAttempts = this.syncAttempts.filter(t => now - t < 60000);
90
+ if (this.syncAttempts.length >= maxAttempts) {
91
+ return {
92
+ attempted: false,
93
+ success: false,
94
+ error: `Rate limited: ${maxAttempts} sync attempts per minute exceeded`,
95
+ };
96
+ }
97
+ this.syncAttempts.push(now);
98
+ // Update sync attempt timestamp in local meta
99
+ setLastSyncAttemptAt(cacheDb, now);
100
+ try {
101
+ // Wrap sync() with timeout using AbortController pattern
102
+ const syncPromise = new Promise((resolve, reject) => {
103
+ const timeout = setTimeout(() => {
104
+ reject(new Error(`Sync timed out after ${syncTimeoutMs}ms`));
105
+ }, syncTimeoutMs);
106
+ try {
107
+ // libsql sync() is synchronous in Node.js binding? No, it returns void or result?
108
+ // Wait, better-sqlite3 style sync might be synchronous, but libsql over HTTP is likely async?
109
+ // The libsql type defs say sync() returns void... wait.
110
+ // Let's assume sync() is synchronous for now as per better-sqlite3-libsql, OR verify.
111
+ // The 'libsql' package (which is @libsql/client usually, but here we are using 'libsql' npm package which is the better-sqlite3 fork)
112
+ // The 'libsql' npm package's sync() IS synchronous usually if it's embedded replica.
113
+ // But if it blocks, we might need to be careful.
114
+ // Actually, looking at docs, db.sync() returns nothing in the better-sqlite3 compatible binding?
115
+ // Let's assume it works synchronously.
116
+ eventsDb.sync();
117
+ clearTimeout(timeout);
118
+ // It doesn't return stats in the binding usually.
119
+ resolve({ frames_synced: 0, frame_no: 0 }); // Mock result since binding might not return it
120
+ }
121
+ catch (err) {
122
+ clearTimeout(timeout);
123
+ reject(err instanceof Error ? err : new Error(String(err)));
124
+ }
125
+ });
126
+ // If it's truly synchronous, the promise wrapper above is silly but harmless.
127
+ // If it's async (which Turso sync implies), then we await it.
128
+ // But 'libsql' (better-sqlite3 fork) sync() blocks.
129
+ await syncPromise;
130
+ // On success: update sync metadata
131
+ const syncTime = Date.now();
132
+ setLastSyncAt(cacheDb, syncTime);
133
+ clearLastSyncError(cacheDb);
134
+ clearDirtySince(cacheDb);
135
+ return {
136
+ attempted: true,
137
+ success: true,
138
+ framesSynced: 0,
139
+ };
140
+ }
141
+ catch (err) {
142
+ // On failure: record error
143
+ const errorMessage = err instanceof Error ? err.message : String(err);
144
+ setLastSyncError(cacheDb, errorMessage);
145
+ return {
146
+ attempted: true,
147
+ success: false,
148
+ error: errorMessage,
149
+ };
150
+ }
151
+ },
152
+ close() {
153
+ eventsDb.close();
154
+ cacheDb.close();
155
+ }
156
+ };
157
+ return datastore;
158
+ }
159
+ //# sourceMappingURL=datastore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datastore.js","sourceRoot":"","sources":["../../src/db/datastore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ export interface LockMetadata {
2
+ pid: number;
3
+ hostname: string;
4
+ startedAt: number;
5
+ command?: string;
6
+ version?: string;
7
+ }
8
+ export interface LockGuard {
9
+ release(): void;
10
+ staleLockCleared: boolean;
11
+ }
12
+ export interface LockOptions {
13
+ command?: string;
14
+ version?: string;
15
+ }
16
+ export declare class DatabaseLock {
17
+ private lockPath;
18
+ private options;
19
+ constructor(lockPath: string, options?: LockOptions);
20
+ /**
21
+ * Read current lock metadata if lock file exists
22
+ */
23
+ readMetadata(): LockMetadata | null;
24
+ /**
25
+ * Check if lock is stale (held by dead process)
26
+ */
27
+ isStale(): boolean;
28
+ /**
29
+ * Clear the lock file
30
+ */
31
+ clear(): void;
32
+ /**
33
+ * Acquire the lock with timeout.
34
+ * Uses exponential backoff starting at 5ms for fast CLI responsiveness.
35
+ */
36
+ acquire(timeoutMs: number): Promise<LockGuard>;
37
+ }
38
+ //# sourceMappingURL=lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/db/lock.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACtB,OAAO,IAAI,IAAI,CAAC;IAChB,gBAAgB,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAkBD,qBAAa,YAAY;IACrB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAc;gBAEjB,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB;IAKvD;;OAEG;IACH,YAAY,IAAI,YAAY,GAAG,IAAI;IAYnC;;OAEG;IACH,OAAO,IAAI,OAAO;IAYlB;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb;;;OAGG;IACG,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;CAmDvD"}
@@ -0,0 +1,114 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ function isPidRunning(pid) {
4
+ try {
5
+ // Sending signal 0 checks if process exists without killing it
6
+ process.kill(pid, 0);
7
+ return true;
8
+ }
9
+ catch (err) {
10
+ const error = err;
11
+ // EPERM means process exists but we lack permission to signal it
12
+ // ESRCH means process does not exist
13
+ if (error.code === 'EPERM') {
14
+ return true;
15
+ }
16
+ return false;
17
+ }
18
+ }
19
+ export class DatabaseLock {
20
+ lockPath;
21
+ options;
22
+ constructor(lockPath, options = {}) {
23
+ this.lockPath = lockPath;
24
+ this.options = options;
25
+ }
26
+ /**
27
+ * Read current lock metadata if lock file exists
28
+ */
29
+ readMetadata() {
30
+ if (!fs.existsSync(this.lockPath)) {
31
+ return null;
32
+ }
33
+ try {
34
+ const content = fs.readFileSync(this.lockPath, 'utf-8');
35
+ return JSON.parse(content);
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ /**
42
+ * Check if lock is stale (held by dead process)
43
+ */
44
+ isStale() {
45
+ const metadata = this.readMetadata();
46
+ if (!metadata)
47
+ return false;
48
+ // Only consider stale if on same hostname (can't check PIDs across machines)
49
+ if (metadata.hostname !== os.hostname()) {
50
+ return false;
51
+ }
52
+ return !isPidRunning(metadata.pid);
53
+ }
54
+ /**
55
+ * Clear the lock file
56
+ */
57
+ clear() {
58
+ if (fs.existsSync(this.lockPath)) {
59
+ fs.unlinkSync(this.lockPath);
60
+ }
61
+ }
62
+ /**
63
+ * Acquire the lock with timeout.
64
+ * Uses exponential backoff starting at 5ms for fast CLI responsiveness.
65
+ */
66
+ async acquire(timeoutMs) {
67
+ const startTime = Date.now();
68
+ let staleLockCleared = false;
69
+ let attempt = 0;
70
+ const BASE_DELAY_MS = 5;
71
+ const MAX_DELAY_MS = 100;
72
+ while (Date.now() - startTime < timeoutMs) {
73
+ // Check for stale lock
74
+ if (this.isStale()) {
75
+ this.clear();
76
+ staleLockCleared = true;
77
+ }
78
+ // Try to create lock file exclusively
79
+ try {
80
+ const metadata = {
81
+ pid: process.pid,
82
+ hostname: os.hostname(),
83
+ startedAt: Date.now(),
84
+ command: this.options.command,
85
+ version: this.options.version,
86
+ };
87
+ // O_EXCL ensures atomic creation - fails if file exists
88
+ const fd = fs.openSync(this.lockPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY);
89
+ fs.writeSync(fd, JSON.stringify(metadata, null, 2));
90
+ fs.closeSync(fd);
91
+ return {
92
+ release: () => this.clear(),
93
+ staleLockCleared,
94
+ };
95
+ }
96
+ catch (err) {
97
+ const error = err;
98
+ if (error.code !== 'EEXIST') {
99
+ throw err;
100
+ }
101
+ // Lock exists, wait with exponential backoff (5ms, 10ms, 20ms, 40ms, 80ms, 100ms max)
102
+ const delay = Math.min(BASE_DELAY_MS * Math.pow(2, attempt), MAX_DELAY_MS);
103
+ await new Promise(resolve => setTimeout(resolve, delay));
104
+ attempt++;
105
+ }
106
+ }
107
+ const metadata = this.readMetadata();
108
+ const holder = metadata
109
+ ? `PID ${metadata.pid} (${metadata.command ?? 'unknown'}) since ${new Date(metadata.startedAt).toISOString()}`
110
+ : 'unknown process';
111
+ throw new Error(`Lock is held by ${holder}. Timeout after ${timeoutMs}ms.`);
112
+ }
113
+ }
114
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/db/lock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type Database from 'libsql';
2
+ /**
3
+ * Schema for hzl_global_meta table (stored in events.db, synced)
4
+ * Contains immutable dataset identity.
5
+ */
6
+ export declare function createGlobalMetaSchema(): string;
7
+ /**
8
+ * Schema for hzl_local_meta table (stored in cache.db, local-only)
9
+ * Contains per-device sync bookkeeping.
10
+ */
11
+ export declare function createLocalMetaSchema(): string;
12
+ export declare function getInstanceId(db: Database.Database): string | null;
13
+ export declare function setInstanceId(db: Database.Database, instanceId: string): void;
14
+ export declare function getDeviceId(db: Database.Database): string | null;
15
+ export declare function setDeviceId(db: Database.Database, deviceId: string): void;
16
+ export declare function getDirtySince(db: Database.Database): number | null;
17
+ export declare function setDirtySince(db: Database.Database, timestamp: number): void;
18
+ export declare function clearDirtySince(db: Database.Database): void;
19
+ export declare function getLastSyncAt(db: Database.Database): number | null;
20
+ export declare function setLastSyncAt(db: Database.Database, timestamp: number): void;
21
+ export declare function getLastSyncError(db: Database.Database): string | null;
22
+ export declare function setLastSyncError(db: Database.Database, error: string): void;
23
+ export declare function clearLastSyncError(db: Database.Database): void;
24
+ export declare function getLastSyncFrameNo(db: Database.Database): number | null;
25
+ export declare function setLastSyncFrameNo(db: Database.Database, frameNo: number): void;
26
+ export declare function getLastSyncAttemptAt(db: Database.Database): number | null;
27
+ export declare function setLastSyncAttemptAt(db: Database.Database, timestamp: number): void;
28
+ //# sourceMappingURL=meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta.d.ts","sourceRoot":"","sources":["../../src/db/meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,QAAQ,CAAC;AAEnC;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAO/C;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAO9C;AAcD,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,GAAG,IAAI,CAGlE;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAO7E;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,GAAG,IAAI,CAGhE;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAEzE;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,GAAG,IAAI,CAGlE;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAE5E;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAE3D;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,GAAG,IAAI,CAGlE;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAE5E;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,GAAG,IAAI,CAGrE;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAE3E;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAE9D;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,GAAG,IAAI,CAGvE;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAE/E;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,GAAG,IAAI,CAGzE;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAEnF"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Schema for hzl_global_meta table (stored in events.db, synced)
3
+ * Contains immutable dataset identity.
4
+ */
5
+ export function createGlobalMetaSchema() {
6
+ return `
7
+ CREATE TABLE IF NOT EXISTS hzl_global_meta (
8
+ key TEXT PRIMARY KEY,
9
+ value TEXT NOT NULL
10
+ );
11
+ `;
12
+ }
13
+ /**
14
+ * Schema for hzl_local_meta table (stored in cache.db, local-only)
15
+ * Contains per-device sync bookkeeping.
16
+ */
17
+ export function createLocalMetaSchema() {
18
+ return `
19
+ CREATE TABLE IF NOT EXISTS hzl_local_meta (
20
+ key TEXT PRIMARY KEY,
21
+ value TEXT NOT NULL
22
+ );
23
+ `;
24
+ }
25
+ // Global meta keys
26
+ const INSTANCE_ID_KEY = 'hzl_instance_id';
27
+ const CREATED_AT_KEY = 'created_at_ms';
28
+ // Local meta keys
29
+ const DEVICE_ID_KEY = 'device_id';
30
+ const DIRTY_SINCE_KEY = 'dirty_since_ms';
31
+ const LAST_SYNC_AT_KEY = 'last_sync_at_ms';
32
+ const LAST_SYNC_ATTEMPT_KEY = 'last_sync_attempt_at_ms';
33
+ const LAST_SYNC_ERROR_KEY = 'last_sync_error';
34
+ const LAST_SYNC_FRAME_KEY = 'last_sync_frame_no';
35
+ export function getInstanceId(db) {
36
+ const row = db.prepare('SELECT value FROM hzl_global_meta WHERE key = ?').get(INSTANCE_ID_KEY);
37
+ return row?.value ?? null;
38
+ }
39
+ export function setInstanceId(db, instanceId) {
40
+ const existing = getInstanceId(db);
41
+ if (existing !== null) {
42
+ throw new Error(`Instance ID already set to ${existing}. Cannot overwrite.`);
43
+ }
44
+ db.prepare('INSERT INTO hzl_global_meta (key, value) VALUES (?, ?)').run(INSTANCE_ID_KEY, instanceId);
45
+ db.prepare('INSERT INTO hzl_global_meta (key, value) VALUES (?, ?)').run(CREATED_AT_KEY, Date.now().toString());
46
+ }
47
+ export function getDeviceId(db) {
48
+ const row = db.prepare('SELECT value FROM hzl_local_meta WHERE key = ?').get(DEVICE_ID_KEY);
49
+ return row?.value ?? null;
50
+ }
51
+ export function setDeviceId(db, deviceId) {
52
+ db.prepare('INSERT OR REPLACE INTO hzl_local_meta (key, value) VALUES (?, ?)').run(DEVICE_ID_KEY, deviceId);
53
+ }
54
+ export function getDirtySince(db) {
55
+ const row = db.prepare('SELECT value FROM hzl_local_meta WHERE key = ?').get(DIRTY_SINCE_KEY);
56
+ return row ? parseInt(row.value, 10) : null;
57
+ }
58
+ export function setDirtySince(db, timestamp) {
59
+ db.prepare('INSERT OR REPLACE INTO hzl_local_meta (key, value) VALUES (?, ?)').run(DIRTY_SINCE_KEY, timestamp.toString());
60
+ }
61
+ export function clearDirtySince(db) {
62
+ db.prepare('DELETE FROM hzl_local_meta WHERE key = ?').run(DIRTY_SINCE_KEY);
63
+ }
64
+ export function getLastSyncAt(db) {
65
+ const row = db.prepare('SELECT value FROM hzl_local_meta WHERE key = ?').get(LAST_SYNC_AT_KEY);
66
+ return row ? parseInt(row.value, 10) : null;
67
+ }
68
+ export function setLastSyncAt(db, timestamp) {
69
+ db.prepare('INSERT OR REPLACE INTO hzl_local_meta (key, value) VALUES (?, ?)').run(LAST_SYNC_AT_KEY, timestamp.toString());
70
+ }
71
+ export function getLastSyncError(db) {
72
+ const row = db.prepare('SELECT value FROM hzl_local_meta WHERE key = ?').get(LAST_SYNC_ERROR_KEY);
73
+ return row?.value ?? null;
74
+ }
75
+ export function setLastSyncError(db, error) {
76
+ db.prepare('INSERT OR REPLACE INTO hzl_local_meta (key, value) VALUES (?, ?)').run(LAST_SYNC_ERROR_KEY, error);
77
+ }
78
+ export function clearLastSyncError(db) {
79
+ db.prepare('DELETE FROM hzl_local_meta WHERE key = ?').run(LAST_SYNC_ERROR_KEY);
80
+ }
81
+ export function getLastSyncFrameNo(db) {
82
+ const row = db.prepare('SELECT value FROM hzl_local_meta WHERE key = ?').get(LAST_SYNC_FRAME_KEY);
83
+ return row ? parseInt(row.value, 10) : null;
84
+ }
85
+ export function setLastSyncFrameNo(db, frameNo) {
86
+ db.prepare('INSERT OR REPLACE INTO hzl_local_meta (key, value) VALUES (?, ?)').run(LAST_SYNC_FRAME_KEY, frameNo.toString());
87
+ }
88
+ export function getLastSyncAttemptAt(db) {
89
+ const row = db.prepare('SELECT value FROM hzl_local_meta WHERE key = ?').get(LAST_SYNC_ATTEMPT_KEY);
90
+ return row ? parseInt(row.value, 10) : null;
91
+ }
92
+ export function setLastSyncAttemptAt(db, timestamp) {
93
+ db.prepare('INSERT OR REPLACE INTO hzl_local_meta (key, value) VALUES (?, ?)').run(LAST_SYNC_ATTEMPT_KEY, timestamp.toString());
94
+ }
95
+ //# sourceMappingURL=meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta.js","sourceRoot":"","sources":["../../src/db/meta.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,24 @@
1
- import type Database from 'better-sqlite3';
2
- export declare function getCurrentVersion(db: Database.Database): number;
3
- export declare function runMigrations(db: Database.Database): void;
1
+ import type Database from 'libsql';
2
+ export interface Migration {
3
+ id: string;
4
+ up: string;
5
+ down?: string;
6
+ }
7
+ export interface MigrationResult {
8
+ success: boolean;
9
+ applied: string[];
10
+ skipped: string[];
11
+ error?: string;
12
+ }
13
+ export declare class MigrationError extends Error {
14
+ readonly failedMigration: string;
15
+ readonly rolledBack: string[];
16
+ constructor(message: string, failedMigration: string, rolledBack: string[]);
17
+ }
18
+ /**
19
+ * Run migrations with atomic rollback on failure.
20
+ * All pending migrations are run in a single transaction.
21
+ * If any migration fails, ALL changes are rolled back.
22
+ */
23
+ export declare function runMigrationsWithRollback(db: Database.Database, migrations: Migration[]): MigrationResult;
4
24
  //# sourceMappingURL=migrations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/db/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAS3C,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,CAgB/D;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI,CA4BzD"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/db/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,QAAQ,CAAC;AAGnC,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,cAAe,SAAQ,KAAK;aAGrB,eAAe,EAAE,MAAM;aACvB,UAAU,EAAE,MAAM,EAAE;gBAFpC,OAAO,EAAE,MAAM,EACC,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,MAAM,EAAE;CAKvC;AAqBD;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,UAAU,EAAE,SAAS,EAAE,GACtB,eAAe,CA4DjB"}