@sincpro/client-framework 0.6.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 (132) hide show
  1. package/AGENTS.md +54 -25
  2. package/MANIFESTO.md +59 -39
  3. package/README.md +324 -345
  4. package/dist/client/{memory.d.ts → adapters/memory.d.ts} +1 -1
  5. package/dist/client/{memory.js → adapters/memory.js} +2 -2
  6. package/dist/{adapters → client/adapters}/parquet.d.ts +1 -1
  7. package/dist/client/{rest.d.ts → adapters/rest.d.ts} +1 -1
  8. package/dist/client/{rest.js → adapters/rest.js} +2 -2
  9. package/dist/client/{entity.d.ts → domain/entity.d.ts} +6 -10
  10. package/dist/client/index.d.ts +16 -0
  11. package/dist/client/index.js +16 -0
  12. package/dist/client/services/propagation.d.ts +32 -0
  13. package/dist/{services/execution.js → client/services/propagation.js} +5 -21
  14. package/dist/client/{retry.js → services/retry.js} +1 -1
  15. package/dist/codegen/{metamodel.d.ts → domain/metamodel.d.ts} +17 -2
  16. package/dist/codegen/{names.d.ts → domain/names.d.ts} +2 -2
  17. package/dist/codegen/{names.js → domain/names.js} +3 -3
  18. package/dist/codegen/{cli.js → entrypoint/cli.js} +9 -8
  19. package/dist/codegen/index.d.ts +7 -7
  20. package/dist/codegen/index.js +5 -10
  21. package/dist/codegen/{client.d.ts → services/client.d.ts} +3 -1
  22. package/dist/codegen/{client.js → services/client.js} +9 -1
  23. package/dist/codegen/services/domain.d.ts +20 -0
  24. package/dist/codegen/{domain.js → services/domain.js} +82 -25
  25. package/dist/codegen/{entities.d.ts → services/entities.d.ts} +4 -3
  26. package/dist/codegen/{entities.js → services/entities.js} +18 -4
  27. package/dist/codegen/{rest.d.ts → services/rest.d.ts} +1 -1
  28. package/dist/codegen/{scaffold.d.ts → services/scaffold.d.ts} +2 -2
  29. package/dist/codegen/{scaffold.js → services/scaffold.js} +31 -36
  30. package/dist/common/index.d.ts +10 -0
  31. package/dist/common/index.js +10 -0
  32. package/dist/context/adapters/memory.d.ts +6 -0
  33. package/dist/context/adapters/memory.js +34 -0
  34. package/dist/context/adapters/plain-codec.d.ts +3 -0
  35. package/dist/context/adapters/plain-codec.js +16 -0
  36. package/dist/context/{codec.d.ts → domain/codec.d.ts} +0 -1
  37. package/dist/context/{codec.js → domain/codec.js} +0 -14
  38. package/dist/context/domain/execution.d.ts +33 -0
  39. package/dist/context/domain/execution.js +11 -0
  40. package/dist/context/{store.d.ts → domain/store.d.ts} +0 -5
  41. package/dist/context/{store.js → domain/store.js} +1 -34
  42. package/dist/context/{context.d.ts → entrypoint/context.d.ts} +1 -1
  43. package/dist/context/index.d.ts +17 -0
  44. package/dist/context/index.js +15 -0
  45. package/dist/context/infrastructure/process.d.ts +13 -0
  46. package/dist/context/infrastructure/process.js +35 -0
  47. package/dist/core/domain/bus.d.ts +18 -0
  48. package/dist/core/domain/bus.js +18 -0
  49. package/dist/core/domain/dto.d.ts +32 -0
  50. package/dist/core/domain/dto.js +20 -0
  51. package/dist/core/domain/errors.d.ts +18 -0
  52. package/dist/core/domain/errors.js +1 -0
  53. package/dist/core/domain/services.d.ts +37 -0
  54. package/dist/core/domain/services.js +28 -0
  55. package/dist/core/entrypoint/bounded-context.d.ts +70 -0
  56. package/dist/core/entrypoint/bounded-context.js +252 -0
  57. package/dist/core/entrypoint/screens.d.ts +6 -0
  58. package/dist/core/entrypoint/screens.js +136 -0
  59. package/dist/core/index.d.ts +17 -0
  60. package/dist/core/index.js +16 -0
  61. package/dist/core/infrastructure/process.d.ts +56 -0
  62. package/dist/core/infrastructure/process.js +35 -0
  63. package/dist/core/services/binding.d.ts +6 -0
  64. package/dist/core/services/binding.js +13 -0
  65. package/dist/core/services/error-handlers.d.ts +11 -0
  66. package/dist/core/services/error-handlers.js +21 -0
  67. package/dist/core/services/execution.d.ts +35 -0
  68. package/dist/core/services/execution.js +137 -0
  69. package/dist/index.d.ts +16 -38
  70. package/dist/index.js +16 -38
  71. package/dist/model/index.d.ts +10 -0
  72. package/dist/model/index.js +10 -0
  73. package/dist/model/{metas.d.ts → infrastructure/metas.d.ts} +1 -1
  74. package/dist/model/{schema.d.ts → services/schema.d.ts} +1 -1
  75. package/dist/model/{schema.js → services/schema.js} +1 -1
  76. package/dist/observability/adapters/console.d.ts +12 -0
  77. package/dist/observability/adapters/console.js +35 -0
  78. package/dist/observability/adapters/memory.d.ts +41 -0
  79. package/dist/observability/adapters/memory.js +49 -0
  80. package/dist/{adapters → observability/adapters}/opentelemetry.d.ts +1 -1
  81. package/dist/{adapters → observability/adapters}/sentry.d.ts +1 -1
  82. package/dist/observability/domain/errors.d.ts +23 -0
  83. package/dist/observability/domain/errors.js +8 -0
  84. package/dist/observability/domain/logging.d.ts +42 -0
  85. package/dist/observability/domain/logging.js +1 -0
  86. package/dist/observability/domain/tracing.d.ts +49 -0
  87. package/dist/observability/domain/tracing.js +20 -0
  88. package/dist/observability/index.d.ts +16 -0
  89. package/dist/observability/index.js +4 -0
  90. package/dist/observability/infrastructure/registry.d.ts +21 -0
  91. package/dist/observability/infrastructure/registry.js +18 -0
  92. package/dist/observability/services/logging.d.ts +27 -0
  93. package/dist/observability/services/logging.js +105 -0
  94. package/dist/observability/services/outcome.d.ts +12 -0
  95. package/dist/observability/services/outcome.js +20 -0
  96. package/dist/{services/report.js → observability/services/reporting.js} +4 -3
  97. package/dist/testing/index.d.ts +3 -33
  98. package/dist/testing/index.js +5 -37
  99. package/package.json +16 -8
  100. package/dist/codegen/domain.d.ts +0 -15
  101. package/dist/observability/ports.d.ts +0 -65
  102. package/dist/observability/ports.js +0 -21
  103. package/dist/services/bounded-context.d.ts +0 -79
  104. package/dist/services/bounded-context.js +0 -261
  105. package/dist/services/execution.d.ts +0 -63
  106. package/dist/services/feature.d.ts +0 -85
  107. package/dist/services/feature.js +0 -86
  108. package/dist/services/process.d.ts +0 -60
  109. package/dist/services/process.js +0 -57
  110. package/dist/services/screens.d.ts +0 -2
  111. package/dist/services/screens.js +0 -76
  112. /package/dist/{adapters → client/adapters}/parquet.js +0 -0
  113. /package/dist/client/{contract.d.ts → domain/contract.d.ts} +0 -0
  114. /package/dist/client/{contract.js → domain/contract.js} +0 -0
  115. /package/dist/client/{entity.js → domain/entity.js} +0 -0
  116. /package/dist/client/{transport.d.ts → domain/transport.d.ts} +0 -0
  117. /package/dist/client/{transport.js → domain/transport.js} +0 -0
  118. /package/dist/client/{discovery.d.ts → services/discovery.d.ts} +0 -0
  119. /package/dist/client/{discovery.js → services/discovery.js} +0 -0
  120. /package/dist/client/{retry.d.ts → services/retry.d.ts} +0 -0
  121. /package/dist/codegen/{metamodel.js → domain/metamodel.js} +0 -0
  122. /package/dist/codegen/{cli.d.ts → entrypoint/cli.d.ts} +0 -0
  123. /package/dist/codegen/{check.d.ts → services/check.d.ts} +0 -0
  124. /package/dist/codegen/{check.js → services/check.js} +0 -0
  125. /package/dist/codegen/{rest.js → services/rest.js} +0 -0
  126. /package/dist/context/{context.js → entrypoint/context.js} +0 -0
  127. /package/dist/model/{meta.d.ts → domain/meta.d.ts} +0 -0
  128. /package/dist/model/{meta.js → domain/meta.js} +0 -0
  129. /package/dist/model/{metas.js → infrastructure/metas.js} +0 -0
  130. /package/dist/{adapters → observability/adapters}/opentelemetry.js +0 -0
  131. /package/dist/{adapters → observability/adapters}/sentry.js +0 -0
  132. /package/dist/{services/report.d.ts → observability/services/reporting.d.ts} +0 -0
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The services of the application layer, as sincpro_framework's: a Feature executes one
3
+ * registered Command; an ApplicationService composes its context's Features.
4
+ *
5
+ * @project.feature(CommandCancelIssue)
6
+ * class CancelIssue extends Feature<CommandCancelIssue, ResponseCancelIssue> {
7
+ * static readonly serviceName = "CancelIssue";
8
+ * async execute(command: CommandCancelIssue) {
9
+ * this.logger.info("Cancelling"); // bound to this run
10
+ * return new ResponseCancelIssue(await this.client.cancelIssue(command));
11
+ * }
12
+ * }
13
+ *
14
+ * Context: while a method runs, `this.context`, `this.span`, `this.logger`, `this.featureBus` and
15
+ * every dependency are that run's (`executeService`). `serviceName` is stable metadata, never
16
+ * `constructor.name`, which a minifier renames.
17
+ */
18
+ export class Feature {
19
+ }
20
+ export class ApplicationService extends Feature {
21
+ }
22
+ /** @internal — what a run finds on `this` that no dependency may take. */
23
+ export const RESERVED_MEMBERS = new Set([
24
+ "featureBus",
25
+ "context",
26
+ "span",
27
+ "logger",
28
+ ]);
@@ -0,0 +1,70 @@
1
+ import type { ContextValues } from "../../context/domain/store.js";
2
+ import { type Context } from "../../context/entrypoint/context.js";
3
+ import { type CommandBus } from "../domain/bus.js";
4
+ import type { Answer, AnyCommand, Command, CommandClass, ResponseClass } from "../domain/dto.js";
5
+ import type { ErrorClass, ErrorHandler } from "../domain/errors.js";
6
+ import type { LayerLogger } from "../../observability/domain/logging.js";
7
+ interface Executor<C extends AnyCommand, R> {
8
+ execute(command: C): R | Promise<R>;
9
+ }
10
+ type ServiceClass<C extends AnyCommand, R> = {
11
+ new (): Executor<C, R>;
12
+ readonly serviceName: string;
13
+ };
14
+ /**
15
+ * A bounded context: the bus of its Commands, as sincpro_framework's `UseFramework`.
16
+ *
17
+ * const project = new BoundedContext<ProjectDependencies>("project");
18
+ * project.addDependency("client", clientOf(transport));
19
+ * project.feature(CommandCancelIssue)(CancelIssue); // or @project.feature(...)
20
+ * project.addFeatureErrorHandler(toEmptyWhenMissing);
21
+ * project.expectErrors(IssueClosed); // refused, never reported
22
+ * await project.execute(new CommandCancelIssue("x")); // ResponseCancelIssue
23
+ * project.logger.info("Catalog preloaded");
24
+ *
25
+ * Context: registrations, dependencies, handlers and expected errors are snapshotted per
26
+ * generation; a call keeps the generation it started with, and what is added later answers from
27
+ * the next call — as the backend's `build_root_bus`.
28
+ */
29
+ export declare class BoundedContext<D extends object = Record<never, never>, C extends object = ContextValues> {
30
+ readonly name: string;
31
+ /** Its level of the context: what every use case of it runs with, under the process's. */
32
+ readonly context: Context<C>;
33
+ /** Its own logger, named after it: for what it does outside a use case. */
34
+ readonly logger: LayerLogger;
35
+ private readonly registrations;
36
+ private readonly dependencyValues;
37
+ private readonly handlers;
38
+ private readonly expected;
39
+ private revision;
40
+ private builtRevision;
41
+ private built;
42
+ constructor(name: string);
43
+ get deps(): Readonly<D>;
44
+ addDependency<K extends keyof D & string>(name: K, dependency: D[K], options?: {
45
+ replaces: D[K];
46
+ }): this;
47
+ feature<T extends AnyCommand>(command: CommandClass<T>): <S extends ServiceClass<NoInfer<T>, Answer<NoInfer<T>>>>(Service: S, decorator?: ClassDecoratorContext<S> | undefined) => S;
48
+ appService<T extends AnyCommand>(command: CommandClass<T>): <S extends ServiceClass<NoInfer<T>, Answer<NoInfer<T>>>>(Service: S, decorator?: ClassDecoratorContext<S> | undefined) => S;
49
+ /** Answers a failure that reaches `execute` (or a scope's): after every layer's handlers. */
50
+ addGlobalErrorHandler(handler: ErrorHandler): this;
51
+ /** Answers a Feature's failure, wherever it was executed from. */
52
+ addFeatureErrorHandler(handler: ErrorHandler): this;
53
+ /** Answers an ApplicationService's failure. */
54
+ addAppServiceErrorHandler(handler: ErrorHandler): this;
55
+ /**
56
+ * Errors of these classes are the business answering, not a fault: the run ends `refused`,
57
+ * its span is no error and nothing is reported. They are still thrown — or handled.
58
+ */
59
+ expectErrors(...types: ErrorClass[]): this;
60
+ execute<R>(command: Command<R>, _returnType?: ResponseClass<NoInfer<R>>): Promise<R>;
61
+ scoped(context: Partial<C>): CommandBus;
62
+ buildRootBus(): void;
63
+ fresh(): BoundedContext<D, C>;
64
+ private addErrorHandler;
65
+ private registrar;
66
+ private registerService;
67
+ private generation;
68
+ private buildGeneration;
69
+ }
70
+ export {};
@@ -0,0 +1,252 @@
1
+ import { contextAt, } from "../../context/entrypoint/context.js";
2
+ import { processContext, processStore, } from "../../context/infrastructure/process.js";
3
+ import { executeTyped, FeatureBus, } from "../domain/bus.js";
4
+ import { ApplicationService, Feature, RESERVED_MEMBERS, } from "../domain/services.js";
5
+ import { handled } from "../services/error-handlers.js";
6
+ import { executeService } from "../services/execution.js";
7
+ import { boundedContextLogger, logHandled, } from "../../observability/services/logging.js";
8
+ function isExecutor(value) {
9
+ return (typeof value === "object" &&
10
+ value !== null &&
11
+ "execute" in value &&
12
+ typeof value.execute === "function");
13
+ }
14
+ /**
15
+ * A bounded context: the bus of its Commands, as sincpro_framework's `UseFramework`.
16
+ *
17
+ * const project = new BoundedContext<ProjectDependencies>("project");
18
+ * project.addDependency("client", clientOf(transport));
19
+ * project.feature(CommandCancelIssue)(CancelIssue); // or @project.feature(...)
20
+ * project.addFeatureErrorHandler(toEmptyWhenMissing);
21
+ * project.expectErrors(IssueClosed); // refused, never reported
22
+ * await project.execute(new CommandCancelIssue("x")); // ResponseCancelIssue
23
+ * project.logger.info("Catalog preloaded");
24
+ *
25
+ * Context: registrations, dependencies, handlers and expected errors are snapshotted per
26
+ * generation; a call keeps the generation it started with, and what is added later answers from
27
+ * the next call — as the backend's `build_root_bus`.
28
+ */
29
+ export class BoundedContext {
30
+ name;
31
+ /** Its level of the context: what every use case of it runs with, under the process's. */
32
+ context;
33
+ /** Its own logger, named after it: for what it does outside a use case. */
34
+ logger;
35
+ registrations = new Map();
36
+ dependencyValues = new Map();
37
+ handlers = {
38
+ global: [],
39
+ feature: [],
40
+ application_service: [],
41
+ };
42
+ expected = [];
43
+ revision = 0;
44
+ builtRevision = -1;
45
+ built;
46
+ constructor(name) {
47
+ this.name = name;
48
+ if (name.trim() === "" || name.includes("/")) {
49
+ throw new TypeError("A bounded context needs a nonempty name without '/'");
50
+ }
51
+ this.context = contextAt(`bounded_context:${name}`, processStore);
52
+ this.logger = boundedContextLogger(name);
53
+ }
54
+ get deps() {
55
+ // The dependency interface is erased; the locator checks real registrations.
56
+ return new Proxy({}, {
57
+ get: (_target, key) => {
58
+ if (typeof key !== "string" || !this.dependencyValues.has(key)) {
59
+ throw new Error(`Dependency is not registered: ${String(key)}`);
60
+ }
61
+ return this.dependencyValues.get(key);
62
+ },
63
+ set: () => {
64
+ throw new Error("Dependencies are read-only; use addDependency");
65
+ },
66
+ deleteProperty: () => {
67
+ throw new Error("Dependencies cannot be deleted");
68
+ },
69
+ defineProperty: () => {
70
+ throw new Error("Dependencies are read-only");
71
+ },
72
+ setPrototypeOf: () => {
73
+ throw new Error("Dependencies are read-only");
74
+ },
75
+ });
76
+ }
77
+ addDependency(name, dependency, options) {
78
+ if (RESERVED_MEMBERS.has(name)) {
79
+ throw new Error(`Reserved dependency name: ${name}`);
80
+ }
81
+ const registered = this.dependencyValues.has(name);
82
+ if (registered && options === undefined) {
83
+ throw new Error(`Dependency already registered: ${name}`);
84
+ }
85
+ if (options && (!registered || this.dependencyValues.get(name) !== options.replaces)) {
86
+ throw new Error(`Dependency replacement does not match the registration: ${name}`);
87
+ }
88
+ this.dependencyValues.set(name, dependency);
89
+ this.revision++;
90
+ return this;
91
+ }
92
+ feature(command) {
93
+ return this.registrar(command, "feature");
94
+ }
95
+ appService(command) {
96
+ return this.registrar(command, "application_service");
97
+ }
98
+ /** Answers a failure that reaches `execute` (or a scope's): after every layer's handlers. */
99
+ addGlobalErrorHandler(handler) {
100
+ return this.addErrorHandler("global", handler);
101
+ }
102
+ /** Answers a Feature's failure, wherever it was executed from. */
103
+ addFeatureErrorHandler(handler) {
104
+ return this.addErrorHandler("feature", handler);
105
+ }
106
+ /** Answers an ApplicationService's failure. */
107
+ addAppServiceErrorHandler(handler) {
108
+ return this.addErrorHandler("application_service", handler);
109
+ }
110
+ /**
111
+ * Errors of these classes are the business answering, not a fault: the run ends `refused`,
112
+ * its span is no error and nothing is reported. They are still thrown — or handled.
113
+ */
114
+ expectErrors(...types) {
115
+ for (const type of types) {
116
+ if (typeof type !== "function")
117
+ throw new TypeError("An expected error is a class");
118
+ if (!this.expected.includes(type))
119
+ this.expected.push(type);
120
+ }
121
+ this.revision++;
122
+ return this;
123
+ }
124
+ execute(command, _returnType) {
125
+ return executeTyped((one) => this.generation().root(one, {}), command);
126
+ }
127
+ scoped(context) {
128
+ const scope = { ...context };
129
+ return new FeatureBus((command) => this.generation().root(command, scope));
130
+ }
131
+ buildRootBus() {
132
+ this.generation();
133
+ }
134
+ fresh() {
135
+ const fresh = new BoundedContext(this.name);
136
+ for (const [command, registration] of this.registrations) {
137
+ fresh.registrations.set(command, registration);
138
+ }
139
+ for (const [name, dependency] of this.dependencyValues) {
140
+ fresh.dependencyValues.set(name, dependency);
141
+ }
142
+ for (const where of ["global", "feature", "application_service"]) {
143
+ fresh.handlers[where].push(...this.handlers[where]);
144
+ }
145
+ fresh.expected.push(...this.expected);
146
+ fresh.revision = this.revision;
147
+ return fresh;
148
+ }
149
+ addErrorHandler(where, handler) {
150
+ if (typeof handler !== "function")
151
+ throw new TypeError("An error handler is a function");
152
+ this.handlers[where].push(handler);
153
+ this.revision++;
154
+ return this;
155
+ }
156
+ registrar(command, layer) {
157
+ return (Service, decorator) => {
158
+ const register = () => this.registerService(command, Service, layer);
159
+ // Standard decorators run before static fields have initialized.
160
+ if (decorator)
161
+ decorator.addInitializer(register);
162
+ else
163
+ register();
164
+ return Service;
165
+ };
166
+ }
167
+ registerService(command, Service, layer) {
168
+ const Base = layer === "feature" ? Feature : ApplicationService;
169
+ if (!(Service.prototype instanceof Base) ||
170
+ (layer === "feature" && Service.prototype instanceof ApplicationService)) {
171
+ throw new TypeError(`The service is not an ${layer}`);
172
+ }
173
+ if (this.registrations.has(command)) {
174
+ throw new Error("Command already registered: one executor across both layers");
175
+ }
176
+ const name = Service.serviceName;
177
+ if (typeof name !== "string" || name.trim() === "") {
178
+ throw new Error("A service needs a nonempty registered name");
179
+ }
180
+ if ([...this.registrations.values()].some((registration) => registration.name === name)) {
181
+ throw new Error(`Service name already registered: ${name}`);
182
+ }
183
+ this.registrations.set(command, { name, layer, Service });
184
+ this.revision++;
185
+ }
186
+ generation() {
187
+ if (this.built && this.builtRevision === this.revision)
188
+ return this.built;
189
+ const revision = this.revision;
190
+ const built = this.buildGeneration();
191
+ this.built = built;
192
+ this.builtRevision = revision;
193
+ return built;
194
+ }
195
+ buildGeneration() {
196
+ const registrations = new Map(this.registrations);
197
+ const dependencies = new Map(this.dependencyValues);
198
+ const handlers = {
199
+ global: [...this.handlers.global],
200
+ feature: [...this.handlers.feature],
201
+ application_service: [...this.handlers.application_service],
202
+ };
203
+ const expectedTypes = [...this.expected];
204
+ const expected = (error) => expectedTypes.some((type) => error instanceof type);
205
+ const instances = new Map();
206
+ for (const registration of registrations.values()) {
207
+ const instance = new registration.Service();
208
+ if (!isExecutor(instance)) {
209
+ throw new Error(`Registered service is unavailable: ${registration.name}`);
210
+ }
211
+ instances.set(registration, instance);
212
+ }
213
+ const dispatch = async (command, parent, featureOnly = false, scope = {}) => {
214
+ // Exact constructor identity: a subclass of a registered Command is another Command.
215
+ const target = registrations.get(Object.getPrototypeOf(command)?.constructor);
216
+ if (!target)
217
+ throw new Error("No executor registered for this command class");
218
+ if (featureOnly && target.layer !== "feature") {
219
+ throw new Error("An ApplicationService may compose only Features");
220
+ }
221
+ const service = instances.get(target);
222
+ if (!service) {
223
+ throw new Error(`Registered service is unavailable: ${target.name}`);
224
+ }
225
+ return executeService(service, command, {
226
+ boundedContext: this.name,
227
+ name: target.name,
228
+ layer: target.layer,
229
+ dependencies,
230
+ context: () => ({ ...processContext.values, ...this.context.values, ...scope }),
231
+ ...(parent ? { parent } : {}),
232
+ featureBus: (execution) => new FeatureBus((child) => dispatch(child, execution, true)),
233
+ expected,
234
+ handlers: handlers[target.layer],
235
+ });
236
+ };
237
+ return {
238
+ root: async (command, scope) => {
239
+ try {
240
+ return await dispatch(command, undefined, false, scope);
241
+ }
242
+ catch (error) {
243
+ if (handlers.global.length === 0)
244
+ throw error;
245
+ const answer = await handled(handlers.global, error);
246
+ logHandled("bounded_context", { boundedContext: this.name });
247
+ return answer;
248
+ }
249
+ },
250
+ };
251
+ }
252
+ }
@@ -0,0 +1,6 @@
1
+ import type { Transport } from "../../client/domain/transport.js";
2
+ /**
3
+ * Screen requests enter the same command pipeline as application services. A generated
4
+ * client injected into a Feature uses its raw transport instead, continuing that execution.
5
+ */
6
+ export declare function screensTransport(transport: Transport, name?: string): Transport;
@@ -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>;