@livestore/adapter-cloudflare 0.0.0-snapshot-8452e32b7fbfc129741b253b9c853f866b52129f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +201 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/WebSocket.d.ts +14 -0
  4. package/dist/WebSocket.d.ts.map +1 -0
  5. package/dist/WebSocket.js +52 -0
  6. package/dist/WebSocket.js.map +1 -0
  7. package/dist/cf-types.d.ts +2 -0
  8. package/dist/cf-types.d.ts.map +1 -0
  9. package/dist/cf-types.js +2 -0
  10. package/dist/cf-types.js.map +1 -0
  11. package/dist/make-adapter.d.ts +9 -0
  12. package/dist/make-adapter.d.ts.map +1 -0
  13. package/dist/make-adapter.js +87 -0
  14. package/dist/make-adapter.js.map +1 -0
  15. package/dist/make-client-durable-object.d.ts +34 -0
  16. package/dist/make-client-durable-object.d.ts.map +1 -0
  17. package/dist/make-client-durable-object.js +25 -0
  18. package/dist/make-client-durable-object.js.map +1 -0
  19. package/dist/make-sqlite-db.d.ts +31 -0
  20. package/dist/make-sqlite-db.d.ts.map +1 -0
  21. package/dist/make-sqlite-db.js +194 -0
  22. package/dist/make-sqlite-db.js.map +1 -0
  23. package/dist/mod.d.ts +5 -0
  24. package/dist/mod.d.ts.map +1 -0
  25. package/dist/mod.js +4 -0
  26. package/dist/mod.js.map +1 -0
  27. package/dist/polyfill.d.ts +2 -0
  28. package/dist/polyfill.d.ts.map +1 -0
  29. package/dist/polyfill.js +40 -0
  30. package/dist/polyfill.js.map +1 -0
  31. package/dist/sync-provider-client.d.ts +12 -0
  32. package/dist/sync-provider-client.d.ts.map +1 -0
  33. package/dist/sync-provider-client.js +24 -0
  34. package/dist/sync-provider-client.js.map +1 -0
  35. package/dist/sync-provider-rpc-client.d.ts +2 -0
  36. package/dist/sync-provider-rpc-client.d.ts.map +1 -0
  37. package/dist/sync-provider-rpc-client.js +139 -0
  38. package/dist/sync-provider-rpc-client.js.map +1 -0
  39. package/dist/sync-provider-ws-client.d.ts +2 -0
  40. package/dist/sync-provider-ws-client.d.ts.map +1 -0
  41. package/dist/sync-provider-ws-client.js +40 -0
  42. package/dist/sync-provider-ws-client.js.map +1 -0
  43. package/package.json +38 -0
  44. package/src/WebSocket.ts +69 -0
  45. package/src/cf-types.ts +20 -0
  46. package/src/make-adapter.ts +144 -0
  47. package/src/make-client-durable-object.ts +91 -0
  48. package/src/make-sqlite-db.ts +261 -0
  49. package/src/mod.ts +12 -0
  50. package/src/polyfill.ts +44 -0
@@ -0,0 +1,261 @@
1
+ import type {
2
+ MakeSqliteDb,
3
+ PersistenceInfo,
4
+ PreparedBindValues,
5
+ PreparedStatement,
6
+ SqliteDb,
7
+ SqliteDbChangeset,
8
+ SqliteDbSession,
9
+ } from '@livestore/common'
10
+ import { SqliteDbHelper, SqliteError } from '@livestore/common'
11
+ import { EventSequenceNumber } from '@livestore/common/schema'
12
+ import { Effect } from '@livestore/utils/effect'
13
+ import type * as CfWorker from './cf-types.ts'
14
+
15
+ // Simplified prepared statement implementation using only public API
16
+ class CloudflarePreparedStatement implements PreparedStatement {
17
+ private sqlStorage: CfWorker.SqlStorage
18
+ public readonly sql: string
19
+
20
+ constructor(sqlStorage: CfWorker.SqlStorage, sql: string) {
21
+ this.sqlStorage = sqlStorage
22
+ this.sql = sql
23
+ }
24
+
25
+ execute = (bindValues?: PreparedBindValues, options?: { onRowsChanged?: (count: number) => void }) => {
26
+ try {
27
+ const cursor = this.sqlStorage.exec(this.sql, ...(bindValues ? Object.values(bindValues) : []))
28
+
29
+ // Count affected rows by iterating through cursor
30
+ let changedCount = 0
31
+ for (const _row of cursor) {
32
+ changedCount++
33
+ }
34
+
35
+ if (options?.onRowsChanged) {
36
+ options.onRowsChanged(changedCount)
37
+ }
38
+ } catch (e) {
39
+ throw new SqliteError({
40
+ query: { bindValues: bindValues ?? {}, sql: this.sql },
41
+ code: (e as any).code ?? -1,
42
+ cause: e,
43
+ })
44
+ }
45
+ }
46
+
47
+ select = <T>(bindValues?: PreparedBindValues): readonly T[] => {
48
+ try {
49
+ const cursor = this.sqlStorage.exec<Record<string, CfWorker.SqlStorageValue>>(
50
+ this.sql,
51
+ ...(bindValues ? Object.values(bindValues) : []),
52
+ )
53
+ const results: T[] = []
54
+
55
+ for (const row of cursor) {
56
+ results.push(row as T)
57
+ }
58
+
59
+ return results
60
+ } catch (e) {
61
+ throw new SqliteError({
62
+ query: { bindValues: bindValues ?? {}, sql: this.sql },
63
+ code: (e as any).code ?? -1,
64
+ cause: e,
65
+ })
66
+ }
67
+ }
68
+
69
+ finalize = () => {
70
+ // No-op for public API - statements are automatically cleaned up
71
+ }
72
+ }
73
+
74
+ type Metadata = {
75
+ _tag: 'file'
76
+ dbPointer: number
77
+ persistenceInfo: PersistenceInfo
78
+ input: CloudflareDatabaseInput
79
+ configureDb: (db: SqliteDb) => void
80
+ }
81
+
82
+ type CloudflareDatabaseInput =
83
+ | {
84
+ _tag: 'file'
85
+ // databaseName: string
86
+ // directory: string
87
+ db: CfWorker.SqlStorage
88
+ configureDb: (db: SqliteDb) => void
89
+ }
90
+ | {
91
+ _tag: 'in-memory'
92
+ db: CfWorker.SqlStorage
93
+ configureDb: (db: SqliteDb) => void
94
+ }
95
+
96
+ export type MakeCloudflareSqliteDb = MakeSqliteDb<Metadata, CloudflareDatabaseInput, { _tag: 'cloudflare' } & Metadata>
97
+
98
+ export const makeSqliteDb: MakeCloudflareSqliteDb = (input: CloudflareDatabaseInput) =>
99
+ Effect.gen(function* () {
100
+ // console.log('makeSqliteDb', input)
101
+ if (input._tag === 'in-memory') {
102
+ return makeSqliteDb_<Metadata>({
103
+ sqlStorage: input.db,
104
+ metadata: {
105
+ _tag: 'file' as const,
106
+ dbPointer: 0,
107
+ // persistenceInfo: { fileName: ':memory:' },
108
+ persistenceInfo: { fileName: 'cf' },
109
+ input,
110
+ configureDb: input.configureDb,
111
+ },
112
+ }) as any
113
+ }
114
+
115
+ if (input._tag === 'file') {
116
+ return makeSqliteDb_<Metadata>({
117
+ sqlStorage: input.db,
118
+ metadata: {
119
+ _tag: 'file' as const,
120
+ dbPointer: 0,
121
+ // persistenceInfo: { fileName: `${input.directory}/${input.databaseName}` },
122
+ persistenceInfo: { fileName: 'cf' },
123
+ input,
124
+ configureDb: input.configureDb,
125
+ },
126
+ }) as any
127
+ }
128
+ })
129
+
130
+ export const makeSqliteDb_ = <
131
+ TMetadata extends {
132
+ persistenceInfo: PersistenceInfo
133
+ // deleteDb: () => void
134
+ configureDb: (db: SqliteDb<TMetadata>) => void
135
+ },
136
+ >({
137
+ sqlStorage,
138
+ metadata,
139
+ }: {
140
+ sqlStorage: CfWorker.SqlStorage
141
+ metadata: TMetadata
142
+ }): SqliteDb<TMetadata> => {
143
+ const preparedStmts: PreparedStatement[] = []
144
+
145
+ let isClosed = false
146
+
147
+ const sqliteDb: SqliteDb<TMetadata> = {
148
+ _tag: 'SqliteDb',
149
+ metadata,
150
+ debug: {
151
+ // Setting initially to root but will be set to correct value shortly after
152
+ head: EventSequenceNumber.ROOT,
153
+ },
154
+ prepare: (queryStr) => {
155
+ try {
156
+ const preparedStmt = new CloudflarePreparedStatement(sqlStorage, queryStr.trim())
157
+ preparedStmts.push(preparedStmt)
158
+ return preparedStmt
159
+ } catch (e) {
160
+ throw new SqliteError({
161
+ query: { sql: queryStr, bindValues: {} },
162
+ code: (e as any).code ?? -1,
163
+ cause: e,
164
+ })
165
+ }
166
+ },
167
+ export: () => {
168
+ // NOTE: Database export not supported with public API
169
+ // This functionality requires undocumented serialize() method
170
+ // throw new SqliteError({
171
+ // query: { sql: 'export', bindValues: {} },
172
+ // code: -1,
173
+ // cause: 'Database export not supported with public SqlStorage API',
174
+ // })
175
+ return new Uint8Array()
176
+ },
177
+ execute: SqliteDbHelper.makeExecute((queryStr, bindValues, options) => {
178
+ const stmt = sqliteDb.prepare(queryStr)
179
+ stmt.execute(bindValues, options)
180
+ stmt.finalize()
181
+ }),
182
+ select: SqliteDbHelper.makeSelect((queryStr, bindValues) => {
183
+ const stmt = sqliteDb.prepare(queryStr)
184
+ const results = stmt.select(bindValues)
185
+ stmt.finalize()
186
+ return results as ReadonlyArray<any>
187
+ }),
188
+ destroy: () => {
189
+ sqliteDb.close()
190
+
191
+ // metadata.deleteDb()
192
+ throw new SqliteError({
193
+ code: -1,
194
+ cause: 'Database destroy not supported with public SqlStorage API',
195
+ })
196
+
197
+ // if (metadata._tag === 'opfs') {
198
+ // metadata.vfs.resetAccessHandle(metadata.fileName)
199
+ // }
200
+ },
201
+ close: () => {
202
+ if (isClosed) {
203
+ return
204
+ }
205
+
206
+ for (const stmt of preparedStmts) {
207
+ stmt.finalize()
208
+ }
209
+
210
+ // NOTE: Database close not supported with public API
211
+ // The database is automatically cleaned up by the runtime
212
+ isClosed = true
213
+ },
214
+ import: (_source) => {
215
+ // NOTE: Database import not supported with public API
216
+ // This functionality requires undocumented deserialize() and backup() methods
217
+ // throw new SqliteError({
218
+ // query: { sql: 'import', bindValues: {} },
219
+ // code: -1,
220
+ // cause: 'Database import not supported with public SqlStorage API',
221
+ // })
222
+ },
223
+ session: () => {
224
+ // NOTE: Session tracking not supported with public API
225
+ // This functionality requires undocumented session_* methods
226
+ // throw new SqliteError({
227
+ // query: { sql: 'session', bindValues: {} },
228
+ // code: -1,
229
+ // cause: 'Session tracking not supported with public SqlStorage API',
230
+ // })
231
+ return {
232
+ changeset: () => new Uint8Array(),
233
+ finish: () => {},
234
+ } satisfies SqliteDbSession
235
+ },
236
+ makeChangeset: (_data) => {
237
+ // NOTE: Changeset operations not supported with public API
238
+ // This functionality requires undocumented changeset_* methods
239
+ const changeset = {
240
+ invert: () => {
241
+ throw new SqliteError({
242
+ code: -1,
243
+ cause: 'Changeset invert not supported with public SqlStorage API',
244
+ })
245
+ },
246
+ apply: () => {
247
+ throw new SqliteError({
248
+ code: -1,
249
+ cause: 'Changeset apply not supported with public SqlStorage API',
250
+ })
251
+ },
252
+ } satisfies SqliteDbChangeset
253
+
254
+ return changeset
255
+ },
256
+ } satisfies SqliteDb<TMetadata>
257
+
258
+ metadata.configureDb(sqliteDb)
259
+
260
+ return sqliteDb
261
+ }
package/src/mod.ts ADDED
@@ -0,0 +1,12 @@
1
+ import './polyfill.ts'
2
+
3
+ export type { ClientDoWithRpcCallback } from '@livestore/common-cf'
4
+ 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'
@@ -0,0 +1,44 @@
1
+ /// <reference lib="dom" />
2
+
3
+ // TODO remove all unused polyfills once we're closer to the release
4
+ globalThis.performance = globalThis.performance ?? {}
5
+ globalThis.performance.mark = globalThis.performance.mark ?? (() => {})
6
+ globalThis.performance.measure = globalThis.performance.measure ?? (() => {})
7
+ globalThis.performance.now = globalThis.performance.now ?? (() => -1)
8
+
9
+ if (typeof globalThis.location === 'undefined') {
10
+ globalThis.location = {
11
+ href: 'https://worker.cloudflare.com/',
12
+ origin: 'https://worker.cloudflare.com',
13
+ protocol: 'https:',
14
+ host: 'worker.cloudflare.com',
15
+ hostname: 'worker.cloudflare.com',
16
+ port: '',
17
+ pathname: '/',
18
+ search: '',
19
+ hash: '',
20
+ } as Location
21
+ }
22
+
23
+ if (typeof globalThis.document === 'undefined') {
24
+ globalThis.document = {
25
+ createElement: () => ({ href: '', pathname: '', search: '', origin: '' }),
26
+ head: { appendChild: () => {} },
27
+ } as any
28
+ }
29
+
30
+ // WeakRef polyfill for Cloudflare Workers
31
+ if (typeof WeakRef === 'undefined') {
32
+ // @ts-expect-error
33
+ globalThis.WeakRef = class WeakRef<T> {
34
+ private target: T | undefined
35
+
36
+ constructor(target: T) {
37
+ this.target = target
38
+ }
39
+
40
+ deref(): T | undefined {
41
+ return this.target
42
+ }
43
+ }
44
+ }