@astrale-os/sdk 0.4.12 → 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 (158) hide show
  1. package/dist/cli/run.d.ts +8 -2
  2. package/dist/cli/run.d.ts.map +1 -1
  3. package/dist/cli/run.js +51 -1
  4. package/dist/cli/run.js.map +1 -1
  5. package/dist/define/remote-function.d.ts +3 -1
  6. package/dist/define/remote-function.d.ts.map +1 -1
  7. package/dist/define/remote-function.js.map +1 -1
  8. package/dist/dispatch/dispatcher.d.ts +10 -20
  9. package/dist/dispatch/dispatcher.d.ts.map +1 -1
  10. package/dist/dispatch/dispatcher.js +55 -54
  11. package/dist/dispatch/dispatcher.js.map +1 -1
  12. package/dist/dispatch/execute.d.ts +2 -1
  13. package/dist/dispatch/execute.d.ts.map +1 -1
  14. package/dist/dispatch/execute.js +1 -0
  15. package/dist/dispatch/execute.js.map +1 -1
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/linter/analyze.d.ts +6 -0
  19. package/dist/linter/analyze.d.ts.map +1 -0
  20. package/dist/linter/analyze.js +12 -0
  21. package/dist/linter/analyze.js.map +1 -0
  22. package/dist/linter/diagnostic.d.ts +32 -0
  23. package/dist/linter/diagnostic.d.ts.map +1 -0
  24. package/dist/linter/diagnostic.js +35 -0
  25. package/dist/linter/diagnostic.js.map +1 -0
  26. package/dist/linter/index.d.ts +10 -0
  27. package/dist/linter/index.d.ts.map +1 -0
  28. package/dist/linter/index.js +6 -0
  29. package/dist/linter/index.js.map +1 -0
  30. package/dist/linter/lint.d.ts +8 -0
  31. package/dist/linter/lint.d.ts.map +1 -0
  32. package/dist/linter/lint.js +26 -0
  33. package/dist/linter/lint.js.map +1 -0
  34. package/dist/linter/oxlint/config.d.ts +14 -0
  35. package/dist/linter/oxlint/config.d.ts.map +1 -0
  36. package/dist/linter/oxlint/config.js +18 -0
  37. package/dist/linter/oxlint/config.js.map +1 -0
  38. package/dist/linter/oxlint/plugin.d.ts +13 -0
  39. package/dist/linter/oxlint/plugin.d.ts.map +1 -0
  40. package/dist/linter/oxlint/plugin.js +14 -0
  41. package/dist/linter/oxlint/plugin.js.map +1 -0
  42. package/dist/linter/oxlint/run.d.ts +7 -0
  43. package/dist/linter/oxlint/run.d.ts.map +1 -0
  44. package/dist/linter/oxlint/run.js +122 -0
  45. package/dist/linter/oxlint/run.js.map +1 -0
  46. package/dist/linter/preflight.d.ts +2 -0
  47. package/dist/linter/preflight.d.ts.map +1 -0
  48. package/dist/linter/preflight.js +162 -0
  49. package/dist/linter/preflight.js.map +1 -0
  50. package/dist/linter/project.d.ts +12 -0
  51. package/dist/linter/project.d.ts.map +1 -0
  52. package/dist/linter/project.js +88 -0
  53. package/dist/linter/project.js.map +1 -0
  54. package/dist/linter/report.d.ts +4 -0
  55. package/dist/linter/report.d.ts.map +1 -0
  56. package/dist/linter/report.js +22 -0
  57. package/dist/linter/report.js.map +1 -0
  58. package/dist/linter/rules/catalog.d.ts +56 -0
  59. package/dist/linter/rules/catalog.d.ts.map +1 -0
  60. package/dist/linter/rules/catalog.js +53 -0
  61. package/dist/linter/rules/catalog.js.map +1 -0
  62. package/dist/linter/rules/core-has-no-async.d.ts +3 -0
  63. package/dist/linter/rules/core-has-no-async.d.ts.map +1 -0
  64. package/dist/linter/rules/core-has-no-async.js +43 -0
  65. package/dist/linter/rules/core-has-no-async.js.map +1 -0
  66. package/dist/linter/rules/core-is-pure.d.ts +3 -0
  67. package/dist/linter/rules/core-is-pure.d.ts.map +1 -0
  68. package/dist/linter/rules/core-is-pure.js +74 -0
  69. package/dist/linter/rules/core-is-pure.js.map +1 -0
  70. package/dist/linter/rules/no-nested-steps.d.ts +3 -0
  71. package/dist/linter/rules/no-nested-steps.d.ts.map +1 -0
  72. package/dist/linter/rules/no-nested-steps.js +32 -0
  73. package/dist/linter/rules/no-nested-steps.js.map +1 -0
  74. package/dist/linter/rules/no-read-inside-mutate.d.ts +3 -0
  75. package/dist/linter/rules/no-read-inside-mutate.d.ts.map +1 -0
  76. package/dist/linter/rules/no-read-inside-mutate.js +64 -0
  77. package/dist/linter/rules/no-read-inside-mutate.js.map +1 -0
  78. package/dist/linter/rules/one-remote-definition-per-file.d.ts +3 -0
  79. package/dist/linter/rules/one-remote-definition-per-file.d.ts.map +1 -0
  80. package/dist/linter/rules/one-remote-definition-per-file.js +23 -0
  81. package/dist/linter/rules/one-remote-definition-per-file.js.map +1 -0
  82. package/dist/linter/rules/oxlint-ast.d.ts +30 -0
  83. package/dist/linter/rules/oxlint-ast.d.ts.map +1 -0
  84. package/dist/linter/rules/oxlint-ast.js +104 -0
  85. package/dist/linter/rules/oxlint-ast.js.map +1 -0
  86. package/dist/linter/rules/oxlint-rule.d.ts +45 -0
  87. package/dist/linter/rules/oxlint-rule.d.ts.map +1 -0
  88. package/dist/linter/rules/oxlint-rule.js +2 -0
  89. package/dist/linter/rules/oxlint-rule.js.map +1 -0
  90. package/dist/linter/rules/project-rule.d.ts +10 -0
  91. package/dist/linter/rules/project-rule.d.ts.map +1 -0
  92. package/dist/linter/rules/project-rule.js +24 -0
  93. package/dist/linter/rules/project-rule.js.map +1 -0
  94. package/dist/linter/rules/thin-schema-composition-root.d.ts +3 -0
  95. package/dist/linter/rules/thin-schema-composition-root.d.ts.map +1 -0
  96. package/dist/linter/rules/thin-schema-composition-root.js +23 -0
  97. package/dist/linter/rules/thin-schema-composition-root.js.map +1 -0
  98. package/dist/linter/source.d.ts +24 -0
  99. package/dist/linter/source.d.ts.map +1 -0
  100. package/dist/linter/source.js +279 -0
  101. package/dist/linter/source.js.map +1 -0
  102. package/dist/linter/suppression.d.ts +3 -0
  103. package/dist/linter/suppression.d.ts.map +1 -0
  104. package/dist/linter/suppression.js +11 -0
  105. package/dist/linter/suppression.js.map +1 -0
  106. package/dist/method/context.d.ts +3 -1
  107. package/dist/method/context.d.ts.map +1 -1
  108. package/dist/server/auxiliary-routes.d.ts.map +1 -1
  109. package/dist/server/auxiliary-routes.js +4 -4
  110. package/dist/server/auxiliary-routes.js.map +1 -1
  111. package/dist/server/create.js +1 -1
  112. package/dist/server/create.js.map +1 -1
  113. package/dist/server/start.d.ts.map +1 -1
  114. package/dist/server/start.js.map +1 -1
  115. package/package.json +27 -7
  116. package/src/cli/run.ts +53 -2
  117. package/src/define/remote-function.ts +3 -1
  118. package/src/dispatch/dispatcher.ts +62 -69
  119. package/src/dispatch/execute.ts +3 -1
  120. package/src/index.ts +2 -2
  121. package/src/linter/analyze.ts +17 -0
  122. package/src/linter/diagnostic.ts +68 -0
  123. package/src/linter/docs/DIAGNOSTICS.md +47 -0
  124. package/src/linter/docs/JUDGMENT.md +81 -0
  125. package/src/linter/docs/PRINCIPLES.md +97 -0
  126. package/src/linter/docs/README.md +38 -0
  127. package/src/linter/docs/RULE-AUTHORING.md +144 -0
  128. package/src/linter/docs/RULES.md +153 -0
  129. package/src/linter/index.ts +14 -0
  130. package/src/linter/lint.ts +32 -0
  131. package/src/linter/oxlint/astrale-ox.d.ts +5 -0
  132. package/src/linter/oxlint/config.ts +19 -0
  133. package/src/linter/oxlint/plugin.js +16 -0
  134. package/src/linter/oxlint/run.ts +162 -0
  135. package/src/linter/preflight.ts +181 -0
  136. package/src/linter/project.ts +108 -0
  137. package/src/linter/report.ts +25 -0
  138. package/src/linter/rules/catalog.ts +67 -0
  139. package/src/linter/rules/core-has-no-async.js +43 -0
  140. package/src/linter/rules/core-is-pure.ts +103 -0
  141. package/src/linter/rules/no-nested-steps.js +37 -0
  142. package/src/linter/rules/no-read-inside-mutate.js +72 -0
  143. package/src/linter/rules/one-remote-definition-per-file.ts +29 -0
  144. package/src/linter/rules/oxlint-ast.js +110 -0
  145. package/src/linter/rules/oxlint-rule.ts +35 -0
  146. package/src/linter/rules/project-rule.ts +37 -0
  147. package/src/linter/rules/thin-schema-composition-root.ts +26 -0
  148. package/src/linter/source.ts +323 -0
  149. package/src/linter/suppression.ts +11 -0
  150. package/src/method/context.ts +3 -1
  151. package/src/server/auxiliary-routes.ts +9 -4
  152. package/src/server/create.ts +1 -1
  153. package/src/server/start.ts +2 -1
  154. package/dist/defer/index.d.ts +0 -24
  155. package/dist/defer/index.d.ts.map +0 -1
  156. package/dist/defer/index.js +0 -42
  157. package/dist/defer/index.js.map +0 -1
  158. package/src/defer/index.ts +0 -64
@@ -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
+ }
@@ -0,0 +1,110 @@
1
+ // @ts-check
2
+
3
+ import { relative, sep } from 'node:path'
4
+
5
+ /** @typedef {import('./oxlint-rule.js').AstNode} AstNode */
6
+ /** @typedef {import('./oxlint-rule.js').OxlintRuleContext} OxlintRuleContext */
7
+ /** @typedef {{ object: AstNode, property: string }} StaticMember */
8
+
9
+ /** @param {AstNode | undefined} node @returns {StaticMember | undefined} */
10
+ export function staticMember(node) {
11
+ if (!node || node.type !== 'MemberExpression' || node.computed) return undefined
12
+ if (!node.object || node.property?.type !== 'Identifier' || !node.property.name) return undefined
13
+ return { object: node.object, property: node.property.name }
14
+ }
15
+
16
+ /** @param {AstNode | undefined} node */
17
+ export function isFunctionNode(node) {
18
+ return (
19
+ node?.type === 'ArrowFunctionExpression' ||
20
+ node?.type === 'FunctionDeclaration' ||
21
+ node?.type === 'FunctionExpression'
22
+ )
23
+ }
24
+
25
+ /** @param {AstNode | undefined} node @returns {AstNode | undefined} */
26
+ export function nearestFunction(node) {
27
+ let current = node
28
+ while (current) {
29
+ if (isFunctionNode(current)) return current
30
+ current = current.parent
31
+ }
32
+ return undefined
33
+ }
34
+
35
+ /** @param {AstNode} callback @returns {AstNode | undefined} */
36
+ export function callbackCall(callback) {
37
+ let current = callback
38
+ while (current.parent && isTransparentWrapper(current.parent)) current = current.parent
39
+ const parent = current.parent
40
+ if (parent?.type !== 'CallExpression' || !parent.arguments?.includes(current)) return undefined
41
+ return parent
42
+ }
43
+
44
+ /** @param {AstNode} node */
45
+ function isTransparentWrapper(node) {
46
+ return (
47
+ node.type === 'ChainExpression' ||
48
+ node.type === 'ParenthesizedExpression' ||
49
+ node.type === 'TSAsExpression' ||
50
+ node.type === 'TSNonNullExpression' ||
51
+ node.type === 'TSSatisfiesExpression'
52
+ )
53
+ }
54
+
55
+ /** @param {AstNode | undefined} call */
56
+ export function isStepRun(call) {
57
+ if (call?.type !== 'CallExpression') return false
58
+ const member = staticMember(call.callee)
59
+ if (!member || member.property !== 'run') return false
60
+ if (member.object.type === 'Identifier') return member.object.name === 'step'
61
+ return staticMember(member.object)?.property === 'step'
62
+ }
63
+
64
+ /** @param {AstNode | undefined} call */
65
+ export function isMutateCall(call) {
66
+ if (call?.type !== 'CallExpression') return false
67
+ const member = staticMember(call.callee)
68
+ if (!member || member.property !== 'mutate') return false
69
+ if (member.object.type === 'Identifier') return member.object.name === 'kernel'
70
+ return staticMember(member.object)?.property === 'kernel'
71
+ }
72
+
73
+ /** @param {OxlintRuleContext} context */
74
+ export function relativeFilename(context) {
75
+ const path = relative(context.cwd, context.filename)
76
+ return sep === '/' ? path : path.split(sep).join('/')
77
+ }
78
+
79
+ /** @param {OxlintRuleContext} context */
80
+ export function isProductionFile(context) {
81
+ const path = relativeFilename(context)
82
+ return (
83
+ !path.split('/').includes('__tests__') &&
84
+ !/\.(?:test|spec|bench|perf)\.[cm]?[jt]sx?$/.test(path) &&
85
+ !path.includes('.gen.')
86
+ )
87
+ }
88
+
89
+ /** @param {OxlintRuleContext} context */
90
+ export function isProductionCoreFile(context) {
91
+ const path = relativeFilename(context)
92
+ return (path === 'core' || path.startsWith('core/')) && isProductionFile(context)
93
+ }
94
+
95
+ /** @param {OxlintRuleContext} context @param {AstNode} node @param {string} id @param {string} messageId */
96
+ export function reportUnlessSuppressed(context, node, id, messageId) {
97
+ if (isSuppressed(context, node, id)) return
98
+ context.report({ node, messageId })
99
+ }
100
+
101
+ /** @param {OxlintRuleContext} context @param {AstNode} node @param {string} id */
102
+ function isSuppressed(context, node, id) {
103
+ const line = context.sourceCode.getLoc(node).start.line
104
+ if (line <= 1) return false
105
+ const previous = context.sourceCode.text.split(/\r?\n/)[line - 2]
106
+ const match = previous
107
+ ? /^\s*\/\/\s*astrale-disable-next-line\s+(.+?)\s+--\s+\S.*$/.exec(previous)
108
+ : null
109
+ return match ? match[1].split(/[\s,]+/).includes(id) : false
110
+ }
@@ -0,0 +1,35 @@
1
+ export type AstNode = {
2
+ type: string
3
+ parent?: AstNode
4
+ range?: [number, number]
5
+ async?: boolean
6
+ await?: boolean
7
+ name?: string
8
+ computed?: boolean
9
+ object?: AstNode
10
+ property?: AstNode
11
+ callee?: AstNode
12
+ arguments?: AstNode[]
13
+ }
14
+
15
+ export type OxlintRuleContext = {
16
+ id: string
17
+ filename: string
18
+ cwd: string
19
+ sourceCode: {
20
+ text: string
21
+ getText(node?: AstNode | null): string
22
+ getLoc(node: AstNode): { start: { line: number; column: number } }
23
+ }
24
+ report(diagnostic: { node: AstNode; messageId: string }): void
25
+ }
26
+
27
+ export type OxlintRule = {
28
+ meta: {
29
+ type: 'problem'
30
+ docs: { description: string; url: string }
31
+ messages: Record<string, string>
32
+ schema: readonly []
33
+ }
34
+ create(context: OxlintRuleContext): Record<string, ((node: AstNode) => void) | undefined>
35
+ }
@@ -0,0 +1,37 @@
1
+ import type { LintDiagnostic } from '../diagnostic.js'
2
+ import type { DomainProject } from '../project.js'
3
+ import type { SourceFile, SourceToken } from '../source.js'
4
+
5
+ import { isSuppressed } from '../suppression.js'
6
+ import { ruleCatalog, ruleId, type ImplementedRuleSlug } from './catalog.js'
7
+
8
+ export type AnalyzerRule = {
9
+ slug: ImplementedRuleSlug
10
+ analyze(project: DomainProject): readonly LintDiagnostic[]
11
+ }
12
+
13
+ export function analyzerDiagnostic(
14
+ slug: ImplementedRuleSlug,
15
+ file: SourceFile,
16
+ token: SourceToken,
17
+ detail?: string,
18
+ ): LintDiagnostic | undefined {
19
+ const info = ruleCatalog[slug]
20
+ const id = ruleId(slug)
21
+ if (isSuppressed(file, token.line, id)) return undefined
22
+ return {
23
+ id,
24
+ severity: 'error',
25
+ message: detail ? `${info.message} ${detail}` : info.message,
26
+ help: info.help,
27
+ url: info.url,
28
+ owner: info.owner,
29
+ location: {
30
+ path: file.relativePath,
31
+ line: token.line,
32
+ column: token.column,
33
+ offset: token.offset,
34
+ length: token.length,
35
+ },
36
+ }
37
+ }
@@ -0,0 +1,26 @@
1
+ import type { LintDiagnostic } from '../diagnostic.js'
2
+ import type { DomainProject } from '../project.js'
3
+
4
+ import { importedCalls } from '../source.js'
5
+ import { analyzerDiagnostic, type AnalyzerRule } from './project-rule.js'
6
+
7
+ const schemaModules = new Set(['@astrale-os/kernel-core', '@astrale-os/kernel-dsl'])
8
+ const memberConstructors = new Set([
9
+ 'edgeClass',
10
+ 'edgeInterface',
11
+ 'fn',
12
+ 'func',
13
+ 'nodeClass',
14
+ 'nodeInterface',
15
+ ])
16
+
17
+ export const thinSchemaCompositionRoot: AnalyzerRule = {
18
+ slug: 'thin-schema-composition-root',
19
+ analyze(project: DomainProject): readonly LintDiagnostic[] {
20
+ const root = project.files.find((file) => file.relativePath === 'schema/index.ts')
21
+ if (!root) return []
22
+ return importedCalls(root, schemaModules, memberConstructors)
23
+ .map((token) => analyzerDiagnostic(this.slug, root, token, `Found ${token.value}().`))
24
+ .filter((diagnostic): diagnostic is LintDiagnostic => diagnostic !== undefined)
25
+ },
26
+ }