@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
@@ -0,0 +1,212 @@
1
+ /** The kinds that fail the same way on every attempt — set aside at the first failure. */
2
+ export const PERMANENT_KINDS = new Set([
3
+ "invalid",
4
+ "unauthenticated",
5
+ "permission_denied",
6
+ "not_found",
7
+ "key_reused",
8
+ "domain",
9
+ ]);
10
+ /** What the caller does next, by kind — the backend's `NEXT_STEP_OF`. */
11
+ const NEXT_STEP_OF = {
12
+ invalid: "fix_request",
13
+ unauthenticated: "authenticate",
14
+ permission_denied: "ask_for_access",
15
+ not_found: "do_not_retry",
16
+ conflict: "refresh_and_retry",
17
+ in_progress: "retry_later",
18
+ key_reused: "fix_request",
19
+ domain: "do_not_retry",
20
+ exhausted: "retry_later",
21
+ unavailable: "retry_later",
22
+ unknown_outcome: "verify_then_retry",
23
+ internal: "retry_later",
24
+ };
25
+ /** The kinds that are the system's problem: reported, never shown as the caller's answer. */
26
+ const FAULTS = new Set([
27
+ "internal",
28
+ "unavailable",
29
+ "unknown_outcome",
30
+ ]);
31
+ const RETRYABLE_STEPS = new Set([
32
+ "retry_later",
33
+ "refresh_and_retry",
34
+ ]);
35
+ /** A retryable kind that says nothing waits a second, as the backend's `DEFAULT_RETRY_AFTER`. */
36
+ const DEFAULT_RETRY_AFTER = 1;
37
+ const KINDS = new Set(Object.keys(NEXT_STEP_OF));
38
+ function isKind(value) {
39
+ return typeof value === "string" && KINDS.has(value);
40
+ }
41
+ /**
42
+ * The kind each wire's own code stands for, when an answer carries no `Problem` — the backend's
43
+ * tables, read back, so a transport of any wire (REST, JSON-RPC, gRPC, another backend's) builds
44
+ * errors that declare the same kinds:
45
+ *
46
+ * class RpcError extends Error {
47
+ * constructor(readonly code: number, message: string) { super(message); }
48
+ * get failureKind() { return kindOfJsonRpcCode(this.code); }
49
+ * }
50
+ */
51
+ export function kindOfHttpStatus(status) {
52
+ if (status === 401)
53
+ return "unauthenticated";
54
+ if (status === 403)
55
+ return "permission_denied";
56
+ if (status === 404 || status === 410)
57
+ return "not_found";
58
+ if (status === 409 || status === 412)
59
+ return "conflict";
60
+ if (status === 429)
61
+ return "exhausted";
62
+ if (status === 502 || status === 503)
63
+ return "unavailable";
64
+ if (status === 504)
65
+ return "unknown_outcome";
66
+ if (status >= 500)
67
+ return "internal";
68
+ return "invalid";
69
+ }
70
+ /** The kind a gRPC status code stands for — the backend's gRPC table, read back. */
71
+ export function kindOfGrpcCode(code) {
72
+ switch (code) {
73
+ case 3: // INVALID_ARGUMENT
74
+ case 11: // OUT_OF_RANGE
75
+ return "invalid";
76
+ case 16: // UNAUTHENTICATED
77
+ return "unauthenticated";
78
+ case 7: // PERMISSION_DENIED
79
+ return "permission_denied";
80
+ case 5: // NOT_FOUND
81
+ case 12: // UNIMPLEMENTED
82
+ return "not_found";
83
+ case 6: // ALREADY_EXISTS
84
+ case 10: // ABORTED — also an idempotent run in flight
85
+ return "conflict";
86
+ case 9: // FAILED_PRECONDITION — also a reused idempotency key
87
+ return "domain";
88
+ case 8: // RESOURCE_EXHAUSTED
89
+ return "exhausted";
90
+ case 14: // UNAVAILABLE
91
+ return "unavailable";
92
+ case 1: // CANCELLED
93
+ case 4: // DEADLINE_EXCEEDED
94
+ return "unknown_outcome";
95
+ default: // UNKNOWN, INTERNAL, DATA_LOSS
96
+ return "internal";
97
+ }
98
+ }
99
+ /** The kind a JSON-RPC error code stands for — the backend's catalogue, read back. */
100
+ export function kindOfJsonRpcCode(code) {
101
+ switch (code) {
102
+ case -32700:
103
+ case -32600:
104
+ case -32602:
105
+ return "invalid";
106
+ case -32601:
107
+ case -32004:
108
+ return "not_found";
109
+ case -32000:
110
+ return "unavailable";
111
+ case -32001:
112
+ return "unauthenticated";
113
+ case -32003:
114
+ return "permission_denied";
115
+ case -32009:
116
+ return "conflict";
117
+ case -32010:
118
+ return "domain";
119
+ case -32022:
120
+ return "key_reused";
121
+ case -32024:
122
+ return "unknown_outcome";
123
+ case -32029: // an idempotent run in flight, or a limit spent — the problem says which
124
+ return "exhausted";
125
+ default:
126
+ return "internal";
127
+ }
128
+ }
129
+ function upperSnake(kind) {
130
+ return kind.toUpperCase();
131
+ }
132
+ /** What the error declares: on itself first, then on its class. */
133
+ function declared(error) {
134
+ const own = error;
135
+ const type = (error.constructor ?? {});
136
+ return {
137
+ ...(isKind(own.failureKind ?? type.failureKind)
138
+ ? { failureKind: own.failureKind ?? type.failureKind }
139
+ : {}),
140
+ ...(typeof (own.reason ?? type.reason) === "string"
141
+ ? { reason: own.reason ?? type.reason }
142
+ : {}),
143
+ ...(typeof (own.retryAfter ?? type.retryAfter) === "number"
144
+ ? { retryAfter: own.retryAfter ?? type.retryAfter }
145
+ : {}),
146
+ };
147
+ }
148
+ function isProblem(value) {
149
+ return (value !== null &&
150
+ typeof value === "object" &&
151
+ isKind(value.kind) &&
152
+ typeof value.next_step === "string");
153
+ }
154
+ function failure(kind, said, aborted = false) {
155
+ const nextStep = said.nextStep ?? NEXT_STEP_OF[kind];
156
+ const retryable = RETRYABLE_STEPS.has(nextStep);
157
+ return {
158
+ kind,
159
+ reason: said.reason ?? upperSnake(kind),
160
+ nextStep,
161
+ retryable,
162
+ retryAfter: retryable ? (said.retryAfter ?? DEFAULT_RETRY_AFTER) : undefined,
163
+ permanent: !aborted && PERMANENT_KINDS.has(kind),
164
+ fault: !aborted && FAULTS.has(kind),
165
+ aborted,
166
+ problem: said.problem,
167
+ };
168
+ }
169
+ /**
170
+ * The failure `error` is.
171
+ *
172
+ * 1. Whoever asked stopped waiting (`AbortError`): aborted — a write may have run, so its kind is
173
+ * `unknown_outcome`, but it is nobody's failure.
174
+ * 2. What the error declares (`failureKind`, `reason`, `retryAfter`), on itself or its class.
175
+ * 3. What the server said (`problem`, a `Problem` — the same over REST, JSON-RPC and gRPC): its
176
+ * kind, reason, next step and wait.
177
+ * 4. A REST answer (`TransportError`) with a status and no problem: the kind its HTTP status
178
+ * stands for. Another wire's transport declares its kind instead (`kindOfGrpcCode`,
179
+ * `kindOfJsonRpcCode`).
180
+ * 5. A request that got no answer (`status: null`): `unknown_outcome` — it may have run.
181
+ * Final: anything else is `internal`, a fault.
182
+ */
183
+ export function failureOf(error) {
184
+ if (error === null || typeof error !== "object")
185
+ return failure("internal", {});
186
+ const named = error.name;
187
+ if (named === "AbortError")
188
+ return failure("unknown_outcome", {}, true);
189
+ const own = declared(error);
190
+ if (own.failureKind !== undefined)
191
+ return failure(own.failureKind, {
192
+ ...(own.reason !== undefined ? { reason: own.reason } : {}),
193
+ ...(own.retryAfter !== undefined ? { retryAfter: own.retryAfter } : {}),
194
+ });
195
+ const answered = error;
196
+ if (isProblem(answered.problem)) {
197
+ const problem = answered.problem;
198
+ return failure(problem.kind, {
199
+ reason: problem.reason,
200
+ nextStep: problem.next_step,
201
+ ...(problem.retry_after !== undefined ? { retryAfter: problem.retry_after } : {}),
202
+ problem,
203
+ });
204
+ }
205
+ if ("problem" in answered && "status" in answered) {
206
+ if (typeof answered.status === "number")
207
+ return failure(kindOfHttpStatus(answered.status), {});
208
+ if (answered.status === null)
209
+ return failure("unknown_outcome", {});
210
+ }
211
+ return failure("internal", {});
212
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The values of the wire, as the backend writes them: ids, a day and an instant, `Decimal`,
3
+ * `Money`, a to-many as a page of its records, and how any value is written and read.
4
+ */
5
+ export { collectionOf, type EntityCollection, type HeldCollection, isCollection, isPartial, relatedCriteria, type RelationKeys, totalOf, } from "./collection.js";
6
+ export { Decimal, type DecimalLike } from "./decimal.js";
7
+ export { type DeclaredFailure, type Failure, failureOf, kindOfGrpcCode, kindOfHttpStatus, kindOfJsonRpcCode, PERMANENT_KINDS, } from "./failures.js";
8
+ export { isEntityId, momentOf, newEntityId } from "./ids.js";
9
+ export { Money } from "./money.js";
10
+ export { BYTES, type JsonValue, pack, readValues, type Rebuild, rebuilt, unpack, valuesOf, } from "./serialization.js";
11
+ export { dayOf, installTimeZone, instantOf, isDay, timeZoneNow, wallTimeOf, } from "./time.js";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The values of the wire, as the backend writes them: ids, a day and an instant, `Decimal`,
3
+ * `Money`, a to-many as a page of its records, and how any value is written and read.
4
+ */
5
+ export { collectionOf, isCollection, isPartial, relatedCriteria, totalOf, } from "./collection.js";
6
+ export { Decimal } from "./decimal.js";
7
+ export { failureOf, kindOfGrpcCode, kindOfHttpStatus, kindOfJsonRpcCode, PERMANENT_KINDS, } from "./failures.js";
8
+ export { isEntityId, momentOf, newEntityId } from "./ids.js";
9
+ export { Money } from "./money.js";
10
+ export { BYTES, pack, readValues, rebuilt, unpack, valuesOf, } from "./serialization.js";
11
+ export { dayOf, installTimeZone, instantOf, isDay, timeZoneNow, wallTimeOf, } from "./time.js";
@@ -0,0 +1,6 @@
1
+ import type { ContextStore } from "../domain/store.js";
2
+ /**
3
+ * The context in this process's memory: the default, on every platform. A test gets a fresh one
4
+ * from `configureProcess()`.
5
+ */
6
+ export declare function memoryContexts(): ContextStore;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The context in this process's memory: the default, on every platform. A test gets a fresh one
3
+ * from `configureProcess()`.
4
+ */
5
+ export function memoryContexts() {
6
+ const held = new Map();
7
+ const versions = new Map();
8
+ const listeners = new Map();
9
+ const changed = (key) => {
10
+ versions.set(key, (versions.get(key) ?? 0) + 1);
11
+ for (const listener of [...(listeners.get(key) ?? [])])
12
+ listener();
13
+ };
14
+ return {
15
+ load: (key) => held.get(key),
16
+ save(key, values) {
17
+ // A copy: whoever handed the values may keep changing their object.
18
+ held.set(key, { ...values });
19
+ changed(key);
20
+ },
21
+ delete(key) {
22
+ if (!held.delete(key))
23
+ return;
24
+ changed(key);
25
+ },
26
+ version: (key) => versions.get(key) ?? 0,
27
+ watch(key, listener) {
28
+ const own = listeners.get(key) ?? new Set();
29
+ listeners.set(key, own);
30
+ own.add(listener);
31
+ return () => void own.delete(listener);
32
+ },
33
+ };
34
+ }
@@ -0,0 +1,3 @@
1
+ import { type ContextCodec } from "../domain/codec.js";
2
+ /** The codec for anything outside the codebase: only the values that travel, as JSON. */
3
+ export declare const plainCodec: ContextCodec;
@@ -0,0 +1,16 @@
1
+ import { travelling, } from "../domain/codec.js";
2
+ /** The codec for anything outside the codebase: only the values that travel, as JSON. */
3
+ export const plainCodec = {
4
+ dumps: (values) => JSON.stringify(travelling(values)),
5
+ loads(text) {
6
+ try {
7
+ const found = JSON.parse(text);
8
+ return found !== null && typeof found === "object" && !Array.isArray(found)
9
+ ? found
10
+ : {};
11
+ }
12
+ catch {
13
+ return {};
14
+ }
15
+ },
16
+ };
@@ -23,4 +23,3 @@ export interface ContextCodec {
23
23
  export declare function travels(value: unknown): boolean;
24
24
  /** The values of `context` that travel; the rest stays in this process. */
25
25
  export declare function travelling(context: ContextValues): ContextValues;
26
- export declare const plainCodec: ContextCodec;
@@ -28,17 +28,3 @@ export function travels(value) {
28
28
  export function travelling(context) {
29
29
  return Object.fromEntries(Object.entries(context).filter(([, value]) => travels(value)));
30
30
  }
31
- export const plainCodec = {
32
- dumps: (values) => JSON.stringify(travelling(values)),
33
- loads(text) {
34
- try {
35
- const found = JSON.parse(text);
36
- return found !== null && typeof found === "object" && !Array.isArray(found)
37
- ? found
38
- : {};
39
- }
40
- catch {
41
- return {};
42
- }
43
- },
44
- };
@@ -0,0 +1,45 @@
1
+ import type { Span } from "../../observability/domain/tracing.js";
2
+ /**
3
+ * One run of a use case: its span, what it runs with, and who ran it.
4
+ *
5
+ * Context: sincpro_framework keeps the running use case in a context variable; a browser has
6
+ * nothing like it that survives an `await` (AsyncContext is a proposal, Zone.js patches the
7
+ * world), and a global stack mixes the parents of two use cases running at once — which a
8
+ * screen with several windows does all the time. So an execution is a value, handed to what
9
+ * the use case calls: the Features its `featureBus` runs, and its dependencies (`BIND`).
10
+ */
11
+ export type Execution = {
12
+ /** UUID v7, as 32 hex: this run. */
13
+ readonly id: string;
14
+ /** `<bounded context>.<Service>` — the use case's identity, as the backend names its own. */
15
+ readonly useCase: string;
16
+ /** The bounded context the use case belongs to. */
17
+ readonly boundedContext: string;
18
+ /** The run that executed this one: an ApplicationService, for its Features. */
19
+ readonly parent: Execution | undefined;
20
+ /**
21
+ * The chain it belongs to: the id of what started it all — the root run, or the first event
22
+ * of a flow it continues. Every run and event of one flow share it.
23
+ */
24
+ readonly correlationId: string;
25
+ /** What directly caused it: its parent run, or the event it executes; none for a root. */
26
+ readonly causationId: string | undefined;
27
+ /**
28
+ * What cancels it: the signal it was executed with, inherited by its Features — and by every
29
+ * request its bound transport sends.
30
+ */
31
+ readonly signal: AbortSignal | undefined;
32
+ readonly span: Span;
33
+ /** What it runs with, read once where the gesture started and inherited by every child. */
34
+ readonly context: Readonly<Record<string, unknown>>;
35
+ };
36
+ /**
37
+ * A dependency that has something to say about the execution it is used in: given that
38
+ * execution, it answers itself bound to it. The transport, every generated client and the
39
+ * predefined loggers are; any other dependency of a bounded context is handed as it is.
40
+ */
41
+ export declare const BIND: unique symbol;
42
+ export type Bindable<T> = {
43
+ [BIND]: (execution: Execution) => T;
44
+ };
45
+ export declare function isBindable<T>(value: T): value is T & Bindable<T>;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * A dependency that has something to say about the execution it is used in: given that
3
+ * execution, it answers itself bound to it. The transport, every generated client and the
4
+ * predefined loggers are; any other dependency of a bounded context is handed as it is.
5
+ */
6
+ export const BIND = Symbol.for("sincpro.client-framework.bind");
7
+ export function isBindable(value) {
8
+ return (value !== null &&
9
+ (typeof value === "object" || typeof value === "function") &&
10
+ typeof value[BIND] === "function");
11
+ }
@@ -44,8 +44,3 @@ export interface ContextStore {
44
44
  */
45
45
  watch?(key: string, listener: () => void): () => void;
46
46
  }
47
- /**
48
- * The context in this process's memory: the default, on every platform. A test gets a fresh one
49
- * from `configureProcess()`.
50
- */
51
- export declare function memoryContexts(): ContextStore;
@@ -23,37 +23,4 @@
23
23
  * Reads are synchronous on purpose: a use case reads its context where a gesture starts, and an
24
24
  * asynchronous store would make every use case wait for it.
25
25
  */
26
- /**
27
- * The context in this process's memory: the default, on every platform. A test gets a fresh one
28
- * from `configureProcess()`.
29
- */
30
- export function memoryContexts() {
31
- const held = new Map();
32
- const versions = new Map();
33
- const listeners = new Map();
34
- const changed = (key) => {
35
- versions.set(key, (versions.get(key) ?? 0) + 1);
36
- for (const listener of [...(listeners.get(key) ?? [])])
37
- listener();
38
- };
39
- return {
40
- load: (key) => held.get(key),
41
- save(key, values) {
42
- // A copy: whoever handed the values may keep changing their object.
43
- held.set(key, { ...values });
44
- changed(key);
45
- },
46
- delete(key) {
47
- if (!held.delete(key))
48
- return;
49
- changed(key);
50
- },
51
- version: (key) => versions.get(key) ?? 0,
52
- watch(key, listener) {
53
- const own = listeners.get(key) ?? new Set();
54
- listeners.set(key, own);
55
- own.add(listener);
56
- return () => void own.delete(listener);
57
- },
58
- };
59
- }
26
+ export {};
@@ -1,4 +1,4 @@
1
- import type { ContextStore, ContextValues } from "./store.js";
1
+ import type { ContextStore, ContextValues } from "../domain/store.js";
2
2
  /**
3
3
  * One level of the context: the process's (`processContext`) or a bounded context's
4
4
  * (`project.context`), read and written the same way on every platform.
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The context: the levels a call sees (the process, its bounded context, a scope), where they
3
+ * live (`ContextStore`, a port each platform implements), what travels with a request, and the
4
+ * execution a run carries.
5
+ *
6
+ * domain/ the ports and rules: `ContextStore`, `ContextCodec`, what travels, `Execution`
7
+ * adapters/ the implementations this package gives: memory, the plain codec
8
+ * entrypoint/ `Context`: one door over a level
9
+ * infrastructure/ the process's store and its level (`processContext`)
10
+ */
11
+ export { memoryContexts } from "./adapters/memory.js";
12
+ export { plainCodec } from "./adapters/plain-codec.js";
13
+ export { CONTEXT_HEADER, type ContextCodec, travelling, travels, } from "./domain/codec.js";
14
+ export { BIND, type Bindable, type Execution, } from "./domain/execution.js";
15
+ export type { ContextStore, ContextValues, } from "./domain/store.js";
16
+ export type { Context } from "./entrypoint/context.js";
17
+ export { processContext } from "./infrastructure/process.js";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The context: the levels a call sees (the process, its bounded context, a scope), where they
3
+ * live (`ContextStore`, a port each platform implements), what travels with a request, and the
4
+ * execution a run carries.
5
+ *
6
+ * domain/ the ports and rules: `ContextStore`, `ContextCodec`, what travels, `Execution`
7
+ * adapters/ the implementations this package gives: memory, the plain codec
8
+ * entrypoint/ `Context`: one door over a level
9
+ * infrastructure/ the process's store and its level (`processContext`)
10
+ */
11
+ export { memoryContexts } from "./adapters/memory.js";
12
+ export { plainCodec } from "./adapters/plain-codec.js";
13
+ export { CONTEXT_HEADER, travelling, travels, } from "./domain/codec.js";
14
+ export { BIND, } from "./domain/execution.js";
15
+ export { processContext } from "./infrastructure/process.js";
@@ -0,0 +1,13 @@
1
+ import type { ContextStore } from "../domain/store.js";
2
+ import { type Context, type StoreSource } from "../entrypoint/context.js";
3
+ /** @internal — `configureProcess` is the way in. */
4
+ export declare function replaceProcessStore(store: ContextStore): void;
5
+ /** The process's store as every level of the context reads it — the process's and each bounded context's. */
6
+ export declare const processStore: StoreSource;
7
+ /**
8
+ * The process's level of the context: what every use case of every bounded context runs with —
9
+ * the tenant, the company, the language.
10
+ *
11
+ * processContext.set({ tenant_id: "acme" });
12
+ */
13
+ export declare const processContext: Context;
@@ -0,0 +1,35 @@
1
+ import { memoryContexts } from "../adapters/memory.js";
2
+ import { contextAt, } from "../entrypoint/context.js";
3
+ /**
4
+ * Where this process keeps its context: the store `configureProcess` chose (`memoryContexts()`
5
+ * until it chooses), read on every call, never at import.
6
+ *
7
+ * Context: a level made before `main` configures the process (`project` is defined when its
8
+ * module loads) still reads the store chosen later; whoever listens moves to the new store and
9
+ * hears the change.
10
+ */
11
+ let current = memoryContexts();
12
+ const replacing = new Set();
13
+ /** @internal — `configureProcess` is the way in. */
14
+ export function replaceProcessStore(store) {
15
+ const before = current;
16
+ current = store;
17
+ if (current !== before)
18
+ for (const listener of [...replacing])
19
+ listener();
20
+ }
21
+ /** The process's store as every level of the context reads it — the process's and each bounded context's. */
22
+ export const processStore = {
23
+ current: () => current,
24
+ replaced(listener) {
25
+ replacing.add(listener);
26
+ return () => void replacing.delete(listener);
27
+ },
28
+ };
29
+ /**
30
+ * The process's level of the context: what every use case of every bounded context runs with —
31
+ * the tenant, the company, the language.
32
+ *
33
+ * processContext.set({ tenant_id: "acme" });
34
+ */
35
+ export const processContext = contextAt("process", processStore);
@@ -0,0 +1,27 @@
1
+ import type { Command, DomainEvent, Executable, ResponseClass } from "./dto.js";
2
+ import type { Cause, ExecuteOptions } from "./pipeline.js";
3
+ /** What the second argument of `execute` is: options, a delivered event's cause, or a type. */
4
+ export declare function optionsOf(second: unknown): ExecuteOptions;
5
+ /**
6
+ * What executes a Command or a domain event: the bounded context, a scope of it, an
7
+ * ApplicationService's features. A Command answers its Response; an event, whatever its
8
+ * subscriber returns.
9
+ */
10
+ export interface CommandBus {
11
+ execute<R>(command: Command<R>, returnType?: ResponseClass<NoInfer<R>>): Promise<R>;
12
+ execute<R>(command: Command<R>, options: ExecuteOptions): Promise<R>;
13
+ execute(event: DomainEvent, options?: ExecuteOptions | Cause): Promise<unknown>;
14
+ }
15
+ /** @internal — how a bus hands what it executes to its generation. */
16
+ export type Dispatch = (executable: Executable, options: ExecuteOptions) => Promise<unknown>;
17
+ /**
18
+ * What an ApplicationService composes its context's Features through (`this.featureBus`): the
19
+ * same pipeline, as a child of the running execution, and Features only.
20
+ */
21
+ export declare class FeatureBus implements CommandBus {
22
+ private readonly dispatch;
23
+ constructor(dispatch: Dispatch);
24
+ execute<R>(command: Command<R>, returnType?: ResponseClass<NoInfer<R>>): Promise<R>;
25
+ execute<R>(command: Command<R>, options: ExecuteOptions): Promise<R>;
26
+ execute(event: DomainEvent, options?: ExecuteOptions | Cause): Promise<unknown>;
27
+ }
@@ -0,0 +1,26 @@
1
+ /** What the second argument of `execute` is: options, a delivered event's cause, or a type. */
2
+ export function optionsOf(second) {
3
+ if (second === null || typeof second !== "object")
4
+ return {};
5
+ const given = second;
6
+ if (typeof given.id === "string" && typeof given.correlationId === "string")
7
+ return { cause: { id: given.id, correlationId: given.correlationId } };
8
+ return {
9
+ ...(given.signal ? { signal: given.signal } : {}),
10
+ ...(given.cause ? { cause: given.cause } : {}),
11
+ };
12
+ }
13
+ /**
14
+ * What an ApplicationService composes its context's Features through (`this.featureBus`): the
15
+ * same pipeline, as a child of the running execution, and Features only.
16
+ */
17
+ export class FeatureBus {
18
+ dispatch;
19
+ constructor(dispatch) {
20
+ this.dispatch = dispatch;
21
+ }
22
+ execute(executable, second) {
23
+ // As with Python's return_type, the answer is not validated at runtime.
24
+ return this.dispatch(executable, optionsOf(second));
25
+ }
26
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The DTOs of the application layer, as sincpro_framework's `DataTransferObject`: a Command asks
3
+ * for work and names, by its type, the Response it answers.
4
+ *
5
+ * class CommandCancelIssue extends Command<ResponseCancelIssue> {
6
+ * constructor(readonly issue_id: string) { super(); }
7
+ * }
8
+ * const answer = await project.execute(new CommandCancelIssue("x")); // ResponseCancelIssue
9
+ *
10
+ * Context: the backend asks `bus(dto, ResponseType)` for the type; here the Command carries it, so
11
+ * `execute` infers it with no second argument. The association is type-only: absent from a
12
+ * serialized DTO, not a runtime validator. A generated DTO is a remote contract (a type), not one
13
+ * of these: a local Command may implement it.
14
+ */
15
+ export declare abstract class DataTransferObject {
16
+ protected readonly dtoBrand: void;
17
+ }
18
+ export declare abstract class Response extends DataTransferObject {
19
+ }
20
+ declare const responseType: unique symbol;
21
+ export declare abstract class Command<R> extends DataTransferObject {
22
+ /** Type-only association: absent from serialized DTOs and not a runtime validator. */
23
+ readonly [responseType]: R;
24
+ }
25
+ /**
26
+ * A fact that already happened, as sincpro_framework's `DomainEvent`: executed like a Command, by
27
+ * the Feature or ApplicationService registered for its class — its subscriber in this bounded
28
+ * context —, through the same pipeline. Delivering it (a queue, an outbox) is the platform's.
29
+ *
30
+ * class OrderPaid extends DomainEvent {
31
+ * static readonly eventName = "shop.order.v1.paid";
32
+ * constructor(readonly order_id: string) { super(); }
33
+ * }
34
+ * project.feature(OrderPaid)(NotifyCustomer);
35
+ * await project.execute(new OrderPaid("o-1"));
36
+ *
37
+ * Context: the backend names an event on the wire by `name`, `<context>.<aggregate>.vN.<fact>`,
38
+ * and defaults it to the class's name; a client is minified, so the name is declared, as
39
+ * `serviceName` is. `name` is reserved, as the backend reserves it: every event reads its class's
40
+ * `eventName` there, and a subclass cannot make it a field.
41
+ */
42
+ export declare abstract class DomainEvent extends DataTransferObject {
43
+ protected readonly eventBrand: void;
44
+ /** Its wire name: its class's `eventName`. */
45
+ get name(): string;
46
+ }
47
+ /** What an event class declares: the name it is known by on the wire. */
48
+ type EventNamed = {
49
+ readonly eventName: string;
50
+ };
51
+ export type AnyCommand = Command<unknown>;
52
+ /** What a service is registered for and executes: a Command, or a domain event. */
53
+ export type Executable = AnyCommand | DomainEvent;
54
+ /** What it answers: a Command's Response; whatever its subscriber returns, for an event. */
55
+ export type Answer<T> = T extends Command<infer R> ? R : unknown;
56
+ /** A Command's class: what a service is registered for, by exact constructor identity. */
57
+ export type CommandClass<C extends AnyCommand> = abstract new (...args: never[]) => C;
58
+ /** A domain event's class, with its wire name. */
59
+ export type EventClass<E extends DomainEvent> = (abstract new (...args: never[]) => E) & EventNamed;
60
+ /** A Response's class: the backend's `return_type`, accepted for the same reading. */
61
+ export type ResponseClass<R> = abstract new (...args: never[]) => R;
62
+ export {};