@getstrata/core 2.1.0 → 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.
- package/CHANGELOG.md +33 -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 +6 -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/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/index.d.ts +7 -0
- package/dist/core/queue/redisQueue.d.ts +1 -0
- package/dist/core/queue/resilientQueue.d.ts +2 -2
- 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/tenantContext.d.ts +5 -4
- package/dist/core/tenant/tenantMiddleware.d.ts +7 -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 +80 -0
- package/dist/entries/auth/jwtGuard.js +80 -0
- package/dist/entries/auth/scimAuthMiddleware.js +17 -7
- package/dist/entries/auth/sessionGuard.js +80 -0
- package/dist/entries/cache/createCacheStore.js +365 -119
- package/dist/entries/cache/repository.js +5 -0
- package/dist/entries/contracts/serviceTokens.js +83 -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 +67 -6
- package/dist/entries/queue/publicQueue.js +67 -6
- package/dist/entries/queue/queueMetrics.js +67 -6
- package/dist/entries/queue/redisQueue.js +3 -0
- 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 +13 -9
- package/dist/index.js +1745 -633
- package/dist/types/services.d.ts +3 -3
- package/package.json +17 -8
|
@@ -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, };
|
|
@@ -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
|
|
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, };
|
|
@@ -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 };
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -300,7 +300,87 @@ var init_dialect = __esm(() => {
|
|
|
300
300
|
dialectContext = createAsyncContextStore("@getstrata/sqlDialect");
|
|
301
301
|
});
|
|
302
302
|
|
|
303
|
+
// ../../src/core/contracts/container.ts
|
|
304
|
+
function createServiceToken(key) {
|
|
305
|
+
if (!key.trim())
|
|
306
|
+
throw new Error("Service token key must not be empty.");
|
|
307
|
+
return key;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
class ServiceContainer {
|
|
311
|
+
services = new Map;
|
|
312
|
+
singletonFactories = new Map;
|
|
313
|
+
bindings = new Map;
|
|
314
|
+
set(key, value) {
|
|
315
|
+
this.singletonFactories.delete(key);
|
|
316
|
+
this.bindings.delete(key);
|
|
317
|
+
this.services.set(key, value);
|
|
318
|
+
return value;
|
|
319
|
+
}
|
|
320
|
+
singleton(key, factory) {
|
|
321
|
+
this.bindings.delete(key);
|
|
322
|
+
this.services.delete(key);
|
|
323
|
+
this.singletonFactories.set(key, factory);
|
|
324
|
+
}
|
|
325
|
+
bind(key, factory) {
|
|
326
|
+
this.singletonFactories.delete(key);
|
|
327
|
+
this.services.delete(key);
|
|
328
|
+
this.bindings.set(key, factory);
|
|
329
|
+
}
|
|
330
|
+
get(key) {
|
|
331
|
+
if (this.services.has(key)) {
|
|
332
|
+
return this.services.get(key);
|
|
333
|
+
}
|
|
334
|
+
const singletonFactory = this.singletonFactories.get(key);
|
|
335
|
+
if (singletonFactory) {
|
|
336
|
+
const value = singletonFactory(this);
|
|
337
|
+
this.services.set(key, value);
|
|
338
|
+
return value;
|
|
339
|
+
}
|
|
340
|
+
const binding = this.bindings.get(key);
|
|
341
|
+
if (binding) {
|
|
342
|
+
return binding(this);
|
|
343
|
+
}
|
|
344
|
+
throw new Error(`Service "${key}" is not registered.`);
|
|
345
|
+
}
|
|
346
|
+
resolve(key) {
|
|
347
|
+
return this.get(key);
|
|
348
|
+
}
|
|
349
|
+
make(key) {
|
|
350
|
+
return this.resolve(key);
|
|
351
|
+
}
|
|
352
|
+
instance(key, value) {
|
|
353
|
+
return this.set(key, value);
|
|
354
|
+
}
|
|
355
|
+
has(key) {
|
|
356
|
+
return this.services.has(key) || this.singletonFactories.has(key) || this.bindings.has(key);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
class ConfigStore {
|
|
361
|
+
values = new Map;
|
|
362
|
+
set(key, value) {
|
|
363
|
+
this.values.set(key, value);
|
|
364
|
+
return value;
|
|
365
|
+
}
|
|
366
|
+
get(key) {
|
|
367
|
+
return this.values.get(key);
|
|
368
|
+
}
|
|
369
|
+
require(key) {
|
|
370
|
+
if (!this.values.has(key)) {
|
|
371
|
+
throw new Error(`Config key "${key}" is not defined.`);
|
|
372
|
+
}
|
|
373
|
+
return this.values.get(key);
|
|
374
|
+
}
|
|
375
|
+
has(key) {
|
|
376
|
+
return this.values.has(key);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
303
380
|
// ../../src/core/contracts/serviceTokens.ts
|
|
381
|
+
var CORE_THROTTLE_QUOTA_POLICY_TOKEN = createServiceToken("core.throttleQuotaPolicy");
|
|
382
|
+
var CORE_TENANT_RESOLVER_TOKEN = createServiceToken("core.tenantResolver");
|
|
383
|
+
var CORE_HTTP_CLEANUP_TOKEN = createServiceToken("core.httpCleanup");
|
|
304
384
|
var CORE_CONFIG_TOKEN = "core.config";
|
|
305
385
|
var CORE_CACHE_TOKEN = "core.cache";
|
|
306
386
|
var CORE_QUEUE_TOKEN = "core.queue";
|
|
@@ -1,5 +1,85 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// ../../src/core/contracts/container.ts
|
|
3
|
+
function createServiceToken(key) {
|
|
4
|
+
if (!key.trim())
|
|
5
|
+
throw new Error("Service token key must not be empty.");
|
|
6
|
+
return key;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class ServiceContainer {
|
|
10
|
+
services = new Map;
|
|
11
|
+
singletonFactories = new Map;
|
|
12
|
+
bindings = new Map;
|
|
13
|
+
set(key, value) {
|
|
14
|
+
this.singletonFactories.delete(key);
|
|
15
|
+
this.bindings.delete(key);
|
|
16
|
+
this.services.set(key, value);
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
singleton(key, factory) {
|
|
20
|
+
this.bindings.delete(key);
|
|
21
|
+
this.services.delete(key);
|
|
22
|
+
this.singletonFactories.set(key, factory);
|
|
23
|
+
}
|
|
24
|
+
bind(key, factory) {
|
|
25
|
+
this.singletonFactories.delete(key);
|
|
26
|
+
this.services.delete(key);
|
|
27
|
+
this.bindings.set(key, factory);
|
|
28
|
+
}
|
|
29
|
+
get(key) {
|
|
30
|
+
if (this.services.has(key)) {
|
|
31
|
+
return this.services.get(key);
|
|
32
|
+
}
|
|
33
|
+
const singletonFactory = this.singletonFactories.get(key);
|
|
34
|
+
if (singletonFactory) {
|
|
35
|
+
const value = singletonFactory(this);
|
|
36
|
+
this.services.set(key, value);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
const binding = this.bindings.get(key);
|
|
40
|
+
if (binding) {
|
|
41
|
+
return binding(this);
|
|
42
|
+
}
|
|
43
|
+
throw new Error(`Service "${key}" is not registered.`);
|
|
44
|
+
}
|
|
45
|
+
resolve(key) {
|
|
46
|
+
return this.get(key);
|
|
47
|
+
}
|
|
48
|
+
make(key) {
|
|
49
|
+
return this.resolve(key);
|
|
50
|
+
}
|
|
51
|
+
instance(key, value) {
|
|
52
|
+
return this.set(key, value);
|
|
53
|
+
}
|
|
54
|
+
has(key) {
|
|
55
|
+
return this.services.has(key) || this.singletonFactories.has(key) || this.bindings.has(key);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
class ConfigStore {
|
|
60
|
+
values = new Map;
|
|
61
|
+
set(key, value) {
|
|
62
|
+
this.values.set(key, value);
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
get(key) {
|
|
66
|
+
return this.values.get(key);
|
|
67
|
+
}
|
|
68
|
+
require(key) {
|
|
69
|
+
if (!this.values.has(key)) {
|
|
70
|
+
throw new Error(`Config key "${key}" is not defined.`);
|
|
71
|
+
}
|
|
72
|
+
return this.values.get(key);
|
|
73
|
+
}
|
|
74
|
+
has(key) {
|
|
75
|
+
return this.values.has(key);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
2
79
|
// ../../src/core/contracts/serviceTokens.ts
|
|
80
|
+
var CORE_THROTTLE_QUOTA_POLICY_TOKEN = createServiceToken("core.throttleQuotaPolicy");
|
|
81
|
+
var CORE_TENANT_RESOLVER_TOKEN = createServiceToken("core.tenantResolver");
|
|
82
|
+
var CORE_HTTP_CLEANUP_TOKEN = createServiceToken("core.httpCleanup");
|
|
3
83
|
var CORE_CONFIG_TOKEN = "core.config";
|
|
4
84
|
var CORE_CACHE_TOKEN = "core.cache";
|
|
5
85
|
var CORE_QUEUE_TOKEN = "core.queue";
|
|
@@ -262,9 +262,12 @@ async function runWithDeferredModelEvents(callback) {
|
|
|
262
262
|
const existing = deferredModelEvents.getStore();
|
|
263
263
|
if (existing) {
|
|
264
264
|
existing.frames.push([]);
|
|
265
|
+
existing.rollbackFrames.push([]);
|
|
265
266
|
try {
|
|
266
267
|
const result = await callback();
|
|
267
268
|
const nested = existing.frames.pop() ?? [];
|
|
269
|
+
const rollback = existing.rollbackFrames.pop() ?? [];
|
|
270
|
+
existing.rollbackFrames.at(-1)?.push(...rollback);
|
|
268
271
|
const parent = existing.frames[existing.frames.length - 1];
|
|
269
272
|
if (parent) {
|
|
270
273
|
parent.push(...nested);
|
|
@@ -272,11 +275,20 @@ async function runWithDeferredModelEvents(callback) {
|
|
|
272
275
|
return result;
|
|
273
276
|
} catch (error) {
|
|
274
277
|
existing.frames.pop();
|
|
278
|
+
for (const rollback of (existing.rollbackFrames.pop() ?? []).reverse())
|
|
279
|
+
rollback();
|
|
275
280
|
throw error;
|
|
276
281
|
}
|
|
277
282
|
}
|
|
278
|
-
const state = { frames: [[]] };
|
|
279
|
-
|
|
283
|
+
const state = { frames: [[]], rollbackFrames: [[]] };
|
|
284
|
+
let result;
|
|
285
|
+
try {
|
|
286
|
+
result = await deferredModelEvents.run(state, callback);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
for (const rollback of (state.rollbackFrames[0] ?? []).reverse())
|
|
289
|
+
rollback();
|
|
290
|
+
throw error;
|
|
291
|
+
}
|
|
280
292
|
await dispatchQueuedModelEvents(state.frames[0] ?? []);
|
|
281
293
|
return result;
|
|
282
294
|
}
|
|
@@ -849,19 +861,17 @@ async function resolveTenant(tenantId) {
|
|
|
849
861
|
if (!isTenancyEnabled()) {
|
|
850
862
|
return {
|
|
851
863
|
id: tenantId,
|
|
852
|
-
slug: "default"
|
|
853
|
-
plan: "free",
|
|
854
|
-
region: "eu"
|
|
864
|
+
slug: "default"
|
|
855
865
|
};
|
|
856
866
|
}
|
|
857
867
|
const rows = await db`
|
|
858
|
-
SELECT id, slug
|
|
868
|
+
SELECT id, slug
|
|
859
869
|
FROM tenant
|
|
860
870
|
WHERE id = ${tenantId}
|
|
861
871
|
LIMIT 1
|
|
862
872
|
`;
|
|
863
873
|
const row = rows[0];
|
|
864
|
-
return row ? { id: row.id, slug: row.slug
|
|
874
|
+
return row ? { id: row.id, slug: row.slug } : null;
|
|
865
875
|
}
|
|
866
876
|
|
|
867
877
|
// ../../src/core/auth/scimAuthMiddleware.ts
|
|
@@ -1,5 +1,85 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// ../../src/core/contracts/container.ts
|
|
3
|
+
function createServiceToken(key) {
|
|
4
|
+
if (!key.trim())
|
|
5
|
+
throw new Error("Service token key must not be empty.");
|
|
6
|
+
return key;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class ServiceContainer {
|
|
10
|
+
services = new Map;
|
|
11
|
+
singletonFactories = new Map;
|
|
12
|
+
bindings = new Map;
|
|
13
|
+
set(key, value) {
|
|
14
|
+
this.singletonFactories.delete(key);
|
|
15
|
+
this.bindings.delete(key);
|
|
16
|
+
this.services.set(key, value);
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
singleton(key, factory) {
|
|
20
|
+
this.bindings.delete(key);
|
|
21
|
+
this.services.delete(key);
|
|
22
|
+
this.singletonFactories.set(key, factory);
|
|
23
|
+
}
|
|
24
|
+
bind(key, factory) {
|
|
25
|
+
this.singletonFactories.delete(key);
|
|
26
|
+
this.services.delete(key);
|
|
27
|
+
this.bindings.set(key, factory);
|
|
28
|
+
}
|
|
29
|
+
get(key) {
|
|
30
|
+
if (this.services.has(key)) {
|
|
31
|
+
return this.services.get(key);
|
|
32
|
+
}
|
|
33
|
+
const singletonFactory = this.singletonFactories.get(key);
|
|
34
|
+
if (singletonFactory) {
|
|
35
|
+
const value = singletonFactory(this);
|
|
36
|
+
this.services.set(key, value);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
const binding = this.bindings.get(key);
|
|
40
|
+
if (binding) {
|
|
41
|
+
return binding(this);
|
|
42
|
+
}
|
|
43
|
+
throw new Error(`Service "${key}" is not registered.`);
|
|
44
|
+
}
|
|
45
|
+
resolve(key) {
|
|
46
|
+
return this.get(key);
|
|
47
|
+
}
|
|
48
|
+
make(key) {
|
|
49
|
+
return this.resolve(key);
|
|
50
|
+
}
|
|
51
|
+
instance(key, value) {
|
|
52
|
+
return this.set(key, value);
|
|
53
|
+
}
|
|
54
|
+
has(key) {
|
|
55
|
+
return this.services.has(key) || this.singletonFactories.has(key) || this.bindings.has(key);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
class ConfigStore {
|
|
60
|
+
values = new Map;
|
|
61
|
+
set(key, value) {
|
|
62
|
+
this.values.set(key, value);
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
get(key) {
|
|
66
|
+
return this.values.get(key);
|
|
67
|
+
}
|
|
68
|
+
require(key) {
|
|
69
|
+
if (!this.values.has(key)) {
|
|
70
|
+
throw new Error(`Config key "${key}" is not defined.`);
|
|
71
|
+
}
|
|
72
|
+
return this.values.get(key);
|
|
73
|
+
}
|
|
74
|
+
has(key) {
|
|
75
|
+
return this.values.has(key);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
2
79
|
// ../../src/core/contracts/serviceTokens.ts
|
|
80
|
+
var CORE_THROTTLE_QUOTA_POLICY_TOKEN = createServiceToken("core.throttleQuotaPolicy");
|
|
81
|
+
var CORE_TENANT_RESOLVER_TOKEN = createServiceToken("core.tenantResolver");
|
|
82
|
+
var CORE_HTTP_CLEANUP_TOKEN = createServiceToken("core.httpCleanup");
|
|
3
83
|
var CORE_CONFIG_TOKEN = "core.config";
|
|
4
84
|
var CORE_CACHE_TOKEN = "core.cache";
|
|
5
85
|
var CORE_QUEUE_TOKEN = "core.queue";
|