@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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapter-types.d.ts +4 -2
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +1 -1
- package/dist/adapter-types.js.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +21 -21
- package/dist/devtools/devtools-messages-common.d.ts +6 -6
- package/dist/devtools/devtools-messages-leader.d.ts +24 -24
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +2 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +42 -38
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +1 -1
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +1 -0
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/mutationlog.d.ts +1 -0
- package/dist/leader-thread/mutationlog.d.ts.map +1 -1
- package/dist/leader-thread/mutationlog.js +1 -0
- package/dist/leader-thread/mutationlog.js.map +1 -1
- package/dist/mutation.d.ts.map +1 -1
- package/dist/mutation.js +13 -2
- package/dist/mutation.js.map +1 -1
- package/dist/query-builder/api.d.ts +118 -20
- package/dist/query-builder/api.d.ts.map +1 -1
- package/dist/query-builder/api.js.map +1 -1
- package/dist/query-builder/astToSql.d.ts +7 -0
- package/dist/query-builder/astToSql.d.ts.map +1 -0
- package/dist/query-builder/astToSql.js +168 -0
- package/dist/query-builder/astToSql.js.map +1 -0
- package/dist/query-builder/impl.d.ts +1 -5
- package/dist/query-builder/impl.d.ts.map +1 -1
- package/dist/query-builder/impl.js +130 -96
- package/dist/query-builder/impl.js.map +1 -1
- package/dist/query-builder/impl.test.js +94 -0
- package/dist/query-builder/impl.test.js.map +1 -1
- package/dist/query-builder/mod.d.ts +7 -0
- package/dist/query-builder/mod.d.ts.map +1 -1
- package/dist/query-builder/mod.js +7 -0
- package/dist/query-builder/mod.js.map +1 -1
- package/dist/query-info.d.ts +4 -1
- package/dist/query-info.d.ts.map +1 -1
- package/dist/query-info.js.map +1 -1
- package/dist/schema/MutationEvent.d.ts +17 -1
- package/dist/schema/MutationEvent.d.ts.map +1 -1
- package/dist/schema/MutationEvent.js +18 -2
- package/dist/schema/MutationEvent.js.map +1 -1
- package/dist/schema/db-schema/dsl/mod.d.ts +7 -5
- package/dist/schema/db-schema/dsl/mod.d.ts.map +1 -1
- package/dist/schema/db-schema/dsl/mod.js +6 -0
- package/dist/schema/db-schema/dsl/mod.js.map +1 -1
- package/dist/schema/mutations.d.ts +11 -2
- package/dist/schema/mutations.d.ts.map +1 -1
- package/dist/schema/mutations.js.map +1 -1
- package/dist/schema/table-def.d.ts +7 -3
- package/dist/schema/table-def.d.ts.map +1 -1
- package/dist/schema/table-def.js +7 -1
- package/dist/schema/table-def.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +2 -0
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +36 -33
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/sync.d.ts +17 -0
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/syncstate.d.ts +38 -16
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +110 -40
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +60 -29
- package/dist/sync/syncstate.test.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/src/adapter-types.ts +4 -2
- package/src/leader-thread/LeaderSyncProcessor.ts +45 -39
- package/src/leader-thread/leader-worker-devtools.ts +1 -1
- package/src/leader-thread/make-leader-thread-layer.ts +1 -0
- package/src/leader-thread/mutationlog.ts +1 -0
- package/src/mutation.ts +20 -3
- package/src/query-builder/api.ts +192 -15
- package/src/query-builder/astToSql.ts +203 -0
- package/src/query-builder/impl.test.ts +104 -0
- package/src/query-builder/impl.ts +157 -113
- package/src/query-builder/mod.ts +7 -0
- package/src/query-info.ts +6 -1
- package/src/schema/MutationEvent.ts +18 -2
- package/src/schema/db-schema/dsl/mod.ts +30 -2
- package/src/schema/mutations.ts +12 -1
- package/src/schema/table-def.ts +14 -4
- package/src/sync/ClientSessionSyncProcessor.ts +39 -33
- package/src/sync/sync.ts +14 -0
- package/src/sync/syncstate.test.ts +72 -38
- package/src/sync/syncstate.ts +138 -58
- 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
|
|
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
|
-
|
|
238
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
373
|
+
const mergeResult = SyncState.merge({
|
|
370
374
|
syncState,
|
|
371
375
|
payload: { _tag: 'local-push', newEvents },
|
|
372
|
-
|
|
376
|
+
isClientEvent,
|
|
373
377
|
isEqualEvent: MutationEvent.isEqualEncoded,
|
|
374
378
|
})
|
|
375
379
|
|
|
376
|
-
switch (
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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(
|
|
446
|
+
casesHandled(mergeResult)
|
|
443
447
|
}
|
|
444
448
|
}
|
|
445
449
|
|
|
446
|
-
yield* SubscriptionRef.set(syncStateSref,
|
|
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:
|
|
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
|
-
|
|
463
|
+
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
|
460
464
|
})
|
|
461
465
|
|
|
462
466
|
// Don't sync clientOnly mutations
|
|
463
|
-
const filteredBatch =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
591
|
+
const mergeResult = SyncState.merge({
|
|
588
592
|
syncState,
|
|
589
593
|
payload: { _tag: 'upstream-advance', newEvents, trimRollbackUntil },
|
|
590
|
-
|
|
594
|
+
isClientEvent,
|
|
591
595
|
isEqualEvent: MutationEvent.isEqualEncoded,
|
|
592
|
-
|
|
596
|
+
ignoreClientEvents: true,
|
|
593
597
|
})
|
|
594
598
|
|
|
595
|
-
if (
|
|
599
|
+
if (mergeResult._tag === 'reject') {
|
|
596
600
|
return shouldNeverHappen('The leader thread should never reject upstream advances')
|
|
597
|
-
} else if (
|
|
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(
|
|
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 (
|
|
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:
|
|
614
|
-
|
|
617
|
+
rollbackCount: mergeResult.eventsToRollback.length,
|
|
618
|
+
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
|
615
619
|
})
|
|
616
620
|
|
|
617
|
-
const filteredRebasedPending =
|
|
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 (
|
|
624
|
-
yield* rollback({ db, dbMutationLog, eventIdsToRollback:
|
|
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:
|
|
631
|
-
rollbackUntil:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
657
|
+
yield* applyMutationItems({ batchItems: mergeResult.newEvents, deferreds: undefined })
|
|
654
658
|
|
|
655
|
-
yield* SubscriptionRef.set(syncStateSref,
|
|
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
|
-
.
|
|
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 }))
|
|
@@ -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
|
-
|
|
38
|
-
|
|
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': {
|
package/src/query-builder/api.ts
CHANGED
|
@@ -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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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<
|
|
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<
|
|
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
|
|