@sincpro/client-framework 0.6.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +95 -25
- package/MANIFESTO.md +103 -40
- package/README.md +608 -333
- package/dist/circuit_breaker/domain/circuit.d.ts +29 -0
- package/dist/circuit_breaker/domain/circuit.js +37 -0
- package/dist/circuit_breaker/entrypoint/circuit-breaker.d.ts +56 -0
- package/dist/circuit_breaker/entrypoint/circuit-breaker.js +94 -0
- package/dist/circuit_breaker/index.d.ts +9 -0
- package/dist/circuit_breaker/index.js +9 -0
- package/dist/client/adapters/json-rpc.d.ts +40 -0
- package/dist/client/adapters/json-rpc.js +97 -0
- package/dist/client/{memory.d.ts → adapters/memory.d.ts} +1 -1
- package/dist/client/{memory.js → adapters/memory.js} +2 -2
- package/dist/{adapters → client/adapters}/parquet.d.ts +1 -1
- package/dist/client/adapters/rest-operations.d.ts +19 -0
- package/dist/client/adapters/rest-operations.js +66 -0
- package/dist/client/{rest.d.ts → adapters/rest.d.ts} +1 -1
- package/dist/client/{rest.js → adapters/rest.js} +2 -2
- package/dist/client/{entity.d.ts → domain/entity.d.ts} +6 -10
- package/dist/client/domain/operation.d.ts +73 -0
- package/dist/client/domain/operation.js +19 -0
- package/dist/client/index.d.ts +20 -0
- package/dist/client/index.js +20 -0
- package/dist/client/services/guarded.d.ts +17 -0
- package/dist/client/services/guarded.js +19 -0
- package/dist/client/services/propagation.d.ts +45 -0
- package/dist/{services/execution.js → client/services/propagation.js} +49 -36
- package/dist/client/{retry.js → services/retry.js} +1 -1
- package/dist/codegen/{metamodel.d.ts → domain/metamodel.d.ts} +17 -2
- package/dist/codegen/{names.d.ts → domain/names.d.ts} +2 -2
- package/dist/codegen/{names.js → domain/names.js} +3 -3
- package/dist/codegen/{cli.js → entrypoint/cli.js} +15 -14
- package/dist/codegen/index.d.ts +7 -7
- package/dist/codegen/index.js +5 -10
- package/dist/codegen/services/client.d.ts +15 -0
- package/dist/codegen/services/client.js +111 -0
- package/dist/codegen/services/domain.d.ts +20 -0
- package/dist/codegen/{domain.js → services/domain.js} +82 -25
- package/dist/codegen/{entities.d.ts → services/entities.d.ts} +4 -3
- package/dist/codegen/{entities.js → services/entities.js} +18 -4
- package/dist/codegen/{rest.d.ts → services/rest.d.ts} +1 -1
- package/dist/codegen/{scaffold.d.ts → services/scaffold.d.ts} +8 -5
- package/dist/codegen/{scaffold.js → services/scaffold.js} +39 -43
- package/dist/common/failures.d.ts +83 -0
- package/dist/common/failures.js +212 -0
- package/dist/common/index.d.ts +11 -0
- package/dist/common/index.js +11 -0
- package/dist/context/adapters/memory.d.ts +6 -0
- package/dist/context/adapters/memory.js +34 -0
- package/dist/context/adapters/plain-codec.d.ts +3 -0
- package/dist/context/adapters/plain-codec.js +16 -0
- package/dist/context/{codec.d.ts → domain/codec.d.ts} +0 -1
- package/dist/context/{codec.js → domain/codec.js} +0 -14
- package/dist/context/domain/execution.d.ts +45 -0
- package/dist/context/domain/execution.js +11 -0
- package/dist/context/{store.d.ts → domain/store.d.ts} +0 -5
- package/dist/context/{store.js → domain/store.js} +1 -34
- package/dist/context/{context.d.ts → entrypoint/context.d.ts} +1 -1
- package/dist/context/index.d.ts +17 -0
- package/dist/context/index.js +15 -0
- package/dist/context/infrastructure/process.d.ts +13 -0
- package/dist/context/infrastructure/process.js +35 -0
- package/dist/core/domain/bus.d.ts +27 -0
- package/dist/core/domain/bus.js +26 -0
- package/dist/core/domain/dto.d.ts +62 -0
- package/dist/core/domain/dto.js +43 -0
- package/dist/core/domain/errors.d.ts +18 -0
- package/dist/core/domain/errors.js +1 -0
- package/dist/core/domain/pipeline.d.ts +89 -0
- package/dist/core/domain/pipeline.js +1 -0
- package/dist/core/domain/services.d.ts +47 -0
- package/dist/core/domain/services.js +31 -0
- package/dist/core/entrypoint/bounded-context.d.ts +139 -0
- package/dist/core/entrypoint/bounded-context.js +426 -0
- package/dist/core/entrypoint/screens.d.ts +6 -0
- package/dist/core/entrypoint/screens.js +136 -0
- package/dist/core/index.d.ts +19 -0
- package/dist/core/index.js +17 -0
- package/dist/core/infrastructure/listeners.d.ts +28 -0
- package/dist/core/infrastructure/listeners.js +34 -0
- package/dist/core/infrastructure/process.d.ts +56 -0
- package/dist/core/infrastructure/process.js +35 -0
- package/dist/core/services/binding.d.ts +6 -0
- package/dist/core/services/binding.js +16 -0
- package/dist/core/services/error-handlers.d.ts +11 -0
- package/dist/core/services/error-handlers.js +21 -0
- package/dist/core/services/execution.d.ts +66 -0
- package/dist/core/services/execution.js +216 -0
- package/dist/core/services/interceptors.d.ts +15 -0
- package/dist/core/services/interceptors.js +25 -0
- package/dist/core/services/listening.d.ts +17 -0
- package/dist/core/services/listening.js +25 -0
- package/dist/event_driven/adapters/memory-event-store.d.ts +10 -0
- package/dist/event_driven/adapters/memory-event-store.js +23 -0
- package/dist/event_driven/adapters/stored-queue.d.ts +67 -0
- package/dist/event_driven/adapters/stored-queue.js +192 -0
- package/dist/event_driven/adapters/sync-queue.d.ts +11 -0
- package/dist/event_driven/adapters/sync-queue.js +19 -0
- package/dist/event_driven/domain/envelope.d.ts +28 -0
- package/dist/event_driven/domain/envelope.js +19 -0
- package/dist/event_driven/domain/queue.d.ts +40 -0
- package/dist/event_driven/domain/queue.js +1 -0
- package/dist/event_driven/domain/store.d.ts +56 -0
- package/dist/event_driven/domain/store.js +1 -0
- package/dist/event_driven/entrypoint/publisher.d.ts +26 -0
- package/dist/event_driven/entrypoint/publisher.js +35 -0
- package/dist/event_driven/entrypoint/subscriber.d.ts +34 -0
- package/dist/event_driven/entrypoint/subscriber.js +51 -0
- package/dist/event_driven/index.d.ts +19 -0
- package/dist/event_driven/index.js +17 -0
- package/dist/index.d.ts +21 -38
- package/dist/index.js +21 -38
- package/dist/model/index.d.ts +10 -0
- package/dist/model/index.js +10 -0
- package/dist/model/{metas.d.ts → infrastructure/metas.d.ts} +1 -1
- package/dist/model/{schema.d.ts → services/schema.d.ts} +1 -1
- package/dist/model/{schema.js → services/schema.js} +1 -1
- package/dist/observability/adapters/console.d.ts +12 -0
- package/dist/observability/adapters/console.js +35 -0
- package/dist/observability/adapters/memory.d.ts +41 -0
- package/dist/observability/adapters/memory.js +49 -0
- package/dist/{adapters → observability/adapters}/opentelemetry.d.ts +1 -1
- package/dist/{adapters → observability/adapters}/sentry.d.ts +1 -1
- package/dist/observability/domain/errors.d.ts +23 -0
- package/dist/observability/domain/errors.js +8 -0
- package/dist/observability/domain/logging.d.ts +44 -0
- package/dist/observability/domain/logging.js +1 -0
- package/dist/observability/domain/tracing.d.ts +49 -0
- package/dist/observability/domain/tracing.js +20 -0
- package/dist/observability/index.d.ts +16 -0
- package/dist/observability/index.js +4 -0
- package/dist/observability/infrastructure/registry.d.ts +21 -0
- package/dist/observability/infrastructure/registry.js +18 -0
- package/dist/observability/services/logging.d.ts +27 -0
- package/dist/observability/services/logging.js +106 -0
- package/dist/observability/services/outcome.d.ts +12 -0
- package/dist/observability/services/outcome.js +17 -0
- package/dist/{services/report.js → observability/services/reporting.js} +4 -3
- package/dist/testing/index.d.ts +3 -33
- package/dist/testing/index.js +5 -37
- package/package.json +16 -8
- package/dist/codegen/client.d.ts +0 -14
- package/dist/codegen/client.js +0 -201
- package/dist/codegen/domain.d.ts +0 -15
- package/dist/observability/ports.d.ts +0 -65
- package/dist/observability/ports.js +0 -21
- package/dist/services/bounded-context.d.ts +0 -79
- package/dist/services/bounded-context.js +0 -261
- package/dist/services/execution.d.ts +0 -63
- package/dist/services/feature.d.ts +0 -85
- package/dist/services/feature.js +0 -86
- package/dist/services/process.d.ts +0 -60
- package/dist/services/process.js +0 -57
- package/dist/services/screens.d.ts +0 -2
- package/dist/services/screens.js +0 -76
- /package/dist/{adapters → client/adapters}/parquet.js +0 -0
- /package/dist/client/{contract.d.ts → domain/contract.d.ts} +0 -0
- /package/dist/client/{contract.js → domain/contract.js} +0 -0
- /package/dist/client/{entity.js → domain/entity.js} +0 -0
- /package/dist/client/{transport.d.ts → domain/transport.d.ts} +0 -0
- /package/dist/client/{transport.js → domain/transport.js} +0 -0
- /package/dist/client/{discovery.d.ts → services/discovery.d.ts} +0 -0
- /package/dist/client/{discovery.js → services/discovery.js} +0 -0
- /package/dist/client/{retry.d.ts → services/retry.d.ts} +0 -0
- /package/dist/codegen/{metamodel.js → domain/metamodel.js} +0 -0
- /package/dist/codegen/{cli.d.ts → entrypoint/cli.d.ts} +0 -0
- /package/dist/codegen/{check.d.ts → services/check.d.ts} +0 -0
- /package/dist/codegen/{check.js → services/check.js} +0 -0
- /package/dist/codegen/{rest.js → services/rest.js} +0 -0
- /package/dist/context/{context.js → entrypoint/context.js} +0 -0
- /package/dist/model/{meta.d.ts → domain/meta.d.ts} +0 -0
- /package/dist/model/{meta.js → domain/meta.js} +0 -0
- /package/dist/model/{metas.js → infrastructure/metas.js} +0 -0
- /package/dist/{adapters → observability/adapters}/opentelemetry.js +0 -0
- /package/dist/{adapters → observability/adapters}/sentry.js +0 -0
- /package/dist/{services/report.d.ts → observability/services/reporting.d.ts} +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { failureOf } from "../../common/failures.js";
|
|
2
|
+
/**
|
|
3
|
+
* How an error ended a use case, by default — read off the failure it is (`failureOf`).
|
|
4
|
+
*
|
|
5
|
+
* Context: the backend reports what nobody expected and only that — a refusal it explained is
|
|
6
|
+
* the business answering, not a fault. Here the same: a failure that is the caller's answer (a
|
|
7
|
+
* 4xx, an error that declares a kind such as `domain` or `not_found`) is `refused`; a request
|
|
8
|
+
* the caller stopped waiting for is `aborted`; a fault — a bug, a 5xx, a server nobody could
|
|
9
|
+
* reach — is `failed`, and reported. A bounded context adds its own expected errors
|
|
10
|
+
* (`expectErrors`); a process replaces this whole (`configureProcess({ outcomeOf })`).
|
|
11
|
+
*/
|
|
12
|
+
export function outcomeOf(error) {
|
|
13
|
+
const failure = failureOf(error);
|
|
14
|
+
if (failure.aborted)
|
|
15
|
+
return "aborted";
|
|
16
|
+
return failure.fault ? "failed" : "refused";
|
|
17
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { newEntityId } from "
|
|
2
|
-
import { processContext
|
|
1
|
+
import { newEntityId } from "../../common/ids.js";
|
|
2
|
+
import { processContext } from "../../context/infrastructure/process.js";
|
|
3
|
+
import { observabilityNow } from "../infrastructure/registry.js";
|
|
3
4
|
/**
|
|
4
5
|
* Whether this is the first time this error is reported. A Feature that fails inside an
|
|
5
6
|
* ApplicationService throws through it, and a screen that draws the failure may break on it:
|
|
@@ -28,7 +29,7 @@ export function firstReport(error) {
|
|
|
28
29
|
* ignored: telling about a failure never becomes a second one.
|
|
29
30
|
*/
|
|
30
31
|
export function reportError(error, where) {
|
|
31
|
-
const settings =
|
|
32
|
+
const settings = observabilityNow();
|
|
32
33
|
try {
|
|
33
34
|
// What the server explained, or what the caller stopped waiting for, is not a fault here either.
|
|
34
35
|
if (settings.outcomeOf(error) !== "failed")
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,35 +1,5 @@
|
|
|
1
|
-
import type { ErrorAbout, ErrorReporter, Outcome, Tracer } from "../observability/ports.js";
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* const tracer = memoryTracer();
|
|
6
|
-
* configureProcess({ tracer });
|
|
7
|
-
* await project.CloseIssue.execute(input);
|
|
8
|
-
* tracer.spans.map((one) => one.name); // ["project/CloseIssue", "project/Issues.close", …]
|
|
9
|
-
*
|
|
10
|
-
* Context: a use case is proven in Node with nothing of a platform; these are the platform's
|
|
11
|
-
* part, held in memory. Not test doubles of this package — real implementations of its ports.
|
|
2
|
+
* `@sincpro/client-framework/testing`: the observability ports in memory, so a use case is
|
|
3
|
+
* proven in Node with nothing of a platform. Not part of what an application runs.
|
|
12
4
|
*/
|
|
13
|
-
|
|
14
|
-
export type RecordedSpan = {
|
|
15
|
-
name: string;
|
|
16
|
-
traceId: string;
|
|
17
|
-
spanId: string;
|
|
18
|
-
/** The span it was opened under, when it was. */
|
|
19
|
-
parentSpanId: string | undefined;
|
|
20
|
-
attributes: Record<string, string | number | boolean>;
|
|
21
|
-
/** How it ended; `undefined` while it is open. */
|
|
22
|
-
outcome: Outcome | undefined;
|
|
23
|
-
error: unknown;
|
|
24
|
-
};
|
|
25
|
-
export type MemoryTracer = Tracer & {
|
|
26
|
-
readonly spans: RecordedSpan[];
|
|
27
|
-
};
|
|
28
|
-
export declare function memoryTracer(): MemoryTracer;
|
|
29
|
-
export type MemoryReporter = ErrorReporter & {
|
|
30
|
-
readonly reports: {
|
|
31
|
-
error: unknown;
|
|
32
|
-
about: ErrorAbout;
|
|
33
|
-
}[];
|
|
34
|
-
};
|
|
35
|
-
export declare function memoryReporter(): MemoryReporter;
|
|
5
|
+
export { type MemoryLogger, memoryLogger, type MemoryReporter, memoryReporter, type MemoryTracer, memoryTracer, type RecordedSpan, } from "../observability/adapters/memory.js";
|
package/dist/testing/index.js
CHANGED
|
@@ -1,37 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return {
|
|
7
|
-
spans,
|
|
8
|
-
start(name, options) {
|
|
9
|
-
const recorded = {
|
|
10
|
-
name,
|
|
11
|
-
traceId: options.parent?.traceId || hex(16),
|
|
12
|
-
spanId: hex(8),
|
|
13
|
-
parentSpanId: options.parent?.spanId || undefined,
|
|
14
|
-
attributes: { ...options.attributes },
|
|
15
|
-
outcome: undefined,
|
|
16
|
-
error: undefined,
|
|
17
|
-
};
|
|
18
|
-
spans.push(recorded);
|
|
19
|
-
return {
|
|
20
|
-
traceId: recorded.traceId,
|
|
21
|
-
spanId: recorded.spanId,
|
|
22
|
-
sampled: true,
|
|
23
|
-
set: (attributes) => void Object.assign(recorded.attributes, attributes),
|
|
24
|
-
end: (outcome, error) => {
|
|
25
|
-
if (recorded.outcome !== undefined)
|
|
26
|
-
return;
|
|
27
|
-
recorded.outcome = outcome;
|
|
28
|
-
recorded.error = error;
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
export function memoryReporter() {
|
|
35
|
-
const reports = [];
|
|
36
|
-
return { reports, report: (error, about) => void reports.push({ error, about }) };
|
|
37
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* `@sincpro/client-framework/testing`: the observability ports in memory, so a use case is
|
|
3
|
+
* proven in Node with nothing of a platform. Not part of what an application runs.
|
|
4
|
+
*/
|
|
5
|
+
export { memoryLogger, memoryReporter, memoryTracer, } from "../observability/adapters/memory.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sincpro/client-framework",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "The client side of sincpro_framework, for every platform: Feature and ApplicationService traced and reported by default, applications made of domains, and the typed client generated from a service's contract. No React, no DOM.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"default": "./dist/index.js"
|
|
12
12
|
},
|
|
13
|
+
"./core": {
|
|
14
|
+
"types": "./dist/core/index.d.ts",
|
|
15
|
+
"default": "./dist/core/index.js"
|
|
16
|
+
},
|
|
13
17
|
"./codegen": {
|
|
14
18
|
"types": "./dist/codegen/index.d.ts",
|
|
15
19
|
"default": "./dist/codegen/index.js"
|
|
@@ -18,21 +22,25 @@
|
|
|
18
22
|
"types": "./dist/testing/index.d.ts",
|
|
19
23
|
"default": "./dist/testing/index.js"
|
|
20
24
|
},
|
|
25
|
+
"./console": {
|
|
26
|
+
"types": "./dist/observability/adapters/console.d.ts",
|
|
27
|
+
"default": "./dist/observability/adapters/console.js"
|
|
28
|
+
},
|
|
21
29
|
"./opentelemetry": {
|
|
22
|
-
"types": "./dist/adapters/opentelemetry.d.ts",
|
|
23
|
-
"default": "./dist/adapters/opentelemetry.js"
|
|
30
|
+
"types": "./dist/observability/adapters/opentelemetry.d.ts",
|
|
31
|
+
"default": "./dist/observability/adapters/opentelemetry.js"
|
|
24
32
|
},
|
|
25
33
|
"./sentry": {
|
|
26
|
-
"types": "./dist/adapters/sentry.d.ts",
|
|
27
|
-
"default": "./dist/adapters/sentry.js"
|
|
34
|
+
"types": "./dist/observability/adapters/sentry.d.ts",
|
|
35
|
+
"default": "./dist/observability/adapters/sentry.js"
|
|
28
36
|
},
|
|
29
37
|
"./parquet": {
|
|
30
|
-
"types": "./dist/adapters/parquet.d.ts",
|
|
31
|
-
"default": "./dist/adapters/parquet.js"
|
|
38
|
+
"types": "./dist/client/adapters/parquet.d.ts",
|
|
39
|
+
"default": "./dist/client/adapters/parquet.js"
|
|
32
40
|
}
|
|
33
41
|
},
|
|
34
42
|
"bin": {
|
|
35
|
-
"sincpro-client": "./dist/codegen/cli.js"
|
|
43
|
+
"sincpro-client": "./dist/codegen/entrypoint/cli.js"
|
|
36
44
|
},
|
|
37
45
|
"files": [
|
|
38
46
|
"dist",
|
package/dist/codegen/client.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { Metamodel } from "./metamodel.js";
|
|
2
|
-
import { type GenerateOptions } from "./rest.js";
|
|
3
|
-
/**
|
|
4
|
-
* The TypeScript of the client of `metamodel`, as the text of one file next to its `domain.ts`.
|
|
5
|
-
*
|
|
6
|
-
* 1. Every operation, by its local name, as data: identity, role, `query`, entities, targets
|
|
7
|
-
* and the binding of each wire that publishes it.
|
|
8
|
-
* 2. Per operation REST reaches, how a transport calls it (`callOf`): a page, a record, a write
|
|
9
|
-
* — or none, said in the client's own doc.
|
|
10
|
-
* 3. The routes of the writes, read once (`writeRoute`).
|
|
11
|
-
* Final: `clientOf(transport)`, a method per operation reached, named by `methodOf`; two with
|
|
12
|
-
* one name are refused, naming both.
|
|
13
|
-
*/
|
|
14
|
-
export declare function clientFileOf(metamodel: Metamodel, options?: GenerateOptions): string;
|
package/dist/codegen/client.js
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import { aliasOf, answerOf, claimed, deprecationOf, docOf, DOMAIN_MODULE, FRAMEWORK, headerOf, identifierOf, localOf, methodOf, recordLiteral, sorted, } from "./names.js";
|
|
2
|
-
import { ownerOf, restOf, WRITES, } from "./rest.js";
|
|
3
|
-
/**
|
|
4
|
-
* The client of a bounded context as TypeScript: every operation as data, and a method for each
|
|
5
|
-
* one REST reaches the way a `Transport` sends — over `@sincpro/client-framework`, so a script,
|
|
6
|
-
* a test or a hook of its own calls the service typed, with no screen.
|
|
7
|
-
*
|
|
8
|
-
* Context: a method only writes down the route the contract published and hands the call to the
|
|
9
|
-
* transport, which keeps the rules — the `Idempotency-Key`, the `Problem`, the model's version.
|
|
10
|
-
* A GET is a page (`list`), one record (`one`), a file (`export`: `downloadRoute`, its bytes as
|
|
11
|
-
* they arrive), or — a Query of the context's own, `GET /me` — a read of its answer as it came
|
|
12
|
-
* (`readRoute`, over a transport that has `read`). A GET's
|
|
13
|
-
* keys in its path are required parameters; every other field is an optional one.
|
|
14
|
-
*/
|
|
15
|
-
/** A search's fields a page asks with, not the screen: the criteria, and its cursor inside it. */
|
|
16
|
-
const PAGED = new Set(["criteria", "cursor"]);
|
|
17
|
-
/** An export's fields its method takes apart: the criteria and the format. */
|
|
18
|
-
const EXPORTED = new Set(["criteria", "cursor", "format"]);
|
|
19
|
-
/** A GET's parameters: the path's keys, and the rest of its input but what a page asks with. */
|
|
20
|
-
function askedOf(operation, rest, paged, apart = PAGED) {
|
|
21
|
-
const path = [...rest.fields].sort();
|
|
22
|
-
const query = Object.keys(operation.input.properties ?? {})
|
|
23
|
-
.filter((one) => !path.includes(one) && !(paged && apart.has(one)))
|
|
24
|
-
.sort();
|
|
25
|
-
return { path, query };
|
|
26
|
-
}
|
|
27
|
-
function callOf(metamodel, operation, rest) {
|
|
28
|
-
if (rest === undefined)
|
|
29
|
-
return { kind: "none", why: "REST does not publish it" };
|
|
30
|
-
if (WRITES.has(rest.method))
|
|
31
|
-
return { kind: "send" };
|
|
32
|
-
const owner = ownerOf(metamodel, operation);
|
|
33
|
-
if (operation.role === "search" && owner !== undefined && owner in metamodel.models)
|
|
34
|
-
return { kind: "list", entity: owner, ...askedOf(operation, rest, true) };
|
|
35
|
-
// An export answers bytes, never JSON: it is downloaded.
|
|
36
|
-
if (operation.role === "export" && owner !== undefined && owner in metamodel.models)
|
|
37
|
-
return { kind: "download", entity: owner, ...askedOf(operation, rest, true, EXPORTED) };
|
|
38
|
-
const field = owner === undefined ? undefined : operation.targets[owner];
|
|
39
|
-
const record = rest.fields.length === 1 && rest.fields[0] === field;
|
|
40
|
-
if (operation.role === "get" && owner !== undefined && field !== undefined && record)
|
|
41
|
-
return { kind: "one", entity: owner, field };
|
|
42
|
-
// Any other GET — a Query of the context's own, `GET /me` — is read as it answers.
|
|
43
|
-
return { kind: "read", ...askedOf(operation, rest, false) };
|
|
44
|
-
}
|
|
45
|
-
/** A parameter list's TypeScript: the path's keys required, the rest optional, all as text. */
|
|
46
|
-
function paramsOf(asked) {
|
|
47
|
-
const union = (names) => names.map((one) => JSON.stringify(one)).join(" | ");
|
|
48
|
-
if (asked.path.length === 0 && asked.query.length === 0)
|
|
49
|
-
return { signature: "", route: (path) => JSON.stringify(path) };
|
|
50
|
-
const required = asked.path.length === 0 ? "" : `Readonly<Record<${union(asked.path)}, string>>`;
|
|
51
|
-
const optional = asked.query.length === 0 ? "" : `Partial<Record<${union(asked.query)}, string>>`;
|
|
52
|
-
const type = [required, optional].filter((one) => one !== "").join(" & ");
|
|
53
|
-
return {
|
|
54
|
-
signature: `params${asked.path.length === 0 ? "?" : ""}: ${type}`,
|
|
55
|
-
route: (path) => `listRoute(${JSON.stringify(path)}, params)`,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
/** The data of one operation: what it is, and how each wire reaches it. */
|
|
59
|
-
function dataOf(metamodel, identity, operation, rest) {
|
|
60
|
-
const lines = [
|
|
61
|
-
` identity: ${JSON.stringify(identity)},`,
|
|
62
|
-
` role: ${JSON.stringify(operation.role)},`,
|
|
63
|
-
` query: ${operation.query},`,
|
|
64
|
-
` entities: ${JSON.stringify(operation.entities)},`,
|
|
65
|
-
` targets: ${recordLiteral(operation.targets)},`,
|
|
66
|
-
];
|
|
67
|
-
if (rest !== undefined)
|
|
68
|
-
lines.push(` rest: { method: ${JSON.stringify(rest.method)}, path: ${JSON.stringify(rest.path)} },`);
|
|
69
|
-
const rpc = metamodel.bindings?.rpc?.[identity];
|
|
70
|
-
if (rpc !== undefined)
|
|
71
|
-
lines.push(` rpc: { method: ${JSON.stringify(rpc.method)} },`);
|
|
72
|
-
const grpc = metamodel.bindings?.grpc?.[identity];
|
|
73
|
-
if (grpc !== undefined)
|
|
74
|
-
lines.push(` grpc: { method: ${JSON.stringify(grpc.method)} },`);
|
|
75
|
-
const mcp = metamodel.bindings?.mcp?.[identity];
|
|
76
|
-
if (mcp !== undefined)
|
|
77
|
-
lines.push(` mcp: { tool: ${JSON.stringify(mcp.tool)} },`);
|
|
78
|
-
return ` ${localOf(metamodel, identity)}: {\n${lines.join("\n")}\n },`;
|
|
79
|
-
}
|
|
80
|
-
function methodSourceOf(local, answer, method, call, rest, doc) {
|
|
81
|
-
if (call.kind === "list") {
|
|
82
|
-
const entity = `dto.${identifierOf(call.entity)}`;
|
|
83
|
-
const params = paramsOf(call);
|
|
84
|
-
const signature = params.signature === "" ? "" : `, ${params.signature}`;
|
|
85
|
-
return `${doc} ${method}: (criteria: Criteria<${entity}>${signature}) =>\n transport.list<${entity}>(${params.route(rest.path)}, criteria),`;
|
|
86
|
-
}
|
|
87
|
-
if (call.kind === "download") {
|
|
88
|
-
const entity = `dto.${identifierOf(call.entity)}`;
|
|
89
|
-
const params = paramsOf(call);
|
|
90
|
-
// The path's keys are required, so they come first: a required parameter never follows an
|
|
91
|
-
// optional one.
|
|
92
|
-
const keys = params.signature === "" ? "" : `${params.signature.replace("params?:", "params:")}, `;
|
|
93
|
-
const given = params.signature === "" ? "" : ", params";
|
|
94
|
-
return `${doc} ${method}: (${keys}criteria?: Criteria<${entity}>, format?: ExportFormat) =>\n downloadRoute(transport, exportRoute(${JSON.stringify(rest.path)}, criteria, format${given})),`;
|
|
95
|
-
}
|
|
96
|
-
if (call.kind === "read") {
|
|
97
|
-
const params = paramsOf(call);
|
|
98
|
-
return `${doc} ${method}: (${params.signature}) =>\n readRoute<dto.${answer}>(transport, ${params.route(rest.path)}),`;
|
|
99
|
-
}
|
|
100
|
-
if (call.kind === "one") {
|
|
101
|
-
const path = rest.path.replace(`{${call.field}}`, "${encodeURIComponent(id)}");
|
|
102
|
-
return `${doc} ${method}: (id: string) =>\n transport.one<dto.${identifierOf(call.entity)}>(\`${path}\`),`;
|
|
103
|
-
}
|
|
104
|
-
const id = rest.fields.includes("id")
|
|
105
|
-
? "body.id === undefined ? undefined : String(body.id)"
|
|
106
|
-
: "undefined";
|
|
107
|
-
return [
|
|
108
|
-
`${doc} ${method}: (body: dto.${local}, options?: SendOptions) =>`,
|
|
109
|
-
` transport.send<dto.${answer}>(`,
|
|
110
|
-
` ${JSON.stringify(rest.method)},`,
|
|
111
|
-
` pathFor(routes.${method}, ${id}, body),`,
|
|
112
|
-
` body,`,
|
|
113
|
-
` undefined,`,
|
|
114
|
-
` options,`,
|
|
115
|
-
` ),`,
|
|
116
|
-
].join("\n");
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* The TypeScript of the client of `metamodel`, as the text of one file next to its `domain.ts`.
|
|
120
|
-
*
|
|
121
|
-
* 1. Every operation, by its local name, as data: identity, role, `query`, entities, targets
|
|
122
|
-
* and the binding of each wire that publishes it.
|
|
123
|
-
* 2. Per operation REST reaches, how a transport calls it (`callOf`): a page, a record, a write
|
|
124
|
-
* — or none, said in the client's own doc.
|
|
125
|
-
* 3. The routes of the writes, read once (`writeRoute`).
|
|
126
|
-
* Final: `clientOf(transport)`, a method per operation reached, named by `methodOf`; two with
|
|
127
|
-
* one name are refused, naming both.
|
|
128
|
-
*/
|
|
129
|
-
export function clientFileOf(metamodel, options = {}) {
|
|
130
|
-
const claim = claimed();
|
|
131
|
-
const operations = sorted(metamodel.operations);
|
|
132
|
-
const data = [];
|
|
133
|
-
const routes = [];
|
|
134
|
-
const methods = [];
|
|
135
|
-
const unreached = [];
|
|
136
|
-
let lists = false;
|
|
137
|
-
let paged = false;
|
|
138
|
-
let reads = false;
|
|
139
|
-
let sends = false;
|
|
140
|
-
let downloads = false;
|
|
141
|
-
for (const [identity, operation] of operations) {
|
|
142
|
-
const local = localOf(metamodel, identity);
|
|
143
|
-
const rest = restOf(metamodel, identity, options);
|
|
144
|
-
data.push(dataOf(metamodel, identity, operation, rest));
|
|
145
|
-
const call = callOf(metamodel, operation, rest);
|
|
146
|
-
if (call.kind === "none" || rest === undefined) {
|
|
147
|
-
unreached.push(`\`${local}\`: ${call.kind === "none" ? call.why : ""}.`);
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
const method = methodOf(local);
|
|
151
|
-
claim(method, `operation ${identity}`);
|
|
152
|
-
const doc = docOf([operation.description, deprecationOf(metamodel, operation)], " ");
|
|
153
|
-
methods.push(methodSourceOf(local, answerOf(metamodel, identity), method, call, rest, doc));
|
|
154
|
-
if (call.kind === "download") {
|
|
155
|
-
downloads = true;
|
|
156
|
-
paged = true;
|
|
157
|
-
}
|
|
158
|
-
if (call.kind === "list" || call.kind === "read") {
|
|
159
|
-
paged ||= call.kind === "list";
|
|
160
|
-
reads ||= call.kind === "read";
|
|
161
|
-
lists ||= call.path.length + call.query.length > 0;
|
|
162
|
-
}
|
|
163
|
-
if (call.kind === "send") {
|
|
164
|
-
sends = true;
|
|
165
|
-
routes.push(` ${method}: writeRoute(${JSON.stringify(`${rest.method} ${rest.path}`)}),`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const core = [
|
|
169
|
-
"bindable",
|
|
170
|
-
...(downloads ? ["downloadRoute", "type ExportFormat", "exportRoute"] : []),
|
|
171
|
-
...(lists ? ["listRoute"] : []),
|
|
172
|
-
...(sends ? ["pathFor"] : []),
|
|
173
|
-
...(reads ? ["readRoute"] : []),
|
|
174
|
-
...(sends ? ["type SendOptions"] : []),
|
|
175
|
-
"type Transport",
|
|
176
|
-
...(sends ? ["writeRoute"] : []),
|
|
177
|
-
];
|
|
178
|
-
const imports = [
|
|
179
|
-
...(paged ? ['import type { Criteria } from "@sincpro/criteria";'] : []),
|
|
180
|
-
`import { ${core.join(", ")} } from "${FRAMEWORK}";`,
|
|
181
|
-
"",
|
|
182
|
-
`import type * as dto from "${DOMAIN_MODULE}";`,
|
|
183
|
-
].join("\n");
|
|
184
|
-
const client = [
|
|
185
|
-
"/**",
|
|
186
|
-
` * «${aliasOf(metamodel)}» over a transport: a method for each operation REST reaches as a`,
|
|
187
|
-
" * page, a record, a read of its own or a write. Used by a Feature, each request carries the",
|
|
188
|
-
" * trace and the context of the use case that sent it (`bindable`).",
|
|
189
|
-
...(unreached.length === 0
|
|
190
|
-
? []
|
|
191
|
-
: [" *", " * Without a method here:", ...unreached.map((one) => ` * - ${one}`)]),
|
|
192
|
-
" */",
|
|
193
|
-
].join("\n");
|
|
194
|
-
return `${[
|
|
195
|
-
headerOf("The client", metamodel),
|
|
196
|
-
imports,
|
|
197
|
-
`/** Every operation of «${aliasOf(metamodel)}», as data: what it is, and how each wire reaches it. */\nexport const operations = {\n${data.join("\n")}\n} as const;`,
|
|
198
|
-
...(routes.length === 0 ? [] : [`const routes = {\n${routes.join("\n")}\n};`]),
|
|
199
|
-
`${client}\nexport function clientOf(transport: Transport) {\n return bindable(transport, (transport) => ({\n${methods.join("\n")}\n }));\n}`,
|
|
200
|
-
].join("\n\n")}\n`;
|
|
201
|
-
}
|
package/dist/codegen/domain.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { Metamodel } from "./metamodel.js";
|
|
2
|
-
/**
|
|
3
|
-
* The TypeScript of every DTO of `metamodel`, as the text of one file.
|
|
4
|
-
*
|
|
5
|
-
* 1. The models, by name: an entity's contract, a related model by its own name.
|
|
6
|
-
* 2. Each operation, by its local name (its identity without `<context>.`): its input as
|
|
7
|
-
* `<Name>` and its answer by the backend's own name for it (`answerOf`: `ResponseX`), with
|
|
8
|
-
* `<Name>Answer` kept as a deprecated alias — or as `<Name>Answer` when it has no name.
|
|
9
|
-
* 3. The shared definitions those reach — and the ones those reach, until none is new —, but
|
|
10
|
-
* never one a package of the stack already types (`Criteria`).
|
|
11
|
-
* Final: a header that says where it came from and not to edit it, the imports, then models,
|
|
12
|
-
* definitions and operations, each sorted by name; two of them with one TypeScript name are
|
|
13
|
-
* refused, naming both.
|
|
14
|
-
*/
|
|
15
|
-
export declare function domainFileOf(metamodel: Metamodel): string;
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* What a platform plugs so every use case is traced and every unexpected error reported.
|
|
3
|
-
*
|
|
4
|
-
* Context: sincpro_framework opens one span per use case and reports what nobody expected; a
|
|
5
|
-
* client has to do the same, and continue the backend's trace. The vendor — OpenTelemetry in a
|
|
6
|
-
* browser, a queue on a phone, GlitchTip, memory in a test — is the platform's, so this package
|
|
7
|
-
* defines the contract and calls it, and bundles none. Two implementations at least (a
|
|
8
|
-
* platform's and a test's) are why these are ports.
|
|
9
|
-
*
|
|
10
|
-
* Nothing here may break a use case: a tracer or a reporter that throws is ignored, and the use
|
|
11
|
-
* case answers as it would have.
|
|
12
|
-
*/
|
|
13
|
-
/** What is said about a span: plain values only, the way OpenTelemetry takes them. */
|
|
14
|
-
export type Attributes = Readonly<Record<string, string | number | boolean>>;
|
|
15
|
-
/**
|
|
16
|
-
* How a use case ended.
|
|
17
|
-
*
|
|
18
|
-
* - `ok`: it answered.
|
|
19
|
-
* - `refused`: the server said no and explained why — a validation, a conflict, a permission.
|
|
20
|
-
* Expected; never reported.
|
|
21
|
-
* - `aborted`: whoever asked stopped waiting (a screen that closed). Never reported.
|
|
22
|
-
* - `failed`: anything else — a bug, a server that broke, a server nobody could reach. Reported.
|
|
23
|
-
*/
|
|
24
|
-
export type Outcome = "ok" | "refused" | "aborted" | "failed";
|
|
25
|
-
/** One span: a use case, open while it runs. */
|
|
26
|
-
export interface Span {
|
|
27
|
-
/** 32 hex — the id the server continues and a person quotes as a support code. Empty when
|
|
28
|
-
* nothing traces. */
|
|
29
|
-
readonly traceId: string;
|
|
30
|
-
/** 16 hex. Empty when nothing traces. */
|
|
31
|
-
readonly spanId: string;
|
|
32
|
-
/** Whether this trace is kept: the server follows the same decision. */
|
|
33
|
-
readonly sampled: boolean;
|
|
34
|
-
/** Vendor state that travels beside the trace (`tracestate`), when there is any. */
|
|
35
|
-
readonly state?: string;
|
|
36
|
-
/** Says more about the use case while it runs. */
|
|
37
|
-
set(attributes: Attributes): void;
|
|
38
|
-
/** Closes it, once. `error` is what it failed or was refused with. */
|
|
39
|
-
end(outcome: Outcome, error?: unknown): void;
|
|
40
|
-
}
|
|
41
|
-
/** Opens spans. One per platform; `NO_TRACER` when nothing traces. */
|
|
42
|
-
export interface Tracer {
|
|
43
|
-
start(name: string, options: {
|
|
44
|
-
parent?: Span;
|
|
45
|
-
attributes?: Attributes;
|
|
46
|
-
}): Span;
|
|
47
|
-
}
|
|
48
|
-
/** What a report says about where the error happened. */
|
|
49
|
-
export type ErrorAbout = {
|
|
50
|
-
/** `<bounded context>.<Service>[.<method>]`: the use case's identity, as the backend's — or,
|
|
51
|
-
* for what failed outside one (`reportError`), where it happened (`view project:issue/acme-3`). */
|
|
52
|
-
useCase: string;
|
|
53
|
-
/** The trace the error belongs to — the same id the server logged, when it got that far. */
|
|
54
|
-
traceId: string;
|
|
55
|
-
/** The execution's own id. */
|
|
56
|
-
executionId: string;
|
|
57
|
-
/** What the use case ran with: the process's context, its bounded context's, its scope's. */
|
|
58
|
-
context: Readonly<Record<string, unknown>>;
|
|
59
|
-
};
|
|
60
|
-
/** Reports what nobody expected. One per platform; none when nothing reports. */
|
|
61
|
-
export interface ErrorReporter {
|
|
62
|
-
report(error: unknown, about: ErrorAbout): void;
|
|
63
|
-
}
|
|
64
|
-
/** Traces nothing: what a use case runs with until a platform plugs a tracer. */
|
|
65
|
-
export declare const NO_TRACER: Tracer;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* What a platform plugs so every use case is traced and every unexpected error reported.
|
|
3
|
-
*
|
|
4
|
-
* Context: sincpro_framework opens one span per use case and reports what nobody expected; a
|
|
5
|
-
* client has to do the same, and continue the backend's trace. The vendor — OpenTelemetry in a
|
|
6
|
-
* browser, a queue on a phone, GlitchTip, memory in a test — is the platform's, so this package
|
|
7
|
-
* defines the contract and calls it, and bundles none. Two implementations at least (a
|
|
8
|
-
* platform's and a test's) are why these are ports.
|
|
9
|
-
*
|
|
10
|
-
* Nothing here may break a use case: a tracer or a reporter that throws is ignored, and the use
|
|
11
|
-
* case answers as it would have.
|
|
12
|
-
*/
|
|
13
|
-
const EMPTY_SPAN = {
|
|
14
|
-
traceId: "",
|
|
15
|
-
spanId: "",
|
|
16
|
-
sampled: false,
|
|
17
|
-
set: () => undefined,
|
|
18
|
-
end: () => undefined,
|
|
19
|
-
};
|
|
20
|
-
/** Traces nothing: what a use case runs with until a platform plugs a tracer. */
|
|
21
|
-
export const NO_TRACER = { start: () => EMPTY_SPAN };
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { type Context } from "../context/context.js";
|
|
2
|
-
import type { ContextValues } from "../context/store.js";
|
|
3
|
-
import { type ServiceClasses, type Services } from "./feature.js";
|
|
4
|
-
/**
|
|
5
|
-
* A bounded context: its dependencies, its services, and the one object everything else uses
|
|
6
|
-
* them through — set up the way sincpro_framework sets one up.
|
|
7
|
-
*
|
|
8
|
-
* // domains/project/infrastructure/dependencies.ts ← what the services can count on
|
|
9
|
-
* export interface ProjectDependencies {
|
|
10
|
-
* client: ProjectClient;
|
|
11
|
-
* session: Session;
|
|
12
|
-
* }
|
|
13
|
-
* export function registerDependencies(bc: UseBoundedContext<ProjectDependencies>) {
|
|
14
|
-
* bc.addDependency("client", clientOf(transport));
|
|
15
|
-
* bc.addDependency("session", session);
|
|
16
|
-
* return bc;
|
|
17
|
-
* }
|
|
18
|
-
*
|
|
19
|
-
* // domains/project/infrastructure/bounded_context.ts
|
|
20
|
-
* export class Feature extends BaseFeature<ProjectDependencies> {}
|
|
21
|
-
* export class ApplicationService extends BaseApplicationService<ProjectDependencies, ProjectFeatures> {}
|
|
22
|
-
* export function configProjectBoundedContext(name: string) {
|
|
23
|
-
* return registerDependencies(new UseBoundedContext<ProjectDependencies>(name));
|
|
24
|
-
* }
|
|
25
|
-
*
|
|
26
|
-
* // domains/project/index.ts
|
|
27
|
-
* export const project = configProjectBoundedContext("project").register({
|
|
28
|
-
* features, // { Issues, Notices }
|
|
29
|
-
* applicationServices, // { CloseAndNotify }
|
|
30
|
-
* });
|
|
31
|
-
*
|
|
32
|
-
* // anywhere — a screen, another bounded context's adapter, a test
|
|
33
|
-
* await project.Issues.close(input); // span "project/Issues.close"
|
|
34
|
-
* await project.CloseAndNotify.execute(input); // span "project/CloseAndNotify", its Features under it
|
|
35
|
-
*
|
|
36
|
-
* Context: sincpro_framework's `UseFramework` (being renamed to bounded context) with
|
|
37
|
-
* `add_dependency`, and `project(Command(...))` written as a property: a service is reached by
|
|
38
|
-
* the name it is registered under, so its span is named by that name — a production build renames
|
|
39
|
-
* classes, never the keys of an object — and the type of `project` is the services it holds.
|
|
40
|
-
*
|
|
41
|
-
* `project` is light and never frozen. It holds references: the classes, the dependencies, and
|
|
42
|
-
* one instance of each service, made the first time it is asked for and shared by everything
|
|
43
|
-
* derived from it. Each call makes a small view of that instance — a few pointers — that carries
|
|
44
|
-
* the call's trace. A screen or a test that needs its own variant derives one, with everything
|
|
45
|
-
* else shared:
|
|
46
|
-
*
|
|
47
|
-
* project.with({ client: fake }) other dependencies — a test, a demo
|
|
48
|
-
* project.scoped({ company_id: 2 }) more context for what runs through it — a window
|
|
49
|
-
*
|
|
50
|
-
* A service is a singleton of its bounded context: what a method writes on `this` stays for the
|
|
51
|
-
* next call and for the call running beside it, as in sincpro_framework. What belongs to one
|
|
52
|
-
* request goes in its arguments or its context, never on `this`.
|
|
53
|
-
*/
|
|
54
|
-
export type BoundedContext<D extends object, C extends object, F extends ServiceClasses, A extends ServiceClasses> = Services<F, D> & Services<A, D> & {
|
|
55
|
-
readonly name: string;
|
|
56
|
-
/** This bounded context's level of the context: under the process's, over a scope's. */
|
|
57
|
-
readonly context: Context<C>;
|
|
58
|
-
/** The same bounded context with other dependencies — the rest, and the services, shared. */
|
|
59
|
-
with(dependencies: Partial<D>): BoundedContext<D, C, F, A>;
|
|
60
|
-
/** The same bounded context with more context for what runs through it. */
|
|
61
|
-
scoped(context: Partial<C>): BoundedContext<D, C, F, A>;
|
|
62
|
-
};
|
|
63
|
-
export declare class UseBoundedContext<D extends object, C extends object = ContextValues> {
|
|
64
|
-
#private;
|
|
65
|
-
readonly name: string;
|
|
66
|
-
/** This bounded context's level of the context, saved in the process's store. */
|
|
67
|
-
readonly context: Context<C>;
|
|
68
|
-
constructor(name: string);
|
|
69
|
-
/** `value` is what every service of this bounded context finds at `this.<name>`. */
|
|
70
|
-
addDependency<K extends keyof D & string>(name: K, value: D[K]): this;
|
|
71
|
-
/**
|
|
72
|
-
* The object the bounded context is used through: each service under its name. A dependency
|
|
73
|
-
* added after this is seen too — the services read them on every call.
|
|
74
|
-
*/
|
|
75
|
-
register<F extends ServiceClasses, A extends ServiceClasses = Record<never, never>>(services: {
|
|
76
|
-
features: F;
|
|
77
|
-
applicationServices?: A;
|
|
78
|
-
}): BoundedContext<D, C, F, A>;
|
|
79
|
-
}
|