@gravito/core 1.0.0-beta.6

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/dist/index.js ADDED
@@ -0,0 +1,4688 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // package.json
9
+ var package_default = {
10
+ name: "@gravito/core",
11
+ version: "1.0.0-beta.6",
12
+ description: "",
13
+ module: "./dist/index.js",
14
+ main: "./dist/index.cjs",
15
+ type: "module",
16
+ types: "./dist/index.d.ts",
17
+ exports: {
18
+ ".": {
19
+ types: "./dist/index.d.ts",
20
+ import: "./dist/index.js",
21
+ require: "./dist/index.cjs"
22
+ },
23
+ "./compat": {
24
+ types: "./dist/compat.d.ts",
25
+ import: "./dist/compat.js",
26
+ require: "./dist/compat.cjs"
27
+ }
28
+ },
29
+ files: [
30
+ "dist",
31
+ "README.md",
32
+ "LICENSE"
33
+ ],
34
+ scripts: {
35
+ build: "bun run build.ts",
36
+ test: "bun test",
37
+ "test:coverage": "bun test --coverage --coverage-threshold=80",
38
+ "test:ci": "bun test --coverage --coverage-threshold=80",
39
+ lint: "biome lint ./src ./tests",
40
+ "lint:fix": "biome lint --write ./src ./tests",
41
+ format: "biome format --write ./src ./tests",
42
+ "format:check": "biome format ./src ./tests",
43
+ check: "biome check ./src ./tests",
44
+ "check:fix": "biome check --write ./src ./tests",
45
+ typecheck: "bun tsc -p tsconfig.json --noEmit --skipLibCheck",
46
+ prepublishOnly: "bun run typecheck && bun run test && bun run build"
47
+ },
48
+ keywords: [],
49
+ author: "Carl Lee <carllee0520@gmail.com>",
50
+ license: "MIT",
51
+ repository: {
52
+ type: "git",
53
+ url: "git+https://github.com/gravito-framework/gravito.git",
54
+ directory: "packages/core"
55
+ },
56
+ bugs: {
57
+ url: "https://github.com/gravito-framework/gravito/issues"
58
+ },
59
+ homepage: "https://github.com/gravito-framework/gravito#readme",
60
+ engines: {
61
+ node: ">=18.0.0"
62
+ },
63
+ devDependencies: {
64
+ "bun-types": "latest",
65
+ tsup: "^8.5.1",
66
+ typescript: "^5.9.3"
67
+ },
68
+ publishConfig: {
69
+ access: "public",
70
+ registry: "https://registry.npmjs.org/"
71
+ },
72
+ dependencies: {
73
+ "@gravito/photon": "workspace:*"
74
+ }
75
+ };
76
+
77
+ // src/adapters/PhotonAdapter.ts
78
+ var PhotonRequestWrapper = class {
79
+ constructor(photonCtx) {
80
+ this.photonCtx = photonCtx;
81
+ }
82
+ get url() {
83
+ return this.photonCtx.req.url;
84
+ }
85
+ get method() {
86
+ return this.photonCtx.req.method;
87
+ }
88
+ get path() {
89
+ return this.photonCtx.req.path;
90
+ }
91
+ param(name) {
92
+ return this.photonCtx.req.param(name);
93
+ }
94
+ params() {
95
+ return this.photonCtx.req.param();
96
+ }
97
+ query(name) {
98
+ return this.photonCtx.req.query(name);
99
+ }
100
+ queries() {
101
+ return this.photonCtx.req.queries();
102
+ }
103
+ header(name) {
104
+ if (name) {
105
+ return this.photonCtx.req.header(name);
106
+ }
107
+ return this.photonCtx.req.header();
108
+ }
109
+ async json() {
110
+ return this.photonCtx.req.json();
111
+ }
112
+ async text() {
113
+ return this.photonCtx.req.text();
114
+ }
115
+ async formData() {
116
+ return this.photonCtx.req.formData();
117
+ }
118
+ async arrayBuffer() {
119
+ return this.photonCtx.req.arrayBuffer();
120
+ }
121
+ async parseBody() {
122
+ return this.photonCtx.req.parseBody();
123
+ }
124
+ get raw() {
125
+ return this.photonCtx.req.raw;
126
+ }
127
+ valid(target) {
128
+ return this.photonCtx.req.valid(target);
129
+ }
130
+ };
131
+ var PhotonContextWrapper = class _PhotonContextWrapper {
132
+ constructor(photonCtx) {
133
+ this.photonCtx = photonCtx;
134
+ this._req = new PhotonRequestWrapper(photonCtx);
135
+ }
136
+ _req;
137
+ /**
138
+ * Create a proxied instance to enable object destructuring of context variables
139
+ * This allows: async list({ userService }: Context)
140
+ */
141
+ static create(photonCtx) {
142
+ const instance = new _PhotonContextWrapper(photonCtx);
143
+ return new Proxy(instance, {
144
+ get(target, prop, receiver) {
145
+ if (prop in target) {
146
+ const value2 = Reflect.get(target, prop, receiver);
147
+ if (typeof value2 === "function") {
148
+ return value2.bind(target);
149
+ }
150
+ return value2;
151
+ }
152
+ if (typeof prop === "string") {
153
+ return target.get(prop);
154
+ }
155
+ return void 0;
156
+ }
157
+ });
158
+ }
159
+ get req() {
160
+ return this._req;
161
+ }
162
+ json(data, status) {
163
+ if (status !== void 0) {
164
+ return this.photonCtx.json(data, status);
165
+ }
166
+ return this.photonCtx.json(data);
167
+ }
168
+ text(text, status) {
169
+ if (status !== void 0) {
170
+ return this.photonCtx.text(text, status);
171
+ }
172
+ return this.photonCtx.text(text);
173
+ }
174
+ html(html, status) {
175
+ if (status !== void 0) {
176
+ return this.photonCtx.html(html, status);
177
+ }
178
+ return this.photonCtx.html(html);
179
+ }
180
+ redirect(url, status = 302) {
181
+ return this.photonCtx.redirect(url, status);
182
+ }
183
+ body(data, status) {
184
+ if (data === null) {
185
+ return this.photonCtx.body(null, status);
186
+ }
187
+ return new Response(data, {
188
+ status: status ?? 200,
189
+ headers: new Headers()
190
+ });
191
+ }
192
+ stream(stream, status) {
193
+ return new Response(stream, {
194
+ status: status ?? 200,
195
+ headers: {
196
+ "Content-Type": "application/octet-stream"
197
+ }
198
+ });
199
+ }
200
+ notFound(message) {
201
+ return this.photonCtx.text(message ?? "Not Found", 404);
202
+ }
203
+ forbidden(message) {
204
+ return this.photonCtx.text(message ?? "Forbidden", 403);
205
+ }
206
+ unauthorized(message) {
207
+ return this.photonCtx.text(message ?? "Unauthorized", 401);
208
+ }
209
+ badRequest(message) {
210
+ return this.photonCtx.text(message ?? "Bad Request", 400);
211
+ }
212
+ header(name, value2, options) {
213
+ if (value2 !== void 0) {
214
+ if (options?.append) {
215
+ this.photonCtx.header(name, value2, { append: true });
216
+ } else {
217
+ this.photonCtx.header(name, value2);
218
+ }
219
+ return void 0;
220
+ }
221
+ return this.photonCtx.req.header(name);
222
+ }
223
+ status(code) {
224
+ this.photonCtx.status(code);
225
+ }
226
+ get(key) {
227
+ return this.photonCtx.get(key);
228
+ }
229
+ set(key, value2) {
230
+ this.photonCtx.set(key, value2);
231
+ }
232
+ get executionCtx() {
233
+ return this.photonCtx.executionCtx;
234
+ }
235
+ get env() {
236
+ return this.photonCtx.env;
237
+ }
238
+ get native() {
239
+ return this.photonCtx;
240
+ }
241
+ };
242
+ function toPhotonMiddleware(middleware) {
243
+ return async (c, next) => {
244
+ const ctx = PhotonContextWrapper.create(c);
245
+ const gravitoNext = async () => {
246
+ await next();
247
+ };
248
+ return middleware(ctx, gravitoNext);
249
+ };
250
+ }
251
+ function toPhotonErrorHandler(handler) {
252
+ return async (err, c) => {
253
+ const ctx = PhotonContextWrapper.create(c);
254
+ return handler(err, ctx);
255
+ };
256
+ }
257
+ var PhotonAdapter = class {
258
+ constructor(config2 = {}, photonInstance) {
259
+ this.config = config2;
260
+ if (photonInstance) {
261
+ this.app = photonInstance;
262
+ } else {
263
+ const { Photon: PhotonClass } = __require("@gravito/photon");
264
+ this.app = new PhotonClass();
265
+ }
266
+ }
267
+ name = "photon";
268
+ version = "1.0.0";
269
+ app;
270
+ /**
271
+ * Get the underlying Photon app instance
272
+ */
273
+ get native() {
274
+ return this.app;
275
+ }
276
+ /**
277
+ * Set the underlying Photon app instance
278
+ * Used by PlanetCore during initialization
279
+ */
280
+ setNative(app2) {
281
+ this.app = app2;
282
+ }
283
+ route(method, path2, ...handlers) {
284
+ const fullPath = (this.config.basePath || "") + path2;
285
+ const photonHandlers = handlers.map((h) => toPhotonMiddleware(h));
286
+ const methodFn = this.app[method];
287
+ if (typeof methodFn !== "function") {
288
+ throw new Error(`Unsupported HTTP method: ${method}`);
289
+ }
290
+ methodFn.call(this.app, fullPath, ...photonHandlers);
291
+ }
292
+ routes(routes) {
293
+ for (const routeDef of routes) {
294
+ const middlewareHandlers = (routeDef.middleware || []).map(
295
+ (m) => m
296
+ );
297
+ const allHandlers = [
298
+ ...middlewareHandlers,
299
+ ...routeDef.handlers
300
+ ];
301
+ this.route(routeDef.method, routeDef.path, ...allHandlers);
302
+ }
303
+ }
304
+ use(path2, ...middleware) {
305
+ const fullPath = (this.config.basePath || "") + path2;
306
+ const photonMiddleware = middleware.map((m) => toPhotonMiddleware(m));
307
+ for (const m of photonMiddleware) {
308
+ this.app.use(fullPath, m);
309
+ }
310
+ }
311
+ useGlobal(...middleware) {
312
+ this.use("*", ...middleware);
313
+ }
314
+ mount(path2, subAdapter) {
315
+ if (subAdapter.name === "photon") {
316
+ this.app.route(path2, subAdapter.native);
317
+ } else {
318
+ this.use(`${path2}/*`, async (ctx) => {
319
+ const response = await subAdapter.fetch(ctx.req.raw);
320
+ return response;
321
+ });
322
+ }
323
+ }
324
+ onError(handler) {
325
+ this.app.onError(toPhotonErrorHandler(handler));
326
+ }
327
+ onNotFound(handler) {
328
+ this.app.notFound(async (c) => {
329
+ const ctx = PhotonContextWrapper.create(c);
330
+ return handler(ctx);
331
+ });
332
+ }
333
+ fetch = (request, server) => {
334
+ return this.app.fetch(request, server);
335
+ };
336
+ createContext(_request) {
337
+ throw new Error(
338
+ "PhotonAdapter.createContext() should not be called directly. Use the router pipeline instead."
339
+ );
340
+ }
341
+ async init() {
342
+ }
343
+ async shutdown() {
344
+ }
345
+ };
346
+ function createPhotonAdapter(config2) {
347
+ return new PhotonAdapter(config2);
348
+ }
349
+ var GravitoAdapter = PhotonAdapter;
350
+ var createGravitoAdapter = createPhotonAdapter;
351
+
352
+ // src/adapters/types.ts
353
+ function isHttpAdapter(value2) {
354
+ return typeof value2 === "object" && value2 !== null && "name" in value2 && "fetch" in value2 && typeof value2.fetch === "function" && "route" in value2 && typeof value2.route === "function";
355
+ }
356
+
357
+ // src/Application.ts
358
+ import fs from "fs/promises";
359
+ import path from "path";
360
+ import { pathToFileURL } from "url";
361
+
362
+ // src/runtime.ts
363
+ import { createRequire } from "module";
364
+ var getRuntimeEnv = () => {
365
+ const kind = getRuntimeKind();
366
+ if (kind === "bun" && typeof Bun !== "undefined") {
367
+ return Bun.env;
368
+ }
369
+ if (kind === "deno") {
370
+ const deno = globalThis.Deno;
371
+ if (deno?.env?.toObject) {
372
+ return deno.env.toObject();
373
+ }
374
+ }
375
+ if (typeof process !== "undefined" && process.env) {
376
+ return process.env;
377
+ }
378
+ return {};
379
+ };
380
+ var getRuntimeKind = () => {
381
+ if (typeof Bun !== "undefined" && typeof Bun.spawn === "function") {
382
+ return "bun";
383
+ }
384
+ const denoRuntime = globalThis.Deno;
385
+ if (typeof denoRuntime !== "undefined" && typeof denoRuntime?.version?.deno === "string") {
386
+ return "deno";
387
+ }
388
+ if (typeof process !== "undefined" && process.versions?.node) {
389
+ return "node";
390
+ }
391
+ return "unknown";
392
+ };
393
+ var toUint8Array = async (data) => {
394
+ if (data instanceof Uint8Array) {
395
+ return data;
396
+ }
397
+ if (typeof data === "string") {
398
+ return new TextEncoder().encode(data);
399
+ }
400
+ if (data instanceof ArrayBuffer) {
401
+ return new Uint8Array(data);
402
+ }
403
+ if (typeof Buffer !== "undefined" && data instanceof Buffer) {
404
+ return new Uint8Array(data);
405
+ }
406
+ if (data instanceof Blob) {
407
+ return new Uint8Array(await data.arrayBuffer());
408
+ }
409
+ return new Uint8Array();
410
+ };
411
+ var createBunAdapter = () => ({
412
+ kind: "bun",
413
+ spawn(command, options = {}) {
414
+ const [cmd, ...args] = command;
415
+ if (!cmd) {
416
+ throw new Error("[RuntimeAdapter] spawn() requires a command");
417
+ }
418
+ const proc = Bun.spawn([cmd, ...args], {
419
+ cwd: options.cwd,
420
+ env: options.env,
421
+ stdin: options.stdin,
422
+ stdout: options.stdout ?? "pipe",
423
+ stderr: options.stderr ?? "pipe"
424
+ });
425
+ return {
426
+ exited: proc.exited,
427
+ stdout: proc.stdout ?? null,
428
+ stderr: proc.stderr ?? null,
429
+ kill: (signal) => {
430
+ const bunSignal = typeof signal === "number" ? signal : signal;
431
+ proc.kill(bunSignal);
432
+ }
433
+ };
434
+ },
435
+ async writeFile(path2, data) {
436
+ await Bun.write(path2, data);
437
+ },
438
+ async readFile(path2) {
439
+ const file = Bun.file(path2);
440
+ const buffer = await file.arrayBuffer();
441
+ return new Uint8Array(buffer);
442
+ },
443
+ async readFileAsBlob(path2) {
444
+ return Bun.file(path2);
445
+ },
446
+ async exists(path2) {
447
+ return await Bun.file(path2).exists();
448
+ },
449
+ async stat(path2) {
450
+ const stats = await Bun.file(path2).stat();
451
+ return { size: stats.size };
452
+ },
453
+ async deleteFile(path2) {
454
+ const fs2 = await import("fs/promises");
455
+ try {
456
+ await fs2.unlink(path2);
457
+ } catch {
458
+ }
459
+ },
460
+ serve(config2) {
461
+ return Bun.serve({
462
+ port: config2.port,
463
+ fetch: config2.fetch,
464
+ websocket: config2.websocket
465
+ });
466
+ }
467
+ });
468
+ var createNodeAdapter = () => ({
469
+ kind: "node",
470
+ spawn(command, options = {}) {
471
+ const [cmd, ...args] = command;
472
+ if (!cmd) {
473
+ throw new Error("[RuntimeAdapter] spawn() requires a command");
474
+ }
475
+ const require2 = createRequire(import.meta.url);
476
+ const childProcess = require2("node:child_process");
477
+ const stream = require2("node:stream");
478
+ const stdioMap = (value2) => {
479
+ if (value2 === "inherit") {
480
+ return "inherit";
481
+ }
482
+ if (value2 === "ignore") {
483
+ return "ignore";
484
+ }
485
+ return "pipe";
486
+ };
487
+ const stdinMap = (value2) => {
488
+ if (value2 === "inherit") {
489
+ return "inherit";
490
+ }
491
+ if (value2 === "ignore") {
492
+ return "ignore";
493
+ }
494
+ return "pipe";
495
+ };
496
+ const child = childProcess.spawn(cmd, args, {
497
+ cwd: options.cwd,
498
+ env: options.env,
499
+ stdio: [stdinMap(options.stdin), stdioMap(options.stdout), stdioMap(options.stderr)]
500
+ });
501
+ const toWeb = (streamReadable) => {
502
+ if (!streamReadable) {
503
+ return null;
504
+ }
505
+ const maybeWeb = streamReadable;
506
+ if (typeof maybeWeb.getReader === "function") {
507
+ return maybeWeb;
508
+ }
509
+ return stream.Readable.toWeb(streamReadable);
510
+ };
511
+ const exited = new Promise((resolve, reject) => {
512
+ child.on("error", reject);
513
+ child.on("exit", (code) => resolve(code ?? 0));
514
+ });
515
+ return {
516
+ exited,
517
+ stdout: toWeb(child.stdout),
518
+ stderr: toWeb(child.stderr),
519
+ kill: (signal) => child.kill(signal)
520
+ };
521
+ },
522
+ async writeFile(path2, data) {
523
+ const fs2 = await import("fs/promises");
524
+ const payload = await toUint8Array(data);
525
+ await fs2.writeFile(path2, payload);
526
+ },
527
+ async readFile(path2) {
528
+ const fs2 = await import("fs/promises");
529
+ const buffer = await fs2.readFile(path2);
530
+ return new Uint8Array(buffer);
531
+ },
532
+ async readFileAsBlob(path2) {
533
+ const buffer = await this.readFile(path2);
534
+ return new Blob([buffer]);
535
+ },
536
+ async exists(path2) {
537
+ const fs2 = await import("fs/promises");
538
+ try {
539
+ await fs2.access(path2);
540
+ return true;
541
+ } catch {
542
+ return false;
543
+ }
544
+ },
545
+ async stat(path2) {
546
+ const fs2 = await import("fs/promises");
547
+ const stats = await fs2.stat(path2);
548
+ return { size: stats.size };
549
+ },
550
+ async deleteFile(path2) {
551
+ const fs2 = await import("fs/promises");
552
+ try {
553
+ await fs2.unlink(path2);
554
+ } catch {
555
+ }
556
+ },
557
+ serve(_config) {
558
+ throw new Error("[RuntimeAdapter] Bun runtime is required for Bun.serve()");
559
+ }
560
+ });
561
+ var createDenoAdapter = () => ({
562
+ kind: "deno",
563
+ spawn(command, options = {}) {
564
+ const [cmd, ...args] = command;
565
+ if (!cmd) {
566
+ throw new Error("[RuntimeAdapter] spawn() requires a command");
567
+ }
568
+ const deno = globalThis.Deno;
569
+ if (!deno?.Command) {
570
+ throw new Error("[RuntimeAdapter] Deno runtime is required for spawn()");
571
+ }
572
+ const stdin = options.stdin === "inherit" ? "inherit" : options.stdin === "ignore" ? "null" : "piped";
573
+ const stdout = options.stdout === "inherit" ? "inherit" : options.stdout === "ignore" ? "null" : "piped";
574
+ const stderr = options.stderr === "inherit" ? "inherit" : options.stderr === "ignore" ? "null" : "piped";
575
+ const proc = new deno.Command(cmd, {
576
+ args,
577
+ cwd: options.cwd,
578
+ env: options.env,
579
+ stdin,
580
+ stdout,
581
+ stderr
582
+ }).spawn();
583
+ const exited = proc.status.then((status) => status.code ?? 0);
584
+ return {
585
+ exited,
586
+ stdout: proc.stdout ?? null,
587
+ stderr: proc.stderr ?? null,
588
+ kill: (signal) => {
589
+ const killSignal = typeof signal === "string" ? signal : "SIGTERM";
590
+ proc.kill(killSignal);
591
+ }
592
+ };
593
+ },
594
+ async writeFile(path2, data) {
595
+ const deno = globalThis.Deno;
596
+ if (!deno?.writeFile) {
597
+ throw new Error("[RuntimeAdapter] Deno runtime is required for writeFile()");
598
+ }
599
+ const payload = await toUint8Array(data);
600
+ await deno.writeFile(path2, payload);
601
+ },
602
+ async readFile(path2) {
603
+ const deno = globalThis.Deno;
604
+ if (!deno?.readFile) {
605
+ throw new Error("[RuntimeAdapter] Deno runtime is required for readFile()");
606
+ }
607
+ return await deno.readFile(path2);
608
+ },
609
+ async readFileAsBlob(path2) {
610
+ const buffer = await this.readFile(path2);
611
+ return new Blob([buffer]);
612
+ },
613
+ async exists(path2) {
614
+ const deno = globalThis.Deno;
615
+ if (!deno?.stat) {
616
+ throw new Error("[RuntimeAdapter] Deno runtime is required for exists()");
617
+ }
618
+ try {
619
+ await deno.stat(path2);
620
+ return true;
621
+ } catch {
622
+ return false;
623
+ }
624
+ },
625
+ async stat(path2) {
626
+ const deno = globalThis.Deno;
627
+ if (!deno?.stat) {
628
+ throw new Error("[RuntimeAdapter] Deno runtime is required for stat()");
629
+ }
630
+ const stats = await deno.stat(path2);
631
+ return { size: stats.size };
632
+ },
633
+ async deleteFile(path2) {
634
+ const deno = globalThis.Deno;
635
+ if (!deno?.remove) {
636
+ throw new Error("[RuntimeAdapter] Deno runtime is required for deleteFile()");
637
+ }
638
+ try {
639
+ await deno.remove(path2);
640
+ } catch {
641
+ }
642
+ },
643
+ serve(_config) {
644
+ throw new Error("[RuntimeAdapter] Bun runtime is required for Bun.serve()");
645
+ }
646
+ });
647
+ var createUnknownAdapter = () => ({
648
+ kind: "unknown",
649
+ spawn() {
650
+ throw new Error("[RuntimeAdapter] Unsupported runtime for spawn()");
651
+ },
652
+ async writeFile() {
653
+ throw new Error("[RuntimeAdapter] Unsupported runtime for writeFile()");
654
+ },
655
+ async readFile() {
656
+ throw new Error("[RuntimeAdapter] Unsupported runtime for readFile()");
657
+ },
658
+ async readFileAsBlob() {
659
+ throw new Error("[RuntimeAdapter] Unsupported runtime for readFileAsBlob()");
660
+ },
661
+ async exists() {
662
+ throw new Error("[RuntimeAdapter] Unsupported runtime for exists()");
663
+ },
664
+ async stat() {
665
+ throw new Error("[RuntimeAdapter] Unsupported runtime for stat()");
666
+ },
667
+ async deleteFile() {
668
+ throw new Error("[RuntimeAdapter] Unsupported runtime for deleteFile()");
669
+ },
670
+ serve() {
671
+ throw new Error("[RuntimeAdapter] Unsupported runtime for serve()");
672
+ }
673
+ });
674
+ var runtimeAdapter = null;
675
+ var getRuntimeAdapter = () => {
676
+ if (runtimeAdapter) {
677
+ return runtimeAdapter;
678
+ }
679
+ const kind = getRuntimeKind();
680
+ runtimeAdapter = kind === "bun" ? createBunAdapter() : kind === "node" ? createNodeAdapter() : kind === "deno" ? createDenoAdapter() : createUnknownAdapter();
681
+ return runtimeAdapter;
682
+ };
683
+ var passwordAdapter = null;
684
+ var getPasswordAdapter = () => {
685
+ if (passwordAdapter) {
686
+ return passwordAdapter;
687
+ }
688
+ const kind = getRuntimeKind();
689
+ if (kind === "bun" && typeof Bun !== "undefined") {
690
+ passwordAdapter = {
691
+ hash: async (value2, options) => {
692
+ if (options.algorithm === "bcrypt") {
693
+ return await Bun.password.hash(value2, {
694
+ algorithm: "bcrypt",
695
+ cost: options.cost ?? 12
696
+ });
697
+ }
698
+ return await Bun.password.hash(value2, {
699
+ algorithm: "argon2id",
700
+ ...options.memoryCost !== void 0 ? { memoryCost: options.memoryCost } : {},
701
+ ...options.timeCost !== void 0 ? { timeCost: options.timeCost } : {},
702
+ ...options.parallelism !== void 0 ? { parallelism: options.parallelism } : {}
703
+ });
704
+ },
705
+ verify: async (value2, hashed) => await Bun.password.verify(value2, hashed)
706
+ };
707
+ return passwordAdapter;
708
+ }
709
+ passwordAdapter = {
710
+ hash: async () => {
711
+ throw new Error(
712
+ "[RuntimeAdapter] Password hashing requires Bun runtime or a Node/Deno adapter"
713
+ );
714
+ },
715
+ verify: async () => {
716
+ throw new Error(
717
+ "[RuntimeAdapter] Password hashing requires Bun runtime or a Node/Deno adapter"
718
+ );
719
+ }
720
+ };
721
+ return passwordAdapter;
722
+ };
723
+ var createSqliteDatabase = async (path2) => {
724
+ const kind = getRuntimeKind();
725
+ if (kind === "bun") {
726
+ const sqlite = await import("bun:sqlite");
727
+ const db = new sqlite.Database(path2, { create: true });
728
+ return db;
729
+ }
730
+ throw new Error("[RuntimeAdapter] SQLite storage requires Bun runtime or a Node/Deno adapter");
731
+ };
732
+
733
+ // src/ConfigManager.ts
734
+ var ConfigManager = class {
735
+ config = /* @__PURE__ */ new Map();
736
+ constructor(initialConfig = {}) {
737
+ for (const [key, value2] of Object.entries(initialConfig)) {
738
+ this.config.set(key, value2);
739
+ }
740
+ this.loadEnv();
741
+ }
742
+ /**
743
+ * Load all environment variables from the active runtime.
744
+ */
745
+ loadEnv() {
746
+ const env2 = getRuntimeEnv();
747
+ for (const key of Object.keys(env2)) {
748
+ if (env2[key] !== void 0) {
749
+ this.config.set(key, env2[key]);
750
+ }
751
+ }
752
+ }
753
+ /**
754
+ * Get a configuration value (generic return type supported).
755
+ * Supports dot notation for deep access (e.g. 'app.name').
756
+ */
757
+ get(key, defaultValue) {
758
+ if (this.config.has(key)) {
759
+ return this.config.get(key);
760
+ }
761
+ if (key.includes(".")) {
762
+ const parts = key.split(".");
763
+ const rootKey = parts[0];
764
+ if (rootKey) {
765
+ let current = this.config.get(rootKey);
766
+ if (current !== void 0) {
767
+ for (let i = 1; i < parts.length; i++) {
768
+ const part = parts[i];
769
+ if (part && current && typeof current === "object" && part in current) {
770
+ current = current[part];
771
+ } else {
772
+ current = void 0;
773
+ break;
774
+ }
775
+ }
776
+ if (current !== void 0) {
777
+ return current;
778
+ }
779
+ }
780
+ }
781
+ }
782
+ if (defaultValue !== void 0) {
783
+ return defaultValue;
784
+ }
785
+ throw new Error(`Config key '${key}' not found`);
786
+ }
787
+ /**
788
+ * Set a configuration value.
789
+ */
790
+ set(key, value2) {
791
+ this.config.set(key, value2);
792
+ }
793
+ /**
794
+ * Check whether a key exists.
795
+ */
796
+ has(key) {
797
+ return this.config.has(key);
798
+ }
799
+ };
800
+
801
+ // src/Container.ts
802
+ var Container = class {
803
+ bindings = /* @__PURE__ */ new Map();
804
+ instances = /* @__PURE__ */ new Map();
805
+ /**
806
+ * Bind a service to the container.
807
+ * New instance will be created on each resolution.
808
+ */
809
+ bind(key, factory) {
810
+ this.bindings.set(key, { factory, shared: false });
811
+ }
812
+ /**
813
+ * Bind a shared service to the container (Singleton).
814
+ * Same instance will be returned on each resolution.
815
+ */
816
+ singleton(key, factory) {
817
+ this.bindings.set(key, { factory, shared: true });
818
+ }
819
+ /**
820
+ * Register an existing instance as shared service.
821
+ */
822
+ instance(key, instance) {
823
+ this.instances.set(key, instance);
824
+ }
825
+ /**
826
+ * Resolve a service from the container.
827
+ */
828
+ make(key) {
829
+ if (this.instances.has(key)) {
830
+ return this.instances.get(key);
831
+ }
832
+ const binding = this.bindings.get(key);
833
+ if (!binding) {
834
+ throw new Error(`Service '${key}' not found in container`);
835
+ }
836
+ const instance = binding.factory(this);
837
+ if (binding.shared) {
838
+ this.instances.set(key, instance);
839
+ }
840
+ return instance;
841
+ }
842
+ /**
843
+ * Check if a service is bound.
844
+ */
845
+ has(key) {
846
+ return this.bindings.has(key) || this.instances.has(key);
847
+ }
848
+ /**
849
+ * Flush all instances and bindings.
850
+ */
851
+ flush() {
852
+ this.bindings.clear();
853
+ this.instances.clear();
854
+ }
855
+ /**
856
+ * Forget a specific instance (but keep binding)
857
+ */
858
+ forget(key) {
859
+ this.instances.delete(key);
860
+ }
861
+ };
862
+
863
+ // src/Logger.ts
864
+ var ConsoleLogger = class {
865
+ debug(message, ...args) {
866
+ console.debug(`[DEBUG] ${message}`, ...args);
867
+ }
868
+ info(message, ...args) {
869
+ console.info(`[INFO] ${message}`, ...args);
870
+ }
871
+ warn(message, ...args) {
872
+ console.warn(`[WARN] ${message}`, ...args);
873
+ }
874
+ error(message, ...args) {
875
+ console.error(`[ERROR] ${message}`, ...args);
876
+ }
877
+ };
878
+
879
+ // src/types/events.ts
880
+ var Event = class {
881
+ /**
882
+ * Whether this event should be broadcast.
883
+ */
884
+ shouldBroadcast() {
885
+ return "broadcastOn" in this && typeof this.broadcastOn === "function";
886
+ }
887
+ /**
888
+ * Get broadcast channel.
889
+ */
890
+ getBroadcastChannel() {
891
+ if (this.shouldBroadcast()) {
892
+ return this.broadcastOn();
893
+ }
894
+ return null;
895
+ }
896
+ /**
897
+ * Get broadcast payload.
898
+ */
899
+ getBroadcastData() {
900
+ if (this.shouldBroadcast()) {
901
+ const broadcast = this;
902
+ if (broadcast.broadcastWith) {
903
+ return broadcast.broadcastWith();
904
+ }
905
+ const data = {};
906
+ for (const [key, value2] of Object.entries(this)) {
907
+ if (!key.startsWith("_") && typeof value2 !== "function") {
908
+ data[key] = value2;
909
+ }
910
+ }
911
+ return data;
912
+ }
913
+ return {};
914
+ }
915
+ /**
916
+ * Get broadcast event name.
917
+ */
918
+ getBroadcastEventName() {
919
+ if (this.shouldBroadcast()) {
920
+ const broadcast = this;
921
+ if (broadcast.broadcastAs) {
922
+ return broadcast.broadcastAs();
923
+ }
924
+ }
925
+ return this.constructor.name;
926
+ }
927
+ };
928
+
929
+ // src/EventManager.ts
930
+ var EventManager = class {
931
+ constructor(core) {
932
+ this.core = core;
933
+ }
934
+ /**
935
+ * Listener registry.
936
+ * Key: event class or event name
937
+ * Value: listener registrations
938
+ */
939
+ listeners = /* @__PURE__ */ new Map();
940
+ /**
941
+ * Broadcast manager (optional, injected by `orbit-broadcasting`).
942
+ */
943
+ broadcastManager;
944
+ /**
945
+ * Queue manager (optional, injected by `orbit-queue`).
946
+ */
947
+ queueManager;
948
+ /**
949
+ * Register the broadcast manager (called by `orbit-broadcasting`).
950
+ */
951
+ setBroadcastManager(manager) {
952
+ this.broadcastManager = manager;
953
+ }
954
+ /**
955
+ * Register the queue manager (called by `orbit-queue`).
956
+ */
957
+ setQueueManager(manager) {
958
+ this.queueManager = manager;
959
+ }
960
+ /**
961
+ * Register an event listener.
962
+ *
963
+ * @param event - Event class or event name
964
+ * @param listener - Listener instance or listener class
965
+ * @param options - Optional queue options
966
+ *
967
+ * @example
968
+ * ```typescript
969
+ * // Synchronous listener
970
+ * core.events.listen(UserRegistered, SendWelcomeEmail)
971
+ *
972
+ * // Queued listener (async)
973
+ * core.events.listen(UserRegistered, SendWelcomeEmail, {
974
+ * queue: 'emails',
975
+ * delay: 60
976
+ * })
977
+ * ```
978
+ */
979
+ listen(event, listener, options) {
980
+ const eventKey = typeof event === "string" ? event : event;
981
+ if (!this.listeners.has(eventKey)) {
982
+ this.listeners.set(eventKey, []);
983
+ }
984
+ const registration = {
985
+ listener,
986
+ ...options
987
+ };
988
+ this.listeners.get(eventKey)?.push(registration);
989
+ }
990
+ /**
991
+ * Remove an event listener.
992
+ *
993
+ * @param event - Event class or event name
994
+ * @param listener - Listener to remove
995
+ */
996
+ unlisten(event, listener) {
997
+ const eventKey = typeof event === "string" ? event : event;
998
+ const registrations = this.listeners.get(eventKey);
999
+ if (!registrations) {
1000
+ return;
1001
+ }
1002
+ const filtered = registrations.filter((reg) => reg.listener !== listener);
1003
+ if (filtered.length === 0) {
1004
+ this.listeners.delete(eventKey);
1005
+ } else {
1006
+ this.listeners.set(eventKey, filtered);
1007
+ }
1008
+ }
1009
+ /**
1010
+ * Dispatch an event.
1011
+ *
1012
+ * Runs all registered listeners. If a listener implements `ShouldQueue` or
1013
+ * has queue options, the listener will be pushed to the queue for async execution.
1014
+ *
1015
+ * @param event - Event instance
1016
+ *
1017
+ * @example
1018
+ * ```typescript
1019
+ * await core.events.dispatch(new UserRegistered(user))
1020
+ * ```
1021
+ */
1022
+ async dispatch(event) {
1023
+ const eventKey = event.constructor;
1024
+ const eventName = event.constructor.name;
1025
+ await this.core.hooks.doAction(`event:${eventName}`, event);
1026
+ await this.core.hooks.doAction("event:dispatched", { event, eventName });
1027
+ if (event instanceof Event && event.shouldBroadcast() && this.broadcastManager) {
1028
+ const channel = event.getBroadcastChannel();
1029
+ if (channel) {
1030
+ const channelName = typeof channel === "string" ? channel : channel.name;
1031
+ const channelType = typeof channel === "string" ? "public" : channel.type;
1032
+ const data = event.getBroadcastData();
1033
+ const broadcastEventName = event.getBroadcastEventName();
1034
+ await this.broadcastManager.broadcast(event, { name: channelName, type: channelType }, data, broadcastEventName).catch((error) => {
1035
+ this.core.logger.error(`[EventManager] Failed to broadcast event ${eventName}:`, error);
1036
+ });
1037
+ }
1038
+ }
1039
+ const registrations = this.listeners.get(eventKey) || [];
1040
+ const stringRegistrations = this.listeners.get(eventName) || [];
1041
+ const allRegistrations = [...registrations, ...stringRegistrations];
1042
+ for (const registration of allRegistrations) {
1043
+ try {
1044
+ let listenerInstance;
1045
+ if (typeof registration.listener === "function") {
1046
+ listenerInstance = new registration.listener();
1047
+ } else {
1048
+ listenerInstance = registration.listener;
1049
+ }
1050
+ const shouldQueue = "queue" in listenerInstance || registration.queue !== void 0 || registration.connection !== void 0 || registration.delay !== void 0;
1051
+ if (shouldQueue && this.queueManager) {
1052
+ const queue = listenerInstance.queue || registration.queue;
1053
+ const connection = listenerInstance.connection || registration.connection;
1054
+ const delay = listenerInstance.delay || registration.delay;
1055
+ const queueJob = {
1056
+ type: "event-listener",
1057
+ event: eventName,
1058
+ listener: listenerInstance.constructor.name,
1059
+ eventData: this.serializeEvent(event),
1060
+ handle: async () => {
1061
+ await listenerInstance.handle(event);
1062
+ }
1063
+ };
1064
+ await this.queueManager.push(queueJob, queue, connection, delay);
1065
+ } else {
1066
+ await listenerInstance.handle(event);
1067
+ }
1068
+ } catch (error) {
1069
+ this.core.logger.error(`[EventManager] Error in listener for event ${eventName}:`, error);
1070
+ }
1071
+ }
1072
+ }
1073
+ /**
1074
+ * Serialize an event (for queueing).
1075
+ */
1076
+ serializeEvent(event) {
1077
+ const data = {};
1078
+ for (const [key, value2] of Object.entries(event)) {
1079
+ if (!key.startsWith("_") && typeof value2 !== "function") {
1080
+ data[key] = value2;
1081
+ }
1082
+ }
1083
+ return data;
1084
+ }
1085
+ /**
1086
+ * Get all registered listeners.
1087
+ */
1088
+ getListeners(event) {
1089
+ if (event) {
1090
+ const eventKey = typeof event === "string" ? event : event;
1091
+ return this.listeners.get(eventKey) || [];
1092
+ }
1093
+ const all = [];
1094
+ for (const registrations of this.listeners.values()) {
1095
+ all.push(...registrations);
1096
+ }
1097
+ return all;
1098
+ }
1099
+ /**
1100
+ * Clear all listeners.
1101
+ */
1102
+ clear() {
1103
+ this.listeners.clear();
1104
+ }
1105
+ };
1106
+
1107
+ // src/exceptions/GravitoException.ts
1108
+ var GravitoException = class extends Error {
1109
+ status;
1110
+ code;
1111
+ i18nKey;
1112
+ i18nParams;
1113
+ constructor(status, code, options = {}) {
1114
+ super(options.message);
1115
+ this.name = "GravitoException";
1116
+ this.status = status;
1117
+ this.cause = options.cause;
1118
+ this.code = code;
1119
+ if (options.i18nKey) {
1120
+ this.i18nKey = options.i18nKey;
1121
+ }
1122
+ if (options.i18nParams) {
1123
+ this.i18nParams = options.i18nParams;
1124
+ }
1125
+ }
1126
+ // Helper for i18n
1127
+ getLocalizedMessage(t) {
1128
+ if (this.i18nKey) {
1129
+ return t(this.i18nKey, this.i18nParams);
1130
+ }
1131
+ return this.message;
1132
+ }
1133
+ };
1134
+
1135
+ // src/exceptions/HttpException.ts
1136
+ var HttpException = class extends GravitoException {
1137
+ constructor(status, options = {}) {
1138
+ super(status, "HTTP_ERROR", options);
1139
+ this.name = "HttpException";
1140
+ }
1141
+ };
1142
+
1143
+ // src/exceptions/ValidationException.ts
1144
+ var ValidationException = class extends GravitoException {
1145
+ errors;
1146
+ redirectTo;
1147
+ input;
1148
+ constructor(errors2, message = "Validation failed") {
1149
+ super(422, "VALIDATION_ERROR", {
1150
+ message,
1151
+ i18nKey: "errors.validation.failed"
1152
+ });
1153
+ this.errors = errors2;
1154
+ }
1155
+ withRedirect(url) {
1156
+ this.redirectTo = url;
1157
+ return this;
1158
+ }
1159
+ withInput(input) {
1160
+ this.input = input;
1161
+ return this;
1162
+ }
1163
+ };
1164
+
1165
+ // src/GlobalErrorHandlers.ts
1166
+ var stateKey = /* @__PURE__ */ Symbol.for("gravito.core.globalErrorHandlers");
1167
+ function getGlobalState() {
1168
+ const g = globalThis;
1169
+ const existing = g[stateKey];
1170
+ if (existing) {
1171
+ return existing;
1172
+ }
1173
+ const created = {
1174
+ nextId: 1,
1175
+ sinks: /* @__PURE__ */ new Map(),
1176
+ listenersInstalled: false,
1177
+ onUnhandledRejection: void 0,
1178
+ onUncaughtException: void 0
1179
+ };
1180
+ g[stateKey] = created;
1181
+ return created;
1182
+ }
1183
+ function offProcess(event, listener) {
1184
+ const p = process;
1185
+ if (typeof p.off === "function") {
1186
+ p.off(event, listener);
1187
+ return;
1188
+ }
1189
+ if (typeof p.removeListener === "function") {
1190
+ p.removeListener(event, listener);
1191
+ }
1192
+ }
1193
+ function safeMessageFromUnknown(error) {
1194
+ if (error instanceof Error) {
1195
+ return error.message || "Error";
1196
+ }
1197
+ if (typeof error === "string") {
1198
+ return error;
1199
+ }
1200
+ try {
1201
+ return JSON.stringify(error);
1202
+ } catch {
1203
+ return String(error);
1204
+ }
1205
+ }
1206
+ async function handleProcessError(kind, error) {
1207
+ const state = getGlobalState();
1208
+ if (state.sinks.size === 0) {
1209
+ return;
1210
+ }
1211
+ const isProduction = process.env.NODE_ENV === "production";
1212
+ let shouldExit = false;
1213
+ let exitCode = 1;
1214
+ let exitTimer;
1215
+ try {
1216
+ const sinks = Array.from(state.sinks.values());
1217
+ const prepared = await Promise.all(
1218
+ sinks.map(async (sink) => {
1219
+ const defaultExit = sink.mode === "exit" || sink.mode === "exitInProduction" && isProduction;
1220
+ let ctx = {
1221
+ ...sink.core ? { core: sink.core } : {},
1222
+ kind,
1223
+ error,
1224
+ isProduction,
1225
+ timestamp: Date.now(),
1226
+ exit: defaultExit,
1227
+ exitCode: sink.exitCode ?? 1,
1228
+ gracePeriodMs: sink.gracePeriodMs ?? 250
1229
+ };
1230
+ if (sink.core) {
1231
+ ctx = await sink.core.hooks.applyFilters(
1232
+ "processError:context",
1233
+ ctx
1234
+ );
1235
+ }
1236
+ return { sink, ctx };
1237
+ })
1238
+ );
1239
+ const exitTargets = prepared.map((p) => p.ctx).filter((ctx) => (ctx.exit ?? false) && (ctx.exitCode ?? 1) >= 0);
1240
+ shouldExit = exitTargets.length > 0;
1241
+ const gracePeriodMs = Math.max(0, ...exitTargets.map((c) => c.gracePeriodMs ?? 250));
1242
+ exitCode = Math.max(0, ...exitTargets.map((c) => c.exitCode ?? 1));
1243
+ if (shouldExit) {
1244
+ exitTimer = setTimeout(() => {
1245
+ process.exit(exitCode);
1246
+ }, gracePeriodMs);
1247
+ exitTimer.unref?.();
1248
+ }
1249
+ await Promise.all(
1250
+ prepared.map(async ({ sink, ctx }) => {
1251
+ const logger2 = sink.logger ?? sink.core?.logger;
1252
+ const logLevel = ctx.logLevel ?? "error";
1253
+ if (logger2 && logLevel !== "none") {
1254
+ const message = safeMessageFromUnknown(ctx.error);
1255
+ const msg = ctx.logMessage ?? `[${ctx.kind}] ${message}`;
1256
+ if (logLevel === "error") {
1257
+ logger2.error(msg, ctx.error);
1258
+ } else if (logLevel === "warn") {
1259
+ logger2.warn(msg, ctx.error);
1260
+ } else {
1261
+ logger2.info(msg, ctx.error);
1262
+ }
1263
+ }
1264
+ if (sink.core) {
1265
+ await sink.core.hooks.doAction("processError:report", ctx);
1266
+ }
1267
+ })
1268
+ );
1269
+ } catch (e) {
1270
+ console.error("[@gravito/core] Failed to handle process-level error:", e);
1271
+ } finally {
1272
+ if (shouldExit) {
1273
+ clearTimeout(exitTimer);
1274
+ process.exit(exitCode);
1275
+ }
1276
+ }
1277
+ }
1278
+ function ensureListenersInstalled() {
1279
+ const state = getGlobalState();
1280
+ if (state.listenersInstalled) {
1281
+ return;
1282
+ }
1283
+ if (typeof process === "undefined" || typeof process.on !== "function") {
1284
+ return;
1285
+ }
1286
+ state.onUnhandledRejection = (reason) => {
1287
+ void handleProcessError("unhandledRejection", reason);
1288
+ };
1289
+ state.onUncaughtException = (error) => {
1290
+ void handleProcessError("uncaughtException", error);
1291
+ };
1292
+ process.on("unhandledRejection", state.onUnhandledRejection);
1293
+ process.on("uncaughtException", state.onUncaughtException);
1294
+ state.listenersInstalled = true;
1295
+ }
1296
+ function teardownListenersIfUnused() {
1297
+ const state = getGlobalState();
1298
+ if (!state.listenersInstalled || state.sinks.size > 0) {
1299
+ return;
1300
+ }
1301
+ if (state.onUnhandledRejection) {
1302
+ offProcess("unhandledRejection", state.onUnhandledRejection);
1303
+ }
1304
+ if (state.onUncaughtException) {
1305
+ offProcess("uncaughtException", state.onUncaughtException);
1306
+ }
1307
+ state.onUnhandledRejection = void 0;
1308
+ state.onUncaughtException = void 0;
1309
+ state.listenersInstalled = false;
1310
+ }
1311
+ function registerGlobalErrorHandlers(options = {}) {
1312
+ const state = getGlobalState();
1313
+ ensureListenersInstalled();
1314
+ const id = state.nextId++;
1315
+ state.sinks.set(id, {
1316
+ ...options,
1317
+ mode: options.mode ?? "exitInProduction"
1318
+ });
1319
+ return () => {
1320
+ state.sinks.delete(id);
1321
+ teardownListenersIfUnused();
1322
+ };
1323
+ }
1324
+
1325
+ // src/HookManager.ts
1326
+ var HookManager = class {
1327
+ filters = /* @__PURE__ */ new Map();
1328
+ actions = /* @__PURE__ */ new Map();
1329
+ /**
1330
+ * Register a filter hook.
1331
+ *
1332
+ * Filters are used to transform a value (input/output).
1333
+ *
1334
+ * @template T - The type of value being filtered.
1335
+ * @param hook - The name of the hook.
1336
+ * @param callback - The callback function to execute.
1337
+ *
1338
+ * @example
1339
+ * ```typescript
1340
+ * core.hooks.addFilter('content', async (content: string) => {
1341
+ * return content.toUpperCase();
1342
+ * });
1343
+ * ```
1344
+ */
1345
+ addFilter(hook, callback) {
1346
+ if (!this.filters.has(hook)) {
1347
+ this.filters.set(hook, []);
1348
+ }
1349
+ this.filters.get(hook)?.push(callback);
1350
+ }
1351
+ /**
1352
+ * Apply all registered filters sequentially.
1353
+ *
1354
+ * Each callback receives the previous callback's return value.
1355
+ *
1356
+ * @template T - The type of value being filtered.
1357
+ * @param hook - The name of the hook.
1358
+ * @param initialValue - The initial value to filter.
1359
+ * @param args - Additional arguments to pass to the callbacks.
1360
+ * @returns The final filtered value.
1361
+ *
1362
+ * @example
1363
+ * ```typescript
1364
+ * const content = await core.hooks.applyFilters('content', 'hello world');
1365
+ * ```
1366
+ */
1367
+ async applyFilters(hook, initialValue, ...args) {
1368
+ const callbacks = this.filters.get(hook) || [];
1369
+ let value2 = initialValue;
1370
+ for (const callback of callbacks) {
1371
+ try {
1372
+ value2 = await callback(value2, ...args);
1373
+ } catch (error) {
1374
+ console.error(`[HookManager] Error in filter '${hook}':`, error);
1375
+ }
1376
+ }
1377
+ return value2;
1378
+ }
1379
+ /**
1380
+ * Register an action hook.
1381
+ *
1382
+ * Actions are for side effects (no return value).
1383
+ *
1384
+ * @template TArgs - The type of arguments passed to the action.
1385
+ * @param hook - The name of the hook.
1386
+ * @param callback - The callback function to execute.
1387
+ *
1388
+ * @example
1389
+ * ```typescript
1390
+ * core.hooks.addAction('user_registered', async (user: User) => {
1391
+ * await sendWelcomeEmail(user);
1392
+ * });
1393
+ * ```
1394
+ */
1395
+ addAction(hook, callback) {
1396
+ if (!this.actions.has(hook)) {
1397
+ this.actions.set(hook, []);
1398
+ }
1399
+ this.actions.get(hook)?.push(callback);
1400
+ }
1401
+ /**
1402
+ * Run all registered actions sequentially.
1403
+ *
1404
+ * @template TArgs - The type of arguments passed to the action.
1405
+ * @param hook - The name of the hook.
1406
+ * @param args - The arguments to pass to the callbacks.
1407
+ *
1408
+ * @example
1409
+ * ```typescript
1410
+ * await core.hooks.doAction('user_registered', user);
1411
+ * ```
1412
+ */
1413
+ async doAction(hook, args) {
1414
+ const callbacks = this.actions.get(hook) || [];
1415
+ for (const callback of callbacks) {
1416
+ try {
1417
+ await callback(args);
1418
+ } catch (error) {
1419
+ console.error(`[HookManager] Error in action '${hook}':`, error);
1420
+ }
1421
+ }
1422
+ }
1423
+ };
1424
+
1425
+ // src/helpers/response.ts
1426
+ function ok(data) {
1427
+ return { success: true, data };
1428
+ }
1429
+ function fail(message, code, details) {
1430
+ const error = { message };
1431
+ if (code !== void 0) {
1432
+ error.code = code;
1433
+ }
1434
+ if (details !== void 0) {
1435
+ error.details = details;
1436
+ }
1437
+ return { success: false, error };
1438
+ }
1439
+ function jsonSuccess(c, data, status = 200) {
1440
+ return c.json(ok(data), status);
1441
+ }
1442
+ function jsonFail(c, message, status = 400, code, details) {
1443
+ return c.json(fail(message, code, details), status);
1444
+ }
1445
+
1446
+ // src/adapters/bun/BunRequest.ts
1447
+ var BunRequest = class {
1448
+ constructor(raw, params = {}) {
1449
+ this.raw = raw;
1450
+ this._url = new URL(raw.url);
1451
+ this._params = params;
1452
+ }
1453
+ _url;
1454
+ _params = {};
1455
+ _query = null;
1456
+ _validated = {};
1457
+ // Internal: Set params after route match
1458
+ setParams(params) {
1459
+ this._params = params;
1460
+ }
1461
+ get url() {
1462
+ return this.raw.url;
1463
+ }
1464
+ get method() {
1465
+ return this.raw.method;
1466
+ }
1467
+ get path() {
1468
+ return this._url.pathname;
1469
+ }
1470
+ // Parameter Access
1471
+ param(name) {
1472
+ return this._params[name];
1473
+ }
1474
+ params() {
1475
+ return this._params;
1476
+ }
1477
+ query(name) {
1478
+ if (!this._query) {
1479
+ this.parseQuery();
1480
+ }
1481
+ const val = this._query?.[name];
1482
+ if (Array.isArray(val)) {
1483
+ return val[0];
1484
+ }
1485
+ return val;
1486
+ }
1487
+ queries() {
1488
+ if (!this._query) {
1489
+ this.parseQuery();
1490
+ }
1491
+ return this._query;
1492
+ }
1493
+ header(name) {
1494
+ if (name) {
1495
+ return this.raw.headers.get(name) || void 0;
1496
+ }
1497
+ const headers = {};
1498
+ this.raw.headers.forEach((value2, key) => {
1499
+ headers[key] = value2;
1500
+ });
1501
+ return headers;
1502
+ }
1503
+ // Body Parsing
1504
+ async json() {
1505
+ return this.raw.json();
1506
+ }
1507
+ async text() {
1508
+ return this.raw.text();
1509
+ }
1510
+ async formData() {
1511
+ return this.raw.formData();
1512
+ }
1513
+ async arrayBuffer() {
1514
+ return this.raw.arrayBuffer();
1515
+ }
1516
+ async parseBody() {
1517
+ const contentType = this.raw.headers.get("Content-Type");
1518
+ if (contentType?.includes("application/x-www-form-urlencoded") || contentType?.includes("multipart/form-data")) {
1519
+ const formData = await this.formData();
1520
+ const body = {};
1521
+ formData.forEach((value2, key) => {
1522
+ body[key] = value2;
1523
+ });
1524
+ return body;
1525
+ }
1526
+ return {};
1527
+ }
1528
+ // Internal method to set validated data
1529
+ // This can be used by middleware to attach validated data to the request
1530
+ setValidated(target, data) {
1531
+ this._validated[target] = data;
1532
+ }
1533
+ valid(target) {
1534
+ const data = this._validated[target];
1535
+ if (data === void 0) {
1536
+ throw new Error(`Validation target '${target}' not found or validation failed.`);
1537
+ }
1538
+ return data;
1539
+ }
1540
+ parseQuery() {
1541
+ this._query = {};
1542
+ for (const [key, value2] of this._url.searchParams) {
1543
+ if (this._query[key]) {
1544
+ if (Array.isArray(this._query[key])) {
1545
+ ;
1546
+ this._query[key].push(value2);
1547
+ } else {
1548
+ this._query[key] = [this._query[key], value2];
1549
+ }
1550
+ } else {
1551
+ this._query[key] = value2;
1552
+ }
1553
+ }
1554
+ }
1555
+ };
1556
+
1557
+ // src/adapters/bun/BunContext.ts
1558
+ var BunContext = class _BunContext {
1559
+ constructor(request, env2 = {}, executionCtx) {
1560
+ this.env = env2;
1561
+ this.req = new BunRequest(request);
1562
+ this._executionCtx = executionCtx;
1563
+ this.native = { request, env: env2, executionCtx };
1564
+ }
1565
+ // Request wrapper
1566
+ req;
1567
+ // Context variables
1568
+ _variables = /* @__PURE__ */ new Map();
1569
+ // Response state
1570
+ _status = 200;
1571
+ _headers = new Headers();
1572
+ _executionCtx;
1573
+ // Stored response (Photon-like behavior)
1574
+ res;
1575
+ native;
1576
+ /**
1577
+ * Create a proxied instance to enable object destructuring of context variables
1578
+ * This allows: async list({ userService }: Context)
1579
+ */
1580
+ static create(request, env2 = {}, executionCtx) {
1581
+ const instance = new _BunContext(request, env2, executionCtx);
1582
+ return new Proxy(instance, {
1583
+ get(target, prop, receiver) {
1584
+ if (prop in target) {
1585
+ const value2 = Reflect.get(target, prop, receiver);
1586
+ if (typeof value2 === "function") {
1587
+ return value2.bind(target);
1588
+ }
1589
+ return value2;
1590
+ }
1591
+ if (typeof prop === "string") {
1592
+ return target.get(prop);
1593
+ }
1594
+ return void 0;
1595
+ }
1596
+ });
1597
+ }
1598
+ // Response Builders
1599
+ json(data, status = 200) {
1600
+ this.status(status);
1601
+ this.header("Content-Type", "application/json");
1602
+ this.res = new Response(JSON.stringify(data), {
1603
+ status: this._status,
1604
+ headers: this._headers
1605
+ });
1606
+ return this.res;
1607
+ }
1608
+ text(text, status = 200) {
1609
+ this.status(status);
1610
+ this.header("Content-Type", "text/plain");
1611
+ this.res = new Response(text, {
1612
+ status: this._status,
1613
+ headers: this._headers
1614
+ });
1615
+ return this.res;
1616
+ }
1617
+ html(html, status = 200) {
1618
+ this.status(status);
1619
+ this.header("Content-Type", "text/html");
1620
+ this.res = new Response(html, {
1621
+ status: this._status,
1622
+ headers: this._headers
1623
+ });
1624
+ return this.res;
1625
+ }
1626
+ redirect(url, status = 302) {
1627
+ this.status(status);
1628
+ this.header("Location", url);
1629
+ this.res = new Response(null, {
1630
+ status: this._status,
1631
+ headers: this._headers
1632
+ });
1633
+ return this.res;
1634
+ }
1635
+ body(data, status = 200) {
1636
+ this.status(status);
1637
+ this.res = new Response(data, {
1638
+ status: this._status,
1639
+ headers: this._headers
1640
+ });
1641
+ return this.res;
1642
+ }
1643
+ stream(stream, status = 200) {
1644
+ this.status(status);
1645
+ this.res = new Response(stream, {
1646
+ status: this._status,
1647
+ headers: this._headers
1648
+ });
1649
+ return this.res;
1650
+ }
1651
+ notFound(message) {
1652
+ return this.text(message ?? "Not Found", 404);
1653
+ }
1654
+ forbidden(message) {
1655
+ return this.text(message ?? "Forbidden", 403);
1656
+ }
1657
+ unauthorized(message) {
1658
+ return this.text(message ?? "Unauthorized", 401);
1659
+ }
1660
+ badRequest(message) {
1661
+ return this.text(message ?? "Bad Request", 400);
1662
+ }
1663
+ header(name, value2, options) {
1664
+ if (value2 === void 0) {
1665
+ return this.req.header(name);
1666
+ }
1667
+ if (options?.append) {
1668
+ this._headers.append(name, value2);
1669
+ this.res?.headers.append(name, value2);
1670
+ } else {
1671
+ this._headers.set(name, value2);
1672
+ if (this.res) {
1673
+ this.res.headers.set(name, value2);
1674
+ }
1675
+ }
1676
+ return void 0;
1677
+ }
1678
+ status(code) {
1679
+ this._status = code;
1680
+ }
1681
+ // Context Variables
1682
+ get(key) {
1683
+ return this._variables.get(key);
1684
+ }
1685
+ set(key, value2) {
1686
+ this._variables.set(key, value2);
1687
+ }
1688
+ // Execution Control
1689
+ get executionCtx() {
1690
+ return this._executionCtx;
1691
+ }
1692
+ };
1693
+
1694
+ // src/adapters/bun/RadixNode.ts
1695
+ var RadixNode = class _RadixNode {
1696
+ // Path segment for this node (e.g., "users", ":id")
1697
+ segment;
1698
+ // Node type (Static, Param, Wildcard)
1699
+ type;
1700
+ // Children nodes (mapped by segment for fast lookup)
1701
+ children = /* @__PURE__ */ new Map();
1702
+ // Specialized child for parameter node (only one per level allowed usually to avoid ambiguity, though some routers support multiple)
1703
+ paramChild = null;
1704
+ // Specialized child for wildcard node
1705
+ wildcardChild = null;
1706
+ // Handlers registered at this node (keyed by HTTP method)
1707
+ handlers = /* @__PURE__ */ new Map();
1708
+ // Parameter name if this is a PARAM node (e.g., "id" for ":id")
1709
+ paramName = null;
1710
+ // Parameter constraints (regex) - only applicable if this is a PARAM node
1711
+ // If we support per-route constraints, they might need to be stored differently,
1712
+ // but for now assume constraints are defined at node level (uncommon) or checked at match time.
1713
+ // Laravel allows global pattern constraints or per-route.
1714
+ // Ideally, constraints should be stored with the handler or part of matching logic.
1715
+ // For a Radix tree, if we have constraints, we might need to backtrack if constraint fails?
1716
+ // Or simply store constraint with the param node.
1717
+ regex = null;
1718
+ constructor(segment = "", type = 0 /* STATIC */) {
1719
+ this.segment = segment;
1720
+ this.type = type;
1721
+ }
1722
+ toJSON() {
1723
+ return {
1724
+ segment: this.segment,
1725
+ type: this.type,
1726
+ children: Array.from(this.children.entries()).map(([k, v]) => [k, v.toJSON()]),
1727
+ paramChild: this.paramChild?.toJSON() || null,
1728
+ wildcardChild: this.wildcardChild?.toJSON() || null,
1729
+ paramName: this.paramName,
1730
+ regex: this.regex ? this.regex.source : null
1731
+ };
1732
+ }
1733
+ static fromJSON(json) {
1734
+ const node = new _RadixNode(json.segment, json.type);
1735
+ node.paramName = json.paramName;
1736
+ if (json.regex) {
1737
+ node.regex = new RegExp(json.regex);
1738
+ }
1739
+ if (json.children) {
1740
+ for (const [key, childJson] of json.children) {
1741
+ node.children.set(key, _RadixNode.fromJSON(childJson));
1742
+ }
1743
+ }
1744
+ if (json.paramChild) {
1745
+ node.paramChild = _RadixNode.fromJSON(json.paramChild);
1746
+ }
1747
+ if (json.wildcardChild) {
1748
+ node.wildcardChild = _RadixNode.fromJSON(json.wildcardChild);
1749
+ }
1750
+ return node;
1751
+ }
1752
+ };
1753
+
1754
+ // src/adapters/bun/RadixRouter.ts
1755
+ var RadixRouter = class _RadixRouter {
1756
+ root = new RadixNode();
1757
+ // Global parameter constraints (e.g., id => /^\d+$/)
1758
+ globalConstraints = /* @__PURE__ */ new Map();
1759
+ /**
1760
+ * Add a generic parameter constraint
1761
+ */
1762
+ where(param, regex) {
1763
+ this.globalConstraints.set(param, regex);
1764
+ }
1765
+ /**
1766
+ * Register a route
1767
+ */
1768
+ add(method, path2, handlers) {
1769
+ let node = this.root;
1770
+ const segments = this.splitPath(path2);
1771
+ for (let i = 0; i < segments.length; i++) {
1772
+ const segment = segments[i];
1773
+ if (segment === "*") {
1774
+ if (!node.wildcardChild) {
1775
+ node.wildcardChild = new RadixNode("*", 2 /* WILDCARD */);
1776
+ }
1777
+ node = node.wildcardChild;
1778
+ break;
1779
+ } else if (segment.startsWith(":")) {
1780
+ const paramName = segment.slice(1);
1781
+ if (!node.paramChild) {
1782
+ const child = new RadixNode(segment, 1 /* PARAM */);
1783
+ child.paramName = paramName;
1784
+ const constraint = this.globalConstraints.get(paramName);
1785
+ if (constraint) {
1786
+ child.regex = constraint;
1787
+ }
1788
+ node.paramChild = child;
1789
+ }
1790
+ node = node.paramChild;
1791
+ } else {
1792
+ if (!node.children.has(segment)) {
1793
+ node.children.set(segment, new RadixNode(segment, 0 /* STATIC */));
1794
+ }
1795
+ node = node.children.get(segment);
1796
+ }
1797
+ }
1798
+ node.handlers.set(method.toLowerCase(), handlers);
1799
+ }
1800
+ /**
1801
+ * Match a request
1802
+ */
1803
+ match(method, path2) {
1804
+ const normalizedMethod = method.toLowerCase();
1805
+ if (path2 === "/" || path2 === "") {
1806
+ const handlers = this.root.handlers.get(normalizedMethod);
1807
+ if (handlers) {
1808
+ return { handlers, params: {} };
1809
+ }
1810
+ return null;
1811
+ }
1812
+ const searchPath = path2.startsWith("/") ? path2.slice(1) : path2;
1813
+ const segments = searchPath.split("/");
1814
+ return this.matchRecursive(this.root, segments, 0, {}, normalizedMethod);
1815
+ }
1816
+ matchRecursive(node, segments, depth, params, method) {
1817
+ if (depth >= segments.length) {
1818
+ let handlers = node.handlers.get(method);
1819
+ if (!handlers) {
1820
+ handlers = node.handlers.get("all");
1821
+ }
1822
+ if (handlers) {
1823
+ return { handlers, params };
1824
+ }
1825
+ return null;
1826
+ }
1827
+ const segment = segments[depth];
1828
+ const staticChild = node.children.get(segment);
1829
+ if (staticChild) {
1830
+ const match = this.matchRecursive(staticChild, segments, depth + 1, params, method);
1831
+ if (match) {
1832
+ return match;
1833
+ }
1834
+ }
1835
+ const paramChild = node.paramChild;
1836
+ if (paramChild) {
1837
+ if (paramChild.regex && !paramChild.regex.test(segment)) {
1838
+ } else {
1839
+ if (paramChild.paramName) {
1840
+ params[paramChild.paramName] = decodeURIComponent(segment);
1841
+ const match = this.matchRecursive(paramChild, segments, depth + 1, params, method);
1842
+ if (match) {
1843
+ return match;
1844
+ }
1845
+ delete params[paramChild.paramName];
1846
+ }
1847
+ }
1848
+ }
1849
+ if (node.wildcardChild) {
1850
+ let handlers = node.wildcardChild.handlers.get(method);
1851
+ if (!handlers) {
1852
+ handlers = node.wildcardChild.handlers.get("all");
1853
+ }
1854
+ if (handlers) {
1855
+ return { handlers, params };
1856
+ }
1857
+ }
1858
+ return null;
1859
+ }
1860
+ splitPath(path2) {
1861
+ if (path2 === "/" || path2 === "") {
1862
+ return [];
1863
+ }
1864
+ let p = path2;
1865
+ if (p.startsWith("/")) {
1866
+ p = p.slice(1);
1867
+ }
1868
+ if (p.endsWith("/")) {
1869
+ p = p.slice(0, -1);
1870
+ }
1871
+ return p.split("/");
1872
+ }
1873
+ /**
1874
+ * Serialize the router to a JSON string
1875
+ */
1876
+ serialize() {
1877
+ return JSON.stringify({
1878
+ root: this.root.toJSON(),
1879
+ globalConstraints: Array.from(this.globalConstraints.entries()).map(([k, v]) => [
1880
+ k,
1881
+ v.source
1882
+ ])
1883
+ });
1884
+ }
1885
+ /**
1886
+ * Restore a router from a serialized JSON string
1887
+ */
1888
+ static fromSerialized(json) {
1889
+ const data = JSON.parse(json);
1890
+ const router2 = new _RadixRouter();
1891
+ router2.root = RadixNode.fromJSON(data.root);
1892
+ if (data.globalConstraints) {
1893
+ for (const [key, source] of data.globalConstraints) {
1894
+ router2.globalConstraints.set(key, new RegExp(source));
1895
+ }
1896
+ }
1897
+ return router2;
1898
+ }
1899
+ };
1900
+
1901
+ // src/adapters/bun/BunNativeAdapter.ts
1902
+ var BunNativeAdapter = class {
1903
+ name = "bun-native";
1904
+ version = "0.0.1";
1905
+ get native() {
1906
+ return this;
1907
+ }
1908
+ router = new RadixRouter();
1909
+ middlewares = [];
1910
+ errorHandler = null;
1911
+ notFoundHandler = null;
1912
+ route(method, path2, ...handlers) {
1913
+ this.router.add(method, path2, handlers);
1914
+ }
1915
+ routes(routes) {
1916
+ for (const route of routes) {
1917
+ this.route(
1918
+ route.method,
1919
+ route.path,
1920
+ ...route.handlers
1921
+ );
1922
+ }
1923
+ }
1924
+ use(path2, ...middleware) {
1925
+ this.middlewares.push({ path: path2, handlers: middleware });
1926
+ }
1927
+ useGlobal(...middleware) {
1928
+ this.use("*", ...middleware);
1929
+ }
1930
+ mount(path2, subAdapter) {
1931
+ const fullPath = path2.endsWith("/") ? `${path2}*` : `${path2}/*`;
1932
+ this.route("all", fullPath, async (ctx) => {
1933
+ const url = new URL(ctx.req.url);
1934
+ const prefix = path2.endsWith("/") ? path2.slice(0, -1) : path2;
1935
+ console.log("[DEBUG] Mount Prefix:", prefix);
1936
+ console.log("[DEBUG] Original Path:", url.pathname);
1937
+ if (url.pathname.startsWith(prefix)) {
1938
+ const newPath = url.pathname.slice(prefix.length);
1939
+ url.pathname = newPath === "" ? "/" : newPath;
1940
+ }
1941
+ const newReq = new Request(url.toString(), {
1942
+ method: ctx.req.method,
1943
+ headers: ctx.req.raw.headers
1944
+ });
1945
+ const res = await subAdapter.fetch(newReq);
1946
+ if ("res" in ctx) {
1947
+ ;
1948
+ ctx.res = res;
1949
+ }
1950
+ return res;
1951
+ });
1952
+ }
1953
+ createContext(request) {
1954
+ return BunContext.create(request);
1955
+ }
1956
+ onError(handler) {
1957
+ this.errorHandler = handler;
1958
+ }
1959
+ onNotFound(handler) {
1960
+ this.notFoundHandler = handler;
1961
+ }
1962
+ async fetch(request, _server) {
1963
+ const ctx = BunContext.create(request);
1964
+ try {
1965
+ const url = new URL(request.url);
1966
+ const path2 = url.pathname;
1967
+ const method = request.method;
1968
+ const match = this.router.match(method, path2);
1969
+ const handlers = [];
1970
+ for (const mw of this.middlewares) {
1971
+ if (mw.path === "*" || path2.startsWith(mw.path)) {
1972
+ handlers.push(...mw.handlers);
1973
+ }
1974
+ }
1975
+ if (match) {
1976
+ if (match.params) {
1977
+ ;
1978
+ ctx.req.setParams(match.params);
1979
+ }
1980
+ handlers.push(...match.handlers);
1981
+ } else {
1982
+ if (this.notFoundHandler) {
1983
+ handlers.push(this.notFoundHandler);
1984
+ } else {
1985
+ }
1986
+ }
1987
+ return await this.executeChain(ctx, handlers);
1988
+ } catch (err) {
1989
+ if (this.errorHandler) {
1990
+ try {
1991
+ const response = await this.errorHandler(err, ctx);
1992
+ if (response) {
1993
+ return response;
1994
+ }
1995
+ } catch (e) {
1996
+ console.error("Error handler failed", e);
1997
+ }
1998
+ }
1999
+ console.error(err);
2000
+ return new Response("Internal Server Error", { status: 500 });
2001
+ }
2002
+ }
2003
+ async executeChain(ctx, handlers) {
2004
+ let index = -1;
2005
+ const dispatch = async (i) => {
2006
+ if (i <= index) {
2007
+ throw new Error("next() called multiple times");
2008
+ }
2009
+ index = i;
2010
+ const fn = handlers[i];
2011
+ if (!fn) {
2012
+ return void 0;
2013
+ }
2014
+ const result = await fn(ctx, async () => {
2015
+ const res = await dispatch(i + 1);
2016
+ if (res && ctx.res !== res) {
2017
+ ;
2018
+ ctx.res = res;
2019
+ }
2020
+ return res;
2021
+ });
2022
+ return result;
2023
+ };
2024
+ const finalResponse = await dispatch(0);
2025
+ if (finalResponse && (finalResponse instanceof Response || typeof finalResponse.status === "number")) {
2026
+ return finalResponse;
2027
+ }
2028
+ if (ctx.res) {
2029
+ return ctx.res;
2030
+ }
2031
+ return new Response("Not Found", { status: 404 });
2032
+ }
2033
+ };
2034
+
2035
+ // src/helpers/data.ts
2036
+ function parsePath(path2) {
2037
+ if (path2 === null || path2 === void 0) {
2038
+ return [];
2039
+ }
2040
+ if (typeof path2 !== "string") {
2041
+ return [...path2];
2042
+ }
2043
+ if (path2 === "") {
2044
+ return [];
2045
+ }
2046
+ return path2.split(".").map((segment) => {
2047
+ const n = Number(segment);
2048
+ if (Number.isInteger(n) && String(n) === segment) {
2049
+ return n;
2050
+ }
2051
+ return segment;
2052
+ });
2053
+ }
2054
+ function getChild(current, key) {
2055
+ if (current === null || current === void 0) {
2056
+ return void 0;
2057
+ }
2058
+ if (current instanceof Map) {
2059
+ return current.get(key);
2060
+ }
2061
+ if (typeof current === "object" || typeof current === "function") {
2062
+ const record = current;
2063
+ return record[key];
2064
+ }
2065
+ return void 0;
2066
+ }
2067
+ function hasChild(current, key) {
2068
+ if (current === null || current === void 0) {
2069
+ return false;
2070
+ }
2071
+ if (current instanceof Map) {
2072
+ return current.has(key);
2073
+ }
2074
+ if (typeof current === "object" || typeof current === "function") {
2075
+ const record = current;
2076
+ return key in record;
2077
+ }
2078
+ return false;
2079
+ }
2080
+ function setChild(current, key, next) {
2081
+ if (current === null || current === void 0) {
2082
+ throw new TypeError("dataSet target cannot be null or undefined.");
2083
+ }
2084
+ if (current instanceof Map) {
2085
+ current.set(key, next);
2086
+ return;
2087
+ }
2088
+ if (typeof current === "object" || typeof current === "function") {
2089
+ const record = current;
2090
+ record[key] = next;
2091
+ return;
2092
+ }
2093
+ throw new TypeError("dataSet target must be object-like.");
2094
+ }
2095
+ function dataGet(target, path2, defaultValue) {
2096
+ const segments = parsePath(path2);
2097
+ if (segments.length === 0) {
2098
+ return target;
2099
+ }
2100
+ let current = target;
2101
+ for (const segment of segments) {
2102
+ current = getChild(current, segment);
2103
+ if (current === void 0) {
2104
+ return defaultValue;
2105
+ }
2106
+ }
2107
+ return current;
2108
+ }
2109
+ function dataHas(target, path2) {
2110
+ const segments = parsePath(path2);
2111
+ if (segments.length === 0) {
2112
+ return true;
2113
+ }
2114
+ let current = target;
2115
+ for (const segment of segments) {
2116
+ if (!hasChild(current, segment)) {
2117
+ return false;
2118
+ }
2119
+ current = getChild(current, segment);
2120
+ }
2121
+ return true;
2122
+ }
2123
+ function dataSet(target, path2, setValue, overwrite = true) {
2124
+ const segments = parsePath(path2);
2125
+ if (segments.length === 0) {
2126
+ return target;
2127
+ }
2128
+ let current = target;
2129
+ for (let i = 0; i < segments.length - 1; i++) {
2130
+ const segment = segments[i];
2131
+ const nextSegment = segments[i + 1];
2132
+ const existing = getChild(current, segment);
2133
+ if (existing !== void 0 && (typeof existing === "object" || typeof existing === "function")) {
2134
+ current = existing;
2135
+ continue;
2136
+ }
2137
+ const created = typeof nextSegment === "number" ? [] : {};
2138
+ setChild(current, segment, created);
2139
+ current = created;
2140
+ }
2141
+ const last = segments[segments.length - 1];
2142
+ const existingLast = getChild(current, last);
2143
+ if (overwrite || existingLast === void 0) {
2144
+ setChild(current, last, setValue);
2145
+ }
2146
+ return target;
2147
+ }
2148
+
2149
+ // src/helpers/Arr.ts
2150
+ var Arr = {
2151
+ get(target, path2, defaultValue) {
2152
+ return dataGet(target, path2, defaultValue);
2153
+ },
2154
+ has(target, path2) {
2155
+ return dataHas(target, path2);
2156
+ },
2157
+ set(target, path2, value2, overwrite = true) {
2158
+ return dataSet(target, path2, value2, overwrite);
2159
+ },
2160
+ wrap(value2) {
2161
+ if (value2 === null || value2 === void 0) {
2162
+ return [];
2163
+ }
2164
+ return Array.isArray(value2) ? value2 : [value2];
2165
+ },
2166
+ first(items, callback) {
2167
+ if (!callback) {
2168
+ return items[0];
2169
+ }
2170
+ for (let i = 0; i < items.length; i++) {
2171
+ const value2 = items[i];
2172
+ if (callback(value2, i)) {
2173
+ return value2;
2174
+ }
2175
+ }
2176
+ return void 0;
2177
+ },
2178
+ last(items, callback) {
2179
+ if (!callback) {
2180
+ return items.length ? items[items.length - 1] : void 0;
2181
+ }
2182
+ for (let i = items.length - 1; i >= 0; i--) {
2183
+ const value2 = items[i];
2184
+ if (callback(value2, i)) {
2185
+ return value2;
2186
+ }
2187
+ }
2188
+ return void 0;
2189
+ },
2190
+ only(target, keys) {
2191
+ const out = {};
2192
+ for (const key of keys) {
2193
+ if (key in target) {
2194
+ out[key] = target[key];
2195
+ }
2196
+ }
2197
+ return out;
2198
+ },
2199
+ except(target, keys) {
2200
+ const out = {};
2201
+ const excluded = new Set(keys);
2202
+ for (const [key, value2] of Object.entries(target)) {
2203
+ if (!excluded.has(key)) {
2204
+ out[key] = value2;
2205
+ }
2206
+ }
2207
+ return out;
2208
+ },
2209
+ flatten(items, depth = Number.POSITIVE_INFINITY) {
2210
+ const out = [];
2211
+ const walk = (value2, currentDepth) => {
2212
+ if (Array.isArray(value2) && currentDepth > 0) {
2213
+ for (const v of value2) {
2214
+ walk(v, currentDepth - 1);
2215
+ }
2216
+ return;
2217
+ }
2218
+ out.push(value2);
2219
+ };
2220
+ for (const item of items) {
2221
+ walk(item, depth);
2222
+ }
2223
+ return out;
2224
+ },
2225
+ pluck(items, valuePath, keyPath) {
2226
+ if (!keyPath) {
2227
+ return items.map((item) => dataGet(item, valuePath));
2228
+ }
2229
+ const out = {};
2230
+ for (const item of items) {
2231
+ const key = dataGet(item, keyPath);
2232
+ out[String(key)] = dataGet(item, valuePath);
2233
+ }
2234
+ return out;
2235
+ },
2236
+ where(items, callback) {
2237
+ const out = [];
2238
+ for (let i = 0; i < items.length; i++) {
2239
+ const value2 = items[i];
2240
+ if (callback(value2, i)) {
2241
+ out.push(value2);
2242
+ }
2243
+ }
2244
+ return out;
2245
+ }
2246
+ };
2247
+
2248
+ // src/helpers/errors.ts
2249
+ function createErrorBag(errors2) {
2250
+ return {
2251
+ has: (field) => (errors2[field]?.length ?? 0) > 0,
2252
+ first: (field) => {
2253
+ if (field) {
2254
+ return errors2[field]?.[0];
2255
+ }
2256
+ for (const key of Object.keys(errors2)) {
2257
+ if (errors2[key]?.[0]) {
2258
+ return errors2[key][0];
2259
+ }
2260
+ }
2261
+ return void 0;
2262
+ },
2263
+ get: (field) => errors2[field] ?? [],
2264
+ all: () => errors2,
2265
+ any: () => Object.keys(errors2).length > 0,
2266
+ count: () => Object.values(errors2).flat().length
2267
+ };
2268
+ }
2269
+ function errors(c) {
2270
+ const session = c.get("session");
2271
+ const flashed = session?.getFlash?.("errors") ?? {};
2272
+ return createErrorBag(flashed);
2273
+ }
2274
+ function old(c, field, defaultValue) {
2275
+ const session = c.get("session");
2276
+ const oldInput = session?.getFlash?.("_old_input") ?? {};
2277
+ return oldInput[field] ?? defaultValue;
2278
+ }
2279
+
2280
+ // src/helpers/Str.ts
2281
+ import { randomBytes, randomUUID } from "crypto";
2282
+ function splitWords(input) {
2283
+ const normalized = input.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
2284
+ return normalized ? normalized.split(/\s+/) : [];
2285
+ }
2286
+ function capitalize(word) {
2287
+ if (!word) {
2288
+ return word;
2289
+ }
2290
+ return word.charAt(0).toUpperCase() + word.slice(1);
2291
+ }
2292
+ var Str = {
2293
+ lower(value2) {
2294
+ return value2.toLowerCase();
2295
+ },
2296
+ upper(value2) {
2297
+ return value2.toUpperCase();
2298
+ },
2299
+ startsWith(haystack, needles) {
2300
+ const list = Array.isArray(needles) ? needles : [needles];
2301
+ for (const needle of list) {
2302
+ if (needle !== "" && haystack.startsWith(needle)) {
2303
+ return true;
2304
+ }
2305
+ }
2306
+ return false;
2307
+ },
2308
+ endsWith(haystack, needles) {
2309
+ const list = Array.isArray(needles) ? needles : [needles];
2310
+ for (const needle of list) {
2311
+ if (needle !== "" && haystack.endsWith(needle)) {
2312
+ return true;
2313
+ }
2314
+ }
2315
+ return false;
2316
+ },
2317
+ contains(haystack, needles) {
2318
+ const list = Array.isArray(needles) ? needles : [needles];
2319
+ for (const needle of list) {
2320
+ if (needle !== "" && haystack.includes(needle)) {
2321
+ return true;
2322
+ }
2323
+ }
2324
+ return false;
2325
+ },
2326
+ snake(value2) {
2327
+ const words = splitWords(value2).map((w) => w.toLowerCase());
2328
+ return words.join("_");
2329
+ },
2330
+ kebab(value2) {
2331
+ const words = splitWords(value2).map((w) => w.toLowerCase());
2332
+ return words.join("-");
2333
+ },
2334
+ studly(value2) {
2335
+ return splitWords(value2).map((w) => capitalize(w.toLowerCase())).join("");
2336
+ },
2337
+ camel(value2) {
2338
+ const words = splitWords(value2).map((w) => w.toLowerCase());
2339
+ if (words.length === 0) {
2340
+ return "";
2341
+ }
2342
+ const first = words[0];
2343
+ if (first === void 0) {
2344
+ return "";
2345
+ }
2346
+ return first + words.slice(1).map(capitalize).join("");
2347
+ },
2348
+ title(value2) {
2349
+ return splitWords(value2).map((w) => capitalize(w.toLowerCase())).join(" ");
2350
+ },
2351
+ limit(value2, limit, end = "...") {
2352
+ if (limit < 0) {
2353
+ return "";
2354
+ }
2355
+ if (value2.length <= limit) {
2356
+ return value2;
2357
+ }
2358
+ return value2.slice(0, limit) + end;
2359
+ },
2360
+ slug(value2, separator = "-") {
2361
+ const normalized = value2.normalize("NFKD").replace(/\p{Diacritic}/gu, "").toLowerCase();
2362
+ const escaped = separator.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2363
+ return normalized.replace(/[^a-z0-9]+/g, separator).replace(new RegExp(`^${escaped}+|${escaped}+$`, "g"), "");
2364
+ },
2365
+ uuid() {
2366
+ if (typeof globalThis.crypto?.randomUUID === "function") {
2367
+ return globalThis.crypto.randomUUID();
2368
+ }
2369
+ return randomUUID();
2370
+ },
2371
+ random(length = 16) {
2372
+ if (length <= 0) {
2373
+ return "";
2374
+ }
2375
+ const bytes = randomBytes(Math.ceil(length * 3 / 4) + 2);
2376
+ const str = bytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
2377
+ return str.slice(0, length);
2378
+ }
2379
+ };
2380
+
2381
+ // src/helpers.ts
2382
+ var DumpDieError = class extends Error {
2383
+ constructor(values) {
2384
+ super("Execution halted by dd()");
2385
+ this.values = values;
2386
+ }
2387
+ name = "DumpDieError";
2388
+ };
2389
+ var defaultDumpOptions = {
2390
+ depth: null,
2391
+ colors: true
2392
+ };
2393
+ function dump(...values) {
2394
+ for (const value2 of values) {
2395
+ console.dir(value2, {
2396
+ depth: defaultDumpOptions.depth,
2397
+ colors: defaultDumpOptions.colors
2398
+ });
2399
+ }
2400
+ }
2401
+ function dd(...values) {
2402
+ dump(...values);
2403
+ throw new DumpDieError(values);
2404
+ }
2405
+ function tap(value2, callback) {
2406
+ callback(value2);
2407
+ return value2;
2408
+ }
2409
+ function value(valueOrFactory, ...args) {
2410
+ if (typeof valueOrFactory === "function") {
2411
+ return valueOrFactory(...args);
2412
+ }
2413
+ return valueOrFactory;
2414
+ }
2415
+ function isPlainObject(value2) {
2416
+ if (value2 === null || typeof value2 !== "object") {
2417
+ return false;
2418
+ }
2419
+ const proto = Object.getPrototypeOf(value2);
2420
+ return proto === Object.prototype || proto === null;
2421
+ }
2422
+ function blank(value2) {
2423
+ if (value2 === null || value2 === void 0) {
2424
+ return true;
2425
+ }
2426
+ if (typeof value2 === "string") {
2427
+ return value2.trim().length === 0;
2428
+ }
2429
+ if (Array.isArray(value2)) {
2430
+ return value2.length === 0;
2431
+ }
2432
+ if (value2 instanceof Map) {
2433
+ return value2.size === 0;
2434
+ }
2435
+ if (value2 instanceof Set) {
2436
+ return value2.size === 0;
2437
+ }
2438
+ if (isPlainObject(value2)) {
2439
+ return Object.keys(value2).length === 0;
2440
+ }
2441
+ return false;
2442
+ }
2443
+ function filled(value2) {
2444
+ return !blank(value2);
2445
+ }
2446
+ function toError(error) {
2447
+ if (typeof error === "string") {
2448
+ return new Error(error);
2449
+ }
2450
+ if (typeof error === "function") {
2451
+ return error();
2452
+ }
2453
+ return error;
2454
+ }
2455
+ function throwIf(condition, error = "Error.") {
2456
+ if (condition) {
2457
+ throw toError(error);
2458
+ }
2459
+ }
2460
+ function throwUnless(condition, error = "Error.") {
2461
+ if (!condition) {
2462
+ throw toError(error);
2463
+ }
2464
+ }
2465
+ function env(key, defaultValue) {
2466
+ const bunEnv = globalThis.Bun?.env;
2467
+ const value2 = bunEnv?.[key] ?? process.env[key];
2468
+ return value2 ?? defaultValue;
2469
+ }
2470
+ var currentApp;
2471
+ function setApp(core) {
2472
+ currentApp = core ?? void 0;
2473
+ }
2474
+ function hasApp() {
2475
+ return currentApp !== void 0;
2476
+ }
2477
+ function app() {
2478
+ if (!currentApp) {
2479
+ throw new Error("No app is bound. Call setApp(core) once during bootstrap.");
2480
+ }
2481
+ return currentApp;
2482
+ }
2483
+ function config(key, defaultValue) {
2484
+ if (defaultValue === void 0) {
2485
+ return app().config.get(key);
2486
+ }
2487
+ return app().config.get(key, defaultValue);
2488
+ }
2489
+ function logger() {
2490
+ return app().logger;
2491
+ }
2492
+ function router() {
2493
+ return app().router;
2494
+ }
2495
+ function abort(status, message) {
2496
+ if (message === void 0) {
2497
+ throw new HttpException(status);
2498
+ }
2499
+ throw new HttpException(status, { message });
2500
+ }
2501
+ function abortIf(condition, status, message) {
2502
+ if (condition) {
2503
+ abort(status, message);
2504
+ }
2505
+ }
2506
+ function abortUnless(condition, status, message) {
2507
+ if (!condition) {
2508
+ abort(status, message);
2509
+ }
2510
+ }
2511
+
2512
+ // src/http/CookieJar.ts
2513
+ var CookieJar = class {
2514
+ constructor(encrypter) {
2515
+ this.encrypter = encrypter;
2516
+ }
2517
+ queued = /* @__PURE__ */ new Map();
2518
+ /**
2519
+ * Queue a cookie to be sent with the response
2520
+ */
2521
+ queue(name, value2, minutes = 60, options = {}) {
2522
+ const resolved = {
2523
+ path: options.path ?? "/",
2524
+ httpOnly: options.httpOnly ?? true,
2525
+ sameSite: options.sameSite ?? "Lax",
2526
+ secure: options.secure ?? process.env.NODE_ENV === "production",
2527
+ ...options
2528
+ };
2529
+ if (minutes && !resolved.maxAge) {
2530
+ resolved.maxAge = minutes * 60;
2531
+ }
2532
+ let finalValue = value2;
2533
+ if (resolved.encrypt) {
2534
+ if (!this.encrypter) {
2535
+ throw new Error("Encryption is not available. Ensure APP_KEY is set.");
2536
+ }
2537
+ finalValue = this.encrypter.encrypt(value2);
2538
+ }
2539
+ this.queued.set(name, { value: finalValue, options: resolved });
2540
+ }
2541
+ /**
2542
+ * Make a cookie that lasts "forever" (5 years)
2543
+ */
2544
+ forever(name, value2, options = {}) {
2545
+ this.queue(name, value2, 2628e3, options);
2546
+ }
2547
+ /**
2548
+ * Expire a cookie
2549
+ */
2550
+ forget(name, options = {}) {
2551
+ this.queue(name, "", 0, { ...options, maxAge: 0, expires: /* @__PURE__ */ new Date(0) });
2552
+ }
2553
+ /**
2554
+ * Serialize a cookie to a Set-Cookie header value
2555
+ */
2556
+ serializeCookie(name, value2, opts) {
2557
+ const parts = [`${name}=${encodeURIComponent(value2)}`];
2558
+ if (opts.maxAge !== void 0) {
2559
+ parts.push(`Max-Age=${opts.maxAge}`);
2560
+ }
2561
+ if (opts.expires) {
2562
+ parts.push(`Expires=${opts.expires.toUTCString()}`);
2563
+ }
2564
+ if (opts.path) {
2565
+ parts.push(`Path=${opts.path}`);
2566
+ }
2567
+ if (opts.domain) {
2568
+ parts.push(`Domain=${opts.domain}`);
2569
+ }
2570
+ if (opts.secure) {
2571
+ parts.push("Secure");
2572
+ }
2573
+ if (opts.httpOnly) {
2574
+ parts.push("HttpOnly");
2575
+ }
2576
+ if (opts.sameSite) {
2577
+ parts.push(`SameSite=${opts.sameSite}`);
2578
+ }
2579
+ return parts.join("; ");
2580
+ }
2581
+ /**
2582
+ * Attach queued cookies to the context
2583
+ */
2584
+ attach(c) {
2585
+ for (const [name, { value: value2, options }] of this.queued) {
2586
+ c.header("Set-Cookie", this.serializeCookie(name, value2, options), { append: true });
2587
+ }
2588
+ }
2589
+ };
2590
+
2591
+ // src/exceptions/ModelNotFoundException.ts
2592
+ var ModelNotFoundException = class extends GravitoException {
2593
+ model;
2594
+ id;
2595
+ constructor(model, id) {
2596
+ super(404, "NOT_FOUND", {
2597
+ message: `${model} not found.`,
2598
+ i18nKey: "errors.model.not_found",
2599
+ i18nParams: { model, id: String(id ?? "") }
2600
+ });
2601
+ this.model = model;
2602
+ if (id !== void 0) {
2603
+ this.id = id;
2604
+ }
2605
+ }
2606
+ };
2607
+
2608
+ // src/Route.ts
2609
+ var Route = class {
2610
+ constructor(router2, method, path2, options) {
2611
+ this.router = router2;
2612
+ this.method = method;
2613
+ this.path = path2;
2614
+ this.options = options;
2615
+ }
2616
+ /**
2617
+ * Name the route
2618
+ */
2619
+ name(name) {
2620
+ this.router.registerName(name, this.method, this.path, this.options);
2621
+ return this;
2622
+ }
2623
+ static get(path2, requestOrHandler, handler) {
2624
+ return router().get(path2, requestOrHandler, handler);
2625
+ }
2626
+ static post(path2, requestOrHandler, handler) {
2627
+ return router().post(path2, requestOrHandler, handler);
2628
+ }
2629
+ static put(path2, requestOrHandler, handler) {
2630
+ return router().put(path2, requestOrHandler, handler);
2631
+ }
2632
+ static delete(path2, requestOrHandler, handler) {
2633
+ return router().delete(path2, requestOrHandler, handler);
2634
+ }
2635
+ static patch(path2, requestOrHandler, handler) {
2636
+ return router().patch(path2, requestOrHandler, handler);
2637
+ }
2638
+ static resource(name, controller, options = {}) {
2639
+ router().resource(name, controller, options);
2640
+ }
2641
+ static prefix(path2) {
2642
+ return router().prefix(path2);
2643
+ }
2644
+ static middleware(...handlers) {
2645
+ return router().middleware(...handlers);
2646
+ }
2647
+ };
2648
+
2649
+ // src/Router.ts
2650
+ function isFormRequestClass(value2) {
2651
+ if (typeof value2 !== "function") {
2652
+ return false;
2653
+ }
2654
+ try {
2655
+ const instance = new value2();
2656
+ return instance !== null && typeof instance === "object" && "schema" in instance && "validate" in instance && typeof instance.validate === "function";
2657
+ } catch {
2658
+ return false;
2659
+ }
2660
+ }
2661
+ function formRequestToMiddleware(RequestClass) {
2662
+ return async (ctx, next) => {
2663
+ const request = new RequestClass();
2664
+ if (typeof request.validate !== "function") {
2665
+ throw new Error("Invalid FormRequest: validate() is missing.");
2666
+ }
2667
+ const result = await request.validate(ctx);
2668
+ if (!result.success) {
2669
+ const errorCode = result.error?.error?.code;
2670
+ const status = errorCode === "AUTHORIZATION_ERROR" ? 403 : 422;
2671
+ return ctx.json(result.error, status);
2672
+ }
2673
+ ctx.set("validated", result.data);
2674
+ await next();
2675
+ return void 0;
2676
+ };
2677
+ }
2678
+ var RouteGroup = class _RouteGroup {
2679
+ constructor(router2, options) {
2680
+ this.router = router2;
2681
+ this.options = options;
2682
+ }
2683
+ /**
2684
+ * Add a prefix to the current group
2685
+ */
2686
+ prefix(path2) {
2687
+ return new _RouteGroup(this.router, {
2688
+ ...this.options,
2689
+ prefix: (this.options.prefix || "") + path2
2690
+ });
2691
+ }
2692
+ /**
2693
+ * Add middleware to the current group.
2694
+ * Accepts individual handlers or arrays of handlers.
2695
+ */
2696
+ middleware(...handlers) {
2697
+ const flattened = handlers.flat();
2698
+ return new _RouteGroup(this.router, {
2699
+ ...this.options,
2700
+ middleware: [...this.options.middleware || [], ...flattened]
2701
+ });
2702
+ }
2703
+ /**
2704
+ * Define routes within this group
2705
+ */
2706
+ group(callback) {
2707
+ callback(this);
2708
+ }
2709
+ get(path2, requestOrHandler, handler) {
2710
+ return this.router.req("get", path2, requestOrHandler, handler, this.options);
2711
+ }
2712
+ post(path2, requestOrHandler, handler) {
2713
+ return this.router.req("post", path2, requestOrHandler, handler, this.options);
2714
+ }
2715
+ put(path2, requestOrHandler, handler) {
2716
+ return this.router.req("put", path2, requestOrHandler, handler, this.options);
2717
+ }
2718
+ delete(path2, requestOrHandler, handler) {
2719
+ return this.router.req("delete", path2, requestOrHandler, handler, this.options);
2720
+ }
2721
+ patch(path2, requestOrHandler, handler) {
2722
+ return this.router.req("patch", path2, requestOrHandler, handler, this.options);
2723
+ }
2724
+ resource(name, controller, options = {}) {
2725
+ const actions = [
2726
+ "index",
2727
+ "create",
2728
+ "store",
2729
+ "show",
2730
+ "edit",
2731
+ "update",
2732
+ "destroy"
2733
+ ];
2734
+ const map = {
2735
+ index: { method: "get", path: `/${name}` },
2736
+ create: { method: "get", path: `/${name}/create` },
2737
+ store: { method: "post", path: `/${name}` },
2738
+ show: { method: "get", path: `/${name}/:id` },
2739
+ edit: { method: "get", path: `/${name}/:id/edit` },
2740
+ update: { method: "put", path: `/${name}/:id` },
2741
+ destroy: { method: "delete", path: `/${name}/:id` }
2742
+ };
2743
+ const allowed = actions.filter((action) => {
2744
+ if (options.only) {
2745
+ return options.only.includes(action);
2746
+ }
2747
+ if (options.except) {
2748
+ return !options.except.includes(action);
2749
+ }
2750
+ return true;
2751
+ });
2752
+ for (const action of allowed) {
2753
+ const { method, path: path2 } = map[action];
2754
+ if (action === "update") {
2755
+ this.router.req("put", path2, [controller, action], void 0, this.options).name(`${name}.${action}`);
2756
+ this.router.req("patch", path2, [controller, action], void 0, this.options);
2757
+ } else {
2758
+ this.router.req(method, path2, [controller, action], void 0, this.options).name(`${name}.${action}`);
2759
+ }
2760
+ }
2761
+ }
2762
+ };
2763
+ var Router = class {
2764
+ constructor(core) {
2765
+ this.core = core;
2766
+ this.core.adapter.useGlobal(async (c, next) => {
2767
+ const routeModels = c.get("routeModels") ?? {};
2768
+ for (const [param, resolver] of this.bindings) {
2769
+ const value2 = c.req.param(param);
2770
+ if (value2) {
2771
+ try {
2772
+ const resolved = await resolver(value2);
2773
+ routeModels[param] = resolved;
2774
+ } catch (err) {
2775
+ const message = err instanceof Error ? err.message : void 0;
2776
+ if (message === "ModelNotFound") {
2777
+ throw new ModelNotFoundException(param, value2);
2778
+ }
2779
+ throw err;
2780
+ }
2781
+ }
2782
+ }
2783
+ c.set("routeModels", routeModels);
2784
+ await next();
2785
+ return void 0;
2786
+ });
2787
+ }
2788
+ // Internal list of all registered routes (for scanning and debugging)
2789
+ routes = [];
2790
+ // Singleton cache for controllers
2791
+ controllers = /* @__PURE__ */ new Map();
2792
+ namedRoutes = /* @__PURE__ */ new Map();
2793
+ bindings = /* @__PURE__ */ new Map();
2794
+ /**
2795
+ * Compile all registered routes into a flat array for caching or manifest generation.
2796
+ */
2797
+ compile() {
2798
+ const routes = [];
2799
+ for (const [name, info] of this.namedRoutes) {
2800
+ routes.push({
2801
+ name,
2802
+ method: info.method,
2803
+ path: info.path,
2804
+ domain: info.domain
2805
+ });
2806
+ }
2807
+ return routes;
2808
+ }
2809
+ /**
2810
+ * Register a named route
2811
+ */
2812
+ registerName(name, method, path2, options = {}) {
2813
+ const fullPath = (options.prefix || "") + path2;
2814
+ this.namedRoutes.set(name, {
2815
+ method: method.toUpperCase(),
2816
+ path: fullPath,
2817
+ domain: options.domain
2818
+ });
2819
+ }
2820
+ /**
2821
+ * Generate a URL from a named route.
2822
+ */
2823
+ url(name, params = {}, query = {}) {
2824
+ const route = this.namedRoutes.get(name);
2825
+ if (!route) {
2826
+ throw new Error(`Named route '${name}' not found`);
2827
+ }
2828
+ let path2 = route.path;
2829
+ path2 = path2.replace(/:([A-Za-z0-9_]+)/g, (_, key) => {
2830
+ const value2 = params[key];
2831
+ if (value2 === void 0 || value2 === null) {
2832
+ throw new Error(`Missing route param '${key}' for route '${name}'`);
2833
+ }
2834
+ return encodeURIComponent(String(value2));
2835
+ });
2836
+ const qs = new URLSearchParams();
2837
+ for (const [k, v] of Object.entries(query)) {
2838
+ if (v === void 0 || v === null) {
2839
+ continue;
2840
+ }
2841
+ qs.set(k, String(v));
2842
+ }
2843
+ const suffix = qs.toString();
2844
+ return suffix ? `${path2}?${suffix}` : path2;
2845
+ }
2846
+ /**
2847
+ * Export named routes as a serializable manifest (for caching).
2848
+ */
2849
+ exportNamedRoutes() {
2850
+ return Object.fromEntries(this.namedRoutes.entries());
2851
+ }
2852
+ /**
2853
+ * Load named routes from a manifest (for caching).
2854
+ */
2855
+ loadNamedRoutes(manifest) {
2856
+ this.namedRoutes = new Map(Object.entries(manifest));
2857
+ }
2858
+ /**
2859
+ * Register a route model binding.
2860
+ */
2861
+ bind(param, resolver) {
2862
+ this.bindings.set(param, resolver);
2863
+ }
2864
+ /**
2865
+ * Register a route model binding for a Model class.
2866
+ */
2867
+ model(param, modelClass) {
2868
+ this.bind(param, async (id) => {
2869
+ if (modelClass && typeof modelClass === "object" && "find" in modelClass && typeof modelClass.find === "function") {
2870
+ const instance = await modelClass.find(id);
2871
+ if (!instance) {
2872
+ throw new Error("ModelNotFound");
2873
+ }
2874
+ return instance;
2875
+ }
2876
+ throw new Error(`Invalid model class for binding '${param}'`);
2877
+ });
2878
+ }
2879
+ /**
2880
+ * Start a route group with a prefix
2881
+ */
2882
+ prefix(path2) {
2883
+ return new RouteGroup(this, { prefix: path2 });
2884
+ }
2885
+ /**
2886
+ * Start a route group with a domain constraint
2887
+ */
2888
+ domain(host) {
2889
+ return new RouteGroup(this, { domain: host });
2890
+ }
2891
+ /**
2892
+ * Start a route group with middleware.
2893
+ * Accepts individual handlers or arrays of handlers.
2894
+ */
2895
+ middleware(...handlers) {
2896
+ return new RouteGroup(this, { middleware: handlers.flat() });
2897
+ }
2898
+ get(path2, requestOrHandler, handler) {
2899
+ return this.req("get", path2, requestOrHandler, handler);
2900
+ }
2901
+ post(path2, requestOrHandler, handler) {
2902
+ return this.req("post", path2, requestOrHandler, handler);
2903
+ }
2904
+ put(path2, requestOrHandler, handler) {
2905
+ return this.req("put", path2, requestOrHandler, handler);
2906
+ }
2907
+ delete(path2, requestOrHandler, handler) {
2908
+ return this.req("delete", path2, requestOrHandler, handler);
2909
+ }
2910
+ patch(path2, requestOrHandler, handler) {
2911
+ return this.req("patch", path2, requestOrHandler, handler);
2912
+ }
2913
+ /**
2914
+ * Register a resource route (Laravel-style).
2915
+ */
2916
+ resource(name, controller, options = {}) {
2917
+ const actions = [
2918
+ "index",
2919
+ "create",
2920
+ "store",
2921
+ "show",
2922
+ "edit",
2923
+ "update",
2924
+ "destroy"
2925
+ ];
2926
+ const map = {
2927
+ index: { method: "get", path: `/${name}` },
2928
+ create: { method: "get", path: `/${name}/create` },
2929
+ store: { method: "post", path: `/${name}` },
2930
+ show: { method: "get", path: `/${name}/:id` },
2931
+ edit: { method: "get", path: `/${name}/:id/edit` },
2932
+ update: { method: "put", path: `/${name}/:id` },
2933
+ destroy: { method: "delete", path: `/${name}/:id` }
2934
+ };
2935
+ const allowed = actions.filter((action) => {
2936
+ if (options.only) {
2937
+ return options.only.includes(action);
2938
+ }
2939
+ if (options.except) {
2940
+ return !options.except.includes(action);
2941
+ }
2942
+ return true;
2943
+ });
2944
+ for (const action of allowed) {
2945
+ const { method, path: path2 } = map[action];
2946
+ if (action === "update") {
2947
+ this.req("put", path2, [controller, action]).name(`${name}.${action}`);
2948
+ this.req("patch", path2, [controller, action]);
2949
+ } else {
2950
+ this.req(method, path2, [controller, action]).name(`${name}.${action}`);
2951
+ }
2952
+ }
2953
+ }
2954
+ /**
2955
+ * Internal Request Registration
2956
+ */
2957
+ req(method, path2, requestOrHandler, handler, options = {}) {
2958
+ const fullPath = (options.prefix || "") + path2;
2959
+ let formRequestMiddleware = null;
2960
+ let finalRouteHandler;
2961
+ if (handler !== void 0) {
2962
+ if (isFormRequestClass(requestOrHandler)) {
2963
+ formRequestMiddleware = formRequestToMiddleware(requestOrHandler);
2964
+ }
2965
+ finalRouteHandler = handler;
2966
+ } else {
2967
+ finalRouteHandler = requestOrHandler;
2968
+ }
2969
+ let resolvedHandler;
2970
+ if (Array.isArray(finalRouteHandler)) {
2971
+ const [CtrlClass, methodName] = finalRouteHandler;
2972
+ resolvedHandler = this.resolveControllerHandler(CtrlClass, methodName);
2973
+ } else {
2974
+ resolvedHandler = finalRouteHandler;
2975
+ }
2976
+ const handlers = [];
2977
+ if (options.middleware) {
2978
+ handlers.push(...options.middleware);
2979
+ }
2980
+ if (formRequestMiddleware) {
2981
+ handlers.push(formRequestMiddleware);
2982
+ }
2983
+ handlers.push(resolvedHandler);
2984
+ if (options.domain) {
2985
+ const _wrappedHandler = async (c) => {
2986
+ if (c.req.header("host") !== options.domain) {
2987
+ return new Response("Not Found", { status: 404 });
2988
+ }
2989
+ return void 0;
2990
+ };
2991
+ const domainCheck = async (c, next) => {
2992
+ if (c.req.header("host") !== options.domain) {
2993
+ return c.text("Not Found", 404);
2994
+ }
2995
+ await next();
2996
+ };
2997
+ handlers.unshift(domainCheck);
2998
+ }
2999
+ this.routes.push({ method, path: fullPath });
3000
+ this.core.adapter.route(method, fullPath, ...handlers);
3001
+ return new Route(this, method, fullPath, options);
3002
+ }
3003
+ /**
3004
+ * Resolve Controller Instance and Method
3005
+ */
3006
+ resolveControllerHandler(CtrlClass, methodName) {
3007
+ let instance = this.controllers.get(CtrlClass);
3008
+ if (!instance) {
3009
+ instance = new CtrlClass(this.core);
3010
+ this.controllers.set(CtrlClass, instance);
3011
+ }
3012
+ const handler = instance[methodName];
3013
+ if (typeof handler !== "function") {
3014
+ throw new Error(`Method '${methodName}' not found in controller '${CtrlClass.name}'`);
3015
+ }
3016
+ return handler.bind(instance);
3017
+ }
3018
+ };
3019
+
3020
+ // src/security/Encrypter.ts
3021
+ import crypto from "crypto";
3022
+ var Encrypter = class {
3023
+ algorithm;
3024
+ key;
3025
+ constructor(options) {
3026
+ this.algorithm = options.cipher || "aes-256-cbc";
3027
+ if (options.key.startsWith("base64:")) {
3028
+ this.key = Buffer.from(options.key.substring(7), "base64");
3029
+ } else {
3030
+ this.key = Buffer.from(options.key);
3031
+ }
3032
+ if (this.algorithm === "aes-128-cbc" && this.key.length !== 16) {
3033
+ throw new Error("The key must be 16 bytes (128 bits) for AES-128-CBC.");
3034
+ }
3035
+ if (this.algorithm === "aes-256-cbc" && this.key.length !== 32) {
3036
+ throw new Error("The key must be 32 bytes (256 bits) for AES-256-CBC.");
3037
+ }
3038
+ }
3039
+ /**
3040
+ * Encrypt a value
3041
+ */
3042
+ encrypt(value2, serialize = true) {
3043
+ const iv = crypto.randomBytes(16);
3044
+ const cipher = crypto.createCipheriv(this.algorithm, this.key, iv);
3045
+ const stringValue = serialize ? JSON.stringify(value2) : String(value2);
3046
+ let encrypted = cipher.update(stringValue, "utf8", "base64");
3047
+ encrypted += cipher.final("base64");
3048
+ const mac = this.hash(iv.toString("base64"), encrypted);
3049
+ const payload = {
3050
+ iv: iv.toString("base64"),
3051
+ value: encrypted,
3052
+ mac,
3053
+ tag: ""
3054
+ // AES-CBC doesn't produce an auth tag, but GCM does. Keeping structure standard.
3055
+ };
3056
+ return Buffer.from(JSON.stringify(payload)).toString("base64");
3057
+ }
3058
+ /**
3059
+ * Decrypt a value
3060
+ */
3061
+ decrypt(payload, deserialize = true) {
3062
+ const json = JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
3063
+ if (!this.validPayload(json)) {
3064
+ throw new Error("The payload is invalid.");
3065
+ }
3066
+ if (!this.validMac(json)) {
3067
+ throw new Error("The MAC is invalid.");
3068
+ }
3069
+ const iv = Buffer.from(json.iv, "base64");
3070
+ const decipher = crypto.createDecipheriv(this.algorithm, this.key, iv);
3071
+ let decrypted = decipher.update(json.value, "base64", "utf8");
3072
+ decrypted += decipher.final("utf8");
3073
+ return deserialize ? JSON.parse(decrypted) : decrypted;
3074
+ }
3075
+ hash(iv, value2) {
3076
+ const hmac = crypto.createHmac("sha256", this.key);
3077
+ hmac.update(iv + value2);
3078
+ return hmac.digest("hex");
3079
+ }
3080
+ validPayload(payload) {
3081
+ return typeof payload === "object" && payload !== null && "iv" in payload && "value" in payload && "mac" in payload;
3082
+ }
3083
+ validMac(payload) {
3084
+ const calculated = this.hash(payload.iv, payload.value);
3085
+ return crypto.timingSafeEqual(Buffer.from(calculated), Buffer.from(payload.mac));
3086
+ }
3087
+ /**
3088
+ * Generate a new key
3089
+ */
3090
+ static generateKey(cipher = "aes-256-cbc") {
3091
+ const bytes = cipher === "aes-128-cbc" ? 16 : 32;
3092
+ return `base64:${crypto.randomBytes(bytes).toString("base64")}`;
3093
+ }
3094
+ };
3095
+
3096
+ // src/security/Hasher.ts
3097
+ var BunHasher = class {
3098
+ async make(value2, options) {
3099
+ const bun = Bun;
3100
+ return await bun.password.hash(value2, options);
3101
+ }
3102
+ async check(value2, hashedValue) {
3103
+ const bun = Bun;
3104
+ return await bun.password.verify(value2, hashedValue);
3105
+ }
3106
+ needsRehash(_hashedValue, _options) {
3107
+ return false;
3108
+ }
3109
+ };
3110
+
3111
+ // src/PlanetCore.ts
3112
+ var PlanetCore = class _PlanetCore {
3113
+ /**
3114
+ * The HTTP adapter used by this core instance.
3115
+ * @since 2.0.0
3116
+ */
3117
+ _adapter;
3118
+ /**
3119
+ * Access the underlying Photon app instance.
3120
+ * @deprecated Use adapter methods for new code. This property is kept for backward compatibility.
3121
+ */
3122
+ get app() {
3123
+ return this._adapter.native;
3124
+ }
3125
+ /**
3126
+ * Get the HTTP adapter instance.
3127
+ * @since 2.0.0
3128
+ */
3129
+ get adapter() {
3130
+ return this._adapter;
3131
+ }
3132
+ logger;
3133
+ config;
3134
+ hooks;
3135
+ events;
3136
+ router;
3137
+ container = new Container();
3138
+ /** @deprecated Use core.container instead */
3139
+ services = /* @__PURE__ */ new Map();
3140
+ encrypter;
3141
+ hasher;
3142
+ providers = [];
3143
+ deferredProviders = /* @__PURE__ */ new Map();
3144
+ bootedProviders = /* @__PURE__ */ new Set();
3145
+ /**
3146
+ * Register a service provider.
3147
+ *
3148
+ * @param provider - The ServiceProvider instance to register.
3149
+ * @returns The PlanetCore instance for chaining.
3150
+ *
3151
+ * @example
3152
+ * ```typescript
3153
+ * core.register(new DatabaseServiceProvider());
3154
+ * ```
3155
+ */
3156
+ register(provider) {
3157
+ provider.setCore(this);
3158
+ if (provider.deferred) {
3159
+ const services = provider.provides();
3160
+ for (const service of services) {
3161
+ this.deferredProviders.set(service, provider);
3162
+ }
3163
+ } else {
3164
+ this.providers.push(provider);
3165
+ }
3166
+ return this;
3167
+ }
3168
+ /**
3169
+ * Bootstrap the application by registering and booting providers.
3170
+ *
3171
+ * This method must be called before the application starts handling requests.
3172
+ * It calls `register()` on all providers first, then `boot()` on all providers.
3173
+ *
3174
+ * Supports async register() methods.
3175
+ *
3176
+ * @returns Promise that resolves when bootstrapping is complete.
3177
+ */
3178
+ async bootstrap() {
3179
+ for (const provider of this.providers) {
3180
+ await provider.register(this.container);
3181
+ }
3182
+ this.setupDeferredProviderResolution();
3183
+ for (const provider of this.providers) {
3184
+ await this.bootProvider(provider);
3185
+ }
3186
+ }
3187
+ /**
3188
+ * Setup deferred provider resolution.
3189
+ * Wraps container.make to auto-register deferred providers on first request.
3190
+ *
3191
+ * @internal
3192
+ */
3193
+ setupDeferredProviderResolution() {
3194
+ const originalMake = this.container.make.bind(this.container);
3195
+ this.container.make = (key) => {
3196
+ if (this.deferredProviders.has(key)) {
3197
+ const provider = this.deferredProviders.get(key);
3198
+ this.registerDeferredProvider(provider);
3199
+ }
3200
+ return originalMake(key);
3201
+ };
3202
+ }
3203
+ /**
3204
+ * Register a deferred provider on-demand.
3205
+ *
3206
+ * @internal
3207
+ */
3208
+ registerDeferredProvider(provider) {
3209
+ for (const service of provider.provides()) {
3210
+ this.deferredProviders.delete(service);
3211
+ }
3212
+ const result = provider.register(this.container);
3213
+ if (result instanceof Promise) {
3214
+ throw new Error(
3215
+ `Deferred provider ${provider.constructor.name} has async register(). Deferred providers must have synchronous register() methods.`
3216
+ );
3217
+ }
3218
+ this.bootProvider(provider);
3219
+ }
3220
+ /**
3221
+ * Boot a single provider if not already booted.
3222
+ *
3223
+ * @internal
3224
+ */
3225
+ async bootProvider(provider) {
3226
+ if (this.bootedProviders.has(provider)) {
3227
+ return;
3228
+ }
3229
+ this.bootedProviders.add(provider);
3230
+ if (provider.boot) {
3231
+ await provider.boot(this);
3232
+ }
3233
+ }
3234
+ constructor(options = {}) {
3235
+ this.logger = options.logger ?? new ConsoleLogger();
3236
+ this.config = new ConfigManager(options.config ?? {});
3237
+ this.hooks = new HookManager();
3238
+ this.events = new EventManager(this);
3239
+ this.hasher = new BunHasher();
3240
+ setApp(this);
3241
+ const appKey = (this.config.has("APP_KEY") ? this.config.get("APP_KEY") : void 0) || process.env.APP_KEY;
3242
+ if (appKey) {
3243
+ try {
3244
+ this.encrypter = new Encrypter({ key: appKey });
3245
+ } catch (e) {
3246
+ this.logger.warn("Failed to initialize Encrypter (invalid APP_KEY?):", e);
3247
+ }
3248
+ }
3249
+ if (options.adapter) {
3250
+ this._adapter = options.adapter;
3251
+ } else if (typeof Bun !== "undefined") {
3252
+ this._adapter = new BunNativeAdapter();
3253
+ } else {
3254
+ this._adapter = new PhotonAdapter();
3255
+ }
3256
+ this.adapter.use("*", async (c, next) => {
3257
+ c.set("core", this);
3258
+ c.set("logger", this.logger);
3259
+ c.set("config", this.config);
3260
+ const cookieJar = new CookieJar(this.encrypter);
3261
+ c.set("cookieJar", cookieJar);
3262
+ c.route = (name, params, query) => this.router.url(name, params, query);
3263
+ await next();
3264
+ return void 0;
3265
+ });
3266
+ this.router = new Router(this);
3267
+ this.adapter.onError(async (err, c) => {
3268
+ const isProduction = process.env.NODE_ENV === "production";
3269
+ const codeFromStatus = (status2) => {
3270
+ switch (status2) {
3271
+ case 400:
3272
+ return "BAD_REQUEST";
3273
+ case 401:
3274
+ return "UNAUTHENTICATED";
3275
+ case 403:
3276
+ return "FORBIDDEN";
3277
+ case 404:
3278
+ return "NOT_FOUND";
3279
+ case 405:
3280
+ return "METHOD_NOT_ALLOWED";
3281
+ case 409:
3282
+ return "CONFLICT";
3283
+ case 422:
3284
+ return "VALIDATION_ERROR";
3285
+ case 429:
3286
+ return "TOO_MANY_REQUESTS";
3287
+ default:
3288
+ return status2 >= 500 ? "INTERNAL_ERROR" : "HTTP_ERROR";
3289
+ }
3290
+ };
3291
+ const messageFromStatus = (status2) => {
3292
+ switch (status2) {
3293
+ case 400:
3294
+ return "Bad Request";
3295
+ case 401:
3296
+ return "Unauthorized";
3297
+ case 403:
3298
+ return "Forbidden";
3299
+ case 404:
3300
+ return "Not Found";
3301
+ case 405:
3302
+ return "Method Not Allowed";
3303
+ case 409:
3304
+ return "Conflict";
3305
+ case 422:
3306
+ return "Unprocessable Content";
3307
+ case 429:
3308
+ return "Too Many Requests";
3309
+ case 500:
3310
+ return "Internal Server Error";
3311
+ case 502:
3312
+ return "Bad Gateway";
3313
+ case 503:
3314
+ return "Service Unavailable";
3315
+ case 504:
3316
+ return "Gateway Timeout";
3317
+ default:
3318
+ return status2 >= 500 ? "Internal Server Error" : "Request Error";
3319
+ }
3320
+ };
3321
+ const view = c.get("view");
3322
+ const i18n = c.get("i18n");
3323
+ const accept = c.req.header("Accept") || "";
3324
+ const wantsHtml = Boolean(
3325
+ view && accept.includes("text/html") && !accept.includes("application/json")
3326
+ );
3327
+ let status = 500;
3328
+ let message = messageFromStatus(500);
3329
+ let code = "INTERNAL_ERROR";
3330
+ let details;
3331
+ if (err instanceof GravitoException) {
3332
+ status = err.status;
3333
+ code = err.code;
3334
+ if (code === "HTTP_ERROR") {
3335
+ code = codeFromStatus(status);
3336
+ }
3337
+ if (i18n?.t && err.i18nKey) {
3338
+ message = i18n.t(err.i18nKey, err.i18nParams);
3339
+ } else {
3340
+ message = err.message || messageFromStatus(status);
3341
+ }
3342
+ if (err instanceof ValidationException) {
3343
+ details = err.errors;
3344
+ if (wantsHtml) {
3345
+ const session = c.get("session");
3346
+ if (session) {
3347
+ const errorBag = {};
3348
+ for (const e of err.errors) {
3349
+ if (!errorBag[e.field]) {
3350
+ errorBag[e.field] = [];
3351
+ }
3352
+ errorBag[e.field]?.push(e.message);
3353
+ }
3354
+ session.flash("errors", errorBag);
3355
+ if (err.input) {
3356
+ session.flash("_old_input", err.input);
3357
+ }
3358
+ const redirectUrl = err.redirectTo ?? c.req.header("Referer") ?? "/";
3359
+ return c.redirect(redirectUrl);
3360
+ }
3361
+ }
3362
+ } else if (err instanceof Error && !isProduction && err.cause) {
3363
+ details = { cause: err.cause };
3364
+ }
3365
+ } else if (err instanceof HttpException) {
3366
+ status = err.status;
3367
+ message = err.message;
3368
+ } else if (err instanceof Error && "status" in err && typeof err.status === "number") {
3369
+ status = err.status;
3370
+ message = err.message;
3371
+ code = codeFromStatus(status);
3372
+ } else if (err instanceof Error) {
3373
+ if (!isProduction) {
3374
+ message = err.message || message;
3375
+ }
3376
+ } else if (typeof err === "string") {
3377
+ if (!isProduction) {
3378
+ message = err;
3379
+ }
3380
+ }
3381
+ if (isProduction && status >= 500) {
3382
+ message = messageFromStatus(status);
3383
+ }
3384
+ if (!isProduction && err instanceof Error && !details) {
3385
+ details = { stack: err.stack, ...details };
3386
+ }
3387
+ let handlerContext = {
3388
+ core: this,
3389
+ c,
3390
+ error: err,
3391
+ isProduction,
3392
+ accept,
3393
+ wantsHtml,
3394
+ status,
3395
+ payload: fail(message, code, details),
3396
+ ...wantsHtml ? {
3397
+ html: {
3398
+ templates: status === 500 ? ["errors/500"] : [`errors/${status}`, "errors/500"],
3399
+ data: {
3400
+ status,
3401
+ message,
3402
+ code,
3403
+ error: !isProduction && err instanceof Error ? err.stack : void 0,
3404
+ debug: !isProduction,
3405
+ details
3406
+ }
3407
+ }
3408
+ } : {}
3409
+ };
3410
+ handlerContext = await this.hooks.applyFilters(
3411
+ "error:context",
3412
+ handlerContext
3413
+ );
3414
+ const defaultLogLevel = handlerContext.status >= 500 ? "error" : "none";
3415
+ const logLevel = handlerContext.logLevel ?? defaultLogLevel;
3416
+ if (logLevel !== "none") {
3417
+ const rawErrorMessage = handlerContext.error instanceof Error ? handlerContext.error.message : typeof handlerContext.error === "string" ? handlerContext.error : handlerContext.payload.error.message;
3418
+ const msg = handlerContext.logMessage ?? (logLevel === "error" ? `Application Error: ${rawErrorMessage || handlerContext.payload.error.message}` : `HTTP ${handlerContext.status}: ${handlerContext.payload.error.message}`);
3419
+ if (logLevel === "error") {
3420
+ this.logger.error(msg, err);
3421
+ } else if (logLevel === "warn") {
3422
+ this.logger.warn(msg);
3423
+ } else {
3424
+ this.logger.info(msg);
3425
+ }
3426
+ }
3427
+ await this.hooks.doAction("error:report", handlerContext);
3428
+ const customResponse = await this.hooks.applyFilters(
3429
+ "error:render",
3430
+ null,
3431
+ handlerContext
3432
+ );
3433
+ if (customResponse) {
3434
+ return customResponse;
3435
+ }
3436
+ if (handlerContext.wantsHtml && view && handlerContext.html) {
3437
+ let lastRenderError;
3438
+ for (const template of handlerContext.html.templates) {
3439
+ try {
3440
+ return c.html(view.render(template, handlerContext.html.data), handlerContext.status);
3441
+ } catch (renderError) {
3442
+ lastRenderError = renderError;
3443
+ }
3444
+ }
3445
+ this.logger.error("Failed to render error view", lastRenderError);
3446
+ }
3447
+ return c.json(handlerContext.payload, handlerContext.status);
3448
+ });
3449
+ this.adapter.onNotFound(async (c) => {
3450
+ const view = c.get("view");
3451
+ const accept = c.req.header("Accept") || "";
3452
+ const wantsHtml = view && accept.includes("text/html") && !accept.includes("application/json");
3453
+ const isProduction = process.env.NODE_ENV === "production";
3454
+ let handlerContext = {
3455
+ core: this,
3456
+ c,
3457
+ error: new HttpException(404, { message: "Route not found" }),
3458
+ isProduction,
3459
+ accept,
3460
+ wantsHtml: Boolean(wantsHtml),
3461
+ status: 404,
3462
+ payload: fail("Route not found", "NOT_FOUND"),
3463
+ ...wantsHtml ? {
3464
+ html: {
3465
+ templates: ["errors/404", "errors/500"],
3466
+ data: {
3467
+ status: 404,
3468
+ message: "Route not found",
3469
+ code: "NOT_FOUND",
3470
+ debug: !isProduction
3471
+ }
3472
+ }
3473
+ } : {}
3474
+ };
3475
+ handlerContext = await this.hooks.applyFilters(
3476
+ "notFound:context",
3477
+ handlerContext
3478
+ );
3479
+ const logLevel = handlerContext.logLevel ?? "info";
3480
+ if (logLevel !== "none") {
3481
+ const msg = handlerContext.logMessage ?? `404 Not Found: ${c.req.url}`;
3482
+ if (logLevel === "error") {
3483
+ this.logger.error(msg);
3484
+ } else if (logLevel === "warn") {
3485
+ this.logger.warn(msg);
3486
+ } else {
3487
+ this.logger.info(msg);
3488
+ }
3489
+ }
3490
+ await this.hooks.doAction("notFound:report", handlerContext);
3491
+ const customResponse = await this.hooks.applyFilters(
3492
+ "notFound:render",
3493
+ null,
3494
+ handlerContext
3495
+ );
3496
+ if (customResponse) {
3497
+ return customResponse;
3498
+ }
3499
+ if (handlerContext.wantsHtml && view && handlerContext.html) {
3500
+ let lastRenderError;
3501
+ for (const template of handlerContext.html.templates) {
3502
+ try {
3503
+ return c.html(view.render(template, handlerContext.html.data), handlerContext.status);
3504
+ } catch (renderError) {
3505
+ lastRenderError = renderError;
3506
+ }
3507
+ }
3508
+ this.logger.error("Failed to render 404 view", lastRenderError);
3509
+ }
3510
+ return c.json(handlerContext.payload, handlerContext.status);
3511
+ });
3512
+ }
3513
+ /**
3514
+ * Programmatically register an infrastructure module (Orbit).
3515
+ * @since 2.0.0
3516
+ *
3517
+ * @param orbit - The orbit class or instance to register.
3518
+ * @returns The PlanetCore instance for chaining.
3519
+ *
3520
+ * @example
3521
+ * ```typescript
3522
+ * await core.orbit(OrbitCache);
3523
+ * ```
3524
+ */
3525
+ async orbit(orbit) {
3526
+ const instance = typeof orbit === "function" ? new orbit() : orbit;
3527
+ await instance.install(this);
3528
+ return this;
3529
+ }
3530
+ /**
3531
+ * Programmatically register a feature module (Satellite).
3532
+ * Alias for register() with provider support.
3533
+ * @since 2.0.0
3534
+ *
3535
+ * @param satellite - The provider or setup function.
3536
+ * @returns The PlanetCore instance for chaining.
3537
+ *
3538
+ * @example
3539
+ * ```typescript
3540
+ * await core.use(new AuthProvider());
3541
+ * ```
3542
+ */
3543
+ async use(satellite) {
3544
+ if (typeof satellite === "function") {
3545
+ await satellite(this);
3546
+ } else {
3547
+ this.register(satellite);
3548
+ }
3549
+ return this;
3550
+ }
3551
+ registerGlobalErrorHandlers(options = {}) {
3552
+ return registerGlobalErrorHandlers({ ...options, core: this });
3553
+ }
3554
+ /**
3555
+ * Boot the application with a configuration object (IoC style default entry)
3556
+ *
3557
+ * @param config - The Gravito configuration object.
3558
+ * @returns A Promise resolving to the booted PlanetCore instance.
3559
+ *
3560
+ * @example
3561
+ * ```typescript
3562
+ * const core = await PlanetCore.boot(config);
3563
+ * ```
3564
+ */
3565
+ static async boot(config2) {
3566
+ const core = new _PlanetCore({
3567
+ ...config2.logger && { logger: config2.logger },
3568
+ ...config2.config && { config: config2.config },
3569
+ ...config2.adapter && { adapter: config2.adapter }
3570
+ });
3571
+ if (config2.orbits) {
3572
+ for (const OrbitClassOrInstance of config2.orbits) {
3573
+ let orbit;
3574
+ if (typeof OrbitClassOrInstance === "function") {
3575
+ orbit = new OrbitClassOrInstance();
3576
+ } else {
3577
+ orbit = OrbitClassOrInstance;
3578
+ }
3579
+ await orbit.install(core);
3580
+ }
3581
+ }
3582
+ return core;
3583
+ }
3584
+ /**
3585
+ * Mount an Orbit (a Photon app) to a path.
3586
+ *
3587
+ * @param path - The URL path to mount the orbit at.
3588
+ * @param orbitApp - The Photon application instance.
3589
+ */
3590
+ mountOrbit(path2, orbitApp) {
3591
+ this.logger.info(`Mounting orbit at path: ${path2}`);
3592
+ if (this.adapter.name === "photon") {
3593
+ ;
3594
+ this.adapter.native.route(path2, orbitApp);
3595
+ } else {
3596
+ const subAdapter = new PhotonAdapter({}, orbitApp);
3597
+ this.adapter.mount(path2, subAdapter);
3598
+ }
3599
+ }
3600
+ /**
3601
+ * Start the core (Liftoff).
3602
+ *
3603
+ * Returns a config object for `Bun.serve`.
3604
+ *
3605
+ * @param port - Optional port number (defaults to config or 3000).
3606
+ * @returns An object compatible with Bun.serve({ ... }).
3607
+ *
3608
+ * @example
3609
+ * ```typescript
3610
+ * export default core.liftoff(3000);
3611
+ * ```
3612
+ */
3613
+ liftoff(port) {
3614
+ const finalPort = port ?? this.config.get("PORT", 3e3);
3615
+ this.hooks.doAction("app:liftoff", { port: finalPort });
3616
+ this.logger.info(`Ready to liftoff on port ${finalPort} \u{1F680}`);
3617
+ return {
3618
+ port: finalPort,
3619
+ fetch: this.adapter.fetch.bind(this.adapter),
3620
+ // Ensure we bind to adapter not app
3621
+ core: this
3622
+ };
3623
+ }
3624
+ };
3625
+
3626
+ // src/Application.ts
3627
+ var Application = class {
3628
+ /**
3629
+ * The underlying PlanetCore instance.
3630
+ */
3631
+ core;
3632
+ /**
3633
+ * The IoC container.
3634
+ */
3635
+ container;
3636
+ /**
3637
+ * The configuration manager.
3638
+ */
3639
+ config;
3640
+ /**
3641
+ * The event manager.
3642
+ */
3643
+ events;
3644
+ /**
3645
+ * The logger instance.
3646
+ */
3647
+ logger;
3648
+ /**
3649
+ * Application base path.
3650
+ */
3651
+ basePath;
3652
+ /**
3653
+ * Environment mode.
3654
+ */
3655
+ env;
3656
+ /**
3657
+ * Configuration options.
3658
+ */
3659
+ options;
3660
+ /**
3661
+ * Whether the application has been booted.
3662
+ */
3663
+ booted = false;
3664
+ constructor(options) {
3665
+ this.options = options;
3666
+ this.basePath = options.basePath;
3667
+ this.env = options.env ?? process.env.NODE_ENV ?? "development";
3668
+ this.logger = options.logger ?? new ConsoleLogger();
3669
+ this.container = new Container();
3670
+ this.config = new ConfigManager(options.config ?? {});
3671
+ this.core = new PlanetCore({
3672
+ logger: this.logger,
3673
+ config: options.config
3674
+ });
3675
+ this.events = this.core.events;
3676
+ this.container.instance("app", this);
3677
+ this.container.instance("config", this.config);
3678
+ this.container.instance("logger", this.logger);
3679
+ this.container.instance("events", this.events);
3680
+ }
3681
+ /**
3682
+ * Boot the application.
3683
+ *
3684
+ * This will:
3685
+ * 1. Load configuration files
3686
+ * 2. Auto-discover providers (if enabled)
3687
+ * 3. Register all providers
3688
+ * 4. Bootstrap the core
3689
+ *
3690
+ * @returns Promise that resolves when boot is complete
3691
+ */
3692
+ async boot() {
3693
+ if (this.booted) {
3694
+ return this;
3695
+ }
3696
+ this.logger.info(`\u{1F680} Booting application in ${this.env} mode...`);
3697
+ await this.loadConfiguration();
3698
+ if (this.options.autoDiscoverProviders !== false) {
3699
+ await this.discoverProviders();
3700
+ }
3701
+ if (this.options.providers) {
3702
+ for (const provider of this.options.providers) {
3703
+ this.core.register(provider);
3704
+ }
3705
+ }
3706
+ await this.core.bootstrap();
3707
+ this.booted = true;
3708
+ this.logger.info("\u2705 Application booted successfully");
3709
+ return this;
3710
+ }
3711
+ /**
3712
+ * Load configuration files from the config directory.
3713
+ *
3714
+ * @internal
3715
+ */
3716
+ async loadConfiguration() {
3717
+ const configPath = path.resolve(this.basePath, this.options.configPath ?? "config");
3718
+ try {
3719
+ const stat = await fs.stat(configPath);
3720
+ if (!stat.isDirectory()) {
3721
+ return;
3722
+ }
3723
+ const files = await fs.readdir(configPath);
3724
+ for (const file of files) {
3725
+ if (!file.endsWith(".ts") && !file.endsWith(".js")) {
3726
+ continue;
3727
+ }
3728
+ const key = path.basename(file, path.extname(file));
3729
+ const filePath = path.resolve(configPath, file);
3730
+ try {
3731
+ const module = await import(pathToFileURL(filePath).href);
3732
+ const value2 = module.default ?? module;
3733
+ this.config.set(key, value2);
3734
+ this.logger.info(`\u{1F4CB} Loaded config: ${key}`);
3735
+ } catch (err) {
3736
+ this.logger.warn(`Failed to load config ${file}:`, err);
3737
+ }
3738
+ }
3739
+ } catch {
3740
+ this.logger.info("No config directory found, skipping config loading");
3741
+ }
3742
+ }
3743
+ /**
3744
+ * Discover and register providers from the providers directory.
3745
+ *
3746
+ * @internal
3747
+ */
3748
+ async discoverProviders() {
3749
+ const providersPath = path.resolve(this.basePath, this.options.providersPath ?? "src/Providers");
3750
+ try {
3751
+ const stat = await fs.stat(providersPath);
3752
+ if (!stat.isDirectory()) {
3753
+ return;
3754
+ }
3755
+ const files = await fs.readdir(providersPath);
3756
+ for (const file of files) {
3757
+ if (!file.endsWith("Provider.ts") && !file.endsWith("Provider.js")) {
3758
+ continue;
3759
+ }
3760
+ const filePath = path.resolve(providersPath, file);
3761
+ try {
3762
+ const module = await import(pathToFileURL(filePath).href);
3763
+ const ProviderClass = module.default ?? Object.values(module).find(
3764
+ (exp) => typeof exp === "function" && exp.prototype?.register
3765
+ );
3766
+ if (ProviderClass && typeof ProviderClass === "function") {
3767
+ const provider = new ProviderClass();
3768
+ this.core.register(provider);
3769
+ this.logger.info(`\u{1F50C} Registered provider: ${ProviderClass.name}`);
3770
+ }
3771
+ } catch (err) {
3772
+ this.logger.warn(`Failed to load provider ${file}:`, err);
3773
+ }
3774
+ }
3775
+ } catch {
3776
+ }
3777
+ }
3778
+ /**
3779
+ * Get a service from the container.
3780
+ *
3781
+ * @param key - The service key
3782
+ * @returns The resolved service
3783
+ */
3784
+ make(key) {
3785
+ return this.core.container.make(key);
3786
+ }
3787
+ /**
3788
+ * Check if a service is bound.
3789
+ *
3790
+ * @param key - The service key
3791
+ * @returns True if bound
3792
+ */
3793
+ has(key) {
3794
+ return this.core.container.has(key);
3795
+ }
3796
+ /**
3797
+ * Get a configuration value.
3798
+ *
3799
+ * @param key - The config key (supports dot notation)
3800
+ * @param defaultValue - Default value if not found
3801
+ * @returns The config value
3802
+ */
3803
+ getConfig(key, defaultValue) {
3804
+ return this.config.get(key, defaultValue);
3805
+ }
3806
+ /**
3807
+ * Create application path helper.
3808
+ *
3809
+ * @param segments - Path segments relative to base path
3810
+ * @returns Absolute path
3811
+ */
3812
+ path(...segments) {
3813
+ return path.resolve(this.basePath, ...segments);
3814
+ }
3815
+ /**
3816
+ * Get the config path.
3817
+ *
3818
+ * @param segments - Additional path segments
3819
+ * @returns Absolute path to config directory
3820
+ */
3821
+ configPath(...segments) {
3822
+ return this.path(this.options.configPath ?? "config", ...segments);
3823
+ }
3824
+ /**
3825
+ * Check if running in production.
3826
+ */
3827
+ isProduction() {
3828
+ return this.env === "production";
3829
+ }
3830
+ /**
3831
+ * Check if running in development.
3832
+ */
3833
+ isDevelopment() {
3834
+ return this.env === "development";
3835
+ }
3836
+ /**
3837
+ * Check if running in testing.
3838
+ */
3839
+ isTesting() {
3840
+ return this.env === "testing";
3841
+ }
3842
+ };
3843
+
3844
+ // src/exceptions/AuthenticationException.ts
3845
+ var AuthenticationException = class extends GravitoException {
3846
+ constructor(message = "Unauthenticated.") {
3847
+ super(401, "UNAUTHENTICATED", {
3848
+ message,
3849
+ i18nKey: "errors.authentication.unauthenticated"
3850
+ });
3851
+ }
3852
+ };
3853
+
3854
+ // src/exceptions/AuthorizationException.ts
3855
+ var AuthorizationException = class extends GravitoException {
3856
+ constructor(message = "This action is unauthorized.") {
3857
+ super(403, "FORBIDDEN", {
3858
+ message,
3859
+ i18nKey: "errors.authorization.forbidden"
3860
+ });
3861
+ }
3862
+ };
3863
+
3864
+ // src/ServiceProvider.ts
3865
+ var ServiceProvider = class _ServiceProvider {
3866
+ /**
3867
+ * Reference to the application core instance.
3868
+ * Set during provider registration.
3869
+ */
3870
+ core;
3871
+ /**
3872
+ * Whether this provider should be deferred.
3873
+ * Deferred providers are only registered when one of their
3874
+ * provided services is actually requested from the container.
3875
+ */
3876
+ deferred = false;
3877
+ /**
3878
+ * Get the services provided by this provider.
3879
+ * Used for deferred loading - provider is only loaded when
3880
+ * one of these services is requested.
3881
+ *
3882
+ * @returns Array of service keys this provider offers
3883
+ *
3884
+ * @example
3885
+ * ```typescript
3886
+ * provides(): string[] {
3887
+ * return ['db', 'db.connection'];
3888
+ * }
3889
+ * ```
3890
+ */
3891
+ provides() {
3892
+ return [];
3893
+ }
3894
+ /**
3895
+ * Set the core instance reference.
3896
+ * Called internally by the application during registration.
3897
+ *
3898
+ * @internal
3899
+ */
3900
+ setCore(core) {
3901
+ this.core = core;
3902
+ }
3903
+ // ─────────────────────────────────────────────────────────────
3904
+ // Configuration Helpers
3905
+ // ─────────────────────────────────────────────────────────────
3906
+ /**
3907
+ * Merge configuration from a file into the application config.
3908
+ *
3909
+ * @param config - The ConfigManager instance
3910
+ * @param key - The configuration key to set
3911
+ * @param value - The configuration value or object
3912
+ *
3913
+ * @example
3914
+ * ```typescript
3915
+ * this.mergeConfig(config, 'database', {
3916
+ * default: 'mysql',
3917
+ * connections: { ... }
3918
+ * });
3919
+ * ```
3920
+ */
3921
+ mergeConfig(config2, key, value2) {
3922
+ const existing = config2.has(key) ? config2.get(key) : {};
3923
+ if (typeof existing === "object" && existing !== null && typeof value2 === "object" && value2 !== null) {
3924
+ config2.set(key, { ...existing, ...value2 });
3925
+ } else {
3926
+ config2.set(key, value2);
3927
+ }
3928
+ }
3929
+ /**
3930
+ * Merge configuration from an async loader.
3931
+ * Useful for loading config from .ts files dynamically.
3932
+ *
3933
+ * @param config - The ConfigManager instance
3934
+ * @param key - The configuration key
3935
+ * @param loader - Async function that returns config value
3936
+ *
3937
+ * @example
3938
+ * ```typescript
3939
+ * await this.mergeConfigFrom(config, 'database', async () => {
3940
+ * return (await import('./config/database')).default;
3941
+ * });
3942
+ * ```
3943
+ */
3944
+ async mergeConfigFrom(config2, key, loader) {
3945
+ const value2 = await loader();
3946
+ this.mergeConfig(config2, key, value2);
3947
+ }
3948
+ // ─────────────────────────────────────────────────────────────
3949
+ // Publishing (for CLI support)
3950
+ // ─────────────────────────────────────────────────────────────
3951
+ /**
3952
+ * Paths that should be published by the CLI.
3953
+ * Maps source paths to destination paths.
3954
+ */
3955
+ static publishables = /* @__PURE__ */ new Map();
3956
+ /**
3957
+ * Register paths to be published.
3958
+ * Used by CLI commands like `gravito vendor:publish`.
3959
+ *
3960
+ * @param paths - Map of source to destination paths
3961
+ * @param group - Optional group name for selective publishing
3962
+ *
3963
+ * @example
3964
+ * ```typescript
3965
+ * this.publishes({
3966
+ * './config/cache.ts': 'config/cache.ts',
3967
+ * './views/errors': 'resources/views/errors'
3968
+ * }, 'config');
3969
+ * ```
3970
+ */
3971
+ publishes(paths, group) {
3972
+ const groupKey = group ?? this.constructor.name;
3973
+ if (!_ServiceProvider.publishables.has(groupKey)) {
3974
+ _ServiceProvider.publishables.set(groupKey, /* @__PURE__ */ new Map());
3975
+ }
3976
+ const groupPaths = _ServiceProvider.publishables.get(groupKey);
3977
+ for (const [source, dest] of Object.entries(paths)) {
3978
+ groupPaths.set(source, dest);
3979
+ }
3980
+ }
3981
+ /**
3982
+ * Get all publishable paths for a group.
3983
+ *
3984
+ * @param group - The group name (defaults to provider class name)
3985
+ * @returns Map of source to destination paths
3986
+ */
3987
+ static getPublishables(group) {
3988
+ if (group) {
3989
+ return _ServiceProvider.publishables.get(group) ?? /* @__PURE__ */ new Map();
3990
+ }
3991
+ const all = /* @__PURE__ */ new Map();
3992
+ for (const paths of _ServiceProvider.publishables.values()) {
3993
+ for (const [source, dest] of paths) {
3994
+ all.set(source, dest);
3995
+ }
3996
+ }
3997
+ return all;
3998
+ }
3999
+ /**
4000
+ * Get all publish groups.
4001
+ *
4002
+ * @returns Array of group names
4003
+ */
4004
+ static getPublishGroups() {
4005
+ return Array.from(_ServiceProvider.publishables.keys());
4006
+ }
4007
+ };
4008
+
4009
+ // src/GravitoServer.ts
4010
+ var GravitoServer = class {
4011
+ /**
4012
+ * 一鍵建立並組裝伺服器
4013
+ * @param manifest 站點描述清單
4014
+ * @param resolvers 模組解析器字典
4015
+ * @param baseOrbits 基礎軌道模組 (例如 OrbitMonolith)
4016
+ */
4017
+ static async create(manifest, resolvers, baseOrbits = []) {
4018
+ const core = new PlanetCore(
4019
+ manifest.config || {
4020
+ adapter: new PhotonAdapter()
4021
+ }
4022
+ );
4023
+ for (const Orbit of baseOrbits) {
4024
+ core.orbit(Orbit);
4025
+ }
4026
+ console.log(`
4027
+ \u{1F30C} [Gravito Core] \u6B63\u5728\u9EDE\u71C3: ${manifest.name} v${manifest.version || "1.0.0"}`);
4028
+ for (const moduleId of manifest.modules) {
4029
+ const resolver = resolvers[moduleId];
4030
+ if (!resolver) continue;
4031
+ try {
4032
+ const exported = await resolver();
4033
+ let instance;
4034
+ if (typeof exported === "function" && exported.prototype instanceof ServiceProvider) {
4035
+ instance = new exported();
4036
+ } else if (exported instanceof ServiceProvider) {
4037
+ instance = exported;
4038
+ } else {
4039
+ continue;
4040
+ }
4041
+ core.register(instance);
4042
+ console.log(` \u2705 \u6A21\u7D44\u9EDE\u706B\u6210\u529F: [${moduleId}]`);
4043
+ } catch (error) {
4044
+ console.error(` \u274C \u6A21\u7D44 [${moduleId}] \u9EDE\u706B\u5931\u6557: ${error.message}`);
4045
+ }
4046
+ }
4047
+ return core;
4048
+ }
4049
+ };
4050
+
4051
+ // src/http/middleware/BodySizeLimit.ts
4052
+ var defaultMethods = ["POST", "PUT", "PATCH", "DELETE"];
4053
+ function bodySizeLimit(maxBytes, options = {}) {
4054
+ const allowedMethods = (options.methods ?? defaultMethods).map((m) => m.toUpperCase());
4055
+ return async (c, next) => {
4056
+ const method = c.req.method.toUpperCase();
4057
+ if (!allowedMethods.includes(method)) {
4058
+ await next();
4059
+ return void 0;
4060
+ }
4061
+ const lengthHeader = c.req.header("Content-Length");
4062
+ if (!lengthHeader) {
4063
+ if (options.requireContentLength) {
4064
+ return c.text("Length Required", 411);
4065
+ }
4066
+ await next();
4067
+ return void 0;
4068
+ }
4069
+ const length = Number(lengthHeader);
4070
+ if (Number.isNaN(length)) {
4071
+ if (options.requireContentLength) {
4072
+ return c.text("Invalid Content-Length", 400);
4073
+ }
4074
+ await next();
4075
+ return void 0;
4076
+ }
4077
+ if (length > maxBytes) {
4078
+ return c.text("Payload Too Large", 413);
4079
+ }
4080
+ await next();
4081
+ return void 0;
4082
+ };
4083
+ }
4084
+
4085
+ // src/http/middleware/Cors.ts
4086
+ function resolveOrigin(origin, requestOrigin) {
4087
+ if (!origin) {
4088
+ return "*";
4089
+ }
4090
+ if (typeof origin === "string") {
4091
+ return origin;
4092
+ }
4093
+ if (Array.isArray(origin)) {
4094
+ return requestOrigin && origin.includes(requestOrigin) ? requestOrigin : false;
4095
+ }
4096
+ return origin(requestOrigin);
4097
+ }
4098
+ function cors(options = {}) {
4099
+ const methods = (options.methods ?? ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]).join(", ");
4100
+ const allowedHeaders = options.allowedHeaders?.join(", ");
4101
+ const exposedHeaders = options.exposedHeaders?.join(", ");
4102
+ const credentials = options.credentials === true;
4103
+ const maxAge = options.maxAge;
4104
+ const optionsStatus = options.optionsSuccessStatus ?? 204;
4105
+ return async (c, next) => {
4106
+ const requestOrigin = c.req.header("Origin");
4107
+ const allowOrigin = resolveOrigin(options.origin, requestOrigin);
4108
+ if (allowOrigin) {
4109
+ c.header("Access-Control-Allow-Origin", allowOrigin);
4110
+ if (allowOrigin !== "*") {
4111
+ c.header("Vary", "Origin");
4112
+ }
4113
+ if (credentials) {
4114
+ c.header("Access-Control-Allow-Credentials", "true");
4115
+ }
4116
+ if (exposedHeaders) {
4117
+ c.header("Access-Control-Expose-Headers", exposedHeaders);
4118
+ }
4119
+ }
4120
+ if (c.req.method.toUpperCase() === "OPTIONS") {
4121
+ const requestMethod = c.req.header("Access-Control-Request-Method");
4122
+ if (requestMethod) {
4123
+ c.header("Access-Control-Allow-Methods", methods);
4124
+ if (allowedHeaders) {
4125
+ c.header("Access-Control-Allow-Headers", allowedHeaders);
4126
+ } else {
4127
+ const reqHeaders = c.req.header("Access-Control-Request-Headers");
4128
+ if (reqHeaders) {
4129
+ c.header("Access-Control-Allow-Headers", reqHeaders);
4130
+ }
4131
+ }
4132
+ if (maxAge !== void 0) {
4133
+ c.header("Access-Control-Max-Age", String(maxAge));
4134
+ }
4135
+ return c.text("", optionsStatus);
4136
+ }
4137
+ }
4138
+ await next();
4139
+ return void 0;
4140
+ };
4141
+ }
4142
+
4143
+ // src/http/middleware/Csrf.ts
4144
+ import crypto2 from "crypto";
4145
+ var defaultSafeMethods = ["GET", "HEAD", "OPTIONS"];
4146
+ function parseCookies(header) {
4147
+ const out = {};
4148
+ if (!header) {
4149
+ return out;
4150
+ }
4151
+ for (const part of header.split(";")) {
4152
+ const [rawKey, ...rest] = part.trim().split("=");
4153
+ if (!rawKey) {
4154
+ continue;
4155
+ }
4156
+ const key = rawKey.trim();
4157
+ const value2 = rest.join("=");
4158
+ out[key] = decodeURIComponent(value2);
4159
+ }
4160
+ return out;
4161
+ }
4162
+ function timingSafeEqual(a, b) {
4163
+ const bufA = Buffer.from(a);
4164
+ const bufB = Buffer.from(b);
4165
+ if (bufA.length !== bufB.length) {
4166
+ return false;
4167
+ }
4168
+ return crypto2.timingSafeEqual(bufA, bufB);
4169
+ }
4170
+ function buildCookieOptions(custom) {
4171
+ return {
4172
+ path: "/",
4173
+ sameSite: "Lax",
4174
+ httpOnly: false,
4175
+ secure: process.env.NODE_ENV === "production",
4176
+ ...custom
4177
+ };
4178
+ }
4179
+ function canSetHeader(c) {
4180
+ return typeof c.header === "function";
4181
+ }
4182
+ function setCookieHeader(c, name, value2, options) {
4183
+ if (!canSetHeader(c)) {
4184
+ return;
4185
+ }
4186
+ const parts = [`${name}=${encodeURIComponent(value2)}`];
4187
+ if (options.maxAge !== void 0) {
4188
+ parts.push(`Max-Age=${options.maxAge}`);
4189
+ }
4190
+ if (options.expires) {
4191
+ parts.push(`Expires=${options.expires.toUTCString()}`);
4192
+ }
4193
+ if (options.path) {
4194
+ parts.push(`Path=${options.path}`);
4195
+ }
4196
+ if (options.domain) {
4197
+ parts.push(`Domain=${options.domain}`);
4198
+ }
4199
+ if (options.secure) {
4200
+ parts.push("Secure");
4201
+ }
4202
+ if (options.httpOnly) {
4203
+ parts.push("HttpOnly");
4204
+ }
4205
+ if (options.sameSite) {
4206
+ parts.push(`SameSite=${options.sameSite}`);
4207
+ }
4208
+ c.header("Set-Cookie", parts.join("; "), { append: true });
4209
+ }
4210
+ function getCsrfToken(c, options = {}) {
4211
+ const cookieName = options.cookieName ?? "gravito_csrf";
4212
+ const cookieHeader = c.req.header("Cookie") || "";
4213
+ const cookies = parseCookies(cookieHeader);
4214
+ let token = cookies[cookieName];
4215
+ if (!token) {
4216
+ token = crypto2.randomBytes(32).toString("hex");
4217
+ const cookieOptions = buildCookieOptions(options.cookie);
4218
+ const cookieJar = c.get("cookieJar");
4219
+ if (cookieJar?.queue) {
4220
+ cookieJar.queue(cookieName, token, 60 * 24 * 7, cookieOptions);
4221
+ } else if (canSetHeader(c)) {
4222
+ setCookieHeader(c, cookieName, token, cookieOptions);
4223
+ }
4224
+ }
4225
+ return token;
4226
+ }
4227
+ function csrfProtection(options = {}) {
4228
+ const cookieName = options.cookieName ?? "gravito_csrf";
4229
+ const headerName = (options.headerName ?? "X-CSRF-Token").toLowerCase();
4230
+ const formFieldName = options.formFieldName ?? "_token";
4231
+ const safeMethods = (options.safeMethods ?? defaultSafeMethods).map((m) => m.toUpperCase());
4232
+ return async (c, next) => {
4233
+ const method = c.req.method.toUpperCase();
4234
+ const cookieHeader = c.req.header("Cookie") || "";
4235
+ const cookies = parseCookies(cookieHeader);
4236
+ const token = cookies[cookieName] || getCsrfToken(c, options);
4237
+ if (safeMethods.includes(method)) {
4238
+ await next();
4239
+ return void 0;
4240
+ }
4241
+ const headerToken = c.req.header(headerName) || c.req.header(headerName.toLowerCase());
4242
+ let bodyToken;
4243
+ const contentType = c.req.header("Content-Type") || "";
4244
+ if (contentType.includes("application/x-www-form-urlencoded") || contentType.includes("multipart/form-data")) {
4245
+ const body = await c.req.parseBody() || {};
4246
+ const raw = body[formFieldName];
4247
+ if (typeof raw === "string") {
4248
+ bodyToken = raw;
4249
+ }
4250
+ }
4251
+ const requestToken = headerToken || bodyToken;
4252
+ if (!requestToken || !timingSafeEqual(token, requestToken)) {
4253
+ return c.text("Invalid CSRF token", 419);
4254
+ }
4255
+ await next();
4256
+ return void 0;
4257
+ };
4258
+ }
4259
+
4260
+ // src/http/middleware/HeaderTokenGate.ts
4261
+ function createHeaderGate(options = {}) {
4262
+ const headerName = options.headerName ?? "x-admin-token";
4263
+ return async (c) => {
4264
+ const expected = typeof options.token === "function" ? options.token(c) : options.token ?? process.env.ADMIN_TOKEN;
4265
+ if (!expected) {
4266
+ return false;
4267
+ }
4268
+ const provided = c.req.header(headerName);
4269
+ return provided === expected;
4270
+ };
4271
+ }
4272
+ function requireHeaderToken(options = {}) {
4273
+ const gate = createHeaderGate(options);
4274
+ const status = options.status ?? 403;
4275
+ const message = options.message ?? "Unauthorized";
4276
+ return async (c, next) => {
4277
+ if (!await gate(c)) {
4278
+ return c.text(message, status);
4279
+ }
4280
+ await next();
4281
+ return void 0;
4282
+ };
4283
+ }
4284
+
4285
+ // src/http/middleware/SecurityHeaders.ts
4286
+ function canSetHeader2(c) {
4287
+ return typeof c.header === "function";
4288
+ }
4289
+ function setHeader(c, name, value2) {
4290
+ if (!canSetHeader2(c)) {
4291
+ return;
4292
+ }
4293
+ c.header(name, value2);
4294
+ }
4295
+ function buildHstsHeader(options) {
4296
+ const parts = [`max-age=${Math.max(0, options.maxAge)}`];
4297
+ if (options.includeSubDomains) {
4298
+ parts.push("includeSubDomains");
4299
+ }
4300
+ if (options.preload) {
4301
+ parts.push("preload");
4302
+ }
4303
+ return parts.join("; ");
4304
+ }
4305
+ function securityHeaders(options = {}) {
4306
+ const defaults = {
4307
+ frameOptions: "DENY",
4308
+ referrerPolicy: "no-referrer",
4309
+ noSniff: true,
4310
+ permissionsPolicy: false,
4311
+ crossOriginOpenerPolicy: "same-origin",
4312
+ crossOriginResourcePolicy: "same-site"
4313
+ };
4314
+ const merged = {
4315
+ ...defaults,
4316
+ ...options
4317
+ };
4318
+ return async (c, next) => {
4319
+ if (merged.noSniff) {
4320
+ setHeader(c, "X-Content-Type-Options", "nosniff");
4321
+ }
4322
+ if (merged.frameOptions) {
4323
+ setHeader(c, "X-Frame-Options", merged.frameOptions);
4324
+ }
4325
+ if (merged.referrerPolicy) {
4326
+ setHeader(c, "Referrer-Policy", merged.referrerPolicy);
4327
+ }
4328
+ if (merged.permissionsPolicy) {
4329
+ setHeader(c, "Permissions-Policy", merged.permissionsPolicy);
4330
+ }
4331
+ if (merged.crossOriginOpenerPolicy) {
4332
+ setHeader(c, "Cross-Origin-Opener-Policy", merged.crossOriginOpenerPolicy);
4333
+ }
4334
+ if (merged.crossOriginResourcePolicy) {
4335
+ setHeader(c, "Cross-Origin-Resource-Policy", merged.crossOriginResourcePolicy);
4336
+ }
4337
+ const cspValue = typeof merged.contentSecurityPolicy === "function" ? merged.contentSecurityPolicy(c) : merged.contentSecurityPolicy;
4338
+ if (cspValue) {
4339
+ setHeader(c, "Content-Security-Policy", cspValue);
4340
+ }
4341
+ if (merged.hsts) {
4342
+ setHeader(c, "Strict-Transport-Security", buildHstsHeader(merged.hsts));
4343
+ }
4344
+ await next();
4345
+ return void 0;
4346
+ };
4347
+ }
4348
+
4349
+ // src/http/middleware/ThrottleRequests.ts
4350
+ var ThrottleRequests = class {
4351
+ constructor(core) {
4352
+ this.core = core;
4353
+ }
4354
+ /**
4355
+ * Create the middleware
4356
+ * @param maxAttempts - Max requests allowed
4357
+ * @param decaySeconds - Time window in seconds
4358
+ */
4359
+ handle(maxAttempts = 60, decaySeconds = 60) {
4360
+ return async (c, next) => {
4361
+ const cache = c.get("cache");
4362
+ if (!cache) {
4363
+ this.core.logger.warn("RateLimiter: OrbitCache not found. Skipping rate limiting.");
4364
+ await next();
4365
+ return void 0;
4366
+ }
4367
+ const trustProxy = this.core.config.get("TRUST_PROXY", false);
4368
+ const forwardedFor = c.req.header("x-forwarded-for") || "";
4369
+ const forwardedIp = forwardedFor.split(",")[0]?.trim();
4370
+ const ip = trustProxy ? forwardedIp || "127.0.0.1" : "127.0.0.1";
4371
+ const key = `throttle:${ip}:${c.req.path}`;
4372
+ const limiter = cache.limiter();
4373
+ const result = await limiter.attempt(key, maxAttempts, decaySeconds);
4374
+ c.header("X-RateLimit-Limit", String(maxAttempts));
4375
+ c.header("X-RateLimit-Remaining", String(Math.max(0, result.remaining)));
4376
+ if (result.reset) {
4377
+ c.header("X-RateLimit-Reset", String(result.reset));
4378
+ }
4379
+ if (!result.allowed) {
4380
+ c.header("Retry-After", String(decaySeconds));
4381
+ return c.text("Too Many Requests", 429);
4382
+ }
4383
+ await next();
4384
+ return void 0;
4385
+ };
4386
+ }
4387
+ };
4388
+
4389
+ // src/testing/TestResponse.ts
4390
+ import { expect } from "bun:test";
4391
+ var TestResponse = class {
4392
+ constructor(response) {
4393
+ this.response = response;
4394
+ }
4395
+ _jsonData = null;
4396
+ _textData = null;
4397
+ /**
4398
+ * Assert the response status code
4399
+ */
4400
+ assertStatus(status) {
4401
+ expect(this.response.status).toBe(status);
4402
+ return this;
4403
+ }
4404
+ /**
4405
+ * Assert that the response has a 200 status code
4406
+ */
4407
+ assertOk() {
4408
+ return this.assertStatus(200);
4409
+ }
4410
+ /**
4411
+ * Assert that the response has a 201 status code
4412
+ */
4413
+ assertCreated() {
4414
+ return this.assertStatus(201);
4415
+ }
4416
+ /**
4417
+ * Assert that the response has a 404 status code
4418
+ */
4419
+ assertNotFound() {
4420
+ return this.assertStatus(404);
4421
+ }
4422
+ /**
4423
+ * Assert that the response has a 403 status code
4424
+ */
4425
+ assertForbidden() {
4426
+ return this.assertStatus(403);
4427
+ }
4428
+ /**
4429
+ * Assert that the response has a 401 status code
4430
+ */
4431
+ assertUnauthorized() {
4432
+ return this.assertStatus(401);
4433
+ }
4434
+ /**
4435
+ * Assert the response is a redirect
4436
+ */
4437
+ assertRedirect(uri) {
4438
+ expect([301, 302, 303, 307, 308]).toContain(this.response.status);
4439
+ if (uri) {
4440
+ expect(this.response.headers.get("Location")).toBe(uri);
4441
+ }
4442
+ return this;
4443
+ }
4444
+ /**
4445
+ * Assert that the response contains the given JSON data.
4446
+ */
4447
+ async assertJson(data) {
4448
+ const json = await this.getJson();
4449
+ expect(json).toMatchObject(data);
4450
+ return this;
4451
+ }
4452
+ /**
4453
+ * Assert that the response contains exactly the given JSON data.
4454
+ */
4455
+ async assertExactJson(data) {
4456
+ const json = await this.getJson();
4457
+ expect(json).toEqual(data);
4458
+ return this;
4459
+ }
4460
+ /**
4461
+ * Assert the structure of the JSON response.
4462
+ */
4463
+ async assertJsonStructure(structure) {
4464
+ const json = await this.getJson();
4465
+ const checkKeys = (data, struct) => {
4466
+ for (const key in struct) {
4467
+ if (Array.isArray(struct[key])) {
4468
+ expect(Array.isArray(data[key])).toBe(true);
4469
+ if (data[key].length > 0) {
4470
+ checkKeys(data[key][0], struct[key][0]);
4471
+ }
4472
+ } else if (typeof struct[key] === "object") {
4473
+ expect(typeof data[key]).toBe("object");
4474
+ checkKeys(data[key], struct[key]);
4475
+ } else {
4476
+ expect(data).toHaveProperty(key);
4477
+ }
4478
+ }
4479
+ };
4480
+ checkKeys(json, structure);
4481
+ return this;
4482
+ }
4483
+ /**
4484
+ * Assert that the response contains the given string.
4485
+ */
4486
+ async assertSee(value2) {
4487
+ const text = await this.getText();
4488
+ expect(text).toContain(value2);
4489
+ return this;
4490
+ }
4491
+ /**
4492
+ * Assert that the response does not contain the given string.
4493
+ */
4494
+ async assertDontSee(value2) {
4495
+ const text = await this.getText();
4496
+ expect(text).not.toContain(value2);
4497
+ return this;
4498
+ }
4499
+ /**
4500
+ * Assert a header exists and matches value
4501
+ */
4502
+ assertHeader(header, value2) {
4503
+ expect(this.response.headers.get(header)).toBe(value2);
4504
+ return this;
4505
+ }
4506
+ /**
4507
+ * Assert a header does not exist
4508
+ */
4509
+ assertHeaderMissing(header) {
4510
+ expect(this.response.headers.has(header)).toBe(false);
4511
+ return this;
4512
+ }
4513
+ /**
4514
+ * Get the JSON content
4515
+ */
4516
+ async getJson() {
4517
+ if (this._jsonData) {
4518
+ return this._jsonData;
4519
+ }
4520
+ this._jsonData = await this.response.json();
4521
+ return this._jsonData;
4522
+ }
4523
+ /**
4524
+ * Get the text content
4525
+ */
4526
+ async getText() {
4527
+ if (this._textData !== null) {
4528
+ return this._textData;
4529
+ }
4530
+ this._textData = await this.response.text();
4531
+ return this._textData;
4532
+ }
4533
+ /**
4534
+ * Alias for getText for standard expectations if needed
4535
+ */
4536
+ get body() {
4537
+ return this.getText();
4538
+ }
4539
+ };
4540
+
4541
+ // src/testing/HttpTester.ts
4542
+ var HttpTester = class {
4543
+ constructor(core) {
4544
+ this.core = core;
4545
+ }
4546
+ /**
4547
+ * Make a GET request
4548
+ */
4549
+ async get(uri, headers = {}) {
4550
+ return this.call("GET", uri, null, headers);
4551
+ }
4552
+ /**
4553
+ * Make a POST request
4554
+ */
4555
+ async post(uri, data = null, headers = {}) {
4556
+ return this.call("POST", uri, data, headers);
4557
+ }
4558
+ /**
4559
+ * Make a PUT request
4560
+ */
4561
+ async put(uri, data = null, headers = {}) {
4562
+ return this.call("PUT", uri, data, headers);
4563
+ }
4564
+ /**
4565
+ * Make a PATCH request
4566
+ */
4567
+ async patch(uri, data = null, headers = {}) {
4568
+ return this.call("PATCH", uri, data, headers);
4569
+ }
4570
+ /**
4571
+ * Make a DELETE request
4572
+ */
4573
+ async delete(uri, data = null, headers = {}) {
4574
+ return this.call("DELETE", uri, data, headers);
4575
+ }
4576
+ /**
4577
+ * Core call method
4578
+ */
4579
+ async call(method, uri, data, headers) {
4580
+ const url = uri.startsWith("http") ? uri : `http://localhost${uri.startsWith("/") ? "" : "/"}${uri}`;
4581
+ let body = null;
4582
+ const requestHeaders = { ...headers };
4583
+ if (data) {
4584
+ if (typeof data === "object" && !(data instanceof FormData) && !(data instanceof Blob)) {
4585
+ body = JSON.stringify(data);
4586
+ if (!requestHeaders["Content-Type"] && !requestHeaders["content-type"]) {
4587
+ requestHeaders["Content-Type"] = "application/json";
4588
+ }
4589
+ } else {
4590
+ body = data;
4591
+ }
4592
+ }
4593
+ const request = new Request(url, {
4594
+ method,
4595
+ headers: requestHeaders,
4596
+ body
4597
+ });
4598
+ const response = await this.core.adapter.fetch(request);
4599
+ return new TestResponse(response);
4600
+ }
4601
+ };
4602
+ function createHttpTester(core) {
4603
+ return new HttpTester(core);
4604
+ }
4605
+
4606
+ // src/index.ts
4607
+ var VERSION = package_default.version;
4608
+ function defineConfig(config2) {
4609
+ return config2;
4610
+ }
4611
+ export {
4612
+ Application,
4613
+ Arr,
4614
+ AuthenticationException,
4615
+ AuthorizationException,
4616
+ ConfigManager,
4617
+ ConsoleLogger,
4618
+ Container,
4619
+ CookieJar,
4620
+ DumpDieError,
4621
+ Encrypter,
4622
+ Event,
4623
+ EventManager,
4624
+ GravitoAdapter,
4625
+ GravitoException,
4626
+ GravitoServer,
4627
+ HookManager,
4628
+ HttpException,
4629
+ HttpTester,
4630
+ ModelNotFoundException,
4631
+ PhotonAdapter,
4632
+ PhotonContextWrapper,
4633
+ PhotonRequestWrapper,
4634
+ PlanetCore,
4635
+ Route,
4636
+ Router,
4637
+ ServiceProvider,
4638
+ Str,
4639
+ TestResponse,
4640
+ ThrottleRequests,
4641
+ VERSION,
4642
+ ValidationException,
4643
+ abort,
4644
+ abortIf,
4645
+ abortUnless,
4646
+ app,
4647
+ blank,
4648
+ bodySizeLimit,
4649
+ config,
4650
+ cors,
4651
+ createErrorBag,
4652
+ createGravitoAdapter,
4653
+ createHeaderGate,
4654
+ createHttpTester,
4655
+ createPhotonAdapter,
4656
+ createSqliteDatabase,
4657
+ csrfProtection,
4658
+ dataGet,
4659
+ dataHas,
4660
+ dataSet,
4661
+ dd,
4662
+ defineConfig,
4663
+ dump,
4664
+ env,
4665
+ errors,
4666
+ fail,
4667
+ filled,
4668
+ getCsrfToken,
4669
+ getPasswordAdapter,
4670
+ getRuntimeAdapter,
4671
+ getRuntimeEnv,
4672
+ hasApp,
4673
+ isHttpAdapter,
4674
+ jsonFail,
4675
+ jsonSuccess,
4676
+ logger,
4677
+ ok,
4678
+ old,
4679
+ registerGlobalErrorHandlers,
4680
+ requireHeaderToken,
4681
+ router,
4682
+ securityHeaders,
4683
+ setApp,
4684
+ tap,
4685
+ throwIf,
4686
+ throwUnless,
4687
+ value
4688
+ };