@rebasepro/common 0.28.2 → 0.29.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"relations.es.js","names":[],"sources":["../../src/util/relations.ts"],"sourcesContent":["import { CollectionConfig, isRelationalCollectionConfig, Property, ResolvedRelation, RelationProperty } from \"@rebasepro/types\";\nimport { toSnakeCase, toWireKey } from \"@rebasepro/utils\";\n\nimport { resolveRelation } from \"./resolve-relation\";\n\n/**\n * Whether the target rows are shared with other parents — a many-to-many, or a\n * multi-hop `via` chain.\n *\n * Decides what a write \"through\" the relation may touch: a shared target\n * belongs to every parent that links it, so the parent owns the *link* and not\n * the row. The backend enforces that (an unlink rather than a delete) and the\n * admin renders it (remove-from-parent rather than delete).\n *\n * Now a field on the resolved relation rather than a re-derivation, so both\n * sides read the same answer instead of each computing one.\n */\nexport function isJunctionBackedRelation(relation: ResolvedRelation): boolean {\n return relation.shared;\n}\n\n/** WeakMap cache — same collection instance always yields the same relation map. */\nconst _resolvedRelationsCache = new WeakMap<CollectionConfig, Record<string, ResolvedRelation>>();\n\n/**\n * Every relation a collection declares, keyed by the name it is addressed by.\n *\n * A relation reaches the map from either of two places — the collection's\n * `relations` array, or a `relation` property that declares one inline — and is\n * keyed by its resolved `relationName`, which is what a nested path segment,\n * an `include` key and an admin tab all match against.\n *\n * Resolution no longer swallows failures. It used to wrap each relation in a\n * `try/catch` that dropped anything it could not work out, so a\n * mis-declared relation silently vanished instead of being reported; with the\n * kind declared, the only remaining failure is a `target` that does not resolve,\n * which is worth hearing about.\n */\nexport function resolveCollectionRelations(\n collection: CollectionConfig\n): Record<string, ResolvedRelation> {\n const cached = _resolvedRelationsCache.get(collection);\n if (cached) return cached;\n\n if (!isRelationalCollectionConfig(collection)) return {};\n\n const relations: Record<string, ResolvedRelation> = {};\n\n for (const relation of collection.relations ?? []) {\n const resolved = resolveRelation(relation, collection);\n relations[resolved.relationName] = resolved;\n }\n\n // A property declaring a relation inline is registered under the property\n // key as well: the fetch layer hydrates the result back onto that key, and\n // it is the name the admin addresses the field by.\n for (const [propertyKey, property] of Object.entries(collection.properties ?? {})) {\n if ((property as Property)?.type !== \"relation\") continue;\n const declared = (property as RelationProperty).relation;\n if (!declared || relations[propertyKey]) continue;\n\n relations[propertyKey] = resolveRelation(declared, collection, propertyKey);\n }\n\n _resolvedRelationsCache.set(collection, relations);\n return relations;\n}\n\n/**\n * The `type: \"relation\"` property that declares a link, or `undefined` for one\n * that only exists in the collection's `relations` array.\n *\n * Both declaration sites end up in {@link resolveCollectionRelations}, and only\n * one of them has a property to carry field-level facts — `name`, `admin`, and\n * the one this exists for, `validation.required`.\n */\nexport function relationDeclaringProperty(\n collection: CollectionConfig,\n relation: ResolvedRelation\n): RelationProperty | undefined {\n const resolved = resolveCollectionRelations(collection);\n for (const [key, raw] of Object.entries(collection.properties ?? {})) {\n const prop = raw as Property | undefined;\n if (prop?.type !== \"relation\") continue;\n // A relation declared inline is keyed by the property; one declared in\n // `relations` is keyed by its name, which the property addresses.\n if (resolved[key] === relation) return prop as RelationProperty;\n const addressed = (prop as RelationProperty).relation?.relationName;\n if (addressed && findRelation(resolved, addressed) === relation) return prop as RelationProperty;\n }\n return undefined;\n}\n\n/**\n * Must every row of this collection point at a target through this link?\n *\n * Read from the declaring property's `validation.required` — the same key every\n * other field uses, and the only place it lives.\n *\n * `RelationBase` carried its own `validation.required` until 0.18, which made\n * this two questions rather than one. They were answered by different readers:\n * the Postgres DDL generator asked the property (so the foreign-key column was\n * `NOT NULL`) and the SDK type generator asked the relation (so the generated\n * `Insert` type made the field optional). A `create()` that left the relation\n * out therefore typechecked and then failed at the database with a not-null\n * violation, and the two `required`s had to be written twice, identically, for\n * the pair to agree.\n *\n * A relation with no declaring property — an entry in `relations` nothing\n * points at — is not required. There is no field to fill in.\n */\nexport function isRelationRequired(collection: CollectionConfig, relation: ResolvedRelation): boolean {\n return Boolean(relationDeclaringProperty(collection, relation)?.validation?.required);\n}\n\n/**\n * The path of the collection a relation property points at, derived from the\n * property alone.\n *\n * A preview holds a property and a value and no collection, so it cannot call\n * `resolveRelationProperty`. It does not need to: both forms that carry a\n * target — the stamped `resolvedRelation` and the inline `relation` — name it\n * directly. Only the third form, a relation declared by name in the\n * collection's `relations` array, is out of reach, and that one has no target\n * to read without the collection anyway.\n *\n * This is what lets a preview render a relation column that arrived as a bare\n * foreign key: the id says *which* row, the declared target says *which\n * collection*, and `RelationPreview` fetches the rest. Without it a scalar id\n * is indistinguishable from a value of the wrong type.\n */\nexport function getRelationTargetPath(property: RelationProperty): string | undefined {\n const stamped = property.resolvedRelation?.targetSlug;\n if (stamped) return stamped;\n\n const target = property.relation?.target;\n if (typeof target !== \"function\") return undefined;\n try {\n return target()?.slug;\n } catch (_e) {\n // A thunk reaching into a module that has not finished initialising:\n // there is no target to name yet, and a preview is not worth throwing over.\n return undefined;\n }\n}\n\n/**\n * The table a collection reads and writes.\n *\n * `table` when it is set, otherwise `toSnakeCase(slug)` — which is what made it\n * safe to drop `table` from the required fields on the config type: the runtime\n * had always derived it, and the type was demanding a value it did not need.\n *\n * The `||` chain is load-bearing. `toSnakeCase(undefined)` returns `\"\"`, not\n * `undefined`, so the previous `??` chain short-circuited on the empty string\n * and the name fallback could never run — a safety net that read like one and\n * caught nothing. It was unreachable while `slug` was required; it stops being\n * unreachable the moment anything constructs a config without one.\n */\nexport function getTableName(collection: CollectionConfig): string {\n const declared = isRelationalCollectionConfig(collection) ? collection.table : undefined;\n return declared || toSnakeCase(collection.slug) || toSnakeCase(collection.name);\n}\n\n/**\n * A JavaScript identifier: what a generated `export const <name> =` needs.\n *\n * Deliberately the same shape the two schema generators already define\n * privately — this is the third place that needed it, and the first two guard\n * property keys and member accesses while nothing guarded the variable name\n * itself.\n */\nconst JS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\n/**\n * The variable name a generated table is bound to.\n *\n * Camel-cases underscores, and then guarantees the result is a legal\n * identifier. It did only the first, so a table name that is legal in Postgres\n * and not in JavaScript produced a `schema.generated.ts` that does not parse:\n *\n * `2024_archive` → `export const 2024Archive = pgTable(…)`\n * \"An identifier or keyword cannot immediately follow\n * a numeric literal\"\n * `reporting.events` → `export const reporting.events = pgTable(…)`\n * \"',' expected\"\n *\n * That file is imported by the server, so the failure is not one broken\n * collection — `rebase build` and `db push` fail at tsc for the whole\n * directory. And it is reachable from a documented flow: `rebase init` against\n * a database holding a table called `2024_archive` writes a collection file\n * that parses and a schema file that does not.\n *\n * **A no-op for every name that already worked**, which is what makes changing\n * a derived name safe here: the only inputs whose output changes are the ones\n * that produced a syntax error, and nothing can be running against those.\n * Separators become camel case rather than disappearing, so `reporting.events`\n * and `reporting_events` do not collide into one variable.\n */\nexport function getTableVarName(tableName: string): string {\n const camel = tableName.replace(/_([a-z])/g, (_, char: string) => char.toUpperCase());\n if (JS_IDENTIFIER.test(camel)) return camel;\n\n const sanitised = camel\n // Any other separator gets the same treatment `_` did, so two tables\n // differing only by separator keep differing.\n .replace(/[^A-Za-z0-9_$]+([A-Za-z0-9])?/g, (_, char?: string) =>\n (char ? char.toUpperCase() : \"\"))\n // A leading digit is legal in Postgres and not in JavaScript. Prefixed\n // rather than stripped, so `2024_archive` and `archive` stay distinct.\n .replace(/^([0-9])/, \"t$1\");\n\n return JS_IDENTIFIER.test(sanitised) ? sanitised : `t${sanitised}`;\n}\n\nexport function getEnumVarName(tableName: string, propName: string): string {\n const tableVar = getTableVarName(tableName);\n const propVar = propName.charAt(0).toUpperCase() + propName.slice(1);\n return `${tableVar}${propVar}`;\n}\n\nexport function getColumnName(fullColumn: string): string {\n return fullColumn.includes(\".\") ? fullColumn.split(\".\").pop()! : fullColumn;\n}\n\n/**\n * The field key a database column is served and addressed under.\n *\n * A column has two names and they are not the same name. `author_id` is what\n * Postgres stores; `authorId` is the key on the JSON row, the key in the\n * generated Drizzle table, and the key a caller writes in `where` and\n * `orderBy`. Every place that starts from a column and has to reach a row, a\n * Drizzle table or a payload goes through here, so there is one answer rather\n * than one per call site — the two that disagreed put `displayName` and\n * `author_id` on the same API.\n *\n * A declared property is the authority when there is one, because its key *is*\n * the wire name and `columnName` is the only thing that ever renamed the\n * column:\n *\n * 1. an explicit `columnName` equal to this column;\n * 2. a property whose key is literally the column (an author who wrote\n * `author_id:` meant `author_id` on the wire, and gets it);\n * 3. a property whose key snake-cases to the column, which is the default\n * mapping — `authorId` → `author_id`.\n *\n * With no property in the way — a foreign key derived from a relation, which\n * usually has none — the name is derived: {@link toWireKey}.\n *\n * Note the fallback is *not* the column verbatim. That was the old behaviour\n * and it is precisely the defect: a derived foreign key reached the wire under\n * its column name while every hand-authored field beside it was camelCase.\n */\nexport function fieldKeyForColumn(collection: CollectionConfig | undefined, column: string): string {\n const properties = collection?.properties;\n if (properties) {\n for (const [key, prop] of Object.entries(properties)) {\n const columnName = (prop as { columnName?: unknown } | undefined)?.columnName;\n if (typeof columnName === \"string\" && columnName === column) return key;\n }\n for (const key of Object.keys(properties)) {\n if (key === column) return key;\n if (toSnakeCase(key) === column) return key;\n }\n }\n return toWireKey(column);\n}\n\n/**\n * Look up a relation by key with forgiving normalization.\n *\n * `resolveCollectionRelations` stores each relation under a single canonical\n * key (no aliases). This helper tries the given key as-is, then falls back to\n * slug form (underscores → hyphens) and snake_case form (hyphens → underscores)\n * so that callers that receive a key from external input (URL path segments,\n * user-provided config, etc.) can still find the right entry.\n */\nexport function findRelation(\n resolvedRelations: Record<string, ResolvedRelation>,\n key: string\n): ResolvedRelation | undefined {\n // Exact match first\n if (resolvedRelations[key]) return resolvedRelations[key];\n\n // Try slug form (e.g. \"company_id\" → \"company-id\")\n const slugKey = key.replace(/_/g, \"-\");\n if (slugKey !== key && resolvedRelations[slugKey]) return resolvedRelations[slugKey];\n\n // Try snake_case form (e.g. \"company-id\" → \"company_id\")\n const snakeKey = key.replace(/-/g, \"_\");\n if (snakeKey !== key && resolvedRelations[snakeKey]) return resolvedRelations[snakeKey];\n\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,SAAgB,yBAAyB,UAAqC;CAC1E,OAAO,SAAS;AACpB;;AAGA,IAAM,0CAA0B,IAAI,QAA4D;;;;;;;;;;;;;;;AAgBhG,SAAgB,2BACZ,YACgC;CAChC,MAAM,SAAS,wBAAwB,IAAI,UAAU;CACrD,IAAI,QAAQ,OAAO;CAEnB,IAAI,CAAC,6BAA6B,UAAU,GAAG,OAAO,CAAC;CAEvD,MAAM,YAA8C,CAAC;CAErD,KAAK,MAAM,YAAY,WAAW,aAAa,CAAC,GAAG;EAC/C,MAAM,WAAW,gBAAgB,UAAU,UAAU;EACrD,UAAU,SAAS,gBAAgB;CACvC;CAKA,KAAK,MAAM,CAAC,aAAa,aAAa,OAAO,QAAQ,WAAW,cAAc,CAAC,CAAC,GAAG;EAC/E,IAAK,UAAuB,SAAS,YAAY;EACjD,MAAM,WAAY,SAA8B;EAChD,IAAI,CAAC,YAAY,UAAU,cAAc;EAEzC,UAAU,eAAe,gBAAgB,UAAU,YAAY,WAAW;CAC9E;CAEA,wBAAwB,IAAI,YAAY,SAAS;CACjD,OAAO;AACX;;;;;;;;;AAUA,SAAgB,0BACZ,YACA,UAC4B;CAC5B,MAAM,WAAW,2BAA2B,UAAU;CACtD,KAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,WAAW,cAAc,CAAC,CAAC,GAAG;EAClE,MAAM,OAAO;EACb,IAAI,MAAM,SAAS,YAAY;EAG/B,IAAI,SAAS,SAAS,UAAU,OAAO;EACvC,MAAM,YAAa,KAA0B,UAAU;EACvD,IAAI,aAAa,aAAa,UAAU,SAAS,MAAM,UAAU,OAAO;CAC5E;AAEJ;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,mBAAmB,YAA8B,UAAqC;CAClG,OAAO,QAAQ,0BAA0B,YAAY,QAAQ,CAAC,EAAE,YAAY,QAAQ;AACxF;;;;;;;;;;;;;;;;;AAkBA,SAAgB,sBAAsB,UAAgD;CAClF,MAAM,UAAU,SAAS,kBAAkB;CAC3C,IAAI,SAAS,OAAO;CAEpB,MAAM,SAAS,SAAS,UAAU;CAClC,IAAI,OAAO,WAAW,YAAY,OAAO,KAAA;CACzC,IAAI;EACA,OAAO,OAAO,CAAC,EAAE;CACrB,SAAS,IAAI;EAGT;CACJ;AACJ;;;;;;;;;;;;;;AAeA,SAAgB,aAAa,YAAsC;CAE/D,QADiB,6BAA6B,UAAU,IAAI,WAAW,QAAQ,KAAA,MAC5D,YAAY,WAAW,IAAI,KAAK,YAAY,WAAW,IAAI;AAClF;;;;;;;;;AAUA,IAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BtB,SAAgB,gBAAgB,WAA2B;CACvD,MAAM,QAAQ,UAAU,QAAQ,cAAc,GAAG,SAAiB,KAAK,YAAY,CAAC;CACpF,IAAI,cAAc,KAAK,KAAK,GAAG,OAAO;CAEtC,MAAM,YAAY,MAGb,QAAQ,mCAAmC,GAAG,SAC1C,OAAO,KAAK,YAAY,IAAI,EAAG,CAAC,CAGpC,QAAQ,YAAY,KAAK;CAE9B,OAAO,cAAc,KAAK,SAAS,IAAI,YAAY,IAAI;AAC3D;AAEA,SAAgB,eAAe,WAAmB,UAA0B;CAGxE,OAAO,GAFU,gBAAgB,SAEvB,IADM,SAAS,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,SAAS,MAAM,CAAC;AAEvE;AAEA,SAAgB,cAAc,YAA4B;CACtD,OAAO,WAAW,SAAS,GAAG,IAAI,WAAW,MAAM,GAAG,CAAC,CAAC,IAAI,IAAK;AACrE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,kBAAkB,YAA0C,QAAwB;CAChG,MAAM,aAAa,YAAY;CAC/B,IAAI,YAAY;EACZ,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,UAAU,GAAG;GAClD,MAAM,aAAc,MAA+C;GACnE,IAAI,OAAO,eAAe,YAAY,eAAe,QAAQ,OAAO;EACxE;EACA,KAAK,MAAM,OAAO,OAAO,KAAK,UAAU,GAAG;GACvC,IAAI,QAAQ,QAAQ,OAAO;GAC3B,IAAI,YAAY,GAAG,MAAM,QAAQ,OAAO;EAC5C;CACJ;CACA,OAAO,UAAU,MAAM;AAC3B;;;;;;;;;;AAWA,SAAgB,aACZ,mBACA,KAC4B;CAE5B,IAAI,kBAAkB,MAAM,OAAO,kBAAkB;CAGrD,MAAM,UAAU,IAAI,QAAQ,MAAM,GAAG;CACrC,IAAI,YAAY,OAAO,kBAAkB,UAAU,OAAO,kBAAkB;CAG5E,MAAM,WAAW,IAAI,QAAQ,MAAM,GAAG;CACtC,IAAI,aAAa,OAAO,kBAAkB,WAAW,OAAO,kBAAkB;AAGlF"}
1
+ {"version":3,"file":"relations.es.js","names":[],"sources":["../../src/util/relations.ts"],"sourcesContent":["import { CollectionConfig, isRelationalCollectionConfig, Property, ResolvedRelation, RelationProperty } from \"@rebasepro/types\";\nimport { toSnakeCase, toWireKey } from \"@rebasepro/utils\";\n\nimport { resolveRelation } from \"./resolve-relation\";\n\n/**\n * Whether the target rows are shared with other parents — a many-to-many, or a\n * multi-hop `via` chain.\n *\n * Decides what a write \"through\" the relation may touch: a shared target\n * belongs to every parent that links it, so the parent owns the *link* and not\n * the row. The backend enforces that (an unlink rather than a delete) and the\n * admin renders it (remove-from-parent rather than delete).\n *\n * Now a field on the resolved relation rather than a re-derivation, so both\n * sides read the same answer instead of each computing one.\n */\nexport function isJunctionBackedRelation(relation: ResolvedRelation): boolean {\n return relation.shared;\n}\n\n/** WeakMap cache — same collection instance always yields the same relation map. */\nconst _resolvedRelationsCache = new WeakMap<CollectionConfig, Record<string, ResolvedRelation>>();\n\n/**\n * Every relation a collection declares, keyed by the name it is addressed by.\n *\n * A relation reaches the map from either of two places — the collection's\n * `relations` array, or a `relation` property that declares one inline — and is\n * keyed by its resolved `relationName`, which is what a nested path segment,\n * an `include` key and an admin tab all match against.\n *\n * Resolution no longer swallows failures. It used to wrap each relation in a\n * `try/catch` that dropped anything it could not work out, so a\n * mis-declared relation silently vanished instead of being reported; with the\n * kind declared, the only remaining failure is a `target` that does not resolve,\n * which is worth hearing about.\n */\nexport function resolveCollectionRelations(\n collection: CollectionConfig\n): Record<string, ResolvedRelation> {\n const cached = _resolvedRelationsCache.get(collection);\n if (cached) return cached;\n\n if (!isRelationalCollectionConfig(collection)) return {};\n\n const relations: Record<string, ResolvedRelation> = {};\n\n for (const relation of collection.relations ?? []) {\n const resolved = resolveRelation(relation, collection);\n relations[resolved.relationName] = resolved;\n }\n\n // A property declaring a relation inline is registered under the property\n // key as well: the fetch layer hydrates the result back onto that key, and\n // it is the name the admin addresses the field by.\n for (const [propertyKey, property] of Object.entries(collection.properties ?? {})) {\n if ((property as Property)?.type !== \"relation\") continue;\n const declared = (property as RelationProperty).relation;\n if (!declared || relations[propertyKey]) continue;\n\n relations[propertyKey] = resolveRelation(declared, collection, propertyKey);\n }\n\n _resolvedRelationsCache.set(collection, relations);\n return relations;\n}\n\n/**\n * The `type: \"relation\"` property that declares a link, or `undefined` for one\n * that only exists in the collection's `relations` array.\n *\n * Both declaration sites end up in {@link resolveCollectionRelations}, and only\n * one of them has a property to carry field-level facts — `name`, `admin`, and\n * the one this exists for, `validation.required`.\n */\nexport function relationDeclaringProperty(\n collection: CollectionConfig,\n relation: ResolvedRelation\n): RelationProperty | undefined {\n const resolved = resolveCollectionRelations(collection);\n for (const [key, raw] of Object.entries(collection.properties ?? {})) {\n const prop = raw as Property | undefined;\n if (prop?.type !== \"relation\") continue;\n // A relation declared inline is keyed by the property; one declared in\n // `relations` is keyed by its name, which the property addresses.\n if (resolved[key] === relation) return prop as RelationProperty;\n const addressed = (prop as RelationProperty).relation?.relationName;\n if (addressed && findRelation(resolved, addressed) === relation) return prop as RelationProperty;\n }\n return undefined;\n}\n\n/**\n * Must every row of this collection point at a target through this link?\n *\n * Read from the declaring property's `validation.required` — the same key every\n * other field uses, and the only place it lives.\n *\n * `RelationBase` carried its own `validation.required` until 0.18, which made\n * this two questions rather than one. They were answered by different readers:\n * the Postgres DDL generator asked the property (so the foreign-key column was\n * `NOT NULL`) and the SDK type generator asked the relation (so the generated\n * `Insert` type made the field optional). A `create()` that left the relation\n * out therefore typechecked and then failed at the database with a not-null\n * violation, and the two `required`s had to be written twice, identically, for\n * the pair to agree.\n *\n * A relation with no declaring property — an entry in `relations` nothing\n * points at — is not required. There is no field to fill in.\n */\nexport function isRelationRequired(collection: CollectionConfig, relation: ResolvedRelation): boolean {\n return Boolean(relationDeclaringProperty(collection, relation)?.validation?.required);\n}\n\n/**\n * The path of the collection a relation property points at, derived from the\n * property alone.\n *\n * A preview holds a property and a value and no collection, so it cannot call\n * `resolveRelationProperty`. It does not need to: both forms that carry a\n * target — the stamped `resolvedRelation` and the inline `relation` — name it\n * directly. Only the third form, a relation declared by name in the\n * collection's `relations` array, is out of reach, and that one has no target\n * to read without the collection anyway.\n *\n * This is what lets a preview render a relation column that arrived as a bare\n * foreign key: the id says *which* row, the declared target says *which\n * collection*, and `RelationPreview` fetches the rest. Without it a scalar id\n * is indistinguishable from a value of the wrong type.\n */\nexport function getRelationTargetPath(property: RelationProperty): string | undefined {\n const stamped = property.resolvedRelation?.targetSlug;\n if (stamped) return stamped;\n\n const target = property.relation?.target;\n if (typeof target !== \"function\") return undefined;\n try {\n return target()?.slug;\n } catch (_e) {\n // A thunk reaching into a module that has not finished initialising:\n // there is no target to name yet, and a preview is not worth throwing over.\n return undefined;\n }\n}\n\n/**\n * The table a collection reads and writes.\n *\n * `table` when it is set, otherwise `toSnakeCase(slug)` — which is what made it\n * safe to drop `table` from the required fields on the config type: the runtime\n * had always derived it, and the type was demanding a value it did not need.\n *\n * The `||` chain is load-bearing. `toSnakeCase(undefined)` returns `\"\"`, not\n * `undefined`, so the previous `??` chain short-circuited on the empty string\n * and the name fallback could never run — a safety net that read like one and\n * caught nothing. It was unreachable while `slug` was required; it stops being\n * unreachable the moment anything constructs a config without one.\n */\nexport function getTableName(collection: CollectionConfig): string {\n const declared = isRelationalCollectionConfig(collection) ? collection.table : undefined;\n return declared || toSnakeCase(collection.slug) || toSnakeCase(collection.name);\n}\n\n/**\n * A JavaScript identifier: what a generated `export const <name> =` needs.\n *\n * Deliberately the same shape the two schema generators already define\n * privately — this is the third place that needed it, and the first two guard\n * property keys and member accesses while nothing guarded the variable name\n * itself.\n */\nconst JS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\n/**\n * The variable name a generated table is bound to.\n *\n * Camel-cases underscores, and then guarantees the result is a legal\n * identifier. It did only the first, so a table name that is legal in Postgres\n * and not in JavaScript produced a `schema.generated.ts` that does not parse:\n *\n * `2024_archive` → `export const 2024Archive = pgTable(…)`\n * \"An identifier or keyword cannot immediately follow\n * a numeric literal\"\n * `reporting.events` → `export const reporting.events = pgTable(…)`\n * \"',' expected\"\n *\n * That file is imported by the server, so the failure is not one broken\n * collection — `rebase build` and `db push` fail at tsc for the whole\n * directory. And it is reachable from a documented flow: `rebase init` against\n * a database holding a table called `2024_archive` writes a collection file\n * that parses and a schema file that does not.\n *\n * **A no-op for every name that already worked**, which is what makes changing\n * a derived name safe here: the only inputs whose output changes are the ones\n * that produced a syntax error, and nothing can be running against those.\n * Separators become camel case rather than disappearing, so `reporting.events`\n * and `reporting_events` do not collide into one variable.\n */\nexport function getTableVarName(tableName: string): string {\n const camel = tableName.replace(/_([a-z])/g, (_, char: string) => char.toUpperCase());\n if (JS_IDENTIFIER.test(camel)) return camel;\n\n const sanitised = camel\n // Any other separator gets the same treatment `_` did, so two tables\n // differing only by separator keep differing.\n .replace(/[^A-Za-z0-9_$]+([A-Za-z0-9])?/g, (_, char?: string) =>\n (char ? char.toUpperCase() : \"\"))\n // A leading digit is legal in Postgres and not in JavaScript. Prefixed\n // rather than stripped, so `2024_archive` and `archive` stay distinct.\n .replace(/^([0-9])/, \"t$1\");\n\n return JS_IDENTIFIER.test(sanitised) ? sanitised : `t${sanitised}`;\n}\n\nexport function getEnumVarName(tableName: string, propName: string): string {\n const tableVar = getTableVarName(tableName);\n const propVar = propName.charAt(0).toUpperCase() + propName.slice(1);\n return `${tableVar}${propVar}`;\n}\n\nexport function getColumnName(fullColumn: string): string {\n return fullColumn.includes(\".\") ? fullColumn.split(\".\").pop()! : fullColumn;\n}\n\n/**\n * The field key a database column is served and addressed under.\n *\n * A column has two names and they are not the same name. `author_id` is what\n * Postgres stores; `authorId` is the key on the JSON row, the key in the\n * generated Drizzle table, and the key a caller writes in `where` and\n * `orderBy`. Every place that starts from a column and has to reach a row, a\n * Drizzle table or a payload goes through here, so there is one answer rather\n * than one per call site — the two that disagreed put `displayName` and\n * `author_id` on the same API.\n *\n * A declared property is the authority when there is one, because its key *is*\n * the wire name and `columnName` is the only thing that ever renamed the\n * column:\n *\n * 1. an explicit `columnName` equal to this column;\n * 2. a property whose key is literally the column (an author who wrote\n * `author_id:` meant `author_id` on the wire, and gets it);\n * 3. a property whose key snake-cases to the column, which is the default\n * mapping — `authorId` → `author_id`.\n *\n * With no property in the way — a foreign key derived from a relation, which\n * usually has none — the name is derived: {@link toWireKey}.\n *\n * Note the fallback is *not* the column verbatim. That was the old behaviour\n * and it is precisely the defect: a derived foreign key reached the wire under\n * its column name while every hand-authored field beside it was camelCase.\n */\nexport function fieldKeyForColumn(collection: { properties?: Record<string, unknown> } | undefined, column: string): string {\n const properties = collection?.properties;\n if (properties) {\n for (const [key, prop] of Object.entries(properties)) {\n const columnName = (prop as { columnName?: unknown } | undefined)?.columnName;\n if (typeof columnName === \"string\" && columnName === column) return key;\n }\n for (const key of Object.keys(properties)) {\n if (key === column) return key;\n if (toSnakeCase(key) === column) return key;\n }\n }\n return toWireKey(column);\n}\n\n/**\n * Look up a relation by key with forgiving normalization.\n *\n * `resolveCollectionRelations` stores each relation under a single canonical\n * key (no aliases). This helper tries the given key as-is, then falls back to\n * slug form (underscores → hyphens) and snake_case form (hyphens → underscores)\n * so that callers that receive a key from external input (URL path segments,\n * user-provided config, etc.) can still find the right entry.\n */\nexport function findRelation(\n resolvedRelations: Record<string, ResolvedRelation>,\n key: string\n): ResolvedRelation | undefined {\n // Exact match first\n if (resolvedRelations[key]) return resolvedRelations[key];\n\n // Try slug form (e.g. \"company_id\" → \"company-id\")\n const slugKey = key.replace(/_/g, \"-\");\n if (slugKey !== key && resolvedRelations[slugKey]) return resolvedRelations[slugKey];\n\n // Try snake_case form (e.g. \"company-id\" → \"company_id\")\n const snakeKey = key.replace(/-/g, \"_\");\n if (snakeKey !== key && resolvedRelations[snakeKey]) return resolvedRelations[snakeKey];\n\n return undefined;\n}\n"],"mappings":"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"}
@@ -9,13 +9,15 @@ import { getIn, isDefaultFieldConfigId, mergeDeep } from "@rebasepro/utils";
9
9
  function resolveProperty(props) {
10
10
  const { property, ignoreMissingFields = false, ...rest } = props;
11
11
  let resultProperty;
12
+ const { entityId, ...builderProps } = rest;
12
13
  if (isPropertyBuilder(property)) {
13
14
  const path = rest.path;
14
15
  if (!path) resultProperty = property;
15
16
  else {
16
17
  const usedPropertyValue = rest.propertyKey ? getIn(rest.values, rest.propertyKey) : void 0;
17
18
  const dynamicProps = property.dynamicProps?.({
18
- ...rest,
19
+ ...builderProps,
20
+ id: entityId,
19
21
  path,
20
22
  propertyValue: usedPropertyValue,
21
23
  values: rest.values ?? {},
@@ -28,7 +30,8 @@ function resolveProperty(props) {
28
30
  const path = rest.path;
29
31
  const usedPropertyValue = rest.propertyKey ? getIn(rest.values, rest.propertyKey) : void 0;
30
32
  const dynamicPropsResult = resultProperty.dynamicProps({
31
- ...rest,
33
+ ...builderProps,
34
+ id: entityId,
32
35
  path,
33
36
  propertyValue: usedPropertyValue,
34
37
  values: rest.values ?? {},
@@ -1 +1 @@
1
- {"version":3,"file":"resolutions.es.js","names":[],"sources":["../../src/util/resolutions.ts"],"sourcesContent":["import {\n ArrayProperty,\n AuthState,\n CollectionConfig,\n EnumValueConfig,\n EnumValues,\n NumberProperty,\n Properties,\n Property,\n RelationProperty,\n ResolvedRelation,\n StringProperty,\n getDataSourceCapabilities,\n getDeclaredSubcollections,\n type EntityChildView\n} from \"@rebasepro/types\";\n\ntype PropertyConfig = { property: unknown; [key: string]: unknown };\nimport { isPropertyBuilder } from \"./entities\";\nimport { enumToObjectEntries } from \"./enums\";\nimport { DEFAULT_ONE_OF_TYPE } from \"./common\";\nimport { isDefaultFieldConfigId } from \"@rebasepro/utils\";\nimport { getIn, mergeDeep } from \"@rebasepro/utils\";\nimport { isJunctionBackedRelation, resolveCollectionRelations } from \"./relations\";\nimport { resolveRelation } from \"./resolve-relation\";\n\n/**\n * Resolve property builders, enums and arrays.\n */\n\nexport type ResolvePropertyProps<M extends Record<string, unknown> = Record<string, unknown>> = {\n property: Property\n propertyKey?: string,\n values?: Partial<M>,\n previousValues?: Partial<M>,\n path?: string,\n entityId?: string | number,\n index?: number,\n propertyConfigs?: Record<string, PropertyConfig>;\n ignoreMissingFields?: boolean;\n authController: AuthState;\n}\n\nexport function resolveProperty<M extends Record<string, unknown> = Record<string, unknown>>(props: ResolvePropertyProps<M>): Property | null {\n\n const {\n property,\n ignoreMissingFields = false,\n ...rest\n } = props;\n\n let resultProperty: Property;\n\n if (isPropertyBuilder(property)) {\n const path = rest.path;\n if (!path) {\n // When path is not available (e.g. in preview contexts), skip dynamic\n // resolution and use the property as-is without dynamic modifications.\n resultProperty = property as Property;\n } else {\n const usedPropertyValue = rest.propertyKey ? getIn(rest.values, rest.propertyKey) : undefined;\n const dynamicProps = property.dynamicProps?.({\n ...rest,\n path,\n propertyValue: usedPropertyValue,\n values: rest.values ?? {},\n previousValues: rest.previousValues ?? rest.values ?? {}\n });\n resultProperty = mergeDeep(property, dynamicProps ?? {});\n }\n } else {\n resultProperty = property as Property;\n }\n\n // Apply dynamic properties if they exist\n if (resultProperty?.dynamicProps && rest.path) {\n const path = rest.path;\n const usedPropertyValue = rest.propertyKey ? getIn(rest.values, rest.propertyKey) : undefined;\n const dynamicPropsResult = resultProperty.dynamicProps({\n ...rest,\n path,\n propertyValue: usedPropertyValue,\n values: rest.values ?? {},\n previousValues: rest.previousValues ?? rest.values ?? {}\n });\n\n if (dynamicPropsResult) {\n resultProperty = mergeDeep(resultProperty, dynamicPropsResult);\n }\n }\n\n let resolvedProperty: Property | null;\n\n if (resultProperty?.type === \"map\" && resultProperty.properties) {\n const properties = resolveProperties({\n ignoreMissingFields,\n ...rest,\n properties: resultProperty.properties\n });\n resolvedProperty = {\n ...resultProperty,\n properties\n } as Property;\n } else if (resultProperty?.type === \"array\") {\n resolvedProperty = resultProperty;\n } else if ((resultProperty?.type === \"string\" || resultProperty?.type === \"number\") && resultProperty.enum) {\n resolvedProperty = resolvePropertyEnum(resultProperty);\n } else {\n resolvedProperty = resultProperty;\n }\n\n if (resolvedProperty?.propertyConfig && !isDefaultFieldConfigId(resolvedProperty.propertyConfig)) {\n const cmsFields = rest.propertyConfigs;\n if (!cmsFields && !ignoreMissingFields) {\n throw Error(`Trying to resolve a property with key '${resolvedProperty.propertyConfig}' that inherits from a custom property config but no custom property configs were provided. Use the property 'propertyConfigs' in your app config to provide them`);\n }\n const customField: PropertyConfig | undefined = cmsFields?.[resolvedProperty.propertyConfig];\n if (!customField) {\n console.warn(`Trying to resolve a property with key '${resolvedProperty.propertyConfig}' that inherits from a custom property config but no custom property config with that key was found. Check the 'propertyConfigs' in your app config`)\n return resolvedProperty;\n }\n if (customField.property) {\n const restConfigProperty = { ...customField.property } as Record<string, unknown>;\n delete restConfigProperty.propertyConfig;\n const customFieldProperty = resolveProperty({\n property: { name: \"\",\n...restConfigProperty } as Property,\n ignoreMissingFields,\n ...rest\n });\n if (customFieldProperty) {\n resolvedProperty = mergeDeep(customFieldProperty, resolvedProperty);\n }\n }\n\n }\n\n return resolvedProperty;\n}\n\n/**\n * The resolved relation a relation property refers to.\n *\n * Normalization stamps `resolvedRelation` onto the property, so this is usually\n * a field read. It falls back to resolving from the collection for properties\n * that never went through the registry — a preview, or a form rendered straight\n * from an authored config.\n */\nexport function resolveRelationProperty(\n property: RelationProperty,\n collection: CollectionConfig,\n propertyKey?: string\n): ResolvedRelation {\n if (property.resolvedRelation) return property.resolvedRelation;\n\n if (property.relation) {\n return resolveRelation(property.relation, collection, propertyKey);\n }\n\n const name = propertyKey ?? \"\";\n const declared = resolveCollectionRelations(collection)[name];\n if (!declared) {\n throw Error(\n `Relation property '${name || \"(unnamed)\"}' on '${collection.slug}' declares no \\`relation\\`, ` +\n \"and the collection has no relation of that name.\"\n );\n }\n return declared;\n}\n\n/**\n * Resolve enum aliases for a string or number property\n * @param property\n */\nexport function resolvePropertyEnum(property: StringProperty | NumberProperty): StringProperty | NumberProperty {\n if (typeof property.enum === \"object\") {\n return {\n ...property,\n enum: enumToObjectEntries(property.enum)?.filter((value) => value && (value.id || value.id === 0) && value.label) ?? []\n };\n }\n return property as StringProperty | NumberProperty;\n}\n\n/**\n * Resolve enums and arrays for properties\n * @param properties\n * @param value\n */\nexport function resolveProperties<M extends Record<string, unknown>>({\n propertyKey,\n properties,\n ignoreMissingFields,\n ...props\n}: {\n propertyKey?: string,\n properties: Properties,\n values?: Partial<M>,\n previousValues?: Partial<M>,\n path?: string,\n entityId?: string | number,\n index?: number,\n propertyConfigs?: Record<string, PropertyConfig>;\n ignoreMissingFields?: boolean;\n authController: AuthState;\n}): Properties {\n return Object.entries<Property>(properties as Record<string, Property>)\n .map(([key, property]) => {\n const childResolvedProperty = resolveProperty({\n propertyKey: propertyKey ? `${propertyKey}.${key}` : undefined,\n property: property,\n ignoreMissingFields,\n ...props\n });\n if (!childResolvedProperty) return {};\n return {\n [key]: childResolvedProperty\n };\n })\n .filter((a) => a !== null)\n .reduce((a, b) => ({ ...a,\n...b }), {}) as Properties;\n}\n\nexport function resolveArrayProperties<M>({\n propertyKey,\n property,\n ignoreMissingFields = false,\n ...props\n}: {\n propertyKey?: string,\n property: ArrayProperty,\n values?: Partial<M>,\n previousValues?: Partial<M>,\n path?: string,\n entityId?: string | number,\n index?: number,\n propertyConfigs?: Record<string, PropertyConfig>;\n ignoreMissingFields?: boolean;\n authController: AuthState;\n}): Property[] {\n const propertyValue = propertyKey ? getIn(props.values, propertyKey) : undefined;\n\n if (property.of) {\n if (Array.isArray(property.of)) {\n return property.of.map((p, index) => {\n return resolveProperty({\n propertyKey: `${propertyKey}.${index}`,\n property: p as Property,\n ignoreMissingFields,\n ...props,\n index\n });\n }) as Property[];\n } else {\n const of = property.of;\n const resolvedProperties = getArrayResolvedProperties({\n propertyValue,\n propertyKey,\n property,\n ignoreMissingFields,\n ...props\n });\n // Destructured to be *excluded* from `...rest`, not to be used —\n // see the comment below. Said explicitly so the discarded-value\n // ratchet does not carry a finding that is working as intended.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { values, previousValues, ...rest } = props;\n const ofProperty = resolveProperty({ // we don't want to pass the values of the parent entity\n property: of,\n ignoreMissingFields,\n ...rest\n });\n if (!ofProperty && !ignoreMissingFields)\n throw Error(\"When using a property builder as the 'of' prop of an ArrayProperty, you must return a valid child property\")\n return resolvedProperties;\n }\n } else if (property.oneOf) {\n const typeField = property.oneOf?.typeField ?? DEFAULT_ONE_OF_TYPE;\n const resolvedProperties: Property[] = Array.isArray(propertyValue)\n ? propertyValue.map((v, index) => {\n const type = v && v[typeField];\n const childProperty = property.oneOf?.properties[type];\n if (!type || !childProperty) return null;\n return resolveProperty({\n propertyKey: `${propertyKey}.${index}`,\n property: childProperty,\n ignoreMissingFields,\n ...props\n });\n }).filter(e => Boolean(e)) as Property[]\n : [];\n return resolvedProperties;\n } else if (!property.columnType) {\n // An array with neither `of`/`oneOf` nor a `columnType` describes no element\n // type, so nothing can be generated or rendered from it.\n //\n // The escape hatch used to be `ui.Field` — \"a custom component can render\n // anything\" — which made a *presentation* field decide whether a schema was\n // valid, in code the Postgres generator runs. `columnType` is the same escape\n // hatch stated as data: `columnType: \"text[]\"` says what the column holds,\n // which is what both the generator and the form actually need.\n throw Error(`The array property (${propertyKey}) needs to declare an 'of' or a 'oneOf' property, or a \\`columnType\\` such as \"text[]\"`);\n } else {\n return [];\n }\n\n}\n\nexport function getArrayResolvedProperties({\n propertyKey,\n propertyValue,\n property,\n ...props\n}: {\n propertyValue: unknown,\n propertyKey?: string,\n property: ArrayProperty,\n ignoreMissingFields: boolean,\n values?: object;\n previousValues?: object;\n path?: string;\n entityId?: string | number;\n index?: number;\n propertyConfigs?: Record<string, PropertyConfig>;\n authController: AuthState;\n}) {\n\n const of = property.of;\n if (!of)\n throw Error(\n `Trying to resolve an array property (${propertyKey}) without providing an 'of' property`\n )\n return Array.isArray(propertyValue)\n ? propertyValue.map((v: unknown, index: number) => {\n return resolveProperty({\n propertyKey: `${propertyKey}.${index}`,\n property: Array.isArray(of) ? of[index] : of,\n ...props,\n index\n });\n }).filter(e => Boolean(e)) as Property[]\n : [];\n}\n\n/**\n * An enum as `{ id, label }` entries, or `undefined` when it is not one.\n *\n * {@link enumToObjectEntries} is the reading: an array is a list of entries or\n * of plain values (`[\"draft\", \"published\"]`, whose ids are the values, not\n * their positions), and a map entry's id is its key even when its value is a\n * config that does not repeat it.\n */\nexport function resolveEnumValues(input: EnumValues | Array<string | number>): EnumValueConfig[] | undefined {\n if (typeof input !== \"object\" || input === null) return undefined;\n return enumToObjectEntries(input);\n}\n\n\n/**\n * The lists rendered inside an entity view of `collection` — its tabs.\n *\n * The single derivation. There used to be two that disagreed: this one, and a\n * copy in `CollectionRegistry.normalizeCollection` that stamped each child with\n * the *target collection's* slug instead of the relation key. Since the\n * registry ran first and cached its answer onto `childCollections`, its version\n * was the one that won, and the frontend addressed child listings by a segment\n * the backend could not resolve.\n *\n * Order of precedence:\n * 1. `childCollections` — the explicit escape hatch for custom drivers.\n * 2. `subcollections` on an engine that has real containment (Firestore).\n * 3. many-relations on an engine that has relations (SQL).\n */\nexport function getEntityChildViews<M extends Record<string, unknown> = Record<string, unknown>>(\n collection: CollectionConfig<M>\n): EntityChildView[] {\n const asSubcollections = (collections: CollectionConfig<Record<string, unknown>>[]): EntityChildView[] =>\n collections.filter(Boolean).map(child => ({\n key: child.slug,\n collection: child,\n source: { kind: \"subcollection\" as const }\n }));\n\n if (collection.childCollections) {\n return asSubcollections(collection.childCollections() ?? []);\n }\n\n const capabilities = getDataSourceCapabilities(collection.engine);\n\n const declaredSubcollections = getDeclaredSubcollections(collection);\n if (capabilities.supportsSubcollections && declaredSubcollections) {\n return asSubcollections(declaredSubcollections() ?? []);\n }\n\n if (!capabilities.supportsRelations) return [];\n\n const resolvedRelations = resolveCollectionRelations(collection);\n const views: EntityChildView[] = [];\n const seen = new Set<string>();\n\n // Keyed by the map key, not by `relationName`: the map key is what\n // `findRelation` matches a path segment against, so it is the only one that\n // addresses the same relation on both sides of the wire. The map registers\n // some relations twice — once canonically, once under the declaring\n // property key — so dedupe on the underlying relation.\n for (const [relationKey, relation] of Object.entries(resolvedRelations)) {\n if (relation.cardinality !== \"many\") continue;\n\n const identity = relation.relationName ?? relationKey;\n if (seen.has(identity)) continue;\n\n let target: CollectionConfig | undefined;\n try {\n target = relation.target();\n } catch {\n continue;\n }\n if (!target) continue;\n seen.add(identity);\n\n // A name given to the declaring property is the author naming the tab.\n // Only the tab: it is a plural (\"Tags\"), and one row of it is still\n // what the target calls one (\"Tag\").\n const declaringProperty = Object.entries((collection.properties ?? {}) as Record<string, Property>)\n .find(([propKey, p]) => p.type === \"relation\" && ((p as RelationProperty).relation?.relationName ?? propKey) === identity);\n const customName = declaringProperty?.[1]?.name;\n\n const base: CollectionConfig<Record<string, unknown>> = {\n ...target,\n slug: relationKey,\n ...(customName ? { name: customName } : {})\n } as CollectionConfig<Record<string, unknown>>;\n\n views.push({\n key: relationKey,\n collection: (relation.overrides ? mergeDeep(base, relation.overrides) : base) as CollectionConfig<Record<string, unknown>>,\n source: {\n kind: \"relation\",\n relationKey,\n mode: isJunctionBackedRelation(relation) ? \"linked\" : \"owned\",\n targetSlug: target.slug\n }\n });\n }\n\n return views;\n}\n\n/**\n * Each of `collection`'s tabs paired with the property that declared it, when a\n * property declared it: child view key → property key.\n *\n * A many-relation can only be declared as a property — that is the documented\n * and only mechanism — and {@link getEntityChildViews} promotes it to a tab. So\n * one declaration reaches the panel twice, and neither surface knew about the\n * other. The form rendered a relation picker beside the tab, and the collection\n * table rendered *two* columns under one heading: the relation's own column,\n * showing the child rows, and a jump-to-tab button carrying the same name.\n *\n * The pairing is what lets each surface decide which half is redundant, and it\n * has to be a pairing rather than two sets because the two keys differ whenever\n * a relation is named. The match is on the resolved `relationName` — the\n * identity `getEntityChildViews` itself dedupes on — so a relation declared in\n * `relations` and pointed at by a differently-named property is recognised too.\n *\n * A relation with no property of its own is absent here, which is the point: it\n * has exactly one surface already, and nothing to weigh it against.\n *\n * Only top-level properties: a relation nested inside a `map` gets no tab.\n */\nexport function getChildViewDeclaringProperties<M extends Record<string, unknown> = Record<string, unknown>>(\n collection: CollectionConfig<M>\n): Map<string, string> {\n const pairs = new Map<string, string>();\n\n const relationProperties = Object.entries((collection.properties ?? {}) as Record<string, Property>)\n .filter(([, property]) => property?.type === \"relation\");\n if (relationProperties.length === 0) return pairs;\n\n const relationViews = getEntityChildViews(collection)\n .filter(view => view.source.kind === \"relation\");\n if (relationViews.length === 0) return pairs;\n\n const resolvedRelations = resolveCollectionRelations(collection);\n const identityOf = (relationKey: string): string =>\n resolvedRelations[relationKey]?.relationName ?? relationKey;\n\n const declaringPropertyByIdentity = new Map<string, string>();\n for (const [propertyKey, property] of relationProperties) {\n const relation = (property as RelationProperty).resolvedRelation ?? resolvedRelations[propertyKey];\n // A to-one relation is a foreign key the author edits, never a tab. No\n // view will match it — the views here are many-relations only — but\n // reading the cardinality says so where someone is looking.\n if (relation?.cardinality !== \"many\") continue;\n const identity = relation.relationName ?? propertyKey;\n if (!declaringPropertyByIdentity.has(identity)) declaringPropertyByIdentity.set(identity, propertyKey);\n }\n\n for (const view of relationViews) {\n const propertyKey = declaringPropertyByIdentity.get(\n identityOf((view.source as { relationKey: string }).relationKey));\n if (propertyKey) pairs.set(view.key, propertyKey);\n }\n\n return pairs;\n}\n\n/**\n * The property keys of `collection` whose relation is already one of its tabs.\n *\n * What a form asks: the tab is the treatment for a list of child rows, so the\n * picker beside it is the redundant half. See\n * {@link getChildViewDeclaringProperties}.\n */\nexport function getChildViewRelationPropertyKeys<M extends Record<string, unknown> = Record<string, unknown>>(\n collection: CollectionConfig<M>\n): Set<string> {\n return new Set(getChildViewDeclaringProperties(collection).values());\n}\n\n/**\n * The child views of `collection` as bare collections.\n *\n * The flattened view of {@link getEntityChildViews}, for navigation code that\n * only needs to match a path segment against a slug. Anything that cares *what\n * kind* of list it is showing — chiefly the admin, which must not offer a\n * global delete on a shared row — should read the views instead.\n */\nexport function getSubcollections<M extends Record<string, unknown> = Record<string, unknown>>(collection: CollectionConfig<M>): CollectionConfig<Record<string, unknown>>[] {\n return getEntityChildViews(collection).map(view => view.collection);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"resolutions.es.js","names":[],"sources":["../../src/util/resolutions.ts"],"sourcesContent":["import {\n ArrayProperty,\n AuthState,\n CollectionConfig,\n EnumValueConfig,\n EnumValues,\n NumberProperty,\n Properties,\n Property,\n RelationProperty,\n ResolvedRelation,\n StringProperty,\n getDataSourceCapabilities,\n getDeclaredSubcollections,\n type EntityChildView\n} from \"@rebasepro/types\";\n\ntype PropertyConfig = { property: unknown; [key: string]: unknown };\nimport { isPropertyBuilder } from \"./entities\";\nimport { enumToObjectEntries } from \"./enums\";\nimport { DEFAULT_ONE_OF_TYPE } from \"./common\";\nimport { isDefaultFieldConfigId } from \"@rebasepro/utils\";\nimport { getIn, mergeDeep } from \"@rebasepro/utils\";\nimport { isJunctionBackedRelation, resolveCollectionRelations } from \"./relations\";\nimport { resolveRelation } from \"./resolve-relation\";\n\n/**\n * Resolve property builders, enums and arrays.\n */\n\nexport type ResolvePropertyProps<M extends Record<string, unknown> = Record<string, unknown>> = {\n property: Property\n propertyKey?: string,\n values?: Partial<M>,\n previousValues?: Partial<M>,\n path?: string,\n entityId?: string | number,\n index?: number,\n propertyConfigs?: Record<string, PropertyConfig>;\n ignoreMissingFields?: boolean;\n authController: AuthState;\n}\n\nexport function resolveProperty<M extends Record<string, unknown> = Record<string, unknown>>(props: ResolvePropertyProps<M>): Property | null {\n\n const {\n property,\n ignoreMissingFields = false,\n ...rest\n } = props;\n\n let resultProperty: Property;\n // What a `dynamicProps` builder is handed: the record's id as `id`.\n const { entityId, ...builderProps } = rest;\n\n if (isPropertyBuilder(property)) {\n const path = rest.path;\n if (!path) {\n // When path is not available (e.g. in preview contexts), skip dynamic\n // resolution and use the property as-is without dynamic modifications.\n resultProperty = property as Property;\n } else {\n const usedPropertyValue = rest.propertyKey ? getIn(rest.values, rest.propertyKey) : undefined;\n const dynamicProps = property.dynamicProps?.({\n ...builderProps,\n id: entityId,\n path,\n propertyValue: usedPropertyValue,\n values: rest.values ?? {},\n previousValues: rest.previousValues ?? rest.values ?? {}\n });\n resultProperty = mergeDeep(property, dynamicProps ?? {});\n }\n } else {\n resultProperty = property as Property;\n }\n\n // Apply dynamic properties if they exist\n if (resultProperty?.dynamicProps && rest.path) {\n const path = rest.path;\n const usedPropertyValue = rest.propertyKey ? getIn(rest.values, rest.propertyKey) : undefined;\n const dynamicPropsResult = resultProperty.dynamicProps({\n ...builderProps,\n id: entityId,\n path,\n propertyValue: usedPropertyValue,\n values: rest.values ?? {},\n previousValues: rest.previousValues ?? rest.values ?? {}\n });\n\n if (dynamicPropsResult) {\n resultProperty = mergeDeep(resultProperty, dynamicPropsResult);\n }\n }\n\n let resolvedProperty: Property | null;\n\n if (resultProperty?.type === \"map\" && resultProperty.properties) {\n const properties = resolveProperties({\n ignoreMissingFields,\n ...rest,\n properties: resultProperty.properties\n });\n resolvedProperty = {\n ...resultProperty,\n properties\n } as Property;\n } else if (resultProperty?.type === \"array\") {\n resolvedProperty = resultProperty;\n } else if ((resultProperty?.type === \"string\" || resultProperty?.type === \"number\") && resultProperty.enum) {\n resolvedProperty = resolvePropertyEnum(resultProperty);\n } else {\n resolvedProperty = resultProperty;\n }\n\n if (resolvedProperty?.propertyConfig && !isDefaultFieldConfigId(resolvedProperty.propertyConfig)) {\n const cmsFields = rest.propertyConfigs;\n if (!cmsFields && !ignoreMissingFields) {\n throw Error(`Trying to resolve a property with key '${resolvedProperty.propertyConfig}' that inherits from a custom property config but no custom property configs were provided. Use the property 'propertyConfigs' in your app config to provide them`);\n }\n const customField: PropertyConfig | undefined = cmsFields?.[resolvedProperty.propertyConfig];\n if (!customField) {\n console.warn(`Trying to resolve a property with key '${resolvedProperty.propertyConfig}' that inherits from a custom property config but no custom property config with that key was found. Check the 'propertyConfigs' in your app config`)\n return resolvedProperty;\n }\n if (customField.property) {\n const restConfigProperty = { ...customField.property } as Record<string, unknown>;\n delete restConfigProperty.propertyConfig;\n const customFieldProperty = resolveProperty({\n property: { name: \"\",\n...restConfigProperty } as Property,\n ignoreMissingFields,\n ...rest\n });\n if (customFieldProperty) {\n resolvedProperty = mergeDeep(customFieldProperty, resolvedProperty);\n }\n }\n\n }\n\n return resolvedProperty;\n}\n\n/**\n * The resolved relation a relation property refers to.\n *\n * Normalization stamps `resolvedRelation` onto the property, so this is usually\n * a field read. It falls back to resolving from the collection for properties\n * that never went through the registry — a preview, or a form rendered straight\n * from an authored config.\n */\nexport function resolveRelationProperty(\n property: RelationProperty,\n collection: CollectionConfig,\n propertyKey?: string\n): ResolvedRelation {\n if (property.resolvedRelation) return property.resolvedRelation;\n\n if (property.relation) {\n return resolveRelation(property.relation, collection, propertyKey);\n }\n\n const name = propertyKey ?? \"\";\n const declared = resolveCollectionRelations(collection)[name];\n if (!declared) {\n throw Error(\n `Relation property '${name || \"(unnamed)\"}' on '${collection.slug}' declares no \\`relation\\`, ` +\n \"and the collection has no relation of that name.\"\n );\n }\n return declared;\n}\n\n/**\n * Resolve enum aliases for a string or number property\n * @param property\n */\nexport function resolvePropertyEnum(property: StringProperty | NumberProperty): StringProperty | NumberProperty {\n if (typeof property.enum === \"object\") {\n return {\n ...property,\n enum: enumToObjectEntries(property.enum)?.filter((value) => value && (value.id || value.id === 0) && value.label) ?? []\n };\n }\n return property as StringProperty | NumberProperty;\n}\n\n/**\n * Resolve enums and arrays for properties\n * @param properties\n * @param value\n */\nexport function resolveProperties<M extends Record<string, unknown>>({\n propertyKey,\n properties,\n ignoreMissingFields,\n ...props\n}: {\n propertyKey?: string,\n properties: Properties,\n values?: Partial<M>,\n previousValues?: Partial<M>,\n path?: string,\n entityId?: string | number,\n index?: number,\n propertyConfigs?: Record<string, PropertyConfig>;\n ignoreMissingFields?: boolean;\n authController: AuthState;\n}): Properties {\n return Object.entries<Property>(properties as Record<string, Property>)\n .map(([key, property]) => {\n const childResolvedProperty = resolveProperty({\n propertyKey: propertyKey ? `${propertyKey}.${key}` : undefined,\n property: property,\n ignoreMissingFields,\n ...props\n });\n if (!childResolvedProperty) return {};\n return {\n [key]: childResolvedProperty\n };\n })\n .filter((a) => a !== null)\n .reduce((a, b) => ({ ...a,\n...b }), {}) as Properties;\n}\n\nexport function resolveArrayProperties<M>({\n propertyKey,\n property,\n ignoreMissingFields = false,\n ...props\n}: {\n propertyKey?: string,\n property: ArrayProperty,\n values?: Partial<M>,\n previousValues?: Partial<M>,\n path?: string,\n entityId?: string | number,\n index?: number,\n propertyConfigs?: Record<string, PropertyConfig>;\n ignoreMissingFields?: boolean;\n authController: AuthState;\n}): Property[] {\n const propertyValue = propertyKey ? getIn(props.values, propertyKey) : undefined;\n\n if (property.of) {\n if (Array.isArray(property.of)) {\n return property.of.map((p, index) => {\n return resolveProperty({\n propertyKey: `${propertyKey}.${index}`,\n property: p as Property,\n ignoreMissingFields,\n ...props,\n index\n });\n }) as Property[];\n } else {\n const of = property.of;\n const resolvedProperties = getArrayResolvedProperties({\n propertyValue,\n propertyKey,\n property,\n ignoreMissingFields,\n ...props\n });\n // Destructured to be *excluded* from `...rest`, not to be used —\n // see the comment below. Said explicitly so the discarded-value\n // ratchet does not carry a finding that is working as intended.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { values, previousValues, ...rest } = props;\n const ofProperty = resolveProperty({ // we don't want to pass the values of the parent entity\n property: of,\n ignoreMissingFields,\n ...rest\n });\n if (!ofProperty && !ignoreMissingFields)\n throw Error(\"When using a property builder as the 'of' prop of an ArrayProperty, you must return a valid child property\")\n return resolvedProperties;\n }\n } else if (property.oneOf) {\n const typeField = property.oneOf?.typeField ?? DEFAULT_ONE_OF_TYPE;\n const resolvedProperties: Property[] = Array.isArray(propertyValue)\n ? propertyValue.map((v, index) => {\n const type = v && v[typeField];\n const childProperty = property.oneOf?.properties[type];\n if (!type || !childProperty) return null;\n return resolveProperty({\n propertyKey: `${propertyKey}.${index}`,\n property: childProperty,\n ignoreMissingFields,\n ...props\n });\n }).filter(e => Boolean(e)) as Property[]\n : [];\n return resolvedProperties;\n } else if (!property.columnType) {\n // An array with neither `of`/`oneOf` nor a `columnType` describes no element\n // type, so nothing can be generated or rendered from it.\n //\n // The escape hatch used to be `ui.Field` — \"a custom component can render\n // anything\" — which made a *presentation* field decide whether a schema was\n // valid, in code the Postgres generator runs. `columnType` is the same escape\n // hatch stated as data: `columnType: \"text[]\"` says what the column holds,\n // which is what both the generator and the form actually need.\n throw Error(`The array property (${propertyKey}) needs to declare an 'of' or a 'oneOf' property, or a \\`columnType\\` such as \"text[]\"`);\n } else {\n return [];\n }\n\n}\n\nexport function getArrayResolvedProperties({\n propertyKey,\n propertyValue,\n property,\n ...props\n}: {\n propertyValue: unknown,\n propertyKey?: string,\n property: ArrayProperty,\n ignoreMissingFields: boolean,\n values?: object;\n previousValues?: object;\n path?: string;\n entityId?: string | number;\n index?: number;\n propertyConfigs?: Record<string, PropertyConfig>;\n authController: AuthState;\n}) {\n\n const of = property.of;\n if (!of)\n throw Error(\n `Trying to resolve an array property (${propertyKey}) without providing an 'of' property`\n )\n return Array.isArray(propertyValue)\n ? propertyValue.map((v: unknown, index: number) => {\n return resolveProperty({\n propertyKey: `${propertyKey}.${index}`,\n property: Array.isArray(of) ? of[index] : of,\n ...props,\n index\n });\n }).filter(e => Boolean(e)) as Property[]\n : [];\n}\n\n/**\n * An enum as `{ id, label }` entries, or `undefined` when it is not one.\n *\n * {@link enumToObjectEntries} is the reading: an array is a list of entries or\n * of plain values (`[\"draft\", \"published\"]`, whose ids are the values, not\n * their positions), and a map entry's id is its key even when its value is a\n * config that does not repeat it.\n */\nexport function resolveEnumValues(input: EnumValues | Array<string | number>): EnumValueConfig[] | undefined {\n if (typeof input !== \"object\" || input === null) return undefined;\n return enumToObjectEntries(input);\n}\n\n\n/**\n * The lists rendered inside an entity view of `collection` — its tabs.\n *\n * The single derivation. There used to be two that disagreed: this one, and a\n * copy in `CollectionRegistry.normalizeCollection` that stamped each child with\n * the *target collection's* slug instead of the relation key. Since the\n * registry ran first and cached its answer onto `childCollections`, its version\n * was the one that won, and the frontend addressed child listings by a segment\n * the backend could not resolve.\n *\n * Order of precedence:\n * 1. `childCollections` — the explicit escape hatch for custom drivers.\n * 2. `subcollections` on an engine that has real containment (Firestore).\n * 3. many-relations on an engine that has relations (SQL).\n */\nexport function getEntityChildViews<M extends Record<string, unknown> = Record<string, unknown>>(\n collection: CollectionConfig<M>\n): EntityChildView[] {\n const asSubcollections = (collections: CollectionConfig<Record<string, unknown>>[]): EntityChildView[] =>\n collections.filter(Boolean).map(child => ({\n key: child.slug,\n collection: child,\n source: { kind: \"subcollection\" as const }\n }));\n\n if (collection.childCollections) {\n return asSubcollections(collection.childCollections() ?? []);\n }\n\n const capabilities = getDataSourceCapabilities(collection.engine);\n\n const declaredSubcollections = getDeclaredSubcollections(collection);\n if (capabilities.supportsSubcollections && declaredSubcollections) {\n return asSubcollections(declaredSubcollections() ?? []);\n }\n\n if (!capabilities.supportsRelations) return [];\n\n const resolvedRelations = resolveCollectionRelations(collection);\n const views: EntityChildView[] = [];\n const seen = new Set<string>();\n\n // Keyed by the map key, not by `relationName`: the map key is what\n // `findRelation` matches a path segment against, so it is the only one that\n // addresses the same relation on both sides of the wire. The map registers\n // some relations twice — once canonically, once under the declaring\n // property key — so dedupe on the underlying relation.\n for (const [relationKey, relation] of Object.entries(resolvedRelations)) {\n if (relation.cardinality !== \"many\") continue;\n\n const identity = relation.relationName ?? relationKey;\n if (seen.has(identity)) continue;\n\n let target: CollectionConfig | undefined;\n try {\n target = relation.target();\n } catch {\n continue;\n }\n if (!target) continue;\n seen.add(identity);\n\n // A name given to the declaring property is the author naming the tab.\n // Only the tab: it is a plural (\"Tags\"), and one row of it is still\n // what the target calls one (\"Tag\").\n const declaringProperty = Object.entries((collection.properties ?? {}) as Record<string, Property>)\n .find(([propKey, p]) => p.type === \"relation\" && ((p as RelationProperty).relation?.relationName ?? propKey) === identity);\n const customName = declaringProperty?.[1]?.name;\n\n const base: CollectionConfig<Record<string, unknown>> = {\n ...target,\n slug: relationKey,\n ...(customName ? { name: customName } : {})\n } as CollectionConfig<Record<string, unknown>>;\n\n views.push({\n key: relationKey,\n collection: (relation.overrides ? mergeDeep(base, relation.overrides) : base) as CollectionConfig<Record<string, unknown>>,\n source: {\n kind: \"relation\",\n relationKey,\n mode: isJunctionBackedRelation(relation) ? \"linked\" : \"owned\",\n targetSlug: target.slug\n }\n });\n }\n\n return views;\n}\n\n/**\n * Each of `collection`'s tabs paired with the property that declared it, when a\n * property declared it: child view key → property key.\n *\n * A many-relation can only be declared as a property — that is the documented\n * and only mechanism — and {@link getEntityChildViews} promotes it to a tab. So\n * one declaration reaches the panel twice, and neither surface knew about the\n * other. The form rendered a relation picker beside the tab, and the collection\n * table rendered *two* columns under one heading: the relation's own column,\n * showing the child rows, and a jump-to-tab button carrying the same name.\n *\n * The pairing is what lets each surface decide which half is redundant, and it\n * has to be a pairing rather than two sets because the two keys differ whenever\n * a relation is named. The match is on the resolved `relationName` — the\n * identity `getEntityChildViews` itself dedupes on — so a relation declared in\n * `relations` and pointed at by a differently-named property is recognised too.\n *\n * A relation with no property of its own is absent here, which is the point: it\n * has exactly one surface already, and nothing to weigh it against.\n *\n * Only top-level properties: a relation nested inside a `map` gets no tab.\n */\nexport function getChildViewDeclaringProperties<M extends Record<string, unknown> = Record<string, unknown>>(\n collection: CollectionConfig<M>\n): Map<string, string> {\n const pairs = new Map<string, string>();\n\n const relationProperties = Object.entries((collection.properties ?? {}) as Record<string, Property>)\n .filter(([, property]) => property?.type === \"relation\");\n if (relationProperties.length === 0) return pairs;\n\n const relationViews = getEntityChildViews(collection)\n .filter(view => view.source.kind === \"relation\");\n if (relationViews.length === 0) return pairs;\n\n const resolvedRelations = resolveCollectionRelations(collection);\n const identityOf = (relationKey: string): string =>\n resolvedRelations[relationKey]?.relationName ?? relationKey;\n\n const declaringPropertyByIdentity = new Map<string, string>();\n for (const [propertyKey, property] of relationProperties) {\n const relation = (property as RelationProperty).resolvedRelation ?? resolvedRelations[propertyKey];\n // A to-one relation is a foreign key the author edits, never a tab. No\n // view will match it — the views here are many-relations only — but\n // reading the cardinality says so where someone is looking.\n if (relation?.cardinality !== \"many\") continue;\n const identity = relation.relationName ?? propertyKey;\n if (!declaringPropertyByIdentity.has(identity)) declaringPropertyByIdentity.set(identity, propertyKey);\n }\n\n for (const view of relationViews) {\n const propertyKey = declaringPropertyByIdentity.get(\n identityOf((view.source as { relationKey: string }).relationKey));\n if (propertyKey) pairs.set(view.key, propertyKey);\n }\n\n return pairs;\n}\n\n/**\n * The property keys of `collection` whose relation is already one of its tabs.\n *\n * What a form asks: the tab is the treatment for a list of child rows, so the\n * picker beside it is the redundant half. See\n * {@link getChildViewDeclaringProperties}.\n */\nexport function getChildViewRelationPropertyKeys<M extends Record<string, unknown> = Record<string, unknown>>(\n collection: CollectionConfig<M>\n): Set<string> {\n return new Set(getChildViewDeclaringProperties(collection).values());\n}\n\n/**\n * The child views of `collection` as bare collections.\n *\n * The flattened view of {@link getEntityChildViews}, for navigation code that\n * only needs to match a path segment against a slug. Anything that cares *what\n * kind* of list it is showing — chiefly the admin, which must not offer a\n * global delete on a shared row — should read the views instead.\n */\nexport function getSubcollections<M extends Record<string, unknown> = Record<string, unknown>>(collection: CollectionConfig<M>): CollectionConfig<Record<string, unknown>>[] {\n return getEntityChildViews(collection).map(view => view.collection);\n}\n"],"mappings":"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"}
@@ -57,7 +57,7 @@ async function resolveStorageFilenameString({ input, storage, values, entityId,
57
57
  if (typeof input === "function") {
58
58
  result = await input({
59
59
  path,
60
- entityId,
60
+ id: entityId,
61
61
  values,
62
62
  property,
63
63
  file,
@@ -80,7 +80,7 @@ function resolveStoragePathString({ input, storage, values, entityId, path, prop
80
80
  if (typeof input === "function") {
81
81
  result = input({
82
82
  path,
83
- entityId,
83
+ id: entityId,
84
84
  values,
85
85
  property,
86
86
  file,
@@ -101,7 +101,7 @@ function resolveStoragePathString({ input, storage, values, entityId, path, prop
101
101
  function replacePlaceholders({ file, input, entityId, propertyKey, path }) {
102
102
  const ext = file.name.split(".").pop();
103
103
  let result = input.replace("{propertyKey}", propertyKey).replace("{rand}", randomString()).replace("{file}", file.name).replace("{file.type}", file.type);
104
- if (entityId) result = result.replace("{entityId}", String(entityId));
104
+ if (entityId) result = result.replace("{id}", String(entityId)).replace("{entityId}", String(entityId));
105
105
  if (path) result = result.replace("{path}", path);
106
106
  if (ext) {
107
107
  result = result.replace("{file.ext}", ext);
@@ -1 +1 @@
1
- {"version":3,"file":"storage.es.js","names":[],"sources":["../../src/util/storage.ts"],"sourcesContent":["import { ArrayProperty, DEFAULT_STORAGE_SOURCE_KEY, EntityValues, StorageConfig, StorageSource, StorageSourceRegistry, StringProperty, UploadedFileContext } from \"@rebasepro/types\";\nimport { randomString } from \"@rebasepro/utils\";\n\n/**\n * Resolve the {@link StorageSource} to use for a property, given the key\n * referenced by `StorageConfig.storageSource`.\n *\n * Resolution priority:\n * 1. No `sourceKey` → the default source.\n * 2. An explicit {@link StorageSourceRegistry} that has the key (e.g. `client.storageRegistry`).\n * 3. A `sources` lookup map (e.g. the `StorageSourcesContext`).\n * 4. `(default)` → the default source.\n * 5. `createSource(key)` — a server-routed source carrying `storageId`, so the\n * server decides: it serves the source, or answers 400/501 naming it.\n * 6. Otherwise a source that refuses every call, naming the key.\n *\n * Never the default source for a key that names another. It used to be: a\n * named source enters the panel's map only once `GET /storage/sources` has\n * answered, so one failed fetch (or a typo, or a `direct` source the app did\n * not pass) silently uploaded the property's files into the default bucket —\n * with the authorize hook asked about `(default)` and the preview looking in\n * the wrong place. The server stopped falling back for the same reason.\n *\n * Shared by the upload hook, the markdown editor, and the read-only previews\n * so the resolution logic lives in one place.\n *\n * @group Storage\n */\nexport function resolveStorageSource(params: {\n /** Key from `StorageConfig.storageSource`. */\n sourceKey?: string | null;\n /** Built sources keyed by storage-source key (e.g. from context). */\n sources?: Record<string, StorageSource>;\n /** Optional explicit registry — takes precedence over `sources`. */\n registry?: StorageSourceRegistry;\n /** Default source, used when no key is set. */\n defaultSource: StorageSource;\n /**\n * Builds a server-routed source for a key nothing else resolves\n * (`client.createStorageSource`).\n */\n createSource?: (key: string) => StorageSource;\n}): StorageSource {\n const { sourceKey, sources, registry, defaultSource, createSource } = params;\n if (!sourceKey) return defaultSource;\n if (registry?.has(sourceKey)) return registry.getOrDefault(sourceKey);\n const fromSources = sources?.[sourceKey];\n if (fromSources) return fromSources;\n if (sourceKey === DEFAULT_STORAGE_SOURCE_KEY) return defaultSource;\n if (createSource) return createSource(sourceKey);\n return unavailableStorageSource(sourceKey);\n}\n\n/**\n * A storage source for a key this app cannot reach: every call fails, naming\n * the key — so an upload is refused instead of landing in another bucket.\n *\n * @group Storage\n */\nexport function unavailableStorageSource(key: string): StorageSource {\n const refuse = (): Promise<never> => Promise.reject(new Error(\n `Storage source \"${key}\" is not available in this app. Check the property's ` +\n \"`storageSource`, that the backend serves it (GET /api/storage/sources), and that a \" +\n \"`direct` source is passed to <Rebase storageSources>.\"\n ));\n return {\n putObject: refuse,\n getSignedUrl: refuse,\n getObject: refuse,\n deleteObject: refuse,\n listObjects: refuse\n };\n}\n\ninterface ResolveFilenameStringParams<M extends Record<string, unknown>> {\n input: string | ((context: UploadedFileContext) => (Promise<string> | string));\n storage: StorageConfig;\n values: EntityValues<M>;\n entityId?: string | number;\n path?: string;\n property: StringProperty | ArrayProperty,\n file: File;\n propertyKey: string;\n}\n\nexport async function resolveStorageFilenameString<M extends Record<string, unknown>>(\n {\n input,\n storage,\n values,\n entityId,\n path,\n property,\n file,\n propertyKey\n }: ResolveFilenameStringParams<M>): Promise<string> {\n let result;\n\n if (typeof input === \"function\") {\n result = await input({\n path,\n entityId,\n values,\n property,\n file,\n storage,\n propertyKey\n });\n if (!result)\n console.warn(\"Storage callback returned empty result. Using default name value\")\n } else {\n result = replacePlaceholders({\n file,\n input,\n entityId,\n propertyKey,\n path\n });\n }\n\n if (!result)\n result = randomString() + \"_\" + file.name;\n\n return result;\n}\n\ninterface ResolveStoragePathStringParams<M extends Record<string, unknown>> {\n input: string | ((context: UploadedFileContext) => string);\n storage: StorageConfig;\n values: EntityValues<M>;\n entityId?: string | number;\n path?: string;\n property: StringProperty | ArrayProperty;\n file: File;\n propertyKey: string;\n}\n\nexport function resolveStoragePathString<M extends Record<string, unknown>>(\n {\n input,\n storage,\n values,\n entityId,\n path,\n property,\n file,\n propertyKey\n }: ResolveStoragePathStringParams<M>): string {\n let result;\n if (typeof input === \"function\") {\n result = input({\n path,\n entityId,\n values,\n property,\n file,\n storage,\n propertyKey\n });\n if (!result)\n console.warn(\"Storage callback returned empty result. Using default name value\")\n } else {\n result = replacePlaceholders({\n file,\n input,\n entityId,\n propertyKey,\n path\n });\n }\n\n if (!result)\n result = randomString() + \"_\" + file.name;\n\n return result;\n}\n\ninterface Placeholders {\n file: File;\n input: string;\n entityId?: string | number;\n propertyKey: string;\n path?: string;\n}\n\nfunction replacePlaceholders({\n file,\n input,\n entityId,\n propertyKey,\n path\n}: Placeholders) {\n const ext = file.name.split(\".\").pop();\n let result = input\n .replace(\"{propertyKey}\", propertyKey)\n .replace(\"{rand}\", randomString())\n .replace(\"{file}\", file.name)\n .replace(\"{file.type}\", file.type);\n if (entityId) {\n result = result.replace(\"{entityId}\", String(entityId));\n }\n if (path) {\n result = result.replace(\"{path}\", path);\n }\n if (ext) {\n result = result.replace(\"{file.ext}\", ext);\n const name = file.name.replace(`.${ext}`, \"\");\n result = result.replace(\"{file.name}\", name)\n }\n\n if (!result)\n result = randomString() + \"_\" + file.name;\n\n return result;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,qBAAqB,QAcnB;CACd,MAAM,EAAE,WAAW,SAAS,UAAU,eAAe,iBAAiB;CACtE,IAAI,CAAC,WAAW,OAAO;CACvB,IAAI,UAAU,IAAI,SAAS,GAAG,OAAO,SAAS,aAAa,SAAS;CACpE,MAAM,cAAc,UAAU;CAC9B,IAAI,aAAa,OAAO;CACxB,IAAI,cAAc,4BAA4B,OAAO;CACrD,IAAI,cAAc,OAAO,aAAa,SAAS;CAC/C,OAAO,yBAAyB,SAAS;AAC7C;;;;;;;AAQA,SAAgB,yBAAyB,KAA4B;CACjE,MAAM,eAA+B,QAAQ,uBAAO,IAAI,MACpD,mBAAmB,IAAI,kMAG3B,CAAC;CACD,OAAO;EACH,WAAW;EACX,cAAc;EACd,WAAW;EACX,cAAc;EACd,aAAa;CACjB;AACJ;AAaA,eAAsB,6BAClB,EACI,OACA,SACA,QACA,UACA,MACA,UACA,MACA,eACgD;CACpD,IAAI;CAEJ,IAAI,OAAO,UAAU,YAAY;EAC7B,SAAS,MAAM,MAAM;GACjB;GACA;GACA;GACA;GACA;GACA;GACA;EACJ,CAAC;EACD,IAAI,CAAC,QACD,QAAQ,KAAK,kEAAkE;CACvF,OACI,SAAS,oBAAoB;EACzB;EACA;EACA;EACA;EACA;CACJ,CAAC;CAGL,IAAI,CAAC,QACD,SAAS,aAAa,IAAI,MAAM,KAAK;CAEzC,OAAO;AACX;AAaA,SAAgB,yBACZ,EACI,OACA,SACA,QACA,UACA,MACA,UACA,MACA,eAC0C;CAC9C,IAAI;CACJ,IAAI,OAAO,UAAU,YAAY;EAC7B,SAAS,MAAM;GACX;GACA;GACA;GACA;GACA;GACA;GACA;EACJ,CAAC;EACD,IAAI,CAAC,QACD,QAAQ,KAAK,kEAAkE;CACvF,OACI,SAAS,oBAAoB;EACzB;EACA;EACA;EACA;EACA;CACJ,CAAC;CAGL,IAAI,CAAC,QACD,SAAS,aAAa,IAAI,MAAM,KAAK;CAEzC,OAAO;AACX;AAUA,SAAS,oBAAoB,EACzB,MACA,OACA,UACA,aACA,QACa;CACb,MAAM,MAAM,KAAK,KAAK,MAAM,GAAG,CAAC,CAAC,IAAI;CACrC,IAAI,SAAS,MACR,QAAQ,iBAAiB,WAAW,CAAC,CACrC,QAAQ,UAAU,aAAa,CAAC,CAAC,CACjC,QAAQ,UAAU,KAAK,IAAI,CAAC,CAC5B,QAAQ,eAAe,KAAK,IAAI;CACrC,IAAI,UACA,SAAS,OAAO,QAAQ,cAAc,OAAO,QAAQ,CAAC;CAE1D,IAAI,MACA,SAAS,OAAO,QAAQ,UAAU,IAAI;CAE1C,IAAI,KAAK;EACL,SAAS,OAAO,QAAQ,cAAc,GAAG;EACzC,MAAM,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,EAAE;EAC5C,SAAS,OAAO,QAAQ,eAAe,IAAI;CAC/C;CAEA,IAAI,CAAC,QACD,SAAS,aAAa,IAAI,MAAM,KAAK;CAEzC,OAAO;AACX"}
1
+ {"version":3,"file":"storage.es.js","names":[],"sources":["../../src/util/storage.ts"],"sourcesContent":["import { ArrayProperty, DEFAULT_STORAGE_SOURCE_KEY, EntityValues, StorageConfig, StorageSource, StorageSourceRegistry, StringProperty, UploadedFileContext } from \"@rebasepro/types\";\nimport { randomString } from \"@rebasepro/utils\";\n\n/**\n * Resolve the {@link StorageSource} to use for a property, given the key\n * referenced by `StorageConfig.storageSource`.\n *\n * Resolution priority:\n * 1. No `sourceKey` → the default source.\n * 2. An explicit {@link StorageSourceRegistry} that has the key (e.g. `client.storageRegistry`).\n * 3. A `sources` lookup map (e.g. the `StorageSourcesContext`).\n * 4. `(default)` → the default source.\n * 5. `createSource(key)` — a server-routed source carrying `storageId`, so the\n * server decides: it serves the source, or answers 400/501 naming it.\n * 6. Otherwise a source that refuses every call, naming the key.\n *\n * Never the default source for a key that names another. It used to be: a\n * named source enters the panel's map only once `GET /storage/sources` has\n * answered, so one failed fetch (or a typo, or a `direct` source the app did\n * not pass) silently uploaded the property's files into the default bucket —\n * with the authorize hook asked about `(default)` and the preview looking in\n * the wrong place. The server stopped falling back for the same reason.\n *\n * Shared by the upload hook, the markdown editor, and the read-only previews\n * so the resolution logic lives in one place.\n *\n * @group Storage\n */\nexport function resolveStorageSource(params: {\n /** Key from `StorageConfig.storageSource`. */\n sourceKey?: string | null;\n /** Built sources keyed by storage-source key (e.g. from context). */\n sources?: Record<string, StorageSource>;\n /** Optional explicit registry — takes precedence over `sources`. */\n registry?: StorageSourceRegistry;\n /** Default source, used when no key is set. */\n defaultSource: StorageSource;\n /**\n * Builds a server-routed source for a key nothing else resolves\n * (`client.createStorageSource`).\n */\n createSource?: (key: string) => StorageSource;\n}): StorageSource {\n const { sourceKey, sources, registry, defaultSource, createSource } = params;\n if (!sourceKey) return defaultSource;\n if (registry?.has(sourceKey)) return registry.getOrDefault(sourceKey);\n const fromSources = sources?.[sourceKey];\n if (fromSources) return fromSources;\n if (sourceKey === DEFAULT_STORAGE_SOURCE_KEY) return defaultSource;\n if (createSource) return createSource(sourceKey);\n return unavailableStorageSource(sourceKey);\n}\n\n/**\n * A storage source for a key this app cannot reach: every call fails, naming\n * the key — so an upload is refused instead of landing in another bucket.\n *\n * @group Storage\n */\nexport function unavailableStorageSource(key: string): StorageSource {\n const refuse = (): Promise<never> => Promise.reject(new Error(\n `Storage source \"${key}\" is not available in this app. Check the property's ` +\n \"`storageSource`, that the backend serves it (GET /api/storage/sources), and that a \" +\n \"`direct` source is passed to <Rebase storageSources>.\"\n ));\n return {\n putObject: refuse,\n getSignedUrl: refuse,\n getObject: refuse,\n deleteObject: refuse,\n listObjects: refuse\n };\n}\n\ninterface ResolveFilenameStringParams<M extends Record<string, unknown>> {\n input: string | ((context: UploadedFileContext) => (Promise<string> | string));\n storage: StorageConfig;\n values: EntityValues<M>;\n entityId?: string | number;\n path?: string;\n property: StringProperty | ArrayProperty,\n file: File;\n propertyKey: string;\n}\n\nexport async function resolveStorageFilenameString<M extends Record<string, unknown>>(\n {\n input,\n storage,\n values,\n entityId,\n path,\n property,\n file,\n propertyKey\n }: ResolveFilenameStringParams<M>): Promise<string> {\n let result;\n\n if (typeof input === \"function\") {\n result = await input({\n path,\n id: entityId,\n values,\n property,\n file,\n storage,\n propertyKey\n });\n if (!result)\n console.warn(\"Storage callback returned empty result. Using default name value\")\n } else {\n result = replacePlaceholders({\n file,\n input,\n entityId,\n propertyKey,\n path\n });\n }\n\n if (!result)\n result = randomString() + \"_\" + file.name;\n\n return result;\n}\n\ninterface ResolveStoragePathStringParams<M extends Record<string, unknown>> {\n input: string | ((context: UploadedFileContext) => string);\n storage: StorageConfig;\n values: EntityValues<M>;\n entityId?: string | number;\n path?: string;\n property: StringProperty | ArrayProperty;\n file: File;\n propertyKey: string;\n}\n\nexport function resolveStoragePathString<M extends Record<string, unknown>>(\n {\n input,\n storage,\n values,\n entityId,\n path,\n property,\n file,\n propertyKey\n }: ResolveStoragePathStringParams<M>): string {\n let result;\n if (typeof input === \"function\") {\n result = input({\n path,\n id: entityId,\n values,\n property,\n file,\n storage,\n propertyKey\n });\n if (!result)\n console.warn(\"Storage callback returned empty result. Using default name value\")\n } else {\n result = replacePlaceholders({\n file,\n input,\n entityId,\n propertyKey,\n path\n });\n }\n\n if (!result)\n result = randomString() + \"_\" + file.name;\n\n return result;\n}\n\ninterface Placeholders {\n file: File;\n input: string;\n entityId?: string | number;\n propertyKey: string;\n path?: string;\n}\n\nfunction replacePlaceholders({\n file,\n input,\n entityId,\n propertyKey,\n path\n}: Placeholders) {\n const ext = file.name.split(\".\").pop();\n let result = input\n .replace(\"{propertyKey}\", propertyKey)\n .replace(\"{rand}\", randomString())\n .replace(\"{file}\", file.name)\n .replace(\"{file.type}\", file.type);\n if (entityId) {\n // `{entityId}` is how configs written before `{id}` spell it, and a\n // stored path keeps meaning what it meant.\n result = result.replace(\"{id}\", String(entityId)).replace(\"{entityId}\", String(entityId));\n }\n if (path) {\n result = result.replace(\"{path}\", path);\n }\n if (ext) {\n result = result.replace(\"{file.ext}\", ext);\n const name = file.name.replace(`.${ext}`, \"\");\n result = result.replace(\"{file.name}\", name)\n }\n\n if (!result)\n result = randomString() + \"_\" + file.name;\n\n return result;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,qBAAqB,QAcnB;CACd,MAAM,EAAE,WAAW,SAAS,UAAU,eAAe,iBAAiB;CACtE,IAAI,CAAC,WAAW,OAAO;CACvB,IAAI,UAAU,IAAI,SAAS,GAAG,OAAO,SAAS,aAAa,SAAS;CACpE,MAAM,cAAc,UAAU;CAC9B,IAAI,aAAa,OAAO;CACxB,IAAI,cAAc,4BAA4B,OAAO;CACrD,IAAI,cAAc,OAAO,aAAa,SAAS;CAC/C,OAAO,yBAAyB,SAAS;AAC7C;;;;;;;AAQA,SAAgB,yBAAyB,KAA4B;CACjE,MAAM,eAA+B,QAAQ,uBAAO,IAAI,MACpD,mBAAmB,IAAI,kMAG3B,CAAC;CACD,OAAO;EACH,WAAW;EACX,cAAc;EACd,WAAW;EACX,cAAc;EACd,aAAa;CACjB;AACJ;AAaA,eAAsB,6BAClB,EACI,OACA,SACA,QACA,UACA,MACA,UACA,MACA,eACgD;CACpD,IAAI;CAEJ,IAAI,OAAO,UAAU,YAAY;EAC7B,SAAS,MAAM,MAAM;GACjB;GACA,IAAI;GACJ;GACA;GACA;GACA;GACA;EACJ,CAAC;EACD,IAAI,CAAC,QACD,QAAQ,KAAK,kEAAkE;CACvF,OACI,SAAS,oBAAoB;EACzB;EACA;EACA;EACA;EACA;CACJ,CAAC;CAGL,IAAI,CAAC,QACD,SAAS,aAAa,IAAI,MAAM,KAAK;CAEzC,OAAO;AACX;AAaA,SAAgB,yBACZ,EACI,OACA,SACA,QACA,UACA,MACA,UACA,MACA,eAC0C;CAC9C,IAAI;CACJ,IAAI,OAAO,UAAU,YAAY;EAC7B,SAAS,MAAM;GACX;GACA,IAAI;GACJ;GACA;GACA;GACA;GACA;EACJ,CAAC;EACD,IAAI,CAAC,QACD,QAAQ,KAAK,kEAAkE;CACvF,OACI,SAAS,oBAAoB;EACzB;EACA;EACA;EACA;EACA;CACJ,CAAC;CAGL,IAAI,CAAC,QACD,SAAS,aAAa,IAAI,MAAM,KAAK;CAEzC,OAAO;AACX;AAUA,SAAS,oBAAoB,EACzB,MACA,OACA,UACA,aACA,QACa;CACb,MAAM,MAAM,KAAK,KAAK,MAAM,GAAG,CAAC,CAAC,IAAI;CACrC,IAAI,SAAS,MACR,QAAQ,iBAAiB,WAAW,CAAC,CACrC,QAAQ,UAAU,aAAa,CAAC,CAAC,CACjC,QAAQ,UAAU,KAAK,IAAI,CAAC,CAC5B,QAAQ,eAAe,KAAK,IAAI;CACrC,IAAI,UAGA,SAAS,OAAO,QAAQ,QAAQ,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,cAAc,OAAO,QAAQ,CAAC;CAE5F,IAAI,MACA,SAAS,OAAO,QAAQ,UAAU,IAAI;CAE1C,IAAI,KAAK;EACL,SAAS,OAAO,QAAQ,cAAc,GAAG;EACzC,MAAM,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,EAAE;EAC5C,SAAS,OAAO,QAAQ,eAAe,IAAI;CAC/C;CAEA,IAAI,CAAC,QACD,SAAS,aAAa,IAAI,MAAM,KAAK;CAEzC,OAAO;AACX"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebasepro/common",
3
- "version": "0.28.2",
3
+ "version": "0.29.0",
4
4
  "description": "Rebase shared core — collection registry, data driver adapter and fluent query builder. No React dependency.",
5
5
  "keywords": [
6
6
  "rebase",
@@ -46,8 +46,8 @@
46
46
  "dependencies": {
47
47
  "fast-equals": "6.0.2",
48
48
  "json-logic-js": "^2.0.5",
49
- "@rebasepro/types": "0.28.2",
50
- "@rebasepro/utils": "0.28.2"
49
+ "@rebasepro/types": "0.29.0",
50
+ "@rebasepro/utils": "0.29.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@jest/globals": "^30.4.1",