@livestore/sync-cf 0.3.0-dev.3 → 0.3.0-dev.5

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.
@@ -21,17 +21,24 @@ const decodeIncomingMessage = Schema.decodeUnknownEither(Schema.parseJson(WSMess
21
21
 
22
22
  // NOTE actual table name is determined at runtime by `WebSocketServer.dbName`
23
23
  export const mutationLogTable = DbSchema.table('__unused', {
24
- idGlobal: DbSchema.integer({ primaryKey: true }),
25
- parentIdGlobal: DbSchema.integer({}),
24
+ id: DbSchema.integer({ primaryKey: true, schema: EventId.GlobalEventId }),
25
+ parentId: DbSchema.integer({ schema: EventId.GlobalEventId }),
26
26
  mutation: DbSchema.text({}),
27
27
  args: DbSchema.text({ schema: Schema.parseJson(Schema.Any) }),
28
- /** ISO date format */
28
+ /** ISO date format. Currently only used for debugging purposes. */
29
29
  createdAt: DbSchema.text({}),
30
30
  })
31
31
 
32
+ /**
33
+ * Needs to be bumped when the storage format changes (e.g. mutationLogTable schema changes)
34
+ *
35
+ * Changing this version number will lead to a "soft reset".
36
+ */
37
+ const PERSISTENCE_FORMAT_VERSION = 2
38
+
32
39
  // Durable Object
33
40
  export class WebSocketServer extends DurableObject<Env> {
34
- dbName = `mutation_log_${this.ctx.id.toString()}`
41
+ dbName = `mutation_log_${PERSISTENCE_FORMAT_VERSION}_${this.ctx.id.toString()}`
35
42
  storage = makeStorage(this.ctx, this.env, this.dbName)
36
43
 
37
44
  constructor(ctx: DurableObjectState, env: Env) {
@@ -101,13 +108,13 @@ export class WebSocketServer extends DurableObject<Env> {
101
108
  case 'WSMessage.PushReq': {
102
109
  // TODO check whether we could use the Durable Object storage for this to speed up the lookup
103
110
  const latestEvent = yield* Effect.promise(() => this.storage.getLatestEvent())
104
- const expectedParentId = latestEvent?.id ?? EventId.ROOT
111
+ const expectedParentId = latestEvent?.id ?? EventId.ROOT.global
105
112
 
106
113
  let i = 0
107
114
  for (const mutationEventEncoded of decodedMessage.batch) {
108
- if (mutationEventEncoded.parentId.global !== expectedParentId.global + i) {
115
+ if (mutationEventEncoded.parentId !== expectedParentId + i) {
109
116
  const err = WSMessage.Error.make({
110
- message: `Invalid parent id. Received ${mutationEventEncoded.parentId.global} but expected ${expectedParentId.global}`,
117
+ message: `Invalid parent id. Received ${mutationEventEncoded.parentId} but expected ${expectedParentId}`,
111
118
  requestId,
112
119
  })
113
120
 
@@ -124,11 +131,7 @@ export class WebSocketServer extends DurableObject<Env> {
124
131
  // NOTE we're currently not blocking on this to allow broadcasting right away
125
132
  const storePromise = this.storage.appendEvent(mutationEventEncoded, createdAt)
126
133
 
127
- ws.send(
128
- encodeOutgoingMessage(
129
- WSMessage.PushAck.make({ mutationId: mutationEventEncoded.id.global, requestId }),
130
- ),
131
- )
134
+ ws.send(encodeOutgoingMessage(WSMessage.PushAck.make({ mutationId: mutationEventEncoded.id, requestId })))
132
135
 
133
136
  // console.debug(`Broadcasting mutation event to ${this.subscribedWebSockets.size} clients`)
134
137
 
@@ -206,50 +209,41 @@ export class WebSocketServer extends DurableObject<Env> {
206
209
  }
207
210
 
208
211
  const makeStorage = (ctx: DurableObjectState, env: Env, dbName: string) => {
209
- const getLatestEvent = async (): Promise<MutationEvent.Any | undefined> => {
210
- const rawEvents = await env.DB.prepare(`SELECT * FROM ${dbName} ORDER BY idGlobal DESC LIMIT 1`).all()
212
+ const getLatestEvent = async (): Promise<MutationEvent.AnyEncodedGlobal | undefined> => {
213
+ const rawEvents = await env.DB.prepare(`SELECT * FROM ${dbName} ORDER BY id DESC LIMIT 1`).all()
211
214
  if (rawEvents.error) {
212
215
  throw new Error(rawEvents.error)
213
216
  }
214
- const events = Schema.decodeUnknownSync(Schema.Array(mutationLogTable.schema))(rawEvents.results).map((e) => ({
215
- ...e,
216
- // TODO remove local ids
217
- id: { global: e.idGlobal, local: 0 },
218
- parentId: { global: e.parentIdGlobal, local: 0 },
219
- }))
217
+ const events = Schema.decodeUnknownSync(Schema.Array(mutationLogTable.schema))(rawEvents.results)
218
+
220
219
  return events[0]
221
220
  }
222
221
 
223
222
  const getEvents = async (
224
223
  cursor: number | undefined,
225
224
  ): Promise<
226
- ReadonlyArray<{ mutationEventEncoded: MutationEvent.AnyEncoded; metadata: Option.Option<SyncMetadata> }>
225
+ ReadonlyArray<{ mutationEventEncoded: MutationEvent.AnyEncodedGlobal; metadata: Option.Option<SyncMetadata> }>
227
226
  > => {
228
- const whereClause = cursor === undefined ? '' : `WHERE idGlobal > ${cursor}`
229
- const sql = `SELECT * FROM ${dbName} ${whereClause} ORDER BY idGlobal ASC`
227
+ const whereClause = cursor === undefined ? '' : `WHERE id > ${cursor}`
228
+ const sql = `SELECT * FROM ${dbName} ${whereClause} ORDER BY id ASC`
230
229
  // TODO handle case where `cursor` was not found
231
230
  const rawEvents = await env.DB.prepare(sql).all()
232
231
  if (rawEvents.error) {
233
232
  throw new Error(rawEvents.error)
234
233
  }
235
234
  const events = Schema.decodeUnknownSync(Schema.Array(mutationLogTable.schema))(rawEvents.results).map(
236
- ({ createdAt, ...e }) => ({
237
- mutationEventEncoded: {
238
- ...e,
239
- // TODO remove local ids
240
- id: { global: e.idGlobal, local: 0 },
241
- parentId: { global: e.parentIdGlobal, local: 0 },
242
- },
235
+ ({ createdAt, ...mutationEventEncoded }) => ({
236
+ mutationEventEncoded,
243
237
  metadata: Option.some({ createdAt }),
244
238
  }),
245
239
  )
246
240
  return events
247
241
  }
248
242
 
249
- const appendEvent = async (event: MutationEvent.Any, createdAt: string) => {
250
- const sql = `INSERT INTO ${dbName} (idGlobal, parentIdGlobal, args, mutation, createdAt) VALUES (?, ?, ?, ?, ?)`
243
+ const appendEvent = async (event: MutationEvent.AnyEncodedGlobal, createdAt: string) => {
244
+ const sql = `INSERT INTO ${dbName} (id, parentId, args, mutation, createdAt) VALUES (?, ?, ?, ?, ?)`
251
245
  await env.DB.prepare(sql)
252
- .bind(event.id.global, event.parentId.global, JSON.stringify(event.args), event.mutation, createdAt)
246
+ .bind(event.id, event.parentId, JSON.stringify(event.args), event.mutation, createdAt)
253
247
  .run()
254
248
  }
255
249
 
@@ -20,7 +20,7 @@ export const PullRes = Schema.TaggedStruct('WSMessage.PullRes', {
20
20
  requestId: Schema.String,
21
21
  events: Schema.Array(
22
22
  Schema.Struct({
23
- mutationEventEncoded: MutationEvent.EncodedAny,
23
+ mutationEventEncoded: MutationEvent.AnyEncodedGlobal,
24
24
  metadata: Schema.Option(SyncMetadata),
25
25
  }),
26
26
  ),
@@ -30,7 +30,7 @@ export const PullRes = Schema.TaggedStruct('WSMessage.PullRes', {
30
30
  export type PullRes = typeof PullRes.Type
31
31
 
32
32
  export const PushBroadcast = Schema.TaggedStruct('WSMessage.PushBroadcast', {
33
- mutationEventEncoded: MutationEvent.EncodedAny,
33
+ mutationEventEncoded: MutationEvent.AnyEncodedGlobal,
34
34
  metadata: Schema.Option(SyncMetadata),
35
35
  })
36
36
 
@@ -38,7 +38,7 @@ export type PushBroadcast = typeof PushBroadcast.Type
38
38
 
39
39
  export const PushReq = Schema.TaggedStruct('WSMessage.PushReq', {
40
40
  requestId: Schema.String,
41
- batch: Schema.Array(MutationEvent.EncodedAny),
41
+ batch: Schema.Array(MutationEvent.AnyEncodedGlobal),
42
42
  })
43
43
 
44
44
  export type PushReq = typeof PushReq.Type