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.
- package/README.md +607 -0
- package/dist/actions/remoteChange.d.ts +204 -0
- package/dist/actions/remoteChange.d.ts.map +1 -0
- package/dist/actions/remoteChange.js +424 -0
- package/dist/actions/remoteChange.js.map +1 -0
- package/dist/actions/truncateTooltip.d.ts +56 -0
- package/dist/actions/truncateTooltip.d.ts.map +1 -0
- package/dist/actions/truncateTooltip.js +312 -0
- package/dist/actions/truncateTooltip.js.map +1 -0
- package/dist/auth/crypto.d.ts +41 -0
- package/dist/auth/crypto.d.ts.map +1 -0
- package/dist/auth/crypto.js +50 -0
- package/dist/auth/crypto.js.map +1 -0
- package/dist/auth/deviceVerification.d.ts +283 -0
- package/dist/auth/deviceVerification.d.ts.map +1 -0
- package/dist/auth/deviceVerification.js +575 -0
- package/dist/auth/deviceVerification.js.map +1 -0
- package/dist/auth/displayUtils.d.ts +98 -0
- package/dist/auth/displayUtils.d.ts.map +1 -0
- package/dist/auth/displayUtils.js +145 -0
- package/dist/auth/displayUtils.js.map +1 -0
- package/dist/auth/loginGuard.d.ts +134 -0
- package/dist/auth/loginGuard.d.ts.map +1 -0
- package/dist/auth/loginGuard.js +276 -0
- package/dist/auth/loginGuard.js.map +1 -0
- package/dist/auth/offlineCredentials.d.ts +105 -0
- package/dist/auth/offlineCredentials.d.ts.map +1 -0
- package/dist/auth/offlineCredentials.js +176 -0
- package/dist/auth/offlineCredentials.js.map +1 -0
- package/dist/auth/offlineSession.d.ts +96 -0
- package/dist/auth/offlineSession.d.ts.map +1 -0
- package/dist/auth/offlineSession.js +145 -0
- package/dist/auth/offlineSession.js.map +1 -0
- package/dist/auth/resolveAuthState.d.ts +85 -0
- package/dist/auth/resolveAuthState.d.ts.map +1 -0
- package/dist/auth/resolveAuthState.js +249 -0
- package/dist/auth/resolveAuthState.js.map +1 -0
- package/dist/auth/singleUser.d.ts +498 -0
- package/dist/auth/singleUser.d.ts.map +1 -0
- package/dist/auth/singleUser.js +1282 -0
- package/dist/auth/singleUser.js.map +1 -0
- 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 +41 -0
- package/dist/bin/install-pwa.d.ts.map +1 -0
- package/dist/bin/install-pwa.js +4594 -0
- package/dist/bin/install-pwa.js.map +1 -0
- package/dist/config.d.ts +249 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +395 -0
- package/dist/config.js.map +1 -0
- package/dist/conflicts.d.ts +306 -0
- package/dist/conflicts.d.ts.map +1 -0
- package/dist/conflicts.js +807 -0
- package/dist/conflicts.js.map +1 -0
- package/dist/crdt/awareness.d.ts +128 -0
- package/dist/crdt/awareness.d.ts.map +1 -0
- package/dist/crdt/awareness.js +284 -0
- package/dist/crdt/awareness.js.map +1 -0
- package/dist/crdt/channel.d.ts +165 -0
- package/dist/crdt/channel.d.ts.map +1 -0
- package/dist/crdt/channel.js +522 -0
- package/dist/crdt/channel.js.map +1 -0
- package/dist/crdt/config.d.ts +58 -0
- package/dist/crdt/config.d.ts.map +1 -0
- package/dist/crdt/config.js +123 -0
- package/dist/crdt/config.js.map +1 -0
- package/dist/crdt/helpers.d.ts +104 -0
- package/dist/crdt/helpers.d.ts.map +1 -0
- package/dist/crdt/helpers.js +116 -0
- package/dist/crdt/helpers.js.map +1 -0
- package/dist/crdt/offline.d.ts +58 -0
- package/dist/crdt/offline.d.ts.map +1 -0
- package/dist/crdt/offline.js +130 -0
- package/dist/crdt/offline.js.map +1 -0
- package/dist/crdt/persistence.d.ts +65 -0
- package/dist/crdt/persistence.d.ts.map +1 -0
- package/dist/crdt/persistence.js +171 -0
- package/dist/crdt/persistence.js.map +1 -0
- package/dist/crdt/provider.d.ts +109 -0
- package/dist/crdt/provider.d.ts.map +1 -0
- package/dist/crdt/provider.js +543 -0
- package/dist/crdt/provider.js.map +1 -0
- package/dist/crdt/store.d.ts +111 -0
- package/dist/crdt/store.d.ts.map +1 -0
- package/dist/crdt/store.js +158 -0
- package/dist/crdt/store.js.map +1 -0
- package/dist/crdt/types.d.ts +281 -0
- package/dist/crdt/types.d.ts.map +1 -0
- package/dist/crdt/types.js +26 -0
- package/dist/crdt/types.js.map +1 -0
- package/dist/data.d.ts +502 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/data.js +862 -0
- package/dist/data.js.map +1 -0
- package/dist/database.d.ts +153 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +325 -0
- package/dist/database.js.map +1 -0
- package/dist/debug.d.ts +87 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +135 -0
- package/dist/debug.js.map +1 -0
- package/dist/demo.d.ts +131 -0
- package/dist/demo.d.ts.map +1 -0
- package/dist/demo.js +168 -0
- package/dist/demo.js.map +1 -0
- package/dist/deviceId.d.ts +47 -0
- package/dist/deviceId.d.ts.map +1 -0
- package/dist/deviceId.js +106 -0
- package/dist/deviceId.js.map +1 -0
- package/dist/diagnostics.d.ts +292 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +378 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/engine.d.ts +230 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +2636 -0
- package/dist/engine.js.map +1 -0
- package/dist/entries/actions.d.ts +16 -0
- package/dist/entries/actions.d.ts.map +1 -0
- package/dist/entries/actions.js +29 -0
- package/dist/entries/actions.js.map +1 -0
- package/dist/entries/auth.d.ts +19 -0
- package/dist/entries/auth.d.ts.map +1 -0
- package/dist/entries/auth.js +50 -0
- package/dist/entries/auth.js.map +1 -0
- package/dist/entries/config.d.ts +15 -0
- package/dist/entries/config.d.ts.map +1 -0
- package/dist/entries/config.js +20 -0
- package/dist/entries/config.js.map +1 -0
- package/dist/entries/crdt.d.ts +32 -0
- package/dist/entries/crdt.d.ts.map +1 -0
- package/dist/entries/crdt.js +52 -0
- package/dist/entries/crdt.js.map +1 -0
- package/dist/entries/kit.d.ts +22 -0
- package/dist/entries/kit.d.ts.map +1 -0
- package/dist/entries/kit.js +58 -0
- package/dist/entries/kit.js.map +1 -0
- package/dist/entries/stores.d.ts +22 -0
- package/dist/entries/stores.d.ts.map +1 -0
- package/dist/entries/stores.js +57 -0
- package/dist/entries/stores.js.map +1 -0
- package/dist/entries/types.d.ts +23 -0
- package/dist/entries/types.d.ts.map +1 -0
- package/dist/entries/types.js +12 -0
- package/dist/entries/types.js.map +1 -0
- package/dist/entries/utils.d.ts +12 -0
- package/dist/entries/utils.d.ts.map +1 -0
- package/dist/entries/utils.js +42 -0
- package/dist/entries/utils.js.map +1 -0
- package/dist/entries/vite.d.ts +20 -0
- package/dist/entries/vite.d.ts.map +1 -0
- package/dist/entries/vite.js +26 -0
- package/dist/entries/vite.js.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +234 -0
- package/dist/index.js.map +1 -0
- package/dist/kit/auth.d.ts +80 -0
- package/dist/kit/auth.d.ts.map +1 -0
- package/dist/kit/auth.js +75 -0
- package/dist/kit/auth.js.map +1 -0
- package/dist/kit/confirm.d.ts +111 -0
- package/dist/kit/confirm.d.ts.map +1 -0
- package/dist/kit/confirm.js +169 -0
- package/dist/kit/confirm.js.map +1 -0
- package/dist/kit/loads.d.ts +187 -0
- package/dist/kit/loads.d.ts.map +1 -0
- package/dist/kit/loads.js +208 -0
- package/dist/kit/loads.js.map +1 -0
- package/dist/kit/server.d.ts +175 -0
- package/dist/kit/server.d.ts.map +1 -0
- package/dist/kit/server.js +297 -0
- package/dist/kit/server.js.map +1 -0
- package/dist/kit/sw.d.ts +176 -0
- package/dist/kit/sw.d.ts.map +1 -0
- package/dist/kit/sw.js +320 -0
- package/dist/kit/sw.js.map +1 -0
- package/dist/queue.d.ts +306 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +925 -0
- package/dist/queue.js.map +1 -0
- package/dist/realtime.d.ts +280 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +1031 -0
- package/dist/realtime.js.map +1 -0
- package/dist/runtime/runtimeConfig.d.ts +110 -0
- package/dist/runtime/runtimeConfig.d.ts.map +1 -0
- package/dist/runtime/runtimeConfig.js +260 -0
- package/dist/runtime/runtimeConfig.js.map +1 -0
- 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/stores/authState.d.ts +204 -0
- package/dist/stores/authState.d.ts.map +1 -0
- package/dist/stores/authState.js +336 -0
- package/dist/stores/authState.js.map +1 -0
- package/dist/stores/factories.d.ts +140 -0
- package/dist/stores/factories.d.ts.map +1 -0
- package/dist/stores/factories.js +157 -0
- package/dist/stores/factories.js.map +1 -0
- package/dist/stores/network.d.ts +48 -0
- package/dist/stores/network.d.ts.map +1 -0
- package/dist/stores/network.js +261 -0
- package/dist/stores/network.js.map +1 -0
- package/dist/stores/remoteChanges.d.ts +417 -0
- package/dist/stores/remoteChanges.d.ts.map +1 -0
- package/dist/stores/remoteChanges.js +626 -0
- package/dist/stores/remoteChanges.js.map +1 -0
- package/dist/stores/sync.d.ts +165 -0
- package/dist/stores/sync.d.ts.map +1 -0
- package/dist/stores/sync.js +275 -0
- package/dist/stores/sync.js.map +1 -0
- package/dist/supabase/auth.d.ts +219 -0
- package/dist/supabase/auth.d.ts.map +1 -0
- package/dist/supabase/auth.js +459 -0
- package/dist/supabase/auth.js.map +1 -0
- package/dist/supabase/client.d.ts +88 -0
- package/dist/supabase/client.d.ts.map +1 -0
- package/dist/supabase/client.js +313 -0
- package/dist/supabase/client.js.map +1 -0
- package/dist/supabase/validate.d.ts +118 -0
- package/dist/supabase/validate.d.ts.map +1 -0
- package/dist/supabase/validate.js +208 -0
- package/dist/supabase/validate.js.map +1 -0
- package/dist/sw/build/vite-plugin.d.ts +149 -0
- package/dist/sw/build/vite-plugin.d.ts.map +1 -0
- package/dist/sw/build/vite-plugin.js +517 -0
- package/dist/sw/build/vite-plugin.js.map +1 -0
- package/dist/sw/sw.js +664 -0
- package/dist/types.d.ts +363 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +85 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +156 -0
- package/dist/utils.js.map +1 -0
- package/package.json +117 -0
- package/src/components/DeferredChangesBanner.svelte +477 -0
- package/src/components/DemoBanner.svelte +110 -0
- package/src/components/SyncStatus.svelte +1732 -0
package/README.md
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
# stellar-drive [](https://www.npmjs.com/package/stellar-drive) [](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.
|