@rebasepro/common 0.10.0 → 0.10.1-canary.14e53ae

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.es.js CHANGED
@@ -1365,6 +1365,210 @@ function getEntityImagePreviewPropertyKey(collection) {
1365
1365
  }
1366
1366
  }
1367
1367
  //#endregion
1368
+ //#region src/util/title-property.ts
1369
+ /**
1370
+ * How good a property is as the human-readable title of an entity.
1371
+ * Higher wins; ties are broken by declaration order.
1372
+ * `DISQUALIFIED` means the property can never be a title.
1373
+ */
1374
+ var SCORE = {
1375
+ DISQUALIFIED: -1,
1376
+ /**
1377
+ * Points at another entity. Only readable once the target resolves, and it
1378
+ * renders as the raw key until then — last resort, for collections (like
1379
+ * junction tables) that hold no text of their own.
1380
+ */
1381
+ REFERENCE: 5,
1382
+ RELATION: 10,
1383
+ /** A user picker: resolves to a person's name, like a relation does */
1384
+ USER: 10,
1385
+ /** Long form text — readable, but a description, not a name */
1386
+ LONG_TEXT: 20,
1387
+ /** A closed set of values: shared by many rows, so it identifies nothing */
1388
+ ENUM: 30,
1389
+ /** Identifying, but PII and usually secondary to a name */
1390
+ EMAIL: 45,
1391
+ /** Any plain, short, free text field */
1392
+ PLAIN_TEXT: 60,
1393
+ /** Free text whose name says it holds the entity's label */
1394
+ NAMED: 100
1395
+ };
1396
+ /**
1397
+ * Keys whose name states outright that the property is the entity label.
1398
+ * This is a *bonus* on top of the structural rules — never a requirement, and
1399
+ * never the mechanism that keeps identifiers out of the title slot.
1400
+ */
1401
+ var TITLE_LIKE_KEYS = new Set([
1402
+ "name",
1403
+ "fullname",
1404
+ "displayname",
1405
+ "title",
1406
+ "label",
1407
+ "heading",
1408
+ "subject",
1409
+ "username",
1410
+ "nickname"
1411
+ ]);
1412
+ function normalizeKey(key) {
1413
+ return key.toLowerCase().replace(/[^a-z0-9]/g, "");
1414
+ }
1415
+ function isHidden$1(property) {
1416
+ return Boolean(property.ui?.hideFromCollection);
1417
+ }
1418
+ /**
1419
+ * File-storage backed content (single image, array of images, generic upload…).
1420
+ * Rendered by the dedicated image slot, so never a title.
1421
+ */
1422
+ function isStorageProperty(property) {
1423
+ if (property.type === "string" && (property.storage || property.ui?.url === "image")) return true;
1424
+ if (property.type === "array" && property.of && !Array.isArray(property.of)) {
1425
+ const inner = property.of;
1426
+ if (inner.type === "string" && (inner.storage || inner.ui?.url === "image")) return true;
1427
+ }
1428
+ return false;
1429
+ }
1430
+ /**
1431
+ * Every column on this collection that stores a foreign key: the `localKey` of
1432
+ * each owning relation (declared inline on a property or in `relations[]`), the
1433
+ * source column of a junction, and the key a many-to-many joins on.
1434
+ *
1435
+ * These hold another entity's id, so they must never fill the title slot even
1436
+ * though they are declared as plain strings.
1437
+ */
1438
+ function getForeignKeyColumns(collection) {
1439
+ const keys = /* @__PURE__ */ new Set();
1440
+ const addRelationKeys = (relation) => {
1441
+ if (relation.localKey) keys.add(relation.localKey);
1442
+ if (relation.through?.sourceColumn) keys.add(relation.through.sourceColumn);
1443
+ const firstStep = relation.joinPath?.[0];
1444
+ if (firstStep) {
1445
+ const from = firstStep.on?.from;
1446
+ for (const column of Array.isArray(from) ? from : [from]) if (column) keys.add(column);
1447
+ }
1448
+ };
1449
+ for (const relation of collection.relations ?? []) addRelationKeys(relation);
1450
+ for (const [key, propertyRaw] of Object.entries(collection.properties ?? {})) {
1451
+ const property = propertyRaw;
1452
+ if (!property || isPropertyBuilder(property) || property.type !== "relation") continue;
1453
+ addRelationKeys(property);
1454
+ if (property.relation) addRelationKeys(property.relation);
1455
+ if ((property.cardinality ?? "one") === "one" && (property.direction ?? "owning") === "owning") keys.add(generateForeignKeyName(property.relationName || key));
1456
+ }
1457
+ return keys;
1458
+ }
1459
+ /**
1460
+ * True when the property is declared to hold an opaque identifier rather than
1461
+ * something a person reads: a primary key, a foreign key, a UUID column, or a
1462
+ * picker bound to an auth user id. Decided from the property *schema*, never
1463
+ * from the key name.
1464
+ */
1465
+ function isIdentifierProperty(property, key, idKeys, foreignKeys) {
1466
+ if (idKeys.has(key)) return true;
1467
+ if (foreignKeys.has(key)) return true;
1468
+ if ("isId" in property && property.isId) return true;
1469
+ if (property.type === "string" && property.columnType === "uuid") return true;
1470
+ return false;
1471
+ }
1472
+ function scoreTitleCandidate(property, key, idKeys, foreignKeys) {
1473
+ if (isHidden$1(property)) return SCORE.DISQUALIFIED;
1474
+ if (isIdentifierProperty(property, key, idKeys, foreignKeys)) return SCORE.DISQUALIFIED;
1475
+ if (isStorageProperty(property)) return SCORE.DISQUALIFIED;
1476
+ if (property.type === "relation") return property.cardinality === "many" || property.relation?.cardinality === "many" ? SCORE.DISQUALIFIED : SCORE.RELATION;
1477
+ if (property.type === "reference") return SCORE.REFERENCE;
1478
+ if (property.type !== "string") return SCORE.DISQUALIFIED;
1479
+ if (property.userSelect) return SCORE.USER;
1480
+ if (property.enum) return SCORE.ENUM;
1481
+ if (property.ui?.multiline || property.ui?.markdown) return SCORE.LONG_TEXT;
1482
+ if (property.email) return SCORE.EMAIL;
1483
+ if (TITLE_LIKE_KEYS.has(normalizeKey(key))) return SCORE.NAMED;
1484
+ return SCORE.PLAIN_TEXT;
1485
+ }
1486
+ /**
1487
+ * All properties that could serve as the entity title, best first.
1488
+ *
1489
+ * Candidates are ranked from the property *schema* — identifiers (primary
1490
+ * keys, foreign keys, UUID columns, user pickers), images and hidden fields are
1491
+ * excluded structurally, so a collection whose id column is called something
1492
+ * other than `id` is handled the same as one where it isn't.
1493
+ *
1494
+ * When the collection declares `propertiesOrder` the developer has already
1495
+ * stated what comes first, so qualifying candidates keep that order; otherwise
1496
+ * they are ranked (a name beats a description beats a relation).
1497
+ *
1498
+ * @group Collections
1499
+ */
1500
+ function getTitlePropertyCandidates(collection) {
1501
+ if (!collection.properties) return [];
1502
+ if (collection.titleProperty && collection.properties[collection.titleProperty]) return [collection.titleProperty];
1503
+ const idKeys = new Set(getPrimaryKeys(collection));
1504
+ const foreignKeys = getForeignKeyColumns(collection);
1505
+ const explicitOrder = collection.propertiesOrder;
1506
+ const order = explicitOrder ?? Object.keys(collection.properties);
1507
+ const scored = [];
1508
+ order.forEach((key, index) => {
1509
+ const property = collection.properties[key];
1510
+ if (!property || isPropertyBuilder(property)) return;
1511
+ const score = scoreTitleCandidate(property, key, idKeys, foreignKeys);
1512
+ if (score === SCORE.DISQUALIFIED) return;
1513
+ scored.push({
1514
+ key,
1515
+ score,
1516
+ index
1517
+ });
1518
+ });
1519
+ if (!explicitOrder) scored.sort((a, b) => b.score - a.score || a.index - b.index);
1520
+ return scored.map((candidate) => candidate.key);
1521
+ }
1522
+ /**
1523
+ * The property that should fill the title slot for a collection, ignoring any
1524
+ * concrete values. Prefer {@link getTitlePropertyKeyForValues} when an entity
1525
+ * is at hand — it can skip candidates that happen to be empty or to hold an id.
1526
+ *
1527
+ * @group Collections
1528
+ */
1529
+ function getTitlePropertyKey(collection) {
1530
+ return getTitlePropertyCandidates(collection)[0];
1531
+ }
1532
+ var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1533
+ var LONG_HEX_REGEX = /^[0-9a-f]{24,}$/i;
1534
+ var CUID_REGEX = /^c[a-z0-9]{20,}$/i;
1535
+ /**
1536
+ * True when a value reads as a machine identifier (UUID, ObjectId/long hex,
1537
+ * cuid) rather than as something worth showing to a person.
1538
+ *
1539
+ * @group Collections
1540
+ */
1541
+ function looksLikeIdentifierValue(value) {
1542
+ if (typeof value !== "string") return false;
1543
+ const trimmed = value.trim();
1544
+ return UUID_REGEX.test(trimmed) || LONG_HEX_REGEX.test(trimmed) || CUID_REGEX.test(trimmed);
1545
+ }
1546
+ /**
1547
+ * The title property for a concrete entity: the best-ranked candidate that
1548
+ * actually carries a readable value. Candidates that are empty, that repeat the
1549
+ * entity id, or that hold an opaque identifier are skipped, so a free-text
1550
+ * column that happens to store UUIDs never ends up as the title.
1551
+ *
1552
+ * Returns the top-ranked candidate when none of them has a usable value, so
1553
+ * callers can still render a placeholder for that property.
1554
+ *
1555
+ * @group Collections
1556
+ */
1557
+ function getTitlePropertyKeyForValues(collection, values, entityId) {
1558
+ const candidates = getTitlePropertyCandidates(collection);
1559
+ if (!values || candidates.length === 0) return candidates[0];
1560
+ for (const key of candidates) {
1561
+ const value = values[key];
1562
+ if (value === void 0 || value === null || value === "") continue;
1563
+ if (typeof value === "string") {
1564
+ if (looksLikeIdentifierValue(value)) continue;
1565
+ if (entityId !== void 0 && value === String(entityId)) continue;
1566
+ }
1567
+ return key;
1568
+ }
1569
+ return candidates[0];
1570
+ }
1571
+ //#endregion
1368
1572
  //#region src/util/navigation_utils.ts
1369
1573
  function removeInitialAndTrailingSlashes(s) {
1370
1574
  return removeInitialSlash(removeTrailingSlash(s));
@@ -3825,6 +4029,6 @@ async function detectJunctionTables(executeSql) {
3825
4029
  return junctionTables;
3826
4030
  }
3827
4031
  //#endregion
3828
- export { COLLECTION_PATH_SEPARATOR, COMPOSITE_ID_SEPARATOR, CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, JUNCTION_TABLES_SQL, QueryBuilder, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, addInitialSlash, and, applyPropertyConditions, buildCollection, buildCompositeId, buildConditionContext, buildProperty, buildPropertyCallbacks, buildRebaseData, buildRoutedRebaseData, buildSdkData, canCreateEntity, canDeleteEntity, canEditEntity, canReadCollection, checkOperation, classifyTable, cond, createDataSourceRegistry, createRelationRef, createRelationRefWithData, defaultUsersCollection, defineCollection, deserializeFilter, deserializeLogicalCondition, deserializeOrderBy, detectJunctionTables, embedParentExpression, enumToObjectEntries, evaluateCondition, evaluatePolicy, findAnonymousGrants, findRelation, fullPathToCollectionSegments, getArrayResolvedProperties, getCollectionBySlugWithin, getCollectionPathsCombinations, getColumnName, getDeclaredPrimaryKeys, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEffectiveSecurityRules, getEntityImagePreviewPropertyKey, getEnumVarName, getInjectedSecurityRules, getJunctionCollectionConfig, getJunctionSecurityRules, getLabelOrConfigFrom, getLastSegment, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getPrimaryKeys, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, getTableVarName, isHidden, isPropertyBuilder, isReadOnly, isRebaseInternalTable, normalizeToEntityRelation, or, parseIdValues, policyToPostgres, registerConditionOperations, removeInitialAndTrailingSlashes, removeInitialSlash, removeTrailingSlash, resolveArrayProperties, resolveCollectionPathIds, resolveCollectionRelations, resolveDataSource, resolveDefaultSelectedView, resolveEnumValues, resolveFilterOperators, resolveJunctionSpecs, resolvePrimaryKeys, resolveProperties, resolveProperty, resolvePropertyEnum, resolvePropertyRelation, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, sanitizeData, sanitizeRelation, securityRuleToConditions, segmentsToStrippedPath, serializeFilter, serializeLogicalCondition, serializeOrderBy, sortProperties, sqlToPolicy, stripCollectionPath, traverseValueProperty, traverseValuesProperties, updateDateAutoValues, wrapAsEntityData, wrapAsSdkData };
4032
+ export { COLLECTION_PATH_SEPARATOR, COMPOSITE_ID_SEPARATOR, CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, JUNCTION_TABLES_SQL, QueryBuilder, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, addInitialSlash, and, applyPropertyConditions, buildCollection, buildCompositeId, buildConditionContext, buildProperty, buildPropertyCallbacks, buildRebaseData, buildRoutedRebaseData, buildSdkData, canCreateEntity, canDeleteEntity, canEditEntity, canReadCollection, checkOperation, classifyTable, cond, createDataSourceRegistry, createRelationRef, createRelationRefWithData, defaultUsersCollection, defineCollection, deserializeFilter, deserializeLogicalCondition, deserializeOrderBy, detectJunctionTables, embedParentExpression, enumToObjectEntries, evaluateCondition, evaluatePolicy, findAnonymousGrants, findRelation, fullPathToCollectionSegments, getArrayResolvedProperties, getCollectionBySlugWithin, getCollectionPathsCombinations, getColumnName, getDeclaredPrimaryKeys, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEffectiveSecurityRules, getEntityImagePreviewPropertyKey, getEnumVarName, getInjectedSecurityRules, getJunctionCollectionConfig, getJunctionSecurityRules, getLabelOrConfigFrom, getLastSegment, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getPrimaryKeys, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, getTableVarName, getTitlePropertyCandidates, getTitlePropertyKey, getTitlePropertyKeyForValues, isHidden, isPropertyBuilder, isReadOnly, isRebaseInternalTable, looksLikeIdentifierValue, normalizeToEntityRelation, or, parseIdValues, policyToPostgres, registerConditionOperations, removeInitialAndTrailingSlashes, removeInitialSlash, removeTrailingSlash, resolveArrayProperties, resolveCollectionPathIds, resolveCollectionRelations, resolveDataSource, resolveDefaultSelectedView, resolveEnumValues, resolveFilterOperators, resolveJunctionSpecs, resolvePrimaryKeys, resolveProperties, resolveProperty, resolvePropertyEnum, resolvePropertyRelation, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, sanitizeData, sanitizeRelation, securityRuleToConditions, segmentsToStrippedPath, serializeFilter, serializeLogicalCondition, serializeOrderBy, sortProperties, sqlToPolicy, stripCollectionPath, traverseValueProperty, traverseValuesProperties, updateDateAutoValues, wrapAsEntityData, wrapAsSdkData };
3829
4033
 
3830
4034
  //# sourceMappingURL=index.es.js.map