nervur 0.14.0 → 0.16.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/LICENSE +2 -1
- package/NOTICE +5 -1
- package/README.md +23 -448
- package/index.js +17 -0
- package/nervur.js +5 -0
- package/package.json +32 -15
- package/GETTING-STARTED.md +0 -111
- package/SECURITY.md +0 -11
- package/admin.mjs +0 -83
- package/being.mjs +0 -639
- package/blueprint.mjs +0 -512
- package/client.mjs +0 -70
- package/contract.mjs +0 -98
- package/ground.mjs +0 -382
- package/index.mjs +0 -20
- package/modules.mjs +0 -55
- package/pointer.mjs +0 -79
- package/program.mjs +0 -119
- package/tools.mjs +0 -162
- package/voice.mjs +0 -107
package/ground.mjs
DELETED
|
@@ -1,382 +0,0 @@
|
|
|
1
|
-
import { ADMIN } from './admin.mjs';
|
|
2
|
-
import {
|
|
3
|
-
Being,
|
|
4
|
-
Compartment,
|
|
5
|
-
Registry,
|
|
6
|
-
SUCCESSION,
|
|
7
|
-
make,
|
|
8
|
-
rootFor,
|
|
9
|
-
spend,
|
|
10
|
-
successionOf,
|
|
11
|
-
} from './being.mjs';
|
|
12
|
-
import { Blueprint, composed, coreClass } from './blueprint.mjs';
|
|
13
|
-
import { Client } from './client.mjs';
|
|
14
|
-
import { Contracts } from './contract.mjs';
|
|
15
|
-
import { Memory } from './modules.mjs';
|
|
16
|
-
import { Mcp, Pointer } from './pointer.mjs';
|
|
17
|
-
import { Program } from './program.mjs';
|
|
18
|
-
import { Bytes, Stamp, Tools } from './tools.mjs';
|
|
19
|
-
import { Voice } from './voice.mjs';
|
|
20
|
-
|
|
21
|
-
const GROUND = 'Ground';
|
|
22
|
-
|
|
23
|
-
const FLOOR = [
|
|
24
|
-
'blueprints',
|
|
25
|
-
'cells',
|
|
26
|
-
'cipher',
|
|
27
|
-
'contracts',
|
|
28
|
-
'keychain',
|
|
29
|
-
'moved',
|
|
30
|
-
'paths',
|
|
31
|
-
'watch',
|
|
32
|
-
'wire',
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
const UNPROVEN = 'ground/unproven/';
|
|
36
|
-
|
|
37
|
-
const HEIR = 'ground/heir';
|
|
38
|
-
|
|
39
|
-
const commits = (cells, bundle) => {
|
|
40
|
-
const boxPk = bundle?.heirBoxPk;
|
|
41
|
-
try {
|
|
42
|
-
if (typeof boxPk !== 'string' || Bytes.unbase64(boxPk).length !== 32) {
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
|
-
} catch {
|
|
46
|
-
return undefined;
|
|
47
|
-
}
|
|
48
|
-
cells.put(HEIR, { boxPk, nextPkHash: bundle.nextPkHash });
|
|
49
|
-
return undefined;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const unprovenAt = (cells) => (at) => {
|
|
53
|
-
try {
|
|
54
|
-
const stood = cells.peek(`${UNPROVEN}${at}`);
|
|
55
|
-
const seen = Date.now();
|
|
56
|
-
cells.put(`${UNPROVEN}${at}`, {
|
|
57
|
-
at,
|
|
58
|
-
last: seen,
|
|
59
|
-
seen: (stood?.seen ?? 0) + 1,
|
|
60
|
-
since: stood?.since ?? seen,
|
|
61
|
-
});
|
|
62
|
-
} catch {
|
|
63
|
-
return undefined;
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const unprovenRows = (cells) => cells.keys(UNPROVEN).map((row) => cells.peek(row));
|
|
68
|
-
|
|
69
|
-
const install = ({ cells, keychain, program, bundle, cipher }) => {
|
|
70
|
-
if (cells.peek('ground/admin') !== undefined || bundle?.next !== undefined) {
|
|
71
|
-
return undefined;
|
|
72
|
-
}
|
|
73
|
-
const voice = Voice.revive(bundle ?? {});
|
|
74
|
-
if (voice === undefined) {
|
|
75
|
-
return undefined;
|
|
76
|
-
}
|
|
77
|
-
const memory = Memory(cells);
|
|
78
|
-
const address = Tools.address(voice.pk);
|
|
79
|
-
const sealed = keychain.seal(voice.pack());
|
|
80
|
-
if (sealed === undefined) {
|
|
81
|
-
return undefined;
|
|
82
|
-
}
|
|
83
|
-
make({ memory, voice, address, program, cipher });
|
|
84
|
-
cells.put('ground/admin', { address, sealed });
|
|
85
|
-
commits(cells, bundle);
|
|
86
|
-
return address;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const succeeds = ({ adminMine, adminVoice, address, bundle, cells, cipher, keychain, memory }) => {
|
|
90
|
-
if (bundle?.next !== undefined) {
|
|
91
|
-
return undefined;
|
|
92
|
-
}
|
|
93
|
-
const heir = Voice.revive(bundle ?? {});
|
|
94
|
-
const committed = memory.read(address, 'chain/nextPkHash');
|
|
95
|
-
if (heir === undefined || committed?.value !== Tools.digest(heir.pk)) {
|
|
96
|
-
return undefined;
|
|
97
|
-
}
|
|
98
|
-
const root = rootFor({ memory, voice: adminVoice, address, cipher });
|
|
99
|
-
const sealed = root === undefined ? undefined : keychain.seal(heir.pack());
|
|
100
|
-
if (sealed === undefined) {
|
|
101
|
-
return undefined;
|
|
102
|
-
}
|
|
103
|
-
const rewrapped = adminMine.write(`keyring/${Tools.address(heir.pk)}`, {
|
|
104
|
-
boxPk: Bytes.base64(heir.boxPk),
|
|
105
|
-
wrapped: Voice.sealTo(heir.boxPk, Bytes.base64(root), cipher),
|
|
106
|
-
});
|
|
107
|
-
if (rewrapped === undefined) {
|
|
108
|
-
return undefined;
|
|
109
|
-
}
|
|
110
|
-
const version = committed.version + 1;
|
|
111
|
-
const certV = successionOf(memory, address).length + 1;
|
|
112
|
-
const handover = { at: address, next: Tools.address(heir.pk), v: certV };
|
|
113
|
-
const certified = adminMine.write(`${SUCCESSION}${certV}`, {
|
|
114
|
-
...handover,
|
|
115
|
-
sig: adminVoice.sign(Tools.canon(handover)),
|
|
116
|
-
});
|
|
117
|
-
if (certified === undefined) {
|
|
118
|
-
return undefined;
|
|
119
|
-
}
|
|
120
|
-
const claim = {
|
|
121
|
-
address,
|
|
122
|
-
nextPkHash: heir.nextPkHash,
|
|
123
|
-
pk: Bytes.hex(heir.pk),
|
|
124
|
-
version,
|
|
125
|
-
};
|
|
126
|
-
if (memory.replace({ ...claim, sig: heir.sign(Tools.canon(claim)) }) === undefined) {
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
cells.put('ground/admin', { address, sealed });
|
|
130
|
-
commits(cells, bundle);
|
|
131
|
-
return Tools.address(heir.pk);
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
// Never a key in the modules object: everything there that is not in FLOOR
|
|
135
|
-
// becomes a module handed to programs under that name.
|
|
136
|
-
const Ground = (modules = {}, { span } = {}) => {
|
|
137
|
-
const cells = modules.cells;
|
|
138
|
-
const keychain = modules.keychain;
|
|
139
|
-
const cipher = modules.cipher;
|
|
140
|
-
if (
|
|
141
|
-
cells === undefined ||
|
|
142
|
-
keychain?.seal === undefined ||
|
|
143
|
-
keychain?.unseal === undefined ||
|
|
144
|
-
cipher?.seal === undefined ||
|
|
145
|
-
cipher?.unseal === undefined
|
|
146
|
-
) {
|
|
147
|
-
return undefined;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const written = modules.contracts ?? Contracts({});
|
|
151
|
-
const extra = Object.fromEntries(
|
|
152
|
-
Object.entries(modules).filter(([name]) => !FLOOR.includes(name)),
|
|
153
|
-
);
|
|
154
|
-
const bound = Object.entries(extra).filter(([, one]) => one?.at !== undefined);
|
|
155
|
-
if (bound.some(([, one]) => !written.has(one.contract))) {
|
|
156
|
-
return undefined;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const extension = modules.blueprints?.ground();
|
|
160
|
-
const program = extension === undefined ? ADMIN : composed(ADMIN, extension);
|
|
161
|
-
const blueprint = program === undefined ? undefined : Blueprint(program);
|
|
162
|
-
if (blueprint?.digest === undefined) {
|
|
163
|
-
return undefined;
|
|
164
|
-
}
|
|
165
|
-
const installed = cells.peek('ground/admin');
|
|
166
|
-
if (installed === undefined) {
|
|
167
|
-
return Object.freeze({
|
|
168
|
-
init: (bundle) => install({ cells, keychain, program, bundle, cipher: modules.cipher }),
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
const bundle = keychain.unseal(installed.sealed);
|
|
172
|
-
const adminVoice = bundle === undefined ? undefined : Voice.revive(bundle);
|
|
173
|
-
if (adminVoice === undefined) {
|
|
174
|
-
return undefined;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Never leaves this closure — nothing outside may be handed it.
|
|
178
|
-
const vouched = Symbol('vouched');
|
|
179
|
-
|
|
180
|
-
const watch = (fact) => {
|
|
181
|
-
try {
|
|
182
|
-
modules.watch?.(Object.freeze(fact));
|
|
183
|
-
} catch {
|
|
184
|
-
return undefined;
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const address = installed.address;
|
|
189
|
-
const memory = Memory(cells, unprovenAt(cells), modules.moved);
|
|
190
|
-
const paths = modules.paths ?? Object.freeze({ find: () => undefined, publish: () => undefined });
|
|
191
|
-
const wire =
|
|
192
|
-
modules.wire ??
|
|
193
|
-
Object.freeze({
|
|
194
|
-
carry: async (endpoint, envelope) =>
|
|
195
|
-
typeof endpoint === 'function' ? endpoint(envelope) : undefined,
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const stamp = Stamp();
|
|
199
|
-
const boundFor = (voice) =>
|
|
200
|
-
Object.fromEntries(
|
|
201
|
-
bound.map(([name, one]) => [
|
|
202
|
-
name,
|
|
203
|
-
Pointer(written.get(one.contract).sdl, async (source, variables) =>
|
|
204
|
-
deliver(await Tools.envelope(voice, one.at, { ask: { source, variables } }, stamp())),
|
|
205
|
-
),
|
|
206
|
-
]),
|
|
207
|
-
);
|
|
208
|
-
const introduced = (unscoped, address) =>
|
|
209
|
-
Object.fromEntries(
|
|
210
|
-
Object.entries(unscoped).map(([name, one]) => [
|
|
211
|
-
name,
|
|
212
|
-
typeof one?.scope === 'function' ? one.scope(address) : one,
|
|
213
|
-
]),
|
|
214
|
-
);
|
|
215
|
-
const declaredOnly = (obj, declared) =>
|
|
216
|
-
Object.fromEntries(Object.entries(obj).filter(([name]) => declared.includes(name)));
|
|
217
|
-
const modulesFor = (voice, declared = []) =>
|
|
218
|
-
Object.freeze(
|
|
219
|
-
introduced(
|
|
220
|
-
{
|
|
221
|
-
...declaredOnly(extra, declared),
|
|
222
|
-
...declaredOnly(boundFor(voice), declared),
|
|
223
|
-
memory,
|
|
224
|
-
},
|
|
225
|
-
Tools.address(voice.pk),
|
|
226
|
-
),
|
|
227
|
-
);
|
|
228
|
-
const offeredNames = new Set([...Object.keys(extra), ...bound.map(([name]) => name), 'memory']);
|
|
229
|
-
const adminDeclared = () =>
|
|
230
|
-
(Program(memory.read(address, 'program')?.value)?.modules ?? blueprint.modules).map(
|
|
231
|
-
(one) => one.module,
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
const adminMine = Compartment({
|
|
235
|
-
memory,
|
|
236
|
-
voice: adminVoice,
|
|
237
|
-
address,
|
|
238
|
-
root: rootFor({ memory, voice: adminVoice, address, cipher: modules.cipher }),
|
|
239
|
-
classOf: (key) =>
|
|
240
|
-
Program(memory.read(address, 'program')?.value)?.classOf(key) ?? coreClass(key),
|
|
241
|
-
cipher: modules.cipher,
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
const receive = async (envelope, token) => {
|
|
245
|
-
const at = envelope?.to;
|
|
246
|
-
const door = at === address ? admin : registry.activate(at);
|
|
247
|
-
if (door === undefined) {
|
|
248
|
-
watch({
|
|
249
|
-
from: envelope?.from,
|
|
250
|
-
kind: envelope?.op === undefined ? undefined : Object.keys(envelope.op)[0],
|
|
251
|
-
outcome: 'no-door',
|
|
252
|
-
to: at,
|
|
253
|
-
});
|
|
254
|
-
return undefined;
|
|
255
|
-
}
|
|
256
|
-
return door(envelope, token);
|
|
257
|
-
};
|
|
258
|
-
const announce = (at) => paths.publish(at, receive);
|
|
259
|
-
const forget = (at, voice) => {
|
|
260
|
-
paths.publish(at, undefined);
|
|
261
|
-
if (voice === undefined) {
|
|
262
|
-
cells.drop(`${at}/`);
|
|
263
|
-
modules.moved?.(at);
|
|
264
|
-
return undefined;
|
|
265
|
-
}
|
|
266
|
-
return spend({ memory, voice, address: at });
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
const attest = async (name) => {
|
|
270
|
-
const one = extra[name];
|
|
271
|
-
if (one === undefined) {
|
|
272
|
-
return false;
|
|
273
|
-
}
|
|
274
|
-
if (one.at === undefined) {
|
|
275
|
-
return null;
|
|
276
|
-
}
|
|
277
|
-
const seen = await deliver(await Tools.envelope(adminVoice, one.at, { describe: {} }, stamp()));
|
|
278
|
-
return seen?.body?.sdl === undefined
|
|
279
|
-
? false
|
|
280
|
-
: written.get(one.contract).satisfies(seen.body.sdl);
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
let adminDoor;
|
|
284
|
-
const buildAdmin = () =>
|
|
285
|
-
Being({
|
|
286
|
-
address,
|
|
287
|
-
administration: true,
|
|
288
|
-
cipher: modules.cipher,
|
|
289
|
-
deliver: (envelope) => deliver(envelope),
|
|
290
|
-
modules: modulesFor(adminVoice, adminDeclared()),
|
|
291
|
-
registry,
|
|
292
|
-
span,
|
|
293
|
-
vouched,
|
|
294
|
-
voice: adminVoice,
|
|
295
|
-
watch,
|
|
296
|
-
});
|
|
297
|
-
const registry = Registry({
|
|
298
|
-
memory,
|
|
299
|
-
adminMine,
|
|
300
|
-
address,
|
|
301
|
-
cipher: modules.cipher,
|
|
302
|
-
deliver: (envelope) => deliver(envelope),
|
|
303
|
-
modules: offeredNames,
|
|
304
|
-
modulesFor,
|
|
305
|
-
announce,
|
|
306
|
-
forget,
|
|
307
|
-
span,
|
|
308
|
-
vouched,
|
|
309
|
-
attest,
|
|
310
|
-
watch,
|
|
311
|
-
extended: () => {
|
|
312
|
-
adminDoor = undefined;
|
|
313
|
-
},
|
|
314
|
-
});
|
|
315
|
-
const admin = (envelope, token) => {
|
|
316
|
-
if (adminDoor === undefined) {
|
|
317
|
-
adminDoor = buildAdmin();
|
|
318
|
-
}
|
|
319
|
-
return adminDoor(envelope, token);
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
announce(address);
|
|
323
|
-
for (const key of adminMine.list('identity/custody/')) {
|
|
324
|
-
announce(key.slice('identity/custody/'.length));
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const held = (at) => at === address || adminMine.read(`identity/custody/${at}`) !== undefined;
|
|
328
|
-
const deliver = async (envelope) =>
|
|
329
|
-
held(envelope.to) ? receive(envelope, vouched) : wire.carry(paths.find(envelope.to), envelope);
|
|
330
|
-
|
|
331
|
-
// Client edge only — a resolver must never be handed this.
|
|
332
|
-
const resolve = (one) => (one === GROUND ? address : (adminMine.read(`aliases/${one}`) ?? one));
|
|
333
|
-
|
|
334
|
-
return Object.freeze({
|
|
335
|
-
address,
|
|
336
|
-
receive,
|
|
337
|
-
rotate: (bundle) =>
|
|
338
|
-
succeeds({
|
|
339
|
-
adminMine,
|
|
340
|
-
adminVoice,
|
|
341
|
-
address,
|
|
342
|
-
bundle,
|
|
343
|
-
cells,
|
|
344
|
-
cipher: modules.cipher,
|
|
345
|
-
keychain,
|
|
346
|
-
memory,
|
|
347
|
-
}),
|
|
348
|
-
serve: (voice) => {
|
|
349
|
-
const client = Client({ voice, deliver, resolve });
|
|
350
|
-
const sdlOf = async (at) => (await client.describe(at))?.body?.sdl;
|
|
351
|
-
const asker = (at) => (source, variables) => client.ask(at, source, variables);
|
|
352
|
-
return Object.freeze({
|
|
353
|
-
address: client.address,
|
|
354
|
-
client,
|
|
355
|
-
gql: (at) =>
|
|
356
|
-
Object.freeze({
|
|
357
|
-
ask: asker(at),
|
|
358
|
-
introspect: () => client.introspect(at),
|
|
359
|
-
sdl: () => sdlOf(at),
|
|
360
|
-
}),
|
|
361
|
-
mcp: async (at) => {
|
|
362
|
-
const sdl = await sdlOf(at);
|
|
363
|
-
return sdl === undefined ? undefined : Mcp(sdl, asker(at));
|
|
364
|
-
},
|
|
365
|
-
pointer: async (at) => {
|
|
366
|
-
const sdl = await sdlOf(at);
|
|
367
|
-
return sdl === undefined ? undefined : Pointer(sdl, asker(at));
|
|
368
|
-
},
|
|
369
|
-
});
|
|
370
|
-
},
|
|
371
|
-
seated: (at, from) => {
|
|
372
|
-
if (at === from) {
|
|
373
|
-
return true;
|
|
374
|
-
}
|
|
375
|
-
const row = held(at) ? memory.read(at, `refs/${from}`)?.value : undefined;
|
|
376
|
-
return row !== undefined && row !== null;
|
|
377
|
-
},
|
|
378
|
-
unproven: () => unprovenRows(cells),
|
|
379
|
-
});
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
export { FLOOR, GROUND, Ground };
|
package/index.mjs
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export { ADMIN } from './admin.mjs';
|
|
2
|
-
export {
|
|
3
|
-
Blueprint,
|
|
4
|
-
CLASSES,
|
|
5
|
-
CORE_ONLY,
|
|
6
|
-
OPEN,
|
|
7
|
-
STRUCTURAL,
|
|
8
|
-
composition,
|
|
9
|
-
coreClass,
|
|
10
|
-
faults,
|
|
11
|
-
reads,
|
|
12
|
-
} from './blueprint.mjs';
|
|
13
|
-
export { Client, speaking } from './client.mjs';
|
|
14
|
-
export { Contract, Contracts } from './contract.mjs';
|
|
15
|
-
export { FLOOR, GROUND, Ground } from './ground.mjs';
|
|
16
|
-
export { Memory } from './modules.mjs';
|
|
17
|
-
export { Mcp, Pointer } from './pointer.mjs';
|
|
18
|
-
export { Program } from './program.mjs';
|
|
19
|
-
export { Bytes, MEASURE, SPAN, Stamp, Tools } from './tools.mjs';
|
|
20
|
-
export { Voice } from './voice.mjs';
|
package/modules.mjs
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { Bytes, Tools } from './tools.mjs';
|
|
2
|
-
|
|
3
|
-
// The ground's own machinery over its cells. Every implementation a host
|
|
4
|
-
// supplies — cells, keychain, cipher, paths, wire — belongs to that host's
|
|
5
|
-
// floor and never to the core (X, Everything is a module).
|
|
6
|
-
const Memory = (cells, unproven, moved) => {
|
|
7
|
-
const held = (address, key) => cells.peek(`${address}/${key}`);
|
|
8
|
-
const writerAt = (address) => {
|
|
9
|
-
const chain = held(address, 'chain/pk');
|
|
10
|
-
return chain === undefined ? Tools.pkOf(address) : Bytes.unhex(chain.value);
|
|
11
|
-
};
|
|
12
|
-
return Object.freeze({
|
|
13
|
-
list: (address, prefix) =>
|
|
14
|
-
cells.keys(`${address}/${prefix}`).map((cell) => cell.slice(address.length + 1)),
|
|
15
|
-
|
|
16
|
-
read: (address, key) => held(address, key),
|
|
17
|
-
|
|
18
|
-
replace: ({ address, pk, nextPkHash, sig }) => {
|
|
19
|
-
const committed = held(address, 'chain/nextPkHash');
|
|
20
|
-
if (committed === undefined || Tools.digest(Bytes.unhex(pk)) !== committed.value) {
|
|
21
|
-
return undefined;
|
|
22
|
-
}
|
|
23
|
-
const version = committed.version + 1;
|
|
24
|
-
const claim = { address, nextPkHash, pk, version };
|
|
25
|
-
if (!Tools.verifies(Bytes.unhex(pk), Tools.canon(claim), sig)) {
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
cells.put(`${address}/chain/pk`, { value: pk, version });
|
|
29
|
-
cells.put(`${address}/chain/nextPkHash`, { value: nextPkHash, version });
|
|
30
|
-
moved?.(address);
|
|
31
|
-
return version;
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
write: ({ address, key, value, version, sig }) => {
|
|
35
|
-
if (key.startsWith('chain/') && version !== 1) {
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
|
-
const writer = writerAt(address);
|
|
39
|
-
if (writer === null || version !== (held(address, key)?.version ?? 0) + 1) {
|
|
40
|
-
return undefined;
|
|
41
|
-
}
|
|
42
|
-
if (!Tools.verifies(writer, Tools.canon({ address, key, value, version }), sig)) {
|
|
43
|
-
unproven?.(address);
|
|
44
|
-
return undefined;
|
|
45
|
-
}
|
|
46
|
-
cells.put(`${address}/${key}`, { value, version });
|
|
47
|
-
if (!key.startsWith('refs/')) {
|
|
48
|
-
moved?.(address);
|
|
49
|
-
}
|
|
50
|
-
return version;
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export { Memory };
|
package/pointer.mjs
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { buildSchema, getNamedType, isAbstractType, isObjectType } from 'graphql';
|
|
2
|
-
|
|
3
|
-
const JSON_TYPE = { Boolean: 'boolean', Float: 'number', ID: 'string', Int: 'integer' };
|
|
4
|
-
|
|
5
|
-
const selection = (type, depth) => {
|
|
6
|
-
const named = getNamedType(type);
|
|
7
|
-
if (!isObjectType(named) || depth === 0) {
|
|
8
|
-
return '';
|
|
9
|
-
}
|
|
10
|
-
const fields = Object.values(named.getFields())
|
|
11
|
-
.filter(
|
|
12
|
-
(one) =>
|
|
13
|
-
!one.args.some((arg) => String(arg.type).endsWith('!') && arg.defaultValue === undefined),
|
|
14
|
-
)
|
|
15
|
-
.filter((one) => !isAbstractType(getNamedType(one.type)))
|
|
16
|
-
.filter((one) => depth > 1 || !isObjectType(getNamedType(one.type)))
|
|
17
|
-
.map((one) => `${one.name}${selection(one.type, depth - 1)}`);
|
|
18
|
-
return fields.length === 0 ? '' : ` { ${fields.join(' ')} }`;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const document = (kind, field, args) => {
|
|
22
|
-
const used = field.args.filter((one) => args[one.name] !== undefined);
|
|
23
|
-
const names = used.map((one) => `$${one.name}: ${one.type}`).join(', ');
|
|
24
|
-
const pass = used.map((one) => `${one.name}: $${one.name}`).join(', ');
|
|
25
|
-
return `${kind}${names ? `(${names})` : ''} { ${field.name}${
|
|
26
|
-
pass ? `(${pass})` : ''
|
|
27
|
-
}${selection(field.type, 3)} }`;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const roots = (schema) =>
|
|
31
|
-
['query', 'mutation'].flatMap((kind) => {
|
|
32
|
-
const type = kind === 'query' ? schema.getQueryType() : schema.getMutationType();
|
|
33
|
-
return type === undefined || type === null
|
|
34
|
-
? []
|
|
35
|
-
: Object.values(type.getFields()).map((field) => ({ field, kind }));
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const calls = (sdl, ask) => {
|
|
39
|
-
const schema = buildSchema(sdl);
|
|
40
|
-
return roots(schema).map(({ field, kind }) => ({
|
|
41
|
-
field,
|
|
42
|
-
kind,
|
|
43
|
-
run: async (args = {}) => {
|
|
44
|
-
const answered = await ask(document(kind, field, args), args);
|
|
45
|
-
return answered?.body?.data?.[field.name];
|
|
46
|
-
},
|
|
47
|
-
}));
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const Pointer = (sdl, ask) =>
|
|
51
|
-
Object.freeze(Object.fromEntries(calls(sdl, ask).map((one) => [one.field.name, one.run])));
|
|
52
|
-
|
|
53
|
-
const Mcp = (sdl, ask) => {
|
|
54
|
-
const held = calls(sdl, ask);
|
|
55
|
-
return Object.freeze({
|
|
56
|
-
call: (name, args) => held.find((one) => one.field.name === name)?.run(args),
|
|
57
|
-
tools: held.map(({ field, kind }) =>
|
|
58
|
-
Object.freeze({
|
|
59
|
-
annotations: { readOnlyHint: kind === 'query' },
|
|
60
|
-
description: field.description ?? undefined,
|
|
61
|
-
inputSchema: {
|
|
62
|
-
properties: Object.fromEntries(
|
|
63
|
-
field.args.map((one) => [
|
|
64
|
-
one.name,
|
|
65
|
-
{ type: JSON_TYPE[getNamedType(one.type).name] ?? 'string' },
|
|
66
|
-
]),
|
|
67
|
-
),
|
|
68
|
-
required: field.args
|
|
69
|
-
.filter((one) => String(one.type).endsWith('!'))
|
|
70
|
-
.map((one) => one.name),
|
|
71
|
-
type: 'object',
|
|
72
|
-
},
|
|
73
|
-
name: field.name,
|
|
74
|
-
}),
|
|
75
|
-
),
|
|
76
|
-
});
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
export { Mcp, Pointer };
|
package/program.mjs
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildSchema,
|
|
3
|
-
defaultFieldResolver,
|
|
4
|
-
getIntrospectionQuery,
|
|
5
|
-
graphql,
|
|
6
|
-
isObjectType,
|
|
7
|
-
parse,
|
|
8
|
-
validate,
|
|
9
|
-
} from 'graphql';
|
|
10
|
-
import { Blueprint } from './blueprint.mjs';
|
|
11
|
-
|
|
12
|
-
const bound = (blueprint) => {
|
|
13
|
-
const schema = blueprint.schema;
|
|
14
|
-
for (const type of Object.values(schema.getTypeMap())) {
|
|
15
|
-
if (!isObjectType(type) || type.name.startsWith('__')) {
|
|
16
|
-
continue;
|
|
17
|
-
}
|
|
18
|
-
for (const [field, held] of Object.entries(type.getFields())) {
|
|
19
|
-
const resolver = blueprint.resolvers[type.name]?.[field] ?? defaultFieldResolver;
|
|
20
|
-
held.resolve = (source, args, context, info) =>
|
|
21
|
-
blueprint.admits(type.name, field, context.rights)
|
|
22
|
-
? resolver(source, args, context, info)
|
|
23
|
-
: null;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return schema;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const Program = (source) => {
|
|
30
|
-
const blueprint = Blueprint(source);
|
|
31
|
-
if (blueprint === undefined || blueprint.trespass.length > 0) {
|
|
32
|
-
return undefined;
|
|
33
|
-
}
|
|
34
|
-
const schema = bound(blueprint);
|
|
35
|
-
const curtained = new Map();
|
|
36
|
-
const seenBy = (held) => {
|
|
37
|
-
if (!curtained.has(held)) {
|
|
38
|
-
try {
|
|
39
|
-
curtained.set(held, buildSchema(blueprint.sdlFor(held)));
|
|
40
|
-
} catch {
|
|
41
|
-
curtained.set(held, undefined);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return curtained.get(held);
|
|
45
|
-
};
|
|
46
|
-
// graphql's validator demands a Query root even to judge a mutation, so an
|
|
47
|
-
// all-mutation filtered world validates against a copy wearing an empty one
|
|
48
|
-
// — never printed, never executed against.
|
|
49
|
-
const askable = new Map();
|
|
50
|
-
const askableBy = (held) => {
|
|
51
|
-
if (!askable.has(held)) {
|
|
52
|
-
const seen = seenBy(held);
|
|
53
|
-
try {
|
|
54
|
-
askable.set(
|
|
55
|
-
held,
|
|
56
|
-
seen === undefined || seen.getQueryType() != null
|
|
57
|
-
? seen
|
|
58
|
-
: buildSchema(`${blueprint.sdlFor(held)}\ntype Query { _stands: Boolean }`),
|
|
59
|
-
);
|
|
60
|
-
} catch {
|
|
61
|
-
askable.set(held, undefined);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return askable.get(held);
|
|
65
|
-
};
|
|
66
|
-
return Object.freeze({
|
|
67
|
-
ask: async ({ source: asked, variables, context }) => {
|
|
68
|
-
const seen = askableBy(context.rights);
|
|
69
|
-
if (seen === undefined) {
|
|
70
|
-
return undefined;
|
|
71
|
-
}
|
|
72
|
-
try {
|
|
73
|
-
if (validate(seen, parse(asked)).length > 0) {
|
|
74
|
-
return undefined;
|
|
75
|
-
}
|
|
76
|
-
} catch {
|
|
77
|
-
return undefined;
|
|
78
|
-
}
|
|
79
|
-
const answered = await graphql({
|
|
80
|
-
contextValue: context,
|
|
81
|
-
schema,
|
|
82
|
-
source: asked,
|
|
83
|
-
variableValues: variables,
|
|
84
|
-
});
|
|
85
|
-
return answered.errors === undefined ? { data: answered.data } : undefined;
|
|
86
|
-
},
|
|
87
|
-
bindings: blueprint.bindings,
|
|
88
|
-
caller: blueprint.caller,
|
|
89
|
-
classOf: blueprint.classOf,
|
|
90
|
-
config: blueprint.config,
|
|
91
|
-
declares: blueprint.declares,
|
|
92
|
-
digest: blueprint.digest,
|
|
93
|
-
invites: blueprint.invites,
|
|
94
|
-
introspect: async (held) => {
|
|
95
|
-
const filtered = seenBy(held);
|
|
96
|
-
if (filtered === undefined) {
|
|
97
|
-
return undefined;
|
|
98
|
-
}
|
|
99
|
-
const answered = await graphql({ schema: filtered, source: getIntrospectionQuery() });
|
|
100
|
-
return answered.errors === undefined ? answered.data : undefined;
|
|
101
|
-
},
|
|
102
|
-
modules: blueprint.modules,
|
|
103
|
-
name: blueprint.name,
|
|
104
|
-
rights: blueprint.rights,
|
|
105
|
-
sdlFor: blueprint.sdlFor,
|
|
106
|
-
visible: (held) => {
|
|
107
|
-
const filtered = seenBy(held);
|
|
108
|
-
if (filtered === undefined) {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
const rooted = [filtered.getQueryType(), filtered.getMutationType()].filter(
|
|
112
|
-
(one) => one !== undefined && one !== null,
|
|
113
|
-
);
|
|
114
|
-
return rooted.some((one) => Object.keys(one.getFields()).length > 0);
|
|
115
|
-
},
|
|
116
|
-
});
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
export { Program };
|