@suss/recognize 0.11.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.
@@ -0,0 +1,127 @@
1
+ import { InvocationRecognizer, PatternPack } from '@suss/extractor';
2
+ export { DeclaredMatch, PackDeclarations, PatternPack } from '@suss/extractor';
3
+ import { a as Chain, S as StorageMethod, L as LinkFunction, C as CallOps, R as ReceiverOrigin } from './chain-DTSij2Mn.js';
4
+ export { A as ArgumentPick, b as ConstructedFrom, c as ContainerLink, D as DeclaredBy, E as Ending, F as FromReceiver, d as Link, M as MatchStart, e as MethodsLink, O as OpsCarrier, f as StartLink, g as StorageEnding, U as UnsettledName, h as constructedFrom, i as declaredBy, o as opsIn } from './chain-DTSij2Mn.js';
5
+ import { Effect } from '@suss/behavioral-ir';
6
+
7
+ /**
8
+ * Turning a chain into the recognizer hook the adapters already call.
9
+ *
10
+ * The links are guards, so the compiled hook checks the cheap one
11
+ * first: looking a method up in a table costs nothing, while following
12
+ * a receiver to the library that made it walks declarations. The chain
13
+ * says which questions to ask, not the order to ask them in. A link
14
+ * that changes what the receiver is, the way Prisma's model property
15
+ * does, will need that order back, and the note here is where to start.
16
+ */
17
+
18
+ /** A chain, as the hook the adapter dispatches to on every call. */
19
+ declare function compile(chain: Chain<StorageMethod>, recognition: string): InvocationRecognizer;
20
+
21
+ /**
22
+ * Running the example that comes with every declaration.
23
+ *
24
+ * A pack's documentation goes stale the moment the library moves, and
25
+ * nothing catches it. An example that the pack's own tests compile and
26
+ * run against the declaration cannot: the day it stops matching, the
27
+ * pack fails rather than the docs quietly lying.
28
+ *
29
+ * The run itself needs a compiler, which this package does not have, so
30
+ * the caller passes one. A pack's test builds a small project with the
31
+ * client library on disk and hands back what the recognizers emitted.
32
+ */
33
+
34
+ /** What a pack's example produced, and what the pack said it would. */
35
+ interface RanExample {
36
+ /** What the declaration matches, in the pack's own words. */
37
+ match: string;
38
+ /** The line of code that came with the declaration. */
39
+ example: string;
40
+ effects: Effect[];
41
+ }
42
+ /** How a pack's test compiles one line and runs the pack over it. */
43
+ type RunOverCode = (code: string) => Effect[];
44
+ /**
45
+ * Every example a pack declares, run. A declaration with no example is
46
+ * left out, which is what the pack health check catches separately.
47
+ */
48
+ declare function runExamples(pack: PatternPack, run: RunOverCode): RanExample[];
49
+ /** The declarations a pack shipped without a line of code to run. */
50
+ declare function examplesMissing(pack: PatternPack): string[];
51
+
52
+ /**
53
+ * The entry point for a pack that recognizes storage calls, and the
54
+ * chain it builds.
55
+ *
56
+ * The entry point says what comes out rather than how, so a pack author
57
+ * writes `storageCalls` and never meets an `Effect`. Each method on the
58
+ * returned value adds one link and gives back a new chain, so a chain
59
+ * is finished data by the time `pack` receives it.
60
+ */
61
+
62
+ /** What a pack says about the store its calls reach. */
63
+ interface StorageCallsSpec {
64
+ /** The store, in the words OpenTelemetry's semantic conventions use. */
65
+ system: string;
66
+ /** The wire, when it differs from the store's own name. */
67
+ transport?: string;
68
+ /** Which of the store's namespaces the calls reach. Defaults to "default". */
69
+ scope?: string;
70
+ /** How the pack pins down the client its calls are on. */
71
+ client: ReceiverOrigin;
72
+ /**
73
+ * What a reader gives back for a name nothing settles. Defaults to
74
+ * "reference", which says which value to go and ask about.
75
+ */
76
+ unsettledName?: "nothing" | "reference";
77
+ }
78
+ /** A storage chain, and the links that can still be added to it. */
79
+ interface StorageCalls {
80
+ /** Which methods count, and what each one reads or writes. */
81
+ methods(table: Readonly<Record<string, StorageMethod>>, options?: {
82
+ ignoringCase?: boolean;
83
+ }): StorageCalls;
84
+ /**
85
+ * Which container a call's selector belongs to. Without this the
86
+ * first name a call reached is the container.
87
+ */
88
+ container(from: LinkFunction<[readonly string[], CallOps], string | null>): StorageCalls;
89
+ /** A line of code this matches, which the pack's tests run. */
90
+ example(code: string): StorageCalls;
91
+ /** The links and the ending, as data. */
92
+ readonly declared: Chain<StorageMethod>;
93
+ }
94
+ /** A pack that recognizes calls against a store. */
95
+ declare function storageCalls(spec: StorageCallsSpec): StorageCalls;
96
+
97
+ /**
98
+ * Assembling declared chains into the pack the adapters load.
99
+ *
100
+ * Everything a `PatternPack` needs beyond the chains is either already
101
+ * in them or the same for every declared pack: the modules the client
102
+ * came from are the import gate, the store's wire is the protocol, and
103
+ * a pack of recognizers discovers nothing. What is left is the pack's
104
+ * own name, the languages it runs on, and the name its effects are
105
+ * recognized under, which is the npm package callers install.
106
+ */
107
+
108
+ /** What a pack says about itself, beyond the calls it matches. */
109
+ interface PackSpec {
110
+ /** The languages the pack runs on, as the adapters name them. */
111
+ languages: string[];
112
+ /**
113
+ * The name effects from this pack are recorded under, which is the
114
+ * npm package a user installs.
115
+ */
116
+ recognizedAs: string;
117
+ /**
118
+ * What the pack calls this build of itself. The extraction cache keys
119
+ * on it, so a pack that never stamps one serves an earlier build's
120
+ * results after an edit.
121
+ */
122
+ version?: string;
123
+ }
124
+ /** A pack, assembled from the calls it says it matches. */
125
+ declare function pack(name: string, matches: readonly StorageCalls[], spec: PackSpec): PatternPack;
126
+
127
+ export { CallOps, Chain, LinkFunction, type PackSpec, type RanExample, ReceiverOrigin, type RunOverCode, type StorageCalls, type StorageCallsSpec, StorageMethod, compile, examplesMissing, pack, runExamples, storageCalls };
package/dist/index.js ADDED
@@ -0,0 +1,232 @@
1
+ // src/compile.ts
2
+ import { storageBinding } from "@suss/behavioral-ir";
3
+
4
+ // src/ops.ts
5
+ function declaredBy(...importedFrom) {
6
+ return { origin: "declaredBy", importedFrom };
7
+ }
8
+ function constructedFrom(...importedFrom) {
9
+ return { origin: "constructed", importedFrom };
10
+ }
11
+ function opsIn(ctx) {
12
+ const carrier = ctx;
13
+ return carrier?.ops ?? null;
14
+ }
15
+
16
+ // src/compile.ts
17
+ function compile(chain, recognition) {
18
+ return (_call, ctx) => {
19
+ const ops = opsIn(ctx);
20
+ if (ops === null) {
21
+ return null;
22
+ }
23
+ const method = ops.method();
24
+ if (method === null) {
25
+ return null;
26
+ }
27
+ const meaning = meaningOf(chain, method);
28
+ if (meaning === null || !startsHere(chain, ops)) {
29
+ return null;
30
+ }
31
+ return YIELD[chain.ending.yields]({
32
+ ops,
33
+ method,
34
+ meaning,
35
+ chain,
36
+ recognition
37
+ });
38
+ };
39
+ }
40
+ function meaningOf(chain, method) {
41
+ const link = chain.links.find(
42
+ (candidate) => candidate.asks === "methods"
43
+ );
44
+ if (link === void 0) {
45
+ return null;
46
+ }
47
+ if (!link.ignoringCase) {
48
+ return link.table[method] ?? null;
49
+ }
50
+ return link.table[method.toLowerCase()] ?? null;
51
+ }
52
+ var START = {
53
+ receiver: (start, ops) => ops.receiverIsFrom(start.origin)
54
+ };
55
+ function startsHere(chain, ops) {
56
+ return chain.links.every(
57
+ (link) => link.asks === "start" ? START[link.at.starts](link.at, ops) : true
58
+ );
59
+ }
60
+ var YIELD = {
61
+ storageAccess
62
+ };
63
+ function storageAccess(matched) {
64
+ const { ops, method, meaning, chain, recognition } = matched;
65
+ const ending = chain.ending;
66
+ const selector = namesAt(ops, meaning.selector, ending.unsettledName);
67
+ const fields = namesAt(ops, meaning.fields, ending.unsettledName);
68
+ const container = containerOf(chain, selector, ops);
69
+ return [
70
+ {
71
+ type: "interaction",
72
+ binding: storageBinding({
73
+ recognition,
74
+ storageSystem: ending.system,
75
+ ...ending.transport === void 0 ? {} : { transport: ending.transport },
76
+ scope: ending.scope,
77
+ container,
78
+ accessPath: null
79
+ }),
80
+ callee: ops.calleeText(),
81
+ interaction: {
82
+ class: "storage-access",
83
+ kind: meaning.kind,
84
+ fields,
85
+ operation: method,
86
+ ...selector.length > 0 ? { selector } : {}
87
+ }
88
+ }
89
+ ];
90
+ }
91
+ function containerOf(chain, selector, ops) {
92
+ const link = chain.links.find(
93
+ (candidate) => candidate.asks === "container"
94
+ );
95
+ if (link === void 0) {
96
+ return selector[0] ?? null;
97
+ }
98
+ return link.from(selector, ops);
99
+ }
100
+ function namesAt(ops, pick, unsettled) {
101
+ if (pick === void 0) {
102
+ return [];
103
+ }
104
+ const last = "at" in pick ? pick.at + 1 : ops.argumentCount();
105
+ const first = "at" in pick ? pick.at : pick.from;
106
+ const names = [];
107
+ for (let index = first; index < last; index += 1) {
108
+ const name = ops.nameAt(index, unsettled);
109
+ if (name !== null) {
110
+ names.push(name);
111
+ }
112
+ }
113
+ return names;
114
+ }
115
+
116
+ // src/example.ts
117
+ function runExamples(pack2, run) {
118
+ const declared = pack2.declarations?.declarations ?? [];
119
+ const ran = [];
120
+ for (const declaration of declared) {
121
+ if (declaration.example === null) {
122
+ continue;
123
+ }
124
+ ran.push({
125
+ match: declaration.name,
126
+ example: declaration.example,
127
+ effects: run(declaration.example)
128
+ });
129
+ }
130
+ return ran;
131
+ }
132
+ function examplesMissing(pack2) {
133
+ return (pack2.declarations?.declarations ?? []).filter((declaration) => declaration.example === null).map((declaration) => declaration.name);
134
+ }
135
+
136
+ // src/pack.ts
137
+ function pack(name, matches, spec) {
138
+ const chains = matches.map((match) => match.declared);
139
+ const recognizers = chains.map(
140
+ (chain) => compile(chain, spec.recognizedAs)
141
+ );
142
+ return {
143
+ name,
144
+ languages: spec.languages,
145
+ ...spec.version === void 0 ? {} : { version: spec.version },
146
+ protocol: protocolOf(chains),
147
+ discovery: [],
148
+ terminals: [],
149
+ inputMapping: { type: "positionalParams", params: [] },
150
+ requiresImport: gateOf(chains),
151
+ invocationRecognizers: recognizers,
152
+ declarations: { declarations: chains.map(describe) }
153
+ };
154
+ }
155
+ function protocolOf(chains) {
156
+ const wires = new Set(
157
+ chains.map((chain) => chain.ending.transport ?? chain.ending.system)
158
+ );
159
+ const [only] = [...wires];
160
+ if (wires.size !== 1 || only === void 0) {
161
+ throw new Error(
162
+ `a pack reaches ${wires.size} wires (${[...wires].join(", ")}); split it into one pack per wire`
163
+ );
164
+ }
165
+ return only;
166
+ }
167
+ function gateOf(chains) {
168
+ const modules = /* @__PURE__ */ new Set();
169
+ for (const chain of chains) {
170
+ for (const link of chain.links) {
171
+ if (link.asks !== "start" || link.at.starts !== "receiver") {
172
+ continue;
173
+ }
174
+ for (const module of link.at.origin.importedFrom) {
175
+ modules.add(module);
176
+ }
177
+ }
178
+ }
179
+ return [...modules];
180
+ }
181
+ function describe(chain) {
182
+ const functions = chain.links.filter(isFunctionLink);
183
+ return {
184
+ name: chain.ending.system,
185
+ dataLinks: chain.links.length - functions.length,
186
+ functionLinks: functions.map((link) => link.asks),
187
+ astLinks: functions.filter((link) => link.from.reachesAst === true).map((link) => link.asks),
188
+ example: chain.example
189
+ };
190
+ }
191
+ function isFunctionLink(link) {
192
+ return link.asks === "container";
193
+ }
194
+
195
+ // src/storage.ts
196
+ function storageCalls(spec) {
197
+ const ending = {
198
+ yields: "storageAccess",
199
+ system: spec.system,
200
+ ...spec.transport === void 0 ? {} : { transport: spec.transport },
201
+ scope: spec.scope ?? "default",
202
+ unsettledName: spec.unsettledName ?? "reference"
203
+ };
204
+ return chainFrom({
205
+ links: [{ asks: "start", at: { starts: "receiver", origin: spec.client } }],
206
+ ending,
207
+ example: null
208
+ });
209
+ }
210
+ function chainFrom(declared) {
211
+ const adding = (link) => chainFrom({ ...declared, links: [...declared.links, link] });
212
+ return {
213
+ declared,
214
+ methods: (table, options) => adding({
215
+ asks: "methods",
216
+ table,
217
+ ignoringCase: options?.ignoringCase ?? false
218
+ }),
219
+ container: (from) => adding({ asks: "container", from }),
220
+ example: (code) => chainFrom({ ...declared, example: code })
221
+ };
222
+ }
223
+ export {
224
+ compile,
225
+ constructedFrom,
226
+ declaredBy,
227
+ examplesMissing,
228
+ opsIn,
229
+ pack,
230
+ runExamples,
231
+ storageCalls
232
+ };
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@suss/recognize",
3
+ "version": "0.11.0",
4
+ "type": "module",
5
+ "description": "Write a pack as data: a chain of named links that any language adapter with the executor ops can run.",
6
+ "keywords": [
7
+ "suss",
8
+ "pattern-pack",
9
+ "static-analysis",
10
+ "recognizer"
11
+ ],
12
+ "license": "Apache-2.0",
13
+ "author": "Nimbus Cloud AI LLC",
14
+ "homepage": "https://github.com/nimbuscloud-ai/suss/tree/main/packages/recognize#readme",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/nimbuscloud-ai/suss.git",
18
+ "directory": "packages/recognize"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/nimbuscloud-ai/suss/issues"
22
+ },
23
+ "main": "./dist/index.cjs",
24
+ "module": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js",
30
+ "require": "./dist/index.cjs"
31
+ },
32
+ "./ast": {
33
+ "types": "./dist/ast.d.ts",
34
+ "import": "./dist/ast.js",
35
+ "require": "./dist/ast.cjs"
36
+ }
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "scripts": {
44
+ "build": "tsup",
45
+ "test": "vitest run",
46
+ "test:coverage": "vitest run --coverage",
47
+ "lint": "biome check .",
48
+ "lint:fix": "biome check --write .",
49
+ "typecheck": "tsc --noEmit"
50
+ },
51
+ "dependencies": {
52
+ "@suss/behavioral-ir": "0.11.0",
53
+ "@suss/extractor": "0.11.0"
54
+ },
55
+ "devDependencies": {
56
+ "tsup": "*",
57
+ "typescript": "*",
58
+ "vitest": "*"
59
+ },
60
+ "publishConfig": {
61
+ "access": "public"
62
+ }
63
+ }