@rebasepro/inference 0.23.0 → 0.24.1-canary.g19596ef

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.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./collection_builder.js";
2
2
  export * from "./util.js";
3
3
  export * from "./strings.js";
4
+ export * from "./numbers.js";
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) {
@@ -179,6 +194,42 @@ function buildReferenceProperty({ name, totalDocsCount, valuesResult }) {
179
194
  };
180
195
  }
181
196
  //#endregion
197
+ //#region src/numbers.ts
198
+ /**
199
+ * A double holds 15 significant decimal digits exactly; a 16th may already be
200
+ * rounded (`9007199254740993` reads back as …992).
201
+ */
202
+ var MAX_EXACT_SIGNIFICANT_DIGITS = 15;
203
+ /**
204
+ * JSON's number grammar: an optional minus, an integer part with no leading
205
+ * zero, an optional fraction and an optional exponent. No `+`, no thousands
206
+ * separator, no currency or percent sign, no hexadecimal, no `Infinity`.
207
+ */
208
+ var NUMBER_SPELLING = /^-?(0|[1-9]\d*)(\.(\d+))?([eE][+-]?\d+)?$/;
209
+ /**
210
+ * The number a text spells, when it spells one that a number gives back with
211
+ * nothing lost — otherwise `undefined`.
212
+ *
213
+ * This is the one answer to "is this text a number" for both the inference
214
+ * that picks a column's type and the import that converts its cells, so the
215
+ * two cannot disagree. It refuses what `Number()` would forgive and silently
216
+ * change: a leading zero (`02134` is a zip code, `00123` a code — as numbers
217
+ * they lose the zero), more significant digits than a double holds exactly (a
218
+ * 20-digit SKU), and anything that is not a plain decimal (`1,234`, `$1.00`,
219
+ * `12%`, `+15551234567`, `N/A`). Formatting a number does not carry —
220
+ * `10.00` and `1e3` — is allowed: their value is exact.
221
+ */
222
+ function readNumberExactly(text) {
223
+ const trimmed = text.trim();
224
+ const match = NUMBER_SPELLING.exec(trimmed);
225
+ if (!match) return void 0;
226
+ const significant = (match[1] + (match[3] ?? "").replace(/0+$/, "")).replace(/^0+/, "");
227
+ if (significant.length > MAX_EXACT_SIGNIFICANT_DIGITS) return void 0;
228
+ const value = Number(trimmed);
229
+ if (!Number.isFinite(value) || value === 0 && significant !== "") return void 0;
230
+ return value;
231
+ }
232
+ //#endregion
182
233
  //#region src/collection_builder.ts
183
234
  /**
184
235
  * The entry a count record holds for a column, if it holds one.
@@ -192,12 +243,12 @@ function buildReferenceProperty({ name, totalDocsCount, valuesResult }) {
192
243
  function ownEntry(record, key) {
193
244
  return Object.hasOwn(record, key) ? record[key] : void 0;
194
245
  }
195
- async function buildEntityPropertiesFromData(data, getType) {
246
+ async function buildEntityPropertiesFromData(data, getType, options = {}) {
196
247
  const typesCount = {};
197
248
  const valuesCount = {};
198
249
  if (data) data.forEach((entry) => {
199
250
  if (entry) Object.entries(entry).forEach(([key, value]) => {
200
- if (key.startsWith("_")) return;
251
+ if (options.ignoreKey?.(key)) return;
201
252
  increaseMapTypeCount(typesCount, key, value, getType);
202
253
  increaseValuesCount(valuesCount, key, value, getType);
203
254
  });
@@ -280,17 +331,24 @@ function increaseTypeCount(type, typesCount, fieldValue, getType) {
280
331
  } else if (!typesCount[type]) typesCount[type] = 1;
281
332
  else typesCount[type] = Number(typesCount[type]) + 1;
282
333
  }
334
+ /**
335
+ * A cell nobody filled in. A CSV reader hands over `""` for it where a
336
+ * spreadsheet reader hands over nothing, and the import writes neither — so it
337
+ * is no value here either: it casts no vote for the column's type and does not
338
+ * count towards `required` or an enum.
339
+ */
340
+ function isBlank(value) {
341
+ return value === null || value === void 0 || value === "";
342
+ }
283
343
  function increaseMapTypeCount(typesCountRecord, key, fieldValue, getType) {
284
- if (key.startsWith("_")) return;
285
344
  let typesCount = ownEntry(typesCountRecord, key);
286
345
  if (!typesCount) {
287
346
  typesCount = {};
288
347
  typesCountRecord[key] = typesCount;
289
348
  }
290
- if (fieldValue != null) increaseTypeCount(getType(fieldValue), typesCount, fieldValue, getType);
349
+ if (!isBlank(fieldValue)) increaseTypeCount(getType(fieldValue), typesCount, fieldValue, getType);
291
350
  }
292
351
  function increaseValuesCount(typeValuesRecord, key, fieldValue, getType) {
293
- if (key.startsWith("_")) return;
294
352
  const type = getType(fieldValue);
295
353
  const valuesRecord = ownEntry(typeValuesRecord, key) ?? {
296
354
  values: [],
@@ -298,18 +356,19 @@ function increaseValuesCount(typeValuesRecord, key, fieldValue, getType) {
298
356
  };
299
357
  typeValuesRecord[key] = valuesRecord;
300
358
  if (type === "map") {
301
- let mapValuesRecord = valuesRecord.map;
359
+ let mapValuesRecord = valuesRecord.mapValues;
302
360
  if (!mapValuesRecord) {
303
361
  mapValuesRecord = {};
304
- valuesRecord.map = mapValuesRecord;
362
+ valuesRecord.mapValues = mapValuesRecord;
305
363
  }
306
364
  if (fieldValue) Object.entries(fieldValue).forEach(([subKey, value]) => increaseValuesCount(mapValuesRecord, subKey, value, getType));
307
365
  } else if (type === "array") {
308
366
  if (Array.isArray(fieldValue)) fieldValue.forEach((value) => {
367
+ if (isBlank(value)) return;
309
368
  valuesRecord.values.push(value);
310
369
  valuesRecord.valuesCount.set(value, (valuesRecord.valuesCount.get(value) ?? 0) + 1);
311
370
  });
312
- } else if (fieldValue !== null && fieldValue !== void 0) {
371
+ } else if (!isBlank(fieldValue)) {
313
372
  valuesRecord.values.push(fieldValue);
314
373
  valuesRecord.valuesCount.set(fieldValue, (valuesRecord.valuesCount.get(fieldValue) ?? 0) + 1);
315
374
  }
@@ -328,7 +387,33 @@ function getHighestTypesCount(typesCount) {
328
387
  function getHighestRecordCount(record) {
329
388
  return Object.entries(record).map(([key, typesCount]) => getHighestTypesCount(typesCount)).reduce((a, b) => Math.max(a, b), 0);
330
389
  }
331
- function getMostProbableType(typesCount) {
390
+ /**
391
+ * The types a cell can be converted between: the import turns any of them into
392
+ * text without loss, and text back into one of them only when the text spells
393
+ * it.
394
+ */
395
+ var SCALAR_TYPES = /* @__PURE__ */ new Set([
396
+ "string",
397
+ "number",
398
+ "boolean",
399
+ "date"
400
+ ]);
401
+ /**
402
+ * A column's type, from the votes its values cast.
403
+ *
404
+ * When the votes mix scalar types — numbers beside text, booleans beside
405
+ * numbers — the majority is not an answer: converting the minority to it
406
+ * changes them (`02134` → 2134, a 20-digit SKU rounded, `N/A` → nothing,
407
+ * 2 → `false`). Such a column is text, which holds every one of them as
408
+ * written. The one exception is numbers beside text that spells numbers
409
+ * exactly (`10.00`, `1e3`): that column is a number, and nothing is lost.
410
+ *
411
+ * @param values every non-blank value the votes were cast for, when known;
412
+ * without them, a mix of numbers and text is text.
413
+ */
414
+ function getMostProbableType(typesCount, values) {
415
+ const scalarTypes = Object.entries(typesCount).filter(([type, count]) => SCALAR_TYPES.has(type) && Number(count) > 0).map(([type]) => type);
416
+ if (scalarTypes.length > 1) return scalarTypes.every((type) => type === "number" || type === "string") && values !== void 0 && values.every((value) => typeof value !== "string" || readNumberExactly(value) !== void 0) ? "number" : "string";
332
417
  let highestCount = -1;
333
418
  let probableType = "string";
334
419
  Object.entries(typesCount).forEach(([type, count]) => {
@@ -362,7 +447,7 @@ function buildPropertyFromCount(key, totalDocsCount, mostProbableType, typesCoun
362
447
  };
363
448
  } else if (mostProbableType === "array") {
364
449
  const arrayTypesCount = typesCount.array;
365
- const of = buildPropertyFromCount(key, totalDocsCount, getMostProbableType(arrayTypesCount), arrayTypesCount, valuesResult);
450
+ const of = buildPropertyFromCount(key, totalDocsCount, getMostProbableType(arrayTypesCount, valuesResult?.values), arrayTypesCount, valuesResult);
366
451
  result = {
367
452
  type: "array",
368
453
  name: title ?? key ?? "",
@@ -375,7 +460,8 @@ function buildPropertyFromCount(key, totalDocsCount, mostProbableType, typesCoun
375
460
  totalDocsCount,
376
461
  valuesResult
377
462
  };
378
- 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);
379
465
  else if (mostProbableType === "reference") result = buildReferenceProperty(propertyProps);
380
466
  else result = { type: mostProbableType };
381
467
  if (title) result.name = title;
@@ -384,11 +470,40 @@ function buildPropertyFromCount(key, totalDocsCount, mostProbableType, typesCoun
384
470
  }
385
471
  return result;
386
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
+ }
387
501
  function buildPropertiesFromCount(totalDocsCount, typesCountRecord, valuesCountRecord) {
388
502
  const res = {};
389
503
  Object.entries(typesCountRecord).forEach(([key, typesCount]) => {
390
- const mostProbableType = getMostProbableType(typesCount);
391
- res[key] = buildPropertyFromCount(key, totalDocsCount, mostProbableType, typesCount, valuesCountRecord ? ownEntry(valuesCountRecord, key) : void 0);
504
+ const valuesEntry = valuesCountRecord ? ownEntry(valuesCountRecord, key) : void 0;
505
+ const mostProbableType = getMostProbableType(typesCount, valuesEntry?.values);
506
+ res[key] = buildPropertyFromCount(key, totalDocsCount, mostProbableType, typesCount, valuesEntry);
392
507
  });
393
508
  return res;
394
509
  }
@@ -403,10 +518,10 @@ function countMaxDocumentsUnder(typesCount) {
403
518
  function getMostProbableTypeInArray(array, getType) {
404
519
  const typesCount = {};
405
520
  array.forEach((value) => {
406
- if (value === null || value === void 0) return;
521
+ if (isBlank(value)) return;
407
522
  increaseTypeCount(getType(value), typesCount, value, getType);
408
523
  });
409
- return getMostProbableType(typesCount);
524
+ return getMostProbableType(typesCount, array.filter((value) => !isBlank(value)));
410
525
  }
411
526
  function checkTypesCountHighVariability(typesCount) {
412
527
  const maxCount = countMaxDocumentsUnder(typesCount);
@@ -422,11 +537,12 @@ function inferTypeFromValue(value) {
422
537
  if (typeof value === "string") return "string";
423
538
  if (typeof value === "number") return "number";
424
539
  if (typeof value === "boolean") return "boolean";
540
+ if (value instanceof Date) return "date";
425
541
  if (Array.isArray(value)) return "array";
426
542
  if (typeof value === "object") return "map";
427
543
  return "string";
428
544
  }
429
545
  //#endregion
430
- export { buildEntityPropertiesFromData, buildPropertiesOrder, buildPropertyFromData, extractEnumFromValues, findCommonInitialStringInPath, inferTypeFromValue, isObject, isPlainObject, looksLikeReference, mergeDeep, parseReferenceString, prettifyIdentifier, removeInitialAndTrailingSlashes, removeInitialSlash, removeTrailingSlash, resolveEnumValues, unslugify };
546
+ export { buildEntityPropertiesFromData, buildPropertiesOrder, buildPropertyFromData, extractEnumFromValues, findCommonInitialStringInPath, inferTypeFromValue, isObject, isPlainObject, looksLikeReference, mergeDeep, parseReferenceString, prettifyIdentifier, readNumberExactly, removeInitialAndTrailingSlashes, removeInitialSlash, removeTrailingSlash, resolveEnumValues, unslugify };
431
547
 
432
548
  //# sourceMappingURL=index.es.js.map
@@ -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/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","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\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\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 (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 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 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 ? ownEntry(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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"}
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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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The number a text spells, when it spells one that a number gives back with
3
+ * nothing lost — otherwise `undefined`.
4
+ *
5
+ * This is the one answer to "is this text a number" for both the inference
6
+ * that picks a column's type and the import that converts its cells, so the
7
+ * two cannot disagree. It refuses what `Number()` would forgive and silently
8
+ * change: a leading zero (`02134` is a zip code, `00123` a code — as numbers
9
+ * they lose the zero), more significant digits than a double holds exactly (a
10
+ * 20-digit SKU), and anything that is not a plain decimal (`1,234`, `$1.00`,
11
+ * `12%`, `+15551234567`, `N/A`). Formatting a number does not carry —
12
+ * `10.00` and `1e3` — is allowed: their value is exact.
13
+ */
14
+ export declare function readNumberExactly(text: string): number | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebasepro/inference",
3
- "version": "0.23.0",
3
+ "version": "0.24.1-canary.g19596ef",
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.23.0",
33
- "@rebasepro/utils": "0.23.0"
32
+ "@rebasepro/types": "0.24.1-canary.g19596ef",
33
+ "@rebasepro/utils": "0.24.1-canary.g19596ef"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/jest": "^30.0.0",