@sincpro/client-framework 0.4.0 → 0.6.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 (46) hide show
  1. package/README.md +59 -9
  2. package/dist/adapters/parquet.d.ts +50 -0
  3. package/dist/adapters/parquet.js +151 -0
  4. package/dist/client/entity.d.ts +16 -1
  5. package/dist/client/entity.js +16 -1
  6. package/dist/client/memory.js +10 -6
  7. package/dist/client/rest.d.ts +7 -1
  8. package/dist/client/rest.js +46 -1
  9. package/dist/client/transport.d.ts +37 -1
  10. package/dist/client/transport.js +6 -0
  11. package/dist/codegen/check.d.ts +64 -0
  12. package/dist/codegen/check.js +195 -0
  13. package/dist/codegen/cli.js +73 -6
  14. package/dist/codegen/client.js +30 -9
  15. package/dist/codegen/domain.d.ts +2 -1
  16. package/dist/codegen/domain.js +63 -10
  17. package/dist/codegen/entities.d.ts +2 -1
  18. package/dist/codegen/entities.js +28 -5
  19. package/dist/codegen/index.d.ts +2 -0
  20. package/dist/codegen/index.js +2 -0
  21. package/dist/codegen/metamodel.d.ts +4 -0
  22. package/dist/codegen/names.d.ts +12 -0
  23. package/dist/codegen/names.js +58 -0
  24. package/dist/codegen/scaffold.d.ts +24 -0
  25. package/dist/codegen/scaffold.js +123 -0
  26. package/dist/common/collection.d.ts +110 -0
  27. package/dist/common/collection.js +169 -0
  28. package/dist/common/decimal.js +8 -0
  29. package/dist/common/serialization.d.ts +5 -1
  30. package/dist/common/serialization.js +17 -4
  31. package/dist/common/time.d.ts +2 -1
  32. package/dist/common/time.js +9 -2
  33. package/dist/context/codec.d.ts +2 -0
  34. package/dist/context/codec.js +16 -0
  35. package/dist/index.d.ts +8 -4
  36. package/dist/index.js +8 -4
  37. package/dist/model/meta.d.ts +1 -0
  38. package/dist/observability/ports.d.ts +2 -1
  39. package/dist/services/bounded-context.js +4 -15
  40. package/dist/services/execution.js +5 -2
  41. package/dist/services/feature.d.ts +4 -1
  42. package/dist/services/report.d.ts +15 -0
  43. package/dist/services/report.js +53 -0
  44. package/dist/services/screens.d.ts +2 -0
  45. package/dist/services/screens.js +76 -0
  46. package/package.json +16 -3
@@ -57,6 +57,9 @@ const pad = (n) => String(n).padStart(2, "0");
57
57
  /** `"2026-03-12T14:30:00Z"` → `"2026-03-12T10:30"` in `America/La_Paz`: the wall clock there,
58
58
  * as a date-and-time control reads it. `null` when it is not an instant. */
59
59
  export function wallTimeOf(instant, timeZone) {
60
+ // A day alone is not an instant: `Date` would read it as midnight in UTC, the day before here.
61
+ if (!/T\d{2}:\d{2}/.test(instant))
62
+ return null;
60
63
  const at = Date.parse(instant);
61
64
  if (Number.isNaN(at))
62
65
  return null;
@@ -82,9 +85,13 @@ export function instantOf(wall, timeZone) {
82
85
  return new Date(second).toISOString();
83
86
  }
84
87
  const DAY = /^\d{4}-\d{2}-\d{2}$/;
85
- /** A day of the calendar as the server writes it: `"2026-03-12"` — never an instant. */
88
+ /** A day of the calendar as the server writes it: `"2026-03-12"` — never an instant, never a day
89
+ * the calendar does not have (`"2026-02-30"`, which `Date` would roll into March). */
86
90
  export function isDay(text) {
87
- return DAY.test(text) && !Number.isNaN(Date.parse(`${text}T00:00:00Z`));
91
+ if (!DAY.test(text))
92
+ return false;
93
+ const at = Date.parse(`${text}T00:00:00Z`);
94
+ return !Number.isNaN(at) && new Date(at).toISOString().slice(0, 10) === text;
88
95
  }
89
96
  /** The day it is at `instant` on the wall clock of `timeZone`: what «today» means for a company.
90
97
  * `null` when it is not an instant. */
@@ -13,6 +13,8 @@ import type { ContextValues } from "./store.js";
13
13
  * context stays where it is. Reading never fails a context whole: text that is not a JSON object
14
14
  * reads as an empty one. A project with a schema of its own writes its own codec over this port.
15
15
  */
16
+ /** The header the context travels in: the one sincpro_framework's entrances read. */
17
+ export declare const CONTEXT_HEADER = "sincpro-context";
16
18
  export interface ContextCodec {
17
19
  dumps(values: ContextValues): string;
18
20
  loads(text: string): ContextValues;
@@ -1,3 +1,19 @@
1
+ /**
2
+ * How a context is written to text and read back: what a `ContextStore` that persists uses
3
+ * (`sessionStorage`, MMKV), and what travels to the server in `sincpro-context`.
4
+ *
5
+ * plainCodec.dumps({ tenant_id: "acme", company_id: 2, session: socket })
6
+ * → '{"tenant_id":"acme","company_id":2}'
7
+ * plainCodec.loads('{"tenant_id":"acme"}') → { tenant_id: "acme" }
8
+ *
9
+ * Context: sincpro_framework's `ContextCodec`, and its `PlainCodec` — the one for anything outside
10
+ * the codebase: another language, a browser, a phone. Only the travelling values go — text,
11
+ * numbers, booleans, `null`, and lists of them; an object, a function or a connection held in the
12
+ * context stays where it is. Reading never fails a context whole: text that is not a JSON object
13
+ * reads as an empty one. A project with a schema of its own writes its own codec over this port.
14
+ */
15
+ /** The header the context travels in: the one sincpro_framework's entrances read. */
16
+ export const CONTEXT_HEADER = "sincpro-context";
1
17
  function isPlain(value) {
2
18
  return (value === null ||
3
19
  typeof value === "string" ||
package/dist/index.d.ts CHANGED
@@ -7,7 +7,8 @@
7
7
  * client it calls and the entity it reads come from the same place.
8
8
  *
9
9
  * common/ the values of the wire, as the backend writes them: ids, a day and an instant,
10
- * `Decimal`, `Money`, and how any value is written and read (`valuesOf`, `rebuilt`)
10
+ * `Decimal`, `Money`, a to-many as a page of its records (`EntityCollection`), and
11
+ * how any value is written and read (`valuesOf`, `rebuilt`)
11
12
  * client/ the seam with a server: the transport and its REST and in-memory implementations,
12
13
  * entities with typed commands, the service's client contract
13
14
  * model/ model definitions: written by hand, read from a JSON Schema, kept once known
@@ -20,17 +21,18 @@
20
21
  * `@sincpro/client-framework/testing`: neither is part of what an application runs.
21
22
  */
22
23
  export { type ClientContract, entityKeyOf, kindOf, metasOf, versionsOf, } from "./client/contract.js";
23
- export { bodyFor, type CommandAnswers, type CommandBodies, type CommandRoute, type Entity, entity, type EntityRef, type EntityRoutes, 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";
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";
24
25
  export { type MemoryOptions, type MemoryTable, memoryTransport, } from "./client/memory.js";
25
26
  export { type RestOptions, restTransport, urlOf, type VersionDrift, } from "./client/rest.js";
26
27
  export { type RetryKeys, retryKeys, unanswered, } from "./client/retry.js";
27
- export { type CallOptions, type Draft, type Method, type One, readRoute, type SendOptions, type Transport, TransportError, } from "./client/transport.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";
28
30
  export { Decimal, type DecimalLike } from "./common/decimal.js";
29
31
  export { isEntityId, momentOf, newEntityId } from "./common/ids.js";
30
32
  export { Money } from "./common/money.js";
31
33
  export { BYTES, type JsonValue, pack, readValues, type Rebuild, rebuilt, unpack, valuesOf, } from "./common/serialization.js";
32
34
  export { dayOf, installTimeZone, instantOf, isDay, timeZoneNow, wallTimeOf, } from "./common/time.js";
33
- export { type ContextCodec, plainCodec, travelling, travels, } from "./context/codec.js";
35
+ export { CONTEXT_HEADER, type ContextCodec, plainCodec, travelling, travels, } from "./context/codec.js";
34
36
  export { type Context } from "./context/context.js";
35
37
  export { type ContextStore, type ContextValues, memoryContexts, } from "./context/store.js";
36
38
  export { defineMeta, field, type FieldOptions, type MetaSpec, type Words, } from "./model/meta.js";
@@ -41,3 +43,5 @@ export { type BoundedContext, UseBoundedContext, } from "./services/bounded-cont
41
43
  export { BIND, type Bindable, bindable, bindDependency, bindTransport, type Execution, headersOf, } from "./services/execution.js";
42
44
  export { ApplicationService, Feature, type ServiceClasses, type ServiceMethods, type Services, } from "./services/feature.js";
43
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";
package/dist/index.js CHANGED
@@ -7,7 +7,8 @@
7
7
  * client it calls and the entity it reads come from the same place.
8
8
  *
9
9
  * common/ the values of the wire, as the backend writes them: ids, a day and an instant,
10
- * `Decimal`, `Money`, and how any value is written and read (`valuesOf`, `rebuilt`)
10
+ * `Decimal`, `Money`, a to-many as a page of its records (`EntityCollection`), and
11
+ * how any value is written and read (`valuesOf`, `rebuilt`)
11
12
  * client/ the seam with a server: the transport and its REST and in-memory implementations,
12
13
  * entities with typed commands, the service's client contract
13
14
  * model/ model definitions: written by hand, read from a JSON Schema, kept once known
@@ -20,17 +21,18 @@
20
21
  * `@sincpro/client-framework/testing`: neither is part of what an application runs.
21
22
  */
22
23
  export { entityKeyOf, kindOf, metasOf, versionsOf, } from "./client/contract.js";
23
- export { bodyFor, entity, listRoute, NEW_PARAM, pathFor, PICK_PARAM, QUERY_PARAM, SCOPE_PARAM, scopeOfRef, startingValuesOf, TRAIL_PARAM, WITH_PARAM, writeRoute, } from "./client/entity.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";
24
25
  export { memoryTransport, } from "./client/memory.js";
25
26
  export { restTransport, urlOf, } from "./client/rest.js";
26
27
  export { retryKeys, unanswered, } from "./client/retry.js";
27
- export { readRoute, TransportError, } from "./client/transport.js";
28
+ export { downloadRoute, readRoute, TransportError, } from "./client/transport.js";
29
+ export { collectionOf, isCollection, isPartial, relatedCriteria, totalOf, } from "./common/collection.js";
28
30
  export { Decimal } from "./common/decimal.js";
29
31
  export { isEntityId, momentOf, newEntityId } from "./common/ids.js";
30
32
  export { Money } from "./common/money.js";
31
33
  export { BYTES, pack, readValues, rebuilt, unpack, valuesOf, } from "./common/serialization.js";
32
34
  export { dayOf, installTimeZone, instantOf, isDay, timeZoneNow, wallTimeOf, } from "./common/time.js";
33
- export { plainCodec, travelling, travels, } from "./context/codec.js";
35
+ export { CONTEXT_HEADER, plainCodec, travelling, travels, } from "./context/codec.js";
34
36
  export {} from "./context/context.js";
35
37
  export { memoryContexts, } from "./context/store.js";
36
38
  export { defineMeta, field, } from "./model/meta.js";
@@ -41,3 +43,5 @@ export { UseBoundedContext, } from "./services/bounded-context.js";
41
43
  export { BIND, bindable, bindDependency, bindTransport, headersOf, } from "./services/execution.js";
42
44
  export { ApplicationService, Feature, } from "./services/feature.js";
43
45
  export { configureProcess, outcomeOf, processContext, } from "./services/process.js";
46
+ export { reportError } from "./services/report.js";
47
+ export { screensTransport } from "./services/screens.js";
@@ -69,6 +69,7 @@ export declare const field: {
69
69
  holds?: Expression | null;
70
70
  computes?: import("@sincpro/criteria").Formula | null;
71
71
  navigable?: boolean;
72
+ reference?: boolean;
72
73
  total?: string | null;
73
74
  many?: boolean;
74
75
  relation?: string | null;
@@ -47,7 +47,8 @@ export interface Tracer {
47
47
  }
48
48
  /** What a report says about where the error happened. */
49
49
  export type ErrorAbout = {
50
- /** `<bounded context>.<Service>[.<method>]`: the use case's identity, as the backend's. */
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`). */
51
52
  useCase: string;
52
53
  /** The trace the error belongs to — the same id the server logged, when it got that far. */
53
54
  traceId: string;
@@ -3,10 +3,11 @@ import { contextAt } from "../context/context.js";
3
3
  import { bindDependency } from "./execution.js";
4
4
  import { composes, isApplicationServiceClass, isFeatureClass, methodsOf, } from "./feature.js";
5
5
  import { processContext, processNow, processStore, } from "./process.js";
6
+ import { firstReport } from "./report.js";
6
7
  /** Names `project` answers itself: no service is registered under them. */
7
8
  const RESERVED = new Set(["name", "context", "with", "scoped"]);
8
9
  /** Names a run answers on `this` itself: no dependency is added under them. */
9
- const ANSWERED = new Set(["context", "feature"]);
10
+ const ANSWERED = new Set(["context", "feature", "span"]);
10
11
  export class UseBoundedContext {
11
12
  name;
12
13
  /** This bounded context's level of the context, saved in the process's store. */
@@ -142,6 +143,8 @@ function viewOf(target, binding, execution) {
142
143
  get(held, property, receiver) {
143
144
  if (property === "context")
144
145
  return execution.context;
146
+ if (property === "span")
147
+ return execution.span;
145
148
  if (property === "feature" && isComposer)
146
149
  return (features ??= featuresOf(binding, execution));
147
150
  if (typeof property === "string") {
@@ -191,20 +194,6 @@ function contextOf(binding) {
191
194
  ...binding.scope,
192
195
  };
193
196
  }
194
- /**
195
- * Whether this is the first run to report this error. A Feature that fails inside an
196
- * ApplicationService throws through it: the error is reported once, by the run where it
197
- * happened — its span is the one that says where — and every run around it still ends `failed`.
198
- */
199
- const REPORTED = new WeakSet();
200
- function firstReport(error) {
201
- if (error === null || (typeof error !== "object" && typeof error !== "function"))
202
- return true;
203
- if (REPORTED.has(error))
204
- return false;
205
- REPORTED.add(error);
206
- return true;
207
- }
208
197
  /** What watches a use case never changes what it answers: a tracer or reporter that throws is ignored. */
209
198
  function quietly(work, otherwise) {
210
199
  try {
@@ -1,4 +1,4 @@
1
- import { plainCodec, travels } from "../context/codec.js";
1
+ import { CONTEXT_HEADER, plainCodec, travels } from "../context/codec.js";
2
2
  /**
3
3
  * A dependency that has something to say about the execution it is used in: given that
4
4
  * execution, it answers itself bound to it. The transport and every generated client are;
@@ -34,7 +34,7 @@ export function headersOf(execution) {
34
34
  }
35
35
  // Only what travels: an object or a function held in the context stays in this process.
36
36
  if (Object.values(context).some(travels))
37
- headers["sincpro-context"] = plainCodec.dumps(context);
37
+ headers[CONTEXT_HEADER] = plainCodec.dumps(context);
38
38
  return headers;
39
39
  }
40
40
  /**
@@ -58,6 +58,9 @@ export function bindTransport(transport, execution) {
58
58
  const read = transport.read;
59
59
  if (read !== undefined)
60
60
  bound.read = (route, signal, options) => read.call(transport, route, signal, call(options));
61
+ const download = transport.download;
62
+ if (download !== undefined)
63
+ bound.download = (route, signal, options) => download.call(transport, route, signal, call(options));
61
64
  return bound;
62
65
  }
63
66
  /**
@@ -1,4 +1,5 @@
1
1
  import type { ContextValues } from "../context/store.js";
2
+ import type { Span } from "../observability/ports.js";
2
3
  /**
3
4
  * `Feature` and `ApplicationService`: the two kinds of service a bounded context holds, as in
4
5
  * sincpro_framework. Each bounded context gives them its dependencies once, in its own base:
@@ -47,6 +48,8 @@ type Within<D extends object, C extends object> = Readonly<D> & {
47
48
  * read once where the gesture started and the same for every Feature it runs.
48
49
  */
49
50
  readonly context: Readonly<Partial<C>>;
51
+ /** This run's span: a use case says more about what it ran on (`this.span.set({ … })`). */
52
+ readonly span: Span;
50
53
  };
51
54
  /** Service classes by the name a bounded context gives them: `{ Issues, Notices }`. */
52
55
  export type ServiceClasses = Readonly<Record<string, new () => object>>;
@@ -55,7 +58,7 @@ export type ServiceClasses = Readonly<Record<string, new () => object>>;
55
58
  * methods, without its dependencies, its context or its `_` helpers.
56
59
  */
57
60
  export type ServiceMethods<I, D> = {
58
- readonly [M in keyof I as M extends keyof D | "context" | "feature" | `_${string}` ? never : I[M] extends (...args: never[]) => unknown ? M : never]: I[M];
61
+ readonly [M in keyof I as M extends keyof D | "context" | "feature" | "span" | `_${string}` ? never : I[M] extends (...args: never[]) => unknown ? M : never]: I[M];
59
62
  };
60
63
  /** The services of a set of classes, by their names. */
61
64
  export type Services<S extends ServiceClasses, D> = {
@@ -0,0 +1,15 @@
1
+ export declare function firstReport(error: unknown): boolean;
2
+ /**
3
+ * Reports what nobody expected that is not a use case — a screen that broke while drawing, a
4
+ * subscriber, a worker — through the process's reporter, with the process's context.
5
+ *
6
+ * <Composer onViewError={(error, { resource }) => reportError(error, `view ${formatRef(resource)}`)} />
7
+ * ErrorUtils.setGlobalHandler((error) => reportError(error, "react-native global"));
8
+ *
9
+ * Context: a use case reports its own failures (the bounded context does it for it); what fails
10
+ * outside one would only reach a console. `where` takes the place of the use case's name in the
11
+ * report. Only what failed is reported (`outcomeOf`: a refusal the server explained or an abort is
12
+ * not); an error a use case already reported is not reported again; and a reporter that throws is
13
+ * ignored: telling about a failure never becomes a second one.
14
+ */
15
+ export declare function reportError(error: unknown, where: string): void;
@@ -0,0 +1,53 @@
1
+ import { newEntityId } from "../common/ids.js";
2
+ import { processContext, processNow } from "./process.js";
3
+ /**
4
+ * Whether this is the first time this error is reported. A Feature that fails inside an
5
+ * ApplicationService throws through it, and a screen that draws the failure may break on it:
6
+ * the error is reported once, by the first that saw it — its span is the one that says where.
7
+ */
8
+ const REPORTED = new WeakSet();
9
+ export function firstReport(error) {
10
+ if (error === null || (typeof error !== "object" && typeof error !== "function"))
11
+ return true;
12
+ if (REPORTED.has(error))
13
+ return false;
14
+ REPORTED.add(error);
15
+ return true;
16
+ }
17
+ /**
18
+ * Reports what nobody expected that is not a use case — a screen that broke while drawing, a
19
+ * subscriber, a worker — through the process's reporter, with the process's context.
20
+ *
21
+ * <Composer onViewError={(error, { resource }) => reportError(error, `view ${formatRef(resource)}`)} />
22
+ * ErrorUtils.setGlobalHandler((error) => reportError(error, "react-native global"));
23
+ *
24
+ * Context: a use case reports its own failures (the bounded context does it for it); what fails
25
+ * outside one would only reach a console. `where` takes the place of the use case's name in the
26
+ * report. Only what failed is reported (`outcomeOf`: a refusal the server explained or an abort is
27
+ * not); an error a use case already reported is not reported again; and a reporter that throws is
28
+ * ignored: telling about a failure never becomes a second one.
29
+ */
30
+ export function reportError(error, where) {
31
+ const settings = processNow();
32
+ try {
33
+ // What the server explained, or what the caller stopped waiting for, is not a fault here either.
34
+ if (settings.outcomeOf(error) !== "failed")
35
+ return;
36
+ }
37
+ catch {
38
+ // An outcome that cannot be told is a failure.
39
+ }
40
+ if (!firstReport(error))
41
+ return;
42
+ try {
43
+ settings.reporter?.report(error, {
44
+ useCase: where,
45
+ traceId: "",
46
+ executionId: newEntityId(),
47
+ context: processContext.values,
48
+ });
49
+ }
50
+ catch {
51
+ // Ignored, as a reporter that fails inside a use case is.
52
+ }
53
+ }
@@ -0,0 +1,2 @@
1
+ import type { Transport } from "../client/transport.js";
2
+ export declare function screensTransport(transport: Transport, name?: string): Transport;
@@ -0,0 +1,76 @@
1
+ import { UseBoundedContext } from "./bounded-context.js";
2
+ import { Feature } from "./feature.js";
3
+ /**
4
+ * What a screen reads of an entity — a page, a record, a read of its own: sincpro_framework's
5
+ * built-in `EntityReads`, on the client.
6
+ */
7
+ class EntityReads extends Feature {
8
+ list(route, criteria, signal, options) {
9
+ this.span.set({ "http.route": route });
10
+ return this.transport.list(route, criteria, signal, options);
11
+ }
12
+ one(route, signal, options) {
13
+ this.span.set({ "http.route": route });
14
+ return this.transport.one(route, signal, options);
15
+ }
16
+ read(route, signal, options) {
17
+ this.span.set({ "http.route": route });
18
+ const read = this.transport.read;
19
+ if (read === undefined)
20
+ return Promise.reject(new Error(`this transport cannot read ${route}: it has no read()`));
21
+ return read.call(this.transport, route, signal, options);
22
+ }
23
+ download(route, signal, options) {
24
+ this.span.set({ "http.route": route });
25
+ const download = this.transport.download;
26
+ if (download === undefined)
27
+ return Promise.reject(new Error(`this transport cannot download ${route}: it has no download()`));
28
+ return download.call(this.transport, route, signal, options);
29
+ }
30
+ }
31
+ /** What a screen writes — a save, a draft, a preview, a Command: each request a use case. */
32
+ class EntityWrites extends Feature {
33
+ send(method, route, body, signal, options) {
34
+ this.span.set({ "http.method": method, "http.route": route });
35
+ return this.transport.send(method, route, body, signal, options);
36
+ }
37
+ }
38
+ /**
39
+ * The transport, every call a use case of a bounded context of its own — `screens` unless named:
40
+ * what a platform hands the screens it draws.
41
+ *
42
+ * const screens = screensTransport(restTransport({ base: "/v1" }));
43
+ * await screens.list("/facturas", criteria); // span screens/EntityReads.list
44
+ *
45
+ * Context: a use case is traced, carries its context and reports what nobody expected; a list a
46
+ * screen reads through the transport was none of that — no span, no `traceparent` (the backend's
47
+ * trace started there, with no client in it), no company, and a 500 reported to nobody. Here
48
+ * each read and write runs as a use case of `screens` — `EntityReads.list`, `.one`, `.read`,
49
+ * `.download`, `EntityWrites.send` —, so it gets all three the way any other does: the span (its route said
50
+ * on it), the trace and the process's context on the request, and a report only when it failed
51
+ * (a 4xx the server explained is the screen's to show). A bounded context's own dependencies are
52
+ * never given this: there its use case is the one that runs.
53
+ */
54
+ /** The transports this made: given one again, it is answered as it is — wrapped twice, each
55
+ * request would be two unrelated use cases, and the server would see only the outer's trace. */
56
+ const MADE = new WeakSet();
57
+ export function screensTransport(transport, name = "screens") {
58
+ if (MADE.has(transport))
59
+ return transport;
60
+ const screens = new UseBoundedContext(name)
61
+ .addDependency("transport", transport)
62
+ .register({ features: { EntityReads, EntityWrites } });
63
+ const reads = screens.EntityReads;
64
+ const writes = screens.EntityWrites;
65
+ const through = {
66
+ list: (route, criteria, signal, options) => reads.list(route, criteria, signal, options),
67
+ one: (route, signal, options) => reads.one(route, signal, options),
68
+ send: (method, route, body, signal, options) => writes.send(method, route, body, signal, options),
69
+ };
70
+ if (transport.read !== undefined)
71
+ through.read = (route, signal, options) => reads.read(route, signal, options);
72
+ if (transport.download !== undefined)
73
+ through.download = (route, signal, options) => reads.download(route, signal, options);
74
+ MADE.add(through);
75
+ return through;
76
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sincpro/client-framework",
3
- "version": "0.4.0",
3
+ "version": "0.6.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",
@@ -25,6 +25,10 @@
25
25
  "./sentry": {
26
26
  "types": "./dist/adapters/sentry.d.ts",
27
27
  "default": "./dist/adapters/sentry.js"
28
+ },
29
+ "./parquet": {
30
+ "types": "./dist/adapters/parquet.d.ts",
31
+ "default": "./dist/adapters/parquet.js"
28
32
  }
29
33
  },
30
34
  "bin": {
@@ -68,22 +72,31 @@
68
72
  },
69
73
  "peerDependencies": {
70
74
  "@opentelemetry/api": "^1.9.0",
71
- "@sincpro/criteria": "^2.0.0"
75
+ "@sincpro/criteria": "^2.1.0",
76
+ "hyparquet": "^1.32.0",
77
+ "typescript": ">=5.5 <7"
72
78
  },
73
79
  "peerDependenciesMeta": {
74
80
  "@opentelemetry/api": {
75
81
  "optional": true
82
+ },
83
+ "hyparquet": {
84
+ "optional": true
85
+ },
86
+ "typescript": {
87
+ "optional": true
76
88
  }
77
89
  },
78
90
  "devDependencies": {
79
91
  "@eslint/js": "^10.0.1",
80
92
  "@opentelemetry/api": "1.10.0",
81
93
  "@opentelemetry/sdk-trace": "3.1.0",
82
- "@sincpro/criteria": "^2.0.0",
94
+ "@sincpro/criteria": "^2.1.0",
83
95
  "@types/node": "^26.6.6",
84
96
  "eslint": "^10.14.0",
85
97
  "eslint-config-prettier": "^10.1.8",
86
98
  "eslint-plugin-simple-import-sort": "^14.0.0",
99
+ "hyparquet": "^1.32.0",
87
100
  "prettier": "^3.9.10",
88
101
  "tsc-alias": "^1.9.9",
89
102
  "typescript": "~6.0.3",