appflare 0.2.36 → 0.2.38

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.
@@ -4,11 +4,285 @@ export function generateQueryHelperFunctionsSection(): string {
4
4
  }
5
5
 
6
6
  function readOperatorValue(record: Record<string, unknown>, key: string): unknown {
7
- if (record[key] !== undefined) {
8
- return record[key];
7
+ return record[key];
8
+ }
9
+
10
+ function getJsonColumnsMap(): Record<string, Record<string, { shape: unknown }>> {
11
+ const rawValue = (mergedSchema as Record<string, unknown>)[
12
+ "__appflareJsonColumns"
13
+ ];
14
+ if (!isRecord(rawValue)) {
15
+ return {};
16
+ }
17
+ const map: Record<string, Record<string, { shape: unknown }>> = {};
18
+ for (const [tableName, tableValue] of Object.entries(rawValue)) {
19
+ if (isRecord(tableValue)) {
20
+ map[tableName] = tableValue as Record<string, { shape: unknown }>;
21
+ }
9
22
  }
10
- const prefixed = "$" + key;
11
- return record[prefixed];
23
+ return map;
24
+ }
25
+
26
+ const jsonColumnsMap = getJsonColumnsMap();
27
+
28
+ function getJsonColumnShape(
29
+ tableName: string | undefined,
30
+ fieldName: string,
31
+ ): { kind: string } | null {
32
+ if (!tableName) return null;
33
+ const tableCols = jsonColumnsMap[tableName];
34
+ if (!tableCols) return null;
35
+ const col = tableCols[fieldName];
36
+ if (!col || !isRecord(col.shape)) return null;
37
+ return col.shape as { kind: string };
38
+ }
39
+
40
+ function buildJsonFieldFilter(
41
+ fieldName: string,
42
+ field: unknown,
43
+ value: unknown,
44
+ tableName: string | undefined,
45
+ ): SQL | undefined {
46
+ const shape = getJsonColumnShape(tableName, fieldName);
47
+ if (!shape) return undefined;
48
+
49
+ if (shape.kind === "array") {
50
+ return buildJsonArrayFilter(fieldName, field, value);
51
+ }
52
+ if (shape.kind === "object") {
53
+ return buildJsonObjectFilter(fieldName, field, value);
54
+ }
55
+ return undefined;
56
+ }
57
+
58
+ function buildJsonArrayElementMatchCondition(
59
+ field: unknown,
60
+ matchValue: unknown,
61
+ ): SQL {
62
+ if (!isRecord(matchValue) || matchValue instanceof Date || Array.isArray(matchValue)) {
63
+ return sql\`EXISTS (SELECT 1 FROM json_each(\${field as never}) j WHERE j.value = \${matchValue})\`;
64
+ }
65
+
66
+ const propConditions: SQL[] = [];
67
+ for (const [key, propValue] of Object.entries(matchValue)) {
68
+ const jsonPath = '$.' + key;
69
+ if (!isRecord(propValue) || propValue instanceof Date || Array.isArray(propValue)) {
70
+ propConditions.push(
71
+ sql\`EXISTS (SELECT 1 FROM json_each(\${field as never}) j WHERE json_extract(j.value, \${jsonPath}) = \${propValue})\`,
72
+ );
73
+ } else {
74
+ const nestedConditions: SQL[] = [];
75
+ const eqVal = readOperatorValue(propValue, "eq");
76
+ if (eqVal !== undefined) {
77
+ nestedConditions.push(
78
+ sql\`EXISTS (SELECT 1 FROM json_each(\${field as never}) j WHERE json_extract(j.value, \${jsonPath}) = \${eqVal})\`,
79
+ );
80
+ }
81
+ const neVal = readOperatorValue(propValue, "ne");
82
+ if (neVal !== undefined) {
83
+ nestedConditions.push(
84
+ sql\`EXISTS (SELECT 1 FROM json_each(\${field as never}) j WHERE json_extract(j.value, \${jsonPath}) != \${neVal})\`,
85
+ );
86
+ }
87
+ const gtVal = readOperatorValue(propValue, "gt");
88
+ if (gtVal !== undefined) {
89
+ nestedConditions.push(
90
+ sql\`EXISTS (SELECT 1 FROM json_each(\${field as never}) j WHERE json_extract(j.value, \${jsonPath}) > \${gtVal})\`,
91
+ );
92
+ }
93
+ const gteVal = readOperatorValue(propValue, "gte");
94
+ if (gteVal !== undefined) {
95
+ nestedConditions.push(
96
+ sql\`EXISTS (SELECT 1 FROM json_each(\${field as never}) j WHERE json_extract(j.value, \${jsonPath}) >= \${gteVal})\`,
97
+ );
98
+ }
99
+ const ltVal = readOperatorValue(propValue, "lt");
100
+ if (ltVal !== undefined) {
101
+ nestedConditions.push(
102
+ sql\`EXISTS (SELECT 1 FROM json_each(\${field as never}) j WHERE json_extract(j.value, \${jsonPath}) < \${ltVal})\`,
103
+ );
104
+ }
105
+ const lteVal = readOperatorValue(propValue, "lte");
106
+ if (lteVal !== undefined) {
107
+ nestedConditions.push(
108
+ sql\`EXISTS (SELECT 1 FROM json_each(\${field as never}) j WHERE json_extract(j.value, \${jsonPath}) <= \${lteVal})\`,
109
+ );
110
+ }
111
+ const inVal = readOperatorValue(propValue, "in");
112
+ if (Array.isArray(inVal) && inVal.length > 0) {
113
+ nestedConditions.push(
114
+ sql\`EXISTS (SELECT 1 FROM json_each(\${field as never}) j WHERE json_extract(j.value, \${jsonPath}) IN (\${sql.join(inVal.map((v) => sql\`\${v}\`), sql\`, \`)})\`,
115
+ );
116
+ }
117
+ if (nestedConditions.length > 0) {
118
+ propConditions.push(
119
+ nestedConditions.length === 1
120
+ ? nestedConditions[0]
121
+ : and(...nestedConditions),
122
+ );
123
+ }
124
+ }
125
+ }
126
+
127
+ if (propConditions.length === 0) {
128
+ return sql\`EXISTS (SELECT 1 FROM json_each(\${field as never}) j WHERE json_type(j.value) = 'object')\`;
129
+ }
130
+
131
+ if (propConditions.length === 1) {
132
+ return propConditions[0];
133
+ }
134
+
135
+ return and(...propConditions);
136
+ }
137
+
138
+ function buildJsonArrayFilter(
139
+ fieldName: string,
140
+ field: unknown,
141
+ value: unknown,
142
+ ): SQL | undefined {
143
+ if (!isRecord(value) || value instanceof Date || Array.isArray(value)) {
144
+ return sql\`json_extract(\${field as never}, '$') = json(\${JSON.stringify(value)})\`;
145
+ }
146
+
147
+ const filters: SQL[] = [];
148
+
149
+ const includesValue = readOperatorValue(value, "includes");
150
+ if (Array.isArray(includesValue) && includesValue.length > 0) {
151
+ const conditions = includesValue.map((v) =>
152
+ buildJsonArrayElementMatchCondition(field, v),
153
+ );
154
+ filters.push(and(...conditions));
155
+ }
156
+
157
+ const includesAnyValue = readOperatorValue(value, "includesAny");
158
+ if (Array.isArray(includesAnyValue) && includesAnyValue.length > 0) {
159
+ const conditions = includesAnyValue.map((v) =>
160
+ buildJsonArrayElementMatchCondition(field, v),
161
+ );
162
+ filters.push(sql\`(\${sql.join(conditions, sql\` OR \`)})\`);
163
+ }
164
+
165
+ const lengthValue = readOperatorValue(value, "length");
166
+ if (lengthValue !== undefined) {
167
+ filters.push(sql\`json_array_length(\${field as never}) = \${lengthValue}\`);
168
+ }
169
+
170
+ const eqValue = readOperatorValue(value, "eq");
171
+ if (eqValue !== undefined) {
172
+ filters.push(sql\`json_extract(\${field as never}, '$') = json(\${JSON.stringify(eqValue)})\`);
173
+ }
174
+
175
+ const neValue = readOperatorValue(value, "ne");
176
+ if (neValue !== undefined) {
177
+ filters.push(sql\`json_extract(\${field as never}, '$') != json(\${JSON.stringify(neValue)})\`);
178
+ }
179
+
180
+ if (filters.length === 0) {
181
+ return undefined;
182
+ }
183
+ if (filters.length === 1) return filters[0];
184
+ return and(...filters);
185
+ }
186
+
187
+ function buildJsonObjectFilter(
188
+ fieldName: string,
189
+ field: unknown,
190
+ value: unknown,
191
+ ): SQL | undefined {
192
+ if (!isRecord(value) || value instanceof Date || Array.isArray(value)) {
193
+ return sql\`json_extract(\${field as never}, '$') = json(\${JSON.stringify(value)})\`;
194
+ }
195
+
196
+ const filters: SQL[] = [];
197
+
198
+ for (const [key, opValue] of Object.entries(value)) {
199
+ if (key === "eq") {
200
+ filters.push(sql\`json_extract(\${field as never}, '$') = json(\${JSON.stringify(opValue)})\`);
201
+ continue;
202
+ }
203
+ if (key === "ne") {
204
+ filters.push(sql\`json_extract(\${field as never}, '$') != json(\${JSON.stringify(opValue)})\`);
205
+ continue;
206
+ }
207
+ if (key === "exists") {
208
+ filters.push(
209
+ opValue
210
+ ? sql\`json_extract(\${field as never}, '$') IS NOT NULL\`
211
+ : sql\`json_extract(\${field as never}, '$') IS NULL\`,
212
+ );
213
+ continue;
214
+ }
215
+
216
+ const nestedFilters = buildJsonObjectPathFilter(field, key, opValue);
217
+ if (nestedFilters) {
218
+ filters.push(nestedFilters);
219
+ }
220
+ }
221
+
222
+ if (filters.length === 0) return undefined;
223
+ if (filters.length === 1) return filters[0];
224
+ return and(...filters);
225
+ }
226
+
227
+ function buildJsonObjectPathFilter(
228
+ field: unknown,
229
+ path: string,
230
+ opValue: unknown,
231
+ ): SQL | undefined {
232
+ const jsonPath = '$.' + path;
233
+ if (!isRecord(opValue) || opValue instanceof Date || Array.isArray(opValue)) {
234
+ return sql\`json_extract(\${field as never}, \${jsonPath}) = \${opValue}\`;
235
+ }
236
+
237
+ const filters: SQL[] = [];
238
+
239
+ const eqVal = readOperatorValue(opValue, "eq");
240
+ if (eqVal !== undefined) {
241
+ filters.push(sql\`json_extract(\${field as never}, \${jsonPath}) = \${eqVal}\`);
242
+ }
243
+
244
+ const neVal = readOperatorValue(opValue, "ne");
245
+ if (neVal !== undefined) {
246
+ filters.push(sql\`json_extract(\${field as never}, \${jsonPath}) != \${neVal}\`);
247
+ }
248
+
249
+ const gtVal = readOperatorValue(opValue, "gt");
250
+ if (gtVal !== undefined) {
251
+ filters.push(sql\`json_extract(\${field as never}, \${jsonPath}) > \${gtVal}\`);
252
+ }
253
+
254
+ const gteVal = readOperatorValue(opValue, "gte");
255
+ if (gteVal !== undefined) {
256
+ filters.push(sql\`json_extract(\${field as never}, \${jsonPath}) >= \${gteVal}\`);
257
+ }
258
+
259
+ const ltVal = readOperatorValue(opValue, "lt");
260
+ if (ltVal !== undefined) {
261
+ filters.push(sql\`json_extract(\${field as never}, \${jsonPath}) < \${ltVal}\`);
262
+ }
263
+
264
+ const lteVal = readOperatorValue(opValue, "lte");
265
+ if (lteVal !== undefined) {
266
+ filters.push(sql\`json_extract(\${field as never}, \${jsonPath}) <= \${lteVal}\`);
267
+ }
268
+
269
+ const inVal = readOperatorValue(opValue, "in");
270
+ if (Array.isArray(inVal) && inVal.length > 0) {
271
+ filters.push(sql\`json_extract(\${field as never}, \${jsonPath}) IN (\${sql.join(inVal.map((v) => sql\`\${v}\`), sql\`, \`)})\`);
272
+ }
273
+
274
+ const existsVal = readOperatorValue(opValue, "exists");
275
+ if (typeof existsVal === "boolean") {
276
+ filters.push(
277
+ existsVal
278
+ ? sql\`json_extract(\${field as never}, \${jsonPath}) IS NOT NULL\`
279
+ : sql\`json_extract(\${field as never}, \${jsonPath}) IS NULL\`,
280
+ );
281
+ }
282
+
283
+ if (filters.length === 0) return undefined;
284
+ if (filters.length === 1) return filters[0];
285
+ return and(...filters);
12
286
  }
13
287
 
14
288
  function normalizeGeoPoint(
@@ -90,17 +364,13 @@ function createGeoDistanceFilter(
90
364
  )\`;
91
365
 
92
366
  const minDistance =
93
- operand.$minDistance ??
367
+ operand.minDistance ??
94
368
  operand.gte ??
95
- operand.$gte ??
96
- operand.gt ??
97
- operand.$gt;
369
+ operand.gt;
98
370
  const maxDistance =
99
- operand.$maxDistance ??
371
+ operand.maxDistance ??
100
372
  operand.lte ??
101
- operand.$lte ??
102
- operand.lt ??
103
- operand.$lt;
373
+ operand.lt;
104
374
 
105
375
  const filters: SQL[] = [];
106
376
  if (typeof minDistance === "number") {
@@ -126,7 +396,18 @@ function buildFieldFilter(
126
396
  field: unknown,
127
397
  value: unknown,
128
398
  fields: Record<string, unknown>,
399
+ tableName?: string,
129
400
  ): SQL | undefined {
401
+ const jsonFilter = buildJsonFieldFilter(fieldName, field, value, tableName);
402
+ if (jsonFilter) return jsonFilter;
403
+
404
+ const isArrayColumn = isRecord(value) && !Array.isArray(value) && !(value instanceof Date) &&
405
+ ("includes" in value || "includesAny" in value || "length" in value);
406
+ if (isArrayColumn) {
407
+ const arrayFilter = buildJsonArrayFilter(fieldName, field, value);
408
+ if (arrayFilter) return arrayFilter;
409
+ }
410
+
130
411
  if (!isRecord(value) || value instanceof Date || Array.isArray(value)) {
131
412
  return eq(field as never, value as never);
132
413
  }
@@ -237,9 +518,7 @@ function buildWhereFilterFromFields(
237
518
 
238
519
  const topLevelGeoWithin = isRecord(where.geoWithin)
239
520
  ? (where.geoWithin as GeoWithinOperand)
240
- : isRecord(where.$geoWithin)
241
- ? (where.$geoWithin as GeoWithinOperand)
242
- : undefined;
521
+ : undefined;
243
522
 
244
523
  if (topLevelGeoWithin) {
245
524
  const geoFilter = createGeoDistanceFilter(topLevelGeoWithin, fields, tableName);
@@ -249,7 +528,7 @@ function buildWhereFilterFromFields(
249
528
  }
250
529
 
251
530
  for (const [fieldName, fieldValue] of Object.entries(where)) {
252
- if (fieldName === "geoWithin" || fieldName === "$geoWithin") {
531
+ if (fieldName === "geoWithin") {
253
532
  continue;
254
533
  }
255
534
 
@@ -277,7 +556,7 @@ function buildWhereFilterFromFields(
277
556
  continue;
278
557
  }
279
558
 
280
- const filter = buildFieldFilter(fieldName, field, fieldValue, fields);
559
+ const filter = buildFieldFilter(fieldName, field, fieldValue, fields, tableName);
281
560
  if (filter) {
282
561
  filters.push(filter);
283
562
  }
@@ -13,8 +13,9 @@ export function generateHandlersArtifacts(
13
13
  schemaImportPath: string,
14
14
  operations: DiscoveredHandlerOperation[],
15
15
  defaultR2Binding?: string,
16
+ roles: string[] = [],
16
17
  ): GeneratedHandlerArtifact[] {
17
- const handlersSource = generateHandlersSource(schemaImportPath);
18
+ const handlersSource = generateHandlersSource(schemaImportPath, roles);
18
19
 
19
20
  const contextSource = generateContextSource(defaultR2Binding);
20
21
 
@@ -4,12 +4,12 @@ import { generateTypesQueryRuntimeSection } from "./generators/types/query-runti
4
4
  import { generateTypesContextSection } from "./generators/types/context";
5
5
  import { generateTypesOperationsSection } from "./generators/types/operations";
6
6
 
7
- export function generateTypes(): string {
7
+ export function generateTypes(roles: string[] = []): string {
8
8
  return [
9
9
  generateTypesCoreSection(),
10
10
  generateTypesQueryDefinitionsSection(),
11
11
  generateTypesQueryRuntimeSection(),
12
- generateTypesContextSection(),
12
+ generateTypesContextSection(roles),
13
13
  generateTypesOperationsSection(),
14
14
  ].join("\n\n");
15
15
  }