nervur 0.7.6 → 0.8.1
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/README.md +56 -0
- package/being.mjs +13 -1
- package/blueprint.mjs +63 -1
- package/ground.mjs +6 -2
- package/package.json +1 -1
- package/program.mjs +1 -0
package/README.md
CHANGED
|
@@ -313,6 +313,62 @@ MEMBER — once, and never again. `Clock` and `Entropy` are not floor: they
|
|
|
313
313
|
are ordinary modules a program declares, pinnable so a program never
|
|
314
314
|
reaches for the wall.
|
|
315
315
|
|
|
316
|
+
## Extending the administration
|
|
317
|
+
|
|
318
|
+
A ground's administration is a being like any other, and the only one whose
|
|
319
|
+
context holds the registry — so a door that must create, upgrade or destroy
|
|
320
|
+
beings belongs on it and nowhere else. Adopters extend it; nobody replaces
|
|
321
|
+
it.
|
|
322
|
+
|
|
323
|
+
Pass an extension through the `blueprints` module and the ground composes it
|
|
324
|
+
with the standard administration. Write it as an ordinary blueprint whose
|
|
325
|
+
schema **extends** the roots:
|
|
326
|
+
|
|
327
|
+
```js
|
|
328
|
+
const cases = `({
|
|
329
|
+
name: 'procese.cases',
|
|
330
|
+
needs: { caller: true, modules: [{ module: 'memory', contract: 'nervur.memory' }] },
|
|
331
|
+
memory: { 'case/*': { type: 'String', class: 'data' } },
|
|
332
|
+
interface: \`
|
|
333
|
+
extend type Mutation {
|
|
334
|
+
openCase(title: String!): String @rights(is: [SELF, MEMBER])
|
|
335
|
+
}
|
|
336
|
+
\`,
|
|
337
|
+
resolvers: {
|
|
338
|
+
Mutation: {
|
|
339
|
+
openCase: (_, { title }, { from, modules, registry }) => {
|
|
340
|
+
const at = registry.create({ program: CASE, refs: [{ address: from }] });
|
|
341
|
+
modules.memory.write('case/' + at, title);
|
|
342
|
+
return at ?? null;
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
})`;
|
|
347
|
+
|
|
348
|
+
const ground = Ground({ cells, keychain, blueprints: { ground: () => cases } });
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
The composed program is one blueprint with one digest, so `describe` answers
|
|
352
|
+
what this ground's administration actually is, and an outsider can pin it.
|
|
353
|
+
|
|
354
|
+
Three rules hold, and each refuses the whole ground rather than bending:
|
|
355
|
+
|
|
356
|
+
1. **Standard door names are reserved.** Declare `create`, `census`,
|
|
357
|
+
`upgrade`, `destroy`, `alias`, `attest`, `opened` or `init` and no ground
|
|
358
|
+
rises. You cannot change what a standard door means — which is why a
|
|
359
|
+
stranger may trust that if `create` answers, it created.
|
|
360
|
+
2. **Add only.** Use `extend type Query` / `extend type Mutation`; cell
|
|
361
|
+
names, module slots and `needs.config` may not collide with the
|
|
362
|
+
administration's own.
|
|
363
|
+
3. **Close a door the way anything is closed here** — fence it behind a
|
|
364
|
+
right the ground never grants. The door stands, means what the
|
|
365
|
+
constitution says, and answers nobody: that is how an appliance ground
|
|
366
|
+
stops creating beings, without redefining a thing.
|
|
367
|
+
|
|
368
|
+
The acts stay the core's own: an extension reaches them through the same
|
|
369
|
+
registry face the standard doors use, so it can invent no power the standard
|
|
370
|
+
administration lacked.
|
|
371
|
+
|
|
316
372
|
## Writing a module
|
|
317
373
|
|
|
318
374
|
Five rules, each load-bearing:
|
package/being.mjs
CHANGED
|
@@ -39,6 +39,17 @@ const Compartment = ({ memory, voice, address, root, classOf }) => {
|
|
|
39
39
|
});
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
const offered = (modules, program, mine) =>
|
|
43
|
+
Object.freeze({
|
|
44
|
+
...Object.fromEntries(
|
|
45
|
+
(program?.modules ?? [])
|
|
46
|
+
.map((one) => one.module)
|
|
47
|
+
.filter((name) => name !== 'memory' && modules[name] !== undefined)
|
|
48
|
+
.map((name) => [name, modules[name]]),
|
|
49
|
+
),
|
|
50
|
+
memory: scoped(mine, program),
|
|
51
|
+
});
|
|
52
|
+
|
|
42
53
|
const grantable = (program) =>
|
|
43
54
|
(program?.rights ?? [...STRUCTURAL, 'MEMBER']).filter((one) => !STRUCTURAL.includes(one));
|
|
44
55
|
|
|
@@ -240,7 +251,8 @@ const Being = ({ voice, address, deliver, modules, registry, vouched, watch }) =
|
|
|
240
251
|
}
|
|
241
252
|
const asked = await program.ask({
|
|
242
253
|
context: {
|
|
243
|
-
|
|
254
|
+
from: program.caller ? from : undefined,
|
|
255
|
+
modules: offered(modules, program, mine),
|
|
244
256
|
port: Port({ voice, mine, deliver, rights }),
|
|
245
257
|
registry,
|
|
246
258
|
rights,
|
package/blueprint.mjs
CHANGED
|
@@ -156,6 +156,67 @@ const resolverSources = (resolvers) =>
|
|
|
156
156
|
.map((field) => `${type}.${field}=${resolvers[type][field].toString()}`),
|
|
157
157
|
);
|
|
158
158
|
|
|
159
|
+
const merged = (base, added) =>
|
|
160
|
+
Object.fromEntries(
|
|
161
|
+
[...new Set([...Object.keys(base ?? {}), ...Object.keys(added ?? {})])].map((key) => [
|
|
162
|
+
key,
|
|
163
|
+
{ ...(base ?? {})[key], ...(added ?? {})[key] },
|
|
164
|
+
]),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const doorsIn = (sdl) =>
|
|
168
|
+
parse(sdl)
|
|
169
|
+
.definitions.filter(
|
|
170
|
+
(one) => one.kind === Kind.OBJECT_TYPE_DEFINITION || one.kind === Kind.OBJECT_TYPE_EXTENSION,
|
|
171
|
+
)
|
|
172
|
+
.flatMap((one) => (one.fields ?? []).map((field) => `${one.name.value}.${field.name.value}`));
|
|
173
|
+
|
|
174
|
+
const composed = (baseSource, addedSource) => {
|
|
175
|
+
try {
|
|
176
|
+
const base = evaluated(baseSource);
|
|
177
|
+
const added = evaluated(addedSource);
|
|
178
|
+
const standard = doorsIn(base.interface);
|
|
179
|
+
if (doorsIn(added.interface).some((one) => standard.includes(one))) {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
const cells = { ...(base.memory ?? {}), ...(added.memory ?? {}) };
|
|
183
|
+
if (
|
|
184
|
+
Object.keys(base.memory ?? {}).some((key) => (added.memory ?? {})[key] !== undefined) ||
|
|
185
|
+
(added.needs?.config !== undefined && base.needs?.config !== undefined)
|
|
186
|
+
) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
const slots = [...(base.needs?.modules ?? []), ...(added.needs?.modules ?? [])];
|
|
190
|
+
if (new Set(slots.map((one) => one.module)).size !== slots.length) {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
const needs = {
|
|
194
|
+
...(base.needs ?? {}),
|
|
195
|
+
...(added.needs ?? {}),
|
|
196
|
+
caller: base.needs?.caller === true || added.needs?.caller === true,
|
|
197
|
+
modules: slots,
|
|
198
|
+
};
|
|
199
|
+
const doors = merged(base.resolvers, added.resolvers);
|
|
200
|
+
const written = Object.entries(doors)
|
|
201
|
+
.map(
|
|
202
|
+
([type, fields]) =>
|
|
203
|
+
`${type}: { ${Object.entries(fields)
|
|
204
|
+
.map(([field, one]) => `${field}: ${one.toString()}`)
|
|
205
|
+
.join(', ')} }`,
|
|
206
|
+
)
|
|
207
|
+
.join(', ');
|
|
208
|
+
return `({
|
|
209
|
+
name: ${JSON.stringify(added.name)},
|
|
210
|
+
needs: ${JSON.stringify(needs)},
|
|
211
|
+
memory: ${JSON.stringify(cells)},
|
|
212
|
+
interface: ${JSON.stringify(`${base.interface}\n${added.interface}`)},
|
|
213
|
+
resolvers: { ${written} },
|
|
214
|
+
})`;
|
|
215
|
+
} catch {
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
159
220
|
const inputCheck = (schema, typeName, given) => {
|
|
160
221
|
const type = schema.getType(typeName);
|
|
161
222
|
if (type?.getFields === undefined) {
|
|
@@ -247,6 +308,7 @@ const Blueprint = (source) => {
|
|
|
247
308
|
resolvers: resolverSources(written.resolvers ?? {}),
|
|
248
309
|
}),
|
|
249
310
|
),
|
|
311
|
+
caller: needs.caller === true,
|
|
250
312
|
memory: Object.freeze(memory),
|
|
251
313
|
modules,
|
|
252
314
|
name: written.name,
|
|
@@ -278,4 +340,4 @@ const Blueprint = (source) => {
|
|
|
278
340
|
}
|
|
279
341
|
};
|
|
280
342
|
|
|
281
|
-
export { Blueprint, CLASSES, CORE_ONLY, FORBIDDEN, OPEN, STRUCTURAL, coreClass, reads };
|
|
343
|
+
export { Blueprint, CLASSES, CORE_ONLY, FORBIDDEN, OPEN, STRUCTURAL, composed, coreClass, reads };
|
package/ground.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ADMIN } from './admin.mjs';
|
|
2
2
|
import { Being, Compartment, Registry, make, rootFor } from './being.mjs';
|
|
3
|
-
import { coreClass } from './blueprint.mjs';
|
|
3
|
+
import { Blueprint, composed, coreClass } from './blueprint.mjs';
|
|
4
4
|
import { Client } from './client.mjs';
|
|
5
5
|
import { Contracts } from './contract.mjs';
|
|
6
6
|
import { Memory, Paths, Wire } from './modules.mjs';
|
|
@@ -41,7 +41,11 @@ const Ground = (modules = {}) => {
|
|
|
41
41
|
return undefined;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const
|
|
44
|
+
const extension = modules.blueprints?.ground();
|
|
45
|
+
const program = extension === undefined ? ADMIN : composed(ADMIN, extension);
|
|
46
|
+
if (program === undefined || Blueprint(program)?.digest === undefined) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
45
49
|
if (cells.peek('ground/admin') === undefined) {
|
|
46
50
|
install({ cells, keychain, program });
|
|
47
51
|
}
|
package/package.json
CHANGED
package/program.mjs
CHANGED
|
@@ -52,6 +52,7 @@ const Program = (source) => {
|
|
|
52
52
|
return answered.errors === undefined ? { data: answered.data } : undefined;
|
|
53
53
|
},
|
|
54
54
|
bindings: blueprint.bindings,
|
|
55
|
+
caller: blueprint.caller,
|
|
55
56
|
classOf: blueprint.classOf,
|
|
56
57
|
config: blueprint.config,
|
|
57
58
|
declares: blueprint.declares,
|