on-zero 0.6.12 → 0.6.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/createZeroSQLiteServer.cjs +319 -0
- package/dist/cjs/createZeroSQLiteServer.native.js +440 -0
- package/dist/cjs/createZeroSQLiteServer.native.js.map +1 -0
- package/dist/cjs/createZeroSQLiteServer.test.cjs +429 -0
- package/dist/cjs/createZeroSQLiteServer.test.native.js +466 -0
- package/dist/cjs/createZeroSQLiteServer.test.native.js.map +1 -0
- package/dist/cjs/createZeroServer.cjs +20 -234
- package/dist/cjs/createZeroServer.native.js +24 -250
- package/dist/cjs/createZeroServer.native.js.map +1 -1
- package/dist/cjs/createZeroServer.test.cjs +35 -1
- package/dist/cjs/createZeroServer.test.native.js +38 -1
- package/dist/cjs/createZeroServer.test.native.js.map +1 -1
- package/dist/cjs/createZeroServerCore.cjs +362 -0
- package/dist/cjs/createZeroServerCore.native.js +389 -0
- package/dist/cjs/createZeroServerCore.native.js.map +1 -0
- package/dist/cjs/generate-lite.cjs +1 -0
- package/dist/cjs/generate-lite.native.js +1 -0
- package/dist/cjs/generate-lite.native.js.map +1 -1
- package/dist/cjs/generate-lite.test.cjs +5 -1
- package/dist/cjs/generate-lite.test.native.js +5 -1
- package/dist/cjs/generate-lite.test.native.js.map +1 -1
- package/dist/cjs/generate.cjs +20 -11
- package/dist/cjs/generate.native.js +29 -21
- package/dist/cjs/generate.native.js.map +1 -1
- package/dist/cjs/generate.test.cjs +110 -0
- package/dist/cjs/generate.test.native.js +110 -0
- package/dist/cjs/generate.test.native.js.map +1 -1
- package/dist/cjs/httpPull/transport.test.cjs +5 -4
- package/dist/cjs/httpPull/transport.test.native.js +6 -5
- package/dist/cjs/httpPull/transport.test.native.js.map +1 -1
- package/dist/cjs/httpPullTransport.cjs +2 -1
- package/dist/cjs/httpPullTransport.native.js +3 -2
- package/dist/cjs/httpPullTransport.native.js.map +1 -1
- package/dist/cjs/server.cjs +2 -1
- package/dist/cjs/syncHost.cjs +164 -0
- package/dist/cjs/syncHost.native.js +371 -0
- package/dist/cjs/syncHost.native.js.map +1 -0
- package/dist/cjs/syncHost.test.cjs +229 -0
- package/dist/cjs/syncHost.test.native.js +278 -0
- package/dist/cjs/syncHost.test.native.js.map +1 -0
- package/dist/esm/createZeroSQLiteServer.mjs +293 -0
- package/dist/esm/createZeroSQLiteServer.mjs.map +1 -0
- package/dist/esm/createZeroSQLiteServer.native.js +411 -0
- package/dist/esm/createZeroSQLiteServer.native.js.map +1 -0
- package/dist/esm/createZeroSQLiteServer.test.mjs +430 -0
- package/dist/esm/createZeroSQLiteServer.test.mjs.map +1 -0
- package/dist/esm/createZeroSQLiteServer.test.native.js +464 -0
- package/dist/esm/createZeroSQLiteServer.test.native.js.map +1 -0
- package/dist/esm/createZeroServer.mjs +17 -231
- package/dist/esm/createZeroServer.mjs.map +1 -1
- package/dist/esm/createZeroServer.native.js +21 -247
- package/dist/esm/createZeroServer.native.js.map +1 -1
- package/dist/esm/createZeroServer.test.mjs +35 -1
- package/dist/esm/createZeroServer.test.mjs.map +1 -1
- package/dist/esm/createZeroServer.test.native.js +38 -1
- package/dist/esm/createZeroServer.test.native.js.map +1 -1
- package/dist/esm/createZeroServerCore.mjs +336 -0
- package/dist/esm/createZeroServerCore.mjs.map +1 -0
- package/dist/esm/createZeroServerCore.native.js +360 -0
- package/dist/esm/createZeroServerCore.native.js.map +1 -0
- package/dist/esm/generate-lite.mjs +1 -0
- package/dist/esm/generate-lite.mjs.map +1 -1
- package/dist/esm/generate-lite.native.js +1 -0
- package/dist/esm/generate-lite.native.js.map +1 -1
- package/dist/esm/generate-lite.test.mjs +5 -1
- package/dist/esm/generate-lite.test.mjs.map +1 -1
- package/dist/esm/generate-lite.test.native.js +5 -1
- package/dist/esm/generate-lite.test.native.js.map +1 -1
- package/dist/esm/generate.mjs +20 -11
- package/dist/esm/generate.mjs.map +1 -1
- package/dist/esm/generate.native.js +29 -21
- package/dist/esm/generate.native.js.map +1 -1
- package/dist/esm/generate.test.mjs +87 -1
- package/dist/esm/generate.test.mjs.map +1 -1
- package/dist/esm/generate.test.native.js +87 -1
- package/dist/esm/generate.test.native.js.map +1 -1
- package/dist/esm/httpPull/transport.test.mjs +5 -4
- package/dist/esm/httpPull/transport.test.mjs.map +1 -1
- package/dist/esm/httpPull/transport.test.native.js +6 -5
- package/dist/esm/httpPull/transport.test.native.js.map +1 -1
- package/dist/esm/httpPullTransport.mjs +2 -1
- package/dist/esm/httpPullTransport.mjs.map +1 -1
- package/dist/esm/httpPullTransport.native.js +3 -2
- package/dist/esm/httpPullTransport.native.js.map +1 -1
- package/dist/esm/server.mjs +1 -0
- package/dist/esm/server.mjs.map +1 -1
- package/dist/esm/syncHost.mjs +138 -0
- package/dist/esm/syncHost.mjs.map +1 -0
- package/dist/esm/syncHost.native.js +342 -0
- package/dist/esm/syncHost.native.js.map +1 -0
- package/dist/esm/syncHost.test.mjs +206 -0
- package/dist/esm/syncHost.test.mjs.map +1 -0
- package/dist/esm/syncHost.test.native.js +252 -0
- package/dist/esm/syncHost.test.native.js.map +1 -0
- package/package.json +19 -3
- package/readme.md +26 -0
- package/src/createZeroSQLiteServer.test.ts +461 -0
- package/src/createZeroSQLiteServer.ts +523 -0
- package/src/createZeroServer.test.ts +39 -1
- package/src/createZeroServer.ts +35 -428
- package/src/createZeroServerCore.ts +557 -0
- package/src/generate-lite.test.ts +4 -0
- package/src/generate-lite.ts +1 -0
- package/src/generate.test.ts +87 -1
- package/src/generate.ts +29 -20
- package/src/httpPull/transport.test.ts +6 -5
- package/src/httpPullTransport.ts +9 -4
- package/src/server.ts +1 -0
- package/src/syncHost.test.ts +216 -0
- package/src/syncHost.ts +255 -0
- package/test-fixtures/server-return-types.ts +50 -0
- package/test-fixtures/tsconfig.json +13 -0
- package/types/createZeroSQLiteServer.d.ts +37 -0
- package/types/createZeroSQLiteServer.d.ts.map +1 -0
- package/types/createZeroSQLiteServer.test.d.ts +2 -0
- package/types/createZeroSQLiteServer.test.d.ts.map +1 -0
- package/types/createZeroServer.d.ts +11 -293
- package/types/createZeroServer.d.ts.map +1 -1
- package/types/createZeroServerCore.d.ts +310 -0
- package/types/createZeroServerCore.d.ts.map +1 -0
- package/types/generate-lite.d.ts.map +1 -1
- package/types/generate.d.ts +2 -1
- package/types/generate.d.ts.map +1 -1
- package/types/httpPullTransport.d.ts +1 -0
- package/types/httpPullTransport.d.ts.map +1 -1
- package/types/server.d.ts +1 -0
- package/types/server.d.ts.map +1 -1
- package/types/syncHost.d.ts +51 -0
- package/types/syncHost.d.ts.map +1 -0
- package/types/syncHost.test.d.ts +2 -0
- package/types/syncHost.test.d.ts.map +1 -0
package/src/createZeroServer.ts
CHANGED
|
@@ -1,475 +1,82 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { PushProcessor } from '@rocicorp/zero/pg'
|
|
3
|
-
import { handleQueryRequest as zeroHandleQueryRequest } from '@rocicorp/zero/server'
|
|
4
|
-
import { zeroNodePg, type NodePgTransaction } from '@rocicorp/zero/server/adapters/pg'
|
|
1
|
+
import { zeroNodePg } from '@rocicorp/zero/server/adapters/pg'
|
|
5
2
|
import { assertString } from '@take-out/helpers'
|
|
6
3
|
import { Pool } from 'pg'
|
|
7
4
|
|
|
8
|
-
import {
|
|
9
|
-
import { createMutators } from './helpers/createMutators'
|
|
10
|
-
import {
|
|
11
|
-
getScopedAuthData,
|
|
12
|
-
isInZeroMutation,
|
|
13
|
-
mutatorContext,
|
|
14
|
-
runWithAuthScope,
|
|
15
|
-
} from './helpers/mutatorContext'
|
|
16
|
-
import { runWithQueryContext } from './helpers/queryContext'
|
|
17
|
-
import { getMutationsPermissions } from './modelRegistry'
|
|
18
|
-
import { setCustomQueries } from './run'
|
|
19
|
-
import { getZQL, setEnvironment, setSchema } from './state'
|
|
20
|
-
import { setEvaluatingPermission } from './where'
|
|
21
|
-
import { setRunner } from './zeroRunner'
|
|
5
|
+
import { createZeroServerCore } from './createZeroServerCore'
|
|
22
6
|
|
|
23
7
|
import type {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
} from '
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
type MutateAuthData = Pick<AuthData, 'email' | 'id'> & Partial<AuthData>
|
|
41
|
-
|
|
42
|
-
type MutateOptions = {
|
|
43
|
-
authData?: MutateAuthData
|
|
44
|
-
awaitEffects?: boolean
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
type AlreadyProcessedMutation = {
|
|
48
|
-
id: {
|
|
49
|
-
clientID: string
|
|
50
|
-
id: number
|
|
51
|
-
}
|
|
52
|
-
result: {
|
|
53
|
-
error: 'alreadyProcessed'
|
|
54
|
-
details?: unknown
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
type PushRequestBody = {
|
|
59
|
-
clientGroupID?: unknown
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
type ServerMutate<Models extends GenericModels> = {
|
|
63
|
-
[Key in keyof Models]: {
|
|
64
|
-
[K in keyof Models[Key]['mutate']]: Models[Key]['mutate'][K] extends (
|
|
65
|
-
ctx: MutatorContext,
|
|
66
|
-
arg: infer Arg,
|
|
67
|
-
) => any
|
|
68
|
-
? (arg: Arg, options?: MutateOptions) => Promise<void>
|
|
69
|
-
: (options?: MutateOptions) => Promise<void>
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export type ValidateQueryArgs = {
|
|
74
|
-
authData: AuthData | null
|
|
75
|
-
queryName: string
|
|
76
|
-
params: unknown
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export type ValidateMutationArgs = {
|
|
80
|
-
authData: AuthData | null
|
|
81
|
-
mutatorName: string
|
|
82
|
-
tableName: string
|
|
83
|
-
args: unknown
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export type ValidateQueryFn = (args: ValidateQueryArgs) => void
|
|
87
|
-
export type ValidateMutationFn = (args: ValidateMutationArgs) => void | Promise<void>
|
|
8
|
+
PersistAlreadyProcessedArgs,
|
|
9
|
+
ZeroServer,
|
|
10
|
+
ZeroServerCommonOptions,
|
|
11
|
+
ZeroServerDatabase,
|
|
12
|
+
} from './createZeroServerCore'
|
|
13
|
+
import type { GenericModels } from './types'
|
|
14
|
+
import type { Schema as ZeroSchema } from '@rocicorp/zero'
|
|
15
|
+
import type { NodePgTransaction } from '@rocicorp/zero/server/adapters/pg'
|
|
16
|
+
|
|
17
|
+
export type {
|
|
18
|
+
ValidateMutationArgs,
|
|
19
|
+
ValidateMutationFn,
|
|
20
|
+
ValidateQueryArgs,
|
|
21
|
+
ValidateQueryFn,
|
|
22
|
+
} from './createZeroServerCore'
|
|
88
23
|
|
|
89
24
|
export function createZeroServer<
|
|
90
25
|
Schema extends ZeroSchema,
|
|
91
26
|
Models extends GenericModels,
|
|
92
27
|
ServerActions extends Record<string, unknown>,
|
|
93
28
|
>({
|
|
94
|
-
createServerActions,
|
|
95
29
|
database,
|
|
96
30
|
pool: externalPool,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
queries,
|
|
100
|
-
mutations: mutationValidators,
|
|
101
|
-
validateQuery,
|
|
102
|
-
validateMutation,
|
|
103
|
-
defaultAllowAdminRole = 'all',
|
|
104
|
-
defaultMutateAuthData = {} as MutateAuthData,
|
|
105
|
-
}: {
|
|
31
|
+
...options
|
|
32
|
+
}: ZeroServerCommonOptions<Schema, Models, ServerActions> & {
|
|
106
33
|
/**
|
|
107
|
-
* Optional pre-built Pool-like object
|
|
108
|
-
* When provided,
|
|
34
|
+
* Optional pre-built Pool-like object, such as a PGlite wrapper.
|
|
35
|
+
* When provided, database is not required.
|
|
109
36
|
*/
|
|
110
37
|
pool?: NodePgTransaction
|
|
111
38
|
/**
|
|
112
|
-
* The
|
|
113
|
-
* Not required when
|
|
39
|
+
* The PostgreSQL connection string.
|
|
40
|
+
* Not required when pool is provided.
|
|
114
41
|
*/
|
|
115
42
|
database?: string
|
|
116
|
-
|
|
117
|
-
models: Models
|
|
118
|
-
createServerActions: () => ServerActions
|
|
119
|
-
queries?: AnyQueryRegistry
|
|
120
|
-
/**
|
|
121
|
-
* Generated valibot validators for mutation args, keyed by model.mutationName.
|
|
122
|
-
* Pass the `mutationValidators` export from generated syncedMutations.ts.
|
|
123
|
-
* Args are auto-validated before running the mutation.
|
|
124
|
-
*/
|
|
125
|
-
mutations?: Record<string, Record<string, any>>
|
|
126
|
-
/**
|
|
127
|
-
* Hook to validate queries before execution. Throw to reject.
|
|
128
|
-
* Must be synchronous.
|
|
129
|
-
*/
|
|
130
|
-
validateQuery?: ValidateQueryFn
|
|
131
|
-
/**
|
|
132
|
-
* Hook to validate mutations before execution. Throw to reject.
|
|
133
|
-
*/
|
|
134
|
-
validateMutation?: ValidateMutationFn
|
|
135
|
-
/**
|
|
136
|
-
* Admin role bypass for permissions:
|
|
137
|
-
* - 'all': admin bypasses both query and mutation permissions (default)
|
|
138
|
-
* - 'queries': admin bypasses only query permissions
|
|
139
|
-
* - 'mutations': admin bypasses only mutation permissions
|
|
140
|
-
* - 'off': admin has no special bypass
|
|
141
|
-
*/
|
|
142
|
-
defaultAllowAdminRole?: AdminRoleMode
|
|
143
|
-
/**
|
|
144
|
-
* Default authData used by zeroServer.mutate when no authData is provided
|
|
145
|
-
* and none is available from mutation context or auth scope.
|
|
146
|
-
* Defaults to {}.
|
|
147
|
-
*/
|
|
148
|
-
defaultMutateAuthData?: MutateAuthData
|
|
149
|
-
}) {
|
|
150
|
-
setSchema(schema)
|
|
151
|
-
setEnvironment('server')
|
|
152
|
-
|
|
43
|
+
}): ZeroServer<Schema, Models, ServerActions> {
|
|
153
44
|
let pool: NodePgTransaction
|
|
154
45
|
if (externalPool) {
|
|
155
46
|
pool = externalPool
|
|
156
47
|
} else {
|
|
157
|
-
const dbString = assertString(database,
|
|
48
|
+
const dbString = assertString(database, 'createZeroServer "database"')
|
|
158
49
|
const pgPool = new Pool({
|
|
159
50
|
connectionString: dbString,
|
|
160
|
-
// handle self-signed certificates in production
|
|
161
51
|
ssl: dbString.includes('sslmode=require')
|
|
162
52
|
? { rejectUnauthorized: false }
|
|
163
53
|
: undefined,
|
|
164
54
|
})
|
|
165
55
|
pgPool.on('error', (error) => {
|
|
166
|
-
console.error(
|
|
56
|
+
console.error('[on-zero] pool error', error.message)
|
|
167
57
|
})
|
|
168
58
|
pgPool.on('connect', (client) => {
|
|
169
59
|
client.on('error', (error) => {
|
|
170
|
-
console.error(
|
|
60
|
+
console.error('[on-zero] client error', error.message)
|
|
171
61
|
})
|
|
172
62
|
})
|
|
173
63
|
pool = pgPool
|
|
174
64
|
}
|
|
175
65
|
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
const permissions = createPermissions<Schema>({
|
|
179
|
-
environment: 'server',
|
|
180
|
-
schema,
|
|
181
|
-
adminRoleMode: defaultAllowAdminRole,
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
const processor = new PushProcessor(zeroDb)
|
|
185
|
-
|
|
186
|
-
const handleMutationRequest = async ({
|
|
187
|
-
authData,
|
|
188
|
-
request,
|
|
189
|
-
skipAsyncTasks,
|
|
190
|
-
}: {
|
|
191
|
-
authData: AuthData | null
|
|
192
|
-
request: Request
|
|
193
|
-
skipAsyncTasks?: boolean
|
|
194
|
-
}) => {
|
|
195
|
-
// since mutations do DB work in transaction, avoid any async tasks during
|
|
196
|
-
const asyncTasks: AsyncAction[] = []
|
|
197
|
-
const recoveryRequest = request.clone()
|
|
198
|
-
|
|
199
|
-
const mutators = createMutators({
|
|
200
|
-
asyncTasks,
|
|
201
|
-
can: permissions.can,
|
|
202
|
-
createServerActions,
|
|
203
|
-
environment: 'server',
|
|
204
|
-
models,
|
|
205
|
-
authData,
|
|
206
|
-
validateMutation,
|
|
207
|
-
mutationValidators,
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
// @ts-expect-error type is ok but config in monorepo
|
|
211
|
-
const response = await processor.process(mutators, request)
|
|
212
|
-
await persistAlreadyProcessedMutationResults(pool, recoveryRequest, response)
|
|
213
|
-
|
|
214
|
-
// now finish
|
|
215
|
-
if (!skipAsyncTasks && asyncTasks.length) {
|
|
216
|
-
Promise.all(asyncTasks.map((task) => runWithAuthScope(authData, task))).catch(
|
|
217
|
-
(err: unknown) => {
|
|
218
|
-
console.error(`[push] async tasks failed`, err)
|
|
219
|
-
},
|
|
220
|
-
)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return {
|
|
224
|
-
response,
|
|
225
|
-
asyncTasks,
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const handleQueryRequest = async ({
|
|
230
|
-
authData,
|
|
231
|
-
request,
|
|
232
|
-
}: {
|
|
233
|
-
authData: AuthData | null
|
|
234
|
-
request: Request
|
|
235
|
-
}) => {
|
|
236
|
-
if (!queries) {
|
|
237
|
-
throw new Error(
|
|
238
|
-
'No queries registered with createZeroServer. ' +
|
|
239
|
-
'Pass the syncedQueries registry to createZeroServer via the queries option.',
|
|
240
|
-
)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const handler = (name: string, args: unknown) => {
|
|
244
|
-
// per-model permission queries registered by on-zero at runtime
|
|
245
|
-
if (name.startsWith('permission.')) {
|
|
246
|
-
const table = name.slice('permission.'.length)
|
|
247
|
-
const { objOrId } = args as {
|
|
248
|
-
objOrId: string | Record<string, any>
|
|
249
|
-
}
|
|
250
|
-
const perm = getMutationsPermissions(table)
|
|
251
|
-
if (!perm) {
|
|
252
|
-
throw new Error(`[permission] no permission defined for table: ${table}`)
|
|
253
|
-
}
|
|
254
|
-
// wrap with setEvaluatingPermission so serverWhere evaluates
|
|
255
|
-
// even when environment is 'client' (SSR hydration)
|
|
256
|
-
setEvaluatingPermission(true)
|
|
257
|
-
try {
|
|
258
|
-
return (getZQL() as any)[table]
|
|
259
|
-
.where((eb: any) => {
|
|
260
|
-
return permissions.buildPermissionQuery(authData, eb, perm, objOrId, table)
|
|
261
|
-
})
|
|
262
|
-
.one()
|
|
263
|
-
} finally {
|
|
264
|
-
setEvaluatingPermission(false)
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// run validation hook if provided (must be sync - throw to reject)
|
|
269
|
-
if (validateQuery) {
|
|
270
|
-
validateQuery({ authData, queryName: name, params: args })
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const query = (mustGetQuery as any)(queries, name)
|
|
274
|
-
return query.fn({ args, ctx: authData })
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// when there is an authenticated user, pin the connection to its id so zero
|
|
278
|
-
// server-validates it (a client cannot claim another user's id). when logged
|
|
279
|
-
// out there is no server identity to validate against, so use zero's
|
|
280
|
-
// client-fallback path (the legacy call signature) which trusts whatever id
|
|
281
|
-
// the client declared — "anon", "guest", a per-device id, etc. on-zero stays
|
|
282
|
-
// un-prescriptive about the anon convention; permissions still gate access by
|
|
283
|
-
// authData. passing userID:null to the object form instead would make zero
|
|
284
|
-
// server-validate against null and reject every anon client, since a string
|
|
285
|
-
// connection userID can never equal null.
|
|
286
|
-
const userID = typeof authData?.id === 'string' ? authData.id : undefined
|
|
287
|
-
|
|
288
|
-
const response = await runWithQueryContext(
|
|
289
|
-
{ authData: authData || ({} as AuthData) },
|
|
290
|
-
() =>
|
|
291
|
-
userID === undefined
|
|
292
|
-
? zeroHandleQueryRequest(handler, schema, request)
|
|
293
|
-
: zeroHandleQueryRequest({ handler, schema, request, userID }),
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
return {
|
|
297
|
-
response,
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async function runMutate(
|
|
302
|
-
modelName: string,
|
|
303
|
-
mutatorName: string,
|
|
304
|
-
mutatorArg: unknown,
|
|
305
|
-
options?: MutateOptions,
|
|
306
|
-
) {
|
|
307
|
-
let authData = options?.authData
|
|
308
|
-
|
|
309
|
-
// auto-resolve authData from mutation context or auth scope
|
|
310
|
-
if (!authData) {
|
|
311
|
-
const scoped = getScopedAuthData()
|
|
312
|
-
if (scoped) {
|
|
313
|
-
authData = scoped as MutateAuthData
|
|
314
|
-
}
|
|
315
|
-
}
|
|
66
|
+
const databaseProvider = zeroNodePg(options.schema, pool)
|
|
316
67
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
environment: 'server',
|
|
322
|
-
asyncTasks,
|
|
323
|
-
authData: {
|
|
324
|
-
...defaultMutateAuthData,
|
|
325
|
-
...authData,
|
|
326
|
-
},
|
|
327
|
-
createServerActions,
|
|
328
|
-
can: permissions.can,
|
|
329
|
-
validateMutation,
|
|
330
|
-
mutationValidators,
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
const modelMutators = mutators[modelName as keyof typeof mutators] as Record<
|
|
334
|
-
string,
|
|
335
|
-
(tx: Transaction, arg?: unknown) => Promise<void>
|
|
336
|
-
>
|
|
337
|
-
const mutator = modelMutators[mutatorName]
|
|
338
|
-
|
|
339
|
-
await transaction(async (tx) => {
|
|
340
|
-
await mutator(tx, mutatorArg)
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
if (asyncTasks.length) {
|
|
344
|
-
const resolvedAuth = authData ?? null
|
|
345
|
-
const promise = Promise.all(
|
|
346
|
-
asyncTasks.map((t) => runWithAuthScope(resolvedAuth, t)),
|
|
347
|
-
)
|
|
348
|
-
if (options?.awaitEffects) {
|
|
349
|
-
await promise
|
|
350
|
-
} else {
|
|
351
|
-
promise.catch((err: unknown) => {
|
|
352
|
-
console.error(`[mutate] async tasks failed`, err)
|
|
353
|
-
})
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// zeroServer.mutate.user.insert(user)
|
|
359
|
-
const mutate = new Proxy({} as ServerMutate<Models>, {
|
|
360
|
-
get(_, modelName: string) {
|
|
361
|
-
return new Proxy(
|
|
362
|
-
{},
|
|
363
|
-
{
|
|
364
|
-
get(_, mutatorName: string) {
|
|
365
|
-
return (arg: unknown, options?: MutateOptions) =>
|
|
366
|
-
runMutate(modelName, mutatorName, arg, options)
|
|
367
|
-
},
|
|
368
|
-
},
|
|
369
|
-
)
|
|
370
|
-
},
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
async function transaction<
|
|
374
|
-
CB extends (tx: Transaction) => Promise<any>,
|
|
375
|
-
Returns extends CB extends (tx: Transaction) => Promise<infer X> ? X : never,
|
|
376
|
-
>(query: CB): Promise<Returns> {
|
|
377
|
-
try {
|
|
378
|
-
if (isInZeroMutation()) {
|
|
379
|
-
const { tx } = mutatorContext()
|
|
380
|
-
return await query(tx)
|
|
381
|
-
}
|
|
382
|
-
// @ts-expect-error type
|
|
383
|
-
const output = await zeroDb.transaction(query, dummyTransactionInput)
|
|
384
|
-
return output
|
|
385
|
-
} catch (err) {
|
|
386
|
-
if ((err as any)?.name !== 'PermissionError') {
|
|
387
|
-
console.error(`Error running transaction(): ${err}`)
|
|
388
|
-
}
|
|
389
|
-
throw err
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function query<R>(
|
|
394
|
-
cb: (q: QueryBuilder) => Query<any, Schema, R>,
|
|
395
|
-
authData?: AuthData | null,
|
|
396
|
-
): Promise<HumanReadable<R>> {
|
|
397
|
-
const run = () =>
|
|
398
|
-
transaction(async (tx) => {
|
|
399
|
-
return tx.run(cb(getZQL()))
|
|
400
|
-
}) as any
|
|
401
|
-
|
|
402
|
-
if (authData !== undefined) {
|
|
403
|
-
return runWithQueryContext({ authData }, run)
|
|
404
|
-
}
|
|
405
|
-
return run()
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// register for global run() helper
|
|
409
|
-
if (queries) {
|
|
410
|
-
setCustomQueries(queries)
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// server uses transaction-based execution
|
|
414
|
-
setRunner((queryObj) => {
|
|
415
|
-
return transaction(async (tx) => {
|
|
416
|
-
return tx.run(queryObj)
|
|
417
|
-
})
|
|
418
|
-
})
|
|
419
|
-
|
|
420
|
-
// This is needed temporarily and will be cleaned up in the future.
|
|
421
|
-
const dummyTransactionInput: TransactionProviderInput = {
|
|
422
|
-
clientGroupID: 'unused',
|
|
423
|
-
clientID: 'unused',
|
|
424
|
-
mutationID: 42,
|
|
425
|
-
upstreamSchema: 'unused',
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
return {
|
|
429
|
-
handleMutationRequest,
|
|
430
|
-
handleQueryRequest,
|
|
431
|
-
transaction,
|
|
432
|
-
mutate,
|
|
433
|
-
query,
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function alreadyProcessedMutations(response: unknown): AlreadyProcessedMutation[] {
|
|
438
|
-
if (!response || typeof response !== 'object') return []
|
|
439
|
-
const mutations = (response as { mutations?: unknown }).mutations
|
|
440
|
-
if (!Array.isArray(mutations)) return []
|
|
441
|
-
|
|
442
|
-
return mutations.filter((mutation): mutation is AlreadyProcessedMutation => {
|
|
443
|
-
if (!mutation || typeof mutation !== 'object') return false
|
|
444
|
-
const id = (mutation as { id?: unknown }).id
|
|
445
|
-
const result = (mutation as { result?: unknown }).result
|
|
446
|
-
return (
|
|
447
|
-
!!id &&
|
|
448
|
-
typeof id === 'object' &&
|
|
449
|
-
typeof (id as { clientID?: unknown }).clientID === 'string' &&
|
|
450
|
-
typeof (id as { id?: unknown }).id === 'number' &&
|
|
451
|
-
!!result &&
|
|
452
|
-
typeof result === 'object' &&
|
|
453
|
-
(result as { error?: unknown }).error === 'alreadyProcessed'
|
|
454
|
-
)
|
|
68
|
+
return createZeroServerCore({
|
|
69
|
+
...options,
|
|
70
|
+
createDatabase: () => databaseProvider as unknown as ZeroServerDatabase,
|
|
71
|
+
persistAlreadyProcessed: (args) => persistAlreadyProcessedMutationResults(pool, args),
|
|
455
72
|
})
|
|
456
73
|
}
|
|
457
74
|
|
|
458
75
|
async function persistAlreadyProcessedMutationResults(
|
|
459
76
|
pool: NodePgTransaction,
|
|
460
|
-
|
|
461
|
-
response: unknown,
|
|
77
|
+
{ clientGroupID, mutations, upstreamSchema }: PersistAlreadyProcessedArgs,
|
|
462
78
|
) {
|
|
463
|
-
const
|
|
464
|
-
if (mutations.length === 0) return
|
|
465
|
-
|
|
466
|
-
const schema = new URL(request.url).searchParams.get('schema')
|
|
467
|
-
if (!schema) return
|
|
468
|
-
|
|
469
|
-
const body = (await request.json()) as PushRequestBody
|
|
470
|
-
if (typeof body.clientGroupID !== 'string') return
|
|
471
|
-
|
|
472
|
-
const table = `${quotePgIdentifier(schema)}."mutations"`
|
|
79
|
+
const table = `${quotePgIdentifier(upstreamSchema)}."mutations"`
|
|
473
80
|
const values = mutations
|
|
474
81
|
.map((_, index) => {
|
|
475
82
|
const offset = index * 4
|
|
@@ -477,7 +84,7 @@ async function persistAlreadyProcessedMutationResults(
|
|
|
477
84
|
})
|
|
478
85
|
.join(', ')
|
|
479
86
|
const params = mutations.flatMap((mutation) => [
|
|
480
|
-
|
|
87
|
+
clientGroupID,
|
|
481
88
|
mutation.id.clientID,
|
|
482
89
|
mutation.id.id,
|
|
483
90
|
JSON.stringify(mutation.result),
|