@prestack/thread 0.1.3 → 0.1.5
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/CHANGELOG.md +563 -0
- package/dist/core/actor.d.ts +1 -1
- package/dist/core/actor.js +5 -4
- package/dist/core/actor.ts +5 -4
- package/dist/core/attach.d.ts +0 -7
- package/dist/core/attach.js +38 -67
- package/dist/core/attach.ts +34 -80
- package/dist/core/chain.js +13 -17
- package/dist/core/chain.ts +11 -15
- package/dist/core/condition.d.ts +4 -4
- package/dist/core/condition.js +2 -2
- package/dist/core/descend.d.ts +4 -2
- package/dist/core/descend.js +91 -23
- package/dist/core/descend.ts +90 -21
- package/dist/core/errors/catalogue.d.ts +52 -0
- package/dist/{operations/errors/errors.js → core/errors/catalogue.js} +1 -1
- package/dist/{operations/errors/errors.ts → core/errors/catalogue.ts} +1 -1
- package/dist/{operations/errors/catalog.d.ts → core/errors/error.d.ts} +2 -3
- package/dist/{operations/errors/catalog.js → core/errors/error.js} +1 -16
- package/dist/{operations/errors/catalog.ts → core/errors/error.ts} +3 -19
- package/dist/core/errors/namespaces.d.ts +3 -0
- package/dist/core/errors/namespaces.js +15 -0
- package/dist/core/errors/namespaces.ts +19 -0
- package/dist/core/finding.js +2 -2
- package/dist/core/finding.ts +1 -2
- package/dist/core/init.d.ts +1 -1
- package/dist/core/init.js +51 -5
- package/dist/core/init.ts +58 -5
- package/dist/core/invoked.d.ts +1 -1
- package/dist/core/invoked.js +3 -1
- package/dist/core/invoked.ts +5 -2
- package/dist/core/round.js +36 -22
- package/dist/core/round.ts +26 -9
- package/dist/core/rows.d.ts +4 -0
- package/dist/core/rows.js +31 -2
- package/dist/core/rows.ts +33 -1
- package/dist/core/scope.d.ts +1 -0
- package/dist/core/scope.js +1 -0
- package/dist/core/scope.ts +2 -0
- package/dist/core/thread.d.ts +3 -3
- package/dist/core/tree.d.ts +4 -1
- package/dist/core/tree.js +26 -6
- package/dist/core/tree.ts +29 -6
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +3 -1
- package/dist/core/types.ts +2 -1
- package/dist/derivation/attach.js +3 -2
- package/dist/derivation/attach.ts +2 -1
- package/dist/derivation/generators/build.js +6 -6
- package/dist/derivation/generators/build.ts +1 -1
- package/dist/derivation/generators/check.js +1 -1
- package/dist/derivation/generators/determinism.js +1 -1
- package/dist/derivation/generators/release.js +60 -47
- package/dist/derivation/generators/release.ts +66 -43
- package/dist/derivation/release.d.ts +7 -3
- package/dist/derivation/release.js +14 -1
- package/dist/derivation/release.ts +22 -2
- package/dist/integrations/attach.d.ts +4 -0
- package/dist/integrations/attach.js +37 -56
- package/dist/integrations/attach.ts +54 -60
- package/dist/integrations/cli.d.ts +37 -0
- package/dist/integrations/cli.js +440 -0
- package/dist/integrations/cli.ts +543 -0
- package/dist/{core/files.d.ts → integrations/disk.d.ts} +16 -24
- package/dist/integrations/disk.js +435 -0
- package/dist/integrations/disk.ts +465 -0
- package/dist/integrations/generators/read.js +6 -6
- package/dist/integrations/generators/read.ts +4 -4
- package/dist/integrations/generators/vault.js +10 -11
- package/dist/integrations/generators/vault.ts +4 -5
- package/dist/{operations/http.d.ts → integrations/https.d.ts} +10 -6
- package/dist/integrations/https.js +170 -0
- package/dist/integrations/https.ts +216 -0
- package/dist/integrations/pglite.d.ts +26 -0
- package/dist/integrations/pglite.js +143 -0
- package/dist/integrations/pglite.ts +206 -0
- package/dist/integrations/secret.d.ts +8 -2
- package/dist/integrations/secret.js +3 -5
- package/dist/integrations/secret.ts +10 -6
- package/dist/integrations/target.d.ts +155 -14
- package/dist/integrations/target.js +171 -50
- package/dist/integrations/target.ts +377 -61
- package/dist/integrations/vault.d.ts +1 -0
- package/dist/integrations/vault.js +20 -5
- package/dist/integrations/vault.ts +17 -5
- package/dist/integrity/attach.js +8 -77
- package/dist/integrity/attach.ts +7 -76
- package/dist/integrity/check-threads.js +5 -5
- package/dist/integrity/check-threads.ts +2 -2
- package/dist/integrity/constraint.d.ts +0 -1
- package/dist/integrity/constraint.js +267 -366
- package/dist/integrity/constraint.ts +267 -375
- package/dist/integrity/generators/compiler.js +14 -17
- package/dist/integrity/generators/compiler.ts +10 -15
- package/dist/integrity/generators/gate.js +15 -15
- package/dist/integrity/generators/gate.ts +10 -10
- package/dist/integrity/generators/issues.js +7 -7
- package/dist/integrity/generators/publish.js +39 -57
- package/dist/integrity/generators/publish.ts +44 -55
- package/dist/integrity/generators/trial.d.ts +3 -3
- package/dist/integrity/generators/trial.js +133 -165
- package/dist/integrity/generators/trial.ts +145 -169
- package/dist/integrity/lint.js +9 -13
- package/dist/integrity/lint.ts +7 -10
- package/dist/integrity/lsp/bin.js +5 -4
- package/dist/integrity/lsp/bin.ts +3 -1
- package/dist/integrity/lsp/diagnostics.js +3 -3
- package/dist/integrity/lsp/diagnostics.ts +1 -1
- package/dist/integrity/lsp/lint.js +19 -30
- package/dist/integrity/lsp/lint.ts +16 -32
- package/dist/integrity/lsp/server.js +3 -3
- package/dist/integrity/slopnator.d.ts +3 -13
- package/dist/integrity/slopnator.js +59 -38
- package/dist/integrity/slopnator.ts +54 -38
- package/dist/integrity/slopnators/sl_broken_lineage.js +2 -2
- package/dist/integrity/slopnators/sl_declared_elsewhere.js +1 -1
- package/dist/integrity/slopnators/sl_derived_drift.js +4 -4
- package/dist/integrity/slopnators/sl_derived_drift.ts +1 -1
- package/dist/integrity/slopnators/sl_door_in_a_node.js +220 -0
- package/dist/integrity/slopnators/sl_door_in_a_node.ts +250 -0
- package/dist/integrity/slopnators/sl_inert_island.js +10 -8
- package/dist/integrity/slopnators/sl_inert_island.ts +8 -6
- package/dist/integrity/slopnators/sl_missing_row.js +6 -51
- package/dist/integrity/slopnators/sl_missing_row.ts +4 -50
- package/dist/integrity/slopnators/sl_no_barrel.js +1 -1
- package/dist/integrity/slopnators/sl_no_child_process.js +36 -18
- package/dist/integrity/slopnators/sl_no_child_process.ts +44 -18
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +80 -149
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +95 -147
- package/dist/integrity/slopnators/sl_no_import_cycle.js +15 -4
- package/dist/integrity/slopnators/sl_no_import_cycle.ts +20 -4
- package/dist/integrity/slopnators/sl_no_such_command.js +2 -2
- package/dist/integrity/slopnators/sl_no_such_command.ts +1 -1
- package/dist/integrity/slopnators/sl_outside_an_operation.js +20 -0
- package/dist/integrity/slopnators/sl_outside_an_operation.ts +25 -0
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.d.ts +2 -0
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.js +61 -0
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +70 -0
- package/dist/integrity/slopnators/sl_secret_literal.js +1 -33
- package/dist/integrity/slopnators/sl_secret_literal.ts +1 -33
- package/dist/integrity/slopnators/sl_stale_path.js +5 -22
- package/dist/integrity/slopnators/sl_stale_path.ts +4 -20
- package/dist/integrity/slopnators/sl_twice_meant.js +1 -1
- package/dist/integrity/slopnators/sl_twice_parented.js +29 -5
- package/dist/integrity/slopnators/sl_twice_parented.ts +28 -4
- package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +4 -1
- package/dist/integrity/slopnators/sl_undeclared_crossing.js +426 -62
- package/dist/integrity/slopnators/sl_undeclared_crossing.ts +454 -70
- package/dist/integrity/slopnators/sl_undeclared_destination.js +59 -25
- package/dist/integrity/slopnators/sl_undeclared_destination.ts +69 -21
- package/dist/integrity/slopnators/sl_undeclared_list.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_list.js +363 -0
- package/dist/integrity/slopnators/sl_undeclared_list.ts +358 -0
- package/dist/integrity/slopnators/sl_undeclared_node.js +1 -11
- package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -10
- package/dist/integrity/slopnators/sl_undeclared_operation.js +111 -29
- package/dist/integrity/slopnators/sl_undeclared_operation.ts +134 -26
- package/dist/integrity/slopnators/sl_undeclared_variant.js +2 -2
- package/dist/integrity/slopnators/sl_undeclared_webhook.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_webhook.js +90 -0
- package/dist/integrity/slopnators/sl_undeclared_webhook.ts +108 -0
- package/dist/integrity/slopnators/sl_underived_route.d.ts +2 -0
- package/dist/integrity/slopnators/sl_underived_route.js +61 -0
- package/dist/integrity/slopnators/sl_underived_route.ts +72 -0
- package/dist/integrity/slopnators/sl_ungoverned_island.js +3 -3
- package/dist/integrity/slopnators/sl_ungoverned_island.ts +1 -1
- package/dist/integrity/slopnators/sl_unlisted_program.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unlisted_program.js +39 -0
- package/dist/integrity/slopnators/sl_unlisted_program.ts +40 -0
- package/dist/integrity/slopnators/sl_unnamed_island.js +1 -1
- package/dist/integrity/slopnators/sl_unpaired_member.js +3 -3
- package/dist/integrity/slopnators/sl_unpaired_member.ts +1 -1
- package/dist/integrity/slopnators/sl_unpaired_slopnator.js +4 -4
- package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +1 -1
- package/dist/integrity/slopnators/sl_unprefixed_member.js +5 -31
- package/dist/integrity/slopnators/sl_unprefixed_member.ts +2 -28
- package/dist/integrity/slopnators/sl_unthreaded.js +11 -7
- package/dist/integrity/slopnators/sl_unthreaded.ts +6 -7
- package/dist/integrity/slopnators/sl_untranslated_key.js +2 -25
- package/dist/integrity/slopnators/sl_untranslated_key.ts +1 -25
- package/dist/integrity/slopnators/sl_unvaulted_secret.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unvaulted_secret.js +107 -0
- package/dist/integrity/slopnators/sl_unvaulted_secret.ts +132 -0
- package/dist/integrity/slopnators/sl_unversioned.js +6 -4
- package/dist/integrity/slopnators/sl_unversioned.ts +5 -3
- package/dist/integrity/sweep.d.ts +1 -0
- package/dist/integrity/sweep.js +105 -42
- package/dist/integrity/sweep.ts +115 -42
- package/dist/knowledge/attach.js +4 -3
- package/dist/knowledge/attach.ts +3 -2
- package/dist/knowledge/generators/docsgen.js +8 -6
- package/dist/knowledge/generators/docsgen.ts +4 -2
- package/dist/knowledge/generators/glossary.js +4 -4
- package/dist/knowledge/generators/glossary.ts +1 -1
- package/dist/operations/access.d.ts +23 -0
- package/dist/operations/access.js +28 -1
- package/dist/operations/access.ts +43 -0
- package/dist/operations/attach.js +33 -113
- package/dist/operations/attach.ts +51 -117
- package/dist/operations/capabilities.d.ts +14 -5
- package/dist/operations/capabilities.js +13 -4
- package/dist/operations/capabilities.ts +27 -8
- package/dist/operations/dispatch.d.ts +8 -2
- package/dist/operations/dispatch.js +128 -185
- package/dist/operations/dispatch.ts +138 -191
- package/dist/operations/error-results.d.ts +1 -1
- package/dist/operations/error-results.js +2 -2
- package/dist/operations/error-results.ts +2 -2
- package/dist/operations/generators/capability.d.ts +11 -1
- package/dist/operations/generators/capability.js +481 -224
- package/dist/operations/generators/capability.ts +562 -233
- package/dist/operations/generators/compile.js +66 -39
- package/dist/operations/generators/compile.ts +78 -39
- package/dist/operations/generators/permits.js +11 -12
- package/dist/operations/generators/permits.ts +3 -4
- package/dist/operations/generators/seed.d.ts +1 -0
- package/dist/operations/generators/seed.js +19 -17
- package/dist/operations/generators/seed.ts +22 -14
- package/dist/operations/generators/trial.d.ts +6 -8
- package/dist/operations/generators/trial.js +200 -70
- package/dist/operations/generators/trial.ts +230 -93
- package/dist/operations/nodes.d.ts +15 -16
- package/dist/operations/nodes.ts +16 -17
- package/dist/operations/operations.d.ts +40 -4
- package/dist/operations/operations.js +1 -0
- package/dist/operations/operations.ts +61 -4
- package/dist/operations/result.d.ts +2 -2
- package/dist/operations/result.ts +2 -2
- package/package.json +1 -4
- package/dist/core/answers.d.ts +0 -171
- package/dist/core/answers.js +0 -789
- package/dist/core/answers.ts +0 -999
- package/dist/core/client.d.ts +0 -63
- package/dist/core/client.js +0 -249
- package/dist/core/client.ts +0 -289
- package/dist/core/files.js +0 -171
- package/dist/core/files.ts +0 -222
- package/dist/integrations/module.d.ts +0 -6
- package/dist/integrations/module.js +0 -153
- package/dist/integrations/module.ts +0 -191
- package/dist/integrity/slopnators/sl_unkept_rows.js +0 -104
- package/dist/integrity/slopnators/sl_unkept_rows.ts +0 -111
- package/dist/integrity/slopnators/sl_unseamed_reach.js +0 -73
- package/dist/integrity/slopnators/sl_unseamed_reach.ts +0 -79
- package/dist/operations/cost.d.ts +0 -14
- package/dist/operations/cost.js +0 -37
- package/dist/operations/cost.ts +0 -58
- package/dist/operations/errors/errors.d.ts +0 -52
- package/dist/operations/http.js +0 -124
- package/dist/operations/http.ts +0 -169
- package/dist/operations/memory.d.ts +0 -104
- package/dist/operations/memory.js +0 -365
- package/dist/operations/memory.ts +0 -470
- package/dist/operations/metering.d.ts +0 -55
- package/dist/operations/metering.js +0 -247
- package/dist/operations/metering.ts +0 -328
- package/dist/operations/queue-policy.d.ts +0 -3
- package/dist/operations/queue-policy.js +0 -8
- package/dist/operations/queue-policy.ts +0 -10
- package/dist/operations/seams.d.ts +0 -239
- package/dist/operations/seams.js +0 -45
- package/dist/operations/seams.ts +0 -364
- package/dist/operations/store.d.ts +0 -14
- package/dist/operations/store.js +0 -1
- package/dist/operations/store.ts +0 -29
- /package/dist/{operations → core}/errors/classes.d.ts +0 -0
- /package/dist/{operations → core}/errors/classes.js +0 -0
- /package/dist/{operations → core}/errors/classes.ts +0 -0
- /package/dist/integrity/slopnators/{sl_unkept_rows.d.ts → sl_door_in_a_node.d.ts} +0 -0
- /package/dist/integrity/slopnators/{sl_unseamed_reach.d.ts → sl_outside_an_operation.d.ts} +0 -0
|
@@ -3,6 +3,7 @@ import { dirname, join, normalize } from 'node:path'
|
|
|
3
3
|
import { parser } from 'typescript-eslint'
|
|
4
4
|
|
|
5
5
|
import { descend } from '../../core/descend.ts'
|
|
6
|
+
import { PREFIXES } from '../../core/thread.ts'
|
|
6
7
|
|
|
7
8
|
import type { Slopnator } from '../slopnator.ts'
|
|
8
9
|
import { here, index, parses, unreadable } from './sl_unthreaded.ts'
|
|
@@ -11,15 +12,8 @@ const RELATIVE = /^\.\.?\//
|
|
|
11
12
|
|
|
12
13
|
const UNDER = 'operations/'
|
|
13
14
|
|
|
14
|
-
const
|
|
15
|
+
const COMPILES = 'thread compile'
|
|
15
16
|
|
|
16
|
-
/*
|
|
17
|
-
* The one thing an operation may be handed to.
|
|
18
|
-
*
|
|
19
|
-
* It is still `dispatch` and it is still the only one. What moved 2026-08-28 is where it lives:
|
|
20
|
-
* it was imported from this framework, and it is now the app's own `operations/_dispatch.ts`,
|
|
21
|
-
* written by `thread compile`. The name a caller writes did not change, so neither did this.
|
|
22
|
-
*/
|
|
23
17
|
const INVOKER = 'dispatch'
|
|
24
18
|
|
|
25
19
|
const slash = (one: string): string => one.split('\\').join('/')
|
|
@@ -34,49 +28,24 @@ interface Held {
|
|
|
34
28
|
readonly quasis?: readonly { readonly value?: { readonly cooked?: string; readonly raw?: string } }[]
|
|
35
29
|
}
|
|
36
30
|
|
|
37
|
-
|
|
38
|
-
const BINDS = new Set(['ImportSpecifier', 'ImportDefaultSpecifier', 'ImportNamespaceSpecifier'])
|
|
31
|
+
const IMPORTED = 'ImportBinding'
|
|
39
32
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
* `const one: typeof operation` runs nothing and reaches nothing — it borrows the type and is erased
|
|
44
|
-
* before anything executes. Refusing it was a false positive found by reading, not by running: the
|
|
45
|
-
* probe written to test the reassignment form used exactly this line to type its own variable, and
|
|
46
|
-
* the law would have refused the probe for its type annotation rather than for the bypass under it.
|
|
47
|
-
*/
|
|
48
|
-
const TYPED = new Set(['TSTypeQuery', 'TSTypeReference', 'TSQualifiedName', 'TSImportType'])
|
|
33
|
+
const TYPE_ONLY = 'type'
|
|
34
|
+
|
|
35
|
+
const QUERIED = 'TSTypeQuery'
|
|
49
36
|
|
|
50
37
|
let bodies: ReadonlySet<string> | undefined
|
|
51
38
|
|
|
52
|
-
/*
|
|
53
|
-
* Which files are operation bodies, asked of the tree rather than guessed from a directory.
|
|
54
|
-
*
|
|
55
|
-
* An operation body is a file a row beneath `operations` DERIVES and that is not a node's body, and
|
|
56
|
-
* the descent already knows every one of them. Reading the directory instead was close enough while
|
|
57
|
-
* an operation was the only thing living there — and stopped being close enough the moment an
|
|
58
|
-
* operation was folded into a body plus the steps it is woven from: the woven body imports its own
|
|
59
|
-
* steps, the steps sat under `operations/`, and this law refused the one file it exists to protect
|
|
60
|
-
* for importing the one file it is made of. A rule that refuses the sanctioned form is deciding
|
|
61
|
-
* nothing, which is the same failure this law was rewritten for on 2026-08-25.
|
|
62
|
-
*/
|
|
63
39
|
function operations(): ReadonlySet<string> {
|
|
64
40
|
if (bodies !== undefined) return bodies
|
|
65
|
-
const pass = descend()
|
|
66
|
-
|
|
67
|
-
const under = (id: string, root: string, seen: Set<string>): boolean => {
|
|
68
|
-
if (id === root) return true
|
|
69
|
-
if (seen.has(id)) return false
|
|
70
|
-
seen.add(id)
|
|
71
|
-
return (pass.coined.get(id)?.from ?? []).some((one) => under(one, root, seen))
|
|
72
|
-
}
|
|
73
41
|
|
|
42
|
+
const prefixed = Object.values(PREFIXES)
|
|
74
43
|
const found = new Set<string>()
|
|
75
|
-
for (const [at, threadings] of
|
|
44
|
+
for (const [at, threadings] of descend().threaded) {
|
|
76
45
|
for (const one of threadings) {
|
|
77
46
|
if (one.list !== 'derives') continue
|
|
78
|
-
if (
|
|
79
|
-
if (
|
|
47
|
+
if (one.row.by !== COMPILES) continue
|
|
48
|
+
if (!prefixed.some((each) => one.by.startsWith(each))) continue
|
|
80
49
|
found.add(at)
|
|
81
50
|
}
|
|
82
51
|
}
|
|
@@ -84,31 +53,35 @@ function operations(): ReadonlySet<string> {
|
|
|
84
53
|
return found
|
|
85
54
|
}
|
|
86
55
|
|
|
87
|
-
/*
|
|
88
|
-
* Whether a path is an operation body.
|
|
89
|
-
*
|
|
90
|
-
* The tree decides for every file the tree reaches. For a file it does not reach — a probe, a
|
|
91
|
-
* scratch file, anything not yet declared — the shape decides: an operation is seeded as one file
|
|
92
|
-
* directly inside `operations/`, so a single segment is a body and a deeper path is something an
|
|
93
|
-
* operation is built from. Nothing the tree does not reach can be part of an app anyway; that
|
|
94
|
-
* fallback is what makes this law's own proofs mean the same thing in every repository.
|
|
95
|
-
*/
|
|
96
56
|
function isBody(path: string): boolean {
|
|
97
57
|
const known = index().reached
|
|
98
58
|
if (known.has(path)) return operations().has(path)
|
|
99
59
|
if (!path.startsWith(UNDER)) return false
|
|
100
|
-
|
|
60
|
+
const file = path.slice(UNDER.length)
|
|
61
|
+
return !file.includes('/') && Object.values(PREFIXES).some((one) => file.startsWith(one))
|
|
101
62
|
}
|
|
102
63
|
|
|
103
|
-
|
|
64
|
+
let endings: ReadonlyMap<string, string> | undefined
|
|
65
|
+
|
|
66
|
+
function spellings(): ReadonlyMap<string, string> {
|
|
67
|
+
if (endings !== undefined) return endings
|
|
68
|
+
const found = new Map<string, string>()
|
|
69
|
+
for (const one of index().reached) {
|
|
70
|
+
const dot = one.lastIndexOf('.')
|
|
71
|
+
if (dot > one.lastIndexOf('/')) found.set(one.slice(0, dot), one)
|
|
72
|
+
}
|
|
73
|
+
endings = found
|
|
74
|
+
return found
|
|
75
|
+
}
|
|
104
76
|
|
|
105
77
|
function reaches(said: string, from: string): boolean {
|
|
106
78
|
if (said === '') return false
|
|
107
79
|
const at = slash(normalize(RELATIVE.test(said) ? join(from, said) : said))
|
|
108
|
-
|
|
80
|
+
if (isBody(at)) return true
|
|
81
|
+
const whole = spellings().get(at)
|
|
82
|
+
return whole !== undefined && isBody(whole)
|
|
109
83
|
}
|
|
110
84
|
|
|
111
|
-
/* Whatever a run-time reach spells out, whole or in pieces. */
|
|
112
85
|
function spelled(node: Held | undefined, said: string[]): void {
|
|
113
86
|
if (node === undefined || node === null || typeof node !== 'object') return
|
|
114
87
|
if (node.type === 'Literal' && typeof node.value === 'string') said.push(node.value)
|
|
@@ -122,59 +95,43 @@ function spelled(node: Held | undefined, said: string[]): void {
|
|
|
122
95
|
}
|
|
123
96
|
}
|
|
124
97
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const clause = one[1] ?? ''
|
|
158
|
-
if (/^type\s/.test(clause)) continue
|
|
159
|
-
if (!reaches(one[2] ?? '', from)) continue
|
|
160
|
-
for (const name of clause.matchAll(LOCAL)) {
|
|
161
|
-
const held = name[1]
|
|
162
|
-
if (held !== undefined && !NOT_A_NAME.has(held)) found.add(held)
|
|
98
|
+
interface Bound {
|
|
99
|
+
readonly type?: string
|
|
100
|
+
readonly node?: { readonly importKind?: string }
|
|
101
|
+
readonly parent?: { readonly importKind?: string; readonly source?: { readonly value?: unknown } }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface Named {
|
|
105
|
+
readonly defs?: readonly Bound[]
|
|
106
|
+
readonly references?: readonly { readonly identifier?: unknown; readonly isValueReference?: boolean }[]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface Scoped {
|
|
110
|
+
readonly scopes?: readonly { readonly variables?: readonly Named[] }[]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface Parsed {
|
|
114
|
+
readonly ast: unknown
|
|
115
|
+
readonly scopeManager?: Scoped
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function handled(scopes: Scoped | undefined, from: string): ReadonlySet<unknown> {
|
|
119
|
+
const found = new Set<unknown>()
|
|
120
|
+
for (const scope of scopes?.scopes ?? []) {
|
|
121
|
+
for (const one of scope.variables ?? []) {
|
|
122
|
+
const made = (one.defs ?? [])[0]
|
|
123
|
+
if (made?.type !== IMPORTED) continue
|
|
124
|
+
if (made.node?.importKind === TYPE_ONLY || made.parent?.importKind === TYPE_ONLY) continue
|
|
125
|
+
const said = made.parent?.source?.value
|
|
126
|
+
if (typeof said !== 'string' || !reaches(said, from)) continue
|
|
127
|
+
for (const seen of one.references ?? []) {
|
|
128
|
+
if (seen.isValueReference !== false && seen.identifier !== undefined) found.add(seen.identifier)
|
|
129
|
+
}
|
|
163
130
|
}
|
|
164
131
|
}
|
|
165
132
|
return found
|
|
166
133
|
}
|
|
167
134
|
|
|
168
|
-
/*
|
|
169
|
-
* Parsed here rather than taken from the visitor.
|
|
170
|
-
*
|
|
171
|
-
* `holds` is handed a node when a law runs as an eslint rule and nothing at all when the sweep runs
|
|
172
|
-
* it over a file — measured 2026-08-25, the sweep calls it once per file with `node` undefined, so a
|
|
173
|
-
* reading that only inspected what it was given threw on the first file it saw. What a file DOES
|
|
174
|
-
* with a binding is a question about the whole file anyway, not about one node in it.
|
|
175
|
-
*
|
|
176
|
-
* The parent comes down with the node because the whole rule is about where a name appears.
|
|
177
|
-
*/
|
|
178
135
|
function walked(
|
|
179
136
|
node: unknown,
|
|
180
137
|
up: Held | undefined,
|
|
@@ -211,42 +168,23 @@ export const slopnator: Slopnator = {
|
|
|
211
168
|
if (!text.includes(UNDER)) return false
|
|
212
169
|
|
|
213
170
|
const from = dirname(path)
|
|
214
|
-
const mine = bound(text, from)
|
|
215
171
|
|
|
216
|
-
let
|
|
172
|
+
let parsed: Parsed
|
|
217
173
|
try {
|
|
218
|
-
|
|
174
|
+
parsed = (parser.parseForESLint as (t: string, o: unknown) => Parsed)(text, {
|
|
219
175
|
range: true,
|
|
220
176
|
ecmaVersion: 'latest',
|
|
221
177
|
sourceType: 'module',
|
|
222
178
|
ecmaFeatures: { jsx: true },
|
|
223
|
-
})
|
|
179
|
+
})
|
|
224
180
|
} catch {
|
|
225
181
|
return false
|
|
226
182
|
}
|
|
227
183
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
*
|
|
231
|
-
* The first reading of this enumerated the SHAPES of a bypass — a member access, a call, an
|
|
232
|
-
* argument — and every shape it did not think of walked through: an alias, a destructure, an
|
|
233
|
-
* array holding the name, a comma operator. Enumerating forms is how a refusal ends up with a
|
|
234
|
-
* list of everything somebody happened to try. The rule is not a list. An operation goes to
|
|
235
|
-
* `dispatch` and nowhere else, so the name may appear where it is bound and as an argument to
|
|
236
|
-
* `dispatch`, and every other appearance is the finding — whatever it happens to look like.
|
|
237
|
-
*/
|
|
184
|
+
const mine = handled(parsed.scopeManager, from)
|
|
185
|
+
|
|
238
186
|
return (
|
|
239
|
-
walked(ast, undefined, (held, parent) => {
|
|
240
|
-
/*
|
|
241
|
-
* A body reached at run time never binds a name at all, so the rule above cannot see it.
|
|
242
|
-
*
|
|
243
|
-
* What is read is everything the reach spells out, whole or in pieces: a plain literal, a
|
|
244
|
-
* concatenation, a template. Measured 2026-08-26 against the reading this replaces, which
|
|
245
|
-
* only ever inspected a plain `Literal` — `await import('../operations/' + name + '.ts')`
|
|
246
|
-
* passed it without a word, which is the exact form its own comment said it was written to
|
|
247
|
-
* catch. Nothing legitimate needs any of it: dispatch is handed the operation, and the one
|
|
248
|
-
* file that hands it over imports it statically.
|
|
249
|
-
*/
|
|
187
|
+
walked(parsed.ast, undefined, (held, parent) => {
|
|
250
188
|
const dynamic =
|
|
251
189
|
held.type === 'ImportExpression'
|
|
252
190
|
? held.source
|
|
@@ -266,9 +204,8 @@ export const slopnator: Slopnator = {
|
|
|
266
204
|
}
|
|
267
205
|
}
|
|
268
206
|
|
|
269
|
-
if (held.type !== 'Identifier' || typeof held.name !== 'string' || !mine.has(held
|
|
270
|
-
|
|
271
|
-
if (BINDS.has(up) || TYPED.has(up)) return undefined
|
|
207
|
+
if (held.type !== 'Identifier' || typeof held.name !== 'string' || !mine.has(held)) return undefined
|
|
208
|
+
if (parent?.type === QUERIED) return undefined
|
|
272
209
|
if (
|
|
273
210
|
parent?.type === 'CallExpression' &&
|
|
274
211
|
parent.callee?.type === 'Identifier' &&
|
|
@@ -286,23 +223,10 @@ export const slopnator: Slopnator = {
|
|
|
286
223
|
}) ?? false
|
|
287
224
|
)
|
|
288
225
|
},
|
|
289
|
-
/*
|
|
290
|
-
* These were driven at `holds` directly, because the trial harness cannot run them.
|
|
291
|
-
*
|
|
292
|
-
* A shipped law is meant to be proved by `thread trials`, and today nothing proves one anywhere:
|
|
293
|
-
* the harness cannot run in this repository at all — the framework has no tree, and the command
|
|
294
|
-
* that once tried was removed in 6e98a6d after it was measured exiting 1 — and in an app
|
|
295
|
-
* `thread trials` answers "0 trials from the 0 rule(s) this tree declares",
|
|
296
|
-
* because a shipped constraint is proved in the package that ships it. So every proof below was
|
|
297
|
-
* written to a real probe file and handed to `slopnator.holds` by hand, 2026-08-26: fourteen
|
|
298
|
-
* refused, six accepted, no failures. That is a stand-in for the harness and it is written down
|
|
299
|
-
* here rather than only in a report, because a proof nobody can run is indistinguishable from a
|
|
300
|
-
* proof that passes, and the next person to read this list deserves to know which one it is.
|
|
301
|
-
*/
|
|
302
226
|
proves: {
|
|
303
227
|
wrong: [
|
|
304
228
|
"import { operation } from './operations/wf_send_invoice.ts'\n\nawait operation.run(input)",
|
|
305
|
-
"import { operation } from './operations/wf_send_invoice.ts'\n\nconst { run } = operation\n\nawait run(
|
|
229
|
+
"import { operation } from './operations/wf_send_invoice.ts'\n\nconst { run } = operation\n\nawait run(opened, held)",
|
|
306
230
|
"import { operation } from './operations/wf_send_invoice.ts'\n\nawait operation(input)",
|
|
307
231
|
"import { operation } from './operations/wf_send_invoice.ts'\n\nawait somewhere(operation, input)",
|
|
308
232
|
"import { operation } from './operations/wf_send_invoice.ts'\n\nconst one = operation\n\nawait one.run(input)",
|
|
@@ -317,10 +241,34 @@ export const slopnator: Slopnator = {
|
|
|
317
241
|
'const held = await import(`../operations/${name}.ts`)',
|
|
318
242
|
],
|
|
319
243
|
right: [
|
|
320
|
-
|
|
244
|
+
{
|
|
245
|
+
files: [
|
|
246
|
+
{ 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" },
|
|
247
|
+
{ 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" },
|
|
248
|
+
{ at: 'threads/_probe_operations.ts', source: "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 describes: 'sends an invoice',\n derives: [\n {\n id: 'wf_send_invoice_body',\n at: 'operations/wf_send_invoice.ts',\n by: 'thread compile',\n },\n ],\n },\n ],\n}\n" },
|
|
249
|
+
{ at: 'probe.tsx', source: "import { dispatch } from './operations/_dispatch.ts'\n\nimport { operation } from './operations/wf_send_invoice.ts'\n\nawait dispatch(operation, input, actor, opened)\n" },
|
|
250
|
+
],
|
|
251
|
+
at: 'probe.tsx',
|
|
252
|
+
},
|
|
321
253
|
"import type { Operation } from '../../operations/operations.ts'\n\nexport const held: Operation = one",
|
|
322
|
-
|
|
323
|
-
|
|
254
|
+
{
|
|
255
|
+
files: [
|
|
256
|
+
{ 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" },
|
|
257
|
+
{ 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" },
|
|
258
|
+
{ at: 'threads/_probe_operations.ts', source: "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 describes: 'sends an invoice',\n derives: [\n {\n id: 'wf_send_invoice_body',\n at: 'operations/wf_send_invoice.ts',\n by: 'thread compile',\n },\n ],\n },\n ],\n}\n" },
|
|
259
|
+
{ at: 'probe.tsx', source: "import { dispatch } from './operations/_dispatch.ts'\n\nawait dispatch(wf_send_invoice_body, input, actor, opened)\n" },
|
|
260
|
+
],
|
|
261
|
+
at: 'probe.tsx',
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
files: [
|
|
265
|
+
{ 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" },
|
|
266
|
+
{ 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" },
|
|
267
|
+
{ at: 'threads/_probe_operations.ts', source: "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 describes: 'sends an invoice',\n derives: [\n {\n id: 'wf_send_invoice_body',\n at: 'operations/wf_send_invoice.ts',\n by: 'thread compile',\n },\n ],\n },\n ],\n}\n" },
|
|
268
|
+
{ at: 'probe.tsx', source: "import { dispatch } from './operations/_dispatch.ts'\n\nimport { operation } from './operations/wf_send_invoice.ts'\n\nlet one: typeof operation\n\nawait dispatch(operation, input, actor, opened)\n" },
|
|
269
|
+
],
|
|
270
|
+
at: 'probe.tsx',
|
|
271
|
+
},
|
|
324
272
|
"import * as steps from './operations/wf_send_invoice/steps.ts'\n\nexport const held = steps.reads_the_invoice",
|
|
325
273
|
"import * as steps from './steps/wf_send_invoice.ts'\n\nexport const held = steps.reads_the_invoice",
|
|
326
274
|
],
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from
|
|
1
|
+
import { existsSync, readFileSync } from '../../integrations/disk.js';
|
|
2
2
|
import { dirname, resolve } from 'node:path';
|
|
3
|
-
import { findTree } from
|
|
3
|
+
import { findTree } from '../../core/tree.js';
|
|
4
4
|
const ROOT = findTree().at;
|
|
5
|
-
const IMPORTS = /(?:^|\n)\s*import\s[^'"]*['"]([^'"]+)['"]/g;
|
|
5
|
+
const IMPORTS = /(?:^|\n)\s*import\s+(?!type\s)[^'"]*['"]([^'"]+)['"]/g;
|
|
6
6
|
const target = (from, spec) => spec.startsWith('.') ? resolve(dirname(from), spec) : undefined;
|
|
7
|
+
const erased = (node) => node.importKind === 'type' ||
|
|
8
|
+
((node.specifiers ?? []).length > 0 && (node.specifiers ?? []).every((one) => one.importKind === 'type'));
|
|
7
9
|
export const slopnator = {
|
|
8
10
|
on: ['ImportDeclaration'],
|
|
9
11
|
holds: (node, at) => {
|
|
12
|
+
if (erased(node))
|
|
13
|
+
return false;
|
|
10
14
|
const spec = node.source?.value;
|
|
11
15
|
if (typeof spec !== 'string')
|
|
12
16
|
return false;
|
|
@@ -34,9 +38,16 @@ export const slopnator = {
|
|
|
34
38
|
right: [
|
|
35
39
|
"import type { Law } from '../../core/generators/meta.ts'",
|
|
36
40
|
"import { SLUG, type Shaped } from '../../core/types.ts'",
|
|
37
|
-
"import {
|
|
41
|
+
"import { join } from 'node:path'",
|
|
38
42
|
"import type { Rule } from 'eslint'",
|
|
39
43
|
"import { CONSTRAINTS } from '../constraint.ts'",
|
|
44
|
+
{
|
|
45
|
+
files: [
|
|
46
|
+
{ at: 'threads/_probe_three.ts', source: "import type { Four } from './_probe_four.ts'\nexport const three: Four = 1\n" },
|
|
47
|
+
{ at: 'threads/_probe_four.ts', source: "import { three } from './_probe_three.ts'\nexport type Four = number\nexport const four = three\n" },
|
|
48
|
+
],
|
|
49
|
+
at: 'threads/_probe_three.ts',
|
|
50
|
+
},
|
|
40
51
|
],
|
|
41
52
|
},
|
|
42
53
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from '../../
|
|
1
|
+
import { existsSync, readFileSync } from '../../integrations/disk.ts'
|
|
2
2
|
import { dirname, resolve } from 'node:path'
|
|
3
3
|
|
|
4
4
|
import type { Slopnator } from '../slopnator.ts'
|
|
@@ -6,16 +6,25 @@ import { findTree } from '../../core/tree.ts'
|
|
|
6
6
|
|
|
7
7
|
const ROOT = findTree().at
|
|
8
8
|
|
|
9
|
-
const IMPORTS = /(?:^|\n)\s*import\s[^'"]*['"]([^'"]+)['"]/g
|
|
9
|
+
const IMPORTS = /(?:^|\n)\s*import\s+(?!type\s)[^'"]*['"]([^'"]+)['"]/g
|
|
10
10
|
|
|
11
|
-
type Node = {
|
|
11
|
+
type Node = {
|
|
12
|
+
source?: { value?: unknown }
|
|
13
|
+
importKind?: string
|
|
14
|
+
specifiers?: readonly { importKind?: string }[]
|
|
15
|
+
}
|
|
12
16
|
|
|
13
17
|
const target = (from: string, spec: string): string | undefined =>
|
|
14
18
|
spec.startsWith('.') ? resolve(dirname(from), spec) : undefined
|
|
15
19
|
|
|
20
|
+
const erased = (node: Node): boolean =>
|
|
21
|
+
node.importKind === 'type' ||
|
|
22
|
+
((node.specifiers ?? []).length > 0 && (node.specifiers ?? []).every((one) => one.importKind === 'type'))
|
|
23
|
+
|
|
16
24
|
export const slopnator: Slopnator = {
|
|
17
25
|
on: ['ImportDeclaration'],
|
|
18
26
|
holds: (node, at) => {
|
|
27
|
+
if (erased(node as Node)) return false
|
|
19
28
|
const spec = (node as Node).source?.value
|
|
20
29
|
if (typeof spec !== 'string') return false
|
|
21
30
|
|
|
@@ -42,9 +51,16 @@ export const slopnator: Slopnator = {
|
|
|
42
51
|
right: [
|
|
43
52
|
"import type { Law } from '../../core/generators/meta.ts'",
|
|
44
53
|
"import { SLUG, type Shaped } from '../../core/types.ts'",
|
|
45
|
-
"import {
|
|
54
|
+
"import { join } from 'node:path'",
|
|
46
55
|
"import type { Rule } from 'eslint'",
|
|
47
56
|
"import { CONSTRAINTS } from '../constraint.ts'",
|
|
57
|
+
{
|
|
58
|
+
files: [
|
|
59
|
+
{ at: 'threads/_probe_three.ts', source: "import type { Four } from './_probe_four.ts'\nexport const three: Four = 1\n" },
|
|
60
|
+
{ at: 'threads/_probe_four.ts', source: "import { three } from './_probe_three.ts'\nexport type Four = number\nexport const four = three\n" },
|
|
61
|
+
],
|
|
62
|
+
at: 'threads/_probe_three.ts',
|
|
63
|
+
},
|
|
48
64
|
],
|
|
49
65
|
},
|
|
50
66
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { readFileSync } from
|
|
1
|
+
import { readFileSync } from '../../integrations/disk.js';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import { findTree } from
|
|
3
|
+
import { findTree } from '../../core/tree.js';
|
|
4
4
|
const ROOT = findTree().at;
|
|
5
5
|
const keyOf = (property) => {
|
|
6
6
|
if (property.computed === true)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { HANDED_TO, judging } from './sl_undeclared_crossing.js';
|
|
2
|
+
const SAYS = 'Each of these uses what this app hands an operation, and an operation is the only thing handed one.';
|
|
3
|
+
export const slopnator = {
|
|
4
|
+
holds: (_node, at) => judging(at, HANDED_TO, SAYS),
|
|
5
|
+
proves: {
|
|
6
|
+
wrong: [
|
|
7
|
+
{
|
|
8
|
+
files: [
|
|
9
|
+
{ at: 'thread/operations/operations.ts', source: 'export interface Store {\n insert(row: unknown): Promise<void>\n}\n\nexport type Opened<R extends string = never> = { readonly [K in R]: Store }\n' },
|
|
10
|
+
{ at: 'threads/_probe_reach.ts', source: "import type { Store } from '../thread/operations/operations.ts'\n\nexport const seeded = async (kept: Store): Promise<void> => {\n await kept['insert']({})\n}\n" },
|
|
11
|
+
],
|
|
12
|
+
at: 'threads/_probe_reach.ts',
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
right: [
|
|
16
|
+
'export const began = Date.now()',
|
|
17
|
+
"import { join } from 'node:path'\n\nexport const at = join('a', 'b')",
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Slopnator } from '../slopnator.ts'
|
|
2
|
+
|
|
3
|
+
import { HANDED_TO, judging } from './sl_undeclared_crossing.ts'
|
|
4
|
+
|
|
5
|
+
const SAYS =
|
|
6
|
+
'Each of these uses what this app hands an operation, and an operation is the only thing handed one.'
|
|
7
|
+
|
|
8
|
+
export const slopnator: Slopnator = {
|
|
9
|
+
holds: (_node, at) => judging(at, HANDED_TO, SAYS),
|
|
10
|
+
proves: {
|
|
11
|
+
wrong: [
|
|
12
|
+
{
|
|
13
|
+
files: [
|
|
14
|
+
{ at: 'thread/operations/operations.ts', source: 'export interface Store {\n insert(row: unknown): Promise<void>\n}\n\nexport type Opened<R extends string = never> = { readonly [K in R]: Store }\n' },
|
|
15
|
+
{ at: 'threads/_probe_reach.ts', source: "import type { Store } from '../thread/operations/operations.ts'\n\nexport const seeded = async (kept: Store): Promise<void> => {\n await kept['insert']({})\n}\n" },
|
|
16
|
+
],
|
|
17
|
+
at: 'threads/_probe_reach.ts',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
right: [
|
|
21
|
+
'export const began = Date.now()',
|
|
22
|
+
"import { join } from 'node:path'\n\nexport const at = join('a', 'b')",
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { descend, read, reached } from '../../core/descend.js';
|
|
2
|
+
import { rowsIn } from './sl_unthreaded.js';
|
|
3
|
+
const WRAPS = 'screen';
|
|
4
|
+
const HANDS = 'dispatch';
|
|
5
|
+
const ELEMENT = 'element';
|
|
6
|
+
const COMPOSITION = 'composition';
|
|
7
|
+
const nameOf = (id) => id.split('-').map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join('');
|
|
8
|
+
function declared() {
|
|
9
|
+
const found = new Set();
|
|
10
|
+
descend();
|
|
11
|
+
for (const at of reached()) {
|
|
12
|
+
const text = read(at);
|
|
13
|
+
if (text === undefined)
|
|
14
|
+
continue;
|
|
15
|
+
let waiting = [];
|
|
16
|
+
for (const one of rowsIn(text)) {
|
|
17
|
+
if (one.id === undefined)
|
|
18
|
+
continue;
|
|
19
|
+
if (one.list === 'declares') {
|
|
20
|
+
waiting.push(one.id);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (one.list !== undefined)
|
|
24
|
+
continue;
|
|
25
|
+
if (one.id === ELEMENT || one.id === COMPOSITION)
|
|
26
|
+
for (const id of waiting)
|
|
27
|
+
found.add(nameOf(id));
|
|
28
|
+
waiting = [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return found;
|
|
32
|
+
}
|
|
33
|
+
export const slopnator = {
|
|
34
|
+
on: ['CallExpression'],
|
|
35
|
+
holds: (node) => {
|
|
36
|
+
const held = node;
|
|
37
|
+
const called = held.callee?.type === 'Identifier' ? held.callee.name : undefined;
|
|
38
|
+
if (called === undefined)
|
|
39
|
+
return false;
|
|
40
|
+
if (called === WRAPS || called === HANDS)
|
|
41
|
+
return false;
|
|
42
|
+
if (declared().has(called))
|
|
43
|
+
return false;
|
|
44
|
+
return (called + ' is called here, and a screen is made of declared elements and compositions and may hand an operation to ' +
|
|
45
|
+
HANDS + '. Nothing else has a place in one, so this is work that belongs in an operation');
|
|
46
|
+
},
|
|
47
|
+
proves: {
|
|
48
|
+
wrong: ["export const Orders = screen('orders', {}, () => loadOrders())"],
|
|
49
|
+
right: [
|
|
50
|
+
{
|
|
51
|
+
files: [
|
|
52
|
+
{ 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" },
|
|
53
|
+
{ 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" },
|
|
54
|
+
{ at: 'threads/_probe_operations.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_list_orders',\n describes: 'lists the orders',\n derives: [\n {\n id: 'wf_list_orders_body',\n at: 'operations/wf_list_orders.ts',\n by: 'thread compile',\n },\n ],\n },\n ],\n}\n" },
|
|
55
|
+
{ at: 'screens/probe.tsx', source: "import { dispatch } from '../operations/_dispatch.ts'\n\nimport { operation } from '../operations/wf_list_orders.ts'\n\nexport const Orders = screen('orders', {}, () => dispatch(operation, input, actor, opened))\n" },
|
|
56
|
+
],
|
|
57
|
+
at: 'screens/probe.tsx',
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Slopnator } from '../slopnator.ts'
|
|
2
|
+
|
|
3
|
+
import { descend, read, reached } from '../../core/descend.ts'
|
|
4
|
+
import { rowsIn } from './sl_unthreaded.ts'
|
|
5
|
+
|
|
6
|
+
const WRAPS = 'screen'
|
|
7
|
+
|
|
8
|
+
const HANDS = 'dispatch'
|
|
9
|
+
|
|
10
|
+
const ELEMENT = 'element'
|
|
11
|
+
|
|
12
|
+
const COMPOSITION = 'composition'
|
|
13
|
+
|
|
14
|
+
const nameOf = (id: string): string =>
|
|
15
|
+
id.split('-').map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join('')
|
|
16
|
+
|
|
17
|
+
function declared(): ReadonlySet<string> {
|
|
18
|
+
const found = new Set<string>()
|
|
19
|
+
|
|
20
|
+
descend()
|
|
21
|
+
|
|
22
|
+
for (const at of reached()) {
|
|
23
|
+
const text = read(at)
|
|
24
|
+
if (text === undefined) continue
|
|
25
|
+
|
|
26
|
+
let waiting: string[] = []
|
|
27
|
+
for (const one of rowsIn(text)) {
|
|
28
|
+
if (one.id === undefined) continue
|
|
29
|
+
if (one.list === 'declares') {
|
|
30
|
+
waiting.push(one.id)
|
|
31
|
+
continue
|
|
32
|
+
}
|
|
33
|
+
if (one.list !== undefined) continue
|
|
34
|
+
if (one.id === ELEMENT || one.id === COMPOSITION) for (const id of waiting) found.add(nameOf(id))
|
|
35
|
+
waiting = []
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return found
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const slopnator: Slopnator = {
|
|
43
|
+
on: ['CallExpression'],
|
|
44
|
+
holds: (node) => {
|
|
45
|
+
const held = node as { readonly callee?: { readonly type?: string; readonly name?: string } }
|
|
46
|
+
const called = held.callee?.type === 'Identifier' ? held.callee.name : undefined
|
|
47
|
+
if (called === undefined) return false
|
|
48
|
+
if (called === WRAPS || called === HANDS) return false
|
|
49
|
+
if (declared().has(called)) return false
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
called + ' is called here, and a screen is made of declared elements and compositions and may hand an operation to ' +
|
|
53
|
+
HANDS + '. Nothing else has a place in one, so this is work that belongs in an operation'
|
|
54
|
+
)
|
|
55
|
+
},
|
|
56
|
+
proves: {
|
|
57
|
+
wrong: ["export const Orders = screen('orders', {}, () => loadOrders())"],
|
|
58
|
+
right: [
|
|
59
|
+
{
|
|
60
|
+
files: [
|
|
61
|
+
{ 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" },
|
|
62
|
+
{ 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" },
|
|
63
|
+
{ at: 'threads/_probe_operations.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_list_orders',\n describes: 'lists the orders',\n derives: [\n {\n id: 'wf_list_orders_body',\n at: 'operations/wf_list_orders.ts',\n by: 'thread compile',\n },\n ],\n },\n ],\n}\n" },
|
|
64
|
+
{ at: 'screens/probe.tsx', source: "import { dispatch } from '../operations/_dispatch.ts'\n\nimport { operation } from '../operations/wf_list_orders.ts'\n\nexport const Orders = screen('orders', {}, () => dispatch(operation, input, actor, opened))\n" },
|
|
65
|
+
],
|
|
66
|
+
at: 'screens/probe.tsx',
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
}
|