rajt 0.0.17 → 0.0.18

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rajt",
3
3
  "description": "A serverless bundler layer, fully typed for AWS Lambda (Node.js and LLRT) and Cloudflare Workers.",
4
- "version": "0.0.17",
4
+ "version": "0.0.18",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "exports": {
@@ -17,7 +17,7 @@
17
17
  "local": "bun run --silent build && bun run --silent sam:local",
18
18
  "build": "bun run --silent cache:routes && bun run --silent export && bun run --silent clean:temp",
19
19
  "build:watch": "chokidar \"../../{actions,configs,models,utils}/**/*.ts\" -c \"bun run --silent build\" --initial",
20
- "export": "esbuild --bundle --minify --outfile=../../dist/index.js --platform=node --target=node20 --format=esm --tree-shaking=true --legal-comments=none src/prod.ts",
20
+ "export": "esbuild --bundle --minify --outfile=../../dist/index.js --platform=node --target=node20 --format=esm --tree-shaking=true --legal-comments=none --external:@aws-sdk --external:@smithy src/prod.ts",
21
21
  "package": "bun run --silent build && bun run --silent sam:package",
22
22
  "deploy": "bun run --silent build && bun run --silent sam:package && bun run --silent sam:deploy",
23
23
  "update": "bun run --silent build && bun run --silent zip && bun run --silent sam:update",
@@ -34,13 +34,13 @@
34
34
  "start": "node ../../dist/index.js"
35
35
  },
36
36
  "dependencies": {
37
- "@aws-lite/client": "^0.23.2",
38
- "@aws-lite/dynamodb": "^0.3.9",
39
- "@aws-lite/dynamodb-types": "^0.3.11",
37
+ "@aws-sdk/client-dynamodb": "3.817.0",
38
+ "@aws-sdk/lib-dynamodb": "3.817.0",
40
39
  "@hono/node-server": "^1.14.1",
41
40
  "@hono/zod-validator": "^0.4.3",
42
41
  "@types/node": "^20.11.0",
43
42
  "chokidar-cli": "^3.0.0",
43
+ "cripta": "^0.1.6",
44
44
  "dotenv": "^16.5.0",
45
45
  "esbuild": "^0.25.2",
46
46
  "hono": "^4.7.6",
@@ -49,6 +49,9 @@
49
49
  "tsx": "^4.19.3",
50
50
  "typescript": "^5.8.3"
51
51
  },
52
+ "resolutions": {
53
+ "@smithy/types": "^4.3.0"
54
+ },
52
55
  "publishConfig": {
53
56
  "registry": "https://registry.npmjs.org"
54
57
  },
package/src/action.ts CHANGED
@@ -2,7 +2,6 @@ import { Context, Handler, HonoRequest, MiddlewareHandler, Next, ValidationTarge
2
2
  import { z, ZodObject, ZodRawShape } from 'zod'
3
3
  import { zValidator } from '@hono/zod-validator'
4
4
  // import { JSONValue } from 'hono/utils/types'
5
- import json from './response'
6
5
  import JsonResponse from './response'
7
6
  import { bufferToFormData } from 'hono/utils/buffer'
8
7
  import { HTTPException } from 'hono/http-exception'
@@ -26,7 +25,7 @@ export default abstract class Action {
26
25
  rule<T extends keyof ValidationTargets>(target: T): { schema: (schema: ZodObject<any>) => RuleDefinition }
27
26
  rule<T extends keyof ValidationTargets>(target: T, schema: ZodObject<any>): RuleDefinition
28
27
 
29
- public rule<T extends keyof ValidationTargets>(target: T, schema?: ZodObject<any>):
28
+ rule<T extends keyof ValidationTargets>(target: T, schema?: ZodObject<any>):
30
29
  | { schema: (schema: ZodObject<any>) => RuleDefinition }
31
30
  | RuleDefinition
32
31
  {
@@ -47,15 +46,15 @@ export default abstract class Action {
47
46
  }
48
47
  }
49
48
 
50
- public param(key: string) {
49
+ param(key: string) {
51
50
  return this.context.req.param(key)
52
51
  }
53
52
 
54
- public query() {
53
+ query() {
55
54
  return this.context.req.query()
56
55
  }
57
56
 
58
- public async form(cType?: string) {
57
+ async form(cType?: string) {
59
58
  cType ??= this.context.req.header('Content-Type')
60
59
  if (!cType) return {}
61
60
 
@@ -92,7 +91,7 @@ export default abstract class Action {
92
91
  return form
93
92
  }
94
93
 
95
- public async json<E>() {
94
+ async json<E>() {
96
95
  try {
97
96
  return await this.context.req.json<E>()
98
97
  } catch {
@@ -100,7 +99,7 @@ export default abstract class Action {
100
99
  }
101
100
  }
102
101
 
103
- public async body<E>() {
102
+ async body<E>() {
104
103
  const cType = this.context.req.header('Content-Type')
105
104
  if (!cType) return {}
106
105
 
@@ -118,11 +117,11 @@ export default abstract class Action {
118
117
  return {}
119
118
  }
120
119
 
121
- public get response() {
120
+ get response() {
122
121
  return this.context ? JsonResponse.setContext(this.context) : JsonResponse
123
122
  }
124
123
 
125
- public validate() {
124
+ validate() {
126
125
  const rules = this.rules()
127
126
  const h = async (c: Context) => {
128
127
  this.context = c
@@ -134,7 +133,7 @@ export default abstract class Action {
134
133
  .map(rule => zValidator(rule.target, rule.schema, (result, c) => {
135
134
  if (!result.success) {
136
135
  // @ts-ignore
137
- return json.badRequest({ ...result.error.flatten()[rule.eTarget] })
136
+ return JsonResponse.badRequest({ ...result.error.flatten()[rule.eTarget] })
138
137
  }
139
138
  }))
140
139
 
@@ -143,7 +142,26 @@ export default abstract class Action {
143
142
  return rulesArray
144
143
  }
145
144
 
146
- public run() {
145
+ get auth() {
146
+ const auth = this.context.get('#auth')
147
+ return auth ? auth?.data : null
148
+ }
149
+
150
+ can(...abilities: string[]): boolean {
151
+ const auth = this.context.get('#auth')
152
+ return auth ? auth.can(...abilities) : false
153
+ }
154
+
155
+ cant(...abilities: string[]): boolean {
156
+ return !this.can(...abilities)
157
+ }
158
+
159
+ hasRole(...roles: string[]): boolean {
160
+ const auth = this.context.get('#auth')
161
+ return auth ? auth.hasRole(...roles) : false
162
+ }
163
+
164
+ run() {
147
165
  return this.validate()
148
166
  }
149
167
 
@@ -0,0 +1,51 @@
1
+ import { Routes } from '../types'
2
+ import { Roles, Abilities } from './types'
3
+
4
+ export class Ability {
5
+ static #roles: Roles = {}
6
+ static #abilities: Abilities = []
7
+
8
+ static empty() {
9
+ this.#roles = {}
10
+ this.#abilities = []
11
+ }
12
+
13
+ static fromRoutes(actions: Routes) {
14
+ if (!actions?.length) return
15
+
16
+ const paths = actions?.map(a => Array.isArray(a) ? a[1] : a.path) ?? []
17
+ const items = new Set(paths)
18
+
19
+ if (items.size !== actions.length)
20
+ throw new Error(`Duplicate routes detected: "${paths.filter((path, index) => paths.indexOf(path) !== index).join('", "')}"`)
21
+
22
+ this.#abilities = Array.from(items).map(a => this.format(a)).filter(Boolean)
23
+ }
24
+
25
+ static fromAction(target: any): string | null {
26
+ return !target || !target?.p ? null : this.format(target.p)
27
+ }
28
+
29
+ static format(path: string) {
30
+ return path.normalize('NFD')
31
+ .replace(/[\u0300-\u036f]/g, '')
32
+ .replace(/^\/*/, '')
33
+ .replace(/[^a-zA-Z0-9/]|[\s_.]/g, '-')
34
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
35
+ .replace(/\//g, '.')
36
+ .replace(/-+/g, '-')
37
+ .toLowerCase()
38
+ }
39
+
40
+ static get abilities() {
41
+ return this.#abilities
42
+ }
43
+
44
+ static get roles() {
45
+ return this.#roles
46
+ }
47
+
48
+ static set roles(roles: Roles) {
49
+ this.#roles = roles
50
+ }
51
+ }
@@ -0,0 +1,72 @@
1
+ import { Ability } from './ability'
2
+
3
+ type Authenticatable = {
4
+ role?: string,
5
+ roles?: string[],
6
+ perms?: string[],
7
+ }
8
+
9
+ export class Authnz<T extends object> {
10
+ #abilities: string[]
11
+ #roles: string[]
12
+ #data: T
13
+
14
+ constructor(data: T, abilities: string[], roles: string[]) {
15
+ this.#abilities = abilities
16
+ this.#roles = roles
17
+ this.#data = data
18
+ }
19
+
20
+ can(...abilities: string[]): boolean {
21
+ if (this.#abilities.includes('*')) return true
22
+
23
+ return abilities.flat().every(ability => {
24
+ if (this.#roles.includes(ability)) return true
25
+ return this.#abilities.some(rule => this.#match(rule, ability))
26
+ })
27
+ }
28
+ cant(...abilities: string[]): boolean {
29
+ return !this.can(...abilities)
30
+ }
31
+
32
+ hasRole(...roles: string[]): boolean {
33
+ return roles.flat().every(role => this.#roles.includes(role))
34
+ }
35
+
36
+ static fromToken<T extends object>(user: any): Authnz<T> | null {
37
+ if (!user || user?.isInvalid()) return null
38
+ user = user.get()
39
+ const roles = [...(user?.role ? [user.role] : []), ...(user?.roles ?? [])]
40
+
41
+ const combined = [...(user?.perms ?? []), ...roles.flatMap(role => {
42
+ const perms = Ability.roles[role]
43
+ if (!perms) return []
44
+ return perms === '*' ? ['*'] : perms;
45
+ })]
46
+
47
+ const abilities = combined.includes('*') ? ['*'] : Array.from(new Set(combined))
48
+
49
+ return new Authnz(user as T, abilities, roles)
50
+ }
51
+
52
+ #match(rule: string, ability: string): boolean {
53
+ if (rule === ability) return true
54
+ if (rule.endsWith('.*')) {
55
+ const prefix = rule.slice(0, -2)
56
+ return ability.startsWith(`${prefix}.`) || ability === prefix
57
+ }
58
+ return false
59
+ }
60
+
61
+ get abilities() {
62
+ return this.#abilities
63
+ }
64
+
65
+ get roles() {
66
+ return this.#roles
67
+ }
68
+
69
+ get data() {
70
+ return this.#data
71
+ }
72
+ }
@@ -0,0 +1,5 @@
1
+ export { Ability } from './ability'
2
+ export { Authnz } from './auth'
3
+ export { Token } from './token'
4
+
5
+ export * from './types'
@@ -0,0 +1,80 @@
1
+ import { Envir } from 't0n'
2
+ import { Token as Factory } from 'cripta'
3
+ import type { Context, HonoRequest, Next } from 'hono'
4
+
5
+ export class Token {
6
+ static #name: string = 'Authorization'
7
+ static #prefix: string = 'bearer'
8
+
9
+ static fromRequest(c: Context) {
10
+ // const token = this.fromHeader(c.req)
11
+ // Mock user
12
+ const token = this.create(c.req, {
13
+ name: 'Nicolau',
14
+ email: 'nicolau@zunq.com',
15
+ role: 'user',
16
+ }).get()
17
+
18
+ return token ? this.parse(c.req, token) : null
19
+ }
20
+
21
+ static fromHeader(req: HonoRequest): string | null {
22
+ const header = req.header(this.#name) || req.header('HTTP_AUTHORIZATION') || req.header('REDIRECT_HTTP_AUTHORIZATION') || null
23
+
24
+ if (header) {
25
+ const position = header.toLowerCase().indexOf(this.#prefix.toLowerCase())
26
+ if (position !== -1) {
27
+ let token = header.slice(position + this.#prefix.length).trim()
28
+ const commaPos = token.indexOf(',')
29
+ if (commaPos !== -1) token = token.slice(0, commaPos).trim()
30
+
31
+ return token
32
+ }
33
+ }
34
+
35
+ return null
36
+ }
37
+
38
+ static parse(req: HonoRequest, token: string) {
39
+ const host = this.host(Envir.get('FLOW_SERVER') || req.header('host') || '')
40
+
41
+ return Factory.parse(token)
42
+ .issuedBy(host)
43
+ .permittedFor(host)
44
+ }
45
+
46
+ static create(req: HonoRequest, user: any, exp: number = 7200) {
47
+ const time = Math.floor(Date.now() / 1000)
48
+ const host = this.host(req.header('host') || '')
49
+
50
+ return Factory.create()
51
+ .issuedBy(host)
52
+ .permittedFor(host)
53
+ .issuedAt(time)
54
+ .expiresAt(time + exp)
55
+ .body(user)
56
+ }
57
+
58
+ static setPrefix(prefix: string): void {
59
+ this.#prefix = prefix
60
+ }
61
+
62
+ static setName(name: string): void {
63
+ this.#name = name
64
+ }
65
+
66
+ static host(url: string | null | undefined): string {
67
+ if (!url) return ''
68
+
69
+ let formattedUrl = String(url)
70
+ if (!formattedUrl.startsWith('http'))
71
+ formattedUrl = 'http://' + formattedUrl
72
+
73
+ try {
74
+ const parsedUrl = new URL(formattedUrl)
75
+ return parsedUrl.host
76
+ } catch (e) {
77
+ return ''
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,2 @@
1
+ export type Roles = Record<string, string[] | '*'>
2
+ export type Abilities = string[]
package/src/create-app.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import { Hono } from 'hono'
3
3
  import type { Env, Context, ErrorHandler, NotFoundHandler, Next } from 'hono'
4
- // import type { Env, Context, ErrorHandler, MiddlewareHandler, NotFoundHandler, Next } from 'hono'
4
+ // import type { MiddlewareHandler } from 'hono'
5
5
  // import { createMiddleware } from 'hono/factory'
6
6
  // import type { H, Handler, HandlerResponse } from 'hono/types'
7
7
  import { HTTPResponseError } from 'hono/types'
8
8
  import { Routes } from './types'
9
9
  import { BadRequest, Unauthorized } from './exceptions'
10
10
  import response from './response'
11
- import resolve from './utils/resolve'
12
- import { getHandler } from './register'
11
+ import { resolve, resolveMiddleware } from './utils/resolve'
12
+ import { getGlobalMiddlewares, getHandler } from './register'
13
13
  import env from './utils/environment'
14
14
 
15
15
  type InitFunction<E extends Env = Env> = (app: Hono<E>) => void
@@ -90,8 +90,14 @@ export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
90
90
  response.setContext(c)
91
91
  await next()
92
92
  },
93
+ ...getGlobalMiddlewares()
93
94
  ]
94
- middlewares.forEach(middleware => app.use(middleware))
95
+ middlewares.forEach(mw => {
96
+ // @ts-ignore
97
+ const h = resolveMiddleware(mw)
98
+ // @ts-ignore
99
+ mw?.p ? app.use(String(mw.p), h) : app.use(h)
100
+ })
95
101
 
96
102
  app.onError(options?.onError || EHandler)
97
103
  app.notFound(options?.notFound || NFHandler)
package/src/dev.ts CHANGED
@@ -2,18 +2,23 @@ import { config } from 'dotenv'
2
2
  import { serve } from '@hono/node-server'
3
3
  import createApp from './create-app'
4
4
  import getRoutes from './routes'
5
+ import { Ability } from './auth'
5
6
  import { getAvailablePort } from './utils/port'
7
+ import jsonImport from './utils/json-import'
6
8
 
7
9
  config({ path: '../../.env.dev' })
8
10
 
9
11
  const routes = await getRoutes()
12
+ Ability.fromRoutes(routes)
13
+ Ability.roles = jsonImport('../../../../.rolefile')
14
+
10
15
  const fetch = createApp({ routes }).fetch
11
16
 
12
17
  const desiredPort = process.env?.PORT ? Number(process.env.PORT) : 3000
13
18
  getAvailablePort(desiredPort)
14
19
  .then(port => {
15
20
  if (port != desiredPort)
16
- console.log(`🟨 Port ${desiredPort} was in use, using ${port} as a fallback`)
21
+ console.warn(`🟨 Port ${desiredPort} was in use, using ${port} as a fallback`)
17
22
 
18
23
  console.log(`🚀 API running on http://localhost:${port}`)
19
24
  serve({ fetch, port })
@@ -1,17 +1,20 @@
1
- import awsLite from '@aws-lite/client'
2
- import dynamodbLite from '@aws-lite/dynamodb'
1
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
2
+ import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'
3
3
  import AbstractModel from './model'
4
4
 
5
- export const aws = await awsLite({
6
- plugins: [dynamodbLite],
7
- // https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-lib-dynamodb/#configuration
8
- // @ts-ignore
9
- awsjsonMarshall: {convertClassInstanceToMap: true},
10
- awsjsonUnmarshall: {convertClassInstanceToMap: true}
11
- })
5
+ const client = new DynamoDBClient(process.env?.AWS_SAM_LOCAL ? {
6
+ region: process.env.AWS_REGION || "us-east-1",
7
+ endpoint: process.env.AWS_ENDPOINT_URL || undefined,
8
+ credentials: {
9
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID || "DUMMYIDEXAMPLE",
10
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || "DUMMYEXAMPLEKEY",
11
+ },
12
+ } : {})
13
+
14
+ export const ddb = DynamoDBDocumentClient.from(client)
12
15
 
13
16
  export class Dynamodb {
14
17
  static model<T extends object>(cls: new (...args: any[]) => T) {
15
- return new AbstractModel<T>(cls, aws.DynamoDB)
18
+ return new AbstractModel<T>(cls, ddb)
16
19
  }
17
20
  }
@@ -2,7 +2,7 @@ import type { SchemaStructure } from './types'
2
2
  import getLength from '../utils/lenght'
3
3
 
4
4
  export default class Compact {
5
- private static typeMap: Record<string, string> = {
5
+ static #typeMap: Record<string, string> = {
6
6
  // Null
7
7
  'null,': 'N,',
8
8
  ',null': ',N',
@@ -33,7 +33,7 @@ export default class Compact {
33
33
  .replace(/'/g, '"')
34
34
  .replace(/~TDQ~/g, "'")
35
35
  .replace(/\\'/g, "^'"),
36
- this.typeMap
36
+ this.#typeMap
37
37
  )
38
38
  }
39
39
 
@@ -49,7 +49,7 @@ export default class Compact {
49
49
  static decode<T = any>(val: string, schema: SchemaStructure): T {
50
50
  if (!val) return val as T
51
51
 
52
- val = this.replaceTypes(val, this.reverseMap(this.typeMap))
52
+ val = this.replaceTypes(val, this.reverseMap(this.#typeMap))
53
53
  .replace(/"/g, '~TSQ~')
54
54
  .replace(/'/g, '"')
55
55
  .replace(/~TSQ~/g, "'")
@@ -1,4 +1,14 @@
1
- import type { AwsLiteDynamoDB } from '@aws-lite/dynamodb-types'
1
+ import {
2
+ DynamoDBDocumentClient,
3
+ BatchGetCommand,
4
+ BatchWriteCommand,
5
+ DeleteCommand,
6
+ GetCommand,
7
+ PutCommand,
8
+ QueryCommand,
9
+ ScanCommand,
10
+ UpdateCommand,
11
+ } from '@aws-sdk/lib-dynamodb'
2
12
  import type { ModelMetadata, Keys, Model, Filter } from './types'
3
13
  import { getModelMetadata } from './decorators'
4
14
  import QueryBuilder from './query-builder'
@@ -6,18 +16,25 @@ import Compact from './compact'
6
16
  import getLength from '../utils/lenght'
7
17
 
8
18
  export default class AbstractModel<T extends object> {
9
- private meta: ModelMetadata
10
- private cls!: Model<T>
11
- private lastKey?: Record<string, any>
19
+ #meta: ModelMetadata
20
+ #cls!: Model<T>
21
+ lastKey?: Record<string, any>
22
+ #db: DynamoDBDocumentClient
23
+ #queryBuilder?: QueryBuilder
24
+ #model?: AbstractModel<T>
12
25
 
13
26
  constructor(
14
27
  cls: Model<T> | ModelMetadata,
15
- private db: AwsLiteDynamoDB,
16
- private queryBuilder?: QueryBuilder,
17
- private model?: AbstractModel<T>
28
+ db: DynamoDBDocumentClient,
29
+ queryBuilder?: QueryBuilder,
30
+ model?: AbstractModel<T>
18
31
  ) {
32
+ this.#db = db
33
+ this.#queryBuilder = queryBuilder
34
+ this.#model = model
35
+
19
36
  if (typeof (cls as ModelMetadata).table === 'string') {
20
- this.meta = cls as ModelMetadata
37
+ this.#meta = cls as ModelMetadata
21
38
  return
22
39
  }
23
40
 
@@ -25,21 +42,21 @@ export default class AbstractModel<T extends object> {
25
42
  if (!meta)
26
43
  throw new Error('Missing model metadata')
27
44
 
28
- this.meta = meta
29
- this.cls = cls as Model<T>
45
+ this.#meta = meta
46
+ this.#cls = cls as Model<T>
30
47
  }
31
48
 
32
49
  get table(): string {
33
- return this.meta.table
50
+ return this.#meta.table
34
51
  }
35
52
 
36
53
  get keySchema() {
37
- return this.meta.keys
54
+ return this.#meta.keys
38
55
  }
39
56
 
40
57
  set lastEvaluatedKey(val: Record<string, any> | undefined) {
41
- if (this.model) {
42
- this.model.lastKey = val
58
+ if (this.#model) {
59
+ this.#model.lastKey = val
43
60
  } else {
44
61
  this.lastKey = val
45
62
  }
@@ -51,52 +68,61 @@ export default class AbstractModel<T extends object> {
51
68
  where(builderFn: (q: QueryBuilder) => void) {
52
69
  const qb = new QueryBuilder()
53
70
  builderFn(qb)
54
- return new AbstractModel<T>(this.meta, this.db, qb, this)
71
+ return new AbstractModel<T>(this.#meta, this.#db, qb, this)
55
72
  }
56
73
 
57
74
  async scan(filterFn?: Filter<T>) {
58
- const result = await this.db.Scan({ TableName: this.table, ...this.queryBuilder?.filters })
75
+ const result = await this.#db.send(new ScanCommand({
76
+ TableName: this.table,
77
+ ...this.#queryBuilder?.filters,
78
+ }))
59
79
 
60
80
  this.lastEvaluatedKey = result.LastEvaluatedKey
61
- return this.processItems(result.Items, filterFn)
81
+ return this.#processItems(result.Items, filterFn)
62
82
  }
63
83
 
64
84
  async query(filterFn?: Filter<T>) {
65
- const result = await this.db.Query({ TableName: this.table, ...this.queryBuilder?.conditions })
85
+ const result = await this.#db.send(new QueryCommand({
86
+ TableName: this.table,
87
+ ...this.#queryBuilder?.conditions,
88
+ }))
66
89
 
67
90
  this.lastEvaluatedKey = result.LastEvaluatedKey
68
- return this.processItems(result.Items, filterFn)
91
+ return this.#processItems(result.Items, filterFn)
69
92
  }
70
93
 
71
94
  async get(key: Keys, sk?: string) {
72
- const result = await this.db.GetItem({ TableName: this.table, Key: this.key(key, sk) })
73
- return result.Item ? this.processItem(result.Item) : undefined
95
+ const result = await this.#db.send(new GetCommand({
96
+ TableName: this.table,
97
+ Key: this.#key(key, sk),
98
+ }))
99
+ return result.Item ? this.#processItem(result.Item) : undefined
74
100
  }
75
101
 
76
102
  async put(item: Partial<T>, key: Keys) {
77
103
  let keys
78
- if (this.meta.zip) {
79
- keys = this.getItemKey(item, key)
80
- this.validateKeys(keys)
104
+ if (this.#meta.zip) {
105
+ keys = this.#getItemKey(item, key)
106
+ this.#validateKeys(keys)
81
107
  // @ts-ignore
82
- item = { ...keys, V: Compact.encode(this.getItemWithoutKeys(item), this.meta.fields)}
108
+ item = { ...keys, V: Compact.encode(this.getItemWithoutKeys(item), this.meta.fields) }
83
109
  } else {
84
- this.validateKeys(item)
110
+ this.#validateKeys(item)
85
111
  }
86
112
 
87
- await this.db.PutItem({ TableName: this.table, Item: item })
88
- return this.processItem(item, keys)
113
+ await this.#db.send(new PutCommand({ TableName: this.table, Item: item }))
114
+ return this.#processItem(item, keys)
89
115
  }
90
116
 
91
117
  async update(attrs: Partial<T>, key: Keys) {
92
118
  let keys
93
- if (this.meta.zip) {
94
- keys = this.getItemKey(attrs, key)
95
- this.validateKeys(keys)
119
+ if (this.#meta.zip) {
120
+ keys = this.#getItemKey(attrs, key)
121
+ this.#validateKeys(keys)
96
122
  // @ts-ignore
97
- attrs = { V: Compact.encode(this.getItemWithoutKeys(attrs), this.meta.fields)}
123
+ attrs = { V: Compact.encode(this.getItemWithoutKeys(attrs), this.meta.fields) }
98
124
  } else {
99
- this.validateKeys(attrs)
125
+ this.#validateKeys(attrs)
100
126
  }
101
127
 
102
128
  const UpdateExpressionParts: string[] = []
@@ -108,26 +134,29 @@ export default class AbstractModel<T extends object> {
108
134
  const UpdateExpression = 'SET ' + UpdateExpressionParts.join(', ')
109
135
  const ExpressionAttributeNames = Object.fromEntries(Object.keys(attrs).map(k => [`#${k}`, k]))
110
136
 
111
- await this.db.UpdateItem({
137
+ await this.#db.send(new UpdateCommand({
112
138
  TableName: this.table,
113
- Key: this.key(key),
139
+ Key: this.#key(key),
114
140
  UpdateExpression,
115
141
  ExpressionAttributeValues,
116
- ExpressionAttributeNames
117
- })
142
+ ExpressionAttributeNames,
143
+ }))
118
144
 
119
- return this.processItem(attrs, keys)
145
+ return this.#processItem(attrs, keys)
120
146
  }
121
147
 
122
148
  async delete(key: Keys, sk?: string) {
123
- return this.db.DeleteItem({ TableName: this.table, Key: this.key(key, sk) })
149
+ return this.#db.send(new DeleteCommand({
150
+ TableName: this.table,
151
+ Key: this.#key(key, sk),
152
+ }))
124
153
  }
125
154
 
126
155
  async batchGet(keys: Array<Keys>) {
127
- const result = await this.db.BatchGetItem({ RequestItems: {
128
- [this.table]: { Keys: keys.map(key => this.key(key)) }
129
- } })
130
- return (result.Responses?.[this.table] as T[] || []).map(item => this.processItem(item))
156
+ const result = await this.#db.send(new BatchGetCommand({
157
+ RequestItems: { [this.table]: { Keys: keys.map(key => this.#key(key)) } },
158
+ }))
159
+ return (result.Responses?.[this.table] as T[] || []).map(item => this.#processItem(item))
131
160
  }
132
161
 
133
162
  async batchWrite(items: Array<{ put?: Partial<T>, delete?: Keys }>) {
@@ -135,12 +164,12 @@ export default class AbstractModel<T extends object> {
135
164
  if (i.put) {
136
165
  return { PutRequest: { Item: i.put } }
137
166
  } else if (i.delete) {
138
- return { DeleteRequest: { Key: this.key(i.delete) } }
167
+ return { DeleteRequest: { Key: this.#key(i.delete) } }
139
168
  }
140
169
  return null
141
- }).filter(Boolean)
170
+ }).filter(Boolean) as any[]
142
171
 
143
- return this.db.BatchWriteItem({ RequestItems: {[this.table]: WriteRequests} })
172
+ return this.#db.send(new BatchWriteCommand({ RequestItems: { [this.table]: WriteRequests } }))
144
173
  }
145
174
 
146
175
  async deleteMany(keys: Array<Keys>) {
@@ -151,8 +180,8 @@ export default class AbstractModel<T extends object> {
151
180
  return this.batchWrite(items.map(item => ({ put: item })))
152
181
  }
153
182
 
154
- private key(key: Keys, sk?: string) {
155
- if (!this.meta.keys) return {}
183
+ #key(key: Keys, sk?: string) {
184
+ if (!this.#meta.keys) return {}
156
185
 
157
186
  let pk: string
158
187
  let skValue: string | undefined
@@ -164,100 +193,100 @@ export default class AbstractModel<T extends object> {
164
193
  skValue = sk
165
194
  }
166
195
 
167
- const keys = { [this.meta.keys.PK]: pk }
196
+ const keys = { [this.#meta.keys.PK]: pk }
168
197
 
169
- if (this.meta.keys?.SK) {
198
+ if (this.#meta.keys?.SK) {
170
199
  if (skValue) {
171
- keys[this.meta.keys.SK] = skValue
172
- } else if (this.meta.defaultSK) {
173
- keys[this.meta.keys.SK] = this.meta.defaultSK
200
+ keys[this.#meta.keys.SK] = skValue
201
+ } else if (this.#meta.defaultSK) {
202
+ keys[this.#meta.keys.SK] = this.#meta.defaultSK
174
203
  }
175
204
  }
176
205
 
177
206
  return keys
178
207
  }
179
208
 
180
- private getItemKey(item: Partial<T>, key?: Keys): Record<string, string> {
181
- if (!this.meta.keys) return {}
209
+ #getItemKey(item: Partial<T>, key?: Keys): Record<string, string> {
210
+ if (!this.#meta.keys) return {}
182
211
 
183
212
  const keys: Record<string, string> = {}
184
213
  if (key)
185
- this.processExplicitKey(keys, key)
214
+ this.#processExplicitKey(keys, key)
186
215
  else if (getLength(item) > 0)
187
- this.processItemKeys(keys, item)
216
+ this.#processItemKeys(keys, item)
188
217
 
189
218
  return keys
190
219
  }
191
220
 
192
- private processExplicitKey(keys: Record<string, string>, key: Keys): void {
193
- if (!this.meta.keys) return
221
+ #processExplicitKey(keys: Record<string, string>, key: Keys): void {
222
+ if (!this.#meta.keys) return
194
223
  if (Array.isArray(key)) {
195
- keys[this.meta.keys.PK] = key[0]
224
+ keys[this.#meta.keys.PK] = key[0]
196
225
 
197
- if (this.meta.keys?.SK) {
226
+ if (this.#meta.keys?.SK) {
198
227
  if (key.length > 1)
199
228
  // @ts-ignore
200
229
  keys[this.meta.keys.SK] = key[1]
201
- else if (this.meta.defaultSK)
202
- keys[this.meta.keys.SK] = this.meta.defaultSK
230
+ else if (this.#meta.defaultSK)
231
+ keys[this.#meta.keys.SK] = this.#meta.defaultSK
203
232
  }
204
233
  } else {
205
- keys[this.meta.keys.PK] = String(key)
234
+ keys[this.#meta.keys.PK] = String(key)
206
235
  }
207
236
  }
208
237
 
209
- private processItemKeys(keys: Record<string, string>, item: Partial<T>): void {
210
- if (!this.meta.keys) return
238
+ #processItemKeys(keys: Record<string, string>, item: Partial<T>): void {
239
+ if (!this.#meta.keys) return
211
240
 
212
- const pkValue = item[this.meta.keys.PK as keyof Partial<T>]
241
+ const pkValue = item[this.#meta.keys.PK as keyof Partial<T>]
213
242
  if (pkValue !== undefined)
214
- keys[this.meta.keys.PK] = String(pkValue)
243
+ keys[this.#meta.keys.PK] = String(pkValue)
215
244
 
216
- if (this.meta.keys?.SK) {
217
- const skValue = item[this.meta.keys.SK as keyof Partial<T>]
245
+ if (this.#meta.keys?.SK) {
246
+ const skValue = item[this.#meta.keys.SK as keyof Partial<T>]
218
247
  if (skValue !== undefined)
219
- keys[this.meta.keys.SK] = String(skValue)
220
- else if (this.meta.defaultSK)
221
- keys[this.meta.keys.SK] = this.meta.defaultSK
248
+ keys[this.#meta.keys.SK] = String(skValue)
249
+ else if (this.#meta.defaultSK)
250
+ keys[this.#meta.keys.SK] = this.#meta.defaultSK
222
251
  }
223
252
  }
224
253
 
225
- private validateKeys(keys: Record<string, any>) {
226
- if (!this.meta.keys)
254
+ #validateKeys(keys: Record<string, any>) {
255
+ if (!this.#meta.keys)
227
256
  throw new Error(`Missing keys of table "${this.table}"`)
228
257
 
229
- if (!(this.meta.keys.PK in keys))
258
+ if (!(this.#meta.keys.PK in keys))
230
259
  throw new Error(`Missing partition key of table "${this.table}" `)
231
260
 
232
- if (this.meta.keys?.SK && !(this.meta.keys.SK in keys))
261
+ if (this.#meta.keys?.SK && !(this.#meta.keys.SK in keys))
233
262
  throw new Error(`Missing sort key of table "${this.table}"`)
234
263
  }
235
264
 
236
- private getItemWithoutKeys(item: Partial<T>): Partial<T> {
237
- if (!this.meta.keys || !item) return { ...item }
265
+ #getItemWithoutKeys(item: Partial<T>): Partial<T> {
266
+ if (!this.#meta.keys || !item) return { ...item }
238
267
 
239
- const { PK, SK } = this.meta.keys
268
+ const { PK, SK } = this.#meta.keys
240
269
  const { [PK as keyof T]: _, [SK as keyof T]: __, ...rest } = item
241
270
 
242
271
  return rest as Partial<T>
243
272
  }
244
273
 
245
- private processItems(items: any[] | undefined, filterFn?: Filter<T>): T[] {
274
+ #processItems(items: any[] | undefined, filterFn?: Filter<T>): T[] {
246
275
  if (!items) return []
247
276
 
248
- items = this.meta.zip ? Compact.smartDecode<T[]>(items, this.meta.fields) : items as T[]
277
+ items = this.#meta.zip ? Compact.smartDecode<T[]>(items, this.#meta.fields) : items as T[]
249
278
  return filterFn ? items.filter(filterFn) : items
250
279
  }
251
280
 
252
- private processItem(item: any, keys?: Record<string, string>): T {
253
- if (this.meta.zip && item?.V) {
254
- const model = new this.cls(Compact.decode(item.V, this.meta.fields))
255
- if (!keys) keys = this.getItemKey(item)
281
+ #processItem(item: any, keys?: Record<string, string>): T {
282
+ if (this.#meta.zip && item?.V) {
283
+ const model = new this.#cls(Compact.decode(item.V, this.#meta.fields))
284
+ if (!keys) keys = this.#getItemKey(item)
256
285
 
257
286
  // @ts-ignore
258
287
  return model.withKey(keys[this.meta.keys.PK], keys[this.meta.keys.SK] || undefined)
259
288
  }
260
289
 
261
- return new this.cls(item)
290
+ return new this.#cls(item)
262
291
  }
263
292
  }
@@ -1,34 +1,34 @@
1
1
  import type { Condition, Operator } from './types'
2
2
 
3
3
  export default class QueryBuilder {
4
- private _conditions: Condition[] = []
5
- private _limit?: number
6
- private _startKey?: Record<string, any>
7
- private _index?: string
4
+ #conditions: Condition[] = []
5
+ #limit?: number
6
+ #startKey?: Record<string, any>
7
+ #index?: string
8
8
 
9
9
  filter(field: string, operator: Operator, value: any = null) {
10
- this._conditions.push({ type: 'filter', field, operator, value })
10
+ this.#conditions.push({ type: 'filter', field, operator, value })
11
11
  return this
12
12
  }
13
13
 
14
14
  keyCondition(field: string, operator: Operator | any, value?: any) {
15
15
  const noVal = value === undefined
16
- this._conditions.push({ type: 'keyCondition', field, operator: noVal ? '=' : operator, value: noVal ? operator : value })
16
+ this.#conditions.push({ type: 'keyCondition', field, operator: noVal ? '=' : operator, value: noVal ? operator : value })
17
17
  return this
18
18
  }
19
19
 
20
20
  limit(n: number) {
21
- this._limit = n
21
+ this.#limit = n
22
22
  return this
23
23
  }
24
24
 
25
25
  exclusiveStartKey(key: Record<string, any>) {
26
- this._startKey = key
26
+ this.#startKey = key
27
27
  return this
28
28
  }
29
29
 
30
30
  index(name: string) {
31
- this._index = name
31
+ this.#index = name
32
32
  return this
33
33
  }
34
34
 
@@ -38,7 +38,7 @@ export default class QueryBuilder {
38
38
  const names: Record<string, string> = {}
39
39
 
40
40
  let i = 0
41
- for (const cond of this._conditions.filter(c => c.type === type)) {
41
+ for (const cond of this.#conditions.filter(c => c.type === type)) {
42
42
  const attr = `#attr${i}`
43
43
  const val = `:val${i}`
44
44
  names[attr] = cond.field
@@ -107,11 +107,11 @@ export default class QueryBuilder {
107
107
  const filter = this.buildExpression('filter')
108
108
  const params: any = {}
109
109
 
110
- if (this._limit)
111
- params.Limit = this._limit
110
+ if (this.#limit)
111
+ params.Limit = this.#limit
112
112
 
113
- if (this._startKey)
114
- params.ExclusiveStartKey = this._startKey
113
+ if (this.#startKey)
114
+ params.ExclusiveStartKey = this.#startKey
115
115
 
116
116
  if (filter.expression)
117
117
  params.FilterExpression = filter.expression
@@ -131,8 +131,8 @@ export default class QueryBuilder {
131
131
 
132
132
  const params: any = { ...filters }
133
133
 
134
- if (this._index)
135
- params.IndexName = this._index
134
+ if (this.#index)
135
+ params.IndexName = this.#index
136
136
 
137
137
  if (keys.expression)
138
138
  params.KeyConditionExpression = keys.expression
package/src/http.ts CHANGED
@@ -1,5 +1,9 @@
1
- import type { MiddlewareHandler } from 'hono'
2
- import BaseMiddleware, { MiddlewareType } from './middleware'
1
+ import type { Context, Next } from 'hono'
2
+ import { MiddlewareType } from './middleware'
3
+ import JsonResponse from './response'
4
+ import { Ability, Authnz, Token } from './auth'
5
+ import { registerGlobalMiddleware } from './register'
6
+ import mergeMiddleware from './utils/merge-middleware'
3
7
 
4
8
  function method(method: string, path = '/') {
5
9
  return function (target: any) {
@@ -31,27 +35,50 @@ export function Delete(path = '/') {
31
35
 
32
36
  export function Middleware(...handlers: MiddlewareType[]) {
33
37
  return function (target: any) {
34
- const middlewareHandlers = handlers.map(handler => {
35
- if (typeof handler === 'function' && handler.length === 2)
36
- return handler
38
+ mergeMiddleware(target, ...handlers)
39
+ }
40
+ }
41
+ export function Middlewares(...handlers: MiddlewareType[]) {
42
+ return Middleware(...handlers)
43
+ }
44
+
45
+ type MiddlewareOpt = string | RegExp
46
+ export function GlobalMiddleware(): ClassDecorator
47
+ export function GlobalMiddleware(target: Function): void
48
+ export function GlobalMiddleware(opt?: MiddlewareOpt): ClassDecorator
49
+ export function GlobalMiddleware(...args: any[]): void | ClassDecorator {
50
+ if (typeof args[0] === 'function')
51
+ return _globalmw(args[0])
52
+
53
+ return (target: any) => _globalmw(target, ...args)
54
+ }
55
+
56
+ export function Auth(target: Function): void
57
+ export function Auth(): ClassDecorator
58
+ export function Auth(...args: any[]): void | ClassDecorator {
59
+ if (args.length === 1 && typeof args[0] === 'function')
60
+ return _auth(args[0])
37
61
 
38
- if (handler instanceof BaseMiddleware)
39
- return handler.handle
62
+ return (target: any) => _auth(target)
63
+ }
40
64
 
41
- if (BaseMiddleware.isPrototypeOf(handler)) {
42
- const instance = new (handler as new () => BaseMiddleware)()
43
- return instance.handle
44
- }
65
+ function _auth(target: Function | any) {
66
+ mergeMiddleware(target, async (c: Context, next: Next) => {
67
+ const unauthorized = JsonResponse.unauthorized()
45
68
 
46
- throw new Error('Invalid middleware provided. Must be a Hono middleware function or MiddlewareClass instance/constructor')
47
- })
69
+ const auth = Authnz.fromToken(Token.fromRequest(c))
70
+ const ability = Ability.fromAction(target)
48
71
 
49
- const existingMiddlewares: MiddlewareHandler[] = target?.mw || []
50
- const allMiddlewares = [...existingMiddlewares, ...middlewareHandlers]
72
+ if (!auth || !ability || auth.cant(ability))
73
+ return unauthorized
51
74
 
52
- target.mw = allMiddlewares
53
- }
75
+ c.set('#auth', auth)
76
+ await next()
77
+ })
54
78
  }
55
- export function Middlewares(...handlers: MiddlewareType[]) {
56
- return Middleware(...handlers)
79
+
80
+ function _globalmw(target: Function | any, path?: string) {
81
+ target.gmw = true
82
+ target.p = path
83
+ registerGlobalMiddleware(target)
57
84
  }
package/src/middleware.ts CHANGED
@@ -5,7 +5,7 @@ export type IMiddleware = {
5
5
  }
6
6
 
7
7
  export default abstract class Middleware implements IMiddleware {
8
- public abstract handle(c: Context, next: Next): Promise<void> | void
8
+ abstract handle(c: Context, next: Next): Promise<void> | void
9
9
  }
10
10
 
11
11
  // export type MiddlewareHandler = (c: Context, next: Next) => Promise<void> | void
package/src/prod.ts CHANGED
@@ -1,14 +1,18 @@
1
- import { config } from 'dotenv'
2
1
  import { handle } from 'hono/aws-lambda'
3
2
  import createApp from './create-app'
4
-
5
- config({ path: '../../.env.prod' })
3
+ import { Ability } from './auth'
6
4
 
7
5
  // @ts-ignore
8
6
  await import('../../../tmp/import-routes.mjs')
9
7
 
10
8
  // @ts-ignore
11
9
  const routes = (await import('../../../tmp/routes.json')).default
10
+
11
+ // @ts-ignore
12
+ Ability.roles = (await import('../../../roles.json')).default
13
+ // @ts-ignore
14
+ Ability.fromRoutes(routes)
15
+
12
16
  // @ts-ignore
13
17
  const app = createApp({ routes })
14
18
 
package/src/register.ts CHANGED
@@ -23,3 +23,9 @@ export function getHandler(id: string): Function {
23
23
  if (!handler) throw new Error(`Handler ${id} not registered`)
24
24
  return handler
25
25
  }
26
+
27
+ export const _mw: Function[] = []
28
+ export const getGlobalMiddlewares = () => _mw
29
+ export function registerGlobalMiddleware(handler: any) {
30
+ _mw.push(handler)
31
+ }
package/src/response.ts CHANGED
@@ -25,19 +25,19 @@ class NullContext {
25
25
  }
26
26
 
27
27
  export default class JsonResponse {
28
- private static _c?: Context
28
+ static #c?: Context
29
29
 
30
30
  static setContext(c: Context) {
31
- this._c = c
31
+ this.#c = c
32
32
  return this
33
33
  }
34
34
 
35
- private static context(): Context | NullContext {
36
- return this._c ?? new NullContext()
35
+ static #context(): Context | NullContext {
36
+ return this.#c ?? new NullContext()
37
37
  }
38
38
 
39
39
  static raw(status?: StatusCode, body?: string) {
40
- return this.context().newResponse(body ? body : null, { status })
40
+ return this.#context().newResponse(body ? body : null, { status })
41
41
  }
42
42
 
43
43
  static ok(): Response
@@ -46,7 +46,7 @@ export default class JsonResponse {
46
46
  if (data === undefined)
47
47
  return this.raw(200)
48
48
 
49
- return this.context().json(data, 200)
49
+ return this.#context().json(data, 200)
50
50
  }
51
51
 
52
52
  static created(): Response
@@ -55,7 +55,7 @@ export default class JsonResponse {
55
55
  if (data === undefined)
56
56
  return this.raw(201)
57
57
 
58
- return this.context().json(data, 201)
58
+ return this.#context().json(data, 201)
59
59
  }
60
60
 
61
61
  static accepted(): Response
@@ -64,7 +64,7 @@ export default class JsonResponse {
64
64
  if (data === undefined)
65
65
  return this.raw(202)
66
66
 
67
- return this.context().json(data, 202)
67
+ return this.#context().json(data, 202)
68
68
  }
69
69
 
70
70
  static deleted(): Response
@@ -88,7 +88,7 @@ export default class JsonResponse {
88
88
  if (data === undefined)
89
89
  return this.raw(401)
90
90
 
91
- return this.context().json(data, 401)
91
+ return this.#context().json(data, 401)
92
92
  }
93
93
 
94
94
  static forbidden(): Response
@@ -97,7 +97,7 @@ export default class JsonResponse {
97
97
  if (data === undefined)
98
98
  return this.raw(403)
99
99
 
100
- return this.context().json(data, 403)
100
+ return this.#context().json(data, 403)
101
101
  }
102
102
 
103
103
  static notFound(): Response
@@ -122,7 +122,7 @@ export default class JsonResponse {
122
122
  }
123
123
 
124
124
  static error(errors?: Errors, msg?: string, status?: ContentfulStatusCode) {
125
- const context = this.context()
125
+ const context = this.#context()
126
126
  status ??= 500
127
127
 
128
128
  if (!errors && !msg)
package/src/routes.ts CHANGED
@@ -1,27 +1,31 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
1
+ import { existsSync, readdirSync, statSync } from 'node:fs'
2
+ import { dirname, join, resolve } from 'node:path'
3
3
  import { fileURLToPath } from 'node:url'
4
4
  import { Route } from './types'
5
5
  import { registerHandler } from './register'
6
6
  import { isAnonFn } from './utils/func'
7
7
 
8
8
  const __filename = fileURLToPath(import.meta.url)
9
- const __dirname = path.dirname(__filename)
9
+ const __dirname = dirname(__filename)
10
10
 
11
- export default async function getRoutes(all: boolean = false, baseDir: string = 'actions'): Promise<Route[]> {
11
+ export default async function getRoutes(
12
+ all: boolean = false,
13
+ dirs: string[] = ['actions', 'features', 'errors', 'middlewares']
14
+ ): Promise<Route[]> {
12
15
  const routes: Route[] = []
13
16
 
14
- const walk = async (dir: string, middlewares: Function[] = []): Promise<void> => {
15
- const files = fs.readdirSync(dir)
17
+ const walk = async (dir: string, baseDir: string, middlewares: Function[] = []): Promise<void> => {
18
+ if (!existsSync(dir)) return
19
+ const files = readdirSync(dir)
16
20
 
17
21
  for (const file of files) {
18
- const fullPath = path.join(dir, file)
19
- const stat = fs.statSync(fullPath)
22
+ const fullPath = join(dir, file)
23
+ const stat = statSync(fullPath)
20
24
 
21
25
  if (stat.isDirectory()) {
22
- const indexFile = path.join(fullPath, 'index.ts')
26
+ const indexFile = join(fullPath, 'index.ts')
23
27
 
24
- if (fs.existsSync(indexFile)) {
28
+ if (existsSync(indexFile)) {
25
29
  const mod = await import(indexFile)
26
30
  const group = mod.default
27
31
  registerHandler(group.name, group)
@@ -37,11 +41,12 @@ export default async function getRoutes(all: boolean = false, baseDir: string =
37
41
  })
38
42
  }
39
43
 
40
- await walk(fullPath, middlewares)
44
+ await walk(fullPath, baseDir, middlewares)
41
45
  } else if (file.endsWith('.ts')) {
42
46
  const mod = await import(fullPath)
43
47
  const handle = mod.default
44
48
 
49
+ if (handle?.gmw) return
45
50
  if (handle?.m) {
46
51
  registerHandler(handle.name, handle)
47
52
 
@@ -58,6 +63,6 @@ export default async function getRoutes(all: boolean = false, baseDir: string =
58
63
  }
59
64
  }
60
65
 
61
- await walk(path.resolve(__dirname, '../../..', baseDir))
66
+ await Promise.all(dirs.map(dir => walk(resolve(__dirname, '../../..', dir), dir)))
62
67
  return routes
63
68
  }
@@ -1,8 +1,15 @@
1
- import { writeFileSync } from 'node:fs'
1
+ import { existsSync, writeFileSync } from 'node:fs'
2
+ import { config } from 'dotenv'
2
3
  import getRoutes from '../routes'
3
4
  import ensureDir from '../utils/ensuredir'
4
5
 
6
+ config({ path: '../../.env.dev' })
7
+
5
8
  async function cacheRoutes() {
9
+ const rolePath = '../../roles.json'
10
+ if (!existsSync(rolePath))
11
+ writeFileSync(rolePath, '{}')
12
+
6
13
  const routes = await getRoutes(true)
7
14
 
8
15
  const iPath = '../../tmp/import-routes.mjs'
@@ -0,0 +1,16 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { dirname, join } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ export default function jsonImport<T = any>(filePath: string, defaultValue: T = {} as T): T {
6
+ const __filename = fileURLToPath(import.meta.url)
7
+ const __dirname = dirname(__filename)
8
+
9
+ try {
10
+ const fullPath = join(__dirname, filePath)
11
+ const fileContent = readFileSync(fullPath, 'utf-8')
12
+ return JSON.parse(fileContent) as T
13
+ } catch (error) {
14
+ return defaultValue
15
+ }
16
+ }
@@ -0,0 +1,9 @@
1
+ import type { MiddlewareHandler } from 'hono'
2
+ import { MiddlewareType } from '../middleware'
3
+ import { resolveMiddleware } from './resolve'
4
+
5
+ export default function mergeMiddleware(target: Function | any, ...handlers: MiddlewareType[]) {
6
+ const existingMiddlewares: MiddlewareHandler[] = target?.mw || []
7
+ const allMiddlewares = [...existingMiddlewares, ...handlers.flat().map(handler => resolveMiddleware(handler))]
8
+ target.mw = allMiddlewares
9
+ }
@@ -1,6 +1,7 @@
1
1
  import Action, { ActionType } from '../action'
2
+ import { MiddlewareType } from '../middleware'
2
3
 
3
- export default function resolve(obj: ActionType) {
4
+ export function resolve(obj: ActionType) {
4
5
  if (typeof obj === 'function' && obj?.length === 2)
5
6
  return [obj]
6
7
 
@@ -16,3 +17,24 @@ export default function resolve(obj: ActionType) {
16
17
 
17
18
  throw new Error('Invalid action')
18
19
  }
20
+
21
+ export function resolveMiddleware(obj: MiddlewareType) {
22
+ if (typeof obj === 'function' && obj.length === 2)
23
+ return obj
24
+
25
+ if (obj?.handle)
26
+ return obj.handle
27
+
28
+ if (obj.prototype?.handle)
29
+ return (new obj()).handle
30
+
31
+ // if (obj instanceof BaseMiddleware)
32
+ // return obj.handle
33
+
34
+ // if (BaseMiddleware.isPrototypeOf(obj)) {
35
+ // const instance = new (obj as new () => BaseMiddleware)()
36
+ // return instance.handle
37
+ // }
38
+
39
+ throw new Error('Invalid middleware provided. Must be a Hono middleware function or MiddlewareClass instance/constructor')
40
+ }