@suss/resolution 0.1.0 → 0.2.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/README.md CHANGED
@@ -19,7 +19,14 @@ factory in TypeScript.
19
19
 
20
20
  ```
21
21
  func(f) f is a function
22
+ objectValue(o) o is an object written out literally
23
+ writtenValue(x) x is an expression written out in source
24
+ rather than a name for one
25
+ holdsProperty(o, n, x) object o holds x under the name n
26
+ readsProperty(x, o, n) x is the expression o.n
22
27
  binds(x, y) the name x is declared as y
28
+ endsHolding(x, y) the name x is written more than once and
29
+ holds y once the writes have run
23
30
  paramOf(f, k, p) p is f's parameter at position k
24
31
  returnsValue(f, v) f returns v
25
32
  bodyCalls(f, c) f's body calls c
@@ -40,6 +47,52 @@ match.
40
47
 
41
48
  Node identity is the adapter's business. The rules only join on it.
42
49
 
50
+ ## What comes out
51
+
52
+ ```
53
+ comesTo(x, z) following x arrives at the value z
54
+ resolves(x, z) comesTo narrowed to functions
55
+ isWrittenAs(x, z) x is written as the expression z
56
+ comesFrom(x, m, n) following x arrives at m's export n
57
+ callsInto(f, m, n) calling f ends up calling m's n
58
+ ```
59
+
60
+ `resolves` is the question most callers ask. `comesTo` is the one
61
+ underneath it, and it can answer with an object, since a chain has to
62
+ pass through objects for `routes.list` to reach what `list` holds.
63
+
64
+ `isWrittenAs` follows the same names to the expression a value is
65
+ written as, whatever kind of expression that turns out to be. A GraphQL
66
+ document is neither a function nor an object, so `comesTo` never
67
+ reaches one.
68
+
69
+ `comesFrom` answers the direction `comesTo` cannot. Every `comesTo`
70
+ chain ends at something written out in the source being read, so a name
71
+ for a library's own function ends nowhere: the library's body is not
72
+ here. `comesFrom` follows the same aliases, imports and barrels and
73
+ answers with the module and the name that module exports.
74
+
75
+ `callsInto` puts that together with the calls a function makes. A
76
+ project writes its own decorator that calls `Resolver()` and applies
77
+ that one to its classes, and the class is a resolver even though nothing
78
+ about it says `Resolver`. Several answers for one function is the normal
79
+ case rather than an ambiguity, since a wrapper composing two library
80
+ decorators applies both, so a caller asks whether the one it cares about
81
+ is among them.
82
+
83
+ One relation comes out for the rules' own use rather than for callers:
84
+
85
+ ```
86
+ objectOf(x, obj) x stands for the object literal obj
87
+ ```
88
+
89
+ An object arrives two ways, through a name or as what a factory call
90
+ returns, and `objectOf` names that step so the rule for `routes.list`
91
+ and the rule for `make(body).handle` are the same rule. A factory call
92
+ gets an `objectOf` answer without getting a `comesTo` answer, since a
93
+ factory call usually is the wrapper and answering with the function it
94
+ returns would fight the unwrapping answer.
95
+
43
96
  ## Why rules and not a walker
44
97
 
45
98
  Each rule describes one hop. The chains people write are longer than
@@ -49,19 +102,20 @@ barrel re-exporting a wrapper. The engine composes what it has, so those
49
102
  work without being named.
50
103
 
51
104
  The corollary is where to look when something comes back empty. Suspect
52
- the facts before the rules. A production service once declared 91
53
- handlers and resolved 11; the fix was one condition in fact extraction,
54
- and the rules found the other 79 unchanged.
105
+ the facts before the rules. Against one production service suss
106
+ resolved 11 handlers and missed most of what the template declared. The
107
+ fix was one condition in fact extraction, and the existing rules found
108
+ the rest unchanged.
55
109
 
56
110
  ## What is not modelled
57
111
 
58
112
  **A handler passed as one property of a config.** `make({ body:
59
113
  handler })`, where the factory reads `opts.body`, does not resolve. A
60
- rule for it was tried and taken out: production wrappers read several
61
- callbacks off the same config (a log extractor, an error builder, the
62
- handler), the rule made each one a candidate for the whole call, and
63
- ambiguity nulled out sixty handlers that used to resolve. Saying which
64
- property is the handler takes something structure does not carry,
114
+ rule for it was tried and taken out: a wrapper often reads several
115
+ callbacks off the same config object, the rule made each one a
116
+ candidate for the whole call, and the resulting ambiguity nulled out
117
+ handlers that used to resolve. Saying which property is the handler
118
+ takes something structure does not carry,
65
119
  either "the only property called" (which needs negation) or a pack
66
120
  naming the property. Until one of those exists, this shape stays
67
121
  unresolved on purpose.
@@ -69,8 +123,27 @@ unresolved on purpose.
69
123
  **An element of an array.** `all[0]` has no fact for what an array
70
124
  holds.
71
125
 
72
- **Reassignment.** A name assigned twice resolves to the first
73
- assignment rather than to both or to neither.
126
+ **Which write a read sees, once control flow decides it.** A name
127
+ written twice in a module's own statement list is answered: those
128
+ statements run once each, top to bottom, so the last write is what
129
+ anything importing the name gets, and the adapter says so with
130
+ `endsHolding`. A write inside a branch, a loop, or a function body is a
131
+ different claim, and the adapter stays quiet. The name then comes to
132
+ nothing, which is the answer until there are control-flow facts to
133
+ reason over.
134
+
135
+ Answering it in general is reaching definitions, per use rather than
136
+ per name. That needs facts saying which statement follows which and
137
+ which branch each sits on, and no adapter emits any today.
138
+
139
+ The adapter picking the write rather than a rule picking it is a cost
140
+ decision. Ordering writes inside the rules means asking which writes
141
+ have no later write, negation says that in one line, and this
142
+ evaluator has to throw its last fixpoint away and start over whenever a
143
+ rule set uses negation. The store evaluates after every wave of facts,
144
+ so one negated rule turned a 66 second run on the Saleor dashboard into
145
+ one that had not finished in ten minutes. Source order is something
146
+ every adapter already knows.
74
147
 
75
148
  **Ambiguity is the caller's problem.** When the rules reach two
76
149
  different functions the store returns nothing, since picking one would
package/dist/index.cjs CHANGED
@@ -20,6 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ ANSWER_RELATIONS: () => ANSWER_RELATIONS,
24
+ RESOLUTION_QUESTIONS: () => RESOLUTION_QUESTIONS,
23
25
  RESOLUTION_RULES: () => RESOLUTION_RULES
24
26
  });
25
27
  module.exports = __toCommonJS(index_exports);
@@ -35,6 +37,16 @@ var RESOLUTION_RULES = [
35
37
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
36
38
  [(0, import_datalog.lit)("binds", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("y")), (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("y"), (0, import_datalog.variable)("z"))]
37
39
  ),
40
+ // A name written more than once holds the value the last write left
41
+ // there, and `binds` says nothing about it. The adapter works out
42
+ // which write that is, in its own language's terms, and stays quiet
43
+ // when control flow decides. A name it stays quiet about comes to no
44
+ // value, which is the answer.
45
+ (0, import_datalog.rule)(
46
+ "comesTo",
47
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
48
+ [(0, import_datalog.lit)("endsHolding", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("y")), (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("y"), (0, import_datalog.variable)("z"))]
49
+ ),
38
50
  // An import comes to what the module exports under that name.
39
51
  (0, import_datalog.rule)(
40
52
  "comesTo",
@@ -67,15 +79,37 @@ var RESOLUTION_RULES = [
67
79
  (0, import_datalog.lit)("moduleExport", (0, import_datalog.variable)("m2"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("value"))
68
80
  ]
69
81
  ),
70
- // Reading a property comes to what the object holds under that name.
71
- // `comesTo` in the middle is what lets the object arrive through an
72
- // alias, an import, or a factory call.
82
+ // The object an expression stands for. A name arrives at one by
83
+ // following `comesTo` through aliases and imports. A factory call
84
+ // arrives at one through what the function it calls returns; the
85
+ // call itself is given no `comesTo`, since a factory call usually IS
86
+ // the wrapper and answering with the raw returned function would
87
+ // fight the unwraps answer.
88
+ (0, import_datalog.rule)(
89
+ "objectOf",
90
+ [(0, import_datalog.variable)("o"), (0, import_datalog.variable)("obj")],
91
+ [(0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("o"), (0, import_datalog.variable)("obj")), (0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("obj"))]
92
+ ),
93
+ (0, import_datalog.rule)(
94
+ "objectOf",
95
+ [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("obj")],
96
+ [
97
+ (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c")),
98
+ (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("f")),
99
+ (0, import_datalog.lit)("returnsValue", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("ret")),
100
+ (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("ret"), (0, import_datalog.variable)("obj")),
101
+ (0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("obj"))
102
+ ]
103
+ ),
104
+ // Reading a property comes to what the object holds under that name,
105
+ // whichever way the object arrived: `routes.list` off a name, or
106
+ // `make(body).handle` off a call.
73
107
  (0, import_datalog.rule)(
74
108
  "comesTo",
75
109
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
76
110
  [
77
111
  (0, import_datalog.lit)("readsProperty", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("o"), (0, import_datalog.variable)("n")),
78
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("o"), (0, import_datalog.variable)("obj")),
112
+ (0, import_datalog.lit)("objectOf", (0, import_datalog.variable)("o"), (0, import_datalog.variable)("obj")),
79
113
  (0, import_datalog.lit)("holdsProperty", (0, import_datalog.variable)("obj"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("held")),
80
114
  (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("held"), (0, import_datalog.variable)("z"))
81
115
  ]
@@ -153,23 +187,6 @@ var RESOLUTION_RULES = [
153
187
  (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("a"), (0, import_datalog.variable)("h"))
154
188
  ]
155
189
  ),
156
- // A property read off a call: `make(body).handle`. The call itself is
157
- // not given a `comesTo`, since a factory call usually IS the wrapper
158
- // and answering with the raw returned function would fight the
159
- // unwraps answer. Only the property read narrows enough to be safe.
160
- (0, import_datalog.rule)(
161
- "comesTo",
162
- [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
163
- [
164
- (0, import_datalog.lit)("readsProperty", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("r"), (0, import_datalog.variable)("n")),
165
- (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c")),
166
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("f")),
167
- (0, import_datalog.lit)("returnsValue", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("ret")),
168
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("ret"), (0, import_datalog.variable)("obj")),
169
- (0, import_datalog.lit)("holdsProperty", (0, import_datalog.variable)("obj"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("held")),
170
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("held"), (0, import_datalog.variable)("z"))
171
- ]
172
- ),
173
190
  // Wrapper transparency, declared: a pack says this callee wraps
174
191
  // argument k, whatever its implementation looks like. The callee has
175
192
  // to have been imported from the library the pack named, so a local
@@ -186,6 +203,57 @@ var RESOLUTION_RULES = [
186
203
  (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("a"), (0, import_datalog.variable)("h"))
187
204
  ]
188
205
  ),
206
+ // Where a name comes from, when what it names lives outside the
207
+ // source being read. `comesTo` ends at something written out, so it
208
+ // never reaches a library's own function, and a project's alias of a
209
+ // library name has no answer at all. This follows the same aliases
210
+ // and barrels in the other direction and answers with the module and
211
+ // the name that module exports.
212
+ (0, import_datalog.rule)(
213
+ "comesFrom",
214
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")],
215
+ [(0, import_datalog.lit)("imports", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"))]
216
+ ),
217
+ (0, import_datalog.rule)(
218
+ "comesFrom",
219
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")],
220
+ [(0, import_datalog.lit)("binds", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("y")), (0, import_datalog.lit)("comesFrom", (0, import_datalog.variable)("y"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"))]
221
+ ),
222
+ (0, import_datalog.rule)(
223
+ "comesFrom",
224
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")],
225
+ [
226
+ (0, import_datalog.lit)("imports", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("m2"), (0, import_datalog.variable)("n2")),
227
+ (0, import_datalog.lit)("moduleExport", (0, import_datalog.variable)("m2"), (0, import_datalog.variable)("n2"), (0, import_datalog.variable)("value")),
228
+ (0, import_datalog.lit)("comesFrom", (0, import_datalog.variable)("value"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"))
229
+ ]
230
+ ),
231
+ // Calling f ends up calling the name n that module m exports. One hop
232
+ // is f's own body, including the closures it declares; the other hop
233
+ // is a function f's body calls, so a wrapper of a wrapper answers
234
+ // with what the innermost one reaches.
235
+ //
236
+ // Several answers for one function is the normal case, not an
237
+ // ambiguity: a project decorator that composes two library decorators
238
+ // applies both. A caller asks whether a particular (m, n) is among
239
+ // them.
240
+ (0, import_datalog.rule)(
241
+ "callsInto",
242
+ [(0, import_datalog.variable)("f"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")],
243
+ [
244
+ (0, import_datalog.lit)("bodyCallsDeep", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("c")),
245
+ (0, import_datalog.lit)("comesFrom", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"))
246
+ ]
247
+ ),
248
+ (0, import_datalog.rule)(
249
+ "callsInto",
250
+ [(0, import_datalog.variable)("f"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")],
251
+ [
252
+ (0, import_datalog.lit)("bodyCallsDeep", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("c")),
253
+ (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("g")),
254
+ (0, import_datalog.lit)("callsInto", (0, import_datalog.variable)("g"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"))
255
+ ]
256
+ ),
189
257
  // The question callers ask: what a value comes to, narrowed to the
190
258
  // functions. Objects appear in the middle of chains and never in an
191
259
  // answer.
@@ -193,9 +261,80 @@ var RESOLUTION_RULES = [
193
261
  "resolves",
194
262
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
195
263
  [(0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")), (0, import_datalog.lit)("func", (0, import_datalog.variable)("z"))]
264
+ ),
265
+ // Following a name to the expression it is written as. A separate
266
+ // relation rather than a widening of `comesTo`, so a caller asking
267
+ // the older question never sees a new answer: a factory call already
268
+ // has an unwrapping answer, and letting it also answer with itself
269
+ // would make that pair ambiguous.
270
+ //
271
+ // The two seeds are the two ways an expression can be written out. An
272
+ // object literal is one of them, and it is the shape a generated
273
+ // GraphQL document takes.
274
+ (0, import_datalog.rule)("isWrittenAs", [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("x")], [(0, import_datalog.lit)("writtenValue", (0, import_datalog.variable)("x"))]),
275
+ (0, import_datalog.rule)("isWrittenAs", [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("x")], [(0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("x"))]),
276
+ (0, import_datalog.rule)(
277
+ "isWrittenAs",
278
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
279
+ [(0, import_datalog.lit)("binds", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("y")), (0, import_datalog.lit)("isWrittenAs", (0, import_datalog.variable)("y"), (0, import_datalog.variable)("z"))]
280
+ ),
281
+ (0, import_datalog.rule)(
282
+ "isWrittenAs",
283
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
284
+ [(0, import_datalog.lit)("endsHolding", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("y")), (0, import_datalog.lit)("isWrittenAs", (0, import_datalog.variable)("y"), (0, import_datalog.variable)("z"))]
285
+ ),
286
+ (0, import_datalog.rule)(
287
+ "isWrittenAs",
288
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
289
+ [
290
+ (0, import_datalog.lit)("imports", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")),
291
+ (0, import_datalog.lit)("moduleExport", (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("value")),
292
+ (0, import_datalog.lit)("isWrittenAs", (0, import_datalog.variable)("value"), (0, import_datalog.variable)("z"))
293
+ ]
196
294
  )
197
295
  ];
296
+ var RESOLUTION_QUESTIONS = [
297
+ (0, import_datalog.rule)(
298
+ "wantedResolves",
299
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
300
+ [(0, import_datalog.lit)("wanted", (0, import_datalog.variable)("x")), (0, import_datalog.lit)("resolves", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("z"))]
301
+ ),
302
+ (0, import_datalog.rule)(
303
+ "wantedComesTo",
304
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
305
+ [(0, import_datalog.lit)("wanted", (0, import_datalog.variable)("x")), (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("z"))]
306
+ ),
307
+ (0, import_datalog.rule)(
308
+ "wantedComesTo",
309
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
310
+ [(0, import_datalog.lit)("wantedOrigin", (0, import_datalog.variable)("x")), (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("z"))]
311
+ ),
312
+ (0, import_datalog.rule)(
313
+ "wantedIsWrittenAs",
314
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
315
+ [(0, import_datalog.lit)("wanted", (0, import_datalog.variable)("x")), (0, import_datalog.lit)("isWrittenAs", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("z"))]
316
+ ),
317
+ (0, import_datalog.rule)(
318
+ "wantedComesFrom",
319
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")],
320
+ [(0, import_datalog.lit)("wantedOrigin", (0, import_datalog.variable)("x")), (0, import_datalog.lit)("comesFrom", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"))]
321
+ ),
322
+ (0, import_datalog.rule)(
323
+ "wantedCallsInto",
324
+ [(0, import_datalog.variable)("g"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")],
325
+ [
326
+ (0, import_datalog.lit)("wantedOrigin", (0, import_datalog.variable)("x")),
327
+ (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("g")),
328
+ (0, import_datalog.lit)("callsInto", (0, import_datalog.variable)("g"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"))
329
+ ]
330
+ )
331
+ ];
332
+ var ANSWER_RELATIONS = [
333
+ ...new Set(RESOLUTION_QUESTIONS.map((r) => r.head.relation))
334
+ ];
198
335
  // Annotate the CommonJS export names for ESM import in node:
199
336
  0 && (module.exports = {
337
+ ANSWER_RELATIONS,
338
+ RESOLUTION_QUESTIONS,
200
339
  RESOLUTION_RULES
201
340
  });
package/dist/index.d.cts CHANGED
@@ -5,5 +5,25 @@ import * as _suss_datalog from '@suss/datalog';
5
5
  * rules onto these before evaluating.
6
6
  */
7
7
  declare const RESOLUTION_RULES: _suss_datalog.Rule[];
8
+ /**
9
+ * The questions a caller asks, written as rules. Two facts say somebody
10
+ * is asking: `wanted(x)` for what a value is, and `wantedOrigin(x)` for
11
+ * where a name came from. Each answer relation holds the pairs for the
12
+ * values somebody asked about, keyed by the value asked about, which is
13
+ * the key the caller looks up by anyway.
14
+ *
15
+ * Written down rather than left to each caller because the engine reads
16
+ * them. `deriveOnDemand` follows a chain only as far as one of these
17
+ * questions reaches it, and a caller that answered its own questions by
18
+ * scanning a relation would give the engine nothing to work from.
19
+ *
20
+ * The two asking facts are kept apart because they pull on different
21
+ * rules. Following a name back to its library goes through every call
22
+ * the value's function makes, so a caller that only wants to know what a
23
+ * handler resolves to should not pay for that.
24
+ */
25
+ declare const RESOLUTION_QUESTIONS: _suss_datalog.Rule[];
26
+ /** The relations `RESOLUTION_QUESTIONS` answers into. */
27
+ declare const ANSWER_RELATIONS: string[];
8
28
 
9
- export { RESOLUTION_RULES };
29
+ export { ANSWER_RELATIONS, RESOLUTION_QUESTIONS, RESOLUTION_RULES };
package/dist/index.d.ts CHANGED
@@ -5,5 +5,25 @@ import * as _suss_datalog from '@suss/datalog';
5
5
  * rules onto these before evaluating.
6
6
  */
7
7
  declare const RESOLUTION_RULES: _suss_datalog.Rule[];
8
+ /**
9
+ * The questions a caller asks, written as rules. Two facts say somebody
10
+ * is asking: `wanted(x)` for what a value is, and `wantedOrigin(x)` for
11
+ * where a name came from. Each answer relation holds the pairs for the
12
+ * values somebody asked about, keyed by the value asked about, which is
13
+ * the key the caller looks up by anyway.
14
+ *
15
+ * Written down rather than left to each caller because the engine reads
16
+ * them. `deriveOnDemand` follows a chain only as far as one of these
17
+ * questions reaches it, and a caller that answered its own questions by
18
+ * scanning a relation would give the engine nothing to work from.
19
+ *
20
+ * The two asking facts are kept apart because they pull on different
21
+ * rules. Following a name back to its library goes through every call
22
+ * the value's function makes, so a caller that only wants to know what a
23
+ * handler resolves to should not pay for that.
24
+ */
25
+ declare const RESOLUTION_QUESTIONS: _suss_datalog.Rule[];
26
+ /** The relations `RESOLUTION_QUESTIONS` answers into. */
27
+ declare const ANSWER_RELATIONS: string[];
8
28
 
9
- export { RESOLUTION_RULES };
29
+ export { ANSWER_RELATIONS, RESOLUTION_QUESTIONS, RESOLUTION_RULES };
package/dist/index.js CHANGED
@@ -11,6 +11,16 @@ var RESOLUTION_RULES = [
11
11
  [v("x"), v("z")],
12
12
  [lit("binds", v("x"), v("y")), lit("comesTo", v("y"), v("z"))]
13
13
  ),
14
+ // A name written more than once holds the value the last write left
15
+ // there, and `binds` says nothing about it. The adapter works out
16
+ // which write that is, in its own language's terms, and stays quiet
17
+ // when control flow decides. A name it stays quiet about comes to no
18
+ // value, which is the answer.
19
+ rule(
20
+ "comesTo",
21
+ [v("x"), v("z")],
22
+ [lit("endsHolding", v("x"), v("y")), lit("comesTo", v("y"), v("z"))]
23
+ ),
14
24
  // An import comes to what the module exports under that name.
15
25
  rule(
16
26
  "comesTo",
@@ -43,15 +53,37 @@ var RESOLUTION_RULES = [
43
53
  lit("moduleExport", v("m2"), v("n"), v("value"))
44
54
  ]
45
55
  ),
46
- // Reading a property comes to what the object holds under that name.
47
- // `comesTo` in the middle is what lets the object arrive through an
48
- // alias, an import, or a factory call.
56
+ // The object an expression stands for. A name arrives at one by
57
+ // following `comesTo` through aliases and imports. A factory call
58
+ // arrives at one through what the function it calls returns; the
59
+ // call itself is given no `comesTo`, since a factory call usually IS
60
+ // the wrapper and answering with the raw returned function would
61
+ // fight the unwraps answer.
62
+ rule(
63
+ "objectOf",
64
+ [v("o"), v("obj")],
65
+ [lit("comesTo", v("o"), v("obj")), lit("objectValue", v("obj"))]
66
+ ),
67
+ rule(
68
+ "objectOf",
69
+ [v("r"), v("obj")],
70
+ [
71
+ lit("call", v("r"), v("c")),
72
+ lit("comesTo", v("c"), v("f")),
73
+ lit("returnsValue", v("f"), v("ret")),
74
+ lit("comesTo", v("ret"), v("obj")),
75
+ lit("objectValue", v("obj"))
76
+ ]
77
+ ),
78
+ // Reading a property comes to what the object holds under that name,
79
+ // whichever way the object arrived: `routes.list` off a name, or
80
+ // `make(body).handle` off a call.
49
81
  rule(
50
82
  "comesTo",
51
83
  [v("x"), v("z")],
52
84
  [
53
85
  lit("readsProperty", v("x"), v("o"), v("n")),
54
- lit("comesTo", v("o"), v("obj")),
86
+ lit("objectOf", v("o"), v("obj")),
55
87
  lit("holdsProperty", v("obj"), v("n"), v("held")),
56
88
  lit("comesTo", v("held"), v("z"))
57
89
  ]
@@ -129,23 +161,6 @@ var RESOLUTION_RULES = [
129
161
  lit("comesTo", v("a"), v("h"))
130
162
  ]
131
163
  ),
132
- // A property read off a call: `make(body).handle`. The call itself is
133
- // not given a `comesTo`, since a factory call usually IS the wrapper
134
- // and answering with the raw returned function would fight the
135
- // unwraps answer. Only the property read narrows enough to be safe.
136
- rule(
137
- "comesTo",
138
- [v("x"), v("z")],
139
- [
140
- lit("readsProperty", v("x"), v("r"), v("n")),
141
- lit("call", v("r"), v("c")),
142
- lit("comesTo", v("c"), v("f")),
143
- lit("returnsValue", v("f"), v("ret")),
144
- lit("comesTo", v("ret"), v("obj")),
145
- lit("holdsProperty", v("obj"), v("n"), v("held")),
146
- lit("comesTo", v("held"), v("z"))
147
- ]
148
- ),
149
164
  // Wrapper transparency, declared: a pack says this callee wraps
150
165
  // argument k, whatever its implementation looks like. The callee has
151
166
  // to have been imported from the library the pack named, so a local
@@ -162,6 +177,57 @@ var RESOLUTION_RULES = [
162
177
  lit("comesTo", v("a"), v("h"))
163
178
  ]
164
179
  ),
180
+ // Where a name comes from, when what it names lives outside the
181
+ // source being read. `comesTo` ends at something written out, so it
182
+ // never reaches a library's own function, and a project's alias of a
183
+ // library name has no answer at all. This follows the same aliases
184
+ // and barrels in the other direction and answers with the module and
185
+ // the name that module exports.
186
+ rule(
187
+ "comesFrom",
188
+ [v("x"), v("m"), v("n")],
189
+ [lit("imports", v("x"), v("m"), v("n"))]
190
+ ),
191
+ rule(
192
+ "comesFrom",
193
+ [v("x"), v("m"), v("n")],
194
+ [lit("binds", v("x"), v("y")), lit("comesFrom", v("y"), v("m"), v("n"))]
195
+ ),
196
+ rule(
197
+ "comesFrom",
198
+ [v("x"), v("m"), v("n")],
199
+ [
200
+ lit("imports", v("x"), v("m2"), v("n2")),
201
+ lit("moduleExport", v("m2"), v("n2"), v("value")),
202
+ lit("comesFrom", v("value"), v("m"), v("n"))
203
+ ]
204
+ ),
205
+ // Calling f ends up calling the name n that module m exports. One hop
206
+ // is f's own body, including the closures it declares; the other hop
207
+ // is a function f's body calls, so a wrapper of a wrapper answers
208
+ // with what the innermost one reaches.
209
+ //
210
+ // Several answers for one function is the normal case, not an
211
+ // ambiguity: a project decorator that composes two library decorators
212
+ // applies both. A caller asks whether a particular (m, n) is among
213
+ // them.
214
+ rule(
215
+ "callsInto",
216
+ [v("f"), v("m"), v("n")],
217
+ [
218
+ lit("bodyCallsDeep", v("f"), v("c")),
219
+ lit("comesFrom", v("c"), v("m"), v("n"))
220
+ ]
221
+ ),
222
+ rule(
223
+ "callsInto",
224
+ [v("f"), v("m"), v("n")],
225
+ [
226
+ lit("bodyCallsDeep", v("f"), v("c")),
227
+ lit("comesTo", v("c"), v("g")),
228
+ lit("callsInto", v("g"), v("m"), v("n"))
229
+ ]
230
+ ),
165
231
  // The question callers ask: what a value comes to, narrowed to the
166
232
  // functions. Objects appear in the middle of chains and never in an
167
233
  // answer.
@@ -169,8 +235,79 @@ var RESOLUTION_RULES = [
169
235
  "resolves",
170
236
  [v("x"), v("z")],
171
237
  [lit("comesTo", v("x"), v("z")), lit("func", v("z"))]
238
+ ),
239
+ // Following a name to the expression it is written as. A separate
240
+ // relation rather than a widening of `comesTo`, so a caller asking
241
+ // the older question never sees a new answer: a factory call already
242
+ // has an unwrapping answer, and letting it also answer with itself
243
+ // would make that pair ambiguous.
244
+ //
245
+ // The two seeds are the two ways an expression can be written out. An
246
+ // object literal is one of them, and it is the shape a generated
247
+ // GraphQL document takes.
248
+ rule("isWrittenAs", [v("x"), v("x")], [lit("writtenValue", v("x"))]),
249
+ rule("isWrittenAs", [v("x"), v("x")], [lit("objectValue", v("x"))]),
250
+ rule(
251
+ "isWrittenAs",
252
+ [v("x"), v("z")],
253
+ [lit("binds", v("x"), v("y")), lit("isWrittenAs", v("y"), v("z"))]
254
+ ),
255
+ rule(
256
+ "isWrittenAs",
257
+ [v("x"), v("z")],
258
+ [lit("endsHolding", v("x"), v("y")), lit("isWrittenAs", v("y"), v("z"))]
259
+ ),
260
+ rule(
261
+ "isWrittenAs",
262
+ [v("x"), v("z")],
263
+ [
264
+ lit("imports", v("x"), v("m"), v("n")),
265
+ lit("moduleExport", v("m"), v("n"), v("value")),
266
+ lit("isWrittenAs", v("value"), v("z"))
267
+ ]
172
268
  )
173
269
  ];
270
+ var RESOLUTION_QUESTIONS = [
271
+ rule(
272
+ "wantedResolves",
273
+ [v("x"), v("z")],
274
+ [lit("wanted", v("x")), lit("resolves", v("x"), v("z"))]
275
+ ),
276
+ rule(
277
+ "wantedComesTo",
278
+ [v("x"), v("z")],
279
+ [lit("wanted", v("x")), lit("comesTo", v("x"), v("z"))]
280
+ ),
281
+ rule(
282
+ "wantedComesTo",
283
+ [v("x"), v("z")],
284
+ [lit("wantedOrigin", v("x")), lit("comesTo", v("x"), v("z"))]
285
+ ),
286
+ rule(
287
+ "wantedIsWrittenAs",
288
+ [v("x"), v("z")],
289
+ [lit("wanted", v("x")), lit("isWrittenAs", v("x"), v("z"))]
290
+ ),
291
+ rule(
292
+ "wantedComesFrom",
293
+ [v("x"), v("m"), v("n")],
294
+ [lit("wantedOrigin", v("x")), lit("comesFrom", v("x"), v("m"), v("n"))]
295
+ ),
296
+ rule(
297
+ "wantedCallsInto",
298
+ [v("g"), v("m"), v("n")],
299
+ [
300
+ lit("wantedOrigin", v("x")),
301
+ lit("comesTo", v("x"), v("g")),
302
+ lit("callsInto", v("g"), v("m"), v("n"))
303
+ ]
304
+ )
305
+ ];
306
+ var ANSWER_RELATIONS = [
307
+ ...new Set(RESOLUTION_QUESTIONS.map((r) => r.head.relation))
308
+ ];
174
309
  export {
310
+ ANSWER_RELATIONS,
311
+ RESOLUTION_QUESTIONS,
175
312
  RESOLUTION_RULES
176
313
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/resolution",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Language-neutral rules for following a value to the function it comes down to.",
6
6
  "keywords": [
@@ -53,6 +53,6 @@
53
53
  "access": "public"
54
54
  },
55
55
  "dependencies": {
56
- "@suss/datalog": "0.1.0"
56
+ "@suss/datalog": "0.2.0"
57
57
  }
58
58
  }