spiceflow 1.0.2 → 1.0.3

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.
@@ -32,7 +32,7 @@ type And<A extends boolean, B extends boolean> = A extends true
32
32
  : false
33
33
 
34
34
  type ReplaceGeneratorWithAsyncGenerator<
35
- in out RecordType extends Record<string, unknown>
35
+ in out RecordType extends Record<string, unknown>,
36
36
  > = {
37
37
  [K in keyof RecordType]: RecordType[K] extends Generator<
38
38
  infer A,
@@ -62,7 +62,7 @@ export namespace SpiceflowClient {
62
62
  }
63
63
 
64
64
  export type Create<
65
- App extends Spiceflow<any, any, any, any, any, any, any, any>
65
+ App extends Spiceflow<any, any, any, any, any, any, any, any>,
66
66
  > = App extends {
67
67
  _routes: infer Schema extends Record<string, any>
68
68
  }
@@ -89,24 +89,19 @@ export namespace SpiceflowClient {
89
89
  : never
90
90
  : Route[K] extends {
91
91
  body: infer Body
92
- headers: infer Headers
92
+ // headers: infer Headers
93
93
  params: any
94
94
  query: infer Query
95
95
  response: infer Response extends Record<number, unknown>
96
96
  }
97
- ? (undefined extends Headers
98
- ? { headers?: Record<string, unknown> }
99
- : {
100
- headers: Headers
101
- }) &
102
- (undefined extends Query
103
- ? { query?: Record<string, unknown> }
104
- : { query: Query }) extends infer Param
97
+ ? { headers?: Record<string, unknown> } & (undefined extends Query
98
+ ? { query?: Record<string, unknown> }
99
+ : { query: Query }) extends infer Param
105
100
  ? {} extends Param
106
101
  ? undefined extends Body
107
102
  ? K extends 'get' | 'head'
108
103
  ? (
109
- options?: Prettify<Param & TreatyParam>
104
+ options?: Prettify<Param & TreatyParam>,
110
105
  ) => Promise<
111
106
  TreatyResponse<
112
107
  ReplaceGeneratorWithAsyncGenerator<Response>
@@ -114,7 +109,7 @@ export namespace SpiceflowClient {
114
109
  >
115
110
  : (
116
111
  body?: Body,
117
- options?: Prettify<Param & TreatyParam>
112
+ options?: Prettify<Param & TreatyParam>,
118
113
  ) => Promise<
119
114
  TreatyResponse<
120
115
  ReplaceGeneratorWithAsyncGenerator<Response>
@@ -124,7 +119,7 @@ export namespace SpiceflowClient {
124
119
  body: Body extends Record<string, unknown>
125
120
  ? ReplaceBlobWithFiles<Body>
126
121
  : Body,
127
- options?: Prettify<Param & TreatyParam>
122
+ options?: Prettify<Param & TreatyParam>,
128
123
  ) => Promise<
129
124
  TreatyResponse<
130
125
  ReplaceGeneratorWithAsyncGenerator<Response>
@@ -132,7 +127,7 @@ export namespace SpiceflowClient {
132
127
  >
133
128
  : K extends 'get' | 'head'
134
129
  ? (
135
- options: Prettify<Param & TreatyParam>
130
+ options: Prettify<Param & TreatyParam>,
136
131
  ) => Promise<
137
132
  TreatyResponse<
138
133
  ReplaceGeneratorWithAsyncGenerator<Response>
@@ -142,7 +137,7 @@ export namespace SpiceflowClient {
142
137
  body: Body extends Record<string, unknown>
143
138
  ? ReplaceBlobWithFiles<Body>
144
139
  : Body,
145
- options: Prettify<Param & TreatyParam>
140
+ options: Prettify<Param & TreatyParam>,
146
141
  ) => Promise<
147
142
  TreatyResponse<
148
143
  ReplaceGeneratorWithAsyncGenerator<Response>
@@ -179,13 +174,13 @@ export namespace SpiceflowClient {
179
174
  | RequestInit['headers']
180
175
  | ((
181
176
  path: string,
182
- options: RequestInit
177
+ options: RequestInit,
183
178
  ) => RequestInit['headers'] | void)
184
179
  >
185
180
  onRequest?: MaybeArray<
186
181
  (
187
182
  path: string,
188
- options: RequestInit
183
+ options: RequestInit,
189
184
  ) => MaybePromise<RequestInit | void>
190
185
  >
191
186
  onResponse?: MaybeArray<(response: Response) => MaybePromise<unknown>>
@@ -226,7 +221,7 @@ export namespace SpiceflowClient {
226
221
 
227
222
  export type WSEvent<
228
223
  K extends keyof WebSocketEventMap,
229
- Data = unknown
224
+ Data = unknown,
230
225
  > = K extends 'message' ? OnMessage<Data> : WebSocketEventMap[K]
231
226
  }
232
227
 
@@ -9,7 +9,7 @@ const randomObject = {
9
9
  c: true,
10
10
  d: false,
11
11
  e: null,
12
- f: new Date(0)
12
+ f: new Date(0),
13
13
  }
14
14
  const randomArray = [
15
15
  'a',
@@ -18,7 +18,7 @@ const randomArray = [
18
18
  false,
19
19
  null,
20
20
  new Date(0),
21
- { a: 'a', b: 2, c: true, d: false, e: null, f: new Date(0) }
21
+ { a: 'a', b: 2, c: true, d: false, e: null, f: new Date(0) },
22
22
  ]
23
23
 
24
24
  const app = new Spiceflow()
@@ -27,22 +27,30 @@ const app = new Spiceflow()
27
27
  .get('/number', () => 1)
28
28
  .get('/true', () => true)
29
29
  .get('/false', () => false)
30
- .post('/array', ({ body }) => body, {
31
- body: t.Array(t.String())
30
+ .post('/array', async ({ request }) => await request.json(), {
31
+ body: t.Array(t.String()),
32
32
  })
33
- .post('/mirror', ({ body }) => body)
34
- .post('/body', ({ body }) => body, {
35
- body: t.String()
33
+ .post('/mirror', async ({ request }) => await request.json())
34
+ .post('/body', async ({ request }) => await request.text(), {
35
+ body: t.String(),
36
36
  })
37
- .delete('/empty', ({ body }) => ({ body: body ?? null }))
38
- .post('/deep/nested/mirror', ({ body }) => body, {
37
+ .delete('/empty', async ({ request }) => {
38
+ const body = await request.text()
39
+ return { body: body || null }
40
+ })
41
+ .post('/deep/nested/mirror', async ({ request }) => await request.json(), {
39
42
  body: t.Object({
40
43
  username: t.String(),
41
- password: t.String()
42
- })
44
+ password: t.String(),
45
+ }),
43
46
  })
44
47
 
45
- .use(new Spiceflow({ basePath: '/nested' }).get('/data', ({ params }) => 'hi'))
48
+ .use(
49
+ new Spiceflow({ basePath: '/nested' }).get(
50
+ '/data',
51
+ ({ params }) => 'hi',
52
+ ),
53
+ )
46
54
  // .get('/error', ({ error }) => error("I'm a teapot", 'Kirifuji Nagisa'), {
47
55
  // response: {
48
56
  // 200: t.Void(),
@@ -59,10 +67,10 @@ const app = new Spiceflow()
59
67
  {
60
68
  response: {
61
69
  200: t.Object({
62
- x: t.String()
63
- })
64
- }
65
- }
70
+ x: t.String(),
71
+ }),
72
+ },
73
+ },
66
74
  )
67
75
 
68
76
  // TODO ajv does not accept dates for some reason
@@ -78,9 +86,9 @@ const app = new Spiceflow()
78
86
  ({ redirect }) => redirect('http://localhost:8083/true'),
79
87
  {
80
88
  body: t.Object({
81
- username: t.String()
82
- })
83
- }
89
+ username: t.String(),
90
+ }),
91
+ },
84
92
  )
85
93
  // .get('/formdata', () => ({
86
94
  // image: Bun.file('./test/kyuukurarin.mp4')
@@ -108,7 +116,7 @@ const client = createSpiceflowClient(app)
108
116
 
109
117
  describe('client', () => {
110
118
  it('get index', async () => {
111
- const { data, error } = await client.index.get()
119
+ const { data, error } = await client.index.get({})
112
120
 
113
121
  expect(data).toBe('a')
114
122
  expect(error).toBeNull()
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  StatusMap,
3
3
  InvertedStatusMap,
4
- redirect as Redirect
4
+ redirect as Redirect,
5
5
  } from './utils.js'
6
6
 
7
7
  import type {
@@ -9,9 +9,10 @@ import type {
9
9
  Prettify,
10
10
  ResolvePath,
11
11
  SingletonBase,
12
- HTTPHeaders
12
+ HTTPHeaders,
13
13
  } from './types.js'
14
- import { error } from './error.js'
14
+
15
+ import { TypedRequest } from '../spiceflow.js'
15
16
 
16
17
  type InvertedStatusMapKey = keyof InvertedStatusMap
17
18
 
@@ -27,10 +28,10 @@ export type ErrorContext<
27
28
  derive: {}
28
29
  resolve: {}
29
30
  },
30
- Path extends string = ''
31
+ Path extends string = '',
31
32
  > = Prettify<
32
33
  {
33
- body: Route['body']
34
+ // body: Route['body']
34
35
  query: undefined extends Route['query']
35
36
  ? Record<string, string | undefined>
36
37
  : Route['query']
@@ -58,10 +59,10 @@ export type ErrorContext<
58
59
  *
59
60
  * @example '/id/:id'
60
61
  */
61
- route: string
62
- request: Request
62
+ // route: string
63
+ request: TypedRequest<Route['body']>
63
64
  store: Singleton['store']
64
- response: Route['response']
65
+ // response: Route['response']
65
66
  } & Singleton['decorator'] &
66
67
  Singleton['derive'] &
67
68
  Singleton['resolve']
@@ -75,11 +76,13 @@ export type Context<
75
76
  derive: {}
76
77
  resolve: {}
77
78
  },
78
- Path extends string = ''
79
+ Path extends string = '',
79
80
  > = Prettify<
80
81
  {
81
- body: Route['body']
82
-
82
+ // body: Route['body']
83
+ query: undefined extends Route['query']
84
+ ? Record<string, string | undefined>
85
+ : Route['query']
83
86
  params: undefined extends Route['params']
84
87
  ? Path extends `${string}/${':' | '*'}${string}`
85
88
  ? ResolvePath<Path>
@@ -127,44 +130,11 @@ export type Context<
127
130
  *
128
131
  * @example '/id/:id'
129
132
  */
130
- route: string
131
- request: Request
133
+ // route: string
134
+ request: TypedRequest<Route['body']>
132
135
  store: Singleton['store']
133
136
  response?: Route['response']
134
- } & ({} extends Route['response']
135
- ? {
136
- error: typeof error
137
- }
138
- : {
139
- error: <
140
- const Code extends
141
- | keyof Route['response']
142
- | InvertedStatusMap[Extract<
143
- InvertedStatusMapKey,
144
- keyof Route['response']
145
- >],
146
- const T extends Code extends keyof Route['response']
147
- ? Route['response'][Code]
148
- : Code extends keyof StatusMap
149
- ? // @ts-ignore StatusMap[Code] always valid because Code generic check
150
- Route['response'][StatusMap[Code]]
151
- : never
152
- >(
153
- code: Code,
154
- response: T
155
- ) => {
156
- // [ELYSIA_RESPONSE]: Code extends keyof StatusMap
157
- // ? StatusMap[Code]
158
- // : Code
159
- response: T
160
- _type: {
161
- [ERROR_CODE in Code extends keyof StatusMap
162
- ? StatusMap[Code]
163
- : Code]: T
164
- }
165
- }
166
- }) &
167
- Singleton['decorator'] &
137
+ } & Singleton['decorator'] &
168
138
  Singleton['derive'] &
169
139
  Singleton['resolve']
170
140
  >
@@ -176,7 +146,7 @@ export type PreContext<
176
146
  store: {}
177
147
  derive: {}
178
148
  resolve: {}
179
- }
149
+ },
180
150
  > = Prettify<
181
151
  {
182
152
  store: Singleton['store']
@@ -191,6 +161,6 @@ export type PreContext<
191
161
  // redirect?: string
192
162
  // }
193
163
 
194
- error: typeof error
164
+ // error: typeof error
195
165
  } & Singleton['decorator']
196
166
  >
@@ -1,8 +1,4 @@
1
- import type { TSchema } from '@sinclair/typebox'
2
- import { Value } from '@sinclair/typebox/value'
3
- import type { TypeCheck, ValueError } from '@sinclair/typebox/compiler'
4
1
 
5
- import { StatusMap, InvertedStatusMap } from './utils'
6
2
 
7
3
  // ? Cloudflare worker support
8
4
  const env =
@@ -22,272 +18,23 @@ export type ELYSIA_RESPONSE = typeof ELYSIA_RESPONSE
22
18
 
23
19
  export const isProduction = (env?.NODE_ENV ?? env?.ENV) === 'production'
24
20
 
25
- export type SpiceflowErrors =
26
- | InternalServerError
27
- | NotFoundError
28
- | ParseError
29
- | ValidationError
30
- | InvalidCookieSignature
31
-
32
- export const error = <
33
- const Code extends number | keyof StatusMap,
34
- const T = Code extends keyof InvertedStatusMap
35
- ? InvertedStatusMap[Code]
36
- : Code,
37
- const Status extends Code extends keyof StatusMap
38
- ? StatusMap[Code]
39
- : Code = Code extends keyof StatusMap ? StatusMap[Code] : Code
40
- >(
41
- code: Code,
42
- response?: T
43
- ): {
44
- [ELYSIA_RESPONSE]: Status
45
- response: T
46
- _type: {
47
- [ERROR_CODE in Status]: T
48
- }
49
- error: Error
50
- } => {
51
- const res =
52
- response ??
53
- (code in InvertedStatusMap
54
- ? // @ts-expect-error Always correct
55
- InvertedStatusMap[code]
56
- : code)
57
-
58
- return {
59
- // @ts-expect-error trust me bro
60
- [ELYSIA_RESPONSE]: StatusMap[code] ?? code,
61
- response: res,
62
- _type: undefined as any,
63
- error: new Error(res)
64
- } as const
65
- }
66
-
67
- export class InternalServerError extends Error {
68
- code = 'INTERNAL_SERVER_ERROR'
69
- status = 500
70
-
71
- constructor(message?: string) {
72
- super(message ?? 'INTERNAL_SERVER_ERROR')
73
- }
21
+ export class ValidationError extends Error {
22
+ code = 'VALIDATION'
23
+ status = 422
74
24
  }
75
25
 
76
26
  export class NotFoundError extends Error {
77
27
  code = 'NOT_FOUND'
78
28
  status = 404
79
-
80
- constructor(message?: string) {
81
- super(message ?? 'NOT_FOUND')
82
- }
83
29
  }
84
30
 
85
31
  export class ParseError extends Error {
86
32
  code = 'PARSE'
87
33
  status = 400
88
-
89
- constructor() {
90
- super('Failed to parse body')
91
- }
92
- }
93
-
94
- export class InvalidCookieSignature extends Error {
95
- code = 'INVALID_COOKIE_SIGNATURE'
96
- status = 400
97
-
98
- constructor(public key: string, message?: string) {
99
- super(message ?? `"${key}" has invalid cookie signature`)
100
- }
101
34
  }
102
35
 
103
- export const mapValueError = (error: ValueError) => {
104
- const { message, path, value, type } = error
105
-
106
- const property = path.slice(1).replaceAll('/', '.')
107
- const isRoot = path === ''
108
-
109
- switch (type) {
110
- case 42:
111
- return {
112
- ...error,
113
- summary: isRoot
114
- ? `Value should not be provided`
115
- : `Property '${property}' should not be provided`
116
- }
117
-
118
- case 45:
119
- return {
120
- ...error,
121
- summary: isRoot
122
- ? `Value is missing`
123
- : `Property '${property}' is missing`
124
- }
125
-
126
- case 50:
127
- // Expected string to match 'email' format
128
- const quoteIndex = message.indexOf("'")!
129
- const format = message.slice(
130
- quoteIndex + 1,
131
- message.indexOf("'", quoteIndex + 1)
132
- )
133
-
134
- return {
135
- ...error,
136
- summary: isRoot
137
- ? `Value should be an email`
138
- : `Property '${property}' should be ${format}`
139
- }
140
-
141
- case 54:
142
- return {
143
- ...error,
144
- summary: `${message.slice(
145
- 0,
146
- 9
147
- )} property '${property}' to be ${message.slice(
148
- 8
149
- )} but found: ${value}`
150
- }
151
-
152
- case 62:
153
- const union = error.schema.anyOf
154
- .map((x: Record<string, unknown>) => `'${x?.format ?? x.type}'`)
155
- .join(', ')
156
-
157
- return {
158
- ...error,
159
- summary: isRoot
160
- ? `Value should be one of ${union}`
161
- : `Property '${property}' should be one of: ${union}`
162
- }
163
-
164
- default:
165
- return { summary: message, ...error }
166
- }
36
+ export class InternalServerError extends Error {
37
+ code = 'INTERNAL_SERVER_ERROR'
38
+ status = 500
167
39
  }
168
40
 
169
- export class ValidationError extends Error {
170
- code = 'VALIDATION'
171
- status = 422
172
-
173
- constructor(
174
- public type: string,
175
- public validator: TSchema | TypeCheck<any>,
176
- public value: unknown
177
- ) {
178
- if (value && typeof value === 'object' && ELYSIA_RESPONSE in value)
179
- // @ts-expect-error
180
- value = value.response
181
-
182
- const error = isProduction
183
- ? undefined
184
- : 'Errors' in validator
185
- ? validator.Errors(value).First()
186
- : Value.Errors(validator, value).First()
187
-
188
- const customError =
189
- error?.schema.error !== undefined
190
- ? typeof error.schema.error === 'function'
191
- ? error.schema.error({
192
- type,
193
- validator,
194
- value,
195
- get errors() {
196
- return [...validator.Errors(value)].map(
197
- mapValueError
198
- )
199
- }
200
- })
201
- : error.schema.error
202
- : undefined
203
-
204
- const accessor = error?.path || 'root'
205
- let message = ''
206
-
207
- if (customError !== undefined) {
208
- message =
209
- typeof customError === 'object'
210
- ? JSON.stringify(customError)
211
- : customError + ''
212
- } else if (isProduction) {
213
- message = JSON.stringify({
214
- type: 'validation',
215
- on: type,
216
- summary: mapValueError(error).summary,
217
- message: error?.message,
218
- found: value
219
- })
220
- } else {
221
- // @ts-ignore private field
222
- const schema = validator?.schema ?? validator
223
- const errors =
224
- 'Errors' in validator
225
- ? [...validator.Errors(value)].map(mapValueError)
226
- : [...Value.Errors(validator, value)].map(mapValueError)
227
-
228
- let expected
229
-
230
- try {
231
- expected = Value.Create(schema)
232
- } catch (error) {
233
- expected = {
234
- type: 'Could not create expected value',
235
- // @ts-expect-error
236
- message: error?.message,
237
- error
238
- }
239
- }
240
-
241
- message = JSON.stringify(
242
- {
243
- type: 'validation',
244
- on: type,
245
- summary: errors[0]?.summary,
246
- property: accessor,
247
- message: error?.message,
248
- expected,
249
- found: value,
250
- errors
251
- },
252
- null,
253
- 2
254
- )
255
- }
256
-
257
- super(message)
258
-
259
- Object.setPrototypeOf(this, ValidationError.prototype)
260
- }
261
-
262
- get all() {
263
- return 'Errors' in this.validator
264
- ? [...this.validator.Errors(this.value)].map(mapValueError)
265
- : // @ts-ignore
266
- [...Value.Errors(this.validator, this.value)].map(mapValueError)
267
- }
268
-
269
- static simplifyModel(validator: TSchema | TypeCheck<any>) {
270
- // @ts-ignore
271
- const model = 'schema' in validator ? validator.schema : validator
272
-
273
- try {
274
- return Value.Create(model)
275
- } catch {
276
- return model
277
- }
278
- }
279
-
280
- get model() {
281
- return ValidationError.simplifyModel(this.validator)
282
- }
283
-
284
- toResponse(headers?: Record<string, any>) {
285
- return new Response(this.message, {
286
- status: 400,
287
- headers: {
288
- ...headers,
289
- 'content-type': 'application/json'
290
- }
291
- })
292
- }
293
- }