@livestore/common 0.0.0-snapshot-83c6b3d6e39244b59235009057fd5c48f6c3103f → 0.0.0-snapshot-2b8a9de3ec1a701aca891ebc2c98eb328274ae9e

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 (96) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/adapter-types.d.ts +4 -2
  3. package/dist/adapter-types.d.ts.map +1 -1
  4. package/dist/adapter-types.js +1 -1
  5. package/dist/adapter-types.js.map +1 -1
  6. package/dist/devtools/devtools-messages-client-session.d.ts +21 -21
  7. package/dist/devtools/devtools-messages-common.d.ts +6 -6
  8. package/dist/devtools/devtools-messages-leader.d.ts +24 -24
  9. package/dist/leader-thread/LeaderSyncProcessor.d.ts +2 -1
  10. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  11. package/dist/leader-thread/LeaderSyncProcessor.js +42 -38
  12. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  13. package/dist/leader-thread/leader-worker-devtools.js +1 -1
  14. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  15. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  16. package/dist/leader-thread/make-leader-thread-layer.js +1 -0
  17. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  18. package/dist/leader-thread/mutationlog.d.ts +1 -0
  19. package/dist/leader-thread/mutationlog.d.ts.map +1 -1
  20. package/dist/leader-thread/mutationlog.js +1 -0
  21. package/dist/leader-thread/mutationlog.js.map +1 -1
  22. package/dist/mutation.d.ts.map +1 -1
  23. package/dist/mutation.js +13 -2
  24. package/dist/mutation.js.map +1 -1
  25. package/dist/query-builder/api.d.ts +118 -20
  26. package/dist/query-builder/api.d.ts.map +1 -1
  27. package/dist/query-builder/api.js.map +1 -1
  28. package/dist/query-builder/astToSql.d.ts +7 -0
  29. package/dist/query-builder/astToSql.d.ts.map +1 -0
  30. package/dist/query-builder/astToSql.js +168 -0
  31. package/dist/query-builder/astToSql.js.map +1 -0
  32. package/dist/query-builder/impl.d.ts +1 -5
  33. package/dist/query-builder/impl.d.ts.map +1 -1
  34. package/dist/query-builder/impl.js +130 -96
  35. package/dist/query-builder/impl.js.map +1 -1
  36. package/dist/query-builder/impl.test.js +94 -0
  37. package/dist/query-builder/impl.test.js.map +1 -1
  38. package/dist/query-builder/mod.d.ts +7 -0
  39. package/dist/query-builder/mod.d.ts.map +1 -1
  40. package/dist/query-builder/mod.js +7 -0
  41. package/dist/query-builder/mod.js.map +1 -1
  42. package/dist/query-info.d.ts +4 -1
  43. package/dist/query-info.d.ts.map +1 -1
  44. package/dist/query-info.js.map +1 -1
  45. package/dist/schema/MutationEvent.d.ts +17 -1
  46. package/dist/schema/MutationEvent.d.ts.map +1 -1
  47. package/dist/schema/MutationEvent.js +18 -2
  48. package/dist/schema/MutationEvent.js.map +1 -1
  49. package/dist/schema/db-schema/dsl/mod.d.ts +7 -5
  50. package/dist/schema/db-schema/dsl/mod.d.ts.map +1 -1
  51. package/dist/schema/db-schema/dsl/mod.js +6 -0
  52. package/dist/schema/db-schema/dsl/mod.js.map +1 -1
  53. package/dist/schema/mutations.d.ts +11 -2
  54. package/dist/schema/mutations.d.ts.map +1 -1
  55. package/dist/schema/mutations.js.map +1 -1
  56. package/dist/schema/table-def.d.ts +7 -3
  57. package/dist/schema/table-def.d.ts.map +1 -1
  58. package/dist/schema/table-def.js +7 -1
  59. package/dist/schema/table-def.js.map +1 -1
  60. package/dist/sync/ClientSessionSyncProcessor.d.ts +2 -0
  61. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  62. package/dist/sync/ClientSessionSyncProcessor.js +36 -33
  63. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  64. package/dist/sync/sync.d.ts +17 -0
  65. package/dist/sync/sync.d.ts.map +1 -1
  66. package/dist/sync/sync.js.map +1 -1
  67. package/dist/sync/syncstate.d.ts +38 -16
  68. package/dist/sync/syncstate.d.ts.map +1 -1
  69. package/dist/sync/syncstate.js +110 -40
  70. package/dist/sync/syncstate.js.map +1 -1
  71. package/dist/sync/syncstate.test.js +60 -29
  72. package/dist/sync/syncstate.test.js.map +1 -1
  73. package/dist/version.d.ts +1 -1
  74. package/dist/version.js +1 -1
  75. package/package.json +2 -2
  76. package/src/adapter-types.ts +4 -2
  77. package/src/leader-thread/LeaderSyncProcessor.ts +45 -39
  78. package/src/leader-thread/leader-worker-devtools.ts +1 -1
  79. package/src/leader-thread/make-leader-thread-layer.ts +1 -0
  80. package/src/leader-thread/mutationlog.ts +1 -0
  81. package/src/mutation.ts +20 -3
  82. package/src/query-builder/api.ts +192 -15
  83. package/src/query-builder/astToSql.ts +203 -0
  84. package/src/query-builder/impl.test.ts +104 -0
  85. package/src/query-builder/impl.ts +157 -113
  86. package/src/query-builder/mod.ts +7 -0
  87. package/src/query-info.ts +6 -1
  88. package/src/schema/MutationEvent.ts +18 -2
  89. package/src/schema/db-schema/dsl/mod.ts +30 -2
  90. package/src/schema/mutations.ts +12 -1
  91. package/src/schema/table-def.ts +14 -4
  92. package/src/sync/ClientSessionSyncProcessor.ts +39 -33
  93. package/src/sync/sync.ts +14 -0
  94. package/src/sync/syncstate.test.ts +72 -38
  95. package/src/sync/syncstate.ts +138 -58
  96. package/src/version.ts +1 -1
@@ -74,6 +74,7 @@ export const makeLeaderSyncProcessor = ({
74
74
  dbMutationLog,
75
75
  clientId,
76
76
  initialBlockingSyncContext,
77
+ onError,
77
78
  }: {
78
79
  schema: LiveStoreSchema
79
80
  /** Only used to know whether we can safely query dbMutationLog during setup execution */
@@ -81,13 +82,14 @@ export const makeLeaderSyncProcessor = ({
81
82
  dbMutationLog: SqliteDb
82
83
  clientId: string
83
84
  initialBlockingSyncContext: InitialBlockingSyncContext
85
+ onError: 'shutdown' | 'ignore'
84
86
  }): Effect.Effect<LeaderSyncProcessor, UnexpectedError, Scope.Scope> =>
85
87
  Effect.gen(function* () {
86
88
  const syncBackendQueue = yield* BucketQueue.make<MutationEvent.EncodedWithMeta>()
87
89
 
88
90
  const syncStateSref = yield* SubscriptionRef.make<SyncState.SyncState | undefined>(undefined)
89
91
 
90
- const isLocalEvent = (mutationEventEncoded: MutationEvent.EncodedWithMeta) => {
92
+ const isClientEvent = (mutationEventEncoded: MutationEvent.EncodedWithMeta) => {
91
93
  const mutationDef = getMutationDef(schema, mutationEventEncoded.mutation)
92
94
  return mutationDef.options.clientOnly
93
95
  }
@@ -234,8 +236,10 @@ export const makeLeaderSyncProcessor = ({
234
236
 
235
237
  const shutdownOnError = (cause: unknown) =>
236
238
  Effect.gen(function* () {
237
- yield* shutdownChannel.send(UnexpectedError.make({ cause }))
238
- yield* Effect.die(cause)
239
+ if (onError === 'shutdown') {
240
+ yield* shutdownChannel.send(UnexpectedError.make({ cause }))
241
+ yield* Effect.die(cause)
242
+ }
239
243
  })
240
244
 
241
245
  yield* backgroundApplyLocalPushes({
@@ -245,7 +249,7 @@ export const makeLeaderSyncProcessor = ({
245
249
  syncStateSref,
246
250
  syncBackendQueue,
247
251
  schema,
248
- isLocalEvent,
252
+ isClientEvent,
249
253
  otelSpan,
250
254
  currentLocalPushGenerationRef,
251
255
  }).pipe(Effect.tapCauseLogPretty, Effect.catchAllCause(shutdownOnError), Effect.forkScoped)
@@ -265,7 +269,7 @@ export const makeLeaderSyncProcessor = ({
265
269
  yield* backgroundBackendPulling({
266
270
  dbReady,
267
271
  initialBackendHead,
268
- isLocalEvent,
272
+ isClientEvent,
269
273
  restartBackendPushing: (filteredRebasedPending) =>
270
274
  Effect.gen(function* () {
271
275
  // Stop current pushing fiber
@@ -319,7 +323,7 @@ const backgroundApplyLocalPushes = ({
319
323
  syncStateSref,
320
324
  syncBackendQueue,
321
325
  schema,
322
- isLocalEvent,
326
+ isClientEvent,
323
327
  otelSpan,
324
328
  currentLocalPushGenerationRef,
325
329
  }: {
@@ -329,7 +333,7 @@ const backgroundApplyLocalPushes = ({
329
333
  syncStateSref: SubscriptionRef.SubscriptionRef<SyncState.SyncState | undefined>
330
334
  syncBackendQueue: BucketQueue.BucketQueue<MutationEvent.EncodedWithMeta>
331
335
  schema: LiveStoreSchema
332
- isLocalEvent: (mutationEventEncoded: MutationEvent.EncodedWithMeta) => boolean
336
+ isClientEvent: (mutationEventEncoded: MutationEvent.EncodedWithMeta) => boolean
333
337
  otelSpan: otel.Span | undefined
334
338
  currentLocalPushGenerationRef: { current: number }
335
339
  }) =>
@@ -366,20 +370,20 @@ const backgroundApplyLocalPushes = ({
366
370
  const syncState = yield* syncStateSref
367
371
  if (syncState === undefined) return shouldNeverHappen('Not initialized')
368
372
 
369
- const updateResult = SyncState.updateSyncState({
373
+ const mergeResult = SyncState.merge({
370
374
  syncState,
371
375
  payload: { _tag: 'local-push', newEvents },
372
- isLocalEvent,
376
+ isClientEvent,
373
377
  isEqualEvent: MutationEvent.isEqualEncoded,
374
378
  })
375
379
 
376
- switch (updateResult._tag) {
380
+ switch (mergeResult._tag) {
377
381
  case 'unexpected-error': {
378
382
  otelSpan?.addEvent('local-push:unexpected-error', {
379
383
  batchSize: newEvents.length,
380
384
  newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
381
385
  })
382
- return yield* Effect.fail(updateResult.cause)
386
+ return yield* Effect.fail(mergeResult.cause)
383
387
  }
384
388
  case 'rebase': {
385
389
  return shouldNeverHappen('The leader thread should never have to rebase due to a local push')
@@ -387,7 +391,7 @@ const backgroundApplyLocalPushes = ({
387
391
  case 'reject': {
388
392
  otelSpan?.addEvent('local-push:reject', {
389
393
  batchSize: newEvents.length,
390
- updateResult: TRACE_VERBOSE ? JSON.stringify(updateResult) : undefined,
394
+ mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
391
395
  })
392
396
 
393
397
  /*
@@ -421,7 +425,7 @@ const backgroundApplyLocalPushes = ({
421
425
  Deferred.fail(
422
426
  deferred,
423
427
  LeaderAheadError.make({
424
- minimumExpectedId: updateResult.expectedMinimumId,
428
+ minimumExpectedId: mergeResult.expectedMinimumId,
425
429
  providedId,
426
430
  // nextGeneration,
427
431
  }),
@@ -439,28 +443,28 @@ const backgroundApplyLocalPushes = ({
439
443
  break
440
444
  }
441
445
  default: {
442
- casesHandled(updateResult)
446
+ casesHandled(mergeResult)
443
447
  }
444
448
  }
445
449
 
446
- yield* SubscriptionRef.set(syncStateSref, updateResult.newSyncState)
450
+ yield* SubscriptionRef.set(syncStateSref, mergeResult.newSyncState)
447
451
 
448
452
  if (clientId === 'client-b') {
449
453
  // yield* Effect.log('offer upstream-advance due to local-push')
450
454
  // debugger
451
455
  }
452
456
  yield* connectedClientSessionPullQueues.offer({
453
- payload: { _tag: 'upstream-advance', newEvents: updateResult.newEvents },
457
+ payload: { _tag: 'upstream-advance', newEvents: mergeResult.newEvents },
454
458
  remaining: 0,
455
459
  })
456
460
 
457
461
  otelSpan?.addEvent('local-push', {
458
462
  batchSize: newEvents.length,
459
- updateResult: TRACE_VERBOSE ? JSON.stringify(updateResult) : undefined,
463
+ mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
460
464
  })
461
465
 
462
466
  // Don't sync clientOnly mutations
463
- const filteredBatch = updateResult.newEvents.filter((mutationEventEncoded) => {
467
+ const filteredBatch = mergeResult.newEvents.filter((mutationEventEncoded) => {
464
468
  const mutationDef = getMutationDef(schema, mutationEventEncoded.mutation)
465
469
  return mutationDef.options.clientOnly === false
466
470
  })
@@ -527,7 +531,7 @@ const makeApplyMutationItems: Effect.Effect<ApplyMutationItems, UnexpectedError,
527
531
  const backgroundBackendPulling = ({
528
532
  dbReady,
529
533
  initialBackendHead,
530
- isLocalEvent,
534
+ isClientEvent,
531
535
  restartBackendPushing,
532
536
  otelSpan,
533
537
  syncStateSref,
@@ -538,7 +542,7 @@ const backgroundBackendPulling = ({
538
542
  }: {
539
543
  dbReady: Deferred.Deferred<void>
540
544
  initialBackendHead: EventId.GlobalEventId
541
- isLocalEvent: (mutationEventEncoded: MutationEvent.EncodedWithMeta) => boolean
545
+ isClientEvent: (mutationEventEncoded: MutationEvent.EncodedWithMeta) => boolean
542
546
  restartBackendPushing: (
543
547
  filteredRebasedPending: ReadonlyArray<MutationEvent.EncodedWithMeta>,
544
548
  ) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx | HttpClient.HttpClient>
@@ -584,51 +588,51 @@ const backgroundBackendPulling = ({
584
588
 
585
589
  const trimRollbackUntil = newEvents.at(-1)!.id
586
590
 
587
- const updateResult = SyncState.updateSyncState({
591
+ const mergeResult = SyncState.merge({
588
592
  syncState,
589
593
  payload: { _tag: 'upstream-advance', newEvents, trimRollbackUntil },
590
- isLocalEvent,
594
+ isClientEvent,
591
595
  isEqualEvent: MutationEvent.isEqualEncoded,
592
- ignoreLocalEvents: true,
596
+ ignoreClientEvents: true,
593
597
  })
594
598
 
595
- if (updateResult._tag === 'reject') {
599
+ if (mergeResult._tag === 'reject') {
596
600
  return shouldNeverHappen('The leader thread should never reject upstream advances')
597
- } else if (updateResult._tag === 'unexpected-error') {
601
+ } else if (mergeResult._tag === 'unexpected-error') {
598
602
  otelSpan?.addEvent('backend-pull:unexpected-error', {
599
603
  newEventsCount: newEvents.length,
600
604
  newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
601
605
  })
602
- return yield* Effect.fail(updateResult.cause)
606
+ return yield* Effect.fail(mergeResult.cause)
603
607
  }
604
608
 
605
609
  const newBackendHead = newEvents.at(-1)!.id
606
610
 
607
611
  updateBackendHead(dbMutationLog, newBackendHead)
608
612
 
609
- if (updateResult._tag === 'rebase') {
613
+ if (mergeResult._tag === 'rebase') {
610
614
  otelSpan?.addEvent('backend-pull:rebase', {
611
615
  newEventsCount: newEvents.length,
612
616
  newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
613
- rollbackCount: updateResult.eventsToRollback.length,
614
- updateResult: TRACE_VERBOSE ? JSON.stringify(updateResult) : undefined,
617
+ rollbackCount: mergeResult.eventsToRollback.length,
618
+ mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
615
619
  })
616
620
 
617
- const filteredRebasedPending = updateResult.newSyncState.pending.filter((mutationEvent) => {
621
+ const filteredRebasedPending = mergeResult.newSyncState.pending.filter((mutationEvent) => {
618
622
  const mutationDef = getMutationDef(schema, mutationEvent.mutation)
619
623
  return mutationDef.options.clientOnly === false
620
624
  })
621
625
  yield* restartBackendPushing(filteredRebasedPending)
622
626
 
623
- if (updateResult.eventsToRollback.length > 0) {
624
- yield* rollback({ db, dbMutationLog, eventIdsToRollback: updateResult.eventsToRollback.map((_) => _.id) })
627
+ if (mergeResult.eventsToRollback.length > 0) {
628
+ yield* rollback({ db, dbMutationLog, eventIdsToRollback: mergeResult.eventsToRollback.map((_) => _.id) })
625
629
  }
626
630
 
627
631
  yield* connectedClientSessionPullQueues.offer({
628
632
  payload: {
629
633
  _tag: 'upstream-rebase',
630
- newEvents: updateResult.newEvents,
631
- rollbackUntil: updateResult.eventsToRollback.at(0)!.id,
634
+ newEvents: mergeResult.newEvents,
635
+ rollbackUntil: mergeResult.eventsToRollback.at(0)!.id,
632
636
  trimRollbackUntil,
633
637
  },
634
638
  remaining,
@@ -636,23 +640,23 @@ const backgroundBackendPulling = ({
636
640
  } else {
637
641
  otelSpan?.addEvent('backend-pull:advance', {
638
642
  newEventsCount: newEvents.length,
639
- updateResult: TRACE_VERBOSE ? JSON.stringify(updateResult) : undefined,
643
+ mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
640
644
  })
641
645
 
642
646
  if (clientId === 'client-b') {
643
647
  // yield* Effect.log('offer upstream-advance due to pull')
644
648
  }
645
649
  yield* connectedClientSessionPullQueues.offer({
646
- payload: { _tag: 'upstream-advance', newEvents: updateResult.newEvents, trimRollbackUntil },
650
+ payload: { _tag: 'upstream-advance', newEvents: mergeResult.newEvents, trimRollbackUntil },
647
651
  remaining,
648
652
  })
649
653
  }
650
654
 
651
655
  trimChangesetRows(db, newBackendHead)
652
656
 
653
- yield* applyMutationItems({ batchItems: updateResult.newEvents, deferreds: undefined })
657
+ yield* applyMutationItems({ batchItems: mergeResult.newEvents, deferreds: undefined })
654
658
 
655
- yield* SubscriptionRef.set(syncStateSref, updateResult.newSyncState)
659
+ yield* SubscriptionRef.set(syncStateSref, mergeResult.newSyncState)
656
660
 
657
661
  if (remaining === 0) {
658
662
  // Allow local pushes to be processed again
@@ -707,7 +711,9 @@ const rollback = ({
707
711
  sql`SELECT * FROM ${SESSION_CHANGESET_META_TABLE} WHERE (idGlobal, idClient) IN (${eventIdsToRollback.map((id) => `(${id.global}, ${id.client})`).join(', ')})`,
708
712
  )
709
713
  .map((_) => ({ id: { global: _.idGlobal, client: _.idClient }, changeset: _.changeset, debug: _.debug }))
710
- .toSorted((a, b) => EventId.compare(a.id, b.id))
714
+ .sort((a, b) => EventId.compare(a.id, b.id))
715
+ // TODO bring back `.toSorted` once Expo supports it
716
+ // .toSorted((a, b) => EventId.compare(a.id, b.id))
711
717
 
712
718
  // Apply changesets in reverse order
713
719
  for (let i = rollbackEvents.length - 1; i >= 0; i--) {
@@ -278,7 +278,7 @@ const listenToDevtools = ({
278
278
  case 'LSD.Leader.SyncingInfoReq': {
279
279
  const syncingInfo = Devtools.Leader.SyncingInfo.make({
280
280
  enabled: syncBackend !== undefined,
281
- metadata: {},
281
+ metadata: syncBackend?.metadata ?? {},
282
282
  })
283
283
 
284
284
  yield* sendMessage(Devtools.Leader.SyncingInfoRes.make({ syncingInfo, ...reqPayload }))
@@ -71,6 +71,7 @@ export const makeLeaderThreadLayer = ({
71
71
  dbMutationLog,
72
72
  initialBlockingSyncContext,
73
73
  clientId,
74
+ onError: syncOptions?.onSyncError ?? 'ignore',
74
75
  })
75
76
 
76
77
  const extraIncomingMessagesQueue = yield* Queue.unbounded<Devtools.Leader.MessageToApp>().pipe(
@@ -7,6 +7,7 @@ import { MUTATION_LOG_META_TABLE, mutationLogMetaTable, SYNC_STATUS_TABLE } from
7
7
  import { prepareBindValues, sql } from '../util.js'
8
8
  import { LeaderThreadCtx } from './types.js'
9
9
 
10
+ /** Exclusive of the "since event" */
10
11
  export const getMutationEventsSince = (
11
12
  since: EventId.EventId,
12
13
  ): Effect.Effect<ReadonlyArray<MutationEvent.AnyEncoded>, never, LeaderThreadCtx> =>
package/src/mutation.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  import { Schema } from '@livestore/utils/effect'
2
2
 
3
3
  import { SessionIdSymbol } from './adapter-types.js'
4
+ import type { QueryBuilder } from './query-builder/api.js'
5
+ import { isQueryBuilder } from './query-builder/api.js'
4
6
  import type * as MutationEvent from './schema/MutationEvent.js'
5
- import type { MutationDef } from './schema/mutations.js'
7
+ import type { MutationDef, MutationHandlerResult } from './schema/mutations.js'
8
+ import type { BindValues } from './sql-queries/sql-queries.js'
6
9
  import type { PreparedBindValues } from './util.js'
7
10
  import { prepareBindValues } from './util.js'
8
11
 
@@ -34,8 +37,22 @@ export const getExecArgsFromMutation = ({
34
37
  case 'function': {
35
38
  const mutationArgsDecoded =
36
39
  mutationEvent.decoded?.args ?? Schema.decodeUnknownSync(mutationDef.schema)(mutationEvent.encoded!.args)
37
- const res = mutationDef.sql(mutationArgsDecoded)
38
- statementRes = Array.isArray(res) ? res : [res]
40
+
41
+ const res = mutationDef.sql(mutationArgsDecoded, {
42
+ clientOnly: mutationDef.options.clientOnly,
43
+ // TODO properly implement this
44
+ currentFacts: new Map(),
45
+ })
46
+
47
+ statementRes = (Array.isArray(res) ? res : [res]).map((_: QueryBuilder.Any | MutationHandlerResult) => {
48
+ if (isQueryBuilder(_)) {
49
+ const { query, bindValues } = _.asSql()
50
+ return { sql: query, bindValues: bindValues as BindValues }
51
+ }
52
+
53
+ return _
54
+ })
55
+
39
56
  break
40
57
  }
41
58
  case 'string': {
@@ -7,10 +7,16 @@ import type { SqliteDsl } from '../schema/db-schema/mod.js'
7
7
  import type { DbSchema } from '../schema/mod.js'
8
8
  import type { SqlValue } from '../util.js'
9
9
 
10
- export type QueryBuilderAst = QueryBuilderAst.SelectQuery | QueryBuilderAst.CountQuery | QueryBuilderAst.RowQuery
10
+ export type QueryBuilderAst =
11
+ | QueryBuilderAst.SelectQuery
12
+ | QueryBuilderAst.CountQuery
13
+ | QueryBuilderAst.RowQuery
14
+ | QueryBuilderAst.InsertQuery
15
+ | QueryBuilderAst.UpdateQuery
16
+ | QueryBuilderAst.DeleteQuery
11
17
 
12
18
  export namespace QueryBuilderAst {
13
- export type SelectQuery = {
19
+ export interface SelectQuery {
14
20
  readonly _tag: 'SelectQuery'
15
21
  readonly columns: string[]
16
22
  readonly pickFirst: false | { fallback: () => any }
@@ -25,27 +31,67 @@ export namespace QueryBuilderAst {
25
31
  readonly resultSchemaSingle: Schema.Schema<any>
26
32
  }
27
33
 
28
- export type CountQuery = {
34
+ export interface CountQuery {
29
35
  readonly _tag: 'CountQuery'
30
36
  readonly tableDef: DbSchema.TableDefBase
31
37
  readonly where: ReadonlyArray<QueryBuilderAst.Where>
32
38
  readonly resultSchema: Schema.Schema<number, ReadonlyArray<{ count: number }>>
33
39
  }
34
40
 
35
- export type RowQuery = {
41
+ export interface RowQuery {
36
42
  readonly _tag: 'RowQuery'
37
43
  readonly tableDef: DbSchema.TableDefBase
38
44
  readonly id: string | SessionIdSymbol | number
39
45
  readonly insertValues: Record<string, unknown>
40
46
  }
41
47
 
42
- export type Where = {
48
+ export interface InsertQuery {
49
+ readonly _tag: 'InsertQuery'
50
+ readonly tableDef: DbSchema.TableDefBase
51
+ readonly values: Record<string, unknown>
52
+ readonly onConflict: OnConflict | undefined
53
+ readonly returning: string[] | undefined
54
+ readonly resultSchema: Schema.Schema<any>
55
+ }
56
+
57
+ export interface OnConflict {
58
+ /** Conflicting column name */
59
+ readonly target: string
60
+ readonly action:
61
+ | { readonly _tag: 'ignore' }
62
+ | { readonly _tag: 'replace' }
63
+ | {
64
+ readonly _tag: 'update'
65
+ readonly update: Record<string, unknown>
66
+ }
67
+ }
68
+
69
+ export interface UpdateQuery {
70
+ readonly _tag: 'UpdateQuery'
71
+ readonly tableDef: DbSchema.TableDefBase
72
+ readonly values: Record<string, unknown>
73
+ readonly where: ReadonlyArray<QueryBuilderAst.Where>
74
+ readonly returning: string[] | undefined
75
+ readonly resultSchema: Schema.Schema<any>
76
+ }
77
+
78
+ export interface DeleteQuery {
79
+ readonly _tag: 'DeleteQuery'
80
+ readonly tableDef: DbSchema.TableDefBase
81
+ readonly where: ReadonlyArray<QueryBuilderAst.Where>
82
+ readonly returning: string[] | undefined
83
+ readonly resultSchema: Schema.Schema<any>
84
+ }
85
+
86
+ export type WriteQuery = InsertQuery | UpdateQuery | DeleteQuery
87
+
88
+ export interface Where {
43
89
  readonly col: string
44
90
  readonly op: QueryBuilder.WhereOps
45
91
  readonly value: unknown
46
92
  }
47
93
 
48
- export type OrderBy = {
94
+ export interface OrderBy {
49
95
  readonly col: string
50
96
  readonly direction: 'asc' | 'desc'
51
97
  }
@@ -86,7 +132,20 @@ export namespace QueryBuilder {
86
132
  export type MultiValue = In
87
133
  }
88
134
 
89
- export type ApiFeature = 'select' | 'where' | 'count' | 'orderBy' | 'offset' | 'limit' | 'first' | 'row'
135
+ export type ApiFeature =
136
+ | 'select'
137
+ | 'where'
138
+ | 'count'
139
+ | 'orderBy'
140
+ | 'offset'
141
+ | 'limit'
142
+ | 'first'
143
+ | 'row'
144
+ | 'insert'
145
+ | 'update'
146
+ | 'delete'
147
+ | 'returning'
148
+ | 'onConflict'
90
149
 
91
150
  export type WhereParams<TTableDef extends DbSchema.TableDefBase> = Partial<{
92
151
  [K in keyof TTableDef['sqliteDef']['columns']]:
@@ -130,7 +189,7 @@ export namespace QueryBuilder {
130
189
  readonly [K in TColumn]: TTableDef['sqliteDef']['columns'][K]['schema']['Type']
131
190
  }>,
132
191
  TTableDef,
133
- TWithout | 'row' | 'select',
192
+ TWithout | 'row' | 'select' | 'returning' | 'onConflict',
134
193
  TQueryInfo
135
194
  >
136
195
  <TColumns extends keyof TTableDef['sqliteDef']['columns'] & string>(
@@ -142,7 +201,7 @@ export namespace QueryBuilder {
142
201
  readonly [K in TColumns]: TTableDef['sqliteDef']['columns'][K]['schema']['Type']
143
202
  }>,
144
203
  TTableDef,
145
- TWithout | 'row' | 'select' | 'count',
204
+ TWithout | 'row' | 'select' | 'count' | 'returning' | 'onConflict',
146
205
  TQueryInfo
147
206
  >
148
207
  }
@@ -187,7 +246,7 @@ export namespace QueryBuilder {
187
246
  readonly count: () => QueryBuilder<
188
247
  number,
189
248
  TTableDef,
190
- TWithout | 'row' | 'count' | 'select' | 'orderBy' | 'first' | 'offset' | 'limit',
249
+ TWithout | 'row' | 'count' | 'select' | 'orderBy' | 'first' | 'offset' | 'limit' | 'returning' | 'onConflict',
191
250
  TQueryInfo
192
251
  >
193
252
 
@@ -201,10 +260,10 @@ export namespace QueryBuilder {
201
260
  <TColName extends keyof TTableDef['sqliteDef']['columns'] & string>(
202
261
  col: TColName,
203
262
  direction: 'asc' | 'desc',
204
- ): QueryBuilder<TResult, TTableDef, TWithout, TQueryInfo>
263
+ ): QueryBuilder<TResult, TTableDef, TWithout | 'returning' | 'onConflict', TQueryInfo>
205
264
  <TParams extends QueryBuilder.OrderByParams<TTableDef>>(
206
265
  params: TParams,
207
- ): QueryBuilder<TResult, TTableDef, TWithout, TQueryInfo>
266
+ ): QueryBuilder<TResult, TTableDef, TWithout | 'returning' | 'onConflict', TQueryInfo>
208
267
  }
209
268
 
210
269
  /**
@@ -215,7 +274,12 @@ export namespace QueryBuilder {
215
274
  */
216
275
  readonly offset: (
217
276
  offset: number,
218
- ) => QueryBuilder<TResult, TTableDef, TWithout | 'row' | 'offset' | 'orderBy', TQueryInfo>
277
+ ) => QueryBuilder<
278
+ TResult,
279
+ TTableDef,
280
+ TWithout | 'row' | 'offset' | 'orderBy' | 'returning' | 'onConflict',
281
+ TQueryInfo
282
+ >
219
283
 
220
284
  /**
221
285
  * Example:
@@ -225,7 +289,12 @@ export namespace QueryBuilder {
225
289
  */
226
290
  readonly limit: (
227
291
  limit: number,
228
- ) => QueryBuilder<TResult, TTableDef, TWithout | 'row' | 'limit' | 'offset' | 'first' | 'orderBy', TQueryInfo>
292
+ ) => QueryBuilder<
293
+ TResult,
294
+ TTableDef,
295
+ TWithout | 'row' | 'limit' | 'offset' | 'first' | 'orderBy' | 'returning' | 'onConflict',
296
+ TQueryInfo
297
+ >
229
298
 
230
299
  /**
231
300
  * Example:
@@ -238,7 +307,7 @@ export namespace QueryBuilder {
238
307
  }) => QueryBuilder<
239
308
  TFallback | GetSingle<TResult>,
240
309
  TTableDef,
241
- TWithout | 'row' | 'first' | 'orderBy' | 'select' | 'limit' | 'offset' | 'where',
310
+ TWithout | 'row' | 'first' | 'orderBy' | 'select' | 'limit' | 'offset' | 'where' | 'returning' | 'onConflict',
242
311
  TQueryInfo
243
312
  >
244
313
 
@@ -258,6 +327,114 @@ export namespace QueryBuilder {
258
327
  id: string | SessionIdSymbol | number,
259
328
  opts: TOptions,
260
329
  ) => QueryBuilder<RowQuery.Result<TTableDef>, TTableDef, QueryBuilder.ApiFeature, QueryInfo.Row>
330
+
331
+ /**
332
+ * Insert a new row into the table
333
+ *
334
+ * Example:
335
+ * ```ts
336
+ * db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' })
337
+ * ```
338
+ */
339
+ readonly insert: (
340
+ values: TTableDef['insertSchema']['Type'],
341
+ ) => QueryBuilder<
342
+ TResult,
343
+ TTableDef,
344
+ TWithout | 'row' | 'select' | 'count' | 'orderBy' | 'first' | 'offset' | 'limit' | 'where',
345
+ QueryInfo.Write
346
+ >
347
+
348
+ /**
349
+ * Example: If the row already exists, it will be ignored.
350
+ * ```ts
351
+ * db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'ignore')
352
+ * ```
353
+ *
354
+ * Example: If the row already exists, it will be replaced.
355
+ * ```ts
356
+ * db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'replace')
357
+ * ```
358
+ *
359
+ * Example: If the row already exists, it will be updated.
360
+ * ```ts
361
+ * db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'update', { text: 'Buy soy milk' })
362
+ * ```
363
+ *
364
+ * NOTE This API doesn't yet support composite primary keys.
365
+ */
366
+ readonly onConflict: {
367
+ (
368
+ target: string,
369
+ action: 'ignore' | 'replace',
370
+ ): QueryBuilder<
371
+ TResult,
372
+ TTableDef,
373
+ TWithout | 'row' | 'select' | 'count' | 'orderBy' | 'first' | 'offset' | 'limit' | 'where',
374
+ TQueryInfo
375
+ >
376
+ <TTarget extends keyof TTableDef['sqliteDef']['columns'] & string>(
377
+ target: TTarget,
378
+ action: 'update',
379
+ updateValues: Partial<TTableDef['schema']['Type']>,
380
+ ): QueryBuilder<
381
+ TResult,
382
+ TTableDef,
383
+ TWithout | 'row' | 'select' | 'count' | 'orderBy' | 'first' | 'offset' | 'limit' | 'where',
384
+ TQueryInfo
385
+ >
386
+ }
387
+
388
+ /**
389
+ * Similar to the `.select` API but for write queries (insert, update, delete).
390
+ *
391
+ * Example:
392
+ * ```ts
393
+ * db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).returning('id')
394
+ * ```
395
+ */
396
+ readonly returning: <TColumns extends keyof TTableDef['sqliteDef']['columns'] & string>(
397
+ ...columns: TColumns[]
398
+ ) => QueryBuilder<
399
+ ReadonlyArray<{
400
+ readonly [K in TColumns]: TTableDef['sqliteDef']['columns'][K]['schema']['Type']
401
+ }>,
402
+ TTableDef
403
+ >
404
+
405
+ /**
406
+ * Update rows in the table that match the where clause
407
+ *
408
+ * Example:
409
+ * ```ts
410
+ * db.todos.update({ status: 'completed' }).where({ id: '123' })
411
+ * ```
412
+ */
413
+ readonly update: (
414
+ values: Partial<TTableDef['schema']['Type']>,
415
+ ) => QueryBuilder<
416
+ TResult,
417
+ TTableDef,
418
+ TWithout | 'row' | 'select' | 'count' | 'orderBy' | 'first' | 'offset' | 'limit' | 'onConflict',
419
+ QueryInfo.Write
420
+ >
421
+
422
+ /**
423
+ * Delete rows from the table that match the where clause
424
+ *
425
+ * Example:
426
+ * ```ts
427
+ * db.todos.delete().where({ status: 'completed' })
428
+ * ```
429
+ *
430
+ * Note that it's generally recommended to do soft-deletes for synced apps.
431
+ */
432
+ readonly delete: () => QueryBuilder<
433
+ TResult,
434
+ TTableDef,
435
+ TWithout | 'row' | 'select' | 'count' | 'orderBy' | 'first' | 'offset' | 'limit' | 'onConflict',
436
+ QueryInfo.Write
437
+ >
261
438
  }
262
439
  }
263
440