@livestore/adapter-cloudflare 0.4.0-dev.8 → 0.4.0

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.
@@ -5,14 +5,21 @@ import {
5
5
  liveStoreStorageFormatVersion,
6
6
  makeClientSession,
7
7
  type SyncOptions,
8
- UnexpectedError,
8
+ UnknownError,
9
9
  } from '@livestore/common'
10
- import { type DevtoolsOptions, Eventlog, LeaderThreadCtx, makeLeaderThreadLayer } from '@livestore/common/leader-thread'
10
+ import type { CfTypes } from '@livestore/common-cf'
11
+ import {
12
+ type DevtoolsOptions,
13
+ Eventlog,
14
+ LeaderThreadCtx,
15
+ makeLeaderThreadLayer,
16
+ streamEventsWithSyncState,
17
+ } from '@livestore/common/leader-thread'
11
18
  import { LiveStoreEvent } from '@livestore/livestore'
12
- import { sqliteDbFactory } from '@livestore/sqlite-wasm/cf'
19
+ import { CF_SQL_VFS_REQUIRED_PRAGMAS, sqliteDbFactory } from '@livestore/sqlite-wasm/cf'
20
+ import { makeSqliteDb as makeDoSqliteDb } from './make-sqlite-db.ts'
13
21
  import { loadSqlite3Wasm } from '@livestore/sqlite-wasm/load-wasm'
14
22
  import { Effect, FetchHttpClient, Layer, Schedule, SubscriptionRef, WebChannel } from '@livestore/utils/effect'
15
- import type * as CfWorker from './cf-types.ts'
16
23
 
17
24
  export const makeAdapter =
18
25
  ({
@@ -22,7 +29,7 @@ export const makeAdapter =
22
29
  sessionId,
23
30
  resetPersistence = false,
24
31
  }: {
25
- storage: CfWorker.DurableObjectStorage
32
+ storage: CfTypes.DurableObjectStorage
26
33
  clientId: string
27
34
  syncOptions: SyncOptions
28
35
  sessionId: string
@@ -30,7 +37,13 @@ export const makeAdapter =
30
37
  }): Adapter =>
31
38
  (adapterArgs) =>
32
39
  Effect.gen(function* () {
33
- const { storeId, /* devtoolsEnabled, shutdown, bootStatusQueue, */ syncPayload, schema } = adapterArgs
40
+ const {
41
+ storeId,
42
+ /* devtoolsEnabled, shutdown, bootStatusQueue, */
43
+ syncPayloadEncoded,
44
+ syncPayloadSchema,
45
+ schema,
46
+ } = adapterArgs
34
47
 
35
48
  const devtoolsOptions = { enabled: false } as DevtoolsOptions
36
49
 
@@ -39,41 +52,39 @@ export const makeAdapter =
39
52
  const makeSqliteDb = sqliteDbFactory({ sqlite3 })
40
53
 
41
54
  const syncInMemoryDb = yield* makeSqliteDb({ _tag: 'in-memory', storage, configureDb: () => {} }).pipe(
42
- UnexpectedError.mapToUnexpectedError,
55
+ UnknownError.mapToUnknownError,
43
56
  )
44
57
 
45
58
  const schemaHashSuffix =
46
59
  schema.state.sqlite.migrations.strategy === 'manual' ? 'fixed' : schema.state.sqlite.hash.toString()
47
60
 
48
- const stateDbFileName = getStateDbFileName(schemaHashSuffix)
49
- const eventlogDbFileName = getEventlogDbFileName()
50
-
51
61
  if (resetPersistence === true) {
52
- yield* resetDurableObjectPersistence({
53
- storage,
54
- storeId,
55
- dbFileNames: [stateDbFileName, eventlogDbFileName],
56
- })
62
+ yield* resetDurableObjectPersistence({ storage, storeId })
57
63
  }
58
64
 
65
+ const stateDbFileName = getStateDbFileName(schemaHashSuffix)
66
+
59
67
  const dbState = yield* makeSqliteDb({
60
68
  _tag: 'storage',
61
69
  storage,
62
70
  fileName: stateDbFileName,
71
+ configureDb: (db) =>
72
+ db.execute(
73
+ [...CF_SQL_VFS_REQUIRED_PRAGMAS, 'cache_size=-8000'].map((p) => `PRAGMA ${p}`).join(';\n'),
74
+ ),
75
+ }).pipe(UnknownError.mapToUnknownError)
76
+
77
+ // dbEventlog runs on DO SQLite directly (not through the VFS). SQL-level transaction
78
+ // control (BEGIN/COMMIT/ROLLBACK) is silently dropped — see isTransactionControlStatement
79
+ // in make-sqlite-db.ts for details on why this is safe.
80
+ const dbEventlog = yield* makeDoSqliteDb({
81
+ _tag: 'file',
82
+ db: storage.sql,
63
83
  configureDb: () => {},
64
- }).pipe(UnexpectedError.mapToUnexpectedError)
65
-
66
- const dbEventlog = yield* makeSqliteDb({
67
- _tag: 'storage',
68
- storage,
69
- fileName: eventlogDbFileName,
70
- configureDb: () => {},
71
- }).pipe(UnexpectedError.mapToUnexpectedError)
84
+ }).pipe(UnknownError.mapToUnknownError)
72
85
 
73
86
  const shutdownChannel = yield* WebChannel.noopChannel<any, any>()
74
87
 
75
- // Use Durable Object sync backend if no backend is specified
76
-
77
88
  const layer = yield* Layer.build(
78
89
  makeLeaderThreadLayer({
79
90
  schema,
@@ -85,12 +96,14 @@ export const makeAdapter =
85
96
  dbEventlog,
86
97
  devtoolsOptions,
87
98
  shutdownChannel,
88
- syncPayload,
99
+ syncPayloadEncoded,
100
+ syncPayloadSchema,
89
101
  }),
90
102
  )
91
103
 
92
104
  const { leaderThread, initialSnapshot } = yield* Effect.gen(function* () {
93
- const { dbState, dbEventlog, syncProcessor, extraIncomingMessagesQueue, initialState } = yield* LeaderThreadCtx
105
+ const { dbState, dbEventlog, syncProcessor, extraIncomingMessagesQueue, initialState, networkStatus } =
106
+ yield* LeaderThreadCtx
94
107
 
95
108
  const initialLeaderHead = Eventlog.getClientHeadFromDb(dbEventlog)
96
109
  // const initialLeaderHead = EventSequenceNumber.ROOT
@@ -101,15 +114,26 @@ export const makeAdapter =
101
114
  pull: ({ cursor }) => syncProcessor.pull({ cursor }),
102
115
  push: (batch) =>
103
116
  syncProcessor.push(
104
- batch.map((item) => new LiveStoreEvent.EncodedWithMeta(item)),
117
+ batch.map((item) => new LiveStoreEvent.Client.EncodedWithMeta(item)),
105
118
  { waitForProcessing: true },
106
119
  ),
120
+ stream: (options) =>
121
+ streamEventsWithSyncState({
122
+ dbEventlog,
123
+ syncState: syncProcessor.syncState,
124
+ options,
125
+ }),
126
+ },
127
+ initialState: {
128
+ leaderHead: initialLeaderHead,
129
+ migrationsReport: initialState.migrationsReport,
130
+ storageMode: 'persisted',
107
131
  },
108
- initialState: { leaderHead: initialLeaderHead, migrationsReport: initialState.migrationsReport },
109
132
  export: Effect.sync(() => dbState.export()),
110
133
  getEventlogData: Effect.sync(() => dbEventlog.export()),
111
- getSyncState: syncProcessor.syncState,
134
+ syncState: syncProcessor.syncState,
112
135
  sendDevtoolsMessage: (message) => extraIncomingMessagesQueue.offer(message),
136
+ networkStatus,
113
137
  },
114
138
  {
115
139
  // overrides: testing?.overrides?.clientSession?.leaderThreadProxy
@@ -130,14 +154,14 @@ export const makeAdapter =
130
154
  sqliteDb: syncInMemoryDb,
131
155
  webmeshMode: 'proxy',
132
156
  connectWebmeshNode: Effect.fnUntraced(function* ({ webmeshNode }) {
133
- console.log('connectWebmeshNode', { webmeshNode })
134
- // if (devtoolsOptions.enabled) {
135
- // yield* Webmesh.connectViaWebSocket({
136
- // node: webmeshNode,
137
- // url: `ws://${devtoolsOptions.host}:${devtoolsOptions.port}`,
138
- // openTimeout: 500,
139
- // }).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
140
- // }
157
+ if (devtoolsOptions.enabled === true) {
158
+ console.log('connectWebmeshNode', { webmeshNode })
159
+ // yield* Webmesh.connectViaWebSocket({
160
+ // node: webmeshNode,
161
+ // url: `ws://${devtoolsOptions.host}:${devtoolsOptions.port}`,
162
+ // openTimeout: 500,
163
+ // }).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
164
+ }
141
165
  }),
142
166
  leaderThread,
143
167
  lockStatus,
@@ -146,6 +170,7 @@ export const makeAdapter =
146
170
  isLeader: true,
147
171
  // Not really applicable for node as there is no "reload the app" concept
148
172
  registerBeforeUnload: (_onBeforeUnload) => () => {},
173
+ origin: undefined,
149
174
  })
150
175
 
151
176
  return clientSession
@@ -156,28 +181,26 @@ export const makeAdapter =
156
181
 
157
182
  const getStateDbFileName = (suffix: string) => `state${suffix}@${liveStoreStorageFormatVersion}.db`
158
183
 
159
- const getEventlogDbFileName = () => `eventlog@${liveStoreStorageFormatVersion}.db`
160
-
161
184
  const resetDurableObjectPersistence = ({
162
185
  storage,
163
186
  storeId,
164
- dbFileNames,
165
187
  }: {
166
- storage: CfWorker.DurableObjectStorage
188
+ storage: CfTypes.DurableObjectStorage
167
189
  storeId: string
168
- dbFileNames: ReadonlyArray<string>
169
190
  }) =>
170
191
  Effect.try({
171
192
  try: () =>
193
+ // All three tables live in the DO's single storage.sql database but are
194
+ // owned by different layers during normal operation:
195
+ // - vfs_pages: written by the wa-sqlite VFS layer (backs dbState)
196
+ // - eventlog, __livestore_sync_status: written directly by dbEventlog via storage.sql
172
197
  storage.transactionSync(() => {
173
- for (const baseName of dbFileNames) {
174
- const likePattern = `${baseName}%`
175
- safeSqlExec(storage, 'DELETE FROM vfs_blocks WHERE file_path LIKE ?', likePattern)
176
- safeSqlExec(storage, 'DELETE FROM vfs_files WHERE file_path LIKE ?', likePattern)
177
- }
198
+ safeSqlExec(storage, 'DELETE FROM vfs_pages')
199
+ safeSqlExec(storage, 'DELETE FROM eventlog')
200
+ safeSqlExec(storage, 'DELETE FROM __livestore_sync_status')
178
201
  }),
179
202
  catch: (cause) =>
180
- new UnexpectedError({
203
+ new UnknownError({
181
204
  cause,
182
205
  note: `@livestore/adapter-cloudflare: Failed to reset persistence for store ${storeId}`,
183
206
  }),
@@ -186,11 +209,11 @@ const resetDurableObjectPersistence = ({
186
209
  Effect.withSpan('@livestore/adapter-cloudflare:resetPersistence', { attributes: { storeId } }),
187
210
  )
188
211
 
189
- const safeSqlExec = (storage: CfWorker.DurableObjectStorage, query: string, binding: string) => {
212
+ const safeSqlExec = (storage: CfTypes.DurableObjectStorage, query: string, binding?: string) => {
190
213
  try {
191
- storage.sql.exec(query, binding)
214
+ binding !== undefined ? storage.sql.exec(query, binding) : storage.sql.exec(query)
192
215
  } catch (error) {
193
- if (isMissingVfsTableError(error)) {
216
+ if (isMissingTableError(error) === true) {
194
217
  return
195
218
  }
196
219
 
@@ -198,5 +221,5 @@ const safeSqlExec = (storage: CfWorker.DurableObjectStorage, query: string, bind
198
221
  }
199
222
  }
200
223
 
201
- const isMissingVfsTableError = (error: unknown): boolean =>
224
+ const isMissingTableError = (error: unknown): boolean =>
202
225
  error instanceof Error && error.message.toLowerCase().includes('no such table')
@@ -8,23 +8,26 @@ import type {
8
8
  SqliteDbSession,
9
9
  } from '@livestore/common'
10
10
  import { SqliteDbHelper, SqliteError } from '@livestore/common'
11
+ import type { CfTypes } from '@livestore/common-cf'
11
12
  import { EventSequenceNumber } from '@livestore/common/schema'
12
13
  import { Effect } from '@livestore/utils/effect'
13
- import type * as CfWorker from './cf-types.ts'
14
14
 
15
15
  // Simplified prepared statement implementation using only public API
16
16
  class CloudflarePreparedStatement implements PreparedStatement {
17
- private sqlStorage: CfWorker.SqlStorage
17
+ private sqlStorage: CfTypes.SqlStorage
18
18
  public readonly sql: string
19
19
 
20
- constructor(sqlStorage: CfWorker.SqlStorage, sql: string) {
20
+ constructor(sqlStorage: CfTypes.SqlStorage, sql: string) {
21
21
  this.sqlStorage = sqlStorage
22
22
  this.sql = sql
23
23
  }
24
24
 
25
25
  execute = (bindValues?: PreparedBindValues, options?: { onRowsChanged?: (count: number) => void }) => {
26
26
  try {
27
- const cursor = this.sqlStorage.exec(this.sql, ...(bindValues ? Object.values(bindValues) : []))
27
+ if (isTransactionControlStatement(this.sql) === true) return
28
+
29
+
30
+ const cursor = this.sqlStorage.exec(this.sql, ...(bindValues !== undefined ? Object.values(bindValues) : []))
28
31
 
29
32
  // Count affected rows by iterating through cursor
30
33
  let changedCount = 0
@@ -32,7 +35,7 @@ class CloudflarePreparedStatement implements PreparedStatement {
32
35
  changedCount++
33
36
  }
34
37
 
35
- if (options?.onRowsChanged) {
38
+ if (options?.onRowsChanged !== undefined) {
36
39
  options.onRowsChanged(changedCount)
37
40
  }
38
41
  } catch (e) {
@@ -46,9 +49,9 @@ class CloudflarePreparedStatement implements PreparedStatement {
46
49
 
47
50
  select = <T>(bindValues?: PreparedBindValues): readonly T[] => {
48
51
  try {
49
- const cursor = this.sqlStorage.exec<Record<string, CfWorker.SqlStorageValue>>(
52
+ const cursor = this.sqlStorage.exec<Record<string, CfTypes.SqlStorageValue>>(
50
53
  this.sql,
51
- ...(bindValues ? Object.values(bindValues) : []),
54
+ ...(bindValues !== undefined ? Object.values(bindValues) : []),
52
55
  )
53
56
  const results: T[] = []
54
57
 
@@ -84,12 +87,12 @@ type CloudflareDatabaseInput =
84
87
  _tag: 'file'
85
88
  // databaseName: string
86
89
  // directory: string
87
- db: CfWorker.SqlStorage
90
+ db: CfTypes.SqlStorage
88
91
  configureDb: (db: SqliteDb) => void
89
92
  }
90
93
  | {
91
94
  _tag: 'in-memory'
92
- db: CfWorker.SqlStorage
95
+ db: CfTypes.SqlStorage
93
96
  configureDb: (db: SqliteDb) => void
94
97
  }
95
98
 
@@ -97,14 +100,12 @@ export type MakeCloudflareSqliteDb = MakeSqliteDb<Metadata, CloudflareDatabaseIn
97
100
 
98
101
  export const makeSqliteDb: MakeCloudflareSqliteDb = (input: CloudflareDatabaseInput) =>
99
102
  Effect.gen(function* () {
100
- // console.log('makeSqliteDb', input)
101
103
  if (input._tag === 'in-memory') {
102
104
  return makeSqliteDb_<Metadata>({
103
105
  sqlStorage: input.db,
104
106
  metadata: {
105
107
  _tag: 'file' as const,
106
108
  dbPointer: 0,
107
- // persistenceInfo: { fileName: ':memory:' },
108
109
  persistenceInfo: { fileName: 'cf' },
109
110
  input,
110
111
  configureDb: input.configureDb,
@@ -118,7 +119,6 @@ export const makeSqliteDb: MakeCloudflareSqliteDb = (input: CloudflareDatabaseIn
118
119
  metadata: {
119
120
  _tag: 'file' as const,
120
121
  dbPointer: 0,
121
- // persistenceInfo: { fileName: `${input.directory}/${input.databaseName}` },
122
122
  persistenceInfo: { fileName: 'cf' },
123
123
  input,
124
124
  configureDb: input.configureDb,
@@ -130,14 +130,13 @@ export const makeSqliteDb: MakeCloudflareSqliteDb = (input: CloudflareDatabaseIn
130
130
  export const makeSqliteDb_ = <
131
131
  TMetadata extends {
132
132
  persistenceInfo: PersistenceInfo
133
- // deleteDb: () => void
134
133
  configureDb: (db: SqliteDb<TMetadata>) => void
135
134
  },
136
135
  >({
137
136
  sqlStorage,
138
137
  metadata,
139
138
  }: {
140
- sqlStorage: CfWorker.SqlStorage
139
+ sqlStorage: CfTypes.SqlStorage
141
140
  metadata: TMetadata
142
141
  }): SqliteDb<TMetadata> => {
143
142
  const preparedStmts: PreparedStatement[] = []
@@ -149,7 +148,7 @@ export const makeSqliteDb_ = <
149
148
  metadata,
150
149
  debug: {
151
150
  // Setting initially to root but will be set to correct value shortly after
152
- head: EventSequenceNumber.ROOT,
151
+ head: EventSequenceNumber.Client.ROOT,
153
152
  },
154
153
  prepare: (queryStr) => {
155
154
  try {
@@ -188,18 +187,13 @@ export const makeSqliteDb_ = <
188
187
  destroy: () => {
189
188
  sqliteDb.close()
190
189
 
191
- // metadata.deleteDb()
192
190
  throw new SqliteError({
193
191
  code: -1,
194
192
  cause: 'Database destroy not supported with public SqlStorage API',
195
193
  })
196
-
197
- // if (metadata._tag === 'opfs') {
198
- // metadata.vfs.resetAccessHandle(metadata.fileName)
199
- // }
200
194
  },
201
195
  close: () => {
202
- if (isClosed) {
196
+ if (isClosed === true) {
203
197
  return
204
198
  }
205
199
 
@@ -259,3 +253,40 @@ export const makeSqliteDb_ = <
259
253
 
260
254
  return sqliteDb
261
255
  }
256
+
257
+ /**
258
+ * CF DO SQLite rejects SQL-level transaction control and requires `storage.transactionSync()` instead.
259
+ * The current adapter only detects and suppresses those SQL statements. It does not yet translate the
260
+ * caller's transaction intent into a shared Durable Object storage transaction.
261
+ *
262
+ * ## Consistency implications
263
+ *
264
+ * `LeaderSyncProcessor.materializeEventsBatch()` wraps both `dbState` and `dbEventlog` in
265
+ * `BEGIN`/`COMMIT` to keep them consistent. Because this adapter drops those statements,
266
+ * eventlog INSERTs are auto-committed individually while `dbState` (VFS-backed) still has
267
+ * real transaction boundaries. If a batch partially fails, earlier eventlog rows survive
268
+ * while `dbState` rolls back.
269
+ *
270
+ * This is safe because:
271
+ * - The eventlog is append-only and idempotent — replaying already-inserted events is a no-op.
272
+ * - State is always rebuildable from the eventlog on cold start (`recreateDb`).
273
+ * - A Durable Object is single-threaded, so no concurrent reader can observe the
274
+ * intermediate inconsistency.
275
+ *
276
+ * Uses prefix matching to cover all SQLite variants:
277
+ * - `BEGIN [DEFERRED | IMMEDIATE | EXCLUSIVE] [TRANSACTION]`
278
+ * - `COMMIT [TRANSACTION]` / `END [TRANSACTION]`
279
+ * - `ROLLBACK [TRANSACTION] [TO [SAVEPOINT] name]`
280
+ * - `SAVEPOINT name` / `RELEASE [SAVEPOINT] name`
281
+ */
282
+ const isTransactionControlStatement = (sql: string): boolean => {
283
+ const upper = sql.trim().toUpperCase()
284
+ return (
285
+ upper.startsWith('BEGIN') === true ||
286
+ upper.startsWith('COMMIT') === true ||
287
+ upper.startsWith('END') === true ||
288
+ upper.startsWith('ROLLBACK') === true ||
289
+ upper.startsWith('SAVEPOINT') === true ||
290
+ upper.startsWith('RELEASE') === true
291
+ )
292
+ }
package/src/mod.ts CHANGED
@@ -1,12 +1,5 @@
1
1
  import './polyfill.ts'
2
2
 
3
3
  export type { ClientDoWithRpcCallback } from '@livestore/common-cf'
4
+ export { type CreateStoreDoOptions, createStoreDo, createStoreDoPromise, type Env } from './create-store-do.ts'
4
5
  export { makeAdapter } from './make-adapter.ts'
5
- export {
6
- type CreateStoreDoOptions,
7
- createStoreDo,
8
- createStoreDoPromise,
9
- type Env,
10
- type MakeDurableObjectClass,
11
- type MakeDurableObjectClassOptions,
12
- } from './make-client-durable-object.ts'
@@ -1,2 +0,0 @@
1
- export { type D1Database, type D1Result, type DurableObject, type DurableObjectNamespace, type DurableObjectState, type DurableObjectStorage, type DurableObjectStub, type MessageEvent, Request, Response, Rpc, type SqlStorage, SqlStorageCursor, SqlStorageStatement, type SqlStorageValue, WebSocket, WebSocketPair, WebSocketRequestResponsePair, } from '@cloudflare/workers-types';
2
- //# sourceMappingURL=cf-types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cf-types.d.ts","sourceRoot":"","sources":["../src/cf-types.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,OAAO,EACP,QAAQ,EACR,GAAG,EACH,KAAK,UAAU,EACf,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,eAAe,EACpB,SAAS,EACT,aAAa,EACb,4BAA4B,GAC7B,MAAM,2BAA2B,CAAA"}
package/dist/cf-types.js DELETED
@@ -1,2 +0,0 @@
1
- export { Request, Response, Rpc, SqlStorageCursor, SqlStorageStatement, WebSocket, WebSocketPair, WebSocketRequestResponsePair, } from '@cloudflare/workers-types';
2
- //# sourceMappingURL=cf-types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cf-types.js","sourceRoot":"","sources":["../src/cf-types.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,OAAO,EACP,QAAQ,EACR,GAAG,EAEH,gBAAgB,EAChB,mBAAmB,EAEnB,SAAS,EACT,aAAa,EACb,4BAA4B,GAC7B,MAAM,2BAA2B,CAAA"}
@@ -1,48 +0,0 @@
1
- import type { UnexpectedError } from '@livestore/common';
2
- import { type LiveStoreSchema, type Store, type Unsubscribe } from '@livestore/livestore';
3
- import type * as CfSyncBackend from '@livestore/sync-cf/cf-worker';
4
- import { Effect } from '@livestore/utils/effect';
5
- import type * as CfWorker from './cf-types.ts';
6
- export type MakeDurableObjectClassOptions<TSchema extends LiveStoreSchema = LiveStoreSchema.Any> = {
7
- schema: TSchema;
8
- clientId: string;
9
- sessionId: string;
10
- onStoreReady?: (store: Store<TSchema>) => Effect.SyncOrPromiseOrEffect<void, UnexpectedError>;
11
- registerQueries?: (store: Store<TSchema>) => Effect.SyncOrPromiseOrEffect<ReadonlyArray<Unsubscribe>>;
12
- syncBackendUrl?: string;
13
- handleCustomRequest?: (request: CfWorker.Request, ensureStore: Effect.Effect<Store<TSchema>, UnexpectedError, never>) => Effect.SyncOrPromiseOrEffect<CfWorker.Response | undefined, UnexpectedError>;
14
- };
15
- export type Env = {
16
- SYNC_BACKEND_DO: CfWorker.DurableObjectNamespace;
17
- };
18
- export type MakeDurableObjectClass = <TSchema extends LiveStoreSchema = LiveStoreSchema.Any>(options: MakeDurableObjectClassOptions<TSchema>) => {
19
- new (ctx: CfWorker.DurableObjectState, env: Env): CfWorker.DurableObject & CfWorker.Rpc.DurableObjectBranded;
20
- };
21
- /**
22
- * Options used to initialize the LiveStore Durable Object runtime.
23
- */
24
- export type CreateStoreDoOptions<TSchema extends LiveStoreSchema = LiveStoreSchema.Any> = {
25
- /** LiveStore schema that defines state, migrations, and validators. */
26
- schema: TSchema;
27
- /** Logical identifier for the store instance persisted inside the Durable Object. */
28
- storeId: string;
29
- /** Unique identifier for the client that owns the Durable Object instance. */
30
- clientId: string;
31
- /** Identifier for the LiveStore session running inside the Durable Object. */
32
- sessionId: string;
33
- /** Cloudflare Durable Object storage binding backing the local SQLite files. */
34
- storage: CfWorker.DurableObjectStorage;
35
- /** RPC stub pointing at the sync backend Durable Object used for replication. */
36
- syncBackendDurableObject: CfWorker.DurableObjectStub<CfSyncBackend.SyncBackendRpcInterface>;
37
- /** Durable Object identifier for the current instance, forwarded to the sync backend. */
38
- durableObjectId: string;
39
- /** Binding name Cloudflare uses to reach this Durable Object from other workers. */
40
- bindingName: string;
41
- /** Enables live pull mode to receive sync updates via Durable Object RPC callbacks. */
42
- livePull?: boolean;
43
- /** Clears existing Durable Object persistence before bootstrapping the store. */
44
- resetPersistence?: boolean;
45
- };
46
- export declare const createStoreDo: <TSchema extends LiveStoreSchema = LiveStoreSchema.Any>({ schema, storeId, clientId, sessionId, storage, syncBackendDurableObject, durableObjectId, bindingName, livePull, resetPersistence, }: CreateStoreDoOptions<TSchema>) => Effect.Effect<Store<TSchema, {}>, UnexpectedError, never>;
47
- export declare const createStoreDoPromise: <TSchema extends LiveStoreSchema = LiveStoreSchema.Any>(options: CreateStoreDoOptions<TSchema>) => Promise<Store<TSchema, {}>>;
48
- //# sourceMappingURL=make-client-durable-object.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"make-client-durable-object.d.ts","sourceRoot":"","sources":["../src/make-client-durable-object.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EAAe,KAAK,eAAe,EAAe,KAAK,KAAK,EAAE,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACnH,OAAO,KAAK,KAAK,aAAa,MAAM,8BAA8B,CAAA;AAElE,OAAO,EAAE,MAAM,EAA2B,MAAM,yBAAyB,CAAA;AACzE,OAAO,KAAK,KAAK,QAAQ,MAAM,eAAe,CAAA;AAK9C,MAAM,MAAM,6BAA6B,CAAC,OAAO,SAAS,eAAe,GAAG,eAAe,CAAC,GAAG,IAAI;IACjG,MAAM,EAAE,OAAO,CAAA;IAEf,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,MAAM,CAAC,qBAAqB,CAAC,IAAI,EAAE,eAAe,CAAC,CAAA;IAG7F,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,MAAM,CAAC,qBAAqB,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAA;IACrG,cAAc,CAAC,EAAE,MAAM,CAAA;IAEvB,mBAAmB,CAAC,EAAE,CACpB,OAAO,EAAE,QAAQ,CAAC,OAAO,EACzB,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,eAAe,EAAE,KAAK,CAAC,KAC/D,MAAM,CAAC,qBAAqB,CAAC,QAAQ,CAAC,QAAQ,GAAG,SAAS,EAAE,eAAe,CAAC,CAAA;CAClF,CAAA;AAED,MAAM,MAAM,GAAG,GAAG;IAChB,eAAe,EAAE,QAAQ,CAAC,sBAAsB,CAAA;CACjD,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,SAAS,eAAe,GAAG,eAAe,CAAC,GAAG,EACzF,OAAO,EAAE,6BAA6B,CAAC,OAAO,CAAC,KAC5C;IACH,KAAK,GAAG,EAAE,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE,GAAG,GAAG,QAAQ,CAAC,aAAa,GAAG,QAAQ,CAAC,GAAG,CAAC,oBAAoB,CAAA;CAC7G,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,oBAAoB,CAAC,OAAO,SAAS,eAAe,GAAG,eAAe,CAAC,GAAG,IAAI;IACxF,uEAAuE;IACvE,MAAM,EAAE,OAAO,CAAA;IACf,qFAAqF;IACrF,OAAO,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAA;IAChB,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAA;IACjB,gFAAgF;IAChF,OAAO,EAAE,QAAQ,CAAC,oBAAoB,CAAA;IACtC,iFAAiF;IACjF,wBAAwB,EAAE,QAAQ,CAAC,iBAAiB,CAAC,aAAa,CAAC,uBAAuB,CAAC,CAAA;IAC3F,yFAAyF;IACzF,eAAe,EAAE,MAAM,CAAA;IACvB,oFAAoF;IACpF,WAAW,EAAE,MAAM,CAAA;IACnB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,iFAAiF;IACjF,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B,CAAA;AAED,eAAO,MAAM,aAAa,GAAI,OAAO,SAAS,eAAe,GAAG,eAAe,CAAC,GAAG,EAAE,wIAWlF,oBAAoB,CAAC,OAAO,CAAC,8DAsB5B,CAAA;AAEJ,eAAO,MAAM,oBAAoB,GAAI,OAAO,SAAS,eAAe,GAAG,eAAe,CAAC,GAAG,EACxF,SAAS,oBAAoB,CAAC,OAAO,CAAC,gCAOrC,CAAA"}
@@ -1,26 +0,0 @@
1
- import { createStore, provideOtel } from '@livestore/livestore';
2
- import { makeDoRpcSync } from '@livestore/sync-cf/client';
3
- import { Effect, Logger, LogLevel, Scope } from '@livestore/utils/effect';
4
- import { makeAdapter } from "./make-adapter.js";
5
- export const createStoreDo = ({ schema, storeId, clientId, sessionId, storage, syncBackendDurableObject, durableObjectId, bindingName, livePull = false, resetPersistence = false, }) => Effect.gen(function* () {
6
- const scope = yield* Scope.make();
7
- const adapter = makeAdapter({
8
- clientId,
9
- sessionId,
10
- storage,
11
- resetPersistence,
12
- syncOptions: {
13
- backend: makeDoRpcSync({
14
- syncBackendStub: syncBackendDurableObject,
15
- durableObjectContext: { bindingName, durableObjectId },
16
- }),
17
- livePull, // Uses DO RPC callbacks for reactive pull
18
- // backend: makeHttpSync({ url: `http://localhost:8787`, livePull: { pollInterval: 500 } }),
19
- initialSyncOptions: { _tag: 'Blocking', timeout: 500 },
20
- // backend: makeWsSyncProviderClient({ durableObject: syncBackendDurableObject }),
21
- },
22
- });
23
- return yield* createStore({ schema, adapter, storeId }).pipe(Scope.extend(scope), provideOtel({}));
24
- });
25
- export const createStoreDoPromise = (options) => createStoreDo(options).pipe(Logger.withMinimumLogLevel(LogLevel.Debug), Effect.provide(Logger.consoleWithThread('DoClient')), Effect.tapCauseLogPretty, Effect.runPromise);
26
- //# sourceMappingURL=make-client-durable-object.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"make-client-durable-object.js","sourceRoot":"","sources":["../src/make-client-durable-object.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAwB,WAAW,EAAgC,MAAM,sBAAsB,CAAA;AAEnH,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAEzE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAyD/C,MAAM,CAAC,MAAM,aAAa,GAAG,CAAwD,EACnF,MAAM,EACN,OAAO,EACP,QAAQ,EACR,SAAS,EACT,OAAO,EACP,wBAAwB,EACxB,eAAe,EACf,WAAW,EACX,QAAQ,GAAG,KAAK,EAChB,gBAAgB,GAAG,KAAK,GACM,EAAE,EAAE,CAClC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;IAEjC,MAAM,OAAO,GAAG,WAAW,CAAC;QAC1B,QAAQ;QACR,SAAS;QACT,OAAO;QACP,gBAAgB;QAChB,WAAW,EAAE;YACX,OAAO,EAAE,aAAa,CAAC;gBACrB,eAAe,EAAE,wBAAwB;gBACzC,oBAAoB,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE;aACvD,CAAC;YACF,QAAQ,EAAE,0CAA0C;YACpD,4FAA4F;YAC5F,kBAAkB,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE;YACtD,kFAAkF;SACnF;KACF,CAAC,CAAA;IAEF,OAAO,KAAK,CAAC,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,CAAA;AACpG,CAAC,CAAC,CAAA;AAEJ,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,OAAsC,EACtC,EAAE,CACF,aAAa,CAAC,OAAO,CAAC,CAAC,IAAI,CACzB,MAAM,CAAC,mBAAmB,CAAC,QAAQ,CAAC,KAAK,CAAC,EAC1C,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC,EACpD,MAAM,CAAC,iBAAiB,EACxB,MAAM,CAAC,UAAU,CAClB,CAAA"}
package/src/cf-types.ts DELETED
@@ -1,20 +0,0 @@
1
- export {
2
- type D1Database,
3
- type D1Result,
4
- type DurableObject,
5
- type DurableObjectNamespace,
6
- type DurableObjectState,
7
- type DurableObjectStorage,
8
- type DurableObjectStub,
9
- type MessageEvent,
10
- Request,
11
- Response,
12
- Rpc,
13
- type SqlStorage,
14
- SqlStorageCursor,
15
- SqlStorageStatement,
16
- type SqlStorageValue,
17
- WebSocket,
18
- WebSocketPair,
19
- WebSocketRequestResponsePair,
20
- } from '@cloudflare/workers-types'
@@ -1,107 +0,0 @@
1
- import type { UnexpectedError } from '@livestore/common'
2
- import { createStore, type LiveStoreSchema, provideOtel, type Store, type Unsubscribe } from '@livestore/livestore'
3
- import type * as CfSyncBackend from '@livestore/sync-cf/cf-worker'
4
- import { makeDoRpcSync } from '@livestore/sync-cf/client'
5
- import { Effect, Logger, LogLevel, Scope } from '@livestore/utils/effect'
6
- import type * as CfWorker from './cf-types.ts'
7
- import { makeAdapter } from './make-adapter.ts'
8
-
9
- declare class Response extends CfWorker.Response {}
10
-
11
- export type MakeDurableObjectClassOptions<TSchema extends LiveStoreSchema = LiveStoreSchema.Any> = {
12
- schema: TSchema
13
- // storeId: string
14
- clientId: string
15
- sessionId: string
16
- onStoreReady?: (store: Store<TSchema>) => Effect.SyncOrPromiseOrEffect<void, UnexpectedError>
17
- // makeStore?: (adapter: Adapter) => Promise<Store<TSchema>>
18
- // onLiveStoreEvent?: (event: LiveStoreEvent.ForSchema<TSchema>) => Promise<void>
19
- registerQueries?: (store: Store<TSchema>) => Effect.SyncOrPromiseOrEffect<ReadonlyArray<Unsubscribe>>
20
- syncBackendUrl?: string
21
- // Hook for custom request handling (e.g., testing endpoints)
22
- handleCustomRequest?: (
23
- request: CfWorker.Request,
24
- ensureStore: Effect.Effect<Store<TSchema>, UnexpectedError, never>,
25
- ) => Effect.SyncOrPromiseOrEffect<CfWorker.Response | undefined, UnexpectedError>
26
- }
27
-
28
- export type Env = {
29
- SYNC_BACKEND_DO: CfWorker.DurableObjectNamespace
30
- }
31
-
32
- export type MakeDurableObjectClass = <TSchema extends LiveStoreSchema = LiveStoreSchema.Any>(
33
- options: MakeDurableObjectClassOptions<TSchema>,
34
- ) => {
35
- new (ctx: CfWorker.DurableObjectState, env: Env): CfWorker.DurableObject & CfWorker.Rpc.DurableObjectBranded
36
- }
37
-
38
- /**
39
- * Options used to initialize the LiveStore Durable Object runtime.
40
- */
41
- export type CreateStoreDoOptions<TSchema extends LiveStoreSchema = LiveStoreSchema.Any> = {
42
- /** LiveStore schema that defines state, migrations, and validators. */
43
- schema: TSchema
44
- /** Logical identifier for the store instance persisted inside the Durable Object. */
45
- storeId: string
46
- /** Unique identifier for the client that owns the Durable Object instance. */
47
- clientId: string
48
- /** Identifier for the LiveStore session running inside the Durable Object. */
49
- sessionId: string
50
- /** Cloudflare Durable Object storage binding backing the local SQLite files. */
51
- storage: CfWorker.DurableObjectStorage
52
- /** RPC stub pointing at the sync backend Durable Object used for replication. */
53
- syncBackendDurableObject: CfWorker.DurableObjectStub<CfSyncBackend.SyncBackendRpcInterface>
54
- /** Durable Object identifier for the current instance, forwarded to the sync backend. */
55
- durableObjectId: string
56
- /** Binding name Cloudflare uses to reach this Durable Object from other workers. */
57
- bindingName: string
58
- /** Enables live pull mode to receive sync updates via Durable Object RPC callbacks. */
59
- livePull?: boolean
60
- /** Clears existing Durable Object persistence before bootstrapping the store. */
61
- resetPersistence?: boolean
62
- }
63
-
64
- export const createStoreDo = <TSchema extends LiveStoreSchema = LiveStoreSchema.Any>({
65
- schema,
66
- storeId,
67
- clientId,
68
- sessionId,
69
- storage,
70
- syncBackendDurableObject,
71
- durableObjectId,
72
- bindingName,
73
- livePull = false,
74
- resetPersistence = false,
75
- }: CreateStoreDoOptions<TSchema>) =>
76
- Effect.gen(function* () {
77
- const scope = yield* Scope.make()
78
-
79
- const adapter = makeAdapter({
80
- clientId,
81
- sessionId,
82
- storage,
83
- resetPersistence,
84
- syncOptions: {
85
- backend: makeDoRpcSync({
86
- syncBackendStub: syncBackendDurableObject,
87
- durableObjectContext: { bindingName, durableObjectId },
88
- }),
89
- livePull, // Uses DO RPC callbacks for reactive pull
90
- // backend: makeHttpSync({ url: `http://localhost:8787`, livePull: { pollInterval: 500 } }),
91
- initialSyncOptions: { _tag: 'Blocking', timeout: 500 },
92
- // backend: makeWsSyncProviderClient({ durableObject: syncBackendDurableObject }),
93
- },
94
- })
95
-
96
- return yield* createStore({ schema, adapter, storeId }).pipe(Scope.extend(scope), provideOtel({}))
97
- })
98
-
99
- export const createStoreDoPromise = <TSchema extends LiveStoreSchema = LiveStoreSchema.Any>(
100
- options: CreateStoreDoOptions<TSchema>,
101
- ) =>
102
- createStoreDo(options).pipe(
103
- Logger.withMinimumLogLevel(LogLevel.Debug),
104
- Effect.provide(Logger.consoleWithThread('DoClient')),
105
- Effect.tapCauseLogPretty,
106
- Effect.runPromise,
107
- )