@rebasepro/server 0.13.0 → 0.13.1-canary.g394d868

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.
Files changed (44) hide show
  1. package/dist/{admin_block-CcSGdH7M.js → admin_block-EmIHae6X.js} +2 -1
  2. package/dist/admin_block-EmIHae6X.js.map +1 -0
  3. package/dist/api/errors.d.ts +0 -6
  4. package/dist/api/openapi-generator.d.ts +14 -0
  5. package/dist/api/rest/idempotency.d.ts +41 -4
  6. package/dist/api/rest/write-validation.d.ts +12 -5
  7. package/dist/auth/index.d.ts +1 -1
  8. package/dist/auth/jwt.d.ts +12 -0
  9. package/dist/{auth-CuC9M2x6.js → auth-Bm5kOV2E.js} +17 -37
  10. package/dist/auth-Bm5kOV2E.js.map +1 -0
  11. package/dist/{backup-CVggVhR2.js → backup-BJiY_mM-.js} +2 -2
  12. package/dist/{backup-CVggVhR2.js.map → backup-BJiY_mM-.js.map} +1 -1
  13. package/dist/boot/boot.d.ts +19 -0
  14. package/dist/cron/cron-scheduler.d.ts +8 -3
  15. package/dist/cron/define-cron.d.ts +5 -3
  16. package/dist/{cron-routes-CrQ0tK-_.js → cron-routes-D5a9v1HY.js} +2 -2
  17. package/dist/{cron-routes-CrQ0tK-_.js.map → cron-routes-D5a9v1HY.js.map} +1 -1
  18. package/dist/{cron-scheduler-B3RFt0HS.js → cron-scheduler-CsQtYLMq.js} +28 -3
  19. package/dist/cron-scheduler-CsQtYLMq.js.map +1 -0
  20. package/dist/{cron-store-BywZsyfZ.js → cron-store-Cu4sOseb.js} +11 -1
  21. package/dist/cron-store-Cu4sOseb.js.map +1 -0
  22. package/dist/{errors-CgkCzoj7.js → errors-BpudWAVU.js} +2 -10
  23. package/dist/errors-BpudWAVU.js.map +1 -0
  24. package/dist/functions/define-function.d.ts +1 -1
  25. package/dist/index.es.js +586 -208
  26. package/dist/index.es.js.map +1 -1
  27. package/dist/{jwt-DD6EtpGj.js → jwt-CzeviDcB.js} +17 -2
  28. package/dist/{jwt-DD6EtpGj.js.map → jwt-CzeviDcB.js.map} +1 -1
  29. package/dist/{openapi-generator-BEwyiaAr.js → openapi-generator-DieRvJdS.js} +305 -127
  30. package/dist/openapi-generator-DieRvJdS.js.map +1 -0
  31. package/dist/{schema-editor-routes-CbF20Mf1.js → schema-editor-routes-C1DxDnDR.js} +3 -3
  32. package/dist/{schema-editor-routes-CbF20Mf1.js.map → schema-editor-routes-C1DxDnDR.js.map} +1 -1
  33. package/dist/{src-Cum9kox5.js → src-CY0jBL-x.js} +111 -30
  34. package/dist/src-CY0jBL-x.js.map +1 -0
  35. package/dist/src-_qQ3RNCK.js.map +1 -1
  36. package/dist/utils/logging.d.ts +0 -4
  37. package/package.json +6 -6
  38. package/dist/admin_block-CcSGdH7M.js.map +0 -1
  39. package/dist/auth-CuC9M2x6.js.map +0 -1
  40. package/dist/cron-scheduler-B3RFt0HS.js.map +0 -1
  41. package/dist/cron-store-BywZsyfZ.js.map +0 -1
  42. package/dist/errors-CgkCzoj7.js.map +0 -1
  43. package/dist/openapi-generator-BEwyiaAr.js.map +0 -1
  44. package/dist/src-Cum9kox5.js.map +0 -1
@@ -2,8 +2,8 @@ import { createRequire as __createRequire } from "module";
2
2
  import "process";
3
3
  __createRequire(import.meta.url);
4
4
  import "./src-_qQ3RNCK.js";
5
- import { t as ADMIN_COLLECTION_KEYS } from "./admin_block-CcSGdH7M.js";
6
- import { n as errorHandler } from "./errors-CgkCzoj7.js";
5
+ import { t as ADMIN_COLLECTION_KEYS } from "./admin_block-EmIHae6X.js";
6
+ import { n as errorHandler } from "./errors-BpudWAVU.js";
7
7
  import * as fs$1 from "fs";
8
8
  import * as path$1 from "path";
9
9
  import { Hono } from "hono";
@@ -245,4 +245,4 @@ function createSchemaEditorRoutes(collectionsDir) {
245
245
  //#endregion
246
246
  export { createSchemaEditorRoutes };
247
247
 
248
- //# sourceMappingURL=schema-editor-routes-CbF20Mf1.js.map
248
+ //# sourceMappingURL=schema-editor-routes-C1DxDnDR.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-editor-routes-CbF20Mf1.js","names":[],"sources":["../src/api/ast-schema-editor.ts","../src/api/schema-editor-routes.ts"],"sourcesContent":["import { Project, SyntaxKind, ObjectLiteralExpression, ObjectLiteralElementLike, PropertyAssignment, VariableDeclaration, IndentationText } from \"ts-morph\";\nimport { ADMIN_COLLECTION_KEYS } from \"@rebasepro/types\";\nimport * as path from \"path\";\nimport * as fs from \"fs\";\n\n/**\n * Move presentation keys into the `admin` block.\n *\n * `ADMIN_COLLECTION_KEYS` comes from `@rebasepro/types` rather than being spelled\n * out here, so adding a field to `AdminCollectionOptions` cannot leave this writer\n * behind. Any such key already inside `admin` wins over a top-level copy: the\n * nested one is what the file said, and a flat duplicate is the view model's\n * flattening leaking back.\n */\nexport function nestAdminKeys(collectionData: Record<string, unknown>): Record<string, unknown> {\n const adminKeys = new Set<string>(ADMIN_COLLECTION_KEYS as readonly string[]);\n const existingBlock = (collectionData.admin ?? {}) as Record<string, unknown>;\n\n const top: Record<string, unknown> = {};\n const block: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(collectionData)) {\n if (key === \"admin\") continue;\n if (adminKeys.has(key)) block[key] = value;\n else top[key] = value;\n }\n\n const merged = { ...block, ...existingBlock };\n if (Object.keys(merged).length > 0) top.admin = merged;\n return top;\n}\n\nexport class AstSchemaEditor {\n private project: Project;\n private collectionsDir: string;\n\n constructor(collectionsDir: string) {\n this.project = new Project({\n manipulationSettings: {\n indentationText: IndentationText.FourSpaces\n }\n });\n if (fs.existsSync(collectionsDir)) {\n this.project.addSourceFilesAtPaths(`${collectionsDir}/**/*.ts`);\n }\n this.collectionsDir = path.resolve(collectionsDir);\n }\n\n /**\n * Sanitize collectionId to prevent path traversal attacks.\n * Only allows alphanumeric characters, underscores, and hyphens.\n */\n private sanitizeCollectionId(collectionId: string): string {\n const sanitized = collectionId.replace(/[^a-zA-Z0-9_-]/g, \"\");\n if (!sanitized || sanitized !== collectionId) {\n throw new Error(`Invalid collection ID: \"${collectionId}\". Only alphanumeric characters, underscores, and hyphens are allowed.`);\n }\n return sanitized;\n }\n\n /**\n * Resolve a file path and ensure it falls within the collectionsDir.\n */\n private safePath(filename: string): string {\n const resolved = path.resolve(this.collectionsDir, filename);\n if (!resolved.startsWith(this.collectionsDir + path.sep) && resolved !== this.collectionsDir) {\n throw new Error(\"Path traversal detected: resolved path is outside the collections directory.\");\n }\n return resolved;\n }\n\n private getCollectionFile(collectionId: string) {\n const safeId = this.sanitizeCollectionId(collectionId);\n const filePath = this.safePath(`${safeId}.ts`);\n let file = this.project.getSourceFile(filePath);\n if (!file && fs.existsSync(filePath)) {\n this.project.addSourceFilesAtPaths(`${this.collectionsDir}/**/*.ts`);\n file = this.project.getSourceFile(filePath);\n }\n return file;\n }\n\n private getCollectionObject(collectionId: string): ObjectLiteralExpression | null {\n const file = this.getCollectionFile(collectionId);\n if (!file) return null;\n\n const defaultExport = file.getDefaultExportSymbol();\n if (defaultExport) {\n const declaration = defaultExport.getDeclarations()[0];\n if (declaration && declaration.getKind() === SyntaxKind.ExportAssignment) {\n const expr = declaration.asKind(SyntaxKind.ExportAssignment)?.getExpression();\n if (expr && expr.getKind() === SyntaxKind.Identifier) {\n const varName = expr.getText();\n const varDecl = file.getVariableDeclaration(varName);\n return varDecl?.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression) || null;\n }\n }\n }\n // Fallback: Just get the first exported VariableDeclaration with an ObjectLiteral\n const varDecls = file.getVariableDeclarations();\n for (const varDecl of varDecls) {\n const init = varDecl.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n if (init) return init;\n }\n return null;\n }\n\n private convertJsonToAstString(obj: unknown, indentLevel = 0, oldAstNode?: ObjectLiteralExpression): string {\n // Base TS-morph parses arrays as 2 levels deep from the property key:\n // PropertiesObject = level 1, PropertyConfig = level 2.\n // We calibrate the spacing multiples to keep the items flush with standard TS format.\n const indentStr = \" \";\n const indent = indentStr.repeat(indentLevel);\n const innerIndent = indentStr.repeat(indentLevel + 1);\n\n if (obj === null || obj === undefined) {\n return \"undefined\";\n }\n if (typeof obj === \"string\") {\n return JSON.stringify(obj);\n }\n if (typeof obj === \"number\" || typeof obj === \"boolean\") {\n return String(obj);\n }\n if (Array.isArray(obj)) {\n if (obj.length === 0) return \"[]\";\n const items = obj.map(item => this.convertJsonToAstString(item, indentLevel + 1));\n return `[\\n${innerIndent}${items.join(`,\\n${innerIndent}`)}\\n${indent}]`;\n }\n if (typeof obj === \"object\") {\n const record = obj as Record<string, unknown>;\n const keys = Object.keys(record);\n\n // Collect preserved AST properties\n const preservedProps: string[] = [];\n if (oldAstNode) {\n const oldProps = oldAstNode.getProperties();\n for (const oldProp of oldProps) {\n if (oldProp.isKind(SyntaxKind.PropertyAssignment)) {\n const nameNode = oldProp.getNameNode();\n let name = nameNode.getText();\n if (name.startsWith('\"') && name.endsWith('\"')) name = name.slice(1, -1);\n if (name.startsWith(\"'\") && name.endsWith(\"'\")) name = name.slice(1, -1);\n\n // If the JSON object doesn't have this key, check if we should preserve it\n if (!(name in record)) {\n const init = oldProp.getInitializer();\n if (init) {\n const kind = init.getKind();\n const isCode = kind === SyntaxKind.ArrowFunction ||\n kind === SyntaxKind.FunctionExpression ||\n kind === SyntaxKind.Identifier ||\n kind === SyntaxKind.CallExpression ||\n kind === SyntaxKind.JsxElement;\n\n if (isCode || name === \"target\" || name === \"callbacks\" || name === \"permissions\" || name === \"securityRules\") {\n // Preserve this property exactly as it was\n const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);\n preservedProps.push(`${keyStr}: ${init.getText()}`);\n }\n }\n }\n }\n }\n }\n\n if (keys.length === 0 && preservedProps.length === 0) return \"{}\";\n\n const props = keys.map(key => {\n const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);\n\n // If the value is an object, pass the old AST node to recurse\n let childAstNode: ObjectLiteralExpression | undefined;\n if (oldAstNode && typeof record[key] === \"object\" && record[key] !== null && !Array.isArray(record[key])) {\n const oldProp = oldAstNode.getProperty(\n (p: ObjectLiteralElementLike) => \"getName\" in p && typeof (p as PropertyAssignment).getName === \"function\" && ((p as PropertyAssignment).getName() === key || (p as PropertyAssignment).getName() === `\"${key}\"` || (p as PropertyAssignment).getName() === `'${key}'`)\n );\n if (oldProp && oldProp.isKind(SyntaxKind.PropertyAssignment)) {\n childAstNode = oldProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n }\n }\n\n return `${keyStr}: ${this.convertJsonToAstString(record[key], indentLevel + 1, childAstNode)}`;\n });\n\n const allProps = [...props, ...preservedProps];\n return `{\\n${innerIndent}${allProps.join(`,\\n${innerIndent}`)}\\n${indent}}`;\n }\n return \"undefined\";\n }\n\n public async saveProperty(collectionId: string, propertyKey: string, propertyConfig: Record<string, unknown>) {\n const collectionObj = this.getCollectionObject(collectionId);\n if (!collectionObj) throw new Error(`Collection ${collectionId} not found in ATS workspace.`);\n\n let propertiesProp = collectionObj.getProperty(\"properties\") as PropertyAssignment;\n if (!propertiesProp) {\n propertiesProp = collectionObj.addPropertyAssignment({\n name: \"properties\",\n initializer: \"{}\"\n });\n }\n\n const propsObj = propertiesProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n if (propsObj) {\n const existingProp = propsObj.getProperty(\n (p: ObjectLiteralElementLike) => \"getName\" in p && typeof (p as PropertyAssignment).getName === \"function\" && ((p as PropertyAssignment).getName() === propertyKey || (p as PropertyAssignment).getName() === `\"${propertyKey}\"`)\n );\n\n let oldPropAstNode: ObjectLiteralExpression | undefined;\n if (existingProp && existingProp.isKind(SyntaxKind.PropertyAssignment)) {\n oldPropAstNode = existingProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n }\n\n const newInitializer = this.convertJsonToAstString(propertyConfig, 2, oldPropAstNode);\n\n if (existingProp) {\n if (existingProp.isKind(SyntaxKind.PropertyAssignment)) {\n existingProp.setInitializer(newInitializer);\n }\n } else {\n propsObj.addPropertyAssignment({\n name: /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(propertyKey) ? propertyKey : JSON.stringify(propertyKey),\n initializer: newInitializer\n });\n }\n\n const file = this.getCollectionFile(collectionId);\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n }\n\n public async deleteProperty(collectionId: string, propertyKey: string) {\n const collectionObj = this.getCollectionObject(collectionId);\n if (!collectionObj) return;\n\n const propertiesProp = collectionObj.getProperty(\"properties\") as PropertyAssignment;\n if (propertiesProp) {\n const propsObj = propertiesProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n if (propsObj) {\n const existingProp = propsObj.getProperty(\n (p: ObjectLiteralElementLike) => \"getName\" in p && typeof (p as PropertyAssignment).getName === \"function\" && ((p as PropertyAssignment).getName() === propertyKey || (p as PropertyAssignment).getName() === `\"${propertyKey}\"`)\n );\n if (existingProp) {\n existingProp.remove();\n const file = this.getCollectionFile(collectionId);\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n }\n }\n }\n\n public async saveCollection(collectionId: string, collectionData: Record<string, unknown>) {\n let file = this.getCollectionFile(collectionId);\n const collectionObj = this.getCollectionObject(collectionId);\n\n if (!file || !collectionObj) {\n // Create a new file\n const safeId = this.sanitizeCollectionId(collectionId);\n const newFilePath = this.safePath(`${safeId}.ts`);\n file = this.project.createSourceFile(newFilePath, `import { CollectionConfig } from \"@rebasepro/types\";\\n\\nconst ${safeId}Collection: CollectionConfig = ${this.convertJsonToAstString(nestAdminKeys(collectionData))};\\n\\nexport default ${safeId}Collection;\\n`, { overwrite: true });\n } else {\n // Update root level properties gracefully\n\n // Force delete securityRules if empty or undefined to handle Formex / serialization stripping\n if (!(\"securityRules\" in collectionData) || collectionData.securityRules === undefined || (Array.isArray(collectionData.securityRules) && collectionData.securityRules.length === 0)) {\n const srProp = collectionObj.getProperty(\"securityRules\");\n if (srProp) {\n srProp.remove();\n }\n\n // If it was in collectionData as an empty array, delete it so the loop below doesn't add it back as \"[]\"\n // Actually, if it's \"[]\", omitting it entirely from the TS file achieves the same logical effect (no RLS rules)\n // and correctly triggers \"unmapped policies\" if the DB still has them.\n delete collectionData[\"securityRules\"];\n }\n\n // The panel works with a flat view model — presentation merged onto the\n // collection — so what arrives here has `icon` and `listProperties` at\n // the top level. On disk they belong inside `admin`. Writing them flat\n // would produce a file the backend loads and ignores and the panel\n // never reads back, which looks exactly like the edit not saving.\n collectionData = nestAdminKeys(collectionData);\n\n for (const key of Object.keys(collectionData)) {\n if (key === \"relations\") continue; // Kept via other AST functions or handled separately.\n\n const prop = collectionObj.getProperty(key) as PropertyAssignment;\n\n let oldAstNode: ObjectLiteralExpression | undefined;\n if (prop && prop.isKind(SyntaxKind.PropertyAssignment)) {\n oldAstNode = prop.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n }\n\n const newInit = this.convertJsonToAstString(collectionData[key], 1, oldAstNode);\n if (prop) {\n prop.setInitializer(newInit);\n } else {\n collectionObj.addPropertyAssignment({\n name: key,\n initializer: newInit\n });\n }\n }\n }\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n\n public async deleteCollection(collectionId: string) {\n const file = this.getCollectionFile(collectionId);\n if (file) {\n file.deleteImmediatelySync();\n }\n }\n}\n","import { Hono } from \"hono\";\nimport { AstSchemaEditor } from \"./ast-schema-editor\";\nimport { errorHandler } from \"./errors\";\nimport { HonoEnv } from \"./types\";\n\nexport function createSchemaEditorRoutes(collectionsDir: string): Hono<HonoEnv> {\n const router = new Hono<HonoEnv>();\n router.onError(errorHandler);\n const editor = new AstSchemaEditor(collectionsDir);\n\n router.post(\"/property/save\", async (c) => {\n const body = await c.req.json();\n const { collectionId, propertyKey, propertyConfig } = body;\n await editor.saveProperty(collectionId, propertyKey, propertyConfig);\n return c.json({ success: true });\n });\n\n router.post(\"/property/delete\", async (c) => {\n const body = await c.req.json();\n const { collectionId, propertyKey } = body;\n await editor.deleteProperty(collectionId, propertyKey);\n return c.json({ success: true });\n });\n\n router.post(\"/collection/save\", async (c) => {\n const body = await c.req.json();\n const { collectionId, collectionData } = body;\n await editor.saveCollection(collectionId, collectionData);\n return c.json({ success: true });\n });\n\n router.post(\"/collection/delete\", async (c) => {\n const body = await c.req.json();\n const { collectionId } = body;\n await editor.deleteCollection(collectionId);\n return c.json({ success: true });\n });\n\n return router;\n}\n\n"],"mappings":"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"}
1
+ {"version":3,"file":"schema-editor-routes-C1DxDnDR.js","names":[],"sources":["../src/api/ast-schema-editor.ts","../src/api/schema-editor-routes.ts"],"sourcesContent":["import { Project, SyntaxKind, ObjectLiteralExpression, ObjectLiteralElementLike, PropertyAssignment, VariableDeclaration, IndentationText } from \"ts-morph\";\nimport { ADMIN_COLLECTION_KEYS } from \"@rebasepro/types\";\nimport * as path from \"path\";\nimport * as fs from \"fs\";\n\n/**\n * Move presentation keys into the `admin` block.\n *\n * `ADMIN_COLLECTION_KEYS` comes from `@rebasepro/types` rather than being spelled\n * out here, so adding a field to `AdminCollectionOptions` cannot leave this writer\n * behind. Any such key already inside `admin` wins over a top-level copy: the\n * nested one is what the file said, and a flat duplicate is the view model's\n * flattening leaking back.\n */\nexport function nestAdminKeys(collectionData: Record<string, unknown>): Record<string, unknown> {\n const adminKeys = new Set<string>(ADMIN_COLLECTION_KEYS as readonly string[]);\n const existingBlock = (collectionData.admin ?? {}) as Record<string, unknown>;\n\n const top: Record<string, unknown> = {};\n const block: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(collectionData)) {\n if (key === \"admin\") continue;\n if (adminKeys.has(key)) block[key] = value;\n else top[key] = value;\n }\n\n const merged = { ...block, ...existingBlock };\n if (Object.keys(merged).length > 0) top.admin = merged;\n return top;\n}\n\nexport class AstSchemaEditor {\n private project: Project;\n private collectionsDir: string;\n\n constructor(collectionsDir: string) {\n this.project = new Project({\n manipulationSettings: {\n indentationText: IndentationText.FourSpaces\n }\n });\n if (fs.existsSync(collectionsDir)) {\n this.project.addSourceFilesAtPaths(`${collectionsDir}/**/*.ts`);\n }\n this.collectionsDir = path.resolve(collectionsDir);\n }\n\n /**\n * Sanitize collectionId to prevent path traversal attacks.\n * Only allows alphanumeric characters, underscores, and hyphens.\n */\n private sanitizeCollectionId(collectionId: string): string {\n const sanitized = collectionId.replace(/[^a-zA-Z0-9_-]/g, \"\");\n if (!sanitized || sanitized !== collectionId) {\n throw new Error(`Invalid collection ID: \"${collectionId}\". Only alphanumeric characters, underscores, and hyphens are allowed.`);\n }\n return sanitized;\n }\n\n /**\n * Resolve a file path and ensure it falls within the collectionsDir.\n */\n private safePath(filename: string): string {\n const resolved = path.resolve(this.collectionsDir, filename);\n if (!resolved.startsWith(this.collectionsDir + path.sep) && resolved !== this.collectionsDir) {\n throw new Error(\"Path traversal detected: resolved path is outside the collections directory.\");\n }\n return resolved;\n }\n\n private getCollectionFile(collectionId: string) {\n const safeId = this.sanitizeCollectionId(collectionId);\n const filePath = this.safePath(`${safeId}.ts`);\n let file = this.project.getSourceFile(filePath);\n if (!file && fs.existsSync(filePath)) {\n this.project.addSourceFilesAtPaths(`${this.collectionsDir}/**/*.ts`);\n file = this.project.getSourceFile(filePath);\n }\n return file;\n }\n\n private getCollectionObject(collectionId: string): ObjectLiteralExpression | null {\n const file = this.getCollectionFile(collectionId);\n if (!file) return null;\n\n const defaultExport = file.getDefaultExportSymbol();\n if (defaultExport) {\n const declaration = defaultExport.getDeclarations()[0];\n if (declaration && declaration.getKind() === SyntaxKind.ExportAssignment) {\n const expr = declaration.asKind(SyntaxKind.ExportAssignment)?.getExpression();\n if (expr && expr.getKind() === SyntaxKind.Identifier) {\n const varName = expr.getText();\n const varDecl = file.getVariableDeclaration(varName);\n return varDecl?.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression) || null;\n }\n }\n }\n // Fallback: Just get the first exported VariableDeclaration with an ObjectLiteral\n const varDecls = file.getVariableDeclarations();\n for (const varDecl of varDecls) {\n const init = varDecl.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n if (init) return init;\n }\n return null;\n }\n\n private convertJsonToAstString(obj: unknown, indentLevel = 0, oldAstNode?: ObjectLiteralExpression): string {\n // Base TS-morph parses arrays as 2 levels deep from the property key:\n // PropertiesObject = level 1, PropertyConfig = level 2.\n // We calibrate the spacing multiples to keep the items flush with standard TS format.\n const indentStr = \" \";\n const indent = indentStr.repeat(indentLevel);\n const innerIndent = indentStr.repeat(indentLevel + 1);\n\n if (obj === null || obj === undefined) {\n return \"undefined\";\n }\n if (typeof obj === \"string\") {\n return JSON.stringify(obj);\n }\n if (typeof obj === \"number\" || typeof obj === \"boolean\") {\n return String(obj);\n }\n if (Array.isArray(obj)) {\n if (obj.length === 0) return \"[]\";\n const items = obj.map(item => this.convertJsonToAstString(item, indentLevel + 1));\n return `[\\n${innerIndent}${items.join(`,\\n${innerIndent}`)}\\n${indent}]`;\n }\n if (typeof obj === \"object\") {\n const record = obj as Record<string, unknown>;\n const keys = Object.keys(record);\n\n // Collect preserved AST properties\n const preservedProps: string[] = [];\n if (oldAstNode) {\n const oldProps = oldAstNode.getProperties();\n for (const oldProp of oldProps) {\n if (oldProp.isKind(SyntaxKind.PropertyAssignment)) {\n const nameNode = oldProp.getNameNode();\n let name = nameNode.getText();\n if (name.startsWith('\"') && name.endsWith('\"')) name = name.slice(1, -1);\n if (name.startsWith(\"'\") && name.endsWith(\"'\")) name = name.slice(1, -1);\n\n // If the JSON object doesn't have this key, check if we should preserve it\n if (!(name in record)) {\n const init = oldProp.getInitializer();\n if (init) {\n const kind = init.getKind();\n const isCode = kind === SyntaxKind.ArrowFunction ||\n kind === SyntaxKind.FunctionExpression ||\n kind === SyntaxKind.Identifier ||\n kind === SyntaxKind.CallExpression ||\n kind === SyntaxKind.JsxElement;\n\n if (isCode || name === \"target\" || name === \"callbacks\" || name === \"permissions\" || name === \"securityRules\") {\n // Preserve this property exactly as it was\n const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);\n preservedProps.push(`${keyStr}: ${init.getText()}`);\n }\n }\n }\n }\n }\n }\n\n if (keys.length === 0 && preservedProps.length === 0) return \"{}\";\n\n const props = keys.map(key => {\n const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);\n\n // If the value is an object, pass the old AST node to recurse\n let childAstNode: ObjectLiteralExpression | undefined;\n if (oldAstNode && typeof record[key] === \"object\" && record[key] !== null && !Array.isArray(record[key])) {\n const oldProp = oldAstNode.getProperty(\n (p: ObjectLiteralElementLike) => \"getName\" in p && typeof (p as PropertyAssignment).getName === \"function\" && ((p as PropertyAssignment).getName() === key || (p as PropertyAssignment).getName() === `\"${key}\"` || (p as PropertyAssignment).getName() === `'${key}'`)\n );\n if (oldProp && oldProp.isKind(SyntaxKind.PropertyAssignment)) {\n childAstNode = oldProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n }\n }\n\n return `${keyStr}: ${this.convertJsonToAstString(record[key], indentLevel + 1, childAstNode)}`;\n });\n\n const allProps = [...props, ...preservedProps];\n return `{\\n${innerIndent}${allProps.join(`,\\n${innerIndent}`)}\\n${indent}}`;\n }\n return \"undefined\";\n }\n\n public async saveProperty(collectionId: string, propertyKey: string, propertyConfig: Record<string, unknown>) {\n const collectionObj = this.getCollectionObject(collectionId);\n if (!collectionObj) throw new Error(`Collection ${collectionId} not found in ATS workspace.`);\n\n let propertiesProp = collectionObj.getProperty(\"properties\") as PropertyAssignment;\n if (!propertiesProp) {\n propertiesProp = collectionObj.addPropertyAssignment({\n name: \"properties\",\n initializer: \"{}\"\n });\n }\n\n const propsObj = propertiesProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n if (propsObj) {\n const existingProp = propsObj.getProperty(\n (p: ObjectLiteralElementLike) => \"getName\" in p && typeof (p as PropertyAssignment).getName === \"function\" && ((p as PropertyAssignment).getName() === propertyKey || (p as PropertyAssignment).getName() === `\"${propertyKey}\"`)\n );\n\n let oldPropAstNode: ObjectLiteralExpression | undefined;\n if (existingProp && existingProp.isKind(SyntaxKind.PropertyAssignment)) {\n oldPropAstNode = existingProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n }\n\n const newInitializer = this.convertJsonToAstString(propertyConfig, 2, oldPropAstNode);\n\n if (existingProp) {\n if (existingProp.isKind(SyntaxKind.PropertyAssignment)) {\n existingProp.setInitializer(newInitializer);\n }\n } else {\n propsObj.addPropertyAssignment({\n name: /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(propertyKey) ? propertyKey : JSON.stringify(propertyKey),\n initializer: newInitializer\n });\n }\n\n const file = this.getCollectionFile(collectionId);\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n }\n\n public async deleteProperty(collectionId: string, propertyKey: string) {\n const collectionObj = this.getCollectionObject(collectionId);\n if (!collectionObj) return;\n\n const propertiesProp = collectionObj.getProperty(\"properties\") as PropertyAssignment;\n if (propertiesProp) {\n const propsObj = propertiesProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n if (propsObj) {\n const existingProp = propsObj.getProperty(\n (p: ObjectLiteralElementLike) => \"getName\" in p && typeof (p as PropertyAssignment).getName === \"function\" && ((p as PropertyAssignment).getName() === propertyKey || (p as PropertyAssignment).getName() === `\"${propertyKey}\"`)\n );\n if (existingProp) {\n existingProp.remove();\n const file = this.getCollectionFile(collectionId);\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n }\n }\n }\n\n public async saveCollection(collectionId: string, collectionData: Record<string, unknown>) {\n let file = this.getCollectionFile(collectionId);\n const collectionObj = this.getCollectionObject(collectionId);\n\n if (!file || !collectionObj) {\n // Create a new file\n const safeId = this.sanitizeCollectionId(collectionId);\n const newFilePath = this.safePath(`${safeId}.ts`);\n file = this.project.createSourceFile(newFilePath, `import { CollectionConfig } from \"@rebasepro/types\";\\n\\nconst ${safeId}Collection: CollectionConfig = ${this.convertJsonToAstString(nestAdminKeys(collectionData))};\\n\\nexport default ${safeId}Collection;\\n`, { overwrite: true });\n } else {\n // Update root level properties gracefully\n\n // Force delete securityRules if empty or undefined to handle Formex / serialization stripping\n if (!(\"securityRules\" in collectionData) || collectionData.securityRules === undefined || (Array.isArray(collectionData.securityRules) && collectionData.securityRules.length === 0)) {\n const srProp = collectionObj.getProperty(\"securityRules\");\n if (srProp) {\n srProp.remove();\n }\n\n // If it was in collectionData as an empty array, delete it so the loop below doesn't add it back as \"[]\"\n // Actually, if it's \"[]\", omitting it entirely from the TS file achieves the same logical effect (no RLS rules)\n // and correctly triggers \"unmapped policies\" if the DB still has them.\n delete collectionData[\"securityRules\"];\n }\n\n // The panel works with a flat view model — presentation merged onto the\n // collection — so what arrives here has `icon` and `listProperties` at\n // the top level. On disk they belong inside `admin`. Writing them flat\n // would produce a file the backend loads and ignores and the panel\n // never reads back, which looks exactly like the edit not saving.\n collectionData = nestAdminKeys(collectionData);\n\n for (const key of Object.keys(collectionData)) {\n if (key === \"relations\") continue; // Kept via other AST functions or handled separately.\n\n const prop = collectionObj.getProperty(key) as PropertyAssignment;\n\n let oldAstNode: ObjectLiteralExpression | undefined;\n if (prop && prop.isKind(SyntaxKind.PropertyAssignment)) {\n oldAstNode = prop.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n }\n\n const newInit = this.convertJsonToAstString(collectionData[key], 1, oldAstNode);\n if (prop) {\n prop.setInitializer(newInit);\n } else {\n collectionObj.addPropertyAssignment({\n name: key,\n initializer: newInit\n });\n }\n }\n }\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n\n public async deleteCollection(collectionId: string) {\n const file = this.getCollectionFile(collectionId);\n if (file) {\n file.deleteImmediatelySync();\n }\n }\n}\n","import { Hono } from \"hono\";\nimport { AstSchemaEditor } from \"./ast-schema-editor\";\nimport { errorHandler } from \"./errors\";\nimport { HonoEnv } from \"./types\";\n\nexport function createSchemaEditorRoutes(collectionsDir: string): Hono<HonoEnv> {\n const router = new Hono<HonoEnv>();\n router.onError(errorHandler);\n const editor = new AstSchemaEditor(collectionsDir);\n\n router.post(\"/property/save\", async (c) => {\n const body = await c.req.json();\n const { collectionId, propertyKey, propertyConfig } = body;\n await editor.saveProperty(collectionId, propertyKey, propertyConfig);\n return c.json({ success: true });\n });\n\n router.post(\"/property/delete\", async (c) => {\n const body = await c.req.json();\n const { collectionId, propertyKey } = body;\n await editor.deleteProperty(collectionId, propertyKey);\n return c.json({ success: true });\n });\n\n router.post(\"/collection/save\", async (c) => {\n const body = await c.req.json();\n const { collectionId, collectionData } = body;\n await editor.saveCollection(collectionId, collectionData);\n return c.json({ success: true });\n });\n\n router.post(\"/collection/delete\", async (c) => {\n const body = await c.req.json();\n const { collectionId } = body;\n await editor.deleteCollection(collectionId);\n return c.json({ success: true });\n });\n\n return router;\n}\n\n"],"mappings":"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"}
@@ -235,6 +235,27 @@ var policy = {
235
235
  authUid: () => ({ kind: "authUid" }),
236
236
  authRoles: () => ({ kind: "authRoles" })
237
237
  };
238
+ /** Hard ceiling clamped onto any client-supplied `limit`, on every surface. */
239
+ var MAX_LIST_LIMIT = 1e3;
240
+ /**
241
+ * Resolve a client-supplied list `limit` into a safe, always-defined value.
242
+ *
243
+ * - A provided limit is coerced to an integer and clamped to `[1, maxLimit]`,
244
+ * so `0`, negatives, and absurd values can never bypass the cap.
245
+ * - An absent / blank / non-numeric limit falls back to the mode default:
246
+ * `vectorDefaultLimit` for a vector search, otherwise `defaultLimit`.
247
+ *
248
+ * The return is never `undefined` — no ingress that routes its client limit
249
+ * through this can produce an unbounded read.
250
+ */
251
+ function resolveClientListLimit(rawLimit, opts = {}) {
252
+ const maxLimit = opts.maxLimit ?? 1e3;
253
+ if (rawLimit != null && String(rawLimit).trim() !== "") {
254
+ const parsed = typeof rawLimit === "number" ? rawLimit : parseInt(String(rawLimit), 10);
255
+ if (Number.isFinite(parsed)) return Math.min(Math.max(1, Math.floor(parsed)), maxLimit);
256
+ }
257
+ return opts.vectorSearch ? opts.vectorDefaultLimit ?? 10 : opts.defaultLimit ?? 50;
258
+ }
238
259
  (/* @__PURE__ */ __commonJSMin(((exports, module) => {
239
260
  (function(e) {
240
261
  var t;
@@ -1382,10 +1403,21 @@ function singularizeForKey(name) {
1382
1403
  * have `id` and `path` fields — these are relation-shaped objects from
1383
1404
  * edge cases in the data pipeline (REST fallback, stale cache, custom data source).
1384
1405
  *
1406
+ * When `targetPath` is given, also accepts a bare id. A relation column is a
1407
+ * foreign key, and the REST layer returns it as the scalar it is; only some
1408
+ * fetch paths hydrate it into an object. Which form a caller sees therefore
1409
+ * depends on how the row was loaded, and a caller that only accepted objects
1410
+ * reported half of its own data as a type error. The declared target is the
1411
+ * missing half: with it, an id is a relation that has not been fetched yet.
1412
+ *
1385
1413
  * Returns null if the value cannot be coerced.
1386
1414
  */
1387
- function normalizeToEntityRelation(value, propertyType) {
1415
+ function normalizeToEntityRelation(value, propertyType, targetPath) {
1388
1416
  if (value instanceof EntityRelation) return value;
1417
+ if (targetPath && (typeof value === "string" || typeof value === "number")) {
1418
+ if (value === "") return null;
1419
+ return new EntityRelation(value, targetPath);
1420
+ }
1389
1421
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
1390
1422
  const obj = value;
1391
1423
  if (!(obj.__type === "relation" || obj.__type === "reference" || typeof obj.isEntityRelation === "function" && obj.isEntityRelation() || typeof obj.isEntityReference === "function" && obj.isEntityReference() || propertyType === "relation" && typeof obj.id !== "undefined" && typeof obj.path === "string")) return null;
@@ -2941,6 +2973,30 @@ var RebasePaginationError = class RebasePaginationError extends Error {
2941
2973
  Object.setPrototypeOf(this, RebasePaginationError.prototype);
2942
2974
  }
2943
2975
  };
2976
+ /**
2977
+ * Resolve `limit`/`offset`/`page` into the window a read will actually use.
2978
+ *
2979
+ * Lives here, next to the walk, for the reason at the top of this file: every
2980
+ * transport has to mean the same thing by "page two". Four of them did not —
2981
+ * the REST layer strode by {@link DEFAULT_LIST_LIMIT}, the local-first
2982
+ * evaluator by {@link DEFAULT_PAGE_SIZE}, the in-process accessor by 20, and
2983
+ * the published type documented a fourth number. Pages that overlap or skip
2984
+ * rows are the mildest of those outcomes.
2985
+ *
2986
+ * `page` wins over `offset`, as {@link FindParams} documents. `driverOffset`
2987
+ * is the value to hand a driver: it stays `undefined` when the caller named no
2988
+ * offset, because keyset pagination seeks with a `where` clause and must not
2989
+ * look like it is paging by offset.
2990
+ */
2991
+ function resolveFindWindow(params) {
2992
+ const limit = params?.limit ?? 50;
2993
+ const offset = params?.page != null ? Math.max(0, (params.page - 1) * limit) : params?.offset ?? 0;
2994
+ return {
2995
+ limit,
2996
+ offset,
2997
+ driverOffset: params?.page != null ? offset : params?.offset
2998
+ };
2999
+ }
2944
3000
  function normalizePageSize(raw) {
2945
3001
  if (raw === void 0 || !Number.isFinite(raw)) return 200;
2946
3002
  return Math.max(1, Math.floor(raw));
@@ -3251,18 +3307,8 @@ function serializeLogicalCondition(cond) {
3251
3307
  }
3252
3308
  return `${cond.column}.${restOp}.${stringifyValue(cond.value)}`;
3253
3309
  }
3254
- /**
3255
- * Parse a logical condition wire-format string back into a
3256
- * `LogicalCondition` or `FilterCondition`.
3257
- *
3258
- * @example
3259
- * deserializeLogicalCondition("status.eq.active")
3260
- * // → { column: "status", operator: "==", value: "active" }
3261
- *
3262
- * deserializeLogicalCondition("or(status.eq.active,age.gte.18)")
3263
- * // → { type: "or", conditions: [...] }
3264
- */
3265
- function deserializeLogicalCondition(str) {
3310
+ function deserializeLogicalCondition(str, nesting = 0) {
3311
+ if (nesting > 32) throw new Error(`Filter groups nest more than 32 levels deep. Flatten the condition — \`or(a,or(b,c))\` is \`or(a,b,c)\`.`);
3266
3312
  const logicalMatch = str.match(/^(and|or)\((.+)\)$/);
3267
3313
  if (logicalMatch) {
3268
3314
  const type = logicalMatch[1];
@@ -3273,10 +3319,10 @@ function deserializeLogicalCondition(str) {
3273
3319
  for (let i = 0; i < innerStr.length; i++) if (innerStr[i] === "(") depth++;
3274
3320
  else if (innerStr[i] === ")") depth--;
3275
3321
  else if (innerStr[i] === "," && depth === 0) {
3276
- conditions.push(deserializeLogicalCondition(innerStr.slice(start, i)));
3322
+ conditions.push(deserializeLogicalCondition(innerStr.slice(start, i), nesting + 1));
3277
3323
  start = i + 1;
3278
3324
  }
3279
- conditions.push(deserializeLogicalCondition(innerStr.slice(start)));
3325
+ conditions.push(deserializeLogicalCondition(innerStr.slice(start), nesting + 1));
3280
3326
  return {
3281
3327
  type,
3282
3328
  conditions
@@ -3390,21 +3436,22 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
3390
3436
  const accessor = {
3391
3437
  async find(params) {
3392
3438
  const filter = params?.where ? deserializeFilter(params.where) : void 0;
3393
- const limit = params?.limit ?? 20;
3394
- const offset = params?.offset ?? 0;
3439
+ const { limit, offset, driverOffset } = resolveFindWindow(params);
3395
3440
  const fetchService = driver.restFetchService;
3396
3441
  const rows = fetchService ? await fetchService.fetchCollectionForRest(slug, {
3397
3442
  filter,
3398
- limit: params?.limit,
3399
- offset: params?.offset,
3443
+ logical: params?.logical,
3444
+ limit,
3445
+ offset: driverOffset,
3400
3446
  orderBy: params?.orderBy?.[0],
3401
3447
  order: params?.orderBy?.[1],
3402
3448
  searchString: params?.searchString
3403
3449
  }, params?.include) : await driver.fetchCollection({
3404
3450
  path: slug,
3405
- limit: params?.limit,
3406
- offset: params?.offset,
3451
+ limit,
3452
+ offset: driverOffset,
3407
3453
  filter,
3454
+ logical: params?.logical,
3408
3455
  orderBy: params?.orderBy?.[0],
3409
3456
  order: params?.orderBy?.[1],
3410
3457
  searchString: params?.searchString
@@ -3414,7 +3461,9 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
3414
3461
  if (driver.count) {
3415
3462
  total = await driver.count({
3416
3463
  path: slug,
3417
- filter
3464
+ filter,
3465
+ logical: params?.logical,
3466
+ searchString: params?.searchString
3418
3467
  });
3419
3468
  hasMore = offset + rows.length < total;
3420
3469
  }
@@ -3466,22 +3515,39 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
3466
3515
  values: {}
3467
3516
  } });
3468
3517
  },
3518
+ updateMany: driver.updateMany ? async (updates) => {
3519
+ return (await driver.updateMany({
3520
+ path: slug,
3521
+ updates: updates.map((u) => ({
3522
+ id: u.id,
3523
+ values: u.data
3524
+ }))
3525
+ })).map((row) => rowToEntity(row, slug, getPks()));
3526
+ } : void 0,
3527
+ deleteMany: driver.deleteMany ? async (ids) => {
3528
+ await driver.deleteMany({
3529
+ path: slug,
3530
+ ids
3531
+ });
3532
+ } : void 0,
3469
3533
  count: driver.count ? async (params) => {
3470
3534
  const filter = params?.where ? deserializeFilter(params.where) : void 0;
3471
3535
  return driver.count({
3472
3536
  path: slug,
3473
- filter
3537
+ filter,
3538
+ logical: params?.logical,
3539
+ searchString: params?.searchString
3474
3540
  });
3475
3541
  } : void 0,
3476
3542
  listen: driver.listenCollection ? (params, onUpdate, onError) => {
3477
- const limit = params?.limit ?? 20;
3478
- const offset = params?.offset ?? 0;
3543
+ const { limit, offset, driverOffset } = resolveFindWindow(params);
3479
3544
  const normalize = driver.restFetchService ? inlineRelationRefs : (row) => row;
3480
3545
  return driver.listenCollection({
3481
3546
  path: slug,
3482
- limit: params?.limit,
3483
- offset: params?.offset,
3547
+ limit,
3548
+ offset: driverOffset,
3484
3549
  filter: params?.where,
3550
+ logical: params?.logical,
3485
3551
  orderBy: params?.orderBy?.[0],
3486
3552
  order: params?.orderBy?.[1],
3487
3553
  searchString: params?.searchString,
@@ -3489,7 +3555,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
3489
3555
  onUpdate({
3490
3556
  data: entities.map((row) => rowToEntity(normalize(row), slug, getPks())),
3491
3557
  meta: {
3492
- total: entities.length,
3558
+ total: offset + entities.length,
3493
3559
  limit,
3494
3560
  offset,
3495
3561
  hasMore: entities.length >= limit
@@ -3666,9 +3732,24 @@ function toSdkCollectionClient(snap, slug = "collection") {
3666
3732
  async update(id, data) {
3667
3733
  return entityToRow(await snap.update(id, data));
3668
3734
  },
3735
+ async updateMany(updates) {
3736
+ if (!Array.isArray(updates)) throw new TypeError("updateMany expects an array of { id, data } entries.");
3737
+ if (updates.length === 0) return [];
3738
+ if (!snap.updateMany) throw new Error("Bulk updates are not supported by this collection's data source. Fall back to update() per record.");
3739
+ return (await snap.updateMany(updates.map((u) => ({
3740
+ id: u.id,
3741
+ data: u.data
3742
+ })))).map(entityToRow);
3743
+ },
3669
3744
  delete(id) {
3670
3745
  return snap.delete(id);
3671
3746
  },
3747
+ async deleteMany(ids) {
3748
+ if (!Array.isArray(ids)) throw new TypeError("deleteMany expects an array of ids.");
3749
+ if (ids.length === 0) return;
3750
+ if (!snap.deleteMany) throw new Error("Bulk deletes are not supported by this collection's data source. Fall back to delete() per record.");
3751
+ await snap.deleteMany(ids);
3752
+ },
3672
3753
  count: snap.count ? (params) => snap.count(params) : void 0,
3673
3754
  listen: snap.listen ? (params, onUpdate, onError) => snap.listen(params, (res) => onUpdate({
3674
3755
  data: res.data.map(entityToRow),
@@ -3772,6 +3853,6 @@ function buildRoutedRebaseData({ defaultData, sources, resolveKey }) {
3772
3853
  } });
3773
3854
  }
3774
3855
  //#endregion
3775
- export { Vector as S, ANONYMOUS_USER_ID as _, serializeFilter as a, EntityRelation as b, paginateFind as c, resolveDataSource as d, getEffectiveSecurityRules as f, toSnakeCase as g, buildCompositeId as h, deserializeLogicalCondition as i, CollectionRegistry as l, resolveCollectionRelations as m, buildSdkData as n, serializeLogicalCondition as o, findRelation as p, deserializeFilter as r, collectAllPages as s, buildRoutedRebaseData as t, createDataSourceRegistry as u, isAnonymousUid as v, GeoPoint as x, EntityReference as y };
3856
+ export { EntityReference as C, Vector as E, isAnonymousUid as S, GeoPoint as T, resolvePrimaryKeys as _, serializeFilter as a, resolveClientListLimit as b, paginateFind as c, createDataSourceRegistry as d, resolveDataSource as f, buildCompositeId as g, resolveCollectionRelations as h, deserializeLogicalCondition as i, resolveFindWindow as l, findRelation as m, buildSdkData as n, serializeLogicalCondition as o, getEffectiveSecurityRules as p, deserializeFilter as r, collectAllPages as s, buildRoutedRebaseData as t, CollectionRegistry as u, toSnakeCase as v, EntityRelation as w, ANONYMOUS_USER_ID as x, MAX_LIST_LIMIT as y };
3776
3857
 
3777
- //# sourceMappingURL=src-Cum9kox5.js.map
3858
+ //# sourceMappingURL=src-CY0jBL-x.js.map