novy-syndicate/src/runtime/dataspace.ts

137 lines
5.5 KiB
TypeScript

import { KeyedDictionary, preserves } from '@preserves/core';
import { AnyValue, Assertion, Entity, Handle, LocalAction, Ref, Turn } from 'runtime/actor';
import { fromObserve, Observe } from '../gen/dataspace';
import { DBind, DDiscard, DLit, Pattern, DCompound, CArr, CDict, CRec } from '../gen/dataspacePatterns';
export { asObserve, fromObserve, toObserve, Observe, $Observe } from '../gen/dataspace';
export * from '../gen/dataspacePatterns';
// Q. Why keep "Observe"? Why not do the clever trick of asserting the
// observer, and having the dataspace read the implicit pattern it's
// interested in off its attenuator?
//
// A. (1) Because we want to have the possibility of more than one
// variety of pattern language. For example, here we have a simple
// label match, but we'll quickly want something about as rich as the
// pattern language in attenuators. And later, we could easily want
// something with, perhaps, as much power as RELAX-NG or similar. (2)
// Because we want to have onlookers have some hope of seeing whether
// a pattern of interest to them is being observed, and if we used
// attenuators to match, we'd have to expose visibility into
// attenuators into the pattern language. See next question. (3)
// Because reflection on attenuators is a big, heavy hammer, and it's
// better to be explicit about patterns! Also, some attenuations
// happen behind a veil of secrecy - they're not all open for the
// world to read about. Actors may proxy communications in arbitrary,
// secret ways.
//
// Q. What kinds of constraints on the pattern language are there?
//
// A. It should be fast to evaluate; ideally, JITtable? It should
// allow patterns on patterns, to some degree. This is for two
// reasons: we occasionally want to observe observers, and we
// frequently want to use attenuators limit the kinds of patterns that
// a principal may observe. As such, it's good to choose a language
// that enforced some kind of normal forms for its patterns, so
// observer-observers and attenuator patterns don't have to deal with
// spurious variation.
export class Dataspace implements Partial<Entity> {
assert(turn: Turn, rec: Assertion, handle: Handle): void {
console.log(preserves`ds ${turn.activeFacet.id} assert ${rec} ${handle}`);
throw new Error("Full dataspaces not implemented");
}
retract(turn: Turn, upstreamHandle: Handle): void {
console.log(preserves`ds ${turn.activeFacet.id} retract ${upstreamHandle}`);
throw new Error("Full dataspaces not implemented");
}
message(turn: Turn, rec: Assertion): void {
console.log(preserves`ds ${turn.activeFacet.id} message ${rec}`);
throw new Error("Full dataspaces not implemented");
}
}
export function during(f: (t: Turn, a: Assertion) => Promise<LocalAction | null>): Partial<Entity> {
const assertionMap = new Map<Handle, LocalAction | 'dead'>();
return {
assert(t: Turn, a: Assertion, h: Handle): void {
f(t, a).then(g => {
if (g === null) g = _t => {};
switch (assertionMap.get(h)) {
case void 0:
assertionMap.set(h, g);
break;
case 'dead':
assertionMap.delete(h);
t.freshen(g);
break;
default:
console.error('during: Duplicate handle in assert: ' + h);
break;
}
});
},
retract(t: Turn, h: Handle): void {
const g = assertionMap.get(h);
switch (g) {
case void 0:
assertionMap.set(h, 'dead');
break;
case 'dead':
console.error('during: Duplicate handle in retract: ' + h);
break;
default:
assertionMap.delete(h);
g(t);
}
},
};
}
export function observe(t: Turn, ds: Ref, pattern: Pattern, e: Partial<Entity>): Handle {
return t.assert(ds, fromObserve(Observe({ pattern, observer: t.ref(e) })));
}
export namespace P {
export function discard(): Pattern {
return Pattern.DDiscard(DDiscard());
}
export function bind(name: string, pattern: Pattern = discard()): Pattern {
return Pattern.DBind(DBind({name: Symbol.for(name), pattern}));
}
export function lit(value: AnyValue): Pattern {
return Pattern.DLit(DLit(value));
}
export function rec(label: AnyValue, ...fields: Pattern[]): Pattern {
const members = new KeyedDictionary<number, Pattern, Ref>();
fields.forEach((f, i) => {
if (f._variant !== 'DDiscard') members.set(i, f);
});
return Pattern.DCompound(DCompound.rec({
ctor: CRec({ label, arity: fields.length }),
members,
}));
}
export function arr(...entries: Pattern[]): Pattern {
const members = new KeyedDictionary<number, Pattern, Ref>();
entries.forEach((f, i) => {
if (f._variant !== 'DDiscard') members.set(i, f);
});
return Pattern.DCompound(DCompound.arr({
ctor: CArr(entries.length),
members,
}));
}
export function dict(...entries: [AnyValue, Pattern][]): Pattern {
const members = new KeyedDictionary<AnyValue, Pattern, Ref>();
entries.forEach(([k, p]) => members.set(k, p));
return Pattern.DCompound(DCompound.dict({
ctor: CDict(),
members,
}));
}
}