Skip to content
epleanerPublic

About

Sleepy household sleep and tummy-time tracker

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Sleepy / Nappy on Cloudflare

The vanilla web app in web/ is unchanged. Production runs at https://sleepy.elipleaner.com on a native Cloudflare Worker with D1; https://nappy.elipleaner.workers.dev serves the same deployment and database. Keeping the original production origin preserves existing browser-local household selection and offline data. Vite builds public web assets to dist/; the dedicated cloudflare/ project builds ../worker.js, handles /api/*, and serves other paths from ../dist/. The existing epleaner-nappy D1 database ID is pinned in cloudflare/cloudflare.config.ts; deployment never recreates or imports the database.

Keep cloudflare/wrangler.config.ts checked in: it is the native SDK's required bundler input for the public asset directory, not a second Worker runtime configuration. Worker settings and bindings remain in cloudflare/cloudflare.config.ts; generated types/build output and local runtime state stay ignored under .cloudflare/ and .wrangler/.

Build and local checks

Use Node 24.22+ and run npm ci && npm --prefix cloudflare ci; then npm test and npm run build:cloudflare.

For local development, initialize the native dev runtime's local D1 state before API mutations: from cloudflare/, run npx cf d1 migrations apply 5559540f-65d9-4a0f-9676-9ff37fee87d5 --dir ../migrations --local --persist-to .wrangler/state; then run npm run dev from the repository root. The explicit persistence directory matters: omitting it initializes the CLI's separate global simulator, not this dev server's database. Check the selected local/remote target before database commands. curl -i http://localhost:8787/api/health returns 200 {"ok":true} after startup.

Use only a disposable household for timer, tummy, entry edit/delete/undo, join and DELETE smoke; never mutate imported household data during verification. For deployment, build first and run npm --prefix cloudflare run deploy using the authenticated cf CLI, or dispatch the repository's Nappy Cloudflare workflow.

Raw PostgreSQL field conversion (lossless)

Apply migrations/0001_initial.sql before any import. Import all four source tables and all original fields, including households.id,created_at,name,timer_revision; active_timers.household_id,mode,started_at,accumulated_seconds,last_resumed_at,is_paused,updated_at; active_tummy_timers.household_id,started_at,updated_at; and entries.id,household_id,mode,started_at,ended_at,duration_seconds,created_at. Keep household IDs, optional historical names, modes, seconds, revision and booleans (Postgres true/false -> SQLite 1/0) unchanged. PostgreSQL UUID values must be imported as their canonical lowercase 8-4-4-4-12 text; API create/edit/delete/undo normalizes PostgreSQL's accepted UUID spellings to that same identity. Derive households.name_key = households.id.toLowerCase() using JavaScript's Unicode mapping. Let D1 generate a fresh internal incarnation and mutation_nonce; neither changes an application field. Import parents before children with foreign keys enabled.

For each timestamptz, convert from the original PostgreSQL value/offset to UTC ISO 8601 YYYY-MM-DDTHH:MM:SS.ffffffZ using an arbitrary-precision timestamp library or PostgreSQL to_char(value AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"'). Six fractional digits are mandatory; never pass imported timestamps through JS Date, Date.parse, floating-point epoch seconds, or JSON millisecond numbers. Keep last_resumed_at SQL NULL when null. The stored text sorts chronologically and round-trips every source microsecond; API responses deliberately expose numeric epoch milliseconds with fractional milliseconds as PostgreSQL did (limited to JS Number precision). New browser timestamps generally have three significant fractional digits and 000 appended, while explicit fractional-millisecond API inputs round to the nearest microsecond. Completing an imported active timer copies its original started_at text unchanged into the new entry. Check each original row and timestamp field against a separate source snapshot before cutover; compare full text microseconds and booleans, not API projections. A final quiesced snapshot must be compared again before DNS change. No private data belongs in this repository or CI logs.

All household mutations use D1 atomic batches with a persisted nonce CAS (including read-dependent timer, neighbor edits, undo and household delete). A rejected concurrent CAS has no side effects; the operation rereads state before retry. timer_revision is distinct from the internal nonce and advances only for timer/tummy transitions. An incarnation token rejects stale operations across delete/recreate of the same household name. Browser local storage remains sleepy.household.id, and this deployment has no PostgreSQL or in-memory fallback.

The manual GitHub workflow builds/tests and publishes the Worker on explicit dispatch. It does not import data, apply migrations, or retire Railway. Store an API token with account-scoped Workers Scripts Write as the repository's CLOUDFLARE_API_TOKEN secret. Prebuilt deployment uses the pinned existing D1 binding with --provision=false and does not require D1 API permission. Initial schema/import operations use the operator's separately authenticated D1 access. The workflow itself needs only GitHub contents: read.

About

Sleepy household sleep and tummy-time tracker

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages