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,223 @@
1
+ import { DatabaseSync, type SQLInputValue } from 'node:sqlite'
2
+
3
+ import { defineQueries, defineQuery } from '@rocicorp/zero'
4
+ import * as v from 'valibot'
5
+ import { afterEach, beforeEach, describe, expect, test } from 'vitest'
6
+
7
+ import { getScopedAuthData } from './helpers/mutatorContext'
8
+ import { zeroHttpFixtureSchema } from './httpPull/fixtureSchema'
9
+ import { createZeroSyncHostAdapter } from './syncHost'
10
+ import { zql } from './zql'
11
+
12
+ import type { SqlStatementMetadata, SQLiteQueryFormat } from './createZeroSQLiteServer'
13
+ import type {
14
+ SyncHostApplicationTransaction,
15
+ SyncHostMutatorContext,
16
+ SyncHostTransactionExecutor,
17
+ } from './syncHost'
18
+
19
+ const queries = defineQueries({
20
+ project: {
21
+ byOwner: defineQuery(v.object({ ownerId: v.string() }), ({ args }) =>
22
+ zql.project.where('ownerId', args.ownerId),
23
+ ),
24
+ },
25
+ })
26
+
27
+ describe('createZeroSyncHostAdapter', () => {
28
+ let db: DatabaseSync
29
+ let executor: SyncHostTransactionExecutor
30
+ let transaction: SyncHostApplicationTransaction
31
+ let lastQueryFormat: SQLiteQueryFormat | undefined
32
+ let statements: SqlStatementMetadata[]
33
+
34
+ beforeEach(() => {
35
+ db = new DatabaseSync(':memory:')
36
+ db.exec(
37
+ 'CREATE TABLE project (id TEXT PRIMARY KEY, ownerId TEXT NOT NULL, name TEXT NOT NULL)',
38
+ )
39
+ statements = []
40
+ executor = {
41
+ exec(sql, params = [], metadata) {
42
+ if (metadata) statements.push(metadata)
43
+ const result = db.prepare(sql).run(...(params as SQLInputValue[]))
44
+ return { changes: Number(result.changes) }
45
+ },
46
+ query<Row extends Record<string, unknown>>(sql, params = []) {
47
+ return db.prepare(sql).all(...(params as SQLInputValue[])) as Row[]
48
+ },
49
+ queryAst<Result>(_ast, format) {
50
+ lastQueryFormat = format
51
+ const rows = db.prepare('SELECT * FROM project ORDER BY id').all()
52
+ return (format.singular ? rows[0] : rows) as Result
53
+ },
54
+ }
55
+ transaction = async (work) => {
56
+ const effects: Array<() => void | Promise<void>> = []
57
+ db.exec('BEGIN')
58
+ try {
59
+ const value = await work(executor, {
60
+ defer(effect) {
61
+ effects.push(effect)
62
+ },
63
+ })
64
+ db.exec('COMMIT')
65
+ for (const effect of effects) await effect()
66
+ return value
67
+ } catch (error) {
68
+ db.exec('ROLLBACK')
69
+ throw error
70
+ }
71
+ }
72
+ })
73
+
74
+ afterEach(() => {
75
+ db.close()
76
+ })
77
+
78
+ test('adapts pipe-named mutators, auth, validation, and deferred effects', async () => {
79
+ let effectAuthID: string | undefined
80
+ let validatedAuthID: string | undefined
81
+ const adapter = createZeroSyncHostAdapter({
82
+ schema: zeroHttpFixtureSchema,
83
+ models: {
84
+ project: {
85
+ mutate: {
86
+ create: async ({ authData, server, tx }, value: Record<string, unknown>) => {
87
+ expect(authData?.id).toBe('u1')
88
+ await tx.mutate.project.insert(value as never)
89
+ server?.enqueueTask(async () => {
90
+ effectAuthID = getScopedAuthData()?.id as string
91
+ })
92
+ },
93
+ },
94
+ },
95
+ },
96
+ createServerActions: () => ({}),
97
+ queries,
98
+ transaction,
99
+ validateMutation({ authData }) {
100
+ validatedAuthID = authData?.id as string
101
+ },
102
+ })
103
+
104
+ expect(Object.keys(adapter.mutators)).toEqual([
105
+ 'project|create',
106
+ '_zero_cleanupResults',
107
+ ])
108
+ await transaction((tx, context) =>
109
+ adapter.mutators['project|create']!(
110
+ tx,
111
+ { id: 'p1', ownerId: 'u1', name: 'first' },
112
+ mutationContext(context),
113
+ ),
114
+ )
115
+
116
+ expect(db.prepare('SELECT * FROM project').all()).toEqual([
117
+ { id: 'p1', ownerId: 'u1', name: 'first' },
118
+ ])
119
+ expect(validatedAuthID).toBe('u1')
120
+ expect(effectAuthID).toBe('u1')
121
+ expect(statements).toEqual([
122
+ { table: 'project', publicTable: 'project', kind: 'insert' },
123
+ ])
124
+ })
125
+
126
+ test('returns a structural application error and drops rolled-back effects', async () => {
127
+ let effectRuns = 0
128
+ const adapter = createZeroSyncHostAdapter({
129
+ schema: zeroHttpFixtureSchema,
130
+ models: {
131
+ project: {
132
+ mutate: {
133
+ reject: async ({ server, tx }, value: Record<string, unknown>) => {
134
+ await tx.mutate.project.insert(value as never)
135
+ server?.enqueueTask(async () => {
136
+ effectRuns++
137
+ })
138
+ throw new Error('denied')
139
+ },
140
+ },
141
+ },
142
+ },
143
+ createServerActions: () => ({}),
144
+ transaction,
145
+ })
146
+
147
+ const error = await transaction((tx, context) =>
148
+ adapter.mutators['project|reject']!(
149
+ tx,
150
+ { id: 'p1', ownerId: 'u1', name: 'must roll back' },
151
+ mutationContext(context),
152
+ ),
153
+ ).catch((caught) => caught)
154
+
155
+ expect(error).toMatchObject({
156
+ name: 'MutationApplicationError',
157
+ message: 'denied',
158
+ details: 'denied',
159
+ })
160
+ expect(db.prepare('SELECT * FROM project').all()).toEqual([])
161
+ expect(effectRuns).toBe(0)
162
+ })
163
+
164
+ test('uses the host queryAst compiler surface for direct server queries', async () => {
165
+ db.prepare('INSERT INTO project (id, ownerId, name) VALUES (?, ?, ?)').run(
166
+ 'p1',
167
+ 'u1',
168
+ 'first',
169
+ )
170
+ const adapter = createZeroSyncHostAdapter({
171
+ schema: zeroHttpFixtureSchema,
172
+ models: {},
173
+ createServerActions: () => ({}),
174
+ transaction,
175
+ })
176
+
177
+ await expect(adapter.server.query((q) => q.project.one())).resolves.toEqual({
178
+ id: 'p1',
179
+ ownerId: 'u1',
180
+ name: 'first',
181
+ })
182
+ expect(lastQueryFormat).toEqual({ relationships: {}, singular: true })
183
+ })
184
+
185
+ test('resolves auth-sensitive named queries and runs query validation', async () => {
186
+ let validation: unknown
187
+ const adapter = createZeroSyncHostAdapter({
188
+ schema: zeroHttpFixtureSchema,
189
+ models: {},
190
+ createServerActions: () => ({}),
191
+ queries,
192
+ transaction,
193
+ validateQuery(args) {
194
+ validation = args
195
+ },
196
+ })
197
+
198
+ const ast = await adapter.resolveQuery('project|byOwner', [{ ownerId: 'u1' }], {
199
+ userID: 'u1',
200
+ role: 'member',
201
+ })
202
+
203
+ expect(ast).toMatchObject({ table: 'project' })
204
+ expect(JSON.stringify(ast)).toContain('ownerId')
205
+ expect(JSON.stringify(ast)).toContain('u1')
206
+ expect(validation).toEqual({
207
+ authData: { id: 'u1', role: 'member', userID: 'u1' },
208
+ queryName: 'project|byOwner',
209
+ params: { ownerId: 'u1' },
210
+ })
211
+ })
212
+ })
213
+
214
+ function mutationContext(context: {
215
+ defer(effect: () => void | Promise<void>): void
216
+ }): SyncHostMutatorContext {
217
+ return {
218
+ ...context,
219
+ claims: { userID: 'u1', role: 'member' },
220
+ clientID: 'client-a',
221
+ mutationID: '1',
222
+ }
223
+ }
@@ -0,0 +1,294 @@
1
+ import { asQueryInternals } from '@rocicorp/zero/bindings'
2
+ import { createBuilder, type Schema as ZeroSchema } from '@rocicorp/zero'
3
+
4
+ import { createPermissions } from './createPermissions'
5
+ import {
6
+ createZeroDirectServer,
7
+ resolveServerQuery,
8
+ type ZeroDirectServer,
9
+ type ZeroServerCommonOptions,
10
+ } from './createZeroDirectServer'
11
+ import {
12
+ createSQLiteDatabase,
13
+ createZeroSQLiteTransaction,
14
+ defaultLedgerTables,
15
+ encodeSQLiteValue,
16
+ type SqlStatementMetadata,
17
+ type SQLiteExecResult,
18
+ type SQLiteTransactionExecutor,
19
+ type SQLiteTransactionProvider,
20
+ type ZeroSQLiteTransactionOptions,
21
+ } from './sqliteRuntime'
22
+ import { createMutators } from './helpers/createMutators'
23
+ import { runWithAuthScope } from './helpers/mutatorContext'
24
+ import { runWithQueryContext } from './helpers/queryContext'
25
+ import { setEnvironment, setSchema } from './state'
26
+
27
+ import type { AsyncTaskEntry, AuthData, GenericModels } from './types'
28
+
29
+ export type SyncHostJsonValue =
30
+ | string
31
+ | number
32
+ | boolean
33
+ | null
34
+ | SyncHostJsonValue[]
35
+ | { [key: string]: SyncHostJsonValue }
36
+
37
+ export type SyncHostClaims = {
38
+ userID: string
39
+ [claim: string]: SyncHostJsonValue
40
+ }
41
+
42
+ export type SyncHostTransactionContext = {
43
+ defer(effect: () => void | Promise<void>): void
44
+ }
45
+
46
+ export type SyncHostTransactionExecutor = {
47
+ exec(
48
+ sql: string,
49
+ params?: readonly unknown[],
50
+ metadata?: SqlStatementMetadata,
51
+ ): SQLiteExecResult | Promise<SQLiteExecResult>
52
+ query<Row extends Record<string, unknown> = Record<string, unknown>>(
53
+ sql: string,
54
+ params?: readonly unknown[],
55
+ ): Row[] | Promise<Row[]>
56
+ queryAst<Result = unknown>(
57
+ ast: SyncHostJsonValue,
58
+ format: import('./sqliteRuntime').SQLiteQueryFormat,
59
+ queryName?: string,
60
+ ): Result | Promise<Result>
61
+ }
62
+
63
+ export type SyncHostMutatorContext = SyncHostTransactionContext & {
64
+ claims: SyncHostClaims
65
+ clientID?: string
66
+ mutationID?: string | number
67
+ }
68
+
69
+ export type SyncHostRegisteredMutator = (
70
+ tx: SyncHostTransactionExecutor,
71
+ args: SyncHostJsonValue,
72
+ context: SyncHostMutatorContext,
73
+ ) => void | Promise<void>
74
+
75
+ export type SyncHostApplicationTransaction = <Value>(
76
+ work: (
77
+ tx: SyncHostTransactionExecutor,
78
+ context: SyncHostTransactionContext,
79
+ ) => Value | Promise<Value>,
80
+ ) => Promise<Value>
81
+
82
+ export type CreateZeroSyncHostAdapterOptions<
83
+ Schema extends ZeroSchema,
84
+ Models extends GenericModels,
85
+ ServerActions extends Record<string, unknown>,
86
+ > = Omit<
87
+ ZeroServerCommonOptions<Schema, Models, ServerActions>,
88
+ 'defaultMutateAuthData'
89
+ > &
90
+ ZeroSQLiteTransactionOptions<Schema> & {
91
+ transaction: SyncHostApplicationTransaction
92
+ mapAuthData?: (claims: SyncHostClaims) => AuthData
93
+ defaultMutateAuthData?: Pick<AuthData, 'email' | 'id'> & Partial<AuthData>
94
+ }
95
+
96
+ export type ZeroSyncHostAdapter<
97
+ Schema extends ZeroSchema,
98
+ Models extends GenericModels,
99
+ > = {
100
+ mutators: Readonly<Record<string, SyncHostRegisteredMutator>>
101
+ resolveQuery(
102
+ name: string,
103
+ args: readonly SyncHostJsonValue[],
104
+ claims: SyncHostClaims,
105
+ ): Promise<SyncHostJsonValue>
106
+ server: ZeroDirectServer<Schema, Models>
107
+ }
108
+
109
+ export class MutationApplicationError extends Error {
110
+ constructor(
111
+ readonly details: string,
112
+ message = details,
113
+ ) {
114
+ super(message)
115
+ this.name = 'MutationApplicationError'
116
+ }
117
+ }
118
+
119
+ export function createZeroSyncHostAdapter<
120
+ Schema extends ZeroSchema,
121
+ Models extends GenericModels,
122
+ ServerActions extends Record<string, unknown>,
123
+ >({
124
+ encodeValue,
125
+ mapAuthData = defaultMapAuthData,
126
+ transaction,
127
+ ...options
128
+ }: CreateZeroSyncHostAdapterOptions<Schema, Models, ServerActions>): ZeroSyncHostAdapter<
129
+ Schema,
130
+ Models
131
+ > {
132
+ setSchema(options.schema, createBuilder(options.schema))
133
+ setEnvironment('server')
134
+
135
+ const permissions = createPermissions({
136
+ environment: 'server',
137
+ schema: options.schema,
138
+ adminRoleMode: options.defaultAllowAdminRole ?? 'all',
139
+ })
140
+ const transactionOptions = {
141
+ encodeValue,
142
+ schema: options.schema,
143
+ }
144
+ const mutators: Record<string, SyncHostRegisteredMutator> = {}
145
+
146
+ for (const [modelName, model] of Object.entries(options.models)) {
147
+ for (const mutatorName of Object.keys(model.mutate ?? {})) {
148
+ mutators[`${modelName}|${mutatorName}`] = async (executor, args, context) => {
149
+ const authData = mapAuthData(context.claims)
150
+ const asyncTaskEntries: AsyncTaskEntry[] = []
151
+ const modelMutators = createMutators({
152
+ enqueueTask(task, opts) {
153
+ asyncTaskEntries.push({ task, barrier: opts?.barrier === true })
154
+ },
155
+ authData,
156
+ can: permissions.can,
157
+ createServerActions: options.createServerActions,
158
+ environment: 'server',
159
+ models: options.models,
160
+ mutationValidators: options.mutations,
161
+ validateMutation: options.validateMutation,
162
+ }) as Record<
163
+ string,
164
+ Record<string, (tx: unknown, args: unknown) => Promise<void>>
165
+ >
166
+ const mutation = modelMutators[modelName]?.[mutatorName]
167
+ if (!mutation)
168
+ throw new Error(`unknown on-zero mutator: ${modelName}|${mutatorName}`)
169
+
170
+ const mutationID = numericMutationID(context.mutationID)
171
+ const onZeroTransaction = createZeroSQLiteTransaction(
172
+ transactionOptions,
173
+ toSQLiteExecutor(executor),
174
+ {
175
+ clientGroupID: 'orez-sync-host',
176
+ clientID: context.clientID ?? '',
177
+ mutationID,
178
+ upstreamSchema: 'orez-sync-host',
179
+ },
180
+ )
181
+ try {
182
+ await mutation(onZeroTransaction, args)
183
+ } catch (error) {
184
+ throw asMutationApplicationError(error)
185
+ }
186
+ for (const { task } of asyncTaskEntries) {
187
+ context.defer(() => runWithAuthScope(authData, task))
188
+ }
189
+ }
190
+ }
191
+ }
192
+
193
+ // orez owns result retention and lmids. zero clients may still emit this
194
+ // protocol mutation, so accepting it advances the host ledger without
195
+ // creating a second on-zero cleanup ledger.
196
+ mutators._zero_cleanupResults = async () => {}
197
+
198
+ const transactionProvider: SQLiteTransactionProvider = (work) =>
199
+ transaction((executor) => work(toSQLiteExecutor(executor)))
200
+ const directDatabaseOptions = {
201
+ encodeValue: encodeValue ?? encodeSQLiteValue,
202
+ initializedLedgers: new Set<string>(),
203
+ ledgerTables: defaultLedgerTables,
204
+ schema: options.schema,
205
+ transactionProvider,
206
+ }
207
+ const direct = createZeroDirectServer({
208
+ ...options,
209
+ createDatabase: () => createSQLiteDatabase(directDatabaseOptions),
210
+ })
211
+
212
+ return {
213
+ mutators: Object.freeze(mutators),
214
+ async resolveQuery(
215
+ name: string,
216
+ args: readonly SyncHostJsonValue[],
217
+ claims: SyncHostClaims,
218
+ ): Promise<SyncHostJsonValue> {
219
+ if (!options.queries) {
220
+ throw new Error(
221
+ 'No queries registered with createZeroSyncHostAdapter. Pass the syncedQueries registry via the queries option.',
222
+ )
223
+ }
224
+ const authData = mapAuthData(claims)
225
+ const query = await runWithQueryContext({ authData }, () =>
226
+ resolveServerQuery({
227
+ authData,
228
+ args: args[0],
229
+ name,
230
+ permissions,
231
+ queries: options.queries!,
232
+ validateQuery: options.validateQuery,
233
+ }),
234
+ )
235
+ return asQueryInternals(query as never).ast as unknown as SyncHostJsonValue
236
+ },
237
+ server: {
238
+ mutate: direct.mutate,
239
+ query: direct.query,
240
+ transaction: direct.transaction,
241
+ },
242
+ }
243
+ }
244
+
245
+ function defaultMapAuthData(claims: SyncHostClaims): AuthData {
246
+ return {
247
+ ...claims,
248
+ id: typeof claims.id === 'string' ? claims.id : claims.userID,
249
+ } as AuthData
250
+ }
251
+
252
+ function toSQLiteExecutor(
253
+ executor: SyncHostTransactionExecutor,
254
+ ): SQLiteTransactionExecutor {
255
+ return {
256
+ exec(sql, params, metadata) {
257
+ return executor.exec(sql, params, metadata)
258
+ },
259
+ query<Row extends Record<string, unknown>>(sql: string, params?: readonly unknown[]) {
260
+ return executor.query<Row>(sql, params)
261
+ },
262
+ queryAst<Result>(ast, format, queryName) {
263
+ return executor.queryAst<Result>(
264
+ ast as unknown as SyncHostJsonValue,
265
+ format,
266
+ queryName,
267
+ )
268
+ },
269
+ }
270
+ }
271
+
272
+ function numericMutationID(value: string | number | undefined): number {
273
+ if (value === undefined) return 0
274
+ const numeric = typeof value === 'number' ? value : Number(value)
275
+ if (!Number.isSafeInteger(numeric) || numeric < 0) {
276
+ throw new TypeError(`invalid Zero mutation ID: ${value}`)
277
+ }
278
+ return numeric
279
+ }
280
+
281
+ function asMutationApplicationError(error: unknown): MutationApplicationError {
282
+ if (
283
+ typeof error === 'object' &&
284
+ error !== null &&
285
+ (error as { name?: unknown }).name === 'MutationApplicationError' &&
286
+ typeof (error as { details?: unknown }).details === 'string'
287
+ ) {
288
+ return error as MutationApplicationError
289
+ }
290
+ const message = error instanceof Error ? error.message : String(error)
291
+ const applicationError = new MutationApplicationError(message)
292
+ if (error instanceof Error && error.stack) applicationError.stack = error.stack
293
+ return applicationError
294
+ }
package/src/types.ts CHANGED
@@ -66,6 +66,11 @@ export type AsyncTaskOptions = {
66
66
  barrier?: boolean
67
67
  }
68
68
 
69
+ export type AsyncTaskEntry = {
70
+ task: AsyncTask
71
+ barrier: boolean
72
+ }
73
+
69
74
  /**
70
75
  * ➗0️⃣ END OVERRIDDEN TYPES
71
76
  */
package/src/zeroRunner.ts CHANGED
@@ -37,7 +37,7 @@ export function getAmbientRunner(instance?: { runner: ZeroRunner | null }): Zero
37
37
 
38
38
  if (!runner) {
39
39
  throw new Error(
40
- 'Zero runner not initialized. Ensure ProvideZero is mounted or server bindings are active.',
40
+ 'Zero runner not initialized. Ensure ProvideZero is mounted (client) or createZeroServer is called (server).',
41
41
  )
42
42
  }
43
43
 
@@ -1,8 +1,19 @@
1
- import { createOrezBindings } from 'on-zero/orez'
1
+ import {
2
+ createZeroServer,
3
+ runAsyncTaskEntries,
4
+ type AsyncTaskEntry,
5
+ type BackgroundTaskScheduler,
6
+ type HandleMutationRequestOptions,
7
+ } from 'on-zero/server'
8
+ import { createZeroSQLiteServer, type SqlStatementMetadata } from 'on-zero/sqlite'
2
9
 
10
+ import {
11
+ runAsyncTaskEntries as runAsyncTaskEntriesFromRoot,
12
+ type Config,
13
+ type MutatorContext,
14
+ type RunAsyncTaskEntriesOptions,
15
+ } from 'on-zero'
3
16
  import type { Schema as ZeroSchema } from '@rocicorp/zero'
4
- import type { NormalizedClaims, SyncExecutor } from 'orez/sync-executor'
5
- import type { Config, MutatorContext } from 'on-zero'
6
17
 
7
18
  type AppAuthData = {
8
19
  email: string
@@ -25,31 +36,57 @@ type Models = {
25
36
  }
26
37
 
27
38
  type Actions = Record<string, never>
39
+ type PostgresServer = ReturnType<typeof createZeroServer<ZeroSchema, Models, Actions>>
40
+ type SQLiteServer = ReturnType<typeof createZeroSQLiteServer<ZeroSchema, Models, Actions>>
28
41
 
29
- const bindings = createOrezBindings<ZeroSchema, Models, Actions>({
30
- schema: {} as ZeroSchema,
31
- models: {} as Models,
32
- createServerActions: () => ({}),
33
- })
34
-
35
- declare const executor: SyncExecutor<ZeroSchema>
36
- const server = bindings.server(executor)
42
+ declare const postgresServer: PostgresServer
43
+ declare const sqliteServer: SQLiteServer
44
+ declare const taskEntries: AsyncTaskEntry[]
37
45
 
38
46
  const authData: Config['authData'] = {
39
47
  email: 'admin@example.com',
40
48
  id: 'admin',
41
49
  role: 'admin',
42
50
  }
43
- const claims: NormalizedClaims = {
44
- email: authData.email,
45
- role: authData.role ?? null,
46
- userID: authData.id,
51
+
52
+ const sqliteWrite: SqlStatementMetadata = {
53
+ table: 'project_records',
54
+ publicTable: 'project',
55
+ kind: 'upsert',
56
+ }
57
+ void sqliteWrite
58
+
59
+ void runAsyncTaskEntries(taskEntries, {
60
+ background(promise) {
61
+ void promise
62
+ },
63
+ })
64
+
65
+ const rootTaskOptions: RunAsyncTaskEntriesOptions = {
66
+ background(promise) {
67
+ void promise
68
+ },
69
+ }
70
+ void runAsyncTaskEntriesFromRoot(taskEntries, rootTaskOptions)
71
+
72
+ const scheduleBackgroundTask: BackgroundTaskScheduler = (promise) => {
73
+ void promise
74
+ }
75
+ const mutationRequest: HandleMutationRequestOptions = {
76
+ authData,
77
+ request: new Request('https://example.com/api/zero/push'),
78
+ scheduleBackgroundTask,
47
79
  }
80
+ void postgresServer.handleMutationRequest(mutationRequest)
48
81
 
49
- void bindings.resolveQuery('project|byID', [{ id: 'project' }], claims)
50
- void server.mutate.project.create({ id: 'project' }, { authData })
51
- void server.transaction(claims, async (tx) => tx.location)
52
- void server.query(claims, () => null as never)
82
+ postgresServer.mutate.project.create(
83
+ { id: 'pg' },
84
+ { authData: { email: authData.email, id: authData.id, role: authData.role } },
85
+ )
86
+ sqliteServer.mutate.project.create(
87
+ { id: 'sqlite' },
88
+ { authData: { email: authData.email, id: authData.id, role: authData.role } },
89
+ )
53
90
 
54
- // @ts-expect-error mutation argument types remain enforced through the Orez facade
55
- void server.mutate.project.create({ id: 42 }, { authData })
91
+ // @ts-expect-error consumer auth augmentation must remain enforced
92
+ postgresServer.mutate.project.create({ id: 'invalid' }, { authData: { id: 'admin' } })
@@ -0,0 +1,11 @@
1
+ type AsyncTaskEntry = {
2
+ task: () => Promise<void>;
3
+ barrier: boolean;
4
+ };
5
+ export type RunAsyncTaskEntriesOptions = {
6
+ /** Receives the background-task promise. Omit this to await all entries. */
7
+ background?: (promise: Promise<void>) => void;
8
+ };
9
+ export declare function runAsyncTaskEntries(entries: readonly AsyncTaskEntry[], { background }?: RunAsyncTaskEntriesOptions): Promise<void>;
10
+ export {};
11
+ //# sourceMappingURL=asyncTasks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asyncTasks.d.ts","sourceRoot":"","sources":["../src/asyncTasks.ts"],"names":[],"mappings":"AAAA,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACzB,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,4EAA4E;IAC5E,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAA;CAC9C,CAAA;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,SAAS,cAAc,EAAE,EAClC,EAAE,UAAU,EAAE,GAAE,0BAA+B,GAC9C,OAAO,CAAC,IAAI,CAAC,CAsBf"}