@m1212e/rumble 0.20.0 → 0.21.2

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.2";
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) {
@@ -802,9 +857,10 @@ const createWhereArgImplementer = ({ db, schemaBuilder, enumImplementer }) => {
802
857
  return acc;
803
858
  }, {});
804
859
  const relations = Object.entries(tableSchema.relations ?? {}).reduce((acc, [key, value]) => {
860
+ const targetTsName = value.targetTableName;
805
861
  const referenceModel = whereArgImplementer({ dbName: tableHelper({
806
862
  db,
807
- table: value.targetTable
863
+ table: targetTsName
808
864
  }).dbName });
809
865
  acc[key] = t.field({
810
866
  type: referenceModel,
@@ -899,7 +955,7 @@ const createCountQueryImplementer = ({ db, schemaBuilder, whereArgImplementer, m
899
955
  type: WhereArg,
900
956
  required: false
901
957
  }) },
902
- resolve: async (root, args, ctx, info) => {
958
+ resolve: async (_root, args, ctx, _info) => {
903
959
  if (isAllowed && !await isAllowed(ctx)) throw new RumbleErrorSafe("Not allowed to perform this action");
904
960
  deepSetProto(args);
905
961
  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 +1082,23 @@ function buildPothosResponseTypeFromGraphQLType({ builder, sqlType, fieldName, n
1026
1082
  }
1027
1083
  //#endregion
1028
1084
  //#region lib/helpers/determineDialectFromSchema.ts
1085
+ const dialectCache = /* @__PURE__ */ new WeakMap();
1029
1086
  function determineDBDialectFromSchema(schema) {
1087
+ const cached = dialectCache.get(schema);
1088
+ if (cached) return cached;
1030
1089
  const found = /* @__PURE__ */ new Set();
1031
1090
  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");
1091
+ if (!table || typeof table !== "object") continue;
1092
+ if (is(table, PgTable)) found.add("postgres");
1093
+ else if (is(table, MySqlTable)) found.add("mysql");
1094
+ else if (is(table, SQLiteTable)) found.add("sqlite");
1036
1095
  }
1037
1096
  const dialects = Array.from(found);
1038
- if (dialects.length === 1) return dialects[0];
1097
+ if (dialects.length === 1) {
1098
+ const only = dialects[0];
1099
+ dialectCache.set(schema, only);
1100
+ return only;
1101
+ }
1039
1102
  if (dialects.length === 0) throw new Error("No tables found in schema, could not determine dialect");
1040
1103
  throw new Error(`Multiple dialects found in schema: ${dialects.join(", ")}`);
1041
1104
  }
@@ -1067,7 +1130,7 @@ async function initSearchIfApplicable(input) {
1067
1130
  */
1068
1131
  function adjustQueryArgsForSearch({ search, args, tableSchema, abilities }) {
1069
1132
  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()));
1133
+ 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
1134
  const searchParam = sql`${args.search}`;
1072
1135
  args.extras = { search_distance: (table) => sql`${sql.join(columnsToSearch.map(([key]) => {
1073
1136
  return sql`COALESCE((${table[key]}::TEXT <-> ${searchParam}), 1)`;
@@ -1133,7 +1196,7 @@ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance
1133
1196
  fields: (t) => {
1134
1197
  const columns = tableSchema.columns;
1135
1198
  const configMap = /* @__PURE__ */ new Map();
1136
- const userAdjustments = adjust?.(new Proxy(t, { get: (target, prop, receiver) => {
1199
+ const userAdjustments = adjust?.(new Proxy(t, { get: (target, prop, _receiver) => {
1137
1200
  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
1201
  return (...params) => {
1139
1202
  const ref = target[prop](...params);
@@ -1155,9 +1218,12 @@ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance
1155
1218
  return acc;
1156
1219
  }
1157
1220
  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;
1221
+ if (is(column, PgColumn)) {
1222
+ const dimensions = column.dimensions;
1223
+ if (dimensions && dimensions > 0) {
1224
+ if (dimensions !== 1) throw new RumbleError("Only one-dimensional arrays are supported for default object implementation");
1225
+ isArray = true;
1226
+ }
1161
1227
  }
1162
1228
  if (isEnumSchema(column)) {
1163
1229
  const enumImpl = enumImplementer({ enumColumn: column });
@@ -1176,9 +1242,10 @@ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance
1176
1242
  return acc;
1177
1243
  }, {});
1178
1244
  const relations = Object.entries(tableSchema.relations ?? {}).reduce((acc, [key, value]) => {
1245
+ const targetTsName = value.targetTableName;
1179
1246
  const relationSchema = tableHelper({
1180
1247
  db,
1181
- table: value.targetTable
1248
+ table: targetTsName
1182
1249
  });
1183
1250
  const WhereArg = whereArgImplementer({ dbName: relationSchema.dbName });
1184
1251
  const OrderArg = orderArgImplementer({ dbName: relationSchema.dbName });
@@ -1186,7 +1253,7 @@ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance
1186
1253
  let nullable = false;
1187
1254
  let isMany = true;
1188
1255
  let filterSpecifier = "many";
1189
- if (value instanceof One) {
1256
+ if (is(value, One)) {
1190
1257
  isMany = false;
1191
1258
  nullable = value.optional || !value.sourceColumns.every((column) => column.notNull);
1192
1259
  filterSpecifier = "single";
@@ -1254,7 +1321,7 @@ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance
1254
1321
  fields.search_distance = t.float({
1255
1322
  description: "The search distance of the object. If a search is provided, this field will be populated with the search distance.",
1256
1323
  nullable: true,
1257
- resolve: (parent, args, ctx, info) => parent.search_distance
1324
+ resolve: (parent, _args, _ctx, _info) => parent.search_distance
1258
1325
  });
1259
1326
  }
1260
1327
  return {
@@ -1619,9 +1686,10 @@ const createSchemaBuilder = ({ db, disableDefaultObjects, pubsub, pothosConfig,
1619
1686
  client: db,
1620
1687
  relations: db._.relations,
1621
1688
  getTableConfig(table) {
1689
+ const columns = isTable(table) ? Object.values(getTableColumns(table)) : [];
1622
1690
  return {
1623
- columns: Object.values(table[Symbol.for("drizzle:Columns")]),
1624
- primaryKeys: Object.values(table[Symbol.for("drizzle:Columns")]).filter((v) => v.primary)
1691
+ columns,
1692
+ primaryKeys: columns.filter((v) => v.primary)
1625
1693
  };
1626
1694
  }
1627
1695
  },
@@ -1658,6 +1726,18 @@ export const db = drizzle(
1658
1726
  },
1659
1727
  );
1660
1728
 
1729
+ `);
1730
+ if (!rumbleInput.schema || typeof rumbleInput.schema !== "object" || Object.keys(rumbleInput.schema).length === 0) throw new RumbleError(`
1731
+ rumble requires the drizzle schema object to be passed when initializing.
1732
+ Import your schema module and pass it alongside \`db\`:
1733
+
1734
+ import * as schema from "./db/schema";
1735
+
1736
+ export const r = rumble({
1737
+ db,
1738
+ schema, // <--- add this line
1739
+ });
1740
+
1661
1741
  `);
1662
1742
  if (!rumbleInput.actions) rumbleInput.actions = [
1663
1743
  "read",