@livestore/common 0.0.0-snapshot-909cdd1ac2fd591945c2be2b0f53e14d87f3c9d4

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 (220) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/__tests__/fixture.d.ts +72 -0
  3. package/dist/__tests__/fixture.d.ts.map +1 -0
  4. package/dist/__tests__/fixture.js +16 -0
  5. package/dist/__tests__/fixture.js.map +1 -0
  6. package/dist/adapter-types.d.ts +202 -0
  7. package/dist/adapter-types.d.ts.map +1 -0
  8. package/dist/adapter-types.js +49 -0
  9. package/dist/adapter-types.js.map +1 -0
  10. package/dist/bounded-collections.d.ts +36 -0
  11. package/dist/bounded-collections.d.ts.map +1 -0
  12. package/dist/bounded-collections.js +98 -0
  13. package/dist/bounded-collections.js.map +1 -0
  14. package/dist/debug-info.d.ts +122 -0
  15. package/dist/debug-info.d.ts.map +1 -0
  16. package/dist/debug-info.js +47 -0
  17. package/dist/debug-info.js.map +1 -0
  18. package/dist/derived-mutations.d.ts +109 -0
  19. package/dist/derived-mutations.d.ts.map +1 -0
  20. package/dist/derived-mutations.js +54 -0
  21. package/dist/derived-mutations.js.map +1 -0
  22. package/dist/derived-mutations.test.d.ts +2 -0
  23. package/dist/derived-mutations.test.d.ts.map +1 -0
  24. package/dist/derived-mutations.test.js +93 -0
  25. package/dist/derived-mutations.test.js.map +1 -0
  26. package/dist/devtools/devtools-bridge.d.ts +12 -0
  27. package/dist/devtools/devtools-bridge.d.ts.map +1 -0
  28. package/dist/devtools/devtools-bridge.js +2 -0
  29. package/dist/devtools/devtools-bridge.js.map +1 -0
  30. package/dist/devtools/devtools-messages.d.ts +705 -0
  31. package/dist/devtools/devtools-messages.d.ts.map +1 -0
  32. package/dist/devtools/devtools-messages.js +178 -0
  33. package/dist/devtools/devtools-messages.js.map +1 -0
  34. package/dist/devtools/devtools-window-message.d.ts +29 -0
  35. package/dist/devtools/devtools-window-message.d.ts.map +1 -0
  36. package/dist/devtools/devtools-window-message.js +33 -0
  37. package/dist/devtools/devtools-window-message.js.map +1 -0
  38. package/dist/devtools/index.d.ts +42 -0
  39. package/dist/devtools/index.d.ts.map +1 -0
  40. package/dist/devtools/index.js +49 -0
  41. package/dist/devtools/index.js.map +1 -0
  42. package/dist/index.d.ts +19 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +15 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/init-singleton-tables.d.ts +4 -0
  47. package/dist/init-singleton-tables.d.ts.map +1 -0
  48. package/dist/init-singleton-tables.js +16 -0
  49. package/dist/init-singleton-tables.js.map +1 -0
  50. package/dist/mutation.d.ts +13 -0
  51. package/dist/mutation.d.ts.map +1 -0
  52. package/dist/mutation.js +43 -0
  53. package/dist/mutation.js.map +1 -0
  54. package/dist/query-info.d.ts +47 -0
  55. package/dist/query-info.d.ts.map +1 -0
  56. package/dist/query-info.js +38 -0
  57. package/dist/query-info.js.map +1 -0
  58. package/dist/rehydrate-from-mutationlog.d.ts +14 -0
  59. package/dist/rehydrate-from-mutationlog.d.ts.map +1 -0
  60. package/dist/rehydrate-from-mutationlog.js +72 -0
  61. package/dist/rehydrate-from-mutationlog.js.map +1 -0
  62. package/dist/schema/index.d.ts +60 -0
  63. package/dist/schema/index.d.ts.map +1 -0
  64. package/dist/schema/index.js +66 -0
  65. package/dist/schema/index.js.map +1 -0
  66. package/dist/schema/mutations.d.ts +227 -0
  67. package/dist/schema/mutations.d.ts.map +1 -0
  68. package/dist/schema/mutations.js +68 -0
  69. package/dist/schema/mutations.js.map +1 -0
  70. package/dist/schema/schema-helpers.d.ts +4 -0
  71. package/dist/schema/schema-helpers.d.ts.map +1 -0
  72. package/dist/schema/schema-helpers.js +30 -0
  73. package/dist/schema/schema-helpers.js.map +1 -0
  74. package/dist/schema/system-tables.d.ts +331 -0
  75. package/dist/schema/system-tables.d.ts.map +1 -0
  76. package/dist/schema/system-tables.js +46 -0
  77. package/dist/schema/system-tables.js.map +1 -0
  78. package/dist/schema/table-def.d.ts +135 -0
  79. package/dist/schema/table-def.d.ts.map +1 -0
  80. package/dist/schema/table-def.js +70 -0
  81. package/dist/schema/table-def.js.map +1 -0
  82. package/dist/schema-management/common.d.ts +13 -0
  83. package/dist/schema-management/common.d.ts.map +1 -0
  84. package/dist/schema-management/common.js +25 -0
  85. package/dist/schema-management/common.js.map +1 -0
  86. package/dist/schema-management/migrations.d.ts +23 -0
  87. package/dist/schema-management/migrations.d.ts.map +1 -0
  88. package/dist/schema-management/migrations.js +116 -0
  89. package/dist/schema-management/migrations.js.map +1 -0
  90. package/dist/schema-management/validate-mutation-defs.d.ts +8 -0
  91. package/dist/schema-management/validate-mutation-defs.d.ts.map +1 -0
  92. package/dist/schema-management/validate-mutation-defs.js +39 -0
  93. package/dist/schema-management/validate-mutation-defs.js.map +1 -0
  94. package/dist/sql-queries/index.d.ts +4 -0
  95. package/dist/sql-queries/index.d.ts.map +1 -0
  96. package/dist/sql-queries/index.js +4 -0
  97. package/dist/sql-queries/index.js.map +1 -0
  98. package/dist/sql-queries/misc.d.ts +2 -0
  99. package/dist/sql-queries/misc.d.ts.map +1 -0
  100. package/dist/sql-queries/misc.js +2 -0
  101. package/dist/sql-queries/misc.js.map +1 -0
  102. package/dist/sql-queries/sql-queries.d.ts +72 -0
  103. package/dist/sql-queries/sql-queries.d.ts.map +1 -0
  104. package/dist/sql-queries/sql-queries.js +191 -0
  105. package/dist/sql-queries/sql-queries.js.map +1 -0
  106. package/dist/sql-queries/sql-query-builder.d.ts +47 -0
  107. package/dist/sql-queries/sql-query-builder.d.ts.map +1 -0
  108. package/dist/sql-queries/sql-query-builder.js +60 -0
  109. package/dist/sql-queries/sql-query-builder.js.map +1 -0
  110. package/dist/sql-queries/types.d.ts +50 -0
  111. package/dist/sql-queries/types.d.ts.map +1 -0
  112. package/dist/sql-queries/types.js +5 -0
  113. package/dist/sql-queries/types.js.map +1 -0
  114. package/dist/sync/index.d.ts +2 -0
  115. package/dist/sync/index.d.ts.map +1 -0
  116. package/dist/sync/index.js +2 -0
  117. package/dist/sync/index.js.map +1 -0
  118. package/dist/sync/next/compact-events.d.ts +15 -0
  119. package/dist/sync/next/compact-events.d.ts.map +1 -0
  120. package/dist/sync/next/compact-events.js +176 -0
  121. package/dist/sync/next/compact-events.js.map +1 -0
  122. package/dist/sync/next/facts.d.ts +37 -0
  123. package/dist/sync/next/facts.d.ts.map +1 -0
  124. package/dist/sync/next/facts.js +156 -0
  125. package/dist/sync/next/facts.js.map +1 -0
  126. package/dist/sync/next/graphology.d.ts +8 -0
  127. package/dist/sync/next/graphology.d.ts.map +1 -0
  128. package/dist/sync/next/graphology.js +36 -0
  129. package/dist/sync/next/graphology.js.map +1 -0
  130. package/dist/sync/next/graphology_.d.ts +3 -0
  131. package/dist/sync/next/graphology_.d.ts.map +1 -0
  132. package/dist/sync/next/graphology_.js +3 -0
  133. package/dist/sync/next/graphology_.js.map +1 -0
  134. package/dist/sync/next/history-dag.d.ts +30 -0
  135. package/dist/sync/next/history-dag.d.ts.map +1 -0
  136. package/dist/sync/next/history-dag.js +69 -0
  137. package/dist/sync/next/history-dag.js.map +1 -0
  138. package/dist/sync/next/mod.d.ts +5 -0
  139. package/dist/sync/next/mod.d.ts.map +1 -0
  140. package/dist/sync/next/mod.js +5 -0
  141. package/dist/sync/next/mod.js.map +1 -0
  142. package/dist/sync/next/rebase-events.d.ts +27 -0
  143. package/dist/sync/next/rebase-events.d.ts.map +1 -0
  144. package/dist/sync/next/rebase-events.js +41 -0
  145. package/dist/sync/next/rebase-events.js.map +1 -0
  146. package/dist/sync/next/test/compact-events.calculator.test.d.ts +2 -0
  147. package/dist/sync/next/test/compact-events.calculator.test.d.ts.map +1 -0
  148. package/dist/sync/next/test/compact-events.calculator.test.js +101 -0
  149. package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -0
  150. package/dist/sync/next/test/compact-events.test.d.ts +2 -0
  151. package/dist/sync/next/test/compact-events.test.d.ts.map +1 -0
  152. package/dist/sync/next/test/compact-events.test.js +201 -0
  153. package/dist/sync/next/test/compact-events.test.js.map +1 -0
  154. package/dist/sync/next/test/mod.d.ts +2 -0
  155. package/dist/sync/next/test/mod.d.ts.map +1 -0
  156. package/dist/sync/next/test/mod.js +2 -0
  157. package/dist/sync/next/test/mod.js.map +1 -0
  158. package/dist/sync/next/test/mutation-fixtures.d.ts +73 -0
  159. package/dist/sync/next/test/mutation-fixtures.d.ts.map +1 -0
  160. package/dist/sync/next/test/mutation-fixtures.js +161 -0
  161. package/dist/sync/next/test/mutation-fixtures.js.map +1 -0
  162. package/dist/sync/sync.d.ts +45 -0
  163. package/dist/sync/sync.d.ts.map +1 -0
  164. package/dist/sync/sync.js +12 -0
  165. package/dist/sync/sync.js.map +1 -0
  166. package/dist/util.d.ts +25 -0
  167. package/dist/util.d.ts.map +1 -0
  168. package/dist/util.js +38 -0
  169. package/dist/util.js.map +1 -0
  170. package/dist/version.d.ts +10 -0
  171. package/dist/version.d.ts.map +1 -0
  172. package/dist/version.js +12 -0
  173. package/dist/version.js.map +1 -0
  174. package/package.json +61 -0
  175. package/src/__tests__/fixture.ts +23 -0
  176. package/src/adapter-types.ts +216 -0
  177. package/src/ambient.d.ts +3 -0
  178. package/src/bounded-collections.ts +121 -0
  179. package/src/debug-info.ts +76 -0
  180. package/src/derived-mutations.test.ts +101 -0
  181. package/src/derived-mutations.ts +170 -0
  182. package/src/devtools/devtools-bridge.ts +13 -0
  183. package/src/devtools/devtools-messages.ts +247 -0
  184. package/src/devtools/devtools-window-message.ts +27 -0
  185. package/src/devtools/index.ts +49 -0
  186. package/src/index.ts +20 -0
  187. package/src/init-singleton-tables.ts +24 -0
  188. package/src/mutation.ts +69 -0
  189. package/src/query-info.ts +104 -0
  190. package/src/rehydrate-from-mutationlog.ts +131 -0
  191. package/src/schema/index.ts +144 -0
  192. package/src/schema/mutations.ts +313 -0
  193. package/src/schema/schema-helpers.ts +49 -0
  194. package/src/schema/system-tables.ts +84 -0
  195. package/src/schema/table-def.ts +312 -0
  196. package/src/schema-management/common.ts +44 -0
  197. package/src/schema-management/migrations.ts +188 -0
  198. package/src/schema-management/validate-mutation-defs.ts +63 -0
  199. package/src/sql-queries/index.ts +3 -0
  200. package/src/sql-queries/misc.ts +2 -0
  201. package/src/sql-queries/sql-queries.ts +359 -0
  202. package/src/sql-queries/sql-query-builder.ts +135 -0
  203. package/src/sql-queries/types.ts +97 -0
  204. package/src/sync/index.ts +1 -0
  205. package/src/sync/next/ambient.d.ts +3 -0
  206. package/src/sync/next/compact-events.ts +218 -0
  207. package/src/sync/next/facts.ts +229 -0
  208. package/src/sync/next/graphology.ts +49 -0
  209. package/src/sync/next/graphology_.ts +2 -0
  210. package/src/sync/next/history-dag.ts +109 -0
  211. package/src/sync/next/mod.ts +4 -0
  212. package/src/sync/next/rebase-events.ts +97 -0
  213. package/src/sync/next/test/compact-events.calculator.test.ts +121 -0
  214. package/src/sync/next/test/compact-events.test.ts +232 -0
  215. package/src/sync/next/test/mod.ts +1 -0
  216. package/src/sync/next/test/mutation-fixtures.ts +230 -0
  217. package/src/sync/sync.ts +46 -0
  218. package/src/util.ts +56 -0
  219. package/src/version.ts +13 -0
  220. package/tsconfig.json +11 -0
@@ -0,0 +1,216 @@
1
+ import type { Cause, Queue, Scope, SubscriptionRef, WebChannel } from '@livestore/utils/effect'
2
+ import { Effect, Schema, Stream } from '@livestore/utils/effect'
3
+
4
+ import type * as Devtools from './devtools/index.js'
5
+ import type { LiveStoreSchema, MutationEvent } from './schema/index.js'
6
+ import type { PreparedBindValues } from './util.js'
7
+
8
+ export interface PreparedStatement {
9
+ execute(bindValues: PreparedBindValues | undefined, options?: { onRowsChanged?: (rowsChanged: number) => void }): void
10
+ select<T>(bindValues: PreparedBindValues | undefined): ReadonlyArray<T>
11
+ finalize(): void
12
+ sql: string
13
+ }
14
+
15
+ export type ClientSession = {
16
+ /** SQLite database with synchronous API running in the same thread (usually in-memory) */
17
+ syncDb: SynchronousDatabase
18
+ /** The coordinator is responsible for persisting the database, syncing etc */
19
+ coordinator: Coordinator
20
+ }
21
+
22
+ export type SynchronousDatabase = {
23
+ _tag: 'SynchronousDatabase'
24
+ prepare(queryStr: string): PreparedStatement
25
+ execute(
26
+ queryStr: string,
27
+ bindValues?: PreparedBindValues | undefined,
28
+ options?: { onRowsChanged?: (rowsChanged: number) => void },
29
+ ): void
30
+ select<T>(queryStr: string, bindValues?: PreparedBindValues | undefined): ReadonlyArray<T>
31
+ export(): Uint8Array
32
+ close(): void
33
+ }
34
+
35
+ export type ResetMode = 'all-data' | 'only-app-db'
36
+
37
+ export const NetworkStatus = Schema.Struct({
38
+ isConnected: Schema.Boolean,
39
+ timestampMs: Schema.Number,
40
+ })
41
+
42
+ export type NetworkStatus = {
43
+ isConnected: boolean
44
+ timestampMs: number
45
+ }
46
+
47
+ export const BootStateProgress = Schema.Struct({
48
+ done: Schema.Number,
49
+ total: Schema.Number,
50
+ })
51
+
52
+ export const BootStatus = Schema.Union(
53
+ Schema.Struct({ stage: Schema.Literal('loading') }),
54
+ Schema.Struct({ stage: Schema.Literal('migrating'), progress: BootStateProgress }),
55
+ Schema.Struct({ stage: Schema.Literal('rehydrating'), progress: BootStateProgress }),
56
+ Schema.Struct({ stage: Schema.Literal('syncing'), progress: BootStateProgress }),
57
+ Schema.Struct({ stage: Schema.Literal('done') }),
58
+ )
59
+
60
+ export type BootStatus = typeof BootStatus.Type
61
+
62
+ export type Coordinator = {
63
+ devtools: {
64
+ enabled: boolean
65
+ // TODO incorporate sessionId and rethink appHostId
66
+ appHostId: string
67
+ }
68
+ sessionId: string
69
+ // TODO is exposing the lock status really needed (or only relevant for web adapter?)
70
+ lockStatus: SubscriptionRef.SubscriptionRef<LockStatus>
71
+ syncMutations: Stream.Stream<MutationEvent.Any, UnexpectedError>
72
+ execute(queryStr: string, bindValues: PreparedBindValues | undefined): Effect.Effect<void, UnexpectedError>
73
+ mutate(
74
+ mutationEventEncoded: MutationEvent.AnyEncoded,
75
+ options: { persisted: boolean },
76
+ ): Effect.Effect<void, UnexpectedError>
77
+ /** Can be called synchronously */
78
+ nextMutationEventIdPair: (opts: { localOnly: boolean }) => Effect.Effect<EventIdPair, UnexpectedError>
79
+ /** Used to initially get the current mutation event id to use as `parentId` for the next mutation event */
80
+ getCurrentMutationEventId: Effect.Effect<EventId, UnexpectedError>
81
+ export: Effect.Effect<Uint8Array | undefined, UnexpectedError>
82
+ getMutationLogData: Effect.Effect<Uint8Array, UnexpectedError>
83
+ networkStatus: SubscriptionRef.SubscriptionRef<NetworkStatus>
84
+ }
85
+
86
+ /**
87
+ * Can be used in queries to refer to the current session id.
88
+ * Will be replaced with the actual session id at runtime
89
+ *
90
+ * Example:
91
+ * ```ts
92
+ * const query$ = rowQuery(tables.app, SessionIdSymbol)
93
+ * ```
94
+ */
95
+ export const SessionIdSymbol = Symbol.for('@livestore/session-id')
96
+ export type SessionIdSymbol = typeof SessionIdSymbol
97
+
98
+ export type LockStatus = 'has-lock' | 'no-lock'
99
+
100
+ /**
101
+ * LiveStore event id value consisting of a globally unique event sequence number
102
+ * and a local sequence number.
103
+ *
104
+ * The local sequence number is only used for localOnly mutations and starts from 0 for each global sequence number.
105
+ */
106
+ export type EventId = { global: number; local: number }
107
+
108
+ export const EventId = Schema.Struct({
109
+ global: Schema.Number,
110
+ local: Schema.Number,
111
+ }).annotations({ title: 'LiveStore.EventId' })
112
+
113
+ export type EventIdPair = { id: EventId; parentId: EventId }
114
+
115
+ export const ROOT_ID = { global: -1, local: 0 } satisfies EventId
116
+
117
+ export type BootDb = {
118
+ _tag: 'BootDb'
119
+ execute(queryStr: string, bindValues?: PreparedBindValues): void
120
+ mutate: <const TMutationArg extends ReadonlyArray<MutationEvent.PartialAny>>(...list: TMutationArg) => void
121
+ select<T>(queryStr: string, bindValues?: PreparedBindValues): ReadonlyArray<T>
122
+ txn(callback: () => void): void
123
+ }
124
+
125
+ export class UnexpectedError extends Schema.TaggedError<UnexpectedError>()('LiveStore.UnexpectedError', {
126
+ cause: Schema.Defect,
127
+ note: Schema.optional(Schema.String),
128
+ payload: Schema.optional(Schema.Any),
129
+ }) {
130
+ static mapToUnexpectedError = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
131
+ effect.pipe(
132
+ Effect.mapError((cause) => (Schema.is(UnexpectedError)(cause) ? cause : new UnexpectedError({ cause }))),
133
+ Effect.catchAllDefect((cause) => new UnexpectedError({ cause })),
134
+ )
135
+
136
+ static mapToUnexpectedErrorStream = <A, E, R>(stream: Stream.Stream<A, E, R>) =>
137
+ stream.pipe(
138
+ Stream.mapError((cause) => (Schema.is(UnexpectedError)(cause) ? cause : new UnexpectedError({ cause }))),
139
+ )
140
+ }
141
+
142
+ export class IntentionalShutdownCause extends Schema.TaggedError<IntentionalShutdownCause>()(
143
+ 'LiveStore.IntentionalShutdownCause',
144
+ {
145
+ reason: Schema.Literal('devtools-reset', 'devtools-import'),
146
+ },
147
+ ) {}
148
+
149
+ export class SqliteError extends Schema.TaggedError<SqliteError>()('LiveStore.SqliteError', {
150
+ query: Schema.optional(
151
+ Schema.Struct({
152
+ sql: Schema.String,
153
+ bindValues: Schema.Union(Schema.Record({ key: Schema.String, value: Schema.Any }), Schema.Array(Schema.Any)),
154
+ }),
155
+ ),
156
+ /** The SQLite result code */
157
+ code: Schema.optional(Schema.Number),
158
+ /** The original SQLite3 error */
159
+ cause: Schema.Defect,
160
+ }) {}
161
+
162
+ // TODO possibly allow a combination of these options
163
+ // TODO allow a way to stream the migration progress back to the app
164
+ export type MigrationOptions<TSchema extends LiveStoreSchema = LiveStoreSchema> =
165
+ | MigrationOptionsFromMutationLog<TSchema>
166
+ | {
167
+ strategy: 'hard-reset'
168
+ hooks?: Partial<MigrationHooks>
169
+ }
170
+ | {
171
+ strategy: 'manual'
172
+ migrate: (oldDb: Uint8Array) => Uint8Array | Promise<Uint8Array> | Effect.Effect<Uint8Array, unknown>
173
+ }
174
+
175
+ export type MigrationHooks = {
176
+ /** Runs on the empty in-memory database with no database schemas applied yet */
177
+ init: MigrationHook
178
+ /** Runs before applying the migration strategy but after table schemas have been applied and singleton rows have been created */
179
+ pre: MigrationHook
180
+ /** Runs after applying the migration strategy before creating export snapshot and closing the database */
181
+ post: MigrationHook
182
+ }
183
+
184
+ export type MigrationHook = (db: SynchronousDatabase) => void | Promise<void> | Effect.Effect<void, unknown>
185
+
186
+ export type MigrationOptionsFromMutationLog<TSchema extends LiveStoreSchema = LiveStoreSchema> = {
187
+ strategy: 'from-mutation-log'
188
+ /**
189
+ * Mutations to exclude in the mutation log
190
+ *
191
+ * @default new Set(['livestore.RawSql'])
192
+ */
193
+ excludeMutations?: ReadonlySet<keyof TSchema['_MutationDefMapType'] & string>
194
+ hooks?: Partial<MigrationHooks>
195
+ logging?: {
196
+ excludeAffectedRows?: (sqlStmt: string) => boolean
197
+ }
198
+ }
199
+
200
+ export type StoreDevtoolsChannel = WebChannel.WebChannel<
201
+ Devtools.MessageToAppHostStore,
202
+ Devtools.MessageFromAppHostStore
203
+ >
204
+
205
+ export type ConnectDevtoolsToStore = (
206
+ storeDevtoolsChannel: StoreDevtoolsChannel,
207
+ ) => Effect.Effect<void, UnexpectedError, Scope.Scope>
208
+
209
+ export type Adapter = (opts: {
210
+ schema: LiveStoreSchema
211
+ storeId: string
212
+ devtoolsEnabled: boolean
213
+ bootStatusQueue: Queue.Queue<BootStatus>
214
+ shutdown: (cause: Cause.Cause<any>) => Effect.Effect<void>
215
+ connectDevtoolsToStore: ConnectDevtoolsToStore
216
+ }) => Effect.Effect<ClientSession, UnexpectedError, Scope.Scope>
@@ -0,0 +1,3 @@
1
+ interface ImportMeta {
2
+ readonly env: ImportMetaEnv
3
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Creates a map that has a fixed number of entries.
3
+ * Once hitting the bound, earliest insertions are removed
4
+ */
5
+ export class BoundMap<K, V> {
6
+ #map = new Map<K, V>()
7
+ #sizeLimit: number
8
+
9
+ constructor(sizeLimit: number) {
10
+ this.#sizeLimit = sizeLimit
11
+ }
12
+
13
+ onEvict: ((key: K, value: V) => void) | undefined
14
+
15
+ set = (key: K, value: V) => {
16
+ this.#map.set(key, value)
17
+ // console.log(this.#map.size, this.#sizeLimit);
18
+ if (this.#map.size > this.#sizeLimit) {
19
+ const firstKey = this.#map.keys().next().value as K
20
+ const deletedValue = this.#map.get(firstKey)!
21
+ this.#map.delete(firstKey)
22
+ if (this.onEvict) {
23
+ this.onEvict(firstKey, deletedValue)
24
+ }
25
+ }
26
+ }
27
+
28
+ get = (key: K): V | undefined => {
29
+ return this.#map.get(key)
30
+ }
31
+
32
+ delete = (key: K) => {
33
+ this.#map.delete(key)
34
+ }
35
+
36
+ keys = () => {
37
+ return this.#map.keys()
38
+ }
39
+ }
40
+
41
+ export class BoundSet<V> {
42
+ #map: BoundMap<V, V>
43
+
44
+ constructor(sizeLimit: number) {
45
+ this.#map = new BoundMap(sizeLimit)
46
+ this.#map.onEvict = this.#onEvict
47
+ }
48
+
49
+ #onEvict = (v: V) => {
50
+ if (this.onEvict) {
51
+ this.onEvict(v)
52
+ }
53
+ }
54
+
55
+ onEvict: ((key: V) => void) | undefined
56
+
57
+ add = (v: V) => {
58
+ this.#map.set(v, v)
59
+ };
60
+
61
+ [Symbol.iterator] = () => {
62
+ return this.#map.keys()
63
+ }
64
+ }
65
+
66
+ export class BoundArray<V> {
67
+ #array: V[] = []
68
+ public sizeLimit: number
69
+
70
+ constructor(sizeLimit: number) {
71
+ this.sizeLimit = sizeLimit
72
+ }
73
+
74
+ static make = <V>(sizeLimit: number, initial: Iterable<V> = []): BoundArray<V> => {
75
+ const b = new BoundArray<V>(sizeLimit)
76
+ for (const v of initial) {
77
+ b.push(v)
78
+ }
79
+ return b
80
+ }
81
+
82
+ onEvict: ((key: V) => void) | undefined
83
+
84
+ push = (v: V) => {
85
+ this.#array.push(v)
86
+ if (this.#array.length > this.sizeLimit) {
87
+ const first = this.#array.shift()
88
+ if (first && this.onEvict) {
89
+ this.onEvict(first)
90
+ }
91
+ }
92
+ }
93
+
94
+ get = (index: number): V | undefined => {
95
+ return this.#array[index]
96
+ }
97
+
98
+ delete = (index: number) => {
99
+ this.#array.splice(index, 1)
100
+ }
101
+
102
+ get length() {
103
+ return this.#array.length
104
+ }
105
+
106
+ [Symbol.iterator] = (): IterableIterator<V> => {
107
+ return this.#array[Symbol.iterator]()
108
+ }
109
+
110
+ map = <T>(fn: (v: V) => T): T[] => {
111
+ return this.#array.map(fn)
112
+ }
113
+
114
+ clear = () => {
115
+ this.#array = []
116
+ }
117
+
118
+ sort = (fn?: (a: V, b: V) => number) => {
119
+ return this.#array.sort(fn)
120
+ }
121
+ }
@@ -0,0 +1,76 @@
1
+ import { ParseResult, Schema } from '@livestore/utils/effect'
2
+
3
+ import { BoundArray } from './bounded-collections.js'
4
+ import { PreparedBindValues } from './util.js'
5
+
6
+ export type SlowQueryInfo = {
7
+ queryStr: string
8
+ bindValues: PreparedBindValues | undefined
9
+ durationMs: number
10
+ rowsCount: number | undefined
11
+ queriedTables: Set<string>
12
+ startTimePerfNow: DOMHighResTimeStamp
13
+ }
14
+
15
+ export const SlowQueryInfo = Schema.Struct({
16
+ queryStr: Schema.String,
17
+ bindValues: Schema.UndefinedOr(PreparedBindValues),
18
+ durationMs: Schema.Number,
19
+ rowsCount: Schema.UndefinedOr(Schema.Number),
20
+ queriedTables: Schema.ReadonlySet(Schema.String),
21
+ startTimePerfNow: Schema.Number,
22
+ })
23
+
24
+ const BoundArraySchemaFromSelf = <A, I, R>(
25
+ item: Schema.Schema<A, I, R>,
26
+ ): Schema.Schema<BoundArray<A>, BoundArray<I>, R> =>
27
+ Schema.declare(
28
+ [item],
29
+ {
30
+ decode: (item) => (input, parseOptions, ast) => {
31
+ if (input instanceof BoundArray) {
32
+ const elements = ParseResult.decodeUnknown(Schema.Array(item))([...input], parseOptions)
33
+ return ParseResult.map(elements, (as): BoundArray<A> => BoundArray.make(input.sizeLimit, as))
34
+ }
35
+ return ParseResult.fail(new ParseResult.Type(ast, input))
36
+ },
37
+ encode: (item) => (input, parseOptions, ast) => {
38
+ if (input instanceof BoundArray) {
39
+ const elements = ParseResult.encodeUnknown(Schema.Array(item))([...input], parseOptions)
40
+ return ParseResult.map(elements, (is): BoundArray<I> => BoundArray.make(input.sizeLimit, is))
41
+ }
42
+ return ParseResult.fail(new ParseResult.Type(ast, input))
43
+ },
44
+ },
45
+ {
46
+ description: `BoundArray<${Schema.format(item)}>`,
47
+ pretty: () => (_) => `BoundArray(${_.length})`,
48
+ arbitrary: () => (fc) => fc.anything() as any,
49
+ equivalence: () => (a, b) => a === b,
50
+ },
51
+ )
52
+
53
+ export const BoundArraySchema = <ItemDecoded, ItemEncoded>(elSchema: Schema.Schema<ItemDecoded, ItemEncoded>) =>
54
+ Schema.transform(
55
+ Schema.Struct({
56
+ size: Schema.Number,
57
+ items: Schema.Array(elSchema),
58
+ }),
59
+ BoundArraySchemaFromSelf(Schema.typeSchema(elSchema)),
60
+ {
61
+ encode: (_) => ({ size: _.sizeLimit, items: [..._] }),
62
+ decode: (_) => BoundArray.make(_.size, _.items),
63
+ },
64
+ )
65
+
66
+ export const DebugInfo = Schema.Struct({
67
+ slowQueries: BoundArraySchema(SlowQueryInfo),
68
+ queryFrameDuration: Schema.Number,
69
+ queryFrameCount: Schema.Number,
70
+ events: BoundArraySchema(Schema.Tuple(Schema.String, Schema.Any)),
71
+ })
72
+
73
+ export type DebugInfo = typeof DebugInfo.Type
74
+
75
+ export const MutableDebugInfo = Schema.mutable(DebugInfo)
76
+ export type MutableDebugInfo = typeof MutableDebugInfo.Type
@@ -0,0 +1,101 @@
1
+ import { describe, expect, test } from 'vitest'
2
+
3
+ import { appConfig, todos } from './__tests__/fixture.js'
4
+ import type { MutationEvent } from './schema/mutations.js'
5
+
6
+ describe('derived mutations', () => {
7
+ test('todos', () => {
8
+ expect(patchId(todos.insert({ id: 't1', completed: true, text: 'Task 1' }))).toMatchInlineSnapshot(`
9
+ {
10
+ "args": {
11
+ "completed": true,
12
+ "id": "t1",
13
+ "text": "Task 1",
14
+ },
15
+ "id": "00000000-0000-0000-0000-000000000000",
16
+ "mutation": "_Derived_Create_todos",
17
+ }
18
+ `)
19
+
20
+ expect(patchId(todos.update({ where: { id: 't1' }, values: { text: 'Task 1 - fixed' } }))).toMatchInlineSnapshot(`
21
+ {
22
+ "args": {
23
+ "values": {
24
+ "text": "Task 1 - fixed",
25
+ },
26
+ "where": {
27
+ "id": "t1",
28
+ },
29
+ },
30
+ "id": "00000000-0000-0000-0000-000000000000",
31
+ "mutation": "_Derived_Update_todos",
32
+ }
33
+ `)
34
+
35
+ expect(patchId(todos.delete({ where: { id: 't1' } }))).toMatchInlineSnapshot(`
36
+ {
37
+ "args": {
38
+ "where": {
39
+ "id": "t1",
40
+ },
41
+ },
42
+ "id": "00000000-0000-0000-0000-000000000000",
43
+ "mutation": "_Derived_Delete_todos",
44
+ }
45
+ `)
46
+ })
47
+
48
+ test('app_config', () => {
49
+ expect(patchId(appConfig.insert())).toMatchInlineSnapshot(`
50
+ {
51
+ "args": {
52
+ "id": "singleton",
53
+ "value": {
54
+ "value": undefined,
55
+ },
56
+ },
57
+ "id": "00000000-0000-0000-0000-000000000000",
58
+ "mutation": "_Derived_Create_app_config",
59
+ }
60
+ `)
61
+
62
+ expect(patchId(appConfig.insert({ fontSize: 12, theme: 'dark' }))).toMatchInlineSnapshot(`
63
+ {
64
+ "args": {
65
+ "id": "singleton",
66
+ "value": {
67
+ "value": {
68
+ "fontSize": 12,
69
+ "theme": "dark",
70
+ },
71
+ },
72
+ },
73
+ "id": "00000000-0000-0000-0000-000000000000",
74
+ "mutation": "_Derived_Create_app_config",
75
+ }
76
+ `)
77
+
78
+ expect(patchId(appConfig.update({ fontSize: 13 }))).toMatchInlineSnapshot(`
79
+ {
80
+ "args": {
81
+ "values": {
82
+ "value": {
83
+ "fontSize": 13,
84
+ },
85
+ },
86
+ "where": {
87
+ "id": "singleton",
88
+ },
89
+ },
90
+ "id": "00000000-0000-0000-0000-000000000000",
91
+ "mutation": "_Derived_Update_app_config",
92
+ }
93
+ `)
94
+ })
95
+ })
96
+
97
+ const patchId = (muationEvent: MutationEvent.PartialAny) => {
98
+ // TODO use new id paradigm
99
+ const id = `00000000-0000-0000-0000-000000000000`
100
+ return { ...muationEvent, id }
101
+ }
@@ -0,0 +1,170 @@
1
+ import type { SqliteDsl } from '@livestore/db-schema'
2
+ import type { GetValForKey } from '@livestore/utils'
3
+ import { ReadonlyRecord, Schema } from '@livestore/utils/effect'
4
+
5
+ import type { MutationEvent } from './schema/mutations.js'
6
+ import { defineMutation } from './schema/mutations.js'
7
+ import { getDefaultValuesDecoded } from './schema/schema-helpers.js'
8
+ import type * as DbSchema from './schema/table-def.js'
9
+ import { deleteRows, insertRow, updateRows } from './sql-queries/sql-queries.js'
10
+
11
+ export const makeDerivedMutationDefsForTable = <
12
+ TTableDef extends DbSchema.TableDef<
13
+ DbSchema.DefaultSqliteTableDefConstrained,
14
+ boolean,
15
+ DbSchema.TableOptions & { deriveMutations: { enabled: true } }
16
+ >,
17
+ >(
18
+ table: TTableDef,
19
+ ) => ({
20
+ insert: deriveCreateMutationDef(table),
21
+ update: deriveUpdateMutationDef(table),
22
+ delete: deriveDeleteMutationDef(table),
23
+ })
24
+
25
+ export const deriveCreateMutationDef = <
26
+ TTableDef extends DbSchema.TableDef<
27
+ DbSchema.DefaultSqliteTableDefConstrained,
28
+ boolean,
29
+ DbSchema.TableOptions & { deriveMutations: { enabled: true } }
30
+ >,
31
+ >(
32
+ table: TTableDef,
33
+ ) => {
34
+ const tableName = table.sqliteDef.name
35
+
36
+ const [optionalFields, requiredColumns] = ReadonlyRecord.partition(
37
+ (table.sqliteDef as DbSchema.DefaultSqliteTableDef).columns,
38
+ (col) => col.nullable === false && col.default._tag === 'None',
39
+ )
40
+
41
+ const insertSchema = Schema.Struct(ReadonlyRecord.map(requiredColumns, (col) => col.schema))
42
+ .pipe(Schema.extend(Schema.partial(Schema.Struct(ReadonlyRecord.map(optionalFields, (col) => col.schema)))))
43
+ .annotations({ title: `${tableName}:Insert` })
44
+
45
+ return defineMutation(
46
+ `_Derived_Create_${tableName}`,
47
+ insertSchema,
48
+ ({ id, ...explicitDefaultValues }) => {
49
+ const defaultValues = getDefaultValuesDecoded(table, explicitDefaultValues)
50
+
51
+ const [sql, bindValues] = insertRow({
52
+ tableName: table.sqliteDef.name,
53
+ columns: table.sqliteDef.columns,
54
+ values: { ...defaultValues, id },
55
+ })
56
+
57
+ return { sql, bindValues, writeTables: new Set([tableName]) }
58
+ },
59
+ { localOnly: table.options.deriveMutations.localOnly },
60
+ )
61
+ }
62
+
63
+ export const deriveUpdateMutationDef = <
64
+ TTableDef extends DbSchema.TableDef<
65
+ DbSchema.DefaultSqliteTableDefConstrained,
66
+ boolean,
67
+ DbSchema.TableOptions & { deriveMutations: { enabled: true } }
68
+ >,
69
+ >(
70
+ table: TTableDef,
71
+ ) => {
72
+ const tableName = table.sqliteDef.name
73
+
74
+ return defineMutation(
75
+ `_Derived_Update_${tableName}`,
76
+ Schema.Struct({
77
+ where: Schema.partial(table.schema),
78
+ values: Schema.partial(table.schema),
79
+ }).annotations({ title: `${tableName}:Update` }),
80
+ ({ where, values }) => {
81
+ const [sql, bindValues] = updateRows({
82
+ tableName: table.sqliteDef.name,
83
+ columns: table.sqliteDef.columns,
84
+ where,
85
+ updateValues: values,
86
+ })
87
+
88
+ return { sql, bindValues, writeTables: new Set([tableName]) }
89
+ },
90
+ { localOnly: table.options.deriveMutations.localOnly },
91
+ )
92
+ }
93
+
94
+ export const deriveDeleteMutationDef = <
95
+ TTableDef extends DbSchema.TableDef<
96
+ DbSchema.DefaultSqliteTableDefConstrained,
97
+ boolean,
98
+ DbSchema.TableOptions & { deriveMutations: { enabled: true } }
99
+ >,
100
+ >(
101
+ table: TTableDef,
102
+ ) => {
103
+ const tableName = table.sqliteDef.name
104
+
105
+ return defineMutation(
106
+ `_Derived_Delete_${tableName}`,
107
+ Schema.Struct({
108
+ where: Schema.partial(table.schema),
109
+ }),
110
+ ({ where }) => {
111
+ const [sql, bindValues] = deleteRows({
112
+ tableName: table.sqliteDef.name,
113
+ columns: table.sqliteDef.columns,
114
+ where,
115
+ })
116
+
117
+ return { sql, bindValues, writeTables: new Set([tableName]) }
118
+ },
119
+ { localOnly: table.options.deriveMutations.localOnly },
120
+ )
121
+ }
122
+
123
+ /**
124
+ * Convenience helper functions on top of the derived mutation definitions.
125
+ */
126
+ export type DerivedMutationHelperFns<
127
+ TColumns extends SqliteDsl.ConstraintColumns,
128
+ TOptions extends DbSchema.TableOptions,
129
+ > = {
130
+ insert: DerivedMutationHelperFns.InsertMutationFn<TColumns, TOptions>
131
+ update: DerivedMutationHelperFns.UpdateMutationFn<TColumns, TOptions>
132
+ delete: DerivedMutationHelperFns.DeleteMutationFn<TColumns, TOptions>
133
+ // TODO also consider adding upsert and deep json mutations (like lenses)
134
+ }
135
+
136
+ export namespace DerivedMutationHelperFns {
137
+ export type InsertMutationFn<
138
+ TColumns extends SqliteDsl.ConstraintColumns,
139
+ TOptions extends DbSchema.TableOptions,
140
+ > = SqliteDsl.AnyIfConstained<
141
+ TColumns,
142
+ UseShortcut<TOptions> extends true
143
+ ? (values?: GetValForKey<SqliteDsl.FromColumns.InsertRowDecoded<TColumns>, 'value'>) => MutationEvent.PartialAny
144
+ : (values: SqliteDsl.FromColumns.InsertRowDecoded<TColumns>) => MutationEvent.PartialAny
145
+ >
146
+
147
+ export type UpdateMutationFn<
148
+ TColumns extends SqliteDsl.ConstraintColumns,
149
+ TOptions extends DbSchema.TableOptions,
150
+ > = SqliteDsl.AnyIfConstained<
151
+ TColumns,
152
+ UseShortcut<TOptions> extends true
153
+ ? (values: Partial<GetValForKey<SqliteDsl.FromColumns.RowDecoded<TColumns>, 'value'>>) => MutationEvent.PartialAny
154
+ : (args: {
155
+ where: Partial<SqliteDsl.FromColumns.RowDecoded<TColumns>>
156
+ values: Partial<SqliteDsl.FromColumns.RowDecoded<TColumns>>
157
+ }) => MutationEvent.PartialAny
158
+ >
159
+
160
+ export type DeleteMutationFn<
161
+ TColumns extends SqliteDsl.ConstraintColumns,
162
+ _TOptions extends DbSchema.TableOptions,
163
+ > = (args: { where: Partial<SqliteDsl.FromColumns.RowDecoded<TColumns>> }) => MutationEvent.PartialAny
164
+
165
+ type UseShortcut<TOptions extends DbSchema.TableOptions> = TOptions['isSingleColumn'] extends true
166
+ ? TOptions['isSingleton'] extends true
167
+ ? true
168
+ : false
169
+ : false
170
+ }
@@ -0,0 +1,13 @@
1
+ import type { Effect, PubSub } from '@livestore/utils/effect'
2
+
3
+ import type * as Devtools from './devtools-messages.js'
4
+
5
+ export type PrepareDevtoolsBridge = {
6
+ /** Messages coming from the app host (usually responses to requests) */
7
+ responsePubSub: PubSub.PubSub<Devtools.MessageFromAppHostCoordinator | Devtools.MessageFromAppHostStore>
8
+ sendToAppHost: (msg: Devtools.MessageToAppHostCoordinator | Devtools.MessageToAppHostStore) => Effect.Effect<void>
9
+ appHostId: string
10
+ copyToClipboard: (text: string) => Effect.Effect<void>
11
+ sendEscapeKey?: Effect.Effect<void>
12
+ isLeader: boolean
13
+ }