@rpcbase/server 0.594.0 → 0.596.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/convertHeifToWebp-BT1n72DQ.js +148 -0
- package/dist/convertHeifToWebp-BT1n72DQ.js.map +1 -0
- package/dist/email-Co1GNjlT.js +7204 -0
- package/dist/email-Co1GNjlT.js.map +1 -0
- package/dist/handler-BqlcQ9sE.js +245 -0
- package/dist/handler-BqlcQ9sE.js.map +1 -0
- package/dist/handler-C92_gJkX.js +743 -0
- package/dist/handler-C92_gJkX.js.map +1 -0
- package/dist/handler-CapJTGzc.js +795 -0
- package/dist/handler-CapJTGzc.js.map +1 -0
- package/dist/handler-Fk-5fOSm.js +119 -0
- package/dist/handler-Fk-5fOSm.js.map +1 -0
- package/dist/htmlTemplate.d.ts +16 -0
- package/dist/htmlTemplate.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5140 -5286
- package/dist/index.js.map +1 -1
- package/dist/notifications.js +273 -382
- package/dist/notifications.js.map +1 -1
- package/dist/postProcessors-iffHvje4.js +89 -0
- package/dist/postProcessors-iffHvje4.js.map +1 -0
- package/dist/queryWindow-DP8zD0lb.js +555 -0
- package/dist/queryWindow-DP8zD0lb.js.map +1 -0
- package/dist/render_resend-B1SSQ4f7.js +7 -0
- package/dist/render_resend-B1SSQ4f7.js.map +1 -0
- package/dist/rts/index.js +990 -1207
- package/dist/rts/index.js.map +1 -1
- package/dist/schemas-B2fUvgYo.js +4151 -0
- package/dist/schemas-B2fUvgYo.js.map +1 -0
- package/dist/shared-DfrVDSp0.js +79 -0
- package/dist/shared-DfrVDSp0.js.map +1 -0
- package/dist/ssrMiddleware.d.ts +7 -3
- package/dist/ssrMiddleware.d.ts.map +1 -1
- package/dist/uploads/worker.js +139 -168
- package/dist/uploads/worker.js.map +1 -1
- package/dist/uploads.js +12 -22
- package/dist/uploads.js.map +1 -1
- package/package.json +1 -1
- package/dist/convertHeifToWebp-C-DGXZ2k.js +0 -169
- package/dist/convertHeifToWebp-C-DGXZ2k.js.map +0 -1
- package/dist/email-BCf24GmK.js +0 -8071
- package/dist/email-BCf24GmK.js.map +0 -1
- package/dist/handler-BqoKvylN.js +0 -147
- package/dist/handler-BqoKvylN.js.map +0 -1
- package/dist/handler-CUOJ51-w.js +0 -903
- package/dist/handler-CUOJ51-w.js.map +0 -1
- package/dist/handler-D-XdgeG_.js +0 -986
- package/dist/handler-D-XdgeG_.js.map +0 -1
- package/dist/handler-F0gFTzvh.js +0 -275
- package/dist/handler-F0gFTzvh.js.map +0 -1
- package/dist/postProcessors-D27fGZP0.js +0 -107
- package/dist/postProcessors-D27fGZP0.js.map +0 -1
- package/dist/queryWindow-Cdr7K-S1.js +0 -714
- package/dist/queryWindow-Cdr7K-S1.js.map +0 -1
- package/dist/render_resend_false-MiC__Smr.js +0 -6
- package/dist/render_resend_false-MiC__Smr.js.map +0 -1
- package/dist/schemas-Drf83ni9.js +0 -4517
- package/dist/schemas-Drf83ni9.js.map +0 -1
- package/dist/shared-CJrm9Wjp.js +0 -104
- package/dist/shared-CJrm9Wjp.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler-CapJTGzc.js","names":["JSDOM","createDOMPurify","UploadFileProcessor","MAX_SVG_BYTES","window","DOMPurify","normalizeForSniff","raw","replace","trimStart","looksLikeSvgText","text","normalized","startsWith","test","looksLikeSvg","sniff","Buffer","toString","sanitizeSvg","svg","sanitize","USE_PROFILES","svgFilters","sanitizeSvgProcessor","id","maxBytes","match","process","data","mimeType","length","Error","svgText","sanitized","trim","sanitizedBuffer","from","convertHeifToWebpProcessor","sanitizeSvgProcessor","UploadFileProcessorContext","filename","clientMimeType","totalSize","sniff","Buffer","UploadFileProcessorResult","data","mimeType","metadata","Record","UploadFileProcessor","id","maxBytes","match","ctx","process","Promise","uploadProcessors","Object","freeze","getMaxUploadProcessorBytes","reduce","max","processor","Math","selectUploadProcessors","filter","applyUploadProcessors","Omit","applied","currentData","currentFilename","currentMimeType","processorCtx","length","Error","result","trim","assign","push","ApiHandler","getTenantFilesystemDb","models","GridFSBucket","Model","Uploads","enqueueUploadPostProcessors","applyUploadProcessors","getMaxUploadProcessorBytes","selectUploadProcessors","SessionUser","UploadChunkDoc","UploadSessionDoc","buildUploadsAbility","ensureUploadIndexes","getBucketName","getModelCtx","getUploadSessionAccessQuery","getTenantId","waitForStreamFinished","stream","NodeJS","WritableStream","Promise","resolve","reject","once","writeToStream","chunk","Buffer","ok","write","onDrain","cleanup","onError","error","off","on","abortUploadStream","abort","destroy","completeUpload","Record","CompleteResponsePayload","_payload","ctx","tenantId","res","status","uploadId","String","req","params","trim","ability","modelCtx","UploadSession","UploadChunk","all","get","can","existing","findOne","$and","_id","lean","fileId","locked","findOneAndUpdate","$set","$unset","returnDocument","fsDb","nativeDb","db","updateOne","bucketName","bucket","lockedUserId","userId","undefined","maxProcessorBytes","shouldBufferForProcessing","totalSize","declaredMimeType","mimeType","toLowerCase","declaredSvg","filename","endsWith","uploadStream","finalFilename","finalMimeType","finalSize","inlineProcessors","finalMetadata","isPublic","ownerKeyHash","Error","cursor","find","sort","index","AsyncIterable","close","expectedIndex","chunks","bufferedBytes","pendingChunks","sniffParts","sniffBytes","chunkDoc","data","push","length","slice","subarray","Math","min","sniff","concat","processors","clientMimeType","some","processor","id","openUploadStream","metadata","pending","chunksTotal","assembled","processed","processedFilename","processedMimeType","applied","processedMetadata","sourceTotalSize","finished","end","catch","console","deleteMany","message","startsWith","ApiHandler","models","Model","Uploads","SessionUser","UploadChunkDoc","UploadSessionDoc","buildUploadsAbility","getModelCtx","getUploadSessionAccessQuery","getTenantId","getStatus","Record","StatusResponsePayload","_payload","ctx","Promise","tenantId","res","status","ok","error","uploadId","String","req","params","trim","ability","modelCtx","UploadSession","UploadChunk","all","get","can","session","findOne","$and","_id","lean","receivedDocs","find","index","sort","received","Array","map","doc","filter","n","Number","isInteger","chunkSize","chunksTotal","fileId","z","InitRoute","ChunkRoute","StatusRoute","CompleteRoute","initRequestSchema","object","filename","string","min","mimeType","isPublic","boolean","optional","totalSize","number","int","InitRequestPayload","infer","initResponseSchema","ok","error","uploadId","uploadKey","chunkSize","chunksTotal","InitResponsePayload","statusResponseSchema","status","enum","received","array","fileId","StatusResponsePayload","completeResponseSchema","CompleteResponsePayload","randomBytes","ApiHandler","models","ObjectId","Model","Uploads","SessionUser","UploadChunkDoc","UploadSessionDoc","buildUploadsAbility","computeSha256Hex","ensureUploadIndexes","getChunkSizeBytes","getModelCtx","getSessionTtlMs","getTenantId","getUserId","initUpload","InitRequestPayload","InitResponsePayload","payload","ctx","Promise","tenantId","res","status","ok","error","userId","parsed","initRequestSchema","safeParse","success","chunkSize","filename","mimeType","totalSize","isPublic","data","chunksTotal","Math","ceil","ability","modelCtx","UploadSession","UploadChunk","all","get","uploadId","toString","now","Date","expiresAt","uploadKey","ownerKeyHash","Buffer","from","undefined","create","_id","createdAt","ApiHandler","models","Model","SessionUser","UploadChunkDoc","UploadSessionDoc","buildUploadsAbility","computeSha256Hex","ensureUploadIndexes","getModelCtx","getUploadSessionAccessQuery","getTenantId","normalizeSha256Hex","toBufferPayload","ChunkResponsePayload","ok","error","uploadChunk","Buffer","payload","ctx","Promise","tenantId","res","status","uploadId","String","req","params","trim","indexRaw","index","Number","isInteger","ability","modelCtx","UploadSession","UploadChunk","all","get","can","session","findOne","$and","_id","lean","chunksTotal","data","expectedSize","totalSize","chunkSize","length","checksumHeader","sha256","undefined","expectedSha256","updateOne","$set","size","expiresAt","$setOnInsert","createdAt","Date","upsert","rawBodyParser","limitBytes","maxClientBytesPerSecond","req","res","next","contentType","headers","String","includes","total","chunks","Buffer","done","paused","throttleTimeout","ReturnType","setTimeout","rateBytesPerSecond","cleanup","off","onData","onEnd","onError","onAborted","clearTimeout","finish","error","body","concat","chunk","buffer","isBuffer","from","length","destroy","status","json","ok","push","now","Date","clientKey","getClientKey","state","getClientRateState","waitMs","consumeRateBudget","pause","resume","err","Error","on","ClientRateState","tokens","lastRefillMs","lastSeenMs","MAX_BURST_SECONDS","STALE_CLIENT_MS","clientRateStates","Map","lastCleanupMs","rawClientIp","clientIp","trim","rawIp","ip","maybeCleanupStates","size","key","delete","capacity","existing","get","Math","min","set","bytes","elapsedMs","max","ceil","Api","completeUpload","getStatus","initUpload","uploadChunk","rawBodyParser","getChunkSizeBytes","getMaxClientUploadBytesPerSecond","getRawBodyLimitBytes","SessionUser","Uploads","api","chunkSizeBytes","use","InitRoute","limitBytes","maxClientBytesPerSecond","post","put","ChunkRoute","get","StatusRoute","CompleteRoute"],"sources":["../src/uploads/api/file-uploads/processors/sanitizeSvg.ts","../src/uploads/api/file-uploads/processors/index.ts","../src/uploads/api/file-uploads/handlers/completeUpload.ts","../src/uploads/api/file-uploads/handlers/getStatus.ts","../src/uploads/api/file-uploads/index.ts","../src/uploads/api/file-uploads/handlers/initUpload.ts","../src/uploads/api/file-uploads/handlers/uploadChunk.ts","../src/uploads/api/file-uploads/middleware/rawBodyParser.ts","../src/uploads/api/file-uploads/handler.ts"],"sourcesContent":["import { JSDOM } from \"jsdom\"\nimport createDOMPurify from \"dompurify\"\n\nimport type { UploadFileProcessor } from \"./index\"\n\n\nconst MAX_SVG_BYTES = 128 * 1024\n\nconst window = new JSDOM(\"\").window\nconst DOMPurify = createDOMPurify(window)\n\nconst normalizeForSniff = (raw: string): string => raw.replace(/^\\uFEFF/, \"\").trimStart()\n\nconst looksLikeSvgText = (text: string): boolean => {\n const normalized = normalizeForSniff(text)\n if (!normalized.startsWith(\"<\")) return false\n return /<svg(?:\\s|>)/i.test(normalized)\n}\n\nexport const looksLikeSvg = (sniff: Buffer): boolean => looksLikeSvgText(sniff.toString(\"utf8\"))\n\nexport const sanitizeSvg = (svg: string): string =>\n DOMPurify.sanitize(svg, {\n USE_PROFILES: { svg: true, svgFilters: true },\n })\n\nexport const sanitizeSvgProcessor: UploadFileProcessor = {\n id: \"sanitize-svg\",\n maxBytes: MAX_SVG_BYTES,\n match: ({ sniff }) => looksLikeSvg(sniff),\n process: (data): { data: Buffer; mimeType: string } => {\n if (data.length > MAX_SVG_BYTES) {\n throw new Error(\"svg_too_large\")\n }\n\n const svgText = data.toString(\"utf8\")\n if (!looksLikeSvgText(svgText)) {\n throw new Error(\"svg_invalid\")\n }\n\n const sanitized = sanitizeSvg(svgText)\n if (!sanitized.trim() || !looksLikeSvgText(sanitized)) {\n throw new Error(\"svg_sanitize_failed\")\n }\n\n const sanitizedBuffer = Buffer.from(sanitized, \"utf8\")\n if (sanitizedBuffer.length > MAX_SVG_BYTES) {\n throw new Error(\"svg_too_large\")\n }\n\n return { data: sanitizedBuffer, mimeType: \"image/svg+xml\" }\n },\n}\n\n","import { convertHeifToWebpProcessor } from \"./convertHeifToWebp\"\nimport { sanitizeSvgProcessor } from \"./sanitizeSvg\"\n\n\nexport type UploadFileProcessorContext = {\n filename: string\n clientMimeType: string\n totalSize: number\n sniff: Buffer\n}\n\nexport type UploadFileProcessorResult = {\n data: Buffer\n filename?: string\n mimeType?: string\n metadata?: Record<string, unknown>\n}\n\nexport type UploadFileProcessor = {\n id: string\n maxBytes: number\n match: (ctx: UploadFileProcessorContext) => boolean\n process: (data: Buffer, ctx: UploadFileProcessorContext) => Promise<UploadFileProcessorResult> | UploadFileProcessorResult\n}\n\nexport const uploadProcessors = Object.freeze([sanitizeSvgProcessor, convertHeifToWebpProcessor] satisfies UploadFileProcessor[])\n\nexport const getMaxUploadProcessorBytes = (): number =>\n uploadProcessors.reduce((max, processor) => Math.max(max, processor.maxBytes), 0)\n\nexport const selectUploadProcessors = (ctx: UploadFileProcessorContext): UploadFileProcessor[] =>\n uploadProcessors.filter((processor) => processor.match(ctx))\n\nexport const applyUploadProcessors = async (\n data: Buffer,\n ctx: Omit<UploadFileProcessorContext, \"sniff\" | \"totalSize\">,\n): Promise<{ data: Buffer; filename: string; mimeType: string; applied: string[]; metadata: Record<string, unknown> }> => {\n let currentData = data\n let currentFilename = ctx.filename\n let currentMimeType = ctx.clientMimeType\n const applied: string[] = []\n const metadata: Record<string, unknown> = {}\n\n for (const processor of uploadProcessors) {\n const processorCtx: UploadFileProcessorContext = {\n filename: currentFilename,\n clientMimeType: currentMimeType,\n totalSize: currentData.length,\n sniff: currentData,\n }\n\n if (!processor.match(processorCtx)) continue\n\n if (currentData.length > processor.maxBytes) {\n throw new Error(\"processor_input_too_large\")\n }\n\n const result = await processor.process(currentData, processorCtx)\n currentData = result.data\n if (typeof result.filename === \"string\" && result.filename.trim()) {\n currentFilename = result.filename.trim()\n }\n if (typeof result.mimeType === \"string\" && result.mimeType.trim()) {\n currentMimeType = result.mimeType.trim()\n }\n if (result.metadata && typeof result.metadata === \"object\") {\n Object.assign(metadata, result.metadata)\n }\n applied.push(processor.id)\n }\n\n return {\n data: currentData,\n filename: currentFilename,\n mimeType: currentMimeType,\n applied,\n metadata,\n }\n}\n","import { ApiHandler } from \"@rpcbase/api\"\nimport { getTenantFilesystemDb, models } from \"@rpcbase/db\"\nimport { GridFSBucket } from \"mongodb\"\nimport type { Model } from \"mongoose\"\n\nimport * as Uploads from \"../index\"\nimport { enqueueUploadPostProcessors } from \"../postProcessors\"\nimport { applyUploadProcessors, getMaxUploadProcessorBytes, selectUploadProcessors } from \"../processors\"\nimport {\n type SessionUser,\n type UploadChunkDoc,\n type UploadSessionDoc,\n buildUploadsAbility,\n ensureUploadIndexes,\n getBucketName,\n getModelCtx,\n getUploadSessionAccessQuery,\n getTenantId,\n} from \"../shared\"\n\n\nconst waitForStreamFinished = async (stream: NodeJS.WritableStream): Promise<void> => new Promise((resolve, reject) => {\n stream.once(\"finish\", resolve)\n stream.once(\"error\", reject)\n})\n\nconst writeToStream = async (stream: NodeJS.WritableStream, chunk: Buffer): Promise<void> => {\n const ok = stream.write(chunk)\n if (ok) return\n await new Promise<void>((resolve, reject) => {\n const onDrain = () => {\n cleanup()\n resolve()\n }\n\n const onError = (error: unknown) => {\n cleanup()\n reject(error)\n }\n\n const cleanup = () => {\n stream.off(\"drain\", onDrain)\n stream.off(\"error\", onError)\n }\n\n stream.on(\"drain\", onDrain)\n stream.on(\"error\", onError)\n })\n}\n\nconst abortUploadStream = async (stream: unknown): Promise<void> => {\n if (!stream) return\n if (typeof (stream as { abort?: unknown }).abort === \"function\") {\n try {\n await (stream as { abort: () => Promise<void> | void }).abort()\n return\n } catch {\n //\n }\n }\n try {\n ;(stream as { destroy?: () => void }).destroy?.()\n } catch {\n //\n }\n}\n\nexport const completeUpload: ApiHandler<Record<string, never>, Uploads.CompleteResponsePayload, SessionUser> = async (\n _payload,\n ctx,\n): Promise<Uploads.CompleteResponsePayload> => {\n const tenantId = getTenantId(ctx)\n if (!tenantId) {\n ctx.res.status(400)\n return { ok: false, error: \"tenant_missing\" }\n }\n\n const uploadId = String(ctx.req.params?.uploadId ?? \"\").trim()\n if (!uploadId) {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_upload_id\" }\n }\n\n const ability = buildUploadsAbility(ctx, tenantId)\n const modelCtx = getModelCtx(ctx, tenantId, ability)\n\n const [UploadSession, UploadChunk] = await Promise.all([\n models.get(\"RBUploadSession\", modelCtx) as Promise<Model<UploadSessionDoc>>,\n models.get(\"RBUploadChunk\", modelCtx) as Promise<Model<UploadChunkDoc>>,\n ])\n\n if (!ability.can(\"update\", \"RBUploadSession\")) {\n ctx.res.status(401)\n return { ok: false, error: \"unauthorized\" }\n }\n\n const existing = await UploadSession.findOne({ $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"read\")] }).lean()\n if (!existing) {\n ctx.res.status(404)\n return { ok: false, error: \"not_found\" }\n }\n\n if (existing.status === \"done\" && existing.fileId) {\n return { ok: true, fileId: existing.fileId }\n }\n\n const locked = await UploadSession.findOneAndUpdate(\n { $and: [{ _id: uploadId }, { status: \"uploading\" }, getUploadSessionAccessQuery(ability, \"update\")] },\n { $set: { status: \"assembling\" }, $unset: { error: \"\" } },\n { returnDocument: \"after\" },\n ).lean()\n\n if (!locked) {\n ctx.res.status(409)\n return { ok: false, error: \"not_uploading\" }\n }\n\n await ensureUploadIndexes(UploadSession, UploadChunk)\n\n const fsDb = await getTenantFilesystemDb(tenantId)\n const nativeDb = fsDb.db\n if (!nativeDb) {\n await UploadSession.updateOne(\n { $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"update\")] },\n { $set: { status: \"error\", error: \"filesystem_db_unavailable\" } },\n )\n ctx.res.status(500)\n return { ok: false, error: \"assembly_failed\" }\n }\n const bucketName = getBucketName()\n const bucket = new GridFSBucket(nativeDb, { bucketName })\n\n const lockedUserId = typeof locked.userId === \"string\" ? locked.userId : undefined\n const maxProcessorBytes = getMaxUploadProcessorBytes()\n const shouldBufferForProcessing = locked.totalSize <= maxProcessorBytes\n const declaredMimeType = locked.mimeType.trim().toLowerCase()\n const declaredSvg = declaredMimeType === \"image/svg+xml\" || locked.filename.trim().toLowerCase().endsWith(\".svg\")\n\n let uploadStream: NodeJS.WritableStream | null = null\n let finalFilename = locked.filename\n let finalMimeType = locked.mimeType\n let finalSize = locked.totalSize\n let inlineProcessors: string[] = []\n let finalMetadata: Record<string, unknown> = {\n uploadId,\n tenantId,\n mimeType: locked.mimeType,\n totalSize: locked.totalSize,\n ...(typeof locked.isPublic === \"boolean\" ? { isPublic: locked.isPublic } : {}),\n ...(typeof locked.ownerKeyHash === \"string\" ? { ownerKeyHash: locked.ownerKeyHash } : {}),\n ...(lockedUserId ? { userId: lockedUserId } : {}),\n }\n\n try {\n if (!shouldBufferForProcessing && declaredSvg) {\n throw new Error(\"svg_too_large\")\n }\n\n const cursor = UploadChunk.find({ uploadId }).sort({ index: 1 }).cursor() as unknown as AsyncIterable<UploadChunkDoc> & {\n close: () => Promise<void>\n }\n\n let expectedIndex = 0\n const chunks: Buffer[] = []\n let bufferedBytes = 0\n\n const pendingChunks: Buffer[] = []\n const sniffParts: Buffer[] = []\n let sniffBytes = 0\n\n try {\n for await (const chunkDoc of cursor) {\n if (chunkDoc.index !== expectedIndex) {\n throw new Error(\"missing_chunks\")\n }\n\n const chunk = chunkDoc.data\n\n if (shouldBufferForProcessing) {\n chunks.push(chunk)\n bufferedBytes += chunk.length\n } else if (!uploadStream) {\n pendingChunks.push(chunk)\n\n if (sniffBytes < maxProcessorBytes) {\n const slice = chunk.subarray(0, Math.min(chunk.length, maxProcessorBytes - sniffBytes))\n if (slice.length) {\n sniffParts.push(slice)\n sniffBytes += slice.length\n }\n }\n\n if (sniffBytes >= maxProcessorBytes) {\n const sniff = Buffer.concat(sniffParts, sniffBytes)\n const processors = selectUploadProcessors({\n filename: locked.filename,\n clientMimeType: locked.mimeType,\n totalSize: locked.totalSize,\n sniff,\n })\n\n if (processors.length) {\n throw new Error(processors.some((processor) => processor.id === \"sanitize-svg\") ? \"svg_too_large\" : \"processor_input_too_large\")\n }\n\n finalMetadata = {\n uploadId,\n tenantId,\n mimeType: locked.mimeType,\n totalSize: locked.totalSize,\n ...(typeof locked.isPublic === \"boolean\" ? { isPublic: locked.isPublic } : {}),\n ...(typeof locked.ownerKeyHash === \"string\" ? { ownerKeyHash: locked.ownerKeyHash } : {}),\n ...(lockedUserId ? { userId: lockedUserId } : {}),\n }\n uploadStream = bucket.openUploadStream(locked.filename, {\n metadata: finalMetadata,\n })\n\n for (const pending of pendingChunks) {\n await writeToStream(uploadStream, pending)\n }\n pendingChunks.length = 0\n }\n } else {\n await writeToStream(uploadStream, chunk)\n }\n\n expectedIndex += 1\n }\n } finally {\n try {\n await cursor.close()\n } catch {\n //\n }\n }\n\n if (expectedIndex !== locked.chunksTotal) {\n throw new Error(\"missing_chunks\")\n }\n\n if (shouldBufferForProcessing) {\n const assembled = Buffer.concat(chunks, bufferedBytes)\n const {\n data: processed,\n filename: processedFilename,\n mimeType: processedMimeType,\n applied,\n metadata: processedMetadata,\n } = await applyUploadProcessors(assembled, {\n filename: locked.filename,\n clientMimeType: locked.mimeType,\n })\n\n finalFilename = processedFilename\n finalMimeType = processedMimeType\n finalSize = processed.length\n inlineProcessors = applied\n finalMetadata = {\n uploadId,\n tenantId,\n mimeType: processedMimeType,\n totalSize: processed.length,\n ...(applied.length ? { processors: applied, sourceTotalSize: locked.totalSize } : {}),\n ...processedMetadata,\n ...(typeof locked.isPublic === \"boolean\" ? { isPublic: locked.isPublic } : {}),\n ...(typeof locked.ownerKeyHash === \"string\" ? { ownerKeyHash: locked.ownerKeyHash } : {}),\n ...(lockedUserId ? { userId: lockedUserId } : {}),\n }\n uploadStream = bucket.openUploadStream(finalFilename, {\n metadata: finalMetadata,\n })\n\n const finished = waitForStreamFinished(uploadStream)\n uploadStream.end(processed)\n await finished\n } else {\n if (!uploadStream) {\n const sniff = Buffer.concat(sniffParts, sniffBytes)\n const processors = selectUploadProcessors({\n filename: locked.filename,\n clientMimeType: locked.mimeType,\n totalSize: locked.totalSize,\n sniff,\n })\n\n if (processors.length) {\n throw new Error(processors.some((processor) => processor.id === \"sanitize-svg\") ? \"svg_too_large\" : \"processor_input_too_large\")\n }\n\n finalMetadata = {\n uploadId,\n tenantId,\n mimeType: locked.mimeType,\n totalSize: locked.totalSize,\n ...(typeof locked.isPublic === \"boolean\" ? { isPublic: locked.isPublic } : {}),\n ...(typeof locked.ownerKeyHash === \"string\" ? { ownerKeyHash: locked.ownerKeyHash } : {}),\n ...(lockedUserId ? { userId: lockedUserId } : {}),\n }\n uploadStream = bucket.openUploadStream(locked.filename, {\n metadata: finalMetadata,\n })\n\n for (const pending of pendingChunks) {\n await writeToStream(uploadStream, pending)\n }\n pendingChunks.length = 0\n }\n\n const finished = waitForStreamFinished(uploadStream)\n uploadStream.end()\n await finished\n }\n\n const fileId = String((uploadStream as unknown as { id?: unknown }).id ?? \"\")\n if (!fileId) {\n throw new Error(\"missing_file_id\")\n }\n\n await UploadSession.updateOne(\n { $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"update\")] },\n { $set: { status: \"done\", fileId, filename: finalFilename, mimeType: finalMimeType, totalSize: finalSize }, $unset: { error: \"\" } },\n )\n\n await enqueueUploadPostProcessors({\n tenantId,\n uploadId,\n fileId,\n filename: finalFilename,\n mimeType: finalMimeType,\n clientMimeType: locked.mimeType,\n totalSize: finalSize,\n ...(typeof locked.isPublic === \"boolean\" ? { isPublic: locked.isPublic } : {}),\n ...(typeof locked.ownerKeyHash === \"string\" ? { ownerKeyHash: locked.ownerKeyHash } : {}),\n ...(lockedUserId ? { userId: lockedUserId } : {}),\n inlineProcessors,\n metadata: finalMetadata,\n }).catch((error) => {\n console.error(\"Upload post processor enqueue failed\", {\n tenantId,\n uploadId,\n fileId,\n error,\n })\n })\n\n try {\n await UploadChunk.deleteMany({ uploadId })\n } catch {\n //\n }\n\n return { ok: true, fileId }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n\n await abortUploadStream(uploadStream)\n\n if (message === \"missing_chunks\") {\n await UploadSession.updateOne(\n { $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"update\")] },\n { $set: { status: \"uploading\" } },\n )\n ctx.res.status(409)\n return { ok: false, error: \"missing_chunks\" }\n }\n\n if (message === \"svg_too_large\") {\n await UploadSession.updateOne(\n { $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"update\")] },\n { $set: { status: \"error\", error: message } },\n )\n ctx.res.status(413)\n return { ok: false, error: message }\n }\n\n if (message === \"heif_too_large\" || message === \"processor_input_too_large\") {\n await UploadSession.updateOne(\n { $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"update\")] },\n { $set: { status: \"error\", error: message } },\n )\n ctx.res.status(413)\n return { ok: false, error: message }\n }\n\n if (message.startsWith(\"heif_decode_unsupported\")) {\n await UploadSession.updateOne(\n { $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"update\")] },\n { $set: { status: \"error\", error: message } },\n )\n ctx.res.status(415)\n return { ok: false, error: message }\n }\n\n if (message === \"svg_invalid\" || message === \"svg_sanitize_failed\") {\n await UploadSession.updateOne(\n { $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"update\")] },\n { $set: { status: \"error\", error: message } },\n )\n ctx.res.status(400)\n return { ok: false, error: message }\n }\n\n await UploadSession.updateOne(\n { $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"update\")] },\n { $set: { status: \"error\", error: message } },\n )\n\n ctx.res.status(500)\n return { ok: false, error: \"assembly_failed\" }\n }\n}\n","import { ApiHandler } from \"@rpcbase/api\"\nimport { models } from \"@rpcbase/db\"\nimport type { Model } from \"mongoose\"\n\nimport * as Uploads from \"../index\"\nimport {\n type SessionUser,\n type UploadChunkDoc,\n type UploadSessionDoc,\n buildUploadsAbility,\n getModelCtx,\n getUploadSessionAccessQuery,\n getTenantId,\n} from \"../shared\"\n\n\nexport const getStatus: ApiHandler<Record<string, never>, Uploads.StatusResponsePayload, SessionUser> = async (\n _payload,\n ctx,\n): Promise<Uploads.StatusResponsePayload> => {\n const tenantId = getTenantId(ctx)\n if (!tenantId) {\n ctx.res.status(400)\n return { ok: false, error: \"tenant_missing\" }\n }\n\n const uploadId = String(ctx.req.params?.uploadId ?? \"\").trim()\n if (!uploadId) {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_upload_id\" }\n }\n\n const ability = buildUploadsAbility(ctx, tenantId)\n const modelCtx = getModelCtx(ctx, tenantId, ability)\n\n const [UploadSession, UploadChunk] = await Promise.all([\n models.get(\"RBUploadSession\", modelCtx) as Promise<Model<UploadSessionDoc>>,\n models.get(\"RBUploadChunk\", modelCtx) as Promise<Model<UploadChunkDoc>>,\n ])\n\n if (!ability.can(\"read\", \"RBUploadSession\")) {\n ctx.res.status(401)\n return { ok: false, error: \"unauthorized\" }\n }\n\n const session = await UploadSession.findOne({ $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"read\")] }).lean()\n if (!session) {\n ctx.res.status(404)\n return { ok: false, error: \"not_found\" }\n }\n\n const receivedDocs = await UploadChunk.find(\n { uploadId },\n { index: 1, _id: 0 },\n ).sort({ index: 1 }).lean()\n\n const received = (receivedDocs as unknown as Array<{ index?: unknown }>)\n .map((doc) => (typeof doc.index === \"number\" ? doc.index : -1))\n .filter((n) => Number.isInteger(n) && n >= 0)\n\n return {\n ok: true,\n status: session.status,\n chunkSize: session.chunkSize,\n chunksTotal: session.chunksTotal,\n received,\n ...(session.fileId ? { fileId: session.fileId } : {}),\n }\n}\n","import { z } from \"zod\"\n\n\nexport const InitRoute = \"/api/rb/file-uploads\"\nexport const ChunkRoute = \"/api/rb/file-uploads/:uploadId/chunks/:index\"\nexport const StatusRoute = \"/api/rb/file-uploads/:uploadId/status\"\nexport const CompleteRoute = \"/api/rb/file-uploads/:uploadId/complete\"\n\nexport const initRequestSchema = z.object({\n filename: z.string().min(1),\n mimeType: z.string().min(1),\n isPublic: z.boolean().optional(),\n totalSize: z.number().int().min(1),\n})\n\nexport type InitRequestPayload = z.infer<typeof initRequestSchema>\n\nexport const initResponseSchema = z.object({\n ok: z.boolean(),\n error: z.string().optional(),\n uploadId: z.string().optional(),\n uploadKey: z.string().optional(),\n chunkSize: z.number().int().optional(),\n chunksTotal: z.number().int().optional(),\n})\n\nexport type InitResponsePayload = z.infer<typeof initResponseSchema>\n\nexport const statusResponseSchema = z.object({\n ok: z.boolean(),\n error: z.string().optional(),\n status: z.enum([\"uploading\", \"assembling\", \"done\", \"error\"]).optional(),\n chunkSize: z.number().int().optional(),\n chunksTotal: z.number().int().optional(),\n received: z.array(z.number().int().min(0)).optional(),\n fileId: z.string().optional(),\n})\n\nexport type StatusResponsePayload = z.infer<typeof statusResponseSchema>\n\nexport const completeResponseSchema = z.object({\n ok: z.boolean(),\n error: z.string().optional(),\n fileId: z.string().optional(),\n})\n\nexport type CompleteResponsePayload = z.infer<typeof completeResponseSchema>\n","import { randomBytes } from \"node:crypto\"\n\nimport { ApiHandler } from \"@rpcbase/api\"\nimport { models } from \"@rpcbase/db\"\nimport { ObjectId } from \"mongodb\"\nimport type { Model } from \"mongoose\"\n\nimport * as Uploads from \"../index\"\nimport {\n type SessionUser,\n type UploadChunkDoc,\n type UploadSessionDoc,\n buildUploadsAbility,\n computeSha256Hex,\n ensureUploadIndexes,\n getChunkSizeBytes,\n getModelCtx,\n getSessionTtlMs,\n getTenantId,\n getUserId,\n} from \"../shared\"\n\n\nexport const initUpload: ApiHandler<Uploads.InitRequestPayload, Uploads.InitResponsePayload, SessionUser> = async (\n payload,\n ctx,\n): Promise<Uploads.InitResponsePayload> => {\n const tenantId = getTenantId(ctx)\n if (!tenantId) {\n ctx.res.status(400)\n return { ok: false, error: \"tenant_missing\" }\n }\n\n const userId = getUserId(ctx)\n\n const parsed = Uploads.initRequestSchema.safeParse(payload ?? {})\n if (!parsed.success) {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_payload\" }\n }\n\n const chunkSize = getChunkSizeBytes()\n const { filename, mimeType, totalSize, isPublic } = parsed.data\n const chunksTotal = Math.ceil(totalSize / chunkSize)\n\n const ability = buildUploadsAbility(ctx, tenantId)\n const modelCtx = getModelCtx(ctx, tenantId, ability)\n\n const [UploadSession, UploadChunk] = await Promise.all([\n models.get(\"RBUploadSession\", modelCtx) as Promise<Model<UploadSessionDoc>>,\n models.get(\"RBUploadChunk\", modelCtx) as Promise<Model<UploadChunkDoc>>,\n ])\n\n await ensureUploadIndexes(UploadSession, UploadChunk)\n\n const uploadId = new ObjectId().toString()\n const now = Date.now()\n const expiresAt = new Date(now + getSessionTtlMs())\n\n const uploadKey = userId ? null : randomBytes(32).toString(\"base64url\")\n const ownerKeyHash = uploadKey ? computeSha256Hex(Buffer.from(uploadKey)) : undefined\n\n await UploadSession.create({\n _id: uploadId,\n ...(userId ? { userId } : {}),\n ...(ownerKeyHash ? { ownerKeyHash } : {}),\n filename,\n mimeType,\n ...(typeof isPublic === \"boolean\" ? { isPublic } : {}),\n totalSize,\n chunkSize,\n chunksTotal,\n status: \"uploading\",\n createdAt: new Date(now),\n expiresAt,\n })\n\n return {\n ok: true,\n uploadId,\n chunkSize,\n chunksTotal,\n ...(uploadKey ? { uploadKey } : {}),\n }\n}\n","import { ApiHandler } from \"@rpcbase/api\"\nimport { models } from \"@rpcbase/db\"\nimport type { Model } from \"mongoose\"\n\nimport {\n type SessionUser,\n type UploadChunkDoc,\n type UploadSessionDoc,\n buildUploadsAbility,\n computeSha256Hex,\n ensureUploadIndexes,\n getModelCtx,\n getUploadSessionAccessQuery,\n getTenantId,\n normalizeSha256Hex,\n toBufferPayload,\n} from \"../shared\"\n\n\ntype ChunkResponsePayload = {\n ok: boolean\n error?: string\n}\n\nexport const uploadChunk: ApiHandler<Buffer, ChunkResponsePayload, SessionUser> = async (\n payload,\n ctx,\n): Promise<ChunkResponsePayload> => {\n const tenantId = getTenantId(ctx)\n if (!tenantId) {\n ctx.res.status(400)\n return { ok: false, error: \"tenant_missing\" }\n }\n\n const uploadId = String(ctx.req.params?.uploadId ?? \"\").trim()\n const indexRaw = String(ctx.req.params?.index ?? \"\").trim()\n const index = Number(indexRaw)\n\n if (!uploadId || !Number.isInteger(index) || index < 0) {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_chunk_ref\" }\n }\n\n const ability = buildUploadsAbility(ctx, tenantId)\n const modelCtx = getModelCtx(ctx, tenantId, ability)\n\n const [UploadSession, UploadChunk] = await Promise.all([\n models.get(\"RBUploadSession\", modelCtx) as Promise<Model<UploadSessionDoc>>,\n models.get(\"RBUploadChunk\", modelCtx) as Promise<Model<UploadChunkDoc>>,\n ])\n\n if (!ability.can(\"update\", \"RBUploadSession\")) {\n ctx.res.status(401)\n return { ok: false, error: \"unauthorized\" }\n }\n\n const session = await UploadSession.findOne({ $and: [{ _id: uploadId }, getUploadSessionAccessQuery(ability, \"update\")] }).lean()\n if (!session) {\n ctx.res.status(404)\n return { ok: false, error: \"not_found\" }\n }\n\n if (session.status !== \"uploading\") {\n ctx.res.status(409)\n return { ok: false, error: \"not_uploading\" }\n }\n\n if (index >= session.chunksTotal) {\n ctx.res.status(400)\n return { ok: false, error: \"index_out_of_range\" }\n }\n\n const data = toBufferPayload(payload)\n if (!data) {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_body\" }\n }\n\n const expectedSize = index === session.chunksTotal - 1\n ? session.totalSize - session.chunkSize * (session.chunksTotal - 1)\n : session.chunkSize\n\n if (data.length > expectedSize) {\n ctx.res.status(413)\n return { ok: false, error: \"chunk_too_large\" }\n }\n\n if (data.length !== expectedSize) {\n ctx.res.status(400)\n return { ok: false, error: \"invalid_chunk_size\" }\n }\n\n const checksumHeader = ctx.req.get(\"X-Chunk-SHA256\")\n const sha256 = checksumHeader ? computeSha256Hex(data) : undefined\n\n if (checksumHeader) {\n const expectedSha256 = normalizeSha256Hex(checksumHeader)\n if (sha256 !== expectedSha256) {\n ctx.res.status(400)\n return { ok: false, error: \"checksum_mismatch\" }\n }\n }\n\n await ensureUploadIndexes(UploadSession, UploadChunk)\n\n await UploadChunk.updateOne(\n { uploadId, index },\n {\n $set: {\n uploadId,\n index,\n data,\n size: data.length,\n sha256,\n expiresAt: session.expiresAt,\n },\n $setOnInsert: {\n createdAt: new Date(),\n },\n },\n { upsert: true },\n )\n\n ctx.res.status(204)\n return { ok: true }\n}\n","export const rawBodyParser = ({\n limitBytes,\n maxClientBytesPerSecond,\n}: {\n limitBytes: number\n maxClientBytesPerSecond?: number | null\n}) => {\n return (req: any, res: any, next: any) => {\n const contentType = typeof req?.headers?.[\"content-type\"] === \"string\"\n ? String(req.headers[\"content-type\"])\n : \"\"\n\n if (!contentType.includes(\"application/octet-stream\")) {\n next()\n return\n }\n\n let total = 0\n const chunks: Buffer[] = []\n let done = false\n let paused = false\n let throttleTimeout: ReturnType<typeof setTimeout> | null = null\n\n const rateBytesPerSecond = typeof maxClientBytesPerSecond === \"number\" && maxClientBytesPerSecond > 0\n ? maxClientBytesPerSecond\n : null\n\n const cleanup = () => {\n req.off(\"data\", onData)\n req.off(\"end\", onEnd)\n req.off(\"error\", onError)\n req.off(\"aborted\", onAborted)\n if (throttleTimeout) {\n clearTimeout(throttleTimeout)\n throttleTimeout = null\n }\n }\n\n const finish = (error?: unknown) => {\n if (done) return\n done = true\n\n cleanup()\n\n if (error) {\n next(error)\n return\n }\n\n req.body = Buffer.concat(chunks, total)\n next()\n }\n\n const onData = (chunk: any) => {\n if (done) return\n const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)\n total += buffer.length\n\n if (total > limitBytes) {\n done = true\n cleanup()\n req.destroy()\n res.status(413).json({ ok: false, error: \"chunk_too_large\" })\n return\n }\n\n chunks.push(buffer)\n\n if (!rateBytesPerSecond) return\n\n const now = Date.now()\n const clientKey = getClientKey(req)\n const state = getClientRateState(clientKey, rateBytesPerSecond, now)\n const waitMs = consumeRateBudget(state, buffer.length, rateBytesPerSecond, now)\n\n if (waitMs > 0 && !paused) {\n paused = true\n req.pause()\n throttleTimeout = setTimeout(() => {\n throttleTimeout = null\n paused = false\n if (done) return\n try {\n req.resume()\n } catch {\n //\n }\n }, waitMs)\n }\n }\n\n const onEnd = () => finish()\n const onError = (err: unknown) => finish(err)\n const onAborted = () => finish(new Error(\"request_aborted\"))\n\n req.on(\"data\", onData)\n req.on(\"end\", onEnd)\n req.on(\"error\", onError)\n req.on(\"aborted\", onAborted)\n }\n}\n\ntype ClientRateState = {\n tokens: number\n lastRefillMs: number\n lastSeenMs: number\n}\n\nconst MAX_BURST_SECONDS = 1\nconst STALE_CLIENT_MS = 15 * 60 * 1000\n\nconst clientRateStates = new Map<string, ClientRateState>()\nlet lastCleanupMs = 0\n\nconst getClientKey = (req: any): string => {\n const rawClientIp = typeof req?.clientIp === \"string\" ? req.clientIp : \"\"\n if (rawClientIp.trim()) return rawClientIp.trim()\n const rawIp = typeof req?.ip === \"string\" ? req.ip : \"\"\n return rawIp.trim() || \"unknown\"\n}\n\nconst maybeCleanupStates = (now: number) => {\n if (now - lastCleanupMs < 60_000) return\n lastCleanupMs = now\n\n if (clientRateStates.size < 2000) return\n\n for (const [key, state] of clientRateStates) {\n if (now - state.lastSeenMs > STALE_CLIENT_MS) {\n clientRateStates.delete(key)\n }\n }\n}\n\nconst getClientRateState = (key: string, rateBytesPerSecond: number, now: number): ClientRateState => {\n maybeCleanupStates(now)\n\n const capacity = rateBytesPerSecond * MAX_BURST_SECONDS\n const existing = clientRateStates.get(key)\n if (existing) {\n existing.lastSeenMs = now\n existing.tokens = Math.min(capacity, existing.tokens)\n return existing\n }\n\n const next: ClientRateState = {\n tokens: capacity,\n lastRefillMs: now,\n lastSeenMs: now,\n }\n clientRateStates.set(key, next)\n return next\n}\n\nconst consumeRateBudget = (\n state: ClientRateState,\n bytes: number,\n rateBytesPerSecond: number,\n now: number,\n): number => {\n const capacity = rateBytesPerSecond * MAX_BURST_SECONDS\n const elapsedMs = Math.max(0, now - state.lastRefillMs)\n\n if (elapsedMs > 0) {\n state.tokens = Math.min(capacity, state.tokens + (elapsedMs * rateBytesPerSecond) / 1000)\n state.lastRefillMs = now\n }\n\n state.tokens -= bytes\n\n if (state.tokens >= 0) return 0\n return Math.ceil((-state.tokens / rateBytesPerSecond) * 1000)\n}\n","import { Api } from \"@rpcbase/api\"\n\nimport { completeUpload } from \"./handlers/completeUpload\"\nimport { getStatus } from \"./handlers/getStatus\"\nimport { initUpload } from \"./handlers/initUpload\"\nimport { uploadChunk } from \"./handlers/uploadChunk\"\nimport { rawBodyParser } from \"./middleware/rawBodyParser\"\nimport { getChunkSizeBytes, getMaxClientUploadBytesPerSecond, getRawBodyLimitBytes, type SessionUser } from \"./shared\"\n\nimport * as Uploads from \"./index\"\n\n\nexport default (api: Api<SessionUser>) => {\n const chunkSizeBytes = getChunkSizeBytes()\n api.use(\n Uploads.InitRoute,\n rawBodyParser({\n limitBytes: getRawBodyLimitBytes(chunkSizeBytes),\n maxClientBytesPerSecond: getMaxClientUploadBytesPerSecond(),\n }),\n )\n\n api.post(Uploads.InitRoute, initUpload)\n api.put(Uploads.ChunkRoute, uploadChunk)\n api.get(Uploads.StatusRoute, getStatus)\n api.post(Uploads.CompleteRoute, completeUpload)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { a as object, i as number, n as array, r as boolean, s as string, t as _enum } from "./schemas-B2fUvgYo.js";
|
|
2
|
+
import { ZRBRtsChangeOp, models } from "@rpcbase/db";
|
|
3
|
+
import { buildAbilityFromSession } from "@rpcbase/db/acl";
|
|
4
|
+
//#region src/rts/api/changes/index.ts
|
|
5
|
+
var Route = "/api/rb/rts/changes";
|
|
6
|
+
var requestSchema = object({
|
|
7
|
+
sinceSeq: number().int().min(0).default(0),
|
|
8
|
+
limit: number().int().min(1).max(5e3).default(2e3),
|
|
9
|
+
modelNames: array(string().min(1)).optional()
|
|
10
|
+
});
|
|
11
|
+
object({
|
|
12
|
+
ok: boolean(),
|
|
13
|
+
needsFullResync: boolean().optional(),
|
|
14
|
+
earliestSeq: number().int().min(0).optional(),
|
|
15
|
+
latestSeq: number().int().min(0),
|
|
16
|
+
changes: array(object({
|
|
17
|
+
seq: number().int().min(1),
|
|
18
|
+
modelName: string().min(1),
|
|
19
|
+
op: _enum(["delete", "reset_model"]),
|
|
20
|
+
docId: string().optional()
|
|
21
|
+
}))
|
|
22
|
+
});
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/rts/api/changes/handler.ts
|
|
25
|
+
var getTenantId = (ctx) => {
|
|
26
|
+
const sessionTenantId = ctx.req.session?.user?.currentTenantId;
|
|
27
|
+
if (typeof sessionTenantId === "string" && sessionTenantId.trim()) return sessionTenantId.trim();
|
|
28
|
+
return null;
|
|
29
|
+
};
|
|
30
|
+
var ensureAuthorized = (ctx, tenantId) => {
|
|
31
|
+
const userId = ctx.req.session?.user?.id;
|
|
32
|
+
if (!userId) return null;
|
|
33
|
+
const currentTenantId = ctx.req.session?.user?.currentTenantId;
|
|
34
|
+
const normalizedCurrentTenantId = typeof currentTenantId === "string" ? currentTenantId.trim() : "";
|
|
35
|
+
if (!normalizedCurrentTenantId || normalizedCurrentTenantId !== tenantId) return null;
|
|
36
|
+
return userId;
|
|
37
|
+
};
|
|
38
|
+
var getModelCtx = (_ctx, tenantId) => ({ req: { session: { user: { currentTenantId: tenantId } } } });
|
|
39
|
+
var isRtsChangeRecord = (value) => {
|
|
40
|
+
if (!value || typeof value !== "object") return false;
|
|
41
|
+
const obj = value;
|
|
42
|
+
const isOp = ZRBRtsChangeOp.safeParse(obj.op).success;
|
|
43
|
+
return typeof obj.seq === "number" && typeof obj.modelName === "string" && isOp;
|
|
44
|
+
};
|
|
45
|
+
var changesHandler = async (payload, ctx) => {
|
|
46
|
+
const parsed = requestSchema.safeParse(payload ?? {});
|
|
47
|
+
if (!parsed.success) {
|
|
48
|
+
ctx.res.status(400);
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
latestSeq: 0,
|
|
52
|
+
changes: []
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const tenantId = getTenantId(ctx);
|
|
56
|
+
if (!tenantId) {
|
|
57
|
+
ctx.res.status(400);
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
latestSeq: 0,
|
|
61
|
+
changes: []
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (!ensureAuthorized(ctx, tenantId)) {
|
|
65
|
+
ctx.res.status(401);
|
|
66
|
+
return {
|
|
67
|
+
ok: false,
|
|
68
|
+
latestSeq: 0,
|
|
69
|
+
changes: []
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const ability = buildAbilityFromSession({
|
|
73
|
+
tenantId,
|
|
74
|
+
session: ctx.req.session
|
|
75
|
+
});
|
|
76
|
+
const modelCtx = getModelCtx(ctx, tenantId);
|
|
77
|
+
const [RtsChange, RtsCounter] = await Promise.all([models.get("RBRtsChange", modelCtx), models.get("RBRtsCounter", modelCtx)]);
|
|
78
|
+
const counter = await RtsCounter.findOne({ _id: "rts" }, { seq: 1 }).lean();
|
|
79
|
+
const latestSeq = Number(counter?.seq ?? 0) || 0;
|
|
80
|
+
const { sinceSeq, limit, modelNames } = parsed.data;
|
|
81
|
+
const requestedModelNames = Array.isArray(modelNames) && modelNames.length ? modelNames.map((m) => String(m)).filter(Boolean) : null;
|
|
82
|
+
const allowedModelNames = requestedModelNames ? requestedModelNames.filter((m) => ability.can("read", m)) : Array.from(new Set((await RtsChange.distinct("modelName")).map((m) => String(m)).filter(Boolean).filter((m) => ability.can("read", m))));
|
|
83
|
+
let earliestSeq;
|
|
84
|
+
if (allowedModelNames.length) {
|
|
85
|
+
const earliest = await RtsChange.findOne({ modelName: { $in: allowedModelNames } }, { seq: 1 }).sort({ seq: 1 }).lean();
|
|
86
|
+
earliestSeq = earliest?.seq ? Number(earliest.seq) : void 0;
|
|
87
|
+
}
|
|
88
|
+
const needsFullResync = typeof earliestSeq === "number" && sinceSeq < earliestSeq - 1;
|
|
89
|
+
const selector = {
|
|
90
|
+
seq: { $gt: sinceSeq },
|
|
91
|
+
modelName: { $in: allowedModelNames }
|
|
92
|
+
};
|
|
93
|
+
const changes = await RtsChange.find(selector, {
|
|
94
|
+
_id: 0,
|
|
95
|
+
seq: 1,
|
|
96
|
+
modelName: 1,
|
|
97
|
+
op: 1,
|
|
98
|
+
docId: 1
|
|
99
|
+
}).sort({ seq: 1 }).limit(limit).lean();
|
|
100
|
+
return {
|
|
101
|
+
ok: true,
|
|
102
|
+
needsFullResync: needsFullResync || void 0,
|
|
103
|
+
earliestSeq,
|
|
104
|
+
latestSeq,
|
|
105
|
+
changes: Array.isArray(changes) ? changes.filter(isRtsChangeRecord).filter((c) => ability.can("read", c.modelName)).map((c) => ({
|
|
106
|
+
seq: Number(c.seq),
|
|
107
|
+
modelName: String(c.modelName),
|
|
108
|
+
op: c.op,
|
|
109
|
+
docId: c.docId ? String(c.docId) : void 0
|
|
110
|
+
})) : []
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
var handler_default = (api) => {
|
|
114
|
+
api.post(Route, changesHandler);
|
|
115
|
+
};
|
|
116
|
+
//#endregion
|
|
117
|
+
export { handler_default as default };
|
|
118
|
+
|
|
119
|
+
//# sourceMappingURL=handler-Fk-5fOSm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler-Fk-5fOSm.js","names":["z","Route","requestSchema","object","sinceSeq","number","int","min","default","limit","max","modelNames","array","string","optional","RequestPayload","infer","responseSchema","ok","boolean","needsFullResync","earliestSeq","latestSeq","changes","seq","modelName","op","enum","docId","ResponsePayload","Api","ApiHandler","Ctx","models","ZRBRtsChangeOp","IRBRtsChange","IRBRtsCounter","LoadModelCtx","buildAbilityFromSession","AclSubjectType","Model","Changes","SessionUser","id","currentTenantId","signedInTenants","RtsCounterDoc","RtsChangeDoc","getTenantId","ctx","sessionTenantId","req","session","user","trim","ensureAuthorized","tenantId","userId","normalizedCurrentTenantId","getModelCtx","_ctx","isRtsChangeRecord","value","obj","Partial","isOp","safeParse","op","success","seq","modelName","changesHandler","RequestPayload","ResponsePayload","payload","Promise","parsed","requestSchema","res","status","ok","latestSeq","changes","ability","modelCtx","RtsChange","RtsCounter","all","get","counter","findOne","_id","lean","Number","sinceSeq","limit","modelNames","data","requestedModelNames","Array","isArray","length","map","m","String","filter","Boolean","allowedModelNames","can","from","Set","distinct","earliestSeq","earliest","$in","sort","undefined","needsFullResync","selector","Record","$gt","find","docId","c","api","post","Route"],"sources":["../src/rts/api/changes/index.ts","../src/rts/api/changes/handler.ts"],"sourcesContent":["import { z } from \"zod\"\n\n\nexport const Route = \"/api/rb/rts/changes\"\n\nexport const requestSchema = z.object({\n sinceSeq: z.number().int().min(0).default(0),\n limit: z.number().int().min(1).max(5000).default(2000),\n modelNames: z.array(z.string().min(1)).optional(),\n})\n\nexport type RequestPayload = z.infer<typeof requestSchema>\n\nexport const responseSchema = z.object({\n ok: z.boolean(),\n needsFullResync: z.boolean().optional(),\n earliestSeq: z.number().int().min(0).optional(),\n latestSeq: z.number().int().min(0),\n changes: z.array(z.object({\n seq: z.number().int().min(1),\n modelName: z.string().min(1),\n op: z.enum([\"delete\", \"reset_model\"]),\n docId: z.string().optional(),\n })),\n})\n\nexport type ResponsePayload = z.infer<typeof responseSchema>\n\n","import { Api, ApiHandler, Ctx } from \"@rpcbase/api\"\nimport { models, ZRBRtsChangeOp, type IRBRtsChange, type IRBRtsCounter, type LoadModelCtx } from \"@rpcbase/db\"\nimport { buildAbilityFromSession, type AclSubjectType } from \"@rpcbase/db/acl\"\nimport type { Model } from \"mongoose\"\n\nimport * as Changes from \"./index\"\n\n\ntype SessionUser = {\n id?: string\n currentTenantId?: string\n signedInTenants?: string[]\n}\n\ntype RtsCounterDoc = IRBRtsCounter\ntype RtsChangeDoc = IRBRtsChange\n\nconst getTenantId = (ctx: Ctx<SessionUser>): string | null => {\n const sessionTenantId = ctx.req.session?.user?.currentTenantId\n if (typeof sessionTenantId === \"string\" && sessionTenantId.trim()) return sessionTenantId.trim()\n\n return null\n}\n\nconst ensureAuthorized = (ctx: Ctx<SessionUser>, tenantId: string): string | null => {\n const userId = ctx.req.session?.user?.id\n if (!userId) return null\n\n const currentTenantId = ctx.req.session?.user?.currentTenantId\n const normalizedCurrentTenantId = typeof currentTenantId === \"string\" ? currentTenantId.trim() : \"\"\n\n if (!normalizedCurrentTenantId || normalizedCurrentTenantId !== tenantId) return null\n return userId\n}\n\nconst getModelCtx = (_ctx: Ctx<SessionUser>, tenantId: string): LoadModelCtx => ({\n req: {\n session: {\n user: {\n currentTenantId: tenantId,\n },\n },\n },\n})\n\nconst isRtsChangeRecord = (value: unknown): value is RtsChangeDoc => {\n if (!value || typeof value !== \"object\") return false\n const obj = value as Partial<RtsChangeDoc>\n const isOp = ZRBRtsChangeOp.safeParse(obj.op).success\n return typeof obj.seq === \"number\" && typeof obj.modelName === \"string\" && isOp\n}\n\nconst changesHandler: ApiHandler<Changes.RequestPayload, Changes.ResponsePayload, SessionUser> = async(\n payload,\n ctx,\n): Promise<Changes.ResponsePayload> => {\n const parsed = Changes.requestSchema.safeParse(payload ?? {})\n if (!parsed.success) {\n ctx.res.status(400)\n return { ok: false, latestSeq: 0, changes: [] }\n }\n\n const tenantId = getTenantId(ctx)\n if (!tenantId) {\n ctx.res.status(400)\n return { ok: false, latestSeq: 0, changes: [] }\n }\n\n const userId = ensureAuthorized(ctx, tenantId)\n if (!userId) {\n ctx.res.status(401)\n return { ok: false, latestSeq: 0, changes: [] }\n }\n\n const ability = buildAbilityFromSession({ tenantId, session: ctx.req.session })\n\n const modelCtx = getModelCtx(ctx, tenantId)\n\n const [RtsChange, RtsCounter] = await Promise.all([\n models.get(\"RBRtsChange\", modelCtx) as Promise<Model<RtsChangeDoc>>,\n models.get(\"RBRtsCounter\", modelCtx) as Promise<Model<RtsCounterDoc>>,\n ])\n\n const counter = await RtsCounter.findOne({ _id: \"rts\" }, { seq: 1 }).lean()\n const latestSeq = Number(counter?.seq ?? 0) || 0\n\n const { sinceSeq, limit, modelNames } = parsed.data\n\n const requestedModelNames = Array.isArray(modelNames) && modelNames.length\n ? modelNames.map((m) => String(m)).filter(Boolean)\n : null\n\n const allowedModelNames = requestedModelNames\n ? requestedModelNames.filter((m) => ability.can(\"read\", m as AclSubjectType))\n : Array.from(\n new Set(\n (await RtsChange.distinct(\"modelName\"))\n .map((m) => String(m))\n .filter(Boolean)\n .filter((m) => ability.can(\"read\", m as AclSubjectType)),\n ),\n )\n\n let earliestSeq: number | undefined\n if (allowedModelNames.length) {\n const earliest = await RtsChange.findOne({ modelName: { $in: allowedModelNames } }, { seq: 1 }).sort({ seq: 1 }).lean()\n earliestSeq = earliest?.seq ? Number(earliest.seq) : undefined\n }\n\n const needsFullResync = typeof earliestSeq === \"number\" && sinceSeq < earliestSeq - 1\n\n const selector: Record<string, unknown> = { seq: { $gt: sinceSeq }, modelName: { $in: allowedModelNames } }\n\n const changes = await RtsChange\n .find(selector, { _id: 0, seq: 1, modelName: 1, op: 1, docId: 1 })\n .sort({ seq: 1 })\n .limit(limit)\n .lean()\n\n return {\n ok: true,\n needsFullResync: needsFullResync || undefined,\n earliestSeq,\n latestSeq,\n changes: Array.isArray(changes)\n ? changes\n .filter(isRtsChangeRecord)\n .filter((c) => ability.can(\"read\", c.modelName as AclSubjectType))\n .map((c) => ({\n seq: Number(c.seq),\n modelName: String(c.modelName),\n op: c.op,\n docId: c.docId ? String(c.docId) : undefined,\n }))\n : [],\n }\n}\n\nexport default (api: Api<SessionUser>) => {\n api.post(Changes.Route, changesHandler)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type HtmlOutlets = {
|
|
2
|
+
head?: string;
|
|
3
|
+
htmlAttributes?: {
|
|
4
|
+
lang?: string;
|
|
5
|
+
dir?: "ltr" | "rtl" | "auto";
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
export type CompiledHtmlTemplate = {
|
|
9
|
+
isLegacy: boolean;
|
|
10
|
+
render: (outlets?: HtmlOutlets) => {
|
|
11
|
+
htmlStart: string;
|
|
12
|
+
htmlEnd: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export declare const compileHtmlTemplate: (template: string) => CompiledHtmlTemplate;
|
|
16
|
+
//# sourceMappingURL=htmlTemplate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"htmlTemplate.d.ts","sourceRoot":"","sources":["../src/htmlTemplate.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,cAAc,CAAC,EAAE;QACf,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAA;KAC7B,CAAA;CACF,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,WAAW,KAAK;QACjC,SAAS,EAAE,MAAM,CAAA;QACjB,OAAO,EAAE,MAAM,CAAA;KAChB,CAAA;CACF,CAAA;AAgPD,eAAO,MAAM,mBAAmB,GAAI,UAAU,MAAM,KAAG,oBA+EtD,CAAA"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,SAAS,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,iBAAiB,CAAA;AAC/B,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACjD,cAAc,SAAS,CAAA"}
|