@rpcbase/server 0.516.0 → 0.518.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.
- package/dist/{handler-3gksYOQv.js → handler-0rPClEv4.js} +36 -11
- package/dist/handler-0rPClEv4.js.map +1 -0
- package/dist/{handler-GZgk5k3c.js → handler-3uwH4f67.js} +103 -59
- package/dist/handler-3uwH4f67.js.map +1 -0
- package/dist/{handler-DY5UEwlw.js → handler-DnSJAQ_B.js} +2 -2
- package/dist/{handler-DY5UEwlw.js.map → handler-DnSJAQ_B.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/notifications/api/notifications/handler.d.ts.map +1 -1
- package/dist/notifications/createNotification.d.ts +2 -1
- package/dist/notifications/createNotification.d.ts.map +1 -1
- package/dist/notifications/digest.d.ts +3 -1
- package/dist/notifications/digest.d.ts.map +1 -1
- package/dist/notifications.js +13 -5
- package/dist/notifications.js.map +1 -1
- package/dist/{queryExecutor-Dn62mIDL.js → queryExecutor-B7lb2FR1.js} +7 -6
- package/dist/queryExecutor-B7lb2FR1.js.map +1 -0
- package/dist/rts/index.js +2 -2
- package/dist/rts/index.js.map +1 -1
- package/dist/rts/queryExecutor.d.ts.map +1 -1
- package/dist/{shared-Dy9x-P7F.js → shared-BqZiSOmf.js} +4 -3
- package/dist/shared-BqZiSOmf.js.map +1 -0
- package/dist/uploads/api/file-uploads/handlers/completeUpload.d.ts.map +1 -1
- package/dist/uploads/api/file-uploads/handlers/initUpload.d.ts.map +1 -1
- package/dist/uploads/api/file-uploads/postProcessors.d.ts +30 -0
- package/dist/uploads/api/file-uploads/postProcessors.d.ts.map +1 -0
- package/dist/uploads/api/file-uploads/shared.d.ts +1 -1
- package/dist/uploads/api/file-uploads/shared.d.ts.map +1 -1
- package/dist/uploads.d.ts +1 -0
- package/dist/uploads.d.ts.map +1 -1
- package/dist/uploads.js +82 -2
- package/dist/uploads.js.map +1 -1
- package/package.json +1 -1
- package/dist/handler-3gksYOQv.js.map +0 -1
- package/dist/handler-GZgk5k3c.js.map +0 -1
- package/dist/queryExecutor-Dn62mIDL.js.map +0 -1
- package/dist/shared-Dy9x-P7F.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler-DY5UEwlw.js","sources":["../src/uploads/api/files/handlers/deleteFile.ts","../src/uploads/api/files/handlers/getFile.ts","../src/uploads/api/files/index.ts","../src/uploads/api/files/handler.ts"],"sourcesContent":["import { ApiHandler } from \"@rpcbase/api\"\nimport { getTenantFilesystemDb } from \"@rpcbase/db\"\nimport { GridFSBucket, ObjectId } from \"mongodb\"\n\nimport { getBucketName, getTenantId, getUploadKeyHash, getUserId, type SessionUser } from \"../../file-uploads/shared\"\n\n\ntype DeleteResponsePayload = {\n ok: boolean\n error?: string\n}\n\nconst resolveHeaderString = (value: unknown): string | null => {\n if (typeof value !== \"string\") return null\n const normalized = value.trim()\n return normalized ? normalized : null\n}\n\nexport const deleteFile: ApiHandler<Record<string, never>, DeleteResponsePayload, SessionUser> = async (\n _payload,\n ctx,\n): Promise<DeleteResponsePayload> => {\n const tenantId = getTenantId(ctx)\n if (!tenantId) {\n ctx.res.status(400)\n return { ok: false, error: \"tenant_missing\" }\n }\n\n const fileIdRaw = String(ctx.req.params?.fileId ?? \"\").trim()\n let fileObjectId: ObjectId\n try {\n fileObjectId = new ObjectId(fileIdRaw)\n } catch {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_file_id\" }\n }\n\n const fsDb = await getTenantFilesystemDb(tenantId)\n const nativeDb = fsDb.db\n if (!nativeDb) {\n ctx.res.status(500)\n return { ok: false, error: \"filesystem_db_unavailable\" }\n }\n\n const bucketName = getBucketName()\n const bucket = new GridFSBucket(nativeDb, { bucketName })\n\n const userId = getUserId(ctx)\n const uploadKeyHash = getUploadKeyHash(ctx)\n if (!userId && !uploadKeyHash) {\n ctx.res.status(401)\n return { ok: false, error: \"unauthorized\" }\n }\n\n const [file] = await bucket.find({ _id: fileObjectId }).limit(1).toArray()\n if (!file) {\n ctx.res.status(204)\n return { ok: true }\n }\n\n const metadataUserId = resolveHeaderString((file as any)?.metadata?.userId)\n const ownerKeyHash = resolveHeaderString((file as any)?.metadata?.ownerKeyHash)\n\n const authorizedByUser = Boolean(userId && metadataUserId && userId === metadataUserId)\n const authorizedByKey = Boolean(uploadKeyHash && ownerKeyHash && uploadKeyHash === ownerKeyHash)\n\n if (!authorizedByUser && !authorizedByKey) {\n ctx.res.status(401)\n return { ok: false, error: \"unauthorized\" }\n }\n\n try {\n await bucket.delete(fileObjectId)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n if (!message.includes(\"FileNotFound\")) {\n ctx.res.status(500)\n return { ok: false, error: \"delete_failed\" }\n }\n }\n\n ctx.res.status(204)\n return { ok: true }\n}\n","import { ApiHandler } from \"@rpcbase/api\"\nimport { getTenantFilesystemDb } from \"@rpcbase/db\"\nimport { GridFSBucket, ObjectId } from \"mongodb\"\n\nimport { getBucketName, getTenantId, getUploadKeyHash, getUserId, type SessionUser } from \"../../file-uploads/shared\"\n\n\nconst resolveHeaderString = (value: unknown): string | null => {\n if (typeof value !== \"string\") return null\n const normalized = value.trim()\n return normalized ? normalized : null\n}\n\nconst resolveHeaderBoolean = (value: unknown): boolean | null => {\n if (typeof value === \"boolean\") return value\n if (typeof value !== \"string\") return null\n const normalized = value.trim().toLowerCase()\n if (!normalized) return null\n if (normalized === \"true\") return true\n if (normalized === \"false\") return false\n return null\n}\n\nconst escapeHeaderFilename = (filename: string): string => filename.replace(/[\\\\\"]/g, \"_\")\n\nexport const getFile: ApiHandler<Record<string, never>, Record<string, never>, SessionUser> = async (\n _payload,\n ctx,\n): Promise<Record<string, never>> => {\n const tenantId = getTenantId(ctx)\n if (!tenantId) {\n ctx.res.status(400).end()\n return {}\n }\n\n const fileIdRaw = String(ctx.req.params?.fileId ?? \"\").trim()\n let fileObjectId: ObjectId\n try {\n fileObjectId = new ObjectId(fileIdRaw)\n } catch {\n ctx.res.status(400).end()\n return {}\n }\n\n const fsDb = await getTenantFilesystemDb(tenantId)\n const nativeDb = fsDb.db\n if (!nativeDb) {\n ctx.res.status(500).end()\n return {}\n }\n\n const bucketName = getBucketName()\n const bucket = new GridFSBucket(nativeDb, { bucketName })\n\n const [file] = await bucket.find({ _id: fileObjectId }).limit(1).toArray()\n if (!file) {\n ctx.res.status(404).end()\n return {}\n }\n\n const isPublic = resolveHeaderBoolean((file as any)?.metadata?.isPublic) ?? false\n if (!isPublic) {\n const userId = getUserId(ctx)\n const uploadKeyHash = getUploadKeyHash(ctx)\n const metadataUserId = resolveHeaderString((file as any)?.metadata?.userId)\n const ownerKeyHash = resolveHeaderString((file as any)?.metadata?.ownerKeyHash)\n\n const authorizedByUser = Boolean(userId && metadataUserId && userId === metadataUserId)\n const authorizedByKey = Boolean(uploadKeyHash && ownerKeyHash && uploadKeyHash === ownerKeyHash)\n\n if (!authorizedByUser && !authorizedByKey) {\n ctx.res.status(401).end()\n return {}\n }\n }\n\n const mimeTypeFromMetadata = resolveHeaderString((file as any)?.metadata?.mimeType)\n const mimeType = mimeTypeFromMetadata ?? \"application/octet-stream\"\n const filenameFromDb = resolveHeaderString((file as any)?.filename)\n const filename = filenameFromDb ?? fileIdRaw\n const filenameSafe = escapeHeaderFilename(filename)\n\n const cacheControl = \"private, max-age=0, must-revalidate\"\n const md5 = resolveHeaderString((file as any)?.md5)\n const uploadDate = (file as any)?.uploadDate instanceof Date ? (file as any).uploadDate : null\n const etagValue = md5 ?? `${fileObjectId.toHexString()}-${String((file as any)?.length ?? 0)}-${String(uploadDate?.getTime() ?? 0)}`\n const etag = md5 ? `\"${etagValue}\"` : `W/\"${etagValue}\"`\n const ifNoneMatch = resolveHeaderString((ctx.req.headers as any)?.[\"if-none-match\"])\n if (ifNoneMatch) {\n const candidates = ifNoneMatch.split(\",\").map((value) => value.trim()).filter(Boolean)\n if (candidates.includes(\"*\") || candidates.includes(etag)) {\n ctx.res.status(304)\n ctx.res.setHeader(\"Cache-Control\", cacheControl)\n ctx.res.setHeader(\"ETag\", etag)\n ctx.res.end()\n return {}\n }\n }\n\n ctx.res.status(200)\n ctx.res.setHeader(\"Content-Type\", mimeType)\n ctx.res.setHeader(\"Content-Length\", String((file as any)?.length ?? 0))\n ctx.res.setHeader(\"Content-Disposition\", `inline; filename=\"${filenameSafe}\"`)\n ctx.res.setHeader(\"Cache-Control\", cacheControl)\n ctx.res.setHeader(\"ETag\", etag)\n ctx.res.setHeader(\"X-Content-Type-Options\", \"nosniff\")\n if (mimeType === \"image/svg+xml\") {\n ctx.res.setHeader(\n \"Content-Security-Policy\",\n \"default-src 'none'; style-src 'unsafe-inline'; sandbox; base-uri 'none'; form-action 'none'\",\n )\n }\n ctx.res.flushHeaders()\n\n if (ctx.req.method === \"HEAD\") {\n ctx.res.end()\n return {}\n }\n\n const stream = bucket.openDownloadStream(fileObjectId)\n\n stream.on(\"error\", () => {\n try {\n ctx.res.destroy()\n } catch {\n //\n }\n })\n\n stream.pipe(ctx.res)\n return {}\n}\n","export const Route = \"/api/rb/files/:fileId\"\n\nexport const GetRoute = Route\nexport const DeleteRoute = Route\n","import { Api } from \"@rpcbase/api\"\n\nimport { deleteFile } from \"./handlers/deleteFile\"\nimport { getFile } from \"./handlers/getFile\"\n\nimport * as Files from \"./index\"\n\n\nexport default (api: Api) => {\n api.get(Files.GetRoute, getFile)\n api.delete(Files.DeleteRoute, deleteFile)\n}\n"],"names":["resolveHeaderString","value","normalized","trim","deleteFile","_payload","ctx","tenantId","getTenantId","res","status","ok","error","fileIdRaw","String","req","params","fileId","fileObjectId","ObjectId","fsDb","getTenantFilesystemDb","nativeDb","db","bucketName","getBucketName","bucket","GridFSBucket","userId","getUserId","uploadKeyHash","getUploadKeyHash","file","find","_id","limit","toArray","metadataUserId","metadata","ownerKeyHash","authorizedByUser","Boolean","authorizedByKey","delete","message","Error","includes","resolveHeaderBoolean","toLowerCase","escapeHeaderFilename","filename","replace","getFile","end","isPublic","mimeTypeFromMetadata","mimeType","filenameFromDb","filenameSafe","cacheControl","md5","uploadDate","Date","etagValue","toHexString","length","getTime","etag","ifNoneMatch","headers","candidates","split","map","filter","setHeader","flushHeaders","method","stream","openDownloadStream","on","destroy","pipe","Route","GetRoute","DeleteRoute","api","get","Files"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"handler-DnSJAQ_B.js","sources":["../src/uploads/api/files/handlers/deleteFile.ts","../src/uploads/api/files/handlers/getFile.ts","../src/uploads/api/files/index.ts","../src/uploads/api/files/handler.ts"],"sourcesContent":["import { ApiHandler } from \"@rpcbase/api\"\nimport { getTenantFilesystemDb } from \"@rpcbase/db\"\nimport { GridFSBucket, ObjectId } from \"mongodb\"\n\nimport { getBucketName, getTenantId, getUploadKeyHash, getUserId, type SessionUser } from \"../../file-uploads/shared\"\n\n\ntype DeleteResponsePayload = {\n ok: boolean\n error?: string\n}\n\nconst resolveHeaderString = (value: unknown): string | null => {\n if (typeof value !== \"string\") return null\n const normalized = value.trim()\n return normalized ? normalized : null\n}\n\nexport const deleteFile: ApiHandler<Record<string, never>, DeleteResponsePayload, SessionUser> = async (\n _payload,\n ctx,\n): Promise<DeleteResponsePayload> => {\n const tenantId = getTenantId(ctx)\n if (!tenantId) {\n ctx.res.status(400)\n return { ok: false, error: \"tenant_missing\" }\n }\n\n const fileIdRaw = String(ctx.req.params?.fileId ?? \"\").trim()\n let fileObjectId: ObjectId\n try {\n fileObjectId = new ObjectId(fileIdRaw)\n } catch {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_file_id\" }\n }\n\n const fsDb = await getTenantFilesystemDb(tenantId)\n const nativeDb = fsDb.db\n if (!nativeDb) {\n ctx.res.status(500)\n return { ok: false, error: \"filesystem_db_unavailable\" }\n }\n\n const bucketName = getBucketName()\n const bucket = new GridFSBucket(nativeDb, { bucketName })\n\n const userId = getUserId(ctx)\n const uploadKeyHash = getUploadKeyHash(ctx)\n if (!userId && !uploadKeyHash) {\n ctx.res.status(401)\n return { ok: false, error: \"unauthorized\" }\n }\n\n const [file] = await bucket.find({ _id: fileObjectId }).limit(1).toArray()\n if (!file) {\n ctx.res.status(204)\n return { ok: true }\n }\n\n const metadataUserId = resolveHeaderString((file as any)?.metadata?.userId)\n const ownerKeyHash = resolveHeaderString((file as any)?.metadata?.ownerKeyHash)\n\n const authorizedByUser = Boolean(userId && metadataUserId && userId === metadataUserId)\n const authorizedByKey = Boolean(uploadKeyHash && ownerKeyHash && uploadKeyHash === ownerKeyHash)\n\n if (!authorizedByUser && !authorizedByKey) {\n ctx.res.status(401)\n return { ok: false, error: \"unauthorized\" }\n }\n\n try {\n await bucket.delete(fileObjectId)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n if (!message.includes(\"FileNotFound\")) {\n ctx.res.status(500)\n return { ok: false, error: \"delete_failed\" }\n }\n }\n\n ctx.res.status(204)\n return { ok: true }\n}\n","import { ApiHandler } from \"@rpcbase/api\"\nimport { getTenantFilesystemDb } from \"@rpcbase/db\"\nimport { GridFSBucket, ObjectId } from \"mongodb\"\n\nimport { getBucketName, getTenantId, getUploadKeyHash, getUserId, type SessionUser } from \"../../file-uploads/shared\"\n\n\nconst resolveHeaderString = (value: unknown): string | null => {\n if (typeof value !== \"string\") return null\n const normalized = value.trim()\n return normalized ? normalized : null\n}\n\nconst resolveHeaderBoolean = (value: unknown): boolean | null => {\n if (typeof value === \"boolean\") return value\n if (typeof value !== \"string\") return null\n const normalized = value.trim().toLowerCase()\n if (!normalized) return null\n if (normalized === \"true\") return true\n if (normalized === \"false\") return false\n return null\n}\n\nconst escapeHeaderFilename = (filename: string): string => filename.replace(/[\\\\\"]/g, \"_\")\n\nexport const getFile: ApiHandler<Record<string, never>, Record<string, never>, SessionUser> = async (\n _payload,\n ctx,\n): Promise<Record<string, never>> => {\n const tenantId = getTenantId(ctx)\n if (!tenantId) {\n ctx.res.status(400).end()\n return {}\n }\n\n const fileIdRaw = String(ctx.req.params?.fileId ?? \"\").trim()\n let fileObjectId: ObjectId\n try {\n fileObjectId = new ObjectId(fileIdRaw)\n } catch {\n ctx.res.status(400).end()\n return {}\n }\n\n const fsDb = await getTenantFilesystemDb(tenantId)\n const nativeDb = fsDb.db\n if (!nativeDb) {\n ctx.res.status(500).end()\n return {}\n }\n\n const bucketName = getBucketName()\n const bucket = new GridFSBucket(nativeDb, { bucketName })\n\n const [file] = await bucket.find({ _id: fileObjectId }).limit(1).toArray()\n if (!file) {\n ctx.res.status(404).end()\n return {}\n }\n\n const isPublic = resolveHeaderBoolean((file as any)?.metadata?.isPublic) ?? false\n if (!isPublic) {\n const userId = getUserId(ctx)\n const uploadKeyHash = getUploadKeyHash(ctx)\n const metadataUserId = resolveHeaderString((file as any)?.metadata?.userId)\n const ownerKeyHash = resolveHeaderString((file as any)?.metadata?.ownerKeyHash)\n\n const authorizedByUser = Boolean(userId && metadataUserId && userId === metadataUserId)\n const authorizedByKey = Boolean(uploadKeyHash && ownerKeyHash && uploadKeyHash === ownerKeyHash)\n\n if (!authorizedByUser && !authorizedByKey) {\n ctx.res.status(401).end()\n return {}\n }\n }\n\n const mimeTypeFromMetadata = resolveHeaderString((file as any)?.metadata?.mimeType)\n const mimeType = mimeTypeFromMetadata ?? \"application/octet-stream\"\n const filenameFromDb = resolveHeaderString((file as any)?.filename)\n const filename = filenameFromDb ?? fileIdRaw\n const filenameSafe = escapeHeaderFilename(filename)\n\n const cacheControl = \"private, max-age=0, must-revalidate\"\n const md5 = resolveHeaderString((file as any)?.md5)\n const uploadDate = (file as any)?.uploadDate instanceof Date ? (file as any).uploadDate : null\n const etagValue = md5 ?? `${fileObjectId.toHexString()}-${String((file as any)?.length ?? 0)}-${String(uploadDate?.getTime() ?? 0)}`\n const etag = md5 ? `\"${etagValue}\"` : `W/\"${etagValue}\"`\n const ifNoneMatch = resolveHeaderString((ctx.req.headers as any)?.[\"if-none-match\"])\n if (ifNoneMatch) {\n const candidates = ifNoneMatch.split(\",\").map((value) => value.trim()).filter(Boolean)\n if (candidates.includes(\"*\") || candidates.includes(etag)) {\n ctx.res.status(304)\n ctx.res.setHeader(\"Cache-Control\", cacheControl)\n ctx.res.setHeader(\"ETag\", etag)\n ctx.res.end()\n return {}\n }\n }\n\n ctx.res.status(200)\n ctx.res.setHeader(\"Content-Type\", mimeType)\n ctx.res.setHeader(\"Content-Length\", String((file as any)?.length ?? 0))\n ctx.res.setHeader(\"Content-Disposition\", `inline; filename=\"${filenameSafe}\"`)\n ctx.res.setHeader(\"Cache-Control\", cacheControl)\n ctx.res.setHeader(\"ETag\", etag)\n ctx.res.setHeader(\"X-Content-Type-Options\", \"nosniff\")\n if (mimeType === \"image/svg+xml\") {\n ctx.res.setHeader(\n \"Content-Security-Policy\",\n \"default-src 'none'; style-src 'unsafe-inline'; sandbox; base-uri 'none'; form-action 'none'\",\n )\n }\n ctx.res.flushHeaders()\n\n if (ctx.req.method === \"HEAD\") {\n ctx.res.end()\n return {}\n }\n\n const stream = bucket.openDownloadStream(fileObjectId)\n\n stream.on(\"error\", () => {\n try {\n ctx.res.destroy()\n } catch {\n //\n }\n })\n\n stream.pipe(ctx.res)\n return {}\n}\n","export const Route = \"/api/rb/files/:fileId\"\n\nexport const GetRoute = Route\nexport const DeleteRoute = Route\n","import { Api } from \"@rpcbase/api\"\n\nimport { deleteFile } from \"./handlers/deleteFile\"\nimport { getFile } from \"./handlers/getFile\"\n\nimport * as Files from \"./index\"\n\n\nexport default (api: Api) => {\n api.get(Files.GetRoute, getFile)\n api.delete(Files.DeleteRoute, deleteFile)\n}\n"],"names":["resolveHeaderString","value","normalized","trim","deleteFile","_payload","ctx","tenantId","getTenantId","res","status","ok","error","fileIdRaw","String","req","params","fileId","fileObjectId","ObjectId","fsDb","getTenantFilesystemDb","nativeDb","db","bucketName","getBucketName","bucket","GridFSBucket","userId","getUserId","uploadKeyHash","getUploadKeyHash","file","find","_id","limit","toArray","metadataUserId","metadata","ownerKeyHash","authorizedByUser","Boolean","authorizedByKey","delete","message","Error","includes","resolveHeaderBoolean","toLowerCase","escapeHeaderFilename","filename","replace","getFile","end","isPublic","mimeTypeFromMetadata","mimeType","filenameFromDb","filenameSafe","cacheControl","md5","uploadDate","Date","etagValue","toHexString","length","getTime","etag","ifNoneMatch","headers","candidates","split","map","filter","setHeader","flushHeaders","method","stream","openDownloadStream","on","destroy","pipe","Route","GetRoute","DeleteRoute","api","get","Files"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { dirname, posix, sep } from "path";
|
|
|
9
9
|
import fs, { createReadStream, readFileSync } from "node:fs";
|
|
10
10
|
import { createInterface } from "node:readline";
|
|
11
11
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
12
|
-
import { g as getDerivedKey, r as resolveRtsRequestTenantId, i as isRtsRequestAuthorized, b as buildRtsAbilityFromRequest, n as normalizeRtsQueryOptions, a as runRtsQuery } from "./queryExecutor-
|
|
12
|
+
import { g as getDerivedKey, r as resolveRtsRequestTenantId, i as isRtsRequestAuthorized, b as buildRtsAbilityFromRequest, n as normalizeRtsQueryOptions, a as runRtsQuery } from "./queryExecutor-B7lb2FR1.js";
|
|
13
13
|
import httpProxy from "http-proxy-3";
|
|
14
14
|
import fsPromises from "node:fs/promises";
|
|
15
15
|
import inspector from "node:inspector";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../../src/notifications/api/notifications/handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAmB,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../../src/notifications/api/notifications/handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAmB,MAAM,cAAc,CAAA;yBAwbnC,KAAK,GAAG;AAAxB,wBASC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Ctx } from '@rpcbase/api';
|
|
2
|
+
import { AppAbility } from '@rpcbase/db/acl';
|
|
2
3
|
export type CreateNotificationInput = {
|
|
3
4
|
userId: string;
|
|
4
5
|
topic?: string;
|
|
@@ -7,7 +8,7 @@ export type CreateNotificationInput = {
|
|
|
7
8
|
url?: string;
|
|
8
9
|
metadata?: Record<string, unknown>;
|
|
9
10
|
};
|
|
10
|
-
export declare const createNotification: (ctx: Ctx, input: CreateNotificationInput) => Promise<{
|
|
11
|
+
export declare const createNotification: (ctx: Ctx, input: CreateNotificationInput, ability: AppAbility) => Promise<{
|
|
11
12
|
id: string;
|
|
12
13
|
}>;
|
|
13
14
|
//# sourceMappingURL=createNotification.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createNotification.d.ts","sourceRoot":"","sources":["../../src/notifications/createNotification.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"createNotification.d.ts","sourceRoot":"","sources":["../../src/notifications/createNotification.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAEvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAGjD,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC,CAAA;AAED,eAAO,MAAM,kBAAkB,GAC7B,KAAK,GAAG,EACR,OAAO,uBAAuB,EAC9B,SAAS,UAAU,KAClB,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAyBxB,CAAA"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Ctx } from '@rpcbase/api';
|
|
2
|
-
|
|
2
|
+
import { AppAbility } from '@rpcbase/db/acl';
|
|
3
|
+
export declare const sendNotificationsDigestForUser: (ctx: Ctx, { userId, ability, force, }: {
|
|
3
4
|
userId: string;
|
|
5
|
+
ability: AppAbility;
|
|
4
6
|
force?: boolean;
|
|
5
7
|
}) => Promise<{
|
|
6
8
|
ok: true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../src/notifications/digest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../src/notifications/digest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAEvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAsCjD,eAAO,MAAM,8BAA8B,GACzC,KAAK,GAAG,EACR,6BAIG;IACD,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,UAAU,CAAA;IACnB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,KACA,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CA2F5F,CAAA"}
|
package/dist/notifications.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { models } from "@rpcbase/db";
|
|
2
2
|
import { s as sendEmail } from "./email-DK8uUU4X.js";
|
|
3
3
|
const routes = Object.entries({
|
|
4
|
-
.../* @__PURE__ */ Object.assign({ "./api/notifications/handler.ts": () => import("./handler-
|
|
4
|
+
.../* @__PURE__ */ Object.assign({ "./api/notifications/handler.ts": () => import("./handler-0rPClEv4.js") })
|
|
5
5
|
}).reduce((acc, [path, mod]) => {
|
|
6
6
|
acc[path.replace("./api/", "@rpcbase/server/notifications/api/")] = mod;
|
|
7
7
|
return acc;
|
|
8
8
|
}, {});
|
|
9
|
-
const createNotification = async (ctx, input) => {
|
|
9
|
+
const createNotification = async (ctx, input, ability) => {
|
|
10
10
|
const userId = input.userId.trim();
|
|
11
11
|
const title = input.title.trim();
|
|
12
12
|
if (!userId) {
|
|
@@ -18,7 +18,10 @@ const createNotification = async (ctx, input) => {
|
|
|
18
18
|
const topic = typeof input.topic === "string" ? input.topic.trim() : "";
|
|
19
19
|
const body = typeof input.body === "string" ? input.body.trim() : "";
|
|
20
20
|
const url = typeof input.url === "string" ? input.url.trim() : "";
|
|
21
|
-
const NotificationModel = await models.get("RBNotification",
|
|
21
|
+
const NotificationModel = await models.get("RBNotification", {
|
|
22
|
+
req: ctx.req,
|
|
23
|
+
ability
|
|
24
|
+
});
|
|
22
25
|
const doc = await NotificationModel.create({
|
|
23
26
|
userId,
|
|
24
27
|
...topic ? {
|
|
@@ -67,10 +70,15 @@ const buildPreferencesByTopic = (settings) => {
|
|
|
67
70
|
};
|
|
68
71
|
const sendNotificationsDigestForUser = async (ctx, {
|
|
69
72
|
userId,
|
|
73
|
+
ability,
|
|
70
74
|
force = false
|
|
71
75
|
}) => {
|
|
72
|
-
const
|
|
73
|
-
|
|
76
|
+
const modelCtx = {
|
|
77
|
+
req: ctx.req,
|
|
78
|
+
ability
|
|
79
|
+
};
|
|
80
|
+
const SettingsModel = await models.get("RBNotificationSettings", modelCtx);
|
|
81
|
+
const NotificationModel = await models.get("RBNotification", modelCtx);
|
|
74
82
|
const settings = await SettingsModel.findOne({
|
|
75
83
|
userId
|
|
76
84
|
}).lean();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications.js","sources":["../src/notifications/routes.ts","../src/notifications/createNotification.ts","../src/notifications/digest.ts"],"sourcesContent":["export const routes = Object.entries({\n ...import.meta.glob(\"./api/**/handler.ts\"),\n}).reduce<Record<string, unknown>>((acc, [path, mod]) => {\n acc[path.replace(\"./api/\", \"@rpcbase/server/notifications/api/\")] = mod\n return acc\n}, {})\n\n","import type { Ctx } from \"@rpcbase/api\"\nimport { models } from \"@rpcbase/db\"\n\n\nexport type CreateNotificationInput = {\n userId: string\n topic?: string\n title: string\n body?: string\n url?: string\n metadata?: Record<string, unknown>\n}\n\nexport const createNotification = async (\n ctx: Ctx,\n input: CreateNotificationInput,\n): Promise<{ id: string }> => {\n const userId = input.userId.trim()\n const title = input.title.trim()\n if (!userId) {\n throw new Error(\"createNotification: userId is required\")\n }\n if (!title) {\n throw new Error(\"createNotification: title is required\")\n }\n\n const topic = typeof input.topic === \"string\" ? input.topic.trim() : \"\"\n const body = typeof input.body === \"string\" ? input.body.trim() : \"\"\n const url = typeof input.url === \"string\" ? input.url.trim() : \"\"\n\n const NotificationModel = await models.get(\"RBNotification\", ctx)\n const doc = await NotificationModel.create({\n userId,\n ...(topic ? { topic } : {}),\n title,\n ...(body ? { body } : {}),\n ...(url ? { url } : {}),\n ...(input.metadata ? { metadata: input.metadata } : {}),\n })\n\n return { id: doc._id.toString() }\n}\n","import type { Ctx } from \"@rpcbase/api\"\nimport { models, type IRBNotification, type IRBNotificationSettings } from \"@rpcbase/db\"\n\nimport { sendEmail } from \"../email\"\n\n\nconst DAY_MS = 24 * 60 * 60 * 1000\nconst WEEK_MS = 7 * DAY_MS\n\ntype DigestFrequency = \"off\" | \"daily\" | \"weekly\"\n\ntype NotificationDoc = IRBNotification & { _id: unknown }\ntype SettingsDoc = IRBNotificationSettings & { _id: unknown }\n\nconst getDigestWindowMs = (frequency: DigestFrequency): number => {\n if (frequency === \"daily\") return DAY_MS\n if (frequency === \"weekly\") return WEEK_MS\n return 0\n}\n\nconst formatIso = (value: unknown): string | undefined => {\n if (!(value instanceof Date)) return undefined\n return value.toISOString()\n}\n\nconst buildPreferencesByTopic = (settings: SettingsDoc | null): Record<string, { emailDigest: boolean }> => {\n const record: Record<string, { emailDigest: boolean }> = {}\n const raw = settings?.topicPreferences\n if (!Array.isArray(raw)) return record\n for (const pref of raw) {\n if (!pref || typeof pref !== \"object\") continue\n const topic = typeof (pref as { topic?: unknown }).topic === \"string\" ? (pref as { topic: string }).topic.trim() : \"\"\n if (!topic) continue\n const emailDigest = (pref as { emailDigest?: unknown }).emailDigest\n record[topic] = { emailDigest: emailDigest === true }\n }\n return record\n}\n\nexport const sendNotificationsDigestForUser = async (\n ctx: Ctx,\n {\n userId,\n force = false,\n }: {\n userId: string\n force?: boolean\n },\n): Promise<{ ok: true; sent: boolean; skippedReason?: string } | { ok: false; error: string }> => {\n const SettingsModel = await models.get(\"RBNotificationSettings\", ctx)\n const NotificationModel = await models.get(\"RBNotification\", ctx)\n\n const settings = (await SettingsModel.findOne({ userId }).lean()) as SettingsDoc | null\n\n const digestFrequencyRaw = typeof settings?.digestFrequency === \"string\" ? settings.digestFrequency : \"weekly\"\n const digestFrequency: DigestFrequency =\n digestFrequencyRaw === \"daily\" || digestFrequencyRaw === \"weekly\" || digestFrequencyRaw === \"off\"\n ? digestFrequencyRaw\n : \"weekly\"\n\n if (digestFrequency === \"off\") {\n return { ok: true, sent: false, skippedReason: \"digest_off\" }\n }\n\n const now = new Date()\n const windowMs = getDigestWindowMs(digestFrequency)\n const lastSentAt = settings?.lastDigestSentAt instanceof Date ? settings.lastDigestSentAt : null\n const since = lastSentAt ?? new Date(now.getTime() - windowMs)\n\n if (!force && lastSentAt && now.getTime() - lastSentAt.getTime() < windowMs) {\n return { ok: true, sent: false, skippedReason: \"not_due\" }\n }\n\n const preferencesByTopic = buildPreferencesByTopic(settings)\n const disabledTopics = Object.entries(preferencesByTopic)\n .filter(([, pref]) => pref.emailDigest === false)\n .map(([topic]) => topic)\n\n const query: Record<string, unknown> = {\n userId,\n archivedAt: { $exists: false },\n readAt: { $exists: false },\n createdAt: { $gt: since },\n }\n\n if (disabledTopics.length > 0) {\n query.topic = { $nin: disabledTopics }\n }\n\n const notifications = (await NotificationModel.find(query)\n .sort({ createdAt: -1 })\n .limit(50)\n .lean()) as NotificationDoc[]\n\n if (!notifications.length) {\n await SettingsModel.updateOne(\n { userId },\n { $set: { lastDigestSentAt: now }, $setOnInsert: { userId, digestFrequency: \"weekly\" } },\n { upsert: true },\n )\n return { ok: true, sent: false, skippedReason: \"empty\" }\n }\n\n const UserModel = await models.getGlobal(\"RBUser\", ctx)\n const user = (await UserModel.findById(userId, { email: 1 }).lean()) as { email?: unknown } | null\n const email = typeof user?.email === \"string\" ? user.email.trim() : \"\"\n if (!email) {\n return { ok: true, sent: false, skippedReason: \"missing_email\" }\n }\n\n const subject = \"Notifications digest\"\n const rows = notifications\n .map((n) => {\n const title = typeof n.title === \"string\" ? n.title : \"\"\n const body = typeof n.body === \"string\" ? n.body : \"\"\n const url = typeof n.url === \"string\" ? n.url : \"\"\n const createdAt = formatIso(n.createdAt) ?? \"\"\n const line = url\n ? `<li><strong>${title}</strong><br>${body}<br><a href=\"${url}\">${url}</a><br><small>${createdAt}</small></li>`\n : `<li><strong>${title}</strong><br>${body}<br><small>${createdAt}</small></li>`\n return line\n })\n .join(\"\")\n\n const html = `<div><p>Here is your notifications digest:</p><ul>${rows}</ul></div>`\n\n const emailResult = await sendEmail({ to: email, subject, html })\n if (emailResult.error) {\n return { ok: false, error: emailResult.error }\n }\n\n await SettingsModel.updateOne(\n { userId },\n { $set: { lastDigestSentAt: now }, $setOnInsert: { userId, digestFrequency: \"weekly\" } },\n { upsert: true },\n )\n\n return { ok: true, sent: emailResult.skipped !== true, ...(emailResult.skipped ? { skippedReason: \"email_skipped\" } : {}) }\n}\n"],"names":["routes","Object","entries","import","reduce","acc","path","mod","replace","createNotification","ctx","input","userId","trim","title","Error","topic","body","url","NotificationModel","models","get","doc","create","metadata","id","_id","toString","DAY_MS","WEEK_MS","getDigestWindowMs","frequency","formatIso","value","Date","undefined","toISOString","buildPreferencesByTopic","settings","record","raw","topicPreferences","Array","isArray","pref","emailDigest","sendNotificationsDigestForUser","force","SettingsModel","findOne","lean","digestFrequencyRaw","digestFrequency","ok","sent","skippedReason","now","windowMs","lastSentAt","lastDigestSentAt","since","getTime","preferencesByTopic","disabledTopics","filter","map","query","archivedAt","$exists","readAt","createdAt","$gt","length","$nin","notifications","find","sort","limit","updateOne","$set","$setOnInsert","upsert","UserModel","getGlobal","user","findById","email","subject","rows","n","line","join","html","emailResult","sendEmail","to","error","skipped"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"notifications.js","sources":["../src/notifications/routes.ts","../src/notifications/createNotification.ts","../src/notifications/digest.ts"],"sourcesContent":["export const routes = Object.entries({\n ...import.meta.glob(\"./api/**/handler.ts\"),\n}).reduce<Record<string, unknown>>((acc, [path, mod]) => {\n acc[path.replace(\"./api/\", \"@rpcbase/server/notifications/api/\")] = mod\n return acc\n}, {})\n\n","import type { Ctx } from \"@rpcbase/api\"\nimport { models } from \"@rpcbase/db\"\nimport type { AppAbility } from \"@rpcbase/db/acl\"\n\n\nexport type CreateNotificationInput = {\n userId: string\n topic?: string\n title: string\n body?: string\n url?: string\n metadata?: Record<string, unknown>\n}\n\nexport const createNotification = async (\n ctx: Ctx,\n input: CreateNotificationInput,\n ability: AppAbility,\n): Promise<{ id: string }> => {\n const userId = input.userId.trim()\n const title = input.title.trim()\n if (!userId) {\n throw new Error(\"createNotification: userId is required\")\n }\n if (!title) {\n throw new Error(\"createNotification: title is required\")\n }\n\n const topic = typeof input.topic === \"string\" ? input.topic.trim() : \"\"\n const body = typeof input.body === \"string\" ? input.body.trim() : \"\"\n const url = typeof input.url === \"string\" ? input.url.trim() : \"\"\n\n const NotificationModel = await models.get(\"RBNotification\", { req: ctx.req, ability })\n const doc = await NotificationModel.create({\n userId,\n ...(topic ? { topic } : {}),\n title,\n ...(body ? { body } : {}),\n ...(url ? { url } : {}),\n ...(input.metadata ? { metadata: input.metadata } : {}),\n })\n\n return { id: doc._id.toString() }\n}\n","import type { Ctx } from \"@rpcbase/api\"\nimport { models, type IRBNotification, type IRBNotificationSettings } from \"@rpcbase/db\"\nimport type { AppAbility } from \"@rpcbase/db/acl\"\n\nimport { sendEmail } from \"../email\"\n\n\nconst DAY_MS = 24 * 60 * 60 * 1000\nconst WEEK_MS = 7 * DAY_MS\n\ntype DigestFrequency = \"off\" | \"daily\" | \"weekly\"\n\ntype NotificationDoc = IRBNotification & { _id: unknown }\ntype SettingsDoc = IRBNotificationSettings & { _id: unknown }\n\nconst getDigestWindowMs = (frequency: DigestFrequency): number => {\n if (frequency === \"daily\") return DAY_MS\n if (frequency === \"weekly\") return WEEK_MS\n return 0\n}\n\nconst formatIso = (value: unknown): string | undefined => {\n if (!(value instanceof Date)) return undefined\n return value.toISOString()\n}\n\nconst buildPreferencesByTopic = (settings: SettingsDoc | null): Record<string, { emailDigest: boolean }> => {\n const record: Record<string, { emailDigest: boolean }> = {}\n const raw = settings?.topicPreferences\n if (!Array.isArray(raw)) return record\n for (const pref of raw) {\n if (!pref || typeof pref !== \"object\") continue\n const topic = typeof (pref as { topic?: unknown }).topic === \"string\" ? (pref as { topic: string }).topic.trim() : \"\"\n if (!topic) continue\n const emailDigest = (pref as { emailDigest?: unknown }).emailDigest\n record[topic] = { emailDigest: emailDigest === true }\n }\n return record\n}\n\nexport const sendNotificationsDigestForUser = async (\n ctx: Ctx,\n {\n userId,\n ability,\n force = false,\n }: {\n userId: string\n ability: AppAbility\n force?: boolean\n },\n): Promise<{ ok: true; sent: boolean; skippedReason?: string } | { ok: false; error: string }> => {\n const modelCtx = { req: ctx.req, ability }\n const SettingsModel = await models.get(\"RBNotificationSettings\", modelCtx)\n const NotificationModel = await models.get(\"RBNotification\", modelCtx)\n\n const settings = (await SettingsModel.findOne({ userId }).lean()) as SettingsDoc | null\n\n const digestFrequencyRaw = typeof settings?.digestFrequency === \"string\" ? settings.digestFrequency : \"weekly\"\n const digestFrequency: DigestFrequency =\n digestFrequencyRaw === \"daily\" || digestFrequencyRaw === \"weekly\" || digestFrequencyRaw === \"off\"\n ? digestFrequencyRaw\n : \"weekly\"\n\n if (digestFrequency === \"off\") {\n return { ok: true, sent: false, skippedReason: \"digest_off\" }\n }\n\n const now = new Date()\n const windowMs = getDigestWindowMs(digestFrequency)\n const lastSentAt = settings?.lastDigestSentAt instanceof Date ? settings.lastDigestSentAt : null\n const since = lastSentAt ?? new Date(now.getTime() - windowMs)\n\n if (!force && lastSentAt && now.getTime() - lastSentAt.getTime() < windowMs) {\n return { ok: true, sent: false, skippedReason: \"not_due\" }\n }\n\n const preferencesByTopic = buildPreferencesByTopic(settings)\n const disabledTopics = Object.entries(preferencesByTopic)\n .filter(([, pref]) => pref.emailDigest === false)\n .map(([topic]) => topic)\n\n const query: Record<string, unknown> = {\n userId,\n archivedAt: { $exists: false },\n readAt: { $exists: false },\n createdAt: { $gt: since },\n }\n\n if (disabledTopics.length > 0) {\n query.topic = { $nin: disabledTopics }\n }\n\n const notifications = (await NotificationModel.find(query)\n .sort({ createdAt: -1 })\n .limit(50)\n .lean()) as NotificationDoc[]\n\n if (!notifications.length) {\n await SettingsModel.updateOne(\n { userId },\n { $set: { lastDigestSentAt: now }, $setOnInsert: { userId, digestFrequency: \"weekly\" } },\n { upsert: true },\n )\n return { ok: true, sent: false, skippedReason: \"empty\" }\n }\n\n const UserModel = await models.getGlobal(\"RBUser\", ctx)\n const user = (await UserModel.findById(userId, { email: 1 }).lean()) as { email?: unknown } | null\n const email = typeof user?.email === \"string\" ? user.email.trim() : \"\"\n if (!email) {\n return { ok: true, sent: false, skippedReason: \"missing_email\" }\n }\n\n const subject = \"Notifications digest\"\n const rows = notifications\n .map((n) => {\n const title = typeof n.title === \"string\" ? n.title : \"\"\n const body = typeof n.body === \"string\" ? n.body : \"\"\n const url = typeof n.url === \"string\" ? n.url : \"\"\n const createdAt = formatIso(n.createdAt) ?? \"\"\n const line = url\n ? `<li><strong>${title}</strong><br>${body}<br><a href=\"${url}\">${url}</a><br><small>${createdAt}</small></li>`\n : `<li><strong>${title}</strong><br>${body}<br><small>${createdAt}</small></li>`\n return line\n })\n .join(\"\")\n\n const html = `<div><p>Here is your notifications digest:</p><ul>${rows}</ul></div>`\n\n const emailResult = await sendEmail({ to: email, subject, html })\n if (emailResult.error) {\n return { ok: false, error: emailResult.error }\n }\n\n await SettingsModel.updateOne(\n { userId },\n { $set: { lastDigestSentAt: now }, $setOnInsert: { userId, digestFrequency: \"weekly\" } },\n { upsert: true },\n )\n\n return { ok: true, sent: emailResult.skipped !== true, ...(emailResult.skipped ? { skippedReason: \"email_skipped\" } : {}) }\n}\n"],"names":["routes","Object","entries","import","reduce","acc","path","mod","replace","createNotification","ctx","input","ability","userId","trim","title","Error","topic","body","url","NotificationModel","models","get","req","doc","create","metadata","id","_id","toString","DAY_MS","WEEK_MS","getDigestWindowMs","frequency","formatIso","value","Date","undefined","toISOString","buildPreferencesByTopic","settings","record","raw","topicPreferences","Array","isArray","pref","emailDigest","sendNotificationsDigestForUser","force","modelCtx","SettingsModel","findOne","lean","digestFrequencyRaw","digestFrequency","ok","sent","skippedReason","now","windowMs","lastSentAt","lastDigestSentAt","since","getTime","preferencesByTopic","disabledTopics","filter","map","query","archivedAt","$exists","readAt","createdAt","$gt","length","$nin","notifications","find","sort","limit","updateOne","$set","$setOnInsert","upsert","UserModel","getGlobal","user","findById","email","subject","rows","n","line","join","html","emailResult","sendEmail","to","error","skipped"],"mappings":"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"}
|
|
@@ -100,7 +100,7 @@ const buildRtsAbilityFromRequest = async (req, tenantId) => {
|
|
|
100
100
|
userId: headerUserId
|
|
101
101
|
};
|
|
102
102
|
};
|
|
103
|
-
const getTenantModel = async (tenantId, modelName) => {
|
|
103
|
+
const getTenantModel = async (tenantId, modelName, ability) => {
|
|
104
104
|
const ctx = {
|
|
105
105
|
req: {
|
|
106
106
|
session: {
|
|
@@ -108,7 +108,8 @@ const getTenantModel = async (tenantId, modelName) => {
|
|
|
108
108
|
currentTenantId: tenantId
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
-
}
|
|
111
|
+
},
|
|
112
|
+
ability
|
|
112
113
|
};
|
|
113
114
|
return models.get(modelName, ctx);
|
|
114
115
|
};
|
|
@@ -222,7 +223,7 @@ const resolvePopulateSpecForModel = async ({
|
|
|
222
223
|
const getModelCached = async (targetModelName) => {
|
|
223
224
|
const cached = modelCache.get(targetModelName);
|
|
224
225
|
if (cached) return cached;
|
|
225
|
-
const loaded = await getTenantModel(tenantId, targetModelName);
|
|
226
|
+
const loaded = await getTenantModel(tenantId, targetModelName, ability);
|
|
226
227
|
modelCache.set(targetModelName, loaded);
|
|
227
228
|
return loaded;
|
|
228
229
|
};
|
|
@@ -312,7 +313,7 @@ const resolveRtsQueryDependencyModelNames = async ({
|
|
|
312
313
|
options,
|
|
313
314
|
allowInternalModels = false
|
|
314
315
|
}) => {
|
|
315
|
-
const model = await getTenantModel(tenantId, modelName);
|
|
316
|
+
const model = await getTenantModel(tenantId, modelName, ability);
|
|
316
317
|
const modelCache = /* @__PURE__ */ new Map();
|
|
317
318
|
modelCache.set(modelName, model);
|
|
318
319
|
const dependencyModelNames = /* @__PURE__ */ new Set();
|
|
@@ -341,7 +342,7 @@ const runRtsQuery = async ({
|
|
|
341
342
|
if (!ability.can("read", modelName)) {
|
|
342
343
|
throw new Error("forbidden");
|
|
343
344
|
}
|
|
344
|
-
const model = await getTenantModel(tenantId, modelName);
|
|
345
|
+
const model = await getTenantModel(tenantId, modelName, ability);
|
|
345
346
|
const projection = options.projection ?? void 0;
|
|
346
347
|
const sort = options.sort;
|
|
347
348
|
const limit = normalizeLimit(options.limit);
|
|
@@ -399,4 +400,4 @@ export {
|
|
|
399
400
|
normalizeRtsQueryOptions as n,
|
|
400
401
|
resolveRtsRequestTenantId as r
|
|
401
402
|
};
|
|
402
|
-
//# sourceMappingURL=queryExecutor-
|
|
403
|
+
//# sourceMappingURL=queryExecutor-B7lb2FR1.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryExecutor-B7lb2FR1.js","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}\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 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 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 const accessQuery = getAccessibleByQuery(ability, \"read\", modelName as Exclude<AclSubjectType, \"all\">)\n const finalQuery: JsonObject = { $and: [query, accessQuery] }\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\n return {\n data: Array.isArray(paginatedResult.nodes) ? paginatedResult.nodes : [],\n pageInfo: paginatedResult.pageInfo,\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"],"names":["getDerivedKey","masterKey","info","length","salt","assert","Buffer","from","hkdfSync","toString","RTS_TENANT_ID_QUERY_PARAM","RTS_USER_ID_HEADER","QUERY_MAX_LIMIT","INTERNAL_MODEL_NAMES","Set","paginationCursorSigningSecret","getPaginationCursorSigningSecret","process","env","MASTER_KEY","trim","Error","normalizeTenantId","value","normalized","normalizeSignedInTenants","Array","isArray","map","tenantId","String","filter","Boolean","getTenantIdFromRequest","req","rawQuery","query","queryTenantId","normalizedFromQuery","session","user","currentTenantId","resolveRtsRequestTenantId","isRtsRequestAuthorized","sessionUser","signedInTenants","includes","buildRtsAbilityFromRequest","sessionUserId","id","ability","buildAbilityFromSession","userId","headerValue","headers","headerUserIdRaw","headerUserId","rbCtx","User","models","getGlobal","findById","tenants","tenantRoles","lean","tenantsRaw","tenant","roles","getTenantRolesFromSessionUser","buildAbility","getTenantModel","modelName","ctx","get","normalizeLimit","limit","Number","isFinite","Math","min","abs","normalizeString","normalizeObject","undefined","normalizePagination","normalizePopulateSelect","normalizePopulateOptions","raw","sort","max","floor","normalizePopulateObject","path","model","select","match","nestedPopulate","normalizeRtsPopulateOption","populate","options","entry","normalizeModelName","resolvePopulateRefModelName","explicitModelName","schema","schemaPath","directRef","ref","arrayRef","caster","virtualPath","virtualpath","virtualRef","mergePopulateMatchWithAcl","populateMatch","aclMatch","Object","keys","$and","resolvePopulateSpecForModel","allowInternalModels","modelCache","dependencyModelNames","getModelCached","targetModelName","cached","loaded","set","resolveOne","parentModel","refModelName","has","can","add","getAccessibleByQuery","nestedModel","normalizedEntry","resolved","Promise","all","filtered","normalizeRtsQueryOptions","projection","pagination","resolveRtsQueryDependencyModelNames","Map","runRtsQuery","accessQuery","finalQuery","paginatedQuery","find","paginatedResult","paginate","cursor","signingSecret","data","nodes","pageInfo","queryPromise"],"mappings":"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"}
|
package/dist/rts/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { models } from "@rpcbase/db";
|
|
3
3
|
import { buildAbilityFromSession, getTenantRolesFromSessionUser, buildAbility } from "@rpcbase/db/acl";
|
|
4
4
|
import { WebSocketServer } from "ws";
|
|
5
|
-
import { R as RTS_TENANT_ID_QUERY_PARAM, c as RTS_USER_ID_HEADER, n as normalizeRtsQueryOptions, d as resolveRtsQueryDependencyModelNames, a as runRtsQuery } from "../queryExecutor-
|
|
5
|
+
import { R as RTS_TENANT_ID_QUERY_PARAM, c as RTS_USER_ID_HEADER, n as normalizeRtsQueryOptions, d as resolveRtsQueryDependencyModelNames, a as runRtsQuery } from "../queryExecutor-B7lb2FR1.js";
|
|
6
6
|
const routes = Object.entries({
|
|
7
7
|
.../* @__PURE__ */ Object.assign({ "./api/changes/handler.ts": () => import("../handler-BsauvgjA.js") })
|
|
8
8
|
}).reduce((acc, [path, mod]) => {
|
|
@@ -234,7 +234,7 @@ const getTenantModel = async (tenantId, modelName) => {
|
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
};
|
|
237
|
-
return models.
|
|
237
|
+
return models.getUnsafe(modelName, ctx);
|
|
238
238
|
};
|
|
239
239
|
const makeDispatchKey = (tenantId, modelName) => `${tenantId}:${modelName}`;
|
|
240
240
|
const clearDispatchTimer = (tenantId, modelName) => {
|