@rebasepro/inference 0.12.1-canary.gf5f1d39 → 0.13.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.
package/dist/index.es.js CHANGED
@@ -219,12 +219,15 @@ function buildPropertiesOrder(properties, propertiesOrder, priorityKeys) {
219
219
  if (k.includes("image") || k.includes("picture")) return 1;
220
220
  return 0;
221
221
  }
222
- const keys = propertiesOrder ?? Object.keys(properties);
223
- keys.sort();
224
- keys.sort((a, b) => {
225
- return propOrder(b) - propOrder(a);
226
- });
227
- return keys;
222
+ function deriveOrder(keys) {
223
+ return [...keys].sort().sort((a, b) => propOrder(b) - propOrder(a));
224
+ }
225
+ if (propertiesOrder) {
226
+ const placed = new Set(propertiesOrder);
227
+ const rest = Object.keys(properties).filter((key) => !placed.has(key));
228
+ return [...propertiesOrder, ...deriveOrder(rest)];
229
+ }
230
+ return deriveOrder(Object.keys(properties));
228
231
  }
229
232
  /**
230
233
  * @param type
@@ -375,7 +378,8 @@ function buildPropertyFromCount(key, totalDocsCount, mostProbableType, typesCoun
375
378
  function buildPropertiesFromCount(totalDocsCount, typesCountRecord, valuesCountRecord) {
376
379
  const res = {};
377
380
  Object.entries(typesCountRecord).forEach(([key, typesCount]) => {
378
- res[key] = buildPropertyFromCount(key, totalDocsCount, getMostProbableType(typesCount), typesCount, valuesCountRecord ? valuesCountRecord[key] : void 0);
381
+ const mostProbableType = getMostProbableType(typesCount);
382
+ res[key] = buildPropertyFromCount(key, totalDocsCount, mostProbableType, typesCount, valuesCountRecord ? valuesCountRecord[key] : void 0);
379
383
  });
380
384
  return res;
381
385
  }
@@ -390,6 +394,7 @@ function countMaxDocumentsUnder(typesCount) {
390
394
  function getMostProbableTypeInArray(array, getType) {
391
395
  const typesCount = {};
392
396
  array.forEach((value) => {
397
+ if (value === null || value === void 0) return;
393
398
  increaseTypeCount(getType(value), typesCount, value, getType);
394
399
  });
395
400
  return getMostProbableType(typesCount);
@@ -1 +1 @@
1
- {"version":3,"file":"index.es.js","names":[],"sources":["../src/strings.ts","../src/util.ts","../src/builders/string_property_builder.ts","../src/builders/validation_builder.ts","../src/builders/reference_property_builder.ts","../src/collection_builder.ts"],"sourcesContent":["import { ValuesCountEntry } from \"./types\";\n\n/**\n * Parse a reference string value which can be in the format:\n * - Simple: \"path/entityId\"\n * - With database: \"database_name:::path/entityId\"\n * Returns the path and database (undefined if not specified or if \"(default)\")\n */\nexport function parseReferenceString(value: string): { path: string; database?: string } | null {\n if (!value) return null;\n\n let database: string | undefined = undefined;\n let fullPath = value;\n\n // Parse the new format: database_name:::path/entityId\n if (value.includes(\":::\")) {\n const [dbName, pathPart] = value.split(\":::\");\n if (dbName && dbName !== \"(default)\") {\n database = dbName;\n }\n fullPath = pathPart;\n }\n\n // Check if it looks like a path (contains at least one slash)\n if (!fullPath || !fullPath.includes(\"/\")) {\n return null;\n }\n\n // Extract the collection path (everything before the last slash)\n const path = fullPath.substring(0, fullPath.lastIndexOf(\"/\"));\n\n return { path,\ndatabase };\n}\n\n/**\n * Check if a string value looks like a reference\n */\nexport function looksLikeReference(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n return parseReferenceString(value) !== null;\n}\n\nexport function findCommonInitialStringInPath(valuesCount?: ValuesCountEntry) {\n\n if (!valuesCount) return undefined;\n\n function getPath(value: unknown): string | undefined {\n let pathString: string | undefined;\n\n if (typeof value === \"string\") {\n pathString = value;\n } else if (value && typeof value === \"object\" && \"slug\" in value && typeof (value as Record<string, unknown>).slug === \"string\") {\n pathString = (value as Record<string, unknown>).slug as string;\n } else {\n console.warn(\"findCommonInitialStringInPath: value is not a string or document with path\", value);\n return undefined;\n }\n\n if (!pathString) return undefined;\n\n // Parse the new format: database_name:::path/entityId\n // Extract just the path portion for comparison\n if (pathString.includes(\":::\")) {\n const [, pathPart] = pathString.split(\":::\");\n pathString = pathPart;\n }\n\n return pathString;\n }\n\n const strings: string[] = valuesCount.values.map((v) => getPath(v)).filter(v => !!v) as string[];\n const pathWithSlash = strings.find((s) => s.includes(\"/\"));\n if (!pathWithSlash)\n return undefined;\n\n const searchedPath = pathWithSlash.substring(0, pathWithSlash.lastIndexOf(\"/\"));\n\n const yep = valuesCount.values\n .filter((value) => {\n const path = getPath(value);\n if (!path) return false;\n return path.startsWith(searchedPath)\n }).length > valuesCount.values.length / 3 * 2;\n\n return yep ? searchedPath : undefined;\n\n}\n\nexport function removeInitialAndTrailingSlashes(s: string): string {\n return removeInitialSlash(removeTrailingSlash(s));\n}\n\nexport function removeInitialSlash(s: string) {\n if (s.startsWith(\"/\"))\n return s.slice(1);\n else return s;\n}\n\nexport function removeTrailingSlash(s: string) {\n if (s.endsWith(\"/\"))\n return s.slice(0, -1);\n else return s;\n}\n","import { EnumValueConfig, EnumValues } from \"@rebasepro/types\";\nimport { unslugify } from \"@rebasepro/utils\";\n\n// Canonical utility functions — single source of truth in @rebasepro/utils\nexport { unslugify, prettifyIdentifier, isObject, isPlainObject, mergeDeep } from \"@rebasepro/utils\";\n\n/**\n * Extract enum values from a list of sample values.\n * This is inference-specific logic (not a general utility).\n */\nexport function extractEnumFromValues(values: unknown[]) {\n if (!Array.isArray(values)) {\n return [];\n }\n const enumValues = values\n .map((value) => {\n if (typeof value === \"string\") {\n return ({\n id: value,\n label: unslugify(value)\n });\n } else\n return null;\n }).filter(Boolean) as Array<{ id: string, label: string }>;\n enumValues.sort((a, b) => a.label.localeCompare(b.label));\n return enumValues;\n}\n\nexport function resolveEnumValues(input: EnumValues): EnumValueConfig[] | undefined {\n // Check Array.isArray first since typeof [] === \"object\" is true in JavaScript\n if (Array.isArray(input)) {\n return input as EnumValueConfig[];\n } else if (typeof input === \"object\" && input !== null) {\n return Object.entries(input).map(([id, value]) =>\n (typeof value === \"string\"\n ? {\n id,\n label: value\n }\n : value));\n } else {\n return undefined;\n }\n}\n","import { findCommonInitialStringInPath } from \"../strings\";\nimport { extractEnumFromValues } from \"../util\";\nimport { FileType, Property, StringProperty } from \"@rebasepro/types\";\nimport { InferencePropertyBuilderProps, ValuesCountEntry } from \"../types\";\n\nconst IMAGE_EXTENSIONS = [\".jpg\", \".jpeg\", \".png\", \".webp\", \".gif\", \".avif\"];\nconst AUDIO_EXTENSIONS = [\".mp3\", \".ogg\", \".opus\", \".aac\"];\nconst VIDEO_EXTENSIONS = [\".avi\", \".mp4\"];\n\nconst emailRegEx = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;\n\nexport function buildStringProperty({\n name,\n totalDocsCount,\n valuesResult\n}: InferencePropertyBuilderProps): Property {\n\n let stringProperty: Property = {\n name: name ?? \"\",\n type: \"string\"\n\n };\n\n if (valuesResult) {\n\n const totalEntriesCount = valuesResult.values.length;\n const totalValues = Array.from(valuesResult.valuesCount.keys()).length;\n\n const config: Partial<StringProperty> = {};\n\n const probablyAURL = valuesResult.values\n .filter((value) => typeof value === \"string\" &&\n value.toString().startsWith(\"http\")).length > totalDocsCount / 3 * 2;\n if (probablyAURL) {\n config.url = true;\n }\n\n const probablyAnEmail = valuesResult.values\n .filter((value) => typeof value === \"string\" &&\n emailRegEx.test(value)).length > totalDocsCount / 3 * 2;\n if (probablyAnEmail) {\n config.email = true;\n }\n\n const probablyUserIds = valuesResult.values\n .filter((value) => typeof value === \"string\" && value.length === 28 && !value.includes(\" \"))\n .length > totalDocsCount / 3 * 2;\n // No `admin.readOnly` here. This package is core — the field does not exist in\n // `@rebasepro/types`, because `@rebasepro/admin-types` declares it — and a\n // guess about whether a form should be editable is presentation, not schema.\n // (It also would not have enforced anything: nothing on the server reads\n // `readOnly`.) The inferred `isId` below is the part that carries meaning.\n\n if (!probablyAnEmail &&\n !probablyAURL &&\n !probablyUserIds &&\n !probablyAURL &&\n totalValues < totalEntriesCount / 3\n ) {\n const enumValues = extractEnumFromValues(Array.from(valuesResult.valuesCount.keys()));\n\n if (Object.keys(enumValues).length > 1)\n config.enum = enumValues;\n }\n\n // regular string\n if (!probablyAnEmail &&\n !probablyAURL &&\n !probablyUserIds &&\n !probablyAURL &&\n !config.enum) {\n const fileType = probableFileType(valuesResult, totalDocsCount);\n if (fileType) {\n config.storage = {\n acceptedFiles: fileType as FileType[],\n storagePath: findCommonInitialStringInPath(valuesResult) ?? \"/\"\n };\n }\n }\n\n if (Object.keys(config).length > 0)\n stringProperty = {\n ...stringProperty,\n ...config\n } as StringProperty;\n }\n\n return stringProperty;\n}\n\nfunction probableFileType(valuesCount: ValuesCountEntry, totalDocsCount: number): false | FileType[] {\n const isImage = (value: string) => IMAGE_EXTENSIONS.some((extension) => value.toString().endsWith(extension));\n const isAudio = (value: string) => AUDIO_EXTENSIONS.some((extension) => value.toString().endsWith(extension));\n const isVideo = (value: string) => VIDEO_EXTENSIONS.some((extension) => value.toString().endsWith(extension));\n\n const stringValues = valuesCount.values.filter((v): v is string => typeof v === \"string\");\n\n let imageCount = 0;\n let audioCount = 0;\n let videoCount = 0;\n\n for (const value of stringValues) {\n if (isImage(value)) imageCount++;\n else if (isAudio(value)) audioCount++;\n else if (isVideo(value)) videoCount++;\n }\n\n const totalMediaCount = imageCount + audioCount + videoCount;\n if (totalMediaCount > (totalDocsCount * 2) / 3) {\n const fileTypes: FileType[] = [];\n if (imageCount > 0) fileTypes.push(\"image/*\");\n if (audioCount > 0) fileTypes.push(\"audio/*\");\n if (videoCount > 0) fileTypes.push(\"video/*\");\n return fileTypes.length > 0 ? fileTypes : false;\n }\n\n return false;\n}\n","import { PropertyValidationSchema } from \"@rebasepro/types\";\nimport { InferencePropertyBuilderProps } from \"../types\";\n\nexport function buildValidation({\n totalDocsCount,\n valuesResult\n}: InferencePropertyBuilderProps): PropertyValidationSchema | undefined {\n\n if (valuesResult) {\n const totalEntriesCount = valuesResult.values.length;\n if (totalDocsCount === totalEntriesCount)\n return {\n required: true\n }\n }\n\n return undefined;\n}\n","import { findCommonInitialStringInPath } from \"../strings\";\nimport { InferencePropertyBuilderProps } from \"../types\";\nimport { Property } from \"@rebasepro/types\";\n\nexport function buildReferenceProperty({\n name,\n totalDocsCount,\n valuesResult\n}: InferencePropertyBuilderProps): Property {\n\n const property: Property = {\n name: name ?? \"\",\n type: \"reference\",\n path: findCommonInitialStringInPath(valuesResult) ?? \"!!!FIX_ME!!!\"\n };\n\n return property;\n}\n","import {\n InferencePropertyBuilderProps,\n TypesCount,\n TypesCountRecord,\n ValuesCountEntry,\n ValuesCountRecord\n} from \"./types\";\nimport { buildStringProperty } from \"./builders/string_property_builder\";\nimport { buildValidation } from \"./builders/validation_builder\";\nimport { buildReferenceProperty } from \"./builders/reference_property_builder\";\nimport { extractEnumFromValues, mergeDeep, prettifyIdentifier, resolveEnumValues } from \"./util\";\nimport { DataType, EnumValues, Properties, Property, StringProperty, Vector } from \"@rebasepro/types\";\n\nexport type InferenceTypeBuilder = (value: unknown) => DataType;\n\nexport async function buildEntityPropertiesFromData(\n data: object[],\n getType: InferenceTypeBuilder\n): Promise<Properties> {\n const typesCount: TypesCountRecord = {};\n const valuesCount: ValuesCountRecord = {};\n if (data) {\n data.forEach((entry) => {\n if (entry) {\n Object.entries(entry).forEach(([key, value]) => {\n if (key.startsWith(\"_\")) return; // Ignore properties starting with _\n increaseMapTypeCount(typesCount, key, value, getType);\n increaseValuesCount(valuesCount, key, value, getType);\n });\n }\n });\n }\n return buildPropertiesFromCount(data.length, typesCount, valuesCount);\n}\n\nexport function buildPropertyFromData(\n data: unknown[],\n property: Property,\n getType: InferenceTypeBuilder\n): Property {\n const typesCount = {};\n const valuesCount: ValuesCountRecord = {};\n if (data) {\n data.forEach((entry) => {\n increaseTypeCount(property.type, typesCount, entry, getType);\n increaseValuesCount(valuesCount, \"inferred_prop\", entry, getType);\n });\n }\n const enumValues = \"enum\" in property ? resolveEnumValues(property[\"enum\"] as EnumValues) : undefined;\n if (enumValues) {\n const newEnumValues = extractEnumFromValues(Array.from(valuesCount[\"inferred_prop\"].valuesCount.keys()));\n return {\n ...property,\n enum: [...newEnumValues, ...enumValues]\n } as StringProperty;\n }\n const generatedProperty = buildPropertyFromCount(\n \"inferred_prop\",\n data.length,\n property.type,\n typesCount,\n valuesCount[\"inferred_prop\"]\n );\n return mergeDeep(generatedProperty, property);\n}\n\nexport function buildPropertiesOrder(\n properties: Properties,\n propertiesOrder?: string[],\n priorityKeys?: string[]\n): string[] {\n const lowerCasePriorityKeys = (priorityKeys ?? []).map((key) => key.toLowerCase());\n\n function propOrder(s: string) {\n const k = s.toLowerCase();\n if (lowerCasePriorityKeys.includes(k)) return 4;\n if (k === \"title\" || k === \"name\") return 3;\n if (k.includes(\"title\") || k.includes(\"name\")) return 2;\n if (k.includes(\"image\") || k.includes(\"picture\")) return 1;\n return 0;\n }\n\n const keys = propertiesOrder ?? Object.keys(properties);\n keys.sort(); // alphabetically\n keys.sort((a, b) => {\n return propOrder(b) - propOrder(a);\n });\n return keys;\n}\n\n/**\n * @param type\n * @param typesCount\n * @param fieldValue\n * @param getType\n */\nfunction increaseTypeCount(\n type: DataType,\n typesCount: TypesCount,\n fieldValue: unknown,\n getType: InferenceTypeBuilder\n) {\n if (type === \"map\") {\n if (fieldValue) {\n let mapTypesCount = typesCount[type];\n if (!mapTypesCount) {\n mapTypesCount = {};\n typesCount[type] = mapTypesCount;\n }\n Object.entries(fieldValue as Record<string, unknown>).forEach(([key, value]) => {\n increaseMapTypeCount(mapTypesCount as TypesCountRecord, key, value, getType);\n });\n }\n } else if (type === \"array\") {\n let arrayTypesCount = typesCount[type];\n if (!arrayTypesCount) {\n arrayTypesCount = {};\n typesCount[type] = arrayTypesCount;\n }\n if (fieldValue && Array.isArray(fieldValue) && fieldValue.length > 0) {\n const arrayType = getMostProbableTypeInArray(fieldValue, getType);\n if (arrayType === \"map\") {\n let mapTypesCount = arrayTypesCount[arrayType];\n if (!mapTypesCount) {\n mapTypesCount = {};\n }\n fieldValue.forEach((value) => {\n if (value && typeof value === \"object\" && !Array.isArray(value)) { // Ensure value is an object for Object.entries\n Object.entries(value).forEach(([key, v]) =>\n increaseMapTypeCount(mapTypesCount, key, v, getType)\n );\n }\n });\n arrayTypesCount[arrayType] = mapTypesCount;\n } else {\n if (!arrayTypesCount[arrayType]) arrayTypesCount[arrayType] = 1;\n else arrayTypesCount[arrayType] = Number(arrayTypesCount[arrayType]) + 1;\n }\n }\n } else {\n if (!typesCount[type]) typesCount[type] = 1;\n else typesCount[type] = Number(typesCount[type]) + 1;\n }\n}\n\nfunction increaseMapTypeCount(\n typesCountRecord: TypesCountRecord,\n key: string,\n fieldValue: unknown,\n getType: InferenceTypeBuilder\n) {\n if (key.startsWith(\"_\")) return; // Ignore properties starting with _\n\n let typesCount: TypesCount = typesCountRecord[key];\n if (!typesCount) {\n typesCount = {};\n typesCountRecord[key] = typesCount;\n }\n\n if (fieldValue != null) {\n // Check that fieldValue is not null or undefined before proceeding\n const type = getType(fieldValue);\n increaseTypeCount(type, typesCount, fieldValue, getType);\n }\n}\n\nfunction increaseValuesCount(\n typeValuesRecord: ValuesCountRecord,\n key: string,\n fieldValue: unknown,\n getType: InferenceTypeBuilder\n) {\n if (key.startsWith(\"_\")) return; // Ignore properties starting with _\n\n const type = getType(fieldValue);\n\n let valuesRecord: {\n values: unknown[];\n valuesCount: Map<unknown, number>;\n map?: ValuesCountRecord;\n } = typeValuesRecord[key];\n\n if (!valuesRecord) {\n valuesRecord = {\n values: [],\n valuesCount: new Map()\n };\n typeValuesRecord[key] = valuesRecord;\n }\n\n if (type === \"map\") {\n let mapValuesRecord: ValuesCountRecord | undefined = valuesRecord.map;\n if (!mapValuesRecord) {\n mapValuesRecord = {};\n valuesRecord.map = mapValuesRecord;\n }\n if (fieldValue)\n Object.entries(fieldValue as Record<string, unknown>).forEach(([subKey, value]) =>\n increaseValuesCount(mapValuesRecord as ValuesCountRecord, subKey, value, getType)\n );\n } else if (type === \"array\") {\n if (Array.isArray(fieldValue)) {\n fieldValue.forEach((value) => {\n valuesRecord.values.push(value);\n valuesRecord.valuesCount.set(value, (valuesRecord.valuesCount.get(value) ?? 0) + 1);\n });\n }\n } else {\n if (fieldValue !== null && fieldValue !== undefined) {\n valuesRecord.values.push(fieldValue);\n valuesRecord.valuesCount.set(fieldValue, (valuesRecord.valuesCount.get(fieldValue) ?? 0) + 1);\n }\n }\n}\n\nfunction getHighestTypesCount(typesCount: TypesCount): number {\n let highestCount = 0;\n Object.entries(typesCount).forEach(([type, count]) => {\n let countValue = 0;\n if (type === \"map\") {\n countValue = getHighestRecordCount(count as TypesCountRecord);\n } else if (type === \"array\") {\n countValue = getHighestTypesCount(count as TypesCount);\n } else {\n countValue = Number(count);\n }\n if (countValue > highestCount) {\n highestCount = countValue;\n }\n });\n\n return highestCount;\n}\n\nfunction getHighestRecordCount(record: TypesCountRecord): number {\n return Object.entries(record)\n .map(([key, typesCount]) => getHighestTypesCount(typesCount))\n .reduce((a, b) => Math.max(a, b), 0);\n}\n\nfunction getMostProbableType(typesCount: TypesCount): DataType {\n let highestCount = -1;\n let probableType: DataType = \"string\"; // default\n Object.entries(typesCount).forEach(([type, count]) => {\n let countValue;\n if (type === \"map\") {\n countValue = getHighestRecordCount(count as TypesCountRecord);\n } else if (type === \"array\") {\n countValue = getHighestTypesCount(count as TypesCount);\n } else {\n countValue = Number(count);\n }\n if (countValue > highestCount) {\n highestCount = countValue;\n probableType = type as DataType;\n }\n });\n return probableType;\n}\n\nfunction buildPropertyFromCount(\n key: string,\n totalDocsCount: number,\n mostProbableType: DataType,\n typesCount: TypesCount,\n valuesResult?: ValuesCountEntry\n): Property {\n let title: string | undefined;\n\n if (key) {\n title = prettifyIdentifier(key);\n }\n\n let result: Property | undefined = undefined;\n if (mostProbableType === \"map\") {\n const highVariability = checkTypesCountHighVariability(typesCount);\n if (highVariability) {\n result = {\n type: \"map\",\n name: title ?? key ?? \"\",\n keyValue: true,\n properties: {}\n };\n }\n const properties = buildPropertiesFromCount(\n totalDocsCount,\n typesCount.map as TypesCountRecord,\n valuesResult ? valuesResult.mapValues : undefined\n );\n result = {\n type: \"map\",\n name: title ?? key ?? \"\",\n properties\n };\n } else if (mostProbableType === \"array\") {\n const arrayTypesCount = typesCount.array as TypesCount;\n const arrayMostProbableType = getMostProbableType(arrayTypesCount);\n const of = buildPropertyFromCount(\n key,\n totalDocsCount,\n arrayMostProbableType,\n arrayTypesCount,\n valuesResult\n );\n result = {\n type: \"array\",\n name: title ?? key ?? \"\",\n of\n };\n }\n\n if (!result) {\n const propertyProps: InferencePropertyBuilderProps = {\n name: key,\n totalDocsCount,\n valuesResult\n };\n if (mostProbableType === \"string\") {\n result = buildStringProperty(propertyProps);\n } else if (mostProbableType === \"reference\") {\n result = buildReferenceProperty(propertyProps);\n } else {\n result = {\n type: mostProbableType\n } as Property;\n }\n\n if (title) {\n result.name = title;\n }\n\n const validation = buildValidation(propertyProps);\n if (validation) {\n result.validation = validation;\n }\n }\n\n return result;\n}\n\nfunction buildPropertiesFromCount(\n totalDocsCount: number,\n typesCountRecord: TypesCountRecord,\n valuesCountRecord?: ValuesCountRecord\n): Properties {\n const res: Properties = {};\n Object.entries(typesCountRecord).forEach(([key, typesCount]) => {\n const mostProbableType = getMostProbableType(typesCount);\n res[key] = buildPropertyFromCount(\n key,\n totalDocsCount,\n mostProbableType,\n typesCount,\n valuesCountRecord ? valuesCountRecord[key] : undefined\n );\n });\n return res;\n}\n\nfunction countMaxDocumentsUnder(typesCount: TypesCount) {\n let count = 0;\n Object.entries(typesCount).forEach(([type, value]) => {\n if (typeof value === \"object\") {\n count = Math.max(count, countMaxDocumentsUnder(value as TypesCountRecord));\n } else {\n count = Math.max(count, Number(value));\n }\n });\n return count;\n}\n\nfunction getMostProbableTypeInArray(\n array: unknown[],\n getType: InferenceTypeBuilder\n): DataType {\n const typesCount: TypesCount = {};\n array.forEach((value) => {\n increaseTypeCount(getType(value), typesCount, value, getType);\n });\n return getMostProbableType(typesCount);\n}\n\nfunction checkTypesCountHighVariability(typesCount: TypesCount) {\n const maxCount = countMaxDocumentsUnder(typesCount);\n let keysWithFewValues = 0;\n Object.entries(typesCount.map ?? {}).forEach(([key, value]) => {\n const count = countMaxDocumentsUnder(value);\n if (count < maxCount / 3) {\n keysWithFewValues++;\n }\n });\n return keysWithFewValues / Object.entries(typesCount.map ?? {}).length > 0.5;\n}\n\n\nexport function inferTypeFromValue(value: unknown): DataType {\n if (value === null || value === undefined) return \"string\";\n if (value instanceof Vector || (value && typeof value === \"object\" && \"__type\" in value && (value as Record<string, unknown>).__type === \"Vector\")) return \"vector\";\n if (typeof value === \"string\") return \"string\";\n if (typeof value === \"number\") return \"number\";\n if (typeof value === \"boolean\") return \"boolean\";\n if (Array.isArray(value)) return \"array\";\n if (typeof value === \"object\") return \"map\";\n return \"string\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.es.js","names":[],"sources":["../src/strings.ts","../src/util.ts","../src/builders/string_property_builder.ts","../src/builders/validation_builder.ts","../src/builders/reference_property_builder.ts","../src/collection_builder.ts"],"sourcesContent":["import { ValuesCountEntry } from \"./types\";\n\n/**\n * Parse a reference string value which can be in the format:\n * - Simple: \"path/entityId\"\n * - With database: \"database_name:::path/entityId\"\n * Returns the path and database (undefined if not specified or if \"(default)\")\n */\nexport function parseReferenceString(value: string): { path: string; database?: string } | null {\n if (!value) return null;\n\n let database: string | undefined = undefined;\n let fullPath = value;\n\n // Parse the new format: database_name:::path/entityId\n if (value.includes(\":::\")) {\n const [dbName, pathPart] = value.split(\":::\");\n if (dbName && dbName !== \"(default)\") {\n database = dbName;\n }\n fullPath = pathPart;\n }\n\n // Check if it looks like a path (contains at least one slash)\n if (!fullPath || !fullPath.includes(\"/\")) {\n return null;\n }\n\n // Extract the collection path (everything before the last slash)\n const path = fullPath.substring(0, fullPath.lastIndexOf(\"/\"));\n\n return { path,\ndatabase };\n}\n\n/**\n * Check if a string value looks like a reference\n */\nexport function looksLikeReference(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n return parseReferenceString(value) !== null;\n}\n\nexport function findCommonInitialStringInPath(valuesCount?: ValuesCountEntry) {\n\n if (!valuesCount) return undefined;\n\n function getPath(value: unknown): string | undefined {\n let pathString: string | undefined;\n\n if (typeof value === \"string\") {\n pathString = value;\n } else if (value && typeof value === \"object\" && \"slug\" in value && typeof (value as Record<string, unknown>).slug === \"string\") {\n pathString = (value as Record<string, unknown>).slug as string;\n } else {\n console.warn(\"findCommonInitialStringInPath: value is not a string or document with path\", value);\n return undefined;\n }\n\n if (!pathString) return undefined;\n\n // Parse the new format: database_name:::path/entityId\n // Extract just the path portion for comparison\n if (pathString.includes(\":::\")) {\n const [, pathPart] = pathString.split(\":::\");\n pathString = pathPart;\n }\n\n return pathString;\n }\n\n const strings: string[] = valuesCount.values.map((v) => getPath(v)).filter(v => !!v) as string[];\n const pathWithSlash = strings.find((s) => s.includes(\"/\"));\n if (!pathWithSlash)\n return undefined;\n\n const searchedPath = pathWithSlash.substring(0, pathWithSlash.lastIndexOf(\"/\"));\n\n const yep = valuesCount.values\n .filter((value) => {\n const path = getPath(value);\n if (!path) return false;\n return path.startsWith(searchedPath)\n }).length > valuesCount.values.length / 3 * 2;\n\n return yep ? searchedPath : undefined;\n\n}\n\nexport function removeInitialAndTrailingSlashes(s: string): string {\n return removeInitialSlash(removeTrailingSlash(s));\n}\n\nexport function removeInitialSlash(s: string) {\n if (s.startsWith(\"/\"))\n return s.slice(1);\n else return s;\n}\n\nexport function removeTrailingSlash(s: string) {\n if (s.endsWith(\"/\"))\n return s.slice(0, -1);\n else return s;\n}\n","import { EnumValueConfig, EnumValues } from \"@rebasepro/types\";\nimport { unslugify } from \"@rebasepro/utils\";\n\n// Canonical utility functions — single source of truth in @rebasepro/utils\nexport { unslugify, prettifyIdentifier, isObject, isPlainObject, mergeDeep } from \"@rebasepro/utils\";\n\n/**\n * Extract enum values from a list of sample values.\n * This is inference-specific logic (not a general utility).\n */\nexport function extractEnumFromValues(values: unknown[]) {\n if (!Array.isArray(values)) {\n return [];\n }\n const enumValues = values\n .map((value) => {\n if (typeof value === \"string\") {\n return ({\n id: value,\n label: unslugify(value)\n });\n } else\n return null;\n }).filter(Boolean) as Array<{ id: string, label: string }>;\n enumValues.sort((a, b) => a.label.localeCompare(b.label));\n return enumValues;\n}\n\nexport function resolveEnumValues(input: EnumValues): EnumValueConfig[] | undefined {\n // Check Array.isArray first since typeof [] === \"object\" is true in JavaScript\n if (Array.isArray(input)) {\n return input as EnumValueConfig[];\n } else if (typeof input === \"object\" && input !== null) {\n return Object.entries(input).map(([id, value]) =>\n (typeof value === \"string\"\n ? {\n id,\n label: value\n }\n : value));\n } else {\n return undefined;\n }\n}\n","import { findCommonInitialStringInPath } from \"../strings\";\nimport { extractEnumFromValues } from \"../util\";\nimport { FileType, Property, StringProperty } from \"@rebasepro/types\";\nimport { InferencePropertyBuilderProps, ValuesCountEntry } from \"../types\";\n\nconst IMAGE_EXTENSIONS = [\".jpg\", \".jpeg\", \".png\", \".webp\", \".gif\", \".avif\"];\nconst AUDIO_EXTENSIONS = [\".mp3\", \".ogg\", \".opus\", \".aac\"];\nconst VIDEO_EXTENSIONS = [\".avi\", \".mp4\"];\n\nconst emailRegEx = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;\n\nexport function buildStringProperty({\n name,\n totalDocsCount,\n valuesResult\n}: InferencePropertyBuilderProps): Property {\n\n let stringProperty: Property = {\n name: name ?? \"\",\n type: \"string\"\n\n };\n\n if (valuesResult) {\n\n const totalEntriesCount = valuesResult.values.length;\n const totalValues = Array.from(valuesResult.valuesCount.keys()).length;\n\n const config: Partial<StringProperty> = {};\n\n const probablyAURL = valuesResult.values\n .filter((value) => typeof value === \"string\" &&\n value.toString().startsWith(\"http\")).length > totalDocsCount / 3 * 2;\n if (probablyAURL) {\n config.url = true;\n }\n\n const probablyAnEmail = valuesResult.values\n .filter((value) => typeof value === \"string\" &&\n emailRegEx.test(value)).length > totalDocsCount / 3 * 2;\n if (probablyAnEmail) {\n config.email = true;\n }\n\n const probablyUserIds = valuesResult.values\n .filter((value) => typeof value === \"string\" && value.length === 28 && !value.includes(\" \"))\n .length > totalDocsCount / 3 * 2;\n // No `admin.readOnly` here. This package is core — the field does not exist in\n // `@rebasepro/types`, because `@rebasepro/admin-types` declares it — and a\n // guess about whether a form should be editable is presentation, not schema.\n // (It also would not have enforced anything: nothing on the server reads\n // `readOnly`.) The inferred `isId` below is the part that carries meaning.\n\n if (!probablyAnEmail &&\n !probablyAURL &&\n !probablyUserIds &&\n !probablyAURL &&\n totalValues < totalEntriesCount / 3\n ) {\n const enumValues = extractEnumFromValues(Array.from(valuesResult.valuesCount.keys()));\n\n if (Object.keys(enumValues).length > 1)\n config.enum = enumValues;\n }\n\n // regular string\n if (!probablyAnEmail &&\n !probablyAURL &&\n !probablyUserIds &&\n !probablyAURL &&\n !config.enum) {\n const fileType = probableFileType(valuesResult, totalDocsCount);\n if (fileType) {\n config.storage = {\n acceptedFiles: fileType as FileType[],\n storagePath: findCommonInitialStringInPath(valuesResult) ?? \"/\"\n };\n }\n }\n\n if (Object.keys(config).length > 0)\n stringProperty = {\n ...stringProperty,\n ...config\n } as StringProperty;\n }\n\n return stringProperty;\n}\n\nfunction probableFileType(valuesCount: ValuesCountEntry, totalDocsCount: number): false | FileType[] {\n const isImage = (value: string) => IMAGE_EXTENSIONS.some((extension) => value.toString().endsWith(extension));\n const isAudio = (value: string) => AUDIO_EXTENSIONS.some((extension) => value.toString().endsWith(extension));\n const isVideo = (value: string) => VIDEO_EXTENSIONS.some((extension) => value.toString().endsWith(extension));\n\n const stringValues = valuesCount.values.filter((v): v is string => typeof v === \"string\");\n\n let imageCount = 0;\n let audioCount = 0;\n let videoCount = 0;\n\n for (const value of stringValues) {\n if (isImage(value)) imageCount++;\n else if (isAudio(value)) audioCount++;\n else if (isVideo(value)) videoCount++;\n }\n\n const totalMediaCount = imageCount + audioCount + videoCount;\n if (totalMediaCount > (totalDocsCount * 2) / 3) {\n const fileTypes: FileType[] = [];\n if (imageCount > 0) fileTypes.push(\"image/*\");\n if (audioCount > 0) fileTypes.push(\"audio/*\");\n if (videoCount > 0) fileTypes.push(\"video/*\");\n return fileTypes.length > 0 ? fileTypes : false;\n }\n\n return false;\n}\n","import { PropertyValidationSchema } from \"@rebasepro/types\";\nimport { InferencePropertyBuilderProps } from \"../types\";\n\nexport function buildValidation({\n totalDocsCount,\n valuesResult\n}: InferencePropertyBuilderProps): PropertyValidationSchema | undefined {\n\n if (valuesResult) {\n const totalEntriesCount = valuesResult.values.length;\n if (totalDocsCount === totalEntriesCount)\n return {\n required: true\n }\n }\n\n return undefined;\n}\n","import { findCommonInitialStringInPath } from \"../strings\";\nimport { InferencePropertyBuilderProps } from \"../types\";\nimport { Property } from \"@rebasepro/types\";\n\nexport function buildReferenceProperty({\n name,\n totalDocsCount,\n valuesResult\n}: InferencePropertyBuilderProps): Property {\n\n const property: Property = {\n name: name ?? \"\",\n type: \"reference\",\n path: findCommonInitialStringInPath(valuesResult) ?? \"!!!FIX_ME!!!\"\n };\n\n return property;\n}\n","import {\n InferencePropertyBuilderProps,\n TypesCount,\n TypesCountRecord,\n ValuesCountEntry,\n ValuesCountRecord\n} from \"./types\";\nimport { buildStringProperty } from \"./builders/string_property_builder\";\nimport { buildValidation } from \"./builders/validation_builder\";\nimport { buildReferenceProperty } from \"./builders/reference_property_builder\";\nimport { extractEnumFromValues, mergeDeep, prettifyIdentifier, resolveEnumValues } from \"./util\";\nimport { DataType, EnumValues, Properties, Property, StringProperty, Vector } from \"@rebasepro/types\";\n\nexport type InferenceTypeBuilder = (value: unknown) => DataType;\n\nexport async function buildEntityPropertiesFromData(\n data: object[],\n getType: InferenceTypeBuilder\n): Promise<Properties> {\n const typesCount: TypesCountRecord = {};\n const valuesCount: ValuesCountRecord = {};\n if (data) {\n data.forEach((entry) => {\n if (entry) {\n Object.entries(entry).forEach(([key, value]) => {\n if (key.startsWith(\"_\")) return; // Ignore properties starting with _\n increaseMapTypeCount(typesCount, key, value, getType);\n increaseValuesCount(valuesCount, key, value, getType);\n });\n }\n });\n }\n return buildPropertiesFromCount(data.length, typesCount, valuesCount);\n}\n\nexport function buildPropertyFromData(\n data: unknown[],\n property: Property,\n getType: InferenceTypeBuilder\n): Property {\n const typesCount = {};\n const valuesCount: ValuesCountRecord = {};\n if (data) {\n data.forEach((entry) => {\n increaseTypeCount(property.type, typesCount, entry, getType);\n increaseValuesCount(valuesCount, \"inferred_prop\", entry, getType);\n });\n }\n const enumValues = \"enum\" in property ? resolveEnumValues(property[\"enum\"] as EnumValues) : undefined;\n if (enumValues) {\n const newEnumValues = extractEnumFromValues(Array.from(valuesCount[\"inferred_prop\"].valuesCount.keys()));\n return {\n ...property,\n enum: [...newEnumValues, ...enumValues]\n } as StringProperty;\n }\n const generatedProperty = buildPropertyFromCount(\n \"inferred_prop\",\n data.length,\n property.type,\n typesCount,\n valuesCount[\"inferred_prop\"]\n );\n return mergeDeep(generatedProperty, property);\n}\n\nexport function buildPropertiesOrder(\n properties: Properties,\n propertiesOrder?: string[],\n priorityKeys?: string[]\n): string[] {\n const lowerCasePriorityKeys = (priorityKeys ?? []).map((key) => key.toLowerCase());\n\n function propOrder(s: string) {\n const k = s.toLowerCase();\n if (lowerCasePriorityKeys.includes(k)) return 4;\n if (k === \"title\" || k === \"name\") return 3;\n if (k.includes(\"title\") || k.includes(\"name\")) return 2;\n if (k.includes(\"image\") || k.includes(\"picture\")) return 1;\n return 0;\n }\n\n function deriveOrder(keys: string[]): string[] {\n return [...keys]\n .sort() // alphabetically\n .sort((a, b) => propOrder(b) - propOrder(a));\n }\n\n // A caller-supplied order is an answer, not an input. It used to be handed\n // to the same alphabetical + priority sort as a derived order, which threw\n // it away entirely — and because `Array.prototype.sort` sorts in place, the\n // caller's own array was silently reordered as a side effect.\n if (propertiesOrder) {\n const placed = new Set(propertiesOrder);\n // Properties the caller did not place — a newly inferred column, say —\n // still have to appear, so they follow in derived order.\n const rest = Object.keys(properties).filter(key => !placed.has(key));\n return [...propertiesOrder, ...deriveOrder(rest)];\n }\n\n return deriveOrder(Object.keys(properties));\n}\n\n/**\n * @param type\n * @param typesCount\n * @param fieldValue\n * @param getType\n */\nfunction increaseTypeCount(\n type: DataType,\n typesCount: TypesCount,\n fieldValue: unknown,\n getType: InferenceTypeBuilder\n) {\n if (type === \"map\") {\n if (fieldValue) {\n let mapTypesCount = typesCount[type];\n if (!mapTypesCount) {\n mapTypesCount = {};\n typesCount[type] = mapTypesCount;\n }\n Object.entries(fieldValue as Record<string, unknown>).forEach(([key, value]) => {\n increaseMapTypeCount(mapTypesCount as TypesCountRecord, key, value, getType);\n });\n }\n } else if (type === \"array\") {\n let arrayTypesCount = typesCount[type];\n if (!arrayTypesCount) {\n arrayTypesCount = {};\n typesCount[type] = arrayTypesCount;\n }\n if (fieldValue && Array.isArray(fieldValue) && fieldValue.length > 0) {\n const arrayType = getMostProbableTypeInArray(fieldValue, getType);\n if (arrayType === \"map\") {\n let mapTypesCount = arrayTypesCount[arrayType];\n if (!mapTypesCount) {\n mapTypesCount = {};\n }\n fieldValue.forEach((value) => {\n if (value && typeof value === \"object\" && !Array.isArray(value)) { // Ensure value is an object for Object.entries\n Object.entries(value).forEach(([key, v]) =>\n increaseMapTypeCount(mapTypesCount, key, v, getType)\n );\n }\n });\n arrayTypesCount[arrayType] = mapTypesCount;\n } else {\n if (!arrayTypesCount[arrayType]) arrayTypesCount[arrayType] = 1;\n else arrayTypesCount[arrayType] = Number(arrayTypesCount[arrayType]) + 1;\n }\n }\n } else {\n if (!typesCount[type]) typesCount[type] = 1;\n else typesCount[type] = Number(typesCount[type]) + 1;\n }\n}\n\nfunction increaseMapTypeCount(\n typesCountRecord: TypesCountRecord,\n key: string,\n fieldValue: unknown,\n getType: InferenceTypeBuilder\n) {\n if (key.startsWith(\"_\")) return; // Ignore properties starting with _\n\n let typesCount: TypesCount = typesCountRecord[key];\n if (!typesCount) {\n typesCount = {};\n typesCountRecord[key] = typesCount;\n }\n\n if (fieldValue != null) {\n // Check that fieldValue is not null or undefined before proceeding\n const type = getType(fieldValue);\n increaseTypeCount(type, typesCount, fieldValue, getType);\n }\n}\n\nfunction increaseValuesCount(\n typeValuesRecord: ValuesCountRecord,\n key: string,\n fieldValue: unknown,\n getType: InferenceTypeBuilder\n) {\n if (key.startsWith(\"_\")) return; // Ignore properties starting with _\n\n const type = getType(fieldValue);\n\n let valuesRecord: {\n values: unknown[];\n valuesCount: Map<unknown, number>;\n map?: ValuesCountRecord;\n } = typeValuesRecord[key];\n\n if (!valuesRecord) {\n valuesRecord = {\n values: [],\n valuesCount: new Map()\n };\n typeValuesRecord[key] = valuesRecord;\n }\n\n if (type === \"map\") {\n let mapValuesRecord: ValuesCountRecord | undefined = valuesRecord.map;\n if (!mapValuesRecord) {\n mapValuesRecord = {};\n valuesRecord.map = mapValuesRecord;\n }\n if (fieldValue)\n Object.entries(fieldValue as Record<string, unknown>).forEach(([subKey, value]) =>\n increaseValuesCount(mapValuesRecord as ValuesCountRecord, subKey, value, getType)\n );\n } else if (type === \"array\") {\n if (Array.isArray(fieldValue)) {\n fieldValue.forEach((value) => {\n valuesRecord.values.push(value);\n valuesRecord.valuesCount.set(value, (valuesRecord.valuesCount.get(value) ?? 0) + 1);\n });\n }\n } else {\n if (fieldValue !== null && fieldValue !== undefined) {\n valuesRecord.values.push(fieldValue);\n valuesRecord.valuesCount.set(fieldValue, (valuesRecord.valuesCount.get(fieldValue) ?? 0) + 1);\n }\n }\n}\n\nfunction getHighestTypesCount(typesCount: TypesCount): number {\n let highestCount = 0;\n Object.entries(typesCount).forEach(([type, count]) => {\n let countValue = 0;\n if (type === \"map\") {\n countValue = getHighestRecordCount(count as TypesCountRecord);\n } else if (type === \"array\") {\n countValue = getHighestTypesCount(count as TypesCount);\n } else {\n countValue = Number(count);\n }\n if (countValue > highestCount) {\n highestCount = countValue;\n }\n });\n\n return highestCount;\n}\n\nfunction getHighestRecordCount(record: TypesCountRecord): number {\n return Object.entries(record)\n .map(([key, typesCount]) => getHighestTypesCount(typesCount))\n .reduce((a, b) => Math.max(a, b), 0);\n}\n\nfunction getMostProbableType(typesCount: TypesCount): DataType {\n let highestCount = -1;\n let probableType: DataType = \"string\"; // default\n Object.entries(typesCount).forEach(([type, count]) => {\n let countValue;\n if (type === \"map\") {\n countValue = getHighestRecordCount(count as TypesCountRecord);\n } else if (type === \"array\") {\n countValue = getHighestTypesCount(count as TypesCount);\n } else {\n countValue = Number(count);\n }\n if (countValue > highestCount) {\n highestCount = countValue;\n probableType = type as DataType;\n }\n });\n return probableType;\n}\n\nfunction buildPropertyFromCount(\n key: string,\n totalDocsCount: number,\n mostProbableType: DataType,\n typesCount: TypesCount,\n valuesResult?: ValuesCountEntry\n): Property {\n let title: string | undefined;\n\n if (key) {\n title = prettifyIdentifier(key);\n }\n\n let result: Property | undefined = undefined;\n if (mostProbableType === \"map\") {\n const highVariability = checkTypesCountHighVariability(typesCount);\n if (highVariability) {\n result = {\n type: \"map\",\n name: title ?? key ?? \"\",\n keyValue: true,\n properties: {}\n };\n }\n const properties = buildPropertiesFromCount(\n totalDocsCount,\n typesCount.map as TypesCountRecord,\n valuesResult ? valuesResult.mapValues : undefined\n );\n result = {\n type: \"map\",\n name: title ?? key ?? \"\",\n properties\n };\n } else if (mostProbableType === \"array\") {\n const arrayTypesCount = typesCount.array as TypesCount;\n const arrayMostProbableType = getMostProbableType(arrayTypesCount);\n const of = buildPropertyFromCount(\n key,\n totalDocsCount,\n arrayMostProbableType,\n arrayTypesCount,\n valuesResult\n );\n result = {\n type: \"array\",\n name: title ?? key ?? \"\",\n of\n };\n }\n\n if (!result) {\n const propertyProps: InferencePropertyBuilderProps = {\n name: key,\n totalDocsCount,\n valuesResult\n };\n if (mostProbableType === \"string\") {\n result = buildStringProperty(propertyProps);\n } else if (mostProbableType === \"reference\") {\n result = buildReferenceProperty(propertyProps);\n } else {\n result = {\n type: mostProbableType\n } as Property;\n }\n\n if (title) {\n result.name = title;\n }\n\n const validation = buildValidation(propertyProps);\n if (validation) {\n result.validation = validation;\n }\n }\n\n return result;\n}\n\nfunction buildPropertiesFromCount(\n totalDocsCount: number,\n typesCountRecord: TypesCountRecord,\n valuesCountRecord?: ValuesCountRecord\n): Properties {\n const res: Properties = {};\n Object.entries(typesCountRecord).forEach(([key, typesCount]) => {\n const mostProbableType = getMostProbableType(typesCount);\n res[key] = buildPropertyFromCount(\n key,\n totalDocsCount,\n mostProbableType,\n typesCount,\n valuesCountRecord ? valuesCountRecord[key] : undefined\n );\n });\n return res;\n}\n\nfunction countMaxDocumentsUnder(typesCount: TypesCount) {\n let count = 0;\n Object.entries(typesCount).forEach(([type, value]) => {\n if (typeof value === \"object\") {\n count = Math.max(count, countMaxDocumentsUnder(value as TypesCountRecord));\n } else {\n count = Math.max(count, Number(value));\n }\n });\n return count;\n}\n\nfunction getMostProbableTypeInArray(\n array: unknown[],\n getType: InferenceTypeBuilder\n): DataType {\n const typesCount: TypesCount = {};\n array.forEach((value) => {\n // A hole in the array says nothing about what the array holds, so it\n // gets no vote. This used to fall out of `getType` answering \"map\" for\n // null and `increaseTypeCount` then skipping falsy map values; now that\n // null infers as \"string\" the skip has to be explicit, or every array\n // with a gap in it would drift towards \"string\".\n if (value === null || value === undefined) return;\n increaseTypeCount(getType(value), typesCount, value, getType);\n });\n return getMostProbableType(typesCount);\n}\n\nfunction checkTypesCountHighVariability(typesCount: TypesCount) {\n const maxCount = countMaxDocumentsUnder(typesCount);\n let keysWithFewValues = 0;\n Object.entries(typesCount.map ?? {}).forEach(([key, value]) => {\n const count = countMaxDocumentsUnder(value);\n if (count < maxCount / 3) {\n keysWithFewValues++;\n }\n });\n return keysWithFewValues / Object.entries(typesCount.map ?? {}).length > 0.5;\n}\n\n\nexport function inferTypeFromValue(value: unknown): DataType {\n if (value === null || value === undefined) return \"string\";\n if (value instanceof Vector || (value && typeof value === \"object\" && \"__type\" in value && (value as Record<string, unknown>).__type === \"Vector\")) return \"vector\";\n if (typeof value === \"string\") return \"string\";\n if (typeof value === \"number\") return \"number\";\n if (typeof value === \"boolean\") return \"boolean\";\n if (Array.isArray(value)) return \"array\";\n if (typeof value === \"object\") return \"map\";\n return \"string\";\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebasepro/inference",
3
- "version": "0.12.1-canary.gf5f1d39",
3
+ "version": "0.13.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -11,16 +11,16 @@
11
11
  "types": "dist/index.d.ts",
12
12
  "source": "src/index.ts",
13
13
  "dependencies": {
14
- "@rebasepro/types": "0.12.1-canary.gf5f1d39",
15
- "@rebasepro/utils": "0.12.1-canary.gf5f1d39"
14
+ "@rebasepro/types": "0.13.0",
15
+ "@rebasepro/utils": "0.13.0"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/jest": "^30.0.0",
19
- "@types/node": "^25.9.3",
19
+ "@types/node": "^26.1.2",
20
20
  "jest": "^30.4.2",
21
- "ts-jest": "^29.4.11",
21
+ "ts-jest": "^29.4.12",
22
22
  "typescript": "^6.0.3",
23
- "vite": "^8.0.16"
23
+ "vite": "^8.1.5"
24
24
  },
25
25
  "exports": {
26
26
  ".": {
@@ -80,12 +80,25 @@ export function buildPropertiesOrder(
80
80
  return 0;
81
81
  }
82
82
 
83
- const keys = propertiesOrder ?? Object.keys(properties);
84
- keys.sort(); // alphabetically
85
- keys.sort((a, b) => {
86
- return propOrder(b) - propOrder(a);
87
- });
88
- return keys;
83
+ function deriveOrder(keys: string[]): string[] {
84
+ return [...keys]
85
+ .sort() // alphabetically
86
+ .sort((a, b) => propOrder(b) - propOrder(a));
87
+ }
88
+
89
+ // A caller-supplied order is an answer, not an input. It used to be handed
90
+ // to the same alphabetical + priority sort as a derived order, which threw
91
+ // it away entirely — and because `Array.prototype.sort` sorts in place, the
92
+ // caller's own array was silently reordered as a side effect.
93
+ if (propertiesOrder) {
94
+ const placed = new Set(propertiesOrder);
95
+ // Properties the caller did not place — a newly inferred column, say —
96
+ // still have to appear, so they follow in derived order.
97
+ const rest = Object.keys(properties).filter(key => !placed.has(key));
98
+ return [...propertiesOrder, ...deriveOrder(rest)];
99
+ }
100
+
101
+ return deriveOrder(Object.keys(properties));
89
102
  }
90
103
 
91
104
  /**
@@ -375,6 +388,12 @@ function getMostProbableTypeInArray(
375
388
  ): DataType {
376
389
  const typesCount: TypesCount = {};
377
390
  array.forEach((value) => {
391
+ // A hole in the array says nothing about what the array holds, so it
392
+ // gets no vote. This used to fall out of `getType` answering "map" for
393
+ // null and `increaseTypeCount` then skipping falsy map values; now that
394
+ // null infers as "string" the skip has to be explicit, or every array
395
+ // with a gap in it would drift towards "string".
396
+ if (value === null || value === undefined) return;
378
397
  increaseTypeCount(getType(value), typesCount, value, getType);
379
398
  });
380
399
  return getMostProbableType(typesCount);