@m1212e/rumble 0.20.0 → 0.21.3

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.mjs CHANGED
@@ -7,9 +7,9 @@ import { SpanStatusCode, trace } from "@opentelemetry/api";
7
7
  import { AttributeNames, SpanNames, createOpenTelemetryWrapper } from "@pothos/tracing-opentelemetry";
8
8
  import { capitalize, cloneDeep, debounce, merge, toMerged } from "es-toolkit";
9
9
  import { createPubSub, createYoga } from "graphql-yoga";
10
- import { One, count, relationsFilterToSQL, sql } from "drizzle-orm";
10
+ import { One, count, getTableColumns, getTableName, is, isTable, relationsFilterToSQL, sql } from "drizzle-orm";
11
11
  import { toCamelCase } from "drizzle-orm/casing";
12
- import { PgEnumColumn, PgTable } from "drizzle-orm/pg-core";
12
+ import { PgColumn, PgEnumColumn, PgEnumObjectColumn, PgTable, isPgEnum } from "drizzle-orm/pg-core";
13
13
  import pluralize from "pluralize";
14
14
  import { MySqlTable } from "drizzle-orm/mysql-core";
15
15
  import { SQLiteTable } from "drizzle-orm/sqlite-core";
@@ -180,7 +180,7 @@ function mapNullFieldsToUndefined(obj) {
180
180
  }
181
181
  //#endregion
182
182
  //#region package.json
183
- var version = "0.20.0";
183
+ var version = "0.21.3";
184
184
  //#endregion
185
185
  //#region lib/helpers/mergeFilters.ts
186
186
  function mergeFilters(filterA, filterB, mode = "AND") {
@@ -300,31 +300,70 @@ function createDistinctValuesFromSQLType(sqlType) {
300
300
  }
301
301
  //#endregion
302
302
  //#region lib/helpers/tableHelpers.ts
303
- const drizzleNameSymbol = Symbol.for("drizzle:Name");
304
- const drizzleOriginalNameSymbol = Symbol.for("drizzle:OriginalName");
305
- const drizzleBaseNameSymbol = Symbol.for("drizzle:BaseName");
306
- const drizzleColumnsSymbol = Symbol.for("drizzle:Columns");
303
+ const indexCache = /* @__PURE__ */ new WeakMap();
304
+ function buildIndex(db) {
305
+ const byTsName = /* @__PURE__ */ new Map();
306
+ const byDbName = /* @__PURE__ */ new Map();
307
+ const byTableRef = /* @__PURE__ */ new Map();
308
+ for (const [tsName, rawEntry] of Object.entries(db._.relations ?? {})) {
309
+ const entry = rawEntry;
310
+ const tableObj = entry.table;
311
+ if (!tableObj) continue;
312
+ const columns = isTable(tableObj) ? getTableColumns(tableObj) : {};
313
+ const primaryKey = Object.fromEntries(Object.entries(columns).filter(([, col]) => col.primary));
314
+ const dbName = isTable(tableObj) ? getTableName(tableObj) : entry.name ?? tsName;
315
+ const resolved = {
316
+ name: entry.name ?? tsName,
317
+ dbName,
318
+ table: tableObj,
319
+ columns,
320
+ primaryKey,
321
+ relations: entry.relations ?? {},
322
+ foundRelation: entry
323
+ };
324
+ byTsName.set(resolved.name, resolved);
325
+ byDbName.set(resolved.dbName, resolved);
326
+ byTableRef.set(tableObj, resolved);
327
+ }
328
+ return {
329
+ byTsName,
330
+ byDbName,
331
+ byTableRef
332
+ };
333
+ }
334
+ function getIndex(db) {
335
+ let idx = indexCache.get(db);
336
+ if (!idx) {
337
+ idx = buildIndex(db);
338
+ indexCache.set(db, idx);
339
+ }
340
+ return idx;
341
+ }
307
342
  function tableHelper({ db, table }) {
308
- let tableName;
309
- if (typeof table !== "string") {
343
+ const idx = getIndex(db);
344
+ let entry;
345
+ if (typeof table === "string") entry = idx.byTsName.get(table) ?? idx.byDbName.get(table);
346
+ else if (table && typeof table === "object") {
310
347
  const t = table;
311
- tableName = t.name ?? t[drizzleNameSymbol] ?? t[drizzleOriginalNameSymbol] ?? t[drizzleBaseNameSymbol];
312
- } else tableName = table;
313
- const foundRelation = Object.values(db._.relations).find((entry) => entry.name === tableName || entry.table[drizzleNameSymbol] === tableName || entry.table[drizzleOriginalNameSymbol] === tableName || entry.table[drizzleBaseNameSymbol] === tableName);
314
- if (!foundRelation) throw new RumbleError(`Could not find schema for ${JSON.stringify(tableName)}`);
315
- const tableObj = foundRelation.table;
316
- const columns = tableObj[drizzleColumnsSymbol] ?? {};
317
- const primaryKey = Object.fromEntries(Object.entries(columns).filter(([_, col]) => col.primary));
318
- const dbName = tableObj[drizzleOriginalNameSymbol] ?? tableObj[drizzleBaseNameSymbol] ?? tableObj[drizzleNameSymbol] ?? foundRelation.name;
319
- const tsName = foundRelation.name;
348
+ if (t.table) {
349
+ entry = idx.byTableRef.get(t.table);
350
+ if (!entry && t.name) entry = idx.byTsName.get(t.name) ?? idx.byDbName.get(t.name);
351
+ }
352
+ if (!entry) entry = idx.byTableRef.get(t);
353
+ if (!entry && isTable(t)) {
354
+ const dbName = getTableName(t);
355
+ entry = idx.byDbName.get(dbName);
356
+ }
357
+ }
358
+ if (!entry) throw new RumbleError(`Could not find schema for ${JSON.stringify(typeof table === "string" ? table : "<table-object>")}`);
320
359
  return {
321
- columns,
322
- primaryKey,
323
- relations: foundRelation.relations,
324
- dbName,
325
- tsName,
326
- table: tableObj,
327
- foundRelation
360
+ columns: entry.columns,
361
+ primaryKey: entry.primaryKey,
362
+ relations: entry.relations,
363
+ dbName: entry.dbName,
364
+ tsName: entry.name,
365
+ table: entry.table,
366
+ foundRelation: entry.foundRelation
328
367
  };
329
368
  }
330
369
  //#endregion
@@ -678,9 +717,10 @@ const createOrderArgImplementer = ({ db, schemaBuilder }) => {
678
717
  return acc;
679
718
  }, {});
680
719
  const relations = Object.entries(tableSchema.relations ?? {}).reduce((acc, [key, value]) => {
720
+ const targetTsName = value.targetTableName;
681
721
  const referenceModel = orderArgImplementer({ dbName: tableHelper({
682
722
  db,
683
- table: value.targetTable
723
+ table: targetTsName
684
724
  }).dbName });
685
725
  acc[key] = t.field({
686
726
  type: referenceModel,
@@ -703,21 +743,36 @@ const createOrderArgImplementer = ({ db, schemaBuilder }) => {
703
743
  //#endregion
704
744
  //#region lib/enum.ts
705
745
  /**
706
- * Checks if a column is a PgEnumColumn
746
+ * Checks if a column is a Postgres enum column
707
747
  */
708
748
  function isEnumSchema(schemaType) {
709
- return schemaType instanceof PgEnumColumn;
749
+ return is(schemaType, PgEnumColumn) || is(schemaType, PgEnumObjectColumn);
710
750
  }
711
- const createEnumImplementer = ({ schemaBuilder }) => {
751
+ const createEnumImplementer = ({ schema, schemaBuilder }) => {
712
752
  const referenceStorage = /* @__PURE__ */ new Map();
713
753
  /**
714
754
  * Registers a Postgres enum as a GraphQL enum type.
715
755
  *
716
- * Pass the `enum` option with the pgEnum object (e.g. `moodEnum`),
717
- * or pass the `enumColumn` option with a column that uses the enum.
756
+ * You can identify the enum in three ways:
757
+ * - `tsName`: the exported TypeScript identifier from your schema module. This is the
758
+ * most typesafe form, only valid enum keys autocomplete, typos are caught at compile time.
759
+ * - `enum`: pass the pgEnum object directly.
760
+ * - `enumColumn`: pass a column that uses the enum.
761
+ *
718
762
  * Use `refName` to override the auto-generated GraphQL type name.
719
763
  */
720
- const enumImplementer = ({ enum: enumObject, enumColumn, refName }) => {
764
+ const enumImplementer = (args) => {
765
+ const { refName } = args;
766
+ const tsName = args.tsName;
767
+ const enumObjectArg = args.enum;
768
+ const enumColumn = args.enumColumn;
769
+ let enumObject = enumObjectArg;
770
+ if (tsName) {
771
+ const candidate = schema[tsName];
772
+ if (!candidate || !isPgEnum(candidate)) throw new RumbleError(`Could not find a pgEnum exported as ${JSON.stringify(tsName)} in the provided schema. Make sure the identifier exists and refers to a value created with \`pgEnum(...)\`.`);
773
+ enumObject = candidate;
774
+ }
775
+ if (!enumObject && !enumColumn) throw new RumbleError("Could not determine enum structure! Pass one of 'tsName' (requires `schema` on rumble()), 'enum' (the pgEnum object), or 'enumColumn' (a column that uses the enum).");
721
776
  let enumSchemaName;
722
777
  let enumValues;
723
778
  if (enumObject) {
@@ -731,10 +786,9 @@ const createEnumImplementer = ({ schemaBuilder }) => {
731
786
  }
732
787
  if (!enumSchemaName || !enumValues) throw new RumbleError("Could not determine enum structure!");
733
788
  const graphqlImplementationName = refName ?? `${capitalize(toCamelCase(enumSchemaName))}Enum`;
734
- let ret = referenceStorage.get(graphqlImplementationName);
735
- if (ret) return ret;
736
- const implement = () => schemaBuilder.enumType(graphqlImplementationName, { values: enumValues });
737
- ret = implement();
789
+ const cached = referenceStorage.get(graphqlImplementationName);
790
+ if (cached) return cached;
791
+ const ret = schemaBuilder.enumType(graphqlImplementationName, { values: enumValues });
738
792
  referenceStorage.set(graphqlImplementationName, ret);
739
793
  return ret;
740
794
  };
@@ -802,9 +856,10 @@ const createWhereArgImplementer = ({ db, schemaBuilder, enumImplementer }) => {
802
856
  return acc;
803
857
  }, {});
804
858
  const relations = Object.entries(tableSchema.relations ?? {}).reduce((acc, [key, value]) => {
859
+ const targetTsName = value.targetTableName;
805
860
  const referenceModel = whereArgImplementer({ dbName: tableHelper({
806
861
  db,
807
- table: value.targetTable
862
+ table: targetTsName
808
863
  }).dbName });
809
864
  acc[key] = t.field({
810
865
  type: referenceModel,
@@ -899,7 +954,7 @@ const createCountQueryImplementer = ({ db, schemaBuilder, whereArgImplementer, m
899
954
  type: WhereArg,
900
955
  required: false
901
956
  }) },
902
- resolve: async (root, args, ctx, info) => {
957
+ resolve: async (_root, args, ctx, _info) => {
903
958
  if (isAllowed && !await isAllowed(ctx)) throw new RumbleErrorSafe("Not allowed to perform this action");
904
959
  deepSetProto(args);
905
960
  return db.select({ count: count() }).from(tableSchema.table).where(ctx.abilities[table].filter(listAction).merge(mapNullFieldsToUndefined(args)).sql.where).then(assertFirstEntryExists).then((r) => r.count);
@@ -1026,16 +1081,23 @@ function buildPothosResponseTypeFromGraphQLType({ builder, sqlType, fieldName, n
1026
1081
  }
1027
1082
  //#endregion
1028
1083
  //#region lib/helpers/determineDialectFromSchema.ts
1084
+ const dialectCache = /* @__PURE__ */ new WeakMap();
1029
1085
  function determineDBDialectFromSchema(schema) {
1086
+ const cached = dialectCache.get(schema);
1087
+ if (cached) return cached;
1030
1088
  const found = /* @__PURE__ */ new Set();
1031
1089
  for (const table of Object.values(schema).map((t) => t.table)) {
1032
- if (typeof table !== "object") continue;
1033
- if (table instanceof PgTable) found.add("postgres");
1034
- else if (table instanceof MySqlTable) found.add("mysql");
1035
- else if (table instanceof SQLiteTable) found.add("sqlite");
1090
+ if (!table || typeof table !== "object") continue;
1091
+ if (is(table, PgTable)) found.add("postgres");
1092
+ else if (is(table, MySqlTable)) found.add("mysql");
1093
+ else if (is(table, SQLiteTable)) found.add("sqlite");
1036
1094
  }
1037
1095
  const dialects = Array.from(found);
1038
- if (dialects.length === 1) return dialects[0];
1096
+ if (dialects.length === 1) {
1097
+ const only = dialects[0];
1098
+ dialectCache.set(schema, only);
1099
+ return only;
1100
+ }
1039
1101
  if (dialects.length === 0) throw new Error("No tables found in schema, could not determine dialect");
1040
1102
  throw new Error(`Multiple dialects found in schema: ${dialects.join(", ")}`);
1041
1103
  }
@@ -1067,7 +1129,7 @@ async function initSearchIfApplicable(input) {
1067
1129
  */
1068
1130
  function adjustQueryArgsForSearch({ search, args, tableSchema, abilities }) {
1069
1131
  if (search?.enabled && args.search && args.search.length > 0) {
1070
- const columnsToSearch = (abilities.query.many.columns ? Object.entries(tableSchema.columns).filter(([key]) => abilities.query.many.columns[key]) : Object.entries(tableSchema.columns)).filter(([key, col]) => isStringLikeSQLTypeString(col.getSQLType()) || isIDLikeSQLTypeString(col.getSQLType()));
1132
+ const columnsToSearch = (abilities.query.many.columns ? Object.entries(tableSchema.columns).filter(([key]) => abilities.query.many.columns[key]) : Object.entries(tableSchema.columns)).filter(([_key, col]) => isStringLikeSQLTypeString(col.getSQLType()) || isIDLikeSQLTypeString(col.getSQLType()));
1071
1133
  const searchParam = sql`${args.search}`;
1072
1134
  args.extras = { search_distance: (table) => sql`${sql.join(columnsToSearch.map(([key]) => {
1073
1135
  return sql`COALESCE((${table[key]}::TEXT <-> ${searchParam}), 1)`;
@@ -1133,7 +1195,7 @@ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance
1133
1195
  fields: (t) => {
1134
1196
  const columns = tableSchema.columns;
1135
1197
  const configMap = /* @__PURE__ */ new Map();
1136
- const userAdjustments = adjust?.(new Proxy(t, { get: (target, prop, receiver) => {
1198
+ const userAdjustments = adjust?.(new Proxy(t, { get: (target, prop, _receiver) => {
1137
1199
  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];
1138
1200
  return (...params) => {
1139
1201
  const ref = target[prop](...params);
@@ -1155,9 +1217,12 @@ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance
1155
1217
  return acc;
1156
1218
  }
1157
1219
  let isArray = false;
1158
- if (column.dimensions > 0) {
1159
- if (column.dimensions !== 1) throw new RumbleError("Only one-dimensional arrays are supported for default object implementation");
1160
- isArray = true;
1220
+ if (is(column, PgColumn)) {
1221
+ const dimensions = column.dimensions;
1222
+ if (dimensions && dimensions > 0) {
1223
+ if (dimensions !== 1) throw new RumbleError("Only one-dimensional arrays are supported for default object implementation");
1224
+ isArray = true;
1225
+ }
1161
1226
  }
1162
1227
  if (isEnumSchema(column)) {
1163
1228
  const enumImpl = enumImplementer({ enumColumn: column });
@@ -1176,9 +1241,10 @@ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance
1176
1241
  return acc;
1177
1242
  }, {});
1178
1243
  const relations = Object.entries(tableSchema.relations ?? {}).reduce((acc, [key, value]) => {
1244
+ const targetTsName = value.targetTableName;
1179
1245
  const relationSchema = tableHelper({
1180
1246
  db,
1181
- table: value.targetTable
1247
+ table: targetTsName
1182
1248
  });
1183
1249
  const WhereArg = whereArgImplementer({ dbName: relationSchema.dbName });
1184
1250
  const OrderArg = orderArgImplementer({ dbName: relationSchema.dbName });
@@ -1186,7 +1252,7 @@ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance
1186
1252
  let nullable = false;
1187
1253
  let isMany = true;
1188
1254
  let filterSpecifier = "many";
1189
- if (value instanceof One) {
1255
+ if (is(value, One)) {
1190
1256
  isMany = false;
1191
1257
  nullable = value.optional || !value.sourceColumns.every((column) => column.notNull);
1192
1258
  filterSpecifier = "single";
@@ -1254,7 +1320,7 @@ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance
1254
1320
  fields.search_distance = t.float({
1255
1321
  description: "The search distance of the object. If a search is provided, this field will be populated with the search distance.",
1256
1322
  nullable: true,
1257
- resolve: (parent, args, ctx, info) => parent.search_distance
1323
+ resolve: (parent, _args, _ctx, _info) => parent.search_distance
1258
1324
  });
1259
1325
  }
1260
1326
  return {
@@ -1619,9 +1685,10 @@ const createSchemaBuilder = ({ db, disableDefaultObjects, pubsub, pothosConfig,
1619
1685
  client: db,
1620
1686
  relations: db._.relations,
1621
1687
  getTableConfig(table) {
1688
+ const columns = isTable(table) ? Object.values(getTableColumns(table)) : [];
1622
1689
  return {
1623
- columns: Object.values(table[Symbol.for("drizzle:Columns")]),
1624
- primaryKeys: Object.values(table[Symbol.for("drizzle:Columns")]).filter((v) => v.primary)
1690
+ columns,
1691
+ primaryKeys: columns.filter((v) => v.primary)
1625
1692
  };
1626
1693
  }
1627
1694
  },
@@ -1658,6 +1725,18 @@ export const db = drizzle(
1658
1725
  },
1659
1726
  );
1660
1727
 
1728
+ `);
1729
+ if (!rumbleInput.schema || typeof rumbleInput.schema !== "object" || Object.keys(rumbleInput.schema).length === 0) throw new RumbleError(`
1730
+ rumble requires the drizzle schema object to be passed when initializing.
1731
+ Import your schema module and pass it alongside \`db\`:
1732
+
1733
+ import * as schema from "./db/schema";
1734
+
1735
+ export const r = rumble({
1736
+ db,
1737
+ schema, // <--- add this line
1738
+ });
1739
+
1661
1740
  `);
1662
1741
  if (!rumbleInput.actions) rumbleInput.actions = [
1663
1742
  "read",