@nifrajs/core 1.9.0 → 1.10.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/assurance.d.ts +5 -0
- package/dist/assurance.d.ts.map +1 -1
- package/dist/assurance.js +8 -1
- package/dist/assurance.js.map +1 -1
- package/dist/budget.d.ts +91 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +172 -0
- package/dist/budget.js.map +1 -0
- package/dist/capabilities.d.ts +117 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +257 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/classification.d.ts +19 -0
- package/dist/classification.d.ts.map +1 -0
- package/dist/classification.js +33 -0
- package/dist/classification.js.map +1 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/idempotency.d.ts +90 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +135 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/capability-runtime.d.ts +16 -0
- package/dist/internal/capability-runtime.d.ts.map +1 -0
- package/dist/internal/capability-runtime.js +27 -0
- package/dist/internal/capability-runtime.js.map +1 -0
- package/dist/internal/route-assurance.d.ts +1 -0
- package/dist/internal/route-assurance.d.ts.map +1 -1
- package/dist/internal/route-assurance.js +1 -0
- package/dist/internal/route-assurance.js.map +1 -1
- package/dist/reflection.d.ts +4 -0
- package/dist/reflection.d.ts.map +1 -1
- package/dist/reflection.js +9 -0
- package/dist/reflection.js.map +1 -1
- package/dist/server/context.d.ts +34 -1
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/contract.d.ts +8 -1
- package/dist/server/contract.d.ts.map +1 -1
- package/dist/server/contract.js +11 -1
- package/dist/server/contract.js.map +1 -1
- package/dist/server/server.d.ts +75 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +195 -9
- package/dist/server/server.js.map +1 -1
- package/package.json +11 -4
- package/src/assurance.ts +15 -1
- package/src/budget.ts +267 -0
- package/src/capabilities.ts +430 -0
- package/src/classification.ts +44 -0
- package/src/errors.ts +1 -0
- package/src/idempotency.ts +205 -0
- package/src/index.ts +6 -1
- package/src/internal/capability-runtime.ts +49 -0
- package/src/internal/route-assurance.ts +1 -0
- package/src/reflection.ts +14 -0
- package/src/server/context.ts +35 -1
- package/src/server/contract.ts +19 -2
- package/src/server/server.ts +328 -11
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route effect/capability assurance. Declarations are reflected at build/CI time; runtime beacons
|
|
3
|
+
* are a fail-closed defence at owned effect seams. Static provenance remains the security anchor.
|
|
4
|
+
*/
|
|
5
|
+
import { CAPABILITY_GUARD, validCapabilityId, } from "./internal/capability-runtime.js";
|
|
6
|
+
import { NIFRA_ASSURANCE_IDS } from "./internal/route-assurance.js";
|
|
7
|
+
import { reflectRoutes } from "./reflection.js";
|
|
8
|
+
const SAFE_METHODS = new Set(["GET", "HEAD"]);
|
|
9
|
+
export { validCapabilityId };
|
|
10
|
+
function normalizedIds(values, label) {
|
|
11
|
+
if (!Array.isArray(values))
|
|
12
|
+
throw new TypeError(`${label} must be an array`);
|
|
13
|
+
const out = [];
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
for (const value of values) {
|
|
16
|
+
if (typeof value !== "string" || !validCapabilityId(value)) {
|
|
17
|
+
throw new Error(`capability assurance: invalid ${label} id ${JSON.stringify(value)}`);
|
|
18
|
+
}
|
|
19
|
+
if (!seen.has(value)) {
|
|
20
|
+
seen.add(value);
|
|
21
|
+
out.push(value);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return Object.freeze(out);
|
|
25
|
+
}
|
|
26
|
+
function validSpecifier(value) {
|
|
27
|
+
return value.trim() === value && value !== "" && !hasControlCharacter(value);
|
|
28
|
+
}
|
|
29
|
+
function hasControlCharacter(value) {
|
|
30
|
+
for (let index = 0; index < value.length; index++) {
|
|
31
|
+
const code = value.charCodeAt(index);
|
|
32
|
+
if (code <= 31 || code === 127)
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
/** Validate and freeze a capability/provenance policy. */
|
|
38
|
+
export function defineCapabilityPolicy(policy) {
|
|
39
|
+
if (policy.provenance === undefined || policy.provenance === null) {
|
|
40
|
+
throw new Error("capability assurance: provenance policy is required");
|
|
41
|
+
}
|
|
42
|
+
const ids = new Set();
|
|
43
|
+
const definitions = policy.definitions.map((definition) => {
|
|
44
|
+
if (!validCapabilityId(definition.id)) {
|
|
45
|
+
throw new Error(`capability assurance: invalid capability id ${JSON.stringify(definition.id)}`);
|
|
46
|
+
}
|
|
47
|
+
if (ids.has(definition.id)) {
|
|
48
|
+
throw new Error(`capability assurance: duplicate capability ${JSON.stringify(definition.id)}`);
|
|
49
|
+
}
|
|
50
|
+
ids.add(definition.id);
|
|
51
|
+
if (definition.zone !== "domain" && definition.zone !== "operational") {
|
|
52
|
+
throw new Error(`capability assurance: invalid zone for ${definition.id}`);
|
|
53
|
+
}
|
|
54
|
+
if (definition.access !== "read" && definition.access !== "write") {
|
|
55
|
+
throw new Error(`capability assurance: invalid access for ${definition.id}`);
|
|
56
|
+
}
|
|
57
|
+
const idempotency = definition.idempotency ?? "none";
|
|
58
|
+
if (idempotency !== "none" && idempotency !== "request" && idempotency !== "durable") {
|
|
59
|
+
throw new Error(`capability assurance: invalid idempotency for ${definition.id}`);
|
|
60
|
+
}
|
|
61
|
+
if (definition.access === "read" && idempotency !== "none") {
|
|
62
|
+
throw new Error(`capability assurance: read capability ${definition.id} cannot require idempotency`);
|
|
63
|
+
}
|
|
64
|
+
return Object.freeze({ ...definition, idempotency });
|
|
65
|
+
});
|
|
66
|
+
const imports = policy.provenance.imports.map((rule) => {
|
|
67
|
+
if (!validSpecifier(rule.specifier))
|
|
68
|
+
throw new Error("capability assurance: invalid import specifier");
|
|
69
|
+
const capabilities = normalizedIds(rule.capabilities, "import capability");
|
|
70
|
+
for (const id of capabilities) {
|
|
71
|
+
if (!ids.has(id))
|
|
72
|
+
throw new Error(`capability assurance: import references unknown ${id}`);
|
|
73
|
+
}
|
|
74
|
+
return Object.freeze({ specifier: rule.specifier, capabilities });
|
|
75
|
+
});
|
|
76
|
+
const forbiddenImports = policy.provenance.forbiddenImports.map((rule) => {
|
|
77
|
+
if (!validSpecifier(rule.specifier) || rule.reason.trim() === "") {
|
|
78
|
+
throw new Error("capability assurance: forbidden import needs a specifier and reason");
|
|
79
|
+
}
|
|
80
|
+
return Object.freeze({ specifier: rule.specifier, reason: rule.reason.trim() });
|
|
81
|
+
});
|
|
82
|
+
const routeModules = policy.provenance.routeModules?.map((rule) => {
|
|
83
|
+
if (!Array.isArray(rule.modules) || rule.modules.length === 0) {
|
|
84
|
+
throw new Error("capability assurance: routeModules entry needs at least one module");
|
|
85
|
+
}
|
|
86
|
+
const modules = rule.modules.map((module) => {
|
|
87
|
+
if (!validSpecifier(module))
|
|
88
|
+
throw new Error("capability assurance: invalid route module");
|
|
89
|
+
return module.replace(/^\.\//, "");
|
|
90
|
+
});
|
|
91
|
+
return Object.freeze({
|
|
92
|
+
match: Object.freeze({
|
|
93
|
+
...(rule.match.methods !== undefined
|
|
94
|
+
? { methods: Object.freeze(rule.match.methods.map((method) => method.toUpperCase())) }
|
|
95
|
+
: {}),
|
|
96
|
+
...(rule.match.paths !== undefined ? { paths: Object.freeze([...rule.match.paths]) } : {}),
|
|
97
|
+
}),
|
|
98
|
+
modules: Object.freeze(modules),
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
return Object.freeze({
|
|
102
|
+
definitions: Object.freeze(definitions),
|
|
103
|
+
provenance: Object.freeze({
|
|
104
|
+
imports: Object.freeze(imports),
|
|
105
|
+
forbiddenImports: Object.freeze(forbiddenImports),
|
|
106
|
+
...(routeModules !== undefined ? { routeModules: Object.freeze(routeModules) } : {}),
|
|
107
|
+
}),
|
|
108
|
+
lockfile: policy.lockfile ?? "capabilities.lock.json",
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function evidenceSourceValid(source) {
|
|
112
|
+
return source.length > 0 && source.length <= 256 && !hasControlCharacter(source);
|
|
113
|
+
}
|
|
114
|
+
function routeKey(method, path) {
|
|
115
|
+
return `${method.toUpperCase()}\n${path}`;
|
|
116
|
+
}
|
|
117
|
+
/** Compare declared route capabilities against coverage-qualified static/runtime evidence. */
|
|
118
|
+
export function evaluateCapabilityAssurance(source, policyInput, evidenceSet) {
|
|
119
|
+
const policy = defineCapabilityPolicy(policyInput);
|
|
120
|
+
const definitions = new Map(policy.definitions.map((definition) => [definition.id, definition]));
|
|
121
|
+
const evidenceByRoute = new Map(evidenceSet.routes.map((route) => [routeKey(route.method, route.path), route]));
|
|
122
|
+
const findings = [];
|
|
123
|
+
const routes = [];
|
|
124
|
+
for (const route of reflectRoutes(source)) {
|
|
125
|
+
const declared = route.capabilities ?? [];
|
|
126
|
+
const supplied = evidenceByRoute.get(routeKey(route.method, route.path));
|
|
127
|
+
const covered = supplied?.covered === true;
|
|
128
|
+
const evidence = [];
|
|
129
|
+
const evidenceIds = new Set();
|
|
130
|
+
for (const item of supplied?.evidence ?? []) {
|
|
131
|
+
if (!validCapabilityId(item.id) ||
|
|
132
|
+
(item.kind !== "static" && item.kind !== "runtime") ||
|
|
133
|
+
!evidenceSourceValid(item.source)) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const key = `${item.id}\n${item.kind}\n${item.source}`;
|
|
137
|
+
if (evidence.some((candidate) => `${candidate.id}\n${candidate.kind}\n${candidate.source}` === key))
|
|
138
|
+
continue;
|
|
139
|
+
evidence.push(Object.freeze({ id: item.id, kind: item.kind, source: item.source }));
|
|
140
|
+
evidenceIds.add(item.id);
|
|
141
|
+
}
|
|
142
|
+
for (const id of declared) {
|
|
143
|
+
if (!definitions.has(id)) {
|
|
144
|
+
findings.push({
|
|
145
|
+
code: "unknown-capability",
|
|
146
|
+
method: route.method,
|
|
147
|
+
path: route.path,
|
|
148
|
+
capability: id,
|
|
149
|
+
message: `${route.method} ${route.path} declares unknown capability ${id}`,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (!covered) {
|
|
154
|
+
findings.push({
|
|
155
|
+
code: "provenance-uncovered",
|
|
156
|
+
method: route.method,
|
|
157
|
+
path: route.path,
|
|
158
|
+
message: `${route.method} ${route.path} has no static provenance coverage`,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
for (const id of evidenceIds) {
|
|
162
|
+
if (!declared.includes(id)) {
|
|
163
|
+
findings.push({
|
|
164
|
+
code: "undeclared-capability-evidence",
|
|
165
|
+
method: route.method,
|
|
166
|
+
path: route.path,
|
|
167
|
+
capability: id,
|
|
168
|
+
message: `${route.method} ${route.path} evidence exceeds its declaration: ${id}`,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const effective = new Set([...declared, ...evidenceIds]);
|
|
173
|
+
const assuranceIds = new Set((route.assurance ?? []).map((item) => item.id));
|
|
174
|
+
for (const id of effective) {
|
|
175
|
+
const definition = definitions.get(id);
|
|
176
|
+
if (definition === undefined)
|
|
177
|
+
continue;
|
|
178
|
+
if (SAFE_METHODS.has(route.method) &&
|
|
179
|
+
definition.zone === "domain" &&
|
|
180
|
+
definition.access === "write") {
|
|
181
|
+
findings.push({
|
|
182
|
+
code: "safe-method-domain-write",
|
|
183
|
+
method: route.method,
|
|
184
|
+
path: route.path,
|
|
185
|
+
capability: id,
|
|
186
|
+
message: `${route.method} ${route.path} cannot carry domain write capability ${id}`,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (definition.idempotency === "request" &&
|
|
190
|
+
!assuranceIds.has(NIFRA_ASSURANCE_IDS.IDEMPOTENCY_KEY)) {
|
|
191
|
+
findings.push({
|
|
192
|
+
code: "missing-request-idempotency",
|
|
193
|
+
method: route.method,
|
|
194
|
+
path: route.path,
|
|
195
|
+
capability: id,
|
|
196
|
+
message: `${route.method} ${route.path} capability ${id} requires request idempotency evidence`,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
if (definition.idempotency === "durable" &&
|
|
200
|
+
!assuranceIds.has(NIFRA_ASSURANCE_IDS.DURABLE_COMMAND)) {
|
|
201
|
+
findings.push({
|
|
202
|
+
code: "missing-durable-idempotency",
|
|
203
|
+
method: route.method,
|
|
204
|
+
path: route.path,
|
|
205
|
+
capability: id,
|
|
206
|
+
message: `${route.method} ${route.path} capability ${id} requires durable command/provider-key evidence`,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
routes.push(Object.freeze({
|
|
211
|
+
method: route.method,
|
|
212
|
+
path: route.path,
|
|
213
|
+
declared: Object.freeze([...declared]),
|
|
214
|
+
evidence: Object.freeze(evidence),
|
|
215
|
+
unproven: Object.freeze(declared.filter((id) => !evidenceIds.has(id))),
|
|
216
|
+
covered,
|
|
217
|
+
...(route.classification !== undefined ? { classification: route.classification } : {}),
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
return Object.freeze({
|
|
221
|
+
ok: findings.length === 0,
|
|
222
|
+
routes: Object.freeze(routes),
|
|
223
|
+
findings: Object.freeze(findings),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Runtime effect beacon for owned adapters. It fails closed when the route omitted the capability or
|
|
228
|
+
* when no route guard is present. Static provenance is still required: code can bypass a beacon.
|
|
229
|
+
*/
|
|
230
|
+
export function useCapability(context, capability) {
|
|
231
|
+
if (!validCapabilityId(capability)) {
|
|
232
|
+
throw new Error(`capability assurance: invalid runtime capability ${JSON.stringify(capability)}`);
|
|
233
|
+
}
|
|
234
|
+
const guard = context[CAPABILITY_GUARD];
|
|
235
|
+
if (guard === undefined) {
|
|
236
|
+
throw new Error(`capability assurance: ${capability} used on a route with no capability declaration`);
|
|
237
|
+
}
|
|
238
|
+
if (!guard.allowed.includes(capability)) {
|
|
239
|
+
throw new Error(`capability assurance: ${capability} is not declared for ${guard.method} ${guard.path}`);
|
|
240
|
+
}
|
|
241
|
+
guard.onUse?.({ capability, method: guard.method, path: guard.path });
|
|
242
|
+
}
|
|
243
|
+
/** Deterministic, PII-free lockfile material. */
|
|
244
|
+
export function snapshotCapabilities(report) {
|
|
245
|
+
const routes = report.routes
|
|
246
|
+
.map((route) => ({
|
|
247
|
+
method: route.method,
|
|
248
|
+
path: route.path,
|
|
249
|
+
declared: Object.freeze([...route.declared].sort()),
|
|
250
|
+
evidenced: Object.freeze([...new Set(route.evidence.map((item) => item.id))].sort()),
|
|
251
|
+
unproven: Object.freeze([...route.unproven].sort()),
|
|
252
|
+
...(route.classification !== undefined ? { classification: route.classification } : {}),
|
|
253
|
+
}))
|
|
254
|
+
.sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));
|
|
255
|
+
return Object.freeze({ nifraCapabilities: 1, routes: Object.freeze(routes) });
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=capabilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response data-classification tags. A route can declare the highest sensitivity its response body
|
|
3
|
+
* carries (`schema.classification`). This is a declarative, compile-time + introspection fact — never
|
|
4
|
+
* read on the request hot path and never enforced at runtime here. Downstream consumers use it: a
|
|
5
|
+
* partner-API surface refuses to expose a route whose response is `pii`/`secret`, privacy tooling
|
|
6
|
+
* learns which routes emit regulated data, and the capability lockfile records it so a route that
|
|
7
|
+
* *starts* returning PII flips the lockfile and forces a review.
|
|
8
|
+
*/
|
|
9
|
+
/** Sensitivity of the data a response carries. Ordered `public` < `pii` < `secret`. */
|
|
10
|
+
export type DataClassification = "public" | "pii" | "secret";
|
|
11
|
+
/** Total order over classifications; higher = more sensitive. */
|
|
12
|
+
export declare const DATA_CLASSIFICATION_RANK: Readonly<Record<DataClassification, number>>;
|
|
13
|
+
/** Whether `value` is a known classification token. */
|
|
14
|
+
export declare function isDataClassification(value: unknown): value is DataClassification;
|
|
15
|
+
/** The most sensitive classification among the inputs; `"public"` when none are given. */
|
|
16
|
+
export declare function maxClassification(values: Iterable<DataClassification>): DataClassification;
|
|
17
|
+
/** True when `value` is at least as sensitive as `floor` (e.g. `classificationAtLeast(x, "pii")`). */
|
|
18
|
+
export declare function classificationAtLeast(value: DataClassification, floor: DataClassification): boolean;
|
|
19
|
+
//# sourceMappingURL=classification.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classification.d.ts","sourceRoot":"","sources":["../src/classification.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,uFAAuF;AACvF,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,KAAK,GAAG,QAAQ,CAAA;AAE5D,iEAAiE;AACjE,eAAO,MAAM,wBAAwB,EAAE,QAAQ,CAAC,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAMjF,CAAA;AAID,uDAAuD;AACvD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,kBAAkB,CAEhF;AAED,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAM1F;AAED,sGAAsG;AACtG,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,kBAAkB,EACzB,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAET"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response data-classification tags. A route can declare the highest sensitivity its response body
|
|
3
|
+
* carries (`schema.classification`). This is a declarative, compile-time + introspection fact — never
|
|
4
|
+
* read on the request hot path and never enforced at runtime here. Downstream consumers use it: a
|
|
5
|
+
* partner-API surface refuses to expose a route whose response is `pii`/`secret`, privacy tooling
|
|
6
|
+
* learns which routes emit regulated data, and the capability lockfile records it so a route that
|
|
7
|
+
* *starts* returning PII flips the lockfile and forces a review.
|
|
8
|
+
*/
|
|
9
|
+
/** Total order over classifications; higher = more sensitive. */
|
|
10
|
+
export const DATA_CLASSIFICATION_RANK = Object.freeze({
|
|
11
|
+
public: 0,
|
|
12
|
+
pii: 1,
|
|
13
|
+
secret: 2,
|
|
14
|
+
});
|
|
15
|
+
const CLASSIFICATIONS = Object.keys(DATA_CLASSIFICATION_RANK);
|
|
16
|
+
/** Whether `value` is a known classification token. */
|
|
17
|
+
export function isDataClassification(value) {
|
|
18
|
+
return typeof value === "string" && CLASSIFICATIONS.includes(value);
|
|
19
|
+
}
|
|
20
|
+
/** The most sensitive classification among the inputs; `"public"` when none are given. */
|
|
21
|
+
export function maxClassification(values) {
|
|
22
|
+
let max = "public";
|
|
23
|
+
for (const value of values) {
|
|
24
|
+
if (DATA_CLASSIFICATION_RANK[value] > DATA_CLASSIFICATION_RANK[max])
|
|
25
|
+
max = value;
|
|
26
|
+
}
|
|
27
|
+
return max;
|
|
28
|
+
}
|
|
29
|
+
/** True when `value` is at least as sensitive as `floor` (e.g. `classificationAtLeast(x, "pii")`). */
|
|
30
|
+
export function classificationAtLeast(value, floor) {
|
|
31
|
+
return DATA_CLASSIFICATION_RANK[value] >= DATA_CLASSIFICATION_RANK[floor];
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=classification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classification.js","sourceRoot":"","sources":["../src/classification.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,iEAAiE;AACjE,MAAM,CAAC,MAAM,wBAAwB,GAAiD,MAAM,CAAC,MAAM,CACjG;IACE,MAAM,EAAE,CAAC;IACT,GAAG,EAAE,CAAC;IACN,MAAM,EAAE,CAAC;CACV,CACF,CAAA;AAED,MAAM,eAAe,GAAG,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAkC,CAAA;AAE9F,uDAAuD;AACvD,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,eAAqC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AAC5F,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,iBAAiB,CAAC,MAAoC;IACpE,IAAI,GAAG,GAAuB,QAAQ,CAAA;IACtC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,wBAAwB,CAAC,KAAK,CAAC,GAAG,wBAAwB,CAAC,GAAG,CAAC;YAAE,GAAG,GAAG,KAAK,CAAA;IAClF,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,qBAAqB,CACnC,KAAyB,EACzB,KAAyB;IAEzB,OAAO,wBAAwB,CAAC,KAAK,CAAC,IAAI,wBAAwB,CAAC,KAAK,CAAC,CAAA;AAC3E,CAAC"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export declare class FrameworkError extends Error {
|
|
|
8
8
|
constructor(code: string, message: string);
|
|
9
9
|
}
|
|
10
10
|
/** Stable codes for boot-time (L2) route configuration failures. */
|
|
11
|
-
export type RouteConfigErrorCode = "DUPLICATE_ROUTE" | "DUPLICATE_PARAM" | "PARAM_NAME_CONFLICT" | "INVALID_PATH" | "INVALID_PARAM_NAME" | "WILDCARD_NOT_LAST" | "INVALID_METHOD";
|
|
11
|
+
export type RouteConfigErrorCode = "DUPLICATE_ROUTE" | "DUPLICATE_PARAM" | "PARAM_NAME_CONFLICT" | "INVALID_PATH" | "INVALID_PARAM_NAME" | "WILDCARD_NOT_LAST" | "INVALID_METHOD" | "INVALID_ASSURANCE";
|
|
12
12
|
/**
|
|
13
13
|
* Thrown at route registration when a route is misconfigured. This is the
|
|
14
14
|
* boot-time rejection layer: loud and early,
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;gBAET,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AAED,oEAAoE;AACpE,MAAM,MAAM,oBAAoB,GAC5B,iBAAiB,GACjB,iBAAiB,GACjB,qBAAqB,GACrB,cAAc,GACd,oBAAoB,GACpB,mBAAmB,GACnB,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;gBAET,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AAED,oEAAoE;AACpE,MAAM,MAAM,oBAAoB,GAC5B,iBAAiB,GACjB,iBAAiB,GACjB,qBAAqB,GACrB,cAAc,GACd,oBAAoB,GACpB,mBAAmB,GACnB,gBAAgB,GAChB,mBAAmB,CAAA;AAEvB;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,cAAc;IAElD,SAAiB,IAAI,EAAE,oBAAoB,CAAA;gBAE/B,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM;CAIxD"}
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEpD;;;;GAIG;AACH,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,IAAI,CAAQ;IAErB,YAAY,IAAY,EAAE,OAAe;QACvC,KAAK,CAAC,IAAI,cAAc,KAAK,OAAO,EAAE,CAAC,CAAA;QACvC,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAA;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEpD;;;;GAIG;AACH,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,IAAI,CAAQ;IAErB,YAAY,IAAY,EAAE,OAAe;QACvC,KAAK,CAAC,IAAI,cAAc,KAAK,OAAO,EAAE,CAAC,CAAA;QACvC,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAA;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;CACF;AAaD;;;;GAIG;AACH,MAAM,OAAO,gBAAiB,SAAQ,cAAc;IAIlD,YAAY,IAA0B,EAAE,OAAe;QACrD,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QACpB,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAA;IAChC,CAAC;CACF"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idempotency primitive. A mutating route can declare `schema.idempotency`; the server then dedupes
|
|
3
|
+
* on an `Idempotency-Key` header: the first request runs and its response is stored, and a retry with
|
|
4
|
+
* the same key replays the stored response byte-for-byte without re-running the handler. A retry that
|
|
5
|
+
* reuses a key with a *different* request body is rejected (409) — a key binds to one request.
|
|
6
|
+
*
|
|
7
|
+
* This module is the runtime-neutral core: the store interface, an in-memory store, the request
|
|
8
|
+
* fingerprint, and response (de)serialization. The server owns the request-path lane that reads the
|
|
9
|
+
* body, consults the store, and captures the response. All logic here is pure/injectable so it is
|
|
10
|
+
* unit-tested without a server. A durable store (cross-restart) implements the same interface.
|
|
11
|
+
*/
|
|
12
|
+
/** Whether a route's idempotency is satisfied by an in-process store or a durable (cross-restart) one. */
|
|
13
|
+
export type IdempotencyScope = "request" | "durable";
|
|
14
|
+
/** Default retention for a stored idempotent response: 24 hours. */
|
|
15
|
+
export declare const DEFAULT_IDEMPOTENCY_TTL_MS = 86400000;
|
|
16
|
+
/** Canonical request header carrying the client-chosen idempotency key. */
|
|
17
|
+
export declare const DEFAULT_IDEMPOTENCY_HEADER = "idempotency-key";
|
|
18
|
+
/** Header stamped on a replayed response so clients/proxies can tell a replay from a fresh run. */
|
|
19
|
+
export declare const IDEMPOTENT_REPLAY_HEADER = "x-nifra-idempotent-replay";
|
|
20
|
+
/** A serialized response held by a store. `body` is base64 so binary payloads round-trip intact. */
|
|
21
|
+
export interface StoredResponse {
|
|
22
|
+
readonly status: number;
|
|
23
|
+
readonly headers: readonly (readonly [string, string])[];
|
|
24
|
+
/** Base64-encoded response body; `""` when the response had no body. */
|
|
25
|
+
readonly body: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Outcome of reserving a key. `new` → the caller runs the handler and later calls {@link
|
|
29
|
+
* IdempotencyStore.complete}. `replay` → return the stored response, handler never runs. `mismatch`
|
|
30
|
+
* → same key, different request fingerprint (client bug) → 409. `in-flight` → the key is reserved but
|
|
31
|
+
* not yet completed (a concurrent duplicate) → 409 + Retry-After.
|
|
32
|
+
*/
|
|
33
|
+
export type IdempotencyBeginResult = {
|
|
34
|
+
readonly state: "new";
|
|
35
|
+
} | {
|
|
36
|
+
readonly state: "replay";
|
|
37
|
+
readonly response: StoredResponse;
|
|
38
|
+
} | {
|
|
39
|
+
readonly state: "mismatch";
|
|
40
|
+
} | {
|
|
41
|
+
readonly state: "in-flight";
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Storage seam for idempotent responses. `begin` MUST be atomic: for one key, exactly one concurrent
|
|
45
|
+
* caller sees `new`; the rest see `in-flight` (or `replay` once completed). The in-memory store gets
|
|
46
|
+
* this free from the single-threaded event loop; a durable store uses an atomic insert.
|
|
47
|
+
*/
|
|
48
|
+
export interface IdempotencyStore {
|
|
49
|
+
/** Reserve `key` for `fingerprint`, retaining any stored response for `ttlMs`. */
|
|
50
|
+
begin(key: string, fingerprint: string, ttlMs: number): IdempotencyBeginResult | Promise<IdempotencyBeginResult>;
|
|
51
|
+
/** Persist the final response for a reserved key so later retries replay it. */
|
|
52
|
+
complete(key: string, response: StoredResponse): void | Promise<void>;
|
|
53
|
+
/** Release a reservation that produced no cacheable response, so the client may retry. */
|
|
54
|
+
abandon(key: string): void | Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
/** A key must be a non-empty, bounded, control-char-free token. Fail closed on anything else. */
|
|
57
|
+
export declare function validIdempotencyKey(key: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* SHA-256 fingerprint binding a key to one request: method, path (+ query), and the raw body bytes.
|
|
60
|
+
* A collision-resistant hash matters — a weak hash would let a crafted body replay another's response.
|
|
61
|
+
*/
|
|
62
|
+
export declare function computeIdempotencyFingerprint(method: string, path: string, body: Uint8Array): Promise<string>;
|
|
63
|
+
/** Buffer a response into a storable form. Clones first so the live response body stays intact. */
|
|
64
|
+
export declare function serializeResponse(response: Response): Promise<StoredResponse>;
|
|
65
|
+
/** Rebuild a live response from storage, stamping the replay marker header. */
|
|
66
|
+
export declare function responseFromStored(stored: StoredResponse): Response;
|
|
67
|
+
export interface MemoryIdempotencyStoreOptions {
|
|
68
|
+
/** Injectable clock (epoch ms) for deterministic TTL tests. Default `Date.now`. */
|
|
69
|
+
readonly now?: () => number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* In-process idempotency store. Reservation is atomic by construction — `begin` never awaits, so the
|
|
73
|
+
* single-threaded event loop serializes concurrent callers for one key. Expired entries are treated
|
|
74
|
+
* as absent (lazy eviction on access); a periodic {@link MemoryIdempotencyStore.sweep} bounds memory.
|
|
75
|
+
*/
|
|
76
|
+
export declare class MemoryIdempotencyStore implements IdempotencyStore {
|
|
77
|
+
private readonly entries;
|
|
78
|
+
private readonly now;
|
|
79
|
+
constructor(options?: MemoryIdempotencyStoreOptions);
|
|
80
|
+
begin(key: string, fingerprint: string, ttlMs: number): IdempotencyBeginResult;
|
|
81
|
+
complete(key: string, response: StoredResponse): void;
|
|
82
|
+
abandon(key: string): void;
|
|
83
|
+
/** Evict expired entries. Callers may run this on an interval; access-time eviction covers the rest. */
|
|
84
|
+
sweep(): void;
|
|
85
|
+
/** Live entry count (post-sweep semantics are the caller's; this is a raw size for tests/metrics). */
|
|
86
|
+
get size(): number;
|
|
87
|
+
}
|
|
88
|
+
/** Convenience factory mirroring the other core primitives' `create*` style. */
|
|
89
|
+
export declare function createMemoryIdempotencyStore(options?: MemoryIdempotencyStoreOptions): MemoryIdempotencyStore;
|
|
90
|
+
//# sourceMappingURL=idempotency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../src/idempotency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,0GAA0G;AAC1G,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,SAAS,CAAA;AAEpD,oEAAoE;AACpE,eAAO,MAAM,0BAA0B,WAAa,CAAA;AAEpD,2EAA2E;AAC3E,eAAO,MAAM,0BAA0B,oBAAoB,CAAA;AAE3D,mGAAmG;AACnG,eAAO,MAAM,wBAAwB,8BAA8B,CAAA;AAInE,oGAAoG;AACpG,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAA;IACxD,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAC9B;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAA;CAAE,GAC/D;IAAE,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAAA;AAEnC;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kFAAkF;IAClF,KAAK,CACH,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,GACZ,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAA;IAC3D,gFAAgF;IAChF,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrE,0FAA0F;IAC1F,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3C;AAED,iGAAiG;AACjG,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOxD;AAID;;;GAGG;AACH,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,MAAM,CAAC,CAUjB;AAmBD,mGAAmG;AACnG,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,CAOnF;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,QAAQ,CASnE;AAQD,MAAM,WAAW,6BAA6B;IAC5C,mFAAmF;IACnF,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CAC5B;AAED;;;;GAIG;AACH,qBAAa,sBAAuB,YAAW,gBAAgB;IAC7D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;gBAEtB,OAAO,GAAE,6BAAkC;IAIvD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,sBAAsB;IAe9E,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI;IAKrD,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAM1B,wGAAwG;IACxG,KAAK,IAAI,IAAI;IAOb,sGAAsG;IACtG,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED,gFAAgF;AAChF,wBAAgB,4BAA4B,CAC1C,OAAO,CAAC,EAAE,6BAA6B,GACtC,sBAAsB,CAExB"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idempotency primitive. A mutating route can declare `schema.idempotency`; the server then dedupes
|
|
3
|
+
* on an `Idempotency-Key` header: the first request runs and its response is stored, and a retry with
|
|
4
|
+
* the same key replays the stored response byte-for-byte without re-running the handler. A retry that
|
|
5
|
+
* reuses a key with a *different* request body is rejected (409) — a key binds to one request.
|
|
6
|
+
*
|
|
7
|
+
* This module is the runtime-neutral core: the store interface, an in-memory store, the request
|
|
8
|
+
* fingerprint, and response (de)serialization. The server owns the request-path lane that reads the
|
|
9
|
+
* body, consults the store, and captures the response. All logic here is pure/injectable so it is
|
|
10
|
+
* unit-tested without a server. A durable store (cross-restart) implements the same interface.
|
|
11
|
+
*/
|
|
12
|
+
/** Default retention for a stored idempotent response: 24 hours. */
|
|
13
|
+
export const DEFAULT_IDEMPOTENCY_TTL_MS = 86_400_000;
|
|
14
|
+
/** Canonical request header carrying the client-chosen idempotency key. */
|
|
15
|
+
export const DEFAULT_IDEMPOTENCY_HEADER = "idempotency-key";
|
|
16
|
+
/** Header stamped on a replayed response so clients/proxies can tell a replay from a fresh run. */
|
|
17
|
+
export const IDEMPOTENT_REPLAY_HEADER = "x-nifra-idempotent-replay";
|
|
18
|
+
const KEY_MAX_LENGTH = 255;
|
|
19
|
+
/** A key must be a non-empty, bounded, control-char-free token. Fail closed on anything else. */
|
|
20
|
+
export function validIdempotencyKey(key) {
|
|
21
|
+
if (typeof key !== "string" || key.length === 0 || key.length > KEY_MAX_LENGTH)
|
|
22
|
+
return false;
|
|
23
|
+
for (let i = 0; i < key.length; i++) {
|
|
24
|
+
const code = key.charCodeAt(i);
|
|
25
|
+
if (code <= 31 || code === 127)
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
const encoder = new TextEncoder();
|
|
31
|
+
/**
|
|
32
|
+
* SHA-256 fingerprint binding a key to one request: method, path (+ query), and the raw body bytes.
|
|
33
|
+
* A collision-resistant hash matters — a weak hash would let a crafted body replay another's response.
|
|
34
|
+
*/
|
|
35
|
+
export async function computeIdempotencyFingerprint(method, path, body) {
|
|
36
|
+
const head = encoder.encode(`${method.toUpperCase()}\n${path}\n`);
|
|
37
|
+
const material = new Uint8Array(head.length + body.length);
|
|
38
|
+
material.set(head, 0);
|
|
39
|
+
material.set(body, head.length);
|
|
40
|
+
const digest = await crypto.subtle.digest("SHA-256", material);
|
|
41
|
+
const bytes = new Uint8Array(digest);
|
|
42
|
+
let hex = "";
|
|
43
|
+
for (let i = 0; i < bytes.length; i++)
|
|
44
|
+
hex += bytes[i].toString(16).padStart(2, "0");
|
|
45
|
+
return hex;
|
|
46
|
+
}
|
|
47
|
+
const BASE64_CHUNK = 0x8000;
|
|
48
|
+
function bytesToBase64(bytes) {
|
|
49
|
+
let binary = "";
|
|
50
|
+
for (let i = 0; i < bytes.length; i += BASE64_CHUNK) {
|
|
51
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + BASE64_CHUNK));
|
|
52
|
+
}
|
|
53
|
+
return btoa(binary);
|
|
54
|
+
}
|
|
55
|
+
function base64ToBytes(value) {
|
|
56
|
+
const binary = atob(value);
|
|
57
|
+
const bytes = new Uint8Array(binary.length);
|
|
58
|
+
for (let i = 0; i < binary.length; i++)
|
|
59
|
+
bytes[i] = binary.charCodeAt(i);
|
|
60
|
+
return bytes;
|
|
61
|
+
}
|
|
62
|
+
/** Buffer a response into a storable form. Clones first so the live response body stays intact. */
|
|
63
|
+
export async function serializeResponse(response) {
|
|
64
|
+
const bytes = new Uint8Array(await response.clone().arrayBuffer());
|
|
65
|
+
return {
|
|
66
|
+
status: response.status,
|
|
67
|
+
headers: [...response.headers],
|
|
68
|
+
body: bytes.length > 0 ? bytesToBase64(bytes) : "",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Rebuild a live response from storage, stamping the replay marker header. */
|
|
72
|
+
export function responseFromStored(stored) {
|
|
73
|
+
const headers = new Headers(stored.headers);
|
|
74
|
+
headers.set(IDEMPOTENT_REPLAY_HEADER, "1");
|
|
75
|
+
// Reference the Response body type structurally: `BodyInit` isn't a named type under every lib config,
|
|
76
|
+
// but a fresh `Uint8Array` is a valid body — the cast bridges the `ArrayBufferLike` generic variance.
|
|
77
|
+
const body = (stored.body === "" ? null : base64ToBytes(stored.body));
|
|
78
|
+
return new Response(body, { status: stored.status, headers });
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* In-process idempotency store. Reservation is atomic by construction — `begin` never awaits, so the
|
|
82
|
+
* single-threaded event loop serializes concurrent callers for one key. Expired entries are treated
|
|
83
|
+
* as absent (lazy eviction on access); a periodic {@link MemoryIdempotencyStore.sweep} bounds memory.
|
|
84
|
+
*/
|
|
85
|
+
export class MemoryIdempotencyStore {
|
|
86
|
+
entries = new Map();
|
|
87
|
+
now;
|
|
88
|
+
constructor(options = {}) {
|
|
89
|
+
this.now = options.now ?? Date.now;
|
|
90
|
+
}
|
|
91
|
+
begin(key, fingerprint, ttlMs) {
|
|
92
|
+
const existing = this.entries.get(key);
|
|
93
|
+
if (existing !== undefined && existing.expiresAt > this.now()) {
|
|
94
|
+
if (existing.fingerprint !== fingerprint)
|
|
95
|
+
return { state: "mismatch" };
|
|
96
|
+
if (existing.response !== undefined)
|
|
97
|
+
return { state: "replay", response: existing.response };
|
|
98
|
+
return { state: "in-flight" };
|
|
99
|
+
}
|
|
100
|
+
this.entries.set(key, {
|
|
101
|
+
fingerprint,
|
|
102
|
+
expiresAt: this.now() + ttlMs,
|
|
103
|
+
response: undefined,
|
|
104
|
+
});
|
|
105
|
+
return { state: "new" };
|
|
106
|
+
}
|
|
107
|
+
complete(key, response) {
|
|
108
|
+
const entry = this.entries.get(key);
|
|
109
|
+
if (entry !== undefined)
|
|
110
|
+
entry.response = response;
|
|
111
|
+
}
|
|
112
|
+
abandon(key) {
|
|
113
|
+
const entry = this.entries.get(key);
|
|
114
|
+
// Only drop a still-pending reservation; never evict a completed (replayable) response.
|
|
115
|
+
if (entry !== undefined && entry.response === undefined)
|
|
116
|
+
this.entries.delete(key);
|
|
117
|
+
}
|
|
118
|
+
/** Evict expired entries. Callers may run this on an interval; access-time eviction covers the rest. */
|
|
119
|
+
sweep() {
|
|
120
|
+
const now = this.now();
|
|
121
|
+
for (const [key, entry] of this.entries) {
|
|
122
|
+
if (entry.expiresAt <= now)
|
|
123
|
+
this.entries.delete(key);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Live entry count (post-sweep semantics are the caller's; this is a raw size for tests/metrics). */
|
|
127
|
+
get size() {
|
|
128
|
+
return this.entries.size;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** Convenience factory mirroring the other core primitives' `create*` style. */
|
|
132
|
+
export function createMemoryIdempotencyStore(options) {
|
|
133
|
+
return new MemoryIdempotencyStore(options);
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=idempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../src/idempotency.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,11 +11,14 @@
|
|
|
11
11
|
* (it shipped at "0.0.0" through 1.0.0 before those guards existed). Kept narrow (`as const`) so
|
|
12
12
|
* consumers can pin it at the type level — see `test/version.test-d.ts`.
|
|
13
13
|
*/
|
|
14
|
-
export declare const VERSION: "1.
|
|
14
|
+
export declare const VERSION: "1.10.0";
|
|
15
15
|
export type Version = typeof VERSION;
|
|
16
16
|
export * from "./assurance.js";
|
|
17
|
+
export * from "./capabilities.js";
|
|
18
|
+
export * from "./classification.js";
|
|
17
19
|
export * from "./diff.js";
|
|
18
20
|
export { FrameworkError, RouteConfigError, type RouteConfigErrorCode } from "./errors.js";
|
|
21
|
+
export * from "./idempotency.js";
|
|
19
22
|
export { FRAMEWORK_NAME, type FrameworkName } from "./internal/brand.js";
|
|
20
23
|
export * from "./reflection.js";
|
|
21
24
|
export { METHODS, type Method, Router, type RouterMatch } from "./router/router.js";
|
|
@@ -27,7 +30,7 @@ export { type ContextForOp, type ContractShape, defineContract, type HandlersFor
|
|
|
27
30
|
export { type CookieOptions, parseCookies, serializeCookie, signValue, unsignValue, } from "./server/cookies.js";
|
|
28
31
|
export { commonSecretPatterns, jsonLogger, type LogFields, type Logger, type RedactOptions, redactLogFields, silentLogger, } from "./server/logger.js";
|
|
29
32
|
export type { Registry, RouteInfo } from "./server/registry.js";
|
|
30
|
-
export { type AnyServer, type DurableObjectNamespaceLike, defineIdentityPlugin, definePlugin, defineRouterPlugin, type ExecutionContext, type Handler, type IdentityPlugin, type McpPromptDescriptor, type McpResourceDescriptor, type Middleware, type NifraPlugin, type NodeServeOutcome, type OnRequestResult, type PromptArgument, type PromptMessage, type ResponseFinalization, type RouteDescriptor, type RunningServer, type ScheduledController, type ScheduledHandler, Server, type ServerOptions, server, type ToolAnnotations, toFetchHandler, } from "./server/server.js";
|
|
33
|
+
export { type AdmissionController, type AdmissionDecision, type AnyServer, type DurableObjectNamespaceLike, defineIdentityPlugin, definePlugin, defineRouterPlugin, type ExecutionContext, type Handler, type IdentityPlugin, type McpPromptDescriptor, type McpResourceDescriptor, type Middleware, type NifraPlugin, type NodeServeOutcome, type OnRequestResult, type PromptArgument, type PromptMessage, type ResponseFinalization, type RouteDescriptor, type RunningServer, type ScheduledController, type ScheduledHandler, Server, type ServerOptions, server, type ToolAnnotations, toFetchHandler, } from "./server/server.js";
|
|
31
34
|
export { type SSEContext, type SSEInit, type SSEMessage, type SSEStream, sse, type TypedSSEStream, typedSSEStream, } from "./server/sse.js";
|
|
32
35
|
export { type SignatureEncoding, type VerifyWebhookOptions, verifyWebhook, type WebhookFailureReason, type WebhookProvider, type WebhookResult, } from "./server/webhook.js";
|
|
33
36
|
export type { NifraWebSocket, StandardWebSocket, WebSocketContext, WebSocketData, WebSocketHandler, WebSocketUpgradeOutcome, } from "./server/websocket.js";
|