@prabhask5/stellar-engine 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +446 -373
- package/dist/bin/commands.d.ts +14 -0
- package/dist/bin/commands.d.ts.map +1 -0
- package/dist/bin/commands.js +68 -0
- package/dist/bin/commands.js.map +1 -0
- package/dist/bin/install-pwa.d.ts +20 -6
- package/dist/bin/install-pwa.d.ts.map +1 -1
- package/dist/bin/install-pwa.js +111 -234
- package/dist/bin/install-pwa.js.map +1 -1
- package/dist/config.d.ts +57 -12
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +257 -22
- package/dist/config.js.map +1 -1
- package/dist/database.d.ts +65 -0
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +105 -0
- package/dist/database.js.map +1 -1
- package/dist/entries/types.d.ts +4 -3
- package/dist/entries/types.d.ts.map +1 -1
- package/dist/entries/utils.d.ts +1 -0
- package/dist/entries/utils.d.ts.map +1 -1
- package/dist/entries/utils.js +8 -0
- package/dist/entries/utils.js.map +1 -1
- package/dist/entries/vite.d.ts +1 -1
- package/dist/entries/vite.d.ts.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +150 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +891 -0
- package/dist/schema.js.map +1 -0
- package/dist/sw/build/vite-plugin.d.ts +93 -18
- package/dist/sw/build/vite-plugin.d.ts.map +1 -1
- package/dist/sw/build/vite-plugin.js +356 -22
- package/dist/sw/build/vite-plugin.js.map +1 -1
- package/dist/types.d.ts +139 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,49 +1,92 @@
|
|
|
1
1
|
# @prabhask5/stellar-engine [](https://www.npmjs.com/package/@prabhask5/stellar-engine) [](https://supabase.com)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An offline-first, real-time 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.
|
|
4
4
|
|
|
5
5
|
## Documentation
|
|
6
6
|
|
|
7
7
|
- [API Reference](./API_REFERENCE.md) -- full signatures, parameters, and usage examples for every public export
|
|
8
8
|
- [Architecture](./ARCHITECTURE.md) -- internal design, data flow, and module responsibilities
|
|
9
|
-
- [
|
|
9
|
+
- [Frameworks](./FRAMEWORKS.md) -- More reading on
|
|
10
|
+
frameworks used in stellar-engine
|
|
10
11
|
|
|
11
12
|
## Features
|
|
12
13
|
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
14
|
+
- **Schema-driven configuration** -- declare tables once and the engine auto-generates Dexie stores, database versioning, TypeScript interfaces, and Supabase SQL
|
|
15
|
+
- **Intent-based sync operations** -- operations preserve intent (`increment`, `set`, `create`, `delete`) instead of final state, enabling smarter coalescing and conflict handling
|
|
16
|
+
- **6-step operation coalescing** -- 50 rapid writes are compressed into 1 outbound operation, dramatically reducing sync traffic
|
|
17
|
+
- **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)
|
|
18
|
+
- **Offline authentication** -- SHA-256 credential caching and offline session tokens let users sign in and work without connectivity; sessions reconcile automatically on reconnect
|
|
19
|
+
- **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
|
|
20
|
+
- **Device verification** -- email OTP for untrusted devices with 90-day trust duration
|
|
21
|
+
- **Realtime subscriptions** -- Supabase Realtime WebSocket push with echo suppression and deduplication against polling
|
|
22
|
+
- **Tombstone management** -- soft deletes with configurable garbage collection
|
|
23
|
+
- **Egress optimization** -- column-level selects, operation coalescing, push-only mode when realtime is healthy, cursor-based pulls
|
|
24
|
+
- **CRDT collaborative editing** -- optional Yjs-based subsystem for real-time multi-user editing via Supabase Broadcast
|
|
25
|
+
- **Demo mode** -- sandboxed database, zero Supabase connections, mock auth for instant onboarding experiences
|
|
26
|
+
- **Reactive stores** -- Svelte-compatible stores for sync status, auth state, network state, and remote changes
|
|
27
|
+
- **Store factories** -- `createCollectionStore` and `createDetailStore` for boilerplate-free reactive data layers
|
|
28
|
+
- **Svelte actions** -- `remoteChangeAnimation`, `trackEditing`, `triggerLocalAnimation` for declarative UI behavior
|
|
29
|
+
- **SQL generation** -- auto-generate `CREATE TABLE` statements, RLS policies, and migrations from your schema config
|
|
30
|
+
- **TypeScript generation** -- auto-generate interfaces from schema
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
npm install @prabhask5/stellar-engine
|
|
28
|
-
```
|
|
48
|
+
```ts
|
|
49
|
+
// ─── Install ───────────────────────────────────────────────────────
|
|
50
|
+
// npm install @prabhask5/stellar-engine
|
|
29
51
|
|
|
30
|
-
|
|
52
|
+
// ─── 1. Initialize the engine ──────────────────────────────────────
|
|
53
|
+
// Call once at app startup (e.g., root layout, main entry point)
|
|
31
54
|
|
|
32
|
-
|
|
33
|
-
|
|
55
|
+
import {
|
|
56
|
+
initEngine,
|
|
57
|
+
startSyncEngine,
|
|
58
|
+
supabase,
|
|
59
|
+
getDb,
|
|
60
|
+
resetDatabase,
|
|
61
|
+
validateSupabaseCredentials,
|
|
62
|
+
validateSchema,
|
|
63
|
+
} from '@prabhask5/stellar-engine';
|
|
34
64
|
import { initConfig } from '@prabhask5/stellar-engine/config';
|
|
35
65
|
import { resolveAuthState } from '@prabhask5/stellar-engine/auth';
|
|
36
66
|
|
|
37
67
|
initEngine({
|
|
38
68
|
prefix: 'myapp',
|
|
39
69
|
supabase,
|
|
70
|
+
// Schema-driven: declare tables once, engine handles the rest
|
|
40
71
|
tables: [
|
|
41
72
|
{
|
|
42
|
-
supabaseName: 'projects',
|
|
43
|
-
|
|
73
|
+
supabaseName: 'projects', // Supabase table name
|
|
74
|
+
// Dexie name auto-derived: 'projects'
|
|
75
|
+
columns: 'id, name, description, sort_order, created_at, updated_at, is_deleted, user_id',
|
|
76
|
+
ownershipFilter: 'user_id', // RLS-aware egress filter
|
|
77
|
+
mergeFields: [], // Fields for numeric merge
|
|
78
|
+
excludeFromConflictResolution: ['updated_at'], // Fields to skip in conflict diffing
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
supabaseName: 'tasks',
|
|
82
|
+
columns: 'id, title, project_id, count, sort_order, created_at, updated_at, is_deleted, user_id',
|
|
83
|
+
ownershipFilter: 'user_id',
|
|
84
|
+
mergeFields: ['count'], // Numeric merge: concurrent increments add up
|
|
44
85
|
},
|
|
45
|
-
// ...more tables
|
|
46
86
|
],
|
|
87
|
+
|
|
88
|
+
// Declarative database versioning -- system tables (syncQueue, conflictHistory,
|
|
89
|
+
// offlineCredentials, offlineSession, singleUserConfig) are auto-merged
|
|
47
90
|
database: {
|
|
48
91
|
name: 'MyAppDB',
|
|
49
92
|
versions: [
|
|
@@ -52,488 +95,518 @@ initEngine({
|
|
|
52
95
|
stores: {
|
|
53
96
|
projects: 'id, user_id, updated_at',
|
|
54
97
|
tasks: 'id, project_id, user_id, updated_at',
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
]
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
],
|
|
58
101
|
},
|
|
102
|
+
|
|
103
|
+
// Auth config -- single-user mode with 4-digit PIN
|
|
59
104
|
auth: {
|
|
105
|
+
singleUser: { gateType: 'code', codeLength: 4 },
|
|
60
106
|
enableOfflineAuth: true,
|
|
107
|
+
// emailConfirmation: { enabled: true },
|
|
108
|
+
// deviceVerification: { enabled: true },
|
|
61
109
|
},
|
|
62
|
-
});
|
|
63
|
-
|
|
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
110
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
111
|
+
// Demo mode config (optional)
|
|
112
|
+
demo: {
|
|
113
|
+
seedData: async (db) => {
|
|
114
|
+
await db.table('projects').bulkPut([
|
|
115
|
+
{ id: 'demo-1', name: 'Sample Project', sort_order: 1, is_deleted: false },
|
|
116
|
+
]);
|
|
117
|
+
},
|
|
118
|
+
mockProfile: { email: 'demo@example.com', firstName: 'Demo', lastName: 'User' },
|
|
119
|
+
},
|
|
77
120
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
121
|
+
// CRDT config (optional)
|
|
122
|
+
crdt: {
|
|
123
|
+
persistIntervalMs: 30000,
|
|
124
|
+
maxOfflineDocuments: 50,
|
|
88
125
|
},
|
|
89
126
|
});
|
|
90
127
|
|
|
128
|
+
// ─── 2. Resolve auth and start the engine ──────────────────────────
|
|
129
|
+
|
|
91
130
|
await initConfig();
|
|
92
131
|
const auth = await resolveAuthState();
|
|
93
132
|
|
|
94
133
|
if (!auth.singleUserSetUp) {
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
// If confirmationRequired, prompt user to check email then call completeSingleUserSetup()
|
|
134
|
+
// First-time setup flow
|
|
135
|
+
// → call setupSingleUser(code, profile, email) from your UI
|
|
98
136
|
} else if (auth.authMode === 'none') {
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
// If deviceVerificationRequired, prompt for OTP then call completeDeviceVerification(tokenHash?)
|
|
137
|
+
// Locked -- show unlock screen
|
|
138
|
+
// → call unlockSingleUser(code) from your UI
|
|
102
139
|
} else {
|
|
140
|
+
// Authenticated -- start syncing
|
|
103
141
|
await startSyncEngine();
|
|
104
142
|
}
|
|
105
|
-
```
|
|
106
143
|
|
|
107
|
-
|
|
144
|
+
// ─── 3. CRUD operations ────────────────────────────────────────────
|
|
108
145
|
|
|
109
|
-
|
|
146
|
+
import {
|
|
147
|
+
engineCreate,
|
|
148
|
+
engineUpdate,
|
|
149
|
+
engineDelete,
|
|
150
|
+
engineIncrement,
|
|
151
|
+
engineBatchWrite,
|
|
152
|
+
queryAll,
|
|
153
|
+
queryOne,
|
|
154
|
+
engineGetOrCreate,
|
|
155
|
+
} from '@prabhask5/stellar-engine/data';
|
|
156
|
+
import { generateId, now } from '@prabhask5/stellar-engine/utils';
|
|
157
|
+
|
|
158
|
+
// Create
|
|
159
|
+
const projectId = generateId();
|
|
160
|
+
await engineCreate('projects', {
|
|
161
|
+
id: projectId,
|
|
162
|
+
name: 'New Project',
|
|
163
|
+
sort_order: 1,
|
|
164
|
+
created_at: now(),
|
|
165
|
+
updated_at: now(),
|
|
166
|
+
is_deleted: false,
|
|
167
|
+
user_id: 'current-user-id',
|
|
168
|
+
});
|
|
110
169
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
170
|
+
// Update (only changed fields are synced)
|
|
171
|
+
await engineUpdate('tasks', taskId, {
|
|
172
|
+
title: 'Updated title',
|
|
173
|
+
updated_at: now(),
|
|
174
|
+
});
|
|
114
175
|
|
|
115
|
-
|
|
176
|
+
// Delete (soft delete -- tombstone managed by engine)
|
|
177
|
+
await engineDelete('tasks', taskId);
|
|
116
178
|
|
|
117
|
-
|
|
179
|
+
// Increment (intent-preserved -- concurrent increments merge correctly)
|
|
180
|
+
await engineIncrement('tasks', taskId, 'count', 1);
|
|
118
181
|
|
|
119
|
-
|
|
182
|
+
// Query all rows from local IndexedDB
|
|
183
|
+
const projects = await queryAll('projects');
|
|
120
184
|
|
|
121
|
-
|
|
185
|
+
// Query a single row
|
|
186
|
+
const project = await queryOne('projects', projectId);
|
|
122
187
|
|
|
123
|
-
|
|
188
|
+
// Get or create (atomic upsert)
|
|
189
|
+
const { record, created } = await engineGetOrCreate('projects', projectId, {
|
|
190
|
+
id: projectId,
|
|
191
|
+
name: 'Default Project',
|
|
192
|
+
sort_order: 0,
|
|
193
|
+
created_at: now(),
|
|
194
|
+
updated_at: now(),
|
|
195
|
+
is_deleted: false,
|
|
196
|
+
user_id: 'current-user-id',
|
|
197
|
+
});
|
|
124
198
|
|
|
125
|
-
|
|
199
|
+
// Batch writes (multiple operations in one sync push)
|
|
200
|
+
await engineBatchWrite([
|
|
201
|
+
{ type: 'create', table: 'tasks', data: { id: generateId(), title: 'Task 1', project_id: projectId, count: 0, sort_order: 1, created_at: now(), updated_at: now(), is_deleted: false, user_id: 'current-user-id' } },
|
|
202
|
+
{ type: 'create', table: 'tasks', data: { id: generateId(), title: 'Task 2', project_id: projectId, count: 0, sort_order: 2, created_at: now(), updated_at: now(), is_deleted: false, user_id: 'current-user-id' } },
|
|
203
|
+
{ type: 'update', table: 'projects', id: projectId, data: { updated_at: now() } },
|
|
204
|
+
]);
|
|
126
205
|
|
|
127
|
-
|
|
206
|
+
// ─── 4. Reactive store factories ───────────────────────────────────
|
|
128
207
|
|
|
129
|
-
|
|
208
|
+
import { createCollectionStore, createDetailStore } from '@prabhask5/stellar-engine/stores';
|
|
130
209
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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 |
|
|
210
|
+
// Collection store -- live-updating list from IndexedDB
|
|
211
|
+
const projectsStore = createCollectionStore('projects', {
|
|
212
|
+
filter: (p) => !p.is_deleted,
|
|
213
|
+
sort: (a, b) => a.sort_order - b.sort_order,
|
|
214
|
+
});
|
|
215
|
+
// Subscribe: projectsStore.subscribe(items => { ... })
|
|
150
216
|
|
|
151
|
-
|
|
217
|
+
// Detail store -- single record by ID
|
|
218
|
+
const projectDetail = createDetailStore('projects', projectId);
|
|
219
|
+
// Subscribe: projectDetail.subscribe(record => { ... })
|
|
152
220
|
|
|
153
|
-
|
|
221
|
+
// ─── 5. Reactive stores ───────────────────────────────────────────
|
|
154
222
|
|
|
155
|
-
|
|
223
|
+
import {
|
|
224
|
+
syncStatusStore,
|
|
225
|
+
authState,
|
|
226
|
+
isOnline,
|
|
227
|
+
remoteChangesStore,
|
|
228
|
+
onSyncComplete,
|
|
229
|
+
} from '@prabhask5/stellar-engine/stores';
|
|
230
|
+
|
|
231
|
+
// Listen for sync completions
|
|
232
|
+
onSyncComplete(() => {
|
|
233
|
+
console.log('Sync cycle finished');
|
|
234
|
+
});
|
|
156
235
|
|
|
157
|
-
|
|
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") |
|
|
236
|
+
// ─── 6. CRDT collaborative editing ────────────────────────────────
|
|
163
237
|
|
|
164
|
-
|
|
238
|
+
import {
|
|
239
|
+
openDocument,
|
|
240
|
+
closeDocument,
|
|
241
|
+
createSharedText,
|
|
242
|
+
createBlockDocument,
|
|
243
|
+
updateCursor,
|
|
244
|
+
getCollaborators,
|
|
245
|
+
onCollaboratorsChange,
|
|
246
|
+
} from '@prabhask5/stellar-engine/crdt';
|
|
165
247
|
|
|
166
|
-
|
|
248
|
+
// Open a collaborative document (uses Supabase Broadcast -- zero DB writes per keystroke)
|
|
249
|
+
const provider = await openDocument('doc-1', 'page-1', {
|
|
250
|
+
offlineEnabled: true,
|
|
251
|
+
initialPresence: { name: 'Alice' },
|
|
252
|
+
});
|
|
167
253
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
```
|
|
254
|
+
// Use with any Yjs-compatible editor (Tiptap, BlockNote, etc.)
|
|
255
|
+
const { content, meta } = createBlockDocument(provider.doc);
|
|
256
|
+
meta.set('title', 'My Page');
|
|
216
257
|
|
|
217
|
-
|
|
258
|
+
// Track collaborator cursors and presence
|
|
259
|
+
const unsub = onCollaboratorsChange('doc-1', (collaborators) => {
|
|
260
|
+
// Update avatar list, cursor positions, etc.
|
|
261
|
+
});
|
|
218
262
|
|
|
219
|
-
|
|
263
|
+
await closeDocument('doc-1');
|
|
220
264
|
|
|
221
|
-
|
|
265
|
+
// ─── 7. Demo mode ─────────────────────────────────────────────────
|
|
222
266
|
|
|
223
|
-
|
|
267
|
+
import { setDemoMode } from '@prabhask5/stellar-engine';
|
|
224
268
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
269
|
+
// Toggle demo mode (requires full page reload)
|
|
270
|
+
setDemoMode(true);
|
|
271
|
+
window.location.href = '/';
|
|
272
|
+
|
|
273
|
+
// In demo mode:
|
|
274
|
+
// - Uses '${name}_demo' IndexedDB (real DB never opened)
|
|
275
|
+
// - Zero Supabase network requests
|
|
276
|
+
// - authMode === 'demo', protected routes work with mock data
|
|
277
|
+
// - seedData callback runs on each page load
|
|
278
|
+
|
|
279
|
+
// ─── 8. Diagnostics and debug ──────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
import {
|
|
282
|
+
setDebugMode,
|
|
283
|
+
isDebugMode,
|
|
284
|
+
getSyncDiagnostics,
|
|
285
|
+
getQueueDiagnostics,
|
|
286
|
+
getRealtimeDiagnostics,
|
|
287
|
+
getConflictDiagnostics,
|
|
288
|
+
getEgressDiagnostics,
|
|
289
|
+
getNetworkDiagnostics,
|
|
290
|
+
} from '@prabhask5/stellar-engine/utils';
|
|
239
291
|
|
|
240
|
-
|
|
292
|
+
setDebugMode(true);
|
|
241
293
|
|
|
242
|
-
|
|
294
|
+
// Runtime diagnostics
|
|
295
|
+
const syncInfo = getSyncDiagnostics();
|
|
296
|
+
const queueInfo = getQueueDiagnostics();
|
|
297
|
+
const egressInfo = getEgressDiagnostics();
|
|
243
298
|
|
|
299
|
+
// ─── 9. SQL and TypeScript generation ──────────────────────────────
|
|
300
|
+
|
|
301
|
+
import {
|
|
302
|
+
generateCreateTableSQL,
|
|
303
|
+
generateRLSPolicies,
|
|
304
|
+
generateTypeScriptInterfaces,
|
|
305
|
+
} from '@prabhask5/stellar-engine/utils';
|
|
306
|
+
|
|
307
|
+
// Auto-generate Supabase SQL from your schema config
|
|
308
|
+
const sql = generateCreateTableSQL('projects', tableConfig);
|
|
309
|
+
const rls = generateRLSPolicies('projects', tableConfig);
|
|
310
|
+
const tsInterfaces = generateTypeScriptInterfaces(allTables);
|
|
244
311
|
```
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
+---------------------+ +---------------------+
|
|
312
|
+
|
|
313
|
+
## Commands
|
|
314
|
+
|
|
315
|
+
### Install PWA
|
|
316
|
+
|
|
317
|
+
Scaffold a complete offline-first SvelteKit PWA project with an interactive walkthrough:
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
npx @prabhask5/stellar-engine install pwa
|
|
270
321
|
```
|
|
271
322
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
323
|
+
The wizard prompts for:
|
|
324
|
+
|
|
325
|
+
| Prompt | Required | Description |
|
|
326
|
+
|--------|----------|-------------|
|
|
327
|
+
| App Name | Yes | Full app name (e.g., "Stellar Planner") |
|
|
328
|
+
| Short Name | Yes | Short name for PWA home screen (under 12 chars) |
|
|
329
|
+
| Prefix | Yes | Lowercase key for localStorage, caches, SW (auto-suggested from name) |
|
|
330
|
+
| Description | No | App description (default: "A self-hosted offline-first PWA") |
|
|
331
|
+
|
|
332
|
+
Generates **34+ files** for a production-ready SvelteKit 2 + Svelte 5 project:
|
|
333
|
+
|
|
334
|
+
- **Config files (8):** `vite.config.ts`, `tsconfig.json`, `svelte.config.js`, `eslint.config.js`, `.prettierrc`, `.prettierignore`, `knip.json`, `.gitignore`
|
|
335
|
+
- **Documentation (3):** `README.md`, `ARCHITECTURE.md`, `FRAMEWORKS.md`
|
|
336
|
+
- **Static assets (13):** `manifest.json`, `offline.html`, placeholder SVG icons, email template placeholders
|
|
337
|
+
- **Database (1):** `supabase-schema.sql` with helper functions, example tables, and `trusted_devices` table
|
|
338
|
+
- **Source files (2):** `src/app.html` (PWA-ready with iOS meta tags, SW registration), `src/app.d.ts`
|
|
339
|
+
- **Route files (16):** Root layout, login, setup, profile, protected area, API endpoints, catch-all redirect
|
|
340
|
+
- **Library (1):** `src/lib/types.ts` with re-exports and app-specific type stubs
|
|
341
|
+
- **Git hooks (1):** `.husky/pre-commit` with lint + format + validate
|
|
275
342
|
|
|
276
343
|
## API overview
|
|
277
344
|
|
|
278
|
-
### Configuration
|
|
345
|
+
### Engine Configuration and Lifecycle
|
|
279
346
|
|
|
280
347
|
| Export | Description |
|
|
281
348
|
|---|---|
|
|
282
|
-
| `initEngine(config)` | Initialize the engine with table definitions,
|
|
283
|
-
| `
|
|
284
|
-
| `
|
|
349
|
+
| `initEngine(config)` | Initialize the engine with table definitions, auth, database, and optional CRDT/demo config |
|
|
350
|
+
| `startSyncEngine()` | Start the sync loop, realtime subscriptions, and event listeners |
|
|
351
|
+
| `stopSyncEngine()` | Tear down sync loop and subscriptions cleanly |
|
|
352
|
+
| `runFullSync()` | Run a complete pull-then-push cycle |
|
|
353
|
+
| `scheduleSyncPush()` | Trigger a debounced push of pending operations |
|
|
354
|
+
| `getEngineConfig()` | Retrieve the current engine config (throws if not initialized) |
|
|
355
|
+
| `validateSupabaseCredentials()` | Verify Supabase URL and anon key are valid |
|
|
356
|
+
| `validateSchema()` | Validate all configured tables exist in Supabase |
|
|
285
357
|
|
|
286
|
-
###
|
|
358
|
+
### Database
|
|
287
359
|
|
|
288
360
|
| Export | Description |
|
|
289
361
|
|---|---|
|
|
290
|
-
| `
|
|
291
|
-
| `
|
|
292
|
-
| `
|
|
293
|
-
| `
|
|
294
|
-
| `
|
|
295
|
-
| `
|
|
362
|
+
| `supabase` | The configured `SupabaseClient` instance |
|
|
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 initialization |
|
|
368
|
+
| `resetSupabaseClient()` | Tear down and reinitialize the Supabase client |
|
|
296
369
|
|
|
297
|
-
###
|
|
370
|
+
### CRUD and Query Operations
|
|
298
371
|
|
|
299
372
|
| Export | Description |
|
|
300
373
|
|---|---|
|
|
301
|
-
| `
|
|
302
|
-
| `
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
| `
|
|
309
|
-
| `
|
|
310
|
-
| `
|
|
311
|
-
| `verifyOtp` | Verify OTP token hash from confirmation
|
|
312
|
-
| `
|
|
313
|
-
| `
|
|
314
|
-
| `
|
|
315
|
-
| `resolveAvatarInitial(session, offline, fallback?)` | Single uppercase initial for avatar display. |
|
|
316
|
-
|
|
317
|
-
### Single-user auth
|
|
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 |
|
|
318
395
|
|
|
319
|
-
|
|
396
|
+
### Authentication -- Single-User
|
|
320
397
|
|
|
321
398
|
| Export | Description |
|
|
322
399
|
|---|---|
|
|
323
|
-
| `
|
|
324
|
-
| `
|
|
325
|
-
| `
|
|
326
|
-
| `
|
|
327
|
-
| `
|
|
328
|
-
| `
|
|
329
|
-
| `
|
|
330
|
-
| `
|
|
331
|
-
| `
|
|
332
|
-
| `
|
|
333
|
-
| `
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
| `
|
|
342
|
-
| `
|
|
343
|
-
| `
|
|
344
|
-
| `
|
|
345
|
-
| `
|
|
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
|
-
###
|
|
425
|
+
### Authentication -- Display Utilities
|
|
348
426
|
|
|
349
427
|
| Export | Description |
|
|
350
428
|
|---|---|
|
|
351
|
-
| `
|
|
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
|
-
###
|
|
433
|
+
### Reactive Stores
|
|
354
434
|
|
|
355
435
|
| Export | Description |
|
|
356
436
|
|---|---|
|
|
357
|
-
| `
|
|
358
|
-
| `
|
|
359
|
-
| `
|
|
360
|
-
| `
|
|
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
|
-
|
|
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
|
-
| `
|
|
367
|
-
| `
|
|
368
|
-
| `
|
|
369
|
-
| `
|
|
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
|
-
###
|
|
464
|
+
### Runtime Config
|
|
374
465
|
|
|
375
466
|
| Export | Description |
|
|
376
467
|
|---|---|
|
|
377
|
-
| `
|
|
378
|
-
| `
|
|
379
|
-
| `
|
|
468
|
+
| `initConfig()` | Initialize runtime configuration |
|
|
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
|
-
###
|
|
476
|
+
### Diagnostics and Debug
|
|
382
477
|
|
|
383
478
|
| Export | Description |
|
|
384
479
|
|---|---|
|
|
385
|
-
| `
|
|
386
|
-
| `
|
|
480
|
+
| `getSyncDiagnostics()` | Sync cycle statistics and recent cycle details |
|
|
481
|
+
| `getQueueDiagnostics()` | Pending operation queue state |
|
|
482
|
+
| `getRealtimeDiagnostics()` | Realtime connection state and health |
|
|
483
|
+
| `getConflictDiagnostics()` | Conflict resolution history and stats |
|
|
484
|
+
| `getEgressDiagnostics()` | Data transfer from Supabase (bytes, per-table breakdown) |
|
|
485
|
+
| `getNetworkDiagnostics()` | Network state and connectivity info |
|
|
486
|
+
| `setDebugMode(enabled)` | Enable/disable debug logging |
|
|
487
|
+
| `isDebugMode()` | Check if debug mode is active |
|
|
488
|
+
| `debugLog` / `debugWarn` / `debugError` | Prefixed console helpers (gated by debug mode) |
|
|
489
|
+
|
|
490
|
+
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
491
|
|
|
388
492
|
### Utilities
|
|
389
493
|
|
|
390
494
|
| Export | Description |
|
|
391
495
|
|---|---|
|
|
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
|
|
396
|
-
| `getDeviceId()` | Stable per-device identifier (persisted in localStorage)
|
|
397
|
-
| `debugLog` / `debugWarn` / `debugError` | Prefixed console helpers (gated by `setDebugMode`). |
|
|
496
|
+
| `generateId()` | Generate a UUID |
|
|
497
|
+
| `now()` | Current ISO timestamp string |
|
|
498
|
+
| `calculateNewOrder(before, after)` | Fractional ordering helper for drag-and-drop reorder |
|
|
499
|
+
| `snakeToCamel(str)` | Convert `snake_case` to `camelCase` |
|
|
500
|
+
| `getDeviceId()` | Stable per-device identifier (persisted in localStorage) |
|
|
398
501
|
|
|
399
|
-
###
|
|
502
|
+
### SQL and TypeScript Generation
|
|
400
503
|
|
|
401
|
-
|
|
504
|
+
| Export | Description |
|
|
505
|
+
|---|---|
|
|
506
|
+
| `generateCreateTableSQL(name, config)` | Generate `CREATE TABLE` statement from schema config |
|
|
507
|
+
| `generateRLSPolicies(name, config)` | Generate Row-Level Security policies |
|
|
508
|
+
| `generateTypeScriptInterfaces(tables)` | Generate TypeScript interfaces from all table configs |
|
|
509
|
+
|
|
510
|
+
### Svelte Actions
|
|
402
511
|
|
|
403
|
-
|
|
|
512
|
+
| Export | Description |
|
|
404
513
|
|---|---|
|
|
405
|
-
| `
|
|
406
|
-
| `
|
|
407
|
-
| `
|
|
408
|
-
| `
|
|
409
|
-
|
|
410
|
-
|
|
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
|
|
514
|
+
| `remoteChangeAnimation` | `use:` action that animates an element when a remote change arrives |
|
|
515
|
+
| `trackEditing` | Action that signals the engine a field is being actively edited (suppresses incoming overwrites) |
|
|
516
|
+
| `triggerLocalAnimation` | Programmatically trigger the local-change animation on a node |
|
|
517
|
+
| `truncateTooltip` | Action that shows a tooltip with full text when content is truncated |
|
|
518
|
+
|
|
519
|
+
### Svelte Components
|
|
418
520
|
|
|
419
521
|
| Export | Description |
|
|
420
522
|
|---|---|
|
|
421
|
-
| `
|
|
422
|
-
| `
|
|
423
|
-
| `
|
|
424
|
-
| `truncateTooltip` | Action that shows a tooltip with full text when content is truncated via CSS overflow. |
|
|
523
|
+
| `@prabhask5/stellar-engine/components/SyncStatus` | Animated sync-state indicator with tooltip and PWA refresh (offline/syncing/synced/error/pending states) |
|
|
524
|
+
| `@prabhask5/stellar-engine/components/DeferredChangesBanner` | Cross-device data conflict notification with Update/Dismiss/Show Changes actions and diff preview |
|
|
525
|
+
| `@prabhask5/stellar-engine/components/DemoBanner` | Demo mode indicator banner |
|
|
425
526
|
|
|
426
|
-
###
|
|
527
|
+
### SvelteKit Helpers (optional)
|
|
528
|
+
|
|
529
|
+
These require `svelte ^5.0.0` as a peer dependency.
|
|
427
530
|
|
|
428
531
|
| Export | Description |
|
|
429
532
|
|---|---|
|
|
430
|
-
|
|
|
431
|
-
|
|
|
533
|
+
| Layout load functions | `resolveAuthState` integration for `+layout.ts` |
|
|
534
|
+
| Server handlers | Factory functions for API routes (`getServerConfig`, `createValidateHandler`, `deployToVercel`) |
|
|
535
|
+
| Email confirmation | `handleEmailConfirmation()`, `broadcastAuthConfirmed()` |
|
|
536
|
+
| SW lifecycle | `monitorSwLifecycle()`, `handleSwUpdate()`, `pollForNewServiceWorker()` |
|
|
537
|
+
| Auth hydration | `hydrateAuthState()` for `+layout.svelte` |
|
|
432
538
|
|
|
433
|
-
|
|
539
|
+
### CRDT Collaborative Editing
|
|
434
540
|
|
|
435
|
-
|
|
541
|
+
| Export | Description |
|
|
542
|
+
|---|---|
|
|
543
|
+
| `openDocument(docId, pageId, options?)` | Open a collaborative document via Supabase Broadcast |
|
|
544
|
+
| `closeDocument(docId)` | Close and clean up a document |
|
|
545
|
+
| `createSharedText(doc)` | Create a shared Yjs text type |
|
|
546
|
+
| `createBlockDocument(doc)` | Create a block-based document structure |
|
|
547
|
+
| `updateCursor(docId, cursor)` | Update cursor position for presence |
|
|
548
|
+
| `getCollaborators(docId)` | Get current collaborators |
|
|
549
|
+
| `onCollaboratorsChange(docId, callback)` | Subscribe to collaborator changes |
|
|
550
|
+
| `enableOffline(docId)` / `disableOffline(docId)` | Toggle offline persistence |
|
|
436
551
|
|
|
437
|
-
|
|
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.
|
|
552
|
+
### Types
|
|
441
553
|
|
|
442
|
-
|
|
554
|
+
All TypeScript types are available from `@prabhask5/stellar-engine/types`:
|
|
443
555
|
|
|
444
|
-
|
|
556
|
+
`Session`, `SyncEngineConfig`, `TableConfig`, `BatchOperation`, `SingleUserConfig`, `DemoConfig`, `SyncStatus`, `AuthState`, `CRDTConfig`, and more.
|
|
445
557
|
|
|
446
|
-
|
|
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)
|
|
558
|
+
## Subpath exports
|
|
451
559
|
|
|
452
|
-
|
|
560
|
+
Import only what you need:
|
|
453
561
|
|
|
454
|
-
|
|
562
|
+
| Subpath | Contents |
|
|
563
|
+
|---|---|
|
|
564
|
+
| `@prabhask5/stellar-engine` | Core: `initEngine`, `startSyncEngine`, `runFullSync`, `supabase`, `getDb`, `resetDatabase`, `validateSupabaseCredentials`, `validateSchema`, CRUD, auth, stores, and all re-exports |
|
|
565
|
+
| `@prabhask5/stellar-engine/data` | CRUD + query operations + helpers |
|
|
566
|
+
| `@prabhask5/stellar-engine/auth` | All auth functions |
|
|
567
|
+
| `@prabhask5/stellar-engine/stores` | Reactive stores + store factories + event subscriptions |
|
|
568
|
+
| `@prabhask5/stellar-engine/types` | All type exports |
|
|
569
|
+
| `@prabhask5/stellar-engine/utils` | Utilities + debug + diagnostics + SQL/TS generation |
|
|
570
|
+
| `@prabhask5/stellar-engine/actions` | Svelte `use:` actions |
|
|
571
|
+
| `@prabhask5/stellar-engine/config` | Runtime config + `getDexieTableFor` |
|
|
572
|
+
| `@prabhask5/stellar-engine/vite` | Vite plugin |
|
|
573
|
+
| `@prabhask5/stellar-engine/kit` | SvelteKit helpers (optional) |
|
|
574
|
+
| `@prabhask5/stellar-engine/crdt` | CRDT collaborative editing |
|
|
575
|
+
| `@prabhask5/stellar-engine/components/SyncStatus` | Sync indicator component |
|
|
576
|
+
| `@prabhask5/stellar-engine/components/DeferredChangesBanner` | Conflict banner component |
|
|
577
|
+
| `@prabhask5/stellar-engine/components/DemoBanner` | Demo mode banner component |
|
|
578
|
+
|
|
579
|
+
## Demo mode
|
|
580
|
+
|
|
581
|
+
stellar-engine includes a built-in demo mode that provides a completely isolated sandbox. When active:
|
|
582
|
+
|
|
583
|
+
- **Separate database** -- uses `${name}_demo` IndexedDB; the real database is never opened
|
|
584
|
+
- **No Supabase** -- zero network requests to the backend
|
|
585
|
+
- **Mock auth** -- `authMode === 'demo'`; protected routes work with mock data only
|
|
586
|
+
- **Auto-seeded** -- your `seedData(db)` callback populates the demo database on each page load
|
|
587
|
+
- **Full isolation** -- page reload required to enter/exit (complete engine teardown)
|
|
455
588
|
|
|
456
589
|
```ts
|
|
457
590
|
import type { DemoConfig } from '@prabhask5/stellar-engine';
|
|
591
|
+
import { setDemoMode } from '@prabhask5/stellar-engine';
|
|
458
592
|
|
|
593
|
+
// Define demo config in initEngine
|
|
459
594
|
const demoConfig: DemoConfig = {
|
|
460
595
|
seedData: async (db) => {
|
|
461
|
-
await db.table('
|
|
596
|
+
await db.table('projects').bulkPut([
|
|
597
|
+
{ id: 'demo-1', name: 'Sample Project', sort_order: 1, is_deleted: false },
|
|
598
|
+
]);
|
|
462
599
|
},
|
|
463
600
|
mockProfile: { email: 'demo@example.com', firstName: 'Demo', lastName: 'User' },
|
|
464
601
|
};
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
2. Pass it to `initEngine()`:
|
|
468
602
|
|
|
469
|
-
|
|
470
|
-
initEngine({ ..., demo: demoConfig });
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
3. Toggle demo mode:
|
|
603
|
+
initEngine({ /* ...config */, demo: demoConfig });
|
|
474
604
|
|
|
475
|
-
|
|
476
|
-
import { setDemoMode } from '@prabhask5/stellar-engine';
|
|
605
|
+
// Toggle demo mode from your UI
|
|
477
606
|
setDemoMode(true);
|
|
478
607
|
window.location.href = '/'; // Full reload required
|
|
479
608
|
```
|
|
480
609
|
|
|
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
610
|
## License
|
|
538
611
|
|
|
539
612
|
Private -- not yet published under an open-source license.
|