@m1212e/rumble 0.11.7 → 0.12.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/out/index.js ADDED
@@ -0,0 +1,1849 @@
1
+ import { access, mkdir, rm, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { GraphQLEnumType, GraphQLError, GraphQLInputObjectType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType } from "graphql";
4
+ import { capitalize, cloneDeep, debounce, merge, toMerged } from "es-toolkit";
5
+ import { empty, fromValue, map, merge as merge$1, onPush, pipe, toObservable, toPromise } from "wonka";
6
+ import { EnvelopArmorPlugin } from "@escape.tech/graphql-armor";
7
+ import { useDisableIntrospection } from "@graphql-yoga/plugin-disable-introspection";
8
+ import { createPubSub, createYoga } from "graphql-yoga";
9
+ import { useSofa } from "sofa-api";
10
+ import { One, relationsFilterToSQL, sql } from "drizzle-orm";
11
+ import { toCamelCase } from "drizzle-orm/casing";
12
+ import { PgEnumColumn, PgTable } from "drizzle-orm/pg-core";
13
+ import pluralize from "pluralize";
14
+ import { MySqlTable } from "drizzle-orm/mysql-core";
15
+ import { SQLiteTable } from "drizzle-orm/sqlite-core";
16
+ import SchemaBuilder, { BasePlugin } from "@pothos/core";
17
+ import DrizzlePlugin from "@pothos/plugin-drizzle";
18
+ import SmartSubscriptionsPlugin, { subscribeOptionsFromIterator } from "@pothos/plugin-smart-subscriptions";
19
+ import { DateResolver, DateTimeISOResolver, JSONResolver } from "graphql-scalars";
20
+
21
+ //#region lib/client/generate/client.ts
22
+ function generateClient({ apiUrl, rumbleImportPath, useExternalUrqlClient, availableSubscriptions }) {
23
+ const imports = [];
24
+ let code = "";
25
+ if (typeof useExternalUrqlClient === "string") imports.push(`import { urqlClient } from "${useExternalUrqlClient}";`);
26
+ else {
27
+ imports.push(`import { Client, fetchExchange } from '@urql/core';`);
28
+ imports.push(`import { cacheExchange } from '@urql/exchange-graphcache';`);
29
+ }
30
+ imports.push(`import { makeLiveQuery, makeMutation, makeSubscription, makeQuery } from '${rumbleImportPath}';`);
31
+ if (!useExternalUrqlClient) code += `
32
+ const urqlClient = new Client({
33
+ url: "${apiUrl ?? "PLEASE PROVIDE A URL WHEN GENERATING OR IMPORT AN EXTERNAL URQL CLIENT"}",
34
+ fetchSubscriptions: true,
35
+ exchanges: [cacheExchange({}), fetchExchange],
36
+ fetchOptions: {
37
+ credentials: "include",
38
+ },
39
+ requestPolicy: "cache-and-network",
40
+ });
41
+ `;
42
+ code += `
43
+ export const client = {
44
+ /**
45
+ * A query and subscription combination. First queries and if exists, also subscribes to a subscription of the same name.
46
+ * Combines the results of both, so the result is first the query result and then live updates from the subscription.
47
+ * Assumes that the query and subscription return the same fields as per default when using the rumble query helpers.
48
+ * If no subscription with the same name exists, this will just be a query.
49
+ */
50
+ liveQuery: makeLiveQuery<Query>({
51
+ urqlClient,
52
+ availableSubscriptions: new Set([${availableSubscriptions.values().toArray().map((value) => `"${value}"`).join(", ")}]),
53
+ }),
54
+ /**
55
+ * A mutation that can be used to e.g. create, update or delete data.
56
+ */
57
+ mutate: makeMutation<Mutation>({
58
+ urqlClient,
59
+ }),
60
+ /**
61
+ * A continuous stream of results that updates when the server sends new data.
62
+ */
63
+ subscribe: makeSubscription<Subscription>({
64
+ urqlClient,
65
+ }),
66
+ /**
67
+ * A one-time fetch of data.
68
+ */
69
+ query: makeQuery<Query>({
70
+ urqlClient,
71
+ }),
72
+ }`;
73
+ return {
74
+ imports,
75
+ code
76
+ };
77
+ }
78
+
79
+ //#endregion
80
+ //#region lib/client/generate/tsRepresentation.ts
81
+ function makeTSRepresentation(model) {
82
+ if (model instanceof GraphQLObjectType) return makeTSTypeFromObject(model);
83
+ else if (model instanceof GraphQLScalarType) return mapGraphqlScalarToTSTypeString(model);
84
+ else if (model instanceof GraphQLEnumType) return makeStringLiteralUnionFromEnum(model);
85
+ else if (model instanceof GraphQLInputObjectType) return makeTSTypeFromInputObject(model);
86
+ throw new Error(`Unknown model type: ${model}`);
87
+ }
88
+ function makeTSTypeFromObject(model) {
89
+ const stringifiedFields = /* @__PURE__ */ new Map();
90
+ for (const [key, value] of Object.entries(model.getFields())) stringifiedFields.set(key, makeTSObjectTypeField(value.type, value.args));
91
+ return `{
92
+ ${stringifiedFields.entries().map(([key, value]) => `${key}: ${value}`).toArray().join(",\n ")}
93
+ }`;
94
+ }
95
+ function makeTSTypeFromInputObject(model) {
96
+ const stringifiedFields = /* @__PURE__ */ new Map();
97
+ for (const [key, value] of Object.entries(model.getFields())) stringifiedFields.set(key, makeTSInputObjectTypeField(value.type));
98
+ return `{
99
+ ${stringifiedFields.entries().map(([key, value]) => `${key}${value.includes("| undefined") ? "?" : ""}: ${value}`).toArray().join(",\n ")}
100
+ }`;
101
+ }
102
+ function makeTSObjectTypeField(returnType, args) {
103
+ let isNonNullReturnType = false;
104
+ let isList = false;
105
+ for (let index = 0; index < 3; index++) {
106
+ if (returnType instanceof GraphQLList) {
107
+ isList = true;
108
+ returnType = returnType.ofType;
109
+ }
110
+ if (returnType instanceof GraphQLNonNull) {
111
+ isNonNullReturnType = true;
112
+ returnType = returnType.ofType;
113
+ }
114
+ }
115
+ let returnTypeString = returnType.name;
116
+ if (isList) returnTypeString += "[]";
117
+ if (!isNonNullReturnType) returnTypeString += " | null";
118
+ const isRelationType = returnType instanceof GraphQLObjectType;
119
+ const argsStringMap = /* @__PURE__ */ new Map();
120
+ for (const arg of args ?? []) argsStringMap.set(arg.name, stringifyTSObjectArg(arg.type));
121
+ if (isRelationType) {
122
+ const makePOptional = argsStringMap.entries().every(([, value]) => value.includes("| undefined"));
123
+ return `(${(args ?? []).length > 0 ? `p${makePOptional ? "?" : ""}: {
124
+ ${argsStringMap.entries().map(([key, value]) => ` ${key}${value.includes("| undefined") ? "?" : ""}: ${value}`).toArray().join(",\n ")}
125
+ }` : ""}) => ${returnTypeString}`;
126
+ } else return returnTypeString;
127
+ }
128
+ function makeTSInputObjectTypeField(returnType) {
129
+ let isNonNullReturnType = false;
130
+ let isList = false;
131
+ for (let index = 0; index < 3; index++) {
132
+ if (returnType instanceof GraphQLList) {
133
+ isList = true;
134
+ returnType = returnType.ofType;
135
+ }
136
+ if (returnType instanceof GraphQLNonNull) {
137
+ isNonNullReturnType = true;
138
+ returnType = returnType.ofType;
139
+ }
140
+ }
141
+ let returnTypeString = returnType.name;
142
+ if (isList) returnTypeString += "[]";
143
+ if (!isNonNullReturnType) returnTypeString += " | null | undefined";
144
+ else if (isList) returnTypeString += " | undefined";
145
+ return returnTypeString;
146
+ }
147
+ function stringifyTSObjectArg(arg) {
148
+ let ret = "unknown";
149
+ let isNullable = true;
150
+ if (arg instanceof GraphQLNonNull) {
151
+ isNullable = false;
152
+ arg = arg.ofType;
153
+ }
154
+ if (arg instanceof GraphQLInputObjectType || arg instanceof GraphQLScalarType) ret = arg.name;
155
+ if (isNullable) ret += " | null | undefined";
156
+ return ret;
157
+ }
158
+ function mapGraphqlScalarToTSTypeString(arg) {
159
+ switch (arg.name) {
160
+ case "ID": return "string";
161
+ case "String": return "string";
162
+ case "Boolean": return "boolean";
163
+ case "Int": return "number";
164
+ case "Float": return "number";
165
+ case "Date": return "Date";
166
+ case "DateTime": return "Date";
167
+ case "JSON": return "any";
168
+ default: return "unknown";
169
+ }
170
+ }
171
+ function makeStringLiteralUnionFromEnum(enumType) {
172
+ return enumType.getValues().map((value) => `"${value.name}"`).join(" | ");
173
+ }
174
+
175
+ //#endregion
176
+ //#region lib/client/generate/generate.ts
177
+ async function generateFromSchema({ outputPath, schema, rumbleImportPath = "@m1212e/rumble", apiUrl, useExternalUrqlClient = false }) {
178
+ try {
179
+ await access(outputPath);
180
+ await rm(outputPath, {
181
+ recursive: true,
182
+ force: true
183
+ });
184
+ } catch (_error) {}
185
+ await mkdir(outputPath, { recursive: true });
186
+ if (!outputPath.endsWith("/")) outputPath += "/";
187
+ const imports = [];
188
+ let code = "";
189
+ const typeMap = /* @__PURE__ */ new Map();
190
+ for (const [key, object] of Object.entries(schema.getTypeMap())) {
191
+ if (key.startsWith("__")) continue;
192
+ typeMap.set(key, object);
193
+ }
194
+ for (const [key, object] of typeMap.entries()) {
195
+ const rep = makeTSRepresentation(object);
196
+ if (rep === key) continue;
197
+ code += `
198
+ export type ${key} = ${rep};
199
+ `;
200
+ }
201
+ const c = generateClient({
202
+ apiUrl,
203
+ useExternalUrqlClient,
204
+ rumbleImportPath,
205
+ availableSubscriptions: new Set(Object.keys(schema.getSubscriptionType()?.getFields() || {}))
206
+ });
207
+ imports.push(...c.imports);
208
+ code += c.code;
209
+ await writeFile(join(outputPath, "client.ts"), `${imports.join("\n")}\n${code}`);
210
+ }
211
+
212
+ //#endregion
213
+ //#region lib/client/request.ts
214
+ const argsKey = "__args";
215
+ function makeGraphQLQuery({ queryName, input, client, enableSubscription = false }) {
216
+ const otwQueryName = `${capitalize(queryName)}Query`;
217
+ const argsString = stringifyArgumentObjectToGraphqlList(input[argsKey] ?? {});
218
+ const operationString = (operationVerb) => `${operationVerb} ${otwQueryName} { ${queryName}${argsString} { ${stringifySelection(input)} }}`;
219
+ let awaitedReturnValueReference;
220
+ const response = pipe(merge$1([client.query(operationString("query"), {}), enableSubscription ? client.subscription(operationString("subscription"), {}) : empty]), map((v) => {
221
+ const data = v.data?.[queryName];
222
+ if (!data && v.error) throw v.error;
223
+ return data;
224
+ }), onPush((data) => {
225
+ if (awaitedReturnValueReference) Object.assign(awaitedReturnValueReference, data);
226
+ }));
227
+ const promise = new Promise((resolve) => {
228
+ const unsub = toObservable(response).subscribe((v) => {
229
+ unsub();
230
+ const newObservableWithAwaitedValue = pipe(merge$1([response, fromValue(v)]), toObservable);
231
+ awaitedReturnValueReference = {
232
+ ...v,
233
+ ...newObservableWithAwaitedValue
234
+ };
235
+ resolve(awaitedReturnValueReference);
236
+ }).unsubscribe;
237
+ });
238
+ Object.assign(promise, toObservable(response));
239
+ return promise;
240
+ }
241
+ function makeGraphQLMutation({ mutationName, input, client }) {
242
+ const otwMutationName = `${capitalize(mutationName)}Mutation`;
243
+ const argsString = stringifyArgumentObjectToGraphqlList(input[argsKey] ?? {});
244
+ const response = pipe(client.mutation(`mutation ${otwMutationName} { ${mutationName}${argsString} { ${stringifySelection(input)} }}`, {}), map((v) => {
245
+ const data = v.data?.[mutationName];
246
+ if (!data && v.error) throw v.error;
247
+ return data;
248
+ }));
249
+ const observable = toObservable(response);
250
+ const promise = toPromise(response).then((res) => {
251
+ Object.assign(res, observable);
252
+ return res;
253
+ });
254
+ Object.assign(promise, observable);
255
+ return promise;
256
+ }
257
+ function makeGraphQLSubscription({ subscriptionName, input, client }) {
258
+ const otwSubscriptionName = `${capitalize(subscriptionName)}Subscription`;
259
+ const argsString = stringifyArgumentObjectToGraphqlList(input[argsKey] ?? {});
260
+ return pipe(client.subscription(`subscription ${otwSubscriptionName} { ${subscriptionName}${argsString} { ${stringifySelection(input)} }}`, {}), map((v) => {
261
+ const data = v.data?.[subscriptionName];
262
+ if (!data && v.error) throw v.error;
263
+ return data;
264
+ }), toObservable);
265
+ }
266
+ function stringifySelection(selection) {
267
+ return Object.entries(selection).filter(([key]) => key !== argsKey).reduce((acc, [key, value]) => {
268
+ if (typeof value === "object") {
269
+ if (value[argsKey]) {
270
+ const argsString = stringifyArgumentObjectToGraphqlList(value[argsKey]);
271
+ acc += `${key}${argsString} { ${stringifySelection(value)} }
272
+ `;
273
+ return acc;
274
+ }
275
+ acc += `${key} { ${stringifySelection(value)} }
276
+ `;
277
+ } else acc += `${key}
278
+ `;
279
+ return acc;
280
+ }, "");
281
+ }
282
+ function stringifyArgumentObjectToGraphqlList(args) {
283
+ const entries = Object.entries(args);
284
+ if (entries.length === 0) return "";
285
+ return `(${entries.map(([key, value]) => `${key}: ${stringifyArgumentValue(value)}`).join(", ")})`;
286
+ }
287
+ function stringifyArgumentValue(arg) {
288
+ switch (typeof arg) {
289
+ case "string": return `"${arg}"`;
290
+ case "number": return `${arg}`;
291
+ case "bigint": return `${arg}`;
292
+ case "boolean": return `${arg}`;
293
+ case "symbol": throw new Error("Cannot stringify a symbol to send as gql arg");
294
+ case "undefined": return "null";
295
+ case "object": return `{ ${Object.entries(arg).map(([key, value]) => `${key}: ${stringifyArgumentValue(value)}`).join(", ")} }`;
296
+ case "function": throw new Error("Cannot stringify a function to send as gql arg");
297
+ }
298
+ }
299
+
300
+ //#endregion
301
+ //#region lib/client/liveQuery.ts
302
+ function makeLiveQuery({ urqlClient, availableSubscriptions }) {
303
+ return new Proxy({}, { get: (_target, prop) => {
304
+ return (input) => {
305
+ return makeGraphQLQuery({
306
+ queryName: prop,
307
+ input,
308
+ client: urqlClient,
309
+ enableSubscription: availableSubscriptions.has(prop)
310
+ });
311
+ };
312
+ } });
313
+ }
314
+
315
+ //#endregion
316
+ //#region lib/client/mutation.ts
317
+ function makeMutation({ urqlClient }) {
318
+ return new Proxy({}, { get: (_target, prop) => {
319
+ return (input) => {
320
+ return makeGraphQLMutation({
321
+ mutationName: prop,
322
+ input,
323
+ client: urqlClient
324
+ });
325
+ };
326
+ } });
327
+ }
328
+
329
+ //#endregion
330
+ //#region lib/client/query.ts
331
+ function makeQuery({ urqlClient }) {
332
+ return new Proxy({}, { get: (_target, prop) => {
333
+ return (input) => {
334
+ return makeGraphQLQuery({
335
+ queryName: prop,
336
+ input,
337
+ client: urqlClient,
338
+ enableSubscription: false
339
+ });
340
+ };
341
+ } });
342
+ }
343
+
344
+ //#endregion
345
+ //#region lib/client/subscription.ts
346
+ function makeSubscription({ urqlClient }) {
347
+ return new Proxy({}, { get: (_target, prop) => {
348
+ return (input) => {
349
+ return makeGraphQLSubscription({
350
+ subscriptionName: prop,
351
+ input,
352
+ client: urqlClient
353
+ });
354
+ };
355
+ } });
356
+ }
357
+
358
+ //#endregion
359
+ //#region lib/types/rumbleError.ts
360
+ /**
361
+ * An error that gets raised by rumble whenever something does not go according to plan.
362
+ * Mostly internals, configuration errors or other unexpected things.
363
+ */
364
+ var RumbleError = class extends Error {
365
+ constructor(message) {
366
+ super(message);
367
+ this.name = "RumbleError";
368
+ }
369
+ };
370
+ /**
371
+ * An error that gets raised by rumble whenever an error occurs in a resolver, containing
372
+ * information safely exposeable to the user.
373
+ * E.g. the assert helpers issue these.
374
+ */
375
+ var RumbleErrorSafe = class extends GraphQLError {};
376
+
377
+ //#endregion
378
+ //#region lib/helpers/asserts.ts
379
+ /**
380
+ *
381
+ * Helper function to map a drizzle findFirst query result,
382
+ * which may be optional, to a correct drizzle type.
383
+ *
384
+ * @throws RumbleError
385
+ *
386
+ * @example
387
+ *
388
+ * ```ts
389
+ * schemaBuilder.queryFields((t) => {
390
+ return {
391
+ findFirstUser: t.drizzleField({
392
+ type: UserRef,
393
+ resolve: (query, root, args, ctx, info) => {
394
+ return (
395
+ db.query.users
396
+ .findFirst({
397
+ ...query,
398
+ where: ctx.abilities.users.filter("read").single.where,
399
+ })
400
+ // note that we need to manually raise an error if the value is not found
401
+ .then(assertFindFirstExists)
402
+ );
403
+ },
404
+ }),
405
+ };
406
+ });
407
+ * ```
408
+ */
409
+ const assertFindFirstExists = (value) => {
410
+ if (!value) throw new RumbleErrorSafe("Value not found but required (findFirst)");
411
+ return value;
412
+ };
413
+ /**
414
+ *
415
+ * Helper function to map a drizzle findFirst query result,
416
+ * which may be optional, to a correct drizzle type.
417
+ *
418
+ * @throws RumbleError
419
+ *
420
+ * @example
421
+ *
422
+ * ```ts
423
+ schemaBuilder.mutationFields((t) => {
424
+ return {
425
+ updateUsername: t.drizzleField({
426
+ type: UserRef,
427
+ args: {
428
+ userId: t.arg.int({ required: true }),
429
+ newName: t.arg.string({ required: true }),
430
+ },
431
+ resolve: (query, root, args, ctx, info) => {
432
+ return db
433
+ .update(schema.users)
434
+ .set({
435
+ name: args.newName,
436
+ })
437
+ .where(
438
+ and(
439
+ eq(schema.users.id, args.userId),
440
+ ctx.abilities.users.filter("update").single.where
441
+ )
442
+ )
443
+ .returning({ id: schema.users.id, name: schema.users.name })
444
+ // note that we need to manually raise an error if the value is not found
445
+ .then(assertFirstEntryExists);
446
+ },
447
+ }),
448
+ };
449
+ });
450
+ * ```
451
+ */
452
+ const assertFirstEntryExists = (value) => {
453
+ const v = value.at(0);
454
+ if (!v) throw new RumbleErrorSafe("Value not found but required (firstEntry)");
455
+ return v;
456
+ };
457
+
458
+ //#endregion
459
+ //#region lib/helpers/mapNullFieldsToUndefined.ts
460
+ /**
461
+ * Helper to map null fields to undefined
462
+ * @param obj The object to map
463
+ * @returns The mapped object with all fields of 'null' transformed to 'undefined'
464
+ *
465
+ * This becomes useful for update mutations where you do not want to pass null (unset value in db)
466
+ * but undefined (do not touch value in db) in case of a value not beeing set in the args of said mutation
467
+ * @example
468
+ * ```ts
469
+ * updateUser: t.drizzleField({
470
+ type: User,
471
+ args: {
472
+ id: t.arg.string({ required: true }),
473
+ email: t.arg.string(),
474
+ lastName: t.arg.string(),
475
+ firstName: t.arg.string(),
476
+ },
477
+ resolve: async (query, root, args, ctx, info) => {
478
+ const mappedArgs = mapNullFieldsToUndefined(args);
479
+ const user = await db.transaction(async (tx) => {
480
+ const user = await tx
481
+ .update(schema.user)
482
+ .set({
483
+
484
+ // email: args.email ?? undefined,
485
+ // lastName: args.lastName ?? undefined,
486
+ // firstName: args.firstName ?? undefined,
487
+
488
+ // becomes this
489
+
490
+ email: mappedArgs.email,
491
+ lastName: mappedArgs.lastName,
492
+ firstName: mappedArgs.firstName,
493
+ })
494
+ .returning()
495
+ .then(assertFirstEntryExists);
496
+ return user;
497
+ });
498
+
499
+ pubsub.updated(user.id);
500
+
501
+ return db.query.user
502
+ .findFirst(
503
+ query(
504
+ ctx.abilities.user.filter('read').merge(
505
+ {
506
+ where: { id: user.id },
507
+ }
508
+ 1).query.single,
509
+ ),
510
+ )
511
+ .then(assertFindFirstExists);
512
+ },
513
+ }),
514
+ *
515
+ *
516
+ * ```
517
+ */
518
+ function mapNullFieldsToUndefined(obj) {
519
+ return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, value === null ? void 0 : value]));
520
+ }
521
+
522
+ //#endregion
523
+ //#region lib/helpers/lazy.ts
524
+ /**
525
+ * Creates a lazily initialized function.
526
+ *
527
+ * The returned function will call the initializer function on its first call and
528
+ * store the result. On subsequent calls, it will return the stored result.
529
+ *
530
+ * @param initializer The function to be called for initialization.
531
+ * @returns A function that calls the initializer function on its first call and
532
+ * returns the stored result on subsequent calls.
533
+ */
534
+ function lazy(initializer) {
535
+ let value;
536
+ let initialized = false;
537
+ return () => {
538
+ if (!initialized) {
539
+ value = initializer();
540
+ initialized = true;
541
+ }
542
+ return value;
543
+ };
544
+ }
545
+
546
+ //#endregion
547
+ //#region lib/helpers/mergeFilters.ts
548
+ function mergeFilters(filterA, filterB) {
549
+ return {
550
+ where: filterA?.where && filterB?.where ? { AND: [filterA?.where, filterB?.where] } : filterA?.where ?? filterB?.where,
551
+ columns: filterA?.columns || filterB?.columns ? new Set([Object.entries(filterA?.columns ?? {}), Object.entries(filterB?.columns ?? {})].flat().filter(([, v]) => v === true).map(([k]) => k)).entries().reduce((acc, [key]) => {
552
+ acc[key] = true;
553
+ return acc;
554
+ }, {}) : void 0,
555
+ extras: filterA?.extras || filterB?.extras ? toMerged(filterA?.extras ?? {}, filterB?.extras ?? {}) : void 0,
556
+ orderBy: filterA?.orderBy || filterB?.orderBy ? toMerged(filterA?.orderBy ?? {}, filterB?.orderBy ?? {}) : void 0,
557
+ limit: filterA?.limit || filterB?.limit ? Math.min(filterA?.limit ?? Infinity, filterB?.limit ?? Infinity) : void 0,
558
+ offset: filterA?.offset || filterB?.offset ? Math.min(filterA?.offset ?? Infinity, filterB?.offset ?? Infinity) : void 0,
559
+ with: filterA?.with || filterB?.with ? toMerged(filterA?.with ?? {}, filterB?.with ?? {}) : void 0
560
+ };
561
+ }
562
+
563
+ //#endregion
564
+ //#region lib/helpers/sqlTypes/types.ts
565
+ const intLikeSQLTypeStrings = [
566
+ "serial",
567
+ "int",
568
+ "integer",
569
+ "tinyint",
570
+ "smallint",
571
+ "mediumint"
572
+ ];
573
+ function isIntLikeSQLTypeString(sqlType) {
574
+ return intLikeSQLTypeStrings.includes(sqlType);
575
+ }
576
+ const floatLikeSQLTypeStrings = [
577
+ "real",
578
+ "decimal",
579
+ "double",
580
+ "float",
581
+ "numeric"
582
+ ];
583
+ function isFloatLikeSQLTypeString(sqlType) {
584
+ return floatLikeSQLTypeStrings.includes(sqlType);
585
+ }
586
+ const stringLikeSQLTypeStrings = [
587
+ "string",
588
+ "text",
589
+ "varchar",
590
+ "char",
591
+ "text(256)"
592
+ ];
593
+ function isStringLikeSQLTypeString(sqlType) {
594
+ return stringLikeSQLTypeStrings.includes(sqlType);
595
+ }
596
+ const IDLikeSQLTypeStrings = ["uuid"];
597
+ function isIDLikeSQLTypeString(sqlType) {
598
+ return IDLikeSQLTypeStrings.includes(sqlType);
599
+ }
600
+ const booleanSQLTypeStrings = ["boolean"];
601
+ function isBooleanSQLTypeString(sqlType) {
602
+ return booleanSQLTypeStrings.includes(sqlType);
603
+ }
604
+ const dateTimeLikeSQLTypeStrings = ["timestamp", "datetime"];
605
+ function isDateTimeLikeSQLTypeString(sqlType) {
606
+ return dateTimeLikeSQLTypeStrings.includes(sqlType);
607
+ }
608
+ const dateLikeSQLTypeStrings = ["date"];
609
+ function isDateLikeSQLTypeString(sqlType) {
610
+ return dateLikeSQLTypeStrings.includes(sqlType);
611
+ }
612
+ const jsonLikeSQLTypeStrings = ["json"];
613
+ function isJSONLikeSQLTypeString(sqlType) {
614
+ return jsonLikeSQLTypeStrings.includes(sqlType);
615
+ }
616
+ [
617
+ ...intLikeSQLTypeStrings,
618
+ ...floatLikeSQLTypeStrings,
619
+ ...stringLikeSQLTypeStrings,
620
+ ...IDLikeSQLTypeStrings,
621
+ ...booleanSQLTypeStrings,
622
+ ...dateTimeLikeSQLTypeStrings,
623
+ ...dateLikeSQLTypeStrings,
624
+ ...jsonLikeSQLTypeStrings
625
+ ];
626
+ const UnknownTypeRumbleError = (sqlType, additionalInfo) => new RumbleError(`RumbleError: Unknown SQL type '${sqlType}'. Please open an issue (https://github.com/m1212e/rumble/issues) so it can be added. (${additionalInfo})`);
627
+
628
+ //#endregion
629
+ //#region lib/helpers/sqlTypes/distinctValuesFromSQLType.ts
630
+ function createDistinctValuesFromSQLType(sqlType) {
631
+ if (isIntLikeSQLTypeString(sqlType)) return {
632
+ value1: 1,
633
+ value2: 2
634
+ };
635
+ if (isFloatLikeSQLTypeString(sqlType)) return {
636
+ value1: 1.1,
637
+ value2: 2.2
638
+ };
639
+ if (isStringLikeSQLTypeString(sqlType)) return {
640
+ value1: "a",
641
+ value2: "b"
642
+ };
643
+ if (isIDLikeSQLTypeString(sqlType)) return {
644
+ value1: "fba31870-5528-42d7-b27e-2e5ee657aea5",
645
+ value2: "fc65db81-c2d1-483d-8a25-a30e2cf6e02d"
646
+ };
647
+ if (isBooleanSQLTypeString(sqlType)) return {
648
+ value1: true,
649
+ value2: false
650
+ };
651
+ if (isDateTimeLikeSQLTypeString(sqlType)) return {
652
+ value1: new Date(2022, 1, 1),
653
+ value2: new Date(2022, 1, 2)
654
+ };
655
+ if (isDateLikeSQLTypeString(sqlType)) return {
656
+ value1: new Date(2022, 1, 1),
657
+ value2: new Date(2022, 1, 2)
658
+ };
659
+ if (isJSONLikeSQLTypeString(sqlType)) return {
660
+ value1: { a: 1 },
661
+ value2: { b: 2 }
662
+ };
663
+ throw UnknownTypeRumbleError(sqlType, "Distinct");
664
+ }
665
+
666
+ //#endregion
667
+ //#region lib/helpers/tableHelpers.ts
668
+ const drizzleNameSymbol = Symbol.for("drizzle:Name");
669
+ const drizzleOriginalNameSymbol = Symbol.for("drizzle:OriginalName");
670
+ const drizzleBaseNameSymbol = Symbol.for("drizzle:BaseName");
671
+ function tableHelper({ db, table }) {
672
+ if (typeof table !== "string") table = table.tsName || table.dbName || table[drizzleNameSymbol] || table[drizzleOriginalNameSymbol] || table[drizzleBaseNameSymbol];
673
+ const foundRelation = Object.values(db._.relations).find((schema) => schema.name === table || schema.table[drizzleNameSymbol] === table || schema.table[drizzleOriginalNameSymbol] === table || schema.table[drizzleBaseNameSymbol] === table);
674
+ if (!foundRelation) throw new RumbleError(`Could not find schema for ${JSON.stringify(table)}`);
675
+ const foundSchema = Object.values(db._.schema).find((schema) => schema.dbName === foundRelation.table[drizzleOriginalNameSymbol]);
676
+ if (!foundSchema) throw new RumbleError(`Could not find schema for ${JSON.stringify(table)}`);
677
+ return {
678
+ columns: foundSchema.columns,
679
+ primaryKey: foundSchema.primaryKey,
680
+ relations: foundRelation.relations,
681
+ dbName: foundSchema.dbName,
682
+ tsName: foundSchema.tsName,
683
+ foundSchema,
684
+ foundRelation
685
+ };
686
+ }
687
+
688
+ //#endregion
689
+ //#region lib/abilityBuilder.ts
690
+ function isDynamicQueryFilter(filter) {
691
+ return typeof filter === "function" && filter.constructor.name !== "AsyncFunction";
692
+ }
693
+ function isStaticQueryFilter(filter) {
694
+ return typeof filter !== "function";
695
+ }
696
+ const nothingRegisteredWarningLogger = debounce((model, action) => {
697
+ console.warn(`
698
+ Warning! No abilities have been registered for
699
+
700
+ ${model}/${action}
701
+
702
+ but has been accessed. This will block everything. If this is intended, you can ignore this warning. If not, please ensure that you register the ability in your ability builder.
703
+ `);
704
+ }, 1e3);
705
+ const createAbilityBuilder = ({ db, actions, defaultLimit }) => {
706
+ let hasBeenBuilt = false;
707
+ const createBuilderForTable = () => {
708
+ const queryFilters = /* @__PURE__ */ new Map();
709
+ const runtimeFilters = /* @__PURE__ */ new Map();
710
+ for (const action of actions) if (!runtimeFilters.has(action)) runtimeFilters.set(action, []);
711
+ return {
712
+ allow: (action) => {
713
+ if (hasBeenBuilt) throw new RumbleError("You can't call allow() after the ability builder has been built. Please ensure that you register all abilities before accessing them.");
714
+ const actions$1 = Array.isArray(action) ? action : [action];
715
+ for (const action$1 of actions$1) {
716
+ let filters = queryFilters.get(action$1);
717
+ if (!filters) {
718
+ filters = "unrestricted";
719
+ queryFilters.set(action$1, filters);
720
+ }
721
+ }
722
+ return { when: (queryFilter) => {
723
+ for (const action$1 of actions$1) {
724
+ if (queryFilters.get(action$1) === "unrestricted") queryFilters.set(action$1, []);
725
+ queryFilters.get(action$1).push(queryFilter);
726
+ }
727
+ } };
728
+ },
729
+ filter: (action) => {
730
+ const actions$1 = Array.isArray(action) ? action : [action];
731
+ return { by: (explicitFilter) => {
732
+ for (const action$1 of actions$1) runtimeFilters.get(action$1).push(explicitFilter);
733
+ } };
734
+ },
735
+ _: {
736
+ runtimeFilters,
737
+ queryFilters
738
+ }
739
+ };
740
+ };
741
+ const buildersPerTable = Object.fromEntries(Object.keys(db.query).map((tableName) => [tableName, createBuilderForTable()]));
742
+ return {
743
+ ...buildersPerTable,
744
+ _: {
745
+ registeredFilters({ action, table }) {
746
+ return buildersPerTable[table]._.runtimeFilters.get(action);
747
+ },
748
+ build() {
749
+ const createFilterForTable = (tableName) => {
750
+ const queryFilters = buildersPerTable[tableName]._.queryFilters;
751
+ const simpleQueryFilters = Object.fromEntries(actions.map((action) => {
752
+ const filters = queryFilters.get(action);
753
+ if (!filters || filters === "unrestricted") return [action, []];
754
+ return [action, filters.filter(isStaticQueryFilter)];
755
+ }));
756
+ const dynamicQueryFilters = Object.fromEntries(actions.map((action) => {
757
+ const filters = queryFilters.get(action);
758
+ if (!filters || filters === "unrestricted") return [action, []];
759
+ return [action, filters.filter(isDynamicQueryFilter)];
760
+ }));
761
+ const tableSchema = tableHelper({
762
+ db,
763
+ table: tableName
764
+ });
765
+ if (Object.keys(tableSchema.primaryKey).length === 0) throw new RumbleError(`No primary key found for entity ${tableName.toString()}`);
766
+ const primaryKeyField = Object.values(tableSchema.primaryKey)[0];
767
+ const distinctValues = createDistinctValuesFromSQLType(primaryKeyField.getSQLType());
768
+ const blockEverythingFilter = { where: { AND: [{ [primaryKeyField.name]: distinctValues.value1 }, { [primaryKeyField.name]: distinctValues.value2 }] } };
769
+ /**
770
+ * Packs the filters into a response object that can be applied for queries by the user
771
+ */
772
+ function transformToResponse(queryFilters$1) {
773
+ const internalTransformer = (filters, mergedLimit) => {
774
+ const limit = lazy(() => {
775
+ if (mergedLimit !== void 0) {
776
+ if (!filters?.limit) return mergedLimit;
777
+ if (filters.limit > mergedLimit) return mergedLimit;
778
+ }
779
+ let limit$1 = filters?.limit;
780
+ if (defaultLimit && (limit$1 === void 0 || limit$1 > defaultLimit)) limit$1 = defaultLimit;
781
+ return limit$1 ?? void 0;
782
+ });
783
+ const sqlTransformedWhere = lazy(() => {
784
+ return filters?.where ? relationsFilterToSQL(tableSchema.foundRelation.table, filters.where) : void 0;
785
+ });
786
+ if (filters?.columns) return {
787
+ query: {
788
+ single: {
789
+ where: filters?.where,
790
+ columns: filters?.columns
791
+ },
792
+ many: {
793
+ where: filters?.where,
794
+ columns: filters?.columns,
795
+ get limit() {
796
+ return limit();
797
+ }
798
+ }
799
+ },
800
+ sql: { get where() {
801
+ return sqlTransformedWhere();
802
+ } }
803
+ };
804
+ else return {
805
+ query: {
806
+ single: { where: filters?.where },
807
+ many: {
808
+ where: filters?.where,
809
+ get limit() {
810
+ return limit();
811
+ }
812
+ }
813
+ },
814
+ sql: { get where() {
815
+ return sqlTransformedWhere();
816
+ } }
817
+ };
818
+ };
819
+ const ret = internalTransformer(queryFilters$1);
820
+ /**
821
+ * Merges the current query filters with the provided filters for this call only
822
+ */
823
+ function merge$2(p) {
824
+ return internalTransformer(mergeFilters(ret.query.many, p), p.limit);
825
+ }
826
+ ret.merge = merge$2;
827
+ return ret;
828
+ }
829
+ return { withContext: (userContext) => {
830
+ return { filter: (action) => {
831
+ const filters = queryFilters.get(action);
832
+ if (filters === "unrestricted") return transformToResponse();
833
+ if (!filters) {
834
+ nothingRegisteredWarningLogger(tableName.toString(), action);
835
+ return transformToResponse(blockEverythingFilter);
836
+ }
837
+ const dynamicResults = new Array(dynamicQueryFilters[action].length);
838
+ let filtersReturned = 0;
839
+ for (let i = 0; i < dynamicQueryFilters[action].length; i++) {
840
+ const func = dynamicQueryFilters[action][i];
841
+ const result = func(userContext);
842
+ if (result === "allow") return transformToResponse();
843
+ if (result === void 0) continue;
844
+ dynamicResults[filtersReturned++] = result;
845
+ }
846
+ dynamicResults.length = filtersReturned;
847
+ const allQueryFilters = [...simpleQueryFilters[action], ...dynamicResults];
848
+ if (allQueryFilters.length === 0) return transformToResponse(blockEverythingFilter);
849
+ return transformToResponse(allQueryFilters.length === 1 ? allQueryFilters[0] : allQueryFilters.reduce((a, b) => {
850
+ return mergeFilters(a, b);
851
+ }, {}));
852
+ } };
853
+ } };
854
+ };
855
+ const abilitiesPerTable = Object.fromEntries(Object.keys(db.query).map((tableName) => [tableName, createFilterForTable(tableName)]));
856
+ hasBeenBuilt = true;
857
+ return (ctx) => {
858
+ return Object.fromEntries(Object.keys(abilitiesPerTable).map((tableName) => [tableName, abilitiesPerTable[tableName].withContext(ctx)]));
859
+ };
860
+ }
861
+ }
862
+ };
863
+ };
864
+
865
+ //#endregion
866
+ //#region lib/args/orderArg.ts
867
+ const makeDefaultName$1 = (dbName) => `${capitalize(toCamelCase(dbName.toString()))}OrderInputArgument`;
868
+ const createOrderArgImplementer = ({ db, schemaBuilder }) => {
869
+ const referenceStorage = /* @__PURE__ */ new Map();
870
+ const sortingParameterEnumRef = lazy(() => schemaBuilder.enumType("SortingParameter", { values: ["asc", "desc"] }));
871
+ const orderArgImplementer = ({ table, refName, dbName }) => {
872
+ const tableSchema = tableHelper({
873
+ db,
874
+ table: dbName ?? table
875
+ });
876
+ const inputTypeName = refName ?? makeDefaultName$1(tableSchema.tsName);
877
+ let ret = referenceStorage.get(inputTypeName);
878
+ if (ret) return ret;
879
+ const implement = () => {
880
+ return schemaBuilder.inputType(inputTypeName, { fields: (t) => {
881
+ const fields = Object.entries(tableSchema.columns).reduce((acc, [key]) => {
882
+ acc[key] = t.field({
883
+ type: sortingParameterEnumRef(),
884
+ required: false
885
+ });
886
+ return acc;
887
+ }, {});
888
+ const relations = Object.entries(tableSchema.relations ?? {}).reduce((acc, [key, value]) => {
889
+ const referenceModel = orderArgImplementer({ dbName: tableHelper({
890
+ db,
891
+ table: value.targetTable
892
+ }).dbName });
893
+ acc[key] = t.field({
894
+ type: referenceModel,
895
+ required: false
896
+ });
897
+ return acc;
898
+ }, {});
899
+ return {
900
+ ...fields,
901
+ ...relations
902
+ };
903
+ } });
904
+ };
905
+ ret = implement();
906
+ referenceStorage.set(inputTypeName, ret);
907
+ return ret;
908
+ };
909
+ return orderArgImplementer;
910
+ };
911
+
912
+ //#endregion
913
+ //#region lib/enum.ts
914
+ /**
915
+ * Checks if a schema type is an enum
916
+ */
917
+ function isEnumSchema(schemaType) {
918
+ return schemaType instanceof PgEnumColumn;
919
+ }
920
+ const createEnumImplementer = ({ db, schemaBuilder }) => {
921
+ const referenceStorage = /* @__PURE__ */ new Map();
922
+ const enumImplementer = ({ tsName, enumColumn, refName }) => {
923
+ let enumSchemaName;
924
+ let enumValues;
925
+ if (tsName) {
926
+ const schemaEnum = db._.schema[tsName];
927
+ enumSchemaName = tsName.toString();
928
+ const enumCol = Object.values(db._.schema).filter((s) => typeof s === "object").map((s) => Object.values(s.columns)).flat(2).find((s) => s.config?.enum === schemaEnum);
929
+ if (!enumCol) throw new RumbleError(`Could not find applied enum column for ${tsName.toString()}.
930
+ Please ensure that you use the enum at least once as a column of a table!`);
931
+ enumValues = enumCol.enumValues;
932
+ } else if (enumColumn) {
933
+ enumSchemaName = enumColumn.config.name;
934
+ enumValues = enumColumn.enumValues;
935
+ }
936
+ if (!enumSchemaName || !enumValues) throw new RumbleError("Could not determine enum structure!");
937
+ const graphqlImplementationName = refName ?? `${capitalize(toCamelCase(enumSchemaName))}Enum`;
938
+ let ret = referenceStorage.get(graphqlImplementationName);
939
+ if (ret) return ret;
940
+ const implement = () => schemaBuilder.enumType(graphqlImplementationName, { values: enumValues });
941
+ ret = implement();
942
+ referenceStorage.set(graphqlImplementationName, ret);
943
+ return ret;
944
+ };
945
+ return enumImplementer;
946
+ };
947
+
948
+ //#endregion
949
+ //#region lib/helpers/sqlTypes/mapSQLTypeToTSType.ts
950
+ function mapSQLTypeToGraphQLType({ sqlType, fieldName }) {
951
+ let ret;
952
+ if (isIntLikeSQLTypeString(sqlType)) ret = "Int";
953
+ if (isFloatLikeSQLTypeString(sqlType)) ret = "Float";
954
+ if (isStringLikeSQLTypeString(sqlType)) if (fieldName && (fieldName.toLowerCase().endsWith("_id") || fieldName.toLowerCase().endsWith("id"))) ret = "ID";
955
+ else ret = "String";
956
+ if (isIDLikeSQLTypeString(sqlType)) ret = "ID";
957
+ if (isBooleanSQLTypeString(sqlType)) ret = "Boolean";
958
+ if (isDateTimeLikeSQLTypeString(sqlType)) ret = "DateTime";
959
+ if (isDateLikeSQLTypeString(sqlType)) ret = "Date";
960
+ if (isJSONLikeSQLTypeString(sqlType)) ret = "JSON";
961
+ if (ret !== void 0) return ret;
962
+ throw UnknownTypeRumbleError(sqlType, "SQL to GQL");
963
+ }
964
+
965
+ //#endregion
966
+ //#region lib/args/whereArg.ts
967
+ const makeDefaultName = (dbName) => `${capitalize(toCamelCase(dbName.toString()))}WhereInputArgument`;
968
+ const createWhereArgImplementer = ({ db, schemaBuilder, enumImplementer }) => {
969
+ const referenceStorage = /* @__PURE__ */ new Map();
970
+ const whereArgImplementer = ({ table, refName, dbName }) => {
971
+ const tableSchema = tableHelper({
972
+ db,
973
+ table: dbName ?? table
974
+ });
975
+ const inputTypeName = refName ?? makeDefaultName(tableSchema.tsName);
976
+ let ret = referenceStorage.get(inputTypeName);
977
+ if (ret) return ret;
978
+ const implement = () => {
979
+ return schemaBuilder.inputType(inputTypeName, { fields: (t) => {
980
+ const mapSQLTypeStringToInputPothosType = (sqlType, fieldName) => {
981
+ const gqlType = mapSQLTypeToGraphQLType({
982
+ sqlType,
983
+ fieldName
984
+ });
985
+ switch (gqlType) {
986
+ case "Int": return t.field({ type: "IntWhereInputArgument" });
987
+ case "String": return t.field({ type: "StringWhereInputArgument" });
988
+ case "Boolean": return t.boolean({ required: false });
989
+ case "Date": return t.field({ type: "DateWhereInputArgument" });
990
+ case "DateTime": return t.field({ type: "DateWhereInputArgument" });
991
+ case "Float": return t.field({ type: "FloatWhereInputArgument" });
992
+ case "ID": return t.id({ required: false });
993
+ case "JSON": return t.field({
994
+ type: "JSON",
995
+ required: false
996
+ });
997
+ default: throw new RumbleError(`Unsupported argument type ${gqlType} for column ${sqlType}`);
998
+ }
999
+ };
1000
+ const fields = Object.entries(tableSchema.columns).reduce((acc, [key, value]) => {
1001
+ if (isEnumSchema(value)) {
1002
+ const enumImpl = enumImplementer({ enumColumn: value });
1003
+ acc[key] = t.field({
1004
+ type: enumImpl,
1005
+ required: false
1006
+ });
1007
+ } else acc[key] = mapSQLTypeStringToInputPothosType(value.getSQLType(), key);
1008
+ return acc;
1009
+ }, {});
1010
+ const relations = Object.entries(tableSchema.relations ?? {}).reduce((acc, [key, value]) => {
1011
+ const referenceModel = whereArgImplementer({ dbName: tableHelper({
1012
+ db,
1013
+ table: value.targetTable
1014
+ }).dbName });
1015
+ acc[key] = t.field({
1016
+ type: referenceModel,
1017
+ required: false
1018
+ });
1019
+ return acc;
1020
+ }, {});
1021
+ return {
1022
+ ...fields,
1023
+ ...relations
1024
+ };
1025
+ } });
1026
+ };
1027
+ ret = implement();
1028
+ referenceStorage.set(inputTypeName, ret);
1029
+ return ret;
1030
+ };
1031
+ return whereArgImplementer;
1032
+ };
1033
+
1034
+ //#endregion
1035
+ //#region lib/client/client.ts
1036
+ const clientCreatorImplementer = ({ builtSchema }) => {
1037
+ if (process.env.NODE_ENV !== "development") console.warn("Running rumble client generation in non development mode. Are you sure this is correct?");
1038
+ const clientCreator = async ({ apiUrl, outputPath, rumbleImportPath, useExternalUrqlClient }) => {
1039
+ await generateFromSchema({
1040
+ schema: builtSchema(),
1041
+ outputPath,
1042
+ rumbleImportPath,
1043
+ apiUrl,
1044
+ useExternalUrqlClient
1045
+ });
1046
+ };
1047
+ return clientCreator;
1048
+ };
1049
+
1050
+ //#endregion
1051
+ //#region lib/context.ts
1052
+ const createContextFunction = ({ context: makeUserContext, abilityBuilder }) => {
1053
+ const builtAbilityBuilder = lazy(() => abilityBuilder._.build());
1054
+ return async (req) => {
1055
+ const userContext = makeUserContext ? await makeUserContext(req) : {};
1056
+ return {
1057
+ ...userContext,
1058
+ abilities: builtAbilityBuilder()(userContext)
1059
+ };
1060
+ };
1061
+ };
1062
+
1063
+ //#endregion
1064
+ //#region lib/helpers/sofaOpenAPIWebhookDocs.ts
1065
+ const sofaOpenAPIWebhookDocs = {
1066
+ paths: {
1067
+ "/webhook": { post: {
1068
+ operationId: "webhook_create",
1069
+ description: "Creates a webhook subscription.",
1070
+ tags: [],
1071
+ parameters: [],
1072
+ requestBody: { content: { "application/json": { schema: { $ref: "#/components/schemas/WebhookCreateBody" } } } },
1073
+ responses: { "200": {
1074
+ description: "",
1075
+ content: { "application/json": { schema: { $ref: "#/components/schemas/WebhookDetailResponse" } } }
1076
+ } }
1077
+ } },
1078
+ "/webhook/{id}": {
1079
+ post: {
1080
+ operationId: "webhook_update",
1081
+ description: "Updates a webhook subscription.",
1082
+ parameters: [{
1083
+ name: "id",
1084
+ in: "path",
1085
+ description: "The ID of the webhook to update",
1086
+ required: true,
1087
+ schema: { type: "string" }
1088
+ }],
1089
+ requestBody: { content: { "application/json": { schema: { $ref: "#/components/schemas/WebhookCreateBody" } } } },
1090
+ responses: { "200": {
1091
+ description: "",
1092
+ content: { "application/json": { schema: { $ref: "#/components/schemas/WebhookDetailResponse" } } }
1093
+ } }
1094
+ },
1095
+ delete: {
1096
+ operationId: "webhook_delete",
1097
+ description: "Removes a webhook subscription.",
1098
+ tags: [],
1099
+ parameters: [{
1100
+ name: "id",
1101
+ in: "path",
1102
+ description: "The ID of the webhook to delete",
1103
+ required: true,
1104
+ schema: { type: "string" }
1105
+ }],
1106
+ responses: { "200": {
1107
+ description: "",
1108
+ content: { "application/json": { schema: { $ref: "#/components/schemas/WebhookDetailResponse" } } }
1109
+ } }
1110
+ }
1111
+ }
1112
+ },
1113
+ components: { schemas: {
1114
+ WebhookCreateBody: {
1115
+ type: "object",
1116
+ properties: {
1117
+ subscription: {
1118
+ description: "The subscription to subscribe to. In many cases, these match the available query IDs without the '_query' suffix. E.g., 'users_query' -> 'users'. See the graphql schema for more details on what subscriptions are available.",
1119
+ type: "string"
1120
+ },
1121
+ variables: {
1122
+ description: "The variables to pass to the subscription.",
1123
+ type: "object"
1124
+ },
1125
+ url: {
1126
+ description: "The URL to send the webhook to.",
1127
+ type: "string"
1128
+ }
1129
+ }
1130
+ },
1131
+ WebhookDetailResponse: {
1132
+ type: "object",
1133
+ properties: { id: {
1134
+ description: "The ID of the webhook. Can be used as reference in update or delete calls.",
1135
+ type: "string"
1136
+ } }
1137
+ },
1138
+ DateTime: {
1139
+ type: "string",
1140
+ format: "date-time"
1141
+ },
1142
+ Date: {
1143
+ type: "string",
1144
+ format: "date"
1145
+ }
1146
+ } }
1147
+ };
1148
+
1149
+ //#endregion
1150
+ //#region lib/helpers/sqlTypes/mapDrizzleTypeToGraphQlType.ts
1151
+ function buildPothosResponseTypeFromGraphQLType({ builder, sqlType, fieldName, nullable }) {
1152
+ const gqlType = mapSQLTypeToGraphQLType({
1153
+ sqlType,
1154
+ fieldName
1155
+ });
1156
+ switch (gqlType) {
1157
+ case "Int": return builder.exposeInt(fieldName, { nullable });
1158
+ case "String": return builder.exposeString(fieldName, { nullable });
1159
+ case "Boolean": return builder.exposeBoolean(fieldName, { nullable });
1160
+ case "Date": return builder.field({
1161
+ type: "Date",
1162
+ resolve: (element) => element[fieldName],
1163
+ nullable
1164
+ });
1165
+ case "DateTime": return builder.field({
1166
+ type: "DateTime",
1167
+ resolve: (element) => element[fieldName],
1168
+ nullable
1169
+ });
1170
+ case "Float": return builder.exposeFloat(fieldName, { nullable });
1171
+ case "ID": return builder.exposeID(fieldName, { nullable });
1172
+ case "JSON": return builder.field({
1173
+ type: "JSON",
1174
+ resolve: (element) => element[fieldName],
1175
+ nullable
1176
+ });
1177
+ default: throw new RumbleError(`Unsupported object type ${gqlType} for column ${fieldName}`);
1178
+ }
1179
+ }
1180
+
1181
+ //#endregion
1182
+ //#region lib/helpers/determineDialectFromSchema.ts
1183
+ function determineDBDialectFromSchema(schema) {
1184
+ const found = /* @__PURE__ */ new Set();
1185
+ for (const table of Object.values(schema)) {
1186
+ if (typeof table !== "object") continue;
1187
+ if (table instanceof PgTable) found.add("postgres");
1188
+ else if (table instanceof MySqlTable) found.add("mysql");
1189
+ else if (table instanceof SQLiteTable) found.add("sqlite");
1190
+ }
1191
+ const dialects = Array.from(found);
1192
+ if (dialects.length === 1) return dialects[0];
1193
+ if (dialects.length === 0) throw new Error("No tables found in schema, could not determine dialect");
1194
+ throw new Error(`Multiple dialects found in schema: ${dialects.join(", ")}`);
1195
+ }
1196
+ function isPostgresDB(db) {
1197
+ return determineDBDialectFromSchema(db._.schema) === "postgres";
1198
+ }
1199
+
1200
+ //#endregion
1201
+ //#region lib/search.ts
1202
+ async function initSearchIfApplicable(db) {
1203
+ if (!isPostgresDB(db)) {
1204
+ console.info("Database dialect is not compatible with search, skipping search initialization.");
1205
+ return;
1206
+ }
1207
+ await db.execute(sql`CREATE EXTENSION IF NOT EXISTS pg_trgm;`);
1208
+ }
1209
+ /**
1210
+ * Performs adjustments to the query args to issue a full text search in case the
1211
+ * respective feature is enabled and a search term was provided.
1212
+ */
1213
+ function adjustQueryArgsForSearch({ search, args, tableSchema, abilities }) {
1214
+ if (search?.enabled && args.search && args.search.length > 0) {
1215
+ const originalOrderBy = cloneDeep(args.orderBy);
1216
+ args.orderBy = (table) => {
1217
+ const argsOrderBySQL = sql.join(Object.entries(originalOrderBy ?? {}).map(([key, value]) => {
1218
+ if (value === "asc") return sql`${table[key]} ASC`;
1219
+ else if (value === "desc") return sql`${table[key]} DESC`;
1220
+ else throw new Error(`Invalid value ${value} for orderBy`);
1221
+ }), sql.raw(", "));
1222
+ const columnsToSearch = abilities.query.many.columns ? Object.entries(tableSchema.columns).filter(([key]) => abilities.query.many.columns[key]) : Object.entries(tableSchema.columns);
1223
+ const searchSQL = sql`GREATEST(${sql.join(columnsToSearch.map(([key]) => {
1224
+ return sql`similarity(${table[key]}::TEXT, ${args.search})`;
1225
+ }), sql.raw(", "))}) DESC`;
1226
+ return originalOrderBy ? sql.join([argsOrderBySQL, searchSQL], sql.raw(", ")) : searchSQL;
1227
+ };
1228
+ args.where = { AND: [cloneDeep(args.where) ?? {}, { RAW: (table) => {
1229
+ return sql`GREATEST(${sql.join(Object.entries(tableSchema.columns).map(([key]) => {
1230
+ return sql`similarity(${table[key]}::TEXT, ${args.search})`;
1231
+ }), sql.raw(", "))}) > ${search.threshold ?? .1}`;
1232
+ } }] };
1233
+ }
1234
+ }
1235
+
1236
+ //#endregion
1237
+ //#region lib/object.ts
1238
+ const isProbablyAConfigObject = (t) => {
1239
+ if (typeof t !== "object") return false;
1240
+ if (Object.keys(t).some((k) => [
1241
+ "args",
1242
+ "nullable",
1243
+ "query",
1244
+ "subscribe",
1245
+ "description",
1246
+ "type",
1247
+ "resolve"
1248
+ ].find((e) => e === k))) return true;
1249
+ return false;
1250
+ };
1251
+ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance, whereArgImplementer, orderArgImplementer, enumImplementer, abilityBuilder }) => {
1252
+ return ({ table, refName, readAction = "read", adjust }) => {
1253
+ const tableSchema = tableHelper({
1254
+ db,
1255
+ table
1256
+ });
1257
+ if (Object.keys(tableSchema.primaryKey).length === 0) console.warn(`Could not find primary key for ${table.toString()}. Cannot register subscriptions!`);
1258
+ const primaryKey = Object.values(tableSchema.primaryKey)[0];
1259
+ const { registerOnInstance } = makePubSubInstance({ table });
1260
+ return schemaBuilder.drizzleObject(table, {
1261
+ name: refName ?? capitalize(table.toString()),
1262
+ subscribe: (subscriptions, element, _context) => {
1263
+ if (!primaryKey) return;
1264
+ const primaryKeyValue = element[primaryKey.name];
1265
+ if (!primaryKeyValue) {
1266
+ console.warn(`Could not find primary key value for ${JSON.stringify(element)}. Cannot register subscription!`);
1267
+ return;
1268
+ }
1269
+ registerOnInstance({
1270
+ instance: subscriptions,
1271
+ action: "updated",
1272
+ primaryKeyValue
1273
+ });
1274
+ },
1275
+ applyFilters: abilityBuilder?._.registeredFilters({
1276
+ table,
1277
+ action: readAction
1278
+ }),
1279
+ fields: (t) => {
1280
+ const columns = tableSchema.columns;
1281
+ const configMap = /* @__PURE__ */ new Map();
1282
+ const userAdjustments = adjust?.(new Proxy(t, { get: (target, prop) => {
1283
+ if (typeof target[prop] !== "function" || prop === "arg" || prop === "builder" || prop === "graphqlKind" || prop === "kind" || prop === "listRef" || prop === "table" || prop === "typename" || prop === "variant" || prop.toString().startsWith("boolean") || prop.toString().startsWith("float") || prop.toString().startsWith("id") || prop.toString().startsWith("int") || prop.toString().startsWith("string") || prop.toString().startsWith("expose")) return target[prop];
1284
+ return (...params) => {
1285
+ const ref = target[prop](...params);
1286
+ const configObject = params.find(isProbablyAConfigObject);
1287
+ if (!configObject) throw new RumbleError("Expected config object to be passed to adjust field");
1288
+ configMap.set(ref, {
1289
+ params,
1290
+ creatorFunction: target[prop],
1291
+ configObject
1292
+ });
1293
+ return ref;
1294
+ };
1295
+ } })) ?? {};
1296
+ const fields = Object.entries(columns).reduce((acc, [key, value]) => {
1297
+ if (userAdjustments[key]) {
1298
+ const { params, creatorFunction, configObject } = configMap.get(userAdjustments[key]);
1299
+ if (typeof configObject.nullable !== "boolean") configObject.nullable = !value.notNull;
1300
+ userAdjustments[key] = creatorFunction.bind(t)(...params);
1301
+ return acc;
1302
+ }
1303
+ if (isEnumSchema(value)) {
1304
+ const enumImpl = enumImplementer({ enumColumn: value });
1305
+ acc[key] = t.field({
1306
+ type: enumImpl,
1307
+ resolve: (element) => element[key],
1308
+ nullable: !value.notNull
1309
+ });
1310
+ } else acc[key] = buildPothosResponseTypeFromGraphQLType({
1311
+ builder: t,
1312
+ sqlType: value.getSQLType(),
1313
+ fieldName: key,
1314
+ nullable: !value.notNull
1315
+ });
1316
+ return acc;
1317
+ }, {});
1318
+ const relations = Object.entries(tableSchema.relations ?? {}).reduce((acc, [key, value]) => {
1319
+ const relationSchema = tableHelper({
1320
+ db,
1321
+ table: value.targetTable
1322
+ });
1323
+ const WhereArg = whereArgImplementer({ dbName: relationSchema.dbName });
1324
+ const OrderArg = orderArgImplementer({ dbName: relationSchema.dbName });
1325
+ const relationTablePubSub = makePubSubInstance({ table: relationSchema.tsName });
1326
+ let nullable = false;
1327
+ let isMany = true;
1328
+ let filterSpecifier = "many";
1329
+ if (value instanceof One) {
1330
+ isMany = false;
1331
+ nullable = value.optional;
1332
+ filterSpecifier = "single";
1333
+ }
1334
+ const subscribe = (subscriptions, _element) => {
1335
+ relationTablePubSub.registerOnInstance({
1336
+ instance: subscriptions,
1337
+ action: "created"
1338
+ });
1339
+ relationTablePubSub.registerOnInstance({
1340
+ instance: subscriptions,
1341
+ action: "removed"
1342
+ });
1343
+ };
1344
+ if (userAdjustments[key]) {
1345
+ const { params, creatorFunction, configObject } = configMap.get(userAdjustments[key]);
1346
+ if (typeof configObject.nullable !== "boolean") configObject.nullable = nullable;
1347
+ if (typeof configObject.subscribe !== "function") configObject.subscribe = subscribe;
1348
+ userAdjustments[key] = creatorFunction.bind(t)(...params);
1349
+ return acc;
1350
+ }
1351
+ const args = {
1352
+ where: t.arg({
1353
+ type: WhereArg,
1354
+ required: false
1355
+ }),
1356
+ orderBy: t.arg({
1357
+ type: OrderArg,
1358
+ required: false
1359
+ }),
1360
+ ...isMany ? {
1361
+ offset: t.arg.int({ required: false }),
1362
+ limit: t.arg.int({ required: false })
1363
+ } : {},
1364
+ search: t.arg.string({ required: false })
1365
+ };
1366
+ if (!search?.enabled || !isMany) delete args.search;
1367
+ acc[key] = t.relation(key, {
1368
+ args,
1369
+ subscribe,
1370
+ nullable,
1371
+ description: `Get the ${pluralize.plural(relationSchema.tsName)} related to this ${pluralize.singular(tableSchema.tsName)}`,
1372
+ query: (args$1, ctx) => {
1373
+ args$1 = JSON.parse(JSON.stringify(args$1));
1374
+ if (isMany) adjustQueryArgsForSearch({
1375
+ search,
1376
+ args: args$1,
1377
+ tableSchema: relationSchema,
1378
+ abilities: ctx.abilities[relationSchema.tsName].filter(readAction)
1379
+ });
1380
+ const filter = ctx.abilities[relationSchema.tsName].filter(readAction).merge({
1381
+ where: args$1.where,
1382
+ limit: args$1.limit
1383
+ }).query[filterSpecifier];
1384
+ if (args$1.offset) filter.offset = args$1.offset;
1385
+ if (args$1.orderBy) filter.orderBy = args$1.orderBy;
1386
+ return filter;
1387
+ }
1388
+ });
1389
+ return acc;
1390
+ }, {});
1391
+ return {
1392
+ ...fields,
1393
+ ...relations,
1394
+ ...userAdjustments
1395
+ };
1396
+ }
1397
+ });
1398
+ };
1399
+ };
1400
+
1401
+ //#endregion
1402
+ //#region lib/pubsub.ts
1403
+ const SUBSCRIPTION_NOTIFIER_RUMBLE_PREFIX = "RUMBLE_SUBSCRIPTION_NOTIFICATION";
1404
+ const SUBSCRIPTION_NOTIFIER_REMOVED = "REMOVED";
1405
+ const SUBSCRIPTION_NOTIFIER_UPDATED = "UPDATED";
1406
+ const SUBSCRIPTION_NOTIFIER_CREATED = "CREATED";
1407
+ const createPubSubInstance = ({ subscriptions }) => {
1408
+ const pubsub = subscriptions ? createPubSub(...subscriptions) : createPubSub();
1409
+ const makePubSubInstance = ({ table }) => {
1410
+ function makePubSubKey({ action, tableName, primaryKeyValue }) {
1411
+ let actionKey;
1412
+ switch (action) {
1413
+ case "created":
1414
+ actionKey = SUBSCRIPTION_NOTIFIER_CREATED;
1415
+ break;
1416
+ case "removed":
1417
+ actionKey = SUBSCRIPTION_NOTIFIER_REMOVED;
1418
+ break;
1419
+ case "updated":
1420
+ actionKey = SUBSCRIPTION_NOTIFIER_UPDATED;
1421
+ break;
1422
+ default: throw new Error(`Unknown action: ${action}`);
1423
+ }
1424
+ return `${SUBSCRIPTION_NOTIFIER_RUMBLE_PREFIX}/${tableName}${primaryKeyValue ? `/${primaryKeyValue}` : ""}/${actionKey}`;
1425
+ }
1426
+ return {
1427
+ registerOnInstance({ instance, action, primaryKeyValue }) {
1428
+ const key = makePubSubKey({
1429
+ tableName: table.toString(),
1430
+ action,
1431
+ primaryKeyValue
1432
+ });
1433
+ instance.register(key);
1434
+ },
1435
+ created() {
1436
+ const key = makePubSubKey({
1437
+ tableName: table.toString(),
1438
+ action: "created"
1439
+ });
1440
+ return pubsub.publish(key);
1441
+ },
1442
+ removed() {
1443
+ const key = makePubSubKey({
1444
+ tableName: table.toString(),
1445
+ action: "removed"
1446
+ });
1447
+ return pubsub.publish(key);
1448
+ },
1449
+ updated(primaryKeyValue) {
1450
+ const keys = (Array.isArray(primaryKeyValue) ? primaryKeyValue : [primaryKeyValue]).map((primaryKeyValue$1) => makePubSubKey({
1451
+ tableName: table.toString(),
1452
+ action: "updated",
1453
+ primaryKeyValue: primaryKeyValue$1
1454
+ }));
1455
+ const uniqueKeys = Array.from(new Set(keys));
1456
+ for (const key of uniqueKeys) pubsub.publish(key);
1457
+ }
1458
+ };
1459
+ };
1460
+ return {
1461
+ pubsub,
1462
+ makePubSubInstance
1463
+ };
1464
+ };
1465
+
1466
+ //#endregion
1467
+ //#region lib/query.ts
1468
+ const createQueryImplementer = ({ db, schemaBuilder, search, whereArgImplementer, orderArgImplementer, makePubSubInstance }) => {
1469
+ return ({ table, readAction = "read", listAction = "read" }) => {
1470
+ const WhereArg = whereArgImplementer({ table });
1471
+ const OrderArg = orderArgImplementer({ table });
1472
+ const tableSchema = tableHelper({
1473
+ db,
1474
+ table
1475
+ });
1476
+ const primaryKeyField = Object.values(tableSchema.primaryKey)[0];
1477
+ const { registerOnInstance } = makePubSubInstance({ table });
1478
+ return schemaBuilder.queryFields((t) => {
1479
+ const manyArgs = {
1480
+ where: t.arg({
1481
+ type: WhereArg,
1482
+ required: false
1483
+ }),
1484
+ orderBy: t.arg({
1485
+ type: OrderArg,
1486
+ required: false
1487
+ }),
1488
+ limit: t.arg.int({ required: false }),
1489
+ offset: t.arg.int({ required: false }),
1490
+ search: t.arg.string({ required: false })
1491
+ };
1492
+ if (!search?.enabled) delete manyArgs.search;
1493
+ return {
1494
+ [pluralize.plural(table.toString())]: t.drizzleField({
1495
+ type: [table],
1496
+ nullable: false,
1497
+ smartSubscription: true,
1498
+ description: `List all ${pluralize.plural(table.toString())}`,
1499
+ subscribe: (subscriptions, _root, _args, _ctx, _info) => {
1500
+ registerOnInstance({
1501
+ instance: subscriptions,
1502
+ action: "created"
1503
+ });
1504
+ registerOnInstance({
1505
+ instance: subscriptions,
1506
+ action: "removed"
1507
+ });
1508
+ },
1509
+ args: manyArgs,
1510
+ resolve: (query, _root, args, ctx, _info) => {
1511
+ Object.setPrototypeOf(args, Object.prototype);
1512
+ adjustQueryArgsForSearch({
1513
+ search,
1514
+ args,
1515
+ tableSchema,
1516
+ abilities: ctx.abilities[table].filter(listAction)
1517
+ });
1518
+ const mappedArgs = mapNullFieldsToUndefined(args);
1519
+ const filter = ctx.abilities[table].filter(listAction).merge(mappedArgs).query.many;
1520
+ if (mappedArgs.offset) filter.offset = mappedArgs.offset;
1521
+ if (mappedArgs.orderBy) filter.orderBy = mappedArgs.orderBy;
1522
+ const queryInstance = query(filter);
1523
+ if (filter.columns) queryInstance.columns = filter.columns;
1524
+ return db.query[table].findMany(queryInstance);
1525
+ }
1526
+ }),
1527
+ [pluralize.singular(table.toString())]: t.drizzleField({
1528
+ type: table,
1529
+ nullable: false,
1530
+ smartSubscription: true,
1531
+ description: `Get a single ${pluralize.singular(table.toString())} by ID`,
1532
+ args: { id: t.arg.id({ required: true }) },
1533
+ resolve: (query, _root, args, ctx, _info) => {
1534
+ Object.setPrototypeOf(args, Object.prototype);
1535
+ const filter = ctx.abilities[table].filter(readAction).merge({ where: { [primaryKeyField.name]: args.id } }).query.single;
1536
+ const q = query(filter);
1537
+ if (filter.columns) q.columns = filter.columns;
1538
+ return db.query[table].findFirst(q).then(assertFindFirstExists);
1539
+ }
1540
+ })
1541
+ };
1542
+ });
1543
+ };
1544
+ };
1545
+
1546
+ //#endregion
1547
+ //#region lib/args/whereArgsImplementer.ts
1548
+ function implementDefaultWhereInputArgs(schemaBuilder) {
1549
+ const IntWhereInputArgument = schemaBuilder.inputRef("IntWhereInputArgument").implement({ fields: (t) => ({
1550
+ eq: t.int(),
1551
+ ne: t.int(),
1552
+ gt: t.int(),
1553
+ gte: t.int(),
1554
+ lt: t.int(),
1555
+ lte: t.int(),
1556
+ in: t.intList(),
1557
+ notIn: t.intList(),
1558
+ like: t.string(),
1559
+ ilike: t.string(),
1560
+ notLike: t.string(),
1561
+ notIlike: t.string(),
1562
+ isNull: t.boolean(),
1563
+ isNotNull: t.boolean(),
1564
+ arrayOverlaps: t.intList(),
1565
+ arrayContained: t.intList(),
1566
+ arrayContains: t.intList(),
1567
+ AND: t.field({ type: [IntWhereInputArgument] }),
1568
+ OR: t.field({ type: [IntWhereInputArgument] }),
1569
+ NOT: t.field({ type: IntWhereInputArgument })
1570
+ }) });
1571
+ const FloatWhereInputArgument = schemaBuilder.inputRef("FloatWhereInputArgument").implement({ fields: (t) => ({
1572
+ eq: t.float(),
1573
+ ne: t.float(),
1574
+ gt: t.float(),
1575
+ gte: t.float(),
1576
+ lt: t.float(),
1577
+ lte: t.float(),
1578
+ in: t.floatList(),
1579
+ notIn: t.floatList(),
1580
+ like: t.string(),
1581
+ ilike: t.string(),
1582
+ notLike: t.string(),
1583
+ notIlike: t.string(),
1584
+ isNull: t.boolean(),
1585
+ isNotNull: t.boolean(),
1586
+ arrayOverlaps: t.floatList(),
1587
+ arrayContained: t.floatList(),
1588
+ arrayContains: t.floatList(),
1589
+ AND: t.field({ type: [FloatWhereInputArgument] }),
1590
+ OR: t.field({ type: [FloatWhereInputArgument] }),
1591
+ NOT: t.field({ type: FloatWhereInputArgument })
1592
+ }) });
1593
+ const StringWhereInputArgument = schemaBuilder.inputRef("StringWhereInputArgument").implement({ fields: (t) => ({
1594
+ eq: t.string(),
1595
+ ne: t.string(),
1596
+ gt: t.string(),
1597
+ gte: t.string(),
1598
+ lt: t.string(),
1599
+ lte: t.string(),
1600
+ in: t.stringList(),
1601
+ notIn: t.stringList(),
1602
+ like: t.string(),
1603
+ ilike: t.string(),
1604
+ notLike: t.string(),
1605
+ notIlike: t.string(),
1606
+ isNull: t.boolean(),
1607
+ isNotNull: t.boolean(),
1608
+ arrayOverlaps: t.stringList(),
1609
+ arrayContained: t.stringList(),
1610
+ arrayContains: t.stringList(),
1611
+ AND: t.field({ type: [StringWhereInputArgument] }),
1612
+ OR: t.field({ type: [StringWhereInputArgument] }),
1613
+ NOT: t.field({ type: StringWhereInputArgument })
1614
+ }) });
1615
+ const DateWhereInputArgument = schemaBuilder.inputRef("DateWhereInputArgument").implement({ fields: (t) => ({
1616
+ eq: t.field({ type: "Date" }),
1617
+ ne: t.field({ type: "Date" }),
1618
+ gt: t.field({ type: "Date" }),
1619
+ gte: t.field({ type: "Date" }),
1620
+ lt: t.field({ type: "Date" }),
1621
+ lte: t.field({ type: "Date" }),
1622
+ in: t.field({ type: ["Date"] }),
1623
+ notIn: t.field({ type: ["Date"] }),
1624
+ like: t.string(),
1625
+ ilike: t.string(),
1626
+ notLike: t.string(),
1627
+ notIlike: t.string(),
1628
+ isNull: t.boolean(),
1629
+ isNotNull: t.boolean(),
1630
+ arrayOverlaps: t.field({ type: ["Date"] }),
1631
+ arrayContained: t.field({ type: ["Date"] }),
1632
+ arrayContains: t.field({ type: ["Date"] }),
1633
+ AND: t.field({ type: [DateWhereInputArgument] }),
1634
+ OR: t.field({ type: [DateWhereInputArgument] }),
1635
+ NOT: t.field({ type: DateWhereInputArgument })
1636
+ }) });
1637
+ }
1638
+
1639
+ //#endregion
1640
+ //#region lib/runtimeFiltersPlugin/filterTypes.ts
1641
+ const pluginName = "RuntimeFiltersPlugin";
1642
+
1643
+ //#endregion
1644
+ //#region lib/helpers/applyFilters.ts
1645
+ /**
1646
+ * A helper to apply a list of filters to a given list of entities.
1647
+ *
1648
+ * @example
1649
+ *
1650
+ * ```ts
1651
+ * const filtered = await applyFilters({
1652
+ filters: abilityBuilder.registeredFilters.posts.update,
1653
+ entities: entitiesToFilter,
1654
+ context: ctx,
1655
+ });
1656
+ * ```
1657
+ */
1658
+ const applyFilters = async ({ filters, entities, context }) => {
1659
+ return Array.from((await Promise.all(filters.map((f) => f({
1660
+ context,
1661
+ entities
1662
+ })))).reduce((acc, val) => {
1663
+ for (const element of val) acc.add(element);
1664
+ return acc;
1665
+ }, /* @__PURE__ */ new Set()));
1666
+ };
1667
+
1668
+ //#endregion
1669
+ //#region lib/runtimeFiltersPlugin/runtimeFiltersPlugin.ts
1670
+ const applyFiltersKey = "applyFilters";
1671
+ var RuntimeFiltersPlugin = class extends BasePlugin {
1672
+ wrapResolve(resolver, fieldConfig) {
1673
+ return async (parent, args, context, info) => {
1674
+ let filters;
1675
+ const fieldType = fieldConfig?.type;
1676
+ if (fieldType.kind === "List") filters = fieldType.type?.ref.currentConfig.pothosOptions[applyFiltersKey];
1677
+ else if (fieldType.kind === "Object") filters = fieldType.ref.currentConfig.pothosOptions[applyFiltersKey];
1678
+ if (!filters || !Array.isArray(filters) || filters.length === 0) return resolver(parent, args, context, info);
1679
+ const resolved = await resolver(parent, args, context, info);
1680
+ const allResolvedValues = Array.isArray(resolved) ? resolved : [resolved];
1681
+ const allowed = await applyFilters({
1682
+ filters: Array.isArray(filters) ? filters : [filters],
1683
+ entities: allResolvedValues,
1684
+ context
1685
+ });
1686
+ if (Array.isArray(resolved)) return allowed;
1687
+ return allowed[0] ?? null;
1688
+ };
1689
+ }
1690
+ };
1691
+ let registered = false;
1692
+ function registerRuntimeFiltersPlugin() {
1693
+ if (!registered) {
1694
+ SchemaBuilder.registerPlugin(pluginName, RuntimeFiltersPlugin);
1695
+ registered = true;
1696
+ }
1697
+ }
1698
+
1699
+ //#endregion
1700
+ //#region lib/schemaBuilder.ts
1701
+ const createSchemaBuilder = ({ db, disableDefaultObjects, pubsub, pothosConfig }) => {
1702
+ registerRuntimeFiltersPlugin();
1703
+ const schemaBuilder = new SchemaBuilder({
1704
+ ...pothosConfig,
1705
+ plugins: [
1706
+ pluginName,
1707
+ DrizzlePlugin,
1708
+ SmartSubscriptionsPlugin,
1709
+ ...pothosConfig?.plugins ?? []
1710
+ ],
1711
+ drizzle: {
1712
+ client: db,
1713
+ relations: db._.relations,
1714
+ getTableConfig(table) {
1715
+ return {
1716
+ columns: Object.values(table[Symbol.for("drizzle:Columns")]),
1717
+ primaryKeys: Object.values(table[Symbol.for("drizzle:Columns")]).filter((v) => v.primary)
1718
+ };
1719
+ }
1720
+ },
1721
+ smartSubscriptions: { ...subscribeOptionsFromIterator((name, _context) => {
1722
+ return pubsub.subscribe(name);
1723
+ }) }
1724
+ });
1725
+ schemaBuilder.addScalarType("JSON", JSONResolver);
1726
+ schemaBuilder.addScalarType("Date", DateResolver);
1727
+ schemaBuilder.addScalarType("DateTime", DateTimeISOResolver);
1728
+ implementDefaultWhereInputArgs(schemaBuilder);
1729
+ if (!disableDefaultObjects?.query) schemaBuilder.queryType({});
1730
+ if (!disableDefaultObjects?.subscription) schemaBuilder.subscriptionType({});
1731
+ if (!disableDefaultObjects?.mutation) schemaBuilder.mutationType({});
1732
+ return { schemaBuilder };
1733
+ };
1734
+
1735
+ //#endregion
1736
+ //#region lib/rumble.ts
1737
+ const rumble = (rumbleInput) => {
1738
+ if (!rumbleInput.db._.schema) throw new RumbleError(`
1739
+ rumble could not find any schema in the provided drizzle instance.
1740
+ Make sure you import the schema and pass it to the drizzle instance:
1741
+
1742
+ export const db = drizzle(
1743
+ "postgres://postgres:postgres@localhost:5432/postgres",
1744
+ {
1745
+ relations,
1746
+ schema, // <--- add this line
1747
+ },
1748
+ );
1749
+
1750
+ `);
1751
+ if (!rumbleInput.db._.relations) throw new RumbleError(`
1752
+ rumble could not find any relations in the provided drizzle instance.
1753
+ Make sure you import the relations and pass them to the drizzle instance:
1754
+
1755
+ export const db = drizzle(
1756
+ "postgres://postgres:postgres@localhost:5432/postgres",
1757
+ {
1758
+ relations, // <--- add this line
1759
+ schema,
1760
+ },
1761
+ );
1762
+
1763
+ `);
1764
+ if (!rumbleInput.actions) rumbleInput.actions = [
1765
+ "read",
1766
+ "update",
1767
+ "delete"
1768
+ ];
1769
+ if (rumbleInput.defaultLimit === void 0) rumbleInput.defaultLimit = 100;
1770
+ if (rumbleInput.search?.enabled) initSearchIfApplicable(rumbleInput.db);
1771
+ const abilityBuilder = createAbilityBuilder(rumbleInput);
1772
+ const context = createContextFunction({
1773
+ ...rumbleInput,
1774
+ abilityBuilder
1775
+ });
1776
+ const { makePubSubInstance, pubsub } = createPubSubInstance({ ...rumbleInput });
1777
+ const { schemaBuilder } = createSchemaBuilder({
1778
+ ...rumbleInput,
1779
+ pubsub
1780
+ });
1781
+ const enum_ = createEnumImplementer({
1782
+ ...rumbleInput,
1783
+ schemaBuilder
1784
+ });
1785
+ const whereArg = createWhereArgImplementer({
1786
+ ...rumbleInput,
1787
+ schemaBuilder,
1788
+ enumImplementer: enum_
1789
+ });
1790
+ const orderArg = createOrderArgImplementer({
1791
+ ...rumbleInput,
1792
+ schemaBuilder
1793
+ });
1794
+ const object = createObjectImplementer({
1795
+ ...rumbleInput,
1796
+ schemaBuilder,
1797
+ makePubSubInstance,
1798
+ whereArgImplementer: whereArg,
1799
+ orderArgImplementer: orderArg,
1800
+ enumImplementer: enum_,
1801
+ abilityBuilder
1802
+ });
1803
+ const query = createQueryImplementer({
1804
+ ...rumbleInput,
1805
+ schemaBuilder,
1806
+ whereArgImplementer: whereArg,
1807
+ orderArgImplementer: orderArg,
1808
+ makePubSubInstance
1809
+ });
1810
+ const builtSchema = lazy(() => schemaBuilder.toSchema());
1811
+ const createYoga$1 = (args) => {
1812
+ const enableApiDocs = args?.enableApiDocs ?? process?.env?.NODE_ENV === "development";
1813
+ return createYoga({
1814
+ ...args,
1815
+ graphiql: enableApiDocs,
1816
+ plugins: [...args?.plugins ?? [], ...enableApiDocs ? [] : [useDisableIntrospection(), EnvelopArmorPlugin()]].filter(Boolean),
1817
+ schema: builtSchema(),
1818
+ context
1819
+ });
1820
+ };
1821
+ const createSofa = (args) => {
1822
+ if (args.openAPI) merge(args.openAPI, sofaOpenAPIWebhookDocs);
1823
+ return useSofa({
1824
+ ...args,
1825
+ schema: builtSchema(),
1826
+ context
1827
+ });
1828
+ };
1829
+ return {
1830
+ abilityBuilder,
1831
+ schemaBuilder,
1832
+ createYoga: createYoga$1,
1833
+ createSofa,
1834
+ object,
1835
+ whereArg,
1836
+ orderArg,
1837
+ query,
1838
+ pubsub: makePubSubInstance,
1839
+ enum_,
1840
+ clientCreator: clientCreatorImplementer({
1841
+ ...rumbleInput,
1842
+ builtSchema
1843
+ })
1844
+ };
1845
+ };
1846
+
1847
+ //#endregion
1848
+ export { RumbleError, RumbleErrorSafe, assertFindFirstExists, assertFirstEntryExists, generateFromSchema, makeLiveQuery, makeMutation, makeQuery, makeSubscription, mapNullFieldsToUndefined, rumble };
1849
+ //# sourceMappingURL=index.js.map