@beignet/core 0.0.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 +5 -0
- package/README.md +288 -0
- package/dist/application/index.d.ts +260 -0
- package/dist/application/index.d.ts.map +1 -0
- package/dist/application/index.js +324 -0
- package/dist/application/index.js.map +1 -0
- package/dist/client/client.d.ts +241 -0
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/client.js +531 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/index.d.ts +10 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +8 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/types.d.ts +139 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/config/index.d.ts +122 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +216 -0
- package/dist/config/index.js.map +1 -0
- package/dist/contracts/contract-builder.d.ts +121 -0
- package/dist/contracts/contract-builder.d.ts.map +1 -0
- package/dist/contracts/contract-builder.js +346 -0
- package/dist/contracts/contract-builder.js.map +1 -0
- package/dist/contracts/contract-group.d.ts +106 -0
- package/dist/contracts/contract-group.d.ts.map +1 -0
- package/dist/contracts/contract-group.js +240 -0
- package/dist/contracts/contract-group.js.map +1 -0
- package/dist/contracts/contract-like.d.ts +21 -0
- package/dist/contracts/contract-like.d.ts.map +1 -0
- package/dist/contracts/contract-like.js +9 -0
- package/dist/contracts/contract-like.js.map +1 -0
- package/dist/contracts/index.d.ts +15 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +11 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/openapi-meta.d.ts +23 -0
- package/dist/contracts/openapi-meta.d.ts.map +1 -0
- package/dist/contracts/openapi-meta.js +2 -0
- package/dist/contracts/openapi-meta.js.map +1 -0
- package/dist/contracts/path-template.d.ts +17 -0
- package/dist/contracts/path-template.d.ts.map +1 -0
- package/dist/contracts/path-template.js +50 -0
- package/dist/contracts/path-template.js.map +1 -0
- package/dist/contracts/rate-limit.d.ts +50 -0
- package/dist/contracts/rate-limit.d.ts.map +1 -0
- package/dist/contracts/rate-limit.js +2 -0
- package/dist/contracts/rate-limit.js.map +1 -0
- package/dist/contracts/types.d.ts +97 -0
- package/dist/contracts/types.d.ts.map +1 -0
- package/dist/contracts/types.js +54 -0
- package/dist/contracts/types.js.map +1 -0
- package/dist/contracts/utils.d.ts +3 -0
- package/dist/contracts/utils.d.ts.map +1 -0
- package/dist/contracts/utils.js +44 -0
- package/dist/contracts/utils.js.map +1 -0
- package/dist/domain/entity.d.ts +87 -0
- package/dist/domain/entity.d.ts.map +1 -0
- package/dist/domain/entity.js +155 -0
- package/dist/domain/entity.js.map +1 -0
- package/dist/domain/events.d.ts +41 -0
- package/dist/domain/events.d.ts.map +1 -0
- package/dist/domain/events.js +21 -0
- package/dist/domain/events.js.map +1 -0
- package/dist/domain/index.d.ts +14 -0
- package/dist/domain/index.d.ts.map +1 -0
- package/dist/domain/index.js +14 -0
- package/dist/domain/index.js.map +1 -0
- package/dist/domain/value-object.d.ts +60 -0
- package/dist/domain/value-object.d.ts.map +1 -0
- package/dist/domain/value-object.js +87 -0
- package/dist/domain/value-object.js.map +1 -0
- package/dist/errors/catalog.d.ts +71 -0
- package/dist/errors/catalog.d.ts.map +1 -0
- package/dist/errors/catalog.js +71 -0
- package/dist/errors/catalog.js.map +1 -0
- package/dist/errors/http.d.ts +77 -0
- package/dist/errors/http.d.ts.map +1 -0
- package/dist/errors/http.js +74 -0
- package/dist/errors/http.js.map +1 -0
- package/dist/errors/index.d.ts +10 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +14 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/response.d.ts +26 -0
- package/dist/errors/response.d.ts.map +1 -0
- package/dist/errors/response.js +34 -0
- package/dist/errors/response.js.map +1 -0
- package/dist/errors/validation.d.ts +18 -0
- package/dist/errors/validation.d.ts.map +1 -0
- package/dist/errors/validation.js +21 -0
- package/dist/errors/validation.js.map +1 -0
- package/dist/events/index.d.ts +58 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +102 -0
- package/dist/events/index.js.map +1 -0
- package/dist/jobs/index.d.ts +56 -0
- package/dist/jobs/index.d.ts.map +1 -0
- package/dist/jobs/index.js +89 -0
- package/dist/jobs/index.js.map +1 -0
- package/dist/mail/index.d.ts +75 -0
- package/dist/mail/index.d.ts.map +1 -0
- package/dist/mail/index.js +84 -0
- package/dist/mail/index.js.map +1 -0
- package/dist/openapi/index.d.ts +207 -0
- package/dist/openapi/index.d.ts.map +1 -0
- package/dist/openapi/index.js +449 -0
- package/dist/openapi/index.js.map +1 -0
- package/dist/openapi/schema-introspector.d.ts +38 -0
- package/dist/openapi/schema-introspector.d.ts.map +1 -0
- package/dist/openapi/schema-introspector.js +67 -0
- package/dist/openapi/schema-introspector.js.map +1 -0
- package/dist/ports/audit.d.ts +58 -0
- package/dist/ports/audit.d.ts.map +1 -0
- package/dist/ports/audit.js +74 -0
- package/dist/ports/audit.js.map +1 -0
- package/dist/ports/auth.d.ts +23 -0
- package/dist/ports/auth.d.ts.map +1 -0
- package/dist/ports/auth.js +31 -0
- package/dist/ports/auth.js.map +1 -0
- package/dist/ports/builder.d.ts +61 -0
- package/dist/ports/builder.d.ts.map +1 -0
- package/dist/ports/builder.js +48 -0
- package/dist/ports/builder.js.map +1 -0
- package/dist/ports/cache.d.ts +15 -0
- package/dist/ports/cache.d.ts.map +1 -0
- package/dist/ports/cache.js +57 -0
- package/dist/ports/cache.js.map +1 -0
- package/dist/ports/clock.d.ts +10 -0
- package/dist/ports/clock.d.ts.map +1 -0
- package/dist/ports/clock.js +21 -0
- package/dist/ports/clock.js.map +1 -0
- package/dist/ports/events.d.ts +71 -0
- package/dist/ports/events.d.ts.map +1 -0
- package/dist/ports/events.js +2 -0
- package/dist/ports/events.js.map +1 -0
- package/dist/ports/id-generator.d.ts +12 -0
- package/dist/ports/id-generator.d.ts.map +1 -0
- package/dist/ports/id-generator.js +22 -0
- package/dist/ports/id-generator.js.map +1 -0
- package/dist/ports/index.d.ts +98 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +67 -0
- package/dist/ports/index.js.map +1 -0
- package/dist/ports/logger.d.ts +22 -0
- package/dist/ports/logger.d.ts.map +1 -0
- package/dist/ports/logger.js +34 -0
- package/dist/ports/logger.js.map +1 -0
- package/dist/ports/mailer.d.ts +6 -0
- package/dist/ports/mailer.d.ts.map +1 -0
- package/dist/ports/mailer.js +2 -0
- package/dist/ports/mailer.js.map +1 -0
- package/dist/ports/policy.d.ts +53 -0
- package/dist/ports/policy.d.ts.map +1 -0
- package/dist/ports/policy.js +81 -0
- package/dist/ports/policy.js.map +1 -0
- package/dist/ports/rate-limit.d.ts +41 -0
- package/dist/ports/rate-limit.d.ts.map +1 -0
- package/dist/ports/rate-limit.js +37 -0
- package/dist/ports/rate-limit.js.map +1 -0
- package/dist/ports/redaction.d.ts +26 -0
- package/dist/ports/redaction.d.ts.map +1 -0
- package/dist/ports/redaction.js +126 -0
- package/dist/ports/redaction.js.map +1 -0
- package/dist/ports/schedules.d.ts +9 -0
- package/dist/ports/schedules.d.ts.map +1 -0
- package/dist/ports/schedules.js +2 -0
- package/dist/ports/schedules.js.map +1 -0
- package/dist/ports/storage.d.ts +47 -0
- package/dist/ports/storage.d.ts.map +1 -0
- package/dist/ports/storage.js +185 -0
- package/dist/ports/storage.js.map +1 -0
- package/dist/ports/testing.d.ts +73 -0
- package/dist/ports/testing.d.ts.map +1 -0
- package/dist/ports/testing.js +105 -0
- package/dist/ports/testing.js.map +1 -0
- package/dist/ports/unit-of-work.d.ts +56 -0
- package/dist/ports/unit-of-work.d.ts.map +1 -0
- package/dist/ports/unit-of-work.js +64 -0
- package/dist/ports/unit-of-work.js.map +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/instrumentation.d.ts +91 -0
- package/dist/providers/instrumentation.d.ts.map +1 -0
- package/dist/providers/instrumentation.js +93 -0
- package/dist/providers/instrumentation.js.map +1 -0
- package/dist/providers/provider.d.ts +146 -0
- package/dist/providers/provider.d.ts.map +1 -0
- package/dist/providers/provider.js +31 -0
- package/dist/providers/provider.js.map +1 -0
- package/dist/schedules/index.d.ts +105 -0
- package/dist/schedules/index.d.ts.map +1 -0
- package/dist/schedules/index.js +178 -0
- package/dist/schedules/index.js.map +1 -0
- package/dist/server/contract-like.d.ts +5 -0
- package/dist/server/contract-like.d.ts.map +1 -0
- package/dist/server/contract-like.js +5 -0
- package/dist/server/contract-like.js.map +1 -0
- package/dist/server/health.d.ts +41 -0
- package/dist/server/health.d.ts.map +1 -0
- package/dist/server/health.js +46 -0
- package/dist/server/health.js.map +1 -0
- package/dist/server/hooks/auth.d.ts +42 -0
- package/dist/server/hooks/auth.d.ts.map +1 -0
- package/dist/server/hooks/auth.js +61 -0
- package/dist/server/hooks/auth.js.map +1 -0
- package/dist/server/hooks/cors.d.ts +13 -0
- package/dist/server/hooks/cors.d.ts.map +1 -0
- package/dist/server/hooks/cors.js +70 -0
- package/dist/server/hooks/cors.js.map +1 -0
- package/dist/server/hooks/errors.d.ts +66 -0
- package/dist/server/hooks/errors.d.ts.map +1 -0
- package/dist/server/hooks/errors.js +83 -0
- package/dist/server/hooks/errors.js.map +1 -0
- package/dist/server/hooks/index.d.ts +12 -0
- package/dist/server/hooks/index.d.ts.map +1 -0
- package/dist/server/hooks/index.js +12 -0
- package/dist/server/hooks/index.js.map +1 -0
- package/dist/server/hooks/logging.d.ts +33 -0
- package/dist/server/hooks/logging.d.ts.map +1 -0
- package/dist/server/hooks/logging.js +90 -0
- package/dist/server/hooks/logging.js.map +1 -0
- package/dist/server/hooks/rate-limit.d.ts +29 -0
- package/dist/server/hooks/rate-limit.d.ts.map +1 -0
- package/dist/server/hooks/rate-limit.js +93 -0
- package/dist/server/hooks/rate-limit.js.map +1 -0
- package/dist/server/hooks/utils.d.ts +9 -0
- package/dist/server/hooks/utils.d.ts.map +1 -0
- package/dist/server/hooks/utils.js +16 -0
- package/dist/server/hooks/utils.js.map +1 -0
- package/dist/server/hooks.d.ts +2 -0
- package/dist/server/hooks.d.ts.map +1 -0
- package/dist/server/hooks.js +2 -0
- package/dist/server/hooks.js.map +1 -0
- package/dist/server/http.d.ts +124 -0
- package/dist/server/http.d.ts.map +1 -0
- package/dist/server/http.js +2 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/index.d.ts +19 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +15 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/openapi.d.ts +32 -0
- package/dist/server/openapi.d.ts.map +1 -0
- package/dist/server/openapi.js +43 -0
- package/dist/server/openapi.js.map +1 -0
- package/dist/server/providers/index.d.ts +4 -0
- package/dist/server/providers/index.d.ts.map +1 -0
- package/dist/server/providers/index.js +4 -0
- package/dist/server/providers/index.js.map +1 -0
- package/dist/server/providers/loadProviderConfig.d.ts +7 -0
- package/dist/server/providers/loadProviderConfig.d.ts.map +1 -0
- package/dist/server/providers/loadProviderConfig.js +42 -0
- package/dist/server/providers/loadProviderConfig.js.map +1 -0
- package/dist/server/server.d.ts +86 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +1031 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/types.d.ts +3 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +3 -0
- package/dist/server/types.js.map +1 -0
- package/package.json +129 -0
- package/src/application/index.ts +747 -0
- package/src/client/client.ts +1105 -0
- package/src/client/index.ts +45 -0
- package/src/client/types.ts +305 -0
- package/src/config/index.ts +497 -0
- package/src/contracts/contract-builder.ts +583 -0
- package/src/contracts/contract-group.ts +502 -0
- package/src/contracts/contract-like.ts +29 -0
- package/src/contracts/index.ts +53 -0
- package/src/contracts/openapi-meta.ts +22 -0
- package/src/contracts/path-template.ts +91 -0
- package/src/contracts/rate-limit.ts +50 -0
- package/src/contracts/types.ts +207 -0
- package/src/contracts/utils.ts +56 -0
- package/src/domain/entity.ts +256 -0
- package/src/domain/events.ts +52 -0
- package/src/domain/index.ts +18 -0
- package/src/domain/value-object.ts +135 -0
- package/src/errors/catalog.ts +149 -0
- package/src/errors/http.ts +80 -0
- package/src/errors/index.ts +28 -0
- package/src/errors/response.ts +54 -0
- package/src/errors/validation.ts +35 -0
- package/src/events/index.ts +246 -0
- package/src/jobs/index.ts +211 -0
- package/src/mail/index.ts +177 -0
- package/src/openapi/index.ts +865 -0
- package/src/openapi/schema-introspector.ts +107 -0
- package/src/ports/audit.ts +176 -0
- package/src/ports/auth.ts +76 -0
- package/src/ports/builder.ts +97 -0
- package/src/ports/cache.ts +94 -0
- package/src/ports/clock.ts +34 -0
- package/src/ports/events.ts +100 -0
- package/src/ports/id-generator.ts +36 -0
- package/src/ports/index.ts +221 -0
- package/src/ports/logger.ts +67 -0
- package/src/ports/policy.ts +242 -0
- package/src/ports/rate-limit.ts +91 -0
- package/src/ports/redaction.ts +199 -0
- package/src/ports/storage.ts +282 -0
- package/src/ports/testing.ts +234 -0
- package/src/ports/unit-of-work.ts +134 -0
- package/src/providers/index.ts +40 -0
- package/src/providers/instrumentation.ts +248 -0
- package/src/providers/provider.ts +191 -0
- package/src/schedules/index.ts +442 -0
- package/src/server/contract-like.ts +8 -0
- package/src/server/health.ts +82 -0
- package/src/server/hooks/auth.ts +147 -0
- package/src/server/hooks/cors.ts +87 -0
- package/src/server/hooks/errors.ts +126 -0
- package/src/server/hooks/index.ts +43 -0
- package/src/server/hooks/logging.ts +121 -0
- package/src/server/hooks/rate-limit.ts +171 -0
- package/src/server/hooks/utils.ts +16 -0
- package/src/server/hooks.ts +1 -0
- package/src/server/http.ts +189 -0
- package/src/server/index.ts +35 -0
- package/src/server/openapi.ts +72 -0
- package/src/server/providers/index.ts +3 -0
- package/src/server/providers/loadProviderConfig.ts +72 -0
- package/src/server/server.ts +1521 -0
- package/src/server/types.ts +2 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface IdGeneratorPort {
|
|
2
|
+
nextId(): string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface SequenceIdGeneratorPort extends IdGeneratorPort {
|
|
6
|
+
reset(next?: number): void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createUuidIdGenerator(): IdGeneratorPort {
|
|
10
|
+
return {
|
|
11
|
+
nextId: () => {
|
|
12
|
+
if (typeof globalThis.crypto?.randomUUID !== "function") {
|
|
13
|
+
throw new Error(
|
|
14
|
+
"createUuidIdGenerator requires globalThis.crypto.randomUUID(). Provide a custom IdGeneratorPort in this runtime.",
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return globalThis.crypto.randomUUID();
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createSequenceIdGenerator(
|
|
24
|
+
options: { prefix?: string; start?: number } = {},
|
|
25
|
+
): SequenceIdGeneratorPort {
|
|
26
|
+
const prefix = options.prefix ?? "id";
|
|
27
|
+
const start = options.start ?? 1;
|
|
28
|
+
let next = start;
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
nextId: () => `${prefix}_${next++}`,
|
|
32
|
+
reset: (value = start) => {
|
|
33
|
+
next = value;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @beignet/core/ports
|
|
3
|
+
*
|
|
4
|
+
* A tiny, framework-agnostic subpath that standardizes how apps define and
|
|
5
|
+
* type their "ports" (outbound dependencies like db, cache, event bus).
|
|
6
|
+
*
|
|
7
|
+
* This subpath provides:
|
|
8
|
+
* - `definePorts` – helper to define a typed ports object
|
|
9
|
+
* - `PortsContext` – a generic type that describes ctx objects that carry ports
|
|
10
|
+
* - `EventBusPort` – interface for event bus implementations
|
|
11
|
+
* - `JobDispatcherPort` – interface for job dispatch implementations
|
|
12
|
+
* - `UnitOfWorkPort` – interface for app-owned transaction boundaries
|
|
13
|
+
* - `AuthPort` – interface for request authentication implementations
|
|
14
|
+
* - `AuditLogPort` – interface for audit/activity log implementations
|
|
15
|
+
* - `ClockPort` – interface for deterministic time
|
|
16
|
+
* - `IdGeneratorPort` – interface for deterministic id generation
|
|
17
|
+
* - `LoggerPort` – interface for structured application logging
|
|
18
|
+
* - `GatePort` – interface for application authorization policies
|
|
19
|
+
* - `RateLimitPort` – interface for rate limiting implementations
|
|
20
|
+
* - `CachePort` – interface for cache implementations
|
|
21
|
+
* - `StoragePort` – interface for object/file storage implementations
|
|
22
|
+
*
|
|
23
|
+
* Dedicated framework areas own their capability-specific APIs:
|
|
24
|
+
* - `@beignet/core/mail` owns `MailerPort` and mail test adapters
|
|
25
|
+
* - `@beignet/core/schedules` owns scheduled task definitions and runners
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A generic map of named "ports" (outbound dependencies) that your
|
|
30
|
+
* application depends on: db, mailer, cache, event bus, etc.
|
|
31
|
+
*
|
|
32
|
+
* This is intentionally very loose: users define the exact shape via
|
|
33
|
+
* `definePorts(...)`.
|
|
34
|
+
*/
|
|
35
|
+
export type AnyPorts = Record<string, unknown>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Define the set of ports (outbound dependencies) for your application.
|
|
39
|
+
*
|
|
40
|
+
* This is essentially a typed identity function. It:
|
|
41
|
+
* - captures the exact shape of the provided `ports` object
|
|
42
|
+
* - allows you to export `type AppPorts = typeof appPorts` in your app
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const appPorts = definePorts({
|
|
47
|
+
* db: dbAdapter,
|
|
48
|
+
* mailer: mailerAdapter,
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* export type AppPorts = typeof appPorts;
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @param ports - The ports object to define
|
|
55
|
+
* @returns The same ports object, unchanged
|
|
56
|
+
*/
|
|
57
|
+
export function definePorts<P extends AnyPorts>(ports: P): P {
|
|
58
|
+
return ports;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A small helper type describing a context object that carries `ports`.
|
|
63
|
+
*
|
|
64
|
+
* You typically use this to define your application context:
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* import type { PortsContext } from "@beignet/core/ports";
|
|
69
|
+
* import type { AppPorts } from "./core/ports";
|
|
70
|
+
*
|
|
71
|
+
* export interface AppCtx extends PortsContext<AppPorts> {
|
|
72
|
+
* user: { id: string } | null;
|
|
73
|
+
* now: () => Date;
|
|
74
|
+
* appError: AppErrorCreator<typeof errors>;
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export interface PortsContext<P extends AnyPorts = AnyPorts> {
|
|
79
|
+
ports: P;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type {
|
|
83
|
+
ActivityActor,
|
|
84
|
+
ActivityActorType,
|
|
85
|
+
ActivityMetadata,
|
|
86
|
+
ActivityMetadataValue,
|
|
87
|
+
ActivityResource,
|
|
88
|
+
ActivityTenant,
|
|
89
|
+
AuditLogEntry,
|
|
90
|
+
AuditLogEntryInput,
|
|
91
|
+
AuditLogOptions,
|
|
92
|
+
AuditLogPort,
|
|
93
|
+
AuditOutcome,
|
|
94
|
+
MemoryAuditLogPort,
|
|
95
|
+
} from "./audit";
|
|
96
|
+
export {
|
|
97
|
+
createAnonymousActor,
|
|
98
|
+
createMemoryAuditLog,
|
|
99
|
+
createRedactedAuditLog,
|
|
100
|
+
createServiceActor,
|
|
101
|
+
createSystemActor,
|
|
102
|
+
createTenant,
|
|
103
|
+
createUserActor,
|
|
104
|
+
normalizeAuditLogEntry,
|
|
105
|
+
redactAuditLogEntry,
|
|
106
|
+
} from "./audit";
|
|
107
|
+
export type {
|
|
108
|
+
AuthPort,
|
|
109
|
+
AuthRequestLike,
|
|
110
|
+
AuthSession,
|
|
111
|
+
StaticAuthSessionFactory,
|
|
112
|
+
} from "./auth";
|
|
113
|
+
export {
|
|
114
|
+
AuthUnauthorizedError,
|
|
115
|
+
createAnonymousAuth,
|
|
116
|
+
createStaticAuth,
|
|
117
|
+
} from "./auth";
|
|
118
|
+
// Ports builder exports
|
|
119
|
+
export {
|
|
120
|
+
createPortsBuilder,
|
|
121
|
+
type PortsBuilder,
|
|
122
|
+
type PortsOf,
|
|
123
|
+
} from "./builder";
|
|
124
|
+
export type { CachePort, CacheSetOptions } from "./cache";
|
|
125
|
+
// Cache port exports
|
|
126
|
+
export { createMemoryCache } from "./cache";
|
|
127
|
+
// Clock port exports
|
|
128
|
+
export type { ClockPort, FrozenClockPort } from "./clock";
|
|
129
|
+
export { createFrozenClock, createSystemClock } from "./clock";
|
|
130
|
+
// Event bus exports
|
|
131
|
+
export type {
|
|
132
|
+
DomainEventDef,
|
|
133
|
+
EventBusPort,
|
|
134
|
+
InferEventPayload,
|
|
135
|
+
InferJobPayload,
|
|
136
|
+
JobDef,
|
|
137
|
+
JobDispatcherPort,
|
|
138
|
+
} from "./events";
|
|
139
|
+
// Id generator port exports
|
|
140
|
+
export type { IdGeneratorPort, SequenceIdGeneratorPort } from "./id-generator";
|
|
141
|
+
export {
|
|
142
|
+
createSequenceIdGenerator,
|
|
143
|
+
createUuidIdGenerator,
|
|
144
|
+
} from "./id-generator";
|
|
145
|
+
// Logger port exports
|
|
146
|
+
export type {
|
|
147
|
+
LoggerPort,
|
|
148
|
+
LogLevel,
|
|
149
|
+
MemoryLogEntry,
|
|
150
|
+
MemoryLoggerPort,
|
|
151
|
+
} from "./logger";
|
|
152
|
+
export { createMemoryLogger, createNoopLogger } from "./logger";
|
|
153
|
+
// Policy and authorization gate exports
|
|
154
|
+
export type {
|
|
155
|
+
BoundGate,
|
|
156
|
+
CreateGateOptions,
|
|
157
|
+
GateAllowedDecision,
|
|
158
|
+
GateDecision,
|
|
159
|
+
GateDeniedDecision,
|
|
160
|
+
GateDenyHandler,
|
|
161
|
+
GatePolicyResult,
|
|
162
|
+
GatePort,
|
|
163
|
+
PolicyContextFromDefinitions,
|
|
164
|
+
PolicyDefinition,
|
|
165
|
+
PolicyMapFromDefinitions,
|
|
166
|
+
PolicyResolver,
|
|
167
|
+
PolicySubjectArgs,
|
|
168
|
+
} from "./policy";
|
|
169
|
+
export {
|
|
170
|
+
allow,
|
|
171
|
+
createGate,
|
|
172
|
+
definePolicy,
|
|
173
|
+
deny,
|
|
174
|
+
GateAuthorizationError,
|
|
175
|
+
} from "./policy";
|
|
176
|
+
export type {
|
|
177
|
+
RateLimitHitOptions,
|
|
178
|
+
RateLimitPort,
|
|
179
|
+
RateLimitResult,
|
|
180
|
+
} from "./rate-limit";
|
|
181
|
+
// Rate limit port exports
|
|
182
|
+
export { createMemoryRateLimiter } from "./rate-limit";
|
|
183
|
+
// Redaction exports
|
|
184
|
+
export {
|
|
185
|
+
createRedactor,
|
|
186
|
+
DEFAULT_CIRCULAR_VALUE,
|
|
187
|
+
DEFAULT_REDACTED_VALUE,
|
|
188
|
+
DEFAULT_SENSITIVE_KEY_TERMS,
|
|
189
|
+
DEFAULT_SENSITIVE_KEYS,
|
|
190
|
+
DEFAULT_TRUNCATED_VALUE,
|
|
191
|
+
isSensitiveKey,
|
|
192
|
+
type RedactableHeaders,
|
|
193
|
+
type RedactionDecisionContext,
|
|
194
|
+
type RedactionOptions,
|
|
195
|
+
type Redactor,
|
|
196
|
+
redactHeaders,
|
|
197
|
+
redactValue,
|
|
198
|
+
} from "./redaction";
|
|
199
|
+
// Storage port exports
|
|
200
|
+
export type {
|
|
201
|
+
MemoryStorageOptions,
|
|
202
|
+
StorageBody,
|
|
203
|
+
StorageMetadata,
|
|
204
|
+
StorageObject,
|
|
205
|
+
StorageObjectBody,
|
|
206
|
+
StoragePort,
|
|
207
|
+
StoragePutOptions,
|
|
208
|
+
StorageVisibility,
|
|
209
|
+
} from "./storage";
|
|
210
|
+
export { createMemoryStorage } from "./storage";
|
|
211
|
+
// Unit of Work exports
|
|
212
|
+
export {
|
|
213
|
+
type BufferedDomainEventRecorder,
|
|
214
|
+
createDomainEventRecorder,
|
|
215
|
+
createNoopUnitOfWork,
|
|
216
|
+
type DomainEventRecorderPort,
|
|
217
|
+
type NoopUnitOfWorkOptions,
|
|
218
|
+
type RecordedDomainEvent,
|
|
219
|
+
type UnitOfWorkCallback,
|
|
220
|
+
type UnitOfWorkPort,
|
|
221
|
+
} from "./unit-of-work";
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
|
|
2
|
+
|
|
3
|
+
export interface LoggerPort {
|
|
4
|
+
trace(message: string, meta?: Record<string, unknown>): void;
|
|
5
|
+
debug(message: string, meta?: Record<string, unknown>): void;
|
|
6
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
7
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
8
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
9
|
+
fatal(message: string, meta?: Record<string, unknown>): void;
|
|
10
|
+
child(bindings: Record<string, unknown>): LoggerPort;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface MemoryLogEntry {
|
|
14
|
+
level: LogLevel;
|
|
15
|
+
message: string;
|
|
16
|
+
meta?: Record<string, unknown>;
|
|
17
|
+
bindings: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MemoryLoggerPort extends LoggerPort {
|
|
21
|
+
entries: MemoryLogEntry[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createNoopLogger(): LoggerPort {
|
|
25
|
+
const logger: LoggerPort = {
|
|
26
|
+
trace: () => {},
|
|
27
|
+
debug: () => {},
|
|
28
|
+
info: () => {},
|
|
29
|
+
warn: () => {},
|
|
30
|
+
error: () => {},
|
|
31
|
+
fatal: () => {},
|
|
32
|
+
child: () => logger,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return logger;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function createMemoryLogger(
|
|
39
|
+
bindings: Record<string, unknown> = {},
|
|
40
|
+
entries: MemoryLogEntry[] = [],
|
|
41
|
+
): MemoryLoggerPort {
|
|
42
|
+
const capturedBindings = { ...bindings };
|
|
43
|
+
const record = (
|
|
44
|
+
level: LogLevel,
|
|
45
|
+
message: string,
|
|
46
|
+
meta?: Record<string, unknown>,
|
|
47
|
+
) => {
|
|
48
|
+
entries.push({
|
|
49
|
+
level,
|
|
50
|
+
message,
|
|
51
|
+
meta: meta ? { ...meta } : undefined,
|
|
52
|
+
bindings: { ...capturedBindings },
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
entries,
|
|
58
|
+
trace: (message, meta) => record("trace", message, meta),
|
|
59
|
+
debug: (message, meta) => record("debug", message, meta),
|
|
60
|
+
info: (message, meta) => record("info", message, meta),
|
|
61
|
+
warn: (message, meta) => record("warn", message, meta),
|
|
62
|
+
error: (message, meta) => record("error", message, meta),
|
|
63
|
+
fatal: (message, meta) => record("fatal", message, meta),
|
|
64
|
+
child: (childBindings) =>
|
|
65
|
+
createMemoryLogger({ ...capturedBindings, ...childBindings }, entries),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
2
|
+
|
|
3
|
+
export type GateAllowedDecision = {
|
|
4
|
+
allowed: true;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type GateDeniedDecision = {
|
|
8
|
+
allowed: false;
|
|
9
|
+
reason?: string;
|
|
10
|
+
code?: string;
|
|
11
|
+
details?: unknown;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type GateDecision = GateAllowedDecision | GateDeniedDecision;
|
|
15
|
+
export type GatePolicyResult = boolean | GateDecision;
|
|
16
|
+
|
|
17
|
+
export type PolicyResolver = (
|
|
18
|
+
...args: never[]
|
|
19
|
+
) => MaybePromise<GatePolicyResult>;
|
|
20
|
+
|
|
21
|
+
export type PolicyDefinition<
|
|
22
|
+
TPolicies extends Record<string, PolicyResolver> = Record<
|
|
23
|
+
string,
|
|
24
|
+
PolicyResolver
|
|
25
|
+
>,
|
|
26
|
+
> = {
|
|
27
|
+
policies: TPolicies;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type PolicyContext<TResolver> = TResolver extends (
|
|
31
|
+
ctx: infer Ctx,
|
|
32
|
+
...args: never[]
|
|
33
|
+
) => MaybePromise<GatePolicyResult>
|
|
34
|
+
? Ctx
|
|
35
|
+
: never;
|
|
36
|
+
|
|
37
|
+
export type PolicySubjectArgs<TResolver> = TResolver extends (
|
|
38
|
+
...args: infer TArgs
|
|
39
|
+
) => MaybePromise<GatePolicyResult>
|
|
40
|
+
? TArgs extends [unknown, infer Subject]
|
|
41
|
+
? [subject: Subject]
|
|
42
|
+
: []
|
|
43
|
+
: [];
|
|
44
|
+
|
|
45
|
+
type UnionToIntersection<T> = (
|
|
46
|
+
T extends unknown
|
|
47
|
+
? (value: T) => void
|
|
48
|
+
: never
|
|
49
|
+
) extends (value: infer U) => void
|
|
50
|
+
? U
|
|
51
|
+
: never;
|
|
52
|
+
|
|
53
|
+
export type PolicyMapFromDefinitions<
|
|
54
|
+
TPolicies extends readonly PolicyDefinition[],
|
|
55
|
+
> = UnionToIntersection<
|
|
56
|
+
TPolicies[number] extends PolicyDefinition<infer TPolicyMap>
|
|
57
|
+
? TPolicyMap
|
|
58
|
+
: never
|
|
59
|
+
>;
|
|
60
|
+
|
|
61
|
+
export type PolicyContextFromDefinitions<
|
|
62
|
+
TPolicies extends readonly PolicyDefinition[],
|
|
63
|
+
> = PolicyContext<
|
|
64
|
+
PolicyMapFromDefinitions<TPolicies>[keyof PolicyMapFromDefinitions<TPolicies>]
|
|
65
|
+
>;
|
|
66
|
+
|
|
67
|
+
export type BoundGate<TPolicies extends readonly PolicyDefinition[]> = {
|
|
68
|
+
can<TAbility extends keyof PolicyMapFromDefinitions<TPolicies> & string>(
|
|
69
|
+
ability: TAbility,
|
|
70
|
+
...subject: PolicySubjectArgs<PolicyMapFromDefinitions<TPolicies>[TAbility]>
|
|
71
|
+
): Promise<boolean>;
|
|
72
|
+
inspect<TAbility extends keyof PolicyMapFromDefinitions<TPolicies> & string>(
|
|
73
|
+
ability: TAbility,
|
|
74
|
+
...subject: PolicySubjectArgs<PolicyMapFromDefinitions<TPolicies>[TAbility]>
|
|
75
|
+
): Promise<GateDecision>;
|
|
76
|
+
authorize<
|
|
77
|
+
TAbility extends keyof PolicyMapFromDefinitions<TPolicies> & string,
|
|
78
|
+
>(
|
|
79
|
+
ability: TAbility,
|
|
80
|
+
...subject: PolicySubjectArgs<PolicyMapFromDefinitions<TPolicies>[TAbility]>
|
|
81
|
+
): Promise<GateAllowedDecision>;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type GatePort<
|
|
85
|
+
TContext,
|
|
86
|
+
TPolicies extends readonly PolicyDefinition[] = readonly PolicyDefinition[],
|
|
87
|
+
> = {
|
|
88
|
+
bind(ctx: TContext): BoundGate<TPolicies>;
|
|
89
|
+
can<TAbility extends keyof PolicyMapFromDefinitions<TPolicies> & string>(
|
|
90
|
+
ctx: TContext,
|
|
91
|
+
ability: TAbility,
|
|
92
|
+
...subject: PolicySubjectArgs<PolicyMapFromDefinitions<TPolicies>[TAbility]>
|
|
93
|
+
): Promise<boolean>;
|
|
94
|
+
inspect<TAbility extends keyof PolicyMapFromDefinitions<TPolicies> & string>(
|
|
95
|
+
ctx: TContext,
|
|
96
|
+
ability: TAbility,
|
|
97
|
+
...subject: PolicySubjectArgs<PolicyMapFromDefinitions<TPolicies>[TAbility]>
|
|
98
|
+
): Promise<GateDecision>;
|
|
99
|
+
authorize<
|
|
100
|
+
TAbility extends keyof PolicyMapFromDefinitions<TPolicies> & string,
|
|
101
|
+
>(
|
|
102
|
+
ctx: TContext,
|
|
103
|
+
ability: TAbility,
|
|
104
|
+
...subject: PolicySubjectArgs<PolicyMapFromDefinitions<TPolicies>[TAbility]>
|
|
105
|
+
): Promise<GateAllowedDecision>;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export type GateDenyHandler<TContext> = (
|
|
109
|
+
decision: GateDeniedDecision,
|
|
110
|
+
params: {
|
|
111
|
+
ctx: TContext;
|
|
112
|
+
ability: string;
|
|
113
|
+
subject?: unknown;
|
|
114
|
+
},
|
|
115
|
+
) => MaybePromise<Error | undefined>;
|
|
116
|
+
|
|
117
|
+
export type CreateGateOptions<
|
|
118
|
+
TContext,
|
|
119
|
+
TPolicies extends readonly PolicyDefinition[],
|
|
120
|
+
> = {
|
|
121
|
+
policies: TPolicies;
|
|
122
|
+
onDeny?: GateDenyHandler<TContext>;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export class GateAuthorizationError extends Error {
|
|
126
|
+
readonly code: string;
|
|
127
|
+
readonly status = 403;
|
|
128
|
+
readonly details?: unknown;
|
|
129
|
+
|
|
130
|
+
constructor(decision: GateDeniedDecision = deny()) {
|
|
131
|
+
super(decision.reason ?? "Forbidden");
|
|
132
|
+
this.name = "GateAuthorizationError";
|
|
133
|
+
this.code = decision.code ?? "FORBIDDEN";
|
|
134
|
+
this.details = decision.details;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function allow(): GateAllowedDecision {
|
|
139
|
+
return { allowed: true };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function deny(
|
|
143
|
+
reasonOrDecision?: string | Omit<GateDeniedDecision, "allowed">,
|
|
144
|
+
): GateDeniedDecision {
|
|
145
|
+
if (typeof reasonOrDecision === "string") {
|
|
146
|
+
return { allowed: false, reason: reasonOrDecision };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
allowed: false,
|
|
151
|
+
...reasonOrDecision,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function definePolicy<
|
|
156
|
+
const TPolicies extends Record<string, PolicyResolver>,
|
|
157
|
+
>(policies: TPolicies): PolicyDefinition<TPolicies> {
|
|
158
|
+
return { policies };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function createGate<
|
|
162
|
+
TContext,
|
|
163
|
+
const TPolicies extends readonly PolicyDefinition[],
|
|
164
|
+
>(
|
|
165
|
+
options: CreateGateOptions<TContext, TPolicies>,
|
|
166
|
+
): GatePort<TContext, TPolicies> {
|
|
167
|
+
const registry = new Map<string, PolicyResolver>();
|
|
168
|
+
|
|
169
|
+
for (const definition of options.policies) {
|
|
170
|
+
for (const [ability, resolver] of Object.entries(definition.policies)) {
|
|
171
|
+
if (registry.has(ability)) {
|
|
172
|
+
throw new Error(`Policy ability "${ability}" is already registered.`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
registry.set(ability, resolver);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function inspect(
|
|
180
|
+
ctx: TContext,
|
|
181
|
+
ability: string,
|
|
182
|
+
subject?: unknown,
|
|
183
|
+
): Promise<GateDecision> {
|
|
184
|
+
const resolver = registry.get(ability);
|
|
185
|
+
if (!resolver) {
|
|
186
|
+
return deny({
|
|
187
|
+
reason: `No policy registered for "${ability}".`,
|
|
188
|
+
code: "POLICY_NOT_FOUND",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const result = await resolver(ctx as never, subject as never);
|
|
193
|
+
return normalizeDecision(result);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function authorize(
|
|
197
|
+
ctx: TContext,
|
|
198
|
+
ability: string,
|
|
199
|
+
subject?: unknown,
|
|
200
|
+
): Promise<GateAllowedDecision> {
|
|
201
|
+
const decision = await inspect(ctx, ability, subject);
|
|
202
|
+
if (decision.allowed) return decision;
|
|
203
|
+
|
|
204
|
+
const thrown = await options.onDeny?.(decision, { ctx, ability, subject });
|
|
205
|
+
throw thrown ?? new GateAuthorizationError(decision);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function bind(ctx: TContext): BoundGate<TPolicies> {
|
|
209
|
+
return {
|
|
210
|
+
can: async (ability, ...subject) => gate.can(ctx, ability, ...subject),
|
|
211
|
+
inspect: async (ability, ...subject) =>
|
|
212
|
+
gate.inspect(ctx, ability, ...subject),
|
|
213
|
+
authorize: async (ability, ...subject) =>
|
|
214
|
+
gate.authorize(ctx, ability, ...subject),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const gate: GatePort<TContext, TPolicies> = {
|
|
219
|
+
bind,
|
|
220
|
+
async can(ctx, ability, ...subject) {
|
|
221
|
+
return (await inspect(ctx, ability, firstSubject(subject))).allowed;
|
|
222
|
+
},
|
|
223
|
+
inspect: async (ctx, ability, ...subject) =>
|
|
224
|
+
inspect(ctx, ability, firstSubject(subject)),
|
|
225
|
+
authorize: async (ctx, ability, ...subject) =>
|
|
226
|
+
authorize(ctx, ability, firstSubject(subject)),
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
return gate;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function normalizeDecision(result: GatePolicyResult): GateDecision {
|
|
233
|
+
if (typeof result === "boolean") {
|
|
234
|
+
return result ? allow() : deny();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function firstSubject(subject: readonly unknown[]): unknown {
|
|
241
|
+
return subject[0];
|
|
242
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export interface RateLimitHitOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Unique key for this rate limit window.
|
|
4
|
+
*
|
|
5
|
+
* Examples: "global", "ip:203.0.113.10", "user:123".
|
|
6
|
+
*/
|
|
7
|
+
key: string;
|
|
8
|
+
/**
|
|
9
|
+
* Maximum allowed hits inside the window.
|
|
10
|
+
*/
|
|
11
|
+
limit: number;
|
|
12
|
+
/**
|
|
13
|
+
* Length of the window in seconds.
|
|
14
|
+
*/
|
|
15
|
+
windowSec: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface RateLimitResult {
|
|
19
|
+
/**
|
|
20
|
+
* True when the hit is within the configured limit.
|
|
21
|
+
*/
|
|
22
|
+
allowed: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Remaining allowed hits in the window, if known. May be null if the
|
|
25
|
+
* implementation does not track it.
|
|
26
|
+
*/
|
|
27
|
+
remaining: number | null;
|
|
28
|
+
/**
|
|
29
|
+
* Date when the window resets, if known. May be null.
|
|
30
|
+
*/
|
|
31
|
+
resetAt: Date | null;
|
|
32
|
+
/**
|
|
33
|
+
* Seconds until the caller should retry, if the hit was rejected and the
|
|
34
|
+
* implementation can calculate it.
|
|
35
|
+
*/
|
|
36
|
+
retryAfterSeconds: number | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface RateLimitPort {
|
|
40
|
+
hit(options: RateLimitHitOptions): Promise<RateLimitResult>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type MemoryRateLimitWindow = {
|
|
44
|
+
count: number;
|
|
45
|
+
resetAt: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function assertPositiveInteger(name: string, value: number): void {
|
|
49
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
50
|
+
throw new Error(`${name} must be a positive integer`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function toRetryAfterSeconds(resetAt: number): number {
|
|
55
|
+
return Math.max(0, Math.ceil((resetAt - Date.now()) / 1000));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createMemoryRateLimiter(): RateLimitPort {
|
|
59
|
+
const windows = new Map<string, MemoryRateLimitWindow>();
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
async hit({ key, limit, windowSec }) {
|
|
63
|
+
assertPositiveInteger("limit", limit);
|
|
64
|
+
assertPositiveInteger("windowSec", windowSec);
|
|
65
|
+
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
const current = windows.get(key);
|
|
68
|
+
const window =
|
|
69
|
+
current && current.resetAt > now
|
|
70
|
+
? current
|
|
71
|
+
: {
|
|
72
|
+
count: 0,
|
|
73
|
+
resetAt: now + windowSec * 1000,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
window.count += 1;
|
|
77
|
+
windows.set(key, window);
|
|
78
|
+
|
|
79
|
+
const allowed = window.count <= limit;
|
|
80
|
+
const remaining = Math.max(0, limit - window.count);
|
|
81
|
+
const resetAt = new Date(window.resetAt);
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
allowed,
|
|
85
|
+
remaining,
|
|
86
|
+
resetAt,
|
|
87
|
+
retryAfterSeconds: allowed ? null : toRetryAfterSeconds(window.resetAt),
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|