@todu/core 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.
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Browser-safe exports from @todu/core.
3
+ *
4
+ * Excludes modules that depend on Node.js APIs:
5
+ * - config.ts (node:os, node:path)
6
+ * - schedule.ts (node:crypto)
7
+ * - validation.ts (imports schedule.ts)
8
+ *
9
+ * Use this entry point in Electron renderer and other browser contexts.
10
+ */
11
+ export * from "./constants.js";
12
+ export * from "./schema.js";
13
+ export * from "./types.js";
14
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Browser-safe exports from @todu/core.
3
+ *
4
+ * Excludes modules that depend on Node.js APIs:
5
+ * - config.ts (node:os, node:path)
6
+ * - schedule.ts (node:crypto)
7
+ * - validation.ts (imports schedule.ts)
8
+ *
9
+ * Use this entry point in Electron renderer and other browser contexts.
10
+ */
11
+ export * from "./constants.js";
12
+ export * from "./schema.js";
13
+ export * from "./types.js";
14
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,44 @@
1
+ export declare const DEFAULT_CONFIG_DIR: string;
2
+ export declare const DEFAULT_CONFIG_FILE: string;
3
+ export declare const DEFAULT_DATA_DIR: string;
4
+ export interface ToduFileConfig {
5
+ /** Path to data directory (absolute or relative to config file) */
6
+ data_dir?: string;
7
+ }
8
+ /**
9
+ * Resolve config file path.
10
+ *
11
+ * Priority:
12
+ * 1. Explicit override (e.g. --config flag)
13
+ * 2. TODU_CONFIG env var
14
+ * 3. Default: ~/.config/todu/config.yaml
15
+ */
16
+ export declare function resolveConfigPath(override?: string): string;
17
+ /**
18
+ * Resolve the data directory path.
19
+ *
20
+ * Priority:
21
+ * 1. TODU_DATA_DIR env var (for tests / backward compat)
22
+ * 2. data_dir from config file (resolved relative to config file location)
23
+ * 3. Default: ~/.config/todu/data
24
+ */
25
+ export declare function resolveDataDir(configPath: string, config: ToduFileConfig): string;
26
+ /**
27
+ * Describe where each resolved value came from.
28
+ * Useful for `todu config show` and debugging.
29
+ */
30
+ export declare function resolveConfigSources(configOverride?: string, config?: ToduFileConfig): {
31
+ configPath: string;
32
+ configSource: string;
33
+ dataDir: string;
34
+ dataDirSource: string;
35
+ };
36
+ /**
37
+ * Resolve storage path using env vars and defaults.
38
+ *
39
+ * For clients that don't parse config files (e.g. Electron).
40
+ * Checks TODU_DATA_DIR env var, then falls back to default.
41
+ * If a config file with data_dir is needed, use resolveDataDir instead.
42
+ */
43
+ export declare function resolveStoragePath(): string;
44
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,kBAAkB,QAA6C,CAAC;AAC7E,eAAO,MAAM,mBAAmB,QAA+C,CAAC;AAChF,eAAO,MAAM,gBAAgB,QAAwC,CAAC;AAEtE,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAI3D;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAcjF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,cAAc,CAAC,EAAE,MAAM,EACvB,MAAM,CAAC,EAAE,cAAc,GACtB;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;CACvB,CAwBA;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAK3C"}
package/dist/config.js ADDED
@@ -0,0 +1,94 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ // ============================================================================
4
+ // Shared configuration resolution
5
+ //
6
+ // Single source of truth for config and data directory paths.
7
+ // Used by CLI, Electron, and any future client.
8
+ //
9
+ // This module handles path resolution only. Config file parsing (YAML)
10
+ // stays in the CLI package which has the yaml dependency.
11
+ // ============================================================================
12
+ export const DEFAULT_CONFIG_DIR = path.join(os.homedir(), ".config", "todu");
13
+ export const DEFAULT_CONFIG_FILE = path.join(DEFAULT_CONFIG_DIR, "config.yaml");
14
+ export const DEFAULT_DATA_DIR = path.join(DEFAULT_CONFIG_DIR, "data");
15
+ /**
16
+ * Resolve config file path.
17
+ *
18
+ * Priority:
19
+ * 1. Explicit override (e.g. --config flag)
20
+ * 2. TODU_CONFIG env var
21
+ * 3. Default: ~/.config/todu/config.yaml
22
+ */
23
+ export function resolveConfigPath(override) {
24
+ if (override)
25
+ return path.resolve(override);
26
+ if (process.env.TODU_CONFIG)
27
+ return path.resolve(process.env.TODU_CONFIG);
28
+ return DEFAULT_CONFIG_FILE;
29
+ }
30
+ /**
31
+ * Resolve the data directory path.
32
+ *
33
+ * Priority:
34
+ * 1. TODU_DATA_DIR env var (for tests / backward compat)
35
+ * 2. data_dir from config file (resolved relative to config file location)
36
+ * 3. Default: ~/.config/todu/data
37
+ */
38
+ export function resolveDataDir(configPath, config) {
39
+ // Env var takes precedence (backward compat, tests)
40
+ if (process.env.TODU_DATA_DIR) {
41
+ return path.resolve(process.env.TODU_DATA_DIR);
42
+ }
43
+ // Config file data_dir
44
+ if (config.data_dir) {
45
+ const configDir = path.dirname(configPath);
46
+ return path.resolve(configDir, config.data_dir);
47
+ }
48
+ // Default
49
+ return DEFAULT_DATA_DIR;
50
+ }
51
+ /**
52
+ * Describe where each resolved value came from.
53
+ * Useful for `todu config show` and debugging.
54
+ */
55
+ export function resolveConfigSources(configOverride, config) {
56
+ const configPath = resolveConfigPath(configOverride);
57
+ let configSource;
58
+ if (configOverride) {
59
+ configSource = "--config flag";
60
+ }
61
+ else if (process.env.TODU_CONFIG) {
62
+ configSource = "TODU_CONFIG env var";
63
+ }
64
+ else {
65
+ configSource = "default";
66
+ }
67
+ const resolvedConfig = config ?? {};
68
+ const dataDir = resolveDataDir(configPath, resolvedConfig);
69
+ let dataDirSource;
70
+ if (process.env.TODU_DATA_DIR) {
71
+ dataDirSource = "TODU_DATA_DIR env var";
72
+ }
73
+ else if (resolvedConfig.data_dir) {
74
+ dataDirSource = `config file (${configPath})`;
75
+ }
76
+ else {
77
+ dataDirSource = "default";
78
+ }
79
+ return { configPath, configSource, dataDir, dataDirSource };
80
+ }
81
+ /**
82
+ * Resolve storage path using env vars and defaults.
83
+ *
84
+ * For clients that don't parse config files (e.g. Electron).
85
+ * Checks TODU_DATA_DIR env var, then falls back to default.
86
+ * If a config file with data_dir is needed, use resolveDataDir instead.
87
+ */
88
+ export function resolveStoragePath() {
89
+ if (process.env.TODU_DATA_DIR) {
90
+ return path.resolve(process.env.TODU_DATA_DIR);
91
+ }
92
+ return DEFAULT_DATA_DIR;
93
+ }
94
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** Automerge document ID for the catalog (stable, well-known) */
2
+ export declare const CATALOG_DOC_KEY = "todu-catalog";
3
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,eAAO,MAAM,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** Automerge document ID for the catalog (stable, well-known) */
2
+ export const CATALOG_DOC_KEY = "todu-catalog";
3
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,MAAM,CAAC,MAAM,eAAe,GAAG,cAAc,CAAC"}
@@ -0,0 +1,7 @@
1
+ export * from "./config.js";
2
+ export * from "./constants.js";
3
+ export * from "./schedule.js";
4
+ export * from "./schema.js";
5
+ export * from "./types.js";
6
+ export * from "./validation.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export * from "./config.js";
2
+ export * from "./constants.js";
3
+ export * from "./schedule.js";
4
+ export * from "./schema.js";
5
+ export * from "./types.js";
6
+ export * from "./validation.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,39 @@
1
+ import type { TaskId } from "./types.js";
2
+ import { type ValidationError } from "./types.js";
3
+ /**
4
+ * Generate a deterministic task ID from a template/habit ID and a date.
5
+ * Same inputs always produce the same output, across all devices.
6
+ *
7
+ * Used to prevent duplicate task generation when multiple devices
8
+ * process the same recurring template independently.
9
+ */
10
+ export declare function generateScheduledTaskId(templateId: string, date: string): TaskId;
11
+ /**
12
+ * Validate an RRULE string at the format level.
13
+ * This is a lightweight check for @todu/core (no RRULE library dependency).
14
+ * Full parsing and occurrence calculation happens in @todu/engine.
15
+ *
16
+ * Checks:
17
+ * - Must contain FREQ= with an allowed frequency
18
+ * - No sub-daily frequencies (HOURLY, MINUTELY, SECONDLY)
19
+ * - Only known RRULE parts
20
+ */
21
+ export declare function validateRRule(rule: string): ValidationError | null;
22
+ /**
23
+ * Validate a date string is in YYYY-MM-DD format and represents a real date.
24
+ */
25
+ export declare function validateDateString(field: string, value: string): ValidationError | null;
26
+ /**
27
+ * Validate a timezone string is a valid IANA timezone.
28
+ */
29
+ export declare function validateTimezone(timezone: string): ValidationError | null;
30
+ /**
31
+ * Validate a complete ScheduleDefinition.
32
+ */
33
+ export declare function validateScheduleDefinition(schedule: {
34
+ rule: string;
35
+ timezone: string;
36
+ startDate: string;
37
+ endDate?: string;
38
+ }): ValidationError | null;
39
+ //# sourceMappingURL=schedule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["../src/schedule.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAGL,KAAK,eAAe,EAErB,MAAM,YAAY,CAAC;AAMpB;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGhF;AAMD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CA0ClE;AAQD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAavF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAOzE;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,eAAe,GAAG,IAAI,CAqBzB"}
@@ -0,0 +1,114 @@
1
+ import crypto from "node:crypto";
2
+ import { ALLOWED_FREQUENCIES, createTaskId, validationError, } from "./types.js";
3
+ // ============================================================================
4
+ // Deterministic ID generation
5
+ // ============================================================================
6
+ /**
7
+ * Generate a deterministic task ID from a template/habit ID and a date.
8
+ * Same inputs always produce the same output, across all devices.
9
+ *
10
+ * Used to prevent duplicate task generation when multiple devices
11
+ * process the same recurring template independently.
12
+ */
13
+ export function generateScheduledTaskId(templateId, date) {
14
+ const hash = crypto.createHash("sha256").update(`${templateId}|${date}`).digest("hex");
15
+ return createTaskId(`sched-${hash.slice(0, 12)}`);
16
+ }
17
+ // ============================================================================
18
+ // RRULE validation (basic format checks — no library dependency)
19
+ // ============================================================================
20
+ /**
21
+ * Validate an RRULE string at the format level.
22
+ * This is a lightweight check for @todu/core (no RRULE library dependency).
23
+ * Full parsing and occurrence calculation happens in @todu/engine.
24
+ *
25
+ * Checks:
26
+ * - Must contain FREQ= with an allowed frequency
27
+ * - No sub-daily frequencies (HOURLY, MINUTELY, SECONDLY)
28
+ * - Only known RRULE parts
29
+ */
30
+ export function validateRRule(rule) {
31
+ if (!rule || rule.trim().length === 0) {
32
+ return validationError("schedule", "RRULE is required");
33
+ }
34
+ const parts = rule.split(";");
35
+ let foundFreq = false;
36
+ for (const part of parts) {
37
+ const [key, value] = part.split("=", 2);
38
+ if (!key || !value) {
39
+ return validationError("schedule", `Invalid RRULE part: ${part}`);
40
+ }
41
+ const upperKey = key.toUpperCase();
42
+ if (upperKey === "FREQ") {
43
+ foundFreq = true;
44
+ const upperValue = value.toUpperCase();
45
+ // Reject sub-daily frequencies
46
+ if (upperValue === "HOURLY" || upperValue === "MINUTELY" || upperValue === "SECONDLY") {
47
+ return validationError("schedule", `Sub-daily frequency "${upperValue}" is not supported. Use DAILY, WEEKLY, MONTHLY, or YEARLY.`);
48
+ }
49
+ if (!ALLOWED_FREQUENCIES.includes(upperValue)) {
50
+ return validationError("schedule", `Invalid frequency: ${upperValue}. Must be one of: ${ALLOWED_FREQUENCIES.join(", ")}`);
51
+ }
52
+ }
53
+ }
54
+ if (!foundFreq) {
55
+ return validationError("schedule", "RRULE must contain FREQ=");
56
+ }
57
+ return null;
58
+ }
59
+ // ============================================================================
60
+ // Date format validation
61
+ // ============================================================================
62
+ const DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
63
+ /**
64
+ * Validate a date string is in YYYY-MM-DD format and represents a real date.
65
+ */
66
+ export function validateDateString(field, value) {
67
+ if (!DATE_REGEX.test(value)) {
68
+ return validationError(field, `Invalid date format: ${value} (expected YYYY-MM-DD)`);
69
+ }
70
+ // Verify it's a real date (e.g., reject 2026-02-30)
71
+ const [year, month, day] = value.split("-").map(Number);
72
+ const date = new Date(year, month - 1, day);
73
+ if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
74
+ return validationError(field, `Invalid date: ${value}`);
75
+ }
76
+ return null;
77
+ }
78
+ /**
79
+ * Validate a timezone string is a valid IANA timezone.
80
+ */
81
+ export function validateTimezone(timezone) {
82
+ try {
83
+ Intl.DateTimeFormat(undefined, { timeZone: timezone });
84
+ return null;
85
+ }
86
+ catch {
87
+ return validationError("timezone", `Invalid timezone: ${timezone}`);
88
+ }
89
+ }
90
+ /**
91
+ * Validate a complete ScheduleDefinition.
92
+ */
93
+ export function validateScheduleDefinition(schedule) {
94
+ const ruleError = validateRRule(schedule.rule);
95
+ if (ruleError)
96
+ return ruleError;
97
+ const tzError = validateTimezone(schedule.timezone);
98
+ if (tzError)
99
+ return tzError;
100
+ const startError = validateDateString("startDate", schedule.startDate);
101
+ if (startError)
102
+ return startError;
103
+ if (schedule.endDate !== undefined) {
104
+ const endError = validateDateString("endDate", schedule.endDate);
105
+ if (endError)
106
+ return endError;
107
+ // endDate must be after startDate
108
+ if (schedule.endDate <= schedule.startDate) {
109
+ return validationError("endDate", "End date must be after start date");
110
+ }
111
+ }
112
+ return null;
113
+ }
114
+ //# 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,82 @@
1
+ import type { Habit, HabitEntry, HabitId, Label, Note, Project, ProjectId, RecurringTemplate, Settings, Task } from "./types.js";
2
+ /**
3
+ * Catalog document (one per todu instance).
4
+ * Contains all small/bounded data: projects, labels, habits, settings,
5
+ * recurring templates, and external system registrations.
6
+ */
7
+ export interface CatalogDocument {
8
+ /** Schema version for migration support */
9
+ version: number;
10
+ /** All projects */
11
+ projects: Project[];
12
+ /** All labels */
13
+ labels: Label[];
14
+ /**
15
+ * Map of projectId → Automerge document ID for that project's task list.
16
+ * Populated when the first task is created in a project.
17
+ */
18
+ taskListDocIds: Record<string, string>;
19
+ /** Automerge document ID for the notes document */
20
+ notesDocId?: string;
21
+ /** Recurring task templates */
22
+ recurringTemplates: RecurringTemplate[];
23
+ /** Habit definitions */
24
+ habits: Habit[];
25
+ /** Map of habitId → Automerge document ID for that habit's log */
26
+ habitLogDocIds: Record<string, string>;
27
+ /** Application settings */
28
+ settings: Settings;
29
+ }
30
+ /**
31
+ * Task list document (one per project).
32
+ * Contains task metadata only — no descriptions or heavy content.
33
+ * Each task is ~200 bytes, so a project with 1000 tasks ≈ 200KB.
34
+ */
35
+ export interface TaskListDocument {
36
+ /** Project this task list belongs to */
37
+ projectId: ProjectId;
38
+ /** Task metadata entries */
39
+ tasks: Task[];
40
+ /**
41
+ * Map of taskId → Automerge document ID for that task's detail doc.
42
+ * Loaded on demand when task.get() is called.
43
+ */
44
+ detailDocIds: Record<string, string>;
45
+ }
46
+ /**
47
+ * Task detail document (one per task).
48
+ * Contains the description and any heavy content.
49
+ * Loaded on demand, not during list operations.
50
+ */
51
+ export interface TaskDetailDocument {
52
+ /** The task this detail belongs to */
53
+ taskId: string;
54
+ /** Full task description (markdown) */
55
+ description: string;
56
+ }
57
+ /**
58
+ * Notes document (one global).
59
+ * Contains all notes — standalone journal entries and entity-attached notes.
60
+ * Each note is ~200B, so thousands fit comfortably.
61
+ */
62
+ export interface NotesDocument {
63
+ /** All notes */
64
+ notes: Note[];
65
+ }
66
+ /**
67
+ * Habit log document (one per habit).
68
+ * Contains check-in entries keyed by date for deterministic multi-device merging.
69
+ */
70
+ export interface HabitLogDocument {
71
+ /** Which habit this log belongs to */
72
+ habitId: string;
73
+ /** Check-in entries keyed by date (YYYY-MM-DD) */
74
+ entries: Record<string, HabitEntry>;
75
+ }
76
+ export declare const SCHEMA_VERSION = 1;
77
+ export declare function createEmptyCatalog(): CatalogDocument;
78
+ export declare function createTaskListDocument(projectId: ProjectId): TaskListDocument;
79
+ export declare function createTaskDetailDocument(taskId: string, description: string): TaskDetailDocument;
80
+ export declare function createNotesDocument(): NotesDocument;
81
+ export declare function createHabitLogDocument(habitId: HabitId): HabitLogDocument;
82
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EACL,UAAU,EACV,OAAO,EACP,KAAK,EACL,IAAI,EACJ,OAAO,EACP,SAAS,EACT,iBAAiB,EACjB,QAAQ,EACR,IAAI,EACL,MAAM,YAAY,CAAC;AAMpB;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAEhB,mBAAmB;IACnB,QAAQ,EAAE,OAAO,EAAE,CAAC;IAEpB,iBAAiB;IACjB,MAAM,EAAE,KAAK,EAAE,CAAC;IAEhB;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEvC,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,+BAA+B;IAC/B,kBAAkB,EAAE,iBAAiB,EAAE,CAAC;IAExC,wBAAwB;IACxB,MAAM,EAAE,KAAK,EAAE,CAAC;IAEhB,oEAAkE;IAClE,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEvC,2BAA2B;IAC3B,QAAQ,EAAE,QAAQ,CAAC;CAIpB;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,SAAS,EAAE,SAAS,CAAC;IAErB,4BAA4B;IAC5B,KAAK,EAAE,IAAI,EAAE,CAAC;IAEd;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IAEf,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,gBAAgB;IAChB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAEhB,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACrC;AAMD,eAAO,MAAM,cAAc,IAAI,CAAC;AAMhC,wBAAgB,kBAAkB,IAAI,eAAe,CAapD;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,SAAS,GAAG,gBAAgB,CAM7E;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,kBAAkB,CAKhG;AAED,wBAAgB,mBAAmB,IAAI,aAAa,CAInD;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,gBAAgB,CAKzE"}
package/dist/schema.js ADDED
@@ -0,0 +1,46 @@
1
+ // ============================================================================
2
+ // Schema version
3
+ // ============================================================================
4
+ export const SCHEMA_VERSION = 1;
5
+ // ============================================================================
6
+ // Factory functions
7
+ // ============================================================================
8
+ export function createEmptyCatalog() {
9
+ return {
10
+ version: SCHEMA_VERSION,
11
+ projects: [],
12
+ labels: [],
13
+ taskListDocIds: {},
14
+ recurringTemplates: [],
15
+ habits: [],
16
+ habitLogDocIds: {},
17
+ settings: {
18
+ schemaVersion: SCHEMA_VERSION,
19
+ },
20
+ };
21
+ }
22
+ export function createTaskListDocument(projectId) {
23
+ return {
24
+ projectId,
25
+ tasks: [],
26
+ detailDocIds: {},
27
+ };
28
+ }
29
+ export function createTaskDetailDocument(taskId, description) {
30
+ return {
31
+ taskId,
32
+ description,
33
+ };
34
+ }
35
+ export function createNotesDocument() {
36
+ return {
37
+ notes: [],
38
+ };
39
+ }
40
+ export function createHabitLogDocument(habitId) {
41
+ return {
42
+ habitId,
43
+ entries: {},
44
+ };
45
+ }
46
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AA+GA,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEhC,+EAA+E;AAC/E,oBAAoB;AACpB,+EAA+E;AAE/E,MAAM,UAAU,kBAAkB,GAAoB;IACpD,OAAO;QACL,OAAO,EAAE,cAAc;QACvB,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,EAAE;QACV,cAAc,EAAE,EAAE;QAClB,kBAAkB,EAAE,EAAE;QACtB,MAAM,EAAE,EAAE;QACV,cAAc,EAAE,EAAE;QAClB,QAAQ,EAAE;YACR,aAAa,EAAE,cAAc;SAC9B;KACF,CAAC;AAAA,CACH;AAED,MAAM,UAAU,sBAAsB,CAAC,SAAoB,EAAoB;IAC7E,OAAO;QACL,SAAS;QACT,KAAK,EAAE,EAAE;QACT,YAAY,EAAE,EAAE;KACjB,CAAC;AAAA,CACH;AAED,MAAM,UAAU,wBAAwB,CAAC,MAAc,EAAE,WAAmB,EAAsB;IAChG,OAAO;QACL,MAAM;QACN,WAAW;KACZ,CAAC;AAAA,CACH;AAED,MAAM,UAAU,mBAAmB,GAAkB;IACnD,OAAO;QACL,KAAK,EAAE,EAAE;KACV,CAAC;AAAA,CACH;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAgB,EAAoB;IACzE,OAAO;QACL,OAAO;QACP,OAAO,EAAE,EAAE;KACZ,CAAC;AAAA,CACH"}