@astrale-os/sdk 0.4.11 → 0.4.13

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 (331) hide show
  1. package/dist/auth/authenticate.d.ts +1 -1
  2. package/dist/auth/authenticate.d.ts.map +1 -1
  3. package/dist/auth/authenticate.js +2 -2
  4. package/dist/auth/authenticate.js.map +1 -1
  5. package/dist/auth/check.js +1 -1
  6. package/dist/auth/check.js.map +1 -1
  7. package/dist/auth/function-context.d.ts +1 -1
  8. package/dist/auth/function-context.d.ts.map +1 -1
  9. package/dist/auth/function-context.js +3 -3
  10. package/dist/auth/function-context.js.map +1 -1
  11. package/dist/auth/index.d.ts +15 -15
  12. package/dist/auth/index.d.ts.map +1 -1
  13. package/dist/auth/index.js +10 -10
  14. package/dist/auth/index.js.map +1 -1
  15. package/dist/auth/issuer-mint.d.ts +1 -1
  16. package/dist/auth/issuer-mint.d.ts.map +1 -1
  17. package/dist/auth/issuer-mint.js +2 -2
  18. package/dist/auth/issuer-mint.js.map +1 -1
  19. package/dist/auth/kernel-client.d.ts +1 -1
  20. package/dist/auth/kernel-client.d.ts.map +1 -1
  21. package/dist/auth/kernel-client.js +2 -2
  22. package/dist/auth/kernel-client.js.map +1 -1
  23. package/dist/auth/resolve.d.ts +2 -2
  24. package/dist/auth/resolve.d.ts.map +1 -1
  25. package/dist/auth/resolve.js +2 -2
  26. package/dist/auth/resolve.js.map +1 -1
  27. package/dist/auth/verify.d.ts +1 -1
  28. package/dist/auth/verify.d.ts.map +1 -1
  29. package/dist/auth/verify.js +2 -2
  30. package/dist/auth/verify.js.map +1 -1
  31. package/dist/cli/bin.js +1 -1
  32. package/dist/cli/bin.js.map +1 -1
  33. package/dist/cli/index.d.ts +2 -2
  34. package/dist/cli/index.d.ts.map +1 -1
  35. package/dist/cli/index.js +2 -2
  36. package/dist/cli/index.js.map +1 -1
  37. package/dist/cli/publish.js +1 -1
  38. package/dist/cli/publish.js.map +1 -1
  39. package/dist/cli/run.d.ts +9 -3
  40. package/dist/cli/run.d.ts.map +1 -1
  41. package/dist/cli/run.js +55 -5
  42. package/dist/cli/run.js.map +1 -1
  43. package/dist/cli/spec.d.ts +1 -1
  44. package/dist/cli/spec.d.ts.map +1 -1
  45. package/dist/cli/spec.js +1 -1
  46. package/dist/cli/spec.js.map +1 -1
  47. package/dist/config/define-domain.d.ts +2 -2
  48. package/dist/config/define-domain.d.ts.map +1 -1
  49. package/dist/config/deploy.d.ts +2 -2
  50. package/dist/config/deploy.d.ts.map +1 -1
  51. package/dist/config/index.d.ts +6 -6
  52. package/dist/config/index.d.ts.map +1 -1
  53. package/dist/config/index.js +3 -3
  54. package/dist/config/index.js.map +1 -1
  55. package/dist/define/index.d.ts +4 -4
  56. package/dist/define/index.d.ts.map +1 -1
  57. package/dist/define/index.js +2 -2
  58. package/dist/define/index.js.map +1 -1
  59. package/dist/define/remote-function.d.ts +7 -5
  60. package/dist/define/remote-function.d.ts.map +1 -1
  61. package/dist/define/remote-function.js.map +1 -1
  62. package/dist/define/view.d.ts +3 -3
  63. package/dist/define/view.d.ts.map +1 -1
  64. package/dist/deploy/check.d.ts +1 -1
  65. package/dist/deploy/check.d.ts.map +1 -1
  66. package/dist/deploy/check.js +1 -1
  67. package/dist/deploy/check.js.map +1 -1
  68. package/dist/deploy/index.d.ts +2 -2
  69. package/dist/deploy/index.d.ts.map +1 -1
  70. package/dist/deploy/index.js +2 -2
  71. package/dist/deploy/index.js.map +1 -1
  72. package/dist/dispatch/authorize.js +1 -1
  73. package/dist/dispatch/authorize.js.map +1 -1
  74. package/dist/dispatch/dispatcher.d.ts +11 -21
  75. package/dist/dispatch/dispatcher.d.ts.map +1 -1
  76. package/dist/dispatch/dispatcher.js +66 -65
  77. package/dist/dispatch/dispatcher.js.map +1 -1
  78. package/dist/dispatch/execute.d.ts +7 -6
  79. package/dist/dispatch/execute.d.ts.map +1 -1
  80. package/dist/dispatch/execute.js +1 -0
  81. package/dist/dispatch/execute.js.map +1 -1
  82. package/dist/dispatch/identity.d.ts +2 -2
  83. package/dist/dispatch/identity.d.ts.map +1 -1
  84. package/dist/dispatch/index.d.ts +6 -6
  85. package/dist/dispatch/index.d.ts.map +1 -1
  86. package/dist/dispatch/index.js +6 -6
  87. package/dist/dispatch/index.js.map +1 -1
  88. package/dist/dispatch/resolve.d.ts +1 -1
  89. package/dist/dispatch/resolve.d.ts.map +1 -1
  90. package/dist/dispatch/self.d.ts +1 -1
  91. package/dist/dispatch/self.d.ts.map +1 -1
  92. package/dist/domain/build-spec.d.ts +1 -1
  93. package/dist/domain/build-spec.d.ts.map +1 -1
  94. package/dist/domain/build-spec.js +2 -2
  95. package/dist/domain/build-spec.js.map +1 -1
  96. package/dist/domain/contract.d.ts +1 -1
  97. package/dist/domain/contract.d.ts.map +1 -1
  98. package/dist/domain/define.d.ts +4 -4
  99. package/dist/domain/define.d.ts.map +1 -1
  100. package/dist/domain/define.js +2 -2
  101. package/dist/domain/define.js.map +1 -1
  102. package/dist/domain/extend-functions.d.ts +1 -1
  103. package/dist/domain/extend-functions.d.ts.map +1 -1
  104. package/dist/domain/extend-functions.js +1 -1
  105. package/dist/domain/extend-functions.js.map +1 -1
  106. package/dist/domain/extend-views.d.ts +1 -1
  107. package/dist/domain/extend-views.d.ts.map +1 -1
  108. package/dist/domain/extend-views.js +1 -1
  109. package/dist/domain/extend-views.js.map +1 -1
  110. package/dist/domain/index.d.ts +4 -4
  111. package/dist/domain/index.d.ts.map +1 -1
  112. package/dist/domain/index.js +3 -3
  113. package/dist/domain/index.js.map +1 -1
  114. package/dist/index.d.ts +19 -19
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +10 -10
  117. package/dist/index.js.map +1 -1
  118. package/dist/linter/analyze.d.ts +6 -0
  119. package/dist/linter/analyze.d.ts.map +1 -0
  120. package/dist/linter/analyze.js +12 -0
  121. package/dist/linter/analyze.js.map +1 -0
  122. package/dist/linter/diagnostic.d.ts +32 -0
  123. package/dist/linter/diagnostic.d.ts.map +1 -0
  124. package/dist/linter/diagnostic.js +35 -0
  125. package/dist/linter/diagnostic.js.map +1 -0
  126. package/dist/linter/index.d.ts +10 -0
  127. package/dist/linter/index.d.ts.map +1 -0
  128. package/dist/linter/index.js +6 -0
  129. package/dist/linter/index.js.map +1 -0
  130. package/dist/linter/lint.d.ts +8 -0
  131. package/dist/linter/lint.d.ts.map +1 -0
  132. package/dist/linter/lint.js +26 -0
  133. package/dist/linter/lint.js.map +1 -0
  134. package/dist/linter/oxlint/config.d.ts +14 -0
  135. package/dist/linter/oxlint/config.d.ts.map +1 -0
  136. package/dist/linter/oxlint/config.js +18 -0
  137. package/dist/linter/oxlint/config.js.map +1 -0
  138. package/dist/linter/oxlint/plugin.d.ts +13 -0
  139. package/dist/linter/oxlint/plugin.d.ts.map +1 -0
  140. package/dist/linter/oxlint/plugin.js +14 -0
  141. package/dist/linter/oxlint/plugin.js.map +1 -0
  142. package/dist/linter/oxlint/run.d.ts +7 -0
  143. package/dist/linter/oxlint/run.d.ts.map +1 -0
  144. package/dist/linter/oxlint/run.js +122 -0
  145. package/dist/linter/oxlint/run.js.map +1 -0
  146. package/dist/linter/preflight.d.ts +2 -0
  147. package/dist/linter/preflight.d.ts.map +1 -0
  148. package/dist/linter/preflight.js +162 -0
  149. package/dist/linter/preflight.js.map +1 -0
  150. package/dist/linter/project.d.ts +12 -0
  151. package/dist/linter/project.d.ts.map +1 -0
  152. package/dist/linter/project.js +88 -0
  153. package/dist/linter/project.js.map +1 -0
  154. package/dist/linter/report.d.ts +4 -0
  155. package/dist/linter/report.d.ts.map +1 -0
  156. package/dist/linter/report.js +22 -0
  157. package/dist/linter/report.js.map +1 -0
  158. package/dist/linter/rules/catalog.d.ts +56 -0
  159. package/dist/linter/rules/catalog.d.ts.map +1 -0
  160. package/dist/linter/rules/catalog.js +53 -0
  161. package/dist/linter/rules/catalog.js.map +1 -0
  162. package/dist/linter/rules/core-has-no-async.d.ts +3 -0
  163. package/dist/linter/rules/core-has-no-async.d.ts.map +1 -0
  164. package/dist/linter/rules/core-has-no-async.js +43 -0
  165. package/dist/linter/rules/core-has-no-async.js.map +1 -0
  166. package/dist/linter/rules/core-is-pure.d.ts +3 -0
  167. package/dist/linter/rules/core-is-pure.d.ts.map +1 -0
  168. package/dist/linter/rules/core-is-pure.js +74 -0
  169. package/dist/linter/rules/core-is-pure.js.map +1 -0
  170. package/dist/linter/rules/no-nested-steps.d.ts +3 -0
  171. package/dist/linter/rules/no-nested-steps.d.ts.map +1 -0
  172. package/dist/linter/rules/no-nested-steps.js +32 -0
  173. package/dist/linter/rules/no-nested-steps.js.map +1 -0
  174. package/dist/linter/rules/no-read-inside-mutate.d.ts +3 -0
  175. package/dist/linter/rules/no-read-inside-mutate.d.ts.map +1 -0
  176. package/dist/linter/rules/no-read-inside-mutate.js +64 -0
  177. package/dist/linter/rules/no-read-inside-mutate.js.map +1 -0
  178. package/dist/linter/rules/one-remote-definition-per-file.d.ts +3 -0
  179. package/dist/linter/rules/one-remote-definition-per-file.d.ts.map +1 -0
  180. package/dist/linter/rules/one-remote-definition-per-file.js +23 -0
  181. package/dist/linter/rules/one-remote-definition-per-file.js.map +1 -0
  182. package/dist/linter/rules/oxlint-ast.d.ts +30 -0
  183. package/dist/linter/rules/oxlint-ast.d.ts.map +1 -0
  184. package/dist/linter/rules/oxlint-ast.js +104 -0
  185. package/dist/linter/rules/oxlint-ast.js.map +1 -0
  186. package/dist/linter/rules/oxlint-rule.d.ts +45 -0
  187. package/dist/linter/rules/oxlint-rule.d.ts.map +1 -0
  188. package/dist/linter/rules/oxlint-rule.js +2 -0
  189. package/dist/linter/rules/oxlint-rule.js.map +1 -0
  190. package/dist/linter/rules/project-rule.d.ts +10 -0
  191. package/dist/linter/rules/project-rule.d.ts.map +1 -0
  192. package/dist/linter/rules/project-rule.js +24 -0
  193. package/dist/linter/rules/project-rule.js.map +1 -0
  194. package/dist/linter/rules/thin-schema-composition-root.d.ts +3 -0
  195. package/dist/linter/rules/thin-schema-composition-root.d.ts.map +1 -0
  196. package/dist/linter/rules/thin-schema-composition-root.js +23 -0
  197. package/dist/linter/rules/thin-schema-composition-root.js.map +1 -0
  198. package/dist/linter/source.d.ts +24 -0
  199. package/dist/linter/source.d.ts.map +1 -0
  200. package/dist/linter/source.js +279 -0
  201. package/dist/linter/source.js.map +1 -0
  202. package/dist/linter/suppression.d.ts +3 -0
  203. package/dist/linter/suppression.d.ts.map +1 -0
  204. package/dist/linter/suppression.js +11 -0
  205. package/dist/linter/suppression.js.map +1 -0
  206. package/dist/method/class.d.ts +3 -3
  207. package/dist/method/class.d.ts.map +1 -1
  208. package/dist/method/context.d.ts +6 -4
  209. package/dist/method/context.d.ts.map +1 -1
  210. package/dist/method/index.d.ts +5 -5
  211. package/dist/method/index.d.ts.map +1 -1
  212. package/dist/method/index.js +2 -2
  213. package/dist/method/index.js.map +1 -1
  214. package/dist/method/single.d.ts +2 -2
  215. package/dist/method/single.d.ts.map +1 -1
  216. package/dist/server/auxiliary-routes.d.ts +3 -3
  217. package/dist/server/auxiliary-routes.d.ts.map +1 -1
  218. package/dist/server/auxiliary-routes.js +11 -11
  219. package/dist/server/auxiliary-routes.js.map +1 -1
  220. package/dist/server/config.d.ts +1 -1
  221. package/dist/server/config.d.ts.map +1 -1
  222. package/dist/server/create.d.ts +2 -2
  223. package/dist/server/create.d.ts.map +1 -1
  224. package/dist/server/create.js +11 -11
  225. package/dist/server/create.js.map +1 -1
  226. package/dist/server/domain-entry.d.ts +5 -5
  227. package/dist/server/domain-entry.d.ts.map +1 -1
  228. package/dist/server/domain-entry.js +2 -2
  229. package/dist/server/domain-entry.js.map +1 -1
  230. package/dist/server/index.d.ts +11 -11
  231. package/dist/server/index.d.ts.map +1 -1
  232. package/dist/server/index.js +7 -7
  233. package/dist/server/index.js.map +1 -1
  234. package/dist/server/start.d.ts +1 -1
  235. package/dist/server/start.d.ts.map +1 -1
  236. package/dist/server/start.js.map +1 -1
  237. package/dist/server/worker-entry.d.ts +1 -1
  238. package/dist/server/worker-entry.d.ts.map +1 -1
  239. package/dist/server/worker-entry.js +3 -3
  240. package/dist/server/worker-entry.js.map +1 -1
  241. package/dist/step/index.d.ts +4 -4
  242. package/dist/step/index.d.ts.map +1 -1
  243. package/dist/step/index.js +2 -2
  244. package/dist/step/index.js.map +1 -1
  245. package/dist/step/inline.d.ts +1 -1
  246. package/dist/step/inline.d.ts.map +1 -1
  247. package/dist/step/inline.js +1 -1
  248. package/dist/step/inline.js.map +1 -1
  249. package/package.json +29 -8
  250. package/src/auth/authenticate.ts +3 -3
  251. package/src/auth/check.ts +1 -1
  252. package/src/auth/function-context.ts +4 -4
  253. package/src/auth/index.ts +15 -15
  254. package/src/auth/issuer-mint.ts +3 -3
  255. package/src/auth/kernel-client.ts +3 -3
  256. package/src/auth/resolve.ts +4 -4
  257. package/src/auth/verify.ts +3 -3
  258. package/src/cli/bin.ts +1 -1
  259. package/src/cli/index.ts +2 -2
  260. package/src/cli/publish.ts +1 -1
  261. package/src/cli/run.ts +59 -8
  262. package/src/cli/spec.ts +3 -3
  263. package/src/config/define-domain.ts +2 -2
  264. package/src/config/deploy.ts +2 -2
  265. package/src/config/index.ts +6 -6
  266. package/src/define/index.ts +4 -4
  267. package/src/define/remote-function.ts +7 -5
  268. package/src/define/view.ts +3 -3
  269. package/src/deploy/check.ts +2 -2
  270. package/src/deploy/index.ts +2 -2
  271. package/src/dispatch/authorize.ts +1 -1
  272. package/src/dispatch/dispatcher.ts +76 -83
  273. package/src/dispatch/execute.ts +8 -6
  274. package/src/dispatch/identity.ts +2 -2
  275. package/src/dispatch/index.ts +6 -6
  276. package/src/dispatch/resolve.ts +1 -1
  277. package/src/dispatch/self.ts +1 -1
  278. package/src/domain/build-spec.ts +4 -4
  279. package/src/domain/contract.ts +1 -1
  280. package/src/domain/define.ts +6 -6
  281. package/src/domain/extend-functions.ts +2 -2
  282. package/src/domain/extend-views.ts +2 -2
  283. package/src/domain/index.ts +4 -4
  284. package/src/index.ts +20 -20
  285. package/src/linter/analyze.ts +17 -0
  286. package/src/linter/diagnostic.ts +68 -0
  287. package/src/linter/docs/DIAGNOSTICS.md +47 -0
  288. package/src/linter/docs/JUDGMENT.md +81 -0
  289. package/src/linter/docs/PRINCIPLES.md +97 -0
  290. package/src/linter/docs/README.md +38 -0
  291. package/src/linter/docs/RULE-AUTHORING.md +144 -0
  292. package/src/linter/docs/RULES.md +153 -0
  293. package/src/linter/index.ts +14 -0
  294. package/src/linter/lint.ts +32 -0
  295. package/src/linter/oxlint/astrale-ox.d.ts +5 -0
  296. package/src/linter/oxlint/config.ts +19 -0
  297. package/src/linter/oxlint/plugin.js +16 -0
  298. package/src/linter/oxlint/run.ts +162 -0
  299. package/src/linter/preflight.ts +181 -0
  300. package/src/linter/project.ts +108 -0
  301. package/src/linter/report.ts +25 -0
  302. package/src/linter/rules/catalog.ts +67 -0
  303. package/src/linter/rules/core-has-no-async.js +43 -0
  304. package/src/linter/rules/core-is-pure.ts +103 -0
  305. package/src/linter/rules/no-nested-steps.js +37 -0
  306. package/src/linter/rules/no-read-inside-mutate.js +72 -0
  307. package/src/linter/rules/one-remote-definition-per-file.ts +29 -0
  308. package/src/linter/rules/oxlint-ast.js +110 -0
  309. package/src/linter/rules/oxlint-rule.ts +35 -0
  310. package/src/linter/rules/project-rule.ts +37 -0
  311. package/src/linter/rules/thin-schema-composition-root.ts +26 -0
  312. package/src/linter/source.ts +323 -0
  313. package/src/linter/suppression.ts +11 -0
  314. package/src/method/class.ts +3 -3
  315. package/src/method/context.ts +6 -4
  316. package/src/method/index.ts +5 -5
  317. package/src/method/single.ts +2 -2
  318. package/src/server/auxiliary-routes.ts +22 -17
  319. package/src/server/config.ts +1 -1
  320. package/src/server/create.ts +15 -15
  321. package/src/server/domain-entry.ts +7 -7
  322. package/src/server/index.ts +11 -11
  323. package/src/server/start.ts +3 -2
  324. package/src/server/worker-entry.ts +4 -4
  325. package/src/step/index.ts +4 -4
  326. package/src/step/inline.ts +2 -2
  327. package/dist/defer/index.d.ts +0 -24
  328. package/dist/defer/index.d.ts.map +0 -1
  329. package/dist/defer/index.js +0 -42
  330. package/dist/defer/index.js.map +0 -1
  331. package/src/defer/index.ts +0 -64
@@ -0,0 +1,181 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { readFile, realpath } from 'node:fs/promises'
3
+ import { createRequire } from 'node:module'
4
+ import { dirname, join, relative, resolve } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ import { LinterToolError } from './diagnostic.js'
8
+
9
+ const brandedPackages = [
10
+ '@astrale-os/kernel-client',
11
+ '@astrale-os/kernel-core',
12
+ '@astrale-os/kernel-dsl',
13
+ ] as const
14
+
15
+ const conventionalMembers = ['client', 'frontend', 'schema', 'worker'] as const
16
+
17
+ type Resolution = {
18
+ consumer: string
19
+ path: string
20
+ }
21
+
22
+ type ConsumerPackageJson = {
23
+ name?: string
24
+ dependencies?: Record<string, string>
25
+ optionalDependencies?: Record<string, string>
26
+ workspaces?: string[] | { packages?: string[] }
27
+ }
28
+
29
+ export async function assertSingleTypeUniverse(root: string): Promise<void> {
30
+ const manifests = await collectConsumerManifests(root)
31
+ const consumers = [
32
+ ...manifests.map((path) => ({ label: relative(root, dirname(path)) || '.', path })),
33
+ { label: '@astrale-os/sdk/linter', path: fileURLPackageJson() },
34
+ ]
35
+
36
+ for (const packageName of brandedPackages) {
37
+ const resolutions: Resolution[] = []
38
+ for (const consumer of consumers) {
39
+ const resolved = await resolvePackageRoot(packageName, consumer.path)
40
+ if (resolved) resolutions.push({ consumer: consumer.label, path: resolved })
41
+ }
42
+ const paths = new Set(resolutions.map((resolution) => resolution.path))
43
+ if (paths.size <= 1) continue
44
+ const detail = formatResolutions(resolutions)
45
+ throw new LinterToolError(
46
+ `Multiple physical installations of ${packageName} would create incompatible Astrale types.\n` +
47
+ `${detail}\nReinstall the workspace so every member resolves one compatible package instance.`,
48
+ )
49
+ }
50
+ }
51
+
52
+ function formatResolutions(resolutions: readonly Resolution[]): string {
53
+ const byPath = new Map<string, string[]>()
54
+ for (const resolution of resolutions) {
55
+ const consumers = byPath.get(resolution.path) ?? []
56
+ consumers.push(resolution.consumer)
57
+ byPath.set(resolution.path, consumers)
58
+ }
59
+ return [...byPath]
60
+ .sort(([a], [b]) => a.localeCompare(b))
61
+ .map(([path, consumers]) => {
62
+ const sorted = [...new Set(consumers)].sort()
63
+ const shown = sorted.slice(0, 4).join(', ')
64
+ const omitted = sorted.length > 4 ? ` (+${sorted.length - 4} more)` : ''
65
+ return ` ${path}\n from: ${shown}${omitted}`
66
+ })
67
+ .join('\n')
68
+ }
69
+
70
+ async function collectConsumerManifests(root: string): Promise<string[]> {
71
+ const rootManifest = join(root, 'package.json')
72
+ if (!existsSync(rootManifest)) return []
73
+
74
+ const manifests = new Set([rootManifest])
75
+ const packageJson = await readPackageJson(rootManifest)
76
+ const workspaces = Array.isArray(packageJson?.workspaces)
77
+ ? packageJson.workspaces
78
+ : packageJson?.workspaces?.packages
79
+
80
+ for (const workspace of workspaces ?? []) {
81
+ if (typeof workspace !== 'string' || hasGlob(workspace)) continue
82
+ addManifestWithinRoot(manifests, root, workspace)
83
+ }
84
+
85
+ for (const member of conventionalMembers) addManifestWithinRoot(manifests, root, member)
86
+ await addDomainContractDependencyManifests(manifests)
87
+ return [...manifests].sort()
88
+ }
89
+
90
+ async function readPackageJson(manifest: string): Promise<ConsumerPackageJson | undefined> {
91
+ try {
92
+ return JSON.parse(await readFile(manifest, 'utf8'))
93
+ } catch {
94
+ return undefined
95
+ }
96
+ }
97
+
98
+ async function addDomainContractDependencyManifests(manifests: Set<string>): Promise<void> {
99
+ const pending = [...manifests]
100
+ for (let index = 0; index < pending.length; index++) {
101
+ const consumer = pending[index]!
102
+ const packageJson = await readPackageJson(consumer)
103
+ const dependencies = {
104
+ ...packageJson?.optionalDependencies,
105
+ ...packageJson?.dependencies,
106
+ }
107
+ for (const packageName of Object.keys(dependencies).sort()) {
108
+ if (!isDomainContractPackage(packageName)) continue
109
+ const manifest = await resolveDependencyManifest(packageName, consumer)
110
+ if (!manifest || manifests.has(manifest)) continue
111
+ manifests.add(manifest)
112
+ pending.push(manifest)
113
+ }
114
+ }
115
+ }
116
+
117
+ async function resolveDependencyManifest(
118
+ packageName: string,
119
+ consumerManifest: string,
120
+ ): Promise<string | undefined> {
121
+ const require = createRequire(consumerManifest)
122
+ try {
123
+ return await realpath(require.resolve(`${packageName}/package.json`))
124
+ } catch {
125
+ try {
126
+ let directory = dirname(await realpath(require.resolve(packageName)))
127
+ for (;;) {
128
+ const manifest = join(directory, 'package.json')
129
+ if (existsSync(manifest)) {
130
+ const packageJson = await readPackageJson(manifest)
131
+ if (packageJson?.name === packageName) return await realpath(manifest)
132
+ }
133
+ const parent = dirname(directory)
134
+ if (parent === directory) return undefined
135
+ directory = parent
136
+ }
137
+ } catch {
138
+ return undefined
139
+ }
140
+ }
141
+ }
142
+
143
+ function addManifestWithinRoot(manifests: Set<string>, root: string, member: string): void {
144
+ const manifest = resolve(root, member, 'package.json')
145
+ const parent = relative(root, manifest)
146
+ if (parent.startsWith('..') || parent === '') return
147
+ if (existsSync(manifest)) manifests.add(manifest)
148
+ }
149
+
150
+ function hasGlob(path: string): boolean {
151
+ return /[*?[\]{}!]/u.test(path)
152
+ }
153
+
154
+ function isDomainContractPackage(packageName: string): boolean {
155
+ return packageName.startsWith('@astrale-domains/')
156
+ }
157
+
158
+ async function resolvePackageRoot(
159
+ packageName: (typeof brandedPackages)[number],
160
+ manifest: string,
161
+ ): Promise<string | undefined> {
162
+ if (!existsSync(manifest)) return undefined
163
+ try {
164
+ const resolved = createRequire(manifest).resolve(`${packageName}/package.json`)
165
+ return await realpath(dirname(resolved))
166
+ } catch {
167
+ return undefined
168
+ }
169
+ }
170
+
171
+ function fileURLPackageJson(): string {
172
+ let directory = dirname(fileURLToPath(import.meta.url))
173
+ for (;;) {
174
+ const candidate = join(directory, 'package.json')
175
+ if (existsSync(candidate)) return candidate
176
+ const parent = dirname(directory)
177
+ if (parent === directory) break
178
+ directory = parent
179
+ }
180
+ return join(directory, 'package.json')
181
+ }
@@ -0,0 +1,108 @@
1
+ import { readdir, readFile, realpath } from 'node:fs/promises'
2
+ import { dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path'
3
+
4
+ import { scanSource, type SourceFile } from './source.js'
5
+
6
+ const ignoredDirectories = new Set([
7
+ '.astrale',
8
+ '.dist',
9
+ '.domain-studio',
10
+ '.git',
11
+ '.output',
12
+ '.turbo',
13
+ '.wrangler',
14
+ 'coverage',
15
+ 'dist',
16
+ 'dist-client',
17
+ 'node_modules',
18
+ ])
19
+
20
+ const sourceExtensions = new Set(['.cjs', '.cts', '.js', '.jsx', '.mjs', '.mts', '.ts', '.tsx'])
21
+
22
+ export type DomainProject = {
23
+ root: string
24
+ files: readonly SourceFile[]
25
+ filesByPath: ReadonlyMap<string, SourceFile>
26
+ }
27
+
28
+ export async function discoverProject(root: string): Promise<DomainProject> {
29
+ const projectRoot = await realpath(resolve(root))
30
+ const paths: string[] = []
31
+ await collectSourcePaths(projectRoot, paths)
32
+ paths.sort()
33
+
34
+ const files = await Promise.all(
35
+ paths.map(async (path) => {
36
+ const text = await readFile(path, 'utf8')
37
+ return scanSource(path, normalizePath(relative(projectRoot, path)), text)
38
+ }),
39
+ )
40
+ return {
41
+ root: projectRoot,
42
+ files,
43
+ filesByPath: new Map(files.map((file) => [file.path, file])),
44
+ }
45
+ }
46
+
47
+ async function collectSourcePaths(directory: string, result: string[]): Promise<void> {
48
+ const entries = await readdir(directory, { withFileTypes: true })
49
+ await Promise.all(
50
+ entries.map(async (entry) => {
51
+ if (entry.isSymbolicLink()) return
52
+ const path = join(directory, entry.name)
53
+ if (entry.isDirectory()) {
54
+ if (!ignoredDirectories.has(entry.name)) await collectSourcePaths(path, result)
55
+ return
56
+ }
57
+ if (!entry.isFile() || !sourceExtensions.has(extname(entry.name))) return
58
+ if (entry.name.endsWith('.d.ts') || entry.name.includes('.gen.')) return
59
+ result.push(path)
60
+ }),
61
+ )
62
+ }
63
+
64
+ export function normalizePath(path: string): string {
65
+ return sep === '/' ? path : path.split(sep).join('/')
66
+ }
67
+
68
+ export function isProductionSource(relativePath: string): boolean {
69
+ const normalized = normalizePath(relativePath)
70
+ return (
71
+ !normalized.split('/').includes('__tests__') &&
72
+ !/\.(?:test|spec|bench|perf)\.[cm]?[jt]sx?$/.test(normalized) &&
73
+ !normalized.includes('.gen.')
74
+ )
75
+ }
76
+
77
+ export function isProductionCore(relativePath: string): boolean {
78
+ const normalized = normalizePath(relativePath)
79
+ return (normalized === 'core' || normalized.startsWith('core/')) && isProductionSource(normalized)
80
+ }
81
+
82
+ export function resolveProjectImport(
83
+ project: DomainProject,
84
+ importer: SourceFile,
85
+ specifier: string,
86
+ ): SourceFile | undefined {
87
+ if (!specifier.startsWith('.')) return undefined
88
+ const unresolved = resolve(dirname(importer.path), specifier)
89
+ for (const candidate of importCandidates(unresolved)) {
90
+ const file = project.filesByPath.get(candidate)
91
+ if (file) return file
92
+ }
93
+ return undefined
94
+ }
95
+
96
+ function importCandidates(path: string): string[] {
97
+ const extension = extname(path)
98
+ const candidates = [path]
99
+ if (extension === '.js' || extension === '.jsx' || extension === '.mjs' || extension === '.cjs') {
100
+ const stem = path.slice(0, -extension.length)
101
+ candidates.push(`${stem}.ts`, `${stem}.tsx`, `${stem}.mts`, `${stem}.cts`)
102
+ }
103
+ if (!extension) {
104
+ candidates.push(`${path}.ts`, `${path}.tsx`, `${path}.mts`, `${path}.cts`)
105
+ candidates.push(join(path, 'index.ts'), join(path, 'index.tsx'))
106
+ }
107
+ return candidates.map((candidate) => (isAbsolute(candidate) ? candidate : resolve(candidate)))
108
+ }
@@ -0,0 +1,25 @@
1
+ import type { LintResult } from './diagnostic.js'
2
+
3
+ export type LintReportFormat = 'json' | 'stylish'
4
+
5
+ export function formatLintResult(result: LintResult, format: LintReportFormat = 'stylish'): string {
6
+ if (format === 'json') return `${JSON.stringify(result, null, 2)}\n`
7
+ const lines: string[] = []
8
+ for (const diagnostic of result.diagnostics) {
9
+ const location = diagnostic.location
10
+ ? `${diagnostic.location.path}:${diagnostic.location.line}:${diagnostic.location.column}`
11
+ : result.root
12
+ lines.push(`${location} ${diagnostic.severity} ${diagnostic.id} ${diagnostic.message}`)
13
+ if (diagnostic.help) lines.push(` ${diagnostic.help}`)
14
+ }
15
+
16
+ const errors = result.diagnostics.filter((diagnostic) => diagnostic.severity === 'error').length
17
+ const warnings = result.diagnostics.length - errors
18
+ if (lines.length > 0) lines.push('')
19
+ lines.push(
20
+ errors === 0
21
+ ? `\u2713 ${result.files} files linted in ${result.durationMs}ms${warnings ? ` (${warnings} warnings)` : ''}`
22
+ : `\u2717 ${errors} ${errors === 1 ? 'error' : 'errors'}, ${warnings} ${warnings === 1 ? 'warning' : 'warnings'} in ${result.durationMs}ms`,
23
+ )
24
+ return `${lines.join('\n')}\n`
25
+ }
@@ -0,0 +1,67 @@
1
+ export type RuleOwner = 'analyzer' | 'oxlint' | 'sdk'
2
+
3
+ export type RuleInfo = {
4
+ slug: string
5
+ owner: RuleOwner
6
+ message: string
7
+ help: string
8
+ url: string
9
+ }
10
+
11
+ const ruleDocsUrl = (slug: string) => `https://docs.astrale.ai/linter/rules/${slug}`
12
+
13
+ export const ruleCatalog = {
14
+ 'thin-schema-composition-root': {
15
+ slug: 'thin-schema-composition-root',
16
+ owner: 'analyzer',
17
+ message: 'The schema composition root defines a schema member inline.',
18
+ help: 'Move this member definition to its bounded-context module and import it here.',
19
+ url: ruleDocsUrl('thin-schema-composition-root'),
20
+ },
21
+ 'one-remote-definition-per-file': {
22
+ slug: 'one-remote-definition-per-file',
23
+ owner: 'analyzer',
24
+ message: 'This file defines more than one Astrale remote callable.',
25
+ help: 'Keep one remoteMethod or defineRemoteFunction definition per implementation file.',
26
+ url: ruleDocsUrl('one-remote-definition-per-file'),
27
+ },
28
+ 'core-is-pure': {
29
+ slug: 'core-is-pure',
30
+ owner: 'analyzer',
31
+ message: 'Pure core code depends on an effectful or runtime-only module.',
32
+ help: 'Move I/O orchestration to runtime or integrations and pass pure data into core.',
33
+ url: ruleDocsUrl('core-is-pure'),
34
+ },
35
+ 'core-has-no-async': {
36
+ slug: 'core-has-no-async',
37
+ owner: 'oxlint',
38
+ message: 'Production core code contains asynchronous syntax.',
39
+ help: 'Keep core deterministic and synchronous; perform asynchronous work in runtime or integrations.',
40
+ url: ruleDocsUrl('core-has-no-async'),
41
+ },
42
+ 'no-nested-steps': {
43
+ slug: 'no-nested-steps',
44
+ owner: 'oxlint',
45
+ message: 'A durable step directly contains another durable step.',
46
+ help: 'Give each stage one step boundary and call helper-owned steps outside another step callback.',
47
+ url: ruleDocsUrl('no-nested-steps'),
48
+ },
49
+ 'no-read-inside-mutate': {
50
+ slug: 'no-read-inside-mutate',
51
+ owner: 'oxlint',
52
+ message: 'A graph mutation builder performs a read or is asynchronous.',
53
+ help: 'Read before mutate, then keep the mutation callback synchronous and limited to patch construction.',
54
+ url: ruleDocsUrl('no-read-inside-mutate'),
55
+ },
56
+ } as const satisfies Record<string, RuleInfo>
57
+
58
+ export type ImplementedRuleSlug = keyof typeof ruleCatalog
59
+
60
+ export function ruleId(slug: ImplementedRuleSlug): `astrale/${ImplementedRuleSlug}` {
61
+ return `astrale/${slug}`
62
+ }
63
+
64
+ export function ruleInfo(id: string): RuleInfo | undefined {
65
+ const slug = id.startsWith('astrale/') ? id.slice('astrale/'.length) : id
66
+ return ruleCatalog[slug as ImplementedRuleSlug]
67
+ }
@@ -0,0 +1,43 @@
1
+ // @ts-check
2
+
3
+ import { isProductionCoreFile, nearestFunction, reportUnlessSuppressed } from './oxlint-ast.js'
4
+
5
+ const id = 'astrale/core-has-no-async'
6
+
7
+ /** @type {import('./oxlint-rule.js').OxlintRule} */
8
+ export const coreHasNoAsync = {
9
+ meta: {
10
+ type: 'problem',
11
+ docs: {
12
+ description: 'Production core code contains asynchronous syntax.',
13
+ url: 'https://docs.astrale.ai/linter/rules/core-has-no-async',
14
+ },
15
+ messages: {
16
+ asyncFunction: 'Production core functions must be synchronous.',
17
+ topLevelAwait: 'Production core modules must not use top-level await.',
18
+ },
19
+ schema: [],
20
+ },
21
+ create(context) {
22
+ if (!isProductionCoreFile(context)) return {}
23
+ /** @param {import('./oxlint-rule.js').AstNode} node */
24
+ const reportFunction = (node) => {
25
+ if (node.async) reportUnlessSuppressed(context, node, id, 'asyncFunction')
26
+ }
27
+ return {
28
+ FunctionDeclaration: reportFunction,
29
+ FunctionExpression: reportFunction,
30
+ ArrowFunctionExpression: reportFunction,
31
+ AwaitExpression(node) {
32
+ if (!nearestFunction(node.parent)) {
33
+ reportUnlessSuppressed(context, node, id, 'topLevelAwait')
34
+ }
35
+ },
36
+ ForOfStatement(node) {
37
+ if (node.await && !nearestFunction(node.parent)) {
38
+ reportUnlessSuppressed(context, node, id, 'topLevelAwait')
39
+ }
40
+ },
41
+ }
42
+ },
43
+ }
@@ -0,0 +1,103 @@
1
+ import type { LintDiagnostic } from '../diagnostic.js'
2
+ import type { SourceFile, SourceImport } from '../source.js'
3
+
4
+ import {
5
+ isProductionCore,
6
+ isProductionSource,
7
+ resolveProjectImport,
8
+ type DomainProject,
9
+ } from '../project.js'
10
+ import { analyzerDiagnostic, type AnalyzerRule } from './project-rule.js'
11
+
12
+ const forbiddenPackages = new Set([
13
+ '@astrale-os/adapter-astrale',
14
+ '@astrale-os/adapter-cloudflare',
15
+ '@astrale-os/kernel-api',
16
+ '@astrale-os/kernel-client',
17
+ '@astrale-os/kernel-server',
18
+ '@astrale-os/kernel-test',
19
+ '@astrale-os/sdk',
20
+ 'node:child_process',
21
+ 'node:cluster',
22
+ 'node:dgram',
23
+ 'node:dns',
24
+ 'node:fs',
25
+ 'node:fs/promises',
26
+ 'node:http',
27
+ 'node:http2',
28
+ 'node:https',
29
+ 'node:net',
30
+ 'node:tls',
31
+ 'node:worker_threads',
32
+ ])
33
+
34
+ const forbiddenRoots = new Set(['client', 'functions', 'integrations', 'runtime', 'simulation'])
35
+ const forbiddenRootFiles = new Set(['astrale.config.ts', 'deps.ts', 'domain.ts', 'env.ts'])
36
+
37
+ export const coreIsPure: AnalyzerRule = {
38
+ slug: 'core-is-pure',
39
+ analyze(project: DomainProject): readonly LintDiagnostic[] {
40
+ const diagnostics: LintDiagnostic[] = []
41
+ const visited = new Set<string>()
42
+ const pending = project.files.filter((file) => isProductionCore(file.relativePath))
43
+
44
+ while (pending.length > 0) {
45
+ const file = pending.pop()!
46
+ if (visited.has(file.path)) continue
47
+ visited.add(file.path)
48
+
49
+ for (const sourceImport of file.imports) {
50
+ const forbiddenPackage = forbiddenPackageName(sourceImport.specifier)
51
+ if (forbiddenPackage) {
52
+ pushDiagnostic(
53
+ diagnostics,
54
+ this.slug,
55
+ file,
56
+ sourceImport,
57
+ `Import ${forbiddenPackage} crosses the pure-core boundary.`,
58
+ )
59
+ continue
60
+ }
61
+
62
+ const dependency = resolveProjectImport(project, file, sourceImport.specifier)
63
+ if (!dependency || !isProductionSource(dependency.relativePath)) continue
64
+ if (isForbiddenProjectLayer(dependency.relativePath)) {
65
+ pushDiagnostic(
66
+ diagnostics,
67
+ this.slug,
68
+ file,
69
+ sourceImport,
70
+ `Import ${sourceImport.specifier} reaches ${dependency.relativePath}.`,
71
+ )
72
+ continue
73
+ }
74
+ pending.push(dependency)
75
+ }
76
+ }
77
+ return diagnostics
78
+ },
79
+ }
80
+
81
+ function forbiddenPackageName(specifier: string): string | undefined {
82
+ if (forbiddenPackages.has(specifier)) return specifier
83
+ for (const name of forbiddenPackages) {
84
+ if (specifier.startsWith(`${name}/`)) return name
85
+ }
86
+ return undefined
87
+ }
88
+
89
+ function isForbiddenProjectLayer(relativePath: string): boolean {
90
+ const [root] = relativePath.split('/')
91
+ return forbiddenRoots.has(root ?? '') || forbiddenRootFiles.has(relativePath)
92
+ }
93
+
94
+ function pushDiagnostic(
95
+ diagnostics: LintDiagnostic[],
96
+ slug: AnalyzerRule['slug'],
97
+ file: SourceFile,
98
+ sourceImport: SourceImport,
99
+ detail: string,
100
+ ): void {
101
+ const diagnostic = analyzerDiagnostic(slug, file, sourceImport.token, detail)
102
+ if (diagnostic) diagnostics.push(diagnostic)
103
+ }
@@ -0,0 +1,37 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ callbackCall,
5
+ isProductionFile,
6
+ isStepRun,
7
+ nearestFunction,
8
+ reportUnlessSuppressed,
9
+ } from './oxlint-ast.js'
10
+
11
+ const id = 'astrale/no-nested-steps'
12
+
13
+ /** @type {import('./oxlint-rule.js').OxlintRule} */
14
+ export const noNestedSteps = {
15
+ meta: {
16
+ type: 'problem',
17
+ docs: {
18
+ description: 'A durable step directly contains another durable step.',
19
+ url: 'https://docs.astrale.ai/linter/rules/no-nested-steps',
20
+ },
21
+ messages: {
22
+ nested: 'A step.run callback must not directly invoke another step.run.',
23
+ },
24
+ schema: [],
25
+ },
26
+ create(context) {
27
+ if (!isProductionFile(context)) return {}
28
+ return {
29
+ CallExpression(node) {
30
+ if (!isStepRun(node)) return
31
+ const containingFunction = nearestFunction(node.parent)
32
+ if (!containingFunction || !isStepRun(callbackCall(containingFunction))) return
33
+ reportUnlessSuppressed(context, node, id, 'nested')
34
+ },
35
+ }
36
+ },
37
+ }
@@ -0,0 +1,72 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ callbackCall,
5
+ isFunctionNode,
6
+ isMutateCall,
7
+ isProductionFile,
8
+ nearestFunction,
9
+ reportUnlessSuppressed,
10
+ staticMember,
11
+ } from './oxlint-ast.js'
12
+
13
+ const id = 'astrale/no-read-inside-mutate'
14
+ const readMethods = new Set([
15
+ 'call',
16
+ 'check',
17
+ 'children',
18
+ 'get',
19
+ 'getOrThrow',
20
+ 'neighbors',
21
+ 'query',
22
+ 'refresh',
23
+ 'whoami',
24
+ ])
25
+
26
+ /** @type {import('./oxlint-rule.js').OxlintRule} */
27
+ export const noReadInsideMutate = {
28
+ meta: {
29
+ type: 'problem',
30
+ docs: {
31
+ description: 'A graph mutation builder performs a read or is asynchronous.',
32
+ url: 'https://docs.astrale.ai/linter/rules/no-read-inside-mutate',
33
+ },
34
+ messages: {
35
+ asyncCallback: 'A kernel.mutate callback must be synchronous.',
36
+ read: 'Read graph state before kernel.mutate; its callback only constructs a patch.',
37
+ },
38
+ schema: [],
39
+ },
40
+ create(context) {
41
+ if (!isProductionFile(context)) return {}
42
+ return {
43
+ CallExpression(node) {
44
+ if (isMutateCall(node)) {
45
+ const callback = node.arguments?.find(isFunctionNode)
46
+ if (callback?.async) {
47
+ reportUnlessSuppressed(context, callback, id, 'asyncCallback')
48
+ }
49
+ return
50
+ }
51
+
52
+ const callback = nearestFunction(node.parent)
53
+ if (!callback || callback.async) return
54
+ const mutate = callbackCall(callback)
55
+ if (!isMutateCall(mutate)) return
56
+
57
+ const mutationMember = staticMember(mutate?.callee)
58
+ const readMember = staticMember(node.callee)
59
+ if (!mutationMember || !readMember || !readMethods.has(readMember.property)) return
60
+ if (!sameKernelRoot(context, mutationMember.object, readMember.object)) return
61
+ reportUnlessSuppressed(context, node, id, 'read')
62
+ },
63
+ }
64
+ },
65
+ }
66
+
67
+ /** @param {import('./oxlint-rule.js').OxlintRuleContext} context @param {import('./oxlint-rule.js').AstNode} mutationRoot @param {import('./oxlint-rule.js').AstNode} readRoot */
68
+ function sameKernelRoot(context, mutationRoot, readRoot) {
69
+ const mutationText = context.sourceCode.getText(mutationRoot)
70
+ const readText = context.sourceCode.getText(readRoot)
71
+ return readText === mutationText || readText.startsWith(`${mutationText}.`)
72
+ }
@@ -0,0 +1,29 @@
1
+ import type { LintDiagnostic } from '../diagnostic.js'
2
+
3
+ import { isProductionSource, type DomainProject } from '../project.js'
4
+ import { importedCalls } from '../source.js'
5
+ import { analyzerDiagnostic, type AnalyzerRule } from './project-rule.js'
6
+
7
+ const sdkModules = new Set(['@astrale-os/sdk'])
8
+ const remoteDefinitions = new Set(['defineRemoteFunction', 'remoteMethod'])
9
+
10
+ export const oneRemoteDefinitionPerFile: AnalyzerRule = {
11
+ slug: 'one-remote-definition-per-file',
12
+ analyze(project: DomainProject): readonly LintDiagnostic[] {
13
+ const diagnostics: LintDiagnostic[] = []
14
+ for (const file of project.files) {
15
+ if (!isProductionSource(file.relativePath)) continue
16
+ const definitions = importedCalls(file, sdkModules, remoteDefinitions)
17
+ for (const definition of definitions.slice(1)) {
18
+ const diagnostic = analyzerDiagnostic(
19
+ this.slug,
20
+ file,
21
+ definition,
22
+ `Found ${definitions.length} definitions.`,
23
+ )
24
+ if (diagnostic) diagnostics.push(diagnostic)
25
+ }
26
+ }
27
+ return diagnostics
28
+ },
29
+ }