@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,28 @@
|
|
|
1
|
+
import { EventPublisher, type EventPublisherShape, type OutboxMessage } from '@satorio/machinery/ports';
|
|
2
|
+
import { Effect, Layer, Ref } from 'effect';
|
|
3
|
+
/**
|
|
4
|
+
* What an in-memory publisher holds: every message it was handed, in the
|
|
5
|
+
* order the batches arrived. A scenario keeps the `Ref` and reads this
|
|
6
|
+
* list to see what a procedure published.
|
|
7
|
+
*/
|
|
8
|
+
export type PublishedMessages = readonly OutboxMessage[];
|
|
9
|
+
/**
|
|
10
|
+
* An in-memory publisher over a `Ref` you keep, so a test reads what was
|
|
11
|
+
* published after the run.
|
|
12
|
+
*/
|
|
13
|
+
export declare const makeInMemoryEventPublisherWith: (published: Ref.Ref<PublishedMessages>) => EventPublisherShape;
|
|
14
|
+
/**
|
|
15
|
+
* An in-memory publisher over a `Ref` of its own, empty at the start.
|
|
16
|
+
*/
|
|
17
|
+
export declare const makeInMemoryEventPublisher: Effect.Effect<EventPublisherShape, never, never>;
|
|
18
|
+
/**
|
|
19
|
+
* The in-memory publisher, mounted.
|
|
20
|
+
*/
|
|
21
|
+
export declare const InMemoryEventPublisherLive: Layer.Layer<EventPublisher, never, never>;
|
|
22
|
+
/**
|
|
23
|
+
* A publisher that takes nothing: every publish fails with
|
|
24
|
+
* `EventPublisherUnavailableError`. Mount it to walk the branch a
|
|
25
|
+
* working stand-in never reaches — the outbox relay that has to leave
|
|
26
|
+
* its messages where they are.
|
|
27
|
+
*/
|
|
28
|
+
export declare const EventPublisherUnavailableLive: Layer.Layer<EventPublisher>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { EventPublisher, EventPublisherUnavailableError, } from '@satorio/machinery/ports';
|
|
2
|
+
import { Effect, Layer, Ref } from 'effect';
|
|
3
|
+
const NOT_MOUNTED = 'event publisher unavailable (test)';
|
|
4
|
+
/**
|
|
5
|
+
* An in-memory publisher over a `Ref` you keep, so a test reads what was
|
|
6
|
+
* published after the run.
|
|
7
|
+
*/
|
|
8
|
+
export const makeInMemoryEventPublisherWith = (published) => ({
|
|
9
|
+
publish: (messages) => Ref.update(published, (current) => [...current, ...messages]),
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* An in-memory publisher over a `Ref` of its own, empty at the start.
|
|
13
|
+
*/
|
|
14
|
+
export const makeInMemoryEventPublisher = Effect.gen(function* () {
|
|
15
|
+
const published = yield* Ref.make([]);
|
|
16
|
+
return makeInMemoryEventPublisherWith(published);
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* The in-memory publisher, mounted.
|
|
20
|
+
*/
|
|
21
|
+
export const InMemoryEventPublisherLive = Layer.effect(EventPublisher, makeInMemoryEventPublisher);
|
|
22
|
+
/**
|
|
23
|
+
* A publisher that takes nothing: every publish fails with
|
|
24
|
+
* `EventPublisherUnavailableError`. Mount it to walk the branch a
|
|
25
|
+
* working stand-in never reaches — the outbox relay that has to leave
|
|
26
|
+
* its messages where they are.
|
|
27
|
+
*/
|
|
28
|
+
export const EventPublisherUnavailableLive = Layer.succeed(EventPublisher, {
|
|
29
|
+
publish: () => Effect.fail(new EventPublisherUnavailableError({
|
|
30
|
+
operation: 'publish',
|
|
31
|
+
cause: NOT_MOUNTED,
|
|
32
|
+
})),
|
|
33
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ExternalHttpClient, type ExternalHttpClientShape } from '@satorio/machinery/ports';
|
|
2
|
+
import { Effect, Layer, Ref } from 'effect';
|
|
3
|
+
/**
|
|
4
|
+
* What an in-memory client answers with: one already-decoded body per
|
|
5
|
+
* path. A path nobody wrote an answer for is a 404, the way a real
|
|
6
|
+
* service refuses a URL it does not serve.
|
|
7
|
+
*/
|
|
8
|
+
export type AnswersByPath = Readonly<Record<string, unknown>>;
|
|
9
|
+
/**
|
|
10
|
+
* An in-memory client over a `Ref` you keep, so a test rewrites what the
|
|
11
|
+
* service answers between two calls of the same scenario.
|
|
12
|
+
*/
|
|
13
|
+
export declare const makeInMemoryExternalHttpClientWith: (answers: Ref.Ref<AnswersByPath>) => ExternalHttpClientShape;
|
|
14
|
+
/**
|
|
15
|
+
* An in-memory client over a `Ref` of its own, seeded with the answers
|
|
16
|
+
* you hand it.
|
|
17
|
+
*/
|
|
18
|
+
export declare const makeInMemoryExternalHttpClient: (answers: AnswersByPath) => Effect.Effect<ExternalHttpClientShape>;
|
|
19
|
+
/**
|
|
20
|
+
* The in-memory client, mounted, answering the paths you seeded it with.
|
|
21
|
+
*/
|
|
22
|
+
export declare const inMemoryExternalHttpClientLive: (answers: AnswersByPath) => Layer.Layer<ExternalHttpClient>;
|
|
23
|
+
/**
|
|
24
|
+
* A client that reaches nothing: every call fails with
|
|
25
|
+
* `ExternalServiceUnavailableError`. Mount it to walk the branch a
|
|
26
|
+
* working stand-in never reaches — the use case that has to carry on
|
|
27
|
+
* without the reference service it wanted.
|
|
28
|
+
*/
|
|
29
|
+
export declare const ExternalHttpClientUnavailableLive: Layer.Layer<ExternalHttpClient>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ExternalHttpClient, ExternalServiceRefusedError, ExternalServiceResponseMalformedError, ExternalServiceUnavailableError, } from '@satorio/machinery/ports';
|
|
2
|
+
import { Effect, Layer, Ref, Schema } from 'effect';
|
|
3
|
+
const NOT_MOUNTED = 'external http client unavailable (test)';
|
|
4
|
+
const IN_MEMORY_SERVICE = 'in-memory';
|
|
5
|
+
const GET_JSON = 'getJson';
|
|
6
|
+
const NOT_ANSWERED_STATUS = 404;
|
|
7
|
+
/**
|
|
8
|
+
* An in-memory client over a `Ref` you keep, so a test rewrites what the
|
|
9
|
+
* service answers between two calls of the same scenario.
|
|
10
|
+
*/
|
|
11
|
+
export const makeInMemoryExternalHttpClientWith = (answers) => ({
|
|
12
|
+
getJson: (path, schema) => Effect.gen(function* () {
|
|
13
|
+
const answered = yield* Ref.get(answers);
|
|
14
|
+
if (!Object.hasOwn(answered, path)) {
|
|
15
|
+
return yield* Effect.fail(new ExternalServiceRefusedError({
|
|
16
|
+
service: IN_MEMORY_SERVICE,
|
|
17
|
+
operation: GET_JSON,
|
|
18
|
+
status: NOT_ANSWERED_STATUS,
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
return yield* Schema.decodeUnknown(schema)(answered[path]).pipe(Effect.mapError((cause) => new ExternalServiceResponseMalformedError({
|
|
22
|
+
service: IN_MEMORY_SERVICE,
|
|
23
|
+
operation: GET_JSON,
|
|
24
|
+
cause,
|
|
25
|
+
})));
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* An in-memory client over a `Ref` of its own, seeded with the answers
|
|
30
|
+
* you hand it.
|
|
31
|
+
*/
|
|
32
|
+
export const makeInMemoryExternalHttpClient = (answers) => Effect.map(Ref.make(answers), makeInMemoryExternalHttpClientWith);
|
|
33
|
+
/**
|
|
34
|
+
* The in-memory client, mounted, answering the paths you seeded it with.
|
|
35
|
+
*/
|
|
36
|
+
export const inMemoryExternalHttpClientLive = (answers) => Layer.effect(ExternalHttpClient, makeInMemoryExternalHttpClient(answers));
|
|
37
|
+
/**
|
|
38
|
+
* A client that reaches nothing: every call fails with
|
|
39
|
+
* `ExternalServiceUnavailableError`. Mount it to walk the branch a
|
|
40
|
+
* working stand-in never reaches — the use case that has to carry on
|
|
41
|
+
* without the reference service it wanted.
|
|
42
|
+
*/
|
|
43
|
+
export const ExternalHttpClientUnavailableLive = Layer.succeed(ExternalHttpClient, {
|
|
44
|
+
getJson: () => Effect.fail(new ExternalServiceUnavailableError({
|
|
45
|
+
service: IN_MEMORY_SERVICE,
|
|
46
|
+
operation: GET_JSON,
|
|
47
|
+
cause: NOT_MOUNTED,
|
|
48
|
+
})),
|
|
49
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry to the testing mechanism: a stand-in for every port, a clock a
|
|
3
|
+
* test drives, and the scenario player. It gives you an in-memory stand-in and
|
|
4
|
+
* a deliberately unavailable one for every port, a clock a test drives, the
|
|
5
|
+
* scenario player, the small readers a test uses to look inside what it just
|
|
6
|
+
* ran, and the settings a test runner and a mutation runner read before either
|
|
7
|
+
* runtime exists. No production file imports any of it.
|
|
8
|
+
*/
|
|
9
|
+
export { BlobStoreUnavailableLive, type BlobsByKey, InMemoryBlobStoreLive, makeInMemoryBlobStore, makeInMemoryBlobStoreWith, } from './blob-store.js';
|
|
10
|
+
export { advanceBy, atTheMoment, clockPinnedAt, nowAsDateTime, nowAsIsoString, setNowTo, } from './clock.js';
|
|
11
|
+
export { type DocumentCountsByClientAndMonth, DocumentNumberCounterUnavailableLive, InMemoryDocumentNumberCounterLive, makeInMemoryDocumentNumberCounter, makeInMemoryDocumentNumberCounterWith, } from './document-number-counter.js';
|
|
12
|
+
export { EventPublisherUnavailableLive, InMemoryEventPublisherLive, makeInMemoryEventPublisher, makeInMemoryEventPublisherWith, type PublishedMessages, } from './event-publisher.js';
|
|
13
|
+
export { type AnswersByPath, ExternalHttpClientUnavailableLive, inMemoryExternalHttpClientLive, makeInMemoryExternalHttpClient, makeInMemoryExternalHttpClientWith, } from './external-http-client.js';
|
|
14
|
+
export { type DispatchedMessageIds, InMemoryIntegrationEventOutboxLive, IntegrationEventOutboxUnavailableLive, IntegrationEventOutboxUnreadableLive, makeInMemoryIntegrationEventOutbox, makeInMemoryIntegrationEventOutboxWith, type QueuedOutboxMessages, } from './integration-event-outbox.js';
|
|
15
|
+
export { mutationSettings, SUITES_THAT_START_A_STAND, } from './mutation-settings.js';
|
|
16
|
+
export { distinctMomentsIn, emptiedTables, } from './postgres-rows.js';
|
|
17
|
+
export { InMemoryProcessedEventRegistryLive, makeInMemoryProcessedEventRegistry, makeInMemoryProcessedEventRegistryWith, ProcessedEventRegistryUnavailableLive, type ProcessedEventsById, } from './processed-event-registry.js';
|
|
18
|
+
export { InMemoryQueueMessageProducerLive, makeInMemoryQueueMessageProducer, makeInMemoryQueueMessageProducerWith, QueueMessageProducerUnavailableLive, } from './queue-message-producer.js';
|
|
19
|
+
export { InMemoryQueueMessageSourceLive, makeInMemoryQueueMessageSource, makeInMemoryQueueMessageSourceWith, makeScriptedMessageSource, messageOf, type PendingQueueMessages, QueueMessageSourceUnavailableLive, scriptedQueueMessageSourceLive, } from './queue-message-source.js';
|
|
20
|
+
export { QuietReporter, quietReporters, verboseOutputWanted, } from './quiet-reporter.js';
|
|
21
|
+
export { InMemoryReadinessProbeLive, ReadinessProbeUnavailableLive, } from './readiness-probe.js';
|
|
22
|
+
export { contextOf, ensureStepsAreInOrder, firstPassLabelsOf, labelOf, labelOfFailure, momentsOfSteps, playScenario, replayLabelsOf, type Scenario, type ScenarioPlayback, type ScenarioService, type ScenarioStep, } from './scenario.js';
|
|
23
|
+
export { inMemoryTransactionBoundaryLive, makeInMemoryTransactionBoundary, stateOfRef, type TransactionalState, TransactionBoundaryUnavailableLive, } from './transaction-boundary.js';
|
|
24
|
+
export { typedFailureOf } from './typed-failure.js';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry to the testing mechanism: a stand-in for every port, a clock a
|
|
3
|
+
* test drives, and the scenario player. It gives you an in-memory stand-in and
|
|
4
|
+
* a deliberately unavailable one for every port, a clock a test drives, the
|
|
5
|
+
* scenario player, the small readers a test uses to look inside what it just
|
|
6
|
+
* ran, and the settings a test runner and a mutation runner read before either
|
|
7
|
+
* runtime exists. No production file imports any of it.
|
|
8
|
+
*/
|
|
9
|
+
export { BlobStoreUnavailableLive, InMemoryBlobStoreLive, makeInMemoryBlobStore, makeInMemoryBlobStoreWith, } from './blob-store.js';
|
|
10
|
+
export { advanceBy, atTheMoment, clockPinnedAt, nowAsDateTime, nowAsIsoString, setNowTo, } from './clock.js';
|
|
11
|
+
export { DocumentNumberCounterUnavailableLive, InMemoryDocumentNumberCounterLive, makeInMemoryDocumentNumberCounter, makeInMemoryDocumentNumberCounterWith, } from './document-number-counter.js';
|
|
12
|
+
export { EventPublisherUnavailableLive, InMemoryEventPublisherLive, makeInMemoryEventPublisher, makeInMemoryEventPublisherWith, } from './event-publisher.js';
|
|
13
|
+
export { ExternalHttpClientUnavailableLive, inMemoryExternalHttpClientLive, makeInMemoryExternalHttpClient, makeInMemoryExternalHttpClientWith, } from './external-http-client.js';
|
|
14
|
+
export { InMemoryIntegrationEventOutboxLive, IntegrationEventOutboxUnavailableLive, IntegrationEventOutboxUnreadableLive, makeInMemoryIntegrationEventOutbox, makeInMemoryIntegrationEventOutboxWith, } from './integration-event-outbox.js';
|
|
15
|
+
export { mutationSettings, SUITES_THAT_START_A_STAND, } from './mutation-settings.js';
|
|
16
|
+
export { distinctMomentsIn, emptiedTables, } from './postgres-rows.js';
|
|
17
|
+
export { InMemoryProcessedEventRegistryLive, makeInMemoryProcessedEventRegistry, makeInMemoryProcessedEventRegistryWith, ProcessedEventRegistryUnavailableLive, } from './processed-event-registry.js';
|
|
18
|
+
export { InMemoryQueueMessageProducerLive, makeInMemoryQueueMessageProducer, makeInMemoryQueueMessageProducerWith, QueueMessageProducerUnavailableLive, } from './queue-message-producer.js';
|
|
19
|
+
export { InMemoryQueueMessageSourceLive, makeInMemoryQueueMessageSource, makeInMemoryQueueMessageSourceWith, makeScriptedMessageSource, messageOf, QueueMessageSourceUnavailableLive, scriptedQueueMessageSourceLive, } from './queue-message-source.js';
|
|
20
|
+
export { QuietReporter, quietReporters, verboseOutputWanted, } from './quiet-reporter.js';
|
|
21
|
+
export { InMemoryReadinessProbeLive, ReadinessProbeUnavailableLive, } from './readiness-probe.js';
|
|
22
|
+
export { contextOf, ensureStepsAreInOrder, firstPassLabelsOf, labelOf, labelOfFailure, momentsOfSteps, playScenario, replayLabelsOf, } from './scenario.js';
|
|
23
|
+
export { inMemoryTransactionBoundaryLive, makeInMemoryTransactionBoundary, stateOfRef, TransactionBoundaryUnavailableLive, } from './transaction-boundary.js';
|
|
24
|
+
export { typedFailureOf } from './typed-failure.js';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { IntegrationEventOutbox, type IntegrationEventOutboxShape, type OutboxMessage } from '@satorio/machinery/ports';
|
|
2
|
+
import { Effect, Layer, Ref } from 'effect';
|
|
3
|
+
/**
|
|
4
|
+
* What an in-memory outbox holds on the write side: every message
|
|
5
|
+
* enqueued, in the order it arrived.
|
|
6
|
+
*/
|
|
7
|
+
export type QueuedOutboxMessages = readonly OutboxMessage[];
|
|
8
|
+
/**
|
|
9
|
+
* What an in-memory outbox holds on the read side: the ids a relay has
|
|
10
|
+
* already marked as sent.
|
|
11
|
+
*/
|
|
12
|
+
export type DispatchedMessageIds = ReadonlySet<string>;
|
|
13
|
+
/**
|
|
14
|
+
* An in-memory outbox over two `Ref`s you keep, so a test can seed what
|
|
15
|
+
* is waiting and read back what the relay dispatched.
|
|
16
|
+
*/
|
|
17
|
+
export declare const makeInMemoryIntegrationEventOutboxWith: (queued: Ref.Ref<QueuedOutboxMessages>, dispatched: Ref.Ref<DispatchedMessageIds>) => IntegrationEventOutboxShape;
|
|
18
|
+
/**
|
|
19
|
+
* An in-memory outbox over `Ref`s of its own, empty at the start.
|
|
20
|
+
*/
|
|
21
|
+
export declare const makeInMemoryIntegrationEventOutbox: Effect.Effect<IntegrationEventOutboxShape, never, never>;
|
|
22
|
+
/**
|
|
23
|
+
* The in-memory outbox, mounted.
|
|
24
|
+
*/
|
|
25
|
+
export declare const InMemoryIntegrationEventOutboxLive: Layer.Layer<IntegrationEventOutbox, never, never>;
|
|
26
|
+
/**
|
|
27
|
+
* An outbox that accepts nothing: `enqueue` refuses, so a use case's
|
|
28
|
+
* write and its message go down together. Mount it to prove a caller
|
|
29
|
+
* that records an event keeps the refusal instead of losing it.
|
|
30
|
+
*/
|
|
31
|
+
export declare const IntegrationEventOutboxUnavailableLive: Layer.Layer<IntegrationEventOutbox>;
|
|
32
|
+
/**
|
|
33
|
+
* An outbox that takes messages and then refuses to hand them back:
|
|
34
|
+
* `pending` fails while `enqueue` succeeds. Mount it to prove a relay
|
|
35
|
+
* reports a read it could not make instead of reporting an empty beat.
|
|
36
|
+
*/
|
|
37
|
+
export declare const IntegrationEventOutboxUnreadableLive: Layer.Layer<IntegrationEventOutbox>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { IntegrationEventOutbox, IntegrationEventOutboxUnavailableError, } from '@satorio/machinery/ports';
|
|
2
|
+
import { Effect, Layer, Ref } from 'effect';
|
|
3
|
+
const NOT_MOUNTED = 'integration event outbox unavailable (test)';
|
|
4
|
+
/**
|
|
5
|
+
* An in-memory outbox over two `Ref`s you keep, so a test can seed what
|
|
6
|
+
* is waiting and read back what the relay dispatched.
|
|
7
|
+
*/
|
|
8
|
+
export const makeInMemoryIntegrationEventOutboxWith = (queued, dispatched) => {
|
|
9
|
+
const enqueue = (messages) => Ref.update(queued, (current) => {
|
|
10
|
+
const knownIds = new Set(current.map((message) => message.messageId));
|
|
11
|
+
const firstAppearance = messages.filter((message) => !knownIds.has(message.messageId));
|
|
12
|
+
return firstAppearance.length === 0
|
|
13
|
+
? current
|
|
14
|
+
: [...current, ...firstAppearance];
|
|
15
|
+
});
|
|
16
|
+
const pending = (limit) => Effect.gen(function* () {
|
|
17
|
+
const current = yield* Ref.get(queued);
|
|
18
|
+
const alreadySent = yield* Ref.get(dispatched);
|
|
19
|
+
return current
|
|
20
|
+
.filter((message) => !alreadySent.has(message.messageId))
|
|
21
|
+
.slice(0, limit);
|
|
22
|
+
});
|
|
23
|
+
const markDispatched = (messageIds) => Ref.update(dispatched, (current) => new Set([...current, ...messageIds]));
|
|
24
|
+
return {
|
|
25
|
+
enqueue,
|
|
26
|
+
pending,
|
|
27
|
+
markDispatched,
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* An in-memory outbox over `Ref`s of its own, empty at the start.
|
|
32
|
+
*/
|
|
33
|
+
export const makeInMemoryIntegrationEventOutbox = Effect.gen(function* () {
|
|
34
|
+
const queued = yield* Ref.make([]);
|
|
35
|
+
const dispatched = yield* Ref.make(new Set());
|
|
36
|
+
return makeInMemoryIntegrationEventOutboxWith(queued, dispatched);
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* The in-memory outbox, mounted.
|
|
40
|
+
*/
|
|
41
|
+
export const InMemoryIntegrationEventOutboxLive = Layer.effect(IntegrationEventOutbox, makeInMemoryIntegrationEventOutbox);
|
|
42
|
+
/**
|
|
43
|
+
* An outbox that accepts nothing: `enqueue` refuses, so a use case's
|
|
44
|
+
* write and its message go down together. Mount it to prove a caller
|
|
45
|
+
* that records an event keeps the refusal instead of losing it.
|
|
46
|
+
*/
|
|
47
|
+
export const IntegrationEventOutboxUnavailableLive = Layer.succeed(IntegrationEventOutbox, {
|
|
48
|
+
enqueue: () => new IntegrationEventOutboxUnavailableError({
|
|
49
|
+
operation: 'enqueue',
|
|
50
|
+
cause: NOT_MOUNTED,
|
|
51
|
+
}),
|
|
52
|
+
pending: () => Effect.succeed([]),
|
|
53
|
+
markDispatched: () => Effect.void,
|
|
54
|
+
});
|
|
55
|
+
/**
|
|
56
|
+
* An outbox that takes messages and then refuses to hand them back:
|
|
57
|
+
* `pending` fails while `enqueue` succeeds. Mount it to prove a relay
|
|
58
|
+
* reports a read it could not make instead of reporting an empty beat.
|
|
59
|
+
*/
|
|
60
|
+
export const IntegrationEventOutboxUnreadableLive = Layer.succeed(IntegrationEventOutbox, {
|
|
61
|
+
enqueue: () => Effect.void,
|
|
62
|
+
pending: () => new IntegrationEventOutboxUnavailableError({
|
|
63
|
+
operation: 'pending',
|
|
64
|
+
cause: NOT_MOUNTED,
|
|
65
|
+
}),
|
|
66
|
+
markDispatched: () => Effect.void,
|
|
67
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The suites that start a live stand, by the file name A11 gives them.
|
|
3
|
+
* A mutation run never wants these: every mutant would start its own
|
|
4
|
+
* database or its own AWS stand, and a container that never comes up
|
|
5
|
+
* looks exactly like a mutant that never returns.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SUITES_THAT_START_A_STAND: readonly string[];
|
|
8
|
+
/**
|
|
9
|
+
* What a service tells the mutation runner about itself: which files to
|
|
10
|
+
* mutate, which test-runner configuration the mutant runs against, and
|
|
11
|
+
* the two numbers a bigger machine may raise.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export interface MutationSettingsOptions {
|
|
16
|
+
readonly mutate: readonly string[];
|
|
17
|
+
readonly vitestConfigFile: string;
|
|
18
|
+
readonly tsconfigFile?: string;
|
|
19
|
+
readonly concurrency?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The settings of one mutation run, in the shape the runner's own
|
|
23
|
+
* configuration file takes.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export interface MutationSettings {
|
|
28
|
+
readonly testRunner: string;
|
|
29
|
+
readonly plugins: readonly string[];
|
|
30
|
+
readonly vitest: {
|
|
31
|
+
readonly configFile: string;
|
|
32
|
+
};
|
|
33
|
+
readonly checkers: readonly string[];
|
|
34
|
+
readonly tsconfigFile: string;
|
|
35
|
+
readonly mutate: readonly string[];
|
|
36
|
+
readonly reporters: readonly string[];
|
|
37
|
+
readonly thresholds: {
|
|
38
|
+
readonly high: number;
|
|
39
|
+
readonly low: number;
|
|
40
|
+
readonly break: number | null;
|
|
41
|
+
};
|
|
42
|
+
readonly concurrency: number;
|
|
43
|
+
readonly timeoutMS: number;
|
|
44
|
+
readonly timeoutFactor: number;
|
|
45
|
+
readonly dryRunTimeoutMinutes: number;
|
|
46
|
+
readonly tempDirName: string;
|
|
47
|
+
readonly cleanTempDir: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* How many mutants run beside each other when nobody says. The runner
|
|
51
|
+
* starts one type checker and one test runner per unit of concurrency,
|
|
52
|
+
* and each type checker holds a whole TypeScript program in memory, so
|
|
53
|
+
* the default here stays well under the core count a machine reports.
|
|
54
|
+
* Raise it deliberately, on a machine you know has the memory.
|
|
55
|
+
*
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
export declare const defaultConcurrency: (cores: number) => number;
|
|
59
|
+
/**
|
|
60
|
+
* Everything a mutation run needs, with the waits bounded and the live
|
|
61
|
+
* stands kept out. Hand it the files to mutate and the test-runner
|
|
62
|
+
* configuration the mutants run against; take the rest as it comes.
|
|
63
|
+
*
|
|
64
|
+
* Three of these numbers exist because a run that hangs reports no score
|
|
65
|
+
* at all. `timeoutMS` and `timeoutFactor` bound one mutant, so a mutant
|
|
66
|
+
* that turns a loop endless is killed as a timeout rather than waited
|
|
67
|
+
* on. `dryRunTimeoutMinutes` bounds the run that happens before any
|
|
68
|
+
* mutant, where a suite that needs a stand nobody started would
|
|
69
|
+
* otherwise wait for ever.
|
|
70
|
+
*/
|
|
71
|
+
export declare const mutationSettings: (options: MutationSettingsOptions) => MutationSettings;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { availableParallelism } from 'node:os';
|
|
2
|
+
/**
|
|
3
|
+
* The suites that start a live stand, by the file name A11 gives them.
|
|
4
|
+
* A mutation run never wants these: every mutant would start its own
|
|
5
|
+
* database or its own AWS stand, and a container that never comes up
|
|
6
|
+
* looks exactly like a mutant that never returns.
|
|
7
|
+
*/
|
|
8
|
+
export const SUITES_THAT_START_A_STAND = [
|
|
9
|
+
'**/*.postgres.test.ts',
|
|
10
|
+
'**/*.localstack.test.ts',
|
|
11
|
+
];
|
|
12
|
+
const HIGHEST_DEFAULT_CONCURRENCY = 4;
|
|
13
|
+
/**
|
|
14
|
+
* How many mutants run beside each other when nobody says. The runner
|
|
15
|
+
* starts one type checker and one test runner per unit of concurrency,
|
|
16
|
+
* and each type checker holds a whole TypeScript program in memory, so
|
|
17
|
+
* the default here stays well under the core count a machine reports.
|
|
18
|
+
* Raise it deliberately, on a machine you know has the memory.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export const defaultConcurrency = (cores) => Math.max(1, Math.min(HIGHEST_DEFAULT_CONCURRENCY, cores - 1));
|
|
23
|
+
/**
|
|
24
|
+
* Everything a mutation run needs, with the waits bounded and the live
|
|
25
|
+
* stands kept out. Hand it the files to mutate and the test-runner
|
|
26
|
+
* configuration the mutants run against; take the rest as it comes.
|
|
27
|
+
*
|
|
28
|
+
* Three of these numbers exist because a run that hangs reports no score
|
|
29
|
+
* at all. `timeoutMS` and `timeoutFactor` bound one mutant, so a mutant
|
|
30
|
+
* that turns a loop endless is killed as a timeout rather than waited
|
|
31
|
+
* on. `dryRunTimeoutMinutes` bounds the run that happens before any
|
|
32
|
+
* mutant, where a suite that needs a stand nobody started would
|
|
33
|
+
* otherwise wait for ever.
|
|
34
|
+
*/
|
|
35
|
+
export const mutationSettings = (options) => ({
|
|
36
|
+
testRunner: 'vitest',
|
|
37
|
+
plugins: [
|
|
38
|
+
'@stryker-mutator/vitest-runner',
|
|
39
|
+
'@stryker-mutator/typescript-checker',
|
|
40
|
+
],
|
|
41
|
+
vitest: { configFile: options.vitestConfigFile },
|
|
42
|
+
checkers: ['typescript'],
|
|
43
|
+
tsconfigFile: options.tsconfigFile ?? 'tsconfig.json',
|
|
44
|
+
mutate: [
|
|
45
|
+
...options.mutate,
|
|
46
|
+
'!**/*.test.ts',
|
|
47
|
+
...SUITES_THAT_START_A_STAND.map((suite) => `!${suite}`),
|
|
48
|
+
],
|
|
49
|
+
reporters: ['clear-text', 'progress', 'html', 'json'],
|
|
50
|
+
thresholds: { high: 80, low: 60, break: null },
|
|
51
|
+
concurrency: options.concurrency ?? defaultConcurrency(availableParallelism()),
|
|
52
|
+
timeoutMS: 15_000,
|
|
53
|
+
timeoutFactor: 2,
|
|
54
|
+
dryRunTimeoutMinutes: 5,
|
|
55
|
+
tempDirName: '.stryker-tmp',
|
|
56
|
+
cleanTempDir: true,
|
|
57
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { SqlClient } from '@effect/sql';
|
|
2
|
+
import { Effect } from 'effect';
|
|
3
|
+
/**
|
|
4
|
+
* Empties the given tables on the live stand so a scenario starts
|
|
5
|
+
* from zero, the way a freshly reset database does for the seeder.
|
|
6
|
+
*/
|
|
7
|
+
export declare const emptiedTables: (tables: readonly string[]) => Effect.Effect<void, never, SqlClient.SqlClient>;
|
|
8
|
+
/**
|
|
9
|
+
* Reads the distinct moments in one timestamp column, as sorted ISO
|
|
10
|
+
* strings in the same format `momentsOfSteps` returns for a scenario.
|
|
11
|
+
*/
|
|
12
|
+
export declare const distinctMomentsIn: (table: string, column: string) => Effect.Effect<readonly string[], never, SqlClient.SqlClient>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SqlClient } from '@effect/sql';
|
|
2
|
+
import { DateTime, Effect } from 'effect';
|
|
3
|
+
/**
|
|
4
|
+
* Empties the given tables on the live stand so a scenario starts
|
|
5
|
+
* from zero, the way a freshly reset database does for the seeder.
|
|
6
|
+
*/
|
|
7
|
+
export const emptiedTables = (tables) => Effect.flatMap(SqlClient.SqlClient, (sql) => sql.unsafe(`TRUNCATE TABLE ${tables.join(', ')}`)).pipe(Effect.asVoid, Effect.orDie);
|
|
8
|
+
/**
|
|
9
|
+
* Reads the distinct moments in one timestamp column, as sorted ISO
|
|
10
|
+
* strings in the same format `momentsOfSteps` returns for a scenario.
|
|
11
|
+
*/
|
|
12
|
+
export const distinctMomentsIn = (table, column) => Effect.flatMap(SqlClient.SqlClient, (sql) => sql `
|
|
13
|
+
SELECT DISTINCT ${sql.unsafe(column)} AS moment
|
|
14
|
+
FROM ${sql.unsafe(table)}
|
|
15
|
+
`).pipe(Effect.map((rows) => rows
|
|
16
|
+
.flatMap((row) => row.moment === null
|
|
17
|
+
? []
|
|
18
|
+
: [DateTime.formatIso(DateTime.unsafeMake(row.moment))])
|
|
19
|
+
.toSorted()), Effect.orDie);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ProcessedEventRegistry, type ProcessedEventRegistryShape } from '@satorio/machinery/ports';
|
|
2
|
+
import { type DateTime, Effect, Layer, Ref } from 'effect';
|
|
3
|
+
/**
|
|
4
|
+
* What an in-memory registry holds: the moment each event id was first
|
|
5
|
+
* claimed. A scenario seeds this map to say an event was handled before.
|
|
6
|
+
*/
|
|
7
|
+
export type ProcessedEventsById = ReadonlyMap<string, DateTime.Utc>;
|
|
8
|
+
/**
|
|
9
|
+
* An in-memory registry over a `Ref` you keep, so a test can seed what
|
|
10
|
+
* was already claimed and read back what the run added.
|
|
11
|
+
*/
|
|
12
|
+
export declare const makeInMemoryProcessedEventRegistryWith: (processed: Ref.Ref<ProcessedEventsById>) => ProcessedEventRegistryShape;
|
|
13
|
+
/**
|
|
14
|
+
* An in-memory registry over a `Ref` of its own, empty at the start.
|
|
15
|
+
*/
|
|
16
|
+
export declare const makeInMemoryProcessedEventRegistry: Effect.Effect<ProcessedEventRegistryShape, never, never>;
|
|
17
|
+
/**
|
|
18
|
+
* The in-memory registry, mounted.
|
|
19
|
+
*/
|
|
20
|
+
export declare const InMemoryProcessedEventRegistryLive: Layer.Layer<ProcessedEventRegistry, never, never>;
|
|
21
|
+
/**
|
|
22
|
+
* A registry that claims nothing: every claim comes back as a refusal.
|
|
23
|
+
* Mount it to walk the branch a working stand-in never reaches.
|
|
24
|
+
*/
|
|
25
|
+
export declare const ProcessedEventRegistryUnavailableLive: Layer.Layer<ProcessedEventRegistry>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { alreadyProcessed, claimAccepted, ProcessedEventRegistry, ProcessedEventRegistryUnavailableError, } from '@satorio/machinery/ports';
|
|
2
|
+
import { Effect, Layer, Ref } from 'effect';
|
|
3
|
+
const NOT_MOUNTED = 'processed event registry unavailable (test)';
|
|
4
|
+
/**
|
|
5
|
+
* An in-memory registry over a `Ref` you keep, so a test can seed what
|
|
6
|
+
* was already claimed and read back what the run added.
|
|
7
|
+
*/
|
|
8
|
+
export const makeInMemoryProcessedEventRegistryWith = (processed) => ({
|
|
9
|
+
claim: (input) => Ref.modify(processed, (current) => {
|
|
10
|
+
const seenAt = current.get(input.eventId);
|
|
11
|
+
return seenAt === undefined
|
|
12
|
+
? [
|
|
13
|
+
claimAccepted,
|
|
14
|
+
new Map(current).set(input.eventId, input.processedAt),
|
|
15
|
+
]
|
|
16
|
+
: [alreadyProcessed(seenAt), current];
|
|
17
|
+
}),
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* An in-memory registry over a `Ref` of its own, empty at the start.
|
|
21
|
+
*/
|
|
22
|
+
export const makeInMemoryProcessedEventRegistry = Effect.gen(function* () {
|
|
23
|
+
const processed = yield* Ref.make(new Map());
|
|
24
|
+
return makeInMemoryProcessedEventRegistryWith(processed);
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* The in-memory registry, mounted.
|
|
28
|
+
*/
|
|
29
|
+
export const InMemoryProcessedEventRegistryLive = Layer.effect(ProcessedEventRegistry, makeInMemoryProcessedEventRegistry);
|
|
30
|
+
/**
|
|
31
|
+
* A registry that claims nothing: every claim comes back as a refusal.
|
|
32
|
+
* Mount it to walk the branch a working stand-in never reaches.
|
|
33
|
+
*/
|
|
34
|
+
export const ProcessedEventRegistryUnavailableLive = Layer.succeed(ProcessedEventRegistry, {
|
|
35
|
+
claim: () => new ProcessedEventRegistryUnavailableError({
|
|
36
|
+
operation: 'claim',
|
|
37
|
+
cause: NOT_MOUNTED,
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { QueueMessageProducer, type QueueMessageProducerShape } from '@satorio/machinery/ports';
|
|
2
|
+
import { Effect, Layer, Ref } from 'effect';
|
|
3
|
+
import { type PendingQueueMessages } from './queue-message-source.js';
|
|
4
|
+
/**
|
|
5
|
+
* An in-memory producer over the same reference an in-memory source reads.
|
|
6
|
+
*
|
|
7
|
+
* An in-memory producer over the same `Ref` an in-memory source reads, so a
|
|
8
|
+
* test sends on one side and receives on the other, the way a real queue pairs
|
|
9
|
+
* the two. Each send gets its own message id, counted from what is already on
|
|
10
|
+
* the queue.
|
|
11
|
+
*/
|
|
12
|
+
export declare const makeInMemoryQueueMessageProducerWith: (pending: Ref.Ref<PendingQueueMessages>) => QueueMessageProducerShape;
|
|
13
|
+
/**
|
|
14
|
+
* An in-memory producer over a `Ref` of its own, whose messages nobody
|
|
15
|
+
* receives. Use it where a test only cares that sending succeeded.
|
|
16
|
+
*/
|
|
17
|
+
export declare const makeInMemoryQueueMessageProducer: Effect.Effect<QueueMessageProducerShape, never, never>;
|
|
18
|
+
/**
|
|
19
|
+
* The in-memory producer, mounted.
|
|
20
|
+
*/
|
|
21
|
+
export declare const InMemoryQueueMessageProducerLive: Layer.Layer<QueueMessageProducer, never, never>;
|
|
22
|
+
/**
|
|
23
|
+
* A producer that answers nothing: every send fails with
|
|
24
|
+
* `QueueMessageProducerUnavailableError`. Mount it to walk the branch a
|
|
25
|
+
* working stand-in never reaches.
|
|
26
|
+
*/
|
|
27
|
+
export declare const QueueMessageProducerUnavailableLive: Layer.Layer<QueueMessageProducer>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { QueueMessageProducer, QueueMessageProducerUnavailableError, } from '@satorio/machinery/ports';
|
|
2
|
+
import { Effect, Layer, Ref } from 'effect';
|
|
3
|
+
import { messageOf } from './queue-message-source.js';
|
|
4
|
+
const NOT_MOUNTED = 'queue unavailable (test)';
|
|
5
|
+
/**
|
|
6
|
+
* An in-memory producer over the same reference an in-memory source reads.
|
|
7
|
+
*
|
|
8
|
+
* An in-memory producer over the same `Ref` an in-memory source reads, so a
|
|
9
|
+
* test sends on one side and receives on the other, the way a real queue pairs
|
|
10
|
+
* the two. Each send gets its own message id, counted from what is already on
|
|
11
|
+
* the queue.
|
|
12
|
+
*/
|
|
13
|
+
export const makeInMemoryQueueMessageProducerWith = (pending) => ({
|
|
14
|
+
send: (body) => Ref.update(pending, (current) => [
|
|
15
|
+
...current,
|
|
16
|
+
messageOf(`message-${current.length + 1}`, body),
|
|
17
|
+
]),
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* An in-memory producer over a `Ref` of its own, whose messages nobody
|
|
21
|
+
* receives. Use it where a test only cares that sending succeeded.
|
|
22
|
+
*/
|
|
23
|
+
export const makeInMemoryQueueMessageProducer = Effect.gen(function* () {
|
|
24
|
+
const pending = yield* Ref.make([]);
|
|
25
|
+
return makeInMemoryQueueMessageProducerWith(pending);
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* The in-memory producer, mounted.
|
|
29
|
+
*/
|
|
30
|
+
export const InMemoryQueueMessageProducerLive = Layer.effect(QueueMessageProducer, makeInMemoryQueueMessageProducer);
|
|
31
|
+
/**
|
|
32
|
+
* A producer that answers nothing: every send fails with
|
|
33
|
+
* `QueueMessageProducerUnavailableError`. Mount it to walk the branch a
|
|
34
|
+
* working stand-in never reaches.
|
|
35
|
+
*/
|
|
36
|
+
export const QueueMessageProducerUnavailableLive = Layer.succeed(QueueMessageProducer, {
|
|
37
|
+
send: () => Effect.fail(new QueueMessageProducerUnavailableError({
|
|
38
|
+
operation: 'send',
|
|
39
|
+
cause: NOT_MOUNTED,
|
|
40
|
+
})),
|
|
41
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { QueueMessageSource, type QueueMessageSourceShape, type RawQueueMessage } from '@satorio/machinery/ports';
|
|
2
|
+
import { Effect, Layer, Ref } from 'effect';
|
|
3
|
+
/**
|
|
4
|
+
* What an in-memory queue holds: the messages waiting to be received, in
|
|
5
|
+
* the order they were put there. A scenario keeps the `Ref` and reads it
|
|
6
|
+
* to see what is still on the queue.
|
|
7
|
+
*/
|
|
8
|
+
export type PendingQueueMessages = readonly RawQueueMessage[];
|
|
9
|
+
/**
|
|
10
|
+
* One raw message, built the way a queue hands it over: an id, the
|
|
11
|
+
* receipt an acknowledgement quotes, and the body.
|
|
12
|
+
*/
|
|
13
|
+
export declare const messageOf: (messageId: string, body: string) => RawQueueMessage;
|
|
14
|
+
/**
|
|
15
|
+
* An in-memory message source over a `Ref` you keep, so a test seeds the
|
|
16
|
+
* queue before the run and reads what is left after it. `receive` hands
|
|
17
|
+
* back everything waiting; `acknowledge` takes one message off for good,
|
|
18
|
+
* as a real queue does when the receipt is deleted.
|
|
19
|
+
*/
|
|
20
|
+
export declare const makeInMemoryQueueMessageSourceWith: (pending: Ref.Ref<PendingQueueMessages>) => QueueMessageSourceShape;
|
|
21
|
+
/**
|
|
22
|
+
* An in-memory message source over a `Ref` of its own, empty at the
|
|
23
|
+
* start.
|
|
24
|
+
*/
|
|
25
|
+
export declare const makeInMemoryQueueMessageSource: Effect.Effect<QueueMessageSourceShape, never, never>;
|
|
26
|
+
/**
|
|
27
|
+
* The in-memory message source, mounted.
|
|
28
|
+
*/
|
|
29
|
+
export declare const InMemoryQueueMessageSourceLive: Layer.Layer<QueueMessageSource, never, never>;
|
|
30
|
+
/**
|
|
31
|
+
* A message source that answers nothing: every operation fails with
|
|
32
|
+
* `QueueMessageSourceUnavailableError`. Mount it to walk the branch a
|
|
33
|
+
* working stand-in never reaches.
|
|
34
|
+
*/
|
|
35
|
+
export declare const QueueMessageSourceUnavailableLive: Layer.Layer<QueueMessageSource>;
|
|
36
|
+
/**
|
|
37
|
+
* A queue source scripted ahead of time: the test writes what each
|
|
38
|
+
* `receive` call returns. Once the script runs out, it stays silent.
|
|
39
|
+
*
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export interface ScriptedQueue {
|
|
43
|
+
readonly source: QueueMessageSourceShape;
|
|
44
|
+
readonly acknowledged: Effect.Effect<readonly string[]>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Builds the scripted source and the reader for what it acknowledged.
|
|
48
|
+
*/
|
|
49
|
+
export declare const makeScriptedMessageSource: (script: readonly (readonly RawQueueMessage[])[]) => Effect.Effect<ScriptedQueue>;
|
|
50
|
+
/**
|
|
51
|
+
* The scripted source, mounted: each `receive` answers with the next
|
|
52
|
+
* batch of the script.
|
|
53
|
+
*/
|
|
54
|
+
export declare const scriptedQueueMessageSourceLive: (script: readonly (readonly RawQueueMessage[])[]) => Layer.Layer<QueueMessageSource>;
|