Let a module's code announce the states it keeps, in Go and TypeScript (issue 1790, S10, ADR 0495 §2 §5)
process.Conn.Keeps, stdio.Keeps and the TypeScript keeps publish the announcement on the module's own subject; afterwards a state the code did not announce is refused at the call, so announcement and code cannot drift. The package goes to 0.1.15 for keeps.
This commit is contained in:
+56
-1
@@ -6,6 +6,12 @@
|
||||
// (`reads`, as `<module>.<name>`), and names each here the way it named it there. Every instance of
|
||||
// the owner may write; a reader reads and watches. The runtime that launched the bundle does the work
|
||||
// and refuses, with the reason, what the module did not declare.
|
||||
//
|
||||
// A module also says on the bus, from its code, which states it keeps and which others may read
|
||||
// (`keeps`, novox/hq ADR 0495 clause 2): a reader is granted what the owner announced, never what a list
|
||||
// or the owner's manifest says (clause 3).
|
||||
|
||||
import { emit } from "../events/index.js";
|
||||
|
||||
/** One key's current value. */
|
||||
export interface StateEntry<T = unknown> {
|
||||
@@ -61,7 +67,7 @@ export function useStateChannel(c: StateChannel): void {
|
||||
channel = c;
|
||||
}
|
||||
|
||||
function bound(): StateChannel {
|
||||
function boundChannel(): StateChannel {
|
||||
if (!channel) {
|
||||
throw new Error(
|
||||
"no runtime to keep state through — a module's state is reached through the runtime that launched " +
|
||||
@@ -71,8 +77,57 @@ function bound(): StateChannel {
|
||||
return channel;
|
||||
}
|
||||
|
||||
/** One state a module keeps, by its local name, and whether other modules may read it. */
|
||||
export interface Kept {
|
||||
readonly name: string;
|
||||
readonly readable?: boolean;
|
||||
}
|
||||
|
||||
const stateName = /^[a-z0-9][a-z0-9-]*$/;
|
||||
let keptNames: Set<string> | undefined;
|
||||
|
||||
/**
|
||||
* Say on the bus, as this bundle's module, every state it keeps and which of them others may read
|
||||
* (novox/hq ADR 0495 clause 2; issue 1790): the announcement `states` on the module's own event subject,
|
||||
* `{v: 1, module, node, states}` with the states sorted by name, which the controller keeps and grants
|
||||
* readers from (clause 3). Call it as the bundle is imported. From then on a state of the module's own
|
||||
* that it did not announce is refused at the call, naming it, so code and announcement cannot drift
|
||||
* (clause 5). A state no bucket could be named by, or one given twice, is refused.
|
||||
*/
|
||||
export async function keeps(states: readonly Kept[]): Promise<void> {
|
||||
const module = process.env.MESH_MODULE || process.env.MESH_SERVED_MODULE || "";
|
||||
if (!stateName.test(module)) {
|
||||
throw new Error(`"${module}" is not a module that keeps state: lower-case letters, digits and hyphens`);
|
||||
}
|
||||
const names = new Set<string>();
|
||||
for (const k of states) {
|
||||
if (!stateName.test(k.name)) {
|
||||
throw new Error(`${module} announces the state "${k.name}", and a state is named locally: lower-case letters, digits and hyphens`);
|
||||
}
|
||||
if (names.has(k.name)) throw new Error(`${module} announces the state "${k.name}" twice`);
|
||||
names.add(k.name);
|
||||
}
|
||||
keptNames = names;
|
||||
const sorted = [...states]
|
||||
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
|
||||
.map((k) => (k.readable ? { name: k.name, readable: true } : { name: k.name }));
|
||||
const node = process.env.MESH_NODE;
|
||||
await emit("states", { v: 1, module, ...(node ? { node } : {}), states: sorted });
|
||||
}
|
||||
|
||||
/** The error of a call on a state of the module's own that `keeps` did not name, before the runtime is asked. */
|
||||
function refusedUnannounced(name: string): void {
|
||||
if (!name.includes(".") && keptNames && !keptNames.has(name)) {
|
||||
throw new Error(`the state "${name}" is not among the states this module announced it keeps (keeps)`);
|
||||
}
|
||||
}
|
||||
|
||||
/** A state this module keeps (by its local name) or reads (`<module>.<name>`). */
|
||||
export function state<T = unknown>(name: string): State<T> {
|
||||
const bound = (): StateChannel => {
|
||||
refusedUnannounced(name);
|
||||
return boundChannel();
|
||||
};
|
||||
return {
|
||||
get: async (key) => (await bound().ask("mesh/state.get", { state: name, key })) as StateEntry<T> | null,
|
||||
put: async (key, value) =>
|
||||
|
||||
Reference in New Issue
Block a user