@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,26 @@
|
|
|
1
|
+
import { Context, type Effect } from 'effect';
|
|
2
|
+
declare const QueueMessageProducerUnavailableError_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 & {
|
|
3
|
+
readonly _tag: "QueueMessageProducerUnavailableError";
|
|
4
|
+
} & Readonly<A>;
|
|
5
|
+
/**
|
|
6
|
+
* The queue producer did not answer; `operation` names the call and `cause`
|
|
7
|
+
* carries the driver's own error.
|
|
8
|
+
*/
|
|
9
|
+
export declare class QueueMessageProducerUnavailableError extends QueueMessageProducerUnavailableError_base<{
|
|
10
|
+
readonly operation: string;
|
|
11
|
+
readonly cause: unknown;
|
|
12
|
+
}> {
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* What a producer offers: one call that puts a message body onto the queue.
|
|
16
|
+
*/
|
|
17
|
+
export interface QueueMessageProducerShape {
|
|
18
|
+
readonly send: (body: string) => Effect.Effect<void, QueueMessageProducerUnavailableError>;
|
|
19
|
+
}
|
|
20
|
+
declare const QueueMessageProducer_base: Context.TagClass<QueueMessageProducer, "QueueMessageProducer", QueueMessageProducerShape>;
|
|
21
|
+
/**
|
|
22
|
+
* The port a use case asks for when it sends a message to a queue.
|
|
23
|
+
*/
|
|
24
|
+
export declare class QueueMessageProducer extends QueueMessageProducer_base {
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Context, Data } from 'effect';
|
|
2
|
+
/**
|
|
3
|
+
* The queue producer did not answer; `operation` names the call and `cause`
|
|
4
|
+
* carries the driver's own error.
|
|
5
|
+
*/
|
|
6
|
+
export class QueueMessageProducerUnavailableError extends Data.TaggedError('QueueMessageProducerUnavailableError') {
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The port a use case asks for when it sends a message to a queue.
|
|
10
|
+
*/
|
|
11
|
+
export class QueueMessageProducer extends Context.Tag('QueueMessageProducer')() {
|
|
12
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Context, type Effect } from 'effect';
|
|
2
|
+
/**
|
|
3
|
+
* One message as the queue hands it over: its own id, the receipt an
|
|
4
|
+
* acknowledgement quotes, and the body, still text.
|
|
5
|
+
*/
|
|
6
|
+
export interface RawQueueMessage {
|
|
7
|
+
readonly messageId: string;
|
|
8
|
+
readonly receiptHandle: string;
|
|
9
|
+
readonly body: string;
|
|
10
|
+
}
|
|
11
|
+
declare const QueueMessageSourceUnavailableError_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 & {
|
|
12
|
+
readonly _tag: "QueueMessageSourceUnavailableError";
|
|
13
|
+
} & Readonly<A>;
|
|
14
|
+
/**
|
|
15
|
+
* The queue source did not answer; `operation` names the call and `cause`
|
|
16
|
+
* carries the driver's own error.
|
|
17
|
+
*/
|
|
18
|
+
export declare class QueueMessageSourceUnavailableError extends QueueMessageSourceUnavailableError_base<{
|
|
19
|
+
readonly operation: string;
|
|
20
|
+
readonly cause: unknown;
|
|
21
|
+
}> {
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A source of queue messages: it receives a batch and acknowledges what was
|
|
25
|
+
* taken. The app never knows whether SQS, a test stub, or another queue
|
|
26
|
+
* stands behind it.
|
|
27
|
+
*/
|
|
28
|
+
export interface QueueMessageSourceShape {
|
|
29
|
+
readonly receive: Effect.Effect<readonly RawQueueMessage[], QueueMessageSourceUnavailableError>;
|
|
30
|
+
readonly acknowledge: (receiptHandle: string) => Effect.Effect<void, QueueMessageSourceUnavailableError>;
|
|
31
|
+
}
|
|
32
|
+
declare const QueueMessageSource_base: Context.TagClass<QueueMessageSource, "QueueMessageSource", QueueMessageSourceShape>;
|
|
33
|
+
/**
|
|
34
|
+
* The port a consumer asks for when it receives messages from a queue and
|
|
35
|
+
* acknowledges what it took.
|
|
36
|
+
*/
|
|
37
|
+
export declare class QueueMessageSource extends QueueMessageSource_base {
|
|
38
|
+
}
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Context, Data } from 'effect';
|
|
2
|
+
/**
|
|
3
|
+
* The queue source did not answer; `operation` names the call and `cause`
|
|
4
|
+
* carries the driver's own error.
|
|
5
|
+
*/
|
|
6
|
+
export class QueueMessageSourceUnavailableError extends Data.TaggedError('QueueMessageSourceUnavailableError') {
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The port a consumer asks for when it receives messages from a queue and
|
|
10
|
+
* acknowledges what it took.
|
|
11
|
+
*/
|
|
12
|
+
export class QueueMessageSource extends Context.Tag('QueueMessageSource')() {
|
|
13
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Context, type Effect } from 'effect';
|
|
2
|
+
declare const DatabaseDidNotAnswerError_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 & {
|
|
3
|
+
readonly _tag: "DatabaseDidNotAnswerError";
|
|
4
|
+
} & Readonly<A>;
|
|
5
|
+
/**
|
|
6
|
+
* The readiness check against the database did not come back; `reason` says
|
|
7
|
+
* what stopped it.
|
|
8
|
+
*/
|
|
9
|
+
export declare class DatabaseDidNotAnswerError extends DatabaseDidNotAnswerError_base<{
|
|
10
|
+
readonly reason: string;
|
|
11
|
+
}> {
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* What a readiness probe offers: one check that the database answers, so the
|
|
15
|
+
* endpoint reports a dependency rather than a guess.
|
|
16
|
+
*/
|
|
17
|
+
export interface ReadinessProbeShape {
|
|
18
|
+
readonly verifyDatabaseAnswers: Effect.Effect<void, DatabaseDidNotAnswerError>;
|
|
19
|
+
}
|
|
20
|
+
declare const ReadinessProbe_base: Context.TagClass<ReadinessProbe, "ReadinessProbe", ReadinessProbeShape>;
|
|
21
|
+
/**
|
|
22
|
+
* The port the readiness endpoint asks for when it has to say whether this
|
|
23
|
+
* service can take traffic.
|
|
24
|
+
*/
|
|
25
|
+
export declare class ReadinessProbe extends ReadinessProbe_base {
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Context, Data } from 'effect';
|
|
2
|
+
/**
|
|
3
|
+
* The readiness check against the database did not come back; `reason` says
|
|
4
|
+
* what stopped it.
|
|
5
|
+
*/
|
|
6
|
+
export class DatabaseDidNotAnswerError extends Data.TaggedError('DatabaseDidNotAnswerError') {
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The port the readiness endpoint asks for when it has to say whether this
|
|
10
|
+
* service can take traffic.
|
|
11
|
+
*/
|
|
12
|
+
export class ReadinessProbe extends Context.Tag('ReadinessProbe')() {
|
|
13
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Context, type Effect } from 'effect';
|
|
2
|
+
declare const TransactionBoundaryUnavailableError_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 & {
|
|
3
|
+
readonly _tag: "TransactionBoundaryUnavailableError";
|
|
4
|
+
} & Readonly<A>;
|
|
5
|
+
/**
|
|
6
|
+
* The transaction driver did not answer; `operation` names the call and
|
|
7
|
+
* `cause` carries the driver's own error.
|
|
8
|
+
*/
|
|
9
|
+
export declare class TransactionBoundaryUnavailableError extends TransactionBoundaryUnavailableError_base<{
|
|
10
|
+
readonly operation: string;
|
|
11
|
+
readonly cause: unknown;
|
|
12
|
+
}> {
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* What a transaction boundary offers: one call that runs work inside a
|
|
16
|
+
* transaction, and rolls the whole of it back when the work fails.
|
|
17
|
+
*/
|
|
18
|
+
export interface TransactionBoundaryShape {
|
|
19
|
+
readonly inTransaction: <A, E, R>(work: Effect.Effect<A, E, R>) => Effect.Effect<A, E | TransactionBoundaryUnavailableError, R>;
|
|
20
|
+
}
|
|
21
|
+
declare const TransactionBoundary_base: Context.TagClass<TransactionBoundary, "TransactionBoundary", TransactionBoundaryShape>;
|
|
22
|
+
/**
|
|
23
|
+
* The port a use case asks for when its writes must land together or not at
|
|
24
|
+
* all.
|
|
25
|
+
*/
|
|
26
|
+
export declare class TransactionBoundary extends TransactionBoundary_base {
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Context, Data } from 'effect';
|
|
2
|
+
/**
|
|
3
|
+
* The transaction driver did not answer; `operation` names the call and
|
|
4
|
+
* `cause` carries the driver's own error.
|
|
5
|
+
*/
|
|
6
|
+
export class TransactionBoundaryUnavailableError extends Data.TaggedError('TransactionBoundaryUnavailableError') {
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The port a use case asks for when its writes must land together or not at
|
|
10
|
+
* all.
|
|
11
|
+
*/
|
|
12
|
+
export class TransactionBoundary extends Context.Tag('TransactionBoundary')() {
|
|
13
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ConnectionOptions } from 'node:tls';
|
|
2
|
+
import { FileSystem } from '@effect/platform';
|
|
3
|
+
import type { PlatformError } from '@effect/platform/Error';
|
|
4
|
+
import { SqlError } from '@effect/sql/SqlError';
|
|
5
|
+
import { Effect, Option, type Scope } from 'effect';
|
|
6
|
+
import { Pool, type PoolConfig } from 'pg';
|
|
7
|
+
import type { PostgresConnectionSettings } from './settings.js';
|
|
8
|
+
/**
|
|
9
|
+
* Reads what the driver needs to encrypt the connection: `false` to talk
|
|
10
|
+
* in the clear, otherwise whether the chain must verify and, when a root
|
|
11
|
+
* certificate is named, the certificate itself.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export declare const sslOptionsFor: (settings: PostgresConnectionSettings) => Effect.Effect<boolean | ConnectionOptions, PlatformError, FileSystem.FileSystem>;
|
|
16
|
+
/**
|
|
17
|
+
* Opens the channel a connection asks a fresh IAM token from. It holds
|
|
18
|
+
* nothing to release — signing a token is arithmetic over credentials,
|
|
19
|
+
* not a socket — so it has no scoped twin, and the region stays optional
|
|
20
|
+
* because the AWS SDK finds its own when nobody names one.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export declare const openIamTokenTransport: (settings: PostgresConnectionSettings, region: Option.Option<string>) => (() => Promise<string>);
|
|
25
|
+
/**
|
|
26
|
+
* Builds the pool options for one service connection: the address, the
|
|
27
|
+
* schema as `search_path`, the name `pg_stat_activity` shows, and
|
|
28
|
+
* whichever of the two ways of proving who you are is in force.
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export declare const poolOptionsFor: (settings: PostgresConnectionSettings, ssl: boolean | ConnectionOptions) => PoolConfig;
|
|
33
|
+
/**
|
|
34
|
+
* Opens the connection pool. This is the one place in this mechanism
|
|
35
|
+
* where the `pg` driver is constructed, so every path to a live socket
|
|
36
|
+
* goes through `scopedPostgresTransport` and closes with its scope.
|
|
37
|
+
*
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
export declare const openPostgresTransport: (options: PoolConfig) => Pool;
|
|
41
|
+
/**
|
|
42
|
+
* Builds an effect that acquires a Postgres connection pool and releases it
|
|
43
|
+
* when its scope closes. Pass a function that builds `PoolConfig` from
|
|
44
|
+
* settings and SSL options, plus the message to use if the pool fails to
|
|
45
|
+
* open; call the result with settings and SSL options to run it. One shape
|
|
46
|
+
* serves both the service and the migration runner — `optionsFor` and the
|
|
47
|
+
* failure message are the only two parts that vary between them.
|
|
48
|
+
*
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
export declare const scopedPostgresTransport: (optionsFor: (settings: PostgresConnectionSettings, ssl: boolean | ConnectionOptions) => PoolConfig, poolCreationFailureMessage: string) => (settings: PostgresConnectionSettings, ssl: boolean | ConnectionOptions) => Effect.Effect<Pool, SqlError, Scope.Scope>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Signer } from '@aws-sdk/rds-signer';
|
|
2
|
+
import { FileSystem } from '@effect/platform';
|
|
3
|
+
import { SqlError } from '@effect/sql/SqlError';
|
|
4
|
+
import { Effect, Match, Option, Redacted } from 'effect';
|
|
5
|
+
import { Pool } from 'pg';
|
|
6
|
+
const encryptionIsDisabled = (sslMode) => sslMode === 'disable';
|
|
7
|
+
const certificateChainMustBeVerified = (sslMode) => sslMode === 'verify-ca' || sslMode === 'verify-full';
|
|
8
|
+
/**
|
|
9
|
+
* Reads what the driver needs to encrypt the connection: `false` to talk
|
|
10
|
+
* in the clear, otherwise whether the chain must verify and, when a root
|
|
11
|
+
* certificate is named, the certificate itself.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export const sslOptionsFor = (settings) => Effect.gen(function* () {
|
|
16
|
+
if (encryptionIsDisabled(settings.sslMode))
|
|
17
|
+
return false;
|
|
18
|
+
const rejectUnauthorized = certificateChainMustBeVerified(settings.sslMode);
|
|
19
|
+
if (Option.isNone(settings.sslRootCertificatePath))
|
|
20
|
+
return { rejectUnauthorized };
|
|
21
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
22
|
+
const ca = yield* fileSystem.readFileString(settings.sslRootCertificatePath.value);
|
|
23
|
+
return { rejectUnauthorized, ca };
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* Opens the channel a connection asks a fresh IAM token from. It holds
|
|
27
|
+
* nothing to release — signing a token is arithmetic over credentials,
|
|
28
|
+
* not a socket — so it has no scoped twin, and the region stays optional
|
|
29
|
+
* because the AWS SDK finds its own when nobody names one.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export const openIamTokenTransport = (settings, region) => {
|
|
34
|
+
const signer = new Signer({
|
|
35
|
+
hostname: settings.host,
|
|
36
|
+
port: settings.port,
|
|
37
|
+
username: settings.user,
|
|
38
|
+
...(Option.isSome(region) ? { region: region.value } : {}),
|
|
39
|
+
});
|
|
40
|
+
return () => signer.getAuthToken();
|
|
41
|
+
};
|
|
42
|
+
const passwordFor = (settings) => Match.value(settings.authentication).pipe(Match.tag('AwsIamAuthentication', ({ region }) => openIamTokenTransport(settings, region)), Match.tag('PasswordAuthentication', ({ password }) => Redacted.value(password)), Match.exhaustive);
|
|
43
|
+
/**
|
|
44
|
+
* Builds the pool options for one service connection: the address, the
|
|
45
|
+
* schema as `search_path`, the name `pg_stat_activity` shows, and
|
|
46
|
+
* whichever of the two ways of proving who you are is in force.
|
|
47
|
+
*
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
export const poolOptionsFor = (settings, ssl) => ({
|
|
51
|
+
host: settings.host,
|
|
52
|
+
port: settings.port,
|
|
53
|
+
user: settings.user,
|
|
54
|
+
database: settings.database,
|
|
55
|
+
options: `-c search_path=${settings.schema}`,
|
|
56
|
+
application_name: settings.applicationName,
|
|
57
|
+
ssl,
|
|
58
|
+
password: passwordFor(settings),
|
|
59
|
+
});
|
|
60
|
+
/**
|
|
61
|
+
* Opens the connection pool. This is the one place in this mechanism
|
|
62
|
+
* where the `pg` driver is constructed, so every path to a live socket
|
|
63
|
+
* goes through `scopedPostgresTransport` and closes with its scope.
|
|
64
|
+
*
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
export const openPostgresTransport = (options) => new Pool(options);
|
|
68
|
+
/**
|
|
69
|
+
* Builds an effect that acquires a Postgres connection pool and releases it
|
|
70
|
+
* when its scope closes. Pass a function that builds `PoolConfig` from
|
|
71
|
+
* settings and SSL options, plus the message to use if the pool fails to
|
|
72
|
+
* open; call the result with settings and SSL options to run it. One shape
|
|
73
|
+
* serves both the service and the migration runner — `optionsFor` and the
|
|
74
|
+
* failure message are the only two parts that vary between them.
|
|
75
|
+
*
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
78
|
+
export const scopedPostgresTransport = (optionsFor, poolCreationFailureMessage) => (settings, ssl) => Effect.acquireRelease(Effect.try({
|
|
79
|
+
try: () => openPostgresTransport(optionsFor(settings, ssl)),
|
|
80
|
+
catch: (cause) => new SqlError({ cause, message: poolCreationFailureMessage }),
|
|
81
|
+
}), (pool) => Effect.promise(() => pool.end()).pipe(Effect.orDie));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PgClient } from '@effect/sql-pg';
|
|
2
|
+
import { DocumentNumberCounterUnavailableError } from '@satorio/machinery/ports';
|
|
3
|
+
import { Effect } from 'effect';
|
|
4
|
+
/**
|
|
5
|
+
* Builds the counter on the service's own pool. One statement takes the
|
|
6
|
+
* next count and reads it back, so two callers asking for the same
|
|
7
|
+
* client and month at the same instant queue behind one row rather than
|
|
8
|
+
* both reading the same number.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare const makePgDocumentNumberCounter: Effect.Effect<{
|
|
13
|
+
nextCount: (input: import("@satorio/machinery/ports").DocumentNumberCountInput) => Effect.Effect<number, DocumentNumberCounterUnavailableError>;
|
|
14
|
+
}, never, PgClient.PgClient>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { PgClient } from '@effect/sql-pg';
|
|
2
|
+
import { DocumentNumberCounterUnavailableError, portUnavailable, } from '@satorio/machinery/ports';
|
|
3
|
+
import { Effect } from 'effect';
|
|
4
|
+
/**
|
|
5
|
+
* Builds the counter on the service's own pool. One statement takes the
|
|
6
|
+
* next count and reads it back, so two callers asking for the same
|
|
7
|
+
* client and month at the same instant queue behind one row rather than
|
|
8
|
+
* both reading the same number.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export const makePgDocumentNumberCounter = Effect.gen(function* () {
|
|
13
|
+
const sql = yield* PgClient.PgClient;
|
|
14
|
+
const unavailable = portUnavailable(DocumentNumberCounterUnavailableError);
|
|
15
|
+
const nextCount = (input) => Effect.gen(function* () {
|
|
16
|
+
const rows = yield* sql `
|
|
17
|
+
INSERT INTO document_number_counter
|
|
18
|
+
(client_code, reporting_month, issued)
|
|
19
|
+
VALUES (${input.clientCode}::text, ${input.reportingMonth}::text, 1)
|
|
20
|
+
ON CONFLICT (client_code, reporting_month) DO UPDATE
|
|
21
|
+
SET issued = document_number_counter.issued + 1
|
|
22
|
+
RETURNING issued
|
|
23
|
+
`;
|
|
24
|
+
return rows[0]?.issued ?? 0;
|
|
25
|
+
}).pipe(Effect.catchTag('SqlError', unavailable('nextCount')));
|
|
26
|
+
return { nextCount };
|
|
27
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
declare const MigrationLockBusyError_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 & {
|
|
2
|
+
readonly _tag: "MigrationLockBusyError";
|
|
3
|
+
} & Readonly<A>;
|
|
4
|
+
/**
|
|
5
|
+
* A second process already holds the migration lock and did not let go
|
|
6
|
+
* within the retry window. Nothing was applied; the run is safe to
|
|
7
|
+
* repeat once the other process finishes.
|
|
8
|
+
*/
|
|
9
|
+
export declare class MigrationLockBusyError extends MigrationLockBusyError_base<{
|
|
10
|
+
readonly lockName: string;
|
|
11
|
+
}> {
|
|
12
|
+
}
|
|
13
|
+
declare const ClaimRaceWindowMissedError_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 & {
|
|
14
|
+
readonly _tag: "ClaimRaceWindowMissedError";
|
|
15
|
+
} & Readonly<A>;
|
|
16
|
+
/**
|
|
17
|
+
* The claim statement took the lock and read who won it in one go, and
|
|
18
|
+
* the read saw neither. That happens when a competing claim has won but
|
|
19
|
+
* has not committed as of this statement's own snapshot. The registry
|
|
20
|
+
* retries a fixed number of times and never lets this failure out.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export declare class ClaimRaceWindowMissedError extends ClaimRaceWindowMissedError_base<{
|
|
25
|
+
readonly eventId: string;
|
|
26
|
+
}> {
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Data } from 'effect';
|
|
2
|
+
/**
|
|
3
|
+
* A second process already holds the migration lock and did not let go
|
|
4
|
+
* within the retry window. Nothing was applied; the run is safe to
|
|
5
|
+
* repeat once the other process finishes.
|
|
6
|
+
*/
|
|
7
|
+
export class MigrationLockBusyError extends Data.TaggedError('MigrationLockBusyError') {
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The claim statement took the lock and read who won it in one go, and
|
|
11
|
+
* the read saw neither. That happens when a competing claim has won but
|
|
12
|
+
* has not committed as of this statement's own snapshot. The registry
|
|
13
|
+
* retries a fixed number of times and never lets this failure out.
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export class ClaimRaceWindowMissedError extends Data.TaggedError('ClaimRaceWindowMissedError') {
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry to the postgres mechanism: every port this machinery defines,
|
|
3
|
+
* bound to a real database. It gives you every port this machinery defines,
|
|
4
|
+
* bound to a real database — the transaction boundary, the exactly-once claim,
|
|
5
|
+
* the outbox, the document number counter and the readiness probe — with the
|
|
6
|
+
* connection read from the environment, a migration runner that holds an
|
|
7
|
+
* advisory lock, and the helpers a store reaches for when a SQL failure has to
|
|
8
|
+
* become a domain failure or a JSON document has to reach a `jsonb` column.
|
|
9
|
+
*/
|
|
10
|
+
export { MigrationLockBusyError } from './failures.js';
|
|
11
|
+
export { jsonbValue } from './jsonb-value.js';
|
|
12
|
+
export { MigrationPgClientLive, PgDocumentNumberCounterLive, PgIntegrationEventOutboxLive, PgProcessedEventRegistryLive, PgReadinessProbeLive, PgTransactionBoundaryLive, PostgresClientLive, type PostgresPorts, PostgresPortsLive, PostgresPortsOnBunLive, } from './layer.js';
|
|
13
|
+
export { type AdvisoryLock, holdingAdvisoryLock, postgresAdvisoryLock, } from './migrations/advisory-lock.js';
|
|
14
|
+
export { applyMigrations, MACHINERY_MIGRATIONS_DIRECTORY, MIGRATION_LOCK_NAME, migrationLockNameFor, reportApplied, } from './migrations/runner.js';
|
|
15
|
+
export { READINESS_QUERY_DEADLINE_MILLIS, READINESS_REASON_WHEN_THE_DATABASE_STAYS_SILENT, READINESS_REASON_WHEN_THE_QUERY_TIMES_OUT, readinessProbeAsking, } from './readiness-probe.js';
|
|
16
|
+
export { configProviderPreferring, DEFAULT_APPLICATION_NAME, DEFAULT_AUTH_MODE, DEFAULT_POSTGRES_PORT, DEFAULT_POSTGRES_SCHEMA, DEFAULT_POSTGRES_SSL_MODE, ENVIRONMENT, POSTGRES_SSL_MODES, type PostgresAuthentication, type PostgresConnectionSettings, type PostgresSslMode, postgresConnectionSettings, schemaDefaultingTo, } from './settings.js';
|
|
17
|
+
export { isUniqueViolation } from './sql-failure.js';
|
|
18
|
+
export { firstRow, onUniqueViolation } from './store-failure.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry to the postgres mechanism: every port this machinery defines,
|
|
3
|
+
* bound to a real database. It gives you every port this machinery defines,
|
|
4
|
+
* bound to a real database — the transaction boundary, the exactly-once claim,
|
|
5
|
+
* the outbox, the document number counter and the readiness probe — with the
|
|
6
|
+
* connection read from the environment, a migration runner that holds an
|
|
7
|
+
* advisory lock, and the helpers a store reaches for when a SQL failure has to
|
|
8
|
+
* become a domain failure or a JSON document has to reach a `jsonb` column.
|
|
9
|
+
*/
|
|
10
|
+
export { MigrationLockBusyError } from './failures.js';
|
|
11
|
+
export { jsonbValue } from './jsonb-value.js';
|
|
12
|
+
export { MigrationPgClientLive, PgDocumentNumberCounterLive, PgIntegrationEventOutboxLive, PgProcessedEventRegistryLive, PgReadinessProbeLive, PgTransactionBoundaryLive, PostgresClientLive, PostgresPortsLive, PostgresPortsOnBunLive, } from './layer.js';
|
|
13
|
+
export { holdingAdvisoryLock, postgresAdvisoryLock, } from './migrations/advisory-lock.js';
|
|
14
|
+
export { applyMigrations, MACHINERY_MIGRATIONS_DIRECTORY, MIGRATION_LOCK_NAME, migrationLockNameFor, reportApplied, } from './migrations/runner.js';
|
|
15
|
+
export { READINESS_QUERY_DEADLINE_MILLIS, READINESS_REASON_WHEN_THE_DATABASE_STAYS_SILENT, READINESS_REASON_WHEN_THE_QUERY_TIMES_OUT, readinessProbeAsking, } from './readiness-probe.js';
|
|
16
|
+
export { configProviderPreferring, DEFAULT_APPLICATION_NAME, DEFAULT_AUTH_MODE, DEFAULT_POSTGRES_PORT, DEFAULT_POSTGRES_SCHEMA, DEFAULT_POSTGRES_SSL_MODE, ENVIRONMENT, POSTGRES_SSL_MODES, postgresConnectionSettings, schemaDefaultingTo, } from './settings.js';
|
|
17
|
+
export { isUniqueViolation } from './sql-failure.js';
|
|
18
|
+
export { firstRow, onUniqueViolation } from './store-failure.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PgClient } from '@effect/sql-pg';
|
|
2
|
+
import { IntegrationEventOutboxUnavailableError, type OutboxMessage } from '@satorio/machinery/ports';
|
|
3
|
+
import { Effect } from 'effect';
|
|
4
|
+
/**
|
|
5
|
+
* Builds the outbox on the service's own pool: an insert that ignores a
|
|
6
|
+
* repeat, a read of what is still undelivered, and a stamp that marks a
|
|
7
|
+
* batch handed on.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export declare const makePgIntegrationEventOutbox: Effect.Effect<{
|
|
12
|
+
enqueue: (messages: readonly OutboxMessage[]) => Effect.Effect<void, IntegrationEventOutboxUnavailableError>;
|
|
13
|
+
pending: (limit: number) => Effect.Effect<readonly OutboxMessage[], IntegrationEventOutboxUnavailableError>;
|
|
14
|
+
markDispatched: (messageIds: readonly string[]) => Effect.Effect<void, IntegrationEventOutboxUnavailableError>;
|
|
15
|
+
}, never, PgClient.PgClient>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { PgClient } from '@effect/sql-pg';
|
|
2
|
+
import { IntegrationEventOutboxUnavailableError, portUnavailable, } from '@satorio/machinery/ports';
|
|
3
|
+
import { DateTime, Effect } from 'effect';
|
|
4
|
+
import { jsonbValue } from './jsonb-value.js';
|
|
5
|
+
const outboxMessageOf = (row) => ({
|
|
6
|
+
messageId: row.messageId,
|
|
7
|
+
eventType: row.eventType,
|
|
8
|
+
schemaVersion: row.schemaVersion,
|
|
9
|
+
occurredAt: DateTime.unsafeMake(row.occurredAt),
|
|
10
|
+
payload: row.payload,
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Builds the outbox on the service's own pool: an insert that ignores a
|
|
14
|
+
* repeat, a read of what is still undelivered, and a stamp that marks a
|
|
15
|
+
* batch handed on.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export const makePgIntegrationEventOutbox = Effect.gen(function* () {
|
|
20
|
+
const sql = yield* PgClient.PgClient;
|
|
21
|
+
const unavailable = portUnavailable(IntegrationEventOutboxUnavailableError);
|
|
22
|
+
const enqueue = (messages) => Effect.forEach(messages, (message) => sql `
|
|
23
|
+
INSERT INTO integration_event_outbox
|
|
24
|
+
(message_id, event_type, schema_version, occurred_at, payload)
|
|
25
|
+
VALUES
|
|
26
|
+
(${message.messageId}::text,
|
|
27
|
+
${message.eventType}::text,
|
|
28
|
+
${message.schemaVersion}::integer,
|
|
29
|
+
${DateTime.toDate(message.occurredAt)}::timestamptz,
|
|
30
|
+
${jsonbValue(sql, message.payload)})
|
|
31
|
+
ON CONFLICT (message_id) DO NOTHING
|
|
32
|
+
`, { discard: true }).pipe(Effect.catchTag('SqlError', unavailable('enqueue')));
|
|
33
|
+
const pending = (limit) => sql `
|
|
34
|
+
SELECT message_id AS "messageId",
|
|
35
|
+
event_type AS "eventType",
|
|
36
|
+
schema_version AS "schemaVersion",
|
|
37
|
+
occurred_at AS "occurredAt",
|
|
38
|
+
payload
|
|
39
|
+
FROM integration_event_outbox
|
|
40
|
+
WHERE dispatched_at IS NULL
|
|
41
|
+
ORDER BY sequence_number ASC
|
|
42
|
+
LIMIT ${limit}::integer
|
|
43
|
+
`.pipe(Effect.map((rows) => rows.map(outboxMessageOf)), Effect.catchTag('SqlError', unavailable('pending')));
|
|
44
|
+
const markDispatched = (messageIds) => Effect.gen(function* () {
|
|
45
|
+
if (messageIds.length === 0)
|
|
46
|
+
return;
|
|
47
|
+
const dispatchedAt = yield* DateTime.now;
|
|
48
|
+
yield* sql `
|
|
49
|
+
UPDATE integration_event_outbox
|
|
50
|
+
SET dispatched_at = ${DateTime.toDate(dispatchedAt)}::timestamptz
|
|
51
|
+
WHERE message_id IN ${sql.in(messageIds)}
|
|
52
|
+
AND dispatched_at IS NULL
|
|
53
|
+
`;
|
|
54
|
+
}).pipe(Effect.catchTag('SqlError', unavailable('markDispatched')));
|
|
55
|
+
return {
|
|
56
|
+
enqueue,
|
|
57
|
+
pending,
|
|
58
|
+
markDispatched,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SqlClient } from '@effect/sql';
|
|
2
|
+
import type { Fragment } from '@effect/sql/Statement';
|
|
3
|
+
/**
|
|
4
|
+
* Hands a value to a `jsonb` column as JSON, whatever its shape. Write
|
|
5
|
+
* it wherever a query holds a JSON parameter, and the parameter travels
|
|
6
|
+
* as text the database casts, so an array stays an array instead of
|
|
7
|
+
* becoming the driver's own array literal. A value the encoder cannot
|
|
8
|
+
* write at all, `undefined` among them, lands as JSON null.
|
|
9
|
+
*/
|
|
10
|
+
export declare const jsonbValue: (sql: SqlClient.SqlClient, value: unknown) => Fragment;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const jsonTextOf = (value) => JSON.stringify(value) ?? 'null';
|
|
2
|
+
/**
|
|
3
|
+
* Hands a value to a `jsonb` column as JSON, whatever its shape. Write
|
|
4
|
+
* it wherever a query holds a JSON parameter, and the parameter travels
|
|
5
|
+
* as text the database casts, so an array stays an array instead of
|
|
6
|
+
* becoming the driver's own array literal. A value the encoder cannot
|
|
7
|
+
* write at all, `undefined` among them, lands as JSON null.
|
|
8
|
+
*/
|
|
9
|
+
export const jsonbValue = (sql, value) => sql `${jsonTextOf(value)}::jsonb`;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { FileSystem } from '@effect/platform';
|
|
2
|
+
import type { PlatformError } from '@effect/platform/Error';
|
|
3
|
+
import { SqlClient } from '@effect/sql';
|
|
4
|
+
import type { SqlError } from '@effect/sql/SqlError';
|
|
5
|
+
import { PgClient } from '@effect/sql-pg';
|
|
6
|
+
import { DocumentNumberCounter, IntegrationEventOutbox, ProcessedEventRegistry, ReadinessProbe, TransactionBoundary } from '@satorio/machinery/ports';
|
|
7
|
+
import { Layer } from 'effect';
|
|
8
|
+
import type { ConfigError } from 'effect/ConfigError';
|
|
9
|
+
/**
|
|
10
|
+
* The `@effect/sql-pg` client every other layer here builds on, opened
|
|
11
|
+
* from the environment `settings.ts` names.
|
|
12
|
+
*/
|
|
13
|
+
export declare const PostgresClientLive: Layer.Layer<PgClient.PgClient | SqlClient.SqlClient, ConfigError | PlatformError | SqlError, FileSystem.FileSystem>;
|
|
14
|
+
/**
|
|
15
|
+
* The client the migration runner uses: one connection, under its own name.
|
|
16
|
+
*
|
|
17
|
+
* The same client for the migration runner: one connection, and its own name
|
|
18
|
+
* in `pg_stat_activity`, so a long migration is easy to spot next to the
|
|
19
|
+
* service's own pool.
|
|
20
|
+
*/
|
|
21
|
+
export declare const MigrationPgClientLive: Layer.Layer<PgClient.PgClient | SqlClient.SqlClient, ConfigError | PlatformError | SqlError, FileSystem.FileSystem>;
|
|
22
|
+
/**
|
|
23
|
+
* The transaction boundary on a real Postgres transaction.
|
|
24
|
+
*/
|
|
25
|
+
export declare const PgTransactionBoundaryLive: Layer.Layer<TransactionBoundary, never, SqlClient.SqlClient>;
|
|
26
|
+
/**
|
|
27
|
+
* The exactly-once claim on a real `processed_event` table.
|
|
28
|
+
*/
|
|
29
|
+
export declare const PgProcessedEventRegistryLive: Layer.Layer<ProcessedEventRegistry, never, PgClient.PgClient>;
|
|
30
|
+
/**
|
|
31
|
+
* The document number counter on a real `document_number_counter` table.
|
|
32
|
+
*/
|
|
33
|
+
export declare const PgDocumentNumberCounterLive: Layer.Layer<DocumentNumberCounter, never, PgClient.PgClient>;
|
|
34
|
+
/**
|
|
35
|
+
* The outbox on a real `integration_event_outbox` table.
|
|
36
|
+
*/
|
|
37
|
+
export declare const PgIntegrationEventOutboxLive: Layer.Layer<IntegrationEventOutbox, never, PgClient.PgClient>;
|
|
38
|
+
/**
|
|
39
|
+
* The readiness probe on one `SELECT 1` against the service's own pool.
|
|
40
|
+
*/
|
|
41
|
+
export declare const PgReadinessProbeLive: Layer.Layer<ReadinessProbe, never, SqlClient.SqlClient>;
|
|
42
|
+
/**
|
|
43
|
+
* The ports this mechanism implements, named together as one type. Your
|
|
44
|
+
* service adds its own stores alongside these in the composition root, on
|
|
45
|
+
* the same database client.
|
|
46
|
+
*/
|
|
47
|
+
export type PostgresPorts = ProcessedEventRegistry | IntegrationEventOutbox | DocumentNumberCounter | TransactionBoundary | ReadinessProbe;
|
|
48
|
+
/**
|
|
49
|
+
* Every port this mechanism implements, on one client, for a service
|
|
50
|
+
* that supplies its own `FileSystem`.
|
|
51
|
+
*/
|
|
52
|
+
export declare const PostgresPortsLive: Layer.Layer<PostgresPorts | PgClient.PgClient | SqlClient.SqlClient, ConfigError | PlatformError | SqlError, FileSystem.FileSystem>;
|
|
53
|
+
/**
|
|
54
|
+
* The same bundle on Bun's own file system, which is the whole Postgres
|
|
55
|
+
* side of the machinery in one layer.
|
|
56
|
+
*/
|
|
57
|
+
export declare const PostgresPortsOnBunLive: Layer.Layer<PostgresPorts | PgClient.PgClient | SqlClient.SqlClient, ConfigError | PlatformError | SqlError>;
|