@nifrajs/core 5.7.0 → 5.9.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/CHANGELOG.md +105 -0
- package/README.md +9 -4
- package/dist/assurance.d.ts +26 -1
- package/dist/assurance.d.ts.map +1 -1
- package/dist/assurance.js +43 -11
- package/dist/assurance.js.map +1 -1
- package/dist/canonical-json.d.ts +11 -0
- package/dist/canonical-json.d.ts.map +1 -0
- package/dist/canonical-json.js +46 -0
- package/dist/canonical-json.js.map +1 -0
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +1 -8
- package/dist/capabilities.js.map +1 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +1 -8
- package/dist/data.js.map +1 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +2 -12
- package/dist/diff.js.map +1 -1
- package/dist/durable-adapters.d.ts +2 -0
- package/dist/durable-adapters.d.ts.map +1 -1
- package/dist/durable-adapters.js +41 -49
- package/dist/durable-adapters.js.map +1 -1
- package/dist/durable-saga.d.ts.map +1 -1
- package/dist/durable-saga.js +14 -20
- package/dist/durable-saga.js.map +1 -1
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +14 -68
- package/dist/evidence.js.map +1 -1
- package/dist/idempotency.d.ts.map +1 -1
- package/dist/idempotency.js +60 -23
- package/dist/idempotency.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/internal/control-character.d.ts +3 -0
- package/dist/internal/control-character.d.ts.map +1 -0
- package/dist/internal/control-character.js +10 -0
- package/dist/internal/control-character.js.map +1 -0
- package/dist/internal/durable-shared.d.ts.map +1 -1
- package/dist/internal/durable-shared.js +4 -1
- package/dist/internal/durable-shared.js.map +1 -1
- package/dist/internal/hex.d.ts +5 -0
- package/dist/internal/hex.d.ts.map +1 -0
- package/dist/internal/hex.js +14 -0
- package/dist/internal/hex.js.map +1 -0
- package/dist/internal/record.d.ts +3 -0
- package/dist/internal/record.d.ts.map +1 -0
- package/dist/internal/record.js +5 -0
- package/dist/internal/record.js.map +1 -0
- package/dist/internal/route-compiler.d.ts.map +1 -1
- package/dist/internal/route-compiler.js +2 -2
- package/dist/internal/route-compiler.js.map +1 -1
- package/dist/internal/route-execution.d.ts +4 -4
- package/dist/internal/route-execution.d.ts.map +1 -1
- package/dist/internal/route-execution.js +4 -0
- package/dist/internal/route-execution.js.map +1 -1
- package/dist/internal/route-lanes.d.ts +4 -3
- package/dist/internal/route-lanes.d.ts.map +1 -1
- package/dist/internal/route-lanes.js +16 -33
- package/dist/internal/route-lanes.js.map +1 -1
- package/dist/internal/schema-parts.d.ts +13 -0
- package/dist/internal/schema-parts.d.ts.map +1 -0
- package/dist/internal/schema-parts.js +15 -0
- package/dist/internal/schema-parts.js.map +1 -0
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +2 -14
- package/dist/ledger.js.map +1 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +10 -37
- package/dist/manifest.js.map +1 -1
- package/dist/open-resolved.d.ts +18 -0
- package/dist/open-resolved.d.ts.map +1 -0
- package/dist/open-resolved.js +62 -0
- package/dist/open-resolved.js.map +1 -0
- package/dist/range.js +1 -1
- package/dist/range.js.map +1 -1
- package/dist/serve-static.d.ts +22 -0
- package/dist/serve-static.d.ts.map +1 -0
- package/dist/serve-static.js +187 -0
- package/dist/serve-static.js.map +1 -0
- package/dist/server/bare-error-lane.d.ts +2 -1
- package/dist/server/bare-error-lane.d.ts.map +1 -1
- package/dist/server/bare-error-lane.js +12 -3
- package/dist/server/bare-error-lane.js.map +1 -1
- package/dist/server/base64-answers.d.ts.map +1 -1
- package/dist/server/base64-answers.js +1 -4
- package/dist/server/base64-answers.js.map +1 -1
- package/dist/server/body-lane.d.ts.map +1 -1
- package/dist/server/body-lane.js +43 -38
- package/dist/server/body-lane.js.map +1 -1
- package/dist/server/body.d.ts.map +1 -1
- package/dist/server/body.js +4 -1
- package/dist/server/body.js.map +1 -1
- package/dist/server/bridge-markers.d.ts +1 -1
- package/dist/server/bridge-markers.js +1 -1
- package/dist/server/cookies.d.ts.map +1 -1
- package/dist/server/cookies.js +4 -1
- package/dist/server/cookies.js.map +1 -1
- package/dist/server/core-copies.js +1 -1
- package/dist/server/http.d.ts +5 -0
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +13 -0
- package/dist/server/http.js.map +1 -1
- package/dist/server/idempotency-lane.d.ts.map +1 -1
- package/dist/server/idempotency-lane.js +2 -0
- package/dist/server/idempotency-lane.js.map +1 -1
- package/dist/server/idle-timeout.d.ts +39 -0
- package/dist/server/idle-timeout.d.ts.map +1 -0
- package/dist/server/idle-timeout.js +95 -0
- package/dist/server/idle-timeout.js.map +1 -0
- package/dist/server/json-shared.d.ts +23 -0
- package/dist/server/json-shared.d.ts.map +1 -0
- package/dist/server/json-shared.js +33 -0
- package/dist/server/json-shared.js.map +1 -0
- package/dist/server/json-text.d.ts +1 -1
- package/dist/server/json-text.js +1 -1
- package/dist/server/node-outcome.js +1 -1
- package/dist/server/node-outcome.js.map +1 -1
- package/dist/server/request-context.d.ts +22 -1
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +44 -4
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/request-local.d.ts +44 -0
- package/dist/server/request-local.d.ts.map +1 -0
- package/dist/server/request-local.js +91 -0
- package/dist/server/request-local.js.map +1 -0
- package/dist/server/respond.js +1 -1
- package/dist/server/respond.js.map +1 -1
- package/dist/server/response-contract-lane.d.ts +10 -2
- package/dist/server/response-contract-lane.d.ts.map +1 -1
- package/dist/server/response-contract-lane.js +93 -64
- package/dist/server/response-contract-lane.js.map +1 -1
- package/dist/server/runtime-core.d.ts +7 -4
- package/dist/server/runtime-core.d.ts.map +1 -1
- package/dist/server/runtime-core.js +9 -7
- package/dist/server/runtime-core.js.map +1 -1
- package/dist/server/server-types.d.ts +27 -0
- package/dist/server/server-types.d.ts.map +1 -1
- package/dist/server/server.d.ts +59 -59
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +869 -512
- package/dist/server/server.js.map +1 -1
- package/dist/server/transport-lane.d.ts.map +1 -1
- package/dist/server/transport-lane.js +2 -0
- package/dist/server/transport-lane.js.map +1 -1
- package/dist/server/webhook.js +1 -1
- package/dist/server/webhook.js.map +1 -1
- package/dist/server/websocket.d.ts +3 -1
- package/dist/server/websocket.d.ts.map +1 -1
- package/dist/server/websocket.js +14 -1
- package/dist/server/websocket.js.map +1 -1
- package/dist/server/ws-adapter-view.d.ts +54 -0
- package/dist/server/ws-adapter-view.d.ts.map +1 -0
- package/dist/server/ws-adapter-view.js +7 -0
- package/dist/server/ws-adapter-view.js.map +1 -0
- package/dist/server.d.ts +2 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -1
- package/dist/server.js.map +1 -1
- package/dist/single-copy.d.ts.map +1 -1
- package/dist/single-copy.js +1 -1
- package/dist/single-copy.js.map +1 -1
- package/dist/static-file.d.ts +156 -4
- package/dist/static-file.d.ts.map +1 -1
- package/dist/static-file.js +198 -4
- package/dist/static-file.js.map +1 -1
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +1 -3
- package/dist/wire.js.map +1 -1
- package/package.json +16 -1
- package/src/assurance.ts +66 -16
- package/src/canonical-json.ts +43 -0
- package/src/capabilities.ts +1 -8
- package/src/data.ts +2 -8
- package/src/diff.ts +2 -12
- package/src/durable-adapters.ts +53 -50
- package/src/durable-saga.ts +30 -23
- package/src/evidence.ts +25 -90
- package/src/idempotency.ts +62 -20
- package/src/index.ts +13 -2
- package/src/internal/control-character.ts +8 -0
- package/src/internal/durable-shared.ts +4 -1
- package/src/internal/hex.ts +14 -0
- package/src/internal/record.ts +4 -0
- package/src/internal/route-compiler.ts +2 -2
- package/src/internal/route-execution.ts +7 -20
- package/src/internal/route-lanes.ts +20 -44
- package/src/internal/schema-parts.ts +19 -0
- package/src/ledger.ts +2 -16
- package/src/manifest.ts +10 -38
- package/src/open-resolved.ts +64 -0
- package/src/range.ts +1 -1
- package/src/serve-static.ts +227 -0
- package/src/server/bare-error-lane.ts +11 -2
- package/src/server/base64-answers.ts +2 -6
- package/src/server/body-lane.ts +58 -51
- package/src/server/body.ts +4 -1
- package/src/server/bridge-markers.ts +1 -1
- package/src/server/cookies.ts +4 -1
- package/src/server/core-copies.ts +1 -1
- package/src/server/http.ts +12 -0
- package/src/server/idempotency-lane.ts +2 -0
- package/src/server/idle-timeout.ts +107 -0
- package/src/server/json-shared.ts +38 -0
- package/src/server/json-text.ts +1 -1
- package/src/server/node-outcome.ts +1 -1
- package/src/server/request-context.ts +62 -3
- package/src/server/request-local.ts +109 -0
- package/src/server/respond.ts +1 -1
- package/src/server/response-contract-lane.ts +112 -65
- package/src/server/runtime-core.ts +10 -8
- package/src/server/server-types.ts +27 -0
- package/src/server/server.ts +1230 -800
- package/src/server/transport-lane.ts +2 -0
- package/src/server/webhook.ts +1 -1
- package/src/server/websocket.ts +18 -1
- package/src/server/ws-adapter-view.ts +63 -0
- package/src/server.ts +12 -1
- package/src/single-copy.ts +1 -3
- package/src/static-file.ts +350 -4
- package/src/wire.ts +2 -4
package/src/evidence.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { AssuranceEvidence, AssuranceFinding, AssuranceReport } from "./assurance.ts"
|
|
10
|
+
import { canonicalJson } from "./canonical-json.ts"
|
|
10
11
|
import type {
|
|
11
12
|
AssuredCapabilityRoute,
|
|
12
13
|
CapabilityAssuranceReport,
|
|
@@ -15,7 +16,9 @@ import type {
|
|
|
15
16
|
} from "./capabilities.ts"
|
|
16
17
|
import type { ResponseClassification } from "./classification.ts"
|
|
17
18
|
import { codeUnitOrder } from "./internal/code-unit-order.ts"
|
|
19
|
+
import { sha256Hex } from "./internal/hex.ts"
|
|
18
20
|
import { evidenceProvenance } from "./internal/route-assurance.ts"
|
|
21
|
+
import { mapSchemaParts } from "./internal/schema-parts.ts"
|
|
19
22
|
import {
|
|
20
23
|
type JsonSchema,
|
|
21
24
|
type ReflectedMount,
|
|
@@ -134,11 +137,6 @@ export interface ProjectEvidenceCompositionPart {
|
|
|
134
137
|
readonly pathPrefix?: string
|
|
135
138
|
}
|
|
136
139
|
|
|
137
|
-
const recordOf = (value: unknown): Readonly<Record<string, unknown>> | undefined =>
|
|
138
|
-
value !== null && typeof value === "object" && !Array.isArray(value)
|
|
139
|
-
? (value as Readonly<Record<string, unknown>>)
|
|
140
|
-
: undefined
|
|
141
|
-
|
|
142
140
|
function schemaPart(value: SchemaReflection | undefined): ProjectEvidenceSchemaPart | undefined {
|
|
143
141
|
if (value === undefined) return undefined
|
|
144
142
|
return Object.freeze({
|
|
@@ -154,12 +152,6 @@ function schemaPart(value: SchemaReflection | undefined): ProjectEvidenceSchemaP
|
|
|
154
152
|
function schemaOf(route: ReflectedRoute): ProjectEvidenceSchema | undefined {
|
|
155
153
|
const source = route.schema
|
|
156
154
|
if (source === undefined) return undefined
|
|
157
|
-
const headers = schemaPart(source.headers)
|
|
158
|
-
const cookies = schemaPart(source.cookies)
|
|
159
|
-
const body = schemaPart(source.body)
|
|
160
|
-
const query = schemaPart(source.query)
|
|
161
|
-
const params = schemaPart(source.params)
|
|
162
|
-
const response = schemaPart(source.response)
|
|
163
155
|
const sse = schemaPart(source.sse)
|
|
164
156
|
const errors: Record<string, ProjectEvidenceSchemaPart> = {}
|
|
165
157
|
for (const [status, value] of Object.entries(source.errors ?? {})) {
|
|
@@ -170,12 +162,7 @@ function schemaOf(route: ReflectedRoute): ProjectEvidenceSchema | undefined {
|
|
|
170
162
|
...(source.validationOrder !== undefined ? { validationOrder: source.validationOrder } : {}),
|
|
171
163
|
...(source.bodyLimit !== undefined ? { bodyLimit: source.bodyLimit } : {}),
|
|
172
164
|
...(source.bodyLimitReason !== undefined ? { bodyLimitReason: source.bodyLimitReason } : {}),
|
|
173
|
-
...(
|
|
174
|
-
...(cookies !== undefined ? { cookies } : {}),
|
|
175
|
-
...(body !== undefined ? { body } : {}),
|
|
176
|
-
...(query !== undefined ? { query } : {}),
|
|
177
|
-
...(params !== undefined ? { params } : {}),
|
|
178
|
-
...(response !== undefined ? { response } : {}),
|
|
165
|
+
...mapSchemaParts(source, schemaPart),
|
|
179
166
|
...(Object.keys(errors).length > 0 ? { errors: Object.freeze(errors) } : {}),
|
|
180
167
|
...(sse !== undefined ? { sse } : {}),
|
|
181
168
|
...(source.validationErrorHook === true ? { validationErrorHook: true as const } : {}),
|
|
@@ -221,6 +208,20 @@ const sortByRoute = <T extends { readonly method: string; readonly path: string
|
|
|
221
208
|
[...values].sort((a, b) => codeUnitOrder(a.path, b.path) || codeUnitOrder(a.method, b.method)),
|
|
222
209
|
)
|
|
223
210
|
|
|
211
|
+
const sortByFinding = <
|
|
212
|
+
T extends { readonly method: string; readonly path: string; readonly code: string },
|
|
213
|
+
>(
|
|
214
|
+
values: readonly T[],
|
|
215
|
+
): readonly T[] =>
|
|
216
|
+
Object.freeze(
|
|
217
|
+
[...values].sort(
|
|
218
|
+
(a, b) =>
|
|
219
|
+
codeUnitOrder(a.path, b.path) ||
|
|
220
|
+
codeUnitOrder(a.method, b.method) ||
|
|
221
|
+
codeUnitOrder(a.code, b.code),
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
|
|
224
225
|
function evidenceRouteOf(
|
|
225
226
|
route: ReflectedRoute,
|
|
226
227
|
locations: readonly ProjectEvidenceSourceLocation[] | undefined,
|
|
@@ -260,14 +261,7 @@ function assuranceOf(report: AssuranceReport | undefined): ProjectEvidenceAssura
|
|
|
260
261
|
}),
|
|
261
262
|
),
|
|
262
263
|
),
|
|
263
|
-
findings:
|
|
264
|
-
[...report.findings].sort(
|
|
265
|
-
(a, b) =>
|
|
266
|
-
codeUnitOrder(a.path, b.path) ||
|
|
267
|
-
codeUnitOrder(a.method, b.method) ||
|
|
268
|
-
codeUnitOrder(a.code, b.code),
|
|
269
|
-
),
|
|
270
|
-
),
|
|
264
|
+
findings: sortByFinding(report.findings),
|
|
271
265
|
})
|
|
272
266
|
}
|
|
273
267
|
|
|
@@ -290,14 +284,7 @@ function capabilitiesOf(
|
|
|
290
284
|
}),
|
|
291
285
|
),
|
|
292
286
|
),
|
|
293
|
-
findings:
|
|
294
|
-
[...report.findings].sort(
|
|
295
|
-
(a, b) =>
|
|
296
|
-
codeUnitOrder(a.path, b.path) ||
|
|
297
|
-
codeUnitOrder(a.method, b.method) ||
|
|
298
|
-
codeUnitOrder(a.code, b.code),
|
|
299
|
-
),
|
|
300
|
-
),
|
|
287
|
+
findings: sortByFinding(report.findings),
|
|
301
288
|
})
|
|
302
289
|
}
|
|
303
290
|
|
|
@@ -443,16 +430,7 @@ export function composeProjectEvidence(
|
|
|
443
430
|
: Object.freeze({
|
|
444
431
|
ok: assuranceReports.every((report) => report.ok),
|
|
445
432
|
routes: sortByRoute(assuranceReports.flatMap((report) => report.routes)),
|
|
446
|
-
findings:
|
|
447
|
-
assuranceReports
|
|
448
|
-
.flatMap((report) => report.findings)
|
|
449
|
-
.sort(
|
|
450
|
-
(a, b) =>
|
|
451
|
-
codeUnitOrder(a.path, b.path) ||
|
|
452
|
-
codeUnitOrder(a.method, b.method) ||
|
|
453
|
-
codeUnitOrder(a.code, b.code),
|
|
454
|
-
),
|
|
455
|
-
),
|
|
433
|
+
findings: sortByFinding(assuranceReports.flatMap((report) => report.findings)),
|
|
456
434
|
})
|
|
457
435
|
const capabilities =
|
|
458
436
|
capabilityReports.length === 0
|
|
@@ -460,16 +438,7 @@ export function composeProjectEvidence(
|
|
|
460
438
|
: Object.freeze({
|
|
461
439
|
ok: capabilityReports.every((report) => report.ok),
|
|
462
440
|
routes: sortByRoute(capabilityReports.flatMap((report) => report.routes)),
|
|
463
|
-
findings:
|
|
464
|
-
capabilityReports
|
|
465
|
-
.flatMap((report) => report.findings)
|
|
466
|
-
.sort(
|
|
467
|
-
(a, b) =>
|
|
468
|
-
codeUnitOrder(a.path, b.path) ||
|
|
469
|
-
codeUnitOrder(a.method, b.method) ||
|
|
470
|
-
codeUnitOrder(a.code, b.code),
|
|
471
|
-
),
|
|
472
|
-
),
|
|
441
|
+
findings: sortByFinding(capabilityReports.flatMap((report) => report.findings)),
|
|
473
442
|
})
|
|
474
443
|
if (assurance !== undefined) validateComposedReportRoutes(assurance, routeKeys, "assurance")
|
|
475
444
|
if (capabilities !== undefined)
|
|
@@ -506,23 +475,12 @@ function schemaFromEvidence(schema: ProjectEvidenceSchema | undefined): Reflecte
|
|
|
506
475
|
schemaPartToReflection(value) as SchemaReflection,
|
|
507
476
|
]),
|
|
508
477
|
)
|
|
509
|
-
const headers = schemaPartToReflection(schema.headers)
|
|
510
|
-
const cookies = schemaPartToReflection(schema.cookies)
|
|
511
|
-
const body = schemaPartToReflection(schema.body)
|
|
512
|
-
const query = schemaPartToReflection(schema.query)
|
|
513
|
-
const params = schemaPartToReflection(schema.params)
|
|
514
|
-
const response = schemaPartToReflection(schema.response)
|
|
515
478
|
const sse = schemaPartToReflection(schema.sse)
|
|
516
479
|
return {
|
|
517
480
|
...(schema.validationOrder !== undefined ? { validationOrder: schema.validationOrder } : {}),
|
|
518
481
|
...(schema.bodyLimit !== undefined ? { bodyLimit: schema.bodyLimit } : {}),
|
|
519
482
|
...(schema.bodyLimitReason !== undefined ? { bodyLimitReason: schema.bodyLimitReason } : {}),
|
|
520
|
-
...(
|
|
521
|
-
...(cookies !== undefined ? { cookies } : {}),
|
|
522
|
-
...(body !== undefined ? { body } : {}),
|
|
523
|
-
...(query !== undefined ? { query } : {}),
|
|
524
|
-
...(params !== undefined ? { params } : {}),
|
|
525
|
-
...(response !== undefined ? { response } : {}),
|
|
483
|
+
...mapSchemaParts(schema, schemaPartToReflection),
|
|
526
484
|
...(Object.keys(errors).length > 0 ? { errors } : {}),
|
|
527
485
|
...(sse !== undefined ? { sse } : {}),
|
|
528
486
|
...(schema.validationErrorHook === true ? { validationErrorHook: true as const } : {}),
|
|
@@ -592,35 +550,12 @@ const mountsPart = (
|
|
|
592
550
|
),
|
|
593
551
|
}
|
|
594
552
|
|
|
595
|
-
function canonicalValue(value: unknown): string {
|
|
596
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
597
|
-
return JSON.stringify(value)
|
|
598
|
-
}
|
|
599
|
-
if (typeof value === "number") {
|
|
600
|
-
if (!Number.isFinite(value))
|
|
601
|
-
throw new TypeError("project evidence cannot encode non-finite numbers")
|
|
602
|
-
return JSON.stringify(value)
|
|
603
|
-
}
|
|
604
|
-
if (Array.isArray(value)) return `[${value.map((item) => canonicalValue(item)).join(",")}]`
|
|
605
|
-
const record = recordOf(value)
|
|
606
|
-
if (record !== undefined) {
|
|
607
|
-
return `{${Object.keys(record)
|
|
608
|
-
.filter((key) => record[key] !== undefined)
|
|
609
|
-
.sort()
|
|
610
|
-
.map((key) => `${JSON.stringify(key)}:${canonicalValue(record[key])}`)
|
|
611
|
-
.join(",")}}`
|
|
612
|
-
}
|
|
613
|
-
throw new TypeError(`project evidence cannot encode ${typeof value}`)
|
|
614
|
-
}
|
|
615
|
-
|
|
616
553
|
/** Stable JSON for logs, MCP, generated artifacts, and snapshot tests. */
|
|
617
554
|
export function serializeProjectEvidence(snapshot: ProjectEvidenceSnapshot): string {
|
|
618
|
-
return
|
|
555
|
+
return canonicalJson(snapshot)
|
|
619
556
|
}
|
|
620
557
|
|
|
621
558
|
/** SHA-256 of the canonical snapshot, useful as a cheap freshness/reference token. */
|
|
622
559
|
export async function digestProjectEvidence(snapshot: ProjectEvidenceSnapshot): Promise<string> {
|
|
623
|
-
|
|
624
|
-
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes))
|
|
625
|
-
return [...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("")
|
|
560
|
+
return sha256Hex(serializeProjectEvidence(snapshot))
|
|
626
561
|
}
|
package/src/idempotency.ts
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* unit-tested without a server. A durable store (cross-restart) implements the same interface.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { canonicalJson } from "./canonical-json.ts"
|
|
14
|
+
import { bytesToHex } from "./internal/hex.ts"
|
|
15
|
+
|
|
13
16
|
/** Whether a route's idempotency is satisfied by an in-process store or a durable (cross-restart) one. */
|
|
14
17
|
export type IdempotencyScope = "request" | "durable"
|
|
15
18
|
|
|
@@ -172,32 +175,64 @@ export async function computeIdempotencyFingerprint(
|
|
|
172
175
|
const material = new Uint8Array(head.length + body.length)
|
|
173
176
|
material.set(head, 0)
|
|
174
177
|
material.set(body, head.length)
|
|
175
|
-
|
|
176
|
-
const bytes = new Uint8Array(digest)
|
|
177
|
-
let hex = ""
|
|
178
|
-
for (let i = 0; i < bytes.length; i++) hex += (bytes[i] as number).toString(16).padStart(2, "0")
|
|
179
|
-
return hex
|
|
178
|
+
return bytesToHex(new Uint8Array(await crypto.subtle.digest("SHA-256", material)))
|
|
180
179
|
}
|
|
181
180
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
181
|
+
const isDigit = (code: number): boolean => code >= 48 && code <= 57
|
|
182
|
+
const isDigitOrDot = (code: number): boolean => isDigit(code) || code === 46
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Whether `text` may hold a JSON number no double holds exactly: one written with 16+ digits (a dot
|
|
186
|
+
* counted) or with a negative exponent of 3+ digits. Any other number round-trips exactly, or
|
|
187
|
+
* overflows to Infinity, which canonical JSON refuses. A match inside a string only costs the slower
|
|
188
|
+
* parse.
|
|
189
|
+
*/
|
|
190
|
+
function mayHoldLossyNumber(text: string): boolean {
|
|
191
|
+
// A run of 16 digits or dots covers an index that is 15 mod 16, so only those indexes are probed.
|
|
192
|
+
for (let i = 15; i < text.length; i += 16) {
|
|
193
|
+
if (!isDigitOrDot(text.charCodeAt(i))) continue
|
|
194
|
+
let start = i
|
|
195
|
+
while (isDigitOrDot(text.charCodeAt(start - 1))) start--
|
|
196
|
+
let end = i + 1
|
|
197
|
+
while (isDigitOrDot(text.charCodeAt(end))) end++
|
|
198
|
+
if (end - start >= 16) return true
|
|
185
199
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
200
|
+
// A negative exponent follows "e-" or "E-"; "-" is the rarer character to search for.
|
|
201
|
+
for (let i = text.indexOf("-"); i !== -1; i = text.indexOf("-", i + 1)) {
|
|
202
|
+
if ((text.charCodeAt(i - 1) | 32) !== 101) continue
|
|
203
|
+
let digit = i + 1
|
|
204
|
+
while (text.charCodeAt(digit) === 48) digit++
|
|
205
|
+
if (
|
|
206
|
+
isDigit(text.charCodeAt(digit)) &&
|
|
207
|
+
isDigit(text.charCodeAt(digit + 1)) &&
|
|
208
|
+
isDigit(text.charCodeAt(digit + 2))
|
|
209
|
+
) {
|
|
210
|
+
return true
|
|
211
|
+
}
|
|
189
212
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
213
|
+
return false
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** A `JSON.parse` reviver that keeps a lossy number's own text and reads every other value as usual. */
|
|
217
|
+
function keepLossyNumberText(
|
|
218
|
+
_key: string,
|
|
219
|
+
value: unknown,
|
|
220
|
+
context?: { readonly source?: string },
|
|
221
|
+
): unknown {
|
|
222
|
+
if (typeof value !== "number") return value
|
|
223
|
+
const source = context?.source
|
|
224
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: the member is optional, so an engine without JSON.rawJSON leaves it undefined.
|
|
225
|
+
const rawJson = (JSON as JSON & { readonly rawJSON?: (text: string) => unknown }).rawJSON
|
|
226
|
+
// Unable to keep the text, the caller fingerprints the exact bytes rather than a rounded number.
|
|
227
|
+
if (source === undefined || rawJson === undefined) {
|
|
228
|
+
throw new TypeError("no JSON number source text")
|
|
197
229
|
}
|
|
198
|
-
|
|
230
|
+
return mayHoldLossyNumber(source) ? rawJson(source) : value
|
|
199
231
|
}
|
|
200
232
|
|
|
233
|
+
// Not fatal: Deno's fatal decode costs several times a replacing one; U+FFFD is checked for instead.
|
|
234
|
+
const utf8 = new TextDecoder()
|
|
235
|
+
|
|
201
236
|
/** Canonicalize JSON bodies so whitespace/property-order retries bind to the same semantic request. */
|
|
202
237
|
export function canonicalizeIdempotencyBody(
|
|
203
238
|
body: Uint8Array,
|
|
@@ -206,7 +241,14 @@ export function canonicalizeIdempotencyBody(
|
|
|
206
241
|
const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase() ?? ""
|
|
207
242
|
if (mediaType !== "application/json" && !mediaType.endsWith("+json")) return body
|
|
208
243
|
try {
|
|
209
|
-
|
|
244
|
+
const text = utf8.decode(body)
|
|
245
|
+
// Every invalid byte decodes to U+FFFD, so two different bodies could read the same: such a
|
|
246
|
+
// body, or one that writes U+FFFD itself, keeps its exact bytes.
|
|
247
|
+
if (text.includes("\uFFFD")) return body
|
|
248
|
+
const parsed: unknown = mayHoldLossyNumber(text)
|
|
249
|
+
? JSON.parse(text, keepLossyNumberText)
|
|
250
|
+
: JSON.parse(text)
|
|
251
|
+
return encoder.encode(canonicalJson(parsed))
|
|
210
252
|
} catch {
|
|
211
253
|
// Invalid JSON is rejected by the validation/body lane. Preserve its raw bytes for a stable
|
|
212
254
|
// fingerprint until that rejection releases the reservation.
|
package/src/index.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* read its own package.json at runtime. `scripts/version.ts` rewrites it on every release bump and
|
|
17
17
|
* `check:publish` asserts it equals `@nifrajs/core`'s package version.
|
|
18
18
|
*/
|
|
19
|
-
export const VERSION = "5.
|
|
19
|
+
export const VERSION = "5.9.0" as const
|
|
20
20
|
|
|
21
21
|
export type Version = typeof VERSION
|
|
22
22
|
|
|
@@ -85,7 +85,13 @@ export {
|
|
|
85
85
|
type ScheduledHandler,
|
|
86
86
|
toFetchHandler,
|
|
87
87
|
} from "./server/edge.ts"
|
|
88
|
-
export {
|
|
88
|
+
export {
|
|
89
|
+
isSameOriginPath,
|
|
90
|
+
pathnameOf,
|
|
91
|
+
type UrlParts,
|
|
92
|
+
urlPartsOf,
|
|
93
|
+
varyOn,
|
|
94
|
+
} from "./server/http.ts"
|
|
89
95
|
export {
|
|
90
96
|
commonSecretPatterns,
|
|
91
97
|
jsonLogger,
|
|
@@ -119,6 +125,11 @@ export type {
|
|
|
119
125
|
RouteInfoFor,
|
|
120
126
|
} from "./server/registry.ts"
|
|
121
127
|
export { replacedRequestOf } from "./server/request-lineage.ts"
|
|
128
|
+
export {
|
|
129
|
+
carryRequestLocals,
|
|
130
|
+
type RequestLocal,
|
|
131
|
+
requestLocal,
|
|
132
|
+
} from "./server/request-local.ts"
|
|
122
133
|
export { writableResponse } from "./server/respond.ts"
|
|
123
134
|
export { routeTemplateOf } from "./server/route-template.ts"
|
|
124
135
|
export {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Whether `value` holds a C0 control character or DEL. */
|
|
2
|
+
export function hasControlCharacter(value: string): boolean {
|
|
3
|
+
for (let index = 0; index < value.length; index++) {
|
|
4
|
+
const code = value.charCodeAt(index)
|
|
5
|
+
if (code <= 31 || code === 127) return true
|
|
6
|
+
}
|
|
7
|
+
return false
|
|
8
|
+
}
|
|
@@ -40,7 +40,10 @@ export function addDuration(timestamp: number, duration: number, label: string):
|
|
|
40
40
|
export function bytesToBase64Url(bytes: Uint8Array): string {
|
|
41
41
|
let binary = ""
|
|
42
42
|
for (const byte of bytes) binary += String.fromCharCode(byte)
|
|
43
|
-
return btoa(binary)
|
|
43
|
+
return btoa(binary)
|
|
44
|
+
.replaceAll("+", "-")
|
|
45
|
+
.replaceAll("/", "_")
|
|
46
|
+
.replace(/={1,2}$/u, "")
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
export async function sha256(value: string): Promise<string> {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const encoder = new TextEncoder()
|
|
2
|
+
|
|
3
|
+
/** Lowercase hex, two digits a byte. */
|
|
4
|
+
export function bytesToHex(bytes: Uint8Array): string {
|
|
5
|
+
let hex = ""
|
|
6
|
+
// Indexed: a for-of over the bytes runs 1.4x slower on Bun.
|
|
7
|
+
for (let i = 0; i < bytes.length; i++) hex += (bytes[i] ?? 0).toString(16).padStart(2, "0")
|
|
8
|
+
return hex
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** SHA-256 of `text`'s UTF-8 bytes, as lowercase hex. */
|
|
12
|
+
export async function sha256Hex(text: string): Promise<string> {
|
|
13
|
+
return bytesToHex(new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(text))))
|
|
14
|
+
}
|
|
@@ -17,6 +17,7 @@ import { assertByteLimit } from "../server/body.ts"
|
|
|
17
17
|
import type { RouteSchema } from "../server/context.ts"
|
|
18
18
|
import type { IdempotencyRuntime, ResolvedIdempotency } from "../server/idempotency-lane.ts"
|
|
19
19
|
import type { EffectLedgerRuntime, ResolvedEffectLedger } from "../server/ledger-lane.ts"
|
|
20
|
+
import { decorationsAssignable } from "../server/request-context.ts"
|
|
20
21
|
import type {
|
|
21
22
|
ResponseContractDefinition,
|
|
22
23
|
ResponseContractRuntime,
|
|
@@ -136,7 +137,6 @@ export function adoptRouteRuntimes(
|
|
|
136
137
|
hasIdempotency: entry.idempotent !== undefined,
|
|
137
138
|
hasLedger: ledgered !== undefined,
|
|
138
139
|
hasResponseContract: responseContract !== undefined,
|
|
139
|
-
hasDecorations: entry.hasDecorations,
|
|
140
140
|
derives: entry.derives.length,
|
|
141
141
|
beforeHandle: entry.beforeHandle.length,
|
|
142
142
|
afterHandle: entry.afterHandle.length,
|
|
@@ -297,7 +297,7 @@ export function compileRouteOptions(
|
|
|
297
297
|
hasIdempotency: idempotent !== undefined,
|
|
298
298
|
hasLedger: ledgered !== undefined,
|
|
299
299
|
hasResponseContract: contracted !== undefined,
|
|
300
|
-
hasDecorations,
|
|
300
|
+
decorationsAssignable: !hasDecorations || decorationsAssignable(routeDecorations),
|
|
301
301
|
derives: context.derives.length,
|
|
302
302
|
beforeHandle: context.beforeHandleHooks.length,
|
|
303
303
|
afterHandle: context.afterHandleHooks.length,
|
|
@@ -140,15 +140,7 @@ export function compileRouteExecutionPlan(options: {
|
|
|
140
140
|
readonly hasLedger: boolean
|
|
141
141
|
readonly fusedWeb: FusedWebRunner | undefined
|
|
142
142
|
readonly fusedBody: FusedBodyRunner | undefined
|
|
143
|
-
readonly fusedLane:
|
|
144
|
-
| "bare"
|
|
145
|
-
| "body"
|
|
146
|
-
| "query"
|
|
147
|
-
| "derive-before"
|
|
148
|
-
| "derive-before-after"
|
|
149
|
-
| "body-derive-before"
|
|
150
|
-
| "body-derive-before-after"
|
|
151
|
-
| undefined
|
|
143
|
+
readonly fusedLane: "bare" | "body" | "query" | "hooks" | "body-hooks" | undefined
|
|
152
144
|
}): RouteExecutionPlan {
|
|
153
145
|
const { lane, contextless, hasAround, hasLedger, fusedWeb, fusedBody, fusedLane } = options
|
|
154
146
|
|
|
@@ -254,6 +246,9 @@ export function compileRouteExecutionPlan(options: {
|
|
|
254
246
|
maxBodyBytes,
|
|
255
247
|
server.protoPoisoning,
|
|
256
248
|
)
|
|
249
|
+
// `c.json` writes by the route's contract, if it has one, so the reply and its check share one walk.
|
|
250
|
+
// Only then: one more own property moves a context that holds a body out of its inline slots.
|
|
251
|
+
if (entry.responseContract !== undefined) ctx.jsonContract = entry.responseContract
|
|
257
252
|
let ledger: RequestLedger | undefined
|
|
258
253
|
// The runtime is always present when a route resolved a ledger (enforced at registration).
|
|
259
254
|
const ledgerRuntime = server.effectLedgerRuntime
|
|
@@ -286,15 +281,7 @@ export interface RouteExecutionPlan {
|
|
|
286
281
|
/** Which builder produced {@link fusedWeb} - a merge rebinds the closure to the executing server
|
|
287
282
|
* and must rebuild it with the SAME semantics (a query-fused route rebuilt as bare would skip its
|
|
288
283
|
* validation). `undefined` iff `fusedWeb` is. */
|
|
289
|
-
readonly fusedLane:
|
|
290
|
-
| "bare"
|
|
291
|
-
| "body"
|
|
292
|
-
| "query"
|
|
293
|
-
| "derive-before"
|
|
294
|
-
| "derive-before-after"
|
|
295
|
-
| "body-derive-before"
|
|
296
|
-
| "body-derive-before-after"
|
|
297
|
-
| undefined
|
|
284
|
+
readonly fusedLane: "bare" | "body" | "query" | "hooks" | "body-hooks" | undefined
|
|
298
285
|
}
|
|
299
286
|
|
|
300
287
|
export interface RouteEntry {
|
|
@@ -334,8 +321,8 @@ export interface RouteEntry {
|
|
|
334
321
|
readonly beforeHandle: ReadonlyArray<RawBeforeHandle>
|
|
335
322
|
readonly afterHandle: ReadonlyArray<RawAfterHandle>
|
|
336
323
|
readonly onError: ReadonlyArray<RawErrorHandler>
|
|
337
|
-
/** Registration-specialized hook shape
|
|
338
|
-
readonly lifecycleHookLane: "
|
|
324
|
+
/** Registration-specialized hook shape: a route whose only lifecycle steps are derive, before and after hooks. */
|
|
325
|
+
readonly lifecycleHookLane: "hooks" | undefined
|
|
339
326
|
/** Wraps the matched route lifecycle. Empty for the common no-around path. */
|
|
340
327
|
readonly around: ReadonlyArray<RawAround>
|
|
341
328
|
/** The single immutable execution decision consumed by portable, Node-direct, and Bun-native paths. */
|
|
@@ -17,16 +17,8 @@ export interface RouteLaneSelection {
|
|
|
17
17
|
readonly fusedBody: boolean
|
|
18
18
|
readonly lane: RouteExecutionLane
|
|
19
19
|
readonly lifecycleLane: LifecycleExecutionLane
|
|
20
|
-
readonly lifecycleHookLane: "
|
|
21
|
-
readonly fusedLane:
|
|
22
|
-
| "bare"
|
|
23
|
-
| "body"
|
|
24
|
-
| "query"
|
|
25
|
-
| "derive-before"
|
|
26
|
-
| "derive-before-after"
|
|
27
|
-
| "body-derive-before"
|
|
28
|
-
| "body-derive-before-after"
|
|
29
|
-
| undefined
|
|
20
|
+
readonly lifecycleHookLane: "hooks" | undefined
|
|
21
|
+
readonly fusedLane: "bare" | "body" | "query" | "hooks" | "body-hooks" | undefined
|
|
30
22
|
}
|
|
31
23
|
|
|
32
24
|
export function selectRouteLanes(options: {
|
|
@@ -35,7 +27,8 @@ export function selectRouteLanes(options: {
|
|
|
35
27
|
readonly hasIdempotency: boolean
|
|
36
28
|
readonly hasLedger: boolean
|
|
37
29
|
readonly hasResponseContract: boolean
|
|
38
|
-
|
|
30
|
+
/** False when a decoration cannot be merged into the context, which keeps the fused hook lanes off. */
|
|
31
|
+
readonly decorationsAssignable?: boolean
|
|
39
32
|
readonly derives: number
|
|
40
33
|
readonly beforeHandle: number
|
|
41
34
|
readonly afterHandle: number
|
|
@@ -48,7 +41,7 @@ export function selectRouteLanes(options: {
|
|
|
48
41
|
hasIdempotency,
|
|
49
42
|
hasLedger,
|
|
50
43
|
hasResponseContract,
|
|
51
|
-
|
|
44
|
+
decorationsAssignable = true,
|
|
52
45
|
derives,
|
|
53
46
|
beforeHandle,
|
|
54
47
|
afterHandle,
|
|
@@ -153,39 +146,31 @@ export function selectRouteLanes(options: {
|
|
|
153
146
|
? "query"
|
|
154
147
|
: "hooks"
|
|
155
148
|
|
|
156
|
-
//
|
|
157
|
-
// one
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
// routes back to the generic lane because the fused builder has no validation step for them
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
// body schema are routed to the `body-derive-before` / `body-derive-before-after` lane below.
|
|
149
|
+
// A route whose only lifecycle steps are derive, before and after hooks (any number of each, at least
|
|
150
|
+
// one) runs them in one fused closure chain, with the route's decorations and error hooks. Anything
|
|
151
|
+
// that can observe around hooks, a response contract or a validation-error recovery keeps the
|
|
152
|
+
// generic runner. A params, headers or cookies schema
|
|
153
|
+
// routes back to the generic lane because the fused builder has no validation step for them, and a
|
|
154
|
+
// `body` schema is the `body-hooks` lane below because this one does not parse the body.
|
|
155
|
+
const hookCount = derives + beforeHandle + afterHandle
|
|
164
156
|
const lifecycleHookLane =
|
|
165
157
|
lane === "lifecycle" &&
|
|
166
158
|
!authBeforeValidation &&
|
|
167
159
|
!hasResponseContract &&
|
|
168
|
-
|
|
160
|
+
decorationsAssignable &&
|
|
169
161
|
!hasIdempotency &&
|
|
170
162
|
!hasLedger &&
|
|
171
163
|
schema?.onValidationError === undefined &&
|
|
172
164
|
!defaultOnValidationError &&
|
|
173
165
|
plain &&
|
|
174
166
|
schema?.body === undefined &&
|
|
175
|
-
|
|
176
|
-
beforeHandle === 1 &&
|
|
177
|
-
afterHandle <= 1 &&
|
|
178
|
-
onError === 0 &&
|
|
167
|
+
hookCount > 0 &&
|
|
179
168
|
around === 0
|
|
180
|
-
?
|
|
181
|
-
? "derive-before"
|
|
182
|
-
: "derive-before-after"
|
|
169
|
+
? "hooks"
|
|
183
170
|
: undefined
|
|
184
171
|
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
// parse → validate → derive → before → handler → after → respond in one function frame, the same
|
|
188
|
-
// win `fusedBody` already gives for the no-hooks body shape.
|
|
172
|
+
// The same hooks around a body schema: the fused closure runs parse, validate, derive, before,
|
|
173
|
+
// handler and after in one function frame, the win `fusedBody` already gives the no-hooks shape.
|
|
189
174
|
const fusedBodyLifecycle =
|
|
190
175
|
lane === "lifecycle" &&
|
|
191
176
|
!authBeforeValidation &&
|
|
@@ -195,13 +180,10 @@ export function selectRouteLanes(options: {
|
|
|
195
180
|
schema?.onValidationError === undefined &&
|
|
196
181
|
!defaultOnValidationError &&
|
|
197
182
|
!hasResponseContract &&
|
|
198
|
-
|
|
183
|
+
decorationsAssignable &&
|
|
199
184
|
!hasIdempotency &&
|
|
200
185
|
!hasLedger &&
|
|
201
|
-
|
|
202
|
-
beforeHandle === 1 &&
|
|
203
|
-
afterHandle <= 1 &&
|
|
204
|
-
onError === 0 &&
|
|
186
|
+
hookCount > 0 &&
|
|
205
187
|
around === 0
|
|
206
188
|
|
|
207
189
|
return {
|
|
@@ -220,13 +202,7 @@ export function selectRouteLanes(options: {
|
|
|
220
202
|
? "body"
|
|
221
203
|
: bare
|
|
222
204
|
? "bare"
|
|
223
|
-
: lifecycleHookLane
|
|
224
|
-
? (lifecycleHookLane as "derive-before" | "derive-before-after")
|
|
225
|
-
: fusedBodyLifecycle
|
|
226
|
-
? afterHandle === 0
|
|
227
|
-
? "body-derive-before"
|
|
228
|
-
: "body-derive-before-after"
|
|
229
|
-
: undefined
|
|
205
|
+
: (lifecycleHookLane ?? (fusedBodyLifecycle ? "body-hooks" : undefined))
|
|
230
206
|
: undefined,
|
|
231
207
|
}
|
|
232
208
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const SCHEMA_PARTS = ["headers", "cookies", "body", "query", "params", "response"] as const
|
|
2
|
+
|
|
3
|
+
type SchemaPartKey = (typeof SCHEMA_PARTS)[number]
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Each request and response schema part of `source` that `map` turns into a value, under its own key,
|
|
7
|
+
* in the order route reflections, evidence and snapshots all write them.
|
|
8
|
+
*/
|
|
9
|
+
export function mapSchemaParts<In, Out>(
|
|
10
|
+
source: { readonly [K in SchemaPartKey]?: In },
|
|
11
|
+
map: (part: In | undefined) => Out | undefined,
|
|
12
|
+
): { [K in SchemaPartKey]?: Out } {
|
|
13
|
+
const parts: { [K in SchemaPartKey]?: Out } = {}
|
|
14
|
+
for (const key of SCHEMA_PARTS) {
|
|
15
|
+
const part = map(source[key])
|
|
16
|
+
if (part !== undefined) parts[key] = part
|
|
17
|
+
}
|
|
18
|
+
return parts
|
|
19
|
+
}
|
package/src/ledger.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { validCapabilityId } from "./internal/capability-runtime.ts"
|
|
18
|
+
import { bytesToHex, sha256Hex } from "./internal/hex.ts"
|
|
18
19
|
|
|
19
20
|
/** Lifecycle phase of one effect. `intent` precedes execution; the rest describe its outcome. */
|
|
20
21
|
export type EffectPhase = "intent" | "committed" | "failed" | "compensated"
|
|
@@ -341,17 +342,6 @@ function canonicalEntry(entry: EffectEntry): string {
|
|
|
341
342
|
)
|
|
342
343
|
}
|
|
343
344
|
|
|
344
|
-
const encoder = new TextEncoder()
|
|
345
|
-
|
|
346
|
-
async function sha256Hex(value: string): Promise<string> {
|
|
347
|
-
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(value)))
|
|
348
|
-
let hex = ""
|
|
349
|
-
for (let index = 0; index < digest.length; index++) {
|
|
350
|
-
hex += (digest[index] as number).toString(16).padStart(2, "0")
|
|
351
|
-
}
|
|
352
|
-
return hex
|
|
353
|
-
}
|
|
354
|
-
|
|
355
345
|
export interface MemoryLedgerSinkOptions {
|
|
356
346
|
/** Retain at most this many sealed ledgers (oldest evicted first). Default 1000. */
|
|
357
347
|
readonly maxLedgers?: number
|
|
@@ -440,11 +430,7 @@ export async function computeEffectDigest(
|
|
|
440
430
|
const mac = new Uint8Array(
|
|
441
431
|
await crypto.subtle.sign("HMAC", cryptoKey, payload as unknown as ArrayBuffer),
|
|
442
432
|
)
|
|
443
|
-
|
|
444
|
-
for (let index = 0; index < mac.length; index++) {
|
|
445
|
-
hex += (mac[index] as number).toString(16).padStart(2, "0")
|
|
446
|
-
}
|
|
447
|
-
return hex
|
|
433
|
+
return bytesToHex(mac)
|
|
448
434
|
}
|
|
449
435
|
|
|
450
436
|
const EFFECT_LEDGER = Symbol("nifra.effect.ledger")
|