@substrat-run/contracts 0.87.0 → 0.89.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.
- package/dist/concurrency.d.ts +82 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +94 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/denial.d.ts +121 -0
- package/dist/denial.d.ts.map +1 -0
- package/dist/denial.js +117 -0
- package/dist/denial.js.map +1 -0
- package/dist/errors.d.ts +40 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +163 -14
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +78 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +57 -0
- package/dist/events.js.map +1 -1
- package/dist/idempotency.d.ts +139 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +161 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/openapi.d.ts +24 -0
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +81 -1
- package/dist/openapi.js.map +1 -1
- package/dist/operations.d.ts +283 -11
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +318 -1
- package/dist/operations.js.map +1 -1
- package/package.json +1 -1
package/dist/errors.js
CHANGED
|
@@ -34,6 +34,18 @@ import { entityRef } from './events.js';
|
|
|
34
34
|
* decision stays a one-line change here for exactly as long as that holds.
|
|
35
35
|
*/
|
|
36
36
|
export const PROBLEM_TYPE_BASE = 'https://substrat.net/errors';
|
|
37
|
+
/** What a problem body is served as. Never `application/json` — RFC 9457 §3. */
|
|
38
|
+
export const PROBLEM_CONTENT_TYPE = 'application/problem+json';
|
|
39
|
+
/**
|
|
40
|
+
* The `type` of a failure that has a status and nothing else.
|
|
41
|
+
*
|
|
42
|
+
* RFC 9457 §4.2.1: `about:blank` means "no semantics beyond the status code", and the
|
|
43
|
+
* title is then the status phrase. That is the honest shape for the two cases a
|
|
44
|
+
* transport cannot type — an untyped throw it refuses to call the platform's fault,
|
|
45
|
+
* and a downstream status it is relaying — and it is what keeps the closed taxonomy
|
|
46
|
+
* closed while every body still parses as a problem.
|
|
47
|
+
*/
|
|
48
|
+
export const PROBLEM_TYPE_BLANK = 'about:blank';
|
|
37
49
|
/**
|
|
38
50
|
* The taxonomy. CLOSED — an open one is a suggestion.
|
|
39
51
|
*
|
|
@@ -102,7 +114,19 @@ export const PROBLEM_EXTENSIONS = {
|
|
|
102
114
|
not_found: z.strictObject({}),
|
|
103
115
|
conflict: z.object({ reason: z.string().min(1).optional() }),
|
|
104
116
|
validation_failed: z.object({ errors: z.array(validationIssue).optional() }),
|
|
105
|
-
precondition_failed: z.
|
|
117
|
+
precondition_failed: z.object({
|
|
118
|
+
/**
|
|
119
|
+
* The entity whose version moved under the caller (#129).
|
|
120
|
+
*
|
|
121
|
+
* **The current version is deliberately NOT carried.** Handing it back turns
|
|
122
|
+
* the obvious client fix into a blind retry with the new tag, which writes
|
|
123
|
+
* over the change that caused the refusal — the exact lost update the
|
|
124
|
+
* precondition exists to prevent, now with a 412 in the log claiming it was
|
|
125
|
+
* prevented. A client that wants to proceed re-reads, and re-reading is what
|
|
126
|
+
* gives its user something to merge.
|
|
127
|
+
*/
|
|
128
|
+
entity: entityRef.optional(),
|
|
129
|
+
}),
|
|
106
130
|
rate_limited: z.object({ retryAfter: z.number().int().nonnegative().optional() }),
|
|
107
131
|
unavailable: z.strictObject({}),
|
|
108
132
|
internal: z.strictObject({}),
|
|
@@ -134,7 +158,22 @@ export const problem = z.object({
|
|
|
134
158
|
* non-event. Removed once the clients are moved, not "eventually".
|
|
135
159
|
*/
|
|
136
160
|
error: z.string().optional(),
|
|
137
|
-
|
|
161
|
+
/**
|
|
162
|
+
* The taxonomy entry this failure is an instance of.
|
|
163
|
+
*
|
|
164
|
+
* OPTIONAL, and its absence is information rather than an omission: it is present
|
|
165
|
+
* exactly when `type` names a registry entry, and absent exactly on the
|
|
166
|
+
* `about:blank` form below — the body a transport builds when a status is genuinely
|
|
167
|
+
* all it has (a throw nobody typed, a downstream's status relayed verbatim). RFC 9457
|
|
168
|
+
* §4.2.1 reserves `about:blank` for precisely that, and a client switching on `code`
|
|
169
|
+
* then falls through to its unknown branch instead of matching a fabricated one.
|
|
170
|
+
*
|
|
171
|
+
* The alternative was to invent a code per relayed status. That reads better in a
|
|
172
|
+
* schema and worse in production: `validation_failed` on a domain error nobody
|
|
173
|
+
* declared is a lie a client would act on, and the taxonomy is closed (§2) precisely
|
|
174
|
+
* so this is not where it grows.
|
|
175
|
+
*/
|
|
176
|
+
code: errorCode.optional(),
|
|
138
177
|
// -- declared extensions (see PROBLEM_EXTENSIONS) ---------------------------
|
|
139
178
|
permission: z.string().min(1).optional(),
|
|
140
179
|
entity: entityRef.optional(),
|
|
@@ -162,9 +201,11 @@ export const ERROR_NAME_PREFIX = 'Substrat.';
|
|
|
162
201
|
* Keeping `PermissionDenied` named `PermissionDenied` rather than renaming it to the
|
|
163
202
|
* generic form is deliberate: `vertical-host`'s classifier and several verticals match
|
|
164
203
|
* on that exact string today, and a rename would be a silent behaviour change bundled
|
|
165
|
-
* into a refactor. `ZodError` earns its row because a parse failure
|
|
166
|
-
*
|
|
167
|
-
* without fields still beats
|
|
204
|
+
* into a refactor. `ZodError` earns its row because a parse failure can arrive with its
|
|
205
|
+
* prototype gone and only its `name` left — a duplicate copy of zod, a structured clone,
|
|
206
|
+
* the legacy pre-envelope RPC path. `validation_failed` without fields still beats
|
|
207
|
+
* `internal`. On the envelope path the fields are no longer lost: `toWireFailure` carries
|
|
208
|
+
* them as `extensions.errors` (#831).
|
|
168
209
|
*/
|
|
169
210
|
const CODE_BY_ERROR_NAME = {
|
|
170
211
|
PermissionDenied: 'permission_denied',
|
|
@@ -236,11 +277,29 @@ export function isSubstratError(err) {
|
|
|
236
277
|
}
|
|
237
278
|
/** Zod's issue list, flattened to the wire shape. */
|
|
238
279
|
export function validationIssuesFrom(error) {
|
|
239
|
-
return error.issues
|
|
240
|
-
|
|
241
|
-
|
|
280
|
+
return flattenIssues(error.issues);
|
|
281
|
+
}
|
|
282
|
+
function flattenIssues(issues) {
|
|
283
|
+
return issues.map((issue) => ({
|
|
284
|
+
path: (issue.path ?? []).map(String).join('.'),
|
|
285
|
+
message: typeof issue.message === 'string' ? issue.message : String(issue.message),
|
|
242
286
|
}));
|
|
243
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* A parse failure's field issues, read BY SHAPE rather than by `instanceof`.
|
|
290
|
+
*
|
|
291
|
+
* Same doctrine as `errorCodeOf` one screen up, for the same two reasons: two copies
|
|
292
|
+
* of zod in one build make `instanceof` a coin toss, and `vertical-host`'s classifier
|
|
293
|
+
* already reads a parse failure this way (`isParseFailure`). `issues` is zod's own
|
|
294
|
+
* array and nothing else on these paths carries one.
|
|
295
|
+
*
|
|
296
|
+
* Returns `undefined` — not `[]` — for a throw that is not a parse failure, so a
|
|
297
|
+
* caller can tell "no issues to report" from "not that kind of error at all".
|
|
298
|
+
*/
|
|
299
|
+
function parseIssuesOf(err) {
|
|
300
|
+
const issues = err?.issues;
|
|
301
|
+
return Array.isArray(issues) ? flattenIssues(issues) : undefined;
|
|
302
|
+
}
|
|
244
303
|
/**
|
|
245
304
|
* Map any throw onto a problem body and its status — the one function replacing every
|
|
246
305
|
* hand-rolled `onError` and the control plane's regex table.
|
|
@@ -251,12 +310,27 @@ export function validationIssuesFrom(error) {
|
|
|
251
310
|
* quietly widening it in the name of better errors.
|
|
252
311
|
*/
|
|
253
312
|
export function toProblem(err, instance) {
|
|
254
|
-
if (err instanceof z.ZodError) {
|
|
255
|
-
return build('validation_failed', 'the input did not parse', instance, {
|
|
256
|
-
errors: validationIssuesFrom(err),
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
313
|
const code = errorCodeOf(err);
|
|
314
|
+
if (code === 'validation_failed') {
|
|
315
|
+
// #831. The issues are the whole value of a parse failure, and they reach here two
|
|
316
|
+
// ways: live on the throw (in-process), or in `extensions.errors` once
|
|
317
|
+
// `toWireFailure` carried them across the ScopeDO hop.
|
|
318
|
+
//
|
|
319
|
+
// Only a PARSE failure takes this branch, and the `errors` list is what identifies
|
|
320
|
+
// one. `validation_failed` is also thrown SEMANTICALLY — `endDate precedes
|
|
321
|
+
// startDate`, `invalid interval`, `at most one party may sign as primary` — where
|
|
322
|
+
// the sentence IS the information and no field list exists. Those fall through to
|
|
323
|
+
// the general branch below and keep their own message, exactly as before.
|
|
324
|
+
const carried = err?.extensions?.errors;
|
|
325
|
+
const errors = parseIssuesOf(err) ?? (Array.isArray(carried) ? carried : undefined);
|
|
326
|
+
if (errors !== undefined) {
|
|
327
|
+
// The detail is the canonical sentence rather than the throw's message: a raw
|
|
328
|
+
// `ZodError` stringifies its whole issue list into `message` as JSON, and echoing
|
|
329
|
+
// that beside the parsed `errors` array publishes the same thing twice — in the
|
|
330
|
+
// shape this change exists to stop clients re-parsing.
|
|
331
|
+
return build('validation_failed', 'the input did not parse', instance, { errors });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
260
334
|
if (code !== undefined && err instanceof Error) {
|
|
261
335
|
// `internal` is still generic even when a throw asked for it by name: the rule is
|
|
262
336
|
// about what reaches a client, not about who chose the code.
|
|
@@ -279,6 +353,59 @@ function build(code, detail, instance, extensions = {}) {
|
|
|
279
353
|
...extensions,
|
|
280
354
|
});
|
|
281
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* The title a degraded body wears — the HTTP status phrase, per RFC 9457 §4.2.1.
|
|
358
|
+
*
|
|
359
|
+
* Only the statuses this platform actually answers with. An unlisted one is not a gap
|
|
360
|
+
* to fill defensively: it gets the class-wide phrase below, which is exactly as much as
|
|
361
|
+
* `about:blank` claims to know.
|
|
362
|
+
*/
|
|
363
|
+
const STATUS_TITLES = {
|
|
364
|
+
400: 'Bad request',
|
|
365
|
+
401: 'Unauthorized',
|
|
366
|
+
403: 'Forbidden',
|
|
367
|
+
404: 'Not found',
|
|
368
|
+
405: 'Method not allowed',
|
|
369
|
+
409: 'Conflict',
|
|
370
|
+
412: 'Precondition failed',
|
|
371
|
+
415: 'Unsupported media type',
|
|
372
|
+
422: 'Unprocessable content',
|
|
373
|
+
429: 'Too many requests',
|
|
374
|
+
500: 'Internal error',
|
|
375
|
+
501: 'Not implemented',
|
|
376
|
+
502: 'Bad gateway',
|
|
377
|
+
503: 'Service unavailable',
|
|
378
|
+
504: 'Gateway timeout',
|
|
379
|
+
};
|
|
380
|
+
/**
|
|
381
|
+
* A problem body for a status and nothing else — the `about:blank` form.
|
|
382
|
+
*
|
|
383
|
+
* Two callers, both transports, both relaying rather than raising:
|
|
384
|
+
*
|
|
385
|
+
* - **A throw the taxonomy does not recognise.** Every vertical answers one with the
|
|
386
|
+
* caller's 400 and relays the message, deliberately (#559: an unrecognised throw must
|
|
387
|
+
* not claim to be the platform's fault, because the control plane retries 5xx). That
|
|
388
|
+
* status is a decision about blame, not a claim about what went wrong, and this is the
|
|
389
|
+
* body that says so.
|
|
390
|
+
* - **A status raised somewhere else.** A downstream vertical's own refusal, a Durable
|
|
391
|
+
* Object fault the runtime named (502). Inventing a code for those would put our
|
|
392
|
+
* vocabulary on someone else's failure.
|
|
393
|
+
*
|
|
394
|
+
* `detail` is carried as the caller passes it. That is safe here and not in `toProblem`
|
|
395
|
+
* because a caller of THIS function has a status it chose or received, which means it
|
|
396
|
+
* has already looked at what it is relaying; `toProblem`'s `internal` branch is the one
|
|
397
|
+
* holding an unreviewed message, and it still refuses to disclose it.
|
|
398
|
+
*/
|
|
399
|
+
export function problemForStatus(status, detail, instance) {
|
|
400
|
+
const title = STATUS_TITLES[status] ?? (status >= 500 ? 'Server error' : 'Request failed');
|
|
401
|
+
return problem.parse({
|
|
402
|
+
type: PROBLEM_TYPE_BLANK,
|
|
403
|
+
title,
|
|
404
|
+
status,
|
|
405
|
+
...(detail === undefined ? {} : { detail, error: detail }),
|
|
406
|
+
...(instance === undefined ? {} : { instance }),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
282
409
|
/**
|
|
283
410
|
* The statuses an operation can actually answer with today, for the emitted document.
|
|
284
411
|
*
|
|
@@ -329,9 +456,31 @@ export function toWireFailure(err) {
|
|
|
329
456
|
name: err.name,
|
|
330
457
|
message: err.message,
|
|
331
458
|
code,
|
|
332
|
-
extensions:
|
|
459
|
+
extensions: extensionsFor(code, err),
|
|
333
460
|
};
|
|
334
461
|
}
|
|
462
|
+
/**
|
|
463
|
+
* The extensions a throw carries onto the wire.
|
|
464
|
+
*
|
|
465
|
+
* A `SubstratError` already holds its own, declared and parsed at the throw site. A
|
|
466
|
+
* `ZodError` holds none — it holds `issues`, which is the same information in zod's
|
|
467
|
+
* shape rather than ours, and #831's whole complaint was that this function dropped it:
|
|
468
|
+
* the field list survived only as JSON inside `message`, leaving every vertical to
|
|
469
|
+
* re-parse a string for what `validationIssue` already models.
|
|
470
|
+
*
|
|
471
|
+
* That mattered more after #893 than before it. The host now parses a declared
|
|
472
|
+
* operation input at the scope door, so on the hosted path the refusal is raised
|
|
473
|
+
* INSIDE the ScopeDO and this is the only seam it crosses — while the same operation
|
|
474
|
+
* under `adapter-sqlite` throws in-process with `issues` intact. Structured in a
|
|
475
|
+
* scenario test, bare in production, is the worst of the two available failures.
|
|
476
|
+
*/
|
|
477
|
+
function extensionsFor(code, err) {
|
|
478
|
+
const declared = { ...(err.extensions ?? {}) };
|
|
479
|
+
if (code !== 'validation_failed' || declared.errors !== undefined)
|
|
480
|
+
return declared;
|
|
481
|
+
const issues = parseIssuesOf(err);
|
|
482
|
+
return issues === undefined ? declared : { ...declared, errors: issues };
|
|
483
|
+
}
|
|
335
484
|
/**
|
|
336
485
|
* Rebuild a throw from the wire.
|
|
337
486
|
*
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/events.d.ts
CHANGED
|
@@ -95,4 +95,82 @@ export declare const domainEvent: z.ZodObject<{
|
|
|
95
95
|
payload: z.ZodUnknown;
|
|
96
96
|
}, z.core.$strip>;
|
|
97
97
|
export type DomainEvent = z.infer<typeof domainEvent>;
|
|
98
|
+
/**
|
|
99
|
+
* One entry of an entity's TIMELINE — the envelope of an event about it, and
|
|
100
|
+
* nothing that was said (#800).
|
|
101
|
+
*
|
|
102
|
+
* Five demos hand-wrote the `SELECT` behind this and all five published a
|
|
103
|
+
* different shape for it, which is the small half of the problem. The large half
|
|
104
|
+
* is that two of the four fields are not what a reader of `_substrat_outbox`
|
|
105
|
+
* assumes:
|
|
106
|
+
*
|
|
107
|
+
* - **`actor` is the union, not an id.** The writer persists
|
|
108
|
+
* `JSON.stringify(actor)`, so a principal is stored WITH its quotes and a
|
|
109
|
+
* system or connector actor is stored as an object. `SELECT actor` returns a
|
|
110
|
+
* string that looks usable and is not — an agent building a timeline hit this
|
|
111
|
+
* as a real bug and had to read the adapter to find it. Here the column is
|
|
112
|
+
* decoded once, so a caller resolving a name gets the union the spine actually
|
|
113
|
+
* recorded rather than a string to trim quotes off.
|
|
114
|
+
* - **`id` is the entity's VERSION at this point** (#901), not just a row key.
|
|
115
|
+
* The same token `versionOf` returns and `If-Match` compares, so "list the
|
|
116
|
+
* history", "restore to this version" and "refuse my stale write" speak one
|
|
117
|
+
* vocabulary. It is therefore the cursor: `ORDER BY id` is creation order
|
|
118
|
+
* because `ulid()` is monotonic, and `OUTBOX_ENTITY_INDEX` makes the walk a
|
|
119
|
+
* seek.
|
|
120
|
+
*/
|
|
121
|
+
export declare const timelineEntry: z.ZodObject<{
|
|
122
|
+
id: z.core.$ZodBranded<z.ZodString, "EventId", "out">;
|
|
123
|
+
type: z.ZodString;
|
|
124
|
+
occurredAt: z.core.$ZodBranded<z.ZodString, "Instant", "out">;
|
|
125
|
+
actor: z.ZodUnion<readonly [z.core.$ZodBranded<z.ZodString, "PrincipalId", "out">, z.ZodObject<{
|
|
126
|
+
system: z.core.$ZodBranded<z.ZodString, "ModuleId", "out">;
|
|
127
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
128
|
+
connection: z.ZodString;
|
|
129
|
+
}, z.core.$strip>]>;
|
|
130
|
+
}, z.core.$strip>;
|
|
131
|
+
export type TimelineEntry = z.infer<typeof timelineEntry>;
|
|
132
|
+
/**
|
|
133
|
+
* A timeline entry plus what a history VIEW needs — the second layer of #800.
|
|
134
|
+
*
|
|
135
|
+
* `timelineEntry` answers *Anna touched this at 14:02*. A history strip has to
|
|
136
|
+
* answer *Anna changed Status from Lead to Customer*, and the outbox already
|
|
137
|
+
* holds the rest of that. Two of these fields have a nullable that is a fact
|
|
138
|
+
* rather than a gap:
|
|
139
|
+
*
|
|
140
|
+
* - **`payload` is null after an erasure.** A shred nulls the payload and keeps
|
|
141
|
+
* the row (§5.3: "pseudonymous keys and transaction facts remain"), so a
|
|
142
|
+
* history correctly degrades to "someone changed this, then". A renderer must
|
|
143
|
+
* expect the null; it is a supported result, not an error.
|
|
144
|
+
* - **`authorization` is null when UNRECORDED** — a row written before K-34
|
|
145
|
+
* added the column — which is a different fact from an empty list (checked
|
|
146
|
+
* nothing). Keeping them distinct is the whole reason the column is nullable
|
|
147
|
+
* in the DDL.
|
|
148
|
+
*
|
|
149
|
+
* Field-level "X → Y" is reconstructed by diffing consecutive payloads: nothing
|
|
150
|
+
* stores a before-state. For the few fields a history strip actually shows
|
|
151
|
+
* (status, owner, value), emitting the previous value explicitly in the fat
|
|
152
|
+
* payload is more honest than making every reader diff — a per-vertical call.
|
|
153
|
+
*/
|
|
154
|
+
export declare const historyEntry: z.ZodObject<{
|
|
155
|
+
id: z.core.$ZodBranded<z.ZodString, "EventId", "out">;
|
|
156
|
+
type: z.ZodString;
|
|
157
|
+
occurredAt: z.core.$ZodBranded<z.ZodString, "Instant", "out">;
|
|
158
|
+
actor: z.ZodUnion<readonly [z.core.$ZodBranded<z.ZodString, "PrincipalId", "out">, z.ZodObject<{
|
|
159
|
+
system: z.core.$ZodBranded<z.ZodString, "ModuleId", "out">;
|
|
160
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
161
|
+
connection: z.ZodString;
|
|
162
|
+
}, z.core.$strip>]>;
|
|
163
|
+
payload: z.ZodUnknown;
|
|
164
|
+
authorization: z.ZodNullable<z.ZodArray<z.ZodObject<{
|
|
165
|
+
permission: z.core.$ZodBranded<z.ZodString, "PermissionKey", "out">;
|
|
166
|
+
grant: z.ZodOptional<z.ZodString>;
|
|
167
|
+
}, z.core.$strip>>>;
|
|
168
|
+
piiClass: z.ZodEnum<{
|
|
169
|
+
direct: "direct";
|
|
170
|
+
none: "none";
|
|
171
|
+
pseudonymous: "pseudonymous";
|
|
172
|
+
}>;
|
|
173
|
+
subjectId: z.ZodNullable<z.core.$ZodBranded<z.ZodString, "DataSubjectId", "out">>;
|
|
174
|
+
}, z.core.$strip>;
|
|
175
|
+
export type HistoryEntry = z.infer<typeof historyEntry>;
|
|
98
176
|
//# sourceMappingURL=events.d.ts.map
|
package/dist/events.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB,eAAO,MAAM,SAAS;;;iBAGpB,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAIlD,eAAO,MAAM,QAAQ;;;;EAA6C,CAAC;AACnE,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAGhD,eAAO,MAAM,SAAS,aAA+C,CAAC;AAEtE,eAAO,MAAM,WAAW;;iBAAiC,CAAC;AAC1D;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc;;iBAA8C,CAAC;AAC1E,eAAO,MAAM,KAAK;;;;mBAAsD,CAAC;AACzE,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,CAAC;AAE1C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB;;;iBAM7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAiBpE,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;iBASD,CAAC;AAC7B,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAGhE,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiBI,CAAC;AAC7B,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB,eAAO,MAAM,SAAS;;;iBAGpB,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAIlD,eAAO,MAAM,QAAQ;;;;EAA6C,CAAC;AACnE,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAGhD,eAAO,MAAM,SAAS,aAA+C,CAAC;AAEtE,eAAO,MAAM,WAAW;;iBAAiC,CAAC;AAC1D;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc;;iBAA8C,CAAC;AAC1E,eAAO,MAAM,KAAK;;;;mBAAsD,CAAC;AACzE,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,CAAC;AAE1C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB;;;iBAM7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAiBpE,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;iBASD,CAAC;AAC7B,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAGhE,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiBI,CAAC;AAC7B,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,aAAa;;;;;;;;;iBAKxB,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;iBAKvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC"}
|
package/dist/events.js
CHANGED
|
@@ -82,4 +82,61 @@ export const domainEvent = z
|
|
|
82
82
|
payload: z.unknown(),
|
|
83
83
|
})
|
|
84
84
|
.superRefine(piiInvariant);
|
|
85
|
+
/**
|
|
86
|
+
* One entry of an entity's TIMELINE — the envelope of an event about it, and
|
|
87
|
+
* nothing that was said (#800).
|
|
88
|
+
*
|
|
89
|
+
* Five demos hand-wrote the `SELECT` behind this and all five published a
|
|
90
|
+
* different shape for it, which is the small half of the problem. The large half
|
|
91
|
+
* is that two of the four fields are not what a reader of `_substrat_outbox`
|
|
92
|
+
* assumes:
|
|
93
|
+
*
|
|
94
|
+
* - **`actor` is the union, not an id.** The writer persists
|
|
95
|
+
* `JSON.stringify(actor)`, so a principal is stored WITH its quotes and a
|
|
96
|
+
* system or connector actor is stored as an object. `SELECT actor` returns a
|
|
97
|
+
* string that looks usable and is not — an agent building a timeline hit this
|
|
98
|
+
* as a real bug and had to read the adapter to find it. Here the column is
|
|
99
|
+
* decoded once, so a caller resolving a name gets the union the spine actually
|
|
100
|
+
* recorded rather than a string to trim quotes off.
|
|
101
|
+
* - **`id` is the entity's VERSION at this point** (#901), not just a row key.
|
|
102
|
+
* The same token `versionOf` returns and `If-Match` compares, so "list the
|
|
103
|
+
* history", "restore to this version" and "refuse my stale write" speak one
|
|
104
|
+
* vocabulary. It is therefore the cursor: `ORDER BY id` is creation order
|
|
105
|
+
* because `ulid()` is monotonic, and `OUTBOX_ENTITY_INDEX` makes the walk a
|
|
106
|
+
* seek.
|
|
107
|
+
*/
|
|
108
|
+
export const timelineEntry = z.object({
|
|
109
|
+
id: eventId,
|
|
110
|
+
type: eventType,
|
|
111
|
+
occurredAt: instant,
|
|
112
|
+
actor,
|
|
113
|
+
});
|
|
114
|
+
/**
|
|
115
|
+
* A timeline entry plus what a history VIEW needs — the second layer of #800.
|
|
116
|
+
*
|
|
117
|
+
* `timelineEntry` answers *Anna touched this at 14:02*. A history strip has to
|
|
118
|
+
* answer *Anna changed Status from Lead to Customer*, and the outbox already
|
|
119
|
+
* holds the rest of that. Two of these fields have a nullable that is a fact
|
|
120
|
+
* rather than a gap:
|
|
121
|
+
*
|
|
122
|
+
* - **`payload` is null after an erasure.** A shred nulls the payload and keeps
|
|
123
|
+
* the row (§5.3: "pseudonymous keys and transaction facts remain"), so a
|
|
124
|
+
* history correctly degrades to "someone changed this, then". A renderer must
|
|
125
|
+
* expect the null; it is a supported result, not an error.
|
|
126
|
+
* - **`authorization` is null when UNRECORDED** — a row written before K-34
|
|
127
|
+
* added the column — which is a different fact from an empty list (checked
|
|
128
|
+
* nothing). Keeping them distinct is the whole reason the column is nullable
|
|
129
|
+
* in the DDL.
|
|
130
|
+
*
|
|
131
|
+
* Field-level "X → Y" is reconstructed by diffing consecutive payloads: nothing
|
|
132
|
+
* stores a before-state. For the few fields a history strip actually shows
|
|
133
|
+
* (status, owner, value), emitting the previous value explicitly in the fat
|
|
134
|
+
* payload is more honest than making every reader diff — a per-vertical call.
|
|
135
|
+
*/
|
|
136
|
+
export const historyEntry = timelineEntry.extend({
|
|
137
|
+
payload: z.unknown(),
|
|
138
|
+
authorization: z.array(eventAuthorization).nullable(),
|
|
139
|
+
piiClass,
|
|
140
|
+
subjectId: dataSubjectId.nullable(),
|
|
141
|
+
});
|
|
85
142
|
//# sourceMappingURL=events.js.map
|
package/dist/events.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request idempotency on the operation surface (#116).
|
|
3
|
+
*
|
|
4
|
+
* A client whose request times out does not know whether the work happened. It
|
|
5
|
+
* retries, and a second work order exists. This is the vocabulary that lets the
|
|
6
|
+
* retry return the FIRST response instead of doing the work twice.
|
|
7
|
+
*
|
|
8
|
+
* ## This is not the event spine's idempotency
|
|
9
|
+
*
|
|
10
|
+
* Consumers have been required-idempotent since the beginning and the contract
|
|
11
|
+
* suite checks it: a consumer may see an event more than once and must settle to
|
|
12
|
+
* the same state. That is about the spine re-delivering. This is about a CLIENT
|
|
13
|
+
* re-sending — a different boundary, a different actor, and no relationship
|
|
14
|
+
* between the two beyond the word.
|
|
15
|
+
*
|
|
16
|
+
* ## Why the wire half lives here and the rest does not
|
|
17
|
+
*
|
|
18
|
+
* The same split `concurrency.ts` makes one file over: contracts sits below the
|
|
19
|
+
* spine, so it can say what the header is called, what makes a key well-formed
|
|
20
|
+
* and what makes two requests the same request. It must not know which table
|
|
21
|
+
* remembers the answer. `@substrat-run/kernel`'s `idempotency.ts` owns that, and
|
|
22
|
+
* the adapters own where it runs.
|
|
23
|
+
*
|
|
24
|
+
* ## The property that makes this cheap
|
|
25
|
+
*
|
|
26
|
+
* Invokes are serialised per scope in both adapters (`rt.actor.enqueue`, the
|
|
27
|
+
* ScopeDO's queue), so a duplicate cannot overlap the original — by the time the
|
|
28
|
+
* retry takes its turn, the first request has committed or rolled back. Every
|
|
29
|
+
* other implementation of this feature needs an in-flight state and a 409 for
|
|
30
|
+
* "still running"; this one does not, and the reason is a property of the host
|
|
31
|
+
* rather than an accident worth relying on quietly.
|
|
32
|
+
*/
|
|
33
|
+
/** The request header carrying the client's retry token. */
|
|
34
|
+
export declare const IDEMPOTENCY_KEY_HEADER = "Idempotency-Key";
|
|
35
|
+
/**
|
|
36
|
+
* Set on a response the server did not compute — it replayed a recorded one.
|
|
37
|
+
*
|
|
38
|
+
* Advisory, and worth having anyway: without it a retry is indistinguishable
|
|
39
|
+
* from a first request that happened to succeed, which makes "did my key work?"
|
|
40
|
+
* unanswerable from the client side and turns every integration test of a retry
|
|
41
|
+
* path into a database query.
|
|
42
|
+
*/
|
|
43
|
+
export declare const IDEMPOTENCY_REPLAYED_HEADER = "Idempotency-Replayed";
|
|
44
|
+
/**
|
|
45
|
+
* The header a cross-origin browser client cannot read unless the server says it
|
|
46
|
+
* may — the same trap `PAGE_EXPOSED_HEADERS` and `CONCURRENCY_EXPOSED_HEADERS`
|
|
47
|
+
* document, and the mildest of the three: an unexposed `Idempotency-Replayed`
|
|
48
|
+
* costs a client an observation, never a guarantee. The dedupe still happened.
|
|
49
|
+
*/
|
|
50
|
+
export declare const IDEMPOTENCY_EXPOSED_HEADERS: readonly ["Idempotency-Replayed"];
|
|
51
|
+
/**
|
|
52
|
+
* How long a key is remembered: 24 hours.
|
|
53
|
+
*
|
|
54
|
+
* Long enough to cover the retries anything sane performs — an agent's backoff,
|
|
55
|
+
* a queue's redelivery, a person reloading a page that failed — and short enough
|
|
56
|
+
* that the recorded responses are a cache rather than an archive.
|
|
57
|
+
*
|
|
58
|
+
* The window is not only a storage bound, and the other reason is the one worth
|
|
59
|
+
* writing down: a recorded response is a SECOND COPY of whatever the operation
|
|
60
|
+
* returned, sitting in the scope database outside the erasure path that reaches
|
|
61
|
+
* the outbox (a shred nulls `payload` and keeps the row; it does not know about
|
|
62
|
+
* this table). A copy that expires in a day is defensible. One that expires in a
|
|
63
|
+
* quarter is a disclosure nobody declared, and one that never expires is a second
|
|
64
|
+
* database of personal data with no owner.
|
|
65
|
+
*/
|
|
66
|
+
export declare const IDEMPOTENCY_RETENTION_MS: number;
|
|
67
|
+
/**
|
|
68
|
+
* The largest result that is recorded for replay: 128 KiB of JSON.
|
|
69
|
+
*
|
|
70
|
+
* Above it the key is still recorded — with no body — and a replay is REFUSED
|
|
71
|
+
* rather than re-executed. That is the fail-closed direction: refusing a retry
|
|
72
|
+
* costs a caller an error it can act on, while re-running the operation is the
|
|
73
|
+
* duplicate this feature exists to prevent, arrived at through the feature
|
|
74
|
+
* itself. Writes return entity-shaped results and do not approach this; a list
|
|
75
|
+
* read might, and a list read never carries a key (the mount forwards the header
|
|
76
|
+
* on unsafe methods only).
|
|
77
|
+
*/
|
|
78
|
+
export declare const IDEMPOTENCY_RESULT_LIMIT: number;
|
|
79
|
+
/** Longest key accepted, matching the IETF draft's guidance for the header. */
|
|
80
|
+
export declare const IDEMPOTENCY_KEY_MAX_LENGTH = 255;
|
|
81
|
+
/**
|
|
82
|
+
* Is this a key we will store and compare?
|
|
83
|
+
*
|
|
84
|
+
* Visible ASCII, bounded, non-empty. Deliberately permissive about STRUCTURE — a
|
|
85
|
+
* UUID is the convention and this refuses to require one, because a client whose
|
|
86
|
+
* natural key is an order number should not have to hash it into a shape we
|
|
87
|
+
* prefer. What it refuses is a key that would make the table a place to put
|
|
88
|
+
* things: control characters, whitespace, and anything unbounded.
|
|
89
|
+
*
|
|
90
|
+
* The key is never interpreted. It is compared, and it is scoped to the subject
|
|
91
|
+
* that sent it, so one client's choice of key cannot collide with another's.
|
|
92
|
+
*/
|
|
93
|
+
export declare function isValidIdempotencyKey(key: string): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Deterministic JSON — the same value serialises to the same string, whatever
|
|
96
|
+
* order its keys arrived in.
|
|
97
|
+
*
|
|
98
|
+
* `JSON.stringify` preserves insertion order, so `{a:1,b:2}` and `{b:2,a:1}`
|
|
99
|
+
* produce different text for the same request. A fingerprint built on that would
|
|
100
|
+
* call a retry a different request roughly whenever a client rebuilt its body
|
|
101
|
+
* from a map — which is to say, unpredictably, and in production rather than in
|
|
102
|
+
* a test.
|
|
103
|
+
*
|
|
104
|
+
* Arrays keep their order, because in a request body order IS meaning (the
|
|
105
|
+
* second line item is not the first). Only object keys are sorted.
|
|
106
|
+
*/
|
|
107
|
+
export declare function canonicalJson(value: unknown): string;
|
|
108
|
+
/**
|
|
109
|
+
* What makes two requests the same request: the operation and its PARSED input.
|
|
110
|
+
*
|
|
111
|
+
* **Parsed, not raw**, and that is load-bearing. The host parses every invocation
|
|
112
|
+
* against the operation's declared schema before anything else runs, which
|
|
113
|
+
* applies defaults — so a retry that omits an optional field the original sent
|
|
114
|
+
* explicitly at its default value is the same request, and fingerprinting the raw
|
|
115
|
+
* body would call it a different one and refuse the retry with a 409.
|
|
116
|
+
*
|
|
117
|
+
* The operation name is inside the hash rather than beside it in the key, so a
|
|
118
|
+
* client reusing one key for two different operations is a MISMATCH (409) rather
|
|
119
|
+
* than two independent records. A key is a client's assertion that "this is the
|
|
120
|
+
* same request I sent before"; two operations is the clearest possible case of
|
|
121
|
+
* that assertion being false, and silently honouring it would replay one
|
|
122
|
+
* operation's response for another one's call.
|
|
123
|
+
*
|
|
124
|
+
* SHA-256 via Web Crypto — the same API in Node, Workers and browsers, per the
|
|
125
|
+
* repo's standing rule against node-only imports and against hand-rolled hashes.
|
|
126
|
+
*/
|
|
127
|
+
export declare function requestFingerprint(operation: string, input: unknown): Promise<string>;
|
|
128
|
+
/**
|
|
129
|
+
* The `conflict` reason slugs this feature owns.
|
|
130
|
+
*
|
|
131
|
+
* Slugs rather than codes, per the closed taxonomy: a module never invents a
|
|
132
|
+
* code, it narrows an existing one with a reason it owns. Both are `conflict`
|
|
133
|
+
* (409) because both mean the same thing to a client — *the key you sent is not
|
|
134
|
+
* available for this request* — and differ only in why, which is what the slug
|
|
135
|
+
* carries.
|
|
136
|
+
*/
|
|
137
|
+
export declare const IDEMPOTENCY_REUSED = "idempotency_key_reused";
|
|
138
|
+
export declare const IDEMPOTENCY_REPLAY_UNAVAILABLE = "idempotency_replay_unavailable";
|
|
139
|
+
//# sourceMappingURL=idempotency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../src/idempotency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,4DAA4D;AAC5D,eAAO,MAAM,sBAAsB,oBAAoB,CAAC;AAExD;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,yBAAyB,CAAC;AAElE;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,mCAAyC,CAAC;AAElF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,wBAAwB,QAAsB,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,QAAa,CAAC;AAEnD,+EAA+E;AAC/E,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAG1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAWpD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAI3F;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,2BAA2B,CAAC;AAC3D,eAAO,MAAM,8BAA8B,mCAAmC,CAAC"}
|