@orpc/server 0.0.0-next.b15d206 → 0.0.0-next.ef3ba82

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. package/dist/{chunk-TDFYNRZV.js → chunk-CVLK2PBB.js} +0 -1
  2. package/dist/fetch.js +74 -650
  3. package/dist/index.js +1 -2
  4. package/dist/src/builder.d.ts +0 -1
  5. package/dist/src/fetch/handle.d.ts +6 -0
  6. package/dist/src/fetch/handler.d.ts +2 -0
  7. package/dist/src/fetch/index.d.ts +3 -0
  8. package/dist/src/{adapters/fetch.d.ts → fetch/types.d.ts} +8 -16
  9. package/dist/src/index.d.ts +0 -1
  10. package/dist/src/middleware.d.ts +0 -1
  11. package/dist/src/procedure-builder.d.ts +0 -1
  12. package/dist/src/procedure-caller.d.ts +0 -1
  13. package/dist/src/procedure-implementer.d.ts +0 -1
  14. package/dist/src/procedure.d.ts +0 -1
  15. package/dist/src/router-builder.d.ts +0 -1
  16. package/dist/src/router-caller.d.ts +0 -1
  17. package/dist/src/router-implementer.d.ts +0 -1
  18. package/dist/src/router.d.ts +0 -1
  19. package/dist/src/types.d.ts +0 -1
  20. package/dist/src/utils.d.ts +0 -1
  21. package/package.json +14 -18
  22. package/dist/chunk-TDFYNRZV.js.map +0 -1
  23. package/dist/fetch.js.map +0 -1
  24. package/dist/index.js.map +0 -1
  25. package/dist/src/adapters/fetch.d.ts.map +0 -1
  26. package/dist/src/builder.d.ts.map +0 -1
  27. package/dist/src/index.d.ts.map +0 -1
  28. package/dist/src/middleware.d.ts.map +0 -1
  29. package/dist/src/procedure-builder.d.ts.map +0 -1
  30. package/dist/src/procedure-caller.d.ts.map +0 -1
  31. package/dist/src/procedure-implementer.d.ts.map +0 -1
  32. package/dist/src/procedure.d.ts.map +0 -1
  33. package/dist/src/router-builder.d.ts.map +0 -1
  34. package/dist/src/router-caller.d.ts.map +0 -1
  35. package/dist/src/router-implementer.d.ts.map +0 -1
  36. package/dist/src/router.d.ts.map +0 -1
  37. package/dist/src/types.d.ts.map +0 -1
  38. package/dist/src/utils.d.ts.map +0 -1
  39. package/dist/tsconfig.tsbuildinfo +0 -1
  40. package/src/adapters/fetch.test.ts +0 -629
  41. package/src/adapters/fetch.ts +0 -290
  42. package/src/builder.test.ts +0 -371
  43. package/src/builder.ts +0 -238
  44. package/src/index.ts +0 -16
  45. package/src/middleware.test.ts +0 -260
  46. package/src/middleware.ts +0 -136
  47. package/src/procedure-builder.test.ts +0 -223
  48. package/src/procedure-builder.ts +0 -158
  49. package/src/procedure-caller.test.ts +0 -171
  50. package/src/procedure-caller.ts +0 -138
  51. package/src/procedure-implementer.test.ts +0 -220
  52. package/src/procedure-implementer.ts +0 -102
  53. package/src/procedure.test.ts +0 -317
  54. package/src/procedure.ts +0 -237
  55. package/src/router-builder.test.ts +0 -106
  56. package/src/router-builder.ts +0 -122
  57. package/src/router-caller.test.ts +0 -126
  58. package/src/router-caller.ts +0 -64
  59. package/src/router-implementer.test.ts +0 -116
  60. package/src/router-implementer.ts +0 -113
  61. package/src/router.test-d.ts +0 -48
  62. package/src/router.test.ts +0 -142
  63. package/src/router.ts +0 -91
  64. package/src/types.test.ts +0 -18
  65. package/src/types.ts +0 -13
  66. package/src/utils.test.ts +0 -16
  67. package/src/utils.ts +0 -16
@@ -1,290 +0,0 @@
1
- /// <reference lib="dom" />
2
-
3
- import type {
4
- PartialOnUndefinedDeep,
5
- Promisable,
6
- Value,
7
- } from '@orpc/shared'
8
- import type { Router } from '../router'
9
- import {
10
- type HTTPPath,
11
- ORPC_HEADER,
12
- ORPC_HEADER_VALUE,
13
- standardizeHTTPPath,
14
- } from '@orpc/contract'
15
- import {
16
- get,
17
- isPlainObject,
18
- mapValues,
19
- trim,
20
- value,
21
- } from '@orpc/shared'
22
- import { ORPCError } from '@orpc/shared/error'
23
- import {
24
- OpenAPIDeserializer,
25
- OpenAPISerializer,
26
- ORPCDeserializer,
27
- ORPCSerializer,
28
- zodCoerce,
29
- } from '@orpc/transformer'
30
- import { LinearRouter } from 'hono/router/linear-router'
31
- import { RegExpRouter } from 'hono/router/reg-exp-router'
32
- import { isProcedure, type WELL_DEFINED_PROCEDURE } from '../procedure'
33
- import { createProcedureCaller } from '../procedure-caller'
34
-
35
- export interface FetchHandlerHooks {
36
- next: () => Promise<Response>
37
- response: (response: Response) => Response
38
- }
39
-
40
- export interface CreateFetchHandlerOptions<TRouter extends Router<any>> {
41
- router: TRouter
42
-
43
- /**
44
- * Hooks for executing logics on lifecycle events.
45
- */
46
- hooks?: (
47
- context: TRouter extends Router<infer UContext> ? UContext : never,
48
- hooks: FetchHandlerHooks,
49
- ) => Promisable<Response>
50
-
51
- /**
52
- * It will help improve the cold start time. But it will increase the performance.
53
- *
54
- * @default false
55
- */
56
- serverless?: boolean
57
- }
58
-
59
- export function createFetchHandler<TRouter extends Router<any>>(
60
- options: CreateFetchHandlerOptions<TRouter>,
61
- ): FetchHandler<TRouter> {
62
- const routing = options.serverless
63
- ? new LinearRouter<[string[], WELL_DEFINED_PROCEDURE]>()
64
- : new RegExpRouter<[string[], WELL_DEFINED_PROCEDURE]>()
65
-
66
- const addRouteRecursively = (router: Router<any>, basePath: string[]) => {
67
- for (const key in router) {
68
- const currentPath = [...basePath, key]
69
- const item = router[key] as WELL_DEFINED_PROCEDURE | Router<any>
70
-
71
- if (isProcedure(item)) {
72
- if (item.zz$p.contract.zz$cp.path) {
73
- const method = item.zz$p.contract.zz$cp.method ?? 'POST'
74
- const path = openAPIPathToRouterPath(item.zz$p.contract.zz$cp.path)
75
-
76
- routing.add(method, path, [currentPath, item])
77
- }
78
- }
79
- else {
80
- addRouteRecursively(item, currentPath)
81
- }
82
- }
83
- }
84
-
85
- addRouteRecursively(options.router, [])
86
-
87
- return async (requestOptions) => {
88
- const isORPCTransformer
89
- = requestOptions.request.headers.get(ORPC_HEADER) === ORPC_HEADER_VALUE
90
- const accept = requestOptions.request.headers.get('Accept') || undefined
91
-
92
- const serializer = isORPCTransformer
93
- ? new ORPCSerializer()
94
- : new OpenAPISerializer({ accept })
95
-
96
- const context = await value(requestOptions.context)
97
-
98
- const handler = async () => {
99
- const url = new URL(requestOptions.request.url)
100
- const pathname = `/${trim(url.pathname.replace(requestOptions.prefix ?? '', ''), '/')}`
101
-
102
- let path: string[] | undefined
103
- let procedure: WELL_DEFINED_PROCEDURE | undefined
104
- let params: Record<string, string> | undefined
105
-
106
- if (isORPCTransformer) {
107
- path = trim(pathname, '/').split('/').map(decodeURIComponent)
108
- const val = get(options.router, path)
109
-
110
- if (isProcedure(val)) {
111
- procedure = val
112
- }
113
- }
114
- else {
115
- const customMethod
116
- = requestOptions.request.method === 'POST'
117
- ? url.searchParams.get('method')?.toUpperCase()
118
- : undefined
119
- const method = customMethod || requestOptions.request.method
120
-
121
- const [matches, params_] = routing.match(method, pathname)
122
-
123
- const [match] = matches.sort((a, b) => {
124
- return Object.keys(a[1]).length - Object.keys(b[1]).length
125
- })
126
-
127
- if (match) {
128
- path = match[0][0]
129
- procedure = match[0][1]
130
-
131
- if (params_) {
132
- params = mapValues(
133
- (match as any)[1]!,
134
- v => params_[v as number]!,
135
- )
136
- }
137
- else {
138
- params = match[1] as Record<string, string>
139
- }
140
- }
141
-
142
- if (!path || !procedure) {
143
- path = trim(pathname, '/').split('/').map(decodeURIComponent)
144
-
145
- const val = get(options.router, path)
146
-
147
- if (isProcedure(val)) {
148
- procedure = val
149
- }
150
- }
151
- }
152
-
153
- if (!path || !procedure) {
154
- throw new ORPCError({ code: 'NOT_FOUND', message: 'Not found' })
155
- }
156
-
157
- const deserializer = isORPCTransformer
158
- ? new ORPCDeserializer()
159
- : new OpenAPIDeserializer({
160
- schema: procedure.zz$p.contract.zz$cp.InputSchema,
161
- })
162
-
163
- const input_ = await (async () => {
164
- try {
165
- return await deserializer.deserialize(requestOptions.request)
166
- }
167
- catch (e) {
168
- throw new ORPCError({
169
- code: 'BAD_REQUEST',
170
- message:
171
- 'Cannot parse request. Please check the request body and Content-Type header.',
172
- cause: e,
173
- })
174
- }
175
- })()
176
-
177
- const input = (() => {
178
- if (!params || Object.keys(params).length === 0) {
179
- return input_
180
- }
181
-
182
- const coercedParams = procedure.zz$p.contract.zz$cp.InputSchema
183
- ? (zodCoerce(
184
- procedure.zz$p.contract.zz$cp.InputSchema,
185
- { ...params },
186
- {
187
- bracketNotation: true,
188
- },
189
- ) as object)
190
- : params
191
-
192
- if (!isPlainObject(input_)) {
193
- return coercedParams
194
- }
195
-
196
- return {
197
- ...coercedParams,
198
- ...input_,
199
- }
200
- })()
201
-
202
- const caller = createProcedureCaller({
203
- context,
204
- procedure,
205
- path,
206
- })
207
-
208
- const output = await caller(input)
209
-
210
- const { body, headers } = serializer.serialize(output)
211
-
212
- return new Response(body, {
213
- status: 200,
214
- headers,
215
- })
216
- }
217
-
218
- try {
219
- return await options.hooks?.(context as any, {
220
- next: handler,
221
- response: response => response,
222
- }) ?? await handler()
223
- }
224
- catch (e) {
225
- const error = toORPCError(e)
226
-
227
- try {
228
- const { body, headers } = serializer.serialize(error.toJSON())
229
-
230
- return new Response(body, {
231
- status: error.status,
232
- headers,
233
- })
234
- }
235
- catch (e) {
236
- const error = toORPCError(e)
237
-
238
- // fallback to OpenAPI serializer (without accept) when expected serializer has failed
239
- const { body, headers } = new OpenAPISerializer().serialize(
240
- error.toJSON(),
241
- )
242
-
243
- return new Response(body, {
244
- status: error.status,
245
- headers,
246
- })
247
- }
248
- }
249
- }
250
- }
251
-
252
- function openAPIPathToRouterPath(path: HTTPPath): string {
253
- return standardizeHTTPPath(path).replace(/\{([^}]+)\}/g, ':$1')
254
- }
255
-
256
- export type FetchHandlerOptions<TRouter extends Router<any>> = {
257
- /**
258
- * The request need to be handled.
259
- */
260
- request: Request
261
-
262
- /**
263
- * Remove the prefix from the request path.
264
- *
265
- * @example /orpc
266
- * @example /api
267
- */
268
- prefix?: string
269
- } & PartialOnUndefinedDeep<{
270
- /**
271
- * The context used to handle the request.
272
- */
273
- context: Value<
274
- TRouter extends Router<infer UContext> ? UContext : never
275
- >
276
- }>
277
-
278
- export interface FetchHandler<TRouter extends Router<any>> {
279
- (options: FetchHandlerOptions<TRouter>): Promise<Response>
280
- }
281
-
282
- function toORPCError(e: unknown): ORPCError<any, any> {
283
- return e instanceof ORPCError
284
- ? e
285
- : new ORPCError({
286
- code: 'INTERNAL_SERVER_ERROR',
287
- message: 'Internal server error',
288
- cause: e,
289
- })
290
- }
@@ -1,371 +0,0 @@
1
- import type {
2
- Builder,
3
- DecoratedMiddleware,
4
- DecoratedProcedure,
5
- Meta,
6
- MiddlewareMeta,
7
- } from '.'
8
- import { oc } from '@orpc/contract'
9
- import { z } from 'zod'
10
- import {
11
- isProcedure,
12
- os,
13
- ProcedureBuilder,
14
- ProcedureImplementer,
15
- RouterImplementer,
16
- } from '.'
17
- import { RouterBuilder } from './router-builder'
18
-
19
- it('context method', () => {
20
- expectTypeOf<
21
- typeof os extends Builder<infer TContext, any> ? TContext : never
22
- >().toEqualTypeOf<undefined | Record<string, unknown>>()
23
-
24
- const os2 = os.context<{ foo: 'bar' }>()
25
-
26
- expectTypeOf<
27
- typeof os2 extends Builder<infer TContext, any> ? TContext : never
28
- >().toEqualTypeOf<{ foo: 'bar' }>()
29
-
30
- const os3 = os.context<{ foo: 'bar' }>().context()
31
-
32
- expectTypeOf<
33
- typeof os3 extends Builder<infer TContext, any> ? TContext : never
34
- >().toEqualTypeOf<{ foo: 'bar' }>()
35
- })
36
-
37
- describe('use middleware', () => {
38
- type Context = { auth: boolean }
39
-
40
- const osw = os.context<Context>()
41
-
42
- it('infer types', () => {
43
- osw.use((input, context, meta) => {
44
- expectTypeOf(input).toEqualTypeOf<unknown>()
45
- expectTypeOf(context).toEqualTypeOf<Context>()
46
- expectTypeOf(meta).toEqualTypeOf<MiddlewareMeta<unknown>>()
47
-
48
- return meta.next({})
49
- })
50
- })
51
-
52
- it('can map context', () => {
53
- osw
54
- .use((_, __, meta) => {
55
- return meta.next({ context: { userId: '1' } })
56
- })
57
- .use((_, context, meta) => {
58
- expectTypeOf(context).toMatchTypeOf<Context & { userId: string }>()
59
-
60
- return meta.next({})
61
- })
62
- })
63
-
64
- it('can map input', () => {
65
- osw
66
- // @ts-expect-error mismatch input
67
- .use((input: { postId: string }) => {})
68
- .use(
69
- (input: { postId: string }, _, meta) => {
70
- return meta.next({ context: { user: '1' } })
71
- },
72
- (input) => {
73
- expectTypeOf(input).toEqualTypeOf<unknown>()
74
- return { postId: '1' }
75
- },
76
- )
77
- .func((_, context) => {
78
- expectTypeOf(context).toMatchTypeOf<{ user: string }>()
79
- })
80
- })
81
- })
82
-
83
- describe('create middleware', () => {
84
- it('infer types', () => {
85
- const mid = os
86
- .context<{ auth: boolean }>()
87
- .middleware((input, context, meta) => {
88
- expectTypeOf(input).toEqualTypeOf<unknown>()
89
- expectTypeOf(context).toEqualTypeOf<{ auth: boolean }>()
90
- expectTypeOf(meta).toEqualTypeOf<MiddlewareMeta<any>>()
91
-
92
- return meta.next({ })
93
- })
94
-
95
- expectTypeOf(mid).toEqualTypeOf<
96
- DecoratedMiddleware<{ auth: boolean }, undefined, unknown, any>
97
- >()
98
- })
99
-
100
- it('map context', () => {
101
- const mid = os.context<{ auth: boolean }>().middleware((_, __, meta) => {
102
- return meta.next({ context: { userId: '1' } })
103
- })
104
-
105
- expectTypeOf(mid).toEqualTypeOf<
106
- DecoratedMiddleware<
107
- { auth: boolean },
108
- { userId: string },
109
- unknown,
110
- any
111
- >
112
- >()
113
- })
114
- })
115
-
116
- it('router method', () => {
117
- const pingContract = oc.input(z.string()).output(z.string())
118
- const userFindContract = oc
119
- .input(z.object({ id: z.string() }))
120
- .output(z.object({ name: z.string() }))
121
-
122
- const contract = oc.router({
123
- ping: pingContract,
124
- user: {
125
- find: userFindContract,
126
- },
127
-
128
- user2: oc.router({
129
- find: userFindContract,
130
- }),
131
-
132
- router: userFindContract,
133
- })
134
-
135
- const osw = os.contract(contract)
136
-
137
- expect(osw.ping).instanceOf(ProcedureImplementer)
138
- expect(osw.ping.zz$pi.contract).toEqual(pingContract)
139
-
140
- expect(osw.user).instanceOf(RouterImplementer)
141
-
142
- expect(osw.user.find).instanceOf(ProcedureImplementer)
143
- expect(osw.user.find.zz$pi.contract).toEqual(userFindContract)
144
-
145
- // Because of the router keyword is special, we can't use instanceof
146
- expect(osw.router.zz$pi.contract).toEqual(userFindContract)
147
- expect(
148
- osw.router.func(() => {
149
- return { name: '' }
150
- }),
151
- ).toSatisfy(isProcedure)
152
- })
153
-
154
- describe('define procedure builder', () => {
155
- const osw = os.context<{ auth: boolean }>()
156
- const schema1 = z.object({})
157
- const example1 = {}
158
- const schema2 = z.object({ a: z.string() })
159
- const example2 = { a: '' }
160
-
161
- it('input method', () => {
162
- const builder = osw.input(schema1, example1)
163
-
164
- expectTypeOf(builder).toEqualTypeOf<
165
- ProcedureBuilder<{ auth: boolean }, undefined, typeof schema1, undefined>
166
- >()
167
-
168
- expect(builder).instanceOf(ProcedureBuilder)
169
- expect(builder.zz$pb.middlewares).toBe(undefined)
170
- expect(builder.zz$pb).toMatchObject({
171
- contract: {
172
- zz$cp: {
173
- InputSchema: schema1,
174
- inputExample: example1,
175
- },
176
- },
177
- })
178
- })
179
-
180
- it('output method', () => {
181
- const builder = osw.output(schema2, example2)
182
-
183
- expectTypeOf(builder).toEqualTypeOf<
184
- ProcedureBuilder<{ auth: boolean }, undefined, undefined, typeof schema2>
185
- >()
186
-
187
- expect(builder).instanceOf(ProcedureBuilder)
188
- expect(builder.zz$pb.middlewares).toBe(undefined)
189
- expect(builder.zz$pb).toMatchObject({
190
- contract: {
191
- zz$cp: {
192
- OutputSchema: schema2,
193
- outputExample: example2,
194
- },
195
- },
196
- })
197
- })
198
-
199
- it('route method', () => {
200
- const builder = osw.route({
201
- method: 'GET',
202
- path: '/test',
203
- deprecated: true,
204
- description: 'des',
205
- summary: 'sum',
206
- tags: ['cccc'],
207
- })
208
-
209
- expectTypeOf(builder).toEqualTypeOf<
210
- ProcedureBuilder<{ auth: boolean }, undefined, undefined, undefined>
211
- >()
212
-
213
- expect(builder).instanceOf(ProcedureBuilder)
214
- expect(builder.zz$pb.middlewares).toBe(undefined)
215
- expect(builder.zz$pb).toMatchObject({
216
- contract: {
217
- zz$cp: {
218
- method: 'GET',
219
- path: '/test',
220
- deprecated: true,
221
- description: 'des',
222
- summary: 'sum',
223
- tags: ['cccc'],
224
- },
225
- },
226
- })
227
- })
228
-
229
- it('with middlewares', () => {
230
- const mid = os.middleware((_, __, meta) => {
231
- return meta.next({
232
- context: {
233
- userId: 'string',
234
- },
235
- })
236
- })
237
-
238
- const mid2 = os.middleware((_, __, meta) => {
239
- return meta.next({
240
- context: {
241
- mid2: true,
242
- },
243
- })
244
- })
245
-
246
- const osw = os.context<{ auth: boolean }>().use(mid).use(mid2)
247
-
248
- const builder1 = osw.input(schema1)
249
- const builder2 = osw.output(schema2)
250
- const builder3 = osw.route({ method: 'GET', path: '/test' })
251
-
252
- expectTypeOf(builder1).toEqualTypeOf<
253
- ProcedureBuilder<
254
- { auth: boolean },
255
- { userId: string } & { mid2: boolean },
256
- typeof schema1,
257
- undefined
258
- >
259
- >()
260
-
261
- expectTypeOf(builder2).toEqualTypeOf<
262
- ProcedureBuilder<
263
- { auth: boolean },
264
- { userId: string } & { mid2: boolean },
265
- undefined,
266
- typeof schema2
267
- >
268
- >()
269
-
270
- expectTypeOf(builder3).toEqualTypeOf<
271
- ProcedureBuilder<
272
- { auth: boolean },
273
- { userId: string } & { mid2: boolean },
274
- undefined,
275
- undefined
276
- >
277
- >()
278
-
279
- expect(builder1.zz$pb.middlewares).toEqual([mid, mid2])
280
- expect(builder2.zz$pb.middlewares).toEqual([mid, mid2])
281
- expect(builder3.zz$pb.middlewares).toEqual([mid, mid2])
282
- })
283
- })
284
-
285
- describe('handler method', () => {
286
- it('without middlewares', () => {
287
- const osw = os.context<{ auth: boolean }>()
288
-
289
- const procedure = osw.func((input, context, meta) => {
290
- expectTypeOf(input).toEqualTypeOf<unknown>()
291
- expectTypeOf(context).toEqualTypeOf<{ auth: boolean }>()
292
- expectTypeOf(meta).toEqualTypeOf<Meta>()
293
- })
294
-
295
- expectTypeOf(procedure).toEqualTypeOf<
296
- DecoratedProcedure<
297
- { auth: boolean },
298
- undefined,
299
- undefined,
300
- undefined,
301
- void
302
- >
303
- >()
304
-
305
- expect(isProcedure(procedure)).toBe(true)
306
- expect(procedure.zz$p.middlewares).toBe(undefined)
307
- })
308
-
309
- it('with middlewares', () => {
310
- const mid = os.middleware((_, __, meta) => {
311
- return meta.next({
312
- context: {
313
- userId: 'string',
314
- },
315
- })
316
- })
317
-
318
- const osw = os.context<{ auth: boolean }>().use(mid)
319
-
320
- const procedure = osw.func((input, context, meta) => {
321
- expectTypeOf(input).toEqualTypeOf<unknown>()
322
- expectTypeOf(context).toMatchTypeOf<{ auth: boolean }>()
323
- expectTypeOf(meta).toEqualTypeOf<Meta>()
324
- })
325
-
326
- expectTypeOf(procedure).toEqualTypeOf<
327
- DecoratedProcedure<
328
- { auth: boolean },
329
- { userId: string },
330
- undefined,
331
- undefined,
332
- void
333
- >
334
- >()
335
-
336
- expect(isProcedure(procedure)).toBe(true)
337
- expect(procedure.zz$p.middlewares).toEqual([mid])
338
- })
339
- })
340
-
341
- it('prefix', () => {
342
- const builder = os
343
- .context<{ auth: boolean }>()
344
- .use((_, __, meta) => {
345
- return meta.next({ context: { userId: '1' } })
346
- })
347
- .prefix('/api')
348
-
349
- expectTypeOf(builder).toEqualTypeOf<
350
- RouterBuilder<{ auth: boolean }, { userId: string }>
351
- >()
352
-
353
- expect(builder).instanceOf(RouterBuilder)
354
- expect(builder.zz$rb.prefix).toEqual('/api')
355
- })
356
-
357
- it('tags', () => {
358
- const builder = os
359
- .context<{ auth: boolean }>()
360
- .use((_, __, meta) => {
361
- return meta.next({ context: { userId: '1' } })
362
- })
363
- .tags('user', 'user2')
364
-
365
- expectTypeOf(builder).toEqualTypeOf<
366
- RouterBuilder<{ auth: boolean }, { userId: string }>
367
- >()
368
-
369
- expect(builder).instanceOf(RouterBuilder)
370
- expect(builder.zz$rb.tags).toEqual(['user', 'user2'])
371
- })