Skip to content

Commit e618b03

Browse files
authored
Merge pull request #65 from hyp3rd/feat/market-scoped
refactor(core): lift the remaining clean record types into @maqro/core
2 parents cf410c9 + 4b429ad commit e618b03

6 files changed

Lines changed: 130 additions & 104 deletions

File tree

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ repos:
1515
package.json|
1616
package-lock.json|
1717
packages/core/package.json|
18+
packages/core/tsconfig.json|
1819
tsconfig.json|
1920
test-results/.last-run.json|
2021
vercel.json|

lib/db.ts

Lines changed: 27 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,18 @@ import type { MicronutrientProfile } from "@/lib/micronutrients/types";
1010
import type { ShoppingAisle } from "@/lib/shopping/categorize";
1111
import { notifyDataChanged } from "@/lib/sync/data-bus";
1212
import { type DBSchema, type IDBPDatabase, openDB } from "idb";
13-
import type { DailyLog, Versioned, WeightEntry } from "@maqro/core/records";
13+
import type {
14+
BloodPressure,
15+
BodyMeasurement,
16+
CustomFood,
17+
DailyLog,
18+
MealTemplate,
19+
PantryNotification,
20+
Sortable,
21+
Versioned,
22+
WaterIntake,
23+
WeightEntry,
24+
} from "@maqro/core/records";
1425

1526
const DB_NAME = "maqro";
1627
const DB_VERSION = 18;
@@ -71,18 +82,21 @@ const PROFILE_KEY = "default";
7182
* literals (forms, mappers, tests) don't have to know about sync
7283
* internals; the saver functions in this file fill them in, and the
7384
* sync engine treats missing/null as "never synced". */
74-
// `Versioned`, `DailyLog`, and `WeightEntry` now live in `@maqro/core/records`
75-
// (shared with the native app). Imported above for this file's record types +
76-
// the idb schema, and re-exported here so `@/lib/db` consumers are unchanged.
77-
export type { DailyLog, Versioned, WeightEntry };
78-
79-
/** Optional per-row position used by the "custom" sort mode in the
80-
* My Foods / Recipes / Templates views. A `double precision` (number)
81-
* rather than an integer so inserting between two rows is just the
82-
* average of the neighbors' values - no renumber cascade. Nullable
83-
* for rows the user hasn't manually positioned yet; those sort by
84-
* `createdAt` as the fallback. */
85-
export type Sortable = { sortOrder?: number };
85+
// These persisted-record types now live in `@maqro/core/records` (shared with
86+
// the native app). Imported above for this file's schema + helpers, and
87+
// re-exported here so `@/lib/db` consumers are unchanged.
88+
export type {
89+
BloodPressure,
90+
BodyMeasurement,
91+
CustomFood,
92+
DailyLog,
93+
MealTemplate,
94+
PantryNotification,
95+
Sortable,
96+
Versioned,
97+
WaterIntake,
98+
WeightEntry,
99+
};
86100

87101
/** Stores that the sync engine can push DELETEs to. Profile is
88102
* excluded (single-row per user; the only deletion is "delete
@@ -113,74 +127,6 @@ export type DeletionRecord = {
113127
deletedAt: number;
114128
};
115129

116-
/** Stored custom food. Macros are per 100g; the id is a client-minted
117-
* UUID so the same record can exist in IndexedDB and Supabase under the
118-
* same key (no mapping needed for sync). createdAt drives most-recent
119-
* ordering. */
120-
export type CustomFood = Omit<Food, "id" | "source"> & {
121-
id: string;
122-
createdAt: number;
123-
} & Versioned &
124-
Sortable;
125-
126-
/** A reusable meal template - the user named some set of foods (e.g.
127-
* "Greek yogurt bowl") and can apply it to any meal slot on any day. The
128-
* `foods` array is captured with portions as-saved. Id is a client-minted
129-
* UUID shared with Supabase. */
130-
export type MealTemplate = {
131-
id: string;
132-
name: string;
133-
foods: FoodItem[];
134-
createdAt: number;
135-
/** Legacy ms-epoch timestamp. Still bumped on local writes so the
136-
* existing list-sort ("most recently edited first") keeps working
137-
* without a refactor - `localUpdatedAt` is the authoritative one
138-
* for sync. */
139-
updatedAt: number;
140-
} & Versioned &
141-
Sortable;
142-
143-
/** A day's cumulative water intake in millilitres. Keyed by `YYYY-MM-DD`
144-
* local date — one row per day, accumulated as the user logs (each tap
145-
* adds to `ml`). Mirrors `WeightEntry`'s date-keyed, last-write-wins shape;
146-
* the saver differs in that it reads-then-adds rather than overwriting. */
147-
export type WaterIntake = {
148-
date: string;
149-
ml: number;
150-
recordedAt: number;
151-
} & Versioned;
152-
153-
/** A single body-measurement entry - waist / neck / hips in cm, plus
154-
* an optional free-form note. All circumferences optional so the
155-
* user can log just what they have today; the Progress view skips
156-
* derived metrics (body-fat estimate) when required inputs are
157-
* missing. Keyed by `YYYY-MM-DD` like weighIns - most-recent
158-
* measurement on a given day wins. */
159-
export type BodyMeasurement = {
160-
date: string;
161-
waistCm?: number;
162-
neckCm?: number;
163-
hipsCm?: number;
164-
notes?: string;
165-
recordedAt: number;
166-
} & Versioned;
167-
168-
/** A single blood-pressure reading - systolic / diastolic in mmHg, with
169-
* an optional pulse (bpm) and free-form note. Both pressures are required
170-
* (a reading is meaningless without the pair); pulse + notes are optional.
171-
* Storage is always mmHg - there's no imperial variant for blood pressure,
172-
* so unlike weight there's no unit conversion at the boundary. Keyed by
173-
* `YYYY-MM-DD` like weigh-ins and body measurements - most-recent reading
174-
* on a given day wins (multiple readings per day is a future enhancement). */
175-
export type BloodPressure = {
176-
date: string;
177-
systolic: number;
178-
diastolic: number;
179-
pulse?: number;
180-
notes?: string;
181-
recordedAt: number;
182-
} & Versioned;
183-
184130
/** One completed intermittent fast, archived on Stop / auto-finalize. Unlike
185131
* weigh-ins or BP this is **id-keyed**, not date-keyed: a fast can span
186132
* midnight and a user can run more than one in a day, so `(user, day)` is the
@@ -219,25 +165,6 @@ export type PantryItem = {
219165
updatedAt: number;
220166
} & Versioned;
221167

222-
/** A pantry notification — currently only the "low-stock" kind, fired
223-
* when consuming a recipe pushes an item's quantity to/below the
224-
* low-stock threshold. Synced so the bell badge + history stay
225-
* consistent across the user's devices. `itemId` links back to the
226-
* pantry row (kept as a plain field, not an FK, since the item may be
227-
* edited/deleted independently and the notification is still a valid
228-
* historical event). `read` toggles when the user opens the drawer. */
229-
export type PantryNotification = {
230-
id: string;
231-
type: "low-stock";
232-
itemId: string;
233-
itemName: string;
234-
quantity: number;
235-
unit: string;
236-
read: boolean;
237-
createdAt: number;
238-
updatedAt: number;
239-
} & Versioned;
240-
241168
/** Per-item user override for the Shopping List view. Keyed by the
242169
* *lowercased* item name (matches the lowercased lookup keys
243170
* ShoppingListView builds), so the same physical food survives

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "maqro",
3-
"version": "0.6.25",
3+
"version": "0.6.26",
44
"private": true,
55
"license": "Attribution-NonCommercial-NoDerivatives 4.0 International",
66
"workspaces": [

packages/core/src/records.ts

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Meal } from "./types";
1+
import type { Food, FoodItem, Meal } from "./types";
22

33
/** Persisted-record domain types — the shapes stored locally (IndexedDB) and
44
* synced to Supabase. Pure types, shared with the native app; the web storage
@@ -30,3 +30,85 @@ export type WeightEntry = {
3030
kg: number;
3131
recordedAt: number;
3232
} & Versioned;
33+
34+
/** Optional per-row position for the "custom" sort mode (My Foods / Recipes /
35+
* Templates). A `double precision` (number), not an integer, so inserting
36+
* between two rows is just the average of the neighbors — no renumber cascade.
37+
* Nullable for rows the user hasn't manually positioned; those fall back to
38+
* `createdAt`. */
39+
export type Sortable = { sortOrder?: number };
40+
41+
/** Stored custom food. Macros are per 100g; the id is a client-minted UUID so
42+
* the same record exists in IndexedDB and Supabase under the same key (no
43+
* mapping for sync). `createdAt` drives most-recent ordering. */
44+
export type CustomFood = Omit<Food, "id" | "source"> & {
45+
id: string;
46+
createdAt: number;
47+
} & Versioned &
48+
Sortable;
49+
50+
/** A reusable meal template — a named set of foods (e.g. "Greek yogurt bowl")
51+
* applicable to any meal slot on any day. `foods` is captured with portions
52+
* as-saved; id is a client-minted UUID shared with Supabase. */
53+
export type MealTemplate = {
54+
id: string;
55+
name: string;
56+
foods: FoodItem[];
57+
createdAt: number;
58+
/** Legacy ms-epoch timestamp, still bumped on local writes so the list-sort
59+
* ("most recently edited first") keeps working — `localUpdatedAt` is the
60+
* authoritative one for sync. */
61+
updatedAt: number;
62+
} & Versioned &
63+
Sortable;
64+
65+
/** A day's cumulative water intake in millilitres. Keyed by `YYYY-MM-DD` local
66+
* date — one row per day, accumulated as the user logs. Mirrors `WeightEntry`'s
67+
* date-keyed, last-write-wins shape. */
68+
export type WaterIntake = {
69+
date: string;
70+
ml: number;
71+
recordedAt: number;
72+
} & Versioned;
73+
74+
/** A single body-measurement entry — waist / neck / hips in cm + an optional
75+
* note. All circumferences optional so the user can log just what they have.
76+
* Keyed by `YYYY-MM-DD` — most-recent measurement on a day wins. */
77+
export type BodyMeasurement = {
78+
date: string;
79+
waistCm?: number;
80+
neckCm?: number;
81+
hipsCm?: number;
82+
notes?: string;
83+
recordedAt: number;
84+
} & Versioned;
85+
86+
/** A single blood-pressure reading — systolic / diastolic in mmHg, optional
87+
* pulse (bpm) + note. Both pressures required (a reading needs the pair).
88+
* Always mmHg — no imperial variant. Keyed by `YYYY-MM-DD` — most-recent
89+
* reading on a day wins. */
90+
export type BloodPressure = {
91+
date: string;
92+
systolic: number;
93+
diastolic: number;
94+
pulse?: number;
95+
notes?: string;
96+
recordedAt: number;
97+
} & Versioned;
98+
99+
/** A pantry notification — currently only "low-stock", fired when consuming a
100+
* recipe pushes an item's quantity to/below its threshold. Synced so the bell
101+
* badge + history stay consistent across devices. `itemId` is a plain link
102+
* (not an FK — the item may change independently and the event is still
103+
* valid). `read` toggles when the user opens the drawer. */
104+
export type PantryNotification = {
105+
id: string;
106+
type: "low-stock";
107+
itemId: string;
108+
itemName: string;
109+
quantity: number;
110+
unit: string;
111+
read: boolean;
112+
createdAt: number;
113+
updatedAt: number;
114+
} & Versioned;

packages/core/tsconfig.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2017",
4+
"lib": ["ESNext"],
5+
"module": "ESNext",
6+
"moduleResolution": "Bundler",
7+
"strict": true,
8+
"esModuleInterop": true,
9+
"isolatedModules": true,
10+
"skipLibCheck": true,
11+
"forceConsistentCasingInFileNames": true,
12+
"noEmit": true,
13+
"types": []
14+
},
15+
"include": ["src/**/*"]
16+
}

0 commit comments

Comments
 (0)