@suss/recognize 0.12.0 → 0.13.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 +294 -2
- package/dist/ast.d.cts +1 -1
- package/dist/ast.d.ts +1 -1
- package/dist/chain-BaxjT_-k.d.cts +530 -0
- package/dist/chain-BaxjT_-k.d.ts +530 -0
- package/dist/index.cjs +385 -85
- package/dist/index.d.cts +141 -40
- package/dist/index.d.ts +141 -40
- package/dist/index.js +383 -85
- package/package.json +4 -3
- package/dist/chain-DTSij2Mn.d.cts +0 -197
- package/dist/chain-DTSij2Mn.d.ts +0 -197
package/README.md
CHANGED
|
@@ -45,8 +45,10 @@ where the match starts -> which method -> read the arguments -> yield
|
|
|
45
45
|
a sub-unit
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
Two endings are built, and both of them yield effects: one asks the
|
|
49
|
+
call what it reached, and one reads the statement the call was handed.
|
|
50
|
+
Discovery, terminals and sub-units are further members of `Ending` with
|
|
51
|
+
an entry in the compile table.
|
|
50
52
|
|
|
51
53
|
### Where a match starts
|
|
52
54
|
|
|
@@ -102,14 +104,304 @@ result on the recognizer context under `ops`.
|
|
|
102
104
|
|---|---|
|
|
103
105
|
| `method()` | which method the call reaches for, as the source spells it |
|
|
104
106
|
| `receiverIsFrom(origin)` | whether the receiver came from where the origin says |
|
|
107
|
+
| `isFrom(origin)` | whether the call itself came from where the origin says |
|
|
105
108
|
| `argumentCount()` | how many arguments the call passes |
|
|
106
109
|
| `nameAt(index, unsettled)` | the name that argument gives, with the hole policy applied |
|
|
107
110
|
| `calleeText()` | the callee, as the source writes it |
|
|
111
|
+
| `receiver()` | the call the receiver is, as ops of its own |
|
|
112
|
+
| `argument(index)` | the call that argument is, as ops of its own |
|
|
113
|
+
| `callee()` | the call the callee itself was written as |
|
|
114
|
+
| `propertyAt(index, property, unsettled)` | what a named property of that argument says |
|
|
115
|
+
| `valueAt(index)` | the value that argument states, as `ValueOps` |
|
|
116
|
+
|
|
117
|
+
`receiver()`, `argument(index)` and `callee()` read a call next to the
|
|
118
|
+
one in hand: each gives back another `CallOps`, so every question above
|
|
119
|
+
works one step along. `callee()` is the one for a class a factory made, where the
|
|
120
|
+
source writes `new User({ name })` and what `User` is comes from the
|
|
121
|
+
`model("User", schema)` call it was declared as.
|
|
122
|
+
|
|
123
|
+
The last two reach a value rather than a call. `propertyAt` pulls one
|
|
124
|
+
name out of a property bag, which is what a pack that wants a bucket or
|
|
125
|
+
a table wants. `valueAt` hands the value over whole, for a pack whose
|
|
126
|
+
rule has to walk it:
|
|
127
|
+
|
|
128
|
+
| op | what it gives back |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `text()` | the string the source wrote, or null for anything else |
|
|
131
|
+
| `flag()` | the yes or no the source wrote, taking a number the way it takes a boolean |
|
|
132
|
+
| `entries(unsettled)` | what an object states, as key and value per entry |
|
|
133
|
+
| `items()` | what a list states, item by item |
|
|
134
|
+
| `property(name)` | what one named property states, as a value of its own |
|
|
135
|
+
| `parts()` | the pieces of text the source wrote, with the holes left out |
|
|
136
|
+
| `holes()` | what the source interpolated between those pieces, each as the call it was written as |
|
|
137
|
+
|
|
138
|
+
A key the source computes, `{ [this.tableName]: [...] }`, is read the
|
|
139
|
+
way any other name is, so an entry's key comes back settled where the
|
|
140
|
+
source settles it.
|
|
108
141
|
|
|
109
142
|
A chain running on an adapter with no ops matches nothing rather than
|
|
110
143
|
throwing, since a pack loaded into the wrong adapter is a configuration
|
|
111
144
|
mistake and not a crash.
|
|
112
145
|
|
|
146
|
+
## Reaching a call next to this one
|
|
147
|
+
|
|
148
|
+
Two of the three storage shapes put what a pack needs somewhere other
|
|
149
|
+
than the call the adapter is standing on.
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
s3.send(new GetObjectCommand({ Bucket: "photos", Key: "a.jpg" }));
|
|
153
|
+
storage.bucket("photos").file("a.jpg").download();
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The first says `send` at every call site in the codebase and puts the
|
|
157
|
+
operation, the bucket and the key in the command. The second puts the
|
|
158
|
+
bucket two hops back up the receivers and the object one hop back.
|
|
159
|
+
Neither needs a question of its own: both are the questions above,
|
|
160
|
+
asked of a different call.
|
|
161
|
+
|
|
162
|
+
A pack says which call with `about`, and a pick says which call with
|
|
163
|
+
`of`. Both take steps, and a step is data:
|
|
164
|
+
|
|
165
|
+
| step | what it reaches |
|
|
166
|
+
|---|---|
|
|
167
|
+
| `{ to: "receiver" }` | the call the receiver is |
|
|
168
|
+
| `{ to: "receiver", method: "bucket" }` | the first call to `bucket` up the receivers |
|
|
169
|
+
| `{ to: "argument", at: 0 }` | the call the first argument is |
|
|
170
|
+
| `{ to: "argument", at: { from: 0 } }` | every argument that is a call, first one that matches wins |
|
|
171
|
+
| `{ to: "argument", at: { from: 0 }, origin }` | the same, keeping only the ones the origin pins down |
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
storageCalls({ system: "s3", client: constructedFrom("@aws-sdk/client-s3") })
|
|
175
|
+
.about({ to: "argument", at: { from: 0 } })
|
|
176
|
+
.methods({ GetObjectCommand: { kind: "read", selector: KEY } })
|
|
177
|
+
.container({ at: 0, property: ["Bucket"] });
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
With a subject, every other link is asked of the call the steps reach:
|
|
181
|
+
the operation is that call's own name, the origin check is about that
|
|
182
|
+
call, and a pick with no `of` reads that call's arguments. The effect
|
|
183
|
+
still records the call in hand as its callee, since that is where a
|
|
184
|
+
reader would go and look.
|
|
185
|
+
|
|
186
|
+
A step follows what the source wrote a name as, so `const side = await
|
|
187
|
+
deck.side("a")` reaches `deck.side("a")` rather than stopping at the
|
|
188
|
+
await.
|
|
189
|
+
|
|
190
|
+
A step that says which method is searched for rather than counted to,
|
|
191
|
+
because `bucket(b).file(p).download()` and `bucket(b).getFiles()` put
|
|
192
|
+
the bucket a different distance back. The search is bounded at eight
|
|
193
|
+
receivers: a receiver chain can come back round to itself through a
|
|
194
|
+
variable, and a pack that meant more than eight hops has written
|
|
195
|
+
something else by mistake.
|
|
196
|
+
|
|
197
|
+
A step to an argument can say where that argument had to have come
|
|
198
|
+
from, which is `isFrom` asked of each candidate in turn. `send(command)`
|
|
199
|
+
takes one argument and a presigner takes two, and the one that matters
|
|
200
|
+
is the command the SDK declares, so the step says so rather than
|
|
201
|
+
reading whatever it lands on. The modules it says go into the pack's
|
|
202
|
+
import gate the way a start link's do.
|
|
203
|
+
|
|
204
|
+
A pick reads the argument itself, or a property of the object the
|
|
205
|
+
argument states. The properties are tried in order, so `property:
|
|
206
|
+
["Key", "Prefix"]` is "the key, or the prefix a listing asked for
|
|
207
|
+
instead". A construction is unwrapped first, since that is where a
|
|
208
|
+
command puts its inputs.
|
|
209
|
+
|
|
210
|
+
## A call that states one request object
|
|
211
|
+
|
|
212
|
+
An AWS SDK command puts everything the call is doing inside one object,
|
|
213
|
+
and four of the things a storage effect records come out of it. Each is
|
|
214
|
+
a link of its own.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
storageCalls({ system: "aws.dynamodb" })
|
|
218
|
+
.about({ to: "argument", at: { from: 0 }, origin: constructedFrom(...MODULES) })
|
|
219
|
+
.methods(COMMANDS)
|
|
220
|
+
.input({ at: 0 })
|
|
221
|
+
.container({ at: 0, property: ["TableName"] })
|
|
222
|
+
.accessPath({ at: 0, property: ["IndexName"] })
|
|
223
|
+
.containersIn({ at: 0, property: ["RequestItems"] });
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`input` says where the call states its inputs. A call that states none
|
|
227
|
+
is not one of these calls, so the chain stops there, and a rule the pack
|
|
228
|
+
wrote over the inputs is handed the object rather than a position to go
|
|
229
|
+
looking in.
|
|
230
|
+
|
|
231
|
+
`accessPath` is the way in the call took, which pairs against a
|
|
232
|
+
declared index rather than against the container itself.
|
|
233
|
+
|
|
234
|
+
`containersIn` is for a call that reaches several containers at once. A
|
|
235
|
+
batch states them as a map, one entry per container, and the chain then
|
|
236
|
+
yields one effect per entry: the entry's key is what the container is
|
|
237
|
+
called, and the entry's value is what the call did there. A chain
|
|
238
|
+
without it yields the single effect its container link addresses.
|
|
239
|
+
|
|
240
|
+
A rule the pack writes for `selector` or `fields` is handed the input,
|
|
241
|
+
that entry when there is one, and whether the call reads or writes:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
fields: ({ input, entry, kind }) =>
|
|
245
|
+
(entry ?? input).property("ProjectionExpression")?.text()?.split(",") ?? [],
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
A method table with a rule inside it is still a table, so the link
|
|
249
|
+
stays counted as data and pack health prices the rule beside it.
|
|
250
|
+
|
|
251
|
+
## A rule that says which value it reads
|
|
252
|
+
|
|
253
|
+
`input` suits a library that puts everything one call is doing in one
|
|
254
|
+
object. Mongoose spreads it over two arguments instead:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
User.find({ email }, { name: 1, email: 1 });
|
|
258
|
+
// ^ selector ^ fields
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Both are rules, since a pick gives back names and neither of these
|
|
262
|
+
states one. So a rule can say where it reads, the way a pick already
|
|
263
|
+
says which call it reads with `of`:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
find: {
|
|
267
|
+
kind: "read",
|
|
268
|
+
selector: { of: { at: 0 }, by: filterKeys },
|
|
269
|
+
fields: { of: { at: 1 }, by: projected },
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`of` is the same `OneArgument` a pick takes, steps and all, so a rule
|
|
274
|
+
can read a value on another call of the chain rather than another
|
|
275
|
+
argument of this one.
|
|
276
|
+
|
|
277
|
+
A rule pointed at an argument the call left out still runs, and what it
|
|
278
|
+
is handed states nothing. Only the pack knows what leaving it out
|
|
279
|
+
means: a Mongoose read with no projection reads every field there is,
|
|
280
|
+
where a call with no request object is not one of these calls at all.
|
|
281
|
+
That guard stays with `input`, which is where it belongs.
|
|
282
|
+
|
|
283
|
+
A method whose own name settles the answer states it as a plain list:
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
findById: { kind: "read", selector: ["_id"], fields: projection(1) },
|
|
287
|
+
deleteOne: { kind: "write", selector: filter(0), fields: ["*"] },
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## An operation the call says rather than the name it goes to
|
|
291
|
+
|
|
292
|
+
A project that wraps a whole API behind one helper puts the operation in
|
|
293
|
+
an argument. `operation` says which one, and a `kind` written with no
|
|
294
|
+
`otherwise` says that an answer the table does not list is not one of
|
|
295
|
+
these calls at all:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
{
|
|
299
|
+
operation: { at: 2 },
|
|
300
|
+
kind: { asks: { at: 2 }, means: { Query: "read", PutItem: "write" } },
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
A pack matching a helper the project rather than a library wrote leaves
|
|
305
|
+
`client` out of `storageCalls`, since the name that project gave is the
|
|
306
|
+
whole of what it has, and passes `requiresImport` to `pack()` for the
|
|
307
|
+
modules that make a file worth reading.
|
|
308
|
+
|
|
309
|
+
## A method the caller says which way round it goes
|
|
310
|
+
|
|
311
|
+
Most methods read or write whatever the call site looks like, and a few
|
|
312
|
+
are told. A signed URL is the one in the shipped packs: the same call
|
|
313
|
+
hands back a URL for reading or for writing depending on what it was
|
|
314
|
+
asked to sign for.
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
kind: {
|
|
318
|
+
asks: { at: 0, property: ["action"] },
|
|
319
|
+
means: { read: "read", write: "write" },
|
|
320
|
+
otherwise: "read",
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
`otherwise` is what the call comes to when it says nothing, which is
|
|
325
|
+
the library's own default rather than a guess.
|
|
326
|
+
|
|
327
|
+
## A statement written as SQL
|
|
328
|
+
|
|
329
|
+
A raw query says what it reached in its text rather than in its
|
|
330
|
+
arguments. So the chain hands the text to `@suss/sql`, and the parse
|
|
331
|
+
settles the container, the kind, the fields and the selector of every
|
|
332
|
+
table the statement touches:
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
sqlStatements({
|
|
336
|
+
system: "postgresql",
|
|
337
|
+
dialect: "postgresql",
|
|
338
|
+
client: declaredBy("@prisma/client", ".prisma/client"),
|
|
339
|
+
})
|
|
340
|
+
.methods({ $queryRaw: { statement: { at: 0 } } })
|
|
341
|
+
.example('prisma.$queryRawUnsafe("SELECT id, email FROM users")');
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
One call comes out as one effect per table, each with its own kind. A
|
|
345
|
+
statement that writes one table while reading another says both, which
|
|
346
|
+
is the thing the storage ending cannot do: that one settles the kind
|
|
347
|
+
once for the whole call.
|
|
348
|
+
|
|
349
|
+
`dialect` is which SQL the statements are written in, and a pack always
|
|
350
|
+
states it. Wherever the store is the database it is the store's own name
|
|
351
|
+
again, which is what Prisma writes above. A Cloudflare D1 database is a
|
|
352
|
+
store of its own whose statements are SQLite, so a pack for it says
|
|
353
|
+
`system: "d1", dialect: "sqlite"`. Nothing defaults, because a reader
|
|
354
|
+
that guessed Postgres for a MySQL project would report the wrong tables
|
|
355
|
+
rather than none: backtick-quoted identifiers parse as something else
|
|
356
|
+
entirely.
|
|
357
|
+
|
|
358
|
+
A tagged template is a call the source wrote without parentheses: the
|
|
359
|
+
tag is the callee and the template is the one argument. All three of
|
|
360
|
+
these state their statement at position 0:
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
prisma.$queryRaw`SELECT id FROM users WHERE id = ${id}`;
|
|
364
|
+
prisma.$queryRawUnsafe("SELECT id FROM users");
|
|
365
|
+
db.execute(sql`SELECT id FROM users`);
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
The third is a tagged template handed to an ordinary call, and a pick at
|
|
369
|
+
position 0 reads it because the text of a value comes back through the
|
|
370
|
+
tag. So a pack whose client takes its statement either way writes one
|
|
371
|
+
method table rather than two declarations.
|
|
372
|
+
|
|
373
|
+
The invocation walk never reaches a tagged template, so `pack` puts a
|
|
374
|
+
chain with this ending on the access walk instead. That walk visits
|
|
375
|
+
calls as well, which is what catches the unsafe form.
|
|
376
|
+
|
|
377
|
+
### A hole that is a table rather than a value
|
|
378
|
+
|
|
379
|
+
`parts()` gives the text either side of each hole, and by default what
|
|
380
|
+
the query interpolated reaches the statement as a parameter, which is
|
|
381
|
+
what it would have been anyway. Drizzle breaks that: a query says which
|
|
382
|
+
table it reached by handing over the schema object.
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
db.execute(sql`UPDATE ${users} SET name = ${name} WHERE id = ${id}`);
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Written as `UPDATE $1 SET name = $2 WHERE id = $3` that does not parse,
|
|
389
|
+
and the query goes unread. Only the pack knows the first hole is a table
|
|
390
|
+
and the other two are values, so the pack says so:
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
sqlStatements({ system: "postgresql", dialect: "postgresql", client })
|
|
394
|
+
.methods({ execute: { statement: { at: 0 } } })
|
|
395
|
+
.interpolating({ from: constructedFrom("drizzle-orm"), named: { at: 0 } });
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
`holes()` gives each hole as the call the source wrote it as, so
|
|
399
|
+
`${users}` comes back as the `pgTable("users", {...})` its schema file
|
|
400
|
+
declares and `named` reads the name off it the way any other pick reads
|
|
401
|
+
an argument. `from` keeps the pack from reading a hole the library did
|
|
402
|
+
not make; a hole that is not a call, or that came from somewhere else,
|
|
403
|
+
stays the parameter it was.
|
|
404
|
+
|
|
113
405
|
## The example every declaration states
|
|
114
406
|
|
|
115
407
|
```ts
|
package/dist/ast.d.cts
CHANGED
package/dist/ast.d.ts
CHANGED