@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.
Files changed (74) hide show
  1. package/CHANGELOG.md +40 -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/di.d.ts +4 -2
  8. package/dist/core/contracts/serviceTokens.d.ts +7 -1
  9. package/dist/core/database/baseRepository.d.ts +0 -1
  10. package/dist/core/database/index.d.ts +1 -1
  11. package/dist/core/database/model.d.ts +51 -22
  12. package/dist/core/database/repositoryQuery.d.ts +16 -1
  13. package/dist/core/database/types.d.ts +25 -2
  14. package/dist/core/database/whereBuilder.d.ts +5 -5
  15. package/dist/core/events/deferredModelEvents.d.ts +2 -1
  16. package/dist/core/http/loginThrottleMiddleware.d.ts +6 -5
  17. package/dist/core/http/memoryThrottleMiddleware.d.ts +11 -3
  18. package/dist/core/http/requestMetaContext.d.ts +2 -0
  19. package/dist/core/http/scimThrottleMiddleware.d.ts +12 -3
  20. package/dist/core/http/throttleMiddleware.d.ts +24 -6
  21. package/dist/core/lifecycle/gracefulShutdown.d.ts +33 -5
  22. package/dist/core/queue/createAppQueue.d.ts +2 -1
  23. package/dist/core/queue/failedJobService.d.ts +3 -1
  24. package/dist/core/queue/index.d.ts +17 -2
  25. package/dist/core/queue/jobRunner.d.ts +7 -1
  26. package/dist/core/queue/publicQueue.d.ts +2 -1
  27. package/dist/core/queue/queueConfig.d.ts +3 -0
  28. package/dist/core/queue/redisQueue.d.ts +6 -7
  29. package/dist/core/queue/redisQueueKeys.d.ts +8 -0
  30. package/dist/core/queue/redisStreams.d.ts +73 -0
  31. package/dist/core/queue/resilientQueue.d.ts +2 -2
  32. package/dist/core/queue/types.d.ts +1 -0
  33. package/dist/core/runtime/applicationRegistry.d.ts +2 -1
  34. package/dist/core/runtime/boundedThrottleStore.d.ts +29 -0
  35. package/dist/core/scheduler/leases.d.ts +40 -0
  36. package/dist/core/scheduler/schedule.d.ts +29 -5
  37. package/dist/core/tenant/publicTenantHost.d.ts +11 -0
  38. package/dist/core/tenant/tenantContext.d.ts +5 -4
  39. package/dist/core/tenant/tenantMiddleware.d.ts +11 -1
  40. package/dist/core/tracing/otel.d.ts +28 -26
  41. package/dist/core/tracing/tracingMiddleware.d.ts +4 -1
  42. package/dist/entries/audit/exportAuditLogs.js +14 -2
  43. package/dist/entries/auth/basicAuthGuard.js +81 -0
  44. package/dist/entries/auth/jwtGuard.js +81 -0
  45. package/dist/entries/auth/scimAuthMiddleware.js +17 -7
  46. package/dist/entries/auth/sessionGuard.js +81 -0
  47. package/dist/entries/cache/createCacheStore.js +365 -119
  48. package/dist/entries/cache/repository.js +5 -0
  49. package/dist/entries/contracts/serviceTokens.js +85 -0
  50. package/dist/entries/database/factory.js +0 -3
  51. package/dist/entries/database/model.js +1 -2486
  52. package/dist/entries/database/repositoryQuery.js +67 -9
  53. package/dist/entries/http/bodySizeLimitMiddleware.js +76 -10
  54. package/dist/entries/http/memoryThrottleMiddleware.js +276 -21
  55. package/dist/entries/http/scimThrottleMiddleware.js +576 -54
  56. package/dist/entries/http/throttleMiddleware.js +65 -16
  57. package/dist/entries/jobs/exportAuditLogsJob.js +44 -7
  58. package/dist/entries/jobs/invalidateCacheTagsJob.js +30 -5
  59. package/dist/entries/queue/createAppQueue.js +748 -72
  60. package/dist/entries/queue/failedJobRepository.js +1 -1
  61. package/dist/entries/queue/failedJobService.js +5 -1
  62. package/dist/entries/queue/jobRunner.js +67 -2
  63. package/dist/entries/queue/publicQueue.js +746 -71
  64. package/dist/entries/queue/queueMetrics.js +761 -78
  65. package/dist/entries/queue/redisQueue.js +768 -148
  66. package/dist/entries/queue.js +30 -5
  67. package/dist/entries/scheduler/schedule.js +1 -72
  68. package/dist/entries/tenant/databaseTenantContext.js +14 -2
  69. package/dist/entries/tenant/tenantDatabaseScope.js +14 -2
  70. package/dist/entries/tracing/tracingMiddleware.js +186 -454
  71. package/dist/framework/public-api.d.ts +14 -10
  72. package/dist/index.js +2444 -688
  73. package/dist/types/services.d.ts +3 -3
  74. package/package.json +17 -8
package/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # @getstrata/core changelog
2
2
 
3
+ ## 2.2.1
4
+
5
+ F02 — complete queue durability. Queues have leased reservations, recovery and fenced acknowledgement, but still use Redis lists. Persisted retry schedules, execution deadlines, cancellation and the planned Streams migration remain unfinished. Retries currently wait inside the running worker.
6
+ F10 — production public-route tenancy. Separate anonymous access from development tenant headers, support a trusted-host resolver, reject unknown hosts, and accept forwarded hosts only through configured trusted proxies. The shop still lacks the approved-domain mapping.
7
+ F12 — initial administrator provisioning and migration guidance. Automatic demo seeding is fixed. Secure initial-admin provisioning and a consolidated upgrade/operations guide remain.
8
+ Distributed qualification. Cache generations/fill leases, scheduler ownership, OpenTelemetry, body limits and lifecycle controls now exist. Their implementation should no longer be described as entirely pending, but broader failure and capacity qualification is still required.
9
+
10
+ ## 2.2.0
11
+
12
+ Consistent model scopes.
13
+ Changed-field model writes.
14
+ Awaited provider startup.
15
+ Shared cache correctness.
16
+ Native HTTP controls and body limits.
17
+ Graceful lifecycle.
18
+ Validated async discovery.
19
+ Stronger model input types.
20
+ OpenTelemetry tracing.
21
+ Distributed scheduler ownership.
22
+ App-owned quota policy.
23
+ Bounded memory throttling.
24
+
25
+ ## Unreleased
26
+
27
+ - Coordinate scheduled minute occurrences with renewable Redis ownership, overlap exclusion, completion retention and lease-loss cancellation. Production defaults to Redis without local fallback; direct task callbacks now accept ownership context. Share appSchedule across root/subpath imports. See SCHEDULER.md for explicit local mode and replay limits.
28
+
29
+ - Use maintained OpenTelemetry tracing with W3C propagation, consistent identities/epoch timestamps, bounded batching, sampling, error status and lifecycle-owned flush/shutdown. Raw URL/exception secrets are omitted; debug x-trace-id no longer overrides identity.
30
+
31
+ - Strengthen declared model write/filter/identifier contracts and add typed plain-record projections with model casts and existing scope/RLS semantics. Source compatibility: use `query().select(...)` instead of partial selects on model-returning helpers, `whereDynamic` for qualified/dynamic predicates, and `newFromTrustedRecord` for deliberate partial driver hydration. Database defaults and mass-assignment rules remain runtime concerns.
32
+
33
+ - Accept awaited provider hooks and explicit cleanup registration; expose optional closure of owned Redis cache/queue clients.
34
+
35
+ - Persist only changed attributes on existing model saves, skip no-op updates and unchanged write casts, and restore retryable snapshots after transaction/savepoint rollback.
36
+
37
+ - Apply related target model scopes and composed filters to lazy/eager/existence/count reads; retain batched connection reuse, isolate morph targets by type and ID, and use dialect-aware through/pivot read parameters.
38
+
39
+ - Compose repeated query predicates and protect global model scopes from caller OR branches; preserve scopes in chunks/cursor pages and intersect cursor bounds with existing filters.
40
+
41
+ - Add typed `defineModel(table)` bases and two-phase model startup; preserve explicit repository overrides and share model identity across package import paths.
42
+
3
43
  ## 2.1.0
4
44
 
5
45
  - refactor(starter): reuse shared config and base repositories - fix(database): await password casts before model writes - fix(database): preserve concrete model types in query results
@@ -1,3 +1,4 @@
1
+ import { type RedisCacheOptions } from "./redisCacheStore";
1
2
  import type { CacheStore } from "./store";
2
3
  type CacheDriver = "array" | "redis";
3
4
  interface CreateCacheStoreOptions {
@@ -5,7 +6,8 @@ interface CreateCacheStoreOptions {
5
6
  ttlMs: number;
6
7
  maxEntries: number;
7
8
  redisUrl?: string;
9
+ redis?: RedisCacheOptions;
8
10
  }
9
11
  declare function createCacheStore(options: CreateCacheStoreOptions): CacheStore;
10
- export type { CacheDriver, CreateCacheStoreOptions };
12
+ export type { CacheDriver, CreateCacheStoreOptions, RedisCacheOptions };
11
13
  export { createCacheStore };
@@ -1,23 +1,36 @@
1
1
  import type { CacheStore } from "./store";
2
+ type RedisCacheOptions = {
3
+ commandTimeoutMs?: number;
4
+ leaseMs?: number;
5
+ fillTimeoutMs?: number;
6
+ waitTimeoutMs?: number;
7
+ pollMs?: number;
8
+ metadataTtlMs?: number;
9
+ metadataLimit?: number;
10
+ };
2
11
  declare class RedisCacheStore implements CacheStore {
3
12
  private readonly ttlMs;
4
13
  private readonly maxEntries;
5
14
  private readonly client;
6
- private readonly inflight;
7
- private readonly keyTags;
8
- constructor(redisUrl: string, ttlMs: number, maxEntries: number);
15
+ private readonly keys;
16
+ private readonly options;
17
+ private readonly fills;
18
+ private closed;
19
+ constructor(redisUrl: string, ttlMs: number, maxEntries: number, options?: RedisCacheOptions);
20
+ close(): void;
9
21
  get<T>(key: string): Promise<T | undefined>;
10
22
  set<T>(key: string, value: T, ttlMs?: number): Promise<void>;
11
- getOrSet<T>(key: string, loader: () => Promise<T>, ttlMs?: number): Promise<T>;
23
+ getOrSet<T>(key: string, loader: (signal?: AbortSignal) => Promise<T>, ttlMs?: number): Promise<T>;
24
+ getOrSetTagged<T>(key: string, loader: (signal?: AbortSignal) => Promise<T>, tags: string[], ttlMs?: number): Promise<T>;
12
25
  attachTags(key: string, tags: string[]): Promise<void>;
13
26
  flushTags(tags: string[]): Promise<number>;
14
27
  invalidate(key: string): Promise<boolean>;
15
28
  invalidateByPrefix(prefix: string): Promise<number>;
16
29
  clear(): Promise<void>;
17
30
  size(): Promise<number>;
18
- private storageKey;
19
- private tagKey;
20
- private detachKeyFromTags;
21
- private enforceMaxEntries;
31
+ private fill;
32
+ private serialize;
33
+ private execute;
22
34
  }
35
+ export type { RedisCacheOptions };
23
36
  export default RedisCacheStore;
@@ -3,12 +3,13 @@ import TaggedCache from "./taggedCache";
3
3
  declare class CacheRepository {
4
4
  private readonly store;
5
5
  constructor(store: CacheStore);
6
+ close(): Promise<void>;
6
7
  get<T>(key: string): Promise<T | undefined>;
7
- remember<T>(key: string, callback: () => Promise<T>, ttlMs?: number): Promise<T>;
8
+ remember<T>(key: string, callback: (signal?: AbortSignal) => Promise<T>, ttlMs?: number): Promise<T>;
8
9
  forget(key: string): Promise<boolean>;
9
10
  flush(): Promise<void>;
10
11
  tags(...names: string[]): TaggedCache;
11
- getOrSet<T>(key: string, loader: () => Promise<T>, ttlMs?: number): Promise<T>;
12
+ getOrSet<T>(key: string, loader: (signal?: AbortSignal) => Promise<T>, ttlMs?: number): Promise<T>;
12
13
  invalidate(key: string): Promise<boolean>;
13
14
  invalidateByPrefix(prefix: string): Promise<number>;
14
15
  clear(): Promise<void>;
@@ -1,7 +1,10 @@
1
1
  interface CacheStore {
2
+ close?(): void | Promise<void>;
2
3
  get<T>(key: string): Promise<T | undefined>;
3
4
  set<T>(key: string, value: T, ttlMs?: number): Promise<void>;
4
- getOrSet<T>(key: string, loader: () => Promise<T>, ttlMs?: number): Promise<T>;
5
+ getOrSet<T>(key: string, loader: (signal?: AbortSignal) => Promise<T>, ttlMs?: number): Promise<T>;
6
+ /** Shared stores can bind tags before starting a fill. */
7
+ getOrSetTagged?<T>(key: string, loader: (signal?: AbortSignal) => Promise<T>, tags: string[], ttlMs?: number): Promise<T>;
5
8
  attachTags(key: string, tags: string[]): Promise<void>;
6
9
  flushTags(tags: string[]): Promise<number>;
7
10
  invalidate(key: string): Promise<boolean>;
@@ -3,7 +3,7 @@ declare class TaggedCache {
3
3
  private readonly store;
4
4
  private readonly tags;
5
5
  constructor(store: CacheStore, tags: string[]);
6
- remember<T>(key: string, callback: () => Promise<T>, ttlMs?: number): Promise<T>;
6
+ remember<T>(key: string, callback: (signal?: AbortSignal) => Promise<T>, ttlMs?: number): Promise<T>;
7
7
  flush(): Promise<number>;
8
8
  }
9
9
  export default TaggedCache;
@@ -13,11 +13,13 @@ interface ProviderContext {
13
13
  container: ServiceContainer;
14
14
  config: ConfigStore;
15
15
  dependencies: MutableAppDependencies;
16
+ /** Register owned resources immediately; flush precedes close, each in reverse order. */
17
+ onCleanup(handler: () => void | Promise<void>, phase?: "drain" | "flush" | "close"): void;
16
18
  }
17
19
  interface ServiceProvider {
18
20
  name: string;
19
- register?(context: ProviderContext): void;
20
- boot?(context: ProviderContext): void;
21
+ register?(context: ProviderContext): void | Promise<void>;
22
+ boot?(context: ProviderContext): void | Promise<void>;
21
23
  }
22
24
  interface AppContext {
23
25
  container: ServiceContainer;
@@ -1,6 +1,12 @@
1
1
  import type { AbilityChecker } from "../auth/abilityChecker";
2
+ import type { ThrottleQuotaPolicy } from "../http/throttleMiddleware";
3
+ import type { PublicTenancyOptions, TenantResolver } from "../tenant/tenantMiddleware";
2
4
  import type { AuthUserDirectory } from "./authUserDirectory";
3
5
  import type { ServiceContainerLike } from "./container";
6
+ declare const CORE_THROTTLE_QUOTA_POLICY_TOKEN: import("./container").ServiceToken<ThrottleQuotaPolicy>;
7
+ declare const CORE_PUBLIC_TENANCY_TOKEN: import("./container").ServiceToken<PublicTenancyOptions>;
8
+ declare const CORE_TENANT_RESOLVER_TOKEN: import("./container").ServiceToken<TenantResolver>;
9
+ declare const CORE_HTTP_CLEANUP_TOKEN: import("./container").ServiceToken<(cleanup: () => void) => void>;
4
10
  declare const CORE_CONFIG_TOKEN = "core.config";
5
11
  declare const CORE_CACHE_TOKEN = "core.cache";
6
12
  declare const CORE_QUEUE_TOKEN = "core.queue";
@@ -14,4 +20,4 @@ declare function isAbilityChecker(value: unknown): value is AbilityChecker;
14
20
  declare function isAuthUserDirectory(value: unknown): value is AuthUserDirectory;
15
21
  declare function resolveAbilityChecker(container: ServiceContainerLike): AbilityChecker;
16
22
  declare function resolveAuthUserDirectory(container: ServiceContainerLike): AuthUserDirectory | null;
17
- export { CORE_ABILITY_CHECKER_TOKEN, CORE_AUTH_TOKEN, CORE_AUTH_USER_DIRECTORY_TOKEN, CORE_CACHE_TOKEN, CORE_CONFIG_TOKEN, CORE_EVENT_BUS_TOKEN, CORE_POLICY_GATE_TOKEN, CORE_QUEUE_TOKEN, CORE_TOKEN_SERVICE_TOKEN, isAbilityChecker, isAuthUserDirectory, resolveAbilityChecker, resolveAuthUserDirectory, };
23
+ export { CORE_ABILITY_CHECKER_TOKEN, CORE_AUTH_TOKEN, CORE_AUTH_USER_DIRECTORY_TOKEN, CORE_CACHE_TOKEN, CORE_CONFIG_TOKEN, CORE_EVENT_BUS_TOKEN, CORE_HTTP_CLEANUP_TOKEN, CORE_POLICY_GATE_TOKEN, CORE_PUBLIC_TENANCY_TOKEN, CORE_QUEUE_TOKEN, CORE_TENANT_RESOLVER_TOKEN, CORE_THROTTLE_QUOTA_POLICY_TOKEN, CORE_TOKEN_SERVICE_TOKEN, isAbilityChecker, isAuthUserDirectory, resolveAbilityChecker, resolveAuthUserDirectory, };
@@ -73,7 +73,6 @@ declare class BaseRepository<TEntity extends object, PrimaryKey extends keyof TE
73
73
  loadHasManyForParents<TParent extends object, LocalKey extends keyof TParent & string, ForeignKey extends keyof TEntity & string>(parents: readonly TParent[], relation: HasManyRelation<TParent, TEntity, LocalKey, ForeignKey>, options?: Omit<QueryOptions<TEntity>, "where">): Promise<Map<TParent[LocalKey], TEntity[]>>;
74
74
  findHasManyThrough<TParent extends object, LocalKey extends keyof TParent & string, FirstKey extends string, SecondLocalKey extends string, SecondKey extends keyof TEntity & string>(parentId: unknown, relation: HasManyThroughRelation<TParent, TEntity, LocalKey, FirstKey, SecondLocalKey, SecondKey>, options?: QueryOptions<TEntity>): Promise<TEntity[]>;
75
75
  loadHasManyThroughForParents<TParent extends object, LocalKey extends keyof TParent & string, FirstKey extends string, SecondLocalKey extends string, SecondKey extends keyof TEntity & string>(parents: readonly TParent[], relation: HasManyThroughRelation<TParent, TEntity, LocalKey, FirstKey, SecondLocalKey, SecondKey>, options?: QueryOptions<TEntity>): Promise<Map<TParent[LocalKey], TEntity[]>>;
76
- private throughSoftDeleteClause;
77
76
  private buildThroughWhere;
78
77
  loadBelongsToForParents<TChild extends object, TParent extends object, ForeignKey extends keyof TChild & string, OwnerKey extends keyof TParent & string>(children: readonly TChild[], relation: BelongsToRelation<TChild, TParent, ForeignKey, OwnerKey>, parentRepository: BaseRepository<TParent, OwnerKey>, options?: Omit<QueryOptions<TParent>, "where">): Promise<Map<TChild[ForeignKey], TParent>>;
79
78
  loadMorphManyForParents<TParent extends object, LocalKey extends keyof TParent & string, MorphTypeKey extends keyof TEntity & string, MorphIdKey extends keyof TEntity & string>(parents: readonly TParent[], relation: MorphManyRelation<TParent, TEntity, LocalKey, MorphTypeKey, MorphIdKey>, options?: Omit<QueryOptions<TEntity>, "where">): Promise<Map<TParent[LocalKey], TEntity[]>>;
@@ -9,7 +9,7 @@ export { mapDatabaseError, withDatabaseErrorHandling } from "./errors.ts";
9
9
  export { Factory } from "./factory.ts";
10
10
  export { foreignKeyFromTable, pivotTableName, singularize } from "./inflection.ts";
11
11
  export type { CastType, GlobalScopeFn, ModelConstructor } from "./model.ts";
12
- export { applyCasts, BelongsToManyRelationQuery, BelongsToRelationQuery, dehydrateValue, filterMassAssignable, HasManyRelationQuery, HasManyThroughRelationQuery, HasOneRelationQuery, hydrateValue, Model, ModelQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, registerModelClass, registerModelRepository, } from "./model.ts";
12
+ export { applyCasts, BelongsToManyRelationQuery, BelongsToRelationQuery, bootModels, defineModel, dehydrateValue, filterMassAssignable, HasManyRelationQuery, HasManyThroughRelationQuery, HasOneRelationQuery, hydrateValue, Model, ModelQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, registerModelClass, registerModelRepository, } from "./model.ts";
13
13
  export type { MysqlConnection, MysqlExecutable, MysqlPool } from "./mysqlConnection.ts";
14
14
  export { createMysqlConnection, createMysqlConnectionFromPool, createMysqlPool, } from "./mysqlConnection.ts";
15
15
  export type { NamedConnectionEntry } from "./namedConnections.ts";
@@ -1,9 +1,10 @@
1
- import type BaseRepository from "./baseRepository.ts";
1
+ import BaseRepository from "./baseRepository.ts";
2
2
  import type { AnyRelationQuery, RelatedModelClass } from "./relationQuery.ts";
3
3
  import { BelongsToManyRelationQuery, BelongsToRelationQuery, HasManyRelationQuery, HasManyThroughRelationQuery, HasOneRelationQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery } from "./relationQuery.ts";
4
4
  import type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasOneRelation } from "./relationships.ts";
5
5
  import type { RepositoryQuery } from "./repositoryQuery.ts";
6
- import type { QueryOptions, QueryWhere } from "./types.ts";
6
+ import type { TableDefinition } from "./table.ts";
7
+ import type { ModelWhere, ModelWriteValues, QueryOptions, QueryWhere } from "./types.ts";
7
8
  type CastType = "date" | "datetime" | "json" | "bool" | "boolean" | "integer" | "int" | "hashed";
8
9
  type LoadedAttributes = Record<string, unknown>;
9
10
  type ModelCasts = Partial<Record<string, CastType>>;
@@ -16,6 +17,9 @@ type AnyModel = Model<Record<string, unknown>, "id">;
16
17
  type ModelAttributes<TModel> = TModel extends {
17
18
  toObject(): infer TRecord extends object;
18
19
  } ? TRecord : Record<string, unknown>;
20
+ type ModelIdentifier<TModel> = TModel extends {
21
+ readonly id: infer TKey;
22
+ } ? TKey : unknown;
19
23
  type WithModelCount<TModel, TCount extends string> = {
20
24
  toObject(): Omit<ModelAttributes<TModel>, TCount> & Record<TCount, unknown>;
21
25
  get<K extends keyof (Omit<ModelAttributes<TModel>, TCount> & Record<TCount, unknown>)>(key: K): (Omit<ModelAttributes<TModel>, TCount> & Record<TCount, unknown>)[K];
@@ -34,6 +38,10 @@ type ModelObserver = {
34
38
  deleting?: (model: AnyModel) => unknown;
35
39
  deleted?: (model: AnyModel) => unknown;
36
40
  };
41
+ /** Typed model base with a lazily created framework repository and active connection. */
42
+ declare function defineModel<TEntity extends object, PrimaryKey extends keyof TEntity & string>(table: TableDefinition<TEntity, PrimaryKey>): typeof Model<TEntity, PrimaryKey>;
43
+ /** Register all relationship names before initializing any model's boot hooks. */
44
+ declare function bootModels(models: readonly object[]): void;
37
45
  /** Alias for `hasMany("Name")` when `Name` is not `constructor.name` or `$morphClass`. */
38
46
  declare function registerModelClass(name: string, model: object): void;
39
47
  declare function hydrateValue(value: unknown, cast: CastType): unknown;
@@ -44,21 +52,31 @@ declare function filterMassAssignable(fillable: readonly string[] | undefined, g
44
52
  declare function applyCasts(values: LoadedAttributes, casts: ModelCasts, direction: "hydrate"): LoadedAttributes;
45
53
  declare function applyCasts(values: LoadedAttributes, casts: ModelCasts, direction: "dehydrate"): Promise<LoadedAttributes>;
46
54
  declare function applyCasts(values: LoadedAttributes, casts: ModelCasts, direction: "hydrate" | "dehydrate"): LoadedAttributes | Promise<LoadedAttributes>;
55
+ /** Read-only plain records; partial selection never acquires model mutation methods. */
56
+ declare class ModelProjection<TEntity extends object, K extends keyof TEntity & string> {
57
+ private readonly load;
58
+ constructor(load: (limit?: number) => Promise<Array<Pick<TEntity, K>>>);
59
+ get(): Promise<Array<Pick<TEntity, K>>>;
60
+ first(): Promise<Pick<TEntity, K> | null>;
61
+ }
47
62
  declare class ModelQuery<TModel extends object = AnyModel, TCounts extends string = never> {
48
63
  private readonly modelClass;
49
64
  readonly query: RepositoryQuery<Record<string, unknown>, "id">;
50
65
  private readonly eager;
51
66
  constructor(modelClass: object, query: RepositoryQuery<Record<string, unknown>, "id">);
52
67
  with(...relations: RelationNameInput[]): this;
53
- where(input: QueryWhere<object> | ((builder: import("./whereBuilder.ts").WhereBuilder<object>) => void)): this;
54
- orWhere(input: QueryWhere<object> | ((builder: import("./whereBuilder.ts").WhereBuilder<object>) => void)): this;
55
- orderBy(orderBy: QueryOptions<object>["orderBy"]): this;
68
+ where(input: ModelWhere<ModelAttributes<TModel>> | ((builder: import("./whereBuilder.ts").WhereBuilder<ModelAttributes<TModel>, ModelWhere<ModelAttributes<TModel>>>) => void)): this;
69
+ orWhere(input: ModelWhere<ModelAttributes<TModel>> | ((builder: import("./whereBuilder.ts").WhereBuilder<ModelAttributes<TModel>, ModelWhere<ModelAttributes<TModel>>>) => void)): this;
70
+ /** Qualified/dynamic filters retain existing SQL semantics through an explicit escape hatch. */
71
+ whereDynamic(input: QueryWhere<object>): this;
72
+ select<K extends keyof ModelAttributes<TModel> & string>(...columns: readonly K[]): ModelProjection<ModelAttributes<TModel>, K>;
73
+ orderBy(orderBy: QueryOptions<ModelAttributes<TModel>>["orderBy"]): this;
56
74
  limit(limit: number): this;
57
75
  offset(offset: number): this;
58
- whereNull(column: string): this;
59
- whereNotNull(column: string): this;
60
- whereIn(column: string, values: readonly unknown[]): this;
61
- whereNotIn(column: string, values: readonly unknown[]): this;
76
+ whereNull(column: keyof ModelAttributes<TModel> & string): this;
77
+ whereNotNull(column: keyof ModelAttributes<TModel> & string): this;
78
+ whereIn<K extends keyof ModelAttributes<TModel> & string>(column: K, values: readonly ModelAttributes<TModel>[K][]): this;
79
+ whereNotIn<K extends keyof ModelAttributes<TModel> & string>(column: K, values: readonly ModelAttributes<TModel>[K][]): this;
62
80
  groupBy(groupBy: QueryOptions<object>["groupBy"]): this;
63
81
  having(having: QueryWhere<object>): this;
64
82
  join(left: `${string}.${string}`, right: `${string}.${string}`): this;
@@ -92,8 +110,8 @@ declare class ModelQuery<TModel extends object = AnyModel, TCounts extends strin
92
110
  pluck(column: string): Promise<unknown[]>;
93
111
  pluck(column: string, keyBy: string): Promise<Map<unknown, unknown>>;
94
112
  value(column: string): Promise<unknown>;
95
- find(id: unknown): Promise<ModelQueryResult<TModel, TCounts> | null>;
96
- findOrFail(id: unknown, errorFactory?: (id: unknown) => Error): Promise<ModelQueryResult<TModel, TCounts>>;
113
+ find(id: ModelIdentifier<TModel>): Promise<ModelQueryResult<TModel, TCounts> | null>;
114
+ findOrFail(id: ModelIdentifier<TModel>, errorFactory?: (id: unknown) => Error): Promise<ModelQueryResult<TModel, TCounts>>;
97
115
  then(onfulfilled?: ((value: ModelQueryResult<TModel, TCounts>[]) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null): Promise<unknown>;
98
116
  withCount<TName extends string, TAlias extends string = `${TName}_count`>(name: TName, alias?: TAlias): ModelQuery<TModel, TCounts | TAlias>;
99
117
  private constrainExists;
@@ -110,11 +128,13 @@ declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & s
110
128
  static $appends?: readonly string[];
111
129
  static $morphClass?: string;
112
130
  private _exists;
131
+ private originalAttributes;
113
132
  private readonly loadedRelations;
114
133
  private hiddenOverrides;
115
134
  private visibleOverrides;
116
135
  private appended;
117
136
  constructor(attributes: TEntity, repository: BaseRepository<TEntity, PrimaryKey>, exists?: boolean);
137
+ private acceptPersistedAttributes;
118
138
  getRepository(): BaseRepository<TEntity, PrimaryKey>;
119
139
  get $exists(): boolean;
120
140
  get<K extends keyof TEntity>(key: K): TEntity[K];
@@ -134,15 +154,24 @@ declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & s
134
154
  static observe(this: object, observer: ModelObserver): void;
135
155
  static addGlobalScope<TEntity extends object, PrimaryKey extends keyof TEntity & string>(this: object, _name: string, scope: GlobalScopeFn<TEntity, PrimaryKey>): void;
136
156
  static repository<TEntity extends object, PrimaryKey extends keyof TEntity & string>(this: object): BaseRepository<TEntity, PrimaryKey>;
157
+ /** Apply this model's global filters to a query with an explicitly selected connection. */
158
+ static scopeQuery<TEntity extends object, PrimaryKey extends keyof TEntity & string>(this: object, query: RepositoryQuery<TEntity, PrimaryKey>): RepositoryQuery<TEntity, PrimaryKey>;
137
159
  static query<TModel extends object>(this: {
138
160
  prototype: TModel;
139
161
  }): ModelQuery<TModel>;
140
162
  static newFromRecord<TModel extends object>(this: {
141
163
  prototype: TModel;
164
+ }, record: NoInfer<ModelWriteValues<ModelAttributes<TModel>>>, exists: false): TModel;
165
+ static newFromRecord<TModel extends object>(this: {
166
+ prototype: TModel;
167
+ }, record: NoInfer<ModelAttributes<TModel>>, exists?: boolean): TModel;
168
+ /** Trusted SQL/driver/partial-record hydration. Callers own shape and completeness. */
169
+ static newFromTrustedRecord<TModel extends object>(this: {
170
+ prototype: TModel;
142
171
  }, record: object, exists?: boolean): TModel;
143
172
  static create<TModel extends object>(this: {
144
173
  prototype: TModel;
145
- }, attributes: Record<string, unknown>, forced?: Record<string, unknown>): Promise<TModel>;
174
+ }, attributes: NoInfer<ModelWriteValues<ModelAttributes<TModel>>>, forced?: NoInfer<ModelWriteValues<ModelAttributes<TModel>>>): Promise<TModel>;
146
175
  static with<TModel extends object>(this: {
147
176
  prototype: TModel;
148
177
  }, ...relations: RelationNameInput[]): ModelQuery<TModel>;
@@ -183,32 +212,32 @@ declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & s
183
212
  }, name: string, constrain?: (query: AnyRelationQuery) => void): ModelQuery<TModel>;
184
213
  static find<TModel extends object>(this: {
185
214
  prototype: TModel;
186
- }, id: unknown): Promise<TModel | null>;
215
+ }, id: NoInfer<ModelIdentifier<TModel>>): Promise<TModel | null>;
187
216
  static findOrFail<TModel extends object>(this: {
188
217
  prototype: TModel;
189
- }, id: unknown, errorFactory?: (id: unknown) => Error): Promise<TModel>;
218
+ }, id: NoInfer<ModelIdentifier<TModel>>, errorFactory?: (id: unknown) => Error): Promise<TModel>;
190
219
  static all<TModel extends object>(this: {
191
220
  prototype: TModel;
192
- }, options?: Omit<QueryOptions<object>, "where">): Promise<TModel[]>;
221
+ }, options?: Omit<QueryOptions<ModelAttributes<TModel>>, "where" | "select">): Promise<TModel[]>;
193
222
  static where<TModel extends object>(this: {
194
223
  prototype: TModel;
195
- }, where: QueryWhere<object>): ModelQuery<TModel>;
224
+ }, where: NoInfer<ModelWhere<ModelAttributes<TModel>>>): ModelQuery<TModel>;
196
225
  static count(this: object): Promise<number>;
197
226
  static pluck(this: object, column: string): Promise<unknown[]>;
198
227
  static pluck(this: object, column: string, keyBy: string): Promise<Map<unknown, unknown>>;
199
228
  static value(this: object, column: string): Promise<unknown>;
200
229
  static firstWhere<TModel extends object>(this: {
201
230
  prototype: TModel;
202
- }, where: QueryWhere<object>, options?: Omit<QueryOptions<object>, "where">): Promise<TModel | null>;
231
+ }, where: NoInfer<ModelWhere<ModelAttributes<TModel>>>, options?: Omit<QueryOptions<ModelAttributes<TModel>>, "where" | "select">): Promise<TModel | null>;
203
232
  static firstOrNew<TModel extends object>(this: {
204
233
  prototype: TModel;
205
- }, where: QueryWhere<object>, values?: Record<string, unknown>): Promise<TModel>;
234
+ }, where: NoInfer<ModelWhere<ModelAttributes<TModel>>>, values?: NoInfer<ModelWriteValues<ModelAttributes<TModel>>>): Promise<TModel>;
206
235
  static firstOrCreate<TModel extends object>(this: {
207
236
  prototype: TModel;
208
- }, where: QueryWhere<object>, values?: Record<string, unknown>): Promise<TModel>;
237
+ }, where: NoInfer<ModelWhere<ModelAttributes<TModel>>>, values?: NoInfer<ModelWriteValues<ModelAttributes<TModel>>>): Promise<TModel>;
209
238
  static updateOrCreate<TModel extends object>(this: {
210
239
  prototype: TModel;
211
- }, where: QueryWhere<object>, values?: Record<string, unknown>): Promise<TModel>;
240
+ }, where: NoInfer<ModelWhere<ModelAttributes<TModel>>>, values?: NoInfer<ModelWriteValues<ModelAttributes<TModel>>>): Promise<TModel>;
212
241
  save(): Promise<this>;
213
242
  update(changes: Partial<TEntity>): Promise<this>;
214
243
  delete(): Promise<boolean>;
@@ -234,5 +263,5 @@ declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & s
234
263
  /** Binds a Model class to its table/connection and names it for `hasMany("User")`. */
235
264
  declare function registerModelRepository<TModelClass>(model: TModelClass, repository: object): TModelClass;
236
265
  export { BelongsToManyRelationQuery, BelongsToRelationQuery, HasManyRelationQuery, HasManyThroughRelationQuery, HasOneRelationQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, } from "./relationQuery.ts";
237
- export type { CastType, GlobalScopeFn, ModelClassType, ModelConstructor };
238
- export { applyCasts, dehydrateValue, filterMassAssignable, hydrateValue, Model, ModelQuery, registerModelClass, registerModelRepository, };
266
+ export type { CastType, GlobalScopeFn, ModelAttributes, ModelClassType, ModelConstructor, ModelProjection, };
267
+ export { applyCasts, bootModels, defineModel, dehydrateValue, filterMassAssignable, hydrateValue, Model, ModelQuery, registerModelClass, registerModelRepository, };
@@ -10,8 +10,11 @@ declare class RepositoryQuery<TEntity extends object, PrimaryKey extends keyof T
10
10
  private queryOptions;
11
11
  private readonly eagerLoads;
12
12
  private readonly whereNodes;
13
+ private readonly protectedWhere;
13
14
  constructor(repository: BaseRepository<TEntity, PrimaryKey>, whereClause?: QueryWhere<TEntity>, queryOptions?: Omit<QueryOptions<TEntity>, "where">);
14
15
  where(input: QueryWhere<TEntity> | ((builder: WhereBuilder<TEntity>) => void)): this;
16
+ /** Preserve completed scope predicates separately from subsequent caller OR conditions. */
17
+ protectWhere(): this;
15
18
  orWhere(input: QueryWhere<TEntity> | ((builder: WhereBuilder<TEntity>) => void)): this;
16
19
  orderBy(orderBy: QueryOptions<TEntity>["orderBy"]): this;
17
20
  limit(limit: number): this;
@@ -31,7 +34,7 @@ declare class RepositoryQuery<TEntity extends object, PrimaryKey extends keyof T
31
34
  withBelongsTo<TParent extends object, ForeignKey extends keyof TEntity & string, OwnerKey extends keyof TParent & string, Alias extends string>(as: Alias, relation: BelongsToRelation<TEntity, TParent, ForeignKey, OwnerKey>, parentRepository: BaseRepository<TParent, OwnerKey>, options?: Omit<QueryOptions<TParent>, "where">): this;
32
35
  withMorphMany<TChild extends object, LocalKey extends keyof TEntity & string, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string, Alias extends string>(as: Alias, relation: MorphManyRelation<TEntity, TChild, LocalKey, MorphTypeKey, MorphIdKey>, childRepository: BaseRepository<TChild, keyof TChild & string>, options?: Omit<QueryOptions<TChild>, "where">): this;
33
36
  withMorphOne<TChild extends object, LocalKey extends keyof TEntity & string, MorphTypeKey extends keyof TChild & string, MorphIdKey extends keyof TChild & string, Alias extends string>(as: Alias, relation: MorphOneRelation<TEntity, TChild, LocalKey, MorphTypeKey, MorphIdKey>, childRepository: BaseRepository<TChild, keyof TChild & string>, options?: Omit<QueryOptions<TChild>, "where">): this;
34
- withMorphTo<TParent extends object, MorphTypeKey extends keyof TEntity & string, MorphIdKey extends keyof TEntity & string, Alias extends string>(as: Alias, relation: MorphToRelation<TEntity, MorphTypeKey, MorphIdKey>, repositoriesByType: ReadonlyMap<string, BaseRepository<TParent, keyof TParent & string>>, options?: Omit<QueryOptions<TParent>, "where">): this;
37
+ withMorphTo<TParent extends object, MorphTypeKey extends keyof TEntity & string, MorphIdKey extends keyof TEntity & string, Alias extends string>(as: Alias, relation: MorphToRelation<TEntity, MorphTypeKey, MorphIdKey>, repositoriesByType: ReadonlyMap<string, BaseRepository<TParent, keyof TParent & string>>, options?: Omit<QueryOptions<TParent>, "where">, optionsByType?: ReadonlyMap<string, QueryOptions<TParent>>): this;
35
38
  withBelongsToMany<TRelated extends object, Pivot extends object, ParentKey extends keyof TEntity & string, RelatedKey extends keyof TRelated & string, ForeignPivotKey extends keyof Pivot & string, RelatedPivotKey extends keyof Pivot & string, Alias extends string>(as: Alias, relation: BelongsToManyRelation<TEntity, TRelated, Pivot, ParentKey, RelatedKey, ForeignPivotKey, RelatedPivotKey>, relatedRepository: BaseRepository<TRelated, RelatedKey>, options?: Omit<QueryOptions<TRelated>, "where">): this;
36
39
  withHasManyThrough<TFar extends object, LocalKey extends keyof TEntity & string, SecondKey extends keyof TFar & string, Alias extends string>(as: Alias, relation: HasManyThroughRelation<TEntity, TFar, LocalKey, string, string, SecondKey>, farRepository: BaseRepository<TFar, keyof TFar & string>, options?: Omit<QueryOptions<TFar>, "where">): this;
37
40
  withTrashed(): this;
@@ -42,11 +45,23 @@ declare class RepositoryQuery<TEntity extends object, PrimaryKey extends keyof T
42
45
  pluck<K extends keyof TEntity & string>(column: K): Promise<Array<TEntity[K]>>;
43
46
  pluck<K extends keyof TEntity & string, KK extends keyof TEntity & string>(column: K, keyBy: KK): Promise<Map<TEntity[KK], TEntity[K]>>;
44
47
  value<K extends keyof TEntity & string>(column: K): Promise<TEntity[K] | null>;
48
+ /** Plain selected records, never complete entities or hydrated models. */
49
+ project<K extends keyof TEntity & string>(columns: readonly K[], limit?: number): Promise<Array<Pick<TEntity, K>>>;
45
50
  attachToRows(rows: readonly TEntity[]): Promise<Array<TEntity & LoadedRow>>;
46
51
  paginate(options: {
47
52
  page: number;
48
53
  perPage: number;
49
54
  }): Promise<PaginatedResult<TEntity>>;
55
+ chunk(count: number, callback: (rows: Array<TEntity & LoadedRow>) => Promise<boolean | void>): Promise<void>;
56
+ cursorPaginate(options: {
57
+ perPage: number;
58
+ cursor?: TEntity[PrimaryKey];
59
+ }): Promise<{
60
+ meta: import("../pagination/index.ts").CursorPaginationMeta<TEntity[PrimaryKey]>;
61
+ data: (TEntity & LoadedRow)[];
62
+ }>;
63
+ /** Compiled query filters for batched relationship loaders. */
64
+ getOptions(): QueryOptions<TEntity>;
50
65
  private buildOptions;
51
66
  private addJoin;
52
67
  private attach;
@@ -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,5 +1,7 @@
1
1
  type RequestMeta = {
2
2
  ipAddress: string | null;
3
+ /** Immediate socket peer recorded by the transport; never a forwarded client header. */
4
+ peerAddress?: string | null;
3
5
  userAgent: string | null;
4
6
  request?: Request;
5
7
  /** Registered route template; never a URL supplied by the client. */
@@ -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 };