@prabhask5/stellar-engine 1.1.6 → 1.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/README.md +68 -25
  2. package/dist/actions/remoteChange.d.ts +143 -18
  3. package/dist/actions/remoteChange.d.ts.map +1 -1
  4. package/dist/actions/remoteChange.js +182 -58
  5. package/dist/actions/remoteChange.js.map +1 -1
  6. package/dist/actions/truncateTooltip.d.ts +56 -0
  7. package/dist/actions/truncateTooltip.d.ts.map +1 -0
  8. package/dist/actions/truncateTooltip.js +312 -0
  9. package/dist/actions/truncateTooltip.js.map +1 -0
  10. package/dist/auth/admin.d.ts +40 -3
  11. package/dist/auth/admin.d.ts.map +1 -1
  12. package/dist/auth/admin.js +45 -5
  13. package/dist/auth/admin.js.map +1 -1
  14. package/dist/auth/crypto.d.ts +55 -5
  15. package/dist/auth/crypto.d.ts.map +1 -1
  16. package/dist/auth/crypto.js +58 -5
  17. package/dist/auth/crypto.js.map +1 -1
  18. package/dist/auth/deviceVerification.d.ts +236 -20
  19. package/dist/auth/deviceVerification.d.ts.map +1 -1
  20. package/dist/auth/deviceVerification.js +293 -40
  21. package/dist/auth/deviceVerification.js.map +1 -1
  22. package/dist/auth/displayUtils.d.ts +98 -0
  23. package/dist/auth/displayUtils.d.ts.map +1 -0
  24. package/dist/auth/displayUtils.js +133 -0
  25. package/dist/auth/displayUtils.js.map +1 -0
  26. package/dist/auth/loginGuard.d.ts +108 -14
  27. package/dist/auth/loginGuard.d.ts.map +1 -1
  28. package/dist/auth/loginGuard.js +153 -31
  29. package/dist/auth/loginGuard.js.map +1 -1
  30. package/dist/auth/offlineCredentials.d.ts +132 -15
  31. package/dist/auth/offlineCredentials.d.ts.map +1 -1
  32. package/dist/auth/offlineCredentials.js +167 -23
  33. package/dist/auth/offlineCredentials.js.map +1 -1
  34. package/dist/auth/offlineLogin.d.ts +96 -10
  35. package/dist/auth/offlineLogin.d.ts.map +1 -1
  36. package/dist/auth/offlineLogin.js +82 -15
  37. package/dist/auth/offlineLogin.js.map +1 -1
  38. package/dist/auth/offlineSession.d.ts +83 -9
  39. package/dist/auth/offlineSession.d.ts.map +1 -1
  40. package/dist/auth/offlineSession.js +104 -13
  41. package/dist/auth/offlineSession.js.map +1 -1
  42. package/dist/auth/resolveAuthState.d.ts +70 -8
  43. package/dist/auth/resolveAuthState.d.ts.map +1 -1
  44. package/dist/auth/resolveAuthState.js +142 -46
  45. package/dist/auth/resolveAuthState.js.map +1 -1
  46. package/dist/auth/singleUser.d.ts +390 -37
  47. package/dist/auth/singleUser.d.ts.map +1 -1
  48. package/dist/auth/singleUser.js +505 -133
  49. package/dist/auth/singleUser.js.map +1 -1
  50. package/dist/bin/install-pwa.d.ts +25 -0
  51. package/dist/bin/install-pwa.d.ts.map +1 -0
  52. package/dist/bin/install-pwa.js +2197 -0
  53. package/dist/bin/install-pwa.js.map +1 -0
  54. package/dist/config.d.ts +132 -12
  55. package/dist/config.d.ts.map +1 -1
  56. package/dist/config.js +87 -9
  57. package/dist/config.js.map +1 -1
  58. package/dist/conflicts.d.ts +246 -23
  59. package/dist/conflicts.d.ts.map +1 -1
  60. package/dist/conflicts.js +495 -46
  61. package/dist/conflicts.js.map +1 -1
  62. package/dist/data.d.ts +338 -18
  63. package/dist/data.d.ts.map +1 -1
  64. package/dist/data.js +385 -34
  65. package/dist/data.js.map +1 -1
  66. package/dist/database.d.ts +72 -14
  67. package/dist/database.d.ts.map +1 -1
  68. package/dist/database.js +120 -29
  69. package/dist/database.js.map +1 -1
  70. package/dist/debug.d.ts +77 -1
  71. package/dist/debug.d.ts.map +1 -1
  72. package/dist/debug.js +88 -1
  73. package/dist/debug.js.map +1 -1
  74. package/dist/deviceId.d.ts +38 -7
  75. package/dist/deviceId.d.ts.map +1 -1
  76. package/dist/deviceId.js +68 -10
  77. package/dist/deviceId.js.map +1 -1
  78. package/dist/engine.d.ts +175 -3
  79. package/dist/engine.d.ts.map +1 -1
  80. package/dist/engine.js +831 -110
  81. package/dist/engine.js.map +1 -1
  82. package/dist/entries/actions.d.ts +14 -0
  83. package/dist/entries/actions.d.ts.map +1 -1
  84. package/dist/entries/actions.js +27 -1
  85. package/dist/entries/actions.js.map +1 -1
  86. package/dist/entries/auth.d.ts +16 -0
  87. package/dist/entries/auth.d.ts.map +1 -1
  88. package/dist/entries/auth.js +73 -1
  89. package/dist/entries/auth.js.map +1 -1
  90. package/dist/entries/config.d.ts +12 -0
  91. package/dist/entries/config.d.ts.map +1 -1
  92. package/dist/entries/config.js +18 -1
  93. package/dist/entries/config.js.map +1 -1
  94. package/dist/entries/kit.d.ts +21 -9
  95. package/dist/entries/kit.d.ts.map +1 -1
  96. package/dist/entries/kit.js +57 -8
  97. package/dist/entries/kit.js.map +1 -1
  98. package/dist/entries/stores.d.ts +11 -0
  99. package/dist/entries/stores.d.ts.map +1 -1
  100. package/dist/entries/stores.js +43 -2
  101. package/dist/entries/stores.js.map +1 -1
  102. package/dist/entries/types.d.ts +11 -1
  103. package/dist/entries/types.d.ts.map +1 -1
  104. package/dist/entries/types.js +10 -0
  105. package/dist/entries/types.js.map +1 -1
  106. package/dist/entries/utils.d.ts +7 -1
  107. package/dist/entries/utils.d.ts.map +1 -1
  108. package/dist/entries/utils.js +23 -2
  109. package/dist/entries/utils.js.map +1 -1
  110. package/dist/entries/vite.d.ts +20 -0
  111. package/dist/entries/vite.d.ts.map +1 -0
  112. package/dist/entries/vite.js +26 -0
  113. package/dist/entries/vite.js.map +1 -0
  114. package/dist/index.d.ts +33 -2
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +176 -21
  117. package/dist/index.js.map +1 -1
  118. package/dist/kit/auth.d.ts +80 -0
  119. package/dist/kit/auth.d.ts.map +1 -0
  120. package/dist/kit/auth.js +72 -0
  121. package/dist/kit/auth.js.map +1 -0
  122. package/dist/kit/confirm.d.ts +111 -0
  123. package/dist/kit/confirm.d.ts.map +1 -0
  124. package/dist/kit/confirm.js +169 -0
  125. package/dist/kit/confirm.js.map +1 -0
  126. package/dist/kit/loads.d.ts +189 -0
  127. package/dist/kit/loads.d.ts.map +1 -0
  128. package/dist/kit/loads.js +205 -0
  129. package/dist/kit/loads.js.map +1 -0
  130. package/dist/kit/server.d.ts +175 -0
  131. package/dist/kit/server.d.ts.map +1 -0
  132. package/dist/kit/server.js +297 -0
  133. package/dist/kit/server.js.map +1 -0
  134. package/dist/kit/sw.d.ts +176 -0
  135. package/dist/kit/sw.d.ts.map +1 -0
  136. package/dist/kit/sw.js +320 -0
  137. package/dist/kit/sw.js.map +1 -0
  138. package/dist/queue.d.ts +274 -0
  139. package/dist/queue.d.ts.map +1 -1
  140. package/dist/queue.js +556 -38
  141. package/dist/queue.js.map +1 -1
  142. package/dist/realtime.d.ts +241 -27
  143. package/dist/realtime.d.ts.map +1 -1
  144. package/dist/realtime.js +633 -109
  145. package/dist/realtime.js.map +1 -1
  146. package/dist/runtime/runtimeConfig.d.ts +91 -16
  147. package/dist/runtime/runtimeConfig.d.ts.map +1 -1
  148. package/dist/runtime/runtimeConfig.js +146 -19
  149. package/dist/runtime/runtimeConfig.js.map +1 -1
  150. package/dist/stores/authState.d.ts +150 -11
  151. package/dist/stores/authState.d.ts.map +1 -1
  152. package/dist/stores/authState.js +169 -17
  153. package/dist/stores/authState.js.map +1 -1
  154. package/dist/stores/network.d.ts +39 -0
  155. package/dist/stores/network.d.ts.map +1 -1
  156. package/dist/stores/network.js +169 -16
  157. package/dist/stores/network.js.map +1 -1
  158. package/dist/stores/remoteChanges.d.ts +327 -52
  159. package/dist/stores/remoteChanges.d.ts.map +1 -1
  160. package/dist/stores/remoteChanges.js +337 -75
  161. package/dist/stores/remoteChanges.js.map +1 -1
  162. package/dist/stores/sync.d.ts +130 -0
  163. package/dist/stores/sync.d.ts.map +1 -1
  164. package/dist/stores/sync.js +167 -7
  165. package/dist/stores/sync.js.map +1 -1
  166. package/dist/supabase/auth.d.ts +326 -19
  167. package/dist/supabase/auth.d.ts.map +1 -1
  168. package/dist/supabase/auth.js +374 -26
  169. package/dist/supabase/auth.js.map +1 -1
  170. package/dist/supabase/client.d.ts +79 -6
  171. package/dist/supabase/client.d.ts.map +1 -1
  172. package/dist/supabase/client.js +158 -15
  173. package/dist/supabase/client.js.map +1 -1
  174. package/dist/supabase/validate.d.ts +101 -7
  175. package/dist/supabase/validate.d.ts.map +1 -1
  176. package/dist/supabase/validate.js +117 -8
  177. package/dist/supabase/validate.js.map +1 -1
  178. package/dist/sw/build/vite-plugin.d.ts +74 -0
  179. package/dist/sw/build/vite-plugin.d.ts.map +1 -0
  180. package/dist/sw/build/vite-plugin.js +183 -0
  181. package/dist/sw/build/vite-plugin.js.map +1 -0
  182. package/dist/sw/sw.js +669 -0
  183. package/dist/types.d.ts +150 -45
  184. package/dist/types.d.ts.map +1 -1
  185. package/dist/types.js +12 -10
  186. package/dist/types.js.map +1 -1
  187. package/dist/utils.d.ts +55 -13
  188. package/dist/utils.d.ts.map +1 -1
  189. package/dist/utils.js +83 -22
  190. package/dist/utils.js.map +1 -1
  191. package/package.json +20 -22
  192. package/src/components/DeferredChangesBanner.svelte +477 -0
  193. package/src/components/SyncStatus.svelte +1732 -0
  194. package/dist/crdt/awareness.d.ts +0 -54
  195. package/dist/crdt/awareness.d.ts.map +0 -1
  196. package/dist/crdt/awareness.js +0 -219
  197. package/dist/crdt/awareness.js.map +0 -1
  198. package/dist/crdt/doc.d.ts +0 -56
  199. package/dist/crdt/doc.d.ts.map +0 -1
  200. package/dist/crdt/doc.js +0 -130
  201. package/dist/crdt/doc.js.map +0 -1
  202. package/dist/crdt/index.d.ts +0 -15
  203. package/dist/crdt/index.d.ts.map +0 -1
  204. package/dist/crdt/index.js +0 -20
  205. package/dist/crdt/index.js.map +0 -1
  206. package/dist/crdt/offline.d.ts +0 -91
  207. package/dist/crdt/offline.d.ts.map +0 -1
  208. package/dist/crdt/offline.js +0 -353
  209. package/dist/crdt/offline.js.map +0 -1
  210. package/dist/crdt/sync.d.ts +0 -58
  211. package/dist/crdt/sync.d.ts.map +0 -1
  212. package/dist/crdt/sync.js +0 -399
  213. package/dist/crdt/sync.js.map +0 -1
  214. package/dist/crdt/types.d.ts +0 -62
  215. package/dist/crdt/types.d.ts.map +0 -1
  216. package/dist/crdt/types.js +0 -7
  217. package/dist/crdt/types.js.map +0 -1
  218. package/dist/email/sendEmail.d.ts +0 -31
  219. package/dist/email/sendEmail.d.ts.map +0 -1
  220. package/dist/email/sendEmail.js +0 -39
  221. package/dist/email/sendEmail.js.map +0 -1
  222. package/dist/email/validateSmtp.d.ts +0 -18
  223. package/dist/email/validateSmtp.d.ts.map +0 -1
  224. package/dist/email/validateSmtp.js +0 -33
  225. package/dist/email/validateSmtp.js.map +0 -1
  226. package/dist/entries/crdt.d.ts +0 -3
  227. package/dist/entries/crdt.d.ts.map +0 -1
  228. package/dist/entries/crdt.js +0 -13
  229. package/dist/entries/crdt.js.map +0 -1
  230. package/dist/entries/email.d.ts +0 -4
  231. package/dist/entries/email.d.ts.map +0 -1
  232. package/dist/entries/email.js +0 -4
  233. package/dist/entries/email.js.map +0 -1
  234. package/dist/kit/authPresets.d.ts +0 -28
  235. package/dist/kit/authPresets.d.ts.map +0 -1
  236. package/dist/kit/authPresets.js +0 -23
  237. package/dist/kit/authPresets.js.map +0 -1
  238. package/dist/kit/configEndpoint.d.ts +0 -18
  239. package/dist/kit/configEndpoint.d.ts.map +0 -1
  240. package/dist/kit/configEndpoint.js +0 -27
  241. package/dist/kit/configEndpoint.js.map +0 -1
  242. package/dist/kit/deployEndpoint.d.ts +0 -22
  243. package/dist/kit/deployEndpoint.d.ts.map +0 -1
  244. package/dist/kit/deployEndpoint.js +0 -79
  245. package/dist/kit/deployEndpoint.js.map +0 -1
  246. package/dist/kit/layoutLoad.d.ts +0 -23
  247. package/dist/kit/layoutLoad.d.ts.map +0 -1
  248. package/dist/kit/layoutLoad.js +0 -41
  249. package/dist/kit/layoutLoad.js.map +0 -1
  250. package/dist/kit/protectedLoad.d.ts +0 -16
  251. package/dist/kit/protectedLoad.d.ts.map +0 -1
  252. package/dist/kit/protectedLoad.js +0 -28
  253. package/dist/kit/protectedLoad.js.map +0 -1
  254. package/dist/kit/setupLoad.d.ts +0 -11
  255. package/dist/kit/setupLoad.d.ts.map +0 -1
  256. package/dist/kit/setupLoad.js +0 -28
  257. package/dist/kit/setupLoad.js.map +0 -1
  258. package/dist/kit/validateEndpoint.d.ts +0 -9
  259. package/dist/kit/validateEndpoint.d.ts.map +0 -1
  260. package/dist/kit/validateEndpoint.js +0 -25
  261. package/dist/kit/validateEndpoint.js.map +0 -1
  262. package/dist/kit/vercelApi.d.ts +0 -6
  263. package/dist/kit/vercelApi.d.ts.map +0 -1
  264. package/dist/kit/vercelApi.js +0 -48
  265. package/dist/kit/vercelApi.js.map +0 -1
package/dist/types.d.ts CHANGED
@@ -1,118 +1,223 @@
1
1
  /**
2
- * Intent-Based Sync Operation Types
2
+ * @fileoverview Core Type Definitions for the Stellar Sync Engine
3
3
  *
4
- * These types enable preserving operation intent (e.g., "increment by 1")
5
- * rather than just final state (e.g., "current_value: 50").
4
+ * Defines the foundational TypeScript types used throughout the engine:
5
+ * - Intent-based sync operation types (preserving operation semantics)
6
+ * - Offline authentication types (credential caching, session tokens)
7
+ * - Conflict resolution types (field-level history tracking)
8
+ * - Single-user authentication types (PIN/password gate)
9
+ * - Device trust types (multi-device verification)
6
10
  *
7
- * Benefits:
8
- * - Rapid increments are coalesced locally (50 +1s -> single +50) reducing sync traffic
9
- * - Pending operations are protected during conflict resolution
10
- *
11
- * Note: True numeric merge across devices (e.g., +50 + +30 = +80) is not implemented.
12
- * Operations are converted to final values before pushing to Supabase, so conflicts
13
- * use last-write-wins. Full numeric merge would require an operation inbox system.
11
+ * Architecture note:
12
+ * Operations use an intent-based model (e.g., "increment by 1") rather than
13
+ * final-state snapshots (e.g., "current_value: 50"). This enables local
14
+ * coalescing (50 rapid +1s become a single +50) and smarter conflict
15
+ * resolution. See {@link queue.ts} for the coalescing implementation.
14
16
  */
15
17
  /**
16
- * Operation types that preserve intent:
17
- * - 'increment': Add delta to numeric field (e.g., current_value += 1)
18
- * - 'set': Set field to value (works for any type)
19
- * - 'create': Create new entity
20
- * - 'delete': Soft delete entity
18
+ * The four supported operation intents for the sync queue.
19
+ *
20
+ * Each intent carries different semantics during coalescing and push:
21
+ * - `'increment'` — Add a numeric delta to a field (coalesceable: multiple deltas sum)
22
+ * - `'set'` — Overwrite field(s) with new value(s) (coalesceable: later sets win)
23
+ * - `'create'` — Insert a new entity (coalesceable: subsequent sets merge into the create payload)
24
+ * - `'delete'` — Soft-delete an entity (a create + delete pair cancels both out entirely)
21
25
  */
22
26
  export type OperationType = 'increment' | 'set' | 'create' | 'delete';
23
27
  /**
24
- * Intent-based sync operation item.
28
+ * A single intent-based sync operation stored in the IndexedDB `syncQueue` table.
29
+ *
30
+ * Design decisions:
31
+ * - `operationType` preserves the *intent* so the coalescer can intelligently merge
32
+ * (e.g., 50 increment ops → one +50 instead of 50 separate server requests).
33
+ * - `field` is optional: increment/single-field set use it; create and multi-field
34
+ * set store data in `value` instead.
35
+ * - `retries` and `lastRetryAt` power exponential backoff for failed pushes.
25
36
  *
26
- * Key design:
27
- * - Uses `operationType` to specify the operation intent
28
- * - Has optional `field` for field-level operations
29
- * - `value` is the delta (for increment) or new value (for set/create)
37
+ * @example
38
+ * // Increment operation: add 1 to `current_value`
39
+ * { table: "goals", entityId: "abc", operationType: "increment", field: "current_value", value: 1 }
30
40
  *
31
- * For create operations, value contains the full entity payload.
32
- * For increment operations, value contains the delta to add.
33
- * For set operations, value contains the new field value(s).
34
- * For delete operations, value is not used.
41
+ * // Multi-field set operation: update title and description
42
+ * { table: "goals", entityId: "abc", operationType: "set", value: { title: "New", description: "..." } }
43
+ *
44
+ * // Create operation: full entity payload in `value`
45
+ * { table: "goals", entityId: "abc", operationType: "create", value: { title: "Goal", target: 10 } }
35
46
  */
36
47
  export interface SyncOperationItem {
48
+ /** Auto-increment primary key (assigned by IndexedDB). */
37
49
  id?: number;
50
+ /** Supabase table name (e.g., `"goals"`, `"goal_lists"`). */
38
51
  table: string;
52
+ /** UUID of the entity being operated on. */
39
53
  entityId: string;
54
+ /** The operation intent: `'increment'`, `'set'`, `'create'`, or `'delete'`. */
40
55
  operationType: OperationType;
56
+ /** Target field name — used by increment and single-field set operations. */
41
57
  field?: string;
58
+ /** Payload — delta (increment), new value (set), full entity (create), or unused (delete). */
42
59
  value?: unknown;
60
+ /** ISO 8601 timestamp of when the operation was enqueued locally. */
43
61
  timestamp: string;
62
+ /** Number of failed push attempts (drives exponential backoff). */
44
63
  retries: number;
64
+ /** ISO 8601 timestamp of the last retry attempt (used for backoff calculation). */
45
65
  lastRetryAt?: string;
46
66
  }
67
+ /**
68
+ * Cached credentials stored in IndexedDB for offline sign-in.
69
+ *
70
+ * Uses a singleton pattern (`id: 'current_user'`) so only one set of
71
+ * credentials is cached at a time. The password is stored as a SHA-256
72
+ * hash — legacy records may contain plaintext from before hashing was added.
73
+ *
74
+ * @see {@link auth/offlineCredentials.ts} for caching/verification logic
75
+ */
47
76
  export interface OfflineCredentials {
77
+ /** Singleton key — always `'current_user'`. */
48
78
  id: string;
79
+ /** Supabase user UUID. */
49
80
  userId: string;
81
+ /** User's email address. */
50
82
  email: string;
83
+ /** SHA-256 hash of the user's password (legacy records may be plaintext). */
51
84
  password: string;
85
+ /** App-specific profile data (e.g., `{ firstName, lastName }`). */
52
86
  profile: Record<string, unknown>;
87
+ /** ISO 8601 timestamp of when credentials were cached. */
53
88
  cachedAt: string;
54
89
  }
90
+ /**
91
+ * Offline session token stored in IndexedDB.
92
+ *
93
+ * Created when the device goes offline (if credentials are cached) and
94
+ * consumed during offline sign-in to verify the user's identity without
95
+ * a network call.
96
+ *
97
+ * Sessions have no expiry — they are revoked only on:
98
+ * 1. Successful online re-authentication
99
+ * 2. Explicit logout
100
+ *
101
+ * @see {@link auth/offlineSession.ts} for session management
102
+ */
55
103
  export interface OfflineSession {
104
+ /** Singleton key — always `'current_session'`. */
56
105
  id: string;
106
+ /** Supabase user UUID. */
57
107
  userId: string;
108
+ /** Random UUID used as the offline session token. */
58
109
  offlineToken: string;
110
+ /** ISO 8601 timestamp of session creation. */
59
111
  createdAt: string;
60
112
  }
61
113
  /**
62
- * Conflict history entry (stored in IndexedDB)
63
- * Records field-level conflict resolutions for review and potential undo
114
+ * A single field-level conflict resolution record stored in IndexedDB.
115
+ *
116
+ * Recorded whenever the conflict resolution engine detects divergent values
117
+ * for the same field across devices. Entries are retained for 30 days to
118
+ * allow review and potential manual override.
119
+ *
120
+ * @see {@link conflicts.ts} for the three-tier resolution algorithm
64
121
  */
65
122
  export interface ConflictHistoryEntry {
123
+ /** Auto-increment primary key (assigned by IndexedDB). */
66
124
  id?: number;
125
+ /** UUID of the conflicting entity. */
67
126
  entityId: string;
127
+ /** Supabase table name (e.g., `"goals"`). */
68
128
  entityType: string;
129
+ /** Field that had conflicting values. */
69
130
  field: string;
131
+ /** Value from the local device. */
70
132
  localValue: unknown;
133
+ /** Value from the remote server. */
71
134
  remoteValue: unknown;
135
+ /** Final merged value written to IndexedDB. */
72
136
  resolvedValue: unknown;
137
+ /** Which side's value was chosen (or `'merged'` for numeric merges). */
73
138
  winner: 'local' | 'remote' | 'merged';
139
+ /** The strategy that resolved this conflict (e.g., `'last_write'`, `'delete_wins'`). */
74
140
  strategy: string;
141
+ /** ISO 8601 timestamp of when the conflict was resolved. */
75
142
  timestamp: string;
76
143
  }
144
+ /**
145
+ * Current state of the sync engine's background loop.
146
+ *
147
+ * - `'idle'` — No active sync; everything is up to date
148
+ * - `'syncing'` — Push or pull currently in progress
149
+ * - `'error'` — Last sync attempt failed (will retry)
150
+ * - `'offline'` — Device has no network connectivity
151
+ */
77
152
  export type SyncStatus = 'idle' | 'syncing' | 'error' | 'offline';
153
+ /**
154
+ * Authentication mode for the engine.
155
+ *
156
+ * - `'supabase'` — Standard Supabase email/password or OAuth auth
157
+ * - `'offline'` — Using cached credentials (device is offline)
158
+ * - `'none'` — No active authentication
159
+ */
78
160
  export type AuthMode = 'supabase' | 'offline' | 'none';
161
+ /**
162
+ * The type of gate protecting single-user mode.
163
+ *
164
+ * - `'code'` — Numeric PIN (4 or 6 digits)
165
+ * - `'password'` — Freeform password string
166
+ */
79
167
  export type SingleUserGateType = 'code' | 'password';
168
+ /**
169
+ * Persistent configuration for single-user mode, stored in IndexedDB.
170
+ *
171
+ * Single-user mode replaces traditional email/password sign-in with a
172
+ * simplified local gate (PIN or password). Under the hood it still uses
173
+ * a real Supabase account — the PIN is padded to meet Supabase's minimum
174
+ * password length and used as the account password.
175
+ *
176
+ * Uses a singleton pattern (`id: 'config'`).
177
+ *
178
+ * @see {@link auth/singleUser.ts} for setup, unlock, and change flows
179
+ */
80
180
  export interface SingleUserConfig {
181
+ /** Singleton key — always `'config'`. */
81
182
  id: string;
183
+ /** Whether the gate is a numeric code or a freeform password. */
82
184
  gateType: SingleUserGateType;
185
+ /** Digit count for code gates (4 or 6). Only set when `gateType === 'code'`. */
83
186
  codeLength?: 4 | 6;
187
+ /** SHA-256 hash of the code/password (deprecated — kept for offline fallback verification). */
84
188
  gateHash?: string;
189
+ /** Email address used for the underlying Supabase account. */
85
190
  email?: string;
191
+ /** App-specific profile data (e.g., `{ firstName, lastName }`). */
86
192
  profile: Record<string, unknown>;
193
+ /** Supabase user UUID (set after first successful online setup). */
87
194
  supabaseUserId?: string;
195
+ /** ISO 8601 timestamp of initial setup. */
88
196
  setupAt: string;
197
+ /** ISO 8601 timestamp of last configuration change. */
89
198
  updatedAt: string;
90
199
  }
200
+ /**
201
+ * A trusted device record stored in the Supabase `trusted_devices` table.
202
+ *
203
+ * When device verification is enabled, untrusted devices must complete an
204
+ * email OTP challenge before they can access data. Once verified, the device
205
+ * is trusted for a configurable duration (default: 90 days).
206
+ *
207
+ * @see {@link auth/deviceVerification.ts} for the trust/verify flow
208
+ */
91
209
  export interface TrustedDevice {
210
+ /** Row UUID (primary key in Supabase). */
92
211
  id: string;
212
+ /** Supabase user UUID who owns this device. */
93
213
  userId: string;
214
+ /** Stable device identifier from localStorage. */
94
215
  deviceId: string;
216
+ /** Human-readable device label (e.g., browser + OS). */
95
217
  deviceLabel?: string;
218
+ /** ISO 8601 timestamp of when the device was first trusted. */
96
219
  trustedAt: string;
220
+ /** ISO 8601 timestamp of the device's most recent use. */
97
221
  lastUsedAt: string;
98
222
  }
99
- /**
100
- * Base interface for all syncable entities.
101
- * Every entity managed by the sync engine has these fields.
102
- */
103
- export interface SyncableEntity {
104
- id: string;
105
- created_at: string;
106
- updated_at: string;
107
- deleted?: boolean;
108
- _version?: number;
109
- device_id?: string | null;
110
- }
111
- /**
112
- * Base interface for entities owned by a user.
113
- * Extends SyncableEntity with a user_id field.
114
- */
115
- export interface UserOwnedEntity extends SyncableEntity {
116
- user_id: string;
117
- }
118
223
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js CHANGED
@@ -1,16 +1,18 @@
1
1
  /**
2
- * Intent-Based Sync Operation Types
2
+ * @fileoverview Core Type Definitions for the Stellar Sync Engine
3
3
  *
4
- * These types enable preserving operation intent (e.g., "increment by 1")
5
- * rather than just final state (e.g., "current_value: 50").
4
+ * Defines the foundational TypeScript types used throughout the engine:
5
+ * - Intent-based sync operation types (preserving operation semantics)
6
+ * - Offline authentication types (credential caching, session tokens)
7
+ * - Conflict resolution types (field-level history tracking)
8
+ * - Single-user authentication types (PIN/password gate)
9
+ * - Device trust types (multi-device verification)
6
10
  *
7
- * Benefits:
8
- * - Rapid increments are coalesced locally (50 +1s -> single +50) reducing sync traffic
9
- * - Pending operations are protected during conflict resolution
10
- *
11
- * Note: True numeric merge across devices (e.g., +50 + +30 = +80) is not implemented.
12
- * Operations are converted to final values before pushing to Supabase, so conflicts
13
- * use last-write-wins. Full numeric merge would require an operation inbox system.
11
+ * Architecture note:
12
+ * Operations use an intent-based model (e.g., "increment by 1") rather than
13
+ * final-state snapshots (e.g., "current_value: 50"). This enables local
14
+ * coalescing (50 rapid +1s become a single +50) and smarter conflict
15
+ * resolution. See {@link queue.ts} for the coalescing implementation.
14
16
  */
15
17
  export {};
16
18
  //# sourceMappingURL=types.js.map
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}
package/dist/utils.d.ts CHANGED
@@ -1,29 +1,71 @@
1
1
  /**
2
- * Common utility functions for sync engine consumers.
2
+ * @fileoverview Common Utility Functions
3
+ *
4
+ * Small, pure helper functions used across the sync engine and exposed
5
+ * to consumers via the `@prabhask5/stellar-engine/utils` subpath export.
6
+ *
7
+ * All functions in this module are side-effect-free and safe to call in
8
+ * both browser and SSR contexts.
3
9
  */
4
10
  /**
5
- * Convert a snake_case string to a safe camelCase identifier.
6
- * Strips invalid characters (keeps only alphanumeric and underscores),
7
- * then converts snake_case to camelCase.
8
- * e.g. 'goal_lists' 'goalLists', 'goals' → 'goals', 'my-table!' → 'mytable'
11
+ * Convert a `snake_case` string to a safe `camelCase` identifier.
12
+ *
13
+ * First strips any characters that are not alphanumeric or underscores,
14
+ * then converts underscore-separated words to camelCase.
15
+ *
16
+ * Used internally to derive Dexie (IndexedDB) table names from Supabase
17
+ * snake_case table names.
18
+ *
19
+ * @param s - The snake_case string to convert.
20
+ * @returns The camelCase equivalent.
21
+ *
22
+ * @example
23
+ * snakeToCamel('goal_lists'); // → 'goalLists'
24
+ * snakeToCamel('goals'); // → 'goals'
25
+ * snakeToCamel('my-table!'); // → 'mytable' (invalid chars stripped first)
9
26
  */
10
27
  export declare function snakeToCamel(s: string): string;
11
28
  /**
12
- * Generate a UUID v4 (random UUID).
29
+ * Generate a UUID v4 (random UUID) using the native Web Crypto API.
30
+ *
31
+ * Suitable for entity primary keys, device IDs, and any context where
32
+ * a universally unique identifier is needed.
33
+ *
34
+ * @returns A lowercase UUID v4 string (e.g., `"550e8400-e29b-41d4-a716-446655440000"`).
13
35
  */
14
36
  export declare function generateId(): string;
15
37
  /**
16
- * Get the current timestamp as an ISO string.
38
+ * Get the current timestamp as an ISO 8601 string.
39
+ *
40
+ * Convenience wrapper around `new Date().toISOString()` used as the
41
+ * default value for `created_at`, `updated_at`, and sync operation
42
+ * timestamps throughout the engine.
43
+ *
44
+ * @returns An ISO 8601 timestamp string (e.g., `"2025-01-15T12:30:00.000Z"`).
17
45
  */
18
46
  export declare function now(): string;
19
47
  /**
20
- * Calculate new order value when moving an item to a new position.
21
- * Uses fractional ordering to minimize updates.
48
+ * Calculate a new `order` value when moving an item to a different position.
49
+ *
50
+ * Uses **fractional ordering** so that only the moved item's `order` value
51
+ * changes — no need to re-index the entire list. The new value is placed
52
+ * at the midpoint between its new neighbours.
53
+ *
54
+ * Includes a guard against floating-point precision exhaustion: if the
55
+ * midpoint collapses to either bound, a small epsilon nudge is applied
56
+ * instead. In practice, precision issues only arise after ~50 consecutive
57
+ * moves between the same two items.
58
+ *
59
+ * @typeParam T - Any object with a numeric `order` property.
60
+ * @param items - The sorted array of items (by `order`, ascending).
61
+ * @param fromIndex - Current index of the item being moved.
62
+ * @param toIndex - Target index where the item should be placed.
63
+ * @returns The new `order` value for the moved item.
22
64
  *
23
- * @param items - The sorted array of items with order property
24
- * @param fromIndex - Current index of the item being moved
25
- * @param toIndex - Target index where the item should be placed
26
- * @returns The new order value for the moved item
65
+ * @example
66
+ * const items = [{ order: 1 }, { order: 2 }, { order: 3 }];
67
+ * calculateNewOrder(items, 2, 0); // 0 (before the first item)
68
+ * calculateNewOrder(items, 0, 1); // 2.5 (between items[1] and items[2])
27
69
  */
28
70
  export declare function calculateNewOrder<T extends {
29
71
  order: number;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED;;GAEG;AACH,wBAAgB,GAAG,IAAI,MAAM,CAE5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAC3D,KAAK,EAAE,CAAC,EAAE,EACV,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,MAAM,CA2CR"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAMD;;;;;;;GAOG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAMD;;;;;;;;GAQG;AACH,wBAAgB,GAAG,IAAI,MAAM,CAE5B;AAMD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAC3D,KAAK,EAAE,CAAC,EAAE,EACV,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,MAAM,CAkDR"}
package/dist/utils.js CHANGED
@@ -1,68 +1,129 @@
1
1
  /**
2
- * Common utility functions for sync engine consumers.
2
+ * @fileoverview Common Utility Functions
3
+ *
4
+ * Small, pure helper functions used across the sync engine and exposed
5
+ * to consumers via the `@prabhask5/stellar-engine/utils` subpath export.
6
+ *
7
+ * All functions in this module are side-effect-free and safe to call in
8
+ * both browser and SSR contexts.
3
9
  */
10
+ // =============================================================================
11
+ // String Utilities
12
+ // =============================================================================
4
13
  /**
5
- * Convert a snake_case string to a safe camelCase identifier.
6
- * Strips invalid characters (keeps only alphanumeric and underscores),
7
- * then converts snake_case to camelCase.
8
- * e.g. 'goal_lists' 'goalLists', 'goals' → 'goals', 'my-table!' → 'mytable'
14
+ * Convert a `snake_case` string to a safe `camelCase` identifier.
15
+ *
16
+ * First strips any characters that are not alphanumeric or underscores,
17
+ * then converts underscore-separated words to camelCase.
18
+ *
19
+ * Used internally to derive Dexie (IndexedDB) table names from Supabase
20
+ * snake_case table names.
21
+ *
22
+ * @param s - The snake_case string to convert.
23
+ * @returns The camelCase equivalent.
24
+ *
25
+ * @example
26
+ * snakeToCamel('goal_lists'); // → 'goalLists'
27
+ * snakeToCamel('goals'); // → 'goals'
28
+ * snakeToCamel('my-table!'); // → 'mytable' (invalid chars stripped first)
9
29
  */
10
30
  export function snakeToCamel(s) {
11
31
  return s.replace(/[^a-zA-Z0-9_]/g, '').replace(/_([a-z])/g, (_, c) => c.toUpperCase());
12
32
  }
33
+ // =============================================================================
34
+ // ID Generation
35
+ // =============================================================================
13
36
  /**
14
- * Generate a UUID v4 (random UUID).
37
+ * Generate a UUID v4 (random UUID) using the native Web Crypto API.
38
+ *
39
+ * Suitable for entity primary keys, device IDs, and any context where
40
+ * a universally unique identifier is needed.
41
+ *
42
+ * @returns A lowercase UUID v4 string (e.g., `"550e8400-e29b-41d4-a716-446655440000"`).
15
43
  */
16
44
  export function generateId() {
17
45
  return crypto.randomUUID();
18
46
  }
47
+ // =============================================================================
48
+ // Timestamp Utilities
49
+ // =============================================================================
19
50
  /**
20
- * Get the current timestamp as an ISO string.
51
+ * Get the current timestamp as an ISO 8601 string.
52
+ *
53
+ * Convenience wrapper around `new Date().toISOString()` used as the
54
+ * default value for `created_at`, `updated_at`, and sync operation
55
+ * timestamps throughout the engine.
56
+ *
57
+ * @returns An ISO 8601 timestamp string (e.g., `"2025-01-15T12:30:00.000Z"`).
21
58
  */
22
59
  export function now() {
23
60
  return new Date().toISOString();
24
61
  }
62
+ // =============================================================================
63
+ // Ordering Utilities
64
+ // =============================================================================
25
65
  /**
26
- * Calculate new order value when moving an item to a new position.
27
- * Uses fractional ordering to minimize updates.
66
+ * Calculate a new `order` value when moving an item to a different position.
67
+ *
68
+ * Uses **fractional ordering** so that only the moved item's `order` value
69
+ * changes — no need to re-index the entire list. The new value is placed
70
+ * at the midpoint between its new neighbours.
71
+ *
72
+ * Includes a guard against floating-point precision exhaustion: if the
73
+ * midpoint collapses to either bound, a small epsilon nudge is applied
74
+ * instead. In practice, precision issues only arise after ~50 consecutive
75
+ * moves between the same two items.
76
+ *
77
+ * @typeParam T - Any object with a numeric `order` property.
78
+ * @param items - The sorted array of items (by `order`, ascending).
79
+ * @param fromIndex - Current index of the item being moved.
80
+ * @param toIndex - Target index where the item should be placed.
81
+ * @returns The new `order` value for the moved item.
28
82
  *
29
- * @param items - The sorted array of items with order property
30
- * @param fromIndex - Current index of the item being moved
31
- * @param toIndex - Target index where the item should be placed
32
- * @returns The new order value for the moved item
83
+ * @example
84
+ * const items = [{ order: 1 }, { order: 2 }, { order: 3 }];
85
+ * calculateNewOrder(items, 2, 0); // 0 (before the first item)
86
+ * calculateNewOrder(items, 0, 1); // 2.5 (between items[1] and items[2])
33
87
  */
34
88
  export function calculateNewOrder(items, fromIndex, toIndex) {
35
- // No movement
89
+ /* No movement — return the item's existing order. */
36
90
  if (fromIndex === toIndex) {
37
91
  return items[fromIndex].order;
38
92
  }
39
- // Moving to the beginning
93
+ /* Moving to the beginning — place 1 unit before the current first item. */
40
94
  if (toIndex === 0) {
41
95
  return items[0].order - 1;
42
96
  }
43
- // Moving to the end
97
+ /* Moving to the end — place 1 unit after the current last item. */
44
98
  if (toIndex === items.length - 1) {
45
99
  return items[items.length - 1].order + 1;
46
100
  }
47
- // Moving between two items
48
- // Account for the shift that happens when removing the item from its original position
101
+ /*
102
+ * Moving between two existing items.
103
+ * Account for the index shift that occurs when the item is removed
104
+ * from its original position before being re-inserted.
105
+ */
49
106
  let prevIndex;
50
107
  let nextIndex;
51
108
  if (fromIndex < toIndex) {
52
- // Moving down: the item will be placed between toIndex and toIndex + 1
109
+ /* Moving down: the item lands between toIndex and toIndex + 1. */
53
110
  prevIndex = toIndex;
54
111
  nextIndex = toIndex + 1;
55
112
  }
56
113
  else {
57
- // Moving up: the item will be placed between toIndex - 1 and toIndex
114
+ /* Moving up: the item lands between toIndex - 1 and toIndex. */
58
115
  prevIndex = toIndex - 1;
59
116
  nextIndex = toIndex;
60
117
  }
61
118
  const prevOrder = items[prevIndex].order;
62
119
  const nextOrder = items[nextIndex].order;
63
120
  const midpoint = (prevOrder + nextOrder) / 2;
64
- // Guard against floating-point precision exhaustion:
65
- // if the midpoint collapses to either bound, nudge by a small epsilon
121
+ /*
122
+ * Guard against floating-point precision exhaustion:
123
+ * If the midpoint collapses to either bound (i.e., they're so close
124
+ * that IEEE 754 can't represent a value between them), nudge by a
125
+ * small epsilon instead.
126
+ */
66
127
  if (midpoint === prevOrder || midpoint === nextOrder) {
67
128
  return prevOrder + Number.EPSILON * 100;
68
129
  }
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,OAAO,CAAC,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AACzF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;AAC7B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,GAAG;IACjB,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAU,EACV,SAAiB,EACjB,OAAe;IAEf,cAAc;IACd,IAAI,SAAS,KAAK,OAAO,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC;IAChC,CAAC;IAED,0BAA0B;IAC1B,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;QAClB,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED,oBAAoB;IACpB,IAAI,OAAO,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;IAC3C,CAAC;IAED,2BAA2B;IAC3B,uFAAuF;IACvF,IAAI,SAAiB,CAAC;IACtB,IAAI,SAAiB,CAAC;IAEtB,IAAI,SAAS,GAAG,OAAO,EAAE,CAAC;QACxB,uEAAuE;QACvE,SAAS,GAAG,OAAO,CAAC;QACpB,SAAS,GAAG,OAAO,GAAG,CAAC,CAAC;IAC1B,CAAC;SAAM,CAAC;QACN,qEAAqE;QACrE,SAAS,GAAG,OAAO,GAAG,CAAC,CAAC;QACxB,SAAS,GAAG,OAAO,CAAC;IACtB,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC;IACzC,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC;IAEzC,MAAM,QAAQ,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAE7C,qDAAqD;IACrD,sEAAsE;IACtE,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QACrD,OAAO,SAAS,GAAG,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC;IAC1C,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,gFAAgF;AAChF,mBAAmB;AACnB,gFAAgF;AAEhF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,OAAO,CAAC,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AACzF,CAAC;AAED,gFAAgF;AAChF,gBAAgB;AAChB,gFAAgF;AAEhF;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;AAC7B,CAAC;AAED,gFAAgF;AAChF,sBAAsB;AACtB,gFAAgF;AAEhF;;;;;;;;GAQG;AACH,MAAM,UAAU,GAAG;IACjB,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED,gFAAgF;AAChF,qBAAqB;AACrB,gFAAgF;AAEhF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAU,EACV,SAAiB,EACjB,OAAe;IAEf,qDAAqD;IACrD,IAAI,SAAS,KAAK,OAAO,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC;IAChC,CAAC;IAED,2EAA2E;IAC3E,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;QAClB,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED,mEAAmE;IACnE,IAAI,OAAO,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;IAC3C,CAAC;IAED;;;;OAIG;IACH,IAAI,SAAiB,CAAC;IACtB,IAAI,SAAiB,CAAC;IAEtB,IAAI,SAAS,GAAG,OAAO,EAAE,CAAC;QACxB,kEAAkE;QAClE,SAAS,GAAG,OAAO,CAAC;QACpB,SAAS,GAAG,OAAO,GAAG,CAAC,CAAC;IAC1B,CAAC;SAAM,CAAC;QACN,gEAAgE;QAChE,SAAS,GAAG,OAAO,GAAG,CAAC,CAAC;QACxB,SAAS,GAAG,OAAO,CAAC;IACtB,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC;IACzC,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC;IAEzC,MAAM,QAAQ,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAE7C;;;;;OAKG;IACH,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QACrD,OAAO,SAAS,GAAG,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC;IAC1C,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}