appflare 0.0.28 → 0.1.0

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.
Files changed (141) hide show
  1. package/cli/commands/index.ts +140 -0
  2. package/cli/generate.ts +149 -0
  3. package/cli/index.ts +56 -447
  4. package/cli/load-config.ts +182 -0
  5. package/cli/schema-compiler.ts +657 -0
  6. package/cli/templates/auth/README.md +156 -0
  7. package/cli/templates/auth/config.ts +61 -0
  8. package/cli/templates/auth/route-config.ts +18 -0
  9. package/cli/templates/auth/route-handler.ts +18 -0
  10. package/cli/templates/auth/route-request-utils.ts +55 -0
  11. package/cli/templates/auth/route.ts +14 -0
  12. package/cli/templates/core/README.md +266 -0
  13. package/cli/templates/core/app-creation.ts +19 -0
  14. package/cli/templates/core/client/appflare.ts +37 -0
  15. package/cli/templates/core/client/index.ts +6 -0
  16. package/cli/templates/core/client/storage.ts +100 -0
  17. package/cli/templates/core/client/types.ts +54 -0
  18. package/cli/templates/core/client-modules/appflare.ts +112 -0
  19. package/cli/templates/core/client-modules/handlers/index.ts +740 -0
  20. package/cli/templates/core/client-modules/handlers.ts +1 -0
  21. package/cli/templates/core/client-modules/index.ts +7 -0
  22. package/cli/templates/core/client-modules/storage.ts +180 -0
  23. package/cli/templates/core/client-modules/types.ts +145 -0
  24. package/cli/templates/core/client.ts +39 -0
  25. package/cli/templates/core/drizzle.ts +15 -0
  26. package/cli/templates/core/export.ts +14 -0
  27. package/cli/templates/core/handlers-route.ts +23 -0
  28. package/cli/templates/core/handlers.ts +1 -0
  29. package/cli/templates/core/imports.ts +8 -0
  30. package/cli/templates/core/server.ts +38 -0
  31. package/cli/templates/core/types.ts +6 -0
  32. package/cli/templates/core/wrangler.ts +109 -0
  33. package/cli/templates/handlers/README.md +265 -0
  34. package/cli/templates/handlers/auth.ts +36 -0
  35. package/cli/templates/handlers/execution.ts +39 -0
  36. package/cli/templates/handlers/generators/context/context-creation.ts +80 -0
  37. package/cli/templates/handlers/generators/context/error-helpers.ts +11 -0
  38. package/cli/templates/handlers/generators/context/scheduler.ts +24 -0
  39. package/cli/templates/handlers/generators/context/storage-api.ts +112 -0
  40. package/cli/templates/handlers/generators/context/storage-helpers.ts +59 -0
  41. package/cli/templates/handlers/generators/context/types.ts +18 -0
  42. package/cli/templates/handlers/generators/context.ts +43 -0
  43. package/cli/templates/handlers/generators/execution.ts +15 -0
  44. package/cli/templates/handlers/generators/handlers.ts +13 -0
  45. package/cli/templates/handlers/index.ts +43 -0
  46. package/cli/templates/handlers/operations.ts +116 -0
  47. package/cli/templates/handlers/registration.ts +1114 -0
  48. package/cli/templates/handlers/types.ts +960 -0
  49. package/cli/templates/handlers/utils.ts +48 -0
  50. package/cli/types.ts +108 -0
  51. package/cli/utils/handler-discovery.ts +366 -0
  52. package/cli/utils/json-utils.ts +24 -0
  53. package/cli/utils/path-utils.ts +19 -0
  54. package/cli/utils/schema-discovery.ts +390 -0
  55. package/index.ts +27 -4
  56. package/package.json +23 -20
  57. package/react/index.ts +5 -3
  58. package/react/use-infinite-query.ts +190 -0
  59. package/react/use-mutation.ts +54 -0
  60. package/react/use-query.ts +158 -0
  61. package/schema.ts +262 -0
  62. package/tsconfig.json +2 -4
  63. package/cli/README.md +0 -108
  64. package/cli/core/build.ts +0 -187
  65. package/cli/core/config.ts +0 -92
  66. package/cli/core/discover-handlers.ts +0 -143
  67. package/cli/core/handlers.ts +0 -7
  68. package/cli/core/index.ts +0 -205
  69. package/cli/generators/generate-api-client/client.ts +0 -163
  70. package/cli/generators/generate-api-client/extract-configuration.ts +0 -121
  71. package/cli/generators/generate-api-client/index.ts +0 -973
  72. package/cli/generators/generate-api-client/types.ts +0 -164
  73. package/cli/generators/generate-api-client/utils.ts +0 -22
  74. package/cli/generators/generate-api-client.ts +0 -1
  75. package/cli/generators/generate-cloudflare-worker/helpers.ts +0 -24
  76. package/cli/generators/generate-cloudflare-worker/index.ts +0 -2
  77. package/cli/generators/generate-cloudflare-worker/worker.ts +0 -148
  78. package/cli/generators/generate-cloudflare-worker/wrangler.ts +0 -108
  79. package/cli/generators/generate-cloudflare-worker.ts +0 -4
  80. package/cli/generators/generate-cron-handlers/cron-handlers-block.ts +0 -2
  81. package/cli/generators/generate-cron-handlers/handler-entries.ts +0 -29
  82. package/cli/generators/generate-cron-handlers/index.ts +0 -61
  83. package/cli/generators/generate-cron-handlers/runtime-block.ts +0 -49
  84. package/cli/generators/generate-cron-handlers/type-helpers-block.ts +0 -60
  85. package/cli/generators/generate-db-handlers/index.ts +0 -33
  86. package/cli/generators/generate-db-handlers/prepare.ts +0 -24
  87. package/cli/generators/generate-db-handlers/templates.ts +0 -189
  88. package/cli/generators/generate-db-handlers.ts +0 -1
  89. package/cli/generators/generate-hono-server/auth.ts +0 -97
  90. package/cli/generators/generate-hono-server/imports.ts +0 -55
  91. package/cli/generators/generate-hono-server/index.ts +0 -52
  92. package/cli/generators/generate-hono-server/routes.ts +0 -115
  93. package/cli/generators/generate-hono-server/template.ts +0 -371
  94. package/cli/generators/generate-hono-server.ts +0 -1
  95. package/cli/generators/generate-scheduler-handlers/constants.ts +0 -8
  96. package/cli/generators/generate-scheduler-handlers/handler-entries.ts +0 -22
  97. package/cli/generators/generate-scheduler-handlers/index.ts +0 -51
  98. package/cli/generators/generate-scheduler-handlers/runtime-block.ts +0 -68
  99. package/cli/generators/generate-scheduler-handlers/scheduler-handlers-block.ts +0 -2
  100. package/cli/generators/generate-scheduler-handlers/type-helpers-block.ts +0 -68
  101. package/cli/generators/generate-scheduler-handlers.ts +0 -1
  102. package/cli/generators/generate-websocket-durable-object/auth.ts +0 -30
  103. package/cli/generators/generate-websocket-durable-object/imports.ts +0 -55
  104. package/cli/generators/generate-websocket-durable-object/index.ts +0 -41
  105. package/cli/generators/generate-websocket-durable-object/query-handlers.ts +0 -18
  106. package/cli/generators/generate-websocket-durable-object/template.ts +0 -714
  107. package/cli/generators/generate-websocket-durable-object.ts +0 -1
  108. package/cli/schema/schema-static-types.ts +0 -702
  109. package/cli/schema/schema.ts +0 -151
  110. package/cli/utils/tsc.ts +0 -54
  111. package/cli/utils/utils.ts +0 -190
  112. package/cli/utils/zod-utils.ts +0 -121
  113. package/lib/README.md +0 -50
  114. package/lib/db.ts +0 -19
  115. package/lib/location.ts +0 -110
  116. package/lib/values.ts +0 -27
  117. package/react/README.md +0 -67
  118. package/react/hooks/useMutation.ts +0 -89
  119. package/react/hooks/usePaginatedQuery.ts +0 -213
  120. package/react/hooks/useQuery.ts +0 -106
  121. package/react/shared/queryShared.ts +0 -174
  122. package/server/README.md +0 -218
  123. package/server/auth.ts +0 -107
  124. package/server/database/builders.ts +0 -83
  125. package/server/database/context.ts +0 -327
  126. package/server/database/populate.ts +0 -234
  127. package/server/database/query-builder.ts +0 -161
  128. package/server/database/query-utils.ts +0 -25
  129. package/server/db.ts +0 -2
  130. package/server/storage/auth.ts +0 -16
  131. package/server/storage/bucket.ts +0 -22
  132. package/server/storage/context.ts +0 -34
  133. package/server/storage/index.ts +0 -38
  134. package/server/storage/operations.ts +0 -149
  135. package/server/storage/route-handler.ts +0 -60
  136. package/server/storage/types.ts +0 -55
  137. package/server/storage/utils.ts +0 -47
  138. package/server/storage.ts +0 -6
  139. package/server/types/schema-refs.ts +0 -66
  140. package/server/types/types.ts +0 -633
  141. package/server/utils/id-utils.ts +0 -230
@@ -0,0 +1,960 @@
1
+ export function generateTypes(): string {
2
+ return `
3
+ import { betterAuth } from "better-auth";
4
+ import { auth } from "./auth.config";
5
+ import {
6
+ and,
7
+ eq,
8
+ ne,
9
+ inArray,
10
+ notInArray,
11
+ gt,
12
+ gte,
13
+ lt,
14
+ lte,
15
+ isNull,
16
+ isNotNull,
17
+ getTableColumns,
18
+ sql,
19
+ type InferInsertModel,
20
+ type InferSelectModel,
21
+ type SQL,
22
+ } from "drizzle-orm";
23
+
24
+ export type WorkerEnv = {
25
+ Bindings: Record<string, unknown>;
26
+ };
27
+
28
+ export type RegisterHandlersOptions = {
29
+ databaseBinding: string;
30
+ kvBinding?: string;
31
+ schedulerBinding?: string;
32
+ r2Binding?: string;
33
+ realtimeBinding?: string;
34
+ realtimeObjectName?: string;
35
+ realtimeSubscribePath?: string;
36
+ realtimeWebsocketPath?: string;
37
+ realtimeProtocol?: string;
38
+ };
39
+
40
+ export type StorageMethod =
41
+ | "download"
42
+ | "get"
43
+ | "delete"
44
+ | "list"
45
+ | "put"
46
+ | "preview";
47
+
48
+ export type StorageAuthorizationArgs = {
49
+ path: string;
50
+ method: StorageMethod;
51
+ headers?: Headers;
52
+ query?: Record<string, string>;
53
+ contentType?: string;
54
+ };
55
+
56
+ export type StorageHandler = (
57
+ ctx: AppflareContext,
58
+ args: StorageAuthorizationArgs,
59
+ ) => boolean | Promise<boolean>;
60
+
61
+ export type RegisteredStorageHandler = {
62
+ kind: "storage-manager";
63
+ definition: {
64
+ handler: StorageHandler;
65
+ };
66
+ };
67
+
68
+ export function storageManager(definition: {
69
+ handler: StorageHandler;
70
+ }): RegisteredStorageHandler {
71
+ return {
72
+ kind: "storage-manager",
73
+ definition,
74
+ };
75
+ }
76
+
77
+ const storageHandlers: StorageHandler[] = [];
78
+
79
+ export function setStorageHandlers(handlers: StorageHandler[]): void {
80
+ storageHandlers.length = 0;
81
+ storageHandlers.push(...handlers);
82
+ }
83
+
84
+ export async function isStorageAllowed(
85
+ ctx: AppflareContext,
86
+ args: StorageAuthorizationArgs,
87
+ ): Promise<boolean> {
88
+ if (storageHandlers.length === 0) {
89
+ return false;
90
+ }
91
+
92
+ for (const handler of storageHandlers) {
93
+ const allowed = await handler(ctx, args);
94
+ if (allowed) {
95
+ return true;
96
+ }
97
+ }
98
+
99
+ return false;
100
+ }
101
+
102
+ const mergedSchema = {
103
+ ...authSchema,
104
+ ...schema,
105
+ };
106
+
107
+ export const createDb = (database: D1Database) =>
108
+ drizzle(database, { schema: mergedSchema });
109
+
110
+ export type AppflareDb = ReturnType<typeof createDb>;
111
+
112
+ type QueryTables = AppflareDb["query"];
113
+ type TableName = Extract<keyof QueryTables, string>;
114
+ type TableQuery<TName extends TableName> = QueryTables[TName];
115
+ type TableFindManyArgs<TName extends TableName> = TableQuery<TName> extends {
116
+ findMany: (args?: infer TArgs) => Promise<unknown>;
117
+ }
118
+ ? TArgs
119
+ : never;
120
+ type TableFindManyResult<TName extends TableName> = TableQuery<TName> extends {
121
+ findMany: (args?: unknown) => infer TResult;
122
+ }
123
+ ? TResult
124
+ : Promise<Array<TableModel<TName>>>;
125
+ type TableFindFirstArgs<TName extends TableName> = TableQuery<TName> extends {
126
+ findFirst: (args?: infer TArgs) => Promise<unknown>;
127
+ }
128
+ ? TArgs
129
+ : never;
130
+ type TableFindFirstResult<TName extends TableName> = TableQuery<TName> extends {
131
+ findFirst: (args?: unknown) => infer TResult;
132
+ }
133
+ ? TResult
134
+ : Promise<TableModel<TName> | null>;
135
+ type TableModel<TName extends TableName> = InferSelectModel<
136
+ (typeof mergedSchema)[TName]
137
+ >;
138
+ type TableInsertModel<TName extends TableName> = InferInsertModel<
139
+ (typeof mergedSchema)[TName]
140
+ >;
141
+
142
+ type Primitive = string | number | boolean | Date;
143
+ type NonNil<T> = Exclude<T, null | undefined>;
144
+ type Friendly<T> = T extends Date ? Date | number : T;
145
+ type Comparable<T> = Friendly<Extract<NonNil<T>, Primitive>>;
146
+ type RegexOperand<T> = T extends string ? string : never;
147
+
148
+ type GeoPoint = {
149
+ latitude: number;
150
+ longitude: number;
151
+ };
152
+
153
+ type GeoCoordinates = {
154
+ type?: "Point";
155
+ coordinates: [number, number] | readonly [number, number];
156
+ };
157
+
158
+ type GeoWithinOperand = {
159
+ $geometry: GeoPoint | GeoCoordinates;
160
+ latitudeField?: string;
161
+ longitudeField?: string;
162
+ $minDistance?: number;
163
+ $maxDistance?: number;
164
+ gt?: number;
165
+ gte?: number;
166
+ lt?: number;
167
+ lte?: number;
168
+ $gt?: number;
169
+ $gte?: number;
170
+ $lt?: number;
171
+ $lte?: number;
172
+ };
173
+
174
+ type FieldOperators<T> = {
175
+ eq?: Friendly<NonNil<T>>;
176
+ $eq?: Friendly<NonNil<T>>;
177
+ ne?: Friendly<NonNil<T>>;
178
+ $ne?: Friendly<NonNil<T>>;
179
+ in?: ReadonlyArray<Friendly<NonNil<T>>>;
180
+ $in?: ReadonlyArray<Friendly<NonNil<T>>>;
181
+ nin?: ReadonlyArray<Friendly<NonNil<T>>>;
182
+ $nin?: ReadonlyArray<Friendly<NonNil<T>>>;
183
+ gt?: Comparable<T>;
184
+ $gt?: Comparable<T>;
185
+ gte?: Comparable<T>;
186
+ $gte?: Comparable<T>;
187
+ lt?: Comparable<T>;
188
+ $lt?: Comparable<T>;
189
+ lte?: Comparable<T>;
190
+ $lte?: Comparable<T>;
191
+ exists?: boolean;
192
+ regex?: RegexOperand<T>;
193
+ $options?: string;
194
+ geoWithin?: GeoWithinOperand;
195
+ };
196
+
197
+ type WhereFieldValue<T> = Friendly<NonNil<T>> | FieldOperators<T>;
198
+
199
+ export type WhereInput<TModel extends Record<string, unknown>> = {
200
+ [K in keyof TModel]?: WhereFieldValue<TModel[K]>;
201
+ } & {
202
+ geoWithin?: GeoWithinOperand;
203
+ $geoWithin?: GeoWithinOperand;
204
+ };
205
+
206
+ export type QueryFindManyArgs<TName extends TableName> = Omit<
207
+ NonNullable<TableFindManyArgs<TName>>,
208
+ "where"
209
+ > & {
210
+ where?: WhereInput<TableModel<TName>>;
211
+ };
212
+
213
+ export type QueryFindFirstArgs<TName extends TableName> = Omit<
214
+ NonNullable<TableFindFirstArgs<TName>>,
215
+ "where"
216
+ > & {
217
+ where?: WhereInput<TableModel<TName>>;
218
+ };
219
+
220
+ export type QueryInsertArgs<TName extends TableName> = {
221
+ values: TableInsertModel<TName> | Array<TableInsertModel<TName>>;
222
+ };
223
+
224
+ export type QueryUpdateArgs<TName extends TableName> = {
225
+ set: Partial<TableInsertModel<TName>>;
226
+ where?: WhereInput<TableModel<TName>>;
227
+ limit?: number;
228
+ };
229
+
230
+ export type QueryDeleteArgs<TName extends TableName> = {
231
+ where?: WhereInput<TableModel<TName>>;
232
+ limit?: number;
233
+ };
234
+
235
+ export type QueryUpsertArgs<TName extends TableName> = {
236
+ values: TableInsertModel<TName> | Array<TableInsertModel<TName>>;
237
+ target?:
238
+ | Extract<keyof TableInsertModel<TName>, string>
239
+ | Array<Extract<keyof TableInsertModel<TName>, string>>;
240
+ set?: Partial<TableInsertModel<TName>>;
241
+ };
242
+
243
+ export type QueryTableApi<TName extends TableName> = {
244
+ findMany: (
245
+ args?: QueryFindManyArgs<TName>,
246
+ ) => TableFindManyResult<TName>;
247
+ findFirst: (
248
+ args?: QueryFindFirstArgs<TName>,
249
+ ) => TableFindFirstResult<TName>;
250
+ insert: (args: QueryInsertArgs<TName>) => Promise<Array<TableModel<TName>>>;
251
+ update: (args: QueryUpdateArgs<TName>) => Promise<Array<TableModel<TName>>>;
252
+ upsert: (args: QueryUpsertArgs<TName>) => Promise<Array<TableModel<TName>>>;
253
+ delete: (args?: QueryDeleteArgs<TName>) => Promise<Array<TableModel<TName>>>;
254
+ };
255
+
256
+ export type AppflareQueryDb = {
257
+ [TName in TableName]: QueryTableApi<TName>;
258
+ };
259
+
260
+ export type DbMutationKind = "insert" | "update" | "upsert" | "delete";
261
+
262
+ export type DbMutationEvent = {
263
+ kind: DbMutationKind;
264
+ table: string;
265
+ args: Record<string, unknown>;
266
+ rows: unknown[];
267
+ };
268
+
269
+ export type QueryDbOptions = {
270
+ onMutation?: (event: DbMutationEvent) => void;
271
+ };
272
+
273
+ function isRecord(value: unknown): value is Record<string, unknown> {
274
+ return typeof value === "object" && value !== null;
275
+ }
276
+
277
+ function readOperatorValue(record: Record<string, unknown>, key: string): unknown {
278
+ if (record[key] !== undefined) {
279
+ return record[key];
280
+ }
281
+ const prefixed = "$" + key;
282
+ return record[prefixed];
283
+ }
284
+
285
+ function normalizeGeoPoint(
286
+ value: GeoPoint | GeoCoordinates,
287
+ ): { latitude: number; longitude: number } | null {
288
+ if ((value as GeoPoint).latitude !== undefined) {
289
+ const point = value as GeoPoint;
290
+ if (
291
+ typeof point.latitude === "number" &&
292
+ typeof point.longitude === "number"
293
+ ) {
294
+ return {
295
+ latitude: point.latitude,
296
+ longitude: point.longitude,
297
+ };
298
+ }
299
+ return null;
300
+ }
301
+
302
+ const coordinates = (value as GeoCoordinates).coordinates;
303
+ if (!Array.isArray(coordinates) || coordinates.length < 2) {
304
+ return null;
305
+ }
306
+
307
+ const [longitude, latitude] = coordinates;
308
+ if (typeof latitude !== "number" || typeof longitude !== "number") {
309
+ return null;
310
+ }
311
+
312
+ return {
313
+ latitude,
314
+ longitude,
315
+ };
316
+ }
317
+
318
+ function createGeoDistanceFilter(
319
+ operand: GeoWithinOperand,
320
+ fields: Record<string, unknown>,
321
+ fallbackLatitudeField?: string,
322
+ ): SQL | undefined {
323
+ const geometry = normalizeGeoPoint(operand.$geometry);
324
+ if (!geometry) {
325
+ return undefined;
326
+ }
327
+
328
+ const latitudeField = operand.latitudeField ?? fallbackLatitudeField;
329
+ const longitudeField = operand.longitudeField;
330
+ if (!latitudeField || !longitudeField) {
331
+ return undefined;
332
+ }
333
+
334
+ const latitudeColumn = fields[latitudeField];
335
+ if (!latitudeColumn) {
336
+ return undefined;
337
+ }
338
+
339
+ const longitudeColumn = fields[longitudeField];
340
+ if (!longitudeColumn) {
341
+ return undefined;
342
+ }
343
+
344
+ const distanceExpression = sql<number>\`(
345
+ 6371000 * acos(
346
+ cos(radians(\${geometry.latitude})) * cos(radians(\${latitudeColumn}))
347
+ * cos(radians(\${longitudeColumn}) - radians(\${geometry.longitude}))
348
+ + sin(radians(\${geometry.latitude})) * sin(radians(\${latitudeColumn}))
349
+ )
350
+ )\`;
351
+
352
+ const minDistance =
353
+ operand.$minDistance ??
354
+ operand.gte ??
355
+ operand.$gte ??
356
+ operand.gt ??
357
+ operand.$gt;
358
+ const maxDistance =
359
+ operand.$maxDistance ??
360
+ operand.lte ??
361
+ operand.$lte ??
362
+ operand.lt ??
363
+ operand.$lt;
364
+
365
+ const filters: SQL[] = [];
366
+ if (typeof minDistance === "number") {
367
+ filters.push(sql\`\${distanceExpression} >= \${minDistance}\`);
368
+ }
369
+ if (typeof maxDistance === "number") {
370
+ filters.push(sql\`\${distanceExpression} <= \${maxDistance}\`);
371
+ }
372
+
373
+ if (filters.length === 0) {
374
+ return undefined;
375
+ }
376
+
377
+ if (filters.length === 1) {
378
+ return filters[0];
379
+ }
380
+
381
+ return and(...filters);
382
+ }
383
+
384
+ function buildFieldFilter(
385
+ fieldName: string,
386
+ field: unknown,
387
+ value: unknown,
388
+ fields: Record<string, unknown>,
389
+ ): SQL | undefined {
390
+ if (!isRecord(value) || value instanceof Date || Array.isArray(value)) {
391
+ return eq(field as never, value as never);
392
+ }
393
+
394
+ const filters: SQL[] = [];
395
+ const eqValue = readOperatorValue(value, "eq");
396
+ if (eqValue !== undefined) {
397
+ filters.push(eq(field as never, eqValue as never));
398
+ }
399
+
400
+ const neValue = readOperatorValue(value, "ne");
401
+ if (neValue !== undefined) {
402
+ filters.push(ne(field as never, neValue as never));
403
+ }
404
+
405
+ const inValue = readOperatorValue(value, "in");
406
+ if (Array.isArray(inValue) && inValue.length > 0) {
407
+ filters.push(inArray(field as never, inValue as never));
408
+ }
409
+
410
+ const ninValue = readOperatorValue(value, "nin");
411
+ if (Array.isArray(ninValue) && ninValue.length > 0) {
412
+ filters.push(notInArray(field as never, ninValue as never));
413
+ }
414
+
415
+ const gtValue = readOperatorValue(value, "gt");
416
+ if (gtValue !== undefined) {
417
+ filters.push(gt(field as never, gtValue as never));
418
+ }
419
+
420
+ const gteValue = readOperatorValue(value, "gte");
421
+ if (gteValue !== undefined) {
422
+ filters.push(gte(field as never, gteValue as never));
423
+ }
424
+
425
+ const ltValue = readOperatorValue(value, "lt");
426
+ if (ltValue !== undefined) {
427
+ filters.push(lt(field as never, ltValue as never));
428
+ }
429
+
430
+ const lteValue = readOperatorValue(value, "lte");
431
+ if (lteValue !== undefined) {
432
+ filters.push(lte(field as never, lteValue as never));
433
+ }
434
+
435
+ if (typeof value.exists === "boolean") {
436
+ filters.push(value.exists ? isNotNull(field as never) : isNull(field as never));
437
+ }
438
+
439
+ if (typeof value.regex === "string") {
440
+ const caseInsensitive = typeof value.$options === "string" && value.$options.includes("i");
441
+ const pattern = "%" + value.regex + "%";
442
+ if (caseInsensitive) {
443
+ filters.push(
444
+ sql\`lower(\${field as never}) like lower(\${pattern})\`,
445
+ );
446
+ } else {
447
+ filters.push(sql\`\${field as never} like \${pattern}\`);
448
+ }
449
+ }
450
+
451
+ if (isRecord(value.geoWithin)) {
452
+ const geoFilter = createGeoDistanceFilter(
453
+ value.geoWithin as GeoWithinOperand,
454
+ fields,
455
+ fieldName,
456
+ );
457
+ if (geoFilter) {
458
+ filters.push(geoFilter);
459
+ }
460
+ }
461
+
462
+ if (filters.length === 0) {
463
+ return eq(field as never, value as never);
464
+ }
465
+
466
+ if (filters.length === 1) {
467
+ return filters[0];
468
+ }
469
+
470
+ return and(...filters);
471
+ }
472
+
473
+ function buildWhereFilter(
474
+ table: unknown,
475
+ where?: Record<string, unknown>,
476
+ ): SQL | undefined {
477
+ if (!table || !where || Object.keys(where).length === 0) {
478
+ return undefined;
479
+ }
480
+
481
+ const fields = getTableColumns(table as never) as Record<string, unknown>;
482
+ const filters: SQL[] = [];
483
+
484
+ const topLevelGeoWithin = isRecord(where.geoWithin)
485
+ ? (where.geoWithin as GeoWithinOperand)
486
+ : isRecord(where.$geoWithin)
487
+ ? (where.$geoWithin as GeoWithinOperand)
488
+ : undefined;
489
+
490
+ if (topLevelGeoWithin) {
491
+ const geoFilter = createGeoDistanceFilter(topLevelGeoWithin, fields);
492
+ if (geoFilter) {
493
+ filters.push(geoFilter);
494
+ }
495
+ }
496
+
497
+ for (const [fieldName, fieldValue] of Object.entries(where)) {
498
+ if (fieldName === "geoWithin" || fieldName === "$geoWithin") {
499
+ continue;
500
+ }
501
+
502
+ if (fieldValue === undefined) {
503
+ continue;
504
+ }
505
+
506
+ const field = fields[fieldName];
507
+ if (!field) {
508
+ continue;
509
+ }
510
+
511
+ const filter = buildFieldFilter(fieldName, field, fieldValue, fields);
512
+ if (filter) {
513
+ filters.push(filter);
514
+ }
515
+ }
516
+
517
+ if (filters.length === 0) {
518
+ return undefined;
519
+ }
520
+
521
+ if (filters.length === 1) {
522
+ return filters[0];
523
+ }
524
+
525
+ return and(...filters);
526
+ }
527
+
528
+ function inferConflictTarget(table: unknown): string[] {
529
+ if (!table) {
530
+ return [];
531
+ }
532
+
533
+ const columns = getTableColumns(table as never) as Record<string, unknown>;
534
+ if (columns.id) {
535
+ return ["id"];
536
+ }
537
+
538
+ return [];
539
+ }
540
+
541
+ export function createQueryDb(
542
+ $db: AppflareDb,
543
+ options?: QueryDbOptions,
544
+ ): AppflareQueryDb {
545
+ const cache = new Map<string, unknown>();
546
+ const onMutation = options?.onMutation;
547
+ const queryDb = $db.query as Record<
548
+ string,
549
+ {
550
+ findMany: (args?: unknown) => Promise<unknown>;
551
+ findFirst: (args?: unknown) => Promise<unknown>;
552
+ }
553
+ >;
554
+
555
+ return new Proxy({} as AppflareQueryDb, {
556
+ get: (_target, property) => {
557
+ const tableName = String(property);
558
+ if (cache.has(tableName)) {
559
+ return cache.get(tableName);
560
+ }
561
+
562
+ const queryTable = queryDb[tableName];
563
+ if (!queryTable) {
564
+ return undefined;
565
+ }
566
+
567
+ const table = (mergedSchema as Record<string, unknown>)[tableName];
568
+
569
+ const emitMutation = (
570
+ kind: DbMutationKind,
571
+ args: Record<string, unknown>,
572
+ rows: unknown[],
573
+ ): void => {
574
+ onMutation?.({
575
+ kind,
576
+ table: tableName,
577
+ args,
578
+ rows,
579
+ });
580
+ };
581
+
582
+ const tableApi = {
583
+ findMany: (args?: Record<string, unknown>) => {
584
+ const where = isRecord(args?.where)
585
+ ? (args?.where as Record<string, unknown>)
586
+ : undefined;
587
+ const whereFilter = buildWhereFilter(table, where);
588
+ if (!whereFilter) {
589
+ return queryTable.findMany(args);
590
+ }
591
+
592
+ return queryTable.findMany({
593
+ ...(args ?? {}),
594
+ where: () => whereFilter,
595
+ });
596
+ },
597
+ findFirst: (args?: Record<string, unknown>) => {
598
+ const where = isRecord(args?.where)
599
+ ? (args?.where as Record<string, unknown>)
600
+ : undefined;
601
+ const whereFilter = buildWhereFilter(table, where);
602
+ if (!whereFilter) {
603
+ return queryTable.findFirst(args);
604
+ }
605
+
606
+ return queryTable.findFirst({
607
+ ...(args ?? {}),
608
+ where: () => whereFilter,
609
+ });
610
+ },
611
+ insert: async (args: QueryInsertArgs<TableName>) => {
612
+ let insertQuery: any = ($db as any)
613
+ .insert(table as any)
614
+ .values(args.values as any);
615
+
616
+ if (typeof insertQuery.returning === "function") {
617
+ insertQuery = insertQuery.returning();
618
+ }
619
+
620
+ const rows = (await insertQuery) as Array<TableModel<TableName>>;
621
+ emitMutation(
622
+ "insert",
623
+ { values: args.values as unknown as Record<string, unknown> },
624
+ rows,
625
+ );
626
+ return rows;
627
+ },
628
+ update: async (args: QueryUpdateArgs<TableName>) => {
629
+ const whereFilter = buildWhereFilter(
630
+ table,
631
+ args.where as Record<string, unknown> | undefined,
632
+ );
633
+ let updateQuery: any = ($db as any)
634
+ .update(table as any)
635
+ .set(args.set as any);
636
+
637
+ if (whereFilter) {
638
+ updateQuery = updateQuery.where(whereFilter);
639
+ }
640
+ if (typeof args.limit === "number" && typeof updateQuery.limit === "function") {
641
+ updateQuery = updateQuery.limit(args.limit);
642
+ }
643
+ if (typeof updateQuery.returning === "function") {
644
+ updateQuery = updateQuery.returning();
645
+ }
646
+
647
+ const rows = (await updateQuery) as Array<TableModel<TableName>>;
648
+ emitMutation(
649
+ "update",
650
+ {
651
+ set: args.set as unknown as Record<string, unknown>,
652
+ where: (args.where ?? undefined) as unknown as Record<string, unknown>,
653
+ limit: args.limit,
654
+ },
655
+ rows,
656
+ );
657
+ return rows;
658
+ },
659
+ upsert: async (args: QueryUpsertArgs<TableName>) => {
660
+ const valuesArray = Array.isArray(args.values)
661
+ ? args.values
662
+ : [args.values];
663
+ const values = Array.isArray(args.values)
664
+ ? args.values
665
+ : args.values;
666
+
667
+ const targets = args.target
668
+ ? Array.isArray(args.target)
669
+ ? args.target
670
+ : [args.target]
671
+ : inferConflictTarget(table);
672
+
673
+ if (targets.length === 0) {
674
+ throw new Error(
675
+ "Unable to infer conflict target for table " + tableName + ". Provide target explicitly.",
676
+ );
677
+ }
678
+
679
+ const tableColumns = getTableColumns(table as never) as Record<string, unknown>;
680
+ const targetColumns = targets
681
+ .map((target) => tableColumns[target])
682
+ .filter(Boolean);
683
+
684
+ if (targetColumns.length === 0) {
685
+ throw new Error(
686
+ "Invalid conflict target for table " + tableName + ".",
687
+ );
688
+ }
689
+
690
+ const setPayload = args.set ?? valuesArray[0] ?? {};
691
+ let upsertQuery: any = ($db as any)
692
+ .insert(table as any)
693
+ .values(values as any)
694
+ .onConflictDoUpdate({
695
+ target: targetColumns as any,
696
+ set: setPayload as any,
697
+ });
698
+
699
+ if (typeof upsertQuery.returning === "function") {
700
+ upsertQuery = upsertQuery.returning();
701
+ }
702
+
703
+ const rows = (await upsertQuery) as Array<TableModel<TableName>>;
704
+ emitMutation(
705
+ "upsert",
706
+ {
707
+ values: args.values as unknown as Record<string, unknown>,
708
+ target: targets as unknown as Record<string, unknown>,
709
+ set: (args.set ?? undefined) as unknown as Record<string, unknown>,
710
+ },
711
+ rows,
712
+ );
713
+ return rows;
714
+ },
715
+ delete: async (args?: QueryDeleteArgs<TableName>) => {
716
+ const whereFilter = buildWhereFilter(
717
+ table,
718
+ args?.where as Record<string, unknown> | undefined,
719
+ );
720
+ let deleteQuery: any = ($db as any).delete(table as any);
721
+
722
+ if (whereFilter) {
723
+ deleteQuery = deleteQuery.where(whereFilter);
724
+ }
725
+ if (typeof args?.limit === "number" && typeof deleteQuery.limit === "function") {
726
+ deleteQuery = deleteQuery.limit(args.limit);
727
+ }
728
+ if (typeof deleteQuery.returning === "function") {
729
+ deleteQuery = deleteQuery.returning();
730
+ }
731
+
732
+ const rows = (await deleteQuery) as Array<TableModel<TableName>>;
733
+ emitMutation(
734
+ "delete",
735
+ {
736
+ where: (args?.where ?? undefined) as unknown as Record<string, unknown>,
737
+ limit: args?.limit,
738
+ },
739
+ rows,
740
+ );
741
+ return rows;
742
+ },
743
+ };
744
+
745
+ cache.set(tableName, tableApi);
746
+ return tableApi;
747
+ },
748
+ }) as AppflareQueryDb;
749
+ }
750
+
751
+ export class AppflareHandledError extends Error {
752
+ public readonly status: number;
753
+ public readonly payload: unknown;
754
+
755
+ public constructor(status: number, payload: unknown) {
756
+ super(typeof payload === "object" ? "Handled error" : String(payload));
757
+ this.status = status;
758
+ this.payload = payload;
759
+ }
760
+ }
761
+
762
+
763
+
764
+ type AuthSession = typeof auth.$Infer.Session;
765
+ type User = AuthSession['user']
766
+ type Session = AuthSession['session']
767
+
768
+ export type StoragePutArgs = {
769
+ path: string;
770
+ body: ReadableStream | ArrayBuffer | ArrayBufferView | string | Blob;
771
+ contentType?: string;
772
+ httpMetadata?: Record<string, unknown>;
773
+ customMetadata?: Record<string, string>;
774
+ };
775
+
776
+ export type StorageGetArgs = {
777
+ path: string;
778
+ method?: StorageMethod;
779
+ onlyIf?: unknown;
780
+ range?: unknown;
781
+ includeBody?: boolean;
782
+ };
783
+
784
+ export type StorageDeleteArgs = {
785
+ path: string;
786
+ };
787
+
788
+ export type StorageListArgs = {
789
+ prefix?: string;
790
+ cursor?: string;
791
+ limit?: number;
792
+ delimiter?: string;
793
+ include?: Array<"httpMetadata" | "customMetadata">;
794
+ method?: StorageMethod;
795
+ };
796
+
797
+ export type StorageSignedUrlArgs = {
798
+ path: string;
799
+ method?: "GET" | "PUT" | "DELETE";
800
+ expiresIn?: number;
801
+ contentType?: string;
802
+ downloadAsAttachment?: boolean;
803
+ fileName?: string;
804
+ };
805
+
806
+ export type AppflareStorage = {
807
+ put: (args: StoragePutArgs) => Promise<unknown>;
808
+ get: (args: StorageGetArgs) => Promise<unknown | null>;
809
+ delete: (args: StorageDeleteArgs) => Promise<void>;
810
+ list: (args?: StorageListArgs) => Promise<unknown>;
811
+ signedUrl: (args: StorageSignedUrlArgs) => Promise<string>;
812
+ };
813
+
814
+ export type AppflareContext = {
815
+ $db: AppflareDb;
816
+ db: AppflareQueryDb;
817
+ mutationEvents: DbMutationEvent[];
818
+ user: User;
819
+ session: Session;
820
+ context: Context<WorkerEnv>;
821
+ scheduler: Scheduler;
822
+ storage: AppflareStorage;
823
+ error: (status: number, message: string, details?: unknown) => never;
824
+ };
825
+
826
+ type InferOperationArgs<TShape extends ZodRawShape> = z.output<z.ZodObject<TShape>>;
827
+
828
+ export type SchedulerEnqueueOptions = {
829
+ delaySeconds?: number;
830
+ };
831
+
832
+ declare global {
833
+ interface AppflareSchedulerHandlerMap {}
834
+ }
835
+
836
+ export type SchedulerTaskName = keyof AppflareSchedulerHandlerMap extends never
837
+ ? string
838
+ : Extract<keyof AppflareSchedulerHandlerMap, string>;
839
+
840
+ export type SchedulerTaskPayload<TTask extends SchedulerTaskName> =
841
+ TTask extends keyof AppflareSchedulerHandlerMap
842
+ ? AppflareSchedulerHandlerMap[TTask]
843
+ : unknown;
844
+
845
+ export type SchedulerEnqueue = {
846
+ <TTask extends SchedulerTaskName>(
847
+ task: TTask,
848
+ ...args: undefined extends SchedulerTaskPayload<TTask>
849
+ ? [
850
+ payload?: SchedulerTaskPayload<TTask>,
851
+ options?: SchedulerEnqueueOptions,
852
+ ]
853
+ : [
854
+ payload: SchedulerTaskPayload<TTask>,
855
+ options?: SchedulerEnqueueOptions,
856
+ ]
857
+ ): Promise<void>;
858
+ };
859
+
860
+ export type Scheduler = {
861
+ enqueue: SchedulerEnqueue;
862
+ };
863
+
864
+ type OperationDefinition<
865
+ TShape extends ZodRawShape,
866
+ TResult,
867
+ > = {
868
+ args: TShape;
869
+ authRequired: boolean;
870
+ middleware?: (
871
+ ctx: AppflareContext,
872
+ index: InferOperationArgs<TShape>,
873
+ request: Request,
874
+ ) => void | Promise<void>;
875
+ handler: (
876
+ ctx: AppflareContext,
877
+ args: InferOperationArgs<TShape>,
878
+ ) => TResult | Promise<TResult>;
879
+ };
880
+
881
+ type OperationDefinitionInput<
882
+ TShape extends ZodRawShape,
883
+ TResult,
884
+ > = Omit<OperationDefinition<TShape, TResult>, "authRequired"> & {
885
+ authRequired?: boolean;
886
+ };
887
+
888
+ export type RegisteredOperation<
889
+ TShape extends ZodRawShape,
890
+ TResult,
891
+ > = {
892
+ kind: "query" | "mutation";
893
+ definition: OperationDefinition<TShape, TResult>;
894
+ };
895
+
896
+ type SchedulerDefinition<TShape extends ZodRawShape | undefined> = {
897
+ args?: TShape;
898
+ handler: (
899
+ ctx: AppflareContext,
900
+ args: TShape extends ZodRawShape ? InferOperationArgs<TShape> : undefined,
901
+ ) => void | Promise<void>;
902
+ };
903
+
904
+ export type RegisteredScheduler<TShape extends ZodRawShape | undefined> = {
905
+ kind: "scheduler";
906
+ definition: SchedulerDefinition<TShape>;
907
+ };
908
+
909
+ type CronDefinition = {
910
+ cronTrigger: string | string[];
911
+ handler: (ctx: AppflareContext) => void | Promise<void>;
912
+ };
913
+
914
+ export type RegisteredCron = {
915
+ kind: "cron";
916
+ definition: CronDefinition;
917
+ };
918
+
919
+ export function query<TShape extends ZodRawShape, TResult>(
920
+ definition: OperationDefinitionInput<TShape, TResult>,
921
+ ): RegisteredOperation<TShape, TResult> {
922
+ return {
923
+ kind: "query",
924
+ definition: {
925
+ authRequired: false,
926
+ ...definition,
927
+ },
928
+ };
929
+ }
930
+
931
+ export function mutation<TShape extends ZodRawShape, TResult>(
932
+ definition: OperationDefinitionInput<TShape, TResult>,
933
+ ): RegisteredOperation<TShape, TResult> {
934
+ return {
935
+ kind: "mutation",
936
+ definition: {
937
+ authRequired: false,
938
+ ...definition,
939
+ },
940
+ };
941
+ }
942
+
943
+ export function scheduler<TShape extends ZodRawShape | undefined = undefined>(
944
+ definition: SchedulerDefinition<TShape>,
945
+ ): RegisteredScheduler<TShape> {
946
+ return {
947
+ kind: "scheduler",
948
+ definition,
949
+ };
950
+ }
951
+
952
+ export function cron(definition: CronDefinition): RegisteredCron {
953
+ return {
954
+ kind: "cron",
955
+ definition,
956
+ };
957
+ }
958
+
959
+ `;
960
+ }