nervur 0.10.0 → 0.11.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 CHANGED
@@ -143,7 +143,10 @@ const rotates = ({ mine }, from, nextPkHash) => {
143
143
  .list('refs/')
144
144
  .map((key) => ({ key, referred: mine.read(key) }))
145
145
  .find(({ referred }) => referred?.nextPkHash === claim);
146
- if (held === undefined) {
146
+ if (
147
+ held === undefined ||
148
+ (held.key !== `refs/${from}` && mine.read(`refs/${from}`) !== undefined)
149
+ ) {
147
150
  return undefined;
148
151
  }
149
152
  mine.write(`refs/${from}`, { nextPkHash, rights: held.referred.rights });
@@ -261,7 +264,7 @@ const Being = ({ voice, address, deliver, modules, registry, vouched, watch }) =
261
264
  });
262
265
  return answer({ referred: from, rights: pending.rights });
263
266
  }
264
- if (op?.ask === undefined || program === undefined) {
267
+ if (op?.ask === undefined || program === undefined || !program.visible(rights)) {
265
268
  return undefined;
266
269
  }
267
270
  const asked = await program.ask({
package/blueprint.mjs CHANGED
@@ -167,12 +167,12 @@ const merged = (base, added) =>
167
167
  ]),
168
168
  );
169
169
 
170
+ // Bare names, not Type.field pairs: X's "if create answers, it created"
171
+ // makes Query.create and Mutation.create one reserved word.
170
172
  const doorsIn = (sdl) =>
171
173
  parse(sdl)
172
- .definitions.filter(
173
- (one) => one.kind === Kind.OBJECT_TYPE_DEFINITION || one.kind === Kind.OBJECT_TYPE_EXTENSION,
174
- )
175
- .flatMap((one) => (one.fields ?? []).map((field) => `${one.name.value}.${field.name.value}`));
174
+ .definitions.filter((one) => doorHolder(one) && ['Mutation', 'Query'].includes(one.name.value))
175
+ .flatMap((one) => (one.fields ?? []).map((field) => field.name.value));
176
176
 
177
177
  const composed = (baseSource, addedSource) => {
178
178
  try {
@@ -319,23 +319,36 @@ const Blueprint = (source) => {
319
319
  rights,
320
320
  schema,
321
321
  // The directive definition stays: pointer.mjs runs buildSchema on this output.
322
- sdlFor: (held) =>
323
- print({
322
+ sdlFor: (held) => {
323
+ const kept = document.definitions
324
+ .map((one) =>
325
+ doorHolder(one)
326
+ ? {
327
+ ...one,
328
+ fields: (one.fields ?? []).filter((field) => {
329
+ const allowed = rightsOf(field);
330
+ return allowed === undefined || allowed.includes(held);
331
+ }),
332
+ }
333
+ : one,
334
+ )
335
+ .filter((one) => !doorHolder(one) || one.fields.length > 0);
336
+ const defined = new Set(
337
+ kept
338
+ .filter((one) => one.kind === Kind.OBJECT_TYPE_DEFINITION)
339
+ .map((one) => one.name.value),
340
+ );
341
+ // A filtered view where only a part's extension survives must still
342
+ // stand alone as a schema, or the right it serves opens nothing.
343
+ return print({
324
344
  ...document,
325
- definitions: document.definitions
326
- .map((one) =>
327
- doorHolder(one)
328
- ? {
329
- ...one,
330
- fields: (one.fields ?? []).filter((field) => {
331
- const allowed = rightsOf(field);
332
- return allowed === undefined || allowed.includes(held);
333
- }),
334
- }
335
- : one,
336
- )
337
- .filter((one) => !doorHolder(one) || one.fields.length > 0),
338
- }),
345
+ definitions: kept.map((one) =>
346
+ one.kind === Kind.OBJECT_TYPE_EXTENSION && !defined.has(one.name.value)
347
+ ? { ...one, kind: Kind.OBJECT_TYPE_DEFINITION }
348
+ : one,
349
+ ),
350
+ });
351
+ },
339
352
  trespass: [],
340
353
  });
341
354
  } catch {
package/client.mjs CHANGED
@@ -64,4 +64,4 @@ const Client = ({ voice, deliver, resolve = (one) => one }) => {
64
64
  });
65
65
  };
66
66
 
67
- export { Client };
67
+ export { Client, speaking };
package/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  export { ADMIN } from './admin.mjs';
2
2
  export { Blueprint, CLASSES, CORE_ONLY, OPEN, STRUCTURAL, coreClass, reads } from './blueprint.mjs';
3
- export { Client } from './client.mjs';
3
+ export { Client, speaking } from './client.mjs';
4
4
  export { Contract, Contracts } from './contract.mjs';
5
5
  export { FLOOR, GROUND, Ground } from './ground.mjs';
6
6
  export { Cells, Clock, Dns, Entropy, Keychain, Memory, Paths, Wire } from './modules.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nervur",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "A distributed system of sovereign beings — one door, one question: by whose authority.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Razvan Gherghina",
package/program.mjs CHANGED
@@ -4,6 +4,8 @@ import {
4
4
  getIntrospectionQuery,
5
5
  graphql,
6
6
  isObjectType,
7
+ parse,
8
+ validate,
7
9
  } from 'graphql';
8
10
  import { Blueprint } from './blueprint.mjs';
9
11
 
@@ -41,8 +43,39 @@ const Program = (source) => {
41
43
  }
42
44
  return curtained.get(held);
43
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
+ };
44
66
  return Object.freeze({
45
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
+ }
46
79
  const answered = await graphql({
47
80
  contextValue: context,
48
81
  schema,