@rebasepro/inference 0.24.0 → 0.24.1-canary.g56af4a4

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @rebasepro/inference
2
2
 
3
- Automatically infer Rebase collection property schemas from sample data — analyzes field types, detects enums, references, validation rules, and nested structures.
3
+ Infer Rebase collection property schemas from sample data: field types, enums, `required`, `email` / `url`, storage fields, and nested maps and arrays.
4
4
 
5
5
  ## Installation
6
6
 
@@ -14,7 +14,7 @@ of it resolves too: Node has supported `require(esm)` since 22.12.
14
14
 
15
15
  ## What This Package Does
16
16
 
17
- `@rebasepro/inference` examines arrays of data objects and produces `Properties` definitions compatible with the Rebase collection schema. It uses statistical analysis to determine the most probable type for each field, detect enum values, identify reference patterns, and build nested map/array structures. Used by the Rebase introspection pipeline and data import tools.
17
+ `@rebasepro/inference` examines arrays of data objects and produces `Properties` definitions compatible with the Rebase collection schema. It counts the types each field holds to pick one: a column that mixes numbers with text that is not exactly a number is a string. It also detects enum values and builds nested map/array structures. A `"reference"` property is built only when your `getType` returns `"reference"`; the default `inferTypeFromValue` never does. The admin's "create a collection from a file" import is its consumer in Rebase; database introspection does not use it.
18
18
 
19
19
  ## Key Exports
20
20
 
@@ -22,10 +22,10 @@ of it resolves too: Node has supported `require(esm)` since 22.12.
22
22
 
23
23
  | Export | Type | Description |
24
24
  |---|---|---|
25
- | `buildEntityPropertiesFromData` | `(data: object[], getType: InferenceTypeBuilder) => Promise<Properties>` | Main entry — infer full property schema from data |
25
+ | `buildEntityPropertiesFromData` | `(data: object[], getType: InferenceTypeBuilder, options?: { ignoreKey?: (key: string) => boolean }) => Promise<Properties>` | Main entry — infer full property schema from data. `ignoreKey` leaves top-level keys out (none by default) |
26
26
  | `buildPropertyFromData` | `(data: unknown[], property: Property, getType: InferenceTypeBuilder) => Property` | Refine an existing property with new sample data |
27
27
  | `buildPropertiesOrder` | `(properties: Properties, propertiesOrder?: string[], priorityKeys?: string[]) => string[]` | Sort property keys (title/name first, then images, then alphabetical) |
28
- | `inferTypeFromValue` | `(value: unknown) => DataType` | Default type inference: string, number, boolean, array, map, vector |
28
+ | `inferTypeFromValue` | `(value: unknown) => DataType` | Default type inference: string, number, boolean, date, array, map, vector |
29
29
  | `InferenceTypeBuilder` | Type | `(value: unknown) => DataType` — pluggable type detector |
30
30
 
31
31
  ### String Utilities
@@ -60,6 +60,10 @@ const sampleData = [
60
60
  { name: "Alice", age: 30, active: true, role: "admin" },
61
61
  { name: "Bob", age: 25, active: false, role: "editor" },
62
62
  { name: "Carol", age: 28, active: true, role: "admin" },
63
+ { name: "Dan", age: 41, active: true, role: "editor" },
64
+ { name: "Eve", age: 35, active: false, role: "admin" },
65
+ { name: "Frank", age: 52, active: true, role: "editor" },
66
+ { name: "Grace", age: 23, active: true, role: "admin" },
63
67
  ];
64
68
 
65
69
  const properties = await buildEntityPropertiesFromData(
@@ -67,11 +71,12 @@ const properties = await buildEntityPropertiesFromData(
67
71
  inferTypeFromValue
68
72
  );
69
73
  // properties = {
70
- // name: { type: "string", name: "Name", ... },
71
- // age: { type: "number", name: "Age", ... },
72
- // active: { type: "boolean", name: "Active", ... },
73
- // role: { type: "string", name: "Role", enum: [...], ... }
74
+ // name: { type: "string", name: "Name", validation: { required: true } },
75
+ // age: { type: "number", name: "Age", validation: { required: true } },
76
+ // active: { type: "boolean", name: "Active", validation: { required: true } },
77
+ // role: { type: "string", name: "Role", enum: [...], validation: { required: true } }
74
78
  // }
79
+ // An enum is inferred when a column has fewer distinct values than a third of its values.
75
80
  ```
76
81
 
77
82
  ## Related Packages
@@ -1,6 +1,14 @@
1
1
  import { DataType, Properties, Property } from "@rebasepro/types";
2
2
  export type InferenceTypeBuilder = (value: unknown) => DataType;
3
- export declare function buildEntityPropertiesFromData(data: object[], getType: InferenceTypeBuilder): Promise<Properties>;
3
+ export type BuildEntityPropertiesOptions = {
4
+ /**
5
+ * Top-level keys to leave out of the inferred properties — Firestore
6
+ * metadata such as `__name__`, say. Nothing is left out by default: a
7
+ * column is data, `_id` in a Mongo export included.
8
+ */
9
+ ignoreKey?: (key: string) => boolean;
10
+ };
11
+ export declare function buildEntityPropertiesFromData(data: object[], getType: InferenceTypeBuilder, options?: BuildEntityPropertiesOptions): Promise<Properties>;
4
12
  export declare function buildPropertyFromData(data: unknown[], property: Property, getType: InferenceTypeBuilder): Property;
5
13
  export declare function buildPropertiesOrder(properties: Properties, propertiesOrder?: string[], priorityKeys?: string[]): string[];
6
14
  export declare function inferTypeFromValue(value: unknown): DataType;
package/dist/index.es.js CHANGED
@@ -74,13 +74,18 @@ function removeTrailingSlash(s) {
74
74
  */
75
75
  function extractEnumFromValues(values) {
76
76
  if (!Array.isArray(values)) return [];
77
- const enumValues = values.map((value) => {
78
- if (typeof value === "string") return {
79
- id: value,
80
- label: unslugify$1(value)
81
- };
82
- else return null;
83
- }).filter(Boolean);
77
+ const seen = /* @__PURE__ */ new Set();
78
+ const enumValues = [];
79
+ for (const value of values) {
80
+ if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") continue;
81
+ const id = String(value);
82
+ if (seen.has(id)) continue;
83
+ seen.add(id);
84
+ enumValues.push({
85
+ id,
86
+ label: typeof value === "string" ? unslugify$1(value) : id
87
+ });
88
+ }
84
89
  enumValues.sort((a, b) => a.label.localeCompare(b.label));
85
90
  return enumValues;
86
91
  }
@@ -110,6 +115,14 @@ var AUDIO_EXTENSIONS = [
110
115
  ];
111
116
  var VIDEO_EXTENSIONS = [".avi", ".mp4"];
112
117
  var 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])?)*$/;
118
+ var EMAIL_SHAPE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
119
+ function isAbsoluteUrl(value) {
120
+ try {
121
+ return Boolean(new URL(value).protocol);
122
+ } catch {
123
+ return false;
124
+ }
125
+ }
113
126
  function buildStringProperty({ name, totalDocsCount, valuesResult }) {
114
127
  let stringProperty = {
115
128
  name: name ?? "",
@@ -119,9 +132,11 @@ function buildStringProperty({ name, totalDocsCount, valuesResult }) {
119
132
  const totalEntriesCount = valuesResult.values.length;
120
133
  const totalValues = Array.from(valuesResult.valuesCount.keys()).length;
121
134
  const config = {};
122
- const probablyAURL = valuesResult.values.filter((value) => typeof value === "string" && value.toString().startsWith("http")).length > totalDocsCount / 3 * 2;
135
+ const nonBlank = valuesResult.values.filter((value) => value !== "" && value !== null && value !== void 0);
136
+ const everyValue = (test) => nonBlank.length > 0 && nonBlank.every((value) => typeof value === "string" && test(value));
137
+ const probablyAURL = everyValue((value) => value.startsWith("http") && isAbsoluteUrl(value));
123
138
  if (probablyAURL) config.url = true;
124
- const probablyAnEmail = valuesResult.values.filter((value) => typeof value === "string" && emailRegEx.test(value)).length > totalDocsCount / 3 * 2;
139
+ const probablyAnEmail = everyValue((value) => emailRegEx.test(value) && EMAIL_SHAPE.test(value));
125
140
  if (probablyAnEmail) config.email = true;
126
141
  const probablyUserIds = valuesResult.values.filter((value) => typeof value === "string" && value.length === 28 && !value.includes(" ")).length > totalDocsCount / 3 * 2;
127
142
  if (!probablyAnEmail && !probablyAURL && !probablyUserIds && !probablyAURL && totalValues < totalEntriesCount / 3) {
@@ -228,12 +243,12 @@ function readNumberExactly(text) {
228
243
  function ownEntry(record, key) {
229
244
  return Object.hasOwn(record, key) ? record[key] : void 0;
230
245
  }
231
- async function buildEntityPropertiesFromData(data, getType) {
246
+ async function buildEntityPropertiesFromData(data, getType, options = {}) {
232
247
  const typesCount = {};
233
248
  const valuesCount = {};
234
249
  if (data) data.forEach((entry) => {
235
250
  if (entry) Object.entries(entry).forEach(([key, value]) => {
236
- if (key.startsWith("_")) return;
251
+ if (options.ignoreKey?.(key)) return;
237
252
  increaseMapTypeCount(typesCount, key, value, getType);
238
253
  increaseValuesCount(valuesCount, key, value, getType);
239
254
  });
@@ -326,7 +341,6 @@ function isBlank(value) {
326
341
  return value === null || value === void 0 || value === "";
327
342
  }
328
343
  function increaseMapTypeCount(typesCountRecord, key, fieldValue, getType) {
329
- if (key.startsWith("_")) return;
330
344
  let typesCount = ownEntry(typesCountRecord, key);
331
345
  if (!typesCount) {
332
346
  typesCount = {};
@@ -335,7 +349,6 @@ function increaseMapTypeCount(typesCountRecord, key, fieldValue, getType) {
335
349
  if (!isBlank(fieldValue)) increaseTypeCount(getType(fieldValue), typesCount, fieldValue, getType);
336
350
  }
337
351
  function increaseValuesCount(typeValuesRecord, key, fieldValue, getType) {
338
- if (key.startsWith("_")) return;
339
352
  const type = getType(fieldValue);
340
353
  const valuesRecord = ownEntry(typeValuesRecord, key) ?? {
341
354
  values: [],
@@ -343,10 +356,10 @@ function increaseValuesCount(typeValuesRecord, key, fieldValue, getType) {
343
356
  };
344
357
  typeValuesRecord[key] = valuesRecord;
345
358
  if (type === "map") {
346
- let mapValuesRecord = valuesRecord.map;
359
+ let mapValuesRecord = valuesRecord.mapValues;
347
360
  if (!mapValuesRecord) {
348
361
  mapValuesRecord = {};
349
- valuesRecord.map = mapValuesRecord;
362
+ valuesRecord.mapValues = mapValuesRecord;
350
363
  }
351
364
  if (fieldValue) Object.entries(fieldValue).forEach(([subKey, value]) => increaseValuesCount(mapValuesRecord, subKey, value, getType));
352
365
  } else if (type === "array") {
@@ -447,7 +460,8 @@ function buildPropertyFromCount(key, totalDocsCount, mostProbableType, typesCoun
447
460
  totalDocsCount,
448
461
  valuesResult
449
462
  };
450
- if (mostProbableType === "string") result = buildStringProperty(propertyProps);
463
+ if ((mostProbableType === "number" || mostProbableType === "string") && isEpochColumn(key, valuesResult?.values)) result = { type: "date" };
464
+ else if (mostProbableType === "string") result = buildStringProperty(propertyProps);
451
465
  else if (mostProbableType === "reference") result = buildReferenceProperty(propertyProps);
452
466
  else result = { type: mostProbableType };
453
467
  if (title) result.name = title;
@@ -456,6 +470,34 @@ function buildPropertyFromCount(key, totalDocsCount, mostProbableType, typesCoun
456
470
  }
457
471
  return result;
458
472
  }
473
+ /** The words that end a column name holding a moment: `created_at`, `updatedOn`, `birth_date`, `timestamp`. */
474
+ var MOMENT_NAME_ENDINGS = /* @__PURE__ */ new Set([
475
+ "at",
476
+ "on",
477
+ "date",
478
+ "time",
479
+ "datetime",
480
+ "timestamp",
481
+ "ts"
482
+ ]);
483
+ /** An epoch in seconds from 2001-09-09 to 2103, or in milliseconds over the same span. */
484
+ function isEpochNumber(value) {
485
+ return Number.isInteger(value) && (value >= 1e9 && value < 42e8 || value >= 0xe8d4a51000 && value < 42e11);
486
+ }
487
+ /**
488
+ * Whether a column holds epoch timestamps: its name ends in a word that says
489
+ * it holds a moment, and every value it has is a whole number of seconds or
490
+ * milliseconds since 1970 (written as a number, or as the digits a CSV
491
+ * writes) in the range a timestamp of this century falls in. Both, because
492
+ * either alone is an order number or a `response_time` in milliseconds. The
493
+ * import reads such a value into a date as soon as the column is one.
494
+ */
495
+ function isEpochColumn(key, values) {
496
+ const words = key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
497
+ if (words.length === 0 || !MOMENT_NAME_ENDINGS.has(words[words.length - 1])) return false;
498
+ const present = (values ?? []).filter((value) => !isBlank(value));
499
+ return present.length > 0 && present.every((value) => typeof value === "number" ? isEpochNumber(value) : typeof value === "string" && /^\d+$/.test(value.trim()) && isEpochNumber(Number(value.trim())));
500
+ }
459
501
  function buildPropertiesFromCount(totalDocsCount, typesCountRecord, valuesCountRecord) {
460
502
  const res = {};
461
503
  Object.entries(typesCountRecord).forEach(([key, typesCount]) => {
@@ -495,6 +537,7 @@ function inferTypeFromValue(value) {
495
537
  if (typeof value === "string") return "string";
496
538
  if (typeof value === "number") return "number";
497
539
  if (typeof value === "boolean") return "boolean";
540
+ if (value instanceof Date) return "date";
498
541
  if (Array.isArray(value)) return "array";
499
542
  if (typeof value === "object") return "map";
500
543
  return "string";
@@ -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/numbers.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/cms-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","/**\n * A double holds 15 significant decimal digits exactly; a 16th may already be\n * rounded (`9007199254740993` reads back as …992).\n */\nconst MAX_EXACT_SIGNIFICANT_DIGITS = 15;\n\n/**\n * JSON's number grammar: an optional minus, an integer part with no leading\n * zero, an optional fraction and an optional exponent. No `+`, no thousands\n * separator, no currency or percent sign, no hexadecimal, no `Infinity`.\n */\nconst NUMBER_SPELLING = /^-?(0|[1-9]\\d*)(\\.(\\d+))?([eE][+-]?\\d+)?$/;\n\n/**\n * The number a text spells, when it spells one that a number gives back with\n * nothing lost — otherwise `undefined`.\n *\n * This is the one answer to \"is this text a number\" for both the inference\n * that picks a column's type and the import that converts its cells, so the\n * two cannot disagree. It refuses what `Number()` would forgive and silently\n * change: a leading zero (`02134` is a zip code, `00123` a code — as numbers\n * they lose the zero), more significant digits than a double holds exactly (a\n * 20-digit SKU), and anything that is not a plain decimal (`1,234`, `$1.00`,\n * `12%`, `+15551234567`, `N/A`). Formatting a number does not carry —\n * `10.00` and `1e3` — is allowed: their value is exact.\n */\nexport function readNumberExactly(text: string): number | undefined {\n const trimmed = text.trim();\n const match = NUMBER_SPELLING.exec(trimmed);\n if (!match) return undefined;\n const integerPart = match[1];\n const fraction = (match[3] ?? \"\").replace(/0+$/, \"\");\n const significant = (integerPart + fraction).replace(/^0+/, \"\");\n if (significant.length > MAX_EXACT_SIGNIFICANT_DIGITS) return undefined;\n const value = Number(trimmed);\n // `1e400` overflows to Infinity and `1e-400` underflows to 0.\n if (!Number.isFinite(value) || (value === 0 && significant !== \"\")) return undefined;\n return value;\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 { readNumberExactly } from \"./numbers\";\nimport { DataType, EnumValues, Properties, Property, StringProperty, Vector } from \"@rebasepro/types\";\n\nexport type InferenceTypeBuilder = (value: unknown) => DataType;\n\n/**\n * The entry a count record holds for a column, if it holds one.\n *\n * The records are keyed by column names out of the data, and a plain object\n * answers `record[\"constructor\"]` or `record[\"toString\"]` with what it\n * inherits: the count was written onto the global `Object`, and the values\n * list was `Object.values`, so importing a file with a `constructor` column\n * threw. Only an own entry is one this record counted.\n */\nfunction ownEntry<T>(record: Record<string, T>, key: string): T | undefined {\n return Object.hasOwn(record, key) ? record[key] : undefined;\n}\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\n/**\n * A cell nobody filled in. A CSV reader hands over `\"\"` for it where a\n * spreadsheet reader hands over nothing, and the import writes neither — so it\n * is no value here either: it casts no vote for the column's type and does not\n * count towards `required` or an enum.\n */\nfunction isBlank(value: unknown): boolean {\n return value === null || value === undefined || value === \"\";\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 | undefined = ownEntry(typesCountRecord, key);\n if (!typesCount) {\n typesCount = {};\n typesCountRecord[key] = typesCount;\n }\n\n if (!isBlank(fieldValue)) {\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 const valuesRecord: {\n values: unknown[];\n valuesCount: Map<unknown, number>;\n map?: ValuesCountRecord;\n } = ownEntry(typeValuesRecord, key) ?? {\n values: [],\n valuesCount: new Map()\n };\n typeValuesRecord[key] = valuesRecord;\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 if (isBlank(value)) return;\n valuesRecord.values.push(value);\n valuesRecord.valuesCount.set(value, (valuesRecord.valuesCount.get(value) ?? 0) + 1);\n });\n }\n } else {\n if (!isBlank(fieldValue)) {\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\n/**\n * The types a cell can be converted between: the import turns any of them into\n * text without loss, and text back into one of them only when the text spells\n * it.\n */\nconst SCALAR_TYPES: ReadonlySet<string> = new Set([\"string\", \"number\", \"boolean\", \"date\"]);\n\n/**\n * A column's type, from the votes its values cast.\n *\n * When the votes mix scalar types — numbers beside text, booleans beside\n * numbers — the majority is not an answer: converting the minority to it\n * changes them (`02134` → 2134, a 20-digit SKU rounded, `N/A` → nothing,\n * 2 → `false`). Such a column is text, which holds every one of them as\n * written. The one exception is numbers beside text that spells numbers\n * exactly (`10.00`, `1e3`): that column is a number, and nothing is lost.\n *\n * @param values every non-blank value the votes were cast for, when known;\n * without them, a mix of numbers and text is text.\n */\nfunction getMostProbableType(typesCount: TypesCount, values?: unknown[]): DataType {\n const scalarTypes = Object.entries(typesCount)\n .filter(([type, count]) => SCALAR_TYPES.has(type) && Number(count) > 0)\n .map(([type]) => type);\n if (scalarTypes.length > 1) {\n const numbersBesideNumericText = scalarTypes.every(type => type === \"number\" || type === \"string\")\n && values !== undefined\n && values.every(value => typeof value !== \"string\" || readNumberExactly(value) !== undefined);\n return numbersBesideNumericText ? \"number\" : \"string\";\n }\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 // The values recorded for an array column are its items.\n const arrayMostProbableType = getMostProbableType(arrayTypesCount, valuesResult?.values);\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 valuesEntry = valuesCountRecord ? ownEntry(valuesCountRecord, key) : undefined;\n const mostProbableType = getMostProbableType(typesCount, valuesEntry?.values);\n res[key] = buildPropertyFromCount(\n key,\n totalDocsCount,\n mostProbableType,\n typesCount,\n valuesEntry\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 (isBlank(value)) return;\n increaseTypeCount(getType(value), typesCount, value, getType);\n });\n return getMostProbableType(typesCount, array.filter(value => !isBlank(value)));\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/numbers.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 // A number or a boolean in a text column is one of its options too (`10`\n // and `12` beside `S`, `M`, `L`): left out, the enum refused the rows it\n // was inferred from. Its id is its text, which is what the import writes.\n const seen = new Set<string>();\n const enumValues: Array<{ id: string, label: string }> = [];\n for (const value of values) {\n if (typeof value !== \"string\" && typeof value !== \"number\" && typeof value !== \"boolean\") continue;\n const id = String(value);\n if (seen.has(id)) continue;\n seen.add(id);\n enumValues.push({ id, label: typeof value === \"string\" ? unslugify(value) : id });\n }\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\n// The shape the server checks an `email` property against\n// (`write-validation.ts`): the regex above alone accepts `a@localhost`.\nconst EMAIL_SHAPE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\nfunction isAbsoluteUrl(value: string): boolean {\n try {\n return Boolean(new URL(value).protocol);\n } catch {\n return false;\n }\n}\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 // `url` and `email` are constraints a write is checked against, so\n // they hold only when every value the column has meets them: set at\n // two thirds, the import refused the rest of the rows it was inferred\n // from. A blank is no value.\n const nonBlank = valuesResult.values.filter((value) => value !== \"\" && value !== null && value !== undefined);\n const everyValue = (test: (value: string) => boolean) =>\n nonBlank.length > 0 && nonBlank.every((value) => typeof value === \"string\" && test(value));\n\n const probablyAURL = everyValue((value) => value.startsWith(\"http\") && isAbsoluteUrl(value));\n if (probablyAURL) {\n config.url = true;\n }\n\n const probablyAnEmail = everyValue((value) => emailRegEx.test(value) && EMAIL_SHAPE.test(value));\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/cms-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","/**\n * A double holds 15 significant decimal digits exactly; a 16th may already be\n * rounded (`9007199254740993` reads back as …992).\n */\nconst MAX_EXACT_SIGNIFICANT_DIGITS = 15;\n\n/**\n * JSON's number grammar: an optional minus, an integer part with no leading\n * zero, an optional fraction and an optional exponent. No `+`, no thousands\n * separator, no currency or percent sign, no hexadecimal, no `Infinity`.\n */\nconst NUMBER_SPELLING = /^-?(0|[1-9]\\d*)(\\.(\\d+))?([eE][+-]?\\d+)?$/;\n\n/**\n * The number a text spells, when it spells one that a number gives back with\n * nothing lost — otherwise `undefined`.\n *\n * This is the one answer to \"is this text a number\" for both the inference\n * that picks a column's type and the import that converts its cells, so the\n * two cannot disagree. It refuses what `Number()` would forgive and silently\n * change: a leading zero (`02134` is a zip code, `00123` a code — as numbers\n * they lose the zero), more significant digits than a double holds exactly (a\n * 20-digit SKU), and anything that is not a plain decimal (`1,234`, `$1.00`,\n * `12%`, `+15551234567`, `N/A`). Formatting a number does not carry —\n * `10.00` and `1e3` — is allowed: their value is exact.\n */\nexport function readNumberExactly(text: string): number | undefined {\n const trimmed = text.trim();\n const match = NUMBER_SPELLING.exec(trimmed);\n if (!match) return undefined;\n const integerPart = match[1];\n const fraction = (match[3] ?? \"\").replace(/0+$/, \"\");\n const significant = (integerPart + fraction).replace(/^0+/, \"\");\n if (significant.length > MAX_EXACT_SIGNIFICANT_DIGITS) return undefined;\n const value = Number(trimmed);\n // `1e400` overflows to Infinity and `1e-400` underflows to 0.\n if (!Number.isFinite(value) || (value === 0 && significant !== \"\")) return undefined;\n return value;\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 { readNumberExactly } from \"./numbers\";\nimport { DataType, EnumValues, Properties, Property, StringProperty, Vector } from \"@rebasepro/types\";\n\nexport type InferenceTypeBuilder = (value: unknown) => DataType;\n\n/**\n * The entry a count record holds for a column, if it holds one.\n *\n * The records are keyed by column names out of the data, and a plain object\n * answers `record[\"constructor\"]` or `record[\"toString\"]` with what it\n * inherits: the count was written onto the global `Object`, and the values\n * list was `Object.values`, so importing a file with a `constructor` column\n * threw. Only an own entry is one this record counted.\n */\nfunction ownEntry<T>(record: Record<string, T>, key: string): T | undefined {\n return Object.hasOwn(record, key) ? record[key] : undefined;\n}\n\nexport type BuildEntityPropertiesOptions = {\n /**\n * Top-level keys to leave out of the inferred properties — Firestore\n * metadata such as `__name__`, say. Nothing is left out by default: a\n * column is data, `_id` in a Mongo export included.\n */\n ignoreKey?: (key: string) => boolean;\n};\n\nexport async function buildEntityPropertiesFromData(\n data: object[],\n getType: InferenceTypeBuilder,\n options: BuildEntityPropertiesOptions = {}\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 (options.ignoreKey?.(key)) return;\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\n/**\n * A cell nobody filled in. A CSV reader hands over `\"\"` for it where a\n * spreadsheet reader hands over nothing, and the import writes neither — so it\n * is no value here either: it casts no vote for the column's type and does not\n * count towards `required` or an enum.\n */\nfunction isBlank(value: unknown): boolean {\n return value === null || value === undefined || value === \"\";\n}\n\nfunction increaseMapTypeCount(\n typesCountRecord: TypesCountRecord,\n key: string,\n fieldValue: unknown,\n getType: InferenceTypeBuilder\n) {\n let typesCount: TypesCount | undefined = ownEntry(typesCountRecord, key);\n if (!typesCount) {\n typesCount = {};\n typesCountRecord[key] = typesCount;\n }\n\n if (!isBlank(fieldValue)) {\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 const type = getType(fieldValue);\n\n const valuesRecord: ValuesCountEntry = ownEntry(typeValuesRecord, key) ?? {\n values: [],\n valuesCount: new Map()\n };\n typeValuesRecord[key] = valuesRecord;\n\n if (type === \"map\") {\n // Under `mapValues`, the field `buildPropertyFromCount` reads. Stored\n // under `map`, a nested field's values were never seen: no enum, no\n // `required`, and no number-from-text decision for it.\n let mapValuesRecord: ValuesCountRecord | undefined = valuesRecord.mapValues;\n if (!mapValuesRecord) {\n mapValuesRecord = {};\n valuesRecord.mapValues = 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 if (isBlank(value)) return;\n valuesRecord.values.push(value);\n valuesRecord.valuesCount.set(value, (valuesRecord.valuesCount.get(value) ?? 0) + 1);\n });\n }\n } else {\n if (!isBlank(fieldValue)) {\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\n/**\n * The types a cell can be converted between: the import turns any of them into\n * text without loss, and text back into one of them only when the text spells\n * it.\n */\nconst SCALAR_TYPES: ReadonlySet<string> = new Set([\"string\", \"number\", \"boolean\", \"date\"]);\n\n/**\n * A column's type, from the votes its values cast.\n *\n * When the votes mix scalar types — numbers beside text, booleans beside\n * numbers — the majority is not an answer: converting the minority to it\n * changes them (`02134` → 2134, a 20-digit SKU rounded, `N/A` → nothing,\n * 2 → `false`). Such a column is text, which holds every one of them as\n * written. The one exception is numbers beside text that spells numbers\n * exactly (`10.00`, `1e3`): that column is a number, and nothing is lost.\n *\n * @param values every non-blank value the votes were cast for, when known;\n * without them, a mix of numbers and text is text.\n */\nfunction getMostProbableType(typesCount: TypesCount, values?: unknown[]): DataType {\n const scalarTypes = Object.entries(typesCount)\n .filter(([type, count]) => SCALAR_TYPES.has(type) && Number(count) > 0)\n .map(([type]) => type);\n if (scalarTypes.length > 1) {\n const numbersBesideNumericText = scalarTypes.every(type => type === \"number\" || type === \"string\")\n && values !== undefined\n && values.every(value => typeof value !== \"string\" || readNumberExactly(value) !== undefined);\n return numbersBesideNumericText ? \"number\" : \"string\";\n }\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 // The values recorded for an array column are its items.\n const arrayMostProbableType = getMostProbableType(arrayTypesCount, valuesResult?.values);\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 === \"number\" || mostProbableType === \"string\") && isEpochColumn(key, valuesResult?.values)) {\n result = { type: \"date\" } as Property;\n } else 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\n/** The words that end a column name holding a moment: `created_at`, `updatedOn`, `birth_date`, `timestamp`. */\nconst MOMENT_NAME_ENDINGS = new Set([\"at\", \"on\", \"date\", \"time\", \"datetime\", \"timestamp\", \"ts\"]);\n\n/** An epoch in seconds from 2001-09-09 to 2103, or in milliseconds over the same span. */\nfunction isEpochNumber(value: number): boolean {\n return Number.isInteger(value)\n && ((value >= 1e9 && value < 4.2e9) || (value >= 1e12 && value < 4.2e12));\n}\n\n/**\n * Whether a column holds epoch timestamps: its name ends in a word that says\n * it holds a moment, and every value it has is a whole number of seconds or\n * milliseconds since 1970 (written as a number, or as the digits a CSV\n * writes) in the range a timestamp of this century falls in. Both, because\n * either alone is an order number or a `response_time` in milliseconds. The\n * import reads such a value into a date as soon as the column is one.\n */\nfunction isEpochColumn(key: string, values: unknown[] | undefined): boolean {\n const words = key.replace(/([a-z0-9])([A-Z])/g, \"$1 $2\").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);\n if (words.length === 0 || !MOMENT_NAME_ENDINGS.has(words[words.length - 1])) return false;\n const present = (values ?? []).filter(value => !isBlank(value));\n return present.length > 0 && present.every(value =>\n typeof value === \"number\"\n ? isEpochNumber(value)\n : typeof value === \"string\" && /^\\d+$/.test(value.trim()) && isEpochNumber(Number(value.trim())));\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 valuesEntry = valuesCountRecord ? ownEntry(valuesCountRecord, key) : undefined;\n const mostProbableType = getMostProbableType(typesCount, valuesEntry?.values);\n res[key] = buildPropertyFromCount(\n key,\n totalDocsCount,\n mostProbableType,\n typesCount,\n valuesEntry\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 (isBlank(value)) return;\n increaseTypeCount(getType(value), typesCount, value, getType);\n });\n return getMostProbableType(typesCount, array.filter(value => !isBlank(value)));\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 // A Date is an object, so it read as a map — while the CMS's own detector\n // (`getInferenceType`) calls it a date. The two defaults agree now.\n if (value instanceof Date) return \"date\";\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.24.0",
3
+ "version": "0.24.1-canary.g56af4a4",
4
4
  "description": "Infer a Rebase collection schema from existing data.",
5
5
  "keywords": [
6
6
  "rebase",
@@ -29,8 +29,8 @@
29
29
  "module": "./dist/index.es.js",
30
30
  "types": "dist/index.d.ts",
31
31
  "dependencies": {
32
- "@rebasepro/types": "0.24.0",
33
- "@rebasepro/utils": "0.24.0"
32
+ "@rebasepro/types": "0.24.1-canary.g56af4a4",
33
+ "@rebasepro/utils": "0.24.1-canary.g56af4a4"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/jest": "^30.0.0",