mirror of
https://github.com/morluto/rea.git
synced 2026-11-14 10:43:30 +00:00
feat(environment): serve query numbers, counted values and made objects
- A query parameter accepts a number or boolean, which serving sends as its JSON text, as agents send page sizes and flags. - A sequence may start at the value its first create took above every value its type held (Vikunja's per-project task index), and a later create's composed, template or started-sequence rule is adopted when it also spells the first create's value. - A made-object rule explains an object the application makes with each created entity: members the request stamped, members holding the entity's own id, and counts one past the held objects' values. - A create's answer names the id of the one object a one-element array member holds, so bulk creates link to their type. - The skill reads each listed object by id before writing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4ede130550
commit
c0cbf95c9c
29 files changed
+831
-38
No files matched your search
@@ -1,13 +1,24 @@
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
import { ownMember, same, type JsonObject } from "./environmentStateRules.js";
|
||||
import {
|
||||
isObject,
|
||||
ownMember,
|
||||
same,
|
||||
scalarText,
|
||||
type JsonObject,
|
||||
} from "./environmentStateRules.js";
|
||||
import { storedKinds } from "./environmentStateWriteApply.js";
|
||||
import { fittedClockFormat } from "./environmentStateWriteClockFormats.js";
|
||||
import { composedValue } from "./environmentStateWriteComposed.js";
|
||||
import {
|
||||
alikeRules,
|
||||
isBlankValue,
|
||||
type FieldRule,
|
||||
type WriteBody,
|
||||
} from "./environmentStateWriteRules.js";
|
||||
import {
|
||||
containsDelimited,
|
||||
sequenceNumber,
|
||||
} from "./environmentStateWriteValues.js";
|
||||
|
||||
type Default = { readonly member: string; readonly value: JsonValue };
|
||||
type Blank = NonNullable<WriteBody["blank"]>[number];
|
||||
@@ -16,20 +27,25 @@ type Blank = NonNullable<WriteBody["blank"]>[number];
|
||||
export interface EarlierEvidence {
|
||||
readonly kept: ReadonlySet<string>;
|
||||
readonly clockOf: (field: string) => JsonValue | undefined;
|
||||
/** The integer the exemplar showed in an unruled field above every value its type held, where a sequence may start. */
|
||||
readonly startOf?: (field: string) => number | undefined;
|
||||
}
|
||||
|
||||
/** The plan's rule for a field and a later call's agree: equal, or clocks one spelling covers given what earlier calls showed. */
|
||||
/** The plan's rule for a field and a later call's agree: equal, clocks one spelling covers given what earlier calls showed, or made objects alike but in the counts only the later call could show. */
|
||||
const agreeing = (
|
||||
held: FieldRule,
|
||||
later: FieldRule,
|
||||
values: {
|
||||
readonly field: string;
|
||||
readonly later: JsonValue | undefined;
|
||||
readonly exemplar: JsonValue | undefined;
|
||||
readonly earlier: EarlierEvidence;
|
||||
},
|
||||
): FieldRule | undefined => {
|
||||
const alike = alikeRules(held, later, values.earlier.kept);
|
||||
if (alike !== undefined) return alike;
|
||||
if (held.rule === "made-object" && later.rule === "made-object")
|
||||
return madeAlike(held, later, values.exemplar);
|
||||
if (held.rule !== "clock" || later.rule !== "clock") return undefined;
|
||||
const format = fittedClockFormat(
|
||||
held.format,
|
||||
@@ -39,6 +55,37 @@ const agreeing = (
|
||||
return format === undefined ? undefined : { ...held, format };
|
||||
};
|
||||
|
||||
type MadeObject = Extract<FieldRule, { rule: "made-object" }>;
|
||||
|
||||
/** Two made-object rules as one: the same clocks, format and own members, and the earlier counts among the later's; a count only the later call shows starts at the exemplar's value, which no earlier object could show past. */
|
||||
const madeAlike = (
|
||||
held: MadeObject,
|
||||
later: MadeObject,
|
||||
exemplar: JsonValue | undefined,
|
||||
): MadeObject | undefined => {
|
||||
if (
|
||||
!same(held.clocks, later.clocks) ||
|
||||
!same(held.own, later.own) ||
|
||||
!same(held.format, later.format) ||
|
||||
held.counts.some(
|
||||
({ member }) => !later.counts.some((count) => count.member === member),
|
||||
)
|
||||
)
|
||||
return undefined;
|
||||
const counts: MadeObject["counts"] = [];
|
||||
for (const { member } of later.counts) {
|
||||
const kept = held.counts.find((count) => count.member === member);
|
||||
const start =
|
||||
kept?.start ??
|
||||
sequenceNumber(
|
||||
isObject(exemplar) ? ownMember(exemplar, member) : undefined,
|
||||
);
|
||||
if (start === undefined) return undefined;
|
||||
counts.push({ member, start });
|
||||
}
|
||||
return { ...held, counts };
|
||||
};
|
||||
|
||||
/** Defaults joined, undefined when two calls showed one member left out at different values. */
|
||||
const joinedDefaults = (
|
||||
held: readonly Default[],
|
||||
@@ -53,6 +100,33 @@ const joinedDefaults = (
|
||||
return [...joined].map(([member, value]) => ({ member, value }));
|
||||
};
|
||||
|
||||
/** A later call's rule for a field the plan kept as shown, when it spells the exemplar's value too: a string composed from the exemplar's fields or holding the value it templates, or a sequence that started at its value. */
|
||||
const spellsExemplar = (
|
||||
field: string,
|
||||
rule: FieldRule,
|
||||
calls: Pick<FieldCalls, "exemplar" | "earlier">,
|
||||
): FieldRule | undefined => {
|
||||
const value = ownMember(calls.exemplar, field);
|
||||
if (rule.rule === "composed")
|
||||
return value !== undefined &&
|
||||
composedValue(rule.parts, calls.exemplar) === value
|
||||
? rule
|
||||
: undefined;
|
||||
if (rule.rule === "template") {
|
||||
const token = scalarText(ownMember(calls.exemplar, rule.from));
|
||||
return typeof value === "string" &&
|
||||
token !== undefined &&
|
||||
containsDelimited(value, token)
|
||||
? rule
|
||||
: undefined;
|
||||
}
|
||||
if (rule.rule !== "sequence") return undefined;
|
||||
const start = calls.earlier.startOf?.(field);
|
||||
return start !== undefined && sequenceNumber(value) === start
|
||||
? { ...rule, start }
|
||||
: undefined;
|
||||
};
|
||||
|
||||
/** What two calls show of one field. */
|
||||
interface FieldCalls {
|
||||
readonly before: FieldRule | undefined;
|
||||
@@ -81,6 +155,7 @@ const addedReference = (
|
||||
: agreeing(added, after, {
|
||||
field,
|
||||
later: later.entity[field],
|
||||
exemplar: ownMember(calls.exemplar, field),
|
||||
earlier: calls.earlier,
|
||||
});
|
||||
return rule === undefined ? undefined : { rule };
|
||||
@@ -114,6 +189,7 @@ const mergedField = (
|
||||
const rule = agreeing(before, after, {
|
||||
field,
|
||||
later: later.entity[field],
|
||||
exemplar: ownMember(exemplar, field),
|
||||
earlier: calls.earlier,
|
||||
});
|
||||
return rule === undefined ? undefined : { rule };
|
||||
@@ -126,6 +202,10 @@ const mergedField = (
|
||||
default: { member: field, value: later.entity[field]! },
|
||||
}
|
||||
: undefined;
|
||||
if (before === undefined && after !== undefined) {
|
||||
const rule = spellsExemplar(field, after, calls);
|
||||
if (rule !== undefined) return { rule };
|
||||
}
|
||||
// This call echoes a member the plan's calls left out: what they stored there is its default.
|
||||
if (after !== undefined)
|
||||
return after.rule === "echo" && !calls.required.includes(field)
|
||||
|
||||
Reference in new issue
Block a user