@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,334 @@
1
+ /**
2
+ * @file The wire shapes of the HTTP server binding: the request-time
3
+ * error taxonomy as data (code → status, msgid, retryable), the error
4
+ * body every non-2xx JSON response carries, header reading, media
5
+ * matching, entity-tag comparison and the query decoder. Nothing here
6
+ * calls a handler or touches a ledger; `dispatch.js` composes these.
7
+ *
8
+ * Every response header name is lowercase; every error response carries
9
+ * `x-jaren-trace` (the server trace, `requestId` in the body) and
10
+ * `cache-control: no-store`. A message is rendered from the msgid
11
+ * through the catalog and never interpolates a request value — the
12
+ * parameters are the operation id, a declared limit, a media type, a
13
+ * method list, a declared header name or a declared error code
14
+ * (docs/CONTRACT-FORMAT.md §7).
15
+ */
16
+ export type HttpRequest = {
17
+ method: string;
18
+ url: string;
19
+ headers: Readonly<Record<string, string | readonly string[]>>;
20
+ body: string | Uint8Array | null;
21
+ signal?: AbortSignal | null;
22
+ };
23
+ export type HttpResponse = {
24
+ status: number;
25
+ headers: Readonly<Record<string, string>>;
26
+ body: string | Uint8Array | null;
27
+ stream?: (sink: {
28
+ write: (chunk: string) => void;
29
+ end: () => void;
30
+ }) => (() => void);
31
+ };
32
+ export type WireErrorBody = {
33
+ /**
34
+ * - a `JC2xxx` code, or the declared error code
35
+ */
36
+ code: string;
37
+ message: string;
38
+ /**
39
+ * - the server trace (`x-jaren-trace`)
40
+ */
41
+ requestId: string;
42
+ details?: unknown;
43
+ retryable: boolean;
44
+ };
45
+ export type WireErrorRow = {
46
+ status: number;
47
+ msgid: string;
48
+ retryable: boolean;
49
+ };
50
+ /**
51
+ * A request as the binding sees it — what an adapter builds and what a
52
+ * test hands to `dispatch` directly. `url` is origin-less: the path plus
53
+ * an optional `?query`; header names are lowercase; a header value is a
54
+ * string, or an array of strings when the adapter can see repeated field
55
+ * lines; `body` is the bytes/text as received (`null` for none);
56
+ * `signal` is the request's abort signal when the host has one.
57
+ * @typedef {Object} HttpRequest
58
+ * @property {string} method
59
+ * @property {string} url
60
+ * @property {Readonly<Record<string, string | readonly string[]>>} headers
61
+ * @property {string | Uint8Array | null} body
62
+ * @property {AbortSignal | null} [signal]
63
+ */
64
+ /**
65
+ * A response as the binding answers it: a status, lowercase header names,
66
+ * and a body that is a string (JSON text), bytes (an opaque operation) or
67
+ * `null` (HEAD, 204, 304). A streaming response (a subscribe operation
68
+ * under `accept: text/event-stream`) carries `body: null` plus `stream`:
69
+ * the adapter writes the headers, then MUST call `stream` exactly once
70
+ * with its sink — the pump writes SSE text through `sink.write` and
71
+ * calls `sink.end()` when the stream terminates; the returned function
72
+ * stops the stream when the consumer cancels.
73
+ * @typedef {Object} HttpResponse
74
+ * @property {number} status
75
+ * @property {Readonly<Record<string, string>>} headers
76
+ * @property {string | Uint8Array | null} body
77
+ * @property {(sink: { write: (chunk: string) => void, end: () => void }) => (() => void)} [stream]
78
+ */
79
+ /**
80
+ * The D7 error body of every non-2xx JSON response.
81
+ * @typedef {Object} WireErrorBody
82
+ * @property {string} code - a `JC2xxx` code, or the declared error code
83
+ * @property {string} message
84
+ * @property {string} requestId - the server trace (`x-jaren-trace`)
85
+ * @property {unknown} [details]
86
+ * @property {boolean} retryable
87
+ */
88
+ /**
89
+ * The taxonomy row of one request-time code.
90
+ * @typedef {{ status: number, msgid: string, retryable: boolean }} WireErrorRow
91
+ */
92
+ /**
93
+ * The request-time taxonomy as data: code → `{ status, msgid, retryable }`.
94
+ * The normative table is docs/CONTRACT-FORMAT.md §7; a test holds the
95
+ * two equal, and equal to `CONTRACT_CODES` and the English catalog. The
96
+ * client binding assembles outcomes from exactly these statuses.
97
+ */
98
+ export declare const HTTP_ERRORS: Readonly<{
99
+ JC2001: Readonly<{
100
+ status: 404;
101
+ msgid: "contract/not-found";
102
+ retryable: false;
103
+ }>;
104
+ JC2002: Readonly<{
105
+ status: 405;
106
+ msgid: "contract/method-not-allowed";
107
+ retryable: false;
108
+ }>;
109
+ JC2003: Readonly<{
110
+ status: 413;
111
+ msgid: "contract/body-too-large";
112
+ retryable: false;
113
+ }>;
114
+ JC2004: Readonly<{
115
+ status: 415;
116
+ msgid: "contract/unsupported-media";
117
+ retryable: false;
118
+ }>;
119
+ JC2005: Readonly<{
120
+ status: 400;
121
+ msgid: "contract/malformed-json";
122
+ retryable: false;
123
+ }>;
124
+ JC2006: Readonly<{
125
+ status: 400;
126
+ msgid: "contract/invalid-input";
127
+ retryable: false;
128
+ }>;
129
+ JC2007: Readonly<{
130
+ status: 400;
131
+ msgid: "contract/idempotency-key-required";
132
+ retryable: false;
133
+ }>;
134
+ JC2008: Readonly<{
135
+ status: 500;
136
+ msgid: "contract/handler-failed";
137
+ retryable: false;
138
+ }>;
139
+ JC2009: Readonly<{
140
+ status: 409;
141
+ msgid: "contract/idempotency-conflict";
142
+ retryable: false;
143
+ }>;
144
+ JC2010: Readonly<{
145
+ status: 500;
146
+ msgid: "contract/invalid-output";
147
+ retryable: false;
148
+ }>;
149
+ JC2011: Readonly<{
150
+ status: 400;
151
+ msgid: "contract/malformed-path";
152
+ retryable: false;
153
+ }>;
154
+ JC2012: Readonly<{
155
+ status: 400;
156
+ msgid: "contract/malformed-query";
157
+ retryable: false;
158
+ }>;
159
+ JC2013: Readonly<{
160
+ status: 501;
161
+ msgid: "contract/not-implemented";
162
+ retryable: false;
163
+ }>;
164
+ JC2014: Readonly<{
165
+ status: 412;
166
+ msgid: "contract/precondition-failed";
167
+ retryable: false;
168
+ }>;
169
+ JC2015: Readonly<{
170
+ status: 400;
171
+ msgid: "contract/invalid-header";
172
+ retryable: false;
173
+ }>;
174
+ }>;
175
+ /** The msgid of a declared operation error that has no message of its own. */
176
+ export declare const HANDLER_ERROR_MSGID = "contract/handler-error";
177
+ /** The default well-known negotiation path, served by `serveHttp` and asked by the client. */
178
+ export declare const WELL_KNOWN_PATH = "/.well-known/jaren-contract";
179
+ /** The default JSON media of the binding. */
180
+ export declare const JSON_MEDIA = "application/json";
181
+ /** The response `content-type` of a JSON body. */
182
+ export declare const JSON_CONTENT_TYPE = "application/json; charset=utf-8";
183
+ export type Catalog = Readonly<Record<string, (params: object, error?: object) => string>>;
184
+ /**
185
+ * A compiled catalog: msgid → render.
186
+ * @typedef {Readonly<Record<string, (params: object, error?: object) => string>>} Catalog
187
+ */
188
+ /**
189
+ * Render a message: the host catalog first, the English catalog second,
190
+ * a fixed fallback last. TOTAL: a rendering closure that throws yields
191
+ * the fallback rather than escaping into the response path.
192
+ * @param {Catalog | null} catalog - the host catalog, or null for English only
193
+ * @param {string} msgid
194
+ * @param {Record<string, unknown>} params
195
+ * @returns {string}
196
+ */
197
+ export declare function renderMessage(catalog: Catalog | null, msgid: string, params: Record<string, unknown>): string;
198
+ /**
199
+ * The message of a declared operation error, as every server-side
200
+ * binding renders it: `contract/error/<code>` from the host catalog when
201
+ * it defines one, else the generic `contract/handler-error` — with the
202
+ * failure's own params plus `op` and `code`.
203
+ * @param {Catalog | null} catalog
204
+ * @param {string} op - the operation id
205
+ * @param {string} code - the declared error code
206
+ * @param {Readonly<Record<string, unknown>>} params
207
+ * @returns {string}
208
+ */
209
+ export declare function declaredMessage(catalog: Catalog | null, op: string, code: string, params: Readonly<Record<string, unknown>>): string;
210
+ /**
211
+ * The single string value of a header: a repeated field is combined with
212
+ * `, ` (RFC 9110 §5.3), an absent one is `undefined`. For the protocol
213
+ * headers the binding itself reads (`content-type`, `content-length`,
214
+ * `idempotency-key`, `if-match`, `if-none-match`).
215
+ * @param {Readonly<Record<string, string | readonly string[]>>} headers
216
+ * @param {string} name - lowercase
217
+ * @returns {string | undefined}
218
+ */
219
+ export declare function headerValue(headers: Readonly<Record<string, string | readonly string[]>>, name: string): string | undefined;
220
+ /**
221
+ * The declared `content-length` as a non-negative integer, or `-1` when
222
+ * absent or not a plain decimal number (a malformed value never blocks
223
+ * the read-length check that follows).
224
+ * @param {Readonly<Record<string, string | readonly string[]>>} headers
225
+ * @returns {number}
226
+ */
227
+ export declare function contentLength(headers: Readonly<Record<string, string | readonly string[]>>): number;
228
+ /**
229
+ * Whether a request `content-type` names the operation's media:
230
+ * type/subtype compared case-insensitively, parameters ignored, and a
231
+ * `+json` structured-syntax suffix accepted for `application/json`.
232
+ * @param {string | undefined} contentType
233
+ * @param {string} media - the operation's declared media
234
+ * @returns {boolean}
235
+ */
236
+ export declare function mediaMatches(contentType: string | undefined, media: string): boolean;
237
+ /**
238
+ * The byte length of a body — text measured as UTF-8. Cheap on the
239
+ * common path: a string shorter than the limit in code units cannot
240
+ * exceed it, and one longer than a third of the limit in code units is
241
+ * measured exactly only when it could.
242
+ * @param {string | Uint8Array} body
243
+ * @param {number} limit
244
+ * @returns {boolean} true when the body exceeds `limit` bytes
245
+ */
246
+ export declare function exceedsBytes(body: string | Uint8Array, limit: number): boolean;
247
+ /**
248
+ * Parse an `If-Match`/`If-None-Match` field into its opaque tags. `*` is
249
+ * reported as `null` in the list; a weak indicator is dropped
250
+ * (`W/"x"` → `x`) — the caller decides weak/strong comparison because a
251
+ * strong comparison must reject weak tags, which `weak[i]` records.
252
+ * @param {string} value
253
+ * @returns {{ any: boolean, tags: string[], weak: boolean[] }}
254
+ */
255
+ export declare function parseEntityTags(value: string): {
256
+ any: boolean;
257
+ tags: string[];
258
+ weak: boolean[];
259
+ };
260
+ /**
261
+ * Whether a conditional header matches the tag the handler armed.
262
+ * Weak comparison ignores the weak indicators (RFC 9110 §8.8.3.2);
263
+ * strong comparison requires both tags strong.
264
+ * @param {string} header - the raw `if-match`/`if-none-match` value
265
+ * @param {string} tag - the armed opaque tag
266
+ * @param {boolean} tagStrong - whether the armed tag is strong
267
+ * @param {boolean} strong - strong comparison
268
+ * @returns {boolean}
269
+ */
270
+ export declare function entityTagMatches(header: string, tag: string, tagStrong: boolean, strong: boolean): boolean;
271
+ /**
272
+ * The `etag` header value of an armed tag.
273
+ * @param {string} tag
274
+ * @param {boolean} strong
275
+ * @returns {string}
276
+ */
277
+ export declare function formatEntityTag(tag: string, strong: boolean): string;
278
+ /**
279
+ * Decode a query string into the declared members of an input object:
280
+ * only declared names are set (an undeclared key is never merged, so no
281
+ * request can smuggle a member); a `repeated` member collects every
282
+ * occurrence into an array, every other member is last-wins; a `+` is a
283
+ * space and escapes decode as `application/x-www-form-urlencoded`
284
+ * (`URLSearchParams`). Returns `false` when the query is not decodable
285
+ * (a malformed percent-escape or invalid UTF-8) — the `JC2012` case.
286
+ * @param {string} query - the part after `?`, possibly empty
287
+ * @param {ReadonlySet<string>} declared - the query member names
288
+ * @param {ReadonlySet<string>} repeated - the array-typed ones
289
+ * @param {Record<string, unknown>} out - the input object under assembly
290
+ * @returns {boolean} false when not decodable
291
+ */
292
+ export declare function decodeQuery(query: string, declared: ReadonlySet<string>, repeated: ReadonlySet<string>, out: Record<string, unknown>): boolean;
293
+ /**
294
+ * The verdict of a compiled validator under either contract: the
295
+ * default `{ valid, errors }` or a host-injected boolean validator.
296
+ * TOTAL: a validator that throws is a failed verdict carrying the throw.
297
+ * Shared by the server pipeline and the client.
298
+ * @param {(value: unknown) => any} validate
299
+ * @param {unknown} value
300
+ * @returns {{ valid: boolean, errors: any[], thrown: unknown }}
301
+ */
302
+ export declare function verdict(validate: (value: unknown) => any, value: unknown): {
303
+ valid: boolean;
304
+ errors: any[];
305
+ thrown: unknown;
306
+ };
307
+ /**
308
+ * The details member of a validation failure by policy: `none` → absent,
309
+ * `paths` → `[{ path, keyword }]`, `full` → the validator's own error
310
+ * records (`toJSON()` when it has one) with their `params`.
311
+ * @param {'none' | 'paths' | 'full'} policy
312
+ * @param {any[]} errors - the validator's error list
313
+ * @returns {unknown}
314
+ */
315
+ export declare function projectValidationDetails(policy: 'none' | 'paths' | 'full', errors: any[]): unknown;
316
+ /**
317
+ * Build the D7 error body and the response around it. `override` is the
318
+ * host's `errorBody` option: called with the wire record (the body plus
319
+ * `status`) and the request context; TOTAL — a projector that throws or
320
+ * returns a non-JSON value falls back to the D7 shape.
321
+ * @param {number} status
322
+ * @param {string} code
323
+ * @param {string} message
324
+ * @param {string} trace
325
+ * @param {unknown} details - `undefined` for none
326
+ * @param {boolean} retryable
327
+ * @param {Readonly<Record<string, string>> | null} extraHeaders - `allow`, `retry-after`, `etag`
328
+ * @param {((wire: WireErrorBody & { status: number }, ctx: any) => unknown) | null} override
329
+ * @param {any} ctx - the request context, or null before an operation matched
330
+ * @returns {HttpResponse}
331
+ */
332
+ export declare function errorResponse(status: number, code: string, message: string, trace: string, details: unknown, retryable: boolean, extraHeaders: Readonly<Record<string, string>> | null, override: ((wire: WireErrorBody & {
333
+ status: number;
334
+ }, ctx: any) => unknown) | null, ctx: any): HttpResponse;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @file Public surface of @jarenjs/contract: `compileContract` turns a
3
+ * `$contract` document into a frozen `Contract` (docs/CONTRACT-FORMAT.md);
4
+ * the error classes, the `JC` code table and the `ContractFailure`
5
+ * factory are what a host catches, reads and returns. The bindings live
6
+ * behind their own subpaths (`./http`, `./fetch`, `./node`, `./ledger`)
7
+ * so a consumer that only compiles never loads them. The path matcher is
8
+ * deliberately NOT exported — it is reached only through `contract.match`
9
+ * and `contract.allowed`.
10
+ */
11
+ export { compileContract } from './compile.js';
12
+ export { ContractCompileError, ContractRuntimeError, ContractHostError, ContractFailure, isContractFailure, CONTRACT_CODES, } from './errors.js';
13
+ export { contractMessagesEn, contractCatalogEn } from './messages.js';
14
+ export type Contract = import('./compile.js').Contract;
15
+ export type CompiledOperation = import('./compile.js').CompiledOperation;
16
+ export type CompiledHttp = import('./compile.js').CompiledHttp;
17
+ export type CompiledPolicy = import('./compile.js').CompiledPolicy;
18
+ export type CompiledInput = import('./compile.js').CompiledInput;
19
+ export type CompiledOutput = import('./compile.js').CompiledOutput;
20
+ export type CompiledErrorDecl = import('./compile.js').CompiledErrorDecl;
21
+ export type InputTransport = import('./compile.js').InputTransport;
22
+ export type CompileContractOptions = import('./compile.js').CompileContractOptions;
23
+ export type ContractDescription = import('./describe.js').ContractDescription;
24
+ export type OperationDescription = import('./describe.js').OperationDescription;
25
+ export type ContractFailureValue = import('./errors.js').ContractFailureValue;
26
+ /**
27
+ * @typedef {import('./compile.js').Contract} Contract
28
+ * @typedef {import('./compile.js').CompiledOperation} CompiledOperation
29
+ * @typedef {import('./compile.js').CompiledHttp} CompiledHttp
30
+ * @typedef {import('./compile.js').CompiledPolicy} CompiledPolicy
31
+ * @typedef {import('./compile.js').CompiledInput} CompiledInput
32
+ * @typedef {import('./compile.js').CompiledOutput} CompiledOutput
33
+ * @typedef {import('./compile.js').CompiledErrorDecl} CompiledErrorDecl
34
+ * @typedef {import('./compile.js').InputTransport} InputTransport
35
+ * @typedef {import('./compile.js').CompileContractOptions} CompileContractOptions
36
+ * @typedef {import('./describe.js').ContractDescription} ContractDescription
37
+ * @typedef {import('./describe.js').OperationDescription} OperationDescription
38
+ * @typedef {import('./errors.js').ContractFailureValue} ContractFailureValue
39
+ */
@@ -0,0 +1,207 @@
1
+ /**
2
+ * @file Idempotency as data: the ledger INTERFACE the http server
3
+ * binding calls when an operation declares `policy.idempotency`, a
4
+ * `createMemoryLedger` reference implementation for tests and
5
+ * single-process hosts, and the two documents a durable host opens with
6
+ * the rest of the suite — `idempotencyLedgerModel` (a `$model` 0.1
7
+ * document for `@jarenjs/db`) and `commandLifecycleFsm` (a `$fsm` 0.1
8
+ * document for `@jarenjs/flow`). Both are JSON only: this package never
9
+ * imports db or flow (docs/CONTRACT-FORMAT.md §8).
10
+ *
11
+ * The three identities stay apart here as everywhere: the idempotency
12
+ * KEY is the caller's (sent as `Idempotency-Key`, scoped by the host's
13
+ * `scope`), the request HASH is the binding's (SHA-256 over the RFC 8785
14
+ * canonical input), and the TRACE is never stored — a replay carries a
15
+ * fresh one.
16
+ */
17
+ export type LedgerRecord = {
18
+ /**
19
+ * - `"<op>|<scope>|<key>"`
20
+ */
21
+ id: string;
22
+ op: string;
23
+ scope: string;
24
+ key: string;
25
+ /**
26
+ * - lowercase hex SHA-256 over the canonical input
27
+ */
28
+ hash: string;
29
+ status: 'started' | 'committed' | 'failed';
30
+ /**
31
+ * - the stored `{ status, headers, body }`, or null
32
+ */
33
+ response: any;
34
+ /**
35
+ * - of a failed record; null otherwise
36
+ */
37
+ retryable: boolean | null;
38
+ /**
39
+ * - epoch ms
40
+ */
41
+ createdAt: number;
42
+ /**
43
+ * - epoch ms
44
+ */
45
+ updatedAt: number;
46
+ /**
47
+ * - epoch ms
48
+ */
49
+ expiresAt: number;
50
+ };
51
+ export type ClaimResult = {
52
+ state: 'new';
53
+ ref: unknown;
54
+ } | {
55
+ state: 'replay';
56
+ response: any;
57
+ } | {
58
+ state: 'in-progress';
59
+ } | {
60
+ state: 'mismatch';
61
+ };
62
+ export type Ledger = {
63
+ claim: (claim: {
64
+ op: string;
65
+ scope: string;
66
+ key: string;
67
+ hash: string;
68
+ now?: number;
69
+ }) => ClaimResult | Promise<ClaimResult>;
70
+ commit: (ref: unknown, response: any) => void | Promise<void>;
71
+ fail: (ref: unknown, retryable: boolean, response?: any) => void | Promise<void>;
72
+ lookup: (key: {
73
+ op: string;
74
+ scope: string;
75
+ key: string;
76
+ }) => LedgerRecord | null | Promise<LedgerRecord | null>;
77
+ };
78
+ /**
79
+ * The reference ledger over a `Map`: synchronous, single-process,
80
+ * expiring on `claim` (a record past `expiresAt` is dropped and the key
81
+ * is `new` again). `sweep()` drops every expired record — a host may
82
+ * call it on a timer.
83
+ * @param {{ ttlMs?: number, now?: () => number }} [options]
84
+ * @returns {Ledger & { sweep(): number, size: number }}
85
+ */
86
+ export declare function createMemoryLedger(options?: {
87
+ ttlMs?: number;
88
+ now?: () => number;
89
+ }): Ledger & {
90
+ sweep(): number;
91
+ size: number;
92
+ };
93
+ /**
94
+ * The `$model` 0.1 document of a durable ledger: one collection,
95
+ * `ledger`, keyed by `/id` (`"<op>|<scope>|<key>"`), indexed on
96
+ * `expiresAt` (the sweep) and `status` (the in-flight scan). A host
97
+ * opens it with `@jarenjs/db`'s `openStore` and implements the `Ledger`
98
+ * interface over the collection; the record shape is exactly what
99
+ * `createMemoryLedger` keeps.
100
+ */
101
+ export declare const idempotencyLedgerModel: Readonly<{
102
+ $model: "0.1";
103
+ collections: {
104
+ ledger: {
105
+ schema: {
106
+ type: string;
107
+ required: string[];
108
+ properties: {
109
+ id: {
110
+ type: string;
111
+ minLength: number;
112
+ };
113
+ op: {
114
+ type: string;
115
+ minLength: number;
116
+ };
117
+ scope: {
118
+ type: string;
119
+ };
120
+ key: {
121
+ type: string;
122
+ minLength: number;
123
+ };
124
+ hash: {
125
+ type: string;
126
+ pattern: string;
127
+ };
128
+ status: {
129
+ type: string;
130
+ enum: string[];
131
+ };
132
+ response: {
133
+ oneOf: ({
134
+ type: string;
135
+ required?: undefined;
136
+ properties?: undefined;
137
+ } | {
138
+ type: string;
139
+ required: string[];
140
+ properties: {
141
+ status: {
142
+ type: string;
143
+ minimum: number;
144
+ maximum: number;
145
+ };
146
+ headers: {
147
+ type: string;
148
+ additionalProperties: {
149
+ type: string;
150
+ };
151
+ };
152
+ body: {
153
+ type: string[];
154
+ };
155
+ };
156
+ })[];
157
+ };
158
+ retryable: {
159
+ type: string[];
160
+ };
161
+ createdAt: {
162
+ type: string;
163
+ };
164
+ updatedAt: {
165
+ type: string;
166
+ };
167
+ expiresAt: {
168
+ type: string;
169
+ };
170
+ };
171
+ additionalProperties: boolean;
172
+ };
173
+ key: string;
174
+ indexes: {
175
+ name: string;
176
+ path: string;
177
+ }[];
178
+ };
179
+ };
180
+ }>;
181
+ /**
182
+ * The `$fsm` 0.1 document of one command's lifecycle under an
183
+ * idempotency key: `idle → started` on `claim`, `started → committed` on
184
+ * `commit`, `started → failed` on `fail`, and `failed → started` on
185
+ * `claim` only when the failure was retryable (`$.context.retryable`).
186
+ * A host compiles it with `@jarenjs/flow` to drive or audit a durable
187
+ * ledger; the memory ledger walks exactly these transitions.
188
+ */
189
+ export declare const commandLifecycleFsm: Readonly<{
190
+ $fsm: "0.1";
191
+ initial: "idle";
192
+ states: (string | {
193
+ id: string;
194
+ final: boolean;
195
+ })[];
196
+ transitions: ({
197
+ from: string;
198
+ event: string;
199
+ to: string;
200
+ guard?: undefined;
201
+ } | {
202
+ from: string;
203
+ event: string;
204
+ guard: string;
205
+ to: string;
206
+ })[];
207
+ }>;