rajt 0.0.17 → 0.0.19
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 +8 -5
- package/src/action.ts +29 -11
- package/src/auth/ability.ts +51 -0
- package/src/auth/auth.ts +72 -0
- package/src/auth/index.ts +5 -0
- package/src/auth/token.ts +73 -0
- package/src/auth/types.ts +2 -0
- package/src/create-app.ts +10 -4
- package/src/dev.ts +8 -2
- package/src/dynamodb/client.ts +13 -10
- package/src/dynamodb/compact.ts +3 -3
- package/src/dynamodb/model.ts +116 -87
- package/src/dynamodb/query-builder.ts +16 -16
- package/src/http.ts +46 -19
- package/src/middleware.ts +1 -1
- package/src/prod.ts +7 -3
- package/src/register.ts +6 -0
- package/src/response.ts +11 -11
- package/src/routes.ts +68 -12
- package/src/scripts/cache-routes.ts +11 -2
- package/src/utils/json-import.ts +16 -0
- package/src/utils/merge-middleware.ts +9 -0
- package/src/utils/resolve.ts +23 -1
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.
|
|
4
|
+
"version": "0.0.19",
|
|
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-
|
|
38
|
-
"@aws-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
+
param(key: string) {
|
|
51
50
|
return this.context.req.param(key)
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
query() {
|
|
55
54
|
return this.context.req.query()
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
120
|
+
get response() {
|
|
122
121
|
return this.context ? JsonResponse.setContext(this.context) : JsonResponse
|
|
123
122
|
}
|
|
124
123
|
|
|
125
|
-
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|
package/src/auth/auth.ts
ADDED
|
@@ -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,73 @@
|
|
|
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
|
+
return token ? this.parse(c.req, token) : null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static fromHeader(req: HonoRequest): string | null {
|
|
15
|
+
const header = req.header(this.#name) || req.header('HTTP_AUTHORIZATION') || req.header('REDIRECT_HTTP_AUTHORIZATION') || null
|
|
16
|
+
|
|
17
|
+
if (header) {
|
|
18
|
+
const position = header.toLowerCase().indexOf(this.#prefix.toLowerCase())
|
|
19
|
+
if (position !== -1) {
|
|
20
|
+
let token = header.slice(position + this.#prefix.length).trim()
|
|
21
|
+
const commaPos = token.indexOf(',')
|
|
22
|
+
if (commaPos !== -1) token = token.slice(0, commaPos).trim()
|
|
23
|
+
|
|
24
|
+
return token
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static parse(req: HonoRequest, token: string) {
|
|
32
|
+
const host = this.host(Envir.get('FLOW_SERVER') || req.header('host') || '')
|
|
33
|
+
|
|
34
|
+
return Factory.parse(token)
|
|
35
|
+
.issuedBy(host)
|
|
36
|
+
.permittedFor(host)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static create(req: HonoRequest, user: any, exp: number = 7200) {
|
|
40
|
+
const time = Math.floor(Date.now() / 1000)
|
|
41
|
+
const host = this.host(req.header('host') || '')
|
|
42
|
+
|
|
43
|
+
return Factory.create()
|
|
44
|
+
.issuedBy(host)
|
|
45
|
+
.permittedFor(host)
|
|
46
|
+
.issuedAt(time)
|
|
47
|
+
.expiresAt(time + exp)
|
|
48
|
+
.body(user)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static setPrefix(prefix: string): void {
|
|
52
|
+
this.#prefix = prefix
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static setName(name: string): void {
|
|
56
|
+
this.#name = name
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static host(url: string | null | undefined): string {
|
|
60
|
+
if (!url) return ''
|
|
61
|
+
|
|
62
|
+
let formattedUrl = String(url)
|
|
63
|
+
if (!formattedUrl.startsWith('http'))
|
|
64
|
+
formattedUrl = 'http://' + formattedUrl
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const parsedUrl = new URL(formattedUrl)
|
|
68
|
+
return parsedUrl.host
|
|
69
|
+
} catch (e) {
|
|
70
|
+
return ''
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
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 {
|
|
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(
|
|
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
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import { config } from 'dotenv'
|
|
2
2
|
import { serve } from '@hono/node-server'
|
|
3
3
|
import createApp from './create-app'
|
|
4
|
-
import getRoutes from './routes'
|
|
4
|
+
import { getRoutes, getMiddlewares } 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
|
+
const middlewares = await getMiddlewares()
|
|
13
|
+
Ability.fromRoutes(routes)
|
|
14
|
+
Ability.roles = jsonImport('../../../../.rolefile')
|
|
15
|
+
|
|
10
16
|
const fetch = createApp({ routes }).fetch
|
|
11
17
|
|
|
12
18
|
const desiredPort = process.env?.PORT ? Number(process.env.PORT) : 3000
|
|
13
19
|
getAvailablePort(desiredPort)
|
|
14
20
|
.then(port => {
|
|
15
21
|
if (port != desiredPort)
|
|
16
|
-
console.
|
|
22
|
+
console.warn(`🟨 Port ${desiredPort} was in use, using ${port} as a fallback`)
|
|
17
23
|
|
|
18
24
|
console.log(`🚀 API running on http://localhost:${port}`)
|
|
19
25
|
serve({ fetch, port })
|
package/src/dynamodb/client.ts
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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,
|
|
18
|
+
return new AbstractModel<T>(cls, ddb)
|
|
16
19
|
}
|
|
17
20
|
}
|
package/src/dynamodb/compact.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
52
|
+
val = this.replaceTypes(val, this.reverseMap(this.#typeMap))
|
|
53
53
|
.replace(/"/g, '~TSQ~')
|
|
54
54
|
.replace(/'/g, '"')
|
|
55
55
|
.replace(/~TSQ~/g, "'")
|