nervur 0.15.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 CHANGED
@@ -1,3 +1,4 @@
1
+
1
2
  Apache License
2
3
  Version 2.0, January 2004
3
4
  http://www.apache.org/licenses/
@@ -178,7 +179,7 @@
178
179
  APPENDIX: How to apply the Apache License to your work.
179
180
 
180
181
  To apply the Apache License to your work, attach the following
181
- boilerplate notice, with the fields enclosed by brackets "{}"
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
183
  replaced with your own identifying information. (Don't include
183
184
  the brackets!) The text should be enclosed in the appropriate
184
185
  comment syntax for the file format. We also recommend that a
package/NOTICE ADDED
@@ -0,0 +1,6 @@
1
+ Quo
2
+ Copyright 2026 Razvan Gherghina
3
+
4
+ This product includes software developed by Razvan Gherghina.
5
+
6
+ Licensed under the Apache License, Version 2.0. See LICENSE for the terms.
package/README.md CHANGED
@@ -1,152 +1,32 @@
1
1
  # nervur
2
2
 
3
- Nervur grown on the working Quo kit. It imports `@quo-systems/js` and adds no
4
- protocol
5
- of its own — no wire, no envelope, no judgment, no arithmetic, ever. What
6
- Nervur is for begins where the constitution stops: the graph, the organs, the
7
- floor, and the projection from an authored contract into Quo blueprints.
3
+ Quo is a protocol that lets an object ask another object and get an answer,
4
+ without knowing whether that other object is in the same process, on the
5
+ same device, or on another planet. Nervur's kit is its first implementation,
6
+ open source, three packages under the `@nervur-org` scope.
8
7
 
9
- ## The rules of this build
8
+ This package is the unscoped twin of that scope. `import` from it and you
9
+ have the library; run it and you have the dock's command:
10
10
 
11
- 1. **`@quo-systems/js` is the whole of the protocol.** It is crossed by its
12
- specifier,
13
- never by a path. A gap the kit shows is reported to the quo table, never
14
- patched here.
15
- 2. **nervur-dream is parked beside this, as reference.** Its mechanics may be
16
- lifted — the compiler, the contract reader, the `@needs` directive. Its
17
- judgement may not: what a thing enforces, refuses or means is authored fresh
18
- against the constitution as it stands today, because dream was built against
19
- a protocol that no longer exists.
20
- 3. **Crockford.** Factory functions, closures for privacy, no `this`, frozen
21
- surfaces. The kit's class idiom stops at its specifier: the ground factory
22
- is the one place a `Warden` instance lives, closed over and never returned.
23
- 4. **One played bench question at a time.** Doubt is settled on the bench, not
24
- argued. Nothing lands without an assert that would fail if it broke.
25
- 5. **A being never learns who is calling, but it knows who may reach it.** The
26
- first half is the kit's doing: the warden places a voice at step three and
27
- hands the field its arguments and its leash, never the caller. Which being
28
- was reached already says who called. The second half is Nervur's: the warden
29
- keeps the record of which voices reach which beings, and a being is handed
30
- its own row to read and to change. No rights and no roles — those are
31
- meanings, and the record holds none.
32
-
33
- ## What stands
34
-
35
- **`ground()`** — one closure over one warden, holding beings and judging what
36
- arrives. `bench/ground.test.js` walks it end to end: a real sealed ask, a real
37
- answer, and silence where a standing does not reach.
38
-
39
- **The outbound relation** — `remember` records an invitation this ground was
40
- handed and names which of its beings spends it; `ask` seals down that relation,
41
- posts it at the hints the invitation carried, and reads the answer back. Every
42
- one of those acts is the kit's. What is Nervur's is the ergonomics: a relation
43
- is found by whose it is, so one being cannot spend another's; the number is the
44
- ground's to pick, so no caller counts by hand; and the first ask carries the
45
- rotation an invitation's heir keys oblige, so nobody meets that trap twice.
46
- `bench/estate.test.js` stands two houses on two real loopback doors and crosses
47
- between them.
48
-
49
- Silence, unreachable and a relation that is not yours are three different
50
- things and stay three: silence is `null`, a road that does not answer throws
51
- out of the carriage, and spending what a being does not hold throws before a
52
- byte moves.
53
-
54
- **`contract(sdl)` and `program(contract, resolvers)`** — the compiler, and
55
- there is exactly one of it. A being and an organ are both a contract plus
56
- resolvers, and `program` cannot tell which it is holding; that is not a
57
- convenience, it is the architecture. A field is read with `hasOwn` and never
58
- through the prototype chain, exactly the declared arguments reach a resolver
59
- and no others, a resolver short of a required argument is not called at all,
60
- and a resolver that throws is answered as silence — nothing here promises that
61
- an ask is fulfilled, so a refusal a caller must tell apart is carried in the
62
- answer type instead. `bench/program.test.js` asserts every one of those, and
63
- asserts the first on a being and an organ side by side.
64
-
65
- **The contract is a GraphQL schema** — schema, resolvers and a context, the
66
- shape every engineer already knows. It is authoring and nothing else: never at
67
- the wire, never an answer, never a describe. The organs a being reaches outward
68
- with are declared in the schema itself, with `schema @needs(organs: [...])`,
69
- and the being's own name is the root type's — `schema { query: Clerk }`.
70
-
71
- **`project(contract)`** — the boundary, and there must be exactly one of it.
72
- Every implementer of Quo speaks the notation and has never heard of Nervur, so
73
- a contract becomes a Quo blueprint in the kit's own notation before anything
74
- holds it, and what a stranger verifies is that blueprint's digest. GraphQL is
75
- nullable by default and the notation is required by default, so `String!`
76
- becomes `text` and `String` becomes `text?`; the schema's other object and
77
- input types become record blocks, and their ordering is the notation's own law
78
- and therefore the kit's `print` to judge. Where GraphQL says something the
79
- notation has no word for — `Float`, `ID`, an enum, a union, an interface, a
80
- record field that takes arguments — the contract is refused at build rather
81
- than guessed at. One word runs the other way: the notation has a field that
82
- answers nothing and GraphQL insists every field has a type, so `Nothing` in
83
- answer position is that field's spelling. `bench/projection.test.js` holds it.
84
-
85
- **`being(sdl, resolvers)`** — a schema and ordinary functions under it. It is
86
- compiled by `program` like anything else; what it adds is the wire skin the kit
87
- leaves open, and the two promises the kit leaves to Nervur: a resolver meets
88
- its arguments as named values and never bytes, and a being answers **exactly**
89
- the fields its contract declares. That second one is the gate the digest's
90
- meaning rests on — the law says what a blueprint does not declare does not
91
- exist, and the kit dispatches on whatever methods the held object happens to
92
- have.
93
-
94
- **Organs** — the only way a being _acts_ outward. That is not a style choice:
95
- a being has no warden and no caller, so it cannot mint standing and a field
96
- answering another being's name hands over a label rather than reach. What it
97
- can still do is pass on standing it was already given — the constitution says
98
- data can carry an `invitation`, and an invitation carries the heir keys — so
99
- "a being cannot hand over reach" is false as a general claim and only the
100
- minting half is true. A being names the organ aliases it needs in its own
101
- schema, the ground supplies them at `hold`, and the resolvers are closed over
102
- exactly those. An organ the schema never asked for is not absent from a check —
103
- it is absent from the object.
104
-
105
- **The organ that crosses** — `errand` mints the reach a being uses to speak to
106
- another house. It is shut over which being spends the relation and which house
107
- it stands at, so a being holds one road on its own behalf and nothing it could
108
- point elsewhere; it never holds the warden and never holds the relation. Which
109
- being at the far house is still the caller's to name, because the standing
110
- decides that and the standing is judged over there. The **leash** is handed in
111
- rather than chosen — the resolver passes on the one the door gave it, whole and
112
- never a number read off it, because what may go onward is this door's dwell
113
- subtracted at the moment of sealing, which is later than any moment the being
114
- could have read. So the remainder reaches the far house without the being
115
- having chosen it, and a being cannot hand its organ more than it received.
116
- `bench/errand.test.js` serves one sealed ask at the near house by crossing to
117
- the far one and answering with what came back.
118
-
119
- **Contacts** — who reaches this being, and the two acts that change it:
120
- `list()`, `admit(keys)` and `remove(voicePk)`. Every one of them goes to the
121
- warden's own inbound record — `grant`, `amend`, `standing` — read live at the
122
- moment it is called and never snapshotted, so a being that admitted someone a
123
- moment ago sees them. Quo keeps that record and refuses to have an opinion
124
- about it, having no word for member, owner or guest; the opinion is Nervur's,
125
- and this is the whole of it. The inverse — voices by being, where the warden
126
- keeps beings by voice — is derived rather than indexed, because a second index
127
- is a second truth.
128
-
129
- The scoping is structural, the same shape `errand` uses: the context is minted
130
- per `hold` with the being shut inside the closure, so there is no address it
131
- could name to reach another being's row. It reaches a resolver as the resolver
132
- factory's second argument, closed over at raise beside the organs, rather than
133
- as a third argument to every resolver. `bench/contacts.test.js` proves the
134
- admission and the removal at the door — a real sealed ask that gets in, and
135
- then meets silence.
11
+ ```bash
12
+ npm install nervur
13
+ npx nervur init
14
+ ```
136
15
 
137
- Organs are declared inside the contract, with `schema @needs(organs: [...])`.
138
- That directive is authoring and never reaches the wire: the projection drops
139
- it, so the blueprint a stranger reads carries no trace of what a being reaches
140
- outward with, which is inside-the-ground business the wire never sees.
16
+ | Package | What it is |
17
+ | --- | --- |
18
+ | [`@nervur-org/nervur`](https://www.npmjs.com/package/@nervur-org/nervur) | the library: harbor, ward and being, the protocol itself, with the spec inside |
19
+ | [`@nervur-org/dock`](https://www.npmjs.com/package/@nervur-org/dock) | what every estate on Quo needs and nobody writes twice: the daemon, the command, the model sides and the screen |
20
+ | [`@nervur-org/ui`](https://www.npmjs.com/package/@nervur-org/ui) | the kit: one token contract, one baseline, and the primitives a screen and a website both need. Knows nothing of Quo |
141
21
 
142
- ## What is not here yet
22
+ It pins the library and the dock exactly, so that the name stands for one
23
+ kit. Its own number counts up on its own, because a name and what it
24
+ stands for are two things. An estate that wants the pieces apart installs
25
+ them apart.
143
26
 
144
- `own` and the floor, memory, standings and the graph walked across them, the
145
- per-call caller, ground-as-a-folder, persistence, register-and-invoke,
146
- collections-as-beings, per-caller beings.
27
+ <https://nervur.org> is the kit. <https://quo.systems> is the protocol.
147
28
 
148
- ## The bench
29
+ ## License
149
30
 
150
- ```bash
151
- npm run gate-nervur
152
- ```
31
+ Apache-2.0. Copyright 2026 Razvan Gherghina. See [LICENSE](LICENSE) and
32
+ [NOTICE](NOTICE).
package/index.js ADDED
@@ -0,0 +1,17 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // The name, and what stands under it. This package is the unscoped twin of
3
+ // the `@nervur-org` scope: `import ... from 'nervur'` is the library, and
4
+ // `npx nervur` is the dock's command. It pins the two exactly, because a
5
+ // name that stands for the kit stands for one version of it.
6
+
7
+ export * from '@nervur-org/nervur';
8
+
9
+ /** The published packages, and what each one is. */
10
+ export const PACKAGES = Object.freeze({
11
+ '@nervur-org/nervur': 'the library: harbor, ward and being, the first kit of Quo',
12
+ '@nervur-org/dock': 'what every estate on Quo needs and nobody writes twice',
13
+ '@nervur-org/ui': 'the kit: one token contract, one baseline, the primitives',
14
+ });
15
+
16
+ /** Where to read what the kit is. */
17
+ export const HOMEPAGE = 'https://nervur.org';
package/nervur.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // `nervur`: the dock's command under the kit's name. Every subcommand is the
4
+ // dock's; this file only finds it.
5
+ import '@nervur-org/dock/cli';
package/package.json CHANGED
@@ -1,24 +1,42 @@
1
1
  {
2
2
  "name": "nervur",
3
- "version": "0.15.0",
4
- "description": "Nervur grown on the Quo kit: the graph, the organs and the floor, with @quo-systems/js as the whole of the protocol",
5
- "license": "Apache-2.0",
3
+ "version": "0.16.0",
4
+ "description": "Nervur's kit of Quo, under one name: the library re-exported, and the dock's command as nervur. The packages are @nervur-org/nervur, @nervur-org/dock and @nervur-org/ui.",
5
+ "keywords": [
6
+ "quo",
7
+ "nervur",
8
+ "protocol",
9
+ "capability",
10
+ "object-capability"
11
+ ],
6
12
  "author": "Razvan Gherghina",
13
+ "license": "Apache-2.0",
14
+ "homepage": "https://nervur.org",
7
15
  "type": "module",
8
- "sideEffects": false,
16
+ "engines": {
17
+ "node": ">=22.18"
18
+ },
19
+ "bin": {
20
+ "nervur": "./nervur.js"
21
+ },
9
22
  "exports": {
10
- ".": "./src/index.js"
23
+ ".": "./index.js"
11
24
  },
12
- "engines": {
13
- "node": ">=22"
25
+ "scripts": {
26
+ "prepublishOnly": "test \"$QUO_GATED\" = 1 || { echo 'publish from the root, gated once: npm run release:nervur' >&2; exit 1; }"
14
27
  },
15
28
  "dependencies": {
16
- "@quo-systems/js": "0.0.3",
17
- "graphql": "17.0.2"
29
+ "@nervur-org/dock": "0.3.0",
30
+ "@nervur-org/nervur": "0.3.0"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
18
34
  },
19
35
  "files": [
20
- "src",
36
+ "index.js",
37
+ "nervur.js",
38
+ "README.md",
21
39
  "LICENSE",
22
- "README.md"
40
+ "NOTICE"
23
41
  ]
24
42
  }
package/src/being.js DELETED
@@ -1,125 +0,0 @@
1
- import { Refusal, parse, recordsOf, encode, decodeAll } from '@quo-systems/js';
2
- import { contract as read } from './contract.js';
3
- import { program } from './program.js';
4
- import { project } from './projection.js';
5
-
6
- const refuse = (code, detail) => {
7
- throw new Refusal(code, detail);
8
- };
9
-
10
- // A being is a contract and its resolvers, and nothing else. The contract is a
11
- // GraphQL schema — authoring, and authoring only — projected to a Quo blueprint
12
- // before anything holds it, so the warden gets the notation it has always got
13
- // and a stranger implementing Quo elsewhere reads a blueprint like any other.
14
- //
15
- // A being is compiled by `program`, which compiles organs too and cannot tell
16
- // which it is holding. What this file adds is the wire skin the kit leaves
17
- // open: bytes in and bytes out, and the object the warden dispatches from.
18
- //
19
- // Two things are Nervur's to keep, and the law is what asks for them. "What a
20
- // blueprint does not declare does not exist," and "granting a being is granting
21
- // every field it declares" — together they make the declared set the scope of a
22
- // grant. A held object with a method the blueprint never named would answer
23
- // callers anyway, which is a grant that grants more than its digest says.
24
- //
25
- // A being is raised rather than built: the organs it declared with
26
- // `schema @needs(organs: [...])` are supplied by the ground at `hold`, and the
27
- // resolvers are closed over them. Reach is a fact about a being, not about a
28
- // call, so it arrives once — and that is what makes the refusal structural. An
29
- // organ the being did not declare is not absent from a check; it is absent from
30
- // the object, and there is nothing to police.
31
- //
32
- // `contacts` arrives the same way, as the resolver factory's second argument.
33
- // What is closed over is a **reader**, not a value: reach is fixed at raise and
34
- // contacts are not, so `list()` goes to the warden's record at the moment it is
35
- // called and there is nothing here to go stale.
36
- export function being(sdl, resolvers) {
37
- const held = read(sdl);
38
- const text = project(held);
39
- const blueprint = parse(text);
40
- const records = recordsOf(blueprint);
41
-
42
- const raise = (supplied = {}, contacts = null) =>
43
- object(held, blueprint, records, resolvers, supplied, contacts);
44
- // A being that cannot stand should say so at once rather than at a hold. But
45
- // only where saying so costs nothing: a resolver factory may allocate, or
46
- // register itself somewhere, and running it speculatively would do that twice
47
- // for a being with no organs and once for a being with them. So a plain set
48
- // of resolvers is checked here, and a factory is run exactly once, at hold.
49
- if (held.needs.length === 0 && typeof resolvers !== 'function') raise();
50
-
51
- return Object.freeze({
52
- contract: text,
53
- organs: Object.freeze([...held.needs]),
54
- raise,
55
- schema: sdl,
56
- });
57
- }
58
-
59
- function object(held, blueprint, records, resolvers, supplied, contacts) {
60
- // The ground offers; the being takes what its schema declared. Two
61
- // narrowings, and an organ offered but not needed is simply not taken — a
62
- // ground handing the same tray to every being it holds is the ordinary case.
63
- const reach = {};
64
- for (const alias of held.needs) {
65
- if (!supplied[alias]) refuse('ORGAN_MISSING', alias);
66
- reach[alias] = supplied[alias];
67
- }
68
- const organs = Object.freeze(reach);
69
-
70
- const built = typeof resolvers === 'function' ? resolvers(organs, contacts) : resolvers;
71
-
72
- // Both halves of the mismatch are refused, and refused here rather than at a
73
- // call: a being that stands and then falls over on one field is a being whose
74
- // digest lied to everyone who read it. `hasOwn`, not a bare lookup — a
75
- // contract declaring `toString` would otherwise be satisfied by
76
- // `Object.prototype`'s, and a resolver is a thing an author wrote, not a
77
- // thing a prototype happens to supply.
78
- for (const field of Object.keys(held.fields)) {
79
- if (!Object.hasOwn(built, field) || typeof built[field] !== 'function') {
80
- refuse('NO_RESOLVER', field);
81
- }
82
- }
83
- // A resolver the contract does not declare is unreachable by design. Silence
84
- // would hide the likelier cause — a name that does not match — so it is an
85
- // error instead.
86
- for (const name of Object.keys(built)) {
87
- if (!Object.hasOwn(held.fields, name)) refuse('UNDECLARED_RESOLVER', name);
88
- }
89
-
90
- const one = program(held, built);
91
-
92
- // No prototype, and this is the gate rather than a tidiness. The kit
93
- // dispatches with `object[name]` and `typeof === 'function'`, and an
94
- // inherited method passes both — a plain object here answers a sealed ask for
95
- // `constructor`, because `Object` is a function and hands back the very bytes
96
- // it was called with, which is exactly what the warden accepts as an answer.
97
- // Freezing does not help: freezing does not remove a prototype.
98
- const table = Object.create(null);
99
- for (const field of blueprint.fields) {
100
- const types = field.args.map((argument) => argument.type);
101
- const names = field.args.map((argument) => argument.name);
102
-
103
- // The kit's signature: bytes in, bytes or nothing out. A resolver sees
104
- // neither. It sees its arguments by the names its contract gave them, and
105
- // its leash — never a caller, never an address, because a being learns
106
- // neither and Nervur does not soften it.
107
- table[field.name] = async (bytes, leash) => {
108
- const values = decodeAll(types, bytes, records);
109
- const answered = await one.answer(
110
- field.name,
111
- Object.fromEntries(names.map((name, at) => [name, values[at]])),
112
- leash,
113
- );
114
- // A field declaring no answer answers nothing, and that is an answer: the
115
- // door sends an envelope with no data in it.
116
- if (!field.answer) return undefined;
117
- // An answer that does not fit its declared type never leaves. `encode`
118
- // refuses it, the warden's global catch turns that into silence, and
119
- // silence is the only refusal a door can express.
120
- return encode(field.answer, answered, records);
121
- };
122
- }
123
-
124
- return Object.freeze(table);
125
- }
package/src/contract.js DELETED
@@ -1,161 +0,0 @@
1
- import { parse as parseGraphQL } from 'graphql';
2
- import { Refusal } from '@quo-systems/js';
3
-
4
- // What a thing answers, read once from its own text. A being's contract and an
5
- // organ's contract are the same file under the same name, because they are one
6
- // kind of code — so there is one reader here and no second one anywhere.
7
- //
8
- // The contract is a GraphQL schema. That is authoring and nothing else: it
9
- // never reaches the wire, is never an answer, and is never a describe. Every
10
- // implementer of Quo speaks the notation and has never heard of Nervur, so a
11
- // contract is projected to a Quo blueprint at the ground's edge and that
12
- // projection is the only boundary.
13
- //
14
- // A contract carries no identity of its own. The being's name is the digest of
15
- // its projected blueprint, which the warden takes, and a second identity taken
16
- // over the GraphQL bytes would be a second truth about the same thing.
17
-
18
- const refuse = (code, detail) => {
19
- throw new Refusal(code, detail);
20
- };
21
-
22
- const OPERATIONS = Object.freeze(['query', 'mutation', 'subscription']);
23
-
24
- const named = (node) => node.name.value;
25
-
26
- // A field's type, flattened to the name it ends at, whether it is a list, and
27
- // whether the caller must give it. GraphQL is nullable by default and says so
28
- // by omission; the projection is where that becomes `?`, not here.
29
- const inner = (node) => (node.kind === 'NamedType' ? named(node) : inner(node.type));
30
-
31
- const typed = (node) =>
32
- node.kind === 'NonNullType'
33
- ? { ...typed(node.type), required: true }
34
- : node.kind === 'ListType'
35
- ? { list: true, of: inner(node.type), required: false, element: typed(node.type) }
36
- : { of: named(node), required: false };
37
-
38
- // What a contract says it needs to work, written in the schema itself:
39
- //
40
- // schema @needs(organs: ["book", "far"]) { query: Clerk }
41
- //
42
- // It is a need and never a binding. A contract is written to stand on any
43
- // ground, so it names what it must reach and says nothing about whose
44
- // implementation answers.
45
- //
46
- // Undeclared is unreachable, and never by a check: a contract that never asked
47
- // for an organ does not have it and cannot learn that it exists, because the
48
- // object it is handed has no such key.
49
- const needed = (definition) => {
50
- for (const directive of definition?.directives ?? []) {
51
- if (directive.name.value !== 'needs') continue;
52
- const organs = (directive.arguments ?? []).find((one) => one.name.value === 'organs');
53
- if (organs?.value?.kind !== 'ListValue') refuse('BAD_NEEDS', 'organs must be a list of names');
54
- return organs.value.values.map((one) => {
55
- if (one.kind !== 'StringValue') refuse('BAD_NEEDS', 'an organ is named by a string');
56
- return one.value;
57
- });
58
- }
59
- return [];
60
- };
61
-
62
- // Which type is the root of each operation. GraphQL's own `schema` block names
63
- // them, and that is where the being's name comes from — `schema { query: Clerk }`
64
- // makes a class block called `Clerk`. With no `schema` block the conventional
65
- // type names stand in, which is the plain form every engineer already writes.
66
- const rooted = (definition) => {
67
- const roots = { query: 'Query', mutation: 'Mutation', subscription: 'Subscription' };
68
- for (const operation of definition?.operationTypes ?? []) {
69
- roots[operation.operation] = named(operation.type);
70
- }
71
- return roots;
72
- };
73
-
74
- const fieldsOf = (definition) =>
75
- Object.fromEntries(
76
- (definition.fields ?? []).map((field) => [
77
- named(field),
78
- Object.freeze({
79
- args: Object.freeze(
80
- Object.fromEntries(
81
- (field.arguments ?? []).map((one) => [named(one), Object.freeze(typed(one.type))]),
82
- ),
83
- ),
84
- answers: Object.freeze(typed(field.type)),
85
- }),
86
- ]),
87
- );
88
-
89
- export function contract(sdl) {
90
- if (typeof sdl !== 'string') refuse('NOT_A_SCHEMA', typeof sdl);
91
- let document;
92
- try {
93
- document = parseGraphQL(sdl);
94
- } catch (error) {
95
- refuse('UNREADABLE_SCHEMA', error.message);
96
- }
97
-
98
- const schema = document.definitions.find(
99
- (one) => one.kind === 'SchemaDefinition' || one.kind === 'SchemaExtension',
100
- );
101
- const roots = rooted(schema);
102
- const rootNames = new Set(Object.values(roots));
103
-
104
- const objects = new Map();
105
- const inputs = new Map();
106
- for (const definition of document.definitions) {
107
- if (definition.kind === 'ObjectTypeDefinition') {
108
- if (objects.has(named(definition))) refuse('DUPLICATE_TYPE', named(definition));
109
- objects.set(named(definition), definition);
110
- continue;
111
- }
112
- if (definition.kind === 'InputObjectTypeDefinition') {
113
- if (inputs.has(named(definition))) refuse('DUPLICATE_TYPE', named(definition));
114
- inputs.set(named(definition), definition);
115
- continue;
116
- }
117
- if (definition.kind === 'SchemaDefinition' || definition.kind === 'SchemaExtension') continue;
118
- // Everything else — interfaces, unions, enums, fragments, an operation —
119
- // is a thing the notation has no word for, and a contract carrying one is
120
- // refused where it is read rather than guessed at where it is projected.
121
- refuse('NO_WORD', definition.kind);
122
- }
123
-
124
- // Every root field, flat, with the operation it stands under. This is what a
125
- // caller may ask, and there is no register of fields anywhere else.
126
- // Resolvers are flat under the contract, not nested under a root: one
127
- // contract is one class block in the notation, so a field name is unique
128
- // across the whole contract and a second layer would name nothing.
129
- const fields = {};
130
- for (const operation of OPERATIONS) {
131
- const definition = objects.get(roots[operation]);
132
- if (!definition) continue;
133
- for (const [name, one] of Object.entries(fieldsOf(definition))) {
134
- if (Object.hasOwn(fields, name)) refuse('DUPLICATE_FIELD', name);
135
- fields[name] = Object.freeze({ ...one, operation });
136
- }
137
- }
138
- if (Object.keys(fields).length === 0) refuse('NO_FIELDS', 'a contract that answers nothing');
139
-
140
- // The name a being wears: the query root's, or the mutation root's where a
141
- // contract only writes. One contract is one class block, so one name.
142
- const name = objects.has(roots.query)
143
- ? roots.query
144
- : objects.has(roots.mutation)
145
- ? roots.mutation
146
- : roots.subscription;
147
-
148
- const records = {};
149
- for (const [typeName, definition] of [...objects, ...inputs]) {
150
- if (rootNames.has(typeName)) continue;
151
- records[typeName] = Object.freeze(fieldsOf(definition));
152
- }
153
-
154
- return Object.freeze({
155
- fields: Object.freeze(fields),
156
- name,
157
- needs: Object.freeze([...new Set(needed(schema))]),
158
- records: Object.freeze(records),
159
- sdl,
160
- });
161
- }
package/src/ground.js DELETED
@@ -1,273 +0,0 @@
1
- import { Warden, commitment, readAnswer, reach, signingPair } from '@quo-systems/js';
2
- import { serve } from '@quo-systems/js/door';
3
-
4
- const same = (a, b) => a.length === b.length && a.every((byte, at) => byte === b[at]);
5
-
6
- // The kit keys both of its records by a pk's hex, and does not export the
7
- // spelling. Zero dependencies and no host API: this is the whole of it.
8
- const hex = (bytes) => [...bytes].map((byte) => byte.toString(16).padStart(2, '0')).join('');
9
-
10
- // One process, many beings, exactly one door. The kit's `Warden` is the door
11
- // and everything behind it, so a ground is a closure over one warden and
12
- // nothing else — Nervur adds no protocol of its own, ever.
13
- //
14
- // The instance is closed over rather than returned. Nervur is written
15
- // Crockford: factory functions, no `this` at a call site, frozen surfaces. A
16
- // warden handed to a caller is a warden whose methods must be called bound,
17
- // which puts the kit's class idiom in Nervur's hands. It stops here.
18
- //
19
- // Every seed and every draw of randomness arrives as an argument, the way the
20
- // kit takes them. A ground that reached for entropy would be a ground the
21
- // bench cannot replay.
22
- export async function ground({ nameSeed, padlockSeed, heirSeed, hints = [], limit = 0n }) {
23
- const warden = await Warden.open({ nameSeed, padlockSeed, heirSeed, hints, limit });
24
-
25
- // The half of an invitation the kit's record has nowhere to keep: the heir
26
- // this ground rotates to the first time it spends the relation. An
27
- // invitation hands over the heir keys and nothing else, so the holder's
28
- // first ask must arrive as a rotation carrying a commitment to a successor
29
- // the granting house has never seen. Meeting that once is a trap; meeting it
30
- // at every call site is the trap kept. So `remember` derives the successor
31
- // and `ask` spends it, and a caller never learns the difference.
32
- const rotations = new Map();
33
-
34
- // A relation is named by the being that spends it and the house it stands
35
- // at, never by a handle: a handle passed around is a relation one being can
36
- // spend on another's behalf, and the whole point of the outbound record is
37
- // that it cannot.
38
- const relationFor = (from, at) => {
39
- for (const row of warden.relationsOf(from)) if (same(row.warden, at)) return row;
40
- return null;
41
- };
42
-
43
- // Contacts: who reaches this being, and the two acts that change it. Quo
44
- // keeps the record and refuses to have an opinion about it — it has no word
45
- // for member, owner or guest and must not, because a protocol a stranger
46
- // implements in another language cannot carry meanings. The opinion is
47
- // Nervur's, and this is where it lives. Nothing is copied: `list` walks the
48
- // warden's own inbound record at the moment it is asked, `admit` is the
49
- // warden's `grant`, `remove` is the warden's `amend`.
50
- //
51
- // The warden holds the record the other way round — voice to beings — so the
52
- // inverse is derived here rather than kept, because a second index is a
53
- // second truth and the first one moves without telling it.
54
- //
55
- // The scoping is the design, and it is structural rather than a check that
56
- // refuses: `beingPk` is shut inside this closure at mint, so a being's
57
- // context has no address it could name to reach another's row. The same
58
- // shape `errand` uses for a relation.
59
- const contacts = (beingPk) => {
60
- const at = hex(beingPk);
61
- const reaches = (row) => row.beings.has(at);
62
- return Object.freeze({
63
- // The voices that reach this being, read live.
64
- list: () => [...warden.inbound.values()].filter(reaches).map((row) => row.voice),
65
-
66
- // Mint a voice against this being and hand back the invitation. A being
67
- // still cannot mint standing — it has no warden — and this does not give
68
- // it one: what it has is a reach the ground minted for it, at one being.
69
- admit: (keys) => warden.grant(beingPk, keys),
70
-
71
- // Take this being out of that voice's row, and nothing else. A voice that
72
- // does not reach this being is not amended at all — the warden would
73
- // answer true to a removal that removed nothing, and a being told its act
74
- // succeeded when it touched no record has been told a lie.
75
- remove: (voicePk) => {
76
- const row = warden.standing(voicePk);
77
- if (!row || !reaches(row)) return false;
78
- return warden.amend(voicePk, { remove: [beingPk] });
79
- },
80
- });
81
- };
82
-
83
- // The one act of spending a relation, written once. `ask` below is a caller
84
- // of it and so is every organ minted by `errand`, because they are the same
85
- // act: seal down a relation, move it, read what came back. A second copy of
86
- // this would be a second place the first-ask rotation could be forgotten.
87
- // `leash`, where there is one, is the call this ask is made in the course of.
88
- // It is handed to the kit rather than read here: what may go onward is the
89
- // difference between when the message arrived and this moment, and only the
90
- // kit can take the second reading at the moment of sealing. A being holding a
91
- // leash therefore cannot widen an allowance even by mistake, which is the
92
- // whole reason the remainder never passes through the being as a number.
93
- const spend = async ({ from, at, being, method, allowance, leash = null, random }) => {
94
- const row = relationFor(from, at);
95
- if (!row) throw new Error('no relation');
96
- const rotation = rotations.get(row) ?? null;
97
- // Spent whether or not an answer comes back: the far door rotates the
98
- // standing at step three, long before it decides whether to speak.
99
- rotations.delete(row);
100
- const envelope = await warden.ask(row, {
101
- seq: row.seq + 1n,
102
- commitment: rotation,
103
- allowance,
104
- leash,
105
- being,
106
- method,
107
- random,
108
- });
109
- // An ask that could not be judged is not made, and the kit says so by
110
- // handing back nothing to send.
111
- if (!envelope) return null;
112
- const answer = await reach(row.hints, envelope);
113
- if (answer === null) return null;
114
- return readAnswer({
115
- envelope: answer,
116
- padlockSecret: warden.padlock.secret,
117
- wardenPk: row.warden,
118
- });
119
- };
120
-
121
- return Object.freeze({
122
- // What a stranger holds: the door's name, its commitment, its padlock and
123
- // where to reach it. No voice, so it opens nothing.
124
- card: () => warden.card(),
125
-
126
- // The door's own being — the one every voice reaches, holders included.
127
- publicBeing: () => warden.publicBeing(),
128
-
129
- // Hold a being: what is held is a contract and its resolvers together,
130
- // never an object and a blueprint that happen to have been handed in at
131
- // the same moment. Those two can disagree, and the whole meaning of a
132
- // digest rests on their not disagreeing.
133
- //
134
- // The organs are supplied here, which is what "scoped by the ground"
135
- // means: the ground decides what is on the tray, and the being takes only
136
- // what it declared. A being cannot reach for one that was never offered,
137
- // and cannot be given one it never asked for.
138
- // The contacts context is minted here, per hold, over this being's own
139
- // name — derived from the seed the way the kit derives it, because the
140
- // being must be shut inside its own context and the hold that names it has
141
- // not happened yet.
142
- hold: async (one, { seed, heirSeed, cells, organs }) => {
143
- const pk = (await signingPair(seed)).pk;
144
- return warden.hold(one.raise(organs, contacts(pk)), {
145
- seed,
146
- heirSeed,
147
- cells,
148
- blueprint: one.contract,
149
- });
150
- },
151
-
152
- // Mint a voice and write the standing that lets it reach one being.
153
- grant: (being, keys) => warden.grant(being, keys),
154
-
155
- // The other direction: record an invitation this ground was handed, and say
156
- // which of its beings spends it. A relation nobody here owns belongs to the
157
- // ground itself and travels nowhere, which is a different thing and not
158
- // this; a being is named, always.
159
- //
160
- // What comes back reads the relation and cannot move it. `seq` is the count
161
- // kept against that far door, which is the one fact a caller has any
162
- // business watching.
163
- remember: async (invitation, { being, heirSeed }) => {
164
- const row = warden.remember(invitation, { being });
165
- const next = await signingPair(heirSeed);
166
- rotations.set(row, await commitment(invitation.warden, next.pk));
167
- return Object.freeze({
168
- warden: invitation.warden,
169
- being,
170
- hints: () => [...row.hints],
171
- seq: () => row.seq,
172
- });
173
- },
174
-
175
- // Spend a relation: seal the ask down it, post it at the hints the
176
- // invitation carried, and read the answer back. All four acts are the
177
- // kit's — `ask`, `carry` under it, `reach`, `readAnswer` — and what is
178
- // Nervur's is only which relation, which number, and the padlock secret
179
- // staying inside this closure.
180
- //
181
- // The number is this ground's to pick, not a caller's: a relation that
182
- // repeats one is a relation the far door refuses, and a caller counting by
183
- // hand is a caller that will eventually get it wrong.
184
- //
185
- // `random` is the **drawn bytes** for this one seal, as at `judge` and
186
- // unlike `listen`, because here the ground has one message and not a
187
- // stream of them.
188
- //
189
- // Silence and unreachable are told apart, and they must be: silence is
190
- // `null`, the far house having judged and said nothing, while a road that
191
- // does not answer throws out of the carriage. Only one of the two means the
192
- // far house said no. A being spending a relation it does not hold is
193
- // neither — it is a fault at home, and it throws before a byte moves.
194
- ask: ({ from, at, being = null, method = null, allowance, random }) =>
195
- spend({ from, at, being, method, allowance, random }),
196
-
197
- // The organ that crosses. Everything above is the ground talking to another
198
- // house; this is how a being does it, and a being may do it no other way.
199
- //
200
- // What is minted is a reach and nothing more. The being does not hold the
201
- // warden, does not hold the relation, and cannot name a house: `from` and
202
- // `at` are shut inside this closure at mint, so an organ is one road to one
203
- // house on behalf of one being. That is what keeps it unspendable twice —
204
- // there is nothing here to copy and point elsewhere.
205
- //
206
- // Which being at the far house is still the caller's to name, because the
207
- // standing the invitation carries is what decides that, and it is judged
208
- // over there. An organ that filtered it here would be this house guessing
209
- // at the far house's answer.
210
- //
211
- // The leash is handed in rather than chosen, and it is the one the door
212
- // gave the resolver. The being passes it on whole and never a number read
213
- // off it: what may go onward is this door's dwell subtracted at the moment
214
- // of sealing, which is later than any moment the being could have read. So
215
- // the remainder reaches the far house without the being having chosen it,
216
- // and a being cannot hand its organ a wider allowance than it received. The
217
- // ground adds no arithmetic of its own — a leash that has run out is
218
- // refused by the kit at the moment of carrying, and shows up here as
219
- // silence.
220
- //
221
- // `random` is a **function** the organ draws from per message, as at
222
- // `listen` and unlike `judge`: an organ has a stream of messages ahead of
223
- // it and not one, and a seal that reused its bytes would be a seal.
224
- errand: ({ from, at, random }) =>
225
- Object.freeze({
226
- ask: (leash, { being = null, method = null } = {}) =>
227
- spend({ from, at, being, method, leash, random: random() }),
228
- }),
229
-
230
- // The whole of the door: bytes in, bytes or silence out.
231
- //
232
- // `random` here is the **drawn bytes** for this one judgment. At `listen`
233
- // below it is a **function** the host draws from, once per message. The
234
- // two differ because the drawing belongs to whoever has messages to count,
235
- // and that is the host — so a ground handed a function here would meet
236
- // every ask with silence, the warden's global catch turning the fault into
237
- // the same refusal as everything else.
238
- judge: (envelope, terrain) => warden.judge(envelope, terrain),
239
-
240
- // Listen. This is the one place the warden is handed anywhere, and it goes
241
- // to the kit's own host adapter rather than out of this closure — the door
242
- // is the half of the carriage that cannot be portable, so a ground that
243
- // only calls out never reaches this and never imports a host's API.
244
- //
245
- // The hint that comes back is the whole address: Quo posts to it exactly
246
- // as given. `close` drops the listening socket and the keep-alive
247
- // connections with it, because a door that waits for the last idle caller
248
- // to time out is a door that never shuts.
249
- // A door holds callers to the limit its warden publishes, because a
250
- // published limit nobody enforces is worse than none — a caller can
251
- // compute it before sending and would be told the truth about a door that
252
- // then reads anything. A warden that publishes zero has stated no limit,
253
- // so nothing is enforced; the kit's door spells that `null`.
254
- //
255
- // A ground that declared no hints publishes the one it just got: an
256
- // ephemeral port is not knowable before the socket is open, so a ground
257
- // could otherwise only ever grant invitations with no road in them. A
258
- // ground that declared its own hints keeps them — it knows where it is
259
- // reachable from and a loopback address appended to that would be a road
260
- // no stranger can take.
261
- listen: async ({ clock, random, host, port, limit } = {}) => {
262
- const door = await serve(warden, {
263
- clock,
264
- random,
265
- host,
266
- port,
267
- limit: limit ?? (warden.limit > 0n ? warden.limit : null),
268
- });
269
- if (warden.hints.length === 0) warden.hints = [door.hint];
270
- return door;
271
- },
272
- });
273
- }
package/src/index.js DELETED
@@ -1,5 +0,0 @@
1
- export { ground } from './ground.js';
2
- export { being } from './being.js';
3
- export { contract } from './contract.js';
4
- export { program } from './program.js';
5
- export { project } from './projection.js';
package/src/program.js DELETED
@@ -1,74 +0,0 @@
1
- import { contract as read } from './contract.js';
2
-
3
- // A contract and its resolvers, made callable. This is the whole compiler, and
4
- // beings and organs both come out of it, because they are one kind of code: a
5
- // field of an organ is a resolver exactly as a field of a being is, and nothing
6
- // here can tell which it is holding. There is no second compiler anywhere, and
7
- // a design that wants one is wrong.
8
- //
9
- // A resolver is sovereign. It sees its arguments by the names its own contract
10
- // gave them, it reaches the world only through the context it was handed, and
11
- // it never throws — a resolver that raises is answered as silence, because
12
- // nothing in this system promises that an ask is fulfilled. A refusal a caller
13
- // must tell apart from silence is carried in the answer type instead.
14
-
15
- // Exactly the arguments the contract declares, and nothing else. What a caller
16
- // may ask is what the contract already showed it, so a parameter nobody
17
- // declared cannot ride in beside one that was.
18
- const given = (args, params) =>
19
- Object.freeze(
20
- Object.fromEntries(
21
- Object.keys(args)
22
- .filter(
23
- (name) =>
24
- params !== null &&
25
- typeof params === 'object' &&
26
- Object.hasOwn(params, name) &&
27
- params[name] !== undefined,
28
- )
29
- .map((name) => [name, params[name]]),
30
- ),
31
- );
32
-
33
- const complete = (args, only) =>
34
- Object.entries(args).every(([name, one]) => !one.required || only[name] !== undefined);
35
-
36
- export function program(one, resolvers) {
37
- const held = typeof one === 'string' ? read(one) : one;
38
-
39
- return Object.freeze({
40
- // What may be asked here, which is the only place to find out. A field this
41
- // does not name does not exist for a caller.
42
- contract: held,
43
-
44
- answer: async (field, params, context) => {
45
- // Read as an own property, never through the prototype chain. Without
46
- // this, `__proto__`, `constructor` and `toString` all name a field nobody
47
- // declared, and a caller reaches something the contract never showed it.
48
- const declared = Object.hasOwn(held.fields, field) ? held.fields[field] : undefined;
49
- if (declared === undefined) return undefined;
50
-
51
- const only = given(declared.args, params);
52
- // A field short of an argument its contract made mandatory is not called
53
- // at all. A resolver written against its own contract may read that
54
- // argument without checking, and calling it anyway would make every
55
- // resolver defensive about a promise the contract already made.
56
- if (!complete(declared.args, only)) return undefined;
57
-
58
- const resolver =
59
- resolvers !== null &&
60
- typeof resolvers === 'object' &&
61
- Object.hasOwn(resolvers, field) &&
62
- typeof resolvers[field] === 'function'
63
- ? resolvers[field]
64
- : undefined;
65
- if (resolver === undefined) return undefined;
66
-
67
- try {
68
- return await resolver(only, context);
69
- } catch {
70
- return undefined;
71
- }
72
- },
73
- });
74
- }
package/src/projection.js DELETED
@@ -1,102 +0,0 @@
1
- import { Refusal, print } from '@quo-systems/js';
2
-
3
- // The boundary, and there is exactly one of it. Inside a ground a contract is
4
- // GraphQL; on the wire there is Quo and only Quo. A stranger implementing the
5
- // protocol in another language has never heard of Nervur and must still read
6
- // the blueprint a being stands on, so a contract is projected into the kit's
7
- // own notation here and nowhere else.
8
- //
9
- // The projection never guesses. Where GraphQL says something the notation has
10
- // no word for, the contract is refused at build — a being that stood on a
11
- // blueprint that quietly dropped half its schema would have a digest that lied
12
- // to everyone who read it.
13
-
14
- const refuse = (code, detail) => {
15
- throw new Refusal(code, detail);
16
- };
17
-
18
- // The closed set of scalars is the law's, so this table is closed too. A name
19
- // missing from it is not a scalar Nervur may invent — it is a word Quo does
20
- // not have.
21
- const SCALARS = Object.freeze({
22
- Boolean: 'bool',
23
- Int: 'int',
24
- String: 'text',
25
- Bytes: 'bytes',
26
- B32: 'b32',
27
- Being: 'being',
28
- Invitation: 'invitation',
29
- Card: 'card',
30
- });
31
-
32
- // The one word the notation has and GraphQL does not: a field that answers
33
- // nothing, which rides as zero bytes and is a real answer. GraphQL insists
34
- // every field has a type, so a contract spells it with this name in answer
35
- // position. It is not a type and may not be asked for or carried.
36
- const NOTHING = 'Nothing';
37
-
38
- // GraphQL is nullable by default and says so by omission; the notation is
39
- // required by default and says the other thing with `?`. This is the whole of
40
- // the difference and it is spelled out rather than inferred.
41
- function projectType(node, where, records) {
42
- if (node.list) {
43
- return node.required
44
- ? { list: projectType(node.element, where, records) }
45
- : { optional: { list: projectType(node.element, where, records) } };
46
- }
47
- const base = SCALARS[node.of] ?? (records.has(node.of) ? node.of : undefined);
48
- if (base === undefined) {
49
- refuse(
50
- 'NO_WORD',
51
- node.of === NOTHING
52
- ? `${NOTHING} at ${where}, which is not a type`
53
- : `${node.of} at ${where}`,
54
- );
55
- }
56
- return node.required ? { base } : { optional: { base } };
57
- }
58
-
59
- const projectArgs = (args, where, records) =>
60
- Object.entries(args).map(([name, one]) => ({
61
- name,
62
- type: projectType(one, `${where}(${name})`, records),
63
- }));
64
-
65
- // A record block holds plain fields: a name and a type, and nothing else. A
66
- // GraphQL object type whose fields take arguments is a type with behaviour,
67
- // which the notation has no word for at all.
68
- const projectRecord = (name, fields, records) =>
69
- Object.freeze({
70
- name,
71
- fields: Object.entries(fields).map(([field, one]) => {
72
- if (Object.keys(one.args).length > 0) refuse('NO_WORD', `arguments on ${name}.${field}`);
73
- if (one.answers.of === NOTHING) refuse('NO_WORD', `${NOTHING} at ${name}.${field}`);
74
- return { name: field, type: projectType(one.answers, `${name}.${field}`, records) };
75
- }),
76
- });
77
-
78
- // The blueprint a being actually stands on: one class block named for the
79
- // contract's root type, its declared fields flat, and every other type of the
80
- // schema behind it as a record. Ordering, canonical spelling, unused records
81
- // and recursion are the notation's own law, so they are the kit's `print` to
82
- // judge and never this file's.
83
- export function project(one) {
84
- const records = new Set(Object.keys(one.records));
85
- const blueprint = {
86
- name: one.name,
87
- fields: Object.entries(one.fields).map(([name, field]) => ({
88
- name,
89
- args: projectArgs(field.args, name, records),
90
- answer:
91
- field.answers.of === NOTHING && !field.answers.list
92
- ? null
93
- : projectType(field.answers, name, records),
94
- })),
95
- records: [...Object.entries(one.records)].map(([name, fields]) =>
96
- projectRecord(name, fields, records),
97
- ),
98
- };
99
- return print(blueprint);
100
- }
101
-
102
- export { NOTHING, SCALARS };