@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,35 @@
|
|
|
1
|
+
import SchemaBuilder from '@pothos/core';
|
|
2
|
+
import '@pothos/plugin-directives';
|
|
3
|
+
import '@pothos/plugin-sub-graph';
|
|
4
|
+
import DirectivesPlugin from '@pothos/plugin-directives';
|
|
5
|
+
import FederationPlugin, { hasResolvableKey } from '@pothos/plugin-federation';
|
|
6
|
+
import SubGraphPlugin from '@pothos/plugin-sub-graph';
|
|
7
|
+
/**
|
|
8
|
+
* The two variations of one schema: `Public` (external) and `Internal`
|
|
9
|
+
* (admin and service operations). Federation (`@key` + `resolveReference`)
|
|
10
|
+
* turns each variation into a subgraph for the gateway.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export const SUB_GRAPH_NAMES = ['Public', 'Internal'];
|
|
15
|
+
/**
|
|
16
|
+
* The federation and subgraph settings every schema in this package
|
|
17
|
+
* shares, fixed here so two services cannot drift apart.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export const subGraphOptions = {
|
|
22
|
+
defaultForTypes: [...SUB_GRAPH_NAMES],
|
|
23
|
+
fieldsInheritFromTypes: true,
|
|
24
|
+
explicitlyIncludeType: (type) => hasResolvableKey(type),
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Returns a builder factory carrying the resolver context type. The context
|
|
28
|
+
* itself belongs to your service, so this returns a factory, not a ready
|
|
29
|
+
* instance. Every service shares the same plugins and subgraph settings,
|
|
30
|
+
* fixed here.
|
|
31
|
+
*/
|
|
32
|
+
export const makeSubGraphSchemaBuilder = () => new SchemaBuilder({
|
|
33
|
+
plugins: [DirectivesPlugin, FederationPlugin, SubGraphPlugin],
|
|
34
|
+
subGraphs: subGraphOptions,
|
|
35
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Effect, Exit, ParseResult } from 'effect';
|
|
2
|
+
import { GraphQLError } from 'graphql';
|
|
3
|
+
import type { OperationFailure } from './failures.js';
|
|
4
|
+
/**
|
|
5
|
+
* Maps a GraphQL failure to a `GraphQLError`: the same three-class operation
|
|
6
|
+
* failure channel, plus input decoder failures — no second catalog. Names
|
|
7
|
+
* and details come from the domain failure and land in `extensions.code`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const asGraphQLError: (failure: OperationFailure | ParseResult.ParseError) => GraphQLError;
|
|
10
|
+
/**
|
|
11
|
+
* Runs the use case's Effect program and returns its result: a success
|
|
12
|
+
* value, a `GraphQLError` for a typed failure, or `INTERNAL` for a defect.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export type RunOperations<R> = <A>(program: Effect.Effect<A, OperationFailure | ParseResult.ParseError, R>) => Promise<A>;
|
|
18
|
+
/**
|
|
19
|
+
* The minimal capability a resolver borrows from the runtime: run a program
|
|
20
|
+
* that needs `R` and return its `Exit`. Both `Runtime.runPromiseExit` and
|
|
21
|
+
* `ManagedRuntime.runPromiseExit` fit this shape, so production and tests
|
|
22
|
+
* share one code path.
|
|
23
|
+
*/
|
|
24
|
+
export type RunOperationsExit<R> = <A, E>(program: Effect.Effect<A, E, R>) => Promise<Exit.Exit<A, E>>;
|
|
25
|
+
/**
|
|
26
|
+
* What a resolver reads from its context: one call that runs a use
|
|
27
|
+
* case's program and answers with its value or a `GraphQLError`.
|
|
28
|
+
*/
|
|
29
|
+
export interface GraphQLContext<R> {
|
|
30
|
+
readonly runOperations: RunOperations<R>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Builds that context around the runtime's own `runPromiseExit`, so a
|
|
34
|
+
* resolver never touches the runtime directly.
|
|
35
|
+
*/
|
|
36
|
+
export declare const graphqlContextFrom: <R>(runExit: RunOperationsExit<R>) => GraphQLContext<R>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Cause, Exit, Option, ParseResult } from 'effect';
|
|
2
|
+
import { GraphQLError } from 'graphql';
|
|
3
|
+
/**
|
|
4
|
+
* Maps a GraphQL failure to a `GraphQLError`: the same three-class operation
|
|
5
|
+
* failure channel, plus input decoder failures — no second catalog. Names
|
|
6
|
+
* and details come from the domain failure and land in `extensions.code`.
|
|
7
|
+
*/
|
|
8
|
+
export const asGraphQLError = (failure) => {
|
|
9
|
+
switch (failure._tag) {
|
|
10
|
+
case 'SubjectNotFoundError':
|
|
11
|
+
return new GraphQLError(`${failure.subject} ${failure.id} not found`, {
|
|
12
|
+
extensions: {
|
|
13
|
+
code: 'NOT_FOUND',
|
|
14
|
+
reason: 'subject-not-found',
|
|
15
|
+
subject: failure.subject,
|
|
16
|
+
id: failure.id,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
case 'OperationRejectedError':
|
|
20
|
+
return new GraphQLError(`${failure.reason}: ${failure.detail}`, {
|
|
21
|
+
extensions: {
|
|
22
|
+
code: 'REJECTED',
|
|
23
|
+
reason: failure.reason,
|
|
24
|
+
detail: failure.detail,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
case 'OperationsUnavailableError':
|
|
28
|
+
return new GraphQLError(`Service unavailable: ${failure.operation}`, {
|
|
29
|
+
extensions: {
|
|
30
|
+
code: 'UNAVAILABLE',
|
|
31
|
+
reason: 'service-unavailable',
|
|
32
|
+
operation: failure.operation,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
case 'ParseError': {
|
|
36
|
+
const detail = ParseResult.TreeFormatter.formatErrorSync(failure);
|
|
37
|
+
return new GraphQLError(`Invalid input: ${detail.split('\n')[0]}`, {
|
|
38
|
+
extensions: {
|
|
39
|
+
code: 'BAD_USER_INPUT',
|
|
40
|
+
reason: 'invalid-input',
|
|
41
|
+
detail,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
default:
|
|
46
|
+
return new GraphQLError('Unknown failure', {
|
|
47
|
+
extensions: { code: 'INTERNAL', reason: 'internal-error' },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Builds that context around the runtime's own `runPromiseExit`, so a
|
|
53
|
+
* resolver never touches the runtime directly.
|
|
54
|
+
*/
|
|
55
|
+
export const graphqlContextFrom = (runExit) => ({
|
|
56
|
+
runOperations: async (program) => {
|
|
57
|
+
const exit = await runExit(program);
|
|
58
|
+
if (Exit.isSuccess(exit)) {
|
|
59
|
+
return exit.value;
|
|
60
|
+
}
|
|
61
|
+
const failure = Cause.failureOption(exit.cause);
|
|
62
|
+
if (Option.isSome(failure)) {
|
|
63
|
+
throw asGraphQLError(failure.value);
|
|
64
|
+
}
|
|
65
|
+
throw new GraphQLError('Internal service error', {
|
|
66
|
+
extensions: { code: 'INTERNAL', detail: Cause.pretty(exit.cause) },
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The proof that every failure a service can raise is mapped at this
|
|
3
|
+
* boundary. Give it the union of your own failures and the union of the
|
|
4
|
+
* tags your resolver classifies, then assign `true` to it once, beside
|
|
5
|
+
* the lists. A failure nobody classified turns the type into that
|
|
6
|
+
* failure's own tag, so the compiler stops and names it rather than
|
|
7
|
+
* letting it reach a caller as `INTERNAL`.
|
|
8
|
+
*/
|
|
9
|
+
export type EveryFailureIsClassified<Failures extends {
|
|
10
|
+
readonly _tag: string;
|
|
11
|
+
}, Classified extends string> = [Exclude<Failures['_tag'], Classified>] extends [never] ? true : Exclude<Failures['_tag'], Classified>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Refusal } from '@satorio/machinery/core';
|
|
2
|
+
declare const SubjectNotFoundError_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: "SubjectNotFoundError";
|
|
4
|
+
} & Readonly<A>;
|
|
5
|
+
/**
|
|
6
|
+
* One of exactly three operation failure classes below — the closed set a
|
|
7
|
+
* resolver maps to. An untagged failure is a defect, not a domain error.
|
|
8
|
+
*/
|
|
9
|
+
export declare class SubjectNotFoundError extends SubjectNotFoundError_base<{
|
|
10
|
+
readonly subject: string;
|
|
11
|
+
readonly id: string;
|
|
12
|
+
}> {
|
|
13
|
+
}
|
|
14
|
+
declare const OperationRejectedError_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 & {
|
|
15
|
+
readonly _tag: "OperationRejectedError";
|
|
16
|
+
} & Readonly<A>;
|
|
17
|
+
/**
|
|
18
|
+
* A resolver's rule rejected the operation, and `reason` names the cause it
|
|
19
|
+
* recognized.
|
|
20
|
+
*
|
|
21
|
+
* `reason` is the named cause from the service's shared failure taxonomy when
|
|
22
|
+
* a domain failure is recognized; otherwise it is the tag of the rule that
|
|
23
|
+
* rejected the operation.
|
|
24
|
+
*/
|
|
25
|
+
export declare class OperationRejectedError extends OperationRejectedError_base<{
|
|
26
|
+
readonly rule: string;
|
|
27
|
+
readonly reason: string;
|
|
28
|
+
readonly detail: string;
|
|
29
|
+
}> {
|
|
30
|
+
}
|
|
31
|
+
declare const OperationsUnavailableError_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 & {
|
|
32
|
+
readonly _tag: "OperationsUnavailableError";
|
|
33
|
+
} & Readonly<A>;
|
|
34
|
+
/**
|
|
35
|
+
* A resolver's own dependency did not answer; `cause` carries the port's own
|
|
36
|
+
* failure.
|
|
37
|
+
*/
|
|
38
|
+
export declare class OperationsUnavailableError extends OperationsUnavailableError_base<{
|
|
39
|
+
readonly operation: string;
|
|
40
|
+
readonly cause: unknown;
|
|
41
|
+
}> {
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Everything a resolver may fail with, as one closed set: not found, rejected,
|
|
45
|
+
* or unavailable.
|
|
46
|
+
*/
|
|
47
|
+
export type OperationFailure = SubjectNotFoundError | OperationRejectedError | OperationsUnavailableError;
|
|
48
|
+
declare const ServerCannotListenError_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 & {
|
|
49
|
+
readonly _tag: "ServerCannotListenError";
|
|
50
|
+
} & Readonly<A>;
|
|
51
|
+
/**
|
|
52
|
+
* The socket never opened. `port` is the port the server asked for, and
|
|
53
|
+
* `refusal` says in three parts which variable decides that port and how
|
|
54
|
+
* to hand it another one.
|
|
55
|
+
*/
|
|
56
|
+
export declare class ServerCannotListenError extends ServerCannotListenError_base<{
|
|
57
|
+
readonly port: number;
|
|
58
|
+
readonly refusal: Refusal;
|
|
59
|
+
readonly cause: unknown;
|
|
60
|
+
}> {
|
|
61
|
+
}
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Data } from 'effect';
|
|
2
|
+
/**
|
|
3
|
+
* One of exactly three operation failure classes below — the closed set a
|
|
4
|
+
* resolver maps to. An untagged failure is a defect, not a domain error.
|
|
5
|
+
*/
|
|
6
|
+
export class SubjectNotFoundError extends Data.TaggedError('SubjectNotFoundError') {
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A resolver's rule rejected the operation, and `reason` names the cause it
|
|
10
|
+
* recognized.
|
|
11
|
+
*
|
|
12
|
+
* `reason` is the named cause from the service's shared failure taxonomy when
|
|
13
|
+
* a domain failure is recognized; otherwise it is the tag of the rule that
|
|
14
|
+
* rejected the operation.
|
|
15
|
+
*/
|
|
16
|
+
export class OperationRejectedError extends Data.TaggedError('OperationRejectedError') {
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A resolver's own dependency did not answer; `cause` carries the port's own
|
|
20
|
+
* failure.
|
|
21
|
+
*/
|
|
22
|
+
export class OperationsUnavailableError extends Data.TaggedError('OperationsUnavailableError') {
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The socket never opened. `port` is the port the server asked for, and
|
|
26
|
+
* `refusal` says in three parts which variable decides that port and how
|
|
27
|
+
* to hand it another one.
|
|
28
|
+
*/
|
|
29
|
+
export class ServerCannotListenError extends Data.TaggedError('ServerCannotListenError') {
|
|
30
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type GraphQLSchema } from 'graphql';
|
|
2
|
+
/**
|
|
3
|
+
* A GraphiQL page: a title and a pre-filled document. Your service writes
|
|
4
|
+
* the examples, since only it knows its schema's operations; the machinery
|
|
5
|
+
* supplies the form, a neutral default, and a check the example still fits.
|
|
6
|
+
*/
|
|
7
|
+
export interface GraphiqlPage {
|
|
8
|
+
readonly title: string;
|
|
9
|
+
readonly defaultQuery: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The page a service gets before it writes examples of its own: one
|
|
13
|
+
* introspection query and a note saying where to write more.
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export declare const DEFAULT_GRAPHIQL_PAGE: GraphiqlPage;
|
|
18
|
+
/**
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export interface GraphiqlExampleProblem {
|
|
22
|
+
readonly message: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Checks a GraphiQL page's default query against a schema. An example that
|
|
26
|
+
* no longer matches the schema is worse than none — it teaches the wrong
|
|
27
|
+
* thing. A service's test runs its page through this check on every change.
|
|
28
|
+
*/
|
|
29
|
+
export declare const graphiqlExampleProblems: (schema: GraphQLSchema, page: GraphiqlPage) => readonly GraphiqlExampleProblem[];
|
|
30
|
+
/**
|
|
31
|
+
* A minimal schema the default page must stay valid against: it asks only
|
|
32
|
+
* for introspection, nothing more, so it fits any real schema too.
|
|
33
|
+
*
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export declare const MINIMAL_SCHEMA_FOR_DEFAULT_PAGE: GraphQLSchema;
|
|
37
|
+
/**
|
|
38
|
+
* The shape Yoga takes for its editor page.
|
|
39
|
+
*
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export declare const graphiqlOptionsFor: (page: GraphiqlPage) => {
|
|
43
|
+
readonly title: string;
|
|
44
|
+
readonly defaultQuery: string;
|
|
45
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { buildSchema, parse, validate } from 'graphql';
|
|
2
|
+
/**
|
|
3
|
+
* The page a service gets before it writes examples of its own: one
|
|
4
|
+
* introspection query and a note saying where to write more.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_GRAPHIQL_PAGE = {
|
|
9
|
+
title: 'Machinery — Internal GraphQL',
|
|
10
|
+
defaultQuery: `# Place your cursor inside an operation and press ▶ to run it.
|
|
11
|
+
# Each field and argument description lives in the schema — see the Docs
|
|
12
|
+
# tab on the right.
|
|
13
|
+
#
|
|
14
|
+
# Your service supplies example operations here. Until it does,
|
|
15
|
+
# introspection shows the schema.
|
|
16
|
+
|
|
17
|
+
query Introspection {
|
|
18
|
+
__schema {
|
|
19
|
+
queryType {
|
|
20
|
+
name
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
`,
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Checks a GraphiQL page's default query against a schema. An example that
|
|
28
|
+
* no longer matches the schema is worse than none — it teaches the wrong
|
|
29
|
+
* thing. A service's test runs its page through this check on every change.
|
|
30
|
+
*/
|
|
31
|
+
export const graphiqlExampleProblems = (schema, page) => {
|
|
32
|
+
try {
|
|
33
|
+
return validate(schema, parse(page.defaultQuery)).map((error) => ({
|
|
34
|
+
message: error.message,
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
catch (cause) {
|
|
38
|
+
return [{ message: cause instanceof Error ? cause.message : String(cause) }];
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* A minimal schema the default page must stay valid against: it asks only
|
|
43
|
+
* for introspection, nothing more, so it fits any real schema too.
|
|
44
|
+
*
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
export const MINIMAL_SCHEMA_FOR_DEFAULT_PAGE = buildSchema('type Query { ping: String }');
|
|
48
|
+
/**
|
|
49
|
+
* The shape Yoga takes for its editor page.
|
|
50
|
+
*
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
export const graphiqlOptionsFor = (page) => ({
|
|
54
|
+
title: page.title,
|
|
55
|
+
defaultQuery: page.defaultQuery,
|
|
56
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry to the graphql mechanism: one server that answers your
|
|
3
|
+
* operations and the readiness probes beside them. It gives you one Yoga
|
|
4
|
+
* server on Bun that serves a federated Pothos schema and the readiness probes
|
|
5
|
+
* on the same port, with a resolver context that runs your use cases and turns
|
|
6
|
+
* their typed failures into one closed set of `GraphQLError` codes.
|
|
7
|
+
*/
|
|
8
|
+
export { makeSubGraphSchemaBuilder } from './builder.js';
|
|
9
|
+
export { asGraphQLError, type GraphQLContext, graphqlContextFrom, type RunOperationsExit, } from './context.js';
|
|
10
|
+
export type { EveryFailureIsClassified } from './failure-classification.js';
|
|
11
|
+
export { type OperationFailure, OperationRejectedError, OperationsUnavailableError, ServerCannotListenError, SubjectNotFoundError, } from './failures.js';
|
|
12
|
+
export { type GraphiqlPage, graphiqlExampleProblems, } from './graphiql.js';
|
|
13
|
+
export { graphqlServerLive } from './layer.js';
|
|
14
|
+
export { makeYogaServer } from './server.js';
|
|
15
|
+
export { ENVIRONMENT, GRAPHQL_ENDPOINT, graphiqlEnabledFromEnvironment, graphqlPortFromEnvironment, serverCannotListen, } from './settings.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry to the graphql mechanism: one server that answers your
|
|
3
|
+
* operations and the readiness probes beside them. It gives you one Yoga
|
|
4
|
+
* server on Bun that serves a federated Pothos schema and the readiness probes
|
|
5
|
+
* on the same port, with a resolver context that runs your use cases and turns
|
|
6
|
+
* their typed failures into one closed set of `GraphQLError` codes.
|
|
7
|
+
*/
|
|
8
|
+
export { makeSubGraphSchemaBuilder } from './builder.js';
|
|
9
|
+
export { asGraphQLError, graphqlContextFrom, } from './context.js';
|
|
10
|
+
export { OperationRejectedError, OperationsUnavailableError, ServerCannotListenError, SubjectNotFoundError, } from './failures.js';
|
|
11
|
+
export { graphiqlExampleProblems, } from './graphiql.js';
|
|
12
|
+
export { graphqlServerLive } from './layer.js';
|
|
13
|
+
export { makeYogaServer } from './server.js';
|
|
14
|
+
export { ENVIRONMENT, GRAPHQL_ENDPOINT, graphiqlEnabledFromEnvironment, graphqlPortFromEnvironment, serverCannotListen, } from './settings.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ConfigError, Layer } from 'effect';
|
|
2
|
+
import type { ServerCannotListenError } from './failures.js';
|
|
3
|
+
import { type GraphQLRequestHandlerOptions } from './request-handler.js';
|
|
4
|
+
/**
|
|
5
|
+
* The one layer this mechanism mounts: the server that answers your
|
|
6
|
+
* operations.
|
|
7
|
+
*
|
|
8
|
+
* The one layer this mechanism mounts: it opens the port, keeps `Bun.serve`
|
|
9
|
+
* alive for the life of the scope, and serves both GraphQL and the readiness
|
|
10
|
+
* probes (`/livez`, `/readyz`, `/healthz`) from one handler, so the probes
|
|
11
|
+
* answer on the SAME port as the schema. A port the runtime cannot open comes
|
|
12
|
+
* back as `ServerCannotListenError`, whose refusal names the variable that
|
|
13
|
+
* decides it.
|
|
14
|
+
*/
|
|
15
|
+
export declare const graphqlServerLive: <R>(options: GraphQLRequestHandlerOptions<R>) => Layer.Layer<never, ConfigError.ConfigError | ServerCannotListenError, R>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Effect, Layer } from 'effect';
|
|
2
|
+
import { graphqlRequestHandler, } from './request-handler.js';
|
|
3
|
+
import { graphqlPortFromEnvironment, serverCannotListen } from './settings.js';
|
|
4
|
+
/**
|
|
5
|
+
* The one layer this mechanism mounts: the server that answers your
|
|
6
|
+
* operations.
|
|
7
|
+
*
|
|
8
|
+
* The one layer this mechanism mounts: it opens the port, keeps `Bun.serve`
|
|
9
|
+
* alive for the life of the scope, and serves both GraphQL and the readiness
|
|
10
|
+
* probes (`/livez`, `/readyz`, `/healthz`) from one handler, so the probes
|
|
11
|
+
* answer on the SAME port as the schema. A port the runtime cannot open comes
|
|
12
|
+
* back as `ServerCannotListenError`, whose refusal names the variable that
|
|
13
|
+
* decides it.
|
|
14
|
+
*/
|
|
15
|
+
export const graphqlServerLive = (options) => Layer.scopedDiscard(Effect.gen(function* () {
|
|
16
|
+
const port = yield* graphqlPortFromEnvironment;
|
|
17
|
+
const fetchHandler = yield* graphqlRequestHandler(options);
|
|
18
|
+
yield* Effect.acquireRelease(Effect.try({
|
|
19
|
+
try: () => Bun.serve({ port, fetch: fetchHandler }),
|
|
20
|
+
catch: (cause) => serverCannotListen(port, cause),
|
|
21
|
+
}), (server) => Effect.promise(() => server.stop(true)));
|
|
22
|
+
yield* Effect.logInfo(`GraphQL (Yoga) and readiness probes are listening on port ${port}`);
|
|
23
|
+
}));
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type NamedHealthCheck } from '@satorio/machinery/http';
|
|
2
|
+
import { type ConfigError, Effect } from 'effect';
|
|
3
|
+
import type { GraphQLSchema } from 'graphql';
|
|
4
|
+
import type { GraphiqlPage } from './graphiql.js';
|
|
5
|
+
/**
|
|
6
|
+
* What a service hands this mechanism to be served: its schema, the
|
|
7
|
+
* readiness checks that answer beside it, and the editor page it wants a
|
|
8
|
+
* reader to open on.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export interface GraphQLRequestHandlerOptions<R> {
|
|
13
|
+
readonly schema: GraphQLSchema;
|
|
14
|
+
readonly graphiqlPage?: GraphiqlPage;
|
|
15
|
+
readonly healthChecks: Effect.Effect<readonly NamedHealthCheck<R>[], ConfigError.ConfigError, R>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* One handler for one port: the readiness routes answer first, and
|
|
19
|
+
* everything they do not claim goes to GraphQL. It captures the Effect
|
|
20
|
+
* runtime carrying the service's ports, so a resolver runs a use case
|
|
21
|
+
* from it. Whatever opens the socket — `Bun.serve` in production, a
|
|
22
|
+
* plain server in a test — serves this one function.
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export declare const graphqlRequestHandler: <R>(options: GraphQLRequestHandlerOptions<R>) => Effect.Effect<(request: Request) => Promise<Response>, ConfigError.ConfigError, R>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { healthRoutesFetch, } from '@satorio/machinery/http';
|
|
2
|
+
import { Effect, Runtime } from 'effect';
|
|
3
|
+
import { graphqlContextFrom } from './context.js';
|
|
4
|
+
import { makeYogaServer } from './server.js';
|
|
5
|
+
import { graphiqlEnabledFromEnvironment } from './settings.js';
|
|
6
|
+
/**
|
|
7
|
+
* One handler for one port: the readiness routes answer first, and
|
|
8
|
+
* everything they do not claim goes to GraphQL. It captures the Effect
|
|
9
|
+
* runtime carrying the service's ports, so a resolver runs a use case
|
|
10
|
+
* from it. Whatever opens the socket — `Bun.serve` in production, a
|
|
11
|
+
* plain server in a test — serves this one function.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export const graphqlRequestHandler = (options) => Effect.gen(function* () {
|
|
16
|
+
const graphiqlEnabled = yield* graphiqlEnabledFromEnvironment;
|
|
17
|
+
const healthChecks = yield* options.healthChecks;
|
|
18
|
+
const runtime = yield* Effect.runtime();
|
|
19
|
+
const runCheck = Runtime.runPromiseExit(runtime);
|
|
20
|
+
const yoga = makeYogaServer({
|
|
21
|
+
schema: options.schema,
|
|
22
|
+
...(options.graphiqlPage === undefined
|
|
23
|
+
? {}
|
|
24
|
+
: { graphiqlPage: options.graphiqlPage }),
|
|
25
|
+
context: graphqlContextFrom(runCheck),
|
|
26
|
+
graphiqlEnabled,
|
|
27
|
+
});
|
|
28
|
+
const health = healthRoutesFetch({ runCheck, checks: healthChecks });
|
|
29
|
+
return async (request) => {
|
|
30
|
+
const healthResponse = await health(request);
|
|
31
|
+
return healthResponse ?? (await yoga.fetch(request));
|
|
32
|
+
};
|
|
33
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { GraphQLSchema } from 'graphql';
|
|
2
|
+
import { type YogaServerInstance } from 'graphql-yoga';
|
|
3
|
+
import type { GraphQLContext } from './context.js';
|
|
4
|
+
import { type GraphiqlPage } from './graphiql.js';
|
|
5
|
+
/**
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export interface GraphQLServerOptions<R> {
|
|
9
|
+
readonly schema: GraphQLSchema;
|
|
10
|
+
readonly graphiqlPage?: GraphiqlPage;
|
|
11
|
+
readonly context: GraphQLContext<R>;
|
|
12
|
+
readonly graphiqlEnabled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Builds a Yoga GraphQL server for Bun. The `graphiqlEnabled` flag only
|
|
16
|
+
* turns off the editor page; the endpoint itself keeps answering either
|
|
17
|
+
* way.
|
|
18
|
+
*/
|
|
19
|
+
export declare const makeYogaServer: <R>(options: GraphQLServerOptions<R>) => YogaServerInstance<Record<string, never>, GraphQLContext<R>>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createYoga } from 'graphql-yoga';
|
|
2
|
+
import { DEFAULT_GRAPHIQL_PAGE, graphiqlOptionsFor, } from './graphiql.js';
|
|
3
|
+
import { GRAPHIQL_ENABLED_BY_DEFAULT, GRAPHQL_ENDPOINT } from './settings.js';
|
|
4
|
+
/**
|
|
5
|
+
* Builds a Yoga GraphQL server for Bun. The `graphiqlEnabled` flag only
|
|
6
|
+
* turns off the editor page; the endpoint itself keeps answering either
|
|
7
|
+
* way.
|
|
8
|
+
*/
|
|
9
|
+
export const makeYogaServer = (options) => createYoga({
|
|
10
|
+
schema: options.schema,
|
|
11
|
+
context: () => options.context,
|
|
12
|
+
graphqlEndpoint: GRAPHQL_ENDPOINT,
|
|
13
|
+
graphiql: (options.graphiqlEnabled ?? GRAPHIQL_ENABLED_BY_DEFAULT)
|
|
14
|
+
? graphiqlOptionsFor(options.graphiqlPage ?? DEFAULT_GRAPHIQL_PAGE)
|
|
15
|
+
: false,
|
|
16
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type EnvironmentEntry, type Refusal } from '@satorio/machinery/core';
|
|
2
|
+
import { Config } from 'effect';
|
|
3
|
+
import { ServerCannotListenError } from './failures.js';
|
|
4
|
+
/**
|
|
5
|
+
* The port the server listens on when nobody names one.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare const DEFAULT_GRAPHQL_PORT = 4000;
|
|
10
|
+
/**
|
|
11
|
+
* GraphiQL is the editor served on the same endpoint: opening `/graphql`
|
|
12
|
+
* in a browser loads the schema docs and examples. It defaults to on
|
|
13
|
+
* because the endpoint is internal; production turns it off with
|
|
14
|
+
* `GRAPHIQL_ENABLED=false`.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export declare const GRAPHIQL_ENABLED_BY_DEFAULT = true;
|
|
19
|
+
/**
|
|
20
|
+
* The one path the GraphQL endpoint answers on. Readiness probes mount
|
|
21
|
+
* beside it, on the same port.
|
|
22
|
+
*/
|
|
23
|
+
export declare const GRAPHQL_ENDPOINT = "/graphql";
|
|
24
|
+
/**
|
|
25
|
+
* The three parts a reader meets when the port is set to something the
|
|
26
|
+
* server cannot listen on. A port left unset takes the default and
|
|
27
|
+
* refuses nothing.
|
|
28
|
+
*
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
export declare const portRefusal: Refusal;
|
|
32
|
+
/**
|
|
33
|
+
* Every variable this adapter reads, and the one state each of them
|
|
34
|
+
* stands in. Neither is prefixed: one process serves one schema, so
|
|
35
|
+
* these two names are whole.
|
|
36
|
+
*/
|
|
37
|
+
export declare const ENVIRONMENT: readonly EnvironmentEntry[];
|
|
38
|
+
/**
|
|
39
|
+
* The port to listen on. Unset, it falls back to the default; set to
|
|
40
|
+
* anything outside the range a TCP port can take, it refuses in three
|
|
41
|
+
* parts.
|
|
42
|
+
*/
|
|
43
|
+
export declare const graphqlPortFromEnvironment: Config.Config<number>;
|
|
44
|
+
/**
|
|
45
|
+
* Whether to serve the editor page. The endpoint itself answers either
|
|
46
|
+
* way; only the page in a browser goes away.
|
|
47
|
+
*/
|
|
48
|
+
export declare const graphiqlEnabledFromEnvironment: Config.Config<boolean>;
|
|
49
|
+
/**
|
|
50
|
+
* Turns whatever the runtime threw while opening the socket into the
|
|
51
|
+
* named failure a caller reads. The text names the variable that decides
|
|
52
|
+
* the port and the port it asked for, because the runtime's own message
|
|
53
|
+
* names neither.
|
|
54
|
+
*/
|
|
55
|
+
export declare const serverCannotListen: (port: number, cause: unknown) => ServerCannotListenError;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { fallsBackTo, wholeNumberFallingBackTo, } from '@satorio/machinery/core';
|
|
2
|
+
import { Config } from 'effect';
|
|
3
|
+
import { ServerCannotListenError } from './failures.js';
|
|
4
|
+
const PORT = 'GRAPHQL_PORT';
|
|
5
|
+
const GRAPHIQL_ENABLED = 'GRAPHIQL_ENABLED';
|
|
6
|
+
const LOWEST_PORT = 1;
|
|
7
|
+
const HIGHEST_PORT = 65_535;
|
|
8
|
+
/**
|
|
9
|
+
* The port the server listens on when nobody names one.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_GRAPHQL_PORT = 4000;
|
|
14
|
+
/**
|
|
15
|
+
* GraphiQL is the editor served on the same endpoint: opening `/graphql`
|
|
16
|
+
* in a browser loads the schema docs and examples. It defaults to on
|
|
17
|
+
* because the endpoint is internal; production turns it off with
|
|
18
|
+
* `GRAPHIQL_ENABLED=false`.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export const GRAPHIQL_ENABLED_BY_DEFAULT = true;
|
|
23
|
+
/**
|
|
24
|
+
* The one path the GraphQL endpoint answers on. Readiness probes mount
|
|
25
|
+
* beside it, on the same port.
|
|
26
|
+
*/
|
|
27
|
+
export const GRAPHQL_ENDPOINT = '/graphql';
|
|
28
|
+
/**
|
|
29
|
+
* The three parts a reader meets when the port is set to something the
|
|
30
|
+
* server cannot listen on. A port left unset takes the default and
|
|
31
|
+
* refuses nothing.
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export const portRefusal = {
|
|
36
|
+
what: `${PORT} is set to something this server cannot listen on.`,
|
|
37
|
+
why: `A TCP port is a whole number from ${LOWEST_PORT} to ${HIGHEST_PORT}, and nothing else can be opened.`,
|
|
38
|
+
whatToDo: `Set ${PORT} to a whole number in that range, or leave it unset and take the ${DEFAULT_GRAPHQL_PORT} default.`,
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Every variable this adapter reads, and the one state each of them
|
|
42
|
+
* stands in. Neither is prefixed: one process serves one schema, so
|
|
43
|
+
* these two names are whole.
|
|
44
|
+
*/
|
|
45
|
+
export const ENVIRONMENT = [
|
|
46
|
+
{
|
|
47
|
+
name: PORT,
|
|
48
|
+
what: 'The port the GraphQL endpoint and the readiness probes listen on.',
|
|
49
|
+
state: fallsBackTo(String(DEFAULT_GRAPHQL_PORT)),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: GRAPHIQL_ENABLED,
|
|
53
|
+
what: 'Whether the GraphiQL editor page is served on the same endpoint.',
|
|
54
|
+
state: fallsBackTo(String(GRAPHIQL_ENABLED_BY_DEFAULT)),
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* The port to listen on. Unset, it falls back to the default; set to
|
|
59
|
+
* anything outside the range a TCP port can take, it refuses in three
|
|
60
|
+
* parts.
|
|
61
|
+
*/
|
|
62
|
+
export const graphqlPortFromEnvironment = wholeNumberFallingBackTo(PORT, LOWEST_PORT, HIGHEST_PORT, DEFAULT_GRAPHQL_PORT, portRefusal);
|
|
63
|
+
/**
|
|
64
|
+
* Whether to serve the editor page. The endpoint itself answers either
|
|
65
|
+
* way; only the page in a browser goes away.
|
|
66
|
+
*/
|
|
67
|
+
export const graphiqlEnabledFromEnvironment = Config.boolean(GRAPHIQL_ENABLED).pipe(Config.withDefault(GRAPHIQL_ENABLED_BY_DEFAULT));
|
|
68
|
+
/**
|
|
69
|
+
* Turns whatever the runtime threw while opening the socket into the
|
|
70
|
+
* named failure a caller reads. The text names the variable that decides
|
|
71
|
+
* the port and the port it asked for, because the runtime's own message
|
|
72
|
+
* names neither.
|
|
73
|
+
*/
|
|
74
|
+
export const serverCannotListen = (port, cause) => new ServerCannotListenError({
|
|
75
|
+
port,
|
|
76
|
+
cause,
|
|
77
|
+
refusal: {
|
|
78
|
+
what: `This service could not open port ${port}.`,
|
|
79
|
+
why: `${PORT} decides which port the GraphQL endpoint and the readiness probes listen on, and it takes ${DEFAULT_GRAPHQL_PORT} when nobody names one. A port another process already holds refuses the socket.`,
|
|
80
|
+
whatToDo: `Free port ${port}, or set ${PORT} to a port nothing else holds, then start the service again.`,
|
|
81
|
+
},
|
|
82
|
+
});
|