@ereo/core 0.1.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,959 @@
1
+ // @bun
2
+ // src/context.ts
3
+ function createContext(request) {
4
+ return new RequestContext(request);
5
+ }
6
+
7
+ class RequestContext {
8
+ url;
9
+ env;
10
+ responseHeaders;
11
+ store = new Map;
12
+ cacheOptions;
13
+ cacheTags = [];
14
+ constructor(request) {
15
+ this.url = new URL(request.url);
16
+ this.env = typeof process !== "undefined" ? process.env : {};
17
+ this.responseHeaders = new Headers;
18
+ }
19
+ cache = {
20
+ set: (options) => {
21
+ this.cacheOptions = options;
22
+ if (options.tags) {
23
+ this.cacheTags = [...new Set([...this.cacheTags, ...options.tags])];
24
+ }
25
+ },
26
+ get: () => this.cacheOptions,
27
+ getTags: () => this.cacheTags
28
+ };
29
+ get(key) {
30
+ return this.store.get(key);
31
+ }
32
+ set(key, value) {
33
+ this.store.set(key, value);
34
+ }
35
+ has(key) {
36
+ return this.store.has(key);
37
+ }
38
+ delete(key) {
39
+ return this.store.delete(key);
40
+ }
41
+ buildCacheControlHeader() {
42
+ if (!this.cacheOptions)
43
+ return null;
44
+ const parts = [];
45
+ const { maxAge, staleWhileRevalidate, private: isPrivate } = this.cacheOptions;
46
+ if (isPrivate) {
47
+ parts.push("private");
48
+ } else {
49
+ parts.push("public");
50
+ }
51
+ if (maxAge !== undefined) {
52
+ parts.push(`max-age=${maxAge}`);
53
+ }
54
+ if (staleWhileRevalidate !== undefined) {
55
+ parts.push(`stale-while-revalidate=${staleWhileRevalidate}`);
56
+ }
57
+ return parts.length > 0 ? parts.join(", ") : null;
58
+ }
59
+ applyToResponse(response) {
60
+ const headers = new Headers(response.headers);
61
+ this.responseHeaders.forEach((value, key) => {
62
+ headers.set(key, value);
63
+ });
64
+ const cacheControl = this.buildCacheControlHeader();
65
+ if (cacheControl && !headers.has("Cache-Control")) {
66
+ headers.set("Cache-Control", cacheControl);
67
+ }
68
+ if (this.cacheTags.length > 0 && !headers.has("X-Cache-Tags")) {
69
+ headers.set("X-Cache-Tags", this.cacheTags.join(","));
70
+ }
71
+ return new Response(response.body, {
72
+ status: response.status,
73
+ statusText: response.statusText,
74
+ headers
75
+ });
76
+ }
77
+ }
78
+ function isRequestContext(value) {
79
+ return value instanceof RequestContext;
80
+ }
81
+ var contextSymbol = Symbol.for("ereo.context");
82
+ function attachContext(request, context) {
83
+ request[contextSymbol] = context;
84
+ }
85
+ function getContext(request) {
86
+ return request[contextSymbol];
87
+ }
88
+
89
+ // src/plugin.ts
90
+ class PluginRegistry {
91
+ plugins = [];
92
+ context;
93
+ constructor(config, mode, root) {
94
+ this.context = { config, mode, root };
95
+ }
96
+ async register(plugin) {
97
+ if (!plugin.name) {
98
+ throw new Error("Plugin must have a name");
99
+ }
100
+ if (this.plugins.some((p) => p.name === plugin.name)) {
101
+ console.warn(`Plugin "${plugin.name}" is already registered, skipping duplicate`);
102
+ return;
103
+ }
104
+ this.plugins.push(plugin);
105
+ if (plugin.setup) {
106
+ await plugin.setup(this.context);
107
+ }
108
+ }
109
+ async registerAll(plugins) {
110
+ for (const plugin of plugins) {
111
+ await this.register(plugin);
112
+ }
113
+ }
114
+ getPlugins() {
115
+ return this.plugins;
116
+ }
117
+ getPlugin(name) {
118
+ return this.plugins.find((p) => p.name === name);
119
+ }
120
+ async transform(code, id) {
121
+ let result = code;
122
+ for (const plugin of this.plugins) {
123
+ if (plugin.transform) {
124
+ const transformed = await plugin.transform(result, id);
125
+ if (transformed !== null) {
126
+ result = transformed;
127
+ }
128
+ }
129
+ }
130
+ return result;
131
+ }
132
+ resolveId(id) {
133
+ for (const plugin of this.plugins) {
134
+ if (plugin.resolveId) {
135
+ const resolved = plugin.resolveId(id);
136
+ if (resolved !== null) {
137
+ return resolved;
138
+ }
139
+ }
140
+ }
141
+ return null;
142
+ }
143
+ async load(id) {
144
+ for (const plugin of this.plugins) {
145
+ if (plugin.load) {
146
+ const loaded = await plugin.load(id);
147
+ if (loaded !== null) {
148
+ return loaded;
149
+ }
150
+ }
151
+ }
152
+ return null;
153
+ }
154
+ async configureServer(server) {
155
+ for (const plugin of this.plugins) {
156
+ if (plugin.configureServer) {
157
+ await plugin.configureServer(server);
158
+ }
159
+ }
160
+ }
161
+ async buildStart() {
162
+ for (const plugin of this.plugins) {
163
+ if (plugin.buildStart) {
164
+ await plugin.buildStart();
165
+ }
166
+ }
167
+ }
168
+ async buildEnd() {
169
+ for (const plugin of this.plugins) {
170
+ if (plugin.buildEnd) {
171
+ await plugin.buildEnd();
172
+ }
173
+ }
174
+ }
175
+ }
176
+ function definePlugin(plugin) {
177
+ return plugin;
178
+ }
179
+ function composePlugins(name, plugins) {
180
+ return {
181
+ name,
182
+ async setup(context) {
183
+ for (const plugin of plugins) {
184
+ if (plugin.setup) {
185
+ await plugin.setup(context);
186
+ }
187
+ }
188
+ },
189
+ async transform(code, id) {
190
+ let result = code;
191
+ for (const plugin of plugins) {
192
+ if (plugin.transform) {
193
+ const transformed = await plugin.transform(result, id);
194
+ if (transformed !== null) {
195
+ result = transformed;
196
+ }
197
+ }
198
+ }
199
+ return result !== code ? result : null;
200
+ },
201
+ resolveId(id) {
202
+ for (const plugin of plugins) {
203
+ if (plugin.resolveId) {
204
+ const resolved = plugin.resolveId(id);
205
+ if (resolved !== null) {
206
+ return resolved;
207
+ }
208
+ }
209
+ }
210
+ return null;
211
+ },
212
+ async load(id) {
213
+ for (const plugin of plugins) {
214
+ if (plugin.load) {
215
+ const loaded = await plugin.load(id);
216
+ if (loaded !== null) {
217
+ return loaded;
218
+ }
219
+ }
220
+ }
221
+ return null;
222
+ },
223
+ async configureServer(server) {
224
+ for (const plugin of plugins) {
225
+ if (plugin.configureServer) {
226
+ await plugin.configureServer(server);
227
+ }
228
+ }
229
+ },
230
+ async buildStart() {
231
+ for (const plugin of plugins) {
232
+ if (plugin.buildStart) {
233
+ await plugin.buildStart();
234
+ }
235
+ }
236
+ },
237
+ async buildEnd() {
238
+ for (const plugin of plugins) {
239
+ if (plugin.buildEnd) {
240
+ await plugin.buildEnd();
241
+ }
242
+ }
243
+ }
244
+ };
245
+ }
246
+ var securityHeadersPlugin = definePlugin({
247
+ name: "ereo:security-headers",
248
+ setup() {}
249
+ });
250
+ function isPlugin(value) {
251
+ return typeof value === "object" && value !== null && "name" in value && typeof value.name === "string";
252
+ }
253
+
254
+ // src/app.ts
255
+ var defaultConfig = {
256
+ server: {
257
+ port: 3000,
258
+ hostname: "localhost",
259
+ development: true
260
+ },
261
+ build: {
262
+ target: "bun",
263
+ outDir: ".ereo",
264
+ minify: true,
265
+ sourcemap: true
266
+ },
267
+ plugins: [],
268
+ basePath: "",
269
+ routesDir: "app/routes"
270
+ };
271
+ function createApp(options = {}) {
272
+ return new EreoApp(options);
273
+ }
274
+ function defineConfig(config) {
275
+ return config;
276
+ }
277
+
278
+ class EreoApp {
279
+ config;
280
+ routes = [];
281
+ plugins = [];
282
+ pluginRegistry;
283
+ middlewares = [];
284
+ routeMatcher = null;
285
+ constructor(options = {}) {
286
+ this.config = this.mergeConfig(defaultConfig, options.config || {});
287
+ const mode = this.config.server?.development ? "development" : "production";
288
+ this.pluginRegistry = new PluginRegistry(this.config, mode, process.cwd());
289
+ if (options.routes) {
290
+ this.routes = options.routes;
291
+ }
292
+ }
293
+ mergeConfig(defaults, overrides) {
294
+ return {
295
+ server: { ...defaults.server, ...overrides.server },
296
+ build: { ...defaults.build, ...overrides.build },
297
+ plugins: [...defaults.plugins || [], ...overrides.plugins || []],
298
+ basePath: overrides.basePath ?? defaults.basePath,
299
+ routesDir: overrides.routesDir ?? defaults.routesDir
300
+ };
301
+ }
302
+ use(plugin) {
303
+ this.plugins.push(plugin);
304
+ return this;
305
+ }
306
+ middleware(handler) {
307
+ this.middlewares.push(handler);
308
+ return this;
309
+ }
310
+ setRouteMatcher(matcher) {
311
+ this.routeMatcher = matcher;
312
+ }
313
+ setRoutes(routes) {
314
+ this.routes = routes;
315
+ }
316
+ async initializePlugins() {
317
+ if (this.config.plugins) {
318
+ await this.pluginRegistry.registerAll(this.config.plugins);
319
+ }
320
+ await this.pluginRegistry.registerAll(this.plugins);
321
+ }
322
+ async handle(request) {
323
+ const context = createContext(request);
324
+ try {
325
+ const response = await this.runMiddleware(request, context, async () => {
326
+ return this.handleRoute(request, context);
327
+ });
328
+ return context.applyToResponse(response);
329
+ } catch (error) {
330
+ return this.handleError(error, context);
331
+ }
332
+ }
333
+ async runMiddleware(request, context, final) {
334
+ let index = 0;
335
+ const next = async () => {
336
+ if (index < this.middlewares.length) {
337
+ const middleware = this.middlewares[index++];
338
+ return middleware(request, context, next);
339
+ }
340
+ return final();
341
+ };
342
+ return next();
343
+ }
344
+ async handleRoute(request, context) {
345
+ const url = new URL(request.url);
346
+ let pathname = url.pathname;
347
+ if (this.config.basePath && pathname.startsWith(this.config.basePath)) {
348
+ pathname = pathname.slice(this.config.basePath.length) || "/";
349
+ }
350
+ if (!this.routeMatcher) {
351
+ return new Response("Router not configured", { status: 500 });
352
+ }
353
+ const match = this.routeMatcher(pathname);
354
+ if (!match) {
355
+ return new Response("Not Found", { status: 404 });
356
+ }
357
+ if (!match.route.module) {
358
+ return new Response("Route module not loaded", { status: 500 });
359
+ }
360
+ const module = match.route.module;
361
+ if (request.method !== "GET" && request.method !== "HEAD") {
362
+ if (module.action) {
363
+ const actionData = await module.action({
364
+ request,
365
+ params: match.params,
366
+ context
367
+ });
368
+ if (actionData instanceof Response) {
369
+ return actionData;
370
+ }
371
+ return new Response(JSON.stringify(actionData), {
372
+ headers: { "Content-Type": "application/json" }
373
+ });
374
+ }
375
+ return new Response("Method Not Allowed", { status: 405 });
376
+ }
377
+ let loaderData = null;
378
+ if (module.loader) {
379
+ loaderData = await module.loader({
380
+ request,
381
+ params: match.params,
382
+ context
383
+ });
384
+ if (loaderData instanceof Response) {
385
+ return loaderData;
386
+ }
387
+ }
388
+ if (request.headers.get("Accept")?.includes("application/json")) {
389
+ return new Response(JSON.stringify(loaderData), {
390
+ headers: { "Content-Type": "application/json" }
391
+ });
392
+ }
393
+ return new Response(JSON.stringify({ loaderData, params: match.params }), {
394
+ headers: { "Content-Type": "application/json" }
395
+ });
396
+ }
397
+ handleError(error, context) {
398
+ const isDev = this.config.server?.development;
399
+ const message = error instanceof Error ? error.message : "Internal Server Error";
400
+ const stack = error instanceof Error ? error.stack : undefined;
401
+ console.error("Request error:", error);
402
+ if (isDev) {
403
+ return new Response(JSON.stringify({
404
+ error: message,
405
+ stack: stack?.split(`
406
+ `)
407
+ }), {
408
+ status: 500,
409
+ headers: { "Content-Type": "application/json" }
410
+ });
411
+ }
412
+ return new Response("Internal Server Error", { status: 500 });
413
+ }
414
+ async dev() {
415
+ await this.initializePlugins();
416
+ console.log(`Starting dev server on http://${this.config.server?.hostname}:${this.config.server?.port}`);
417
+ }
418
+ async build() {
419
+ await this.initializePlugins();
420
+ await this.pluginRegistry.buildStart();
421
+ console.log("Building for production...");
422
+ console.log(`Target: ${this.config.build?.target}`);
423
+ console.log(`Output: ${this.config.build?.outDir}`);
424
+ await this.pluginRegistry.buildEnd();
425
+ }
426
+ async start() {
427
+ await this.initializePlugins();
428
+ console.log(`Starting production server on http://${this.config.server?.hostname}:${this.config.server?.port}`);
429
+ }
430
+ getPluginRegistry() {
431
+ return this.pluginRegistry;
432
+ }
433
+ }
434
+ function isEreoApp(value) {
435
+ return value instanceof EreoApp;
436
+ }
437
+ // src/env.ts
438
+ import { join } from "path";
439
+ function createSchemaBuilder(baseSchema) {
440
+ const schema = { ...baseSchema };
441
+ const builder = {
442
+ required() {
443
+ schema.required = true;
444
+ schema.default = undefined;
445
+ return builder;
446
+ },
447
+ default(value) {
448
+ schema.default = value;
449
+ schema.required = false;
450
+ return builder;
451
+ },
452
+ description(desc) {
453
+ schema.description = desc;
454
+ return builder;
455
+ },
456
+ validate(fn) {
457
+ schema.validate = fn;
458
+ return builder;
459
+ },
460
+ public() {
461
+ schema.public = true;
462
+ return builder;
463
+ },
464
+ _schema: schema
465
+ };
466
+ Object.defineProperty(builder, "_schema", {
467
+ get: () => schema
468
+ });
469
+ return builder;
470
+ }
471
+ var env = {
472
+ string() {
473
+ return createSchemaBuilder({
474
+ type: "string",
475
+ required: false
476
+ });
477
+ },
478
+ number() {
479
+ return createSchemaBuilder({
480
+ type: "number",
481
+ required: false,
482
+ transform: (value) => {
483
+ const num = parseFloat(value);
484
+ if (isNaN(num)) {
485
+ throw new Error(`Invalid number: ${value}`);
486
+ }
487
+ return num;
488
+ }
489
+ });
490
+ },
491
+ boolean() {
492
+ return createSchemaBuilder({
493
+ type: "boolean",
494
+ required: false,
495
+ transform: (value) => {
496
+ const lower = value.toLowerCase();
497
+ if (["true", "1", "yes", "on"].includes(lower))
498
+ return true;
499
+ if (["false", "0", "no", "off", ""].includes(lower))
500
+ return false;
501
+ throw new Error(`Invalid boolean: ${value}`);
502
+ }
503
+ });
504
+ },
505
+ json() {
506
+ return createSchemaBuilder({
507
+ type: "json",
508
+ required: false,
509
+ transform: (value) => {
510
+ try {
511
+ return JSON.parse(value);
512
+ } catch {
513
+ throw new Error(`Invalid JSON: ${value}`);
514
+ }
515
+ }
516
+ });
517
+ },
518
+ array() {
519
+ return createSchemaBuilder({
520
+ type: "array",
521
+ required: false,
522
+ transform: (value) => {
523
+ if (!value.trim())
524
+ return [];
525
+ return value.split(",").map((s) => s.trim());
526
+ }
527
+ });
528
+ },
529
+ enum(values) {
530
+ return createSchemaBuilder({
531
+ type: "string",
532
+ required: false,
533
+ validate: (value) => {
534
+ if (!values.includes(value)) {
535
+ return `Must be one of: ${values.join(", ")}`;
536
+ }
537
+ return true;
538
+ }
539
+ });
540
+ },
541
+ url() {
542
+ return createSchemaBuilder({
543
+ type: "string",
544
+ required: false,
545
+ validate: (value) => {
546
+ try {
547
+ new URL(value);
548
+ return true;
549
+ } catch {
550
+ return "Invalid URL";
551
+ }
552
+ }
553
+ });
554
+ },
555
+ port() {
556
+ return createSchemaBuilder({
557
+ type: "number",
558
+ required: false,
559
+ transform: (value) => parseInt(value, 10),
560
+ validate: (value) => {
561
+ if (!Number.isInteger(value) || value < 1 || value > 65535) {
562
+ return "Port must be an integer between 1 and 65535";
563
+ }
564
+ return true;
565
+ }
566
+ });
567
+ }
568
+ };
569
+ function parseEnvFile(content) {
570
+ const result = {};
571
+ const lines = content.split(`
572
+ `);
573
+ for (let line of lines) {
574
+ line = line.trim();
575
+ if (!line || line.startsWith("#"))
576
+ continue;
577
+ const eqIndex = line.indexOf("=");
578
+ if (eqIndex === -1)
579
+ continue;
580
+ const key = line.slice(0, eqIndex).trim();
581
+ let value = line.slice(eqIndex + 1).trim();
582
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
583
+ value = value.slice(1, -1);
584
+ }
585
+ if (line.slice(eqIndex + 1).trim().startsWith('"')) {
586
+ value = value.replace(/\\n/g, `
587
+ `).replace(/\\r/g, "\r").replace(/\\t/g, "\t").replace(/\\\\/g, "\\");
588
+ }
589
+ result[key] = value;
590
+ }
591
+ return result;
592
+ }
593
+ async function loadEnvFiles(root, mode = "development") {
594
+ const envFiles = [
595
+ ".env",
596
+ `.env.${mode}`,
597
+ `.env.${mode}.local`,
598
+ ".env.local"
599
+ ];
600
+ const loaded = {};
601
+ for (const file of envFiles) {
602
+ const filePath = join(root, file);
603
+ try {
604
+ const bunFile = Bun.file(filePath);
605
+ if (await bunFile.exists()) {
606
+ const content = await bunFile.text();
607
+ const parsed = parseEnvFile(content);
608
+ Object.assign(loaded, parsed);
609
+ }
610
+ } catch {}
611
+ }
612
+ return loaded;
613
+ }
614
+ function validateEnv(schema, rawEnv) {
615
+ const errors = [];
616
+ const warnings = [];
617
+ const parsed = {};
618
+ for (const [key, builder] of Object.entries(schema)) {
619
+ const definition = builder._schema;
620
+ const rawValue = rawEnv[key];
621
+ if (rawValue === undefined || rawValue === "") {
622
+ if (definition.required) {
623
+ errors.push({
624
+ key,
625
+ message: `Missing required environment variable: ${key}`,
626
+ expected: definition.type
627
+ });
628
+ continue;
629
+ }
630
+ if (definition.default !== undefined) {
631
+ parsed[key] = definition.default;
632
+ continue;
633
+ }
634
+ continue;
635
+ }
636
+ try {
637
+ let value;
638
+ if (definition.transform) {
639
+ value = definition.transform(rawValue);
640
+ } else {
641
+ value = rawValue;
642
+ }
643
+ if (definition.validate) {
644
+ const result = definition.validate(value);
645
+ if (result !== true) {
646
+ errors.push({
647
+ key,
648
+ message: typeof result === "string" ? result : `Validation failed for ${key}`,
649
+ expected: definition.description || definition.type,
650
+ received: String(rawValue)
651
+ });
652
+ continue;
653
+ }
654
+ }
655
+ parsed[key] = value;
656
+ } catch (error) {
657
+ errors.push({
658
+ key,
659
+ message: error instanceof Error ? error.message : `Invalid value for ${key}`,
660
+ expected: definition.type,
661
+ received: rawValue
662
+ });
663
+ }
664
+ }
665
+ for (const key of Object.keys(rawEnv)) {
666
+ if (key.startsWith("EREO_") && !schema[key]) {
667
+ warnings.push(`Unknown environment variable with EREO_ prefix: ${key}`);
668
+ }
669
+ }
670
+ return {
671
+ valid: errors.length === 0,
672
+ errors,
673
+ warnings,
674
+ env: parsed
675
+ };
676
+ }
677
+ var globalEnv = {};
678
+ var envInitialized = false;
679
+ function initializeEnv(validatedEnv) {
680
+ globalEnv = { ...validatedEnv };
681
+ envInitialized = true;
682
+ }
683
+ function getEnv(key) {
684
+ if (!envInitialized) {
685
+ return process.env[key];
686
+ }
687
+ return globalEnv[key];
688
+ }
689
+ function requireEnv(key) {
690
+ const value = getEnv(key);
691
+ if (value === undefined) {
692
+ throw new Error(`Required environment variable not set: ${key}`);
693
+ }
694
+ return value;
695
+ }
696
+ function getAllEnv() {
697
+ return { ...globalEnv };
698
+ }
699
+ function getPublicEnv(schema) {
700
+ const publicEnv = {};
701
+ for (const [key, builder] of Object.entries(schema)) {
702
+ const definition = builder._schema;
703
+ if (definition.public && globalEnv[key] !== undefined) {
704
+ publicEnv[key] = globalEnv[key];
705
+ }
706
+ }
707
+ return publicEnv;
708
+ }
709
+ async function setupEnv(root, schema, mode = "development") {
710
+ const fileEnv = await loadEnvFiles(root, mode);
711
+ const combinedEnv = {
712
+ ...fileEnv,
713
+ ...process.env
714
+ };
715
+ const result = validateEnv(schema, combinedEnv);
716
+ if (result.valid) {
717
+ initializeEnv(result.env);
718
+ }
719
+ for (const warning of result.warnings) {
720
+ console.warn(`[env] Warning: ${warning}`);
721
+ }
722
+ if (!result.valid) {
723
+ console.error("[env] Environment validation failed:");
724
+ for (const error of result.errors) {
725
+ console.error(` - ${error.key}: ${error.message}`);
726
+ if (error.expected)
727
+ console.error(` Expected: ${error.expected}`);
728
+ if (error.received)
729
+ console.error(` Received: ${error.received}`);
730
+ }
731
+ }
732
+ return result;
733
+ }
734
+ function generateEnvTypes(schema) {
735
+ const lines = [
736
+ "// Auto-generated by @ereo/core",
737
+ "// Do not edit this file manually",
738
+ "",
739
+ "declare module '@ereo/core' {",
740
+ " interface EnvTypes {"
741
+ ];
742
+ for (const [key, builder] of Object.entries(schema)) {
743
+ const definition = builder._schema;
744
+ let tsType;
745
+ switch (definition.type) {
746
+ case "number":
747
+ tsType = "number";
748
+ break;
749
+ case "boolean":
750
+ tsType = "boolean";
751
+ break;
752
+ case "json":
753
+ tsType = "Record<string, unknown>";
754
+ break;
755
+ case "array":
756
+ tsType = "string[]";
757
+ break;
758
+ default:
759
+ tsType = "string";
760
+ }
761
+ const optional = !definition.required && definition.default === undefined;
762
+ lines.push(` ${key}${optional ? "?" : ""}: ${tsType};`);
763
+ }
764
+ lines.push(" }");
765
+ lines.push("}");
766
+ lines.push("");
767
+ lines.push("export {};");
768
+ return lines.join(`
769
+ `);
770
+ }
771
+ var typedEnv = new Proxy({}, {
772
+ get(_target, key) {
773
+ return getEnv(key);
774
+ }
775
+ });
776
+ // src/cache.ts
777
+ class MemoryCacheAdapter {
778
+ cache = new Map;
779
+ tagIndex = new Map;
780
+ maxSize;
781
+ defaultTtl;
782
+ constructor(options = {}) {
783
+ this.maxSize = options.maxSize ?? Infinity;
784
+ this.defaultTtl = options.defaultTtl;
785
+ }
786
+ async get(key) {
787
+ const entry = this.cache.get(key);
788
+ if (!entry) {
789
+ return;
790
+ }
791
+ if (this.isExpired(entry)) {
792
+ await this.delete(key);
793
+ return;
794
+ }
795
+ return entry.value;
796
+ }
797
+ async set(key, value, options) {
798
+ if (this.cache.has(key)) {
799
+ await this.delete(key);
800
+ }
801
+ if (this.cache.size >= this.maxSize) {
802
+ const firstKey = this.cache.keys().next().value;
803
+ if (firstKey) {
804
+ await this.delete(firstKey);
805
+ }
806
+ }
807
+ const tags = options?.tags ?? [];
808
+ const ttl = options?.ttl ?? this.defaultTtl;
809
+ const entry = {
810
+ value,
811
+ createdAt: Date.now(),
812
+ ttl,
813
+ tags
814
+ };
815
+ this.cache.set(key, entry);
816
+ for (const tag of tags) {
817
+ if (!this.tagIndex.has(tag)) {
818
+ this.tagIndex.set(tag, new Set);
819
+ }
820
+ this.tagIndex.get(tag).add(key);
821
+ }
822
+ }
823
+ async delete(key) {
824
+ const entry = this.cache.get(key);
825
+ if (!entry) {
826
+ return false;
827
+ }
828
+ for (const tag of entry.tags) {
829
+ const tagSet = this.tagIndex.get(tag);
830
+ if (tagSet) {
831
+ tagSet.delete(key);
832
+ if (tagSet.size === 0) {
833
+ this.tagIndex.delete(tag);
834
+ }
835
+ }
836
+ }
837
+ return this.cache.delete(key);
838
+ }
839
+ async has(key) {
840
+ const entry = this.cache.get(key);
841
+ if (!entry) {
842
+ return false;
843
+ }
844
+ if (this.isExpired(entry)) {
845
+ await this.delete(key);
846
+ return false;
847
+ }
848
+ return true;
849
+ }
850
+ async clear() {
851
+ this.cache.clear();
852
+ this.tagIndex.clear();
853
+ }
854
+ async invalidateTag(tag) {
855
+ const keys = this.tagIndex.get(tag);
856
+ if (!keys) {
857
+ return;
858
+ }
859
+ const keysCopy = [...keys];
860
+ for (const key of keysCopy) {
861
+ await this.delete(key);
862
+ }
863
+ }
864
+ async invalidateTags(tags) {
865
+ for (const tag of tags) {
866
+ await this.invalidateTag(tag);
867
+ }
868
+ }
869
+ async getByTag(tag) {
870
+ const keys = this.tagIndex.get(tag);
871
+ if (!keys) {
872
+ return [];
873
+ }
874
+ const validKeys = [];
875
+ for (const key of keys) {
876
+ if (await this.has(key)) {
877
+ validKeys.push(key);
878
+ }
879
+ }
880
+ return validKeys;
881
+ }
882
+ getStats() {
883
+ return {
884
+ size: this.cache.size,
885
+ tags: this.tagIndex.size
886
+ };
887
+ }
888
+ async keys() {
889
+ return Array.from(this.cache.keys());
890
+ }
891
+ isExpired(entry) {
892
+ if (!entry.ttl) {
893
+ return false;
894
+ }
895
+ const age = Date.now() - entry.createdAt;
896
+ return age > entry.ttl * 1000;
897
+ }
898
+ }
899
+ function createCache(options) {
900
+ return new MemoryCacheAdapter(options);
901
+ }
902
+ function createTaggedCache(options) {
903
+ return new MemoryCacheAdapter({ ...options, tagged: true });
904
+ }
905
+ function isTaggedCache(cache) {
906
+ return "invalidateTag" in cache && "invalidateTags" in cache && "getByTag" in cache && typeof cache.invalidateTag === "function" && typeof cache.invalidateTags === "function" && typeof cache.getByTag === "function";
907
+ }
908
+ function wrapCacheAdapter(impl) {
909
+ return {
910
+ async get(key) {
911
+ return impl.get(key);
912
+ },
913
+ async set(key, value, options) {
914
+ await impl.set(key, value, options);
915
+ },
916
+ async delete(key) {
917
+ return impl.delete(key);
918
+ },
919
+ async has(key) {
920
+ return impl.has(key);
921
+ },
922
+ async clear() {
923
+ await impl.clear();
924
+ }
925
+ };
926
+ }
927
+ export {
928
+ wrapCacheAdapter,
929
+ validateEnv,
930
+ typedEnv,
931
+ setupEnv,
932
+ securityHeadersPlugin,
933
+ requireEnv,
934
+ parseEnvFile,
935
+ loadEnvFiles,
936
+ isTaggedCache,
937
+ isRequestContext,
938
+ isPlugin,
939
+ isEreoApp,
940
+ initializeEnv,
941
+ getPublicEnv,
942
+ getEnv,
943
+ getContext,
944
+ getAllEnv,
945
+ generateEnvTypes,
946
+ env,
947
+ definePlugin,
948
+ defineConfig,
949
+ createTaggedCache,
950
+ createContext,
951
+ createCache,
952
+ createApp,
953
+ composePlugins,
954
+ attachContext,
955
+ RequestContext,
956
+ PluginRegistry,
957
+ MemoryCacheAdapter,
958
+ EreoApp
959
+ };