duto 0.0.0 → 0.260906.0

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 (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -12
  3. package/package.json +91 -2
  4. package/src/cli.ts +42 -0
  5. package/src/enum.ts +2 -0
  6. package/src/index.d.ts +61 -0
  7. package/src/index.ts +19 -0
  8. package/src/middleware.ts +2 -0
  9. package/src/pages/client/app.tsx +123 -0
  10. package/src/pages/client/hmr.ts +13 -0
  11. package/src/pages/client/island.ts +136 -0
  12. package/src/pages/client/link.ts +70 -0
  13. package/src/pages/client/router.ts +329 -0
  14. package/src/pages/client/storage.ts +95 -0
  15. package/src/pages/commands/build.ts +74 -0
  16. package/src/pages/commands/dev-old.ts +213 -0
  17. package/src/pages/commands/dev.ts +87 -0
  18. package/src/pages/commands/preview.ts +33 -0
  19. package/src/pages/compiler/aliases.ts +36 -0
  20. package/src/pages/compiler/assets.ts +43 -0
  21. package/src/pages/compiler/ast.ts +764 -0
  22. package/src/pages/compiler/constants.ts +24 -0
  23. package/src/pages/compiler/file.ts +60 -0
  24. package/src/pages/compiler/index.ts +307 -0
  25. package/src/pages/compiler/island.ts +108 -0
  26. package/src/pages/compiler/meta.ts +601 -0
  27. package/src/pages/compiler/routes.ts +29 -0
  28. package/src/pages/compiler/types.ts +209 -0
  29. package/src/pages/compiler/utils.ts +5 -0
  30. package/src/pages/compiler/vite-plugin.ts +324 -0
  31. package/src/pages/components/link.tsx +38 -0
  32. package/src/pages/i18n/client.ts +403 -0
  33. package/src/pages/i18n/element.ts +44 -0
  34. package/src/pages/i18n/server.ts +56 -0
  35. package/src/pages/index.ts +28 -0
  36. package/src/pages/preact/client-dev.ts +5 -0
  37. package/src/pages/preact/client.ts +87 -0
  38. package/src/pages/preact/context.ts +41 -0
  39. package/src/pages/preact/server.ts +186 -0
  40. package/src/pages/preact/signals.ts +89 -0
  41. package/src/pages/preact/static-html.ts +45 -0
  42. package/src/pages/preact/types.ts +26 -0
  43. package/src/server/adapter/aws.ts +4 -0
  44. package/src/server/adapter/workerd.ts +4 -0
  45. package/src/server/app.ts +96 -0
  46. package/src/server/auth/ability.ts +72 -0
  47. package/src/server/auth/authnz.ts +86 -0
  48. package/src/server/auth/index.ts +5 -0
  49. package/src/server/auth/token.ts +147 -0
  50. package/src/server/auth/types.ts +2 -0
  51. package/src/server/cli/commands/build.ts +41 -0
  52. package/src/server/cli/commands/deploy.ts +82 -0
  53. package/src/server/cli/commands/dev.ts +277 -0
  54. package/src/server/cli/commands/make.ts +98 -0
  55. package/src/server/cli/commands/migrate.ts +93 -0
  56. package/src/server/cli/commands/routes.ts +79 -0
  57. package/src/server/cli/index.ts +27 -0
  58. package/src/server/cli/stubs.ts +39 -0
  59. package/src/server/cli/types.ts +3 -0
  60. package/src/server/cli/utils.ts +443 -0
  61. package/src/server/config.ts +4 -0
  62. package/src/server/db/d1.d.ts +1 -0
  63. package/src/server/db/d1.ts +1 -0
  64. package/src/server/db/dynamodb.d.ts +1 -0
  65. package/src/server/db/dynamodb.ts +1 -0
  66. package/src/server/db/index.ts +1 -0
  67. package/src/server/entry.ts +19 -0
  68. package/src/server/handle.ts +30 -0
  69. package/src/server/http.ts +23 -0
  70. package/src/server/middlewares/auth.ts +40 -0
  71. package/src/server/middlewares/logger.ts +30 -0
  72. package/src/server/open-api/register.ts +29 -0
  73. package/src/server/open-api/spec.ts +107 -0
  74. package/src/server/request.ts +233 -0
  75. package/src/server/response.ts +142 -0
  76. package/src/server/routes.ts +474 -0
  77. package/src/server/types.ts +142 -0
  78. package/src/server/utils/environment.ts +17 -0
  79. package/src/server/utils.ts +46 -0
  80. package/src/server/validator.ts +70 -0
  81. package/src/utils.ts +5 -0
@@ -0,0 +1,474 @@
1
+ import { Glob, file, write } from 'bun'
2
+
3
+ import { copyFileSync } from 'node:fs'
4
+ import { join, relative } from 'pathe'
5
+
6
+ import { camelCase, IMPORT, JSJSON, snakeCase } from 't0n'
7
+ import { rn, substep, warn } from 't0n/log'
8
+ import { config } from 'dotenv'
9
+ import { describeRoute, resolver, validator } from 'hono-openapi'
10
+ import { mimes } from 'hono/utils/mime'
11
+ import { STATUS_CODES } from 'node:http'
12
+ import $ from './app'
13
+ import _response from './response'
14
+ import _validator from './validator'
15
+ import { _root, _duto } from '@/utils'
16
+ import { getLastCommitHash, ensureDir, nextId } from './utils'
17
+ import { generateOpenAPI, generateOpenAPIClient, openAPIConfig } from './open-api/spec'
18
+ import { verbAlias } from './http'
19
+ import { highlightedMethod, highlightedURI } from './cli/utils'
20
+
21
+ import type { Routes, Rule, StandardSchemaV1 } from './types'
22
+ import { autorizedPath } from './middlewares/auth'
23
+ import { isDev } from './utils/environment'
24
+
25
+ export async function* scan(
26
+ dir: string | string[],
27
+ ext: string | string[],
28
+ ignore: string[] = ['*.d.*'],
29
+ cwd?: string,
30
+ ): AsyncGenerator<[string, string, any]> {
31
+ if (typeof dir !== 'string')
32
+ dir = ext.length === 1 ? dir[0] : `{${dir.join(',')}}`
33
+
34
+ if (typeof ext !== 'string')
35
+ ext = ext.length === 1 ? ext[0] : `{${ext.join(',')}}`
36
+
37
+ cwd ??= _root
38
+ for await (const file of new Glob(dir +'/**/*.'+ ext).scan({
39
+ cwd,
40
+ absolute: true,
41
+ onlyFiles: true, // @ts-ignore
42
+ ignore,
43
+ })) {
44
+ yield [file, file.split(cwd as string)[1], await IMPORT(file)]
45
+ }
46
+ }
47
+
48
+ const importName = (name: string | undefined, dirs: string[]) => (name || nextId()).replace(/\.ts$/, '')
49
+
50
+ function isSchema(obj: unknown): obj is StandardSchemaV1 {
51
+ return (
52
+ obj !== null &&
53
+ typeof obj === 'object' &&
54
+ '~standard' in obj &&
55
+ typeof (obj as StandardSchemaV1)['~standard'] === 'object'
56
+ )
57
+ }
58
+
59
+ function withoutChecks(obj: any) {
60
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return obj
61
+
62
+ for (const attr of [
63
+ 'format', 'pattern',
64
+ 'minLength', 'maxLength',
65
+ 'exclusiveMinimum', 'minimum',
66
+ 'exclusiveMaximum', 'maximum',
67
+ 'multipleOf',
68
+ 'minItems', 'maxItems', 'uniqueItems', 'contains',
69
+ 'minProperties', 'maxProperties', 'patternProperties', 'dependentRequired',
70
+ 'required',
71
+ ]) {
72
+ if (attr in obj)
73
+ delete obj[attr]
74
+ }
75
+
76
+ for (const key of ['items', 'properties']) {
77
+ if (key in obj) {
78
+ for (const prop in obj[key])
79
+ obj[key][prop] = withoutChecks(obj[key][prop])
80
+ }
81
+ }
82
+
83
+ return obj
84
+ }
85
+
86
+ async function ResolveDescribeSchema(obj: any, deep = false) {
87
+ if (!obj || typeof obj !== 'object') return obj
88
+
89
+ if (isSchema(obj)) {
90
+ const oas = await resolver(obj).toOpenAPISchema()
91
+ const cType = oas.schema.properties ? 'application/json' : 'text/plain'
92
+ return { content: { [cType]: {
93
+ schema: withoutChecks(oas.schema),
94
+ // TODO: obter o schema aqui..
95
+ // example:{
96
+ // id: '123',
97
+ // name: 'John Doe',
98
+ // image: 'image.jpg',
99
+ // token: 'dksjdksjdksjdksjd',
100
+ // exp: 987654321,
101
+ // }
102
+ }}}
103
+ }
104
+
105
+ if (obj.content && typeof obj.content === 'object') {
106
+ for (const mediaType in obj.content) {
107
+ const contentItem = obj.content[mediaType]
108
+
109
+ if (contentItem?.schema && isSchema(contentItem.schema)) {
110
+ const oas = await resolver(contentItem.schema).toOpenAPISchema()
111
+ contentItem.schema = withoutChecks(oas.schema)
112
+ // TODO: ler o schema dinamicamente
113
+ // contentItem.example = {
114
+ // id: '123',
115
+ // name: 'John Doe',
116
+ // image: 'image.jpg',
117
+ // token: 'dksjdksjdksjdksjd',
118
+ // exp: 987654321,
119
+ // }
120
+
121
+ }
122
+
123
+ if (mediaType in mimes) {
124
+ obj.content[mimes[mediaType]] = contentItem
125
+ delete obj.content[mediaType]
126
+ }
127
+ }
128
+
129
+ return obj
130
+ }
131
+
132
+ for (const key in obj) {
133
+ if (obj[key] && typeof obj[key] === 'object') {
134
+ obj[key] = await ResolveDescribeSchema(obj[key], true)
135
+
136
+ if (!deep && !obj[key]?.description) {
137
+ const desc = new Response(null, { status: Number(key) }).statusText || STATUS_CODES[key]
138
+ if (desc) obj[key].description = desc
139
+ }
140
+ }
141
+ }
142
+
143
+ return obj
144
+ }
145
+
146
+ let hasDuplicatedRoutes = false
147
+ export async function getRoutes(
148
+ dirs: string[] = ['actions', 'features', 'routes']
149
+ ): Promise<Routes> {
150
+ hasDuplicatedRoutes = false
151
+ const routes: Routes = []
152
+
153
+ let length = 0
154
+ const keys: Set<string> = new Set()
155
+ const bag: Record<string, string[]> = {}
156
+ const _route = (key: string, _: string) => {
157
+ if (!keys.has(key)) {
158
+ keys.add(key)
159
+ } else {
160
+ ; (bag[key] ||= []).push(_)
161
+ length++
162
+ }
163
+ }
164
+
165
+ for await (const [file, path, mod] of scan(dirs, ['ts', 'js'])) {
166
+ if (!mod?.handle && !mod?.handler) continue
167
+
168
+ const method = extractHttpVerb(path)
169
+ const uri = extractHttpPath(path)
170
+
171
+ const noAuth = autorizedPath(uri, method.toUpperCase())
172
+
173
+ const desc = mod?.desc || {}
174
+ const name = desc.summary || desc.name || uri.replace(/^\//, '') || 'index'
175
+
176
+ function parseRules(ruleFn: Function | undefined, fn: Function) {
177
+ const rules = typeof ruleFn === 'function' ? ruleFn(_validator) : null
178
+ return rules ? (Array.isArray(rules) ? rules : [rules]).flatMap(fn as any) : []
179
+ }
180
+
181
+ async function parseBody(ruleFn?: Function) {
182
+ const body = (await Promise.all(parseRules(ruleFn, async (rule: Rule) => {
183
+ if (!['json', 'form'].includes(rule.target))
184
+ return null
185
+
186
+ return { content: { [
187
+ rule.target === 'json'
188
+ ? 'application/json'
189
+ : 'multipart/form-data'
190
+ ]: { ...(await resolver(rule.schema).toOpenAPISchema()) } } }
191
+ }))).filter(Boolean)
192
+
193
+ return body.length === 1 ? body[0] : undefined
194
+ }
195
+
196
+ async function parseParams(ruleFn?: Function) {
197
+ const params = (await Promise.all(parseRules(ruleFn, async (rule: Rule) => {
198
+ if (['json', 'form'].includes(rule.target))
199
+ return null
200
+
201
+ const items = []
202
+ const oas = await resolver(rule.schema).toOpenAPISchema()
203
+
204
+ if (oas?.schema?.properties) {
205
+ const type = rule.target === 'param' ? 'path' : rule.target
206
+ items.push(Object.entries(oas.schema.properties).map(([name, props]) => ({
207
+ schema: props,
208
+ in: type,
209
+ name,
210
+ required: oas.schema.required?.includes(name) || false
211
+ })))
212
+ }
213
+
214
+ return items
215
+ }))).filter(Boolean).flat(Infinity)
216
+
217
+ return params.length ? params : undefined
218
+ }
219
+
220
+
221
+ routes.push({
222
+ method, path: uri,
223
+ name: snakeCase(name),
224
+ file,
225
+ rules: mod.rules,
226
+ handle: mod.handle || mod.handler,
227
+ middlewares: mod?.middlewares?.length ? mod.middlewares.flatMap((obj: any) => {
228
+ return typeof obj === 'string' ? obj : obj?.name || null
229
+ }).filter(Boolean) as Function[] : [],
230
+ desc: {
231
+ ...desc,
232
+ summary: desc.summary || desc.name || camelCase(path.replace(new RegExp(`^/(${dirs.join('|')})/`), '').replace(/\.[^/.]+(?:[.][^/.]+)*$/, '')),
233
+ parameters: await parseParams(mod.rules),
234
+ requestBody: await parseBody(mod.rules),
235
+ ...(noAuth ? {security: []} : {} ),
236
+ responses: {
237
+ ...(noAuth ? {} : {401: {description: 'Unauthorized'}} ),
238
+ 500: { $ref: '#/components/responses/500' },
239
+ ...(await ResolveDescribeSchema(desc?.responses)),
240
+ }
241
+ },
242
+ })
243
+
244
+ const routeKey = method +'|'+ uri
245
+ _route(name, file)
246
+ _route(routeKey, file)
247
+ }
248
+
249
+ if (length) {
250
+ hasDuplicatedRoutes = true
251
+ Object.entries(bag).forEach(([name, paths]) => {
252
+ if (name.includes('|')) {
253
+ let [method, uri] = name.split('|')
254
+ method = method.toUpperCase()
255
+ name = `${highlightedMethod(method, null, true)} "${highlightedURI(uri, method)}"`
256
+ }
257
+
258
+ warn(`Route ${name} has `+ (paths.length > 1 ? `registered ${paths.length} times:` : 'already been registered:'))
259
+ substep(...paths)
260
+ })
261
+
262
+ rn()
263
+ }
264
+
265
+ return sortRoutes(routes)
266
+ }
267
+
268
+ function extractHttpVerb(file: string) {
269
+ if (!file) return 'get'
270
+ const match = file.match(/\.(get|post|put|patch|delete|head|options)\.(?=[jt]s$)/i)
271
+ return match && match[1] ? match[1].toLowerCase() : 'get'
272
+ }
273
+
274
+ function extractHttpPath(file: string) {
275
+ const route = '/'+ file.replace(/\\/g, '/')
276
+ .replace(/^\/(actions|features|routes)/, '')
277
+ .replace(/\.[jt]s$/, '')
278
+ .replace(/\.(get|post|put|patch|delete|head|options)$/i, '')
279
+ .replace(/\/index$/, '')
280
+ .split('/')
281
+ .filter(Boolean)
282
+ .filter(part => !(part.startsWith('(') && part.endsWith(')')))
283
+ .map(part => part.startsWith('[') && part.endsWith(']') ? ':'+ part.slice(1, -1) : part)
284
+ .join('/')
285
+
286
+ return route === '/' ? '/' : route.replace(/\/$/, '')
287
+ }
288
+
289
+ export function sortRoutes(routes: Routes) {
290
+ const metas = new Map<string, { score: number, segmentsCount: number }>()
291
+
292
+ for (const route of routes)
293
+ metas.set(route.path, computeRouteMeta(route.path))
294
+
295
+ const list = routes.sort((a, b) => {
296
+ const metaA = metas.get(a.path)!
297
+ const metaB = metas.get(b.path)!
298
+
299
+ if (metaA.score === metaB.score)
300
+ return metaB.segmentsCount - metaA.segmentsCount
301
+
302
+ return metaB.score - metaA.score
303
+ })
304
+
305
+ if (list.length < 2) return list
306
+
307
+ const toMove = []
308
+ while (list.length && list.at(-1)?.path === '/') {
309
+ toMove.push(list.pop())
310
+ } // @ts-ignore
311
+ toMove.length && list.unshift(...toMove)
312
+ // while (list.length && list.at(-1)?.path === '/') {
313
+ // const last = list.pop()
314
+ // last && list.unshift(last)
315
+ // }
316
+
317
+ return list
318
+ }
319
+
320
+ function computeRouteMeta(path: string) {
321
+ const segments = path.split('/').filter(Boolean)
322
+
323
+ let score = 0
324
+ for (const segment of segments) {
325
+ if (segment === '*') {
326
+ continue
327
+ } else if (segment.startsWith(':')) {
328
+ score += 1
329
+ } else {
330
+ score += 10
331
+ }
332
+ }
333
+
334
+ return { score, segmentsCount: segments.length }
335
+ }
336
+
337
+ export async function getMiddlewares(dir: string = 'middlewares'): Promise<Routes> {
338
+ const mw: Routes = []
339
+
340
+ for await (const [file, path, mod] of scan(dir, ['ts', 'js'])) {
341
+ if (!mod?.handle && !mod?.handler) continue
342
+ // @ts-ignore
343
+ mw.push({
344
+ path: mod?.path || '*',
345
+ name: snakeCase(path.replace(/\\/g, '/')
346
+ .replace(new RegExp(`^\/(${dir})/`), '')
347
+ .replace(/\.[jt]s$/, '')
348
+ .replace(/\/index$/, '')
349
+ ),
350
+ file,
351
+ handle: mod.handle || mod.handler,
352
+ })
353
+ }
354
+
355
+ return mw
356
+ }
357
+
358
+ export async function getConfigs(dir: string = 'configs'): Promise<Record<string, any>> {
359
+ if (!join(_root, dir)) return {}
360
+ const configs: Record<string, any> = {}
361
+
362
+ for await (const [file, path, mod] of scan(dir, ['ts', 'js', 'cjs', 'mjs', 'json'])) {
363
+ const keyPath = path.replace(/\.[^/.]+$/, '').replaceAll('/', '.').split('.').slice(2)
364
+
365
+ keyPath.reduce((acc, key, index) => {
366
+ if (index === keyPath.length - 1) {
367
+ acc[key] = mod.default ?? mod
368
+ } else if (!acc[key] || typeof acc[key] !== 'object') {
369
+ acc[key] = {}
370
+ }
371
+
372
+ return acc[key]
373
+ }, configs)
374
+ }
375
+
376
+ return configs
377
+ }
378
+
379
+ export async function dependencyEntry(lib: string, root: string) {
380
+ const path = await import.meta.resolve(lib)
381
+ return relative(root, path.replace('file://', ''))
382
+ }
383
+
384
+ async function dependencyPath(lib: string) {
385
+ const entry = await dependencyEntry(lib, join(_root, '.duto'))
386
+ return entry.substring(0, entry.lastIndexOf(lib) + lib.length)
387
+ }
388
+
389
+ export async function cacheRoutes() {
390
+ const env = Object.entries(
391
+ config({ path: join(_root, '.env.prod') })?.parsed || {}
392
+ ).filter(([key, val]) => !key?.toLowerCase().startsWith('aws')) // prevent AWS credentials
393
+
394
+ const version = getLastCommitHash()
395
+ if (version) {
396
+ env.push(['VERSION_SHA', process.env['VERSION_SHA'] = version])
397
+ env.push(['VERSION_HASH', process.env['VERSION_HASH'] = version?.substring(0, 7)])
398
+ }
399
+
400
+ const rolePath = join(_root, 'configs/roles.ts')
401
+ ensureDir(rolePath)
402
+ const roleFile = file(rolePath)
403
+ if (!(await roleFile.exists()))
404
+ await write(rolePath, `export default {\n\n}`)
405
+
406
+ const configs = await getConfigs()
407
+ const middlewares = await getMiddlewares()
408
+ if (isDev()) {
409
+ const _file = join(_duto, 'server', 'middlewares', 'logger.ts')
410
+ const _mod = await IMPORT(_file) // @ts-ignore
411
+ middlewares.unshift({
412
+ path: _mod?.path || '*',
413
+ name: 'dev_logger',
414
+ file: _file,
415
+ handle: _mod.handle,
416
+ })
417
+ }
418
+
419
+ const routes = await getRoutes()
420
+ if (hasDuplicatedRoutes)
421
+ throw new Error("The app can't build with duplicate routes")
422
+
423
+ // _validator.setParser((rule: Rule) => validator(rule.target, rule.schema, (result, c) => {
424
+ // if (!result.success) // @ts-ignore
425
+ // return _response.badRequest(result.error)
426
+ // }))
427
+
428
+ const oasConfig = openAPIConfig(configs.duto)
429
+ if (!oasConfig.disable) {
430
+ // @ts-ignore
431
+ const app = $({ routes, routeRegister: (app: Hono, route: Route) => {
432
+ app[route.method](route.path, describeRoute(route.desc))
433
+ // app[route.method](route.path, describeRoute(route.desc), ...resolveHandle(route.middlewares, route.handle, route.rules))
434
+ } })
435
+
436
+ configs.DUTO_OAS = JSON.stringify(await generateOpenAPI(app, oasConfig))
437
+ configs.DUTO_OAC = await generateOpenAPIClient(app, oasConfig)
438
+ configs.duto.docs = {path: oasConfig.path, auth: oasConfig?.auth}
439
+ }
440
+
441
+ const iPath = join(_root, '.duto/imports.mjs')
442
+ ensureDir(iPath)
443
+
444
+ const localfireEntry = await dependencyEntry('localflare-api', _root)
445
+ copyFileSync(localfireEntry, join(_root, '.duto/localfire.js'))
446
+
447
+ const _dutoDir = await dependencyPath('duto')
448
+
449
+ await write(iPath, `// AUTO-GENERATED FILE - DO NOT EDIT
450
+ ${env?.length ? `import { Envir } from '${await dependencyPath('t0n')}/src/envir'\nEnvir.add(${JSJSON(Object.fromEntries(env))})` : ''}
451
+ ${Object.entries(configs)?.length ? `import Config from '${_dutoDir}/src/server/config'\nConfig.add(${JSJSON(configs)})` : ''}
452
+
453
+ ${middlewares.map(r => `import { handle as MW${r.name} } from '../${normalizeImportPath(r.file)}'`).join('\n')}
454
+ export const middlewares = [[${middlewares.map(mw => [
455
+ JSJSON(mw.path || false),
456
+ 'MW'+ mw.name,
457
+ JSJSON(mw.name),
458
+ ]).join('], [')}]]
459
+
460
+ ${routes.map(r => `import { handle as ${r.name}${r.rules ? ', rules as R'+r.name : ''} } from '../${normalizeImportPath(r.file)}'`).join('\n')}
461
+
462
+ export const routes = [[${routes.filter(r => r.method && r.path).map(route => [
463
+ verbAlias[route.method],
464
+ JSJSON(route.path),
465
+ JSJSON(route.middlewares),
466
+ ...(route.rules ? [route.name, 'R' + route.name] : [route.name]),
467
+ JSJSON(route.name)
468
+ ]).join('], [')}]]
469
+ `)
470
+ }
471
+
472
+ function normalizeImportPath(file: string) {
473
+ return relative(_root, file.replace(/\.tsx?$/i, '').replace(/(\/index)+$/i, '').replace(/\/+$/g, ''))
474
+ }
@@ -0,0 +1,142 @@
1
+ import type {
2
+ Hono, Env, Handler,
3
+ ErrorHandler, NotFoundHandler,
4
+ ValidationTargets,
5
+ // MiddlewareHandler,
6
+ } from 'hono'
7
+ import type { ResponseHeader } from 'hono/utils/headers'
8
+ // import type { StatusCode } from 'hono/utils/http-status'
9
+ import { mimes, type BaseMime } from 'hono/utils/mime'
10
+ import type { OpenAPIV3_1, OpenAPIV3 } from 'openapi-types'
11
+ import type { StandardSchemaV1 } from '@standard-schema/spec'
12
+ import type { DescribeRouteOptions as RawDescribeRouteOptions, ResolverReturnType } from 'hono-openapi'
13
+ import type * as z from 'zod'
14
+ import type zm from 'zod/mini'
15
+ // import Action from './action'
16
+ import request from './request'
17
+ import response from './response'
18
+ import validator from './validator'
19
+
20
+ // export type { H, Handler, HandlerResponse } from 'hono/types'
21
+ export type {
22
+ Hono,
23
+ Env, Context, Next,
24
+ // ErrorHandler, NotFoundHandler,
25
+ MiddlewareHandler, // TODO: remove..
26
+ ValidationTargets,
27
+ } from 'hono'
28
+ export type { HTTPResponseError } from 'hono/types'
29
+
30
+ export type {
31
+ ContentfulStatusCode,
32
+ RedirectStatusCode,
33
+ StatusCode,
34
+ } from 'hono/utils/http-status'
35
+ export type { BaseMime, StandardSchemaV1 }
36
+
37
+ export type zObject = z.ZodTypeAny | zm.ZodMiniObject
38
+ type PublicMethods<T> = {
39
+ [K in keyof T]: K extends `#${string}` | `$${string}` | symbol | 'prototype' ? never : K
40
+ }[keyof T]
41
+
42
+ export type IRequest = Pick<request, PublicMethods<request>>
43
+ export type IResponse = Pick<typeof response, PublicMethods<typeof response>>
44
+
45
+ export type IValidator = Pick<typeof validator, PublicMethods<typeof validator>>
46
+ export type Rule = {
47
+ schema: zObject
48
+ target: keyof ValidationTargets
49
+ eTarget?: 'fieldErrors' | 'formErrors'
50
+ }
51
+ export type Rules = Rule[] | Rule | null
52
+ export type RuleFn = (schema: zObject) => Rule
53
+ export type RuleFactory = (validator: IValidator) => Rules
54
+
55
+ export type StandardSchema = StandardSchemaV1 | OpenAPIV3_1.ReferenceObject
56
+ export type DescribeRouteOptions = Omit<RawDescribeRouteOptions, 'responses'> & {
57
+ responses?: {
58
+ [key: string | number]: (Omit<OpenAPIV3.ResponseObject, 'description' | 'headers' | 'content' | 'links'> & {
59
+ description?: string,
60
+ headers?: { // TODO maybe dont accept ResolverReturnType
61
+ [header: string]: OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.HeaderObject
62
+ },
63
+ content?: {
64
+ [key: BaseMime | keyof typeof mimes]: Omit<OpenAPIV3_1.MediaTypeObject, 'schema'> & {
65
+ schema?: StandardSchema | OpenAPIV3_1.SchemaObject | ResolverReturnType,
66
+ },
67
+ },
68
+ links?: { // TODO maybe dont accept ResolverReturnType
69
+ [link: string]: OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.LinkObject
70
+ },
71
+ }) | StandardSchema,
72
+ },
73
+ }
74
+
75
+ export type Route = {
76
+ method: string,
77
+ path: string,
78
+ name: string,
79
+ file: string,
80
+ middlewares: Function[],
81
+ handle: Handlers,
82
+ rules?: Rules,
83
+ desc: DescribeRouteOptions,
84
+ }
85
+
86
+ // export type ActionType = Function | Handler | Action | (new () => Action)
87
+
88
+ export type Handle = Function | Handler
89
+ export type Handlers = Handle[]
90
+ // export type Handlers = (Function | Handler | (new () => Action))[]
91
+
92
+ export type Routes = Route[]
93
+
94
+ export type HeaderRecord =
95
+ | Record<'Content-Type', BaseMime>
96
+ | Record<ResponseHeader, string | string[]>
97
+ | Record<string, string | string[]>
98
+
99
+
100
+ export type InitFunction<E extends Env = Env> = (app: Hono<E>) => void
101
+ export type ServerOptions<E extends Env = Env> = Partial<{
102
+ routes: Routes,
103
+ middlewares: Routes[],
104
+ notFound: NotFoundHandler<E>,
105
+ onError: ErrorHandler<E>,
106
+ root: string,
107
+ app?: Hono<E>,
108
+ init?: InitFunction<E>,
109
+ }>
110
+
111
+ // export type MiddlewareType = MiddlewareHandler | Middleware | (new () => Middleware)
112
+
113
+ export type Errors = Record<string, string | string[]>
114
+ export type ErrorResponse = {
115
+ m?: string, // message
116
+ e?: Errors, // error bag
117
+ }
118
+
119
+ // TODO: not used..
120
+
121
+ // export type LambdaResponse = {
122
+ // statusCode: StatusCode,
123
+ // body: string,
124
+ // }
125
+
126
+ // export type ResponseHeadersInit = [
127
+ // string,
128
+ // string
129
+ // ][] | Record<"Content-Type", BaseMime> | Record<ResponseHeader, string> | Record<string, string> | Headers
130
+ // export type ResponseInit<T extends StatusCode = StatusCode> = {
131
+ // headers?: ResponseHeadersInit,
132
+ // status?: T,
133
+ // statusText?: string,
134
+ // }
135
+ // export type ResponseOrInit<T extends StatusCode = StatusCode> = ResponseInit<T> | Response
136
+ // export type JSONValue =
137
+ // | string
138
+ // | number
139
+ // | boolean
140
+ // | null
141
+ // | { [key: string]: JSONValue }
142
+ // | JSONValue[]
@@ -0,0 +1,17 @@
1
+ export const $prd = Symbol('prd')
2
+ export const $dev = Symbol('dev')
3
+
4
+ let env = $prd
5
+
6
+ export const getEnv = () => env
7
+ export const setEnv = (e: symbol) => env = e
8
+
9
+ export const isEnv = (e: symbol) => env === e
10
+
11
+ export const isDev = () => env === $dev
12
+ export const isDevelop = isDev
13
+ export const isDevelopment = isDev
14
+
15
+ export const isPrd = () => env === $prd
16
+ export const isProd = isPrd
17
+ export const isProduction = isPrd
@@ -0,0 +1,46 @@
1
+ import { mkdirSync, existsSync, readFileSync } from 'node:fs'
2
+ import { dirname, join } from 'pathe'
3
+ import { _root } from '@/utils'
4
+
5
+ export function getLastCommitHash(path: string = '.git') {
6
+ try {
7
+ const gitDir = join(_root, path)
8
+
9
+ let headContent = readFileSync(join(gitDir, 'HEAD'), 'utf8').trim()
10
+
11
+ if (headContent.startsWith('ref:'))
12
+ headContent = readFileSync(join(gitDir, headContent.substring(5)), 'utf8').trim()
13
+
14
+ return headContent
15
+ } catch (e) {
16
+ return null
17
+ }
18
+ }
19
+
20
+ export function ensureDir(filePath: string) {
21
+ const dir = dirname(filePath)
22
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
23
+ }
24
+
25
+
26
+
27
+ let id = '';
28
+ export const nextId = () => {
29
+ const chars = id.split('')
30
+ let i = chars.length - 1
31
+
32
+ while (i >= 0) {
33
+ if (chars[i] === 'z') {
34
+ chars[i] = 'A'
35
+ i--
36
+ } else if (chars[i] === 'Z') {
37
+ chars[i] = 'a'
38
+ i--
39
+ } else {
40
+ chars[i] = String.fromCharCode(chars[i].charCodeAt(0) + 1)
41
+ return id = chars.join('')
42
+ }
43
+ }
44
+
45
+ return id = 'a' + chars.join('')
46
+ }