@jarenjs/contract 0.43.1

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 (84) hide show
  1. package/README.md +508 -0
  2. package/dist/types/adapters/fetch.d.ts +27 -0
  3. package/dist/types/adapters/node.d.ts +47 -0
  4. package/dist/types/app/binding.d.ts +122 -0
  5. package/dist/types/app/effect.d.ts +77 -0
  6. package/dist/types/app/index.d.ts +31 -0
  7. package/dist/types/app/subscription.d.ts +82 -0
  8. package/dist/types/bundle.d.ts +43 -0
  9. package/dist/types/cli.d.ts +15 -0
  10. package/dist/types/client/http.d.ts +242 -0
  11. package/dist/types/client/outcome.d.ts +289 -0
  12. package/dist/types/compat.d.ts +36 -0
  13. package/dist/types/compile.d.ts +196 -0
  14. package/dist/types/describe.d.ts +115 -0
  15. package/dist/types/diff.d.ts +91 -0
  16. package/dist/types/errors.d.ts +205 -0
  17. package/dist/types/http/dispatch.d.ts +148 -0
  18. package/dist/types/http/serve.d.ts +154 -0
  19. package/dist/types/http/wire.d.ts +334 -0
  20. package/dist/types/index.d.ts +39 -0
  21. package/dist/types/ledger.d.ts +207 -0
  22. package/dist/types/local/index.d.ts +127 -0
  23. package/dist/types/messages.d.ts +63 -0
  24. package/dist/types/path.d.ts +119 -0
  25. package/dist/types/pipeline.d.ts +157 -0
  26. package/dist/types/port/client.d.ts +142 -0
  27. package/dist/types/port/frame.d.ts +195 -0
  28. package/dist/types/port/serve.d.ts +102 -0
  29. package/dist/types/project/index.d.ts +34 -0
  30. package/dist/types/project/markdown.d.ts +28 -0
  31. package/dist/types/project/openapi.d.ts +102 -0
  32. package/dist/types/project/tools.d.ts +57 -0
  33. package/dist/types/project/typescript.d.ts +59 -0
  34. package/dist/types/public.d.ts +73 -0
  35. package/dist/types/revision.d.ts +36 -0
  36. package/dist/types/stream/client.d.ts +104 -0
  37. package/dist/types/stream/server.d.ts +106 -0
  38. package/dist/types/stream/sse.d.ts +62 -0
  39. package/docs/APP-INTEGRATION.md +301 -0
  40. package/docs/CONTRACT-FORMAT.md +1923 -0
  41. package/package.json +110 -0
  42. package/schemas/jaren-contract-port.draft-07.schema.json +241 -0
  43. package/schemas/jaren-contract-port.schema.json +241 -0
  44. package/schemas/jaren-contract.draft-07.schema.json +287 -0
  45. package/schemas/jaren-contract.schema.json +287 -0
  46. package/src/adapters/fetch.js +109 -0
  47. package/src/adapters/node.js +238 -0
  48. package/src/app/binding.js +426 -0
  49. package/src/app/effect.js +190 -0
  50. package/src/app/index.js +26 -0
  51. package/src/app/subscription.js +130 -0
  52. package/src/bundle.js +168 -0
  53. package/src/cli.js +264 -0
  54. package/src/client/http.js +1150 -0
  55. package/src/client/outcome.js +364 -0
  56. package/src/compat.js +62 -0
  57. package/src/compile.js +1162 -0
  58. package/src/describe.js +109 -0
  59. package/src/diff.js +610 -0
  60. package/src/errors.js +236 -0
  61. package/src/http/dispatch.js +1054 -0
  62. package/src/http/serve.js +301 -0
  63. package/src/http/wire.js +469 -0
  64. package/src/index.js +33 -0
  65. package/src/ledger.js +225 -0
  66. package/src/local/index.js +363 -0
  67. package/src/messages.js +68 -0
  68. package/src/path.js +471 -0
  69. package/src/pipeline.js +241 -0
  70. package/src/port/client.js +518 -0
  71. package/src/port/frame.js +196 -0
  72. package/src/port/serve.js +442 -0
  73. package/src/project/index.js +29 -0
  74. package/src/project/markdown.js +244 -0
  75. package/src/project/openapi.js +564 -0
  76. package/src/project/openapi.jslt.json +149 -0
  77. package/src/project/tools.js +139 -0
  78. package/src/project/typescript.js +152 -0
  79. package/src/project/typescript.jtlt.json +72 -0
  80. package/src/public.js +206 -0
  81. package/src/revision.js +90 -0
  82. package/src/stream/client.js +212 -0
  83. package/src/stream/server.js +306 -0
  84. package/src/stream/sse.js +67 -0
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @file `openLocalClient(contract, handlers, options)`: the in-process
3
+ * binding of a compiled contract (docs/CONTRACT-FORMAT.md §15) — the
4
+ * same operation pipeline the HTTP server runs, with no wire: the test
5
+ * seam, SSR, a CLI. The client and the server are one object;
6
+ * `serveLocal` is the same factory under the serve name, for symmetry
7
+ * with the other bindings.
8
+ *
9
+ * `invoke(op, input, ctx)` validates the input with the operation's
10
+ * compiled validator (a refusal is the `JC2050` outcome — nothing ran),
11
+ * runs the neutral pipeline against the handler with a frozen context
12
+ * `{ op, trace, signal, params: null, headers: {}, fail, idempotency:
13
+ * null }`, and resolves a D6 outcome: a declared failure is `kind:
14
+ * "failure"` with `status: null` (the member present, never omitted —
15
+ * this binding carries no statuses and `capabilities` says so); a
16
+ * handler fault of any class — a throw, an undeclared code, a broken
17
+ * output or error-details schema — is `kind: "contract"` `JC2070`, its
18
+ * cause reported to `onError`, so a host bug is never mistaken for a
19
+ * declared failure; an aborted `ctx.signal` (or `close()`) is `kind:
20
+ * "cancelled"`, and a handler still running then settles into nothing.
21
+ *
22
+ * What this binding cannot carry, it refuses or ignores loudly:
23
+ * statuses, headers, entity tags and non-JSON media do not exist here
24
+ * (`invoke` of an opaque operation throws `JC1005`; a handler given for
25
+ * one is accepted so an HTTP handler table can be reused verbatim, and
26
+ * never called); a declared `policy.idempotency` is ALLOWED and ignored
27
+ * — the same contract must serve over http and locally, and re-running
28
+ * a command in one process is the caller's own hand — with
29
+ * `capabilities.idempotency: false` saying so.
30
+ */
31
+ import { PORT_LOCAL_ERRORS } from '../pipeline.js';
32
+ export { PORT_LOCAL_ERRORS };
33
+ export type Contract = import('../compile.js').Contract;
34
+ export type CompiledOperation = import('../compile.js').CompiledOperation;
35
+ export type Catalog = import('../http/wire.js').Catalog;
36
+ export type Handler = import('../http/dispatch.js').Handler;
37
+ export type Outcome = import('../client/outcome.js').Outcome;
38
+ export type OutcomeMeta = import('../client/outcome.js').OutcomeMeta;
39
+ export type PipelineRoute = import('../pipeline.js').PipelineRoute;
40
+ export type LocalOptions = {
41
+ /**
42
+ * - the trace generator; default `crypto.randomUUID`
43
+ */
44
+ trace?: () => string;
45
+ /**
46
+ * - `'never'` is a declared
47
+ * downgrade, reported in `capabilities.validatedOutput`
48
+ */
49
+ validateOutput?: 'always' | 'never';
50
+ /**
51
+ * - a message catalog consulted before the English one
52
+ */
53
+ catalog?: Record<string, string | ((params: object) => string)>;
54
+ /**
55
+ * - observes the cause behind every `JC2070` outcome and validator throw
56
+ */
57
+ onError?: (error: unknown, ctx: {
58
+ op: string;
59
+ trace: string;
60
+ } | null) => void;
61
+ };
62
+ export type LocalInvokeContext = {
63
+ /**
64
+ * - resolves the outcome `cancelled`
65
+ */
66
+ signal?: AbortSignal;
67
+ /**
68
+ * - the caller's attempt id, echoed in `meta.attempt`
69
+ */
70
+ attempt?: unknown;
71
+ };
72
+ export type LocalCapabilities = {
73
+ name: 'local';
74
+ status: false;
75
+ headers: false;
76
+ media: false;
77
+ etag: false;
78
+ idempotency: false;
79
+ /**
80
+ * - the output validator runs
81
+ */
82
+ validatedOutput: boolean;
83
+ stream: false;
84
+ cancel: 'signal';
85
+ };
86
+ export type LocalClient = {
87
+ invoke: (op: string, input?: unknown, ctx?: LocalInvokeContext) => Promise<Outcome>;
88
+ capabilities: LocalCapabilities;
89
+ contract: Contract;
90
+ describe: () => any;
91
+ /**
92
+ * - later invokes resolve `cancelled`; running handlers see their signal abort
93
+ */
94
+ close: () => void;
95
+ };
96
+ export type LocalRoute = PipelineRoute & {
97
+ hasInput: boolean;
98
+ outcome: import('../client/outcome.js').OutcomeRoute;
99
+ };
100
+ /**
101
+ * Serve a compiled contract in-process and call it through the same
102
+ * object: the `open(contract) → Client` half and the `serve(contract,
103
+ * handlers)` half of the driver pair are one here. Construction refuses
104
+ * host mistakes (`JC1001` handler table or option, `JC1002` missing
105
+ * handler — an opaque operation is exempt: this binding cannot invoke
106
+ * it, and an HTTP handler table that carries one may be reused verbatim).
107
+ *
108
+ * @param {Contract} contract
109
+ * @param {Record<string, Handler>} handlers - operation id → handler
110
+ * @param {LocalOptions} [options]
111
+ * @returns {LocalClient}
112
+ * @throws {ContractHostError}
113
+ * @example
114
+ * const client = openLocalClient(contract, {
115
+ * 'catalog.load': () => catalog,
116
+ * 'product.save': (input, ctx) => saved ? product : ctx.fail('conflict', {}, { current }),
117
+ * });
118
+ * const outcome = await client.invoke('catalog.load', { since: '2026-01-01T00:00:00Z' });
119
+ * if (!outcome.ok && outcome.kind === 'failure') show(outcome.error.code); // status is null here
120
+ */
121
+ export declare function openLocalClient(contract: Contract, handlers: Record<string, Handler>, options?: LocalOptions): LocalClient;
122
+ /**
123
+ * The serve name of the same factory: the server half IS the client half
124
+ * here — one object, both names, for symmetry with `serveHttp`/
125
+ * `openHttpClient` and `servePort`/`openPortClient`.
126
+ */
127
+ export declare const serveLocal: typeof openLocalClient;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @file The English message catalog: one template per `contract/*` msgid
3
+ * a binding can answer with — the HTTP taxonomy, the port/local codes,
4
+ * `contract/handler-error` (the generic text for a declared operation
5
+ * error that has no message of its own) — and one per client-originated
6
+ * outcome (the `JC205x` codes a client resolves without a server
7
+ * message). Compiled ONCE at module scope
8
+ * with `@jarenjs/core`'s `compileMessageCatalog` (the two-stage house
9
+ * rule applied to messages) and exported in plain form for the locale
10
+ * packs to mirror key for key.
11
+ *
12
+ * A message never interpolates a request value: the parameters are the
13
+ * operation id, a declared limit, a media type, a method list, a
14
+ * declared header member name, a declared error code, a status, a
15
+ * platform error's NAME or a contract version — trusted artifacts or
16
+ * protocol facts, never something a peer sent as content (the
17
+ * trust-boundary rule of docs/CONTRACT-FORMAT.md §7).
18
+ */
19
+ /**
20
+ * The plain English catalog: msgid → template (`{param}` placeholders).
21
+ * The keys are exactly the msgids of the CONTRACT-FORMAT.md §7 taxonomy,
22
+ * `contract/handler-error`, and the §10 client table; a test holds them
23
+ * equal.
24
+ */
25
+ export declare const contractMessagesEn: Readonly<{
26
+ 'contract/not-found': "no operation matches the request method and path";
27
+ 'contract/method-not-allowed': "the path is served under other methods: {allow}";
28
+ 'contract/body-too-large': "the request body of operation {op} exceeds its {limit}-byte limit";
29
+ 'contract/unsupported-media': "operation {op} accepts {media} bodies only";
30
+ 'contract/malformed-json': "the request body of operation {op} is not valid JSON";
31
+ 'contract/invalid-input': "the input of operation {op} is invalid";
32
+ 'contract/idempotency-key-required': "operation {op} requires an Idempotency-Key header";
33
+ 'contract/handler-failed': "operation {op} failed";
34
+ 'contract/idempotency-conflict': "the Idempotency-Key of operation {op} conflicts with an earlier request ({kind})";
35
+ 'contract/invalid-output': "operation {op} produced a response that violates its contract";
36
+ 'contract/malformed-path': "the request path carries a malformed percent-escape";
37
+ 'contract/malformed-query': "the query string is not decodable";
38
+ 'contract/not-implemented': "operation {op} is not implemented on this server";
39
+ 'contract/precondition-failed': "the If-Match precondition of operation {op} failed";
40
+ 'contract/invalid-header': "the {header} header of operation {op} is invalid";
41
+ 'contract/handler-error': "operation {op} failed with {code}";
42
+ 'contract/client-invalid-input': "the input of operation {op} is invalid; nothing was sent";
43
+ 'contract/network': "the request of {op} did not complete ({name})";
44
+ 'contract/cancelled': "the request of operation {op} was cancelled";
45
+ 'contract/invalid-response': "the response of operation {op} violates its contract";
46
+ 'contract/key-storage-failed': "the idempotency key of operation {op} could not be stored; nothing was sent";
47
+ 'contract/undeclared-response': "operation {op} answered an undeclared response (status {status})";
48
+ 'contract/not-a-contract': "the server does not describe contract {id} at its well-known path";
49
+ 'contract/incompatible': "the server speaks version {server} of contract {id}; this client speaks {client} and neither end declares the other compatible";
50
+ 'contract/host-failed': "operation {op} failed in the host before an outcome was produced";
51
+ 'contract/local-handler-failed': "operation {op} failed in the serving host";
52
+ 'contract/unknown-operation': "the request names no operation served on this channel";
53
+ 'contract/port-timeout': "operation {op} got no answer on the channel within {ms}ms";
54
+ 'contract/malformed-frame': "the response frame of operation {op} is malformed";
55
+ 'contract/channel-closed': "the channel of operation {op} is closed";
56
+ 'contract/not-a-stream': "the server answered the subscription of operation {op} with a non-stream response";
57
+ 'contract/invalid-snapshot': "operation {op} produced a snapshot that violates its contract";
58
+ 'contract/seq-regression': "the stream of operation {op} violated its seq order";
59
+ 'contract/stream-error': "the stream of operation {op} ended with a server error ({code})";
60
+ 'contract/heartbeat-missed': "the stream of operation {op} went silent for {ms}ms";
61
+ }>;
62
+ /** The compiled English catalog (module-level singleton). */
63
+ export declare const contractCatalogEn: Readonly<Record<string, (params: object, error?: object) => string>>;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * @file The path matcher: RFC 6570 level-1 templates (`/api/products/{id}`)
3
+ * compiled into one static-segment tree per HTTP method, walked by
4
+ * char-code scan with zero allocation until the leaf is known.
5
+ *
6
+ * Package-private (docs/CONTRACT-FORMAT.md §5). Two stages:
7
+ *
8
+ * 1. `parsePathTemplate` — the one template parser in the suite: it
9
+ * canonicalizes `:name` to `{name}`, splits static from variable
10
+ * segments and refuses every reserved form with a message that names
11
+ * it. `compileContract` calls it once per operation and maps a
12
+ * refusal to `JC0008`.
13
+ * 2. `compileRoutes` — the operation table becomes a tree of nodes
14
+ * `{ statics, variable, leaf }`; `match(method, path)` walks statics
15
+ * first, falls back to the variable child, and backtracks on a dead
16
+ * end. Static beats variable REGARDLESS of registration order — the
17
+ * ordering case a regex-per-route router refuses or gets wrong.
18
+ *
19
+ * Matching rules a binding relies on: exact on trailing slash (`/a/` is
20
+ * a different shape from `/a` and, since no template has an empty
21
+ * segment, never matches); a variable never binds an empty segment; each
22
+ * segment is percent-decoded once, statics compared in decoded space, and
23
+ * a decoded `/` never re-splits; a malformed escape makes `match` return
24
+ * `null` (never throw — a request is hostile input). `?`/`#` never reach
25
+ * `match`: the binding splits the query off first.
26
+ *
27
+ * Variable names live on the LEAF, not on the variable node: two
28
+ * templates that differ only in a variable's name (`/a/{x}/b`,
29
+ * `/a/{y}/c`) share the variable child and each leaf binds its own name.
30
+ * The one shape that cannot be represented is two leaves for one method
31
+ * and one shape — a duplicate the compiler has already refused as
32
+ * `JC0010`, so here it is a `TypeError` (a host programming guard).
33
+ */
34
+ export type PathSegment = {
35
+ variable: boolean;
36
+ text: string;
37
+ };
38
+ export type ParsedPathTemplate = {
39
+ /**
40
+ * - The canonical template (`{name}` form).
41
+ */
42
+ path: string;
43
+ /**
44
+ * - In order; empty for `/`.
45
+ */
46
+ segments: readonly PathSegment[];
47
+ /**
48
+ * - Variable names in order.
49
+ */
50
+ variables: readonly string[];
51
+ };
52
+ /**
53
+ * Parse a path template into segments, canonicalizing `:name` to
54
+ * `{name}`. Throws a `TypeError` whose message names the rule that was
55
+ * broken (the compiler wraps it as `JC0008`).
56
+ *
57
+ * The dialect: a leading `/`; the root template `/` has no segments;
58
+ * every other segment is non-empty; a variable is a whole segment,
59
+ * `{name}` or `:name`, with `name` an identifier, declared once per
60
+ * template; the RFC 6570 operator and modifier forms (`{+name}`,
61
+ * `{?name}`, `{name*}`, `{name:3}`, `{a,b}`), the `{name+}` tail and the
62
+ * `*` wildcard are reserved and refused by name.
63
+ * @param {unknown} source
64
+ * @returns {ParsedPathTemplate}
65
+ */
66
+ export declare function parsePathTemplate(source: unknown): ParsedPathTemplate;
67
+ /**
68
+ * The shape of a template with every variable normalized to `{}` — the
69
+ * identity `JC0010` is decided on.
70
+ * @param {ParsedPathTemplate} parsed
71
+ * @returns {string}
72
+ */
73
+ export declare function pathShape(parsed: ParsedPathTemplate): string;
74
+ export type Leaf = {
75
+ key: any;
76
+ names: readonly string[];
77
+ depths: readonly number[];
78
+ };
79
+ export type Node = {
80
+ statics: Map<string, Node> | null;
81
+ variable: Node | null;
82
+ leaf: Leaf | null;
83
+ };
84
+ export type Router = {
85
+ match: (method: string, path: string) => {
86
+ key: any;
87
+ params: Record<string, string>;
88
+ } | null;
89
+ /**
90
+ * - the methods whose tree
91
+ * reaches a leaf for this path shape, sorted — what a 405 lists in
92
+ * `Allow`; `[]` for a malformed or unknown path
93
+ */
94
+ allowed: (path: string) => string[];
95
+ };
96
+ /**
97
+ * A compiled router.
98
+ * @typedef {Object} Router
99
+ * @property {(method: string, path: string) => { key: any, params: Record<string, string> } | null} match
100
+ * @property {(path: string) => string[]} allowed - the methods whose tree
101
+ * reaches a leaf for this path shape, sorted — what a 405 lists in
102
+ * `Allow`; `[]` for a malformed or unknown path
103
+ */
104
+ /**
105
+ * Compile a route table into a `Router`. `entries` are
106
+ * `{ method, path, key }` with `path` in canonical `{name}` form (a
107
+ * `:name` template is a `TypeError` here — canonicalize with
108
+ * `parsePathTemplate` first). Two entries with one method and one shape
109
+ * are a `TypeError` (the compiler's `JC0010` has already refused them);
110
+ * templates that differ only in a variable name under a shared parent are
111
+ * fine as long as they end in different leaves.
112
+ * @param {readonly { method: string, path: string, key: any }[]} entries
113
+ * @returns {Router}
114
+ */
115
+ export declare function compileRoutes(entries: readonly {
116
+ method: string;
117
+ path: string;
118
+ key: any;
119
+ }[]): Router;
@@ -0,0 +1,157 @@
1
+ /**
2
+ * @file The transport-neutral core of every server-side binding: validate
3
+ * the assembled input, call the handler through ONE uniform promise
4
+ * boundary — a synchronous throw, a non-promise return and a rejection
5
+ * settle exactly alike — classify the settlement (a declared operation
6
+ * failure, with its details validated against the declaration's schema,
7
+ * or a host fault), and validate the output. The HTTP binding wraps the
8
+ * result in statuses, headers and bodies; the `local` and `port`
9
+ * bindings wrap it in D6 outcomes and frames — the classification is
10
+ * decided here once so the three can never disagree.
11
+ *
12
+ * Total for everything a handler can do: a hostile value whose `then`
13
+ * accessor throws is a rejection at the boundary (`JC2008`-class), a
14
+ * value whose other members throw survives it and dies in output
15
+ * validation (`JC2010`-class); nothing here throws for a settlement and
16
+ * nothing renders a message — the binding renders at its edge.
17
+ */
18
+ export type CompiledOperation = import('./compile.js').CompiledOperation;
19
+ export type CompiledErrorDecl = import('./compile.js').CompiledErrorDecl;
20
+ export type PipelineRoute = {
21
+ op: CompiledOperation;
22
+ handler: ((input: any, ctx: any) => unknown) | null;
23
+ /**
24
+ * - opaque: the value is the binding's to check, never the output validator's
25
+ */
26
+ raw: boolean;
27
+ validateInput: ((value: unknown) => any) | null;
28
+ validateOutput: (value: unknown) => any;
29
+ details: 'none' | 'paths' | 'full';
30
+ errors: Readonly<Record<string, CompiledErrorDecl>>;
31
+ retryOn: ReadonlySet<string>;
32
+ };
33
+ export type OperationResult = {
34
+ kind: 'value';
35
+ value: unknown;
36
+ } | {
37
+ kind: 'failure';
38
+ code: string;
39
+ params: Readonly<Record<string, unknown>>;
40
+ details: unknown;
41
+ retryable: boolean;
42
+ status: number;
43
+ } | {
44
+ kind: 'contract';
45
+ code: 'JC2006' | 'JC2008' | 'JC2010';
46
+ details: unknown;
47
+ cause: unknown;
48
+ };
49
+ /**
50
+ * @typedef {import('./compile.js').CompiledOperation} CompiledOperation
51
+ * @typedef {import('./compile.js').CompiledErrorDecl} CompiledErrorDecl
52
+ */
53
+ /**
54
+ * The neutral subset of a binding's prepared route — what the pipeline
55
+ * reads per operation. The HTTP binding's `Route` carries these members
56
+ * verbatim; the `local` and `port` bindings prepare exactly this.
57
+ * @typedef {Object} PipelineRoute
58
+ * @property {CompiledOperation} op
59
+ * @property {((input: any, ctx: any) => unknown) | null} handler
60
+ * @property {boolean} raw - opaque: the value is the binding's to check, never the output validator's
61
+ * @property {((value: unknown) => any) | null} validateInput
62
+ * @property {(value: unknown) => any} validateOutput
63
+ * @property {'none' | 'paths' | 'full'} details
64
+ * @property {Readonly<Record<string, CompiledErrorDecl>>} errors
65
+ * @property {ReadonlySet<string>} retryOn
66
+ */
67
+ /**
68
+ * The classified settlement of one operation:
69
+ *
70
+ * - `value` — the handler's output, validated;
71
+ * - `failure` — a declared operation error (`errors[code]`), its
72
+ * `details` validated, `retryable` from the failure or the retry
73
+ * policy, `status` the declaration's (a status-less binding ignores
74
+ * it);
75
+ * - `contract` — the host broke the contract: `JC2006` the input fails
76
+ * its validator (`details` by `policy.errors.details`), `JC2008` the
77
+ * handler threw a non-declared error, rejected or answered an
78
+ * undeclared code, `JC2010` the output or a declared error's details
79
+ * fail their schema. `cause` is for the binding's `onError` observer
80
+ * (`undefined` when there is nothing to report); it never crosses a
81
+ * wire.
82
+ *
83
+ * @typedef {{ kind: 'value', value: unknown }
84
+ * | { kind: 'failure', code: string, params: Readonly<Record<string, unknown>>, details: unknown, retryable: boolean, status: number }
85
+ * | { kind: 'contract', code: 'JC2006' | 'JC2008' | 'JC2010', details: unknown, cause: unknown }} OperationResult
86
+ */
87
+ /**
88
+ * The request-time codes of the `local` and `port` bindings —
89
+ * code → `{ msgid, retryable }`, the same table-as-data shape as
90
+ * `HTTP_ERRORS` and `CLIENT_ERRORS`. The normative table is
91
+ * docs/CONTRACT-FORMAT.md §15–§16; a test holds them equal.
92
+ */
93
+ export declare const PORT_LOCAL_ERRORS: Readonly<{
94
+ JC2070: Readonly<{
95
+ msgid: "contract/local-handler-failed";
96
+ retryable: false;
97
+ }>;
98
+ JC2071: Readonly<{
99
+ msgid: "contract/unknown-operation";
100
+ retryable: false;
101
+ }>;
102
+ JC2072: Readonly<{
103
+ msgid: "contract/port-timeout";
104
+ retryable: true;
105
+ }>;
106
+ JC2073: Readonly<{
107
+ msgid: "contract/malformed-frame";
108
+ retryable: false;
109
+ }>;
110
+ JC2074: Readonly<{
111
+ msgid: "contract/channel-closed";
112
+ retryable: false;
113
+ }>;
114
+ }>;
115
+ /**
116
+ * A server trace id from a host generator. TOTAL: a generator that
117
+ * throws or answers a non-string is replaced by the platform's UUID.
118
+ * @param {() => string} trace
119
+ * @returns {string}
120
+ */
121
+ export declare function safeTrace(trace: () => string): string;
122
+ /**
123
+ * Validate the assembled input against the operation's input validator.
124
+ * `null` when valid (or the operation validates nothing); a `JC2006`
125
+ * contract result otherwise, its `details` projected by
126
+ * `policy.errors.details` and its `cause` the validator's own throw when
127
+ * it had one.
128
+ * @param {PipelineRoute} route
129
+ * @param {unknown} input
130
+ * @returns {OperationResult | null}
131
+ */
132
+ export declare function validateOperationInput(route: PipelineRoute, input: unknown): OperationResult | null;
133
+ /**
134
+ * Call the handler through the uniform promise boundary and classify the
135
+ * settlement. Never rejects; the returned promise always resolves an
136
+ * {@link OperationResult}.
137
+ * @param {PipelineRoute} route
138
+ * @param {any} input - the validated input (`null` for an input-less operation)
139
+ * @param {any} ctx - the frozen per-request context the handler receives
140
+ * @param {boolean} validateOutput
141
+ * @returns {Promise<OperationResult>}
142
+ */
143
+ export declare function settleOperation(route: PipelineRoute, input: any, ctx: any, validateOutput: boolean): Promise<OperationResult>;
144
+ /**
145
+ * The whole neutral pipeline of one operation: validate the input, then
146
+ * call and classify — for a binding with nothing of its own between the
147
+ * two steps (the HTTP binding interposes its idempotency claim and calls
148
+ * the two halves itself).
149
+ * @param {PipelineRoute} route
150
+ * @param {any} input
151
+ * @param {any} ctx
152
+ * @param {{ validateOutput?: boolean }} [options]
153
+ * @returns {Promise<OperationResult>}
154
+ */
155
+ export declare function runOperation(route: PipelineRoute, input: any, ctx: any, options?: {
156
+ validateOutput?: boolean;
157
+ }): Promise<OperationResult>;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * @file `openPortClient(contract, options)`: the client half of the port
3
+ * binding (docs/CONTRACT-FORMAT.md §16). `invoke(op, input, ctx)`
4
+ * validates the input with the operation's compiled validator (a refusal
5
+ * is the `JC2050` outcome — nothing is posted), posts one request frame,
6
+ * and resolves a D6 outcome for every way a channel can answer: the
7
+ * matching response (success validated against the output schema,
8
+ * `JC2053`; a declared or taxonomy error a `failure` with `status:
9
+ * null`; a served-host fault `kind: "contract"` with the server's
10
+ * `JC2070`/`JC2071` code kept), no answer within `timeoutMs` (`JC2072`,
11
+ * network), a malformed response frame (`JC2073`, contract), a channel
12
+ * whose `postMessage` throws (`JC2074`, network), and a caller abort
13
+ * (`cancelled` — a cancel frame is also posted so the server can stop
14
+ * work; the id scoping is the guarantee, so a late response for a
15
+ * cancelled or timed-out id is dropped silently).
16
+ *
17
+ * Request ids are `"<clientId>:<seq>"` with a UUID client id, and every
18
+ * incoming frame is prefix-tested against `clientId + ":"` BEFORE any
19
+ * map lookup — two clients on one shared channel can never cross-settle,
20
+ * whatever the other one sends. `close()` rejects nothing: pending
21
+ * invokes resolve `cancelled` and the listener is removed (the channel
22
+ * itself is the host's to close).
23
+ */
24
+ import { PORT_LOCAL_ERRORS } from '../pipeline.js';
25
+ export { PORT_LOCAL_ERRORS };
26
+ export type Contract = import('../compile.js').Contract;
27
+ export type CompiledOperation = import('../compile.js').CompiledOperation;
28
+ export type Catalog = import('../http/wire.js').Catalog;
29
+ export type Outcome = import('../client/outcome.js').Outcome;
30
+ export type OutcomeMeta = import('../client/outcome.js').OutcomeMeta;
31
+ export type OutcomeRoute = import('../client/outcome.js').OutcomeRoute;
32
+ export type ChannelLike = import('./frame.js').ChannelLike;
33
+ export type PortClientOptions = {
34
+ /**
35
+ * - the channel to talk over (required)
36
+ */
37
+ channel: ChannelLike;
38
+ /**
39
+ * - per request; default 15000; `0` disables
40
+ */
41
+ timeoutMs?: number;
42
+ /**
43
+ * - a message catalog consulted before the English one
44
+ */
45
+ catalog?: Record<string, string | ((params: object) => string)>;
46
+ };
47
+ export type PortInvokeContext = {
48
+ /**
49
+ * - resolves the outcome `cancelled` and posts a cancel frame
50
+ */
51
+ signal?: AbortSignal;
52
+ /**
53
+ * - the caller's attempt id, echoed in `meta.attempt`, never sent
54
+ */
55
+ attempt?: unknown;
56
+ };
57
+ export type PortClientCapabilities = {
58
+ name: 'port';
59
+ status: false;
60
+ headers: false;
61
+ media: false;
62
+ etag: false;
63
+ idempotency: false;
64
+ /**
65
+ * - `subscribe` carries push-frame streams (docs/CONTRACT-FORMAT.md §18.2)
66
+ */
67
+ stream: true;
68
+ cancel: 'message';
69
+ };
70
+ export type PortSubscribeOptions = {
71
+ onSnapshot?: (value: unknown, info: {
72
+ seq: number;
73
+ resumed: boolean;
74
+ }) => void;
75
+ onPatch?: (emission: {
76
+ patch: unknown[];
77
+ seq: number;
78
+ }) => void;
79
+ onError?: (outcome: Outcome) => void;
80
+ onEnd?: (info: {
81
+ reason: string;
82
+ }) => void;
83
+ /**
84
+ * - stops the subscription silently
85
+ */
86
+ signal?: AbortSignal;
87
+ /**
88
+ * - the resume seq (what a reconnect passes)
89
+ */
90
+ lastSeq?: number;
91
+ };
92
+ export type PortClient = {
93
+ invoke: (op: string, input?: unknown, ctx?: PortInvokeContext) => Promise<Outcome>;
94
+ subscribe: (op: string, input?: unknown, options?: PortSubscribeOptions) => {
95
+ stop: () => void;
96
+ };
97
+ capabilities: PortClientCapabilities;
98
+ contract: Contract;
99
+ describe: () => any;
100
+ close: () => void;
101
+ };
102
+ export type PortRoute = {
103
+ op: CompiledOperation;
104
+ raw: boolean;
105
+ hasInput: boolean;
106
+ validateInput: ((value: unknown) => any) | null;
107
+ details: 'none' | 'paths' | 'full';
108
+ outcome: OutcomeRoute;
109
+ };
110
+ export type Pending = {
111
+ resolve: (outcome: Outcome) => void;
112
+ route: PortRoute;
113
+ meta: OutcomeMeta;
114
+ timer: ReturnType<typeof setTimeout> | 0;
115
+ /**
116
+ * - removes the abort listener
117
+ */
118
+ cleanup: () => void;
119
+ };
120
+ /**
121
+ * One pending request: what the listener needs to settle it.
122
+ * @typedef {Object} Pending
123
+ * @property {(outcome: Outcome) => void} resolve
124
+ * @property {PortRoute} route
125
+ * @property {OutcomeMeta} meta
126
+ * @property {ReturnType<typeof setTimeout> | 0} timer
127
+ * @property {() => void} cleanup - removes the abort listener
128
+ */
129
+ /**
130
+ * Open a port client over a compiled contract.
131
+ *
132
+ * @param {Contract} contract
133
+ * @param {PortClientOptions} options
134
+ * @returns {PortClient}
135
+ * @throws {ContractHostError} `JC1008` for a malformed argument or option
136
+ * @example
137
+ * const worker = new Worker(new URL('./owner.js', import.meta.url), { type: 'module' });
138
+ * const client = openPortClient(contract, { channel: worker });
139
+ * const outcome = await client.invoke('data.rows', { collection: 'notes' });
140
+ * if (outcome.ok) render(outcome.value); // outcome.error.status is null on this binding
141
+ */
142
+ export declare function openPortClient(contract: Contract, options: PortClientOptions): PortClient;