@suss/recognize 0.13.1 → 0.13.2

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/ast.d.cts CHANGED
@@ -1,4 +1,6 @@
1
- import { C as CallOps, L as LinkFunction } from './chain-BaxjT_-k.cjs';
1
+ import { CallOps } from '@suss/extractor';
2
+ import { L as LinkFunction } from './chain-58V6lcel.cjs';
3
+ import '@suss/behavioral-ir';
2
4
 
3
5
  /**
4
6
  * The floor: a link that reads the adapter's own syntax tree.
@@ -14,19 +16,10 @@ import { C as CallOps, L as LinkFunction } from './chain-BaxjT_-k.cjs';
14
16
  * against, and that cast is how the pack says which adapter it needs.
15
17
  */
16
18
 
17
- /**
18
- * The ops an adapter implements when it can hand out its own nodes. The
19
- * extra member is here rather than on `CallOps` so that a pack reaching
20
- * for a node has to import this module first.
21
- */
22
- interface AstCapableOps extends CallOps {
23
- /** The adapter's own node for the call in hand. */
24
- ast(): unknown;
25
- }
26
19
  /**
27
20
  * A link answered by reading the call's own node. `read` is handed the
28
21
  * node and whatever the link would have been handed anyway.
29
22
  */
30
23
  declare function astLink<A extends unknown[], R>(read: (node: unknown, ...given: A) => R): LinkFunction<[...A, CallOps], R>;
31
24
 
32
- export { type AstCapableOps, astLink };
25
+ export { astLink };
package/dist/ast.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- import { C as CallOps, L as LinkFunction } from './chain-BaxjT_-k.js';
1
+ import { CallOps } from '@suss/extractor';
2
+ import { L as LinkFunction } from './chain-58V6lcel.js';
3
+ import '@suss/behavioral-ir';
2
4
 
3
5
  /**
4
6
  * The floor: a link that reads the adapter's own syntax tree.
@@ -14,19 +16,10 @@ import { C as CallOps, L as LinkFunction } from './chain-BaxjT_-k.js';
14
16
  * against, and that cast is how the pack says which adapter it needs.
15
17
  */
16
18
 
17
- /**
18
- * The ops an adapter implements when it can hand out its own nodes. The
19
- * extra member is here rather than on `CallOps` so that a pack reaching
20
- * for a node has to import this module first.
21
- */
22
- interface AstCapableOps extends CallOps {
23
- /** The adapter's own node for the call in hand. */
24
- ast(): unknown;
25
- }
26
19
  /**
27
20
  * A link answered by reading the call's own node. `read` is handed the
28
21
  * node and whatever the link would have been handed anyway.
29
22
  */
30
23
  declare function astLink<A extends unknown[], R>(read: (node: unknown, ...given: A) => R): LinkFunction<[...A, CallOps], R>;
31
24
 
32
- export { type AstCapableOps, astLink };
25
+ export { astLink };
@@ -1,196 +1,5 @@
1
- /**
2
- * The operations a chain performs at one call site, and what a language
3
- * adapter has to be able to work out for it.
4
- *
5
- * A chain is data, so something has to turn "did this module declare
6
- * the method" into a yes or no about a particular program. That is the
7
- * adapter's job, and this interface is the whole of what a chain asks
8
- * for. An adapter implements it once and every declared pack runs
9
- * there, which is what lets one pack drive several languages.
10
- *
11
- * Nothing here mentions a syntax tree. A link that needs the tree goes
12
- * through `@suss/recognize/ast`, a separate import so that reaching for
13
- * it shows up in a diff and in the pack health report.
14
- */
15
- /**
16
- * How a pack pins down the receiver a call is on.
17
- *
18
- * The migration plan for #542 lists four more: `factoryMade` (`app =
19
- * express()`), `imported`, `anchored` (a chain read from its anchor
20
- * call), `inherits` (a Ruby or Python receiver matched by ancestry) and
21
- * `global` (`process.env`, bare `fetch`). Each arrives as a member here
22
- * with an entry in every adapter's dispatch table. The README beside
23
- * this file says which starts are not receiver-shaped at all.
24
- */
25
- type ReceiverOrigin = DeclaredBy | ConstructedFrom;
26
- /**
27
- * A receiver whose method one of these modules declared.
28
- *
29
- * This is the origin for a client the source never spells out. `const
30
- * redis = await this.getClient()` says nothing about ioredis, and the
31
- * declaration behind `redis.get` says everything.
32
- */
33
- interface DeclaredBy {
34
- readonly origin: "declaredBy";
35
- /** The modules whose declarations settle the call. */
36
- readonly importedFrom: readonly string[];
37
- }
38
- /**
39
- * A client made from a module's export, however the program caches it.
40
- * Asked of the receiver itself, so it still works where the method is untyped
41
- * and `declaredBy` finds nothing to read. A call that reaches for no
42
- * receiver, `new GetObjectCommand(...)`, is made by its own callee.
43
- */
44
- interface ConstructedFrom {
45
- readonly origin: "constructed";
46
- /** The modules whose export the client was made from. */
47
- readonly importedFrom: readonly string[];
48
- }
49
- /**
50
- * A receiver whose method one of these modules declared. Pass every
51
- * module that ships the same client, the way three packages all speak
52
- * one wire protocol.
53
- */
54
- declare function declaredBy(...importedFrom: readonly string[]): DeclaredBy;
55
- /** A client made from one of these modules' exports. */
56
- declare function constructedFrom(...importedFrom: readonly string[]): ConstructedFrom;
57
- /**
58
- * What a reader gives back for a name nothing in the source settles.
59
- * `"nothing"` gives null, `"reference"` gives the value to go and ask
60
- * about instead.
61
- */
62
- type UnsettledName = "nothing" | "reference";
63
- /**
64
- * One value a call states, as the questions a pack can ask about it.
65
- *
66
- * `CallOps` reaches a call beside the one in hand, and this reaches the
67
- * values that are not calls. A library that takes one request object
68
- * puts everything the call is doing inside it, sometimes as a list and
69
- * sometimes as a string in a little language of the library's own, and
70
- * a pack that reads those is handed this rather than the adapter's own
71
- * node. So the rule it writes runs wherever the ops do.
72
- */
73
- interface ValueOps {
74
- /** The text of the string the source wrote, or null for anything else. */
75
- text(): string | null;
76
- /**
77
- * The yes or no the source wrote here, or null for anything else. A
78
- * library that asks which fields a call wants states them as a map of
79
- * flags, `{ name: 1, password: 0 }`, and a number and a boolean mean
80
- * the same thing in one of those.
81
- */
82
- flag(): boolean | null;
83
- /** What this object states, entry by entry. Empty for anything else. */
84
- entries(unsettled: UnsettledName): readonly ValueEntry[];
85
- /** What this list states, item by item. Empty for anything else. */
86
- items(): readonly ValueOps[];
87
- /** What one named property of this object states, or null for none. */
88
- property(name: string): ValueOps | null;
89
- /**
90
- * The pieces of text the source wrote here, in order, with whatever
91
- * it interpolated between them left out. A string is one piece and a
92
- * template is one piece per hole plus one, so a reader that means to
93
- * put its own placeholders in the holes can. Null when the source
94
- * wrote neither.
95
- */
96
- parts(): readonly string[] | null;
97
- /**
98
- * What the source interpolated between those pieces, in order, each
99
- * as the call it was written as. There is one per gap, one fewer than
100
- * `parts` gives, and a hole the source wrote as something other than
101
- * a call is null rather than being dropped, so the two lists stay in
102
- * step.
103
- *
104
- * What a hole comes to is the pack's to say. This says what was
105
- * there, so a query that hands over a table object can still be read.
106
- */
107
- holes(): readonly (CallOps | null)[];
108
- }
109
- /** One entry of an object a call states. */
110
- interface ValueEntry {
111
- /**
112
- * What the entry is called. A key the source computes,
113
- * `{ [table]: ... }`, is read the way any other name is.
114
- */
115
- readonly key: string | null;
116
- /** What the entry says. */
117
- readonly value: ValueOps;
118
- }
119
- /**
120
- * One call site, as the questions a chain asks about it.
121
- *
122
- * An adapter builds one of these per call and hands it to the
123
- * recognizer through its context. Every member is about the call in
124
- * hand, so there is no node to pass around and no place for a pack to
125
- * reach past what it declared.
126
- *
127
- * Two members give back another `CallOps`, which is how these questions
128
- * reach a call next to this one. A chain of calls and a command object
129
- * are both these same questions asked one step along, so neither needed
130
- * a question of its own.
131
- */
132
- interface CallOps {
133
- /** Which method the call reaches for, spelled as the source spells it. */
134
- method(): string | null;
135
- /** Whether the receiver came from where the origin says. */
136
- receiverIsFrom(origin: ReceiverOrigin): boolean;
137
- /**
138
- * Whether the call itself came from where the origin says, which is
139
- * the same question asked of the callee rather than of what it was
140
- * called on. `new GetCommand(...)` is the case: a pack that steps to
141
- * an argument asks this of it before reading anything, so it never
142
- * reads an argument that was never the one.
143
- */
144
- isFrom(origin: ReceiverOrigin): boolean;
145
- /** How many arguments the call passes. */
146
- argumentCount(): number;
147
- /** The name the argument in this position gives. */
148
- nameAt(index: number, unsettled: UnsettledName): string | null;
149
- /** The callee, as the source writes it. */
150
- calleeText(): string;
151
- /**
152
- * The call the receiver is, or null when the receiver is not a call.
153
- * A receiver the source wrote into a variable comes back as the call
154
- * it was written as.
155
- */
156
- receiver(): CallOps | null;
157
- /**
158
- * The call the argument in this position is, or null when that
159
- * argument is neither a call nor a construction. `new
160
- * GetObjectCommand(...)` comes back as a call whose callee is the
161
- * class, the same as any other call.
162
- */
163
- argument(index: number): CallOps | null;
164
- /**
165
- * The call the callee itself was written as, or null when nothing
166
- * wrote it as one. A class a factory made is the case: `new
167
- * User({...})` says nothing about what `User` is, and the
168
- * `model("User", schema)` call it was declared as says everything.
169
- */
170
- callee(): CallOps | null;
171
- /**
172
- * What a named property of the object an argument states says. A
173
- * property bag is not a call, so nothing else here reaches into one.
174
- */
175
- propertyAt(index: number, property: string, unsettled: UnsettledName): string | null;
176
- /**
177
- * The value the argument in this position states, or null when the
178
- * call passes none. `propertyAt` reads one name out of a property
179
- * bag, which covers a pack that wants one; this hands the bag over
180
- * for a pack whose rule has to walk it.
181
- */
182
- valueAt(index: number): ValueOps | null;
183
- }
184
- /**
185
- * The property an adapter puts its ops on, in the context it hands a
186
- * recognizer. A context without it belongs to an adapter that has not
187
- * implemented the ops, and a declared pack matches nothing there.
188
- */
189
- interface OpsCarrier {
190
- ops?: CallOps;
191
- }
192
- /** The ops in a recognizer context, or null when the adapter has none. */
193
- declare function opsIn(ctx: unknown): CallOps | null;
1
+ import { MessageBusSemantics } from '@suss/behavioral-ir';
2
+ import { ReceiverOrigin, ValueOps, CallOps, UnsettledName } from '@suss/extractor';
194
3
 
195
4
  /**
196
5
  * The spine every pack shares, as a chain of links.
@@ -475,9 +284,14 @@ interface SqlMethod {
475
284
  readonly statement: OneArgument;
476
285
  }
477
286
  /** What one method table can say a method does. */
478
- type MethodMeaning = StorageMethod | SqlMethod;
287
+ /** What one send method does, as the message-send ending reads it. */
288
+ interface MessageSendMethod {
289
+ /** Where the call states the message, or the collection of them. */
290
+ readonly input: OneArgument;
291
+ }
292
+ type MethodMeaning = StorageMethod | SqlMethod | MessageSendMethod;
479
293
  /** What a chain produces when every link matches. */
480
- type Ending = StorageEnding | SqlEnding;
294
+ type Ending = StorageEnding | SqlEnding | MessageSendEnding;
481
295
  /** A storage access: one call, one thing it read or wrote. */
482
296
  interface StorageEnding {
483
297
  readonly yields: "storageAccess";
@@ -516,6 +330,61 @@ interface SqlEnding {
516
330
  */
517
331
  readonly dialect: string;
518
332
  }
333
+ /**
334
+ * Where the messages a call sends are written.
335
+ *
336
+ * A library either takes one message as the call's input or takes a
337
+ * collection of them under a property. Which of the two is a fact about
338
+ * the command, so a library offering both spells them as two
339
+ * declarations rather than one with a setting on it: SQS has
340
+ * `SendMessageCommand` and `SendMessageBatchCommand`, and they are
341
+ * different shapes.
342
+ */
343
+ type MessageLocation = OneMessage | ManyIn;
344
+ /** The call's input is the message. */
345
+ interface OneMessage {
346
+ readonly each: "theInput";
347
+ }
348
+ /** A property of the call's input contains the messages, one each. */
349
+ interface ManyIn {
350
+ readonly each: "in";
351
+ /** The property the collection is written on. */
352
+ readonly property: string;
353
+ }
354
+ /**
355
+ * One part of the channel a message goes to, read off the message.
356
+ *
357
+ * A channel is one name on some wires and several on others. SQS has a
358
+ * queue, EventBridge has a bus and a subject on it, and both are read
359
+ * the same way, so a pack states the parts in the order they join.
360
+ */
361
+ interface ChannelPart {
362
+ /** The property the message states this part on. */
363
+ readonly property: string;
364
+ /** What the library uses when the message leaves this part out. */
365
+ readonly whenAbsent?: string;
366
+ }
367
+ /** A message sent on a wire, which pairs with whatever consumes that channel. */
368
+ interface MessageSendEnding {
369
+ readonly yields: "messageSend";
370
+ /** The wire, in the words the IR's message-bus semantics use. */
371
+ readonly wire: MessageBusSemantics["messageBus"];
372
+ /** Where the messages are. */
373
+ readonly messages: MessageLocation;
374
+ /** The parts of the channel, joined in the order they are written. */
375
+ readonly channel: readonly ChannelPart[];
376
+ /** What joins the parts, when there is more than one. */
377
+ readonly channelSeparator?: string;
378
+ /** The property the message states its body on, when the pack can say. */
379
+ readonly body?: string;
380
+ /**
381
+ * What a reader gives back for a channel nothing in the source
382
+ * settles. A queue URL only exists at deploy time, so the code writes
383
+ * `process.env.ORDERS_QUEUE_URL` and the env var's name is what both
384
+ * sides of the boundary agree on. `"reference"` keeps that name.
385
+ */
386
+ readonly unsettledName: UnsettledName;
387
+ }
519
388
  /**
520
389
  * A pack's declaration for one kind of call: the links, the ending, and
521
390
  * a line of code it matches.
@@ -527,4 +396,4 @@ interface Chain<TMeaning = MethodMeaning> {
527
396
  readonly example: string | null;
528
397
  }
529
398
 
530
- export { type ArgumentPick as A, declaredBy as B, type CallOps as C, type DeclaredBy as D, type Ending as E, type FromReceiver as F, opsIn as G, type InterpolatesLink as I, type KindAsAsked as K, type LinkFunction as L, type MethodMeaning as M, type OneArgument as O, type ReceiverOrigin as R, type SqlMethod as S, type ToArgument as T, type UnsettledName as U, type ValueEntry as V, type Chain as a, type CallStep as b, type StorageMethod as c, type AccessKind as d, type AccessPathLink as e, type ArgumentsFrom as f, type ConstructedFrom as g, type ContainerArgument as h, type ContainerLink as i, type ContainerRule as j, type ContainersLink as k, type InputLink as l, type InputRule as m, type Link as n, type MatchStart as o, type MethodsLink as p, type OpsCarrier as q, type SqlEnding as r, type StartLink as s, type StatedInputs as t, type StatedRule as u, type StorageEnding as v, type SubjectLink as w, type ToReceiver as x, type ValueOps as y, constructedFrom as z };
399
+ 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 };
@@ -1,196 +1,5 @@
1
- /**
2
- * The operations a chain performs at one call site, and what a language
3
- * adapter has to be able to work out for it.
4
- *
5
- * A chain is data, so something has to turn "did this module declare
6
- * the method" into a yes or no about a particular program. That is the
7
- * adapter's job, and this interface is the whole of what a chain asks
8
- * for. An adapter implements it once and every declared pack runs
9
- * there, which is what lets one pack drive several languages.
10
- *
11
- * Nothing here mentions a syntax tree. A link that needs the tree goes
12
- * through `@suss/recognize/ast`, a separate import so that reaching for
13
- * it shows up in a diff and in the pack health report.
14
- */
15
- /**
16
- * How a pack pins down the receiver a call is on.
17
- *
18
- * The migration plan for #542 lists four more: `factoryMade` (`app =
19
- * express()`), `imported`, `anchored` (a chain read from its anchor
20
- * call), `inherits` (a Ruby or Python receiver matched by ancestry) and
21
- * `global` (`process.env`, bare `fetch`). Each arrives as a member here
22
- * with an entry in every adapter's dispatch table. The README beside
23
- * this file says which starts are not receiver-shaped at all.
24
- */
25
- type ReceiverOrigin = DeclaredBy | ConstructedFrom;
26
- /**
27
- * A receiver whose method one of these modules declared.
28
- *
29
- * This is the origin for a client the source never spells out. `const
30
- * redis = await this.getClient()` says nothing about ioredis, and the
31
- * declaration behind `redis.get` says everything.
32
- */
33
- interface DeclaredBy {
34
- readonly origin: "declaredBy";
35
- /** The modules whose declarations settle the call. */
36
- readonly importedFrom: readonly string[];
37
- }
38
- /**
39
- * A client made from a module's export, however the program caches it.
40
- * Asked of the receiver itself, so it still works where the method is untyped
41
- * and `declaredBy` finds nothing to read. A call that reaches for no
42
- * receiver, `new GetObjectCommand(...)`, is made by its own callee.
43
- */
44
- interface ConstructedFrom {
45
- readonly origin: "constructed";
46
- /** The modules whose export the client was made from. */
47
- readonly importedFrom: readonly string[];
48
- }
49
- /**
50
- * A receiver whose method one of these modules declared. Pass every
51
- * module that ships the same client, the way three packages all speak
52
- * one wire protocol.
53
- */
54
- declare function declaredBy(...importedFrom: readonly string[]): DeclaredBy;
55
- /** A client made from one of these modules' exports. */
56
- declare function constructedFrom(...importedFrom: readonly string[]): ConstructedFrom;
57
- /**
58
- * What a reader gives back for a name nothing in the source settles.
59
- * `"nothing"` gives null, `"reference"` gives the value to go and ask
60
- * about instead.
61
- */
62
- type UnsettledName = "nothing" | "reference";
63
- /**
64
- * One value a call states, as the questions a pack can ask about it.
65
- *
66
- * `CallOps` reaches a call beside the one in hand, and this reaches the
67
- * values that are not calls. A library that takes one request object
68
- * puts everything the call is doing inside it, sometimes as a list and
69
- * sometimes as a string in a little language of the library's own, and
70
- * a pack that reads those is handed this rather than the adapter's own
71
- * node. So the rule it writes runs wherever the ops do.
72
- */
73
- interface ValueOps {
74
- /** The text of the string the source wrote, or null for anything else. */
75
- text(): string | null;
76
- /**
77
- * The yes or no the source wrote here, or null for anything else. A
78
- * library that asks which fields a call wants states them as a map of
79
- * flags, `{ name: 1, password: 0 }`, and a number and a boolean mean
80
- * the same thing in one of those.
81
- */
82
- flag(): boolean | null;
83
- /** What this object states, entry by entry. Empty for anything else. */
84
- entries(unsettled: UnsettledName): readonly ValueEntry[];
85
- /** What this list states, item by item. Empty for anything else. */
86
- items(): readonly ValueOps[];
87
- /** What one named property of this object states, or null for none. */
88
- property(name: string): ValueOps | null;
89
- /**
90
- * The pieces of text the source wrote here, in order, with whatever
91
- * it interpolated between them left out. A string is one piece and a
92
- * template is one piece per hole plus one, so a reader that means to
93
- * put its own placeholders in the holes can. Null when the source
94
- * wrote neither.
95
- */
96
- parts(): readonly string[] | null;
97
- /**
98
- * What the source interpolated between those pieces, in order, each
99
- * as the call it was written as. There is one per gap, one fewer than
100
- * `parts` gives, and a hole the source wrote as something other than
101
- * a call is null rather than being dropped, so the two lists stay in
102
- * step.
103
- *
104
- * What a hole comes to is the pack's to say. This says what was
105
- * there, so a query that hands over a table object can still be read.
106
- */
107
- holes(): readonly (CallOps | null)[];
108
- }
109
- /** One entry of an object a call states. */
110
- interface ValueEntry {
111
- /**
112
- * What the entry is called. A key the source computes,
113
- * `{ [table]: ... }`, is read the way any other name is.
114
- */
115
- readonly key: string | null;
116
- /** What the entry says. */
117
- readonly value: ValueOps;
118
- }
119
- /**
120
- * One call site, as the questions a chain asks about it.
121
- *
122
- * An adapter builds one of these per call and hands it to the
123
- * recognizer through its context. Every member is about the call in
124
- * hand, so there is no node to pass around and no place for a pack to
125
- * reach past what it declared.
126
- *
127
- * Two members give back another `CallOps`, which is how these questions
128
- * reach a call next to this one. A chain of calls and a command object
129
- * are both these same questions asked one step along, so neither needed
130
- * a question of its own.
131
- */
132
- interface CallOps {
133
- /** Which method the call reaches for, spelled as the source spells it. */
134
- method(): string | null;
135
- /** Whether the receiver came from where the origin says. */
136
- receiverIsFrom(origin: ReceiverOrigin): boolean;
137
- /**
138
- * Whether the call itself came from where the origin says, which is
139
- * the same question asked of the callee rather than of what it was
140
- * called on. `new GetCommand(...)` is the case: a pack that steps to
141
- * an argument asks this of it before reading anything, so it never
142
- * reads an argument that was never the one.
143
- */
144
- isFrom(origin: ReceiverOrigin): boolean;
145
- /** How many arguments the call passes. */
146
- argumentCount(): number;
147
- /** The name the argument in this position gives. */
148
- nameAt(index: number, unsettled: UnsettledName): string | null;
149
- /** The callee, as the source writes it. */
150
- calleeText(): string;
151
- /**
152
- * The call the receiver is, or null when the receiver is not a call.
153
- * A receiver the source wrote into a variable comes back as the call
154
- * it was written as.
155
- */
156
- receiver(): CallOps | null;
157
- /**
158
- * The call the argument in this position is, or null when that
159
- * argument is neither a call nor a construction. `new
160
- * GetObjectCommand(...)` comes back as a call whose callee is the
161
- * class, the same as any other call.
162
- */
163
- argument(index: number): CallOps | null;
164
- /**
165
- * The call the callee itself was written as, or null when nothing
166
- * wrote it as one. A class a factory made is the case: `new
167
- * User({...})` says nothing about what `User` is, and the
168
- * `model("User", schema)` call it was declared as says everything.
169
- */
170
- callee(): CallOps | null;
171
- /**
172
- * What a named property of the object an argument states says. A
173
- * property bag is not a call, so nothing else here reaches into one.
174
- */
175
- propertyAt(index: number, property: string, unsettled: UnsettledName): string | null;
176
- /**
177
- * The value the argument in this position states, or null when the
178
- * call passes none. `propertyAt` reads one name out of a property
179
- * bag, which covers a pack that wants one; this hands the bag over
180
- * for a pack whose rule has to walk it.
181
- */
182
- valueAt(index: number): ValueOps | null;
183
- }
184
- /**
185
- * The property an adapter puts its ops on, in the context it hands a
186
- * recognizer. A context without it belongs to an adapter that has not
187
- * implemented the ops, and a declared pack matches nothing there.
188
- */
189
- interface OpsCarrier {
190
- ops?: CallOps;
191
- }
192
- /** The ops in a recognizer context, or null when the adapter has none. */
193
- declare function opsIn(ctx: unknown): CallOps | null;
1
+ import { MessageBusSemantics } from '@suss/behavioral-ir';
2
+ import { ReceiverOrigin, ValueOps, CallOps, UnsettledName } from '@suss/extractor';
194
3
 
195
4
  /**
196
5
  * The spine every pack shares, as a chain of links.
@@ -475,9 +284,14 @@ interface SqlMethod {
475
284
  readonly statement: OneArgument;
476
285
  }
477
286
  /** What one method table can say a method does. */
478
- type MethodMeaning = StorageMethod | SqlMethod;
287
+ /** What one send method does, as the message-send ending reads it. */
288
+ interface MessageSendMethod {
289
+ /** Where the call states the message, or the collection of them. */
290
+ readonly input: OneArgument;
291
+ }
292
+ type MethodMeaning = StorageMethod | SqlMethod | MessageSendMethod;
479
293
  /** What a chain produces when every link matches. */
480
- type Ending = StorageEnding | SqlEnding;
294
+ type Ending = StorageEnding | SqlEnding | MessageSendEnding;
481
295
  /** A storage access: one call, one thing it read or wrote. */
482
296
  interface StorageEnding {
483
297
  readonly yields: "storageAccess";
@@ -516,6 +330,61 @@ interface SqlEnding {
516
330
  */
517
331
  readonly dialect: string;
518
332
  }
333
+ /**
334
+ * Where the messages a call sends are written.
335
+ *
336
+ * A library either takes one message as the call's input or takes a
337
+ * collection of them under a property. Which of the two is a fact about
338
+ * the command, so a library offering both spells them as two
339
+ * declarations rather than one with a setting on it: SQS has
340
+ * `SendMessageCommand` and `SendMessageBatchCommand`, and they are
341
+ * different shapes.
342
+ */
343
+ type MessageLocation = OneMessage | ManyIn;
344
+ /** The call's input is the message. */
345
+ interface OneMessage {
346
+ readonly each: "theInput";
347
+ }
348
+ /** A property of the call's input contains the messages, one each. */
349
+ interface ManyIn {
350
+ readonly each: "in";
351
+ /** The property the collection is written on. */
352
+ readonly property: string;
353
+ }
354
+ /**
355
+ * One part of the channel a message goes to, read off the message.
356
+ *
357
+ * A channel is one name on some wires and several on others. SQS has a
358
+ * queue, EventBridge has a bus and a subject on it, and both are read
359
+ * the same way, so a pack states the parts in the order they join.
360
+ */
361
+ interface ChannelPart {
362
+ /** The property the message states this part on. */
363
+ readonly property: string;
364
+ /** What the library uses when the message leaves this part out. */
365
+ readonly whenAbsent?: string;
366
+ }
367
+ /** A message sent on a wire, which pairs with whatever consumes that channel. */
368
+ interface MessageSendEnding {
369
+ readonly yields: "messageSend";
370
+ /** The wire, in the words the IR's message-bus semantics use. */
371
+ readonly wire: MessageBusSemantics["messageBus"];
372
+ /** Where the messages are. */
373
+ readonly messages: MessageLocation;
374
+ /** The parts of the channel, joined in the order they are written. */
375
+ readonly channel: readonly ChannelPart[];
376
+ /** What joins the parts, when there is more than one. */
377
+ readonly channelSeparator?: string;
378
+ /** The property the message states its body on, when the pack can say. */
379
+ readonly body?: string;
380
+ /**
381
+ * What a reader gives back for a channel nothing in the source
382
+ * settles. A queue URL only exists at deploy time, so the code writes
383
+ * `process.env.ORDERS_QUEUE_URL` and the env var's name is what both
384
+ * sides of the boundary agree on. `"reference"` keeps that name.
385
+ */
386
+ readonly unsettledName: UnsettledName;
387
+ }
519
388
  /**
520
389
  * A pack's declaration for one kind of call: the links, the ending, and
521
390
  * a line of code it matches.
@@ -527,4 +396,4 @@ interface Chain<TMeaning = MethodMeaning> {
527
396
  readonly example: string | null;
528
397
  }
529
398
 
530
- export { type ArgumentPick as A, declaredBy as B, type CallOps as C, type DeclaredBy as D, type Ending as E, type FromReceiver as F, opsIn as G, type InterpolatesLink as I, type KindAsAsked as K, type LinkFunction as L, type MethodMeaning as M, type OneArgument as O, type ReceiverOrigin as R, type SqlMethod as S, type ToArgument as T, type UnsettledName as U, type ValueEntry as V, type Chain as a, type CallStep as b, type StorageMethod as c, type AccessKind as d, type AccessPathLink as e, type ArgumentsFrom as f, type ConstructedFrom as g, type ContainerArgument as h, type ContainerLink as i, type ContainerRule as j, type ContainersLink as k, type InputLink as l, type InputRule as m, type Link as n, type MatchStart as o, type MethodsLink as p, type OpsCarrier as q, type SqlEnding as r, type StartLink as s, type StatedInputs as t, type StatedRule as u, type StorageEnding as v, type SubjectLink as w, type ToReceiver as x, type ValueOps as y, constructedFrom as z };
399
+ 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 };
package/dist/index.cjs CHANGED
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  declarationsIn: () => declarationsIn,
26
26
  declaredBy: () => declaredBy,
27
27
  examplesMissing: () => examplesMissing,
28
+ messageSends: () => messageSends,
28
29
  opsIn: () => opsIn,
29
30
  pack: () => pack,
30
31
  runExamples: () => runExamples,
@@ -41,8 +42,16 @@ var import_sql = require("@suss/sql");
41
42
  function declaredBy(...importedFrom) {
42
43
  return { origin: "declaredBy", importedFrom };
43
44
  }
44
- function constructedFrom(...importedFrom) {
45
- return { origin: "constructed", importedFrom };
45
+ function constructedFrom(...args) {
46
+ const [first] = args;
47
+ if (typeof first === "object") {
48
+ return {
49
+ origin: "constructed",
50
+ importedFrom: first.from,
51
+ named: first.named
52
+ };
53
+ }
54
+ return { origin: "constructed", importedFrom: args };
46
55
  }
47
56
  function opsIn(ctx) {
48
57
  const carrier = ctx;
@@ -152,7 +161,8 @@ function startsHere(chain, subject) {
152
161
  }
153
162
  var YIELD = {
154
163
  storageAccess,
155
- sqlAccess
164
+ sqlAccess,
165
+ messageSend
156
166
  };
157
167
  function storageAccess(matched) {
158
168
  const { subject, chain } = matched;
@@ -247,6 +257,7 @@ function statedValue(subject, pick) {
247
257
  }
248
258
  var NOTHING_STATED = {
249
259
  text: () => null,
260
+ name: () => null,
250
261
  flag: () => null,
251
262
  entries: () => [],
252
263
  items: () => [],
@@ -299,6 +310,57 @@ function nameOf(ops, pick, index, unsettled) {
299
310
  }
300
311
  return null;
301
312
  }
313
+ function messageSend(matched) {
314
+ const { ops, subject, chain, recognition } = matched;
315
+ const ending = chain.ending;
316
+ const input = statedValue(
317
+ subject,
318
+ matched.meaning.input
319
+ );
320
+ if (input === null) {
321
+ return null;
322
+ }
323
+ const messages = messagesIn(input, ending.messages);
324
+ return messages.length === 0 ? null : messages.map((message) => ({
325
+ type: "interaction",
326
+ binding: (0, import_behavioral_ir.messageBusBinding)({
327
+ recognition,
328
+ messageBus: ending.wire,
329
+ channel: channelOf(message, ending)
330
+ }),
331
+ callee: ops.calleeText(),
332
+ interaction: {
333
+ class: "message-send",
334
+ ...bodyOf(message, ending)
335
+ }
336
+ }));
337
+ }
338
+ function messagesIn(input, location) {
339
+ if (location.each === "theInput") {
340
+ return [input];
341
+ }
342
+ const collection = input.property(location.property);
343
+ return collection === null ? [] : collection.items();
344
+ }
345
+ function channelOf(message, ending) {
346
+ const parts = [];
347
+ for (const part of ending.channel) {
348
+ const stated = message.property(part.property)?.name(ending.unsettledName) ?? null;
349
+ const value = stated === null || stated === "" ? part.whenAbsent : stated;
350
+ if (value === void 0) {
351
+ return null;
352
+ }
353
+ parts.push(value);
354
+ }
355
+ return parts.join(ending.channelSeparator ?? "#");
356
+ }
357
+ function bodyOf(message, ending) {
358
+ if (ending.body === void 0) {
359
+ return {};
360
+ }
361
+ const stated = message.property(ending.body)?.text() ?? null;
362
+ return stated === null ? {} : { body: stated };
363
+ }
302
364
  function sqlAccess(matched) {
303
365
  const { ops, subject, method, chain, recognition } = matched;
304
366
  const ending = chain.ending;
@@ -362,10 +424,46 @@ function examplesMissing(pack2) {
362
424
  return (pack2.declarations?.declarations ?? []).filter((declaration) => declaration.example === null).map((declaration) => declaration.name);
363
425
  }
364
426
 
427
+ // src/chain.ts
428
+ function chainStart(client) {
429
+ return client === void 0 ? [] : [{ asks: "start", at: { starts: "receiver", origin: client } }];
430
+ }
431
+
432
+ // src/messageSends.ts
433
+ function messageSends(spec) {
434
+ const ending = {
435
+ yields: "messageSend",
436
+ wire: spec.wire,
437
+ messages: spec.messages,
438
+ channel: spec.channel,
439
+ ...spec.channelSeparator === void 0 ? {} : { channelSeparator: spec.channelSeparator },
440
+ ...spec.body === void 0 ? {} : { body: spec.body },
441
+ unsettledName: spec.unsettledName ?? "reference"
442
+ };
443
+ return chainFrom({
444
+ links: chainStart(spec.client),
445
+ ending,
446
+ example: null
447
+ });
448
+ }
449
+ function chainFrom(declared) {
450
+ const adding = (link) => chainFrom({ ...declared, links: [...declared.links, link] });
451
+ return {
452
+ declared,
453
+ methods: (table, options) => adding({
454
+ asks: "methods",
455
+ table,
456
+ ignoringCase: options?.ignoringCase ?? false
457
+ }),
458
+ example: (code) => chainFrom({ ...declared, example: code })
459
+ };
460
+ }
461
+
365
462
  // src/pack.ts
366
463
  var DISPATCHED_ON = {
367
464
  storageAccess: "invocation",
368
- sqlAccess: "access"
465
+ sqlAccess: "access",
466
+ messageSend: "invocation"
369
467
  };
370
468
  function walkedBy(chains, walk2, recognizedAs) {
371
469
  return chains.filter((chain) => DISPATCHED_ON[chain.ending.yields] === walk2).map((chain) => compile(chain, recognizedAs));
@@ -392,10 +490,17 @@ function pack(name, matches, spec) {
392
490
  function declarationsIn(matches) {
393
491
  return { declarations: matches.map((match) => describe(match.declared)) };
394
492
  }
493
+ function wireOf(ending) {
494
+ if (ending.yields === "messageSend") {
495
+ return ending.wire;
496
+ }
497
+ return ending.transport ?? ending.system;
498
+ }
499
+ function declaredName(ending) {
500
+ return ending.yields === "messageSend" ? ending.wire : ending.system;
501
+ }
395
502
  function protocolOf(chains) {
396
- const wires = new Set(
397
- chains.map((chain) => chain.ending.transport ?? chain.ending.system)
398
- );
503
+ const wires = new Set(chains.map((chain) => wireOf(chain.ending)));
399
504
  const [only] = [...wires];
400
505
  if (wires.size !== 1 || only === void 0) {
401
506
  throw new Error(
@@ -445,7 +550,7 @@ function describe(chain) {
445
550
  ...rulesIn(chain)
446
551
  ];
447
552
  return {
448
- name: chain.ending.system,
553
+ name: declaredName(chain.ending),
449
554
  // A method table with a rule inside it is still a table, so that
450
555
  // link stays counted as data and the rule is priced beside it.
451
556
  dataLinks: chain.links.length - links.length,
@@ -496,14 +601,14 @@ function sqlStatements(spec) {
496
601
  dialect: spec.dialect
497
602
  };
498
603
  const client = spec.client;
499
- return chainFrom({
500
- links: client === void 0 ? [] : [{ asks: "start", at: { starts: "receiver", origin: client } }],
604
+ return chainFrom2({
605
+ links: chainStart(client),
501
606
  ending,
502
607
  example: null
503
608
  });
504
609
  }
505
- function chainFrom(declared) {
506
- const adding = (link) => chainFrom({ ...declared, links: [...declared.links, link] });
610
+ function chainFrom2(declared) {
611
+ const adding = (link) => chainFrom2({ ...declared, links: [...declared.links, link] });
507
612
  return {
508
613
  declared,
509
614
  methods: (table, options) => adding({
@@ -512,7 +617,7 @@ function chainFrom(declared) {
512
617
  ignoringCase: options?.ignoringCase ?? false
513
618
  }),
514
619
  interpolating: (says) => adding({ asks: "interpolates", ...says }),
515
- example: (code) => chainFrom({ ...declared, example: code })
620
+ example: (code) => chainFrom2({ ...declared, example: code })
516
621
  };
517
622
  }
518
623
 
@@ -526,14 +631,14 @@ function storageCalls(spec) {
526
631
  unsettledName: spec.unsettledName ?? "reference"
527
632
  };
528
633
  const client = spec.client;
529
- return chainFrom2({
530
- links: client === void 0 ? [] : [{ asks: "start", at: { starts: "receiver", origin: client } }],
634
+ return chainFrom3({
635
+ links: chainStart(client),
531
636
  ending,
532
637
  example: null
533
638
  });
534
639
  }
535
- function chainFrom2(declared) {
536
- const adding = (link) => chainFrom2({ ...declared, links: [...declared.links, link] });
640
+ function chainFrom3(declared) {
641
+ const adding = (link) => chainFrom3({ ...declared, links: [...declared.links, link] });
537
642
  return {
538
643
  declared,
539
644
  about: (...of) => adding({ asks: "subject", of }),
@@ -548,7 +653,7 @@ function chainFrom2(declared) {
548
653
  accessPath: (says) => adding({ asks: "accessPath", argument: says }),
549
654
  containersIn: (says) => adding({ asks: "containers", in: says }),
550
655
  input: (says) => adding({ asks: "input", at: says }),
551
- example: (code) => chainFrom2({ ...declared, example: code })
656
+ example: (code) => chainFrom3({ ...declared, example: code })
552
657
  };
553
658
  }
554
659
  // Annotate the CommonJS export names for ESM import in node:
@@ -558,6 +663,7 @@ function chainFrom2(declared) {
558
663
  declarationsIn,
559
664
  declaredBy,
560
665
  examplesMissing,
666
+ messageSends,
561
667
  opsIn,
562
668
  pack,
563
669
  runExamples,
package/dist/index.d.cts CHANGED
@@ -1,8 +1,33 @@
1
- import { InvocationRecognizer, PatternPack, PackDeclarations } from '@suss/extractor';
2
- export { DeclaredMatch, PackDeclarations, PatternPack } from '@suss/extractor';
3
- import { a as Chain, M as MethodMeaning, S as SqlMethod, I as InterpolatesLink, R as ReceiverOrigin, b as CallStep, c as StorageMethod, A as ArgumentPick, L as LinkFunction, C as CallOps, O as OneArgument } from './chain-BaxjT_-k.cjs';
4
- export { d as AccessKind, e as AccessPathLink, f as ArgumentsFrom, g as ConstructedFrom, h as ContainerArgument, i as ContainerLink, j as ContainerRule, k as ContainersLink, D as DeclaredBy, E as Ending, F as FromReceiver, l as InputLink, m as InputRule, K as KindAsAsked, n as Link, o as MatchStart, p as MethodsLink, q as OpsCarrier, r as SqlEnding, s as StartLink, t as StatedInputs, u as StatedRule, v as StorageEnding, w as SubjectLink, T as ToArgument, x as ToReceiver, U as UnsettledName, V as ValueEntry, y as ValueOps, z as constructedFrom, B as declaredBy, G as opsIn } from './chain-BaxjT_-k.cjs';
5
- import { Effect } from '@suss/behavioral-ir';
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-58V6lcel.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-58V6lcel.cjs';
5
+ import { Effect, MessageBusSemantics } from '@suss/behavioral-ir';
6
+
7
+ /**
8
+ * The small builders a pack writes its origins with, and the vocabulary
9
+ * they produce.
10
+ *
11
+ * The types live in `@suss/extractor` because an adapter implements them
12
+ * and never calls any of this. They are re-exported here so a pack
13
+ * author has one import.
14
+ */
15
+
16
+ /**
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.
20
+ */
21
+ declare function declaredBy(...importedFrom: readonly string[]): DeclaredBy;
22
+ /** A client made from one of these modules' exports. */
23
+ declare function constructedFrom(...importedFrom: readonly string[]): ConstructedFrom;
24
+ /** A client made from named exports of those modules. */
25
+ declare function constructedFrom(spec: {
26
+ from: readonly string[];
27
+ named: readonly string[];
28
+ }): ConstructedFrom;
29
+ /** The ops in a recognizer context, or null when the adapter has none. */
30
+ declare function opsIn(ctx: unknown): CallOps | null;
6
31
 
7
32
  /**
8
33
  * Turning a chain into the recognizer hook the adapters already call.
@@ -53,6 +78,57 @@ declare function runExamples(pack: PatternPack, run: RunOverCode): RanExample[];
53
78
  /** The declarations a pack shipped without a line of code to run. */
54
79
  declare function examplesMissing(pack: PatternPack): string[];
55
80
 
81
+ /**
82
+ * messageSends.ts: a pack that recognizes a call putting a message on a
83
+ * wire.
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:
89
+ *
90
+ * client.send(new SendMessageCommand({ QueueUrl, MessageBody }))
91
+ * client.send(new PutEventsCommand({ Entries: [{ EventBusName, DetailType }] }))
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.
96
+ */
97
+
98
+ interface MessageSendsSpec {
99
+ /** The wire, in the words the IR's message-bus semantics use. */
100
+ wire: MessageBusSemantics["messageBus"];
101
+ /** Where the messages are: the call's input, or a property holding many. */
102
+ messages: MessageLocation;
103
+ /** The parts of the channel, joined in the order they are written. */
104
+ channel: readonly ChannelPart[];
105
+ /** What joins the parts. Defaults to "#", which is what a bus and a subject use. */
106
+ channelSeparator?: string;
107
+ /** The property a message states its body on. */
108
+ body?: string;
109
+ /**
110
+ * What a reader gives back for a channel nothing in the source
111
+ * settles. Defaults to keeping the reference, since a queue named
112
+ * only at deploy time is the common case rather than the exception.
113
+ */
114
+ unsettledName?: UnsettledName;
115
+ /** How the pack pins down the client its calls are on. */
116
+ client?: ReceiverOrigin;
117
+ }
118
+ /** A chain over sends, and the links that can still be added. */
119
+ interface MessageSends {
120
+ /** Which methods send, and where each one states the message. */
121
+ methods(table: Readonly<Record<string, MessageSendMethod>>, options?: {
122
+ ignoringCase?: boolean;
123
+ }): MessageSends;
124
+ /** A line of code this matches, which the pack's tests run. */
125
+ example(code: string): MessageSends;
126
+ /** The links and the ending, as data. */
127
+ readonly declared: Chain<MessageSendMethod>;
128
+ }
129
+ /** A pack that recognizes a call sending a message. */
130
+ declare function messageSends(spec: MessageSendsSpec): MessageSends;
131
+
56
132
  /**
57
133
  * Assembling declared chains into the pack the adapters load.
58
134
  *
@@ -225,4 +301,4 @@ interface StorageCalls {
225
301
  /** A pack that recognizes calls against a store. */
226
302
  declare function storageCalls(spec: StorageCallsSpec): StorageCalls;
227
303
 
228
- export { ArgumentPick, CallOps, CallStep, Chain, InterpolatesLink, LinkFunction, type Match, MethodMeaning, OneArgument, type PackSpec, type RanExample, ReceiverOrigin, type RunOverCode, SqlMethod, type SqlStatements, type SqlStatementsSpec, type StorageCalls, type StorageCallsSpec, StorageMethod, compile, declarationsIn, examplesMissing, pack, runExamples, sqlStatements, storageCalls };
304
+ 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 };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,33 @@
1
- import { InvocationRecognizer, PatternPack, PackDeclarations } from '@suss/extractor';
2
- export { DeclaredMatch, PackDeclarations, PatternPack } from '@suss/extractor';
3
- import { a as Chain, M as MethodMeaning, S as SqlMethod, I as InterpolatesLink, R as ReceiverOrigin, b as CallStep, c as StorageMethod, A as ArgumentPick, L as LinkFunction, C as CallOps, O as OneArgument } from './chain-BaxjT_-k.js';
4
- export { d as AccessKind, e as AccessPathLink, f as ArgumentsFrom, g as ConstructedFrom, h as ContainerArgument, i as ContainerLink, j as ContainerRule, k as ContainersLink, D as DeclaredBy, E as Ending, F as FromReceiver, l as InputLink, m as InputRule, K as KindAsAsked, n as Link, o as MatchStart, p as MethodsLink, q as OpsCarrier, r as SqlEnding, s as StartLink, t as StatedInputs, u as StatedRule, v as StorageEnding, w as SubjectLink, T as ToArgument, x as ToReceiver, U as UnsettledName, V as ValueEntry, y as ValueOps, z as constructedFrom, B as declaredBy, G as opsIn } from './chain-BaxjT_-k.js';
5
- import { Effect } from '@suss/behavioral-ir';
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-58V6lcel.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-58V6lcel.js';
5
+ import { Effect, MessageBusSemantics } from '@suss/behavioral-ir';
6
+
7
+ /**
8
+ * The small builders a pack writes its origins with, and the vocabulary
9
+ * they produce.
10
+ *
11
+ * The types live in `@suss/extractor` because an adapter implements them
12
+ * and never calls any of this. They are re-exported here so a pack
13
+ * author has one import.
14
+ */
15
+
16
+ /**
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.
20
+ */
21
+ declare function declaredBy(...importedFrom: readonly string[]): DeclaredBy;
22
+ /** A client made from one of these modules' exports. */
23
+ declare function constructedFrom(...importedFrom: readonly string[]): ConstructedFrom;
24
+ /** A client made from named exports of those modules. */
25
+ declare function constructedFrom(spec: {
26
+ from: readonly string[];
27
+ named: readonly string[];
28
+ }): ConstructedFrom;
29
+ /** The ops in a recognizer context, or null when the adapter has none. */
30
+ declare function opsIn(ctx: unknown): CallOps | null;
6
31
 
7
32
  /**
8
33
  * Turning a chain into the recognizer hook the adapters already call.
@@ -53,6 +78,57 @@ declare function runExamples(pack: PatternPack, run: RunOverCode): RanExample[];
53
78
  /** The declarations a pack shipped without a line of code to run. */
54
79
  declare function examplesMissing(pack: PatternPack): string[];
55
80
 
81
+ /**
82
+ * messageSends.ts: a pack that recognizes a call putting a message on a
83
+ * wire.
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:
89
+ *
90
+ * client.send(new SendMessageCommand({ QueueUrl, MessageBody }))
91
+ * client.send(new PutEventsCommand({ Entries: [{ EventBusName, DetailType }] }))
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.
96
+ */
97
+
98
+ interface MessageSendsSpec {
99
+ /** The wire, in the words the IR's message-bus semantics use. */
100
+ wire: MessageBusSemantics["messageBus"];
101
+ /** Where the messages are: the call's input, or a property holding many. */
102
+ messages: MessageLocation;
103
+ /** The parts of the channel, joined in the order they are written. */
104
+ channel: readonly ChannelPart[];
105
+ /** What joins the parts. Defaults to "#", which is what a bus and a subject use. */
106
+ channelSeparator?: string;
107
+ /** The property a message states its body on. */
108
+ body?: string;
109
+ /**
110
+ * What a reader gives back for a channel nothing in the source
111
+ * settles. Defaults to keeping the reference, since a queue named
112
+ * only at deploy time is the common case rather than the exception.
113
+ */
114
+ unsettledName?: UnsettledName;
115
+ /** How the pack pins down the client its calls are on. */
116
+ client?: ReceiverOrigin;
117
+ }
118
+ /** A chain over sends, and the links that can still be added. */
119
+ interface MessageSends {
120
+ /** Which methods send, and where each one states the message. */
121
+ methods(table: Readonly<Record<string, MessageSendMethod>>, options?: {
122
+ ignoringCase?: boolean;
123
+ }): MessageSends;
124
+ /** A line of code this matches, which the pack's tests run. */
125
+ example(code: string): MessageSends;
126
+ /** The links and the ending, as data. */
127
+ readonly declared: Chain<MessageSendMethod>;
128
+ }
129
+ /** A pack that recognizes a call sending a message. */
130
+ declare function messageSends(spec: MessageSendsSpec): MessageSends;
131
+
56
132
  /**
57
133
  * Assembling declared chains into the pack the adapters load.
58
134
  *
@@ -225,4 +301,4 @@ interface StorageCalls {
225
301
  /** A pack that recognizes calls against a store. */
226
302
  declare function storageCalls(spec: StorageCallsSpec): StorageCalls;
227
303
 
228
- export { ArgumentPick, CallOps, CallStep, Chain, InterpolatesLink, LinkFunction, type Match, MethodMeaning, OneArgument, type PackSpec, type RanExample, ReceiverOrigin, type RunOverCode, SqlMethod, type SqlStatements, type SqlStatementsSpec, type StorageCalls, type StorageCallsSpec, StorageMethod, compile, declarationsIn, examplesMissing, pack, runExamples, sqlStatements, storageCalls };
304
+ 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 };
package/dist/index.js CHANGED
@@ -1,13 +1,21 @@
1
1
  // src/compile.ts
2
- import { storageBinding } from "@suss/behavioral-ir";
2
+ import { messageBusBinding, storageBinding } from "@suss/behavioral-ir";
3
3
  import { readSqlAccess, sqlFromParts } from "@suss/sql";
4
4
 
5
5
  // src/ops.ts
6
6
  function declaredBy(...importedFrom) {
7
7
  return { origin: "declaredBy", importedFrom };
8
8
  }
9
- function constructedFrom(...importedFrom) {
10
- return { origin: "constructed", importedFrom };
9
+ function constructedFrom(...args) {
10
+ const [first] = args;
11
+ if (typeof first === "object") {
12
+ return {
13
+ origin: "constructed",
14
+ importedFrom: first.from,
15
+ named: first.named
16
+ };
17
+ }
18
+ return { origin: "constructed", importedFrom: args };
11
19
  }
12
20
  function opsIn(ctx) {
13
21
  const carrier = ctx;
@@ -117,7 +125,8 @@ function startsHere(chain, subject) {
117
125
  }
118
126
  var YIELD = {
119
127
  storageAccess,
120
- sqlAccess
128
+ sqlAccess,
129
+ messageSend
121
130
  };
122
131
  function storageAccess(matched) {
123
132
  const { subject, chain } = matched;
@@ -212,6 +221,7 @@ function statedValue(subject, pick) {
212
221
  }
213
222
  var NOTHING_STATED = {
214
223
  text: () => null,
224
+ name: () => null,
215
225
  flag: () => null,
216
226
  entries: () => [],
217
227
  items: () => [],
@@ -264,6 +274,57 @@ function nameOf(ops, pick, index, unsettled) {
264
274
  }
265
275
  return null;
266
276
  }
277
+ function messageSend(matched) {
278
+ const { ops, subject, chain, recognition } = matched;
279
+ const ending = chain.ending;
280
+ const input = statedValue(
281
+ subject,
282
+ matched.meaning.input
283
+ );
284
+ if (input === null) {
285
+ return null;
286
+ }
287
+ const messages = messagesIn(input, ending.messages);
288
+ return messages.length === 0 ? null : messages.map((message) => ({
289
+ type: "interaction",
290
+ binding: messageBusBinding({
291
+ recognition,
292
+ messageBus: ending.wire,
293
+ channel: channelOf(message, ending)
294
+ }),
295
+ callee: ops.calleeText(),
296
+ interaction: {
297
+ class: "message-send",
298
+ ...bodyOf(message, ending)
299
+ }
300
+ }));
301
+ }
302
+ function messagesIn(input, location) {
303
+ if (location.each === "theInput") {
304
+ return [input];
305
+ }
306
+ const collection = input.property(location.property);
307
+ return collection === null ? [] : collection.items();
308
+ }
309
+ function channelOf(message, ending) {
310
+ const parts = [];
311
+ for (const part of ending.channel) {
312
+ const stated = message.property(part.property)?.name(ending.unsettledName) ?? null;
313
+ const value = stated === null || stated === "" ? part.whenAbsent : stated;
314
+ if (value === void 0) {
315
+ return null;
316
+ }
317
+ parts.push(value);
318
+ }
319
+ return parts.join(ending.channelSeparator ?? "#");
320
+ }
321
+ function bodyOf(message, ending) {
322
+ if (ending.body === void 0) {
323
+ return {};
324
+ }
325
+ const stated = message.property(ending.body)?.text() ?? null;
326
+ return stated === null ? {} : { body: stated };
327
+ }
267
328
  function sqlAccess(matched) {
268
329
  const { ops, subject, method, chain, recognition } = matched;
269
330
  const ending = chain.ending;
@@ -327,10 +388,46 @@ function examplesMissing(pack2) {
327
388
  return (pack2.declarations?.declarations ?? []).filter((declaration) => declaration.example === null).map((declaration) => declaration.name);
328
389
  }
329
390
 
391
+ // src/chain.ts
392
+ function chainStart(client) {
393
+ return client === void 0 ? [] : [{ asks: "start", at: { starts: "receiver", origin: client } }];
394
+ }
395
+
396
+ // src/messageSends.ts
397
+ function messageSends(spec) {
398
+ const ending = {
399
+ yields: "messageSend",
400
+ wire: spec.wire,
401
+ messages: spec.messages,
402
+ channel: spec.channel,
403
+ ...spec.channelSeparator === void 0 ? {} : { channelSeparator: spec.channelSeparator },
404
+ ...spec.body === void 0 ? {} : { body: spec.body },
405
+ unsettledName: spec.unsettledName ?? "reference"
406
+ };
407
+ return chainFrom({
408
+ links: chainStart(spec.client),
409
+ ending,
410
+ example: null
411
+ });
412
+ }
413
+ function chainFrom(declared) {
414
+ const adding = (link) => chainFrom({ ...declared, links: [...declared.links, link] });
415
+ return {
416
+ declared,
417
+ methods: (table, options) => adding({
418
+ asks: "methods",
419
+ table,
420
+ ignoringCase: options?.ignoringCase ?? false
421
+ }),
422
+ example: (code) => chainFrom({ ...declared, example: code })
423
+ };
424
+ }
425
+
330
426
  // src/pack.ts
331
427
  var DISPATCHED_ON = {
332
428
  storageAccess: "invocation",
333
- sqlAccess: "access"
429
+ sqlAccess: "access",
430
+ messageSend: "invocation"
334
431
  };
335
432
  function walkedBy(chains, walk2, recognizedAs) {
336
433
  return chains.filter((chain) => DISPATCHED_ON[chain.ending.yields] === walk2).map((chain) => compile(chain, recognizedAs));
@@ -357,10 +454,17 @@ function pack(name, matches, spec) {
357
454
  function declarationsIn(matches) {
358
455
  return { declarations: matches.map((match) => describe(match.declared)) };
359
456
  }
457
+ function wireOf(ending) {
458
+ if (ending.yields === "messageSend") {
459
+ return ending.wire;
460
+ }
461
+ return ending.transport ?? ending.system;
462
+ }
463
+ function declaredName(ending) {
464
+ return ending.yields === "messageSend" ? ending.wire : ending.system;
465
+ }
360
466
  function protocolOf(chains) {
361
- const wires = new Set(
362
- chains.map((chain) => chain.ending.transport ?? chain.ending.system)
363
- );
467
+ const wires = new Set(chains.map((chain) => wireOf(chain.ending)));
364
468
  const [only] = [...wires];
365
469
  if (wires.size !== 1 || only === void 0) {
366
470
  throw new Error(
@@ -410,7 +514,7 @@ function describe(chain) {
410
514
  ...rulesIn(chain)
411
515
  ];
412
516
  return {
413
- name: chain.ending.system,
517
+ name: declaredName(chain.ending),
414
518
  // A method table with a rule inside it is still a table, so that
415
519
  // link stays counted as data and the rule is priced beside it.
416
520
  dataLinks: chain.links.length - links.length,
@@ -461,14 +565,14 @@ function sqlStatements(spec) {
461
565
  dialect: spec.dialect
462
566
  };
463
567
  const client = spec.client;
464
- return chainFrom({
465
- links: client === void 0 ? [] : [{ asks: "start", at: { starts: "receiver", origin: client } }],
568
+ return chainFrom2({
569
+ links: chainStart(client),
466
570
  ending,
467
571
  example: null
468
572
  });
469
573
  }
470
- function chainFrom(declared) {
471
- const adding = (link) => chainFrom({ ...declared, links: [...declared.links, link] });
574
+ function chainFrom2(declared) {
575
+ const adding = (link) => chainFrom2({ ...declared, links: [...declared.links, link] });
472
576
  return {
473
577
  declared,
474
578
  methods: (table, options) => adding({
@@ -477,7 +581,7 @@ function chainFrom(declared) {
477
581
  ignoringCase: options?.ignoringCase ?? false
478
582
  }),
479
583
  interpolating: (says) => adding({ asks: "interpolates", ...says }),
480
- example: (code) => chainFrom({ ...declared, example: code })
584
+ example: (code) => chainFrom2({ ...declared, example: code })
481
585
  };
482
586
  }
483
587
 
@@ -491,14 +595,14 @@ function storageCalls(spec) {
491
595
  unsettledName: spec.unsettledName ?? "reference"
492
596
  };
493
597
  const client = spec.client;
494
- return chainFrom2({
495
- links: client === void 0 ? [] : [{ asks: "start", at: { starts: "receiver", origin: client } }],
598
+ return chainFrom3({
599
+ links: chainStart(client),
496
600
  ending,
497
601
  example: null
498
602
  });
499
603
  }
500
- function chainFrom2(declared) {
501
- const adding = (link) => chainFrom2({ ...declared, links: [...declared.links, link] });
604
+ function chainFrom3(declared) {
605
+ const adding = (link) => chainFrom3({ ...declared, links: [...declared.links, link] });
502
606
  return {
503
607
  declared,
504
608
  about: (...of) => adding({ asks: "subject", of }),
@@ -513,7 +617,7 @@ function chainFrom2(declared) {
513
617
  accessPath: (says) => adding({ asks: "accessPath", argument: says }),
514
618
  containersIn: (says) => adding({ asks: "containers", in: says }),
515
619
  input: (says) => adding({ asks: "input", at: says }),
516
- example: (code) => chainFrom2({ ...declared, example: code })
620
+ example: (code) => chainFrom3({ ...declared, example: code })
517
621
  };
518
622
  }
519
623
  export {
@@ -522,6 +626,7 @@ export {
522
626
  declarationsIn,
523
627
  declaredBy,
524
628
  examplesMissing,
629
+ messageSends,
525
630
  opsIn,
526
631
  pack,
527
632
  runExamples,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/recognize",
3
- "version": "0.13.1",
3
+ "version": "0.13.2",
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.13.1",
53
- "@suss/extractor": "0.13.1",
54
- "@suss/sql": "0.13.1"
52
+ "@suss/behavioral-ir": "0.13.2",
53
+ "@suss/extractor": "0.13.2",
54
+ "@suss/sql": "0.13.2"
55
55
  },
56
56
  "devDependencies": {
57
57
  "tsup": "*",