@rebasepro/common 0.9.1-canary.a57c262 → 0.9.1-canary.ad25bc0

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
@@ -1,5 +1,5 @@
1
1
  import { ALL_WHERE_FILTER_OPS, ANONYMOUS_USER_ID, CANONICAL_TO_REST, DEFAULT_DATA_SOURCE_KEY, EntityReference, EntityRelation, NULL_OPS, REST_TO_CANONICAL, getDataSourceCapabilities, getDeclaredSubcollections, isPostgresCollectionConfig, policy, toCanonicalOp } from "@rebasepro/types";
2
- import { deepClone, generateForeignKeyName, getIn, getPolicyOperations, isDefaultFieldConfigId, mergeDeep, randomString, removeFunctions, toSnakeCase } from "@rebasepro/utils";
2
+ import { deepClone, generateForeignKeyName, getIn, isDefaultFieldConfigId, mergeDeep, randomString, removeFunctions, toSnakeCase } from "@rebasepro/utils";
3
3
  import jsonLogic from "json-logic-js";
4
4
  import { deepEqual } from "fast-equals";
5
5
  //#region src/util/common.ts
@@ -300,17 +300,12 @@ function parseIdValues(idValue, primaryKeys) {
300
300
  /**
301
301
  * The primary keys of a collection, as declared by its properties.
302
302
  *
303
- * This is the only tier both sides can read, because it is the only one written
304
- * in the config: the postgres driver can also infer keys from the Drizzle
305
- * schema, which the browser never sees and is never sent — the admin compiles
306
- * the collection files into its own bundle rather than being served them. A key
307
- * that lives only in the Drizzle schema is therefore invisible here, and the
308
- * server says so at boot (`warnOnKeysTheAdminCannotResolve`) naming the `isId`
309
- * to add.
310
- *
311
- * Returns an empty array when a collection declares none, which callers must
312
- * treat as "not addressable" rather than defaulting to `id`: guessing a key
313
- * that is not the real one produces confidently wrong addresses.
303
+ * The driver can also infer keys from the Drizzle schema, which the browser
304
+ * cannot see — so the server normalizes what it resolved onto the config it
305
+ * serves (see `stampPrimaryKeys`), and this reads that back. Returns an empty
306
+ * array when a collection declares none, which callers must treat as "not
307
+ * addressable" rather than defaulting to `id`: guessing a key that is not the
308
+ * real one produces confidently wrong addresses.
314
309
  */
315
310
  function getDeclaredPrimaryKeys(collection) {
316
311
  const properties = collection.properties;
@@ -335,15 +330,9 @@ function getDeclaredPrimaryKeys(collection) {
335
330
  * The postgres driver tries, in order: properties marked `isId`; the primary
336
331
  * keys of the Drizzle schema; and finally a column literally named `id`. Only
337
332
  * the first and last are visible in a `CollectionConfig`, which is what both
338
- * sides share.
339
- *
340
- * So the two agree except on a collection that declares no `isId` and whose key
341
- * is known only to Drizzle. There, the driver reads the real key, and this
342
- * either resolves nothing (reported to the console by the caller) or — if the
343
- * table happens to have an unrelated `id` property — resolves `id`, which is
344
- * the wrong key and cannot be detected from here: the addresses look right and
345
- * route wrong. Only the config can settle it, so the server names both cases
346
- * at boot (`warnOnKeysTheAdminCannotResolve`) with the `isId` to add.
333
+ * sides share — so a collection whose key is *only* known to Drizzle, and is
334
+ * not named `id`, resolves to nothing here. That is reported rather than
335
+ * guessed: inventing a key produces addresses that look right and route wrong.
347
336
  */
348
337
  function resolvePrimaryKeys(collection) {
349
338
  const declared = getDeclaredPrimaryKeys(collection);
@@ -1802,7 +1791,7 @@ var buildPropertyCallbacks = (properties) => {
1802
1791
  * Opt out with `disableDefaultPolicies: true` to take full responsibility for
1803
1792
  * the collection's RLS.
1804
1793
  */
1805
- var SERVER_OR_ADMIN_EXPR$1 = policy.or(policy.serverContext(), policy.rolesOverlap(["admin"]));
1794
+ var SERVER_OR_ADMIN_EXPR = policy.or(policy.serverContext(), policy.rolesOverlap(["admin"]));
1806
1795
  /** Write operations that must be admin-gated by default on auth collections. */
1807
1796
  var DEFAULT_GUARDED_OPS = [
1808
1797
  "insert",
@@ -1815,7 +1804,7 @@ function isAuthCollection(collection) {
1815
1804
  return auth === true || typeof auth === "object" && auth?.enabled === true;
1816
1805
  }
1817
1806
  /** The property marked as the row id (falls back to `id`). */
1818
- function getIdPropertyName$1(collection) {
1807
+ function getIdPropertyName(collection) {
1819
1808
  for (const [name, prop] of Object.entries(collection.properties ?? {})) if (prop && typeof prop === "object" && "isId" in prop && prop.isId) return name;
1820
1809
  return "id";
1821
1810
  }
@@ -1835,26 +1824,26 @@ function getEffectiveSecurityRules(collection) {
1835
1824
  injected.push({
1836
1825
  name: `${tableName}_default_admin_read`,
1837
1826
  operations: ["select"],
1838
- condition: SERVER_OR_ADMIN_EXPR$1
1827
+ condition: SERVER_OR_ADMIN_EXPR
1839
1828
  });
1840
1829
  injected.push({
1841
1830
  name: `${tableName}_default_admin_write`,
1842
1831
  operations: [...DEFAULT_GUARDED_OPS],
1843
- condition: SERVER_OR_ADMIN_EXPR$1,
1844
- check: SERVER_OR_ADMIN_EXPR$1
1832
+ condition: SERVER_OR_ADMIN_EXPR,
1833
+ check: SERVER_OR_ADMIN_EXPR
1845
1834
  });
1846
1835
  if (isAuthCollection(collection)) {
1847
1836
  injected.push({
1848
1837
  name: `${tableName}_default_self_read`,
1849
1838
  operations: ["select"],
1850
- condition: policy.compare(policy.field(getIdPropertyName$1(collection)), "eq", policy.authUid())
1839
+ condition: policy.compare(policy.field(getIdPropertyName(collection)), "eq", policy.authUid())
1851
1840
  });
1852
1841
  injected.push({
1853
1842
  name: `${tableName}_require_admin_write`,
1854
1843
  mode: "restrictive",
1855
1844
  operations: [...DEFAULT_GUARDED_OPS],
1856
- condition: SERVER_OR_ADMIN_EXPR$1,
1857
- check: SERVER_OR_ADMIN_EXPR$1
1845
+ condition: SERVER_OR_ADMIN_EXPR,
1846
+ check: SERVER_OR_ADMIN_EXPR
1858
1847
  });
1859
1848
  }
1860
1849
  return [...explicit, ...injected];
@@ -1876,208 +1865,6 @@ function getInjectedSecurityRules(collection) {
1876
1865
  return getEffectiveSecurityRules(collection).slice(explicitCount);
1877
1866
  }
1878
1867
  //#endregion
1879
- //#region src/util/junction-policies.ts
1880
- var SERVER_OR_ADMIN_EXPR = policy.or(policy.serverContext(), policy.rolesOverlap(["admin"]));
1881
- /**
1882
- * Walk every collection's resolved relations and aggregate the junction tables
1883
- * they declare. Two collections may declare the same junction from opposite
1884
- * sides (posts→tags and tags→posts through `posts_tags`); both become
1885
- * `declaringSides` of one spec, so derived write grants consider both.
1886
- */
1887
- function resolveJunctionSpecs(collections) {
1888
- const specs = /* @__PURE__ */ new Map();
1889
- for (const collection of collections) {
1890
- const resolved = resolveCollectionRelations(collection);
1891
- for (const relation of Object.values(resolved)) {
1892
- if (!relation.through) continue;
1893
- const targetCollection = typeof relation.target === "function" ? relation.target() : void 0;
1894
- if (!targetCollection) continue;
1895
- const rawName = relation.through.table;
1896
- const table = rawName.includes(".") ? rawName.split(".").pop() : rawName;
1897
- const schema = "public";
1898
- const source = {
1899
- collection,
1900
- junctionColumn: relation.through.sourceColumn,
1901
- relation
1902
- };
1903
- const target = {
1904
- collection: targetCollection,
1905
- junctionColumn: relation.through.targetColumn
1906
- };
1907
- const existing = specs.get(table);
1908
- if (!existing) specs.set(table, {
1909
- table,
1910
- schema,
1911
- endpoints: [source, target],
1912
- declaringSides: [source]
1913
- });
1914
- else if (!existing.declaringSides.some((s) => s.collection === collection)) existing.declaringSides.push(source);
1915
- }
1916
- }
1917
- return specs;
1918
- }
1919
- /**
1920
- * A synthetic CollectionConfig standing in for the junction during policy
1921
- * compilation and naming. Its two FK columns carry explicit `columnName`s so
1922
- * `outerField` operands resolve to the exact columns the CREATE TABLE emitted,
1923
- * whatever their casing.
1924
- */
1925
- function getJunctionCollectionConfig(spec) {
1926
- const properties = {};
1927
- for (const endpoint of spec.endpoints) properties[endpoint.junctionColumn] = {
1928
- type: "string",
1929
- columnName: endpoint.junctionColumn
1930
- };
1931
- return {
1932
- slug: spec.table,
1933
- name: spec.table,
1934
- table: spec.table,
1935
- schema: spec.schema,
1936
- properties
1937
- };
1938
- }
1939
- /** The property marked as the row id (falls back to `id`). */
1940
- function getIdPropertyName(collection) {
1941
- for (const [name, prop] of Object.entries(collection.properties ?? {})) if (prop && typeof prop === "object" && "isId" in prop && prop.isId) return name;
1942
- return "id";
1943
- }
1944
- /** `EXISTS (SELECT 1 FROM endpoint WHERE endpoint.pk = junction.fk [AND extra])`. */
1945
- function existsEndpoint(endpoint, extra) {
1946
- const correlation = policy.compare(policy.field(getIdPropertyName(endpoint.collection)), "eq", policy.outerField(endpoint.junctionColumn));
1947
- return policy.existsIn({
1948
- collection: endpoint.collection.slug,
1949
- where: extra ? policy.and(correlation, extra) : correlation
1950
- });
1951
- }
1952
- /**
1953
- * Whether a parent-rule expression keeps its meaning when moved inside the
1954
- * junction's `EXISTS` subquery — and the re-scoped copy if it does.
1955
- *
1956
- * Returns `null` when the rule cannot be embedded faithfully: `raw` SQL
1957
- * anywhere (its `{column}` placeholders would bind to the junction), or an
1958
- * `outerField` inside a nested `existsIn` (it would bind to the junction while
1959
- * the author meant the parent, and no operand can express "the middle scope").
1960
- * Top-level `outerField`s are rewritten to `field`, which is what they meant.
1961
- */
1962
- function embedParentExpression(expr, depth = 0) {
1963
- switch (expr.kind) {
1964
- case "raw": return null;
1965
- case "and":
1966
- case "or": {
1967
- const parts = [];
1968
- for (const child of expr.operands) {
1969
- const embedded = embedParentExpression(child, depth);
1970
- if (!embedded) return null;
1971
- parts.push(embedded);
1972
- }
1973
- return expr.kind === "and" ? policy.and(...parts) : policy.or(...parts);
1974
- }
1975
- case "not": {
1976
- const embedded = embedParentExpression(expr.operand, depth);
1977
- return embedded ? policy.not(embedded) : null;
1978
- }
1979
- case "existsIn": {
1980
- const where = embedParentExpression(expr.where, depth + 1);
1981
- return where ? policy.existsIn({
1982
- collection: expr.collection,
1983
- where
1984
- }) : null;
1985
- }
1986
- case "compare": {
1987
- const left = embedOperand(expr.left, depth);
1988
- const right = embedOperand(expr.right, depth);
1989
- if (!left || !right) return null;
1990
- return {
1991
- ...expr,
1992
- left,
1993
- right
1994
- };
1995
- }
1996
- default: return expr;
1997
- }
1998
- }
1999
- /** Re-scope an operand, or return `null` if its binding cannot be preserved. */
2000
- function embedOperand(operand, depth) {
2001
- if (operand.kind === "outerField") {
2002
- if (depth === 0) return policy.field(operand.name);
2003
- return null;
2004
- }
2005
- return operand;
2006
- }
2007
- /** Does the rule cover the `update` operation? */
2008
- function coversUpdate(rule) {
2009
- return getPolicyOperations(rule).some((op) => op === "update" || op === "all");
2010
- }
2011
- /**
2012
- * The full derived policy set for a junction table: the locked server/admin
2013
- * baseline, the endpoint-visibility read grant, inherited write grants, and
2014
- * inherited restrictive gates. Returns `[]` when every declaring collection set
2015
- * `disableDefaultPolicies` — the junction is then the author's to police, and
2016
- * stays locked (RLS is still enabled) until they write policies for it.
2017
- */
2018
- function getJunctionSecurityRules(spec) {
2019
- if (spec.declaringSides.every((side) => side.collection.disableDefaultPolicies)) return [];
2020
- const rules = [];
2021
- rules.push({
2022
- name: `${spec.table}_default_admin_read`,
2023
- operations: ["select"],
2024
- condition: SERVER_OR_ADMIN_EXPR
2025
- });
2026
- rules.push({
2027
- name: `${spec.table}_default_admin_write`,
2028
- operations: [
2029
- "insert",
2030
- "update",
2031
- "delete"
2032
- ],
2033
- condition: SERVER_OR_ADMIN_EXPR,
2034
- check: SERVER_OR_ADMIN_EXPR
2035
- });
2036
- rules.push({
2037
- name: `${spec.table}_default_edge_read`,
2038
- operations: ["select"],
2039
- condition: policy.and(existsEndpoint(spec.endpoints[0]), existsEndpoint(spec.endpoints[1]))
2040
- });
2041
- const writeGrants = [];
2042
- for (const side of spec.declaringSides) {
2043
- const updateRules = ((isPostgresCollectionConfig(side.collection) ? side.collection.securityRules : void 0) ?? []).filter(coversUpdate);
2044
- const permissive = updateRules.filter((r) => r.mode !== "restrictive");
2045
- const restrictive = updateRules.filter((r) => r.mode === "restrictive");
2046
- const embeddedGates = [];
2047
- let gatesEmbeddable = true;
2048
- for (const gate of restrictive) {
2049
- const using = securityRuleToConditions(gate).usingExpr;
2050
- const embedded = using ? embedParentExpression(using) : null;
2051
- if (!embedded) {
2052
- gatesEmbeddable = false;
2053
- break;
2054
- }
2055
- embeddedGates.push(embedded);
2056
- }
2057
- if (!gatesEmbeddable) continue;
2058
- const grants = [];
2059
- for (const rule of permissive) {
2060
- const using = securityRuleToConditions(rule).usingExpr;
2061
- const embedded = using ? embedParentExpression(using) : null;
2062
- if (embedded) grants.push(embedded);
2063
- }
2064
- if (grants.length === 0) continue;
2065
- const condition = embeddedGates.length > 0 ? policy.and(policy.or(...grants), ...embeddedGates) : policy.or(...grants);
2066
- writeGrants.push(existsEndpoint(side, condition));
2067
- }
2068
- if (writeGrants.length > 0) rules.push({
2069
- name: `${spec.table}_default_edge_write`,
2070
- operations: [
2071
- "insert",
2072
- "update",
2073
- "delete"
2074
- ],
2075
- condition: writeGrants.length === 1 ? writeGrants[0] : policy.or(...writeGrants),
2076
- check: writeGrants.length === 1 ? writeGrants[0] : policy.or(...writeGrants)
2077
- });
2078
- return rules;
2079
- }
2080
- //#endregion
2081
1868
  //#region src/util/conditions.ts
2082
1869
  /**
2083
1870
  * Access a nested property from an object via dot notation.
@@ -3225,7 +3012,7 @@ function createPrimaryKeyResolver(options) {
3225
3012
  }
3226
3013
  if (!warned.has(slug)) {
3227
3014
  warned.add(slug);
3228
- console.warn(`[rebase] Collection '${slug}' declares no primary key, so its rows have no address: detail links, caching and relations will not work for it. Mark the key property with \`isId\` in its collection config — the server logs which column to mark at boot, if its schema knows the key.`);
3015
+ console.warn(`[rebase] Collection '${slug}' declares no primary key, so its rows have no address: detail links, caching and relations will not work for it. Mark the key property with \`isId\` in its collection config.`);
3229
3016
  }
3230
3017
  return keys;
3231
3018
  };
@@ -3823,6 +3610,6 @@ async function detectJunctionTables(executeSql) {
3823
3610
  return junctionTables;
3824
3611
  }
3825
3612
  //#endregion
3826
- 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 };
3613
+ 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, enumToObjectEntries, evaluateCondition, evaluatePolicy, findAnonymousGrants, findRelation, fullPathToCollectionSegments, getArrayResolvedProperties, getCollectionBySlugWithin, getCollectionPathsCombinations, getColumnName, getDeclaredPrimaryKeys, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEffectiveSecurityRules, getEntityImagePreviewPropertyKey, getEnumVarName, getInjectedSecurityRules, 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, resolvePrimaryKeys, resolveProperties, resolveProperty, resolvePropertyEnum, resolvePropertyRelation, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, sanitizeData, sanitizeRelation, securityRuleToConditions, segmentsToStrippedPath, serializeFilter, serializeLogicalCondition, serializeOrderBy, sortProperties, sqlToPolicy, stripCollectionPath, traverseValueProperty, traverseValuesProperties, updateDateAutoValues, wrapAsEntityData, wrapAsSdkData };
3827
3614
 
3828
3615
  //# sourceMappingURL=index.es.js.map