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 +2 -1
- package/NOTICE +6 -0
- package/README.md +23 -143
- package/index.js +17 -0
- package/nervur.js +5 -0
- package/package.json +29 -11
- package/src/being.js +0 -125
- package/src/contract.js +0 -161
- package/src/ground.js +0 -273
- package/src/index.js +0 -5
- package/src/program.js +0 -74
- package/src/projection.js +0 -102
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
package/README.md
CHANGED
|
@@ -1,152 +1,32 @@
|
|
|
1
1
|
# nervur
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
29
|
+
## License
|
|
149
30
|
|
|
150
|
-
|
|
151
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,24 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nervur",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Nervur
|
|
5
|
-
"
|
|
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
|
-
"
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=22.18"
|
|
18
|
+
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"nervur": "./nervur.js"
|
|
21
|
+
},
|
|
9
22
|
"exports": {
|
|
10
|
-
".": "./
|
|
23
|
+
".": "./index.js"
|
|
11
24
|
},
|
|
12
|
-
"
|
|
13
|
-
"
|
|
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
|
-
"@
|
|
17
|
-
"
|
|
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
|
-
"
|
|
36
|
+
"index.js",
|
|
37
|
+
"nervur.js",
|
|
38
|
+
"README.md",
|
|
21
39
|
"LICENSE",
|
|
22
|
-
"
|
|
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
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 };
|