@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.
Files changed (134) hide show
  1. package/AGENTS.md +54 -25
  2. package/MANIFESTO.md +59 -39
  3. package/README.md +329 -328
  4. package/dist/client/{memory.d.ts → adapters/memory.d.ts} +1 -1
  5. package/dist/client/{memory.js → adapters/memory.js} +7 -5
  6. package/dist/client/adapters/parquet.d.ts +50 -0
  7. package/dist/client/adapters/parquet.js +151 -0
  8. package/dist/client/{rest.d.ts → adapters/rest.d.ts} +7 -1
  9. package/dist/client/{rest.js → adapters/rest.js} +48 -3
  10. package/dist/client/{entity.d.ts → domain/entity.d.ts} +22 -11
  11. package/dist/client/{entity.js → domain/entity.js} +16 -1
  12. package/dist/client/{transport.d.ts → domain/transport.d.ts} +37 -1
  13. package/dist/client/{transport.js → domain/transport.js} +6 -0
  14. package/dist/client/index.d.ts +16 -0
  15. package/dist/client/index.js +16 -0
  16. package/dist/client/services/propagation.d.ts +32 -0
  17. package/dist/{services/execution.js → client/services/propagation.js} +8 -21
  18. package/dist/client/{retry.js → services/retry.js} +1 -1
  19. package/dist/codegen/{metamodel.d.ts → domain/metamodel.d.ts} +21 -2
  20. package/dist/codegen/{names.d.ts → domain/names.d.ts} +2 -2
  21. package/dist/codegen/{names.js → domain/names.js} +3 -3
  22. package/dist/codegen/{cli.js → entrypoint/cli.js} +9 -8
  23. package/dist/codegen/index.d.ts +7 -7
  24. package/dist/codegen/index.js +5 -10
  25. package/dist/codegen/{client.d.ts → services/client.d.ts} +3 -1
  26. package/dist/codegen/{client.js → services/client.js} +34 -5
  27. package/dist/codegen/services/domain.d.ts +20 -0
  28. package/dist/codegen/{domain.js → services/domain.js} +121 -23
  29. package/dist/codegen/{entities.d.ts → services/entities.d.ts} +4 -2
  30. package/dist/codegen/{entities.js → services/entities.js} +34 -4
  31. package/dist/codegen/{rest.d.ts → services/rest.d.ts} +1 -1
  32. package/dist/codegen/{scaffold.d.ts → services/scaffold.d.ts} +2 -2
  33. package/dist/codegen/{scaffold.js → services/scaffold.js} +31 -36
  34. package/dist/common/collection.d.ts +110 -0
  35. package/dist/common/collection.js +169 -0
  36. package/dist/common/index.d.ts +10 -0
  37. package/dist/common/index.js +10 -0
  38. package/dist/context/adapters/memory.d.ts +6 -0
  39. package/dist/context/adapters/memory.js +34 -0
  40. package/dist/context/adapters/plain-codec.d.ts +3 -0
  41. package/dist/context/adapters/plain-codec.js +16 -0
  42. package/dist/context/{codec.d.ts → domain/codec.d.ts} +0 -1
  43. package/dist/context/{codec.js → domain/codec.js} +0 -14
  44. package/dist/context/domain/execution.d.ts +33 -0
  45. package/dist/context/domain/execution.js +11 -0
  46. package/dist/context/{store.d.ts → domain/store.d.ts} +0 -5
  47. package/dist/context/{store.js → domain/store.js} +1 -34
  48. package/dist/context/{context.d.ts → entrypoint/context.d.ts} +1 -1
  49. package/dist/context/index.d.ts +17 -0
  50. package/dist/context/index.js +15 -0
  51. package/dist/context/infrastructure/process.d.ts +13 -0
  52. package/dist/context/infrastructure/process.js +35 -0
  53. package/dist/core/domain/bus.d.ts +18 -0
  54. package/dist/core/domain/bus.js +18 -0
  55. package/dist/core/domain/dto.d.ts +32 -0
  56. package/dist/core/domain/dto.js +20 -0
  57. package/dist/core/domain/errors.d.ts +18 -0
  58. package/dist/core/domain/errors.js +1 -0
  59. package/dist/core/domain/services.d.ts +37 -0
  60. package/dist/core/domain/services.js +28 -0
  61. package/dist/core/entrypoint/bounded-context.d.ts +70 -0
  62. package/dist/core/entrypoint/bounded-context.js +252 -0
  63. package/dist/core/entrypoint/screens.d.ts +6 -0
  64. package/dist/core/entrypoint/screens.js +136 -0
  65. package/dist/core/index.d.ts +17 -0
  66. package/dist/core/index.js +16 -0
  67. package/dist/core/infrastructure/process.d.ts +56 -0
  68. package/dist/core/infrastructure/process.js +35 -0
  69. package/dist/core/services/binding.d.ts +6 -0
  70. package/dist/core/services/binding.js +13 -0
  71. package/dist/core/services/error-handlers.d.ts +11 -0
  72. package/dist/core/services/error-handlers.js +21 -0
  73. package/dist/core/services/execution.d.ts +35 -0
  74. package/dist/core/services/execution.js +137 -0
  75. package/dist/index.d.ts +16 -36
  76. package/dist/index.js +16 -36
  77. package/dist/model/{meta.d.ts → domain/meta.d.ts} +1 -0
  78. package/dist/model/index.d.ts +10 -0
  79. package/dist/model/index.js +10 -0
  80. package/dist/model/{metas.d.ts → infrastructure/metas.d.ts} +1 -1
  81. package/dist/model/{schema.d.ts → services/schema.d.ts} +1 -1
  82. package/dist/model/{schema.js → services/schema.js} +1 -1
  83. package/dist/observability/adapters/console.d.ts +12 -0
  84. package/dist/observability/adapters/console.js +35 -0
  85. package/dist/observability/adapters/memory.d.ts +41 -0
  86. package/dist/observability/adapters/memory.js +49 -0
  87. package/dist/{adapters → observability/adapters}/opentelemetry.d.ts +1 -1
  88. package/dist/{adapters → observability/adapters}/sentry.d.ts +1 -1
  89. package/dist/observability/domain/errors.d.ts +23 -0
  90. package/dist/observability/domain/errors.js +8 -0
  91. package/dist/observability/domain/logging.d.ts +42 -0
  92. package/dist/observability/domain/logging.js +1 -0
  93. package/dist/observability/domain/tracing.d.ts +49 -0
  94. package/dist/observability/domain/tracing.js +20 -0
  95. package/dist/observability/index.d.ts +16 -0
  96. package/dist/observability/index.js +4 -0
  97. package/dist/observability/infrastructure/registry.d.ts +21 -0
  98. package/dist/observability/infrastructure/registry.js +18 -0
  99. package/dist/observability/services/logging.d.ts +27 -0
  100. package/dist/observability/services/logging.js +105 -0
  101. package/dist/observability/services/outcome.d.ts +12 -0
  102. package/dist/observability/services/outcome.js +20 -0
  103. package/dist/{services/report.js → observability/services/reporting.js} +4 -3
  104. package/dist/testing/index.d.ts +3 -33
  105. package/dist/testing/index.js +5 -37
  106. package/package.json +25 -8
  107. package/dist/codegen/domain.d.ts +0 -15
  108. package/dist/observability/ports.d.ts +0 -65
  109. package/dist/observability/ports.js +0 -21
  110. package/dist/services/bounded-context.d.ts +0 -79
  111. package/dist/services/bounded-context.js +0 -261
  112. package/dist/services/execution.d.ts +0 -63
  113. package/dist/services/feature.d.ts +0 -85
  114. package/dist/services/feature.js +0 -86
  115. package/dist/services/process.d.ts +0 -60
  116. package/dist/services/process.js +0 -57
  117. package/dist/services/screens.d.ts +0 -2
  118. package/dist/services/screens.js +0 -67
  119. /package/dist/client/{contract.d.ts → domain/contract.d.ts} +0 -0
  120. /package/dist/client/{contract.js → domain/contract.js} +0 -0
  121. /package/dist/client/{discovery.d.ts → services/discovery.d.ts} +0 -0
  122. /package/dist/client/{discovery.js → services/discovery.js} +0 -0
  123. /package/dist/client/{retry.d.ts → services/retry.d.ts} +0 -0
  124. /package/dist/codegen/{metamodel.js → domain/metamodel.js} +0 -0
  125. /package/dist/codegen/{cli.d.ts → entrypoint/cli.d.ts} +0 -0
  126. /package/dist/codegen/{check.d.ts → services/check.d.ts} +0 -0
  127. /package/dist/codegen/{check.js → services/check.js} +0 -0
  128. /package/dist/codegen/{rest.js → services/rest.js} +0 -0
  129. /package/dist/context/{context.js → entrypoint/context.js} +0 -0
  130. /package/dist/model/{meta.js → domain/meta.js} +0 -0
  131. /package/dist/model/{metas.js → infrastructure/metas.js} +0 -0
  132. /package/dist/{adapters → observability/adapters}/opentelemetry.js +0 -0
  133. /package/dist/{adapters → observability/adapters}/sentry.js +0 -0
  134. /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, and a use case, the
7
- * client it calls and the entity it reads come from the same place.
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/ the values of the wire, as the backend writes them: ids, a day and an instant,
10
- * `Decimal`, `Money`, and how any value is written and read (`valuesOf`, `rebuilt`)
11
- * client/ the seam with a server: the transport and its REST and in-memory implementations,
12
- * entities with typed commands, the service's client contract
13
- * model/ model definitions: written by hand, read from a JSON Schema, kept once known
14
- * services/ the bounded context (`UseBoundedContext`), its `Feature` and `ApplicationService`,
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 tracer and reporter
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 { type ClientContract, entityKeyOf, kindOf, metasOf, versionsOf, } from "./client/contract.js";
23
- export { bodyFor, type CommandAnswers, type CommandBodies, type CommandRoute, type Entity, entity, type EntityRef, type EntityRoutes, listRoute, NEW_PARAM, pathFor, PICK_PARAM, QUERY_PARAM, SCOPE_PARAM, scopeOfRef, startingValuesOf, type SummaryFields, type SummaryPart, TRAIL_PARAM, WITH_PARAM, type WriteRoute, writeRoute, } from "./client/entity.js";
24
- export { type MemoryOptions, type MemoryTable, memoryTransport, } from "./client/memory.js";
25
- export { type RestOptions, restTransport, urlOf, type VersionDrift, } from "./client/rest.js";
26
- export { type RetryKeys, retryKeys, unanswered, } from "./client/retry.js";
27
- export { type CallOptions, type Draft, type Method, type One, readRoute, type SendOptions, type Transport, TransportError, } from "./client/transport.js";
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, and a use case, the
7
- * client it calls and the entity it reads come from the same place.
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/ the values of the wire, as the backend writes them: ids, a day and an instant,
10
- * `Decimal`, `Money`, and how any value is written and read (`valuesOf`, `rebuilt`)
11
- * client/ the seam with a server: the transport and its REST and in-memory implementations,
12
- * entities with typed commands, the service's client contract
13
- * model/ model definitions: written by hand, read from a JSON Schema, kept once known
14
- * services/ the bounded context (`UseBoundedContext`), its `Feature` and `ApplicationService`,
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 tracer and reporter
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 { entityKeyOf, kindOf, metasOf, versionsOf, } from "./client/contract.js";
23
- export { bodyFor, entity, listRoute, NEW_PARAM, pathFor, PICK_PARAM, QUERY_PARAM, SCOPE_PARAM, scopeOfRef, startingValuesOf, TRAIL_PARAM, WITH_PARAM, writeRoute, } from "./client/entity.js";
24
- export { memoryTransport, } from "./client/memory.js";
25
- export { restTransport, urlOf, } from "./client/rest.js";
26
- export { retryKeys, unanswered, } from "./client/retry.js";
27
- export { readRoute, TransportError, } from "./client/transport.js";
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";
@@ -69,6 +69,7 @@ export declare const field: {
69
69
  holds?: Expression | null;
70
70
  computes?: import("@sincpro/criteria").Formula | null;
71
71
  navigable?: boolean;
72
+ reference?: boolean;
72
73
  total?: string | null;
73
74
  many?: boolean;
74
75
  relation?: string | null;
@@ -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";