Add checked React field bindings and Web Studio factories
Replace opaque props with checked generic presentation contracts and lazy typed interface references. Generate readonly/writable field APIs and component checks. Preserve CRDT editing through explicit resolved getter/setter contracts, binding- fenced delta RPCs, native watches and replica-aware field adapters. Custom setters retain semantic writes; storage snapshots never grant write authority. Cover concurrent edits, lost acknowledgements, readonly contracts and authorization. Add receiver-free static factory dispatch, state-field binding shorthand, and conformance-based creation. Migrate TODO, editable scaffolds and authoring guides. Verify language/codegen, SDK, RPC, browser lifecycle, local scaffolds, production browser bundling and CRDT persistence with temporary PostgreSQL.
This commit is contained in:
@@ -23,6 +23,7 @@ import type {
|
||||
OwnedAttachment,
|
||||
PackageExport,
|
||||
PackageOperationExport,
|
||||
PackageFunctionExport,
|
||||
PackageRevision,
|
||||
PackageRevisionId,
|
||||
PersistentAttachment,
|
||||
@@ -1423,6 +1424,24 @@ const validateConformances = (
|
||||
}
|
||||
const operation = operationEntry.operation;
|
||||
const binding = entry.binding;
|
||||
if (operation.scope === "class" && (!conformance.id || operation.mode !== "call")) {
|
||||
issue(
|
||||
issues,
|
||||
"invalid-package-binding",
|
||||
bindingPath,
|
||||
"Class capabilities require an explicit conformance ID and call mode",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (operation.scope === "class" && binding.kind !== "package") {
|
||||
issue(
|
||||
issues,
|
||||
"invalid-package-binding",
|
||||
bindingPath,
|
||||
"Class capabilities must bind a free package function, not instance storage",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (binding.kind === "state") {
|
||||
const attachment = findAttachment(indexes, "state", binding.slotId);
|
||||
if (!attachment || attachment.attachment.kind !== "state") {
|
||||
@@ -1562,31 +1581,49 @@ const validateConformances = (
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (packageExport.kind !== "operation") {
|
||||
if (operation.scope === "class" ? packageExport.kind !== "function" : packageExport.kind !== "operation") {
|
||||
issue(
|
||||
issues,
|
||||
"invalid-package-binding",
|
||||
`${bindingPath}.binding.exportId`,
|
||||
`Package export ${binding.exportId} is ${packageExport.kind}, not an operation`,
|
||||
`Package export ${binding.exportId} must be ${operation.scope === "class" ? "a free function" : "an instance operation"}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (!signaturesMatch(operation, packageExport)) {
|
||||
if (
|
||||
!signaturesMatch(operation, {
|
||||
...packageExport,
|
||||
mode: packageExport.kind === "operation" ? packageExport.mode : "call",
|
||||
})
|
||||
) {
|
||||
issue(
|
||||
issues,
|
||||
"invalid-package-binding",
|
||||
`${bindingPath}.binding.exportId`,
|
||||
`Package export provides ${describeSignature(packageExport)}, but operation requires ${describeSignature(operation)}`,
|
||||
`Package export signature does not match ${describeSignature(operation)}`,
|
||||
);
|
||||
}
|
||||
if (packageExport.kind === "operation")
|
||||
validatePackageReceiver(issues, {
|
||||
entry: packageExport,
|
||||
atomId: conformance.atomId,
|
||||
path: `${bindingPath}.binding.exportId`,
|
||||
indexes,
|
||||
requirementGraph,
|
||||
graphSourceKey: key,
|
||||
});
|
||||
if (
|
||||
operation.scope === "class" &&
|
||||
(binding.dependencies.some((entry) => entry.binding.kind !== "constructor") ||
|
||||
packageExport.dependencyPorts.some((entry) => entry.requirement.kind !== "constructor"))
|
||||
) {
|
||||
issue(
|
||||
issues,
|
||||
"invalid-package-binding",
|
||||
bindingPath,
|
||||
"Class functions may inject constructors, not instance-dependent ports",
|
||||
);
|
||||
}
|
||||
validatePackageReceiver(issues, {
|
||||
entry: packageExport,
|
||||
atomId: conformance.atomId,
|
||||
path: `${bindingPath}.binding.exportId`,
|
||||
indexes,
|
||||
requirementGraph,
|
||||
graphSourceKey: key,
|
||||
});
|
||||
validateBoundDependencies(issues, {
|
||||
dependencies: binding.dependencies,
|
||||
dependencyPorts: packageExport.dependencyPorts,
|
||||
@@ -1919,7 +1956,7 @@ export type ResolvedOperationPlan =
|
||||
kind: "package";
|
||||
binding: Extract<Binding, { kind: "package" }>;
|
||||
packageRevision: PackageRevision;
|
||||
packageExport: PackageOperationExport;
|
||||
packageExport: PackageOperationExport | PackageFunctionExport;
|
||||
dependencies: Array<{
|
||||
port: DependencyPort;
|
||||
binding: DependencyBinding;
|
||||
@@ -1962,7 +1999,8 @@ export const resolveOperationPlan = (
|
||||
}
|
||||
const packageRevision = plan.packages.get(binding.packageRevisionId);
|
||||
const packageExport = packageRevision?.exports.find(
|
||||
(entry): entry is PackageOperationExport => entry.id === binding.exportId && entry.kind === "operation",
|
||||
(entry): entry is PackageOperationExport | PackageFunctionExport =>
|
||||
entry.id === binding.exportId && (entry.kind === "operation" || entry.kind === "function"),
|
||||
);
|
||||
if (!packageRevision || !packageExport) {
|
||||
return undefined;
|
||||
|
||||
Reference in New Issue
Block a user