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.
Files changed (151) 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 +747 -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 +353 -0
  34. package/cli/templates/handlers/auth.ts +37 -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/generators/registration/modules/cron.ts +30 -0
  46. package/cli/templates/handlers/generators/registration/modules/realtime.ts +631 -0
  47. package/cli/templates/handlers/generators/registration/modules/scheduler.ts +56 -0
  48. package/cli/templates/handlers/generators/registration/modules/storage.ts +194 -0
  49. package/cli/templates/handlers/generators/registration/sections.ts +210 -0
  50. package/cli/templates/handlers/generators/types/context.ts +64 -0
  51. package/cli/templates/handlers/generators/types/core.ts +101 -0
  52. package/cli/templates/handlers/generators/types/operations.ts +135 -0
  53. package/cli/templates/handlers/generators/types/query-definitions.ts +1029 -0
  54. package/cli/templates/handlers/generators/types/query-runtime.ts +417 -0
  55. package/cli/templates/handlers/index.ts +43 -0
  56. package/cli/templates/handlers/operations.ts +116 -0
  57. package/cli/templates/handlers/registration.ts +83 -0
  58. package/cli/templates/handlers/types.ts +15 -0
  59. package/cli/templates/handlers/utils.ts +48 -0
  60. package/cli/types.ts +108 -0
  61. package/cli/utils/handler-discovery.ts +366 -0
  62. package/cli/utils/json-utils.ts +24 -0
  63. package/cli/utils/path-utils.ts +19 -0
  64. package/cli/utils/schema-discovery.ts +390 -0
  65. package/index.ts +27 -4
  66. package/package.json +23 -20
  67. package/react/index.ts +5 -3
  68. package/react/use-infinite-query.ts +190 -0
  69. package/react/use-mutation.ts +54 -0
  70. package/react/use-query.ts +158 -0
  71. package/schema.ts +262 -0
  72. package/tsconfig.json +2 -4
  73. package/cli/README.md +0 -108
  74. package/cli/core/build.ts +0 -187
  75. package/cli/core/config.ts +0 -92
  76. package/cli/core/discover-handlers.ts +0 -143
  77. package/cli/core/handlers.ts +0 -7
  78. package/cli/core/index.ts +0 -205
  79. package/cli/generators/generate-api-client/client.ts +0 -163
  80. package/cli/generators/generate-api-client/extract-configuration.ts +0 -121
  81. package/cli/generators/generate-api-client/index.ts +0 -973
  82. package/cli/generators/generate-api-client/types.ts +0 -164
  83. package/cli/generators/generate-api-client/utils.ts +0 -22
  84. package/cli/generators/generate-api-client.ts +0 -1
  85. package/cli/generators/generate-cloudflare-worker/helpers.ts +0 -24
  86. package/cli/generators/generate-cloudflare-worker/index.ts +0 -2
  87. package/cli/generators/generate-cloudflare-worker/worker.ts +0 -148
  88. package/cli/generators/generate-cloudflare-worker/wrangler.ts +0 -108
  89. package/cli/generators/generate-cloudflare-worker.ts +0 -4
  90. package/cli/generators/generate-cron-handlers/cron-handlers-block.ts +0 -2
  91. package/cli/generators/generate-cron-handlers/handler-entries.ts +0 -29
  92. package/cli/generators/generate-cron-handlers/index.ts +0 -61
  93. package/cli/generators/generate-cron-handlers/runtime-block.ts +0 -49
  94. package/cli/generators/generate-cron-handlers/type-helpers-block.ts +0 -60
  95. package/cli/generators/generate-db-handlers/index.ts +0 -33
  96. package/cli/generators/generate-db-handlers/prepare.ts +0 -24
  97. package/cli/generators/generate-db-handlers/templates.ts +0 -189
  98. package/cli/generators/generate-db-handlers.ts +0 -1
  99. package/cli/generators/generate-hono-server/auth.ts +0 -97
  100. package/cli/generators/generate-hono-server/imports.ts +0 -55
  101. package/cli/generators/generate-hono-server/index.ts +0 -52
  102. package/cli/generators/generate-hono-server/routes.ts +0 -115
  103. package/cli/generators/generate-hono-server/template.ts +0 -371
  104. package/cli/generators/generate-hono-server.ts +0 -1
  105. package/cli/generators/generate-scheduler-handlers/constants.ts +0 -8
  106. package/cli/generators/generate-scheduler-handlers/handler-entries.ts +0 -22
  107. package/cli/generators/generate-scheduler-handlers/index.ts +0 -51
  108. package/cli/generators/generate-scheduler-handlers/runtime-block.ts +0 -68
  109. package/cli/generators/generate-scheduler-handlers/scheduler-handlers-block.ts +0 -2
  110. package/cli/generators/generate-scheduler-handlers/type-helpers-block.ts +0 -68
  111. package/cli/generators/generate-scheduler-handlers.ts +0 -1
  112. package/cli/generators/generate-websocket-durable-object/auth.ts +0 -30
  113. package/cli/generators/generate-websocket-durable-object/imports.ts +0 -55
  114. package/cli/generators/generate-websocket-durable-object/index.ts +0 -41
  115. package/cli/generators/generate-websocket-durable-object/query-handlers.ts +0 -18
  116. package/cli/generators/generate-websocket-durable-object/template.ts +0 -714
  117. package/cli/generators/generate-websocket-durable-object.ts +0 -1
  118. package/cli/schema/schema-static-types.ts +0 -702
  119. package/cli/schema/schema.ts +0 -151
  120. package/cli/utils/tsc.ts +0 -54
  121. package/cli/utils/utils.ts +0 -190
  122. package/cli/utils/zod-utils.ts +0 -121
  123. package/lib/README.md +0 -50
  124. package/lib/db.ts +0 -19
  125. package/lib/location.ts +0 -110
  126. package/lib/values.ts +0 -27
  127. package/react/README.md +0 -67
  128. package/react/hooks/useMutation.ts +0 -89
  129. package/react/hooks/usePaginatedQuery.ts +0 -213
  130. package/react/hooks/useQuery.ts +0 -106
  131. package/react/shared/queryShared.ts +0 -174
  132. package/server/README.md +0 -218
  133. package/server/auth.ts +0 -107
  134. package/server/database/builders.ts +0 -83
  135. package/server/database/context.ts +0 -327
  136. package/server/database/populate.ts +0 -234
  137. package/server/database/query-builder.ts +0 -161
  138. package/server/database/query-utils.ts +0 -25
  139. package/server/db.ts +0 -2
  140. package/server/storage/auth.ts +0 -16
  141. package/server/storage/bucket.ts +0 -22
  142. package/server/storage/context.ts +0 -34
  143. package/server/storage/index.ts +0 -38
  144. package/server/storage/operations.ts +0 -149
  145. package/server/storage/route-handler.ts +0 -60
  146. package/server/storage/types.ts +0 -55
  147. package/server/storage/utils.ts +0 -47
  148. package/server/storage.ts +0 -6
  149. package/server/types/schema-refs.ts +0 -66
  150. package/server/types/types.ts +0 -633
  151. package/server/utils/id-utils.ts +0 -230
@@ -0,0 +1,1029 @@
1
+ export function generateTypesQueryDefinitionsSection(): string {
2
+ return `const mergedSchema = {
3
+ ...authSchema,
4
+ ...schema,
5
+ };
6
+
7
+ export const createDb = (database: D1Database) =>
8
+ drizzle(database, { schema: mergedSchema });
9
+
10
+ export type AppflareDb = ReturnType<typeof createDb>;
11
+
12
+ type QueryTables = AppflareDb["query"];
13
+ type TableName = Extract<keyof QueryTables, string>;
14
+ type TableQuery<TName extends TableName> = QueryTables[TName];
15
+ type TableFindManyArgs<TName extends TableName> = TableQuery<TName> extends {
16
+ findMany: (args?: infer TArgs) => Promise<unknown>;
17
+ }
18
+ ? TArgs
19
+ : never;
20
+ type TableFindManyResult<TName extends TableName> = TableQuery<TName> extends {
21
+ findMany: (args?: unknown) => infer TResult;
22
+ }
23
+ ? TResult
24
+ : Promise<Array<TableModel<TName>>>;
25
+ type TableFindFirstArgs<TName extends TableName> = TableQuery<TName> extends {
26
+ findFirst: (args?: infer TArgs) => Promise<unknown>;
27
+ }
28
+ ? TArgs
29
+ : never;
30
+ type TableFindFirstResult<TName extends TableName> = TableQuery<TName> extends {
31
+ findFirst: (args?: unknown) => infer TResult;
32
+ }
33
+ ? TResult
34
+ : Promise<TableModel<TName> | null>;
35
+ type TableModel<TName extends TableName> = InferSelectModel<
36
+ (typeof mergedSchema)[TName]
37
+ >;
38
+ type TableInsertModel<TName extends TableName> = InferInsertModel<
39
+ (typeof mergedSchema)[TName]
40
+ >;
41
+
42
+ type Primitive = string | number | boolean | Date;
43
+ type NonNil<T> = Exclude<T, null | undefined>;
44
+ type Friendly<T> = T extends Date ? Date | number : T;
45
+ type Comparable<T> = Friendly<Extract<NonNil<T>, Primitive>>;
46
+ type RegexOperand<T> = T extends string ? string : never;
47
+
48
+ type GeoPoint = {
49
+ latitude: number;
50
+ longitude: number;
51
+ };
52
+
53
+ type GeoCoordinates = {
54
+ type?: "Point";
55
+ coordinates: [number, number] | readonly [number, number];
56
+ };
57
+
58
+ type GeoWithinOperand = {
59
+ $geometry: GeoPoint | GeoCoordinates;
60
+ latitudeField?: string;
61
+ longitudeField?: string;
62
+ $minDistance?: number;
63
+ $maxDistance?: number;
64
+ gt?: number;
65
+ gte?: number;
66
+ lt?: number;
67
+ lte?: number;
68
+ $gt?: number;
69
+ $gte?: number;
70
+ $lt?: number;
71
+ $lte?: number;
72
+ };
73
+
74
+ type GeoWithinOperandForField<TFieldKey extends string> = Omit<
75
+ GeoWithinOperand,
76
+ "latitudeField" | "longitudeField"
77
+ > & {
78
+ latitudeField?: TFieldKey;
79
+ longitudeField?: TFieldKey;
80
+ };
81
+
82
+ type FieldOperators<T, TFieldKey extends string = string> = {
83
+ eq?: Friendly<NonNil<T>>;
84
+ $eq?: Friendly<NonNil<T>>;
85
+ ne?: Friendly<NonNil<T>>;
86
+ $ne?: Friendly<NonNil<T>>;
87
+ in?: ReadonlyArray<Friendly<NonNil<T>>>;
88
+ $in?: ReadonlyArray<Friendly<NonNil<T>>>;
89
+ nin?: ReadonlyArray<Friendly<NonNil<T>>>;
90
+ $nin?: ReadonlyArray<Friendly<NonNil<T>>>;
91
+ gt?: Comparable<T>;
92
+ $gt?: Comparable<T>;
93
+ gte?: Comparable<T>;
94
+ $gte?: Comparable<T>;
95
+ lt?: Comparable<T>;
96
+ $lt?: Comparable<T>;
97
+ lte?: Comparable<T>;
98
+ $lte?: Comparable<T>;
99
+ exists?: boolean;
100
+ regex?: RegexOperand<T>;
101
+ $options?: string;
102
+ geoWithin?: GeoWithinOperandForField<TFieldKey>;
103
+ };
104
+
105
+ type WhereFieldValue<T, TFieldKey extends string> =
106
+ | Friendly<NonNil<T>>
107
+ | FieldOperators<T, TFieldKey>;
108
+
109
+ export type WhereInput<TModel extends Record<string, unknown>> = {
110
+ [K in keyof TModel]?: WhereFieldValue<
111
+ TModel[K],
112
+ Extract<keyof TModel, string>
113
+ >;
114
+ } & {
115
+ geoWithin?: GeoWithinOperandForField<Extract<keyof TModel, string>>;
116
+ $geoWithin?: GeoWithinOperandForField<Extract<keyof TModel, string>>;
117
+ };
118
+
119
+ type ColumnData<TColumn> = TColumn extends { _: { data: infer TData } }
120
+ ? TData
121
+ : unknown;
122
+
123
+ type InferRelationWhereFields<TConfig> = TConfig extends { where?: infer TWhere }
124
+ ? TWhere extends (...args: infer TArgs) => unknown
125
+ ? TArgs extends [infer TFields, ...unknown[]]
126
+ ? TFields extends Record<string, unknown>
127
+ ? TFields
128
+ : never
129
+ : never
130
+ : never
131
+ : never;
132
+
133
+ type RelationModelFromConfig<TConfig extends Record<string, unknown>> =
134
+ InferRelationWhereFields<TConfig> extends infer TFields
135
+ ? TFields extends Record<string, unknown>
136
+ ? {
137
+ [K in Extract<keyof TFields, string>]: ColumnData<TFields[K]>;
138
+ }
139
+ : Record<string, unknown>
140
+ : Record<string, unknown>;
141
+
142
+ type NumericModelFieldKey<TModel extends Record<string, unknown>> = Extract<
143
+ {
144
+ [K in keyof TModel]-?: NonNullable<TModel[K]> extends number ? K : never;
145
+ }[keyof TModel],
146
+ string
147
+ >;
148
+
149
+ type RelationAvgSelectionInput<TModel extends Record<string, unknown>> =
150
+ Partial<Record<NumericModelFieldKey<TModel>, boolean>>;
151
+
152
+ type RelationAggregateSelectionInput<TModel extends Record<string, unknown>> = {
153
+ _count?: boolean;
154
+ _avg?: RelationAvgSelectionInput<TModel>;
155
+ };
156
+
157
+ type QueryWithRelationInput<TEntry> =
158
+ | (Extract<TEntry, true> extends never ? never : true)
159
+ | (Extract<TEntry, Record<string, unknown>> extends infer TRelation
160
+ ? TRelation extends Record<string, unknown>
161
+ ? Omit<TRelation, "where" | "with" | "_count" | "_avg"> &
162
+ RelationAggregateSelectionInput<RelationModelFromConfig<TRelation>> & {
163
+ where?: WhereInput<RelationModelFromConfig<TRelation>>;
164
+ with?: TRelation extends { with?: infer TNestedWith }
165
+ ? QueryWithInput<TNestedWith>
166
+ : never;
167
+ }
168
+ : never
169
+ : never);
170
+
171
+ type QueryWithInput<TWith> = TWith extends Record<string, unknown>
172
+ ? {
173
+ [K in keyof TWith]?: QueryWithRelationInput<TWith[K]>;
174
+ }
175
+ : TWith;
176
+
177
+ export type QueryFindManyArgs<TName extends TableName> = Omit<
178
+ NonNullable<TableFindManyArgs<TName>>,
179
+ "where" | "with"
180
+ > & {
181
+ where?: WhereInput<TableModel<TName>>;
182
+ with?: TableFindManyArgs<TName> extends { with?: infer TWith }
183
+ ? QueryWithInput<TWith>
184
+ : never;
185
+ };
186
+
187
+ export type QueryFindFirstArgs<TName extends TableName> = Omit<
188
+ NonNullable<TableFindFirstArgs<TName>>,
189
+ "where" | "with"
190
+ > & {
191
+ where?: WhereInput<TableModel<TName>>;
192
+ with?: TableFindFirstArgs<TName> extends { with?: infer TWith }
193
+ ? QueryWithInput<TWith>
194
+ : never;
195
+ };
196
+
197
+ export type QueryInsertArgs<TName extends TableName> = {
198
+ values: TableInsertModel<TName> | Array<TableInsertModel<TName>>;
199
+ };
200
+
201
+ export type QueryUpdateArgs<TName extends TableName> = {
202
+ set: Partial<TableInsertModel<TName>>;
203
+ where?: WhereInput<TableModel<TName>>;
204
+ limit?: number;
205
+ };
206
+
207
+ export type QueryDeleteArgs<TName extends TableName> = {
208
+ where?: WhereInput<TableModel<TName>>;
209
+ limit?: number;
210
+ };
211
+
212
+ type AggregateWithInput<TName extends TableName> =
213
+ TableFindManyArgs<TName> extends { with?: infer TWith }
214
+ ? QueryWithInput<TWith>
215
+ : never;
216
+
217
+ type NumericFieldKey<TName extends TableName> = NumericModelFieldKey<
218
+ TableModel<TName>
219
+ >;
220
+
221
+ type RelationAggregateAvgResult<TAvg> = TAvg extends Record<string, unknown>
222
+ ? {
223
+ [K in Extract<keyof TAvg, string> as TAvg[K] extends true ? K : never]: number;
224
+ }
225
+ : {};
226
+
227
+ type RelationAggregateResult<TEntry> =
228
+ Extract<TEntry, Record<string, unknown>> extends infer TRelation
229
+ ? TRelation extends Record<string, unknown>
230
+ ? (TRelation extends { _count?: true } ? { count: number } : {}) &
231
+ (TRelation extends { _avg?: infer TAvg }
232
+ ? { avg: RelationAggregateAvgResult<TAvg> }
233
+ : {})
234
+ : {}
235
+ : {};
236
+
237
+ type HasRelationAggregateRequest<TEntry> =
238
+ Extract<TEntry, Record<string, unknown>> extends infer TRelation
239
+ ? TRelation extends Record<string, unknown>
240
+ ? TRelation extends { _count?: true }
241
+ ? true
242
+ : TRelation extends { _avg?: Record<string, unknown> }
243
+ ? true
244
+ : false
245
+ : false
246
+ : false;
247
+
248
+ type QueryWithAggregateResult<TWith> = TWith extends Record<string, unknown>
249
+ ? {
250
+ [K in Extract<keyof TWith, string> as HasRelationAggregateRequest<
251
+ TWith[K]
252
+ > extends true
253
+ ? \`\${K}Aggregate\`
254
+ : never]: RelationAggregateResult<TWith[K]>;
255
+ }
256
+ : {};
257
+
258
+ type WithFromArgs<TArgs> = TArgs extends { with?: infer TWith }
259
+ ? TWith
260
+ : undefined;
261
+
262
+ type AppendRelationAggregatesToRow<TRow, TWith> = TRow &
263
+ QueryWithAggregateResult<TWith>;
264
+
265
+ type ApplyFindManyWithAggregateResult<TResult, TArgs> =
266
+ TResult extends Promise<infer TRows>
267
+ ? TRows extends Array<infer TRow>
268
+ ? Promise<
269
+ Array<AppendRelationAggregatesToRow<TRow, WithFromArgs<TArgs>>>
270
+ >
271
+ : TResult
272
+ : TResult;
273
+
274
+ type ApplyFindFirstWithAggregateResult<TResult, TArgs> =
275
+ TResult extends Promise<infer TRow>
276
+ ? Promise<
277
+ TRow extends null
278
+ ? null
279
+ : AppendRelationAggregatesToRow<TRow, WithFromArgs<TArgs>>
280
+ >
281
+ : TResult;
282
+
283
+ export type QueryCountArgs<TName extends TableName> = {
284
+ where?: WhereInput<TableModel<TName>>;
285
+ field?: Extract<keyof TableModel<TName>, string> | string;
286
+ distinct?: boolean;
287
+ with?: AggregateWithInput<TName>;
288
+ };
289
+
290
+ export type QueryAvgArgs<TName extends TableName> = {
291
+ where?: WhereInput<TableModel<TName>>;
292
+ field: NumericFieldKey<TName> | string;
293
+ distinct?: boolean;
294
+ with?: AggregateWithInput<TName>;
295
+ };
296
+
297
+ export type QueryUpsertArgs<TName extends TableName> = {
298
+ values: TableInsertModel<TName> | Array<TableInsertModel<TName>>;
299
+ target?:
300
+ | Extract<keyof TableInsertModel<TName>, string>
301
+ | Array<Extract<keyof TableInsertModel<TName>, string>>;
302
+ set?: Partial<TableInsertModel<TName>>;
303
+ };
304
+
305
+ export type QueryTableApi<TName extends TableName> = {
306
+ findMany: <
307
+ TArgs extends QueryFindManyArgs<TName> | undefined =
308
+ | QueryFindManyArgs<TName>
309
+ | undefined,
310
+ >(
311
+ args?: TArgs,
312
+ ) => ApplyFindManyWithAggregateResult<TableFindManyResult<TName>, TArgs>;
313
+ findFirst: <
314
+ TArgs extends QueryFindFirstArgs<TName> | undefined =
315
+ | QueryFindFirstArgs<TName>
316
+ | undefined,
317
+ >(
318
+ args?: TArgs,
319
+ ) => ApplyFindFirstWithAggregateResult<TableFindFirstResult<TName>, TArgs>;
320
+ insert: (args: QueryInsertArgs<TName>) => Promise<Array<TableModel<TName>>>;
321
+ update: (args: QueryUpdateArgs<TName>) => Promise<Array<TableModel<TName>>>;
322
+ upsert: (args: QueryUpsertArgs<TName>) => Promise<Array<TableModel<TName>>>;
323
+ delete: (args?: QueryDeleteArgs<TName>) => Promise<Array<TableModel<TName>>>;
324
+ count: (args?: QueryCountArgs<TName>) => Promise<number>;
325
+ avg: (args: QueryAvgArgs<TName>) => Promise<number | null>;
326
+ };
327
+
328
+ export type AppflareQueryDb = {
329
+ [TName in TableName]: QueryTableApi<TName>;
330
+ };
331
+
332
+ export type DbMutationKind = "insert" | "update" | "upsert" | "delete";
333
+
334
+ export type DbMutationEvent = {
335
+ kind: DbMutationKind;
336
+ table: string;
337
+ args: Record<string, unknown>;
338
+ rows: unknown[];
339
+ };
340
+
341
+ export type QueryDbOptions = {
342
+ onMutation?: (event: DbMutationEvent) => void;
343
+ };
344
+
345
+ function isRecord(value: unknown): value is Record<string, unknown> {
346
+ return typeof value === "object" && value !== null;
347
+ }
348
+
349
+ function readOperatorValue(record: Record<string, unknown>, key: string): unknown {
350
+ if (record[key] !== undefined) {
351
+ return record[key];
352
+ }
353
+ const prefixed = "$" + key;
354
+ return record[prefixed];
355
+ }
356
+
357
+ function normalizeGeoPoint(
358
+ value: GeoPoint | GeoCoordinates,
359
+ ): { latitude: number; longitude: number } | null {
360
+ if ((value as GeoPoint).latitude !== undefined) {
361
+ const point = value as GeoPoint;
362
+ if (
363
+ typeof point.latitude === "number" &&
364
+ typeof point.longitude === "number"
365
+ ) {
366
+ return {
367
+ latitude: point.latitude,
368
+ longitude: point.longitude,
369
+ };
370
+ }
371
+ return null;
372
+ }
373
+
374
+ const coordinates = (value as GeoCoordinates).coordinates;
375
+ if (!Array.isArray(coordinates) || coordinates.length < 2) {
376
+ return null;
377
+ }
378
+
379
+ const [longitude, latitude] = coordinates;
380
+ if (typeof latitude !== "number" || typeof longitude !== "number") {
381
+ return null;
382
+ }
383
+
384
+ return {
385
+ latitude,
386
+ longitude,
387
+ };
388
+ }
389
+
390
+ function createGeoDistanceFilter(
391
+ operand: GeoWithinOperand,
392
+ fields: Record<string, unknown>,
393
+ tableName?: string,
394
+ fallbackLatitudeField?: string,
395
+ ): SQL | undefined {
396
+ const geometry = normalizeGeoPoint(operand.$geometry);
397
+ if (!geometry) {
398
+ return undefined;
399
+ }
400
+
401
+ const latitudeField = operand.latitudeField ?? fallbackLatitudeField;
402
+ const longitudeField = operand.longitudeField;
403
+ if (!latitudeField || !longitudeField) {
404
+ return undefined;
405
+ }
406
+
407
+ const latitudeColumn = fields[latitudeField];
408
+ if (!latitudeColumn) {
409
+ console.warn(
410
+ "Invalid geoWithin latitudeField",
411
+ tableName ?? "unknown",
412
+ latitudeField,
413
+ );
414
+ return undefined;
415
+ }
416
+
417
+ const longitudeColumn = fields[longitudeField];
418
+ if (!longitudeColumn) {
419
+ console.warn(
420
+ "Invalid geoWithin longitudeField",
421
+ tableName ?? "unknown",
422
+ longitudeField,
423
+ );
424
+ return undefined;
425
+ }
426
+
427
+ const distanceExpression = sql<number>\`(
428
+ 6371000 * acos(
429
+ cos(radians(\${geometry.latitude})) * cos(radians(\${latitudeColumn}))
430
+ * cos(radians(\${longitudeColumn}) - radians(\${geometry.longitude}))
431
+ + sin(radians(\${geometry.latitude})) * sin(radians(\${latitudeColumn}))
432
+ )
433
+ )\`;
434
+
435
+ const minDistance =
436
+ operand.$minDistance ??
437
+ operand.gte ??
438
+ operand.$gte ??
439
+ operand.gt ??
440
+ operand.$gt;
441
+ const maxDistance =
442
+ operand.$maxDistance ??
443
+ operand.lte ??
444
+ operand.$lte ??
445
+ operand.lt ??
446
+ operand.$lt;
447
+
448
+ const filters: SQL[] = [];
449
+ if (typeof minDistance === "number") {
450
+ filters.push(sql\`\${distanceExpression} >= \${minDistance}\`);
451
+ }
452
+ if (typeof maxDistance === "number") {
453
+ filters.push(sql\`\${distanceExpression} <= \${maxDistance}\`);
454
+ }
455
+
456
+ if (filters.length === 0) {
457
+ return undefined;
458
+ }
459
+
460
+ if (filters.length === 1) {
461
+ return filters[0];
462
+ }
463
+
464
+ return and(...filters);
465
+ }
466
+
467
+ function buildFieldFilter(
468
+ fieldName: string,
469
+ field: unknown,
470
+ value: unknown,
471
+ fields: Record<string, unknown>,
472
+ ): SQL | undefined {
473
+ if (!isRecord(value) || value instanceof Date || Array.isArray(value)) {
474
+ return eq(field as never, value as never);
475
+ }
476
+
477
+ const filters: SQL[] = [];
478
+ const eqValue = readOperatorValue(value, "eq");
479
+ if (eqValue !== undefined) {
480
+ filters.push(eq(field as never, eqValue as never));
481
+ }
482
+
483
+ const neValue = readOperatorValue(value, "ne");
484
+ if (neValue !== undefined) {
485
+ filters.push(ne(field as never, neValue as never));
486
+ }
487
+
488
+ const inValue = readOperatorValue(value, "in");
489
+ if (Array.isArray(inValue) && inValue.length > 0) {
490
+ filters.push(inArray(field as never, inValue as never));
491
+ }
492
+
493
+ const ninValue = readOperatorValue(value, "nin");
494
+ if (Array.isArray(ninValue) && ninValue.length > 0) {
495
+ filters.push(notInArray(field as never, ninValue as never));
496
+ }
497
+
498
+ const gtValue = readOperatorValue(value, "gt");
499
+ if (gtValue !== undefined) {
500
+ filters.push(gt(field as never, gtValue as never));
501
+ }
502
+
503
+ const gteValue = readOperatorValue(value, "gte");
504
+ if (gteValue !== undefined) {
505
+ filters.push(gte(field as never, gteValue as never));
506
+ }
507
+
508
+ const ltValue = readOperatorValue(value, "lt");
509
+ if (ltValue !== undefined) {
510
+ filters.push(lt(field as never, ltValue as never));
511
+ }
512
+
513
+ const lteValue = readOperatorValue(value, "lte");
514
+ if (lteValue !== undefined) {
515
+ filters.push(lte(field as never, lteValue as never));
516
+ }
517
+
518
+ if (typeof value.exists === "boolean") {
519
+ filters.push(value.exists ? isNotNull(field as never) : isNull(field as never));
520
+ }
521
+
522
+ if (typeof value.regex === "string") {
523
+ const caseInsensitive = typeof value.$options === "string" && value.$options.includes("i");
524
+ const pattern = "%" + value.regex + "%";
525
+ if (caseInsensitive) {
526
+ filters.push(
527
+ sql\`lower(\${field as never}) like lower(\${pattern})\`,
528
+ );
529
+ } else {
530
+ filters.push(sql\`\${field as never} like \${pattern}\`);
531
+ }
532
+ }
533
+
534
+ if (isRecord(value.geoWithin)) {
535
+ const geoFilter = createGeoDistanceFilter(
536
+ value.geoWithin as GeoWithinOperand,
537
+ fields,
538
+ undefined,
539
+ fieldName,
540
+ );
541
+ if (geoFilter) {
542
+ filters.push(geoFilter);
543
+ }
544
+ }
545
+
546
+ if (filters.length === 0) {
547
+ return eq(field as never, value as never);
548
+ }
549
+
550
+ if (filters.length === 1) {
551
+ return filters[0];
552
+ }
553
+
554
+ return and(...filters);
555
+ }
556
+
557
+ function buildWhereFilter(
558
+ table: unknown,
559
+ where?: Record<string, unknown>,
560
+ tableName?: string,
561
+ ): SQL | undefined {
562
+ if (!table || !where || Object.keys(where).length === 0) {
563
+ return undefined;
564
+ }
565
+
566
+ const fields = getTableColumns(table as never) as Record<string, unknown>;
567
+ return buildWhereFilterFromFields(fields, where, tableName);
568
+ }
569
+
570
+ function buildWhereFilterFromFields(
571
+ fields: Record<string, unknown>,
572
+ where?: Record<string, unknown>,
573
+ tableName?: string,
574
+ ): SQL | undefined {
575
+ if (!where || Object.keys(where).length === 0) {
576
+ return undefined;
577
+ }
578
+
579
+ const filters: SQL[] = [];
580
+
581
+ const topLevelGeoWithin = isRecord(where.geoWithin)
582
+ ? (where.geoWithin as GeoWithinOperand)
583
+ : isRecord(where.$geoWithin)
584
+ ? (where.$geoWithin as GeoWithinOperand)
585
+ : undefined;
586
+
587
+ if (topLevelGeoWithin) {
588
+ const geoFilter = createGeoDistanceFilter(topLevelGeoWithin, fields, tableName);
589
+ if (geoFilter) {
590
+ filters.push(geoFilter);
591
+ }
592
+ }
593
+
594
+ for (const [fieldName, fieldValue] of Object.entries(where)) {
595
+ if (fieldName === "geoWithin" || fieldName === "$geoWithin") {
596
+ continue;
597
+ }
598
+
599
+ if (fieldValue === undefined) {
600
+ continue;
601
+ }
602
+
603
+ const field = fields[fieldName];
604
+ if (!field) {
605
+ continue;
606
+ }
607
+
608
+ const filter = buildFieldFilter(fieldName, field, fieldValue, fields);
609
+ if (filter) {
610
+ filters.push(filter);
611
+ }
612
+ }
613
+
614
+ if (filters.length === 0) {
615
+ return undefined;
616
+ }
617
+
618
+ if (filters.length === 1) {
619
+ return filters[0];
620
+ }
621
+
622
+ return and(...filters);
623
+ }
624
+
625
+ function transformWithRelations(withValue: unknown): unknown {
626
+ return transformWithRelationsAndExtractAggregates(withValue).with;
627
+ }
628
+
629
+ type RelationWithAggregatePlanEntry = {
630
+ count: boolean;
631
+ avgFields: string[];
632
+ nested?: RelationWithAggregatePlan;
633
+ };
634
+
635
+ type RelationWithAggregatePlan = Record<string, RelationWithAggregatePlanEntry>;
636
+
637
+ function hasRelationAggregatePlanEntries(
638
+ plan: RelationWithAggregatePlan | undefined,
639
+ ): boolean {
640
+ return !!plan && Object.keys(plan).length > 0;
641
+ }
642
+
643
+ function extractRelationAvgFieldNames(value: unknown): string[] {
644
+ if (!isRecord(value)) {
645
+ return [];
646
+ }
647
+
648
+ return Object.entries(value)
649
+ .filter(([, enabled]) => enabled === true)
650
+ .map(([fieldName]) => fieldName);
651
+ }
652
+
653
+ function transformWithRelationsAndExtractAggregates(withValue: unknown): {
654
+ with: unknown;
655
+ aggregatePlan: RelationWithAggregatePlan | undefined;
656
+ } {
657
+ if (!isRecord(withValue)) {
658
+ return {
659
+ with: withValue,
660
+ aggregatePlan: undefined,
661
+ };
662
+ }
663
+
664
+ const transformed: Record<string, unknown> = {};
665
+ const aggregatePlan: RelationWithAggregatePlan = {};
666
+ for (const [relationName, relationValue] of Object.entries(withValue)) {
667
+ if (typeof relationValue === "boolean") {
668
+ transformed[relationName] = relationValue;
669
+ continue;
670
+ }
671
+
672
+ if (!isRecord(relationValue)) {
673
+ transformed[relationName] = relationValue;
674
+ continue;
675
+ }
676
+
677
+ const relationConfig: Record<string, unknown> = {
678
+ ...relationValue,
679
+ };
680
+ const countRequested = relationConfig._count === true;
681
+ const avgFieldNames = extractRelationAvgFieldNames(relationConfig._avg);
682
+ delete relationConfig._count;
683
+ delete relationConfig._avg;
684
+
685
+ let nestedAggregatePlan: RelationWithAggregatePlan | undefined;
686
+
687
+ const relationWhere = relationConfig.where;
688
+ if (relationWhere !== undefined && !isRecord(relationWhere)) {
689
+ throw new Error(
690
+ "Invalid relational with.where for relation " +
691
+ relationName +
692
+ ": only shorthand object filters are supported.",
693
+ );
694
+ }
695
+
696
+ if (isRecord(relationWhere)) {
697
+ relationConfig.where = (fields: Record<string, unknown>) => {
698
+ const filter = buildWhereFilterFromFields(
699
+ fields,
700
+ relationWhere,
701
+ relationName,
702
+ );
703
+ if (filter) {
704
+ return filter;
705
+ }
706
+
707
+ return sql\`1 = 1\`;
708
+ };
709
+ }
710
+
711
+ if (relationConfig.with !== undefined) {
712
+ const nestedTransform = transformWithRelationsAndExtractAggregates(
713
+ relationConfig.with,
714
+ );
715
+ relationConfig.with = nestedTransform.with;
716
+ nestedAggregatePlan = nestedTransform.aggregatePlan;
717
+ }
718
+
719
+ if (
720
+ countRequested ||
721
+ avgFieldNames.length > 0 ||
722
+ hasRelationAggregatePlanEntries(nestedAggregatePlan)
723
+ ) {
724
+ aggregatePlan[relationName] = {
725
+ count: countRequested,
726
+ avgFields: avgFieldNames,
727
+ ...(nestedAggregatePlan
728
+ ? { nested: nestedAggregatePlan }
729
+ : {}),
730
+ };
731
+ }
732
+
733
+ transformed[relationName] = relationConfig;
734
+ }
735
+
736
+ return {
737
+ with: transformed,
738
+ aggregatePlan: hasRelationAggregatePlanEntries(aggregatePlan)
739
+ ? aggregatePlan
740
+ : undefined,
741
+ };
742
+ }
743
+
744
+ function computeAverageOrZero(values: number[]): number {
745
+ if (values.length === 0) {
746
+ return 0;
747
+ }
748
+
749
+ const total = values.reduce((sum, current) => sum + current, 0);
750
+ return total / values.length;
751
+ }
752
+
753
+ function applyRelationAggregatePlanToRow(
754
+ row: unknown,
755
+ plan: RelationWithAggregatePlan,
756
+ ): unknown {
757
+ if (!isRecord(row)) {
758
+ return row;
759
+ }
760
+
761
+ const nextRow: Record<string, unknown> = {
762
+ ...row,
763
+ };
764
+
765
+ for (const [relationName, relationPlan] of Object.entries(plan)) {
766
+ const relationData = nextRow[relationName];
767
+ const relationEntries = Array.isArray(relationData)
768
+ ? relationData.filter((entry) => entry !== null && entry !== undefined)
769
+ : relationData === null || relationData === undefined
770
+ ? []
771
+ : [relationData];
772
+
773
+ if (relationPlan.nested) {
774
+ if (Array.isArray(relationData)) {
775
+ nextRow[relationName] = relationData.map((entry) =>
776
+ applyRelationAggregatePlanToRow(entry, relationPlan.nested as RelationWithAggregatePlan),
777
+ );
778
+ } else if (isRecord(relationData)) {
779
+ nextRow[relationName] = applyRelationAggregatePlanToRow(
780
+ relationData,
781
+ relationPlan.nested,
782
+ );
783
+ }
784
+ }
785
+
786
+ if (!relationPlan.count && relationPlan.avgFields.length === 0) {
787
+ continue;
788
+ }
789
+
790
+ const aggregatePayload: Record<string, unknown> = {};
791
+ if (relationPlan.count) {
792
+ aggregatePayload.count = relationEntries.length;
793
+ }
794
+
795
+ if (relationPlan.avgFields.length > 0) {
796
+ const averagePayload: Record<string, number> = {};
797
+ for (const fieldName of relationPlan.avgFields) {
798
+ const numericValues = relationEntries
799
+ .map((entry) =>
800
+ isRecord(entry) ? toFiniteNumber(entry[fieldName]) : null,
801
+ )
802
+ .filter((value): value is number => value !== null);
803
+ averagePayload[fieldName] = computeAverageOrZero(numericValues);
804
+ }
805
+ aggregatePayload.avg = averagePayload;
806
+ }
807
+
808
+ nextRow[relationName + "Aggregate"] = aggregatePayload;
809
+ }
810
+
811
+ return nextRow;
812
+ }
813
+
814
+ function applyRelationAggregatePlanToResult(
815
+ result: unknown,
816
+ plan: RelationWithAggregatePlan | undefined,
817
+ ): unknown {
818
+ if (!hasRelationAggregatePlanEntries(plan)) {
819
+ return result;
820
+ }
821
+
822
+ if (Array.isArray(result)) {
823
+ return result.map((row) => applyRelationAggregatePlanToRow(row, plan));
824
+ }
825
+
826
+ if (result === null || result === undefined) {
827
+ return result;
828
+ }
829
+
830
+ return applyRelationAggregatePlanToRow(result, plan);
831
+ }
832
+
833
+ function hasAggregateWithConstraints(withValue: unknown): boolean {
834
+ if (!isRecord(withValue)) {
835
+ return false;
836
+ }
837
+
838
+ for (const relationValue of Object.values(withValue)) {
839
+ if (!isRecord(relationValue)) {
840
+ continue;
841
+ }
842
+
843
+ if (isRecord(relationValue.where)) {
844
+ return true;
845
+ }
846
+
847
+ if (hasAggregateWithConstraints(relationValue.with)) {
848
+ return true;
849
+ }
850
+ }
851
+
852
+ return false;
853
+ }
854
+
855
+ function rowMatchesAggregateWithConstraints(
856
+ row: unknown,
857
+ withValue: unknown,
858
+ ): boolean {
859
+ if (!isRecord(withValue)) {
860
+ return true;
861
+ }
862
+
863
+ if (!isRecord(row)) {
864
+ return !hasAggregateWithConstraints(withValue);
865
+ }
866
+
867
+ for (const [relationName, relationValue] of Object.entries(withValue)) {
868
+ if (!isRecord(relationValue)) {
869
+ continue;
870
+ }
871
+
872
+ const nestedWith = relationValue.with;
873
+ const requiresPresence =
874
+ isRecord(relationValue.where) || hasAggregateWithConstraints(nestedWith);
875
+ if (!requiresPresence) {
876
+ continue;
877
+ }
878
+
879
+ const relationData = row[relationName];
880
+ if (Array.isArray(relationData)) {
881
+ if (relationData.length === 0) {
882
+ return false;
883
+ }
884
+
885
+ if (hasAggregateWithConstraints(nestedWith)) {
886
+ const hasNestedMatch = relationData.some((entry) =>
887
+ rowMatchesAggregateWithConstraints(entry, nestedWith),
888
+ );
889
+ if (!hasNestedMatch) {
890
+ return false;
891
+ }
892
+ }
893
+
894
+ continue;
895
+ }
896
+
897
+ if (!isRecord(relationData)) {
898
+ return false;
899
+ }
900
+
901
+ if (
902
+ hasAggregateWithConstraints(nestedWith) &&
903
+ !rowMatchesAggregateWithConstraints(relationData, nestedWith)
904
+ ) {
905
+ return false;
906
+ }
907
+ }
908
+
909
+ return true;
910
+ }
911
+
912
+ function splitAggregateFieldPath(field: string): string[] {
913
+ return field
914
+ .split(".")
915
+ .map((segment) => segment.trim())
916
+ .filter((segment) => segment.length > 0);
917
+ }
918
+
919
+ function collectValuesFromPath(
920
+ value: unknown,
921
+ pathSegments: string[],
922
+ index = 0,
923
+ ): unknown[] {
924
+ if (index >= pathSegments.length) {
925
+ return [value];
926
+ }
927
+
928
+ if (value === null || value === undefined) {
929
+ return [];
930
+ }
931
+
932
+ if (Array.isArray(value)) {
933
+ return value.flatMap((entry) =>
934
+ collectValuesFromPath(entry, pathSegments, index),
935
+ );
936
+ }
937
+
938
+ if (!isRecord(value)) {
939
+ return [];
940
+ }
941
+
942
+ const nextValue = value[pathSegments[index]];
943
+ return collectValuesFromPath(nextValue, pathSegments, index + 1);
944
+ }
945
+
946
+ function createDistinctValueKey(value: unknown): string {
947
+ if (value instanceof Date) {
948
+ return "date:" + value.toISOString();
949
+ }
950
+
951
+ if (Array.isArray(value)) {
952
+ return (
953
+ "[" + value.map((entry) => createDistinctValueKey(entry)).join(",") + "]"
954
+ );
955
+ }
956
+
957
+ if (isRecord(value)) {
958
+ const keys = Object.keys(value).sort((left, right) => left.localeCompare(right));
959
+ return (
960
+ "{" +
961
+ keys
962
+ .map((key) => JSON.stringify(key) + ":" + createDistinctValueKey(value[key]))
963
+ .join(",") +
964
+ "}"
965
+ );
966
+ }
967
+
968
+ if (value === null) {
969
+ return "null";
970
+ }
971
+
972
+ if (value === undefined) {
973
+ return "undefined";
974
+ }
975
+
976
+ if (typeof value === "number" && Number.isNaN(value)) {
977
+ return "number:NaN";
978
+ }
979
+
980
+ return typeof value + ":" + String(value);
981
+ }
982
+
983
+ function countAggregateValues(values: unknown[], distinct: boolean): number {
984
+ const nonNullValues = values.filter(
985
+ (value) => value !== null && value !== undefined,
986
+ );
987
+ if (!distinct) {
988
+ return nonNullValues.length;
989
+ }
990
+
991
+ const seen = new Set<string>();
992
+ for (const value of nonNullValues) {
993
+ seen.add(createDistinctValueKey(value));
994
+ }
995
+
996
+ return seen.size;
997
+ }
998
+
999
+ function toFiniteNumber(value: unknown): number | null {
1000
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1001
+ return null;
1002
+ }
1003
+
1004
+ return value;
1005
+ }
1006
+
1007
+ function averageAggregateValues(values: number[]): number | null {
1008
+ if (values.length === 0) {
1009
+ return null;
1010
+ }
1011
+
1012
+ const total = values.reduce((sum, current) => sum + current, 0);
1013
+ return total / values.length;
1014
+ }
1015
+
1016
+ function inferConflictTarget(table: unknown): string[] {
1017
+ if (!table) {
1018
+ return [];
1019
+ }
1020
+
1021
+ const columns = getTableColumns(table as never) as Record<string, unknown>;
1022
+ if (columns.id) {
1023
+ return ["id"];
1024
+ }
1025
+
1026
+ return [];
1027
+ }
1028
+ `;
1029
+ }