appflare 0.0.14 → 0.0.15
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/cli/core/discover-handlers.ts +11 -1
- package/cli/generators/generate-api-client/client.ts +145 -75
- package/cli/generators/generate-api-client/index.ts +8 -5
- package/cli/generators/generate-api-client/types.ts +128 -43
- package/cli/generators/generate-api-client/utils.ts +11 -7
- package/cli/generators/generate-cron-handlers/handler-entries.ts +2 -2
- package/cli/generators/generate-hono-server/imports.ts +1 -1
- package/cli/generators/generate-hono-server/routes.ts +3 -3
- package/cli/generators/generate-scheduler-handlers/handler-entries.ts +2 -2
- package/cli/generators/generate-websocket-durable-object/imports.ts +1 -1
- package/cli/generators/generate-websocket-durable-object/query-handlers.ts +3 -3
- package/cli/generators/generate-websocket-durable-object/template.ts +11 -4
- package/cli/utils/utils.ts +2 -0
- package/package.json +1 -1
|
@@ -29,6 +29,13 @@ export async function discoverHandlers(params: {
|
|
|
29
29
|
if (path.resolve(fileAbs) === path.resolve(params.schemaPathAbs)) continue;
|
|
30
30
|
if (path.resolve(fileAbs) === path.resolve(params.configPathAbs)) continue;
|
|
31
31
|
|
|
32
|
+
const relPathRaw = path.relative(params.projectDirAbs, fileAbs);
|
|
33
|
+
const relPath = relPathRaw.replace(/\\/g, "/");
|
|
34
|
+
const rawRoutePath = relPath.replace(/\.ts$/, "");
|
|
35
|
+
const routePath = rawRoutePath.endsWith("/index")
|
|
36
|
+
? rawRoutePath.slice(0, -"/index".length) || "index"
|
|
37
|
+
: rawRoutePath;
|
|
38
|
+
|
|
32
39
|
const content = await fs.readFile(fileAbs, "utf8");
|
|
33
40
|
const cronTriggersByHandler = extractCronTriggers(content);
|
|
34
41
|
const regex =
|
|
@@ -38,6 +45,7 @@ export async function discoverHandlers(params: {
|
|
|
38
45
|
const kind = match[2] as HandlerKind;
|
|
39
46
|
handlers.push({
|
|
40
47
|
fileName: path.basename(fileAbs, ".ts"),
|
|
48
|
+
routePath,
|
|
41
49
|
name: match[1],
|
|
42
50
|
kind,
|
|
43
51
|
sourceFileAbs: fileAbs,
|
|
@@ -50,7 +58,7 @@ export async function discoverHandlers(params: {
|
|
|
50
58
|
// De-dupe: keep first occurrence
|
|
51
59
|
const seen = new Set<string>();
|
|
52
60
|
const unique = handlers.filter((h) => {
|
|
53
|
-
const key = `${h.kind}:${h.
|
|
61
|
+
const key = `${h.kind}:${h.routePath}:${h.name}`;
|
|
54
62
|
if (seen.has(key)) return false;
|
|
55
63
|
seen.add(key);
|
|
56
64
|
return true;
|
|
@@ -58,6 +66,8 @@ export async function discoverHandlers(params: {
|
|
|
58
66
|
|
|
59
67
|
unique.sort((a, b) => {
|
|
60
68
|
if (a.kind !== b.kind) return a.kind.localeCompare(b.kind);
|
|
69
|
+
if (a.routePath !== b.routePath)
|
|
70
|
+
return a.routePath.localeCompare(b.routePath);
|
|
61
71
|
if (a.fileName !== b.fileName) return a.fileName.localeCompare(b.fileName);
|
|
62
72
|
return a.name.localeCompare(b.name);
|
|
63
73
|
});
|
|
@@ -3,91 +3,161 @@ import {
|
|
|
3
3
|
handlerTypePrefix,
|
|
4
4
|
normalizeTableName,
|
|
5
5
|
renderObjectKey,
|
|
6
|
-
sortedEntries,
|
|
7
6
|
} from "./utils";
|
|
8
7
|
|
|
8
|
+
type PathTree<T> = {
|
|
9
|
+
leaf?: { path: string; items: T[] };
|
|
10
|
+
children: Map<string, PathTree<T>>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const buildPathTree = <T>(byPath: Map<string, T[]>): PathTree<T> => {
|
|
14
|
+
const root: PathTree<T> = { children: new Map() };
|
|
15
|
+
for (const [path, items] of Array.from(byPath.entries())) {
|
|
16
|
+
const segments = path.split("/").filter(Boolean);
|
|
17
|
+
let node = root;
|
|
18
|
+
for (const segment of segments) {
|
|
19
|
+
if (!node.children.has(segment)) {
|
|
20
|
+
node.children.set(segment, { children: new Map() });
|
|
21
|
+
}
|
|
22
|
+
node = node.children.get(segment)!;
|
|
23
|
+
}
|
|
24
|
+
node.leaf = { path, items };
|
|
25
|
+
}
|
|
26
|
+
return root;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const indent = (depth: number): string => "\t".repeat(depth);
|
|
30
|
+
|
|
31
|
+
const renderPathTreeLines = <T>(
|
|
32
|
+
node: PathTree<T>,
|
|
33
|
+
depth: number,
|
|
34
|
+
renderLeaf: (leaf: { path: string; items: T[] }, depth: number) => string[]
|
|
35
|
+
): string[] => {
|
|
36
|
+
const lines: string[] = [];
|
|
37
|
+
if (node.leaf) {
|
|
38
|
+
lines.push(...renderLeaf(node.leaf, depth));
|
|
39
|
+
}
|
|
40
|
+
const children = Array.from(node.children.entries()).sort((a, b) =>
|
|
41
|
+
a[0].localeCompare(b[0])
|
|
42
|
+
);
|
|
43
|
+
for (const [segment, child] of children) {
|
|
44
|
+
lines.push(`${indent(depth + 1)}${renderObjectKey(segment)}: {`);
|
|
45
|
+
lines.push(...renderPathTreeLines(child, depth + 1, renderLeaf));
|
|
46
|
+
lines.push(`${indent(depth + 1)}}`);
|
|
47
|
+
}
|
|
48
|
+
return lines;
|
|
49
|
+
};
|
|
50
|
+
|
|
9
51
|
export function generateQueriesClientLines(
|
|
10
52
|
queriesByFile: Map<string, DiscoveredHandler[]>,
|
|
11
53
|
importAliasBySource: Map<string, string>
|
|
12
54
|
): string {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
const tree = buildPathTree(queriesByFile);
|
|
56
|
+
const renderLeaf = (
|
|
57
|
+
leaf: { path: string; items: DiscoveredHandler[] },
|
|
58
|
+
depth: number
|
|
59
|
+
): string[] => {
|
|
60
|
+
const inner = leaf.items
|
|
61
|
+
.slice()
|
|
62
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
63
|
+
.map((h) => {
|
|
64
|
+
const pascal = handlerTypePrefix(h);
|
|
65
|
+
const route = `/queries/${leaf.path}/${h.name}`;
|
|
66
|
+
const importAlias = importAliasBySource.get(h.sourceFileAbs)!;
|
|
67
|
+
const handlerAccessor = `${importAlias}.${h.name}`;
|
|
68
|
+
const pad = indent(depth + 1);
|
|
69
|
+
return (
|
|
70
|
+
`${pad}${h.name}: withHandlerMetadata<${pascal}Definition>(\n` +
|
|
71
|
+
`${pad}\tasync (args: ${pascal}Args, init) => {\n` +
|
|
72
|
+
`${pad}\t\tconst url = buildQueryUrl(baseUrl, ${JSON.stringify(route)}, args);\n` +
|
|
73
|
+
`${pad}\t\tconst response = await request(url, {\n` +
|
|
74
|
+
`${pad}\t\t\t...(init ?? {}),\n` +
|
|
75
|
+
`${pad}\t\t\tmethod: "GET",\n` +
|
|
76
|
+
`${pad}\t\t});\n` +
|
|
77
|
+
`${pad}\t\treturn parseJson<${pascal}Result>(response);\n` +
|
|
78
|
+
`${pad}\t},\n` +
|
|
79
|
+
`${pad}\t{\n` +
|
|
80
|
+
`${pad}\t\tschema: createHandlerSchema(${handlerAccessor}.args),\n` +
|
|
81
|
+
`${pad}\t\twebsocket: createHandlerWebsocket<${pascal}Args, ${pascal}Result>(realtime, {\n` +
|
|
82
|
+
`${pad}\t\t\tdefaultTable: ${JSON.stringify(normalizeTableName(h.fileName))},\n` +
|
|
83
|
+
`${pad}\t\t\tdefaultHandler: { file: ${JSON.stringify(leaf.path)}, name: ${JSON.stringify(h.name)} },\n` +
|
|
84
|
+
`${pad}\t\t}),\n` +
|
|
85
|
+
`${pad}\t\tpath: ${JSON.stringify(route)},\n` +
|
|
86
|
+
`${pad}\t}\n` +
|
|
87
|
+
`${pad}),`
|
|
88
|
+
);
|
|
89
|
+
})
|
|
90
|
+
.join("\n");
|
|
91
|
+
|
|
92
|
+
return inner ? [inner] : [`${indent(depth + 1)}// (none)`];
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const lines: string[] = [];
|
|
96
|
+
const children = Array.from(tree.children.entries()).sort((a, b) =>
|
|
97
|
+
a[0].localeCompare(b[0])
|
|
98
|
+
);
|
|
99
|
+
for (const [segment, child] of children) {
|
|
100
|
+
lines.push(`${indent(1)}${renderObjectKey(segment)}: {`);
|
|
101
|
+
lines.push(...renderPathTreeLines(child, 1, renderLeaf));
|
|
102
|
+
lines.push(`${indent(1)}}`);
|
|
103
|
+
}
|
|
104
|
+
return lines.join("\n");
|
|
49
105
|
}
|
|
50
106
|
|
|
51
107
|
export function generateMutationsClientLines(
|
|
52
108
|
mutationsByFile: Map<string, DiscoveredHandler[]>,
|
|
53
109
|
importAliasBySource: Map<string, string>
|
|
54
110
|
): string {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
111
|
+
const tree = buildPathTree(mutationsByFile);
|
|
112
|
+
const renderLeaf = (
|
|
113
|
+
leaf: { path: string; items: DiscoveredHandler[] },
|
|
114
|
+
depth: number
|
|
115
|
+
): string[] => {
|
|
116
|
+
const inner = leaf.items
|
|
117
|
+
.slice()
|
|
118
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
119
|
+
.map((h) => {
|
|
120
|
+
const pascal = handlerTypePrefix(h);
|
|
121
|
+
const route = `/mutations/${leaf.path}/${h.name}`;
|
|
122
|
+
const importAlias = importAliasBySource.get(h.sourceFileAbs)!;
|
|
123
|
+
const handlerAccessor = `${importAlias}.${h.name}`;
|
|
124
|
+
const pad = indent(depth + 1);
|
|
125
|
+
return (
|
|
126
|
+
`${pad}${h.name}: withHandlerMetadata<${pascal}Definition>(\n` +
|
|
127
|
+
`${pad}\tasync (args: ${pascal}Args, init) => {\n` +
|
|
128
|
+
`${pad}\t\tconst url = buildUrl(baseUrl, ${JSON.stringify(route)});\n` +
|
|
129
|
+
`${pad}\t\tconst response = await request(url, {\n` +
|
|
130
|
+
`${pad}\t\t\t...(init ?? {}),\n` +
|
|
131
|
+
`${pad}\t\t\tmethod: "POST",\n` +
|
|
132
|
+
`${pad}\t\t\theaders: ensureJsonHeaders(init?.headers),\n` +
|
|
133
|
+
`${pad}\t\t\tbody: JSON.stringify(args),\n` +
|
|
134
|
+
`${pad}\t\t});\n` +
|
|
135
|
+
`${pad}\t\treturn parseJson<${pascal}Result>(response);\n` +
|
|
136
|
+
`${pad}\t},\n` +
|
|
137
|
+
`${pad}\t{\n` +
|
|
138
|
+
`${pad}\t\tschema: createHandlerSchema(${handlerAccessor}.args),\n` +
|
|
139
|
+
`${pad}\t\twebsocket: createHandlerWebsocket<${pascal}Args, ${pascal}Result>(realtime, {\n` +
|
|
140
|
+
`${pad}\t\t\tdefaultTable: ${JSON.stringify(normalizeTableName(h.fileName))},\n` +
|
|
141
|
+
`${pad}\t\t\tdefaultHandler: { file: ${JSON.stringify(leaf.path)}, name: ${JSON.stringify(h.name)} },\n` +
|
|
142
|
+
`${pad}\t\t}),\n` +
|
|
143
|
+
`${pad}\t\tpath: ${JSON.stringify(route)},\n` +
|
|
144
|
+
`${pad}\t}\n` +
|
|
145
|
+
`${pad}),`
|
|
146
|
+
);
|
|
147
|
+
})
|
|
148
|
+
.join("\n");
|
|
149
|
+
|
|
150
|
+
return inner ? [inner] : [`${indent(depth + 1)}// (none)`];
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const lines: string[] = [];
|
|
154
|
+
const children = Array.from(tree.children.entries()).sort((a, b) =>
|
|
155
|
+
a[0].localeCompare(b[0])
|
|
156
|
+
);
|
|
157
|
+
for (const [segment, child] of children) {
|
|
158
|
+
lines.push(`${indent(1)}${renderObjectKey(segment)}: {`);
|
|
159
|
+
lines.push(...renderPathTreeLines(child, 1, renderLeaf));
|
|
160
|
+
lines.push(`${indent(1)}}`);
|
|
161
|
+
}
|
|
162
|
+
return lines.join("\n");
|
|
93
163
|
}
|
|
@@ -681,7 +681,7 @@ function generateImports(params: {
|
|
|
681
681
|
const importLines: string[] = [];
|
|
682
682
|
const importAliasBySource = new Map<string, string>();
|
|
683
683
|
for (const [fileAbs, list] of Array.from(handlerImportsGrouped.entries())) {
|
|
684
|
-
const alias = `__appflare_${pascalCase(list[0].
|
|
684
|
+
const alias = `__appflare_${pascalCase(list[0].routePath)}`;
|
|
685
685
|
importAliasBySource.set(fileAbs, alias);
|
|
686
686
|
const importPath = toImportPathFromGeneratedSrc(params.outDirAbs, fileAbs);
|
|
687
687
|
importLines.push(
|
|
@@ -704,10 +704,13 @@ function generateGroupedHandlers(handlers: DiscoveredHandler[]): {
|
|
|
704
704
|
(h) => h.kind === "internalMutation"
|
|
705
705
|
);
|
|
706
706
|
|
|
707
|
-
const queriesByFile = groupBy(queries, (h) => h.
|
|
708
|
-
const mutationsByFile = groupBy(mutations, (h) => h.
|
|
709
|
-
const internalQueriesByFile = groupBy(internalQueries, (h) => h.
|
|
710
|
-
const internalMutationsByFile = groupBy(
|
|
707
|
+
const queriesByFile = groupBy(queries, (h) => h.routePath);
|
|
708
|
+
const mutationsByFile = groupBy(mutations, (h) => h.routePath);
|
|
709
|
+
const internalQueriesByFile = groupBy(internalQueries, (h) => h.routePath);
|
|
710
|
+
const internalMutationsByFile = groupBy(
|
|
711
|
+
internalMutations,
|
|
712
|
+
(h) => h.routePath
|
|
713
|
+
);
|
|
711
714
|
|
|
712
715
|
return {
|
|
713
716
|
queriesByFile,
|
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
import { DiscoveredHandler } from "../../utils/utils";
|
|
2
|
-
import { handlerTypePrefix, renderObjectKey
|
|
2
|
+
import { handlerTypePrefix, renderObjectKey } from "./utils";
|
|
3
|
+
|
|
4
|
+
type PathTree<T> = {
|
|
5
|
+
leaf?: { path: string; items: T[] };
|
|
6
|
+
children: Map<string, PathTree<T>>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const buildPathTree = <T>(byPath: Map<string, T[]>): PathTree<T> => {
|
|
10
|
+
const root: PathTree<T> = { children: new Map() };
|
|
11
|
+
for (const [path, items] of Array.from(byPath.entries())) {
|
|
12
|
+
const segments = path.split("/").filter(Boolean);
|
|
13
|
+
let node = root;
|
|
14
|
+
for (const segment of segments) {
|
|
15
|
+
if (!node.children.has(segment)) {
|
|
16
|
+
node.children.set(segment, { children: new Map() });
|
|
17
|
+
}
|
|
18
|
+
node = node.children.get(segment)!;
|
|
19
|
+
}
|
|
20
|
+
node.leaf = { path, items };
|
|
21
|
+
}
|
|
22
|
+
return root;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const indent = (depth: number): string => "\t".repeat(depth);
|
|
26
|
+
|
|
27
|
+
const renderPathTreeLines = <T>(
|
|
28
|
+
node: PathTree<T>,
|
|
29
|
+
depth: number,
|
|
30
|
+
renderLeaf: (leaf: { path: string; items: T[] }, depth: number) => string[]
|
|
31
|
+
): string[] => {
|
|
32
|
+
const lines: string[] = [];
|
|
33
|
+
if (node.leaf) {
|
|
34
|
+
lines.push(...renderLeaf(node.leaf, depth));
|
|
35
|
+
}
|
|
36
|
+
const children = Array.from(node.children.entries()).sort((a, b) =>
|
|
37
|
+
a[0].localeCompare(b[0])
|
|
38
|
+
);
|
|
39
|
+
for (const [segment, child] of children) {
|
|
40
|
+
lines.push(`${indent(depth + 1)}${renderObjectKey(segment)}: {`);
|
|
41
|
+
lines.push(...renderPathTreeLines(child, depth + 1, renderLeaf));
|
|
42
|
+
lines.push(`${indent(depth + 1)}}`);
|
|
43
|
+
}
|
|
44
|
+
return lines;
|
|
45
|
+
};
|
|
3
46
|
|
|
4
47
|
export function generateTypeBlocks(
|
|
5
48
|
handlers: DiscoveredHandler[],
|
|
@@ -23,57 +66,99 @@ export function generateTypeBlocks(
|
|
|
23
66
|
export function generateQueriesTypeLines(
|
|
24
67
|
queriesByFile: Map<string, DiscoveredHandler[]>
|
|
25
68
|
): string {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
69
|
+
const tree = buildPathTree(queriesByFile);
|
|
70
|
+
const renderLeaf = (
|
|
71
|
+
leaf: { path: string; items: DiscoveredHandler[] },
|
|
72
|
+
depth: number
|
|
73
|
+
): string[] => {
|
|
74
|
+
const inner = leaf.items
|
|
75
|
+
.slice()
|
|
76
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
77
|
+
.map((h) => {
|
|
78
|
+
const pascal = handlerTypePrefix(h);
|
|
79
|
+
return `${indent(depth + 1)}${h.name}: ${pascal}Client;`;
|
|
80
|
+
})
|
|
81
|
+
.join("\n");
|
|
82
|
+
|
|
83
|
+
return inner ? [inner] : [`${indent(depth + 1)}// (none)`];
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const lines: string[] = [];
|
|
87
|
+
const children = Array.from(tree.children.entries()).sort((a, b) =>
|
|
88
|
+
a[0].localeCompare(b[0])
|
|
89
|
+
);
|
|
90
|
+
for (const [segment, child] of children) {
|
|
91
|
+
lines.push(`${indent(1)}${renderObjectKey(segment)}: {`);
|
|
92
|
+
lines.push(...renderPathTreeLines(child, 1, renderLeaf));
|
|
93
|
+
lines.push(`${indent(1)}}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return lines.join("\n");
|
|
40
97
|
}
|
|
41
98
|
|
|
42
99
|
export function generateMutationsTypeLines(
|
|
43
100
|
mutationsByFile: Map<string, DiscoveredHandler[]>
|
|
44
101
|
): string {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
102
|
+
const tree = buildPathTree(mutationsByFile);
|
|
103
|
+
const renderLeaf = (
|
|
104
|
+
leaf: { path: string; items: DiscoveredHandler[] },
|
|
105
|
+
depth: number
|
|
106
|
+
): string[] => {
|
|
107
|
+
const inner = leaf.items
|
|
108
|
+
.slice()
|
|
109
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
110
|
+
.map((h) => {
|
|
111
|
+
const pascal = handlerTypePrefix(h);
|
|
112
|
+
return `${indent(depth + 1)}${h.name}: ${pascal}Client;`;
|
|
113
|
+
})
|
|
114
|
+
.join("\n");
|
|
115
|
+
|
|
116
|
+
return inner ? [inner] : [`${indent(depth + 1)}// (none)`];
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const lines: string[] = [];
|
|
120
|
+
const children = Array.from(tree.children.entries()).sort((a, b) =>
|
|
121
|
+
a[0].localeCompare(b[0])
|
|
122
|
+
);
|
|
123
|
+
for (const [segment, child] of children) {
|
|
124
|
+
lines.push(`${indent(1)}${renderObjectKey(segment)}: {`);
|
|
125
|
+
lines.push(...renderPathTreeLines(child, 1, renderLeaf));
|
|
126
|
+
lines.push(`${indent(1)}}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return lines.join("\n");
|
|
59
130
|
}
|
|
60
131
|
|
|
61
132
|
export function generateInternalTypeLines(
|
|
62
133
|
internalByFile: Map<string, DiscoveredHandler[]>,
|
|
63
134
|
importAliasBySource: Map<string, string>
|
|
64
135
|
): string {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
136
|
+
const tree = buildPathTree(internalByFile);
|
|
137
|
+
const renderLeaf = (
|
|
138
|
+
leaf: { path: string; items: DiscoveredHandler[] },
|
|
139
|
+
depth: number
|
|
140
|
+
): string[] => {
|
|
141
|
+
const inner = leaf.items
|
|
142
|
+
.slice()
|
|
143
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
144
|
+
.map((h) => {
|
|
145
|
+
const alias = importAliasBySource.get(h.sourceFileAbs)!;
|
|
146
|
+
return `${indent(depth + 1)}${h.name}: typeof ${alias}[${JSON.stringify(h.name)}];`;
|
|
147
|
+
})
|
|
148
|
+
.join("\n");
|
|
149
|
+
|
|
150
|
+
return inner ? [inner] : [`${indent(depth + 1)}// (none)`];
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const lines: string[] = [];
|
|
154
|
+
const children = Array.from(tree.children.entries()).sort((a, b) =>
|
|
155
|
+
a[0].localeCompare(b[0])
|
|
156
|
+
);
|
|
157
|
+
for (const [segment, child] of children) {
|
|
158
|
+
lines.push(`${indent(1)}${renderObjectKey(segment)}: {`);
|
|
159
|
+
lines.push(...renderPathTreeLines(child, 1, renderLeaf));
|
|
160
|
+
lines.push(`${indent(1)}}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return lines.join("\n");
|
|
79
164
|
}
|
|
@@ -6,13 +6,17 @@ export const sortedEntries = <T>(map: Map<string, T[]>): Array<[string, T[]]> =>
|
|
|
6
6
|
export const renderObjectKey = (key: string): string =>
|
|
7
7
|
isValidIdentifier(key) ? key : JSON.stringify(key);
|
|
8
8
|
|
|
9
|
-
export const handlerTypePrefix = (h: any): string =>
|
|
10
|
-
h.fileName
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
.
|
|
9
|
+
export const handlerTypePrefix = (h: any): string => {
|
|
10
|
+
const base = (h.routePath ?? h.fileName) as string;
|
|
11
|
+
return (
|
|
12
|
+
base
|
|
13
|
+
.replace(/[^a-zA-Z0-9]/g, "")
|
|
14
|
+
.replace(/^./, (c: string) => c.toUpperCase()) +
|
|
15
|
+
(h.name as string)
|
|
16
|
+
.replace(/[^a-zA-Z0-9]/g, "")
|
|
17
|
+
.replace(/^./, (c: string) => c.toUpperCase())
|
|
18
|
+
);
|
|
19
|
+
};
|
|
16
20
|
|
|
17
21
|
export const normalizeTableName = (fileName: string): string =>
|
|
18
22
|
fileName.endsWith("s") ? fileName : `${fileName}s`;
|
|
@@ -14,11 +14,11 @@ export const buildCronHandlerEntries = (params: {
|
|
|
14
14
|
return params.handlers
|
|
15
15
|
.map((handler) => {
|
|
16
16
|
const local = params.localNameFor(handler);
|
|
17
|
-
const task = `${handler.
|
|
17
|
+
const task = `${handler.routePath}/${handler.name}`;
|
|
18
18
|
const fallbackTriggers = stringifyTriggers(handler.cronTriggers);
|
|
19
19
|
return (
|
|
20
20
|
`\t${JSON.stringify(task)}: {\n` +
|
|
21
|
-
`\t\tfile: ${JSON.stringify(handler.
|
|
21
|
+
`\t\tfile: ${JSON.stringify(handler.routePath)},\n` +
|
|
22
22
|
`\t\tname: ${JSON.stringify(handler.name)},\n` +
|
|
23
23
|
`\t\tcronTrigger: ${local}.cronTrigger ?? ${fallbackTriggers},\n` +
|
|
24
24
|
`\t\trun: ${local}.handler,\n` +
|
|
@@ -30,7 +30,7 @@ export function buildImportSection(params: {
|
|
|
30
30
|
);
|
|
31
31
|
const configImportLine = `import appflareConfig from ${JSON.stringify(configImportPath)};`;
|
|
32
32
|
const localNameFor = (handler: DiscoveredHandler): string =>
|
|
33
|
-
`__appflare_${pascalCase(handler.
|
|
33
|
+
`__appflare_${pascalCase(handler.routePath)}_${handler.name}`;
|
|
34
34
|
const grouped = groupBy(params.handlers, (handler) => handler.sourceFileAbs);
|
|
35
35
|
const handlerImports: string[] = [];
|
|
36
36
|
for (const [fileAbs, list] of Array.from(grouped.entries())) {
|
|
@@ -10,7 +10,7 @@ export function buildRouteLines(params: {
|
|
|
10
10
|
const local = params.localNameFor(query);
|
|
11
11
|
routeLines.push(
|
|
12
12
|
`app.get(\n` +
|
|
13
|
-
|
|
13
|
+
` ${JSON.stringify(`/queries/${query.routePath}/${query.name}`)},\n` +
|
|
14
14
|
`\tsValidator("query", z.object(${local}.args as any)),\n` +
|
|
15
15
|
`\tasync (c) => {\n` +
|
|
16
16
|
`\t\ttry {\n` +
|
|
@@ -39,7 +39,7 @@ export function buildRouteLines(params: {
|
|
|
39
39
|
const local = params.localNameFor(mutation);
|
|
40
40
|
routeLines.push(
|
|
41
41
|
`app.post(\n` +
|
|
42
|
-
`\t${JSON.stringify(`/mutations/${mutation.
|
|
42
|
+
`\t${JSON.stringify(`/mutations/${mutation.routePath}/${mutation.name}`)},\n` +
|
|
43
43
|
`\tsValidator("json", z.object(${local}.args as any)),\n` +
|
|
44
44
|
`\tasync (c) => {\n` +
|
|
45
45
|
`\t\ttry {\n` +
|
|
@@ -58,7 +58,7 @@ export function buildRouteLines(params: {
|
|
|
58
58
|
`\t\t\t\ttry {\n` +
|
|
59
59
|
`\t\t\t\t\tawait notifyMutation({\n` +
|
|
60
60
|
`\t\t\t\t\t table: normalizeTableName(${JSON.stringify(mutation.fileName)}),\n` +
|
|
61
|
-
`\t\t\t\t\t handler: { file: ${JSON.stringify(mutation.
|
|
61
|
+
`\t\t\t\t\t handler: { file: ${JSON.stringify(mutation.routePath)}, name: ${JSON.stringify(mutation.name)} },\n` +
|
|
62
62
|
`\t\t\t\t\t args: body,\n` +
|
|
63
63
|
`\t\t\t\t\t result,\n` +
|
|
64
64
|
`\t\t\t\t});\n` +
|
|
@@ -9,10 +9,10 @@ export const buildHandlerEntries = (params: {
|
|
|
9
9
|
return params.handlers
|
|
10
10
|
.map((handler) => {
|
|
11
11
|
const local = params.localNameFor(handler);
|
|
12
|
-
const task = `${handler.
|
|
12
|
+
const task = `${handler.routePath}/${handler.name}`;
|
|
13
13
|
return (
|
|
14
14
|
`\t${JSON.stringify(task)}: {\n` +
|
|
15
|
-
|
|
15
|
+
` \tfile: ${JSON.stringify(handler.routePath)},\n` +
|
|
16
16
|
`\t\tname: ${JSON.stringify(handler.name)},\n` +
|
|
17
17
|
`\t\trun: ${local}.handler,\n` +
|
|
18
18
|
`\t},`
|
|
@@ -28,7 +28,7 @@ export function buildImportSection(params: {
|
|
|
28
28
|
);
|
|
29
29
|
|
|
30
30
|
const localNameFor = (handler: DiscoveredHandler): string =>
|
|
31
|
-
`__appflare_${pascalCase(handler.
|
|
31
|
+
`__appflare_${pascalCase(handler.routePath)}_${handler.name}`;
|
|
32
32
|
|
|
33
33
|
const grouped = groupBy(params.queries, (handler) => handler.sourceFileAbs);
|
|
34
34
|
const handlerImports: string[] = [];
|
|
@@ -7,12 +7,12 @@ export function buildQueryHandlerEntries(params: {
|
|
|
7
7
|
return params.queries
|
|
8
8
|
.slice()
|
|
9
9
|
.sort((a, b) => {
|
|
10
|
-
if (a.
|
|
11
|
-
return a.
|
|
10
|
+
if (a.routePath === b.routePath) return a.name.localeCompare(b.name);
|
|
11
|
+
return a.routePath.localeCompare(b.routePath);
|
|
12
12
|
})
|
|
13
13
|
.map(
|
|
14
14
|
(query) =>
|
|
15
|
-
|
|
15
|
+
` ${JSON.stringify(`${query.routePath}/${query.name}`)}: { file: ${JSON.stringify(query.routePath)}, name: ${JSON.stringify(query.name)}, definition: ${params.localNameFor(query)} },`
|
|
16
16
|
)
|
|
17
17
|
.join("\n");
|
|
18
18
|
}
|
|
@@ -137,10 +137,17 @@ const defaultHandlerForTable = (
|
|
|
137
137
|
\t\tpossible.push(tableStr.slice(0, -1));
|
|
138
138
|
\t}
|
|
139
139
|
\tfor (const candidate of possible) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
const handlerName = "get" + pascalCase(candidate);
|
|
141
|
+
const suffix = "/" + handlerName;
|
|
142
|
+
const matchKey = Object.keys(queryHandlers).find((key) => {
|
|
143
|
+
if (!key.endsWith(suffix)) return false;
|
|
144
|
+
const segments = key.split("/");
|
|
145
|
+
return segments.length >= 2 && segments[segments.length - 2] === candidate;
|
|
146
|
+
});
|
|
147
|
+
if (matchKey) {
|
|
148
|
+
const file = matchKey.slice(0, matchKey.length - suffix.length);
|
|
149
|
+
return { file, name: handlerName };
|
|
150
|
+
}
|
|
144
151
|
\t}
|
|
145
152
|
\treturn null;
|
|
146
153
|
};
|
package/cli/utils/utils.ts
CHANGED