@livestore/common 0.3.0-dev.34 → 0.3.0-dev.37

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 (44) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/devtools/devtools-messages-client-session.d.ts +21 -21
  3. package/dist/devtools/devtools-messages-common.d.ts +6 -6
  4. package/dist/devtools/devtools-messages-leader.d.ts +24 -24
  5. package/dist/devtools/devtools-sessioninfo.d.ts +2 -0
  6. package/dist/devtools/devtools-sessioninfo.d.ts.map +1 -1
  7. package/dist/devtools/devtools-sessioninfo.js +1 -0
  8. package/dist/devtools/devtools-sessioninfo.js.map +1 -1
  9. package/dist/leader-thread/LeaderSyncProcessor.js +10 -10
  10. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  11. package/dist/leader-thread/materialize-event.d.ts.map +1 -1
  12. package/dist/leader-thread/materialize-event.js +3 -1
  13. package/dist/leader-thread/materialize-event.js.map +1 -1
  14. package/dist/materializer-helper.d.ts +5 -5
  15. package/dist/materializer-helper.d.ts.map +1 -1
  16. package/dist/materializer-helper.js +16 -2
  17. package/dist/materializer-helper.js.map +1 -1
  18. package/dist/schema/EventDef.d.ts +11 -1
  19. package/dist/schema/EventDef.d.ts.map +1 -1
  20. package/dist/schema/EventDef.js.map +1 -1
  21. package/dist/schema/schema.d.ts +13 -0
  22. package/dist/schema/schema.d.ts.map +1 -1
  23. package/dist/schema/schema.js +3 -0
  24. package/dist/schema/schema.js.map +1 -1
  25. package/dist/schema/state/sqlite/client-document-def.test.js +5 -5
  26. package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
  27. package/dist/schema/state/sqlite/query-builder/api.d.ts +4 -3
  28. package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
  29. package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
  30. package/dist/schema/state/sqlite/query-builder/impl.js +2 -2
  31. package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
  32. package/dist/version.d.ts +1 -1
  33. package/dist/version.js +1 -1
  34. package/package.json +3 -3
  35. package/src/devtools/devtools-sessioninfo.ts +1 -0
  36. package/src/leader-thread/LeaderSyncProcessor.ts +10 -10
  37. package/src/leader-thread/materialize-event.ts +3 -1
  38. package/src/materializer-helper.ts +33 -9
  39. package/src/schema/EventDef.ts +12 -1
  40. package/src/schema/schema.ts +16 -0
  41. package/src/schema/state/sqlite/client-document-def.test.ts +5 -6
  42. package/src/schema/state/sqlite/query-builder/api.ts +4 -3
  43. package/src/schema/state/sqlite/query-builder/impl.ts +3 -2
  44. package/src/version.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livestore/common",
3
- "version": "0.3.0-dev.34",
3
+ "version": "0.3.0-dev.37",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -54,11 +54,11 @@
54
54
  "graphology": "0.26.0-alpha1",
55
55
  "graphology-dag": "0.4.1",
56
56
  "graphology-types": "0.24.8",
57
- "@livestore/utils": "0.3.0-dev.34"
57
+ "@livestore/utils": "0.3.0-dev.37"
58
58
  },
59
59
  "devDependencies": {
60
60
  "vitest": "^3.1.1",
61
- "@livestore/utils-dev": "0.3.0-dev.34"
61
+ "@livestore/utils-dev": "0.3.0-dev.37"
62
62
  },
63
63
  "files": [
64
64
  "package.json",
@@ -19,6 +19,7 @@ export const SessionInfo = Schema.TaggedStruct('SessionInfo', {
19
19
  storeId: Schema.String,
20
20
  clientId: Schema.String,
21
21
  sessionId: Schema.String,
22
+ schemaAlias: Schema.String,
22
23
  })
23
24
  export type SessionInfo = typeof SessionInfo.Type
24
25
 
@@ -107,8 +107,8 @@ export const makeLeaderSyncProcessor = ({
107
107
  const syncStateSref = yield* SubscriptionRef.make<SyncState.SyncState | undefined>(undefined)
108
108
 
109
109
  const isClientEvent = (eventEncoded: LiveStoreEvent.EncodedWithMeta) => {
110
- const eventDef = getEventDef(schema, eventEncoded.name)
111
- return eventDef.eventDef.options.clientOnly
110
+ const { eventDef } = getEventDef(schema, eventEncoded.name)
111
+ return eventDef.options.clientOnly
112
112
  }
113
113
 
114
114
  const connectedClientSessionPullQueues = yield* makePullQueueSet
@@ -196,14 +196,14 @@ export const makeLeaderSyncProcessor = ({
196
196
  const syncState = yield* syncStateSref
197
197
  if (syncState === undefined) return shouldNeverHappen('Not initialized')
198
198
 
199
- const eventDef = getEventDef(schema, name)
199
+ const { eventDef } = getEventDef(schema, name)
200
200
 
201
201
  const eventEncoded = new LiveStoreEvent.EncodedWithMeta({
202
202
  name,
203
203
  args,
204
204
  clientId,
205
205
  sessionId,
206
- ...EventId.nextPair(syncState.localHead, eventDef.eventDef.options.clientOnly),
206
+ ...EventId.nextPair(syncState.localHead, eventDef.options.clientOnly),
207
207
  })
208
208
 
209
209
  yield* push([eventEncoded])
@@ -251,8 +251,8 @@ export const makeLeaderSyncProcessor = ({
251
251
  const globalPendingEvents = pendingEvents
252
252
  // Don't sync clientOnly events
253
253
  .filter((eventEncoded) => {
254
- const eventDef = getEventDef(schema, eventEncoded.name)
255
- return eventDef.eventDef.options.clientOnly === false
254
+ const { eventDef } = getEventDef(schema, eventEncoded.name)
255
+ return eventDef.options.clientOnly === false
256
256
  })
257
257
 
258
258
  if (globalPendingEvents.length > 0) {
@@ -540,8 +540,8 @@ const backgroundApplyLocalPushes = ({
540
540
 
541
541
  // Don't sync clientOnly events
542
542
  const filteredBatch = mergeResult.newEvents.filter((eventEncoded) => {
543
- const eventDef = getEventDef(schema, eventEncoded.name)
544
- return eventDef.eventDef.options.clientOnly === false
543
+ const { eventDef } = getEventDef(schema, eventEncoded.name)
544
+ return eventDef.options.clientOnly === false
545
545
  })
546
546
 
547
547
  yield* BucketQueue.offerAll(syncBackendPushQueue, filteredBatch)
@@ -688,8 +688,8 @@ const backgroundBackendPulling = ({
688
688
  })
689
689
 
690
690
  const globalRebasedPendingEvents = mergeResult.newSyncState.pending.filter((event) => {
691
- const eventDef = getEventDef(schema, event.name)
692
- return eventDef.eventDef.options.clientOnly === false
691
+ const { eventDef } = getEventDef(schema, event.name)
692
+ return eventDef.options.clientOnly === false
693
693
  })
694
694
  yield* restartBackendPushing(globalRebasedPendingEvents)
695
695
 
@@ -33,10 +33,12 @@ export const makeMaterializeEvent = ({
33
33
  const skipEventlog = options?.skipEventlog ?? false
34
34
 
35
35
  const eventName = eventEncoded.name
36
- const eventDef = getEventDef(schema, eventName)
36
+ const { eventDef, materializer } = getEventDef(schema, eventName)
37
37
 
38
38
  const execArgsArr = getExecArgsFromEvent({
39
39
  eventDef,
40
+ materializer,
41
+ db,
40
42
  event: { decoded: undefined, encoded: eventEncoded },
41
43
  })
42
44
 
@@ -1,22 +1,26 @@
1
1
  import { isReadonlyArray } from '@livestore/utils'
2
2
  import { Schema } from '@livestore/utils/effect'
3
3
 
4
+ import type { SqliteDb } from './adapter-types.js'
4
5
  import { SessionIdSymbol } from './adapter-types.js'
5
- import type { EventDef, Materializer, MaterializerResult } from './schema/EventDef.js'
6
+ import type { EventDef, Materializer, MaterializerContextQuery, MaterializerResult } from './schema/EventDef.js'
6
7
  import type * as LiveStoreEvent from './schema/LiveStoreEvent.js'
8
+ import type { QueryBuilder } from './schema/state/sqlite/query-builder/api.js'
7
9
  import { isQueryBuilder } from './schema/state/sqlite/query-builder/api.js'
8
- import type { BindValues } from './sql-queries/sql-queries.js'
9
- import type { PreparedBindValues } from './util.js'
10
+ import { getResultSchema } from './schema/state/sqlite/query-builder/impl.js'
11
+ import { type BindValues } from './sql-queries/sql-queries.js'
12
+ import type { ParamsObject, PreparedBindValues } from './util.js'
10
13
  import { prepareBindValues } from './util.js'
11
14
 
12
15
  export const getExecArgsFromEvent = ({
13
- eventDef: { eventDef, materializer },
16
+ eventDef,
17
+ materializer,
18
+ db,
14
19
  event,
15
20
  }: {
16
- eventDef: {
17
- eventDef: EventDef.AnyWithoutFn
18
- materializer: Materializer
19
- }
21
+ eventDef: EventDef.AnyWithoutFn
22
+ materializer: Materializer
23
+ db: SqliteDb
20
24
  /** Both encoded and decoded events are supported to reduce the number of times we need to decode/encode */
21
25
  event:
22
26
  | {
@@ -34,8 +38,28 @@ export const getExecArgsFromEvent = ({
34
38
  }> => {
35
39
  const eventArgsDecoded = event.decoded?.args ?? Schema.decodeUnknownSync(eventDef.schema)(event.encoded!.args)
36
40
 
41
+ const query: MaterializerContextQuery = (
42
+ rawQueryOrQueryBuilder:
43
+ | {
44
+ query: string
45
+ bindValues: ParamsObject
46
+ }
47
+ | QueryBuilder.Any,
48
+ ) => {
49
+ if (isQueryBuilder(rawQueryOrQueryBuilder)) {
50
+ const { query, bindValues } = rawQueryOrQueryBuilder.asSql()
51
+ const rawResults = db.select(query, prepareBindValues(bindValues, query))
52
+ const resultSchema = getResultSchema(rawQueryOrQueryBuilder)
53
+ return Schema.decodeSync(resultSchema)(rawResults)
54
+ } else {
55
+ const { query, bindValues } = rawQueryOrQueryBuilder
56
+ return db.select(query, prepareBindValues(bindValues, query))
57
+ }
58
+ }
59
+
37
60
  const res = materializer(eventArgsDecoded, {
38
- clientOnly: eventDef.options.clientOnly,
61
+ eventDef,
62
+ query,
39
63
  // TODO properly implement this
40
64
  currentFacts: new Map(),
41
65
  })
@@ -3,6 +3,7 @@ import { shouldNeverHappen } from '@livestore/utils'
3
3
  import { Schema } from '@livestore/utils/effect'
4
4
 
5
5
  import type { BindValues } from '../sql-queries/sql-queries.js'
6
+ import type { ParamsObject } from '../util.js'
6
7
  import type { QueryBuilder } from './state/sqlite/query-builder/mod.js'
7
8
 
8
9
  export type EventDefMap = {
@@ -167,9 +168,19 @@ export type MaterializerResult =
167
168
  | QueryBuilder.Any
168
169
  | string
169
170
 
171
+ export type MaterializerContextQuery = {
172
+ (args: { query: string; bindValues: ParamsObject }): ReadonlyArray<unknown>
173
+ <TResult>(qb: QueryBuilder<TResult, any, any>): TResult
174
+ }
175
+
170
176
  export type Materializer<TEventDef extends EventDef.AnyWithoutFn = EventDef.AnyWithoutFn> = (
171
177
  event: TEventDef['schema']['Type'],
172
- context: { currentFacts: EventDefFacts; clientOnly: boolean },
178
+ context: {
179
+ currentFacts: EventDefFacts
180
+ eventDef: TEventDef
181
+ /** Can be used to query the current state */
182
+ query: MaterializerContextQuery
183
+ },
173
184
  ) => SingleOrReadonlyArray<MaterializerResult>
174
185
 
175
186
  export const defineMaterializer = <TEventDef extends EventDef.AnyWithoutFn>(
@@ -23,6 +23,10 @@ export type LiveStoreSchema<
23
23
 
24
24
  readonly state: State
25
25
  readonly eventsDefsMap: Map<string, EventDef.AnyWithoutFn>
26
+ readonly devtools: {
27
+ /** @default 'default' */
28
+ readonly alias: string
29
+ }
26
30
  }
27
31
 
28
32
  // TODO abstract this further away from sqlite/tables
@@ -39,6 +43,15 @@ export type State = {
39
43
  export type InputSchema = {
40
44
  readonly events: ReadonlyArray<EventDef.AnyWithoutFn> | Record<string, EventDef.AnyWithoutFn>
41
45
  readonly state: State
46
+ readonly devtools?: {
47
+ /**
48
+ * This alias value is used to disambiguate between multiple schemas in the devtools.
49
+ * Only needed when an app uses multiple schemas.
50
+ *
51
+ * @default 'default'
52
+ */
53
+ readonly alias?: string
54
+ }
42
55
  }
43
56
 
44
57
  export const makeSchema = <TInputSchema extends InputSchema>(
@@ -81,6 +94,9 @@ export const makeSchema = <TInputSchema extends InputSchema>(
81
94
  _EventDefMapType: Symbol.for('livestore.EventDefMapType') as any,
82
95
  state,
83
96
  eventsDefsMap,
97
+ devtools: {
98
+ alias: inputSchema.devtools?.alias ?? 'default',
99
+ },
84
100
  } satisfies LiveStoreSchema
85
101
  }
86
102
 
@@ -5,11 +5,6 @@ import { tables } from '../../../__tests__/fixture.js'
5
5
  import type * as LiveStoreEvent from '../../LiveStoreEvent.js'
6
6
  import { clientDocument, ClientDocumentTableDefSymbol } from './client-document-def.js'
7
7
 
8
- const materializerContext = {
9
- currentFacts: new Map(),
10
- clientOnly: false,
11
- }
12
-
13
8
  describe('client document table', () => {
14
9
  test('set event', () => {
15
10
  expect(patchId(tables.UiState.set({ showSidebar: false }, 'session-1'))).toMatchInlineSnapshot(`
@@ -51,7 +46,11 @@ describe('client document table', () => {
51
46
 
52
47
  const materializer = Doc[ClientDocumentTableDefSymbol].derived.setMaterializer
53
48
 
54
- return materializer(Doc.set(value, id as any).args, materializerContext)
49
+ return materializer(Doc.set(value, id as any).args, {
50
+ currentFacts: new Map(),
51
+ query: {} as any, // unused
52
+ eventDef: Doc[ClientDocumentTableDefSymbol].derived.setEventDef,
53
+ })
55
54
  }
56
55
 
57
56
  test('string value', () => {
@@ -19,7 +19,7 @@ export namespace QueryBuilderAst {
19
19
  export interface SelectQuery {
20
20
  readonly _tag: 'SelectQuery'
21
21
  readonly columns: string[]
22
- readonly pickFirst: false | { fallback: () => any } | 'no-fallback'
22
+ readonly pickFirst: false | { fallback: () => any } | 'throws'
23
23
  readonly select: {
24
24
  columns: ReadonlyArray<string>
25
25
  }
@@ -288,10 +288,11 @@ export namespace QueryBuilder {
288
288
  * db.todos.where('id', '123').first()
289
289
  * ```
290
290
  *
291
- * Query will fail if no rows are returned and no fallback is provided.
291
+ * Query will throw if no rows are returned and no fallback is provided.
292
292
  */
293
293
  readonly first: <TFallback = never>(options?: {
294
- fallback?: () => TFallback | GetSingle<TResult>
294
+ /** @default 'throws' */
295
+ fallback?: (() => TFallback | GetSingle<TResult>) | 'throws'
295
296
  }) => QueryBuilder<
296
297
  TFallback | GetSingle<TResult>,
297
298
  TTableDef,
@@ -143,7 +143,8 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
143
143
  return makeQueryBuilder(tableDef, {
144
144
  ...ast,
145
145
  limit: Option.some(1),
146
- pickFirst: options?.fallback ? { fallback: options.fallback } : 'no-fallback',
146
+ pickFirst:
147
+ options?.fallback !== undefined && options.fallback !== 'throws' ? { fallback: options.fallback } : 'throws',
147
148
  })
148
149
  },
149
150
  // // eslint-disable-next-line prefer-arrow/prefer-arrow-functions
@@ -309,7 +310,7 @@ export const getResultSchema = (qb: QueryBuilder<any, any, any>): Schema.Schema<
309
310
  const arraySchema = Schema.Array(queryAst.resultSchemaSingle)
310
311
  if (queryAst.pickFirst === false) {
311
312
  return arraySchema
312
- } else if (queryAst.pickFirst === 'no-fallback') {
313
+ } else if (queryAst.pickFirst === 'throws') {
313
314
  // Will throw if the array is empty
314
315
  return arraySchema.pipe(Schema.headOrElse())
315
316
  } else {
package/src/version.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // import packageJson from '../package.json' with { type: 'json' }
3
3
  // export const liveStoreVersion = packageJson.version
4
4
 
5
- export const liveStoreVersion = '0.3.0-dev.34' as const
5
+ export const liveStoreVersion = '0.3.0-dev.37' as const
6
6
 
7
7
  /**
8
8
  * This version number is incremented whenever the internal storage format changes in a breaking way.