stratal 0.0.24 → 0.0.26
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/bin/quarry.mjs +60 -4
- package/dist/bin/quarry.mjs.map +1 -1
- package/dist/cache/index.d.mts +1 -1
- package/dist/cache/index.mjs +2 -2
- package/dist/{command-CPhFHjG3.d.mts → command-DoBD2Cwl.d.mts} +2 -2
- package/dist/{command-CPhFHjG3.d.mts.map → command-DoBD2Cwl.d.mts.map} +1 -1
- package/dist/config/index.d.mts +1 -1
- package/dist/config/index.mjs +2 -2
- package/dist/{controller.decorator-C5UVeJS3.mjs → controller.decorator-YSTPQntu.mjs} +3 -3
- package/dist/{controller.decorator-C5UVeJS3.mjs.map → controller.decorator-YSTPQntu.mjs.map} +1 -1
- package/dist/cron/index.d.mts +1 -1
- package/dist/cron/index.mjs +1 -1
- package/dist/{cron.module-Bgzq5hiT.mjs → cron.module-C81HTzR7.mjs} +3 -3
- package/dist/{cron.module-Bgzq5hiT.mjs.map → cron.module-C81HTzR7.mjs.map} +1 -1
- package/dist/di/index.d.mts +2 -2
- package/dist/di/index.mjs +2 -2
- package/dist/{di-DseMn-z9.mjs → di-D7qmrAir.mjs} +60 -3
- package/dist/di-D7qmrAir.mjs.map +1 -0
- package/dist/email/index.d.mts +2 -2
- package/dist/email/index.mjs +3 -3
- package/dist/errors/index.d.mts +2 -2
- package/dist/errors/index.mjs +3 -3
- package/dist/{errors-mXYxG0XB.mjs → errors-C01O2T-n.mjs} +19 -4
- package/dist/errors-C01O2T-n.mjs.map +1 -0
- package/dist/events/index.d.mts +8 -0
- package/dist/events/index.d.mts.map +1 -1
- package/dist/events/index.mjs +1 -1
- package/dist/{events-BXJGZjpG.mjs → events-BhEQuT1X.mjs} +5 -2
- package/dist/events-BhEQuT1X.mjs.map +1 -0
- package/dist/{exception-context-kEoMFwze.mjs → exception-context-D-kvney-.mjs} +2 -2
- package/dist/{exception-context-kEoMFwze.mjs.map → exception-context-D-kvney-.mjs.map} +1 -1
- package/dist/{gateway-context-TMu_AlJt.mjs → gateway-context-m7kEzRa2.mjs} +4 -4
- package/dist/{gateway-context-TMu_AlJt.mjs.map → gateway-context-m7kEzRa2.mjs.map} +1 -1
- package/dist/guards/index.d.mts +1 -1
- package/dist/{hono-app-CvV3hOfT.mjs → hono-app-COAgmutc.mjs} +18 -11
- package/dist/hono-app-COAgmutc.mjs.map +1 -0
- package/dist/{http-method.decorator-ByWZb9DO.mjs → http-method.decorator-BljM8BDj.mjs} +2 -2
- package/dist/{http-method.decorator-ByWZb9DO.mjs.map → http-method.decorator-BljM8BDj.mjs.map} +1 -1
- package/dist/i18n/index.d.mts +10 -3
- package/dist/i18n/index.d.mts.map +1 -1
- package/dist/i18n/index.mjs +3 -3
- package/dist/{i18n.module-DRQAZoSZ.mjs → i18n.module-B2DvWUPa.mjs} +24 -9
- package/dist/i18n.module-B2DvWUPa.mjs.map +1 -0
- package/dist/{index-B5JBRcWD.d.mts → index-CNuFQSNj.d.mts} +6 -4
- package/dist/{index-B5JBRcWD.d.mts.map → index-CNuFQSNj.d.mts.map} +1 -1
- package/dist/{index-B_JoEl3V.d.mts → index-uybm0bhQ.d.mts} +111 -6
- package/dist/index-uybm0bhQ.d.mts.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +2 -2
- package/dist/{lazy-module-loader-Ib383jH_.d.mts → lazy-module-loader-M6YKudNL.d.mts} +2 -2
- package/dist/{lazy-module-loader-Ib383jH_.d.mts.map → lazy-module-loader-M6YKudNL.d.mts.map} +1 -1
- package/dist/{locale-path.service-D-dHiIPc.mjs → locale-path.service-CH0CaxwH.mjs} +3 -3
- package/dist/{locale-path.service-D-dHiIPc.mjs.map → locale-path.service-CH0CaxwH.mjs.map} +1 -1
- package/dist/{locale-url.service-C2EWmGdq.mjs → locale-url.service-6bgia24_.mjs} +2 -2
- package/dist/{locale-url.service-C2EWmGdq.mjs.map → locale-url.service-6bgia24_.mjs.map} +1 -1
- package/dist/logger/index.mjs +1 -1
- package/dist/module/index.d.mts +2 -2
- package/dist/module/index.mjs +2 -2
- package/dist/{module-registry-Dm-pqHd3.mjs → module-registry-NxX5O0Qk.mjs} +4 -4
- package/dist/{module-registry-Dm-pqHd3.mjs.map → module-registry-NxX5O0Qk.mjs.map} +1 -1
- package/dist/openapi/index.d.mts +2 -2
- package/dist/openapi/index.mjs +1 -1
- package/dist/{openapi-CstuTM8S.mjs → openapi-CMwuCp31.mjs} +3 -3
- package/dist/{openapi-CstuTM8S.mjs.map → openapi-CMwuCp31.mjs.map} +1 -1
- package/dist/{openapi.service-YhTiJ1bO.d.mts → openapi.service-2rvJBCEg.d.mts} +2 -2
- package/dist/{openapi.service-YhTiJ1bO.d.mts.map → openapi.service-2rvJBCEg.d.mts.map} +1 -1
- package/dist/quarry/index.d.mts +3 -3
- package/dist/quarry/index.mjs +1 -1
- package/dist/quarry/runner.d.mts +6 -6
- package/dist/quarry/runner.mjs +5 -5
- package/dist/{quarry-registry-CXg0RFXq.d.mts → quarry-registry-DRnV-DDa.d.mts} +3 -3
- package/dist/{quarry-registry-CXg0RFXq.d.mts.map → quarry-registry-DRnV-DDa.d.mts.map} +1 -1
- package/dist/{quarry.module-BuRPGMDm.mjs → quarry.module-CcGxU2dJ.mjs} +3 -3
- package/dist/{quarry.module-BuRPGMDm.mjs.map → quarry.module-CcGxU2dJ.mjs.map} +1 -1
- package/dist/queue/index.d.mts +1 -1
- package/dist/queue/index.mjs +2 -2
- package/dist/{queue.module-nddvxzCB.mjs → queue.module-CEs4_kEM.mjs} +15 -8
- package/dist/{queue.module-nddvxzCB.mjs.map → queue.module-CEs4_kEM.mjs.map} +1 -1
- package/dist/{r2-storage.provider-DCxQt9dD.mjs → r2-storage.provider-BoZmR6Ut.mjs} +2 -2
- package/dist/{r2-storage.provider-DCxQt9dD.mjs.map → r2-storage.provider-BoZmR6Ut.mjs.map} +1 -1
- package/dist/{rate-limit.decorator-BPAie_p3.mjs → rate-limit.decorator-Djs4oYDB.mjs} +2 -2
- package/dist/{rate-limit.decorator-BPAie_p3.mjs.map → rate-limit.decorator-Djs4oYDB.mjs.map} +1 -1
- package/dist/rate-limiter/index.d.mts +54 -47
- package/dist/rate-limiter/index.d.mts.map +1 -1
- package/dist/rate-limiter/index.mjs +16 -7
- package/dist/rate-limiter/index.mjs.map +1 -1
- package/dist/{route-registration.service-D6vSwiKP.mjs → route-registration.service-CDPQKpm4.mjs} +9 -9
- package/dist/{route-registration.service-D6vSwiKP.mjs.map → route-registration.service-CDPQKpm4.mjs.map} +1 -1
- package/dist/{route-registry-CYqLp2Nj.mjs → route-registry-BvLJisvK.mjs} +3 -3
- package/dist/{route-registry-CYqLp2Nj.mjs.map → route-registry-BvLJisvK.mjs.map} +1 -1
- package/dist/router/index.d.mts +2 -2
- package/dist/router/index.mjs +16 -16
- package/dist/{router-CWGBD-Bg.mjs → router-DwyqEXgf.mjs} +13 -13
- package/dist/{router-CWGBD-Bg.mjs.map → router-DwyqEXgf.mjs.map} +1 -1
- package/dist/{router-resolver-D4YlPNlm.mjs → router-resolver-sUV_jTrU.mjs} +2 -2
- package/dist/{router-resolver-D4YlPNlm.mjs.map → router-resolver-sUV_jTrU.mjs.map} +1 -1
- package/dist/seeder/index.d.mts +2 -2
- package/dist/seeder/index.mjs +3 -3
- package/dist/{seeder-7ubkms-Y.mjs → seeder-BPGY5rUb.mjs} +4 -4
- package/dist/{seeder-7ubkms-Y.mjs.map → seeder-BPGY5rUb.mjs.map} +1 -1
- package/dist/{seeder-registry-CyUmKsJq.mjs → seeder-registry-DEvCycsT.mjs} +3 -3
- package/dist/{seeder-registry-CyUmKsJq.mjs.map → seeder-registry-DEvCycsT.mjs.map} +1 -1
- package/dist/{seeder.module-CYYwk3Qk.mjs → seeder.module-CIwQbdN4.mjs} +2 -2
- package/dist/{seeder.module-CYYwk3Qk.mjs.map → seeder.module-CIwQbdN4.mjs.map} +1 -1
- package/dist/storage/index.d.mts +1 -1
- package/dist/storage/index.mjs +2 -2
- package/dist/storage/providers/index.mjs +1 -1
- package/dist/{storage-MDZypIE9.mjs → storage-C30X81CS.mjs} +7 -7
- package/dist/{storage-MDZypIE9.mjs.map → storage-C30X81CS.mjs.map} +1 -1
- package/dist/{storage.error-Dnib4VHc.mjs → storage.error-BStXPmO4.mjs} +2 -2
- package/dist/{storage.error-Dnib4VHc.mjs.map → storage.error-BStXPmO4.mjs.map} +1 -1
- package/dist/{stratal-DL9M38_s.mjs → stratal-BL6FKUM_.mjs} +85 -35
- package/dist/stratal-BL6FKUM_.mjs.map +1 -0
- package/dist/{stratal-DwDJPY9N.d.mts → stratal-D5j_I14G.d.mts} +13 -3
- package/dist/stratal-D5j_I14G.d.mts.map +1 -0
- package/dist/trailing-slash-2SctvePW.mjs +80 -0
- package/dist/trailing-slash-2SctvePW.mjs.map +1 -0
- package/dist/{uri-h7Q8Jug9.mjs → uri-iwofWJ_T.mjs} +12 -10
- package/dist/uri-iwofWJ_T.mjs.map +1 -0
- package/dist/{versioning.service-C6aHky8-.mjs → versioning.service-CCa2oYMJ.mjs} +3 -3
- package/dist/{versioning.service-C6aHky8-.mjs.map → versioning.service-CCa2oYMJ.mjs.map} +1 -1
- package/dist/websocket/index.d.mts +1 -1
- package/dist/websocket/index.mjs +1 -1
- package/dist/workers/index.d.mts +1 -1
- package/dist/workers/index.mjs +2 -2
- package/package.json +1 -1
- package/dist/di-DseMn-z9.mjs.map +0 -1
- package/dist/errors-mXYxG0XB.mjs.map +0 -1
- package/dist/events-BXJGZjpG.mjs.map +0 -1
- package/dist/hono-app-CvV3hOfT.mjs.map +0 -1
- package/dist/i18n.module-DRQAZoSZ.mjs.map +0 -1
- package/dist/index-B_JoEl3V.d.mts.map +0 -1
- package/dist/stratal-DL9M38_s.mjs.map +0 -1
- package/dist/stratal-DwDJPY9N.d.mts.map +0 -1
- package/dist/trailing-slash-CFyw8nYu.mjs +0 -34
- package/dist/trailing-slash-CFyw8nYu.mjs.map +0 -1
- package/dist/uri-h7Q8Jug9.mjs.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { b as ROUTER_TOKENS, r as DI_TOKENS, t as Container } from "./di-D7qmrAir.mjs";
|
|
2
2
|
import { n as getContainer, r as runWithContainer, t as containerStorage } from "./container-storage-BmOJ4_Na.mjs";
|
|
3
3
|
import { JsonFormatter, LOGGER_TOKENS, LoggerService, PrettyFormatter } from "./logger/index.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { i as createQueueExceptionContext, n as createCronExceptionContext, t as createCliExceptionContext } from "./exception-context-
|
|
6
|
-
import { a as getListenerHandlers } from "./events-
|
|
7
|
-
import { d as LazyModuleLoader, t as ModuleRegistry } from "./module-registry-
|
|
8
|
-
import { t as SEEDER_TOKENS } from "./seeder-registry-
|
|
4
|
+
import { i as StratalNotInitializedError, o as DefaultExceptionHandler, r as StratalSupersededError } from "./errors-C01O2T-n.mjs";
|
|
5
|
+
import { i as createQueueExceptionContext, n as createCronExceptionContext, t as createCliExceptionContext } from "./exception-context-D-kvney-.mjs";
|
|
6
|
+
import { a as getListenerHandlers } from "./events-BhEQuT1X.mjs";
|
|
7
|
+
import { d as LazyModuleLoader, t as ModuleRegistry } from "./module-registry-NxX5O0Qk.mjs";
|
|
8
|
+
import { t as SEEDER_TOKENS } from "./seeder-registry-DEvCycsT.mjs";
|
|
9
9
|
//#region src/application.ts
|
|
10
10
|
var Application = class {
|
|
11
11
|
_container;
|
|
@@ -48,10 +48,17 @@ var Application = class {
|
|
|
48
48
|
}
|
|
49
49
|
async initialize() {
|
|
50
50
|
if (this.initialized) return;
|
|
51
|
-
|
|
51
|
+
try {
|
|
52
|
+
await runWithContainer(this._container, () => this.initializeInternal());
|
|
53
|
+
} catch (error) {
|
|
54
|
+
await this.teardown().catch((teardownError) => {
|
|
55
|
+
console.error("[stratal] Teardown after failed initialization also failed:", teardownError);
|
|
56
|
+
});
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
52
59
|
}
|
|
53
60
|
async initializeInternal() {
|
|
54
|
-
const [{ QuarryModule }, { SeederModule }] = await Promise.all([import("./quarry.module-
|
|
61
|
+
const [{ QuarryModule }, { SeederModule }] = await Promise.all([import("./quarry.module-CcGxU2dJ.mjs").then((n) => n.n), import("./seeder.module-CIwQbdN4.mjs").then((n) => n.n)]);
|
|
55
62
|
this.moduleRegistry.registerAll([QuarryModule, SeederModule]);
|
|
56
63
|
this.moduleRegistry.register(this.appConfig.module);
|
|
57
64
|
await this.moduleRegistry.initialize();
|
|
@@ -64,14 +71,14 @@ var Application = class {
|
|
|
64
71
|
}
|
|
65
72
|
async registerRoutingServices() {
|
|
66
73
|
const [{ HonoApp }, { RouteRegistry }, { RouterResolver }, { VersioningService }, { LocalePathService }, { LocaleUrlService }, { RouteRegistrationService }, { Uri }] = await Promise.all([
|
|
67
|
-
import("./hono-app-
|
|
68
|
-
import("./route-registry-
|
|
69
|
-
import("./router-resolver-
|
|
70
|
-
import("./versioning.service-
|
|
71
|
-
import("./locale-path.service-
|
|
72
|
-
import("./locale-url.service-
|
|
73
|
-
import("./route-registration.service-
|
|
74
|
-
import("./uri-
|
|
74
|
+
import("./hono-app-COAgmutc.mjs").then((n) => n.n),
|
|
75
|
+
import("./route-registry-BvLJisvK.mjs").then((n) => n.n),
|
|
76
|
+
import("./router-resolver-sUV_jTrU.mjs"),
|
|
77
|
+
import("./versioning.service-CCa2oYMJ.mjs").then((n) => n.n),
|
|
78
|
+
import("./locale-path.service-CH0CaxwH.mjs").then((n) => n.n),
|
|
79
|
+
import("./locale-url.service-6bgia24_.mjs").then((n) => n.n),
|
|
80
|
+
import("./route-registration.service-CDPQKpm4.mjs").then((n) => n.n),
|
|
81
|
+
import("./uri-iwofWJ_T.mjs").then((n) => n.r)
|
|
75
82
|
]);
|
|
76
83
|
this._container.register(ROUTER_TOKENS.VersioningService, VersioningService);
|
|
77
84
|
this._container.register(ROUTER_TOKENS.HonoApp, HonoApp);
|
|
@@ -92,7 +99,7 @@ var Application = class {
|
|
|
92
99
|
*/
|
|
93
100
|
initializeEventListeners() {
|
|
94
101
|
this.eventsInitPromise ??= runWithContainer(this._container, async () => {
|
|
95
|
-
const { EventsModule } = await import("./events-
|
|
102
|
+
const { EventsModule } = await import("./events-BhEQuT1X.mjs").then((n) => n.n);
|
|
96
103
|
await this.moduleRegistry.registerLazy(EventsModule);
|
|
97
104
|
this.registerEventListeners();
|
|
98
105
|
});
|
|
@@ -122,7 +129,7 @@ var Application = class {
|
|
|
122
129
|
initializeQueue() {
|
|
123
130
|
this.queueInitPromise ??= runWithContainer(this._container, async () => {
|
|
124
131
|
await this.ensureI18n();
|
|
125
|
-
const { QueueModule } = await import("./queue.module-
|
|
132
|
+
const { QueueModule } = await import("./queue.module-CEs4_kEM.mjs").then((n) => n.n);
|
|
126
133
|
this.moduleRegistry.register(QueueModule);
|
|
127
134
|
this.consumerRegistry = this._container.resolve(DI_TOKENS.ConsumerRegistry);
|
|
128
135
|
await this.registerQueueConsumers();
|
|
@@ -137,7 +144,7 @@ var Application = class {
|
|
|
137
144
|
*/
|
|
138
145
|
ensureI18n() {
|
|
139
146
|
this.i18nInitPromise ??= runWithContainer(this._container, async () => {
|
|
140
|
-
const { I18nModule } = await import("./i18n.module-
|
|
147
|
+
const { I18nModule } = await import("./i18n.module-B2DvWUPa.mjs").then((n) => n.n);
|
|
141
148
|
await this.moduleRegistry.registerLazy(I18nModule);
|
|
142
149
|
});
|
|
143
150
|
return this.i18nInitPromise;
|
|
@@ -148,7 +155,7 @@ var Application = class {
|
|
|
148
155
|
*/
|
|
149
156
|
ensureCron() {
|
|
150
157
|
this.cronInitPromise ??= runWithContainer(this._container, async () => {
|
|
151
|
-
const { CronModule } = await import("./cron.module-
|
|
158
|
+
const { CronModule } = await import("./cron.module-C81HTzR7.mjs").then((n) => n.n);
|
|
152
159
|
this.moduleRegistry.register(CronModule);
|
|
153
160
|
this.cronManager = this._container.resolve(DI_TOKENS.Cron);
|
|
154
161
|
this.registerCronJobs(this.cronManager);
|
|
@@ -196,15 +203,20 @@ var Application = class {
|
|
|
196
203
|
return {
|
|
197
204
|
getLocale: () => locale,
|
|
198
205
|
setLocale: () => {},
|
|
206
|
+
header: () => void 0,
|
|
199
207
|
getContainer: () => containerStorage.getStore() ?? this._container
|
|
200
208
|
};
|
|
201
209
|
}
|
|
202
210
|
async shutdown() {
|
|
203
211
|
if (!this.initialized) return;
|
|
204
212
|
this.initialized = false;
|
|
213
|
+
await this.teardown();
|
|
214
|
+
}
|
|
215
|
+
/** Releases everything boot acquired: module resources, then the container. */
|
|
216
|
+
async teardown() {
|
|
205
217
|
await this.moduleRegistry.shutdown();
|
|
206
218
|
this._container.resolve(LOGGER_TOKENS.LoggerService).info("Disposing container...");
|
|
207
|
-
this._container.dispose();
|
|
219
|
+
await this._container.dispose();
|
|
208
220
|
}
|
|
209
221
|
async handleCommand(name, input) {
|
|
210
222
|
await this.initializeRouting();
|
|
@@ -315,16 +327,31 @@ var Stratal = class Stratal {
|
|
|
315
327
|
initPromise;
|
|
316
328
|
static _application = null;
|
|
317
329
|
static _generation = 0;
|
|
318
|
-
static
|
|
330
|
+
static _current = null;
|
|
331
|
+
/**
|
|
332
|
+
* Serializes generation teardown: each new instance appends the previous
|
|
333
|
+
* instance's shutdown here, and `prepareApp` awaits the chain before
|
|
334
|
+
* allocating the new Application. Old and new DI graphs never coexist,
|
|
335
|
+
* so repeated Vite HMR reloads can't accumulate live object graphs.
|
|
336
|
+
*/
|
|
337
|
+
static _teardownChain = Promise.resolve();
|
|
319
338
|
constructor(config) {
|
|
320
339
|
this.fetch = this.fetch.bind(this);
|
|
321
340
|
this.queue = this.queue.bind(this);
|
|
322
341
|
this.scheduled = this.scheduled.bind(this);
|
|
323
342
|
const generation = ++Stratal._generation;
|
|
324
|
-
|
|
325
|
-
Stratal.
|
|
326
|
-
|
|
343
|
+
const previous = Stratal._current;
|
|
344
|
+
Stratal._current = this;
|
|
345
|
+
if (previous) Stratal._teardownChain = Stratal._teardownChain.then(() => previous.shutdown()).catch((error) => {
|
|
346
|
+
console.error("[stratal] Failed to shut down superseded instance:", error);
|
|
347
|
+
});
|
|
348
|
+
const teardownBarrier = Stratal._teardownChain;
|
|
349
|
+
this.initPromise = this.prepareApp(config, generation, teardownBarrier);
|
|
327
350
|
Stratal._application = this.initPromise;
|
|
351
|
+
this.initPromise.catch((error) => {
|
|
352
|
+
if (error instanceof StratalSupersededError) return;
|
|
353
|
+
console.error("[stratal] Initialization failed:", error);
|
|
354
|
+
});
|
|
328
355
|
}
|
|
329
356
|
async fetch(request, env, ctx) {
|
|
330
357
|
return (await (await this.ensureReady()).ensureHono()).fetch(request, env, ctx);
|
|
@@ -336,7 +363,7 @@ var Stratal = class Stratal {
|
|
|
336
363
|
return (await this.ensureReady()).handleScheduled(controller);
|
|
337
364
|
}
|
|
338
365
|
get hono() {
|
|
339
|
-
return this.
|
|
366
|
+
return this.ensureReady().then((app) => app.ensureHono());
|
|
340
367
|
}
|
|
341
368
|
async shutdown() {
|
|
342
369
|
try {
|
|
@@ -346,24 +373,47 @@ var Stratal = class Stratal {
|
|
|
346
373
|
await this.app.shutdown();
|
|
347
374
|
this.app = null;
|
|
348
375
|
}
|
|
376
|
+
if (Stratal._current === this) {
|
|
377
|
+
Stratal._current = null;
|
|
378
|
+
Stratal._application = null;
|
|
379
|
+
}
|
|
349
380
|
}
|
|
350
381
|
/**
|
|
351
382
|
* @internal
|
|
352
383
|
* Resolves the Application instance from the static singleton.
|
|
353
384
|
* Used by worker base classes (DurableObject, Workflow, WorkerEntrypoint)
|
|
354
385
|
* to access the DI container without going through Cloudflare RPC.
|
|
386
|
+
* Generations superseded mid-boot reject with {@link StratalSupersededError};
|
|
387
|
+
* this retries against the replacing generation so callers always land on
|
|
388
|
+
* the live Application.
|
|
355
389
|
*/
|
|
356
|
-
static resolveApplication() {
|
|
357
|
-
|
|
358
|
-
|
|
390
|
+
static async resolveApplication() {
|
|
391
|
+
let current = Stratal._application;
|
|
392
|
+
while (current) try {
|
|
393
|
+
return await current;
|
|
394
|
+
} catch (error) {
|
|
395
|
+
if (error instanceof StratalSupersededError && Stratal._application !== current) {
|
|
396
|
+
current = Stratal._application;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
throw error;
|
|
400
|
+
}
|
|
401
|
+
throw new StratalNotInitializedError();
|
|
359
402
|
}
|
|
360
403
|
async ensureReady() {
|
|
361
|
-
this.app
|
|
362
|
-
|
|
404
|
+
if (this.app) return this.app;
|
|
405
|
+
try {
|
|
406
|
+
this.app = await this.initPromise;
|
|
407
|
+
return this.app;
|
|
408
|
+
} catch (error) {
|
|
409
|
+
if (error instanceof StratalSupersededError) return Stratal.resolveApplication();
|
|
410
|
+
throw error;
|
|
411
|
+
}
|
|
363
412
|
}
|
|
364
|
-
async prepareApp(config, generation) {
|
|
413
|
+
async prepareApp(config, generation, teardownBarrier) {
|
|
365
414
|
const { env, waitUntil } = await import("cloudflare:workers");
|
|
366
|
-
|
|
415
|
+
await teardownBarrier;
|
|
416
|
+
if (generation !== Stratal._generation) throw new StratalSupersededError(generation);
|
|
367
417
|
const app = new Application({
|
|
368
418
|
...config,
|
|
369
419
|
env,
|
|
@@ -372,7 +422,7 @@ var Stratal = class Stratal {
|
|
|
372
422
|
await app.initialize();
|
|
373
423
|
if (generation !== Stratal._generation) {
|
|
374
424
|
await app.shutdown();
|
|
375
|
-
|
|
425
|
+
throw new StratalSupersededError(generation);
|
|
376
426
|
}
|
|
377
427
|
return app;
|
|
378
428
|
}
|
|
@@ -380,4 +430,4 @@ var Stratal = class Stratal {
|
|
|
380
430
|
//#endregion
|
|
381
431
|
export { Application as n, Stratal as t };
|
|
382
432
|
|
|
383
|
-
//# sourceMappingURL=stratal-
|
|
433
|
+
//# sourceMappingURL=stratal-BL6FKUM_.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stratal-BL6FKUM_.mjs","names":[],"sources":["../src/application.ts","../src/stratal.ts"],"sourcesContent":["import type { CronJob } from './cron/cron-job'\nimport type { CronManager } from './cron/cron-manager'\nimport { Container } from './di/container'\nimport { containerStorage, getContainer, runWithContainer } from './di/container-storage'\nimport { DI_TOKENS } from './di/tokens'\nimport { type StratalEnv } from './env'\nimport { DefaultExceptionHandler } from './errors/default-exception-handler'\nimport { createCliExceptionContext, createCronExceptionContext, createQueueExceptionContext } from './errors/exception-context'\nimport type { ExceptionHandler } from './errors/exception-handler'\nimport type { EventHandler, EventRegistry } from './events'\nimport { getListenerHandlers } from './events'\nimport type { StratalExecutionContext } from './execution-context'\nimport { JsonFormatter, LOGGER_TOKENS, LoggerService, LogLevel, PrettyFormatter } from './logger'\nimport { LazyModuleLoader } from './module/lazy-module-loader'\nimport { ModuleRegistry } from './module/module-registry'\nimport type { DynamicModule, ModuleClass } from './module/types'\nimport type { Command } from './quarry/command'\nimport type { QuarryRegistry } from './quarry/quarry-registry'\nimport type { CommandInput, CommandResult } from './quarry/types'\nimport type { ConsumerRegistry } from './queue/consumer-registry'\nimport type { IQueueConsumer, QueueMessage } from './queue/queue-consumer'\nimport type { QueueManager } from './queue/queue-manager'\nimport type { RouterContext } from './router'\nimport type { HonoApp } from './router/hono-app'\nimport { ROUTER_TOKENS } from './router/router.tokens'\nimport type { TrailingSlashConfig, VersioningOptions } from './router/types'\nimport type { Seeder } from './seeder/seeder'\nimport { SEEDER_TOKENS } from './seeder/seeder-registry'\nimport type { SeederRegistry } from './seeder/seeder-registry'\nimport type { Constructor } from './types'\n\nexport interface ApplicationConfig {\n module: ModuleClass | DynamicModule\n logging?: {\n level?: LogLevel\n formatter?: 'json' | 'pretty'\n }\n versioning?: VersioningOptions\n trailingSlash?: TrailingSlashConfig\n exceptionHandler?: Constructor<ExceptionHandler>\n}\n\nexport interface ApplicationOptions extends ApplicationConfig {\n env: StratalEnv\n ctx: StratalExecutionContext\n}\n\nexport class Application {\n private _container: Container\n private honoApp!: HonoApp\n private moduleRegistry: ModuleRegistry\n private consumerRegistry!: ConsumerRegistry\n private cronManager?: CronManager\n private quarry!: QuarryRegistry\n private initialized = false\n\n // Independently memoized lazy-init promises — each built-in subsystem is\n // loaded on demand at its trigger point to keep cold start lean.\n private routingInitPromise: Promise<void> | null = null\n private eventsInitPromise: Promise<void> | null = null\n private queueInitPromise: Promise<void> | null = null\n private i18nInitPromise: Promise<void> | null = null\n private cronInitPromise: Promise<void> | null = null\n\n readonly env: StratalEnv\n private readonly appConfig: ApplicationConfig\n\n constructor({ env, ctx, ...config }: ApplicationOptions) {\n this.env = env\n this.appConfig = config\n\n this._container = new Container()\n\n this._container.registerValue(DI_TOKENS.Application, this)\n this._container.registerValue(DI_TOKENS.CloudflareEnv, env)\n this._container.registerValue(DI_TOKENS.ExecutionContext, ctx)\n this._container.registerValue(ROUTER_TOKENS.RouterContext, null)\n\n this.registerLoggerService()\n this.registerCoreServices()\n\n const logger = this._container.resolve<LoggerService>(LOGGER_TOKENS.LoggerService)\n this.moduleRegistry = new ModuleRegistry(this._container, logger)\n\n this._container.registerValue(DI_TOKENS.ModuleRegistry, this.moduleRegistry)\n }\n\n get container(): Container {\n return this._container\n }\n\n async ensureHono(): Promise<HonoApp> {\n await this.initializeRouting()\n return this.honoApp\n }\n\n get config(): ApplicationConfig {\n return this.appConfig\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) {\n return\n }\n\n try {\n await runWithContainer(this._container, () => this.initializeInternal())\n } catch (error) {\n // A failed boot can still hold live resources: every module whose\n // onInitialize already ran (DB pools, timers) and every container-cached\n // singleton. Tear them down here so callers never have to special-case\n // a failed initialize() — shutdown() stays a no-op for it.\n await this.teardown().catch((teardownError: unknown) => {\n console.error('[stratal] Teardown after failed initialization also failed:', teardownError)\n })\n throw error\n }\n }\n\n private async initializeInternal(): Promise<void> {\n // Eager subsystem modules — Quarry/Seeder are resolved synchronously\n // post-init (CLI runner, test harness), so they must be registered before\n // initialize() completes. Dynamic import keeps application.ts free of static\n // subsystem imports (uniform with the other built-ins). They load at boot\n // regardless, so this is for consistency, not cold-start deferral.\n const [{ QuarryModule }, { SeederModule }] = await Promise.all([\n import('./quarry/quarry.module'),\n import('./seeder/seeder.module'),\n ])\n this.moduleRegistry.registerAll([QuarryModule, SeederModule])\n\n // Register the user's root module (traverses imports). Other built-in\n // subsystems (i18n, queue, cache, openapi, cron, events, router) load on\n // demand at their trigger points.\n this.moduleRegistry.register(this.appConfig.module)\n\n // Initialize all modules (only those with lifecycle hooks)\n await this.moduleRegistry.initialize()\n\n // Initialize ExceptionHandler\n this.initializeExceptionHandler()\n\n // Register CLI commands + seeders (class refs only; no instantiation)\n this.registerCommands()\n this.registerSeeders()\n\n // Wire event listeners as part of initialization whenever the application\n // declares any, so emitted events reach their handlers regardless of which\n // entry point drives the app. Listener wiring otherwise happens lazily only\n // on the HTTP router (`initializeRouting`) and queue/scheduled/command\n // (`ensureScopedHandlers`) paths; any code that emits events without going\n // through one of those — a direct service or repository call, an RPC\n // entrypoint, or a Durable Object — would dispatch into a registry with no\n // handlers attached, silently dropping the event. Guarded on there being\n // listeners so applications with none still skip loading the events\n // subsystem; `initializeEventListeners` dedups, so any later lazy trigger\n // becomes a no-op.\n if (this.moduleRegistry.getAllListeners().length > 0) {\n await this.initializeEventListeners()\n }\n\n // Cron only loads when the app actually declares scheduled jobs\n if (this.moduleRegistry.getAllJobs().length > 0) {\n await this.ensureCron()\n }\n\n this.initialized = true\n }\n\n private async registerRoutingServices(): Promise<void> {\n const [\n { HonoApp },\n { RouteRegistry },\n { RouterResolver },\n { VersioningService },\n { LocalePathService },\n { LocaleUrlService },\n { RouteRegistrationService },\n { Uri },\n ] = await Promise.all([\n import('./router/hono-app'),\n import('./router/route-registry'),\n import('./router/router-resolver'),\n import('./router/services/versioning.service'),\n import('./router/services/locale-path.service'),\n import('./router/services/locale-url.service'),\n import('./router/services/route-registration.service'),\n import('./router/uri'),\n ])\n\n this._container.register(ROUTER_TOKENS.VersioningService, VersioningService)\n this._container.register(ROUTER_TOKENS.HonoApp, HonoApp)\n this._container.register(ROUTER_TOKENS.LocalePathService, LocalePathService)\n this._container.register(ROUTER_TOKENS.LocaleUrlService, LocaleUrlService)\n this._container.register(ROUTER_TOKENS.RouteRegistry, RouteRegistry)\n this._container.register(ROUTER_TOKENS.Uri, Uri)\n\n const routerConfigs = this.moduleRegistry.getAllRouterConfigs()\n if (routerConfigs.length > 0) {\n this._container.registerValue(ROUTER_TOKENS.RouterResolver, new RouterResolver(routerConfigs))\n }\n\n this._container.register(RouteRegistrationService, RouteRegistrationService)\n }\n\n /**\n * Load the events subsystem and wire listeners. Needed by the HTTP, queue,\n * and scheduled paths (handlers emit events). Independent of the queue\n * subsystem so HTTP-only apps never load queue code. EventsModule is\n * registered before the (possibly empty) listener wiring so `emit()` works\n * even with zero listeners; dedups against the framework's own load.\n */\n private initializeEventListeners(): Promise<void> {\n this.eventsInitPromise ??= runWithContainer(this._container, async () => {\n const { EventsModule } = await import('./events/events.module')\n await this.moduleRegistry.registerLazy(EventsModule)\n this.registerEventListeners()\n })\n return this.eventsInitPromise\n }\n\n /**\n * Wire event and queue handlers for any non-HTTP request scope — queue\n * batches, scheduled/cron runs, CLI commands, Durable Objects, Workflows,\n * WorkerEntrypoints — all of which may emit events or dispatch to queues from\n * arbitrary user code. This is the single source of truth for that wiring:\n * every non-HTTP entry point routes through it rather than open-coding which\n * subsystems to init, so a subsystem can't be silently dropped by a future\n * refactor (the queue half was once lost from the command path that way).\n *\n * HTTP (`fetch`) deliberately does NOT use this: a fetch worker only enqueues\n * to the async Cloudflare queue and never processes consumers inline, so it\n * skips queue init via `initializeRouting`.\n */\n async ensureScopedHandlers(): Promise<void> {\n await this.initializeEventListeners()\n await this.initializeQueue()\n }\n\n /**\n * Load the queue subsystem on demand (first queue/scheduled trigger). i18n is\n * ensured first because the queue registry depends on it.\n */\n private initializeQueue(): Promise<void> {\n this.queueInitPromise ??= runWithContainer(this._container, async () => {\n await this.ensureI18n()\n const { QueueModule } = await import('./queue/queue.module')\n this.moduleRegistry.register(QueueModule as unknown as ModuleClass)\n this.consumerRegistry = this._container.resolve<ConsumerRegistry>(DI_TOKENS.ConsumerRegistry)\n await this.registerQueueConsumers()\n })\n return this.queueInitPromise\n }\n\n /**\n * Load i18n on demand. Coupled to the request path (Zod validation error maps,\n * OpenAPI descriptions, queue registry), so it loads before routing/queue\n * handling. Uses `registerLazy` because I18nModule has an `onInitialize` hook\n * (configures the Zod error map). Dedups if the app already imported i18n.\n */\n private ensureI18n(): Promise<void> {\n this.i18nInitPromise ??= runWithContainer(this._container, async () => {\n const { I18nModule } = await import('./i18n/i18n.module')\n await this.moduleRegistry.registerLazy(I18nModule as unknown as ModuleClass)\n })\n return this.i18nInitPromise\n }\n\n /**\n * Load the cron subsystem on demand (first scheduled trigger, or at bootstrap\n * when the app declares jobs).\n */\n private ensureCron(): Promise<CronManager> {\n this.cronInitPromise ??= runWithContainer(this._container, async () => {\n const { CronModule } = await import('./cron/cron.module')\n this.moduleRegistry.register(CronModule)\n this.cronManager = this._container.resolve<CronManager>(DI_TOKENS.Cron)\n this.registerCronJobs(this.cronManager)\n })\n return this.cronInitPromise.then(() => this.cronManager!)\n }\n\n resolve<T>(token: symbol): T {\n try {\n return this._container.resolve(token)\n } catch (error) {\n const handler = this._container.resolve<ExceptionHandler>(DI_TOKENS.ExceptionHandler)\n const ctx = createCliExceptionContext('resolve')\n void handler.handle(error, ctx)\n throw error\n }\n }\n\n async handleQueue(batch: MessageBatch, queueName: string): Promise<void> {\n await this.ensureScopedHandlers()\n\n const firstMessage = batch.messages[0]?.body as QueueMessage | undefined\n const locale = firstMessage?.metadata?.locale ?? 'en'\n const mockRouterContext = this.createMockRouterContext(locale)\n\n await this._container.runInRequestScope(mockRouterContext, async (requestContainer) => {\n try {\n const queueManager = requestContainer.resolve<QueueManager>(DI_TOKENS.Queue)\n await queueManager.processBatch(queueName, batch)\n } catch (error) {\n const handler = requestContainer.resolve<ExceptionHandler>(DI_TOKENS.ExceptionHandler)\n await handler.handle(error, createQueueExceptionContext(queueName))\n throw error\n }\n })\n }\n\n async handleScheduled(controller: ScheduledController): Promise<void> {\n const cronManager = await this.ensureCron()\n // A scheduled job is a non-HTTP scope that can emit events and dispatch to\n // queues (timeout/reminder/digest emails) — wire both so dispatches aren't\n // silently dropped by the sync provider. i18n is ensured independently for\n // the job execution context. All inits are memoized, so this is idempotent.\n await this.ensureScopedHandlers()\n await this.ensureI18n()\n\n const mockRouterContext = this.createMockRouterContext('en')\n\n await this._container.runInRequestScope(mockRouterContext, async (requestContainer) => {\n try {\n await cronManager.executeScheduled(controller, requestContainer)\n } catch (error) {\n const handler = requestContainer.resolve<ExceptionHandler>(DI_TOKENS.ExceptionHandler)\n await handler.handle(error, createCronExceptionContext())\n throw error\n }\n })\n }\n\n createMockRouterContext(locale = 'en'): RouterContext {\n return {\n getLocale: () => locale,\n setLocale: () => { /* no-op */ },\n // Non-HTTP scopes have no request headers\n header: () => undefined,\n // Inside runInRequestScope the active container is the request-scoped child\n // (set in AsyncLocalStorage); return it so request-scoped providers resolve\n // against the scope, not the global container.\n getContainer: () => containerStorage.getStore() ?? this._container,\n } as unknown as RouterContext\n }\n\n async shutdown(): Promise<void> {\n // A failed initialize() has already torn itself down — only a fully\n // initialized application owns resources here.\n if (!this.initialized) return\n this.initialized = false\n\n await this.teardown()\n }\n\n /** Releases everything boot acquired: module resources, then the container. */\n private async teardown(): Promise<void> {\n await this.moduleRegistry.shutdown()\n\n const logger = this._container.resolve<LoggerService>(LOGGER_TOKENS.LoggerService)\n logger.info('Disposing container...')\n\n await this._container.dispose()\n }\n\n async handleCommand(name: string, input?: CommandInput): Promise<CommandResult> {\n // Routing first: commands generate URLs via route() (e.g. tenant:bootstrap\n // prints tenant URLs / builds email links).\n await this.initializeRouting()\n // A CLI command is a non-HTTP scope that can dispatch to queues and emit\n // events from arbitrary user code (e.g. tenant:bootstrap → email.send /\n // tenant.geo.seed). Without this the dev/CLI sync provider finds zero\n // consumers and silently drops every dispatched message. All inits are\n // memoized, so the events half (already wired by routing) is a no-op here.\n await this.ensureScopedHandlers()\n // Resolve QuarryRegistry lazily (deferred from bootstrap)\n this.quarry ??= this._container.resolve<QuarryRegistry>(DI_TOKENS.Quarry)\n const mockContext = this.createMockRouterContext('en')\n return this._container.runInRequestScope(mockContext, async () => {\n return this.quarry.call(name, input)\n })\n }\n\n private initializeRouting(): Promise<void> {\n this.routingInitPromise ??= runWithContainer(this._container, async () => {\n await this.initializeEventListeners()\n await this.ensureI18n()\n const { OpenAPIModule } = await import('./openapi')\n this.moduleRegistry.register(OpenAPIModule as unknown as ModuleClass)\n await this.registerRoutingServices()\n this.honoApp = this._container.resolve<HonoApp>(ROUTER_TOKENS.HonoApp)\n await this.honoApp.configure()\n })\n return this.routingInitPromise\n }\n\n private registerCommands(): void {\n this.quarry ??= this._container.resolve<QuarryRegistry>(DI_TOKENS.Quarry)\n const commands = this.moduleRegistry.getAllCommands()\n for (const CommandClass of commands) {\n this.quarry.register(CommandClass as Constructor<Command>)\n }\n }\n\n private registerSeeders(): void {\n const seeders = this.moduleRegistry.getAllSeeders()\n if (seeders.length === 0) return\n const registry = this._container.resolve<SeederRegistry>(SEEDER_TOKENS.SeederRegistry)\n for (const SeederClass of seeders) {\n registry.register(SeederClass as Constructor<Seeder>)\n }\n }\n\n private async registerQueueConsumers(): Promise<void> {\n const consumerClasses = this.moduleRegistry.getAllConsumers()\n if (consumerClasses.length === 0) return\n\n // Consumers are resolved fresh per message at dispatch time (see\n // QueueManager / SyncQueueProvider). Here we only need each consumer's\n // declared message types; read them from a throwaway instance built inside a\n // request scope so a consumer that injects request-scoped providers\n // (@InjectQueue, i18n, auth context, …) doesn't fail to instantiate at boot.\n const mockContext = this.createMockRouterContext('en')\n await this._container.runInRequestScope(mockContext, (requestContainer) => {\n for (const ConsumerClass of consumerClasses) {\n const consumer = requestContainer.resolve(ConsumerClass) as IQueueConsumer\n this.consumerRegistry.register(\n ConsumerClass as Constructor<IQueueConsumer>,\n consumer.messageTypes,\n )\n }\n })\n }\n\n private registerCronJobs(cronManager: CronManager): void {\n for (const JobClass of this.moduleRegistry.getAllJobs()) {\n const schedule = (JobClass as unknown as { schedule: string }).schedule\n if (schedule) {\n cronManager.registerJob(schedule, JobClass as Constructor<CronJob>)\n } else {\n const logger = this._container.resolve<LoggerService>(LOGGER_TOKENS.LoggerService)\n logger.warn(`Cron job \"${JobClass.name}\" has no static schedule property — skipped`)\n }\n }\n }\n\n private registerEventListeners(): void {\n const listeners = this.moduleRegistry.getAllListeners()\n if (listeners.length === 0) {\n return\n }\n\n const eventRegistry = this._container.resolve<EventRegistry>(DI_TOKENS.EventRegistry)\n\n for (const ListenerClass of listeners) {\n const handlers = getListenerHandlers(ListenerClass)\n\n for (const { methodName, event, options } of handlers) {\n // Resolve the listener fresh per event from the active scope (events are\n // emitted inside an HTTP request or runInScope), so request-scoped\n // listener dependencies bind to the emitting request rather than being\n // frozen at boot. Listener metadata is read from the class statically —\n // no instance is created until an event actually fires.\n const handler: EventHandler = ((...args: unknown[]) => {\n const instance = getContainer().resolve(ListenerClass) as Record<\n string,\n (...a: unknown[]) => unknown\n >\n return instance[methodName](...args)\n }) as EventHandler\n\n eventRegistry.on(event, handler, options)\n }\n }\n }\n\n private registerLoggerService(): void {\n const logLevel = this.appConfig.logging?.level ?? LogLevel.INFO\n const formatter = this.appConfig.logging?.formatter ?? 'json'\n\n this._container.registerValue(LOGGER_TOKENS.LogLevelOptions, logLevel)\n\n this._container\n .when(() => formatter === 'pretty')\n .use(LOGGER_TOKENS.Formatter)\n .give(PrettyFormatter)\n .otherwise(JsonFormatter)\n\n this._container.registerSingleton(LOGGER_TOKENS.LoggerService, LoggerService)\n }\n\n /**\n * Bootstrap kernel — registered imperatively because they cannot be expressed\n * as ordinary module providers: ExceptionHandler is a user-overridable forced\n * singleton (a module ClassProvider derives scope from the class decorator,\n * which a user handler may not carry), and LazyModuleLoader is the loader\n * itself. Subsystem registries (events/cron/quarry/seeder) are modules.\n */\n private registerCoreServices(): void {\n this._container.registerSingleton(\n DI_TOKENS.ExceptionHandler,\n (this.appConfig.exceptionHandler ?? DefaultExceptionHandler) as Constructor,\n )\n this._container.registerSingleton(DI_TOKENS.LazyModuleLoader, LazyModuleLoader)\n }\n\n private initializeExceptionHandler(): void {\n const handler = this._container.resolve<ExceptionHandler>(DI_TOKENS.ExceptionHandler)\n handler.register()\n this.moduleRegistry.configureExceptionHandlers(handler)\n }\n}\n","import { Application, type ApplicationConfig } from './application'\nimport type { StratalEnv } from './env'\nimport { StratalNotInitializedError, StratalSupersededError } from './errors'\nimport type { HonoApp } from './router/hono-app'\n\n/**\n * Stratal — Hono-style entry point for Cloudflare Workers.\n *\n * Eagerly bootstraps the Application at construction time, dynamically\n * importing `cloudflare:workers` for env and waitUntil.\n *\n * @example\n * ```typescript\n * import { Stratal } from 'stratal'\n * import { AppModule } from './app.module'\n *\n * export default new Stratal({ module: AppModule })\n * ```\n */\nexport class Stratal<Env extends StratalEnv = StratalEnv> {\n private app: Application | null = null\n private initPromise: Promise<Application>\n\n private static _application: Promise<Application> | null = null\n private static _generation = 0\n private static _current: Stratal | null = null\n /**\n * Serializes generation teardown: each new instance appends the previous\n * instance's shutdown here, and `prepareApp` awaits the chain before\n * allocating the new Application. Old and new DI graphs never coexist,\n * so repeated Vite HMR reloads can't accumulate live object graphs.\n */\n private static _teardownChain: Promise<void> = Promise.resolve()\n\n constructor(config: ApplicationConfig) {\n this.fetch = this.fetch.bind(this)\n this.queue = this.queue.bind(this)\n this.scheduled = this.scheduled.bind(this)\n\n // Invalidate any in-flight initialization from a previous instance (Vite HMR reload)\n const generation = ++Stratal._generation\n\n const previous = Stratal._current\n Stratal._current = this\n if (previous) {\n Stratal._teardownChain = Stratal._teardownChain\n .then(() => previous.shutdown())\n .catch((error: unknown) => {\n console.error('[stratal] Failed to shut down superseded instance:', error)\n })\n }\n\n // Capture the chain as of THIS construction: it contains only strictly\n // older generations' teardowns. Reading the static inside prepareApp\n // instead would deadlock when this generation is superseded before its\n // first await — it would wait on a chain containing its own shutdown,\n // which waits on this initPromise.\n const teardownBarrier = Stratal._teardownChain\n\n this.initPromise = this.prepareApp(config, generation, teardownBarrier)\n Stratal._application = this.initPromise\n // A superseded generation rejects; without an awaiter (no in-flight\n // request during the reload) that would surface as an unhandled rejection.\n // Anything else is a real bootstrap failure — log it so it isn't invisible\n // until the first request arrives (awaiters still get the rejection).\n this.initPromise.catch((error: unknown) => {\n if (error instanceof StratalSupersededError) return\n console.error('[stratal] Initialization failed:', error)\n })\n }\n\n async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {\n const app = await this.ensureReady()\n const hono = await app.ensureHono()\n return hono.fetch(request, env, ctx)\n }\n\n async queue(batch: MessageBatch): Promise<void> {\n const app = await this.ensureReady()\n return app.handleQueue(batch, batch.queue)\n }\n\n async scheduled(controller: ScheduledController): Promise<void> {\n const app = await this.ensureReady()\n return app.handleScheduled(controller)\n }\n\n get hono(): Promise<HonoApp> {\n return this.ensureReady().then(app => app.ensureHono())\n }\n\n async shutdown(): Promise<void> {\n try { this.app = await this.initPromise } catch { /* ignore */ }\n if (this.app) {\n await this.app.shutdown()\n this.app = null\n }\n // If we're still the live generation (explicit shutdown, not supersession),\n // clear the statics so resolveApplication() throws StratalNotInitializedError\n // instead of handing out the disposed Application.\n if (Stratal._current === this) {\n Stratal._current = null\n Stratal._application = null\n }\n }\n\n /**\n * @internal\n * Resolves the Application instance from the static singleton.\n * Used by worker base classes (DurableObject, Workflow, WorkerEntrypoint)\n * to access the DI container without going through Cloudflare RPC.\n * Generations superseded mid-boot reject with {@link StratalSupersededError};\n * this retries against the replacing generation so callers always land on\n * the live Application.\n */\n static async resolveApplication(): Promise<Application> {\n let current = Stratal._application\n while (current) {\n try {\n return await current\n } catch (error) {\n // Superseded by a newer generation while booting — its constructor has\n // already swapped _application; retry against the replacement.\n if (error instanceof StratalSupersededError && Stratal._application !== current) {\n current = Stratal._application\n continue\n }\n throw error\n }\n }\n throw new StratalNotInitializedError()\n }\n\n private async ensureReady(): Promise<Application> {\n if (this.app) return this.app\n try {\n this.app = await this.initPromise\n return this.app\n } catch (error) {\n if (error instanceof StratalSupersededError) {\n // This instance was replaced mid-boot (Vite HMR reload) — serve the\n // request from the generation that replaced it.\n return Stratal.resolveApplication()\n }\n throw error\n }\n }\n\n private async prepareApp(\n config: ApplicationConfig,\n generation: number,\n teardownBarrier: Promise<void>,\n ): Promise<Application> {\n const { env, waitUntil } = await import('cloudflare:workers')\n\n // Let every previous generation finish tearing down before allocating the\n // new DI graph — concurrent old+new graphs are what leak across reloads.\n await teardownBarrier\n\n // After the awaits, check if a newer instance has replaced us (Vite HMR reload)\n if (generation !== Stratal._generation) {\n throw new StratalSupersededError(generation)\n }\n\n const app = new Application({ ...config, env: env as Env, ctx: { waitUntil } })\n await app.initialize()\n\n // Check again after initialization completes\n if (generation !== Stratal._generation) {\n await app.shutdown()\n throw new StratalSupersededError(generation)\n }\n\n return app\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Sn as HonoApp, ft as Application, pt as ApplicationConfig } from "./index-uybm0bhQ.mjs";
|
|
2
2
|
import { t as StratalEnv } from "./env-ug22bJj7.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/stratal.d.ts
|
|
@@ -21,7 +21,14 @@ declare class Stratal<Env extends StratalEnv = StratalEnv> {
|
|
|
21
21
|
private initPromise;
|
|
22
22
|
private static _application;
|
|
23
23
|
private static _generation;
|
|
24
|
-
private static
|
|
24
|
+
private static _current;
|
|
25
|
+
/**
|
|
26
|
+
* Serializes generation teardown: each new instance appends the previous
|
|
27
|
+
* instance's shutdown here, and `prepareApp` awaits the chain before
|
|
28
|
+
* allocating the new Application. Old and new DI graphs never coexist,
|
|
29
|
+
* so repeated Vite HMR reloads can't accumulate live object graphs.
|
|
30
|
+
*/
|
|
31
|
+
private static _teardownChain;
|
|
25
32
|
constructor(config: ApplicationConfig);
|
|
26
33
|
fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response>;
|
|
27
34
|
queue(batch: MessageBatch): Promise<void>;
|
|
@@ -33,6 +40,9 @@ declare class Stratal<Env extends StratalEnv = StratalEnv> {
|
|
|
33
40
|
* Resolves the Application instance from the static singleton.
|
|
34
41
|
* Used by worker base classes (DurableObject, Workflow, WorkerEntrypoint)
|
|
35
42
|
* to access the DI container without going through Cloudflare RPC.
|
|
43
|
+
* Generations superseded mid-boot reject with {@link StratalSupersededError};
|
|
44
|
+
* this retries against the replacing generation so callers always land on
|
|
45
|
+
* the live Application.
|
|
36
46
|
*/
|
|
37
47
|
static resolveApplication(): Promise<Application>;
|
|
38
48
|
private ensureReady;
|
|
@@ -40,4 +50,4 @@ declare class Stratal<Env extends StratalEnv = StratalEnv> {
|
|
|
40
50
|
}
|
|
41
51
|
//#endregion
|
|
42
52
|
export { Stratal as t };
|
|
43
|
-
//# sourceMappingURL=stratal-
|
|
53
|
+
//# sourceMappingURL=stratal-D5j_I14G.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stratal-D5j_I14G.d.mts","names":[],"sources":["../src/stratal.ts"],"mappings":";;;;;AAmBA;;;;;;;;;;;;;cAAa,OAAA,aAAoB,UAAA,GAAa,UAAA;EAAA,QACpC,GAAA;EAAA,QACA,WAAA;EAAA,eAEO,YAAA;EAAA,eACA,WAAA;EAAA,eACA,QAAA;EA0FoB;;;;;;EAAA,eAnFpB,cAAA;cAEH,MAAA,EAAQ,iBAAA;EAqCd,KAAA,CAAM,OAAA,EAAS,OAAA,EAAS,GAAA,EAAK,GAAA,EAAK,GAAA,EAAK,gBAAA,GAAmB,OAAA,CAAQ,QAAA;EAMlE,KAAA,CAAM,KAAA,EAAO,YAAA,GAAe,OAAA;EAK5B,SAAA,CAAU,UAAA,EAAY,mBAAA,GAAsB,OAAA;EAAA,IAK9C,IAAA,IAAQ,OAAA,CAAQ,OAAA;EAId,QAAA,IAAY,OAAA;EAzDE;;;;;;;;;EAAA,OAiFP,kBAAA,IAAsB,OAAA,CAAQ,WAAA;EAAA,QAkB7B,WAAA;EAAA,QAeA,UAAA;AAAA"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { r as stripLocalePrefix } from "./locale-url-nZrZxqJP.mjs";
|
|
2
|
+
//#region src/router/trailing-slash.ts
|
|
3
|
+
/** Normalise the `trailingSlash` config (bare mode or `{ mode, exclude }`). */
|
|
4
|
+
function resolveTrailingSlash(config) {
|
|
5
|
+
if (!config) return { mode: "ignore" };
|
|
6
|
+
if (typeof config === "string") return { mode: config };
|
|
7
|
+
return config;
|
|
8
|
+
}
|
|
9
|
+
/** Strip a single trailing `/` from a path. Root (`/`) is left untouched. */
|
|
10
|
+
const stripTrailingSlash = (path) => path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
|
|
11
|
+
/**
|
|
12
|
+
* Whether a pathname is exempt from trailing-slash canonicalisation.
|
|
13
|
+
*
|
|
14
|
+
* String patterns are segment-aware prefixes. RegExps are tested against
|
|
15
|
+
* both forms of the pathname (with and without the trailing slash), so a
|
|
16
|
+
* pattern anchored to either form exempts both — same guarantee as strings.
|
|
17
|
+
*
|
|
18
|
+
* When `locales` is given (path-based locale detection), a leading locale
|
|
19
|
+
* segment is stripped and the bare path matched too, so `'/callback'` also
|
|
20
|
+
* exempts `/fr/callback` — exclusions are written in route space, like the
|
|
21
|
+
* routes themselves.
|
|
22
|
+
*/
|
|
23
|
+
function isTrailingSlashExcluded(pathname, exclude, locales) {
|
|
24
|
+
if (!exclude?.length) return false;
|
|
25
|
+
const candidates = [pathname];
|
|
26
|
+
if (locales?.length) {
|
|
27
|
+
const stripped = stripLocalePrefix(pathname, locales);
|
|
28
|
+
if (stripped !== pathname) candidates.push(stripped);
|
|
29
|
+
}
|
|
30
|
+
return candidates.some((candidate) => {
|
|
31
|
+
const bare = stripTrailingSlash(candidate);
|
|
32
|
+
const slashed = bare === "/" ? bare : `${bare}/`;
|
|
33
|
+
return exclude.some((pattern) => {
|
|
34
|
+
if (typeof pattern !== "string") return pattern.test(bare) || pattern.test(slashed);
|
|
35
|
+
const prefix = stripTrailingSlash(pattern);
|
|
36
|
+
return bare === prefix || bare.startsWith(`${prefix}/`);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Apply a trailing-slash config to a URL or path.
|
|
42
|
+
*
|
|
43
|
+
* - `'ignore'` — return as-is.
|
|
44
|
+
* - `'always'` — append `/` to the pathname unless it already has one.
|
|
45
|
+
* Skipped when the last segment contains `.` (file-like paths) and for the
|
|
46
|
+
* root `/` path.
|
|
47
|
+
* - `'never'` — strip a trailing `/` from the pathname. Skipped for root.
|
|
48
|
+
*
|
|
49
|
+
* Paths matching the config's `exclude` list are returned as-is — their
|
|
50
|
+
* canonical form is owned elsewhere (e.g. an OAuth redirect URI registered
|
|
51
|
+
* with an IdP and matched byte-for-byte). Pass `locales` when path-based
|
|
52
|
+
* locale detection is active so locale-prefixed forms of excluded paths are
|
|
53
|
+
* exempt too (see {@link isTrailingSlashExcluded}).
|
|
54
|
+
*
|
|
55
|
+
* Preserves query string and hash. Handles both relative paths
|
|
56
|
+
* (`/foo?x=1`) and absolute URLs (`https://host/foo?x=1`).
|
|
57
|
+
*
|
|
58
|
+
* Used by URL-generation helpers and the redirect middleware so canonical
|
|
59
|
+
* form is computed in one place.
|
|
60
|
+
*/
|
|
61
|
+
function applyTrailingSlash(url, config, locales) {
|
|
62
|
+
const { mode, exclude } = resolveTrailingSlash(config);
|
|
63
|
+
if (mode === "ignore") return url;
|
|
64
|
+
const isAbsolute = /^https?:\/\//i.test(url);
|
|
65
|
+
const parsed = isAbsolute ? new URL(url) : new URL(url, "http://placeholder.local");
|
|
66
|
+
const path = parsed.pathname;
|
|
67
|
+
if (path === "/") return url;
|
|
68
|
+
if (isTrailingSlashExcluded(path, exclude, locales)) return url;
|
|
69
|
+
const hasTrailing = path.endsWith("/");
|
|
70
|
+
if (mode === "always" && !hasTrailing) {
|
|
71
|
+
if (path.slice(path.lastIndexOf("/") + 1).includes(".")) return url;
|
|
72
|
+
parsed.pathname = `${path}/`;
|
|
73
|
+
} else if (mode === "never" && hasTrailing) parsed.pathname = path.slice(0, -1);
|
|
74
|
+
else return url;
|
|
75
|
+
return isAbsolute ? parsed.toString() : `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { isTrailingSlashExcluded as n, resolveTrailingSlash as r, applyTrailingSlash as t };
|
|
79
|
+
|
|
80
|
+
//# sourceMappingURL=trailing-slash-2SctvePW.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trailing-slash-2SctvePW.mjs","names":[],"sources":["../src/router/trailing-slash.ts"],"sourcesContent":["import { stripLocalePrefix } from './locale-url'\nimport type { TrailingSlashConfig, TrailingSlashExclude, TrailingSlashOptions } from './types'\n\n/** Normalise the `trailingSlash` config (bare mode or `{ mode, exclude }`). */\nexport function resolveTrailingSlash(config: TrailingSlashConfig | undefined): TrailingSlashOptions {\n if (!config) return { mode: 'ignore' }\n if (typeof config === 'string') return { mode: config }\n return config\n}\n\n/** Strip a single trailing `/` from a path. Root (`/`) is left untouched. */\nconst stripTrailingSlash = (path: string): string =>\n path.length > 1 && path.endsWith('/') ? path.slice(0, -1) : path\n\n/**\n * Whether a pathname is exempt from trailing-slash canonicalisation.\n *\n * String patterns are segment-aware prefixes. RegExps are tested against\n * both forms of the pathname (with and without the trailing slash), so a\n * pattern anchored to either form exempts both — same guarantee as strings.\n *\n * When `locales` is given (path-based locale detection), a leading locale\n * segment is stripped and the bare path matched too, so `'/callback'` also\n * exempts `/fr/callback` — exclusions are written in route space, like the\n * routes themselves.\n */\nexport function isTrailingSlashExcluded(\n pathname: string,\n exclude: readonly TrailingSlashExclude[] | undefined,\n locales?: readonly string[],\n): boolean {\n if (!exclude?.length) return false\n\n const candidates = [pathname]\n if (locales?.length) {\n const stripped = stripLocalePrefix(pathname, locales)\n if (stripped !== pathname) candidates.push(stripped)\n }\n\n return candidates.some((candidate) => {\n const bare = stripTrailingSlash(candidate)\n const slashed = bare === '/' ? bare : `${bare}/`\n return exclude.some((pattern) => {\n if (typeof pattern !== 'string') return pattern.test(bare) || pattern.test(slashed)\n const prefix = stripTrailingSlash(pattern)\n return bare === prefix || bare.startsWith(`${prefix}/`)\n })\n })\n}\n\n/**\n * Apply a trailing-slash config to a URL or path.\n *\n * - `'ignore'` — return as-is.\n * - `'always'` — append `/` to the pathname unless it already has one.\n * Skipped when the last segment contains `.` (file-like paths) and for the\n * root `/` path.\n * - `'never'` — strip a trailing `/` from the pathname. Skipped for root.\n *\n * Paths matching the config's `exclude` list are returned as-is — their\n * canonical form is owned elsewhere (e.g. an OAuth redirect URI registered\n * with an IdP and matched byte-for-byte). Pass `locales` when path-based\n * locale detection is active so locale-prefixed forms of excluded paths are\n * exempt too (see {@link isTrailingSlashExcluded}).\n *\n * Preserves query string and hash. Handles both relative paths\n * (`/foo?x=1`) and absolute URLs (`https://host/foo?x=1`).\n *\n * Used by URL-generation helpers and the redirect middleware so canonical\n * form is computed in one place.\n */\nexport function applyTrailingSlash(url: string, config: TrailingSlashConfig, locales?: readonly string[]): string {\n const { mode, exclude } = resolveTrailingSlash(config)\n if (mode === 'ignore') return url\n\n const isAbsolute = /^https?:\\/\\//i.test(url)\n const parsed = isAbsolute\n ? new URL(url)\n : new URL(url, 'http://placeholder.local')\n\n const path = parsed.pathname\n if (path === '/') return url\n if (isTrailingSlashExcluded(path, exclude, locales)) return url\n\n const hasTrailing = path.endsWith('/')\n\n if (mode === 'always' && !hasTrailing) {\n const lastSegment = path.slice(path.lastIndexOf('/') + 1)\n if (lastSegment.includes('.')) return url\n parsed.pathname = `${path}/`\n } else if (mode === 'never' && hasTrailing) {\n parsed.pathname = path.slice(0, -1)\n } else {\n return url\n }\n\n return isAbsolute\n ? parsed.toString()\n : `${parsed.pathname}${parsed.search}${parsed.hash}`\n}\n"],"mappings":";;;AAIA,SAAgB,qBAAqB,QAA+D;CAClG,IAAI,CAAC,QAAQ,OAAO,EAAE,MAAM,SAAS;CACrC,IAAI,OAAO,WAAW,UAAU,OAAO,EAAE,MAAM,OAAO;CACtD,OAAO;AACT;;AAGA,MAAM,sBAAsB,SAC1B,KAAK,SAAS,KAAK,KAAK,SAAS,GAAG,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI;;;;;;;;;;;;;AAc9D,SAAgB,wBACd,UACA,SACA,SACS;CACT,IAAI,CAAC,SAAS,QAAQ,OAAO;CAE7B,MAAM,aAAa,CAAC,QAAQ;CAC5B,IAAI,SAAS,QAAQ;EACnB,MAAM,WAAW,kBAAkB,UAAU,OAAO;EACpD,IAAI,aAAa,UAAU,WAAW,KAAK,QAAQ;CACrD;CAEA,OAAO,WAAW,MAAM,cAAc;EACpC,MAAM,OAAO,mBAAmB,SAAS;EACzC,MAAM,UAAU,SAAS,MAAM,OAAO,GAAG,KAAK;EAC9C,OAAO,QAAQ,MAAM,YAAY;GAC/B,IAAI,OAAO,YAAY,UAAU,OAAO,QAAQ,KAAK,IAAI,KAAK,QAAQ,KAAK,OAAO;GAClF,MAAM,SAAS,mBAAmB,OAAO;GACzC,OAAO,SAAS,UAAU,KAAK,WAAW,GAAG,OAAO,EAAE;EACxD,CAAC;CACH,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,mBAAmB,KAAa,QAA6B,SAAqC;CAChH,MAAM,EAAE,MAAM,YAAY,qBAAqB,MAAM;CACrD,IAAI,SAAS,UAAU,OAAO;CAE9B,MAAM,aAAa,gBAAgB,KAAK,GAAG;CAC3C,MAAM,SAAS,aACX,IAAI,IAAI,GAAG,IACX,IAAI,IAAI,KAAK,0BAA0B;CAE3C,MAAM,OAAO,OAAO;CACpB,IAAI,SAAS,KAAK,OAAO;CACzB,IAAI,wBAAwB,MAAM,SAAS,OAAO,GAAG,OAAO;CAE5D,MAAM,cAAc,KAAK,SAAS,GAAG;CAErC,IAAI,SAAS,YAAY,CAAC,aAAa;EAErC,IADoB,KAAK,MAAM,KAAK,YAAY,GAAG,IAAI,CACzC,EAAE,SAAS,GAAG,GAAG,OAAO;EACtC,OAAO,WAAW,GAAG,KAAK;CAC5B,OAAO,IAAI,SAAS,WAAW,aAC7B,OAAO,WAAW,KAAK,MAAM,GAAG,EAAE;MAElC,OAAO;CAGT,OAAO,aACH,OAAO,SAAS,IAChB,GAAG,OAAO,WAAW,OAAO,SAAS,OAAO;AAClD"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-BBjsoOtd.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { b as ROUTER_TOKENS, c as Request, p as inject, r as DI_TOKENS } from "./di-D7qmrAir.mjs";
|
|
3
3
|
import { n as __decorateParam, t as __decorate } from "./decorate-CuAoSZvs.mjs";
|
|
4
|
-
import { o as RouterError } from "./module-registry-
|
|
5
|
-
import { t as applyTrailingSlash } from "./trailing-slash-CFyw8nYu.mjs";
|
|
4
|
+
import { o as RouterError } from "./module-registry-NxX5O0Qk.mjs";
|
|
6
5
|
import { t as applyLocalePrefix } from "./locale-url-nZrZxqJP.mjs";
|
|
6
|
+
import { r as resolveTrailingSlash, t as applyTrailingSlash } from "./trailing-slash-2SctvePW.mjs";
|
|
7
7
|
import { n as verifySignedUrl, t as signUrl } from "./signed-url-DIU0sK_6.mjs";
|
|
8
8
|
//#region src/router/uri.ts
|
|
9
9
|
var uri_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -70,11 +70,13 @@ let Uri = class Uri {
|
|
|
70
70
|
routerContext;
|
|
71
71
|
_defaults = {};
|
|
72
72
|
trailingSlash;
|
|
73
|
+
locales;
|
|
73
74
|
localeConfig;
|
|
74
75
|
constructor(registry, routerContext, application, localePathService) {
|
|
75
76
|
this.registry = registry;
|
|
76
77
|
this.routerContext = routerContext;
|
|
77
|
-
this.trailingSlash = application.config.trailingSlash
|
|
78
|
+
this.trailingSlash = resolveTrailingSlash(application.config.trailingSlash);
|
|
79
|
+
this.locales = localePathService.localePathConfig?.allLocales;
|
|
78
80
|
this.localeConfig = {
|
|
79
81
|
defaultLocale: localePathService.localePathConfig?.defaultLocale ?? null,
|
|
80
82
|
prefixDefaultLocale: localePathService.prefixDefaultLocale
|
|
@@ -123,7 +125,7 @@ let Uri = class Uri {
|
|
|
123
125
|
let url = applyTrailingSlash(buildRouteUrl(registeredRoute, name, {
|
|
124
126
|
...this._defaults,
|
|
125
127
|
...params
|
|
126
|
-
}, this.localeConfig), this.trailingSlash);
|
|
128
|
+
}, this.localeConfig), this.trailingSlash, this.locales);
|
|
127
129
|
if (options?.absolute && !url.startsWith("http")) url = `${new URL(this.routerContext.c.req.url).origin}${url}`;
|
|
128
130
|
return url;
|
|
129
131
|
}
|
|
@@ -171,14 +173,14 @@ let Uri = class Uri {
|
|
|
171
173
|
* Get the current request URL pathname (without query string).
|
|
172
174
|
*/
|
|
173
175
|
current() {
|
|
174
|
-
return applyTrailingSlash(new URL(this.routerContext.c.req.url).pathname, this.trailingSlash);
|
|
176
|
+
return applyTrailingSlash(new URL(this.routerContext.c.req.url).pathname, this.trailingSlash, this.locales);
|
|
175
177
|
}
|
|
176
178
|
/**
|
|
177
179
|
* Get the current request URL with query string (pathname + search).
|
|
178
180
|
*/
|
|
179
181
|
full() {
|
|
180
182
|
const parsed = new URL(this.routerContext.c.req.url);
|
|
181
|
-
return applyTrailingSlash(`${parsed.pathname}${parsed.search}`, this.trailingSlash);
|
|
183
|
+
return applyTrailingSlash(`${parsed.pathname}${parsed.search}`, this.trailingSlash, this.locales);
|
|
182
184
|
}
|
|
183
185
|
/**
|
|
184
186
|
* Get the previous request URL from the Referer header.
|
|
@@ -210,7 +212,7 @@ let Uri = class Uri {
|
|
|
210
212
|
* @param options - URL generation options
|
|
211
213
|
*/
|
|
212
214
|
to(path, queryParams, options) {
|
|
213
|
-
let url = applyTrailingSlash(path, this.trailingSlash);
|
|
215
|
+
let url = applyTrailingSlash(path, this.trailingSlash, this.locales);
|
|
214
216
|
if (queryParams) {
|
|
215
217
|
const entries = Object.entries(queryParams);
|
|
216
218
|
if (entries.length > 0) {
|
|
@@ -230,7 +232,7 @@ let Uri = class Uri {
|
|
|
230
232
|
query(path, queryParams) {
|
|
231
233
|
const parsed = new URL(path, "https://placeholder.local");
|
|
232
234
|
for (const [key, value] of Object.entries(queryParams)) parsed.searchParams.set(key, value);
|
|
233
|
-
return applyTrailingSlash(`${parsed.pathname}${parsed.search}`, this.trailingSlash);
|
|
235
|
+
return applyTrailingSlash(`${parsed.pathname}${parsed.search}`, this.trailingSlash, this.locales);
|
|
234
236
|
}
|
|
235
237
|
getAppSecret() {
|
|
236
238
|
const secret = this.routerContext.c.env.APP_SECRET;
|
|
@@ -248,4 +250,4 @@ Uri = __decorate([
|
|
|
248
250
|
//#endregion
|
|
249
251
|
export { buildRouteUrl as n, uri_exports as r, Uri as t };
|
|
250
252
|
|
|
251
|
-
//# sourceMappingURL=uri-
|
|
253
|
+
//# sourceMappingURL=uri-iwofWJ_T.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uri-iwofWJ_T.mjs","names":[],"sources":["../src/router/uri.ts"],"sourcesContent":["import type { Application } from '../application';\nimport { inject } from '../di';\nimport { Request } from '../di/decorators';\nimport { DI_TOKENS } from '../di/tokens';\nimport { applyLocalePrefix } from './locale-url';\nimport type { RouteName, RouteParams } from './route-map';\nimport type { RegisteredRoute, RouteRegistry } from './route-registry';\nimport type { RouterContext } from './router-context';\nimport { RouterError } from './router.error';\nimport { ROUTER_TOKENS } from './router.tokens';\nimport type { LocalePathService } from './services/locale-path.service';\nimport { signUrl, verifySignedUrl, type SignedUrlOptions } from './signed-url';\nimport { applyTrailingSlash, resolveTrailingSlash } from './trailing-slash';\nimport type { LocaleUrlConfig, TrailingSlashOptions } from './types';\n\n/**\n * Options for URL generation methods.\n */\nexport interface UriOptions {\n /** Generate absolute URL (scheme + host). Defaults to false. */\n absolute?: boolean\n}\n\n/**\n * Options for signed URL generation methods.\n */\nexport interface SignedUriOptions extends UriOptions, SignedUrlOptions { }\n\n/**\n * Encode a value for use as a path parameter.\n *\n * Splits on `/` and encodes each segment with `encodeURIComponent`, so callers\n * can pass slash-containing values for catch-all params (e.g. `:slug{.+}`) and\n * still get a usable URL — `'auth/login'` becomes `'auth/login'`, not\n * `'auth%2Flogin'`. Single segments behave exactly like `encodeURIComponent`.\n */\nfunction encodePathParam(value: string): string {\n return value.split('/').map(encodeURIComponent).join('/')\n}\n\n/**\n * Build a URL from a registered route, filling path/domain params and appending extras as query string.\n *\n * Pure function — no request context needed. Used by both the `Uri` class and the standalone `route()` function.\n *\n * @param route - The registered route to build a URL for\n * @param name - Route name (used in error messages)\n * @param params - Path params, domain params, and extra query params\n * @returns Relative URL string (or absolute with domain prefix if route has a domain pattern)\n *\n * @throws RouterError if a required path or domain param is missing\n */\nexport function buildRouteUrl(\n route: RegisteredRoute,\n name: string,\n params?: Record<string, string>,\n localeConfig?: LocaleUrlConfig,\n): string {\n const allParams = { ...params }\n const consumedKeys = new Set<string>()\n let url = route.path\n\n if (allParams.locale && route.localePaths?.length) {\n url = applyLocalePrefix(url, allParams.locale, localeConfig)\n consumedKeys.add('locale')\n }\n\n // Fill path :param placeholders (handles optional regex constraints like :locale{en|de|fr})\n for (const paramName of route.paramNames) {\n const value = allParams[paramName]\n if (value === undefined) {\n throw new RouterError(`Missing required route parameter \"${paramName}\" for route \"${name}\" (path: ${route.path})`)\n }\n url = url.replace(\n new RegExp(`:${paramName}(\\\\{[^}]*\\\\})?`),\n encodePathParam(value),\n )\n consumedKeys.add(paramName)\n }\n\n // Build domain if present\n let domain: string | undefined\n if (route.domain) {\n domain = route.domain\n for (const domainParam of route.domainParamNames) {\n const value = allParams[domainParam]\n if (value === undefined) {\n throw new RouterError(`Missing required domain parameter \"${domainParam}\" for route \"${name}\" (domain: ${route.domain})`)\n }\n domain = domain.replace(`{${domainParam}}`, encodeURIComponent(value))\n consumedKeys.add(domainParam)\n }\n }\n\n // Remaining params (not consumed by path or domain) become query string\n const queryEntries = Object.entries(allParams).filter(([key]) => !consumedKeys.has(key))\n if (queryEntries.length > 0) {\n const queryString = queryEntries\n .filter(([, value]) => Boolean(value))\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)\n .join('&')\n url = `${url}${queryString.length ? `?${queryString}` : ''}`\n }\n\n // Prepend domain if present\n if (domain) {\n url = `https://${domain}${url}`\n }\n\n return url\n}\n\n/**\n * URL generation service for named routes, signed URLs, and request URL access.\n *\n * Registered as request-scoped in the container — has access to the current request\n * via RouterContext for features like `current()`, `full()`, and signed URLs.\n *\n * @example\n * ```typescript\n * // In a controller:\n * const uri = ctx.getContainer().resolve<Uri>(ROUTER_TOKENS.Uri)\n * uri.route('users.show', { id: '1' })\n * uri.current()\n * await uri.signedRoute('unsubscribe', { user: '1' }, { expiresIn: 3600 })\n *\n * // Set defaults (e.g., in middleware):\n * uri.defaults({ locale: 'en' })\n * uri.route('posts.index') // auto-fills :locale param\n * ```\n */\n@Request(ROUTER_TOKENS.Uri)\nexport class Uri {\n private _defaults: Record<string, string> = {}\n private readonly trailingSlash: TrailingSlashOptions\n private readonly locales?: readonly string[]\n private readonly localeConfig: LocaleUrlConfig\n\n constructor(\n @inject(ROUTER_TOKENS.RouteRegistry) private readonly registry: RouteRegistry,\n @inject(ROUTER_TOKENS.RouterContext) private readonly routerContext: RouterContext,\n @inject(DI_TOKENS.Application) application: Application,\n @inject(ROUTER_TOKENS.LocalePathService) localePathService: LocalePathService,\n ) {\n // Resolved once — applyTrailingSlash accepts the resolved form directly.\n this.trailingSlash = resolveTrailingSlash(application.config.trailingSlash)\n this.locales = localePathService.localePathConfig?.allLocales\n this.localeConfig = {\n defaultLocale: localePathService.localePathConfig?.defaultLocale ?? null,\n prefixDefaultLocale: localePathService.prefixDefaultLocale,\n }\n }\n\n /**\n * Set default URL parameters for this request.\n * Applied to all subsequent `route()` calls — explicit params override defaults.\n *\n * @param params - Default parameters (e.g., `{ locale: 'en' }`)\n */\n defaults(params: Record<string, string>): void {\n this._defaults = { ...this._defaults, ...params }\n }\n\n /**\n * Read the currently configured default URL parameters.\n *\n * Used by frameworks that need to share these with the client (e.g. the\n * Inertia adapter ships them as a shared prop so `route()` calls in the\n * browser auto-fill the same sticky params as the server).\n */\n getDefaults(): Record<string, string> {\n return { ...this._defaults }\n }\n\n /**\n * Generate a URL from a named route.\n *\n * Keys matching `:param` placeholders fill the path.\n * Domain params (`{tenant}`) are consumed from the same object.\n * Extra keys become query string parameters.\n * Default params (from `defaults()`) are merged — explicit params override.\n *\n * @param name - Named route identifier\n * @param params - Route params + domain params + extra query params\n * @param options - URL generation options\n * @returns Generated URL string\n *\n * @throws RouterError if route name not found or required params missing\n */\n route<N extends RouteName>(name: N, params?: RouteParams<N>, options?: UriOptions): string {\n const registeredRoute = this.registry.get(name)\n if (!registeredRoute) {\n throw new RouterError(`Route name \"${name}\" was not found in the registry`)\n }\n\n const mergedParams = { ...this._defaults, ...params } as Record<string, string>\n let url = applyTrailingSlash(buildRouteUrl(registeredRoute, name, mergedParams, this.localeConfig), this.trailingSlash, this.locales)\n\n if (options?.absolute && !url.startsWith('http')) {\n const origin = new URL(this.routerContext.c.req.url).origin\n url = `${origin}${url}`\n }\n\n return url\n }\n\n /**\n * Generate a signed URL from a named route.\n *\n * @param name - Named route identifier\n * @param params - Route params + domain params + extra query params\n * @param options - Signing options (e.g., expiresIn) and URL options\n * @returns Signed URL string with signature query param\n *\n * @throws Error if APP_SECRET environment variable is not set\n */\n async signedRoute<N extends RouteName>(name: N, params?: RouteParams<N>, options?: SignedUriOptions): Promise<string> {\n const url = this.route(name, params, options)\n const secret = this.getAppSecret()\n return signUrl(url, secret, options)\n }\n\n /**\n * Generate a temporary signed URL from a named route.\n *\n * @param name - Named route identifier\n * @param expiresIn - Time-to-live in seconds\n * @param params - Route params + domain params + extra query params\n * @param options - URL generation options\n * @returns Signed URL string with signature and expires query params\n *\n * @throws Error if APP_SECRET environment variable is not set\n */\n async temporarySignedRoute<N extends RouteName>(name: N, expiresIn: number, params?: RouteParams<N>, options?: UriOptions): Promise<string> {\n return this.signedRoute(name, params, { ...options, expiresIn })\n }\n\n /**\n * Check if the current request has a valid signature.\n *\n * @returns true if the URL signature is valid and not expired\n */\n async hasValidSignature(): Promise<boolean> {\n const secret = (this.routerContext.c.env as unknown as Record<string, string>).APP_SECRET\n if (!secret) return false\n return verifySignedUrl(this.routerContext.c.req.url, secret)\n }\n\n /**\n * Get the current request URL pathname (without query string).\n */\n current(): string {\n const parsed = new URL(this.routerContext.c.req.url)\n return applyTrailingSlash(parsed.pathname, this.trailingSlash, this.locales)\n }\n\n /**\n * Get the current request URL with query string (pathname + search).\n */\n full(): string {\n const parsed = new URL(this.routerContext.c.req.url)\n return applyTrailingSlash(`${parsed.pathname}${parsed.search}`, this.trailingSlash, this.locales)\n }\n\n /**\n * Get the previous request URL from the Referer header.\n *\n * @param fallback - URL to return if no Referer header (default: '/')\n */\n previous(fallback = '/'): string {\n return this.routerContext.c.req.header('referer') ?? fallback\n }\n\n /**\n * Get the previous request URL pathname (no query string or host) from the Referer header.\n *\n * @param fallback - Path to return if no Referer header (default: '/')\n */\n previousPath(fallback = '/'): string {\n const referer = this.routerContext.c.req.header('referer')\n if (!referer) return fallback\n\n try {\n const parsed = new URL(referer)\n return parsed.pathname\n } catch {\n return referer\n }\n }\n\n /**\n * Build a URL to a raw path (not a named route) with optional query params.\n *\n * @param path - URL path (e.g., '/users')\n * @param queryParams - Query parameters to append\n * @param options - URL generation options\n */\n to(path: string, queryParams?: Record<string, string>, options?: UriOptions): string {\n let url = applyTrailingSlash(path, this.trailingSlash, this.locales)\n\n if (queryParams) {\n const entries = Object.entries(queryParams)\n if (entries.length > 0) {\n const queryString = entries\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)\n .join('&')\n url = url.includes('?') ? `${url}&${queryString}` : `${url}?${queryString}`\n }\n }\n\n if (options?.absolute && !url.startsWith('http')) {\n const origin = new URL(this.routerContext.c.req.url).origin\n url = `${origin}${url}`\n }\n\n return url\n }\n\n /**\n * Build a URL with query string parameters. Merges with existing query params in path.\n *\n * @param path - URL path, may already contain query params\n * @param queryParams - Query parameters to merge (new values override existing)\n */\n query(path: string, queryParams: Record<string, string>): string {\n const parsed = new URL(path, 'https://placeholder.local')\n for (const [key, value] of Object.entries(queryParams)) {\n parsed.searchParams.set(key, value)\n }\n return applyTrailingSlash(`${parsed.pathname}${parsed.search}`, this.trailingSlash, this.locales)\n }\n\n private getAppSecret(): string {\n const secret = this.routerContext.c.env.APP_SECRET\n if (!secret) {\n throw new Error('APP_SECRET environment variable is required for signed URLs')\n }\n return secret\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-BBjsoOtd.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { l as Singleton, p as inject, r as DI_TOKENS } from "./di-D7qmrAir.mjs";
|
|
3
3
|
import { n as __decorateParam, t as __decorate } from "./decorate-CuAoSZvs.mjs";
|
|
4
|
-
import { f as VERSION_NEUTRAL } from "./exception-context-
|
|
4
|
+
import { f as VERSION_NEUTRAL } from "./exception-context-D-kvney-.mjs";
|
|
5
5
|
//#region src/router/services/versioning.service.ts
|
|
6
6
|
var versioning_service_exports = /* @__PURE__ */ __exportAll({ VersioningService: () => VersioningService });
|
|
7
7
|
let VersioningService = class VersioningService {
|
|
@@ -33,4 +33,4 @@ VersioningService = __decorate([Singleton(), __decorateParam(0, inject(DI_TOKENS
|
|
|
33
33
|
//#endregion
|
|
34
34
|
export { versioning_service_exports as n, VersioningService as t };
|
|
35
35
|
|
|
36
|
-
//# sourceMappingURL=versioning.service-
|
|
36
|
+
//# sourceMappingURL=versioning.service-CCa2oYMJ.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"versioning.service-
|
|
1
|
+
{"version":3,"file":"versioning.service-CCa2oYMJ.mjs","names":[],"sources":["../src/router/services/versioning.service.ts"],"sourcesContent":["import { inject } from '../../di'\nimport { Singleton } from '../../di/decorators'\nimport { DI_TOKENS } from '../../di/tokens'\nimport type { Application } from '../../application'\nimport { VERSION_NEUTRAL } from '../constants'\nimport type { VersioningOptions } from '../types'\n\n/**\n * Resolves version prefixes for route paths.\n *\n * Handles VERSION_NEUTRAL, multi-version arrays, default version fallback,\n * and configurable prefix (default: 'v').\n *\n * Registered as a singleton in the container.\n */\n@Singleton()\nexport class VersioningService {\n private readonly options: VersioningOptions | null\n\n constructor(@inject(DI_TOKENS.Application) app: Application) {\n this.options = app.config.versioning ?? null\n }\n\n /** Whether versioning is enabled */\n get enabled(): boolean {\n return this.options !== null\n }\n\n /**\n * Resolve versioned paths for a base path.\n *\n * @param basePath - The base path (e.g., '/users')\n * @param version - Explicit version from controller/router config\n * @returns Array of versioned path strings (e.g., ['/v1/users', '/v2/users'])\n */\n resolve(basePath: string, version?: string | string[] | typeof VERSION_NEUTRAL): string[] {\n // Versioning disabled — return base path as-is\n if (!this.options) {\n return [basePath]\n }\n\n // VERSION_NEUTRAL — explicitly opt out of versioning\n if (version === VERSION_NEUTRAL) {\n return [basePath]\n }\n\n const prefix = this.options.prefix ?? 'v'\n\n // Explicit version(s) on the controller/router\n if (version !== undefined) {\n const versions = Array.isArray(version) ? version : [version]\n return versions.map(v => `/${prefix}${v}${basePath}`)\n }\n\n // No explicit version — apply defaultVersion if set\n if (this.options.defaultVersion !== undefined) {\n const defaults = Array.isArray(this.options.defaultVersion)\n ? this.options.defaultVersion\n : [this.options.defaultVersion]\n return defaults.map(v => `/${prefix}${v}${basePath}`)\n }\n\n // Versioning enabled but no version and no default — no prefix\n return [basePath]\n }\n}\n"],"mappings":";;;;;;AAgBO,IAAA,oBAAA,MAAM,kBAAkB;CAC7B;CAEA,YAAY,KAAiD;EAC3D,KAAK,UAAU,IAAI,OAAO,cAAc;CAC1C;;CAGA,IAAI,UAAmB;EACrB,OAAO,KAAK,YAAY;CAC1B;;;;;;;;CASA,QAAQ,UAAkB,SAAgE;EAExF,IAAI,CAAC,KAAK,SACR,OAAO,CAAC,QAAQ;EAIlB,IAAI,YAAY,iBACd,OAAO,CAAC,QAAQ;EAGlB,MAAM,SAAS,KAAK,QAAQ,UAAU;EAGtC,IAAI,YAAY,KAAA,GAEd,QADiB,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC,OAAO,GAC5C,KAAI,MAAK,IAAI,SAAS,IAAI,UAAU;EAItD,IAAI,KAAK,QAAQ,mBAAmB,KAAA,GAIlC,QAHiB,MAAM,QAAQ,KAAK,QAAQ,cAAc,IACtD,KAAK,QAAQ,iBACb,CAAC,KAAK,QAAQ,cAAc,GAChB,KAAI,MAAK,IAAI,SAAS,IAAI,UAAU;EAItD,OAAO,CAAC,QAAQ;CAClB;AACF;gCAlDC,UAAU,GAAA,gBAAA,GAII,OAAO,UAAU,WAAW,CAAA,CAAA,GAAA,iBAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Ir as ApplicationError, fr as RouterEnv, ot as ContextQueryResult, st as RouterContext, tr as ControllerOptions } from "../index-uybm0bhQ.mjs";
|
|
2
2
|
import { t as Constructor } from "../types-CmV_9xBD.mjs";
|
|
3
3
|
import { Context } from "hono";
|
|
4
4
|
import { WSContext, WSContext as WSContext$1, WSEvents, WSMessageReceive, WSReadyState, WSReadyState as WSReadyState$1 } from "hono/ws";
|
package/dist/websocket/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as getWsOnCloseMethod, c as WebSocketError, i as OnMessage, l as Gateway, n as OnClose, o as getWsOnErrorMethod, r as OnError, s as getWsOnMessageMethod, t as GatewayContext, u as isGateway } from "../gateway-context-
|
|
1
|
+
import { a as getWsOnCloseMethod, c as WebSocketError, i as OnMessage, l as Gateway, n as OnClose, o as getWsOnErrorMethod, r as OnError, s as getWsOnMessageMethod, t as GatewayContext, u as isGateway } from "../gateway-context-m7kEzRa2.mjs";
|
|
2
2
|
export { Gateway, GatewayContext, OnClose, OnError, OnMessage, WebSocketError, getWsOnCloseMethod, getWsOnErrorMethod, getWsOnMessageMethod, isGateway };
|