@gravito/cosmos 3.0.1 → 3.1.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.
- package/README.md +105 -45
- package/README.zh-TW.md +102 -22
- package/docs/plans/01-performance.md +187 -0
- package/docs/plans/02-architecture.md +309 -0
- package/docs/plans/03-api-enhancement.md +345 -0
- package/docs/plans/04-testing.md +431 -0
- package/docs/plans/README.md +47 -0
- package/ion/src/index.js +1179 -1138
- package/package.json +3 -2
- package/src/I18nService.ts +575 -91
- package/src/index.ts +25 -6
- package/src/loader.ts +45 -12
- package/tests/helpers/factory.ts +41 -0
- package/tests/performance/translate.bench.ts +27 -0
- package/tests/unit/api.test.ts +37 -0
- package/tests/unit/detector.test.ts +70 -0
- package/tests/unit/edge.test.ts +100 -0
- package/tests/unit/fallback.test.ts +66 -0
- package/tests/unit/lazy.test.ts +35 -0
- package/tests/{loader.test.ts → unit/loader.test.ts} +1 -1
- package/tests/{manager.test.ts → unit/manager.test.ts} +1 -1
- package/tests/unit/plural.test.ts +58 -0
- package/tests/{service.test.ts → unit/service.test.ts} +2 -2
- package/tsconfig.json +12 -24
- package/.turbo/turbo-build.log +0 -20
- package/.turbo/turbo-test$colon$ci.log +0 -35
- package/.turbo/turbo-test$colon$coverage.log +0 -35
- package/.turbo/turbo-test.log +0 -27
- package/.turbo/turbo-typecheck.log +0 -2
- package/dist/index.cjs +0 -309
- package/dist/index.d.cts +0 -274
- package/dist/index.d.ts +0 -274
- package/dist/index.js +0 -277
package/ion/src/index.js
CHANGED
|
@@ -1,970 +1,978 @@
|
|
|
1
|
-
import { createRequire } from
|
|
2
|
-
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
|
|
3
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url)
|
|
3
4
|
// ../core/package.json
|
|
4
5
|
var package_default = {
|
|
5
|
-
name:
|
|
6
|
-
version:
|
|
7
|
-
description:
|
|
8
|
-
module:
|
|
9
|
-
main:
|
|
10
|
-
type:
|
|
11
|
-
types:
|
|
6
|
+
name: '@gravito/core',
|
|
7
|
+
version: '1.0.0-beta.2',
|
|
8
|
+
description: '',
|
|
9
|
+
module: './dist/index.mjs',
|
|
10
|
+
main: './dist/index.cjs',
|
|
11
|
+
type: 'module',
|
|
12
|
+
types: './dist/index.d.ts',
|
|
12
13
|
exports: {
|
|
13
|
-
|
|
14
|
-
types:
|
|
15
|
-
import:
|
|
16
|
-
require:
|
|
14
|
+
'.': {
|
|
15
|
+
types: './dist/index.d.ts',
|
|
16
|
+
import: './dist/index.mjs',
|
|
17
|
+
require: './dist/index.cjs',
|
|
18
|
+
},
|
|
19
|
+
'./compat': {
|
|
20
|
+
types: './dist/compat.d.ts',
|
|
21
|
+
import: './dist/compat.mjs',
|
|
22
|
+
require: './dist/compat.cjs',
|
|
17
23
|
},
|
|
18
|
-
"./compat": {
|
|
19
|
-
types: "./dist/compat.d.ts",
|
|
20
|
-
import: "./dist/compat.mjs",
|
|
21
|
-
require: "./dist/compat.cjs"
|
|
22
|
-
}
|
|
23
24
|
},
|
|
24
|
-
files: [
|
|
25
|
-
"dist",
|
|
26
|
-
"README.md",
|
|
27
|
-
"LICENSE"
|
|
28
|
-
],
|
|
25
|
+
files: ['dist', 'README.md', 'LICENSE'],
|
|
29
26
|
scripts: {
|
|
30
|
-
build:
|
|
31
|
-
test:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
lint:
|
|
35
|
-
|
|
36
|
-
format:
|
|
37
|
-
|
|
38
|
-
check:
|
|
39
|
-
|
|
40
|
-
typecheck:
|
|
41
|
-
prepublishOnly:
|
|
27
|
+
build: 'bun run build.ts',
|
|
28
|
+
test: 'bun test',
|
|
29
|
+
'test:coverage': 'bun test --coverage',
|
|
30
|
+
'test:ci': 'bun test --coverage --coverage-threshold=100',
|
|
31
|
+
lint: 'biome lint ./src ./tests',
|
|
32
|
+
'lint:fix': 'biome lint --write ./src ./tests',
|
|
33
|
+
format: 'biome format --write ./src ./tests',
|
|
34
|
+
'format:check': 'biome format ./src ./tests',
|
|
35
|
+
check: 'biome check ./src ./tests',
|
|
36
|
+
'check:fix': 'biome check --write ./src ./tests',
|
|
37
|
+
typecheck: 'tsc --noEmit',
|
|
38
|
+
prepublishOnly: 'bun run typecheck && bun run test && bun run build',
|
|
42
39
|
},
|
|
43
40
|
keywords: [],
|
|
44
|
-
author:
|
|
45
|
-
license:
|
|
41
|
+
author: 'Carl Lee <carllee0520@gmail.com>',
|
|
42
|
+
license: 'MIT',
|
|
46
43
|
repository: {
|
|
47
|
-
type:
|
|
48
|
-
url:
|
|
49
|
-
directory:
|
|
44
|
+
type: 'git',
|
|
45
|
+
url: 'git+https://github.com/gravito-framework/gravito.git',
|
|
46
|
+
directory: 'packages/core',
|
|
50
47
|
},
|
|
51
48
|
bugs: {
|
|
52
|
-
url:
|
|
49
|
+
url: 'https://github.com/gravito-framework/gravito/issues',
|
|
53
50
|
},
|
|
54
|
-
homepage:
|
|
51
|
+
homepage: 'https://github.com/gravito-framework/gravito#readme',
|
|
55
52
|
engines: {
|
|
56
|
-
node:
|
|
53
|
+
node: '>=18.0.0',
|
|
57
54
|
},
|
|
58
55
|
devDependencies: {
|
|
59
|
-
|
|
60
|
-
typescript:
|
|
56
|
+
'bun-types': 'latest',
|
|
57
|
+
typescript: '^5.9.3',
|
|
61
58
|
},
|
|
62
59
|
publishConfig: {
|
|
63
|
-
access:
|
|
64
|
-
registry:
|
|
60
|
+
access: 'public',
|
|
61
|
+
registry: 'https://registry.npmjs.org/',
|
|
65
62
|
},
|
|
66
63
|
dependencies: {
|
|
67
|
-
hono:
|
|
68
|
-
}
|
|
69
|
-
}
|
|
64
|
+
hono: '^4.11.1',
|
|
65
|
+
},
|
|
66
|
+
}
|
|
70
67
|
|
|
71
68
|
// ../core/src/adapters/HonoAdapter.ts
|
|
72
69
|
class HonoRequestWrapper {
|
|
73
|
-
honoCtx
|
|
70
|
+
honoCtx
|
|
74
71
|
constructor(honoCtx) {
|
|
75
|
-
this.honoCtx = honoCtx
|
|
72
|
+
this.honoCtx = honoCtx
|
|
76
73
|
}
|
|
77
74
|
get url() {
|
|
78
|
-
return this.honoCtx.req.url
|
|
75
|
+
return this.honoCtx.req.url
|
|
79
76
|
}
|
|
80
77
|
get method() {
|
|
81
|
-
return this.honoCtx.req.method
|
|
78
|
+
return this.honoCtx.req.method
|
|
82
79
|
}
|
|
83
80
|
get path() {
|
|
84
|
-
return this.honoCtx.req.path
|
|
81
|
+
return this.honoCtx.req.path
|
|
85
82
|
}
|
|
86
83
|
param(name) {
|
|
87
|
-
return this.honoCtx.req.param(name)
|
|
84
|
+
return this.honoCtx.req.param(name)
|
|
88
85
|
}
|
|
89
86
|
params() {
|
|
90
|
-
return this.honoCtx.req.param()
|
|
87
|
+
return this.honoCtx.req.param()
|
|
91
88
|
}
|
|
92
89
|
query(name) {
|
|
93
|
-
return this.honoCtx.req.query(name)
|
|
90
|
+
return this.honoCtx.req.query(name)
|
|
94
91
|
}
|
|
95
92
|
queries() {
|
|
96
|
-
return this.honoCtx.req.queries()
|
|
93
|
+
return this.honoCtx.req.queries()
|
|
97
94
|
}
|
|
98
95
|
header(name) {
|
|
99
96
|
if (name) {
|
|
100
|
-
return this.honoCtx.req.header(name)
|
|
97
|
+
return this.honoCtx.req.header(name)
|
|
101
98
|
}
|
|
102
|
-
return this.honoCtx.req.header()
|
|
99
|
+
return this.honoCtx.req.header()
|
|
103
100
|
}
|
|
104
101
|
async json() {
|
|
105
|
-
return this.honoCtx.req.json()
|
|
102
|
+
return this.honoCtx.req.json()
|
|
106
103
|
}
|
|
107
104
|
async text() {
|
|
108
|
-
return this.honoCtx.req.text()
|
|
105
|
+
return this.honoCtx.req.text()
|
|
109
106
|
}
|
|
110
107
|
async formData() {
|
|
111
|
-
return this.honoCtx.req.formData()
|
|
108
|
+
return this.honoCtx.req.formData()
|
|
112
109
|
}
|
|
113
110
|
async arrayBuffer() {
|
|
114
|
-
return this.honoCtx.req.arrayBuffer()
|
|
111
|
+
return this.honoCtx.req.arrayBuffer()
|
|
115
112
|
}
|
|
116
113
|
get raw() {
|
|
117
|
-
return this.honoCtx.req.raw
|
|
114
|
+
return this.honoCtx.req.raw
|
|
118
115
|
}
|
|
119
116
|
valid(target) {
|
|
120
|
-
return this.honoCtx.req.valid(target)
|
|
117
|
+
return this.honoCtx.req.valid(target)
|
|
121
118
|
}
|
|
122
119
|
}
|
|
123
120
|
|
|
124
121
|
class HonoContextWrapper {
|
|
125
|
-
honoCtx
|
|
126
|
-
_req
|
|
122
|
+
honoCtx
|
|
123
|
+
_req
|
|
127
124
|
constructor(honoCtx) {
|
|
128
|
-
this.honoCtx = honoCtx
|
|
129
|
-
this._req = new HonoRequestWrapper(honoCtx)
|
|
125
|
+
this.honoCtx = honoCtx
|
|
126
|
+
this._req = new HonoRequestWrapper(honoCtx)
|
|
130
127
|
}
|
|
131
128
|
static create(honoCtx) {
|
|
132
|
-
const instance = new HonoContextWrapper(honoCtx)
|
|
129
|
+
const instance = new HonoContextWrapper(honoCtx)
|
|
133
130
|
return new Proxy(instance, {
|
|
134
131
|
get(target, prop, receiver) {
|
|
135
132
|
if (prop in target) {
|
|
136
|
-
const value = Reflect.get(target, prop, receiver)
|
|
137
|
-
if (typeof value ===
|
|
138
|
-
return value.bind(target)
|
|
133
|
+
const value = Reflect.get(target, prop, receiver)
|
|
134
|
+
if (typeof value === 'function') {
|
|
135
|
+
return value.bind(target)
|
|
139
136
|
}
|
|
140
|
-
return value
|
|
137
|
+
return value
|
|
141
138
|
}
|
|
142
|
-
if (typeof prop ===
|
|
143
|
-
return target.get(prop)
|
|
139
|
+
if (typeof prop === 'string') {
|
|
140
|
+
return target.get(prop)
|
|
144
141
|
}
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
|
-
})
|
|
142
|
+
return
|
|
143
|
+
},
|
|
144
|
+
})
|
|
148
145
|
}
|
|
149
146
|
get req() {
|
|
150
|
-
return this._req
|
|
147
|
+
return this._req
|
|
151
148
|
}
|
|
152
149
|
json(data, status) {
|
|
153
150
|
if (status !== undefined) {
|
|
154
|
-
return this.honoCtx.json(data, status)
|
|
151
|
+
return this.honoCtx.json(data, status)
|
|
155
152
|
}
|
|
156
|
-
return this.honoCtx.json(data)
|
|
153
|
+
return this.honoCtx.json(data)
|
|
157
154
|
}
|
|
158
155
|
text(text, status) {
|
|
159
156
|
if (status !== undefined) {
|
|
160
|
-
return this.honoCtx.text(text, status)
|
|
157
|
+
return this.honoCtx.text(text, status)
|
|
161
158
|
}
|
|
162
|
-
return this.honoCtx.text(text)
|
|
159
|
+
return this.honoCtx.text(text)
|
|
163
160
|
}
|
|
164
161
|
html(html, status) {
|
|
165
162
|
if (status !== undefined) {
|
|
166
|
-
return this.honoCtx.html(html, status)
|
|
163
|
+
return this.honoCtx.html(html, status)
|
|
167
164
|
}
|
|
168
|
-
return this.honoCtx.html(html)
|
|
165
|
+
return this.honoCtx.html(html)
|
|
169
166
|
}
|
|
170
167
|
redirect(url, status = 302) {
|
|
171
|
-
return this.honoCtx.redirect(url, status)
|
|
168
|
+
return this.honoCtx.redirect(url, status)
|
|
172
169
|
}
|
|
173
170
|
body(data, status) {
|
|
174
171
|
if (data === null) {
|
|
175
|
-
return this.honoCtx.body(null, status)
|
|
172
|
+
return this.honoCtx.body(null, status)
|
|
176
173
|
}
|
|
177
174
|
return new Response(data, {
|
|
178
175
|
status: status ?? 200,
|
|
179
|
-
headers: new Headers
|
|
180
|
-
})
|
|
176
|
+
headers: new Headers(),
|
|
177
|
+
})
|
|
181
178
|
}
|
|
182
179
|
stream(stream, status) {
|
|
183
180
|
return new Response(stream, {
|
|
184
181
|
status: status ?? 200,
|
|
185
182
|
headers: {
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
})
|
|
183
|
+
'Content-Type': 'application/octet-stream',
|
|
184
|
+
},
|
|
185
|
+
})
|
|
189
186
|
}
|
|
190
187
|
header(name, value, options) {
|
|
191
188
|
if (value !== undefined) {
|
|
192
189
|
if (options?.append) {
|
|
193
|
-
this.honoCtx.header(name, value, { append: true })
|
|
190
|
+
this.honoCtx.header(name, value, { append: true })
|
|
194
191
|
} else {
|
|
195
|
-
this.honoCtx.header(name, value)
|
|
192
|
+
this.honoCtx.header(name, value)
|
|
196
193
|
}
|
|
197
|
-
return
|
|
194
|
+
return
|
|
198
195
|
}
|
|
199
|
-
return this.honoCtx.req.header(name)
|
|
196
|
+
return this.honoCtx.req.header(name)
|
|
200
197
|
}
|
|
201
198
|
status(code) {
|
|
202
|
-
this.honoCtx.status(code)
|
|
199
|
+
this.honoCtx.status(code)
|
|
203
200
|
}
|
|
204
201
|
get(key) {
|
|
205
|
-
return this.honoCtx.get(key)
|
|
202
|
+
return this.honoCtx.get(key)
|
|
206
203
|
}
|
|
207
204
|
set(key, value) {
|
|
208
|
-
this.honoCtx.set(key, value)
|
|
205
|
+
this.honoCtx.set(key, value)
|
|
209
206
|
}
|
|
210
207
|
get executionCtx() {
|
|
211
|
-
return this.honoCtx.executionCtx
|
|
208
|
+
return this.honoCtx.executionCtx
|
|
212
209
|
}
|
|
213
210
|
get env() {
|
|
214
|
-
return this.honoCtx.env
|
|
211
|
+
return this.honoCtx.env
|
|
215
212
|
}
|
|
216
213
|
get native() {
|
|
217
|
-
return this.honoCtx
|
|
214
|
+
return this.honoCtx
|
|
218
215
|
}
|
|
219
216
|
}
|
|
220
217
|
function toHonoMiddleware(middleware) {
|
|
221
218
|
return async (c, next) => {
|
|
222
|
-
const ctx = HonoContextWrapper.create(c)
|
|
219
|
+
const ctx = HonoContextWrapper.create(c)
|
|
223
220
|
const gravitoNext = async () => {
|
|
224
|
-
await next()
|
|
225
|
-
}
|
|
226
|
-
return middleware(ctx, gravitoNext)
|
|
227
|
-
}
|
|
221
|
+
await next()
|
|
222
|
+
}
|
|
223
|
+
return middleware(ctx, gravitoNext)
|
|
224
|
+
}
|
|
228
225
|
}
|
|
229
226
|
function toHonoErrorHandler(handler) {
|
|
230
227
|
return async (err, c) => {
|
|
231
|
-
const ctx = HonoContextWrapper.create(c)
|
|
232
|
-
return handler(err, ctx)
|
|
233
|
-
}
|
|
228
|
+
const ctx = HonoContextWrapper.create(c)
|
|
229
|
+
return handler(err, ctx)
|
|
230
|
+
}
|
|
234
231
|
}
|
|
235
232
|
|
|
236
233
|
class HonoAdapter {
|
|
237
|
-
config
|
|
238
|
-
name =
|
|
239
|
-
version =
|
|
240
|
-
app
|
|
234
|
+
config
|
|
235
|
+
name = 'hono'
|
|
236
|
+
version = '1.0.0'
|
|
237
|
+
app
|
|
241
238
|
constructor(config = {}, honoInstance) {
|
|
242
|
-
this.config = config
|
|
239
|
+
this.config = config
|
|
243
240
|
if (honoInstance) {
|
|
244
|
-
this.app = honoInstance
|
|
241
|
+
this.app = honoInstance
|
|
245
242
|
} else {
|
|
246
|
-
const { Hono: HonoClass } = __require(
|
|
247
|
-
this.app = new HonoClass
|
|
243
|
+
const { Hono: HonoClass } = __require('hono')
|
|
244
|
+
this.app = new HonoClass()
|
|
248
245
|
}
|
|
249
246
|
}
|
|
250
247
|
get native() {
|
|
251
|
-
return this.app
|
|
248
|
+
return this.app
|
|
252
249
|
}
|
|
253
250
|
setNative(app) {
|
|
254
|
-
this.app = app
|
|
251
|
+
this.app = app
|
|
255
252
|
}
|
|
256
253
|
route(method, path, ...handlers) {
|
|
257
|
-
const fullPath = (this.config.basePath ||
|
|
258
|
-
const honoHandlers = handlers.map((h) => toHonoMiddleware(h))
|
|
259
|
-
const methodFn = this.app[method]
|
|
260
|
-
if (typeof methodFn !==
|
|
261
|
-
throw new Error(`Unsupported HTTP method: ${method}`)
|
|
254
|
+
const fullPath = (this.config.basePath || '') + path
|
|
255
|
+
const honoHandlers = handlers.map((h) => toHonoMiddleware(h))
|
|
256
|
+
const methodFn = this.app[method]
|
|
257
|
+
if (typeof methodFn !== 'function') {
|
|
258
|
+
throw new Error(`Unsupported HTTP method: ${method}`)
|
|
262
259
|
}
|
|
263
|
-
methodFn.call(this.app, fullPath, ...honoHandlers)
|
|
260
|
+
methodFn.call(this.app, fullPath, ...honoHandlers)
|
|
264
261
|
}
|
|
265
262
|
routes(routes) {
|
|
266
263
|
for (const routeDef of routes) {
|
|
267
|
-
const middlewareHandlers = (routeDef.middleware || []).map((m) => m)
|
|
268
|
-
const allHandlers = [
|
|
269
|
-
|
|
270
|
-
...routeDef.handlers
|
|
271
|
-
];
|
|
272
|
-
this.route(routeDef.method, routeDef.path, ...allHandlers);
|
|
264
|
+
const middlewareHandlers = (routeDef.middleware || []).map((m) => m)
|
|
265
|
+
const allHandlers = [...middlewareHandlers, ...routeDef.handlers]
|
|
266
|
+
this.route(routeDef.method, routeDef.path, ...allHandlers)
|
|
273
267
|
}
|
|
274
268
|
}
|
|
275
269
|
use(path, ...middleware) {
|
|
276
|
-
const fullPath = (this.config.basePath ||
|
|
277
|
-
const honoMiddleware = middleware.map((m) => toHonoMiddleware(m))
|
|
270
|
+
const fullPath = (this.config.basePath || '') + path
|
|
271
|
+
const honoMiddleware = middleware.map((m) => toHonoMiddleware(m))
|
|
278
272
|
for (const m of honoMiddleware) {
|
|
279
|
-
this.app.use(fullPath, m)
|
|
273
|
+
this.app.use(fullPath, m)
|
|
280
274
|
}
|
|
281
275
|
}
|
|
282
276
|
useGlobal(...middleware) {
|
|
283
|
-
this.use(
|
|
277
|
+
this.use('*', ...middleware)
|
|
284
278
|
}
|
|
285
279
|
mount(path, subAdapter) {
|
|
286
|
-
if (subAdapter.name ===
|
|
287
|
-
this.app.route(path, subAdapter.native)
|
|
280
|
+
if (subAdapter.name === 'hono') {
|
|
281
|
+
this.app.route(path, subAdapter.native)
|
|
288
282
|
} else {
|
|
289
283
|
this.use(`${path}/*`, async (ctx) => {
|
|
290
|
-
const response = await subAdapter.fetch(ctx.req.raw)
|
|
291
|
-
return response
|
|
292
|
-
})
|
|
284
|
+
const response = await subAdapter.fetch(ctx.req.raw)
|
|
285
|
+
return response
|
|
286
|
+
})
|
|
293
287
|
}
|
|
294
288
|
}
|
|
295
289
|
onError(handler) {
|
|
296
|
-
this.app.onError(toHonoErrorHandler(handler))
|
|
290
|
+
this.app.onError(toHonoErrorHandler(handler))
|
|
297
291
|
}
|
|
298
292
|
onNotFound(handler) {
|
|
299
293
|
this.app.notFound(async (c) => {
|
|
300
|
-
const ctx = HonoContextWrapper.create(c)
|
|
301
|
-
return handler(ctx)
|
|
302
|
-
})
|
|
294
|
+
const ctx = HonoContextWrapper.create(c)
|
|
295
|
+
return handler(ctx)
|
|
296
|
+
})
|
|
303
297
|
}
|
|
304
298
|
fetch = (request, server) => {
|
|
305
|
-
return this.app.fetch(request, server)
|
|
306
|
-
}
|
|
299
|
+
return this.app.fetch(request, server)
|
|
300
|
+
}
|
|
307
301
|
createContext(_request) {
|
|
308
|
-
throw new Error(
|
|
302
|
+
throw new Error(
|
|
303
|
+
'HonoAdapter.createContext() should not be called directly. ' +
|
|
304
|
+
'Use the router pipeline instead.'
|
|
305
|
+
)
|
|
309
306
|
}
|
|
310
307
|
async init() {}
|
|
311
308
|
async shutdown() {}
|
|
312
309
|
}
|
|
313
310
|
// ../core/src/ConfigManager.ts
|
|
314
311
|
class ConfigManager {
|
|
315
|
-
config = new Map
|
|
312
|
+
config = new Map()
|
|
316
313
|
constructor(initialConfig = {}) {
|
|
317
314
|
for (const [key, value] of Object.entries(initialConfig)) {
|
|
318
|
-
this.config.set(key, value)
|
|
315
|
+
this.config.set(key, value)
|
|
319
316
|
}
|
|
320
|
-
this.loadEnv()
|
|
317
|
+
this.loadEnv()
|
|
321
318
|
}
|
|
322
319
|
loadEnv() {
|
|
323
|
-
const env = Bun.env
|
|
320
|
+
const env = Bun.env
|
|
324
321
|
for (const key of Object.keys(env)) {
|
|
325
322
|
if (env[key] !== undefined) {
|
|
326
|
-
this.config.set(key, env[key])
|
|
323
|
+
this.config.set(key, env[key])
|
|
327
324
|
}
|
|
328
325
|
}
|
|
329
326
|
}
|
|
330
327
|
get(key, defaultValue) {
|
|
331
328
|
if (this.config.has(key)) {
|
|
332
|
-
return this.config.get(key)
|
|
329
|
+
return this.config.get(key)
|
|
333
330
|
}
|
|
334
331
|
if (defaultValue !== undefined) {
|
|
335
|
-
return defaultValue
|
|
332
|
+
return defaultValue
|
|
336
333
|
}
|
|
337
|
-
throw new Error(`Config key '${key}' not found`)
|
|
334
|
+
throw new Error(`Config key '${key}' not found`)
|
|
338
335
|
}
|
|
339
336
|
set(key, value) {
|
|
340
|
-
this.config.set(key, value)
|
|
337
|
+
this.config.set(key, value)
|
|
341
338
|
}
|
|
342
339
|
has(key) {
|
|
343
|
-
return this.config.has(key)
|
|
340
|
+
return this.config.has(key)
|
|
344
341
|
}
|
|
345
342
|
}
|
|
346
343
|
// ../core/src/Container.ts
|
|
347
344
|
class Container {
|
|
348
|
-
bindings = new Map
|
|
349
|
-
instances = new Map
|
|
345
|
+
bindings = new Map()
|
|
346
|
+
instances = new Map()
|
|
350
347
|
bind(key, factory) {
|
|
351
|
-
this.bindings.set(key, { factory, shared: false })
|
|
348
|
+
this.bindings.set(key, { factory, shared: false })
|
|
352
349
|
}
|
|
353
350
|
singleton(key, factory) {
|
|
354
|
-
this.bindings.set(key, { factory, shared: true })
|
|
351
|
+
this.bindings.set(key, { factory, shared: true })
|
|
355
352
|
}
|
|
356
353
|
instance(key, instance) {
|
|
357
|
-
this.instances.set(key, instance)
|
|
354
|
+
this.instances.set(key, instance)
|
|
358
355
|
}
|
|
359
356
|
make(key) {
|
|
360
357
|
if (this.instances.has(key)) {
|
|
361
|
-
return this.instances.get(key)
|
|
358
|
+
return this.instances.get(key)
|
|
362
359
|
}
|
|
363
|
-
const binding = this.bindings.get(key)
|
|
360
|
+
const binding = this.bindings.get(key)
|
|
364
361
|
if (!binding) {
|
|
365
|
-
throw new Error(`Service '${key}' not found in container`)
|
|
362
|
+
throw new Error(`Service '${key}' not found in container`)
|
|
366
363
|
}
|
|
367
|
-
const instance = binding.factory(this)
|
|
364
|
+
const instance = binding.factory(this)
|
|
368
365
|
if (binding.shared) {
|
|
369
|
-
this.instances.set(key, instance)
|
|
366
|
+
this.instances.set(key, instance)
|
|
370
367
|
}
|
|
371
|
-
return instance
|
|
368
|
+
return instance
|
|
372
369
|
}
|
|
373
370
|
has(key) {
|
|
374
|
-
return this.bindings.has(key) || this.instances.has(key)
|
|
371
|
+
return this.bindings.has(key) || this.instances.has(key)
|
|
375
372
|
}
|
|
376
373
|
flush() {
|
|
377
|
-
this.bindings.clear()
|
|
378
|
-
this.instances.clear()
|
|
374
|
+
this.bindings.clear()
|
|
375
|
+
this.instances.clear()
|
|
379
376
|
}
|
|
380
377
|
forget(key) {
|
|
381
|
-
this.instances.delete(key)
|
|
378
|
+
this.instances.delete(key)
|
|
382
379
|
}
|
|
383
380
|
}
|
|
384
381
|
// ../core/src/types/events.ts
|
|
385
382
|
class Event {
|
|
386
383
|
shouldBroadcast() {
|
|
387
|
-
return
|
|
384
|
+
return 'broadcastOn' in this && typeof this.broadcastOn === 'function'
|
|
388
385
|
}
|
|
389
386
|
getBroadcastChannel() {
|
|
390
387
|
if (this.shouldBroadcast()) {
|
|
391
|
-
return this.broadcastOn()
|
|
388
|
+
return this.broadcastOn()
|
|
392
389
|
}
|
|
393
|
-
return null
|
|
390
|
+
return null
|
|
394
391
|
}
|
|
395
392
|
getBroadcastData() {
|
|
396
393
|
if (this.shouldBroadcast()) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
return broadcast.broadcastWith();
|
|
394
|
+
if (this.broadcastWith) {
|
|
395
|
+
return this.broadcastWith()
|
|
400
396
|
}
|
|
401
|
-
const data = {}
|
|
397
|
+
const data = {}
|
|
402
398
|
for (const [key, value] of Object.entries(this)) {
|
|
403
|
-
if (!key.startsWith(
|
|
404
|
-
data[key] = value
|
|
399
|
+
if (!key.startsWith('_') && typeof value !== 'function') {
|
|
400
|
+
data[key] = value
|
|
405
401
|
}
|
|
406
402
|
}
|
|
407
|
-
return data
|
|
403
|
+
return data
|
|
408
404
|
}
|
|
409
|
-
return {}
|
|
405
|
+
return {}
|
|
410
406
|
}
|
|
411
407
|
getBroadcastEventName() {
|
|
412
408
|
if (this.shouldBroadcast()) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
return broadcast.broadcastAs();
|
|
409
|
+
if (this.broadcastAs) {
|
|
410
|
+
return this.broadcastAs()
|
|
416
411
|
}
|
|
417
412
|
}
|
|
418
|
-
return this.constructor.name
|
|
413
|
+
return this.constructor.name
|
|
419
414
|
}
|
|
420
415
|
}
|
|
421
416
|
// ../core/src/EventManager.ts
|
|
422
417
|
class EventManager {
|
|
423
|
-
core
|
|
424
|
-
listeners = new Map
|
|
425
|
-
broadcastManager
|
|
426
|
-
queueManager
|
|
418
|
+
core
|
|
419
|
+
listeners = new Map()
|
|
420
|
+
broadcastManager
|
|
421
|
+
queueManager
|
|
427
422
|
constructor(core) {
|
|
428
|
-
this.core = core
|
|
423
|
+
this.core = core
|
|
429
424
|
}
|
|
430
425
|
setBroadcastManager(manager) {
|
|
431
|
-
this.broadcastManager = manager
|
|
426
|
+
this.broadcastManager = manager
|
|
432
427
|
}
|
|
433
428
|
setQueueManager(manager) {
|
|
434
|
-
this.queueManager = manager
|
|
429
|
+
this.queueManager = manager
|
|
435
430
|
}
|
|
436
431
|
listen(event, listener, options) {
|
|
437
|
-
const eventKey = typeof event ===
|
|
432
|
+
const eventKey = typeof event === 'string' ? event : event
|
|
438
433
|
if (!this.listeners.has(eventKey)) {
|
|
439
|
-
this.listeners.set(eventKey, [])
|
|
434
|
+
this.listeners.set(eventKey, [])
|
|
440
435
|
}
|
|
441
436
|
const registration = {
|
|
442
437
|
listener,
|
|
443
|
-
...options
|
|
444
|
-
}
|
|
445
|
-
this.listeners.get(eventKey)?.push(registration)
|
|
438
|
+
...options,
|
|
439
|
+
}
|
|
440
|
+
this.listeners.get(eventKey)?.push(registration)
|
|
446
441
|
}
|
|
447
442
|
unlisten(event, listener) {
|
|
448
|
-
const eventKey = typeof event ===
|
|
449
|
-
const registrations = this.listeners.get(eventKey)
|
|
443
|
+
const eventKey = typeof event === 'string' ? event : event
|
|
444
|
+
const registrations = this.listeners.get(eventKey)
|
|
450
445
|
if (!registrations) {
|
|
451
|
-
return
|
|
446
|
+
return
|
|
452
447
|
}
|
|
453
|
-
const filtered = registrations.filter((reg) => reg.listener !== listener)
|
|
448
|
+
const filtered = registrations.filter((reg) => reg.listener !== listener)
|
|
454
449
|
if (filtered.length === 0) {
|
|
455
|
-
this.listeners.delete(eventKey)
|
|
450
|
+
this.listeners.delete(eventKey)
|
|
456
451
|
} else {
|
|
457
|
-
this.listeners.set(eventKey, filtered)
|
|
452
|
+
this.listeners.set(eventKey, filtered)
|
|
458
453
|
}
|
|
459
454
|
}
|
|
460
455
|
async dispatch(event) {
|
|
461
|
-
const eventKey = event.constructor
|
|
462
|
-
const eventName = event.constructor.name
|
|
463
|
-
await this.core.hooks.doAction(`event:${eventName}`, event)
|
|
464
|
-
await this.core.hooks.doAction(
|
|
456
|
+
const eventKey = event.constructor
|
|
457
|
+
const eventName = event.constructor.name
|
|
458
|
+
await this.core.hooks.doAction(`event:${eventName}`, event)
|
|
459
|
+
await this.core.hooks.doAction('event:dispatched', { event, eventName })
|
|
465
460
|
if (event instanceof Event && event.shouldBroadcast() && this.broadcastManager) {
|
|
466
|
-
const channel = event.getBroadcastChannel()
|
|
461
|
+
const channel = event.getBroadcastChannel()
|
|
467
462
|
if (channel) {
|
|
468
|
-
const channelName = typeof channel ===
|
|
469
|
-
const channelType = typeof channel ===
|
|
470
|
-
const data = event.getBroadcastData()
|
|
471
|
-
const broadcastEventName = event.getBroadcastEventName()
|
|
472
|
-
await this.broadcastManager
|
|
473
|
-
|
|
474
|
-
|
|
463
|
+
const channelName = typeof channel === 'string' ? channel : channel.name
|
|
464
|
+
const channelType = typeof channel === 'string' ? 'public' : channel.type
|
|
465
|
+
const data = event.getBroadcastData()
|
|
466
|
+
const broadcastEventName = event.getBroadcastEventName()
|
|
467
|
+
await this.broadcastManager
|
|
468
|
+
.broadcast(event, { name: channelName, type: channelType }, data, broadcastEventName)
|
|
469
|
+
.catch((error) => {
|
|
470
|
+
this.core.logger.error(`[EventManager] Failed to broadcast event ${eventName}:`, error)
|
|
471
|
+
})
|
|
475
472
|
}
|
|
476
473
|
}
|
|
477
|
-
const registrations = this.listeners.get(eventKey) || []
|
|
478
|
-
const stringRegistrations = this.listeners.get(eventName) || []
|
|
479
|
-
const allRegistrations = [...registrations, ...stringRegistrations]
|
|
474
|
+
const registrations = this.listeners.get(eventKey) || []
|
|
475
|
+
const stringRegistrations = this.listeners.get(eventName) || []
|
|
476
|
+
const allRegistrations = [...registrations, ...stringRegistrations]
|
|
480
477
|
for (const registration of allRegistrations) {
|
|
481
478
|
try {
|
|
482
|
-
let listenerInstance
|
|
483
|
-
if (typeof registration.listener ===
|
|
484
|
-
listenerInstance = new registration.listener
|
|
479
|
+
let listenerInstance
|
|
480
|
+
if (typeof registration.listener === 'function') {
|
|
481
|
+
listenerInstance = new registration.listener()
|
|
485
482
|
} else {
|
|
486
|
-
listenerInstance = registration.listener
|
|
483
|
+
listenerInstance = registration.listener
|
|
487
484
|
}
|
|
488
|
-
const shouldQueue =
|
|
485
|
+
const shouldQueue =
|
|
486
|
+
'queue' in listenerInstance ||
|
|
487
|
+
registration.queue !== undefined ||
|
|
488
|
+
registration.connection !== undefined ||
|
|
489
|
+
registration.delay !== undefined
|
|
489
490
|
if (shouldQueue && this.queueManager) {
|
|
490
|
-
const queue = listenerInstance.queue || registration.queue
|
|
491
|
-
const connection = listenerInstance.connection || registration.connection
|
|
492
|
-
const delay = listenerInstance.delay || registration.delay
|
|
491
|
+
const queue = listenerInstance.queue || registration.queue
|
|
492
|
+
const connection = listenerInstance.connection || registration.connection
|
|
493
|
+
const delay = listenerInstance.delay || registration.delay
|
|
493
494
|
const queueJob = {
|
|
494
|
-
type:
|
|
495
|
+
type: 'event-listener',
|
|
495
496
|
event: eventName,
|
|
496
497
|
listener: listenerInstance.constructor.name,
|
|
497
498
|
eventData: this.serializeEvent(event),
|
|
498
499
|
handle: async () => {
|
|
499
|
-
await listenerInstance.handle(event)
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
await this.queueManager.push(queueJob, queue, connection, delay)
|
|
500
|
+
await listenerInstance.handle(event)
|
|
501
|
+
},
|
|
502
|
+
}
|
|
503
|
+
await this.queueManager.push(queueJob, queue, connection, delay)
|
|
503
504
|
} else {
|
|
504
|
-
await listenerInstance.handle(event)
|
|
505
|
+
await listenerInstance.handle(event)
|
|
505
506
|
}
|
|
506
507
|
} catch (error) {
|
|
507
|
-
this.core.logger.error(`[EventManager] Error in listener for event ${eventName}:`, error)
|
|
508
|
+
this.core.logger.error(`[EventManager] Error in listener for event ${eventName}:`, error)
|
|
508
509
|
}
|
|
509
510
|
}
|
|
510
511
|
}
|
|
511
512
|
serializeEvent(event) {
|
|
512
|
-
const data = {}
|
|
513
|
+
const data = {}
|
|
513
514
|
for (const [key, value] of Object.entries(event)) {
|
|
514
|
-
if (!key.startsWith(
|
|
515
|
-
data[key] = value
|
|
515
|
+
if (!key.startsWith('_') && typeof value !== 'function') {
|
|
516
|
+
data[key] = value
|
|
516
517
|
}
|
|
517
518
|
}
|
|
518
|
-
return data
|
|
519
|
+
return data
|
|
519
520
|
}
|
|
520
521
|
getListeners(event) {
|
|
521
522
|
if (event) {
|
|
522
|
-
const eventKey = typeof event ===
|
|
523
|
-
return this.listeners.get(eventKey) || []
|
|
523
|
+
const eventKey = typeof event === 'string' ? event : event
|
|
524
|
+
return this.listeners.get(eventKey) || []
|
|
524
525
|
}
|
|
525
|
-
const all = []
|
|
526
|
+
const all = []
|
|
526
527
|
for (const registrations of this.listeners.values()) {
|
|
527
|
-
all.push(...registrations)
|
|
528
|
+
all.push(...registrations)
|
|
528
529
|
}
|
|
529
|
-
return all
|
|
530
|
+
return all
|
|
530
531
|
}
|
|
531
532
|
clear() {
|
|
532
|
-
this.listeners.clear()
|
|
533
|
+
this.listeners.clear()
|
|
533
534
|
}
|
|
534
535
|
}
|
|
535
536
|
// ../core/src/exceptions/GravitoException.ts
|
|
536
537
|
class GravitoException extends Error {
|
|
537
|
-
status
|
|
538
|
-
code
|
|
539
|
-
i18nKey
|
|
540
|
-
i18nParams
|
|
538
|
+
status
|
|
539
|
+
code
|
|
540
|
+
i18nKey
|
|
541
|
+
i18nParams
|
|
541
542
|
constructor(status, code, options = {}) {
|
|
542
|
-
super(options.message)
|
|
543
|
-
this.name =
|
|
544
|
-
this.status = status
|
|
545
|
-
this.cause = options.cause
|
|
546
|
-
this.code = code
|
|
543
|
+
super(options.message)
|
|
544
|
+
this.name = 'GravitoException'
|
|
545
|
+
this.status = status
|
|
546
|
+
this.cause = options.cause
|
|
547
|
+
this.code = code
|
|
547
548
|
if (options.i18nKey) {
|
|
548
|
-
this.i18nKey = options.i18nKey
|
|
549
|
+
this.i18nKey = options.i18nKey
|
|
549
550
|
}
|
|
550
551
|
if (options.i18nParams) {
|
|
551
|
-
this.i18nParams = options.i18nParams
|
|
552
|
+
this.i18nParams = options.i18nParams
|
|
552
553
|
}
|
|
553
554
|
}
|
|
554
555
|
getLocalizedMessage(t) {
|
|
555
556
|
if (this.i18nKey) {
|
|
556
|
-
return t(this.i18nKey, this.i18nParams)
|
|
557
|
+
return t(this.i18nKey, this.i18nParams)
|
|
557
558
|
}
|
|
558
|
-
return this.message
|
|
559
|
+
return this.message
|
|
559
560
|
}
|
|
560
561
|
}
|
|
561
562
|
// ../core/src/exceptions/HttpException.ts
|
|
562
563
|
class HttpException extends GravitoException {
|
|
563
564
|
constructor(status, options = {}) {
|
|
564
|
-
super(status,
|
|
565
|
-
this.name =
|
|
565
|
+
super(status, 'HTTP_ERROR', options)
|
|
566
|
+
this.name = 'HttpException'
|
|
566
567
|
}
|
|
567
568
|
}
|
|
568
569
|
// ../core/src/exceptions/ModelNotFoundException.ts
|
|
569
570
|
class ModelNotFoundException extends GravitoException {
|
|
570
|
-
model
|
|
571
|
-
id
|
|
571
|
+
model
|
|
572
|
+
id
|
|
572
573
|
constructor(model, id) {
|
|
573
|
-
super(404,
|
|
574
|
+
super(404, 'NOT_FOUND', {
|
|
574
575
|
message: `${model} not found.`,
|
|
575
|
-
i18nKey:
|
|
576
|
-
i18nParams: { model, id: String(id ??
|
|
577
|
-
})
|
|
578
|
-
this.model = model
|
|
576
|
+
i18nKey: 'errors.model.not_found',
|
|
577
|
+
i18nParams: { model, id: String(id ?? '') },
|
|
578
|
+
})
|
|
579
|
+
this.model = model
|
|
579
580
|
if (id !== undefined) {
|
|
580
|
-
this.id = id
|
|
581
|
+
this.id = id
|
|
581
582
|
}
|
|
582
583
|
}
|
|
583
584
|
}
|
|
584
585
|
// ../core/src/exceptions/ValidationException.ts
|
|
585
586
|
class ValidationException extends GravitoException {
|
|
586
|
-
errors
|
|
587
|
-
redirectTo
|
|
588
|
-
input
|
|
589
|
-
constructor(errors, message =
|
|
590
|
-
super(422,
|
|
587
|
+
errors
|
|
588
|
+
redirectTo
|
|
589
|
+
input
|
|
590
|
+
constructor(errors, message = 'Validation failed') {
|
|
591
|
+
super(422, 'VALIDATION_ERROR', {
|
|
591
592
|
message,
|
|
592
|
-
i18nKey:
|
|
593
|
-
})
|
|
594
|
-
this.errors = errors
|
|
593
|
+
i18nKey: 'errors.validation.failed',
|
|
594
|
+
})
|
|
595
|
+
this.errors = errors
|
|
595
596
|
}
|
|
596
597
|
withRedirect(url) {
|
|
597
|
-
this.redirectTo = url
|
|
598
|
-
return this
|
|
598
|
+
this.redirectTo = url
|
|
599
|
+
return this
|
|
599
600
|
}
|
|
600
601
|
withInput(input) {
|
|
601
|
-
this.input = input
|
|
602
|
-
return this
|
|
602
|
+
this.input = input
|
|
603
|
+
return this
|
|
603
604
|
}
|
|
604
605
|
}
|
|
605
606
|
// ../core/src/GlobalErrorHandlers.ts
|
|
606
|
-
var stateKey = Symbol.for(
|
|
607
|
+
var stateKey = Symbol.for('gravito.core.globalErrorHandlers')
|
|
607
608
|
function getGlobalState() {
|
|
608
|
-
const g = globalThis
|
|
609
|
-
const existing = g[stateKey]
|
|
609
|
+
const g = globalThis
|
|
610
|
+
const existing = g[stateKey]
|
|
610
611
|
if (existing) {
|
|
611
|
-
return existing
|
|
612
|
+
return existing
|
|
612
613
|
}
|
|
613
614
|
const created = {
|
|
614
615
|
nextId: 1,
|
|
615
|
-
sinks: new Map,
|
|
616
|
+
sinks: new Map(),
|
|
616
617
|
listenersInstalled: false,
|
|
617
618
|
onUnhandledRejection: undefined,
|
|
618
|
-
onUncaughtException: undefined
|
|
619
|
-
}
|
|
620
|
-
g[stateKey] = created
|
|
621
|
-
return created
|
|
619
|
+
onUncaughtException: undefined,
|
|
620
|
+
}
|
|
621
|
+
g[stateKey] = created
|
|
622
|
+
return created
|
|
622
623
|
}
|
|
623
624
|
function offProcess(event, listener) {
|
|
624
|
-
const p = process
|
|
625
|
-
if (typeof p.off ===
|
|
626
|
-
p.off(event, listener)
|
|
627
|
-
return
|
|
625
|
+
const p = process
|
|
626
|
+
if (typeof p.off === 'function') {
|
|
627
|
+
p.off(event, listener)
|
|
628
|
+
return
|
|
628
629
|
}
|
|
629
|
-
if (typeof p.removeListener ===
|
|
630
|
-
p.removeListener(event, listener)
|
|
630
|
+
if (typeof p.removeListener === 'function') {
|
|
631
|
+
p.removeListener(event, listener)
|
|
631
632
|
}
|
|
632
633
|
}
|
|
633
634
|
function safeMessageFromUnknown(error) {
|
|
634
635
|
if (error instanceof Error) {
|
|
635
|
-
return error.message ||
|
|
636
|
+
return error.message || 'Error'
|
|
636
637
|
}
|
|
637
|
-
if (typeof error ===
|
|
638
|
-
return error
|
|
638
|
+
if (typeof error === 'string') {
|
|
639
|
+
return error
|
|
639
640
|
}
|
|
640
641
|
try {
|
|
641
|
-
return JSON.stringify(error)
|
|
642
|
+
return JSON.stringify(error)
|
|
642
643
|
} catch {
|
|
643
|
-
return String(error)
|
|
644
|
+
return String(error)
|
|
644
645
|
}
|
|
645
646
|
}
|
|
646
647
|
async function handleProcessError(kind, error) {
|
|
647
|
-
const state = getGlobalState()
|
|
648
|
+
const state = getGlobalState()
|
|
648
649
|
if (state.sinks.size === 0) {
|
|
649
|
-
return
|
|
650
|
+
return
|
|
650
651
|
}
|
|
651
|
-
const isProduction = false
|
|
652
|
-
let shouldExit = false
|
|
653
|
-
let exitCode = 1
|
|
654
|
-
let exitTimer
|
|
652
|
+
const isProduction = false
|
|
653
|
+
let shouldExit = false
|
|
654
|
+
let exitCode = 1
|
|
655
|
+
let exitTimer
|
|
655
656
|
try {
|
|
656
|
-
const sinks = Array.from(state.sinks.values())
|
|
657
|
-
const prepared = await Promise.all(
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
657
|
+
const sinks = Array.from(state.sinks.values())
|
|
658
|
+
const prepared = await Promise.all(
|
|
659
|
+
sinks.map(async (sink) => {
|
|
660
|
+
const defaultExit =
|
|
661
|
+
sink.mode === 'exit' || (sink.mode === 'exitInProduction' && isProduction)
|
|
662
|
+
let ctx = {
|
|
663
|
+
...(sink.core ? { core: sink.core } : {}),
|
|
664
|
+
kind,
|
|
665
|
+
error,
|
|
666
|
+
isProduction,
|
|
667
|
+
timestamp: Date.now(),
|
|
668
|
+
exit: defaultExit,
|
|
669
|
+
exitCode: sink.exitCode ?? 1,
|
|
670
|
+
gracePeriodMs: sink.gracePeriodMs ?? 250,
|
|
671
|
+
}
|
|
672
|
+
if (sink.core) {
|
|
673
|
+
ctx = await sink.core.hooks.applyFilters('processError:context', ctx)
|
|
674
|
+
}
|
|
675
|
+
return { sink, ctx }
|
|
676
|
+
})
|
|
677
|
+
)
|
|
678
|
+
const exitTargets = prepared
|
|
679
|
+
.map((p) => p.ctx)
|
|
680
|
+
.filter((ctx) => (ctx.exit ?? false) && (ctx.exitCode ?? 1) >= 0)
|
|
681
|
+
shouldExit = exitTargets.length > 0
|
|
682
|
+
const gracePeriodMs = Math.max(0, ...exitTargets.map((c) => c.gracePeriodMs ?? 250))
|
|
683
|
+
exitCode = Math.max(0, ...exitTargets.map((c) => c.exitCode ?? 1))
|
|
678
684
|
if (shouldExit) {
|
|
679
685
|
exitTimer = setTimeout(() => {
|
|
680
|
-
process.exit(exitCode)
|
|
681
|
-
}, gracePeriodMs)
|
|
682
|
-
exitTimer.unref?.()
|
|
683
|
-
}
|
|
684
|
-
await Promise.all(
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
686
|
+
process.exit(exitCode)
|
|
687
|
+
}, gracePeriodMs)
|
|
688
|
+
exitTimer.unref?.()
|
|
689
|
+
}
|
|
690
|
+
await Promise.all(
|
|
691
|
+
prepared.map(async ({ sink, ctx }) => {
|
|
692
|
+
const logger = sink.logger ?? sink.core?.logger
|
|
693
|
+
const logLevel = ctx.logLevel ?? 'error'
|
|
694
|
+
if (logger && logLevel !== 'none') {
|
|
695
|
+
const message = safeMessageFromUnknown(ctx.error)
|
|
696
|
+
const msg = ctx.logMessage ?? `[${ctx.kind}] ${message}`
|
|
697
|
+
if (logLevel === 'error') {
|
|
698
|
+
logger.error(msg, ctx.error)
|
|
699
|
+
} else if (logLevel === 'warn') {
|
|
700
|
+
logger.warn(msg, ctx.error)
|
|
701
|
+
} else {
|
|
702
|
+
logger.info(msg, ctx.error)
|
|
703
|
+
}
|
|
696
704
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
}
|
|
701
|
-
|
|
705
|
+
if (sink.core) {
|
|
706
|
+
await sink.core.hooks.doAction('processError:report', ctx)
|
|
707
|
+
}
|
|
708
|
+
})
|
|
709
|
+
)
|
|
702
710
|
} catch (e) {
|
|
703
|
-
console.error(
|
|
711
|
+
console.error('[@gravito/core] Failed to handle process-level error:', e)
|
|
704
712
|
} finally {
|
|
705
713
|
if (shouldExit) {
|
|
706
|
-
clearTimeout(exitTimer)
|
|
707
|
-
process.exit(exitCode)
|
|
714
|
+
clearTimeout(exitTimer)
|
|
715
|
+
process.exit(exitCode)
|
|
708
716
|
}
|
|
709
717
|
}
|
|
710
718
|
}
|
|
711
719
|
function ensureListenersInstalled() {
|
|
712
|
-
const state = getGlobalState()
|
|
720
|
+
const state = getGlobalState()
|
|
713
721
|
if (state.listenersInstalled) {
|
|
714
|
-
return
|
|
722
|
+
return
|
|
715
723
|
}
|
|
716
|
-
if (typeof process ===
|
|
717
|
-
return
|
|
724
|
+
if (typeof process === 'undefined' || typeof process.on !== 'function') {
|
|
725
|
+
return
|
|
718
726
|
}
|
|
719
727
|
state.onUnhandledRejection = (reason) => {
|
|
720
|
-
handleProcessError(
|
|
721
|
-
}
|
|
728
|
+
handleProcessError('unhandledRejection', reason)
|
|
729
|
+
}
|
|
722
730
|
state.onUncaughtException = (error) => {
|
|
723
|
-
handleProcessError(
|
|
724
|
-
}
|
|
725
|
-
process.on(
|
|
726
|
-
process.on(
|
|
727
|
-
state.listenersInstalled = true
|
|
731
|
+
handleProcessError('uncaughtException', error)
|
|
732
|
+
}
|
|
733
|
+
process.on('unhandledRejection', state.onUnhandledRejection)
|
|
734
|
+
process.on('uncaughtException', state.onUncaughtException)
|
|
735
|
+
state.listenersInstalled = true
|
|
728
736
|
}
|
|
729
737
|
function teardownListenersIfUnused() {
|
|
730
|
-
const state = getGlobalState()
|
|
738
|
+
const state = getGlobalState()
|
|
731
739
|
if (!state.listenersInstalled || state.sinks.size > 0) {
|
|
732
|
-
return
|
|
740
|
+
return
|
|
733
741
|
}
|
|
734
742
|
if (state.onUnhandledRejection) {
|
|
735
|
-
offProcess(
|
|
743
|
+
offProcess('unhandledRejection', state.onUnhandledRejection)
|
|
736
744
|
}
|
|
737
745
|
if (state.onUncaughtException) {
|
|
738
|
-
offProcess(
|
|
746
|
+
offProcess('uncaughtException', state.onUncaughtException)
|
|
739
747
|
}
|
|
740
|
-
state.onUnhandledRejection = undefined
|
|
741
|
-
state.onUncaughtException = undefined
|
|
742
|
-
state.listenersInstalled = false
|
|
748
|
+
state.onUnhandledRejection = undefined
|
|
749
|
+
state.onUncaughtException = undefined
|
|
750
|
+
state.listenersInstalled = false
|
|
743
751
|
}
|
|
744
752
|
function registerGlobalErrorHandlers(options = {}) {
|
|
745
|
-
const state = getGlobalState()
|
|
746
|
-
ensureListenersInstalled()
|
|
747
|
-
const id = state.nextId
|
|
753
|
+
const state = getGlobalState()
|
|
754
|
+
ensureListenersInstalled()
|
|
755
|
+
const id = state.nextId++
|
|
748
756
|
state.sinks.set(id, {
|
|
749
757
|
...options,
|
|
750
|
-
mode: options.mode ??
|
|
751
|
-
})
|
|
758
|
+
mode: options.mode ?? 'exitInProduction',
|
|
759
|
+
})
|
|
752
760
|
return () => {
|
|
753
|
-
state.sinks.delete(id)
|
|
754
|
-
teardownListenersIfUnused()
|
|
755
|
-
}
|
|
761
|
+
state.sinks.delete(id)
|
|
762
|
+
teardownListenersIfUnused()
|
|
763
|
+
}
|
|
756
764
|
}
|
|
757
765
|
// ../core/src/HookManager.ts
|
|
758
766
|
class HookManager {
|
|
759
|
-
filters = new Map
|
|
760
|
-
actions = new Map
|
|
767
|
+
filters = new Map()
|
|
768
|
+
actions = new Map()
|
|
761
769
|
addFilter(hook, callback) {
|
|
762
770
|
if (!this.filters.has(hook)) {
|
|
763
|
-
this.filters.set(hook, [])
|
|
771
|
+
this.filters.set(hook, [])
|
|
764
772
|
}
|
|
765
|
-
this.filters.get(hook)?.push(callback)
|
|
773
|
+
this.filters.get(hook)?.push(callback)
|
|
766
774
|
}
|
|
767
775
|
async applyFilters(hook, initialValue, ...args) {
|
|
768
|
-
const callbacks = this.filters.get(hook) || []
|
|
769
|
-
let value = initialValue
|
|
776
|
+
const callbacks = this.filters.get(hook) || []
|
|
777
|
+
let value = initialValue
|
|
770
778
|
for (const callback of callbacks) {
|
|
771
779
|
try {
|
|
772
|
-
value = await callback(value, ...args)
|
|
780
|
+
value = await callback(value, ...args)
|
|
773
781
|
} catch (error) {
|
|
774
|
-
console.error(`[HookManager] Error in filter '${hook}':`, error)
|
|
782
|
+
console.error(`[HookManager] Error in filter '${hook}':`, error)
|
|
775
783
|
}
|
|
776
784
|
}
|
|
777
|
-
return value
|
|
785
|
+
return value
|
|
778
786
|
}
|
|
779
787
|
addAction(hook, callback) {
|
|
780
788
|
if (!this.actions.has(hook)) {
|
|
781
|
-
this.actions.set(hook, [])
|
|
789
|
+
this.actions.set(hook, [])
|
|
782
790
|
}
|
|
783
|
-
this.actions.get(hook)?.push(callback)
|
|
791
|
+
this.actions.get(hook)?.push(callback)
|
|
784
792
|
}
|
|
785
793
|
async doAction(hook, args) {
|
|
786
|
-
const callbacks = this.actions.get(hook) || []
|
|
794
|
+
const callbacks = this.actions.get(hook) || []
|
|
787
795
|
for (const callback of callbacks) {
|
|
788
796
|
try {
|
|
789
|
-
await callback(args)
|
|
797
|
+
await callback(args)
|
|
790
798
|
} catch (error) {
|
|
791
|
-
console.error(`[HookManager] Error in action '${hook}':`, error)
|
|
799
|
+
console.error(`[HookManager] Error in action '${hook}':`, error)
|
|
792
800
|
}
|
|
793
801
|
}
|
|
794
802
|
}
|
|
795
803
|
}
|
|
796
804
|
// ../core/src/helpers/response.ts
|
|
797
805
|
function fail(message, code, details) {
|
|
798
|
-
const error = { message }
|
|
806
|
+
const error = { message }
|
|
799
807
|
if (code !== undefined) {
|
|
800
|
-
error.code = code
|
|
808
|
+
error.code = code
|
|
801
809
|
}
|
|
802
810
|
if (details !== undefined) {
|
|
803
|
-
error.details = details
|
|
811
|
+
error.details = details
|
|
804
812
|
}
|
|
805
|
-
return { success: false, error }
|
|
813
|
+
return { success: false, error }
|
|
806
814
|
}
|
|
807
815
|
// ../core/src/http/CookieJar.ts
|
|
808
816
|
class CookieJar {
|
|
809
|
-
encrypter
|
|
810
|
-
queued = new Map
|
|
817
|
+
encrypter
|
|
818
|
+
queued = new Map()
|
|
811
819
|
constructor(encrypter) {
|
|
812
|
-
this.encrypter = encrypter
|
|
820
|
+
this.encrypter = encrypter
|
|
813
821
|
}
|
|
814
822
|
queue(name, value, minutes = 60, options = {}) {
|
|
815
823
|
if (minutes && !options.maxAge) {
|
|
816
|
-
options.maxAge = minutes * 60
|
|
824
|
+
options.maxAge = minutes * 60
|
|
817
825
|
}
|
|
818
|
-
let finalValue = value
|
|
826
|
+
let finalValue = value
|
|
819
827
|
if (options.encrypt) {
|
|
820
828
|
if (!this.encrypter) {
|
|
821
|
-
throw new Error(
|
|
829
|
+
throw new Error('Encryption is not available. Ensure APP_KEY is set.')
|
|
822
830
|
}
|
|
823
|
-
finalValue = this.encrypter.encrypt(value)
|
|
831
|
+
finalValue = this.encrypter.encrypt(value)
|
|
824
832
|
}
|
|
825
|
-
this.queued.set(name, { value: finalValue, options })
|
|
833
|
+
this.queued.set(name, { value: finalValue, options })
|
|
826
834
|
}
|
|
827
835
|
forever(name, value, options = {}) {
|
|
828
|
-
this.queue(name, value, 2628000, options)
|
|
836
|
+
this.queue(name, value, 2628000, options)
|
|
829
837
|
}
|
|
830
838
|
forget(name, options = {}) {
|
|
831
|
-
this.queue(name,
|
|
839
|
+
this.queue(name, '', 0, { ...options, maxAge: 0, expires: new Date(0) })
|
|
832
840
|
}
|
|
833
841
|
serializeCookie(name, value, opts) {
|
|
834
|
-
const parts = [`${name}=${encodeURIComponent(value)}`]
|
|
842
|
+
const parts = [`${name}=${encodeURIComponent(value)}`]
|
|
835
843
|
if (opts.maxAge !== undefined) {
|
|
836
|
-
parts.push(`Max-Age=${opts.maxAge}`)
|
|
844
|
+
parts.push(`Max-Age=${opts.maxAge}`)
|
|
837
845
|
}
|
|
838
846
|
if (opts.expires) {
|
|
839
|
-
parts.push(`Expires=${opts.expires.toUTCString()}`)
|
|
847
|
+
parts.push(`Expires=${opts.expires.toUTCString()}`)
|
|
840
848
|
}
|
|
841
849
|
if (opts.path) {
|
|
842
|
-
parts.push(`Path=${opts.path}`)
|
|
850
|
+
parts.push(`Path=${opts.path}`)
|
|
843
851
|
}
|
|
844
852
|
if (opts.domain) {
|
|
845
|
-
parts.push(`Domain=${opts.domain}`)
|
|
853
|
+
parts.push(`Domain=${opts.domain}`)
|
|
846
854
|
}
|
|
847
855
|
if (opts.secure) {
|
|
848
|
-
parts.push(
|
|
856
|
+
parts.push('Secure')
|
|
849
857
|
}
|
|
850
858
|
if (opts.httpOnly) {
|
|
851
|
-
parts.push(
|
|
859
|
+
parts.push('HttpOnly')
|
|
852
860
|
}
|
|
853
861
|
if (opts.sameSite) {
|
|
854
|
-
parts.push(`SameSite=${opts.sameSite}`)
|
|
862
|
+
parts.push(`SameSite=${opts.sameSite}`)
|
|
855
863
|
}
|
|
856
|
-
return parts.join(
|
|
864
|
+
return parts.join('; ')
|
|
857
865
|
}
|
|
858
866
|
attach(c) {
|
|
859
867
|
for (const [name, { value, options }] of this.queued) {
|
|
860
|
-
c.header(
|
|
868
|
+
c.header('Set-Cookie', this.serializeCookie(name, value, options), { append: true })
|
|
861
869
|
}
|
|
862
870
|
}
|
|
863
871
|
}
|
|
864
872
|
// ../core/src/Logger.ts
|
|
865
873
|
class ConsoleLogger {
|
|
866
874
|
debug(message, ...args) {
|
|
867
|
-
console.debug(`[DEBUG] ${message}`, ...args)
|
|
875
|
+
console.debug(`[DEBUG] ${message}`, ...args)
|
|
868
876
|
}
|
|
869
877
|
info(message, ...args) {
|
|
870
|
-
console.info(`[INFO] ${message}`, ...args)
|
|
878
|
+
console.info(`[INFO] ${message}`, ...args)
|
|
871
879
|
}
|
|
872
880
|
warn(message, ...args) {
|
|
873
|
-
console.warn(`[WARN] ${message}`, ...args)
|
|
881
|
+
console.warn(`[WARN] ${message}`, ...args)
|
|
874
882
|
}
|
|
875
883
|
error(message, ...args) {
|
|
876
|
-
console.error(`[ERROR] ${message}`, ...args)
|
|
884
|
+
console.error(`[ERROR] ${message}`, ...args)
|
|
877
885
|
}
|
|
878
886
|
}
|
|
879
887
|
// ../core/src/adapters/bun/BunRequest.ts
|
|
880
888
|
class BunRequest {
|
|
881
|
-
raw
|
|
882
|
-
_url
|
|
883
|
-
_params = {}
|
|
884
|
-
_query = null
|
|
885
|
-
_validated = {}
|
|
889
|
+
raw
|
|
890
|
+
_url
|
|
891
|
+
_params = {}
|
|
892
|
+
_query = null
|
|
893
|
+
_validated = {}
|
|
886
894
|
constructor(raw, params = {}) {
|
|
887
|
-
this.raw = raw
|
|
888
|
-
this._url = new URL(raw.url)
|
|
889
|
-
this._params = params
|
|
895
|
+
this.raw = raw
|
|
896
|
+
this._url = new URL(raw.url)
|
|
897
|
+
this._params = params
|
|
890
898
|
}
|
|
891
899
|
setParams(params) {
|
|
892
|
-
this._params = params
|
|
900
|
+
this._params = params
|
|
893
901
|
}
|
|
894
902
|
get url() {
|
|
895
|
-
return this.raw.url
|
|
903
|
+
return this.raw.url
|
|
896
904
|
}
|
|
897
905
|
get method() {
|
|
898
|
-
return this.raw.method
|
|
906
|
+
return this.raw.method
|
|
899
907
|
}
|
|
900
908
|
get path() {
|
|
901
|
-
return this._url.pathname
|
|
909
|
+
return this._url.pathname
|
|
902
910
|
}
|
|
903
911
|
param(name) {
|
|
904
|
-
return this._params[name]
|
|
912
|
+
return this._params[name]
|
|
905
913
|
}
|
|
906
914
|
params() {
|
|
907
|
-
return this._params
|
|
915
|
+
return this._params
|
|
908
916
|
}
|
|
909
917
|
query(name) {
|
|
910
918
|
if (!this._query) {
|
|
911
|
-
this.parseQuery()
|
|
919
|
+
this.parseQuery()
|
|
912
920
|
}
|
|
913
|
-
const val = this._query?.[name]
|
|
921
|
+
const val = this._query?.[name]
|
|
914
922
|
if (Array.isArray(val)) {
|
|
915
|
-
return val[0]
|
|
923
|
+
return val[0]
|
|
916
924
|
}
|
|
917
|
-
return val
|
|
925
|
+
return val
|
|
918
926
|
}
|
|
919
927
|
queries() {
|
|
920
928
|
if (!this._query) {
|
|
921
|
-
this.parseQuery()
|
|
929
|
+
this.parseQuery()
|
|
922
930
|
}
|
|
923
|
-
return this._query
|
|
931
|
+
return this._query
|
|
924
932
|
}
|
|
925
933
|
header(name) {
|
|
926
934
|
if (name) {
|
|
927
|
-
return this.raw.headers.get(name) || undefined
|
|
935
|
+
return this.raw.headers.get(name) || undefined
|
|
928
936
|
}
|
|
929
|
-
const headers = {}
|
|
937
|
+
const headers = {}
|
|
930
938
|
this.raw.headers.forEach((value, key) => {
|
|
931
|
-
headers[key] = value
|
|
932
|
-
})
|
|
933
|
-
return headers
|
|
939
|
+
headers[key] = value
|
|
940
|
+
})
|
|
941
|
+
return headers
|
|
934
942
|
}
|
|
935
943
|
async json() {
|
|
936
|
-
return this.raw.json()
|
|
944
|
+
return this.raw.json()
|
|
937
945
|
}
|
|
938
946
|
async text() {
|
|
939
|
-
return this.raw.text()
|
|
947
|
+
return this.raw.text()
|
|
940
948
|
}
|
|
941
949
|
async formData() {
|
|
942
|
-
return this.raw.formData()
|
|
950
|
+
return this.raw.formData()
|
|
943
951
|
}
|
|
944
952
|
async arrayBuffer() {
|
|
945
|
-
return this.raw.arrayBuffer()
|
|
953
|
+
return this.raw.arrayBuffer()
|
|
946
954
|
}
|
|
947
955
|
setValidated(target, data2) {
|
|
948
|
-
this._validated[target] = data2
|
|
956
|
+
this._validated[target] = data2
|
|
949
957
|
}
|
|
950
958
|
valid(target) {
|
|
951
|
-
const data2 = this._validated[target]
|
|
959
|
+
const data2 = this._validated[target]
|
|
952
960
|
if (data2 === undefined) {
|
|
953
|
-
throw new Error(`Validation target '${target}' not found or validation failed.`)
|
|
961
|
+
throw new Error(`Validation target '${target}' not found or validation failed.`)
|
|
954
962
|
}
|
|
955
|
-
return data2
|
|
963
|
+
return data2
|
|
956
964
|
}
|
|
957
965
|
parseQuery() {
|
|
958
|
-
this._query = {}
|
|
966
|
+
this._query = {}
|
|
959
967
|
for (const [key, value] of this._url.searchParams) {
|
|
960
968
|
if (this._query[key]) {
|
|
961
969
|
if (Array.isArray(this._query[key])) {
|
|
962
|
-
this._query[key].push(value)
|
|
970
|
+
this._query[key].push(value)
|
|
963
971
|
} else {
|
|
964
|
-
this._query[key] = [this._query[key], value]
|
|
972
|
+
this._query[key] = [this._query[key], value]
|
|
965
973
|
}
|
|
966
974
|
} else {
|
|
967
|
-
this._query[key] = value
|
|
975
|
+
this._query[key] = value
|
|
968
976
|
}
|
|
969
977
|
}
|
|
970
978
|
}
|
|
@@ -972,132 +980,132 @@ class BunRequest {
|
|
|
972
980
|
|
|
973
981
|
// ../core/src/adapters/bun/BunContext.ts
|
|
974
982
|
class BunContext {
|
|
975
|
-
env
|
|
976
|
-
req
|
|
977
|
-
_variables = new Map
|
|
978
|
-
_status = 200
|
|
979
|
-
_headers = new Headers
|
|
980
|
-
_executionCtx
|
|
981
|
-
res
|
|
982
|
-
native
|
|
983
|
+
env
|
|
984
|
+
req
|
|
985
|
+
_variables = new Map()
|
|
986
|
+
_status = 200
|
|
987
|
+
_headers = new Headers()
|
|
988
|
+
_executionCtx
|
|
989
|
+
res
|
|
990
|
+
native
|
|
983
991
|
constructor(request, env = {}, executionCtx) {
|
|
984
|
-
this.env = env
|
|
985
|
-
this.req = new BunRequest(request)
|
|
986
|
-
this._executionCtx = executionCtx
|
|
987
|
-
this.native = { request, env, executionCtx }
|
|
992
|
+
this.env = env
|
|
993
|
+
this.req = new BunRequest(request)
|
|
994
|
+
this._executionCtx = executionCtx
|
|
995
|
+
this.native = { request, env, executionCtx }
|
|
988
996
|
}
|
|
989
997
|
static create(request, env = {}, executionCtx) {
|
|
990
|
-
const instance = new BunContext(request, env, executionCtx)
|
|
998
|
+
const instance = new BunContext(request, env, executionCtx)
|
|
991
999
|
return new Proxy(instance, {
|
|
992
1000
|
get(target, prop, receiver) {
|
|
993
1001
|
if (prop in target) {
|
|
994
|
-
const value = Reflect.get(target, prop, receiver)
|
|
995
|
-
if (typeof value ===
|
|
996
|
-
return value.bind(target)
|
|
1002
|
+
const value = Reflect.get(target, prop, receiver)
|
|
1003
|
+
if (typeof value === 'function') {
|
|
1004
|
+
return value.bind(target)
|
|
997
1005
|
}
|
|
998
|
-
return value
|
|
1006
|
+
return value
|
|
999
1007
|
}
|
|
1000
|
-
if (typeof prop ===
|
|
1001
|
-
return target.get(prop)
|
|
1008
|
+
if (typeof prop === 'string') {
|
|
1009
|
+
return target.get(prop)
|
|
1002
1010
|
}
|
|
1003
|
-
return
|
|
1004
|
-
}
|
|
1005
|
-
})
|
|
1011
|
+
return
|
|
1012
|
+
},
|
|
1013
|
+
})
|
|
1006
1014
|
}
|
|
1007
1015
|
json(data2, status = 200) {
|
|
1008
|
-
this.status(status)
|
|
1009
|
-
this.header(
|
|
1016
|
+
this.status(status)
|
|
1017
|
+
this.header('Content-Type', 'application/json')
|
|
1010
1018
|
this.res = new Response(JSON.stringify(data2), {
|
|
1011
1019
|
status: this._status,
|
|
1012
|
-
headers: this._headers
|
|
1013
|
-
})
|
|
1014
|
-
return this.res
|
|
1020
|
+
headers: this._headers,
|
|
1021
|
+
})
|
|
1022
|
+
return this.res
|
|
1015
1023
|
}
|
|
1016
1024
|
text(text, status = 200) {
|
|
1017
|
-
this.status(status)
|
|
1018
|
-
this.header(
|
|
1025
|
+
this.status(status)
|
|
1026
|
+
this.header('Content-Type', 'text/plain')
|
|
1019
1027
|
this.res = new Response(text, {
|
|
1020
1028
|
status: this._status,
|
|
1021
|
-
headers: this._headers
|
|
1022
|
-
})
|
|
1023
|
-
return this.res
|
|
1029
|
+
headers: this._headers,
|
|
1030
|
+
})
|
|
1031
|
+
return this.res
|
|
1024
1032
|
}
|
|
1025
1033
|
html(html, status = 200) {
|
|
1026
|
-
this.status(status)
|
|
1027
|
-
this.header(
|
|
1034
|
+
this.status(status)
|
|
1035
|
+
this.header('Content-Type', 'text/html')
|
|
1028
1036
|
this.res = new Response(html, {
|
|
1029
1037
|
status: this._status,
|
|
1030
|
-
headers: this._headers
|
|
1031
|
-
})
|
|
1032
|
-
return this.res
|
|
1038
|
+
headers: this._headers,
|
|
1039
|
+
})
|
|
1040
|
+
return this.res
|
|
1033
1041
|
}
|
|
1034
1042
|
redirect(url, status = 302) {
|
|
1035
|
-
this.status(status)
|
|
1036
|
-
this.header(
|
|
1043
|
+
this.status(status)
|
|
1044
|
+
this.header('Location', url)
|
|
1037
1045
|
this.res = new Response(null, {
|
|
1038
1046
|
status: this._status,
|
|
1039
|
-
headers: this._headers
|
|
1040
|
-
})
|
|
1041
|
-
return this.res
|
|
1047
|
+
headers: this._headers,
|
|
1048
|
+
})
|
|
1049
|
+
return this.res
|
|
1042
1050
|
}
|
|
1043
1051
|
body(data2, status = 200) {
|
|
1044
|
-
this.status(status)
|
|
1052
|
+
this.status(status)
|
|
1045
1053
|
this.res = new Response(data2, {
|
|
1046
1054
|
status: this._status,
|
|
1047
|
-
headers: this._headers
|
|
1048
|
-
})
|
|
1049
|
-
return this.res
|
|
1055
|
+
headers: this._headers,
|
|
1056
|
+
})
|
|
1057
|
+
return this.res
|
|
1050
1058
|
}
|
|
1051
1059
|
stream(stream, status = 200) {
|
|
1052
|
-
this.status(status)
|
|
1060
|
+
this.status(status)
|
|
1053
1061
|
this.res = new Response(stream, {
|
|
1054
1062
|
status: this._status,
|
|
1055
|
-
headers: this._headers
|
|
1056
|
-
})
|
|
1057
|
-
return this.res
|
|
1063
|
+
headers: this._headers,
|
|
1064
|
+
})
|
|
1065
|
+
return this.res
|
|
1058
1066
|
}
|
|
1059
1067
|
header(name, value, options) {
|
|
1060
1068
|
if (value === undefined) {
|
|
1061
|
-
return this.req.header(name)
|
|
1069
|
+
return this.req.header(name)
|
|
1062
1070
|
}
|
|
1063
1071
|
if (options?.append) {
|
|
1064
|
-
this._headers.append(name, value)
|
|
1065
|
-
this.res?.headers.append(name, value)
|
|
1072
|
+
this._headers.append(name, value)
|
|
1073
|
+
this.res?.headers.append(name, value)
|
|
1066
1074
|
} else {
|
|
1067
|
-
this._headers.set(name, value)
|
|
1075
|
+
this._headers.set(name, value)
|
|
1068
1076
|
if (this.res) {
|
|
1069
|
-
this.res.headers.set(name, value)
|
|
1077
|
+
this.res.headers.set(name, value)
|
|
1070
1078
|
}
|
|
1071
1079
|
}
|
|
1072
|
-
return
|
|
1080
|
+
return
|
|
1073
1081
|
}
|
|
1074
1082
|
status(code) {
|
|
1075
|
-
this._status = code
|
|
1083
|
+
this._status = code
|
|
1076
1084
|
}
|
|
1077
1085
|
get(key) {
|
|
1078
|
-
return this._variables.get(key)
|
|
1086
|
+
return this._variables.get(key)
|
|
1079
1087
|
}
|
|
1080
1088
|
set(key, value) {
|
|
1081
|
-
this._variables.set(key, value)
|
|
1089
|
+
this._variables.set(key, value)
|
|
1082
1090
|
}
|
|
1083
1091
|
get executionCtx() {
|
|
1084
|
-
return this._executionCtx
|
|
1092
|
+
return this._executionCtx
|
|
1085
1093
|
}
|
|
1086
1094
|
}
|
|
1087
1095
|
|
|
1088
1096
|
// ../core/src/adapters/bun/RadixNode.ts
|
|
1089
1097
|
class RadixNode {
|
|
1090
|
-
segment
|
|
1091
|
-
type
|
|
1092
|
-
children = new Map
|
|
1093
|
-
paramChild = null
|
|
1094
|
-
wildcardChild = null
|
|
1095
|
-
handlers = new Map
|
|
1096
|
-
paramName = null
|
|
1097
|
-
regex = null
|
|
1098
|
-
constructor(segment =
|
|
1099
|
-
this.segment = segment
|
|
1100
|
-
this.type = type
|
|
1098
|
+
segment
|
|
1099
|
+
type
|
|
1100
|
+
children = new Map()
|
|
1101
|
+
paramChild = null
|
|
1102
|
+
wildcardChild = null
|
|
1103
|
+
handlers = new Map()
|
|
1104
|
+
paramName = null
|
|
1105
|
+
regex = null
|
|
1106
|
+
constructor(segment = '', type = 0 /* STATIC */) {
|
|
1107
|
+
this.segment = segment
|
|
1108
|
+
this.type = type
|
|
1101
1109
|
}
|
|
1102
1110
|
toJSON() {
|
|
1103
1111
|
return {
|
|
@@ -1107,890 +1115,903 @@ class RadixNode {
|
|
|
1107
1115
|
paramChild: this.paramChild?.toJSON() || null,
|
|
1108
1116
|
wildcardChild: this.wildcardChild?.toJSON() || null,
|
|
1109
1117
|
paramName: this.paramName,
|
|
1110
|
-
regex: this.regex ? this.regex.source : null
|
|
1111
|
-
}
|
|
1118
|
+
regex: this.regex ? this.regex.source : null,
|
|
1119
|
+
}
|
|
1112
1120
|
}
|
|
1113
1121
|
static fromJSON(json) {
|
|
1114
|
-
const node = new RadixNode(json.segment, json.type)
|
|
1115
|
-
node.paramName = json.paramName
|
|
1122
|
+
const node = new RadixNode(json.segment, json.type)
|
|
1123
|
+
node.paramName = json.paramName
|
|
1116
1124
|
if (json.regex) {
|
|
1117
|
-
node.regex = new RegExp(json.regex)
|
|
1125
|
+
node.regex = new RegExp(json.regex)
|
|
1118
1126
|
}
|
|
1119
1127
|
if (json.children) {
|
|
1120
1128
|
for (const [key, childJson] of json.children) {
|
|
1121
|
-
node.children.set(key, RadixNode.fromJSON(childJson))
|
|
1129
|
+
node.children.set(key, RadixNode.fromJSON(childJson))
|
|
1122
1130
|
}
|
|
1123
1131
|
}
|
|
1124
1132
|
if (json.paramChild) {
|
|
1125
|
-
node.paramChild = RadixNode.fromJSON(json.paramChild)
|
|
1133
|
+
node.paramChild = RadixNode.fromJSON(json.paramChild)
|
|
1126
1134
|
}
|
|
1127
1135
|
if (json.wildcardChild) {
|
|
1128
|
-
node.wildcardChild = RadixNode.fromJSON(json.wildcardChild)
|
|
1136
|
+
node.wildcardChild = RadixNode.fromJSON(json.wildcardChild)
|
|
1129
1137
|
}
|
|
1130
|
-
return node
|
|
1138
|
+
return node
|
|
1131
1139
|
}
|
|
1132
1140
|
}
|
|
1133
1141
|
|
|
1134
1142
|
// ../core/src/adapters/bun/RadixRouter.ts
|
|
1135
1143
|
class RadixRouter {
|
|
1136
|
-
root = new RadixNode
|
|
1137
|
-
globalConstraints = new Map
|
|
1144
|
+
root = new RadixNode()
|
|
1145
|
+
globalConstraints = new Map()
|
|
1138
1146
|
where(param, regex) {
|
|
1139
|
-
this.globalConstraints.set(param, regex)
|
|
1147
|
+
this.globalConstraints.set(param, regex)
|
|
1140
1148
|
}
|
|
1141
1149
|
add(method, path, handlers) {
|
|
1142
|
-
let node = this.root
|
|
1143
|
-
const segments = this.splitPath(path)
|
|
1144
|
-
for (let i = 0;i < segments.length; i++) {
|
|
1145
|
-
const segment = segments[i]
|
|
1146
|
-
if (segment ===
|
|
1150
|
+
let node = this.root
|
|
1151
|
+
const segments = this.splitPath(path)
|
|
1152
|
+
for (let i = 0; i < segments.length; i++) {
|
|
1153
|
+
const segment = segments[i]
|
|
1154
|
+
if (segment === '*') {
|
|
1147
1155
|
if (!node.wildcardChild) {
|
|
1148
|
-
node.wildcardChild = new RadixNode(
|
|
1156
|
+
node.wildcardChild = new RadixNode('*', 2 /* WILDCARD */)
|
|
1149
1157
|
}
|
|
1150
|
-
node = node.wildcardChild
|
|
1151
|
-
break
|
|
1152
|
-
} else if (segment.startsWith(
|
|
1153
|
-
const paramName = segment.slice(1)
|
|
1158
|
+
node = node.wildcardChild
|
|
1159
|
+
break
|
|
1160
|
+
} else if (segment.startsWith(':')) {
|
|
1161
|
+
const paramName = segment.slice(1)
|
|
1154
1162
|
if (!node.paramChild) {
|
|
1155
|
-
const child = new RadixNode(segment, 1 /* PARAM */)
|
|
1156
|
-
child.paramName = paramName
|
|
1157
|
-
const constraint = this.globalConstraints.get(paramName)
|
|
1163
|
+
const child = new RadixNode(segment, 1 /* PARAM */)
|
|
1164
|
+
child.paramName = paramName
|
|
1165
|
+
const constraint = this.globalConstraints.get(paramName)
|
|
1158
1166
|
if (constraint) {
|
|
1159
|
-
child.regex = constraint
|
|
1167
|
+
child.regex = constraint
|
|
1160
1168
|
}
|
|
1161
|
-
node.paramChild = child
|
|
1169
|
+
node.paramChild = child
|
|
1162
1170
|
}
|
|
1163
|
-
node = node.paramChild
|
|
1171
|
+
node = node.paramChild
|
|
1164
1172
|
} else {
|
|
1165
1173
|
if (!node.children.has(segment)) {
|
|
1166
|
-
node.children.set(segment, new RadixNode(segment, 0 /* STATIC */))
|
|
1174
|
+
node.children.set(segment, new RadixNode(segment, 0 /* STATIC */))
|
|
1167
1175
|
}
|
|
1168
|
-
node = node.children.get(segment)
|
|
1176
|
+
node = node.children.get(segment)
|
|
1169
1177
|
}
|
|
1170
1178
|
}
|
|
1171
|
-
node.handlers.set(method.toLowerCase(), handlers)
|
|
1179
|
+
node.handlers.set(method.toLowerCase(), handlers)
|
|
1172
1180
|
}
|
|
1173
1181
|
match(method, path) {
|
|
1174
|
-
const normalizedMethod = method.toLowerCase()
|
|
1175
|
-
if (path ===
|
|
1176
|
-
const handlers = this.root.handlers.get(normalizedMethod)
|
|
1182
|
+
const normalizedMethod = method.toLowerCase()
|
|
1183
|
+
if (path === '/' || path === '') {
|
|
1184
|
+
const handlers = this.root.handlers.get(normalizedMethod)
|
|
1177
1185
|
if (handlers) {
|
|
1178
|
-
return { handlers, params: {} }
|
|
1186
|
+
return { handlers, params: {} }
|
|
1179
1187
|
}
|
|
1180
|
-
return null
|
|
1188
|
+
return null
|
|
1181
1189
|
}
|
|
1182
|
-
const searchPath = path.startsWith(
|
|
1183
|
-
const segments = searchPath.split(
|
|
1184
|
-
return this.matchRecursive(this.root, segments, 0, {}, normalizedMethod)
|
|
1190
|
+
const searchPath = path.startsWith('/') ? path.slice(1) : path
|
|
1191
|
+
const segments = searchPath.split('/')
|
|
1192
|
+
return this.matchRecursive(this.root, segments, 0, {}, normalizedMethod)
|
|
1185
1193
|
}
|
|
1186
1194
|
matchRecursive(node, segments, depth, params, method) {
|
|
1187
1195
|
if (depth >= segments.length) {
|
|
1188
|
-
let handlers = node.handlers.get(method)
|
|
1196
|
+
let handlers = node.handlers.get(method)
|
|
1189
1197
|
if (!handlers) {
|
|
1190
|
-
handlers = node.handlers.get(
|
|
1198
|
+
handlers = node.handlers.get('all')
|
|
1191
1199
|
}
|
|
1192
1200
|
if (handlers) {
|
|
1193
|
-
return { handlers, params }
|
|
1201
|
+
return { handlers, params }
|
|
1194
1202
|
}
|
|
1195
|
-
return null
|
|
1203
|
+
return null
|
|
1196
1204
|
}
|
|
1197
|
-
const segment = segments[depth]
|
|
1198
|
-
const staticChild = node.children.get(segment)
|
|
1205
|
+
const segment = segments[depth]
|
|
1206
|
+
const staticChild = node.children.get(segment)
|
|
1199
1207
|
if (staticChild) {
|
|
1200
|
-
const match = this.matchRecursive(staticChild, segments, depth + 1, params, method)
|
|
1208
|
+
const match = this.matchRecursive(staticChild, segments, depth + 1, params, method)
|
|
1201
1209
|
if (match) {
|
|
1202
|
-
return match
|
|
1210
|
+
return match
|
|
1203
1211
|
}
|
|
1204
1212
|
}
|
|
1205
|
-
const paramChild = node.paramChild
|
|
1213
|
+
const paramChild = node.paramChild
|
|
1206
1214
|
if (paramChild) {
|
|
1207
|
-
if (paramChild.regex && !paramChild.regex.test(segment)) {
|
|
1215
|
+
if (paramChild.regex && !paramChild.regex.test(segment)) {
|
|
1216
|
+
} else {
|
|
1208
1217
|
if (paramChild.paramName) {
|
|
1209
|
-
params[paramChild.paramName] = decodeURIComponent(segment)
|
|
1210
|
-
const match = this.matchRecursive(paramChild, segments, depth + 1, params, method)
|
|
1218
|
+
params[paramChild.paramName] = decodeURIComponent(segment)
|
|
1219
|
+
const match = this.matchRecursive(paramChild, segments, depth + 1, params, method)
|
|
1211
1220
|
if (match) {
|
|
1212
|
-
return match
|
|
1221
|
+
return match
|
|
1213
1222
|
}
|
|
1214
|
-
delete params[paramChild.paramName]
|
|
1223
|
+
delete params[paramChild.paramName]
|
|
1215
1224
|
}
|
|
1216
1225
|
}
|
|
1217
1226
|
}
|
|
1218
1227
|
if (node.wildcardChild) {
|
|
1219
|
-
let handlers = node.wildcardChild.handlers.get(method)
|
|
1228
|
+
let handlers = node.wildcardChild.handlers.get(method)
|
|
1220
1229
|
if (!handlers) {
|
|
1221
|
-
handlers = node.wildcardChild.handlers.get(
|
|
1230
|
+
handlers = node.wildcardChild.handlers.get('all')
|
|
1222
1231
|
}
|
|
1223
1232
|
if (handlers) {
|
|
1224
|
-
return { handlers, params }
|
|
1233
|
+
return { handlers, params }
|
|
1225
1234
|
}
|
|
1226
1235
|
}
|
|
1227
|
-
return null
|
|
1236
|
+
return null
|
|
1228
1237
|
}
|
|
1229
1238
|
splitPath(path) {
|
|
1230
|
-
if (path ===
|
|
1231
|
-
return []
|
|
1239
|
+
if (path === '/' || path === '') {
|
|
1240
|
+
return []
|
|
1232
1241
|
}
|
|
1233
|
-
let p = path
|
|
1234
|
-
if (p.startsWith(
|
|
1235
|
-
p = p.slice(1)
|
|
1242
|
+
let p = path
|
|
1243
|
+
if (p.startsWith('/')) {
|
|
1244
|
+
p = p.slice(1)
|
|
1236
1245
|
}
|
|
1237
|
-
if (p.endsWith(
|
|
1238
|
-
p = p.slice(0, -1)
|
|
1246
|
+
if (p.endsWith('/')) {
|
|
1247
|
+
p = p.slice(0, -1)
|
|
1239
1248
|
}
|
|
1240
|
-
return p.split(
|
|
1249
|
+
return p.split('/')
|
|
1241
1250
|
}
|
|
1242
1251
|
serialize() {
|
|
1243
1252
|
return JSON.stringify({
|
|
1244
1253
|
root: this.root.toJSON(),
|
|
1245
1254
|
globalConstraints: Array.from(this.globalConstraints.entries()).map(([k, v]) => [
|
|
1246
1255
|
k,
|
|
1247
|
-
v.source
|
|
1248
|
-
])
|
|
1249
|
-
})
|
|
1256
|
+
v.source,
|
|
1257
|
+
]),
|
|
1258
|
+
})
|
|
1250
1259
|
}
|
|
1251
1260
|
static fromSerialized(json) {
|
|
1252
|
-
const data2 = JSON.parse(json)
|
|
1253
|
-
const router = new RadixRouter
|
|
1254
|
-
router.root = RadixNode.fromJSON(data2.root)
|
|
1261
|
+
const data2 = JSON.parse(json)
|
|
1262
|
+
const router = new RadixRouter()
|
|
1263
|
+
router.root = RadixNode.fromJSON(data2.root)
|
|
1255
1264
|
if (data2.globalConstraints) {
|
|
1256
1265
|
for (const [key, source] of data2.globalConstraints) {
|
|
1257
|
-
router.globalConstraints.set(key, new RegExp(source))
|
|
1266
|
+
router.globalConstraints.set(key, new RegExp(source))
|
|
1258
1267
|
}
|
|
1259
1268
|
}
|
|
1260
|
-
return router
|
|
1269
|
+
return router
|
|
1261
1270
|
}
|
|
1262
1271
|
}
|
|
1263
1272
|
|
|
1264
1273
|
// ../core/src/adapters/bun/BunNativeAdapter.ts
|
|
1265
1274
|
class BunNativeAdapter {
|
|
1266
|
-
name =
|
|
1267
|
-
version =
|
|
1275
|
+
name = 'bun-native'
|
|
1276
|
+
version = '0.0.1'
|
|
1268
1277
|
get native() {
|
|
1269
|
-
return this
|
|
1278
|
+
return this
|
|
1270
1279
|
}
|
|
1271
|
-
router = new RadixRouter
|
|
1272
|
-
middlewares = []
|
|
1273
|
-
errorHandler = null
|
|
1274
|
-
notFoundHandler = null
|
|
1280
|
+
router = new RadixRouter()
|
|
1281
|
+
middlewares = []
|
|
1282
|
+
errorHandler = null
|
|
1283
|
+
notFoundHandler = null
|
|
1275
1284
|
route(method, path, ...handlers) {
|
|
1276
|
-
this.router.add(method, path, handlers)
|
|
1285
|
+
this.router.add(method, path, handlers)
|
|
1277
1286
|
}
|
|
1278
1287
|
routes(routes) {
|
|
1279
1288
|
for (const route of routes) {
|
|
1280
|
-
this.route(route.method, route.path, ...route.handlers)
|
|
1289
|
+
this.route(route.method, route.path, ...route.handlers)
|
|
1281
1290
|
}
|
|
1282
1291
|
}
|
|
1283
1292
|
use(path, ...middleware) {
|
|
1284
|
-
this.middlewares.push({ path, handlers: middleware })
|
|
1293
|
+
this.middlewares.push({ path, handlers: middleware })
|
|
1285
1294
|
}
|
|
1286
1295
|
useGlobal(...middleware) {
|
|
1287
|
-
this.use(
|
|
1296
|
+
this.use('*', ...middleware)
|
|
1288
1297
|
}
|
|
1289
1298
|
mount(path, subAdapter) {
|
|
1290
|
-
const fullPath = path.endsWith(
|
|
1291
|
-
this.route(
|
|
1292
|
-
const url = new URL(ctx.req.url)
|
|
1293
|
-
const prefix = path.endsWith(
|
|
1294
|
-
console.log(
|
|
1295
|
-
console.log(
|
|
1299
|
+
const fullPath = path.endsWith('/') ? `${path}*` : `${path}/*`
|
|
1300
|
+
this.route('all', fullPath, async (ctx) => {
|
|
1301
|
+
const url = new URL(ctx.req.url)
|
|
1302
|
+
const prefix = path.endsWith('/') ? path.slice(0, -1) : path
|
|
1303
|
+
console.log('[DEBUG] Mount Prefix:', prefix)
|
|
1304
|
+
console.log('[DEBUG] Original Path:', url.pathname)
|
|
1296
1305
|
if (url.pathname.startsWith(prefix)) {
|
|
1297
|
-
const newPath = url.pathname.slice(prefix.length)
|
|
1298
|
-
url.pathname = newPath ===
|
|
1306
|
+
const newPath = url.pathname.slice(prefix.length)
|
|
1307
|
+
url.pathname = newPath === '' ? '/' : newPath
|
|
1299
1308
|
}
|
|
1300
1309
|
const newReq = new Request(url.toString(), {
|
|
1301
1310
|
method: ctx.req.method,
|
|
1302
|
-
headers: ctx.req.raw.headers
|
|
1303
|
-
})
|
|
1304
|
-
const res = await subAdapter.fetch(newReq)
|
|
1311
|
+
headers: ctx.req.raw.headers,
|
|
1312
|
+
})
|
|
1313
|
+
const res = await subAdapter.fetch(newReq)
|
|
1305
1314
|
if (ctx instanceof BunContext) {
|
|
1306
|
-
ctx.res = res
|
|
1315
|
+
ctx.res = res
|
|
1307
1316
|
}
|
|
1308
|
-
return res
|
|
1309
|
-
})
|
|
1317
|
+
return res
|
|
1318
|
+
})
|
|
1310
1319
|
}
|
|
1311
1320
|
createContext(request) {
|
|
1312
|
-
return BunContext.create(request)
|
|
1321
|
+
return BunContext.create(request)
|
|
1313
1322
|
}
|
|
1314
1323
|
onError(handler) {
|
|
1315
|
-
this.errorHandler = handler
|
|
1324
|
+
this.errorHandler = handler
|
|
1316
1325
|
}
|
|
1317
1326
|
onNotFound(handler) {
|
|
1318
|
-
this.notFoundHandler = handler
|
|
1327
|
+
this.notFoundHandler = handler
|
|
1319
1328
|
}
|
|
1320
1329
|
async fetch(request, _server) {
|
|
1321
|
-
const ctx = BunContext.create(request)
|
|
1330
|
+
const ctx = BunContext.create(request)
|
|
1322
1331
|
try {
|
|
1323
|
-
const url = new URL(request.url)
|
|
1324
|
-
const path = url.pathname
|
|
1325
|
-
const method = request.method
|
|
1326
|
-
const match = this.router.match(method, path)
|
|
1327
|
-
const handlers = []
|
|
1332
|
+
const url = new URL(request.url)
|
|
1333
|
+
const path = url.pathname
|
|
1334
|
+
const method = request.method
|
|
1335
|
+
const match = this.router.match(method, path)
|
|
1336
|
+
const handlers = []
|
|
1328
1337
|
for (const mw of this.middlewares) {
|
|
1329
|
-
if (mw.path ===
|
|
1330
|
-
handlers.push(...mw.handlers)
|
|
1338
|
+
if (mw.path === '*' || path.startsWith(mw.path)) {
|
|
1339
|
+
handlers.push(...mw.handlers)
|
|
1331
1340
|
}
|
|
1332
1341
|
}
|
|
1333
1342
|
if (match) {
|
|
1334
1343
|
if (match.params) {
|
|
1335
|
-
ctx.req.setParams(match.params)
|
|
1344
|
+
ctx.req.setParams(match.params)
|
|
1336
1345
|
}
|
|
1337
|
-
handlers.push(...match.handlers)
|
|
1346
|
+
handlers.push(...match.handlers)
|
|
1338
1347
|
} else {
|
|
1339
1348
|
if (this.notFoundHandler) {
|
|
1340
|
-
handlers.push(this.notFoundHandler)
|
|
1341
|
-
} else {
|
|
1349
|
+
handlers.push(this.notFoundHandler)
|
|
1350
|
+
} else {
|
|
1351
|
+
}
|
|
1342
1352
|
}
|
|
1343
|
-
return await this.executeChain(ctx, handlers)
|
|
1353
|
+
return await this.executeChain(ctx, handlers)
|
|
1344
1354
|
} catch (err) {
|
|
1345
1355
|
if (this.errorHandler) {
|
|
1346
1356
|
try {
|
|
1347
|
-
const response2 = await this.errorHandler(err, ctx)
|
|
1357
|
+
const response2 = await this.errorHandler(err, ctx)
|
|
1348
1358
|
if (response2) {
|
|
1349
|
-
return response2
|
|
1359
|
+
return response2
|
|
1350
1360
|
}
|
|
1351
1361
|
} catch (e) {
|
|
1352
|
-
console.error(
|
|
1362
|
+
console.error('Error handler failed', e)
|
|
1353
1363
|
}
|
|
1354
1364
|
}
|
|
1355
|
-
console.error(err)
|
|
1356
|
-
return new Response(
|
|
1365
|
+
console.error(err)
|
|
1366
|
+
return new Response('Internal Server Error', { status: 500 })
|
|
1357
1367
|
}
|
|
1358
1368
|
}
|
|
1359
1369
|
async executeChain(ctx, handlers) {
|
|
1360
|
-
let index = -1
|
|
1370
|
+
let index = -1
|
|
1361
1371
|
const dispatch = async (i) => {
|
|
1362
1372
|
if (i <= index) {
|
|
1363
|
-
throw new Error(
|
|
1373
|
+
throw new Error('next() called multiple times')
|
|
1364
1374
|
}
|
|
1365
|
-
index = i
|
|
1366
|
-
const fn = handlers[i]
|
|
1375
|
+
index = i
|
|
1376
|
+
const fn = handlers[i]
|
|
1367
1377
|
if (!fn) {
|
|
1368
|
-
return
|
|
1378
|
+
return
|
|
1369
1379
|
}
|
|
1370
1380
|
const result = await fn(ctx, async () => {
|
|
1371
|
-
const res = await dispatch(i + 1)
|
|
1381
|
+
const res = await dispatch(i + 1)
|
|
1372
1382
|
if (res && ctx.res !== res) {
|
|
1373
|
-
ctx.res = res
|
|
1383
|
+
ctx.res = res
|
|
1374
1384
|
}
|
|
1375
|
-
return res
|
|
1376
|
-
})
|
|
1377
|
-
return result
|
|
1378
|
-
}
|
|
1379
|
-
const finalResponse = await dispatch(0)
|
|
1380
|
-
if (
|
|
1381
|
-
|
|
1385
|
+
return res
|
|
1386
|
+
})
|
|
1387
|
+
return result
|
|
1388
|
+
}
|
|
1389
|
+
const finalResponse = await dispatch(0)
|
|
1390
|
+
if (
|
|
1391
|
+
finalResponse &&
|
|
1392
|
+
(finalResponse instanceof Response || typeof finalResponse.status === 'number')
|
|
1393
|
+
) {
|
|
1394
|
+
return finalResponse
|
|
1382
1395
|
}
|
|
1383
1396
|
if (ctx.res) {
|
|
1384
|
-
return ctx.res
|
|
1397
|
+
return ctx.res
|
|
1385
1398
|
}
|
|
1386
|
-
return new Response(
|
|
1399
|
+
return new Response('Not Found', { status: 404 })
|
|
1387
1400
|
}
|
|
1388
1401
|
}
|
|
1389
1402
|
|
|
1390
1403
|
// ../core/src/Route.ts
|
|
1391
1404
|
class Route {
|
|
1392
|
-
router
|
|
1393
|
-
method
|
|
1394
|
-
path
|
|
1395
|
-
options
|
|
1405
|
+
router
|
|
1406
|
+
method
|
|
1407
|
+
path
|
|
1408
|
+
options
|
|
1396
1409
|
constructor(router, method, path, options) {
|
|
1397
|
-
this.router = router
|
|
1398
|
-
this.method = method
|
|
1399
|
-
this.path = path
|
|
1400
|
-
this.options = options
|
|
1410
|
+
this.router = router
|
|
1411
|
+
this.method = method
|
|
1412
|
+
this.path = path
|
|
1413
|
+
this.options = options
|
|
1401
1414
|
}
|
|
1402
1415
|
name(name) {
|
|
1403
|
-
this.router.registerName(name, this.method, this.path, this.options)
|
|
1404
|
-
return this
|
|
1416
|
+
this.router.registerName(name, this.method, this.path, this.options)
|
|
1417
|
+
return this
|
|
1405
1418
|
}
|
|
1406
1419
|
}
|
|
1407
1420
|
|
|
1408
1421
|
// ../core/src/Router.ts
|
|
1409
1422
|
function isFormRequestClass(value) {
|
|
1410
|
-
if (typeof value !==
|
|
1411
|
-
return false
|
|
1423
|
+
if (typeof value !== 'function') {
|
|
1424
|
+
return false
|
|
1412
1425
|
}
|
|
1413
1426
|
try {
|
|
1414
|
-
const instance = new value
|
|
1415
|
-
return
|
|
1427
|
+
const instance = new value()
|
|
1428
|
+
return (
|
|
1429
|
+
instance !== null &&
|
|
1430
|
+
typeof instance === 'object' &&
|
|
1431
|
+
'schema' in instance &&
|
|
1432
|
+
'validate' in instance &&
|
|
1433
|
+
typeof instance.validate === 'function'
|
|
1434
|
+
)
|
|
1416
1435
|
} catch {
|
|
1417
|
-
return false
|
|
1436
|
+
return false
|
|
1418
1437
|
}
|
|
1419
1438
|
}
|
|
1420
1439
|
function formRequestToMiddleware(RequestClass) {
|
|
1421
1440
|
return async (ctx, next) => {
|
|
1422
|
-
const request = new RequestClass
|
|
1423
|
-
if (typeof request.validate !==
|
|
1424
|
-
throw new Error(
|
|
1441
|
+
const request = new RequestClass()
|
|
1442
|
+
if (typeof request.validate !== 'function') {
|
|
1443
|
+
throw new Error('Invalid FormRequest: validate() is missing.')
|
|
1425
1444
|
}
|
|
1426
|
-
const result = await request.validate(ctx)
|
|
1445
|
+
const result = await request.validate(ctx)
|
|
1427
1446
|
if (!result.success) {
|
|
1428
|
-
const errorCode = result.error?.error?.code
|
|
1429
|
-
const status = errorCode ===
|
|
1430
|
-
return ctx.json(result.error, status)
|
|
1431
|
-
}
|
|
1432
|
-
ctx.set(
|
|
1433
|
-
await next()
|
|
1434
|
-
return
|
|
1435
|
-
}
|
|
1447
|
+
const errorCode = result.error?.error?.code
|
|
1448
|
+
const status = errorCode === 'AUTHORIZATION_ERROR' ? 403 : 422
|
|
1449
|
+
return ctx.json(result.error, status)
|
|
1450
|
+
}
|
|
1451
|
+
ctx.set('validated', result.data)
|
|
1452
|
+
await next()
|
|
1453
|
+
return
|
|
1454
|
+
}
|
|
1436
1455
|
}
|
|
1437
1456
|
|
|
1438
1457
|
class RouteGroup {
|
|
1439
|
-
router
|
|
1440
|
-
options
|
|
1458
|
+
router
|
|
1459
|
+
options
|
|
1441
1460
|
constructor(router, options) {
|
|
1442
|
-
this.router = router
|
|
1443
|
-
this.options = options
|
|
1461
|
+
this.router = router
|
|
1462
|
+
this.options = options
|
|
1444
1463
|
}
|
|
1445
1464
|
prefix(path) {
|
|
1446
1465
|
return new RouteGroup(this.router, {
|
|
1447
1466
|
...this.options,
|
|
1448
|
-
prefix: (this.options.prefix ||
|
|
1449
|
-
})
|
|
1467
|
+
prefix: (this.options.prefix || '') + path,
|
|
1468
|
+
})
|
|
1450
1469
|
}
|
|
1451
1470
|
middleware(...handlers) {
|
|
1452
|
-
const flattened = handlers.flat()
|
|
1471
|
+
const flattened = handlers.flat()
|
|
1453
1472
|
return new RouteGroup(this.router, {
|
|
1454
1473
|
...this.options,
|
|
1455
|
-
middleware: [...this.options.middleware || [], ...flattened]
|
|
1456
|
-
})
|
|
1474
|
+
middleware: [...(this.options.middleware || []), ...flattened],
|
|
1475
|
+
})
|
|
1457
1476
|
}
|
|
1458
1477
|
group(callback) {
|
|
1459
|
-
callback(this)
|
|
1478
|
+
callback(this)
|
|
1460
1479
|
}
|
|
1461
1480
|
get(path, requestOrHandler, handler) {
|
|
1462
|
-
return this.router.req(
|
|
1481
|
+
return this.router.req('get', path, requestOrHandler, handler, this.options)
|
|
1463
1482
|
}
|
|
1464
1483
|
post(path, requestOrHandler, handler) {
|
|
1465
|
-
return this.router.req(
|
|
1484
|
+
return this.router.req('post', path, requestOrHandler, handler, this.options)
|
|
1466
1485
|
}
|
|
1467
1486
|
put(path, requestOrHandler, handler) {
|
|
1468
|
-
return this.router.req(
|
|
1487
|
+
return this.router.req('put', path, requestOrHandler, handler, this.options)
|
|
1469
1488
|
}
|
|
1470
1489
|
delete(path, requestOrHandler, handler) {
|
|
1471
|
-
return this.router.req(
|
|
1490
|
+
return this.router.req('delete', path, requestOrHandler, handler, this.options)
|
|
1472
1491
|
}
|
|
1473
1492
|
patch(path, requestOrHandler, handler) {
|
|
1474
|
-
return this.router.req(
|
|
1493
|
+
return this.router.req('patch', path, requestOrHandler, handler, this.options)
|
|
1475
1494
|
}
|
|
1476
1495
|
resource(name, controller, options = {}) {
|
|
1477
|
-
const actions = [
|
|
1478
|
-
"index",
|
|
1479
|
-
"create",
|
|
1480
|
-
"store",
|
|
1481
|
-
"show",
|
|
1482
|
-
"edit",
|
|
1483
|
-
"update",
|
|
1484
|
-
"destroy"
|
|
1485
|
-
];
|
|
1496
|
+
const actions = ['index', 'create', 'store', 'show', 'edit', 'update', 'destroy']
|
|
1486
1497
|
const map = {
|
|
1487
|
-
index: { method:
|
|
1488
|
-
create: { method:
|
|
1489
|
-
store: { method:
|
|
1490
|
-
show: { method:
|
|
1491
|
-
edit: { method:
|
|
1492
|
-
update: { method:
|
|
1493
|
-
destroy: { method:
|
|
1494
|
-
}
|
|
1498
|
+
index: { method: 'get', path: `/${name}` },
|
|
1499
|
+
create: { method: 'get', path: `/${name}/create` },
|
|
1500
|
+
store: { method: 'post', path: `/${name}` },
|
|
1501
|
+
show: { method: 'get', path: `/${name}/:id` },
|
|
1502
|
+
edit: { method: 'get', path: `/${name}/:id/edit` },
|
|
1503
|
+
update: { method: 'put', path: `/${name}/:id` },
|
|
1504
|
+
destroy: { method: 'delete', path: `/${name}/:id` },
|
|
1505
|
+
}
|
|
1495
1506
|
const allowed = actions.filter((action) => {
|
|
1496
1507
|
if (options.only) {
|
|
1497
|
-
return options.only.includes(action)
|
|
1508
|
+
return options.only.includes(action)
|
|
1498
1509
|
}
|
|
1499
1510
|
if (options.except) {
|
|
1500
|
-
return !options.except.includes(action)
|
|
1511
|
+
return !options.except.includes(action)
|
|
1501
1512
|
}
|
|
1502
|
-
return true
|
|
1503
|
-
})
|
|
1513
|
+
return true
|
|
1514
|
+
})
|
|
1504
1515
|
for (const action of allowed) {
|
|
1505
|
-
const { method, path } = map[action]
|
|
1506
|
-
if (action ===
|
|
1507
|
-
this.router
|
|
1508
|
-
|
|
1516
|
+
const { method, path } = map[action]
|
|
1517
|
+
if (action === 'update') {
|
|
1518
|
+
this.router
|
|
1519
|
+
.req('put', path, [controller, action], undefined, this.options)
|
|
1520
|
+
.name(`${name}.${action}`)
|
|
1521
|
+
this.router.req('patch', path, [controller, action], undefined, this.options)
|
|
1509
1522
|
} else {
|
|
1510
|
-
this.router
|
|
1523
|
+
this.router
|
|
1524
|
+
.req(method, path, [controller, action], undefined, this.options)
|
|
1525
|
+
.name(`${name}.${action}`)
|
|
1511
1526
|
}
|
|
1512
1527
|
}
|
|
1513
1528
|
}
|
|
1514
1529
|
}
|
|
1515
1530
|
|
|
1516
1531
|
class Router {
|
|
1517
|
-
core
|
|
1518
|
-
controllers = new Map
|
|
1519
|
-
namedRoutes = new Map
|
|
1520
|
-
bindings = new Map
|
|
1532
|
+
core
|
|
1533
|
+
controllers = new Map()
|
|
1534
|
+
namedRoutes = new Map()
|
|
1535
|
+
bindings = new Map()
|
|
1521
1536
|
compile() {
|
|
1522
|
-
const routes = []
|
|
1537
|
+
const routes = []
|
|
1523
1538
|
for (const [name, info] of this.namedRoutes) {
|
|
1524
1539
|
routes.push({
|
|
1525
1540
|
name,
|
|
1526
1541
|
method: info.method,
|
|
1527
1542
|
path: info.path,
|
|
1528
|
-
domain: info.domain
|
|
1529
|
-
})
|
|
1543
|
+
domain: info.domain,
|
|
1544
|
+
})
|
|
1530
1545
|
}
|
|
1531
|
-
return routes
|
|
1546
|
+
return routes
|
|
1532
1547
|
}
|
|
1533
1548
|
registerName(name, method, path, options = {}) {
|
|
1534
|
-
const fullPath = (options.prefix ||
|
|
1549
|
+
const fullPath = (options.prefix || '') + path
|
|
1535
1550
|
this.namedRoutes.set(name, {
|
|
1536
1551
|
method: method.toUpperCase(),
|
|
1537
1552
|
path: fullPath,
|
|
1538
|
-
domain: options.domain
|
|
1539
|
-
})
|
|
1553
|
+
domain: options.domain,
|
|
1554
|
+
})
|
|
1540
1555
|
}
|
|
1541
1556
|
url(name, params = {}, query = {}) {
|
|
1542
|
-
const route = this.namedRoutes.get(name)
|
|
1557
|
+
const route = this.namedRoutes.get(name)
|
|
1543
1558
|
if (!route) {
|
|
1544
|
-
throw new Error(`Named route '${name}' not found`)
|
|
1559
|
+
throw new Error(`Named route '${name}' not found`)
|
|
1545
1560
|
}
|
|
1546
|
-
let path = route.path
|
|
1561
|
+
let path = route.path
|
|
1547
1562
|
path = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => {
|
|
1548
|
-
const value = params[key]
|
|
1563
|
+
const value = params[key]
|
|
1549
1564
|
if (value === undefined || value === null) {
|
|
1550
|
-
throw new Error(`Missing route param '${key}' for route '${name}'`)
|
|
1565
|
+
throw new Error(`Missing route param '${key}' for route '${name}'`)
|
|
1551
1566
|
}
|
|
1552
|
-
return encodeURIComponent(String(value))
|
|
1553
|
-
})
|
|
1554
|
-
const qs = new URLSearchParams
|
|
1567
|
+
return encodeURIComponent(String(value))
|
|
1568
|
+
})
|
|
1569
|
+
const qs = new URLSearchParams()
|
|
1555
1570
|
for (const [k, v] of Object.entries(query)) {
|
|
1556
1571
|
if (v === undefined || v === null) {
|
|
1557
|
-
continue
|
|
1572
|
+
continue
|
|
1558
1573
|
}
|
|
1559
|
-
qs.set(k, String(v))
|
|
1574
|
+
qs.set(k, String(v))
|
|
1560
1575
|
}
|
|
1561
|
-
const suffix = qs.toString()
|
|
1562
|
-
return suffix ? `${path}?${suffix}` : path
|
|
1576
|
+
const suffix = qs.toString()
|
|
1577
|
+
return suffix ? `${path}?${suffix}` : path
|
|
1563
1578
|
}
|
|
1564
1579
|
exportNamedRoutes() {
|
|
1565
|
-
return Object.fromEntries(this.namedRoutes.entries())
|
|
1580
|
+
return Object.fromEntries(this.namedRoutes.entries())
|
|
1566
1581
|
}
|
|
1567
1582
|
loadNamedRoutes(manifest) {
|
|
1568
|
-
this.namedRoutes = new Map(Object.entries(manifest))
|
|
1583
|
+
this.namedRoutes = new Map(Object.entries(manifest))
|
|
1569
1584
|
}
|
|
1570
1585
|
bind(param, resolver) {
|
|
1571
|
-
this.bindings.set(param, resolver)
|
|
1586
|
+
this.bindings.set(param, resolver)
|
|
1572
1587
|
}
|
|
1573
1588
|
model(param, modelClass) {
|
|
1574
1589
|
this.bind(param, async (id) => {
|
|
1575
|
-
if (
|
|
1576
|
-
|
|
1590
|
+
if (
|
|
1591
|
+
modelClass &&
|
|
1592
|
+
typeof modelClass === 'object' &&
|
|
1593
|
+
'find' in modelClass &&
|
|
1594
|
+
typeof modelClass.find === 'function'
|
|
1595
|
+
) {
|
|
1596
|
+
const instance = await modelClass.find(id)
|
|
1577
1597
|
if (!instance) {
|
|
1578
|
-
throw new Error(
|
|
1598
|
+
throw new Error('ModelNotFound')
|
|
1579
1599
|
}
|
|
1580
|
-
return instance
|
|
1600
|
+
return instance
|
|
1581
1601
|
}
|
|
1582
|
-
throw new Error(`Invalid model class for binding '${param}'`)
|
|
1583
|
-
})
|
|
1602
|
+
throw new Error(`Invalid model class for binding '${param}'`)
|
|
1603
|
+
})
|
|
1584
1604
|
}
|
|
1585
1605
|
constructor(core) {
|
|
1586
|
-
this.core = core
|
|
1606
|
+
this.core = core
|
|
1587
1607
|
this.core.adapter.useGlobal(async (c, next) => {
|
|
1588
|
-
const routeModels = c.get(
|
|
1608
|
+
const routeModels = c.get('routeModels') ?? {}
|
|
1589
1609
|
for (const [param, resolver] of this.bindings) {
|
|
1590
|
-
const value = c.req.param(param)
|
|
1610
|
+
const value = c.req.param(param)
|
|
1591
1611
|
if (value) {
|
|
1592
1612
|
try {
|
|
1593
|
-
const resolved = await resolver(value)
|
|
1594
|
-
routeModels[param] = resolved
|
|
1613
|
+
const resolved = await resolver(value)
|
|
1614
|
+
routeModels[param] = resolved
|
|
1595
1615
|
} catch (err) {
|
|
1596
|
-
const message = err instanceof Error ? err.message : undefined
|
|
1597
|
-
if (message ===
|
|
1598
|
-
throw new ModelNotFoundException(param, value)
|
|
1616
|
+
const message = err instanceof Error ? err.message : undefined
|
|
1617
|
+
if (message === 'ModelNotFound') {
|
|
1618
|
+
throw new ModelNotFoundException(param, value)
|
|
1599
1619
|
}
|
|
1600
|
-
throw err
|
|
1620
|
+
throw err
|
|
1601
1621
|
}
|
|
1602
1622
|
}
|
|
1603
1623
|
}
|
|
1604
|
-
c.set(
|
|
1605
|
-
await next()
|
|
1606
|
-
return
|
|
1607
|
-
})
|
|
1624
|
+
c.set('routeModels', routeModels)
|
|
1625
|
+
await next()
|
|
1626
|
+
return
|
|
1627
|
+
})
|
|
1608
1628
|
}
|
|
1609
1629
|
prefix(path) {
|
|
1610
|
-
return new RouteGroup(this, { prefix: path })
|
|
1630
|
+
return new RouteGroup(this, { prefix: path })
|
|
1611
1631
|
}
|
|
1612
1632
|
domain(host) {
|
|
1613
|
-
return new RouteGroup(this, { domain: host })
|
|
1633
|
+
return new RouteGroup(this, { domain: host })
|
|
1614
1634
|
}
|
|
1615
1635
|
middleware(...handlers) {
|
|
1616
|
-
return new RouteGroup(this, { middleware: handlers.flat() })
|
|
1636
|
+
return new RouteGroup(this, { middleware: handlers.flat() })
|
|
1617
1637
|
}
|
|
1618
1638
|
get(path, requestOrHandler, handler) {
|
|
1619
|
-
return this.req(
|
|
1639
|
+
return this.req('get', path, requestOrHandler, handler)
|
|
1620
1640
|
}
|
|
1621
1641
|
post(path, requestOrHandler, handler) {
|
|
1622
|
-
return this.req(
|
|
1642
|
+
return this.req('post', path, requestOrHandler, handler)
|
|
1623
1643
|
}
|
|
1624
1644
|
put(path, requestOrHandler, handler) {
|
|
1625
|
-
return this.req(
|
|
1645
|
+
return this.req('put', path, requestOrHandler, handler)
|
|
1626
1646
|
}
|
|
1627
1647
|
delete(path, requestOrHandler, handler) {
|
|
1628
|
-
return this.req(
|
|
1648
|
+
return this.req('delete', path, requestOrHandler, handler)
|
|
1629
1649
|
}
|
|
1630
1650
|
patch(path, requestOrHandler, handler) {
|
|
1631
|
-
return this.req(
|
|
1651
|
+
return this.req('patch', path, requestOrHandler, handler)
|
|
1632
1652
|
}
|
|
1633
1653
|
resource(name, controller, options = {}) {
|
|
1634
|
-
const actions = [
|
|
1635
|
-
"index",
|
|
1636
|
-
"create",
|
|
1637
|
-
"store",
|
|
1638
|
-
"show",
|
|
1639
|
-
"edit",
|
|
1640
|
-
"update",
|
|
1641
|
-
"destroy"
|
|
1642
|
-
];
|
|
1654
|
+
const actions = ['index', 'create', 'store', 'show', 'edit', 'update', 'destroy']
|
|
1643
1655
|
const map = {
|
|
1644
|
-
index: { method:
|
|
1645
|
-
create: { method:
|
|
1646
|
-
store: { method:
|
|
1647
|
-
show: { method:
|
|
1648
|
-
edit: { method:
|
|
1649
|
-
update: { method:
|
|
1650
|
-
destroy: { method:
|
|
1651
|
-
}
|
|
1656
|
+
index: { method: 'get', path: `/${name}` },
|
|
1657
|
+
create: { method: 'get', path: `/${name}/create` },
|
|
1658
|
+
store: { method: 'post', path: `/${name}` },
|
|
1659
|
+
show: { method: 'get', path: `/${name}/:id` },
|
|
1660
|
+
edit: { method: 'get', path: `/${name}/:id/edit` },
|
|
1661
|
+
update: { method: 'put', path: `/${name}/:id` },
|
|
1662
|
+
destroy: { method: 'delete', path: `/${name}/:id` },
|
|
1663
|
+
}
|
|
1652
1664
|
const allowed = actions.filter((action) => {
|
|
1653
1665
|
if (options.only) {
|
|
1654
|
-
return options.only.includes(action)
|
|
1666
|
+
return options.only.includes(action)
|
|
1655
1667
|
}
|
|
1656
1668
|
if (options.except) {
|
|
1657
|
-
return !options.except.includes(action)
|
|
1669
|
+
return !options.except.includes(action)
|
|
1658
1670
|
}
|
|
1659
|
-
return true
|
|
1660
|
-
})
|
|
1671
|
+
return true
|
|
1672
|
+
})
|
|
1661
1673
|
for (const action of allowed) {
|
|
1662
|
-
const { method, path } = map[action]
|
|
1663
|
-
if (action ===
|
|
1664
|
-
this.req(
|
|
1665
|
-
this.req(
|
|
1674
|
+
const { method, path } = map[action]
|
|
1675
|
+
if (action === 'update') {
|
|
1676
|
+
this.req('put', path, [controller, action]).name(`${name}.${action}`)
|
|
1677
|
+
this.req('patch', path, [controller, action])
|
|
1666
1678
|
} else {
|
|
1667
|
-
this.req(method, path, [controller, action]).name(`${name}.${action}`)
|
|
1679
|
+
this.req(method, path, [controller, action]).name(`${name}.${action}`)
|
|
1668
1680
|
}
|
|
1669
1681
|
}
|
|
1670
1682
|
}
|
|
1671
1683
|
req(method, path, requestOrHandler, handler, options = {}) {
|
|
1672
|
-
const fullPath = (options.prefix ||
|
|
1673
|
-
let formRequestMiddleware = null
|
|
1674
|
-
let finalRouteHandler
|
|
1684
|
+
const fullPath = (options.prefix || '') + path
|
|
1685
|
+
let formRequestMiddleware = null
|
|
1686
|
+
let finalRouteHandler
|
|
1675
1687
|
if (handler !== undefined) {
|
|
1676
1688
|
if (isFormRequestClass(requestOrHandler)) {
|
|
1677
|
-
formRequestMiddleware = formRequestToMiddleware(requestOrHandler)
|
|
1689
|
+
formRequestMiddleware = formRequestToMiddleware(requestOrHandler)
|
|
1678
1690
|
}
|
|
1679
|
-
finalRouteHandler = handler
|
|
1691
|
+
finalRouteHandler = handler
|
|
1680
1692
|
} else {
|
|
1681
|
-
finalRouteHandler = requestOrHandler
|
|
1693
|
+
finalRouteHandler = requestOrHandler
|
|
1682
1694
|
}
|
|
1683
|
-
let resolvedHandler
|
|
1695
|
+
let resolvedHandler
|
|
1684
1696
|
if (Array.isArray(finalRouteHandler)) {
|
|
1685
|
-
const [CtrlClass, methodName] = finalRouteHandler
|
|
1686
|
-
resolvedHandler = this.resolveControllerHandler(CtrlClass, methodName)
|
|
1697
|
+
const [CtrlClass, methodName] = finalRouteHandler
|
|
1698
|
+
resolvedHandler = this.resolveControllerHandler(CtrlClass, methodName)
|
|
1687
1699
|
} else {
|
|
1688
|
-
resolvedHandler = finalRouteHandler
|
|
1700
|
+
resolvedHandler = finalRouteHandler
|
|
1689
1701
|
}
|
|
1690
|
-
const handlers = []
|
|
1702
|
+
const handlers = []
|
|
1691
1703
|
if (options.middleware) {
|
|
1692
|
-
handlers.push(...options.middleware)
|
|
1704
|
+
handlers.push(...options.middleware)
|
|
1693
1705
|
}
|
|
1694
1706
|
if (formRequestMiddleware) {
|
|
1695
|
-
handlers.push(formRequestMiddleware)
|
|
1707
|
+
handlers.push(formRequestMiddleware)
|
|
1696
1708
|
}
|
|
1697
|
-
handlers.push(resolvedHandler)
|
|
1709
|
+
handlers.push(resolvedHandler)
|
|
1698
1710
|
if (options.domain) {
|
|
1699
1711
|
const _wrappedHandler = async (c) => {
|
|
1700
|
-
if (c.req.header(
|
|
1701
|
-
return new Response(
|
|
1712
|
+
if (c.req.header('host') !== options.domain) {
|
|
1713
|
+
return new Response('Not Found', { status: 404 })
|
|
1702
1714
|
}
|
|
1703
|
-
return
|
|
1704
|
-
}
|
|
1715
|
+
return
|
|
1716
|
+
}
|
|
1705
1717
|
const domainCheck = async (c, next) => {
|
|
1706
|
-
if (c.req.header(
|
|
1707
|
-
return c.text(
|
|
1718
|
+
if (c.req.header('host') !== options.domain) {
|
|
1719
|
+
return c.text('Not Found', 404)
|
|
1708
1720
|
}
|
|
1709
|
-
await next()
|
|
1710
|
-
}
|
|
1711
|
-
handlers.unshift(domainCheck)
|
|
1721
|
+
await next()
|
|
1722
|
+
}
|
|
1723
|
+
handlers.unshift(domainCheck)
|
|
1712
1724
|
}
|
|
1713
|
-
this.core.adapter.route(method, fullPath, ...handlers)
|
|
1714
|
-
return new Route(this, method, fullPath, options)
|
|
1725
|
+
this.core.adapter.route(method, fullPath, ...handlers)
|
|
1726
|
+
return new Route(this, method, fullPath, options)
|
|
1715
1727
|
}
|
|
1716
1728
|
resolveControllerHandler(CtrlClass, methodName) {
|
|
1717
|
-
let instance = this.controllers.get(CtrlClass)
|
|
1729
|
+
let instance = this.controllers.get(CtrlClass)
|
|
1718
1730
|
if (!instance) {
|
|
1719
|
-
instance = new CtrlClass(this.core)
|
|
1720
|
-
this.controllers.set(CtrlClass, instance)
|
|
1731
|
+
instance = new CtrlClass(this.core)
|
|
1732
|
+
this.controllers.set(CtrlClass, instance)
|
|
1721
1733
|
}
|
|
1722
|
-
const handler = instance[methodName]
|
|
1723
|
-
if (typeof handler !==
|
|
1724
|
-
throw new Error(`Method '${methodName}' not found in controller '${CtrlClass.name}'`)
|
|
1734
|
+
const handler = instance[methodName]
|
|
1735
|
+
if (typeof handler !== 'function') {
|
|
1736
|
+
throw new Error(`Method '${methodName}' not found in controller '${CtrlClass.name}'`)
|
|
1725
1737
|
}
|
|
1726
|
-
return handler.bind(instance)
|
|
1738
|
+
return handler.bind(instance)
|
|
1727
1739
|
}
|
|
1728
1740
|
}
|
|
1729
1741
|
|
|
1730
1742
|
// ../core/src/security/Encrypter.ts
|
|
1731
|
-
import crypto from
|
|
1743
|
+
import crypto from 'node:crypto'
|
|
1732
1744
|
|
|
1733
1745
|
class Encrypter {
|
|
1734
|
-
algorithm
|
|
1735
|
-
key
|
|
1746
|
+
algorithm
|
|
1747
|
+
key
|
|
1736
1748
|
constructor(options) {
|
|
1737
|
-
this.algorithm = options.cipher ||
|
|
1738
|
-
if (options.key.startsWith(
|
|
1739
|
-
this.key = Buffer.from(options.key.substring(7),
|
|
1749
|
+
this.algorithm = options.cipher || 'aes-256-cbc'
|
|
1750
|
+
if (options.key.startsWith('base64:')) {
|
|
1751
|
+
this.key = Buffer.from(options.key.substring(7), 'base64')
|
|
1740
1752
|
} else {
|
|
1741
|
-
this.key = Buffer.from(options.key)
|
|
1753
|
+
this.key = Buffer.from(options.key)
|
|
1742
1754
|
}
|
|
1743
|
-
if (this.algorithm ===
|
|
1744
|
-
throw new Error(
|
|
1755
|
+
if (this.algorithm === 'aes-128-cbc' && this.key.length !== 16) {
|
|
1756
|
+
throw new Error('The key must be 16 bytes (128 bits) for AES-128-CBC.')
|
|
1745
1757
|
}
|
|
1746
|
-
if (this.algorithm ===
|
|
1747
|
-
throw new Error(
|
|
1758
|
+
if (this.algorithm === 'aes-256-cbc' && this.key.length !== 32) {
|
|
1759
|
+
throw new Error('The key must be 32 bytes (256 bits) for AES-256-CBC.')
|
|
1748
1760
|
}
|
|
1749
1761
|
}
|
|
1750
1762
|
encrypt(value, serialize = true) {
|
|
1751
|
-
const iv = crypto.randomBytes(16)
|
|
1752
|
-
const cipher = crypto.createCipheriv(this.algorithm, this.key, iv)
|
|
1753
|
-
const stringValue = serialize ? JSON.stringify(value) : String(value)
|
|
1754
|
-
let encrypted = cipher.update(stringValue,
|
|
1755
|
-
encrypted += cipher.final(
|
|
1756
|
-
const mac = this.hash(iv.toString(
|
|
1763
|
+
const iv = crypto.randomBytes(16)
|
|
1764
|
+
const cipher = crypto.createCipheriv(this.algorithm, this.key, iv)
|
|
1765
|
+
const stringValue = serialize ? JSON.stringify(value) : String(value)
|
|
1766
|
+
let encrypted = cipher.update(stringValue, 'utf8', 'base64')
|
|
1767
|
+
encrypted += cipher.final('base64')
|
|
1768
|
+
const mac = this.hash(iv.toString('base64'), encrypted)
|
|
1757
1769
|
const payload = {
|
|
1758
|
-
iv: iv.toString(
|
|
1770
|
+
iv: iv.toString('base64'),
|
|
1759
1771
|
value: encrypted,
|
|
1760
1772
|
mac,
|
|
1761
|
-
tag:
|
|
1762
|
-
}
|
|
1763
|
-
return Buffer.from(JSON.stringify(payload)).toString(
|
|
1773
|
+
tag: '',
|
|
1774
|
+
}
|
|
1775
|
+
return Buffer.from(JSON.stringify(payload)).toString('base64')
|
|
1764
1776
|
}
|
|
1765
1777
|
decrypt(payload, deserialize = true) {
|
|
1766
|
-
const json = JSON.parse(Buffer.from(payload,
|
|
1778
|
+
const json = JSON.parse(Buffer.from(payload, 'base64').toString('utf8'))
|
|
1767
1779
|
if (!this.validPayload(json)) {
|
|
1768
|
-
throw new Error(
|
|
1780
|
+
throw new Error('The payload is invalid.')
|
|
1769
1781
|
}
|
|
1770
1782
|
if (!this.validMac(json)) {
|
|
1771
|
-
throw new Error(
|
|
1783
|
+
throw new Error('The MAC is invalid.')
|
|
1772
1784
|
}
|
|
1773
|
-
const iv = Buffer.from(json.iv,
|
|
1774
|
-
const decipher = crypto.createDecipheriv(this.algorithm, this.key, iv)
|
|
1775
|
-
let decrypted = decipher.update(json.value,
|
|
1776
|
-
decrypted += decipher.final(
|
|
1777
|
-
return deserialize ? JSON.parse(decrypted) : decrypted
|
|
1785
|
+
const iv = Buffer.from(json.iv, 'base64')
|
|
1786
|
+
const decipher = crypto.createDecipheriv(this.algorithm, this.key, iv)
|
|
1787
|
+
let decrypted = decipher.update(json.value, 'base64', 'utf8')
|
|
1788
|
+
decrypted += decipher.final('utf8')
|
|
1789
|
+
return deserialize ? JSON.parse(decrypted) : decrypted
|
|
1778
1790
|
}
|
|
1779
1791
|
hash(iv, value) {
|
|
1780
|
-
const hmac = crypto.createHmac(
|
|
1781
|
-
hmac.update(iv + value)
|
|
1782
|
-
return hmac.digest(
|
|
1792
|
+
const hmac = crypto.createHmac('sha256', this.key)
|
|
1793
|
+
hmac.update(iv + value)
|
|
1794
|
+
return hmac.digest('hex')
|
|
1783
1795
|
}
|
|
1784
1796
|
validPayload(payload) {
|
|
1785
|
-
return
|
|
1797
|
+
return (
|
|
1798
|
+
typeof payload === 'object' &&
|
|
1799
|
+
payload !== null &&
|
|
1800
|
+
'iv' in payload &&
|
|
1801
|
+
'value' in payload &&
|
|
1802
|
+
'mac' in payload
|
|
1803
|
+
)
|
|
1786
1804
|
}
|
|
1787
1805
|
validMac(payload) {
|
|
1788
|
-
const calculated = this.hash(payload.iv, payload.value)
|
|
1789
|
-
return crypto.timingSafeEqual(Buffer.from(calculated), Buffer.from(payload.mac))
|
|
1806
|
+
const calculated = this.hash(payload.iv, payload.value)
|
|
1807
|
+
return crypto.timingSafeEqual(Buffer.from(calculated), Buffer.from(payload.mac))
|
|
1790
1808
|
}
|
|
1791
|
-
static generateKey(cipher =
|
|
1792
|
-
const bytes = cipher ===
|
|
1793
|
-
return `base64:${crypto.randomBytes(bytes).toString(
|
|
1809
|
+
static generateKey(cipher = 'aes-256-cbc') {
|
|
1810
|
+
const bytes = cipher === 'aes-128-cbc' ? 16 : 32
|
|
1811
|
+
return `base64:${crypto.randomBytes(bytes).toString('base64')}`
|
|
1794
1812
|
}
|
|
1795
1813
|
}
|
|
1796
1814
|
|
|
1797
1815
|
// ../core/src/security/Hasher.ts
|
|
1798
1816
|
class BunHasher {
|
|
1799
1817
|
async make(value, options) {
|
|
1800
|
-
const bun = Bun
|
|
1801
|
-
return await bun.password.hash(value, options)
|
|
1818
|
+
const bun = Bun
|
|
1819
|
+
return await bun.password.hash(value, options)
|
|
1802
1820
|
}
|
|
1803
1821
|
async check(value, hashedValue) {
|
|
1804
|
-
const bun = Bun
|
|
1805
|
-
return await bun.password.verify(value, hashedValue)
|
|
1822
|
+
const bun = Bun
|
|
1823
|
+
return await bun.password.verify(value, hashedValue)
|
|
1806
1824
|
}
|
|
1807
1825
|
needsRehash(_hashedValue, _options) {
|
|
1808
|
-
return false
|
|
1826
|
+
return false
|
|
1809
1827
|
}
|
|
1810
1828
|
}
|
|
1811
1829
|
|
|
1812
1830
|
// ../core/src/PlanetCore.ts
|
|
1813
1831
|
class PlanetCore {
|
|
1814
|
-
_adapter
|
|
1832
|
+
_adapter
|
|
1815
1833
|
get app() {
|
|
1816
|
-
return this._adapter.native
|
|
1834
|
+
return this._adapter.native
|
|
1817
1835
|
}
|
|
1818
1836
|
get adapter() {
|
|
1819
|
-
return this._adapter
|
|
1820
|
-
}
|
|
1821
|
-
logger
|
|
1822
|
-
config
|
|
1823
|
-
hooks
|
|
1824
|
-
events
|
|
1825
|
-
router
|
|
1826
|
-
container = new Container
|
|
1827
|
-
services = new Map
|
|
1828
|
-
encrypter
|
|
1829
|
-
hasher
|
|
1830
|
-
providers = []
|
|
1837
|
+
return this._adapter
|
|
1838
|
+
}
|
|
1839
|
+
logger
|
|
1840
|
+
config
|
|
1841
|
+
hooks
|
|
1842
|
+
events
|
|
1843
|
+
router
|
|
1844
|
+
container = new Container()
|
|
1845
|
+
services = new Map()
|
|
1846
|
+
encrypter
|
|
1847
|
+
hasher
|
|
1848
|
+
providers = []
|
|
1831
1849
|
register(provider) {
|
|
1832
|
-
this.providers.push(provider)
|
|
1833
|
-
return this
|
|
1850
|
+
this.providers.push(provider)
|
|
1851
|
+
return this
|
|
1834
1852
|
}
|
|
1835
1853
|
async bootstrap() {
|
|
1836
1854
|
for (const provider of this.providers) {
|
|
1837
|
-
provider.register(this.container)
|
|
1855
|
+
provider.register(this.container)
|
|
1838
1856
|
}
|
|
1839
1857
|
for (const provider of this.providers) {
|
|
1840
1858
|
if (provider.boot) {
|
|
1841
|
-
await provider.boot(this)
|
|
1859
|
+
await provider.boot(this)
|
|
1842
1860
|
}
|
|
1843
1861
|
}
|
|
1844
1862
|
}
|
|
1845
1863
|
constructor(options = {}) {
|
|
1846
|
-
this.logger = options.logger ?? new ConsoleLogger
|
|
1847
|
-
this.config = new ConfigManager(options.config ?? {})
|
|
1848
|
-
this.hooks = new HookManager
|
|
1849
|
-
this.events = new EventManager(this)
|
|
1850
|
-
this.hasher = new BunHasher
|
|
1851
|
-
const appKey =
|
|
1864
|
+
this.logger = options.logger ?? new ConsoleLogger()
|
|
1865
|
+
this.config = new ConfigManager(options.config ?? {})
|
|
1866
|
+
this.hooks = new HookManager()
|
|
1867
|
+
this.events = new EventManager(this)
|
|
1868
|
+
this.hasher = new BunHasher()
|
|
1869
|
+
const appKey =
|
|
1870
|
+
(this.config.has('APP_KEY') ? this.config.get('APP_KEY') : undefined) || process.env.APP_KEY
|
|
1852
1871
|
if (appKey) {
|
|
1853
1872
|
try {
|
|
1854
|
-
this.encrypter = new Encrypter({ key: appKey })
|
|
1873
|
+
this.encrypter = new Encrypter({ key: appKey })
|
|
1855
1874
|
} catch (e) {
|
|
1856
|
-
this.logger.warn(
|
|
1875
|
+
this.logger.warn('Failed to initialize Encrypter (invalid APP_KEY?):', e)
|
|
1857
1876
|
}
|
|
1858
1877
|
}
|
|
1859
1878
|
if (options.adapter) {
|
|
1860
|
-
this._adapter = options.adapter
|
|
1861
|
-
} else if (typeof Bun !==
|
|
1862
|
-
this._adapter = new BunNativeAdapter
|
|
1879
|
+
this._adapter = options.adapter
|
|
1880
|
+
} else if (typeof Bun !== 'undefined') {
|
|
1881
|
+
this._adapter = new BunNativeAdapter()
|
|
1863
1882
|
} else {
|
|
1864
|
-
this._adapter = new HonoAdapter
|
|
1865
|
-
}
|
|
1866
|
-
this.adapter.use(
|
|
1867
|
-
c.set(
|
|
1868
|
-
c.set(
|
|
1869
|
-
c.set(
|
|
1870
|
-
const cookieJar = new CookieJar(this.encrypter)
|
|
1871
|
-
c.set(
|
|
1872
|
-
c.route = (name, params, query) => this.router.url(name, params, query)
|
|
1873
|
-
await next()
|
|
1874
|
-
return
|
|
1875
|
-
})
|
|
1876
|
-
this.router = new Router(this)
|
|
1883
|
+
this._adapter = new HonoAdapter()
|
|
1884
|
+
}
|
|
1885
|
+
this.adapter.use('*', async (c, next) => {
|
|
1886
|
+
c.set('core', this)
|
|
1887
|
+
c.set('logger', this.logger)
|
|
1888
|
+
c.set('config', this.config)
|
|
1889
|
+
const cookieJar = new CookieJar(this.encrypter)
|
|
1890
|
+
c.set('cookieJar', cookieJar)
|
|
1891
|
+
c.route = (name, params, query) => this.router.url(name, params, query)
|
|
1892
|
+
await next()
|
|
1893
|
+
return
|
|
1894
|
+
})
|
|
1895
|
+
this.router = new Router(this)
|
|
1877
1896
|
this.adapter.onError(async (err, c) => {
|
|
1878
|
-
const isProduction = false
|
|
1897
|
+
const isProduction = false
|
|
1879
1898
|
const codeFromStatus = (status2) => {
|
|
1880
1899
|
switch (status2) {
|
|
1881
1900
|
case 400:
|
|
1882
|
-
return
|
|
1901
|
+
return 'BAD_REQUEST'
|
|
1883
1902
|
case 401:
|
|
1884
|
-
return
|
|
1903
|
+
return 'UNAUTHENTICATED'
|
|
1885
1904
|
case 403:
|
|
1886
|
-
return
|
|
1905
|
+
return 'FORBIDDEN'
|
|
1887
1906
|
case 404:
|
|
1888
|
-
return
|
|
1907
|
+
return 'NOT_FOUND'
|
|
1889
1908
|
case 405:
|
|
1890
|
-
return
|
|
1909
|
+
return 'METHOD_NOT_ALLOWED'
|
|
1891
1910
|
case 409:
|
|
1892
|
-
return
|
|
1911
|
+
return 'CONFLICT'
|
|
1893
1912
|
case 422:
|
|
1894
|
-
return
|
|
1913
|
+
return 'VALIDATION_ERROR'
|
|
1895
1914
|
case 429:
|
|
1896
|
-
return
|
|
1915
|
+
return 'TOO_MANY_REQUESTS'
|
|
1897
1916
|
default:
|
|
1898
|
-
return status2 >= 500 ?
|
|
1917
|
+
return status2 >= 500 ? 'INTERNAL_ERROR' : 'HTTP_ERROR'
|
|
1899
1918
|
}
|
|
1900
|
-
}
|
|
1919
|
+
}
|
|
1901
1920
|
const messageFromStatus = (status2) => {
|
|
1902
1921
|
switch (status2) {
|
|
1903
1922
|
case 400:
|
|
1904
|
-
return
|
|
1923
|
+
return 'Bad Request'
|
|
1905
1924
|
case 401:
|
|
1906
|
-
return
|
|
1925
|
+
return 'Unauthorized'
|
|
1907
1926
|
case 403:
|
|
1908
|
-
return
|
|
1927
|
+
return 'Forbidden'
|
|
1909
1928
|
case 404:
|
|
1910
|
-
return
|
|
1929
|
+
return 'Not Found'
|
|
1911
1930
|
case 405:
|
|
1912
|
-
return
|
|
1931
|
+
return 'Method Not Allowed'
|
|
1913
1932
|
case 409:
|
|
1914
|
-
return
|
|
1933
|
+
return 'Conflict'
|
|
1915
1934
|
case 422:
|
|
1916
|
-
return
|
|
1935
|
+
return 'Unprocessable Content'
|
|
1917
1936
|
case 429:
|
|
1918
|
-
return
|
|
1937
|
+
return 'Too Many Requests'
|
|
1919
1938
|
case 500:
|
|
1920
|
-
return
|
|
1939
|
+
return 'Internal Server Error'
|
|
1921
1940
|
case 502:
|
|
1922
|
-
return
|
|
1941
|
+
return 'Bad Gateway'
|
|
1923
1942
|
case 503:
|
|
1924
|
-
return
|
|
1943
|
+
return 'Service Unavailable'
|
|
1925
1944
|
case 504:
|
|
1926
|
-
return
|
|
1945
|
+
return 'Gateway Timeout'
|
|
1927
1946
|
default:
|
|
1928
|
-
return status2 >= 500 ?
|
|
1947
|
+
return status2 >= 500 ? 'Internal Server Error' : 'Request Error'
|
|
1929
1948
|
}
|
|
1930
|
-
}
|
|
1931
|
-
const view = c.get(
|
|
1932
|
-
const i18n = c.get(
|
|
1933
|
-
const accept = c.req.header(
|
|
1934
|
-
const wantsHtml = Boolean(
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
let
|
|
1938
|
-
let
|
|
1949
|
+
}
|
|
1950
|
+
const view = c.get('view')
|
|
1951
|
+
const i18n = c.get('i18n')
|
|
1952
|
+
const accept = c.req.header('Accept') || ''
|
|
1953
|
+
const wantsHtml = Boolean(
|
|
1954
|
+
view && accept.includes('text/html') && !accept.includes('application/json')
|
|
1955
|
+
)
|
|
1956
|
+
let status = 500
|
|
1957
|
+
let message = messageFromStatus(500)
|
|
1958
|
+
let code = 'INTERNAL_ERROR'
|
|
1959
|
+
let details
|
|
1939
1960
|
if (err instanceof GravitoException) {
|
|
1940
|
-
status = err.status
|
|
1941
|
-
code = err.code
|
|
1942
|
-
if (code ===
|
|
1943
|
-
code = codeFromStatus(status)
|
|
1961
|
+
status = err.status
|
|
1962
|
+
code = err.code
|
|
1963
|
+
if (code === 'HTTP_ERROR') {
|
|
1964
|
+
code = codeFromStatus(status)
|
|
1944
1965
|
}
|
|
1945
1966
|
if (i18n?.t && err.i18nKey) {
|
|
1946
|
-
message = i18n.t(err.i18nKey, err.i18nParams)
|
|
1967
|
+
message = i18n.t(err.i18nKey, err.i18nParams)
|
|
1947
1968
|
} else {
|
|
1948
|
-
message = err.message || messageFromStatus(status)
|
|
1969
|
+
message = err.message || messageFromStatus(status)
|
|
1949
1970
|
}
|
|
1950
1971
|
if (err instanceof ValidationException) {
|
|
1951
|
-
details = err.errors
|
|
1972
|
+
details = err.errors
|
|
1952
1973
|
if (wantsHtml) {
|
|
1953
|
-
const session = c.get(
|
|
1974
|
+
const session = c.get('session')
|
|
1954
1975
|
if (session) {
|
|
1955
|
-
const errorBag = {}
|
|
1976
|
+
const errorBag = {}
|
|
1956
1977
|
for (const e of err.errors) {
|
|
1957
1978
|
if (!errorBag[e.field]) {
|
|
1958
|
-
errorBag[e.field] = []
|
|
1979
|
+
errorBag[e.field] = []
|
|
1959
1980
|
}
|
|
1960
|
-
errorBag[e.field]?.push(e.message)
|
|
1981
|
+
errorBag[e.field]?.push(e.message)
|
|
1961
1982
|
}
|
|
1962
|
-
session.flash(
|
|
1983
|
+
session.flash('errors', errorBag)
|
|
1963
1984
|
if (err.input) {
|
|
1964
|
-
session.flash(
|
|
1985
|
+
session.flash('_old_input', err.input)
|
|
1965
1986
|
}
|
|
1966
|
-
const redirectUrl = err.redirectTo ?? c.req.header(
|
|
1967
|
-
return c.redirect(redirectUrl)
|
|
1987
|
+
const redirectUrl = err.redirectTo ?? c.req.header('Referer') ?? '/'
|
|
1988
|
+
return c.redirect(redirectUrl)
|
|
1968
1989
|
}
|
|
1969
1990
|
}
|
|
1970
1991
|
} else if (err instanceof Error && !isProduction && err.cause) {
|
|
1971
|
-
details = { cause: err.cause }
|
|
1992
|
+
details = { cause: err.cause }
|
|
1972
1993
|
}
|
|
1973
1994
|
} else if (err instanceof HttpException) {
|
|
1974
|
-
status = err.status
|
|
1975
|
-
message = err.message
|
|
1976
|
-
} else if (err instanceof Error &&
|
|
1977
|
-
status = err.status
|
|
1978
|
-
message = err.message
|
|
1979
|
-
code = codeFromStatus(status)
|
|
1995
|
+
status = err.status
|
|
1996
|
+
message = err.message
|
|
1997
|
+
} else if (err instanceof Error && 'status' in err && typeof err.status === 'number') {
|
|
1998
|
+
status = err.status
|
|
1999
|
+
message = err.message
|
|
2000
|
+
code = codeFromStatus(status)
|
|
1980
2001
|
} else if (err instanceof Error) {
|
|
1981
2002
|
if (!isProduction) {
|
|
1982
|
-
message = err.message || message
|
|
2003
|
+
message = err.message || message
|
|
1983
2004
|
}
|
|
1984
|
-
} else if (typeof err ===
|
|
2005
|
+
} else if (typeof err === 'string') {
|
|
1985
2006
|
if (!isProduction) {
|
|
1986
|
-
message = err
|
|
2007
|
+
message = err
|
|
1987
2008
|
}
|
|
1988
2009
|
}
|
|
1989
2010
|
if (isProduction && status >= 500) {
|
|
1990
|
-
message = messageFromStatus(status)
|
|
2011
|
+
message = messageFromStatus(status)
|
|
1991
2012
|
}
|
|
1992
2013
|
if (!isProduction && err instanceof Error && !details) {
|
|
1993
|
-
details = { stack: err.stack, ...details }
|
|
2014
|
+
details = { stack: err.stack, ...details }
|
|
1994
2015
|
}
|
|
1995
2016
|
let handlerContext = {
|
|
1996
2017
|
core: this,
|
|
@@ -2001,249 +2022,269 @@ class PlanetCore {
|
|
|
2001
2022
|
wantsHtml,
|
|
2002
2023
|
status,
|
|
2003
2024
|
payload: fail(message, code, details),
|
|
2004
|
-
...wantsHtml
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2025
|
+
...(wantsHtml
|
|
2026
|
+
? {
|
|
2027
|
+
html: {
|
|
2028
|
+
templates: status === 500 ? ['errors/500'] : [`errors/${status}`, 'errors/500'],
|
|
2029
|
+
data: {
|
|
2030
|
+
status,
|
|
2031
|
+
message,
|
|
2032
|
+
code,
|
|
2033
|
+
error: !isProduction && err instanceof Error ? err.stack : undefined,
|
|
2034
|
+
debug: !isProduction,
|
|
2035
|
+
details,
|
|
2036
|
+
},
|
|
2037
|
+
},
|
|
2014
2038
|
}
|
|
2015
|
-
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2039
|
+
: {}),
|
|
2040
|
+
}
|
|
2041
|
+
handlerContext = await this.hooks.applyFilters('error:context', handlerContext)
|
|
2042
|
+
const defaultLogLevel = handlerContext.status >= 500 ? 'error' : 'none'
|
|
2043
|
+
const logLevel = handlerContext.logLevel ?? defaultLogLevel
|
|
2044
|
+
if (logLevel !== 'none') {
|
|
2045
|
+
const rawErrorMessage =
|
|
2046
|
+
handlerContext.error instanceof Error
|
|
2047
|
+
? handlerContext.error.message
|
|
2048
|
+
: typeof handlerContext.error === 'string'
|
|
2049
|
+
? handlerContext.error
|
|
2050
|
+
: handlerContext.payload.error.message
|
|
2051
|
+
const msg =
|
|
2052
|
+
handlerContext.logMessage ??
|
|
2053
|
+
(logLevel === 'error'
|
|
2054
|
+
? `Application Error: ${rawErrorMessage || handlerContext.payload.error.message}`
|
|
2055
|
+
: `HTTP ${handlerContext.status}: ${handlerContext.payload.error.message}`)
|
|
2056
|
+
if (logLevel === 'error') {
|
|
2057
|
+
this.logger.error(msg, err)
|
|
2058
|
+
} else if (logLevel === 'warn') {
|
|
2059
|
+
this.logger.warn(msg)
|
|
2028
2060
|
} else {
|
|
2029
|
-
this.logger.info(msg)
|
|
2061
|
+
this.logger.info(msg)
|
|
2030
2062
|
}
|
|
2031
2063
|
}
|
|
2032
|
-
await this.hooks.doAction(
|
|
2033
|
-
const customResponse = await this.hooks.applyFilters(
|
|
2064
|
+
await this.hooks.doAction('error:report', handlerContext)
|
|
2065
|
+
const customResponse = await this.hooks.applyFilters('error:render', null, handlerContext)
|
|
2034
2066
|
if (customResponse) {
|
|
2035
|
-
return customResponse
|
|
2067
|
+
return customResponse
|
|
2036
2068
|
}
|
|
2037
2069
|
if (handlerContext.wantsHtml && view && handlerContext.html) {
|
|
2038
|
-
let lastRenderError
|
|
2070
|
+
let lastRenderError
|
|
2039
2071
|
for (const template of handlerContext.html.templates) {
|
|
2040
2072
|
try {
|
|
2041
|
-
return c.html(view.render(template, handlerContext.html.data), handlerContext.status)
|
|
2073
|
+
return c.html(view.render(template, handlerContext.html.data), handlerContext.status)
|
|
2042
2074
|
} catch (renderError) {
|
|
2043
|
-
lastRenderError = renderError
|
|
2075
|
+
lastRenderError = renderError
|
|
2044
2076
|
}
|
|
2045
2077
|
}
|
|
2046
|
-
this.logger.error(
|
|
2078
|
+
this.logger.error('Failed to render error view', lastRenderError)
|
|
2047
2079
|
}
|
|
2048
|
-
return c.json(handlerContext.payload, handlerContext.status)
|
|
2049
|
-
})
|
|
2080
|
+
return c.json(handlerContext.payload, handlerContext.status)
|
|
2081
|
+
})
|
|
2050
2082
|
this.adapter.onNotFound(async (c) => {
|
|
2051
|
-
const view = c.get(
|
|
2052
|
-
const accept = c.req.header(
|
|
2053
|
-
const wantsHtml = view && accept.includes(
|
|
2054
|
-
const isProduction = false
|
|
2083
|
+
const view = c.get('view')
|
|
2084
|
+
const accept = c.req.header('Accept') || ''
|
|
2085
|
+
const wantsHtml = view && accept.includes('text/html') && !accept.includes('application/json')
|
|
2086
|
+
const isProduction = false
|
|
2055
2087
|
let handlerContext = {
|
|
2056
2088
|
core: this,
|
|
2057
2089
|
c,
|
|
2058
|
-
error: new HttpException(404, { message:
|
|
2090
|
+
error: new HttpException(404, { message: 'Route not found' }),
|
|
2059
2091
|
isProduction,
|
|
2060
2092
|
accept,
|
|
2061
2093
|
wantsHtml: Boolean(wantsHtml),
|
|
2062
2094
|
status: 404,
|
|
2063
|
-
payload: fail(
|
|
2064
|
-
...wantsHtml
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2095
|
+
payload: fail('Route not found', 'NOT_FOUND'),
|
|
2096
|
+
...(wantsHtml
|
|
2097
|
+
? {
|
|
2098
|
+
html: {
|
|
2099
|
+
templates: ['errors/404', 'errors/500'],
|
|
2100
|
+
data: {
|
|
2101
|
+
status: 404,
|
|
2102
|
+
message: 'Route not found',
|
|
2103
|
+
code: 'NOT_FOUND',
|
|
2104
|
+
debug: !isProduction,
|
|
2105
|
+
},
|
|
2106
|
+
},
|
|
2072
2107
|
}
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
this.logger.warn(msg);
|
|
2108
|
+
: {}),
|
|
2109
|
+
}
|
|
2110
|
+
handlerContext = await this.hooks.applyFilters('notFound:context', handlerContext)
|
|
2111
|
+
const logLevel = handlerContext.logLevel ?? 'info'
|
|
2112
|
+
if (logLevel !== 'none') {
|
|
2113
|
+
const msg = handlerContext.logMessage ?? `404 Not Found: ${c.req.url}`
|
|
2114
|
+
if (logLevel === 'error') {
|
|
2115
|
+
this.logger.error(msg)
|
|
2116
|
+
} else if (logLevel === 'warn') {
|
|
2117
|
+
this.logger.warn(msg)
|
|
2084
2118
|
} else {
|
|
2085
|
-
this.logger.info(msg)
|
|
2119
|
+
this.logger.info(msg)
|
|
2086
2120
|
}
|
|
2087
2121
|
}
|
|
2088
|
-
await this.hooks.doAction(
|
|
2089
|
-
const customResponse = await this.hooks.applyFilters(
|
|
2122
|
+
await this.hooks.doAction('notFound:report', handlerContext)
|
|
2123
|
+
const customResponse = await this.hooks.applyFilters('notFound:render', null, handlerContext)
|
|
2090
2124
|
if (customResponse) {
|
|
2091
|
-
return customResponse
|
|
2125
|
+
return customResponse
|
|
2092
2126
|
}
|
|
2093
2127
|
if (handlerContext.wantsHtml && view && handlerContext.html) {
|
|
2094
|
-
let lastRenderError
|
|
2128
|
+
let lastRenderError
|
|
2095
2129
|
for (const template of handlerContext.html.templates) {
|
|
2096
2130
|
try {
|
|
2097
|
-
return c.html(view.render(template, handlerContext.html.data), handlerContext.status)
|
|
2131
|
+
return c.html(view.render(template, handlerContext.html.data), handlerContext.status)
|
|
2098
2132
|
} catch (renderError) {
|
|
2099
|
-
lastRenderError = renderError
|
|
2133
|
+
lastRenderError = renderError
|
|
2100
2134
|
}
|
|
2101
2135
|
}
|
|
2102
|
-
this.logger.error(
|
|
2136
|
+
this.logger.error('Failed to render 404 view', lastRenderError)
|
|
2103
2137
|
}
|
|
2104
|
-
return c.json(handlerContext.payload, handlerContext.status)
|
|
2105
|
-
})
|
|
2138
|
+
return c.json(handlerContext.payload, handlerContext.status)
|
|
2139
|
+
})
|
|
2106
2140
|
}
|
|
2107
2141
|
async orbit(orbit) {
|
|
2108
|
-
const instance = typeof orbit ===
|
|
2109
|
-
await instance.install(this)
|
|
2110
|
-
return this
|
|
2142
|
+
const instance = typeof orbit === 'function' ? new orbit() : orbit
|
|
2143
|
+
await instance.install(this)
|
|
2144
|
+
return this
|
|
2111
2145
|
}
|
|
2112
2146
|
async use(satellite) {
|
|
2113
|
-
if (typeof satellite ===
|
|
2114
|
-
await satellite(this)
|
|
2147
|
+
if (typeof satellite === 'function') {
|
|
2148
|
+
await satellite(this)
|
|
2115
2149
|
} else {
|
|
2116
|
-
this.register(satellite)
|
|
2150
|
+
this.register(satellite)
|
|
2117
2151
|
}
|
|
2118
|
-
return this
|
|
2152
|
+
return this
|
|
2119
2153
|
}
|
|
2120
2154
|
registerGlobalErrorHandlers(options = {}) {
|
|
2121
|
-
return registerGlobalErrorHandlers({ ...options, core: this })
|
|
2155
|
+
return registerGlobalErrorHandlers({ ...options, core: this })
|
|
2122
2156
|
}
|
|
2123
2157
|
static async boot(config) {
|
|
2124
2158
|
const core = new PlanetCore({
|
|
2125
|
-
...config.logger && { logger: config.logger },
|
|
2126
|
-
...config.config && { config: config.config },
|
|
2127
|
-
...config.adapter && { adapter: config.adapter }
|
|
2128
|
-
})
|
|
2159
|
+
...(config.logger && { logger: config.logger }),
|
|
2160
|
+
...(config.config && { config: config.config }),
|
|
2161
|
+
...(config.adapter && { adapter: config.adapter }),
|
|
2162
|
+
})
|
|
2129
2163
|
if (config.orbits) {
|
|
2130
2164
|
for (const OrbitClassOrInstance of config.orbits) {
|
|
2131
|
-
let orbit
|
|
2132
|
-
if (typeof OrbitClassOrInstance ===
|
|
2133
|
-
orbit = new OrbitClassOrInstance
|
|
2165
|
+
let orbit
|
|
2166
|
+
if (typeof OrbitClassOrInstance === 'function') {
|
|
2167
|
+
orbit = new OrbitClassOrInstance()
|
|
2134
2168
|
} else {
|
|
2135
|
-
orbit = OrbitClassOrInstance
|
|
2169
|
+
orbit = OrbitClassOrInstance
|
|
2136
2170
|
}
|
|
2137
|
-
await orbit.install(core)
|
|
2171
|
+
await orbit.install(core)
|
|
2138
2172
|
}
|
|
2139
2173
|
}
|
|
2140
|
-
return core
|
|
2174
|
+
return core
|
|
2141
2175
|
}
|
|
2142
2176
|
mountOrbit(path, orbitApp) {
|
|
2143
|
-
this.logger.info(`Mounting orbit at path: ${path}`)
|
|
2144
|
-
if (this.adapter.name ===
|
|
2145
|
-
this.adapter.native.route(path, orbitApp)
|
|
2177
|
+
this.logger.info(`Mounting orbit at path: ${path}`)
|
|
2178
|
+
if (this.adapter.name === 'hono') {
|
|
2179
|
+
this.adapter.native.route(path, orbitApp)
|
|
2146
2180
|
} else {
|
|
2147
|
-
const subAdapter = new HonoAdapter({}, orbitApp)
|
|
2148
|
-
this.adapter.mount(path, subAdapter)
|
|
2181
|
+
const subAdapter = new HonoAdapter({}, orbitApp)
|
|
2182
|
+
this.adapter.mount(path, subAdapter)
|
|
2149
2183
|
}
|
|
2150
2184
|
}
|
|
2151
2185
|
liftoff(port) {
|
|
2152
|
-
const finalPort = port ?? this.config.get(
|
|
2153
|
-
this.hooks.doAction(
|
|
2154
|
-
this.logger.info(`Ready to liftoff on port ${finalPort} \uD83D\uDE80`)
|
|
2186
|
+
const finalPort = port ?? this.config.get('PORT', 3000)
|
|
2187
|
+
this.hooks.doAction('app:liftoff', { port: finalPort })
|
|
2188
|
+
this.logger.info(`Ready to liftoff on port ${finalPort} \uD83D\uDE80`)
|
|
2155
2189
|
return {
|
|
2156
2190
|
port: finalPort,
|
|
2157
2191
|
fetch: this.adapter.fetch.bind(this.adapter),
|
|
2158
|
-
core: this
|
|
2159
|
-
}
|
|
2192
|
+
core: this,
|
|
2193
|
+
}
|
|
2160
2194
|
}
|
|
2161
2195
|
}
|
|
2162
2196
|
|
|
2163
2197
|
// ../core/src/index.ts
|
|
2164
|
-
var VERSION = package_default.version
|
|
2198
|
+
var VERSION = package_default.version
|
|
2165
2199
|
|
|
2166
2200
|
// ../ion/src/InertiaService.ts
|
|
2167
2201
|
class InertiaService {
|
|
2168
|
-
context
|
|
2169
|
-
config
|
|
2170
|
-
sharedProps = {}
|
|
2202
|
+
context
|
|
2203
|
+
config
|
|
2204
|
+
sharedProps = {}
|
|
2171
2205
|
constructor(context, config = {}) {
|
|
2172
|
-
this.context = context
|
|
2173
|
-
this.config = config
|
|
2206
|
+
this.context = context
|
|
2207
|
+
this.config = config
|
|
2174
2208
|
}
|
|
2175
2209
|
escapeForSingleQuotedHtmlAttribute(value) {
|
|
2176
|
-
return value
|
|
2210
|
+
return value
|
|
2211
|
+
.replace(/&/g, '&')
|
|
2212
|
+
.replace(/\\"/g, '\\"')
|
|
2213
|
+
.replace(/</g, '<')
|
|
2214
|
+
.replace(/>/g, '>')
|
|
2215
|
+
.replace(/'/g, ''')
|
|
2177
2216
|
}
|
|
2178
2217
|
render(component, props = {}, rootVars = {}) {
|
|
2179
|
-
let pageUrl
|
|
2218
|
+
let pageUrl
|
|
2180
2219
|
try {
|
|
2181
|
-
const reqUrl = new URL(this.context.req.url,
|
|
2182
|
-
pageUrl = reqUrl.pathname + reqUrl.search
|
|
2220
|
+
const reqUrl = new URL(this.context.req.url, 'http://localhost')
|
|
2221
|
+
pageUrl = reqUrl.pathname + reqUrl.search
|
|
2183
2222
|
} catch {
|
|
2184
|
-
pageUrl = this.context.req.url
|
|
2223
|
+
pageUrl = this.context.req.url
|
|
2185
2224
|
}
|
|
2186
2225
|
const resolveProps = (p) => {
|
|
2187
|
-
const resolved = {}
|
|
2226
|
+
const resolved = {}
|
|
2188
2227
|
for (const [key, value] of Object.entries(p)) {
|
|
2189
|
-
resolved[key] = typeof value ===
|
|
2228
|
+
resolved[key] = typeof value === 'function' ? value() : value
|
|
2190
2229
|
}
|
|
2191
|
-
return resolved
|
|
2192
|
-
}
|
|
2230
|
+
return resolved
|
|
2231
|
+
}
|
|
2193
2232
|
const page = {
|
|
2194
2233
|
component,
|
|
2195
2234
|
props: resolveProps({ ...this.sharedProps, ...props }),
|
|
2196
2235
|
url: pageUrl,
|
|
2197
|
-
version: this.config.version
|
|
2198
|
-
}
|
|
2199
|
-
if (this.context.req.header(
|
|
2200
|
-
this.context.header(
|
|
2201
|
-
this.context.header(
|
|
2202
|
-
return this.context.json(page)
|
|
2203
|
-
}
|
|
2204
|
-
const view = this.context.get("view");
|
|
2205
|
-
const rootView = this.config.rootView ?? "app";
|
|
2206
|
-
if (!view) {
|
|
2207
|
-
throw new Error("OrbitPrism is required for the initial page load in OrbitIon");
|
|
2236
|
+
version: this.config.version,
|
|
2237
|
+
}
|
|
2238
|
+
if (this.context.req.header('X-Inertia')) {
|
|
2239
|
+
this.context.header('X-Inertia', 'true')
|
|
2240
|
+
this.context.header('Vary', 'Accept')
|
|
2241
|
+
return this.context.json(page)
|
|
2208
2242
|
}
|
|
2209
|
-
const
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2243
|
+
const view = this.context.get('view')
|
|
2244
|
+
const rootView = this.config.rootView ?? 'app'
|
|
2245
|
+
if (!view) {
|
|
2246
|
+
throw new Error('OrbitPrism is required for the initial page load in OrbitIon')
|
|
2247
|
+
}
|
|
2248
|
+
const isDev = true
|
|
2249
|
+
return this.context.html(
|
|
2250
|
+
view.render(
|
|
2251
|
+
rootView,
|
|
2252
|
+
{
|
|
2253
|
+
...rootVars,
|
|
2254
|
+
page: this.escapeForSingleQuotedHtmlAttribute(JSON.stringify(page)),
|
|
2255
|
+
isDev,
|
|
2256
|
+
},
|
|
2257
|
+
{ layout: '' }
|
|
2258
|
+
)
|
|
2259
|
+
)
|
|
2215
2260
|
}
|
|
2216
2261
|
share(key, value) {
|
|
2217
|
-
this.sharedProps[key] = value
|
|
2262
|
+
this.sharedProps[key] = value
|
|
2218
2263
|
}
|
|
2219
2264
|
shareAll(props) {
|
|
2220
|
-
Object.assign(this.sharedProps, props)
|
|
2265
|
+
Object.assign(this.sharedProps, props)
|
|
2221
2266
|
}
|
|
2222
2267
|
getSharedProps() {
|
|
2223
|
-
return { ...this.sharedProps }
|
|
2268
|
+
return { ...this.sharedProps }
|
|
2224
2269
|
}
|
|
2225
2270
|
}
|
|
2226
2271
|
|
|
2227
2272
|
// ../ion/src/index.ts
|
|
2228
2273
|
class OrbitIon {
|
|
2229
2274
|
install(core) {
|
|
2230
|
-
core.logger.info(
|
|
2231
|
-
const appVersion = core.config.get(
|
|
2232
|
-
core.adapter.use(
|
|
2233
|
-
const gravitoCtx = new HonoContextWrapper(c)
|
|
2275
|
+
core.logger.info('\uD83D\uDEF0️ Orbit Inertia installed')
|
|
2276
|
+
const appVersion = core.config.get('APP_VERSION', '1.0.0')
|
|
2277
|
+
core.adapter.use('*', async (c, next) => {
|
|
2278
|
+
const gravitoCtx = new HonoContextWrapper(c)
|
|
2234
2279
|
const inertia = new InertiaService(gravitoCtx, {
|
|
2235
2280
|
version: String(appVersion),
|
|
2236
|
-
rootView:
|
|
2237
|
-
})
|
|
2238
|
-
c.set(
|
|
2239
|
-
await next()
|
|
2240
|
-
return
|
|
2241
|
-
})
|
|
2281
|
+
rootView: 'app',
|
|
2282
|
+
})
|
|
2283
|
+
c.set('inertia', inertia)
|
|
2284
|
+
await next()
|
|
2285
|
+
return
|
|
2286
|
+
})
|
|
2242
2287
|
}
|
|
2243
2288
|
}
|
|
2244
|
-
var src_default = OrbitIon
|
|
2245
|
-
export {
|
|
2246
|
-
src_default as default,
|
|
2247
|
-
OrbitIon,
|
|
2248
|
-
InertiaService
|
|
2249
|
-
};
|
|
2289
|
+
var src_default = OrbitIon
|
|
2290
|
+
export { src_default as default, OrbitIon, InertiaService }
|