@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 +205 -1
- package/dist/index.es.js.map +1 -1
- package/dist/util/index.d.ts +1 -0
- package/dist/util/title-property.d.ts +43 -0
- package/dist/util/title-property.test.d.ts +1 -0
- package/package.json +3 -3
- package/src/util/index.ts +1 -0
- package/src/util/title-property.test.ts +343 -0
- package/src/util/title-property.ts +264 -0
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
|