mirror of
https://github.com/morluto/rea.git
synced 2026-11-05 22:36:08 +00:00
Share bounded UTF-8 and UTF-16 JSON measurements across state serving, write retention and source-map admission. Remove duplicate counters and distinguish retained state estimates from encoded bytes while preserving limits and exact refusal diagnostics.
160 lines
5.2 KiB
TypeScript
160 lines
5.2 KiB
TypeScript
import { jsonTextCodeUnits } from "../jsonSerialization.js";
|
|
import type { JsonValue } from "../jsonValue.js";
|
|
import {
|
|
at as valueAt,
|
|
isObject,
|
|
pathSegmentMatches,
|
|
pointerTo,
|
|
same,
|
|
segmentsOf,
|
|
type JsonObject,
|
|
} from "./environmentStateRules.js";
|
|
import {
|
|
documentBodyProblem,
|
|
writtenDocument,
|
|
} from "./environmentStateWriteApply.js";
|
|
import { documentTarget } from "./environmentStateWriteChecks.js";
|
|
import {
|
|
built,
|
|
callOf,
|
|
fail,
|
|
refuse,
|
|
requestObject,
|
|
UNKNOWN,
|
|
type EffectContext,
|
|
type EffectResult,
|
|
type WriteDraft,
|
|
type WriteStep,
|
|
} from "./environmentStateWriteEffects.js";
|
|
import { documentPointerOf } from "./environmentStateWriteExplain.js";
|
|
|
|
/** Whether a write's path names the held document's: each segment verbatim, or either spelling once percent-decoded, as serving matches a supplied value. */
|
|
const namesDocument = (path: string, held: string): boolean => {
|
|
const written = segmentsOf(path);
|
|
const recorded = segmentsOf(held);
|
|
return (
|
|
written.length === recorded.length &&
|
|
recorded.every((segment, index) =>
|
|
pathSegmentMatches(segment, written[index]!),
|
|
)
|
|
);
|
|
};
|
|
|
|
/** Where a write goes: a built plan writes where serving does, at its own object; an exemplar at the object holding most of its members. */
|
|
const writePointer = (
|
|
draft: WriteDraft,
|
|
value: JsonValue,
|
|
body: JsonObject,
|
|
): string | undefined =>
|
|
built(draft)
|
|
? draft.plan.observation?.pointer
|
|
: documentPointerOf(value, body);
|
|
|
|
/**
|
|
* The fields a document write changed, as pointers: a merge the members it sent at its object, a replacement every
|
|
* member it wrote or removed there, so later writes can overwrite its effect (undefined: the whole document).
|
|
*/
|
|
const writtenPointers = (
|
|
draft: WriteDraft,
|
|
body: JsonObject,
|
|
values: { readonly before: JsonValue; readonly after: JsonValue },
|
|
): readonly string[] | undefined => {
|
|
const at = draft.plan.observation?.pointer ?? "";
|
|
if (draft.plan.merge === "merge")
|
|
return Object.keys(body).map((member) => `${at}${pointerTo(member)}`);
|
|
const members = new Set(
|
|
[values.before, values.after].flatMap((value) => {
|
|
const object = valueAt(value, at);
|
|
return isObject(object) ? Object.keys(object) : [];
|
|
}),
|
|
);
|
|
if (members.size === 0) return at === "" ? undefined : [at];
|
|
return [...members].map((member) => `${at}${pointerTo(member)}`);
|
|
};
|
|
|
|
/** Build a document plan from its exemplar: its object, merge or replacement by method, and the members it sent. */
|
|
const buildDocument = (
|
|
draft: WriteDraft,
|
|
step: WriteStep,
|
|
evidence: { readonly pointer: string; readonly body: JsonObject },
|
|
): void => {
|
|
const { ordinal } = step.record;
|
|
Object.assign(draft.plan, {
|
|
exemplar_ordinal: ordinal,
|
|
observation: { ordinal, pointer: evidence.pointer },
|
|
merge: step.tool.method === "PUT" ? "replace" : "merge",
|
|
body: {
|
|
accepted: Object.keys(evidence.body),
|
|
fixed: [],
|
|
required: [],
|
|
identity: null,
|
|
kinds: [],
|
|
},
|
|
});
|
|
};
|
|
|
|
/** Apply a recorded document write: the body merged into, or replacing, the object holding most of its members. */
|
|
export const applyDocument = (
|
|
context: EffectContext,
|
|
draft: WriteDraft,
|
|
step: WriteStep,
|
|
): EffectResult => {
|
|
const tool = draft.plan.target!;
|
|
const initial = context.initialDocument(tool);
|
|
// Serving refuses a write at another path, so one here changes no document the state holds.
|
|
if (initial !== undefined && !namesDocument(step.record.path, initial.path))
|
|
return UNKNOWN;
|
|
const value = context.state.document(tool);
|
|
const body = requestObject(context, step.record);
|
|
const pointer =
|
|
value === undefined || body === undefined
|
|
? undefined
|
|
: writePointer(draft, value, body);
|
|
if (step.observeOnly || pointer === undefined) {
|
|
fail(draft, step, body === undefined ? "body-unusable" : "no-observation");
|
|
return UNKNOWN;
|
|
}
|
|
if (!built(draft)) buildDocument(draft, step, { pointer, body: body! });
|
|
const call = callOf(step, body!);
|
|
// Serving refuses a body its check refuses, so such a call cannot confirm the plan.
|
|
if (
|
|
documentBodyProblem(draft.plan, call, {
|
|
initial: initial?.value,
|
|
current: value!,
|
|
}) !== undefined
|
|
) {
|
|
refuse(draft, step, "unexplained-change");
|
|
return UNKNOWN;
|
|
}
|
|
const written = writtenDocument(draft.plan, value!, call);
|
|
if (!written.ok) {
|
|
refuse(draft, step, "no-observation");
|
|
return UNKNOWN;
|
|
}
|
|
const { ordinal } = step.record;
|
|
draft.plan.response ??= context.io.withBody(ordinal, (response) => ({
|
|
ordinal,
|
|
shape: context.io.emptyResponse(ordinal)
|
|
? "empty"
|
|
: response !== undefined && same(response, written.value)
|
|
? "entity"
|
|
: "recorded",
|
|
pointer: null,
|
|
}));
|
|
context.io.retain(64 + jsonTextCodeUnits(written.value) * 2);
|
|
context.state.setDocument(tool, written.value);
|
|
const fields = writtenPointers(draft, body!, {
|
|
before: value!,
|
|
after: written.value,
|
|
});
|
|
const replaced = draft.plan.merge === "merge" ? undefined : pointer;
|
|
return {
|
|
kind: "applied",
|
|
targets: [documentTarget(tool)],
|
|
...(fields === undefined ? {} : { fields, pointers: true }),
|
|
...(fields === undefined || replaced === undefined
|
|
? {}
|
|
: { overwrites: [replaced] }),
|
|
};
|
|
};
|