local-lite 0.0.19 → 0.0.21

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 (69) hide show
  1. package/README.md +5 -1
  2. package/dist/actions/store.js +1 -1
  3. package/dist/auth/index.d.ts +1 -0
  4. package/dist/auth/index.js +1 -0
  5. package/dist/auth/session.d.ts +107 -0
  6. package/dist/auth/session.js +259 -0
  7. package/dist/core/client.d.ts +18 -0
  8. package/dist/core/client.js +55 -2
  9. package/dist/index.d.ts +5 -0
  10. package/dist/index.js +5 -0
  11. package/dist/keys/generator.d.ts +38 -0
  12. package/dist/keys/generator.js +136 -0
  13. package/dist/keys/index.d.ts +5 -0
  14. package/dist/keys/index.js +21 -0
  15. package/dist/keys/manager.d.ts +59 -0
  16. package/dist/keys/manager.js +127 -0
  17. package/dist/keys/signed-token.d.ts +18 -0
  18. package/dist/keys/signed-token.js +171 -0
  19. package/dist/keys/store.d.ts +23 -0
  20. package/dist/keys/store.js +109 -0
  21. package/dist/keys/types.d.ts +95 -0
  22. package/dist/keys/types.js +2 -0
  23. package/dist/kv/index.d.ts +3 -0
  24. package/dist/kv/index.js +19 -0
  25. package/dist/kv/manager.d.ts +52 -0
  26. package/dist/kv/manager.js +164 -0
  27. package/dist/kv/store.d.ts +13 -0
  28. package/dist/kv/store.js +85 -0
  29. package/dist/kv/types.d.ts +27 -0
  30. package/dist/kv/types.js +2 -0
  31. package/dist/react/index.d.ts +3 -0
  32. package/dist/react/index.js +3 -0
  33. package/dist/react/kv.d.ts +9 -0
  34. package/dist/react/kv.js +53 -0
  35. package/dist/react/search.d.ts +19 -0
  36. package/dist/react/search.js +49 -0
  37. package/dist/react/session.d.ts +26 -0
  38. package/dist/react/session.js +118 -0
  39. package/dist/reactive/event-emitter.d.ts +1 -0
  40. package/dist/reactive/event-emitter.js +9 -6
  41. package/dist/scheduler/cron.d.ts +6 -0
  42. package/dist/scheduler/cron.js +62 -0
  43. package/dist/scheduler/index.d.ts +4 -0
  44. package/dist/scheduler/index.js +20 -0
  45. package/dist/scheduler/manager.d.ts +59 -0
  46. package/dist/scheduler/manager.js +186 -0
  47. package/dist/scheduler/store.d.ts +20 -0
  48. package/dist/scheduler/store.js +134 -0
  49. package/dist/scheduler/types.d.ts +44 -0
  50. package/dist/scheduler/types.js +2 -0
  51. package/dist/search/index.d.ts +3 -0
  52. package/dist/search/index.js +19 -0
  53. package/dist/search/manager.d.ts +42 -0
  54. package/dist/search/manager.js +123 -0
  55. package/dist/search/store.d.ts +33 -0
  56. package/dist/search/store.js +137 -0
  57. package/dist/search/types.d.ts +49 -0
  58. package/dist/search/types.js +2 -0
  59. package/dist/storage/driver.d.ts +25 -0
  60. package/dist/storage/driver.js +92 -0
  61. package/dist/storage/index.d.ts +4 -0
  62. package/dist/storage/index.js +20 -0
  63. package/dist/storage/manager.d.ts +48 -0
  64. package/dist/storage/manager.js +149 -0
  65. package/dist/storage/store.d.ts +14 -0
  66. package/dist/storage/store.js +116 -0
  67. package/dist/storage/types.d.ts +44 -0
  68. package/dist/storage/types.js +2 -0
  69. package/package.json +26 -1
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useSession = useSession;
4
+ const react_1 = require("react");
5
+ /**
6
+ * Reactive React hook for persistent, offline-first user session management.
7
+ * The session remains authenticated even when offline, ensuring uninterrupted UX.
8
+ *
9
+ * Example:
10
+ * const { user, isAuthenticated, isOffline, signOut } = useSession(client);
11
+ */
12
+ function useSession(client) {
13
+ const [session, setSession] = (0, react_1.useState)(null);
14
+ const [loading, setLoading] = (0, react_1.useState)(true);
15
+ const [isOffline, setIsOffline] = (0, react_1.useState)(false);
16
+ const loadSession = (0, react_1.useCallback)(async () => {
17
+ if (!client) {
18
+ setSession(null);
19
+ setLoading(false);
20
+ return null;
21
+ }
22
+ try {
23
+ const active = await client.session.getActiveSession();
24
+ setSession(active);
25
+ return active;
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ finally {
31
+ setLoading(false);
32
+ }
33
+ }, [client]);
34
+ (0, react_1.useEffect)(() => {
35
+ if (!client) {
36
+ setSession(null);
37
+ setLoading(false);
38
+ return;
39
+ }
40
+ let isMounted = true;
41
+ // Check network
42
+ const checkNetwork = () => {
43
+ const net = client.syncWorker?.networkStatusProvider;
44
+ if (net && typeof net.isOnline === 'function') {
45
+ const res = net.isOnline();
46
+ if (typeof res === 'boolean') {
47
+ if (isMounted)
48
+ setIsOffline(!res);
49
+ }
50
+ else if (res && typeof res.then === 'function') {
51
+ res.then((online) => {
52
+ if (isMounted)
53
+ setIsOffline(!online);
54
+ });
55
+ }
56
+ }
57
+ };
58
+ checkNetwork();
59
+ loadSession();
60
+ // Listen to session changes
61
+ const unsubscribeSession = client.events.on('_local_sessions', () => {
62
+ if (isMounted) {
63
+ loadSession();
64
+ }
65
+ });
66
+ // Listen to network changes if available
67
+ const unsubscribeNetwork = client.events.on('network', (status) => {
68
+ if (isMounted && status && typeof status.online === 'boolean') {
69
+ setIsOffline(!status.online);
70
+ }
71
+ });
72
+ return () => {
73
+ isMounted = false;
74
+ unsubscribeSession();
75
+ if (typeof unsubscribeNetwork === 'function') {
76
+ unsubscribeNetwork();
77
+ }
78
+ };
79
+ }, [client, loadSession]);
80
+ const createSession = (0, react_1.useCallback)(async (options) => {
81
+ if (!client)
82
+ throw new Error('Client not provided to useSession.');
83
+ const res = await client.session.createSession(options);
84
+ setSession(res);
85
+ return res;
86
+ }, [client]);
87
+ const updateUser = (0, react_1.useCallback)(async (partialUser) => {
88
+ if (!client)
89
+ return null;
90
+ const res = await client.session.updateUser(partialUser);
91
+ setSession(res);
92
+ return res;
93
+ }, [client]);
94
+ const updateTokens = (0, react_1.useCallback)(async (tokens) => {
95
+ if (!client)
96
+ return;
97
+ await client.session.updateTokens(tokens);
98
+ await loadSession();
99
+ }, [client, loadSession]);
100
+ const signOut = (0, react_1.useCallback)(async () => {
101
+ if (!client)
102
+ return;
103
+ await client.session.clearSession();
104
+ setSession(null);
105
+ }, [client]);
106
+ return {
107
+ session,
108
+ user: session ? session.user : null,
109
+ isAuthenticated: session !== null,
110
+ isOffline,
111
+ loading,
112
+ createSession,
113
+ updateUser,
114
+ updateTokens,
115
+ signOut,
116
+ refetchSession: loadSession,
117
+ };
118
+ }
@@ -2,6 +2,7 @@ type EventCallback = (data: any) => void;
2
2
  export declare class EventEmitter {
3
3
  private listeners;
4
4
  on(event: string, callback: EventCallback): () => void;
5
+ off(event: string, callback: EventCallback): void;
5
6
  emit(event: string, data: any): void;
6
7
  }
7
8
  export {};
@@ -9,14 +9,17 @@ class EventEmitter {
9
9
  }
10
10
  this.listeners.get(event).add(callback);
11
11
  return () => {
12
- const eventSet = this.listeners.get(event);
13
- if (eventSet) {
14
- eventSet.delete(callback);
15
- if (eventSet.size === 0)
16
- this.listeners.delete(event);
17
- }
12
+ this.off(event, callback);
18
13
  };
19
14
  }
15
+ off(event, callback) {
16
+ const eventSet = this.listeners.get(event);
17
+ if (eventSet) {
18
+ eventSet.delete(callback);
19
+ if (eventSet.size === 0)
20
+ this.listeners.delete(event);
21
+ }
22
+ }
20
23
  emit(event, data) {
21
24
  const eventSet = this.listeners.get(event);
22
25
  if (eventSet) {
@@ -0,0 +1,6 @@
1
+ import { ScheduleType } from './types.js';
2
+ export declare function parseScheduleToIntervalMs(schedule: ScheduleType): number;
3
+ /**
4
+ * Calculates next run timestamp given schedule and last run (or current) timestamp.
5
+ */
6
+ export declare function calculateNextRun(schedule: ScheduleType, fromTimestamp?: number): number;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseScheduleToIntervalMs = parseScheduleToIntervalMs;
4
+ exports.calculateNextRun = calculateNextRun;
5
+ function parseScheduleToIntervalMs(schedule) {
6
+ if (typeof schedule === 'object' && schedule !== null && 'intervalMs' in schedule) {
7
+ return Math.max(1000, Number(schedule.intervalMs) || 60000);
8
+ }
9
+ if (typeof schedule === 'number') {
10
+ return Math.max(1000, schedule);
11
+ }
12
+ const str = String(schedule).trim().toLowerCase();
13
+ if (str === 'hourly')
14
+ return 60 * 60 * 1000;
15
+ if (str === 'daily')
16
+ return 24 * 60 * 60 * 1000;
17
+ if (str === 'weekly')
18
+ return 7 * 24 * 60 * 60 * 1000;
19
+ // 'every X seconds/minutes/hours/days'
20
+ const match = str.match(/^every\s+(\d+)\s*(s|sec|seconds?|m|min|minutes?|h|hours?|d|days?)$/i);
21
+ if (match) {
22
+ const val = parseInt(match[1], 10);
23
+ const unit = match[2].toLowerCase();
24
+ if (unit.startsWith('s'))
25
+ return val * 1000;
26
+ if (unit.startsWith('m'))
27
+ return val * 60 * 1000;
28
+ if (unit.startsWith('h'))
29
+ return val * 60 * 60 * 1000;
30
+ if (unit.startsWith('d'))
31
+ return val * 24 * 60 * 60 * 1000;
32
+ }
33
+ // 5-field cron expression: min hour dom month dow
34
+ const parts = str.split(/\s+/);
35
+ if (parts.length === 5) {
36
+ // If it's */n in minutes
37
+ if (parts[0].startsWith('*/')) {
38
+ const step = parseInt(parts[0].slice(2), 10);
39
+ if (!isNaN(step) && step > 0) {
40
+ return step * 60 * 1000;
41
+ }
42
+ }
43
+ // If it's */n in hours (e.g. 0 */2 * * *)
44
+ if (parts[1].startsWith('*/')) {
45
+ const step = parseInt(parts[1].slice(2), 10);
46
+ if (!isNaN(step) && step > 0) {
47
+ return step * 60 * 60 * 1000;
48
+ }
49
+ }
50
+ // Default fallback for daily cron like 0 9 * * * or 0 0 * * *
51
+ return 24 * 60 * 60 * 1000;
52
+ }
53
+ // Default fallback: 1 hour
54
+ return 60 * 60 * 1000;
55
+ }
56
+ /**
57
+ * Calculates next run timestamp given schedule and last run (or current) timestamp.
58
+ */
59
+ function calculateNextRun(schedule, fromTimestamp = Date.now()) {
60
+ const interval = parseScheduleToIntervalMs(schedule);
61
+ return fromTimestamp + interval;
62
+ }
@@ -0,0 +1,4 @@
1
+ export * from './types.js';
2
+ export * from './cron.js';
3
+ export * from './store.js';
4
+ export * from './manager.js';
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types.js"), exports);
18
+ __exportStar(require("./cron.js"), exports);
19
+ __exportStar(require("./store.js"), exports);
20
+ __exportStar(require("./manager.js"), exports);
@@ -0,0 +1,59 @@
1
+ import { DBExecutable } from '../core/schema-manager.js';
2
+ import { JobDefinition, JobRecord, JobRunRecord, JobSchedulerOptions } from './types.js';
3
+ export declare class JobScheduler {
4
+ private db;
5
+ private handlers;
6
+ private timer;
7
+ private isTicking;
8
+ private checkIntervalMs;
9
+ constructor(db: DBExecutable, options?: JobSchedulerOptions);
10
+ initialize(): Promise<void>;
11
+ /**
12
+ * Registers a background recurring job and stores its schedule in SQLite.
13
+ */
14
+ register(job: JobDefinition): Promise<JobRecord>;
15
+ /**
16
+ * Attaches or updates a handler function for an already registered job id.
17
+ */
18
+ setHandler(jobId: string, handler: (job: JobRecord) => Promise<void> | void): void;
19
+ /**
20
+ * Removes a job from schedule and handlers.
21
+ */
22
+ unregister(jobId: string): Promise<void>;
23
+ /**
24
+ * Enables a paused job.
25
+ */
26
+ enable(jobId: string): Promise<void>;
27
+ /**
28
+ * Disables/pauses a job.
29
+ */
30
+ disable(jobId: string): Promise<void>;
31
+ /**
32
+ * Manually triggers a job immediately, logging execution in run history.
33
+ */
34
+ trigger(jobId: string): Promise<JobRunRecord>;
35
+ /**
36
+ * Checks for due jobs and executes them. Safe against reentrant concurrent ticks.
37
+ */
38
+ tick(): Promise<number>;
39
+ /**
40
+ * Starts periodic background ticker.
41
+ */
42
+ start(intervalMs?: number): void;
43
+ /**
44
+ * Stops the background periodic ticker.
45
+ */
46
+ stop(): void;
47
+ /**
48
+ * Returns all scheduled jobs.
49
+ */
50
+ getJobs(): Promise<JobRecord[]>;
51
+ /**
52
+ * Returns run history for a specific job or all jobs.
53
+ */
54
+ getHistory(jobId?: string, limit?: number): Promise<JobRunRecord[]>;
55
+ /**
56
+ * Prunes historical run logs older than the given age in milliseconds (default: 30 days).
57
+ */
58
+ pruneHistory(maxAgeMs?: number): Promise<void>;
59
+ }
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JobScheduler = void 0;
4
+ const uuid_js_1 = require("../utils/uuid.js");
5
+ const cron_js_1 = require("./cron.js");
6
+ const store_js_1 = require("./store.js");
7
+ class JobScheduler {
8
+ db;
9
+ handlers = new Map();
10
+ timer = null;
11
+ isTicking = false;
12
+ checkIntervalMs;
13
+ constructor(db, options = {}) {
14
+ this.db = db;
15
+ this.checkIntervalMs = options.checkIntervalMs ?? 30000;
16
+ }
17
+ async initialize() {
18
+ await store_js_1.JobStore.ensureSchema(this.db);
19
+ }
20
+ /**
21
+ * Registers a background recurring job and stores its schedule in SQLite.
22
+ */
23
+ async register(job) {
24
+ const intervalMs = (0, cron_js_1.parseScheduleToIntervalMs)(job.schedule);
25
+ const now = Date.now();
26
+ const nextRunAt = (0, cron_js_1.calculateNextRun)(job.schedule, now);
27
+ const record = {
28
+ id: job.id,
29
+ name: job.name || job.id,
30
+ schedule: typeof job.schedule === 'string' ? job.schedule : `${intervalMs}ms`,
31
+ intervalMs,
32
+ lastRunAt: null,
33
+ nextRunAt,
34
+ enabled: job.enabled !== false,
35
+ payload: job.payload,
36
+ createdAt: now,
37
+ };
38
+ if (job.handler) {
39
+ this.handlers.set(job.id, job.handler);
40
+ }
41
+ await store_js_1.JobStore.upsertJob(this.db, undefined, record);
42
+ return record;
43
+ }
44
+ /**
45
+ * Attaches or updates a handler function for an already registered job id.
46
+ */
47
+ setHandler(jobId, handler) {
48
+ this.handlers.set(jobId, handler);
49
+ }
50
+ /**
51
+ * Removes a job from schedule and handlers.
52
+ */
53
+ async unregister(jobId) {
54
+ this.handlers.delete(jobId);
55
+ await store_js_1.JobStore.deleteJob(this.db, undefined, jobId);
56
+ }
57
+ /**
58
+ * Enables a paused job.
59
+ */
60
+ async enable(jobId) {
61
+ await store_js_1.JobStore.setJobEnabled(this.db, undefined, jobId, true);
62
+ }
63
+ /**
64
+ * Disables/pauses a job.
65
+ */
66
+ async disable(jobId) {
67
+ await store_js_1.JobStore.setJobEnabled(this.db, undefined, jobId, false);
68
+ }
69
+ /**
70
+ * Manually triggers a job immediately, logging execution in run history.
71
+ */
72
+ async trigger(jobId) {
73
+ const job = await store_js_1.JobStore.getJob(this.db, undefined, jobId);
74
+ if (!job) {
75
+ throw new Error(`Job "${jobId}" not found.`);
76
+ }
77
+ const runId = (0, uuid_js_1.generateUUID)();
78
+ const startedAt = Date.now();
79
+ const runRecord = {
80
+ id: runId,
81
+ jobId,
82
+ status: 'RUNNING',
83
+ startedAt,
84
+ finishedAt: null,
85
+ durationMs: null,
86
+ };
87
+ await store_js_1.JobStore.recordRunStart(this.db, undefined, runRecord);
88
+ const handler = this.handlers.get(jobId);
89
+ let runStatus = 'SUCCESS';
90
+ let runError = null;
91
+ try {
92
+ if (handler) {
93
+ await handler(job);
94
+ }
95
+ }
96
+ catch (err) {
97
+ runStatus = 'FAILED';
98
+ runError = err?.message || String(err);
99
+ }
100
+ const finishedAt = Date.now();
101
+ const durationMs = finishedAt - startedAt;
102
+ await store_js_1.JobStore.recordRunFinish(this.db, undefined, runId, runStatus, finishedAt, durationMs, runError);
103
+ const nextRunAt = (0, cron_js_1.calculateNextRun)(job.schedule, finishedAt);
104
+ await store_js_1.JobStore.updateJobSchedule(this.db, undefined, jobId, finishedAt, nextRunAt);
105
+ return {
106
+ id: runId,
107
+ jobId,
108
+ status: runStatus,
109
+ startedAt,
110
+ finishedAt,
111
+ durationMs,
112
+ error: runError,
113
+ };
114
+ }
115
+ /**
116
+ * Checks for due jobs and executes them. Safe against reentrant concurrent ticks.
117
+ */
118
+ async tick() {
119
+ if (this.isTicking)
120
+ return 0;
121
+ this.isTicking = true;
122
+ try {
123
+ const now = Date.now();
124
+ const dueJobs = await store_js_1.JobStore.getDueJobs(this.db, undefined, now);
125
+ if (dueJobs.length === 0)
126
+ return 0;
127
+ let executed = 0;
128
+ for (const job of dueJobs) {
129
+ try {
130
+ await this.trigger(job.id);
131
+ executed++;
132
+ }
133
+ catch {
134
+ // Failure logged in trigger; continue next job
135
+ }
136
+ }
137
+ return executed;
138
+ }
139
+ finally {
140
+ this.isTicking = false;
141
+ }
142
+ }
143
+ /**
144
+ * Starts periodic background ticker.
145
+ */
146
+ start(intervalMs) {
147
+ if (this.timer)
148
+ return;
149
+ const interval = intervalMs ?? this.checkIntervalMs;
150
+ this.timer = setInterval(() => {
151
+ this.tick().catch(() => { });
152
+ }, interval);
153
+ if (typeof this.timer?.unref === 'function') {
154
+ this.timer.unref();
155
+ }
156
+ }
157
+ /**
158
+ * Stops the background periodic ticker.
159
+ */
160
+ stop() {
161
+ if (this.timer) {
162
+ clearInterval(this.timer);
163
+ this.timer = null;
164
+ }
165
+ }
166
+ /**
167
+ * Returns all scheduled jobs.
168
+ */
169
+ async getJobs() {
170
+ return await store_js_1.JobStore.getAllJobs(this.db);
171
+ }
172
+ /**
173
+ * Returns run history for a specific job or all jobs.
174
+ */
175
+ async getHistory(jobId, limit = 50) {
176
+ return await store_js_1.JobStore.getRuns(this.db, undefined, jobId, limit);
177
+ }
178
+ /**
179
+ * Prunes historical run logs older than the given age in milliseconds (default: 30 days).
180
+ */
181
+ async pruneHistory(maxAgeMs = 30 * 24 * 60 * 60 * 1000) {
182
+ const cutoff = Date.now() - maxAgeMs;
183
+ await store_js_1.JobStore.pruneRuns(this.db, undefined, cutoff);
184
+ }
185
+ }
186
+ exports.JobScheduler = JobScheduler;
@@ -0,0 +1,20 @@
1
+ import { DBExecutable } from '../core/schema-manager.js';
2
+ import { JobRecord, JobRunRecord } from './types.js';
3
+ export declare const DEFAULT_JOBS_TABLE = "_local_scheduled_jobs";
4
+ export declare const DEFAULT_JOB_RUNS_TABLE = "_local_job_runs";
5
+ export declare function mapJobRow(r: any): JobRecord;
6
+ export declare function mapJobRunRow(r: any): JobRunRecord;
7
+ export declare class JobStore {
8
+ static ensureSchema(db: DBExecutable, jobsTable?: string, runsTable?: string): Promise<void>;
9
+ static upsertJob(db: DBExecutable, jobsTable: string | undefined, job: JobRecord): Promise<void>;
10
+ static getJob(db: DBExecutable, jobsTable: string | undefined, id: string): Promise<JobRecord | null>;
11
+ static getAllJobs(db: DBExecutable, jobsTable?: string): Promise<JobRecord[]>;
12
+ static getDueJobs(db: DBExecutable, jobsTable?: string, now?: number): Promise<JobRecord[]>;
13
+ static updateJobSchedule(db: DBExecutable, jobsTable: string | undefined, id: string, lastRunAt: number, nextRunAt: number): Promise<void>;
14
+ static setJobEnabled(db: DBExecutable, jobsTable: string | undefined, id: string, enabled: boolean): Promise<void>;
15
+ static deleteJob(db: DBExecutable, jobsTable: string | undefined, id: string): Promise<void>;
16
+ static recordRunStart(db: DBExecutable, runsTable: string | undefined, run: JobRunRecord): Promise<void>;
17
+ static recordRunFinish(db: DBExecutable, runsTable: string | undefined, id: string, status: 'SUCCESS' | 'FAILED', finishedAt: number, durationMs: number, error?: string | null): Promise<void>;
18
+ static getRuns(db: DBExecutable, runsTable?: string, jobId?: string, limit?: number): Promise<JobRunRecord[]>;
19
+ static pruneRuns(db: DBExecutable, runsTable: string | undefined, cutoffTimestamp: number): Promise<void>;
20
+ }
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JobStore = exports.DEFAULT_JOB_RUNS_TABLE = exports.DEFAULT_JOBS_TABLE = void 0;
4
+ exports.mapJobRow = mapJobRow;
5
+ exports.mapJobRunRow = mapJobRunRow;
6
+ exports.DEFAULT_JOBS_TABLE = '_local_scheduled_jobs';
7
+ exports.DEFAULT_JOB_RUNS_TABLE = '_local_job_runs';
8
+ function mapJobRow(r) {
9
+ let payload = undefined;
10
+ if (r.payload) {
11
+ try {
12
+ payload = typeof r.payload === 'string' ? JSON.parse(r.payload) : r.payload;
13
+ }
14
+ catch {
15
+ payload = r.payload;
16
+ }
17
+ }
18
+ return {
19
+ id: r.id,
20
+ name: r.name,
21
+ schedule: r.schedule,
22
+ intervalMs: Number(r.interval_ms || 0),
23
+ lastRunAt: r.last_run_at != null ? Number(r.last_run_at) : null,
24
+ nextRunAt: Number(r.next_run_at || 0),
25
+ enabled: r.enabled === 1 || r.enabled === '1' || r.enabled === true,
26
+ payload,
27
+ createdAt: Number(r.created_at || 0),
28
+ };
29
+ }
30
+ function mapJobRunRow(r) {
31
+ return {
32
+ id: r.id,
33
+ jobId: r.job_id,
34
+ status: r.status,
35
+ startedAt: Number(r.started_at || 0),
36
+ finishedAt: r.finished_at != null ? Number(r.finished_at) : null,
37
+ durationMs: r.duration_ms != null ? Number(r.duration_ms) : null,
38
+ error: r.error || null,
39
+ };
40
+ }
41
+ class JobStore {
42
+ static async ensureSchema(db, jobsTable = exports.DEFAULT_JOBS_TABLE, runsTable = exports.DEFAULT_JOB_RUNS_TABLE) {
43
+ await db.execute(`CREATE TABLE IF NOT EXISTS ${jobsTable} (
44
+ id TEXT PRIMARY KEY,
45
+ name TEXT NOT NULL,
46
+ schedule TEXT NOT NULL,
47
+ interval_ms INTEGER NOT NULL,
48
+ last_run_at INTEGER,
49
+ next_run_at INTEGER NOT NULL,
50
+ enabled INTEGER NOT NULL DEFAULT 1,
51
+ payload TEXT,
52
+ created_at INTEGER NOT NULL
53
+ );`);
54
+ await db.execute(`CREATE INDEX IF NOT EXISTS idx_${jobsTable}_next ON ${jobsTable} (next_run_at);`);
55
+ await db.execute(`CREATE TABLE IF NOT EXISTS ${runsTable} (
56
+ id TEXT PRIMARY KEY,
57
+ job_id TEXT NOT NULL,
58
+ status TEXT NOT NULL,
59
+ started_at INTEGER NOT NULL,
60
+ finished_at INTEGER,
61
+ duration_ms INTEGER,
62
+ error TEXT
63
+ );`);
64
+ await db.execute(`CREATE INDEX IF NOT EXISTS idx_${runsTable}_job ON ${runsTable} (job_id, started_at);`);
65
+ }
66
+ static async upsertJob(db, jobsTable = exports.DEFAULT_JOBS_TABLE, job) {
67
+ const payloadStr = job.payload !== undefined ? JSON.stringify(job.payload) : null;
68
+ await db.execute(`INSERT INTO ${jobsTable} (id, name, schedule, interval_ms, last_run_at, next_run_at, enabled, payload, created_at)
69
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
70
+ ON CONFLICT(id) DO UPDATE SET
71
+ name = excluded.name,
72
+ schedule = excluded.schedule,
73
+ interval_ms = excluded.interval_ms,
74
+ last_run_at = excluded.last_run_at,
75
+ next_run_at = excluded.next_run_at,
76
+ enabled = excluded.enabled,
77
+ payload = excluded.payload;`, [
78
+ job.id,
79
+ job.name,
80
+ job.schedule,
81
+ job.intervalMs,
82
+ job.lastRunAt ?? null,
83
+ job.nextRunAt,
84
+ job.enabled ? 1 : 0,
85
+ payloadStr,
86
+ job.createdAt,
87
+ ]);
88
+ }
89
+ static async getJob(db, jobsTable = exports.DEFAULT_JOBS_TABLE, id) {
90
+ const rows = await db.execute(`SELECT * FROM ${jobsTable} WHERE id = ? LIMIT 1;`, [id]);
91
+ if (!rows || rows.length === 0)
92
+ return null;
93
+ return mapJobRow(rows[0]);
94
+ }
95
+ static async getAllJobs(db, jobsTable = exports.DEFAULT_JOBS_TABLE) {
96
+ const rows = await db.execute(`SELECT * FROM ${jobsTable} ORDER BY next_run_at ASC;`);
97
+ return (rows || []).map(mapJobRow);
98
+ }
99
+ static async getDueJobs(db, jobsTable = exports.DEFAULT_JOBS_TABLE, now = Date.now()) {
100
+ const rows = await db.execute(`SELECT * FROM ${jobsTable} WHERE enabled = 1 AND next_run_at <= ? ORDER BY next_run_at ASC;`, [now]);
101
+ return (rows || []).map(mapJobRow);
102
+ }
103
+ static async updateJobSchedule(db, jobsTable = exports.DEFAULT_JOBS_TABLE, id, lastRunAt, nextRunAt) {
104
+ await db.execute(`UPDATE ${jobsTable} SET last_run_at = ?, next_run_at = ? WHERE id = ?;`, [lastRunAt, nextRunAt, id]);
105
+ }
106
+ static async setJobEnabled(db, jobsTable = exports.DEFAULT_JOBS_TABLE, id, enabled) {
107
+ await db.execute(`UPDATE ${jobsTable} SET enabled = ? WHERE id = ?;`, [enabled ? 1 : 0, id]);
108
+ }
109
+ static async deleteJob(db, jobsTable = exports.DEFAULT_JOBS_TABLE, id) {
110
+ await db.execute(`DELETE FROM ${jobsTable} WHERE id = ?;`, [id]);
111
+ }
112
+ // --- Run History ---
113
+ static async recordRunStart(db, runsTable = exports.DEFAULT_JOB_RUNS_TABLE, run) {
114
+ await db.execute(`INSERT INTO ${runsTable} (id, job_id, status, started_at, finished_at, duration_ms, error)
115
+ VALUES (?, ?, ?, ?, ?, ?, ?);`, [run.id, run.jobId, run.status, run.startedAt, null, null, null]);
116
+ }
117
+ static async recordRunFinish(db, runsTable = exports.DEFAULT_JOB_RUNS_TABLE, id, status, finishedAt, durationMs, error) {
118
+ await db.execute(`UPDATE ${runsTable} SET status = ?, finished_at = ?, duration_ms = ?, error = ? WHERE id = ?;`, [status, finishedAt, durationMs, error ?? null, id]);
119
+ }
120
+ static async getRuns(db, runsTable = exports.DEFAULT_JOB_RUNS_TABLE, jobId, limit = 50) {
121
+ let rows;
122
+ if (jobId) {
123
+ rows = await db.execute(`SELECT * FROM ${runsTable} WHERE job_id = ? ORDER BY started_at DESC LIMIT ?;`, [jobId, limit]);
124
+ }
125
+ else {
126
+ rows = await db.execute(`SELECT * FROM ${runsTable} ORDER BY started_at DESC LIMIT ?;`, [limit]);
127
+ }
128
+ return (rows || []).map(mapJobRunRow);
129
+ }
130
+ static async pruneRuns(db, runsTable = exports.DEFAULT_JOB_RUNS_TABLE, cutoffTimestamp) {
131
+ await db.execute(`DELETE FROM ${runsTable} WHERE started_at < ?;`, [cutoffTimestamp]);
132
+ }
133
+ }
134
+ exports.JobStore = JobStore;