@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.
- package/dist/cli/run.d.ts +8 -2
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +51 -1
- package/dist/cli/run.js.map +1 -1
- package/dist/define/remote-function.d.ts +3 -1
- package/dist/define/remote-function.d.ts.map +1 -1
- package/dist/define/remote-function.js.map +1 -1
- package/dist/dispatch/dispatcher.d.ts +10 -20
- package/dist/dispatch/dispatcher.d.ts.map +1 -1
- package/dist/dispatch/dispatcher.js +55 -54
- package/dist/dispatch/dispatcher.js.map +1 -1
- package/dist/dispatch/execute.d.ts +2 -1
- package/dist/dispatch/execute.d.ts.map +1 -1
- package/dist/dispatch/execute.js +1 -0
- package/dist/dispatch/execute.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/linter/analyze.d.ts +6 -0
- package/dist/linter/analyze.d.ts.map +1 -0
- package/dist/linter/analyze.js +12 -0
- package/dist/linter/analyze.js.map +1 -0
- package/dist/linter/diagnostic.d.ts +32 -0
- package/dist/linter/diagnostic.d.ts.map +1 -0
- package/dist/linter/diagnostic.js +35 -0
- package/dist/linter/diagnostic.js.map +1 -0
- package/dist/linter/index.d.ts +10 -0
- package/dist/linter/index.d.ts.map +1 -0
- package/dist/linter/index.js +6 -0
- package/dist/linter/index.js.map +1 -0
- package/dist/linter/lint.d.ts +8 -0
- package/dist/linter/lint.d.ts.map +1 -0
- package/dist/linter/lint.js +26 -0
- package/dist/linter/lint.js.map +1 -0
- package/dist/linter/oxlint/config.d.ts +14 -0
- package/dist/linter/oxlint/config.d.ts.map +1 -0
- package/dist/linter/oxlint/config.js +18 -0
- package/dist/linter/oxlint/config.js.map +1 -0
- package/dist/linter/oxlint/plugin.d.ts +13 -0
- package/dist/linter/oxlint/plugin.d.ts.map +1 -0
- package/dist/linter/oxlint/plugin.js +14 -0
- package/dist/linter/oxlint/plugin.js.map +1 -0
- package/dist/linter/oxlint/run.d.ts +7 -0
- package/dist/linter/oxlint/run.d.ts.map +1 -0
- package/dist/linter/oxlint/run.js +122 -0
- package/dist/linter/oxlint/run.js.map +1 -0
- package/dist/linter/preflight.d.ts +2 -0
- package/dist/linter/preflight.d.ts.map +1 -0
- package/dist/linter/preflight.js +162 -0
- package/dist/linter/preflight.js.map +1 -0
- package/dist/linter/project.d.ts +12 -0
- package/dist/linter/project.d.ts.map +1 -0
- package/dist/linter/project.js +88 -0
- package/dist/linter/project.js.map +1 -0
- package/dist/linter/report.d.ts +4 -0
- package/dist/linter/report.d.ts.map +1 -0
- package/dist/linter/report.js +22 -0
- package/dist/linter/report.js.map +1 -0
- package/dist/linter/rules/catalog.d.ts +56 -0
- package/dist/linter/rules/catalog.d.ts.map +1 -0
- package/dist/linter/rules/catalog.js +53 -0
- package/dist/linter/rules/catalog.js.map +1 -0
- package/dist/linter/rules/core-has-no-async.d.ts +3 -0
- package/dist/linter/rules/core-has-no-async.d.ts.map +1 -0
- package/dist/linter/rules/core-has-no-async.js +43 -0
- package/dist/linter/rules/core-has-no-async.js.map +1 -0
- package/dist/linter/rules/core-is-pure.d.ts +3 -0
- package/dist/linter/rules/core-is-pure.d.ts.map +1 -0
- package/dist/linter/rules/core-is-pure.js +74 -0
- package/dist/linter/rules/core-is-pure.js.map +1 -0
- package/dist/linter/rules/no-nested-steps.d.ts +3 -0
- package/dist/linter/rules/no-nested-steps.d.ts.map +1 -0
- package/dist/linter/rules/no-nested-steps.js +32 -0
- package/dist/linter/rules/no-nested-steps.js.map +1 -0
- package/dist/linter/rules/no-read-inside-mutate.d.ts +3 -0
- package/dist/linter/rules/no-read-inside-mutate.d.ts.map +1 -0
- package/dist/linter/rules/no-read-inside-mutate.js +64 -0
- package/dist/linter/rules/no-read-inside-mutate.js.map +1 -0
- package/dist/linter/rules/one-remote-definition-per-file.d.ts +3 -0
- package/dist/linter/rules/one-remote-definition-per-file.d.ts.map +1 -0
- package/dist/linter/rules/one-remote-definition-per-file.js +23 -0
- package/dist/linter/rules/one-remote-definition-per-file.js.map +1 -0
- package/dist/linter/rules/oxlint-ast.d.ts +30 -0
- package/dist/linter/rules/oxlint-ast.d.ts.map +1 -0
- package/dist/linter/rules/oxlint-ast.js +104 -0
- package/dist/linter/rules/oxlint-ast.js.map +1 -0
- package/dist/linter/rules/oxlint-rule.d.ts +45 -0
- package/dist/linter/rules/oxlint-rule.d.ts.map +1 -0
- package/dist/linter/rules/oxlint-rule.js +2 -0
- package/dist/linter/rules/oxlint-rule.js.map +1 -0
- package/dist/linter/rules/project-rule.d.ts +10 -0
- package/dist/linter/rules/project-rule.d.ts.map +1 -0
- package/dist/linter/rules/project-rule.js +24 -0
- package/dist/linter/rules/project-rule.js.map +1 -0
- package/dist/linter/rules/thin-schema-composition-root.d.ts +3 -0
- package/dist/linter/rules/thin-schema-composition-root.d.ts.map +1 -0
- package/dist/linter/rules/thin-schema-composition-root.js +23 -0
- package/dist/linter/rules/thin-schema-composition-root.js.map +1 -0
- package/dist/linter/source.d.ts +24 -0
- package/dist/linter/source.d.ts.map +1 -0
- package/dist/linter/source.js +279 -0
- package/dist/linter/source.js.map +1 -0
- package/dist/linter/suppression.d.ts +3 -0
- package/dist/linter/suppression.d.ts.map +1 -0
- package/dist/linter/suppression.js +11 -0
- package/dist/linter/suppression.js.map +1 -0
- package/dist/method/context.d.ts +3 -1
- package/dist/method/context.d.ts.map +1 -1
- package/dist/server/auxiliary-routes.d.ts.map +1 -1
- package/dist/server/auxiliary-routes.js +4 -4
- package/dist/server/auxiliary-routes.js.map +1 -1
- package/dist/server/create.js +1 -1
- package/dist/server/create.js.map +1 -1
- package/dist/server/start.d.ts.map +1 -1
- package/dist/server/start.js.map +1 -1
- package/package.json +27 -7
- package/src/cli/run.ts +53 -2
- package/src/define/remote-function.ts +3 -1
- package/src/dispatch/dispatcher.ts +62 -69
- package/src/dispatch/execute.ts +3 -1
- package/src/index.ts +2 -2
- package/src/linter/analyze.ts +17 -0
- package/src/linter/diagnostic.ts +68 -0
- package/src/linter/docs/DIAGNOSTICS.md +47 -0
- package/src/linter/docs/JUDGMENT.md +81 -0
- package/src/linter/docs/PRINCIPLES.md +97 -0
- package/src/linter/docs/README.md +38 -0
- package/src/linter/docs/RULE-AUTHORING.md +144 -0
- package/src/linter/docs/RULES.md +153 -0
- package/src/linter/index.ts +14 -0
- package/src/linter/lint.ts +32 -0
- package/src/linter/oxlint/astrale-ox.d.ts +5 -0
- package/src/linter/oxlint/config.ts +19 -0
- package/src/linter/oxlint/plugin.js +16 -0
- package/src/linter/oxlint/run.ts +162 -0
- package/src/linter/preflight.ts +181 -0
- package/src/linter/project.ts +108 -0
- package/src/linter/report.ts +25 -0
- package/src/linter/rules/catalog.ts +67 -0
- package/src/linter/rules/core-has-no-async.js +43 -0
- package/src/linter/rules/core-is-pure.ts +103 -0
- package/src/linter/rules/no-nested-steps.js +37 -0
- package/src/linter/rules/no-read-inside-mutate.js +72 -0
- package/src/linter/rules/one-remote-definition-per-file.ts +29 -0
- package/src/linter/rules/oxlint-ast.js +110 -0
- package/src/linter/rules/oxlint-rule.ts +35 -0
- package/src/linter/rules/project-rule.ts +37 -0
- package/src/linter/rules/thin-schema-composition-root.ts +26 -0
- package/src/linter/source.ts +323 -0
- package/src/linter/suppression.ts +11 -0
- package/src/method/context.ts +3 -1
- package/src/server/auxiliary-routes.ts +9 -4
- package/src/server/create.ts +1 -1
- package/src/server/start.ts +2 -1
- package/dist/defer/index.d.ts +0 -24
- package/dist/defer/index.d.ts.map +0 -1
- package/dist/defer/index.js +0 -42
- package/dist/defer/index.js.map +0 -1
- package/src/defer/index.ts +0 -64
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
export type SourceToken = {
|
|
2
|
+
kind: 'identifier' | 'string' | 'punctuation'
|
|
3
|
+
value: string
|
|
4
|
+
offset: number
|
|
5
|
+
length: number
|
|
6
|
+
line: number
|
|
7
|
+
column: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type SourceImport = {
|
|
11
|
+
specifier: string
|
|
12
|
+
token: SourceToken
|
|
13
|
+
bindings: ReadonlyMap<string, string>
|
|
14
|
+
namespace?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type SourceFile = {
|
|
18
|
+
path: string
|
|
19
|
+
relativePath: string
|
|
20
|
+
text: string
|
|
21
|
+
tokens: readonly SourceToken[]
|
|
22
|
+
imports: readonly SourceImport[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const identifierStart = /[A-Za-z_$]/
|
|
26
|
+
const identifierPart = /[A-Za-z0-9_$]/
|
|
27
|
+
|
|
28
|
+
export function scanSource(path: string, relativePath: string, text: string): SourceFile {
|
|
29
|
+
const tokens = tokenize(text)
|
|
30
|
+
return { path, relativePath, text, tokens, imports: parseImports(tokens) }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function tokenize(text: string): SourceToken[] {
|
|
34
|
+
const tokens: SourceToken[] = []
|
|
35
|
+
let offset = 0
|
|
36
|
+
let line = 1
|
|
37
|
+
let column = 1
|
|
38
|
+
|
|
39
|
+
const advance = (): string => {
|
|
40
|
+
const char = text[offset++]!
|
|
41
|
+
if (char === '\n') {
|
|
42
|
+
line++
|
|
43
|
+
column = 1
|
|
44
|
+
} else {
|
|
45
|
+
column++
|
|
46
|
+
}
|
|
47
|
+
return char
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
while (offset < text.length) {
|
|
51
|
+
const char = text[offset]!
|
|
52
|
+
if (/\s/.test(char)) {
|
|
53
|
+
advance()
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (char === '/' && text[offset + 1] === '/') {
|
|
58
|
+
advance()
|
|
59
|
+
advance()
|
|
60
|
+
while (offset < text.length && text[offset] !== '\n') advance()
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (char === '/' && text[offset + 1] === '*') {
|
|
65
|
+
advance()
|
|
66
|
+
advance()
|
|
67
|
+
while (offset < text.length) {
|
|
68
|
+
if (text[offset] === '*' && text[offset + 1] === '/') {
|
|
69
|
+
advance()
|
|
70
|
+
advance()
|
|
71
|
+
break
|
|
72
|
+
}
|
|
73
|
+
advance()
|
|
74
|
+
}
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (char === "'" || char === '"') {
|
|
79
|
+
const quote = char
|
|
80
|
+
const start = offset
|
|
81
|
+
const startLine = line
|
|
82
|
+
const startColumn = column
|
|
83
|
+
advance()
|
|
84
|
+
let value = ''
|
|
85
|
+
while (offset < text.length) {
|
|
86
|
+
const current = advance()
|
|
87
|
+
if (current === '\\') {
|
|
88
|
+
if (offset < text.length) value += decodeEscape(advance())
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
if (current === quote) break
|
|
92
|
+
value += current
|
|
93
|
+
}
|
|
94
|
+
tokens.push({
|
|
95
|
+
kind: 'string',
|
|
96
|
+
value,
|
|
97
|
+
offset: start,
|
|
98
|
+
length: offset - start,
|
|
99
|
+
line: startLine,
|
|
100
|
+
column: startColumn,
|
|
101
|
+
})
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (char === '`') {
|
|
106
|
+
advance()
|
|
107
|
+
while (offset < text.length) {
|
|
108
|
+
const current = advance()
|
|
109
|
+
if (current === '\\') {
|
|
110
|
+
if (offset < text.length) advance()
|
|
111
|
+
continue
|
|
112
|
+
}
|
|
113
|
+
if (current === '`') break
|
|
114
|
+
}
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (identifierStart.test(char)) {
|
|
119
|
+
const start = offset
|
|
120
|
+
const startLine = line
|
|
121
|
+
const startColumn = column
|
|
122
|
+
let value = advance()
|
|
123
|
+
while (offset < text.length && identifierPart.test(text[offset]!)) value += advance()
|
|
124
|
+
tokens.push({
|
|
125
|
+
kind: 'identifier',
|
|
126
|
+
value,
|
|
127
|
+
offset: start,
|
|
128
|
+
length: offset - start,
|
|
129
|
+
line: startLine,
|
|
130
|
+
column: startColumn,
|
|
131
|
+
})
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
tokens.push({
|
|
136
|
+
kind: 'punctuation',
|
|
137
|
+
value: char,
|
|
138
|
+
offset,
|
|
139
|
+
length: 1,
|
|
140
|
+
line,
|
|
141
|
+
column,
|
|
142
|
+
})
|
|
143
|
+
advance()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return tokens
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function decodeEscape(char: string): string {
|
|
150
|
+
switch (char) {
|
|
151
|
+
case 'n':
|
|
152
|
+
return '\n'
|
|
153
|
+
case 'r':
|
|
154
|
+
return '\r'
|
|
155
|
+
case 't':
|
|
156
|
+
return '\t'
|
|
157
|
+
default:
|
|
158
|
+
return char
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function parseImports(tokens: readonly SourceToken[]): SourceImport[] {
|
|
163
|
+
const imports: SourceImport[] = []
|
|
164
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
165
|
+
const token = tokens[index]!
|
|
166
|
+
if (token.kind !== 'identifier' || (token.value !== 'import' && token.value !== 'export')) {
|
|
167
|
+
continue
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (token.value === 'import' && tokens[index + 1]?.value === '(') {
|
|
171
|
+
const specifier = tokens[index + 2]
|
|
172
|
+
if (specifier?.kind === 'string') {
|
|
173
|
+
imports.push({ specifier: specifier.value, token, bindings: new Map() })
|
|
174
|
+
}
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const end = statementEnd(tokens, index + 1)
|
|
179
|
+
let specifierIndex = -1
|
|
180
|
+
for (let cursor = index + 1; cursor < end; cursor++) {
|
|
181
|
+
const candidate = tokens[cursor]!
|
|
182
|
+
if (candidate.kind === 'string') specifierIndex = cursor
|
|
183
|
+
}
|
|
184
|
+
if (specifierIndex < 0) continue
|
|
185
|
+
|
|
186
|
+
const specifier = tokens[specifierIndex]!
|
|
187
|
+
const bindings = new Map<string, string>()
|
|
188
|
+
let namespace: string | undefined
|
|
189
|
+
if (token.value === 'import') {
|
|
190
|
+
const fromIndex = findToken(tokens, index + 1, specifierIndex, 'from')
|
|
191
|
+
const bindingsEnd = fromIndex >= 0 ? fromIndex : specifierIndex
|
|
192
|
+
const open = findToken(tokens, index + 1, bindingsEnd, '{')
|
|
193
|
+
if (open >= 0) {
|
|
194
|
+
const close = findMatching(tokens, open, '{', '}', bindingsEnd)
|
|
195
|
+
if (close >= 0) parseNamedBindings(tokens, open + 1, close, bindings)
|
|
196
|
+
}
|
|
197
|
+
const star = findToken(tokens, index + 1, bindingsEnd, '*')
|
|
198
|
+
if (star >= 0 && tokens[star + 1]?.value === 'as') namespace = tokens[star + 2]?.value
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
imports.push({
|
|
202
|
+
specifier: specifier.value,
|
|
203
|
+
token,
|
|
204
|
+
bindings,
|
|
205
|
+
...(namespace ? { namespace } : {}),
|
|
206
|
+
})
|
|
207
|
+
index = Math.max(index, specifierIndex)
|
|
208
|
+
}
|
|
209
|
+
return imports
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function statementEnd(tokens: readonly SourceToken[], start: number): number {
|
|
213
|
+
let depth = 0
|
|
214
|
+
let sawSpecifier = false
|
|
215
|
+
for (let index = start; index < tokens.length; index++) {
|
|
216
|
+
const token = tokens[index]!
|
|
217
|
+
if (token.value === ';' && depth === 0) return index
|
|
218
|
+
if (token.value === '{' || token.value === '(' || token.value === '[') depth++
|
|
219
|
+
if (token.value === '}' || token.value === ')' || token.value === ']') depth--
|
|
220
|
+
if (token.kind === 'string' && depth === 0) sawSpecifier = true
|
|
221
|
+
const next = tokens[index + 1]
|
|
222
|
+
if (sawSpecifier && depth === 0 && next && next.line > token.line) return index + 1
|
|
223
|
+
}
|
|
224
|
+
return tokens.length
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function findToken(
|
|
228
|
+
tokens: readonly SourceToken[],
|
|
229
|
+
start: number,
|
|
230
|
+
end: number,
|
|
231
|
+
value: string,
|
|
232
|
+
): number {
|
|
233
|
+
for (let index = start; index < end; index++) {
|
|
234
|
+
if (tokens[index]?.value === value) return index
|
|
235
|
+
}
|
|
236
|
+
return -1
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function findMatching(
|
|
240
|
+
tokens: readonly SourceToken[],
|
|
241
|
+
start: number,
|
|
242
|
+
open: string,
|
|
243
|
+
close: string,
|
|
244
|
+
end: number,
|
|
245
|
+
): number {
|
|
246
|
+
let depth = 0
|
|
247
|
+
for (let index = start; index < end; index++) {
|
|
248
|
+
if (tokens[index]?.value === open) depth++
|
|
249
|
+
if (tokens[index]?.value === close && --depth === 0) return index
|
|
250
|
+
}
|
|
251
|
+
return -1
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function parseNamedBindings(
|
|
255
|
+
tokens: readonly SourceToken[],
|
|
256
|
+
start: number,
|
|
257
|
+
end: number,
|
|
258
|
+
bindings: Map<string, string>,
|
|
259
|
+
): void {
|
|
260
|
+
let index = start
|
|
261
|
+
while (index < end) {
|
|
262
|
+
if (tokens[index]?.value === ',' || tokens[index]?.value === 'type') {
|
|
263
|
+
index++
|
|
264
|
+
continue
|
|
265
|
+
}
|
|
266
|
+
const imported = tokens[index]
|
|
267
|
+
if (!imported || imported.kind !== 'identifier') {
|
|
268
|
+
index++
|
|
269
|
+
continue
|
|
270
|
+
}
|
|
271
|
+
if (tokens[index + 1]?.value === 'as' && tokens[index + 2]?.kind === 'identifier') {
|
|
272
|
+
bindings.set(imported.value, tokens[index + 2]!.value)
|
|
273
|
+
index += 3
|
|
274
|
+
continue
|
|
275
|
+
}
|
|
276
|
+
bindings.set(imported.value, imported.value)
|
|
277
|
+
index++
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function importedCalls(
|
|
282
|
+
file: SourceFile,
|
|
283
|
+
modules: ReadonlySet<string>,
|
|
284
|
+
names: ReadonlySet<string>,
|
|
285
|
+
): SourceToken[] {
|
|
286
|
+
const locals = new Set<string>()
|
|
287
|
+
const namespaces = new Set<string>()
|
|
288
|
+
for (const sourceImport of file.imports) {
|
|
289
|
+
if (!modules.has(sourceImport.specifier)) continue
|
|
290
|
+
for (const name of names) {
|
|
291
|
+
const local = sourceImport.bindings.get(name)
|
|
292
|
+
if (local) locals.add(local)
|
|
293
|
+
}
|
|
294
|
+
if (sourceImport.namespace) namespaces.add(sourceImport.namespace)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const calls: SourceToken[] = []
|
|
298
|
+
for (let index = 0; index < file.tokens.length; index++) {
|
|
299
|
+
const token = file.tokens[index]!
|
|
300
|
+
if (token.kind !== 'identifier') continue
|
|
301
|
+
if (locals.has(token.value) && callFollows(file.tokens, index)) {
|
|
302
|
+
calls.push(token)
|
|
303
|
+
continue
|
|
304
|
+
}
|
|
305
|
+
if (
|
|
306
|
+
namespaces.has(token.value) &&
|
|
307
|
+
file.tokens[index + 1]?.value === '.' &&
|
|
308
|
+
names.has(file.tokens[index + 2]?.value ?? '') &&
|
|
309
|
+
callFollows(file.tokens, index + 2)
|
|
310
|
+
) {
|
|
311
|
+
calls.push(file.tokens[index + 2]!)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return calls
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function callFollows(tokens: readonly SourceToken[], index: number): boolean {
|
|
318
|
+
const next = tokens[index + 1]
|
|
319
|
+
if (next?.value === '(') return true
|
|
320
|
+
if (next?.value !== '<') return false
|
|
321
|
+
const close = findMatching(tokens, index + 1, '<', '>', tokens.length)
|
|
322
|
+
return close >= 0 && tokens[close + 1]?.value === '('
|
|
323
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SourceFile } from './source.js'
|
|
2
|
+
|
|
3
|
+
const suppression = /^\s*\/\/\s*astrale-disable-next-line\s+(.+?)\s+--\s+\S.*$/
|
|
4
|
+
|
|
5
|
+
export function isSuppressed(file: SourceFile, line: number, id: string): boolean {
|
|
6
|
+
if (line <= 1) return false
|
|
7
|
+
const previous = file.text.split(/\r?\n/)[line - 2]
|
|
8
|
+
const match = previous ? suppression.exec(previous) : null
|
|
9
|
+
if (!match) return false
|
|
10
|
+
return match[1]!.split(/[\s,]+/).includes(id)
|
|
11
|
+
}
|
package/src/method/context.ts
CHANGED
|
@@ -13,10 +13,10 @@ import type { FnMap, SchemaBoundView } from '@astrale-os/kernel-client'
|
|
|
13
13
|
import type { BoundClientSessionView } from '@astrale-os/kernel-client/session'
|
|
14
14
|
import type { AuthContext } from '@astrale-os/kernel-core'
|
|
15
15
|
import type { Schema } from '@astrale-os/kernel-dsl'
|
|
16
|
+
import type { Defer, Sleep } from '@astrale-os/kernel-server'
|
|
16
17
|
|
|
17
18
|
import type { FunctionContextApi } from '../auth/function-context.js'
|
|
18
19
|
import type { DomainAuthority } from '../auth/issuer-mint.js'
|
|
19
|
-
import type { Defer } from '../defer/index.js'
|
|
20
20
|
import type { Step } from '../step/index.js'
|
|
21
21
|
|
|
22
22
|
export type { GraphApi } from '@astrale-os/kernel-client/graph'
|
|
@@ -130,4 +130,6 @@ export interface RemoteContext<
|
|
|
130
130
|
step: Step
|
|
131
131
|
/** Run independent work without making its completion part of this result. */
|
|
132
132
|
defer: Defer
|
|
133
|
+
/** Pause execution for a relative duration. Durable hosts may persist the wait. */
|
|
134
|
+
sleep: Sleep
|
|
133
135
|
}
|
|
@@ -36,7 +36,12 @@ import {
|
|
|
36
36
|
type FunctionBinding,
|
|
37
37
|
type AuthPolicy,
|
|
38
38
|
} from '@astrale-os/kernel-api/routed'
|
|
39
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
buildCorsHeaders,
|
|
41
|
+
createCapabilityLifecycle,
|
|
42
|
+
honoCapabilityHost,
|
|
43
|
+
type CorsConfig,
|
|
44
|
+
} from '@astrale-os/kernel-server'
|
|
40
45
|
|
|
41
46
|
import type { RemoteIdentityConfig } from '../auth/identity.js'
|
|
42
47
|
import type { AnyRemoteFunctionDef } from '../define/remote-function.js'
|
|
@@ -46,7 +51,6 @@ import type { AuxIdentityMap } from '../dispatch/identity.js'
|
|
|
46
51
|
import { makeFunctionContext } from '../auth/function-context.js'
|
|
47
52
|
import { makeDomainAuthority } from '../auth/issuer-mint.js'
|
|
48
53
|
import { resolveInboundAuth } from '../auth/resolve.js'
|
|
49
|
-
import { createHandlerLifecycle, executionLifetime } from '../defer/index.js'
|
|
50
54
|
import { runAuthorize } from '../dispatch/authorize.js'
|
|
51
55
|
import { SdkResultValidationError, SdkValidationError } from '../dispatch/errors.js'
|
|
52
56
|
import { validateParams, validateResult } from '../dispatch/validate.js'
|
|
@@ -172,7 +176,7 @@ export function mountAuxiliaryRoutes<TDeps>(config: AuxiliaryRoutesConfig<TDeps>
|
|
|
172
176
|
}),
|
|
173
177
|
}
|
|
174
178
|
if (def.authorize) await runAuthorize(def.authorize, ctx)
|
|
175
|
-
const lifecycle =
|
|
179
|
+
const lifecycle = createCapabilityLifecycle(honoCapabilityHost(c))
|
|
176
180
|
const result = await (async () => {
|
|
177
181
|
try {
|
|
178
182
|
return await def.execute({
|
|
@@ -180,7 +184,8 @@ export function mountAuxiliaryRoutes<TDeps>(config: AuxiliaryRoutesConfig<TDeps>
|
|
|
180
184
|
step: createInlineStep({
|
|
181
185
|
scope: { kind: 'function', ref: `function.${slug}`, slug },
|
|
182
186
|
}),
|
|
183
|
-
defer: lifecycle.defer,
|
|
187
|
+
defer: lifecycle.capabilities.defer,
|
|
188
|
+
sleep: lifecycle.capabilities.sleep,
|
|
184
189
|
})
|
|
185
190
|
} finally {
|
|
186
191
|
await lifecycle.settle()
|
package/src/server/create.ts
CHANGED
|
@@ -143,7 +143,7 @@ export function createRemoteServer<TDeps>(config: RemoteServerConfig<TDeps>): Re
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
const { app } = createKernelApp({
|
|
146
|
-
|
|
146
|
+
dispatcher,
|
|
147
147
|
domain: config.domain.methods.map(toSdkContract),
|
|
148
148
|
host: { url: config.url },
|
|
149
149
|
jwks,
|
package/src/server/start.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Cloudflare, consumers go through `server.app.fetch` directly.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import type { serve as nodeServe } from '@hono/node-server'
|
|
9
10
|
import type { Hono } from 'hono'
|
|
10
11
|
|
|
11
12
|
import type { RemoteServerHandle } from './handle.js'
|
|
@@ -15,7 +16,7 @@ export async function startNodeServer(app: Hono, port = 3000): Promise<RemoteSer
|
|
|
15
16
|
// bundles (wrangler/esbuild) traverse it even though they never call it. A
|
|
16
17
|
// literal import() would make them resolve the optional peer and fail.
|
|
17
18
|
const nodeServerModule = '@hono/node-server'
|
|
18
|
-
const { serve } = (await import(nodeServerModule)) as typeof
|
|
19
|
+
const { serve } = (await import(nodeServerModule)) as { serve: typeof nodeServe }
|
|
19
20
|
// oxlint-disable-next-line no-explicit-any
|
|
20
21
|
const server = serve({ fetch: app.fetch, port }) as any
|
|
21
22
|
|
package/dist/defer/index.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { Context } from 'hono';
|
|
2
|
-
/** Internal structural host capability; not part of the author-facing SDK contract. */
|
|
3
|
-
export type HandlerExecutionLifetime = {
|
|
4
|
-
waitUntil(promise: Promise<unknown>): void;
|
|
5
|
-
};
|
|
6
|
-
/**
|
|
7
|
-
* Work detached from the current handler result.
|
|
8
|
-
*
|
|
9
|
-
* The callback is deliberately lazy: the SDK registers the resulting task with
|
|
10
|
-
* the active execution backend before it starts. Current request-based hosts
|
|
11
|
-
* extend their request lifetime; future durable backends may provide stronger
|
|
12
|
-
* execution guarantees without changing the author-facing API.
|
|
13
|
-
*/
|
|
14
|
-
export type DeferredTask = () => void | PromiseLike<unknown>;
|
|
15
|
-
export type Defer = (task: DeferredTask) => void;
|
|
16
|
-
export type HandlerLifecycle = {
|
|
17
|
-
defer: Defer;
|
|
18
|
-
settle(): Promise<void>;
|
|
19
|
-
};
|
|
20
|
-
/** Bind detached work to the host, with a safe blocking fallback for other runtimes. */
|
|
21
|
-
export declare function createHandlerLifecycle(lifetime?: HandlerExecutionLifetime): HandlerLifecycle;
|
|
22
|
-
/** Hono omits its execution context in Node and in lightweight unit tests. */
|
|
23
|
-
export declare function executionLifetime(c: Context): HandlerExecutionLifetime | undefined;
|
|
24
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/defer/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,uFAAuF;AACvF,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;CAC3C,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAA;AAE5D,MAAM,MAAM,KAAK,GAAG,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAA;AAEhD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,KAAK,CAAA;IACZ,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACxB,CAAA;AAED,wFAAwF;AACxF,wBAAgB,sBAAsB,CAAC,QAAQ,CAAC,EAAE,wBAAwB,GAAG,gBAAgB,CA4B5F;AAED,8EAA8E;AAC9E,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,GAAG,wBAAwB,GAAG,SAAS,CAOlF"}
|
package/dist/defer/index.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/** Bind detached work to the host, with a safe blocking fallback for other runtimes. */
|
|
2
|
-
export function createHandlerLifecycle(lifetime) {
|
|
3
|
-
const pending = [];
|
|
4
|
-
return {
|
|
5
|
-
defer(task) {
|
|
6
|
-
const promise = Promise.resolve().then(task);
|
|
7
|
-
if (lifetime)
|
|
8
|
-
lifetime.waitUntil(promise);
|
|
9
|
-
else
|
|
10
|
-
pending.push(promise);
|
|
11
|
-
},
|
|
12
|
-
async settle() {
|
|
13
|
-
if (lifetime || pending.length === 0)
|
|
14
|
-
return;
|
|
15
|
-
// A deferred task may register more work before it settles. Drain until
|
|
16
|
-
// the lifecycle is stable so non-request runtimes never abandon children.
|
|
17
|
-
let cursor = 0;
|
|
18
|
-
while (cursor < pending.length) {
|
|
19
|
-
const batch = pending.slice(cursor);
|
|
20
|
-
cursor = pending.length;
|
|
21
|
-
const results = await Promise.allSettled(batch);
|
|
22
|
-
for (const result of results) {
|
|
23
|
-
if (result.status === 'rejected') {
|
|
24
|
-
// oxlint-disable-next-line no-console
|
|
25
|
-
console.error('[sdk] deferred task failed:', result.reason);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
/** Hono omits its execution context in Node and in lightweight unit tests. */
|
|
33
|
-
export function executionLifetime(c) {
|
|
34
|
-
try {
|
|
35
|
-
const host = c.executionCtx;
|
|
36
|
-
return { waitUntil: (promise) => host.waitUntil(promise) };
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return undefined;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
//# sourceMappingURL=index.js.map
|
package/dist/defer/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/defer/index.ts"],"names":[],"mappings":"AAwBA,wFAAwF;AACxF,MAAM,UAAU,sBAAsB,CAAC,QAAmC;IACxE,MAAM,OAAO,GAAuB,EAAE,CAAA;IAEtC,OAAO;QACL,KAAK,CAAC,IAAI;YACR,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAC5C,IAAI,QAAQ;gBAAE,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;;gBACpC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAC5B,CAAC;QACD,KAAK,CAAC,MAAM;YACV,IAAI,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAM;YAE5C,wEAAwE;YACxE,0EAA0E;YAC1E,IAAI,MAAM,GAAG,CAAC,CAAA;YACd,OAAO,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;gBAC/B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;gBACnC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;gBACvB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;gBAC/C,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;oBAC7B,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;wBACjC,sCAAsC;wBACtC,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;oBAC7D,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,iBAAiB,CAAC,CAAU;IAC1C,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,CAAC,CAAC,YAAY,CAAA;QAC3B,OAAO,EAAE,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAA;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC"}
|
package/src/defer/index.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import type { Context } from 'hono'
|
|
2
|
-
|
|
3
|
-
/** Internal structural host capability; not part of the author-facing SDK contract. */
|
|
4
|
-
export type HandlerExecutionLifetime = {
|
|
5
|
-
waitUntil(promise: Promise<unknown>): void
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Work detached from the current handler result.
|
|
10
|
-
*
|
|
11
|
-
* The callback is deliberately lazy: the SDK registers the resulting task with
|
|
12
|
-
* the active execution backend before it starts. Current request-based hosts
|
|
13
|
-
* extend their request lifetime; future durable backends may provide stronger
|
|
14
|
-
* execution guarantees without changing the author-facing API.
|
|
15
|
-
*/
|
|
16
|
-
export type DeferredTask = () => void | PromiseLike<unknown>
|
|
17
|
-
|
|
18
|
-
export type Defer = (task: DeferredTask) => void
|
|
19
|
-
|
|
20
|
-
export type HandlerLifecycle = {
|
|
21
|
-
defer: Defer
|
|
22
|
-
settle(): Promise<void>
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Bind detached work to the host, with a safe blocking fallback for other runtimes. */
|
|
26
|
-
export function createHandlerLifecycle(lifetime?: HandlerExecutionLifetime): HandlerLifecycle {
|
|
27
|
-
const pending: Promise<unknown>[] = []
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
defer(task) {
|
|
31
|
-
const promise = Promise.resolve().then(task)
|
|
32
|
-
if (lifetime) lifetime.waitUntil(promise)
|
|
33
|
-
else pending.push(promise)
|
|
34
|
-
},
|
|
35
|
-
async settle() {
|
|
36
|
-
if (lifetime || pending.length === 0) return
|
|
37
|
-
|
|
38
|
-
// A deferred task may register more work before it settles. Drain until
|
|
39
|
-
// the lifecycle is stable so non-request runtimes never abandon children.
|
|
40
|
-
let cursor = 0
|
|
41
|
-
while (cursor < pending.length) {
|
|
42
|
-
const batch = pending.slice(cursor)
|
|
43
|
-
cursor = pending.length
|
|
44
|
-
const results = await Promise.allSettled(batch)
|
|
45
|
-
for (const result of results) {
|
|
46
|
-
if (result.status === 'rejected') {
|
|
47
|
-
// oxlint-disable-next-line no-console
|
|
48
|
-
console.error('[sdk] deferred task failed:', result.reason)
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Hono omits its execution context in Node and in lightweight unit tests. */
|
|
57
|
-
export function executionLifetime(c: Context): HandlerExecutionLifetime | undefined {
|
|
58
|
-
try {
|
|
59
|
-
const host = c.executionCtx
|
|
60
|
-
return { waitUntil: (promise) => host.waitUntil(promise) }
|
|
61
|
-
} catch {
|
|
62
|
-
return undefined
|
|
63
|
-
}
|
|
64
|
-
}
|