appflare 0.0.28 → 0.1.1
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/commands/index.ts +140 -0
- package/cli/generate.ts +149 -0
- package/cli/index.ts +56 -447
- package/cli/load-config.ts +182 -0
- package/cli/schema-compiler.ts +657 -0
- package/cli/templates/auth/README.md +156 -0
- package/cli/templates/auth/config.ts +61 -0
- package/cli/templates/auth/route-config.ts +18 -0
- package/cli/templates/auth/route-handler.ts +18 -0
- package/cli/templates/auth/route-request-utils.ts +55 -0
- package/cli/templates/auth/route.ts +14 -0
- package/cli/templates/core/README.md +266 -0
- package/cli/templates/core/app-creation.ts +19 -0
- package/cli/templates/core/client/appflare.ts +37 -0
- package/cli/templates/core/client/index.ts +6 -0
- package/cli/templates/core/client/storage.ts +100 -0
- package/cli/templates/core/client/types.ts +54 -0
- package/cli/templates/core/client-modules/appflare.ts +112 -0
- package/cli/templates/core/client-modules/handlers/index.ts +747 -0
- package/cli/templates/core/client-modules/handlers.ts +1 -0
- package/cli/templates/core/client-modules/index.ts +7 -0
- package/cli/templates/core/client-modules/storage.ts +180 -0
- package/cli/templates/core/client-modules/types.ts +145 -0
- package/cli/templates/core/client.ts +39 -0
- package/cli/templates/core/drizzle.ts +15 -0
- package/cli/templates/core/export.ts +14 -0
- package/cli/templates/core/handlers-route.ts +23 -0
- package/cli/templates/core/handlers.ts +1 -0
- package/cli/templates/core/imports.ts +8 -0
- package/cli/templates/core/server.ts +38 -0
- package/cli/templates/core/types.ts +6 -0
- package/cli/templates/core/wrangler.ts +109 -0
- package/cli/templates/handlers/README.md +353 -0
- package/cli/templates/handlers/auth.ts +37 -0
- package/cli/templates/handlers/execution.ts +39 -0
- package/cli/templates/handlers/generators/context/context-creation.ts +80 -0
- package/cli/templates/handlers/generators/context/error-helpers.ts +11 -0
- package/cli/templates/handlers/generators/context/scheduler.ts +24 -0
- package/cli/templates/handlers/generators/context/storage-api.ts +112 -0
- package/cli/templates/handlers/generators/context/storage-helpers.ts +59 -0
- package/cli/templates/handlers/generators/context/types.ts +18 -0
- package/cli/templates/handlers/generators/context.ts +43 -0
- package/cli/templates/handlers/generators/execution.ts +15 -0
- package/cli/templates/handlers/generators/handlers.ts +13 -0
- package/cli/templates/handlers/generators/registration/modules/cron.ts +30 -0
- package/cli/templates/handlers/generators/registration/modules/realtime.ts +631 -0
- package/cli/templates/handlers/generators/registration/modules/scheduler.ts +56 -0
- package/cli/templates/handlers/generators/registration/modules/storage.ts +194 -0
- package/cli/templates/handlers/generators/registration/sections.ts +210 -0
- package/cli/templates/handlers/generators/types/context.ts +64 -0
- package/cli/templates/handlers/generators/types/core.ts +101 -0
- package/cli/templates/handlers/generators/types/operations.ts +135 -0
- package/cli/templates/handlers/generators/types/query-definitions.ts +1029 -0
- package/cli/templates/handlers/generators/types/query-runtime.ts +417 -0
- package/cli/templates/handlers/index.ts +43 -0
- package/cli/templates/handlers/operations.ts +116 -0
- package/cli/templates/handlers/registration.ts +83 -0
- package/cli/templates/handlers/types.ts +15 -0
- package/cli/templates/handlers/utils.ts +48 -0
- package/cli/types.ts +108 -0
- package/cli/utils/handler-discovery.ts +366 -0
- package/cli/utils/json-utils.ts +24 -0
- package/cli/utils/path-utils.ts +19 -0
- package/cli/utils/schema-discovery.ts +390 -0
- package/index.ts +27 -4
- package/package.json +23 -20
- package/react/index.ts +5 -3
- package/react/use-infinite-query.ts +190 -0
- package/react/use-mutation.ts +54 -0
- package/react/use-query.ts +158 -0
- package/schema.ts +262 -0
- package/tsconfig.json +2 -4
- package/cli/README.md +0 -108
- package/cli/core/build.ts +0 -187
- package/cli/core/config.ts +0 -92
- package/cli/core/discover-handlers.ts +0 -143
- package/cli/core/handlers.ts +0 -7
- package/cli/core/index.ts +0 -205
- package/cli/generators/generate-api-client/client.ts +0 -163
- package/cli/generators/generate-api-client/extract-configuration.ts +0 -121
- package/cli/generators/generate-api-client/index.ts +0 -973
- package/cli/generators/generate-api-client/types.ts +0 -164
- package/cli/generators/generate-api-client/utils.ts +0 -22
- package/cli/generators/generate-api-client.ts +0 -1
- package/cli/generators/generate-cloudflare-worker/helpers.ts +0 -24
- package/cli/generators/generate-cloudflare-worker/index.ts +0 -2
- package/cli/generators/generate-cloudflare-worker/worker.ts +0 -148
- package/cli/generators/generate-cloudflare-worker/wrangler.ts +0 -108
- package/cli/generators/generate-cloudflare-worker.ts +0 -4
- package/cli/generators/generate-cron-handlers/cron-handlers-block.ts +0 -2
- package/cli/generators/generate-cron-handlers/handler-entries.ts +0 -29
- package/cli/generators/generate-cron-handlers/index.ts +0 -61
- package/cli/generators/generate-cron-handlers/runtime-block.ts +0 -49
- package/cli/generators/generate-cron-handlers/type-helpers-block.ts +0 -60
- package/cli/generators/generate-db-handlers/index.ts +0 -33
- package/cli/generators/generate-db-handlers/prepare.ts +0 -24
- package/cli/generators/generate-db-handlers/templates.ts +0 -189
- package/cli/generators/generate-db-handlers.ts +0 -1
- package/cli/generators/generate-hono-server/auth.ts +0 -97
- package/cli/generators/generate-hono-server/imports.ts +0 -55
- package/cli/generators/generate-hono-server/index.ts +0 -52
- package/cli/generators/generate-hono-server/routes.ts +0 -115
- package/cli/generators/generate-hono-server/template.ts +0 -371
- package/cli/generators/generate-hono-server.ts +0 -1
- package/cli/generators/generate-scheduler-handlers/constants.ts +0 -8
- package/cli/generators/generate-scheduler-handlers/handler-entries.ts +0 -22
- package/cli/generators/generate-scheduler-handlers/index.ts +0 -51
- package/cli/generators/generate-scheduler-handlers/runtime-block.ts +0 -68
- package/cli/generators/generate-scheduler-handlers/scheduler-handlers-block.ts +0 -2
- package/cli/generators/generate-scheduler-handlers/type-helpers-block.ts +0 -68
- package/cli/generators/generate-scheduler-handlers.ts +0 -1
- package/cli/generators/generate-websocket-durable-object/auth.ts +0 -30
- package/cli/generators/generate-websocket-durable-object/imports.ts +0 -55
- package/cli/generators/generate-websocket-durable-object/index.ts +0 -41
- package/cli/generators/generate-websocket-durable-object/query-handlers.ts +0 -18
- package/cli/generators/generate-websocket-durable-object/template.ts +0 -714
- package/cli/generators/generate-websocket-durable-object.ts +0 -1
- package/cli/schema/schema-static-types.ts +0 -702
- package/cli/schema/schema.ts +0 -151
- package/cli/utils/tsc.ts +0 -54
- package/cli/utils/utils.ts +0 -190
- package/cli/utils/zod-utils.ts +0 -121
- package/lib/README.md +0 -50
- package/lib/db.ts +0 -19
- package/lib/location.ts +0 -110
- package/lib/values.ts +0 -27
- package/react/README.md +0 -67
- package/react/hooks/useMutation.ts +0 -89
- package/react/hooks/usePaginatedQuery.ts +0 -213
- package/react/hooks/useQuery.ts +0 -106
- package/react/shared/queryShared.ts +0 -174
- package/server/README.md +0 -218
- package/server/auth.ts +0 -107
- package/server/database/builders.ts +0 -83
- package/server/database/context.ts +0 -327
- package/server/database/populate.ts +0 -234
- package/server/database/query-builder.ts +0 -161
- package/server/database/query-utils.ts +0 -25
- package/server/db.ts +0 -2
- package/server/storage/auth.ts +0 -16
- package/server/storage/bucket.ts +0 -22
- package/server/storage/context.ts +0 -34
- package/server/storage/index.ts +0 -38
- package/server/storage/operations.ts +0 -149
- package/server/storage/route-handler.ts +0 -60
- package/server/storage/types.ts +0 -55
- package/server/storage/utils.ts +0 -47
- package/server/storage.ts +0 -6
- package/server/types/schema-refs.ts +0 -66
- package/server/types/types.ts +0 -633
- package/server/utils/id-utils.ts +0 -230
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
export function generateTypesQueryRuntimeSection(): string {
|
|
2
|
+
return `export function createQueryDb(
|
|
3
|
+
$db: AppflareDb,
|
|
4
|
+
options?: QueryDbOptions,
|
|
5
|
+
): AppflareQueryDb {
|
|
6
|
+
const cache = new Map<string, unknown>();
|
|
7
|
+
const onMutation = options?.onMutation;
|
|
8
|
+
const queryDb = $db.query as Record<
|
|
9
|
+
string,
|
|
10
|
+
{
|
|
11
|
+
findMany: (args?: unknown) => Promise<unknown>;
|
|
12
|
+
findFirst: (args?: unknown) => Promise<unknown>;
|
|
13
|
+
}
|
|
14
|
+
>;
|
|
15
|
+
|
|
16
|
+
return new Proxy({} as AppflareQueryDb, {
|
|
17
|
+
get: (_target, property) => {
|
|
18
|
+
const tableName = String(property);
|
|
19
|
+
if (cache.has(tableName)) {
|
|
20
|
+
return cache.get(tableName);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const queryTable = queryDb[tableName];
|
|
24
|
+
if (!queryTable) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const table = (mergedSchema as Record<string, unknown>)[tableName];
|
|
29
|
+
|
|
30
|
+
const emitMutation = (
|
|
31
|
+
kind: DbMutationKind,
|
|
32
|
+
args: Record<string, unknown>,
|
|
33
|
+
rows: unknown[],
|
|
34
|
+
): void => {
|
|
35
|
+
onMutation?.({
|
|
36
|
+
kind,
|
|
37
|
+
table: tableName,
|
|
38
|
+
args,
|
|
39
|
+
rows,
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const tableApi = {
|
|
44
|
+
findMany: (args?: Record<string, unknown>) => {
|
|
45
|
+
const where = isRecord(args?.where)
|
|
46
|
+
? (args?.where as Record<string, unknown>)
|
|
47
|
+
: undefined;
|
|
48
|
+
const whereFilter = buildWhereFilter(table, where, tableName);
|
|
49
|
+
const withValue = args?.with;
|
|
50
|
+
const transformedWithResult =
|
|
51
|
+
withValue === undefined
|
|
52
|
+
? {
|
|
53
|
+
with: undefined,
|
|
54
|
+
aggregatePlan: undefined,
|
|
55
|
+
}
|
|
56
|
+
: transformWithRelationsAndExtractAggregates(withValue);
|
|
57
|
+
const transformedWith = transformedWithResult.with;
|
|
58
|
+
const aggregatePlan = transformedWithResult.aggregatePlan;
|
|
59
|
+
if (!whereFilter && transformedWith === withValue && !aggregatePlan) {
|
|
60
|
+
return queryTable.findMany(args);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const queryPromise = queryTable.findMany({
|
|
64
|
+
...(args ?? {}),
|
|
65
|
+
...(whereFilter ? { where: () => whereFilter } : {}),
|
|
66
|
+
...(transformedWith !== undefined ? { with: transformedWith } : {}),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (!aggregatePlan) {
|
|
70
|
+
return queryPromise;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return queryPromise.then((result) =>
|
|
74
|
+
applyRelationAggregatePlanToResult(result, aggregatePlan),
|
|
75
|
+
);
|
|
76
|
+
},
|
|
77
|
+
findFirst: (args?: Record<string, unknown>) => {
|
|
78
|
+
const where = isRecord(args?.where)
|
|
79
|
+
? (args?.where as Record<string, unknown>)
|
|
80
|
+
: undefined;
|
|
81
|
+
const whereFilter = buildWhereFilter(table, where, tableName);
|
|
82
|
+
const withValue = args?.with;
|
|
83
|
+
const transformedWithResult =
|
|
84
|
+
withValue === undefined
|
|
85
|
+
? {
|
|
86
|
+
with: undefined,
|
|
87
|
+
aggregatePlan: undefined,
|
|
88
|
+
}
|
|
89
|
+
: transformWithRelationsAndExtractAggregates(withValue);
|
|
90
|
+
const transformedWith = transformedWithResult.with;
|
|
91
|
+
const aggregatePlan = transformedWithResult.aggregatePlan;
|
|
92
|
+
if (!whereFilter && transformedWith === withValue && !aggregatePlan) {
|
|
93
|
+
return queryTable.findFirst(args);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const queryPromise = queryTable.findFirst({
|
|
97
|
+
...(args ?? {}),
|
|
98
|
+
...(whereFilter ? { where: () => whereFilter } : {}),
|
|
99
|
+
...(transformedWith !== undefined ? { with: transformedWith } : {}),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (!aggregatePlan) {
|
|
103
|
+
return queryPromise;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return queryPromise.then((result) =>
|
|
107
|
+
applyRelationAggregatePlanToResult(result, aggregatePlan),
|
|
108
|
+
);
|
|
109
|
+
},
|
|
110
|
+
insert: async (args: QueryInsertArgs<TableName>) => {
|
|
111
|
+
let insertQuery: any = ($db as any)
|
|
112
|
+
.insert(table as any)
|
|
113
|
+
.values(args.values as any);
|
|
114
|
+
|
|
115
|
+
if (typeof insertQuery.returning === "function") {
|
|
116
|
+
insertQuery = insertQuery.returning();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const rows = (await insertQuery) as Array<TableModel<TableName>>;
|
|
120
|
+
emitMutation(
|
|
121
|
+
"insert",
|
|
122
|
+
{ values: args.values as unknown as Record<string, unknown> },
|
|
123
|
+
rows,
|
|
124
|
+
);
|
|
125
|
+
return rows;
|
|
126
|
+
},
|
|
127
|
+
update: async (args: QueryUpdateArgs<TableName>) => {
|
|
128
|
+
const whereFilter = buildWhereFilter(
|
|
129
|
+
table,
|
|
130
|
+
args.where as Record<string, unknown> | undefined,
|
|
131
|
+
tableName,
|
|
132
|
+
);
|
|
133
|
+
let updateQuery: any = ($db as any)
|
|
134
|
+
.update(table as any)
|
|
135
|
+
.set(args.set as any);
|
|
136
|
+
|
|
137
|
+
if (whereFilter) {
|
|
138
|
+
updateQuery = updateQuery.where(whereFilter);
|
|
139
|
+
}
|
|
140
|
+
if (typeof args.limit === "number" && typeof updateQuery.limit === "function") {
|
|
141
|
+
updateQuery = updateQuery.limit(args.limit);
|
|
142
|
+
}
|
|
143
|
+
if (typeof updateQuery.returning === "function") {
|
|
144
|
+
updateQuery = updateQuery.returning();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const rows = (await updateQuery) as Array<TableModel<TableName>>;
|
|
148
|
+
emitMutation(
|
|
149
|
+
"update",
|
|
150
|
+
{
|
|
151
|
+
set: args.set as unknown as Record<string, unknown>,
|
|
152
|
+
where: (args.where ?? undefined) as unknown as Record<string, unknown>,
|
|
153
|
+
limit: args.limit,
|
|
154
|
+
},
|
|
155
|
+
rows,
|
|
156
|
+
);
|
|
157
|
+
return rows;
|
|
158
|
+
},
|
|
159
|
+
upsert: async (args: QueryUpsertArgs<TableName>) => {
|
|
160
|
+
const valuesArray = Array.isArray(args.values)
|
|
161
|
+
? args.values
|
|
162
|
+
: [args.values];
|
|
163
|
+
const values = Array.isArray(args.values)
|
|
164
|
+
? args.values
|
|
165
|
+
: args.values;
|
|
166
|
+
|
|
167
|
+
const targets = args.target
|
|
168
|
+
? Array.isArray(args.target)
|
|
169
|
+
? args.target
|
|
170
|
+
: [args.target]
|
|
171
|
+
: inferConflictTarget(table);
|
|
172
|
+
|
|
173
|
+
if (targets.length === 0) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
"Unable to infer conflict target for table " + tableName + ". Provide target explicitly.",
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const tableColumns = getTableColumns(table as never) as Record<string, unknown>;
|
|
180
|
+
const targetColumns = targets
|
|
181
|
+
.map((target) => tableColumns[target])
|
|
182
|
+
.filter(Boolean);
|
|
183
|
+
|
|
184
|
+
if (targetColumns.length === 0) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
"Invalid conflict target for table " + tableName + ".",
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const setPayload = args.set ?? valuesArray[0] ?? {};
|
|
191
|
+
let upsertQuery: any = ($db as any)
|
|
192
|
+
.insert(table as any)
|
|
193
|
+
.values(values as any)
|
|
194
|
+
.onConflictDoUpdate({
|
|
195
|
+
target: targetColumns as any,
|
|
196
|
+
set: setPayload as any,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
if (typeof upsertQuery.returning === "function") {
|
|
200
|
+
upsertQuery = upsertQuery.returning();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const rows = (await upsertQuery) as Array<TableModel<TableName>>;
|
|
204
|
+
emitMutation(
|
|
205
|
+
"upsert",
|
|
206
|
+
{
|
|
207
|
+
values: args.values as unknown as Record<string, unknown>,
|
|
208
|
+
target: targets as unknown as Record<string, unknown>,
|
|
209
|
+
set: (args.set ?? undefined) as unknown as Record<string, unknown>,
|
|
210
|
+
},
|
|
211
|
+
rows,
|
|
212
|
+
);
|
|
213
|
+
return rows;
|
|
214
|
+
},
|
|
215
|
+
delete: async (args?: QueryDeleteArgs<TableName>) => {
|
|
216
|
+
const whereFilter = buildWhereFilter(
|
|
217
|
+
table,
|
|
218
|
+
args?.where as Record<string, unknown> | undefined,
|
|
219
|
+
tableName,
|
|
220
|
+
);
|
|
221
|
+
let deleteQuery: any = ($db as any).delete(table as any);
|
|
222
|
+
|
|
223
|
+
if (whereFilter) {
|
|
224
|
+
deleteQuery = deleteQuery.where(whereFilter);
|
|
225
|
+
}
|
|
226
|
+
if (typeof args?.limit === "number" && typeof deleteQuery.limit === "function") {
|
|
227
|
+
deleteQuery = deleteQuery.limit(args.limit);
|
|
228
|
+
}
|
|
229
|
+
if (typeof deleteQuery.returning === "function") {
|
|
230
|
+
deleteQuery = deleteQuery.returning();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const rows = (await deleteQuery) as Array<TableModel<TableName>>;
|
|
234
|
+
emitMutation(
|
|
235
|
+
"delete",
|
|
236
|
+
{
|
|
237
|
+
where: (args?.where ?? undefined) as unknown as Record<string, unknown>,
|
|
238
|
+
limit: args?.limit,
|
|
239
|
+
},
|
|
240
|
+
rows,
|
|
241
|
+
);
|
|
242
|
+
return rows;
|
|
243
|
+
},
|
|
244
|
+
count: async (args?: QueryCountArgs<TableName>) => {
|
|
245
|
+
const withValue = args?.with;
|
|
246
|
+
const pathSegments = args?.field
|
|
247
|
+
? splitAggregateFieldPath(String(args.field))
|
|
248
|
+
: [];
|
|
249
|
+
if (args?.field && pathSegments.length === 0) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
"Invalid count field for table " + tableName + ": " + String(args.field),
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (withValue !== undefined || pathSegments.length > 1) {
|
|
256
|
+
const whereFilter = buildWhereFilter(
|
|
257
|
+
table,
|
|
258
|
+
args?.where as Record<string, unknown> | undefined,
|
|
259
|
+
tableName,
|
|
260
|
+
);
|
|
261
|
+
const transformedWith =
|
|
262
|
+
withValue === undefined
|
|
263
|
+
? undefined
|
|
264
|
+
: transformWithRelations(withValue);
|
|
265
|
+
const rawRows = await queryTable.findMany({
|
|
266
|
+
...(whereFilter ? { where: () => whereFilter } : {}),
|
|
267
|
+
...(transformedWith !== undefined ? { with: transformedWith } : {}),
|
|
268
|
+
});
|
|
269
|
+
const rows = Array.isArray(rawRows) ? rawRows : [];
|
|
270
|
+
const constrainedRows = hasAggregateWithConstraints(withValue)
|
|
271
|
+
? rows.filter((row) =>
|
|
272
|
+
rowMatchesAggregateWithConstraints(row, withValue),
|
|
273
|
+
)
|
|
274
|
+
: rows;
|
|
275
|
+
|
|
276
|
+
if (!args?.field) {
|
|
277
|
+
return constrainedRows.length;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const values = constrainedRows.flatMap((row) =>
|
|
281
|
+
collectValuesFromPath(row, pathSegments),
|
|
282
|
+
);
|
|
283
|
+
const useDistinct = args?.distinct ?? withValue !== undefined;
|
|
284
|
+
return countAggregateValues(values, useDistinct);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const whereFilter = buildWhereFilter(
|
|
288
|
+
table,
|
|
289
|
+
args?.where as Record<string, unknown> | undefined,
|
|
290
|
+
tableName,
|
|
291
|
+
);
|
|
292
|
+
const tableColumns = getTableColumns(table as never) as Record<string, unknown>;
|
|
293
|
+
const selectedField = args?.field
|
|
294
|
+
? tableColumns[String(args.field)]
|
|
295
|
+
: undefined;
|
|
296
|
+
|
|
297
|
+
if (args?.field && !selectedField) {
|
|
298
|
+
throw new Error(
|
|
299
|
+
"Invalid count field for table " + tableName + ": " + args.field,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let expression: SQL;
|
|
304
|
+
if (selectedField) {
|
|
305
|
+
expression = args?.distinct
|
|
306
|
+
? sql\`count(distinct \${selectedField as never})\`
|
|
307
|
+
: sql\`count(\${selectedField as never})\`;
|
|
308
|
+
} else {
|
|
309
|
+
expression = sql\`count(*)\`;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let query = ($db as any)
|
|
313
|
+
.select({ value: expression })
|
|
314
|
+
.from(table as any);
|
|
315
|
+
|
|
316
|
+
if (whereFilter) {
|
|
317
|
+
query = query.where(whereFilter);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const rows = (await query) as Array<{ value: unknown }>;
|
|
321
|
+
const value = Number(rows[0]?.value ?? 0);
|
|
322
|
+
return Number.isFinite(value) ? value : 0;
|
|
323
|
+
},
|
|
324
|
+
avg: async (args: QueryAvgArgs<TableName>) => {
|
|
325
|
+
const withValue = args.with;
|
|
326
|
+
const pathSegments = splitAggregateFieldPath(String(args.field));
|
|
327
|
+
if (pathSegments.length === 0) {
|
|
328
|
+
throw new Error(
|
|
329
|
+
"Invalid avg field for table " + tableName + ": " + String(args.field),
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (withValue !== undefined || pathSegments.length > 1) {
|
|
334
|
+
const whereFilter = buildWhereFilter(
|
|
335
|
+
table,
|
|
336
|
+
args.where as Record<string, unknown> | undefined,
|
|
337
|
+
tableName,
|
|
338
|
+
);
|
|
339
|
+
const transformedWith =
|
|
340
|
+
withValue === undefined
|
|
341
|
+
? undefined
|
|
342
|
+
: transformWithRelations(withValue);
|
|
343
|
+
const rawRows = await queryTable.findMany({
|
|
344
|
+
...(whereFilter ? { where: () => whereFilter } : {}),
|
|
345
|
+
...(transformedWith !== undefined ? { with: transformedWith } : {}),
|
|
346
|
+
});
|
|
347
|
+
const rows = Array.isArray(rawRows) ? rawRows : [];
|
|
348
|
+
const constrainedRows = hasAggregateWithConstraints(withValue)
|
|
349
|
+
? rows.filter((row) =>
|
|
350
|
+
rowMatchesAggregateWithConstraints(row, withValue),
|
|
351
|
+
)
|
|
352
|
+
: rows;
|
|
353
|
+
const numericValues = constrainedRows
|
|
354
|
+
.flatMap((row) => collectValuesFromPath(row, pathSegments))
|
|
355
|
+
.map((value) => toFiniteNumber(value))
|
|
356
|
+
.filter((value): value is number => value !== null);
|
|
357
|
+
const values = args.distinct
|
|
358
|
+
? Array.from(new Set<number>(numericValues))
|
|
359
|
+
: numericValues;
|
|
360
|
+
return averageAggregateValues(values);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const whereFilter = buildWhereFilter(
|
|
364
|
+
table,
|
|
365
|
+
args.where as Record<string, unknown> | undefined,
|
|
366
|
+
tableName,
|
|
367
|
+
);
|
|
368
|
+
const tableColumns = getTableColumns(table as never) as Record<string, unknown>;
|
|
369
|
+
const selectedField = tableColumns[String(args.field)];
|
|
370
|
+
|
|
371
|
+
if (!selectedField) {
|
|
372
|
+
throw new Error(
|
|
373
|
+
"Invalid avg field for table " + tableName + ": " + String(args.field),
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const expression = args.distinct
|
|
378
|
+
? sql\`avg(distinct \${selectedField as never})\`
|
|
379
|
+
: sql\`avg(\${selectedField as never})\`;
|
|
380
|
+
|
|
381
|
+
let query = ($db as any)
|
|
382
|
+
.select({ value: expression })
|
|
383
|
+
.from(table as any);
|
|
384
|
+
|
|
385
|
+
if (whereFilter) {
|
|
386
|
+
query = query.where(whereFilter);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const rows = (await query) as Array<{ value: unknown }>;
|
|
390
|
+
const rawValue = rows[0]?.value;
|
|
391
|
+
if (rawValue === null || rawValue === undefined) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const value = Number(rawValue);
|
|
396
|
+
return Number.isFinite(value) ? value : null;
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
cache.set(tableName, tableApi);
|
|
401
|
+
return tableApi;
|
|
402
|
+
},
|
|
403
|
+
}) as AppflareQueryDb;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export class AppflareHandledError extends Error {
|
|
407
|
+
public readonly status: number;
|
|
408
|
+
public readonly payload: unknown;
|
|
409
|
+
|
|
410
|
+
public constructor(status: number, payload: unknown) {
|
|
411
|
+
super(typeof payload === "object" ? "Handled error" : String(payload));
|
|
412
|
+
this.status = status;
|
|
413
|
+
this.payload = payload;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
`;
|
|
417
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { DiscoveredHandlerOperation } from "../../utils/handler-discovery";
|
|
2
|
+
import { generateContextSource } from "./generators/context";
|
|
3
|
+
import { generateExecutionSource } from "./generators/execution";
|
|
4
|
+
import { generateHandlersSource } from "./generators/handlers";
|
|
5
|
+
import { generateRegistration } from "./registration";
|
|
6
|
+
|
|
7
|
+
export type GeneratedHandlerArtifact = {
|
|
8
|
+
relativePath: string;
|
|
9
|
+
source: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function generateHandlersArtifacts(
|
|
13
|
+
schemaImportPath: string,
|
|
14
|
+
operations: DiscoveredHandlerOperation[],
|
|
15
|
+
defaultR2Binding?: string,
|
|
16
|
+
): GeneratedHandlerArtifact[] {
|
|
17
|
+
const handlersSource = generateHandlersSource(schemaImportPath);
|
|
18
|
+
|
|
19
|
+
const contextSource = generateContextSource(defaultR2Binding);
|
|
20
|
+
|
|
21
|
+
const executionSource = generateExecutionSource();
|
|
22
|
+
|
|
23
|
+
const routesSource = generateRegistration(operations);
|
|
24
|
+
|
|
25
|
+
return [
|
|
26
|
+
{
|
|
27
|
+
relativePath: "handlers.ts",
|
|
28
|
+
source: handlersSource,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
relativePath: "handlers.context.ts",
|
|
32
|
+
source: contextSource,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
relativePath: "handlers.execution.ts",
|
|
36
|
+
source: executionSource,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
relativePath: "handlers.routes.ts",
|
|
40
|
+
source: routesSource,
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {
|
|
2
|
+
toPascalCase,
|
|
3
|
+
singularize,
|
|
4
|
+
zodForColumn,
|
|
5
|
+
zodForQueryColumn,
|
|
6
|
+
} from "./utils";
|
|
7
|
+
import type {
|
|
8
|
+
DiscoveredColumn,
|
|
9
|
+
DiscoveredTable,
|
|
10
|
+
} from "../../utils/schema-discovery";
|
|
11
|
+
|
|
12
|
+
function createArgsShape(columns: DiscoveredColumn[]): string {
|
|
13
|
+
const creatableColumns = columns.filter(
|
|
14
|
+
(column) => !(column.primaryKey && column.autoIncrement),
|
|
15
|
+
);
|
|
16
|
+
if (creatableColumns.length === 0) {
|
|
17
|
+
return "{}";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return `{
|
|
21
|
+
${creatableColumns.map((column) => `\t\t${column.name}: ${zodForColumn(column)},`).join("\n")}
|
|
22
|
+
\t}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function createInsertValues(columns: DiscoveredColumn[]): string {
|
|
26
|
+
const creatableColumns = columns.filter(
|
|
27
|
+
(column) => !(column.primaryKey && column.autoIncrement),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (creatableColumns.length === 0) {
|
|
31
|
+
return "{}";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return `{
|
|
35
|
+
${creatableColumns.map((column) => `\t\t\t${column.name}: args.${column.name},`).join("\n")}
|
|
36
|
+
\t\t}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createQueryBlock(table: DiscoveredTable): string {
|
|
40
|
+
const idColumn = table.columns.find((column) => column.name === "id");
|
|
41
|
+
const queryName = `get${toPascalCase(table.exportName)}`;
|
|
42
|
+
const args = idColumn
|
|
43
|
+
? `{
|
|
44
|
+
id: ${zodForQueryColumn(idColumn)},
|
|
45
|
+
\t}`
|
|
46
|
+
: "{}";
|
|
47
|
+
const queryBody = idColumn
|
|
48
|
+
? `if (args.id !== undefined) {
|
|
49
|
+
return ctx.$db.select().from(schema.${table.exportName}).where(eq(schema.${table.exportName}.id, args.id));
|
|
50
|
+
\t\t}
|
|
51
|
+
\n\t\treturn ctx.$db.select().from(schema.${table.exportName});`
|
|
52
|
+
: `return ctx.$db.select().from(schema.${table.exportName});`;
|
|
53
|
+
|
|
54
|
+
return `export const ${queryName} = query({
|
|
55
|
+
\targs: ${args},
|
|
56
|
+
\thandler: async (ctx, args) => {
|
|
57
|
+
\t\t${queryBody}
|
|
58
|
+
\t},
|
|
59
|
+
});`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createMutationBlock(table: DiscoveredTable): string {
|
|
63
|
+
const mutationName = `create${toPascalCase(singularize(table.exportName))}`;
|
|
64
|
+
const argsShape = createArgsShape(table.columns);
|
|
65
|
+
const values = createInsertValues(table.columns);
|
|
66
|
+
|
|
67
|
+
return `export const ${mutationName} = mutation({
|
|
68
|
+
\targs: ${argsShape},
|
|
69
|
+
\thandler: async (ctx, args) => {
|
|
70
|
+
const [created] = await ctx.$db
|
|
71
|
+
\t\t\t.insert(schema.${table.exportName})
|
|
72
|
+
\t\t\t.values(${values})
|
|
73
|
+
\t\t\t.returning();
|
|
74
|
+
|
|
75
|
+
\n\t\treturn created ?? null;
|
|
76
|
+
\t},
|
|
77
|
+
});`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function createRegistry(tableNames: string[]): string {
|
|
81
|
+
const queryEntries = tableNames.map((name) => {
|
|
82
|
+
return `\t${`get${toPascalCase(name)}`},`;
|
|
83
|
+
});
|
|
84
|
+
const mutationEntries = tableNames.map((name) => {
|
|
85
|
+
return `\t${`create${toPascalCase(singularize(name))}`},`;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return `const generatedQueries = {
|
|
89
|
+
${queryEntries.join("\n")}
|
|
90
|
+
} as const;
|
|
91
|
+
|
|
92
|
+
const generatedMutations = {
|
|
93
|
+
${mutationEntries.join("\n")}
|
|
94
|
+
} as const;`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function generateOperations(tables: DiscoveredTable[]): string {
|
|
98
|
+
if (tables.length === 0) {
|
|
99
|
+
return `const generatedQueries = {} as const;
|
|
100
|
+
const generatedMutations = {} as const;`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const queryBlocks = tables
|
|
104
|
+
.map((table) => createQueryBlock(table))
|
|
105
|
+
.join("\n\n");
|
|
106
|
+
const mutationBlocks = tables
|
|
107
|
+
.map((table) => createMutationBlock(table))
|
|
108
|
+
.join("\n\n");
|
|
109
|
+
const registry = createRegistry(tables.map((table) => table.exportName));
|
|
110
|
+
|
|
111
|
+
return `${queryBlocks}
|
|
112
|
+
|
|
113
|
+
${mutationBlocks}
|
|
114
|
+
|
|
115
|
+
${registry}`;
|
|
116
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { DiscoveredHandlerOperation } from "../../utils/handler-discovery";
|
|
2
|
+
import { buildRegistrationTemplateSections } from "./generators/registration/sections";
|
|
3
|
+
import { realtimeModule } from "./generators/registration/modules/realtime";
|
|
4
|
+
import { storageModule } from "./generators/registration/modules/storage";
|
|
5
|
+
import { schedulerModule } from "./generators/registration/modules/scheduler";
|
|
6
|
+
import { cronModule } from "./generators/registration/modules/cron";
|
|
7
|
+
|
|
8
|
+
export function generateRegistration(
|
|
9
|
+
operations: DiscoveredHandlerOperation[],
|
|
10
|
+
): string {
|
|
11
|
+
const {
|
|
12
|
+
imports,
|
|
13
|
+
operationSchemas,
|
|
14
|
+
schedulerSchemas,
|
|
15
|
+
queryRoutes,
|
|
16
|
+
mutationRoutes,
|
|
17
|
+
queryRegistryEntries,
|
|
18
|
+
schedulerEntries,
|
|
19
|
+
schedulerPayloadMapEntries,
|
|
20
|
+
cronEntries,
|
|
21
|
+
storageHandlersEntries,
|
|
22
|
+
} = buildRegistrationTemplateSections(operations);
|
|
23
|
+
|
|
24
|
+
return `import { sValidator } from "@hono/standard-validator";
|
|
25
|
+
import type { Hono } from "hono";
|
|
26
|
+
import type { D1Database, IncomingRequestCfProperties, KVNamespace } from "@cloudflare/workers-types";
|
|
27
|
+
import { ZodError, z } from "zod";
|
|
28
|
+
import {
|
|
29
|
+
AppflareHandledError,
|
|
30
|
+
type AppflareContext,
|
|
31
|
+
type DbMutationEvent,
|
|
32
|
+
createDb,
|
|
33
|
+
createQueryDb,
|
|
34
|
+
setStorageHandlers,
|
|
35
|
+
type RegisterHandlersOptions,
|
|
36
|
+
type StorageMethod,
|
|
37
|
+
type WorkerEnv,
|
|
38
|
+
} from "./handlers";
|
|
39
|
+
import { createExecutionContext, createSchedulerExecutionContext, resolveSession } from "./handlers.context";
|
|
40
|
+
import { executeOperation, handleOperationError } from "./handlers.execution";
|
|
41
|
+
${imports ? `\n${imports}` : ""}
|
|
42
|
+
|
|
43
|
+
${operationSchemas}
|
|
44
|
+
${schedulerSchemas}
|
|
45
|
+
|
|
46
|
+
const realtimeQueryHandlers = {${queryRegistryEntries || "\n"}
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
const schedulerHandlers = {${schedulerEntries || "\n"}
|
|
50
|
+
} as const;
|
|
51
|
+
|
|
52
|
+
type GeneratedSchedulerPayloadMap = {${
|
|
53
|
+
schedulerPayloadMapEntries ? `\n${schedulerPayloadMapEntries}\n` : ""
|
|
54
|
+
}};
|
|
55
|
+
|
|
56
|
+
declare global {
|
|
57
|
+
interface AppflareSchedulerHandlerMap extends GeneratedSchedulerPayloadMap {}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const cronHandlers = [${cronEntries || "\n"}
|
|
61
|
+
] as const;
|
|
62
|
+
|
|
63
|
+
const storageHandlers = [${storageHandlersEntries || "\n"}
|
|
64
|
+
] as const;
|
|
65
|
+
|
|
66
|
+
setStorageHandlers([...storageHandlers]);
|
|
67
|
+
|
|
68
|
+
${schedulerModule}
|
|
69
|
+
|
|
70
|
+
${realtimeModule}
|
|
71
|
+
|
|
72
|
+
export function registerGeneratedHandlers(
|
|
73
|
+
app: Hono<WorkerEnv>,
|
|
74
|
+
options: RegisterHandlersOptions,
|
|
75
|
+
): void {
|
|
76
|
+
registerRealtimeRoutes(app, options);${queryRoutes || "\n\t// No query handlers discovered under scanDir/queries.\n"}${mutationRoutes || "\n\t// No mutation handlers discovered under scanDir/mutations.\n"}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
${storageModule}
|
|
80
|
+
|
|
81
|
+
${cronModule}
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { generateTypesCoreSection } from "./generators/types/core";
|
|
2
|
+
import { generateTypesQueryDefinitionsSection } from "./generators/types/query-definitions";
|
|
3
|
+
import { generateTypesQueryRuntimeSection } from "./generators/types/query-runtime";
|
|
4
|
+
import { generateTypesContextSection } from "./generators/types/context";
|
|
5
|
+
import { generateTypesOperationsSection } from "./generators/types/operations";
|
|
6
|
+
|
|
7
|
+
export function generateTypes(): string {
|
|
8
|
+
return [
|
|
9
|
+
generateTypesCoreSection(),
|
|
10
|
+
generateTypesQueryDefinitionsSection(),
|
|
11
|
+
generateTypesQueryRuntimeSection(),
|
|
12
|
+
generateTypesContextSection(),
|
|
13
|
+
generateTypesOperationsSection(),
|
|
14
|
+
].join("\n\n");
|
|
15
|
+
}
|