Implement main-thread Camino sessions and synchronous CRDT editing
This commit is contained in:
@@ -148,6 +148,11 @@ const requireText = (issues: CapabilityValidationIssue[], value: string, path: s
|
||||
|
||||
const typeLabel = (type: ValueType): string => {
|
||||
switch (type.kind) {
|
||||
case "variant":
|
||||
return `variant:${type.id}{${Object.entries(type.cases)
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([label, entry]) => `${label}:${entry.id}:${typeLabel(entry.value)}`)
|
||||
.join(";")}}`;
|
||||
case "builtin":
|
||||
case "scalar":
|
||||
return type.name;
|
||||
@@ -176,6 +181,19 @@ export const valueTypesEqual = (left: ValueType, right: ValueType): boolean => {
|
||||
return false;
|
||||
}
|
||||
switch (left.kind) {
|
||||
case "variant": {
|
||||
const other = right as typeof left;
|
||||
return (
|
||||
left.id === other.id &&
|
||||
Object.keys(left.cases).length === Object.keys(other.cases).length &&
|
||||
Object.entries(left.cases).every(
|
||||
([label, entry]) =>
|
||||
Object.hasOwn(other.cases, label) &&
|
||||
entry.id === other.cases[label].id &&
|
||||
valueTypesEqual(entry.value, other.cases[label].value),
|
||||
)
|
||||
);
|
||||
}
|
||||
case "builtin":
|
||||
case "scalar":
|
||||
return left.name === (right as typeof left).name;
|
||||
@@ -232,6 +250,20 @@ const validateValueType = (
|
||||
indexes: Pick<ValidationIndexes, "atoms" | "interfaces">,
|
||||
) => {
|
||||
switch (type.kind) {
|
||||
case "variant": {
|
||||
requireText(issues, type.id, `${path}.id`, "Variant identity");
|
||||
const entries = Object.entries(type.cases),
|
||||
ids = new Set<string>();
|
||||
if (!entries.length || entries.length > 256) issue(issues, "invalid-value-type", path, "Use 1–256 variant cases");
|
||||
for (const [label, entry] of entries) {
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(label)) issue(issues, "invalid-value-type", path, "Invalid variant label");
|
||||
requireText(issues, entry.id, `${path}.${label}.id`, "Case identity");
|
||||
if (ids.has(entry.id)) issue(issues, "invalid-value-type", path, "Duplicate case identity");
|
||||
ids.add(entry.id);
|
||||
validateValueType(issues, entry.value, `${path}.${label}`, indexes);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case "record":
|
||||
for (const [name, field] of Object.entries(type.fields)) {
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
|
||||
@@ -569,6 +601,7 @@ const collectIdentityIndexes = (
|
||||
if (member.kind === "value") {
|
||||
const queryValue = (type: ValueType): boolean =>
|
||||
type.kind === "scalar" ||
|
||||
(type.kind === "variant" && isStorableType(type)) ||
|
||||
((type.kind === "optional" || type.kind === "list") && queryValue(type.value)) ||
|
||||
(type.kind === "record" &&
|
||||
Object.keys(type.fields).length > 0 &&
|
||||
@@ -893,6 +926,7 @@ const validateAttachments = (issues: CapabilityValidationIssue[], indexes: Valid
|
||||
type.kind === "object-ref" ||
|
||||
type.kind === "change" ||
|
||||
(type.kind === "record" && Object.values(type.fields).some(containsRpcType)) ||
|
||||
(type.kind === "variant" && Object.values(type.cases).some((entry) => containsRpcType(entry.value))) ||
|
||||
((type.kind === "optional" || type.kind === "list") && containsRpcType(type.value));
|
||||
if (
|
||||
containsRpcType(attachment.valueType) ||
|
||||
@@ -999,6 +1033,26 @@ const validatePackages = (
|
||||
const exportPath = `${path}.exports[${exportIndex}]`;
|
||||
validateValueType(issues, entry.inputType, `${exportPath}.inputType`, indexes);
|
||||
validateValueType(issues, entry.outputType, `${exportPath}.outputType`, indexes);
|
||||
if (entry.preparation) {
|
||||
const port = entry.dependencyPorts.find((port) => port.id === entry.preparation!.port);
|
||||
if (
|
||||
entry.execution?.kind !== "transaction" ||
|
||||
entry.kind === "constructor" ||
|
||||
(entry.kind === "operation" && entry.mode !== "call") ||
|
||||
!entry.preparation.key ||
|
||||
port?.requirement.kind !== "creation" ||
|
||||
!valueTypesEqual(port.requirement.inputType, entry.inputType) ||
|
||||
entry.outputType.kind !== "object-ref" ||
|
||||
(entry.outputType.expectation.kind === "atom" &&
|
||||
entry.outputType.expectation.atomId !== port.requirement.atomId)
|
||||
)
|
||||
issue(
|
||||
issues,
|
||||
"invalid-dependency-binding",
|
||||
exportPath,
|
||||
"Prepared creation requires a transaction returning its declared creation, with matching captured input types and a fixed key",
|
||||
);
|
||||
}
|
||||
if (entry.acceptanceExportId !== undefined) {
|
||||
const check = revision.exports.find((candidate) => candidate.id === entry.acceptanceExportId);
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user