@rebasepro/utils 0.16.0 → 0.16.1-canary.g0d7af95

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.d.ts CHANGED
@@ -1,13 +1,13 @@
1
- export * from "./strings";
2
- export * from "./objects";
3
- export * from "./arrays";
4
- export * from "./dates";
5
- export * from "./storage";
6
- export * from "./hash";
7
- export * from "./sha1";
8
- export * from "./policy-names";
9
- export * from "./regexp";
10
- export * from "./flatten_object";
11
- export * from "./plurals";
12
- export * from "./names";
13
- export * from "./fields";
1
+ export * from "./strings.js";
2
+ export * from "./objects.js";
3
+ export * from "./arrays.js";
4
+ export * from "./dates.js";
5
+ export * from "./storage.js";
6
+ export * from "./hash.js";
7
+ export * from "./sha1.js";
8
+ export * from "./policy-names.js";
9
+ export * from "./regexp.js";
10
+ export * from "./flatten_object.js";
11
+ export * from "./plurals.js";
12
+ export * from "./names.js";
13
+ export * from "./fields.js";
package/dist/index.es.js CHANGED
@@ -911,9 +911,28 @@ function legacyForeignKeyName(name) {
911
911
  * and `TextDecoder` are standard in both runtimes and need no ambient types.
912
912
  */
913
913
  function toPostgresIdentifier(name) {
914
+ return truncateToBytes(name, 63);
915
+ }
916
+ /**
917
+ * {@link toPostgresIdentifier} with the bound lifted to a parameter.
918
+ *
919
+ * Exists for names that end in something load-bearing. Truncating at 63 keeps
920
+ * the *head* of a name and discards the tail, which is right for a descriptive
921
+ * identifier and wrong for a hashed one: the hash is the part that makes it
922
+ * unique, and it is at the end. A caller that appends a fingerprint truncates
923
+ * the readable head to `63 - <tail>` itself and then appends, so the bound is
924
+ * still 63 and the hash always survives.
925
+ *
926
+ * `contracts/derived-names.txt` records what the alternative costs — a foreign
927
+ * key frozen as `..._corres`, its `_fkey` suffix truncated away, so a second
928
+ * foreign key on that table would derive a byte-identical name.
929
+ *
930
+ * One truncation rule, in one function, so the two cannot drift.
931
+ */
932
+ function truncateToBytes(name, maxBytes) {
914
933
  const bytes = new TextEncoder().encode(name);
915
- if (bytes.byteLength <= 63) return name;
916
- return new TextDecoder("utf-8").decode(bytes.subarray(0, 63)).replace(/�+$/, "");
934
+ if (bytes.byteLength <= maxBytes) return name;
935
+ return new TextDecoder("utf-8").decode(bytes.subarray(0, maxBytes)).replace(/�+$/, "");
917
936
  }
918
937
  /**
919
938
  * The API name a database column is served under.
@@ -1011,6 +1030,6 @@ function isDefaultFieldConfigId(id) {
1011
1030
  ].includes(id);
1012
1031
  }
1013
1032
  //#endregion
1014
- export { camelCase, clone, deepClone, defaultDateFormat, firstFreeKey, flattenObject, formatRelativeTime, generateForeignKeyName, getArrayValuesCount, getHashValue, getIn, getPolicyNameHash, getPolicyNamesForRule, getPolicyNamesForRules, getPolicyOperations, getValueInPath, getWebStorage, hashString, hydrateRegExp, isArrayValue, isDefaultFieldConfigId, isEmptyArray, isEmptyObject, isFunction, isInteger, isNaN, isObject, isPlainObject, isPrototypePollutingKey, isRecordValue, isValidRegExp, legacyForeignKeyName, mergeDeep, pathTraversesPrototype, pick, plural, prettifyIdentifier, randomColor, randomString, readStoredJson, readStoredString, removeFunctions, removeInPath, removeNulls, removePropsIfExisting, removeUndefined, serializeRegExp, setIn, sha1Hex, singular, slugify, toArray, toKebabCase, toPostgresIdentifier, toSnakeCase, toWireKey, unslugify, writeStoredJson, writeStoredString };
1033
+ export { camelCase, clone, deepClone, defaultDateFormat, firstFreeKey, flattenObject, formatRelativeTime, generateForeignKeyName, getArrayValuesCount, getHashValue, getIn, getPolicyNameHash, getPolicyNamesForRule, getPolicyNamesForRules, getPolicyOperations, getValueInPath, getWebStorage, hashString, hydrateRegExp, isArrayValue, isDefaultFieldConfigId, isEmptyArray, isEmptyObject, isFunction, isInteger, isNaN, isObject, isPlainObject, isPrototypePollutingKey, isRecordValue, isValidRegExp, legacyForeignKeyName, mergeDeep, pathTraversesPrototype, pick, plural, prettifyIdentifier, randomColor, randomString, readStoredJson, readStoredString, removeFunctions, removeInPath, removeNulls, removePropsIfExisting, removeUndefined, serializeRegExp, setIn, sha1Hex, singular, slugify, toArray, toKebabCase, toPostgresIdentifier, toSnakeCase, toWireKey, truncateToBytes, unslugify, writeStoredJson, writeStoredString };
1015
1034
 
1016
1035
  //# sourceMappingURL=index.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.es.js","names":[],"sources":["../src/strings.ts","../src/objects.ts","../src/arrays.ts","../src/dates.ts","../src/storage.ts","../src/hash.ts","../src/sha1.ts","../src/policy-names.ts","../src/regexp.ts","../src/flatten_object.ts","../src/plurals.ts","../src/names.ts","../src/fields.ts"],"sourcesContent":["const tokenizeRegex = /[A-Z]{2,}(?=[A-Z][a-z]|\\b)|[A-Z]?[a-z]+|[0-9]+(?:[a-z](?![a-z]))?|[A-Z]/g;\n\nexport const toKebabCase = (str?: string) => {\n if (!str || typeof str !== \"string\") return \"\";\n const regExpMatchArray = str.match(tokenizeRegex);\n if (!regExpMatchArray) return \"\";\n return regExpMatchArray\n .map(x => x.toLowerCase())\n .join(\"-\");\n};\n\nconst snakeCaseRegex = tokenizeRegex;\n\nexport const toSnakeCase = (str?: string) => {\n if (!str || typeof str !== \"string\") return \"\";\n const regExpMatchArray = str.match(snakeCaseRegex);\n if (!regExpMatchArray) return \"\";\n return regExpMatchArray\n .map(x => x.toLowerCase())\n .join(\"_\");\n};\n\nexport function camelCase(str: string): string {\n if (!str) return \"\";\n if (str.length === 1) return str.toLowerCase();\n\n // Split by hyphens, underscores, or spaces and filter out empty strings\n const parts = str.split(/[-_ ]+/).filter(Boolean);\n\n if (parts.length === 0) return \"\";\n\n // Start with first part in lowercase\n return parts[0].toLowerCase() +\n // Transform remaining parts to have first letter uppercase\n parts.slice(1)\n .map(part => part.charAt(0).toUpperCase() + part.substring(1).toLowerCase())\n .join(\"\");\n}\n\n/**\n * A random base-36 string of exactly `strLength` characters.\n *\n * Not `Math.random().toString(36).slice(2, 2 + strLength)`: that has no\n * guaranteed length. Base-36 of a double drops trailing zeros, so the source\n * string is short about once in 36 calls and the slice quietly returns fewer\n * characters than asked for — `randomString(10)` returning 9. These values\n * prefix uploaded filenames to keep them apart, so a short one is a likelier\n * collision, and it fails at the rate that makes a test look flaky.\n */\nexport function randomString(strLength = 5) {\n const alphabet = \"0123456789abcdefghijklmnopqrstuvwxyz\";\n let result = \"\";\n for (let i = 0; i < strLength; i++) {\n result += alphabet.charAt(Math.floor(Math.random() * alphabet.length));\n }\n return result;\n}\n\nexport function randomColor() {\n return Math.floor(Math.random() * 16777215).toString(16);\n}\n\nexport function slugify(text?: string, separator = \"_\", lowercase = true) {\n if (!text) return \"\";\n const from = \"ãàáäâẽèéëêìíïîõòóöôùúüûñç·/_,:;-\"\n const to = `aaaaaeeeeeiiiiooooouuuunc${separator}${separator}${separator}${separator}${separator}${separator}${separator}`;\n\n for (let i = 0, l = from.length; i < l; i++) {\n text = text.replace(new RegExp(from.charAt(i), \"g\"), to.charAt(i));\n }\n\n text = text\n .toString() // Cast to string\n .trim() // Remove whitespace from both sides of a string\n .replace(/^\\s+|\\s+$/g, \"\")\n .replace(/\\s+/g, separator) // Replace spaces with separator\n .replace(/&/g, separator) // Replace & with separator\n .replace(/[^\\w\\\\-]+/g, \"\") // Remove all non-word chars\n .replace(new RegExp(\"\\\\\" + separator + \"\\\\\" + separator + \"+\", \"g\"),\n separator); // Replace multiple separators with single one\n\n return lowercase\n ? text.toLowerCase() // Convert the string to lowercase letters\n : text;\n}\n\nexport function unslugify(slug?: string): string {\n if (!slug) return \"\";\n if (slug.includes(\"-\") || slug.includes(\"_\") || !slug.includes(\" \")) {\n const result = slug.replace(/[-_]/g, \" \");\n return result.replace(/\\w\\S*/g, function (txt) {\n return txt.charAt(0).toUpperCase() + txt.substring(1);\n }).trim();\n } else {\n return slug.trim();\n }\n}\n\nexport function prettifyIdentifier(input: string) {\n if (!input) return \"\";\n\n let text = input;\n\n // 1. Handle camelCase and Acronyms\n // Group 1 ($1 $2): Lowercase followed by Uppercase (e.g., imageURL -> image URL)\n // Group 2 ($3 $4): Uppercase followed by Uppercase+lowercase (e.g., XMLParser -> XML Parser)\n text = text.replace(/([a-z])([A-Z])|([A-Z])([A-Z][a-z])/g, \"$1$3 $2$4\");\n\n // 2. Replace hyphens/underscores with spaces\n text = text.replace(/[_-]+/g, \" \");\n\n // 3. Capitalize first letter of each word (Title Case)\n const s = text\n .trim()\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n return s;\n}\n","import hash from \"object-hash\";\nimport { GeoPoint } from \"@rebasepro/types\";\n\n/** @private is the value an empty array? */\nexport const isEmptyArray = (value?: unknown) =>\n Array.isArray(value) && value.length === 0;\n\n/** @private is the given object a Function? */\nexport const isFunction = (obj: unknown): obj is (...args: unknown[]) => unknown =>\n typeof obj === \"function\";\n\n/** @private is the given object an integer? */\nexport const isInteger = (obj: unknown): boolean =>\n String(Math.floor(Number(obj))) === String(obj);\n\n/** @private is the given object a NaN? */\n\nexport const isNaN = (obj: unknown): boolean => obj !== obj;\n\n/**\n * Segments that reach the prototype chain rather than a property of the object.\n *\n * The twin of this function in `@rebasepro/forms` could be made to write onto\n * `Object.prototype` through a path of `__proto__.x`. This copy survives the\n * write by accident — its `clone` always spreads into a fresh object, while the\n * form engine's has a \"preserve class instances\" branch that hands back\n * `Object.prototype` itself — but `getIn` still *reads* through the chain, and\n * handing back `Object.prototype` is how a polluted value is read out again.\n *\n * Closed on both sides here, so the two implementations agree.\n */\nconst UNSAFE_PATH_SEGMENTS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\n/** Whether any segment of this path would traverse the prototype chain. */\nexport function pathTraversesPrototype(path: string | string[]): boolean {\n return toPath(path).some(segment => UNSAFE_PATH_SEGMENTS.has(segment));\n}\n\n/**\n * Whether writing this single key with `obj[key] = …` would reach the prototype\n * chain instead of creating a property.\n *\n * The single-key counterpart of {@link pathTraversesPrototype}, for the many\n * places that copy an object one key at a time. `JSON.parse` creates\n * `__proto__` as an *own* property, so it survives `hasOwnProperty` — and then\n * `target[key] = value` invokes the setter and replaces the target's prototype.\n */\nexport function isPrototypePollutingKey(key: string): boolean {\n return UNSAFE_PATH_SEGMENTS.has(key);\n}\n\n/**\n * Deeply get a value from an object via its path.\n */\nexport function getIn(\n obj: Record<string, unknown> | unknown[] | unknown,\n key: string | string[],\n def?: unknown,\n p = 0\n) {\n if (pathTraversesPrototype(key)) return def;\n\n const path = toPath(key);\n while (obj && p < path.length) {\n obj = (obj as Record<string, unknown>)[path[p++]];\n }\n\n // check if path is not in the end\n if (p !== path.length && !obj) {\n return def;\n }\n\n return obj === undefined ? def : obj;\n}\n\nexport function setIn<T>(obj: T, path: string, value: unknown): T {\n // See `pathTraversesPrototype`. This copy's `clone` happens to contain the\n // write, but relying on that is relying on an implementation detail of a\n // different function.\n if (pathTraversesPrototype(path)) return obj;\n\n const res = clone(obj) as Record<string, unknown>;\n let resVal: Record<string, unknown> = res;\n let i = 0;\n const pathArray = toPath(path);\n\n for (; i < pathArray.length - 1; i++) {\n const currentPath: string = pathArray[i];\n const currentObj = getIn(obj as Record<string, unknown>, pathArray.slice(0, i + 1));\n\n if (currentObj && (isObject(currentObj) || Array.isArray(currentObj))) {\n resVal = resVal[currentPath] = clone(currentObj) as Record<string, unknown>;\n } else {\n const nextPath: string = pathArray[i + 1];\n resVal = resVal[currentPath] =\n (isInteger(nextPath) && Number(nextPath) >= 0 ? [] : {}) as Record<string, unknown>;\n }\n }\n\n // Return original object if new value is the same as current\n if ((i === 0 ? obj as Record<string, unknown> : resVal)[pathArray[i]] === value) {\n return obj;\n }\n\n if (value === undefined) {\n delete resVal[pathArray[i]];\n } else {\n resVal[pathArray[i]] = value;\n }\n\n // If the path array has a single element, the loop did not run.\n // Deleting on `resVal` had no effect in this scenario, so we delete on the result instead.\n if (i === 0 && value === undefined) {\n delete res[pathArray[i]];\n }\n\n return res as T;\n}\n\nexport function clone<T>(value: T): T {\n if (Array.isArray(value)) {\n return [...value] as T;\n } else if (typeof value === \"object\" && value !== null) {\n return { ...value } as T;\n } else {\n return value; // This is for primitive types which do not need cloning.\n }\n}\n\n/**\n * Deep clone a value, preserving function references and class instances.\n * Unlike structuredClone, this handles objects that contain functions\n * (e.g. CollectionConfig with target(), childCollections(), callbacks).\n */\nexport function deepClone<T>(value: T): T {\n if (value === null || value === undefined) return value;\n if (typeof value === \"function\") return value;\n if (typeof value !== \"object\") return value;\n\n if (Array.isArray(value)) {\n return value.map(item => deepClone(item)) as T;\n }\n\n // Preserve class instances (Date, GeoPoint, etc.) — don't recurse\n if (Object.getPrototypeOf(value) !== Object.prototype) {\n return value;\n }\n\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n result[key] = deepClone((value as Record<string, unknown>)[key]);\n }\n return result as T;\n}\n\nfunction toPath(value: string | string[]) {\n if (Array.isArray(value)) return value; // Already in path array form.\n // Replace brackets with dots, remove leading/trailing dots, then split by dot.\n return value.replace(/\\[(\\d+)]/g, \".$1\").replace(/^\\./, \"\").replace(/\\.$/, \"\").split(\".\");\n}\n\n\nexport const pick: <T extends Record<string, unknown>>(obj: T, ...args: (keyof T)[]) => Partial<T> = <T extends Record<string, unknown>>(obj: T, ...args: (keyof T)[]) => ({\n ...args.reduce<Record<string, unknown>>((res, key) => ({\n ...res,\n [key as string]: obj[key as string]\n }), {})\n}) as Partial<T>;\n\nexport function isObject(item: unknown): item is Record<string, unknown> {\n return !!item && typeof item === \"object\" && !Array.isArray(item);\n}\n\nexport function isPlainObject(obj: unknown): obj is Record<string, unknown> {\n // 1. Rule out non-objects, null, and arrays\n if (typeof obj !== \"object\" || obj === null || Array.isArray(obj)) {\n return false;\n }\n\n // 2. Get the object's direct prototype\n const proto = Object.getPrototypeOf(obj);\n\n // 3. A plain object's direct prototype is Object.prototype\n return proto === Object.prototype;\n}\n\nexport function mergeDeep<T extends object, U extends object>(\n target: T,\n source: U,\n ignoreUndefined = false\n): T & U {\n // If target is not a true object (e.g., null, array, primitive), return target itself.\n if (!isObject(target)) {\n return target as T & U;\n }\n\n // Create a shallow copy of the target to avoid modifying the original object.\n const output = { ...target };\n\n // If source is not a true object, there's nothing to merge from it.\n // Return the shallow copy of target.\n if (!isObject(source)) {\n return output as T & U;\n }\n\n // Iterate over keys in the source object.\n for (const key in source) {\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\") {\n continue;\n }\n if (Object.prototype.hasOwnProperty.call(source, key)) {\n const sourceValue = source[key];\n const outputValue = (output as Record<string, unknown>)[key]; // Current value in our merged object (originating from target)\n\n // Skip if source value is undefined and ignoreUndefined is true.\n // This handles both not adding new undefined properties and not overwriting existing properties with undefined.\n if (ignoreUndefined && sourceValue === undefined) {\n continue;\n }\n\n if (sourceValue instanceof Date) {\n // If source value is a Date, create a new Date instance.\n (output as Record<string, unknown>)[key] = new Date(sourceValue.getTime());\n } else if (Array.isArray(sourceValue)) {\n if (Array.isArray(outputValue)) {\n // If the array contains primitives or class instances (non-plain objects),\n // overwrite the array entirely instead of doing element-wise merging.\n const hasPlainObjects = sourceValue.some(isPlainObject) || outputValue.some(isPlainObject);\n if (!hasPlainObjects) {\n (output as Record<string, unknown>)[key] = [...sourceValue];\n } else {\n const newArray = [];\n const maxLength = Math.max(outputValue.length, sourceValue.length);\n for (let i = 0; i < maxLength; i++) {\n const sourceItem = sourceValue[i];\n const targetItem = outputValue[i];\n\n if (i >= sourceValue.length) { // source is shorter\n newArray[i] = targetItem;\n } else if (i >= outputValue.length) { // target is shorter\n newArray[i] = sourceItem;\n } else if (sourceItem === null) {\n newArray[i] = targetItem;\n } else if (isPlainObject(sourceItem) && isPlainObject(targetItem)) {\n // Only recursively merge plain objects, preserve class instances\n newArray[i] = mergeDeep(targetItem, sourceItem, ignoreUndefined);\n } else {\n // For class instances and primitives, use source directly\n newArray[i] = sourceItem;\n }\n }\n (output as Record<string, unknown>)[key] = newArray;\n }\n } else {\n // If output's value (from target) is not an array,\n // overwrite with a shallow copy of the source array.\n (output as Record<string, unknown>)[key] = [...sourceValue];\n }\n } else if (isPlainObject(sourceValue)) {\n // If source value is a plain object (not a class instance like EntityReference, GeoPoint, etc.):\n if (isPlainObject(outputValue)) {\n // If the corresponding value in output (from target) is also a plain object, recurse.\n // Ensure the ignoreUndefined flag is passed down.\n (output as Record<string, unknown>)[key] = mergeDeep(outputValue as Record<string, unknown>, sourceValue, ignoreUndefined);\n } else {\n // If output's value (from target) is not a plain object (e.g., null, primitive, class instance, or key didn't exist in original target),\n // overwrite with the source object.\n (output as Record<string, unknown>)[key] = sourceValue;\n }\n } else if (isObject(sourceValue)) {\n // If source value is a class instance (not a plain object), use it directly to preserve prototype\n (output as Record<string, unknown>)[key] = sourceValue;\n } else {\n // If source value is a primitive, null, or undefined (and not ignored).\n (output as Record<string, unknown>)[key] = sourceValue;\n }\n }\n }\n\n return output as T & U;\n}\n\nexport function getValueInPath(o: object | undefined, path: string): unknown {\n if (!o) return undefined;\n if (typeof o === \"object\") {\n if (path in o) {\n return (o as Record<string, unknown>)[path];\n }\n if (path.includes(\".\") || path.includes(\"[\")) {\n let pathSegments = path.split(/[.[]/);\n if (path.includes(\"[\")) {\n pathSegments = pathSegments.map(segment => segment.replace(\"]\", \"\"));\n }\n const firstSegment = pathSegments[0];\n const isArrayAndIndexExists = Array.isArray((o as Record<string, unknown>)[firstSegment]) && !isNaN(parseInt(pathSegments[1]));\n const nextObject = isArrayAndIndexExists\n ? ((o as Record<string, unknown>)[firstSegment] as unknown[])[parseInt(pathSegments[1])]\n : (o as Record<string, unknown>)[firstSegment];\n\n const nextPath = pathSegments.slice(isArrayAndIndexExists ? 2 : 1).join(\".\");\n if (nextPath === \"\")\n return nextObject;\n return getValueInPath(nextObject as object | undefined, nextPath);\n }\n }\n return undefined;\n}\n\nexport function removeInPath(o: object, path: string): object | undefined {\n const res = clone(o) as Record<string, unknown>;\n let current = res;\n const parts = path.split(\".\");\n const last = parts.pop();\n for (const part of parts) {\n if (part in current && current[part] !== null && typeof current[part] === \"object\") {\n current[part] = clone(current[part]) as Record<string, unknown>;\n current = current[part] as Record<string, unknown>;\n } else {\n return res;\n }\n }\n if (last && current && typeof current === \"object\") {\n delete current[last];\n }\n return res;\n}\n\nexport function removeFunctions(o: unknown): unknown {\n if (o === undefined) return undefined;\n if (o === null) return null;\n if (typeof o === \"object\") {\n // Handle arrays first - drop function elements, then recurse.\n // Only object *properties* used to be filtered, so a function sitting\n // directly in an array survived — and the callers strip functions\n // precisely because a function survives no deep comparison.\n if (Array.isArray(o)) {\n return o\n .filter(v => typeof v !== \"function\")\n .map(v => removeFunctions(v));\n }\n // Preserve class instances (EntityReference, GeoPoint, etc.) - don't recurse into them\n if (!isPlainObject(o)) {\n return o;\n }\n return Object.entries(o)\n .filter(([_, value]) => typeof value !== \"function\")\n .reduce<Record<string, unknown>>((acc, [key, value]) => {\n acc[key] = removeFunctions(value);\n return acc;\n }, {});\n }\n return o;\n}\n\nexport function getHashValue<T>(v: T): string | null {\n if (!v) return null;\n if (typeof v === \"object\" && v !== null) {\n if (\"id\" in v)\n return String((v as Record<string, unknown>).id);\n else if (v instanceof Date)\n return v.toLocaleString();\n else if (v instanceof GeoPoint)\n return hash(v as Record<string, unknown>);\n }\n return hash(v as object, { ignoreUnknown: true });\n}\n\nexport function removeUndefined(value: unknown, removeEmptyStrings?: boolean): unknown {\n if (typeof value === \"function\") {\n return value;\n }\n if (Array.isArray(value)) {\n return value.map((v: unknown) => removeUndefined(v, removeEmptyStrings));\n }\n if (typeof value === \"object\") {\n if (value === null)\n return value;\n // Preserve class instances (EntityReference, GeoPoint, etc.) - don't recurse into them\n if (!isPlainObject(value)) {\n return value;\n }\n const res: Record<string, unknown> = {};\n Object.keys(value).forEach((key) => {\n if (!isEmptyObject(value as object)) {\n const childRes = removeUndefined((value as Record<string, unknown>)[key], removeEmptyStrings);\n const isString = typeof childRes === \"string\";\n const shouldKeepIfString = !removeEmptyStrings || (removeEmptyStrings && !isString) || (removeEmptyStrings && isString && childRes !== \"\");\n if (childRes !== undefined && !isEmptyObject(childRes as object) && shouldKeepIfString)\n res[key] = childRes;\n }\n });\n return res;\n }\n return value;\n}\n\nexport function removeNulls(value: unknown): unknown {\n if (typeof value === \"function\") {\n return value;\n }\n if (Array.isArray(value)) {\n return value.map((v: unknown) => removeNulls(v));\n }\n if (typeof value === \"object\") {\n if (value === null)\n return value;\n // Preserve class instances (EntityReference, GeoPoint, etc.) - don't recurse into them\n if (!isPlainObject(value)) {\n return value;\n }\n const res: Record<string, unknown> = {};\n const obj = value as Record<string, unknown>;\n Object.keys(obj).forEach((key) => {\n if (obj[key] !== null)\n res[key] = removeNulls(obj[key]);\n });\n return res;\n }\n return value;\n}\n\nexport function isEmptyObject(obj: object) {\n return obj &&\n Object.getPrototypeOf(obj) === Object.prototype &&\n Object.keys(obj).length === 0\n}\n\nexport function removePropsIfExisting(source: Record<string, unknown> | unknown[], comparison: Record<string, unknown> | unknown[]) {\n const isObject = (val: unknown): val is Record<string, unknown> => typeof val === \"object\" && val !== null;\n const isArray = (val: unknown): val is unknown[] => Array.isArray(val);\n\n if (!isObject(source) || !isObject(comparison)) {\n return source;\n }\n\n const res = isArray(source) ? [...source] : { ...source };\n\n if (isArray(res)) {\n for (let i = res.length - 1; i >= 0; i--) {\n if (res[i] === comparison[i]) {\n res.splice(i, 1);\n } else if (isObject(res[i]) && isObject(comparison[i])) {\n res[i] = removePropsIfExisting(res[i] as unknown as Record<string, unknown>, (comparison as unknown as unknown[])[i] as Record<string, unknown>);\n }\n }\n } else {\n Object.keys(comparison).forEach(key => {\n if (key in res) {\n if (isObject(res[key]) && isObject(comparison[key])) {\n res[key] = removePropsIfExisting(res[key], comparison[key]);\n } else if (res[key] === comparison[key]) {\n delete res[key];\n }\n }\n });\n }\n\n return res;\n}\n","/**\n * Normalise a value that may be a single item or a list into a list.\n *\n * Only `null`/`undefined` mean \"nothing\". A truthiness check here silently\n * swallowed legitimate values — `toArray(0)`, `toArray(false)` and `toArray(\"\")`\n * all came back empty, so a caller normalising a single falsy item lost it.\n */\nexport function toArray<T>(input?: T | T[] | null): T[] {\n if (Array.isArray(input)) return input;\n if (input === undefined || input === null) return [];\n return [input];\n}\n","export const defaultDateFormat = \"MMMM dd, yyyy, HH:mm:ss\";\n\n/** Seven days, the distance past which a relative phrase stops being useful. */\nconst DEFAULT_MAX_MS = 7 * 24 * 60 * 60 * 1000;\n\nexport type FormatRelativeTimeOptions = {\n /**\n * The instant the distance is measured from. Defaults to the current time.\n * Pass it explicitly to make a caller testable without faking the clock.\n */\n now?: Date | number;\n /**\n * How far a value may sit from {@link now} and still be described\n * relatively. Beyond it the function returns `null` and the caller renders\n * an absolute date instead. Defaults to seven days.\n */\n maxMs?: number;\n};\n\nfunction toTime(value: Date | string | number | null | undefined): number | null {\n if (value === null || value === undefined || value === \"\") return null;\n const time = value instanceof Date ? value.getTime() : new Date(value).getTime();\n return Number.isNaN(time) ? null : time;\n}\n\n/**\n * Describes an instant relative to another one — \"5m ago\", \"in 3h\".\n *\n * The direction is part of the answer. Every hand-rolled version of this in the\n * codebase computed `now - then` and then tested only the positive side, so a\n * timestamp in the future fell through to whichever branch happened to be\n * first: a date scheduled for next month read \"Just now\", and one a couple of\n * hours out read \"-1d ago\". Both are dates a CMS holds all the time — a publish\n * date, a due date, an expiry — and neither shape can occur here, because the\n * distance is measured with {@link Math.abs} and the tense is chosen from the\n * sign rather than assumed.\n *\n * Returns `null` when the value is unreadable, or when it is further than\n * {@link FormatRelativeTimeOptions.maxMs} away in either direction. `null` is\n * \"say it another way\", not an error: the caller owns the absolute format, and\n * the locale and precision that go with it.\n */\nexport function formatRelativeTime(\n value: Date | string | number | null | undefined,\n options: FormatRelativeTimeOptions = {}\n): string | null {\n const then = toTime(value);\n if (then === null) return null;\n\n const now = options.now instanceof Date ? options.now.getTime() : (options.now ?? Date.now());\n const maxMs = options.maxMs ?? DEFAULT_MAX_MS;\n\n // Positive is the past, which is the only case the callers used to handle.\n const delta = now - then;\n const distance = Math.abs(delta);\n if (distance > maxMs) return null;\n\n const future = delta < 0;\n\n const minutes = Math.floor(distance / 60_000);\n if (minutes < 1) return future ? \"in a moment\" : \"just now\";\n if (minutes < 60) return future ? `in ${minutes}m` : `${minutes}m ago`;\n\n const hours = Math.floor(distance / 3_600_000);\n if (hours < 24) return future ? `in ${hours}h` : `${hours}h ago`;\n\n const days = Math.floor(distance / 86_400_000);\n return future ? `in ${days}d` : `${days}d ago`;\n}\n","/**\n * Reading and writing the small amounts of JSON a UI keeps between sessions —\n * open tabs, column widths, collapsed groups, recent searches.\n *\n * Every one of those reads is a read of *aged* state: it was written by whatever\n * version of the app the user last ran, and it is parsed by this one. The same\n * class the database upgrade path is careful about, in a place nothing migrates.\n *\n * A hand-rolled `JSON.parse(localStorage.getItem(key)!)` has four ways to throw\n * and no way to recover from any of them:\n *\n * - `localStorage` itself throws on access when storage is disabled (Safari\n * private browsing, blocked third-party cookies) or absent (SSR, Node).\n * - the stored text is not JSON, because a write was interrupted or a user\n * edited it.\n * - the stored text is valid JSON of the *wrong shape*, because an older\n * release wrote an object where this one expects an array. `parsed.map` is\n * then not a function.\n * - `setItem` throws `QuotaExceededError` once the origin's few megabytes are\n * full, which a view that persists query text on every edit will reach.\n *\n * When any of those happens inside a `useState` initializer it throws during\n * render, and the bad value is still there on reload, so the view is bricked\n * until someone opens devtools. These helpers turn all four into the fallback.\n */\n\nexport interface WebStorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/**\n * The ambient `localStorage`, or `null` where there is not one. Access itself\n * is what throws when storage is disabled, so even reaching for it is guarded.\n */\nexport function getWebStorage(): WebStorageLike | null {\n try {\n const storage = (globalThis as { localStorage?: WebStorageLike }).localStorage;\n return storage ?? null;\n } catch {\n return null;\n }\n}\n\nexport type ReadStoredJsonOptions<T> = {\n /** Returned whenever the stored value is missing, unreadable or rejected. */\n fallback: T;\n /**\n * Whether the parsed value is the shape this caller expects. Pass it\n * whenever the fallback is an array or a keyed object: valid JSON of the\n * wrong shape is the failure an upgrade actually produces, and it survives\n * `JSON.parse` untouched to fail later at the first `.map` or `.find`.\n */\n accept?: (value: unknown) => boolean;\n /** Defaults to the ambient `localStorage`. */\n storage?: WebStorageLike | null;\n};\n\n/**\n * Reads and parses a JSON value a previous session stored, falling back rather\n * than throwing. See the module comment for what it is falling back from.\n *\n * A rejected value is deliberately left in place rather than cleared: this\n * version not understanding it is not evidence that nothing does.\n */\nexport function readStoredJson<T>(key: string, options: ReadStoredJsonOptions<T>): T {\n const storage = options.storage === undefined ? getWebStorage() : options.storage;\n if (!storage) return options.fallback;\n\n let raw: string | null;\n try {\n raw = storage.getItem(key);\n } catch {\n return options.fallback;\n }\n if (raw === null || raw === \"\") return options.fallback;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return options.fallback;\n }\n\n if (options.accept && !options.accept(parsed)) return options.fallback;\n return parsed as T;\n}\n\n/**\n * Persists a value as JSON. Returns whether it was stored, so a caller that\n * cares can say so — most do not, and for them the point is simply that a full\n * quota does not throw out of the effect doing the writing.\n */\nexport function writeStoredJson(\n key: string,\n value: unknown,\n options: { storage?: WebStorageLike | null } = {}\n): boolean {\n const storage = options.storage === undefined ? getWebStorage() : options.storage;\n if (!storage) return false;\n try {\n storage.setItem(key, JSON.stringify(value));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Persists an already-serialised string, for the values kept as plain text\n * rather than JSON — a selected id, a pane size.\n */\nexport function writeStoredString(\n key: string,\n value: string,\n options: { storage?: WebStorageLike | null } = {}\n): boolean {\n const storage = options.storage === undefined ? getWebStorage() : options.storage;\n if (!storage) return false;\n try {\n storage.setItem(key, value);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Reads a plain string, absent rather than throwing where there is no storage. */\nexport function readStoredString(\n key: string,\n options: { storage?: WebStorageLike | null } = {}\n): string | null {\n const storage = options.storage === undefined ? getWebStorage() : options.storage;\n if (!storage) return null;\n try {\n return storage.getItem(key);\n } catch {\n return null;\n }\n}\n\n/** `accept` for a caller whose fallback is an array. */\nexport const isArrayValue = (value: unknown): boolean => Array.isArray(value);\n\n/** `accept` for a caller whose fallback is a keyed object — and not an array. */\nexport const isRecordValue = (value: unknown): boolean =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n","export function hashString(str: string): number {\n if (!str) return 0;\n let hash = 0;\n let i;\n let chr;\n for (i = 0; i < str.length; i++) {\n chr = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0; // Convert to 32bit integer\n }\n return Math.abs(hash);\n}\n","/**\n * Minimal SHA-1 implementation that runs in both Node and the browser.\n *\n * This exists because generated Postgres policy names embed a SHA-1 digest of\n * the security rule. The DDL generator runs on the server (where `node:crypto`\n * is available) but the Studio has to derive the same names in the browser to\n * tell a policy it generated apart from one it did not. `node:crypto` cannot be\n * bundled for the browser, so the shared derivation needs a portable digest.\n *\n * SHA-1 is used purely to name things deterministically — never for security.\n * The output is byte-identical to `createHash(\"sha1\").update(str).digest(\"hex\")`,\n * which `sha1.test.ts` pins against `node:crypto` directly.\n */\n\n/** Rotate a 32-bit word left by `n` bits. */\nfunction rotl(value: number, n: number): number {\n return (value << n) | (value >>> (32 - n));\n}\n\n/**\n * SHA-1 digest of a string, hex-encoded.\n *\n * The input is encoded as UTF-8, matching Node's default handling of strings\n * passed to `hash.update(str)`.\n */\nexport function sha1Hex(input: string): string {\n const bytes: number[] = Array.from(new TextEncoder().encode(input));\n const bitLength = bytes.length * 8;\n\n // Padding: 0x80, then zeroes up to 56 bytes mod 64, then the length as a\n // 64-bit big-endian integer.\n bytes.push(0x80);\n while (bytes.length % 64 !== 56) bytes.push(0);\n\n const hi = Math.floor(bitLength / 0x100000000);\n const lo = bitLength >>> 0;\n bytes.push((hi >>> 24) & 0xff, (hi >>> 16) & 0xff, (hi >>> 8) & 0xff, hi & 0xff);\n bytes.push((lo >>> 24) & 0xff, (lo >>> 16) & 0xff, (lo >>> 8) & 0xff, lo & 0xff);\n\n let h0 = 0x67452301;\n let h1 = 0xefcdab89;\n let h2 = 0x98badcfe;\n let h3 = 0x10325476;\n let h4 = 0xc3d2e1f0;\n\n const w = new Array<number>(80);\n\n for (let offset = 0; offset < bytes.length; offset += 64) {\n for (let i = 0; i < 16; i++) {\n const j = offset + i * 4;\n w[i] = ((bytes[j] << 24) | (bytes[j + 1] << 16) | (bytes[j + 2] << 8) | bytes[j + 3]) | 0;\n }\n for (let i = 16; i < 80; i++) {\n w[i] = rotl(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);\n }\n\n let a = h0;\n let b = h1;\n let c = h2;\n let d = h3;\n let e = h4;\n\n for (let i = 0; i < 80; i++) {\n let f: number;\n let k: number;\n if (i < 20) {\n f = (b & c) | (~b & d);\n k = 0x5a827999;\n } else if (i < 40) {\n f = b ^ c ^ d;\n k = 0x6ed9eba1;\n } else if (i < 60) {\n f = (b & c) | (b & d) | (c & d);\n k = 0x8f1bbcdc;\n } else {\n f = b ^ c ^ d;\n k = 0xca62c1d6;\n }\n\n const temp = (rotl(a, 5) + f + e + k + w[i]) | 0;\n e = d;\n d = c;\n c = rotl(b, 30);\n b = a;\n a = temp;\n }\n\n h0 = (h0 + a) | 0;\n h1 = (h1 + b) | 0;\n h2 = (h2 + c) | 0;\n h3 = (h3 + d) | 0;\n h4 = (h4 + e) | 0;\n }\n\n return [h0, h1, h2, h3, h4]\n .map(word => (word >>> 0).toString(16).padStart(8, \"0\"))\n .join(\"\");\n}\n","import type { SecurityOperation, SecurityRule } from \"@rebasepro/types\";\nimport { sha1Hex } from \"./sha1\";\n\n/**\n * Naming of the Postgres policies generated from a collection's security rules.\n *\n * A rule without an explicit `name` is compiled to `<table>_<op>_<hash>`, where\n * the hash covers the rule's semantics. The Studio needs the same names to tell\n * \"this policy came from your code\" apart from \"someone wrote this in SQL\" —\n * without them it treats generated policies as foreign and offers to import\n * them back into the codebase they came from.\n *\n * This is the single definition of that naming. The DDL and Drizzle generators\n * both derive names from here, so a change cannot silently rename every policy\n * in every deployed database while the UI keeps matching the old ones.\n */\n\n/** Stable digest of the parts of a rule that determine what the policy does. */\nexport function getPolicyNameHash(rule: SecurityRule): string {\n const data = JSON.stringify({\n a: rule.access,\n m: rule.mode,\n op: rule.operation,\n ops: rule.operations?.slice().sort(),\n own: rule.ownerField,\n rol: rule.roles?.slice().sort(),\n pg: rule.pgRoles?.slice().sort(),\n u: rule.using,\n w: rule.withCheck,\n c: rule.condition,\n ch: rule.check\n });\n return sha1Hex(data).substring(0, 7);\n}\n\n/** The operations a rule expands to — `operations` wins over `operation`. */\nexport function getPolicyOperations(rule: SecurityRule): readonly SecurityOperation[] {\n return rule.operations && rule.operations.length > 0\n ? rule.operations\n : [rule.operation ?? \"all\"];\n}\n\n/**\n * Every Postgres policy name a single rule compiles to — one per operation.\n *\n * @param rule The security rule as written in the collection config.\n * @param tableName The rule's table (see `getTableName` in `@rebasepro/common`).\n */\nexport function getPolicyNamesForRule(rule: SecurityRule, tableName: string): string[] {\n const ops = getPolicyOperations(rule);\n const ruleHash = getPolicyNameHash(rule);\n\n return ops.map((op, opIdx) => rule.name\n ? (ops.length > 1 ? `${rule.name}_${op}` : rule.name)\n : `${tableName}_${op}_${ruleHash}${ops.length > 1 ? `_${opIdx}` : \"\"}`);\n}\n\n/** Every policy name a set of rules compiles to, for membership checks. */\nexport function getPolicyNamesForRules(rules: SecurityRule[], tableName: string): Set<string> {\n const names = new Set<string>();\n for (const rule of rules) {\n for (const name of getPolicyNamesForRule(rule, tableName)) names.add(name);\n }\n return names;\n}\n","export function serializeRegExp(input: RegExp): string {\n if (!input) return \"\";\n // const fragments = input.toString().match(/\\/(.*?)\\/([a-z]*)?$/i);\n // if (fragments) {\n // if (fragments[2])\n // return input.toString();\n // return fragments[1];\n // }\n return input.toString();\n}\n\n/**\n * Get a RegExp out of a serialized string\n * @param input\n */\nexport function hydrateRegExp(input?: string): RegExp | undefined {\n if (!input) return undefined;\n const fragments = input.match(/\\/(.*?)\\/([a-z]*)?$/i);\n if (fragments) {\n return new RegExp(fragments[1], fragments[2] || \"\");\n } else {\n return new RegExp(input, \"\");\n }\n}\n\n/**\n * Is `input` something {@link hydrateRegExp} can turn into a working RegExp?\n *\n * This used to pattern-match the *shape* of a regex literal and, failing that,\n * fall back to \"does it contain any regex-ish character\" — which said yes to\n * malformed input like `/[a-z/g`. The only answer that matters to a caller is\n * whether hydration succeeds, so ask the engine instead of approximating it.\n */\nexport function isValidRegExp(input: string): boolean {\n if (!input) return false;\n try {\n return hydrateRegExp(input) !== undefined;\n } catch {\n return false;\n }\n}\n","export function flattenObject(obj: Record<string, unknown>, parentKey = \"\") {\n if (!obj) return obj;\n return Object.keys(obj).reduce((flatObj, key) => {\n const newKey = parentKey ? `${parentKey}.${key}` : key;\n\n if (typeof obj[key] === \"object\" && obj[key] !== null) {\n if (Array.isArray(obj[key])) {\n obj[key].forEach((item: unknown, index: number) => {\n if (typeof item === \"object\" && item !== null) {\n Object.assign(flatObj, flattenObject(item as Record<string, unknown>, `${newKey}[${index}]`));\n } else {\n flatObj[`${newKey}[${index}]`] = item;\n }\n });\n } else {\n Object.assign(flatObj, flattenObject(obj[key] as Record<string, unknown>, newKey));\n }\n } else {\n flatObj[newKey] = obj[key];\n }\n\n return flatObj;\n }, {} as { [key: string]: unknown });\n}\n\n\n// map from nested property key like \"a.b.c\" to the maximum array count found in a list of objects for that array\nexport type ArrayValuesCount = Record<string, number>;\n\nexport function getArrayValuesCount(array: Record<string, unknown>[]): ArrayValuesCount {\n return array.reduce((acc: ArrayValuesCount, obj: Record<string, unknown>) => {\n Object.entries(obj).forEach(([key, value]) => {\n // proceed only if value is an array\n if (Array.isArray(value)) {\n acc[key] = Math.max(acc[key] || 0, value.length);\n }\n\n // handle nested object\n if (typeof value === \"object\" && value !== null) {\n const nested = getArrayValuesCount([value as Record<string, unknown>]);\n Object.entries(nested).forEach(([nestedKey, nestedCount]) => {\n const compoundKey = `${key}.${nestedKey}`;\n acc[compoundKey] = Math.max(acc[compoundKey] || 0, nestedCount);\n });\n }\n });\n return acc;\n }, {});\n}\n","/**\n * Returns the plural of an English word.\n *\n * @param {string} word\n * @param {number} [amount]\n * @returns {string}\n */\nexport function plural(word: string, amount?: number): string {\n if (amount !== undefined && amount === 1) {\n return word\n }\n const plurals: { [key: string]: string } = {\n \"(quiz)$\": \"$1zes\",\n \"^(ox)$\": \"$1en\",\n \"([m|l])ouse$\": \"$1ice\",\n \"(matr|vert|ind)ix|ex$\": \"$1ices\",\n \"(x|ch|ss|sh)$\": \"$1es\",\n \"([^aeiouy]|qu)y$\": \"$1ies\",\n \"(hive)$\": \"$1s\",\n \"(?:([^f])fe|([lr])f)$\": \"$1$2ves\",\n \"(shea|lea|loa|thie)f$\": \"$1ves\",\n sis$: \"ses\",\n \"([ti])um$\": \"$1a\",\n \"(tomat|potat|ech|her|vet)o$\": \"$1oes\",\n \"(bu)s$\": \"$1ses\",\n \"(alias)$\": \"$1es\",\n \"(octop)us$\": \"$1i\",\n \"(ax|test)is$\": \"$1es\",\n \"(us)$\": \"$1es\",\n \"([^s]+)$\": \"$1s\"\n }\n const irregular: { [key: string]: string } = {\n move: \"moves\",\n foot: \"feet\",\n goose: \"geese\",\n sex: \"sexes\",\n child: \"children\",\n man: \"men\",\n tooth: \"teeth\",\n person: \"people\"\n }\n const uncountable: string[] = [\n \"sheep\",\n \"fish\",\n \"deer\",\n \"moose\",\n \"series\",\n \"species\",\n \"money\",\n \"rice\",\n \"information\",\n \"equipment\",\n \"bison\",\n \"cod\",\n \"offspring\",\n \"pike\",\n \"salmon\",\n \"shrimp\",\n \"swine\",\n \"trout\",\n \"aircraft\",\n \"hovercraft\",\n \"spacecraft\",\n \"sugar\",\n \"tuna\",\n \"you\",\n \"wood\"\n ]\n // save some time in the case that singular and plural are the same\n if (uncountable.indexOf(word.toLowerCase()) >= 0) {\n return word;\n }\n // check for irregular forms\n for (const w in irregular) {\n const pattern = new RegExp(`${w}$`, \"i\")\n const replace = irregular[w]\n if (pattern.test(word)) {\n return word.replace(pattern, replace);\n }\n }\n // check for matches using regular expressions\n for (const reg in plurals) {\n const pattern = new RegExp(reg, \"i\")\n if (pattern.test(word)) {\n return word.replace(pattern, plurals[reg])\n }\n }\n return word;\n}\n\n/**\n * Returns the singular of an English word.\n *\n * @param {string} word\n * @param {number} [amount]\n * @returns {string}\n */\nexport function singular(word: string, amount?: number): string {\n if (amount !== undefined && amount !== 1) {\n return word;\n }\n const singulars: { [key: string]: string } = {\n \"(quiz)zes$\": \"$1\",\n \"(matr)ices$\": \"$1ix\",\n \"(vert|ind)ices$\": \"$1ex\",\n \"^(ox)en$\": \"$1\",\n \"(alias)es$\": \"$1\",\n \"(octop|vir)i$\": \"$1us\",\n \"(cris|ax|test)es$\": \"$1is\",\n \"(shoe)s$\": \"$1\",\n \"(o)es$\": \"$1\",\n \"(bus)es$\": \"$1\",\n \"([m|l])ice$\": \"$1ouse\",\n \"(x|ch|ss|sh)es$\": \"$1\",\n \"(m)ovies$\": \"$1ovie\",\n \"(s)eries$\": \"$1eries\",\n \"([^aeiouy]|qu)ies$\": \"$1y\",\n \"([lr])ves$\": \"$1f\",\n \"(tive)s$\": \"$1\",\n \"(hive)s$\": \"$1\",\n \"(li|wi|kni)ves$\": \"$1fe\",\n \"(shea|loa|lea|thie)ves$\": \"$1f\",\n \"(^analy)ses$\": \"$1sis\",\n \"((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$\": \"$1$2sis\",\n \"([ti])a$\": \"$1um\",\n \"(n)ews$\": \"$1ews\",\n \"(h|bl)ouses$\": \"$1ouse\",\n \"(corpse)s$\": \"$1\",\n \"(us)es$\": \"$1\",\n s$: \"\"\n }\n const irregular: { [key: string]: string } = {\n move: \"moves\",\n foot: \"feet\",\n goose: \"geese\",\n sex: \"sexes\",\n child: \"children\",\n man: \"men\",\n tooth: \"teeth\",\n person: \"people\"\n }\n const uncountable: string[] = [\n \"sheep\",\n \"fish\",\n \"deer\",\n \"moose\",\n \"series\",\n \"species\",\n \"money\",\n \"rice\",\n \"information\",\n \"equipment\",\n \"bison\",\n \"cod\",\n \"offspring\",\n \"pike\",\n \"salmon\",\n \"shrimp\",\n \"swine\",\n \"trout\",\n \"aircraft\",\n \"hovercraft\",\n \"spacecraft\",\n \"sugar\",\n \"tuna\",\n \"you\",\n \"wood\"\n ]\n // save some time in the case that singular and plural are the same\n if (uncountable.indexOf(word.toLowerCase()) >= 0) {\n return word;\n }\n // check for irregular forms\n for (const w in irregular) {\n const pattern = new RegExp(`${irregular[w]}$`, \"i\");\n if (pattern.test(word)) {\n return word.replace(pattern, w);\n }\n }\n // check for matches using regular expressions\n for (const reg in singulars) {\n const pattern = new RegExp(reg, \"i\");\n if (pattern.test(word)) {\n return word.replace(pattern, singulars[reg]);\n }\n }\n return word;\n}\n","import { singular } from \"./plurals\";\nimport { toSnakeCase } from \"./strings\";\n\n/**\n * Generates a foreign key column name from a given string, typically a collection slug or name.\n * It singularizes the name, converts it to snake_case and appends '_id'.\n *\n * Singularization runs *before* snake-casing so that acronyms survive: `toSnakeCase`\n * splits on every capital, which turned \"URLs\" into \"ur_ls\" and then \"ur_l_id\".\n *\n * @param name The base name to convert to a foreign key.\n * @returns A foreign key name in the format 'singular_name_id'.\n *\n * @example\n * // returns \"user_id\"\n * generateForeignKeyName(\"users\")\n *\n * @example\n * // returns \"category_id\"\n * generateForeignKeyName(\"categories\")\n *\n * @example\n * // returns \"product_id\"\n * generateForeignKeyName(\"Product\")\n *\n */\nexport function generateForeignKeyName(name: string): string {\n return `${toSnakeCase(singularizeForKey(name))}_id`;\n}\n\n/**\n * `singular()` handles real English plurals, but its final catch-all rule strips\n * any trailing \"s\", which mangles words that only look plural. Guard the two\n * cases that produce a column name nobody would recognise:\n *\n * - a double \"s\" ending is never a plural marker (\"address\", \"class\", \"process\"),\n * so stripping it yields \"addres\";\n * - a name that singularizes to nothing (the literal \"s\") would yield \"_id\".\n */\nfunction singularizeForKey(name: string): string {\n if (/ss$/i.test(name)) return name;\n const result = singular(name);\n return result.length > 0 ? result : name;\n}\n\n/**\n * What `generateForeignKeyName` returned before it learned to singularize:\n * snake-case the name, then chop one trailing \"s\".\n *\n * This is here to be *detected*, never to be generated. A database provisioned\n * under the old rule carries `categorie_id`, `addresse_id`, `children_id` or\n * `ur_l_id` where the current rule expects `category_id`, `address_id`,\n * `child_id` and `url_id` — and the boot-time schema ensure is additive, so it\n * would create the new column empty beside the populated old one and leave the\n * relation reading nothing. No error, no missing table: the failure is silent,\n * which is the only reason this function still exists.\n *\n * `ensureCollectionSchema` calls it to recognise that shape and say so.\n * Returns the same string as `generateForeignKeyName` for every regular plural,\n * so a caller can compare the two and act only when they differ.\n */\nexport function legacyForeignKeyName(name: string): string {\n const snake = toSnakeCase(name);\n return `${snake.endsWith(\"s\") ? snake.slice(0, -1) : snake}_id`;\n}\n\n/**\n * Truncate an identifier to what Postgres will actually store.\n *\n * Postgres silently truncates identifiers at NAMEDATALEN-1 = 63 **bytes**, so a\n * name generated longer than that is not the name the database ends up holding.\n * Anything that later looks the object up by the name it generated then misses.\n *\n * Byte length, not string length: NAMEDATALEN is a byte bound, and a multi-byte\n * character straddling the boundary would be cut mid-sequence by `slice(0, 63)`.\n *\n * `TextEncoder` rather than `Buffer`, which is not a matter of taste: `Buffer`\n * is a Node global, and this package is imported by browser-facing ones. It\n * typechecked only where `@types/node` happened to be in scope, so\n * `packages/codegen` — whose tsconfig is `lib: [\"ESNext\", \"dom\"]` — could not\n * compile the file at all, and both of its suites failed to run. `TextEncoder`\n * and `TextDecoder` are standard in both runtimes and need no ambient types.\n */\nexport function toPostgresIdentifier(name: string): string {\n const bytes = new TextEncoder().encode(name);\n if (bytes.byteLength <= 63) return name;\n // Decoding a slice that ends mid-character yields U+FFFD; dropping it lands\n // on the last whole character that fits, which is what Postgres does.\n return new TextDecoder(\"utf-8\").decode(bytes.subarray(0, 63)).replace(/�+$/, \"\");\n}\n\n/**\n * The API name a database column is served under.\n *\n * The wire name of a field is its property key, and Rebase's property keys are\n * camelCase — `displayName`, `createdAt`, `photoURL`. Columns are snake_case,\n * because an unquoted Postgres identifier folds to lower case and a camelCase\n * column is therefore reachable only as `\"authorId\"` forever: in hand-written\n * SQL, in psql, in an RLS policy body, in a dump, and in every third-party tool\n * that ever touches the database. So the two conventions are both right, and\n * this is the function that crosses between them.\n *\n * It exists because two sources of field names never crossed: a foreign key\n * derived from a relation (`author_id`) and a column read back by introspection\n * (`user_id`) both landed on the wire under their column name, while every\n * hand-authored collection next to them used camelCase. One API, two\n * conventions, and no rule a caller could infer from outside — those names are\n * also the `where` and `orderBy` keys, so it was not a matter of taste.\n *\n * Rules, in the order they matter:\n *\n * - **A name with no separator is returned unchanged.** `photoURL` stays\n * `photoURL` and `id` stays `id`. Lower-casing a single token is what makes\n * a \"camelCase\" helper destructive — `camelCase(\"photoURL\")` is `photourl` —\n * and this function is applied to names that are *already* keys.\n * - **Each following segment keeps its own casing** apart from an upper-cased\n * first letter, so `photo_URL` → `photoURL` rather than `photoUrl`.\n * - **The result may still not be a JavaScript identifier.** `2fa_enabled`\n * becomes `2faEnabled`, which is a perfectly good object key and still needs\n * quoting where one is written into generated source.\n *\n * Not the inverse of {@link toSnakeCase}: `toSnakeCase` tokenises on case\n * boundaries and would turn `photoURL` into `photo_url`. Round-tripping is not\n * a property either function promises, which is why a column name that a\n * property maps explicitly is always read off `columnName` rather than derived.\n */\nexport function toWireKey(columnName: string): string {\n if (!columnName) return columnName;\n const segments = columnName.split(/[-_ ]+/).filter(Boolean);\n if (segments.length <= 1) return columnName;\n return segments\n .map((segment, index) =>\n index === 0\n ? segment.charAt(0).toLowerCase() + segment.slice(1)\n : segment.charAt(0).toUpperCase() + segment.slice(1))\n .join(\"\");\n}\n\n/**\n * The first candidate key not already used, or a numbered fallback.\n *\n * Introspection turns a set of column names into a set of object keys, and the\n * mapping is not injective: `user_id` and `userId` are two columns and one\n * {@link toWireKey}, and two foreign keys can strip to the same relation name.\n * A duplicate key in a generated object literal is a TypeScript error, so the\n * whole collection stops compiling — and a duplicate key in a `Record` built at\n * runtime is worse, because it silently drops a column instead.\n *\n * The numbered tail is what makes this total: a function that returns a key it\n * cannot guarantee is free has only moved the duplicate one line down.\n *\n * Structurally typed on `has` so a `Map` of emitted blocks and a `Set` of taken\n * names both satisfy it. Lives here, in the package both introspection\n * producers and the admin's table import can reach, because they must resolve a\n * collision the same way or one database describes itself three ways.\n */\nexport function firstFreeKey(candidates: string[], taken: { has(key: string): boolean }): string {\n for (const candidate of candidates) {\n if (!taken.has(candidate)) return candidate;\n }\n const base = candidates[candidates.length - 1];\n for (let suffix = 2; ; suffix++) {\n const candidate = `${base}_${suffix}`;\n if (!taken.has(candidate)) return candidate;\n }\n}\n","\n\nexport function isDefaultFieldConfigId(id: string): boolean {\n return [\"text_field\",\n \"multiline\",\n \"markdown\",\n \"url\",\n \"email\",\n \"switch\",\n \"select\",\n \"multi_select\",\n \"number_input\",\n \"number_select\",\n \"multi_number_select\",\n \"file_upload\",\n \"multi_file_upload\",\n \"reference\",\n \"multi_references\",\n \"relation\",\n \"date_time\",\n \"group\",\n \"key_value\",\n \"repeat\",\n \"custom_array\",\n \"block\"\n ].includes(id);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.es.js","names":[],"sources":["../src/strings.ts","../src/objects.ts","../src/arrays.ts","../src/dates.ts","../src/storage.ts","../src/hash.ts","../src/sha1.ts","../src/policy-names.ts","../src/regexp.ts","../src/flatten_object.ts","../src/plurals.ts","../src/names.ts","../src/fields.ts"],"sourcesContent":["const tokenizeRegex = /[A-Z]{2,}(?=[A-Z][a-z]|\\b)|[A-Z]?[a-z]+|[0-9]+(?:[a-z](?![a-z]))?|[A-Z]/g;\n\nexport const toKebabCase = (str?: string) => {\n if (!str || typeof str !== \"string\") return \"\";\n const regExpMatchArray = str.match(tokenizeRegex);\n if (!regExpMatchArray) return \"\";\n return regExpMatchArray\n .map(x => x.toLowerCase())\n .join(\"-\");\n};\n\nconst snakeCaseRegex = tokenizeRegex;\n\nexport const toSnakeCase = (str?: string) => {\n if (!str || typeof str !== \"string\") return \"\";\n const regExpMatchArray = str.match(snakeCaseRegex);\n if (!regExpMatchArray) return \"\";\n return regExpMatchArray\n .map(x => x.toLowerCase())\n .join(\"_\");\n};\n\nexport function camelCase(str: string): string {\n if (!str) return \"\";\n if (str.length === 1) return str.toLowerCase();\n\n // Split by hyphens, underscores, or spaces and filter out empty strings\n const parts = str.split(/[-_ ]+/).filter(Boolean);\n\n if (parts.length === 0) return \"\";\n\n // Start with first part in lowercase\n return parts[0].toLowerCase() +\n // Transform remaining parts to have first letter uppercase\n parts.slice(1)\n .map(part => part.charAt(0).toUpperCase() + part.substring(1).toLowerCase())\n .join(\"\");\n}\n\n/**\n * A random base-36 string of exactly `strLength` characters.\n *\n * Not `Math.random().toString(36).slice(2, 2 + strLength)`: that has no\n * guaranteed length. Base-36 of a double drops trailing zeros, so the source\n * string is short about once in 36 calls and the slice quietly returns fewer\n * characters than asked for — `randomString(10)` returning 9. These values\n * prefix uploaded filenames to keep them apart, so a short one is a likelier\n * collision, and it fails at the rate that makes a test look flaky.\n */\nexport function randomString(strLength = 5) {\n const alphabet = \"0123456789abcdefghijklmnopqrstuvwxyz\";\n let result = \"\";\n for (let i = 0; i < strLength; i++) {\n result += alphabet.charAt(Math.floor(Math.random() * alphabet.length));\n }\n return result;\n}\n\nexport function randomColor() {\n return Math.floor(Math.random() * 16777215).toString(16);\n}\n\nexport function slugify(text?: string, separator = \"_\", lowercase = true) {\n if (!text) return \"\";\n const from = \"ãàáäâẽèéëêìíïîõòóöôùúüûñç·/_,:;-\"\n const to = `aaaaaeeeeeiiiiooooouuuunc${separator}${separator}${separator}${separator}${separator}${separator}${separator}`;\n\n for (let i = 0, l = from.length; i < l; i++) {\n text = text.replace(new RegExp(from.charAt(i), \"g\"), to.charAt(i));\n }\n\n text = text\n .toString() // Cast to string\n .trim() // Remove whitespace from both sides of a string\n .replace(/^\\s+|\\s+$/g, \"\")\n .replace(/\\s+/g, separator) // Replace spaces with separator\n .replace(/&/g, separator) // Replace & with separator\n .replace(/[^\\w\\\\-]+/g, \"\") // Remove all non-word chars\n .replace(new RegExp(\"\\\\\" + separator + \"\\\\\" + separator + \"+\", \"g\"),\n separator); // Replace multiple separators with single one\n\n return lowercase\n ? text.toLowerCase() // Convert the string to lowercase letters\n : text;\n}\n\nexport function unslugify(slug?: string): string {\n if (!slug) return \"\";\n if (slug.includes(\"-\") || slug.includes(\"_\") || !slug.includes(\" \")) {\n const result = slug.replace(/[-_]/g, \" \");\n return result.replace(/\\w\\S*/g, function (txt) {\n return txt.charAt(0).toUpperCase() + txt.substring(1);\n }).trim();\n } else {\n return slug.trim();\n }\n}\n\nexport function prettifyIdentifier(input: string) {\n if (!input) return \"\";\n\n let text = input;\n\n // 1. Handle camelCase and Acronyms\n // Group 1 ($1 $2): Lowercase followed by Uppercase (e.g., imageURL -> image URL)\n // Group 2 ($3 $4): Uppercase followed by Uppercase+lowercase (e.g., XMLParser -> XML Parser)\n text = text.replace(/([a-z])([A-Z])|([A-Z])([A-Z][a-z])/g, \"$1$3 $2$4\");\n\n // 2. Replace hyphens/underscores with spaces\n text = text.replace(/[_-]+/g, \" \");\n\n // 3. Capitalize first letter of each word (Title Case)\n const s = text\n .trim()\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n return s;\n}\n","import hash from \"object-hash\";\nimport { GeoPoint } from \"@rebasepro/types\";\n\n/** @private is the value an empty array? */\nexport const isEmptyArray = (value?: unknown) =>\n Array.isArray(value) && value.length === 0;\n\n/** @private is the given object a Function? */\nexport const isFunction = (obj: unknown): obj is (...args: unknown[]) => unknown =>\n typeof obj === \"function\";\n\n/** @private is the given object an integer? */\nexport const isInteger = (obj: unknown): boolean =>\n String(Math.floor(Number(obj))) === String(obj);\n\n/** @private is the given object a NaN? */\n\nexport const isNaN = (obj: unknown): boolean => obj !== obj;\n\n/**\n * Segments that reach the prototype chain rather than a property of the object.\n *\n * The twin of this function in `@rebasepro/forms` could be made to write onto\n * `Object.prototype` through a path of `__proto__.x`. This copy survives the\n * write by accident — its `clone` always spreads into a fresh object, while the\n * form engine's has a \"preserve class instances\" branch that hands back\n * `Object.prototype` itself — but `getIn` still *reads* through the chain, and\n * handing back `Object.prototype` is how a polluted value is read out again.\n *\n * Closed on both sides here, so the two implementations agree.\n */\nconst UNSAFE_PATH_SEGMENTS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\n/** Whether any segment of this path would traverse the prototype chain. */\nexport function pathTraversesPrototype(path: string | string[]): boolean {\n return toPath(path).some(segment => UNSAFE_PATH_SEGMENTS.has(segment));\n}\n\n/**\n * Whether writing this single key with `obj[key] = …` would reach the prototype\n * chain instead of creating a property.\n *\n * The single-key counterpart of {@link pathTraversesPrototype}, for the many\n * places that copy an object one key at a time. `JSON.parse` creates\n * `__proto__` as an *own* property, so it survives `hasOwnProperty` — and then\n * `target[key] = value` invokes the setter and replaces the target's prototype.\n */\nexport function isPrototypePollutingKey(key: string): boolean {\n return UNSAFE_PATH_SEGMENTS.has(key);\n}\n\n/**\n * Deeply get a value from an object via its path.\n */\nexport function getIn(\n obj: Record<string, unknown> | unknown[] | unknown,\n key: string | string[],\n def?: unknown,\n p = 0\n) {\n if (pathTraversesPrototype(key)) return def;\n\n const path = toPath(key);\n while (obj && p < path.length) {\n obj = (obj as Record<string, unknown>)[path[p++]];\n }\n\n // check if path is not in the end\n if (p !== path.length && !obj) {\n return def;\n }\n\n return obj === undefined ? def : obj;\n}\n\nexport function setIn<T>(obj: T, path: string, value: unknown): T {\n // See `pathTraversesPrototype`. This copy's `clone` happens to contain the\n // write, but relying on that is relying on an implementation detail of a\n // different function.\n if (pathTraversesPrototype(path)) return obj;\n\n const res = clone(obj) as Record<string, unknown>;\n let resVal: Record<string, unknown> = res;\n let i = 0;\n const pathArray = toPath(path);\n\n for (; i < pathArray.length - 1; i++) {\n const currentPath: string = pathArray[i];\n const currentObj = getIn(obj as Record<string, unknown>, pathArray.slice(0, i + 1));\n\n if (currentObj && (isObject(currentObj) || Array.isArray(currentObj))) {\n resVal = resVal[currentPath] = clone(currentObj) as Record<string, unknown>;\n } else {\n const nextPath: string = pathArray[i + 1];\n resVal = resVal[currentPath] =\n (isInteger(nextPath) && Number(nextPath) >= 0 ? [] : {}) as Record<string, unknown>;\n }\n }\n\n // Return original object if new value is the same as current\n if ((i === 0 ? obj as Record<string, unknown> : resVal)[pathArray[i]] === value) {\n return obj;\n }\n\n if (value === undefined) {\n delete resVal[pathArray[i]];\n } else {\n resVal[pathArray[i]] = value;\n }\n\n // If the path array has a single element, the loop did not run.\n // Deleting on `resVal` had no effect in this scenario, so we delete on the result instead.\n if (i === 0 && value === undefined) {\n delete res[pathArray[i]];\n }\n\n return res as T;\n}\n\nexport function clone<T>(value: T): T {\n if (Array.isArray(value)) {\n return [...value] as T;\n } else if (typeof value === \"object\" && value !== null) {\n return { ...value } as T;\n } else {\n return value; // This is for primitive types which do not need cloning.\n }\n}\n\n/**\n * Deep clone a value, preserving function references and class instances.\n * Unlike structuredClone, this handles objects that contain functions\n * (e.g. CollectionConfig with target(), childCollections(), callbacks).\n */\nexport function deepClone<T>(value: T): T {\n if (value === null || value === undefined) return value;\n if (typeof value === \"function\") return value;\n if (typeof value !== \"object\") return value;\n\n if (Array.isArray(value)) {\n return value.map(item => deepClone(item)) as T;\n }\n\n // Preserve class instances (Date, GeoPoint, etc.) — don't recurse\n if (Object.getPrototypeOf(value) !== Object.prototype) {\n return value;\n }\n\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n result[key] = deepClone((value as Record<string, unknown>)[key]);\n }\n return result as T;\n}\n\nfunction toPath(value: string | string[]) {\n if (Array.isArray(value)) return value; // Already in path array form.\n // Replace brackets with dots, remove leading/trailing dots, then split by dot.\n return value.replace(/\\[(\\d+)]/g, \".$1\").replace(/^\\./, \"\").replace(/\\.$/, \"\").split(\".\");\n}\n\n\nexport const pick: <T extends Record<string, unknown>>(obj: T, ...args: (keyof T)[]) => Partial<T> = <T extends Record<string, unknown>>(obj: T, ...args: (keyof T)[]) => ({\n ...args.reduce<Record<string, unknown>>((res, key) => ({\n ...res,\n [key as string]: obj[key as string]\n }), {})\n}) as Partial<T>;\n\nexport function isObject(item: unknown): item is Record<string, unknown> {\n return !!item && typeof item === \"object\" && !Array.isArray(item);\n}\n\nexport function isPlainObject(obj: unknown): obj is Record<string, unknown> {\n // 1. Rule out non-objects, null, and arrays\n if (typeof obj !== \"object\" || obj === null || Array.isArray(obj)) {\n return false;\n }\n\n // 2. Get the object's direct prototype\n const proto = Object.getPrototypeOf(obj);\n\n // 3. A plain object's direct prototype is Object.prototype\n return proto === Object.prototype;\n}\n\nexport function mergeDeep<T extends object, U extends object>(\n target: T,\n source: U,\n ignoreUndefined = false\n): T & U {\n // If target is not a true object (e.g., null, array, primitive), return target itself.\n if (!isObject(target)) {\n return target as T & U;\n }\n\n // Create a shallow copy of the target to avoid modifying the original object.\n const output = { ...target };\n\n // If source is not a true object, there's nothing to merge from it.\n // Return the shallow copy of target.\n if (!isObject(source)) {\n return output as T & U;\n }\n\n // Iterate over keys in the source object.\n for (const key in source) {\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\") {\n continue;\n }\n if (Object.prototype.hasOwnProperty.call(source, key)) {\n const sourceValue = source[key];\n const outputValue = (output as Record<string, unknown>)[key]; // Current value in our merged object (originating from target)\n\n // Skip if source value is undefined and ignoreUndefined is true.\n // This handles both not adding new undefined properties and not overwriting existing properties with undefined.\n if (ignoreUndefined && sourceValue === undefined) {\n continue;\n }\n\n if (sourceValue instanceof Date) {\n // If source value is a Date, create a new Date instance.\n (output as Record<string, unknown>)[key] = new Date(sourceValue.getTime());\n } else if (Array.isArray(sourceValue)) {\n if (Array.isArray(outputValue)) {\n // If the array contains primitives or class instances (non-plain objects),\n // overwrite the array entirely instead of doing element-wise merging.\n const hasPlainObjects = sourceValue.some(isPlainObject) || outputValue.some(isPlainObject);\n if (!hasPlainObjects) {\n (output as Record<string, unknown>)[key] = [...sourceValue];\n } else {\n const newArray = [];\n const maxLength = Math.max(outputValue.length, sourceValue.length);\n for (let i = 0; i < maxLength; i++) {\n const sourceItem = sourceValue[i];\n const targetItem = outputValue[i];\n\n if (i >= sourceValue.length) { // source is shorter\n newArray[i] = targetItem;\n } else if (i >= outputValue.length) { // target is shorter\n newArray[i] = sourceItem;\n } else if (sourceItem === null) {\n newArray[i] = targetItem;\n } else if (isPlainObject(sourceItem) && isPlainObject(targetItem)) {\n // Only recursively merge plain objects, preserve class instances\n newArray[i] = mergeDeep(targetItem, sourceItem, ignoreUndefined);\n } else {\n // For class instances and primitives, use source directly\n newArray[i] = sourceItem;\n }\n }\n (output as Record<string, unknown>)[key] = newArray;\n }\n } else {\n // If output's value (from target) is not an array,\n // overwrite with a shallow copy of the source array.\n (output as Record<string, unknown>)[key] = [...sourceValue];\n }\n } else if (isPlainObject(sourceValue)) {\n // If source value is a plain object (not a class instance like EntityReference, GeoPoint, etc.):\n if (isPlainObject(outputValue)) {\n // If the corresponding value in output (from target) is also a plain object, recurse.\n // Ensure the ignoreUndefined flag is passed down.\n (output as Record<string, unknown>)[key] = mergeDeep(outputValue as Record<string, unknown>, sourceValue, ignoreUndefined);\n } else {\n // If output's value (from target) is not a plain object (e.g., null, primitive, class instance, or key didn't exist in original target),\n // overwrite with the source object.\n (output as Record<string, unknown>)[key] = sourceValue;\n }\n } else if (isObject(sourceValue)) {\n // If source value is a class instance (not a plain object), use it directly to preserve prototype\n (output as Record<string, unknown>)[key] = sourceValue;\n } else {\n // If source value is a primitive, null, or undefined (and not ignored).\n (output as Record<string, unknown>)[key] = sourceValue;\n }\n }\n }\n\n return output as T & U;\n}\n\nexport function getValueInPath(o: object | undefined, path: string): unknown {\n if (!o) return undefined;\n if (typeof o === \"object\") {\n if (path in o) {\n return (o as Record<string, unknown>)[path];\n }\n if (path.includes(\".\") || path.includes(\"[\")) {\n let pathSegments = path.split(/[.[]/);\n if (path.includes(\"[\")) {\n pathSegments = pathSegments.map(segment => segment.replace(\"]\", \"\"));\n }\n const firstSegment = pathSegments[0];\n const isArrayAndIndexExists = Array.isArray((o as Record<string, unknown>)[firstSegment]) && !isNaN(parseInt(pathSegments[1]));\n const nextObject = isArrayAndIndexExists\n ? ((o as Record<string, unknown>)[firstSegment] as unknown[])[parseInt(pathSegments[1])]\n : (o as Record<string, unknown>)[firstSegment];\n\n const nextPath = pathSegments.slice(isArrayAndIndexExists ? 2 : 1).join(\".\");\n if (nextPath === \"\")\n return nextObject;\n return getValueInPath(nextObject as object | undefined, nextPath);\n }\n }\n return undefined;\n}\n\nexport function removeInPath(o: object, path: string): object | undefined {\n const res = clone(o) as Record<string, unknown>;\n let current = res;\n const parts = path.split(\".\");\n const last = parts.pop();\n for (const part of parts) {\n if (part in current && current[part] !== null && typeof current[part] === \"object\") {\n current[part] = clone(current[part]) as Record<string, unknown>;\n current = current[part] as Record<string, unknown>;\n } else {\n return res;\n }\n }\n if (last && current && typeof current === \"object\") {\n delete current[last];\n }\n return res;\n}\n\nexport function removeFunctions(o: unknown): unknown {\n if (o === undefined) return undefined;\n if (o === null) return null;\n if (typeof o === \"object\") {\n // Handle arrays first - drop function elements, then recurse.\n // Only object *properties* used to be filtered, so a function sitting\n // directly in an array survived — and the callers strip functions\n // precisely because a function survives no deep comparison.\n if (Array.isArray(o)) {\n return o\n .filter(v => typeof v !== \"function\")\n .map(v => removeFunctions(v));\n }\n // Preserve class instances (EntityReference, GeoPoint, etc.) - don't recurse into them\n if (!isPlainObject(o)) {\n return o;\n }\n return Object.entries(o)\n .filter(([_, value]) => typeof value !== \"function\")\n .reduce<Record<string, unknown>>((acc, [key, value]) => {\n acc[key] = removeFunctions(value);\n return acc;\n }, {});\n }\n return o;\n}\n\nexport function getHashValue<T>(v: T): string | null {\n if (!v) return null;\n if (typeof v === \"object\" && v !== null) {\n if (\"id\" in v)\n return String((v as Record<string, unknown>).id);\n else if (v instanceof Date)\n return v.toLocaleString();\n else if (v instanceof GeoPoint)\n return hash(v as Record<string, unknown>);\n }\n return hash(v as object, { ignoreUnknown: true });\n}\n\nexport function removeUndefined(value: unknown, removeEmptyStrings?: boolean): unknown {\n if (typeof value === \"function\") {\n return value;\n }\n if (Array.isArray(value)) {\n return value.map((v: unknown) => removeUndefined(v, removeEmptyStrings));\n }\n if (typeof value === \"object\") {\n if (value === null)\n return value;\n // Preserve class instances (EntityReference, GeoPoint, etc.) - don't recurse into them\n if (!isPlainObject(value)) {\n return value;\n }\n const res: Record<string, unknown> = {};\n Object.keys(value).forEach((key) => {\n if (!isEmptyObject(value as object)) {\n const childRes = removeUndefined((value as Record<string, unknown>)[key], removeEmptyStrings);\n const isString = typeof childRes === \"string\";\n const shouldKeepIfString = !removeEmptyStrings || (removeEmptyStrings && !isString) || (removeEmptyStrings && isString && childRes !== \"\");\n if (childRes !== undefined && !isEmptyObject(childRes as object) && shouldKeepIfString)\n res[key] = childRes;\n }\n });\n return res;\n }\n return value;\n}\n\nexport function removeNulls(value: unknown): unknown {\n if (typeof value === \"function\") {\n return value;\n }\n if (Array.isArray(value)) {\n return value.map((v: unknown) => removeNulls(v));\n }\n if (typeof value === \"object\") {\n if (value === null)\n return value;\n // Preserve class instances (EntityReference, GeoPoint, etc.) - don't recurse into them\n if (!isPlainObject(value)) {\n return value;\n }\n const res: Record<string, unknown> = {};\n const obj = value as Record<string, unknown>;\n Object.keys(obj).forEach((key) => {\n if (obj[key] !== null)\n res[key] = removeNulls(obj[key]);\n });\n return res;\n }\n return value;\n}\n\nexport function isEmptyObject(obj: object) {\n return obj &&\n Object.getPrototypeOf(obj) === Object.prototype &&\n Object.keys(obj).length === 0\n}\n\nexport function removePropsIfExisting(source: Record<string, unknown> | unknown[], comparison: Record<string, unknown> | unknown[]) {\n const isObject = (val: unknown): val is Record<string, unknown> => typeof val === \"object\" && val !== null;\n const isArray = (val: unknown): val is unknown[] => Array.isArray(val);\n\n if (!isObject(source) || !isObject(comparison)) {\n return source;\n }\n\n const res = isArray(source) ? [...source] : { ...source };\n\n if (isArray(res)) {\n for (let i = res.length - 1; i >= 0; i--) {\n if (res[i] === comparison[i]) {\n res.splice(i, 1);\n } else if (isObject(res[i]) && isObject(comparison[i])) {\n res[i] = removePropsIfExisting(res[i] as unknown as Record<string, unknown>, (comparison as unknown as unknown[])[i] as Record<string, unknown>);\n }\n }\n } else {\n Object.keys(comparison).forEach(key => {\n if (key in res) {\n if (isObject(res[key]) && isObject(comparison[key])) {\n res[key] = removePropsIfExisting(res[key], comparison[key]);\n } else if (res[key] === comparison[key]) {\n delete res[key];\n }\n }\n });\n }\n\n return res;\n}\n","/**\n * Normalise a value that may be a single item or a list into a list.\n *\n * Only `null`/`undefined` mean \"nothing\". A truthiness check here silently\n * swallowed legitimate values — `toArray(0)`, `toArray(false)` and `toArray(\"\")`\n * all came back empty, so a caller normalising a single falsy item lost it.\n */\nexport function toArray<T>(input?: T | T[] | null): T[] {\n if (Array.isArray(input)) return input;\n if (input === undefined || input === null) return [];\n return [input];\n}\n","export const defaultDateFormat = \"MMMM dd, yyyy, HH:mm:ss\";\n\n/** Seven days, the distance past which a relative phrase stops being useful. */\nconst DEFAULT_MAX_MS = 7 * 24 * 60 * 60 * 1000;\n\nexport type FormatRelativeTimeOptions = {\n /**\n * The instant the distance is measured from. Defaults to the current time.\n * Pass it explicitly to make a caller testable without faking the clock.\n */\n now?: Date | number;\n /**\n * How far a value may sit from {@link now} and still be described\n * relatively. Beyond it the function returns `null` and the caller renders\n * an absolute date instead. Defaults to seven days.\n */\n maxMs?: number;\n};\n\nfunction toTime(value: Date | string | number | null | undefined): number | null {\n if (value === null || value === undefined || value === \"\") return null;\n const time = value instanceof Date ? value.getTime() : new Date(value).getTime();\n return Number.isNaN(time) ? null : time;\n}\n\n/**\n * Describes an instant relative to another one — \"5m ago\", \"in 3h\".\n *\n * The direction is part of the answer. Every hand-rolled version of this in the\n * codebase computed `now - then` and then tested only the positive side, so a\n * timestamp in the future fell through to whichever branch happened to be\n * first: a date scheduled for next month read \"Just now\", and one a couple of\n * hours out read \"-1d ago\". Both are dates a CMS holds all the time — a publish\n * date, a due date, an expiry — and neither shape can occur here, because the\n * distance is measured with {@link Math.abs} and the tense is chosen from the\n * sign rather than assumed.\n *\n * Returns `null` when the value is unreadable, or when it is further than\n * {@link FormatRelativeTimeOptions.maxMs} away in either direction. `null` is\n * \"say it another way\", not an error: the caller owns the absolute format, and\n * the locale and precision that go with it.\n */\nexport function formatRelativeTime(\n value: Date | string | number | null | undefined,\n options: FormatRelativeTimeOptions = {}\n): string | null {\n const then = toTime(value);\n if (then === null) return null;\n\n const now = options.now instanceof Date ? options.now.getTime() : (options.now ?? Date.now());\n const maxMs = options.maxMs ?? DEFAULT_MAX_MS;\n\n // Positive is the past, which is the only case the callers used to handle.\n const delta = now - then;\n const distance = Math.abs(delta);\n if (distance > maxMs) return null;\n\n const future = delta < 0;\n\n const minutes = Math.floor(distance / 60_000);\n if (minutes < 1) return future ? \"in a moment\" : \"just now\";\n if (minutes < 60) return future ? `in ${minutes}m` : `${minutes}m ago`;\n\n const hours = Math.floor(distance / 3_600_000);\n if (hours < 24) return future ? `in ${hours}h` : `${hours}h ago`;\n\n const days = Math.floor(distance / 86_400_000);\n return future ? `in ${days}d` : `${days}d ago`;\n}\n","/**\n * Reading and writing the small amounts of JSON a UI keeps between sessions —\n * open tabs, column widths, collapsed groups, recent searches.\n *\n * Every one of those reads is a read of *aged* state: it was written by whatever\n * version of the app the user last ran, and it is parsed by this one. The same\n * class the database upgrade path is careful about, in a place nothing migrates.\n *\n * A hand-rolled `JSON.parse(localStorage.getItem(key)!)` has four ways to throw\n * and no way to recover from any of them:\n *\n * - `localStorage` itself throws on access when storage is disabled (Safari\n * private browsing, blocked third-party cookies) or absent (SSR, Node).\n * - the stored text is not JSON, because a write was interrupted or a user\n * edited it.\n * - the stored text is valid JSON of the *wrong shape*, because an older\n * release wrote an object where this one expects an array. `parsed.map` is\n * then not a function.\n * - `setItem` throws `QuotaExceededError` once the origin's few megabytes are\n * full, which a view that persists query text on every edit will reach.\n *\n * When any of those happens inside a `useState` initializer it throws during\n * render, and the bad value is still there on reload, so the view is bricked\n * until someone opens devtools. These helpers turn all four into the fallback.\n */\n\nexport interface WebStorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/**\n * The ambient `localStorage`, or `null` where there is not one. Access itself\n * is what throws when storage is disabled, so even reaching for it is guarded.\n */\nexport function getWebStorage(): WebStorageLike | null {\n try {\n const storage = (globalThis as { localStorage?: WebStorageLike }).localStorage;\n return storage ?? null;\n } catch {\n return null;\n }\n}\n\nexport type ReadStoredJsonOptions<T> = {\n /** Returned whenever the stored value is missing, unreadable or rejected. */\n fallback: T;\n /**\n * Whether the parsed value is the shape this caller expects. Pass it\n * whenever the fallback is an array or a keyed object: valid JSON of the\n * wrong shape is the failure an upgrade actually produces, and it survives\n * `JSON.parse` untouched to fail later at the first `.map` or `.find`.\n */\n accept?: (value: unknown) => boolean;\n /** Defaults to the ambient `localStorage`. */\n storage?: WebStorageLike | null;\n};\n\n/**\n * Reads and parses a JSON value a previous session stored, falling back rather\n * than throwing. See the module comment for what it is falling back from.\n *\n * A rejected value is deliberately left in place rather than cleared: this\n * version not understanding it is not evidence that nothing does.\n */\nexport function readStoredJson<T>(key: string, options: ReadStoredJsonOptions<T>): T {\n const storage = options.storage === undefined ? getWebStorage() : options.storage;\n if (!storage) return options.fallback;\n\n let raw: string | null;\n try {\n raw = storage.getItem(key);\n } catch {\n return options.fallback;\n }\n if (raw === null || raw === \"\") return options.fallback;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return options.fallback;\n }\n\n if (options.accept && !options.accept(parsed)) return options.fallback;\n return parsed as T;\n}\n\n/**\n * Persists a value as JSON. Returns whether it was stored, so a caller that\n * cares can say so — most do not, and for them the point is simply that a full\n * quota does not throw out of the effect doing the writing.\n */\nexport function writeStoredJson(\n key: string,\n value: unknown,\n options: { storage?: WebStorageLike | null } = {}\n): boolean {\n const storage = options.storage === undefined ? getWebStorage() : options.storage;\n if (!storage) return false;\n try {\n storage.setItem(key, JSON.stringify(value));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Persists an already-serialised string, for the values kept as plain text\n * rather than JSON — a selected id, a pane size.\n */\nexport function writeStoredString(\n key: string,\n value: string,\n options: { storage?: WebStorageLike | null } = {}\n): boolean {\n const storage = options.storage === undefined ? getWebStorage() : options.storage;\n if (!storage) return false;\n try {\n storage.setItem(key, value);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Reads a plain string, absent rather than throwing where there is no storage. */\nexport function readStoredString(\n key: string,\n options: { storage?: WebStorageLike | null } = {}\n): string | null {\n const storage = options.storage === undefined ? getWebStorage() : options.storage;\n if (!storage) return null;\n try {\n return storage.getItem(key);\n } catch {\n return null;\n }\n}\n\n/** `accept` for a caller whose fallback is an array. */\nexport const isArrayValue = (value: unknown): boolean => Array.isArray(value);\n\n/** `accept` for a caller whose fallback is a keyed object — and not an array. */\nexport const isRecordValue = (value: unknown): boolean =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n","export function hashString(str: string): number {\n if (!str) return 0;\n let hash = 0;\n let i;\n let chr;\n for (i = 0; i < str.length; i++) {\n chr = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0; // Convert to 32bit integer\n }\n return Math.abs(hash);\n}\n","/**\n * Minimal SHA-1 implementation that runs in both Node and the browser.\n *\n * This exists because generated Postgres policy names embed a SHA-1 digest of\n * the security rule. The DDL generator runs on the server (where `node:crypto`\n * is available) but the Studio has to derive the same names in the browser to\n * tell a policy it generated apart from one it did not. `node:crypto` cannot be\n * bundled for the browser, so the shared derivation needs a portable digest.\n *\n * SHA-1 is used purely to name things deterministically — never for security.\n * The output is byte-identical to `createHash(\"sha1\").update(str).digest(\"hex\")`,\n * which `sha1.test.ts` pins against `node:crypto` directly.\n */\n\n/** Rotate a 32-bit word left by `n` bits. */\nfunction rotl(value: number, n: number): number {\n return (value << n) | (value >>> (32 - n));\n}\n\n/**\n * SHA-1 digest of a string, hex-encoded.\n *\n * The input is encoded as UTF-8, matching Node's default handling of strings\n * passed to `hash.update(str)`.\n */\nexport function sha1Hex(input: string): string {\n const bytes: number[] = Array.from(new TextEncoder().encode(input));\n const bitLength = bytes.length * 8;\n\n // Padding: 0x80, then zeroes up to 56 bytes mod 64, then the length as a\n // 64-bit big-endian integer.\n bytes.push(0x80);\n while (bytes.length % 64 !== 56) bytes.push(0);\n\n const hi = Math.floor(bitLength / 0x100000000);\n const lo = bitLength >>> 0;\n bytes.push((hi >>> 24) & 0xff, (hi >>> 16) & 0xff, (hi >>> 8) & 0xff, hi & 0xff);\n bytes.push((lo >>> 24) & 0xff, (lo >>> 16) & 0xff, (lo >>> 8) & 0xff, lo & 0xff);\n\n let h0 = 0x67452301;\n let h1 = 0xefcdab89;\n let h2 = 0x98badcfe;\n let h3 = 0x10325476;\n let h4 = 0xc3d2e1f0;\n\n const w = new Array<number>(80);\n\n for (let offset = 0; offset < bytes.length; offset += 64) {\n for (let i = 0; i < 16; i++) {\n const j = offset + i * 4;\n w[i] = ((bytes[j] << 24) | (bytes[j + 1] << 16) | (bytes[j + 2] << 8) | bytes[j + 3]) | 0;\n }\n for (let i = 16; i < 80; i++) {\n w[i] = rotl(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);\n }\n\n let a = h0;\n let b = h1;\n let c = h2;\n let d = h3;\n let e = h4;\n\n for (let i = 0; i < 80; i++) {\n let f: number;\n let k: number;\n if (i < 20) {\n f = (b & c) | (~b & d);\n k = 0x5a827999;\n } else if (i < 40) {\n f = b ^ c ^ d;\n k = 0x6ed9eba1;\n } else if (i < 60) {\n f = (b & c) | (b & d) | (c & d);\n k = 0x8f1bbcdc;\n } else {\n f = b ^ c ^ d;\n k = 0xca62c1d6;\n }\n\n const temp = (rotl(a, 5) + f + e + k + w[i]) | 0;\n e = d;\n d = c;\n c = rotl(b, 30);\n b = a;\n a = temp;\n }\n\n h0 = (h0 + a) | 0;\n h1 = (h1 + b) | 0;\n h2 = (h2 + c) | 0;\n h3 = (h3 + d) | 0;\n h4 = (h4 + e) | 0;\n }\n\n return [h0, h1, h2, h3, h4]\n .map(word => (word >>> 0).toString(16).padStart(8, \"0\"))\n .join(\"\");\n}\n","import type { SecurityOperation, SecurityRule } from \"@rebasepro/types\";\nimport { sha1Hex } from \"./sha1\";\n\n/**\n * Naming of the Postgres policies generated from a collection's security rules.\n *\n * A rule without an explicit `name` is compiled to `<table>_<op>_<hash>`, where\n * the hash covers the rule's semantics. The Studio needs the same names to tell\n * \"this policy came from your code\" apart from \"someone wrote this in SQL\" —\n * without them it treats generated policies as foreign and offers to import\n * them back into the codebase they came from.\n *\n * This is the single definition of that naming. The DDL and Drizzle generators\n * both derive names from here, so a change cannot silently rename every policy\n * in every deployed database while the UI keeps matching the old ones.\n */\n\n/** Stable digest of the parts of a rule that determine what the policy does. */\nexport function getPolicyNameHash(rule: SecurityRule): string {\n const data = JSON.stringify({\n a: rule.access,\n m: rule.mode,\n op: rule.operation,\n ops: rule.operations?.slice().sort(),\n own: rule.ownerField,\n rol: rule.roles?.slice().sort(),\n pg: rule.pgRoles?.slice().sort(),\n u: rule.using,\n w: rule.withCheck,\n c: rule.condition,\n ch: rule.check\n });\n return sha1Hex(data).substring(0, 7);\n}\n\n/** The operations a rule expands to — `operations` wins over `operation`. */\nexport function getPolicyOperations(rule: SecurityRule): readonly SecurityOperation[] {\n return rule.operations && rule.operations.length > 0\n ? rule.operations\n : [rule.operation ?? \"all\"];\n}\n\n/**\n * Every Postgres policy name a single rule compiles to — one per operation.\n *\n * @param rule The security rule as written in the collection config.\n * @param tableName The rule's table (see `getTableName` in `@rebasepro/common`).\n */\nexport function getPolicyNamesForRule(rule: SecurityRule, tableName: string): string[] {\n const ops = getPolicyOperations(rule);\n const ruleHash = getPolicyNameHash(rule);\n\n return ops.map((op, opIdx) => rule.name\n ? (ops.length > 1 ? `${rule.name}_${op}` : rule.name)\n : `${tableName}_${op}_${ruleHash}${ops.length > 1 ? `_${opIdx}` : \"\"}`);\n}\n\n/** Every policy name a set of rules compiles to, for membership checks. */\nexport function getPolicyNamesForRules(rules: SecurityRule[], tableName: string): Set<string> {\n const names = new Set<string>();\n for (const rule of rules) {\n for (const name of getPolicyNamesForRule(rule, tableName)) names.add(name);\n }\n return names;\n}\n","export function serializeRegExp(input: RegExp): string {\n if (!input) return \"\";\n // const fragments = input.toString().match(/\\/(.*?)\\/([a-z]*)?$/i);\n // if (fragments) {\n // if (fragments[2])\n // return input.toString();\n // return fragments[1];\n // }\n return input.toString();\n}\n\n/**\n * Get a RegExp out of a serialized string\n * @param input\n */\nexport function hydrateRegExp(input?: string): RegExp | undefined {\n if (!input) return undefined;\n const fragments = input.match(/\\/(.*?)\\/([a-z]*)?$/i);\n if (fragments) {\n return new RegExp(fragments[1], fragments[2] || \"\");\n } else {\n return new RegExp(input, \"\");\n }\n}\n\n/**\n * Is `input` something {@link hydrateRegExp} can turn into a working RegExp?\n *\n * This used to pattern-match the *shape* of a regex literal and, failing that,\n * fall back to \"does it contain any regex-ish character\" — which said yes to\n * malformed input like `/[a-z/g`. The only answer that matters to a caller is\n * whether hydration succeeds, so ask the engine instead of approximating it.\n */\nexport function isValidRegExp(input: string): boolean {\n if (!input) return false;\n try {\n return hydrateRegExp(input) !== undefined;\n } catch {\n return false;\n }\n}\n","export function flattenObject(obj: Record<string, unknown>, parentKey = \"\") {\n if (!obj) return obj;\n return Object.keys(obj).reduce((flatObj, key) => {\n const newKey = parentKey ? `${parentKey}.${key}` : key;\n\n if (typeof obj[key] === \"object\" && obj[key] !== null) {\n if (Array.isArray(obj[key])) {\n obj[key].forEach((item: unknown, index: number) => {\n if (typeof item === \"object\" && item !== null) {\n Object.assign(flatObj, flattenObject(item as Record<string, unknown>, `${newKey}[${index}]`));\n } else {\n flatObj[`${newKey}[${index}]`] = item;\n }\n });\n } else {\n Object.assign(flatObj, flattenObject(obj[key] as Record<string, unknown>, newKey));\n }\n } else {\n flatObj[newKey] = obj[key];\n }\n\n return flatObj;\n }, {} as { [key: string]: unknown });\n}\n\n\n// map from nested property key like \"a.b.c\" to the maximum array count found in a list of objects for that array\nexport type ArrayValuesCount = Record<string, number>;\n\nexport function getArrayValuesCount(array: Record<string, unknown>[]): ArrayValuesCount {\n return array.reduce((acc: ArrayValuesCount, obj: Record<string, unknown>) => {\n Object.entries(obj).forEach(([key, value]) => {\n // proceed only if value is an array\n if (Array.isArray(value)) {\n acc[key] = Math.max(acc[key] || 0, value.length);\n }\n\n // handle nested object\n if (typeof value === \"object\" && value !== null) {\n const nested = getArrayValuesCount([value as Record<string, unknown>]);\n Object.entries(nested).forEach(([nestedKey, nestedCount]) => {\n const compoundKey = `${key}.${nestedKey}`;\n acc[compoundKey] = Math.max(acc[compoundKey] || 0, nestedCount);\n });\n }\n });\n return acc;\n }, {});\n}\n","/**\n * Returns the plural of an English word.\n *\n * @param {string} word\n * @param {number} [amount]\n * @returns {string}\n */\nexport function plural(word: string, amount?: number): string {\n if (amount !== undefined && amount === 1) {\n return word\n }\n const plurals: { [key: string]: string } = {\n \"(quiz)$\": \"$1zes\",\n \"^(ox)$\": \"$1en\",\n \"([m|l])ouse$\": \"$1ice\",\n \"(matr|vert|ind)ix|ex$\": \"$1ices\",\n \"(x|ch|ss|sh)$\": \"$1es\",\n \"([^aeiouy]|qu)y$\": \"$1ies\",\n \"(hive)$\": \"$1s\",\n \"(?:([^f])fe|([lr])f)$\": \"$1$2ves\",\n \"(shea|lea|loa|thie)f$\": \"$1ves\",\n sis$: \"ses\",\n \"([ti])um$\": \"$1a\",\n \"(tomat|potat|ech|her|vet)o$\": \"$1oes\",\n \"(bu)s$\": \"$1ses\",\n \"(alias)$\": \"$1es\",\n \"(octop)us$\": \"$1i\",\n \"(ax|test)is$\": \"$1es\",\n \"(us)$\": \"$1es\",\n \"([^s]+)$\": \"$1s\"\n }\n const irregular: { [key: string]: string } = {\n move: \"moves\",\n foot: \"feet\",\n goose: \"geese\",\n sex: \"sexes\",\n child: \"children\",\n man: \"men\",\n tooth: \"teeth\",\n person: \"people\"\n }\n const uncountable: string[] = [\n \"sheep\",\n \"fish\",\n \"deer\",\n \"moose\",\n \"series\",\n \"species\",\n \"money\",\n \"rice\",\n \"information\",\n \"equipment\",\n \"bison\",\n \"cod\",\n \"offspring\",\n \"pike\",\n \"salmon\",\n \"shrimp\",\n \"swine\",\n \"trout\",\n \"aircraft\",\n \"hovercraft\",\n \"spacecraft\",\n \"sugar\",\n \"tuna\",\n \"you\",\n \"wood\"\n ]\n // save some time in the case that singular and plural are the same\n if (uncountable.indexOf(word.toLowerCase()) >= 0) {\n return word;\n }\n // check for irregular forms\n for (const w in irregular) {\n const pattern = new RegExp(`${w}$`, \"i\")\n const replace = irregular[w]\n if (pattern.test(word)) {\n return word.replace(pattern, replace);\n }\n }\n // check for matches using regular expressions\n for (const reg in plurals) {\n const pattern = new RegExp(reg, \"i\")\n if (pattern.test(word)) {\n return word.replace(pattern, plurals[reg])\n }\n }\n return word;\n}\n\n/**\n * Returns the singular of an English word.\n *\n * @param {string} word\n * @param {number} [amount]\n * @returns {string}\n */\nexport function singular(word: string, amount?: number): string {\n if (amount !== undefined && amount !== 1) {\n return word;\n }\n const singulars: { [key: string]: string } = {\n \"(quiz)zes$\": \"$1\",\n \"(matr)ices$\": \"$1ix\",\n \"(vert|ind)ices$\": \"$1ex\",\n \"^(ox)en$\": \"$1\",\n \"(alias)es$\": \"$1\",\n \"(octop|vir)i$\": \"$1us\",\n \"(cris|ax|test)es$\": \"$1is\",\n \"(shoe)s$\": \"$1\",\n \"(o)es$\": \"$1\",\n \"(bus)es$\": \"$1\",\n \"([m|l])ice$\": \"$1ouse\",\n \"(x|ch|ss|sh)es$\": \"$1\",\n \"(m)ovies$\": \"$1ovie\",\n \"(s)eries$\": \"$1eries\",\n \"([^aeiouy]|qu)ies$\": \"$1y\",\n \"([lr])ves$\": \"$1f\",\n \"(tive)s$\": \"$1\",\n \"(hive)s$\": \"$1\",\n \"(li|wi|kni)ves$\": \"$1fe\",\n \"(shea|loa|lea|thie)ves$\": \"$1f\",\n \"(^analy)ses$\": \"$1sis\",\n \"((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$\": \"$1$2sis\",\n \"([ti])a$\": \"$1um\",\n \"(n)ews$\": \"$1ews\",\n \"(h|bl)ouses$\": \"$1ouse\",\n \"(corpse)s$\": \"$1\",\n \"(us)es$\": \"$1\",\n s$: \"\"\n }\n const irregular: { [key: string]: string } = {\n move: \"moves\",\n foot: \"feet\",\n goose: \"geese\",\n sex: \"sexes\",\n child: \"children\",\n man: \"men\",\n tooth: \"teeth\",\n person: \"people\"\n }\n const uncountable: string[] = [\n \"sheep\",\n \"fish\",\n \"deer\",\n \"moose\",\n \"series\",\n \"species\",\n \"money\",\n \"rice\",\n \"information\",\n \"equipment\",\n \"bison\",\n \"cod\",\n \"offspring\",\n \"pike\",\n \"salmon\",\n \"shrimp\",\n \"swine\",\n \"trout\",\n \"aircraft\",\n \"hovercraft\",\n \"spacecraft\",\n \"sugar\",\n \"tuna\",\n \"you\",\n \"wood\"\n ]\n // save some time in the case that singular and plural are the same\n if (uncountable.indexOf(word.toLowerCase()) >= 0) {\n return word;\n }\n // check for irregular forms\n for (const w in irregular) {\n const pattern = new RegExp(`${irregular[w]}$`, \"i\");\n if (pattern.test(word)) {\n return word.replace(pattern, w);\n }\n }\n // check for matches using regular expressions\n for (const reg in singulars) {\n const pattern = new RegExp(reg, \"i\");\n if (pattern.test(word)) {\n return word.replace(pattern, singulars[reg]);\n }\n }\n return word;\n}\n","import { singular } from \"./plurals\";\nimport { toSnakeCase } from \"./strings\";\n\n/**\n * Generates a foreign key column name from a given string, typically a collection slug or name.\n * It singularizes the name, converts it to snake_case and appends '_id'.\n *\n * Singularization runs *before* snake-casing so that acronyms survive: `toSnakeCase`\n * splits on every capital, which turned \"URLs\" into \"ur_ls\" and then \"ur_l_id\".\n *\n * @param name The base name to convert to a foreign key.\n * @returns A foreign key name in the format 'singular_name_id'.\n *\n * @example\n * // returns \"user_id\"\n * generateForeignKeyName(\"users\")\n *\n * @example\n * // returns \"category_id\"\n * generateForeignKeyName(\"categories\")\n *\n * @example\n * // returns \"product_id\"\n * generateForeignKeyName(\"Product\")\n *\n */\nexport function generateForeignKeyName(name: string): string {\n return `${toSnakeCase(singularizeForKey(name))}_id`;\n}\n\n/**\n * `singular()` handles real English plurals, but its final catch-all rule strips\n * any trailing \"s\", which mangles words that only look plural. Guard the two\n * cases that produce a column name nobody would recognise:\n *\n * - a double \"s\" ending is never a plural marker (\"address\", \"class\", \"process\"),\n * so stripping it yields \"addres\";\n * - a name that singularizes to nothing (the literal \"s\") would yield \"_id\".\n */\nfunction singularizeForKey(name: string): string {\n if (/ss$/i.test(name)) return name;\n const result = singular(name);\n return result.length > 0 ? result : name;\n}\n\n/**\n * What `generateForeignKeyName` returned before it learned to singularize:\n * snake-case the name, then chop one trailing \"s\".\n *\n * This is here to be *detected*, never to be generated. A database provisioned\n * under the old rule carries `categorie_id`, `addresse_id`, `children_id` or\n * `ur_l_id` where the current rule expects `category_id`, `address_id`,\n * `child_id` and `url_id` — and the boot-time schema ensure is additive, so it\n * would create the new column empty beside the populated old one and leave the\n * relation reading nothing. No error, no missing table: the failure is silent,\n * which is the only reason this function still exists.\n *\n * `ensureCollectionSchema` calls it to recognise that shape and say so.\n * Returns the same string as `generateForeignKeyName` for every regular plural,\n * so a caller can compare the two and act only when they differ.\n */\nexport function legacyForeignKeyName(name: string): string {\n const snake = toSnakeCase(name);\n return `${snake.endsWith(\"s\") ? snake.slice(0, -1) : snake}_id`;\n}\n\n/**\n * Truncate an identifier to what Postgres will actually store.\n *\n * Postgres silently truncates identifiers at NAMEDATALEN-1 = 63 **bytes**, so a\n * name generated longer than that is not the name the database ends up holding.\n * Anything that later looks the object up by the name it generated then misses.\n *\n * Byte length, not string length: NAMEDATALEN is a byte bound, and a multi-byte\n * character straddling the boundary would be cut mid-sequence by `slice(0, 63)`.\n *\n * `TextEncoder` rather than `Buffer`, which is not a matter of taste: `Buffer`\n * is a Node global, and this package is imported by browser-facing ones. It\n * typechecked only where `@types/node` happened to be in scope, so\n * `packages/codegen` — whose tsconfig is `lib: [\"ESNext\", \"dom\"]` — could not\n * compile the file at all, and both of its suites failed to run. `TextEncoder`\n * and `TextDecoder` are standard in both runtimes and need no ambient types.\n */\nexport function toPostgresIdentifier(name: string): string {\n return truncateToBytes(name, 63);\n}\n\n/**\n * {@link toPostgresIdentifier} with the bound lifted to a parameter.\n *\n * Exists for names that end in something load-bearing. Truncating at 63 keeps\n * the *head* of a name and discards the tail, which is right for a descriptive\n * identifier and wrong for a hashed one: the hash is the part that makes it\n * unique, and it is at the end. A caller that appends a fingerprint truncates\n * the readable head to `63 - <tail>` itself and then appends, so the bound is\n * still 63 and the hash always survives.\n *\n * `contracts/derived-names.txt` records what the alternative costs — a foreign\n * key frozen as `..._corres`, its `_fkey` suffix truncated away, so a second\n * foreign key on that table would derive a byte-identical name.\n *\n * One truncation rule, in one function, so the two cannot drift.\n */\nexport function truncateToBytes(name: string, maxBytes: number): string {\n const bytes = new TextEncoder().encode(name);\n if (bytes.byteLength <= maxBytes) return name;\n // Decoding a slice that ends mid-character yields U+FFFD; dropping it lands\n // on the last whole character that fits, which is what Postgres does.\n return new TextDecoder(\"utf-8\").decode(bytes.subarray(0, maxBytes)).replace(/�+$/, \"\");\n}\n\n/**\n * The API name a database column is served under.\n *\n * The wire name of a field is its property key, and Rebase's property keys are\n * camelCase — `displayName`, `createdAt`, `photoURL`. Columns are snake_case,\n * because an unquoted Postgres identifier folds to lower case and a camelCase\n * column is therefore reachable only as `\"authorId\"` forever: in hand-written\n * SQL, in psql, in an RLS policy body, in a dump, and in every third-party tool\n * that ever touches the database. So the two conventions are both right, and\n * this is the function that crosses between them.\n *\n * It exists because two sources of field names never crossed: a foreign key\n * derived from a relation (`author_id`) and a column read back by introspection\n * (`user_id`) both landed on the wire under their column name, while every\n * hand-authored collection next to them used camelCase. One API, two\n * conventions, and no rule a caller could infer from outside — those names are\n * also the `where` and `orderBy` keys, so it was not a matter of taste.\n *\n * Rules, in the order they matter:\n *\n * - **A name with no separator is returned unchanged.** `photoURL` stays\n * `photoURL` and `id` stays `id`. Lower-casing a single token is what makes\n * a \"camelCase\" helper destructive — `camelCase(\"photoURL\")` is `photourl` —\n * and this function is applied to names that are *already* keys.\n * - **Each following segment keeps its own casing** apart from an upper-cased\n * first letter, so `photo_URL` → `photoURL` rather than `photoUrl`.\n * - **The result may still not be a JavaScript identifier.** `2fa_enabled`\n * becomes `2faEnabled`, which is a perfectly good object key and still needs\n * quoting where one is written into generated source.\n *\n * Not the inverse of {@link toSnakeCase}: `toSnakeCase` tokenises on case\n * boundaries and would turn `photoURL` into `photo_url`. Round-tripping is not\n * a property either function promises, which is why a column name that a\n * property maps explicitly is always read off `columnName` rather than derived.\n */\nexport function toWireKey(columnName: string): string {\n if (!columnName) return columnName;\n const segments = columnName.split(/[-_ ]+/).filter(Boolean);\n if (segments.length <= 1) return columnName;\n return segments\n .map((segment, index) =>\n index === 0\n ? segment.charAt(0).toLowerCase() + segment.slice(1)\n : segment.charAt(0).toUpperCase() + segment.slice(1))\n .join(\"\");\n}\n\n/**\n * The first candidate key not already used, or a numbered fallback.\n *\n * Introspection turns a set of column names into a set of object keys, and the\n * mapping is not injective: `user_id` and `userId` are two columns and one\n * {@link toWireKey}, and two foreign keys can strip to the same relation name.\n * A duplicate key in a generated object literal is a TypeScript error, so the\n * whole collection stops compiling — and a duplicate key in a `Record` built at\n * runtime is worse, because it silently drops a column instead.\n *\n * The numbered tail is what makes this total: a function that returns a key it\n * cannot guarantee is free has only moved the duplicate one line down.\n *\n * Structurally typed on `has` so a `Map` of emitted blocks and a `Set` of taken\n * names both satisfy it. Lives here, in the package both introspection\n * producers and the admin's table import can reach, because they must resolve a\n * collision the same way or one database describes itself three ways.\n */\nexport function firstFreeKey(candidates: string[], taken: { has(key: string): boolean }): string {\n for (const candidate of candidates) {\n if (!taken.has(candidate)) return candidate;\n }\n const base = candidates[candidates.length - 1];\n for (let suffix = 2; ; suffix++) {\n const candidate = `${base}_${suffix}`;\n if (!taken.has(candidate)) return candidate;\n }\n}\n","\n\nexport function isDefaultFieldConfigId(id: string): boolean {\n return [\"text_field\",\n \"multiline\",\n \"markdown\",\n \"url\",\n \"email\",\n \"switch\",\n \"select\",\n \"multi_select\",\n \"number_input\",\n \"number_select\",\n \"multi_number_select\",\n \"file_upload\",\n \"multi_file_upload\",\n \"reference\",\n \"multi_references\",\n \"relation\",\n \"date_time\",\n \"group\",\n \"key_value\",\n \"repeat\",\n \"custom_array\",\n \"block\"\n ].includes(id);\n}\n"],"mappings":"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"}
package/dist/names.d.ts CHANGED
@@ -57,6 +57,23 @@ export declare function legacyForeignKeyName(name: string): string;
57
57
  * and `TextDecoder` are standard in both runtimes and need no ambient types.
58
58
  */
59
59
  export declare function toPostgresIdentifier(name: string): string;
60
+ /**
61
+ * {@link toPostgresIdentifier} with the bound lifted to a parameter.
62
+ *
63
+ * Exists for names that end in something load-bearing. Truncating at 63 keeps
64
+ * the *head* of a name and discards the tail, which is right for a descriptive
65
+ * identifier and wrong for a hashed one: the hash is the part that makes it
66
+ * unique, and it is at the end. A caller that appends a fingerprint truncates
67
+ * the readable head to `63 - <tail>` itself and then appends, so the bound is
68
+ * still 63 and the hash always survives.
69
+ *
70
+ * `contracts/derived-names.txt` records what the alternative costs — a foreign
71
+ * key frozen as `..._corres`, its `_fkey` suffix truncated away, so a second
72
+ * foreign key on that table would derive a byte-identical name.
73
+ *
74
+ * One truncation rule, in one function, so the two cannot drift.
75
+ */
76
+ export declare function truncateToBytes(name: string, maxBytes: number): string;
60
77
  /**
61
78
  * The API name a database column is served under.
62
79
  *
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/utils",
3
3
  "type": "module",
4
- "version": "0.16.0",
4
+ "version": "0.16.1-canary.g0d7af95",
5
5
  "description": "Utility functions for Rebase",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "object-hash": "^3.0.0",
42
- "@rebasepro/types": "0.16.0"
42
+ "@rebasepro/types": "0.16.1-canary.g0d7af95"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@jest/globals": "^30.4.1",
@@ -94,7 +94,7 @@
94
94
  },
95
95
  "scripts": {
96
96
  "watch": "vite build --watch",
97
- "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json && node ../../scripts/assert-build-output.mjs",
97
+ "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json && node ../../tooling/scripts/add-dts-extensions.mjs dist && node ../../tooling/scripts/assert-build-output.mjs",
98
98
  "test:lint": "eslint \"src/**\" --quiet",
99
99
  "test": "jest --passWithNoTests",
100
100
  "clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f",
package/src/names.ts CHANGED
@@ -82,11 +82,31 @@ export function legacyForeignKeyName(name: string): string {
82
82
  * and `TextDecoder` are standard in both runtimes and need no ambient types.
83
83
  */
84
84
  export function toPostgresIdentifier(name: string): string {
85
+ return truncateToBytes(name, 63);
86
+ }
87
+
88
+ /**
89
+ * {@link toPostgresIdentifier} with the bound lifted to a parameter.
90
+ *
91
+ * Exists for names that end in something load-bearing. Truncating at 63 keeps
92
+ * the *head* of a name and discards the tail, which is right for a descriptive
93
+ * identifier and wrong for a hashed one: the hash is the part that makes it
94
+ * unique, and it is at the end. A caller that appends a fingerprint truncates
95
+ * the readable head to `63 - <tail>` itself and then appends, so the bound is
96
+ * still 63 and the hash always survives.
97
+ *
98
+ * `contracts/derived-names.txt` records what the alternative costs — a foreign
99
+ * key frozen as `..._corres`, its `_fkey` suffix truncated away, so a second
100
+ * foreign key on that table would derive a byte-identical name.
101
+ *
102
+ * One truncation rule, in one function, so the two cannot drift.
103
+ */
104
+ export function truncateToBytes(name: string, maxBytes: number): string {
85
105
  const bytes = new TextEncoder().encode(name);
86
- if (bytes.byteLength <= 63) return name;
106
+ if (bytes.byteLength <= maxBytes) return name;
87
107
  // Decoding a slice that ends mid-character yields U+FFFD; dropping it lands
88
108
  // on the last whole character that fits, which is what Postgres does.
89
- return new TextDecoder("utf-8").decode(bytes.subarray(0, 63)).replace(/�+$/, "");
109
+ return new TextDecoder("utf-8").decode(bytes.subarray(0, maxBytes)).replace(/�+$/, "");
90
110
  }
91
111
 
92
112
  /**