@dawn-ai/core 0.0.2
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/README.md +11 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +225 -0
- package/dist/discovery/discover-routes.d.ts +3 -0
- package/dist/discovery/discover-routes.d.ts.map +1 -0
- package/dist/discovery/discover-routes.js +114 -0
- package/dist/discovery/find-dawn-app.d.ts +4 -0
- package/dist/discovery/find-dawn-app.d.ts.map +1 -0
- package/dist/discovery/find-dawn-app.js +58 -0
- package/dist/discovery/load-authoring-route-definition.d.ts +9 -0
- package/dist/discovery/load-authoring-route-definition.d.ts.map +1 -0
- package/dist/discovery/load-authoring-route-definition.js +57 -0
- package/dist/discovery/route-segments.d.ts +5 -0
- package/dist/discovery/route-segments.d.ts.map +1 -0
- package/dist/discovery/route-segments.js +36 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/typegen/extract-tool-types.d.ts +7 -0
- package/dist/typegen/extract-tool-types.d.ts.map +1 -0
- package/dist/typegen/extract-tool-types.js +93 -0
- package/dist/typegen/render-route-types.d.ts +4 -0
- package/dist/typegen/render-route-types.d.ts.map +1 -0
- package/dist/typegen/render-route-types.js +70 -0
- package/dist/typegen/render-tool-types.d.ts +3 -0
- package/dist/typegen/render-tool-types.d.ts.map +1 -0
- package/dist/typegen/render-tool-types.js +19 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @dawn-ai/core
|
|
2
|
+
|
|
3
|
+
Shared Dawn app discovery, config loading, route validation, and route type generation primitives.
|
|
4
|
+
|
|
5
|
+
Public surface:
|
|
6
|
+
- `loadDawnConfig()`
|
|
7
|
+
- `findDawnApp()`
|
|
8
|
+
- `discoverRoutes()`
|
|
9
|
+
- `renderRouteTypes()`
|
|
10
|
+
|
|
11
|
+
This package owns Dawn's filesystem and metadata conventions. It does not provide a graph runtime.
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { LoadDawnConfigOptions, LoadedDawnConfig } from "./types.js";
|
|
2
|
+
export declare const DAWN_CONFIG_FILE = "dawn.config.ts";
|
|
3
|
+
export declare function loadDawnConfig(options: LoadDawnConfigOptions): Promise<LoadedDawnConfig>;
|
|
4
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAc,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAErF,eAAO,MAAM,gBAAgB,mBAAmB,CAAA;AAsBhD,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAY9F"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access, readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
export const DAWN_CONFIG_FILE = "dawn.config.ts";
|
|
5
|
+
export async function loadDawnConfig(options) {
|
|
6
|
+
const configPath = join(options.appRoot, DAWN_CONFIG_FILE);
|
|
7
|
+
await access(configPath, constants.F_OK);
|
|
8
|
+
const source = await readFile(configPath, "utf8");
|
|
9
|
+
return {
|
|
10
|
+
appRoot: options.appRoot,
|
|
11
|
+
config: parseDawnConfig(source),
|
|
12
|
+
configPath,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function parseDawnConfig(source) {
|
|
16
|
+
const parser = new DawnConfigParser(source);
|
|
17
|
+
return parser.parse();
|
|
18
|
+
}
|
|
19
|
+
class DawnConfigParser {
|
|
20
|
+
tokens;
|
|
21
|
+
currentIndex = 0;
|
|
22
|
+
stringBindings = new Map();
|
|
23
|
+
constructor(source) {
|
|
24
|
+
this.tokens = tokenize(source);
|
|
25
|
+
}
|
|
26
|
+
parse() {
|
|
27
|
+
while (this.match("const")) {
|
|
28
|
+
this.parseConstDeclaration();
|
|
29
|
+
this.consumeOptional("semicolon");
|
|
30
|
+
}
|
|
31
|
+
this.consume("export");
|
|
32
|
+
this.consume("default");
|
|
33
|
+
const config = this.parseConfigObject();
|
|
34
|
+
this.consumeOptional("semicolon");
|
|
35
|
+
this.consume("eof");
|
|
36
|
+
return config;
|
|
37
|
+
}
|
|
38
|
+
parseConstDeclaration() {
|
|
39
|
+
const identifier = this.consume("identifier");
|
|
40
|
+
this.consume("equals");
|
|
41
|
+
const value = this.consume("string");
|
|
42
|
+
this.stringBindings.set(identifier.value, value.value);
|
|
43
|
+
}
|
|
44
|
+
parseConfigObject() {
|
|
45
|
+
this.consume("lbrace");
|
|
46
|
+
let appDir;
|
|
47
|
+
while (!this.check("rbrace")) {
|
|
48
|
+
const property = this.consume("identifier");
|
|
49
|
+
if (property.value !== "appDir") {
|
|
50
|
+
throw unsupportedConfig(`unsupported property "${property.value}"`);
|
|
51
|
+
}
|
|
52
|
+
const resolvedValue = this.match("colon")
|
|
53
|
+
? this.parsePropertyValue()
|
|
54
|
+
: this.resolveIdentifier(property.value);
|
|
55
|
+
appDir = resolvedValue;
|
|
56
|
+
if (!this.match("comma")) {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
this.consume("rbrace");
|
|
61
|
+
return appDir ? { appDir } : {};
|
|
62
|
+
}
|
|
63
|
+
parsePropertyValue() {
|
|
64
|
+
if (this.check("string")) {
|
|
65
|
+
return this.consume("string").value;
|
|
66
|
+
}
|
|
67
|
+
if (this.check("identifier")) {
|
|
68
|
+
return this.resolveIdentifier(this.consume("identifier").value);
|
|
69
|
+
}
|
|
70
|
+
throw unsupportedConfig("property values must be string literals or const identifiers");
|
|
71
|
+
}
|
|
72
|
+
resolveIdentifier(identifier) {
|
|
73
|
+
const resolved = this.stringBindings.get(identifier);
|
|
74
|
+
if (!resolved) {
|
|
75
|
+
throw unsupportedConfig(`unknown identifier "${identifier}"`);
|
|
76
|
+
}
|
|
77
|
+
return resolved;
|
|
78
|
+
}
|
|
79
|
+
match(type) {
|
|
80
|
+
if (!this.check(type)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
this.currentIndex += 1;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
consume(type) {
|
|
87
|
+
const token = this.peek();
|
|
88
|
+
if (token.type !== type) {
|
|
89
|
+
throw unsupportedConfig(`expected ${type} but found ${describeToken(token)}`);
|
|
90
|
+
}
|
|
91
|
+
this.currentIndex += 1;
|
|
92
|
+
return token;
|
|
93
|
+
}
|
|
94
|
+
consumeOptional(type) {
|
|
95
|
+
this.match(type);
|
|
96
|
+
}
|
|
97
|
+
check(type) {
|
|
98
|
+
return this.peek().type === type;
|
|
99
|
+
}
|
|
100
|
+
peek() {
|
|
101
|
+
return this.tokens[this.currentIndex] ?? { type: "eof" };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function tokenize(source) {
|
|
105
|
+
const tokens = [];
|
|
106
|
+
let index = source.startsWith("\uFEFF") ? 1 : 0;
|
|
107
|
+
while (index < source.length) {
|
|
108
|
+
const character = source[index];
|
|
109
|
+
if (!character) {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
if (isWhitespace(character)) {
|
|
113
|
+
index += 1;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (character === "/" && source[index + 1] === "/") {
|
|
117
|
+
index += 2;
|
|
118
|
+
while (index < source.length && source[index] !== "\n") {
|
|
119
|
+
index += 1;
|
|
120
|
+
}
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (character === "/" && source[index + 1] === "*") {
|
|
124
|
+
const commentEnd = source.indexOf("*/", index + 2);
|
|
125
|
+
if (commentEnd === -1) {
|
|
126
|
+
throw unsupportedConfig("unterminated block comment");
|
|
127
|
+
}
|
|
128
|
+
index = commentEnd + 2;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (character === "{") {
|
|
132
|
+
tokens.push({ type: "lbrace" });
|
|
133
|
+
index += 1;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (character === "}") {
|
|
137
|
+
tokens.push({ type: "rbrace" });
|
|
138
|
+
index += 1;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (character === ":") {
|
|
142
|
+
tokens.push({ type: "colon" });
|
|
143
|
+
index += 1;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (character === ",") {
|
|
147
|
+
tokens.push({ type: "comma" });
|
|
148
|
+
index += 1;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (character === "=") {
|
|
152
|
+
tokens.push({ type: "equals" });
|
|
153
|
+
index += 1;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (character === ";") {
|
|
157
|
+
tokens.push({ type: "semicolon" });
|
|
158
|
+
index += 1;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (character === '"' || character === "'") {
|
|
162
|
+
const [value, nextIndex] = readStringLiteral(source, index, character);
|
|
163
|
+
tokens.push({ type: "string", value });
|
|
164
|
+
index = nextIndex;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (isIdentifierStart(character)) {
|
|
168
|
+
const [identifier, nextIndex] = readIdentifier(source, index);
|
|
169
|
+
index = nextIndex;
|
|
170
|
+
if (identifier === "const" || identifier === "export" || identifier === "default") {
|
|
171
|
+
tokens.push({ type: identifier });
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
tokens.push({ type: "identifier", value: identifier });
|
|
175
|
+
}
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
throw unsupportedConfig(`unexpected token "${character}"`);
|
|
179
|
+
}
|
|
180
|
+
tokens.push({ type: "eof" });
|
|
181
|
+
return tokens;
|
|
182
|
+
}
|
|
183
|
+
function readStringLiteral(source, startIndex, quote) {
|
|
184
|
+
let index = startIndex + 1;
|
|
185
|
+
let value = "";
|
|
186
|
+
while (index < source.length) {
|
|
187
|
+
const character = source[index];
|
|
188
|
+
if (!character) {
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
if (character === "\\") {
|
|
192
|
+
throw unsupportedConfig("escaped string literals are not supported");
|
|
193
|
+
}
|
|
194
|
+
if (character === quote) {
|
|
195
|
+
return [value, index + 1];
|
|
196
|
+
}
|
|
197
|
+
value += character;
|
|
198
|
+
index += 1;
|
|
199
|
+
}
|
|
200
|
+
throw unsupportedConfig("unterminated string literal");
|
|
201
|
+
}
|
|
202
|
+
function readIdentifier(source, startIndex) {
|
|
203
|
+
let index = startIndex + 1;
|
|
204
|
+
while (index < source.length && isIdentifierPart(source[index] ?? "")) {
|
|
205
|
+
index += 1;
|
|
206
|
+
}
|
|
207
|
+
return [source.slice(startIndex, index), index];
|
|
208
|
+
}
|
|
209
|
+
function isIdentifierStart(character) {
|
|
210
|
+
return /[A-Za-z_$]/.test(character);
|
|
211
|
+
}
|
|
212
|
+
function isIdentifierPart(character) {
|
|
213
|
+
return /[A-Za-z0-9_$]/.test(character);
|
|
214
|
+
}
|
|
215
|
+
function isWhitespace(character) {
|
|
216
|
+
return /\s/.test(character);
|
|
217
|
+
}
|
|
218
|
+
function describeToken(token) {
|
|
219
|
+
return token.type === "identifier" || token.type === "string"
|
|
220
|
+
? `${token.type} "${token.value}"`
|
|
221
|
+
: token.type;
|
|
222
|
+
}
|
|
223
|
+
function unsupportedConfig(reason) {
|
|
224
|
+
return new Error(`Unsupported dawn.config.ts syntax: ${reason}. Supported subset: optional const string declarations followed by export default { appDir } or export default { appDir: "..." }.`);
|
|
225
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover-routes.d.ts","sourceRoot":"","sources":["../../src/discovery/discover-routes.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAA;AAQxF,wBAAsB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,aAAa,CAAC,CAQhG"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { findDawnApp } from "./find-dawn-app.js";
|
|
5
|
+
import { isPrivateSegment, isRouteGroupSegment, toRouteSegments } from "./route-segments.js";
|
|
6
|
+
const INDEX_FILE = "index.ts";
|
|
7
|
+
let loaderPromise;
|
|
8
|
+
export async function discoverRoutes(options = {}) {
|
|
9
|
+
const app = await findDawnApp(options);
|
|
10
|
+
const routes = validateRouteCollisions(await collectRouteDefinitions(app.routesDir));
|
|
11
|
+
return {
|
|
12
|
+
appRoot: app.appRoot,
|
|
13
|
+
routes: routes.sort((left, right) => left.pathname.localeCompare(right.pathname)),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
async function collectRouteDefinitions(routesDir) {
|
|
17
|
+
const discovered = [];
|
|
18
|
+
await walkRouteTree(routesDir, routesDir, discovered);
|
|
19
|
+
return discovered;
|
|
20
|
+
}
|
|
21
|
+
async function walkRouteTree(routesDir, currentDir, discovered) {
|
|
22
|
+
const routeEntry = await readRouteEntry(routesDir, currentDir);
|
|
23
|
+
if (routeEntry) {
|
|
24
|
+
discovered.push(routeEntry);
|
|
25
|
+
}
|
|
26
|
+
const entries = (await readdir(currentDir, { withFileTypes: true })).sort((left, right) => left.name.localeCompare(right.name));
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
if (!entry.isDirectory() || isPrivateSegment(entry.name)) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
await walkRouteTree(routesDir, join(currentDir, entry.name), discovered);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function readRouteEntry(routesDir, routeDir) {
|
|
35
|
+
const entries = await readdir(routeDir, { withFileTypes: true }).catch(() => null);
|
|
36
|
+
if (!entries) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const hasIndex = entries.some((entry) => entry.isFile() && entry.name === INDEX_FILE);
|
|
40
|
+
if (!hasIndex) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const indexFile = resolve(routeDir, INDEX_FILE);
|
|
44
|
+
const kind = await inferRouteKind(indexFile);
|
|
45
|
+
if (!kind) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const routeSegments = relative(routesDir, routeDir)
|
|
49
|
+
.split(sep)
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.filter((segment) => !isRouteGroupSegment(segment));
|
|
52
|
+
return {
|
|
53
|
+
id: toPathname(routeSegments),
|
|
54
|
+
pathname: toPathname(routeSegments),
|
|
55
|
+
kind,
|
|
56
|
+
entryFile: indexFile,
|
|
57
|
+
routeDir,
|
|
58
|
+
segments: toRouteSegments(routeSegments),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function inferRouteKind(indexFile) {
|
|
62
|
+
await registerTsxLoader();
|
|
63
|
+
const routeExports = await loadRouteExports(indexFile);
|
|
64
|
+
const hasChain = "chain" in routeExports && routeExports.chain !== undefined;
|
|
65
|
+
const hasGraph = "graph" in routeExports && routeExports.graph !== undefined;
|
|
66
|
+
const hasWorkflow = "workflow" in routeExports && routeExports.workflow !== undefined;
|
|
67
|
+
const count = [hasChain, hasGraph, hasWorkflow].filter(Boolean).length;
|
|
68
|
+
if (count > 1) {
|
|
69
|
+
throw new Error(`Route index.ts must export exactly one of "workflow", "graph", or "chain"`);
|
|
70
|
+
}
|
|
71
|
+
if (hasChain) {
|
|
72
|
+
return "chain";
|
|
73
|
+
}
|
|
74
|
+
if (hasGraph) {
|
|
75
|
+
return "graph";
|
|
76
|
+
}
|
|
77
|
+
if (hasWorkflow) {
|
|
78
|
+
return "workflow";
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
async function loadRouteExports(indexFile) {
|
|
83
|
+
try {
|
|
84
|
+
return (await import(pathToFileURL(indexFile).href));
|
|
85
|
+
}
|
|
86
|
+
catch (cause) {
|
|
87
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
88
|
+
throw new Error(`Failed to load route at ${indexFile}: ${reason}`, { cause });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function registerTsxLoader() {
|
|
92
|
+
loaderPromise ??= (async () => {
|
|
93
|
+
const { register } = (await import("tsx/esm/api"));
|
|
94
|
+
register();
|
|
95
|
+
})();
|
|
96
|
+
await loaderPromise;
|
|
97
|
+
}
|
|
98
|
+
function validateRouteCollisions(routes) {
|
|
99
|
+
const byPathname = new Map();
|
|
100
|
+
for (const route of routes) {
|
|
101
|
+
const existingRoute = byPathname.get(route.pathname);
|
|
102
|
+
if (existingRoute) {
|
|
103
|
+
throw new Error(`Duplicate Dawn route pathname "${route.pathname}" detected at ${existingRoute.routeDir} and ${route.routeDir}`);
|
|
104
|
+
}
|
|
105
|
+
byPathname.set(route.pathname, route);
|
|
106
|
+
}
|
|
107
|
+
return [...routes];
|
|
108
|
+
}
|
|
109
|
+
function toPathname(routeSegments) {
|
|
110
|
+
if (routeSegments.length === 0) {
|
|
111
|
+
return "/";
|
|
112
|
+
}
|
|
113
|
+
return `/${routeSegments.join("/")}`;
|
|
114
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DiscoveredDawnApp, FindDawnAppOptions } from "../types.js";
|
|
2
|
+
export declare function findDawnApp(options?: FindDawnAppOptions): Promise<DiscoveredDawnApp>;
|
|
3
|
+
export declare function assertDawnRoutesDir(appRoot: string, routesDir?: string): Promise<void>;
|
|
4
|
+
//# sourceMappingURL=find-dawn-app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-dawn-app.d.ts","sourceRoot":"","sources":["../../src/discovery/find-dawn-app.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAMxE,wBAAsB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgB9F;AAiCD,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,SAAS,SAAiC,GACzC,OAAO,CAAC,IAAI,CAAC,CAMf"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { DAWN_CONFIG_FILE, loadDawnConfig } from "../config.js";
|
|
5
|
+
const PACKAGE_JSON_FILE = "package.json";
|
|
6
|
+
const DEFAULT_APP_DIR = "src/app";
|
|
7
|
+
const DAWN_DIR = ".dawn";
|
|
8
|
+
export async function findDawnApp(options = {}) {
|
|
9
|
+
const appRoot = options.appRoot ? resolve(options.appRoot) : await findAppRootFromCwd(options.cwd);
|
|
10
|
+
await assertDawnAppFiles(appRoot);
|
|
11
|
+
const loadedConfig = await loadDawnConfig({ appRoot });
|
|
12
|
+
const routesDir = resolve(appRoot, loadedConfig.config.appDir ?? DEFAULT_APP_DIR);
|
|
13
|
+
await assertDawnRoutesDir(appRoot, routesDir);
|
|
14
|
+
const dawnDir = join(appRoot, DAWN_DIR);
|
|
15
|
+
return {
|
|
16
|
+
appRoot,
|
|
17
|
+
configPath: loadedConfig.configPath,
|
|
18
|
+
dawnDir,
|
|
19
|
+
routesDir,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async function findAppRootFromCwd(cwd = process.cwd()) {
|
|
23
|
+
let currentDir = resolve(cwd);
|
|
24
|
+
while (true) {
|
|
25
|
+
if ((await fileExists(join(currentDir, DAWN_CONFIG_FILE))) &&
|
|
26
|
+
(await fileExists(join(currentDir, PACKAGE_JSON_FILE)))) {
|
|
27
|
+
return currentDir;
|
|
28
|
+
}
|
|
29
|
+
const parentDir = dirname(currentDir);
|
|
30
|
+
if (parentDir === currentDir) {
|
|
31
|
+
throw new Error(`Could not find ${DAWN_CONFIG_FILE} from ${cwd}`);
|
|
32
|
+
}
|
|
33
|
+
currentDir = parentDir;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function assertDawnAppFiles(appRoot) {
|
|
37
|
+
const missingPaths = await Promise.all([join(appRoot, PACKAGE_JSON_FILE), join(appRoot, DAWN_CONFIG_FILE)].map(async (filePath) => (await fileExists(filePath)) ? null : filePath));
|
|
38
|
+
throwIfMissing(appRoot, missingPaths);
|
|
39
|
+
}
|
|
40
|
+
export async function assertDawnRoutesDir(appRoot, routesDir = join(appRoot, DEFAULT_APP_DIR)) {
|
|
41
|
+
const missingPaths = await Promise.all([routesDir].map(async (filePath) => ((await fileExists(filePath)) ? null : filePath)));
|
|
42
|
+
throwIfMissing(appRoot, missingPaths);
|
|
43
|
+
}
|
|
44
|
+
function throwIfMissing(appRoot, missingPaths) {
|
|
45
|
+
const missing = missingPaths.filter((value) => value !== null);
|
|
46
|
+
if (missing.length > 0) {
|
|
47
|
+
throw new Error(`Invalid Dawn app at ${appRoot}. Missing: ${missing.join(", ")}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function fileExists(filePath) {
|
|
51
|
+
try {
|
|
52
|
+
await access(filePath, constants.F_OK);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ResolvedAuthoringRouteDefinition {
|
|
2
|
+
readonly entry: "./graph.ts" | "./workflow.ts";
|
|
3
|
+
readonly executableFile: string;
|
|
4
|
+
readonly kind: "graph" | "workflow";
|
|
5
|
+
readonly routeDefinitionFile: string;
|
|
6
|
+
readonly routeDir: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function loadAuthoringRouteDefinition(routeDefinitionFile: string): Promise<ResolvedAuthoringRouteDefinition | null>;
|
|
9
|
+
//# sourceMappingURL=load-authoring-route-definition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-authoring-route-definition.d.ts","sourceRoot":"","sources":["../../src/discovery/load-authoring-route-definition.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,eAAe,CAAA;IAC9C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,CAAA;IACnC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAA;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,wBAAsB,4BAA4B,CAChD,mBAAmB,EAAE,MAAM,GAC1B,OAAO,CAAC,gCAAgC,GAAG,IAAI,CAAC,CAwDlD"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
let loaderPromise;
|
|
6
|
+
const TSX_MODULE = "tsx";
|
|
7
|
+
export async function loadAuthoringRouteDefinition(routeDefinitionFile) {
|
|
8
|
+
if (!(await fileExists(routeDefinitionFile))) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
await registerTsxLoader();
|
|
12
|
+
const routeModule = (await import(pathToFileURL(routeDefinitionFile).href));
|
|
13
|
+
const definition = routeModule.route ?? routeModule.default;
|
|
14
|
+
if (!isRecord(definition)) {
|
|
15
|
+
throw new Error(`Route definition ${routeDefinitionFile} must export a Dawn route definition`);
|
|
16
|
+
}
|
|
17
|
+
const routeDir = dirname(routeDefinitionFile);
|
|
18
|
+
const kind = definition.kind;
|
|
19
|
+
const entry = definition.entry;
|
|
20
|
+
if (kind !== "graph" && kind !== "workflow") {
|
|
21
|
+
throw new Error(`Route definition ${routeDefinitionFile} must define kind as "graph" or "workflow"`);
|
|
22
|
+
}
|
|
23
|
+
if (entry !== "./graph.ts" && entry !== "./workflow.ts") {
|
|
24
|
+
throw new Error(`Route definition ${routeDefinitionFile} kind "${kind}" must bind entry "./${kind}.ts", received ${JSON.stringify(entry)}`);
|
|
25
|
+
}
|
|
26
|
+
if ((kind === "graph" && entry !== "./graph.ts") ||
|
|
27
|
+
(kind === "workflow" && entry !== "./workflow.ts")) {
|
|
28
|
+
throw new Error(`Route definition ${routeDefinitionFile} kind "${kind}" must bind entry "./${kind}.ts", received ${JSON.stringify(entry)}`);
|
|
29
|
+
}
|
|
30
|
+
const executableFile = resolve(routeDir, entry.slice(2));
|
|
31
|
+
if (!(await fileExists(executableFile))) {
|
|
32
|
+
throw new Error(`Route definition ${routeDefinitionFile} binds to missing executable file: ${executableFile}`);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
entry,
|
|
36
|
+
executableFile,
|
|
37
|
+
kind,
|
|
38
|
+
routeDefinitionFile,
|
|
39
|
+
routeDir,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
async function registerTsxLoader() {
|
|
43
|
+
loaderPromise ??= import(TSX_MODULE).then(() => undefined);
|
|
44
|
+
await loaderPromise;
|
|
45
|
+
}
|
|
46
|
+
async function fileExists(filePath) {
|
|
47
|
+
try {
|
|
48
|
+
await access(filePath, constants.F_OK);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function isRecord(value) {
|
|
56
|
+
return typeof value === "object" && value !== null;
|
|
57
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RouteSegment } from "../types.js";
|
|
2
|
+
export declare function isRouteGroupSegment(segment: string): boolean;
|
|
3
|
+
export declare function isPrivateSegment(segment: string): boolean;
|
|
4
|
+
export declare function toRouteSegments(routeSegments: readonly string[]): RouteSegment[];
|
|
5
|
+
//# sourceMappingURL=route-segments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-segments.d.ts","sourceRoot":"","sources":["../../src/discovery/route-segments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wBAAgB,eAAe,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,GAAG,YAAY,EAAE,CAEhF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function isRouteGroupSegment(segment) {
|
|
2
|
+
return segment.startsWith("(") && segment.endsWith(")");
|
|
3
|
+
}
|
|
4
|
+
export function isPrivateSegment(segment) {
|
|
5
|
+
return segment.startsWith("_");
|
|
6
|
+
}
|
|
7
|
+
export function toRouteSegments(routeSegments) {
|
|
8
|
+
return routeSegments.map((segment) => parseRouteSegment(segment));
|
|
9
|
+
}
|
|
10
|
+
function parseRouteSegment(segment) {
|
|
11
|
+
if (segment.startsWith("[[...") && segment.endsWith("]]")) {
|
|
12
|
+
return {
|
|
13
|
+
kind: "optional-catchall",
|
|
14
|
+
name: segment.slice(5, -2),
|
|
15
|
+
raw: segment,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (segment.startsWith("[...") && segment.endsWith("]")) {
|
|
19
|
+
return {
|
|
20
|
+
kind: "catchall",
|
|
21
|
+
name: segment.slice(4, -1),
|
|
22
|
+
raw: segment,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
26
|
+
return {
|
|
27
|
+
kind: "dynamic",
|
|
28
|
+
name: segment.slice(1, -1),
|
|
29
|
+
raw: segment,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
kind: "static",
|
|
34
|
+
raw: segment,
|
|
35
|
+
};
|
|
36
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { loadDawnConfig } from "./config.js";
|
|
2
|
+
export { discoverRoutes } from "./discovery/discover-routes.js";
|
|
3
|
+
export { assertDawnRoutesDir, findDawnApp } from "./discovery/find-dawn-app.js";
|
|
4
|
+
export { isPrivateSegment, isRouteGroupSegment, toRouteSegments, } from "./discovery/route-segments.js";
|
|
5
|
+
export type { ExtractToolTypesOptions } from "./typegen/extract-tool-types.js";
|
|
6
|
+
export { extractToolTypesForRoute } from "./typegen/extract-tool-types.js";
|
|
7
|
+
export { renderDawnTypes, renderRouteTypes } from "./typegen/render-route-types.js";
|
|
8
|
+
export { renderToolTypes } from "./typegen/render-tool-types.js";
|
|
9
|
+
export type { DawnConfig, DiscoveredDawnApp, DiscoverRoutesOptions, ExtractedToolType, FindDawnAppOptions, LoadDawnConfigOptions, LoadedDawnConfig, RouteDefinition, RouteKind, RouteManifest, RouteSegment, RouteToolTypes, } from "./types.js";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAC/D,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC/E,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,GAChB,MAAM,+BAA+B,CAAA;AACtC,YAAY,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAA;AAC9E,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAA;AAC1E,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AACnF,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,aAAa,EACb,YAAY,EACZ,cAAc,GACf,MAAM,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { loadDawnConfig } from "./config.js";
|
|
2
|
+
export { discoverRoutes } from "./discovery/discover-routes.js";
|
|
3
|
+
export { assertDawnRoutesDir, findDawnApp } from "./discovery/find-dawn-app.js";
|
|
4
|
+
export { isPrivateSegment, isRouteGroupSegment, toRouteSegments, } from "./discovery/route-segments.js";
|
|
5
|
+
export { extractToolTypesForRoute } from "./typegen/extract-tool-types.js";
|
|
6
|
+
export { renderDawnTypes, renderRouteTypes } from "./typegen/render-route-types.js";
|
|
7
|
+
export { renderToolTypes } from "./typegen/render-tool-types.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ExtractedToolType } from "../types.js";
|
|
2
|
+
export interface ExtractToolTypesOptions {
|
|
3
|
+
readonly routeDir: string;
|
|
4
|
+
readonly sharedToolsDir: string | undefined;
|
|
5
|
+
}
|
|
6
|
+
export declare function extractToolTypesForRoute(options: ExtractToolTypesOptions): Promise<readonly ExtractedToolType[]>;
|
|
7
|
+
//# sourceMappingURL=extract-tool-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-tool-types.d.ts","sourceRoot":"","sources":["../../src/typegen/extract-tool-types.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEpD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAA;CAC5C;AAED,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAsEvC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
export async function extractToolTypesForRoute(options) {
|
|
5
|
+
const routeToolFiles = discoverToolFiles(join(options.routeDir, "tools"));
|
|
6
|
+
const sharedToolFiles = options.sharedToolsDir
|
|
7
|
+
? discoverToolFiles(join(options.sharedToolsDir, "tools"))
|
|
8
|
+
: new Map();
|
|
9
|
+
// Merge: route-local tools shadow shared tools
|
|
10
|
+
const merged = new Map(sharedToolFiles);
|
|
11
|
+
for (const [name, filePath] of routeToolFiles) {
|
|
12
|
+
merged.set(name, filePath);
|
|
13
|
+
}
|
|
14
|
+
if (merged.size === 0)
|
|
15
|
+
return [];
|
|
16
|
+
const allFilePaths = [...merged.values()];
|
|
17
|
+
const compilerOptions = {
|
|
18
|
+
target: ts.ScriptTarget.ES2022,
|
|
19
|
+
module: ts.ModuleKind.ESNext,
|
|
20
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
21
|
+
strict: true,
|
|
22
|
+
noEmit: true,
|
|
23
|
+
lib: ["lib.es2022.d.ts"],
|
|
24
|
+
};
|
|
25
|
+
const program = ts.createProgram(allFilePaths, compilerOptions);
|
|
26
|
+
const checker = program.getTypeChecker();
|
|
27
|
+
const results = [];
|
|
28
|
+
for (const [name, filePath] of merged) {
|
|
29
|
+
const sourceFile = program.getSourceFile(filePath);
|
|
30
|
+
if (!sourceFile)
|
|
31
|
+
continue;
|
|
32
|
+
const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
|
|
33
|
+
if (!moduleSymbol)
|
|
34
|
+
continue;
|
|
35
|
+
const exports = checker.getExportsOfModule(moduleSymbol);
|
|
36
|
+
const defaultExport = exports.find((e) => e.escapedName === "default");
|
|
37
|
+
if (!defaultExport)
|
|
38
|
+
continue;
|
|
39
|
+
const exportType = checker.getTypeOfSymbolAtLocation(defaultExport, sourceFile);
|
|
40
|
+
const signatures = checker.getSignaturesOfType(exportType, ts.SignatureKind.Call);
|
|
41
|
+
if (signatures.length === 0)
|
|
42
|
+
continue;
|
|
43
|
+
const signature = signatures[0];
|
|
44
|
+
if (!signature)
|
|
45
|
+
continue;
|
|
46
|
+
const params = signature.getParameters();
|
|
47
|
+
let inputType;
|
|
48
|
+
if (params.length === 0) {
|
|
49
|
+
inputType = "void";
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const firstParam = params[0];
|
|
53
|
+
if (!firstParam)
|
|
54
|
+
continue;
|
|
55
|
+
const paramType = checker.getTypeOfSymbolAtLocation(firstParam, sourceFile);
|
|
56
|
+
inputType = checker.typeToString(paramType);
|
|
57
|
+
}
|
|
58
|
+
const returnType = checker.getReturnTypeOfSignature(signature);
|
|
59
|
+
const outputType = unwrapPromise(returnType);
|
|
60
|
+
results.push({
|
|
61
|
+
name,
|
|
62
|
+
inputType,
|
|
63
|
+
outputType: checker.typeToString(outputType),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
results.sort((a, b) => a.name.localeCompare(b.name));
|
|
67
|
+
return results;
|
|
68
|
+
}
|
|
69
|
+
function unwrapPromise(type) {
|
|
70
|
+
const symbol = type.getSymbol();
|
|
71
|
+
if (symbol && symbol.getName() === "Promise") {
|
|
72
|
+
const typeArgs = type.typeArguments;
|
|
73
|
+
if (typeArgs && typeArgs.length > 0 && typeArgs[0]) {
|
|
74
|
+
return typeArgs[0];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return type;
|
|
78
|
+
}
|
|
79
|
+
function discoverToolFiles(toolsDir) {
|
|
80
|
+
const files = new Map();
|
|
81
|
+
if (!existsSync(toolsDir))
|
|
82
|
+
return files;
|
|
83
|
+
const entries = readdirSync(toolsDir);
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
if (!entry.endsWith(".ts"))
|
|
86
|
+
continue;
|
|
87
|
+
if (entry.endsWith(".d.ts"))
|
|
88
|
+
continue;
|
|
89
|
+
const name = entry.replace(/\.ts$/, "");
|
|
90
|
+
files.set(name, join(toolsDir, entry));
|
|
91
|
+
}
|
|
92
|
+
return files;
|
|
93
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RouteManifest, RouteToolTypes } from "../types.js";
|
|
2
|
+
export declare function renderDawnTypes(manifest: RouteManifest, toolTypes: readonly RouteToolTypes[]): string;
|
|
3
|
+
export declare function renderRouteTypes(manifest: RouteManifest): string;
|
|
4
|
+
//# sourceMappingURL=render-route-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-route-types.d.ts","sourceRoot":"","sources":["../../src/typegen/render-route-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAG9E,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,aAAa,EACvB,SAAS,EAAE,SAAS,cAAc,EAAE,GACnC,MAAM,CA4BR;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CA+BhE"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { renderToolTypes } from "./render-tool-types.js";
|
|
2
|
+
export function renderDawnTypes(manifest, toolTypes) {
|
|
3
|
+
const pathUnion = manifest.routes.length > 0
|
|
4
|
+
? manifest.routes.map((route) => JSON.stringify(route.pathname)).join(" | ")
|
|
5
|
+
: "never";
|
|
6
|
+
const paramLines = manifest.routes.map((route) => {
|
|
7
|
+
const params = renderParamsForSegments(route.segments);
|
|
8
|
+
return ` ${JSON.stringify(route.pathname)}: ${params};`;
|
|
9
|
+
});
|
|
10
|
+
const paramBlock = paramLines.length === 0
|
|
11
|
+
? " export interface DawnRouteParams {}"
|
|
12
|
+
: [" export interface DawnRouteParams {", ...paramLines, " }"].join("\n");
|
|
13
|
+
const toolBlock = renderToolTypes(toolTypes).trimEnd();
|
|
14
|
+
return [
|
|
15
|
+
'declare module "dawn:routes" {',
|
|
16
|
+
` export type DawnRoutePath = ${pathUnion};`,
|
|
17
|
+
"",
|
|
18
|
+
paramBlock,
|
|
19
|
+
"",
|
|
20
|
+
toolBlock,
|
|
21
|
+
"}",
|
|
22
|
+
"",
|
|
23
|
+
].join("\n");
|
|
24
|
+
}
|
|
25
|
+
export function renderRouteTypes(manifest) {
|
|
26
|
+
const pathUnion = manifest.routes.length > 0
|
|
27
|
+
? manifest.routes.map((route) => JSON.stringify(route.pathname)).join(" | ")
|
|
28
|
+
: "never";
|
|
29
|
+
const paramLines = manifest.routes.map((route) => {
|
|
30
|
+
const params = renderParamsForSegments(route.segments);
|
|
31
|
+
return ` ${JSON.stringify(route.pathname)}: ${params};`;
|
|
32
|
+
});
|
|
33
|
+
if (paramLines.length === 0) {
|
|
34
|
+
return [
|
|
35
|
+
'declare module "dawn:routes" {',
|
|
36
|
+
` export type DawnRoutePath = ${pathUnion};`,
|
|
37
|
+
"",
|
|
38
|
+
" export interface DawnRouteParams {}",
|
|
39
|
+
"}",
|
|
40
|
+
"",
|
|
41
|
+
].join("\n");
|
|
42
|
+
}
|
|
43
|
+
return [
|
|
44
|
+
'declare module "dawn:routes" {',
|
|
45
|
+
` export type DawnRoutePath = ${pathUnion};`,
|
|
46
|
+
"",
|
|
47
|
+
" export interface DawnRouteParams {",
|
|
48
|
+
...paramLines,
|
|
49
|
+
" }",
|
|
50
|
+
"}",
|
|
51
|
+
"",
|
|
52
|
+
].join("\n");
|
|
53
|
+
}
|
|
54
|
+
function renderParamsForSegments(segments) {
|
|
55
|
+
const params = segments.filter((segment) => segment.kind !== "static");
|
|
56
|
+
if (params.length === 0) {
|
|
57
|
+
return "{}";
|
|
58
|
+
}
|
|
59
|
+
return `{ ${params.map(renderParam).join("; ")} }`;
|
|
60
|
+
}
|
|
61
|
+
function renderParam(segment) {
|
|
62
|
+
switch (segment.kind) {
|
|
63
|
+
case "dynamic":
|
|
64
|
+
return `${segment.name}: string`;
|
|
65
|
+
case "catchall":
|
|
66
|
+
return `${segment.name}: string[]`;
|
|
67
|
+
case "optional-catchall":
|
|
68
|
+
return `${segment.name}?: string[]`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-tool-types.d.ts","sourceRoot":"","sources":["../../src/typegen/render-tool-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,wBAAgB,eAAe,CAAC,UAAU,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,CAyB7E"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function renderToolTypes(routeTools) {
|
|
2
|
+
const routesWithTools = routeTools.filter((r) => r.tools.length > 0);
|
|
3
|
+
const routeToolsType = " export type RouteTools<P extends DawnRoutePath> = DawnRouteTools[P];";
|
|
4
|
+
if (routesWithTools.length === 0) {
|
|
5
|
+
return [" export interface DawnRouteTools {}", "", routeToolsType, ""].join("\n");
|
|
6
|
+
}
|
|
7
|
+
const routeLines = [];
|
|
8
|
+
for (const route of routesWithTools) {
|
|
9
|
+
routeLines.push(` ${JSON.stringify(route.pathname)}: {`);
|
|
10
|
+
for (const tool of route.tools) {
|
|
11
|
+
const sig = tool.inputType === "void"
|
|
12
|
+
? `() => Promise<${tool.outputType}>`
|
|
13
|
+
: `(input: ${tool.inputType}) => Promise<${tool.outputType}>`;
|
|
14
|
+
routeLines.push(` readonly ${tool.name}: ${sig};`);
|
|
15
|
+
}
|
|
16
|
+
routeLines.push(" };");
|
|
17
|
+
}
|
|
18
|
+
return [" export interface DawnRouteTools {", ...routeLines, " }", "", routeToolsType, ""].join("\n");
|
|
19
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { RouteKind } from "@dawn-ai/sdk";
|
|
2
|
+
export type { RouteKind };
|
|
3
|
+
export interface DawnConfig {
|
|
4
|
+
readonly appDir?: string;
|
|
5
|
+
}
|
|
6
|
+
export type RouteSegment = {
|
|
7
|
+
readonly kind: "static";
|
|
8
|
+
readonly raw: string;
|
|
9
|
+
} | {
|
|
10
|
+
readonly kind: "dynamic" | "catchall" | "optional-catchall";
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly raw: string;
|
|
13
|
+
};
|
|
14
|
+
export interface RouteDefinition {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly pathname: string;
|
|
17
|
+
readonly kind: RouteKind;
|
|
18
|
+
readonly entryFile: string;
|
|
19
|
+
readonly routeDir: string;
|
|
20
|
+
readonly segments: RouteSegment[];
|
|
21
|
+
}
|
|
22
|
+
export interface RouteManifest {
|
|
23
|
+
readonly appRoot: string;
|
|
24
|
+
readonly routes: RouteDefinition[];
|
|
25
|
+
}
|
|
26
|
+
export interface LoadDawnConfigOptions {
|
|
27
|
+
readonly appRoot: string;
|
|
28
|
+
}
|
|
29
|
+
export interface LoadedDawnConfig {
|
|
30
|
+
readonly appRoot: string;
|
|
31
|
+
readonly config: DawnConfig;
|
|
32
|
+
readonly configPath: string;
|
|
33
|
+
}
|
|
34
|
+
export interface FindDawnAppOptions {
|
|
35
|
+
readonly appRoot?: string;
|
|
36
|
+
readonly cwd?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface DiscoveredDawnApp {
|
|
39
|
+
readonly appRoot: string;
|
|
40
|
+
readonly configPath: string;
|
|
41
|
+
readonly dawnDir: string;
|
|
42
|
+
readonly routesDir: string;
|
|
43
|
+
}
|
|
44
|
+
export interface DiscoverRoutesOptions {
|
|
45
|
+
readonly appRoot?: string;
|
|
46
|
+
readonly cwd?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface ExtractedToolType {
|
|
49
|
+
readonly name: string;
|
|
50
|
+
readonly inputType: string;
|
|
51
|
+
readonly outputType: string;
|
|
52
|
+
}
|
|
53
|
+
export interface RouteToolTypes {
|
|
54
|
+
readonly pathname: string;
|
|
55
|
+
readonly tools: readonly ExtractedToolType[];
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,YAAY,EAAE,SAAS,EAAE,CAAA;AAEzB,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,MAAM,YAAY,GACpB;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,mBAAmB,CAAA;IAC3D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB,CAAA;AAEL,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAClC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,eAAe,EAAE,CAAA;CACnC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,iBAAiB,EAAE,CAAA;CAC7C"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dawn-ai/core",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/cacheplane/dawnai/tree/main/packages/core#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/cacheplane/dawnai.git",
|
|
11
|
+
"directory": "packages/core"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/cacheplane/dawnai/issues"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=22.12.0"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"tsx": "^4.8.1",
|
|
34
|
+
"typescript": "5.8.3",
|
|
35
|
+
"@dawn-ai/sdk": "0.0.2"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "25.6.0",
|
|
39
|
+
"@dawn-ai/config-typescript": "0.0.2"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc -b tsconfig.json",
|
|
43
|
+
"lint": "biome check --config-path ../config-biome/biome.json package.json src test tsconfig.json vitest.config.ts",
|
|
44
|
+
"test": "vitest --run --config vitest.config.ts",
|
|
45
|
+
"typecheck": "tsc --noEmit"
|
|
46
|
+
}
|
|
47
|
+
}
|