on-zero 0.7.2-1784530701923 → 0.7.2

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 (260) hide show
  1. package/dist/cjs/asyncTasks.cjs +51 -0
  2. package/dist/cjs/asyncTasks.native.js +84 -0
  3. package/dist/cjs/asyncTasks.native.js.map +1 -0
  4. package/dist/cjs/createZeroDirectServer.cjs +269 -0
  5. package/dist/cjs/createZeroDirectServer.native.js +287 -0
  6. package/dist/cjs/createZeroDirectServer.native.js.map +1 -0
  7. package/dist/cjs/createZeroSQLiteServer.cjs +50 -0
  8. package/dist/cjs/createZeroSQLiteServer.native.js +58 -0
  9. package/dist/cjs/createZeroSQLiteServer.native.js.map +1 -0
  10. package/dist/cjs/createZeroSQLiteServer.test.cjs +595 -0
  11. package/dist/cjs/createZeroSQLiteServer.test.native.js +647 -0
  12. package/dist/cjs/createZeroSQLiteServer.test.native.js.map +1 -0
  13. package/dist/cjs/createZeroServer.cjs +82 -0
  14. package/dist/cjs/createZeroServer.native.js +93 -0
  15. package/dist/cjs/createZeroServer.native.js.map +1 -0
  16. package/dist/cjs/createZeroServer.test.cjs +378 -0
  17. package/dist/cjs/createZeroServer.test.native.js +448 -0
  18. package/dist/cjs/createZeroServer.test.native.js.map +1 -0
  19. package/dist/cjs/createZeroServerCore.cjs +211 -0
  20. package/dist/cjs/createZeroServerCore.native.js +355 -0
  21. package/dist/cjs/createZeroServerCore.native.js.map +1 -0
  22. package/dist/cjs/helpers/createMutators.cjs +3 -4
  23. package/dist/cjs/helpers/createMutators.native.js +3 -6
  24. package/dist/cjs/helpers/createMutators.native.js.map +1 -1
  25. package/dist/cjs/httpPull/auth.test.cjs +197 -0
  26. package/dist/cjs/httpPull/auth.test.native.js +279 -0
  27. package/dist/cjs/httpPull/auth.test.native.js.map +1 -0
  28. package/dist/cjs/httpPull/churn.test.cjs +132 -0
  29. package/dist/cjs/httpPull/churn.test.native.js +155 -0
  30. package/dist/cjs/httpPull/churn.test.native.js.map +1 -0
  31. package/dist/cjs/httpPull/completeAck.test.cjs +69 -0
  32. package/dist/cjs/httpPull/completeAck.test.native.js +76 -0
  33. package/dist/cjs/httpPull/completeAck.test.native.js.map +1 -0
  34. package/dist/cjs/httpPull/fixtureServer.cjs +340 -0
  35. package/dist/cjs/httpPull/fixtureServer.native.js +534 -0
  36. package/dist/cjs/httpPull/fixtureServer.native.js.map +1 -0
  37. package/dist/cjs/httpPull/integration.test.cjs +53 -0
  38. package/dist/cjs/httpPull/integration.test.native.js +60 -0
  39. package/dist/cjs/httpPull/integration.test.native.js.map +1 -0
  40. package/dist/cjs/httpPull/rebase.test.cjs +360 -0
  41. package/dist/cjs/httpPull/rebase.test.native.js +420 -0
  42. package/dist/cjs/httpPull/rebase.test.native.js.map +1 -0
  43. package/dist/cjs/httpPull/relations.test.cjs +107 -0
  44. package/dist/cjs/httpPull/relations.test.native.js +119 -0
  45. package/dist/cjs/httpPull/relations.test.native.js.map +1 -0
  46. package/dist/cjs/httpPull/testHarness.cjs +100 -0
  47. package/dist/cjs/httpPull/testHarness.native.js +112 -0
  48. package/dist/cjs/httpPull/testHarness.native.js.map +1 -0
  49. package/dist/cjs/index.cjs +1 -0
  50. package/dist/cjs/index.native.js +1 -0
  51. package/dist/cjs/index.native.js.map +1 -1
  52. package/dist/cjs/run.cjs +1 -1
  53. package/dist/cjs/run.native.js +1 -1
  54. package/dist/cjs/server.cjs +22 -0
  55. package/dist/cjs/server.native.js +4 -0
  56. package/dist/cjs/server.native.js.map +1 -0
  57. package/dist/cjs/sqliteRuntime.cjs +318 -0
  58. package/dist/cjs/sqliteRuntime.native.js +438 -0
  59. package/dist/cjs/sqliteRuntime.native.js.map +1 -0
  60. package/dist/cjs/state.cjs +1 -1
  61. package/dist/cjs/state.native.js +1 -1
  62. package/dist/cjs/syncHost.cjs +179 -0
  63. package/dist/cjs/syncHost.native.js +388 -0
  64. package/dist/cjs/syncHost.native.js.map +1 -0
  65. package/dist/cjs/syncHost.test.cjs +240 -0
  66. package/dist/cjs/syncHost.test.native.js +290 -0
  67. package/dist/cjs/syncHost.test.native.js.map +1 -0
  68. package/dist/cjs/zeroRunner.cjs +1 -1
  69. package/dist/cjs/zeroRunner.native.js +1 -1
  70. package/dist/esm/asyncTasks.mjs +26 -0
  71. package/dist/esm/asyncTasks.mjs.map +1 -0
  72. package/dist/esm/asyncTasks.native.js +56 -0
  73. package/dist/esm/asyncTasks.native.js.map +1 -0
  74. package/dist/esm/createZeroDirectServer.mjs +241 -0
  75. package/dist/esm/createZeroDirectServer.mjs.map +1 -0
  76. package/dist/esm/createZeroDirectServer.native.js +256 -0
  77. package/dist/esm/createZeroDirectServer.native.js.map +1 -0
  78. package/dist/esm/createZeroSQLiteServer.mjs +24 -0
  79. package/dist/esm/createZeroSQLiteServer.mjs.map +1 -0
  80. package/dist/esm/createZeroSQLiteServer.native.js +29 -0
  81. package/dist/esm/createZeroSQLiteServer.native.js.map +1 -0
  82. package/dist/esm/createZeroSQLiteServer.test.mjs +596 -0
  83. package/dist/esm/createZeroSQLiteServer.test.mjs.map +1 -0
  84. package/dist/esm/createZeroSQLiteServer.test.native.js +645 -0
  85. package/dist/esm/createZeroSQLiteServer.test.native.js.map +1 -0
  86. package/dist/esm/createZeroServer.mjs +57 -0
  87. package/dist/esm/createZeroServer.mjs.map +1 -0
  88. package/dist/esm/createZeroServer.native.js +65 -0
  89. package/dist/esm/createZeroServer.native.js.map +1 -0
  90. package/dist/esm/createZeroServer.test.mjs +379 -0
  91. package/dist/esm/createZeroServer.test.mjs.map +1 -0
  92. package/dist/esm/createZeroServer.test.native.js +446 -0
  93. package/dist/esm/createZeroServer.test.native.js.map +1 -0
  94. package/dist/esm/createZeroServerCore.mjs +186 -0
  95. package/dist/esm/createZeroServerCore.mjs.map +1 -0
  96. package/dist/esm/createZeroServerCore.native.js +327 -0
  97. package/dist/esm/createZeroServerCore.native.js.map +1 -0
  98. package/dist/esm/helpers/createMutators.mjs +3 -4
  99. package/dist/esm/helpers/createMutators.mjs.map +1 -1
  100. package/dist/esm/helpers/createMutators.native.js +3 -6
  101. package/dist/esm/helpers/createMutators.native.js.map +1 -1
  102. package/dist/esm/httpPull/auth.test.mjs +198 -0
  103. package/dist/esm/httpPull/auth.test.mjs.map +1 -0
  104. package/dist/esm/httpPull/auth.test.native.js +277 -0
  105. package/dist/esm/httpPull/auth.test.native.js.map +1 -0
  106. package/dist/esm/httpPull/churn.test.mjs +133 -0
  107. package/dist/esm/httpPull/churn.test.mjs.map +1 -0
  108. package/dist/esm/httpPull/churn.test.native.js +153 -0
  109. package/dist/esm/httpPull/churn.test.native.js.map +1 -0
  110. package/dist/esm/httpPull/completeAck.test.mjs +70 -0
  111. package/dist/esm/httpPull/completeAck.test.mjs.map +1 -0
  112. package/dist/esm/httpPull/completeAck.test.native.js +74 -0
  113. package/dist/esm/httpPull/completeAck.test.native.js.map +1 -0
  114. package/dist/esm/httpPull/fixtureServer.mjs +315 -0
  115. package/dist/esm/httpPull/fixtureServer.mjs.map +1 -0
  116. package/dist/esm/httpPull/fixtureServer.native.js +506 -0
  117. package/dist/esm/httpPull/fixtureServer.native.js.map +1 -0
  118. package/dist/esm/httpPull/integration.test.mjs +54 -0
  119. package/dist/esm/httpPull/integration.test.mjs.map +1 -0
  120. package/dist/esm/httpPull/integration.test.native.js +58 -0
  121. package/dist/esm/httpPull/integration.test.native.js.map +1 -0
  122. package/dist/esm/httpPull/rebase.test.mjs +361 -0
  123. package/dist/esm/httpPull/rebase.test.mjs.map +1 -0
  124. package/dist/esm/httpPull/rebase.test.native.js +418 -0
  125. package/dist/esm/httpPull/rebase.test.native.js.map +1 -0
  126. package/dist/esm/httpPull/relations.test.mjs +108 -0
  127. package/dist/esm/httpPull/relations.test.mjs.map +1 -0
  128. package/dist/esm/httpPull/relations.test.native.js +117 -0
  129. package/dist/esm/httpPull/relations.test.native.js.map +1 -0
  130. package/dist/esm/httpPull/testHarness.mjs +72 -0
  131. package/dist/esm/httpPull/testHarness.mjs.map +1 -0
  132. package/dist/esm/httpPull/testHarness.native.js +81 -0
  133. package/dist/esm/httpPull/testHarness.native.js.map +1 -0
  134. package/dist/esm/index.js +1 -0
  135. package/dist/esm/index.js.map +1 -1
  136. package/dist/esm/index.mjs +1 -0
  137. package/dist/esm/index.mjs.map +1 -1
  138. package/dist/esm/index.native.js +1 -0
  139. package/dist/esm/index.native.js.map +1 -1
  140. package/dist/esm/run.mjs +1 -1
  141. package/dist/esm/run.mjs.map +1 -1
  142. package/dist/esm/run.native.js +1 -1
  143. package/dist/esm/server.mjs +4 -0
  144. package/dist/esm/server.mjs.map +1 -0
  145. package/dist/esm/server.native.js +2 -0
  146. package/dist/esm/server.native.js.map +1 -0
  147. package/dist/esm/sqliteRuntime.mjs +289 -0
  148. package/dist/esm/sqliteRuntime.mjs.map +1 -0
  149. package/dist/esm/sqliteRuntime.native.js +406 -0
  150. package/dist/esm/sqliteRuntime.native.js.map +1 -0
  151. package/dist/esm/state.mjs +1 -1
  152. package/dist/esm/state.native.js +1 -1
  153. package/dist/esm/syncHost.mjs +153 -0
  154. package/dist/esm/syncHost.mjs.map +1 -0
  155. package/dist/esm/syncHost.native.js +359 -0
  156. package/dist/esm/syncHost.native.js.map +1 -0
  157. package/dist/esm/syncHost.test.mjs +217 -0
  158. package/dist/esm/syncHost.test.mjs.map +1 -0
  159. package/dist/esm/syncHost.test.native.js +264 -0
  160. package/dist/esm/syncHost.test.native.js.map +1 -0
  161. package/dist/esm/zeroRunner.mjs +1 -1
  162. package/dist/esm/zeroRunner.mjs.map +1 -1
  163. package/dist/esm/zeroRunner.native.js +1 -1
  164. package/package.json +23 -8
  165. package/readme.md +65 -40
  166. package/src/asyncTasks.ts +36 -0
  167. package/src/createZeroClient.tsx +1 -1
  168. package/src/createZeroDirectServer.ts +411 -0
  169. package/src/createZeroSQLiteServer.test.ts +614 -0
  170. package/src/createZeroSQLiteServer.ts +56 -0
  171. package/src/createZeroServer.test.ts +421 -0
  172. package/src/createZeroServer.ts +105 -0
  173. package/src/createZeroServerCore.ts +296 -0
  174. package/src/helpers/createMutators.ts +2 -6
  175. package/src/httpPull/auth.test.ts +208 -0
  176. package/src/httpPull/churn.test.ts +147 -0
  177. package/src/httpPull/completeAck.test.ts +80 -0
  178. package/src/httpPull/fixtureServer.ts +391 -0
  179. package/src/httpPull/integration.test.ts +57 -0
  180. package/src/httpPull/rebase.test.ts +368 -0
  181. package/src/httpPull/relations.test.ts +135 -0
  182. package/src/httpPull/testHarness.ts +95 -0
  183. package/src/index.ts +1 -0
  184. package/src/run.ts +1 -1
  185. package/src/server.native.ts +3 -0
  186. package/src/server.ts +4 -0
  187. package/src/sqliteRuntime.ts +512 -0
  188. package/src/state.ts +1 -1
  189. package/src/syncHost.test.ts +223 -0
  190. package/src/syncHost.ts +294 -0
  191. package/src/types.ts +5 -0
  192. package/src/zeroRunner.ts +1 -1
  193. package/test-fixtures/server-return-types.ts +58 -21
  194. package/types/asyncTasks.d.ts +11 -0
  195. package/types/asyncTasks.d.ts.map +1 -0
  196. package/types/createZeroDirectServer.d.ts +80 -0
  197. package/types/createZeroDirectServer.d.ts.map +1 -0
  198. package/types/createZeroSQLiteServer.d.ts +12 -0
  199. package/types/createZeroSQLiteServer.d.ts.map +1 -0
  200. package/types/createZeroSQLiteServer.test.d.ts +2 -0
  201. package/types/createZeroSQLiteServer.test.d.ts.map +1 -0
  202. package/types/createZeroServer.d.ts +18 -0
  203. package/types/createZeroServer.d.ts.map +1 -0
  204. package/types/createZeroServer.test.d.ts +2 -0
  205. package/types/createZeroServer.test.d.ts.map +1 -0
  206. package/types/createZeroServerCore.d.ts +256 -0
  207. package/types/createZeroServerCore.d.ts.map +1 -0
  208. package/types/helpers/createMutators.d.ts +1 -2
  209. package/types/helpers/createMutators.d.ts.map +1 -1
  210. package/types/httpPull/auth.test.d.ts +2 -0
  211. package/types/httpPull/auth.test.d.ts.map +1 -0
  212. package/types/httpPull/churn.test.d.ts +2 -0
  213. package/types/httpPull/churn.test.d.ts.map +1 -0
  214. package/types/httpPull/completeAck.test.d.ts +2 -0
  215. package/types/httpPull/completeAck.test.d.ts.map +1 -0
  216. package/types/httpPull/fixtureSchema.d.ts +1 -1
  217. package/types/httpPull/fixtureServer.d.ts +14 -0
  218. package/types/httpPull/fixtureServer.d.ts.map +1 -0
  219. package/types/httpPull/integration.test.d.ts +2 -0
  220. package/types/httpPull/integration.test.d.ts.map +1 -0
  221. package/types/httpPull/rebase.test.d.ts +2 -0
  222. package/types/httpPull/rebase.test.d.ts.map +1 -0
  223. package/types/httpPull/relations.test.d.ts +2 -0
  224. package/types/httpPull/relations.test.d.ts.map +1 -0
  225. package/types/httpPull/testHarness.d.ts +32 -0
  226. package/types/httpPull/testHarness.d.ts.map +1 -0
  227. package/types/index.d.ts +1 -0
  228. package/types/index.d.ts.map +1 -1
  229. package/types/server.d.ts +5 -0
  230. package/types/server.d.ts.map +1 -0
  231. package/types/server.native.d.ts +1 -0
  232. package/types/server.native.d.ts.map +1 -0
  233. package/types/sqliteRuntime.d.ts +71 -0
  234. package/types/sqliteRuntime.d.ts.map +1 -0
  235. package/types/syncHost.d.ts +42 -0
  236. package/types/syncHost.d.ts.map +1 -0
  237. package/types/syncHost.test.d.ts +2 -0
  238. package/types/syncHost.test.d.ts.map +1 -0
  239. package/types/types.d.ts +4 -0
  240. package/types/types.d.ts.map +1 -1
  241. package/dist/cjs/orez.cjs +0 -227
  242. package/dist/cjs/orez.native.js +0 -314
  243. package/dist/cjs/orez.native.js.map +0 -1
  244. package/dist/cjs/orez.test.cjs +0 -211
  245. package/dist/cjs/orez.test.native.js +0 -226
  246. package/dist/cjs/orez.test.native.js.map +0 -1
  247. package/dist/esm/orez.mjs +0 -202
  248. package/dist/esm/orez.mjs.map +0 -1
  249. package/dist/esm/orez.native.js +0 -286
  250. package/dist/esm/orez.native.js.map +0 -1
  251. package/dist/esm/orez.test.mjs +0 -188
  252. package/dist/esm/orez.test.mjs.map +0 -1
  253. package/dist/esm/orez.test.native.js +0 -200
  254. package/dist/esm/orez.test.native.js.map +0 -1
  255. package/src/orez.test.ts +0 -195
  256. package/src/orez.ts +0 -331
  257. package/types/orez.d.ts +0 -53
  258. package/types/orez.d.ts.map +0 -1
  259. package/types/orez.test.d.ts +0 -2
  260. package/types/orez.test.d.ts.map +0 -1
@@ -0,0 +1,512 @@
1
+ import { createBuilder } from '@rocicorp/zero'
2
+ import { asQueryInternals } from '@rocicorp/zero/bindings'
3
+
4
+ import type { ZeroServerDatabase } from './createZeroDirectServer'
5
+
6
+ import type { Transaction } from './types'
7
+ import type { AST, Schema as ZeroSchema } from '@rocicorp/zero'
8
+ import type {
9
+ TransactionProviderHooks,
10
+ TransactionProviderInput,
11
+ } from '@rocicorp/zero/server'
12
+
13
+ export type SQLiteTransactionExecutor = {
14
+ exec(
15
+ sql: string,
16
+ params?: readonly unknown[],
17
+ metadata?: SqlStatementMetadata,
18
+ ): SQLiteExecResult | Promise<SQLiteExecResult>
19
+ query<Row extends Record<string, unknown> = Record<string, unknown>>(
20
+ sql: string,
21
+ params?: readonly unknown[],
22
+ ): Row[] | Promise<Row[]>
23
+ queryAst<Result = unknown>(
24
+ ast: AST,
25
+ format: SQLiteQueryFormat,
26
+ queryName?: string,
27
+ ): Result | Promise<Result>
28
+ }
29
+
30
+ export type SQLiteExecResult = {
31
+ changes: number
32
+ }
33
+
34
+ /**
35
+ * Identifies an application write without asking the host to parse its SQL.
36
+ * `table` is the physical SQLite table used by storage triggers; `publicTable`
37
+ * is the logical Zero table name exposed by the change feed.
38
+ */
39
+ export type SqlStatementMetadata = {
40
+ table: string
41
+ publicTable: string
42
+ kind: 'delete' | 'insert' | 'update' | 'upsert'
43
+ }
44
+
45
+ export type SQLiteQueryFormat = {
46
+ relationships: Record<string, SQLiteQueryFormat>
47
+ singular: boolean
48
+ }
49
+
50
+ export type SQLiteTransactionProvider = <R>(
51
+ callback: (tx: SQLiteTransactionExecutor) => R | Promise<R>,
52
+ ) => Promise<R>
53
+
54
+ type SQLiteColumn = ZeroSchema['tables'][string]['columns'][string]
55
+
56
+ export type SQLiteValueContext = {
57
+ column: SQLiteColumn
58
+ columnName: string
59
+ tableName: string
60
+ }
61
+
62
+ export type ZeroSQLiteTransactionOptions<Schema extends ZeroSchema> = {
63
+ schema: Schema
64
+ encodeValue?: (value: unknown, context: SQLiteValueContext) => unknown
65
+ }
66
+
67
+ export type SQLiteLedgerTables = {
68
+ clients: string
69
+ mutations: string
70
+ }
71
+
72
+ export type PersistAlreadyProcessedArgs = {
73
+ clientGroupID: string
74
+ mutations: Array<{
75
+ id: { clientID: string; id: number }
76
+ result: { error: 'alreadyProcessed'; details?: unknown }
77
+ }>
78
+ upstreamSchema: string
79
+ }
80
+
81
+ type SQLiteTransactionOptions<Schema extends ZeroSchema> = {
82
+ encodeValue: (value: unknown, context: SQLiteValueContext) => unknown
83
+ schema: Schema
84
+ }
85
+
86
+ export type SQLiteDatabaseOptions<Schema extends ZeroSchema> =
87
+ SQLiteTransactionOptions<Schema> & {
88
+ initializedLedgers: Set<string>
89
+ ledgerTables: (upstreamSchema: string) => SQLiteLedgerTables
90
+ transactionProvider: SQLiteTransactionProvider
91
+ }
92
+
93
+ export function createSQLiteDatabase<Schema extends ZeroSchema>(
94
+ options: SQLiteDatabaseOptions<Schema>,
95
+ ): ZeroServerDatabase {
96
+ return {
97
+ async transaction<R>(
98
+ callback: (
99
+ tx: Transaction,
100
+ transactionHooks: TransactionProviderHooks,
101
+ ) => R | Promise<R>,
102
+ input?: TransactionProviderInput,
103
+ ) {
104
+ let committedLedgers = new Set<string>()
105
+ const value = await options.transactionProvider(async (executor) => {
106
+ const attemptLedgers = new Set<string>()
107
+ const ensureLedger = async (upstreamSchema: string) => {
108
+ const tables = options.ledgerTables(upstreamSchema)
109
+ const key = ledgerKey(tables)
110
+ if (options.initializedLedgers.has(key) || attemptLedgers.has(key)) {
111
+ return
112
+ }
113
+ await createLedgerTables(executor, tables)
114
+ attemptLedgers.add(key)
115
+ }
116
+ const transaction = createZeroSQLiteTransaction(options, executor, input)
117
+ const result = await callback(
118
+ transaction,
119
+ createLedgerHooks(options, executor, ensureLedger, input),
120
+ )
121
+ committedLedgers = attemptLedgers
122
+ return result
123
+ })
124
+ for (const key of committedLedgers) options.initializedLedgers.add(key)
125
+ return value
126
+ },
127
+ }
128
+ }
129
+
130
+ export function createZeroSQLiteTransaction<Schema extends ZeroSchema>(
131
+ options: ZeroSQLiteTransactionOptions<Schema>,
132
+ executor: SQLiteTransactionExecutor,
133
+ input?: TransactionProviderInput,
134
+ ): Transaction {
135
+ const transactionOptions = {
136
+ encodeValue: options.encodeValue ?? encodeSQLiteValue,
137
+ schema: options.schema,
138
+ }
139
+ const clientID = input?.clientID ?? ''
140
+ const mutationID = input?.mutationID ?? 0
141
+ const mutate = new Proxy(
142
+ {},
143
+ {
144
+ get(_target, tableKey) {
145
+ const tableName = String(tableKey)
146
+ if (!options.schema.tables[tableName]) {
147
+ throw new Error(`unknown SQLite mutation table ${tableName}`)
148
+ }
149
+ return new Proxy(
150
+ {},
151
+ {
152
+ get(_innerTarget, kindKey) {
153
+ const kind = String(kindKey)
154
+ if (!isCrudKind(kind)) return
155
+ return (value: unknown) =>
156
+ executeCrud(transactionOptions, executor, tableName, kind, value)
157
+ },
158
+ },
159
+ )
160
+ },
161
+ },
162
+ )
163
+
164
+ const runQuery = (
165
+ ast: AST,
166
+ format: SQLiteQueryFormat,
167
+ queryName?: string,
168
+ ): Promise<unknown> => Promise.resolve(executor.queryAst(ast, format, queryName))
169
+ const dbTransaction = {
170
+ wrappedTransaction: executor,
171
+ query: (sql: string, args: unknown[]) => executor.query(sql, args),
172
+ runQuery,
173
+ }
174
+
175
+ return {
176
+ clientID,
177
+ dbTransaction,
178
+ location: 'server',
179
+ mutate,
180
+ mutationID,
181
+ query: transactionOptions.schema.enableLegacyQueries
182
+ ? createLegacyQueryBuilder(transactionOptions.schema, (query) => {
183
+ const internals = asQueryInternals(query as never)
184
+ return runQuery(internals.ast, internals.format, internals.customQueryID?.name)
185
+ })
186
+ : undefined,
187
+ reason: 'authoritative',
188
+ async run(query: unknown) {
189
+ const internals = asQueryInternals(query as never)
190
+ return runQuery(internals.ast, internals.format, internals.customQueryID?.name)
191
+ },
192
+ } as unknown as Transaction
193
+ }
194
+
195
+ type CrudKind = 'delete' | 'insert' | 'update' | 'upsert'
196
+
197
+ function isCrudKind(value: string): value is CrudKind {
198
+ return (
199
+ value === 'delete' || value === 'insert' || value === 'update' || value === 'upsert'
200
+ )
201
+ }
202
+
203
+ async function executeCrud<Schema extends ZeroSchema>(
204
+ options: SQLiteTransactionOptions<Schema>,
205
+ executor: SQLiteTransactionExecutor,
206
+ tableName: string,
207
+ kind: CrudKind,
208
+ raw: unknown,
209
+ ) {
210
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
211
+ throw new Error(`${tableName}.${kind} expects an object`)
212
+ }
213
+
214
+ const table = options.schema.tables[tableName]!
215
+ const values = Object.fromEntries(
216
+ Object.entries(raw as Record<string, unknown>).filter(
217
+ ([, value]) => value !== undefined,
218
+ ),
219
+ )
220
+ for (const columnName of Object.keys(values)) {
221
+ if (!table.columns[columnName]) {
222
+ throw new Error(`unknown SQLite column ${tableName}.${columnName}`)
223
+ }
224
+ }
225
+
226
+ const primaryValues = table.primaryKey.map((columnName) => {
227
+ if (!(columnName in values)) {
228
+ throw new Error(`${tableName}.${kind} is missing primary key ${columnName}`)
229
+ }
230
+ return encodeColumn(options, tableName, columnName, values[columnName])
231
+ })
232
+ const primaryWhere = table.primaryKey
233
+ .map((columnName) => `${quoteSQLiteIdentifier(serverColumn(table, columnName))} = ?`)
234
+ .join(' AND ')
235
+ const serverTableName = quoteSQLiteIdentifier(table.serverName ?? table.name)
236
+
237
+ if (kind === 'delete') {
238
+ await executor.exec(
239
+ `DELETE FROM ${serverTableName} WHERE ${primaryWhere}`,
240
+ primaryValues,
241
+ { table: table.serverName ?? table.name, publicTable: tableName, kind },
242
+ )
243
+ return
244
+ }
245
+
246
+ const columnNames = Object.keys(values)
247
+ if (columnNames.length === 0) {
248
+ throw new Error(`${tableName}.${kind} has no values`)
249
+ }
250
+
251
+ const serverColumns = columnNames.map((columnName) =>
252
+ quoteSQLiteIdentifier(serverColumn(table, columnName)),
253
+ )
254
+ const params = columnNames.map((columnName) =>
255
+ encodeColumn(options, tableName, columnName, values[columnName]),
256
+ )
257
+ const insert = `INSERT INTO ${serverTableName} (${serverColumns.join(
258
+ ', ',
259
+ )}) VALUES (${columnNames.map(() => '?').join(', ')})`
260
+ const conflictColumns = table.primaryKey
261
+ .map((columnName) => quoteSQLiteIdentifier(serverColumn(table, columnName)))
262
+ .join(', ')
263
+
264
+ if (kind === 'insert') {
265
+ await executor.exec(`${insert} ON CONFLICT (${conflictColumns}) DO NOTHING`, params, {
266
+ table: table.serverName ?? table.name,
267
+ publicTable: tableName,
268
+ kind,
269
+ })
270
+ return
271
+ }
272
+
273
+ if (kind === 'upsert') {
274
+ const mutableColumns = columnNames.filter(
275
+ (columnName) => !table.primaryKey.includes(columnName),
276
+ )
277
+ const conflictAction =
278
+ mutableColumns.length === 0
279
+ ? 'DO NOTHING'
280
+ : `DO UPDATE SET ${mutableColumns
281
+ .map((columnName) => {
282
+ const column = quoteSQLiteIdentifier(serverColumn(table, columnName))
283
+ return `${column} = excluded.${column}`
284
+ })
285
+ .join(', ')}`
286
+ await executor.exec(
287
+ `${insert} ON CONFLICT (${conflictColumns}) ${conflictAction}`,
288
+ params,
289
+ { table: table.serverName ?? table.name, publicTable: tableName, kind },
290
+ )
291
+ return
292
+ }
293
+
294
+ const mutableColumns = columnNames.filter(
295
+ (columnName) => !table.primaryKey.includes(columnName),
296
+ )
297
+ if (mutableColumns.length === 0) return
298
+ await executor.exec(
299
+ `UPDATE ${serverTableName} SET ${mutableColumns
300
+ .map(
301
+ (columnName) => `${quoteSQLiteIdentifier(serverColumn(table, columnName))} = ?`,
302
+ )
303
+ .join(', ')} WHERE ${primaryWhere}`,
304
+ [
305
+ ...mutableColumns.map((columnName) =>
306
+ encodeColumn(options, tableName, columnName, values[columnName]),
307
+ ),
308
+ ...primaryValues,
309
+ ],
310
+ { table: table.serverName ?? table.name, publicTable: tableName, kind },
311
+ )
312
+ }
313
+
314
+ function serverColumn(table: ZeroSchema['tables'][string], columnName: string) {
315
+ const column = table.columns[columnName]
316
+ if (!column) throw new Error(`unknown SQLite column ${table.name}.${columnName}`)
317
+ return column.serverName ?? columnName
318
+ }
319
+
320
+ function encodeColumn<Schema extends ZeroSchema>(
321
+ options: SQLiteTransactionOptions<Schema>,
322
+ tableName: string,
323
+ columnName: string,
324
+ value: unknown,
325
+ ) {
326
+ return options.encodeValue(value, {
327
+ column: options.schema.tables[tableName]!.columns[columnName]!,
328
+ columnName,
329
+ tableName,
330
+ })
331
+ }
332
+
333
+ function createLegacyQueryBuilder<Schema extends ZeroSchema>(
334
+ schema: Schema,
335
+ run: (query: unknown) => Promise<unknown>,
336
+ ) {
337
+ const builder = createBuilder(schema)
338
+ return new Proxy(builder, {
339
+ get(target, property) {
340
+ return makeRunnableQuery(Reflect.get(target, property, target), run)
341
+ },
342
+ })
343
+ }
344
+
345
+ function makeRunnableQuery(
346
+ query: unknown,
347
+ run: (query: unknown) => Promise<unknown>,
348
+ ): unknown {
349
+ if (!isQueryBuilder(query)) return query
350
+ return new Proxy(query as object, {
351
+ get(target, property) {
352
+ if (property === 'run') return () => run(target)
353
+ if (property === 'materialize' || property === 'preload') {
354
+ return () => {
355
+ throw new Error(`${String(property)}() is not supported in server transactions`)
356
+ }
357
+ }
358
+ const value = Reflect.get(target, property, target)
359
+ if (typeof value !== 'function') return value
360
+ return (...args: unknown[]) =>
361
+ makeRunnableQuery(Reflect.apply(value, target, args), run)
362
+ },
363
+ })
364
+ }
365
+
366
+ function isQueryBuilder(value: unknown) {
367
+ if (!value || typeof value !== 'object') return false
368
+ try {
369
+ asQueryInternals(value as never)
370
+ return true
371
+ } catch {
372
+ return false
373
+ }
374
+ }
375
+
376
+ function createLedgerHooks<Schema extends ZeroSchema>(
377
+ options: SQLiteDatabaseOptions<Schema>,
378
+ executor: SQLiteTransactionExecutor,
379
+ ensureLedger: (upstreamSchema: string) => Promise<void>,
380
+ input?: TransactionProviderInput,
381
+ ): TransactionProviderHooks {
382
+ const upstreamSchema = input?.upstreamSchema ?? ''
383
+ const clientGroupID = input?.clientGroupID ?? ''
384
+ const clientID = input?.clientID ?? ''
385
+ const tables = options.ledgerTables(upstreamSchema)
386
+ const clientsTable = quoteSQLiteIdentifier(tables.clients)
387
+ const mutationsTable = quoteSQLiteIdentifier(tables.mutations)
388
+
389
+ return {
390
+ async updateClientMutationID() {
391
+ await ensureLedger(upstreamSchema)
392
+ const rows = await executor.query<{ lastMutationID: number | bigint }>(
393
+ `INSERT INTO ${clientsTable}
394
+ (clientGroupID, clientID, lastMutationID)
395
+ VALUES (?, ?, 1)
396
+ ON CONFLICT (clientGroupID, clientID)
397
+ DO UPDATE SET lastMutationID = lastMutationID + 1
398
+ RETURNING lastMutationID`,
399
+ [clientGroupID, clientID],
400
+ )
401
+ const lastMutationID = rows[0]?.lastMutationID
402
+ if (typeof lastMutationID !== 'number' && typeof lastMutationID !== 'bigint') {
403
+ throw new Error('SQLite mutation ledger returned no lastMutationID')
404
+ }
405
+ return { lastMutationID }
406
+ },
407
+ async writeMutationResult(result) {
408
+ await ensureLedger(upstreamSchema)
409
+ await executor.exec(
410
+ `INSERT INTO ${mutationsTable}
411
+ (clientGroupID, clientID, mutationID, result)
412
+ VALUES (?, ?, ?, ?)`,
413
+ [clientGroupID, result.id.clientID, result.id.id, JSON.stringify(result.result)],
414
+ )
415
+ },
416
+ async deleteMutationResults(args) {
417
+ await ensureLedger(upstreamSchema)
418
+ if ('type' in args && args.type === 'bulk') {
419
+ if (args.clientIDs.length === 0) return
420
+ await executor.exec(
421
+ `DELETE FROM ${mutationsTable}
422
+ WHERE clientGroupID = ?
423
+ AND clientID IN (${args.clientIDs.map(() => '?').join(', ')})`,
424
+ [args.clientGroupID, ...args.clientIDs],
425
+ )
426
+ return
427
+ }
428
+ await executor.exec(
429
+ `DELETE FROM ${mutationsTable}
430
+ WHERE clientGroupID = ?
431
+ AND clientID = ?
432
+ AND mutationID <= ?`,
433
+ [args.clientGroupID, args.clientID, args.upToMutationID],
434
+ )
435
+ },
436
+ }
437
+ }
438
+
439
+ export async function persistAlreadyProcessedMutationResults<Schema extends ZeroSchema>(
440
+ options: SQLiteDatabaseOptions<Schema>,
441
+ { clientGroupID, mutations, upstreamSchema }: PersistAlreadyProcessedArgs,
442
+ ) {
443
+ const tables = options.ledgerTables(upstreamSchema)
444
+ const table = quoteSQLiteIdentifier(tables.mutations)
445
+ const key = ledgerKey(tables)
446
+ await options.transactionProvider(async (executor) => {
447
+ if (!options.initializedLedgers.has(key)) {
448
+ await createLedgerTables(executor, tables)
449
+ }
450
+ for (const mutation of mutations) {
451
+ await executor.exec(
452
+ `INSERT INTO ${table}
453
+ (clientGroupID, clientID, mutationID, result)
454
+ VALUES (?, ?, ?, ?)
455
+ ON CONFLICT (clientGroupID, clientID, mutationID) DO NOTHING`,
456
+ [
457
+ clientGroupID,
458
+ mutation.id.clientID,
459
+ mutation.id.id,
460
+ JSON.stringify(mutation.result),
461
+ ],
462
+ )
463
+ }
464
+ })
465
+ options.initializedLedgers.add(key)
466
+ }
467
+
468
+ async function createLedgerTables(
469
+ executor: SQLiteTransactionExecutor,
470
+ tables: SQLiteLedgerTables,
471
+ ) {
472
+ await executor.exec(
473
+ `CREATE TABLE IF NOT EXISTS ${quoteSQLiteIdentifier(tables.clients)} (
474
+ clientGroupID TEXT NOT NULL,
475
+ clientID TEXT NOT NULL,
476
+ lastMutationID INTEGER NOT NULL,
477
+ PRIMARY KEY (clientGroupID, clientID)
478
+ )`,
479
+ )
480
+ await executor.exec(
481
+ `CREATE TABLE IF NOT EXISTS ${quoteSQLiteIdentifier(tables.mutations)} (
482
+ clientGroupID TEXT NOT NULL,
483
+ clientID TEXT NOT NULL,
484
+ mutationID INTEGER NOT NULL,
485
+ result TEXT NOT NULL,
486
+ PRIMARY KEY (clientGroupID, clientID, mutationID)
487
+ )`,
488
+ )
489
+ }
490
+
491
+ function ledgerKey(tables: SQLiteLedgerTables) {
492
+ return `${tables.clients}\0${tables.mutations}`
493
+ }
494
+
495
+ export function defaultLedgerTables(upstreamSchema: string): SQLiteLedgerTables {
496
+ const prefix = upstreamSchema ? `${upstreamSchema}_` : ''
497
+ return {
498
+ clients: `${prefix}clients`,
499
+ mutations: `${prefix}mutations`,
500
+ }
501
+ }
502
+
503
+ export function encodeSQLiteValue(value: unknown, { column }: SQLiteValueContext) {
504
+ if (value === null) return null
505
+ if (column.type === 'boolean') return value ? 1 : 0
506
+ if (column.type === 'json') return JSON.stringify(value)
507
+ return value
508
+ }
509
+
510
+ function quoteSQLiteIdentifier(value: string) {
511
+ return `"${value.replaceAll('"', '""')}"`
512
+ }
package/src/state.ts CHANGED
@@ -18,7 +18,7 @@ const getState = () =>
18
18
  environment: null,
19
19
  }))
20
20
 
21
- const errMessage = `Zero client or server bindings have not been created yet!`
21
+ const errMessage = `Haven't called createZeroClient or createZeroServer yet!`
22
22
 
23
23
  export const getZQL = () => {
24
24
  const { zql } = getState()