@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,21 @@
1
+ /**
2
+ * The answer of a chain of error handlers to `error`, or what its last handler threw.
3
+ *
4
+ * await handled([toEmpty, logAndRethrow], new IssueNotFound()) -> toEmpty's answer
5
+ *
6
+ * 1. Each handler, first-registered-first, receives what the one before it threw.
7
+ * Final: the first answer; when every handler threw, the last thrown — the original error when
8
+ * there is no handler at all.
9
+ */
10
+ export async function handled(handlers, error) {
11
+ let current = error;
12
+ for (const handler of handlers) {
13
+ try {
14
+ return await handler(current);
15
+ }
16
+ catch (thrown) {
17
+ current = thrown;
18
+ }
19
+ }
20
+ throw current;
21
+ }
@@ -0,0 +1,35 @@
1
+ import type { Execution } from "../../context/domain/execution.js";
2
+ import type { ContextValues } from "../../context/domain/store.js";
3
+ import type { ErrorHandler } from "../domain/errors.js";
4
+ export interface RuntimeService {
5
+ execute(command: object): unknown;
6
+ }
7
+ type Layer = "feature" | "application_service";
8
+ export interface ExecutionOptions {
9
+ readonly boundedContext: string;
10
+ readonly name: string;
11
+ readonly layer: Layer;
12
+ readonly dependencies: ReadonlyMap<string, unknown>;
13
+ readonly context: () => ContextValues;
14
+ readonly parent?: Execution;
15
+ readonly featureBus: (execution: Execution) => object;
16
+ /** The bounded context's expected errors: they end `refused`, never reported. */
17
+ readonly expected: (error: unknown) => boolean;
18
+ /** The bounded context's handlers for this layer: what answers a failure instead of raising it. */
19
+ readonly handlers: readonly ErrorHandler[];
20
+ }
21
+ /**
22
+ * One pipeline for every registered Feature and ApplicationService.
23
+ *
24
+ * 1. A span `<bounded context>/<serviceName>`, a child of the parent run when there is one, and
25
+ * the execution every dependency, `this.logger` and child Feature are bound to.
26
+ * 2. The service runs, seeing that execution through its own view of the instance.
27
+ * 3. A failure is observed where it happened: its outcome (expected by the bounded context →
28
+ * `refused`, else the process's `outcomeOf`), the span closed with it, its log line and —
29
+ * when `failed`, and only once per error along the run — its report.
30
+ * 4. Then the layer's error handlers answer it, in order; with none, or when the last throws,
31
+ * the error reaches the caller.
32
+ * Final: the service's answer, or its handlers'.
33
+ */
34
+ export declare function executeService(target: RuntimeService, command: object, options: ExecutionOptions): Promise<unknown>;
35
+ export {};
@@ -0,0 +1,137 @@
1
+ import { newEntityId } from "../../common/ids.js";
2
+ import { bindDependency } from "./binding.js";
3
+ import { handled } from "./error-handlers.js";
4
+ import { NO_TRACER, } from "../../observability/domain/tracing.js";
5
+ import { observabilityNow } from "../../observability/infrastructure/registry.js";
6
+ import { executionLogger, logExecution, logHandled, } from "../../observability/services/logging.js";
7
+ import { firstReport } from "../../observability/services/reporting.js";
8
+ const REPORTED_BY_EXECUTION = new WeakMap();
9
+ /** Observability adapters are best-effort; their failures must not replace a business result. */
10
+ function observe(work, fallback) {
11
+ try {
12
+ return work();
13
+ }
14
+ catch {
15
+ return fallback;
16
+ }
17
+ }
18
+ function serviceView(target, options, execution) {
19
+ const dependencies = new Map();
20
+ const logger = executionLogger(options.layer, execution);
21
+ let features;
22
+ return new Proxy(target, {
23
+ get(held, property, receiver) {
24
+ if (property === "context")
25
+ return execution.context;
26
+ if (property === "span")
27
+ return execution.span;
28
+ if (property === "logger")
29
+ return logger;
30
+ if (property === "featureBus" && options.layer === "application_service") {
31
+ return (features ??= options.featureBus(execution));
32
+ }
33
+ if (typeof property === "string" && options.dependencies.has(property)) {
34
+ if (!dependencies.has(property)) {
35
+ dependencies.set(property, bindDependency(options.dependencies.get(property), execution));
36
+ }
37
+ return dependencies.get(property);
38
+ }
39
+ if (Reflect.has(held, property))
40
+ return Reflect.get(held, property, receiver);
41
+ if (typeof property === "string") {
42
+ throw new Error(`${options.boundedContext}: dependency or member is not registered: ${property}`);
43
+ }
44
+ return undefined;
45
+ },
46
+ set(held, property, value) {
47
+ if (property === "context" ||
48
+ property === "span" ||
49
+ property === "logger" ||
50
+ property === "featureBus" ||
51
+ (typeof property === "string" && options.dependencies.has(property))) {
52
+ throw new Error(`Execution dependencies and context are read-only: ${String(property)}`);
53
+ }
54
+ return Reflect.set(held, property, value);
55
+ },
56
+ });
57
+ }
58
+ /**
59
+ * One pipeline for every registered Feature and ApplicationService.
60
+ *
61
+ * 1. A span `<bounded context>/<serviceName>`, a child of the parent run when there is one, and
62
+ * the execution every dependency, `this.logger` and child Feature are bound to.
63
+ * 2. The service runs, seeing that execution through its own view of the instance.
64
+ * 3. A failure is observed where it happened: its outcome (expected by the bounded context →
65
+ * `refused`, else the process's `outcomeOf`), the span closed with it, its log line and —
66
+ * when `failed`, and only once per error along the run — its report.
67
+ * 4. Then the layer's error handlers answer it, in order; with none, or when the last throws,
68
+ * the error reaches the caller.
69
+ * Final: the service's answer, or its handlers'.
70
+ */
71
+ export async function executeService(target, command, options) {
72
+ const ports = observabilityNow();
73
+ const startedAt = Date.now();
74
+ const useCase = `${options.boundedContext}.${options.name}`;
75
+ const context = options.parent?.context ?? options.context();
76
+ const id = newEntityId();
77
+ const attributes = {
78
+ "sincpro.use_case": useCase,
79
+ "sincpro.layer": options.layer,
80
+ "sincpro.context": options.boundedContext,
81
+ "sincpro.execution_id": id,
82
+ };
83
+ const span = observe(() => ports.tracer.start(`${options.boundedContext}/${options.name}`, {
84
+ ...(options.parent ? { parent: options.parent.span } : {}),
85
+ attributes,
86
+ }), NO_TRACER.start(useCase, {}));
87
+ const execution = {
88
+ id,
89
+ useCase,
90
+ boundedContext: options.boundedContext,
91
+ parent: options.parent,
92
+ span,
93
+ context,
94
+ };
95
+ const reported = options.parent
96
+ ? (REPORTED_BY_EXECUTION.get(options.parent) ?? new Set())
97
+ : new Set();
98
+ REPORTED_BY_EXECUTION.set(execution, reported);
99
+ logExecution(options.layer, execution);
100
+ const finish = (error) => {
101
+ const outcome = error
102
+ ? observe(() => (options.expected(error.value) ? "refused" : ports.outcomeOf(error.value)), "failed")
103
+ : "ok";
104
+ observe(() => span.end(outcome, error?.value), undefined);
105
+ logExecution(options.layer, execution, {
106
+ outcome,
107
+ durationMs: Math.max(0, Date.now() - startedAt),
108
+ });
109
+ if (outcome === "failed" && error && ports.reporter && !reported.has(error.value)) {
110
+ reported.add(error.value);
111
+ // Mark for reportError outside the call; a different root may report the same Error.
112
+ firstReport(error.value);
113
+ observe(() => ports.reporter?.report(error.value, {
114
+ useCase,
115
+ traceId: span.traceId,
116
+ executionId: id,
117
+ context,
118
+ }), undefined);
119
+ }
120
+ };
121
+ let answer;
122
+ try {
123
+ answer = await Reflect.apply(target.execute, serviceView(target, options, execution), [
124
+ command,
125
+ ]);
126
+ }
127
+ catch (error) {
128
+ finish({ value: error });
129
+ if (options.handlers.length === 0)
130
+ throw error;
131
+ const recovered = await handled(options.handlers, error);
132
+ logHandled(options.layer, { execution });
133
+ return recovered;
134
+ }
135
+ finish();
136
+ return answer;
137
+ }
package/dist/index.d.ts CHANGED
@@ -3,45 +3,23 @@
3
3
  * service needs, on any platform.
4
4
  *
5
5
  * Context: one entry, not one per area — the package has no side effects at import
6
- * (`sideEffects: false`), so an application pays only for what it uses, and a use case, the
7
- * client it calls and the entity it reads come from the same place.
6
+ * (`sideEffects: false`), so an application pays only for what it uses. Each module is laid out
7
+ * as sincpro_framework's are — `domain/` (ports and rules), `adapters/` (their implementations),
8
+ * `services/`, `infrastructure/`, `entrypoint/` — and says what it offers in its own `index.ts`:
8
9
  *
9
- * common/ the values of the wire, as the backend writes them: ids, a day and an instant,
10
- * `Decimal`, `Money`, a to-many as a page of its records (`EntityCollection`), and
11
- * how any value is written and read (`valuesOf`, `rebuilt`)
12
- * client/ the seam with a server: the transport and its REST and in-memory implementations,
13
- * entities with typed commands, the service's client contract
14
- * model/ model definitions: written by hand, read from a JSON Schema, kept once known
15
- * services/ the bounded context (`UseBoundedContext`), its `Feature` and `ApplicationService`,
16
- * the process they run in (`configureProcess`), what each run carries
17
- * context/ the context: one door (`Context`) over the store each platform plugs
18
- * observability/ the ports a platform plugs: a tracer and an error reporter
10
+ * common/ the values of the wire, as the backend writes them
11
+ * context/ the context's levels, where they live, what travels, the execution
12
+ * observability/ tracing, error reporting and logging: ports, console, loggers
13
+ * client/ the seam with a server: transports, entities, the client contract
14
+ * model/ model definitions
15
+ * core/ Commands, Features, ApplicationServices, BoundedContext, the process
19
16
  *
20
- * The generator is `@sincpro/client-framework/codegen`, and the in-memory tracer and reporter
17
+ * The generator is `@sincpro/client-framework/codegen`, and the in-memory observability
21
18
  * `@sincpro/client-framework/testing`: neither is part of what an application runs.
22
19
  */
23
- export { type ClientContract, entityKeyOf, kindOf, metasOf, versionsOf, } from "./client/contract.js";
24
- export { bodyFor, type CommandAnswers, type CommandBodies, type CommandRoute, type Entity, entity, type EntityRef, type EntityRoutes, type ExportFormat, exportRoute, listRoute, NEW_PARAM, pathFor, PICK_PARAM, QUERY_PARAM, SCOPE_PARAM, scopeOfRef, startingValuesOf, type SummaryFields, type SummaryPart, TRAIL_PARAM, WITH_PARAM, type WriteRoute, writeRoute, } from "./client/entity.js";
25
- export { type MemoryOptions, type MemoryTable, memoryTransport, } from "./client/memory.js";
26
- export { type RestOptions, restTransport, urlOf, type VersionDrift, } from "./client/rest.js";
27
- export { type RetryKeys, retryKeys, unanswered, } from "./client/retry.js";
28
- export { type CallOptions, type Decoder, type Download, downloadRoute, type Draft, type Method, type One, readRoute, type SendOptions, type Transport, TransportError, } from "./client/transport.js";
29
- export { collectionOf, type EntityCollection, type HeldCollection, isCollection, isPartial, relatedCriteria, type RelationKeys, totalOf, } from "./common/collection.js";
30
- export { Decimal, type DecimalLike } from "./common/decimal.js";
31
- export { isEntityId, momentOf, newEntityId } from "./common/ids.js";
32
- export { Money } from "./common/money.js";
33
- export { BYTES, type JsonValue, pack, readValues, type Rebuild, rebuilt, unpack, valuesOf, } from "./common/serialization.js";
34
- export { dayOf, installTimeZone, instantOf, isDay, timeZoneNow, wallTimeOf, } from "./common/time.js";
35
- export { CONTEXT_HEADER, type ContextCodec, plainCodec, travelling, travels, } from "./context/codec.js";
36
- export { type Context } from "./context/context.js";
37
- export { type ContextStore, type ContextValues, memoryContexts, } from "./context/store.js";
38
- export { defineMeta, field, type FieldOptions, type MetaSpec, type Words, } from "./model/meta.js";
39
- export { type MetaStore, metaStore } from "./model/metas.js";
40
- export { completeMeta, type JsonSchema, metaFromSchema, type SchemaComponents, } from "./model/schema.js";
41
- export { type Attributes, type ErrorAbout, type ErrorReporter, NO_TRACER, type Outcome, type Span, type Tracer, } from "./observability/ports.js";
42
- export { type BoundedContext, UseBoundedContext, } from "./services/bounded-context.js";
43
- export { BIND, type Bindable, bindable, bindDependency, bindTransport, type Execution, headersOf, } from "./services/execution.js";
44
- export { ApplicationService, Feature, type ServiceClasses, type ServiceMethods, type Services, } from "./services/feature.js";
45
- export { configureProcess, outcomeOf, processContext, type ProcessOptions, } from "./services/process.js";
46
- export { reportError } from "./services/report.js";
47
- export { screensTransport } from "./services/screens.js";
20
+ export * from "./client/index.js";
21
+ export * from "./common/index.js";
22
+ export * from "./context/index.js";
23
+ export * from "./core/index.js";
24
+ export * from "./model/index.js";
25
+ export * from "./observability/index.js";
package/dist/index.js CHANGED
@@ -3,45 +3,23 @@
3
3
  * service needs, on any platform.
4
4
  *
5
5
  * Context: one entry, not one per area — the package has no side effects at import
6
- * (`sideEffects: false`), so an application pays only for what it uses, and a use case, the
7
- * client it calls and the entity it reads come from the same place.
6
+ * (`sideEffects: false`), so an application pays only for what it uses. Each module is laid out
7
+ * as sincpro_framework's are — `domain/` (ports and rules), `adapters/` (their implementations),
8
+ * `services/`, `infrastructure/`, `entrypoint/` — and says what it offers in its own `index.ts`:
8
9
  *
9
- * common/ the values of the wire, as the backend writes them: ids, a day and an instant,
10
- * `Decimal`, `Money`, a to-many as a page of its records (`EntityCollection`), and
11
- * how any value is written and read (`valuesOf`, `rebuilt`)
12
- * client/ the seam with a server: the transport and its REST and in-memory implementations,
13
- * entities with typed commands, the service's client contract
14
- * model/ model definitions: written by hand, read from a JSON Schema, kept once known
15
- * services/ the bounded context (`UseBoundedContext`), its `Feature` and `ApplicationService`,
16
- * the process they run in (`configureProcess`), what each run carries
17
- * context/ the context: one door (`Context`) over the store each platform plugs
18
- * observability/ the ports a platform plugs: a tracer and an error reporter
10
+ * common/ the values of the wire, as the backend writes them
11
+ * context/ the context's levels, where they live, what travels, the execution
12
+ * observability/ tracing, error reporting and logging: ports, console, loggers
13
+ * client/ the seam with a server: transports, entities, the client contract
14
+ * model/ model definitions
15
+ * core/ Commands, Features, ApplicationServices, BoundedContext, the process
19
16
  *
20
- * The generator is `@sincpro/client-framework/codegen`, and the in-memory tracer and reporter
17
+ * The generator is `@sincpro/client-framework/codegen`, and the in-memory observability
21
18
  * `@sincpro/client-framework/testing`: neither is part of what an application runs.
22
19
  */
23
- export { entityKeyOf, kindOf, metasOf, versionsOf, } from "./client/contract.js";
24
- export { bodyFor, entity, exportRoute, listRoute, NEW_PARAM, pathFor, PICK_PARAM, QUERY_PARAM, SCOPE_PARAM, scopeOfRef, startingValuesOf, TRAIL_PARAM, WITH_PARAM, writeRoute, } from "./client/entity.js";
25
- export { memoryTransport, } from "./client/memory.js";
26
- export { restTransport, urlOf, } from "./client/rest.js";
27
- export { retryKeys, unanswered, } from "./client/retry.js";
28
- export { downloadRoute, readRoute, TransportError, } from "./client/transport.js";
29
- export { collectionOf, isCollection, isPartial, relatedCriteria, totalOf, } from "./common/collection.js";
30
- export { Decimal } from "./common/decimal.js";
31
- export { isEntityId, momentOf, newEntityId } from "./common/ids.js";
32
- export { Money } from "./common/money.js";
33
- export { BYTES, pack, readValues, rebuilt, unpack, valuesOf, } from "./common/serialization.js";
34
- export { dayOf, installTimeZone, instantOf, isDay, timeZoneNow, wallTimeOf, } from "./common/time.js";
35
- export { CONTEXT_HEADER, plainCodec, travelling, travels, } from "./context/codec.js";
36
- export {} from "./context/context.js";
37
- export { memoryContexts, } from "./context/store.js";
38
- export { defineMeta, field, } from "./model/meta.js";
39
- export { metaStore } from "./model/metas.js";
40
- export { completeMeta, metaFromSchema, } from "./model/schema.js";
41
- export { NO_TRACER, } from "./observability/ports.js";
42
- export { UseBoundedContext, } from "./services/bounded-context.js";
43
- export { BIND, bindable, bindDependency, bindTransport, headersOf, } from "./services/execution.js";
44
- export { ApplicationService, Feature, } from "./services/feature.js";
45
- export { configureProcess, outcomeOf, processContext, } from "./services/process.js";
46
- export { reportError } from "./services/report.js";
47
- export { screensTransport } from "./services/screens.js";
20
+ export * from "./client/index.js";
21
+ export * from "./common/index.js";
22
+ export * from "./context/index.js";
23
+ export * from "./core/index.js";
24
+ export * from "./model/index.js";
25
+ export * from "./observability/index.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Model definitions: written by hand (`defineMeta`), read from a JSON Schema, kept once known.
3
+ *
4
+ * domain/ `defineMeta`, `field`
5
+ * services/ `metaFromSchema`, `completeMeta`
6
+ * infrastructure/ `metaStore`: the definitions already known
7
+ */
8
+ export { defineMeta, field, type FieldOptions, type MetaSpec, type Words, } from "./domain/meta.js";
9
+ export { type MetaStore, metaStore, } from "./infrastructure/metas.js";
10
+ export { completeMeta, type JsonSchema, metaFromSchema, type SchemaComponents, } from "./services/schema.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Model definitions: written by hand (`defineMeta`), read from a JSON Schema, kept once known.
3
+ *
4
+ * domain/ `defineMeta`, `field`
5
+ * services/ `metaFromSchema`, `completeMeta`
6
+ * infrastructure/ `metaStore`: the definitions already known
7
+ */
8
+ export { defineMeta, field, } from "./domain/meta.js";
9
+ export { metaStore, } from "./infrastructure/metas.js";
10
+ export { completeMeta, metaFromSchema, } from "./services/schema.js";
@@ -3,7 +3,7 @@ import type { Meta } from "@sincpro/criteria";
3
3
  * The model definitions already known, by entity key (`"billing:invoice"`).
4
4
  *
5
5
  * Context: sincpro_framework publishes each model once, in the discovery document the
6
- * transport reads (`core/discovery`); an answer carries only its version. This keeps the
6
+ * transport reads (`client/services/discovery`); an answer carries only its version. This keeps the
7
7
  * definitions ACROSS readings, so the second list of the same entity, the record opened from it
8
8
  * and the select that points at it draw their labels before the rows arrive.
9
9
  *
@@ -3,7 +3,7 @@ import type { Meta } from "@sincpro/criteria";
3
3
  * A model definition read from a JSON Schema — what an OpenAPI document holds.
4
4
  *
5
5
  * Context: a model a READ answers is published by sincpro_framework as its contract — JSON
6
- * Schema with `x-sincpro` — and read by the transport (`metaFromContract`, `core/discovery`).
6
+ * Schema with `x-sincpro` — and read by the transport (`metaFromContract`, `client/services/discovery`).
7
7
  * What has no `x-sincpro` is a plain JSON Schema: a COMMAND's body, a value object it carries.
8
8
  *
9
9
  * So this reads such a component schema into a `Meta` — the form of a Command, a value object —
@@ -1,4 +1,4 @@
1
- import { field } from "./meta.js";
1
+ import { field } from "../domain/meta.js";
2
2
  const UNKNOWN = {
3
3
  type: "unknown",
4
4
  kind: "scalar",
@@ -0,0 +1,12 @@
1
+ import type { Logger, LogLayer, LogLevel } from "../domain/logging.js";
2
+ export type ConsoleLoggerOptions = {
3
+ mode: "development" | "production";
4
+ /** Development INFO/DEBUG threshold. WARN/ERROR are never hidden by layer/level masks. */
5
+ level?: LogLevel;
6
+ layers?: Partial<Record<LogLayer, boolean>>;
7
+ };
8
+ /**
9
+ * Built-in console engine: silent in production, configurable
10
+ * development layers, and WARN/ERROR preserved. Other sinks are filtered independently.
11
+ */
12
+ export declare function consoleLogger(options: ConsoleLoggerOptions): Logger;
@@ -0,0 +1,35 @@
1
+ const RANK = { debug: 0, info: 1, warn: 2, error: 3 };
2
+ /**
3
+ * Built-in console engine: silent in production, configurable
4
+ * development layers, and WARN/ERROR preserved. Other sinks are filtered independently.
5
+ */
6
+ export function consoleLogger(options) {
7
+ const mode = options.mode;
8
+ if (mode !== "development" && mode !== "production") {
9
+ throw new TypeError("Console logger mode must be development or production");
10
+ }
11
+ const level = options.level ?? "debug";
12
+ if (!Object.hasOwn(RANK, level)) {
13
+ throw new TypeError("Console logger level must be debug, info, warn or error");
14
+ }
15
+ const layers = { ...options.layers };
16
+ return {
17
+ log(record) {
18
+ if (mode === "production")
19
+ return;
20
+ if (record.level !== "warn" && record.level !== "error") {
21
+ if (layers[record.layer] === false || RANK[record.level] < RANK[level])
22
+ return;
23
+ }
24
+ const prefix = `${record.timestamp} [${record.layer}] ${record.level.toUpperCase()}`;
25
+ const method = record.level === "debug"
26
+ ? console.debug
27
+ : record.level === "warn"
28
+ ? console.warn
29
+ : record.level === "error"
30
+ ? console.error
31
+ : console.log;
32
+ method.call(console, prefix, record.message, record);
33
+ },
34
+ };
35
+ }
@@ -0,0 +1,41 @@
1
+ import type { ErrorAbout, ErrorReporter } from "../domain/errors.js";
2
+ import type { Logger, LogRecord } from "../domain/logging.js";
3
+ import type { Outcome, Tracer } from "../domain/tracing.js";
4
+ /**
5
+ * Observability adapters that keep what they were told, so a test reads it back.
6
+ *
7
+ * const tracer = memoryTracer();
8
+ * configureProcess({ tracer });
9
+ * await project.execute(new CommandCloseIssue("issue-1"));
10
+ * tracer.spans.map((one) => one.name); // ["project/CloseIssue", "project/Store", …]
11
+ *
12
+ * Context: a use case is proven in Node with nothing of a platform; these are the platform's
13
+ * part, held in memory. Not test doubles of this package — real implementations of its ports.
14
+ */
15
+ /** A span as the memory tracer kept it. */
16
+ export type RecordedSpan = {
17
+ name: string;
18
+ traceId: string;
19
+ spanId: string;
20
+ /** The span it was opened under, when it was. */
21
+ parentSpanId: string | undefined;
22
+ attributes: Record<string, string | number | boolean>;
23
+ /** How it ended; `undefined` while it is open. */
24
+ outcome: Outcome | undefined;
25
+ error: unknown;
26
+ };
27
+ export type MemoryTracer = Tracer & {
28
+ readonly spans: RecordedSpan[];
29
+ };
30
+ export declare function memoryTracer(): MemoryTracer;
31
+ export type MemoryReporter = ErrorReporter & {
32
+ readonly reports: {
33
+ error: unknown;
34
+ about: ErrorAbout;
35
+ }[];
36
+ };
37
+ export declare function memoryReporter(): MemoryReporter;
38
+ export type MemoryLogger = Logger & {
39
+ readonly records: LogRecord[];
40
+ };
41
+ export declare function memoryLogger(): MemoryLogger;
@@ -0,0 +1,49 @@
1
+ function hex(bytes) {
2
+ return Array.from(globalThis.crypto.getRandomValues(new Uint8Array(bytes)), (byte) => byte.toString(16).padStart(2, "0")).join("");
3
+ }
4
+ export function memoryTracer() {
5
+ const spans = [];
6
+ return {
7
+ spans,
8
+ start(name, options) {
9
+ const recorded = {
10
+ name,
11
+ traceId: options.parent?.traceId || hex(16),
12
+ spanId: hex(8),
13
+ parentSpanId: options.parent?.spanId || undefined,
14
+ attributes: { ...options.attributes },
15
+ outcome: undefined,
16
+ error: undefined,
17
+ };
18
+ spans.push(recorded);
19
+ return {
20
+ traceId: recorded.traceId,
21
+ spanId: recorded.spanId,
22
+ sampled: true,
23
+ set: (attributes) => void Object.assign(recorded.attributes, attributes),
24
+ end: (outcome, error) => {
25
+ if (recorded.outcome !== undefined)
26
+ return;
27
+ recorded.outcome = outcome;
28
+ recorded.error = error;
29
+ },
30
+ };
31
+ },
32
+ };
33
+ }
34
+ export function memoryReporter() {
35
+ const reports = [];
36
+ return { reports, report: (error, about) => void reports.push({ error, about }) };
37
+ }
38
+ export function memoryLogger() {
39
+ const records = [];
40
+ return {
41
+ records,
42
+ log(record) {
43
+ records.push({
44
+ ...record,
45
+ ...(record.attributes ? { attributes: { ...record.attributes } } : {}),
46
+ });
47
+ },
48
+ };
49
+ }
@@ -1,5 +1,5 @@
1
1
  import { type Tracer as OtelTracer } from "@opentelemetry/api";
2
- import type { Tracer } from "../observability/ports.js";
2
+ import type { Tracer } from "../domain/tracing.js";
3
3
  /**
4
4
  * The `Tracer` port over OpenTelemetry's API: every use case a span of whatever provider the
5
5
  * platform registered.
@@ -1,4 +1,4 @@
1
- import type { ErrorReporter } from "../observability/ports.js";
1
+ import type { ErrorReporter } from "../domain/errors.js";
2
2
  /**
3
3
  * What this adapter needs of a Sentry-compatible client: its `captureException`. A browser's
4
4
  * `BrowserClient`, Node's, React Native's — and GlitchTip, which speaks Sentry's protocol.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * What a platform plugs to report what nobody expected.
3
+ *
4
+ * Context: the backend reports each unexpected failure once, with the use case and the trace it
5
+ * belongs to; GlitchTip, Sentry or memory in a test receive it here. A reporter that fails is
6
+ * ignored: telling about a failure never becomes a second one.
7
+ */
8
+ /** What a report says about where the error happened. */
9
+ export type ErrorAbout = {
10
+ /** `<bounded context>.<Service>`: the use case's identity, as the backend's — or, for what
11
+ * failed outside one (`reportError`), where it happened (`view project:issue/acme-3`). */
12
+ useCase: string;
13
+ /** The trace the error belongs to — the same id the server logged, when it got that far. */
14
+ traceId: string;
15
+ /** The execution's own id. */
16
+ executionId: string;
17
+ /** What the use case ran with: the process's context, its bounded context's, its scope's. */
18
+ context: Readonly<Record<string, unknown>>;
19
+ };
20
+ /** Reports what nobody expected. One per platform; none when nothing reports. */
21
+ export interface ErrorReporter {
22
+ report(error: unknown, about: ErrorAbout): void;
23
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * What a platform plugs to report what nobody expected.
3
+ *
4
+ * Context: the backend reports each unexpected failure once, with the use case and the trace it
5
+ * belongs to; GlitchTip, Sentry or memory in a test receive it here. A reporter that fails is
6
+ * ignored: telling about a failure never becomes a second one.
7
+ */
8
+ export {};
@@ -0,0 +1,42 @@
1
+ import type { Attributes, Outcome } from "./tracing.js";
2
+ /**
3
+ * What a platform plugs to receive structured logs: a sink (`Logger`) that owns filtering and
4
+ * delivery — the console by default, Loki, a phone's queue, memory in a test.
5
+ *
6
+ * Context: a line says which layer wrote it, the way sincpro_framework's architecture names
7
+ * them: the application layer (a bounded context and its ApplicationServices and Features,
8
+ * through `project.logger` and `this.logger`), its adapters (`loggerAdapter`) and the
9
+ * infrastructure around them (`logger`). A line written inside a use case carries that
10
+ * execution's identity; nothing writes a DTO, a response, an error body or the session's
11
+ * context on its own.
12
+ */
13
+ export type LogLevel = "debug" | "info" | "warn" | "error";
14
+ /** The layer a line comes from. */
15
+ export type LogLayer = "infrastructure" | "adapter" | "bounded_context" | "application_service" | "feature";
16
+ /** Structured logging never implicitly includes a DTO, response, error body or session context. */
17
+ export type LogRecord = {
18
+ readonly timestamp: string;
19
+ readonly level: LogLevel;
20
+ readonly layer: LogLayer;
21
+ readonly message: string;
22
+ readonly attributes?: Attributes;
23
+ readonly event?: "execution.started" | "execution.finished" | "error.handled";
24
+ readonly boundedContext?: string;
25
+ readonly useCase?: string;
26
+ readonly executionId?: string;
27
+ readonly traceId?: string;
28
+ readonly spanId?: string;
29
+ readonly outcome?: Outcome;
30
+ readonly durationMs?: number;
31
+ };
32
+ /** A sink owns filtering and delivery. Logging is best-effort and never awaited by business work. */
33
+ export interface Logger {
34
+ log(record: LogRecord): void | Promise<void>;
35
+ }
36
+ /** How code writes a line: the logger of a layer, of a bounded context, or of a run. */
37
+ export interface LayerLogger {
38
+ debug(message: string, attributes?: Attributes): void;
39
+ info(message: string, attributes?: Attributes): void;
40
+ warn(message: string, attributes?: Attributes): void;
41
+ error(message: string, attributes?: Attributes): void;
42
+ }
@@ -0,0 +1 @@
1
+ export {};