appflare 0.0.13 → 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/build.ts +15 -2
- package/cli/core/discover-handlers.ts +11 -1
- package/cli/core/index.ts +19 -1
- package/cli/generators/generate-api-client/client.ts +145 -75
- package/cli/generators/generate-api-client/index.ts +26 -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 +4 -2
- package/cli/generators/generate-hono-server/routes.ts +21 -5
- package/cli/generators/generate-hono-server/template.ts +89 -5
- 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 +117 -29
- package/cli/index.ts +12 -0
- package/cli/schema/schema-static-types.ts +84 -3
- package/cli/utils/tsc.ts +3 -2
- package/cli/utils/utils.ts +2 -0
- package/cli/utils/zod-utils.ts +2 -2
- package/lib/db.ts +12 -2
- package/lib/values.ts +6 -5
- package/package.json +1 -1
- package/server/database/context.ts +7 -5
- package/server/database/query-builder.ts +8 -2
- package/server/storage/auth.ts +4 -2
- package/server/types/types.ts +51 -1
- package/server/utils/id-utils.ts +100 -1
package/cli/core/build.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
AppflareConfig,
|
|
6
6
|
assertDirExists,
|
|
7
7
|
assertFileExists,
|
|
8
|
+
toImportPathFromGeneratedSrc,
|
|
8
9
|
} from "../utils/utils";
|
|
9
10
|
import { getSchemaTableNames, generateSchemaTypes } from "../schema/schema";
|
|
10
11
|
import {
|
|
@@ -38,6 +39,17 @@ export async function buildFromConfig(params: {
|
|
|
38
39
|
await fs.mkdir(path.join(outDirAbs, "src"), { recursive: true });
|
|
39
40
|
await fs.mkdir(path.join(outDirAbs, "server"), { recursive: true });
|
|
40
41
|
|
|
42
|
+
// Re-export the user schema inside the generated output so downstream code can import it from the build directory.
|
|
43
|
+
const schemaImportPathForGeneratedSrc = toImportPathFromGeneratedSrc(
|
|
44
|
+
outDirAbs,
|
|
45
|
+
schemaPathAbs
|
|
46
|
+
);
|
|
47
|
+
const schemaReexport = `import schema from ${JSON.stringify(schemaImportPathForGeneratedSrc)};
|
|
48
|
+
export type AppflareGeneratedSchema = typeof schema;
|
|
49
|
+
export default schema;
|
|
50
|
+
`;
|
|
51
|
+
await fs.writeFile(path.join(outDirAbs, "src", "schema.ts"), schemaReexport);
|
|
52
|
+
|
|
41
53
|
const schemaTypesTs = await generateSchemaTypes({
|
|
42
54
|
schemaPathAbs,
|
|
43
55
|
configPathAbs,
|
|
@@ -140,17 +152,18 @@ async function writeEmitTsconfig(params: {
|
|
|
140
152
|
declaration: true,
|
|
141
153
|
emitDeclarationOnly: false,
|
|
142
154
|
outDir: `./${outDirRel}/dist`,
|
|
143
|
-
rootDir:
|
|
155
|
+
rootDir: `.`,
|
|
144
156
|
sourceMap: false,
|
|
145
157
|
declarationMap: false,
|
|
146
158
|
skipLibCheck: true,
|
|
147
159
|
target: "ES2022",
|
|
148
160
|
module: "ES2022",
|
|
149
161
|
moduleResolution: "Bundler",
|
|
150
|
-
types: [],
|
|
162
|
+
types: ["node"],
|
|
151
163
|
},
|
|
152
164
|
include: [
|
|
153
165
|
`./${outDirRel}/src/schema-types.ts`,
|
|
166
|
+
`./${outDirRel}/src/schema.ts`,
|
|
154
167
|
`./${outDirRel}/src/handlers/**/*`,
|
|
155
168
|
],
|
|
156
169
|
};
|
|
@@ -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
|
});
|
package/cli/core/index.ts
CHANGED
|
@@ -14,12 +14,20 @@ import {
|
|
|
14
14
|
} from "./handlers";
|
|
15
15
|
import { generateSchemaTypes, getSchemaTableNames } from "../schema/schema";
|
|
16
16
|
import { runTscEmit, writeEmitTsconfig } from "../utils/tsc";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
assertDirExists,
|
|
19
|
+
assertFileExists,
|
|
20
|
+
toImportPathFromGeneratedSrc,
|
|
21
|
+
} from "../utils/utils";
|
|
18
22
|
|
|
19
23
|
type AppflareConfig = {
|
|
20
24
|
dir: string;
|
|
21
25
|
schema: string;
|
|
22
26
|
outDir: string;
|
|
27
|
+
auth?: {
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
basePath?: string;
|
|
30
|
+
};
|
|
23
31
|
};
|
|
24
32
|
|
|
25
33
|
const program = new Command();
|
|
@@ -106,6 +114,16 @@ async function buildFromConfig(params: {
|
|
|
106
114
|
await fs.mkdir(path.join(outDirAbs, "src"), { recursive: true });
|
|
107
115
|
await fs.mkdir(path.join(outDirAbs, "server"), { recursive: true });
|
|
108
116
|
|
|
117
|
+
const schemaImportPathForGeneratedSrc = toImportPathFromGeneratedSrc(
|
|
118
|
+
outDirAbs,
|
|
119
|
+
schemaPathAbs
|
|
120
|
+
);
|
|
121
|
+
const schemaReexport = `import schema from ${JSON.stringify(schemaImportPathForGeneratedSrc)};
|
|
122
|
+
export type AppflareGeneratedSchema = typeof schema;
|
|
123
|
+
export default schema;
|
|
124
|
+
`;
|
|
125
|
+
await fs.writeFile(path.join(outDirAbs, "src", "schema.ts"), schemaReexport);
|
|
126
|
+
|
|
109
127
|
const schemaTypesTs = await generateSchemaTypes({
|
|
110
128
|
schemaPathAbs,
|
|
111
129
|
configPathAbs,
|
|
@@ -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
|
}
|
|
@@ -369,6 +369,15 @@ export async function runInternalQuery<
|
|
|
369
369
|
args: HandlerArgsFromShape<TArgs>
|
|
370
370
|
): Promise<TResult> {
|
|
371
371
|
const parsed = parseHandlerArgs(handler as any, args as any);
|
|
372
|
+
if (handler.middleware) {
|
|
373
|
+
const middlewareResult = await handler.middleware(
|
|
374
|
+
ctx as any,
|
|
375
|
+
parsed as any
|
|
376
|
+
);
|
|
377
|
+
if (typeof middlewareResult !== "undefined") {
|
|
378
|
+
return middlewareResult as TResult;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
372
381
|
return handler.handler(ctx as any, parsed as any);
|
|
373
382
|
}
|
|
374
383
|
|
|
@@ -381,6 +390,15 @@ export async function runInternalMutation<
|
|
|
381
390
|
args: HandlerArgsFromShape<TArgs>
|
|
382
391
|
): Promise<TResult> {
|
|
383
392
|
const parsed = parseHandlerArgs(handler as any, args as any);
|
|
393
|
+
if (handler.middleware) {
|
|
394
|
+
const middlewareResult = await handler.middleware(
|
|
395
|
+
ctx as any,
|
|
396
|
+
parsed as any
|
|
397
|
+
);
|
|
398
|
+
if (typeof middlewareResult !== "undefined") {
|
|
399
|
+
return middlewareResult as TResult;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
384
402
|
return handler.handler(ctx as any, parsed as any);
|
|
385
403
|
}
|
|
386
404
|
|
|
@@ -663,7 +681,7 @@ function generateImports(params: {
|
|
|
663
681
|
const importLines: string[] = [];
|
|
664
682
|
const importAliasBySource = new Map<string, string>();
|
|
665
683
|
for (const [fileAbs, list] of Array.from(handlerImportsGrouped.entries())) {
|
|
666
|
-
const alias = `__appflare_${pascalCase(list[0].
|
|
684
|
+
const alias = `__appflare_${pascalCase(list[0].routePath)}`;
|
|
667
685
|
importAliasBySource.set(fileAbs, alias);
|
|
668
686
|
const importPath = toImportPathFromGeneratedSrc(params.outDirAbs, fileAbs);
|
|
669
687
|
importLines.push(
|
|
@@ -686,10 +704,13 @@ function generateGroupedHandlers(handlers: DiscoveredHandler[]): {
|
|
|
686
704
|
(h) => h.kind === "internalMutation"
|
|
687
705
|
);
|
|
688
706
|
|
|
689
|
-
const queriesByFile = groupBy(queries, (h) => h.
|
|
690
|
-
const mutationsByFile = groupBy(mutations, (h) => h.
|
|
691
|
-
const internalQueriesByFile = groupBy(internalQueries, (h) => h.
|
|
692
|
-
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
|
+
);
|
|
693
714
|
|
|
694
715
|
return {
|
|
695
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` +
|