@grest-ts/http 0.0.5

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 (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +613 -0
  3. package/dist/src/client/GGHttpSchema.createClient.d.ts +14 -0
  4. package/dist/src/client/GGHttpSchema.createClient.d.ts.map +1 -0
  5. package/dist/src/client/GGHttpSchema.createClient.js +80 -0
  6. package/dist/src/client/GGHttpSchema.createClient.js.map +1 -0
  7. package/dist/src/index-browser.d.ts +8 -0
  8. package/dist/src/index-browser.d.ts.map +1 -0
  9. package/dist/src/index-browser.js +11 -0
  10. package/dist/src/index-browser.js.map +1 -0
  11. package/dist/src/index-node.d.ts +18 -0
  12. package/dist/src/index-node.d.ts.map +1 -0
  13. package/dist/src/index-node.js +32 -0
  14. package/dist/src/index-node.js.map +1 -0
  15. package/dist/src/rpc/GGHttpRouteRPC.d.ts +19 -0
  16. package/dist/src/rpc/GGHttpRouteRPC.d.ts.map +1 -0
  17. package/dist/src/rpc/GGHttpRouteRPC.js +32 -0
  18. package/dist/src/rpc/GGHttpRouteRPC.js.map +1 -0
  19. package/dist/src/rpc/RpcRequest/GGRpcRequestBuilder.d.ts +18 -0
  20. package/dist/src/rpc/RpcRequest/GGRpcRequestBuilder.d.ts.map +1 -0
  21. package/dist/src/rpc/RpcRequest/GGRpcRequestBuilder.js +80 -0
  22. package/dist/src/rpc/RpcRequest/GGRpcRequestBuilder.js.map +1 -0
  23. package/dist/src/rpc/RpcRequest/GGRpcRequestParser.d.ts +18 -0
  24. package/dist/src/rpc/RpcRequest/GGRpcRequestParser.d.ts.map +1 -0
  25. package/dist/src/rpc/RpcRequest/GGRpcRequestParser.js +90 -0
  26. package/dist/src/rpc/RpcRequest/GGRpcRequestParser.js.map +1 -0
  27. package/dist/src/rpc/RpcResponse/GGRpcResponseBuilder.d.ts +12 -0
  28. package/dist/src/rpc/RpcResponse/GGRpcResponseBuilder.d.ts.map +1 -0
  29. package/dist/src/rpc/RpcResponse/GGRpcResponseBuilder.js +77 -0
  30. package/dist/src/rpc/RpcResponse/GGRpcResponseBuilder.js.map +1 -0
  31. package/dist/src/rpc/RpcResponse/GGRpcResponseParser.d.ts +7 -0
  32. package/dist/src/rpc/RpcResponse/GGRpcResponseParser.d.ts.map +1 -0
  33. package/dist/src/rpc/RpcResponse/GGRpcResponseParser.js +21 -0
  34. package/dist/src/rpc/RpcResponse/GGRpcResponseParser.js.map +1 -0
  35. package/dist/src/schema/GGHttpSchema.d.ts +68 -0
  36. package/dist/src/schema/GGHttpSchema.d.ts.map +1 -0
  37. package/dist/src/schema/GGHttpSchema.js +18 -0
  38. package/dist/src/schema/GGHttpSchema.js.map +1 -0
  39. package/dist/src/schema/httpSchema.d.ts +43 -0
  40. package/dist/src/schema/httpSchema.d.ts.map +1 -0
  41. package/dist/src/schema/httpSchema.js +85 -0
  42. package/dist/src/schema/httpSchema.js.map +1 -0
  43. package/dist/src/server/GGHttp.d.ts +12 -0
  44. package/dist/src/server/GGHttp.d.ts.map +1 -0
  45. package/dist/src/server/GGHttp.js +16 -0
  46. package/dist/src/server/GGHttp.js.map +1 -0
  47. package/dist/src/server/GGHttpMetrics.d.ts +22 -0
  48. package/dist/src/server/GGHttpMetrics.d.ts.map +1 -0
  49. package/dist/src/server/GGHttpMetrics.js +15 -0
  50. package/dist/src/server/GGHttpMetrics.js.map +1 -0
  51. package/dist/src/server/GGHttpSchema.startServer.d.ts +30 -0
  52. package/dist/src/server/GGHttpSchema.startServer.d.ts.map +1 -0
  53. package/dist/src/server/GGHttpSchema.startServer.js +114 -0
  54. package/dist/src/server/GGHttpSchema.startServer.js.map +1 -0
  55. package/dist/src/server/GGHttpServer.d.ts +32 -0
  56. package/dist/src/server/GGHttpServer.d.ts.map +1 -0
  57. package/dist/src/server/GGHttpServer.js +116 -0
  58. package/dist/src/server/GGHttpServer.js.map +1 -0
  59. package/dist/src/server/GG_HTTP_REQUEST.d.ts +16 -0
  60. package/dist/src/server/GG_HTTP_REQUEST.d.ts.map +1 -0
  61. package/dist/src/server/GG_HTTP_REQUEST.js +10 -0
  62. package/dist/src/server/GG_HTTP_REQUEST.js.map +1 -0
  63. package/dist/src/server/GG_HTTP_SERVER.d.ts +4 -0
  64. package/dist/src/server/GG_HTTP_SERVER.d.ts.map +1 -0
  65. package/dist/src/server/GG_HTTP_SERVER.js +3 -0
  66. package/dist/src/server/GG_HTTP_SERVER.js.map +1 -0
  67. package/dist/src/tsconfig.json +17 -0
  68. package/dist/testkit/clientHttp/GGHttpCall.d.ts +35 -0
  69. package/dist/testkit/clientHttp/GGHttpCall.d.ts.map +1 -0
  70. package/dist/testkit/clientHttp/GGHttpCall.js +37 -0
  71. package/dist/testkit/clientHttp/GGHttpCall.js.map +1 -0
  72. package/dist/testkit/clientHttp/GGHttpSchema.callOn.d.ts +37 -0
  73. package/dist/testkit/clientHttp/GGHttpSchema.callOn.d.ts.map +1 -0
  74. package/dist/testkit/clientHttp/GGHttpSchema.callOn.js +29 -0
  75. package/dist/testkit/clientHttp/GGHttpSchema.callOn.js.map +1 -0
  76. package/dist/testkit/index-testkit.d.ts +8 -0
  77. package/dist/testkit/index-testkit.d.ts.map +1 -0
  78. package/dist/testkit/index-testkit.js +8 -0
  79. package/dist/testkit/index-testkit.js.map +1 -0
  80. package/dist/testkit/mock/GGHttpInterceptorsServer.d.ts +13 -0
  81. package/dist/testkit/mock/GGHttpInterceptorsServer.d.ts.map +1 -0
  82. package/dist/testkit/mock/GGHttpInterceptorsServer.js +100 -0
  83. package/dist/testkit/mock/GGHttpInterceptorsServer.js.map +1 -0
  84. package/dist/testkit/mock/GGHttpSchema.mock.d.ts +36 -0
  85. package/dist/testkit/mock/GGHttpSchema.mock.d.ts.map +1 -0
  86. package/dist/testkit/mock/GGHttpSchema.mock.js +78 -0
  87. package/dist/testkit/mock/GGHttpSchema.mock.js.map +1 -0
  88. package/dist/testkit/routing/GGApiRoutingSelector.d.ts +8 -0
  89. package/dist/testkit/routing/GGApiRoutingSelector.d.ts.map +1 -0
  90. package/dist/testkit/routing/GGApiRoutingSelector.js +4 -0
  91. package/dist/testkit/routing/GGApiRoutingSelector.js.map +1 -0
  92. package/dist/testkit/routing/GGHttpSchema.routing.d.ts +14 -0
  93. package/dist/testkit/routing/GGHttpSchema.routing.d.ts.map +1 -0
  94. package/dist/testkit/routing/GGHttpSchema.routing.js +21 -0
  95. package/dist/testkit/routing/GGHttpSchema.routing.js.map +1 -0
  96. package/dist/testkit/utils/validateContractResponse.d.ts +8 -0
  97. package/dist/testkit/utils/validateContractResponse.d.ts.map +1 -0
  98. package/dist/testkit/utils/validateContractResponse.js +68 -0
  99. package/dist/testkit/utils/validateContractResponse.js.map +1 -0
  100. package/dist/tsconfig.publish.tsbuildinfo +1 -0
  101. package/package.json +74 -0
  102. package/src/client/GGHttpSchema.createClient.ts +107 -0
  103. package/src/index-browser.ts +12 -0
  104. package/src/index-node.ts +38 -0
  105. package/src/rpc/GGHttpRouteRPC.ts +42 -0
  106. package/src/rpc/RpcRequest/GGRpcRequestBuilder.ts +91 -0
  107. package/src/rpc/RpcRequest/GGRpcRequestParser.ts +100 -0
  108. package/src/rpc/RpcResponse/GGRpcResponseBuilder.ts +84 -0
  109. package/src/rpc/RpcResponse/GGRpcResponseParser.ts +23 -0
  110. package/src/schema/GGHttpSchema.ts +115 -0
  111. package/src/schema/httpSchema.ts +99 -0
  112. package/src/server/GGHttp.ts +27 -0
  113. package/src/server/GGHttpMetrics.ts +31 -0
  114. package/src/server/GGHttpSchema.startServer.ts +161 -0
  115. package/src/server/GGHttpServer.ts +133 -0
  116. package/src/server/GG_HTTP_REQUEST.ts +12 -0
  117. package/src/server/GG_HTTP_SERVER.ts +4 -0
  118. package/src/tsconfig.json +17 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Grest Games OÜ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,613 @@
1
+ # HTTP Package Usage (@grest-ts/http)
2
+
3
+ How to use the HTTP package for building type-safe HTTP and WebSocket APIs.
4
+
5
+ ## HTTP API Definition
6
+
7
+ ### Basic API Structure
8
+
9
+ ```typescript
10
+ // MyApi.ts
11
+ import { GGRpc, httpApi } from "@grest-ts/http"
12
+ import { IsArray, IsObject, IsString, IsBoolean, IsUint } from "@grest-ts/schema"
13
+ import { defineApi, NOT_FOUND, FORBIDDEN, VALIDATION_ERROR, SERVER_ERROR } from "@grest-ts/contract"
14
+
15
+ // ---------------------------------------------------------
16
+ // Type Schemas
17
+ // ---------------------------------------------------------
18
+
19
+ export const IsItemId = IsString.brand("ItemId")
20
+ export type tItemId = typeof IsItemId.infer
21
+
22
+ export const IsItem = IsObject({
23
+ id: IsItemId,
24
+ title: IsString,
25
+ description: IsString.orUndefined,
26
+ done: IsBoolean,
27
+ createdAt: IsUint,
28
+ updatedAt: IsUint
29
+ })
30
+ export type Item = typeof IsItem.infer
31
+
32
+ export const IsCreateItemRequest = IsObject({
33
+ title: IsString.nonEmpty,
34
+ description: IsString.orUndefined
35
+ })
36
+ export type CreateItemRequest = typeof IsCreateItemRequest.infer
37
+
38
+ export const IsUpdateItemRequest = IsObject({
39
+ id: IsItemId,
40
+ title: IsString.orUndefined,
41
+ description: IsString.orUndefined
42
+ })
43
+ export type UpdateItemRequest = typeof IsUpdateItemRequest.infer
44
+
45
+ export const IsItemIdParam = IsObject({
46
+ id: IsItemId
47
+ })
48
+
49
+ // ---------------------------------------------------------
50
+ // Contract & API
51
+ // ---------------------------------------------------------
52
+
53
+ export const MyApiContract = defineApi("MyApi", () => ({
54
+ list: {
55
+ success: IsArray(IsItem),
56
+ errors: [SERVER_ERROR]
57
+ },
58
+ get: {
59
+ input: IsItemIdParam,
60
+ success: IsItem,
61
+ errors: [NOT_FOUND, SERVER_ERROR]
62
+ },
63
+ create: {
64
+ input: IsCreateItemRequest,
65
+ success: IsItem,
66
+ errors: [VALIDATION_ERROR, SERVER_ERROR]
67
+ },
68
+ update: {
69
+ input: IsUpdateItemRequest,
70
+ success: IsItem,
71
+ errors: [NOT_FOUND, FORBIDDEN, VALIDATION_ERROR, SERVER_ERROR]
72
+ },
73
+ delete: {
74
+ input: IsItemIdParam,
75
+ success: undefined as undefined,
76
+ errors: [NOT_FOUND, SERVER_ERROR]
77
+ }
78
+ }))
79
+
80
+ export const MyApi = httpApi(MyApiContract)
81
+ .pathPrefix("api/items")
82
+ .routes({
83
+ list: GGRpc.GET("list"),
84
+ get: GGRpc.GET("get/:id"),
85
+ create: GGRpc.POST("create"),
86
+ update: GGRpc.PUT("update"),
87
+ delete: GGRpc.DELETE("delete/:id")
88
+ })
89
+ ```
90
+
91
+ ### HTTP Methods
92
+
93
+ ```typescript
94
+ GGRpc.GET("path") // GET request
95
+ GGRpc.POST("path") // POST request
96
+ GGRpc.PUT("path") // PUT request
97
+ GGRpc.PATCH("path") // PATCH request
98
+ GGRpc.DELETE("path") // DELETE request
99
+ ```
100
+
101
+ ### Path Parameters
102
+
103
+ Use `:paramName` in paths - parameters are matched by position:
104
+
105
+ ```typescript
106
+ export const MyApiContract = defineApi("MyApi", () => ({
107
+ getUser: {
108
+ input: IsObject({ userId: IsUserId }),
109
+ success: IsUser,
110
+ errors: [NOT_FOUND, SERVER_ERROR]
111
+ },
112
+ getUserPost: {
113
+ input: IsObject({ userId: IsUserId, postId: IsPostId }),
114
+ success: IsPost,
115
+ errors: [NOT_FOUND, SERVER_ERROR]
116
+ }
117
+ }))
118
+
119
+ export const MyApi = httpApi(MyApiContract)
120
+ .pathPrefix("api")
121
+ .routes({
122
+ getUser: GGRpc.GET("users/:userId"),
123
+ getUserPost: GGRpc.GET("users/:userId/posts/:postId")
124
+ })
125
+ ```
126
+
127
+ ### Query Parameters
128
+
129
+ For GET/DELETE, object parameters become query strings:
130
+
131
+ ```typescript
132
+ export const MyApiContract = defineApi("MyApi", () => ({
133
+ search: {
134
+ input: IsObject({
135
+ term: IsString,
136
+ page: IsUint.orUndefined,
137
+ limit: IsUint.orUndefined
138
+ }),
139
+ success: IsSearchResults,
140
+ errors: [SERVER_ERROR]
141
+ }
142
+ }))
143
+
144
+ // Client usage: client.search({ term: "foo", page: 1 })
145
+ // Results in: GET /api/search?term=foo&page=1
146
+ ```
147
+
148
+ ### Request Body
149
+
150
+ For POST/PUT/PATCH, the input becomes the JSON body:
151
+
152
+ ```typescript
153
+ export const MyApiContract = defineApi("MyApi", () => ({
154
+ create: {
155
+ input: IsCreateRequest,
156
+ success: IsItem,
157
+ errors: [VALIDATION_ERROR, SERVER_ERROR]
158
+ },
159
+ update: {
160
+ input: IsUpdateRequest,
161
+ success: IsItem,
162
+ errors: [VALIDATION_ERROR, SERVER_ERROR]
163
+ }
164
+ }))
165
+ ```
166
+
167
+ ## Authentication & Context
168
+
169
+ ### Using Codec (Recommended for Header-Based Auth)
170
+
171
+ ```typescript
172
+ // auth/UserAuth.ts
173
+ import { GGContextKey } from "@grest-ts/context"
174
+ import { IsObject, IsString } from "@grest-ts/schema"
175
+
176
+ export const IsUserAuthToken = IsString.brand("UserAuthToken")
177
+ export type tUserAuthToken = typeof IsUserAuthToken.infer
178
+
179
+ export const IsUserId = IsString.brand("UserId")
180
+ export type tUserId = typeof IsUserId.infer
181
+
182
+ export const IsUser = IsObject({
183
+ id: IsUserId,
184
+ username: IsString,
185
+ email: IsString
186
+ })
187
+ export type User = typeof IsUser.infer
188
+
189
+ // Define the context value schema
190
+ const IsUserAuthContext = IsObject({
191
+ token: IsUserAuthToken
192
+ })
193
+ export type UserAuthContext = typeof IsUserAuthContext.infer
194
+
195
+ // Define the header schema
196
+ const HEADER_AUTHORIZATION = "authorization"
197
+ const HeaderType = IsObject({
198
+ [HEADER_AUTHORIZATION]: IsString.orUndefined
199
+ })
200
+
201
+ // Create context key with codec
202
+ export const GG_USER_AUTH = new GGContextKey<UserAuthContext>("user_auth", IsUserAuthContext)
203
+ GG_USER_AUTH.addCodec("http", HeaderType.codecTo(IsUserAuthContext, {
204
+ encode: (headers) => {
205
+ const authHeader = headers[HEADER_AUTHORIZATION]
206
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
207
+ return { token: undefined as any } // Will fail validation if required
208
+ }
209
+ return { token: authHeader.substring(7) as tUserAuthToken }
210
+ },
211
+ decode: (value) => {
212
+ return { [HEADER_AUTHORIZATION]: value.token ? `Bearer ${value.token}` : undefined }
213
+ }
214
+ }))
215
+ ```
216
+
217
+ ### Using Middleware (For Complex Logic)
218
+
219
+ ```typescript
220
+ // middleware/ClientInfoMiddleware.ts
221
+ import { GGHttpRequest, GGHttpTransportMiddleware } from "@grest-ts/http"
222
+ import { GGContextKey } from "@grest-ts/context"
223
+ import { IsObject, IsString, IsLiteral } from "@grest-ts/schema"
224
+
225
+ export interface ClientInfo {
226
+ version: string
227
+ platform: 'web' | 'ios' | 'android'
228
+ }
229
+
230
+ export const GG_CLIENT_INFO = new GGContextKey<ClientInfo>('clientInfo', IsObject({
231
+ version: IsString,
232
+ platform: IsLiteral("web", "ios", "android")
233
+ }))
234
+
235
+ export const ClientInfoMiddleware: GGHttpTransportMiddleware = {
236
+ updateRequest(req: GGHttpRequest): void {
237
+ const info = GG_CLIENT_INFO.get()
238
+ if (info) {
239
+ req.headers['x-client-version'] = info.version
240
+ req.headers['x-client-platform'] = info.platform
241
+ }
242
+ },
243
+ parseRequest(req: GGHttpRequest): void {
244
+ GG_CLIENT_INFO.set({
245
+ version: req.headers['x-client-version'] ?? 'unknown',
246
+ platform: (req.headers['x-client-platform'] ?? 'web') as ClientInfo['platform']
247
+ })
248
+ }
249
+ }
250
+ ```
251
+
252
+ ### Adding Auth/Context to API
253
+
254
+ ```typescript
255
+ import { GG_USER_AUTH } from "./auth/UserAuth"
256
+ import { ClientInfoMiddleware } from "./middleware/ClientInfoMiddleware"
257
+ import { GG_INTL_LOCALE } from "@grest-ts/intl"
258
+
259
+ export const MyApiContract = defineApi("MyApi", () => ({
260
+ list: {
261
+ success: IsArray(IsItem),
262
+ errors: [NOT_AUTHORIZED, SERVER_ERROR]
263
+ },
264
+ create: {
265
+ input: IsCreateRequest,
266
+ success: IsItem,
267
+ errors: [NOT_AUTHORIZED, VALIDATION_ERROR, SERVER_ERROR]
268
+ }
269
+ }))
270
+
271
+ // Chain multiple context providers
272
+ export const MyApi = httpApi(MyApiContract)
273
+ .pathPrefix("api/items")
274
+ .useHeader(GG_INTL_LOCALE) // Use codec from context key
275
+ .useHeader(GG_USER_AUTH) // Use codec from context key
276
+ .use(ClientInfoMiddleware) // Use middleware object
277
+ .routes({
278
+ list: GGRpc.GET("list"),
279
+ create: GGRpc.POST("create")
280
+ })
281
+ ```
282
+
283
+ ### Public API (No Auth)
284
+
285
+ ```typescript
286
+ export const PublicApiContract = defineApi("PublicApi", () => ({
287
+ status: {
288
+ success: IsStatusResponse,
289
+ errors: [SERVER_ERROR]
290
+ },
291
+ login: {
292
+ input: IsLoginRequest,
293
+ success: IsLoginResponse,
294
+ errors: [VALIDATION_ERROR, SERVER_ERROR]
295
+ }
296
+ }))
297
+
298
+ export const PublicApi = httpApi(PublicApiContract)
299
+ .pathPrefix("pub")
300
+ .routes({
301
+ status: GGRpc.GET("status"),
302
+ login: GGRpc.POST("login")
303
+ })
304
+ ```
305
+
306
+ ## Error Types
307
+
308
+ ### Declaring Errors in Contract
309
+
310
+ ```typescript
311
+ import { defineApi, NOT_FOUND, FORBIDDEN, VALIDATION_ERROR, SERVER_ERROR, BAD_REQUEST } from "@grest-ts/contract"
312
+
313
+ // Custom error type
314
+ export class InvalidCredentialsError extends BAD_REQUEST<"INVALID_CREDENTIALS", undefined> {
315
+ public static TYPE = "INVALID_CREDENTIALS"
316
+
317
+ constructor() {
318
+ super("INVALID_CREDENTIALS", undefined)
319
+ }
320
+ }
321
+
322
+ export const MyApiContract = defineApi("MyApi", () => ({
323
+ get: {
324
+ input: IsItemIdParam,
325
+ success: IsItem,
326
+ errors: [NOT_FOUND, SERVER_ERROR]
327
+ },
328
+ update: {
329
+ input: IsUpdateRequest,
330
+ success: IsItem,
331
+ errors: [NOT_FOUND, FORBIDDEN, VALIDATION_ERROR, SERVER_ERROR]
332
+ },
333
+ login: {
334
+ input: IsLoginRequest,
335
+ success: IsLoginResponse,
336
+ errors: [InvalidCredentialsError, VALIDATION_ERROR, SERVER_ERROR]
337
+ }
338
+ }))
339
+ ```
340
+
341
+ ### Throwing Errors in Service
342
+
343
+ ```typescript
344
+ import { GGServerApi, NOT_FOUND, FORBIDDEN } from "@grest-ts/node"
345
+
346
+ export class MyService implements GGServerApi<typeof MyApiContract["methods"]> {
347
+ async get({ id }: { id: tItemId }): Promise<Item> {
348
+ const item = await this.findItem(id)
349
+ if (!item) throw new NOT_FOUND()
350
+ return item
351
+ }
352
+
353
+ async update(request: UpdateRequest): Promise<Item> {
354
+ const item = await this.findItem(request.id)
355
+ if (!item) throw new NOT_FOUND()
356
+
357
+ const user = UserContext.get()
358
+ if (item.ownerId !== user.id) throw new FORBIDDEN()
359
+
360
+ return this.updateItem(item, request)
361
+ }
362
+ }
363
+ ```
364
+
365
+ ## HTTP Server Setup
366
+
367
+ ### Using GGHttp (Fluent API)
368
+
369
+ ```typescript
370
+ import { GGHttp } from "@grest-ts/http"
371
+
372
+ protected compose(): void {
373
+ new GGHttp()
374
+ .http(PublicApi, publicService)
375
+ .http(StatusApi, {
376
+ status: async () => ({ status: true })
377
+ })
378
+
379
+ new GGHttp("authenticated")
380
+ .use(new UserContextMiddleware(userService))
381
+ .http(MyApi, myService)
382
+ .http(UserAuthApi, userService)
383
+ .websocket(NotificationApi, notificationService.handleConnection)
384
+ }
385
+ ```
386
+
387
+ ### Using GGHttpServer (Direct)
388
+
389
+ ```typescript
390
+ import { GGHttpServer } from "@grest-ts/http"
391
+
392
+ protected compose(): void {
393
+ const httpServer = new GGHttpServer()
394
+ MyApi.startServer(httpServer, myService)
395
+ OtherApi.startServer(httpServer, otherService)
396
+ }
397
+ ```
398
+
399
+ ### Multiple HTTP Servers
400
+
401
+ ```typescript
402
+ protected compose(): void {
403
+ // Main public server
404
+ new GGHttp()
405
+ .http(PublicApi, publicService)
406
+
407
+ // Internal server on different port
408
+ new GGHttp("internal")
409
+ .http(InternalApi, internalService)
410
+ }
411
+ ```
412
+
413
+ ## HTTP Client
414
+
415
+ ### Creating Clients
416
+
417
+ ```typescript
418
+ // Unauthenticated client
419
+ const client = MyApi.createClient({ url: "http://localhost:3000" })
420
+
421
+ // Authenticated client
422
+ const authClient = MyApi.createClient(authState, { url: "http://localhost:3000" })
423
+
424
+ // Test client
425
+ const testClient = MyApi.createTestClient()
426
+ ```
427
+
428
+ ### Making Requests
429
+
430
+ ```typescript
431
+ // Simple request
432
+ const items = await client.list()
433
+
434
+ // With path parameter
435
+ const item = await client.get({ id: "item-123" })
436
+
437
+ // With query parameters
438
+ const results = await client.search({ term: "foo", page: 1 })
439
+
440
+ // With body
441
+ const newItem = await client.create({ title: "New Item" })
442
+ ```
443
+
444
+ ### Handling Results
445
+
446
+ ```typescript
447
+ // Direct (throws on error)
448
+ const item = await client.get({ id: "item-123" })
449
+
450
+ // Using .asResult() for safe error handling
451
+ const result = await client.get({ id: "item-123" }).asResult()
452
+ if (result.success) {
453
+ console.log("Item:", result.data)
454
+ } else {
455
+ console.log("Error:", result.type) // "NOT_FOUND", etc.
456
+ }
457
+ ```
458
+
459
+ ## WebSocket APIs
460
+
461
+ ### Defining WebSocket API
462
+
463
+ ```typescript
464
+ import { webSocketApi } from "@grest-ts/http"
465
+ import { defineTwoWayApi, SERVER_ERROR, VALIDATION_ERROR } from "@grest-ts/contract"
466
+ import { IsObject, IsString, IsBoolean } from "@grest-ts/schema"
467
+ import { GG_USER_AUTH_TOKEN } from "./auth/UserAuth"
468
+
469
+ // Message schemas
470
+ export const IsItemMarkedEvent = IsObject({
471
+ item: IsItem,
472
+ markedBy: IsString
473
+ })
474
+ export type ItemMarkedEvent = typeof IsItemMarkedEvent.infer
475
+
476
+ export const IsUpdateItemRequest = IsObject({
477
+ item: IsItem,
478
+ reason: IsString.orUndefined
479
+ })
480
+
481
+ export const IsUpdateItemResponse = IsObject({
482
+ success: IsBoolean,
483
+ message: IsString
484
+ })
485
+
486
+ // Contract definition
487
+ export const NotificationApiContract = defineTwoWayApi("NotificationApi", () => ({
488
+ clientToServer: {
489
+ updateItem: {
490
+ input: IsUpdateItemRequest,
491
+ success: IsUpdateItemResponse,
492
+ errors: [VALIDATION_ERROR, SERVER_ERROR]
493
+ },
494
+ ping: {}
495
+ },
496
+ serverToClient: {
497
+ itemMarked: {
498
+ input: IsItemMarkedEvent
499
+ },
500
+ areYouThere: {
501
+ success: IsBoolean,
502
+ errors: [SERVER_ERROR]
503
+ }
504
+ }
505
+ }))
506
+
507
+ export const NotificationApi = webSocketApi(NotificationApiContract)
508
+ .path("ws/notifications")
509
+ .use(GG_USER_AUTH_TOKEN)
510
+ .done()
511
+ ```
512
+
513
+ ### WebSocket Server Handler
514
+
515
+ ```typescript
516
+ import { GGSocketApi, WebSocketIncoming, WebSocketOutgoing } from "@grest-ts/http"
517
+
518
+ type IncomingHandler = WebSocketIncoming<GGSocketApi<typeof NotificationApiContract.methods["clientToServer"]>>
519
+ type OutgoingConnection = WebSocketOutgoing<GGSocketApi<typeof NotificationApiContract.methods["serverToClient"]>>
520
+
521
+ export class NotificationService {
522
+ private connections = new Map<string, Set<OutgoingConnection>>()
523
+
524
+ handleConnection = (incoming: IncomingHandler, outgoing: OutgoingConnection): void => {
525
+ const user = UserContext.get()
526
+
527
+ // Track connection
528
+ if (!this.connections.has(user.id)) {
529
+ this.connections.set(user.id, new Set())
530
+ }
531
+ this.connections.get(user.id)!.add(outgoing)
532
+
533
+ // Handle incoming messages
534
+ incoming.on({
535
+ updateItem: async (request) => {
536
+ // Process update
537
+ return { success: true, message: "Updated" }
538
+ },
539
+ ping: async () => {
540
+ // Handle ping
541
+ }
542
+ })
543
+
544
+ // Handle disconnect
545
+ outgoing.onClose(() => {
546
+ this.connections.get(user.id)?.delete(outgoing)
547
+ })
548
+ }
549
+
550
+ // Broadcast to user
551
+ notifyUser(userId: string, event: ItemMarkedEvent): void {
552
+ const userConnections = this.connections.get(userId)
553
+ userConnections?.forEach(conn => conn.itemMarked(event))
554
+ }
555
+ }
556
+ ```
557
+
558
+ ### WebSocket in Runtime
559
+
560
+ ```typescript
561
+ protected compose(): void {
562
+ new GGHttp()
563
+ .use(new UserContextMiddleware(userService))
564
+ .websocket(NotificationApi, notificationService.handleConnection)
565
+ }
566
+ ```
567
+
568
+ ### WebSocket Client (Browser)
569
+
570
+ ```typescript
571
+ // Connect with message handlers
572
+ const socket = await authenticatedSDK.connectNotification({
573
+ itemMarked: (event) => {
574
+ console.log("Item marked:", event.item.title)
575
+ },
576
+ areYouThere: async () => {
577
+ return true
578
+ }
579
+ })
580
+
581
+ // Send messages
582
+ const response = await socket.updateItem({ item, reason: "Updated via UI" })
583
+
584
+ // Close connection
585
+ socket.close()
586
+ ```
587
+
588
+ ## SDK (Auto-Generated)
589
+
590
+ The SDK is auto-generated from your API definitions. The generated SDK provides:
591
+
592
+ - Type-safe client methods for all endpoints
593
+ - Automatic auth token handling
594
+ - WebSocket connection management
595
+ - Error type inference
596
+
597
+ ### Using Generated SDK
598
+
599
+ ```typescript
600
+ import { UserAppSDK } from "./UserAppSDK.gen"
601
+
602
+ const sdk = new UserAppSDK({ url: "http://localhost:3000" })
603
+
604
+ // Public endpoints
605
+ const loginResult = await sdk.login({ username, password })
606
+
607
+ // Authenticated endpoints (returned from login)
608
+ const authSDK = loginResult.data.sdk
609
+ const items = await authSDK.checklist.list()
610
+ const socket = await authSDK.connectNotification({
611
+ itemMarked: (event) => console.log(event)
612
+ })
613
+ ```
@@ -0,0 +1,14 @@
1
+ import { GGContractApiDefinition, GGContractClient } from "@grest-ts/schema";
2
+ import { GGHttpSchema } from "../schema/GGHttpSchema";
3
+ declare module "../schema/GGHttpSchema" {
4
+ interface GGHttpSchema<TContract extends GGContractApiDefinition, TContext = {}> {
5
+ createClient(config?: GGHttpClientConfig): GGContractClient<TContract>;
6
+ }
7
+ }
8
+ export interface GGHttpClientConfig {
9
+ url?: string;
10
+ timeout?: number;
11
+ noValidation?: boolean;
12
+ }
13
+ export declare function createClient<TContract extends GGContractApiDefinition, TContext>(httpSchema: GGHttpSchema<TContract, TContext>, config?: GGHttpClientConfig): GGContractClient<TContract>;
14
+ //# sourceMappingURL=GGHttpSchema.createClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GGHttpSchema.createClient.d.ts","sourceRoot":"","sources":["../../../src/client/GGHttpSchema.createClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,uBAAuB,EAAE,gBAAgB,EAA4E,MAAM,kBAAkB,CAAA;AACvK,OAAO,EAAwD,YAAY,EAAC,MAAM,wBAAwB,CAAC;AAI3G,OAAO,QAAQ,wBAAwB,CAAC;IACpC,UAAU,YAAY,CAAC,SAAS,SAAS,uBAAuB,EAAE,QAAQ,GAAG,EAAE;QAC3E,YAAY,CAAC,MAAM,CAAC,EAAE,kBAAkB,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAA;KACzE;CACJ;AAED,MAAM,WAAW,kBAAkB;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAA;CACzB;AASD,wBAAgB,YAAY,CAAC,SAAS,SAAS,uBAAuB,EAAE,QAAQ,EAC5E,UAAU,EAAE,YAAY,CAAC,SAAS,EAAE,QAAQ,CAAC,EAC7C,MAAM,CAAC,EAAE,kBAAkB,GAC5B,gBAAgB,CAAC,SAAS,CAAC,CA+E7B"}