@sincpro/client-framework 0.6.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/AGENTS.md +95 -25
  2. package/MANIFESTO.md +103 -40
  3. package/README.md +608 -333
  4. package/dist/circuit_breaker/domain/circuit.d.ts +29 -0
  5. package/dist/circuit_breaker/domain/circuit.js +37 -0
  6. package/dist/circuit_breaker/entrypoint/circuit-breaker.d.ts +56 -0
  7. package/dist/circuit_breaker/entrypoint/circuit-breaker.js +94 -0
  8. package/dist/circuit_breaker/index.d.ts +9 -0
  9. package/dist/circuit_breaker/index.js +9 -0
  10. package/dist/client/adapters/json-rpc.d.ts +40 -0
  11. package/dist/client/adapters/json-rpc.js +97 -0
  12. package/dist/client/{memory.d.ts → adapters/memory.d.ts} +1 -1
  13. package/dist/client/{memory.js → adapters/memory.js} +2 -2
  14. package/dist/{adapters → client/adapters}/parquet.d.ts +1 -1
  15. package/dist/client/adapters/rest-operations.d.ts +19 -0
  16. package/dist/client/adapters/rest-operations.js +66 -0
  17. package/dist/client/{rest.d.ts → adapters/rest.d.ts} +1 -1
  18. package/dist/client/{rest.js → adapters/rest.js} +2 -2
  19. package/dist/client/{entity.d.ts → domain/entity.d.ts} +6 -10
  20. package/dist/client/domain/operation.d.ts +73 -0
  21. package/dist/client/domain/operation.js +19 -0
  22. package/dist/client/index.d.ts +20 -0
  23. package/dist/client/index.js +20 -0
  24. package/dist/client/services/guarded.d.ts +17 -0
  25. package/dist/client/services/guarded.js +19 -0
  26. package/dist/client/services/propagation.d.ts +45 -0
  27. package/dist/{services/execution.js → client/services/propagation.js} +49 -36
  28. package/dist/client/{retry.js → services/retry.js} +1 -1
  29. package/dist/codegen/{metamodel.d.ts → domain/metamodel.d.ts} +17 -2
  30. package/dist/codegen/{names.d.ts → domain/names.d.ts} +2 -2
  31. package/dist/codegen/{names.js → domain/names.js} +3 -3
  32. package/dist/codegen/{cli.js → entrypoint/cli.js} +15 -14
  33. package/dist/codegen/index.d.ts +7 -7
  34. package/dist/codegen/index.js +5 -10
  35. package/dist/codegen/services/client.d.ts +15 -0
  36. package/dist/codegen/services/client.js +111 -0
  37. package/dist/codegen/services/domain.d.ts +20 -0
  38. package/dist/codegen/{domain.js → services/domain.js} +82 -25
  39. package/dist/codegen/{entities.d.ts → services/entities.d.ts} +4 -3
  40. package/dist/codegen/{entities.js → services/entities.js} +18 -4
  41. package/dist/codegen/{rest.d.ts → services/rest.d.ts} +1 -1
  42. package/dist/codegen/{scaffold.d.ts → services/scaffold.d.ts} +8 -5
  43. package/dist/codegen/{scaffold.js → services/scaffold.js} +39 -43
  44. package/dist/common/failures.d.ts +83 -0
  45. package/dist/common/failures.js +212 -0
  46. package/dist/common/index.d.ts +11 -0
  47. package/dist/common/index.js +11 -0
  48. package/dist/context/adapters/memory.d.ts +6 -0
  49. package/dist/context/adapters/memory.js +34 -0
  50. package/dist/context/adapters/plain-codec.d.ts +3 -0
  51. package/dist/context/adapters/plain-codec.js +16 -0
  52. package/dist/context/{codec.d.ts → domain/codec.d.ts} +0 -1
  53. package/dist/context/{codec.js → domain/codec.js} +0 -14
  54. package/dist/context/domain/execution.d.ts +45 -0
  55. package/dist/context/domain/execution.js +11 -0
  56. package/dist/context/{store.d.ts → domain/store.d.ts} +0 -5
  57. package/dist/context/{store.js → domain/store.js} +1 -34
  58. package/dist/context/{context.d.ts → entrypoint/context.d.ts} +1 -1
  59. package/dist/context/index.d.ts +17 -0
  60. package/dist/context/index.js +15 -0
  61. package/dist/context/infrastructure/process.d.ts +13 -0
  62. package/dist/context/infrastructure/process.js +35 -0
  63. package/dist/core/domain/bus.d.ts +27 -0
  64. package/dist/core/domain/bus.js +26 -0
  65. package/dist/core/domain/dto.d.ts +62 -0
  66. package/dist/core/domain/dto.js +43 -0
  67. package/dist/core/domain/errors.d.ts +18 -0
  68. package/dist/core/domain/errors.js +1 -0
  69. package/dist/core/domain/pipeline.d.ts +89 -0
  70. package/dist/core/domain/pipeline.js +1 -0
  71. package/dist/core/domain/services.d.ts +47 -0
  72. package/dist/core/domain/services.js +31 -0
  73. package/dist/core/entrypoint/bounded-context.d.ts +139 -0
  74. package/dist/core/entrypoint/bounded-context.js +426 -0
  75. package/dist/core/entrypoint/screens.d.ts +6 -0
  76. package/dist/core/entrypoint/screens.js +136 -0
  77. package/dist/core/index.d.ts +19 -0
  78. package/dist/core/index.js +17 -0
  79. package/dist/core/infrastructure/listeners.d.ts +28 -0
  80. package/dist/core/infrastructure/listeners.js +34 -0
  81. package/dist/core/infrastructure/process.d.ts +56 -0
  82. package/dist/core/infrastructure/process.js +35 -0
  83. package/dist/core/services/binding.d.ts +6 -0
  84. package/dist/core/services/binding.js +16 -0
  85. package/dist/core/services/error-handlers.d.ts +11 -0
  86. package/dist/core/services/error-handlers.js +21 -0
  87. package/dist/core/services/execution.d.ts +66 -0
  88. package/dist/core/services/execution.js +216 -0
  89. package/dist/core/services/interceptors.d.ts +15 -0
  90. package/dist/core/services/interceptors.js +25 -0
  91. package/dist/core/services/listening.d.ts +17 -0
  92. package/dist/core/services/listening.js +25 -0
  93. package/dist/event_driven/adapters/memory-event-store.d.ts +10 -0
  94. package/dist/event_driven/adapters/memory-event-store.js +23 -0
  95. package/dist/event_driven/adapters/stored-queue.d.ts +67 -0
  96. package/dist/event_driven/adapters/stored-queue.js +192 -0
  97. package/dist/event_driven/adapters/sync-queue.d.ts +11 -0
  98. package/dist/event_driven/adapters/sync-queue.js +19 -0
  99. package/dist/event_driven/domain/envelope.d.ts +28 -0
  100. package/dist/event_driven/domain/envelope.js +19 -0
  101. package/dist/event_driven/domain/queue.d.ts +40 -0
  102. package/dist/event_driven/domain/queue.js +1 -0
  103. package/dist/event_driven/domain/store.d.ts +56 -0
  104. package/dist/event_driven/domain/store.js +1 -0
  105. package/dist/event_driven/entrypoint/publisher.d.ts +26 -0
  106. package/dist/event_driven/entrypoint/publisher.js +35 -0
  107. package/dist/event_driven/entrypoint/subscriber.d.ts +34 -0
  108. package/dist/event_driven/entrypoint/subscriber.js +51 -0
  109. package/dist/event_driven/index.d.ts +19 -0
  110. package/dist/event_driven/index.js +17 -0
  111. package/dist/index.d.ts +21 -38
  112. package/dist/index.js +21 -38
  113. package/dist/model/index.d.ts +10 -0
  114. package/dist/model/index.js +10 -0
  115. package/dist/model/{metas.d.ts → infrastructure/metas.d.ts} +1 -1
  116. package/dist/model/{schema.d.ts → services/schema.d.ts} +1 -1
  117. package/dist/model/{schema.js → services/schema.js} +1 -1
  118. package/dist/observability/adapters/console.d.ts +12 -0
  119. package/dist/observability/adapters/console.js +35 -0
  120. package/dist/observability/adapters/memory.d.ts +41 -0
  121. package/dist/observability/adapters/memory.js +49 -0
  122. package/dist/{adapters → observability/adapters}/opentelemetry.d.ts +1 -1
  123. package/dist/{adapters → observability/adapters}/sentry.d.ts +1 -1
  124. package/dist/observability/domain/errors.d.ts +23 -0
  125. package/dist/observability/domain/errors.js +8 -0
  126. package/dist/observability/domain/logging.d.ts +44 -0
  127. package/dist/observability/domain/logging.js +1 -0
  128. package/dist/observability/domain/tracing.d.ts +49 -0
  129. package/dist/observability/domain/tracing.js +20 -0
  130. package/dist/observability/index.d.ts +16 -0
  131. package/dist/observability/index.js +4 -0
  132. package/dist/observability/infrastructure/registry.d.ts +21 -0
  133. package/dist/observability/infrastructure/registry.js +18 -0
  134. package/dist/observability/services/logging.d.ts +27 -0
  135. package/dist/observability/services/logging.js +106 -0
  136. package/dist/observability/services/outcome.d.ts +12 -0
  137. package/dist/observability/services/outcome.js +17 -0
  138. package/dist/{services/report.js → observability/services/reporting.js} +4 -3
  139. package/dist/testing/index.d.ts +3 -33
  140. package/dist/testing/index.js +5 -37
  141. package/package.json +16 -8
  142. package/dist/codegen/client.d.ts +0 -14
  143. package/dist/codegen/client.js +0 -201
  144. package/dist/codegen/domain.d.ts +0 -15
  145. package/dist/observability/ports.d.ts +0 -65
  146. package/dist/observability/ports.js +0 -21
  147. package/dist/services/bounded-context.d.ts +0 -79
  148. package/dist/services/bounded-context.js +0 -261
  149. package/dist/services/execution.d.ts +0 -63
  150. package/dist/services/feature.d.ts +0 -85
  151. package/dist/services/feature.js +0 -86
  152. package/dist/services/process.d.ts +0 -60
  153. package/dist/services/process.js +0 -57
  154. package/dist/services/screens.d.ts +0 -2
  155. package/dist/services/screens.js +0 -76
  156. /package/dist/{adapters → client/adapters}/parquet.js +0 -0
  157. /package/dist/client/{contract.d.ts → domain/contract.d.ts} +0 -0
  158. /package/dist/client/{contract.js → domain/contract.js} +0 -0
  159. /package/dist/client/{entity.js → domain/entity.js} +0 -0
  160. /package/dist/client/{transport.d.ts → domain/transport.d.ts} +0 -0
  161. /package/dist/client/{transport.js → domain/transport.js} +0 -0
  162. /package/dist/client/{discovery.d.ts → services/discovery.d.ts} +0 -0
  163. /package/dist/client/{discovery.js → services/discovery.js} +0 -0
  164. /package/dist/client/{retry.d.ts → services/retry.d.ts} +0 -0
  165. /package/dist/codegen/{metamodel.js → domain/metamodel.js} +0 -0
  166. /package/dist/codegen/{cli.d.ts → entrypoint/cli.d.ts} +0 -0
  167. /package/dist/codegen/{check.d.ts → services/check.d.ts} +0 -0
  168. /package/dist/codegen/{check.js → services/check.js} +0 -0
  169. /package/dist/codegen/{rest.js → services/rest.js} +0 -0
  170. /package/dist/context/{context.js → entrypoint/context.js} +0 -0
  171. /package/dist/model/{meta.d.ts → domain/meta.d.ts} +0 -0
  172. /package/dist/model/{meta.js → domain/meta.js} +0 -0
  173. /package/dist/model/{metas.js → infrastructure/metas.js} +0 -0
  174. /package/dist/{adapters → observability/adapters}/opentelemetry.js +0 -0
  175. /package/dist/{adapters → observability/adapters}/sentry.js +0 -0
  176. /package/dist/{services/report.d.ts → observability/services/reporting.d.ts} +0 -0
@@ -1,261 +0,0 @@
1
- import { newEntityId } from "../common/ids.js";
2
- import { contextAt } from "../context/context.js";
3
- import { bindDependency } from "./execution.js";
4
- import { composes, isApplicationServiceClass, isFeatureClass, methodsOf, } from "./feature.js";
5
- import { processContext, processNow, processStore, } from "./process.js";
6
- import { firstReport } from "./report.js";
7
- /** Names `project` answers itself: no service is registered under them. */
8
- const RESERVED = new Set(["name", "context", "with", "scoped"]);
9
- /** Names a run answers on `this` itself: no dependency is added under them. */
10
- const ANSWERED = new Set(["context", "feature", "span"]);
11
- export class UseBoundedContext {
12
- name;
13
- /** This bounded context's level of the context, saved in the process's store. */
14
- context;
15
- #dependencies = {};
16
- constructor(name) {
17
- if (name === "" || name.includes("/"))
18
- throw new Error(`"${name}" is not a bounded context's name — it names its spans: "${name}/Issues.close"`);
19
- this.name = name;
20
- this.context = contextAt(`bounded_context:${name}`, processStore);
21
- }
22
- /** `value` is what every service of this bounded context finds at `this.<name>`. */
23
- addDependency(name, value) {
24
- if (ANSWERED.has(name))
25
- throw new Error(`${this.name}: a dependency cannot be called "${name}" — every service answers this.${name} itself`);
26
- this.#dependencies[name] = value;
27
- return this;
28
- }
29
- /**
30
- * The object the bounded context is used through: each service under its name. A dependency
31
- * added after this is seen too — the services read them on every call.
32
- */
33
- register(services) {
34
- const classes = new Map();
35
- const add = (key, service) => {
36
- if (RESERVED.has(key))
37
- throw new Error(`${this.name}: no service can be called "${key}" — it is the bounded context's own`);
38
- if (classes.has(key))
39
- throw new Error(`${this.name}: two services are called "${key}"`);
40
- classes.set(key, service);
41
- };
42
- for (const [key, service] of Object.entries(services.features)) {
43
- if (typeof service !== "function" || !isFeatureClass(service))
44
- throw new Error(`${this.name}: "${key}" is not a Feature — an ApplicationService goes in applicationServices`);
45
- add(key, service);
46
- }
47
- for (const [key, service] of Object.entries(services.applicationServices ?? {})) {
48
- if (typeof service !== "function" || !isApplicationServiceClass(service))
49
- throw new Error(`${this.name}: "${key}" is not an ApplicationService — a Feature goes in features`);
50
- add(key, service);
51
- }
52
- const registry = {
53
- name: this.name,
54
- dependencies: this.#dependencies,
55
- classes,
56
- features: new Set(Object.keys(services.features)),
57
- instances: new Map(),
58
- context: this.context,
59
- };
60
- return assemble({ registry, overrides: {}, scope: {} });
61
- }
62
- }
63
- function assemble(binding) {
64
- const { registry } = binding;
65
- const held = {};
66
- Object.defineProperties(held, {
67
- name: { value: registry.name, enumerable: true },
68
- context: { value: registry.context, enumerable: true },
69
- with: {
70
- value: (dependencies) => assemble({ ...binding, overrides: { ...binding.overrides, ...dependencies } }),
71
- },
72
- scoped: {
73
- value: (values) => assemble({ ...binding, scope: { ...binding.scope, ...values } }),
74
- },
75
- });
76
- // Each service is made and wrapped the first time it is asked for, then kept on this object.
77
- for (const key of registry.classes.keys())
78
- Object.defineProperty(held, key, {
79
- configurable: true,
80
- enumerable: true,
81
- get() {
82
- const service = intercepted(key, binding, undefined);
83
- Object.defineProperty(held, key, { value: service, enumerable: true });
84
- return service;
85
- },
86
- });
87
- return held;
88
- }
89
- function instanceOf(registry, key) {
90
- const known = registry.instances.get(key);
91
- if (known !== undefined)
92
- return known;
93
- const Service = registry.classes.get(key);
94
- if (Service === undefined)
95
- throw new Error(`${registry.name}: no service is called "${key}"`);
96
- const made = new Service();
97
- registry.instances.set(key, made);
98
- return made;
99
- }
100
- /**
101
- * A service as its holder sees it: each public method runs in an execution of its own — a child
102
- * of `parent` when an ApplicationService runs it. A method is the same function every time it is
103
- * read, so a screen can keep it (`useService(project.Issues.close)`).
104
- */
105
- function intercepted(key, binding, parent) {
106
- const target = instanceOf(binding.registry, key);
107
- const traced = methodsOf(target);
108
- const methods = new Map();
109
- return new Proxy(target, {
110
- get(held, property) {
111
- const value = Reflect.get(held, property, held);
112
- if (typeof property !== "string" ||
113
- typeof value !== "function" ||
114
- !traced.has(property))
115
- return value;
116
- let method = methods.get(property);
117
- if (method === undefined) {
118
- const body = value;
119
- method = (...args) => run(held, key, property, body, args, binding, parent);
120
- methods.set(property, method);
121
- }
122
- return method;
123
- },
124
- });
125
- }
126
- function dependencyOf(binding, name) {
127
- if (Object.hasOwn(binding.overrides, name))
128
- return { found: true, value: binding.overrides[name] };
129
- if (Object.hasOwn(binding.registry.dependencies, name))
130
- return { found: true, value: binding.registry.dependencies[name] };
131
- return { found: false, value: undefined };
132
- }
133
- /**
134
- * The run's own view of the instance: its context, its dependencies bound to this execution
135
- * (each bound the first time it is read), and — for an ApplicationService — its Features as
136
- * children of this run. Anything else is the instance's.
137
- */
138
- function viewOf(target, binding, execution) {
139
- const bound = new Map();
140
- let features;
141
- const isComposer = composes(target);
142
- return new Proxy(target, {
143
- get(held, property, receiver) {
144
- if (property === "context")
145
- return execution.context;
146
- if (property === "span")
147
- return execution.span;
148
- if (property === "feature" && isComposer)
149
- return (features ??= featuresOf(binding, execution));
150
- if (typeof property === "string") {
151
- if (bound.has(property))
152
- return bound.get(property);
153
- const dependency = dependencyOf(binding, property);
154
- if (dependency.found) {
155
- const value = bindDependency(dependency.value, execution);
156
- bound.set(property, value);
157
- return value;
158
- }
159
- }
160
- return Reflect.get(held, property, receiver);
161
- },
162
- set(held, property, value) {
163
- if (ANSWERED.has(String(property)))
164
- return false;
165
- if (typeof property === "string" && dependencyOf(binding, property).found)
166
- return false;
167
- // What a method writes on `this` lands on the instance — it is the bounded context's.
168
- return Reflect.set(held, property, value);
169
- },
170
- });
171
- }
172
- /** An ApplicationService's `this.feature`: each Feature, asked by name, a child of the run. */
173
- function featuresOf(binding, execution) {
174
- const children = new Map();
175
- return new Proxy({}, {
176
- get(_, property) {
177
- if (typeof property !== "string" || !binding.registry.features.has(property))
178
- return undefined;
179
- let child = children.get(property);
180
- if (child === undefined) {
181
- child = intercepted(property, binding, execution);
182
- children.set(property, child);
183
- }
184
- return child;
185
- },
186
- });
187
- }
188
- const NOTHING = {};
189
- /** What a gesture runs with: the process's context, under it the bounded context's, its scope's. */
190
- function contextOf(binding) {
191
- return {
192
- ...processContext.values,
193
- ...binding.registry.context.values,
194
- ...binding.scope,
195
- };
196
- }
197
- /** What watches a use case never changes what it answers: a tracer or reporter that throws is ignored. */
198
- function quietly(work, otherwise) {
199
- try {
200
- return work();
201
- }
202
- catch {
203
- return otherwise;
204
- }
205
- }
206
- const NO_SPAN = {
207
- traceId: "",
208
- spanId: "",
209
- sampled: false,
210
- set: () => undefined,
211
- end: () => undefined,
212
- };
213
- function run(target, key, method, body, args, binding, parent) {
214
- const settings = processNow();
215
- const boundedContext = binding.registry.name;
216
- // As sincpro_framework names a use case: the span `context/Name`, the identity `context.Name`.
217
- const identity = method === "execute" ? key : `${key}.${method}`;
218
- const useCase = `${boundedContext}.${identity}`;
219
- const context = parent?.context ?? quietly(() => contextOf(binding), NOTHING);
220
- const id = newEntityId();
221
- const attributes = {
222
- "sincpro.use_case": useCase,
223
- "sincpro.layer": composes(target) ? "application_service" : "feature",
224
- "sincpro.context": boundedContext,
225
- "sincpro.execution_id": id,
226
- };
227
- const span = quietly(() => settings.tracer.start(`${boundedContext}/${identity}`, {
228
- ...(parent === undefined ? {} : { parent: parent.span }),
229
- attributes,
230
- }), NO_SPAN);
231
- const execution = { id, useCase, boundedContext, parent, span, context };
232
- const finish = (failed, error) => {
233
- const outcome = failed ? quietly(() => settings.outcomeOf(error), "failed") : "ok";
234
- quietly(() => span.end(outcome, error), undefined);
235
- if (outcome === "failed" && firstReport(error))
236
- quietly(() => settings.reporter?.report(error, {
237
- useCase,
238
- traceId: span.traceId,
239
- executionId: id,
240
- context,
241
- }), undefined);
242
- };
243
- let answer;
244
- try {
245
- answer = body.apply(viewOf(target, binding, execution), args);
246
- }
247
- catch (error) {
248
- finish(true, error);
249
- throw error;
250
- }
251
- if (answer instanceof Promise)
252
- return answer.then((value) => {
253
- finish(false);
254
- return value;
255
- }, (error) => {
256
- finish(true, error);
257
- throw error;
258
- });
259
- finish(false);
260
- return answer;
261
- }
@@ -1,63 +0,0 @@
1
- import type { Transport } from "../client/transport.js";
2
- import type { Span } from "../observability/ports.js";
3
- /**
4
- * One run of a use case: its span, what it runs with, and who ran it.
5
- *
6
- * Context: sincpro_framework keeps the running use case in a context variable; a browser has
7
- * nothing like it that survives an `await` (AsyncContext is a proposal, Zone.js patches the
8
- * world), and a global stack mixes the parents of two use cases running at once — which a
9
- * screen with several windows does all the time. So an execution is a value, handed to what
10
- * the use case calls: its Features (`this.feature`) and its dependencies (`BIND`).
11
- */
12
- export type Execution = {
13
- /** UUID v7, as 32 hex: this run. */
14
- readonly id: string;
15
- /** `<bounded context>.<Service>[.<method>]` — the use case's identity, as the backend names its own. */
16
- readonly useCase: string;
17
- /** The bounded context the use case belongs to. */
18
- readonly boundedContext: string;
19
- /** The run that executed this one: an ApplicationService, for its Features. */
20
- readonly parent: Execution | undefined;
21
- readonly span: Span;
22
- /** What it runs with, read once where the gesture started and inherited by every child. */
23
- readonly context: Readonly<Record<string, unknown>>;
24
- };
25
- /**
26
- * A dependency that has something to say about the execution it is used in: given that
27
- * execution, it answers itself bound to it. The transport and every generated client are;
28
- * any other dependency of a bounded context is handed as it is.
29
- */
30
- export declare const BIND: unique symbol;
31
- export type Bindable<T> = {
32
- [BIND]: (execution: Execution) => T;
33
- };
34
- /**
35
- * What every request of an execution carries: its trace (`traceparent`, W3C) and its context
36
- * (`sincpro-context`, the header sincpro_framework's entrances read, written with `plainCodec`).
37
- * Nothing when there is no trace and no context that travels.
38
- */
39
- export declare function headersOf(execution: Execution): Record<string, string>;
40
- /**
41
- * The transport, writing the execution's headers on every call — the caller's own headers of a
42
- * call still win.
43
- *
44
- * Context: the transport's own headers (`restTransport({ headers })`) are the session's — the
45
- * token, the tenant — the same for every call; these are the use case's, different for two use
46
- * cases running at once, so they travel per call (`CallOptions`).
47
- */
48
- export declare function bindTransport(transport: Transport, execution: Execution): Transport;
49
- /**
50
- * A client made over a transport, made again over the transport bound to each execution it is
51
- * used in. What a generated `clientOf(transport)` answers, so a Feature that calls
52
- * `this.client.sendIssue(…)` sends that request with its own trace.
53
- *
54
- * export function clientOf(transport: Transport) {
55
- * return bindable(transport, (transport) => ({ sendIssue: (body) => transport.send(…) }));
56
- * }
57
- *
58
- * Context: it takes the function that makes the client, not the client — rebuilding through
59
- * `clientOf` itself would make TypeScript infer `clientOf` from its own return.
60
- */
61
- export declare function bindable<T extends object>(transport: Transport, make: (transport: Transport) => T): T;
62
- /** A dependency as the execution should see it: bound when it can be, as it is otherwise. */
63
- export declare function bindDependency<T>(value: T, execution: Execution): T;
@@ -1,85 +0,0 @@
1
- import type { ContextValues } from "../context/store.js";
2
- import type { Span } from "../observability/ports.js";
3
- /**
4
- * `Feature` and `ApplicationService`: the two kinds of service a bounded context holds, as in
5
- * sincpro_framework. Each bounded context gives them its dependencies once, in its own base:
6
- *
7
- * // domains/project/infrastructure/bounded_context.ts
8
- * export class Feature extends BaseFeature<ProjectDependencies> {}
9
- * export class ApplicationService extends BaseApplicationService<ProjectDependencies, ProjectFeatures> {}
10
- *
11
- * // domains/project/services/issues.ts
12
- * export class Issues extends Feature {
13
- * close(input: dto.CommandCloseIssue) {
14
- * this.session.require(); // each dependency is a property — typed
15
- * return this.client.closeIssue(input);
16
- * }
17
- * }
18
- *
19
- * // domains/project/services/close_and_notify.ts
20
- * export class CloseAndNotify extends ApplicationService {
21
- * async execute(input: dto.CommandCloseIssue) {
22
- * await this.feature.Issues.close(input); // a child of this run, on the same trace
23
- * return this.feature.Notices.tell(input);
24
- * }
25
- * }
26
- *
27
- * Context: a Feature is a facade — the operations of one part of the domain, each a public
28
- * method; an ApplicationService composes the Features of its own bounded context and never
29
- * another ApplicationService (sincpro_framework's rule). Neither writes a span nor catches an
30
- * error to report it: the bounded context runs every public method inside its own execution
31
- * (`UseBoundedContext`).
32
- *
33
- * The dependencies are typed, not declared: `BaseFeature<ProjectDependencies>` types
34
- * `this.client` and `this.session` from the bounded context's dependency type — sincpro_framework's
35
- * `DependencyContextType` — and the bounded context hands them in on each run. So a service
36
- * constructs with nothing, keeps no dependency on `this`, and declares no `#private` field: a run
37
- * sees the instance through a view of its own (its dependencies bound to its trace), and
38
- * `#private` refuses views. TypeScript's `private` is the same promise without that.
39
- */
40
- declare class FeatureCore {
41
- }
42
- declare class ApplicationServiceCore extends FeatureCore {
43
- }
44
- /** What every service sees on `this` inside a method. */
45
- type Within<D extends object, C extends object> = Readonly<D> & {
46
- /**
47
- * What this run executes with: the process's context, its bounded context's, its scope's —
48
- * read once where the gesture started and the same for every Feature it runs.
49
- */
50
- readonly context: Readonly<Partial<C>>;
51
- /** This run's span: a use case says more about what it ran on (`this.span.set({ … })`). */
52
- readonly span: Span;
53
- };
54
- /** Service classes by the name a bounded context gives them: `{ Issues, Notices }`. */
55
- export type ServiceClasses = Readonly<Record<string, new () => object>>;
56
- /**
57
- * What a service offers to whoever holds it — `project.Issues`, `this.feature.Issues`: its public
58
- * methods, without its dependencies, its context or its `_` helpers.
59
- */
60
- export type ServiceMethods<I, D> = {
61
- readonly [M in keyof I as M extends keyof D | "context" | "feature" | "span" | `_${string}` ? never : I[M] extends (...args: never[]) => unknown ? M : never]: I[M];
62
- };
63
- /** The services of a set of classes, by their names. */
64
- export type Services<S extends ServiceClasses, D> = {
65
- readonly [K in keyof S]: ServiceMethods<InstanceType<S[K]>, D>;
66
- };
67
- type FeatureClass = abstract new <D extends object = Record<never, never>, C extends object = ContextValues>() => FeatureCore & Within<D, C>;
68
- type ApplicationServiceClass = abstract new <D extends object = Record<never, never>, F extends ServiceClasses = Record<never, never>, C extends object = ContextValues>() => ApplicationServiceCore & Within<D, C> & {
69
- /** The Features of this bounded context, each run a child of this one. */
70
- readonly feature: Services<F, D>;
71
- };
72
- /** The base of a bounded context's Features: `class Feature extends BaseFeature<Deps> {}`. */
73
- export declare const Feature: FeatureClass;
74
- /**
75
- * The base of a bounded context's ApplicationServices:
76
- * `class ApplicationService extends BaseApplicationService<Deps, typeof features> {}`.
77
- */
78
- export declare const ApplicationService: ApplicationServiceClass;
79
- /** Whether a class is a Feature — and not an ApplicationService. */
80
- export declare function isFeatureClass(service: new () => object): boolean;
81
- export declare function isApplicationServiceClass(service: new () => object): boolean;
82
- /** Whether a service composes Features — an ApplicationService. */
83
- export declare function composes(service: object): boolean;
84
- export declare function methodsOf(service: object): ReadonlySet<string>;
85
- export {};
@@ -1,86 +0,0 @@
1
- /**
2
- * `Feature` and `ApplicationService`: the two kinds of service a bounded context holds, as in
3
- * sincpro_framework. Each bounded context gives them its dependencies once, in its own base:
4
- *
5
- * // domains/project/infrastructure/bounded_context.ts
6
- * export class Feature extends BaseFeature<ProjectDependencies> {}
7
- * export class ApplicationService extends BaseApplicationService<ProjectDependencies, ProjectFeatures> {}
8
- *
9
- * // domains/project/services/issues.ts
10
- * export class Issues extends Feature {
11
- * close(input: dto.CommandCloseIssue) {
12
- * this.session.require(); // each dependency is a property — typed
13
- * return this.client.closeIssue(input);
14
- * }
15
- * }
16
- *
17
- * // domains/project/services/close_and_notify.ts
18
- * export class CloseAndNotify extends ApplicationService {
19
- * async execute(input: dto.CommandCloseIssue) {
20
- * await this.feature.Issues.close(input); // a child of this run, on the same trace
21
- * return this.feature.Notices.tell(input);
22
- * }
23
- * }
24
- *
25
- * Context: a Feature is a facade — the operations of one part of the domain, each a public
26
- * method; an ApplicationService composes the Features of its own bounded context and never
27
- * another ApplicationService (sincpro_framework's rule). Neither writes a span nor catches an
28
- * error to report it: the bounded context runs every public method inside its own execution
29
- * (`UseBoundedContext`).
30
- *
31
- * The dependencies are typed, not declared: `BaseFeature<ProjectDependencies>` types
32
- * `this.client` and `this.session` from the bounded context's dependency type — sincpro_framework's
33
- * `DependencyContextType` — and the bounded context hands them in on each run. So a service
34
- * constructs with nothing, keeps no dependency on `this`, and declares no `#private` field: a run
35
- * sees the instance through a view of its own (its dependencies bound to its trace), and
36
- * `#private` refuses views. TypeScript's `private` is the same promise without that.
37
- */
38
- class FeatureCore {
39
- }
40
- class ApplicationServiceCore extends FeatureCore {
41
- }
42
- /** The base of a bounded context's Features: `class Feature extends BaseFeature<Deps> {}`. */
43
- export const Feature = FeatureCore;
44
- /**
45
- * The base of a bounded context's ApplicationServices:
46
- * `class ApplicationService extends BaseApplicationService<Deps, typeof features> {}`.
47
- */
48
- export const ApplicationService = ApplicationServiceCore;
49
- /** Whether a class is a Feature — and not an ApplicationService. */
50
- export function isFeatureClass(service) {
51
- return (service.prototype instanceof FeatureCore &&
52
- !(service.prototype instanceof ApplicationServiceCore));
53
- }
54
- export function isApplicationServiceClass(service) {
55
- return service.prototype instanceof ApplicationServiceCore;
56
- }
57
- /** Whether a service composes Features — an ApplicationService. */
58
- export function composes(service) {
59
- return service instanceof ApplicationServiceCore;
60
- }
61
- /**
62
- * The methods a class adds over its base — what is traced — read once per class: every public
63
- * method of the class and its parents up to `Feature`, but `constructor` and a name that starts
64
- * with `_`.
65
- */
66
- const METHODS = new WeakMap();
67
- export function methodsOf(service) {
68
- const prototype = Object.getPrototypeOf(service);
69
- const known = METHODS.get(prototype);
70
- if (known !== undefined)
71
- return known;
72
- const names = new Set();
73
- for (let level = prototype; level !== null &&
74
- level !== ApplicationServiceCore.prototype &&
75
- level !== FeatureCore.prototype &&
76
- level !== Object.prototype; level = Object.getPrototypeOf(level))
77
- for (const name of Object.getOwnPropertyNames(level)) {
78
- const descriptor = Object.getOwnPropertyDescriptor(level, name);
79
- if (name !== "constructor" &&
80
- !name.startsWith("_") &&
81
- typeof descriptor?.value === "function")
82
- names.add(name);
83
- }
84
- METHODS.set(prototype, names);
85
- return names;
86
- }
@@ -1,60 +0,0 @@
1
- import { type Context, type StoreSource } from "../context/context.js";
2
- import { type ContextStore } from "../context/store.js";
3
- import { type ErrorReporter, type Outcome, type Tracer } from "../observability/ports.js";
4
- /**
5
- * What every bounded context of this process runs with — set once, in `main`, the way
6
- * sincpro_framework's process is: who traces, who reports, where the context lives.
7
- *
8
- * // entrypoints/main.tsx — the platform's pieces, built from the application's settings
9
- * configureProcess({
10
- * tracer: browserTracer({ endpoint: settings.otlp, service: "forge-web" }),
11
- * reporter: glitchtipReporter({ dsn: settings.glitchtip }),
12
- * context: memoryContexts(),
13
- * });
14
- *
15
- * Context: everything is optional, and nothing breaks without it. A process that never configures
16
- * runs every use case untraced, unreported, with its context in memory — so a bounded context is
17
- * usable the moment it is imported, and `main` adds what it watches with. The bounded contexts
18
- * read the process on every call, never at import: `project` may be defined before `main` runs.
19
- *
20
- * It replaces everything at once: what is not given goes back to its default — a test calls it
21
- * with its own tracer and starts from a clean context.
22
- */
23
- export type ProcessOptions = {
24
- /** The platform's (OpenTelemetry in a browser, a queue on a phone, memory in a test). */
25
- tracer?: Tracer;
26
- /** The platform's (GlitchTip, Sentry, memory in a test). Only what nobody expected reaches it. */
27
- reporter?: ErrorReporter;
28
- /** Where the context lives (`memoryContexts()` by default). */
29
- context?: ContextStore;
30
- /** How an error ended a use case (`outcomeOf` by default). Only `failed` is reported. */
31
- outcomeOf?: (error: unknown) => Outcome;
32
- };
33
- /** The process as a use case reads it. Not public: `configureProcess` is the way in. */
34
- export type Process = {
35
- readonly tracer: Tracer;
36
- readonly reporter: ErrorReporter | undefined;
37
- readonly store: ContextStore;
38
- readonly outcomeOf: (error: unknown) => Outcome;
39
- };
40
- export declare function configureProcess(options?: ProcessOptions): void;
41
- /** The process's store as every level of the context reads it — the process's and each bounded context's. */
42
- export declare const processStore: StoreSource;
43
- /** What the process runs with now. */
44
- export declare function processNow(): Process;
45
- /**
46
- * The process's level of the context: what every use case of every bounded context runs with —
47
- * the tenant, the company, the language.
48
- *
49
- * processContext.set({ tenant_id: "acme" });
50
- */
51
- export declare const processContext: Context;
52
- /**
53
- * How an error ended a use case, by default.
54
- *
55
- * Context: the backend reports what nobody expected and only that — a refusal it explained is
56
- * the business answering, not a fault. Here the same: a `TransportError` the server answered
57
- * with a 4xx is `refused`; a request the caller stopped waiting for is `aborted`; everything
58
- * else — a bug, a 5xx, a server nobody could reach — is `failed`, and reported.
59
- */
60
- export declare function outcomeOf(error: unknown): Outcome;
@@ -1,57 +0,0 @@
1
- import { TransportError } from "../client/transport.js";
2
- import { contextAt, } from "../context/context.js";
3
- import { memoryContexts } from "../context/store.js";
4
- import { NO_TRACER, } from "../observability/ports.js";
5
- function settled(options) {
6
- return {
7
- tracer: options.tracer ?? NO_TRACER,
8
- reporter: options.reporter,
9
- store: options.context ?? memoryContexts(),
10
- outcomeOf: options.outcomeOf ?? outcomeOf,
11
- };
12
- }
13
- let current = settled({});
14
- const replacing = new Set();
15
- export function configureProcess(options = {}) {
16
- const before = current.store;
17
- current = settled(options);
18
- if (current.store !== before)
19
- for (const listener of [...replacing])
20
- listener();
21
- }
22
- /** The process's store as every level of the context reads it — the process's and each bounded context's. */
23
- export const processStore = {
24
- current: () => current.store,
25
- replaced(listener) {
26
- replacing.add(listener);
27
- return () => void replacing.delete(listener);
28
- },
29
- };
30
- /** What the process runs with now. */
31
- export function processNow() {
32
- return current;
33
- }
34
- /**
35
- * The process's level of the context: what every use case of every bounded context runs with —
36
- * the tenant, the company, the language.
37
- *
38
- * processContext.set({ tenant_id: "acme" });
39
- */
40
- export const processContext = contextAt("process", processStore);
41
- /**
42
- * How an error ended a use case, by default.
43
- *
44
- * Context: the backend reports what nobody expected and only that — a refusal it explained is
45
- * the business answering, not a fault. Here the same: a `TransportError` the server answered
46
- * with a 4xx is `refused`; a request the caller stopped waiting for is `aborted`; everything
47
- * else — a bug, a 5xx, a server nobody could reach — is `failed`, and reported.
48
- */
49
- export function outcomeOf(error) {
50
- if (error instanceof TransportError && error.status !== null)
51
- return error.status >= 400 && error.status < 500 ? "refused" : "failed";
52
- if (error !== null &&
53
- typeof error === "object" &&
54
- error.name === "AbortError")
55
- return "aborted";
56
- return "failed";
57
- }
@@ -1,2 +0,0 @@
1
- import type { Transport } from "../client/transport.js";
2
- export declare function screensTransport(transport: Transport, name?: string): Transport;