@todu/engine 0.1.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.
- package/dist/change-observer.d.ts +15 -0
- package/dist/change-observer.d.ts.map +1 -0
- package/dist/change-observer.js +64 -0
- package/dist/change-observer.js.map +1 -0
- package/dist/habits.d.ts +6 -0
- package/dist/habits.d.ts.map +1 -0
- package/dist/habits.js +397 -0
- package/dist/habits.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/dist/labels.d.ts +5 -0
- package/dist/labels.d.ts.map +1 -0
- package/dist/labels.js +113 -0
- package/dist/labels.js.map +1 -0
- package/dist/notes.d.ts +5 -0
- package/dist/notes.d.ts.map +1 -0
- package/dist/notes.js +163 -0
- package/dist/notes.js.map +1 -0
- package/dist/projects.d.ts +5 -0
- package/dist/projects.d.ts.map +1 -0
- package/dist/projects.js +117 -0
- package/dist/projects.js.map +1 -0
- package/dist/recurring.d.ts +23 -0
- package/dist/recurring.d.ts.map +1 -0
- package/dist/recurring.js +372 -0
- package/dist/recurring.js.map +1 -0
- package/dist/schedule.d.ts +38 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/schedule.js +330 -0
- package/dist/schedule.js.map +1 -0
- package/dist/scheduling.d.ts +62 -0
- package/dist/scheduling.d.ts.map +1 -0
- package/dist/scheduling.js +58 -0
- package/dist/scheduling.js.map +1 -0
- package/dist/storage.d.ts +34 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +204 -0
- package/dist/storage.js.map +1 -0
- package/dist/sync-client.d.ts +16 -0
- package/dist/sync-client.d.ts.map +1 -0
- package/dist/sync-client.js +54 -0
- package/dist/sync-client.js.map +1 -0
- package/dist/sync-server.d.ts +12 -0
- package/dist/sync-server.d.ts.map +1 -0
- package/dist/sync-server.js +21 -0
- package/dist/sync-server.js.map +1 -0
- package/dist/tasks.d.ts +18 -0
- package/dist/tasks.d.ts.map +1 -0
- package/dist/tasks.js +396 -0
- package/dist/tasks.js.map +1 -0
- package/dist/todu.d.ts +115 -0
- package/dist/todu.d.ts.map +1 -0
- package/dist/todu.js +76 -0
- package/dist/todu.js.map +1 -0
- package/package.json +52 -0
package/dist/schedule.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { validateRRule as coreValidateRRule, validationError, } from "@todu/core";
|
|
2
|
+
// rrule is CJS. Node wraps it in { default: ... }, Bun resolves ESM named exports.
|
|
3
|
+
// Use namespace import and unwrap at runtime to support both.
|
|
4
|
+
import * as rruleNs from "rrule";
|
|
5
|
+
// biome-ignore lint/suspicious/noExplicitAny: CJS/ESM interop requires runtime detection
|
|
6
|
+
const rruleMod = rruleNs.default ?? rruleNs;
|
|
7
|
+
const RRule = rruleMod.RRule;
|
|
8
|
+
/** Internal: create an RRule from parsed options */
|
|
9
|
+
function createRRule(options) {
|
|
10
|
+
return new RRule(options);
|
|
11
|
+
}
|
|
12
|
+
/** Internal: parse an RRULE string into options */
|
|
13
|
+
function parseRRuleString(rule) {
|
|
14
|
+
return RRule.parseString(rule);
|
|
15
|
+
}
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// RRULE parsing and occurrence calculation
|
|
18
|
+
// ============================================================================
|
|
19
|
+
/**
|
|
20
|
+
* Parse an RRULE string into an internal RRule instance with a start date.
|
|
21
|
+
* Returns the instance for use by nextOccurrence/nextOccurrences.
|
|
22
|
+
* The RRule type is not exposed — consumers use the higher-level functions.
|
|
23
|
+
*/
|
|
24
|
+
function parseRule(rule, startDate, timezone) {
|
|
25
|
+
// Validate format first
|
|
26
|
+
const formatError = coreValidateRRule(rule);
|
|
27
|
+
if (formatError)
|
|
28
|
+
return { error: formatError };
|
|
29
|
+
try {
|
|
30
|
+
// Parse start date as a Date in UTC (rrule library works in UTC internally)
|
|
31
|
+
const dtstart = dateStringToUTC(startDate, timezone);
|
|
32
|
+
// Parse the RRULE string and create instance
|
|
33
|
+
const parsed = parseRRuleString(rule);
|
|
34
|
+
const rrule = createRRule({
|
|
35
|
+
...parsed,
|
|
36
|
+
dtstart,
|
|
37
|
+
});
|
|
38
|
+
return { rrule };
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
return {
|
|
42
|
+
error: validationError("schedule", `Failed to parse RRULE: ${e instanceof Error ? e.message : String(e)}`),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Calculate the next occurrence after a given date.
|
|
48
|
+
*
|
|
49
|
+
* @param rule - RRULE string
|
|
50
|
+
* @param startDate - Schedule start date (YYYY-MM-DD)
|
|
51
|
+
* @param timezone - IANA timezone
|
|
52
|
+
* @param afterDate - Find next occurrence after this date (YYYY-MM-DD)
|
|
53
|
+
* @param endDate - Optional end date (YYYY-MM-DD) — no occurrences after this
|
|
54
|
+
* @returns Next occurrence as YYYY-MM-DD string, or null if no more occurrences
|
|
55
|
+
*/
|
|
56
|
+
export function nextOccurrence(rule, startDate, timezone, afterDate, endDate) {
|
|
57
|
+
const parsed = parseRule(rule, startDate, timezone);
|
|
58
|
+
if (parsed.error)
|
|
59
|
+
return null;
|
|
60
|
+
const after = dateStringToUTC(afterDate, timezone);
|
|
61
|
+
const next = parsed.rrule.after(after);
|
|
62
|
+
if (!next)
|
|
63
|
+
return null;
|
|
64
|
+
// Convert back to date string in the template's timezone
|
|
65
|
+
const dateStr = utcToDateString(next, timezone);
|
|
66
|
+
// Check against end date
|
|
67
|
+
if (endDate && dateStr > endDate)
|
|
68
|
+
return null;
|
|
69
|
+
return dateStr;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Calculate the next N occurrences starting from a given date (inclusive).
|
|
73
|
+
* Used for the "upcoming" view.
|
|
74
|
+
*
|
|
75
|
+
* @param rule - RRULE string
|
|
76
|
+
* @param startDate - Schedule start date (YYYY-MM-DD)
|
|
77
|
+
* @param timezone - IANA timezone
|
|
78
|
+
* @param fromDate - Start listing from this date (YYYY-MM-DD), inclusive
|
|
79
|
+
* @param count - Number of occurrences to return
|
|
80
|
+
* @param endDate - Optional end date (YYYY-MM-DD)
|
|
81
|
+
* @returns Array of YYYY-MM-DD date strings
|
|
82
|
+
*/
|
|
83
|
+
export function nextOccurrences(rule, startDate, timezone, fromDate, count, endDate) {
|
|
84
|
+
const parsed = parseRule(rule, startDate, timezone);
|
|
85
|
+
if (parsed.error)
|
|
86
|
+
return [];
|
|
87
|
+
const results = [];
|
|
88
|
+
// Get occurrences starting from the day before fromDate
|
|
89
|
+
// (so fromDate itself is included if it matches)
|
|
90
|
+
const dayBefore = shiftDate(fromDate, -1);
|
|
91
|
+
const after = dateStringToUTC(dayBefore, timezone);
|
|
92
|
+
// Use between() for bounded queries when we have an end date
|
|
93
|
+
const effectiveEnd = endDate ? dateStringToUTC(endDate, timezone) : null;
|
|
94
|
+
let current = parsed.rrule.after(after);
|
|
95
|
+
while (current && results.length < count) {
|
|
96
|
+
const dateStr = utcToDateString(current, timezone);
|
|
97
|
+
// Check end date
|
|
98
|
+
if (effectiveEnd && current > effectiveEnd)
|
|
99
|
+
break;
|
|
100
|
+
results.push(dateStr);
|
|
101
|
+
current = parsed.rrule.after(current);
|
|
102
|
+
}
|
|
103
|
+
return results;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if a specific date is a valid occurrence of the schedule.
|
|
107
|
+
* Used to validate early materialization requests.
|
|
108
|
+
*/
|
|
109
|
+
export function isScheduledDate(rule, startDate, timezone, date, endDate) {
|
|
110
|
+
const parsed = parseRule(rule, startDate, timezone);
|
|
111
|
+
if (parsed.error)
|
|
112
|
+
return false;
|
|
113
|
+
// Check if date is within bounds
|
|
114
|
+
if (date < startDate)
|
|
115
|
+
return false;
|
|
116
|
+
if (endDate && date > endDate)
|
|
117
|
+
return false;
|
|
118
|
+
// Check the day before to see if this date is the next occurrence
|
|
119
|
+
const dayBefore = shiftDate(date, -1);
|
|
120
|
+
const after = dateStringToUTC(dayBefore, timezone);
|
|
121
|
+
const next = parsed.rrule.after(after);
|
|
122
|
+
if (!next)
|
|
123
|
+
return false;
|
|
124
|
+
const nextStr = utcToDateString(next, timezone);
|
|
125
|
+
return nextStr === date;
|
|
126
|
+
}
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// Human-readable schedule descriptions
|
|
129
|
+
// ============================================================================
|
|
130
|
+
const DAY_NAMES = {
|
|
131
|
+
MO: "Monday",
|
|
132
|
+
TU: "Tuesday",
|
|
133
|
+
WE: "Wednesday",
|
|
134
|
+
TH: "Thursday",
|
|
135
|
+
FR: "Friday",
|
|
136
|
+
SA: "Saturday",
|
|
137
|
+
SU: "Sunday",
|
|
138
|
+
};
|
|
139
|
+
const SHORT_DAY_NAMES = {
|
|
140
|
+
MO: "Mon",
|
|
141
|
+
TU: "Tue",
|
|
142
|
+
WE: "Wed",
|
|
143
|
+
TH: "Thu",
|
|
144
|
+
FR: "Fri",
|
|
145
|
+
SA: "Sat",
|
|
146
|
+
SU: "Sun",
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Convert an RRULE string to a human-readable description.
|
|
150
|
+
*/
|
|
151
|
+
export function describeSchedule(rule) {
|
|
152
|
+
const parts = parseRRuleParts(rule);
|
|
153
|
+
const freq = (parts.FREQ || "").toUpperCase();
|
|
154
|
+
const interval = parts.INTERVAL ? Number.parseInt(parts.INTERVAL, 10) : 1;
|
|
155
|
+
const byday = parts.BYDAY;
|
|
156
|
+
const bymonthday = parts.BYMONTHDAY;
|
|
157
|
+
const bymonth = parts.BYMONTH;
|
|
158
|
+
switch (freq) {
|
|
159
|
+
case "DAILY": {
|
|
160
|
+
if (interval === 1) {
|
|
161
|
+
if (byday)
|
|
162
|
+
return `Daily on ${formatDayList(byday)}`;
|
|
163
|
+
return "Daily";
|
|
164
|
+
}
|
|
165
|
+
return `Every ${interval} days`;
|
|
166
|
+
}
|
|
167
|
+
case "WEEKLY": {
|
|
168
|
+
const prefix = interval === 1 ? "Weekly" : `Every ${interval} weeks`;
|
|
169
|
+
if (byday) {
|
|
170
|
+
const days = byday.split(",");
|
|
171
|
+
// Check for common patterns
|
|
172
|
+
if (isWeekdays(days))
|
|
173
|
+
return interval === 1 ? "Every weekday" : `${prefix} on weekdays`;
|
|
174
|
+
return `${prefix} on ${formatDayList(byday)}`;
|
|
175
|
+
}
|
|
176
|
+
return prefix;
|
|
177
|
+
}
|
|
178
|
+
case "MONTHLY": {
|
|
179
|
+
const prefix = interval === 1 ? "Monthly" : `Every ${interval} months`;
|
|
180
|
+
if (bymonthday)
|
|
181
|
+
return `${prefix} on day ${bymonthday}`;
|
|
182
|
+
return prefix;
|
|
183
|
+
}
|
|
184
|
+
case "YEARLY": {
|
|
185
|
+
const prefix = interval === 1 ? "Yearly" : `Every ${interval} years`;
|
|
186
|
+
if (bymonth && bymonthday) {
|
|
187
|
+
const monthName = getMonthName(Number.parseInt(bymonth, 10));
|
|
188
|
+
return `${prefix} on ${monthName} ${bymonthday}`;
|
|
189
|
+
}
|
|
190
|
+
return prefix;
|
|
191
|
+
}
|
|
192
|
+
default:
|
|
193
|
+
return rule;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// ============================================================================
|
|
197
|
+
// Today helper — get current date in a timezone
|
|
198
|
+
// ============================================================================
|
|
199
|
+
/**
|
|
200
|
+
* Get today's date in the given timezone as YYYY-MM-DD.
|
|
201
|
+
*/
|
|
202
|
+
export function todayInTimezone(timezone) {
|
|
203
|
+
const formatter = new Intl.DateTimeFormat("en-CA", {
|
|
204
|
+
timeZone: timezone,
|
|
205
|
+
year: "numeric",
|
|
206
|
+
month: "2-digit",
|
|
207
|
+
day: "2-digit",
|
|
208
|
+
});
|
|
209
|
+
return formatter.format(new Date());
|
|
210
|
+
}
|
|
211
|
+
// ============================================================================
|
|
212
|
+
// Internal helpers
|
|
213
|
+
// ============================================================================
|
|
214
|
+
/**
|
|
215
|
+
* Convert a YYYY-MM-DD date string to a UTC Date object,
|
|
216
|
+
* treating the date as midnight in the given timezone.
|
|
217
|
+
*/
|
|
218
|
+
function dateStringToUTC(dateStr, timezone) {
|
|
219
|
+
// Parse the date parts
|
|
220
|
+
const [year, month, day] = dateStr.split("-").map(Number);
|
|
221
|
+
// Create a date at midnight UTC first
|
|
222
|
+
const utcDate = new Date(Date.UTC(year, month - 1, day));
|
|
223
|
+
// Get the timezone offset for this date
|
|
224
|
+
// We use a formatter to determine what time it is in the target timezone
|
|
225
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
226
|
+
timeZone: timezone,
|
|
227
|
+
year: "numeric",
|
|
228
|
+
month: "numeric",
|
|
229
|
+
day: "numeric",
|
|
230
|
+
hour: "numeric",
|
|
231
|
+
minute: "numeric",
|
|
232
|
+
hourCycle: "h23",
|
|
233
|
+
});
|
|
234
|
+
// Find the offset by comparing the formatted date to the UTC date
|
|
235
|
+
const parts = formatter.formatToParts(utcDate);
|
|
236
|
+
const getPart = (type) => Number(parts.find((p) => p.type === type)?.value || 0);
|
|
237
|
+
const tzYear = getPart("year");
|
|
238
|
+
const tzMonth = getPart("month");
|
|
239
|
+
const tzDay = getPart("day");
|
|
240
|
+
const tzHour = getPart("hour");
|
|
241
|
+
const tzMinute = getPart("minute");
|
|
242
|
+
// If the timezone date differs from what we want, we need to adjust
|
|
243
|
+
// We want midnight in the target timezone
|
|
244
|
+
const tzDate = new Date(Date.UTC(tzYear, tzMonth - 1, tzDay, tzHour, tzMinute));
|
|
245
|
+
const offsetMs = tzDate.getTime() - utcDate.getTime();
|
|
246
|
+
// Midnight in the target timezone = UTC midnight minus the offset
|
|
247
|
+
return new Date(Date.UTC(year, month - 1, day) - offsetMs);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Convert a UTC Date object to a YYYY-MM-DD string in the given timezone.
|
|
251
|
+
*/
|
|
252
|
+
function utcToDateString(date, timezone) {
|
|
253
|
+
const formatter = new Intl.DateTimeFormat("en-CA", {
|
|
254
|
+
timeZone: timezone,
|
|
255
|
+
year: "numeric",
|
|
256
|
+
month: "2-digit",
|
|
257
|
+
day: "2-digit",
|
|
258
|
+
});
|
|
259
|
+
return formatter.format(date);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Shift a YYYY-MM-DD date string by N days.
|
|
263
|
+
*/
|
|
264
|
+
function shiftDate(dateStr, days) {
|
|
265
|
+
const [year, month, day] = dateStr.split("-").map(Number);
|
|
266
|
+
const date = new Date(Date.UTC(year, month - 1, day + days));
|
|
267
|
+
const y = date.getUTCFullYear();
|
|
268
|
+
const m = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
269
|
+
const d = String(date.getUTCDate()).padStart(2, "0");
|
|
270
|
+
return `${y}-${m}-${d}`;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Parse an RRULE string into key-value pairs.
|
|
274
|
+
*/
|
|
275
|
+
function parseRRuleParts(rule) {
|
|
276
|
+
const result = {};
|
|
277
|
+
for (const part of rule.split(";")) {
|
|
278
|
+
const [key, value] = part.split("=", 2);
|
|
279
|
+
if (key && value) {
|
|
280
|
+
result[key.toUpperCase()] = value;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Format a comma-separated BYDAY list into human-readable form.
|
|
287
|
+
*/
|
|
288
|
+
function formatDayList(byday) {
|
|
289
|
+
const days = byday.split(",").map((d) => d.trim().toUpperCase());
|
|
290
|
+
if (days.length === 1) {
|
|
291
|
+
return DAY_NAMES[days[0]] || days[0];
|
|
292
|
+
}
|
|
293
|
+
// Use short names for multiple days
|
|
294
|
+
const names = days.map((d) => SHORT_DAY_NAMES[d] || d);
|
|
295
|
+
if (names.length === 2) {
|
|
296
|
+
return `${names[0]} and ${names[1]}`;
|
|
297
|
+
}
|
|
298
|
+
return `${names.slice(0, -1).join(", ")}, and ${names[names.length - 1]}`;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Check if a set of days represents weekdays (Mon-Fri).
|
|
302
|
+
*/
|
|
303
|
+
function isWeekdays(days) {
|
|
304
|
+
const weekdays = new Set(["MO", "TU", "WE", "TH", "FR"]);
|
|
305
|
+
if (days.length !== 5)
|
|
306
|
+
return false;
|
|
307
|
+
return days.every((d) => weekdays.has(d.toUpperCase()));
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Get month name from number (1-12).
|
|
311
|
+
*/
|
|
312
|
+
function getMonthName(month) {
|
|
313
|
+
const names = [
|
|
314
|
+
"",
|
|
315
|
+
"January",
|
|
316
|
+
"February",
|
|
317
|
+
"March",
|
|
318
|
+
"April",
|
|
319
|
+
"May",
|
|
320
|
+
"June",
|
|
321
|
+
"July",
|
|
322
|
+
"August",
|
|
323
|
+
"September",
|
|
324
|
+
"October",
|
|
325
|
+
"November",
|
|
326
|
+
"December",
|
|
327
|
+
];
|
|
328
|
+
return names[month] || String(month);
|
|
329
|
+
}
|
|
330
|
+
//# sourceMappingURL=schedule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule.js","sourceRoot":"","sources":["../src/schedule.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { DocHandle } from "@automerge/automerge-repo";
|
|
2
|
+
import type { CatalogDocument } from "@todu/core";
|
|
3
|
+
/**
|
|
4
|
+
* A schedulable item from the catalog — either a recurring template or a habit.
|
|
5
|
+
* Both share these fields that the framework needs to check.
|
|
6
|
+
*/
|
|
7
|
+
export interface SchedulableItem {
|
|
8
|
+
id: string;
|
|
9
|
+
nextDue: string;
|
|
10
|
+
timezone: string;
|
|
11
|
+
paused: boolean;
|
|
12
|
+
endDate?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Context passed to template processors during processTemplates().
|
|
16
|
+
* Processors determine "today" per-item using todayInTimezone()
|
|
17
|
+
* with each item's configured timezone.
|
|
18
|
+
*/
|
|
19
|
+
export interface ProcessingContext {
|
|
20
|
+
/** The catalog document handle for reading/writing */
|
|
21
|
+
catalog: DocHandle<CatalogDocument>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A handler that processes due items of a specific type.
|
|
25
|
+
* Registered by recurring templates and habits modules.
|
|
26
|
+
*
|
|
27
|
+
* @param context - Processing context with catalog access
|
|
28
|
+
* @returns Number of items processed
|
|
29
|
+
*/
|
|
30
|
+
export type TemplateProcessor = (context: ProcessingContext) => Promise<number>;
|
|
31
|
+
/**
|
|
32
|
+
* Register a template processor.
|
|
33
|
+
* Called by recurring.ts and habits.ts to register their processing logic.
|
|
34
|
+
*
|
|
35
|
+
* @param type - Processor type identifier (e.g., "recurring", "habit")
|
|
36
|
+
* @param processor - The processing function
|
|
37
|
+
*/
|
|
38
|
+
export declare function registerProcessor(type: string, processor: TemplateProcessor): void;
|
|
39
|
+
/**
|
|
40
|
+
* Clear all registered processors.
|
|
41
|
+
* Used in tests to reset state between runs.
|
|
42
|
+
*/
|
|
43
|
+
export declare function clearProcessors(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Get all registered processor types.
|
|
46
|
+
* Used in tests to verify registration.
|
|
47
|
+
*/
|
|
48
|
+
export declare function getRegisteredProcessors(): string[];
|
|
49
|
+
/**
|
|
50
|
+
* Process all due templates and habits.
|
|
51
|
+
*
|
|
52
|
+
* Called during createTodu() initialization. Runs each registered
|
|
53
|
+
* processor in sequence. Each processor is responsible for:
|
|
54
|
+
* 1. Reading its items from the catalog
|
|
55
|
+
* 2. Checking which are due (nextDue <= today, not paused)
|
|
56
|
+
* 3. Performing the appropriate action (generate tasks, advance nextDue, etc.)
|
|
57
|
+
*
|
|
58
|
+
* This function is the single entry point — no daemon, no polling,
|
|
59
|
+
* no on-complete triggers. Just process on access.
|
|
60
|
+
*/
|
|
61
|
+
export declare function processTemplates(catalog: DocHandle<CatalogDocument>): Promise<void>;
|
|
62
|
+
//# sourceMappingURL=scheduling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduling.d.ts","sourceRoot":"","sources":["../src/scheduling.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAMlD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,OAAO,EAAE,SAAS,CAAC,eAAe,CAAC,CAAC;CACrC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAQhF;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,iBAAiB,GAAG,IAAI,CAElF;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,EAAE,CAElD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,SAAS,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBzF"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry of template processors.
|
|
3
|
+
* Each processor handles a specific type of schedulable item.
|
|
4
|
+
*/
|
|
5
|
+
const processors = new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Register a template processor.
|
|
8
|
+
* Called by recurring.ts and habits.ts to register their processing logic.
|
|
9
|
+
*
|
|
10
|
+
* @param type - Processor type identifier (e.g., "recurring", "habit")
|
|
11
|
+
* @param processor - The processing function
|
|
12
|
+
*/
|
|
13
|
+
export function registerProcessor(type, processor) {
|
|
14
|
+
processors.set(type, processor);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Clear all registered processors.
|
|
18
|
+
* Used in tests to reset state between runs.
|
|
19
|
+
*/
|
|
20
|
+
export function clearProcessors() {
|
|
21
|
+
processors.clear();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get all registered processor types.
|
|
25
|
+
* Used in tests to verify registration.
|
|
26
|
+
*/
|
|
27
|
+
export function getRegisteredProcessors() {
|
|
28
|
+
return Array.from(processors.keys());
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Process all due templates and habits.
|
|
32
|
+
*
|
|
33
|
+
* Called during createTodu() initialization. Runs each registered
|
|
34
|
+
* processor in sequence. Each processor is responsible for:
|
|
35
|
+
* 1. Reading its items from the catalog
|
|
36
|
+
* 2. Checking which are due (nextDue <= today, not paused)
|
|
37
|
+
* 3. Performing the appropriate action (generate tasks, advance nextDue, etc.)
|
|
38
|
+
*
|
|
39
|
+
* This function is the single entry point — no daemon, no polling,
|
|
40
|
+
* no on-complete triggers. Just process on access.
|
|
41
|
+
*/
|
|
42
|
+
export async function processTemplates(catalog) {
|
|
43
|
+
if (processors.size === 0)
|
|
44
|
+
return;
|
|
45
|
+
for (const [type, processor] of processors) {
|
|
46
|
+
try {
|
|
47
|
+
const context = {
|
|
48
|
+
catalog,
|
|
49
|
+
};
|
|
50
|
+
await processor(context);
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
// Log but don't fail — one processor failing shouldn't block others
|
|
54
|
+
console.error(`[scheduling] processor "${type}" failed:`, e instanceof Error ? e.message : String(e));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=scheduling.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduling.js","sourceRoot":"","sources":["../src/scheduling.ts"],"names":[],"mappings":"AAsCA;;;GAGG;AACH,MAAM,UAAU,GAAmC,IAAI,GAAG,EAAE,CAAC;AAE7D;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,SAA4B,EAAQ;IAClF,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AAAA,CACjC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,GAAS;IACtC,UAAU,CAAC,KAAK,EAAE,CAAC;AAAA,CACpB;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,GAAa;IAClD,OAAO,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;AAAA,CACtC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,OAAmC,EAAiB;IACzF,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO;IAElC,KAAK,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3C,IAAI,CAAC;YACH,MAAM,OAAO,GAAsB;gBACjC,OAAO;aACR,CAAC;YACF,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,sEAAoE;YACpE,OAAO,CAAC,KAAK,CACX,2BAA2B,IAAI,WAAW,EAC1C,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAC3C,CAAC;QACJ,CAAC;IACH,CAAC;AAAA,CACF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DocHandle } from "@automerge/automerge-repo";
|
|
2
|
+
import { Repo } from "@automerge/automerge-repo";
|
|
3
|
+
import { type CatalogDocument } from "@todu/core";
|
|
4
|
+
export interface Storage {
|
|
5
|
+
/** The Automerge repo instance */
|
|
6
|
+
repo: Repo;
|
|
7
|
+
/** The catalog document handle */
|
|
8
|
+
catalog: DocHandle<CatalogDocument>;
|
|
9
|
+
/** Whether this storage is ephemeral (in-memory, no persistence) */
|
|
10
|
+
ephemeral: boolean;
|
|
11
|
+
/** Shut down the repo */
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Initialize storage: create data directory, set up Automerge repo,
|
|
16
|
+
* and load or create the catalog document.
|
|
17
|
+
*/
|
|
18
|
+
export declare function initStorage(storagePath: string): Promise<Storage>;
|
|
19
|
+
/**
|
|
20
|
+
* Initialize ephemeral storage with no filesystem persistence.
|
|
21
|
+
* Used by CLI when syncing with a running Electron instance.
|
|
22
|
+
*
|
|
23
|
+
* Creates an in-memory Automerge repo and reads the catalog document ID
|
|
24
|
+
* from the marker file (written by the persistent owner).
|
|
25
|
+
*
|
|
26
|
+
* IMPORTANT: The caller must connect a sync adapter to the repo BEFORE
|
|
27
|
+
* calling `findCatalog()`. The ephemeral repo has no local data — it
|
|
28
|
+
* needs a sync peer to provide the document contents.
|
|
29
|
+
*/
|
|
30
|
+
export declare function initEphemeralStorage(storagePath: string): Promise<Omit<Storage, "catalog"> & {
|
|
31
|
+
/** Find the catalog document via sync. Call AFTER connecting a sync adapter. */
|
|
32
|
+
findCatalog(): Promise<Storage>;
|
|
33
|
+
}>;
|
|
34
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AAEjD,OAAO,EAEL,KAAK,eAAe,EAGrB,MAAM,YAAY,CAAC;AAwCpB,MAAM,WAAW,OAAO;IACtB,kCAAkC;IAClC,IAAI,EAAE,IAAI,CAAC;IAEX,kCAAkC;IAClC,OAAO,EAAE,SAAS,CAAC,eAAe,CAAC,CAAC;IAEpC,oEAAoE;IACpE,SAAS,EAAE,OAAO,CAAC;IAEnB,yBAAyB;IACzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAqBvE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CACtE,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,GAAG;IACzB,gFAAgF;IAChF,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC,CACF,CAwDA"}
|