@suss/recognize 0.33.1 → 0.34.1

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/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ConstructedFrom, DeclaredBy, CallOps, InvocationRecognizer, PatternPack, UnsettledName, ReceiverOrigin, ProjectHelpers, PackDeclarations } from '@suss/extractor';
2
- export { CallOps, ConstructedFrom, DeclaredBy, DeclaredMatch, HelperDeclarations, HelperSink, HelperValue, OpsCarrier, PackDeclarations, PatternPack, ProjectHelper, ProjectHelpers, ReceiverOrigin, UnsettledName, ValueEntry, ValueOps } from '@suss/extractor';
3
- import { C as Chain, M as MethodMeaning, a as MessageSendMethod, b as MessageLocation, c as ChannelPart, S as SqlMethod, I as InterpolatesLink, d as CallStep, e as StorageMethod, A as ArgumentPick, L as LinkFunction, O as OneArgument, U as UnitInvokeMethod } from './chain-CVG6Jnr-.js';
4
- export { f as AccessKind, g as AccessPathLink, h as ArgumentsFrom, i as CallsLink, j as ContainerArgument, k as ContainerLink, l as ContainerRule, m as ContainersLink, E as Ending, F as FromReceiver, n as InputLink, o as InputRule, K as KindAsAsked, p as Link, q as ManyIn, r as MatchStart, s as MessageSendEnding, t as MethodsLink, u as OneMessage, v as ScopeLink, w as SelectorParamPick, x as SqlEnding, y as StartLink, z as StatedInputs, B as StatedRule, D as StorageEnding, G as SubjectLink, T as ToArgument, H as ToReceiver, J as UnitInvokeEnding } from './chain-CVG6Jnr-.js';
2
+ export { CallOps, ClassOps, ConstructedFrom, DeclaredBy, DeclaredMatch, HelperDeclarations, HelperSink, HelperValue, OpsCarrier, PackDeclarations, PatternPack, ProjectHelper, ProjectHelpers, ReceiverOrigin, UnsettledName, ValueEntry, ValueOps } from '@suss/extractor';
3
+ import { C as Chain, M as MethodMeaning, a as MessageSendMethod, b as MessageLocation, c as ChannelPart, S as SqlMethod, I as InterpolatesLink, d as CallStep, e as StorageMethod, A as ArgumentPick, L as LinkFunction, O as OneArgument, U as UnitInvokeMethod } from './chain-DYU9ZdzF.js';
4
+ export { f as AccessKind, g as AccessPathLink, h as ArgumentsFrom, i as CallsLink, j as ContainerArgument, k as ContainerLink, l as ContainerRule, m as ContainersLink, E as Ending, F as FromReceiver, n as InputLink, o as InputRule, K as KindAsAsked, p as Link, q as ManyIn, r as MatchStart, s as MessageSendEnding, t as MethodsLink, u as OneMessage, v as ScopeLink, w as SelectorParamPick, x as SqlEnding, y as StartLink, z as StatedInputs, B as StatedRule, D as StorageEnding, G as SubjectLink, T as ToArgument, H as ToReceiver, J as UnitInvokeEnding } from './chain-DYU9ZdzF.js';
5
5
  import { Effect, MessageBusSemantics, DeployableUnit } from '@suss/behavioral-ir';
6
6
 
7
7
  /**
@@ -15,8 +15,8 @@ import { Effect, MessageBusSemantics, DeployableUnit } from '@suss/behavioral-ir
15
15
 
16
16
  /**
17
17
  * A receiver whose method one of these modules declared. Pass every
18
- * module that ships the same client, the way three packages all speak
19
- * one wire protocol.
18
+ * module that ships the same client, for example several packages that
19
+ * implement one wire protocol.
20
20
  */
21
21
  declare function declaredBy(...importedFrom: readonly string[]): DeclaredBy;
22
22
  /** A client made from one of these modules' exports. */
@@ -30,18 +30,18 @@ declare function constructedFrom(spec: {
30
30
  declare function opsIn(ctx: unknown): CallOps | null;
31
31
 
32
32
  /**
33
- * Turning a chain into the recognizer hook the adapters already call.
33
+ * Turns a chain into the recognizer hook the adapters already call.
34
34
  *
35
- * The links are guards, so the compiled hook checks the cheap one
36
- * first: looking a method up in a table costs nothing, while following
37
- * a receiver to the library that made it walks declarations. The chain
38
- * says which questions to ask, not the order to ask them in. A link
39
- * that changes what the receiver is, the way Prisma's model property
40
- * does, will need that order back, and the note here is where to start.
35
+ * Every link is a guard, and the compiled hook checks the cheapest one
36
+ * first. Looking a method up in a table costs nothing, while following
37
+ * a receiver to the library that made it walks declarations. So the
38
+ * hook ignores the order the pack declared its links in. A link that
39
+ * changes what the receiver is, as a Prisma model property would, needs
40
+ * declared order back, and that change goes here.
41
41
  *
42
42
  * A chain that states a subject asks its questions of a call next to
43
- * the one in hand, so the walk lives here. The README beside this file
44
- * says what each step reaches and why the walk is bounded.
43
+ * the one in hand, and the walk to that call is also here. DESIGN.md
44
+ * describes what each step reaches and why the walk is bounded.
45
45
  */
46
46
 
47
47
  /** A chain, as the hook the adapter dispatches to on every call. */
@@ -71,28 +71,27 @@ interface RanExample {
71
71
  /** How a pack's test compiles one line and runs the pack over it. */
72
72
  type RunOverCode = (code: string) => Effect[];
73
73
  /**
74
- * Every example a pack declares, run. A declaration with no example is
75
- * left out, which is what the pack health check catches separately.
74
+ * Runs every example a pack declares. A declaration with no example is
75
+ * skipped here; `examplesMissing` lists those, and the pack health
76
+ * check reports them.
76
77
  */
77
78
  declare function runExamples(pack: PatternPack, run: RunOverCode): RanExample[];
78
79
  /** The declarations a pack shipped without a line of code to run. */
79
80
  declare function examplesMissing(pack: PatternPack): string[];
80
81
 
81
82
  /**
82
- * messageSends.ts: a pack that recognizes a call putting a message on a
83
- * wire.
83
+ * A pack that recognizes a call putting a message on a wire.
84
84
  *
85
- * The AWS SDK shape is the one this was written against. An operation
86
- * is a command class and its arguments are one object, so the pack says
87
- * which command, where the message is inside it, and which of the
88
- * message's properties name the channel:
85
+ * It was written against the AWS SDK. There an operation is a command
86
+ * class and its arguments are one object, so the pack says which
87
+ * command, where the message is inside it, and which of the message's
88
+ * properties make up the channel:
89
89
  *
90
90
  * client.send(new SendMessageCommand({ QueueUrl, MessageBody }))
91
91
  * client.send(new PutEventsCommand({ Entries: [{ EventBusName, DetailType }] }))
92
92
  *
93
- * A library taking one message and a batch of them offers two commands,
94
- * so a pack writes two declarations. Which of the two a command is
95
- * belongs to the command rather than to a setting on one declaration.
93
+ * A library that sends one message and a batch through separate
94
+ * commands gets two declarations, one per command.
96
95
  */
97
96
 
98
97
  interface MessageSendsSpec {
@@ -102,19 +101,19 @@ interface MessageSendsSpec {
102
101
  messages: MessageLocation;
103
102
  /** The parts of the channel, joined in the order they are written. */
104
103
  channel: readonly ChannelPart[];
105
- /** What joins the parts. Defaults to "#", which is what a bus and a subject use. */
104
+ /** What joins the parts. Defaults to "#", as used between a bus and a subject. */
106
105
  channelSeparator?: string;
107
106
  /** The property a message states its body on. */
108
107
  body?: string;
109
108
  /**
110
- * The property whose literal value rides along as the routing key,
111
- * scoping the message for a reader without joining the channel.
109
+ * The property whose literal value is recorded as the routing key. A
110
+ * reader can filter on it, and it stays out of the channel name.
112
111
  */
113
112
  routingKey?: string;
114
113
  /**
115
114
  * What a reader gives back for a channel nothing in the source
116
- * settles. Defaults to keeping the reference, since a queue named
117
- * only at deploy time is the common case rather than the exception.
115
+ * settles. Defaults to keeping the reference, because most queues get
116
+ * their name at deploy time.
118
117
  */
119
118
  unsettledName?: UnsettledName;
120
119
  /** How the pack pins down the client its calls are on. */
@@ -146,15 +145,15 @@ declare function messageSends(spec: MessageSendsSpec): MessageSends;
146
145
  */
147
146
 
148
147
  /**
149
- * One thing a pack matches, as whichever entry point built it hands it
150
- * over. `storageCalls` and `sqlStatements` both come out as this.
148
+ * One thing a pack matches, as an entry point returns it. `storageCalls`
149
+ * and `sqlStatements` both return this.
151
150
  */
152
151
  interface Match {
153
152
  readonly declared: Chain<MethodMeaning>;
154
153
  }
155
154
  /** What a pack says about itself, beyond the calls it matches. */
156
155
  interface PackSpec {
157
- /** The languages the pack runs on, as the adapters name them. */
156
+ /** The languages the pack runs on, spelled as the adapters spell them. */
158
157
  languages: string[];
159
158
  /**
160
159
  * The name effects from this pack are recorded under, which is the
@@ -168,24 +167,23 @@ interface PackSpec {
168
167
  */
169
168
  version?: string;
170
169
  /**
171
- * What the pack itself speaks, when that is not the wire its accesses
172
- * record. An S3 access goes over the AWS SDK and says so on every
173
- * effect, while the pack speaks S3.
170
+ * The pack's own protocol, when it differs from the wire its accesses
171
+ * record. An S3 pack records the AWS SDK as the wire on every effect,
172
+ * and its protocol is S3.
174
173
  */
175
174
  protocol?: string;
176
175
  /**
177
- * Further modules whose presence makes a file worth reading, beyond
178
- * the ones the chains match on. A helper a project reaches by a
179
- * relative path gives the gate nothing; the library that helper
180
- * imports gives it something.
176
+ * More modules that make a file worth reading, beyond the ones the
177
+ * chains match on. A helper imported by a relative path does not open
178
+ * the import gate, so the pack lists the library the helper imports.
181
179
  */
182
180
  requiresImport?: string[];
183
181
  /**
184
182
  * Recognizers written against the adapter directly, run alongside the
185
- * declared chains. A pack migrating one call at a time keeps what is
186
- * not declared yet here, and a pack that needs a shape the endings
187
- * cannot say keeps it here for good. Health reporting counts these as
188
- * function links, so the cost of staying here is visible.
183
+ * declared chains. A pack moving to chains one call at a time keeps
184
+ * the calls it has not declared yet here, and a call no ending can
185
+ * describe stays here for good. The pack health report counts these
186
+ * as function links, so a reader sees how many are left.
189
187
  */
190
188
  recognizers?: InvocationRecognizer[];
191
189
  /**
@@ -198,9 +196,10 @@ interface PackSpec {
198
196
  /** A pack, assembled from the calls it says it matches. */
199
197
  declare function pack(name: string, matches: readonly Match[], spec: PackSpec): PatternPack;
200
198
  /**
201
- * What a set of chains cost, for a pack that assembles itself. A pack
202
- * part way through the migration still has a hand-rolled walk beside
203
- * its declarations, and this is how the part that moved is priced.
199
+ * Describes a set of chains for the pack health report, for a pack that
200
+ * builds its `PatternPack` by hand. A pack partway through moving to
201
+ * declared chains keeps hand-written recognizers beside them, and this
202
+ * reports on the part that has moved.
204
203
  */
205
204
  declare function declarationsIn(matches: readonly Match[]): PackDeclarations;
206
205
 
@@ -208,13 +207,13 @@ declare function declarationsIn(matches: readonly Match[]): PackDeclarations;
208
207
  * The entry point for a pack whose calls hand the store a statement
209
208
  * written as SQL.
210
209
  *
211
- * A typed client says what it reached in its arguments, so the chain
212
- * asks the call. A raw statement says it in the text, so the chain
213
- * hands the text to `@suss/sql` and the parse settles the container,
214
- * the kind, the fields and the selector of every table the statement
215
- * touches. The links up to that point are the same ones every chain
216
- * states, which is why this is an ending rather than a grammar of its
217
- * own.
210
+ * With a typed client, the call's arguments show which table it
211
+ * reached. With a raw statement that information is in the SQL text,
212
+ * so the chain passes the text to `@suss/sql`. The parse gives the
213
+ * container, the kind, the fields and the selector of every table the
214
+ * statement touches. Every link before that point is one any other
215
+ * chain uses too, so SQL is a chain ending and needs no separate
216
+ * grammar.
218
217
  */
219
218
 
220
219
  /** What a pack says about the store its statements reach. */
@@ -255,10 +254,10 @@ declare function sqlStatements(spec: SqlStatementsSpec): SqlStatements;
255
254
  * The entry point for a pack that recognizes storage calls, and the
256
255
  * chain it builds.
257
256
  *
258
- * The entry point says what comes out rather than how, so a pack author
259
- * writes `storageCalls` and never meets an `Effect`. Each method on the
260
- * returned value adds one link and gives back a new chain, so a chain
261
- * is finished data by the time `pack` receives it.
257
+ * A pack author writes `storageCalls` and never builds an `Effect` by
258
+ * hand. Each method on the returned value adds one link and returns a
259
+ * new chain, so by the time `pack` receives a chain it is plain data
260
+ * that nothing changes afterwards.
262
261
  */
263
262
 
264
263
  /** What a pack says about the store its calls reach. */
@@ -271,8 +270,8 @@ interface StorageCallsSpec {
271
270
  scope?: string;
272
271
  /**
273
272
  * How the pack pins down the client its calls are on. A pack that
274
- * matches a helper a project lists in its own config leaves this out,
275
- * because the name that project gave is the whole of what it has.
273
+ * matches a helper listed in a project's own config leaves this out,
274
+ * because the helper's name is the only thing it has to match on.
276
275
  */
277
276
  client?: ReceiverOrigin;
278
277
  /**
@@ -294,9 +293,9 @@ interface StorageCalls {
294
293
  ignoringCase?: boolean;
295
294
  }): StorageCalls;
296
295
  /**
297
- * What a bare call of the client itself does. A store hook is the
298
- * case: `useAppStore((s) => s.bears)` reaches for no method, so the
299
- * one meaning here is the whole answer.
296
+ * What a bare call of the client itself does. A store hook such as
297
+ * `useAppStore((s) => s.bears)` calls no method, so this meaning
298
+ * applies to every call.
300
299
  */
301
300
  calls(meaning: StorageMethod): StorageCalls;
302
301
  /**
@@ -336,13 +335,12 @@ interface StorageCalls {
336
335
  declare function storageCalls(spec: StorageCallsSpec): StorageCalls;
337
336
 
338
337
  /**
339
- * unitInvokes.ts: a pack that recognizes a call invoking one deployed
340
- * unit by name.
338
+ * A pack that recognizes a call invoking one deployed unit by name.
341
339
  *
342
- * The AWS SDK shape is the one this was written against, and it is the
343
- * same shape a send has: an operation is a command class and its
344
- * arguments are one object, so the pack says which command and which of
345
- * its properties says which unit the call reaches.
340
+ * It was written against the AWS SDK, where an invoke looks like a
341
+ * message send: an operation is a command class and its arguments are
342
+ * one object. The pack says which command, and which of its properties
343
+ * identifies the unit the call reaches.
346
344
  *
347
345
  * client.send(new InvokeCommand({ FunctionName, Payload }))
348
346
  */
package/dist/index.js CHANGED
@@ -31,6 +31,7 @@ function opsIn(ctx) {
31
31
  // src/compile.ts
32
32
  var MAX_RECEIVER_HOPS = 8;
33
33
  function compile(chain, recognition) {
34
+ const receiverMethods = methodsWhoseReceiversAreAsked(chain);
34
35
  return (_call, ctx) => {
35
36
  const ops = opsIn(ctx);
36
37
  if (ops === null) {
@@ -51,6 +52,9 @@ function compile(chain, recognition) {
51
52
  if (methods === null) {
52
53
  return null;
53
54
  }
55
+ if (receiverMethods !== null) {
56
+ ops.readingReceivers?.(receiverMethods);
57
+ }
54
58
  for (const subject of subjectsOf(chain, ops)) {
55
59
  const method = operationOf(subject);
56
60
  const meaning = method === null ? null : listed(methods, method);
@@ -69,6 +73,14 @@ function compile(chain, recognition) {
69
73
  return null;
70
74
  };
71
75
  }
76
+ function methodsWhoseReceiversAreAsked(chain) {
77
+ const start = linkIn(chain, "start");
78
+ const table = methodsIn(chain);
79
+ if (start === null || start.at.starts !== "receiver" || start.at.origin.origin !== "constructed" || linkIn(chain, "subject") !== null || table === null || table.ignoringCase) {
80
+ return null;
81
+ }
82
+ return new Set(Object.keys(table.table));
83
+ }
72
84
  function linkIn(chain, asks) {
73
85
  const link = chain.links.find((candidate) => candidate.asks === asks);
74
86
  return link ?? null;
@@ -102,7 +114,23 @@ function listed(link, method) {
102
114
  }
103
115
  function subjectsOf(chain, ops) {
104
116
  const link = linkIn(chain, "subject");
105
- return link === null ? [ops] : walk([ops], link.of);
117
+ if (link === null) {
118
+ return [ops];
119
+ }
120
+ announceArgumentReads(link.of, ops);
121
+ return walk([ops], link.of);
122
+ }
123
+ function announceArgumentReads(steps, ops) {
124
+ const first = steps[0];
125
+ if (first === void 0 || first.to !== "argument") {
126
+ return;
127
+ }
128
+ const at = first.at;
129
+ if (typeof at === "number") {
130
+ ops.readingArguments?.(at, at);
131
+ return;
132
+ }
133
+ ops.readingArguments?.(at.from);
106
134
  }
107
135
  var STEP = {
108
136
  receiver: (step, ops) => receiversOf(ops, step.method),
@@ -184,9 +212,24 @@ function accessEffect(matched, access) {
184
212
  const ending = chain.ending;
185
213
  const { input, kind, reached, unsettled } = access;
186
214
  const stated = { input, entry: reached.entry, kind };
187
- const selector = namesFor(meaning.selector, subject, stated, unsettled);
188
- const fields = namesFor(meaning.fields, subject, stated, unsettled);
189
- return {
215
+ const listening = ops.statesSlots !== void 0;
216
+ const selectorRead = listening ? /* @__PURE__ */ new Map() : null;
217
+ const fieldsRead = listening ? /* @__PURE__ */ new Map() : null;
218
+ const selector = namesFor(
219
+ meaning.selector,
220
+ subject,
221
+ stated,
222
+ unsettled,
223
+ selectorRead
224
+ );
225
+ const fields = namesFor(
226
+ meaning.fields,
227
+ subject,
228
+ stated,
229
+ unsettled,
230
+ fieldsRead
231
+ );
232
+ const effect = {
190
233
  type: "interaction",
191
234
  binding: storageBinding({
192
235
  recognition,
@@ -205,6 +248,69 @@ function accessEffect(matched, access) {
205
248
  ...selector.length > 0 ? { selector } : {}
206
249
  }
207
250
  };
251
+ const slots = [
252
+ ...slotsRead("selector", selector, selectorRead),
253
+ ...slotsRead("field", fields, fieldsRead)
254
+ ];
255
+ if (slots.length > 0) {
256
+ ops.statesSlots?.(effect, slots);
257
+ }
258
+ return effect;
259
+ }
260
+ function slotsRead(slot, names, read) {
261
+ if (read === null) {
262
+ return [];
263
+ }
264
+ return [...new Set(names)].flatMap(
265
+ (name) => (read.get(name) ?? []).map((value) => ({ slot, name, value }))
266
+ );
267
+ }
268
+ function noteRead(read, name, value) {
269
+ const already = read.get(name);
270
+ if (already === void 0) {
271
+ read.set(name, [value]);
272
+ return;
273
+ }
274
+ if (!already.includes(value)) {
275
+ already.push(value);
276
+ }
277
+ }
278
+ function recording(value, read) {
279
+ return {
280
+ text: () => value.text(),
281
+ name: (unsettled) => value.name(unsettled),
282
+ ...value.names === void 0 ? {} : { names: (cap) => value.names?.(cap) ?? null },
283
+ flag: () => value.flag(),
284
+ entries: (unsettled) => value.entries(unsettled).map((entry) => {
285
+ if (entry.key !== null) {
286
+ noteRead(read, entry.key, entry.value);
287
+ }
288
+ return { key: entry.key, value: recording(entry.value, read) };
289
+ }),
290
+ items: () => value.items().map((item) => recording(item, read)),
291
+ property: (name) => {
292
+ const inside = value.property(name);
293
+ if (inside === null) {
294
+ return null;
295
+ }
296
+ noteRead(read, name, inside);
297
+ return recording(inside, read);
298
+ },
299
+ asArg: () => value.asArg(),
300
+ parts: () => value.parts(),
301
+ holes: () => value.holes(),
302
+ ...value.interpolated === void 0 ? {} : { interpolated: () => value.interpolated?.() ?? [] }
303
+ };
304
+ }
305
+ function watched(inputs, read) {
306
+ if (read === null) {
307
+ return inputs;
308
+ }
309
+ return {
310
+ ...inputs,
311
+ input: recording(inputs.input, read),
312
+ entry: inputs.entry === null ? null : recording(inputs.entry, read)
313
+ };
208
314
  }
209
315
  function reachedBy(chain, subject, unsettled) {
210
316
  const link = linkIn(chain, "containers");
@@ -283,7 +389,7 @@ var NOTHING_STATED = {
283
389
  parts: () => null,
284
390
  holes: () => []
285
391
  };
286
- function namesFor(says, subject, stated, unsettled) {
392
+ function namesFor(says, subject, stated, unsettled, read = null) {
287
393
  if (says === void 0) {
288
394
  return [];
289
395
  }
@@ -292,7 +398,7 @@ function namesFor(says, subject, stated, unsettled) {
292
398
  }
293
399
  if (typeof says === "function") {
294
400
  const input2 = stated.input;
295
- return input2 === null ? [] : [...says({ ...stated, input: input2 })];
401
+ return input2 === null ? [] : [...says(watched({ ...stated, input: input2 }, read))];
296
402
  }
297
403
  if ("selectorParam" in says) {
298
404
  return [...subject.parameterReadsAt?.(says.selectorParam) ?? []];
@@ -302,7 +408,7 @@ function namesFor(says, subject, stated, unsettled) {
302
408
  return namesAt(subject, pointed, unsettled);
303
409
  }
304
410
  const input = statedValue(subject, pointed.of) ?? NOTHING_STATED;
305
- return [...pointed.by({ ...stated, input })];
411
+ return [...pointed.by(watched({ ...stated, input }, read))];
306
412
  }
307
413
  function namesAt(subject, pick, unsettled) {
308
414
  if (pick === void 0) {
@@ -343,20 +449,27 @@ function messageSend(matched) {
343
449
  }
344
450
  const messages = messagesIn(input, ending.messages);
345
451
  const sent = messages.length === 0 ? [NOTHING_STATED] : messages;
346
- return sent.map((message) => ({
347
- type: "interaction",
348
- binding: messageBusBinding({
349
- recognition,
350
- messageBus: ending.wire,
351
- channel: channelOf(message, input, ending)
352
- }),
353
- callee: ops.calleeText(),
354
- interaction: {
355
- class: "message-send",
356
- ...bodyOf(message, ending),
357
- ...routingKeyOf(message, ending)
452
+ const effects = [];
453
+ for (const message of sent) {
454
+ const channels = channelsOf({ message, input, ending });
455
+ for (const channel of channels) {
456
+ effects.push({
457
+ type: "interaction",
458
+ binding: messageBusBinding({
459
+ recognition,
460
+ messageBus: ending.wire,
461
+ channel
462
+ }),
463
+ callee: ops.calleeText(),
464
+ interaction: {
465
+ class: "message-send",
466
+ ...bodyOf(message, ending),
467
+ ...routingKeyOf(message, ending)
468
+ }
469
+ });
358
470
  }
359
- }));
471
+ }
472
+ return effects;
360
473
  }
361
474
  function messagesIn(input, location) {
362
475
  if (location.each === "theInput") {
@@ -365,25 +478,47 @@ function messagesIn(input, location) {
365
478
  const collection = input.property(location.property);
366
479
  return collection === null ? [] : collection.items();
367
480
  }
368
- function channelOf(message, input, ending) {
369
- const parts = [];
370
- for (const part of ending.channel) {
371
- const holder = part.on === "theInput" ? input : message;
372
- const written = firstWritten(holder, part.property);
373
- if (written === null) {
374
- if (part.whenAbsent === void 0) {
375
- return null;
376
- }
377
- parts.push(part.whenAbsent);
378
- continue;
481
+ var CHANNEL_CAP = 16;
482
+ function oneName(written, unsettled) {
483
+ const stated = written.name(unsettled);
484
+ return stated === null || stated === "" ? null : [stated];
485
+ }
486
+ function everyString(written, unsettled) {
487
+ const each = written.names?.(CHANNEL_CAP) ?? null;
488
+ if (each === null || each.length < 2 || each.includes("")) {
489
+ return oneName(written, unsettled);
490
+ }
491
+ return each;
492
+ }
493
+ function channelsOf(sending) {
494
+ const each = channelsRead(sending, "everyString");
495
+ return each.length > CHANNEL_CAP ? channelsRead(sending, "oneName") : each;
496
+ }
497
+ function channelsRead(sending, read) {
498
+ const separator = sending.ending.channelSeparator ?? "#";
499
+ let channels = null;
500
+ for (const part of sending.ending.channel) {
501
+ const values = partValues(part, sending, read);
502
+ if (values === null) {
503
+ return [null];
379
504
  }
380
- const stated = written.name(part.unsettled ?? ending.unsettledName);
381
- if (stated === null || stated === "") {
382
- return null;
383
- }
384
- parts.push(stated);
505
+ channels = channels === null ? values : crossed(channels, values, separator);
506
+ }
507
+ return channels ?? [""];
508
+ }
509
+ function crossed(heads, values, separator) {
510
+ return heads.flatMap(
511
+ (head) => values.map((value) => `${head}${separator}${value}`)
512
+ );
513
+ }
514
+ function partValues(part, sending, read) {
515
+ const holder = part.on === "theInput" ? sending.input : sending.message;
516
+ const written = firstWritten(holder, part.property);
517
+ if (written === null) {
518
+ return part.whenAbsent === void 0 ? null : [part.whenAbsent];
385
519
  }
386
- return parts.join(ending.channelSeparator ?? "#");
520
+ const unsettled = part.unsettled ?? sending.ending.unsettledName;
521
+ return read === "everyString" ? everyString(written, unsettled) : oneName(written, unsettled);
387
522
  }
388
523
  function firstWritten(holder, properties) {
389
524
  for (const property of properties) {
@@ -466,28 +601,68 @@ function sqlAccess(matched) {
466
601
  namesInHoles(chain, stated.value),
467
602
  settledHoles(stated.value)
468
603
  );
469
- const accesses = readSqlAccess(statement, { dialect: ending.dialect });
470
- return accesses.length === 0 ? null : accesses.map((access) => ({
471
- type: "interaction",
472
- binding: storageBinding({
473
- recognition,
474
- storageSystem: ending.system,
475
- ...ending.transport === void 0 ? {} : { transport: ending.transport },
476
- // A statement that qualifies its table says which namespace
477
- // the access is in, and the pack's own scope is the fallback
478
- // for a statement that leaves it out.
479
- scope: access.qualifier[access.qualifier.length - 1] ?? ending.scope,
480
- container: access.table
481
- }),
482
- callee: ops.calleeText(),
483
- interaction: {
484
- class: "storage-access",
485
- kind: access.kind,
486
- fields: access.fields,
487
- ...access.selector.length > 0 ? { selector: access.selector } : {},
488
- operation: method
604
+ const accesses = readSqlAccess(statement, {
605
+ dialect: ending.dialect,
606
+ placeholders: ops.statesSlots !== void 0
607
+ });
608
+ if (accesses.length === 0) {
609
+ return null;
610
+ }
611
+ const values = placeholderValues(
612
+ stated,
613
+ subject,
614
+ matched.meaning.parameters
615
+ );
616
+ return accesses.map((access) => {
617
+ const effect = {
618
+ type: "interaction",
619
+ binding: storageBinding({
620
+ recognition,
621
+ storageSystem: ending.system,
622
+ ...ending.transport === void 0 ? {} : { transport: ending.transport },
623
+ // A statement that qualifies its table says which namespace
624
+ // the access is in, and the pack's own scope is the fallback
625
+ // for a statement that leaves it out.
626
+ scope: access.qualifier[access.qualifier.length - 1] ?? ending.scope,
627
+ container: access.table
628
+ }),
629
+ callee: ops.calleeText(),
630
+ interaction: {
631
+ class: "storage-access",
632
+ kind: access.kind,
633
+ fields: access.fields,
634
+ ...access.selector.length > 0 ? { selector: access.selector } : {},
635
+ operation: method
636
+ }
637
+ };
638
+ const slots = (access.placeholders ?? []).flatMap((one) => {
639
+ const value = placeholderValue(values, one.placeholder);
640
+ return value === null ? [] : [{ slot: SLOT_OF[one.clause], name: one.field, value }];
641
+ });
642
+ if (slots.length > 0) {
643
+ ops.statesSlots?.(effect, slots);
489
644
  }
490
- }));
645
+ return effect;
646
+ });
647
+ }
648
+ var SLOT_OF = {
649
+ fields: "field",
650
+ selector: "selector"
651
+ };
652
+ function placeholderValues(stated, subject, says) {
653
+ const writesItsOwn = stated.parts.some((part) => /\$\d/.test(part));
654
+ if (stated.parts.length > 1 && !writesItsOwn) {
655
+ return { numbered: stated.value.interpolated?.() ?? [], named: null };
656
+ }
657
+ const picks = says === void 0 ? [] : Array.isArray(says) ? says : [says];
658
+ const given = picks.map((pick) => statedValue(subject, pick)).find((value) => value !== null);
659
+ return given === void 0 ? { numbered: [], named: null } : { numbered: given.items(), named: given };
660
+ }
661
+ function placeholderValue(values, placeholder) {
662
+ if (typeof placeholder === "number") {
663
+ return values.numbered[placeholder - 1] ?? null;
664
+ }
665
+ return values.named?.property(placeholder) ?? null;
491
666
  }
492
667
  function statementIn(subject, says) {
493
668
  const picks = Array.isArray(says) ? says : [says];
@@ -679,8 +854,8 @@ function describe(chain) {
679
854
  ];
680
855
  return {
681
856
  name: declaredName(chain.ending),
682
- // A method table with a rule inside it is still a table, so that
683
- // link stays counted as data and the rule is priced beside it.
857
+ // A method table with a rule inside still counts as a data link. The
858
+ // rule is counted on its own, as a function link.
684
859
  dataLinks: chain.links.length - links.length,
685
860
  functionLinks: written.map((rule) => rule.asks),
686
861
  astLinks: written.filter((rule) => rule.reachesAst).map((rule) => rule.asks),