@satorio/machinery 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +71 -0
- package/dictionary/all.json +4656 -0
- package/dictionary/all.toon +639 -0
- package/dictionary/core.json +221 -0
- package/dictionary/core.toon +33 -0
- package/dictionary/graphql.json +212 -0
- package/dictionary/graphql.toon +32 -0
- package/dictionary/http.json +113 -0
- package/dictionary/http.toon +21 -0
- package/dictionary/identifiers.json +266 -0
- package/dictionary/identifiers.toon +38 -0
- package/dictionary/index.json +96 -0
- package/dictionary/index.toon +18 -0
- package/dictionary/integration.json +131 -0
- package/dictionary/integration.toon +23 -0
- package/dictionary/money.json +383 -0
- package/dictionary/money.toon +51 -0
- package/dictionary/ports.json +392 -0
- package/dictionary/ports.toon +52 -0
- package/dictionary/postgres.json +473 -0
- package/dictionary/postgres.toon +61 -0
- package/dictionary/publishing.json +131 -0
- package/dictionary/publishing.toon +23 -0
- package/dictionary/queue.json +365 -0
- package/dictionary/queue.toon +49 -0
- package/dictionary/reference/api.mdx +843 -0
- package/dictionary/reference/uk/api.mdx +844 -0
- package/dictionary/storage.json +194 -0
- package/dictionary/storage.toon +30 -0
- package/dictionary/testing.json +707 -0
- package/dictionary/testing.toon +87 -0
- package/dictionary/time.json +311 -0
- package/dictionary/time.toon +43 -0
- package/dictionary/vitest.json +356 -0
- package/dictionary/vitest.toon +48 -0
- package/dictionary/workflow.json +185 -0
- package/dictionary/workflow.toon +29 -0
- package/dist/bin/command.d.ts +45 -0
- package/dist/bin/command.js +113 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +11 -0
- package/dist/core/bigint-json.d.ts +6 -0
- package/dist/core/bigint-json.js +6 -0
- package/dist/core/closed-set.d.ts +14 -0
- package/dist/core/closed-set.js +8 -0
- package/dist/core/domain-error.d.ts +8 -0
- package/dist/core/domain-error.js +11 -0
- package/dist/core/environment.d.ts +124 -0
- package/dist/core/environment.js +115 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +13 -0
- package/dist/core/ulid.d.ts +27 -0
- package/dist/core/ulid.js +27 -0
- package/dist/graphql/builder.d.ts +36 -0
- package/dist/graphql/builder.js +35 -0
- package/dist/graphql/context.d.ts +36 -0
- package/dist/graphql/context.js +69 -0
- package/dist/graphql/failure-classification.d.ts +11 -0
- package/dist/graphql/failure-classification.js +1 -0
- package/dist/graphql/failures.d.ts +62 -0
- package/dist/graphql/failures.js +30 -0
- package/dist/graphql/graphiql.d.ts +45 -0
- package/dist/graphql/graphiql.js +56 -0
- package/dist/graphql/index.d.ts +15 -0
- package/dist/graphql/index.js +14 -0
- package/dist/graphql/layer.d.ts +15 -0
- package/dist/graphql/layer.js +23 -0
- package/dist/graphql/request-handler.d.ts +26 -0
- package/dist/graphql/request-handler.js +33 -0
- package/dist/graphql/server.d.ts +19 -0
- package/dist/graphql/server.js +16 -0
- package/dist/graphql/settings.d.ts +55 -0
- package/dist/graphql/settings.js +82 -0
- package/dist/http/health.d.ts +70 -0
- package/dist/http/health.js +60 -0
- package/dist/http/index.d.ts +5 -0
- package/dist/http/index.js +5 -0
- package/dist/identifiers/client-code.d.ts +37 -0
- package/dist/identifiers/client-code.js +41 -0
- package/dist/identifiers/creditor-reference.d.ts +38 -0
- package/dist/identifiers/creditor-reference.js +32 -0
- package/dist/identifiers/damm.d.ts +22 -0
- package/dist/identifiers/damm.js +50 -0
- package/dist/identifiers/document-number.d.ts +68 -0
- package/dist/identifiers/document-number.js +73 -0
- package/dist/identifiers/document-numbers.d.ts +27 -0
- package/dist/identifiers/document-numbers.js +16 -0
- package/dist/identifiers/failures.d.ts +46 -0
- package/dist/identifiers/failures.js +48 -0
- package/dist/identifiers/index.d.ts +14 -0
- package/dist/identifiers/index.js +13 -0
- package/dist/identifiers/next-document-number.d.ts +26 -0
- package/dist/identifiers/next-document-number.js +31 -0
- package/dist/identifiers/ulid.d.ts +28 -0
- package/dist/identifiers/ulid.js +71 -0
- package/dist/integration/external-http-client.d.ts +22 -0
- package/dist/integration/external-http-client.js +74 -0
- package/dist/integration/index.d.ts +12 -0
- package/dist/integration/index.js +12 -0
- package/dist/integration/layer.d.ts +19 -0
- package/dist/integration/layer.js +30 -0
- package/dist/integration/retry.d.ts +65 -0
- package/dist/integration/retry.js +54 -0
- package/dist/integration/settings.d.ts +73 -0
- package/dist/integration/settings.js +92 -0
- package/dist/money/amount.d.ts +60 -0
- package/dist/money/amount.js +67 -0
- package/dist/money/arithmetic.d.ts +60 -0
- package/dist/money/arithmetic.js +73 -0
- package/dist/money/currency.d.ts +25 -0
- package/dist/money/currency.js +24 -0
- package/dist/money/decimal.d.ts +29 -0
- package/dist/money/decimal.js +39 -0
- package/dist/money/exchange-rate.d.ts +32 -0
- package/dist/money/exchange-rate.js +28 -0
- package/dist/money/failures.d.ts +86 -0
- package/dist/money/failures.js +97 -0
- package/dist/money/index.d.ts +15 -0
- package/dist/money/index.js +15 -0
- package/dist/money/percent.d.ts +35 -0
- package/dist/money/percent.js +50 -0
- package/dist/money/schema.d.ts +25 -0
- package/dist/money/schema.js +41 -0
- package/dist/ports/blob-store.d.ts +42 -0
- package/dist/ports/blob-store.js +13 -0
- package/dist/ports/document-number-counter.d.ts +36 -0
- package/dist/ports/document-number-counter.js +12 -0
- package/dist/ports/event-publisher.d.ts +29 -0
- package/dist/ports/event-publisher.js +13 -0
- package/dist/ports/external-http-client.d.ts +60 -0
- package/dist/ports/external-http-client.js +25 -0
- package/dist/ports/index.d.ts +17 -0
- package/dist/ports/index.js +17 -0
- package/dist/ports/integration-event-outbox.d.ts +44 -0
- package/dist/ports/integration-event-outbox.js +13 -0
- package/dist/ports/port-unavailable.d.ts +21 -0
- package/dist/ports/port-unavailable.js +30 -0
- package/dist/ports/processed-event-registry.d.ts +57 -0
- package/dist/ports/processed-event-registry.js +24 -0
- package/dist/ports/queue-message-producer.d.ts +26 -0
- package/dist/ports/queue-message-producer.js +12 -0
- package/dist/ports/queue-message-source.d.ts +39 -0
- package/dist/ports/queue-message-source.js +13 -0
- package/dist/ports/readiness-probe.d.ts +27 -0
- package/dist/ports/readiness-probe.js +13 -0
- package/dist/ports/transaction-boundary.d.ts +28 -0
- package/dist/ports/transaction-boundary.js +13 -0
- package/dist/postgres/client.d.ts +51 -0
- package/dist/postgres/client.js +81 -0
- package/dist/postgres/document-number-counter.d.ts +14 -0
- package/dist/postgres/document-number-counter.js +27 -0
- package/dist/postgres/failures.d.ts +28 -0
- package/dist/postgres/failures.js +18 -0
- package/dist/postgres/index.d.ts +18 -0
- package/dist/postgres/index.js +18 -0
- package/dist/postgres/integration-event-outbox.d.ts +15 -0
- package/dist/postgres/integration-event-outbox.js +60 -0
- package/dist/postgres/jsonb-value.d.ts +10 -0
- package/dist/postgres/jsonb-value.js +9 -0
- package/dist/postgres/layer.d.ts +57 -0
- package/dist/postgres/layer.js +72 -0
- package/dist/postgres/migrations/advisory-lock.d.ts +47 -0
- package/dist/postgres/migrations/advisory-lock.js +59 -0
- package/dist/postgres/migrations/runner.d.ts +56 -0
- package/dist/postgres/migrations/runner.js +69 -0
- package/dist/postgres/migrations/sql-file-loader.d.ts +59 -0
- package/dist/postgres/migrations/sql-file-loader.js +99 -0
- package/dist/postgres/processed-event-registry.d.ts +13 -0
- package/dist/postgres/processed-event-registry.js +44 -0
- package/dist/postgres/readiness-probe.d.ts +27 -0
- package/dist/postgres/readiness-probe.js +33 -0
- package/dist/postgres/settings.d.ts +134 -0
- package/dist/postgres/settings.js +210 -0
- package/dist/postgres/sql/0001_processed_event.sql +4 -0
- package/dist/postgres/sql/0002_integration_event_outbox.sql +13 -0
- package/dist/postgres/sql/0003_document_number_counter.sql +6 -0
- package/dist/postgres/sql-failure.d.ts +19 -0
- package/dist/postgres/sql-failure.js +22 -0
- package/dist/postgres/store-failure.d.ts +17 -0
- package/dist/postgres/store-failure.js +16 -0
- package/dist/postgres/transaction-boundary.d.ts +13 -0
- package/dist/postgres/transaction-boundary.js +21 -0
- package/dist/publishing/event-publisher.sns.d.ts +66 -0
- package/dist/publishing/event-publisher.sns.js +75 -0
- package/dist/publishing/event-publisher.stdout.d.ts +15 -0
- package/dist/publishing/event-publisher.stdout.js +24 -0
- package/dist/publishing/index.d.ts +10 -0
- package/dist/publishing/index.js +10 -0
- package/dist/publishing/layer.d.ts +25 -0
- package/dist/publishing/layer.js +39 -0
- package/dist/publishing/settings.d.ts +60 -0
- package/dist/publishing/settings.js +85 -0
- package/dist/queue/consumer.d.ts +91 -0
- package/dist/queue/consumer.js +92 -0
- package/dist/queue/envelope.d.ts +39 -0
- package/dist/queue/envelope.js +39 -0
- package/dist/queue/failures.d.ts +33 -0
- package/dist/queue/failures.js +15 -0
- package/dist/queue/index.d.ts +14 -0
- package/dist/queue/index.js +14 -0
- package/dist/queue/layer.d.ts +33 -0
- package/dist/queue/layer.js +58 -0
- package/dist/queue/message-codec.d.ts +43 -0
- package/dist/queue/message-codec.js +33 -0
- package/dist/queue/queue-message-producer.d.ts +61 -0
- package/dist/queue/queue-message-producer.js +52 -0
- package/dist/queue/queue-message-source.d.ts +93 -0
- package/dist/queue/queue-message-source.js +86 -0
- package/dist/queue/readiness.d.ts +78 -0
- package/dist/queue/readiness.js +86 -0
- package/dist/queue/settings.d.ts +106 -0
- package/dist/queue/settings.js +160 -0
- package/dist/storage/blob-store.d.ts +84 -0
- package/dist/storage/blob-store.js +109 -0
- package/dist/storage/index.d.ts +9 -0
- package/dist/storage/index.js +9 -0
- package/dist/storage/layer.d.ts +17 -0
- package/dist/storage/layer.js +30 -0
- package/dist/storage/settings.d.ts +48 -0
- package/dist/storage/settings.js +65 -0
- package/dist/testing/blob-store.d.ts +27 -0
- package/dist/testing/blob-store.js +45 -0
- package/dist/testing/clock.d.ts +29 -0
- package/dist/testing/clock.js +41 -0
- package/dist/testing/dna-perturbator/attention.d.ts +55 -0
- package/dist/testing/dna-perturbator/attention.js +49 -0
- package/dist/testing/dna-perturbator/chains.d.ts +31 -0
- package/dist/testing/dna-perturbator/chains.js +53 -0
- package/dist/testing/dna-perturbator/report.d.ts +12 -0
- package/dist/testing/dna-perturbator/report.js +22 -0
- package/dist/testing/dna-perturbator/verdict.d.ts +26 -0
- package/dist/testing/dna-perturbator/verdict.js +28 -0
- package/dist/testing/dna-perturbator.d.ts +48 -0
- package/dist/testing/dna-perturbator.js +145 -0
- package/dist/testing/document-number-counter.d.ts +29 -0
- package/dist/testing/document-number-counter.js +39 -0
- package/dist/testing/event-publisher.d.ts +28 -0
- package/dist/testing/event-publisher.js +33 -0
- package/dist/testing/external-http-client.d.ts +29 -0
- package/dist/testing/external-http-client.js +49 -0
- package/dist/testing/index.d.ts +24 -0
- package/dist/testing/index.js +24 -0
- package/dist/testing/integration-event-outbox.d.ts +37 -0
- package/dist/testing/integration-event-outbox.js +67 -0
- package/dist/testing/mutation-settings.d.ts +71 -0
- package/dist/testing/mutation-settings.js +57 -0
- package/dist/testing/postgres-rows.d.ts +12 -0
- package/dist/testing/postgres-rows.js +19 -0
- package/dist/testing/processed-event-registry.d.ts +25 -0
- package/dist/testing/processed-event-registry.js +39 -0
- package/dist/testing/queue-message-producer.d.ts +27 -0
- package/dist/testing/queue-message-producer.js +41 -0
- package/dist/testing/queue-message-source.d.ts +54 -0
- package/dist/testing/queue-message-source.js +71 -0
- package/dist/testing/quiet-reporter.d.ts +86 -0
- package/dist/testing/quiet-reporter.js +81 -0
- package/dist/testing/readiness-probe.d.ts +27 -0
- package/dist/testing/readiness-probe.js +33 -0
- package/dist/testing/scenario.d.ts +94 -0
- package/dist/testing/scenario.js +81 -0
- package/dist/testing/transaction-boundary.d.ts +30 -0
- package/dist/testing/transaction-boundary.js +36 -0
- package/dist/testing/typed-failure.d.ts +8 -0
- package/dist/testing/typed-failure.js +15 -0
- package/dist/time/calendar-day.d.ts +83 -0
- package/dist/time/calendar-day.js +112 -0
- package/dist/time/failures.d.ts +51 -0
- package/dist/time/failures.js +56 -0
- package/dist/time/index.d.ts +11 -0
- package/dist/time/index.js +11 -0
- package/dist/time/instant.d.ts +64 -0
- package/dist/time/instant.js +67 -0
- package/dist/time/period.d.ts +45 -0
- package/dist/time/period.js +55 -0
- package/dist/vitest/container-reuse.d.ts +14 -0
- package/dist/vitest/container-reuse.js +15 -0
- package/dist/vitest/dotenv.d.ts +16 -0
- package/dist/vitest/dotenv.js +48 -0
- package/dist/vitest/effect-single-instance.d.ts +36 -0
- package/dist/vitest/effect-single-instance.js +125 -0
- package/dist/vitest/fast-check-single-instance.d.ts +11 -0
- package/dist/vitest/fast-check-single-instance.js +26 -0
- package/dist/vitest/index.d.ts +31 -0
- package/dist/vitest/index.js +30 -0
- package/dist/vitest/localstack-container.d.ts +32 -0
- package/dist/vitest/localstack-container.js +55 -0
- package/dist/vitest/port-contract-tester.d.ts +8 -0
- package/dist/vitest/port-contract-tester.js +1 -0
- package/dist/vitest/port-contracts/blob-store.contract.d.ts +22 -0
- package/dist/vitest/port-contracts/blob-store.contract.js +108 -0
- package/dist/vitest/port-contracts/document-number-counter.contract.d.ts +18 -0
- package/dist/vitest/port-contracts/document-number-counter.contract.js +90 -0
- package/dist/vitest/port-contracts/event-publisher.contract.d.ts +35 -0
- package/dist/vitest/port-contracts/event-publisher.contract.js +67 -0
- package/dist/vitest/port-contracts/external-http-client.contract.d.ts +35 -0
- package/dist/vitest/port-contracts/external-http-client.contract.js +52 -0
- package/dist/vitest/port-contracts/integration-event-outbox.contract.d.ts +23 -0
- package/dist/vitest/port-contracts/integration-event-outbox.contract.js +75 -0
- package/dist/vitest/port-contracts/processed-event-registry.contract.d.ts +18 -0
- package/dist/vitest/port-contracts/processed-event-registry.contract.js +84 -0
- package/dist/vitest/port-contracts/queue-message-producer.contract.d.ts +26 -0
- package/dist/vitest/port-contracts/queue-message-producer.contract.js +48 -0
- package/dist/vitest/port-contracts/queue-message-source.contract.d.ts +29 -0
- package/dist/vitest/port-contracts/queue-message-source.contract.js +71 -0
- package/dist/vitest/port-contracts/readiness-probe.contract.d.ts +18 -0
- package/dist/vitest/port-contracts/readiness-probe.contract.js +41 -0
- package/dist/vitest/port-contracts/transaction-boundary.contract.d.ts +21 -0
- package/dist/vitest/port-contracts/transaction-boundary.contract.js +64 -0
- package/dist/vitest/postgres-container.d.ts +57 -0
- package/dist/vitest/postgres-container.js +118 -0
- package/dist/vitest/reader-port-contract.d.ts +41 -0
- package/dist/vitest/reader-port-contract.js +43 -0
- package/dist/vitest/repo-source-files.d.ts +37 -0
- package/dist/vitest/repo-source-files.js +61 -0
- package/dist/vitest/reset-tables.d.ts +18 -0
- package/dist/vitest/reset-tables.js +30 -0
- package/dist/vitest/settings-contract.d.ts +38 -0
- package/dist/vitest/settings-contract.js +77 -0
- package/dist/vitest/stub-server.d.ts +43 -0
- package/dist/vitest/stub-server.js +60 -0
- package/dist/vitest/term-registry.json +730 -0
- package/dist/vitest/testclock-fork-isolated-files.d.ts +13 -0
- package/dist/vitest/testclock-fork-isolated-files.js +18 -0
- package/dist/vitest/testclock-fork-isolation.d.ts +77 -0
- package/dist/vitest/testclock-fork-isolation.js +188 -0
- package/dist/workflow/claim-once.d.ts +16 -0
- package/dist/workflow/claim-once.js +9 -0
- package/dist/workflow/failures.d.ts +47 -0
- package/dist/workflow/failures.js +22 -0
- package/dist/workflow/index.d.ts +12 -0
- package/dist/workflow/index.js +12 -0
- package/dist/workflow/on-claim.d.ts +17 -0
- package/dist/workflow/on-claim.js +8 -0
- package/dist/workflow/publish-pending-events.d.ts +31 -0
- package/dist/workflow/publish-pending-events.js +31 -0
- package/dist/workflow/transactionally.d.ts +8 -0
- package/dist/workflow/transactionally.js +8 -0
- package/dist/workflow/versioned-payload.d.ts +35 -0
- package/dist/workflow/versioned-payload.js +43 -0
- package/package.json +309 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry to the publishing mechanism: the event publisher, bound to a
|
|
3
|
+
* topic or to one line on stdout. It gives you the `EventPublisher` port bound
|
|
4
|
+
* to SNS or to one JSON line on stdout, with the topic read from one prefixed
|
|
5
|
+
* variable and a loud stand in its place when nobody set it.
|
|
6
|
+
*/
|
|
7
|
+
export { snsMessageBodyOf } from './event-publisher.sns.js';
|
|
8
|
+
export { stdoutLineOf } from './event-publisher.stdout.js';
|
|
9
|
+
export { StdoutEventPublisherLive, snsEventPublisherLive } from './layer.js';
|
|
10
|
+
export { DEFAULT_EVENT_CHANNEL, ENVIRONMENT, type SnsPublisherSettings, snsPublisherSettings, topicArnVariable, } from './settings.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry to the publishing mechanism: the event publisher, bound to a
|
|
3
|
+
* topic or to one line on stdout. It gives you the `EventPublisher` port bound
|
|
4
|
+
* to SNS or to one JSON line on stdout, with the topic read from one prefixed
|
|
5
|
+
* variable and a loud stand in its place when nobody set it.
|
|
6
|
+
*/
|
|
7
|
+
export { snsMessageBodyOf } from './event-publisher.sns.js';
|
|
8
|
+
export { stdoutLineOf } from './event-publisher.stdout.js';
|
|
9
|
+
export { StdoutEventPublisherLive, snsEventPublisherLive } from './layer.js';
|
|
10
|
+
export { DEFAULT_EVENT_CHANNEL, ENVIRONMENT, snsPublisherSettings, topicArnVariable, } from './settings.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { EventPublisher } from '@satorio/machinery/ports';
|
|
2
|
+
import { type ConfigError, Layer } from 'effect';
|
|
3
|
+
import { type ClosableSnsTransport } from './event-publisher.sns.js';
|
|
4
|
+
import { type SnsPublisherSettings } from './settings.js';
|
|
5
|
+
/**
|
|
6
|
+
* An SNS publisher built from environment variables with the given
|
|
7
|
+
* prefix. With no topic set it mounts the loud stand and logs the one
|
|
8
|
+
* refusal every later publish repeats, so startup carries on and no
|
|
9
|
+
* event goes missing in silence. The transport is a parameter, so a test
|
|
10
|
+
* substitutes its own without touching the wiring.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export declare const snsEventPublisherFromEnvironmentLive: (variablePrefix: string, openTransport: (settings: SnsPublisherSettings) => ClosableSnsTransport) => Layer.Layer<EventPublisher, ConfigError.ConfigError>;
|
|
15
|
+
/**
|
|
16
|
+
* The SNS publisher on the real AWS SDK transport, reading the variables
|
|
17
|
+
* this prefix names.
|
|
18
|
+
*/
|
|
19
|
+
export declare const snsEventPublisherLive: (variablePrefix: string) => Layer.Layer<EventPublisher, ConfigError.ConfigError>;
|
|
20
|
+
/**
|
|
21
|
+
* The stdout publisher, mounted: one JSON line per event under the
|
|
22
|
+
* channel `EVENT_CHANNEL` names. Nothing here can refuse, so the layer
|
|
23
|
+
* carries no failure of its own.
|
|
24
|
+
*/
|
|
25
|
+
export declare const StdoutEventPublisherLive: Layer.Layer<EventPublisher>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { refusalText } from '@satorio/machinery/core';
|
|
2
|
+
import { EventPublisher } from '@satorio/machinery/ports';
|
|
3
|
+
import { Console, Effect, Layer, Option } from 'effect';
|
|
4
|
+
import { eventPublisherUnavailableWithoutConfiguration, makeSnsEventPublisher, openSnsTransport, scopedSnsTransport, } from './event-publisher.sns.js';
|
|
5
|
+
import { makeStdoutEventPublisher } from './event-publisher.stdout.js';
|
|
6
|
+
import { configuredTopicArn, eventChannelFromEnvironment, snsPublisherSettings, topicArnRefusal, } from './settings.js';
|
|
7
|
+
/**
|
|
8
|
+
* An SNS publisher built from environment variables with the given
|
|
9
|
+
* prefix. With no topic set it mounts the loud stand and logs the one
|
|
10
|
+
* refusal every later publish repeats, so startup carries on and no
|
|
11
|
+
* event goes missing in silence. The transport is a parameter, so a test
|
|
12
|
+
* substitutes its own without touching the wiring.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export const snsEventPublisherFromEnvironmentLive = (variablePrefix, openTransport) => Layer.unwrapEffect(Effect.gen(function* () {
|
|
17
|
+
const configured = yield* configuredTopicArn(variablePrefix);
|
|
18
|
+
if (Option.isNone(configured)) {
|
|
19
|
+
const refusal = topicArnRefusal(variablePrefix);
|
|
20
|
+
yield* Effect.logError(refusalText(refusal));
|
|
21
|
+
return Layer.succeed(EventPublisher, eventPublisherUnavailableWithoutConfiguration(refusal));
|
|
22
|
+
}
|
|
23
|
+
return Layer.scoped(EventPublisher, Effect.gen(function* () {
|
|
24
|
+
const settings = yield* snsPublisherSettings(variablePrefix);
|
|
25
|
+
const transport = yield* scopedSnsTransport(() => openTransport(settings));
|
|
26
|
+
return makeSnsEventPublisher(transport, settings);
|
|
27
|
+
}));
|
|
28
|
+
}));
|
|
29
|
+
/**
|
|
30
|
+
* The SNS publisher on the real AWS SDK transport, reading the variables
|
|
31
|
+
* this prefix names.
|
|
32
|
+
*/
|
|
33
|
+
export const snsEventPublisherLive = (variablePrefix) => snsEventPublisherFromEnvironmentLive(variablePrefix, openSnsTransport);
|
|
34
|
+
/**
|
|
35
|
+
* The stdout publisher, mounted: one JSON line per event under the
|
|
36
|
+
* channel `EVENT_CHANNEL` names. Nothing here can refuse, so the layer
|
|
37
|
+
* carries no failure of its own.
|
|
38
|
+
*/
|
|
39
|
+
export const StdoutEventPublisherLive = Layer.effect(EventPublisher, Effect.map(eventChannelFromEnvironment, (channel) => makeStdoutEventPublisher(channel, (line) => Console.log(line)))).pipe(Layer.orDie);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type EnvironmentEntry, type Refusal } from '@satorio/machinery/core';
|
|
2
|
+
import { Config, type Option } from 'effect';
|
|
3
|
+
/**
|
|
4
|
+
* The log channel the stdout publisher writes its events under when
|
|
5
|
+
* nobody names one. A log collector filters on this word, so it has to
|
|
6
|
+
* mean something on its own.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DEFAULT_EVENT_CHANNEL = "machinery-integration-events";
|
|
9
|
+
/**
|
|
10
|
+
* Builds the environment variable name from the prefix the application
|
|
11
|
+
* gives at mount time. A per-prefix name lets several topics share one
|
|
12
|
+
* process and stay readable: `EVENTS_TOPIC_ARN`, `AUDIT_TOPIC_ARN`, and
|
|
13
|
+
* so on.
|
|
14
|
+
*/
|
|
15
|
+
export declare const topicArnVariable: (variablePrefix: string) => string;
|
|
16
|
+
/**
|
|
17
|
+
* What the SNS publisher needs before it can send: one topic, and the
|
|
18
|
+
* two variables the AWS SDK reads for itself when nobody names them.
|
|
19
|
+
*/
|
|
20
|
+
export interface SnsPublisherSettings {
|
|
21
|
+
readonly topicArn: string;
|
|
22
|
+
readonly region: Option.Option<string>;
|
|
23
|
+
readonly endpoint: Option.Option<string>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The three parts a reader meets when the topic is not set. The mount
|
|
27
|
+
* desk logs this text and every publish of the loud stand fails with it,
|
|
28
|
+
* so an operator reads one sentence rather than two.
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export declare const topicArnRefusal: (variablePrefix: string) => Refusal;
|
|
33
|
+
/**
|
|
34
|
+
* Reads the topic without refusing, so the mount desk can put up the
|
|
35
|
+
* loud stand instead of failing service startup.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export declare const configuredTopicArn: (variablePrefix: string) => Config.Config<Option.Option<string>>;
|
|
40
|
+
/**
|
|
41
|
+
* Every variable this adapter reads, and the one state each of them
|
|
42
|
+
* stands in. `<PREFIX>` is the prefix the application passes at mount
|
|
43
|
+
* time; `EVENT_CHANNEL` belongs to the stdout publisher, which serves
|
|
44
|
+
* one process and needs no prefix.
|
|
45
|
+
*/
|
|
46
|
+
export declare const ENVIRONMENT: readonly EnvironmentEntry[];
|
|
47
|
+
/**
|
|
48
|
+
* Reads every variable the SNS publisher needs under one prefix. A
|
|
49
|
+
* missing or blank topic refuses in three parts; region and endpoint
|
|
50
|
+
* stay `Option.none`, and the SDK decides.
|
|
51
|
+
*/
|
|
52
|
+
export declare const snsPublisherSettings: (variablePrefix: string) => Config.Config<SnsPublisherSettings>;
|
|
53
|
+
/**
|
|
54
|
+
* Reads the channel the stdout publisher writes under. Nobody has to set
|
|
55
|
+
* it: an unset channel takes the default rather than refusing, because a
|
|
56
|
+
* line on stdout is readable either way.
|
|
57
|
+
*
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
export declare const eventChannelFromEnvironment: Config.Config<string>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { fallsBackTo, handedOn, optionalText, refuses, requiredText, } from '@satorio/machinery/core';
|
|
2
|
+
import { Config } from 'effect';
|
|
3
|
+
const TOPIC_ARN_SUFFIX = '_TOPIC_ARN';
|
|
4
|
+
const EVENT_CHANNEL = 'EVENT_CHANNEL';
|
|
5
|
+
const REGION = 'AWS_REGION';
|
|
6
|
+
const ENDPOINT = 'AWS_ENDPOINT_URL';
|
|
7
|
+
/**
|
|
8
|
+
* The log channel the stdout publisher writes its events under when
|
|
9
|
+
* nobody names one. A log collector filters on this word, so it has to
|
|
10
|
+
* mean something on its own.
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_EVENT_CHANNEL = 'machinery-integration-events';
|
|
13
|
+
/**
|
|
14
|
+
* Builds the environment variable name from the prefix the application
|
|
15
|
+
* gives at mount time. A per-prefix name lets several topics share one
|
|
16
|
+
* process and stay readable: `EVENTS_TOPIC_ARN`, `AUDIT_TOPIC_ARN`, and
|
|
17
|
+
* so on.
|
|
18
|
+
*/
|
|
19
|
+
export const topicArnVariable = (variablePrefix) => `${variablePrefix}${TOPIC_ARN_SUFFIX}`;
|
|
20
|
+
/**
|
|
21
|
+
* The three parts a reader meets when the topic is not set. The mount
|
|
22
|
+
* desk logs this text and every publish of the loud stand fails with it,
|
|
23
|
+
* so an operator reads one sentence rather than two.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export const topicArnRefusal = (variablePrefix) => ({
|
|
28
|
+
what: `${topicArnVariable(variablePrefix)} is not set, so this publisher has no topic.`,
|
|
29
|
+
why: 'One process publishes to several topics, each under its own prefix, so the publisher cannot guess which one you meant.',
|
|
30
|
+
whatToDo: `Set ${topicArnVariable(variablePrefix)} to the topic ARN from the AWS console or your .env, then start the service again.`,
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Reads the topic without refusing, so the mount desk can put up the
|
|
34
|
+
* loud stand instead of failing service startup.
|
|
35
|
+
*
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export const configuredTopicArn = (variablePrefix) => optionalText(topicArnVariable(variablePrefix));
|
|
39
|
+
/**
|
|
40
|
+
* Every variable this adapter reads, and the one state each of them
|
|
41
|
+
* stands in. `<PREFIX>` is the prefix the application passes at mount
|
|
42
|
+
* time; `EVENT_CHANNEL` belongs to the stdout publisher, which serves
|
|
43
|
+
* one process and needs no prefix.
|
|
44
|
+
*/
|
|
45
|
+
export const ENVIRONMENT = [
|
|
46
|
+
{
|
|
47
|
+
name: TOPIC_ARN_SUFFIX,
|
|
48
|
+
prefixed: true,
|
|
49
|
+
what: 'The SNS topic this publisher sends events to.',
|
|
50
|
+
state: refuses(topicArnRefusal('<PREFIX>')),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: EVENT_CHANNEL,
|
|
54
|
+
what: "The word a log collector filters the stdout publisher's lines by.",
|
|
55
|
+
state: fallsBackTo(DEFAULT_EVENT_CHANNEL),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: REGION,
|
|
59
|
+
what: 'The region the SNS client talks to.',
|
|
60
|
+
state: handedOn('the AWS SDK finds its own region from the machine it runs on.'),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: ENDPOINT,
|
|
64
|
+
what: 'A local stand to talk to instead of real SNS, such as localstack.',
|
|
65
|
+
state: handedOn('the client goes to the real SNS for the region it found.'),
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
/**
|
|
69
|
+
* Reads every variable the SNS publisher needs under one prefix. A
|
|
70
|
+
* missing or blank topic refuses in three parts; region and endpoint
|
|
71
|
+
* stay `Option.none`, and the SDK decides.
|
|
72
|
+
*/
|
|
73
|
+
export const snsPublisherSettings = (variablePrefix) => Config.all({
|
|
74
|
+
topicArn: requiredText(topicArnVariable(variablePrefix), topicArnRefusal(variablePrefix)),
|
|
75
|
+
region: optionalText(REGION),
|
|
76
|
+
endpoint: optionalText(ENDPOINT),
|
|
77
|
+
});
|
|
78
|
+
/**
|
|
79
|
+
* Reads the channel the stdout publisher writes under. Nobody has to set
|
|
80
|
+
* it: an unset channel takes the default rather than refusing, because a
|
|
81
|
+
* line on stdout is readable either way.
|
|
82
|
+
*
|
|
83
|
+
* @internal
|
|
84
|
+
*/
|
|
85
|
+
export const eventChannelFromEnvironment = Config.string(EVENT_CHANNEL).pipe(Config.withDefault(DEFAULT_EVENT_CHANNEL));
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { QueueMessageSource, type QueueMessageSourceUnavailableError } from '@satorio/machinery/ports';
|
|
2
|
+
import type { DateTime } from 'effect';
|
|
3
|
+
import { Effect, Schedule, Stream } from 'effect';
|
|
4
|
+
import type { QueueMessageNotDecodableError } from './failures.js';
|
|
5
|
+
import { type PayloadDecoder } from './message-codec.js';
|
|
6
|
+
/**
|
|
7
|
+
* One message as a handler meets it: the envelope's own fields, and the
|
|
8
|
+
* decoded event itself.
|
|
9
|
+
*/
|
|
10
|
+
export interface IncomingQueueMessage<A> {
|
|
11
|
+
readonly eventId: string;
|
|
12
|
+
readonly eventType: string;
|
|
13
|
+
readonly schemaVersion: number;
|
|
14
|
+
readonly occurredAt: DateTime.Utc;
|
|
15
|
+
readonly event: A;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Why a message goes back on the queue: it never decoded, or the handler
|
|
19
|
+
* rejected it.
|
|
20
|
+
*/
|
|
21
|
+
export type RedeliveryReason = {
|
|
22
|
+
readonly _tag: 'MessageNotDecodable';
|
|
23
|
+
readonly error: QueueMessageNotDecodableError;
|
|
24
|
+
} | {
|
|
25
|
+
readonly _tag: 'HandlerRejectedMessage';
|
|
26
|
+
readonly error: unknown;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The reason for a message that never decoded.
|
|
30
|
+
*/
|
|
31
|
+
export declare const messageNotDecodable: (error: QueueMessageNotDecodableError) => RedeliveryReason;
|
|
32
|
+
/**
|
|
33
|
+
* The reason for a message the handler itself rejected.
|
|
34
|
+
*/
|
|
35
|
+
export declare const handlerRejectedMessage: (error: unknown) => RedeliveryReason;
|
|
36
|
+
/**
|
|
37
|
+
* The fate of one message has exactly three endings: acknowledged, left
|
|
38
|
+
* for redelivery, or refused permanently. There is no fourth, silent
|
|
39
|
+
* outcome.
|
|
40
|
+
*/
|
|
41
|
+
export type QueueMessageOutcome = {
|
|
42
|
+
readonly _tag: 'MessageAcknowledged';
|
|
43
|
+
readonly messageId: string;
|
|
44
|
+
} | {
|
|
45
|
+
readonly _tag: 'MessageLeftForRedelivery';
|
|
46
|
+
readonly messageId: string;
|
|
47
|
+
readonly reason: RedeliveryReason;
|
|
48
|
+
} | {
|
|
49
|
+
readonly _tag: 'MessageRefusedPermanently';
|
|
50
|
+
readonly messageId: string;
|
|
51
|
+
readonly eventId: string;
|
|
52
|
+
readonly refusal: unknown;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* The fate of a message the handler took, so the queue may drop it.
|
|
56
|
+
*/
|
|
57
|
+
export declare const messageAcknowledged: (messageId: string) => QueueMessageOutcome;
|
|
58
|
+
/**
|
|
59
|
+
* The fate of a message that goes back on the queue, with the reason it did.
|
|
60
|
+
*/
|
|
61
|
+
export declare const messageLeftForRedelivery: (messageId: string, reason: RedeliveryReason) => QueueMessageOutcome;
|
|
62
|
+
/**
|
|
63
|
+
* The fate of a message no retry can help, so the platform's own redrive takes
|
|
64
|
+
* it from here.
|
|
65
|
+
*/
|
|
66
|
+
export declare const messageRefusedPermanently: (messageId: string, eventId: string, refusal: unknown) => QueueMessageOutcome;
|
|
67
|
+
/**
|
|
68
|
+
* How a consumer waits and tries again while the queue itself does not answer.
|
|
69
|
+
*/
|
|
70
|
+
export type QueueRetrySchedule = Schedule.Schedule<unknown, QueueMessageSourceUnavailableError>;
|
|
71
|
+
/**
|
|
72
|
+
* The waiting a consumer keeps when a service names no schedule of its own:
|
|
73
|
+
* growing pauses, jittered, and a limit on how often it tries.
|
|
74
|
+
*/
|
|
75
|
+
export declare const defaultQueueRetrySchedule: QueueRetrySchedule;
|
|
76
|
+
/**
|
|
77
|
+
* What a consumer needs to turn a queue message into work. It names how to
|
|
78
|
+
* decode a payload, what to do with it, how to wait out a queue that is down,
|
|
79
|
+
* and which refusals no retry can help.
|
|
80
|
+
*/
|
|
81
|
+
export interface QueueMessageConsumerOptions<A, E, R> {
|
|
82
|
+
readonly decodePayload: PayloadDecoder<A>;
|
|
83
|
+
readonly handle: (input: IncomingQueueMessage<A>) => Effect.Effect<unknown, E, R>;
|
|
84
|
+
readonly retrySchedule: QueueRetrySchedule;
|
|
85
|
+
readonly isPermanentRefusal?: (error: E) => boolean;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Reads messages off the queue and hands each one to the handler, answering
|
|
89
|
+
* with the fate of every message it took.
|
|
90
|
+
*/
|
|
91
|
+
export declare const consumeQueueMessages: <A, E, R>(options: QueueMessageConsumerOptions<A, E, R>) => Stream.Stream<QueueMessageOutcome, QueueMessageSourceUnavailableError, R | QueueMessageSource>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { QueueMessageSource, } from '@satorio/machinery/ports';
|
|
2
|
+
import { Chunk, Duration, Effect, Match, Schedule, Stream } from 'effect';
|
|
3
|
+
import { decodeQueueMessageBody } from './message-codec.js';
|
|
4
|
+
/**
|
|
5
|
+
* The reason for a message that never decoded.
|
|
6
|
+
*/
|
|
7
|
+
export const messageNotDecodable = (error) => ({
|
|
8
|
+
_tag: 'MessageNotDecodable',
|
|
9
|
+
error,
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* The reason for a message the handler itself rejected.
|
|
13
|
+
*/
|
|
14
|
+
export const handlerRejectedMessage = (error) => ({
|
|
15
|
+
_tag: 'HandlerRejectedMessage',
|
|
16
|
+
error,
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* The fate of a message the handler took, so the queue may drop it.
|
|
20
|
+
*/
|
|
21
|
+
export const messageAcknowledged = (messageId) => ({
|
|
22
|
+
_tag: 'MessageAcknowledged',
|
|
23
|
+
messageId,
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* The fate of a message that goes back on the queue, with the reason it did.
|
|
27
|
+
*/
|
|
28
|
+
export const messageLeftForRedelivery = (messageId, reason) => ({
|
|
29
|
+
_tag: 'MessageLeftForRedelivery',
|
|
30
|
+
messageId,
|
|
31
|
+
reason,
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* The fate of a message no retry can help, so the platform's own redrive takes
|
|
35
|
+
* it from here.
|
|
36
|
+
*/
|
|
37
|
+
export const messageRefusedPermanently = (messageId, eventId, refusal) => ({
|
|
38
|
+
_tag: 'MessageRefusedPermanently',
|
|
39
|
+
messageId,
|
|
40
|
+
eventId,
|
|
41
|
+
refusal,
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* The waiting a consumer keeps when a service names no schedule of its own:
|
|
45
|
+
* growing pauses, jittered, and a limit on how often it tries.
|
|
46
|
+
*/
|
|
47
|
+
export const defaultQueueRetrySchedule = Schedule.exponential(Duration.millis(200), 2).pipe(Schedule.union(Schedule.spaced(Duration.seconds(30))), Schedule.jittered, Schedule.intersect(Schedule.recurs(10)));
|
|
48
|
+
const useCaseInputFrom = (envelope, event) => ({
|
|
49
|
+
eventId: envelope.eventId,
|
|
50
|
+
eventType: envelope.eventType,
|
|
51
|
+
schemaVersion: envelope.schemaVersion,
|
|
52
|
+
occurredAt: envelope.occurredAt,
|
|
53
|
+
event,
|
|
54
|
+
});
|
|
55
|
+
const outcomeOf = (options, message) => decodeQueueMessageBody(message.body).pipe(Effect.flatMap((envelope) => options
|
|
56
|
+
.decodePayload(envelope.schemaVersion, envelope.payload)
|
|
57
|
+
.pipe(Effect.map((event) => useCaseInputFrom(envelope, event)))), Effect.matchEffect({
|
|
58
|
+
onFailure: (error) => {
|
|
59
|
+
const reason = messageNotDecodable(error);
|
|
60
|
+
return Effect.logWarning('Queue message: left for redelivery', {
|
|
61
|
+
messageId: message.messageId,
|
|
62
|
+
reason: reason._tag,
|
|
63
|
+
error,
|
|
64
|
+
}).pipe(Effect.as(messageLeftForRedelivery(message.messageId, reason)));
|
|
65
|
+
},
|
|
66
|
+
onSuccess: (input) => options.handle(input).pipe(Effect.matchEffect({
|
|
67
|
+
onFailure: (error) => {
|
|
68
|
+
if (options.isPermanentRefusal?.(error) === true) {
|
|
69
|
+
return Effect.logError('Queue message: event refused permanently, message goes to the platform DLQ after the redrive limit runs out', {
|
|
70
|
+
eventId: input.eventId,
|
|
71
|
+
eventType: input.eventType,
|
|
72
|
+
refusal: error,
|
|
73
|
+
}).pipe(Effect.as(messageRefusedPermanently(message.messageId, input.eventId, error)));
|
|
74
|
+
}
|
|
75
|
+
const reason = handlerRejectedMessage(error);
|
|
76
|
+
return Effect.logWarning('Queue message: left for redelivery', {
|
|
77
|
+
messageId: message.messageId,
|
|
78
|
+
reason: reason._tag,
|
|
79
|
+
error,
|
|
80
|
+
}).pipe(Effect.as(messageLeftForRedelivery(message.messageId, reason)));
|
|
81
|
+
},
|
|
82
|
+
onSuccess: () => Effect.succeed(messageAcknowledged(message.messageId)),
|
|
83
|
+
})),
|
|
84
|
+
}));
|
|
85
|
+
const applyOutcome = (source, message, retrySchedule, outcome) => Match.value(outcome).pipe(Match.tag('MessageAcknowledged', () => source
|
|
86
|
+
.acknowledge(message.receiptHandle)
|
|
87
|
+
.pipe(Effect.retry(retrySchedule))), Match.tag('MessageLeftForRedelivery', () => Effect.void), Match.tag('MessageRefusedPermanently', () => Effect.void), Match.exhaustive);
|
|
88
|
+
/**
|
|
89
|
+
* Reads messages off the queue and hands each one to the handler, answering
|
|
90
|
+
* with the fate of every message it took.
|
|
91
|
+
*/
|
|
92
|
+
export const consumeQueueMessages = (options) => Stream.unwrap(Effect.map(QueueMessageSource, (source) => Stream.repeatEffectChunk(source.receive.pipe(Effect.retry(options.retrySchedule), Effect.map(Chunk.fromIterable))).pipe(Stream.mapEffect((message) => outcomeOf(options, message).pipe(Effect.tap((outcome) => applyOutcome(source, message, options.retrySchedule, outcome)))))));
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { VersionedEventDefinition } from '@satorio/machinery/workflow';
|
|
2
|
+
import { Effect, Schema } from 'effect';
|
|
3
|
+
import { UnknownEventTypeError } from './failures.js';
|
|
4
|
+
/**
|
|
5
|
+
* The version a stored payload was shaped by: a whole number above zero, and
|
|
6
|
+
* never anything else.
|
|
7
|
+
*/
|
|
8
|
+
export declare const SchemaVersion: Schema.brand<Schema.filter<typeof Schema.Int>, "SchemaVersion">;
|
|
9
|
+
export type SchemaVersion = typeof SchemaVersion.Type;
|
|
10
|
+
/**
|
|
11
|
+
* The shared queue contract every message travels in.
|
|
12
|
+
*
|
|
13
|
+
* The shared queue contract: who sent it (eventId), what it is (eventType),
|
|
14
|
+
* which schema shaped it (schemaVersion), when it happened (occurredAt), and
|
|
15
|
+
* the content itself (payload). The payload is the service's concern; the
|
|
16
|
+
* envelope is the machinery's concern.
|
|
17
|
+
*/
|
|
18
|
+
export declare const StoredEventEnvelope: Schema.Struct<{
|
|
19
|
+
eventId: typeof Schema.NonEmptyTrimmedString;
|
|
20
|
+
eventType: typeof Schema.NonEmptyTrimmedString;
|
|
21
|
+
schemaVersion: Schema.brand<Schema.filter<typeof Schema.Int>, "SchemaVersion">;
|
|
22
|
+
occurredAt: typeof Schema.DateTimeUtc;
|
|
23
|
+
payload: Schema.Record$<typeof Schema.String, typeof Schema.Unknown>;
|
|
24
|
+
}>;
|
|
25
|
+
export type StoredEventEnvelope = typeof StoredEventEnvelope.Type;
|
|
26
|
+
/**
|
|
27
|
+
* What a service's own list of event definitions offers: whether an event type
|
|
28
|
+
* is known, and the definition behind it.
|
|
29
|
+
*/
|
|
30
|
+
export interface EventDefinitionRegistry<S extends Schema.Schema.AnyNoContext> {
|
|
31
|
+
readonly knows: (eventType: string) => boolean;
|
|
32
|
+
readonly definitionFor: (eventType: string) => Effect.Effect<VersionedEventDefinition<S>, UnknownEventTypeError>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Builds a registry of known event definitions. Your service lists its
|
|
36
|
+
* definitions; the machinery gives lookup and a loud refusal for an
|
|
37
|
+
* unknown type.
|
|
38
|
+
*/
|
|
39
|
+
export declare const eventDefinitionRegistry: <S extends Schema.Schema.AnyNoContext>(definitions: readonly VersionedEventDefinition<S>[]) => EventDefinitionRegistry<S>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Effect, Schema } from 'effect';
|
|
2
|
+
import { UnknownEventTypeError } from './failures.js';
|
|
3
|
+
/**
|
|
4
|
+
* The version a stored payload was shaped by: a whole number above zero, and
|
|
5
|
+
* never anything else.
|
|
6
|
+
*/
|
|
7
|
+
export const SchemaVersion = Schema.Int.pipe(Schema.positive(), Schema.brand('SchemaVersion')).annotations({ identifier: 'SchemaVersion' });
|
|
8
|
+
/**
|
|
9
|
+
* The shared queue contract every message travels in.
|
|
10
|
+
*
|
|
11
|
+
* The shared queue contract: who sent it (eventId), what it is (eventType),
|
|
12
|
+
* which schema shaped it (schemaVersion), when it happened (occurredAt), and
|
|
13
|
+
* the content itself (payload). The payload is the service's concern; the
|
|
14
|
+
* envelope is the machinery's concern.
|
|
15
|
+
*/
|
|
16
|
+
export const StoredEventEnvelope = Schema.Struct({
|
|
17
|
+
eventId: Schema.NonEmptyTrimmedString,
|
|
18
|
+
eventType: Schema.NonEmptyTrimmedString,
|
|
19
|
+
schemaVersion: SchemaVersion,
|
|
20
|
+
occurredAt: Schema.DateTimeUtc,
|
|
21
|
+
payload: Schema.Record({ key: Schema.String, value: Schema.Unknown }),
|
|
22
|
+
}).annotations({ identifier: 'StoredEventEnvelope' });
|
|
23
|
+
/**
|
|
24
|
+
* Builds a registry of known event definitions. Your service lists its
|
|
25
|
+
* definitions; the machinery gives lookup and a loud refusal for an
|
|
26
|
+
* unknown type.
|
|
27
|
+
*/
|
|
28
|
+
export const eventDefinitionRegistry = (definitions) => {
|
|
29
|
+
const byEventType = new Map(definitions.map((definition) => [definition.eventType, definition]));
|
|
30
|
+
return {
|
|
31
|
+
knows: (eventType) => byEventType.has(eventType),
|
|
32
|
+
definitionFor: (eventType) => {
|
|
33
|
+
const found = byEventType.get(eventType);
|
|
34
|
+
return found === undefined
|
|
35
|
+
? Effect.fail(new UnknownEventTypeError({ eventType }))
|
|
36
|
+
: Effect.succeed(found);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stage a message fell over at: the envelope every message shares,
|
|
3
|
+
* or the payload one event type defines.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export type QueueMessageDecodingStage = 'envelope' | 'payload';
|
|
8
|
+
declare const QueueMessageNotDecodableError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
9
|
+
readonly _tag: "QueueMessageNotDecodableError";
|
|
10
|
+
} & Readonly<A>;
|
|
11
|
+
/**
|
|
12
|
+
* A message whose text this service cannot turn into an event. The stage
|
|
13
|
+
* says whether the shared envelope or the event's own payload was the
|
|
14
|
+
* part that would not decode.
|
|
15
|
+
*/
|
|
16
|
+
export declare class QueueMessageNotDecodableError extends QueueMessageNotDecodableError_base<{
|
|
17
|
+
readonly stage: QueueMessageDecodingStage;
|
|
18
|
+
readonly cause: unknown;
|
|
19
|
+
}> {
|
|
20
|
+
}
|
|
21
|
+
declare const UnknownEventTypeError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
22
|
+
readonly _tag: "UnknownEventTypeError";
|
|
23
|
+
} & Readonly<A>;
|
|
24
|
+
/**
|
|
25
|
+
* A message naming an event type this service never registered. The
|
|
26
|
+
* registry refuses loudly rather than guessing which definition was
|
|
27
|
+
* meant.
|
|
28
|
+
*/
|
|
29
|
+
export declare class UnknownEventTypeError extends UnknownEventTypeError_base<{
|
|
30
|
+
readonly eventType: string;
|
|
31
|
+
}> {
|
|
32
|
+
}
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Data } from 'effect';
|
|
2
|
+
/**
|
|
3
|
+
* A message whose text this service cannot turn into an event. The stage
|
|
4
|
+
* says whether the shared envelope or the event's own payload was the
|
|
5
|
+
* part that would not decode.
|
|
6
|
+
*/
|
|
7
|
+
export class QueueMessageNotDecodableError extends Data.TaggedError('QueueMessageNotDecodableError') {
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A message naming an event type this service never registered. The
|
|
11
|
+
* registry refuses loudly rather than guessing which definition was
|
|
12
|
+
* meant.
|
|
13
|
+
*/
|
|
14
|
+
export class UnknownEventTypeError extends Data.TaggedError('UnknownEventTypeError') {
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry to the queue mechanism: the envelope a message travels in, and
|
|
3
|
+
* the consumer that decides its fate. It gives you the envelope every message
|
|
4
|
+
* carries, the decoder that opens it, the consumer that decides each message's
|
|
5
|
+
* fate, and the SQS source and producer read from one prefix of environment
|
|
6
|
+
* variables.
|
|
7
|
+
*/
|
|
8
|
+
export { consumeQueueMessages, defaultQueueRetrySchedule, handlerRejectedMessage, type IncomingQueueMessage, messageAcknowledged, messageLeftForRedelivery, messageNotDecodable, messageRefusedPermanently, type QueueMessageConsumerOptions, type QueueMessageOutcome, type QueueRetrySchedule, type RedeliveryReason, } from './consumer.js';
|
|
9
|
+
export { type EventDefinitionRegistry, eventDefinitionRegistry, SchemaVersion, StoredEventEnvelope, } from './envelope.js';
|
|
10
|
+
export { QueueMessageNotDecodableError, UnknownEventTypeError, } from './failures.js';
|
|
11
|
+
export { sqsQueueMessageProducerLive, sqsQueueMessageSourceLive, } from './layer.js';
|
|
12
|
+
export { decodeQueueMessageBody, type PayloadDecoder, payloadDecoderFor, QueueMessageBody, queueMessageBodyOf, } from './message-codec.js';
|
|
13
|
+
export { queueReadinessCheck } from './readiness.js';
|
|
14
|
+
export { ENVIRONMENT, type QueueConsumerSettings, type QueueProducerSettings, queueConsumerSettings, queueProducerSettings, queueUrlVariable, SQS_DEFAULT_VISIBILITY_TIMEOUT_SECONDS, } from './settings.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry to the queue mechanism: the envelope a message travels in, and
|
|
3
|
+
* the consumer that decides its fate. It gives you the envelope every message
|
|
4
|
+
* carries, the decoder that opens it, the consumer that decides each message's
|
|
5
|
+
* fate, and the SQS source and producer read from one prefix of environment
|
|
6
|
+
* variables.
|
|
7
|
+
*/
|
|
8
|
+
export { consumeQueueMessages, defaultQueueRetrySchedule, handlerRejectedMessage, messageAcknowledged, messageLeftForRedelivery, messageNotDecodable, messageRefusedPermanently, } from './consumer.js';
|
|
9
|
+
export { eventDefinitionRegistry, SchemaVersion, StoredEventEnvelope, } from './envelope.js';
|
|
10
|
+
export { QueueMessageNotDecodableError, UnknownEventTypeError, } from './failures.js';
|
|
11
|
+
export { sqsQueueMessageProducerLive, sqsQueueMessageSourceLive, } from './layer.js';
|
|
12
|
+
export { decodeQueueMessageBody, payloadDecoderFor, QueueMessageBody, queueMessageBodyOf, } from './message-codec.js';
|
|
13
|
+
export { queueReadinessCheck } from './readiness.js';
|
|
14
|
+
export { ENVIRONMENT, queueConsumerSettings, queueProducerSettings, queueUrlVariable, SQS_DEFAULT_VISIBILITY_TIMEOUT_SECONDS, } from './settings.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { QueueMessageProducer, QueueMessageSource } from '@satorio/machinery/ports';
|
|
2
|
+
import { type ConfigError, Layer } from 'effect';
|
|
3
|
+
import { type ClosableSqsSendTransport } from './queue-message-producer.js';
|
|
4
|
+
import { type ClosableSqsTransport } from './queue-message-source.js';
|
|
5
|
+
import { type QueueConsumerSettings, type QueueProducerSettings } from './settings.js';
|
|
6
|
+
/**
|
|
7
|
+
* A message source built from environment variables with the given
|
|
8
|
+
* prefix. With no queue URL set it mounts the loud stand and logs the
|
|
9
|
+
* one refusal every later receive repeats, so startup carries on and
|
|
10
|
+
* nothing goes missing in silence. The transport is a parameter, so a
|
|
11
|
+
* test substitutes its own without touching the wiring.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export declare const queueMessageSourceFromEnvironmentLive: (variablePrefix: string, openTransport: (settings: QueueConsumerSettings) => ClosableSqsTransport) => Layer.Layer<QueueMessageSource, ConfigError.ConfigError>;
|
|
16
|
+
/**
|
|
17
|
+
* The message source on the real AWS SDK transport, reading the
|
|
18
|
+
* variables this prefix names.
|
|
19
|
+
*/
|
|
20
|
+
export declare const sqsQueueMessageSourceLive: (variablePrefix: string) => Layer.Layer<QueueMessageSource, ConfigError.ConfigError>;
|
|
21
|
+
/**
|
|
22
|
+
* A message producer built from environment variables with the given
|
|
23
|
+
* prefix, mounting the loud stand when no queue URL is set. The
|
|
24
|
+
* transport is a parameter for the same reason the source's is.
|
|
25
|
+
*
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export declare const queueMessageProducerFromEnvironmentLive: (variablePrefix: string, openTransport: (settings: QueueProducerSettings) => ClosableSqsSendTransport) => Layer.Layer<QueueMessageProducer, ConfigError.ConfigError>;
|
|
29
|
+
/**
|
|
30
|
+
* The message producer on the real AWS SDK transport, reading the
|
|
31
|
+
* variables this prefix names.
|
|
32
|
+
*/
|
|
33
|
+
export declare const sqsQueueMessageProducerLive: (variablePrefix: string) => Layer.Layer<QueueMessageProducer, ConfigError.ConfigError>;
|