@bgord/bun 1.21.11 → 1.22.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/dist/alert-channel-composite.adapter.d.ts.map +1 -1
- package/dist/alert-channel-composite.adapter.js +1 -0
- package/dist/alert-channel-composite.adapter.js.map +1 -1
- package/dist/api-version.middleware.d.ts +1 -0
- package/dist/api-version.middleware.d.ts.map +1 -1
- package/dist/api-version.middleware.js +3 -1
- package/dist/api-version.middleware.js.map +1 -1
- package/dist/cache-codec-identity.strategy.d.ts +7 -0
- package/dist/cache-codec-identity.strategy.d.ts.map +1 -0
- package/dist/cache-codec-identity.strategy.js +9 -0
- package/dist/cache-codec-identity.strategy.js.map +1 -0
- package/dist/cache-codec-sitemap-entries.strategy.d.ts +11 -0
- package/dist/cache-codec-sitemap-entries.strategy.d.ts.map +1 -0
- package/dist/cache-codec-sitemap-entries.strategy.js +13 -0
- package/dist/cache-codec-sitemap-entries.strategy.js.map +1 -0
- package/dist/cache-codec.strategy.d.ts +6 -0
- package/dist/cache-codec.strategy.d.ts.map +1 -0
- package/dist/cache-codec.strategy.js +2 -0
- package/dist/cache-codec.strategy.js.map +1 -0
- package/dist/cache-repository-node-cache.adapter.d.ts +3 -2
- package/dist/cache-repository-node-cache.adapter.d.ts.map +1 -1
- package/dist/cache-repository-node-cache.adapter.js +6 -4
- package/dist/cache-repository-node-cache.adapter.js.map +1 -1
- package/dist/cache-repository-noop.adapter.d.ts +3 -2
- package/dist/cache-repository-noop.adapter.d.ts.map +1 -1
- package/dist/cache-repository-noop.adapter.js.map +1 -1
- package/dist/cache-repository-redis.adapter.d.ts +3 -2
- package/dist/cache-repository-redis.adapter.d.ts.map +1 -1
- package/dist/cache-repository-redis.adapter.js +8 -5
- package/dist/cache-repository-redis.adapter.js.map +1 -1
- package/dist/cache-repository.port.d.ts +3 -2
- package/dist/cache-repository.port.d.ts.map +1 -1
- package/dist/cache-resolver-noop.strategy.d.ts +8 -0
- package/dist/cache-resolver-noop.strategy.d.ts.map +1 -0
- package/dist/cache-resolver-noop.strategy.js +7 -0
- package/dist/cache-resolver-noop.strategy.js.map +1 -0
- package/dist/cache-resolver-read-through.strategy.d.ts +15 -0
- package/dist/cache-resolver-read-through.strategy.d.ts.map +1 -0
- package/dist/cache-resolver-read-through.strategy.js +21 -0
- package/dist/cache-resolver-read-through.strategy.js.map +1 -0
- package/dist/cache-resolver.strategy.d.ts +2 -9
- package/dist/cache-resolver.strategy.d.ts.map +1 -1
- package/dist/cache-resolver.strategy.js +1 -5
- package/dist/cache-resolver.strategy.js.map +1 -1
- package/dist/cache-value.vo.d.ts +6 -0
- package/dist/cache-value.vo.d.ts.map +1 -0
- package/dist/cache-value.vo.js +2 -0
- package/dist/cache-value.vo.js.map +1 -0
- package/dist/correlation-id.vo.d.ts.map +1 -1
- package/dist/correlation-id.vo.js +1 -0
- package/dist/correlation-id.vo.js.map +1 -1
- package/dist/cron-scheduler-with-logger.adapter.d.ts +3 -0
- package/dist/cron-scheduler-with-logger.adapter.d.ts.map +1 -1
- package/dist/cron-scheduler-with-logger.adapter.js +29 -7
- package/dist/cron-scheduler-with-logger.adapter.js.map +1 -1
- package/dist/cron-task-handler-with-logger.strategy.js +1 -1
- package/dist/cron-task-handler-with-logger.strategy.js.map +1 -1
- package/dist/crypto-key-provider-with-memo.adapter.d.ts +12 -0
- package/dist/crypto-key-provider-with-memo.adapter.d.ts.map +1 -0
- package/dist/crypto-key-provider-with-memo.adapter.js +17 -0
- package/dist/crypto-key-provider-with-memo.adapter.js.map +1 -0
- package/dist/environment-loader-process-safe.adapter.d.ts +3 -1
- package/dist/environment-loader-process-safe.adapter.d.ts.map +1 -1
- package/dist/environment-loader-process-safe.adapter.js +4 -2
- package/dist/environment-loader-process-safe.adapter.js.map +1 -1
- package/dist/event-store-with-logger.adapter.d.ts +3 -0
- package/dist/event-store-with-logger.adapter.d.ts.map +1 -1
- package/dist/event-store-with-logger.adapter.js +88 -27
- package/dist/event-store-with-logger.adapter.js.map +1 -1
- package/dist/event-stream.vo.d.ts.map +1 -1
- package/dist/event-stream.vo.js +3 -1
- package/dist/event-stream.vo.js.map +1 -1
- package/dist/file-draft-tar-gz.service.d.ts +8 -0
- package/dist/file-draft-tar-gz.service.d.ts.map +1 -0
- package/dist/file-draft-tar-gz.service.js +19 -0
- package/dist/file-draft-tar-gz.service.js.map +1 -0
- package/dist/file-reader-json-with-cache.adapter.d.ts +1 -0
- package/dist/file-reader-json-with-cache.adapter.d.ts.map +1 -1
- package/dist/file-reader-json-with-cache.adapter.js +3 -1
- package/dist/file-reader-json-with-cache.adapter.js.map +1 -1
- package/dist/file-reader-text-with-cache.adapter.d.ts +1 -0
- package/dist/file-reader-text-with-cache.adapter.d.ts.map +1 -1
- package/dist/file-reader-text-with-cache.adapter.js +3 -1
- package/dist/file-reader-text-with-cache.adapter.js.map +1 -1
- package/dist/healthcheck-hono.handler.js +2 -2
- package/dist/healthcheck-hono.handler.js.map +1 -1
- package/dist/healthcheck.handler.d.ts +1 -0
- package/dist/healthcheck.handler.d.ts.map +1 -1
- package/dist/healthcheck.handler.js +1 -0
- package/dist/healthcheck.handler.js.map +1 -1
- package/dist/http-logger-hono.middleware.d.ts.map +1 -1
- package/dist/http-logger-hono.middleware.js +0 -3
- package/dist/http-logger-hono.middleware.js.map +1 -1
- package/dist/http-logger.middleware.d.ts +0 -1
- package/dist/http-logger.middleware.d.ts.map +1 -1
- package/dist/http-logger.middleware.js +0 -1
- package/dist/http-logger.middleware.js.map +1 -1
- package/dist/idempotency-store-cache.adapter.d.ts +13 -0
- package/dist/idempotency-store-cache.adapter.d.ts.map +1 -0
- package/dist/idempotency-store-cache.adapter.js +14 -0
- package/dist/idempotency-store-cache.adapter.js.map +1 -0
- package/dist/idempotency-store-noop.adapter.d.ts +6 -0
- package/dist/idempotency-store-noop.adapter.d.ts.map +1 -0
- package/dist/idempotency-store-noop.adapter.js +6 -0
- package/dist/idempotency-store-noop.adapter.js.map +1 -0
- package/dist/idempotency-store-with-logger.adapter.d.ts +17 -0
- package/dist/idempotency-store-with-logger.adapter.d.ts.map +1 -0
- package/dist/idempotency-store-with-logger.adapter.js +48 -0
- package/dist/idempotency-store-with-logger.adapter.js.map +1 -0
- package/dist/idempotency-store.port.d.ts +5 -0
- package/dist/idempotency-store.port.d.ts.map +1 -0
- package/dist/idempotency-store.port.js +2 -0
- package/dist/idempotency-store.port.js.map +1 -0
- package/dist/image-generator-web-view.adapter.js +8 -72
- package/dist/image-generator-web-view.adapter.js.map +1 -1
- package/dist/index.d.ts +19 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -6
- package/dist/index.js.map +1 -1
- package/dist/job-queue-with-logger.adapter.d.ts +2 -0
- package/dist/job-queue-with-logger.adapter.d.ts.map +1 -1
- package/dist/job-queue-with-logger.adapter.js +133 -36
- package/dist/job-queue-with-logger.adapter.js.map +1 -1
- package/dist/logger.port.d.ts +0 -1
- package/dist/logger.port.d.ts.map +1 -1
- package/dist/mailer-content-html.vo.js +1 -1
- package/dist/mailer-content-html.vo.js.map +1 -1
- package/dist/message-bus-with-logger.adapter.d.ts +2 -0
- package/dist/message-bus-with-logger.adapter.d.ts.map +1 -1
- package/dist/message-bus-with-logger.adapter.js +28 -8
- package/dist/message-bus-with-logger.adapter.js.map +1 -1
- package/dist/modules/preferences/invariants/user-language-has-changed.d.ts +0 -1
- package/dist/modules/preferences/invariants/user-language-has-changed.d.ts.map +1 -1
- package/dist/modules/preferences/invariants/user-language-has-changed.js +0 -4
- package/dist/modules/preferences/invariants/user-language-has-changed.js.map +1 -1
- package/dist/modules/system/invariants/feature-flag-enabled.d.ts +0 -1
- package/dist/modules/system/invariants/feature-flag-enabled.d.ts.map +1 -1
- package/dist/modules/system/invariants/feature-flag-enabled.js +0 -4
- package/dist/modules/system/invariants/feature-flag-enabled.js.map +1 -1
- package/dist/prerequisite-verifier-sqlite.adapter.js +1 -1
- package/dist/prerequisite-verifier-sqlite.adapter.js.map +1 -1
- package/dist/prerequisite-verifier-with-cache.adapter.d.ts +2 -0
- package/dist/prerequisite-verifier-with-cache.adapter.d.ts.map +1 -1
- package/dist/prerequisite-verifier-with-cache.adapter.js +8 -5
- package/dist/prerequisite-verifier-with-cache.adapter.js.map +1 -1
- package/dist/rate-limiter.service.d.ts +22 -0
- package/dist/rate-limiter.service.d.ts.map +1 -0
- package/dist/rate-limiter.service.js +31 -0
- package/dist/rate-limiter.service.js.map +1 -0
- package/dist/reactive-config-with-cache.adapter.d.ts +3 -1
- package/dist/reactive-config-with-cache.adapter.d.ts.map +1 -1
- package/dist/reactive-config-with-cache.adapter.js +3 -1
- package/dist/reactive-config-with-cache.adapter.js.map +1 -1
- package/dist/request-context-hono.adapter.d.ts.map +1 -1
- package/dist/request-context-hono.adapter.js +4 -1
- package/dist/request-context-hono.adapter.js.map +1 -1
- package/dist/secure-key-generator-crypto.adapter.js +1 -1
- package/dist/secure-key-generator-crypto.adapter.js.map +1 -1
- package/dist/security-rule-violation-threshold.strategy.d.ts.map +1 -1
- package/dist/security-rule-violation-threshold.strategy.js +5 -3
- package/dist/security-rule-violation-threshold.strategy.js.map +1 -1
- package/dist/shield-rate-limit-hono.strategy.d.ts +2 -2
- package/dist/shield-rate-limit-hono.strategy.d.ts.map +1 -1
- package/dist/shield-rate-limit.strategy.d.ts +2 -2
- package/dist/shield-rate-limit.strategy.d.ts.map +1 -1
- package/dist/shield-rate-limit.strategy.js +8 -2
- package/dist/shield-rate-limit.strategy.js.map +1 -1
- package/dist/shield-webhook-hono.strategy.d.ts +2 -2
- package/dist/shield-webhook-hono.strategy.d.ts.map +1 -1
- package/dist/shield-webhook-hono.strategy.js +7 -3
- package/dist/shield-webhook-hono.strategy.js.map +1 -1
- package/dist/shield-webhook.strategy.d.ts +23 -3
- package/dist/shield-webhook.strategy.d.ts.map +1 -1
- package/dist/shield-webhook.strategy.js +18 -4
- package/dist/shield-webhook.strategy.js.map +1 -1
- package/dist/sitemap-entries-provider-with-cache.adapter.d.ts +2 -0
- package/dist/sitemap-entries-provider-with-cache.adapter.d.ts.map +1 -1
- package/dist/sitemap-entries-provider-with-cache.adapter.js +8 -5
- package/dist/sitemap-entries-provider-with-cache.adapter.js.map +1 -1
- package/dist/sitemap-entry.vo.d.ts +11 -10
- package/dist/sitemap-entry.vo.d.ts.map +1 -1
- package/dist/sitemap-entry.vo.js +19 -0
- package/dist/sitemap-entry.vo.js.map +1 -1
- package/dist/smtp-port.vo.d.ts.map +1 -1
- package/dist/smtp-port.vo.js +1 -0
- package/dist/smtp-port.vo.js.map +1 -1
- package/dist/sse-registry-with-logger.adapter.d.ts +2 -0
- package/dist/sse-registry-with-logger.adapter.d.ts.map +1 -1
- package/dist/sse-registry-with-logger.adapter.js +80 -22
- package/dist/sse-registry-with-logger.adapter.js.map +1 -1
- package/dist/translations-provider-with-cache.adapter.d.ts +21 -0
- package/dist/translations-provider-with-cache.adapter.d.ts.map +1 -0
- package/dist/translations-provider-with-cache.adapter.js +22 -0
- package/dist/translations-provider-with-cache.adapter.js.map +1 -0
- package/dist/uuid.vo.js +1 -0
- package/dist/uuid.vo.js.map +1 -1
- package/dist/webhook-id-extractor-header.strategy.d.ts +9 -0
- package/dist/webhook-id-extractor-header.strategy.d.ts.map +1 -0
- package/dist/webhook-id-extractor-header.strategy.js +13 -0
- package/dist/webhook-id-extractor-header.strategy.js.map +1 -0
- package/dist/webhook-id-extractor-json-field.strategy.d.ts +9 -0
- package/dist/webhook-id-extractor-json-field.strategy.d.ts.map +1 -0
- package/dist/webhook-id-extractor-json-field.strategy.js +14 -0
- package/dist/webhook-id-extractor-json-field.strategy.js.map +1 -0
- package/dist/webhook-id-extractor.strategy.d.ts +6 -0
- package/dist/webhook-id-extractor.strategy.d.ts.map +1 -0
- package/dist/webhook-id-extractor.strategy.js +2 -0
- package/dist/webhook-id-extractor.strategy.js.map +1 -0
- package/dist/webhook-id.vo.d.ts +9 -0
- package/dist/webhook-id.vo.d.ts.map +1 -0
- package/dist/webhook-id.vo.js +10 -0
- package/dist/webhook-id.vo.js.map +1 -0
- package/dist/{webhook-signature-extractor-header-exact.strategy.d.ts → webhook-signature-extractor-header.strategy.d.ts} +2 -2
- package/dist/webhook-signature-extractor-header.strategy.d.ts.map +1 -0
- package/dist/{webhook-signature-extractor-header-exact.strategy.js → webhook-signature-extractor-header.strategy.js} +2 -2
- package/dist/webhook-signature-extractor-header.strategy.js.map +1 -0
- package/package.json +12 -18
- package/readme.md +19 -6
- package/src/alert-channel-composite.adapter.ts +1 -0
- package/src/api-version.middleware.ts +6 -2
- package/src/cache-codec-identity.strategy.ts +12 -0
- package/src/cache-codec-sitemap-entries.strategy.ts +17 -0
- package/src/cache-codec.strategy.ts +6 -0
- package/src/cache-repository-node-cache.adapter.ts +9 -5
- package/src/cache-repository-noop.adapter.ts +3 -2
- package/src/cache-repository-redis.adapter.ts +10 -7
- package/src/cache-repository.port.ts +3 -2
- package/src/cache-resolver-noop.strategy.ts +11 -0
- package/src/cache-resolver-read-through.strategy.ts +27 -0
- package/src/cache-resolver.strategy.ts +2 -11
- package/src/cache-value.vo.ts +9 -0
- package/src/correlation-id.vo.ts +1 -0
- package/src/cron-scheduler-with-logger.adapter.ts +35 -9
- package/src/cron-task-handler-with-logger.strategy.ts +1 -1
- package/src/crypto-key-provider-with-memo.adapter.ts +21 -0
- package/src/environment-loader-process-safe.adapter.ts +12 -4
- package/src/event-store-with-logger.adapter.ts +97 -27
- package/src/event-stream.vo.ts +1 -0
- package/src/file-draft-tar-gz.service.ts +23 -0
- package/src/file-reader-json-with-cache.adapter.ts +7 -2
- package/src/file-reader-text-with-cache.adapter.ts +8 -1
- package/src/healthcheck-hono.handler.ts +2 -2
- package/src/healthcheck.handler.ts +2 -0
- package/src/http-logger-hono.middleware.ts +0 -3
- package/src/http-logger.middleware.ts +0 -2
- package/src/idempotency-store-cache.adapter.ts +19 -0
- package/src/idempotency-store-noop.adapter.ts +8 -0
- package/src/idempotency-store-with-logger.adapter.ts +59 -0
- package/src/idempotency-store.port.ts +5 -0
- package/src/index.ts +19 -6
- package/src/job-queue-with-logger.adapter.ts +156 -43
- package/src/logger.port.ts +0 -1
- package/src/message-bus-with-logger.adapter.ts +37 -10
- package/src/modules/preferences/invariants/user-language-has-changed.ts +1 -6
- package/src/modules/system/invariants/feature-flag-enabled.ts +1 -6
- package/src/prerequisite-verifier-sqlite.adapter.ts +1 -1
- package/src/prerequisite-verifier-with-cache.adapter.ts +13 -7
- package/src/rate-limiter.service.ts +45 -0
- package/src/reactive-config-with-cache.adapter.ts +8 -2
- package/src/request-context-hono.adapter.ts +5 -1
- package/src/security-rule-violation-threshold.strategy.ts +5 -6
- package/src/shield-rate-limit-hono.strategy.ts +2 -2
- package/src/shield-rate-limit.strategy.ts +14 -7
- package/src/shield-webhook-hono.strategy.ts +11 -3
- package/src/shield-webhook.strategy.ts +41 -6
- package/src/sitemap-entries-provider-with-cache.adapter.ts +13 -7
- package/src/sitemap-entry.vo.ts +23 -11
- package/src/smtp-port.vo.ts +1 -0
- package/src/sse-registry-with-logger.adapter.ts +94 -25
- package/src/translations-provider-with-cache.adapter.ts +38 -0
- package/src/uuid.vo.ts +1 -1
- package/src/webhook-id-extractor-header.strategy.ts +14 -0
- package/src/webhook-id-extractor-json-field.strategy.ts +16 -0
- package/src/webhook-id-extractor.strategy.ts +6 -0
- package/src/webhook-id.vo.ts +17 -0
- package/src/{webhook-signature-extractor-header-exact.strategy.ts → webhook-signature-extractor-header.strategy.ts} +1 -1
- package/dist/cache-resolver-simple.strategy.d.ts +0 -18
- package/dist/cache-resolver-simple.strategy.d.ts.map +0 -1
- package/dist/cache-resolver-simple.strategy.js +0 -23
- package/dist/cache-resolver-simple.strategy.js.map +0 -1
- package/dist/cache-response-hono.middleware.d.ts +0 -9
- package/dist/cache-response-hono.middleware.d.ts.map +0 -1
- package/dist/cache-response-hono.middleware.js +0 -34
- package/dist/cache-response-hono.middleware.js.map +0 -1
- package/dist/cache-response.middleware.d.ts +0 -28
- package/dist/cache-response.middleware.d.ts.map +0 -1
- package/dist/cache-response.middleware.js +0 -20
- package/dist/cache-response.middleware.js.map +0 -1
- package/dist/crypto-key-provider-with-cache.adapter.d.ts +0 -19
- package/dist/crypto-key-provider-with-cache.adapter.d.ts.map +0 -1
- package/dist/crypto-key-provider-with-cache.adapter.js +0 -19
- package/dist/crypto-key-provider-with-cache.adapter.js.map +0 -1
- package/dist/file-draft-zip.service.d.ts +0 -14
- package/dist/file-draft-zip.service.d.ts.map +0 -1
- package/dist/file-draft-zip.service.js +0 -41
- package/dist/file-draft-zip.service.js.map +0 -1
- package/dist/webhook-signature-extractor-header-exact.strategy.d.ts.map +0 -1
- package/dist/webhook-signature-extractor-header-exact.strategy.js.map +0 -1
- package/src/cache-resolver-simple.strategy.ts +0 -33
- package/src/cache-response-hono.middleware.ts +0 -53
- package/src/cache-response.middleware.ts +0 -47
- package/src/crypto-key-provider-with-cache.adapter.ts +0 -30
- package/src/file-draft-zip.service.ts +0 -56
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import type * as tools from "@bgord/tools";
|
|
2
|
+
import type { ClockPort } from "./clock.port";
|
|
2
3
|
import { CorrelationStorage } from "./correlation-storage.service";
|
|
3
4
|
import type { GenericJob } from "./job.types";
|
|
4
5
|
import type { JobQueuePort } from "./job-queue.port";
|
|
5
6
|
import type { JobHandler } from "./job-registry.port";
|
|
6
7
|
import type { JobRetryPolicyStrategy } from "./job-retry-policy.strategy";
|
|
7
8
|
import type { LoggerPort } from "./logger.port";
|
|
9
|
+
import { Stopwatch } from "./stopwatch.service";
|
|
8
10
|
|
|
9
|
-
type Dependencies<Job extends GenericJob> = {
|
|
11
|
+
type Dependencies<Job extends GenericJob> = {
|
|
12
|
+
inner: JobQueuePort<Job>;
|
|
13
|
+
Logger: LoggerPort;
|
|
14
|
+
Clock: ClockPort;
|
|
15
|
+
};
|
|
10
16
|
|
|
11
17
|
export class JobQueueWithLoggerAdapter<Job extends GenericJob> implements JobQueuePort<Job> {
|
|
12
18
|
private readonly base = { component: "infra", operation: "job_queue" };
|
|
@@ -14,49 +20,135 @@ export class JobQueueWithLoggerAdapter<Job extends GenericJob> implements JobQue
|
|
|
14
20
|
constructor(private readonly deps: Dependencies<Job>) {}
|
|
15
21
|
|
|
16
22
|
async enqueue<EnqueuedJob extends Job>(job: EnqueuedJob, delay?: tools.Duration): Promise<EnqueuedJob> {
|
|
17
|
-
this.deps
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
const duration = new Stopwatch(this.deps);
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
this.deps.Logger.info({
|
|
27
|
+
message: "Job queue enqueue attempt",
|
|
28
|
+
correlationId: CorrelationStorage.get(),
|
|
29
|
+
metadata: { job, delay },
|
|
30
|
+
...this.base,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const result = await this.deps.inner.enqueue(job, delay);
|
|
34
|
+
|
|
35
|
+
this.deps.Logger.info({
|
|
36
|
+
message: "Job queue enqueue success",
|
|
37
|
+
correlationId: CorrelationStorage.get(),
|
|
38
|
+
metadata: { job, delay, duration: duration.stop() },
|
|
39
|
+
...this.base,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return result;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
this.deps.Logger.error({
|
|
45
|
+
message: "Job queue enqueue error",
|
|
46
|
+
correlationId: CorrelationStorage.get(),
|
|
47
|
+
error,
|
|
48
|
+
metadata: { job, delay, duration: duration.stop() },
|
|
49
|
+
...this.base,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
25
54
|
}
|
|
26
55
|
|
|
27
56
|
async claim(limit: tools.IntegerPositiveType): Promise<ReadonlyArray<Job>> {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
57
|
+
const duration = new Stopwatch(this.deps);
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
this.deps.Logger.info({
|
|
61
|
+
message: "Job queue claim attempt",
|
|
62
|
+
correlationId: CorrelationStorage.get(),
|
|
63
|
+
metadata: { limit },
|
|
64
|
+
...this.base,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const jobs = await this.deps.inner.claim(limit);
|
|
68
|
+
|
|
69
|
+
this.deps.Logger.info({
|
|
70
|
+
message: "Job queue claim success",
|
|
71
|
+
correlationId: CorrelationStorage.get(),
|
|
72
|
+
metadata: { limit, count: jobs.length, jobs, duration: duration.stop() },
|
|
73
|
+
...this.base,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return jobs;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
this.deps.Logger.error({
|
|
79
|
+
message: "Job queue claim error",
|
|
80
|
+
correlationId: CorrelationStorage.get(),
|
|
81
|
+
error,
|
|
82
|
+
metadata: { limit, duration: duration.stop() },
|
|
83
|
+
...this.base,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
38
88
|
}
|
|
39
89
|
|
|
40
90
|
async complete(id: GenericJob["id"]): Promise<void> {
|
|
41
|
-
this.deps
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
91
|
+
const duration = new Stopwatch(this.deps);
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
this.deps.Logger.info({
|
|
95
|
+
message: "Job queue complete attempt",
|
|
96
|
+
correlationId: CorrelationStorage.get(),
|
|
97
|
+
metadata: { id },
|
|
98
|
+
...this.base,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await this.deps.inner.complete(id);
|
|
102
|
+
|
|
103
|
+
this.deps.Logger.info({
|
|
104
|
+
message: "Job queue complete success",
|
|
105
|
+
correlationId: CorrelationStorage.get(),
|
|
106
|
+
metadata: { id, duration: duration.stop() },
|
|
107
|
+
...this.base,
|
|
108
|
+
});
|
|
109
|
+
} catch (error) {
|
|
110
|
+
this.deps.Logger.error({
|
|
111
|
+
message: "Job queue complete error",
|
|
112
|
+
correlationId: CorrelationStorage.get(),
|
|
113
|
+
error,
|
|
114
|
+
metadata: { id, duration: duration.stop() },
|
|
115
|
+
...this.base,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
49
120
|
}
|
|
50
121
|
|
|
51
122
|
async fail(id: GenericJob["id"]): Promise<void> {
|
|
52
|
-
this.deps
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
123
|
+
const duration = new Stopwatch(this.deps);
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
this.deps.Logger.warn({
|
|
127
|
+
message: "Job queue fail attempt",
|
|
128
|
+
correlationId: CorrelationStorage.get(),
|
|
129
|
+
metadata: { id },
|
|
130
|
+
...this.base,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
await this.deps.inner.fail(id);
|
|
134
|
+
|
|
135
|
+
this.deps.Logger.warn({
|
|
136
|
+
message: "Job queue fail success",
|
|
137
|
+
correlationId: CorrelationStorage.get(),
|
|
138
|
+
metadata: { id, duration: duration.stop() },
|
|
139
|
+
...this.base,
|
|
140
|
+
});
|
|
141
|
+
} catch (error) {
|
|
142
|
+
this.deps.Logger.error({
|
|
143
|
+
message: "Job queue fail error",
|
|
144
|
+
correlationId: CorrelationStorage.get(),
|
|
145
|
+
error,
|
|
146
|
+
metadata: { id, duration: duration.stop() },
|
|
147
|
+
...this.base,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
60
152
|
}
|
|
61
153
|
|
|
62
154
|
async requeue(
|
|
@@ -64,14 +156,35 @@ export class JobQueueWithLoggerAdapter<Job extends GenericJob> implements JobQue
|
|
|
64
156
|
revision: GenericJob["revision"],
|
|
65
157
|
delay: tools.Duration,
|
|
66
158
|
): Promise<void> {
|
|
67
|
-
this.deps
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
159
|
+
const duration = new Stopwatch(this.deps);
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
this.deps.Logger.warn({
|
|
163
|
+
message: "Job queue requeue attempt",
|
|
164
|
+
correlationId: CorrelationStorage.get(),
|
|
165
|
+
metadata: { id, revision, delay },
|
|
166
|
+
...this.base,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await this.deps.inner.requeue(id, revision, delay);
|
|
170
|
+
|
|
171
|
+
this.deps.Logger.warn({
|
|
172
|
+
message: "Job queue requeue success",
|
|
173
|
+
correlationId: CorrelationStorage.get(),
|
|
174
|
+
metadata: { id, revision, delay, duration: duration.stop() },
|
|
175
|
+
...this.base,
|
|
176
|
+
});
|
|
177
|
+
} catch (error) {
|
|
178
|
+
this.deps.Logger.error({
|
|
179
|
+
message: "Job queue requeue error",
|
|
180
|
+
correlationId: CorrelationStorage.get(),
|
|
181
|
+
error,
|
|
182
|
+
metadata: { id, revision, delay, duration: duration.stop() },
|
|
183
|
+
...this.base,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
75
188
|
}
|
|
76
189
|
|
|
77
190
|
getRetryPolicy(name: GenericJob["name"]): JobRetryPolicyStrategy {
|
package/src/logger.port.ts
CHANGED
|
@@ -1,24 +1,51 @@
|
|
|
1
|
+
import type { ClockPort } from "./clock.port";
|
|
1
2
|
import { CorrelationStorage } from "./correlation-storage.service";
|
|
2
3
|
import type { LoggerPort } from "./logger.port";
|
|
3
4
|
import type { Message, ToMessageMap } from "./message.types";
|
|
4
5
|
import type { MessageBusPort } from "./message-bus.port";
|
|
6
|
+
import { Stopwatch } from "./stopwatch.service";
|
|
5
7
|
|
|
6
|
-
type Dependencies<Messages extends Message> = {
|
|
8
|
+
type Dependencies<Messages extends Message> = {
|
|
9
|
+
inner: MessageBusPort<Messages>;
|
|
10
|
+
Logger: LoggerPort;
|
|
11
|
+
Clock: ClockPort;
|
|
12
|
+
};
|
|
7
13
|
|
|
8
14
|
export class MessageBusWithLoggerAdapter<Messages extends Message> implements MessageBusPort<Messages> {
|
|
9
|
-
private readonly base = { component: "infra", operation: "
|
|
15
|
+
private readonly base = { component: "infra", operation: "message_bus" };
|
|
10
16
|
|
|
11
17
|
constructor(private readonly deps: Dependencies<Messages>) {}
|
|
12
18
|
|
|
13
19
|
async emit<M extends Messages>(message: M): Promise<void> {
|
|
14
|
-
this.deps
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const duration = new Stopwatch(this.deps);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
this.deps.Logger.info({
|
|
24
|
+
message: "Message bus emit attempt",
|
|
25
|
+
correlationId: CorrelationStorage.get(),
|
|
26
|
+
metadata: { message },
|
|
27
|
+
...this.base,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
await this.deps.inner.emit(message);
|
|
31
|
+
|
|
32
|
+
this.deps.Logger.info({
|
|
33
|
+
message: "Message bus emit success",
|
|
34
|
+
correlationId: CorrelationStorage.get(),
|
|
35
|
+
metadata: { message, duration: duration.stop() },
|
|
36
|
+
...this.base,
|
|
37
|
+
});
|
|
38
|
+
} catch (error) {
|
|
39
|
+
this.deps.Logger.error({
|
|
40
|
+
message: "Message bus emit error",
|
|
41
|
+
correlationId: CorrelationStorage.get(),
|
|
42
|
+
error,
|
|
43
|
+
metadata: { message, duration: duration.stop() },
|
|
44
|
+
...this.base,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
22
49
|
}
|
|
23
50
|
|
|
24
51
|
on<MessageName extends keyof ToMessageMap<Messages>>(
|
|
@@ -3,12 +3,7 @@ import { Invariant, InvariantFailureKind } from "../../../invariant.service";
|
|
|
3
3
|
|
|
4
4
|
type Config = { current?: tools.LanguageType | null; candidate: tools.LanguageType };
|
|
5
5
|
|
|
6
|
-
class UserLanguageHasChangedError extends Error {
|
|
7
|
-
constructor() {
|
|
8
|
-
super();
|
|
9
|
-
Object.setPrototypeOf(this, UserLanguageHasChangedError.prototype);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
6
|
+
class UserLanguageHasChangedError extends Error {}
|
|
12
7
|
|
|
13
8
|
class UserLanguageHasChangedFactory extends Invariant<Config> {
|
|
14
9
|
passes(config: Config) {
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import * as tools from "@bgord/tools";
|
|
2
2
|
import { Invariant, InvariantFailureKind } from "../../../invariant.service";
|
|
3
3
|
|
|
4
|
-
class FeatureFlagEnabledError extends Error {
|
|
5
|
-
constructor() {
|
|
6
|
-
super();
|
|
7
|
-
Object.setPrototypeOf(this, FeatureFlagEnabledError.prototype);
|
|
8
|
-
}
|
|
9
|
-
}
|
|
4
|
+
class FeatureFlagEnabledError extends Error {}
|
|
10
5
|
|
|
11
6
|
type FeatureFlagEnabledConfigType = { flag: tools.FeatureFlagValueType };
|
|
12
7
|
|
|
@@ -17,7 +17,7 @@ export class PrerequisiteVerifierSQLiteAdapter implements PrerequisiteVerifierPo
|
|
|
17
17
|
| { integrity_check?: string }
|
|
18
18
|
| undefined;
|
|
19
19
|
|
|
20
|
-
if (
|
|
20
|
+
if (integrity?.integrity_check?.toLowerCase() !== "ok") {
|
|
21
21
|
return PrerequisiteVerification.failure("Integrity check failed");
|
|
22
22
|
}
|
|
23
23
|
return PrerequisiteVerification.success;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CacheCodecIdentityStrategy } from "./cache-codec-identity.strategy";
|
|
1
2
|
import type { CacheResolverStrategy } from "./cache-resolver.strategy";
|
|
2
3
|
import type { HashContentStrategy } from "./hash-content.strategy";
|
|
3
4
|
import type { PrerequisiteVerificationResult, PrerequisiteVerifierPort } from "./prerequisite-verifier.port";
|
|
@@ -8,13 +9,14 @@ type Dependencies = { CacheResolver: CacheResolverStrategy; HashContent: HashCon
|
|
|
8
9
|
type Config = { id: string; inner: PrerequisiteVerifierPort };
|
|
9
10
|
|
|
10
11
|
export class PrerequisiteVerifierWithCacheAdapter implements PrerequisiteVerifierPort {
|
|
12
|
+
private readonly codec = new CacheCodecIdentityStrategy<PrerequisiteVerificationResult>();
|
|
13
|
+
private readonly resolver: SubjectApplicationResolver;
|
|
14
|
+
|
|
11
15
|
constructor(
|
|
12
16
|
private readonly config: Config,
|
|
13
17
|
private readonly deps: Dependencies,
|
|
14
|
-
) {
|
|
15
|
-
|
|
16
|
-
async verify(): Promise<PrerequisiteVerificationResult> {
|
|
17
|
-
const resolver = new SubjectApplicationResolver(
|
|
18
|
+
) {
|
|
19
|
+
this.resolver = new SubjectApplicationResolver(
|
|
18
20
|
[
|
|
19
21
|
new SubjectSegmentFixedStrategy("prerequisite_verifier"),
|
|
20
22
|
new SubjectSegmentFixedStrategy(this.kind),
|
|
@@ -22,11 +24,15 @@ export class PrerequisiteVerifierWithCacheAdapter implements PrerequisiteVerifie
|
|
|
22
24
|
],
|
|
23
25
|
this.deps,
|
|
24
26
|
);
|
|
27
|
+
}
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
async verify(): Promise<PrerequisiteVerificationResult> {
|
|
30
|
+
const subject = await this.resolver.resolve();
|
|
27
31
|
|
|
28
|
-
return this.deps.CacheResolver.resolve<PrerequisiteVerificationResult>(
|
|
29
|
-
|
|
32
|
+
return this.deps.CacheResolver.resolve<PrerequisiteVerificationResult>(
|
|
33
|
+
subject.hex,
|
|
34
|
+
async () => this.config.inner.verify(),
|
|
35
|
+
this.codec,
|
|
30
36
|
);
|
|
31
37
|
}
|
|
32
38
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as tools from "@bgord/tools";
|
|
2
|
+
|
|
3
|
+
type RateLimiterResultSuccessType = { allowed: true };
|
|
4
|
+
type RateLimiterResultErrorType = { allowed: false; remaining: tools.Duration };
|
|
5
|
+
type RateLimiterResultType = RateLimiterResultSuccessType | RateLimiterResultErrorType;
|
|
6
|
+
|
|
7
|
+
export type RateLimiterStateType = { lastInvocation: tools.TimestampValueType | null };
|
|
8
|
+
|
|
9
|
+
export class RateLimiter {
|
|
10
|
+
private lastInvocation: tools.Timestamp | null = null;
|
|
11
|
+
|
|
12
|
+
constructor(private readonly duration: tools.Duration) {}
|
|
13
|
+
|
|
14
|
+
static fromState(duration: tools.Duration, state: RateLimiterStateType): RateLimiter {
|
|
15
|
+
const limiter = new RateLimiter(duration);
|
|
16
|
+
|
|
17
|
+
if (state.lastInvocation !== null) {
|
|
18
|
+
limiter.lastInvocation = tools.Timestamp.fromNumber(state.lastInvocation);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return limiter;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
verify(now: tools.Timestamp): RateLimiterResultType {
|
|
25
|
+
if (this.lastInvocation === null) {
|
|
26
|
+
this.lastInvocation = now;
|
|
27
|
+
|
|
28
|
+
return { allowed: true };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const nextAllowedTimestamp = this.lastInvocation.add(this.duration);
|
|
32
|
+
|
|
33
|
+
if (nextAllowedTimestamp.isBeforeOrEqual(now)) {
|
|
34
|
+
this.lastInvocation = now;
|
|
35
|
+
|
|
36
|
+
return { allowed: true };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { allowed: false, remaining: nextAllowedTimestamp.difference(now) };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
toJSON(): RateLimiterStateType {
|
|
43
|
+
return { lastInvocation: this.lastInvocation === null ? null : this.lastInvocation.toJSON() };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import { CacheCodecIdentityStrategy } from "./cache-codec-identity.strategy";
|
|
1
2
|
import type { CacheResolverStrategy } from "./cache-resolver.strategy";
|
|
3
|
+
import type { CacheValueType } from "./cache-value.vo";
|
|
2
4
|
import type { HashContentStrategy } from "./hash-content.strategy";
|
|
3
5
|
import type { ReactiveConfigPort } from "./reactive-config.port";
|
|
4
6
|
|
|
5
7
|
type Dependencies = { CacheResolver: CacheResolverStrategy; HashContent: HashContentStrategy };
|
|
6
8
|
|
|
7
|
-
export class ReactiveConfigWithCacheAdapter<T extends object
|
|
9
|
+
export class ReactiveConfigWithCacheAdapter<T extends object & CacheValueType>
|
|
10
|
+
implements ReactiveConfigPort<T>
|
|
11
|
+
{
|
|
12
|
+
private readonly codec = new CacheCodecIdentityStrategy<T>();
|
|
13
|
+
|
|
8
14
|
constructor(
|
|
9
15
|
private readonly inner: ReactiveConfigPort<T>,
|
|
10
16
|
private readonly subject: string,
|
|
@@ -14,6 +20,6 @@ export class ReactiveConfigWithCacheAdapter<T extends object> implements Reactiv
|
|
|
14
20
|
async get(): Promise<Readonly<T>> {
|
|
15
21
|
const key = await this.deps.HashContent.hash(this.subject);
|
|
16
22
|
|
|
17
|
-
return Object.freeze(await this.deps.CacheResolver.resolve(key, () => this.inner.get()));
|
|
23
|
+
return Object.freeze(await this.deps.CacheResolver.resolve<T>(key, () => this.inner.get(), this.codec));
|
|
18
24
|
}
|
|
19
25
|
}
|
|
@@ -27,7 +27,11 @@ export class RequestContextHonoAdapter implements RequestContext {
|
|
|
27
27
|
cookie: (name) => getCookie(context)[name],
|
|
28
28
|
json: async () => {
|
|
29
29
|
try {
|
|
30
|
-
|
|
30
|
+
const body = await context.req.raw.clone().json();
|
|
31
|
+
|
|
32
|
+
if (body === null || typeof body !== "object") return {};
|
|
33
|
+
|
|
34
|
+
return body;
|
|
31
35
|
} catch {
|
|
32
36
|
return {};
|
|
33
37
|
}
|
|
@@ -32,14 +32,13 @@ export class SecurityRuleViolationThresholdStrategy implements SecurityRuleStrat
|
|
|
32
32
|
if (!violated) return false;
|
|
33
33
|
|
|
34
34
|
try {
|
|
35
|
-
const
|
|
35
|
+
const cached = await this.deps.CacheRepository.get(subject.hex);
|
|
36
|
+
const count = typeof cached === "number" ? cached : 0;
|
|
37
|
+
const next = tools.Int.nonNegative(count + 1);
|
|
36
38
|
|
|
37
|
-
await this.deps.CacheRepository.set
|
|
38
|
-
subject.hex,
|
|
39
|
-
tools.Int.nonNegative(count + 1),
|
|
40
|
-
);
|
|
39
|
+
await this.deps.CacheRepository.set(subject.hex, next);
|
|
41
40
|
|
|
42
|
-
if (
|
|
41
|
+
if (next >= this.config.threshold) return true;
|
|
43
42
|
return false;
|
|
44
43
|
} catch {
|
|
45
44
|
return false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MiddlewareHandler } from "hono";
|
|
2
2
|
import { HTTPException } from "hono/http-exception";
|
|
3
|
-
import type {
|
|
3
|
+
import type { CacheRepositoryPort } from "./cache-repository.port";
|
|
4
4
|
import type { ClockPort } from "./clock.port";
|
|
5
5
|
import type { MiddlewareHonoPort } from "./middleware-hono.port";
|
|
6
6
|
import { RequestContextHonoAdapter } from "./request-context-hono.adapter";
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
ShieldRateLimitStrategyError,
|
|
11
11
|
} from "./shield-rate-limit.strategy";
|
|
12
12
|
|
|
13
|
-
type Dependencies = { Clock: ClockPort;
|
|
13
|
+
type Dependencies = { Clock: ClockPort; CacheRepository: CacheRepositoryPort };
|
|
14
14
|
|
|
15
15
|
export class ShieldRateLimitHonoStrategy implements MiddlewareHonoPort {
|
|
16
16
|
private readonly strategy: ShieldRateLimitStrategy;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import * as tools from "@bgord/tools";
|
|
2
|
-
import type {
|
|
2
|
+
import type { CacheRepositoryPort } from "./cache-repository.port";
|
|
3
3
|
import type { ClockPort } from "./clock.port";
|
|
4
|
+
import { RateLimiter, type RateLimiterStateType } from "./rate-limiter.service";
|
|
4
5
|
import type { RequestContext } from "./request-context.port";
|
|
5
6
|
import type { SubjectRequestResolver } from "./subject-request-resolver.vo";
|
|
6
7
|
|
|
7
8
|
export type ShieldRateLimitConfig = { resolver: SubjectRequestResolver; interval: tools.Duration };
|
|
8
9
|
type ShieldRateLimitResult = { allowed: true } | { allowed: false; retryAfter: tools.Duration };
|
|
9
10
|
|
|
10
|
-
type Dependencies = { Clock: ClockPort;
|
|
11
|
+
type Dependencies = { Clock: ClockPort; CacheRepository: CacheRepositoryPort };
|
|
11
12
|
|
|
12
13
|
export const ShieldRateLimitStrategyError = { Rejected: "shield.rate.limit.rejected" };
|
|
13
14
|
|
|
@@ -22,14 +23,20 @@ export class ShieldRateLimitStrategy {
|
|
|
22
23
|
async evaluate(context: RequestContext): Promise<ShieldRateLimitResult> {
|
|
23
24
|
const subject = await this.config.resolver.resolve(context);
|
|
24
25
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const state = await this.deps.CacheRepository.get(subject.hex);
|
|
27
|
+
|
|
28
|
+
const limiter =
|
|
29
|
+
state === null
|
|
30
|
+
? new RateLimiter(this.config.interval)
|
|
31
|
+
: RateLimiter.fromState(this.config.interval, state as RateLimiterStateType);
|
|
29
32
|
|
|
30
33
|
const decision = limiter.verify(this.deps.Clock.now());
|
|
31
34
|
|
|
32
|
-
if (decision.allowed)
|
|
35
|
+
if (decision.allowed) {
|
|
36
|
+
await this.deps.CacheRepository.set(subject.hex, limiter.toJSON());
|
|
37
|
+
|
|
38
|
+
return { allowed: true };
|
|
39
|
+
}
|
|
33
40
|
|
|
34
41
|
return {
|
|
35
42
|
allowed: false,
|
|
@@ -5,21 +5,29 @@ import { RequestContextHonoAdapter } from "./request-context-hono.adapter";
|
|
|
5
5
|
import {
|
|
6
6
|
ShieldWebhookStrategy,
|
|
7
7
|
type ShieldWebhookStrategyConfig,
|
|
8
|
+
type ShieldWebhookStrategyDependencies,
|
|
8
9
|
ShieldWebhookStrategyError,
|
|
9
10
|
} from "./shield-webhook.strategy";
|
|
10
11
|
|
|
11
12
|
export class ShieldWebhookHonoStrategy implements MiddlewareHonoPort {
|
|
12
13
|
private readonly strategy: ShieldWebhookStrategy;
|
|
13
14
|
|
|
14
|
-
constructor(config: ShieldWebhookStrategyConfig) {
|
|
15
|
-
this.strategy = new ShieldWebhookStrategy(config);
|
|
15
|
+
constructor(config: ShieldWebhookStrategyConfig, deps: ShieldWebhookStrategyDependencies) {
|
|
16
|
+
this.strategy = new ShieldWebhookStrategy(config, deps);
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
handle(): MiddlewareHandler {
|
|
19
20
|
return async (c, next) => {
|
|
20
21
|
const context = new RequestContextHonoAdapter(c);
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
const result = await this.strategy.evaluate(context);
|
|
24
|
+
|
|
25
|
+
if (result.accepted) return next();
|
|
26
|
+
|
|
27
|
+
if (result.reason === "duplicate") {
|
|
28
|
+
return c.json({ message: ShieldWebhookStrategyError.Duplicate }, 200);
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
throw new HTTPException(401, { message: ShieldWebhookStrategyError.Rejected });
|
|
24
32
|
};
|
|
25
33
|
}
|
|
@@ -1,26 +1,61 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HashContentStrategy } from "./hash-content.strategy";
|
|
2
|
+
import type { IdempotencyStorePort } from "./idempotency-store.port";
|
|
3
|
+
import type { HasRequestHeader, HasRequestJson, HasRequestText } from "./request-context.port";
|
|
2
4
|
import type { WebhookBodyBuilderStrategy } from "./webhook-body-builder.strategy";
|
|
5
|
+
import type { WebhookIdExtractorStrategy } from "./webhook-id-extractor.strategy";
|
|
3
6
|
import type { WebhookSignatureExtractorStrategy } from "./webhook-signature-extractor.strategy";
|
|
4
7
|
import type { WebhookVerifierStrategy } from "./webhook-verifier.strategy";
|
|
5
8
|
|
|
6
|
-
export const ShieldWebhookStrategyError = {
|
|
9
|
+
export const ShieldWebhookStrategyError = {
|
|
10
|
+
Rejected: "shield.webhook.rejected",
|
|
11
|
+
Duplicate: "shield.webhook.duplicate",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type ShieldWebhookStrategyResult =
|
|
15
|
+
| { accepted: true }
|
|
16
|
+
| { accepted: false; reason: "rejected" }
|
|
17
|
+
| { accepted: false; reason: "duplicate" };
|
|
7
18
|
|
|
8
19
|
export type ShieldWebhookStrategyConfig = {
|
|
20
|
+
namespace: string;
|
|
9
21
|
WebhookBodyBuilder: WebhookBodyBuilderStrategy;
|
|
22
|
+
WebhookIdExtractor: WebhookIdExtractorStrategy;
|
|
10
23
|
WebhookSignatureExtractor: WebhookSignatureExtractorStrategy;
|
|
11
24
|
WebhookVerifier: WebhookVerifierStrategy;
|
|
12
25
|
};
|
|
13
26
|
|
|
27
|
+
export type ShieldWebhookStrategyDependencies = {
|
|
28
|
+
HashContent: HashContentStrategy;
|
|
29
|
+
IdempotencyStore: IdempotencyStorePort;
|
|
30
|
+
};
|
|
31
|
+
|
|
14
32
|
export class ShieldWebhookStrategy {
|
|
15
|
-
constructor(
|
|
33
|
+
constructor(
|
|
34
|
+
private readonly config: ShieldWebhookStrategyConfig,
|
|
35
|
+
private readonly deps: ShieldWebhookStrategyDependencies,
|
|
36
|
+
) {}
|
|
16
37
|
|
|
17
|
-
async evaluate(
|
|
38
|
+
async evaluate(
|
|
39
|
+
context: HasRequestHeader & HasRequestJson & HasRequestText,
|
|
40
|
+
): Promise<ShieldWebhookStrategyResult> {
|
|
18
41
|
const signature = this.config.WebhookSignatureExtractor.extract(context);
|
|
19
42
|
|
|
20
|
-
if (!signature) return false;
|
|
43
|
+
if (!signature) return { accepted: false, reason: "rejected" };
|
|
21
44
|
|
|
22
45
|
const body = await this.config.WebhookBodyBuilder.build(context);
|
|
23
46
|
|
|
24
|
-
|
|
47
|
+
if (!this.config.WebhookVerifier.verify(body, signature)) return { accepted: false, reason: "rejected" };
|
|
48
|
+
|
|
49
|
+
const id = await this.config.WebhookIdExtractor.extract(context);
|
|
50
|
+
|
|
51
|
+
if (!id) return { accepted: false, reason: "rejected" };
|
|
52
|
+
|
|
53
|
+
const subject = await this.deps.HashContent.hash(JSON.stringify([[this.config.namespace, id]]));
|
|
54
|
+
|
|
55
|
+
const claimed = await this.deps.IdempotencyStore.claim(subject);
|
|
56
|
+
|
|
57
|
+
if (!claimed) return { accepted: false, reason: "duplicate" };
|
|
58
|
+
|
|
59
|
+
return { accepted: true };
|
|
25
60
|
}
|
|
26
61
|
}
|