@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,49 @@
1
+ /**
2
+ * What a platform plugs for execution traces: one span per use case.
3
+ *
4
+ * Context: sincpro_framework opens one span per use case; a client has to do the same, and
5
+ * continue the backend's trace. The vendor — OpenTelemetry in a browser, a queue on a phone,
6
+ * memory in a test — is the platform's, so this package defines the contract and calls it, and
7
+ * bundles none. Two implementations at least (a platform's and a test's) are why it is a port.
8
+ *
9
+ * Nothing here may break a use case: a tracer that fails is ignored, and the use case answers as
10
+ * it would have.
11
+ */
12
+ /** What is said about a span: plain values only, the way OpenTelemetry takes them. */
13
+ export type Attributes = Readonly<Record<string, string | number | boolean>>;
14
+ /**
15
+ * How a use case ended.
16
+ *
17
+ * - `ok`: it answered.
18
+ * - `refused`: expected — the server said no and explained why (a validation, a conflict, a
19
+ * permission), or the bounded context declared the error expected (`expectErrors`). Never
20
+ * reported.
21
+ * - `aborted`: whoever asked stopped waiting (a screen that closed). Never reported.
22
+ * - `failed`: anything else — a bug, a server that broke, a server nobody could reach. Reported.
23
+ */
24
+ export type Outcome = "ok" | "refused" | "aborted" | "failed";
25
+ /** One span: a use case, open while it runs. */
26
+ export interface Span {
27
+ /** 32 hex — the id the server continues and a person quotes as a support code. Empty when
28
+ * nothing traces. */
29
+ readonly traceId: string;
30
+ /** 16 hex. Empty when nothing traces. */
31
+ readonly spanId: string;
32
+ /** Whether this trace is kept: the server follows the same decision. */
33
+ readonly sampled: boolean;
34
+ /** Vendor state that travels beside the trace (`tracestate`), when there is any. */
35
+ readonly state?: string;
36
+ /** Says more about the use case while it runs. */
37
+ set(attributes: Attributes): void;
38
+ /** Closes it, once. `error` is what it failed or was refused with. */
39
+ end(outcome: Outcome, error?: unknown): void;
40
+ }
41
+ /** Opens spans. One per platform; `NO_TRACER` when nothing traces. */
42
+ export interface Tracer {
43
+ start(name: string, options: {
44
+ parent?: Span;
45
+ attributes?: Attributes;
46
+ }): Span;
47
+ }
48
+ /** Traces nothing: what a use case runs with until a platform plugs a tracer. */
49
+ export declare const NO_TRACER: Tracer;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * What a platform plugs for execution traces: one span per use case.
3
+ *
4
+ * Context: sincpro_framework opens one span per use case; a client has to do the same, and
5
+ * continue the backend's trace. The vendor — OpenTelemetry in a browser, a queue on a phone,
6
+ * memory in a test — is the platform's, so this package defines the contract and calls it, and
7
+ * bundles none. Two implementations at least (a platform's and a test's) are why it is a port.
8
+ *
9
+ * Nothing here may break a use case: a tracer that fails is ignored, and the use case answers as
10
+ * it would have.
11
+ */
12
+ const EMPTY_SPAN = {
13
+ traceId: "",
14
+ spanId: "",
15
+ sampled: false,
16
+ set: () => undefined,
17
+ end: () => undefined,
18
+ };
19
+ /** Traces nothing: what a use case runs with until a platform plugs a tracer. */
20
+ export const NO_TRACER = { start: () => EMPTY_SPAN };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Observability: the ports a platform plugs (`Tracer`, `ErrorReporter`, `Logger`), the console
3
+ * engine logs go to by default, and how code writes and reports.
4
+ *
5
+ * domain/ the ports: tracing, errors, logging
6
+ * adapters/ console (default), memory (`./testing`), OpenTelemetry and Sentry (optional
7
+ * entries `./opentelemetry`, `./sentry`)
8
+ * infrastructure/ the ports this process runs with (`configureProcess` plugs them)
9
+ * services/ the loggers, `reportError`, the default `outcomeOf`
10
+ */
11
+ export type { ErrorAbout, ErrorReporter, } from "./domain/errors.js";
12
+ export type { LayerLogger, Logger, LogLayer, LogLevel, LogRecord, } from "./domain/logging.js";
13
+ export { type Attributes, NO_TRACER, type Outcome, type Span, type Tracer, } from "./domain/tracing.js";
14
+ export { combineLoggers, logger, loggerAdapter, } from "./services/logging.js";
15
+ export { outcomeOf } from "./services/outcome.js";
16
+ export { reportError } from "./services/reporting.js";
@@ -0,0 +1,4 @@
1
+ export { NO_TRACER, } from "./domain/tracing.js";
2
+ export { combineLoggers, logger, loggerAdapter, } from "./services/logging.js";
3
+ export { outcomeOf } from "./services/outcome.js";
4
+ export { reportError } from "./services/reporting.js";
@@ -0,0 +1,21 @@
1
+ import type { ErrorReporter } from "../domain/errors.js";
2
+ import type { Logger } from "../domain/logging.js";
3
+ import { type Outcome, type Tracer } from "../domain/tracing.js";
4
+ /**
5
+ * The observability this process runs with: who traces, who reports, where logs go and how an
6
+ * error ended a use case — the ports `configureProcess` plugged, read on every call.
7
+ *
8
+ * Context: sincpro_framework keeps the same state in its `observability_registry`, outside the
9
+ * buses, so a test resets everything with one call. Until a process configures, nothing traces,
10
+ * nothing reports, and logs go to the console in development mode.
11
+ */
12
+ export type ObservabilityPorts = {
13
+ readonly tracer: Tracer;
14
+ readonly reporter: ErrorReporter | undefined;
15
+ readonly logger: Logger | undefined;
16
+ readonly outcomeOf: (error: unknown) => Outcome;
17
+ };
18
+ /** What the process observes with now. */
19
+ export declare function observabilityNow(): ObservabilityPorts;
20
+ /** @internal — `configureProcess` and `configureLogger` are the way in. */
21
+ export declare function replaceObservability(ports: Partial<ObservabilityPorts>): void;
@@ -0,0 +1,18 @@
1
+ import { consoleLogger } from "../adapters/console.js";
2
+ import { NO_TRACER, } from "../domain/tracing.js";
3
+ import { outcomeOf } from "../services/outcome.js";
4
+ const DEFAULTS = {
5
+ tracer: NO_TRACER,
6
+ reporter: undefined,
7
+ logger: consoleLogger({ mode: "development" }),
8
+ outcomeOf,
9
+ };
10
+ let current = DEFAULTS;
11
+ /** What the process observes with now. */
12
+ export function observabilityNow() {
13
+ return current;
14
+ }
15
+ /** @internal — `configureProcess` and `configureLogger` are the way in. */
16
+ export function replaceObservability(ports) {
17
+ current = { ...current, ...ports };
18
+ }
@@ -0,0 +1,27 @@
1
+ import { type Execution } from "../../context/domain/execution.js";
2
+ import type { LayerLogger, Logger, LogLayer } from "../domain/logging.js";
3
+ import type { Outcome } from "../domain/tracing.js";
4
+ /** The infrastructure's logger — the global one: the code that starts and holds the application. */
5
+ export declare const logger: LayerLogger;
6
+ /** The adapters' logger: what talks to a printer, a database, another system. */
7
+ export declare const loggerAdapter: LayerLogger;
8
+ /** @internal — a bounded context's own logger (`project.logger`). */
9
+ export declare function boundedContextLogger(name: string): LayerLogger;
10
+ /** @internal — what a Feature or ApplicationService finds as `this.logger` while it runs. */
11
+ export declare function executionLogger(layer: "feature" | "application_service", execution: Execution): LayerLogger;
12
+ /** Each destination keeps its own policy; one failing sink does not hold back the others. */
13
+ export declare function combineLoggers(...loggers: readonly Logger[]): Logger;
14
+ /** @internal — lifecycle records contain correlation and outcome, never business payloads. */
15
+ export declare function logExecution(layer: "feature" | "application_service", execution: Execution, completed?: {
16
+ outcome: Outcome;
17
+ durationMs: number;
18
+ }): void;
19
+ /**
20
+ * @internal — an error a handler of the bounded context answered instead of raising: said once,
21
+ * with where it was handled, never with the error itself.
22
+ */
23
+ export declare function logHandled(layer: LogLayer, correlation: {
24
+ execution: Execution;
25
+ } | {
26
+ boundedContext: string;
27
+ }): void;
@@ -0,0 +1,105 @@
1
+ import { BIND } from "../../context/domain/execution.js";
2
+ import { observabilityNow } from "../infrastructure/registry.js";
3
+ const processSink = () => observabilityNow().logger;
4
+ /** Sink failures cannot replace a business outcome or cause unhandled async rejections. */
5
+ function deliver(logger, record) {
6
+ if (!logger)
7
+ return;
8
+ try {
9
+ const pending = logger.log(record);
10
+ if (pending)
11
+ void Promise.resolve(pending).catch(() => { });
12
+ }
13
+ catch {
14
+ // Logging adapters are best-effort, as are tracing and error-reporting adapters.
15
+ }
16
+ }
17
+ function correlationOf(execution) {
18
+ return {
19
+ boundedContext: execution.boundedContext,
20
+ useCase: execution.useCase,
21
+ executionId: execution.id,
22
+ ...(execution.span.traceId ? { traceId: execution.span.traceId } : {}),
23
+ ...(execution.span.spanId ? { spanId: execution.span.spanId } : {}),
24
+ };
25
+ }
26
+ function layerLogger(layer, sink, correlation = {}) {
27
+ const write = (level, message, attributes) => {
28
+ deliver(sink(), {
29
+ timestamp: new Date().toISOString(),
30
+ level,
31
+ layer,
32
+ message,
33
+ ...(attributes ? { attributes: { ...attributes } } : {}),
34
+ ...correlation,
35
+ });
36
+ };
37
+ const logger = {
38
+ debug: (message, attributes) => write("debug", message, attributes),
39
+ info: (message, attributes) => write("info", message, attributes),
40
+ warn: (message, attributes) => write("warn", message, attributes),
41
+ error: (message, attributes) => write("error", message, attributes),
42
+ };
43
+ Object.defineProperty(logger, BIND, {
44
+ value: (run) => layerLogger(layer, sink, correlationOf(run)),
45
+ enumerable: false,
46
+ });
47
+ return logger;
48
+ }
49
+ /** The infrastructure's logger — the global one: the code that starts and holds the application. */
50
+ export const logger = layerLogger("infrastructure", processSink);
51
+ /** The adapters' logger: what talks to a printer, a database, another system. */
52
+ export const loggerAdapter = layerLogger("adapter", processSink);
53
+ /** @internal — a bounded context's own logger (`project.logger`). */
54
+ export function boundedContextLogger(name) {
55
+ return layerLogger("bounded_context", processSink, { boundedContext: name });
56
+ }
57
+ /** @internal — what a Feature or ApplicationService finds as `this.logger` while it runs. */
58
+ export function executionLogger(layer, execution) {
59
+ return layerLogger(layer, processSink, correlationOf(execution));
60
+ }
61
+ /** Each destination keeps its own policy; one failing sink does not hold back the others. */
62
+ export function combineLoggers(...loggers) {
63
+ return {
64
+ log: (record) => {
65
+ for (const logger of loggers)
66
+ deliver(logger, record);
67
+ },
68
+ };
69
+ }
70
+ /** @internal — lifecycle records contain correlation and outcome, never business payloads. */
71
+ export function logExecution(layer, execution, completed) {
72
+ const outcome = completed?.outcome;
73
+ const level = outcome === "failed"
74
+ ? "error"
75
+ : outcome === "refused"
76
+ ? "warn"
77
+ : completed
78
+ ? "info"
79
+ : "debug";
80
+ deliver(processSink(), {
81
+ timestamp: new Date().toISOString(),
82
+ level,
83
+ layer,
84
+ event: completed ? "execution.finished" : "execution.started",
85
+ message: completed ? `Execution ${completed.outcome}` : "Execution started",
86
+ ...correlationOf(execution),
87
+ ...(completed ? completed : {}),
88
+ });
89
+ }
90
+ /**
91
+ * @internal — an error a handler of the bounded context answered instead of raising: said once,
92
+ * with where it was handled, never with the error itself.
93
+ */
94
+ export function logHandled(layer, correlation) {
95
+ deliver(processSink(), {
96
+ timestamp: new Date().toISOString(),
97
+ level: "info",
98
+ layer,
99
+ event: "error.handled",
100
+ message: "Error handled",
101
+ ...("execution" in correlation
102
+ ? correlationOf(correlation.execution)
103
+ : { boundedContext: correlation.boundedContext }),
104
+ });
105
+ }
@@ -0,0 +1,12 @@
1
+ import type { Outcome } from "../domain/tracing.js";
2
+ /**
3
+ * How an error ended a use case, by default.
4
+ *
5
+ * Context: the backend reports what nobody expected and only that — a refusal it explained is
6
+ * the business answering, not a fault. Here the same: a `TransportError` the server answered
7
+ * with a 4xx is `refused`; a request the caller stopped waiting for is `aborted`; everything
8
+ * else — a bug, a 5xx, a server nobody could reach — is `failed`, and reported. A bounded
9
+ * context adds its own expected errors (`expectErrors`); a process replaces this whole
10
+ * (`configureProcess({ outcomeOf })`).
11
+ */
12
+ export declare function outcomeOf(error: unknown): Outcome;
@@ -0,0 +1,20 @@
1
+ import { TransportError } from "../../client/domain/transport.js";
2
+ /**
3
+ * How an error ended a use case, by default.
4
+ *
5
+ * Context: the backend reports what nobody expected and only that — a refusal it explained is
6
+ * the business answering, not a fault. Here the same: a `TransportError` the server answered
7
+ * with a 4xx is `refused`; a request the caller stopped waiting for is `aborted`; everything
8
+ * else — a bug, a 5xx, a server nobody could reach — is `failed`, and reported. A bounded
9
+ * context adds its own expected errors (`expectErrors`); a process replaces this whole
10
+ * (`configureProcess({ outcomeOf })`).
11
+ */
12
+ export function outcomeOf(error) {
13
+ if (error instanceof TransportError && error.status !== null)
14
+ return error.status >= 400 && error.status < 500 ? "refused" : "failed";
15
+ if (error !== null &&
16
+ typeof error === "object" &&
17
+ error.name === "AbortError")
18
+ return "aborted";
19
+ return "failed";
20
+ }
@@ -1,5 +1,6 @@
1
- import { newEntityId } from "../common/ids.js";
2
- import { processContext, processNow } from "./process.js";
1
+ import { newEntityId } from "../../common/ids.js";
2
+ import { processContext } from "../../context/infrastructure/process.js";
3
+ import { observabilityNow } from "../infrastructure/registry.js";
3
4
  /**
4
5
  * Whether this is the first time this error is reported. A Feature that fails inside an
5
6
  * ApplicationService throws through it, and a screen that draws the failure may break on it:
@@ -28,7 +29,7 @@ export function firstReport(error) {
28
29
  * ignored: telling about a failure never becomes a second one.
29
30
  */
30
31
  export function reportError(error, where) {
31
- const settings = processNow();
32
+ const settings = observabilityNow();
32
33
  try {
33
34
  // What the server explained, or what the caller stopped waiting for, is not a fault here either.
34
35
  if (settings.outcomeOf(error) !== "failed")
@@ -1,35 +1,5 @@
1
- import type { ErrorAbout, ErrorReporter, Outcome, Tracer } from "../observability/ports.js";
2
1
  /**
3
- * A tracer and a reporter that keep what they were told, so a test reads it back.
4
- *
5
- * const tracer = memoryTracer();
6
- * configureProcess({ tracer });
7
- * await project.CloseIssue.execute(input);
8
- * tracer.spans.map((one) => one.name); // ["project/CloseIssue", "project/Issues.close", …]
9
- *
10
- * Context: a use case is proven in Node with nothing of a platform; these are the platform's
11
- * part, held in memory. Not test doubles of this package — real implementations of its ports.
2
+ * `@sincpro/client-framework/testing`: the observability ports in memory, so a use case is
3
+ * proven in Node with nothing of a platform. Not part of what an application runs.
12
4
  */
13
- /** A span as the memory tracer kept it. */
14
- export type RecordedSpan = {
15
- name: string;
16
- traceId: string;
17
- spanId: string;
18
- /** The span it was opened under, when it was. */
19
- parentSpanId: string | undefined;
20
- attributes: Record<string, string | number | boolean>;
21
- /** How it ended; `undefined` while it is open. */
22
- outcome: Outcome | undefined;
23
- error: unknown;
24
- };
25
- export type MemoryTracer = Tracer & {
26
- readonly spans: RecordedSpan[];
27
- };
28
- export declare function memoryTracer(): MemoryTracer;
29
- export type MemoryReporter = ErrorReporter & {
30
- readonly reports: {
31
- error: unknown;
32
- about: ErrorAbout;
33
- }[];
34
- };
35
- export declare function memoryReporter(): MemoryReporter;
5
+ export { type MemoryLogger, memoryLogger, type MemoryReporter, memoryReporter, type MemoryTracer, memoryTracer, type RecordedSpan, } from "../observability/adapters/memory.js";
@@ -1,37 +1,5 @@
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
- }
1
+ /**
2
+ * `@sincpro/client-framework/testing`: the observability ports in memory, so a use case is
3
+ * proven in Node with nothing of a platform. Not part of what an application runs.
4
+ */
5
+ export { memoryLogger, memoryReporter, memoryTracer, } from "../observability/adapters/memory.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sincpro/client-framework",
3
- "version": "0.6.0",
3
+ "version": "1.0.0",
4
4
  "description": "The client side of sincpro_framework, for every platform: Feature and ApplicationService traced and reported by default, applications made of domains, and the typed client generated from a service's contract. No React, no DOM.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -10,6 +10,10 @@
10
10
  "types": "./dist/index.d.ts",
11
11
  "default": "./dist/index.js"
12
12
  },
13
+ "./core": {
14
+ "types": "./dist/core/index.d.ts",
15
+ "default": "./dist/core/index.js"
16
+ },
13
17
  "./codegen": {
14
18
  "types": "./dist/codegen/index.d.ts",
15
19
  "default": "./dist/codegen/index.js"
@@ -18,21 +22,25 @@
18
22
  "types": "./dist/testing/index.d.ts",
19
23
  "default": "./dist/testing/index.js"
20
24
  },
25
+ "./console": {
26
+ "types": "./dist/observability/adapters/console.d.ts",
27
+ "default": "./dist/observability/adapters/console.js"
28
+ },
21
29
  "./opentelemetry": {
22
- "types": "./dist/adapters/opentelemetry.d.ts",
23
- "default": "./dist/adapters/opentelemetry.js"
30
+ "types": "./dist/observability/adapters/opentelemetry.d.ts",
31
+ "default": "./dist/observability/adapters/opentelemetry.js"
24
32
  },
25
33
  "./sentry": {
26
- "types": "./dist/adapters/sentry.d.ts",
27
- "default": "./dist/adapters/sentry.js"
34
+ "types": "./dist/observability/adapters/sentry.d.ts",
35
+ "default": "./dist/observability/adapters/sentry.js"
28
36
  },
29
37
  "./parquet": {
30
- "types": "./dist/adapters/parquet.d.ts",
31
- "default": "./dist/adapters/parquet.js"
38
+ "types": "./dist/client/adapters/parquet.d.ts",
39
+ "default": "./dist/client/adapters/parquet.js"
32
40
  }
33
41
  },
34
42
  "bin": {
35
- "sincpro-client": "./dist/codegen/cli.js"
43
+ "sincpro-client": "./dist/codegen/entrypoint/cli.js"
36
44
  },
37
45
  "files": [
38
46
  "dist",
@@ -1,15 +0,0 @@
1
- import type { Metamodel } from "./metamodel.js";
2
- /**
3
- * The TypeScript of every DTO of `metamodel`, as the text of one file.
4
- *
5
- * 1. The models, by name: an entity's contract, a related model by its own name.
6
- * 2. Each operation, by its local name (its identity without `<context>.`): its input as
7
- * `<Name>` and its answer by the backend's own name for it (`answerOf`: `ResponseX`), with
8
- * `<Name>Answer` kept as a deprecated alias — or as `<Name>Answer` when it has no name.
9
- * 3. The shared definitions those reach — and the ones those reach, until none is new —, but
10
- * never one a package of the stack already types (`Criteria`).
11
- * Final: a header that says where it came from and not to edit it, the imports, then models,
12
- * definitions and operations, each sorted by name; two of them with one TypeScript name are
13
- * refused, naming both.
14
- */
15
- export declare function domainFileOf(metamodel: Metamodel): string;
@@ -1,65 +0,0 @@
1
- /**
2
- * What a platform plugs so every use case is traced and every unexpected error reported.
3
- *
4
- * Context: sincpro_framework opens one span per use case and reports what nobody expected; a
5
- * client has to do the same, and continue the backend's trace. The vendor — OpenTelemetry in a
6
- * browser, a queue on a phone, GlitchTip, memory in a test — is the platform's, so this package
7
- * defines the contract and calls it, and bundles none. Two implementations at least (a
8
- * platform's and a test's) are why these are ports.
9
- *
10
- * Nothing here may break a use case: a tracer or a reporter that throws is ignored, and the use
11
- * case answers as it would have.
12
- */
13
- /** What is said about a span: plain values only, the way OpenTelemetry takes them. */
14
- export type Attributes = Readonly<Record<string, string | number | boolean>>;
15
- /**
16
- * How a use case ended.
17
- *
18
- * - `ok`: it answered.
19
- * - `refused`: the server said no and explained why — a validation, a conflict, a permission.
20
- * Expected; never reported.
21
- * - `aborted`: whoever asked stopped waiting (a screen that closed). Never reported.
22
- * - `failed`: anything else — a bug, a server that broke, a server nobody could reach. Reported.
23
- */
24
- export type Outcome = "ok" | "refused" | "aborted" | "failed";
25
- /** One span: a use case, open while it runs. */
26
- export interface Span {
27
- /** 32 hex — the id the server continues and a person quotes as a support code. Empty when
28
- * nothing traces. */
29
- readonly traceId: string;
30
- /** 16 hex. Empty when nothing traces. */
31
- readonly spanId: string;
32
- /** Whether this trace is kept: the server follows the same decision. */
33
- readonly sampled: boolean;
34
- /** Vendor state that travels beside the trace (`tracestate`), when there is any. */
35
- readonly state?: string;
36
- /** Says more about the use case while it runs. */
37
- set(attributes: Attributes): void;
38
- /** Closes it, once. `error` is what it failed or was refused with. */
39
- end(outcome: Outcome, error?: unknown): void;
40
- }
41
- /** Opens spans. One per platform; `NO_TRACER` when nothing traces. */
42
- export interface Tracer {
43
- start(name: string, options: {
44
- parent?: Span;
45
- attributes?: Attributes;
46
- }): Span;
47
- }
48
- /** What a report says about where the error happened. */
49
- export type ErrorAbout = {
50
- /** `<bounded context>.<Service>[.<method>]`: the use case's identity, as the backend's — or,
51
- * for what failed outside one (`reportError`), where it happened (`view project:issue/acme-3`). */
52
- useCase: string;
53
- /** The trace the error belongs to — the same id the server logged, when it got that far. */
54
- traceId: string;
55
- /** The execution's own id. */
56
- executionId: string;
57
- /** What the use case ran with: the process's context, its bounded context's, its scope's. */
58
- context: Readonly<Record<string, unknown>>;
59
- };
60
- /** Reports what nobody expected. One per platform; none when nothing reports. */
61
- export interface ErrorReporter {
62
- report(error: unknown, about: ErrorAbout): void;
63
- }
64
- /** Traces nothing: what a use case runs with until a platform plugs a tracer. */
65
- export declare const NO_TRACER: Tracer;
@@ -1,21 +0,0 @@
1
- /**
2
- * What a platform plugs so every use case is traced and every unexpected error reported.
3
- *
4
- * Context: sincpro_framework opens one span per use case and reports what nobody expected; a
5
- * client has to do the same, and continue the backend's trace. The vendor — OpenTelemetry in a
6
- * browser, a queue on a phone, GlitchTip, memory in a test — is the platform's, so this package
7
- * defines the contract and calls it, and bundles none. Two implementations at least (a
8
- * platform's and a test's) are why these are ports.
9
- *
10
- * Nothing here may break a use case: a tracer or a reporter that throws is ignored, and the use
11
- * case answers as it would have.
12
- */
13
- const EMPTY_SPAN = {
14
- traceId: "",
15
- spanId: "",
16
- sampled: false,
17
- set: () => undefined,
18
- end: () => undefined,
19
- };
20
- /** Traces nothing: what a use case runs with until a platform plugs a tracer. */
21
- export const NO_TRACER = { start: () => EMPTY_SPAN };
@@ -1,79 +0,0 @@
1
- import { type Context } from "../context/context.js";
2
- import type { ContextValues } from "../context/store.js";
3
- import { type ServiceClasses, type Services } from "./feature.js";
4
- /**
5
- * A bounded context: its dependencies, its services, and the one object everything else uses
6
- * them through — set up the way sincpro_framework sets one up.
7
- *
8
- * // domains/project/infrastructure/dependencies.ts ← what the services can count on
9
- * export interface ProjectDependencies {
10
- * client: ProjectClient;
11
- * session: Session;
12
- * }
13
- * export function registerDependencies(bc: UseBoundedContext<ProjectDependencies>) {
14
- * bc.addDependency("client", clientOf(transport));
15
- * bc.addDependency("session", session);
16
- * return bc;
17
- * }
18
- *
19
- * // domains/project/infrastructure/bounded_context.ts
20
- * export class Feature extends BaseFeature<ProjectDependencies> {}
21
- * export class ApplicationService extends BaseApplicationService<ProjectDependencies, ProjectFeatures> {}
22
- * export function configProjectBoundedContext(name: string) {
23
- * return registerDependencies(new UseBoundedContext<ProjectDependencies>(name));
24
- * }
25
- *
26
- * // domains/project/index.ts
27
- * export const project = configProjectBoundedContext("project").register({
28
- * features, // { Issues, Notices }
29
- * applicationServices, // { CloseAndNotify }
30
- * });
31
- *
32
- * // anywhere — a screen, another bounded context's adapter, a test
33
- * await project.Issues.close(input); // span "project/Issues.close"
34
- * await project.CloseAndNotify.execute(input); // span "project/CloseAndNotify", its Features under it
35
- *
36
- * Context: sincpro_framework's `UseFramework` (being renamed to bounded context) with
37
- * `add_dependency`, and `project(Command(...))` written as a property: a service is reached by
38
- * the name it is registered under, so its span is named by that name — a production build renames
39
- * classes, never the keys of an object — and the type of `project` is the services it holds.
40
- *
41
- * `project` is light and never frozen. It holds references: the classes, the dependencies, and
42
- * one instance of each service, made the first time it is asked for and shared by everything
43
- * derived from it. Each call makes a small view of that instance — a few pointers — that carries
44
- * the call's trace. A screen or a test that needs its own variant derives one, with everything
45
- * else shared:
46
- *
47
- * project.with({ client: fake }) other dependencies — a test, a demo
48
- * project.scoped({ company_id: 2 }) more context for what runs through it — a window
49
- *
50
- * A service is a singleton of its bounded context: what a method writes on `this` stays for the
51
- * next call and for the call running beside it, as in sincpro_framework. What belongs to one
52
- * request goes in its arguments or its context, never on `this`.
53
- */
54
- export type BoundedContext<D extends object, C extends object, F extends ServiceClasses, A extends ServiceClasses> = Services<F, D> & Services<A, D> & {
55
- readonly name: string;
56
- /** This bounded context's level of the context: under the process's, over a scope's. */
57
- readonly context: Context<C>;
58
- /** The same bounded context with other dependencies — the rest, and the services, shared. */
59
- with(dependencies: Partial<D>): BoundedContext<D, C, F, A>;
60
- /** The same bounded context with more context for what runs through it. */
61
- scoped(context: Partial<C>): BoundedContext<D, C, F, A>;
62
- };
63
- export declare class UseBoundedContext<D extends object, C extends object = ContextValues> {
64
- #private;
65
- readonly name: string;
66
- /** This bounded context's level of the context, saved in the process's store. */
67
- readonly context: Context<C>;
68
- constructor(name: string);
69
- /** `value` is what every service of this bounded context finds at `this.<name>`. */
70
- addDependency<K extends keyof D & string>(name: K, value: D[K]): this;
71
- /**
72
- * The object the bounded context is used through: each service under its name. A dependency
73
- * added after this is seen too — the services read them on every call.
74
- */
75
- register<F extends ServiceClasses, A extends ServiceClasses = Record<never, never>>(services: {
76
- features: F;
77
- applicationServices?: A;
78
- }): BoundedContext<D, C, F, A>;
79
- }