rajt 0.0.16 → 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.16",
4
+ "version": "0.0.18",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "exports": {
@@ -11,15 +11,13 @@
11
11
  "./http": "./src/http.ts",
12
12
  "./types": "./src/types.ts"
13
13
  },
14
- "files": [
15
- "src"
16
- ],
14
+ "files": ["src"],
17
15
  "scripts": {
18
16
  "dev": "tsx watch src/dev.ts",
19
17
  "local": "bun run --silent build && bun run --silent sam:local",
20
18
  "build": "bun run --silent cache:routes && bun run --silent export && bun run --silent clean:temp",
21
19
  "build:watch": "chokidar \"../../{actions,configs,models,utils}/**/*.ts\" -c \"bun run --silent build\" --initial",
22
- "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",
23
21
  "package": "bun run --silent build && bun run --silent sam:package",
24
22
  "deploy": "bun run --silent build && bun run --silent sam:package && bun run --silent sam:deploy",
25
23
  "update": "bun run --silent build && bun run --silent zip && bun run --silent sam:update",
@@ -36,13 +34,13 @@
36
34
  "start": "node ../../dist/index.js"
37
35
  },
38
36
  "dependencies": {
39
- "@aws-lite/client": "^0.23.2",
40
- "@aws-lite/dynamodb": "^0.3.9",
41
- "@aws-lite/dynamodb-types": "^0.3.11",
37
+ "@aws-sdk/client-dynamodb": "3.817.0",
38
+ "@aws-sdk/lib-dynamodb": "3.817.0",
42
39
  "@hono/node-server": "^1.14.1",
43
40
  "@hono/zod-validator": "^0.4.3",
44
41
  "@types/node": "^20.11.0",
45
42
  "chokidar-cli": "^3.0.0",
43
+ "cripta": "^0.1.6",
46
44
  "dotenv": "^16.5.0",
47
45
  "esbuild": "^0.25.2",
48
46
  "hono": "^4.7.6",
@@ -51,10 +49,14 @@
51
49
  "tsx": "^4.19.3",
52
50
  "typescript": "^5.8.3"
53
51
  },
52
+ "resolutions": {
53
+ "@smithy/types": "^4.3.0"
54
+ },
54
55
  "publishConfig": {
55
56
  "registry": "https://registry.npmjs.org"
56
57
  },
57
58
  "author": "Zunq <open-source@zunq.com>",
59
+ "license": "MIT",
58
60
  "homepage": "https://zunq.dev",
59
61
  "repository": "git://github.com/attla/rajt",
60
62
  "bugs": "https://github.com/attla/rajt/issues",
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, "'")