@getstrata/core 0.5.12 → 0.5.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Stable **Strata** framework surface for application modules.
4
4
 
5
5
  **Source:** `src/framework/public-api.ts` (monorepo)
6
- **Repository:** [EyK-26/strata](https://github.com/EyK-26/strata) directory `packages/strata-core`
6
+ **Repository:** [EyK-26/strata](https://github.com/EyK-26/strata), directory `packages/strata-core`
7
7
 
8
8
  WorkHub is the reference application built on Strata; import the framework from this package in your own modules.
9
9
 
@@ -15,21 +15,31 @@ Set `DATABASE_URL` before importing (connection is created lazily on first query
15
15
  process.env.DATABASE_URL ??= "postgresql://postgres:postgres@localhost:5432/myapp";
16
16
 
17
17
  import {
18
+ AdminResourceRegistry,
18
19
  BaseRepository,
19
20
  EtaViewEngine,
20
21
  FormRequest,
21
22
  Policy,
23
+ formatAdminValue,
22
24
  mailer,
23
25
  storage,
24
26
  withErrorHandling,
25
27
  } from "@getstrata/core";
26
28
  ```
27
29
 
28
- **Dependency:** `eta` is bundled as a direct dependency of `@getstrata/core` apps do not need to list it separately. The database driver is your app's choice WorkHub and getstrata use **Bun's built-in `Bun.sql`** client; bind it with `bindDatabaseConnection()`.
30
+ **Dependency:** `eta` is bundled as a direct dependency of `@getstrata/core`. Apps do not need to list it separately. The database driver is your app's choice. WorkHub and getstrata use **Bun's built-in `Bun.sql`** client; bind it with `bindDatabaseConnection()`.
29
31
 
30
- `orderBy` accepts explicit `{ column, direction }` objects or Laravel-style shorthand `{ published_at: "desc" }`.
32
+ `orderBy` accepts explicit `{ column, direction }` objects or column shorthand such as `{ published_at: "desc" }`.
31
33
 
32
- ## Build & verify (monorepo root)
34
+ ## Admin and queue helpers
35
+
36
+ Exports for admin dashboards and queue recovery:
37
+
38
+ - `AdminResourceRegistry`, `formatAdminValue`: read-only resource browsers
39
+ - `createFailedJobService`, `FailedJobService.delete()`: failed job persistence and cleanup
40
+ - `runQueueJob`, `jobRegistry`: dispatch retried jobs from admin UIs
41
+
42
+ ## Build and verify (monorepo root)
33
43
 
34
44
  ```bash
35
45
  bun run build:framework
@@ -41,9 +51,9 @@ bun run verify:framework # build + public API tests
41
51
  Package name: **`@getstrata/core`** (npm org [`@getstrata`](https://www.npmjs.com/org/getstrata)).
42
52
 
43
53
  1. Add `NPM_TOKEN` to GitHub repository secrets.
44
- 2. Tag a release: `git tag v0.2.0 && git push origin v0.2.0`
54
+ 2. Tag a release: `git tag v0.5.13 && git push origin v0.5.13`
45
55
  3. [Release workflow](../../.github/workflows/release.yml) builds and runs `npm publish --access public`.
46
56
 
47
- Previously published as `@eyk-workhub/framework@0.1.0` deprecated in favor of this package.
57
+ Previously published as `@eyk-workhub/framework@0.1.0`, deprecated in favor of this package.
48
58
 
49
59
  See [docs/PACKAGING.md](../../docs/PACKAGING.md) for boundaries and future extraction.
@@ -1,5 +1,4 @@
1
- import type { Policy } from "../core/auth/policy";
2
- import { type Middleware, type RouteHandler } from "../core/http/middleware";
1
+ import { type Middleware, type Policy, type RouteHandler } from "@getstrata/core";
3
2
  import type { AppDependencies } from "./contracts";
4
3
  type MiddlewareGroupName = "api" | "authenticated" | "web";
5
4
  declare class HttpKernel {
@@ -0,0 +1,3 @@
1
+ import type { AdminColumnType } from "./types.ts";
2
+ declare function formatAdminValue(value: unknown, type?: AdminColumnType): string;
3
+ export { formatAdminValue };
@@ -0,0 +1,3 @@
1
+ export { formatAdminValue } from "./formatValue.ts";
2
+ export { AdminResourceRegistry } from "./registry.ts";
3
+ export type { AdminColumn, AdminColumnType, AdminResource, AdminResourceDefinition, AdminResourceHandlers, } from "./types.ts";
@@ -0,0 +1,11 @@
1
+ import type { AdminResource, AdminResourceDefinition } from "./types.ts";
2
+ declare class AdminResourceRegistry {
3
+ private readonly resources;
4
+ constructor();
5
+ register<TEntity extends object>(resource: AdminResource<TEntity>): void;
6
+ get(name: string): AdminResource<object> | undefined;
7
+ list(): AdminResourceDefinition[];
8
+ all(): AdminResource<object>[];
9
+ clear(): void;
10
+ }
11
+ export { AdminResourceRegistry };
@@ -0,0 +1,24 @@
1
+ import type { PaginatedResult } from "../pagination/index.ts";
2
+ type AdminColumnType = "text" | "number" | "boolean" | "datetime" | "code";
3
+ interface AdminColumn {
4
+ key: string;
5
+ label: string;
6
+ type?: AdminColumnType;
7
+ }
8
+ interface AdminResourceHandlers<TEntity extends object> {
9
+ paginate(options: {
10
+ page: number;
11
+ perPage: number;
12
+ }): Promise<PaginatedResult<TEntity>>;
13
+ findById?(id: number): Promise<TEntity | null>;
14
+ }
15
+ interface AdminResourceDefinition {
16
+ name: string;
17
+ label: string;
18
+ labelPlural: string;
19
+ columns: AdminColumn[];
20
+ }
21
+ interface AdminResource<TEntity extends object> extends AdminResourceDefinition {
22
+ handlers: AdminResourceHandlers<TEntity>;
23
+ }
24
+ export type { AdminColumn, AdminColumnType, AdminResource, AdminResourceDefinition, AdminResourceHandlers, };
@@ -1,5 +1,4 @@
1
- import type { MailMessage } from "./mailer.ts";
2
- import type { Mailer } from "./mailer.ts";
1
+ import type { Mailer, MailMessage } from "./mailer.ts";
3
2
  import { type MarkdownMailLayoutOptions } from "./markdownMail.ts";
4
3
  interface MarkdownMailableInput {
5
4
  to: string;
@@ -1,6 +1,6 @@
1
1
  import type { DatabaseNotificationPayload, MailNotificationMessage, NotificationChannelName } from "./types.ts";
2
2
  declare abstract class Notification<TNotifiable = unknown> {
3
- abstract via(notifiable: TNotifiable): NotificationChannelName[];
3
+ via(_notifiable: TNotifiable): NotificationChannelName[];
4
4
  toMail(_notifiable: TNotifiable): MailNotificationMessage | null;
5
5
  toDatabase(_notifiable: TNotifiable): DatabaseNotificationPayload | null;
6
6
  }
@@ -10,6 +10,7 @@ declare class FailedJobService {
10
10
  }): Promise<FailedJobRecord>;
11
11
  listRecent(limit?: number): Promise<FailedJobRecord[]>;
12
12
  retry(id: number): Promise<FailedJobRecord>;
13
+ delete(id: number): Promise<void>;
13
14
  flush(): Promise<number>;
14
15
  }
15
16
  export default FailedJobService;
@@ -2,6 +2,7 @@ import FailedJobRepository from "./failedJobRepository.ts";
2
2
  import FailedJobService from "./failedJobService.ts";
3
3
  import type { Job, Queue } from "./index.ts";
4
4
  import { jobRegistry } from "./jobRegistry.ts";
5
+ import { runQueueJob } from "./jobRunner.ts";
5
6
  import { QueueWorker, RedisQueue } from "./redisQueue.ts";
6
7
  import { ResilientQueue } from "./resilientQueue.ts";
7
8
  declare function createFailedJobService(): FailedJobService;
@@ -12,4 +13,4 @@ declare function createProductionQueue(driver: "sync" | "async" | "redis", optio
12
13
  registerJobs?: () => void;
13
14
  }): Queue;
14
15
  declare function createQueueWorker(redisUrl: string, failedJobs?: FailedJobService): QueueWorker;
15
- export { createFailedJobService, createProductionQueue, createQueueWorker, createTrackedJob, FailedJobRepository, FailedJobService, jobRegistry, QueueWorker, RedisQueue, ResilientQueue, };
16
+ export { createFailedJobService, createProductionQueue, createQueueWorker, createTrackedJob, FailedJobRepository, FailedJobService, jobRegistry, QueueWorker, RedisQueue, ResilientQueue, runQueueJob, };
@@ -12,8 +12,9 @@ interface S3StorageConfig {
12
12
  endpoint?: string;
13
13
  }
14
14
  declare class LocalStorageDriver implements StorageDriver {
15
- private readonly rootDirectory;
16
- constructor(rootDirectory: string);
15
+ private readonly rootDirectory?;
16
+ constructor(rootDirectory?: string | undefined);
17
+ private resolveRootDirectory;
17
18
  private resolvePath;
18
19
  put(path: string, contents: string | Uint8Array): Promise<string>;
19
20
  get(path: string): Promise<Uint8Array | null>;
@@ -37,5 +38,7 @@ declare function resolveS3Config(): S3StorageConfig;
37
38
  declare function createS3Client(config?: S3StorageConfig): S3Client;
38
39
  declare function createStorageDriver(): StorageDriver;
39
40
  declare function storage(): StorageManager;
41
+ /** Test hook: drop the process-wide storage singleton (e.g. after changing STORAGE_PATH). */
42
+ declare function resetDefaultStorage(): void;
40
43
  export type { S3StorageConfig, StorageDriver };
41
- export { createS3Client, createStorageDriver, LocalStorageDriver, resolveS3Config, S3StorageDriver, StorageManager, storage, };
44
+ export { createS3Client, createStorageDriver, LocalStorageDriver, resetDefaultStorage, resolveS3Config, S3StorageDriver, StorageManager, storage, };
@@ -2773,6 +2773,12 @@ class FailedJobService {
2773
2773
  await this.repository.deleteById(id);
2774
2774
  return failedJob;
2775
2775
  }
2776
+ async delete(id) {
2777
+ const deleted = await this.repository.deleteById(id);
2778
+ if (!deleted) {
2779
+ throw new Error(`Failed job ${id} not found.`);
2780
+ }
2781
+ }
2776
2782
  async flush() {
2777
2783
  const jobs = await this.repository.findAll();
2778
2784
  let deleted = 0;
@@ -2786,9 +2792,6 @@ class FailedJobService {
2786
2792
  }
2787
2793
  var failedJobService_default = FailedJobService;
2788
2794
 
2789
- // ../../src/core/queue/redisQueue.ts
2790
- var {RedisClient } = globalThis.Bun;
2791
-
2792
2795
  // ../../src/config/queue.ts
2793
2796
  var queueConfig = {
2794
2797
  driver: process.env.QUEUE_DRIVER === "redis" || process.env.QUEUE_DRIVER === "async" || process.env.QUEUE_DRIVER === "sync" ? process.env.QUEUE_DRIVER : DEFAULT_QUEUE_DRIVER,
@@ -2827,6 +2830,7 @@ async function runQueueJob(envelope, failedJobs) {
2827
2830
  }
2828
2831
 
2829
2832
  // ../../src/core/queue/redisQueue.ts
2833
+ var {RedisClient } = globalThis.Bun;
2830
2834
  var QUEUE_LIST_KEY = "workhub:queue:default";
2831
2835
  var QUEUE_HIGH_KEY = "workhub:queue:high";
2832
2836
  var QUEUE_LOW_KEY = "workhub:queue:low";
@@ -24,6 +24,12 @@ class FailedJobService {
24
24
  await this.repository.deleteById(id);
25
25
  return failedJob;
26
26
  }
27
+ async delete(id) {
28
+ const deleted = await this.repository.deleteById(id);
29
+ if (!deleted) {
30
+ throw new Error(`Failed job ${id} not found.`);
31
+ }
32
+ }
27
33
  async flush() {
28
34
  const jobs = await this.repository.findAll();
29
35
  let deleted = 0;
@@ -2375,6 +2375,12 @@ class FailedJobService {
2375
2375
  await this.repository.deleteById(id);
2376
2376
  return failedJob;
2377
2377
  }
2378
+ async delete(id) {
2379
+ const deleted = await this.repository.deleteById(id);
2380
+ if (!deleted) {
2381
+ throw new Error(`Failed job ${id} not found.`);
2382
+ }
2383
+ }
2378
2384
  async flush() {
2379
2385
  const jobs = await this.repository.findAll();
2380
2386
  let deleted = 0;
@@ -2416,9 +2422,6 @@ class JobRegistry {
2416
2422
  }
2417
2423
  var jobRegistry = new JobRegistry;
2418
2424
 
2419
- // ../../src/core/queue/redisQueue.ts
2420
- var {RedisClient } = globalThis.Bun;
2421
-
2422
2425
  // ../../src/bootstrap/config.ts
2423
2426
  var CORE_QUEUE_TOKEN = "core.queue";
2424
2427
  var CORE_POLICY_GATE_TOKEN = "core.policyGate";
@@ -2464,6 +2467,7 @@ async function runQueueJob(envelope, failedJobs) {
2464
2467
  }
2465
2468
 
2466
2469
  // ../../src/core/queue/redisQueue.ts
2470
+ var {RedisClient } = globalThis.Bun;
2467
2471
  var QUEUE_LIST_KEY = "workhub:queue:default";
2468
2472
  var QUEUE_HIGH_KEY = "workhub:queue:high";
2469
2473
  var QUEUE_LOW_KEY = "workhub:queue:low";
@@ -2637,6 +2641,7 @@ function createQueueWorker(redisUrl, failedJobs = createFailedJobService()) {
2637
2641
  return new QueueWorker(redisUrl, failedJobs);
2638
2642
  }
2639
2643
  export {
2644
+ runQueueJob,
2640
2645
  jobRegistry,
2641
2646
  createTrackedJob,
2642
2647
  createQueueWorker,
@@ -2783,6 +2783,12 @@ class FailedJobService {
2783
2783
  await this.repository.deleteById(id);
2784
2784
  return failedJob;
2785
2785
  }
2786
+ async delete(id) {
2787
+ const deleted = await this.repository.deleteById(id);
2788
+ if (!deleted) {
2789
+ throw new Error(`Failed job ${id} not found.`);
2790
+ }
2791
+ }
2786
2792
  async flush() {
2787
2793
  const jobs = await this.repository.findAll();
2788
2794
  let deleted = 0;
@@ -2796,9 +2802,6 @@ class FailedJobService {
2796
2802
  }
2797
2803
  var failedJobService_default = FailedJobService;
2798
2804
 
2799
- // ../../src/core/queue/redisQueue.ts
2800
- var {RedisClient } = globalThis.Bun;
2801
-
2802
2805
  // ../../src/core/queue/jobRunner.ts
2803
2806
  async function runQueueJob(envelope, failedJobs) {
2804
2807
  const job = jobRegistry.create(envelope.name);
@@ -2830,6 +2833,7 @@ async function runQueueJob(envelope, failedJobs) {
2830
2833
  }
2831
2834
 
2832
2835
  // ../../src/core/queue/redisQueue.ts
2836
+ var {RedisClient } = globalThis.Bun;
2833
2837
  var QUEUE_LIST_KEY = "workhub:queue:default";
2834
2838
  var QUEUE_HIGH_KEY = "workhub:queue:high";
2835
2839
  var QUEUE_LOW_KEY = "workhub:queue:low";
@@ -9,8 +9,11 @@ class LocalStorageDriver {
9
9
  constructor(rootDirectory) {
10
10
  this.rootDirectory = rootDirectory;
11
11
  }
12
+ resolveRootDirectory() {
13
+ return this.rootDirectory ?? process.env.STORAGE_PATH ?? "storage";
14
+ }
12
15
  resolvePath(path) {
13
- return join(this.rootDirectory, path.replace(/^\/+/, ""));
16
+ return join(this.resolveRootDirectory(), path.replace(/^\/+/, ""));
14
17
  }
15
18
  async put(path, contents) {
16
19
  const absolutePath = this.resolvePath(path);
@@ -106,15 +109,22 @@ function createStorageDriver() {
106
109
  if (driver === "s3") {
107
110
  return new S3StorageDriver(createS3Client());
108
111
  }
109
- return new LocalStorageDriver(process.env.STORAGE_PATH ?? "storage");
112
+ return new LocalStorageDriver;
110
113
  }
111
- var defaultStorage = new StorageManager(createStorageDriver());
114
+ var defaultStorage = { current: null };
112
115
  function storage() {
113
- return defaultStorage;
116
+ if (!defaultStorage.current) {
117
+ defaultStorage.current = new StorageManager(createStorageDriver());
118
+ }
119
+ return defaultStorage.current;
120
+ }
121
+ function resetDefaultStorage() {
122
+ defaultStorage.current = null;
114
123
  }
115
124
  export {
116
125
  storage,
117
126
  resolveS3Config,
127
+ resetDefaultStorage,
118
128
  createStorageDriver,
119
129
  createS3Client,
120
130
  StorageManager,
@@ -5,9 +5,12 @@
5
5
  export { resolveApplicationAuth, resolveApplicationCache, resolveApplicationConfig, resolveApplicationDependencies, resolveApplicationLogger, resolveApplicationPolicyGate, resolveApplicationQueue, setActiveApplicationContext, } from "../bootstrap/applicationRegistry.ts";
6
6
  export type { ServiceProvider } from "../bootstrap/contracts.ts";
7
7
  export { ConfigStore, resolveService, ServiceContainer, } from "../bootstrap/contracts.ts";
8
+ export type { AdminColumn, AdminColumnType, AdminResource, AdminResourceDefinition, AdminResourceHandlers, } from "../core/admin/index.ts";
9
+ export { AdminResourceRegistry, formatAdminValue, } from "../core/admin/index.ts";
8
10
  export type { AbilityChecker } from "../core/auth/abilityChecker.ts";
9
11
  export type { AuthUser } from "../core/auth/authContext.ts";
10
12
  export { currentAuthUser, runWithAuthUser } from "../core/auth/authContext.ts";
13
+ export { createMembershipMiddleware } from "../core/auth/membershipMiddleware.ts";
11
14
  export { Policy, PolicyGate } from "../core/auth/policy.ts";
12
15
  export { default as CacheRepository } from "../core/cache/repository.ts";
13
16
  export { CACHE_TAGS } from "../core/cache/tags.ts";
@@ -38,16 +41,20 @@ export { EventBus } from "../core/events/eventBus.ts";
38
41
  export { auth, cache, config, events, log, mail, policyGate, queue, storage, } from "../core/facades/index.ts";
39
42
  export { createBodySizeLimitMiddleware } from "../core/http/bodySizeLimitMiddleware.ts";
40
43
  export { readBunRequestCookie, readRequestCookie } from "../core/http/cookies.ts";
44
+ export { createCorsMiddleware } from "../core/http/corsMiddleware.ts";
41
45
  export { createCsrfMiddleware } from "../core/http/csrfMiddleware.ts";
42
46
  export { createCsrfProtection } from "../core/http/csrfProtection.ts";
43
47
  export { createCsrfTokenCookie, readSubmittedCsrfToken, readSubmittedCsrfTokenFromBody, resolveCsrfToken, resolveCsrfTokenForRequest, verifyCsrfToken, } from "../core/http/csrfToken.ts";
44
48
  export { assertIfMatch, etagFromResource, isEtagEnabled, } from "../core/http/etag.ts";
49
+ export { createFlashMiddleware } from "../core/http/flashMiddleware.ts";
45
50
  export { FormRequest } from "../core/http/formRequest.ts";
46
- export { applyMiddlewareToRoutes, composeMiddleware, createAuthMiddleware, createAuthorizeMiddleware, createdResponse, createRequireAuthMiddleware, jsonResponse, noContentResponse, paginatedResponse, parsePaginationQuery, securedBindRouteModel, securedBindRouteModelByKey, withErrorHandling, withMiddleware, } from "../core/http/index.ts";
51
+ export { applyMiddlewareToRoutes, composeMiddleware, createAuthMiddleware, createAuthorizeMiddleware, createdResponse, createRequireAuthMiddleware, jsonResponse, noContentResponse, paginatedResponse, parsePaginationQuery, requestIdMiddleware, securedBindRouteModel, securedBindRouteModelByKey, withErrorHandling, withMiddleware, } from "../core/http/index.ts";
47
52
  export { createLoginThrottleMiddleware } from "../core/http/loginThrottleMiddleware.ts";
48
53
  export { createMemoryThrottleMiddleware } from "../core/http/memoryThrottleMiddleware.ts";
49
54
  export { createMetricsMiddleware, normalizeMetricPath } from "../core/http/metricsMiddleware.ts";
50
55
  export type { Middleware, RouteHandler } from "../core/http/middleware.ts";
56
+ export { createRequireAbilityMiddleware } from "../core/http/requireAbilityMiddleware.ts";
57
+ export { createRequireGlobalAdminMiddleware } from "../core/http/requireGlobalAdminMiddleware.ts";
51
58
  export { createRequireWebAuthMiddleware } from "../core/http/requireWebAuthMiddleware.ts";
52
59
  export { serializeDate, toPaginatedResourceCollection, toResourceCollection, } from "../core/http/resources.ts";
53
60
  export type { RouteRequest } from "../core/http/route.ts";
@@ -55,24 +62,28 @@ export { createSecurityHeadersMiddleware } from "../core/http/securityHeadersMid
55
62
  export { createThrottleMiddleware } from "../core/http/throttleMiddleware.ts";
56
63
  export { WebFormRequest } from "../core/http/webFormRequest.ts";
57
64
  export { installGracefulShutdownSignals, registerShutdownHandler, runGracefulShutdown, } from "../core/lifecycle/gracefulShutdown.ts";
65
+ export { createRequestLoggingMiddleware } from "../core/logging/requestLoggingMiddleware.ts";
58
66
  export type { MailDriver, MailMessage } from "../core/mail/mailer.ts";
59
67
  export { buildSmtpPayload, LogMailDriver, Mailer, mailer, } from "../core/mail/mailer.ts";
60
68
  export type { MarkdownMailLayoutOptions, RenderedMarkdownMail } from "../core/mail/markdownMail.ts";
61
69
  export { markdownToHtml, renderMarkdownMail, stripMarkdown, wrapMarkdownMailLayout, } from "../core/mail/markdownMail.ts";
62
70
  export type { MarkdownMailableInput } from "../core/mail/markdownMailable.ts";
63
71
  export { buildMarkdownMailMessage, sendMarkdownMail } from "../core/mail/markdownMailable.ts";
64
- export type { DatabaseNotificationPayload, DatabaseNotificationStore, MailNotificationMessage, Notifiable, NotificationChannelName, } from "../core/notifications/index.ts";
65
- export { createNotificationDispatcher, Notification, NotificationDispatcher, } from "../core/notifications/index.ts";
66
72
  export type { MetricLabels } from "../core/metrics/prometheus.ts";
67
73
  export { PrometheusRegistry, prometheusRegistry } from "../core/metrics/prometheus.ts";
74
+ export type { DatabaseNotificationPayload, DatabaseNotificationStore, MailNotificationMessage, Notifiable, NotificationChannelName, } from "../core/notifications/index.ts";
75
+ export { createNotificationDispatcher, Notification, NotificationDispatcher, } from "../core/notifications/index.ts";
68
76
  export type { CursorPaginatedResult, PaginatedResult, PaginationMeta, } from "../core/pagination/index.ts";
69
77
  export type { Queue, QueuePriority } from "../core/queue/index.ts";
70
78
  export { AsyncQueue, createQueue, Job, SyncQueue } from "../core/queue/index.ts";
71
- export { createFailedJobService, createProductionQueue, createQueueWorker, createTrackedJob, FailedJobRepository, FailedJobService, jobRegistry, QueueWorker, RedisQueue, ResilientQueue, } from "../core/queue/publicQueue.ts";
79
+ export { createFailedJobService, createProductionQueue, createQueueWorker, createTrackedJob, FailedJobRepository, FailedJobService, jobRegistry, QueueWorker, RedisQueue, ResilientQueue, runQueueJob, } from "../core/queue/publicQueue.ts";
72
80
  export type { ScheduledTask } from "../core/scheduler/schedule.ts";
73
81
  export { appSchedule, runDueScheduledTasks, Schedule } from "../core/scheduler/schedule.ts";
82
+ export { isPublicReadsEnabled } from "../core/security/publicReads.ts";
74
83
  export type { StorageDriver } from "../core/storage/storage.ts";
75
- export { LocalStorageDriver, StorageManager } from "../core/storage/storage.ts";
84
+ export { LocalStorageDriver, resetDefaultStorage, StorageManager, } from "../core/storage/storage.ts";
85
+ export { createTenantMiddleware } from "../core/tenant/tenantMiddleware.ts";
86
+ export { createTracingMiddleware } from "../core/tracing/tracingMiddleware.ts";
76
87
  export type { ValidationRule, ValidationSchema } from "../core/validation/rules.ts";
77
88
  export { emailRule, maxLength, minLength, required, stringRule, validateObject, } from "../core/validation/rules.ts";
78
89
  export { DEFAULT_VIEWS_DIRECTORY, EtaViewEngine, htmlResponse, isHtmxRequest, resolveWebLayoutData, } from "../core/view/index.ts";