@rpcbase/server 0.531.0 → 0.532.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -253,9 +253,13 @@ var resolveRtsQueryDependencyModelNames = async ({ tenantId, ability, modelName,
253
253
  return Array.from(dependencyModelNames);
254
254
  };
255
255
  var runRtsQuery = async ({ tenantId, ability, modelName, query, options, allowInternalModels = false }) => {
256
- if (!allowInternalModels && INTERNAL_MODEL_NAMES.has(modelName)) throw new Error("Model not allowed");
257
- if (!ability.can("read", modelName)) throw new Error("forbidden");
258
- const model = await getTenantModel(tenantId, modelName, ability);
256
+ const { model, finalQuery } = await prepareRtsExecution({
257
+ tenantId,
258
+ ability,
259
+ modelName,
260
+ query,
261
+ allowInternalModels
262
+ });
259
263
  const projection = options.projection ?? void 0;
260
264
  const sort = options.sort;
261
265
  const limit = normalizeLimit(options.limit);
@@ -270,7 +274,6 @@ var runRtsQuery = async ({ tenantId, ability, modelName, query, options, allowIn
270
274
  modelCache,
271
275
  dependencyModelNames: /* @__PURE__ */ new Set()
272
276
  });
273
- const finalQuery = { $and: [query, getAccessibleByQuery(ability, "read", modelName)] };
274
277
  if (options.pagination) {
275
278
  const paginatedQuery = model.find(finalQuery, projection);
276
279
  if (populate !== void 0) paginatedQuery.populate(populate);
@@ -289,7 +292,27 @@ var runRtsQuery = async ({ tenantId, ability, modelName, query, options, allowIn
289
292
  const data = await queryPromise;
290
293
  return { data: Array.isArray(data) ? data : [] };
291
294
  };
295
+ var prepareRtsExecution = async ({ tenantId, ability, modelName, query, allowInternalModels = false }) => {
296
+ if (!allowInternalModels && INTERNAL_MODEL_NAMES.has(modelName)) throw new Error("Model not allowed");
297
+ if (!ability.can("read", modelName)) throw new Error("forbidden");
298
+ return {
299
+ model: await getTenantModel(tenantId, modelName, ability),
300
+ finalQuery: { $and: [query, getAccessibleByQuery(ability, "read", modelName)] }
301
+ };
302
+ };
303
+ var runRtsCount = async ({ tenantId, ability, modelName, query, allowInternalModels = false }) => {
304
+ const { model, finalQuery } = await prepareRtsExecution({
305
+ tenantId,
306
+ ability,
307
+ modelName,
308
+ query,
309
+ allowInternalModels
310
+ });
311
+ const count = await model.countDocuments(finalQuery);
312
+ if (typeof count !== "number" || !Number.isFinite(count) || count < 0) return 0;
313
+ return Math.floor(count);
314
+ };
292
315
  //#endregion
293
- export { normalizeRtsQueryOptions as a, runRtsQuery as c, isRtsRequestAuthorized as i, getDerivedKey as l, RTS_USER_ID_HEADER as n, resolveRtsQueryDependencyModelNames as o, buildRtsAbilityFromRequest as r, resolveRtsRequestTenantId as s, RTS_TENANT_ID_QUERY_PARAM as t };
316
+ export { normalizeRtsQueryOptions as a, runRtsCount as c, isRtsRequestAuthorized as i, runRtsQuery as l, RTS_USER_ID_HEADER as n, resolveRtsQueryDependencyModelNames as o, buildRtsAbilityFromRequest as r, resolveRtsRequestTenantId as s, RTS_TENANT_ID_QUERY_PARAM as t, getDerivedKey as u };
294
317
 
295
- //# sourceMappingURL=queryExecutor-DYVlCvns.js.map
318
+ //# sourceMappingURL=queryExecutor-JadZcQSQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryExecutor-JadZcQSQ.js","names":["assert","hkdfSync","getDerivedKey","masterKey","info","length","salt","Buffer","from","toString","Request","PaginationPageInfo","PaginationSpec","models","LoadModelCtx","buildAbility","buildAbilityFromSession","getAccessibleByQuery","getTenantRolesFromSessionUser","AclSubjectType","AppAbility","Model","getDerivedKey","JsonObject","Record","SessionUser","id","currentTenantId","signedInTenants","HeaderUserDoc","tenants","tenantRoles","RtsPopulateObject","path","model","select","match","options","sort","limit","populate","RtsPopulateOption","Array","RtsQueryOptions","projection","pagination","RtsQueryResult","data","pageInfo","totalCount","PreparedRtsExecution","finalQuery","RTS_TENANT_ID_QUERY_PARAM","RTS_USER_ID_HEADER","QUERY_MAX_LIMIT","INTERNAL_MODEL_NAMES","Set","paginationCursorSigningSecret","getPaginationCursorSigningSecret","masterKey","process","env","MASTER_KEY","trim","Error","normalizeTenantId","value","normalized","normalizeSignedInTenants","isArray","map","tenantId","String","filter","Boolean","getTenantIdFromRequest","req","rawQuery","query","queryTenantId","normalizedFromQuery","session","user","resolveRtsRequestTenantId","resolveRtsRequestUserId","sessionUserId","headerValue","headers","headerUserId","isRtsRequestAuthorized","sessionUser","length","includes","buildRtsAbilityFromRequest","Promise","ability","userId","headerUserIdRaw","rbCtx","User","getGlobal","findById","lean","tenantsRaw","tenant","roles","getTenantModel","modelName","ctx","get","normalizeLimit","Number","isFinite","Math","min","abs","normalizeString","normalizeObject","undefined","normalizePagination","normalizePopulateSelect","normalizePopulateOptions","raw","max","floor","normalizePopulateObject","nestedPopulate","normalizeRtsPopulateOption","entry","normalizeModelName","resolvePopulateRefModelName","explicitModelName","schema","schemaPath","directRef","ref","arrayRef","caster","virtualPath","virtualpath","virtualRef","mergePopulateMatchWithAcl","populateMatch","aclMatch","Object","keys","$and","PreparedPopulateObject","PreparedPopulateOption","resolvePopulateSpecForModel","allowInternalModels","modelCache","dependencyModelNames","Map","getModelCached","targetModelName","cached","loaded","set","resolveOne","parentModel","refModelName","has","can","add","Exclude","nestedModel","normalizedEntry","resolved","all","filtered","normalizeRtsQueryOptions","resolveRtsQueryDependencyModelNames","from","runRtsQuery","prepareRtsExecution","paginatedQuery","find","paginatedResult","paginate","cursor","signingSecret","nodes","queryPromise","accessQuery","runRtsCount","count","countDocuments"],"sources":["../src/getDerivedKey.ts","../src/rts/queryExecutor.ts"],"sourcesContent":["import assert from \"assert\"\nimport { hkdfSync } from \"crypto\"\n\n\nexport const getDerivedKey = (\n masterKey: string,\n info: string,\n length: number = 32, // Default to 256-bit keys\n salt: string = \"\",\n): string => {\n assert(masterKey?.length >= 32, \"MASTER_KEY must be 32 chars or longer.\")\n\n return Buffer.from(hkdfSync(\n \"sha256\",\n masterKey,\n Buffer.from(salt),\n Buffer.from(info),\n length,\n )).toString(\"hex\")\n}\n","import type { Request } from \"express\"\nimport type { PaginationPageInfo, PaginationSpec } from \"@rpcbase/api\"\nimport { models, type LoadModelCtx } from \"@rpcbase/db\"\nimport { buildAbility, buildAbilityFromSession, getAccessibleByQuery, getTenantRolesFromSessionUser, type AclSubjectType, type AppAbility } from \"@rpcbase/db/acl\"\nimport type { Model } from \"mongoose\"\n\nimport { getDerivedKey } from \"../getDerivedKey\"\n\n\ntype JsonObject = Record<string, unknown>\n\ntype SessionUser = {\n id?: unknown\n currentTenantId?: unknown\n signedInTenants?: unknown\n}\n\ntype HeaderUserDoc = {\n tenants?: unknown\n tenantRoles?: unknown\n}\n\nexport type RtsPopulateObject = {\n path: string\n model?: string\n select?: string | JsonObject\n match?: JsonObject\n options?: {\n sort?: Record<string, 1 | -1>\n limit?: number\n }\n populate?: RtsPopulateOption\n}\n\nexport type RtsPopulateOption =\n | string\n | RtsPopulateObject\n | Array<string | RtsPopulateObject>\n\nexport type RtsQueryOptions = {\n projection?: JsonObject\n sort?: Record<string, 1 | -1>\n limit?: number\n populate?: RtsPopulateOption\n pagination?: PaginationSpec\n}\n\nexport type RtsQueryResult = {\n data: unknown[]\n pageInfo?: PaginationPageInfo\n totalCount?: number\n}\n\ntype PreparedRtsExecution = {\n model: Model<any>\n finalQuery: JsonObject\n}\n\nexport const RTS_TENANT_ID_QUERY_PARAM = \"rb-tenant-id\"\nexport const RTS_USER_ID_HEADER = \"rb-user-id\"\n\nconst QUERY_MAX_LIMIT = 4096\nconst INTERNAL_MODEL_NAMES = new Set([\"RBRtsChange\", \"RBRtsCounter\"])\nlet paginationCursorSigningSecret: string | null = null\n\nconst getPaginationCursorSigningSecret = (): string => {\n if (paginationCursorSigningSecret) return paginationCursorSigningSecret\n const masterKey = process.env.MASTER_KEY?.trim()\n if (!masterKey) {\n throw new Error(\"MASTER_KEY must be defined to derive pagination cursor signing secret\")\n }\n paginationCursorSigningSecret = getDerivedKey(masterKey, \"pagination_cursor_signing\")\n return paginationCursorSigningSecret\n}\n\nconst normalizeTenantId = (value: unknown): string | null => {\n if (typeof value !== \"string\") return null\n const normalized = value.trim()\n return normalized ? normalized : null\n}\n\nconst normalizeSignedInTenants = (value: unknown): string[] => {\n if (!Array.isArray(value)) return []\n return value\n .map((tenantId) => normalizeTenantId(String(tenantId)))\n .filter((tenantId): tenantId is string => Boolean(tenantId))\n}\n\nconst getTenantIdFromRequest = (req: Request): string | null => {\n const rawQuery = req.query?.[RTS_TENANT_ID_QUERY_PARAM]\n const queryTenantId = Array.isArray(rawQuery) ? rawQuery[0] : rawQuery\n const normalizedFromQuery = normalizeTenantId(queryTenantId)\n if (normalizedFromQuery) return normalizedFromQuery\n\n return normalizeTenantId((req.session?.user as SessionUser | undefined)?.currentTenantId)\n}\n\nexport const resolveRtsRequestTenantId = (req: Request): string | null => {\n return getTenantIdFromRequest(req)\n}\n\nexport const resolveRtsRequestUserId = (req: Request): string | null => {\n const sessionUserId = normalizeTenantId((req.session?.user as SessionUser | undefined)?.id)\n if (sessionUserId) return sessionUserId\n\n const headerValue = req.headers[RTS_USER_ID_HEADER]\n const headerUserId = Array.isArray(headerValue) ? headerValue[0] : headerValue\n return normalizeTenantId(headerUserId)\n}\n\nexport const isRtsRequestAuthorized = (req: Request, tenantId: string): boolean => {\n const sessionUser = req.session?.user as SessionUser | undefined\n if (!sessionUser) return false\n\n const signedInTenants = normalizeSignedInTenants(sessionUser.signedInTenants)\n if (signedInTenants.length > 0) {\n return signedInTenants.includes(tenantId)\n }\n\n const currentTenantId = normalizeTenantId(sessionUser.currentTenantId)\n if (!currentTenantId) return false\n return currentTenantId === tenantId\n}\n\nexport const buildRtsAbilityFromRequest = async (\n req: Request,\n tenantId: string,\n): Promise<{ ability: AppAbility; userId: string | null }> => {\n const sessionUserId = normalizeTenantId((req.session?.user as SessionUser | undefined)?.id)\n if (sessionUserId) {\n const ability = buildAbilityFromSession({ tenantId, session: req.session })\n return { ability, userId: sessionUserId }\n }\n\n const headerValue = req.headers[RTS_USER_ID_HEADER]\n const headerUserIdRaw = Array.isArray(headerValue) ? headerValue[0] : headerValue\n const headerUserId = normalizeTenantId(headerUserIdRaw)\n if (!headerUserId) {\n const ability = buildAbilityFromSession({ tenantId, session: req.session })\n return { ability, userId: null }\n }\n\n const rbCtx: LoadModelCtx = { req: { session: null } }\n const User = await models.getGlobal(\"RBUser\", rbCtx)\n const user = await User.findById(headerUserId, { tenants: 1, tenantRoles: 1 }).lean() as HeaderUserDoc | null\n\n const tenantsRaw = user?.tenants\n const tenants = Array.isArray(tenantsRaw) ? tenantsRaw.map((tenant) => String(tenant)) : []\n if (!tenants.includes(tenantId)) {\n throw new Error(\"Tenant not authorized for this session\")\n }\n\n const roles = getTenantRolesFromSessionUser(user, tenantId)\n return {\n ability: buildAbility({ tenantId, userId: headerUserId, roles: roles.length ? roles : [\"owner\"] }),\n userId: headerUserId,\n }\n}\n\nconst getTenantModel = async (tenantId: string, modelName: string, ability: AppAbility): Promise<Model<any>> => {\n const ctx: LoadModelCtx = {\n req: {\n session: {\n user: {\n currentTenantId: tenantId,\n },\n },\n },\n ability,\n }\n\n return models.get(modelName, ctx)\n}\n\nconst normalizeLimit = (limit?: number): number => {\n if (typeof limit !== \"number\") return QUERY_MAX_LIMIT\n if (!Number.isFinite(limit)) return QUERY_MAX_LIMIT\n return Math.min(QUERY_MAX_LIMIT, Math.abs(limit))\n}\n\nconst normalizeString = (value: unknown): string => {\n return typeof value === \"string\" ? value.trim() : \"\"\n}\n\nconst normalizeObject = (value: unknown): JsonObject | undefined => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined\n return value as JsonObject\n}\n\nconst normalizePagination = (value: unknown): PaginationSpec | undefined => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined\n return value as PaginationSpec\n}\n\nconst normalizePopulateSelect = (value: unknown): string | JsonObject | undefined => {\n if (typeof value === \"string\") {\n const normalized = value.trim()\n return normalized || undefined\n }\n return normalizeObject(value)\n}\n\nconst normalizePopulateOptions = (value: unknown): RtsPopulateObject[\"options\"] | undefined => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined\n const raw = value as { sort?: unknown; limit?: unknown }\n const normalized: RtsPopulateObject[\"options\"] = {}\n\n if (raw.sort && typeof raw.sort === \"object\" && !Array.isArray(raw.sort)) {\n normalized.sort = raw.sort as Record<string, 1 | -1>\n }\n\n if (typeof raw.limit === \"number\" && Number.isFinite(raw.limit)) {\n normalized.limit = Math.max(0, Math.floor(Math.abs(raw.limit)))\n }\n\n if (!normalized.sort && normalized.limit === undefined) return undefined\n return normalized\n}\n\nconst normalizePopulateObject = (value: unknown): RtsPopulateObject | undefined => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined\n const raw = value as Record<string, unknown>\n const path = normalizeString(raw.path)\n if (!path) return undefined\n\n const normalized: RtsPopulateObject = { path }\n\n const model = normalizeString(raw.model)\n if (model) normalized.model = model\n\n const select = normalizePopulateSelect(raw.select)\n if (select !== undefined) normalized.select = select\n\n const match = normalizeObject(raw.match)\n if (match) normalized.match = match\n\n const nestedPopulate = normalizeRtsPopulateOption(raw.populate)\n if (nestedPopulate !== undefined) normalized.populate = nestedPopulate\n\n const options = normalizePopulateOptions(raw.options)\n if (options) normalized.options = options\n\n return normalized\n}\n\nconst normalizeRtsPopulateOption = (value: unknown): RtsPopulateOption | undefined => {\n if (typeof value === \"string\") {\n const normalized = value.trim()\n return normalized || undefined\n }\n\n if (Array.isArray(value)) {\n const normalized = value\n .map((entry) => {\n if (typeof entry === \"string\") {\n const path = entry.trim()\n return path || null\n }\n return normalizePopulateObject(entry) ?? null\n })\n .filter((entry): entry is string | RtsPopulateObject => entry !== null)\n\n return normalized.length > 0 ? normalized : undefined\n }\n\n return normalizePopulateObject(value)\n}\n\nconst normalizeModelName = (value: unknown): string | null => {\n if (typeof value !== \"string\") return null\n const normalized = value.trim()\n return normalized || null\n}\n\nconst resolvePopulateRefModelName = (\n model: Model<any>,\n path: string,\n explicitModelName: string | null,\n): string | null => {\n if (explicitModelName) return explicitModelName\n\n const schema = model.schema as any\n const schemaPath = typeof schema.path === \"function\" ? schema.path(path) : null\n const directRef = normalizeModelName(schemaPath?.options?.ref)\n if (directRef) return directRef\n\n const arrayRef = normalizeModelName(schemaPath?.caster?.options?.ref)\n if (arrayRef) return arrayRef\n\n const virtualPath = typeof schema.virtualpath === \"function\" ? schema.virtualpath(path) : null\n const virtualRef = normalizeModelName(virtualPath?.options?.ref)\n if (virtualRef) return virtualRef\n\n return null\n}\n\nconst mergePopulateMatchWithAcl = (\n populateMatch: JsonObject | undefined,\n aclMatch: JsonObject,\n): JsonObject => {\n if (!populateMatch || Object.keys(populateMatch).length === 0) return aclMatch\n return { $and: [populateMatch, aclMatch] }\n}\n\ntype PreparedPopulateObject = {\n path: string\n model?: string\n select?: string | JsonObject\n match?: JsonObject\n options?: {\n sort?: Record<string, 1 | -1>\n limit?: number\n }\n populate?: PreparedPopulateOption\n}\n\ntype PreparedPopulateOption =\n | string\n | PreparedPopulateObject\n | Array<string | PreparedPopulateObject>\n\nconst resolvePopulateSpecForModel = async ({\n tenantId,\n model,\n ability,\n populate,\n allowInternalModels,\n modelCache,\n dependencyModelNames,\n}: {\n tenantId: string\n model: Model<any>\n ability: AppAbility\n populate: RtsPopulateOption | undefined\n allowInternalModels: boolean\n modelCache: Map<string, Model<any>>\n dependencyModelNames: Set<string>\n}): Promise<PreparedPopulateOption | undefined> => {\n if (!populate) return undefined\n\n const getModelCached = async (targetModelName: string): Promise<Model<any>> => {\n const cached = modelCache.get(targetModelName)\n if (cached) return cached\n const loaded = await getTenantModel(tenantId, targetModelName, ability)\n modelCache.set(targetModelName, loaded)\n return loaded\n }\n\n const resolveOne = async (\n entry: string | RtsPopulateObject,\n parentModel: Model<any>,\n ): Promise<string | PreparedPopulateObject | null> => {\n if (typeof entry === \"string\") {\n const path = entry.trim()\n if (!path) return null\n\n const refModelName = resolvePopulateRefModelName(parentModel, path, null)\n if (!refModelName) return path\n if (!allowInternalModels && INTERNAL_MODEL_NAMES.has(refModelName)) {\n throw new Error(\"Model not allowed\")\n }\n if (!ability.can(\"read\", refModelName as AclSubjectType)) {\n throw new Error(\"forbidden\")\n }\n\n dependencyModelNames.add(refModelName)\n\n const aclMatch = getAccessibleByQuery(\n ability,\n \"read\",\n refModelName as Exclude<AclSubjectType, \"all\">,\n )\n return {\n path,\n match: aclMatch as JsonObject,\n }\n }\n\n const path = entry.path.trim()\n if (!path) return null\n\n const explicitModelName = normalizeModelName(entry.model)\n const refModelName = resolvePopulateRefModelName(parentModel, path, explicitModelName)\n let nestedModel = parentModel\n\n const normalizedEntry: PreparedPopulateObject = {\n path,\n }\n\n if (entry.select !== undefined) normalizedEntry.select = entry.select\n if (entry.options !== undefined) normalizedEntry.options = entry.options\n if (explicitModelName) normalizedEntry.model = explicitModelName\n if (entry.match !== undefined) normalizedEntry.match = entry.match\n\n if (refModelName) {\n if (!allowInternalModels && INTERNAL_MODEL_NAMES.has(refModelName)) {\n throw new Error(\"Model not allowed\")\n }\n if (!ability.can(\"read\", refModelName as AclSubjectType)) {\n throw new Error(\"forbidden\")\n }\n\n dependencyModelNames.add(refModelName)\n nestedModel = await getModelCached(refModelName)\n\n const aclMatch = getAccessibleByQuery(\n ability,\n \"read\",\n refModelName as Exclude<AclSubjectType, \"all\">,\n ) as JsonObject\n normalizedEntry.match = mergePopulateMatchWithAcl(\n normalizedEntry.match,\n aclMatch,\n )\n } else if (entry.populate !== undefined) {\n throw new Error(\"Populate path must reference a model when nested populate is used\")\n }\n\n const nestedPopulate = await resolvePopulateSpecForModel({\n tenantId,\n model: nestedModel,\n ability,\n populate: entry.populate,\n allowInternalModels,\n modelCache,\n dependencyModelNames,\n })\n if (nestedPopulate !== undefined) normalizedEntry.populate = nestedPopulate\n\n return normalizedEntry\n }\n\n if (Array.isArray(populate)) {\n const resolved = await Promise.all(populate.map((entry) => resolveOne(entry, model)))\n const filtered = resolved.filter((entry): entry is string | PreparedPopulateObject => entry !== null)\n return filtered.length > 0 ? filtered : undefined\n }\n\n const resolved = await resolveOne(populate, model)\n return resolved ?? undefined\n}\n\nexport const normalizeRtsQueryOptions = (options: RtsQueryOptions | undefined): RtsQueryOptions => {\n if (!options || typeof options !== \"object\") return {}\n const normalized: RtsQueryOptions = {}\n\n if (options.projection && typeof options.projection === \"object\" && !Array.isArray(options.projection)) {\n normalized.projection = options.projection\n }\n\n if (options.sort && typeof options.sort === \"object\" && !Array.isArray(options.sort)) {\n normalized.sort = options.sort\n }\n\n normalized.limit = normalizeLimit(options.limit)\n normalized.populate = normalizeRtsPopulateOption(options.populate)\n normalized.pagination = normalizePagination(options.pagination)\n\n return normalized\n}\n\nexport const resolveRtsQueryDependencyModelNames = async ({\n tenantId,\n ability,\n modelName,\n options,\n allowInternalModels = false,\n}: {\n tenantId: string\n ability: AppAbility\n modelName: string\n options: RtsQueryOptions\n allowInternalModels?: boolean\n}): Promise<string[]> => {\n const model = await getTenantModel(tenantId, modelName, ability)\n const modelCache = new Map<string, Model<any>>()\n modelCache.set(modelName, model)\n\n const dependencyModelNames = new Set<string>()\n await resolvePopulateSpecForModel({\n tenantId,\n model,\n ability,\n populate: options.populate,\n allowInternalModels,\n modelCache,\n dependencyModelNames,\n })\n\n return Array.from(dependencyModelNames)\n}\n\nexport const runRtsQuery = async ({\n tenantId,\n ability,\n modelName,\n query,\n options,\n allowInternalModels = false,\n}: {\n tenantId: string\n ability: AppAbility\n modelName: string\n query: JsonObject\n options: RtsQueryOptions\n allowInternalModels?: boolean\n}): Promise<RtsQueryResult> => {\n const { model, finalQuery } = await prepareRtsExecution({\n tenantId,\n ability,\n modelName,\n query,\n allowInternalModels,\n })\n const projection = options.projection ?? undefined\n const sort = options.sort\n const limit = normalizeLimit(options.limit)\n const modelCache = new Map<string, Model<any>>()\n modelCache.set(modelName, model)\n\n const populate = await resolvePopulateSpecForModel({\n tenantId,\n model,\n ability,\n populate: options.populate,\n allowInternalModels,\n modelCache,\n dependencyModelNames: new Set<string>(),\n })\n\n if (options.pagination) {\n const paginatedQuery = model.find(finalQuery, projection)\n if (populate !== undefined) {\n paginatedQuery.populate(populate as any)\n }\n\n const paginatedResult = await paginatedQuery.paginate(options.pagination, {\n cursor: {\n signingSecret: getPaginationCursorSigningSecret(),\n },\n })\n const totalCount = typeof paginatedResult.totalCount === \"number\"\n && Number.isFinite(paginatedResult.totalCount)\n && paginatedResult.totalCount >= 0\n ? Math.floor(paginatedResult.totalCount)\n : undefined\n\n return {\n data: Array.isArray(paginatedResult.nodes) ? paginatedResult.nodes : [],\n pageInfo: paginatedResult.pageInfo,\n ...(totalCount !== undefined ? { totalCount } : {}),\n }\n }\n\n const queryPromise = model.find(finalQuery, projection)\n if (populate !== undefined) {\n queryPromise.populate(populate as any)\n }\n if (sort && Object.keys(sort).length) {\n queryPromise.sort(sort)\n }\n queryPromise.limit(limit)\n\n const data = await queryPromise\n return { data: Array.isArray(data) ? data : [] }\n}\n\nconst prepareRtsExecution = async ({\n tenantId,\n ability,\n modelName,\n query,\n allowInternalModels = false,\n}: {\n tenantId: string\n ability: AppAbility\n modelName: string\n query: JsonObject\n allowInternalModels?: boolean\n}): Promise<PreparedRtsExecution> => {\n if (!allowInternalModels && INTERNAL_MODEL_NAMES.has(modelName)) {\n throw new Error(\"Model not allowed\")\n }\n\n if (!ability.can(\"read\", modelName as AclSubjectType)) {\n throw new Error(\"forbidden\")\n }\n\n const model = await getTenantModel(tenantId, modelName, ability)\n const accessQuery = getAccessibleByQuery(ability, \"read\", modelName as Exclude<AclSubjectType, \"all\">)\n const finalQuery: JsonObject = { $and: [query, accessQuery] }\n\n return { model, finalQuery }\n}\n\nexport const runRtsCount = async ({\n tenantId,\n ability,\n modelName,\n query,\n allowInternalModels = false,\n}: {\n tenantId: string\n ability: AppAbility\n modelName: string\n query: JsonObject\n allowInternalModels?: boolean\n}): Promise<number> => {\n const { model, finalQuery } = await prepareRtsExecution({\n tenantId,\n ability,\n modelName,\n query,\n allowInternalModels,\n })\n\n const count = await model.countDocuments(finalQuery)\n if (typeof count !== \"number\" || !Number.isFinite(count) || count < 0) {\n return 0\n }\n\n return Math.floor(count)\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rts/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAA;AAGtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAG7C,OAAO,EAA6F,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAG5I,OAAO,EAAiC,KAAK,SAAS,EAAE,MAAM,IAAI,CAAA;AAwClE,KAAK,UAAU,GAAG;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,UAAU,CAAA;CACpB,CAAA;AA0BD,KAAK,SAAS,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAA;AA6B3D,cAAM,SAAS;IACb,SAAgB,EAAE,EAAE,MAAM,CAAA;IAC1B,SAAgB,QAAQ,EAAE,MAAM,CAAA;IAChC,SAAgB,MAAM,EAAE,MAAM,CAAA;IAE9B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAW;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqD;gBAE3D,EACjB,EAAE,EACF,EAAE,EACF,IAAI,GACL,EAAE;QACD,EAAE,EAAE,MAAM,CAAA;QACV,EAAE,EAAE,SAAS,CAAA;QACb,IAAI,EAAE,UAAU,CAAA;KACjB;IAOM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI;IAOlE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI;IAO7D,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI;IAI5C,KAAK,IAAI,IAAI;IAQb,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;CAOvD;AA4lBD,eAAO,MAAM,OAAO,GAAI,4NAQrB;IACD,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,iBAAiB,CAAC,EAAE,cAAc,CAAA;IAClC,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B,KAAG,IAyHH,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,SAAS,SAAS,KAAG,IAEvD,CAAA;AAED,eAAO,MAAM,qBAAqB,GAAI,UAAU,MAAM,EAAE,WAAW,MAAM,KAAG,IAE3E,CAAA;AAED,cAAc,UAAU,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rts/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAA;AAGtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAG7C,OAAO,EAA6F,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAG5I,OAAO,EAAiC,KAAK,SAAS,EAAE,MAAM,IAAI,CAAA;AA+DlE,KAAK,UAAU,GAAG;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,UAAU,CAAA;CACpB,CAAA;AA+BD,KAAK,SAAS,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAA;AA+B3D,cAAM,SAAS;IACb,SAAgB,EAAE,EAAE,MAAM,CAAA;IAC1B,SAAgB,QAAQ,EAAE,MAAM,CAAA;IAChC,SAAgB,MAAM,EAAE,MAAM,CAAA;IAE9B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAW;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqD;gBAE3D,EACjB,EAAE,EACF,EAAE,EACF,IAAI,GACL,EAAE;QACD,EAAE,EAAE,MAAM,CAAA;QACV,EAAE,EAAE,SAAS,CAAA;QACb,IAAI,EAAE,UAAU,CAAA;KACjB;IAOM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI;IAOlE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI;IAO7D,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI;IAI5C,KAAK,IAAI,IAAI;IAQb,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;CAOvD;AAq4BD,eAAO,MAAM,OAAO,GAAI,4NAQrB;IACD,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,iBAAiB,CAAC,EAAE,cAAc,CAAA;IAClC,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B,KAAG,IA4HH,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,SAAS,SAAS,KAAG,IAEvD,CAAA;AAED,eAAO,MAAM,qBAAqB,GAAI,UAAU,MAAM,EAAE,WAAW,MAAM,KAAG,IAE3E,CAAA;AAED,cAAc,UAAU,CAAA"}
package/dist/rts/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as normalizeRtsQueryOptions, c as runRtsQuery, n as RTS_USER_ID_HEADER, o as resolveRtsQueryDependencyModelNames, t as RTS_TENANT_ID_QUERY_PARAM } from "../queryExecutor-DYVlCvns.js";
1
+ import { a as normalizeRtsQueryOptions, c as runRtsCount, l as runRtsQuery, n as RTS_USER_ID_HEADER, o as resolveRtsQueryDependencyModelNames, t as RTS_TENANT_ID_QUERY_PARAM } from "../queryExecutor-JadZcQSQ.js";
2
2
  import { models } from "@rpcbase/db";
3
3
  import { buildAbility, buildAbilityFromSession, getTenantRolesFromSessionUser } from "@rpcbase/db/acl";
4
4
  import { randomUUID } from "node:crypto";
@@ -22,7 +22,9 @@ var socketMeta = /* @__PURE__ */ new Map();
22
22
  var socketWrappers = /* @__PURE__ */ new Map();
23
23
  var socketCleanup = /* @__PURE__ */ new Map();
24
24
  var socketSubscriptions = /* @__PURE__ */ new Map();
25
+ var socketCountSubscriptions = /* @__PURE__ */ new Map();
25
26
  var subscriptions = /* @__PURE__ */ new Map();
27
+ var countSubscriptions = /* @__PURE__ */ new Map();
26
28
  var changeStreams = /* @__PURE__ */ new Map();
27
29
  var dispatchTimers = /* @__PURE__ */ new Map();
28
30
  var upgradeMeta = /* @__PURE__ */ new WeakMap();
@@ -206,7 +208,7 @@ var scheduleDispatchSubscriptionsForModel = (tenantId, modelName) => {
206
208
  const delay = Math.max(0, Math.min(1e3, Math.floor(dispatchDebounceMs)));
207
209
  dispatchTimers.set(key, setTimeout(() => {
208
210
  dispatchTimers.delete(key);
209
- dispatchSubscriptionsForModel(tenantId, modelName);
211
+ Promise.all([dispatchSubscriptionsForModel(tenantId, modelName), dispatchCountSubscriptionsForModel(tenantId, modelName)]).catch(() => {});
210
212
  }, delay));
211
213
  };
212
214
  var runAndSendQuery = async ({ tenantId, targetSocketIds, ability, modelName, queryKey, query, options }) => {
@@ -232,6 +234,25 @@ var runAndSendQuery = async ({ tenantId, targetSocketIds, ability, modelName, qu
232
234
  sendWs(ws, payload);
233
235
  }
234
236
  };
237
+ var runAndSendCount = async ({ tenantId, targetSocketIds, ability, modelName, queryKey, query }) => {
238
+ const payload = {
239
+ type: "count-payload",
240
+ modelName,
241
+ queryKey,
242
+ count: await runRtsCount({
243
+ tenantId,
244
+ ability,
245
+ modelName,
246
+ query,
247
+ allowInternalModels
248
+ })
249
+ };
250
+ for (const socketId of targetSocketIds) {
251
+ const ws = sockets.get(socketId);
252
+ if (!ws) continue;
253
+ sendWs(ws, payload);
254
+ }
255
+ };
235
256
  var subscriptionDependsOnModel = (changedModelName, ownerModelName, subscription) => {
236
257
  if (ownerModelName === changedModelName) return true;
237
258
  return subscription.dependencyModelNames.includes(changedModelName);
@@ -242,6 +263,12 @@ var hasAnySubscriptionsDependingOnModel = (tenantId, modelName) => {
242
263
  for (const userSubs of tenantSubs.values()) for (const [ownerModelName, modelSubs] of userSubs.entries()) for (const subscription of modelSubs.values()) if (subscriptionDependsOnModel(modelName, ownerModelName, subscription)) return true;
243
264
  return false;
244
265
  };
266
+ var hasAnyCountSubscriptionsForModel = (tenantId, modelName) => {
267
+ const tenantSubs = countSubscriptions.get(tenantId);
268
+ if (!tenantSubs) return false;
269
+ for (const userSubs of tenantSubs.values()) if (userSubs.get(modelName)?.size) return true;
270
+ return false;
271
+ };
245
272
  var dispatchSubscriptionsForModel = async (tenantId, modelName) => {
246
273
  const tenantSubs = subscriptions.get(tenantId);
247
274
  if (!tenantSubs || !tenantSubs.size) return;
@@ -277,6 +304,43 @@ var dispatchSubscriptionsForModel = async (tenantId, modelName) => {
277
304
  }
278
305
  }
279
306
  };
307
+ var dispatchCountSubscriptionsForModel = async (tenantId, modelName) => {
308
+ const tenantSubs = countSubscriptions.get(tenantId);
309
+ if (!tenantSubs || !tenantSubs.size) return;
310
+ for (const userSubs of tenantSubs.values()) {
311
+ const modelSubs = userSubs.get(modelName);
312
+ if (!modelSubs) continue;
313
+ for (const [queryKey, sub] of modelSubs.entries()) {
314
+ const targetSocketIds = Array.from(sub.socketIds);
315
+ if (!targetSocketIds.length) continue;
316
+ const socketId = targetSocketIds[0];
317
+ const ability = socketMeta.get(socketId)?.ability;
318
+ if (!ability) continue;
319
+ try {
320
+ await runAndSendCount({
321
+ tenantId,
322
+ targetSocketIds,
323
+ ability,
324
+ modelName,
325
+ queryKey,
326
+ query: sub.query
327
+ });
328
+ } catch (err) {
329
+ const payload = {
330
+ type: "count-payload",
331
+ modelName,
332
+ queryKey,
333
+ error: redactErrorMessage(err)
334
+ };
335
+ for (const targetSocketId of targetSocketIds) {
336
+ const ws = sockets.get(targetSocketId);
337
+ if (!ws) continue;
338
+ sendWs(ws, payload);
339
+ }
340
+ }
341
+ }
342
+ }
343
+ };
280
344
  var ensureChangeStream = async (tenantId, modelName) => {
281
345
  const tenantStreams = changeStreams.get(tenantId) ?? /* @__PURE__ */ new Map();
282
346
  changeStreams.set(tenantId, tenantStreams);
@@ -322,6 +386,25 @@ var addSocketSubscription = ({ socketId, tenantId, userId, modelName, queryKey,
322
386
  byModel.set(modelName, querySet);
323
387
  querySet.add(queryKey);
324
388
  };
389
+ var addSocketCountSubscription = ({ socketId, tenantId, userId, modelName, queryKey, query }) => {
390
+ const tenantSubs = countSubscriptions.get(tenantId) ?? /* @__PURE__ */ new Map();
391
+ countSubscriptions.set(tenantId, tenantSubs);
392
+ const userSubs = tenantSubs.get(userId) ?? /* @__PURE__ */ new Map();
393
+ tenantSubs.set(userId, userSubs);
394
+ const modelSubs = userSubs.get(modelName) ?? /* @__PURE__ */ new Map();
395
+ userSubs.set(modelName, modelSubs);
396
+ const existing = modelSubs.get(queryKey);
397
+ if (existing) existing.socketIds.add(socketId);
398
+ else modelSubs.set(queryKey, {
399
+ query,
400
+ socketIds: new Set([socketId])
401
+ });
402
+ const byModel = socketCountSubscriptions.get(socketId) ?? /* @__PURE__ */ new Map();
403
+ socketCountSubscriptions.set(socketId, byModel);
404
+ const querySet = byModel.get(modelName) ?? /* @__PURE__ */ new Set();
405
+ byModel.set(modelName, querySet);
406
+ querySet.add(queryKey);
407
+ };
325
408
  var removeSocketSubscription = ({ socketId, tenantId, userId, modelName, queryKey }) => {
326
409
  const tenantSubs = subscriptions.get(tenantId);
327
410
  const userSubs = tenantSubs?.get(userId);
@@ -341,7 +424,7 @@ var removeSocketSubscription = ({ socketId, tenantId, userId, modelName, queryKe
341
424
  if (modelSubs && modelSubs.size === 0) userSubs?.delete(modelName);
342
425
  if (userSubs && userSubs.size === 0) tenantSubs?.delete(userId);
343
426
  for (const affectedModelName of affectedModelNames) {
344
- if (hasAnySubscriptionsDependingOnModel(tenantId, affectedModelName)) continue;
427
+ if (hasAnySubscriptionsDependingOnModel(tenantId, affectedModelName) || hasAnyCountSubscriptionsForModel(tenantId, affectedModelName)) continue;
345
428
  const tenantStreams = changeStreams.get(tenantId);
346
429
  const stream = tenantStreams?.get(affectedModelName);
347
430
  if (stream) {
@@ -356,6 +439,46 @@ var removeSocketSubscription = ({ socketId, tenantId, userId, modelName, queryKe
356
439
  if (tenantSubs && tenantSubs.size === 0) subscriptions.delete(tenantId);
357
440
  if (byModel && byModel.size === 0) socketSubscriptions.delete(socketId);
358
441
  };
442
+ var removeSocketCountSubscription = ({ socketId, tenantId, userId, modelName, queryKey }) => {
443
+ const tenantSubs = countSubscriptions.get(tenantId);
444
+ const userSubs = tenantSubs?.get(userId);
445
+ const modelSubs = userSubs?.get(modelName);
446
+ const sub = modelSubs?.get(queryKey);
447
+ if (sub) {
448
+ sub.socketIds.delete(socketId);
449
+ if (!sub.socketIds.size) modelSubs?.delete(queryKey);
450
+ }
451
+ const byModel = socketCountSubscriptions.get(socketId);
452
+ const set = byModel?.get(modelName);
453
+ if (set) {
454
+ set.delete(queryKey);
455
+ if (!set.size) byModel?.delete(modelName);
456
+ }
457
+ if (modelSubs && modelSubs.size === 0) userSubs?.delete(modelName);
458
+ if (userSubs && userSubs.size === 0) tenantSubs?.delete(userId);
459
+ if (!hasAnySubscriptionsDependingOnModel(tenantId, modelName) && !hasAnyCountSubscriptionsForModel(tenantId, modelName)) {
460
+ const tenantStreams = changeStreams.get(tenantId);
461
+ const stream = tenantStreams?.get(modelName);
462
+ if (stream) {
463
+ try {
464
+ stream.close();
465
+ } catch {}
466
+ clearDispatchTimer(tenantId, modelName);
467
+ tenantStreams?.delete(modelName);
468
+ if (tenantStreams && tenantStreams.size === 0) changeStreams.delete(tenantId);
469
+ }
470
+ }
471
+ if (tenantSubs && tenantSubs.size === 0) countSubscriptions.delete(tenantId);
472
+ if (byModel && byModel.size === 0) socketCountSubscriptions.delete(socketId);
473
+ };
474
+ var getSocketSubscriptionCount = (socketId) => {
475
+ let count = 0;
476
+ const querySubscriptionsByModel = socketSubscriptions.get(socketId);
477
+ if (querySubscriptionsByModel) for (const set of querySubscriptionsByModel.values()) count += set.size;
478
+ const countSubscriptionsByModel = socketCountSubscriptions.get(socketId);
479
+ if (countSubscriptionsByModel) for (const set of countSubscriptionsByModel.values()) count += set.size;
480
+ return count;
481
+ };
359
482
  var cleanupSocket = (socketId) => {
360
483
  const meta = socketMeta.get(socketId);
361
484
  if (meta) {
@@ -367,8 +490,17 @@ var cleanupSocket = (socketId) => {
367
490
  modelName,
368
491
  queryKey
369
492
  });
493
+ const countByModel = socketCountSubscriptions.get(socketId);
494
+ if (countByModel) for (const [modelName, keys] of countByModel.entries()) for (const queryKey of keys.values()) removeSocketCountSubscription({
495
+ socketId,
496
+ tenantId: meta.tenantId,
497
+ userId: meta.userId,
498
+ modelName,
499
+ queryKey
500
+ });
370
501
  }
371
502
  socketSubscriptions.delete(socketId);
503
+ socketCountSubscriptions.delete(socketId);
372
504
  const cleanupFns = socketCleanup.get(socketId) ?? [];
373
505
  socketCleanup.delete(socketId);
374
506
  for (const fn of cleanupFns) try {
@@ -385,14 +517,27 @@ var handleClientMessage = async ({ socketId, meta, message }) => {
385
517
  socketWrappers.get(socketId)?.dispatch(message.event, message.payload);
386
518
  return;
387
519
  }
388
- if (!message.modelName || typeof message.modelName !== "string") return;
389
- if (!allowInternalModels && INTERNAL_MODEL_NAMES.has(message.modelName)) {
520
+ const isCountMessage = message.type === "run-count" || message.type === "register-count" || message.type === "remove-count";
521
+ const sendAccessError = (error) => {
522
+ if (isCountMessage) {
523
+ sendWs(ws, {
524
+ type: "count-payload",
525
+ modelName: message.modelName ?? "",
526
+ queryKey: message.queryKey ?? "",
527
+ error
528
+ });
529
+ return;
530
+ }
390
531
  sendWs(ws, {
391
532
  type: "query-payload",
392
- modelName: message.modelName,
533
+ modelName: message.modelName ?? "",
393
534
  queryKey: message.queryKey ?? "",
394
- error: "Model not allowed"
535
+ error
395
536
  });
537
+ };
538
+ if (!message.modelName || typeof message.modelName !== "string") return;
539
+ if (!allowInternalModels && INTERNAL_MODEL_NAMES.has(message.modelName)) {
540
+ sendAccessError("Model not allowed");
396
541
  return;
397
542
  }
398
543
  if (!message.queryKey || typeof message.queryKey !== "string") return;
@@ -407,24 +552,26 @@ var handleClientMessage = async ({ socketId, meta, message }) => {
407
552
  });
408
553
  return;
409
554
  }
555
+ if (message.type === "remove-count") {
556
+ removeSocketCountSubscription({
557
+ socketId,
558
+ tenantId: meta.tenantId,
559
+ userId: meta.userId,
560
+ modelName: message.modelName,
561
+ queryKey: message.queryKey
562
+ });
563
+ return;
564
+ }
410
565
  if (!message.query || typeof message.query !== "object") return;
411
- const options = normalizeRtsQueryOptions(message.options);
412
566
  const ability = meta.ability;
413
567
  if (!ability.can("read", message.modelName)) {
414
- sendWs(ws, {
415
- type: "query-payload",
416
- modelName: message.modelName,
417
- queryKey: message.queryKey,
418
- error: "forbidden"
419
- });
568
+ sendAccessError("forbidden");
420
569
  return;
421
570
  }
422
- if (message.type === "register-query") {
423
- if (!(socketSubscriptions.get(socketId)?.get(message.modelName)?.has(message.queryKey) ?? false)) {
424
- let count = 0;
425
- const byModel = socketSubscriptions.get(socketId);
426
- if (byModel) for (const set of byModel.values()) count += set.size;
427
- if (count >= maxSubscriptionsPerSocket) {
571
+ if (message.type === "register-query" || message.type === "run-query") {
572
+ const options = normalizeRtsQueryOptions(message.options);
573
+ if (message.type === "register-query") {
574
+ if (!(socketSubscriptions.get(socketId)?.get(message.modelName)?.has(message.queryKey) ?? false) && getSocketSubscriptionCount(socketId) >= maxSubscriptionsPerSocket) {
428
575
  sendWs(ws, {
429
576
  type: "query-payload",
430
577
  modelName: message.modelName,
@@ -433,15 +580,59 @@ var handleClientMessage = async ({ socketId, meta, message }) => {
433
580
  });
434
581
  return;
435
582
  }
583
+ let dependencyModelNames = [];
584
+ if (options.populate !== void 0) try {
585
+ dependencyModelNames = await resolveRtsQueryDependencyModelNames({
586
+ tenantId: meta.tenantId,
587
+ ability,
588
+ modelName: message.modelName,
589
+ options,
590
+ allowInternalModels
591
+ });
592
+ } catch (err) {
593
+ const error = redactErrorMessage(err);
594
+ sendWs(ws, {
595
+ type: "query-payload",
596
+ modelName: message.modelName,
597
+ queryKey: message.queryKey,
598
+ error
599
+ });
600
+ return;
601
+ }
602
+ addSocketSubscription({
603
+ socketId,
604
+ tenantId: meta.tenantId,
605
+ userId: meta.userId,
606
+ modelName: message.modelName,
607
+ queryKey: message.queryKey,
608
+ query: message.query,
609
+ options,
610
+ dependencyModelNames
611
+ });
612
+ try {
613
+ const modelNamesToWatch = new Set([message.modelName, ...dependencyModelNames]);
614
+ for (const modelName of modelNamesToWatch) await ensureChangeStream(meta.tenantId, modelName);
615
+ } catch (err) {
616
+ const error = redactErrorMessage(err);
617
+ sendWs(ws, {
618
+ type: "query-payload",
619
+ modelName: message.modelName,
620
+ queryKey: message.queryKey,
621
+ error
622
+ });
623
+ return;
624
+ }
625
+ if (message.runInitialQuery === false) return;
436
626
  }
437
- let dependencyModelNames = [];
438
- if (options.populate !== void 0) try {
439
- dependencyModelNames = await resolveRtsQueryDependencyModelNames({
627
+ try {
628
+ await runAndSendQuery({
440
629
  tenantId: meta.tenantId,
630
+ targetSocketIds: [socketId],
441
631
  ability,
442
632
  modelName: message.modelName,
443
- options,
444
- allowInternalModels
633
+ queryKey: message.queryKey,
634
+ query: message.query,
635
+ options
445
636
  });
446
637
  } catch (err) {
447
638
  const error = redactErrorMessage(err);
@@ -451,51 +642,60 @@ var handleClientMessage = async ({ socketId, meta, message }) => {
451
642
  queryKey: message.queryKey,
452
643
  error
453
644
  });
454
- return;
455
645
  }
456
- addSocketSubscription({
457
- socketId,
458
- tenantId: meta.tenantId,
459
- userId: meta.userId,
460
- modelName: message.modelName,
461
- queryKey: message.queryKey,
462
- query: message.query,
463
- options,
464
- dependencyModelNames
465
- });
646
+ return;
647
+ }
648
+ if (message.type === "register-count" || message.type === "run-count") {
649
+ if (message.type === "register-count") {
650
+ if (!(socketCountSubscriptions.get(socketId)?.get(message.modelName)?.has(message.queryKey) ?? false) && getSocketSubscriptionCount(socketId) >= maxSubscriptionsPerSocket) {
651
+ sendWs(ws, {
652
+ type: "count-payload",
653
+ modelName: message.modelName,
654
+ queryKey: message.queryKey,
655
+ error: "Too many subscriptions"
656
+ });
657
+ return;
658
+ }
659
+ addSocketCountSubscription({
660
+ socketId,
661
+ tenantId: meta.tenantId,
662
+ userId: meta.userId,
663
+ modelName: message.modelName,
664
+ queryKey: message.queryKey,
665
+ query: message.query
666
+ });
667
+ try {
668
+ await ensureChangeStream(meta.tenantId, message.modelName);
669
+ } catch (err) {
670
+ const error = redactErrorMessage(err);
671
+ sendWs(ws, {
672
+ type: "count-payload",
673
+ modelName: message.modelName,
674
+ queryKey: message.queryKey,
675
+ error
676
+ });
677
+ return;
678
+ }
679
+ if (message.runInitialQuery === false) return;
680
+ }
466
681
  try {
467
- const modelNamesToWatch = new Set([message.modelName, ...dependencyModelNames]);
468
- for (const modelName of modelNamesToWatch) await ensureChangeStream(meta.tenantId, modelName);
682
+ await runAndSendCount({
683
+ tenantId: meta.tenantId,
684
+ targetSocketIds: [socketId],
685
+ ability,
686
+ modelName: message.modelName,
687
+ queryKey: message.queryKey,
688
+ query: message.query
689
+ });
469
690
  } catch (err) {
470
691
  const error = redactErrorMessage(err);
471
692
  sendWs(ws, {
472
- type: "query-payload",
693
+ type: "count-payload",
473
694
  modelName: message.modelName,
474
695
  queryKey: message.queryKey,
475
696
  error
476
697
  });
477
- return;
478
698
  }
479
- if (message.runInitialQuery === false) return;
480
- }
481
- try {
482
- await runAndSendQuery({
483
- tenantId: meta.tenantId,
484
- targetSocketIds: [socketId],
485
- ability,
486
- modelName: message.modelName,
487
- queryKey: message.queryKey,
488
- query: message.query,
489
- options
490
- });
491
- } catch (err) {
492
- const error = redactErrorMessage(err);
493
- sendWs(ws, {
494
- type: "query-payload",
495
- modelName: message.modelName,
496
- queryKey: message.queryKey,
497
- error
498
- });
499
699
  }
500
700
  };
501
701
  var initRts = ({ server, path = "/rts", sessionMiddleware, maxPayloadBytes: maxPayloadBytesArg, maxSubscriptionsPerSocket: maxSubscriptionsPerSocketArg, dispatchDebounceMs: dispatchDebounceMsArg, allowInternalModels: allowInternalModelsArg }) => {
@@ -581,7 +781,7 @@ var initRts = ({ server, path = "/rts", sessionMiddleware, maxPayloadBytes: maxP
581
781
  }
582
782
  if (!parsed || typeof parsed !== "object") return;
583
783
  const message = parsed;
584
- if (message.type !== "event" && message.type !== "run-query" && message.type !== "register-query" && message.type !== "remove-query") return;
784
+ if (message.type !== "event" && message.type !== "run-query" && message.type !== "register-query" && message.type !== "remove-query" && message.type !== "run-count" && message.type !== "register-count" && message.type !== "remove-count") return;
585
785
  handleClientMessage({
586
786
  socketId,
587
787
  meta,