on-zero 0.6.14 → 0.6.16

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 (163) 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/cli.cjs +8 -1
  5. package/dist/cjs/cli.native.js +8 -1
  6. package/dist/cjs/cli.native.js.map +1 -1
  7. package/dist/cjs/createSchemaFromDrizzle.cjs +4 -4
  8. package/dist/cjs/createSchemaFromDrizzle.native.js +4 -4
  9. package/dist/cjs/createSchemaFromDrizzle.native.js.map +1 -1
  10. package/dist/cjs/createZeroDirectServer.cjs +269 -0
  11. package/dist/cjs/createZeroDirectServer.native.js +287 -0
  12. package/dist/cjs/createZeroDirectServer.native.js.map +1 -0
  13. package/dist/cjs/createZeroSQLiteServer.cjs +8 -277
  14. package/dist/cjs/createZeroSQLiteServer.native.js +8 -390
  15. package/dist/cjs/createZeroSQLiteServer.native.js.map +1 -1
  16. package/dist/cjs/createZeroSQLiteServer.test.cjs +36 -9
  17. package/dist/cjs/createZeroSQLiteServer.test.native.js +37 -9
  18. package/dist/cjs/createZeroSQLiteServer.test.native.js.map +1 -1
  19. package/dist/cjs/createZeroServer.test.cjs +79 -1
  20. package/dist/cjs/createZeroServer.test.native.js +85 -1
  21. package/dist/cjs/createZeroServer.test.native.js.map +1 -1
  22. package/dist/cjs/createZeroServerCore.cjs +28 -208
  23. package/dist/cjs/createZeroServerCore.native.js +33 -218
  24. package/dist/cjs/createZeroServerCore.native.js.map +1 -1
  25. package/dist/cjs/generate.cjs +15 -5
  26. package/dist/cjs/generate.native.js +15 -5
  27. package/dist/cjs/generate.native.js.map +1 -1
  28. package/dist/cjs/generate.test.cjs +55 -0
  29. package/dist/cjs/generate.test.native.js +55 -0
  30. package/dist/cjs/generate.test.native.js.map +1 -1
  31. package/dist/cjs/helpers/createMutators.cjs +2 -2
  32. package/dist/cjs/helpers/createMutators.native.js +2 -2
  33. package/dist/cjs/helpers/createMutators.native.js.map +1 -1
  34. package/dist/cjs/index.cjs +1 -0
  35. package/dist/cjs/index.native.js +1 -0
  36. package/dist/cjs/index.native.js.map +1 -1
  37. package/dist/cjs/server.cjs +2 -1
  38. package/dist/cjs/sqliteRuntime.cjs +318 -0
  39. package/dist/cjs/sqliteRuntime.native.js +438 -0
  40. package/dist/cjs/sqliteRuntime.native.js.map +1 -0
  41. package/dist/cjs/syncHost.cjs +26 -12
  42. package/dist/cjs/syncHost.native.js +32 -16
  43. package/dist/cjs/syncHost.native.js.map +1 -1
  44. package/dist/cjs/syncHost.test.cjs +15 -4
  45. package/dist/cjs/syncHost.test.native.js +16 -4
  46. package/dist/cjs/syncHost.test.native.js.map +1 -1
  47. package/dist/cjs/vite-plugin.cjs +2 -1
  48. package/dist/cjs/vite-plugin.native.js +2 -1
  49. package/dist/cjs/vite-plugin.native.js.map +1 -1
  50. package/dist/esm/asyncTasks.mjs +26 -0
  51. package/dist/esm/asyncTasks.mjs.map +1 -0
  52. package/dist/esm/asyncTasks.native.js +56 -0
  53. package/dist/esm/asyncTasks.native.js.map +1 -0
  54. package/dist/esm/cli.mjs +8 -1
  55. package/dist/esm/cli.mjs.map +1 -1
  56. package/dist/esm/cli.native.js +8 -1
  57. package/dist/esm/cli.native.js.map +1 -1
  58. package/dist/esm/createSchemaFromDrizzle.mjs +2 -2
  59. package/dist/esm/createSchemaFromDrizzle.native.js +2 -2
  60. package/dist/esm/createZeroDirectServer.mjs +241 -0
  61. package/dist/esm/createZeroDirectServer.mjs.map +1 -0
  62. package/dist/esm/createZeroDirectServer.native.js +256 -0
  63. package/dist/esm/createZeroDirectServer.native.js.map +1 -0
  64. package/dist/esm/createZeroSQLiteServer.mjs +3 -272
  65. package/dist/esm/createZeroSQLiteServer.mjs.map +1 -1
  66. package/dist/esm/createZeroSQLiteServer.native.js +3 -385
  67. package/dist/esm/createZeroSQLiteServer.native.js.map +1 -1
  68. package/dist/esm/createZeroSQLiteServer.test.mjs +36 -9
  69. package/dist/esm/createZeroSQLiteServer.test.mjs.map +1 -1
  70. package/dist/esm/createZeroSQLiteServer.test.native.js +37 -9
  71. package/dist/esm/createZeroSQLiteServer.test.native.js.map +1 -1
  72. package/dist/esm/createZeroServer.test.mjs +80 -2
  73. package/dist/esm/createZeroServer.test.mjs.map +1 -1
  74. package/dist/esm/createZeroServer.test.native.js +86 -2
  75. package/dist/esm/createZeroServer.test.native.js.map +1 -1
  76. package/dist/esm/createZeroServerCore.mjs +26 -205
  77. package/dist/esm/createZeroServerCore.mjs.map +1 -1
  78. package/dist/esm/createZeroServerCore.native.js +32 -216
  79. package/dist/esm/createZeroServerCore.native.js.map +1 -1
  80. package/dist/esm/generate.mjs +15 -5
  81. package/dist/esm/generate.mjs.map +1 -1
  82. package/dist/esm/generate.native.js +15 -5
  83. package/dist/esm/generate.native.js.map +1 -1
  84. package/dist/esm/generate.test.mjs +55 -1
  85. package/dist/esm/generate.test.mjs.map +1 -1
  86. package/dist/esm/generate.test.native.js +55 -1
  87. package/dist/esm/generate.test.native.js.map +1 -1
  88. package/dist/esm/helpers/createMutators.mjs +2 -2
  89. package/dist/esm/helpers/createMutators.mjs.map +1 -1
  90. package/dist/esm/helpers/createMutators.native.js +2 -2
  91. package/dist/esm/helpers/createMutators.native.js.map +1 -1
  92. package/dist/esm/index.js +1 -0
  93. package/dist/esm/index.js.map +1 -1
  94. package/dist/esm/index.mjs +1 -0
  95. package/dist/esm/index.mjs.map +1 -1
  96. package/dist/esm/index.native.js +1 -0
  97. package/dist/esm/index.native.js.map +1 -1
  98. package/dist/esm/server.mjs +1 -0
  99. package/dist/esm/server.mjs.map +1 -1
  100. package/dist/esm/sqliteRuntime.mjs +289 -0
  101. package/dist/esm/sqliteRuntime.mjs.map +1 -0
  102. package/dist/esm/sqliteRuntime.native.js +406 -0
  103. package/dist/esm/sqliteRuntime.native.js.map +1 -0
  104. package/dist/esm/syncHost.mjs +24 -10
  105. package/dist/esm/syncHost.mjs.map +1 -1
  106. package/dist/esm/syncHost.native.js +30 -14
  107. package/dist/esm/syncHost.native.js.map +1 -1
  108. package/dist/esm/syncHost.test.mjs +15 -4
  109. package/dist/esm/syncHost.test.mjs.map +1 -1
  110. package/dist/esm/syncHost.test.native.js +16 -4
  111. package/dist/esm/syncHost.test.native.js.map +1 -1
  112. package/dist/esm/vite-plugin.mjs +2 -1
  113. package/dist/esm/vite-plugin.mjs.map +1 -1
  114. package/dist/esm/vite-plugin.native.js +2 -1
  115. package/dist/esm/vite-plugin.native.js.map +1 -1
  116. package/lint/ssr-guards.js +8 -7
  117. package/package.json +5 -6
  118. package/readme.md +13 -4
  119. package/src/asyncTasks.ts +33 -0
  120. package/src/cli.ts +7 -1
  121. package/src/createSchemaFromDrizzle.ts +2 -2
  122. package/src/createZeroDirectServer.ts +411 -0
  123. package/src/createZeroSQLiteServer.test.ts +28 -12
  124. package/src/createZeroSQLiteServer.ts +14 -481
  125. package/src/createZeroServer.test.ts +86 -2
  126. package/src/createZeroServerCore.ts +39 -343
  127. package/src/generate.test.ts +75 -1
  128. package/src/generate.ts +30 -4
  129. package/src/helpers/createMutators.ts +3 -3
  130. package/src/helpers/mutatorContext.ts +1 -1
  131. package/src/index.ts +1 -0
  132. package/src/server.ts +2 -0
  133. package/src/sqliteRuntime.ts +512 -0
  134. package/src/syncHost.test.ts +12 -5
  135. package/src/syncHost.ts +55 -16
  136. package/src/types.ts +21 -3
  137. package/src/vite-plugin.ts +1 -0
  138. package/test-fixtures/server-return-types.ts +20 -2
  139. package/types/asyncTasks.d.ts +7 -0
  140. package/types/asyncTasks.d.ts.map +1 -0
  141. package/types/createSchemaFromDrizzle.d.ts +2 -2
  142. package/types/createSchemaFromDrizzle.d.ts.map +1 -1
  143. package/types/createZeroDirectServer.d.ts +80 -0
  144. package/types/createZeroDirectServer.d.ts.map +1 -0
  145. package/types/createZeroSQLiteServer.d.ts +5 -30
  146. package/types/createZeroSQLiteServer.d.ts.map +1 -1
  147. package/types/createZeroServerCore.d.ts +30 -87
  148. package/types/createZeroServerCore.d.ts.map +1 -1
  149. package/types/generate.d.ts +2 -0
  150. package/types/generate.d.ts.map +1 -1
  151. package/types/helpers/createMutators.d.ts +3 -3
  152. package/types/helpers/createMutators.d.ts.map +1 -1
  153. package/types/index.d.ts +1 -0
  154. package/types/index.d.ts.map +1 -1
  155. package/types/server.d.ts +2 -0
  156. package/types/server.d.ts.map +1 -1
  157. package/types/sqliteRuntime.d.ts +71 -0
  158. package/types/sqliteRuntime.d.ts.map +1 -0
  159. package/types/syncHost.d.ts +10 -19
  160. package/types/syncHost.d.ts.map +1 -1
  161. package/types/types.d.ts +18 -2
  162. package/types/types.d.ts.map +1 -1
  163. package/types/vite-plugin.d.ts.map +1 -1
@@ -0,0 +1,411 @@
1
+ import { mustGetQuery } from '@rocicorp/zero'
2
+
3
+ import { runAsyncTaskEntries } from './asyncTasks'
4
+ import { createPermissions } from './createPermissions'
5
+ import { createMutators } from './helpers/createMutators'
6
+ import {
7
+ getScopedAuthData,
8
+ isInZeroMutation,
9
+ mutatorContext,
10
+ runWithAuthScope,
11
+ } from './helpers/mutatorContext'
12
+ import { runWithQueryContext } from './helpers/queryContext'
13
+ import { getMutationsPermissions } from './modelRegistry'
14
+ import { setCustomQueries } from './run'
15
+ import { getZQL, setEnvironment, setSchema } from './state'
16
+ import { setEvaluatingPermission } from './where'
17
+ import { setRunner } from './zeroRunner'
18
+
19
+ import type {
20
+ AdminRoleMode,
21
+ AsyncTask,
22
+ AsyncTaskEntry,
23
+ AsyncTaskOptions,
24
+ AuthData,
25
+ GenericModels,
26
+ MutatorContext,
27
+ QueryBuilder,
28
+ Transaction,
29
+ } from './types'
30
+ import type {
31
+ AnyQueryRegistry,
32
+ HumanReadable,
33
+ Query,
34
+ Schema as ZeroSchema,
35
+ } from '@rocicorp/zero'
36
+ import type { Database, TransactionProviderInput } from '@rocicorp/zero/server'
37
+
38
+ type MutateAuthData = Pick<AuthData, 'email' | 'id'> & Partial<AuthData>
39
+
40
+ export type MutateOptions = {
41
+ authData?: MutateAuthData
42
+ awaitEffects?: boolean
43
+ }
44
+
45
+ export type ServerMutate<Models extends GenericModels> = {
46
+ [Key in keyof Models]: {
47
+ [K in keyof Models[Key]['mutate']]: Models[Key]['mutate'][K] extends (
48
+ ctx: MutatorContext,
49
+ arg: infer Arg,
50
+ ) => any
51
+ ? (arg: Arg, options?: MutateOptions) => Promise<void>
52
+ : (options?: MutateOptions) => Promise<void>
53
+ }
54
+ }
55
+
56
+ export type ValidateQueryArgs = {
57
+ authData: AuthData | null
58
+ queryName: string
59
+ params: unknown
60
+ }
61
+
62
+ export type ValidateMutationArgs = {
63
+ authData: AuthData | null
64
+ mutatorName: string
65
+ tableName: string
66
+ args: unknown
67
+ }
68
+
69
+ export type ValidateQueryFn = (args: ValidateQueryArgs) => void
70
+ export type ValidateMutationFn = (args: ValidateMutationArgs) => void | Promise<void>
71
+
72
+ export type ServerQueryResolverOptions = {
73
+ authData: AuthData | null
74
+ name: string
75
+ args: unknown
76
+ queries: AnyQueryRegistry
77
+ permissions: ReturnType<typeof createPermissions>
78
+ validateQuery?: ValidateQueryFn
79
+ }
80
+
81
+ export function resolveServerQuery({
82
+ authData,
83
+ name,
84
+ args,
85
+ queries,
86
+ permissions,
87
+ validateQuery,
88
+ }: ServerQueryResolverOptions) {
89
+ if (name.startsWith('permission.')) {
90
+ const table = name.slice('permission.'.length)
91
+ const { objOrId } = args as {
92
+ objOrId: string | Record<string, any>
93
+ }
94
+ const permission = getMutationsPermissions(table)
95
+ if (!permission) {
96
+ throw new Error(`[permission] no permission defined for table: ${table}`)
97
+ }
98
+ setEvaluatingPermission(true)
99
+ try {
100
+ return (getZQL() as any)[table]
101
+ .where((eb: any) => {
102
+ return permissions.buildPermissionQuery(
103
+ authData,
104
+ eb,
105
+ permission,
106
+ objOrId,
107
+ table,
108
+ )
109
+ })
110
+ .one()
111
+ } finally {
112
+ setEvaluatingPermission(false)
113
+ }
114
+ }
115
+
116
+ if (validateQuery) {
117
+ validateQuery({ authData, queryName: name, params: args })
118
+ }
119
+
120
+ const query = (mustGetQuery as any)(queries, name)
121
+ return query.fn({ args, ctx: authData })
122
+ }
123
+
124
+ export type TransactionAttemptLifecycle = {
125
+ beginAttempt(): void
126
+ completeAttempt(): void
127
+ rollbackAttempt(): void
128
+ commitTransaction(): void
129
+ rollbackTransaction(): void
130
+ }
131
+
132
+ export type ZeroServerDatabase = Database<Transaction>
133
+
134
+ export type ZeroServerCommonOptions<
135
+ Schema extends ZeroSchema,
136
+ Models extends GenericModels,
137
+ ServerActions extends Record<string, unknown>,
138
+ > = {
139
+ schema: Schema
140
+ models: Models
141
+ createServerActions: () => ServerActions
142
+ queries?: AnyQueryRegistry
143
+ /** generated validators keyed by model and mutation name */
144
+ mutations?: Record<string, Record<string, any>>
145
+ /** synchronous query authorization hook; throw to reject */
146
+ validateQuery?: ValidateQueryFn
147
+ /** mutation authorization hook; throw to reject */
148
+ validateMutation?: ValidateMutationFn
149
+ /** controls which operations the admin role bypasses */
150
+ defaultAllowAdminRole?: AdminRoleMode
151
+ /** auth data used by direct server mutations when no scope provides it */
152
+ defaultMutateAuthData?: MutateAuthData
153
+ }
154
+
155
+ export type ZeroDirectServerOptions<
156
+ Schema extends ZeroSchema,
157
+ Models extends GenericModels,
158
+ ServerActions extends Record<string, unknown>,
159
+ > = ZeroServerCommonOptions<Schema, Models, ServerActions> & {
160
+ createDatabase: () => ZeroServerDatabase
161
+ }
162
+
163
+ export type ZeroDirectServer<Schema extends ZeroSchema, Models extends GenericModels> = {
164
+ transaction: <
165
+ CB extends (tx: Transaction) => Promise<any>,
166
+ Returns extends CB extends (tx: Transaction) => Promise<infer X> ? X : never,
167
+ >(
168
+ query: CB,
169
+ ) => Promise<Returns>
170
+ mutate: ServerMutate<Models>
171
+ query: <R>(
172
+ cb: (q: QueryBuilder) => Query<any, Schema, R>,
173
+ authData?: AuthData | null,
174
+ ) => Promise<HumanReadable<R>>
175
+ }
176
+
177
+ export function createAsyncTaskCollector() {
178
+ const committed: AsyncTaskEntry[] = []
179
+ let attempt: AsyncTaskEntry[] | undefined
180
+ let completedAttempt: AsyncTaskEntry[] | undefined
181
+
182
+ const enqueueTask = (task: AsyncTask, opts?: AsyncTaskOptions) => {
183
+ const entries = attempt ?? committed
184
+ entries.push({ task, barrier: opts?.barrier === true })
185
+ }
186
+
187
+ const lifecycle: TransactionAttemptLifecycle = {
188
+ beginAttempt() {
189
+ attempt = []
190
+ completedAttempt = undefined
191
+ },
192
+ completeAttempt() {
193
+ completedAttempt = attempt
194
+ attempt = undefined
195
+ },
196
+ rollbackAttempt() {
197
+ attempt = undefined
198
+ completedAttempt = undefined
199
+ },
200
+ commitTransaction() {
201
+ if (completedAttempt) committed.push(...completedAttempt)
202
+ completedAttempt = undefined
203
+ },
204
+ rollbackTransaction() {
205
+ attempt = undefined
206
+ completedAttempt = undefined
207
+ },
208
+ }
209
+
210
+ return { committed, enqueueTask, lifecycle }
211
+ }
212
+
213
+ export function createZeroDirectServerRuntime<
214
+ Schema extends ZeroSchema,
215
+ Models extends GenericModels,
216
+ ServerActions extends Record<string, unknown>,
217
+ >({
218
+ createDatabase,
219
+ createServerActions,
220
+ defaultAllowAdminRole = 'all',
221
+ defaultMutateAuthData = {} as MutateAuthData,
222
+ models,
223
+ mutations: mutationValidators,
224
+ queries,
225
+ schema,
226
+ validateMutation,
227
+ validateQuery,
228
+ }: ZeroDirectServerOptions<Schema, Models, ServerActions>): {
229
+ permissions: ReturnType<typeof createPermissions<Schema>>
230
+ server: ZeroDirectServer<Schema, Models>
231
+ } {
232
+ setSchema(schema)
233
+ setEnvironment('server')
234
+
235
+ const permissions = createPermissions<Schema>({
236
+ environment: 'server',
237
+ schema,
238
+ adminRoleMode: defaultAllowAdminRole,
239
+ })
240
+
241
+ async function runTransaction<
242
+ CB extends (tx: Transaction) => Promise<any>,
243
+ Returns extends CB extends (tx: Transaction) => Promise<infer X> ? X : never,
244
+ >(database: ZeroServerDatabase, query: CB): Promise<Returns> {
245
+ try {
246
+ if (isInZeroMutation()) {
247
+ const { tx } = mutatorContext()
248
+ return await query(tx)
249
+ }
250
+ return (await database.transaction(query, dummyTransactionInput)) as Returns
251
+ } catch (err) {
252
+ if ((err as any)?.name !== 'PermissionError') {
253
+ console.error(`Error running transaction(): ${err}`)
254
+ }
255
+ throw err
256
+ }
257
+ }
258
+
259
+ async function transaction<
260
+ CB extends (tx: Transaction) => Promise<any>,
261
+ Returns extends CB extends (tx: Transaction) => Promise<infer X> ? X : never,
262
+ >(query: CB): Promise<Returns> {
263
+ return runTransaction(createDatabase(), query)
264
+ }
265
+
266
+ async function runMutate(
267
+ modelName: string,
268
+ mutatorName: string,
269
+ mutatorArg: unknown,
270
+ options?: MutateOptions,
271
+ ) {
272
+ let authData = options?.authData
273
+ if (!authData) {
274
+ const scoped = getScopedAuthData()
275
+ if (scoped) authData = scoped as MutateAuthData
276
+ }
277
+
278
+ const tasks = createAsyncTaskCollector()
279
+ const mutators = createMutators({
280
+ models,
281
+ environment: 'server',
282
+ enqueueTask: tasks.enqueueTask,
283
+ authData: {
284
+ ...defaultMutateAuthData,
285
+ ...authData,
286
+ },
287
+ createServerActions,
288
+ can: permissions.can,
289
+ validateMutation,
290
+ mutationValidators,
291
+ })
292
+ const modelMutators = mutators[modelName as keyof typeof mutators] as Record<
293
+ string,
294
+ (tx: Transaction, arg?: unknown) => Promise<void>
295
+ >
296
+ const mutator = modelMutators[mutatorName]
297
+
298
+ await runTransaction(
299
+ createDatabaseWithLifecycle(createDatabase(), tasks.lifecycle),
300
+ async (tx) => {
301
+ await mutator(tx, mutatorArg)
302
+ },
303
+ )
304
+
305
+ if (tasks.committed.length) {
306
+ const resolvedAuth = authData ?? null
307
+ const entries = tasks.committed.map((entry) => ({
308
+ ...entry,
309
+ task: () => runWithAuthScope(resolvedAuth, entry.task),
310
+ }))
311
+ if (options?.awaitEffects) {
312
+ await Promise.all(entries.map(({ task }) => task()))
313
+ } else {
314
+ await runAsyncTaskEntries(entries, { background: () => undefined })
315
+ }
316
+ }
317
+ }
318
+
319
+ const mutate = new Proxy({} as ServerMutate<Models>, {
320
+ get(_, modelName: string) {
321
+ return new Proxy(
322
+ {},
323
+ {
324
+ get(_, mutatorName: string) {
325
+ return (arg: unknown, options?: MutateOptions) =>
326
+ runMutate(modelName, mutatorName, arg, options)
327
+ },
328
+ },
329
+ )
330
+ },
331
+ })
332
+
333
+ function query<R>(
334
+ cb: (q: QueryBuilder) => Query<any, Schema, R>,
335
+ authData?: AuthData | null,
336
+ ): Promise<HumanReadable<R>> {
337
+ const run = () =>
338
+ transaction(async (tx) => {
339
+ return tx.run(cb(getZQL()))
340
+ }) as any
341
+
342
+ if (authData !== undefined) {
343
+ return runWithQueryContext({ authData }, run)
344
+ }
345
+ return run()
346
+ }
347
+
348
+ if (queries) {
349
+ setCustomQueries(queries)
350
+ }
351
+
352
+ setRunner((queryObj) => {
353
+ return transaction(async (tx) => {
354
+ return tx.run(queryObj)
355
+ })
356
+ })
357
+
358
+ return {
359
+ permissions,
360
+ server: {
361
+ transaction,
362
+ mutate,
363
+ query,
364
+ },
365
+ }
366
+ }
367
+
368
+ export function createZeroDirectServer<
369
+ Schema extends ZeroSchema,
370
+ Models extends GenericModels,
371
+ ServerActions extends Record<string, unknown>,
372
+ >(
373
+ options: ZeroDirectServerOptions<Schema, Models, ServerActions>,
374
+ ): ZeroDirectServer<Schema, Models> {
375
+ return createZeroDirectServerRuntime(options).server
376
+ }
377
+
378
+ function createDatabaseWithLifecycle(
379
+ database: ZeroServerDatabase,
380
+ lifecycle: TransactionAttemptLifecycle,
381
+ ): ZeroServerDatabase {
382
+ return {
383
+ async transaction(callback, input) {
384
+ try {
385
+ const value = await database.transaction(async (tx, hooks) => {
386
+ lifecycle.beginAttempt()
387
+ try {
388
+ const result = await callback(tx, hooks)
389
+ lifecycle.completeAttempt()
390
+ return result
391
+ } catch (error) {
392
+ lifecycle.rollbackAttempt()
393
+ throw error
394
+ }
395
+ }, input)
396
+ lifecycle.commitTransaction()
397
+ return value
398
+ } catch (error) {
399
+ lifecycle.rollbackTransaction()
400
+ throw error
401
+ }
402
+ },
403
+ }
404
+ }
405
+
406
+ const dummyTransactionInput: TransactionProviderInput = {
407
+ clientGroupID: 'unused',
408
+ clientID: 'unused',
409
+ mutationID: 42,
410
+ upstreamSchema: 'unused',
411
+ }
@@ -8,6 +8,7 @@ import { createZeroSQLiteServer } from './createZeroSQLiteServer'
8
8
  import { zeroHttpFixtureSchema } from './httpPull/fixtureSchema'
9
9
 
10
10
  import type {
11
+ SqlStatementMetadata,
11
12
  SQLiteTransactionExecutor,
12
13
  SQLiteTransactionProvider,
13
14
  } from './createZeroSQLiteServer'
@@ -115,6 +116,7 @@ describe('createZeroSQLiteServer', () => {
115
116
  let db: DatabaseSync
116
117
  let retryNextTransaction: boolean
117
118
  let transactionProvider: SQLiteTransactionProvider
119
+ let statements: SqlStatementMetadata[]
118
120
 
119
121
  beforeEach(() => {
120
122
  db = new DatabaseSync(':memory:')
@@ -126,9 +128,12 @@ describe('createZeroSQLiteServer', () => {
126
128
  )
127
129
 
128
130
  retryNextTransaction = false
131
+ statements = []
129
132
  const executor: SQLiteTransactionExecutor = {
130
- async exec(sql, params = []) {
131
- db.prepare(sql).run(...(params as SQLInputValue[]))
133
+ async exec(sql, params = [], metadata) {
134
+ if (metadata) statements.push(metadata)
135
+ const result = db.prepare(sql).run(...(params as SQLInputValue[]))
136
+ return { changes: Number(result.changes) }
132
137
  },
133
138
  async query<Row extends Record<string, unknown>>(sql, params = []) {
134
139
  return db.prepare(sql).all(...(params as SQLInputValue[])) as Row[]
@@ -165,7 +170,9 @@ describe('createZeroSQLiteServer', () => {
165
170
  return (format.singular ? rows[0] : rows) as Result
166
171
  },
167
172
  }
168
- const run = async <R>(callback: (tx: SQLiteTransactionExecutor) => Promise<R>) => {
173
+ const run = async <R>(
174
+ callback: (tx: SQLiteTransactionExecutor) => R | Promise<R>,
175
+ ) => {
169
176
  db.exec('BEGIN')
170
177
  try {
171
178
  const value = await callback(executor)
@@ -177,7 +184,7 @@ describe('createZeroSQLiteServer', () => {
177
184
  }
178
185
  }
179
186
  transactionProvider = async <R>(
180
- callback: (tx: SQLiteTransactionExecutor) => Promise<R>,
187
+ callback: (tx: SQLiteTransactionExecutor) => R | Promise<R>,
181
188
  ) => {
182
189
  if (retryNextTransaction) {
183
190
  retryNextTransaction = false
@@ -202,12 +209,12 @@ describe('createZeroSQLiteServer', () => {
202
209
  mutate: {
203
210
  create: async ({ tx, server }, value: Record<string, unknown>) => {
204
211
  await tx.mutate.project.insert(value as never)
205
- server?.asyncTasks.push(async () => {})
206
- expect(server?.asyncTasks).toHaveLength(1)
207
- server?.asyncTasks.splice(0, 1, async () => {})
208
- server!.asyncTasks[0] = async () => {
209
- effectRuns++
210
- }
212
+ server?.enqueueTask(
213
+ async () => {
214
+ effectRuns++
215
+ },
216
+ { barrier: true },
217
+ )
211
218
  },
212
219
  },
213
220
  },
@@ -235,7 +242,8 @@ describe('createZeroSQLiteServer', () => {
235
242
  { clientGroupID: 'cg1', clientID: 'client-a', lastMutationID: 1 },
236
243
  ])
237
244
  expect(result.asyncTasks).toHaveLength(1)
238
- await result.asyncTasks[0]!()
245
+ expect(result.asyncTasks[0]!.barrier).toBe(true)
246
+ await result.asyncTasks[0]!.task()
239
247
  expect(effectRuns).toBe(1)
240
248
  })
241
249
 
@@ -403,6 +411,12 @@ describe('createZeroSQLiteServer', () => {
403
411
 
404
412
  await server.mutate.note.delete({ id: 'n1' })
405
413
  expect(db.prepare('SELECT * FROM note_records').all()).toEqual([])
414
+ expect(statements).toEqual([
415
+ { table: 'note_records', publicTable: 'note', kind: 'insert' },
416
+ { table: 'note_records', publicTable: 'note', kind: 'upsert' },
417
+ { table: 'note_records', publicTable: 'note', kind: 'update' },
418
+ { table: 'note_records', publicTable: 'note', kind: 'delete' },
419
+ ])
406
420
  })
407
421
 
408
422
  test('decodes destination rows through a hidden two-hop junction', async () => {
@@ -435,7 +449,9 @@ describe('createZeroSQLiteServer', () => {
435
449
  }
436
450
  let receivedQueryName: string | undefined
437
451
  const hydratedExecutor = {
438
- async exec() {},
452
+ async exec() {
453
+ return { changes: 0 }
454
+ },
439
455
  async query() {
440
456
  return []
441
457
  },