duto 0.260808.0 → 0.260915.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 (98) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +35 -13
  3. package/package.json +54 -13
  4. package/src/cli.ts +45 -0
  5. package/src/commands/new.ts +147 -0
  6. package/src/commands/templates/base/.editorconfig +21 -0
  7. package/src/commands/templates/base/.env.dev.example +2 -0
  8. package/src/commands/templates/base/.env.prod.example +2 -0
  9. package/src/commands/templates/base/README.md +29 -0
  10. package/src/commands/templates/base/_gitignore +100 -0
  11. package/src/commands/templates/base/actions/blog/index.get.ts +9 -0
  12. package/src/commands/templates/base/actions/blog/post/[id].get.ts +9 -0
  13. package/src/commands/templates/base/actions/index.get.ts +5 -0
  14. package/src/commands/templates/base/configs/duto.ts +11 -0
  15. package/src/commands/templates/base/configs/roles.ts +5 -0
  16. package/src/commands/templates/base/package.json +24 -0
  17. package/src/commands/templates/base/tsconfig.json +42 -0
  18. package/src/commands/templates/workerd/wrangler.toml +47 -0
  19. package/src/commands/upgrade.ts +30 -0
  20. package/src/enum.ts +2 -0
  21. package/src/index.d.ts +61 -0
  22. package/src/index.ts +19 -23
  23. package/src/middleware.ts +2 -0
  24. package/src/{cli → pages/cli}/commands/build.ts +13 -7
  25. package/src/{cli → pages/cli}/commands/dev.ts +11 -4
  26. package/src/{cli → pages/cli}/commands/preview.ts +6 -4
  27. package/src/pages/cli/index.ts +10 -0
  28. package/src/{cli → pages/cli}/utils.ts +4 -5
  29. package/src/{client → pages/client}/app.tsx +1 -1
  30. package/src/{client → pages/client}/router.ts +1 -1
  31. package/src/{compiler → pages/compiler}/file.ts +1 -1
  32. package/src/{compiler → pages/compiler}/index.ts +2 -4
  33. package/src/{compiler → pages/compiler}/meta.ts +2 -2
  34. package/src/{compiler → pages/compiler}/routes.ts +1 -1
  35. package/src/{compiler → pages/compiler}/types.ts +10 -0
  36. package/src/{compiler → pages/compiler}/vite-plugin.ts +20 -45
  37. package/src/{i18n → pages/i18n}/client.ts +2 -2
  38. package/src/{i18n → pages/i18n}/element.ts +1 -1
  39. package/src/{i18n → pages/i18n}/server.ts +1 -1
  40. package/src/pages/index.ts +28 -0
  41. package/src/{preact → pages/preact}/server.ts +1 -1
  42. package/src/server/adapter/aws.ts +4 -0
  43. package/src/server/adapter/workerd.ts +4 -0
  44. package/src/server/app.ts +91 -0
  45. package/src/server/auth/ability.ts +72 -0
  46. package/src/server/auth/authnz.ts +86 -0
  47. package/src/server/auth/index.ts +5 -0
  48. package/src/server/auth/token.ts +147 -0
  49. package/src/server/auth/types.ts +2 -0
  50. package/src/server/cli/commands/build.ts +41 -0
  51. package/src/server/cli/commands/deploy.ts +81 -0
  52. package/src/server/cli/commands/dev.ts +279 -0
  53. package/src/server/cli/commands/make.ts +98 -0
  54. package/src/server/cli/commands/migrate.ts +93 -0
  55. package/src/server/cli/commands/routes.ts +78 -0
  56. package/src/server/cli/index.ts +27 -0
  57. package/src/server/cli/stubs.ts +39 -0
  58. package/src/server/cli/types.ts +3 -0
  59. package/src/server/cli/utils.ts +437 -0
  60. package/src/server/config.ts +19 -0
  61. package/src/server/db/d1.d.ts +1 -0
  62. package/src/server/db/d1.ts +1 -0
  63. package/src/server/db/dynamodb.d.ts +1 -0
  64. package/src/server/db/dynamodb.ts +1 -0
  65. package/src/server/db/index.ts +1 -0
  66. package/src/server/entry.ts +19 -0
  67. package/src/server/handle.ts +30 -0
  68. package/src/server/middlewares/auth.ts +40 -0
  69. package/src/server/middlewares/logger.ts +30 -0
  70. package/src/server/open-api/register.ts +29 -0
  71. package/src/server/open-api/spec.ts +114 -0
  72. package/src/server/request.ts +265 -0
  73. package/src/server/response.ts +142 -0
  74. package/src/server/routes.ts +473 -0
  75. package/src/server/types.ts +142 -0
  76. package/src/server/utils/environment.ts +17 -0
  77. package/src/server/utils.ts +71 -0
  78. package/src/server/validator.ts +70 -0
  79. package/src/utils.ts +15 -0
  80. package/src/cli/commands/dev-old.ts +0 -213
  81. package/src/cli/index.ts +0 -28
  82. package/src/compiler/utils.ts +0 -5
  83. /package/src/{client → pages/client}/hmr.ts +0 -0
  84. /package/src/{client → pages/client}/island.ts +0 -0
  85. /package/src/{client → pages/client}/link.ts +0 -0
  86. /package/src/{client → pages/client}/storage.ts +0 -0
  87. /package/src/{compiler → pages/compiler}/aliases.ts +0 -0
  88. /package/src/{compiler → pages/compiler}/assets.ts +0 -0
  89. /package/src/{compiler → pages/compiler}/ast.ts +0 -0
  90. /package/src/{compiler → pages/compiler}/constants.ts +0 -0
  91. /package/src/{compiler → pages/compiler}/island.ts +0 -0
  92. /package/src/{components → pages/components}/link.tsx +0 -0
  93. /package/src/{preact → pages/preact}/client-dev.ts +0 -0
  94. /package/src/{preact → pages/preact}/client.ts +0 -0
  95. /package/src/{preact → pages/preact}/context.ts +0 -0
  96. /package/src/{preact → pages/preact}/signals.ts +0 -0
  97. /package/src/{preact → pages/preact}/static-html.ts +0 -0
  98. /package/src/{preact → pages/preact}/types.ts +0 -0
@@ -0,0 +1,437 @@
1
+ import ts from 'typescript'
2
+ import { Miniflare } from 'miniflare'
3
+ import { mkdirSync, existsSync, statSync, readdirSync, rmSync, unlinkSync, copyFileSync, writeFileSync } from 'node:fs'
4
+ import { stat } from 'node:fs/promises'
5
+ import { basename, dirname, join } from 'pathe'
6
+ import { createHash, createHmac } from 'node:crypto'
7
+
8
+ import { findWranglerConfig, parseWranglerConfig, WRANGLER_CONFIG_FILES } from 'localflare-core'
9
+ import type { WranglerConfig, LocalflareManifest } from 'localflare-core'
10
+
11
+ import { gray, bold, italic, purple, yellow, red } from 't0n/color'
12
+ import { substep, event, error, warn } from 't0n/log'
13
+
14
+ import { cacheRoutes } from '$/routes'
15
+ import { _root, _server, filesize } from '@/utils'
16
+
17
+ import type { Platform } from './types'
18
+
19
+ export const platforms = [
20
+ 'aws', 'lambda',
21
+ 'cf', 'cloudflare', 'worker', 'workers', 'workerd',
22
+ 'node', 'bun',
23
+ 'vercel',
24
+ ] as const
25
+
26
+ export function normalizePlatform(platform: Platform) {
27
+ platform = platform?.toLowerCase() as Platform
28
+ if (!platforms?.includes(platform)) return null
29
+
30
+ switch (platform) {
31
+ case 'lambda':
32
+ return 'aws'
33
+
34
+ case 'cf':
35
+ case 'cloudflare':
36
+ case 'worker':
37
+ case 'workers':
38
+ return 'workerd'
39
+
40
+ case 'bun':
41
+ return 'node'
42
+
43
+ default:
44
+ return platform
45
+ }
46
+ }
47
+
48
+ const formatArgs = (...args: any[]) => args.flat().map(a => gray(italic(bold(a)))).join(', ')
49
+ export const platformError = () => error(`Provide a valid platform: ${formatArgs(platforms)}.\n`)
50
+
51
+ const nodeModules = [
52
+ 'crypto', 'buffer', 'http', 'fs', 'path', 'events', 'stream', 'util',
53
+ 'url', 'querystring', 'os', 'child_process', 'cluster', 'dns', 'net',
54
+ 'tls', 'https', 'zlib', 'readline', 'repl', 'vm', 'module', 'assert',
55
+ 'timers', 'string_decoder', 'punycode', 'perf_hooks', 'dgram', 'tty',
56
+ 'worker_threads', 'wasi', 'process', 'diagnostics_channel', 'sqlite',
57
+ 'async_hooks', 'console', 'fsevents',
58
+ ].flatMap(lib => ['node:'+ lib, lib])
59
+
60
+ const printer = ts.createPrinter()
61
+ function stripDecorators(source: string) {
62
+ const sourceFile = ts.createSourceFile(
63
+ 'tmp.ts', source, ts.ScriptTarget.ESNext, false
64
+ )
65
+
66
+ const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
67
+ const visit: ts.Visitor = (node) => {
68
+ if (ts.isClassDeclaration(node) && node.modifiers?.length) {
69
+ let hasDecorator = false
70
+ const modifiers = []
71
+
72
+ for (const m of node.modifiers) {
73
+ if (m.kind === ts.SyntaxKind.Decorator) {
74
+ hasDecorator = true
75
+ continue
76
+ }
77
+ modifiers.push(m)
78
+ }
79
+
80
+ if (hasDecorator) {
81
+ return ts.factory.updateClassDeclaration(
82
+ node,
83
+ modifiers.length ? modifiers : undefined,
84
+ node.name,
85
+ node.typeParameters,
86
+ node.heritageClauses,
87
+ node.members
88
+ )
89
+ }
90
+ }
91
+
92
+ return ts.visitEachChild(node, visit, context)
93
+ }
94
+
95
+ return (node) => ts.visitNode(node, visit) as ts.SourceFile
96
+ }
97
+
98
+ const result = ts.transform(sourceFile, [transformer])
99
+ const code = printer.printFile(result.transformed[0])
100
+
101
+ result.dispose()
102
+ return code
103
+ }
104
+
105
+ const dist = '.duto'
106
+ export const build = async ({
107
+ env = 'prd',
108
+ platform,
109
+ silent = false,
110
+ minify = true,
111
+ }: {
112
+ env: string,
113
+ platform: Platform,
114
+ silent: boolean,
115
+ minify: boolean,
116
+ }) => {
117
+ const startTime = performance.now()
118
+ const isWorkerd = platform === 'workerd'
119
+ cleanDir(join(_root, dist))
120
+
121
+ if (isWorkerd) {
122
+ for (let file of WRANGLER_CONFIG_FILES) {
123
+ file = join(_root, file)
124
+ if (existsSync(file))
125
+ copyFileSync(file, join(_root, dist, basename(file)))
126
+ }
127
+ }
128
+
129
+ if (['bun', 'vercel'].includes(platform)) platform = 'workerd'
130
+
131
+ const outdir = join(_root, dist)
132
+
133
+ const USE_STRICT_RE = /(["'`])\s*use strict\s*\1;?/g
134
+
135
+ await cacheRoutes()
136
+ if (!silent) event('Routes cached')
137
+
138
+ const result = await Bun.build({
139
+ entrypoints: [join(_server, 'adapter', `${platform}.ts`)],
140
+ outdir,
141
+ format: 'esm',
142
+ target: 'bun',
143
+ conditions: isWorkerd ? ['worker', 'browser'] : [],
144
+ minify: !!minify,
145
+
146
+ treeShaking: true,
147
+ legalComments: 'none',
148
+ metafile: false,
149
+ external: [
150
+ '@aws-sdk', '@smithy',
151
+ ...(isWorkerd ? [
152
+ 'cloudflare:workers',
153
+ ...nodeModules,
154
+ ] : []),
155
+ ],
156
+
157
+ define: {
158
+ 'process.env.DUTO_ENV': JSON.stringify(env),
159
+ },
160
+
161
+ plugins: [
162
+ {
163
+ name: 'duto',
164
+ setup(build) {
165
+ build.onResolve({ filter: /\.duto[\/\\]/ }, args => ({ path: join(_root, args.path) }))
166
+ build.onLoad({ filter: /\.ts$/ }, async (args) => {
167
+ return { contents: stripDecorators(await Bun.file(args.path).text()), loader: 'ts' }
168
+ })
169
+ },
170
+ },
171
+ ],
172
+ })
173
+
174
+ if (!result.success || !result?.outputs?.length)
175
+ throw new Error('build fail')
176
+
177
+ const output = result.outputs.find(file => file.kind === 'entry-point' && file.path.endsWith('.js'))
178
+ if (!output)
179
+ throw new Error('Build output not found')
180
+
181
+ let code = (await output.text()).replace(/\/\/\s*@bun\s*\n/g, '')
182
+
183
+ if (USE_STRICT_RE.test(code)) {
184
+ code = code.replace(USE_STRICT_RE, '')
185
+ await Bun.write(output.path, code)
186
+ }
187
+
188
+ const outfile = join(outdir, 'index.js')
189
+ if (output.path !== outfile) {
190
+ await Bun.write(outfile, code)
191
+
192
+ if (existsSync(output.path))
193
+ await Bun.file(output.path).delete()
194
+ }
195
+
196
+ if (!silent) {
197
+ event(`Build done in ${Math.ceil(performance.now() - startTime)}ms`)
198
+ substep(
199
+ // `${relative(join(_root, 'node_modules/duto/src'), opts.entryPoints[0])} → ${relative(_root, opts.outfile)}`,
200
+ `Size: ${filesize((await stat(outfile)).size)}`,
201
+ `Files: ${Object.keys(result.outputs).length}`
202
+ )
203
+ }
204
+ }
205
+
206
+ export function cleanDir(path: string) {
207
+ try {
208
+ if (!existsSync(path))
209
+ return mkdirSync(path, {recursive: true})
210
+
211
+ const files = readdirSync(path)
212
+
213
+ for (const file of files) {
214
+ const filePath = join(path, file)
215
+ const stat = statSync(filePath)
216
+
217
+ stat.isDirectory()
218
+ ? rmSync(filePath, {recursive: true, force: true})
219
+ : unlinkSync(filePath)
220
+ }
221
+ } catch {}
222
+ }
223
+
224
+ function durableObjectNamespace(name: string, uniqueKey: string) {
225
+ const key = createHash('sha256').update(uniqueKey).digest()
226
+ const nameHmac = createHmac('sha256', key).update(name).digest().subarray(0, 16)
227
+ return Buffer.concat([
228
+ nameHmac,
229
+ createHmac('sha256', key).update(nameHmac).digest().subarray(0, 16)
230
+ ]).toString('hex')
231
+ }
232
+
233
+ export function cleanDB(key: string, config?: WranglerConfig) {
234
+ config ??= wranglerConfig()
235
+ if (!config?.d1_databases) return
236
+ for (const db of config.d1_databases) {
237
+ if (key === db.database_name || key === db.binding) {
238
+ const ns = 'miniflare-D1DatabaseObject'
239
+ const fileName = join(ns, durableObjectNamespace(db.preview_database_id || db.database_id || db.binding, ns) +'.sqlite')
240
+
241
+ for (const file of [
242
+ fileName,
243
+ fileName +'-shm',
244
+ fileName +'-wal',
245
+ ]) {
246
+ const filePath = join(d1Path, file)
247
+ if (existsSync(filePath))
248
+ unlinkSync(filePath)
249
+ }
250
+ }
251
+ }
252
+ }
253
+
254
+ export function wranglerConfig(file: string | null = null) {
255
+ file ??= findWranglerConfig(_root)
256
+ if (!file) {
257
+ error('Could not find wrangler config file')
258
+ error('Looking for: '+ formatArgs(WRANGLER_CONFIG_FILES))
259
+ process.exit(1)
260
+ }
261
+
262
+ try {
263
+ return parseWranglerConfig(file)
264
+ } catch (e) {
265
+ warn(`Could not parse '${file}', using defaults`)
266
+ return {}
267
+ }
268
+ }
269
+
270
+ export function localflareManifest(opts: WranglerConfig): LocalflareManifest {
271
+ return {
272
+ name: opts.name || 'worker',
273
+ d1: (opts.d1_databases || []).map((db) => ({
274
+ binding: db.binding,
275
+ database_name: db.database_name,
276
+ })),
277
+ kv: (opts.kv_namespaces || []).map((kv) => ({
278
+ binding: kv.binding,
279
+ })),
280
+ r2: (opts.r2_buckets || []).map((r2) => ({
281
+ binding: r2.binding,
282
+ bucket_name: r2.bucket_name,
283
+ })),
284
+ queues: {
285
+ producers: (opts.queues?.producers || []).map((p) => ({
286
+ binding: p.binding,
287
+ queue: p.queue,
288
+ })),
289
+ consumers: (opts.queues?.consumers || []).map((c) => ({
290
+ queue: c.queue,
291
+ max_batch_size: c.max_batch_size,
292
+ max_batch_timeout: c.max_batch_timeout,
293
+ max_retries: c.max_retries,
294
+ dead_letter_queue: c.dead_letter_queue,
295
+ })),
296
+ },
297
+ do: (opts.durable_objects?.bindings || []).map((d) => ({
298
+ binding: d.name,
299
+ className: d.class_name,
300
+ })),
301
+ vars: Object.entries(opts.vars || {}).map(([key, value]) => ({
302
+ key,
303
+ value,
304
+ isSecret: false,
305
+ // isSecret: looksLikeSecret(key, value),
306
+ })),
307
+ }
308
+ }
309
+
310
+ const wranglerPath = '.wrangler/state/v3'
311
+ export const kvPath = join(_root, wranglerPath, 'kv')
312
+ export const d1Path = join(_root, wranglerPath, 'd1')
313
+ export const r2Path = join(_root, wranglerPath, 'r2')
314
+ export const doPath = join(_root, wranglerPath, 'durable_objects')
315
+
316
+ export function createMiniflare(opts = {}) {
317
+ const entry = join(_root, opts?.main || opts?.script)
318
+ return new Miniflare({
319
+ host: opts.host || 'localhost',
320
+ port: opts.port || 8787,
321
+ https: opts.https || false,
322
+ httpsKey: opts.httpsKey,
323
+ httpsCert: opts.httpsCert,
324
+ liveReload: opts.liveReload !== false,
325
+ updateCheck: false,
326
+ scriptPath: entry,
327
+ modules: [
328
+ {type: 'ESModule', path: entry},
329
+ ],
330
+ compatibilityDate: opts.compatibility_date || '2024-11-01',
331
+ compatibilityFlags: opts.compatibility_flags || [
332
+ 'nodejs_compat',
333
+ ],
334
+ bindings: {
335
+ ...opts.vars,
336
+ },
337
+
338
+ d1Databases: Array.isArray(opts.d1_databases) ? Object.fromEntries(opts.d1_databases.map(db => [db.binding, db.database_id])) : {},
339
+ kvNamespaces: Object.fromEntries((opts.kv_namespaces || []).map(ns => [ns.binding, ns.id])),
340
+ // r2Buckets: Object.fromEntries((opts.r2_buckets || []).map(r2 => [r2.binding, r2.bucket_name])),
341
+ // durableObjects: Object.fromEntries((opts.durable_objects?.bindings || []).map(do => [do.name, do.class_name])),
342
+
343
+ kvPersist: kvPath,
344
+ cachePersist: join(_root, wranglerPath, 'cache'),
345
+ d1Persist: d1Path,
346
+ r2Persist: r2Path,
347
+ durablesPersist: doPath,
348
+
349
+ verbose: false,
350
+
351
+ // Logging
352
+ // log: new console.Console({
353
+ // stdout: process.stdout,
354
+ // stderr: process.stderr,
355
+ // inspectOptions: { depth: 3 }
356
+ // }),
357
+
358
+ cfFetch: false, // disable cf requests
359
+ upstream: opts.upstream || 'https://example.com',
360
+
361
+ sitePath: opts.site?.bucket ?
362
+ join(_root, opts.site.bucket) : undefined,
363
+ siteInclude: opts.site?.include || ['**/*'],
364
+ siteExclude: opts.site?.exclude || [],
365
+
366
+ globalAsyncIO: true,
367
+ globalTimers: true,
368
+ globalRandom: true,
369
+
370
+ inspectorPort: opts.inspectorPort || 9229,
371
+
372
+ cache: true,
373
+ cacheWarnUsage: true,
374
+ })
375
+ }
376
+
377
+ export async function wait(ms: number) {
378
+ return new Promise(r => setTimeout(r, ms))
379
+ }
380
+
381
+ export function getDockerHost() {
382
+ const platform = process.platform
383
+
384
+ if (platform === 'darwin') {
385
+ for (const socket of [
386
+ '/Users/'+ process.env.USER +'/.docker/run/docker.sock',
387
+ '/var/run/docker.sock',
388
+ process.env.DOCKER_HOST
389
+ ]) {
390
+ if (socket && existsSync(socket.replace(/^unix:\/\//, '')))
391
+ return socket.includes('://') ? socket : `unix://${socket}`
392
+ }
393
+
394
+ return 'tcp://localhost:2375'
395
+ }
396
+
397
+ return process.env.DOCKER_HOST || (platform === 'win32' ? 'tcp://localhost:2375' : 'unix:///var/run/docker.sock')
398
+ }
399
+
400
+ export function makeFile(path: string, content: string) {
401
+ const dir = dirname(path)
402
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
403
+
404
+ writeFileSync(path, content)
405
+ }
406
+
407
+ export function hasExt(path: string) {
408
+ const index = path.lastIndexOf('.')
409
+ return index > 0 && index < path.length - 1
410
+ }
411
+
412
+ export const highlightedMethod = (method: string, str?: string | null, siblings: boolean = false): string => {
413
+ const val = str || method
414
+
415
+ switch (method) {
416
+ case 'HEAD':
417
+ case 'OPTIONS':
418
+ case 'CONNECT':
419
+ case 'TRACE':
420
+ return gray(val)
421
+ case 'GET':
422
+ return purple(val) + (siblings ? gray('|') + highlightedMethod('HEAD') : '')
423
+ case 'POST':
424
+ case 'PUT':
425
+ case 'PATCH':
426
+ return yellow(val)
427
+ case 'DELETE':
428
+ return red(val)
429
+ }
430
+
431
+ return val
432
+ }
433
+
434
+ export const highlightedURI = (uri: string, method: string) => uri.replace(
435
+ /(?::([a-zA-Z_][a-zA-Z0-9_]*)(\{[^}]+\})?|\*)/g,
436
+ _ => highlightedMethod(method, _)
437
+ )
@@ -0,0 +1,19 @@
1
+
2
+ import { get, set } from 't0n'
3
+
4
+ const obj: Record<string, unknown> = {}
5
+
6
+ export function getConfig<T = unknown>(key: string, defaultValue: T): T
7
+ export function getConfig<T = unknown>(key: string, defaultValue?: T): T | undefined
8
+ export function getConfig<T = unknown>(key: string, defaultValue?: T) {
9
+ return get<T>(obj, key, defaultValue)
10
+ }
11
+ export const setConfig = (key: string, val: unknown) => set(obj, key, val)
12
+ export function addConfig<T = unknown>(data: Record<string, T>) {
13
+ Object.assign(obj, data)
14
+ }
15
+ // export const getConfig = <T = unknown>(key: string, defaultValue?: T) => get<T>(obj, key, defaultValue)
16
+ // import { DataBag } from 't0n'
17
+
18
+ // const Config = new DataBag()
19
+ // export default Config
@@ -0,0 +1 @@
1
+ export * from 'forj/d1/types'
@@ -0,0 +1 @@
1
+ export * from 'forj/d1'
@@ -0,0 +1 @@
1
+ export * from 'forj/dynamodb/types'
@@ -0,0 +1 @@
1
+ export * from 'forj/dynamodb'
@@ -0,0 +1 @@
1
+ export { Migration, Schema, Blueprint } from 'forj'
@@ -0,0 +1,19 @@
1
+ import { getConfig } from '$/config'
2
+ import { registerOpenAPI } from '$/open-api/register'
3
+ import { Ability } from '$/auth'
4
+ import $ from '$/app'
5
+
6
+ // @ts-ignore
7
+ import '.duto/imports.mjs'
8
+ // @ts-ignore
9
+ import { routes, middlewares } from '.duto/imports.mjs'
10
+
11
+ // @ts-ignore
12
+ Ability.fromRoutes(routes)
13
+ Ability.roles = getConfig('roles', {})
14
+
15
+ // @ts-ignore
16
+ const app = $({ routes, middlewares })
17
+ registerOpenAPI(app, getConfig('duto.docs', {}))
18
+
19
+ export { app }
@@ -0,0 +1,30 @@
1
+ import res from './response'
2
+ import $v from './validator'
3
+ import { GET_REQUEST } from './request'
4
+ import type { Context, Next, RuleFactory } from './types'
5
+
6
+ export const _mw: Record<string, Function> = {}
7
+ export const getMiddlewares = () => _mw
8
+ export function getMiddleware(id: string | Function): Function {
9
+ if (typeof id === 'function') return id
10
+ const mw = _mw[id] || null
11
+ if (!mw) throw new Error(`Handler ${id} not registered`)
12
+ return mw
13
+ }
14
+ export function registerMiddleware(id: string, handler: Function) {
15
+ _mw[id] = handler
16
+ }
17
+
18
+ export const resolveMw = (mw: Function) => {
19
+ return mw.length < 3 ? mw : async (c: Context, next: Next) => await getMiddleware(mw)(c.get(GET_REQUEST as unknown as string), res, next, c)
20
+ }
21
+
22
+ export const resolveHandle = (mw: (string | Function)[], handle: Function, ruleFn?: RuleFactory) => {
23
+ const h = async (c: Context) => await handle(c.get(GET_REQUEST as unknown as string), res)
24
+ mw = mw?.length ? mw.map(getMiddleware) : []
25
+
26
+ const rules = typeof ruleFn === 'function' ? ruleFn($v) : null
27
+ if (!rules) return [...mw, h]
28
+
29
+ return [...mw, ...$v.parse(rules), h]
30
+ }
@@ -0,0 +1,40 @@
1
+ import { Ability } from '$/auth'
2
+ import type { Req, Res, Next } from '@/.'
3
+
4
+ import { RegExpRouter } from 'hono/router/reg-exp-router'
5
+ import { SmartRouter } from 'hono/router/smart-router'
6
+ import { TrieRouter } from 'hono/router/trie-router'
7
+
8
+ let used = false
9
+ export const useAuth = () => used
10
+
11
+ const whiteList = new SmartRouter({
12
+ routers: [new RegExpRouter(), new TrieRouter()],
13
+ })
14
+
15
+ export function autorizedPath(path: string, method = 'ALL'){
16
+ return used ? whiteList.match(method, path)[0][0]?.[0] !== undefined : true
17
+ }
18
+
19
+ export function auth(publicPaths: (string | [string, string])[]) {
20
+ if (!used) {
21
+ used = true
22
+ for (const route of publicPaths) {
23
+ const str = typeof route === 'string'
24
+ const path = str ? route : route[1]
25
+
26
+ whiteList.add(str ? 'ALL' : route[0], path, path)
27
+ }
28
+ }
29
+
30
+ return async (req: Req, res: Res, next: Next) => {
31
+ if (!autorizedPath(req.path, req.method)) {
32
+ const ability = Ability.fromRequest(req)
33
+
34
+ if (!req?.user || !ability || req.cant(ability))
35
+ return res.unauthorized()
36
+ }
37
+
38
+ await next()
39
+ }
40
+ }
@@ -0,0 +1,30 @@
1
+ import { logger } from 'hono/logger'
2
+ import { matchedRoutes } from 'hono/route'
3
+ import { Datte } from 't0n'
4
+ import { gray } from 't0n/color'
5
+ import type { Req, Res, Context, Next } from '@/.'
6
+
7
+ export const path = '*'
8
+ export async function handle(req: Req, res: Res, next: Next, c: Context) {
9
+ const method = c.req.method
10
+ const route = matchedRoutes(c).find(route => route.method === method)?.path
11
+ const logWithRoute = (args: string[]) => {
12
+ if (!route || !args.length) return args
13
+ return args.map(arg => {
14
+ if (!arg) return arg
15
+ const split = arg?.split(' ')
16
+ if (split.length < 3 || split[2] === route)
17
+ return arg
18
+
19
+ split.splice(Math.min(3, split.length), 0, gray(route))
20
+ return split.join(' ')
21
+ })
22
+ }
23
+
24
+ const devLogger = logger((...args: any[]) => {
25
+ const timestamp = gray(Datte.dateTime())
26
+ console.log(timestamp, ...logWithRoute(args))
27
+ })
28
+
29
+ await devLogger(c, next)
30
+ }
@@ -0,0 +1,29 @@
1
+ import { basicAuth } from 'hono/basic-auth'
2
+ import response from '$/response'
3
+ import { getConfig } from '$/config'
4
+ import type { Hono } from '$/types'
5
+
6
+ export function registerOpenAPI(app: Hono, opts: any) {
7
+ if (!opts.path) return
8
+
9
+ if (opts?.auth?.username && opts?.auth?.password) {
10
+ app.use(opts.path +'/*', async (c, next) => {
11
+ const realm = opts.auth?.realm || 'Docs'
12
+ const unauthorized = response.unauthorized(
13
+ null,
14
+ {'WWW-Authenticate': `Basic realm="${realm.replace(/"/g, '\\"')}", charset="UTF-8"`}
15
+ )
16
+ if (!c.req.raw.headers.get('Authorization')) return unauthorized
17
+ const auth = basicAuth({ username: opts.auth.username, password: opts.auth.password, realm })
18
+
19
+ try {
20
+ await auth(c, next)
21
+ } catch {
22
+ return unauthorized
23
+ }
24
+ })
25
+ }
26
+
27
+ app.get(opts.path, async () => response.html(getConfig('DUTO_OAC')))
28
+ app.get(opts.path +'/openapi', async () => response.raw(200, getConfig('DUTO_OAS'), 'application/json'))
29
+ }