@richhaase/c2 0.3.2 → 0.4.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/src/storage.ts CHANGED
@@ -1,75 +1,113 @@
1
1
  import { appendFile, readFile, stat, writeFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { dataDir } from "./config.ts";
4
2
  import type { StrokeData, Workout } from "./models.ts";
3
+ import type { DataPaths } from "./paths.ts";
5
4
 
6
- function workoutsPath(): string {
7
- return join(dataDir(), "workouts.jsonl");
5
+ export interface StoreMeta {
6
+ schema_version: number;
7
+ created: string;
8
+ last_sync?: string;
8
9
  }
9
10
 
10
- function strokesPath(workoutId: number): string {
11
- return join(dataDir(), "strokes", `${workoutId}.jsonl`);
12
- }
11
+ export const SCHEMA_VERSION = 1;
13
12
 
14
- export async function readWorkouts(): Promise<Workout[]> {
13
+ export async function readWorkouts(paths: DataPaths): Promise<Workout[]> {
15
14
  try {
16
- const text = await readFile(workoutsPath(), "utf-8");
15
+ const text = await readFile(paths.workouts, "utf-8");
17
16
  return text
18
17
  .split("\n")
19
18
  .filter((line) => line.trim() !== "")
20
19
  .map((line) => JSON.parse(line) as Workout);
21
20
  } catch (err: unknown) {
22
- if ((err as NodeJS.ErrnoException).code === "ENOENT") return [];
21
+ const code = (err as NodeJS.ErrnoException).code;
22
+ if (code === "ENOENT" || code === "ENOTDIR") return [];
23
23
  throw err;
24
24
  }
25
25
  }
26
26
 
27
- export async function appendWorkouts(newWorkouts: Workout[]): Promise<number> {
28
- const existing = await readWorkouts();
27
+ export async function appendWorkouts(paths: DataPaths, newWorkouts: Workout[]): Promise<number> {
28
+ const existing = await readWorkouts(paths);
29
29
  const seen = new Set(existing.map((w) => w.id));
30
30
 
31
31
  const toWrite = newWorkouts.filter((w) => !seen.has(w.id));
32
32
  if (toWrite.length === 0) return 0;
33
33
 
34
34
  const lines = `${toWrite.map((w) => JSON.stringify(w)).join("\n")}\n`;
35
- await appendFile(workoutsPath(), lines, "utf-8");
35
+ await appendFile(paths.workouts, lines, "utf-8");
36
36
  return toWrite.length;
37
37
  }
38
38
 
39
- export async function workoutCount(): Promise<number> {
39
+ export async function workoutCount(paths: DataPaths): Promise<number> {
40
40
  try {
41
- const text = await readFile(workoutsPath(), "utf-8");
41
+ const text = await readFile(paths.workouts, "utf-8");
42
42
  return text.split("\n").filter((line) => line.trim() !== "").length;
43
43
  } catch (err: unknown) {
44
- if ((err as NodeJS.ErrnoException).code === "ENOENT") return 0;
44
+ const code = (err as NodeJS.ErrnoException).code;
45
+ if (code === "ENOENT" || code === "ENOTDIR") return 0;
45
46
  throw err;
46
47
  }
47
48
  }
48
49
 
49
- export async function hasStrokeData(workoutId: number): Promise<boolean> {
50
+ export async function hasStrokeData(paths: DataPaths, workoutId: number): Promise<boolean> {
50
51
  try {
51
- await stat(strokesPath(workoutId));
52
+ await stat(paths.strokeFile(workoutId));
52
53
  return true;
53
54
  } catch (err: unknown) {
54
- if ((err as NodeJS.ErrnoException).code === "ENOENT") return false;
55
+ const code = (err as NodeJS.ErrnoException).code;
56
+ if (code === "ENOENT" || code === "ENOTDIR") return false;
55
57
  throw err;
56
58
  }
57
59
  }
58
60
 
59
- export async function writeStrokeData(workoutId: number, strokes: StrokeData[]): Promise<void> {
61
+ export async function writeStrokeData(
62
+ paths: DataPaths,
63
+ workoutId: number,
64
+ strokes: StrokeData[],
65
+ ): Promise<void> {
60
66
  const lines = `${strokes.map((s) => JSON.stringify(s)).join("\n")}\n`;
61
- await writeFile(strokesPath(workoutId), lines, "utf-8");
67
+ await writeFile(paths.strokeFile(workoutId), lines, "utf-8");
62
68
  }
63
69
 
64
- export async function readStrokeData(workoutId: number): Promise<StrokeData[]> {
70
+ export async function readStrokeData(paths: DataPaths, workoutId: number): Promise<StrokeData[]> {
71
+ let text: string;
65
72
  try {
66
- const text = await readFile(strokesPath(workoutId), "utf-8");
67
- return text
68
- .split("\n")
69
- .filter((line) => line.trim() !== "")
70
- .map((line) => JSON.parse(line) as StrokeData);
73
+ text = await readFile(paths.strokeFile(workoutId), "utf-8");
71
74
  } catch (err: unknown) {
72
- if ((err as NodeJS.ErrnoException).code === "ENOENT") return [];
75
+ const code = (err as NodeJS.ErrnoException).code;
76
+ if (code === "ENOENT" || code === "ENOTDIR") return [];
73
77
  throw err;
74
78
  }
79
+ const strokes: StrokeData[] = [];
80
+ for (const line of text.split("\n")) {
81
+ if (line.trim() === "") continue;
82
+ try {
83
+ const parsed = JSON.parse(line) as unknown;
84
+ if (parsed != null && typeof parsed === "object" && !Array.isArray(parsed)) {
85
+ strokes.push(parsed as StrokeData);
86
+ }
87
+ } catch {}
88
+ }
89
+ return strokes;
90
+ }
91
+
92
+ export async function readMeta(paths: DataPaths): Promise<StoreMeta | null> {
93
+ let text: string;
94
+ try {
95
+ text = await readFile(paths.meta, "utf-8");
96
+ } catch (err: unknown) {
97
+ const code = (err as NodeJS.ErrnoException).code;
98
+ if (code !== "ENOENT" && code !== "ENOTDIR") {
99
+ console.error(`Warning: ${paths.meta} is unreadable and will be ignored.`);
100
+ }
101
+ return null;
102
+ }
103
+ try {
104
+ return JSON.parse(text) as StoreMeta;
105
+ } catch {
106
+ console.error(`Warning: ${paths.meta} is corrupt and will be ignored.`);
107
+ return null;
108
+ }
109
+ }
110
+
111
+ export async function writeMeta(paths: DataPaths, meta: StoreMeta): Promise<void> {
112
+ await writeFile(paths.meta, `${JSON.stringify(meta, null, 2)}\n`, "utf-8");
75
113
  }