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
@@ -1,113 +1,83 @@
1
- import { EventType, PROJECT_EVENT_TASK_ID } from '../events/types.js';
2
- import { generateId } from '../utils/id.js';
3
- import { SCHEMA_V1, PRAGMAS } from './schema.js';
4
- const MIGRATIONS = {
5
- 1: SCHEMA_V1,
6
- };
7
- export function getCurrentVersion(db) {
8
- try {
9
- const rows = db
10
- .prepare('SELECT version FROM schema_migrations')
11
- .all();
12
- let maxVersion = 0;
13
- for (const row of rows) {
14
- const parsed = Number(row.version);
15
- if (Number.isFinite(parsed) && parsed > maxVersion) {
16
- maxVersion = parsed;
17
- }
18
- }
19
- return maxVersion;
20
- }
21
- catch {
22
- return 0;
1
+ import crypto from 'crypto';
2
+ export class MigrationError extends Error {
3
+ failedMigration;
4
+ rolledBack;
5
+ constructor(message, failedMigration, rolledBack) {
6
+ super(message);
7
+ this.failedMigration = failedMigration;
8
+ this.rolledBack = rolledBack;
9
+ this.name = 'MigrationError';
23
10
  }
24
11
  }
25
- export function runMigrations(db) {
26
- db.exec(PRAGMAS);
12
+ function computeChecksum(sql) {
13
+ return crypto.createHash('sha256').update(sql).digest('hex').slice(0, 16);
14
+ }
15
+ function ensureMigrationsTable(db) {
27
16
  db.exec(`
28
17
  CREATE TABLE IF NOT EXISTS schema_migrations (
29
- version INTEGER PRIMARY KEY,
30
- applied_at TEXT NOT NULL
18
+ migration_id TEXT PRIMARY KEY,
19
+ applied_at_ms INTEGER NOT NULL,
20
+ checksum TEXT NOT NULL
31
21
  )
32
22
  `);
33
- const currentVersion = getCurrentVersion(db);
34
- const versions = Object.keys(MIGRATIONS)
35
- .map(Number)
36
- .sort((a, b) => a - b);
37
- for (const version of versions) {
38
- if (version > currentVersion) {
39
- db.transaction(() => {
40
- db.exec(MIGRATIONS[version]);
41
- db.prepare('INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)').run(version, new Date().toISOString());
42
- })();
43
- }
44
- }
45
- migrateToProjectsTable(db);
46
- db.exec(SCHEMA_V1);
47
23
  }
48
- function migrateToProjectsTable(db) {
49
- const tableExists = db
50
- .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='projects'")
51
- .get();
52
- if (!tableExists) {
53
- db.exec(`
54
- CREATE TABLE projects (
55
- name TEXT PRIMARY KEY,
56
- description TEXT,
57
- is_protected INTEGER NOT NULL DEFAULT 0,
58
- created_at TEXT NOT NULL,
59
- last_event_id INTEGER NOT NULL
60
- );
61
- CREATE INDEX idx_projects_protected ON projects(is_protected);
62
- `);
63
- }
64
- const eventsTableExists = db
65
- .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='events'")
66
- .get();
67
- if (!eventsTableExists) {
68
- return;
24
+ function getAppliedMigrations(db) {
25
+ const rows = db.prepare('SELECT migration_id FROM schema_migrations').all();
26
+ return new Set(rows.map(r => r.migration_id));
27
+ }
28
+ /**
29
+ * Run migrations with atomic rollback on failure.
30
+ * All pending migrations are run in a single transaction.
31
+ * If any migration fails, ALL changes are rolled back.
32
+ */
33
+ export function runMigrationsWithRollback(db, migrations) {
34
+ ensureMigrationsTable(db);
35
+ const applied = [];
36
+ const skipped = [];
37
+ const alreadyApplied = getAppliedMigrations(db);
38
+ // Filter to pending migrations
39
+ const pendingMigrations = migrations.filter(m => {
40
+ if (alreadyApplied.has(m.id)) {
41
+ skipped.push(m.id);
42
+ return false;
43
+ }
44
+ return true;
45
+ });
46
+ if (pendingMigrations.length === 0) {
47
+ return { success: true, applied, skipped };
69
48
  }
70
- const tasksCurrentExists = db
71
- .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='tasks_current'")
72
- .get();
73
- const existingProjects = tasksCurrentExists
74
- ? db.prepare('SELECT DISTINCT project FROM tasks_current').all()
75
- : [];
76
- // Check what work needs to be done before starting any transaction
77
- const existingProjectNames = new Set(db.prepare('SELECT name FROM projects').all().map((r) => r.name));
78
- const projectsToCreate = existingProjects.filter(({ project }) => !existingProjectNames.has(project));
79
- const needsInbox = !existingProjectNames.has('inbox');
80
- const inboxNeedsProtection = existingProjectNames.has('inbox') &&
81
- db.prepare('SELECT is_protected FROM projects WHERE name = ?').get('inbox')?.is_protected !== 1;
82
- // Only start a transaction if there's actual work to do
83
- if (projectsToCreate.length === 0 && !needsInbox && !inboxNeedsProtection) {
84
- return;
49
+ // Run all pending migrations in a single transaction for atomic rollback
50
+ try {
51
+ db.exec('BEGIN IMMEDIATE');
52
+ for (const migration of pendingMigrations) {
53
+ try {
54
+ db.exec(migration.up);
55
+ // Record migration
56
+ db.prepare('INSERT INTO schema_migrations (migration_id, applied_at_ms, checksum) VALUES (?, ?, ?)').run(migration.id, Date.now(), computeChecksum(migration.up));
57
+ applied.push(migration.id);
58
+ }
59
+ catch (err) {
60
+ // Rollback the entire transaction
61
+ db.exec('ROLLBACK');
62
+ throw new MigrationError(`Migration ${migration.id} failed: ${err instanceof Error ? err.message : String(err)}`, migration.id, applied // These were applied before failure but are now rolled back
63
+ );
64
+ }
65
+ }
66
+ db.exec('COMMIT');
67
+ return { success: true, applied, skipped };
85
68
  }
86
- const timestamp = new Date().toISOString();
87
- const insertEvent = db.prepare(`
88
- INSERT INTO events (event_id, task_id, type, data, timestamp)
89
- VALUES (?, ?, ?, ?, ?)
90
- `);
91
- const insertProject = db.prepare(`
92
- INSERT OR IGNORE INTO projects (name, description, is_protected, created_at, last_event_id)
93
- VALUES (?, NULL, ?, ?, ?)
94
- `);
95
- db.transaction(() => {
96
- for (const { project } of projectsToCreate) {
97
- const eventId = generateId();
98
- const data = JSON.stringify({ name: project });
99
- const result = insertEvent.run(eventId, PROJECT_EVENT_TASK_ID, EventType.ProjectCreated, data, timestamp);
100
- insertProject.run(project, 0, timestamp, result.lastInsertRowid);
69
+ catch (err) {
70
+ if (err instanceof MigrationError) {
71
+ throw err;
101
72
  }
102
- if (needsInbox) {
103
- const eventId = generateId();
104
- const data = JSON.stringify({ name: 'inbox', is_protected: true });
105
- const result = insertEvent.run(eventId, PROJECT_EVENT_TASK_ID, EventType.ProjectCreated, data, timestamp);
106
- insertProject.run('inbox', 1, timestamp, result.lastInsertRowid);
73
+ // Unexpected error - ensure rollback
74
+ try {
75
+ db.exec('ROLLBACK');
107
76
  }
108
- else if (inboxNeedsProtection) {
109
- db.prepare('UPDATE projects SET is_protected = 1 WHERE name = ?').run('inbox');
77
+ catch {
78
+ // Ignore rollback errors
110
79
  }
111
- })();
80
+ throw err;
81
+ }
112
82
  }
113
83
  //# sourceMappingURL=migrations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/db/migrations.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/db/migrations.ts"],"names":[],"mappings":"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"}
@@ -1,115 +1,50 @@
1
1
  import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import Database from 'better-sqlite3';
3
- import { runMigrations, getCurrentVersion } from './migrations.js';
4
- import { SCHEMA_V1 } from './schema.js';
5
- describe('migrations', () => {
2
+ import { runMigrationsWithRollback, MigrationError } from './migrations.js';
3
+ import { createTestDb } from './test-utils.js';
4
+ describe('migrations with rollback', () => {
6
5
  let db;
7
6
  beforeEach(() => {
8
- db = new Database(':memory:');
7
+ db = createTestDb();
9
8
  });
10
9
  afterEach(() => {
11
10
  db.close();
12
11
  });
13
- it('creates schema_migrations table', () => {
14
- runMigrations(db);
15
- const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='schema_migrations'").get();
16
- expect(table).toBeDefined();
17
- });
18
- it('creates events table with correct columns', () => {
19
- runMigrations(db);
20
- const columns = db.prepare("PRAGMA table_info(events)").all();
21
- const columnNames = columns.map((c) => c.name);
22
- expect(columnNames).toContain('event_id');
23
- expect(columnNames).toContain('task_id');
24
- expect(columnNames).toContain('type');
25
- expect(columnNames).toContain('data');
26
- expect(columnNames).toContain('timestamp');
27
- });
28
- it('creates tasks_current projection table with lease fields', () => {
29
- runMigrations(db);
30
- const columns = db.prepare("PRAGMA table_info(tasks_current)").all();
31
- const columnNames = columns.map((c) => c.name);
32
- expect(columnNames).toContain('task_id');
33
- expect(columnNames).toContain('title');
34
- expect(columnNames).toContain('status');
35
- expect(columnNames).toContain('project');
36
- expect(columnNames).toContain('claimed_at');
37
- expect(columnNames).toContain('lease_until');
38
- });
39
- it('creates task_dependencies table', () => {
40
- runMigrations(db);
41
- const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='task_dependencies'").get();
42
- expect(table).toBeDefined();
43
- });
44
- it('creates projection_state table', () => {
45
- runMigrations(db);
46
- const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='projection_state'").get();
47
- expect(table).toBeDefined();
48
- });
49
- it('creates task_tags table for fast tag filtering', () => {
50
- runMigrations(db);
51
- const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='task_tags'").get();
52
- expect(table).toBeDefined();
53
- });
54
- it('creates task_comments table', () => {
55
- runMigrations(db);
56
- const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='task_comments'").get();
57
- expect(table).toBeDefined();
58
- });
59
- it('creates task_checkpoints table', () => {
60
- runMigrations(db);
61
- const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='task_checkpoints'").get();
62
- expect(table).toBeDefined();
63
- });
64
- it('creates task_search FTS5 table', () => {
65
- runMigrations(db);
66
- const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='task_search'").get();
67
- expect(table).toBeDefined();
68
- });
69
- it('is idempotent', () => {
70
- runMigrations(db);
71
- runMigrations(db);
72
- const version = getCurrentVersion(db);
73
- expect(version).toBe(1);
74
- });
75
- });
76
- describe('projects table migration', () => {
77
- it('should create projects table', () => {
78
- const db = new Database(':memory:');
79
- db.exec(SCHEMA_V1);
80
- db.exec('DROP TABLE projects');
81
- db.exec('DROP INDEX IF EXISTS idx_projects_protected');
82
- db.prepare(`INSERT INTO tasks_current (task_id, title, project, status, created_at, updated_at, last_event_id)
83
- VALUES ('t1', 'Task 1', 'projectA', 'ready', datetime('now'), datetime('now'), 1)`).run();
84
- db.prepare(`INSERT INTO tasks_current (task_id, title, project, status, created_at, updated_at, last_event_id)
85
- VALUES ('t2', 'Task 2', 'projectB', 'ready', datetime('now'), datetime('now'), 2)`).run();
86
- runMigrations(db);
87
- const projects = db.prepare('SELECT name FROM projects ORDER BY name').all();
88
- expect(projects.map((p) => p.name)).toContain('inbox');
89
- expect(projects.map((p) => p.name)).toContain('projectA');
90
- expect(projects.map((p) => p.name)).toContain('projectB');
91
- const inbox = db
92
- .prepare('SELECT is_protected FROM projects WHERE name = ?')
93
- .get('inbox');
94
- expect(inbox.is_protected).toBe(1);
95
- db.close();
96
- });
97
- it('should emit synthetic ProjectCreated events for existing projects', () => {
98
- const db = new Database(':memory:');
99
- db.exec(SCHEMA_V1);
100
- db.exec('DROP TABLE projects');
101
- db.exec('DROP INDEX IF EXISTS idx_projects_protected');
102
- db.prepare(`INSERT INTO tasks_current (task_id, title, project, status, created_at, updated_at, last_event_id)
103
- VALUES ('t1', 'Task 1', 'myproject', 'ready', datetime('now'), datetime('now'), 1)`).run();
104
- runMigrations(db);
105
- const events = db
106
- .prepare(`SELECT * FROM events WHERE type = 'project_created'`)
107
- .all();
108
- expect(events.length).toBeGreaterThanOrEqual(2);
109
- const projectNames = events.map((e) => JSON.parse(e.data).name);
110
- expect(projectNames).toContain('inbox');
111
- expect(projectNames).toContain('myproject');
112
- db.close();
12
+ it('applies migrations successfully', () => {
13
+ const migrations = [
14
+ { id: 'v001', up: 'CREATE TABLE test1 (id INTEGER PRIMARY KEY)' },
15
+ { id: 'v002', up: 'CREATE TABLE test2 (id INTEGER PRIMARY KEY)' },
16
+ ];
17
+ const result = runMigrationsWithRollback(db, migrations);
18
+ expect(result.success).toBe(true);
19
+ expect(result.applied).toEqual(['v001', 'v002']);
20
+ // Tables should exist
21
+ const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all();
22
+ const tableNames = tables.map((t) => t.name);
23
+ expect(tableNames).toContain('test1');
24
+ expect(tableNames).toContain('test2');
25
+ });
26
+ it('rolls back on partial failure', () => {
27
+ const migrations = [
28
+ { id: 'v001', up: 'CREATE TABLE test1 (id INTEGER PRIMARY KEY)' },
29
+ { id: 'v002', up: 'INVALID SQL SYNTAX' }, // This will fail
30
+ ];
31
+ expect(() => runMigrationsWithRollback(db, migrations)).toThrow(MigrationError);
32
+ // test1 should NOT exist due to rollback
33
+ const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all();
34
+ const tableNames = tables.map((t) => t.name);
35
+ expect(tableNames).not.toContain('test1');
36
+ });
37
+ it('skips already applied migrations', () => {
38
+ const migrations = [
39
+ { id: 'v001', up: 'CREATE TABLE test1 (id INTEGER PRIMARY KEY)' },
40
+ ];
41
+ // Apply first time
42
+ runMigrationsWithRollback(db, migrations);
43
+ // Apply again - should skip
44
+ const result = runMigrationsWithRollback(db, migrations);
45
+ expect(result.success).toBe(true);
46
+ expect(result.applied).toEqual([]);
47
+ expect(result.skipped).toEqual(['v001']);
113
48
  });
114
49
  });
115
50
  //# sourceMappingURL=migrations.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.test.js","sourceRoot":"","sources":["../../src/db/migrations.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"migrations.test.js","sourceRoot":"","sources":["../../src/db/migrations.test.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
- export declare const SCHEMA_V1 = "\n-- Append-only event store (source of truth)\nCREATE TABLE IF NOT EXISTS events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n event_id TEXT NOT NULL UNIQUE,\n task_id TEXT NOT NULL,\n type TEXT NOT NULL,\n data TEXT NOT NULL CHECK (json_valid(data)),\n author TEXT,\n agent_id TEXT,\n session_id TEXT,\n correlation_id TEXT,\n causation_id TEXT,\n timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))\n);\n\n-- Track last applied event id per projection (enables incremental projection + doctor checks)\nCREATE TABLE IF NOT EXISTS projection_state (\n name TEXT PRIMARY KEY,\n last_event_id INTEGER NOT NULL DEFAULT 0,\n updated_at TEXT NOT NULL\n);\n\n-- Current-state projection for fast reads (rebuildable from events)\nCREATE TABLE IF NOT EXISTS tasks_current (\n task_id TEXT PRIMARY KEY,\n title TEXT NOT NULL,\n project TEXT NOT NULL,\n status TEXT NOT NULL CHECK (status IN ('backlog','ready','in_progress','done','archived')),\n parent_id TEXT,\n description TEXT,\n links TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(links)),\n tags TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(tags)),\n priority INTEGER NOT NULL DEFAULT 0 CHECK (priority BETWEEN 0 AND 3),\n due_at TEXT,\n metadata TEXT NOT NULL DEFAULT '{}' CHECK (json_valid(metadata)),\n claimed_at TEXT,\n claimed_by_author TEXT,\n claimed_by_agent_id TEXT,\n lease_until TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n last_event_id INTEGER NOT NULL\n);\n\n-- Dependency edges for fast availability checks (rebuildable from events)\nCREATE TABLE IF NOT EXISTS task_dependencies (\n task_id TEXT NOT NULL,\n depends_on_id TEXT NOT NULL,\n PRIMARY KEY (task_id, depends_on_id)\n);\n\n-- Fast tag filtering (rebuildable)\nCREATE TABLE IF NOT EXISTS task_tags (\n task_id TEXT NOT NULL,\n tag TEXT NOT NULL,\n PRIMARY KEY (task_id, tag)\n);\n\n-- Fast access for steering comments (rebuildable)\nCREATE TABLE IF NOT EXISTS task_comments (\n event_rowid INTEGER PRIMARY KEY,\n task_id TEXT NOT NULL,\n author TEXT,\n agent_id TEXT,\n text TEXT NOT NULL,\n timestamp TEXT NOT NULL\n);\n\n-- Fast access for checkpoints (rebuildable)\nCREATE TABLE IF NOT EXISTS task_checkpoints (\n event_rowid INTEGER PRIMARY KEY,\n task_id TEXT NOT NULL,\n name TEXT NOT NULL,\n data TEXT NOT NULL DEFAULT '{}' CHECK (json_valid(data)),\n timestamp TEXT NOT NULL\n);\n\n-- Full-text search over tasks (rebuildable)\nCREATE VIRTUAL TABLE IF NOT EXISTS task_search USING fts5(\n task_id UNINDEXED,\n title,\n description\n);\n\n-- Projects table (projection from events)\nCREATE TABLE IF NOT EXISTS projects (\n name TEXT PRIMARY KEY,\n description TEXT,\n is_protected INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL,\n last_event_id INTEGER NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_projects_protected ON projects(is_protected);\n\n-- Indexes for events\nCREATE INDEX IF NOT EXISTS idx_events_task_id ON events(task_id);\nCREATE INDEX IF NOT EXISTS idx_events_task_id_id ON events(task_id, id);\nCREATE INDEX IF NOT EXISTS idx_events_type ON events(type);\nCREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);\nCREATE INDEX IF NOT EXISTS idx_events_correlation_id ON events(correlation_id);\n\n-- Indexes for tasks_current\nCREATE INDEX IF NOT EXISTS idx_tasks_current_project_status ON tasks_current(project, status);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_priority ON tasks_current(project, priority, created_at);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_claim_next ON tasks_current(project, status, priority DESC, created_at ASC, task_id ASC);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_stuck ON tasks_current(project, status, claimed_at);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_parent ON tasks_current(parent_id);\n\n-- Indexes for dependencies\nCREATE INDEX IF NOT EXISTS idx_deps_depends_on ON task_dependencies(depends_on_id);\n\n-- Indexes for tags/comments/checkpoints\nCREATE INDEX IF NOT EXISTS idx_task_tags_tag ON task_tags(tag, task_id);\nCREATE INDEX IF NOT EXISTS idx_task_comments_task ON task_comments(task_id, event_rowid);\nCREATE INDEX IF NOT EXISTS idx_task_checkpoints_task ON task_checkpoints(task_id, event_rowid);\n";
1
+ export declare const EVENTS_SCHEMA_V2 = "\n-- Append-only event store (source of truth)\nCREATE TABLE IF NOT EXISTS events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n event_id TEXT NOT NULL UNIQUE,\n task_id TEXT NOT NULL,\n type TEXT NOT NULL,\n data TEXT NOT NULL CHECK (json_valid(data)),\n author TEXT,\n agent_id TEXT,\n session_id TEXT,\n correlation_id TEXT,\n causation_id TEXT,\n timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))\n);\n\n-- Append-only enforcement: prevent UPDATE on events\nCREATE TRIGGER IF NOT EXISTS events_no_update\nBEFORE UPDATE ON events\nBEGIN\n SELECT RAISE(ABORT, 'Events table is append-only: cannot UPDATE');\nEND;\n\n-- Append-only enforcement: prevent DELETE on events\nCREATE TRIGGER IF NOT EXISTS events_no_delete\nBEFORE DELETE ON events\nBEGIN\n SELECT RAISE(ABORT, 'Events table is append-only: cannot DELETE');\nEND;\n\n-- Global metadata (synced, immutable after creation)\nCREATE TABLE IF NOT EXISTS hzl_global_meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\n-- Schema migrations tracking (append-only)\nCREATE TABLE IF NOT EXISTS schema_migrations (\n migration_id TEXT PRIMARY KEY,\n applied_at_ms INTEGER NOT NULL,\n checksum TEXT NOT NULL\n);\n\n-- Indexes for events\nCREATE INDEX IF NOT EXISTS idx_events_task_id ON events(task_id);\nCREATE INDEX IF NOT EXISTS idx_events_task_id_id ON events(task_id, id);\nCREATE INDEX IF NOT EXISTS idx_events_type ON events(type);\nCREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);\nCREATE INDEX IF NOT EXISTS idx_events_correlation_id ON events(correlation_id);\n";
2
+ export declare const CACHE_SCHEMA_V1 = "\n-- Local metadata (per-device sync state)\nCREATE TABLE IF NOT EXISTS hzl_local_meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\n-- Projection cursor (tracks last applied event)\nCREATE TABLE IF NOT EXISTS projection_cursor (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\n-- Track last applied event id per projection\nCREATE TABLE IF NOT EXISTS projection_state (\n name TEXT PRIMARY KEY,\n last_event_id INTEGER NOT NULL DEFAULT 0,\n updated_at TEXT NOT NULL\n);\n\n-- Current-state projection for fast reads (rebuildable from events)\nCREATE TABLE IF NOT EXISTS tasks_current (\n task_id TEXT PRIMARY KEY,\n title TEXT NOT NULL,\n project TEXT NOT NULL,\n status TEXT NOT NULL CHECK (status IN ('backlog','ready','in_progress','done','archived')),\n parent_id TEXT,\n description TEXT,\n links TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(links)),\n tags TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(tags)),\n priority INTEGER NOT NULL DEFAULT 0 CHECK (priority BETWEEN 0 AND 3),\n due_at TEXT,\n metadata TEXT NOT NULL DEFAULT '{}' CHECK (json_valid(metadata)),\n claimed_at TEXT,\n claimed_by_author TEXT,\n claimed_by_agent_id TEXT,\n lease_until TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n last_event_id INTEGER NOT NULL\n);\n\n-- Dependency edges for fast availability checks (rebuildable)\nCREATE TABLE IF NOT EXISTS task_dependencies (\n task_id TEXT NOT NULL,\n depends_on_id TEXT NOT NULL,\n PRIMARY KEY (task_id, depends_on_id)\n);\n\n-- Fast tag filtering (rebuildable)\nCREATE TABLE IF NOT EXISTS task_tags (\n task_id TEXT NOT NULL,\n tag TEXT NOT NULL,\n PRIMARY KEY (task_id, tag)\n);\n\n-- Fast access for steering comments (rebuildable)\nCREATE TABLE IF NOT EXISTS task_comments (\n event_rowid INTEGER PRIMARY KEY,\n task_id TEXT NOT NULL,\n author TEXT,\n agent_id TEXT,\n text TEXT NOT NULL,\n timestamp TEXT NOT NULL\n);\n\n-- Fast access for checkpoints (rebuildable)\nCREATE TABLE IF NOT EXISTS task_checkpoints (\n event_rowid INTEGER PRIMARY KEY,\n task_id TEXT NOT NULL,\n name TEXT NOT NULL,\n data TEXT NOT NULL DEFAULT '{}' CHECK (json_valid(data)),\n timestamp TEXT NOT NULL\n);\n\n-- Full-text search over tasks (rebuildable)\nCREATE VIRTUAL TABLE IF NOT EXISTS task_search USING fts5(\n task_id UNINDEXED,\n title,\n description\n);\n\n-- Projects table (projection from events)\nCREATE TABLE IF NOT EXISTS projects (\n name TEXT PRIMARY KEY,\n description TEXT,\n is_protected INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL,\n last_event_id INTEGER NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_projects_protected ON projects(is_protected);\n\n-- Indexes for tasks_current\nCREATE INDEX IF NOT EXISTS idx_tasks_current_project_status ON tasks_current(project, status);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_priority ON tasks_current(project, priority, created_at);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_claim_next ON tasks_current(project, status, priority DESC, created_at ASC, task_id ASC);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_stuck ON tasks_current(project, status, claimed_at);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_parent ON tasks_current(parent_id);\n\n-- Indexes for dependencies\nCREATE INDEX IF NOT EXISTS idx_deps_depends_on ON task_dependencies(depends_on_id);\n\n-- Indexes for tags/comments/checkpoints\nCREATE INDEX IF NOT EXISTS idx_task_tags_tag ON task_tags(tag, task_id);\nCREATE INDEX IF NOT EXISTS idx_task_comments_task ON task_comments(task_id, event_rowid);\nCREATE INDEX IF NOT EXISTS idx_task_checkpoints_task ON task_checkpoints(task_id, event_rowid);\n";
2
3
  export declare const PRAGMAS = "\nPRAGMA journal_mode=WAL;\nPRAGMA synchronous=NORMAL;\nPRAGMA foreign_keys=ON;\nPRAGMA busy_timeout=5000;\n";
3
4
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,wjJAqHrB,CAAC;AAEF,eAAO,MAAM,OAAO,iHAKnB,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,gBAAgB,srDAiD5B,CAAC;AAGF,eAAO,MAAM,eAAe,y4HA2G3B,CAAC;AAEF,eAAO,MAAM,OAAO,iHAKnB,CAAC"}
package/dist/db/schema.js CHANGED
@@ -1,4 +1,5 @@
1
- export const SCHEMA_V1 = `
1
+ // Schema for events.db (source of truth)
2
+ export const EVENTS_SCHEMA_V2 = `
2
3
  -- Append-only event store (source of truth)
3
4
  CREATE TABLE IF NOT EXISTS events (
4
5
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -14,7 +15,55 @@ CREATE TABLE IF NOT EXISTS events (
14
15
  timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
15
16
  );
16
17
 
17
- -- Track last applied event id per projection (enables incremental projection + doctor checks)
18
+ -- Append-only enforcement: prevent UPDATE on events
19
+ CREATE TRIGGER IF NOT EXISTS events_no_update
20
+ BEFORE UPDATE ON events
21
+ BEGIN
22
+ SELECT RAISE(ABORT, 'Events table is append-only: cannot UPDATE');
23
+ END;
24
+
25
+ -- Append-only enforcement: prevent DELETE on events
26
+ CREATE TRIGGER IF NOT EXISTS events_no_delete
27
+ BEFORE DELETE ON events
28
+ BEGIN
29
+ SELECT RAISE(ABORT, 'Events table is append-only: cannot DELETE');
30
+ END;
31
+
32
+ -- Global metadata (synced, immutable after creation)
33
+ CREATE TABLE IF NOT EXISTS hzl_global_meta (
34
+ key TEXT PRIMARY KEY,
35
+ value TEXT NOT NULL
36
+ );
37
+
38
+ -- Schema migrations tracking (append-only)
39
+ CREATE TABLE IF NOT EXISTS schema_migrations (
40
+ migration_id TEXT PRIMARY KEY,
41
+ applied_at_ms INTEGER NOT NULL,
42
+ checksum TEXT NOT NULL
43
+ );
44
+
45
+ -- Indexes for events
46
+ CREATE INDEX IF NOT EXISTS idx_events_task_id ON events(task_id);
47
+ CREATE INDEX IF NOT EXISTS idx_events_task_id_id ON events(task_id, id);
48
+ CREATE INDEX IF NOT EXISTS idx_events_type ON events(type);
49
+ CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
50
+ CREATE INDEX IF NOT EXISTS idx_events_correlation_id ON events(correlation_id);
51
+ `;
52
+ // Cache schema (local-only, rebuildable)
53
+ export const CACHE_SCHEMA_V1 = `
54
+ -- Local metadata (per-device sync state)
55
+ CREATE TABLE IF NOT EXISTS hzl_local_meta (
56
+ key TEXT PRIMARY KEY,
57
+ value TEXT NOT NULL
58
+ );
59
+
60
+ -- Projection cursor (tracks last applied event)
61
+ CREATE TABLE IF NOT EXISTS projection_cursor (
62
+ key TEXT PRIMARY KEY,
63
+ value TEXT NOT NULL
64
+ );
65
+
66
+ -- Track last applied event id per projection
18
67
  CREATE TABLE IF NOT EXISTS projection_state (
19
68
  name TEXT PRIMARY KEY,
20
69
  last_event_id INTEGER NOT NULL DEFAULT 0,
@@ -43,7 +92,7 @@ CREATE TABLE IF NOT EXISTS tasks_current (
43
92
  last_event_id INTEGER NOT NULL
44
93
  );
45
94
 
46
- -- Dependency edges for fast availability checks (rebuildable from events)
95
+ -- Dependency edges for fast availability checks (rebuildable)
47
96
  CREATE TABLE IF NOT EXISTS task_dependencies (
48
97
  task_id TEXT NOT NULL,
49
98
  depends_on_id TEXT NOT NULL,
@@ -94,13 +143,6 @@ CREATE TABLE IF NOT EXISTS projects (
94
143
 
95
144
  CREATE INDEX IF NOT EXISTS idx_projects_protected ON projects(is_protected);
96
145
 
97
- -- Indexes for events
98
- CREATE INDEX IF NOT EXISTS idx_events_task_id ON events(task_id);
99
- CREATE INDEX IF NOT EXISTS idx_events_task_id_id ON events(task_id, id);
100
- CREATE INDEX IF NOT EXISTS idx_events_type ON events(type);
101
- CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
102
- CREATE INDEX IF NOT EXISTS idx_events_correlation_id ON events(correlation_id);
103
-
104
146
  -- Indexes for tasks_current
105
147
  CREATE INDEX IF NOT EXISTS idx_tasks_current_project_status ON tasks_current(project, status);
106
148
  CREATE INDEX IF NOT EXISTS idx_tasks_current_priority ON tasks_current(project, priority, created_at);
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqHxB,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAAG;;;;;CAKtB,CAAC"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiD/B,CAAC;AAEF,yCAAyC;AACzC,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2G9B,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAAG;;;;;CAKtB,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { SyncConfig, SyncPolicy as SyncPolicyType } from './types.js';
2
+ export interface SyncState {
3
+ lastSyncAt: number | null;
4
+ isDirty: boolean;
5
+ lastSyncAttemptAt?: number | null;
6
+ lastSyncError?: string | null;
7
+ }
8
+ export interface AfterWriteState {
9
+ isDirty: boolean;
10
+ lastSyncAttemptAt?: number | null;
11
+ }
12
+ export type SyncTrigger = 'stale' | 'dirty' | 'forced' | 'none';
13
+ export type SyncErrorAction = 'continue' | 'fail';
14
+ export interface SyncPolicy {
15
+ type: SyncPolicyType;
16
+ shouldSyncBefore(state: SyncState): boolean;
17
+ shouldSyncAfter(state: AfterWriteState): boolean;
18
+ onSyncError(error: Error): SyncErrorAction;
19
+ }
20
+ export declare function createSyncPolicy(config?: Partial<SyncConfig>): SyncPolicy;
21
+ //# sourceMappingURL=sync-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-policy.d.ts","sourceRoot":"","sources":["../../src/db/sync-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,YAAY,CAAC;AAE3E,MAAM,WAAW,SAAS;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAChE,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,MAAM,CAAC;AAElD,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,cAAc,CAAC;IACrB,gBAAgB,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC;IAC5C,eAAe,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC;IACjD,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,eAAe,CAAC;CAC9C;AAMD,wBAAgB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,UAAU,CAAM,GAAG,UAAU,CAqE7E"}
@@ -0,0 +1,62 @@
1
+ const DEFAULT_STALE_AFTER_MS = 60000;
2
+ const DEFAULT_MIN_INTERVAL_MS = 15000;
3
+ const DEFAULT_FAILURE_BACKOFF_MS = 60000;
4
+ export function createSyncPolicy(config = {}) {
5
+ const policyType = config.policy ?? 'opportunistic';
6
+ const staleAfterMs = config.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
7
+ const minIntervalMs = config.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS;
8
+ const failureBackoffMs = config.failureBackoffMs ?? DEFAULT_FAILURE_BACKOFF_MS;
9
+ if (policyType === 'manual') {
10
+ return {
11
+ type: 'manual',
12
+ shouldSyncBefore: () => false,
13
+ shouldSyncAfter: () => false,
14
+ onSyncError: () => 'continue',
15
+ };
16
+ }
17
+ if (policyType === 'strict') {
18
+ return {
19
+ type: 'strict',
20
+ shouldSyncBefore: () => true,
21
+ shouldSyncAfter: () => true,
22
+ onSyncError: () => 'fail',
23
+ };
24
+ }
25
+ // Opportunistic policy
26
+ return {
27
+ type: 'opportunistic',
28
+ shouldSyncBefore(state) {
29
+ const now = Date.now();
30
+ // Check for failure backoff
31
+ if (state.lastSyncError && state.lastSyncAttemptAt) {
32
+ const timeSinceAttempt = now - state.lastSyncAttemptAt;
33
+ if (timeSinceAttempt < failureBackoffMs) {
34
+ return false;
35
+ }
36
+ }
37
+ // Never synced - should sync
38
+ if (state.lastSyncAt === null) {
39
+ return true;
40
+ }
41
+ // Check if stale
42
+ const timeSinceSync = now - state.lastSyncAt;
43
+ return timeSinceSync > staleAfterMs;
44
+ },
45
+ shouldSyncAfter(state) {
46
+ if (!state.isDirty) {
47
+ return false;
48
+ }
49
+ const now = Date.now();
50
+ // Check min interval
51
+ if (state.lastSyncAttemptAt) {
52
+ const timeSinceAttempt = now - state.lastSyncAttemptAt;
53
+ if (timeSinceAttempt < minIntervalMs) {
54
+ return false;
55
+ }
56
+ }
57
+ return true;
58
+ },
59
+ onSyncError: () => 'continue',
60
+ };
61
+ }
62
+ //# sourceMappingURL=sync-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-policy.js","sourceRoot":"","sources":["../../src/db/sync-policy.ts"],"names":[],"mappings":"AAwBA,MAAM,sBAAsB,GAAG,KAAK,CAAC;AACrC,MAAM,uBAAuB,GAAG,KAAK,CAAC;AACtC,MAAM,0BAA0B,GAAG,KAAK,CAAC;AAEzC,MAAM,UAAU,gBAAgB,CAAC,SAA8B,EAAE;IAC7D,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,IAAI,eAAe,CAAC;IACpD,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,sBAAsB,CAAC;IACnE,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,IAAI,uBAAuB,CAAC;IACtE,MAAM,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,IAAI,0BAA0B,CAAC;IAE/E,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO;YACH,IAAI,EAAE,QAAQ;YACd,gBAAgB,EAAE,GAAG,EAAE,CAAC,KAAK;YAC7B,eAAe,EAAE,GAAG,EAAE,CAAC,KAAK;YAC5B,WAAW,EAAE,GAAG,EAAE,CAAC,UAAU;SAChC,CAAC;IACN,CAAC;IAED,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO;YACH,IAAI,EAAE,QAAQ;YACd,gBAAgB,EAAE,GAAG,EAAE,CAAC,IAAI;YAC5B,eAAe,EAAE,GAAG,EAAE,CAAC,IAAI;YAC3B,WAAW,EAAE,GAAG,EAAE,CAAC,MAAM;SAC5B,CAAC;IACN,CAAC;IAED,uBAAuB;IACvB,OAAO;QACH,IAAI,EAAE,eAAe;QAErB,gBAAgB,CAAC,KAAgB;YAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAEvB,4BAA4B;YAC5B,IAAI,KAAK,CAAC,aAAa,IAAI,KAAK,CAAC,iBAAiB,EAAE,CAAC;gBACjD,MAAM,gBAAgB,GAAG,GAAG,GAAG,KAAK,CAAC,iBAAiB,CAAC;gBACvD,IAAI,gBAAgB,GAAG,gBAAgB,EAAE,CAAC;oBACtC,OAAO,KAAK,CAAC;gBACjB,CAAC;YACL,CAAC;YAED,6BAA6B;YAC7B,IAAI,KAAK,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;gBAC5B,OAAO,IAAI,CAAC;YAChB,CAAC;YAED,iBAAiB;YACjB,MAAM,aAAa,GAAG,GAAG,GAAG,KAAK,CAAC,UAAU,CAAC;YAC7C,OAAO,aAAa,GAAG,YAAY,CAAC;QACxC,CAAC;QAED,eAAe,CAAC,KAAsB;YAClC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;gBACjB,OAAO,KAAK,CAAC;YACjB,CAAC;YAED,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAEvB,qBAAqB;YACrB,IAAI,KAAK,CAAC,iBAAiB,EAAE,CAAC;gBAC1B,MAAM,gBAAgB,GAAG,GAAG,GAAG,KAAK,CAAC,iBAAiB,CAAC;gBACvD,IAAI,gBAAgB,GAAG,aAAa,EAAE,CAAC;oBACnC,OAAO,KAAK,CAAC;gBACjB,CAAC;YACL,CAAC;YAED,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,WAAW,EAAE,GAAG,EAAE,CAAC,UAAU;KAChC,CAAC;AACN,CAAC"}