stellar-drive 1.0.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.
Files changed (246) hide show
  1. package/README.md +607 -0
  2. package/dist/actions/remoteChange.d.ts +204 -0
  3. package/dist/actions/remoteChange.d.ts.map +1 -0
  4. package/dist/actions/remoteChange.js +424 -0
  5. package/dist/actions/remoteChange.js.map +1 -0
  6. package/dist/actions/truncateTooltip.d.ts +56 -0
  7. package/dist/actions/truncateTooltip.d.ts.map +1 -0
  8. package/dist/actions/truncateTooltip.js +312 -0
  9. package/dist/actions/truncateTooltip.js.map +1 -0
  10. package/dist/auth/crypto.d.ts +41 -0
  11. package/dist/auth/crypto.d.ts.map +1 -0
  12. package/dist/auth/crypto.js +50 -0
  13. package/dist/auth/crypto.js.map +1 -0
  14. package/dist/auth/deviceVerification.d.ts +283 -0
  15. package/dist/auth/deviceVerification.d.ts.map +1 -0
  16. package/dist/auth/deviceVerification.js +575 -0
  17. package/dist/auth/deviceVerification.js.map +1 -0
  18. package/dist/auth/displayUtils.d.ts +98 -0
  19. package/dist/auth/displayUtils.d.ts.map +1 -0
  20. package/dist/auth/displayUtils.js +145 -0
  21. package/dist/auth/displayUtils.js.map +1 -0
  22. package/dist/auth/loginGuard.d.ts +134 -0
  23. package/dist/auth/loginGuard.d.ts.map +1 -0
  24. package/dist/auth/loginGuard.js +276 -0
  25. package/dist/auth/loginGuard.js.map +1 -0
  26. package/dist/auth/offlineCredentials.d.ts +105 -0
  27. package/dist/auth/offlineCredentials.d.ts.map +1 -0
  28. package/dist/auth/offlineCredentials.js +176 -0
  29. package/dist/auth/offlineCredentials.js.map +1 -0
  30. package/dist/auth/offlineSession.d.ts +96 -0
  31. package/dist/auth/offlineSession.d.ts.map +1 -0
  32. package/dist/auth/offlineSession.js +145 -0
  33. package/dist/auth/offlineSession.js.map +1 -0
  34. package/dist/auth/resolveAuthState.d.ts +85 -0
  35. package/dist/auth/resolveAuthState.d.ts.map +1 -0
  36. package/dist/auth/resolveAuthState.js +249 -0
  37. package/dist/auth/resolveAuthState.js.map +1 -0
  38. package/dist/auth/singleUser.d.ts +498 -0
  39. package/dist/auth/singleUser.d.ts.map +1 -0
  40. package/dist/auth/singleUser.js +1282 -0
  41. package/dist/auth/singleUser.js.map +1 -0
  42. package/dist/bin/commands.d.ts +14 -0
  43. package/dist/bin/commands.d.ts.map +1 -0
  44. package/dist/bin/commands.js +68 -0
  45. package/dist/bin/commands.js.map +1 -0
  46. package/dist/bin/install-pwa.d.ts +41 -0
  47. package/dist/bin/install-pwa.d.ts.map +1 -0
  48. package/dist/bin/install-pwa.js +4594 -0
  49. package/dist/bin/install-pwa.js.map +1 -0
  50. package/dist/config.d.ts +249 -0
  51. package/dist/config.d.ts.map +1 -0
  52. package/dist/config.js +395 -0
  53. package/dist/config.js.map +1 -0
  54. package/dist/conflicts.d.ts +306 -0
  55. package/dist/conflicts.d.ts.map +1 -0
  56. package/dist/conflicts.js +807 -0
  57. package/dist/conflicts.js.map +1 -0
  58. package/dist/crdt/awareness.d.ts +128 -0
  59. package/dist/crdt/awareness.d.ts.map +1 -0
  60. package/dist/crdt/awareness.js +284 -0
  61. package/dist/crdt/awareness.js.map +1 -0
  62. package/dist/crdt/channel.d.ts +165 -0
  63. package/dist/crdt/channel.d.ts.map +1 -0
  64. package/dist/crdt/channel.js +522 -0
  65. package/dist/crdt/channel.js.map +1 -0
  66. package/dist/crdt/config.d.ts +58 -0
  67. package/dist/crdt/config.d.ts.map +1 -0
  68. package/dist/crdt/config.js +123 -0
  69. package/dist/crdt/config.js.map +1 -0
  70. package/dist/crdt/helpers.d.ts +104 -0
  71. package/dist/crdt/helpers.d.ts.map +1 -0
  72. package/dist/crdt/helpers.js +116 -0
  73. package/dist/crdt/helpers.js.map +1 -0
  74. package/dist/crdt/offline.d.ts +58 -0
  75. package/dist/crdt/offline.d.ts.map +1 -0
  76. package/dist/crdt/offline.js +130 -0
  77. package/dist/crdt/offline.js.map +1 -0
  78. package/dist/crdt/persistence.d.ts +65 -0
  79. package/dist/crdt/persistence.d.ts.map +1 -0
  80. package/dist/crdt/persistence.js +171 -0
  81. package/dist/crdt/persistence.js.map +1 -0
  82. package/dist/crdt/provider.d.ts +109 -0
  83. package/dist/crdt/provider.d.ts.map +1 -0
  84. package/dist/crdt/provider.js +543 -0
  85. package/dist/crdt/provider.js.map +1 -0
  86. package/dist/crdt/store.d.ts +111 -0
  87. package/dist/crdt/store.d.ts.map +1 -0
  88. package/dist/crdt/store.js +158 -0
  89. package/dist/crdt/store.js.map +1 -0
  90. package/dist/crdt/types.d.ts +281 -0
  91. package/dist/crdt/types.d.ts.map +1 -0
  92. package/dist/crdt/types.js +26 -0
  93. package/dist/crdt/types.js.map +1 -0
  94. package/dist/data.d.ts +502 -0
  95. package/dist/data.d.ts.map +1 -0
  96. package/dist/data.js +862 -0
  97. package/dist/data.js.map +1 -0
  98. package/dist/database.d.ts +153 -0
  99. package/dist/database.d.ts.map +1 -0
  100. package/dist/database.js +325 -0
  101. package/dist/database.js.map +1 -0
  102. package/dist/debug.d.ts +87 -0
  103. package/dist/debug.d.ts.map +1 -0
  104. package/dist/debug.js +135 -0
  105. package/dist/debug.js.map +1 -0
  106. package/dist/demo.d.ts +131 -0
  107. package/dist/demo.d.ts.map +1 -0
  108. package/dist/demo.js +168 -0
  109. package/dist/demo.js.map +1 -0
  110. package/dist/deviceId.d.ts +47 -0
  111. package/dist/deviceId.d.ts.map +1 -0
  112. package/dist/deviceId.js +106 -0
  113. package/dist/deviceId.js.map +1 -0
  114. package/dist/diagnostics.d.ts +292 -0
  115. package/dist/diagnostics.d.ts.map +1 -0
  116. package/dist/diagnostics.js +378 -0
  117. package/dist/diagnostics.js.map +1 -0
  118. package/dist/engine.d.ts +230 -0
  119. package/dist/engine.d.ts.map +1 -0
  120. package/dist/engine.js +2636 -0
  121. package/dist/engine.js.map +1 -0
  122. package/dist/entries/actions.d.ts +16 -0
  123. package/dist/entries/actions.d.ts.map +1 -0
  124. package/dist/entries/actions.js +29 -0
  125. package/dist/entries/actions.js.map +1 -0
  126. package/dist/entries/auth.d.ts +19 -0
  127. package/dist/entries/auth.d.ts.map +1 -0
  128. package/dist/entries/auth.js +50 -0
  129. package/dist/entries/auth.js.map +1 -0
  130. package/dist/entries/config.d.ts +15 -0
  131. package/dist/entries/config.d.ts.map +1 -0
  132. package/dist/entries/config.js +20 -0
  133. package/dist/entries/config.js.map +1 -0
  134. package/dist/entries/crdt.d.ts +32 -0
  135. package/dist/entries/crdt.d.ts.map +1 -0
  136. package/dist/entries/crdt.js +52 -0
  137. package/dist/entries/crdt.js.map +1 -0
  138. package/dist/entries/kit.d.ts +22 -0
  139. package/dist/entries/kit.d.ts.map +1 -0
  140. package/dist/entries/kit.js +58 -0
  141. package/dist/entries/kit.js.map +1 -0
  142. package/dist/entries/stores.d.ts +22 -0
  143. package/dist/entries/stores.d.ts.map +1 -0
  144. package/dist/entries/stores.js +57 -0
  145. package/dist/entries/stores.js.map +1 -0
  146. package/dist/entries/types.d.ts +23 -0
  147. package/dist/entries/types.d.ts.map +1 -0
  148. package/dist/entries/types.js +12 -0
  149. package/dist/entries/types.js.map +1 -0
  150. package/dist/entries/utils.d.ts +12 -0
  151. package/dist/entries/utils.d.ts.map +1 -0
  152. package/dist/entries/utils.js +42 -0
  153. package/dist/entries/utils.js.map +1 -0
  154. package/dist/entries/vite.d.ts +20 -0
  155. package/dist/entries/vite.d.ts.map +1 -0
  156. package/dist/entries/vite.js +26 -0
  157. package/dist/entries/vite.js.map +1 -0
  158. package/dist/index.d.ts +77 -0
  159. package/dist/index.d.ts.map +1 -0
  160. package/dist/index.js +234 -0
  161. package/dist/index.js.map +1 -0
  162. package/dist/kit/auth.d.ts +80 -0
  163. package/dist/kit/auth.d.ts.map +1 -0
  164. package/dist/kit/auth.js +75 -0
  165. package/dist/kit/auth.js.map +1 -0
  166. package/dist/kit/confirm.d.ts +111 -0
  167. package/dist/kit/confirm.d.ts.map +1 -0
  168. package/dist/kit/confirm.js +169 -0
  169. package/dist/kit/confirm.js.map +1 -0
  170. package/dist/kit/loads.d.ts +187 -0
  171. package/dist/kit/loads.d.ts.map +1 -0
  172. package/dist/kit/loads.js +208 -0
  173. package/dist/kit/loads.js.map +1 -0
  174. package/dist/kit/server.d.ts +175 -0
  175. package/dist/kit/server.d.ts.map +1 -0
  176. package/dist/kit/server.js +297 -0
  177. package/dist/kit/server.js.map +1 -0
  178. package/dist/kit/sw.d.ts +176 -0
  179. package/dist/kit/sw.d.ts.map +1 -0
  180. package/dist/kit/sw.js +320 -0
  181. package/dist/kit/sw.js.map +1 -0
  182. package/dist/queue.d.ts +306 -0
  183. package/dist/queue.d.ts.map +1 -0
  184. package/dist/queue.js +925 -0
  185. package/dist/queue.js.map +1 -0
  186. package/dist/realtime.d.ts +280 -0
  187. package/dist/realtime.d.ts.map +1 -0
  188. package/dist/realtime.js +1031 -0
  189. package/dist/realtime.js.map +1 -0
  190. package/dist/runtime/runtimeConfig.d.ts +110 -0
  191. package/dist/runtime/runtimeConfig.d.ts.map +1 -0
  192. package/dist/runtime/runtimeConfig.js +260 -0
  193. package/dist/runtime/runtimeConfig.js.map +1 -0
  194. package/dist/schema.d.ts +150 -0
  195. package/dist/schema.d.ts.map +1 -0
  196. package/dist/schema.js +891 -0
  197. package/dist/schema.js.map +1 -0
  198. package/dist/stores/authState.d.ts +204 -0
  199. package/dist/stores/authState.d.ts.map +1 -0
  200. package/dist/stores/authState.js +336 -0
  201. package/dist/stores/authState.js.map +1 -0
  202. package/dist/stores/factories.d.ts +140 -0
  203. package/dist/stores/factories.d.ts.map +1 -0
  204. package/dist/stores/factories.js +157 -0
  205. package/dist/stores/factories.js.map +1 -0
  206. package/dist/stores/network.d.ts +48 -0
  207. package/dist/stores/network.d.ts.map +1 -0
  208. package/dist/stores/network.js +261 -0
  209. package/dist/stores/network.js.map +1 -0
  210. package/dist/stores/remoteChanges.d.ts +417 -0
  211. package/dist/stores/remoteChanges.d.ts.map +1 -0
  212. package/dist/stores/remoteChanges.js +626 -0
  213. package/dist/stores/remoteChanges.js.map +1 -0
  214. package/dist/stores/sync.d.ts +165 -0
  215. package/dist/stores/sync.d.ts.map +1 -0
  216. package/dist/stores/sync.js +275 -0
  217. package/dist/stores/sync.js.map +1 -0
  218. package/dist/supabase/auth.d.ts +219 -0
  219. package/dist/supabase/auth.d.ts.map +1 -0
  220. package/dist/supabase/auth.js +459 -0
  221. package/dist/supabase/auth.js.map +1 -0
  222. package/dist/supabase/client.d.ts +88 -0
  223. package/dist/supabase/client.d.ts.map +1 -0
  224. package/dist/supabase/client.js +313 -0
  225. package/dist/supabase/client.js.map +1 -0
  226. package/dist/supabase/validate.d.ts +118 -0
  227. package/dist/supabase/validate.d.ts.map +1 -0
  228. package/dist/supabase/validate.js +208 -0
  229. package/dist/supabase/validate.js.map +1 -0
  230. package/dist/sw/build/vite-plugin.d.ts +149 -0
  231. package/dist/sw/build/vite-plugin.d.ts.map +1 -0
  232. package/dist/sw/build/vite-plugin.js +517 -0
  233. package/dist/sw/build/vite-plugin.js.map +1 -0
  234. package/dist/sw/sw.js +664 -0
  235. package/dist/types.d.ts +363 -0
  236. package/dist/types.d.ts.map +1 -0
  237. package/dist/types.js +18 -0
  238. package/dist/types.js.map +1 -0
  239. package/dist/utils.d.ts +85 -0
  240. package/dist/utils.d.ts.map +1 -0
  241. package/dist/utils.js +156 -0
  242. package/dist/utils.js.map +1 -0
  243. package/package.json +117 -0
  244. package/src/components/DeferredChangesBanner.svelte +477 -0
  245. package/src/components/DemoBanner.svelte +110 -0
  246. package/src/components/SyncStatus.svelte +1732 -0
package/README.md ADDED
@@ -0,0 +1,607 @@
1
+ # stellar-drive [![npm version](https://img.shields.io/npm/v/stellar-drive.svg?style=flat)](https://www.npmjs.com/package/stellar-drive) [![Made with Supabase](https://supabase.com/badge-made-with-supabase-dark.svg)](https://supabase.com)
2
+
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
+
5
+ ## Documentation
6
+
7
+ - [Architecture](./ARCHITECTURE.md) -- internal design, data flow, and module responsibilities
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-drive
10
+
11
+ ## Features
12
+
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-drive 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
45
+
46
+ ## Quick start
47
+
48
+ ```ts
49
+ // ─── Install ───────────────────────────────────────────────────────
50
+ // npm install stellar-drive
51
+
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.
55
+
56
+ import { initEngine, startSyncEngine, getDb, resetDatabase } from 'stellar-drive';
57
+ import { initConfig } from 'stellar-drive/config';
58
+ import { resolveAuthState } from 'stellar-drive/auth';
59
+
60
+ initEngine({
61
+ prefix: 'myapp',
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
74
+ },
75
+ },
76
+
77
+ // Auth: flat format with sensible defaults (all fields optional).
78
+ // No nested `singleUser` key needed -- engine normalizes internally.
79
+ auth: {
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 }),
86
+ },
87
+
88
+ // Optional CRDT collaborative editing
89
+ crdt: true, // or { persistIntervalMs: 60000, maxOfflineDocuments: 50 }
90
+
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' },
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
105
+ });
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
+
111
+ await initConfig();
112
+ const auth = await resolveAuthState();
113
+
114
+ if (!auth.singleUserSetUp) {
115
+ // First-time setup flow
116
+ // -> call setupSingleUser(code, profile, email) from your UI
117
+ } else if (auth.authMode === 'none') {
118
+ // Locked -- show unlock screen
119
+ // -> call unlockSingleUser(code) from your UI
120
+ } else {
121
+ // Authenticated -- start syncing
122
+ await startSyncEngine();
123
+ }
124
+
125
+ // ─── 3. CRUD operations ────────────────────────────────────────────
126
+
127
+ import {
128
+ engineCreate,
129
+ engineUpdate,
130
+ engineDelete,
131
+ engineIncrement,
132
+ engineBatchWrite,
133
+ queryAll,
134
+ queryOne,
135
+ engineGetOrCreate,
136
+ } from 'stellar-drive/data';
137
+ import { generateId, now } from 'stellar-drive/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
+ });
150
+
151
+ // Update (only changed fields are synced)
152
+ await engineUpdate('tasks', taskId, {
153
+ title: 'Updated title',
154
+ updated_at: now(),
155
+ });
156
+
157
+ // Delete (soft delete -- tombstone managed by engine)
158
+ await engineDelete('tasks', taskId);
159
+
160
+ // Increment (intent-preserved -- concurrent increments merge correctly)
161
+ await engineIncrement('goals', goalId, 'current_value', 1);
162
+
163
+ // Query all rows from local IndexedDB
164
+ const projects = await queryAll('projects');
165
+
166
+ // Query a single row
167
+ const project = await queryOne('projects', projectId);
168
+
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
+ });
178
+
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
+ ]);
185
+
186
+ // ─── 4. Reactive store factories ───────────────────────────────────
187
+
188
+ import { createCollectionStore, createDetailStore } from 'stellar-drive/stores';
189
+
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 => { ... })
196
+
197
+ // Detail store -- single record by ID
198
+ const projectDetail = createDetailStore('projects', projectId);
199
+ // Subscribe: projectDetail.subscribe(record => { ... })
200
+
201
+ // ─── 5. Reactive stores ────────────────────────────────────────────
202
+
203
+ import {
204
+ syncStatusStore,
205
+ authState,
206
+ isOnline,
207
+ remoteChangesStore,
208
+ onSyncComplete,
209
+ } from 'stellar-drive/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
+ });
220
+
221
+ // ─── 6. Svelte actions ─────────────────────────────────────────────
222
+
223
+ import { remoteChangeAnimation, trackEditing } from 'stellar-drive/actions';
224
+
225
+ // use:remoteChangeAnimation={{ table: 'tasks', id: task.id }}
226
+ // Animates elements when remote changes arrive for that entity.
227
+
228
+ // use:trackEditing={{ table: 'tasks', id: task.id }}
229
+ // Signals the engine a field is being actively edited (suppresses incoming overwrites).
230
+
231
+ // ─── 7. CRDT collaborative editing ────────────────────────────────
232
+
233
+ import {
234
+ openDocument,
235
+ closeDocument,
236
+ createSharedText,
237
+ createBlockDocument,
238
+ updateCursor,
239
+ getCollaborators,
240
+ onCollaboratorsChange,
241
+ } from 'stellar-drive/crdt';
242
+
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
+ });
248
+
249
+ // Use with any Yjs-compatible editor (Tiptap, BlockNote, etc.)
250
+ const { content, meta } = createBlockDocument(provider.doc);
251
+ meta.set('title', 'My Page');
252
+
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 'stellar-drive';
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
274
+ }
275
+
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 'stellar-drive/utils';
283
+ import { getEngineConfig } from 'stellar-drive';
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 'stellar-drive/utils';
296
+ import { getDiagnostics } from 'stellar-drive';
297
+
298
+ setDebugMode(true);
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()
312
+ ```
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 stellar-drive install pwa
322
+ ```
323
+
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
343
+
344
+ ## API overview
345
+
346
+ ### Engine Configuration and Lifecycle
347
+
348
+ | Export | Description |
349
+ |---|---|
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 |
358
+
359
+ ### Database
360
+
361
+ | Export | Description |
362
+ |---|---|
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 |
369
+
370
+ ### CRUD and Query Operations
371
+
372
+ | Export | Description |
373
+ |---|---|
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
385
+
386
+ | Export | Description |
387
+ |---|---|
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 |
395
+
396
+ ### Authentication -- Single-User
397
+
398
+ | Export | Description |
399
+ |---|---|
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
413
+
414
+ | Export | Description |
415
+ |---|---|
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 |
424
+
425
+ ### Authentication -- Display Utilities
426
+
427
+ | Export | Description |
428
+ |---|---|
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 |
432
+
433
+ ### Reactive Stores
434
+
435
+ | Export | Description |
436
+ |---|---|
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
449
+
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
456
+
457
+ | Export | Description |
458
+ |---|---|
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 |
463
+
464
+ ### Runtime Config
465
+
466
+ | Export | Description |
467
+ |---|---|
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 |
475
+
476
+ ### Diagnostics and Debug
477
+
478
+ | Export | Description |
479
+ |---|---|
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()`).
486
+
487
+ ### Utilities
488
+
489
+ | Export | Description |
490
+ |---|---|
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) |
496
+
497
+ ### SQL and TypeScript Generation
498
+
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 |
504
+
505
+ ### Svelte Actions
506
+
507
+ | Export | Description |
508
+ |---|---|
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)
515
+
516
+ | Export | Description |
517
+ |---|---|
518
+ | `stellar-drive/components/SyncStatus` | Animated sync-state indicator with tooltip and PWA refresh |
519
+ | `stellar-drive/components/DeferredChangesBanner` | Cross-device data conflict notification with diff preview |
520
+ | `stellar-drive/components/DemoBanner` | Demo mode indicator banner |
521
+
522
+ ### SvelteKit Helpers (Optional - SvelteKit)
523
+
524
+ These require `svelte ^5.0.0` and `@sveltejs/kit` as peer dependencies.
525
+
526
+ | Export | Description |
527
+ |---|---|
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` |
533
+
534
+ ### CRDT Collaborative Editing
535
+
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 |
546
+
547
+ ### Types
548
+
549
+ All TypeScript types are available from `stellar-drive/types`:
550
+
551
+ `Session`, `SyncEngineConfig`, `TableConfig`, `AuthConfig`, `SchemaDefinition`, `SchemaTableConfig`, `FieldType`, `BatchOperation`, `SingleUserConfig`, `DemoConfig`, `SyncStatus`, `AuthMode`, `CRDTConfig`, and more.
552
+
553
+ ## Subpath exports
554
+
555
+ Import only what you need:
556
+
557
+ | Subpath | Contents |
558
+ |---|---|
559
+ | `stellar-drive` | Core: `initEngine`, `startSyncEngine`, `runFullSync`, `getDb`, `resetDatabase`, `getDiagnostics`, CRUD, auth, stores, and all re-exports |
560
+ | `stellar-drive/data` | CRUD + query operations + helpers |
561
+ | `stellar-drive/auth` | All auth functions |
562
+ | `stellar-drive/stores` | Reactive stores + store factories + event subscriptions |
563
+ | `stellar-drive/types` | All type exports |
564
+ | `stellar-drive/utils` | Utilities + debug + diagnostics + SQL/TS generation |
565
+ | `stellar-drive/actions` | Svelte `use:` actions |
566
+ | `stellar-drive/config` | Runtime config + `getDexieTableFor` |
567
+ | `stellar-drive/vite` | Vite plugin |
568
+ | `stellar-drive/kit` | SvelteKit helpers (optional) |
569
+ | `stellar-drive/crdt` | CRDT collaborative editing |
570
+ | `stellar-drive/components/SyncStatus` | Sync indicator component |
571
+ | `stellar-drive/components/DeferredChangesBanner` | Conflict banner component |
572
+ | `stellar-drive/components/DemoBanner` | Demo mode banner component |
573
+
574
+ ## Demo mode
575
+
576
+ stellar-drive 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)
583
+
584
+ ```ts
585
+ import type { DemoConfig } from 'stellar-drive';
586
+ import { setDemoMode, isDemoMode } from 'stellar-drive';
587
+
588
+ // Define demo config in initEngine
589
+ const demoConfig: DemoConfig = {
590
+ seedData: async (db) => {
591
+ await db.table('projects').bulkPut([
592
+ { id: 'demo-1', name: 'Sample Project', order: 1 },
593
+ ]);
594
+ },
595
+ mockProfile: { email: 'demo@example.com', firstName: 'Demo', lastName: 'User' },
596
+ };
597
+
598
+ initEngine({ /* ...config */, demo: demoConfig });
599
+
600
+ // Toggle demo mode from your UI
601
+ setDemoMode(true);
602
+ window.location.href = '/'; // Full reload required
603
+ ```
604
+
605
+ ## License
606
+
607
+ Private -- not yet published under an open-source license.