@getstrata/core 2.0.9 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/core/cache/createCacheStore.d.ts +3 -1
  3. package/dist/core/cache/redisCacheStore.d.ts +21 -8
  4. package/dist/core/cache/repository.d.ts +3 -2
  5. package/dist/core/cache/store.d.ts +4 -1
  6. package/dist/core/cache/taggedCache.d.ts +1 -1
  7. package/dist/core/contracts/container.d.ts +28 -10
  8. package/dist/core/contracts/di.d.ts +7 -4
  9. package/dist/core/contracts/serviceTokens.d.ts +6 -1
  10. package/dist/core/database/baseRepository.d.ts +0 -1
  11. package/dist/core/database/index.d.ts +1 -1
  12. package/dist/core/database/model.d.ts +123 -42
  13. package/dist/core/database/repositoryQuery.d.ts +16 -1
  14. package/dist/core/database/types.d.ts +25 -2
  15. package/dist/core/database/whereBuilder.d.ts +5 -5
  16. package/dist/core/events/deferredModelEvents.d.ts +2 -1
  17. package/dist/core/http/loginThrottleMiddleware.d.ts +6 -5
  18. package/dist/core/http/memoryThrottleMiddleware.d.ts +11 -3
  19. package/dist/core/http/middleware.d.ts +2 -2
  20. package/dist/core/http/response.d.ts +2 -0
  21. package/dist/core/http/routeMiddleware.d.ts +3 -2
  22. package/dist/core/http/scimThrottleMiddleware.d.ts +12 -3
  23. package/dist/core/http/throttleMiddleware.d.ts +24 -6
  24. package/dist/core/lifecycle/gracefulShutdown.d.ts +33 -5
  25. package/dist/core/queue/index.d.ts +7 -0
  26. package/dist/core/queue/redisQueue.d.ts +1 -0
  27. package/dist/core/queue/resilientQueue.d.ts +2 -2
  28. package/dist/core/runtime/applicationRegistry.d.ts +2 -1
  29. package/dist/core/runtime/boundedThrottleStore.d.ts +29 -0
  30. package/dist/core/scheduler/leases.d.ts +40 -0
  31. package/dist/core/scheduler/schedule.d.ts +29 -5
  32. package/dist/core/tenant/tenantContext.d.ts +5 -4
  33. package/dist/core/tenant/tenantMiddleware.d.ts +7 -1
  34. package/dist/core/tracing/otel.d.ts +28 -26
  35. package/dist/core/tracing/tracingMiddleware.d.ts +4 -1
  36. package/dist/entries/audit/exportAuditLogs.js +14 -2
  37. package/dist/entries/auth/basicAuthGuard.js +80 -0
  38. package/dist/entries/auth/jwtGuard.js +80 -0
  39. package/dist/entries/auth/scimAuthMiddleware.js +17 -7
  40. package/dist/entries/auth/sessionGuard.js +80 -0
  41. package/dist/entries/cache/createCacheStore.js +365 -119
  42. package/dist/entries/cache/repository.js +5 -0
  43. package/dist/entries/contracts/container.js +8 -1
  44. package/dist/entries/contracts/serviceTokens.js +83 -0
  45. package/dist/entries/database/factory.js +0 -3
  46. package/dist/entries/database/model.js +1 -2465
  47. package/dist/entries/database/repositoryQuery.js +67 -9
  48. package/dist/entries/http/bodySizeLimitMiddleware.js +76 -10
  49. package/dist/entries/http/memoryThrottleMiddleware.js +276 -21
  50. package/dist/entries/http/routeMiddleware.js +1 -4
  51. package/dist/entries/http/scimThrottleMiddleware.js +576 -54
  52. package/dist/entries/http/throttleMiddleware.js +65 -16
  53. package/dist/entries/jobs/exportAuditLogsJob.js +44 -7
  54. package/dist/entries/jobs/invalidateCacheTagsJob.js +30 -5
  55. package/dist/entries/queue/createAppQueue.js +67 -6
  56. package/dist/entries/queue/publicQueue.js +67 -6
  57. package/dist/entries/queue/queueMetrics.js +67 -6
  58. package/dist/entries/queue/redisQueue.js +3 -0
  59. package/dist/entries/queue.js +30 -5
  60. package/dist/entries/scheduler/schedule.js +1 -72
  61. package/dist/entries/tenant/databaseTenantContext.js +14 -2
  62. package/dist/entries/tenant/tenantDatabaseScope.js +14 -2
  63. package/dist/entries/tracing/tracingMiddleware.js +186 -454
  64. package/dist/framework/public-api.d.ts +14 -10
  65. package/dist/index.js +1770 -638
  66. package/dist/types/services.d.ts +3 -3
  67. package/package.json +17 -8
@@ -1,3 +1,4 @@
1
+ import type { WhereNode } from "./whereBuilder.ts";
1
2
  type DatabaseComparable = string | number | Date;
2
3
  type DatabaseScalar = DatabaseComparable | boolean | null;
3
4
  type QueryOperator = {
@@ -15,6 +16,27 @@ type QueryOperator = {
15
16
  };
16
17
  type QueryFilterValue = DatabaseScalar | readonly DatabaseScalar[] | QueryOperator;
17
18
  type QueryWhere<TEntity> = Partial<Record<keyof TEntity & string, QueryFilterValue>> & Partial<Record<string, QueryFilterValue>>;
19
+ /** Ordinary model filters constrain keys and operators to their declared scalar values. */
20
+ type ModelFilterScalar<T> = unknown extends T ? DatabaseScalar : Extract<T, DatabaseScalar>;
21
+ type ModelFilterOperator<T> = {
22
+ eq?: ModelFilterScalar<T>;
23
+ ne?: ModelFilterScalar<T>;
24
+ in?: readonly ModelFilterScalar<T>[];
25
+ notIn?: readonly ModelFilterScalar<T>[];
26
+ gt?: Extract<ModelFilterScalar<T>, DatabaseComparable>;
27
+ gte?: Extract<ModelFilterScalar<T>, DatabaseComparable>;
28
+ lt?: Extract<ModelFilterScalar<T>, DatabaseComparable>;
29
+ lte?: Extract<ModelFilterScalar<T>, DatabaseComparable>;
30
+ isNull?: boolean;
31
+ } & (Extract<T, string> extends never ? unknown : {
32
+ ilike?: string;
33
+ tsMatch?: string;
34
+ });
35
+ type ModelWhere<TEntity> = string extends keyof TEntity ? QueryWhere<TEntity> : {
36
+ [K in keyof TEntity & string]?: ModelFilterScalar<TEntity[K]> | readonly ModelFilterScalar<TEntity[K]>[] | ModelFilterOperator<TEntity[K]>;
37
+ };
38
+ /** Omission stays legal: DB defaults, generated columns and fillable/timestamp policy are runtime concerns. */
39
+ type ModelWriteValues<TEntity> = Partial<TEntity>;
18
40
  type QueryOrder<TEntity> = {
19
41
  column: keyof TEntity & string;
20
42
  direction?: "ASC" | "DESC" | "asc" | "desc";
@@ -56,7 +78,8 @@ type QuerySelectItem = {
56
78
  query: string;
57
79
  as: string;
58
80
  };
59
- interface QueryOptions<TEntity> {
81
+ interface QueryOptions<TEntity extends object> {
82
+ whereNodes?: WhereNode<TEntity>[];
60
83
  where?: QueryWhere<TEntity>;
61
84
  orderBy?: QueryOrder<TEntity> | QueryOrder<TEntity>[] | QueryOrderShorthand<TEntity>;
62
85
  limit?: number;
@@ -70,4 +93,4 @@ interface QueryOptions<TEntity> {
70
93
  }
71
94
  type MutationValues<TEntity> = Partial<TEntity>;
72
95
  type UpdateValues<TEntity, PrimaryKey extends keyof TEntity & string = keyof TEntity & string> = Partial<Omit<TEntity, PrimaryKey>>;
73
- export type { DatabaseComparable, DatabaseScalar, MutationValues, QueryFilterValue, QueryJoin, QueryJoinOn, QueryOperator, QueryOptions, QueryOrder, QuerySelectItem, QueryWhere, UpdateValues, };
96
+ export type { DatabaseComparable, DatabaseScalar, ModelWhere, ModelWriteValues, MutationValues, QueryFilterValue, QueryJoin, QueryJoinOn, QueryOperator, QueryOptions, QueryOrder, QuerySelectItem, QueryWhere, UpdateValues, };
@@ -14,12 +14,12 @@ type WhereNode<TEntity extends object> = {
14
14
  kind: "and" | "or";
15
15
  exists: ExistsClause;
16
16
  };
17
- declare class WhereBuilder<TEntity extends object> {
17
+ declare class WhereBuilder<TEntity extends object, TWhere extends object = QueryWhere<TEntity>> {
18
18
  readonly nodes: WhereNode<TEntity>[];
19
- where(where: QueryWhere<TEntity>): this;
20
- orWhere(where: QueryWhere<TEntity>): this;
21
- whereGroup(fn: (builder: WhereBuilder<TEntity>) => void): this;
22
- orWhereGroup(fn: (builder: WhereBuilder<TEntity>) => void): this;
19
+ where(where: TWhere): this;
20
+ orWhere(where: TWhere): this;
21
+ whereGroup(fn: (builder: WhereBuilder<TEntity, TWhere>) => void): this;
22
+ orWhereGroup(fn: (builder: WhereBuilder<TEntity, TWhere>) => void): this;
23
23
  }
24
24
  export type { ExistsClause, WhereNode };
25
25
  export { WhereBuilder };
@@ -1,3 +1,4 @@
1
1
  declare function dispatchModelEvent(event: string, payload: unknown): Promise<void>;
2
2
  declare function runWithDeferredModelEvents<T>(callback: () => T | Promise<T>): Promise<T>;
3
- export { dispatchModelEvent, runWithDeferredModelEvents };
3
+ declare function onDeferredModelRollback(callback: () => void): void;
4
+ export { dispatchModelEvent, onDeferredModelRollback, runWithDeferredModelEvents };
@@ -1,6 +1,7 @@
1
- import type { Middleware } from "./middleware";
2
- import { type RedisThrottleClient } from "./throttleMiddleware";
3
- interface LoginThrottleOptions {
1
+ import { type MemoryThrottleStorageOptions } from "../runtime/boundedThrottleStore";
2
+ import type { DisposableMemoryThrottle } from "./memoryThrottleMiddleware";
3
+ import { type DisposableThrottle, type RedisThrottleClient } from "./throttleMiddleware";
4
+ interface LoginThrottleOptions extends MemoryThrottleStorageOptions {
4
5
  redisUrl?: string;
5
6
  maxAttempts: number;
6
7
  decaySeconds: number;
@@ -10,8 +11,8 @@ interface LoginThrottleOptions {
10
11
  }
11
12
  declare function resolveLoginIdentity(request: Request): string;
12
13
  declare function resolveLoginEmail(request: Request): Promise<string>;
13
- declare function createMemoryLoginThrottleMiddleware(options: LoginThrottleOptions): Middleware;
14
- declare function createLoginThrottleMiddleware(options: LoginThrottleOptions): Middleware;
14
+ declare function createMemoryLoginThrottleMiddleware(options: LoginThrottleOptions): DisposableMemoryThrottle;
15
+ declare function createLoginThrottleMiddleware(options: LoginThrottleOptions): DisposableThrottle;
15
16
  declare function resetMemoryLoginThrottleForTests(): void;
16
17
  export type { LoginThrottleOptions };
17
18
  export { createLoginThrottleMiddleware, createMemoryLoginThrottleMiddleware, resetMemoryLoginThrottleForTests, resolveLoginEmail, resolveLoginIdentity, };
@@ -1,10 +1,18 @@
1
+ import { type MemoryThrottleStats, type MemoryThrottleStorageOptions } from "../runtime/boundedThrottleStore";
1
2
  import type { Middleware } from "./middleware";
2
- interface MemoryThrottleOptions {
3
+ import { type ThrottleQuotaPolicy } from "./throttleMiddleware";
4
+ interface MemoryThrottleOptions extends MemoryThrottleStorageOptions {
3
5
  maxAttempts: number;
4
6
  decaySeconds: number;
5
7
  keyPrefix?: string;
8
+ quotaPolicy?: ThrottleQuotaPolicy;
6
9
  }
7
- declare function createMemoryThrottleMiddleware(options: MemoryThrottleOptions): Middleware;
10
+ type DisposableMemoryThrottle = Middleware & {
11
+ dispose(): void;
12
+ stats(): MemoryThrottleStats;
13
+ };
14
+ declare function createMemoryThrottleMiddleware(options: MemoryThrottleOptions): DisposableMemoryThrottle;
15
+ /** Lazy generation reset avoids retaining middleware instances in a global test registry. */
8
16
  declare function resetMemoryThrottleForTests(): void;
9
- export type { MemoryThrottleOptions };
17
+ export type { DisposableMemoryThrottle, MemoryThrottleOptions, MemoryThrottleStats, MemoryThrottleStorageOptions, };
10
18
  export { createMemoryThrottleMiddleware, resetMemoryThrottleForTests };
@@ -1,6 +1,6 @@
1
1
  type Middleware = (request: Request, next: () => Promise<Response>) => Promise<Response>;
2
- type RouteHandler = (request: Request) => Response | Promise<Response>;
3
- declare function composeMiddleware(...middleware: Middleware[]): (handler: RouteHandler) => RouteHandler;
2
+ type RouteHandler<TRequest extends Request = Request> = (request: TRequest) => Response | Promise<Response>;
3
+ declare function composeMiddleware(...middleware: Middleware[]): <TRequest extends Request = Request>(handler: RouteHandler<TRequest>) => RouteHandler<TRequest>;
4
4
  declare function requestIdMiddleware(request: Request, next: () => Promise<Response>): Promise<Response>;
5
5
  declare function wrapRouteHandler<T>(handler: T, middleware: Middleware[]): T;
6
6
  declare function applyMiddlewareToRoutes<T extends Record<string, unknown>>(routes: T, middleware: Middleware[]): T;
@@ -3,7 +3,9 @@ declare function jsonResponse(data: unknown, init?: ResponseInit): Response;
3
3
  declare function createdResponse(data: unknown, init?: ResponseInit): Response;
4
4
  declare function noContentResponse(): Response;
5
5
  declare function errorResponse(error: unknown): Response;
6
+ declare function withErrorHandling<THandler extends (request: Request) => Response | Promise<Response>>(handler: THandler): (...args: Parameters<THandler>) => Promise<Response>;
6
7
  declare function withErrorHandling<TArgs extends unknown[]>(handler: (...args: TArgs) => Response | Promise<Response>): (...args: TArgs) => Promise<Response>;
8
+ declare function withJsonErrorHandling<THandler extends (request: Request) => Response | Promise<Response>>(handler: THandler): (...args: Parameters<THandler>) => Promise<Response>;
7
9
  declare function withJsonErrorHandling<TArgs extends unknown[]>(handler: (...args: TArgs) => Response | Promise<Response>): (...args: TArgs) => Promise<Response>;
8
10
  declare function createJsonErrorMiddleware(): Middleware;
9
11
  declare function createWebErrorMiddleware(): Middleware;
@@ -1,3 +1,4 @@
1
- import { type Middleware, type RouteHandler } from "./middleware";
2
- declare function withMiddleware(...middleware: Middleware[]): (handler: RouteHandler) => RouteHandler;
1
+ import { type Middleware } from "./middleware";
2
+ /** Middleware consumes native Request; composition preserves the handler's request subtype. */
3
+ declare function withMiddleware(...middleware: Middleware[]): <TRequest extends Request = Request>(handler: import("./middleware").RouteHandler<TRequest>) => import("./middleware").RouteHandler<TRequest>;
3
4
  export { withMiddleware };
@@ -1,9 +1,18 @@
1
+ import { type MemoryThrottleStorageOptions } from "../runtime/boundedThrottleStore";
1
2
  import type { Middleware } from "./middleware";
2
- interface ScimThrottleOptions {
3
+ import { type RedisThrottleClient } from "./throttleMiddleware";
4
+ interface ScimThrottleOptions extends MemoryThrottleStorageOptions {
3
5
  redisUrl?: string;
4
6
  maxAttempts: number;
5
7
  decaySeconds: number;
8
+ commandTimeoutMs?: number;
9
+ redisClient?: RedisThrottleClient;
10
+ keyPrefix?: string;
6
11
  }
7
- declare function createScimThrottleMiddleware(options: ScimThrottleOptions): Middleware;
12
+ declare function resolveScimIdentity(request: Request): string;
13
+ declare function createScimThrottleMiddleware(options: ScimThrottleOptions): Middleware & {
14
+ dispose: () => void;
15
+ stats: () => import("./memoryThrottleMiddleware").MemoryThrottleStats | undefined;
16
+ };
8
17
  export type { ScimThrottleOptions };
9
- export { createScimThrottleMiddleware };
18
+ export { createScimThrottleMiddleware, resolveScimIdentity };
@@ -1,5 +1,15 @@
1
+ import { type TenantContext } from "@getstrata/core/tenant/tenantContext";
1
2
  import { RedisClient } from "bun";
2
3
  import type { Middleware } from "./middleware";
4
+ interface ThrottleQuotaContext {
5
+ readonly request: Request;
6
+ readonly tenant: TenantContext | null;
7
+ readonly identity: string;
8
+ readonly routeTemplate: string;
9
+ readonly maxAttempts: number;
10
+ }
11
+ /** A synchronous application-owned attempt limit; the window remains fixed. */
12
+ type ThrottleQuotaPolicy = (context: ThrottleQuotaContext) => number;
3
13
  interface ThrottleOptions {
4
14
  redisUrl: string;
5
15
  maxAttempts: number;
@@ -7,14 +17,22 @@ interface ThrottleOptions {
7
17
  keyPrefix?: string;
8
18
  commandTimeoutMs?: number;
9
19
  redisClient?: RedisThrottleClient;
20
+ quotaPolicy?: ThrottleQuotaPolicy;
10
21
  }
11
22
  declare function resolveThrottleIdentity(request: Request): string;
12
23
  type RedisThrottleClient = Pick<RedisClient, "send">;
13
- declare function redisThrottleKey(request: Request, prefix: string, identity: string): string;
14
- declare function consumeRedisThrottle(client: RedisThrottleClient, key: string, decaySeconds: number, timeoutMs?: number): Promise<number>;
24
+ declare function redisThrottleKey(request: Request, prefix: string, identity: string, tenantId?: number | null): string;
25
+ declare function consumeRedisThrottle(client: RedisThrottleClient, key: string, decaySeconds: number, timeoutMs?: number, signal?: AbortSignal): Promise<number>;
15
26
  /** Reuse a healthy connection; failed owned connections are recreated on the next request. */
16
- declare function createRedisThrottleConsumer(options: Pick<ThrottleOptions, "redisUrl" | "redisClient" | "commandTimeoutMs">): (key: string, decaySeconds: number) => Promise<number>;
27
+ declare function createRedisThrottleConsumer(options: Pick<ThrottleOptions, "redisUrl" | "redisClient" | "commandTimeoutMs">): ((key: string, decaySeconds: number) => Promise<number>) & {
28
+ isDisposed: () => boolean;
29
+ dispose(): void;
30
+ };
31
+ type DisposableThrottle = Middleware & {
32
+ dispose(): void;
33
+ };
17
34
  declare function throttleUnavailableResponse(): Response;
18
- declare function createThrottleMiddleware(options: ThrottleOptions): Middleware;
19
- export type { RedisThrottleClient, ThrottleOptions };
20
- export { consumeRedisThrottle, createRedisThrottleConsumer, createThrottleMiddleware, redisThrottleKey, resolveThrottleIdentity, throttleUnavailableResponse, };
35
+ declare function resolveThrottleQuota(request: Request, options: Pick<ThrottleOptions, "maxAttempts" | "quotaPolicy">, identity?: string): number;
36
+ declare function createThrottleMiddleware(options: ThrottleOptions): DisposableThrottle;
37
+ export type { DisposableThrottle, RedisThrottleClient, ThrottleOptions, ThrottleQuotaContext, ThrottleQuotaPolicy, };
38
+ export { consumeRedisThrottle, createRedisThrottleConsumer, createThrottleMiddleware, redisThrottleKey, resolveThrottleIdentity, resolveThrottleQuota, throttleUnavailableResponse, };
@@ -1,6 +1,34 @@
1
- type ShutdownHandler = () => void | Promise<void>;
2
- declare function registerShutdownHandler(name: string, handler: ShutdownHandler): () => void;
3
- declare function runGracefulShutdown(signal: string): Promise<void>;
4
- declare function installGracefulShutdownSignals(signals?: NodeJS.Signals[]): void;
1
+ type ShutdownPhase = "stop" | "drain" | "flush" | "close" | "force";
2
+ type ShutdownContext = {
3
+ signal: AbortSignal;
4
+ reason: string;
5
+ };
6
+ type ShutdownHandler = (context: ShutdownContext) => void | Promise<void>;
7
+ type ShutdownFailure = {
8
+ name: string;
9
+ phase: ShutdownPhase;
10
+ error: unknown;
11
+ };
12
+ type ShutdownResult = {
13
+ successful: boolean;
14
+ timedOut: boolean;
15
+ errors: ShutdownFailure[];
16
+ };
17
+ declare class LifecycleCoordinator {
18
+ private readonly handlers;
19
+ private running?;
20
+ private readonly timeoutMs;
21
+ constructor(options?: {
22
+ timeoutMs?: number;
23
+ });
24
+ get isShuttingDown(): boolean;
25
+ register(name: string, handler: ShutdownHandler, phase?: ShutdownPhase): () => void;
26
+ shutdown(reason?: string): Promise<ShutdownResult>;
27
+ private execute;
28
+ }
29
+ declare function registerShutdownHandler(name: string, handler: ShutdownHandler, phase?: ShutdownPhase): () => void;
30
+ declare function runGracefulShutdown(signal: string): Promise<ShutdownResult>;
31
+ declare function installGracefulShutdownSignals(signals?: NodeJS.Signals[], lifecycle?: LifecycleCoordinator): () => void;
5
32
  declare function resetGracefulShutdownForTests(): void;
6
- export { installGracefulShutdownSignals, registerShutdownHandler, resetGracefulShutdownForTests, runGracefulShutdown, };
33
+ export type { ShutdownContext, ShutdownFailure, ShutdownHandler, ShutdownPhase, ShutdownResult };
34
+ export { installGracefulShutdownSignals, LifecycleCoordinator, registerShutdownHandler, resetGracefulShutdownForTests, runGracefulShutdown, };
@@ -8,13 +8,20 @@ declare abstract class Job<TPayload extends object = object> {
8
8
  abstract handle(payload: TPayload): Promise<void>;
9
9
  }
10
10
  interface Queue {
11
+ close?(): void | Promise<void>;
11
12
  dispatch<TPayload extends object>(job: Job<TPayload>, payload: TPayload): Promise<void>;
12
13
  }
13
14
  declare class SyncQueue implements Queue {
14
15
  dispatch<TPayload extends object>(job: Job<TPayload>, payload: TPayload): Promise<void>;
15
16
  }
16
17
  declare class AsyncQueue implements Queue {
18
+ private readonly active;
19
+ private closing?;
20
+ private closed;
21
+ protected assertOpen(): void;
22
+ protected enqueue(task: () => Promise<void>, onError: (error: unknown) => void): void;
17
23
  dispatch<TPayload extends object>(job: Job<TPayload>, payload: TPayload): Promise<void>;
24
+ close(): Promise<void>;
18
25
  }
19
26
  declare function createQueue(driver: "sync" | "async"): Queue;
20
27
  export type { Queue, QueuePriority };
@@ -23,6 +23,7 @@ declare function reclaimExpiredQueueReservations(client: RedisClient, queueKeys?
23
23
  declare class RedisQueue implements Queue {
24
24
  private readonly client;
25
25
  constructor(redisUrl: string);
26
+ close(): void;
26
27
  dispatch<TPayload extends object>(job: Job<TPayload>, payload: TPayload): Promise<void>;
27
28
  }
28
29
  declare class QueueWorker {
@@ -1,6 +1,6 @@
1
1
  import type FailedJobService from "./failedJobService";
2
- import type { Job, Queue } from "./index";
3
- declare class ResilientQueue implements Queue {
2
+ import { AsyncQueue, type Job } from "./index";
3
+ declare class ResilientQueue extends AsyncQueue {
4
4
  private readonly failedJobs;
5
5
  private readonly asyncDispatch;
6
6
  constructor(failedJobs: FailedJobService, asyncDispatch?: boolean);
@@ -6,6 +6,7 @@ import type { AppContext } from "../contracts/di";
6
6
  import { type Logger } from "../logging/logger";
7
7
  import type { Queue } from "../queue";
8
8
  declare function setActiveApplicationContext(context: AppContext): void;
9
+ declare function clearActiveApplicationContext(context: AppContext): void;
9
10
  declare function resolveApplicationCache(): CacheLike;
10
11
  declare function resolveApplicationQueue(): Queue;
11
12
  declare function resolveApplicationEventBus(): EventBus;
@@ -14,4 +15,4 @@ declare function resolveApplicationPolicyGate(): PolicyGate;
14
15
  declare function resolveApplicationConfig(): import("../contracts/container").ConfigStoreLike;
15
16
  declare function resolveApplicationLogger(): Logger;
16
17
  declare function resolveApplicationDependencies(): import("../contracts/di").AppDependencies;
17
- export { resolveApplicationAuth, resolveApplicationCache, resolveApplicationConfig, resolveApplicationDependencies, resolveApplicationEventBus, resolveApplicationLogger, resolveApplicationPolicyGate, resolveApplicationQueue, setActiveApplicationContext, };
18
+ export { clearActiveApplicationContext, resolveApplicationAuth, resolveApplicationCache, resolveApplicationConfig, resolveApplicationDependencies, resolveApplicationEventBus, resolveApplicationLogger, resolveApplicationPolicyGate, resolveApplicationQueue, setActiveApplicationContext, };
@@ -0,0 +1,29 @@
1
+ interface MemoryThrottleStorageOptions {
2
+ /** Maximum live identities retained per middleware instance. */
3
+ maxBuckets?: number;
4
+ /** Maximum expired entries removed on each attempt. */
5
+ pruneBatchSize?: number;
6
+ }
7
+ interface MemoryThrottleStats {
8
+ readonly retainedBuckets: number;
9
+ readonly prunedOnLastConsume: number;
10
+ readonly disposed: boolean;
11
+ }
12
+ /** Fixed-window insertion order also orders expiry; hits never refresh a window. */
13
+ declare class BoundedThrottleStore {
14
+ private readonly decayMs;
15
+ private readonly now;
16
+ private readonly buckets;
17
+ private readonly maxBuckets;
18
+ private readonly pruneBatchSize;
19
+ private disposed;
20
+ private pruned;
21
+ constructor(decayMs: number, options?: MemoryThrottleStorageOptions, now?: () => number);
22
+ /** null means closed or saturated: callers must fail closed, never evict live buckets. */
23
+ consume(key: string): number | null;
24
+ clear(): void;
25
+ dispose(): void;
26
+ stats(): MemoryThrottleStats;
27
+ }
28
+ export type { MemoryThrottleStats, MemoryThrottleStorageOptions };
29
+ export { BoundedThrottleStore };
@@ -0,0 +1,40 @@
1
+ import { RedisClient } from "bun";
2
+ type SchedulerLease = {
3
+ taskId: string;
4
+ occurrenceId: string;
5
+ token: string;
6
+ };
7
+ interface SchedulerLeaseStore {
8
+ acquire(lease: SchedulerLease, leaseMs: number): Promise<boolean>;
9
+ renew(lease: SchedulerLease, leaseMs: number): Promise<boolean>;
10
+ complete(lease: SchedulerLease, retentionMs: number): Promise<boolean>;
11
+ release(lease: SchedulerLease): Promise<void>;
12
+ }
13
+ declare class RedisSchedulerLeaseStore implements SchedulerLeaseStore {
14
+ private readonly client;
15
+ private readonly owned?;
16
+ private readonly namespace;
17
+ private readonly timeoutMs;
18
+ constructor(options: {
19
+ redisUrl: string;
20
+ namespace: string;
21
+ commandTimeoutMs?: number;
22
+ client?: Pick<RedisClient, "send">;
23
+ });
24
+ private keys;
25
+ private execute;
26
+ acquire(lease: SchedulerLease, leaseMs: number): Promise<boolean>;
27
+ renew(lease: SchedulerLease, leaseMs: number): Promise<boolean>;
28
+ complete(lease: SchedulerLease, retentionMs: number): Promise<boolean>;
29
+ release(lease: SchedulerLease): Promise<void>;
30
+ close(): void;
31
+ }
32
+ declare class SingleRunnerLeaseStore implements SchedulerLeaseStore {
33
+ private readonly tasks;
34
+ acquire(lease: SchedulerLease, leaseMs: number): Promise<boolean>;
35
+ renew(lease: SchedulerLease, leaseMs: number): Promise<boolean>;
36
+ complete(lease: SchedulerLease): Promise<boolean>;
37
+ release(lease: SchedulerLease): Promise<void>;
38
+ }
39
+ export type { SchedulerLease, SchedulerLeaseStore };
40
+ export { RedisSchedulerLeaseStore, SingleRunnerLeaseStore };
@@ -1,15 +1,39 @@
1
+ import { RedisSchedulerLeaseStore, type SchedulerLeaseStore } from "./leases";
2
+ type ScheduledTaskContext = {
3
+ taskName: string;
4
+ occurrenceId: string;
5
+ scheduledAt: Date;
6
+ /** Aborted on loss/uncertainty of lease ownership, not normal admission shutdown. */
7
+ signal: AbortSignal;
8
+ assertOwnership(): Promise<void>;
9
+ };
1
10
  type ScheduledTask = {
2
11
  expression: string;
3
12
  name: string;
4
- run: () => void | Promise<void>;
13
+ run: (context: ScheduledTaskContext) => void | Promise<void>;
14
+ };
15
+ type SchedulerRunOptions = {
16
+ signal?: AbortSignal;
17
+ coordination?: "redis" | "single-runner";
18
+ redisUrl?: string;
19
+ namespace?: string;
20
+ leaseStore?: SchedulerLeaseStore;
21
+ leaseMs?: number;
22
+ renewalMs?: number;
23
+ commandTimeoutMs?: number;
24
+ completedRetentionMs?: number;
5
25
  };
26
+ declare class SchedulerLeaseLostError extends Error {
27
+ constructor(taskName: string, options?: ErrorOptions);
28
+ }
6
29
  declare class Schedule {
7
30
  private readonly tasks;
8
- command(expression: string, name: string, run: () => void | Promise<void>): this;
31
+ command(expression: string, name: string, run: ScheduledTask["run"]): this;
9
32
  dueTasks(now?: Date): ScheduledTask[];
10
33
  tasksList(): ScheduledTask[];
11
34
  }
12
35
  declare const appSchedule: Schedule;
13
- declare function runDueScheduledTasks(schedule?: Schedule, now?: Date): Promise<number>;
14
- export type { ScheduledTask };
15
- export { appSchedule, runDueScheduledTasks, Schedule };
36
+ declare function runDueScheduledTasks(schedule?: Schedule, now?: Date, options?: SchedulerRunOptions): Promise<number>;
37
+ export type { SchedulerLease, SchedulerLeaseStore } from "./leases";
38
+ export type { ScheduledTask, ScheduledTaskContext, SchedulerRunOptions };
39
+ export { appSchedule, RedisSchedulerLeaseStore, runDueScheduledTasks, Schedule, SchedulerLeaseLostError, };
@@ -1,13 +1,14 @@
1
1
  type TenantContext = {
2
2
  id: number;
3
3
  slug: string;
4
- plan: "free" | "pro" | "enterprise";
5
- region: "eu" | "us" | "apac";
4
+ /** Trusted application data; never inferred from request headers. */
5
+ metadata?: Readonly<Record<string, unknown>>;
6
+ /** Existing application extensions remain opaque to core. */
7
+ [key: string]: unknown;
6
8
  };
7
9
  declare const tenantContext: import("node:async_hooks").AsyncLocalStorage<TenantContext>;
8
10
  declare function runWithTenant<T>(tenant: TenantContext, callback: () => T | Promise<T>): T | Promise<T>;
9
11
  declare function currentTenant(): TenantContext | null;
10
12
  declare function currentTenantId(): number;
11
- declare function rateLimitMultiplierForPlan(plan: TenantContext["plan"]): number;
12
13
  export type { TenantContext };
13
- export { currentTenant, currentTenantId, rateLimitMultiplierForPlan, runWithTenant, tenantContext };
14
+ export { currentTenant, currentTenantId, runWithTenant, tenantContext };
@@ -2,5 +2,11 @@ import { type TenantContext } from "./tenantContext";
2
2
  declare const DEFAULT_TENANT: TenantContext;
3
3
  declare function resolveUserTenantId(userId: number): Promise<number>;
4
4
  declare function auditChecksum(payload: Record<string, unknown>): string;
5
- declare function createTenantMiddleware(): (request: Request, next: () => Promise<Response>) => Promise<Response>;
5
+ type TenantResolver = (id: number) => Promise<TenantContext | null>;
6
+ interface TenantMiddlewareOptions {
7
+ /** Load trusted application metadata after the framework selects the tenant ID. */
8
+ resolveTenant?: TenantResolver;
9
+ }
10
+ declare function createTenantMiddleware(options?: TenantMiddlewareOptions): (request: Request, next: () => Promise<Response>) => Promise<Response>;
11
+ export type { TenantMiddlewareOptions, TenantResolver };
6
12
  export { auditChecksum, createTenantMiddleware, DEFAULT_TENANT, resolveUserTenantId };
@@ -1,26 +1,28 @@
1
- interface OtelSpan {
2
- traceId: string;
3
- spanId: string;
4
- name: string;
5
- startTimeUnixNano: string;
6
- endTimeUnixNano: string;
7
- attributes: Array<{
8
- key: string;
9
- value: {
10
- stringValue: string;
11
- };
12
- }>;
13
- status: {
14
- code: number;
15
- };
16
- }
17
- declare function createSpan(input: {
18
- traceId: string;
19
- name: string;
20
- startedAt: number;
21
- endedAt: number;
22
- attributes?: Record<string, string>;
23
- }): OtelSpan;
24
- declare function exportOtelSpan(span: OtelSpan): Promise<void>;
25
- export type { OtelSpan };
26
- export { createSpan, exportOtelSpan };
1
+ import { type Context, type Tracer } from "@opentelemetry/api";
2
+ import { W3CTraceContextPropagator } from "@opentelemetry/core";
3
+ import { type SpanExporter } from "@opentelemetry/sdk-trace-base";
4
+ type TracingOptions = {
5
+ exporter?: SpanExporter;
6
+ endpoint?: string;
7
+ serviceName?: string;
8
+ sampleRatio?: number;
9
+ maxQueueSize?: number;
10
+ maxExportBatchSize?: number;
11
+ scheduledDelayMillis?: number;
12
+ exportTimeoutMillis?: number;
13
+ };
14
+ type TracingRuntime = {
15
+ tracer: Tracer;
16
+ propagator: W3CTraceContextPropagator;
17
+ run<T>(parent: Context, callback: () => T): T;
18
+ forceFlush(): Promise<void>;
19
+ shutdown(): Promise<void>;
20
+ };
21
+ declare function createTracingRuntime(options?: TracingOptions): TracingRuntime;
22
+ declare function getTracingRuntime(): TracingRuntime;
23
+ declare function acquireTracingRuntime(): {
24
+ flush(): Promise<void>;
25
+ release(): Promise<void>;
26
+ };
27
+ export type { TracingOptions, TracingRuntime };
28
+ export { acquireTracingRuntime, createTracingRuntime, getTracingRuntime };
@@ -1,3 +1,6 @@
1
1
  import type { Middleware } from "@getstrata/core/http/middleware";
2
- declare function createTracingMiddleware(): Middleware;
2
+ import { type TracingRuntime } from "./otel";
3
+ declare function createTracingMiddleware(runtime?: TracingRuntime): Middleware;
4
+ export type { TracingOptions, TracingRuntime } from "./otel";
5
+ export { acquireTracingRuntime, createTracingRuntime, getTracingRuntime } from "./otel";
3
6
  export { createTracingMiddleware };
@@ -306,9 +306,12 @@ async function runWithDeferredModelEvents(callback) {
306
306
  const existing = deferredModelEvents.getStore();
307
307
  if (existing) {
308
308
  existing.frames.push([]);
309
+ existing.rollbackFrames.push([]);
309
310
  try {
310
311
  const result = await callback();
311
312
  const nested = existing.frames.pop() ?? [];
313
+ const rollback = existing.rollbackFrames.pop() ?? [];
314
+ existing.rollbackFrames.at(-1)?.push(...rollback);
312
315
  const parent = existing.frames[existing.frames.length - 1];
313
316
  if (parent) {
314
317
  parent.push(...nested);
@@ -316,11 +319,20 @@ async function runWithDeferredModelEvents(callback) {
316
319
  return result;
317
320
  } catch (error) {
318
321
  existing.frames.pop();
322
+ for (const rollback of (existing.rollbackFrames.pop() ?? []).reverse())
323
+ rollback();
319
324
  throw error;
320
325
  }
321
326
  }
322
- const state = { frames: [[]] };
323
- const result = await deferredModelEvents.run(state, callback);
327
+ const state = { frames: [[]], rollbackFrames: [[]] };
328
+ let result;
329
+ try {
330
+ result = await deferredModelEvents.run(state, callback);
331
+ } catch (error) {
332
+ for (const rollback of (state.rollbackFrames[0] ?? []).reverse())
333
+ rollback();
334
+ throw error;
335
+ }
324
336
  await dispatchQueuedModelEvents(state.frames[0] ?? []);
325
337
  return result;
326
338
  }