@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.
- package/LICENSE +201 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/WebSocket.d.ts +14 -0
- package/dist/WebSocket.d.ts.map +1 -0
- package/dist/WebSocket.js +52 -0
- package/dist/WebSocket.js.map +1 -0
- package/dist/cf-types.d.ts +2 -0
- package/dist/cf-types.d.ts.map +1 -0
- package/dist/cf-types.js +2 -0
- package/dist/cf-types.js.map +1 -0
- package/dist/make-adapter.d.ts +9 -0
- package/dist/make-adapter.d.ts.map +1 -0
- package/dist/make-adapter.js +87 -0
- package/dist/make-adapter.js.map +1 -0
- package/dist/make-client-durable-object.d.ts +34 -0
- package/dist/make-client-durable-object.d.ts.map +1 -0
- package/dist/make-client-durable-object.js +25 -0
- package/dist/make-client-durable-object.js.map +1 -0
- package/dist/make-sqlite-db.d.ts +31 -0
- package/dist/make-sqlite-db.d.ts.map +1 -0
- package/dist/make-sqlite-db.js +194 -0
- package/dist/make-sqlite-db.js.map +1 -0
- package/dist/mod.d.ts +5 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +4 -0
- package/dist/mod.js.map +1 -0
- package/dist/polyfill.d.ts +2 -0
- package/dist/polyfill.d.ts.map +1 -0
- package/dist/polyfill.js +40 -0
- package/dist/polyfill.js.map +1 -0
- package/dist/sync-provider-client.d.ts +12 -0
- package/dist/sync-provider-client.d.ts.map +1 -0
- package/dist/sync-provider-client.js +24 -0
- package/dist/sync-provider-client.js.map +1 -0
- package/dist/sync-provider-rpc-client.d.ts +2 -0
- package/dist/sync-provider-rpc-client.d.ts.map +1 -0
- package/dist/sync-provider-rpc-client.js +139 -0
- package/dist/sync-provider-rpc-client.js.map +1 -0
- package/dist/sync-provider-ws-client.d.ts +2 -0
- package/dist/sync-provider-ws-client.d.ts.map +1 -0
- package/dist/sync-provider-ws-client.js +40 -0
- package/dist/sync-provider-ws-client.js.map +1 -0
- package/package.json +38 -0
- package/src/WebSocket.ts +69 -0
- package/src/cf-types.ts +20 -0
- package/src/make-adapter.ts +144 -0
- package/src/make-client-durable-object.ts +91 -0
- package/src/make-sqlite-db.ts +261 -0
- package/src/mod.ts +12 -0
- 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'
|
package/src/polyfill.ts
ADDED
|
@@ -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
|
+
}
|