@rebasepro/common 0.12.1-canary.g4e7bcbf → 0.12.1-canary.g6861540
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 +76 -28
- package/dist/index.es.js.map +1 -1
- package/dist/util/builders.d.ts +1 -20
- package/dist/util/email.d.ts +29 -0
- package/dist/util/index.d.ts +1 -0
- package/package.json +3 -3
- package/src/util/builders.ts +0 -50
- package/src/util/email.ts +32 -0
- package/src/util/index.ts +1 -0
- package/src/util/resolve-relation.ts +74 -9
package/dist/index.es.js
CHANGED
|
@@ -330,6 +330,39 @@ function resolvePrimaryKeys(collection) {
|
|
|
330
330
|
return [];
|
|
331
331
|
}
|
|
332
332
|
//#endregion
|
|
333
|
+
//#region src/util/email.ts
|
|
334
|
+
/**
|
|
335
|
+
* Email normalization — one implementation, because the database enforces it.
|
|
336
|
+
*
|
|
337
|
+
* `ensureAuthTablesExist` puts a `UNIQUE INDEX ON users (lower(email))` on the
|
|
338
|
+
* auth table. That index decides what "the same address" means, and it does not
|
|
339
|
+
* trim: to Postgres, `' foo@bar.com'` and `'foo@bar.com'` are two addresses and
|
|
340
|
+
* both may exist. So every write that reaches the column has to agree with
|
|
341
|
+
* every read, exactly, or the two disagree in the one direction that matters —
|
|
342
|
+
* a row that exists and cannot be found.
|
|
343
|
+
*
|
|
344
|
+
* That is not hypothetical. The lookup path trimmed and the admin create paths
|
|
345
|
+
* did not, so a user created through `POST /api/data/users` or
|
|
346
|
+
* `POST /api/auth/admin/users` with a stray space was stored untrimmed,
|
|
347
|
+
* survived the unique index alongside the real address, and was unreachable by
|
|
348
|
+
* login forever after. The HTTP auth routes were unaffected only because Zod's
|
|
349
|
+
* `.email()` happens to reject surrounding whitespace — a guard on a different
|
|
350
|
+
* layer, for a different reason, that the admin paths do not sit behind.
|
|
351
|
+
*
|
|
352
|
+
* It lives in `common` because `server`, `server-postgres` and `server-mongo`
|
|
353
|
+
* all write this column and must agree exactly, and `common` is the only
|
|
354
|
+
* package all three already depend on.
|
|
355
|
+
*/
|
|
356
|
+
/**
|
|
357
|
+
* Canonical form of an email address: trimmed, lower-cased.
|
|
358
|
+
*
|
|
359
|
+
* Non-strings pass through untouched, so this is safe to apply to a value out
|
|
360
|
+
* of a partial update payload whose type is not known yet.
|
|
361
|
+
*/
|
|
362
|
+
function normalizeEmail(email) {
|
|
363
|
+
return typeof email === "string" ? email.trim().toLowerCase() : email;
|
|
364
|
+
}
|
|
365
|
+
//#endregion
|
|
333
366
|
//#region src/util/enums.ts
|
|
334
367
|
function enumToObjectEntries(enumValues) {
|
|
335
368
|
if (Array.isArray(enumValues)) return enumValues;
|
|
@@ -393,8 +426,7 @@ function fullPathToCollectionSegments(path) {
|
|
|
393
426
|
function resolveRelation(relation, sourceCollection, propertyKey) {
|
|
394
427
|
const target = relation.target;
|
|
395
428
|
if (typeof target !== "function") throw new Error(`Relation${relation.relationName ? ` '${relation.relationName}'` : ""} on '${sourceCollection.slug}' has no \`target\`. Give it a thunk: \`target: () => otherCollection\`.`);
|
|
396
|
-
const targetCollection = target
|
|
397
|
-
if (!targetCollection?.slug) throw new Error(`Relation${relation.relationName ? ` '${relation.relationName}'` : ""} on '${sourceCollection.slug}' has a \`target\` that did not resolve to a collection.`);
|
|
429
|
+
const targetCollection = callTarget(relation, sourceCollection, propertyKey, target);
|
|
398
430
|
const relationName = relation.relationName ?? propertyKey ?? toSnakeCase(targetCollection.slug);
|
|
399
431
|
const shared = {
|
|
400
432
|
relationName,
|
|
@@ -421,7 +453,8 @@ function resolveRelation(relation, sourceCollection, propertyKey) {
|
|
|
421
453
|
cardinality: "one",
|
|
422
454
|
writable: true,
|
|
423
455
|
shared: false,
|
|
424
|
-
foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName)
|
|
456
|
+
foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName),
|
|
457
|
+
sourceKey: relation.sourceKey
|
|
425
458
|
};
|
|
426
459
|
case "hasMany": return {
|
|
427
460
|
...shared,
|
|
@@ -429,7 +462,8 @@ function resolveRelation(relation, sourceCollection, propertyKey) {
|
|
|
429
462
|
cardinality: "many",
|
|
430
463
|
writable: true,
|
|
431
464
|
shared: false,
|
|
432
|
-
foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName)
|
|
465
|
+
foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName),
|
|
466
|
+
sourceKey: relation.sourceKey
|
|
433
467
|
};
|
|
434
468
|
case "manyToMany": {
|
|
435
469
|
const sourceTable = getTableName(sourceCollection);
|
|
@@ -458,6 +492,43 @@ function resolveRelation(relation, sourceCollection, propertyKey) {
|
|
|
458
492
|
default: throw new Error(`Unknown relation kind: ${JSON.stringify(relation)}`);
|
|
459
493
|
}
|
|
460
494
|
}
|
|
495
|
+
/** How this relation is addressed in an error message, before it has a resolved name. */
|
|
496
|
+
function describe(relation, sourceCollection, propertyKey) {
|
|
497
|
+
const name = relation.relationName ?? propertyKey;
|
|
498
|
+
return `Relation${name ? ` '${name}'` : ""} on '${sourceCollection.slug}'`;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Call the `target` thunk, and translate the two ways an import cycle breaks it
|
|
502
|
+
* into an error that names the cause.
|
|
503
|
+
*
|
|
504
|
+
* The thunk exists to defer the reference until every module has finished
|
|
505
|
+
* evaluating, and for a cycle that closes at import time it does. What it cannot
|
|
506
|
+
* defer is a cycle that leaves the binding permanently unusable, and there are
|
|
507
|
+
* two shapes of that:
|
|
508
|
+
*
|
|
509
|
+
* - **ESM/TDZ.** `const` and `class` bindings in a not-yet-evaluated module are
|
|
510
|
+
* in the temporal dead zone, so reading one throws `ReferenceError: x is not
|
|
511
|
+
* defined`. The stack points at the thunk — a one-line arrow function that is
|
|
512
|
+
* obviously fine — and says nothing about the cycle that made it throw.
|
|
513
|
+
* - **CJS interop.** The half-initialised module object has no `default` yet,
|
|
514
|
+
* the import resolves to `undefined`, and the thunk returns it without
|
|
515
|
+
* complaint. That one used to surface here as "did not resolve to a
|
|
516
|
+
* collection", which is true and unhelpful.
|
|
517
|
+
*
|
|
518
|
+
* Both mean the same thing, and the fix for both is the same: break the cycle,
|
|
519
|
+
* or move the relation into the collection that does not close it.
|
|
520
|
+
*/
|
|
521
|
+
function callTarget(relation, sourceCollection, propertyKey, target) {
|
|
522
|
+
let targetCollection;
|
|
523
|
+
try {
|
|
524
|
+
targetCollection = target();
|
|
525
|
+
} catch (error) {
|
|
526
|
+
if (error instanceof ReferenceError) throw new Error(`${describe(relation, sourceCollection, propertyKey)} targets a collection that is not initialized yet — almost always an import cycle between the two collection files. Break the cycle (move the shared piece into a third module, or import the target lazily) so the target's module finishes evaluating before the registry is built.`, { cause: error });
|
|
527
|
+
throw error;
|
|
528
|
+
}
|
|
529
|
+
if (!targetCollection?.slug) throw new Error(`${describe(relation, sourceCollection, propertyKey)} has a \`target\` that resolved to ${targetCollection === void 0 ? "`undefined`" : "something that is not a collection"}. ` + (targetCollection === void 0 ? "Under CommonJS interop an import cycle resolves the default import to `undefined`, so check whether this collection and its target import each other. Otherwise the thunk is returning the wrong value — it must return the collection itself, not a promise or a module." : "The thunk must return a collection config with a `slug`."));
|
|
530
|
+
return targetCollection;
|
|
531
|
+
}
|
|
461
532
|
//#endregion
|
|
462
533
|
//#region src/util/relations.ts
|
|
463
534
|
/**
|
|
@@ -1361,35 +1432,12 @@ function canDeleteEntity(collection, authContext, path, entity) {
|
|
|
1361
1432
|
//#endregion
|
|
1362
1433
|
//#region src/util/builders.ts
|
|
1363
1434
|
/**
|
|
1364
|
-
* @deprecated Use {@link defineCollection} instead — it infers property
|
|
1365
|
-
* types automatically (autocomplete on `titleProperty`, `sort`,
|
|
1366
|
-
* `propertiesOrder`, callbacks) without manual generics.
|
|
1367
|
-
* `buildCollection` is kept for FireCMS migration compatibility and will
|
|
1368
|
-
* be removed before 1.0.
|
|
1369
|
-
*
|
|
1370
|
-
* @group Builder
|
|
1371
|
-
*/
|
|
1372
|
-
function buildCollection(collection) {
|
|
1373
|
-
return collection;
|
|
1374
|
-
}
|
|
1375
|
-
/**
|
|
1376
1435
|
* Implementation — delegates to the correct overload at the type level.
|
|
1377
1436
|
* At runtime this is a plain identity function.
|
|
1378
1437
|
*/
|
|
1379
1438
|
function defineCollection(collection) {
|
|
1380
1439
|
return collection;
|
|
1381
1440
|
}
|
|
1382
|
-
/**
|
|
1383
|
-
* @deprecated Use plain typed property objects with {@link defineCollection}
|
|
1384
|
-
* instead — `defineCollection` infers property types automatically, making
|
|
1385
|
-
* this wrapper unnecessary. `buildProperty` is kept for FireCMS migration
|
|
1386
|
-
* compatibility and will be removed before 1.0.
|
|
1387
|
-
*
|
|
1388
|
-
* @group Builder
|
|
1389
|
-
*/
|
|
1390
|
-
function buildProperty(property) {
|
|
1391
|
-
return property;
|
|
1392
|
-
}
|
|
1393
1441
|
//#endregion
|
|
1394
1442
|
//#region src/util/storage.ts
|
|
1395
1443
|
/**
|
|
@@ -3830,6 +3878,6 @@ async function detectJunctionTables(executeSql) {
|
|
|
3830
3878
|
return junctionTables;
|
|
3831
3879
|
}
|
|
3832
3880
|
//#endregion
|
|
3833
|
-
export { COLLECTION_PATH_SEPARATOR, COMPOSITE_ID_SEPARATOR, CollectionRegistry, DEFAULT_FIND_ALL_MAX_ROWS, DEFAULT_MAX_PAGES, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, DEFAULT_PAGE_SIZE, DEFAULT_STRING_COLUMN_LENGTH, JUNCTION_TABLES_SQL, QueryBuilder, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, RebasePaginationError, and,
|
|
3881
|
+
export { COLLECTION_PATH_SEPARATOR, COMPOSITE_ID_SEPARATOR, CollectionRegistry, DEFAULT_FIND_ALL_MAX_ROWS, DEFAULT_MAX_PAGES, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, DEFAULT_PAGE_SIZE, DEFAULT_STRING_COLUMN_LENGTH, JUNCTION_TABLES_SQL, QueryBuilder, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, RebasePaginationError, and, buildCollectionFromTableMetadata, buildCompositeId, buildConditionContext, buildPropertyCallbacks, buildRebaseData, buildRoutedRebaseData, buildSdkData, canCreateEntity, canDeleteEntity, canEditEntity, canReadCollection, checkOperation, classifyTable, collectAllPages, cond, createDataSourceRegistry, createPaginationHelpers, createRelationRef, createRelationRefWithData, defaultUsersCollection, defineCollection, deserializeFilter, deserializeLogicalCondition, deserializeOrderBy, detectJunctionTables, embedParentExpression, enumToObjectEntries, evaluateCondition, evaluatePolicy, findAnonymousGrants, findRelation, fullPathToCollectionSegments, getArrayResolvedProperties, getColumnName, getDeclaredPrimaryKeys, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEffectiveSecurityRules, getEntityChildViews, getEnumVarName, getGeneratedPolicyNames, getInjectedSecurityRules, getJunctionCollectionConfig, getJunctionSecurityRules, getLabelOrConfigFrom, getPrimaryKeys, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, getTableVarName, isJunctionBackedRelation, isPropertyBuilder, isRebaseInternalTable, normalizeEmail, normalizeToEntityRelation, or, paginateFind, parseIdValues, policyToPostgres, registerConditionOperations, resolveArrayProperties, resolveCollectionRelations, resolveDataSource, resolveEnumValues, resolveJunctionSpecs, resolvePrimaryKeys, resolveProperties, resolveProperty, resolvePropertyEnum, resolveRelation, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, resolveStringColumnLength, sanitizeData, securityRuleToConditions, segmentsToStrippedPath, serializeFilter, serializeLogicalCondition, serializeOrderBy, sortProperties, sqlToPolicy, stripCollectionPath, traverseValueProperty, traverseValuesProperties, updateDateAutoValues, wrapAsEntityData, wrapAsSdkData };
|
|
3834
3882
|
|
|
3835
3883
|
//# sourceMappingURL=index.es.js.map
|