@camstack/system 1.2.313 → 1.2.314
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/dist/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/claim-gate.d.ts +32 -0
- package/dist/builtins/composer/composed-device-set.d.ts +38 -0
- package/dist/builtins/composer/composed-device.d.ts +4 -3
- package/dist/builtins/composer/composer-grafts.d.ts +54 -0
- package/dist/builtins/composer/composer-plan.d.ts +89 -0
- package/dist/builtins/composer/composer-verdict.d.ts +19 -0
- package/dist/builtins/composer/composer.addon.js +2500 -422
- package/dist/builtins/composer/composer.addon.mjs +2493 -415
- package/dist/builtins/composer/composer.d.ts +66 -51
- package/dist/builtins/composer/composition-runtime.d.ts +84 -9
- package/dist/builtins/composer/confirmed-seed.d.ts +32 -0
- package/dist/builtins/composer/existing-target.d.ts +87 -0
- package/dist/builtins/composer/field-record.d.ts +41 -0
- package/dist/builtins/composer/graft-host.d.ts +47 -0
- package/dist/builtins/composer/held-claims.d.ts +12 -0
- package/dist/builtins/composer/owned-fields-target.d.ts +55 -0
- package/dist/builtins/composer/slice-assembler.d.ts +10 -0
- package/dist/builtins/composer/source-readings.d.ts +122 -0
- package/dist/builtins/composer/source-tracker.d.ts +35 -42
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/composition-api.d.ts +7 -4
- package/dist/builtins/core-blocks/composition-peers.d.ts +9 -0
- package/dist/builtins/core-blocks/composition-sources.d.ts +14 -3
- package/dist/builtins/core-blocks/core-block-store.d.ts +8 -1
- package/dist/builtins/core-blocks/core-blocks.addon.d.ts +4 -3
- package/dist/builtins/core-blocks/core-blocks.addon.js +326 -50
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +326 -50
- package/dist/builtins/device-manager/claimed-status-overlay.d.ts +12 -0
- package/dist/builtins/device-manager/device-manager.addon.d.ts +16 -0
- package/dist/builtins/device-manager/device-manager.addon.js +1825 -317
- package/dist/builtins/device-manager/device-manager.addon.mjs +1825 -317
- package/dist/builtins/device-manager/device-provider-context.d.ts +24 -2
- package/dist/builtins/device-manager/device-row-store.d.ts +2 -0
- package/dist/builtins/device-manager/device-state-claim-views.d.ts +77 -0
- package/dist/builtins/device-manager/device-state-claims.d.ts +43 -0
- package/dist/builtins/device-manager/device-state-mirror.d.ts +145 -61
- package/dist/builtins/device-manager/field-claims-index.d.ts +69 -0
- package/dist/builtins/device-manager/field-ownership.d.ts +53 -0
- package/dist/builtins/device-manager/migrate-device.d.ts +27 -0
- package/dist/builtins/device-manager/migrate-guard.d.ts +7 -0
- package/dist/builtins/device-manager/migrate-hardware-state.d.ts +55 -0
- package/dist/builtins/device-manager/migration-refused.d.ts +23 -0
- package/dist/builtins/device-manager/mirror-row-writer.d.ts +138 -0
- package/dist/builtins/device-manager/runtime-state-persist-gate.d.ts +4 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -3
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -3
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{child-cap-dispatch-CHTiHbsv.mjs → child-cap-dispatch-AAltfBUH.mjs} +1484 -1417
- package/dist/{child-cap-dispatch-Dyn_7ezH.js → child-cap-dispatch-DzcUd_ct.js} +1503 -1424
- package/dist/{dist-CkRwnhbo.js → dist-BtNkk3Qz.js} +4577 -3824
- package/dist/{dist-Cj7pJmXq.mjs → dist-cH4t1ZEm.mjs} +4541 -3824
- package/dist/index.js +137 -8
- package/dist/index.mjs +133 -8
- package/dist/kernel/capability-registry.d.ts +43 -1
- package/dist/kernel/index.d.ts +3 -1
- package/dist/kernel/moleculer/device-cap-proxy.d.ts +0 -10
- package/dist/kernel/status-overlay.d.ts +18 -0
- package/dist/kernel/transport/claimed-get-status.d.ts +15 -0
- package/dist/kernel/transport/index.d.ts +2 -0
- package/dist/kernel/transport/local-child-registry.d.ts +23 -0
- package/dist/kernel/transport/parent-unowned-call.d.ts +25 -0
- package/dist/{retired-settings-keys-CGSuXhP8.js → retired-settings-keys-B7imb-qH.js} +1 -1
- package/dist/{retired-settings-keys-C1duPikd.mjs → retired-settings-keys-DQYHfZ0S.mjs} +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-BtNkk3Qz.js");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
|
+
let _camstack_types_node = require("@camstack/types/node");
|
|
5
6
|
let node_fs_promises = require("node:fs/promises");
|
|
6
7
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
7
8
|
let node_path = require("node:path");
|
|
@@ -340,7 +341,7 @@ var BlockSupervisor = class {
|
|
|
340
341
|
for (const { block } of compositions) await this.report(block.id, "failed", "no composer is available on this node — a composition cannot run here");
|
|
341
342
|
return;
|
|
342
343
|
}
|
|
343
|
-
if (runners.find((r) => r.name ===
|
|
344
|
+
if (runners.find((r) => r.name === require_dist.COMPOSER_RUNNER_ID)?.state === "failed") {
|
|
344
345
|
for (const { block } of compositions) await this.report(block.id, "failed", "the composer runner's crash circuit-breaker tripped — no composition runs until it is restarted");
|
|
345
346
|
return;
|
|
346
347
|
}
|
|
@@ -1013,15 +1014,103 @@ function armBlocksIntegration(options) {
|
|
|
1013
1014
|
})() };
|
|
1014
1015
|
}
|
|
1015
1016
|
//#endregion
|
|
1017
|
+
//#region src/builtins/core-blocks/composition-peers.ts
|
|
1018
|
+
/**
|
|
1019
|
+
* The stored compositions a save is judged against, as graph peers (D663,
|
|
1020
|
+
* ruling S2). Each is identified by its BLOCK ID, targets the device it writes
|
|
1021
|
+
* — `composedDeviceRef(block.id)` for a new device, the customized device for an
|
|
1022
|
+
* existing one — and, on an existing device, names the fields it REPLACES, so
|
|
1023
|
+
* its self-read of a replaced field is the native value and never an edge.
|
|
1024
|
+
*
|
|
1025
|
+
* `replaced` is not stored anywhere: it is recomputed at save time from the
|
|
1026
|
+
* peer's target description (`planComposition(..., target)`), read in the same
|
|
1027
|
+
* batch as the candidate's. A peer whose device could not be read has unknown
|
|
1028
|
+
* replaced fields: it is OVER-approximated — nothing replaced, every self-read
|
|
1029
|
+
* an edge (edges only added, never a cycle hidden) — and its device is listed
|
|
1030
|
+
* in `unread`, so the validator refuses a cyclic component only when it holds
|
|
1031
|
+
* a candidate node, and names the device (`findCandidateCycles`). An unreadable
|
|
1032
|
+
* device never blocks an unrelated save. A peer whose device is GONE is treated
|
|
1033
|
+
* the same way (M-1): a stored peer validated earlier may still read it, and
|
|
1034
|
+
* assuming every field replaced would remove edges.
|
|
1035
|
+
*
|
|
1036
|
+
* The candidate's own old version is kept in the list: the graph drops it BY ID
|
|
1037
|
+
* (`compositionEdges`), never by target — that is how a second block on
|
|
1038
|
+
* the same device reaches `duplicate-target`.
|
|
1039
|
+
*/
|
|
1040
|
+
function storedCompositions(blocks) {
|
|
1041
|
+
return blocks.flatMap((block) => {
|
|
1042
|
+
const composition = require_dist.coreBlockComposition(block);
|
|
1043
|
+
return composition === null ? [] : [{
|
|
1044
|
+
blockId: block.id,
|
|
1045
|
+
composition
|
|
1046
|
+
}];
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
/** The existing devices the stored customizations write — each read once with the candidate's. */
|
|
1050
|
+
function storedCustomizationTargets(blocks) {
|
|
1051
|
+
return storedCompositions(blocks).flatMap(({ composition }) => composition.target.kind === "existing" ? [composition.target.device] : []);
|
|
1052
|
+
}
|
|
1053
|
+
/** The fields a stored customization replaces, or why they are unknown. */
|
|
1054
|
+
function replacedOf(stored, description, lookupCap) {
|
|
1055
|
+
if (description === void 0) return { error: "it was not read" };
|
|
1056
|
+
if (description.kind === "unreadable") return { error: description.error };
|
|
1057
|
+
if (description.kind === "absent") return { error: "the device does not exist" };
|
|
1058
|
+
return require_dist.replacedFieldKeys(require_dist.planComposition(stored.composition, lookupCap, description));
|
|
1059
|
+
}
|
|
1060
|
+
function peerOf(stored, targets, lookupCap, candidateBlockId) {
|
|
1061
|
+
const { blockId, composition } = stored;
|
|
1062
|
+
if (composition.target.kind === "new") return {
|
|
1063
|
+
peer: {
|
|
1064
|
+
blockId,
|
|
1065
|
+
target: require_dist.composedDeviceRef(blockId),
|
|
1066
|
+
composition
|
|
1067
|
+
},
|
|
1068
|
+
unread: null
|
|
1069
|
+
};
|
|
1070
|
+
const device = composition.target.device;
|
|
1071
|
+
const key = require_dist.compositionSourceKey(device);
|
|
1072
|
+
const replaced = replacedOf(stored, targets.get(key), lookupCap);
|
|
1073
|
+
if (!("error" in replaced)) return {
|
|
1074
|
+
peer: {
|
|
1075
|
+
blockId,
|
|
1076
|
+
target: device,
|
|
1077
|
+
composition,
|
|
1078
|
+
replaced
|
|
1079
|
+
},
|
|
1080
|
+
unread: null
|
|
1081
|
+
};
|
|
1082
|
+
return {
|
|
1083
|
+
peer: {
|
|
1084
|
+
blockId,
|
|
1085
|
+
target: device,
|
|
1086
|
+
composition,
|
|
1087
|
+
replaced: /* @__PURE__ */ new Set()
|
|
1088
|
+
},
|
|
1089
|
+
unread: blockId === candidateBlockId ? null : [key, replaced.error]
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
function storedPeers(blocks, targets, lookupCap, candidateBlockId) {
|
|
1093
|
+
const read = storedCompositions(blocks).map((stored) => peerOf(stored, targets, lookupCap, candidateBlockId));
|
|
1094
|
+
return {
|
|
1095
|
+
peers: read.map((r) => r.peer),
|
|
1096
|
+
unread: new Map(read.flatMap((r) => r.unread === null ? [] : [r.unread]))
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
//#endregion
|
|
1016
1100
|
//#region src/builtins/core-blocks/composition-api.ts
|
|
1017
1101
|
/**
|
|
1018
1102
|
* The composition half of the `core-blocks` provider (D659). Flat methods,
|
|
1019
1103
|
* spread into the provider: no nested sub-object.
|
|
1020
1104
|
*
|
|
1021
|
-
* Every save is VALIDATED against the live sources
|
|
1022
|
-
*
|
|
1023
|
-
* and stores nothing. The store's
|
|
1024
|
-
* code block) propagate unchanged —
|
|
1105
|
+
* Every save is VALIDATED against the live sources, its existing target (read
|
|
1106
|
+
* once: slim row + `getBindings`) and every stored composition first; a refusal
|
|
1107
|
+
* names each problem by path and reason (D391) and stores nothing. The store's
|
|
1108
|
+
* own refusals (not loaded, unknown block, a code block) propagate unchanged —
|
|
1109
|
+
* this layer never swallows them.
|
|
1110
|
+
*
|
|
1111
|
+
* A customization of an existing device (D663) has no name of its own: the
|
|
1112
|
+
* server derives it (`customizationBlockName`), refuses one the client sends,
|
|
1113
|
+
* and never lets it move to another device. One per device, by block id (S2).
|
|
1025
1114
|
*/
|
|
1026
1115
|
var NOT_LOADED_REASON = "core_blocks has not loaded yet";
|
|
1027
1116
|
function writeQueue() {
|
|
@@ -1048,24 +1137,30 @@ function refuseIfChanged(deps, blockId, expectedUpdatedAt) {
|
|
|
1048
1137
|
} });
|
|
1049
1138
|
throw new Error(`core-blocks: the composition "${stored.name}" changed since you read it: stored updatedAt ${stored.updatedAt}, yours ${expectedUpdatedAt} — reload and re-apply your edit`);
|
|
1050
1139
|
}
|
|
1140
|
+
/** The device a composition writes, when it is an EXISTING one; null for a new device. */
|
|
1141
|
+
function existingTarget(composition) {
|
|
1142
|
+
return composition.target.kind === "existing" ? composition.target.device : null;
|
|
1143
|
+
}
|
|
1051
1144
|
async function validateAgainstStore(deps, composition, blockId) {
|
|
1052
1145
|
if (!deps.store.isLoaded()) throw new Error(`${NOT_LOADED_REASON} — a composition cannot be judged against the stored ones until it has; retry shortly`);
|
|
1053
1146
|
const self = blockId === null ? null : require_dist.composedDeviceRef(blockId);
|
|
1054
|
-
const
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
});
|
|
1061
|
-
const selfKey = self === null ? null : require_dist.compositionSourceKey(self);
|
|
1062
|
-
const refs = require_dist.compositionSourceRefs(composition).filter((r) => require_dist.compositionSourceKey(r) !== selfKey);
|
|
1063
|
-
const sources = await deps.describeSources(refs);
|
|
1147
|
+
const target = existingTarget(composition);
|
|
1148
|
+
const ownKeys = new Set([self, target].flatMap((ref) => ref === null ? [] : [require_dist.compositionSourceKey(ref)]));
|
|
1149
|
+
const refs = require_dist.compositionSourceRefs(composition).filter((r) => !ownKeys.has(require_dist.compositionSourceKey(r)));
|
|
1150
|
+
const blocks = deps.store.list();
|
|
1151
|
+
const read = await deps.describe(refs, [...target === null ? [] : [target], ...storedCustomizationTargets(blocks)]);
|
|
1152
|
+
const stored = storedPeers(blocks, read.targets, deps.lookupCap, blockId);
|
|
1064
1153
|
return require_dist.validateComposition(composition, {
|
|
1065
1154
|
lookupCap: deps.lookupCap,
|
|
1066
|
-
sources,
|
|
1155
|
+
sources: read.sources,
|
|
1067
1156
|
self,
|
|
1068
|
-
|
|
1157
|
+
blockId,
|
|
1158
|
+
target: target === null ? null : read.targets.get(require_dist.compositionSourceKey(target)) ?? {
|
|
1159
|
+
kind: "unreadable",
|
|
1160
|
+
error: "it was not read"
|
|
1161
|
+
},
|
|
1162
|
+
peers: stored.peers,
|
|
1163
|
+
unreadTargets: stored.unread
|
|
1069
1164
|
});
|
|
1070
1165
|
}
|
|
1071
1166
|
function describeProblems(validation) {
|
|
@@ -1100,6 +1195,16 @@ async function readStates(deps, blockIds) {
|
|
|
1100
1195
|
}]));
|
|
1101
1196
|
}
|
|
1102
1197
|
}
|
|
1198
|
+
/**
|
|
1199
|
+
* One block's composer state. A block the answer leaves out is UNREACHABLE,
|
|
1200
|
+
* never folded into "no composition" (D315, M-4).
|
|
1201
|
+
*/
|
|
1202
|
+
async function readState(deps, blockId) {
|
|
1203
|
+
return (await readStates(deps, [blockId])).get(blockId) ?? {
|
|
1204
|
+
kind: "unreachable",
|
|
1205
|
+
error: "the composer state read returned no entry for the block"
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1103
1208
|
function toViews(blocks, states) {
|
|
1104
1209
|
return blocks.flatMap((block) => {
|
|
1105
1210
|
const state = states.get(block.id);
|
|
@@ -1109,6 +1214,98 @@ function toViews(blocks, states) {
|
|
|
1109
1214
|
}];
|
|
1110
1215
|
});
|
|
1111
1216
|
}
|
|
1217
|
+
var NO_NAME_OF_ITS_OWN = "a customization has no name of its own — the device's name is shown; omit `name`";
|
|
1218
|
+
var NEW_DEVICE_NEEDS_NAME = "a new device needs a name";
|
|
1219
|
+
var DEVICE_IS_FIXED = "a customization's device cannot be changed — delete it and customize the other device";
|
|
1220
|
+
var NEVER_BECOMES_CUSTOMIZATION = "a composed device cannot become the customization of another device — create the customization instead";
|
|
1221
|
+
/**
|
|
1222
|
+
* The numeric id of a save's existing target, for the refusal line's tag
|
|
1223
|
+
* (D391: a line about a device carries `tags.deviceId`). Read only on a
|
|
1224
|
+
* refusal — one listing — and a failed read names itself on the line instead.
|
|
1225
|
+
*/
|
|
1226
|
+
async function resolveTarget(deps, target) {
|
|
1227
|
+
try {
|
|
1228
|
+
const found = (await deps.describe([], [target])).targets.get(require_dist.compositionSourceKey(target));
|
|
1229
|
+
if (found?.kind === "found") return {
|
|
1230
|
+
kind: "device",
|
|
1231
|
+
deviceId: found.deviceId
|
|
1232
|
+
};
|
|
1233
|
+
return {
|
|
1234
|
+
kind: "unresolved",
|
|
1235
|
+
why: found?.kind ?? "not described"
|
|
1236
|
+
};
|
|
1237
|
+
} catch (err) {
|
|
1238
|
+
return {
|
|
1239
|
+
kind: "unresolved",
|
|
1240
|
+
why: require_dist.errMsg(err)
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
/** A save refused before validation, by name (D391): logged, then thrown. */
|
|
1245
|
+
async function refuseSave(deps, subject, target, reason) {
|
|
1246
|
+
const resolved = target === null ? null : await resolveTarget(deps, target);
|
|
1247
|
+
deps.logger.warn("composition save refused", {
|
|
1248
|
+
...resolved?.kind === "device" ? { tags: { deviceId: resolved.deviceId } } : {},
|
|
1249
|
+
meta: {
|
|
1250
|
+
name: subject.name,
|
|
1251
|
+
blockId: subject.blockId,
|
|
1252
|
+
target: target === null ? null : require_dist.compositionSourceKey(target),
|
|
1253
|
+
...resolved?.kind === "unresolved" ? { targetUnresolved: resolved.why } : {},
|
|
1254
|
+
reason
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
throw new Error(`core-blocks: the composition "${subject.name}" was refused — ${reason}`);
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* The name a new block is stored under (rule 1): a new device's own, required;
|
|
1261
|
+
* a customization's DERIVED from its device (ruling S2), never the client's.
|
|
1262
|
+
*/
|
|
1263
|
+
async function nameOnCreate(deps, block) {
|
|
1264
|
+
const target = existingTarget(block.composition);
|
|
1265
|
+
if (target === null) {
|
|
1266
|
+
if (block.name === void 0) return refuseSave(deps, {
|
|
1267
|
+
name: "(unnamed)",
|
|
1268
|
+
blockId: null
|
|
1269
|
+
}, null, NEW_DEVICE_NEEDS_NAME);
|
|
1270
|
+
return block.name;
|
|
1271
|
+
}
|
|
1272
|
+
if (block.name !== void 0) return refuseSave(deps, {
|
|
1273
|
+
name: block.name,
|
|
1274
|
+
blockId: null
|
|
1275
|
+
}, target, NO_NAME_OF_ITS_OWN);
|
|
1276
|
+
return (0, _camstack_types_node.customizationBlockName)(target);
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* A customization is pinned to its device and carries no client name (rule 2);
|
|
1280
|
+
* a composed device never turns into one — its name would be the client's. An
|
|
1281
|
+
* unknown or code block falls through to the store, whose refusal names it.
|
|
1282
|
+
*/
|
|
1283
|
+
async function refuseRetarget(deps, blockId, patch) {
|
|
1284
|
+
const stored = deps.store.get(blockId);
|
|
1285
|
+
const current = stored === null ? null : require_dist.coreBlockComposition(stored);
|
|
1286
|
+
if (stored === null || current === null) return;
|
|
1287
|
+
const subject = {
|
|
1288
|
+
name: stored.name,
|
|
1289
|
+
blockId
|
|
1290
|
+
};
|
|
1291
|
+
const pinned = existingTarget(current);
|
|
1292
|
+
const next = patch.composition === void 0 ? pinned : existingTarget(patch.composition);
|
|
1293
|
+
if (pinned === null) {
|
|
1294
|
+
if (next !== null) return refuseSave(deps, subject, next, NEVER_BECOMES_CUSTOMIZATION);
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
if (patch.name !== void 0) return refuseSave(deps, subject, pinned, NO_NAME_OF_ITS_OWN);
|
|
1298
|
+
if (next === null || require_dist.compositionSourceKey(next) !== require_dist.compositionSourceKey(pinned)) return refuseSave(deps, subject, pinned, DEVICE_IS_FIXED);
|
|
1299
|
+
}
|
|
1300
|
+
/** The block customizing `device`, found by TARGET — never by its derived name (rule 4). */
|
|
1301
|
+
function customizationOf(blocks, device) {
|
|
1302
|
+
const key = require_dist.compositionSourceKey(device);
|
|
1303
|
+
return blocks.find((block) => {
|
|
1304
|
+
const composition = require_dist.coreBlockComposition(block);
|
|
1305
|
+
const target = composition === null ? null : existingTarget(composition);
|
|
1306
|
+
return target !== null && require_dist.compositionSourceKey(target) === key;
|
|
1307
|
+
}) ?? null;
|
|
1308
|
+
}
|
|
1112
1309
|
/**
|
|
1113
1310
|
* Every write runs validate-then-persist on ONE chain. Validation awaits a
|
|
1114
1311
|
* source read before the save lands; two unserialized saves (A reads B, B
|
|
@@ -1119,12 +1316,16 @@ function buildCompositionApi(deps) {
|
|
|
1119
1316
|
const serialized = writeQueue();
|
|
1120
1317
|
return {
|
|
1121
1318
|
createComposition: ({ block, caller }) => serialized(async () => {
|
|
1319
|
+
const name = await nameOnCreate(deps, block);
|
|
1122
1320
|
const validation = await validateAgainstStore(deps, block.composition, null);
|
|
1123
1321
|
if (!validation.ok) refuse(deps, {
|
|
1124
|
-
name
|
|
1322
|
+
name,
|
|
1125
1323
|
blockId: null
|
|
1126
1324
|
}, validation);
|
|
1127
|
-
const created = await deps.store.createComposition(
|
|
1325
|
+
const created = await deps.store.createComposition({
|
|
1326
|
+
...block,
|
|
1327
|
+
name
|
|
1328
|
+
}, caller.userId);
|
|
1128
1329
|
deps.logger.info("composition created", { meta: {
|
|
1129
1330
|
blockId: created.id,
|
|
1130
1331
|
name: created.name,
|
|
@@ -1135,6 +1336,7 @@ function buildCompositionApi(deps) {
|
|
|
1135
1336
|
}),
|
|
1136
1337
|
updateComposition: ({ blockId, expectedUpdatedAt, block, caller }) => serialized(async () => {
|
|
1137
1338
|
refuseIfChanged(deps, blockId, expectedUpdatedAt);
|
|
1339
|
+
await refuseRetarget(deps, blockId, block);
|
|
1138
1340
|
if (block.composition !== void 0) {
|
|
1139
1341
|
const validation = await validateAgainstStore(deps, block.composition, blockId);
|
|
1140
1342
|
if (!validation.ok) refuse(deps, {
|
|
@@ -1160,10 +1362,12 @@ function buildCompositionApi(deps) {
|
|
|
1160
1362
|
state: "loaded",
|
|
1161
1363
|
view: null
|
|
1162
1364
|
};
|
|
1163
|
-
const [view] = toViews([block], await readStates(deps, [blockId]));
|
|
1164
1365
|
return {
|
|
1165
1366
|
state: "loaded",
|
|
1166
|
-
view:
|
|
1367
|
+
view: {
|
|
1368
|
+
block,
|
|
1369
|
+
state: await readState(deps, blockId)
|
|
1370
|
+
}
|
|
1167
1371
|
};
|
|
1168
1372
|
},
|
|
1169
1373
|
listCompositions: async () => {
|
|
@@ -1181,18 +1385,45 @@ function buildCompositionApi(deps) {
|
|
|
1181
1385
|
views: toViews(blocks, await readStates(deps, blocks.map((b) => b.id)))
|
|
1182
1386
|
};
|
|
1183
1387
|
},
|
|
1388
|
+
getCustomization: async ({ addonId, stableId }) => {
|
|
1389
|
+
if (!deps.store.isLoaded()) return {
|
|
1390
|
+
state: "not-loaded",
|
|
1391
|
+
reason: NOT_LOADED_REASON
|
|
1392
|
+
};
|
|
1393
|
+
const block = customizationOf(deps.store.list(), {
|
|
1394
|
+
addonId,
|
|
1395
|
+
stableId
|
|
1396
|
+
});
|
|
1397
|
+
if (block === null) return {
|
|
1398
|
+
state: "loaded",
|
|
1399
|
+
view: null
|
|
1400
|
+
};
|
|
1401
|
+
return {
|
|
1402
|
+
state: "loaded",
|
|
1403
|
+
view: {
|
|
1404
|
+
block,
|
|
1405
|
+
state: await readState(deps, block.id)
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
},
|
|
1184
1409
|
validateComposition: async ({ composition, blockId }) => validateAgainstStore(deps, composition, blockId ?? null)
|
|
1185
1410
|
};
|
|
1186
1411
|
}
|
|
1187
1412
|
//#endregion
|
|
1188
1413
|
//#region src/builtins/core-blocks/composition-sources.ts
|
|
1189
1414
|
/**
|
|
1190
|
-
* Describe a composition
|
|
1191
|
-
* `listAll` per owning addon, one `getBindings` per found
|
|
1192
|
-
*
|
|
1415
|
+
* Describe what a composition reads and writes for SAVE-time validation
|
|
1416
|
+
* (D659, D663): one `listAll` per owning addon, one `getBindings` per found
|
|
1417
|
+
* device — shared by a device that is both a source and a target. A read that
|
|
1418
|
+
* fails makes the device `unreadable`, and the save is REFUSED rather than
|
|
1193
1419
|
* stored unchecked. Refusing a save destroys nothing (D49).
|
|
1194
1420
|
*
|
|
1195
|
-
*
|
|
1421
|
+
* A SOURCE is described by the caps bound to it; an EXISTING TARGET by its row
|
|
1422
|
+
* type and each bound cap with the addon serving it — what `planComposition`
|
|
1423
|
+
* needs to tell a replace from an add (the composer reads it the same way, from
|
|
1424
|
+
* the same two reads: the slim row and `getBindings`).
|
|
1425
|
+
*
|
|
1426
|
+
* Devices are keyed by `{addonId, stableId}` — never a numeric device id,
|
|
1196
1427
|
* which is this node's allocation and not the composition's identity.
|
|
1197
1428
|
*/
|
|
1198
1429
|
async function listOwners(ports, refs) {
|
|
@@ -1206,7 +1437,7 @@ async function listOwners(ports, refs) {
|
|
|
1206
1437
|
}));
|
|
1207
1438
|
return new Map(listed);
|
|
1208
1439
|
}
|
|
1209
|
-
|
|
1440
|
+
function rowOf(ref, listing) {
|
|
1210
1441
|
if (listing === void 0) return {
|
|
1211
1442
|
kind: "unreadable",
|
|
1212
1443
|
error: `\`${ref.addonId}\` was not listed`
|
|
@@ -1216,24 +1447,68 @@ async function describeOne(ports, ref, listing) {
|
|
|
1216
1447
|
error: listing.error
|
|
1217
1448
|
};
|
|
1218
1449
|
const row = listing.find((r) => r.stableId === ref.stableId);
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1450
|
+
return row === void 0 ? { kind: "absent" } : {
|
|
1451
|
+
kind: "row",
|
|
1452
|
+
row
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
/** ONE `getBindings` per found device, whether it is read as a source, a target, or both. */
|
|
1456
|
+
async function readBindings(ports, deviceIds) {
|
|
1457
|
+
const read = await Promise.all([...new Set(deviceIds)].map(async (deviceId) => {
|
|
1458
|
+
try {
|
|
1459
|
+
return [deviceId, await ports.bindings(deviceId)];
|
|
1460
|
+
} catch (err) {
|
|
1461
|
+
return [deviceId, { error: require_dist.errMsg(err) }];
|
|
1462
|
+
}
|
|
1463
|
+
}));
|
|
1464
|
+
return new Map(read);
|
|
1465
|
+
}
|
|
1466
|
+
function describeSource(lookup, bindings) {
|
|
1467
|
+
if (lookup.kind !== "row") return lookup;
|
|
1468
|
+
const bound = bindings.get(lookup.row.id);
|
|
1469
|
+
if (bound === void 0) return {
|
|
1470
|
+
kind: "unreadable",
|
|
1471
|
+
error: "its bindings were not read"
|
|
1472
|
+
};
|
|
1473
|
+
if ("error" in bound) return {
|
|
1474
|
+
kind: "unreadable",
|
|
1475
|
+
error: bound.error
|
|
1476
|
+
};
|
|
1477
|
+
return {
|
|
1478
|
+
kind: "found",
|
|
1479
|
+
deviceId: lookup.row.id,
|
|
1480
|
+
caps: new Set(bound.map((b) => b.capName))
|
|
1481
|
+
};
|
|
1232
1482
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
const
|
|
1236
|
-
|
|
1483
|
+
function describeTarget(lookup, bindings) {
|
|
1484
|
+
if (lookup.kind !== "row") return lookup;
|
|
1485
|
+
const { id, type } = lookup.row;
|
|
1486
|
+
const bound = bindings.get(id);
|
|
1487
|
+
if (bound === void 0) return {
|
|
1488
|
+
kind: "unreadable",
|
|
1489
|
+
error: "its bindings were not read"
|
|
1490
|
+
};
|
|
1491
|
+
if ("error" in bound) return {
|
|
1492
|
+
kind: "unreadable",
|
|
1493
|
+
error: bound.error
|
|
1494
|
+
};
|
|
1495
|
+
return {
|
|
1496
|
+
kind: "found",
|
|
1497
|
+
deviceId: id,
|
|
1498
|
+
type,
|
|
1499
|
+
nativeCaps: new Map(bound.map((b) => [b.capName, b.nativeAddonId]))
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
async function describeCompositionSources(ports, sources, targets = []) {
|
|
1503
|
+
const owners = await listOwners(ports, [...sources, ...targets]);
|
|
1504
|
+
const lookup = (ref) => [require_dist.compositionSourceKey(ref), rowOf(ref, owners.get(ref.addonId))];
|
|
1505
|
+
const sourceRows = sources.map(lookup);
|
|
1506
|
+
const targetRows = targets.map(lookup);
|
|
1507
|
+
const bindings = await readBindings(ports, [...sourceRows, ...targetRows].flatMap(([, l]) => l.kind === "row" ? [l.row.id] : []));
|
|
1508
|
+
return {
|
|
1509
|
+
sources: new Map(sourceRows.map(([key, l]) => [key, describeSource(l, bindings)])),
|
|
1510
|
+
targets: new Map(targetRows.map(([key, l]) => [key, describeTarget(l, bindings)]))
|
|
1511
|
+
};
|
|
1237
1512
|
}
|
|
1238
1513
|
//#endregion
|
|
1239
1514
|
//#region src/builtins/core-blocks/core-blocks.addon.ts
|
|
@@ -1507,9 +1782,10 @@ var CoreBlocksAddon = class extends require_dist.BaseAddon {
|
|
|
1507
1782
|
};
|
|
1508
1783
|
}
|
|
1509
1784
|
/**
|
|
1510
|
-
* The composition methods (D659), flat. Sources
|
|
1511
|
-
* SAVE time — one listing per owning addon, one binding
|
|
1512
|
-
* device — and a read that fails refuses the save rather than
|
|
1785
|
+
* The composition methods (D659), flat. Sources and existing targets are read
|
|
1786
|
+
* over `ctx.api` at SAVE time — one slim listing per owning addon, one binding
|
|
1787
|
+
* read per found device — and a read that fails refuses the save rather than
|
|
1788
|
+
* skipping it.
|
|
1513
1789
|
*/
|
|
1514
1790
|
buildCompositionApi() {
|
|
1515
1791
|
const requireApi = () => {
|
|
@@ -1519,13 +1795,13 @@ var CoreBlocksAddon = class extends require_dist.BaseAddon {
|
|
|
1519
1795
|
};
|
|
1520
1796
|
return buildCompositionApi({
|
|
1521
1797
|
store: this.requireStore(),
|
|
1522
|
-
|
|
1798
|
+
describe: (sources, targets) => describeCompositionSources({
|
|
1523
1799
|
listByAddon: async (addonId) => requireApi().deviceManager.listAll.query({
|
|
1524
1800
|
addonId,
|
|
1525
1801
|
projection: "slim"
|
|
1526
1802
|
}),
|
|
1527
|
-
|
|
1528
|
-
},
|
|
1803
|
+
bindings: async (deviceId) => (await requireApi().deviceManager.getBindings.query({ deviceId })).entries
|
|
1804
|
+
}, sources, targets),
|
|
1529
1805
|
lookupCap: require_dist.buildCapabilityLookup(require_dist.ALL_CAPABILITY_DEFINITIONS),
|
|
1530
1806
|
composerStates: async (blockIds) => (await requireApi().composer.getStates.query({ blockIds: [...blockIds] })).states,
|
|
1531
1807
|
convergeSoon: () => this.convergeSoon(),
|