@livestore/common 0.4.0-dev.0 → 0.4.0-dev.10
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/ClientSessionLeaderThreadProxy.d.ts +7 -2
- package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
- package/dist/ClientSessionLeaderThreadProxy.js.map +1 -1
- package/dist/adapter-types.d.ts +9 -3
- package/dist/adapter-types.d.ts.map +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 +7 -14
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-common.js +1 -6
- package/dist/devtools/devtools-messages-common.js.map +1 -1
- package/dist/devtools/devtools-messages-leader.d.ts +27 -25
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/errors.d.ts +47 -5
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +22 -3
- package/dist/errors.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +7 -3
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +122 -49
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/eventlog.d.ts +4 -10
- package/dist/leader-thread/eventlog.d.ts.map +1 -1
- package/dist/leader-thread/eventlog.js +4 -6
- package/dist/leader-thread/eventlog.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +6 -2
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +1 -2
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +68 -19
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.test.d.ts +2 -0
- package/dist/leader-thread/make-leader-thread-layer.test.d.ts.map +1 -0
- package/dist/leader-thread/make-leader-thread-layer.test.js +32 -0
- package/dist/leader-thread/make-leader-thread-layer.test.js.map +1 -0
- package/dist/leader-thread/materialize-event.d.ts +2 -2
- package/dist/leader-thread/materialize-event.d.ts.map +1 -1
- package/dist/leader-thread/materialize-event.js +23 -9
- package/dist/leader-thread/materialize-event.js.map +1 -1
- package/dist/leader-thread/recreate-db.d.ts +2 -3
- package/dist/leader-thread/recreate-db.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.js +1 -1
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/shutdown-channel.d.ts +2 -2
- package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
- package/dist/leader-thread/shutdown-channel.js +2 -2
- package/dist/leader-thread/shutdown-channel.js.map +1 -1
- package/dist/leader-thread/types.d.ts +7 -5
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/materializer-helper.d.ts +1 -1
- package/dist/materializer-helper.d.ts.map +1 -1
- package/dist/materializer-helper.js +20 -4
- package/dist/materializer-helper.js.map +1 -1
- package/dist/rematerialize-from-eventlog.d.ts +1 -1
- package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
- package/dist/rematerialize-from-eventlog.js +25 -16
- package/dist/rematerialize-from-eventlog.js.map +1 -1
- package/dist/schema/EventDef.d.ts +3 -0
- package/dist/schema/EventDef.d.ts.map +1 -1
- package/dist/schema/EventDef.js.map +1 -1
- package/dist/schema/LiveStoreEvent.d.ts +1 -1
- package/dist/schema/LiveStoreEvent.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent.js +1 -2
- package/dist/schema/LiveStoreEvent.js.map +1 -1
- package/dist/schema/mod.d.ts +2 -0
- package/dist/schema/mod.d.ts.map +1 -1
- package/dist/schema/mod.js +1 -0
- package/dist/schema/mod.js.map +1 -1
- package/dist/schema/schema.d.ts +15 -0
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +26 -1
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.d.ts +35 -5
- package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.js +95 -4
- package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.test.js +16 -0
- package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.js +14 -6
- package/dist/schema/state/sqlite/column-annotations.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.d.ts +19 -0
- package/dist/schema/state/sqlite/column-def.d.ts.map +1 -0
- package/dist/schema/state/sqlite/column-def.js +179 -0
- package/dist/schema/state/sqlite/column-def.js.map +1 -0
- package/dist/schema/state/sqlite/column-def.test.d.ts +2 -0
- package/dist/schema/state/sqlite/column-def.test.d.ts.map +1 -0
- package/dist/schema/state/sqlite/column-def.test.js +572 -0
- package/dist/schema/state/sqlite/column-def.test.js.map +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +2 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +23 -6
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js +2 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
- package/dist/schema/state/sqlite/mod.d.ts +1 -1
- package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/mod.js +1 -1
- package/dist/schema/state/sqlite/mod.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/api.d.ts +5 -2
- package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.js +6 -2
- package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.test.js +137 -2
- package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
- package/dist/schema/state/sqlite/system-tables.d.ts +42 -6
- package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -1
- package/dist/schema/state/sqlite/system-tables.js +2 -0
- package/dist/schema/state/sqlite/system-tables.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.d.ts +6 -8
- package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/table-def.js +4 -211
- package/dist/schema/state/sqlite/table-def.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.test.js +59 -453
- package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
- package/dist/schema/unknown-events.d.ts +47 -0
- package/dist/schema/unknown-events.d.ts.map +1 -0
- package/dist/schema/unknown-events.js +69 -0
- package/dist/schema/unknown-events.js.map +1 -0
- package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
- package/dist/sql-queries/sql-query-builder.js +2 -1
- package/dist/sql-queries/sql-query-builder.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +9 -11
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +35 -33
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/errors.d.ts +61 -0
- package/dist/sync/errors.d.ts.map +1 -0
- package/dist/sync/errors.js +36 -0
- package/dist/sync/errors.js.map +1 -0
- package/dist/sync/index.d.ts +3 -0
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +3 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/mock-sync-backend.d.ts +23 -0
- package/dist/sync/mock-sync-backend.d.ts.map +1 -0
- package/dist/sync/mock-sync-backend.js +114 -0
- package/dist/sync/mock-sync-backend.js.map +1 -0
- package/dist/sync/next/compact-events.d.ts.map +1 -1
- package/dist/sync/next/compact-events.js +4 -5
- package/dist/sync/next/compact-events.js.map +1 -1
- package/dist/sync/next/facts.d.ts.map +1 -1
- package/dist/sync/next/facts.js +1 -2
- package/dist/sync/next/facts.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts +50 -11
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +193 -4
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/history-dag.d.ts.map +1 -1
- package/dist/sync/next/history-dag.js +3 -1
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/sync-backend-kv.d.ts +7 -0
- package/dist/sync/sync-backend-kv.d.ts.map +1 -0
- package/dist/sync/sync-backend-kv.js +18 -0
- package/dist/sync/sync-backend-kv.js.map +1 -0
- package/dist/sync/sync-backend.d.ts +105 -0
- package/dist/sync/sync-backend.d.ts.map +1 -0
- package/dist/sync/sync-backend.js +61 -0
- package/dist/sync/sync-backend.js.map +1 -0
- package/dist/sync/sync.d.ts +6 -84
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js +2 -27
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/transport-chunking.d.ts +36 -0
- package/dist/sync/transport-chunking.d.ts.map +1 -0
- package/dist/sync/transport-chunking.js +56 -0
- package/dist/sync/transport-chunking.js.map +1 -0
- package/dist/sync/validate-push-payload.d.ts +1 -1
- package/dist/sync/validate-push-payload.d.ts.map +1 -1
- package/dist/sync/validate-push-payload.js +6 -6
- package/dist/sync/validate-push-payload.js.map +1 -1
- package/dist/testing/event-factory.d.ts +68 -0
- package/dist/testing/event-factory.d.ts.map +1 -0
- package/dist/testing/event-factory.js +80 -0
- package/dist/testing/event-factory.js.map +1 -0
- package/dist/testing/mod.d.ts +2 -0
- package/dist/testing/mod.d.ts.map +1 -0
- package/dist/testing/mod.js +2 -0
- package/dist/testing/mod.js.map +1 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +2 -2
- package/dist/version.js.map +1 -1
- package/package.json +7 -8
- package/src/ClientSessionLeaderThreadProxy.ts +7 -2
- package/src/adapter-types.ts +13 -3
- package/src/devtools/devtools-messages-common.ts +1 -8
- package/src/errors.ts +33 -4
- package/src/leader-thread/LeaderSyncProcessor.ts +179 -57
- package/src/leader-thread/eventlog.ts +10 -6
- package/src/leader-thread/leader-worker-devtools.ts +6 -2
- package/src/leader-thread/make-leader-thread-layer.test.ts +44 -0
- package/src/leader-thread/make-leader-thread-layer.ts +137 -26
- package/src/leader-thread/materialize-event.ts +34 -9
- package/src/leader-thread/recreate-db.ts +11 -3
- package/src/leader-thread/shutdown-channel.ts +16 -2
- package/src/leader-thread/types.ts +7 -5
- package/src/materializer-helper.ts +22 -5
- package/src/rematerialize-from-eventlog.ts +33 -23
- package/src/schema/EventDef.ts +3 -0
- package/src/schema/LiveStoreEvent.ts +1 -2
- package/src/schema/mod.ts +2 -0
- package/src/schema/schema.ts +37 -1
- package/src/schema/state/sqlite/client-document-def.test.ts +17 -0
- package/src/schema/state/sqlite/client-document-def.ts +117 -5
- package/src/schema/state/sqlite/column-annotations.ts +16 -6
- package/src/schema/state/sqlite/column-def.test.ts +722 -0
- package/src/schema/state/sqlite/column-def.ts +215 -0
- package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +26 -6
- package/src/schema/state/sqlite/db-schema/dsl/mod.ts +2 -1
- package/src/schema/state/sqlite/mod.ts +1 -0
- package/src/schema/state/sqlite/query-builder/api.ts +7 -2
- package/src/schema/state/sqlite/query-builder/impl.test.ts +187 -6
- package/src/schema/state/sqlite/query-builder/impl.ts +8 -2
- package/src/schema/state/sqlite/system-tables.ts +2 -0
- package/src/schema/state/sqlite/table-def.test.ts +74 -569
- package/src/schema/state/sqlite/table-def.ts +13 -262
- package/src/schema/unknown-events.ts +131 -0
- package/src/sql-queries/sql-query-builder.ts +2 -1
- package/src/sync/ClientSessionSyncProcessor.ts +55 -49
- package/src/sync/errors.ts +38 -0
- package/src/sync/index.ts +3 -0
- package/src/sync/mock-sync-backend.ts +184 -0
- package/src/sync/next/compact-events.ts +4 -5
- package/src/sync/next/facts.ts +1 -3
- package/src/sync/next/history-dag-common.ts +272 -21
- package/src/sync/next/history-dag.ts +3 -1
- package/src/sync/sync-backend-kv.ts +22 -0
- package/src/sync/sync-backend.ts +185 -0
- package/src/sync/sync.ts +6 -89
- package/src/sync/transport-chunking.ts +90 -0
- package/src/sync/validate-push-payload.ts +6 -7
- package/src/testing/event-factory.ts +133 -0
- package/src/testing/mod.ts +1 -0
- package/src/version.ts +2 -2
- package/dist/schema-management/migrations.test.d.ts +0 -2
- package/dist/schema-management/migrations.test.d.ts.map +0 -1
- package/dist/schema-management/migrations.test.js +0 -52
- package/dist/schema-management/migrations.test.js.map +0 -1
- package/dist/sync/next/graphology.d.ts +0 -8
- package/dist/sync/next/graphology.d.ts.map +0 -1
- package/dist/sync/next/graphology.js +0 -30
- package/dist/sync/next/graphology.js.map +0 -1
- package/dist/sync/next/graphology_.d.ts +0 -3
- package/dist/sync/next/graphology_.d.ts.map +0 -1
- package/dist/sync/next/graphology_.js +0 -3
- package/dist/sync/next/graphology_.js.map +0 -1
- package/src/sync/next/ambient.d.ts +0 -3
- package/src/sync/next/graphology.ts +0 -41
- package/src/sync/next/graphology_.ts +0 -2
@@ -1,13 +1,16 @@
|
|
1
|
-
import { type Nullable, shouldNeverHappen
|
1
|
+
import { type Nullable, shouldNeverHappen } from '@livestore/utils'
|
2
2
|
import { Option, Schema, SchemaAST, type Types } from '@livestore/utils/effect'
|
3
3
|
|
4
|
-
import {
|
4
|
+
import { getColumnDefForSchema, schemaFieldsToColumns } from './column-def.ts'
|
5
5
|
import { SqliteDsl } from './db-schema/mod.ts'
|
6
6
|
import type { QueryBuilder } from './query-builder/mod.ts'
|
7
7
|
import { makeQueryBuilder, QueryBuilderAstSymbol, QueryBuilderTypeId } from './query-builder/mod.ts'
|
8
8
|
|
9
9
|
export const { blob, boolean, column, datetime, integer, isColumnDefinition, json, real, text } = SqliteDsl
|
10
10
|
|
11
|
+
// Re-export the column definition function
|
12
|
+
export { getColumnDefForSchema }
|
13
|
+
|
11
14
|
export type StateType = 'singleton' | 'dynamic'
|
12
15
|
|
13
16
|
export type DefaultSqliteTableDef = SqliteDsl.TableDefinition<string, SqliteDsl.Columns>
|
@@ -218,7 +221,7 @@ export function table<
|
|
218
221
|
) as SqliteDsl.Columns
|
219
222
|
additionalIndexes = []
|
220
223
|
} else if ('schema' in args) {
|
221
|
-
const result = schemaFieldsToColumns(
|
224
|
+
const result = schemaFieldsToColumns(Schema.getResolvedPropertySignatures(args.schema))
|
222
225
|
columns = result.columns
|
223
226
|
|
224
227
|
// We'll set tableName first, then use it for index names
|
@@ -378,12 +381,13 @@ export declare namespace SchemaToColumns {
|
|
378
381
|
export type ColumnDefForType<TEncoded, TType> = SqliteDsl.ColumnDefinition<TEncoded, TType>
|
379
382
|
|
380
383
|
// Create columns type from schema Type and Encoded
|
381
|
-
export type FromTypes<TType, TEncoded> =
|
382
|
-
?
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
384
|
+
export type FromTypes<TType, TEncoded> = TEncoded extends Record<string, any>
|
385
|
+
? {
|
386
|
+
[K in keyof TEncoded]-?: ColumnDefForType<
|
387
|
+
TEncoded[K],
|
388
|
+
TType extends Record<string, any> ? (K extends keyof TType ? TType[K] : TEncoded[K]) : TEncoded[K]
|
389
|
+
>
|
390
|
+
}
|
387
391
|
: SqliteDsl.Columns
|
388
392
|
}
|
389
393
|
|
@@ -398,256 +402,3 @@ export declare namespace TableDefInput {
|
|
398
402
|
SchemaToColumns.FromTypes<TType, TEncoded>
|
399
403
|
>
|
400
404
|
}
|
401
|
-
|
402
|
-
/**
|
403
|
-
* Checks if a property signature has a specific annotation, checking both
|
404
|
-
* the property signature itself and its type AST.
|
405
|
-
*/
|
406
|
-
const hasPropertyAnnotation = <T>(
|
407
|
-
propertySignature: SchemaAST.PropertySignature,
|
408
|
-
annotationId: symbol,
|
409
|
-
): Option.Option<T> => {
|
410
|
-
// When using Schema.optional(Schema.String).pipe(withPrimaryKey) in a struct,
|
411
|
-
// the annotation ends up on a PropertySignatureDeclaration, not the Union type
|
412
|
-
// Check if this is a PropertySignatureDeclaration with annotations
|
413
|
-
if ('annotations' in propertySignature && propertySignature.annotations) {
|
414
|
-
const annotation = SchemaAST.getAnnotation<T>(annotationId)(propertySignature as any)
|
415
|
-
if (Option.isSome(annotation)) {
|
416
|
-
return annotation
|
417
|
-
}
|
418
|
-
}
|
419
|
-
|
420
|
-
// Otherwise check the type AST
|
421
|
-
return SchemaAST.getAnnotation<T>(annotationId)(propertySignature.type)
|
422
|
-
}
|
423
|
-
|
424
|
-
/**
|
425
|
-
* Maps schema property signatures to SQLite column definitions.
|
426
|
-
* Returns both columns and unique column names for index creation.
|
427
|
-
*/
|
428
|
-
const schemaFieldsToColumns = (
|
429
|
-
propertySignatures: ReadonlyArray<SchemaAST.PropertySignature>,
|
430
|
-
): { columns: SqliteDsl.Columns; uniqueColumns: string[] } => {
|
431
|
-
const columns: SqliteDsl.Columns = {}
|
432
|
-
const uniqueColumns: string[] = []
|
433
|
-
|
434
|
-
for (const prop of propertySignatures) {
|
435
|
-
if (typeof prop.name === 'string') {
|
436
|
-
// Create a schema from the AST
|
437
|
-
const fieldSchema = Schema.make(prop.type)
|
438
|
-
// Check if property has primary key annotation
|
439
|
-
const hasPrimaryKey = hasPropertyAnnotation<boolean>(prop, PrimaryKeyId).pipe(Option.getOrElse(() => false))
|
440
|
-
// Check if property has unique annotation
|
441
|
-
const hasUnique = hasPropertyAnnotation<boolean>(prop, Unique).pipe(Option.getOrElse(() => false))
|
442
|
-
|
443
|
-
columns[prop.name] = schemaFieldToColumn(fieldSchema, prop, hasPrimaryKey)
|
444
|
-
|
445
|
-
if (hasUnique) {
|
446
|
-
uniqueColumns.push(prop.name)
|
447
|
-
}
|
448
|
-
}
|
449
|
-
}
|
450
|
-
|
451
|
-
return { columns, uniqueColumns }
|
452
|
-
}
|
453
|
-
|
454
|
-
/**
|
455
|
-
* Converts a schema field and its property signature to a SQLite column definition.
|
456
|
-
*/
|
457
|
-
const schemaFieldToColumn = (
|
458
|
-
fieldSchema: Schema.Schema.AnyNoContext,
|
459
|
-
propertySignature: SchemaAST.PropertySignature,
|
460
|
-
forceHasPrimaryKey?: boolean,
|
461
|
-
): SqliteDsl.ColumnDefinition.Any => {
|
462
|
-
// Determine column type based on schema type
|
463
|
-
const columnDef = getColumnDefForSchema(fieldSchema, propertySignature)
|
464
|
-
|
465
|
-
// Create a new object with appropriate properties
|
466
|
-
const result: Writeable<SqliteDsl.ColumnDefinition.Any> = {
|
467
|
-
columnType: columnDef.columnType,
|
468
|
-
schema: columnDef.schema,
|
469
|
-
default: columnDef.default,
|
470
|
-
nullable: columnDef.nullable,
|
471
|
-
primaryKey: columnDef.primaryKey,
|
472
|
-
autoIncrement: columnDef.autoIncrement,
|
473
|
-
}
|
474
|
-
|
475
|
-
// Set primaryKey property explicitly
|
476
|
-
if (forceHasPrimaryKey || columnDef.primaryKey) {
|
477
|
-
result.primaryKey = true
|
478
|
-
} else {
|
479
|
-
result.primaryKey = false
|
480
|
-
}
|
481
|
-
|
482
|
-
// Check for invalid primary key + nullable combination
|
483
|
-
if (result.primaryKey && (propertySignature.isOptional || columnDef.nullable)) {
|
484
|
-
return shouldNeverHappen(
|
485
|
-
`Primary key columns cannot be nullable. Found nullable primary key for column. ` +
|
486
|
-
`Either remove the primary key annotation or use a non-nullable schema.`,
|
487
|
-
)
|
488
|
-
}
|
489
|
-
|
490
|
-
// Set nullable property explicitly
|
491
|
-
if (propertySignature.isOptional) {
|
492
|
-
result.nullable = true
|
493
|
-
} else if (columnDef.nullable) {
|
494
|
-
result.nullable = true
|
495
|
-
} else {
|
496
|
-
result.nullable = false
|
497
|
-
}
|
498
|
-
|
499
|
-
// Only add autoIncrement if it's true
|
500
|
-
if (columnDef.autoIncrement) {
|
501
|
-
result.autoIncrement = true
|
502
|
-
}
|
503
|
-
|
504
|
-
return result as SqliteDsl.ColumnDefinition.Any
|
505
|
-
}
|
506
|
-
|
507
|
-
/**
|
508
|
-
* Maps a schema to a SQLite column definition, respecting column annotations.
|
509
|
-
*/
|
510
|
-
export const getColumnDefForSchema = (
|
511
|
-
schema: Schema.Schema.AnyNoContext,
|
512
|
-
propertySignature?: SchemaAST.PropertySignature,
|
513
|
-
): SqliteDsl.ColumnDefinition.Any => {
|
514
|
-
const ast = schema.ast
|
515
|
-
|
516
|
-
// Check for annotations
|
517
|
-
const hasPrimaryKey = propertySignature
|
518
|
-
? hasPropertyAnnotation<boolean>(propertySignature, PrimaryKeyId).pipe(Option.getOrElse(() => false))
|
519
|
-
: SchemaAST.getAnnotation<boolean>(PrimaryKeyId)(ast).pipe(Option.getOrElse(() => false))
|
520
|
-
|
521
|
-
const hasAutoIncrement = propertySignature
|
522
|
-
? hasPropertyAnnotation<boolean>(propertySignature, AutoIncrement).pipe(Option.getOrElse(() => false))
|
523
|
-
: SchemaAST.getAnnotation<boolean>(AutoIncrement)(ast).pipe(Option.getOrElse(() => false))
|
524
|
-
|
525
|
-
const defaultValue = propertySignature
|
526
|
-
? hasPropertyAnnotation<unknown>(propertySignature, Default)
|
527
|
-
: SchemaAST.getAnnotation<unknown>(Default)(ast)
|
528
|
-
|
529
|
-
/** Adds annotations to a column definition if they are present. */
|
530
|
-
const withAnnotationsIfNeeded = (columnDef: SqliteDsl.ColumnDefinition.Any): SqliteDsl.ColumnDefinition.Any => {
|
531
|
-
const result = { ...columnDef }
|
532
|
-
|
533
|
-
if (hasPrimaryKey) {
|
534
|
-
result.primaryKey = true
|
535
|
-
}
|
536
|
-
|
537
|
-
if (hasAutoIncrement) {
|
538
|
-
result.autoIncrement = true
|
539
|
-
}
|
540
|
-
|
541
|
-
if (Option.isSome(defaultValue)) {
|
542
|
-
result.default = Option.some(defaultValue.value)
|
543
|
-
}
|
544
|
-
|
545
|
-
return result
|
546
|
-
}
|
547
|
-
|
548
|
-
// Check for custom column type annotation
|
549
|
-
const columnTypeAnnotation = SchemaAST.getAnnotation<SqliteDsl.FieldColumnType>(ColumnType)(ast)
|
550
|
-
if (Option.isSome(columnTypeAnnotation)) {
|
551
|
-
const columnType = columnTypeAnnotation.value
|
552
|
-
let columnDef: SqliteDsl.ColumnDefinition.Any
|
553
|
-
switch (columnType) {
|
554
|
-
case 'text':
|
555
|
-
columnDef = SqliteDsl.text()
|
556
|
-
break
|
557
|
-
case 'integer':
|
558
|
-
columnDef = SqliteDsl.integer()
|
559
|
-
break
|
560
|
-
case 'real':
|
561
|
-
columnDef = SqliteDsl.real()
|
562
|
-
break
|
563
|
-
case 'blob':
|
564
|
-
columnDef = SqliteDsl.blob()
|
565
|
-
break
|
566
|
-
default:
|
567
|
-
return shouldNeverHappen(`Unsupported column type annotation: ${columnType}`)
|
568
|
-
}
|
569
|
-
|
570
|
-
return withAnnotationsIfNeeded(columnDef)
|
571
|
-
}
|
572
|
-
|
573
|
-
// Check for refinements (e.g., Schema.Int)
|
574
|
-
if (SchemaAST.isRefinement(ast)) {
|
575
|
-
// Check if this is specifically Schema.Int by looking at the identifier annotation
|
576
|
-
const identifier = SchemaAST.getIdentifierAnnotation(ast).pipe(Option.getOrElse(() => ''))
|
577
|
-
if (identifier === 'Int') {
|
578
|
-
return withAnnotationsIfNeeded(SqliteDsl.integer())
|
579
|
-
}
|
580
|
-
// For other refinements, check the underlying type
|
581
|
-
return getColumnDefForSchema(Schema.make(ast.from), propertySignature)
|
582
|
-
}
|
583
|
-
|
584
|
-
// Check for string types
|
585
|
-
if (SchemaAST.isStringKeyword(ast)) {
|
586
|
-
return withAnnotationsIfNeeded(SqliteDsl.text())
|
587
|
-
}
|
588
|
-
|
589
|
-
// Check for number types
|
590
|
-
if (SchemaAST.isNumberKeyword(ast)) {
|
591
|
-
return withAnnotationsIfNeeded(SqliteDsl.real())
|
592
|
-
}
|
593
|
-
|
594
|
-
// Check for boolean types
|
595
|
-
if (SchemaAST.isBooleanKeyword(ast)) {
|
596
|
-
return withAnnotationsIfNeeded(SqliteDsl.boolean())
|
597
|
-
}
|
598
|
-
|
599
|
-
// Check for unions (like optional or nullable)
|
600
|
-
if (SchemaAST.isUnion(ast)) {
|
601
|
-
// Check if this union contains null or undefined (making it nullable/optional)
|
602
|
-
let hasNull = false
|
603
|
-
let hasUndefined = false
|
604
|
-
let nonNullableType: SchemaAST.AST | undefined
|
605
|
-
|
606
|
-
for (const type of ast.types) {
|
607
|
-
if (SchemaAST.isUndefinedKeyword(type)) {
|
608
|
-
hasUndefined = true
|
609
|
-
} else if (SchemaAST.isLiteral(type) && type.literal === null) {
|
610
|
-
hasNull = true
|
611
|
-
} else {
|
612
|
-
nonNullableType = type
|
613
|
-
}
|
614
|
-
}
|
615
|
-
|
616
|
-
// If we found a non-nullable type, use it for the column definition
|
617
|
-
if (nonNullableType) {
|
618
|
-
const innerSchema = Schema.make(nonNullableType)
|
619
|
-
const innerColumnDef = getColumnDefForSchema(innerSchema, propertySignature)
|
620
|
-
|
621
|
-
// If the union contains null or undefined, mark as nullable
|
622
|
-
if (hasNull || hasUndefined) {
|
623
|
-
return withAnnotationsIfNeeded({
|
624
|
-
...innerColumnDef,
|
625
|
-
nullable: true,
|
626
|
-
})
|
627
|
-
}
|
628
|
-
|
629
|
-
return withAnnotationsIfNeeded(innerColumnDef)
|
630
|
-
}
|
631
|
-
}
|
632
|
-
|
633
|
-
// Check for Date types
|
634
|
-
if (SchemaAST.isTransformation(ast)) {
|
635
|
-
// Try to map the transformation's target type
|
636
|
-
return getColumnDefForSchema(Schema.make(ast.to), propertySignature)
|
637
|
-
}
|
638
|
-
|
639
|
-
// Check for literal types
|
640
|
-
if (SchemaAST.isLiteral(ast)) {
|
641
|
-
const value = ast.literal
|
642
|
-
if (typeof value === 'string') {
|
643
|
-
return withAnnotationsIfNeeded(SqliteDsl.text())
|
644
|
-
} else if (typeof value === 'number') {
|
645
|
-
return withAnnotationsIfNeeded(SqliteDsl.real())
|
646
|
-
} else if (typeof value === 'boolean') {
|
647
|
-
return withAnnotationsIfNeeded(SqliteDsl.boolean())
|
648
|
-
}
|
649
|
-
}
|
650
|
-
|
651
|
-
// Default to JSON column for complex types
|
652
|
-
return withAnnotationsIfNeeded(SqliteDsl.json({ schema }))
|
653
|
-
}
|
@@ -0,0 +1,131 @@
|
|
1
|
+
import { Effect } from '@livestore/utils/effect'
|
2
|
+
|
3
|
+
import { UnknownEventError } from '../errors.ts'
|
4
|
+
import type { EventDef, Materializer } from './EventDef.ts'
|
5
|
+
import type * as LiveStoreEvent from './LiveStoreEvent.ts'
|
6
|
+
import type { LiveStoreSchema } from './schema.ts'
|
7
|
+
|
8
|
+
export type UnknownEventContext = {
|
9
|
+
readonly event: Pick<LiveStoreEvent.AnyEncoded, 'name' | 'args' | 'seqNum' | 'clientId' | 'sessionId'>
|
10
|
+
readonly reason: 'event-definition-missing' | 'materializer-missing'
|
11
|
+
readonly operation: string
|
12
|
+
}
|
13
|
+
|
14
|
+
export namespace UnknownEvents {
|
15
|
+
export type HandlingStrategy = 'warn' | 'fail' | 'ignore' | 'callback'
|
16
|
+
|
17
|
+
export type Callback = (
|
18
|
+
context: UnknownEventContext,
|
19
|
+
error: UnknownEventError,
|
20
|
+
) => Effect.SyncOrPromiseOrEffect<void, unknown, never>
|
21
|
+
|
22
|
+
export type HandlingConfig =
|
23
|
+
| { readonly strategy: 'warn' }
|
24
|
+
| { readonly strategy: 'ignore' }
|
25
|
+
| { readonly strategy: 'fail' }
|
26
|
+
| { readonly strategy: 'callback'; readonly onUnknownEvent: Callback }
|
27
|
+
|
28
|
+
export type Reason = UnknownEventContext['reason']
|
29
|
+
|
30
|
+
export type ResolveContext = Omit<UnknownEventContext, 'reason'>
|
31
|
+
|
32
|
+
export type Resolved =
|
33
|
+
| {
|
34
|
+
readonly _tag: 'known'
|
35
|
+
readonly eventDef: EventDef.AnyWithoutFn
|
36
|
+
readonly materializer: Materializer
|
37
|
+
}
|
38
|
+
| {
|
39
|
+
readonly _tag: 'unknown'
|
40
|
+
readonly reason: Reason
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
const DEFAULT_UNKNOWN_EVENT_HANDLING: UnknownEvents.HandlingConfig = { strategy: 'warn' }
|
45
|
+
|
46
|
+
export const normalizeUnknownEventHandling = (
|
47
|
+
input: UnknownEvents.HandlingConfig | undefined,
|
48
|
+
): UnknownEvents.HandlingConfig => input ?? DEFAULT_UNKNOWN_EVENT_HANDLING
|
49
|
+
|
50
|
+
const handleUnknownEvent = ({
|
51
|
+
schema,
|
52
|
+
context,
|
53
|
+
}: {
|
54
|
+
schema: LiveStoreSchema
|
55
|
+
context: UnknownEventContext
|
56
|
+
}): Effect.Effect<void, UnknownEventError> =>
|
57
|
+
Effect.gen(function* () {
|
58
|
+
const config = schema.unknownEventHandling
|
59
|
+
const error = new UnknownEventError(context)
|
60
|
+
|
61
|
+
switch (config.strategy) {
|
62
|
+
case 'fail': {
|
63
|
+
return yield* Effect.fail(error)
|
64
|
+
}
|
65
|
+
case 'warn': {
|
66
|
+
yield* Effect.logWarning('@livestore/common:schema:unknown-event', context)
|
67
|
+
return
|
68
|
+
}
|
69
|
+
case 'ignore': {
|
70
|
+
return
|
71
|
+
}
|
72
|
+
case 'callback': {
|
73
|
+
const callback = config.onUnknownEvent
|
74
|
+
|
75
|
+
yield* Effect.tryAll<void>(() => callback(context, error)).pipe(
|
76
|
+
Effect.catchAll((cause) =>
|
77
|
+
Effect.logWarning('@livestore/common:schema:unknown-event:callback-error', {
|
78
|
+
event: context.event,
|
79
|
+
reason: context.reason,
|
80
|
+
operation: context.operation,
|
81
|
+
cause,
|
82
|
+
}),
|
83
|
+
),
|
84
|
+
)
|
85
|
+
return
|
86
|
+
}
|
87
|
+
}
|
88
|
+
})
|
89
|
+
|
90
|
+
/**
|
91
|
+
* Resolves the runtime event definition + materializer for a given event name.
|
92
|
+
*
|
93
|
+
* Behaviour is intentionally split across the result and error channels:
|
94
|
+
* - For `'fail'` handling, we surface an `UnknownEventError` via the failure channel so
|
95
|
+
* callers can convert it into the appropriate domain error (for example `MaterializeError`).
|
96
|
+
* - For all other strategies (`warn`, `ignore`, `callback`) we succeed with an
|
97
|
+
* `{ _tag: 'unknown' }` value, signalling that the caller should skip the event while
|
98
|
+
* continuing normal processing.
|
99
|
+
*/
|
100
|
+
export const resolveEventDef = (
|
101
|
+
schema: LiveStoreSchema,
|
102
|
+
context: UnknownEvents.ResolveContext,
|
103
|
+
): Effect.Effect<UnknownEvents.Resolved, UnknownEventError> =>
|
104
|
+
Effect.gen(function* () {
|
105
|
+
const eventName = context.event.name
|
106
|
+
const eventDef = schema.eventsDefsMap.get(eventName)
|
107
|
+
if (eventDef === undefined) {
|
108
|
+
yield* handleUnknownEvent({
|
109
|
+
schema,
|
110
|
+
context: {
|
111
|
+
event: context.event,
|
112
|
+
reason: 'event-definition-missing',
|
113
|
+
operation: context.operation,
|
114
|
+
},
|
115
|
+
})
|
116
|
+
return { _tag: 'unknown', reason: 'event-definition-missing' }
|
117
|
+
}
|
118
|
+
const materializer = schema.state.materializers.get(eventName)
|
119
|
+
if (materializer === undefined) {
|
120
|
+
yield* handleUnknownEvent({
|
121
|
+
schema,
|
122
|
+
context: {
|
123
|
+
event: context.event,
|
124
|
+
reason: 'materializer-missing',
|
125
|
+
operation: context.operation,
|
126
|
+
},
|
127
|
+
})
|
128
|
+
return { _tag: 'unknown', reason: 'materializer-missing' }
|
129
|
+
}
|
130
|
+
return { _tag: 'known', eventDef, materializer }
|
131
|
+
})
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { omitUndefineds } from '@livestore/utils'
|
1
2
|
import type { SqliteDsl } from '../schema/state/sqlite/db-schema/mod.ts'
|
2
3
|
import type { BindValues } from './sql-queries.ts'
|
3
4
|
import * as SqlQueries from './sql-queries.ts'
|
@@ -16,7 +17,7 @@ export const makeSqlQueryBuilder = <TSchema extends SqliteDsl.DbSchema>(schema:
|
|
16
17
|
limit?: number
|
17
18
|
}): [string, BindValues, TTableName] => {
|
18
19
|
const columns = schema[tableName]!.columns
|
19
|
-
const [stmt, bindValues] = SqlQueries.findManyRows({ columns, tableName, where, limit })
|
20
|
+
const [stmt, bindValues] = SqlQueries.findManyRows({ columns, tableName, where, ...omitUndefineds({ limit }) })
|
20
21
|
return [stmt, bindValues, tableName]
|
21
22
|
}
|
22
23
|
|
@@ -13,12 +13,13 @@ import {
|
|
13
13
|
Stream,
|
14
14
|
Subscribable,
|
15
15
|
} from '@livestore/utils/effect'
|
16
|
-
import * as otel from '@opentelemetry/api'
|
16
|
+
import type * as otel from '@opentelemetry/api'
|
17
17
|
|
18
|
-
import { type ClientSession,
|
18
|
+
import { type ClientSession, UnexpectedError } from '../adapter-types.ts'
|
19
|
+
import type { MaterializeError } from '../errors.ts'
|
19
20
|
import * as EventSequenceNumber from '../schema/EventSequenceNumber.ts'
|
20
21
|
import * as LiveStoreEvent from '../schema/LiveStoreEvent.ts'
|
21
|
-
import {
|
22
|
+
import type { LiveStoreSchema } from '../schema/mod.ts'
|
22
23
|
import * as SyncState from './syncstate.ts'
|
23
24
|
|
24
25
|
/**
|
@@ -51,16 +52,19 @@ export const makeClientSessionSyncProcessor = ({
|
|
51
52
|
clientSession: ClientSession
|
52
53
|
runtime: Runtime.Runtime<Scope.Scope>
|
53
54
|
materializeEvent: (
|
54
|
-
|
55
|
-
options: {
|
56
|
-
) =>
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
55
|
+
eventEncoded: LiveStoreEvent.EncodedWithMeta,
|
56
|
+
options: { withChangeset: boolean; materializerHashLeader: Option.Option<number> },
|
57
|
+
) => Effect.Effect<
|
58
|
+
{
|
59
|
+
writeTables: Set<string>
|
60
|
+
sessionChangeset:
|
61
|
+
| { _tag: 'sessionChangeset'; data: Uint8Array<ArrayBuffer>; debug: any }
|
62
|
+
| { _tag: 'no-op' }
|
63
|
+
| { _tag: 'unset' }
|
64
|
+
materializerHash: Option.Option<number>
|
65
|
+
},
|
66
|
+
MaterializeError
|
67
|
+
>
|
64
68
|
rollback: (changeset: Uint8Array<ArrayBuffer>) => void
|
65
69
|
refreshTables: (tables: Set<string>) => void
|
66
70
|
span: otel.Span
|
@@ -91,23 +95,26 @@ export const makeClientSessionSyncProcessor = ({
|
|
91
95
|
}),
|
92
96
|
}
|
93
97
|
|
94
|
-
/** Only used for debugging / observability, it's not relied upon for correctness of the sync processor. */
|
98
|
+
/** Only used for debugging / observability / testing, it's not relied upon for correctness of the sync processor. */
|
95
99
|
const syncStateUpdateQueue = Queue.unbounded<SyncState.SyncState>().pipe(Effect.runSync)
|
96
100
|
const isClientEvent = (eventEncoded: LiveStoreEvent.EncodedWithMeta) =>
|
97
|
-
|
101
|
+
schema.eventsDefsMap.get(eventEncoded.name)?.options.clientOnly ?? false
|
98
102
|
|
99
103
|
/** We're queuing push requests to reduce the number of messages sent to the leader by batching them */
|
100
104
|
const leaderPushQueue = BucketQueue.make<LiveStoreEvent.EncodedWithMeta>().pipe(Effect.runSync)
|
101
105
|
|
102
|
-
const push: ClientSessionSyncProcessor['push'] = (batch
|
106
|
+
const push: ClientSessionSyncProcessor['push'] = Effect.fn('client-session-sync-processor:push')(function* (batch) {
|
103
107
|
// TODO validate batch
|
104
108
|
|
105
109
|
let baseEventSequenceNumber = syncStateRef.current.localHead
|
106
110
|
const encodedEventDefs = batch.map(({ name, args }) => {
|
107
|
-
const eventDef =
|
111
|
+
const eventDef = schema.eventsDefsMap.get(name)
|
112
|
+
if (eventDef === undefined) {
|
113
|
+
return shouldNeverHappen(`No event definition found for \`${name}\`.`)
|
114
|
+
}
|
108
115
|
const nextNumPair = EventSequenceNumber.nextPair({
|
109
116
|
seqNum: baseEventSequenceNumber,
|
110
|
-
isClient: eventDef.
|
117
|
+
isClient: eventDef.options.clientOnly,
|
111
118
|
})
|
112
119
|
baseEventSequenceNumber = nextNumPair.seqNum
|
113
120
|
return new LiveStoreEvent.EncodedWithMeta(
|
@@ -128,33 +135,35 @@ export const makeClientSessionSyncProcessor = ({
|
|
128
135
|
isEqualEvent: LiveStoreEvent.isEqualEncoded,
|
129
136
|
})
|
130
137
|
|
138
|
+
yield* Effect.annotateCurrentSpan({
|
139
|
+
batchSize: encodedEventDefs.length,
|
140
|
+
mergeResultTag: mergeResult._tag,
|
141
|
+
eventCounts: encodedEventDefs.reduce<Record<string, number>>((acc, event) => {
|
142
|
+
acc[event.name] = (acc[event.name] ?? 0) + 1
|
143
|
+
return acc
|
144
|
+
}, {}),
|
145
|
+
...(TRACE_VERBOSE && { mergeResult: JSON.stringify(mergeResult) }),
|
146
|
+
})
|
147
|
+
|
131
148
|
if (mergeResult._tag === 'unexpected-error') {
|
132
149
|
return shouldNeverHappen('Unexpected error in client-session-sync-processor', mergeResult.message)
|
133
150
|
}
|
134
151
|
|
135
|
-
span.addEvent('local-push', {
|
136
|
-
batchSize: encodedEventDefs.length,
|
137
|
-
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
138
|
-
})
|
139
|
-
|
140
152
|
if (mergeResult._tag !== 'advance') {
|
141
153
|
return shouldNeverHappen(`Expected advance, got ${mergeResult._tag}`)
|
142
154
|
}
|
143
155
|
|
144
156
|
syncStateRef.current = mergeResult.newSyncState
|
145
|
-
syncStateUpdateQueue.offer(mergeResult.newSyncState)
|
157
|
+
yield* syncStateUpdateQueue.offer(mergeResult.newSyncState)
|
146
158
|
|
147
159
|
// Materialize events to state
|
148
160
|
const writeTables = new Set<string>()
|
149
161
|
for (const event of mergeResult.newEvents) {
|
150
|
-
// TODO avoid encoding and decoding here again
|
151
|
-
const decodedEventDef = Schema.decodeSync(eventSchema)(event)
|
152
162
|
const {
|
153
163
|
writeTables: newWriteTables,
|
154
164
|
sessionChangeset,
|
155
165
|
materializerHash,
|
156
|
-
} = materializeEvent(
|
157
|
-
otelContext,
|
166
|
+
} = yield* materializeEvent(event, {
|
158
167
|
withChangeset: true,
|
159
168
|
materializerHashLeader: Option.none(),
|
160
169
|
})
|
@@ -167,10 +176,10 @@ export const makeClientSessionSyncProcessor = ({
|
|
167
176
|
|
168
177
|
// Trigger push to leader
|
169
178
|
// console.debug('pushToLeader', encodedEventDefs.length, ...encodedEventDefs.map((_) => _.toJSON()))
|
170
|
-
BucketQueue.offerAll(leaderPushQueue, encodedEventDefs)
|
179
|
+
yield* BucketQueue.offerAll(leaderPushQueue, encodedEventDefs)
|
171
180
|
|
172
181
|
return { writeTables }
|
173
|
-
}
|
182
|
+
})
|
174
183
|
|
175
184
|
const debugInfo = {
|
176
185
|
rebaseCount: 0,
|
@@ -178,8 +187,6 @@ export const makeClientSessionSyncProcessor = ({
|
|
178
187
|
rejectCount: 0,
|
179
188
|
}
|
180
189
|
|
181
|
-
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
182
|
-
|
183
190
|
const boot: ClientSessionSyncProcessor['boot'] = Effect.gen(function* () {
|
184
191
|
if (confirmUnsavedChanges && typeof window !== 'undefined' && typeof window.addEventListener === 'function') {
|
185
192
|
const onBeforeUnload = (event: BeforeUnloadEvent) => {
|
@@ -229,13 +236,12 @@ export const makeClientSessionSyncProcessor = ({
|
|
229
236
|
})
|
230
237
|
|
231
238
|
if (mergeResult._tag === 'unexpected-error') {
|
232
|
-
return yield* new
|
239
|
+
return yield* new UnexpectedError({ cause: mergeResult.message })
|
233
240
|
} else if (mergeResult._tag === 'reject') {
|
234
241
|
return shouldNeverHappen('Unexpected reject in client-session-sync-processor', mergeResult)
|
235
242
|
}
|
236
243
|
|
237
244
|
syncStateRef.current = mergeResult.newSyncState
|
238
|
-
yield* syncStateUpdateQueue.offer(mergeResult.newSyncState)
|
239
245
|
|
240
246
|
if (mergeResult._tag === 'rebase') {
|
241
247
|
span.addEvent('merge:pull:rebase', {
|
@@ -244,7 +250,6 @@ export const makeClientSessionSyncProcessor = ({
|
|
244
250
|
newEventsCount: mergeResult.newEvents.length,
|
245
251
|
rollbackCount: mergeResult.rollbackEvents.length,
|
246
252
|
res: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
247
|
-
rebaseGeneration: mergeResult.newSyncState.localHead.rebaseGeneration,
|
248
253
|
})
|
249
254
|
|
250
255
|
debugInfo.rebaseCount++
|
@@ -294,18 +299,19 @@ export const makeClientSessionSyncProcessor = ({
|
|
294
299
|
debugInfo.advanceCount++
|
295
300
|
}
|
296
301
|
|
297
|
-
if (mergeResult.newEvents.length === 0)
|
302
|
+
if (mergeResult.newEvents.length === 0) {
|
303
|
+
// If there are no new events, we need to update the sync state as well
|
304
|
+
yield* syncStateUpdateQueue.offer(mergeResult.newSyncState)
|
305
|
+
return
|
306
|
+
}
|
298
307
|
|
299
308
|
const writeTables = new Set<string>()
|
300
309
|
for (const event of mergeResult.newEvents) {
|
301
|
-
// TODO apply changeset if available (will require tracking of write tables as well)
|
302
|
-
const decodedEventDef = Schema.decodeSync(eventSchema)(event)
|
303
310
|
const {
|
304
311
|
writeTables: newWriteTables,
|
305
312
|
sessionChangeset,
|
306
313
|
materializerHash,
|
307
|
-
} = materializeEvent(
|
308
|
-
otelContext,
|
314
|
+
} = yield* materializeEvent(event, {
|
309
315
|
withChangeset: true,
|
310
316
|
materializerHashLeader: event.meta.materializerHashLeader,
|
311
317
|
})
|
@@ -318,6 +324,9 @@ export const makeClientSessionSyncProcessor = ({
|
|
318
324
|
}
|
319
325
|
|
320
326
|
refreshTables(writeTables)
|
327
|
+
|
328
|
+
// We're only triggering the sync state update after all events have been materialized
|
329
|
+
yield* syncStateUpdateQueue.offer(mergeResult.newSyncState)
|
321
330
|
}).pipe(
|
322
331
|
Effect.tapCauseLogPretty,
|
323
332
|
Effect.catchAllCause((cause) => clientSession.shutdown(Exit.failCause(cause))),
|
@@ -364,10 +373,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
364
373
|
export interface ClientSessionSyncProcessor {
|
365
374
|
push: (
|
366
375
|
batch: ReadonlyArray<LiveStoreEvent.PartialAnyDecoded>,
|
367
|
-
|
368
|
-
) => {
|
369
|
-
writeTables: Set<string>
|
370
|
-
}
|
376
|
+
) => Effect.Effect<{ writeTables: Set<string> }, MaterializeError>
|
371
377
|
boot: Effect.Effect<void, UnexpectedError, Scope.Scope>
|
372
378
|
/**
|
373
379
|
* Only used for debugging / observability.
|
@@ -388,11 +394,11 @@ const SIMULATION_ENABLED = true
|
|
388
394
|
// Warning: High values for the simulation params can lead to very long test runs since those get multiplied with the number of events
|
389
395
|
export const ClientSessionSyncProcessorSimulationParams = Schema.Struct({
|
390
396
|
pull: Schema.Struct({
|
391
|
-
'1_before_leader_push_fiber_interrupt': Schema.Int.pipe(Schema.between(0,
|
392
|
-
'2_before_leader_push_queue_clear': Schema.Int.pipe(Schema.between(0,
|
393
|
-
'3_before_rebase_rollback': Schema.Int.pipe(Schema.between(0,
|
394
|
-
'4_before_leader_push_queue_offer': Schema.Int.pipe(Schema.between(0,
|
395
|
-
'5_before_leader_push_fiber_run': Schema.Int.pipe(Schema.between(0,
|
397
|
+
'1_before_leader_push_fiber_interrupt': Schema.Int.pipe(Schema.between(0, 15)),
|
398
|
+
'2_before_leader_push_queue_clear': Schema.Int.pipe(Schema.between(0, 15)),
|
399
|
+
'3_before_rebase_rollback': Schema.Int.pipe(Schema.between(0, 15)),
|
400
|
+
'4_before_leader_push_queue_offer': Schema.Int.pipe(Schema.between(0, 15)),
|
401
|
+
'5_before_leader_push_fiber_run': Schema.Int.pipe(Schema.between(0, 15)),
|
396
402
|
}),
|
397
403
|
})
|
398
404
|
type ClientSessionSyncProcessorSimulationParams = typeof ClientSessionSyncProcessorSimulationParams.Type
|