@sincpro/client-framework 0.5.0 → 1.0.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 +54 -25
- package/MANIFESTO.md +59 -39
- package/README.md +329 -328
- package/dist/client/{memory.d.ts → adapters/memory.d.ts} +1 -1
- package/dist/client/{memory.js → adapters/memory.js} +7 -5
- package/dist/client/adapters/parquet.d.ts +50 -0
- package/dist/client/adapters/parquet.js +151 -0
- package/dist/client/{rest.d.ts → adapters/rest.d.ts} +7 -1
- package/dist/client/{rest.js → adapters/rest.js} +48 -3
- package/dist/client/{entity.d.ts → domain/entity.d.ts} +22 -11
- package/dist/client/{entity.js → domain/entity.js} +16 -1
- package/dist/client/{transport.d.ts → domain/transport.d.ts} +37 -1
- package/dist/client/{transport.js → domain/transport.js} +6 -0
- package/dist/client/index.d.ts +16 -0
- package/dist/client/index.js +16 -0
- package/dist/client/services/propagation.d.ts +32 -0
- package/dist/{services/execution.js → client/services/propagation.js} +8 -21
- package/dist/client/{retry.js → services/retry.js} +1 -1
- package/dist/codegen/{metamodel.d.ts → domain/metamodel.d.ts} +21 -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} +9 -8
- package/dist/codegen/index.d.ts +7 -7
- package/dist/codegen/index.js +5 -10
- package/dist/codegen/{client.d.ts → services/client.d.ts} +3 -1
- package/dist/codegen/{client.js → services/client.js} +34 -5
- package/dist/codegen/services/domain.d.ts +20 -0
- package/dist/codegen/{domain.js → services/domain.js} +121 -23
- package/dist/codegen/{entities.d.ts → services/entities.d.ts} +4 -2
- package/dist/codegen/{entities.js → services/entities.js} +34 -4
- package/dist/codegen/{rest.d.ts → services/rest.d.ts} +1 -1
- package/dist/codegen/{scaffold.d.ts → services/scaffold.d.ts} +2 -2
- package/dist/codegen/{scaffold.js → services/scaffold.js} +31 -36
- package/dist/common/collection.d.ts +110 -0
- package/dist/common/collection.js +169 -0
- package/dist/common/index.d.ts +10 -0
- package/dist/common/index.js +10 -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 +33 -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 +18 -0
- package/dist/core/domain/bus.js +18 -0
- package/dist/core/domain/dto.d.ts +32 -0
- package/dist/core/domain/dto.js +20 -0
- package/dist/core/domain/errors.d.ts +18 -0
- package/dist/core/domain/errors.js +1 -0
- package/dist/core/domain/services.d.ts +37 -0
- package/dist/core/domain/services.js +28 -0
- package/dist/core/entrypoint/bounded-context.d.ts +70 -0
- package/dist/core/entrypoint/bounded-context.js +252 -0
- package/dist/core/entrypoint/screens.d.ts +6 -0
- package/dist/core/entrypoint/screens.js +136 -0
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.js +16 -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 +13 -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 +35 -0
- package/dist/core/services/execution.js +137 -0
- package/dist/index.d.ts +16 -36
- package/dist/index.js +16 -36
- package/dist/model/{meta.d.ts → domain/meta.d.ts} +1 -0
- 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 +42 -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 +105 -0
- package/dist/observability/services/outcome.d.ts +12 -0
- package/dist/observability/services/outcome.js +20 -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 +25 -8
- 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 -67
- /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/{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.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,136 @@
|
|
|
1
|
+
import { Command } from "../domain/dto.js";
|
|
2
|
+
import { Feature } from "../domain/services.js";
|
|
3
|
+
import { BoundedContext } from "./bounded-context.js";
|
|
4
|
+
class List extends Command {
|
|
5
|
+
route;
|
|
6
|
+
criteria;
|
|
7
|
+
signal;
|
|
8
|
+
options;
|
|
9
|
+
constructor(route, criteria, signal, options) {
|
|
10
|
+
super();
|
|
11
|
+
this.route = route;
|
|
12
|
+
this.criteria = criteria;
|
|
13
|
+
this.signal = signal;
|
|
14
|
+
this.options = options;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
class Get extends Command {
|
|
18
|
+
route;
|
|
19
|
+
signal;
|
|
20
|
+
options;
|
|
21
|
+
constructor(route, signal, options) {
|
|
22
|
+
super();
|
|
23
|
+
this.route = route;
|
|
24
|
+
this.signal = signal;
|
|
25
|
+
this.options = options;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
class Read extends Command {
|
|
29
|
+
route;
|
|
30
|
+
signal;
|
|
31
|
+
options;
|
|
32
|
+
constructor(route, signal, options) {
|
|
33
|
+
super();
|
|
34
|
+
this.route = route;
|
|
35
|
+
this.signal = signal;
|
|
36
|
+
this.options = options;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
class Export extends Command {
|
|
40
|
+
route;
|
|
41
|
+
signal;
|
|
42
|
+
options;
|
|
43
|
+
constructor(route, signal, options) {
|
|
44
|
+
super();
|
|
45
|
+
this.route = route;
|
|
46
|
+
this.signal = signal;
|
|
47
|
+
this.options = options;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
class Send extends Command {
|
|
51
|
+
method;
|
|
52
|
+
route;
|
|
53
|
+
body;
|
|
54
|
+
signal;
|
|
55
|
+
options;
|
|
56
|
+
constructor(method, route, body, signal, options) {
|
|
57
|
+
super();
|
|
58
|
+
this.method = method;
|
|
59
|
+
this.route = route;
|
|
60
|
+
this.body = body;
|
|
61
|
+
this.signal = signal;
|
|
62
|
+
this.options = options;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
class EntityList extends Feature {
|
|
66
|
+
static serviceName = "EntityReads.list";
|
|
67
|
+
execute(dto) {
|
|
68
|
+
this.span.set({ "http.route": dto.route });
|
|
69
|
+
return this.transport.list(dto.route, dto.criteria, dto.signal, dto.options);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
class EntityGet extends Feature {
|
|
73
|
+
static serviceName = "EntityReads.one";
|
|
74
|
+
execute(dto) {
|
|
75
|
+
this.span.set({ "http.route": dto.route });
|
|
76
|
+
return this.transport.one(dto.route, dto.signal, dto.options);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
class EntityRead extends Feature {
|
|
80
|
+
static serviceName = "EntityReads.read";
|
|
81
|
+
execute(dto) {
|
|
82
|
+
this.span.set({ "http.route": dto.route });
|
|
83
|
+
const transport = this.transport;
|
|
84
|
+
if (!transport.read) {
|
|
85
|
+
return Promise.reject(new Error(`this transport cannot read ${dto.route}: it has no read()`));
|
|
86
|
+
}
|
|
87
|
+
return transport.read(dto.route, dto.signal, dto.options);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
class EntityExport extends Feature {
|
|
91
|
+
static serviceName = "EntityReads.download";
|
|
92
|
+
execute(dto) {
|
|
93
|
+
this.span.set({ "http.route": dto.route });
|
|
94
|
+
const transport = this.transport;
|
|
95
|
+
if (!transport.download) {
|
|
96
|
+
return Promise.reject(new Error(`this transport cannot download ${dto.route}: it has no download()`));
|
|
97
|
+
}
|
|
98
|
+
return transport.download(dto.route, dto.signal, dto.options);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
class EntitySend extends Feature {
|
|
102
|
+
static serviceName = "EntityWrites.send";
|
|
103
|
+
execute(dto) {
|
|
104
|
+
this.span.set({ "http.method": dto.method, "http.route": dto.route });
|
|
105
|
+
return this.transport.send(dto.method, dto.route, dto.body, dto.signal, dto.options);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const MADE = new WeakSet();
|
|
109
|
+
/**
|
|
110
|
+
* Screen requests enter the same command pipeline as application services. A generated
|
|
111
|
+
* client injected into a Feature uses its raw transport instead, continuing that execution.
|
|
112
|
+
*/
|
|
113
|
+
export function screensTransport(transport, name = "screens") {
|
|
114
|
+
if (MADE.has(transport))
|
|
115
|
+
return transport;
|
|
116
|
+
const screens = new BoundedContext(name);
|
|
117
|
+
screens.addDependency("transport", transport);
|
|
118
|
+
screens.feature(List)(EntityList);
|
|
119
|
+
screens.feature(Get)(EntityGet);
|
|
120
|
+
screens.feature(Read)(EntityRead);
|
|
121
|
+
screens.feature(Export)(EntityExport);
|
|
122
|
+
screens.feature(Send)(EntitySend);
|
|
123
|
+
const through = {
|
|
124
|
+
list: (route, criteria, signal, options) => screens.execute(new List(route, criteria, signal, options)),
|
|
125
|
+
one: (route, signal, options) => screens.execute(new Get(route, signal, options)),
|
|
126
|
+
send: (method, route, body, signal, options) => screens.execute(new Send(method, route, body, signal, options)),
|
|
127
|
+
};
|
|
128
|
+
if (transport.read) {
|
|
129
|
+
through.read = (route, signal, options) => screens.execute(new Read(route, signal, options));
|
|
130
|
+
}
|
|
131
|
+
if (transport.download) {
|
|
132
|
+
through.download = (route, signal, options) => screens.execute(new Export(route, signal, options));
|
|
133
|
+
}
|
|
134
|
+
MADE.add(through);
|
|
135
|
+
return through;
|
|
136
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The application core (`./core`): the backend's application abstractions — class Commands and
|
|
3
|
+
* Responses, Features and ApplicationServices, a bounded context that is their bus, its error
|
|
4
|
+
* handlers — and the process they run in.
|
|
5
|
+
*
|
|
6
|
+
* domain/ DTOs, the bus, the service bases, the error-handler contract
|
|
7
|
+
* services/ the execution pipeline, dependency binding, the handler chain
|
|
8
|
+
* entrypoint/ `BoundedContext`, `screensTransport`
|
|
9
|
+
* infrastructure/ `configureProcess`, `configureLogger`
|
|
10
|
+
*/
|
|
11
|
+
export { type CommandBus, FeatureBus } from "./domain/bus.js";
|
|
12
|
+
export { Command, DataTransferObject, Response, } from "./domain/dto.js";
|
|
13
|
+
export type { ErrorClass, ErrorHandler } from "./domain/errors.js";
|
|
14
|
+
export { ApplicationService, Feature } from "./domain/services.js";
|
|
15
|
+
export { BoundedContext } from "./entrypoint/bounded-context.js";
|
|
16
|
+
export { screensTransport } from "./entrypoint/screens.js";
|
|
17
|
+
export { configureLogger, configureProcess, type LoggerConfiguration, type ProcessOptions, } from "./infrastructure/process.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The application core (`./core`): the backend's application abstractions — class Commands and
|
|
3
|
+
* Responses, Features and ApplicationServices, a bounded context that is their bus, its error
|
|
4
|
+
* handlers — and the process they run in.
|
|
5
|
+
*
|
|
6
|
+
* domain/ DTOs, the bus, the service bases, the error-handler contract
|
|
7
|
+
* services/ the execution pipeline, dependency binding, the handler chain
|
|
8
|
+
* entrypoint/ `BoundedContext`, `screensTransport`
|
|
9
|
+
* infrastructure/ `configureProcess`, `configureLogger`
|
|
10
|
+
*/
|
|
11
|
+
export { FeatureBus } from "./domain/bus.js";
|
|
12
|
+
export { Command, DataTransferObject, Response, } from "./domain/dto.js";
|
|
13
|
+
export { ApplicationService, Feature } from "./domain/services.js";
|
|
14
|
+
export { BoundedContext } from "./entrypoint/bounded-context.js";
|
|
15
|
+
export { screensTransport } from "./entrypoint/screens.js";
|
|
16
|
+
export { configureLogger, configureProcess, } from "./infrastructure/process.js";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ContextStore } from "../../context/domain/store.js";
|
|
2
|
+
import { type ConsoleLoggerOptions } from "../../observability/adapters/console.js";
|
|
3
|
+
import type { ErrorReporter } from "../../observability/domain/errors.js";
|
|
4
|
+
import type { Logger } from "../../observability/domain/logging.js";
|
|
5
|
+
import { type Outcome, type Tracer } from "../../observability/domain/tracing.js";
|
|
6
|
+
/**
|
|
7
|
+
* Where logs go: the console by default, any `Logger` otherwise, or nowhere.
|
|
8
|
+
*
|
|
9
|
+
* { mode: "development", level: "info", layers: { adapter: false } } // the console, filtered
|
|
10
|
+
* { mode: "production" } // the console, silent
|
|
11
|
+
* { engine: combineLoggers(consoleLogger({ mode }), lokiLogger) } // the platform's sinks
|
|
12
|
+
* { engine: false } // no logs
|
|
13
|
+
*
|
|
14
|
+
* Context: one shape for both doors — `configureProcess({ logger })`, which replaces everything,
|
|
15
|
+
* and `configureLogger(...)`, which replaces only logging (a debug switch on a phone). `mode`,
|
|
16
|
+
* `level` and `layers` configure the console engine; a supplied `engine` owns its own policy.
|
|
17
|
+
*/
|
|
18
|
+
export type LoggerConfiguration = Partial<ConsoleLoggerOptions> & {
|
|
19
|
+
engine?: Logger | false;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* What every bounded context of this process runs with — set once, in `main`, the way
|
|
23
|
+
* sincpro_framework's process is: who traces, who reports, where logs go, where the context lives.
|
|
24
|
+
*
|
|
25
|
+
* // entrypoints/main.tsx — the platform's pieces, built from the application's settings
|
|
26
|
+
* configureProcess({
|
|
27
|
+
* tracer: browserTracer({ endpoint: settings.otlp, service: "forge-web" }),
|
|
28
|
+
* reporter: glitchtipReporter({ dsn: settings.glitchtip }),
|
|
29
|
+
* logger: { mode: settings.production ? "production" : "development" },
|
|
30
|
+
* context: memoryContexts(),
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* Context: everything is optional, and nothing breaks without it. A process that never configures
|
|
34
|
+
* runs untraced and unreported, with memory context and development console logging — so a bounded
|
|
35
|
+
* context is usable the moment it is imported, and `main` adds what it watches with. The bounded
|
|
36
|
+
* contexts read the process on every call, never at import: `project` may be defined before `main`
|
|
37
|
+
* runs.
|
|
38
|
+
*
|
|
39
|
+
* It replaces everything at once: what is not given goes back to its default — a test calls it
|
|
40
|
+
* with its own tracer and starts from a clean context.
|
|
41
|
+
*/
|
|
42
|
+
export type ProcessOptions = {
|
|
43
|
+
/** The platform's (OpenTelemetry in a browser, a queue on a phone, memory in a test). */
|
|
44
|
+
tracer?: Tracer;
|
|
45
|
+
/** The platform's (GlitchTip, Sentry, memory in a test). Only what nobody expected reaches it. */
|
|
46
|
+
reporter?: ErrorReporter;
|
|
47
|
+
/** Where logs go (`LoggerConfiguration`); the console in development mode by default. */
|
|
48
|
+
logger?: LoggerConfiguration;
|
|
49
|
+
/** Where the context lives (`memoryContexts()` by default). */
|
|
50
|
+
context?: ContextStore;
|
|
51
|
+
/** How an error ended a use case (`outcomeOf` by default). Only `failed` is reported. */
|
|
52
|
+
outcomeOf?: (error: unknown) => Outcome;
|
|
53
|
+
};
|
|
54
|
+
export declare function configureProcess(options?: ProcessOptions): void;
|
|
55
|
+
/** Replace logging only; tracing, reporting and the context stay as they are. */
|
|
56
|
+
export declare function configureLogger(options?: LoggerConfiguration): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { memoryContexts } from "../../context/adapters/memory.js";
|
|
2
|
+
import { replaceProcessStore } from "../../context/infrastructure/process.js";
|
|
3
|
+
import { consoleLogger, } from "../../observability/adapters/console.js";
|
|
4
|
+
import { NO_TRACER, } from "../../observability/domain/tracing.js";
|
|
5
|
+
import { replaceObservability } from "../../observability/infrastructure/registry.js";
|
|
6
|
+
import { outcomeOf } from "../../observability/services/outcome.js";
|
|
7
|
+
function loggingEngine(options) {
|
|
8
|
+
if (options.engine === false)
|
|
9
|
+
return undefined;
|
|
10
|
+
if (options.engine !== undefined) {
|
|
11
|
+
if (!options.engine || typeof options.engine.log !== "function") {
|
|
12
|
+
throw new TypeError("Logger engine must implement log(record)");
|
|
13
|
+
}
|
|
14
|
+
return options.engine;
|
|
15
|
+
}
|
|
16
|
+
return consoleLogger({
|
|
17
|
+
mode: options.mode ?? "development",
|
|
18
|
+
...(options.level ? { level: options.level } : {}),
|
|
19
|
+
...(options.layers ? { layers: options.layers } : {}),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export function configureProcess(options = {}) {
|
|
23
|
+
const logger = loggingEngine(options.logger ?? {});
|
|
24
|
+
replaceObservability({
|
|
25
|
+
tracer: options.tracer ?? NO_TRACER,
|
|
26
|
+
reporter: options.reporter,
|
|
27
|
+
logger,
|
|
28
|
+
outcomeOf: options.outcomeOf ?? outcomeOf,
|
|
29
|
+
});
|
|
30
|
+
replaceProcessStore(options.context ?? memoryContexts());
|
|
31
|
+
}
|
|
32
|
+
/** Replace logging only; tracing, reporting and the context stay as they are. */
|
|
33
|
+
export function configureLogger(options = {}) {
|
|
34
|
+
replaceObservability({ logger: loggingEngine(options) });
|
|
35
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Execution } from "../../context/domain/execution.js";
|
|
2
|
+
/**
|
|
3
|
+
* A dependency as the execution should see it: bound when it can be (`BIND`, or a `Transport`),
|
|
4
|
+
* as it is otherwise.
|
|
5
|
+
*/
|
|
6
|
+
export declare function bindDependency<T>(value: T, execution: Execution): T;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { bindTransport, isTransport, } from "../../client/services/propagation.js";
|
|
2
|
+
import { BIND, isBindable, } from "../../context/domain/execution.js";
|
|
3
|
+
/**
|
|
4
|
+
* A dependency as the execution should see it: bound when it can be (`BIND`, or a `Transport`),
|
|
5
|
+
* as it is otherwise.
|
|
6
|
+
*/
|
|
7
|
+
export function bindDependency(value, execution) {
|
|
8
|
+
if (isBindable(value))
|
|
9
|
+
return value[BIND](execution);
|
|
10
|
+
if (isTransport(value))
|
|
11
|
+
return bindTransport(value, execution);
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ErrorHandler } from "../domain/errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* The answer of a chain of error handlers to `error`, or what its last handler threw.
|
|
4
|
+
*
|
|
5
|
+
* await handled([toEmpty, logAndRethrow], new IssueNotFound()) -> toEmpty's answer
|
|
6
|
+
*
|
|
7
|
+
* 1. Each handler, first-registered-first, receives what the one before it threw.
|
|
8
|
+
* Final: the first answer; when every handler threw, the last thrown — the original error when
|
|
9
|
+
* there is no handler at all.
|
|
10
|
+
*/
|
|
11
|
+
export declare function handled(handlers: readonly ErrorHandler[], error: unknown): Promise<unknown>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The answer of a chain of error handlers to `error`, or what its last handler threw.
|
|
3
|
+
*
|
|
4
|
+
* await handled([toEmpty, logAndRethrow], new IssueNotFound()) -> toEmpty's answer
|
|
5
|
+
*
|
|
6
|
+
* 1. Each handler, first-registered-first, receives what the one before it threw.
|
|
7
|
+
* Final: the first answer; when every handler threw, the last thrown — the original error when
|
|
8
|
+
* there is no handler at all.
|
|
9
|
+
*/
|
|
10
|
+
export async function handled(handlers, error) {
|
|
11
|
+
let current = error;
|
|
12
|
+
for (const handler of handlers) {
|
|
13
|
+
try {
|
|
14
|
+
return await handler(current);
|
|
15
|
+
}
|
|
16
|
+
catch (thrown) {
|
|
17
|
+
current = thrown;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
throw current;
|
|
21
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Execution } from "../../context/domain/execution.js";
|
|
2
|
+
import type { ContextValues } from "../../context/domain/store.js";
|
|
3
|
+
import type { ErrorHandler } from "../domain/errors.js";
|
|
4
|
+
export interface RuntimeService {
|
|
5
|
+
execute(command: object): unknown;
|
|
6
|
+
}
|
|
7
|
+
type Layer = "feature" | "application_service";
|
|
8
|
+
export interface ExecutionOptions {
|
|
9
|
+
readonly boundedContext: string;
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly layer: Layer;
|
|
12
|
+
readonly dependencies: ReadonlyMap<string, unknown>;
|
|
13
|
+
readonly context: () => ContextValues;
|
|
14
|
+
readonly parent?: Execution;
|
|
15
|
+
readonly featureBus: (execution: Execution) => object;
|
|
16
|
+
/** The bounded context's expected errors: they end `refused`, never reported. */
|
|
17
|
+
readonly expected: (error: unknown) => boolean;
|
|
18
|
+
/** The bounded context's handlers for this layer: what answers a failure instead of raising it. */
|
|
19
|
+
readonly handlers: readonly ErrorHandler[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* One pipeline for every registered Feature and ApplicationService.
|
|
23
|
+
*
|
|
24
|
+
* 1. A span `<bounded context>/<serviceName>`, a child of the parent run when there is one, and
|
|
25
|
+
* the execution every dependency, `this.logger` and child Feature are bound to.
|
|
26
|
+
* 2. The service runs, seeing that execution through its own view of the instance.
|
|
27
|
+
* 3. A failure is observed where it happened: its outcome (expected by the bounded context →
|
|
28
|
+
* `refused`, else the process's `outcomeOf`), the span closed with it, its log line and —
|
|
29
|
+
* when `failed`, and only once per error along the run — its report.
|
|
30
|
+
* 4. Then the layer's error handlers answer it, in order; with none, or when the last throws,
|
|
31
|
+
* the error reaches the caller.
|
|
32
|
+
* Final: the service's answer, or its handlers'.
|
|
33
|
+
*/
|
|
34
|
+
export declare function executeService(target: RuntimeService, command: object, options: ExecutionOptions): Promise<unknown>;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { newEntityId } from "../../common/ids.js";
|
|
2
|
+
import { bindDependency } from "./binding.js";
|
|
3
|
+
import { handled } from "./error-handlers.js";
|
|
4
|
+
import { NO_TRACER, } from "../../observability/domain/tracing.js";
|
|
5
|
+
import { observabilityNow } from "../../observability/infrastructure/registry.js";
|
|
6
|
+
import { executionLogger, logExecution, logHandled, } from "../../observability/services/logging.js";
|
|
7
|
+
import { firstReport } from "../../observability/services/reporting.js";
|
|
8
|
+
const REPORTED_BY_EXECUTION = new WeakMap();
|
|
9
|
+
/** Observability adapters are best-effort; their failures must not replace a business result. */
|
|
10
|
+
function observe(work, fallback) {
|
|
11
|
+
try {
|
|
12
|
+
return work();
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return fallback;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function serviceView(target, options, execution) {
|
|
19
|
+
const dependencies = new Map();
|
|
20
|
+
const logger = executionLogger(options.layer, execution);
|
|
21
|
+
let features;
|
|
22
|
+
return new Proxy(target, {
|
|
23
|
+
get(held, property, receiver) {
|
|
24
|
+
if (property === "context")
|
|
25
|
+
return execution.context;
|
|
26
|
+
if (property === "span")
|
|
27
|
+
return execution.span;
|
|
28
|
+
if (property === "logger")
|
|
29
|
+
return logger;
|
|
30
|
+
if (property === "featureBus" && options.layer === "application_service") {
|
|
31
|
+
return (features ??= options.featureBus(execution));
|
|
32
|
+
}
|
|
33
|
+
if (typeof property === "string" && options.dependencies.has(property)) {
|
|
34
|
+
if (!dependencies.has(property)) {
|
|
35
|
+
dependencies.set(property, bindDependency(options.dependencies.get(property), execution));
|
|
36
|
+
}
|
|
37
|
+
return dependencies.get(property);
|
|
38
|
+
}
|
|
39
|
+
if (Reflect.has(held, property))
|
|
40
|
+
return Reflect.get(held, property, receiver);
|
|
41
|
+
if (typeof property === "string") {
|
|
42
|
+
throw new Error(`${options.boundedContext}: dependency or member is not registered: ${property}`);
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
},
|
|
46
|
+
set(held, property, value) {
|
|
47
|
+
if (property === "context" ||
|
|
48
|
+
property === "span" ||
|
|
49
|
+
property === "logger" ||
|
|
50
|
+
property === "featureBus" ||
|
|
51
|
+
(typeof property === "string" && options.dependencies.has(property))) {
|
|
52
|
+
throw new Error(`Execution dependencies and context are read-only: ${String(property)}`);
|
|
53
|
+
}
|
|
54
|
+
return Reflect.set(held, property, value);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* One pipeline for every registered Feature and ApplicationService.
|
|
60
|
+
*
|
|
61
|
+
* 1. A span `<bounded context>/<serviceName>`, a child of the parent run when there is one, and
|
|
62
|
+
* the execution every dependency, `this.logger` and child Feature are bound to.
|
|
63
|
+
* 2. The service runs, seeing that execution through its own view of the instance.
|
|
64
|
+
* 3. A failure is observed where it happened: its outcome (expected by the bounded context →
|
|
65
|
+
* `refused`, else the process's `outcomeOf`), the span closed with it, its log line and —
|
|
66
|
+
* when `failed`, and only once per error along the run — its report.
|
|
67
|
+
* 4. Then the layer's error handlers answer it, in order; with none, or when the last throws,
|
|
68
|
+
* the error reaches the caller.
|
|
69
|
+
* Final: the service's answer, or its handlers'.
|
|
70
|
+
*/
|
|
71
|
+
export async function executeService(target, command, options) {
|
|
72
|
+
const ports = observabilityNow();
|
|
73
|
+
const startedAt = Date.now();
|
|
74
|
+
const useCase = `${options.boundedContext}.${options.name}`;
|
|
75
|
+
const context = options.parent?.context ?? options.context();
|
|
76
|
+
const id = newEntityId();
|
|
77
|
+
const attributes = {
|
|
78
|
+
"sincpro.use_case": useCase,
|
|
79
|
+
"sincpro.layer": options.layer,
|
|
80
|
+
"sincpro.context": options.boundedContext,
|
|
81
|
+
"sincpro.execution_id": id,
|
|
82
|
+
};
|
|
83
|
+
const span = observe(() => ports.tracer.start(`${options.boundedContext}/${options.name}`, {
|
|
84
|
+
...(options.parent ? { parent: options.parent.span } : {}),
|
|
85
|
+
attributes,
|
|
86
|
+
}), NO_TRACER.start(useCase, {}));
|
|
87
|
+
const execution = {
|
|
88
|
+
id,
|
|
89
|
+
useCase,
|
|
90
|
+
boundedContext: options.boundedContext,
|
|
91
|
+
parent: options.parent,
|
|
92
|
+
span,
|
|
93
|
+
context,
|
|
94
|
+
};
|
|
95
|
+
const reported = options.parent
|
|
96
|
+
? (REPORTED_BY_EXECUTION.get(options.parent) ?? new Set())
|
|
97
|
+
: new Set();
|
|
98
|
+
REPORTED_BY_EXECUTION.set(execution, reported);
|
|
99
|
+
logExecution(options.layer, execution);
|
|
100
|
+
const finish = (error) => {
|
|
101
|
+
const outcome = error
|
|
102
|
+
? observe(() => (options.expected(error.value) ? "refused" : ports.outcomeOf(error.value)), "failed")
|
|
103
|
+
: "ok";
|
|
104
|
+
observe(() => span.end(outcome, error?.value), undefined);
|
|
105
|
+
logExecution(options.layer, execution, {
|
|
106
|
+
outcome,
|
|
107
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
108
|
+
});
|
|
109
|
+
if (outcome === "failed" && error && ports.reporter && !reported.has(error.value)) {
|
|
110
|
+
reported.add(error.value);
|
|
111
|
+
// Mark for reportError outside the call; a different root may report the same Error.
|
|
112
|
+
firstReport(error.value);
|
|
113
|
+
observe(() => ports.reporter?.report(error.value, {
|
|
114
|
+
useCase,
|
|
115
|
+
traceId: span.traceId,
|
|
116
|
+
executionId: id,
|
|
117
|
+
context,
|
|
118
|
+
}), undefined);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
let answer;
|
|
122
|
+
try {
|
|
123
|
+
answer = await Reflect.apply(target.execute, serviceView(target, options, execution), [
|
|
124
|
+
command,
|
|
125
|
+
]);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
finish({ value: error });
|
|
129
|
+
if (options.handlers.length === 0)
|
|
130
|
+
throw error;
|
|
131
|
+
const recovered = await handled(options.handlers, error);
|
|
132
|
+
logHandled(options.layer, { execution });
|
|
133
|
+
return recovered;
|
|
134
|
+
}
|
|
135
|
+
finish();
|
|
136
|
+
return answer;
|
|
137
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,43 +3,23 @@
|
|
|
3
3
|
* service needs, on any platform.
|
|
4
4
|
*
|
|
5
5
|
* Context: one entry, not one per area — the package has no side effects at import
|
|
6
|
-
* (`sideEffects: false`), so an application pays only for what it uses
|
|
7
|
-
*
|
|
6
|
+
* (`sideEffects: false`), so an application pays only for what it uses. Each module is laid out
|
|
7
|
+
* as sincpro_framework's are — `domain/` (ports and rules), `adapters/` (their implementations),
|
|
8
|
+
* `services/`, `infrastructure/`, `entrypoint/` — and says what it offers in its own `index.ts`:
|
|
8
9
|
*
|
|
9
|
-
* common/
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* model/
|
|
14
|
-
*
|
|
15
|
-
* the process they run in (`configureProcess`), what each run carries
|
|
16
|
-
* context/ the context: one door (`Context`) over the store each platform plugs
|
|
17
|
-
* observability/ the ports a platform plugs: a tracer and an error reporter
|
|
10
|
+
* common/ the values of the wire, as the backend writes them
|
|
11
|
+
* context/ the context's levels, where they live, what travels, the execution
|
|
12
|
+
* observability/ tracing, error reporting and logging: ports, console, loggers
|
|
13
|
+
* client/ the seam with a server: transports, entities, the client contract
|
|
14
|
+
* model/ model definitions
|
|
15
|
+
* core/ Commands, Features, ApplicationServices, BoundedContext, the process
|
|
18
16
|
*
|
|
19
|
-
* The generator is `@sincpro/client-framework/codegen`, and the in-memory
|
|
17
|
+
* The generator is `@sincpro/client-framework/codegen`, and the in-memory observability
|
|
20
18
|
* `@sincpro/client-framework/testing`: neither is part of what an application runs.
|
|
21
19
|
*/
|
|
22
|
-
export
|
|
23
|
-
export
|
|
24
|
-
export
|
|
25
|
-
export
|
|
26
|
-
export
|
|
27
|
-
export
|
|
28
|
-
export { Decimal, type DecimalLike } from "./common/decimal.js";
|
|
29
|
-
export { isEntityId, momentOf, newEntityId } from "./common/ids.js";
|
|
30
|
-
export { Money } from "./common/money.js";
|
|
31
|
-
export { BYTES, type JsonValue, pack, readValues, type Rebuild, rebuilt, unpack, valuesOf, } from "./common/serialization.js";
|
|
32
|
-
export { dayOf, installTimeZone, instantOf, isDay, timeZoneNow, wallTimeOf, } from "./common/time.js";
|
|
33
|
-
export { CONTEXT_HEADER, type ContextCodec, plainCodec, travelling, travels, } from "./context/codec.js";
|
|
34
|
-
export { type Context } from "./context/context.js";
|
|
35
|
-
export { type ContextStore, type ContextValues, memoryContexts, } from "./context/store.js";
|
|
36
|
-
export { defineMeta, field, type FieldOptions, type MetaSpec, type Words, } from "./model/meta.js";
|
|
37
|
-
export { type MetaStore, metaStore } from "./model/metas.js";
|
|
38
|
-
export { completeMeta, type JsonSchema, metaFromSchema, type SchemaComponents, } from "./model/schema.js";
|
|
39
|
-
export { type Attributes, type ErrorAbout, type ErrorReporter, NO_TRACER, type Outcome, type Span, type Tracer, } from "./observability/ports.js";
|
|
40
|
-
export { type BoundedContext, UseBoundedContext, } from "./services/bounded-context.js";
|
|
41
|
-
export { BIND, type Bindable, bindable, bindDependency, bindTransport, type Execution, headersOf, } from "./services/execution.js";
|
|
42
|
-
export { ApplicationService, Feature, type ServiceClasses, type ServiceMethods, type Services, } from "./services/feature.js";
|
|
43
|
-
export { configureProcess, outcomeOf, processContext, type ProcessOptions, } from "./services/process.js";
|
|
44
|
-
export { reportError } from "./services/report.js";
|
|
45
|
-
export { screensTransport } from "./services/screens.js";
|
|
20
|
+
export * from "./client/index.js";
|
|
21
|
+
export * from "./common/index.js";
|
|
22
|
+
export * from "./context/index.js";
|
|
23
|
+
export * from "./core/index.js";
|
|
24
|
+
export * from "./model/index.js";
|
|
25
|
+
export * from "./observability/index.js";
|
package/dist/index.js
CHANGED
|
@@ -3,43 +3,23 @@
|
|
|
3
3
|
* service needs, on any platform.
|
|
4
4
|
*
|
|
5
5
|
* Context: one entry, not one per area — the package has no side effects at import
|
|
6
|
-
* (`sideEffects: false`), so an application pays only for what it uses
|
|
7
|
-
*
|
|
6
|
+
* (`sideEffects: false`), so an application pays only for what it uses. Each module is laid out
|
|
7
|
+
* as sincpro_framework's are — `domain/` (ports and rules), `adapters/` (their implementations),
|
|
8
|
+
* `services/`, `infrastructure/`, `entrypoint/` — and says what it offers in its own `index.ts`:
|
|
8
9
|
*
|
|
9
|
-
* common/
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* model/
|
|
14
|
-
*
|
|
15
|
-
* the process they run in (`configureProcess`), what each run carries
|
|
16
|
-
* context/ the context: one door (`Context`) over the store each platform plugs
|
|
17
|
-
* observability/ the ports a platform plugs: a tracer and an error reporter
|
|
10
|
+
* common/ the values of the wire, as the backend writes them
|
|
11
|
+
* context/ the context's levels, where they live, what travels, the execution
|
|
12
|
+
* observability/ tracing, error reporting and logging: ports, console, loggers
|
|
13
|
+
* client/ the seam with a server: transports, entities, the client contract
|
|
14
|
+
* model/ model definitions
|
|
15
|
+
* core/ Commands, Features, ApplicationServices, BoundedContext, the process
|
|
18
16
|
*
|
|
19
|
-
* The generator is `@sincpro/client-framework/codegen`, and the in-memory
|
|
17
|
+
* The generator is `@sincpro/client-framework/codegen`, and the in-memory observability
|
|
20
18
|
* `@sincpro/client-framework/testing`: neither is part of what an application runs.
|
|
21
19
|
*/
|
|
22
|
-
export
|
|
23
|
-
export
|
|
24
|
-
export
|
|
25
|
-
export
|
|
26
|
-
export
|
|
27
|
-
export
|
|
28
|
-
export { Decimal } from "./common/decimal.js";
|
|
29
|
-
export { isEntityId, momentOf, newEntityId } from "./common/ids.js";
|
|
30
|
-
export { Money } from "./common/money.js";
|
|
31
|
-
export { BYTES, pack, readValues, rebuilt, unpack, valuesOf, } from "./common/serialization.js";
|
|
32
|
-
export { dayOf, installTimeZone, instantOf, isDay, timeZoneNow, wallTimeOf, } from "./common/time.js";
|
|
33
|
-
export { CONTEXT_HEADER, plainCodec, travelling, travels, } from "./context/codec.js";
|
|
34
|
-
export {} from "./context/context.js";
|
|
35
|
-
export { memoryContexts, } from "./context/store.js";
|
|
36
|
-
export { defineMeta, field, } from "./model/meta.js";
|
|
37
|
-
export { metaStore } from "./model/metas.js";
|
|
38
|
-
export { completeMeta, metaFromSchema, } from "./model/schema.js";
|
|
39
|
-
export { NO_TRACER, } from "./observability/ports.js";
|
|
40
|
-
export { UseBoundedContext, } from "./services/bounded-context.js";
|
|
41
|
-
export { BIND, bindable, bindDependency, bindTransport, headersOf, } from "./services/execution.js";
|
|
42
|
-
export { ApplicationService, Feature, } from "./services/feature.js";
|
|
43
|
-
export { configureProcess, outcomeOf, processContext, } from "./services/process.js";
|
|
44
|
-
export { reportError } from "./services/report.js";
|
|
45
|
-
export { screensTransport } from "./services/screens.js";
|
|
20
|
+
export * from "./client/index.js";
|
|
21
|
+
export * from "./common/index.js";
|
|
22
|
+
export * from "./context/index.js";
|
|
23
|
+
export * from "./core/index.js";
|
|
24
|
+
export * from "./model/index.js";
|
|
25
|
+
export * from "./observability/index.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model definitions: written by hand (`defineMeta`), read from a JSON Schema, kept once known.
|
|
3
|
+
*
|
|
4
|
+
* domain/ `defineMeta`, `field`
|
|
5
|
+
* services/ `metaFromSchema`, `completeMeta`
|
|
6
|
+
* infrastructure/ `metaStore`: the definitions already known
|
|
7
|
+
*/
|
|
8
|
+
export { defineMeta, field, type FieldOptions, type MetaSpec, type Words, } from "./domain/meta.js";
|
|
9
|
+
export { type MetaStore, metaStore, } from "./infrastructure/metas.js";
|
|
10
|
+
export { completeMeta, type JsonSchema, metaFromSchema, type SchemaComponents, } from "./services/schema.js";
|