@prestack/thread 0.1.20 → 0.1.22

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.
Files changed (148) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/dist/core/attach.js +21 -0
  3. package/dist/core/descend.d.ts +2 -0
  4. package/dist/core/descend.js +18 -3
  5. package/dist/core/generators/commit.js +55 -3
  6. package/dist/core/generators/dev.d.ts +1 -0
  7. package/dist/core/generators/dev.js +68 -0
  8. package/dist/core/rows.d.ts +5 -0
  9. package/dist/core/rows.js +45 -0
  10. package/dist/core/tree.d.ts +1 -0
  11. package/dist/core/tree.js +7 -1
  12. package/dist/derivation/generators/build.d.ts +3 -1
  13. package/dist/derivation/generators/build.js +103 -22
  14. package/dist/derivation/generators/release.js +74 -26
  15. package/dist/derivation/release.js +3 -3
  16. package/dist/integrations/cli.d.ts +3 -9
  17. package/dist/integrations/cli.js +96 -36
  18. package/dist/integrations/declared.d.ts +8 -0
  19. package/dist/integrations/declared.js +22 -0
  20. package/dist/integrations/pglite.js +13 -3
  21. package/dist/integrations/target.d.ts +4 -1
  22. package/dist/integrations/target.js +20 -6
  23. package/dist/integrity/attach.js +1 -5
  24. package/dist/integrity/generators/publish.js +2 -1
  25. package/dist/integrity/slopnators/sl_stale_path.js +10 -0
  26. package/dist/integrity/slopnators/sl_unlisted_program.js +2 -2
  27. package/dist/operations/access.js +3 -2
  28. package/dist/operations/capabilities.d.ts +2 -0
  29. package/dist/operations/capabilities.js +2 -0
  30. package/dist/operations/generators/capability.js +234 -24
  31. package/dist/operations/generators/compile.d.ts +21 -0
  32. package/dist/operations/generators/compile.js +151 -51
  33. package/dist/operations/generators/permits.d.ts +2 -1
  34. package/dist/operations/generators/permits.js +52 -47
  35. package/dist/operations/generators/seed.d.ts +3 -0
  36. package/dist/operations/generators/seed.js +25 -3
  37. package/dist/operations/nodes.d.ts +2 -2
  38. package/package.json +1 -1
  39. package/dist/core/actor.ts +0 -21
  40. package/dist/core/attach.ts +0 -613
  41. package/dist/core/chain.ts +0 -145
  42. package/dist/core/condition.ts +0 -227
  43. package/dist/core/descend.ts +0 -1173
  44. package/dist/core/errors/catalogue.ts +0 -314
  45. package/dist/core/errors/classes.ts +0 -51
  46. package/dist/core/errors/error.ts +0 -152
  47. package/dist/core/errors/namespaces.ts +0 -19
  48. package/dist/core/finding.ts +0 -37
  49. package/dist/core/generators/commit.ts +0 -157
  50. package/dist/core/init.ts +0 -222
  51. package/dist/core/invoked.ts +0 -52
  52. package/dist/core/round.ts +0 -74
  53. package/dist/core/rows.ts +0 -459
  54. package/dist/core/scope.ts +0 -20
  55. package/dist/core/thread.ts +0 -29
  56. package/dist/core/tree.ts +0 -90
  57. package/dist/core/types.ts +0 -351
  58. package/dist/derivation/attach.ts +0 -57
  59. package/dist/derivation/generators/build.ts +0 -371
  60. package/dist/derivation/generators/check.ts +0 -118
  61. package/dist/derivation/generators/determinism.ts +0 -145
  62. package/dist/derivation/generators/release.ts +0 -236
  63. package/dist/derivation/release.ts +0 -43
  64. package/dist/integrations/attach.ts +0 -187
  65. package/dist/integrations/cli.ts +0 -615
  66. package/dist/integrations/disk.ts +0 -466
  67. package/dist/integrations/generators/read.ts +0 -127
  68. package/dist/integrations/generators/vault.ts +0 -210
  69. package/dist/integrations/https.ts +0 -216
  70. package/dist/integrations/pglite.ts +0 -206
  71. package/dist/integrations/secret.ts +0 -54
  72. package/dist/integrations/target.ts +0 -656
  73. package/dist/integrations/vault.ts +0 -148
  74. package/dist/integrity/attach.ts +0 -150
  75. package/dist/integrity/check-threads.ts +0 -166
  76. package/dist/integrity/constraint.ts +0 -1246
  77. package/dist/integrity/generators/compiler.ts +0 -138
  78. package/dist/integrity/generators/gate.ts +0 -227
  79. package/dist/integrity/generators/issues.ts +0 -160
  80. package/dist/integrity/generators/publish.ts +0 -232
  81. package/dist/integrity/generators/trial.ts +0 -359
  82. package/dist/integrity/lint.ts +0 -214
  83. package/dist/integrity/lsp/bin.ts +0 -44
  84. package/dist/integrity/lsp/diagnostics.ts +0 -92
  85. package/dist/integrity/lsp/findings.ts +0 -63
  86. package/dist/integrity/lsp/lint.ts +0 -124
  87. package/dist/integrity/lsp/protocol.ts +0 -78
  88. package/dist/integrity/lsp/server.ts +0 -224
  89. package/dist/integrity/slopnator.ts +0 -324
  90. package/dist/integrity/slopnators/sl_broken_lineage.ts +0 -38
  91. package/dist/integrity/slopnators/sl_declared_elsewhere.ts +0 -60
  92. package/dist/integrity/slopnators/sl_derived_drift.ts +0 -101
  93. package/dist/integrity/slopnators/sl_door_in_a_node.ts +0 -250
  94. package/dist/integrity/slopnators/sl_hardcoded_style.ts +0 -55
  95. package/dist/integrity/slopnators/sl_inert_island.ts +0 -84
  96. package/dist/integrity/slopnators/sl_inert_slopnator.ts +0 -93
  97. package/dist/integrity/slopnators/sl_missing_row.ts +0 -140
  98. package/dist/integrity/slopnators/sl_no_barrel.ts +0 -48
  99. package/dist/integrity/slopnators/sl_no_child_process.ts +0 -100
  100. package/dist/integrity/slopnators/sl_no_comment.ts +0 -43
  101. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +0 -276
  102. package/dist/integrity/slopnators/sl_no_import_cycle.ts +0 -66
  103. package/dist/integrity/slopnators/sl_no_such_command.ts +0 -81
  104. package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +0 -63
  105. package/dist/integrity/slopnators/sl_outside_an_operation.ts +0 -25
  106. package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +0 -70
  107. package/dist/integrity/slopnators/sl_secret_literal.ts +0 -68
  108. package/dist/integrity/slopnators/sl_stale_path.ts +0 -178
  109. package/dist/integrity/slopnators/sl_twice_meant.ts +0 -57
  110. package/dist/integrity/slopnators/sl_twice_parented.ts +0 -82
  111. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +0 -481
  112. package/dist/integrity/slopnators/sl_undeclared_destination.ts +0 -113
  113. package/dist/integrity/slopnators/sl_undeclared_element.ts +0 -44
  114. package/dist/integrity/slopnators/sl_undeclared_list.ts +0 -358
  115. package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -75
  116. package/dist/integrity/slopnators/sl_undeclared_operation.ts +0 -257
  117. package/dist/integrity/slopnators/sl_undeclared_variant.ts +0 -152
  118. package/dist/integrity/slopnators/sl_undeclared_webhook.ts +0 -108
  119. package/dist/integrity/slopnators/sl_underived_route.ts +0 -72
  120. package/dist/integrity/slopnators/sl_undescribed.ts +0 -65
  121. package/dist/integrity/slopnators/sl_ungoverned_island.ts +0 -60
  122. package/dist/integrity/slopnators/sl_unlisted_program.ts +0 -46
  123. package/dist/integrity/slopnators/sl_unnamed_island.ts +0 -41
  124. package/dist/integrity/slopnators/sl_unpaired_member.ts +0 -139
  125. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +0 -71
  126. package/dist/integrity/slopnators/sl_unprefixed_member.ts +0 -110
  127. package/dist/integrity/slopnators/sl_unthreaded.ts +0 -421
  128. package/dist/integrity/slopnators/sl_untranslated_key.ts +0 -140
  129. package/dist/integrity/slopnators/sl_unvaulted_secret.ts +0 -132
  130. package/dist/integrity/slopnators/sl_unversioned.ts +0 -52
  131. package/dist/integrity/sweep.ts +0 -395
  132. package/dist/knowledge/attach.ts +0 -327
  133. package/dist/knowledge/generators/docsgen.ts +0 -186
  134. package/dist/knowledge/generators/glossary.ts +0 -73
  135. package/dist/knowledge/glossary.ts +0 -6
  136. package/dist/operations/access.ts +0 -131
  137. package/dist/operations/attach.ts +0 -408
  138. package/dist/operations/capabilities.ts +0 -88
  139. package/dist/operations/dispatch.ts +0 -294
  140. package/dist/operations/error-results.ts +0 -31
  141. package/dist/operations/generators/capability.ts +0 -1054
  142. package/dist/operations/generators/compile.ts +0 -701
  143. package/dist/operations/generators/permits.ts +0 -224
  144. package/dist/operations/generators/seed.ts +0 -359
  145. package/dist/operations/generators/trial.ts +0 -666
  146. package/dist/operations/nodes.ts +0 -53
  147. package/dist/operations/operations.ts +0 -91
  148. package/dist/operations/result.ts +0 -28
@@ -1,358 +0,0 @@
1
- import { dirname, resolve } from 'node:path'
2
-
3
- import ts from 'typescript'
4
-
5
- import { readFileSync } from '../../integrations/disk.ts'
6
-
7
- import type { Slopnator, SlopnatorContext } from '../slopnator.ts'
8
-
9
- const LIST = 2
10
-
11
- interface Held {
12
- readonly type?: string
13
- readonly value?: unknown
14
- readonly name?: string
15
- readonly operator?: string
16
- readonly elements?: readonly (Held | null)[]
17
- readonly properties?: readonly (Held | null)[]
18
- readonly arguments?: readonly Held[]
19
- readonly init?: Held | null
20
- readonly id?: { readonly type?: string; readonly name?: string }
21
- readonly callee?: Held
22
- readonly object?: Held
23
- readonly property?: Held
24
- readonly expression?: Held
25
- readonly argument?: Held | null
26
- readonly test?: Held | null
27
- readonly consequent?: Held | null
28
- readonly alternate?: Held | null
29
- readonly body?: readonly (Held | null)[]
30
- }
31
-
32
- const WIDENED = 'TSAsExpression'
33
-
34
- function bare(held: Held | null | undefined): Held | undefined {
35
- let one = held ?? undefined
36
- while (one?.type === WIDENED) one = one.expression
37
- return one
38
- }
39
-
40
- const spoken = (held: Held | null | undefined): boolean => {
41
- const one = bare(held)
42
- return one?.type === 'Literal' && typeof one.value === 'string'
43
- }
44
-
45
- const strings = (held: Held | null | undefined): boolean => {
46
- const one = bare(held)
47
- if (one?.type !== 'ArrayExpression') return false
48
- const all = one.elements ?? []
49
- return all.length >= LIST && all.every((each) => spoken(each) || woven(each))
50
- }
51
-
52
- const SPREAD = 'SpreadElement'
53
-
54
- function woven(held: Held | null | undefined): boolean {
55
- const one = bare(held)
56
- if (one?.type !== SPREAD) return false
57
- let words = 0
58
- let other = false
59
- const walk = (node: unknown, seen: Set<unknown>): void => {
60
- if (node === null || typeof node !== 'object' || seen.has(node)) return
61
- seen.add(node)
62
- if (Array.isArray(node)) {
63
- for (const each of node) walk(each, seen)
64
- return
65
- }
66
- const held = node as Held
67
- if (held.type === 'Literal') {
68
- if (typeof held.value === 'string') words += 1
69
- else other = true
70
- return
71
- }
72
- for (const [key, each] of Object.entries(node as Record<string, unknown>)) {
73
- if (key !== BACK) walk(each, seen)
74
- }
75
- }
76
- walk(one.argument, new Set())
77
- return words > 0 && !other
78
- }
79
-
80
- function valued(held: Held): readonly Held[] {
81
- const found: Held[] = []
82
- for (const one of held.properties ?? []) {
83
- if (one?.type === 'Property') found.push(one.value as Held)
84
- }
85
- return found
86
- }
87
-
88
- function tabled(held: Held | null | undefined, seen: Set<unknown>): boolean {
89
- const one = bare(held)
90
- if (one === undefined || one.type !== 'ObjectExpression' || seen.has(one)) return false
91
- seen.add(one)
92
- const all = valued(one)
93
- if (all.filter((each) => spoken(each)).length >= LIST) return true
94
- return all.some((each) => strings(each) || tabled(each, seen))
95
- }
96
-
97
- function drawn(held: Held): string | undefined {
98
- if (held.type !== 'NewExpression' || held.callee?.type !== 'Identifier' || held.callee.name !== 'Set') return undefined
99
- const only = held.arguments ?? []
100
- if (only.length !== 1) return undefined
101
- const call = bare(only[0])
102
- const callee = call?.callee
103
- if (call?.type !== 'CallExpression' || callee?.type !== 'MemberExpression') return undefined
104
- if (callee.object?.type !== 'Identifier' || callee.object.name !== 'Object') return undefined
105
- const asked = callee.property?.name
106
- if (asked !== 'keys' && asked !== 'values') return undefined
107
- const from = bare((call.arguments ?? [])[0])
108
- return from?.type === 'Identifier' ? from.name : undefined
109
- }
110
-
111
- function adapted(node: ts.Node): Held | undefined {
112
- if (ts.isAsExpression(node) || ts.isParenthesizedExpression(node)) {
113
- const inner = adapted(node.expression)
114
- return inner === undefined ? undefined : { type: WIDENED, expression: inner }
115
- }
116
- if (ts.isStringLiteral(node)) return { type: 'Literal', value: node.text }
117
- if (ts.isSpreadElement(node)) return { type: SPREAD, argument: adapted(node.expression) ?? null }
118
- if (ts.isCallExpression(node)) {
119
- return { type: 'CallExpression', arguments: node.arguments.map((one) => adapted(one)).filter((one): one is Held => one !== undefined) }
120
- }
121
- if (ts.isArrayLiteralExpression(node)) {
122
- return { type: 'ArrayExpression', elements: node.elements.map((one) => adapted(one) ?? null) }
123
- }
124
- if (ts.isObjectLiteralExpression(node)) {
125
- const properties: Held[] = []
126
- for (const one of node.properties) {
127
- if (!ts.isPropertyAssignment(one)) continue
128
- const value = adapted(one.initializer)
129
- if (value !== undefined) properties.push({ type: 'Property', value })
130
- }
131
- return { type: 'ObjectExpression', properties }
132
- }
133
- return undefined
134
- }
135
-
136
- function parsed(at: string, text: string): ts.SourceFile {
137
- return ts.createSourceFile(at, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)
138
- }
139
-
140
- function brought(source: ts.SourceFile, name: string): string | undefined {
141
- for (const statement of source.statements) {
142
- if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) continue
143
- const bindings = statement.importClause?.namedBindings
144
- if (bindings === undefined || !ts.isNamedImports(bindings)) continue
145
- if (!bindings.elements.some((one) => one.name.text === name)) continue
146
- return statement.moduleSpecifier.text
147
- }
148
- return undefined
149
- }
150
-
151
- function elsewhere(at: SlopnatorContext, name: string): Held | undefined {
152
- const specifier = brought(parsed(at.filename, at.text), name)
153
- if (specifier === undefined || !specifier.startsWith('.')) return undefined
154
- const path = resolve(dirname(at.filename), specifier)
155
- let text: string
156
- try {
157
- text = readFileSync(path, 'utf8')
158
- } catch {
159
- return undefined
160
- }
161
- for (const statement of parsed(path, text).statements) {
162
- if (!ts.isVariableStatement(statement)) continue
163
- for (const one of statement.declarationList.declarations) {
164
- if (!ts.isIdentifier(one.name) || one.name.text !== name || one.initializer === undefined) continue
165
- return adapted(one.initializer)
166
- }
167
- }
168
- return undefined
169
- }
170
-
171
- function initOf(held: unknown, name: string, seen: Set<unknown>): Held | undefined {
172
- if (held === null || typeof held !== 'object' || seen.has(held)) return undefined
173
- seen.add(held)
174
- if (Array.isArray(held)) {
175
- for (const one of held) {
176
- const found = initOf(one, name, seen)
177
- if (found !== undefined) return found
178
- }
179
- return undefined
180
- }
181
- const node = held as Held
182
- const init = node.init ?? undefined
183
- if (node.type === 'VariableDeclarator' && node.id?.type === 'Identifier' && node.id.name === name && init !== undefined) return init
184
- for (const [key, one] of Object.entries(held as Record<string, unknown>)) {
185
- if (key === BACK) continue
186
- const found = initOf(one, name, seen)
187
- if (found !== undefined) return found
188
- }
189
- return undefined
190
- }
191
-
192
- function listed(init: Held | null | undefined, program: unknown, at: SlopnatorContext, through: ReadonlySet<string>): boolean {
193
- const one = bare(init)
194
- if (one === undefined) return false
195
- if (strings(one)) return true
196
- if (tabled(one, new Set())) return true
197
- if (one.type === 'NewExpression' || one.type === 'CallExpression') {
198
- const only = one.arguments ?? []
199
- if (only.length === 1 && strings(only[0])) return true
200
- }
201
- const from = drawn(one)
202
- if (from === undefined || through.has(from)) return false
203
- const followed = new Set(through)
204
- followed.add(from)
205
- const here = initOf(program, from, new Set())
206
- return listed(here ?? elsewhere(at, from), program, at, followed)
207
- }
208
-
209
- const asks = (held: Held | undefined, name: string): boolean => {
210
- const callee = held?.callee
211
- if (held?.type !== 'CallExpression' || callee?.type !== 'MemberExpression') return false
212
- const on = bare(callee.object)
213
- if (on?.type !== 'Identifier' || on.name !== name) return false
214
- const said = callee.property?.name
215
- return said === 'has' || said === 'includes'
216
- }
217
-
218
- const BACK = 'parent'
219
-
220
- function asking(held: unknown, name: string, seen: Set<unknown>): boolean {
221
- if (held === null || typeof held !== 'object' || seen.has(held)) return false
222
- seen.add(held)
223
- if (Array.isArray(held)) return held.some((one) => asking(one, name, seen))
224
- if (asks(held as Held, name)) return true
225
- return Object.entries(held as Record<string, unknown>).some(([key, one]) => key !== BACK && asking(one, name, seen))
226
- }
227
-
228
- function consults(node: Held, name: string): boolean {
229
- if (node.type === 'MemberExpression') {
230
- const on = bare(node.object)
231
- if (on?.type === 'Identifier' && on.name === name) return true
232
- }
233
- const callee = node.callee
234
- if (node.type !== 'CallExpression' || callee?.type !== 'MemberExpression') return false
235
- if (callee.object?.type !== 'Identifier' || callee.object.name !== 'Object') return false
236
- const only = node.arguments ?? []
237
- const from = bare(only[0])
238
- return only.length === 1 && from?.type === 'Identifier' && from.name === name
239
- }
240
-
241
- function consulted(held: unknown, name: string, seen: Set<unknown>): boolean {
242
- if (held === null || typeof held !== 'object' || seen.has(held)) return false
243
- seen.add(held)
244
- if (Array.isArray(held)) return held.some((one) => consulted(one, name, seen))
245
- if (consults(held as Held, name)) return true
246
- return Object.entries(held as Record<string, unknown>).some(([key, one]) => key !== BACK && consulted(one, name, seen))
247
- }
248
-
249
- function continues(held: Held | null | undefined): boolean {
250
- if (held === null || held === undefined) return false
251
- if (held.type === 'ContinueStatement') return true
252
- if (held.type !== 'BlockStatement') return false
253
- return (held.body ?? []).some((one) => one?.type === 'ContinueStatement')
254
- }
255
-
256
- const LEAVES = 'ReturnStatement'
257
-
258
- const THROWN = 'ThrowStatement'
259
-
260
- const leaving = (one: Held | null | undefined): boolean => one?.type === LEAVES || one?.type === THROWN
261
-
262
- function exits(held: Held | null | undefined): boolean {
263
- if (held === null || held === undefined) return false
264
- if (leaving(held)) return true
265
- if (held.type !== 'BlockStatement') return false
266
- return (held.body ?? []).some((one) => leaving(one))
267
- }
268
-
269
- function look(held: unknown, name: string, found: { gates: boolean; skips: boolean }, seen: Set<unknown>): void {
270
- if (held === null || typeof held !== 'object' || seen.has(held)) return
271
- seen.add(held)
272
- if (Array.isArray(held)) {
273
- for (const one of held) look(one, name, found, seen)
274
- return
275
- }
276
- const node = held as Held
277
- if (node.type === 'UnaryExpression' && node.operator === '!' && asks(node.argument ?? undefined, name)) {
278
- found.skips = true
279
- return
280
- }
281
- if (node.type === 'IfStatement' && continues(node.consequent) && asking(node.test, name, new Set())) {
282
- found.skips = true
283
- look(node.consequent, name, found, seen)
284
- look(node.alternate, name, found, seen)
285
- return
286
- }
287
- if (node.type === 'IfStatement' && exits(node.consequent) && asking(node.test, name, new Set())) {
288
- found.gates = true
289
- return
290
- }
291
- if (asks(node, name)) {
292
- found.gates = true
293
- return
294
- }
295
- for (const [key, one] of Object.entries(node as Record<string, unknown>)) {
296
- if (key !== BACK) look(one, name, found, seen)
297
- }
298
- }
299
-
300
- export const slopnator: Slopnator = {
301
- on: ['VariableDeclarator'],
302
- holds: (node, at) => {
303
- const held = node as Held
304
- const program = at.ancestors().find((one) => (one as Held | undefined)?.type === 'Program')
305
- if (!listed(held.init, program, at, new Set<string>())) return false
306
- const named = held.id?.type === 'Identifier' ? held.id.name : undefined
307
- const table = bare(held.init)?.type === 'ObjectExpression'
308
- if (table && (named === undefined || !consulted(program, named, new Set()))) return false
309
- if (named !== undefined) {
310
- const found = { gates: false, skips: false }
311
- look(program, named, found, new Set())
312
- if (!found.gates && found.skips) return false
313
- }
314
- return (
315
- `\`${named ?? 'this'}\` is ${table ? 'a table' : 'a list'} of words written out here and this check refuses what is IN it, so ` +
316
- 'whatever breaks the rule under a name nobody typed walks straight past. Wrapping them in `as const`, nesting them under a key, ' +
317
- 'or building a set out of the keys leaves the same list one step further out of sight. A list that decides what a check CATCHES ' +
318
- 'has to come from the tree: `thread inspect <word>` names the row that coins any word you need, and a check that reads those ' +
319
- 'rows widens when the tree does. A list a check only SKIPS is left alone, because adding a word to one of those makes it ' +
320
- 'stricter and it can never go blind.'
321
- )
322
- },
323
- proves: {
324
- wrong: [
325
- "const CROSSINGS = ['fetch', 'setTimeout', 'setInterval']",
326
- "const STARTS = new Set(['node:child_process', 'child_process'])\nexport const opens = (from: string): boolean => {\n if (!STARTS.has(from)) return false\n return true\n}",
327
- "const GLOBALS = new Set(['Date', 'performance', 'Math'])",
328
- "const MOVES = new Set(['push', 'replace'])\nexport const moved = (one: string): boolean => MOVES.has(one)",
329
- "const SURFACES = ['cli', 'api', 'mcp'] as const\nexport const surfaced = (one: string): boolean => (SURFACES as readonly string[]).includes(one)",
330
- "const MOVING = { globals: ['window', 'globalThis'], histories: ['router', 'history'] }\nexport const moves = (one: string): boolean => MOVING.globals.includes(one)",
331
- "const HANDED_BY = { postgres: 'store', pglite: 'store', https: 'http' }\nexport const handedBy = (reach: string): string => HANDED_BY[reach] ?? reach",
332
- "const HANDS = { postgres: 'store', pglite: 'store' }\nconst PINS = new Set(Object.keys(HANDS))\nexport const pinned = (one: string): boolean => PINS.has(one)",
333
- {
334
- files: [
335
- { at: 'threads/held.ts', source: "export const PINNED = { clock: ['Date.now', 'setTimeout'] }\n" },
336
- { at: 'threads/slopnators/sl_probe_followed.ts', source: "import { PINNED } from '../held.ts'\n\nconst PINS = new Set(Object.keys(PINNED))\nexport const pinned = (one: string): boolean => PINS.has(one)\n" },
337
- ],
338
- at: 'threads/slopnators/sl_probe_followed.ts',
339
- },
340
- ],
341
- right: [
342
- 'const CROSSINGS = declared()',
343
- "const UNDER = 'operations/'",
344
- "const COMMON = new Set(['a', 'the'])\nexport const words = (all: readonly string[]): readonly string[] => all.filter((one) => !COMMON.has(one))",
345
- "const SKIP = new Set(['dist', 'node_modules'])\nexport function walk(names: readonly string[]): number {\n let seen = 0\n for (const name of names) {\n if (SKIP.has(name)) continue\n seen += 1\n }\n return seen\n}",
346
- "const LEFT = ['dist', 'node_modules'] as const\nexport function walked(names: readonly string[]): number {\n let seen = 0\n for (const name of names) {\n if ((LEFT as readonly string[]).includes(name)) continue\n seen += 1\n }\n return seen\n}",
347
- "export const SAYS = { cli: 'runs at a terminal', api: 'answers a request' }",
348
- {
349
- files: [
350
- { at: 'threads/made.ts', source: "export const MADE = declared()\n" },
351
- { at: 'threads/slopnators/sl_probe_derived.ts', source: "import { MADE } from '../made.ts'\n\nconst PICKED = new Set(Object.keys(MADE))\nexport const picked = (one: string): boolean => PICKED.has(one)\n" },
352
- ],
353
- at: 'threads/slopnators/sl_probe_derived.ts',
354
- },
355
- "export const slopnator = { on: ['CallExpression', 'NewExpression'], holds: () => false, proves: { wrong: ['new Date()'], right: ['const at = 1'] } }",
356
- ],
357
- },
358
- }
@@ -1,75 +0,0 @@
1
- import { dirname, join, normalize } from 'node:path'
2
-
3
- import type { Slopnator } from '../slopnator.ts'
4
- import { here, index, parses, unreadable } from './sl_unthreaded.ts'
5
-
6
- const SAID = /'((?:[^'\\\n]|\\.)*)'|"((?:[^"\\\n]|\\.)*)"|`((?:[^`\\$]|\\.)*)`/g
7
-
8
- const RELATIVE = /^\.\.?\//
9
-
10
- const UNDER = 'nodes/'
11
-
12
- const slash = (one: string): string => one.split('\\').join('/')
13
-
14
- function nodeAt(said: string, from: string): string | undefined {
15
- if (said === '') return undefined
16
- const path = slash(normalize(RELATIVE.test(said) ? join(from, said) : said))
17
- return path.startsWith(UNDER) ? path : undefined
18
- }
19
-
20
- export const slopnator: Slopnator = {
21
- holds: (_node, at) => {
22
- const path = here(at.filename)
23
- if (unreadable(path, at.text) !== undefined) return false
24
- if (!parses(path)) return false
25
-
26
- const declared = index().reached
27
- const from = dirname(path)
28
-
29
- for (const found of String(at.text).matchAll(SAID)) {
30
- const named = nodeAt(found[1] ?? found[2] ?? found[3] ?? '', from)
31
- if (named === undefined || declared.has(named)) continue
32
-
33
- return (
34
- `this reaches \`${named}\`, and the descent from the tree root never arrives there — so one of ` +
35
- 'its steps is a node nobody declared. An operation that takes a step nothing agreed to is doing ' +
36
- 'something this app never said it could do, and the flow reads as agreed the whole way down'
37
- )
38
- }
39
-
40
- return false
41
- },
42
- proves: {
43
- wrong: [
44
- "import { node as ghost } from '../nodes/nd_ghost.ts'",
45
- "const held = await import('../nodes/nd_ghost.ts')",
46
- "const held = require('../nodes/nd_ghost.ts')",
47
- "export { node as ghost } from '../nodes/nd_ghost.ts'",
48
- "const AT = '../nodes/nd_ghost.ts'",
49
- "import { node as invented } from 'nodes/nd_nowhere.ts'",
50
- ],
51
- right: [
52
- {
53
- files: [
54
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
55
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_nodes.ts',\n },\n ],\n}\n" },
56
- { at: 'threads/_probe_nodes.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const NODES: Threaded = {\n id: 'nodes',\n declares: [\n {\n id: 'read',\n at: 'nodes/nd_read.ts',\n },\n {\n id: 'action',\n at: 'nodes/nd_action.ts',\n },\n ],\n}\n" },
57
- { at: 'operations/_probe_read.ts', source: "import { node as read } from '../nodes/nd_read.ts'" },
58
- ],
59
- at: 'operations/_probe_read.ts',
60
- },
61
- {
62
- files: [
63
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
64
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_nodes.ts',\n },\n ],\n}\n" },
65
- { at: 'threads/_probe_nodes.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const NODES: Threaded = {\n id: 'nodes',\n declares: [\n {\n id: 'read',\n at: 'nodes/nd_read.ts',\n },\n {\n id: 'action',\n at: 'nodes/nd_action.ts',\n },\n ],\n}\n" },
66
- { at: 'operations/_probe_multi.ts', source: "import { node as action } from '../nodes/nd_action.ts'\nimport { node as read } from '../nodes/nd_read.ts'" },
67
- ],
68
- at: 'operations/_probe_multi.ts',
69
- },
70
- "import { z } from 'zod'",
71
- "import type { Operation } from '../../operations/operations.ts'",
72
- "import type { Held } from '../../operations/nodes.ts'",
73
- ],
74
- },
75
- }
@@ -1,257 +0,0 @@
1
- import type { Slopnator } from '../slopnator.ts'
2
-
3
- import { descend, read, reached } from '../../core/descend.ts'
4
-
5
- import { rowsIn } from './sl_unthreaded.ts'
6
-
7
- const UNDER = 'operations/'
8
-
9
- const DISPATCH = 'dispatch'
10
-
11
- function declared(): ReadonlySet<string> {
12
- const found = new Set<string>()
13
-
14
- descend()
15
-
16
- for (const at of reached()) {
17
- const text = read(at)
18
- if (text === undefined) continue
19
-
20
- for (const one of rowsIn(text)) {
21
- const where = one.at?.split('\\').join('/')
22
- if (where === undefined || !where.includes(UNDER)) continue
23
- if (one.id !== undefined) found.add(one.id)
24
- }
25
- }
26
-
27
- return found
28
- }
29
-
30
- function bodies(): ReadonlySet<string> {
31
- const found = new Set<string>()
32
-
33
- descend()
34
-
35
- for (const at of reached()) {
36
- const text = read(at)
37
- if (text === undefined) continue
38
-
39
- for (const one of rowsIn(text)) {
40
- const where = one.at?.split('\\').join('/')
41
- if (where === undefined || !where.includes(UNDER)) continue
42
- if (one.id !== undefined) found.add(where.replace(/\.[^./]+$/, ''))
43
- }
44
- }
45
-
46
- return found
47
- }
48
-
49
- function declaring(): ReadonlySet<string> {
50
- const found = new Set<string>()
51
-
52
- descend()
53
-
54
- for (const at of reached()) {
55
- const text = read(at)
56
- if (text === undefined) continue
57
-
58
- for (const one of rowsIn(text)) {
59
- const where = one.at?.split('\\').join('/')
60
- if (where === undefined) continue
61
- found.add(where.replace(/\.[^./]+$/, ''))
62
- }
63
- }
64
-
65
- return found
66
- }
67
-
68
- interface Imported {
69
- readonly type?: string
70
- readonly source?: { readonly value?: unknown }
71
- readonly specifiers?: readonly { readonly local?: { readonly name?: unknown } }[]
72
- }
73
-
74
- function importedFrom(name: string, ancestors: readonly unknown[]): string | undefined {
75
- const program = ancestors.find((one) => (one as { readonly type?: string } | undefined)?.type === 'Program') as
76
- | { readonly body?: readonly Imported[] }
77
- | undefined
78
- for (const one of program?.body ?? []) {
79
- if (one.type !== 'ImportDeclaration' || typeof one.source?.value !== 'string') continue
80
- if (!(one.specifiers ?? []).some((each) => each.local?.name === name)) continue
81
- return one.source.value
82
- }
83
- return undefined
84
- }
85
-
86
- interface Typed {
87
- readonly typeName?: { readonly name?: unknown }
88
- readonly typeAnnotation?: Typed
89
- readonly types?: readonly Typed[]
90
- }
91
-
92
- interface Annotated {
93
- readonly type?: string
94
- readonly name?: unknown
95
- readonly typeAnnotation?: Typed
96
- }
97
-
98
- interface Held {
99
- readonly type?: string
100
- readonly params?: readonly Annotated[]
101
- readonly body?: unknown
102
- }
103
-
104
- function namedIn(said: Typed | undefined): string | undefined {
105
- if (said === undefined) return undefined
106
- if (typeof said.typeName?.name === 'string') return said.typeName.name
107
- const inner = namedIn(said.typeAnnotation)
108
- if (inner !== undefined) return inner
109
- for (const one of said.types ?? []) {
110
- const found = namedIn(one)
111
- if (found !== undefined) return found
112
- }
113
- return undefined
114
- }
115
-
116
- const typeNamed = (held: Annotated | undefined): string | undefined => namedIn(held?.typeAnnotation?.typeAnnotation)
117
-
118
- interface Stated {
119
- readonly type?: string
120
- readonly declarations?: readonly { readonly id?: Annotated }[]
121
- }
122
-
123
- function annotating(name: string, ancestors: readonly unknown[]): string | undefined {
124
- for (const held of ancestors) {
125
- for (const param of (held as Held).params ?? []) {
126
- if (param.type !== 'Identifier' || param.name !== name) continue
127
- const said = typeNamed(param)
128
- if (said !== undefined) return said
129
- }
130
-
131
- const body = (held as Held).body
132
- for (const one of Array.isArray(body) ? (body as readonly Stated[]) : []) {
133
- if (one.type !== 'VariableDeclaration') continue
134
- for (const each of one.declarations ?? []) {
135
- if (each.id?.type !== 'Identifier' || each.id.name !== name) continue
136
- const said = typeNamed(each.id)
137
- if (said !== undefined) return said
138
- }
139
- }
140
- }
141
- return undefined
142
- }
143
-
144
- const settled = (from: string, source: string): string => {
145
- if (!source.startsWith('.')) return source.replace(/\.[^./]+$/, '')
146
- const parts = from.split('/').slice(0, -1)
147
- for (const step of source.replace(/\.[^./]+$/, '').split('/')) {
148
- if (step === '.' || step === '') continue
149
- if (step === '..') parts.pop()
150
- else parts.push(step)
151
- }
152
- return parts.join('/')
153
- }
154
-
155
- interface Called {
156
- readonly callee?: { readonly type?: string; readonly name?: string; readonly property?: { readonly type?: string; readonly name?: string } }
157
- readonly arguments?: readonly { readonly type?: string; readonly value?: unknown; readonly name?: string }[]
158
- }
159
-
160
- export const slopnator: Slopnator = {
161
- on: ['CallExpression'],
162
- holds: (node, at) => {
163
- const call = node as Called | undefined
164
- const callee = call?.callee
165
- const named =
166
- callee?.type === 'Identifier'
167
- ? callee.name
168
- : callee?.property?.type === 'Identifier'
169
- ? callee.property.name
170
- : undefined
171
- if (named !== DISPATCH) return false
172
-
173
- const first = call?.arguments?.[0]
174
- if (first === undefined) {
175
- return (
176
- 'dispatch is called with nothing to dispatch, so no operation is named and none can be found — the name ' +
177
- 'is the whole of what makes an operation reachable'
178
- )
179
- }
180
-
181
- const all = declared()
182
-
183
- if (first.type === 'Identifier' && typeof first.name === 'string') {
184
- if (all.has(first.name)) return false
185
- const source = importedFrom(first.name, at.ancestors())
186
- if (source !== undefined && bodies().has(settled(at.filename, source))) return false
187
- const named = annotating(first.name, at.ancestors())
188
- const typed = named === undefined ? undefined : importedFrom(named, at.ancestors())
189
- if (typed !== undefined && declaring().has(settled(at.filename, typed))) return false
190
- return (
191
- `dispatch is handed \`${first.name}\`, and it is not imported from a file any row declares as an operation. ` +
192
- 'A binding is only checkable when it is the operation — import the compiled operation and hand that over, so ' +
193
- 'what runs and what was declared are the same thing rather than two things that agree today'
194
- )
195
- }
196
-
197
- if (first.type !== 'Literal' || typeof first.value !== 'string') {
198
- return (
199
- 'dispatch is handed a name this file does not write down, so nothing can hold it against what is declared. ' +
200
- 'A name assembled at runtime reads from every check exactly like a name that was checked and found — the ' +
201
- 'operation is reachable and no declaration was ever consulted'
202
- )
203
- }
204
-
205
- if (all.has(first.value)) return false
206
-
207
- return (
208
- `dispatch names \`${String(first.value)}\` and nothing declares an operation by that name, so this reaches for ` +
209
- 'something that does not exist and fails where a person is waiting rather than where it was written. ' +
210
- (all.size === 0
211
- ? 'Nothing declares an operation at all, so no name would have been accepted — the declaration is what has to change'
212
- : `Declared: ${[...all].sort().join(', ')}`)
213
- )
214
- },
215
- proves: {
216
- wrong: [
217
- {
218
- files: [
219
- {
220
- at: 'threads/_probe_operations.ts',
221
- source:
222
- "export const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_send_invoice',\n at: 'operations/wf_send_invoice.ts',\n },\n ],\n}\n",
223
- },
224
- { at: 'app/_probe_unknown.tsx', source: "export const HELD = dispatch('wf_not_declared')\n" },
225
- ],
226
- at: 'app/_probe_unknown.tsx',
227
- },
228
- {
229
- files: [
230
- {
231
- at: 'threads/_probe_operations.ts',
232
- source:
233
- "export const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_send_invoice',\n at: 'operations/wf_send_invoice.ts',\n },\n ],\n}\n",
234
- },
235
- { at: 'app/_probe_built.tsx', source: "export const HELD = dispatch(`wf_${name}`)\n" },
236
- ],
237
- at: 'app/_probe_built.tsx',
238
- },
239
- ],
240
- right: [
241
- {
242
- files: [
243
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
244
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_operations.ts',\n },\n ],\n}\n" },
245
- {
246
- at: 'threads/_probe_operations.ts',
247
- source:
248
- "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_send_invoice',\n at: 'operations/wf_send_invoice.ts',\n },\n ],\n}\n",
249
- },
250
- { at: 'app/_probe_known.tsx', source: "export const HELD = dispatch('wf_send_invoice')\n" },
251
- ],
252
- at: 'app/_probe_known.tsx',
253
- },
254
- "export const HELD = translate('order.place')",
255
- ],
256
- },
257
- }