@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.
@@ -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