@ypanagidis/joqi 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +444 -0
- package/dist/effect.d.ts +2 -0
- package/dist/effect.js +3 -0
- package/dist/index-w8tJonRi.d.ts +1213 -0
- package/dist/index-w8tJonRi.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/src-DGGMT7nT.js +1298 -0
- package/dist/src-DGGMT7nT.js.map +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1,1298 @@
|
|
|
1
|
+
import { ZodError, z } from "zod";
|
|
2
|
+
import { Effect, Schema } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/specs/query.ts
|
|
5
|
+
const QueryVersionSchema = z.literal("v1");
|
|
6
|
+
const QuerySortDirectionSchema = z.enum(["asc", "desc"]);
|
|
7
|
+
const QueryFilterOperatorSchema = z.enum([
|
|
8
|
+
"eq",
|
|
9
|
+
"neq",
|
|
10
|
+
"gt",
|
|
11
|
+
"gte",
|
|
12
|
+
"lt",
|
|
13
|
+
"lte",
|
|
14
|
+
"in",
|
|
15
|
+
"contains",
|
|
16
|
+
"startsWith",
|
|
17
|
+
"endsWith",
|
|
18
|
+
"isNull",
|
|
19
|
+
"isNotNull"
|
|
20
|
+
]);
|
|
21
|
+
const JsonScalarSchema = z.union([
|
|
22
|
+
z.string(),
|
|
23
|
+
z.number(),
|
|
24
|
+
z.boolean(),
|
|
25
|
+
z.null()
|
|
26
|
+
]);
|
|
27
|
+
const QueryParamRefSchema = z.object({ $param: z.string().min(1) }).strict();
|
|
28
|
+
const JsonValueSchema = z.lazy(() => z.union([
|
|
29
|
+
JsonScalarSchema,
|
|
30
|
+
z.array(JsonValueSchema),
|
|
31
|
+
z.record(z.string(), JsonValueSchema)
|
|
32
|
+
]));
|
|
33
|
+
const JsonLiteralValueSchema = JsonValueSchema.refine((value) => value === null || typeof value !== "object" || Array.isArray(value) || !Object.hasOwn(value, "$param"), { message: "Use {$param: string} without additional properties for parameter refs" });
|
|
34
|
+
const QueryValueSchema = z.union([QueryParamRefSchema, JsonLiteralValueSchema]);
|
|
35
|
+
const QueryLimitValueSchema = z.union([z.number().int().nonnegative(), QueryParamRefSchema]);
|
|
36
|
+
const QueryParamsSchema = z.record(z.string(), JsonValueSchema);
|
|
37
|
+
const QueryFilterSchema = z.lazy(() => z.union([
|
|
38
|
+
z.object({ and: z.array(QueryFilterSchema).min(1) }),
|
|
39
|
+
z.object({ or: z.array(QueryFilterSchema).min(1) }),
|
|
40
|
+
z.object({
|
|
41
|
+
field: z.string().min(1),
|
|
42
|
+
op: QueryFilterOperatorSchema,
|
|
43
|
+
value: QueryValueSchema.optional()
|
|
44
|
+
})
|
|
45
|
+
]));
|
|
46
|
+
const QueryOrderBySchema = z.object({
|
|
47
|
+
field: z.string().min(1),
|
|
48
|
+
direction: QuerySortDirectionSchema
|
|
49
|
+
});
|
|
50
|
+
const QuerySpecSchema = z.object({
|
|
51
|
+
version: QueryVersionSchema,
|
|
52
|
+
source: z.string().min(1),
|
|
53
|
+
select: z.array(z.string().min(1)).min(1),
|
|
54
|
+
where: QueryFilterSchema.optional(),
|
|
55
|
+
groupBy: z.array(z.string().min(1)).optional(),
|
|
56
|
+
orderBy: z.array(QueryOrderBySchema).optional(),
|
|
57
|
+
limit: QueryLimitValueSchema.optional(),
|
|
58
|
+
offset: QueryLimitValueSchema.optional()
|
|
59
|
+
});
|
|
60
|
+
const parseQuerySpec = (input) => QuerySpecSchema.parse(input);
|
|
61
|
+
const safeParseQuerySpec = (input) => QuerySpecSchema.safeParse(input);
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/specs/registries.ts
|
|
65
|
+
const RegistryVersionSchema = z.literal("v1");
|
|
66
|
+
const FieldTypeSchema = z.enum([
|
|
67
|
+
"string",
|
|
68
|
+
"number",
|
|
69
|
+
"boolean",
|
|
70
|
+
"date",
|
|
71
|
+
"datetime",
|
|
72
|
+
"json",
|
|
73
|
+
"enum",
|
|
74
|
+
"unknown"
|
|
75
|
+
]);
|
|
76
|
+
const PhysicalSourceKindSchema = z.enum([
|
|
77
|
+
"table",
|
|
78
|
+
"view",
|
|
79
|
+
"model"
|
|
80
|
+
]);
|
|
81
|
+
const RelationKindSchema = z.enum(["one", "many"]);
|
|
82
|
+
const AggregationSchema = z.enum([
|
|
83
|
+
"count",
|
|
84
|
+
"sum",
|
|
85
|
+
"avg",
|
|
86
|
+
"min",
|
|
87
|
+
"max"
|
|
88
|
+
]);
|
|
89
|
+
const ExposureModeSchema = z.enum(["deny-by-default", "allow-by-default"]);
|
|
90
|
+
const nonEmptyRecord = (valueSchema) => z.record(z.string().min(1), valueSchema).refine((value) => Object.keys(value).length > 0, { message: "Expected at least one entry" });
|
|
91
|
+
const AdapterMetaSchema = z.record(z.string().min(1), z.unknown());
|
|
92
|
+
const PhysicalFieldSchema = z.object({
|
|
93
|
+
type: FieldTypeSchema,
|
|
94
|
+
nullable: z.boolean(),
|
|
95
|
+
enumValues: z.array(z.string()).optional(),
|
|
96
|
+
adapterMeta: AdapterMetaSchema.optional()
|
|
97
|
+
});
|
|
98
|
+
const PhysicalRelationSchema = z.object({
|
|
99
|
+
kind: RelationKindSchema,
|
|
100
|
+
target: z.string().min(1),
|
|
101
|
+
localFields: z.array(z.string().min(1)).min(1),
|
|
102
|
+
foreignFields: z.array(z.string().min(1)).min(1),
|
|
103
|
+
nullable: z.boolean().optional(),
|
|
104
|
+
adapterMeta: AdapterMetaSchema.optional()
|
|
105
|
+
});
|
|
106
|
+
const PhysicalSourceSchema = z.object({
|
|
107
|
+
kind: PhysicalSourceKindSchema,
|
|
108
|
+
name: z.string().min(1),
|
|
109
|
+
schema: z.string().min(1).optional(),
|
|
110
|
+
primaryKey: z.array(z.string().min(1)).optional(),
|
|
111
|
+
fields: nonEmptyRecord(PhysicalFieldSchema),
|
|
112
|
+
relations: z.record(z.string().min(1), PhysicalRelationSchema).optional(),
|
|
113
|
+
adapterMeta: AdapterMetaSchema.optional()
|
|
114
|
+
});
|
|
115
|
+
const PhysicalRegistrySchema = z.object({
|
|
116
|
+
version: RegistryVersionSchema,
|
|
117
|
+
sources: nonEmptyRecord(PhysicalSourceSchema)
|
|
118
|
+
});
|
|
119
|
+
const FieldPolicySchema = z.object({
|
|
120
|
+
expose: z.boolean().optional(),
|
|
121
|
+
exposeAs: z.string().min(1).optional(),
|
|
122
|
+
label: z.string().min(1).optional(),
|
|
123
|
+
description: z.string().min(1).optional(),
|
|
124
|
+
type: FieldTypeSchema.exclude(["unknown"]).optional(),
|
|
125
|
+
selectable: z.boolean().optional(),
|
|
126
|
+
filterable: z.boolean().optional(),
|
|
127
|
+
sortable: z.boolean().optional(),
|
|
128
|
+
groupable: z.boolean().optional(),
|
|
129
|
+
operators: z.array(QueryFilterOperatorSchema).optional(),
|
|
130
|
+
aggregations: z.array(AggregationSchema).optional()
|
|
131
|
+
});
|
|
132
|
+
const RelationPolicySchema = z.object({
|
|
133
|
+
expose: z.boolean().optional(),
|
|
134
|
+
exposeAs: z.string().min(1).optional(),
|
|
135
|
+
target: z.string().min(1).optional(),
|
|
136
|
+
selectable: z.boolean().optional(),
|
|
137
|
+
filterable: z.boolean().optional(),
|
|
138
|
+
maxDepth: z.number().int().nonnegative().optional()
|
|
139
|
+
});
|
|
140
|
+
const SourcePolicySchema = z.object({
|
|
141
|
+
expose: z.boolean().optional(),
|
|
142
|
+
exposeAs: z.string().min(1).optional(),
|
|
143
|
+
label: z.string().min(1).optional(),
|
|
144
|
+
description: z.string().min(1).optional(),
|
|
145
|
+
selectable: z.boolean().optional(),
|
|
146
|
+
filterable: z.boolean().optional(),
|
|
147
|
+
sortable: z.boolean().optional(),
|
|
148
|
+
maxLimit: z.number().int().nonnegative().optional(),
|
|
149
|
+
defaultLimit: z.number().int().nonnegative().optional(),
|
|
150
|
+
fields: z.record(z.string().min(1), FieldPolicySchema).optional(),
|
|
151
|
+
relations: z.record(z.string().min(1), RelationPolicySchema).optional()
|
|
152
|
+
});
|
|
153
|
+
const RegistryPolicySchema = z.object({
|
|
154
|
+
version: RegistryVersionSchema,
|
|
155
|
+
sources: z.record(z.string().min(1), SourcePolicySchema).optional()
|
|
156
|
+
});
|
|
157
|
+
const SourceDefaultsSchema = z.object({
|
|
158
|
+
selectable: z.boolean().optional(),
|
|
159
|
+
filterable: z.boolean().optional(),
|
|
160
|
+
sortable: z.boolean().optional(),
|
|
161
|
+
maxLimit: z.number().int().nonnegative().optional()
|
|
162
|
+
});
|
|
163
|
+
const FieldDefaultsSchema = z.object({
|
|
164
|
+
selectable: z.boolean().optional(),
|
|
165
|
+
filterable: z.boolean().optional(),
|
|
166
|
+
sortable: z.boolean().optional(),
|
|
167
|
+
groupable: z.boolean().optional(),
|
|
168
|
+
operators: z.union([z.literal("byType"), z.array(QueryFilterOperatorSchema)]).optional()
|
|
169
|
+
});
|
|
170
|
+
const RelationDefaultsSchema = z.object({
|
|
171
|
+
selectable: z.boolean().optional(),
|
|
172
|
+
filterable: z.boolean().optional(),
|
|
173
|
+
maxDepth: z.number().int().nonnegative().optional()
|
|
174
|
+
});
|
|
175
|
+
const RegistryDefaultsSchema = z.object({
|
|
176
|
+
exposure: ExposureModeSchema.default("deny-by-default"),
|
|
177
|
+
source: SourceDefaultsSchema.optional(),
|
|
178
|
+
field: FieldDefaultsSchema.optional(),
|
|
179
|
+
relation: RelationDefaultsSchema.optional()
|
|
180
|
+
});
|
|
181
|
+
const ResolvedFieldSchema = z.object({
|
|
182
|
+
physicalSource: z.string().min(1),
|
|
183
|
+
physicalField: z.string().min(1),
|
|
184
|
+
publicName: z.string().min(1),
|
|
185
|
+
type: FieldTypeSchema,
|
|
186
|
+
nullable: z.boolean(),
|
|
187
|
+
label: z.string().min(1).optional(),
|
|
188
|
+
description: z.string().min(1).optional(),
|
|
189
|
+
selectable: z.boolean(),
|
|
190
|
+
filterable: z.boolean(),
|
|
191
|
+
sortable: z.boolean(),
|
|
192
|
+
groupable: z.boolean(),
|
|
193
|
+
operators: z.array(QueryFilterOperatorSchema),
|
|
194
|
+
aggregations: z.array(AggregationSchema)
|
|
195
|
+
});
|
|
196
|
+
const ResolvedRelationSchema = z.object({
|
|
197
|
+
physicalSource: z.string().min(1),
|
|
198
|
+
physicalRelation: z.string().min(1),
|
|
199
|
+
publicName: z.string().min(1),
|
|
200
|
+
target: z.string().min(1),
|
|
201
|
+
kind: RelationKindSchema,
|
|
202
|
+
localFields: z.array(z.string().min(1)).min(1),
|
|
203
|
+
foreignFields: z.array(z.string().min(1)).min(1),
|
|
204
|
+
selectable: z.boolean(),
|
|
205
|
+
filterable: z.boolean(),
|
|
206
|
+
maxDepth: z.number().int().nonnegative()
|
|
207
|
+
});
|
|
208
|
+
const ResolvedSourceSchema = z.object({
|
|
209
|
+
physicalSource: z.string().min(1),
|
|
210
|
+
publicName: z.string().min(1),
|
|
211
|
+
label: z.string().min(1).optional(),
|
|
212
|
+
description: z.string().min(1).optional(),
|
|
213
|
+
maxLimit: z.number().int().nonnegative().optional(),
|
|
214
|
+
defaultLimit: z.number().int().nonnegative().optional(),
|
|
215
|
+
fields: nonEmptyRecord(ResolvedFieldSchema),
|
|
216
|
+
relations: z.record(z.string().min(1), ResolvedRelationSchema)
|
|
217
|
+
});
|
|
218
|
+
const ResolvedRegistrySchema = z.object({
|
|
219
|
+
version: RegistryVersionSchema,
|
|
220
|
+
sources: z.record(z.string().min(1), ResolvedSourceSchema)
|
|
221
|
+
});
|
|
222
|
+
const parsePhysicalRegistry = (input) => PhysicalRegistrySchema.parse(input);
|
|
223
|
+
const parseRegistryPolicy = (input) => RegistryPolicySchema.parse(input);
|
|
224
|
+
const parseRegistryDefaults = (input) => RegistryDefaultsSchema.parse(input);
|
|
225
|
+
const parseResolvedRegistry = (input) => ResolvedRegistrySchema.parse(input);
|
|
226
|
+
const safeParsePhysicalRegistry = (input) => PhysicalRegistrySchema.safeParse(input);
|
|
227
|
+
const safeParseRegistryPolicy = (input) => RegistryPolicySchema.safeParse(input);
|
|
228
|
+
const safeParseRegistryDefaults = (input) => RegistryDefaultsSchema.safeParse(input);
|
|
229
|
+
const safeParseResolvedRegistry = (input) => ResolvedRegistrySchema.safeParse(input);
|
|
230
|
+
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/registry/resolve.ts
|
|
233
|
+
const FieldTypeErrorSchema = Schema.Literal("string", "number", "boolean", "date", "datetime", "json", "enum", "unknown");
|
|
234
|
+
const QueryFilterOperatorErrorSchema$1 = Schema.Literal("eq", "neq", "gt", "gte", "lt", "lte", "in", "contains", "startsWith", "endsWith", "isNull", "isNotNull");
|
|
235
|
+
const RegistryResolutionIssueSchema = Schema.Union(Schema.Struct({
|
|
236
|
+
code: Schema.Literal("unknown_source"),
|
|
237
|
+
source: Schema.String
|
|
238
|
+
}), Schema.Struct({
|
|
239
|
+
code: Schema.Literal("unknown_field"),
|
|
240
|
+
source: Schema.String,
|
|
241
|
+
field: Schema.String
|
|
242
|
+
}), Schema.Struct({
|
|
243
|
+
code: Schema.Literal("unknown_relation"),
|
|
244
|
+
source: Schema.String,
|
|
245
|
+
relation: Schema.String
|
|
246
|
+
}), Schema.Struct({
|
|
247
|
+
code: Schema.Literal("duplicate_public_source"),
|
|
248
|
+
publicName: Schema.String,
|
|
249
|
+
sources: Schema.Array(Schema.String)
|
|
250
|
+
}), Schema.Struct({
|
|
251
|
+
code: Schema.Literal("duplicate_public_field"),
|
|
252
|
+
source: Schema.String,
|
|
253
|
+
publicName: Schema.String,
|
|
254
|
+
fields: Schema.Array(Schema.String)
|
|
255
|
+
}), Schema.Struct({
|
|
256
|
+
code: Schema.Literal("duplicate_public_relation"),
|
|
257
|
+
source: Schema.String,
|
|
258
|
+
publicName: Schema.String,
|
|
259
|
+
relations: Schema.Array(Schema.String)
|
|
260
|
+
}), Schema.Struct({
|
|
261
|
+
code: Schema.Literal("source_has_no_fields"),
|
|
262
|
+
source: Schema.String,
|
|
263
|
+
publicName: Schema.String
|
|
264
|
+
}), Schema.Struct({
|
|
265
|
+
code: Schema.Literal("invalid_default_limit"),
|
|
266
|
+
source: Schema.String,
|
|
267
|
+
defaultLimit: Schema.Number,
|
|
268
|
+
maxLimit: Schema.Number
|
|
269
|
+
}), Schema.Struct({
|
|
270
|
+
code: Schema.Literal("unknown_relation_target"),
|
|
271
|
+
source: Schema.String,
|
|
272
|
+
relation: Schema.String,
|
|
273
|
+
target: Schema.String
|
|
274
|
+
}), Schema.Struct({
|
|
275
|
+
code: Schema.Literal("invalid_operator_for_field_type"),
|
|
276
|
+
source: Schema.String,
|
|
277
|
+
field: Schema.String,
|
|
278
|
+
type: FieldTypeErrorSchema,
|
|
279
|
+
operator: QueryFilterOperatorErrorSchema$1
|
|
280
|
+
}));
|
|
281
|
+
var RegistryResolutionError = class extends Schema.TaggedError()("RegistryResolutionError", { issues: Schema.Array(RegistryResolutionIssueSchema) }) {};
|
|
282
|
+
var RegistryParseError = class extends Schema.TaggedError()("RegistryParseError", { error: Schema.Defect }) {};
|
|
283
|
+
const resolveRegistryEffect = Effect.fn("resolveRegistry")(function* (input) {
|
|
284
|
+
const physical = yield* parseRegistryInput(() => PhysicalRegistrySchema.parse(input.physical));
|
|
285
|
+
const defaults = yield* parseRegistryInput(() => RegistryDefaultsSchema.parse(input.defaults ?? {}));
|
|
286
|
+
const policy = mergePolicies(yield* parsePolicies(input));
|
|
287
|
+
const issues = [];
|
|
288
|
+
collectStalePolicyReferenceIssues(physical, policy, issues);
|
|
289
|
+
if (issues.length > 0) return yield* new RegistryResolutionError({ issues });
|
|
290
|
+
const publicSourcesByPhysical = /* @__PURE__ */ new Map();
|
|
291
|
+
const physicalSourcesByPublic = /* @__PURE__ */ new Map();
|
|
292
|
+
const duplicateSourceIssues = [];
|
|
293
|
+
for (const sourceName of Object.keys(physical.sources)) {
|
|
294
|
+
const sourcePolicy = policy[sourceName];
|
|
295
|
+
if (!isExposed(defaults, sourcePolicy?.expose)) continue;
|
|
296
|
+
const publicName = sourcePolicy?.exposeAs ?? sourceName;
|
|
297
|
+
const existingSource = physicalSourcesByPublic.get(publicName);
|
|
298
|
+
if (existingSource !== void 0) {
|
|
299
|
+
duplicateSourceIssues.push({
|
|
300
|
+
code: "duplicate_public_source",
|
|
301
|
+
publicName,
|
|
302
|
+
sources: [existingSource, sourceName]
|
|
303
|
+
});
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
physicalSourcesByPublic.set(publicName, sourceName);
|
|
307
|
+
publicSourcesByPhysical.set(sourceName, publicName);
|
|
308
|
+
}
|
|
309
|
+
const sources = {};
|
|
310
|
+
for (const [sourceName, physicalSource] of Object.entries(physical.sources)) {
|
|
311
|
+
const publicName = publicSourcesByPhysical.get(sourceName);
|
|
312
|
+
if (publicName === void 0) continue;
|
|
313
|
+
const sourcePolicy = policy[sourceName];
|
|
314
|
+
const sourceCapabilities = resolveSourceCapabilities(defaults, sourcePolicy);
|
|
315
|
+
const fields = resolveFields({
|
|
316
|
+
sourceName,
|
|
317
|
+
physicalFields: physicalSource.fields,
|
|
318
|
+
defaults,
|
|
319
|
+
sourcePolicy,
|
|
320
|
+
sourceCapabilities,
|
|
321
|
+
issues
|
|
322
|
+
});
|
|
323
|
+
const relations = resolveRelations({
|
|
324
|
+
sourceName,
|
|
325
|
+
physicalRelations: physicalSource.relations ?? {},
|
|
326
|
+
defaults,
|
|
327
|
+
sourcePolicy,
|
|
328
|
+
sourceCapabilities,
|
|
329
|
+
publicSourcesByPhysical,
|
|
330
|
+
physicalSourcesByPublic,
|
|
331
|
+
issues
|
|
332
|
+
});
|
|
333
|
+
if (Object.keys(fields).length === 0) {
|
|
334
|
+
issues.push({
|
|
335
|
+
code: "source_has_no_fields",
|
|
336
|
+
source: sourceName,
|
|
337
|
+
publicName
|
|
338
|
+
});
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const maxLimit = sourcePolicy?.maxLimit ?? defaults.source?.maxLimit;
|
|
342
|
+
const defaultLimit = sourcePolicy?.defaultLimit;
|
|
343
|
+
if (defaultLimit !== void 0 && maxLimit !== void 0 && defaultLimit > maxLimit) {
|
|
344
|
+
issues.push({
|
|
345
|
+
code: "invalid_default_limit",
|
|
346
|
+
source: sourceName,
|
|
347
|
+
defaultLimit,
|
|
348
|
+
maxLimit
|
|
349
|
+
});
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
sources[publicName] = {
|
|
353
|
+
physicalSource: sourceName,
|
|
354
|
+
publicName,
|
|
355
|
+
...sourcePolicy?.label === void 0 ? {} : { label: sourcePolicy.label },
|
|
356
|
+
...sourcePolicy?.description === void 0 ? {} : { description: sourcePolicy.description },
|
|
357
|
+
...maxLimit === void 0 ? {} : { maxLimit },
|
|
358
|
+
...defaultLimit === void 0 ? {} : { defaultLimit },
|
|
359
|
+
fields,
|
|
360
|
+
relations
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
issues.push(...duplicateSourceIssues);
|
|
364
|
+
if (issues.length > 0) return yield* new RegistryResolutionError({ issues });
|
|
365
|
+
return yield* parseRegistryInput(() => ResolvedRegistrySchema.parse({
|
|
366
|
+
version: "v1",
|
|
367
|
+
sources
|
|
368
|
+
}));
|
|
369
|
+
});
|
|
370
|
+
const resolveRegistry = (input) => unwrapResolveRegistryResult(Effect.runSync(Effect.either(resolveRegistryEffect(input))));
|
|
371
|
+
const resolveRegistryPromise = async (input) => unwrapResolveRegistryResult(await Effect.runPromise(Effect.either(resolveRegistryEffect(input))));
|
|
372
|
+
const parseRegistryInput = (parse) => Effect.try({
|
|
373
|
+
try: parse,
|
|
374
|
+
catch: (error) => {
|
|
375
|
+
if (error instanceof ZodError) return new RegistryParseError({ error });
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
const unwrapResolveRegistryResult = (result) => {
|
|
380
|
+
if (result._tag === "Left") throw result.left;
|
|
381
|
+
return result.right;
|
|
382
|
+
};
|
|
383
|
+
const parsePolicies = Effect.fn("parseRegistryPolicies")(function* (input) {
|
|
384
|
+
const policies = [];
|
|
385
|
+
if (input.policy !== void 0) policies.push(yield* parseRegistryInput(() => RegistryPolicySchema.parse(input.policy)));
|
|
386
|
+
for (const policy of input.policies ?? []) policies.push(yield* parseRegistryInput(() => RegistryPolicySchema.parse(policy)));
|
|
387
|
+
return policies;
|
|
388
|
+
});
|
|
389
|
+
const mergePolicies = (policies) => {
|
|
390
|
+
const merged = {};
|
|
391
|
+
for (const policy of policies) for (const [sourceName, sourcePolicy] of Object.entries(policy.sources ?? {})) {
|
|
392
|
+
const currentSourcePolicy = merged[sourceName] ?? {};
|
|
393
|
+
const fields = mergeNestedPolicy(currentSourcePolicy.fields, sourcePolicy.fields);
|
|
394
|
+
const relations = mergeNestedPolicy(currentSourcePolicy.relations, sourcePolicy.relations);
|
|
395
|
+
merged[sourceName] = {
|
|
396
|
+
...currentSourcePolicy,
|
|
397
|
+
...sourcePolicy,
|
|
398
|
+
...fields === void 0 ? {} : { fields },
|
|
399
|
+
...relations === void 0 ? {} : { relations }
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
return merged;
|
|
403
|
+
};
|
|
404
|
+
const mergeNestedPolicy = (current, next) => {
|
|
405
|
+
if (current === void 0 && next === void 0) return;
|
|
406
|
+
const merged = { ...current };
|
|
407
|
+
for (const [name, policy] of Object.entries(next ?? {})) merged[name] = {
|
|
408
|
+
...merged[name],
|
|
409
|
+
...policy
|
|
410
|
+
};
|
|
411
|
+
return merged;
|
|
412
|
+
};
|
|
413
|
+
const collectStalePolicyReferenceIssues = (physical, policy, issues) => {
|
|
414
|
+
for (const [sourceName, sourcePolicy] of Object.entries(policy)) {
|
|
415
|
+
const physicalSource = physical.sources[sourceName];
|
|
416
|
+
if (physicalSource === void 0) {
|
|
417
|
+
issues.push({
|
|
418
|
+
code: "unknown_source",
|
|
419
|
+
source: sourceName
|
|
420
|
+
});
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
for (const fieldName of Object.keys(sourcePolicy.fields ?? {})) if (physicalSource.fields[fieldName] === void 0) issues.push({
|
|
424
|
+
code: "unknown_field",
|
|
425
|
+
source: sourceName,
|
|
426
|
+
field: fieldName
|
|
427
|
+
});
|
|
428
|
+
for (const relationName of Object.keys(sourcePolicy.relations ?? {})) if (physicalSource.relations?.[relationName] === void 0) issues.push({
|
|
429
|
+
code: "unknown_relation",
|
|
430
|
+
source: sourceName,
|
|
431
|
+
relation: relationName
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
const isExposed = (defaults, expose) => {
|
|
436
|
+
if (expose !== void 0) return expose;
|
|
437
|
+
return defaults.exposure === "allow-by-default";
|
|
438
|
+
};
|
|
439
|
+
const resolveSourceCapabilities = (defaults, sourcePolicy) => ({
|
|
440
|
+
selectable: sourcePolicy?.selectable ?? defaults.source?.selectable ?? true,
|
|
441
|
+
filterable: sourcePolicy?.filterable ?? defaults.source?.filterable ?? true,
|
|
442
|
+
sortable: sourcePolicy?.sortable ?? defaults.source?.sortable ?? true
|
|
443
|
+
});
|
|
444
|
+
const resolveFields = (input) => {
|
|
445
|
+
const fields = {};
|
|
446
|
+
const physicalFieldsByPublic = /* @__PURE__ */ new Map();
|
|
447
|
+
for (const [fieldName, physicalField] of Object.entries(input.physicalFields)) {
|
|
448
|
+
const fieldPolicy = input.sourcePolicy?.fields?.[fieldName];
|
|
449
|
+
if (!isExposed(input.defaults, fieldPolicy?.expose)) continue;
|
|
450
|
+
const publicName = fieldPolicy?.exposeAs ?? fieldName;
|
|
451
|
+
const existingField = physicalFieldsByPublic.get(publicName);
|
|
452
|
+
if (existingField !== void 0) {
|
|
453
|
+
input.issues.push({
|
|
454
|
+
code: "duplicate_public_field",
|
|
455
|
+
source: input.sourceName,
|
|
456
|
+
publicName,
|
|
457
|
+
fields: [existingField, fieldName]
|
|
458
|
+
});
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
physicalFieldsByPublic.set(publicName, fieldName);
|
|
462
|
+
const type = fieldPolicy?.type ?? physicalField.type;
|
|
463
|
+
const selectable = input.sourceCapabilities.selectable && (fieldPolicy?.selectable ?? input.defaults.field?.selectable ?? true);
|
|
464
|
+
const filterable = input.sourceCapabilities.filterable && (fieldPolicy?.filterable ?? input.defaults.field?.filterable ?? false);
|
|
465
|
+
const sortable = input.sourceCapabilities.sortable && (fieldPolicy?.sortable ?? input.defaults.field?.sortable ?? false);
|
|
466
|
+
const operators = filterable ? resolveOperators({
|
|
467
|
+
sourceName: input.sourceName,
|
|
468
|
+
fieldName,
|
|
469
|
+
type,
|
|
470
|
+
fieldPolicy,
|
|
471
|
+
defaults: input.defaults,
|
|
472
|
+
issues: input.issues
|
|
473
|
+
}) : [];
|
|
474
|
+
fields[publicName] = {
|
|
475
|
+
physicalSource: input.sourceName,
|
|
476
|
+
physicalField: fieldName,
|
|
477
|
+
publicName,
|
|
478
|
+
type,
|
|
479
|
+
nullable: physicalField.nullable,
|
|
480
|
+
...fieldPolicy?.label === void 0 ? {} : { label: fieldPolicy.label },
|
|
481
|
+
...fieldPolicy?.description === void 0 ? {} : { description: fieldPolicy.description },
|
|
482
|
+
selectable,
|
|
483
|
+
filterable,
|
|
484
|
+
sortable,
|
|
485
|
+
groupable: fieldPolicy?.groupable ?? input.defaults.field?.groupable ?? false,
|
|
486
|
+
operators,
|
|
487
|
+
aggregations: fieldPolicy?.aggregations ?? []
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
return fields;
|
|
491
|
+
};
|
|
492
|
+
const resolveRelations = (input) => {
|
|
493
|
+
const relations = {};
|
|
494
|
+
const physicalRelationsByPublic = /* @__PURE__ */ new Map();
|
|
495
|
+
for (const [relationName, physicalRelation] of Object.entries(input.physicalRelations)) {
|
|
496
|
+
const relationPolicy = input.sourcePolicy?.relations?.[relationName];
|
|
497
|
+
if (!isExposed(input.defaults, relationPolicy?.expose)) continue;
|
|
498
|
+
const publicName = relationPolicy?.exposeAs ?? relationName;
|
|
499
|
+
const existingRelation = physicalRelationsByPublic.get(publicName);
|
|
500
|
+
if (existingRelation !== void 0) {
|
|
501
|
+
input.issues.push({
|
|
502
|
+
code: "duplicate_public_relation",
|
|
503
|
+
source: input.sourceName,
|
|
504
|
+
publicName,
|
|
505
|
+
relations: [existingRelation, relationName]
|
|
506
|
+
});
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
const target = relationPolicy?.target ?? input.publicSourcesByPhysical.get(physicalRelation.target);
|
|
510
|
+
if (target === void 0 || !input.physicalSourcesByPublic.has(target)) {
|
|
511
|
+
if (relationPolicy?.expose === true) input.issues.push({
|
|
512
|
+
code: "unknown_relation_target",
|
|
513
|
+
source: input.sourceName,
|
|
514
|
+
relation: relationName,
|
|
515
|
+
target: relationPolicy?.target ?? physicalRelation.target
|
|
516
|
+
});
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
physicalRelationsByPublic.set(publicName, relationName);
|
|
520
|
+
relations[publicName] = {
|
|
521
|
+
physicalSource: input.sourceName,
|
|
522
|
+
physicalRelation: relationName,
|
|
523
|
+
publicName,
|
|
524
|
+
target,
|
|
525
|
+
kind: physicalRelation.kind,
|
|
526
|
+
localFields: physicalRelation.localFields,
|
|
527
|
+
foreignFields: physicalRelation.foreignFields,
|
|
528
|
+
selectable: input.sourceCapabilities.selectable && (relationPolicy?.selectable ?? input.defaults.relation?.selectable ?? false),
|
|
529
|
+
filterable: input.sourceCapabilities.filterable && (relationPolicy?.filterable ?? input.defaults.relation?.filterable ?? false),
|
|
530
|
+
maxDepth: relationPolicy?.maxDepth ?? input.defaults.relation?.maxDepth ?? 1
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
return relations;
|
|
534
|
+
};
|
|
535
|
+
const resolveOperators = (input) => {
|
|
536
|
+
const requestedOperators = input.fieldPolicy?.operators ?? input.defaults.field?.operators ?? "byType";
|
|
537
|
+
const operators = requestedOperators === "byType" ? defaultOperatorsByType[input.type] : requestedOperators;
|
|
538
|
+
const validOperators = new Set(defaultOperatorsByType[input.type]);
|
|
539
|
+
for (const operator of operators) if (!validOperators.has(operator)) input.issues.push({
|
|
540
|
+
code: "invalid_operator_for_field_type",
|
|
541
|
+
source: input.sourceName,
|
|
542
|
+
field: input.fieldName,
|
|
543
|
+
type: input.type,
|
|
544
|
+
operator
|
|
545
|
+
});
|
|
546
|
+
return [...operators];
|
|
547
|
+
};
|
|
548
|
+
const nullableOperators = ["isNull", "isNotNull"];
|
|
549
|
+
const equalityOperators = [
|
|
550
|
+
"eq",
|
|
551
|
+
"neq",
|
|
552
|
+
"in",
|
|
553
|
+
...nullableOperators
|
|
554
|
+
];
|
|
555
|
+
const comparableOperators = [
|
|
556
|
+
"eq",
|
|
557
|
+
"neq",
|
|
558
|
+
"gt",
|
|
559
|
+
"gte",
|
|
560
|
+
"lt",
|
|
561
|
+
"lte",
|
|
562
|
+
"in",
|
|
563
|
+
...nullableOperators
|
|
564
|
+
];
|
|
565
|
+
const defaultOperatorsByType = {
|
|
566
|
+
string: [
|
|
567
|
+
"eq",
|
|
568
|
+
"neq",
|
|
569
|
+
"in",
|
|
570
|
+
"contains",
|
|
571
|
+
"startsWith",
|
|
572
|
+
"endsWith",
|
|
573
|
+
...nullableOperators
|
|
574
|
+
],
|
|
575
|
+
number: comparableOperators,
|
|
576
|
+
boolean: [
|
|
577
|
+
"eq",
|
|
578
|
+
"neq",
|
|
579
|
+
...nullableOperators
|
|
580
|
+
],
|
|
581
|
+
date: comparableOperators,
|
|
582
|
+
datetime: comparableOperators,
|
|
583
|
+
json: [
|
|
584
|
+
"eq",
|
|
585
|
+
"neq",
|
|
586
|
+
...nullableOperators
|
|
587
|
+
],
|
|
588
|
+
enum: equalityOperators,
|
|
589
|
+
unknown: []
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
//#endregion
|
|
593
|
+
//#region src/query/validate.ts
|
|
594
|
+
const QueryFilterOperatorErrorSchema = Schema.Literal("eq", "neq", "gt", "gte", "lt", "lte", "in", "contains", "startsWith", "endsWith", "isNull", "isNotNull");
|
|
595
|
+
const QueryValidationIssueSchema = Schema.Union(Schema.Struct({
|
|
596
|
+
code: Schema.Literal("unknown_source"),
|
|
597
|
+
source: Schema.String
|
|
598
|
+
}), Schema.Struct({
|
|
599
|
+
code: Schema.Literal("unknown_field"),
|
|
600
|
+
source: Schema.String,
|
|
601
|
+
path: Schema.String,
|
|
602
|
+
field: Schema.String
|
|
603
|
+
}), Schema.Struct({
|
|
604
|
+
code: Schema.Literal("unknown_relation"),
|
|
605
|
+
source: Schema.String,
|
|
606
|
+
path: Schema.String,
|
|
607
|
+
relation: Schema.String
|
|
608
|
+
}), Schema.Struct({
|
|
609
|
+
code: Schema.Literal("field_not_selectable", "field_not_filterable", "field_not_sortable", "field_not_groupable"),
|
|
610
|
+
source: Schema.String,
|
|
611
|
+
path: Schema.String,
|
|
612
|
+
field: Schema.String
|
|
613
|
+
}), Schema.Struct({
|
|
614
|
+
code: Schema.Literal("relation_not_selectable", "relation_not_filterable"),
|
|
615
|
+
source: Schema.String,
|
|
616
|
+
path: Schema.String,
|
|
617
|
+
relation: Schema.String
|
|
618
|
+
}), Schema.Struct({
|
|
619
|
+
code: Schema.Literal("relation_depth_exceeded"),
|
|
620
|
+
source: Schema.String,
|
|
621
|
+
path: Schema.String,
|
|
622
|
+
relation: Schema.String,
|
|
623
|
+
requestedDepth: Schema.Number,
|
|
624
|
+
maxDepth: Schema.Number
|
|
625
|
+
}), Schema.Struct({
|
|
626
|
+
code: Schema.Literal("operator_not_allowed"),
|
|
627
|
+
source: Schema.String,
|
|
628
|
+
path: Schema.String,
|
|
629
|
+
field: Schema.String,
|
|
630
|
+
operator: QueryFilterOperatorErrorSchema,
|
|
631
|
+
allowedOperators: Schema.Array(QueryFilterOperatorErrorSchema)
|
|
632
|
+
}), Schema.Struct({
|
|
633
|
+
code: Schema.Literal("limit_exceeds_max"),
|
|
634
|
+
source: Schema.String,
|
|
635
|
+
limit: Schema.Number,
|
|
636
|
+
maxLimit: Schema.Number
|
|
637
|
+
}), Schema.Struct({
|
|
638
|
+
code: Schema.Literal("missing_param"),
|
|
639
|
+
param: Schema.String,
|
|
640
|
+
path: Schema.String
|
|
641
|
+
}), Schema.Struct({
|
|
642
|
+
code: Schema.Literal("invalid_param_value"),
|
|
643
|
+
param: Schema.String,
|
|
644
|
+
path: Schema.String,
|
|
645
|
+
expected: Schema.String
|
|
646
|
+
}));
|
|
647
|
+
var QueryParseError = class extends Schema.TaggedError()("QueryParseError", {
|
|
648
|
+
input: Schema.Literal("query", "registry", "params"),
|
|
649
|
+
error: Schema.Defect
|
|
650
|
+
}) {};
|
|
651
|
+
var QueryValidationError = class extends Schema.TaggedError()("QueryValidationError", { issues: Schema.Array(QueryValidationIssueSchema) }) {};
|
|
652
|
+
const validateQuerySpecEffect = Effect.fn("validateQuerySpec")(function* (input) {
|
|
653
|
+
const parsedQuery = yield* parseValidationInput("query", () => QuerySpecSchema.parse(input.query));
|
|
654
|
+
const params = yield* parseValidationInput("params", () => QueryParamsSchema.parse(input.params ?? {}));
|
|
655
|
+
const registry = yield* parseValidationInput("registry", () => ResolvedRegistrySchema.parse(input.registry));
|
|
656
|
+
const issues = [];
|
|
657
|
+
const query = bindQueryParams({
|
|
658
|
+
query: parsedQuery,
|
|
659
|
+
params,
|
|
660
|
+
issues
|
|
661
|
+
});
|
|
662
|
+
const source = registry.sources[query.source];
|
|
663
|
+
if (source === void 0) return yield* new QueryValidationError({ issues: [{
|
|
664
|
+
code: "unknown_source",
|
|
665
|
+
source: query.source
|
|
666
|
+
}] });
|
|
667
|
+
validateLimit(query, source, issues);
|
|
668
|
+
for (const fieldPath of query.select) validateFieldPath({
|
|
669
|
+
registry,
|
|
670
|
+
source,
|
|
671
|
+
fieldPath,
|
|
672
|
+
fieldCapability: "selectable",
|
|
673
|
+
relationCapability: "selectable",
|
|
674
|
+
issues
|
|
675
|
+
});
|
|
676
|
+
if (query.where !== void 0) validateFilter({
|
|
677
|
+
registry,
|
|
678
|
+
source,
|
|
679
|
+
filter: query.where,
|
|
680
|
+
issues
|
|
681
|
+
});
|
|
682
|
+
for (const fieldPath of query.groupBy ?? []) validateFieldPath({
|
|
683
|
+
registry,
|
|
684
|
+
source,
|
|
685
|
+
fieldPath,
|
|
686
|
+
fieldCapability: "groupable",
|
|
687
|
+
relationCapability: "selectable",
|
|
688
|
+
issues
|
|
689
|
+
});
|
|
690
|
+
for (const orderBy of query.orderBy ?? []) validateFieldPath({
|
|
691
|
+
registry,
|
|
692
|
+
source,
|
|
693
|
+
fieldPath: orderBy.field,
|
|
694
|
+
fieldCapability: "sortable",
|
|
695
|
+
relationCapability: "selectable",
|
|
696
|
+
issues
|
|
697
|
+
});
|
|
698
|
+
if (issues.length > 0) return yield* new QueryValidationError({ issues });
|
|
699
|
+
return stripQueryParamSources(query);
|
|
700
|
+
});
|
|
701
|
+
const validateQuerySpec = (input) => unwrapValidateQuerySpecResult(Effect.runSync(Effect.either(validateQuerySpecEffect(input))));
|
|
702
|
+
const validateQuerySpecPromise = async (input) => unwrapValidateQuerySpecResult(await Effect.runPromise(Effect.either(validateQuerySpecEffect(input))));
|
|
703
|
+
const parseValidationInput = (input, parse) => Effect.try({
|
|
704
|
+
try: parse,
|
|
705
|
+
catch: (error) => {
|
|
706
|
+
if (error instanceof ZodError) return new QueryParseError({
|
|
707
|
+
input,
|
|
708
|
+
error
|
|
709
|
+
});
|
|
710
|
+
throw error;
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
const unwrapValidateQuerySpecResult = (result) => {
|
|
714
|
+
if (result._tag === "Left") throw result.left;
|
|
715
|
+
return result.right;
|
|
716
|
+
};
|
|
717
|
+
const stripQueryParamSources = (query) => ({
|
|
718
|
+
version: query.version,
|
|
719
|
+
source: query.source,
|
|
720
|
+
select: query.select,
|
|
721
|
+
...query.groupBy === void 0 ? {} : { groupBy: query.groupBy },
|
|
722
|
+
...query.orderBy === void 0 ? {} : { orderBy: query.orderBy },
|
|
723
|
+
...query.where === void 0 ? {} : { where: stripFilterParamSources(query.where) },
|
|
724
|
+
...query.limit === void 0 ? {} : { limit: query.limit },
|
|
725
|
+
...query.offset === void 0 ? {} : { offset: query.offset }
|
|
726
|
+
});
|
|
727
|
+
const stripFilterParamSources = (filter) => {
|
|
728
|
+
if ("and" in filter) return { and: filter.and.map(stripFilterParamSources) };
|
|
729
|
+
if ("or" in filter) return { or: filter.or.map(stripFilterParamSources) };
|
|
730
|
+
return {
|
|
731
|
+
field: filter.field,
|
|
732
|
+
op: filter.op,
|
|
733
|
+
...filter.value === void 0 ? {} : { value: filter.value }
|
|
734
|
+
};
|
|
735
|
+
};
|
|
736
|
+
const bindQueryParams = (input) => ({
|
|
737
|
+
version: input.query.version,
|
|
738
|
+
source: input.query.source,
|
|
739
|
+
select: input.query.select,
|
|
740
|
+
...input.query.groupBy === void 0 ? {} : { groupBy: input.query.groupBy },
|
|
741
|
+
...input.query.orderBy === void 0 ? {} : { orderBy: input.query.orderBy },
|
|
742
|
+
...input.query.where === void 0 ? {} : { where: bindFilterParams({
|
|
743
|
+
...input,
|
|
744
|
+
filter: input.query.where,
|
|
745
|
+
path: "where"
|
|
746
|
+
}) },
|
|
747
|
+
...input.query.limit === void 0 ? {} : { limit: bindLimitParam({
|
|
748
|
+
...input,
|
|
749
|
+
value: input.query.limit,
|
|
750
|
+
path: "limit"
|
|
751
|
+
}) },
|
|
752
|
+
...input.query.offset === void 0 ? {} : { offset: bindLimitParam({
|
|
753
|
+
...input,
|
|
754
|
+
value: input.query.offset,
|
|
755
|
+
path: "offset"
|
|
756
|
+
}) }
|
|
757
|
+
});
|
|
758
|
+
const bindFilterParams = (input) => {
|
|
759
|
+
if ("and" in input.filter) return { and: input.filter.and.map((filter, index) => bindFilterParams({
|
|
760
|
+
...input,
|
|
761
|
+
filter,
|
|
762
|
+
path: `${input.path}.and[${index}]`
|
|
763
|
+
})) };
|
|
764
|
+
if ("or" in input.filter) return { or: input.filter.or.map((filter, index) => bindFilterParams({
|
|
765
|
+
...input,
|
|
766
|
+
filter,
|
|
767
|
+
path: `${input.path}.or[${index}]`
|
|
768
|
+
})) };
|
|
769
|
+
if (input.filter.value === void 0) return {
|
|
770
|
+
field: input.filter.field,
|
|
771
|
+
op: input.filter.op
|
|
772
|
+
};
|
|
773
|
+
const value = bindJsonParam({
|
|
774
|
+
params: input.params,
|
|
775
|
+
issues: input.issues,
|
|
776
|
+
value: input.filter.value,
|
|
777
|
+
path: `${input.path}.value`
|
|
778
|
+
});
|
|
779
|
+
if (input.filter.op === "in" && isQueryParamRef(input.filter.value) && Object.hasOwn(input.params, input.filter.value.$param) && (!Array.isArray(value) || value.length === 0)) input.issues.push({
|
|
780
|
+
code: "invalid_param_value",
|
|
781
|
+
param: input.filter.value.$param,
|
|
782
|
+
path: `${input.path}.value`,
|
|
783
|
+
expected: "non-empty array"
|
|
784
|
+
});
|
|
785
|
+
return {
|
|
786
|
+
field: input.filter.field,
|
|
787
|
+
op: input.filter.op,
|
|
788
|
+
value,
|
|
789
|
+
...isQueryParamRef(input.filter.value) ? { valueParam: input.filter.value.$param } : {}
|
|
790
|
+
};
|
|
791
|
+
};
|
|
792
|
+
const bindLimitParam = (input) => {
|
|
793
|
+
if (typeof input.value === "number") return input.value;
|
|
794
|
+
if (isQueryParamRef(input.value) && !Object.hasOwn(input.params, input.value.$param)) {
|
|
795
|
+
input.issues.push({
|
|
796
|
+
code: "missing_param",
|
|
797
|
+
param: input.value.$param,
|
|
798
|
+
path: input.path
|
|
799
|
+
});
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const value = bindJsonParam({
|
|
803
|
+
params: input.params,
|
|
804
|
+
issues: input.issues,
|
|
805
|
+
value: input.value,
|
|
806
|
+
path: input.path
|
|
807
|
+
});
|
|
808
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0) return value;
|
|
809
|
+
if (isQueryParamRef(input.value)) input.issues.push({
|
|
810
|
+
code: "invalid_param_value",
|
|
811
|
+
param: input.value.$param,
|
|
812
|
+
path: input.path,
|
|
813
|
+
expected: "non-negative integer"
|
|
814
|
+
});
|
|
815
|
+
};
|
|
816
|
+
const bindJsonParam = (input) => {
|
|
817
|
+
if (!isQueryParamRef(input.value)) return input.value;
|
|
818
|
+
if (!Object.hasOwn(input.params, input.value.$param)) {
|
|
819
|
+
input.issues.push({
|
|
820
|
+
code: "missing_param",
|
|
821
|
+
param: input.value.$param,
|
|
822
|
+
path: input.path
|
|
823
|
+
});
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
return input.params[input.value.$param];
|
|
827
|
+
};
|
|
828
|
+
const isQueryParamRef = (value) => value !== null && typeof value === "object" && Object.keys(value).length === 1 && typeof value.$param === "string";
|
|
829
|
+
const validateLimit = (query, source, issues) => {
|
|
830
|
+
if (query.limit !== void 0 && source.maxLimit !== void 0 && query.limit > source.maxLimit) issues.push({
|
|
831
|
+
code: "limit_exceeds_max",
|
|
832
|
+
source: source.publicName,
|
|
833
|
+
limit: query.limit,
|
|
834
|
+
maxLimit: source.maxLimit
|
|
835
|
+
});
|
|
836
|
+
};
|
|
837
|
+
const validateFilter = (input) => {
|
|
838
|
+
if ("and" in input.filter) {
|
|
839
|
+
for (const filter of input.filter.and) validateFilter({
|
|
840
|
+
...input,
|
|
841
|
+
filter
|
|
842
|
+
});
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
if ("or" in input.filter) {
|
|
846
|
+
for (const filter of input.filter.or) validateFilter({
|
|
847
|
+
...input,
|
|
848
|
+
filter
|
|
849
|
+
});
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
const result = validateFieldPath({
|
|
853
|
+
registry: input.registry,
|
|
854
|
+
source: input.source,
|
|
855
|
+
fieldPath: input.filter.field,
|
|
856
|
+
fieldCapability: "filterable",
|
|
857
|
+
relationCapability: "filterable",
|
|
858
|
+
issues: input.issues
|
|
859
|
+
});
|
|
860
|
+
if (result === void 0 || !result.field.filterable) return;
|
|
861
|
+
if (!result.field.operators.includes(input.filter.op)) input.issues.push({
|
|
862
|
+
code: "operator_not_allowed",
|
|
863
|
+
source: result.source.publicName,
|
|
864
|
+
path: input.filter.field,
|
|
865
|
+
field: result.field.publicName,
|
|
866
|
+
operator: input.filter.op,
|
|
867
|
+
allowedOperators: result.field.operators
|
|
868
|
+
});
|
|
869
|
+
validateFilterParamValue({
|
|
870
|
+
filter: input.filter,
|
|
871
|
+
field: result.field,
|
|
872
|
+
issues: input.issues
|
|
873
|
+
});
|
|
874
|
+
};
|
|
875
|
+
const validateFilterParamValue = (input) => {
|
|
876
|
+
if (input.filter.valueParam === void 0 || input.filter.value === void 0) return;
|
|
877
|
+
if (input.filter.op === "isNull" || input.filter.op === "isNotNull") return;
|
|
878
|
+
if (input.filter.op === "in") {
|
|
879
|
+
if (!Array.isArray(input.filter.value) || input.filter.value.length === 0) return;
|
|
880
|
+
if (input.filter.value.some((value) => !isValueForFieldType(value, input.field.type))) input.issues.push({
|
|
881
|
+
code: "invalid_param_value",
|
|
882
|
+
param: input.filter.valueParam,
|
|
883
|
+
path: `${input.filter.field}.value`,
|
|
884
|
+
expected: `array of ${fieldTypeExpectation(input.field.type)}`
|
|
885
|
+
});
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
if (!isValueForFieldType(input.filter.value, input.field.type)) input.issues.push({
|
|
889
|
+
code: "invalid_param_value",
|
|
890
|
+
param: input.filter.valueParam,
|
|
891
|
+
path: `${input.filter.field}.value`,
|
|
892
|
+
expected: fieldTypeExpectation(input.field.type)
|
|
893
|
+
});
|
|
894
|
+
};
|
|
895
|
+
const isValueForFieldType = (value, type) => {
|
|
896
|
+
if (value === null) return true;
|
|
897
|
+
switch (type) {
|
|
898
|
+
case "number": return typeof value === "number";
|
|
899
|
+
case "boolean": return typeof value === "boolean";
|
|
900
|
+
case "string":
|
|
901
|
+
case "enum":
|
|
902
|
+
case "date":
|
|
903
|
+
case "datetime": return typeof value === "string";
|
|
904
|
+
case "json":
|
|
905
|
+
case "unknown": return true;
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
const fieldTypeExpectation = (type) => {
|
|
909
|
+
switch (type) {
|
|
910
|
+
case "number": return "number";
|
|
911
|
+
case "boolean": return "boolean";
|
|
912
|
+
case "string":
|
|
913
|
+
case "enum":
|
|
914
|
+
case "date":
|
|
915
|
+
case "datetime": return "string";
|
|
916
|
+
case "json":
|
|
917
|
+
case "unknown": return "JSON value";
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
const validateFieldPath = (input) => {
|
|
921
|
+
const parts = input.fieldPath.split(".");
|
|
922
|
+
const fieldName = parts.at(-1);
|
|
923
|
+
if (fieldName === void 0 || fieldName.length === 0) return;
|
|
924
|
+
let source = input.source;
|
|
925
|
+
const relationNames = parts.slice(0, -1);
|
|
926
|
+
for (const [index, relationName] of relationNames.entries()) {
|
|
927
|
+
const relation = source.relations[relationName];
|
|
928
|
+
if (relation === void 0) {
|
|
929
|
+
input.issues.push({
|
|
930
|
+
code: "unknown_relation",
|
|
931
|
+
source: source.publicName,
|
|
932
|
+
path: input.fieldPath,
|
|
933
|
+
relation: relationName
|
|
934
|
+
});
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
validateRelationPath({
|
|
938
|
+
relation,
|
|
939
|
+
source,
|
|
940
|
+
relationName,
|
|
941
|
+
requestedDepth: relationNames.length - index,
|
|
942
|
+
fieldPath: input.fieldPath,
|
|
943
|
+
relationCapability: input.relationCapability,
|
|
944
|
+
issues: input.issues
|
|
945
|
+
});
|
|
946
|
+
const targetSource = input.registry.sources[relation.target];
|
|
947
|
+
if (targetSource === void 0) {
|
|
948
|
+
input.issues.push({
|
|
949
|
+
code: "unknown_source",
|
|
950
|
+
source: relation.target
|
|
951
|
+
});
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
source = targetSource;
|
|
955
|
+
}
|
|
956
|
+
const field = source.fields[fieldName];
|
|
957
|
+
if (field === void 0) {
|
|
958
|
+
input.issues.push({
|
|
959
|
+
code: "unknown_field",
|
|
960
|
+
source: source.publicName,
|
|
961
|
+
path: input.fieldPath,
|
|
962
|
+
field: fieldName
|
|
963
|
+
});
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
validateFieldCapability({
|
|
967
|
+
source,
|
|
968
|
+
field,
|
|
969
|
+
fieldPath: input.fieldPath,
|
|
970
|
+
fieldCapability: input.fieldCapability,
|
|
971
|
+
issues: input.issues
|
|
972
|
+
});
|
|
973
|
+
return {
|
|
974
|
+
source,
|
|
975
|
+
field
|
|
976
|
+
};
|
|
977
|
+
};
|
|
978
|
+
const validateRelationPath = (input) => {
|
|
979
|
+
if (!input.relation[input.relationCapability]) input.issues.push({
|
|
980
|
+
code: input.relationCapability === "filterable" ? "relation_not_filterable" : "relation_not_selectable",
|
|
981
|
+
source: input.source.publicName,
|
|
982
|
+
path: input.fieldPath,
|
|
983
|
+
relation: input.relationName
|
|
984
|
+
});
|
|
985
|
+
if (input.requestedDepth > input.relation.maxDepth) input.issues.push({
|
|
986
|
+
code: "relation_depth_exceeded",
|
|
987
|
+
source: input.source.publicName,
|
|
988
|
+
path: input.fieldPath,
|
|
989
|
+
relation: input.relationName,
|
|
990
|
+
requestedDepth: input.requestedDepth,
|
|
991
|
+
maxDepth: input.relation.maxDepth
|
|
992
|
+
});
|
|
993
|
+
};
|
|
994
|
+
const validateFieldCapability = (input) => {
|
|
995
|
+
if (input.field[input.fieldCapability]) return;
|
|
996
|
+
input.issues.push({
|
|
997
|
+
code: fieldCapabilityIssueCode[input.fieldCapability],
|
|
998
|
+
source: input.source.publicName,
|
|
999
|
+
path: input.fieldPath,
|
|
1000
|
+
field: input.field.publicName
|
|
1001
|
+
});
|
|
1002
|
+
};
|
|
1003
|
+
const fieldCapabilityIssueCode = {
|
|
1004
|
+
selectable: "field_not_selectable",
|
|
1005
|
+
filterable: "field_not_filterable",
|
|
1006
|
+
sortable: "field_not_sortable",
|
|
1007
|
+
groupable: "field_not_groupable"
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
//#endregion
|
|
1011
|
+
//#region src/query/lower.ts
|
|
1012
|
+
const lowerQuerySpecToIREffect = Effect.fn("lowerQuerySpecToIR")(function* (input) {
|
|
1013
|
+
const query = yield* validateQuerySpecEffect(input);
|
|
1014
|
+
const registry = ResolvedRegistrySchema.parse(input.registry);
|
|
1015
|
+
const source = registry.sources[query.source];
|
|
1016
|
+
const joins = /* @__PURE__ */ new Map();
|
|
1017
|
+
const context = {
|
|
1018
|
+
registry,
|
|
1019
|
+
joins
|
|
1020
|
+
};
|
|
1021
|
+
const where = query.where === void 0 ? void 0 : lowerFilter(context, source, query.where);
|
|
1022
|
+
const select = query.select.map((fieldPath) => lowerFieldPath(context, source, fieldPath));
|
|
1023
|
+
const groupBy = (query.groupBy ?? []).map((fieldPath) => lowerFieldPath(context, source, fieldPath));
|
|
1024
|
+
const orderBy = (query.orderBy ?? []).map((orderBy$1) => ({
|
|
1025
|
+
field: lowerFieldPath(context, source, orderBy$1.field),
|
|
1026
|
+
direction: orderBy$1.direction
|
|
1027
|
+
}));
|
|
1028
|
+
return {
|
|
1029
|
+
kind: "select",
|
|
1030
|
+
source: sourceRef(source),
|
|
1031
|
+
select,
|
|
1032
|
+
joins: [...joins.values()],
|
|
1033
|
+
...where === void 0 ? {} : { where },
|
|
1034
|
+
groupBy,
|
|
1035
|
+
orderBy,
|
|
1036
|
+
...query.limit === void 0 ? {} : { limit: query.limit },
|
|
1037
|
+
...query.offset === void 0 ? {} : { offset: query.offset }
|
|
1038
|
+
};
|
|
1039
|
+
});
|
|
1040
|
+
const lowerQuerySpecToIR = (input) => unwrapLowerQuerySpecResult(Effect.runSync(Effect.either(lowerQuerySpecToIREffect(input))));
|
|
1041
|
+
const lowerQuerySpecToIRPromise = async (input) => unwrapLowerQuerySpecResult(await Effect.runPromise(Effect.either(lowerQuerySpecToIREffect(input))));
|
|
1042
|
+
const unwrapLowerQuerySpecResult = (result) => {
|
|
1043
|
+
if (result._tag === "Left") throw result.left;
|
|
1044
|
+
return result.right;
|
|
1045
|
+
};
|
|
1046
|
+
const lowerFilter = (context, source, filter) => {
|
|
1047
|
+
if ("and" in filter) return { and: filter.and.map((child) => lowerFilter(context, source, child)) };
|
|
1048
|
+
if ("or" in filter) return { or: filter.or.map((child) => lowerFilter(context, source, child)) };
|
|
1049
|
+
return {
|
|
1050
|
+
field: lowerFieldPath(context, source, filter.field),
|
|
1051
|
+
op: filter.op,
|
|
1052
|
+
...filter.value === void 0 ? {} : { value: filter.value }
|
|
1053
|
+
};
|
|
1054
|
+
};
|
|
1055
|
+
const lowerFieldPath = (context, source, fieldPath) => {
|
|
1056
|
+
const parts = fieldPath.split(".");
|
|
1057
|
+
const fieldName = parts.at(-1);
|
|
1058
|
+
const relationNames = parts.slice(0, -1);
|
|
1059
|
+
let currentSource = source;
|
|
1060
|
+
const joinPathParts = [];
|
|
1061
|
+
for (const relationName of relationNames) {
|
|
1062
|
+
const relation = currentSource.relations[relationName];
|
|
1063
|
+
const targetSource = context.registry.sources[relation.target];
|
|
1064
|
+
joinPathParts.push(relationName);
|
|
1065
|
+
const joinPath = joinPathParts.join(".");
|
|
1066
|
+
if (!context.joins.has(joinPath)) context.joins.set(joinPath, {
|
|
1067
|
+
path: joinPath,
|
|
1068
|
+
relation: {
|
|
1069
|
+
publicName: relation.publicName,
|
|
1070
|
+
physicalRelation: relation.physicalRelation
|
|
1071
|
+
},
|
|
1072
|
+
kind: relation.kind,
|
|
1073
|
+
from: sourceRef(currentSource),
|
|
1074
|
+
to: sourceRef(targetSource),
|
|
1075
|
+
localFields: relation.localFields,
|
|
1076
|
+
foreignFields: relation.foreignFields
|
|
1077
|
+
});
|
|
1078
|
+
currentSource = targetSource;
|
|
1079
|
+
}
|
|
1080
|
+
const field = currentSource.fields[fieldName];
|
|
1081
|
+
return {
|
|
1082
|
+
path: fieldPath,
|
|
1083
|
+
source: {
|
|
1084
|
+
publicName: currentSource.publicName,
|
|
1085
|
+
physicalSource: field.physicalSource
|
|
1086
|
+
},
|
|
1087
|
+
field: {
|
|
1088
|
+
publicName: field.publicName,
|
|
1089
|
+
physicalField: field.physicalField
|
|
1090
|
+
},
|
|
1091
|
+
type: field.type,
|
|
1092
|
+
nullable: field.nullable
|
|
1093
|
+
};
|
|
1094
|
+
};
|
|
1095
|
+
const sourceRef = (source) => ({
|
|
1096
|
+
publicName: source.publicName,
|
|
1097
|
+
physicalSource: source.physicalSource
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
//#endregion
|
|
1101
|
+
//#region src/compiler/sql/dialects/mysql.ts
|
|
1102
|
+
const mysqlDialectCompiler = {
|
|
1103
|
+
dialect: "mysql",
|
|
1104
|
+
quoteIdentifier: (identifier) => `\`${identifier.replaceAll("`", "``")}\``,
|
|
1105
|
+
placeholder: () => "?",
|
|
1106
|
+
likeEscapeSql: " escape '\\\\'"
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
//#endregion
|
|
1110
|
+
//#region src/compiler/sql/dialects/postgres.ts
|
|
1111
|
+
const postgresDialectCompiler = {
|
|
1112
|
+
dialect: "postgres",
|
|
1113
|
+
quoteIdentifier: (identifier) => `"${identifier.replaceAll("\"", "\"\"")}"`,
|
|
1114
|
+
placeholder: (index) => `$${index}`,
|
|
1115
|
+
likeEscapeSql: " escape '\\'"
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
//#endregion
|
|
1119
|
+
//#region src/compiler/sql/dialects/sqlite.ts
|
|
1120
|
+
const sqliteDialectCompiler = {
|
|
1121
|
+
dialect: "sqlite",
|
|
1122
|
+
quoteIdentifier: (identifier) => `"${identifier.replaceAll("\"", "\"\"")}"`,
|
|
1123
|
+
placeholder: () => "?",
|
|
1124
|
+
likeEscapeSql: " escape '\\'"
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
//#endregion
|
|
1128
|
+
//#region src/compiler/sql/index.ts
|
|
1129
|
+
const compileQuerySpecToSQLEffect = Effect.fn("compileQuerySpecToSQL")(function* (input) {
|
|
1130
|
+
return compileIRToSQL(yield* lowerQuerySpecToIREffect(input), dialectCompilerFor(input.dialect ?? "mysql"));
|
|
1131
|
+
});
|
|
1132
|
+
const compileQuerySpecToSQL = (input) => unwrapCompileQuerySpecToSQLResult(Effect.runSync(Effect.either(compileQuerySpecToSQLEffect(input))));
|
|
1133
|
+
const compileQuerySpecToSQLPromise = async (input) => unwrapCompileQuerySpecToSQLResult(await Effect.runPromise(Effect.either(compileQuerySpecToSQLEffect(input))));
|
|
1134
|
+
const dialectCompilers = {
|
|
1135
|
+
mysql: mysqlDialectCompiler,
|
|
1136
|
+
postgres: postgresDialectCompiler,
|
|
1137
|
+
sqlite: sqliteDialectCompiler
|
|
1138
|
+
};
|
|
1139
|
+
const dialectCompilerFor = (dialect) => dialectCompilers[dialect];
|
|
1140
|
+
const unwrapCompileQuerySpecToSQLResult = (result) => {
|
|
1141
|
+
if (result._tag === "Left") throw result.left;
|
|
1142
|
+
return result.right;
|
|
1143
|
+
};
|
|
1144
|
+
const compileIRToSQL = (ir, compiler) => {
|
|
1145
|
+
const context = {
|
|
1146
|
+
compiler,
|
|
1147
|
+
params: [],
|
|
1148
|
+
aliasesByPath: new Map([["", "t0"]])
|
|
1149
|
+
};
|
|
1150
|
+
ir.joins.forEach((join, index) => {
|
|
1151
|
+
context.aliasesByPath.set(join.path, `t${index + 1}`);
|
|
1152
|
+
});
|
|
1153
|
+
const sqlParts = [
|
|
1154
|
+
compileSelect(ir, context),
|
|
1155
|
+
`from ${quoteIdentifier(context, ir.source.physicalSource)} as ${quoteIdentifier(context, "t0")}`,
|
|
1156
|
+
...ir.joins.map((join) => {
|
|
1157
|
+
const fromAlias = aliasForPath(context, parentPath(join.path));
|
|
1158
|
+
const toAlias = aliasForPath(context, join.path);
|
|
1159
|
+
const on = join.localFields.map((localField, index) => `${quoteIdentifier(context, fromAlias)}.${quoteIdentifier(context, localField)} = ${quoteIdentifier(context, toAlias)}.${quoteIdentifier(context, join.foreignFields[index])}`).join(" and ");
|
|
1160
|
+
return `left join ${quoteIdentifier(context, join.to.physicalSource)} as ${quoteIdentifier(context, toAlias)} on ${on}`;
|
|
1161
|
+
}),
|
|
1162
|
+
...ir.where === void 0 ? [] : [`where ${compileFilter(context, ir.where)}`],
|
|
1163
|
+
...ir.groupBy.length === 0 ? [] : [`group by ${ir.groupBy.map((field) => compileFieldRef(context, field)).join(", ")}`],
|
|
1164
|
+
...ir.orderBy.length === 0 ? [] : [`order by ${ir.orderBy.map((orderBy) => `${compileFieldRef(context, orderBy.field)} ${orderBy.direction}`).join(", ")}`],
|
|
1165
|
+
...ir.limit === void 0 ? [] : [`limit ${addParam(context, ir.limit)}`],
|
|
1166
|
+
...ir.offset === void 0 ? [] : [`offset ${addParam(context, ir.offset)}`]
|
|
1167
|
+
];
|
|
1168
|
+
return {
|
|
1169
|
+
dialect: compiler.dialect,
|
|
1170
|
+
sql: sqlParts.join("\n"),
|
|
1171
|
+
params: context.params
|
|
1172
|
+
};
|
|
1173
|
+
};
|
|
1174
|
+
const compileSelect = (ir, context) => `select ${ir.select.map((field) => `${compileFieldRef(context, field)} as ${quoteIdentifier(context, field.path)}`).join(", ")}`;
|
|
1175
|
+
const compileFilter = (context, filter) => {
|
|
1176
|
+
if ("and" in filter) return filter.and.map((child) => `(${compileFilter(context, child)})`).join(" and ");
|
|
1177
|
+
if ("or" in filter) return filter.or.map((child) => `(${compileFilter(context, child)})`).join(" or ");
|
|
1178
|
+
return compilePredicate(context, filter.field, filter.op, filter.value);
|
|
1179
|
+
};
|
|
1180
|
+
const compilePredicate = (context, field, operator, value) => {
|
|
1181
|
+
const fieldRef = compileFieldRef(context, field);
|
|
1182
|
+
switch (operator) {
|
|
1183
|
+
case "eq":
|
|
1184
|
+
if (value === null) return `${fieldRef} is null`;
|
|
1185
|
+
return `${fieldRef} = ${addParam(context, value ?? null)}`;
|
|
1186
|
+
case "neq":
|
|
1187
|
+
if (value === null) return `${fieldRef} is not null`;
|
|
1188
|
+
return `${fieldRef} <> ${addParam(context, value ?? null)}`;
|
|
1189
|
+
case "gt": return `${fieldRef} > ${addParam(context, value ?? null)}`;
|
|
1190
|
+
case "gte": return `${fieldRef} >= ${addParam(context, value ?? null)}`;
|
|
1191
|
+
case "lt": return `${fieldRef} < ${addParam(context, value ?? null)}`;
|
|
1192
|
+
case "lte": return `${fieldRef} <= ${addParam(context, value ?? null)}`;
|
|
1193
|
+
case "in": return `${fieldRef} in (${compileInList(context, value)})`;
|
|
1194
|
+
case "contains": return `${fieldRef} like ${addParam(context, `%${escapeLikePattern(stringValue(value))}%`)}${context.compiler.likeEscapeSql}`;
|
|
1195
|
+
case "startsWith": return `${fieldRef} like ${addParam(context, `${escapeLikePattern(stringValue(value))}%`)}${context.compiler.likeEscapeSql}`;
|
|
1196
|
+
case "endsWith": return `${fieldRef} like ${addParam(context, `%${escapeLikePattern(stringValue(value))}`)}${context.compiler.likeEscapeSql}`;
|
|
1197
|
+
case "isNull": return `${fieldRef} is null`;
|
|
1198
|
+
case "isNotNull": return `${fieldRef} is not null`;
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
const compileInList = (context, value) => {
|
|
1202
|
+
if (!Array.isArray(value) || value.length === 0) return "null";
|
|
1203
|
+
return value.map((item) => addParam(context, item)).join(", ");
|
|
1204
|
+
};
|
|
1205
|
+
const compileFieldRef = (context, field) => {
|
|
1206
|
+
return `${quoteIdentifier(context, aliasForPath(context, parentPath(field.path)))}.${quoteIdentifier(context, field.field.physicalField)}`;
|
|
1207
|
+
};
|
|
1208
|
+
const addParam = (context, value) => {
|
|
1209
|
+
context.params.push(value);
|
|
1210
|
+
return context.compiler.placeholder(context.params.length);
|
|
1211
|
+
};
|
|
1212
|
+
const aliasForPath = (context, path) => context.aliasesByPath.get(path);
|
|
1213
|
+
const parentPath = (path) => {
|
|
1214
|
+
const parts = path.split(".");
|
|
1215
|
+
parts.pop();
|
|
1216
|
+
return parts.join(".");
|
|
1217
|
+
};
|
|
1218
|
+
const quoteIdentifier = (context, identifier) => context.compiler.quoteIdentifier(identifier);
|
|
1219
|
+
const stringValue = (value) => {
|
|
1220
|
+
if (value === void 0 || value === null) return "";
|
|
1221
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
|
|
1222
|
+
return JSON.stringify(value);
|
|
1223
|
+
};
|
|
1224
|
+
const escapeLikePattern = (value) => value.replaceAll(/([\\%_])/g, "\\$1");
|
|
1225
|
+
|
|
1226
|
+
//#endregion
|
|
1227
|
+
//#region src/results/schema.ts
|
|
1228
|
+
const buildQueryIRRowSchema = (ir) => {
|
|
1229
|
+
const shape = {};
|
|
1230
|
+
for (const field of ir.select) shape[field.path] = fieldSchemaForOutput(field);
|
|
1231
|
+
return z.object(shape).strict();
|
|
1232
|
+
};
|
|
1233
|
+
const buildQueryIRResultSchema = (ir) => z.array(buildQueryIRRowSchema(ir));
|
|
1234
|
+
const parseQueryIRResultRows = (ir, rows) => buildQueryIRResultSchema(ir).parse(rows);
|
|
1235
|
+
const safeParseQueryIRResultRows = (ir, rows) => buildQueryIRResultSchema(ir).safeParse(rows);
|
|
1236
|
+
const fieldSchemaForOutput = (field) => {
|
|
1237
|
+
const schema = fieldTypeSchema(field.type);
|
|
1238
|
+
if (field.nullable || isRelationPath(field.path)) return schema.nullable();
|
|
1239
|
+
return schema;
|
|
1240
|
+
};
|
|
1241
|
+
const fieldTypeSchema = (type) => {
|
|
1242
|
+
switch (type) {
|
|
1243
|
+
case "string":
|
|
1244
|
+
case "enum": return z.string();
|
|
1245
|
+
case "number": return z.number();
|
|
1246
|
+
case "boolean": return z.boolean();
|
|
1247
|
+
case "date":
|
|
1248
|
+
case "datetime": return z.union([z.string(), z.date()]);
|
|
1249
|
+
case "json": return JsonValueSchema;
|
|
1250
|
+
case "unknown": return z.unknown();
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
const isRelationPath = (path) => path.includes(".");
|
|
1254
|
+
|
|
1255
|
+
//#endregion
|
|
1256
|
+
//#region src/runtime.ts
|
|
1257
|
+
const createQueryRuntime = (input) => {
|
|
1258
|
+
async function run(runInput) {
|
|
1259
|
+
const registry = await resolveRegistryPromise({
|
|
1260
|
+
physical: input.physicalRegistry,
|
|
1261
|
+
...input.defaults === void 0 ? {} : { defaults: input.defaults },
|
|
1262
|
+
...input.policy === void 0 ? {} : { policy: input.policy },
|
|
1263
|
+
...input.policies === void 0 ? {} : { policies: input.policies }
|
|
1264
|
+
});
|
|
1265
|
+
const queryInput = {
|
|
1266
|
+
query: runInput.spec,
|
|
1267
|
+
registry,
|
|
1268
|
+
params: runInput.params
|
|
1269
|
+
};
|
|
1270
|
+
const ir = await lowerQuerySpecToIRPromise(queryInput);
|
|
1271
|
+
const sqlPlan = await compileQuerySpecToSQLPromise({
|
|
1272
|
+
...queryInput,
|
|
1273
|
+
...input.dialect === void 0 ? {} : { dialect: input.dialect }
|
|
1274
|
+
});
|
|
1275
|
+
const rows = parseQueryIRResultRows(ir, await input.executor({
|
|
1276
|
+
db: input.db,
|
|
1277
|
+
plan: sqlPlan
|
|
1278
|
+
}));
|
|
1279
|
+
if (runInput.explain === true) return {
|
|
1280
|
+
rows,
|
|
1281
|
+
explain: {
|
|
1282
|
+
registry,
|
|
1283
|
+
ir,
|
|
1284
|
+
sqlPlan
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
return { rows };
|
|
1288
|
+
}
|
|
1289
|
+
return { run };
|
|
1290
|
+
};
|
|
1291
|
+
|
|
1292
|
+
//#endregion
|
|
1293
|
+
//#region src/index.ts
|
|
1294
|
+
const queryKitVersion = "0.0.2-alpha.0";
|
|
1295
|
+
|
|
1296
|
+
//#endregion
|
|
1297
|
+
export { safeParseRegistryDefaults as $, FieldTypeSchema as A, RelationKindSchema as B, resolveRegistryEffect as C, ExposureModeSchema as D, AggregationSchema as E, PhysicalSourceSchema as F, ResolvedSourceSchema as G, ResolvedFieldSchema as H, RegistryDefaultsSchema as I, parsePhysicalRegistry as J, SourceDefaultsSchema as K, RegistryPolicySchema as L, PhysicalRegistrySchema as M, PhysicalRelationSchema as N, FieldDefaultsSchema as O, PhysicalSourceKindSchema as P, safeParsePhysicalRegistry as Q, RegistryVersionSchema as R, resolveRegistry as S, AdapterMetaSchema as T, ResolvedRegistrySchema as U, RelationPolicySchema as V, ResolvedRelationSchema as W, parseRegistryPolicy as X, parseRegistryDefaults as Y, parseResolvedRegistry as Z, validateQuerySpecEffect as _, parseQueryIRResultRows as a, QueryOrderBySchema as at, RegistryResolutionError as b, compileQuerySpecToSQLEffect as c, QuerySortDirectionSchema as ct, lowerQuerySpecToIREffect as d, parseQuerySpec as dt, safeParseRegistryPolicy as et, lowerQuerySpecToIRPromise as f, safeParseQuerySpec as ft, validateQuerySpec as g, QueryValidationIssueSchema as h, buildQueryIRRowSchema as i, QueryFilterSchema as it, PhysicalFieldSchema as j, FieldPolicySchema as k, compileQuerySpecToSQLPromise as l, QuerySpecSchema as lt, QueryValidationError as m, createQueryRuntime as n, JsonValueSchema as nt, safeParseQueryIRResultRows as o, QueryParamRefSchema as ot, QueryParseError as p, SourcePolicySchema as q, buildQueryIRResultSchema as r, QueryFilterOperatorSchema as rt, compileQuerySpecToSQL as s, QueryParamsSchema as st, queryKitVersion as t, safeParseResolvedRegistry as tt, lowerQuerySpecToIR as u, QueryVersionSchema as ut, validateQuerySpecPromise as v, resolveRegistryPromise as w, RegistryResolutionIssueSchema as x, RegistryParseError as y, RelationDefaultsSchema as z };
|
|
1298
|
+
//# sourceMappingURL=src-DGGMT7nT.js.map
|