@prabhask5/stellar-engine 1.2.0 → 1.2.2

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.
Files changed (49) hide show
  1. package/README.md +454 -386
  2. package/dist/auth/resolveAuthState.js +3 -16
  3. package/dist/auth/resolveAuthState.js.map +1 -1
  4. package/dist/auth/singleUser.d.ts.map +1 -1
  5. package/dist/auth/singleUser.js +2 -3
  6. package/dist/auth/singleUser.js.map +1 -1
  7. package/dist/bin/commands.d.ts +14 -0
  8. package/dist/bin/commands.d.ts.map +1 -0
  9. package/dist/bin/commands.js +68 -0
  10. package/dist/bin/commands.js.map +1 -0
  11. package/dist/bin/install-pwa.d.ts +20 -6
  12. package/dist/bin/install-pwa.d.ts.map +1 -1
  13. package/dist/bin/install-pwa.js +111 -234
  14. package/dist/bin/install-pwa.js.map +1 -1
  15. package/dist/config.d.ts +63 -29
  16. package/dist/config.d.ts.map +1 -1
  17. package/dist/config.js +265 -37
  18. package/dist/config.js.map +1 -1
  19. package/dist/database.d.ts +64 -14
  20. package/dist/database.d.ts.map +1 -1
  21. package/dist/database.js +104 -16
  22. package/dist/database.js.map +1 -1
  23. package/dist/engine.d.ts.map +1 -1
  24. package/dist/engine.js +8 -11
  25. package/dist/engine.js.map +1 -1
  26. package/dist/entries/types.d.ts +4 -3
  27. package/dist/entries/types.d.ts.map +1 -1
  28. package/dist/entries/utils.d.ts +1 -0
  29. package/dist/entries/utils.d.ts.map +1 -1
  30. package/dist/entries/utils.js +8 -0
  31. package/dist/entries/utils.js.map +1 -1
  32. package/dist/entries/vite.d.ts +1 -1
  33. package/dist/entries/vite.d.ts.map +1 -1
  34. package/dist/index.d.ts +6 -2
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +7 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/schema.d.ts +150 -0
  39. package/dist/schema.d.ts.map +1 -0
  40. package/dist/schema.js +891 -0
  41. package/dist/schema.js.map +1 -0
  42. package/dist/sw/build/vite-plugin.d.ts +93 -18
  43. package/dist/sw/build/vite-plugin.d.ts.map +1 -1
  44. package/dist/sw/build/vite-plugin.js +356 -22
  45. package/dist/sw/build/vite-plugin.js.map +1 -1
  46. package/dist/sw/sw.js +0 -5
  47. package/dist/types.d.ts +139 -0
  48. package/dist/types.d.ts.map +1 -1
  49. package/package.json +3 -2
package/README.md CHANGED
@@ -1,539 +1,607 @@
1
1
  # @prabhask5/stellar-engine [![npm version](https://img.shields.io/npm/v/@prabhask5/stellar-engine.svg?style=flat)](https://www.npmjs.com/package/@prabhask5/stellar-engine) [![Made with Supabase](https://supabase.com/badge-made-with-supabase-dark.svg)](https://supabase.com)
2
2
 
3
- A local-first, offline-capable sync engine for **SvelteKit + Supabase + Dexie** applications. All reads come from IndexedDB, all writes land locally first, and a background sync loop ships changes to Supabase -- so your app stays fast and functional regardless of network state.
3
+ A plug-and-play, offline-first sync engine for **Supabase + Dexie.js** applications. All reads come from IndexedDB, all writes land locally first, and a background sync loop ships changes to Supabase -- so your app stays fast and functional regardless of network state. Optional **SvelteKit** integrations are included for teams building with Svelte 5, but the core engine works with any framework or vanilla JS.
4
4
 
5
5
  ## Documentation
6
6
 
7
- - [API Reference](./API_REFERENCE.md) -- full signatures, parameters, and usage examples for every public export
8
7
  - [Architecture](./ARCHITECTURE.md) -- internal design, data flow, and module responsibilities
9
- - [Framework Integration](./FRAMEWORKS.md) -- SvelteKit-specific patterns and conventions
8
+ - [API Reference](./API_REFERENCE.md) -- full signatures, parameters, and usage examples for every public export
9
+ - [Frameworks](./FRAMEWORKS.md) -- more reading on frameworks used in stellar-engine
10
10
 
11
11
  ## Features
12
12
 
13
- - **Intent-based sync operations** -- operations preserve intent (`increment`, `set`, `create`, `delete`) instead of just final state, enabling smarter coalescing and conflict handling.
14
- - **Three-tier conflict resolution** -- field-level diffing, numeric merge fields, and configurable exclusion lists let you resolve conflicts precisely rather than with blanket last-write-wins.
15
- - **Offline authentication** -- credential caching and offline session tokens let users sign in and work without connectivity; sessions reconcile automatically on reconnect.
16
- - **Single-user auth mode** -- for personal apps, use a simplified PIN code or password gate backed by real Supabase email/password auth. The user provides an email during setup; the PIN is padded to meet Supabase's minimum password length and verified server-side. Setup, unlock, lock, and gate change are all handled by the engine with full offline support.
17
- - **Realtime subscriptions** -- Supabase Realtime channels push remote changes into local state instantly, with duplicate-delivery guards to prevent re-processing.
18
- - **Operation coalescing** -- batches of rapid local writes (e.g., 50 individual increments) are compressed into a single outbound operation, reducing sync traffic dramatically.
19
- - **Tombstone management** -- soft deletes are propagated cleanly, and stale tombstones are garbage-collected after a configurable retention period.
20
- - **Egress optimization** -- column-level select lists and ownership filters ensure only the data your client needs is fetched from Supabase.
13
+ - **Schema-driven configuration** -- declare tables once in a simple object; the engine auto-generates Dexie stores, database versioning, TypeScript interfaces, and Supabase SQL
14
+ - **Intent-based sync operations** -- operations preserve intent (`increment`, `set`, `create`, `delete`) instead of final state, enabling smarter coalescing and conflict handling
15
+ - **6-step operation coalescing** -- 50 rapid writes are compressed into 1 outbound operation, dramatically reducing sync traffic
16
+ - **Three-tier conflict resolution** -- field-level auto-merge for non-overlapping changes, different-field merge, and same-field resolution (`local_pending` > `delete_wins` > `last_write_wins` with device ID tiebreaker)
17
+ - **Offline authentication** -- SHA-256 credential caching and offline session tokens let users sign in and work without connectivity; sessions reconcile automatically on reconnect
18
+ - **Single-user PIN/password auth** -- simplified gate backed by real Supabase email/password auth; PIN is padded to meet minimum length and verified server-side
19
+ - **Device verification** -- email OTP for untrusted devices with configurable trust duration
20
+ - **Realtime subscriptions** -- Supabase Realtime WebSocket push with echo suppression and deduplication against polling
21
+ - **Tombstone management** -- soft deletes with configurable garbage collection
22
+ - **Egress optimization** -- column-level selects, operation coalescing, push-only mode when realtime is healthy, cursor-based pulls
23
+ - **CRDT collaborative editing** -- optional Yjs-based subsystem for real-time multi-user editing via Supabase Broadcast
24
+ - **Demo mode** -- sandboxed database, zero Supabase connections, mock auth for instant onboarding experiences
25
+ - **Reactive stores** -- Svelte-compatible stores for sync status, auth state, network state, and remote changes
26
+ - **Store factories** -- `createCollectionStore` and `createDetailStore` for boilerplate-free reactive data layers
27
+ - **Svelte actions** -- `remoteChangeAnimation`, `trackEditing`, `triggerLocalAnimation` for declarative UI behavior
28
+ - **SQL generation** -- auto-generate `CREATE TABLE` statements, RLS policies, and migrations from your schema config
29
+ - **TypeScript generation** -- auto-generate interfaces from schema
30
+ - **Migration generation** -- auto-generate `ALTER TABLE` rename and column rename SQL from `renamedFrom` / `renamedColumns` hints
31
+ - **Diagnostics** -- comprehensive runtime diagnostics covering sync, queue, realtime, conflicts, egress, and network
32
+ - **Debug utilities** -- opt-in debug logging and `window` debug utilities for browser console inspection
33
+ - **SvelteKit integration** (optional) -- layout helpers, server handlers, email confirmation, service worker lifecycle, and auth hydration
34
+ - **PWA scaffolding CLI** -- `stellar-engine install pwa` generates a complete SvelteKit PWA project (34+ files)
35
+
36
+ ### Use cases
37
+
38
+ - Productivity and task management apps
39
+ - Notion-like block editors (with CRDT collaborative editing)
40
+ - Personal finance trackers (numeric merge across devices)
41
+ - File and asset management UIs (fractional ordering for drag-and-drop)
42
+ - Habit trackers and daily planners
43
+ - Knowledge bases and note-taking apps
44
+ - Any app needing offline-first multi-device sync
21
45
 
22
46
  ## Quick start
23
47
 
24
- Install from npm:
25
-
26
- ```bash
27
- npm install @prabhask5/stellar-engine
28
- ```
48
+ ```ts
49
+ // ─── Install ───────────────────────────────────────────────────────
50
+ // npm install @prabhask5/stellar-engine
29
51
 
30
- Initialize the engine at app startup (e.g., in a SvelteKit root `+layout.ts`):
52
+ // ─── 1. Initialize the engine ──────────────────────────────────────
53
+ // Call once at app startup (e.g., root layout, main entry point).
54
+ // Schema-driven: declare tables once, engine handles everything else.
31
55
 
32
- ```ts
33
- import { initEngine, startSyncEngine, supabase } from '@prabhask5/stellar-engine';
56
+ import { initEngine, startSyncEngine, getDb, resetDatabase } from '@prabhask5/stellar-engine';
34
57
  import { initConfig } from '@prabhask5/stellar-engine/config';
35
58
  import { resolveAuthState } from '@prabhask5/stellar-engine/auth';
36
59
 
37
60
  initEngine({
38
61
  prefix: 'myapp',
39
- supabase,
40
- tables: [
41
- {
42
- supabaseName: 'projects',
43
- columns: 'id, name, created_at, updated_at, is_deleted, user_id',
62
+
63
+ // Schema-driven: declare tables once, engine handles the rest.
64
+ // System indexes (id, user_id, created_at, updated_at, deleted, _version)
65
+ // are auto-appended to every table. Database name auto-derived as `${prefix}DB`.
66
+ schema: {
67
+ projects: 'order', // String shorthand = indexes only
68
+ tasks: 'project_id, order', // Comma-separated Dexie indexes
69
+ focus_settings: { singleton: true }, // Object form for full control
70
+ goals: {
71
+ indexes: 'goal_list_id, order',
72
+ numericMergeFields: ['current_value'], // Additive merge on conflicts
73
+ excludeFromConflict: ['device_id'], // Skip these in conflict diffing
44
74
  },
45
- // ...more tables
46
- ],
47
- database: {
48
- name: 'MyAppDB',
49
- versions: [
50
- {
51
- version: 1,
52
- stores: {
53
- projects: 'id, user_id, updated_at',
54
- tasks: 'id, project_id, user_id, updated_at',
55
- }
56
- }
57
- ]
58
75
  },
76
+
77
+ // Auth: flat format with sensible defaults (all fields optional).
78
+ // No nested `singleUser` key needed -- engine normalizes internally.
59
79
  auth: {
60
- enableOfflineAuth: true,
80
+ gateType: 'code', // 'code' | 'password' (default: 'code')
81
+ codeLength: 6, // 4 | 6 (default: 6)
82
+ emailConfirmation: true, // default: true
83
+ deviceVerification: true, // default: true
84
+ profileExtractor: (meta) => ({ firstName: meta.first_name }),
85
+ profileToMetadata: (p) => ({ first_name: p.firstName }),
61
86
  },
62
- });
63
87
 
64
- await initConfig();
65
- const auth = await resolveAuthState();
66
- if (auth.authMode !== 'none') await startSyncEngine();
67
- ```
68
-
69
- ### Single-user mode
70
-
71
- For personal apps with a PIN code gate backed by real Supabase email/password auth:
72
-
73
- ```ts
74
- import { initEngine, startSyncEngine, supabase } from '@prabhask5/stellar-engine';
75
- import { initConfig } from '@prabhask5/stellar-engine/config';
76
- import { resolveAuthState } from '@prabhask5/stellar-engine/auth';
88
+ // Optional CRDT collaborative editing
89
+ crdt: true, // or { persistIntervalMs: 60000, maxOfflineDocuments: 50 }
77
90
 
78
- initEngine({
79
- prefix: 'myapp',
80
- supabase,
81
- tables: [/* ... */],
82
- database: {/* ... */},
83
- auth: {
84
- singleUser: { gateType: 'code', codeLength: 4 },
85
- enableOfflineAuth: true,
86
- // emailConfirmation: { enabled: true }, // require email confirmation on setup
87
- // deviceVerification: { enabled: true }, // require OTP verification on new devices
91
+ // Optional demo mode
92
+ demo: {
93
+ seedData: async (db) => {
94
+ await db.table('projects').bulkPut([
95
+ { id: 'demo-1', name: 'Sample Project', order: 1 },
96
+ ]);
97
+ },
98
+ mockProfile: { email: 'demo@test.com', firstName: 'Demo', lastName: 'User' },
88
99
  },
100
+
101
+ // Tuning (all optional with defaults)
102
+ syncDebounceMs: 2000, // Default: 2000
103
+ syncIntervalMs: 900000, // Default: 900000 (15 min)
104
+ tombstoneMaxAgeDays: 7, // Default: 7
89
105
  });
90
106
 
107
+ // ─── 2. Resolve auth and start the engine ──────────────────────────
108
+ // The engine fetches runtime config (Supabase URL + anon key) from
109
+ // your /api/config endpoint -- no need to pass a supabase client.
110
+
91
111
  await initConfig();
92
112
  const auth = await resolveAuthState();
93
113
 
94
114
  if (!auth.singleUserSetUp) {
95
- // Show setup screen → call setupSingleUser(code, profile, email)
96
- // Returns { error, confirmationRequired }
97
- // If confirmationRequired, prompt user to check email then call completeSingleUserSetup()
115
+ // First-time setup flow
116
+ // -> call setupSingleUser(code, profile, email) from your UI
98
117
  } else if (auth.authMode === 'none') {
99
- // Show unlock screen → call unlockSingleUser(code)
100
- // Returns { error, deviceVerificationRequired?, maskedEmail? }
101
- // If deviceVerificationRequired, prompt for OTP then call completeDeviceVerification(tokenHash?)
118
+ // Locked -- show unlock screen
119
+ // -> call unlockSingleUser(code) from your UI
102
120
  } else {
121
+ // Authenticated -- start syncing
103
122
  await startSyncEngine();
104
123
  }
105
- ```
106
124
 
107
- ## Install PWA Command
125
+ // ─── 3. CRUD operations ────────────────────────────────────────────
108
126
 
109
- Scaffold a complete offline-first PWA project with an interactive walkthrough:
127
+ import {
128
+ engineCreate,
129
+ engineUpdate,
130
+ engineDelete,
131
+ engineIncrement,
132
+ engineBatchWrite,
133
+ queryAll,
134
+ queryOne,
135
+ engineGetOrCreate,
136
+ } from '@prabhask5/stellar-engine/data';
137
+ import { generateId, now } from '@prabhask5/stellar-engine/utils';
138
+
139
+ // Create
140
+ const projectId = generateId();
141
+ await engineCreate('projects', {
142
+ id: projectId,
143
+ name: 'New Project',
144
+ order: 1,
145
+ created_at: now(),
146
+ updated_at: now(),
147
+ deleted: false,
148
+ user_id: 'current-user-id',
149
+ });
110
150
 
111
- ```bash
112
- npx @prabhask5/stellar-engine install pwa
113
- ```
151
+ // Update (only changed fields are synced)
152
+ await engineUpdate('tasks', taskId, {
153
+ title: 'Updated title',
154
+ updated_at: now(),
155
+ });
114
156
 
115
- The wizard guides you through each option (app name, short name, prefix, description), validates input inline, shows a confirmation summary, then scaffolds with animated progress.
157
+ // Delete (soft delete -- tombstone managed by engine)
158
+ await engineDelete('tasks', taskId);
116
159
 
117
- ### What it generates
160
+ // Increment (intent-preserved -- concurrent increments merge correctly)
161
+ await engineIncrement('goals', goalId, 'current_value', 1);
118
162
 
119
- The command creates a full SvelteKit 2 + Svelte 5 project with:
163
+ // Query all rows from local IndexedDB
164
+ const projects = await queryAll('projects');
120
165
 
121
- **Configuration files (8):** `vite.config.ts`, `tsconfig.json`, `svelte.config.js`, `eslint.config.js`, `.prettierrc`, `.prettierignore`, `knip.json`, `.gitignore`
166
+ // Query a single row
167
+ const project = await queryOne('projects', projectId);
122
168
 
123
- **Documentation (3):** `README.md`, `ARCHITECTURE.md`, `FRAMEWORKS.md`
169
+ // Get or create (atomic upsert)
170
+ const { record, created } = await engineGetOrCreate('focus_settings', settingsId, {
171
+ id: settingsId,
172
+ theme: 'dark',
173
+ created_at: now(),
174
+ updated_at: now(),
175
+ deleted: false,
176
+ user_id: 'current-user-id',
177
+ });
124
178
 
125
- **Static assets (13):** `manifest.json`, `offline.html`, placeholder SVG icons (app, dark, maskable, favicon, monochrome, splash, apple-touch), email template placeholders (signup, change-email, device-verification)
179
+ // Batch writes (multiple operations in one sync push)
180
+ await engineBatchWrite([
181
+ { type: 'create', table: 'tasks', data: { id: generateId(), title: 'Task 1', project_id: projectId, order: 1, created_at: now(), updated_at: now(), deleted: false, user_id: 'uid' } },
182
+ { type: 'create', table: 'tasks', data: { id: generateId(), title: 'Task 2', project_id: projectId, order: 2, created_at: now(), updated_at: now(), deleted: false, user_id: 'uid' } },
183
+ { type: 'update', table: 'projects', id: projectId, data: { updated_at: now() } },
184
+ ]);
126
185
 
127
- **Database (1):** `supabase-schema.sql` with helper functions, example table pattern, and `trusted_devices` table
186
+ // ─── 4. Reactive store factories ───────────────────────────────────
128
187
 
129
- **Source files (2):** `src/app.html` (PWA-ready with iOS meta tags, landscape blocker, zoom prevention, SW registration), `src/app.d.ts`
188
+ import { createCollectionStore, createDetailStore } from '@prabhask5/stellar-engine/stores';
130
189
 
131
- **Route files (16):**
132
- | File | What stellar-engine manages | What you customize (TODO) |
133
- |------|---------------------------|--------------------------|
134
- | `src/routes/+layout.ts` | Auth resolution, config init, sync engine startup via `resolveAuthState()`, `initConfig()`, `startSyncEngine()` | `initEngine()` config with your database schema |
135
- | `src/routes/+layout.svelte` | Auth state hydration via `hydrateAuthState()` | App shell (navbar, tab bar, overlays) |
136
- | `src/routes/+page.svelte` | Imports `resolveFirstName`, `onSyncComplete`, `authState`; derives `firstName` reactively | Home page UI |
137
- | `src/routes/+error.svelte` | — | Error page UI |
138
- | `src/routes/setup/+page.ts` | Config check, session validation via `getConfig()`, `getValidSession()` | — (fully managed) |
139
- | `src/routes/setup/+page.svelte` | Imports `setConfig`, `isOnline`, `pollForNewServiceWorker` | Setup wizard UI |
140
- | `src/routes/policy/+page.svelte` | — | Privacy policy content |
141
- | `src/routes/login/+page.svelte` | All auth functions: `setupSingleUser`, `unlockSingleUser`, `getSingleUserInfo`, `completeSingleUserSetup`, `completeDeviceVerification`, `pollDeviceVerification`, `fetchRemoteGateConfig`, `linkSingleUserDevice`, `sendDeviceVerification` | Login page UI |
142
- | `src/routes/confirm/+page.svelte` | Email confirmation via `handleEmailConfirmation()`, `broadcastAuthConfirmed()` | Confirmation page UI |
143
- | `src/routes/api/config/+server.ts` | Fully managed: `getServerConfig()` | — |
144
- | `src/routes/api/setup/deploy/+server.ts` | Fully managed: `deployToVercel()` | — |
145
- | `src/routes/api/setup/validate/+server.ts` | Fully managed: `createValidateHandler()` | — |
146
- | `src/routes/[...catchall]/+page.ts` | Redirect to `/` | — |
147
- | `src/routes/(protected)/+layout.ts` | Auth guard via `resolveAuthState()` with login redirect | — (fully managed) |
148
- | `src/routes/(protected)/+layout.svelte` | — | Protected area chrome |
149
- | `src/routes/(protected)/profile/+page.svelte` | All profile functions: `changeSingleUserGate`, `updateSingleUserProfile`, `getSingleUserInfo`, `changeSingleUserEmail`, `completeSingleUserEmailChange`, `resetDatabase`, `getTrustedDevices`, `removeTrustedDevice`, `getCurrentDeviceId`, `isDebugMode`, `setDebugMode` | Profile page UI |
190
+ // Collection store -- live-updating list from IndexedDB
191
+ const projectsStore = createCollectionStore('projects', {
192
+ filter: (p) => !p.deleted,
193
+ sort: (a, b) => a.order - b.order,
194
+ });
195
+ // Subscribe: projectsStore.subscribe(items => { ... })
150
196
 
151
- **Library (1):** `src/lib/types.ts` with re-exports from stellar-engine + app-specific type stubs
197
+ // Detail store -- single record by ID
198
+ const projectDetail = createDetailStore('projects', projectId);
199
+ // Subscribe: projectDetail.subscribe(record => { ... })
152
200
 
153
- **Git hooks (1):** `.husky/pre-commit` with lint + format + validate
201
+ // ─── 5. Reactive stores ────────────────────────────────────────────
154
202
 
155
- ### Interactive Prompts
203
+ import {
204
+ syncStatusStore,
205
+ authState,
206
+ isOnline,
207
+ remoteChangesStore,
208
+ onSyncComplete,
209
+ } from '@prabhask5/stellar-engine/stores';
210
+
211
+ // $syncStatusStore -- current SyncStatus, last sync time, errors
212
+ // $authState -- { mode, session, offlineProfile, isLoading, authKickedMessage }
213
+ // $isOnline -- reactive boolean reflecting network state
214
+ // remoteChangesStore -- tracks entities recently changed by remote peers
215
+
216
+ // Listen for sync completions
217
+ onSyncComplete(() => {
218
+ console.log('Sync cycle finished');
219
+ });
156
220
 
157
- | Prompt | Required | Description |
158
- |--------|----------|-------------|
159
- | App Name | Yes | Full app name (e.g., "Stellar Planner") |
160
- | Short Name | Yes | Short name for PWA home screen (under 12 chars) |
161
- | Prefix | Yes | Lowercase key for localStorage, caches, SW (auto-suggested from name) |
162
- | Description | No | App description (default: "A self-hosted offline-first PWA") |
221
+ // ─── 6. Svelte actions ─────────────────────────────────────────────
163
222
 
164
- ## Subpath exports
223
+ import { remoteChangeAnimation, trackEditing } from '@prabhask5/stellar-engine/actions';
165
224
 
166
- Import only what you need via subpath exports:
225
+ // use:remoteChangeAnimation={{ table: 'tasks', id: task.id }}
226
+ // Animates elements when remote changes arrive for that entity.
167
227
 
168
- | Subpath | Contents |
169
- |---|---|
170
- | `@prabhask5/stellar-engine` | `initEngine`, `startSyncEngine`, `runFullSync`, `supabase`, `getDb`, `validateSupabaseCredentials`, `validateSchema` |
171
- | `@prabhask5/stellar-engine/data` | All engine CRUD + query operations (`engineCreate`, `engineUpdate`, etc.) |
172
- | `@prabhask5/stellar-engine/auth` | All auth functions (`resolveAuthState`, `signOut`, `setupSingleUser`, `unlockSingleUser`, `lockSingleUser`, `completeSingleUserSetup`, `completeDeviceVerification`, `changeSingleUserEmail`, `completeSingleUserEmailChange`, `padPin`, etc.) |
173
- | `@prabhask5/stellar-engine/stores` | Reactive stores + event subscriptions (`syncStatusStore`, `authState`, `onSyncComplete`, etc.) |
174
- | `@prabhask5/stellar-engine/types` | All type exports (`Session`, `SyncEngineConfig`, `BatchOperation`, `SingleUserConfig`, etc.) |
175
- | `@prabhask5/stellar-engine/utils` | Utility functions (`generateId`, `now`, `calculateNewOrder`, `snakeToCamel`, `debug`, etc.) |
176
- | `@prabhask5/stellar-engine/actions` | Svelte `use:` actions (`remoteChangeAnimation`, `trackEditing`, `triggerLocalAnimation`, `truncateTooltip`) |
177
- | `@prabhask5/stellar-engine/kit` | SvelteKit route helpers, server APIs, load functions, confirmation, auth hydration |
178
- | `@prabhask5/stellar-engine/components/SyncStatus` | Sync status indicator Svelte component |
179
- | `@prabhask5/stellar-engine/components/DeferredChangesBanner` | Cross-device conflict banner Svelte component |
180
- | `@prabhask5/stellar-engine/config` | Runtime config (`initConfig`, `getConfig`, `setConfig`, `getDexieTableFor`) |
181
-
182
- The root export (`@prabhask5/stellar-engine`) re-exports everything for backward compatibility.
183
-
184
- ## Requirements
185
-
186
- **Supabase**
187
-
188
- Your Supabase project needs tables matching the `supabaseName` entries in your config. The corresponding Dexie (IndexedDB) table name is automatically derived from `supabaseName` using `snakeToCamel()` conversion (e.g., `goal_lists` becomes `goalLists`). Each table should have at minimum:
189
- - `id` (uuid primary key)
190
- - `updated_at` (timestamptz) -- used as the sync cursor
191
- - `is_deleted` (boolean, default false) -- for soft-delete / tombstone support
192
- - An ownership column (e.g., `user_id`) if you use `ownershipFilter`
193
-
194
- Row-Level Security policies should scope reads and writes to the authenticated user.
195
-
196
- **Single-user mode additional requirements:**
197
-
198
- Single-user mode uses real Supabase email/password auth where the PIN is padded to meet Supabase's minimum password length. The user provides an email during setup, and the PIN is verified server-side.
199
-
200
- If `deviceVerification` is enabled in the auth config, you need a `trusted_devices` table:
201
-
202
- ```sql
203
- CREATE TABLE trusted_devices (
204
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
205
- user_id uuid REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
206
- device_id text NOT NULL,
207
- device_label text,
208
- trusted_at timestamptz DEFAULT now() NOT NULL,
209
- last_used_at timestamptz DEFAULT now() NOT NULL,
210
- UNIQUE(user_id, device_id)
211
- );
212
- ALTER TABLE trusted_devices ENABLE ROW LEVEL SECURITY;
213
- CREATE POLICY "Users manage own devices" ON trusted_devices FOR ALL
214
- USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);
215
- ```
228
+ // use:trackEditing={{ table: 'tasks', id: task.id }}
229
+ // Signals the engine a field is being actively edited (suppresses incoming overwrites).
216
230
 
217
- If `emailConfirmation` is enabled, Supabase email templates must be configured. See [EMAIL_TEMPLATES.md](./EMAIL_TEMPLATES.md) for the full HTML templates for signup confirmation, email change confirmation, and device verification emails.
231
+ // ─── 7. CRDT collaborative editing ────────────────────────────────
218
232
 
219
- **Schema validation:** The engine automatically validates that all configured tables (and `trusted_devices` when `deviceVerification.enabled`) exist in Supabase on the first sync. Missing tables are reported via `syncStatusStore` and the debug console.
233
+ import {
234
+ openDocument,
235
+ closeDocument,
236
+ createSharedText,
237
+ createBlockDocument,
238
+ updateCursor,
239
+ getCollaborators,
240
+ onCollaboratorsChange,
241
+ } from '@prabhask5/stellar-engine/crdt';
220
242
 
221
- **Dexie (IndexedDB)**
243
+ // Open a collaborative document (uses Supabase Broadcast -- zero DB writes per keystroke)
244
+ const provider = await openDocument('doc-1', 'page-1', {
245
+ offlineEnabled: true,
246
+ initialPresence: { name: 'Alice' },
247
+ });
222
248
 
223
- When you provide a `database` config to `initEngine`, the engine creates and manages the Dexie instance for you. System tables (`syncQueue`, `conflictHistory`, `offlineCredentials`, `offlineSession`, `singleUserConfig`) are automatically merged into every schema version -- you only declare your application tables. Note that the store keys use the **camelCase** Dexie table names (auto-derived from `supabaseName` via `snakeToCamel()`):
249
+ // Use with any Yjs-compatible editor (Tiptap, BlockNote, etc.)
250
+ const { content, meta } = createBlockDocument(provider.doc);
251
+ meta.set('title', 'My Page');
224
252
 
225
- ```ts
226
- database: {
227
- name: 'MyAppDB',
228
- versions: [
229
- {
230
- version: 1,
231
- stores: {
232
- projects: 'id, user_id, updated_at',
233
- tasks: 'id, project_id, user_id, updated_at',
234
- }
235
- }
236
- ]
253
+ // Shared text for simpler use cases
254
+ const text = createSharedText(provider.doc);
255
+
256
+ // Track collaborator cursors and presence
257
+ const unsub = onCollaboratorsChange('doc-1', (collaborators) => {
258
+ // Update avatar list, cursor positions, etc.
259
+ });
260
+
261
+ await closeDocument('doc-1');
262
+
263
+ // ─── 8. Demo mode ──────────────────────────────────────────────────
264
+
265
+ import { setDemoMode, isDemoMode } from '@prabhask5/stellar-engine';
266
+
267
+ // Check if demo mode is active
268
+ if (isDemoMode()) {
269
+ // In demo mode:
270
+ // - Uses '${prefix}DB_demo' IndexedDB (real DB never opened)
271
+ // - Zero Supabase network requests
272
+ // - authMode === 'demo', protected routes work with mock data
273
+ // - seedData callback runs on each page load
237
274
  }
238
- ```
239
275
 
240
- Alternatively, you can provide a pre-created Dexie instance via the `db` config option for full control.
276
+ // Toggle demo mode from your UI (requires full page reload)
277
+ setDemoMode(true);
278
+ window.location.href = '/';
279
+
280
+ // ─── 9. SQL and TypeScript generation ──────────────────────────────
281
+
282
+ import { generateSupabaseSQL, generateTypeScript } from '@prabhask5/stellar-engine/utils';
283
+ import { getEngineConfig } from '@prabhask5/stellar-engine';
284
+
285
+ const config = getEngineConfig();
286
+
287
+ // Auto-generate Supabase SQL (CREATE TABLE + RLS policies) from schema
288
+ const sql = generateSupabaseSQL(config.schema!);
289
+
290
+ // Auto-generate TypeScript interfaces from schema
291
+ const ts = generateTypeScript(config.schema!);
292
+
293
+ // ─── 10. Diagnostics and debug ─────────────────────────────────────
294
+
295
+ import { setDebugMode, isDebugMode } from '@prabhask5/stellar-engine/utils';
296
+ import { getDiagnostics } from '@prabhask5/stellar-engine';
241
297
 
242
- ## Architecture
298
+ setDebugMode(true);
243
299
 
300
+ // Comprehensive runtime diagnostics
301
+ const diagnostics = await getDiagnostics();
302
+ // diagnostics.sync -- sync cycle statistics and recent cycle details
303
+ // diagnostics.queue -- pending operation queue state
304
+ // diagnostics.realtime -- realtime connection state and health
305
+ // diagnostics.conflict -- conflict resolution history and stats
306
+ // diagnostics.egress -- data transfer from Supabase (bytes, per-table breakdown)
307
+ // diagnostics.network -- network state and connectivity info
308
+
309
+ // When debug mode is enabled, utilities are exposed on `window`:
310
+ // window.__myappSyncStats(), window.__myappEgress(), window.__myappTombstones()
311
+ // window.__myappSync.forceFullSync()
244
312
  ```
245
- +---------------------+
246
- | Application UI |
247
- +---------------------+
248
- |
249
- v
250
- +---------------------+ +-------------------+
251
- | Repositories | ----> | Dexie (IDB) |
252
- | (read/write local) | | - app tables |
253
- +---------------------+ | - syncQueue |
254
- | | - conflictHistory |
255
- | queueSyncOperation +-------------------+
256
- v ^
257
- +---------------------+ |
258
- | Sync Engine | ---------------+
259
- | - coalesce ops | hydrate / reconcile
260
- | - push to remote |
261
- | - pull from remote |
262
- | - resolve conflicts |
263
- +---------------------+
264
- |
265
- v
266
- +---------------------+ +---------------------+
267
- | Supabase REST | | Supabase Realtime |
268
- | (push / pull) | | (live subscriptions)|
269
- +---------------------+ +---------------------+
313
+
314
+ ## Commands
315
+
316
+ ### Install PWA
317
+
318
+ Scaffold a complete offline-first SvelteKit PWA project with an interactive walkthrough:
319
+
320
+ ```bash
321
+ npx @prabhask5/stellar-engine install pwa
270
322
  ```
271
323
 
272
- 1. **Repositories** read from and write to Dexie, then enqueue a `SyncOperationItem` describing the intent of the change.
273
- 2. **The engine** debounces outbound operations, coalesces them, and pushes to Supabase via REST. On pull, it fetches rows newer than the local sync cursor and reconciles them with any pending local operations.
274
- 3. **Realtime** channels deliver server-side changes immediately, skipping the next poll cycle when the subscription is healthy.
324
+ The wizard prompts for:
325
+
326
+ | Prompt | Required | Description |
327
+ |--------|----------|-------------|
328
+ | App Name | Yes | Full app name (e.g., "Stellar Planner") |
329
+ | Short Name | Yes | Short name for PWA home screen (under 12 chars) |
330
+ | Prefix | Yes | Lowercase key for localStorage, caches, SW (auto-suggested from name) |
331
+ | Description | No | App description (default: "A self-hosted offline-first PWA") |
332
+
333
+ Generates **34+ files** for a production-ready SvelteKit 2 + Svelte 5 project:
334
+
335
+ - **Config files (8):** `vite.config.ts`, `tsconfig.json`, `svelte.config.js`, `eslint.config.js`, `.prettierrc`, `.prettierignore`, `knip.json`, `.gitignore`
336
+ - **Documentation (3):** `README.md`, `ARCHITECTURE.md`, `FRAMEWORKS.md`
337
+ - **Static assets (13):** `manifest.json`, `offline.html`, placeholder SVG icons, email template placeholders
338
+ - **Database (1):** `supabase-schema.sql` with helper functions, example tables, and `trusted_devices` table
339
+ - **Source files (2):** `src/app.html` (PWA-ready with iOS meta tags, SW registration), `src/app.d.ts`
340
+ - **Route files (16):** Root layout, login, setup, profile, protected area, API endpoints, catch-all redirect
341
+ - **Library (1):** `src/lib/types.ts` with re-exports and app-specific type stubs
342
+ - **Git hooks (1):** `.husky/pre-commit` with lint + format + validate
275
343
 
276
344
  ## API overview
277
345
 
278
- ### Configuration
346
+ ### Engine Configuration and Lifecycle
279
347
 
280
348
  | Export | Description |
281
349
  |---|---|
282
- | `initEngine(config)` | Initialize the engine with table definitions, Supabase client, and Dexie instance. |
283
- | `getEngineConfig()` | Retrieve the current config (throws if not initialized). |
284
- | `SyncEngineConfig` / `TableConfig` | TypeScript interfaces for the config objects. `TableConfig` uses `supabaseName` only; Dexie table names are auto-derived. |
350
+ | `initEngine(config)` | Initialize the engine with schema, auth, and optional CRDT/demo config |
351
+ | `startSyncEngine()` | Start the sync loop, realtime subscriptions, and event listeners |
352
+ | `stopSyncEngine()` | Tear down sync loop and subscriptions cleanly |
353
+ | `runFullSync()` | Run a complete pull-then-push cycle |
354
+ | `scheduleSyncPush()` | Trigger a debounced push of pending operations |
355
+ | `getEngineConfig()` | Retrieve the current engine config (throws if not initialized) |
356
+ | `validateSupabaseCredentials()` | Verify Supabase URL and anon key are valid |
357
+ | `validateSchema()` | Validate all configured tables exist in Supabase |
285
358
 
286
- ### Engine lifecycle
359
+ ### Database
287
360
 
288
361
  | Export | Description |
289
362
  |---|---|
290
- | `startSyncEngine()` | Start the sync loop, realtime subscriptions, and event listeners. |
291
- | `stopSyncEngine()` | Tear down everything cleanly. |
292
- | `scheduleSyncPush()` | Trigger a debounced push of pending operations. |
293
- | `runFullSync()` | Run a complete pull-then-push cycle. |
294
- | `clearLocalCache()` | Wipe all local application data. |
295
- | `clearPendingSyncQueue()` | Drop all pending outbound operations. |
363
+ | `getDb()` | Get the Dexie database instance |
364
+ | `resetDatabase()` | Drop and recreate the local IndexedDB database |
365
+ | `clearLocalCache()` | Wipe all local application data |
366
+ | `clearPendingSyncQueue()` | Drop all pending outbound operations |
367
+ | `getSupabaseAsync()` | Async getter that waits for Supabase client initialization |
368
+ | `resetSupabaseClient()` | Tear down and reinitialize the Supabase client |
296
369
 
297
- ### Entity tracking
370
+ ### CRUD and Query Operations
298
371
 
299
372
  | Export | Description |
300
373
  |---|---|
301
- | `markEntityModified(table, id)` | Record that an entity was recently modified locally (prevents incoming realtime from overwriting). |
302
- | `onSyncComplete(callback)` | Register a callback invoked after each successful sync cycle. |
303
-
304
- ### Auth Utilities
374
+ | `engineCreate(table, data)` | Create a record locally and enqueue sync |
375
+ | `engineUpdate(table, id, data)` | Update specific fields locally and enqueue sync |
376
+ | `engineDelete(table, id)` | Soft-delete a record (tombstone) |
377
+ | `engineIncrement(table, id, field, delta)` | Intent-preserving numeric increment |
378
+ | `engineBatchWrite(operations)` | Execute multiple operations in a single sync push |
379
+ | `engineGetOrCreate(table, id, defaults)` | Atomic get-or-create (upsert) |
380
+ | `queryAll(table, options?)` | Query all rows from local IndexedDB |
381
+ | `queryOne(table, id)` | Query a single row by ID |
382
+ | `markEntityModified(table, id)` | Suppress incoming realtime overwrites for a recently modified entity |
383
+
384
+ ### Authentication -- Core
305
385
 
306
386
  | Export | Description |
307
387
  |---|---|
308
- | `signOut` | Full teardown: stops sync, clears caches, signs out of Supabase. |
309
- | `resendConfirmationEmail` | Resend signup confirmation email. |
310
- | `getUserProfile` / `updateProfile` | Profile read/write via Supabase user metadata. |
311
- | `verifyOtp` | Verify OTP token hash from confirmation email links. |
312
- | `getValidSession` | Get a non-expired Supabase session, or `null`. |
313
- | `resolveFirstName(session, offline, fallback?)` | Resolve display name from session or offline profile with configurable fallback. |
314
- | `resolveUserId(session, offline)` | Extract user UUID from session or offline credentials. |
315
- | `resolveAvatarInitial(session, offline, fallback?)` | Single uppercase initial for avatar display. |
388
+ | `resolveAuthState()` | Determine current auth state (online, offline, or none) |
389
+ | `signOut()` | Full teardown: stop sync, clear caches, sign out of Supabase |
390
+ | `getValidSession()` | Get a non-expired Supabase session, or `null` |
391
+ | `verifyOtp(tokenHash)` | Verify OTP token hash from email confirmation links |
392
+ | `resendConfirmationEmail()` | Resend signup confirmation email |
393
+ | `getUserProfile()` | Read profile from Supabase user metadata |
394
+ | `updateProfile(data)` | Write profile to Supabase user metadata |
316
395
 
317
- ### Single-user auth
318
-
319
- For personal apps that use a simplified PIN or password gate. Uses real Supabase email/password auth where the PIN is padded to meet minimum password length. Enable by setting `auth.singleUser` in the engine config.
396
+ ### Authentication -- Single-User
320
397
 
321
398
  | Export | Description |
322
399
  |---|---|
323
- | `isSingleUserSetUp()` | Check if initial setup is complete. |
324
- | `getSingleUserInfo()` | Get display info (profile, gate type) for the unlock screen. |
325
- | `setupSingleUser(gate, profile, email)` | First-time setup: create gate, Supabase email/password user, and store config. Returns `{ error, confirmationRequired }`. |
326
- | `unlockSingleUser(gate)` | Verify gate and restore session (online or offline). Returns `{ error, deviceVerificationRequired?, maskedEmail? }`. |
327
- | `completeSingleUserSetup()` | Called after the user confirms their email (when `emailConfirmation` is enabled). |
328
- | `completeDeviceVerification(tokenHash?)` | Called after the user completes device OTP verification (when `deviceVerification` is enabled). |
329
- | `lockSingleUser()` | Stop sync and reset auth state without destroying data. |
330
- | `changeSingleUserGate(oldGate, newGate)` | Change the PIN code or password. |
331
- | `updateSingleUserProfile(profile)` | Update profile in IndexedDB and Supabase metadata. |
332
- | `changeSingleUserEmail(newEmail)` | Request email change for single-user mode. Returns `{ error, confirmationRequired }`. |
333
- | `completeSingleUserEmailChange()` | Finalize email change: refresh session, update IndexedDB config and cached credentials. |
334
- | `resetSingleUser()` | Full reset: clear config, sign out, wipe local data. |
335
- | `padPin(pin)` | Pad a PIN to meet Supabase's minimum password length requirement. |
336
-
337
- ### Queue
400
+ | `setupSingleUser(gate, profile, email)` | First-time setup: create gate, Supabase user, and store config |
401
+ | `unlockSingleUser(gate)` | Verify gate and restore session (online or offline) |
402
+ | `lockSingleUser()` | Stop sync and reset auth state without destroying data |
403
+ | `isSingleUserSetUp()` | Check if initial setup is complete |
404
+ | `getSingleUserInfo()` | Get display info (profile, gate type) for the unlock screen |
405
+ | `changeSingleUserGate(oldGate, newGate)` | Change PIN code or password |
406
+ | `updateSingleUserProfile(profile)` | Update profile in IndexedDB and Supabase metadata |
407
+ | `changeSingleUserEmail(newEmail)` | Request email change |
408
+ | `completeSingleUserEmailChange()` | Finalize email change after confirmation |
409
+ | `resetSingleUser()` | Full reset: clear config, sign out, wipe local data |
410
+ | `padPin(pin)` | Pad a PIN to meet Supabase's minimum password length |
411
+
412
+ ### Authentication -- Device Verification
338
413
 
339
414
  | Export | Description |
340
415
  |---|---|
341
- | `queueSyncOperation(item)` | Enqueue a raw `SyncOperationItem`. |
342
- | `queueCreateOperation(table, id, payload)` | Enqueue entity creation. |
343
- | `queueDeleteOperation(table, id)` | Enqueue a soft delete. |
344
- | `coalescePendingOps()` | Compress the outbox in-place (called automatically before push). |
345
- | `getPendingSync()` / `getPendingEntityIds()` | Inspect the current outbox. |
416
+ | `completeDeviceVerification(tokenHash?)` | Complete device OTP verification |
417
+ | `sendDeviceVerification()` | Send device verification email |
418
+ | `pollDeviceVerification()` | Poll for device verification completion |
419
+ | `linkSingleUserDevice()` | Link current device to user after verification |
420
+ | `getTrustedDevices()` | List all trusted devices for current user |
421
+ | `removeTrustedDevice(deviceId)` | Remove a trusted device |
422
+ | `getCurrentDeviceId()` | Get the stable device identifier |
423
+ | `fetchRemoteGateConfig()` | Fetch gate config from Supabase for cross-device setup |
346
424
 
347
- ### Conflict resolution
425
+ ### Authentication -- Display Utilities
348
426
 
349
427
  | Export | Description |
350
428
  |---|---|
351
- | `resolveConflicts(table, localEntity, remoteEntity, pendingOps)` | Three-tier field-level conflict resolver. Returns the merged entity. |
429
+ | `resolveFirstName(session, offline, fallback?)` | Resolve display name from session or offline profile |
430
+ | `resolveUserId(session, offline)` | Extract user UUID from session or offline credentials |
431
+ | `resolveAvatarInitial(session, offline, fallback?)` | Single uppercase initial for avatar display |
352
432
 
353
- ### Realtime
433
+ ### Reactive Stores
354
434
 
355
435
  | Export | Description |
356
436
  |---|---|
357
- | `startRealtimeSubscriptions()` / `stopRealtimeSubscriptions()` | Manage Supabase Realtime channels for all configured tables. |
358
- | `isRealtimeHealthy()` | Realtime connection health check. |
359
- | `wasRecentlyProcessedByRealtime(table, id)` | Guard against duplicate processing. |
360
- | `onRealtimeDataUpdate(callback)` | Register a handler for incoming realtime changes. |
437
+ | `syncStatusStore` | Current `SyncStatus`, last sync time, and errors |
438
+ | `authState` | Reactive auth state object (`mode`, `session`, `offlineProfile`, `isLoading`) |
439
+ | `isAuthenticated` | Derived boolean for auth status |
440
+ | `userDisplayInfo` | Derived display name and avatar info |
441
+ | `isOnline` | Reactive boolean reflecting network state |
442
+ | `remoteChangesStore` | Tracks entities recently changed by remote peers |
443
+ | `createRecentChangeIndicator(table, id)` | Derived indicator for UI highlighting of remote changes |
444
+ | `createPendingDeleteIndicator(table, id)` | Derived indicator for entities awaiting delete confirmation |
445
+ | `onSyncComplete(callback)` | Register a callback invoked after each successful sync cycle |
446
+ | `onRealtimeDataUpdate(callback)` | Register a handler for incoming realtime changes |
447
+
448
+ ### Store Factories
361
449
 
362
- ### Stores (Svelte 5 compatible)
450
+ | Export | Description |
451
+ |---|---|
452
+ | `createCollectionStore(table, options?)` | Live-updating list store from IndexedDB with filter and sort |
453
+ | `createDetailStore(table, id)` | Single-record store by ID |
454
+
455
+ ### Realtime
363
456
 
364
457
  | Export | Description |
365
458
  |---|---|
366
- | `syncStatusStore` | Reactive store exposing current `SyncStatus`, last sync time, and errors. |
367
- | `remoteChangesStore` | Tracks which entities were recently changed by remote peers. |
368
- | `createRecentChangeIndicator(table, id)` | Derived indicator for UI highlighting of remote changes. |
369
- | `createPendingDeleteIndicator(table, id)` | Derived indicator for entities awaiting delete confirmation. |
370
- | `isOnline` | Reactive boolean reflecting network state. |
371
- | `authState` / `isAuthenticated` / `userDisplayInfo` | Reactive auth status stores. |
459
+ | `startRealtimeSubscriptions()` | Start Supabase Realtime channels for all configured tables |
460
+ | `stopRealtimeSubscriptions()` | Stop all Realtime channels |
461
+ | `isRealtimeHealthy()` | Realtime connection health check |
462
+ | `wasRecentlyProcessedByRealtime(table, id)` | Guard against duplicate processing |
372
463
 
373
- ### Supabase client
464
+ ### Runtime Config
374
465
 
375
466
  | Export | Description |
376
467
  |---|---|
377
- | `supabase` | The configured `SupabaseClient` instance. |
378
- | `getSupabaseAsync()` | Async getter that waits for initialization. |
379
- | `resetSupabaseClient()` | Tear down and reinitialize the client. |
468
+ | `initConfig()` | Initialize runtime configuration (fetches Supabase credentials from `/api/config`) |
469
+ | `getConfig()` | Get current config |
470
+ | `setConfig(config)` | Update runtime config |
471
+ | `waitForConfig()` | Async getter that waits for config initialization |
472
+ | `isConfigured()` | Check if config is initialized |
473
+ | `clearConfigCache()` | Clear cached config |
474
+ | `getDexieTableFor(supabaseName)` | Get the Dexie table name for a Supabase table name |
380
475
 
381
- ### Runtime config
476
+ ### Diagnostics and Debug
382
477
 
383
478
  | Export | Description |
384
479
  |---|---|
385
- | `initConfig` / `getConfig` / `waitForConfig` / `setConfig` | Manage app-level runtime configuration (e.g., feature flags loaded from the server). |
386
- | `isConfigured()` / `clearConfigCache()` | Status and cache management. |
480
+ | `getDiagnostics()` | Comprehensive runtime diagnostics (sync, queue, realtime, conflict, egress, network) |
481
+ | `setDebugMode(enabled)` | Enable/disable debug logging |
482
+ | `isDebugMode()` | Check if debug mode is active |
483
+ | `debugLog` / `debugWarn` / `debugError` | Prefixed console helpers (gated by debug mode) |
484
+
485
+ When debug mode is enabled, the engine exposes utilities on `window` using your configured prefix (e.g., `window.__myappSyncStats()`, `window.__myappEgress()`, `window.__myappTombstones()`, `window.__myappSync.forceFullSync()`).
387
486
 
388
487
  ### Utilities
389
488
 
390
489
  | Export | Description |
391
490
  |---|---|
392
- | `generateId()` | Generate a UUID. |
393
- | `now()` | Current ISO timestamp string. |
394
- | `calculateNewOrder(before, after)` | Fractional ordering helper for drag-and-drop reorder. |
395
- | `snakeToCamel(str)` | Convert a `snake_case` string to `camelCase` (also strips invalid characters). Used internally to derive Dexie table names from `supabaseName`. |
396
- | `getDeviceId()` | Stable per-device identifier (persisted in localStorage). |
397
- | `debugLog` / `debugWarn` / `debugError` | Prefixed console helpers (gated by `setDebugMode`). |
491
+ | `generateId()` | Generate a UUID |
492
+ | `now()` | Current ISO timestamp string |
493
+ | `calculateNewOrder(before, after)` | Fractional ordering helper for drag-and-drop reorder |
494
+ | `snakeToCamel(str)` | Convert `snake_case` to `camelCase` |
495
+ | `getDeviceId()` | Stable per-device identifier (persisted in localStorage) |
398
496
 
399
- ### Browser console debug utilities
497
+ ### SQL and TypeScript Generation
400
498
 
401
- When debug mode is enabled, the engine exposes utilities on the `window` object using the configured app prefix (e.g. `stellar`):
499
+ | Export | Description |
500
+ |---|---|
501
+ | `generateSupabaseSQL(schema)` | Generate `CREATE TABLE` statements and RLS policies from schema |
502
+ | `generateTypeScript(schema)` | Generate TypeScript interfaces from schema |
503
+ | `generateMigrationSQL(oldSchema, newSchema)` | Generate `ALTER TABLE` migration SQL for schema changes |
402
504
 
403
- | Window property | Description |
505
+ ### Svelte Actions
506
+
507
+ | Export | Description |
404
508
  |---|---|
405
- | `window.__<prefix>SyncStats()` | View sync cycle statistics (total cycles, recent cycle details, trigger types). |
406
- | `window.__<prefix>Egress()` | Monitor data transfer from Supabase (total bytes, per-table breakdown, recent cycles). |
407
- | `window.__<prefix>Tombstones()` | Check soft-deleted record counts across all tables. |
408
- | `window.__<prefix>Tombstones({ cleanup: true })` | Manually trigger tombstone cleanup. |
409
- | `window.__<prefix>Tombstones({ cleanup: true, force: true })` | Force server cleanup (bypasses 24-hour interval). |
410
- | `window.__<prefix>Sync.forceFullSync()` | Reset sync cursor, clear local data, and re-download everything from server. |
411
- | `window.__<prefix>Sync.resetSyncCursor()` | Clear the stored cursor so the next sync pulls all data. |
412
- | `window.__<prefix>Sync.sync()` | Trigger a manual sync cycle. |
413
- | `window.__<prefix>Sync.getStatus()` | View current sync cursor and pending operation count. |
414
- | `window.__<prefix>Sync.checkConnection()` | Test Supabase connectivity. |
415
- | `window.__<prefix>Sync.realtimeStatus()` | Check realtime connection state and health. |
416
-
417
- ### Svelte actions
509
+ | `remoteChangeAnimation` | `use:` action that animates an element when a remote change arrives |
510
+ | `trackEditing` | Action that signals the engine a field is being actively edited (suppresses incoming overwrites) |
511
+ | `triggerLocalAnimation` | Programmatically trigger the local-change animation on a node |
512
+ | `truncateTooltip` | Action that shows a tooltip with full text when content is truncated |
513
+
514
+ ### Svelte Components (Optional - SvelteKit)
418
515
 
419
516
  | Export | Description |
420
517
  |---|---|
421
- | `remoteChangeAnimation` | Svelte `use:` action that animates an element when a remote change arrives. |
422
- | `trackEditing` | Action that signals the engine a field is being actively edited (suppresses incoming overwrites). |
423
- | `triggerLocalAnimation` | Programmatically trigger the local-change animation on a node. |
424
- | `truncateTooltip` | Action that shows a tooltip with full text when content is truncated via CSS overflow. |
518
+ | `@prabhask5/stellar-engine/components/SyncStatus` | Animated sync-state indicator with tooltip and PWA refresh |
519
+ | `@prabhask5/stellar-engine/components/DeferredChangesBanner` | Cross-device data conflict notification with diff preview |
520
+ | `@prabhask5/stellar-engine/components/DemoBanner` | Demo mode indicator banner |
521
+
522
+ ### SvelteKit Helpers (Optional - SvelteKit)
425
523
 
426
- ### Svelte components
524
+ These require `svelte ^5.0.0` and `@sveltejs/kit` as peer dependencies.
427
525
 
428
526
  | Export | Description |
429
527
  |---|---|
430
- | `@prabhask5/stellar-engine/components/SyncStatus` | Full Svelte 5 component for animated sync-state indicator with tooltip and PWA refresh. Shows offline/syncing/synced/error/pending states with live indicator. |
431
- | `@prabhask5/stellar-engine/components/DeferredChangesBanner` | Full Svelte 5 component for cross-device data conflict notification. Shows when another device pushes changes while user is editing. Provides Update/Dismiss/Show Changes actions with diff preview. |
528
+ | Layout load functions | `resolveAuthState` integration for `+layout.ts` |
529
+ | Server handlers | Factory functions for API routes (`getServerConfig`, `createValidateHandler`, `deployToVercel`) |
530
+ | Email confirmation | `handleEmailConfirmation()`, `broadcastAuthConfirmed()` |
531
+ | SW lifecycle | `monitorSwLifecycle()`, `handleSwUpdate()`, `pollForNewServiceWorker()` |
532
+ | Auth hydration | `hydrateAuthState()` for `+layout.svelte` |
432
533
 
433
- The `UpdatePrompt` component is **not** shipped as a stellar-engine export. Instead, it is generated by `stellar-engine install pwa` at `src/lib/components/UpdatePrompt.svelte` with TODO UI placeholders. The generated component imports `monitorSwLifecycle` and `handleSwUpdate` from `@prabhask5/stellar-engine/kit` for all SW lifecycle logic.
534
+ ### CRDT Collaborative Editing
434
535
 
435
- ## Use cases
536
+ | Export | Description |
537
+ |---|---|
538
+ | `openDocument(docId, pageId, options?)` | Open a collaborative document via Supabase Broadcast |
539
+ | `closeDocument(docId)` | Close and clean up a document |
540
+ | `createSharedText(doc)` | Create a shared Yjs text type |
541
+ | `createBlockDocument(doc)` | Create a block-based document structure |
542
+ | `updateCursor(docId, cursor)` | Update cursor position for presence |
543
+ | `getCollaborators(docId)` | Get current collaborators |
544
+ | `onCollaboratorsChange(docId, callback)` | Subscribe to collaborator changes |
545
+ | `enableOffline(docId)` / `disableOffline(docId)` | Toggle offline persistence |
436
546
 
437
- - **Productivity and task management apps** -- offline-capable task boards, habit trackers, daily planners with cross-device sync.
438
- - **Notion-like editors** -- block-based documents where each block is a synced entity with field-level conflict resolution.
439
- - **Personal finance trackers** -- numeric merge fields handle concurrent balance adjustments across devices.
440
- - **File and asset management UIs** -- fractional ordering keeps drag-and-drop sort order consistent without rewriting every row.
547
+ ### Types
441
548
 
442
- ## Demo Mode
549
+ All TypeScript types are available from `@prabhask5/stellar-engine/types`:
443
550
 
444
- stellar-engine includes a built-in demo mode that provides a completely isolated sandbox for consumer apps. When active:
551
+ `Session`, `SyncEngineConfig`, `TableConfig`, `AuthConfig`, `SchemaDefinition`, `SchemaTableConfig`, `FieldType`, `BatchOperation`, `SingleUserConfig`, `DemoConfig`, `SyncStatus`, `AuthMode`, `CRDTConfig`, and more.
445
552
 
446
- - **Separate database**: Uses `${name}_demo` IndexedDB — the real DB is never opened
447
- - **No Supabase**: Zero network requests to the backend
448
- - **Mock auth**: `authMode === 'demo'` — protected routes work with mock data only
449
- - **Auto-seeded**: Consumer's `seedData(db)` populates the demo DB on each page load
450
- - **Full isolation**: Page reload required to enter/exit (complete engine teardown)
553
+ ## Subpath exports
451
554
 
452
- ### Quick Start
555
+ Import only what you need:
453
556
 
454
- 1. Define a `DemoConfig` with mock data and profile:
557
+ | Subpath | Contents |
558
+ |---|---|
559
+ | `@prabhask5/stellar-engine` | Core: `initEngine`, `startSyncEngine`, `runFullSync`, `getDb`, `resetDatabase`, `getDiagnostics`, CRUD, auth, stores, and all re-exports |
560
+ | `@prabhask5/stellar-engine/data` | CRUD + query operations + helpers |
561
+ | `@prabhask5/stellar-engine/auth` | All auth functions |
562
+ | `@prabhask5/stellar-engine/stores` | Reactive stores + store factories + event subscriptions |
563
+ | `@prabhask5/stellar-engine/types` | All type exports |
564
+ | `@prabhask5/stellar-engine/utils` | Utilities + debug + diagnostics + SQL/TS generation |
565
+ | `@prabhask5/stellar-engine/actions` | Svelte `use:` actions |
566
+ | `@prabhask5/stellar-engine/config` | Runtime config + `getDexieTableFor` |
567
+ | `@prabhask5/stellar-engine/vite` | Vite plugin |
568
+ | `@prabhask5/stellar-engine/kit` | SvelteKit helpers (optional) |
569
+ | `@prabhask5/stellar-engine/crdt` | CRDT collaborative editing |
570
+ | `@prabhask5/stellar-engine/components/SyncStatus` | Sync indicator component |
571
+ | `@prabhask5/stellar-engine/components/DeferredChangesBanner` | Conflict banner component |
572
+ | `@prabhask5/stellar-engine/components/DemoBanner` | Demo mode banner component |
573
+
574
+ ## Demo mode
575
+
576
+ stellar-engine includes a built-in demo mode that provides a completely isolated sandbox. When active:
577
+
578
+ - **Separate database** -- uses `${prefix}DB_demo` IndexedDB; the real database is never opened
579
+ - **No Supabase** -- zero network requests to the backend
580
+ - **Mock auth** -- `authMode === 'demo'`; protected routes work with mock data only
581
+ - **Auto-seeded** -- your `seedData(db)` callback populates the demo database on each page load
582
+ - **Full isolation** -- page reload required to enter/exit (complete engine teardown)
455
583
 
456
584
  ```ts
457
585
  import type { DemoConfig } from '@prabhask5/stellar-engine';
586
+ import { setDemoMode, isDemoMode } from '@prabhask5/stellar-engine';
458
587
 
588
+ // Define demo config in initEngine
459
589
  const demoConfig: DemoConfig = {
460
590
  seedData: async (db) => {
461
- await db.table('items').bulkPut([{ id: '1', name: 'Sample', ... }]);
591
+ await db.table('projects').bulkPut([
592
+ { id: 'demo-1', name: 'Sample Project', order: 1 },
593
+ ]);
462
594
  },
463
595
  mockProfile: { email: 'demo@example.com', firstName: 'Demo', lastName: 'User' },
464
596
  };
465
- ```
466
597
 
467
- 2. Pass it to `initEngine()`:
598
+ initEngine({ /* ...config */, demo: demoConfig });
468
599
 
469
- ```ts
470
- initEngine({ ..., demo: demoConfig });
471
- ```
472
-
473
- 3. Toggle demo mode:
474
-
475
- ```ts
476
- import { setDemoMode } from '@prabhask5/stellar-engine';
600
+ // Toggle demo mode from your UI
477
601
  setDemoMode(true);
478
602
  window.location.href = '/'; // Full reload required
479
603
  ```
480
604
 
481
- The `stellar-engine install pwa` scaffolding generates demo files automatically.
482
-
483
- ## CRDT Collaborative Editing (optional)
484
-
485
- The engine includes an optional Yjs-based CRDT subsystem for real-time collaborative document editing. Enable it by adding `crdt` to your `initEngine()` config:
486
-
487
- ```ts
488
- initEngine({
489
- prefix: 'myapp',
490
- tables: [...],
491
- database: { name: 'myapp-db', versions: [...] },
492
- crdt: {
493
- persistIntervalMs: 30000, // Persist to Supabase every 30s
494
- maxOfflineDocuments: 50, // Max docs stored offline
495
- },
496
- });
497
- ```
498
-
499
- Then use the `@prabhask5/stellar-engine/crdt` subpath:
500
-
501
- ```ts
502
- import {
503
- openDocument, closeDocument,
504
- createSharedText, createBlockDocument,
505
- updateCursor, getCollaborators, onCollaboratorsChange,
506
- enableOffline, disableOffline,
507
- type YDoc, type YText,
508
- } from '@prabhask5/stellar-engine/crdt';
509
-
510
- // Open a collaborative document
511
- const provider = await openDocument('doc-1', 'page-1', {
512
- offlineEnabled: true,
513
- initialPresence: { name: 'Alice' },
514
- });
515
-
516
- // Use with any Yjs-compatible editor (Tiptap, BlockNote, etc.)
517
- const { content, meta } = createBlockDocument(provider.doc);
518
- meta.set('title', 'My Page');
519
-
520
- // Track collaborator cursors
521
- const unsub = onCollaboratorsChange('doc-1', (collaborators) => {
522
- // Update avatar list, cursor positions, etc.
523
- });
524
-
525
- // Close when done
526
- await closeDocument('doc-1');
527
- ```
528
-
529
- Key features:
530
- - **Real-time multi-user editing** via Supabase Broadcast (zero DB writes per keystroke)
531
- - **Cursor/presence awareness** via Supabase Presence
532
- - **Offline-first** with IndexedDB persistence and crash recovery
533
- - **Periodic Supabase persistence** (every 30s) for durable cross-device storage
534
- - **Cross-tab sync** via browser BroadcastChannel API (avoids network for same-device)
535
- - **Consumers never import yjs** -- all Yjs types are re-exported from the engine
536
-
537
605
  ## License
538
606
 
539
607
  Private -- not yet published under an open-source license.