@prestack/thread 0.1.2 → 0.1.4

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 (265) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/dist/core/actor.d.ts +1 -1
  3. package/dist/core/actor.js +5 -4
  4. package/dist/core/actor.ts +5 -4
  5. package/dist/core/attach.d.ts +0 -7
  6. package/dist/core/attach.js +67 -58
  7. package/dist/core/attach.ts +61 -68
  8. package/dist/core/chain.js +13 -17
  9. package/dist/core/chain.ts +11 -15
  10. package/dist/core/condition.d.ts +4 -4
  11. package/dist/core/condition.js +2 -2
  12. package/dist/core/descend.d.ts +4 -2
  13. package/dist/core/descend.js +89 -21
  14. package/dist/core/descend.ts +86 -17
  15. package/dist/core/errors/catalogue.d.ts +52 -0
  16. package/dist/{operations/errors/errors.js → core/errors/catalogue.js} +1 -1
  17. package/dist/{operations/errors/errors.ts → core/errors/catalogue.ts} +1 -1
  18. package/dist/{operations/errors/catalog.d.ts → core/errors/error.d.ts} +2 -3
  19. package/dist/{operations/errors/catalog.js → core/errors/error.js} +1 -16
  20. package/dist/{operations/errors/catalog.ts → core/errors/error.ts} +3 -19
  21. package/dist/core/errors/namespaces.d.ts +3 -0
  22. package/dist/core/errors/namespaces.js +15 -0
  23. package/dist/core/errors/namespaces.ts +19 -0
  24. package/dist/core/finding.js +2 -2
  25. package/dist/core/finding.ts +1 -2
  26. package/dist/core/init.d.ts +0 -2
  27. package/dist/core/init.js +51 -15
  28. package/dist/core/init.ts +58 -15
  29. package/dist/core/invoked.d.ts +1 -1
  30. package/dist/core/invoked.js +3 -1
  31. package/dist/core/invoked.ts +5 -2
  32. package/dist/core/round.js +36 -22
  33. package/dist/core/round.ts +26 -9
  34. package/dist/core/rows.d.ts +4 -0
  35. package/dist/core/rows.js +31 -2
  36. package/dist/core/rows.ts +33 -1
  37. package/dist/core/thread.d.ts +3 -3
  38. package/dist/core/tree.d.ts +4 -1
  39. package/dist/core/tree.js +26 -6
  40. package/dist/core/tree.ts +29 -6
  41. package/dist/core/types.d.ts +3 -3
  42. package/dist/core/types.js +3 -1
  43. package/dist/core/types.ts +2 -1
  44. package/dist/derivation/attach.js +1 -1
  45. package/dist/derivation/generators/build.js +6 -6
  46. package/dist/derivation/generators/build.ts +1 -1
  47. package/dist/derivation/generators/check.js +1 -1
  48. package/dist/derivation/generators/determinism.js +1 -1
  49. package/dist/derivation/generators/release.js +60 -47
  50. package/dist/derivation/generators/release.ts +66 -43
  51. package/dist/derivation/release.d.ts +7 -3
  52. package/dist/derivation/release.js +14 -1
  53. package/dist/derivation/release.ts +22 -2
  54. package/dist/integrations/attach.d.ts +4 -0
  55. package/dist/integrations/attach.js +33 -54
  56. package/dist/integrations/attach.ts +50 -57
  57. package/dist/integrations/cli.d.ts +37 -0
  58. package/dist/integrations/cli.js +440 -0
  59. package/dist/integrations/cli.ts +543 -0
  60. package/dist/{core/files.d.ts → integrations/disk.d.ts} +16 -20
  61. package/dist/integrations/disk.js +435 -0
  62. package/dist/integrations/disk.ts +465 -0
  63. package/dist/integrations/generators/read.js +6 -6
  64. package/dist/integrations/generators/read.ts +4 -4
  65. package/dist/integrations/generators/vault.js +10 -11
  66. package/dist/integrations/generators/vault.ts +4 -5
  67. package/dist/{operations/http.d.ts → integrations/https.d.ts} +10 -6
  68. package/dist/integrations/https.js +170 -0
  69. package/dist/integrations/https.ts +216 -0
  70. package/dist/integrations/pglite.d.ts +26 -0
  71. package/dist/integrations/pglite.js +143 -0
  72. package/dist/integrations/pglite.ts +206 -0
  73. package/dist/integrations/secret.d.ts +8 -2
  74. package/dist/integrations/secret.js +3 -5
  75. package/dist/integrations/secret.ts +10 -6
  76. package/dist/integrations/target.d.ts +155 -14
  77. package/dist/integrations/target.js +171 -50
  78. package/dist/integrations/target.ts +377 -61
  79. package/dist/integrations/vault.d.ts +1 -0
  80. package/dist/integrations/vault.js +20 -5
  81. package/dist/integrations/vault.ts +17 -5
  82. package/dist/integrity/attach.js +3 -73
  83. package/dist/integrity/attach.ts +1 -71
  84. package/dist/integrity/check-threads.js +5 -5
  85. package/dist/integrity/check-threads.ts +2 -2
  86. package/dist/integrity/constraint.d.ts +0 -1
  87. package/dist/integrity/constraint.js +267 -366
  88. package/dist/integrity/constraint.ts +267 -375
  89. package/dist/integrity/generators/compiler.js +14 -17
  90. package/dist/integrity/generators/compiler.ts +10 -15
  91. package/dist/integrity/generators/gate.js +15 -15
  92. package/dist/integrity/generators/gate.ts +10 -10
  93. package/dist/integrity/generators/issues.js +7 -7
  94. package/dist/integrity/generators/publish.js +39 -57
  95. package/dist/integrity/generators/publish.ts +44 -55
  96. package/dist/integrity/generators/trial.d.ts +3 -3
  97. package/dist/integrity/generators/trial.js +133 -161
  98. package/dist/integrity/generators/trial.ts +145 -165
  99. package/dist/integrity/lint.js +9 -13
  100. package/dist/integrity/lint.ts +7 -10
  101. package/dist/integrity/lsp/bin.js +5 -4
  102. package/dist/integrity/lsp/bin.ts +3 -1
  103. package/dist/integrity/lsp/diagnostics.js +3 -3
  104. package/dist/integrity/lsp/diagnostics.ts +1 -1
  105. package/dist/integrity/lsp/lint.js +19 -30
  106. package/dist/integrity/lsp/lint.ts +16 -32
  107. package/dist/integrity/lsp/server.js +3 -3
  108. package/dist/integrity/slopnator.d.ts +3 -13
  109. package/dist/integrity/slopnator.js +58 -37
  110. package/dist/integrity/slopnator.ts +53 -37
  111. package/dist/integrity/slopnators/sl_broken_lineage.js +2 -2
  112. package/dist/integrity/slopnators/sl_declared_elsewhere.js +1 -1
  113. package/dist/integrity/slopnators/sl_derived_drift.js +4 -4
  114. package/dist/integrity/slopnators/sl_derived_drift.ts +1 -1
  115. package/dist/integrity/slopnators/sl_door_in_a_node.js +220 -0
  116. package/dist/integrity/slopnators/sl_door_in_a_node.ts +250 -0
  117. package/dist/integrity/slopnators/sl_inert_island.js +10 -8
  118. package/dist/integrity/slopnators/sl_inert_island.ts +8 -6
  119. package/dist/integrity/slopnators/sl_missing_row.js +6 -51
  120. package/dist/integrity/slopnators/sl_missing_row.ts +4 -50
  121. package/dist/integrity/slopnators/sl_no_barrel.js +1 -1
  122. package/dist/integrity/slopnators/sl_no_child_process.js +36 -18
  123. package/dist/integrity/slopnators/sl_no_child_process.ts +44 -18
  124. package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +80 -149
  125. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +95 -147
  126. package/dist/integrity/slopnators/sl_no_import_cycle.js +15 -4
  127. package/dist/integrity/slopnators/sl_no_import_cycle.ts +20 -4
  128. package/dist/integrity/slopnators/sl_no_such_command.js +2 -2
  129. package/dist/integrity/slopnators/sl_no_such_command.ts +1 -1
  130. package/dist/integrity/slopnators/sl_outside_an_operation.js +20 -0
  131. package/dist/integrity/slopnators/sl_outside_an_operation.ts +25 -0
  132. package/dist/integrity/slopnators/sl_screen_holds_only_declared.d.ts +2 -0
  133. package/dist/integrity/slopnators/sl_screen_holds_only_declared.js +61 -0
  134. package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +70 -0
  135. package/dist/integrity/slopnators/sl_secret_literal.js +1 -33
  136. package/dist/integrity/slopnators/sl_secret_literal.ts +1 -33
  137. package/dist/integrity/slopnators/sl_stale_path.js +5 -22
  138. package/dist/integrity/slopnators/sl_stale_path.ts +4 -20
  139. package/dist/integrity/slopnators/sl_twice_meant.js +1 -1
  140. package/dist/integrity/slopnators/sl_twice_parented.js +29 -5
  141. package/dist/integrity/slopnators/sl_twice_parented.ts +28 -4
  142. package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +4 -1
  143. package/dist/integrity/slopnators/sl_undeclared_crossing.js +426 -62
  144. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +454 -70
  145. package/dist/integrity/slopnators/sl_undeclared_destination.js +59 -25
  146. package/dist/integrity/slopnators/sl_undeclared_destination.ts +69 -21
  147. package/dist/integrity/slopnators/sl_undeclared_list.d.ts +2 -0
  148. package/dist/integrity/slopnators/sl_undeclared_list.js +363 -0
  149. package/dist/integrity/slopnators/sl_undeclared_list.ts +358 -0
  150. package/dist/integrity/slopnators/sl_undeclared_node.js +1 -11
  151. package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -10
  152. package/dist/integrity/slopnators/sl_undeclared_operation.js +111 -29
  153. package/dist/integrity/slopnators/sl_undeclared_operation.ts +134 -26
  154. package/dist/integrity/slopnators/sl_undeclared_variant.js +2 -2
  155. package/dist/integrity/slopnators/sl_undeclared_webhook.d.ts +2 -0
  156. package/dist/integrity/slopnators/sl_undeclared_webhook.js +90 -0
  157. package/dist/integrity/slopnators/sl_undeclared_webhook.ts +108 -0
  158. package/dist/integrity/slopnators/sl_underived_route.d.ts +2 -0
  159. package/dist/integrity/slopnators/sl_underived_route.js +61 -0
  160. package/dist/integrity/slopnators/sl_underived_route.ts +72 -0
  161. package/dist/integrity/slopnators/sl_ungoverned_island.js +3 -3
  162. package/dist/integrity/slopnators/sl_ungoverned_island.ts +1 -1
  163. package/dist/integrity/slopnators/sl_unlisted_program.d.ts +2 -0
  164. package/dist/integrity/slopnators/sl_unlisted_program.js +39 -0
  165. package/dist/integrity/slopnators/sl_unlisted_program.ts +40 -0
  166. package/dist/integrity/slopnators/sl_unnamed_island.js +1 -1
  167. package/dist/integrity/slopnators/sl_unpaired_member.js +3 -3
  168. package/dist/integrity/slopnators/sl_unpaired_member.ts +1 -1
  169. package/dist/integrity/slopnators/sl_unpaired_slopnator.js +4 -4
  170. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +1 -1
  171. package/dist/integrity/slopnators/sl_unprefixed_member.js +5 -31
  172. package/dist/integrity/slopnators/sl_unprefixed_member.ts +2 -28
  173. package/dist/integrity/slopnators/sl_unthreaded.js +11 -7
  174. package/dist/integrity/slopnators/sl_unthreaded.ts +6 -7
  175. package/dist/integrity/slopnators/sl_untranslated_key.js +2 -25
  176. package/dist/integrity/slopnators/sl_untranslated_key.ts +1 -25
  177. package/dist/integrity/slopnators/sl_unvaulted_secret.d.ts +2 -0
  178. package/dist/integrity/slopnators/sl_unvaulted_secret.js +107 -0
  179. package/dist/integrity/slopnators/sl_unvaulted_secret.ts +132 -0
  180. package/dist/integrity/slopnators/sl_unversioned.js +6 -4
  181. package/dist/integrity/slopnators/sl_unversioned.ts +5 -3
  182. package/dist/integrity/sweep.d.ts +1 -0
  183. package/dist/integrity/sweep.js +105 -42
  184. package/dist/integrity/sweep.ts +115 -42
  185. package/dist/knowledge/attach.js +1 -1
  186. package/dist/knowledge/generators/docsgen.js +8 -6
  187. package/dist/knowledge/generators/docsgen.ts +4 -2
  188. package/dist/knowledge/generators/glossary.js +4 -4
  189. package/dist/knowledge/generators/glossary.ts +1 -1
  190. package/dist/operations/access.d.ts +23 -0
  191. package/dist/operations/access.js +28 -1
  192. package/dist/operations/access.ts +43 -0
  193. package/dist/operations/attach.js +28 -109
  194. package/dist/operations/attach.ts +46 -113
  195. package/dist/operations/capabilities.d.ts +14 -5
  196. package/dist/operations/capabilities.js +13 -4
  197. package/dist/operations/capabilities.ts +27 -8
  198. package/dist/operations/dispatch.d.ts +8 -2
  199. package/dist/operations/dispatch.js +128 -185
  200. package/dist/operations/dispatch.ts +138 -191
  201. package/dist/operations/error-results.d.ts +1 -1
  202. package/dist/operations/error-results.js +2 -2
  203. package/dist/operations/error-results.ts +2 -2
  204. package/dist/operations/generators/capability.d.ts +11 -1
  205. package/dist/operations/generators/capability.js +481 -224
  206. package/dist/operations/generators/capability.ts +562 -233
  207. package/dist/operations/generators/compile.js +66 -39
  208. package/dist/operations/generators/compile.ts +78 -39
  209. package/dist/operations/generators/permits.js +11 -12
  210. package/dist/operations/generators/permits.ts +3 -4
  211. package/dist/operations/generators/seed.d.ts +1 -0
  212. package/dist/operations/generators/seed.js +19 -17
  213. package/dist/operations/generators/seed.ts +22 -14
  214. package/dist/operations/generators/trial.d.ts +6 -8
  215. package/dist/operations/generators/trial.js +200 -70
  216. package/dist/operations/generators/trial.ts +230 -93
  217. package/dist/operations/nodes.d.ts +15 -16
  218. package/dist/operations/nodes.ts +16 -17
  219. package/dist/operations/operations.d.ts +40 -4
  220. package/dist/operations/operations.js +1 -0
  221. package/dist/operations/operations.ts +61 -4
  222. package/dist/operations/result.d.ts +2 -2
  223. package/dist/operations/result.ts +2 -2
  224. package/package.json +1 -4
  225. package/dist/core/answers.d.ts +0 -171
  226. package/dist/core/answers.js +0 -789
  227. package/dist/core/answers.ts +0 -999
  228. package/dist/core/client.d.ts +0 -57
  229. package/dist/core/client.js +0 -247
  230. package/dist/core/client.ts +0 -286
  231. package/dist/core/files.js +0 -145
  232. package/dist/core/files.ts +0 -193
  233. package/dist/integrations/module.d.ts +0 -6
  234. package/dist/integrations/module.js +0 -153
  235. package/dist/integrations/module.ts +0 -191
  236. package/dist/integrity/slopnators/sl_unkept_rows.js +0 -104
  237. package/dist/integrity/slopnators/sl_unkept_rows.ts +0 -111
  238. package/dist/integrity/slopnators/sl_unseamed_reach.js +0 -73
  239. package/dist/integrity/slopnators/sl_unseamed_reach.ts +0 -79
  240. package/dist/operations/cost.d.ts +0 -14
  241. package/dist/operations/cost.js +0 -37
  242. package/dist/operations/cost.ts +0 -58
  243. package/dist/operations/errors/errors.d.ts +0 -52
  244. package/dist/operations/http.js +0 -124
  245. package/dist/operations/http.ts +0 -169
  246. package/dist/operations/memory.d.ts +0 -104
  247. package/dist/operations/memory.js +0 -365
  248. package/dist/operations/memory.ts +0 -470
  249. package/dist/operations/metering.d.ts +0 -55
  250. package/dist/operations/metering.js +0 -247
  251. package/dist/operations/metering.ts +0 -328
  252. package/dist/operations/queue-policy.d.ts +0 -3
  253. package/dist/operations/queue-policy.js +0 -8
  254. package/dist/operations/queue-policy.ts +0 -10
  255. package/dist/operations/seams.d.ts +0 -239
  256. package/dist/operations/seams.js +0 -45
  257. package/dist/operations/seams.ts +0 -364
  258. package/dist/operations/store.d.ts +0 -14
  259. package/dist/operations/store.js +0 -1
  260. package/dist/operations/store.ts +0 -29
  261. /package/dist/{operations → core}/errors/classes.d.ts +0 -0
  262. /package/dist/{operations → core}/errors/classes.js +0 -0
  263. /package/dist/{operations → core}/errors/classes.ts +0 -0
  264. /package/dist/integrity/slopnators/{sl_unkept_rows.d.ts → sl_door_in_a_node.d.ts} +0 -0
  265. /package/dist/integrity/slopnators/{sl_unseamed_reach.d.ts → sl_outside_an_operation.d.ts} +0 -0
@@ -1,12 +1,14 @@
1
- import { existsSync, readFileSync, rmSync, writeFileSync } from './files.ts'
1
+ import { installing, needing } from '../integrations/cli.ts'
2
+ import { attached, existsSync, readFileSync, rmSync, writeFileSync } from '../integrations/disk.ts'
2
3
  import { createRequire } from 'node:module'
3
4
  import { join, relative, resolve } from 'node:path'
4
5
  import { stdout } from 'node:process'
5
6
 
6
7
  import { LISTS } from './descend.ts'
7
8
  import { ROOT, TREE, root as rooted } from './init.ts'
9
+ import { BESIDE } from './invoked.ts'
8
10
  import { STATED, holdsRow, listBounds, quoted, reconciled, rowsIn, withRow, withoutRow } from './rows.ts'
9
- import { ours as isOurs } from './scope.ts'
11
+ import { idOf, ours as isOurs } from './scope.ts'
10
12
 
11
13
  const SHAPE = /export interface [A-Za-z0-9_]+ extends Threaded \{[\s\S]*?\n\}\n/
12
14
 
@@ -35,16 +37,6 @@ export interface Attaching {
35
37
  readonly declares: string
36
38
  readonly rows?: readonly Appending[]
37
39
 
38
- readonly retires?: readonly {
39
- readonly into: string
40
- readonly list: string
41
- readonly id: string
42
-
43
- readonly made?: string
44
-
45
- readonly why: string
46
- }[]
47
-
48
40
  readonly attaches?: readonly string[]
49
41
 
50
42
  readonly scripts?: Readonly<Record<string, string>>
@@ -108,18 +100,27 @@ export async function attaches(into: string, name: string): Promise<boolean> {
108
100
  }
109
101
  }
110
102
 
103
+ function shipped(spec: string, owner: string): string {
104
+ const rest = spec.slice(owner.length + 1)
105
+ const named = idOf(owner) ?? owner
106
+ const part = rest === DOOR.slice(1) ? named : String(rest.split('/')[0])
107
+ return new URL('../' + part + '/' + BESIDE('attach'), import.meta.url).href
108
+ }
109
+
111
110
  async function attaching(into: string, name: string): Promise<Attaching> {
112
111
  const spec = name.endsWith(DOOR) ? name : `${name}${DOOR}`
113
112
  const owner = packageOf(name)
114
113
  let held: { ATTACH?: Attaching }
115
114
  try {
115
+ if (attached() && isOurs(owner)) held = (await import(shipped(spec, owner))) as { ATTACH?: Attaching }
116
+ else
116
117
  held = (await import(createRequire(join(into, 'package.json')).resolve(spec))) as { ATTACH?: Attaching }
117
118
  } catch {
118
119
  if (!existsSync(join(into, 'node_modules', owner, 'package.json'))) {
119
120
  throw new UnattachableError(
120
121
  owner,
121
- `is not installed in this app — run \`npm i -D ${owner}\` first, because \`thread attach\` takes on a ` +
122
- 'package that is already here rather than fetching one',
122
+ 'is not installed here, and fetching it did not put it on disk either. Check the name — `thread attach` ' +
123
+ 'takes the package as the registry spells it. Nothing here was changed',
123
124
  )
124
125
  }
125
126
  throw new UnattachableError(
@@ -163,6 +164,28 @@ function edit(at: string, change: (text: string) => string, touched: Set<string>
163
164
  touched.add(relative(into, at))
164
165
  }
165
166
 
167
+ function acquire(into: string, owner: string): void {
168
+ const manifest = join(into, 'package.json')
169
+ const was = readFileSync(manifest, 'utf8')
170
+ const parsed = JSON.parse(was) as Record<string, unknown> & { dependencies?: Record<string, string> }
171
+ stdout.write(`thread: ${owner} is not here yet, so this is fetching it. Nothing has to be typed for that.\n`)
172
+ writeFileSync(manifest, `${JSON.stringify({ ...parsed, dependencies: { ...parsed.dependencies, [owner]: 'latest' } }, null, 2)}\n`, 'utf8')
173
+ try {
174
+ installing(into)
175
+ } catch (thrown) {
176
+ writeFileSync(manifest, was, 'utf8')
177
+ throw new Error(
178
+ `${owner} could not be fetched — ${thrown instanceof Error ? thrown.message : String(thrown)} package.json is as it was, so nothing here names a package that is not on disk.`,
179
+ )
180
+ }
181
+ const at = join(into, 'node_modules', owner, 'package.json')
182
+ if (!existsSync(at)) return
183
+ const got = (JSON.parse(readFileSync(at, 'utf8')) as { version?: string }).version
184
+ if (got === undefined) return
185
+ const now = JSON.parse(readFileSync(manifest, 'utf8')) as Record<string, unknown> & { dependencies?: Record<string, string> }
186
+ writeFileSync(manifest, `${JSON.stringify({ ...now, dependencies: { ...now.dependencies, [owner]: '^' + got } }, null, 2)}\n`, 'utf8')
187
+ }
188
+
166
189
  export async function attach(
167
190
  into: string,
168
191
  name: string,
@@ -174,13 +197,17 @@ export async function attach(
174
197
  const root = join(dir, ROOT)
175
198
  if (!existsSync(root)) throw new UnthreadedError()
176
199
 
200
+ const owned = packageOf(name)
201
+ if (!existsSync(join(into, 'node_modules', owned, 'package.json'))) acquire(into, owned)
202
+
177
203
  const one = await attaching(into, name)
178
204
 
179
205
  for (const on of one.attaches ?? []) {
206
+ if (!(await attaches(into, on))) acquire(into, packageOf(on))
180
207
  if (!(await attaches(into, on))) {
181
208
  throw new UnattachableError(
182
209
  name,
183
- `must have ${on} attached before it, and ${on} is not installed here — run \`npm install -D ${packageOf(on)}\` and attach ${name} again. ` +
210
+ `must have ${on} attached before it, and fetching ${packageOf(on)} did not put it on disk. ` +
184
211
  `${on} is named in \`attaches\` and not in dependencies, because a package that only appends a row to a file ` +
185
212
  'another package owns would otherwise carry an npm edge it never imports',
186
213
  )
@@ -190,7 +217,6 @@ export async function attach(
190
217
 
191
218
  const plan = new Map<string, string>()
192
219
 
193
- const retired: string[] = []
194
220
 
195
221
  const reshaped: string[] = []
196
222
 
@@ -336,13 +362,6 @@ export async function attach(
336
362
  if (where === undefined) return false
337
363
  return !text.slice(where.open, where.close).includes(each.source.trim().replace(/,$/, ''))
338
364
  }).length
339
- const withdrawing = (one.retires ?? []).filter((gone) => {
340
- const at = join(dir, gone.into)
341
- if (!existsSync(at)) return false
342
- const text = plan.get(at) ?? readFileSync(at, 'utf8')
343
- const where = listBounds(text, gone.list)
344
- return where !== undefined && holdsRow(text, where, gone.id)
345
- }).length
346
365
 
347
366
  throw new Error(
348
367
  `${name} brings a noun called \`${coined}\`, and this tree already declares something by that name. One ` +
@@ -354,11 +373,10 @@ export async function attach(
354
373
 
355
374
  `A refusal here names a command you can actually run. If the one above does not fit what you are trying ` +
356
375
  `to do, that is a gap in this framework rather than something to work around — say so.` +
357
- (waiting + withdrawing === 0
376
+ (waiting === 0
358
377
  ? ''
359
378
  : `\n\nAn attach is all or nothing, so this is also holding back everything else ${name} would have ` +
360
- `delivered: ${String(waiting)} row(s) this tree does not hold at the version installed` +
361
- `${withdrawing === 0 ? '' : ` and ${String(withdrawing)} it has withdrawn`}. None of that arrives until the ` +
379
+ `delivered: ${String(waiting)} row(s) this tree does not hold at the version installed. None of that arrives until the ` +
362
380
  `collision above is cleared, and nothing else will tell you — \`npm install\` will go on reporting this ` +
363
381
  `tree current.`),
364
382
  )
@@ -383,34 +401,6 @@ export async function attach(
383
401
  plan.set(target, withRow(held, bounds, row.source))
384
402
  }
385
403
 
386
- for (const gone of one.retires ?? []) {
387
- const shipped = (one.rows ?? []).find(
388
- (row) => row.into === gone.into && row.list === gone.list && row.id === gone.id,
389
- )
390
- if (shipped !== undefined) {
391
- throw new UnattachableError(
392
- name,
393
- `both ships and retires \`${gone.id}\` in ${gone.into}, so attaching it would add and remove one row`,
394
- )
395
- }
396
-
397
- if (gone.made !== undefined) {
398
- const byproduct = join(into, gone.made)
399
- if (existsSync(byproduct)) {
400
- rmSync(byproduct)
401
- retired.push(`${gone.made} — the file \`${gone.id}\` derived, which nothing writes now`)
402
- }
403
- }
404
-
405
- const target = join(dir, gone.into)
406
- if (!existsSync(target) && !plan.has(target)) continue
407
- const text = plan.get(target) ?? readFileSync(target, 'utf8')
408
- const where = listBounds(text, gone.list)
409
- if (where === undefined || !holdsRow(text, where, gone.id)) continue
410
- plan.set(target, withoutRow(text, where, gone.id))
411
- retired.push(`${gone.into}: ${gone.id} — ${gone.why}`)
412
- }
413
-
414
404
  if (one.scripts !== undefined) {
415
405
  const manifest = join(into, 'package.json')
416
406
  const held = readFileSync(manifest, 'utf8')
@@ -427,19 +417,23 @@ export async function attach(
427
417
 
428
418
  if (one.needs !== undefined) {
429
419
  const manifest = join(into, 'package.json')
430
-
431
- const held = plan.get(manifest) ?? readFileSync(manifest, 'utf8')
432
- const parsed = JSON.parse(held) as Record<string, unknown> & { dependencies?: Record<string, string> }
433
- const needs = { ...parsed.dependencies }
434
- const added: string[] = []
435
- for (const [name, at] of Object.entries(one.needs)) {
436
- if (needs[name] !== undefined) continue
437
- needs[name] = at
438
- added.push(name)
420
+ const was = readFileSync(manifest, 'utf8')
421
+ const scripted = plan.get(manifest)
422
+ if (scripted !== undefined) {
423
+ writeFileSync(manifest, scripted, 'utf8')
424
+ plan.delete(manifest)
425
+ touched.add(relative(into, manifest))
426
+ }
427
+ let added: readonly string[]
428
+ try {
429
+ added = needing(into, one.needs)
430
+ } catch (thrown) {
431
+ writeFileSync(manifest, was, 'utf8')
432
+ throw new Error(`${thrown instanceof Error ? thrown.message : String(thrown)} Nothing was attached.`)
439
433
  }
440
434
  if (added.length > 0) {
441
- plan.set(manifest, `${JSON.stringify({ ...parsed, dependencies: needs }, null, 2)}\n`)
442
- stdout.write(`thread: added ${added.join(', ')} to dependencies — run \`npm install\` to fetch ${added.length === 1 ? 'it' : 'them'}\n`)
435
+ touched.add(relative(into, manifest))
436
+ stdout.write(`thread: ${name} needs ${added.join(', ')}, so ${added.length === 1 ? 'it is' : 'they are'} in package.json now and fetched. Nothing has to be typed for that.\n`)
443
437
  }
444
438
  }
445
439
 
@@ -459,7 +453,6 @@ export async function attach(
459
453
  touched.add(relative(into, path))
460
454
  }
461
455
 
462
- for (const line of retired) stdout.write(`thread: retired ${line}\n`)
463
456
  for (const file of reshaped) stdout.write(`thread: reshaped ${file} — what a row may state has changed\n`)
464
457
  for (const id of noticed) stdout.write(`thread: declared ${id} — a file this app grew after \`thread init\` ran\n`)
465
458
 
@@ -545,7 +538,7 @@ export async function detach(into: string, name: string): Promise<readonly strin
545
538
  }
546
539
 
547
540
  const DESCRIBES =
548
- 'The package every tree installs first: the descent, the CLI, and the makers and generators that used to be separate packages.'
541
+ 'The package every tree installs first: the descent that reads a tree, the gate that checks it, and the generators every tree runs.'
549
542
 
550
543
  const CORE_SOURCE = `import type { Threaded } from '../core/thread.ts'
551
544
 
@@ -567,7 +560,7 @@ const SEEDED = {
567
560
  ' {',
568
561
  ' id: \'core\',',
569
562
  ' at: \'_threads/core.ts\',',
570
- ' describes: \'The package every tree installs first: the descent, the CLI, and the makers and generators that used to be separate packages.\',',
563
+ ' describes: \'The package every tree installs first: the descent that reads a tree, the gate that checks it, and the generators every tree runs.\',',
571
564
  ' version: 1,',
572
565
  ' },',
573
566
  ].join('\n'),
@@ -1,10 +1,10 @@
1
- import { spawnSync } from 'node:child_process';
1
+ import { starts } from '../integrations/cli.js';
2
2
  import { join } from 'node:path';
3
3
  import { execPath } from 'node:process';
4
4
  import { fileURLToPath } from 'node:url';
5
- import { existsSync, readFileSync, readdirSync } from "./files.js";
6
- import { valuesIn } from "./rows.js";
7
- import { findTree } from "./tree.js";
5
+ import { existsSync, readFileSync, readdirSync } from '../integrations/disk.js';
6
+ import { valuesIn } from './rows.js';
7
+ import { findTree } from './tree.js';
8
8
  const STATING = 'derivation.ts';
9
9
  export async function generators(tree = findTree()) {
10
10
  if (!existsSync(tree.dir))
@@ -39,12 +39,8 @@ export const REFUSED_BY = 'round: refused';
39
39
  export function batched(at, commands, within) {
40
40
  if (commands.length === 0)
41
41
  return [];
42
- const out = spawnSync(execPath, [...LOADER, RUNNER, ...new Set(commands)], {
43
- cwd: at,
44
- encoding: 'utf8',
45
- ...(within === undefined ? {} : { timeout: within, killSignal: 'SIGKILL' }),
46
- });
47
- const lines = `${out.stdout ?? ''}${out.stderr ?? ''}`.split('\n');
42
+ const out = starts(execPath, [...LOADER, RUNNER, ...new Set(commands)], { at, shows: 'errors', within });
43
+ const lines = out.out.split('\n');
48
44
  const found = new Map();
49
45
  let held = [];
50
46
  for (const line of lines) {
@@ -58,12 +54,12 @@ export function batched(at, commands, within) {
58
54
  held = [];
59
55
  }
60
56
  const orphaned = held.join('\n').trim();
61
- const died = out.error !== undefined
62
- ? ` The runner itself failed to start or was stopped: ${out.error.message}.`
63
- : out.signal !== null && out.signal !== undefined
64
- ? ` The runner was killed by ${out.signal}, so everything after the command it was running is unreported rather than refused.`
65
- : found.size === 0 && out.status !== 0
66
- ? ` The runner exited ${String(out.status)} having reported nothing, so it died before the first command finished.`
57
+ const died = out.why !== ''
58
+ ? ` The runner itself failed to start or was stopped: ${out.why}.`
59
+ : out.killed !== null
60
+ ? ` The runner was killed by ${out.killed} after ${String(within ?? 0)} ms. The last "settle: running" line above names the command it was inside, and everything after that one is unreported rather than refused.`
61
+ : found.size === 0 && !out.ok
62
+ ? ` The runner exited ${String(out.code)} having reported nothing, so it died before the first command finished.`
67
63
  : '';
68
64
  let firstUnreached = true;
69
65
  return commands.map((one) => {
@@ -93,7 +89,7 @@ export function settling(at, all, skip = []) {
93
89
  let before;
94
90
  while (round < ROUNDS) {
95
91
  round += 1;
96
- const asking = round === 1 ? mine.map((one) => word(one.runs)) : batch(at, mine.map((one) => word(one.runs))).refused;
92
+ const asking = batch(at, mine.map((one) => word(one.runs))).refused;
97
93
  const unhappy = mine.filter((one) => asking.includes(word(one.runs)));
98
94
  if (unhappy.length === 0)
99
95
  return { ran, unsettled: [], rounds: round };
@@ -1,10 +1,10 @@
1
- import { spawnSync } from 'node:child_process'
1
+ import { starts } from '../integrations/cli.ts'
2
2
  import { join } from 'node:path'
3
3
  import { execPath } from 'node:process'
4
4
  import { fileURLToPath } from 'node:url'
5
5
 
6
6
  import type { Row } from './descend.ts'
7
- import { existsSync, readFileSync, readdirSync } from './files.ts'
7
+ import { existsSync, readFileSync, readdirSync } from '../integrations/disk.ts'
8
8
  import { valuesIn } from './rows.ts'
9
9
  import { findTree, type Tree } from './tree.ts'
10
10
 
@@ -58,12 +58,8 @@ export function batched(
58
58
  ): readonly { readonly command: string; readonly ok: boolean; readonly said: string }[] {
59
59
  if (commands.length === 0) return []
60
60
 
61
- const out = spawnSync(execPath, [...LOADER, RUNNER, ...new Set(commands)], {
62
- cwd: at,
63
- encoding: 'utf8',
64
- ...(within === undefined ? {} : { timeout: within, killSignal: 'SIGKILL' as const }),
65
- })
66
- const lines = `${out.stdout ?? ''}${out.stderr ?? ''}`.split('\n')
61
+ const out = starts(execPath, [...LOADER, RUNNER, ...new Set(commands)], { at, shows: 'errors', within })
62
+ const lines = out.out.split('\n')
67
63
 
68
64
  const found = new Map<string, { readonly command: string; readonly ok: boolean; readonly said: string }>()
69
65
  let held: string[] = []
@@ -80,12 +76,12 @@ export function batched(
80
76
 
81
77
  const orphaned = held.join('\n').trim()
82
78
  const died =
83
- out.error !== undefined
84
- ? ` The runner itself failed to start or was stopped: ${out.error.message}.`
85
- : out.signal !== null && out.signal !== undefined
86
- ? ` The runner was killed by ${out.signal}, so everything after the command it was running is unreported rather than refused.`
87
- : found.size === 0 && out.status !== 0
88
- ? ` The runner exited ${String(out.status)} having reported nothing, so it died before the first command finished.`
79
+ out.why !== ''
80
+ ? ` The runner itself failed to start or was stopped: ${out.why}.`
81
+ : out.killed !== null
82
+ ? ` The runner was killed by ${out.killed} after ${String(within ?? 0)} ms. The last "settle: running" line above names the command it was inside, and everything after that one is unreported rather than refused.`
83
+ : found.size === 0 && !out.ok
84
+ ? ` The runner exited ${String(out.code)} having reported nothing, so it died before the first command finished.`
89
85
  : ''
90
86
  let firstUnreached = true
91
87
  return commands.map((one) => {
@@ -127,7 +123,7 @@ export function settling(
127
123
  while (round < ROUNDS) {
128
124
  round += 1
129
125
 
130
- const asking = round === 1 ? mine.map((one) => word(one.runs)) : batch(at, mine.map((one) => word(one.runs))).refused
126
+ const asking = batch(at, mine.map((one) => word(one.runs))).refused
131
127
  const unhappy = mine.filter((one) => asking.includes(word(one.runs)))
132
128
  if (unhappy.length === 0) return { ran, unsettled: [], rounds: round }
133
129
 
@@ -1,8 +1,8 @@
1
1
  import { type Actor } from './actor.ts';
2
- export declare const COMPARE_OPS: readonly ["eq", "neq", "lt", "lte", "gt", "gte", "in", "has"];
2
+ export declare const COMPARE_OPS: readonly ['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'in', 'has'];
3
3
  export type CompareOp = (typeof COMPARE_OPS)[number];
4
- export declare const AGGREGATIONS: readonly ["count", "sum", "min", "max", "avg"];
5
- export declare const ACTOR_FACTS: readonly ["id", "roles", "entitlements", ...("run" | "address" | "device" | "replay")[]];
4
+ export declare const AGGREGATIONS: readonly ['count', 'sum', 'min', 'max', 'avg'];
5
+ export declare const ACTOR_FACTS: readonly ["id", "roles", "entitlements", ...("address" | "device" | "moment" | "replay" | "run")[]];
6
6
  export declare const AGO: RegExp;
7
7
  export type Operand = {
8
8
  readonly value: string | number | boolean | null;
@@ -41,7 +41,7 @@ export declare function walkOperands(condition: Condition, take: (one: Operand)
41
41
  export declare function mentionsActor(condition: Condition): boolean;
42
42
  export declare function agoSeconds(ago: string): number | undefined;
43
43
  export declare function satisfies(condition: Condition, actor: Actor): boolean;
44
- export declare const CONDITION_ANSWERS: readonly ["is", "than", "field", "value", "actor", "ago", "of", "how", "over", "op", "when", "joins"];
44
+ export declare const CONDITION_ANSWERS: readonly ['is', 'than', 'field', 'value', 'actor', 'ago', 'of', 'how', 'over', 'op', 'when', 'joins'];
45
45
  export declare function conditioned(ask: (key: string, question: string) => Promise<string>, say: (line: string) => void, subject: {
46
46
  readonly id: string;
47
47
  readonly fields: readonly string[];
@@ -1,4 +1,4 @@
1
- import { ENVIRONMENT } from "./actor.js";
1
+ import { ENVIRONMENT } from './actor.js';
2
2
  export const COMPARE_OPS = ['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'in', 'has'];
3
3
  export const AGGREGATIONS = ['count', 'sum', 'min', 'max', 'avg'];
4
4
  export const ACTOR_FACTS = ['id', 'roles', 'entitlements', ...ENVIRONMENT.map(([fact]) => fact)];
@@ -182,7 +182,7 @@ export async function conditioned(ask, say, subject, all) {
182
182
  const first = held[0];
183
183
  return held.length === 1 ? first : joiner === 'all' ? { all: held } : { any: held };
184
184
  }
185
- import { quoted } from "./rows.js";
185
+ import { quoted } from './rows.js';
186
186
  export function said(one) {
187
187
  if (typeof one === 'string')
188
188
  return quoted(one);
@@ -4,7 +4,7 @@ export declare const ROOT: string;
4
4
  export declare const TREE: string;
5
5
  export declare const THREAD_AT: string;
6
6
  export declare function unkept(at: string): boolean;
7
- export declare const LISTS: readonly ["declares", "derives", "seeds"];
7
+ export declare const LISTS: readonly ['declares', 'derives', 'seeds'];
8
8
  export declare const ROW_LISTS: readonly ["declares", "derives", "seeds", "holds"];
9
9
  export interface Row {
10
10
  readonly at?: string;
@@ -59,6 +59,7 @@ export declare function shapeFields(named: string, inside?: string): readonly {
59
59
  readonly map: boolean;
60
60
  readonly worded: boolean;
61
61
  readonly guide: string;
62
+ readonly record: readonly string[];
62
63
  }[];
63
64
  export interface Field {
64
65
  readonly name: string;
@@ -68,9 +69,10 @@ export interface Field {
68
69
  readonly boolean: boolean;
69
70
  readonly worded: boolean;
70
71
  readonly guide: string;
72
+ readonly record: readonly string[];
71
73
  }
72
74
  export declare function listShape(at: string, list: string): string | undefined;
73
- export declare function listFields(at: string, list: string): readonly Field[];
75
+ export declare function listFields(at: string, list: string, within?: number): readonly Field[];
74
76
  export declare function rowShape(at: string, list: string): string | undefined;
75
77
  interface Held {
76
78
  readonly name: string;
@@ -1,10 +1,10 @@
1
- import { spawnSync } from 'node:child_process';
2
- import { attached, existsSync, kind, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "./files.js";
1
+ import { starts } from '../integrations/cli.js';
2
+ import { attached, beyond, existsSync, here as answering, kind, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from '../integrations/disk.js';
3
3
  import { dirname, join, relative, resolve } from 'node:path';
4
4
  import ts from 'typescript';
5
- import { ignored } from "./init.js";
6
- import { NAMED, ORGANISATION } from "./scope.js";
7
- import { findTree } from "./tree.js";
5
+ import { ignored } from './init.js';
6
+ import { NAMED, ORGANISATION } from './scope.js';
7
+ import { findTree } from './tree.js';
8
8
  const BUILT = new Set(ignored()
9
9
  .split('\n')
10
10
  .map((one) => one.trim())
@@ -32,7 +32,7 @@ function ownIgnores() {
32
32
  if (readIgnores)
33
33
  return ignores;
34
34
  readIgnores = true;
35
- if (!attached() && spawnSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd: ROOT, stdio: 'ignore' }).status === 0) {
35
+ if (!attached() && starts('git', ['rev-parse', '--is-inside-work-tree'], { at: ROOT }).ok) {
36
36
  return ignores;
37
37
  }
38
38
  try {
@@ -55,7 +55,7 @@ export function unkept(at) {
55
55
  return held;
56
56
  const own = ownIgnores();
57
57
  const found = own === undefined
58
- ? spawnSync('git', ['check-ignore', '-q', '--', at], { cwd: ROOT, stdio: 'ignore' }).status === 0
58
+ ? starts('git', ['check-ignore', '-q', '--', at], { at: ROOT }).ok
59
59
  : own.some((one) => at === one || at.startsWith(`${one}/`) || at.split('/').includes(one));
60
60
  asked.set(at, found);
61
61
  return found;
@@ -198,7 +198,7 @@ function listing(at, take, skip, only, deep) {
198
198
  function parsing(from) {
199
199
  return {
200
200
  useCaseSensitiveFileNames: true,
201
- fileExists: (at) => existsSync(at),
201
+ fileExists: (at) => !beyond(at) && existsSync(at),
202
202
  readFile: (at) => {
203
203
  try {
204
204
  return readFileSync(at);
@@ -237,23 +237,34 @@ export function configured() {
237
237
  }
238
238
  return { options: { ...FALLBACK, ...INVARIANT }, from: '' };
239
239
  }
240
+ const libraries = new Map();
241
+ const DEPENDENCIES = '/node_' + 'modules/';
240
242
  export function hosting(options) {
241
243
  const host = ts.createCompilerHost(options, true);
242
244
  const lib = host.getDefaultLibLocation?.();
245
+ const settings = JSON.stringify(options);
243
246
  const toolchain = (at) => lib !== undefined && (at === lib || at.startsWith(`${lib}/`));
247
+ const seen = (at) => (beyond(at) ? 'none' : kind(at));
244
248
  return {
245
249
  ...host,
246
- fileExists: (at) => (toolchain(at) ? host.fileExists(at) : kind(at) === 'file'),
247
- directoryExists: (at) => (toolchain(at) ? (host.directoryExists?.(at) ?? false) : kind(at) === 'folder'),
248
- readFile: (at) => (toolchain(at) ? host.readFile(at) : kind(at) === 'file' ? readFileSync(at) : undefined),
250
+ fileExists: (at) => (toolchain(at) ? host.fileExists(at) : seen(at) === 'file'),
251
+ directoryExists: (at) => (toolchain(at) ? (host.directoryExists?.(at) ?? false) : seen(at) === 'folder'),
252
+ readFile: (at) => (toolchain(at) ? host.readFile(at) : seen(at) === 'file' ? readFileSync(at) : undefined),
249
253
  getSourceFile: (at, version) => {
250
- if (toolchain(at))
251
- return host.getSourceFile(at, version);
252
- if (kind(at) !== 'file')
253
- return undefined;
254
- return ts.createSourceFile(at, readFileSync(at), version, true);
254
+ const lasting = toolchain(at) ? true : at.includes(DEPENDENCIES);
255
+ const key = settings + at + JSON.stringify(version);
256
+ if (lasting && libraries.has(key))
257
+ return libraries.get(key);
258
+ const made = toolchain(at)
259
+ ? host.getSourceFile(at, version)
260
+ : seen(at) === 'file'
261
+ ? ts.createSourceFile(at, readFileSync(at), version, true)
262
+ : undefined;
263
+ if (lasting)
264
+ libraries.set(key, made);
265
+ return made;
255
266
  },
256
- getDirectories: (at) => kind(at) === 'folder' ? readdirSync(at, { withFileTypes: true }).filter((one) => one.isDirectory()).map((one) => one.name) : [],
267
+ getDirectories: (at) => seen(at) === 'folder' ? readdirSync(at, { withFileTypes: true }).filter((one) => one.isDirectory()).map((one) => one.name) : [],
257
268
  realpath: (at) => (toolchain(at) ? (host.realpath?.(at) ?? at) : realpathSync(at)),
258
269
  writeFile: (at, said) => {
259
270
  writeFileSync(at, said);
@@ -328,8 +339,13 @@ export function read(at) {
328
339
  source.set(at, text);
329
340
  return text;
330
341
  }
342
+ const shapes = new WeakMap();
331
343
  export function shapesNamed() {
332
- const { program, checker } = compiled();
344
+ const held = compiled();
345
+ const known = shapes.get(held);
346
+ if (known !== undefined)
347
+ return known;
348
+ const { program, checker } = held;
333
349
  const shape = threadedType(checker, program);
334
350
  const found = new Map();
335
351
  if (shape === undefined)
@@ -368,6 +384,7 @@ export function shapesNamed() {
368
384
  found.set(named, [...where, at]);
369
385
  }
370
386
  }
387
+ shapes.set(held, found);
371
388
  return found;
372
389
  }
373
390
  export function threading() {
@@ -408,13 +425,14 @@ export function shapeFields(named, inside) {
408
425
  map: mapped(kind),
409
426
  worded: (kind.isUnion() ? kind.types : [kind]).some((each) => (each.flags & ts.TypeFlags.StringLike) !== 0),
410
427
  guide: ts.displayPartsToString(one.getDocumentationComment(checker)).trim(),
428
+ record: recordOf(checker, kind),
411
429
  };
412
430
  });
413
431
  }
414
432
  }
415
433
  return [];
416
434
  }
417
- function elementAt(at, list) {
435
+ function elementAt(at, list, within) {
418
436
  const { program, checker } = compiled();
419
437
  const file = program.getSourceFile(resolve(ROOT, at));
420
438
  if (file === undefined)
@@ -424,6 +442,8 @@ function elementAt(at, list) {
424
442
  const consider = (node) => {
425
443
  if (held !== undefined)
426
444
  return;
445
+ if (within !== undefined && node.getStart() !== within)
446
+ return;
427
447
  const wanted = checker.getContextualType(node) ?? checker.getTypeAtLocation(node);
428
448
  const kinds = (wanted.isUnionOrIntersection() ? wanted.types : [wanted])
429
449
  .filter((one) => checker.getIndexTypeOfType(one, ts.IndexKind.Number) !== undefined)
@@ -455,8 +475,8 @@ export function listShape(at, list) {
455
475
  const held = elementAt(at, list);
456
476
  return held === undefined ? undefined : compiled().checker.typeToString(held.type);
457
477
  }
458
- export function listFields(at, list) {
459
- const held = elementAt(at, list);
478
+ export function listFields(at, list, within) {
479
+ const held = elementAt(at, list, within);
460
480
  if (held === undefined)
461
481
  return [];
462
482
  const { checker } = compiled();
@@ -480,12 +500,29 @@ export function listFields(at, list) {
480
500
  boolean: flagged(kind),
481
501
  worded: worded(kind),
482
502
  guide: ts.displayPartsToString(one.getDocumentationComment(checker)).trim(),
503
+ record: recordOf(checker, kind),
483
504
  };
484
505
  });
485
506
  }
486
507
  function elementOf(checker, type) {
487
508
  return checker.getIndexTypeOfType(type, ts.IndexKind.Number) ?? type;
488
509
  }
510
+ function recordOf(checker, type) {
511
+ for (const each of type.isUnion() ? type.types : [type]) {
512
+ if ((each.flags & ts.TypeFlags.StringLike) !== 0 || checker.getIndexTypeOfType(each, ts.IndexKind.Number) === undefined)
513
+ continue;
514
+ const element = elementOf(checker, each);
515
+ if ((element.flags & ts.TypeFlags.Object) === 0)
516
+ return [];
517
+ const fields = checker.getPropertiesOfType(element);
518
+ const worded = fields.every((one) => {
519
+ const kind = checker.getNonNullableType(checker.getTypeOfSymbol(one));
520
+ return (kind.isUnion() ? kind.types : [kind]).every((part) => (part.flags & ts.TypeFlags.StringLike) !== 0);
521
+ });
522
+ return worded ? fields.map((one) => one.getName()) : [];
523
+ }
524
+ return [];
525
+ }
489
526
  export function rowShape(at, list) {
490
527
  const { program, checker } = compiled();
491
528
  const file = program.getSourceFile(resolve(ROOT, at));
@@ -666,7 +703,38 @@ let passed;
666
703
  export function reached() {
667
704
  return [THREAD_AT, ...descend().threaded.keys()];
668
705
  }
706
+ let world = answering();
707
+ const worlds = new WeakMap();
708
+ function kept() {
709
+ const was = { passed, built, scanned, commands, ignores, readIgnores, source: new Map(source), asked: new Map(asked) };
710
+ return () => {
711
+ passed = was.passed;
712
+ built = was.built;
713
+ scanned = was.scanned;
714
+ commands = was.commands;
715
+ ignores = was.ignores;
716
+ readIgnores = was.readIgnores;
717
+ source.clear();
718
+ for (const [at, text] of was.source)
719
+ source.set(at, text);
720
+ asked.clear();
721
+ for (const [at, ignore] of was.asked)
722
+ asked.set(at, ignore);
723
+ };
724
+ }
669
725
  export function forget() {
726
+ const now = answering();
727
+ if (now === world)
728
+ libraries.clear();
729
+ else {
730
+ worlds.set(world, kept());
731
+ world = now;
732
+ const back = worlds.get(now);
733
+ if (back !== undefined) {
734
+ back();
735
+ return;
736
+ }
737
+ }
670
738
  passed = undefined;
671
739
  built = undefined;
672
740
  scanned = undefined;