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.
- package/Documentation.md +8 -8
- package/cli/commands/index.ts +1 -1
- package/cli/generate.ts +24 -0
- package/cli/index.ts +1 -1
- package/cli/schema-compiler.ts +180 -2
- package/cli/templates/core/README.md +1 -1
- package/cli/templates/handlers/generators/handlers.ts +2 -2
- package/cli/templates/handlers/generators/registration/modules/realtime/utils.ts +4 -10
- package/cli/templates/handlers/generators/types/context.ts +8 -2
- package/cli/templates/handlers/generators/types/query-definitions/filter-and-where-types.ts +6 -16
- package/cli/templates/handlers/generators/types/query-definitions/query-helper-functions.ts +296 -17
- package/cli/templates/handlers/index.ts +2 -1
- package/cli/templates/handlers/types.ts +2 -2
- package/dist/cli/index.js +582 -301
- package/dist/cli/index.mjs +582 -301
- package/dist/index.d.mts +81 -2
- package/dist/index.d.ts +81 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/schema.ts +229 -3
|
@@ -4,11 +4,285 @@ export function generateQueryHelperFunctionsSection(): string {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
function readOperatorValue(record: Record<string, unknown>, key: string): unknown {
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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
|
|
367
|
+
operand.minDistance ??
|
|
94
368
|
operand.gte ??
|
|
95
|
-
operand
|
|
96
|
-
operand.gt ??
|
|
97
|
-
operand.$gt;
|
|
369
|
+
operand.gt;
|
|
98
370
|
const maxDistance =
|
|
99
|
-
operand
|
|
371
|
+
operand.maxDistance ??
|
|
100
372
|
operand.lte ??
|
|
101
|
-
operand
|
|
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
|
-
:
|
|
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"
|
|
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
|
}
|