nervur 0.3.0 → 0.6.0

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/being.mjs ADDED
@@ -0,0 +1,369 @@
1
+ import { OPEN, STRUCTURAL, coreClass } from './blueprint.mjs';
2
+ import { Program } from './program.mjs';
3
+ import { MEASURE, Stamp, Tools } from './tools.mjs';
4
+ import { Voice } from './voice.mjs';
5
+
6
+ const Compartment = ({ memory, voice, address, root, classOf }) => {
7
+ const held = (key) => memory.read(address, key);
8
+ const sealed = (key) => {
9
+ const kind = classOf(key);
10
+ return kind === undefined || OPEN.includes(kind) ? undefined : Tools.derive(root, kind);
11
+ };
12
+ const put = (key, value) => {
13
+ const claim = { address, key, value, version: (held(key)?.version ?? 0) + 1 };
14
+ return memory.write({ ...claim, sig: voice.sign(Tools.canon(claim)) });
15
+ };
16
+
17
+ return Object.freeze({
18
+ classOf,
19
+ list: (prefix) =>
20
+ memory.list(address, prefix).filter((key) => memory.read(address, key)?.value !== null),
21
+ read: (key) => {
22
+ const cell = held(key);
23
+ if (cell === undefined || cell.value === null || classOf(key) === undefined) {
24
+ return undefined;
25
+ }
26
+ const secret = sealed(key);
27
+ return secret === undefined ? cell.value : Tools.unseal(secret, cell.value);
28
+ },
29
+ write: (key, plain) => {
30
+ if (classOf(key) === undefined) {
31
+ return undefined;
32
+ }
33
+ if (plain === undefined) {
34
+ return put(key, null);
35
+ }
36
+ const secret = sealed(key);
37
+ return put(key, secret === undefined ? plain : Tools.seal(secret, plain));
38
+ },
39
+ });
40
+ };
41
+
42
+ const scoped = (mine, program) =>
43
+ Object.freeze({
44
+ classOf: mine.classOf,
45
+ list: mine.list,
46
+ read: mine.read,
47
+ write: (key, plain) =>
48
+ key.startsWith('refs/') || key.startsWith('handles/') || program?.declares(key) !== undefined
49
+ ? mine.write(key, plain)
50
+ : undefined,
51
+ });
52
+
53
+ const rootFor = ({ memory, voice, address }) => {
54
+ const cell = memory.read(address, `keyring/${Tools.address(voice.pk)}`);
55
+ const opened = cell === undefined ? undefined : voice.open(cell.value.wrapped);
56
+ return opened === undefined ? undefined : Buffer.from(opened, 'base64');
57
+ };
58
+
59
+ const Port = ({ voice, mine, modules, rights }) => {
60
+ const stamp = Stamp();
61
+ const lanes = new Map();
62
+ const send = async (to, op) => {
63
+ const turn = (lanes.get(to) ?? Promise.resolve()).then(() => {
64
+ const handle = mine.read(`handles/${to}`);
65
+ return modules.deliver(
66
+ Tools.envelope(handle === undefined ? voice : Voice.revive(handle.face), to, op, stamp()),
67
+ );
68
+ });
69
+ const settled = turn.then(
70
+ () => undefined,
71
+ () => undefined,
72
+ );
73
+ lanes.set(to, settled);
74
+ settled.then(() => {
75
+ if (lanes.get(to) === settled) {
76
+ lanes.delete(to);
77
+ }
78
+ });
79
+ return turn;
80
+ };
81
+ return Object.freeze({
82
+ face: (to) => {
83
+ const handle = mine.read(`handles/${to}`);
84
+ return Tools.address(handle === undefined ? voice.pk : Voice.revive(handle.face).pk);
85
+ },
86
+ mint: (to) => {
87
+ const minted = Voice();
88
+ mine.write(`handles/${to}`, { face: minted.pack() });
89
+ return { address: Tools.address(minted.pk), nextPkHash: minted.nextPkHash };
90
+ },
91
+ release: async (to) => {
92
+ const gone = await send(to, { release: {} });
93
+ mine.write(`handles/${to}`, undefined);
94
+ return gone;
95
+ },
96
+ rights,
97
+ send,
98
+ });
99
+ };
100
+
101
+ const rotates = ({ mine }, from, nextPkHash) => {
102
+ const claim = Tools.digest(Tools.pkOf(from));
103
+ const held = mine
104
+ .list('refs/')
105
+ .map((key) => ({ key, referred: mine.read(key) }))
106
+ .find(({ referred }) => referred?.nextPkHash === claim);
107
+ if (held === undefined) {
108
+ return undefined;
109
+ }
110
+ mine.write(`refs/${from}`, { nextPkHash, rights: held.referred.rights });
111
+ mine.write(held.key, undefined);
112
+ return from;
113
+ };
114
+
115
+ const Being = ({ voice, address, modules, registry, vouched, watch }) => {
116
+ const memory = modules.memory;
117
+ const root = rootFor({ memory, voice, address });
118
+ const source = memory.read(address, 'program')?.value;
119
+ const program = source === undefined ? undefined : Program(source);
120
+ const mine = Compartment({
121
+ memory,
122
+ voice,
123
+ address,
124
+ root,
125
+ classOf: (key) => program?.classOf(key) ?? coreClass(key),
126
+ });
127
+ const answer = (spoken) => {
128
+ const body = Tools.canon(spoken);
129
+ return body.length > MEASURE ? undefined : { body: spoken, sig: voice.sign(body) };
130
+ };
131
+ const note = (envelope, outcome) => {
132
+ try {
133
+ watch?.(
134
+ Object.freeze({
135
+ from: envelope?.from,
136
+ kind: envelope?.op === undefined ? undefined : Object.keys(envelope.op)[0],
137
+ outcome,
138
+ to: envelope?.to,
139
+ }),
140
+ );
141
+ } catch {
142
+ return undefined;
143
+ }
144
+ };
145
+
146
+ return Object.freeze(async (envelope, token) => {
147
+ let gated;
148
+ const spoken = await (async () => {
149
+ try {
150
+ const { from, op, seq, sig, to } = envelope;
151
+ const claim = Tools.canon({ from, op, seq, to });
152
+ const callerPk = Tools.pkOf(from);
153
+ const proven =
154
+ claim.length <= MEASURE &&
155
+ callerPk !== null &&
156
+ ((vouched !== undefined && token === vouched) || Tools.verifies(callerPk, claim, sig));
157
+ if (to !== address || !proven) {
158
+ gated = 'unproven';
159
+ return undefined;
160
+ }
161
+ const self = from === Tools.address(voice.pk);
162
+ const row = self ? undefined : mine.read(`refs/${from}`);
163
+ if (row !== undefined && !(vouched !== undefined && token === vouched)) {
164
+ if (!(seq > (row.seen ?? 0))) {
165
+ gated = 'replayed';
166
+ return undefined;
167
+ }
168
+ mine.write(`refs/${from}`, { ...row, seen: seq });
169
+ }
170
+ const rights = self ? 'SELF' : (row?.rights ?? 'STRANGER');
171
+ if (op?.describe !== undefined) {
172
+ return answer({
173
+ address,
174
+ digest: program?.digest,
175
+ rights,
176
+ sdl: program?.sdlFor(rights),
177
+ });
178
+ }
179
+ if (op?.introspect !== undefined) {
180
+ const seen = await program?.introspect(rights);
181
+ return seen === undefined ? undefined : answer({ introspection: seen });
182
+ }
183
+ if (op?.rotate !== undefined) {
184
+ const rotated = rotates({ mine }, from, op.rotate.nextPkHash);
185
+ return rotated === undefined ? undefined : answer({ rotated });
186
+ }
187
+ if (op?.release !== undefined) {
188
+ if (row === undefined) {
189
+ return undefined;
190
+ }
191
+ mine.write(`refs/${from}`, undefined);
192
+ return answer({ released: from });
193
+ }
194
+ if (op?.acceptInvite !== undefined) {
195
+ const offer = Tools.digest(Buffer.from(op.acceptInvite.proof, 'base64'));
196
+ const pending = mine.read(`refs/pending/${offer}`);
197
+ if (pending === undefined || pending.spent) {
198
+ return undefined;
199
+ }
200
+ mine.write(`refs/pending/${offer}`, { spent: true });
201
+ mine.write(`refs/${from}`, {
202
+ nextPkHash: op.acceptInvite.nextPkHash ?? null,
203
+ rights: pending.rights,
204
+ seen: seq ?? 0,
205
+ });
206
+ return answer({ referred: from, rights: pending.rights });
207
+ }
208
+ if (op?.ask === undefined || program === undefined) {
209
+ return undefined;
210
+ }
211
+ const asked = await program.ask({
212
+ context: {
213
+ modules: Object.freeze({ ...modules, memory: scoped(mine, program) }),
214
+ port: Port({ voice, mine, modules, rights }),
215
+ registry,
216
+ rights,
217
+ },
218
+ source: op.ask.source,
219
+ variables: op.ask.variables,
220
+ });
221
+ return asked === undefined ? undefined : answer(asked);
222
+ } catch {
223
+ return undefined;
224
+ }
225
+ })();
226
+ note(envelope, gated ?? (spoken === undefined ? 'mute' : 'answered'));
227
+ return spoken;
228
+ });
229
+ };
230
+
231
+ const make = ({ memory, voice, address, program }) => {
232
+ const root = Tools.secret();
233
+ const put = (key, value) => {
234
+ const claim = { address, key, value, version: (memory.read(address, key)?.version ?? 0) + 1 };
235
+ return memory.write({ ...claim, sig: voice.sign(Tools.canon(claim)) });
236
+ };
237
+ put('chain/pk', Buffer.from(voice.pk).toString('hex'));
238
+ put('chain/nextPkHash', voice.nextPkHash);
239
+ put('program', program ?? '({})');
240
+ put(`keyring/${Tools.address(voice.pk)}`, {
241
+ boxPk: Buffer.from(voice.boxPk).toString('base64'),
242
+ wrapped: Voice.sealTo(voice.boxPk, root.toString('base64')),
243
+ });
244
+ const written = program === undefined ? undefined : Program(program);
245
+ return Compartment({
246
+ memory,
247
+ voice,
248
+ address,
249
+ root,
250
+ classOf: (key) => written?.classOf(key) ?? coreClass(key),
251
+ });
252
+ };
253
+
254
+ const Registry = ({
255
+ memory,
256
+ adminMine,
257
+ modules,
258
+ modulesFor,
259
+ announce,
260
+ forget,
261
+ vouched,
262
+ attest,
263
+ watch,
264
+ }) => {
265
+ const live = new Map();
266
+ const activate = (address) => {
267
+ if (live.has(address)) {
268
+ return live.get(address);
269
+ }
270
+ const held = adminMine.read(`identity/custody/${address}`);
271
+ const voice = held === undefined ? undefined : Voice.revive(held);
272
+ if (voice === undefined) {
273
+ return undefined;
274
+ }
275
+ const door = Being({ voice, address, modules: modulesFor(voice), vouched, watch });
276
+ live.set(address, door);
277
+ return door;
278
+ };
279
+ return Object.freeze({
280
+ activate,
281
+ active: () => live.size,
282
+ attest,
283
+ create: ({ program, refs = [], data = {}, bindings, config } = {}) => {
284
+ const written = program === undefined ? undefined : Program(program);
285
+ if (program !== undefined && written === undefined) {
286
+ return undefined;
287
+ }
288
+ const missing = (written?.modules ?? []).filter((one) => modules[one.module] === undefined);
289
+ if (missing.length > 0) {
290
+ return undefined;
291
+ }
292
+ const brought = written?.bindings(bindings);
293
+ const settings = written?.config(config);
294
+ if (written !== undefined && (brought === undefined || settings === undefined)) {
295
+ return undefined;
296
+ }
297
+ const allowed = written?.rights ?? [...STRUCTURAL, 'MEMBER'];
298
+ if (refs.some((ref) => !allowed.includes(ref.rights ?? 'MEMBER'))) {
299
+ return undefined;
300
+ }
301
+ if (refs.some((ref) => typeof ref.address !== 'string' || Tools.pkOf(ref.address) === null)) {
302
+ return undefined;
303
+ }
304
+ const voice = Voice();
305
+ const address = Tools.address(voice.pk);
306
+ const mine = make({ memory, voice, address, program });
307
+ for (const [key, value] of Object.entries(data)) {
308
+ if (mine.write(key, value) === undefined) {
309
+ forget(address);
310
+ return undefined;
311
+ }
312
+ }
313
+ if (written !== undefined) {
314
+ mine.write('config', settings);
315
+ for (const [module, bound] of Object.entries(brought)) {
316
+ mine.write(`bindings/${module}`, bound);
317
+ }
318
+ }
319
+ for (const ref of refs) {
320
+ mine.write(`refs/${ref.address}`, {
321
+ nextPkHash: ref.nextPkHash ?? null,
322
+ rights: ref.rights ?? 'MEMBER',
323
+ });
324
+ }
325
+ adminMine.write(`identity/custody/${address}`, voice.pack());
326
+ announce(address);
327
+ return address;
328
+ },
329
+ deactivate: (address) => live.delete(address),
330
+ upgrade: ({ address, program }) => {
331
+ const held = adminMine.read(`identity/custody/${address}`);
332
+ const voice = held === undefined ? undefined : Voice.revive(held);
333
+ const written = program === undefined ? undefined : Program(program);
334
+ if (voice === undefined || written === undefined) {
335
+ return undefined;
336
+ }
337
+ const orphaned = memory
338
+ .list(address, '')
339
+ .filter((key) => memory.read(address, key)?.value !== null)
340
+ .filter((key) => written.classOf(key) === undefined);
341
+ if (orphaned.length > 0) {
342
+ return undefined;
343
+ }
344
+ const claim = {
345
+ address,
346
+ key: 'program',
347
+ value: program,
348
+ version: (memory.read(address, 'program')?.version ?? 0) + 1,
349
+ };
350
+ if (memory.write({ ...claim, sig: voice.sign(Tools.canon(claim)) }) === undefined) {
351
+ return undefined;
352
+ }
353
+ live.delete(address);
354
+ return address;
355
+ },
356
+ destroy: (address) => {
357
+ if (adminMine.read(`identity/custody/${address}`) === undefined) {
358
+ return undefined;
359
+ }
360
+ adminMine.write(`identity/custody/${address}`, undefined);
361
+ live.delete(address);
362
+ forget(address);
363
+ return address;
364
+ },
365
+ stored: () => adminMine.list('identity/custody/').length,
366
+ });
367
+ };
368
+
369
+ export { Being, Compartment, Registry, make, rootFor };
package/blueprint.mjs ADDED
@@ -0,0 +1,281 @@
1
+ import { Kind, buildSchema, coerceInputValue, parse, print } from 'graphql';
2
+ import { Tools } from './tools.mjs';
3
+
4
+ const STRUCTURAL = ['SELF', 'STRANGER'];
5
+
6
+ const CLASSES = [
7
+ 'bindings',
8
+ 'chain',
9
+ 'config',
10
+ 'data',
11
+ 'handles',
12
+ 'identity',
13
+ 'keyring',
14
+ 'program',
15
+ 'refs',
16
+ ];
17
+
18
+ const OPEN = ['chain', 'keyring', 'program'];
19
+
20
+ const CORE_ONLY = ['chain', 'identity', 'keyring', 'program'];
21
+
22
+ const CORE = [
23
+ ['chain/', 'chain'],
24
+ ['keyring/', 'keyring'],
25
+ ['refs/', 'refs'],
26
+ ['handles/', 'handles'],
27
+ ['bindings/', 'bindings'],
28
+ ['identity/', 'identity'],
29
+ ['config', 'config'],
30
+ ['program', 'program'],
31
+ ];
32
+
33
+ const coreClass = (key) =>
34
+ CORE.find(([mark]) => (mark.endsWith('/') ? key.startsWith(mark) : key === mark))?.[1];
35
+
36
+ const declaredClass = (memory, key) => {
37
+ const held = memory[key];
38
+ if (held !== undefined) {
39
+ return held.class;
40
+ }
41
+ const under = Object.keys(memory)
42
+ .filter((one) => one.endsWith('/*'))
43
+ .find((one) => key.startsWith(one.slice(0, -1)));
44
+ return under === undefined ? undefined : memory[under].class;
45
+ };
46
+
47
+ const AMBIENT = [
48
+ 'globalThis',
49
+ 'process',
50
+ 'require',
51
+ 'import',
52
+ 'eval',
53
+ 'Function',
54
+ 'fetch',
55
+ 'XMLHttpRequest',
56
+ 'WebAssembly',
57
+ 'SharedArrayBuffer',
58
+ 'Atomics',
59
+ 'Buffer',
60
+ 'crypto',
61
+ 'Date',
62
+ 'setTimeout',
63
+ 'setInterval',
64
+ 'queueMicrotask',
65
+ ];
66
+
67
+ const REACHES = [
68
+ ['constructor', /\bconstructor\b/],
69
+ ['__proto__', /\b__proto__\b/],
70
+ ['prototype', /\bprototype\b/],
71
+ ['Math.random', /\bMath\s*\.\s*random\b/],
72
+ ];
73
+
74
+ const FORBIDDEN = [
75
+ ...AMBIENT.map((name) => [name, new RegExp(`(?<![.$\\w])${name}\\b(?!\\s*:)`)]),
76
+ ...REACHES,
77
+ ];
78
+
79
+ const RESERVED = ['eval', 'import', '__proto__', 'prototype', 'Math.random'];
80
+
81
+ const SHADOWED = FORBIDDEN.map(([name]) => name).filter((name) => !RESERVED.includes(name));
82
+
83
+ const RIGHTS_DIRECTIVE = 'directive @rights(is: [Rights!]!) on FIELD_DEFINITION';
84
+
85
+ const DEFAULT_RIGHTS = `enum Rights { SELF MEMBER STRANGER }`;
86
+
87
+ const codeOnly = (source) => {
88
+ const out = [];
89
+ const modes = [{ depth: 0, kind: 'code' }];
90
+ let at = 0;
91
+ while (at < source.length) {
92
+ const mode = modes[modes.length - 1];
93
+ const one = source[at];
94
+ if (mode.kind !== 'code') {
95
+ if (one === '\\') {
96
+ out.push(' ');
97
+ at += 2;
98
+ } else if (one === mode.mark) {
99
+ modes.pop();
100
+ out.push(' ');
101
+ at += 1;
102
+ } else if (mode.kind === 'template' && one === '$' && source[at + 1] === '{') {
103
+ modes.push({ depth: 0, kind: 'code' });
104
+ out.push(' ');
105
+ at += 2;
106
+ } else {
107
+ out.push(one === '\n' ? '\n' : ' ');
108
+ at += 1;
109
+ }
110
+ continue;
111
+ }
112
+ if (one === "'" || one === '"') {
113
+ modes.push({ kind: 'quote', mark: one });
114
+ } else if (one === '`') {
115
+ modes.push({ kind: 'template', mark: '`' });
116
+ } else if (one === '{') {
117
+ mode.depth += 1;
118
+ } else if (one === '}' && mode.depth === 0 && modes.length > 1) {
119
+ modes.pop();
120
+ out.push(' ');
121
+ at += 1;
122
+ continue;
123
+ } else if (one === '}') {
124
+ mode.depth -= 1;
125
+ }
126
+ out.push(one === "'" || one === '"' || one === '`' ? ' ' : one);
127
+ at += 1;
128
+ }
129
+ return out.join('');
130
+ };
131
+
132
+ const reads = (source) => {
133
+ const code = codeOnly(source);
134
+ return FORBIDDEN.filter(([, mark]) => mark.test(code)).map(([name]) => name);
135
+ };
136
+
137
+ const evaluated = (source) => {
138
+ const body = `"use strict"; return (${source});`;
139
+ return new Function(...SHADOWED, body)(...SHADOWED.map(() => undefined));
140
+ };
141
+
142
+ const rightsOf = (field) => {
143
+ const marked = field.directives?.find((one) => one.name.value === 'rights');
144
+ const listed = marked?.arguments?.find((one) => one.name.value === 'is');
145
+ return listed === undefined
146
+ ? undefined
147
+ : listed.value.values.map((one) => one.value ?? one.name?.value);
148
+ };
149
+
150
+ const resolverSources = (resolvers) =>
151
+ Object.keys(resolvers)
152
+ .sort()
153
+ .flatMap((type) =>
154
+ Object.keys(resolvers[type])
155
+ .sort()
156
+ .map((field) => `${type}.${field}=${resolvers[type][field].toString()}`),
157
+ );
158
+
159
+ const inputCheck = (schema, typeName, given) => {
160
+ const type = schema.getType(typeName);
161
+ if (type?.getFields === undefined) {
162
+ return undefined;
163
+ }
164
+ let failed = false;
165
+ const taken = coerceInputValue(given ?? {}, type, () => {
166
+ failed = true;
167
+ });
168
+ return failed || taken === undefined
169
+ ? undefined
170
+ : Object.freeze(JSON.parse(JSON.stringify(taken)));
171
+ };
172
+
173
+ const Blueprint = (source) => {
174
+ const trespass = reads(source);
175
+ if (trespass.length > 0) {
176
+ return { trespass };
177
+ }
178
+ try {
179
+ const written = evaluated(source);
180
+ const declares = /enum\s+Rights\b/.test(written.interface) ? '' : DEFAULT_RIGHTS;
181
+ const sdl = [RIGHTS_DIRECTIVE, declares, written.interface].filter(Boolean).join('\n');
182
+ const document = parse(sdl);
183
+ const schema = buildSchema(sdl);
184
+ const rights = [
185
+ ...new Set([
186
+ ...STRUCTURAL,
187
+ ...(schema.getType('Rights')?.getValues() ?? []).map((one) => one.name),
188
+ ]),
189
+ ];
190
+
191
+ const admits = (typeName, fieldName, held) => {
192
+ const type = document.definitions.find(
193
+ (one) => one.kind === Kind.OBJECT_TYPE_DEFINITION && one.name.value === typeName,
194
+ );
195
+ const field = type?.fields?.find((one) => one.name.value === fieldName);
196
+ const allowed = field === undefined ? undefined : rightsOf(field);
197
+ return allowed === undefined ? true : allowed.includes(held);
198
+ };
199
+
200
+ const needs = written.needs ?? {};
201
+ const modules = needs.modules ?? [];
202
+ const memory = written.memory ?? {};
203
+ const misdeclared = Object.entries(memory).filter(
204
+ ([key, cell]) => !CLASSES.includes(cell?.class) || coreClass(key) !== undefined,
205
+ );
206
+ if (misdeclared.length > 0) {
207
+ return undefined;
208
+ }
209
+ const roots = [
210
+ schema.getQueryType()?.name,
211
+ schema.getMutationType()?.name,
212
+ schema.getSubscriptionType()?.name,
213
+ ].filter((one) => one !== undefined);
214
+ const undeclared = document.definitions
215
+ .filter((one) => one.kind === Kind.OBJECT_TYPE_DEFINITION && roots.includes(one.name.value))
216
+ .flatMap((one) => one.fields ?? [])
217
+ .filter((one) => {
218
+ const allowed = rightsOf(one);
219
+ return allowed === undefined || allowed.some((held) => !rights.includes(held));
220
+ });
221
+ if (undeclared.length > 0) {
222
+ return undefined;
223
+ }
224
+
225
+ return Object.freeze({
226
+ admits,
227
+ bindings: (given) =>
228
+ modules
229
+ .filter((one) => one.binding !== undefined)
230
+ .reduce((taken, one) => {
231
+ if (taken === undefined) {
232
+ return undefined;
233
+ }
234
+ const checked = inputCheck(schema, one.binding, given?.[one.module]);
235
+ return checked === undefined ? undefined : { ...taken, [one.module]: checked };
236
+ }, {}),
237
+ classOf: (key) => coreClass(key) ?? declaredClass(memory, key),
238
+ declares: (key) => declaredClass(memory, key),
239
+ config: (given) =>
240
+ needs.config === undefined ? Object.freeze({}) : inputCheck(schema, needs.config, given),
241
+ digest: Tools.digest(
242
+ Tools.canon({
243
+ interface: print(document),
244
+ memory,
245
+ name: written.name,
246
+ needs,
247
+ resolvers: resolverSources(written.resolvers ?? {}),
248
+ }),
249
+ ),
250
+ memory: Object.freeze(memory),
251
+ modules,
252
+ name: written.name,
253
+ resolvers: written.resolvers ?? {},
254
+ rights,
255
+ schema,
256
+ // The directive definition stays: pointer.mjs runs buildSchema on this output.
257
+ sdlFor: (held) =>
258
+ print({
259
+ ...document,
260
+ definitions: document.definitions
261
+ .map((one) =>
262
+ one.kind === Kind.OBJECT_TYPE_DEFINITION
263
+ ? {
264
+ ...one,
265
+ fields: one.fields.filter((field) => {
266
+ const allowed = rightsOf(field);
267
+ return allowed === undefined || allowed.includes(held);
268
+ }),
269
+ }
270
+ : one,
271
+ )
272
+ .filter((one) => one.kind !== Kind.OBJECT_TYPE_DEFINITION || one.fields.length > 0),
273
+ }),
274
+ trespass: [],
275
+ });
276
+ } catch {
277
+ return undefined;
278
+ }
279
+ };
280
+
281
+ export { Blueprint, CLASSES, CORE_ONLY, FORBIDDEN, OPEN, STRUCTURAL, coreClass, reads };
package/client.mjs ADDED
@@ -0,0 +1,43 @@
1
+ import { Stamp, Tools } from './tools.mjs';
2
+
3
+ const Client = ({ voice, deliver, resolve = (one) => one }) => {
4
+ const stamp = Stamp();
5
+ const lanes = new Map();
6
+ const send = async (to, op) => {
7
+ const at = resolve(to);
8
+ const answering = at === undefined ? null : Tools.pkOf(at);
9
+ if (answering === null) {
10
+ return undefined;
11
+ }
12
+ const turn = (lanes.get(at) ?? Promise.resolve()).then(async () => {
13
+ const reply = await deliver(Tools.envelope(voice, at, op, stamp()));
14
+ return reply?.body === undefined ||
15
+ !Tools.verifies(answering, Tools.canon(reply.body), reply.sig)
16
+ ? undefined
17
+ : reply;
18
+ });
19
+ const settled = turn.then(
20
+ () => undefined,
21
+ () => undefined,
22
+ );
23
+ lanes.set(at, settled);
24
+ settled.then(() => {
25
+ if (lanes.get(at) === settled) {
26
+ lanes.delete(at);
27
+ }
28
+ });
29
+ return turn;
30
+ };
31
+
32
+ return Object.freeze({
33
+ address: Tools.address(voice.pk),
34
+ ask: (to, source, variables) => send(to, { ask: { source, variables } }),
35
+ describe: (to) => send(to, { describe: {} }),
36
+ introspect: (to) => send(to, { introspect: {} }),
37
+ pk: voice.pk,
38
+ release: (to) => send(to, { release: {} }),
39
+ send,
40
+ });
41
+ };
42
+
43
+ export { Client };