better-convex 0.8.4 → 0.9.0

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.
@@ -529,6 +529,7 @@ const OrmContext = Symbol.for("better-convex:OrmContext");
529
529
  const RlsPolicies = Symbol.for("better-convex:RlsPolicies");
530
530
  const EnableRLS = Symbol.for("better-convex:EnableRLS");
531
531
  const TableDeleteConfig = Symbol.for("better-convex:TableDeleteConfig");
532
+ const TablePolymorphic = Symbol.for("better-convex:TablePolymorphic");
532
533
  const OrmSchemaOptions = Symbol.for("better-convex:OrmSchemaOptions");
533
534
  const OrmSchemaDefinition = Symbol.for("better-convex:OrmSchemaDefinition");
534
535
  const OrmSchemaPluginTables = Symbol.for("better-convex:OrmSchemaPluginTables");
@@ -544,6 +545,8 @@ const RESERVED_COLUMN_NAMES = new Set([
544
545
  "_id",
545
546
  "_creationTime"
546
547
  ]);
548
+ const DEFAULT_POLYMORPHIC_ALIAS = "details";
549
+ const CONVEX_TABLE_FIELD_LIMIT = 1024;
547
550
  /**
548
551
  * Valid table name pattern: starts with letter/underscore, contains only alphanumeric and underscore
549
552
  */
@@ -568,6 +571,21 @@ function createValidatorFromColumns(columns) {
568
571
  const validatorFields = Object.fromEntries(Object.entries(columns).map(([key, builder]) => [key, builder.convexValidator]));
569
572
  return v.object(validatorFields);
570
573
  }
574
+ function discriminator(config) {
575
+ if (!config || typeof config !== "object") throw new Error("discriminator(...) requires a config object.");
576
+ if (!config.variants || typeof config.variants !== "object" || Object.keys(config.variants).length === 0) throw new Error("discriminator(...).variants must contain at least one case.");
577
+ if (config.as !== void 0 && (typeof config.as !== "string" || config.as.length === 0)) throw new Error("discriminator(...).as must be a non-empty string when set.");
578
+ const builder = text().notNull();
579
+ builder.__polymorphic = {
580
+ as: config.as ?? DEFAULT_POLYMORPHIC_ALIAS,
581
+ variants: config.variants
582
+ };
583
+ builder.config.discriminator = {
584
+ as: config.as,
585
+ variants: config.variants
586
+ };
587
+ return builder;
588
+ }
571
589
  var ConvexDeletionBuilder = class {
572
590
  static [entityKind] = "ConvexDeletionBuilder";
573
591
  [entityKind] = "ConvexDeletionBuilder";
@@ -699,6 +717,86 @@ function assertRankOrderFieldType(column, indexName) {
699
717
  ].includes(columnType)) throw new Error(`rankIndex '${indexName}' orderBy() supports integer()/timestamp()/date() columns only. Field '${getColumnName(column)}' is type '${columnType}'.`);
700
718
  }
701
719
  const dedupeFieldNames = (fields) => [...new Set(fields)];
720
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
721
+ const isColumnBuilder = (value) => isRecord(value) && typeof value.build === "function";
722
+ const getDiscriminatorConfig = (value) => {
723
+ if (!isColumnBuilder(value)) return;
724
+ const discriminator = value.config?.discriminator;
725
+ if (!discriminator) return;
726
+ return discriminator;
727
+ };
728
+ const getPolymorphicFieldSignature = (column) => {
729
+ const validator = column.convexValidator ?? column.build();
730
+ return JSON.stringify({
731
+ columnType: column.config?.columnType,
732
+ validator: validator?.json
733
+ });
734
+ };
735
+ function resolveTableColumns(tableName, columns) {
736
+ const resolvedColumns = {};
737
+ const pendingPolymorphic = [];
738
+ for (const [columnName, rawBuilder] of Object.entries(columns)) {
739
+ if (!isColumnBuilder(rawBuilder)) throw new Error(`Column '${columnName}' on '${tableName}' must be a column builder.`);
740
+ resolvedColumns[columnName] = rawBuilder;
741
+ const discriminatorConfig = getDiscriminatorConfig(rawBuilder);
742
+ if (!discriminatorConfig) continue;
743
+ if (!isRecord(discriminatorConfig.variants) || Object.keys(discriminatorConfig.variants).length === 0) throw new Error(`discriminator('${tableName}.${columnName}') requires at least one variant.`);
744
+ const alias = discriminatorConfig.as === void 0 ? DEFAULT_POLYMORPHIC_ALIAS : discriminatorConfig.as;
745
+ if (typeof alias !== "string" || alias.length === 0) throw new Error(`discriminator('${tableName}.${columnName}').as must be a non-empty string.`);
746
+ pendingPolymorphic.push({
747
+ discriminator: columnName,
748
+ alias,
749
+ variants: discriminatorConfig.variants
750
+ });
751
+ }
752
+ if (pendingPolymorphic.length > 1) throw new Error(`Only one discriminator(...) column is currently supported on '${tableName}'.`);
753
+ const polymorphicConfigs = [];
754
+ for (const pending of pendingPolymorphic) {
755
+ if (pending.alias in resolvedColumns) throw new Error(`discriminator('${tableName}.${pending.discriminator}') alias '${pending.alias}' collides with an existing column.`);
756
+ const generatedFieldMap = /* @__PURE__ */ new Map();
757
+ const variantRuntime = {};
758
+ for (const [variantKey, rawVariantColumns] of Object.entries(pending.variants)) {
759
+ if (!isRecord(rawVariantColumns)) throw new Error(`discriminator('${tableName}.${pending.discriminator}') variant '${variantKey}' must be an object.`);
760
+ const fieldNames = [];
761
+ const requiredFieldNames = [];
762
+ for (const [fieldName, rawFieldBuilder] of Object.entries(rawVariantColumns)) {
763
+ if (!isColumnBuilder(rawFieldBuilder)) throw new Error(`discriminator('${tableName}.${pending.discriminator}').variants.${variantKey}.${fieldName} must be a column builder.`);
764
+ if (fieldName in resolvedColumns) throw new Error(`discriminator('${tableName}.${pending.discriminator}').variants.${variantKey}.${fieldName} collides with an existing table column.`);
765
+ const fieldBuilder = rawFieldBuilder;
766
+ const fieldConfig = fieldBuilder.config;
767
+ const isRequiredForVariant = fieldConfig?.notNull === true && fieldConfig.hasDefault !== true && typeof fieldConfig.defaultFn !== "function";
768
+ const signature = getPolymorphicFieldSignature(fieldBuilder);
769
+ const existing = generatedFieldMap.get(fieldName);
770
+ if (existing && existing.signature !== signature) throw new Error(`discriminator('${tableName}.${pending.discriminator}') field '${fieldName}' has conflicting builder signatures across variants.`);
771
+ if (!existing) {
772
+ if (fieldConfig) fieldConfig.notNull = false;
773
+ generatedFieldMap.set(fieldName, {
774
+ builder: fieldBuilder,
775
+ signature
776
+ });
777
+ }
778
+ fieldNames.push(fieldName);
779
+ if (isRequiredForVariant) requiredFieldNames.push(fieldName);
780
+ }
781
+ variantRuntime[variantKey] = {
782
+ fieldNames,
783
+ requiredFieldNames
784
+ };
785
+ }
786
+ for (const [fieldName, { builder }] of generatedFieldMap.entries()) resolvedColumns[fieldName] = builder;
787
+ polymorphicConfigs.push({
788
+ discriminator: pending.discriminator,
789
+ alias: pending.alias,
790
+ generatedFieldNames: Object.freeze([...generatedFieldMap.keys()]),
791
+ variants: Object.freeze(variantRuntime)
792
+ });
793
+ }
794
+ if (Object.keys(resolvedColumns).length > CONVEX_TABLE_FIELD_LIMIT) throw new Error(`Table '${tableName}' exceeds Convex field count limit (${CONVEX_TABLE_FIELD_LIMIT}) after discriminator expansion.`);
795
+ return {
796
+ columns: resolvedColumns,
797
+ polymorphicConfigs
798
+ };
799
+ }
702
800
  function applyExtraConfig(table, config) {
703
801
  if (!config) return;
704
802
  const entries = Array.isArray(config) ? config : Object.values(config);
@@ -920,11 +1018,12 @@ var ConvexTableImpl = class {
920
1018
  [EnableRLS] = false;
921
1019
  [RlsPolicies] = [];
922
1020
  [TableDeleteConfig];
1021
+ [TablePolymorphic];
923
1022
  /**
924
1023
  * Public tableName for convenience
925
1024
  */
926
1025
  tableName;
927
- constructor(name, columns) {
1026
+ constructor(name, columns, polymorphicConfigs) {
928
1027
  validateTableName(name);
929
1028
  for (const columnName of Object.keys(columns)) if (RESERVED_COLUMN_NAMES.has(columnName)) throw new Error(`Column name '${columnName}' is reserved. System fields are managed by Convex ORM.`);
930
1029
  this[TableName] = name;
@@ -936,6 +1035,7 @@ var ConvexTableImpl = class {
936
1035
  }));
937
1036
  this[Columns] = namedColumns;
938
1037
  this.tableName = name;
1038
+ if (polymorphicConfigs && polymorphicConfigs.length > 0) this[TablePolymorphic] = polymorphicConfigs;
939
1039
  this.validator = createValidatorFromColumns(namedColumns);
940
1040
  for (const [columnName, builder] of Object.entries(namedColumns)) {
941
1041
  const config = builder.config;
@@ -958,6 +1058,9 @@ var ConvexTableImpl = class {
958
1058
  });
959
1059
  }
960
1060
  }
1061
+ getPolymorphicConfigs() {
1062
+ return this[TablePolymorphic];
1063
+ }
961
1064
  /**
962
1065
  * Internal: add index to table from builder extraConfig
963
1066
  *
@@ -1163,7 +1266,8 @@ var ConvexTableImpl = class {
1163
1266
  }
1164
1267
  };
1165
1268
  const convexTableInternal = (name, columns, extraConfig) => {
1166
- const rawTable = new ConvexTableImpl(name, columns);
1269
+ const expanded = resolveTableColumns(name, columns);
1270
+ const rawTable = new ConvexTableImpl(name, expanded.columns, expanded.polymorphicConfigs);
1167
1271
  const systemFields = createSystemFields(name);
1168
1272
  for (const builder of Object.values(systemFields)) builder.config.table = rawTable;
1169
1273
  const table = Object.assign(rawTable, systemFields, rawTable[Columns]);
@@ -1192,4 +1296,4 @@ const convexTableWithRLS = (name, columns, extraConfig) => {
1192
1296
  const convexTable = Object.assign(convexTableInternal, { withRLS: convexTableWithRLS });
1193
1297
 
1194
1298
  //#endregion
1195
- export { integer as C, createSystemFields as S, entityKind as T, rankIndex as _, EnableRLS as a, vectorIndex as b, OrmSchemaOptions as c, TableDeleteConfig as d, TableName as f, index as g, aggregateIndex as h, Columns as i, OrmSchemaPluginTables as l, rlsPolicy as m, deletion as n, OrmContext as o, RlsPolicy as p, Brand as r, OrmSchemaDefinition as s, convexTable as t, RlsPolicies as u, searchIndex as v, ConvexColumnBuilder as w, text as x, uniqueIndex as y };
1299
+ export { text as C, entityKind as D, ConvexColumnBuilder as E, vectorIndex as S, integer as T, aggregateIndex as _, Columns as a, searchIndex as b, OrmSchemaDefinition as c, RlsPolicies as d, TableDeleteConfig as f, rlsPolicy as g, RlsPolicy as h, Brand as i, OrmSchemaOptions as l, TablePolymorphic as m, deletion as n, EnableRLS as o, TableName as p, discriminator as r, OrmContext as s, convexTable as t, OrmSchemaPluginTables as u, index as v, createSystemFields as w, uniqueIndex as x, rankIndex as y };
@@ -1,4 +1,4 @@
1
- import { T as entityKind, w as ConvexColumnBuilder } from "./table-B7yzBihE.js";
1
+ import { D as entityKind, E as ConvexColumnBuilder } from "./table-Bxqm450r.js";
2
2
  import { v } from "convex/values";
3
3
 
4
4
  //#region src/orm/builders/text-enum.ts