@getstrata/core 2.1.0 → 2.2.1
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/CHANGELOG.md +40 -0
- package/dist/core/cache/createCacheStore.d.ts +3 -1
- package/dist/core/cache/redisCacheStore.d.ts +21 -8
- package/dist/core/cache/repository.d.ts +3 -2
- package/dist/core/cache/store.d.ts +4 -1
- package/dist/core/cache/taggedCache.d.ts +1 -1
- package/dist/core/contracts/di.d.ts +4 -2
- package/dist/core/contracts/serviceTokens.d.ts +7 -1
- package/dist/core/database/baseRepository.d.ts +0 -1
- package/dist/core/database/index.d.ts +1 -1
- package/dist/core/database/model.d.ts +51 -22
- package/dist/core/database/repositoryQuery.d.ts +16 -1
- package/dist/core/database/types.d.ts +25 -2
- package/dist/core/database/whereBuilder.d.ts +5 -5
- package/dist/core/events/deferredModelEvents.d.ts +2 -1
- package/dist/core/http/loginThrottleMiddleware.d.ts +6 -5
- package/dist/core/http/memoryThrottleMiddleware.d.ts +11 -3
- package/dist/core/http/requestMetaContext.d.ts +2 -0
- package/dist/core/http/scimThrottleMiddleware.d.ts +12 -3
- package/dist/core/http/throttleMiddleware.d.ts +24 -6
- package/dist/core/lifecycle/gracefulShutdown.d.ts +33 -5
- package/dist/core/queue/createAppQueue.d.ts +2 -1
- package/dist/core/queue/failedJobService.d.ts +3 -1
- package/dist/core/queue/index.d.ts +17 -2
- package/dist/core/queue/jobRunner.d.ts +7 -1
- package/dist/core/queue/publicQueue.d.ts +2 -1
- package/dist/core/queue/queueConfig.d.ts +3 -0
- package/dist/core/queue/redisQueue.d.ts +6 -7
- package/dist/core/queue/redisQueueKeys.d.ts +8 -0
- package/dist/core/queue/redisStreams.d.ts +73 -0
- package/dist/core/queue/resilientQueue.d.ts +2 -2
- package/dist/core/queue/types.d.ts +1 -0
- package/dist/core/runtime/applicationRegistry.d.ts +2 -1
- package/dist/core/runtime/boundedThrottleStore.d.ts +29 -0
- package/dist/core/scheduler/leases.d.ts +40 -0
- package/dist/core/scheduler/schedule.d.ts +29 -5
- package/dist/core/tenant/publicTenantHost.d.ts +11 -0
- package/dist/core/tenant/tenantContext.d.ts +5 -4
- package/dist/core/tenant/tenantMiddleware.d.ts +11 -1
- package/dist/core/tracing/otel.d.ts +28 -26
- package/dist/core/tracing/tracingMiddleware.d.ts +4 -1
- package/dist/entries/audit/exportAuditLogs.js +14 -2
- package/dist/entries/auth/basicAuthGuard.js +81 -0
- package/dist/entries/auth/jwtGuard.js +81 -0
- package/dist/entries/auth/scimAuthMiddleware.js +17 -7
- package/dist/entries/auth/sessionGuard.js +81 -0
- package/dist/entries/cache/createCacheStore.js +365 -119
- package/dist/entries/cache/repository.js +5 -0
- package/dist/entries/contracts/serviceTokens.js +85 -0
- package/dist/entries/database/factory.js +0 -3
- package/dist/entries/database/model.js +1 -2486
- package/dist/entries/database/repositoryQuery.js +67 -9
- package/dist/entries/http/bodySizeLimitMiddleware.js +76 -10
- package/dist/entries/http/memoryThrottleMiddleware.js +276 -21
- package/dist/entries/http/scimThrottleMiddleware.js +576 -54
- package/dist/entries/http/throttleMiddleware.js +65 -16
- package/dist/entries/jobs/exportAuditLogsJob.js +44 -7
- package/dist/entries/jobs/invalidateCacheTagsJob.js +30 -5
- package/dist/entries/queue/createAppQueue.js +748 -72
- package/dist/entries/queue/failedJobRepository.js +1 -1
- package/dist/entries/queue/failedJobService.js +5 -1
- package/dist/entries/queue/jobRunner.js +67 -2
- package/dist/entries/queue/publicQueue.js +746 -71
- package/dist/entries/queue/queueMetrics.js +761 -78
- package/dist/entries/queue/redisQueue.js +768 -148
- package/dist/entries/queue.js +30 -5
- package/dist/entries/scheduler/schedule.js +1 -72
- package/dist/entries/tenant/databaseTenantContext.js +14 -2
- package/dist/entries/tenant/tenantDatabaseScope.js +14 -2
- package/dist/entries/tracing/tracingMiddleware.js +186 -454
- package/dist/framework/public-api.d.ts +14 -10
- package/dist/index.js +2444 -688
- package/dist/types/services.d.ts +3 -3
- package/package.json +17 -8
|
@@ -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
|
|
19
|
-
|
|
20
|
-
export {
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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 {
|
|
33
|
+
export type { ShutdownContext, ShutdownFailure, ShutdownHandler, ShutdownPhase, ShutdownResult };
|
|
34
|
+
export { installGracefulShutdownSignals, LifecycleCoordinator, registerShutdownHandler, resetGracefulShutdownForTests, runGracefulShutdown, };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Queue } from "./index";
|
|
2
2
|
import { createFailedJobService, createQueueWorker, createTrackedJob, type FailedJobService } from "./publicQueue";
|
|
3
|
+
import { resolveQueueConfig } from "./queueConfig";
|
|
3
4
|
declare const FAILED_JOB_SERVICE_TOKEN = "core.failedJobs";
|
|
4
5
|
declare function createAppQueue(driver: "sync" | "async" | "redis", redisUrl?: string, failedJobs?: FailedJobService, registerJobs?: () => void): Queue;
|
|
5
|
-
export { createAppQueue, createFailedJobService, createQueueWorker, createTrackedJob, FAILED_JOB_SERVICE_TOKEN, };
|
|
6
|
+
export { createAppQueue, createFailedJobService, createQueueWorker, createTrackedJob, FAILED_JOB_SERVICE_TOKEN, resolveQueueConfig, };
|
|
@@ -5,11 +5,13 @@ declare class FailedJobService {
|
|
|
5
5
|
constructor(repository: FailedJobRepository);
|
|
6
6
|
recordFailure(input: {
|
|
7
7
|
jobName: string;
|
|
8
|
+
jobId?: string;
|
|
8
9
|
payload: Record<string, unknown>;
|
|
9
10
|
exception: string;
|
|
10
11
|
}): Promise<FailedJobRecord>;
|
|
11
12
|
listRecent(limit?: number): Promise<FailedJobRecord[]>;
|
|
12
|
-
|
|
13
|
+
/** Admit the replay before deleting its recovery record. Delivery remains at least once. */
|
|
14
|
+
retry(id: number, enqueue: (record: FailedJobRecord) => Promise<void>): Promise<FailedJobRecord>;
|
|
13
15
|
delete(id: number): Promise<void>;
|
|
14
16
|
flush(): Promise<number>;
|
|
15
17
|
}
|
|
@@ -1,21 +1,36 @@
|
|
|
1
1
|
type QueuePriority = "high" | "default" | "low";
|
|
2
|
+
/** Logical identity is stable across automatic recovery and supported manual replay. */
|
|
3
|
+
interface JobContext {
|
|
4
|
+
readonly jobId: string;
|
|
5
|
+
readonly signal: AbortSignal;
|
|
6
|
+
readonly deadlineAtMs?: number;
|
|
7
|
+
}
|
|
2
8
|
declare abstract class Job<TPayload extends object = object> {
|
|
3
9
|
static readonly jobName?: string;
|
|
4
10
|
readonly maxAttempts?: number;
|
|
5
11
|
readonly backoffMs?: number;
|
|
6
12
|
readonly priority?: QueuePriority;
|
|
7
13
|
constructor();
|
|
8
|
-
abstract handle(payload: TPayload): Promise<void>;
|
|
14
|
+
abstract handle(payload: TPayload, context?: JobContext): Promise<void>;
|
|
9
15
|
}
|
|
10
16
|
interface Queue {
|
|
17
|
+
close?(): void | Promise<void>;
|
|
18
|
+
/** Preserve a stored logical identity on deliberate replay; reset execution controls. */
|
|
19
|
+
replay?<TPayload extends object>(job: Job<TPayload>, payload: TPayload, jobId: string): Promise<void>;
|
|
11
20
|
dispatch<TPayload extends object>(job: Job<TPayload>, payload: TPayload): Promise<void>;
|
|
12
21
|
}
|
|
13
22
|
declare class SyncQueue implements Queue {
|
|
14
23
|
dispatch<TPayload extends object>(job: Job<TPayload>, payload: TPayload): Promise<void>;
|
|
15
24
|
}
|
|
16
25
|
declare class AsyncQueue implements Queue {
|
|
26
|
+
private readonly active;
|
|
27
|
+
private closing?;
|
|
28
|
+
private closed;
|
|
29
|
+
protected assertOpen(): void;
|
|
30
|
+
protected enqueue(task: () => Promise<void>, onError: (error: unknown) => void): void;
|
|
17
31
|
dispatch<TPayload extends object>(job: Job<TPayload>, payload: TPayload): Promise<void>;
|
|
32
|
+
close(): Promise<void>;
|
|
18
33
|
}
|
|
19
34
|
declare function createQueue(driver: "sync" | "async"): Queue;
|
|
20
|
-
export type { Queue, QueuePriority };
|
|
35
|
+
export type { JobContext, Queue, QueuePriority };
|
|
21
36
|
export { AsyncQueue, createQueue, Job, SyncQueue };
|
|
@@ -3,9 +3,15 @@ interface QueueJobEnvelope {
|
|
|
3
3
|
name: string;
|
|
4
4
|
payload: Record<string, unknown>;
|
|
5
5
|
attempts?: number;
|
|
6
|
+
jobId?: string;
|
|
7
|
+
deadlineAtMs?: number;
|
|
8
|
+
cancellable?: boolean;
|
|
6
9
|
}
|
|
10
|
+
declare function parseQueueJobEnvelope(rawPayload: string): QueueJobEnvelope | null;
|
|
7
11
|
declare function runQueueJob(envelope: QueueJobEnvelope, failedJobs: FailedJobService, options?: {
|
|
8
12
|
onFailureRecorded?: () => void;
|
|
13
|
+
context?: import("./index").JobContext;
|
|
14
|
+
deferRetry?: (envelope: QueueJobEnvelope, delayMs: number) => Promise<void>;
|
|
9
15
|
}): Promise<void>;
|
|
10
16
|
export type { QueueJobEnvelope };
|
|
11
|
-
export { runQueueJob };
|
|
17
|
+
export { parseQueueJobEnvelope, runQueueJob };
|
|
@@ -4,6 +4,7 @@ import type { Job, Queue } from "./index.ts";
|
|
|
4
4
|
import { jobRegistry } from "./jobRegistry.ts";
|
|
5
5
|
import { runQueueJob } from "./jobRunner.ts";
|
|
6
6
|
import { QueueWorker, RedisQueue } from "./redisQueue.ts";
|
|
7
|
+
import { RedisStreamsWorker } from "./redisStreams";
|
|
7
8
|
import { ResilientQueue } from "./resilientQueue.ts";
|
|
8
9
|
declare function createFailedJobService(): FailedJobService;
|
|
9
10
|
declare function createTrackedJob<TPayload extends object>(name: string, job: Job<TPayload>): Job<TPayload>;
|
|
@@ -12,5 +13,5 @@ declare function createProductionQueue(driver: "sync" | "async" | "redis", optio
|
|
|
12
13
|
failedJobs?: FailedJobService;
|
|
13
14
|
registerJobs?: () => void;
|
|
14
15
|
}): Queue;
|
|
15
|
-
declare function createQueueWorker(redisUrl: string, failedJobs?: FailedJobService): QueueWorker;
|
|
16
|
+
declare function createQueueWorker(redisUrl: string, failedJobs?: FailedJobService): QueueWorker | RedisStreamsWorker;
|
|
16
17
|
export { createFailedJobService, createProductionQueue, createQueueWorker, createTrackedJob, FailedJobRepository, FailedJobService, jobRegistry, QueueWorker, RedisQueue, ResilientQueue, runQueueJob, };
|
|
@@ -7,3 +7,6 @@ declare function resolveQueueConfig(): QueueConfig;
|
|
|
7
7
|
declare const queueConfig: QueueConfig;
|
|
8
8
|
export type { QueueConfig };
|
|
9
9
|
export { queueConfig, resolveQueueConfig };
|
|
10
|
+
declare function resolveRedisQueueTransport(): "lists" | "streams";
|
|
11
|
+
declare function readQueueVisibilityMs(): number;
|
|
12
|
+
export { readQueueVisibilityMs, resolveRedisQueueTransport };
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { RedisClient } from "bun";
|
|
2
2
|
import type FailedJobService from "./failedJobService";
|
|
3
|
-
import type { Job, Queue
|
|
4
|
-
import { type QueueJobEnvelope } from "./jobRunner";
|
|
5
|
-
|
|
6
|
-
declare const QUEUE_HIGH_KEY: string;
|
|
7
|
-
declare const QUEUE_LOW_KEY: string;
|
|
3
|
+
import type { Job, Queue } from "./index";
|
|
4
|
+
import { parseQueueJobEnvelope, type QueueJobEnvelope } from "./jobRunner";
|
|
5
|
+
import { QUEUE_HIGH_KEY, QUEUE_LIST_KEY, QUEUE_LOW_KEY, queueKeyForPriority } from "./redisQueueKeys";
|
|
8
6
|
declare function queueProcessingKey(queueKey: string): string;
|
|
9
7
|
declare function queueProcessingLeaseKey(queueKey: string): string;
|
|
10
8
|
declare function reservationExpired(lease: string | null, now: number, visibilityMs: number): boolean;
|
|
11
|
-
declare function queueKeyForPriority(priority?: QueuePriority): string;
|
|
12
|
-
declare function parseQueueJobEnvelope(rawPayload: string): QueueJobEnvelope | null;
|
|
13
9
|
type QueueReservation = {
|
|
14
10
|
id: string;
|
|
15
11
|
owner: string;
|
|
@@ -23,6 +19,7 @@ declare function reclaimExpiredQueueReservations(client: RedisClient, queueKeys?
|
|
|
23
19
|
declare class RedisQueue implements Queue {
|
|
24
20
|
private readonly client;
|
|
25
21
|
constructor(redisUrl: string);
|
|
22
|
+
close(): void;
|
|
26
23
|
dispatch<TPayload extends object>(job: Job<TPayload>, payload: TPayload): Promise<void>;
|
|
27
24
|
}
|
|
28
25
|
declare class QueueWorker {
|
|
@@ -42,5 +39,7 @@ declare class QueueWorker {
|
|
|
42
39
|
close(): void;
|
|
43
40
|
}
|
|
44
41
|
declare function countPendingQueueJobs(redisUrl: string): Promise<number>;
|
|
42
|
+
export type { StreamReservation, StreamsEnqueueOptions } from "./redisStreams";
|
|
43
|
+
export { migrateLegacyQueueToStreams, RedisStreamsQueue, RedisStreamsWorker, STREAM_GROUP, streamDeadLetterKey, streamQueueKey, updateStreamReservation, } from "./redisStreams";
|
|
45
44
|
export type { QueueJobEnvelope, QueueReservation };
|
|
46
45
|
export { countPendingQueueJobs, parseQueueJobEnvelope, QUEUE_HIGH_KEY, QUEUE_LIST_KEY, QUEUE_LOW_KEY, QueueWorker, queueInvalidKey, queueKeyForPriority, queueProcessingKey, queueProcessingLeaseKey, RedisQueue, reclaimExpiredQueueReservations, reservationExpired, reserveQueueJob, updateQueueReservation, };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { QueuePriority } from "./index";
|
|
2
|
+
declare const QUEUE_LIST_KEY: string;
|
|
3
|
+
declare const QUEUE_HIGH_KEY: string;
|
|
4
|
+
declare const QUEUE_LOW_KEY: string;
|
|
5
|
+
declare function defaultQueueKeys(): readonly [string, string, string];
|
|
6
|
+
declare const QUEUE_KEYS: readonly [string, string, string];
|
|
7
|
+
declare function queueKeyForPriority(priority?: QueuePriority): string;
|
|
8
|
+
export { defaultQueueKeys, QUEUE_HIGH_KEY, QUEUE_KEYS, QUEUE_LIST_KEY, QUEUE_LOW_KEY, queueKeyForPriority, };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { RedisClient } from "bun";
|
|
2
|
+
import type FailedJobService from "./failedJobService";
|
|
3
|
+
import type { Job, Queue } from "./index";
|
|
4
|
+
import { type QueueJobEnvelope } from "./jobRunner";
|
|
5
|
+
declare const STREAM_GROUP = "strata-workers-v1";
|
|
6
|
+
declare function streamQueueKey(queueKey: string): string;
|
|
7
|
+
declare function streamDeadLetterKey(queueKey: string): string;
|
|
8
|
+
declare function streamRetryKey(queueKey: string): string;
|
|
9
|
+
interface StreamsEnqueueOptions {
|
|
10
|
+
/** Total lifetime including time waiting and retries, measured from Redis server time. */
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
cancellable?: boolean;
|
|
13
|
+
}
|
|
14
|
+
declare function streamControlKey(key: string, jobId: string): string;
|
|
15
|
+
declare function cancelStreamJob(client: RedisClient, key: string, jobId: string): Promise<boolean>;
|
|
16
|
+
declare function readStreamCancellation(client: RedisClient, controlKey: string): Promise<boolean>;
|
|
17
|
+
declare function readStreamTime(client: RedisClient): Promise<number>;
|
|
18
|
+
declare function watchStreamDeadline(client: RedisClient, deadlineAtMs: number, remainingMs: number, expire: () => void, fail: (error: unknown) => void): () => void;
|
|
19
|
+
declare function scheduleStreamRetry(client: RedisClient, key: string, reservation: StreamReservation, envelope: QueueJobEnvelope, delayMs: number): Promise<void>;
|
|
20
|
+
declare function promoteStreamRetries(client: RedisClient, key: string): Promise<number>;
|
|
21
|
+
declare function readStreamQueueDepth(client: RedisClient, key: string): Promise<number>;
|
|
22
|
+
type StreamReservation = {
|
|
23
|
+
id: string;
|
|
24
|
+
owner: string;
|
|
25
|
+
payload: string;
|
|
26
|
+
};
|
|
27
|
+
declare function ensureStreamGroup(client: RedisClient, key: string): Promise<void>;
|
|
28
|
+
declare function readStreamEntry(value: unknown, owner: string): StreamReservation | null;
|
|
29
|
+
declare function updateStreamReservation(client: RedisClient, key: string, reservation: StreamReservation, action: "renew" | "ack" | "quarantine", controlKey?: string): Promise<boolean>;
|
|
30
|
+
declare function reserveStreamJob(client: RedisClient, key: string, owner: string, cursor?: string): Promise<{
|
|
31
|
+
reservation: StreamReservation | null;
|
|
32
|
+
cursor: string;
|
|
33
|
+
}>;
|
|
34
|
+
declare class RedisStreamsQueue implements Queue {
|
|
35
|
+
private readonly client;
|
|
36
|
+
constructor(redisUrl: string);
|
|
37
|
+
close(): void;
|
|
38
|
+
dispatch<TPayload extends object>(job: Job<TPayload>, payload: TPayload): Promise<void>;
|
|
39
|
+
enqueue<TPayload extends object>(job: Job<TPayload>, payload: TPayload, options?: StreamsEnqueueOptions): Promise<string>;
|
|
40
|
+
replay<TPayload extends object>(job: Job<TPayload>, payload: TPayload, jobId: string): Promise<void>;
|
|
41
|
+
private admit;
|
|
42
|
+
cancel(jobId: string): Promise<boolean>;
|
|
43
|
+
}
|
|
44
|
+
declare class RedisStreamsWorker {
|
|
45
|
+
private readonly failedJobs;
|
|
46
|
+
private readonly timeoutSeconds;
|
|
47
|
+
private readonly queueKeys;
|
|
48
|
+
private readonly client;
|
|
49
|
+
private readonly owner;
|
|
50
|
+
private readonly cursors;
|
|
51
|
+
private processing;
|
|
52
|
+
private initialized;
|
|
53
|
+
private running;
|
|
54
|
+
private stopping;
|
|
55
|
+
private active?;
|
|
56
|
+
constructor(redisUrl: string, failedJobs: FailedJobService, timeoutSeconds?: number, queueKeys?: readonly string[]);
|
|
57
|
+
requestStop(): void;
|
|
58
|
+
isRunning(): boolean;
|
|
59
|
+
close(): void;
|
|
60
|
+
private initialize;
|
|
61
|
+
private reserve;
|
|
62
|
+
processNext(): Promise<boolean>;
|
|
63
|
+
private processOne;
|
|
64
|
+
private handle;
|
|
65
|
+
run(): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
declare function migrateLegacyQueueToStreams(client: RedisClient, options: {
|
|
68
|
+
maintenance: true;
|
|
69
|
+
batchSize?: number;
|
|
70
|
+
queueKeys?: readonly string[];
|
|
71
|
+
}): Promise<number>;
|
|
72
|
+
export type { StreamReservation, StreamsEnqueueOptions };
|
|
73
|
+
export { cancelStreamJob, ensureStreamGroup, migrateLegacyQueueToStreams, promoteStreamRetries, RedisStreamsQueue, RedisStreamsWorker, readStreamCancellation, readStreamEntry, readStreamQueueDepth, readStreamTime, reserveStreamJob, STREAM_GROUP, scheduleStreamRetry, streamControlKey, streamDeadLetterKey, streamQueueKey, streamRetryKey, updateStreamReservation, watchStreamDeadline, };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type FailedJobService from "./failedJobService";
|
|
2
|
-
import
|
|
3
|
-
declare class ResilientQueue
|
|
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:
|
|
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 {
|
|
15
|
-
export {
|
|
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, };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface PublicTenancyOptions {
|
|
2
|
+
/** Application-owned lookup of approved normalized hostnames; unknown hosts return null. */
|
|
3
|
+
resolveTenantId(hostname: string): Promise<number | null>;
|
|
4
|
+
/** Exact immediate socket-peer IP addresses. The proxy must overwrite X-Forwarded-Host. */
|
|
5
|
+
trustedProxyAddresses?: readonly string[];
|
|
6
|
+
}
|
|
7
|
+
/** Normalize an HTTP authority for approved-domain storage and lookup, without its port. */
|
|
8
|
+
declare function normalizeTenantHostname(authority: string): string;
|
|
9
|
+
declare function createPublicTenantSelector(options: PublicTenancyOptions): (request: Request) => Promise<number>;
|
|
10
|
+
export type { PublicTenancyOptions };
|
|
11
|
+
export { createPublicTenantSelector, normalizeTenantHostname };
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
type TenantContext = {
|
|
2
2
|
id: number;
|
|
3
3
|
slug: string;
|
|
4
|
-
|
|
5
|
-
|
|
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,
|
|
14
|
+
export { currentTenant, currentTenantId, runWithTenant, tenantContext };
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
import { type PublicTenancyOptions } from "./publicTenantHost";
|
|
1
2
|
import { type TenantContext } from "./tenantContext";
|
|
2
3
|
declare const DEFAULT_TENANT: TenantContext;
|
|
3
4
|
declare function resolveUserTenantId(userId: number): Promise<number>;
|
|
4
5
|
declare function auditChecksum(payload: Record<string, unknown>): string;
|
|
5
|
-
|
|
6
|
+
type TenantResolver = (id: number) => Promise<TenantContext | null>;
|
|
7
|
+
interface TenantMiddlewareOptions {
|
|
8
|
+
/** Load trusted application metadata after the framework selects the tenant ID. */
|
|
9
|
+
resolveTenant?: TenantResolver;
|
|
10
|
+
publicTenancy?: PublicTenancyOptions;
|
|
11
|
+
}
|
|
12
|
+
declare function createTenantMiddleware(options?: TenantMiddlewareOptions): (request: Request, next: () => Promise<Response>) => Promise<Response>;
|
|
13
|
+
export type { PublicTenancyOptions } from "./publicTenantHost";
|
|
14
|
+
export { normalizeTenantHostname } from "./publicTenantHost";
|
|
15
|
+
export type { TenantMiddlewareOptions, TenantResolver };
|
|
6
16
|
export { auditChecksum, createTenantMiddleware, DEFAULT_TENANT, resolveUserTenantId };
|
|
@@ -1,26 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|