@suss/recognize 0.19.0 → 0.21.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
@@ -76,6 +76,15 @@ ioredis, and the declaration behind `redis.get` says everything.
76
76
  `constructed` asks the receiver itself, so it still works where the
77
77
  method is untyped.
78
78
 
79
+ A chain can also match a bare call of the tracked value itself, with
80
+ `.calls(meaning)` beside its methods table. A store hook is the case:
81
+ `useAppStore((s) => s.bears)` reaches for no method, so there is no
82
+ name for the table to list; the call matches when its callee is a
83
+ bound name whose written value came from the chain's origin. The
84
+ fields such a call reads are usually stated in a selector lambda, and
85
+ `fields: { selectorParam: 0 }` picks them off the lambda's parameter,
86
+ one per distinct first segment.
87
+
79
88
  ## The gradient
80
89
 
81
90
  Expressiveness is bought link by link, and the price is printed.
@@ -111,6 +120,8 @@ result on the recognizer context under `ops`.
111
120
  | `receiver()` | the call the receiver is, as ops of its own |
112
121
  | `argument(index)` | the call that argument is, as ops of its own |
113
122
  | `callee()` | the call the callee itself was written as |
123
+ | `namedCallee()` | whether the call goes to a name the program bound (optional) |
124
+ | `parameterReadsAt(index)` | the fields a selector lambda in that position reads off its parameter (optional) |
114
125
  | `propertyAt(index, property, unsettled)` | what a named property of that argument says |
115
126
  | `valueAt(index)` | the value that argument states, as `ValueOps` |
116
127
 
package/dist/ast.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CallOps } from '@suss/extractor';
2
- import { L as LinkFunction } from './chain-C7w99UMv.cjs';
2
+ import { L as LinkFunction } from './chain-IogmSi0t.cjs';
3
3
  import '@suss/behavioral-ir';
4
4
 
5
5
  /**
package/dist/ast.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CallOps } from '@suss/extractor';
2
- import { L as LinkFunction } from './chain-C7w99UMv.js';
2
+ import { L as LinkFunction } from './chain-IogmSi0t.js';
3
3
  import '@suss/behavioral-ir';
4
4
 
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { MessageBusSemantics } from '@suss/behavioral-ir';
1
+ import { MessageBusSemantics, DeployableUnit } from '@suss/behavioral-ir';
2
2
  import { ReceiverOrigin, ValueOps, CallOps, UnsettledName } from '@suss/extractor';
3
3
 
4
4
  /**
@@ -136,6 +136,13 @@ interface AccessPathLink {
136
136
  interface ContainersLink {
137
137
  readonly asks: "containers";
138
138
  readonly in: OneArgument;
139
+ /**
140
+ * What one of them is. An entry keys the container by name and says
141
+ * what the call did there, which is how a batch write is written. A
142
+ * name is the container on its own, which is how a call that reads
143
+ * several parameters at once is written. Defaults to an entry.
144
+ */
145
+ readonly each?: "entry" | "name";
139
146
  }
140
147
  /**
141
148
  * Where a call states its inputs, when it states them as one object
@@ -165,12 +172,30 @@ interface InterpolatesLink {
165
172
  readonly from?: ReceiverOrigin;
166
173
  }
167
174
  /** One question in a chain, and the answer the pack gave for it. */
168
- type Link<TMeaning> = StartLink | SubjectLink | MethodsLink<TMeaning> | ContainerLink | AccessPathLink | ContainersLink | InputLink | InterpolatesLink;
175
+ type Link<TMeaning> = StartLink | SubjectLink | MethodsLink<TMeaning> | CallsLink<TMeaning> | ContainerLink | AccessPathLink | ContainersLink | InputLink | InterpolatesLink;
176
+ /**
177
+ * The meaning of a bare call of the tracked client itself. A store
178
+ * hook is the case: `useAppStore((s) => s.bears)` reaches for no
179
+ * method, so there is no name for a methods table to list; the call
180
+ * matches by what its callee was written as.
181
+ */
182
+ interface CallsLink<TMeaning> {
183
+ readonly asks: "calls";
184
+ readonly meaning: TMeaning;
185
+ }
169
186
  /**
170
187
  * Which argument or arguments say what a call reached, and where to go
171
188
  * looking. Without `of` the argument belongs to the chain's subject.
172
189
  */
173
190
  type ArgumentPick = OneArgument | ArgumentsFrom;
191
+ /**
192
+ * The fields a selector lambda reads off its parameter, one per
193
+ * distinct first segment: `(s) => s.bears.count` reads `bears`. The
194
+ * value says which argument the lambda is passed as.
195
+ */
196
+ interface SelectorParamPick {
197
+ readonly selectorParam: number;
198
+ }
174
199
  /** The argument in one position. */
175
200
  interface OneArgument {
176
201
  /** The steps to the call the argument belongs to. */
@@ -261,13 +286,13 @@ interface StorageMethod {
261
286
  * wrote, or the plain list a method whose own name settles it states
262
287
  * outright: `findById` picks by `_id` however the id is spelt.
263
288
  */
264
- readonly selector?: readonly string[] | ArgumentPick | InputRule | StatedRule;
289
+ readonly selector?: readonly string[] | ArgumentPick | SelectorParamPick | InputRule | StatedRule;
265
290
  /**
266
291
  * Which fields the call touched, said the same three ways. A delete
267
292
  * touches the whole document and states `["*"]`, since nothing in its
268
293
  * arguments says so.
269
294
  */
270
- readonly fields?: readonly string[] | ArgumentPick | InputRule | StatedRule;
295
+ readonly fields?: readonly string[] | ArgumentPick | SelectorParamPick | InputRule | StatedRule;
271
296
  }
272
297
  /**
273
298
  * What one method of a client that takes a statement written as SQL
@@ -289,9 +314,15 @@ interface MessageSendMethod {
289
314
  /** Where the call states the message, or the collection of them. */
290
315
  readonly input: OneArgument;
291
316
  }
317
+ /**
318
+ * What one invoke method does. An invoke states its whole request as
319
+ * one object the same way a send does, so both say where that object
320
+ * is and nothing else.
321
+ */
322
+ type UnitInvokeMethod = MessageSendMethod;
292
323
  type MethodMeaning = StorageMethod | SqlMethod | MessageSendMethod;
293
324
  /** What a chain produces when every link matches. */
294
- type Ending = StorageEnding | SqlEnding | MessageSendEnding;
325
+ type Ending = StorageEnding | SqlEnding | MessageSendEnding | UnitInvokeEnding;
295
326
  /** A storage access: one call, one thing it read or wrote. */
296
327
  interface StorageEnding {
297
328
  readonly yields: "storageAccess";
@@ -359,8 +390,13 @@ interface ManyIn {
359
390
  * the same way, so a pack states the parts in the order they join.
360
391
  */
361
392
  interface ChannelPart {
362
- /** The property the message states this part on. */
363
- readonly property: string;
393
+ /**
394
+ * The properties the message may state this part on, tried in order.
395
+ * A library that lets one destination be written more than one way
396
+ * lists each spelling: an SNS publish writes its topic as `TopicArn`
397
+ * or as `TargetArn`, and the two are the same part of the channel.
398
+ */
399
+ readonly property: readonly string[];
364
400
  /**
365
401
  * Where the property is written. A batch command states the queue
366
402
  * once beside the list of messages, so its channel part reads off
@@ -407,6 +443,29 @@ interface MessageSendEnding {
407
443
  */
408
444
  readonly unsettledName: UnsettledName;
409
445
  }
446
+ /**
447
+ * A call that hands a payload to one deployed unit, named on the call.
448
+ *
449
+ * The unit's name is read off the call's own input rather than off a
450
+ * message, because there is one callee per call however many messages a
451
+ * send takes. A name written as a fully-qualified cloud id is reduced
452
+ * to the resource segment, so an ARN and a bare name come out the same.
453
+ */
454
+ interface UnitInvokeEnding {
455
+ readonly yields: "unitInvoke";
456
+ /** The platform running the unit, in the IR's deployable-unit words. */
457
+ readonly platform: DeployableUnit["deploymentTarget"];
458
+ /** The properties the call may name the unit on, tried in order. */
459
+ readonly named: readonly string[];
460
+ /** The property the call states its payload on, when the pack can say. */
461
+ readonly payload?: string;
462
+ /**
463
+ * What a reader gives back for a name nothing in the source settles.
464
+ * A function name usually arrives through an env var, whose name both
465
+ * sides agree on, so `"reference"` keeps it.
466
+ */
467
+ readonly unsettledName: UnsettledName;
468
+ }
410
469
  /**
411
470
  * A pack's declaration for one kind of call: the links, the ending, and
412
471
  * a line of code it matches.
@@ -418,4 +477,4 @@ interface Chain<TMeaning = MethodMeaning> {
418
477
  readonly example: string | null;
419
478
  }
420
479
 
421
- export type { ArgumentPick as A, ToReceiver as B, Chain as C, Ending as E, FromReceiver as F, InterpolatesLink as I, KindAsAsked as K, LinkFunction as L, MethodMeaning as M, OneArgument as O, SqlMethod as S, ToArgument as T, MessageSendMethod as a, MessageLocation as b, ChannelPart as c, CallStep as d, StorageMethod as e, AccessKind as f, AccessPathLink as g, ArgumentsFrom as h, ContainerArgument as i, ContainerLink as j, ContainerRule as k, ContainersLink as l, InputLink as m, InputRule as n, Link as o, ManyIn as p, MatchStart as q, MessageSendEnding as r, MethodsLink as s, OneMessage as t, SqlEnding as u, StartLink as v, StatedInputs as w, StatedRule as x, StorageEnding as y, SubjectLink as z };
480
+ export type { ArgumentPick as A, StorageEnding as B, Chain as C, SubjectLink as D, Ending as E, FromReceiver as F, ToReceiver as G, UnitInvokeEnding as H, InterpolatesLink as I, KindAsAsked as K, LinkFunction as L, MethodMeaning as M, OneArgument as O, SqlMethod as S, ToArgument as T, UnitInvokeMethod as U, MessageSendMethod as a, MessageLocation as b, ChannelPart as c, CallStep as d, StorageMethod as e, AccessKind as f, AccessPathLink as g, ArgumentsFrom as h, CallsLink as i, ContainerArgument as j, ContainerLink as k, ContainerRule as l, ContainersLink as m, InputLink as n, InputRule as o, Link as p, ManyIn as q, MatchStart as r, MessageSendEnding as s, MethodsLink as t, OneMessage as u, SelectorParamPick as v, SqlEnding as w, StartLink as x, StatedInputs as y, StatedRule as z };
@@ -1,4 +1,4 @@
1
- import { MessageBusSemantics } from '@suss/behavioral-ir';
1
+ import { MessageBusSemantics, DeployableUnit } from '@suss/behavioral-ir';
2
2
  import { ReceiverOrigin, ValueOps, CallOps, UnsettledName } from '@suss/extractor';
3
3
 
4
4
  /**
@@ -136,6 +136,13 @@ interface AccessPathLink {
136
136
  interface ContainersLink {
137
137
  readonly asks: "containers";
138
138
  readonly in: OneArgument;
139
+ /**
140
+ * What one of them is. An entry keys the container by name and says
141
+ * what the call did there, which is how a batch write is written. A
142
+ * name is the container on its own, which is how a call that reads
143
+ * several parameters at once is written. Defaults to an entry.
144
+ */
145
+ readonly each?: "entry" | "name";
139
146
  }
140
147
  /**
141
148
  * Where a call states its inputs, when it states them as one object
@@ -165,12 +172,30 @@ interface InterpolatesLink {
165
172
  readonly from?: ReceiverOrigin;
166
173
  }
167
174
  /** One question in a chain, and the answer the pack gave for it. */
168
- type Link<TMeaning> = StartLink | SubjectLink | MethodsLink<TMeaning> | ContainerLink | AccessPathLink | ContainersLink | InputLink | InterpolatesLink;
175
+ type Link<TMeaning> = StartLink | SubjectLink | MethodsLink<TMeaning> | CallsLink<TMeaning> | ContainerLink | AccessPathLink | ContainersLink | InputLink | InterpolatesLink;
176
+ /**
177
+ * The meaning of a bare call of the tracked client itself. A store
178
+ * hook is the case: `useAppStore((s) => s.bears)` reaches for no
179
+ * method, so there is no name for a methods table to list; the call
180
+ * matches by what its callee was written as.
181
+ */
182
+ interface CallsLink<TMeaning> {
183
+ readonly asks: "calls";
184
+ readonly meaning: TMeaning;
185
+ }
169
186
  /**
170
187
  * Which argument or arguments say what a call reached, and where to go
171
188
  * looking. Without `of` the argument belongs to the chain's subject.
172
189
  */
173
190
  type ArgumentPick = OneArgument | ArgumentsFrom;
191
+ /**
192
+ * The fields a selector lambda reads off its parameter, one per
193
+ * distinct first segment: `(s) => s.bears.count` reads `bears`. The
194
+ * value says which argument the lambda is passed as.
195
+ */
196
+ interface SelectorParamPick {
197
+ readonly selectorParam: number;
198
+ }
174
199
  /** The argument in one position. */
175
200
  interface OneArgument {
176
201
  /** The steps to the call the argument belongs to. */
@@ -261,13 +286,13 @@ interface StorageMethod {
261
286
  * wrote, or the plain list a method whose own name settles it states
262
287
  * outright: `findById` picks by `_id` however the id is spelt.
263
288
  */
264
- readonly selector?: readonly string[] | ArgumentPick | InputRule | StatedRule;
289
+ readonly selector?: readonly string[] | ArgumentPick | SelectorParamPick | InputRule | StatedRule;
265
290
  /**
266
291
  * Which fields the call touched, said the same three ways. A delete
267
292
  * touches the whole document and states `["*"]`, since nothing in its
268
293
  * arguments says so.
269
294
  */
270
- readonly fields?: readonly string[] | ArgumentPick | InputRule | StatedRule;
295
+ readonly fields?: readonly string[] | ArgumentPick | SelectorParamPick | InputRule | StatedRule;
271
296
  }
272
297
  /**
273
298
  * What one method of a client that takes a statement written as SQL
@@ -289,9 +314,15 @@ interface MessageSendMethod {
289
314
  /** Where the call states the message, or the collection of them. */
290
315
  readonly input: OneArgument;
291
316
  }
317
+ /**
318
+ * What one invoke method does. An invoke states its whole request as
319
+ * one object the same way a send does, so both say where that object
320
+ * is and nothing else.
321
+ */
322
+ type UnitInvokeMethod = MessageSendMethod;
292
323
  type MethodMeaning = StorageMethod | SqlMethod | MessageSendMethod;
293
324
  /** What a chain produces when every link matches. */
294
- type Ending = StorageEnding | SqlEnding | MessageSendEnding;
325
+ type Ending = StorageEnding | SqlEnding | MessageSendEnding | UnitInvokeEnding;
295
326
  /** A storage access: one call, one thing it read or wrote. */
296
327
  interface StorageEnding {
297
328
  readonly yields: "storageAccess";
@@ -359,8 +390,13 @@ interface ManyIn {
359
390
  * the same way, so a pack states the parts in the order they join.
360
391
  */
361
392
  interface ChannelPart {
362
- /** The property the message states this part on. */
363
- readonly property: string;
393
+ /**
394
+ * The properties the message may state this part on, tried in order.
395
+ * A library that lets one destination be written more than one way
396
+ * lists each spelling: an SNS publish writes its topic as `TopicArn`
397
+ * or as `TargetArn`, and the two are the same part of the channel.
398
+ */
399
+ readonly property: readonly string[];
364
400
  /**
365
401
  * Where the property is written. A batch command states the queue
366
402
  * once beside the list of messages, so its channel part reads off
@@ -407,6 +443,29 @@ interface MessageSendEnding {
407
443
  */
408
444
  readonly unsettledName: UnsettledName;
409
445
  }
446
+ /**
447
+ * A call that hands a payload to one deployed unit, named on the call.
448
+ *
449
+ * The unit's name is read off the call's own input rather than off a
450
+ * message, because there is one callee per call however many messages a
451
+ * send takes. A name written as a fully-qualified cloud id is reduced
452
+ * to the resource segment, so an ARN and a bare name come out the same.
453
+ */
454
+ interface UnitInvokeEnding {
455
+ readonly yields: "unitInvoke";
456
+ /** The platform running the unit, in the IR's deployable-unit words. */
457
+ readonly platform: DeployableUnit["deploymentTarget"];
458
+ /** The properties the call may name the unit on, tried in order. */
459
+ readonly named: readonly string[];
460
+ /** The property the call states its payload on, when the pack can say. */
461
+ readonly payload?: string;
462
+ /**
463
+ * What a reader gives back for a name nothing in the source settles.
464
+ * A function name usually arrives through an env var, whose name both
465
+ * sides agree on, so `"reference"` keeps it.
466
+ */
467
+ readonly unsettledName: UnsettledName;
468
+ }
410
469
  /**
411
470
  * A pack's declaration for one kind of call: the links, the ending, and
412
471
  * a line of code it matches.
@@ -418,4 +477,4 @@ interface Chain<TMeaning = MethodMeaning> {
418
477
  readonly example: string | null;
419
478
  }
420
479
 
421
- export type { ArgumentPick as A, ToReceiver as B, Chain as C, Ending as E, FromReceiver as F, InterpolatesLink as I, KindAsAsked as K, LinkFunction as L, MethodMeaning as M, OneArgument as O, SqlMethod as S, ToArgument as T, MessageSendMethod as a, MessageLocation as b, ChannelPart as c, CallStep as d, StorageMethod as e, AccessKind as f, AccessPathLink as g, ArgumentsFrom as h, ContainerArgument as i, ContainerLink as j, ContainerRule as k, ContainersLink as l, InputLink as m, InputRule as n, Link as o, ManyIn as p, MatchStart as q, MessageSendEnding as r, MethodsLink as s, OneMessage as t, SqlEnding as u, StartLink as v, StatedInputs as w, StatedRule as x, StorageEnding as y, SubjectLink as z };
480
+ export type { ArgumentPick as A, StorageEnding as B, Chain as C, SubjectLink as D, Ending as E, FromReceiver as F, ToReceiver as G, UnitInvokeEnding as H, InterpolatesLink as I, KindAsAsked as K, LinkFunction as L, MethodMeaning as M, OneArgument as O, SqlMethod as S, ToArgument as T, UnitInvokeMethod as U, MessageSendMethod as a, MessageLocation as b, ChannelPart as c, CallStep as d, StorageMethod as e, AccessKind as f, AccessPathLink as g, ArgumentsFrom as h, CallsLink as i, ContainerArgument as j, ContainerLink as k, ContainerRule as l, ContainersLink as m, InputLink as n, InputRule as o, Link as p, ManyIn as q, MatchStart as r, MessageSendEnding as s, MethodsLink as t, OneMessage as u, SelectorParamPick as v, SqlEnding as w, StartLink as x, StatedInputs as y, StatedRule as z };
package/dist/index.cjs CHANGED
@@ -30,7 +30,8 @@ __export(index_exports, {
30
30
  pack: () => pack,
31
31
  runExamples: () => runExamples,
32
32
  sqlStatements: () => sqlStatements,
33
- storageCalls: () => storageCalls
33
+ storageCalls: () => storageCalls,
34
+ unitInvokes: () => unitInvokes
34
35
  });
35
36
  module.exports = __toCommonJS(index_exports);
36
37
 
@@ -64,8 +65,22 @@ var MAX_RECEIVER_HOPS = 8;
64
65
  function compile(chain, recognition) {
65
66
  return (_call, ctx) => {
66
67
  const ops = opsIn(ctx);
68
+ if (ops === null) {
69
+ return null;
70
+ }
71
+ const bare = bareCall(chain, ops);
72
+ if (bare !== null) {
73
+ return YIELD[chain.ending.yields]({
74
+ ops,
75
+ subject: ops,
76
+ method: bare.method,
77
+ meaning: bare.meaning,
78
+ chain,
79
+ recognition
80
+ });
81
+ }
67
82
  const methods = methodsIn(chain);
68
- if (ops === null || methods === null) {
83
+ if (methods === null) {
69
84
  return null;
70
85
  }
71
86
  for (const subject of subjectsOf(chain, ops)) {
@@ -90,6 +105,21 @@ function linkIn(chain, asks) {
90
105
  const link = chain.links.find((candidate) => candidate.asks === asks);
91
106
  return link ?? null;
92
107
  }
108
+ function bareCall(chain, ops) {
109
+ const link = linkIn(chain, "calls");
110
+ const start = linkIn(chain, "start");
111
+ if (link === null || start === null || start.at.starts !== "receiver") {
112
+ return null;
113
+ }
114
+ if (ops.method() !== null || ops.namedCallee?.() !== true) {
115
+ return null;
116
+ }
117
+ const written = ops.callee();
118
+ if (written === null || !written.isFrom(start.at.origin)) {
119
+ return null;
120
+ }
121
+ return { method: ops.calleeText(), meaning: link.meaning };
122
+ }
93
123
  function methodsIn(chain) {
94
124
  return linkIn(chain, "methods");
95
125
  }
@@ -163,7 +193,8 @@ function startsHere(chain, subject) {
163
193
  var YIELD = {
164
194
  storageAccess,
165
195
  sqlAccess,
166
- messageSend
196
+ messageSend,
197
+ unitInvoke
167
198
  };
168
199
  function storageAccess(matched) {
169
200
  const { subject, chain } = matched;
@@ -194,7 +225,7 @@ function accessEffect(matched, access) {
194
225
  storageSystem: ending.system,
195
226
  ...ending.transport === void 0 ? {} : { transport: ending.transport },
196
227
  scope: ending.scope,
197
- container: reached.entry === null ? containerOf(chain, selector, subject, unsettled) : reached.container,
228
+ container: reached.stated ? reached.container : containerOf(chain, selector, subject, unsettled),
198
229
  accessPath: accessPathOf(chain, subject, unsettled)
199
230
  }),
200
231
  callee: ops.calleeText(),
@@ -209,11 +240,24 @@ function accessEffect(matched, access) {
209
240
  }
210
241
  function reachedBy(chain, subject, unsettled) {
211
242
  const link = linkIn(chain, "containers");
212
- const map = link === null ? null : statedValue(subject, link.in);
213
- if (map === null) {
214
- return [{ container: null, entry: null }];
215
- }
216
- return map.entries(unsettled).map((entry) => ({ container: entry.key, entry: entry.value }));
243
+ const alone = [{ container: null, entry: null, stated: false }];
244
+ const stated = link === null ? null : statedValue(subject, link.in);
245
+ if (link === null || stated === null) {
246
+ return alone;
247
+ }
248
+ if (link.each === "name") {
249
+ const named = stated.items().map((item) => ({
250
+ container: item.name(unsettled),
251
+ entry: null,
252
+ stated: true
253
+ }));
254
+ return named.length === 0 ? alone : named;
255
+ }
256
+ return stated.entries(unsettled).map((entry) => ({
257
+ container: entry.key,
258
+ entry: entry.value,
259
+ stated: true
260
+ }));
217
261
  }
218
262
  function kindOf(kind, subject, unsettled) {
219
263
  if (typeof kind === "string") {
@@ -278,6 +322,9 @@ function namesFor(says, subject, stated, unsettled) {
278
322
  const input2 = stated.input;
279
323
  return input2 === null ? [] : [...says({ ...stated, input: input2 })];
280
324
  }
325
+ if ("selectorParam" in says) {
326
+ return [...subject.parameterReadsAt?.(says.selectorParam) ?? []];
327
+ }
281
328
  const pointed = says;
282
329
  if (!("by" in pointed)) {
283
330
  return namesAt(subject, pointed, unsettled);
@@ -350,7 +397,7 @@ function channelOf(message, input, ending) {
350
397
  const parts = [];
351
398
  for (const part of ending.channel) {
352
399
  const holder = part.on === "theInput" ? input : message;
353
- const written = holder.property(part.property);
400
+ const written = firstWritten(holder, part.property);
354
401
  if (written === null) {
355
402
  if (part.whenAbsent === void 0) {
356
403
  return null;
@@ -366,6 +413,15 @@ function channelOf(message, input, ending) {
366
413
  }
367
414
  return parts.join(ending.channelSeparator ?? "#");
368
415
  }
416
+ function firstWritten(holder, properties) {
417
+ for (const property of properties) {
418
+ const written = holder.property(property);
419
+ if (written !== null) {
420
+ return written;
421
+ }
422
+ }
423
+ return null;
424
+ }
369
425
  function bodyOf(message, ending) {
370
426
  if (ending.body === void 0) {
371
427
  return {};
@@ -382,6 +438,50 @@ function routingKeyOf(message, ending) {
382
438
  const stated = message.property(ending.routingKey)?.text() ?? null;
383
439
  return stated === null || stated === "" ? {} : { routingKey: stated };
384
440
  }
441
+ function unitInvoke(matched) {
442
+ const { ops, subject, chain, recognition } = matched;
443
+ const ending = chain.ending;
444
+ const input = statedValue(
445
+ subject,
446
+ matched.meaning.input
447
+ );
448
+ if (input === null) {
449
+ return null;
450
+ }
451
+ return [
452
+ {
453
+ type: "interaction",
454
+ binding: (0, import_behavioral_ir.unitInvocationBinding)({
455
+ recognition,
456
+ deploymentTarget: ending.platform,
457
+ instanceName: invokedUnitIn(input, ending)
458
+ }),
459
+ callee: ops.calleeText(),
460
+ interaction: {
461
+ class: "unit-invoke",
462
+ ...payloadOf(input, ending)
463
+ }
464
+ }
465
+ ];
466
+ }
467
+ function invokedUnitIn(input, ending) {
468
+ for (const property of ending.named) {
469
+ const stated = input.property(property)?.name(ending.unsettledName) ?? null;
470
+ if (stated !== null && stated !== "") {
471
+ return (0, import_behavioral_ir.resourceNameIn)(stated);
472
+ }
473
+ }
474
+ return null;
475
+ }
476
+ function payloadOf(input, ending) {
477
+ if (ending.payload === void 0) {
478
+ return {};
479
+ }
480
+ const stated = (0, import_extractor.unwrapJsonStringify)(
481
+ input.property(ending.payload)?.asArg() ?? null
482
+ );
483
+ return stated === null ? {} : { payload: stated };
484
+ }
385
485
  function sqlAccess(matched) {
386
486
  const { ops, subject, method, chain, recognition } = matched;
387
487
  const ending = chain.ending;
@@ -485,7 +585,8 @@ function chainFrom(declared) {
485
585
  var DISPATCHED_ON = {
486
586
  storageAccess: "invocation",
487
587
  sqlAccess: "access",
488
- messageSend: "invocation"
588
+ messageSend: "invocation",
589
+ unitInvoke: "invocation"
489
590
  };
490
591
  function walkedBy(chains, walk2, recognizedAs) {
491
592
  return chains.filter((chain) => DISPATCHED_ON[chain.ending.yields] === walk2).map((chain) => compile(chain, recognizedAs));
@@ -509,6 +610,7 @@ function pack(name, matches, spec) {
509
610
  ...spec.recognizers ?? []
510
611
  ],
511
612
  ...access.length === 0 ? {} : { accessRecognizers: access },
613
+ ...spec.projectHelpers === void 0 ? {} : { projectHelpers: spec.projectHelpers },
512
614
  declarations: declarationsIn(matches)
513
615
  };
514
616
  }
@@ -519,10 +621,19 @@ function wireOf(ending) {
519
621
  if (ending.yields === "messageSend") {
520
622
  return ending.wire;
521
623
  }
624
+ if (ending.yields === "unitInvoke") {
625
+ return ending.platform;
626
+ }
522
627
  return ending.transport ?? ending.system;
523
628
  }
524
629
  function declaredName(ending) {
525
- return ending.yields === "messageSend" ? ending.wire : ending.system;
630
+ if (ending.yields === "messageSend") {
631
+ return ending.wire;
632
+ }
633
+ if (ending.yields === "unitInvoke") {
634
+ return ending.platform;
635
+ }
636
+ return ending.system;
526
637
  }
527
638
  function protocolOf(chains) {
528
639
  const wires = new Set(chains.map((chain) => wireOf(chain.ending)));
@@ -591,8 +702,15 @@ function rulesIn(chain) {
591
702
  const link = chain.links.find(
592
703
  (candidate) => candidate.asks === "methods"
593
704
  );
705
+ const calls = chain.links.find(
706
+ (candidate) => candidate.asks === "calls"
707
+ );
708
+ const meanings = [
709
+ ...Object.values(link?.table ?? {}),
710
+ ...calls === void 0 ? [] : [calls.meaning]
711
+ ];
594
712
  const written = /* @__PURE__ */ new Map();
595
- for (const method of Object.values(link?.table ?? {})) {
713
+ for (const method of meanings) {
596
714
  for (const asks of ["selector", "fields"]) {
597
715
  const rule = ruleFor(method[asks]);
598
716
  if (rule !== null) {
@@ -672,15 +790,48 @@ function chainFrom3(declared) {
672
790
  table,
673
791
  ignoringCase: options?.ignoringCase ?? false
674
792
  }),
793
+ calls: (meaning) => adding({ asks: "calls", meaning }),
675
794
  container: (says) => adding(
676
795
  typeof says === "function" ? { asks: "container", from: says } : { asks: "container", argument: says }
677
796
  ),
678
797
  accessPath: (says) => adding({ asks: "accessPath", argument: says }),
679
- containersIn: (says) => adding({ asks: "containers", in: says }),
798
+ containersIn: (says, options) => adding({
799
+ asks: "containers",
800
+ in: says,
801
+ each: options?.each ?? "entry"
802
+ }),
680
803
  input: (says) => adding({ asks: "input", at: says }),
681
804
  example: (code) => chainFrom3({ ...declared, example: code })
682
805
  };
683
806
  }
807
+
808
+ // src/unitInvokes.ts
809
+ function unitInvokes(spec) {
810
+ const ending = {
811
+ yields: "unitInvoke",
812
+ platform: spec.platform,
813
+ named: spec.named,
814
+ ...spec.payload === void 0 ? {} : { payload: spec.payload },
815
+ unsettledName: spec.unsettledName ?? "reference"
816
+ };
817
+ return chainFrom4({
818
+ links: chainStart(spec.client),
819
+ ending,
820
+ example: null
821
+ });
822
+ }
823
+ function chainFrom4(declared) {
824
+ const adding = (link) => chainFrom4({ ...declared, links: [...declared.links, link] });
825
+ return {
826
+ declared,
827
+ methods: (table, options) => adding({
828
+ asks: "methods",
829
+ table,
830
+ ignoringCase: options?.ignoringCase ?? false
831
+ }),
832
+ example: (code) => chainFrom4({ ...declared, example: code })
833
+ };
834
+ }
684
835
  // Annotate the CommonJS export names for ESM import in node:
685
836
  0 && (module.exports = {
686
837
  compile,
@@ -693,5 +844,6 @@ function chainFrom3(declared) {
693
844
  pack,
694
845
  runExamples,
695
846
  sqlStatements,
696
- storageCalls
847
+ storageCalls,
848
+ unitInvokes
697
849
  });
package/dist/index.d.cts CHANGED
@@ -1,8 +1,8 @@
1
- import { ConstructedFrom, DeclaredBy, CallOps, InvocationRecognizer, PatternPack, UnsettledName, ReceiverOrigin, PackDeclarations } from '@suss/extractor';
2
- export { CallOps, ConstructedFrom, DeclaredBy, DeclaredMatch, OpsCarrier, PackDeclarations, PatternPack, 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 } from './chain-C7w99UMv.cjs';
4
- export { f as AccessKind, g as AccessPathLink, h as ArgumentsFrom, i as ContainerArgument, j as ContainerLink, k as ContainerRule, l as ContainersLink, E as Ending, F as FromReceiver, m as InputLink, n as InputRule, K as KindAsAsked, o as Link, p as ManyIn, q as MatchStart, r as MessageSendEnding, s as MethodsLink, t as OneMessage, u as SqlEnding, v as StartLink, w as StatedInputs, x as StatedRule, y as StorageEnding, z as SubjectLink, T as ToArgument, B as ToReceiver } from './chain-C7w99UMv.cjs';
5
- import { Effect, MessageBusSemantics } from '@suss/behavioral-ir';
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-IogmSi0t.cjs';
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 SelectorParamPick, w as SqlEnding, x as StartLink, y as StatedInputs, z as StatedRule, B as StorageEnding, D as SubjectLink, T as ToArgument, G as ToReceiver, H as UnitInvokeEnding } from './chain-IogmSi0t.cjs';
5
+ import { Effect, MessageBusSemantics, DeployableUnit } from '@suss/behavioral-ir';
6
6
 
7
7
  /**
8
8
  * The small builders a pack writes its origins with, and the vocabulary
@@ -188,6 +188,12 @@ interface PackSpec {
188
188
  * function links, so the cost of staying here is visible.
189
189
  */
190
190
  recognizers?: InvocationRecognizer[];
191
+ /**
192
+ * Functions the project wrote in front of this library, read once
193
+ * across the project before extraction. What the pack makes of them
194
+ * joins the chains above for the rest of the run.
195
+ */
196
+ projectHelpers?: ProjectHelpers;
191
197
  }
192
198
  /** A pack, assembled from the calls it says it matches. */
193
199
  declare function pack(name: string, matches: readonly Match[], spec: PackSpec): PatternPack;
@@ -287,6 +293,12 @@ interface StorageCalls {
287
293
  methods(table: Readonly<Record<string, StorageMethod>>, options?: {
288
294
  ignoringCase?: boolean;
289
295
  }): StorageCalls;
296
+ /**
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.
300
+ */
301
+ calls(meaning: StorageMethod): StorageCalls;
290
302
  /**
291
303
  * Which container a call's selector belongs to, as an argument or as
292
304
  * the pack's own rule. Without this the first name a call reached is
@@ -297,9 +309,12 @@ interface StorageCalls {
297
309
  accessPath(says: ArgumentPick): StorageCalls;
298
310
  /**
299
311
  * Where a call that reaches several containers at once states them.
300
- * Each entry becomes one effect, against the container its key says.
312
+ * Each entry becomes one effect, against the container its key says,
313
+ * or against the name itself where the call lists names alone.
301
314
  */
302
- containersIn(says: OneArgument): StorageCalls;
315
+ containersIn(says: OneArgument, options?: {
316
+ each?: "entry" | "name";
317
+ }): StorageCalls;
303
318
  /**
304
319
  * Where a call states its inputs, when it states them as one object.
305
320
  * A call that states none does not match, and a rule the pack wrote
@@ -314,4 +329,46 @@ interface StorageCalls {
314
329
  /** A pack that recognizes calls against a store. */
315
330
  declare function storageCalls(spec: StorageCallsSpec): StorageCalls;
316
331
 
317
- export { ArgumentPick, CallStep, Chain, ChannelPart, InterpolatesLink, LinkFunction, type Match, MessageLocation, MessageSendMethod, type MessageSends, type MessageSendsSpec, MethodMeaning, OneArgument, type PackSpec, type RanExample, type RunOverCode, SqlMethod, type SqlStatements, type SqlStatementsSpec, type StorageCalls, type StorageCallsSpec, StorageMethod, compile, constructedFrom, declarationsIn, declaredBy, examplesMissing, messageSends, opsIn, pack, runExamples, sqlStatements, storageCalls };
332
+ /**
333
+ * unitInvokes.ts: a pack that recognizes a call invoking one deployed
334
+ * unit by name.
335
+ *
336
+ * The AWS SDK shape is the one this was written against, and it is the
337
+ * same shape a send has: an operation is a command class and its
338
+ * arguments are one object, so the pack says which command and which of
339
+ * its properties says which unit the call reaches.
340
+ *
341
+ * client.send(new InvokeCommand({ FunctionName, Payload }))
342
+ */
343
+
344
+ interface UnitInvokesSpec {
345
+ /** The platform running the unit, in the IR's deployable-unit words. */
346
+ platform: DeployableUnit["deploymentTarget"];
347
+ /** The properties the call may name the unit on, tried in order. */
348
+ named: readonly string[];
349
+ /** The property the call states its payload on. */
350
+ payload?: string;
351
+ /**
352
+ * What a reader gives back for a name nothing settles. Defaults to
353
+ * keeping the reference, since a function name reaches the code
354
+ * through an env var far more often than it is written out.
355
+ */
356
+ unsettledName?: UnsettledName;
357
+ /** How the pack pins down the client its calls are on. */
358
+ client?: ReceiverOrigin;
359
+ }
360
+ /** A chain over invokes, and the links that can still be added. */
361
+ interface UnitInvokes {
362
+ /** Which methods invoke, and where each one states its request. */
363
+ methods(table: Readonly<Record<string, UnitInvokeMethod>>, options?: {
364
+ ignoringCase?: boolean;
365
+ }): UnitInvokes;
366
+ /** A line of code this matches, which the pack's tests run. */
367
+ example(code: string): UnitInvokes;
368
+ /** The links and the ending, as data. */
369
+ readonly declared: Chain<UnitInvokeMethod>;
370
+ }
371
+ /** A pack that recognizes a call invoking a deployed unit. */
372
+ declare function unitInvokes(spec: UnitInvokesSpec): UnitInvokes;
373
+
374
+ export { ArgumentPick, CallStep, Chain, ChannelPart, InterpolatesLink, LinkFunction, type Match, MessageLocation, MessageSendMethod, type MessageSends, type MessageSendsSpec, MethodMeaning, OneArgument, type PackSpec, type RanExample, type RunOverCode, SqlMethod, type SqlStatements, type SqlStatementsSpec, type StorageCalls, type StorageCallsSpec, StorageMethod, UnitInvokeMethod, type UnitInvokes, type UnitInvokesSpec, compile, constructedFrom, declarationsIn, declaredBy, examplesMissing, messageSends, opsIn, pack, runExamples, sqlStatements, storageCalls, unitInvokes };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { ConstructedFrom, DeclaredBy, CallOps, InvocationRecognizer, PatternPack, UnsettledName, ReceiverOrigin, PackDeclarations } from '@suss/extractor';
2
- export { CallOps, ConstructedFrom, DeclaredBy, DeclaredMatch, OpsCarrier, PackDeclarations, PatternPack, 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 } from './chain-C7w99UMv.js';
4
- export { f as AccessKind, g as AccessPathLink, h as ArgumentsFrom, i as ContainerArgument, j as ContainerLink, k as ContainerRule, l as ContainersLink, E as Ending, F as FromReceiver, m as InputLink, n as InputRule, K as KindAsAsked, o as Link, p as ManyIn, q as MatchStart, r as MessageSendEnding, s as MethodsLink, t as OneMessage, u as SqlEnding, v as StartLink, w as StatedInputs, x as StatedRule, y as StorageEnding, z as SubjectLink, T as ToArgument, B as ToReceiver } from './chain-C7w99UMv.js';
5
- import { Effect, MessageBusSemantics } from '@suss/behavioral-ir';
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-IogmSi0t.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 SelectorParamPick, w as SqlEnding, x as StartLink, y as StatedInputs, z as StatedRule, B as StorageEnding, D as SubjectLink, T as ToArgument, G as ToReceiver, H as UnitInvokeEnding } from './chain-IogmSi0t.js';
5
+ import { Effect, MessageBusSemantics, DeployableUnit } from '@suss/behavioral-ir';
6
6
 
7
7
  /**
8
8
  * The small builders a pack writes its origins with, and the vocabulary
@@ -188,6 +188,12 @@ interface PackSpec {
188
188
  * function links, so the cost of staying here is visible.
189
189
  */
190
190
  recognizers?: InvocationRecognizer[];
191
+ /**
192
+ * Functions the project wrote in front of this library, read once
193
+ * across the project before extraction. What the pack makes of them
194
+ * joins the chains above for the rest of the run.
195
+ */
196
+ projectHelpers?: ProjectHelpers;
191
197
  }
192
198
  /** A pack, assembled from the calls it says it matches. */
193
199
  declare function pack(name: string, matches: readonly Match[], spec: PackSpec): PatternPack;
@@ -287,6 +293,12 @@ interface StorageCalls {
287
293
  methods(table: Readonly<Record<string, StorageMethod>>, options?: {
288
294
  ignoringCase?: boolean;
289
295
  }): StorageCalls;
296
+ /**
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.
300
+ */
301
+ calls(meaning: StorageMethod): StorageCalls;
290
302
  /**
291
303
  * Which container a call's selector belongs to, as an argument or as
292
304
  * the pack's own rule. Without this the first name a call reached is
@@ -297,9 +309,12 @@ interface StorageCalls {
297
309
  accessPath(says: ArgumentPick): StorageCalls;
298
310
  /**
299
311
  * Where a call that reaches several containers at once states them.
300
- * Each entry becomes one effect, against the container its key says.
312
+ * Each entry becomes one effect, against the container its key says,
313
+ * or against the name itself where the call lists names alone.
301
314
  */
302
- containersIn(says: OneArgument): StorageCalls;
315
+ containersIn(says: OneArgument, options?: {
316
+ each?: "entry" | "name";
317
+ }): StorageCalls;
303
318
  /**
304
319
  * Where a call states its inputs, when it states them as one object.
305
320
  * A call that states none does not match, and a rule the pack wrote
@@ -314,4 +329,46 @@ interface StorageCalls {
314
329
  /** A pack that recognizes calls against a store. */
315
330
  declare function storageCalls(spec: StorageCallsSpec): StorageCalls;
316
331
 
317
- export { ArgumentPick, CallStep, Chain, ChannelPart, InterpolatesLink, LinkFunction, type Match, MessageLocation, MessageSendMethod, type MessageSends, type MessageSendsSpec, MethodMeaning, OneArgument, type PackSpec, type RanExample, type RunOverCode, SqlMethod, type SqlStatements, type SqlStatementsSpec, type StorageCalls, type StorageCallsSpec, StorageMethod, compile, constructedFrom, declarationsIn, declaredBy, examplesMissing, messageSends, opsIn, pack, runExamples, sqlStatements, storageCalls };
332
+ /**
333
+ * unitInvokes.ts: a pack that recognizes a call invoking one deployed
334
+ * unit by name.
335
+ *
336
+ * The AWS SDK shape is the one this was written against, and it is the
337
+ * same shape a send has: an operation is a command class and its
338
+ * arguments are one object, so the pack says which command and which of
339
+ * its properties says which unit the call reaches.
340
+ *
341
+ * client.send(new InvokeCommand({ FunctionName, Payload }))
342
+ */
343
+
344
+ interface UnitInvokesSpec {
345
+ /** The platform running the unit, in the IR's deployable-unit words. */
346
+ platform: DeployableUnit["deploymentTarget"];
347
+ /** The properties the call may name the unit on, tried in order. */
348
+ named: readonly string[];
349
+ /** The property the call states its payload on. */
350
+ payload?: string;
351
+ /**
352
+ * What a reader gives back for a name nothing settles. Defaults to
353
+ * keeping the reference, since a function name reaches the code
354
+ * through an env var far more often than it is written out.
355
+ */
356
+ unsettledName?: UnsettledName;
357
+ /** How the pack pins down the client its calls are on. */
358
+ client?: ReceiverOrigin;
359
+ }
360
+ /** A chain over invokes, and the links that can still be added. */
361
+ interface UnitInvokes {
362
+ /** Which methods invoke, and where each one states its request. */
363
+ methods(table: Readonly<Record<string, UnitInvokeMethod>>, options?: {
364
+ ignoringCase?: boolean;
365
+ }): UnitInvokes;
366
+ /** A line of code this matches, which the pack's tests run. */
367
+ example(code: string): UnitInvokes;
368
+ /** The links and the ending, as data. */
369
+ readonly declared: Chain<UnitInvokeMethod>;
370
+ }
371
+ /** A pack that recognizes a call invoking a deployed unit. */
372
+ declare function unitInvokes(spec: UnitInvokesSpec): UnitInvokes;
373
+
374
+ export { ArgumentPick, CallStep, Chain, ChannelPart, InterpolatesLink, LinkFunction, type Match, MessageLocation, MessageSendMethod, type MessageSends, type MessageSendsSpec, MethodMeaning, OneArgument, type PackSpec, type RanExample, type RunOverCode, SqlMethod, type SqlStatements, type SqlStatementsSpec, type StorageCalls, type StorageCallsSpec, StorageMethod, UnitInvokeMethod, type UnitInvokes, type UnitInvokesSpec, compile, constructedFrom, declarationsIn, declaredBy, examplesMissing, messageSends, opsIn, pack, runExamples, sqlStatements, storageCalls, unitInvokes };
package/dist/index.js CHANGED
@@ -1,5 +1,10 @@
1
1
  // src/compile.ts
2
- import { messageBusBinding, storageBinding } from "@suss/behavioral-ir";
2
+ import {
3
+ messageBusBinding,
4
+ resourceNameIn,
5
+ storageBinding,
6
+ unitInvocationBinding
7
+ } from "@suss/behavioral-ir";
3
8
  import { unwrapJsonStringify } from "@suss/extractor";
4
9
  import { readSqlAccess, sqlFromParts } from "@suss/sql";
5
10
 
@@ -28,8 +33,22 @@ var MAX_RECEIVER_HOPS = 8;
28
33
  function compile(chain, recognition) {
29
34
  return (_call, ctx) => {
30
35
  const ops = opsIn(ctx);
36
+ if (ops === null) {
37
+ return null;
38
+ }
39
+ const bare = bareCall(chain, ops);
40
+ if (bare !== null) {
41
+ return YIELD[chain.ending.yields]({
42
+ ops,
43
+ subject: ops,
44
+ method: bare.method,
45
+ meaning: bare.meaning,
46
+ chain,
47
+ recognition
48
+ });
49
+ }
31
50
  const methods = methodsIn(chain);
32
- if (ops === null || methods === null) {
51
+ if (methods === null) {
33
52
  return null;
34
53
  }
35
54
  for (const subject of subjectsOf(chain, ops)) {
@@ -54,6 +73,21 @@ function linkIn(chain, asks) {
54
73
  const link = chain.links.find((candidate) => candidate.asks === asks);
55
74
  return link ?? null;
56
75
  }
76
+ function bareCall(chain, ops) {
77
+ const link = linkIn(chain, "calls");
78
+ const start = linkIn(chain, "start");
79
+ if (link === null || start === null || start.at.starts !== "receiver") {
80
+ return null;
81
+ }
82
+ if (ops.method() !== null || ops.namedCallee?.() !== true) {
83
+ return null;
84
+ }
85
+ const written = ops.callee();
86
+ if (written === null || !written.isFrom(start.at.origin)) {
87
+ return null;
88
+ }
89
+ return { method: ops.calleeText(), meaning: link.meaning };
90
+ }
57
91
  function methodsIn(chain) {
58
92
  return linkIn(chain, "methods");
59
93
  }
@@ -127,7 +161,8 @@ function startsHere(chain, subject) {
127
161
  var YIELD = {
128
162
  storageAccess,
129
163
  sqlAccess,
130
- messageSend
164
+ messageSend,
165
+ unitInvoke
131
166
  };
132
167
  function storageAccess(matched) {
133
168
  const { subject, chain } = matched;
@@ -158,7 +193,7 @@ function accessEffect(matched, access) {
158
193
  storageSystem: ending.system,
159
194
  ...ending.transport === void 0 ? {} : { transport: ending.transport },
160
195
  scope: ending.scope,
161
- container: reached.entry === null ? containerOf(chain, selector, subject, unsettled) : reached.container,
196
+ container: reached.stated ? reached.container : containerOf(chain, selector, subject, unsettled),
162
197
  accessPath: accessPathOf(chain, subject, unsettled)
163
198
  }),
164
199
  callee: ops.calleeText(),
@@ -173,11 +208,24 @@ function accessEffect(matched, access) {
173
208
  }
174
209
  function reachedBy(chain, subject, unsettled) {
175
210
  const link = linkIn(chain, "containers");
176
- const map = link === null ? null : statedValue(subject, link.in);
177
- if (map === null) {
178
- return [{ container: null, entry: null }];
179
- }
180
- return map.entries(unsettled).map((entry) => ({ container: entry.key, entry: entry.value }));
211
+ const alone = [{ container: null, entry: null, stated: false }];
212
+ const stated = link === null ? null : statedValue(subject, link.in);
213
+ if (link === null || stated === null) {
214
+ return alone;
215
+ }
216
+ if (link.each === "name") {
217
+ const named = stated.items().map((item) => ({
218
+ container: item.name(unsettled),
219
+ entry: null,
220
+ stated: true
221
+ }));
222
+ return named.length === 0 ? alone : named;
223
+ }
224
+ return stated.entries(unsettled).map((entry) => ({
225
+ container: entry.key,
226
+ entry: entry.value,
227
+ stated: true
228
+ }));
181
229
  }
182
230
  function kindOf(kind, subject, unsettled) {
183
231
  if (typeof kind === "string") {
@@ -242,6 +290,9 @@ function namesFor(says, subject, stated, unsettled) {
242
290
  const input2 = stated.input;
243
291
  return input2 === null ? [] : [...says({ ...stated, input: input2 })];
244
292
  }
293
+ if ("selectorParam" in says) {
294
+ return [...subject.parameterReadsAt?.(says.selectorParam) ?? []];
295
+ }
245
296
  const pointed = says;
246
297
  if (!("by" in pointed)) {
247
298
  return namesAt(subject, pointed, unsettled);
@@ -314,7 +365,7 @@ function channelOf(message, input, ending) {
314
365
  const parts = [];
315
366
  for (const part of ending.channel) {
316
367
  const holder = part.on === "theInput" ? input : message;
317
- const written = holder.property(part.property);
368
+ const written = firstWritten(holder, part.property);
318
369
  if (written === null) {
319
370
  if (part.whenAbsent === void 0) {
320
371
  return null;
@@ -330,6 +381,15 @@ function channelOf(message, input, ending) {
330
381
  }
331
382
  return parts.join(ending.channelSeparator ?? "#");
332
383
  }
384
+ function firstWritten(holder, properties) {
385
+ for (const property of properties) {
386
+ const written = holder.property(property);
387
+ if (written !== null) {
388
+ return written;
389
+ }
390
+ }
391
+ return null;
392
+ }
333
393
  function bodyOf(message, ending) {
334
394
  if (ending.body === void 0) {
335
395
  return {};
@@ -346,6 +406,50 @@ function routingKeyOf(message, ending) {
346
406
  const stated = message.property(ending.routingKey)?.text() ?? null;
347
407
  return stated === null || stated === "" ? {} : { routingKey: stated };
348
408
  }
409
+ function unitInvoke(matched) {
410
+ const { ops, subject, chain, recognition } = matched;
411
+ const ending = chain.ending;
412
+ const input = statedValue(
413
+ subject,
414
+ matched.meaning.input
415
+ );
416
+ if (input === null) {
417
+ return null;
418
+ }
419
+ return [
420
+ {
421
+ type: "interaction",
422
+ binding: unitInvocationBinding({
423
+ recognition,
424
+ deploymentTarget: ending.platform,
425
+ instanceName: invokedUnitIn(input, ending)
426
+ }),
427
+ callee: ops.calleeText(),
428
+ interaction: {
429
+ class: "unit-invoke",
430
+ ...payloadOf(input, ending)
431
+ }
432
+ }
433
+ ];
434
+ }
435
+ function invokedUnitIn(input, ending) {
436
+ for (const property of ending.named) {
437
+ const stated = input.property(property)?.name(ending.unsettledName) ?? null;
438
+ if (stated !== null && stated !== "") {
439
+ return resourceNameIn(stated);
440
+ }
441
+ }
442
+ return null;
443
+ }
444
+ function payloadOf(input, ending) {
445
+ if (ending.payload === void 0) {
446
+ return {};
447
+ }
448
+ const stated = unwrapJsonStringify(
449
+ input.property(ending.payload)?.asArg() ?? null
450
+ );
451
+ return stated === null ? {} : { payload: stated };
452
+ }
349
453
  function sqlAccess(matched) {
350
454
  const { ops, subject, method, chain, recognition } = matched;
351
455
  const ending = chain.ending;
@@ -449,7 +553,8 @@ function chainFrom(declared) {
449
553
  var DISPATCHED_ON = {
450
554
  storageAccess: "invocation",
451
555
  sqlAccess: "access",
452
- messageSend: "invocation"
556
+ messageSend: "invocation",
557
+ unitInvoke: "invocation"
453
558
  };
454
559
  function walkedBy(chains, walk2, recognizedAs) {
455
560
  return chains.filter((chain) => DISPATCHED_ON[chain.ending.yields] === walk2).map((chain) => compile(chain, recognizedAs));
@@ -473,6 +578,7 @@ function pack(name, matches, spec) {
473
578
  ...spec.recognizers ?? []
474
579
  ],
475
580
  ...access.length === 0 ? {} : { accessRecognizers: access },
581
+ ...spec.projectHelpers === void 0 ? {} : { projectHelpers: spec.projectHelpers },
476
582
  declarations: declarationsIn(matches)
477
583
  };
478
584
  }
@@ -483,10 +589,19 @@ function wireOf(ending) {
483
589
  if (ending.yields === "messageSend") {
484
590
  return ending.wire;
485
591
  }
592
+ if (ending.yields === "unitInvoke") {
593
+ return ending.platform;
594
+ }
486
595
  return ending.transport ?? ending.system;
487
596
  }
488
597
  function declaredName(ending) {
489
- return ending.yields === "messageSend" ? ending.wire : ending.system;
598
+ if (ending.yields === "messageSend") {
599
+ return ending.wire;
600
+ }
601
+ if (ending.yields === "unitInvoke") {
602
+ return ending.platform;
603
+ }
604
+ return ending.system;
490
605
  }
491
606
  function protocolOf(chains) {
492
607
  const wires = new Set(chains.map((chain) => wireOf(chain.ending)));
@@ -555,8 +670,15 @@ function rulesIn(chain) {
555
670
  const link = chain.links.find(
556
671
  (candidate) => candidate.asks === "methods"
557
672
  );
673
+ const calls = chain.links.find(
674
+ (candidate) => candidate.asks === "calls"
675
+ );
676
+ const meanings = [
677
+ ...Object.values(link?.table ?? {}),
678
+ ...calls === void 0 ? [] : [calls.meaning]
679
+ ];
558
680
  const written = /* @__PURE__ */ new Map();
559
- for (const method of Object.values(link?.table ?? {})) {
681
+ for (const method of meanings) {
560
682
  for (const asks of ["selector", "fields"]) {
561
683
  const rule = ruleFor(method[asks]);
562
684
  if (rule !== null) {
@@ -636,15 +758,48 @@ function chainFrom3(declared) {
636
758
  table,
637
759
  ignoringCase: options?.ignoringCase ?? false
638
760
  }),
761
+ calls: (meaning) => adding({ asks: "calls", meaning }),
639
762
  container: (says) => adding(
640
763
  typeof says === "function" ? { asks: "container", from: says } : { asks: "container", argument: says }
641
764
  ),
642
765
  accessPath: (says) => adding({ asks: "accessPath", argument: says }),
643
- containersIn: (says) => adding({ asks: "containers", in: says }),
766
+ containersIn: (says, options) => adding({
767
+ asks: "containers",
768
+ in: says,
769
+ each: options?.each ?? "entry"
770
+ }),
644
771
  input: (says) => adding({ asks: "input", at: says }),
645
772
  example: (code) => chainFrom3({ ...declared, example: code })
646
773
  };
647
774
  }
775
+
776
+ // src/unitInvokes.ts
777
+ function unitInvokes(spec) {
778
+ const ending = {
779
+ yields: "unitInvoke",
780
+ platform: spec.platform,
781
+ named: spec.named,
782
+ ...spec.payload === void 0 ? {} : { payload: spec.payload },
783
+ unsettledName: spec.unsettledName ?? "reference"
784
+ };
785
+ return chainFrom4({
786
+ links: chainStart(spec.client),
787
+ ending,
788
+ example: null
789
+ });
790
+ }
791
+ function chainFrom4(declared) {
792
+ const adding = (link) => chainFrom4({ ...declared, links: [...declared.links, link] });
793
+ return {
794
+ declared,
795
+ methods: (table, options) => adding({
796
+ asks: "methods",
797
+ table,
798
+ ignoringCase: options?.ignoringCase ?? false
799
+ }),
800
+ example: (code) => chainFrom4({ ...declared, example: code })
801
+ };
802
+ }
648
803
  export {
649
804
  compile,
650
805
  constructedFrom,
@@ -656,5 +811,6 @@ export {
656
811
  pack,
657
812
  runExamples,
658
813
  sqlStatements,
659
- storageCalls
814
+ storageCalls,
815
+ unitInvokes
660
816
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/recognize",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "type": "module",
5
5
  "description": "Write a pack as data: a chain of named links that any language adapter with the executor ops can run.",
6
6
  "keywords": [
@@ -49,9 +49,9 @@
49
49
  "typecheck": "tsc --noEmit"
50
50
  },
51
51
  "dependencies": {
52
- "@suss/behavioral-ir": "0.19.0",
53
- "@suss/extractor": "0.19.0",
54
- "@suss/sql": "0.19.0"
52
+ "@suss/behavioral-ir": "0.21.0",
53
+ "@suss/extractor": "0.21.0",
54
+ "@suss/sql": "0.21.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "tsup": "*",