@rpcbase/server 0.486.0 → 0.487.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.
@@ -104,3 +104,4 @@ export {
104
104
  normalizeSha256Hex as n,
105
105
  toBufferPayload as t
106
106
  };
107
+ //# sourceMappingURL=shared-BJomDDWK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-BJomDDWK.js","sources":["../src/uploads/api/file-uploads/shared.ts"],"sourcesContent":["import { createHash, timingSafeEqual } from \"node:crypto\"\n\nimport { Ctx } from \"@rpcbase/api\"\nimport {\n type IRBUploadChunk,\n type IRBUploadSession,\n type LoadModelCtx,\n} from \"@rpcbase/db\"\nimport { buildAbilityFromSession, getAccessibleByQuery, type AppAbility } from \"@rpcbase/db/acl\"\nimport type { Model } from \"mongoose\"\n\n\nexport type SessionUser = {\n id?: string\n currentTenantId?: string\n}\n\nexport type UploadSessionDoc = IRBUploadSession\nexport type UploadChunkDoc = Omit<IRBUploadChunk, \"data\"> & { data: Buffer }\n\nconst DEFAULT_CHUNK_SIZE_BYTES = 5 * 1024 * 1024\nconst MAX_CHUNK_SIZE_BYTES = 15 * 1024 * 1024\n\nconst DEFAULT_MAX_CLIENT_BYTES_PER_SECOND = 10 * 1024 * 1024\n\nconst DEFAULT_SESSION_TTL_S = 60 * 60 * 24\n\nconst ensuredIndexDbNames = new Set<string>()\n\nconst parseOptionalPositiveInt = (rawValue: unknown): number | null => {\n if (typeof rawValue !== \"string\") return null\n const normalized = rawValue.trim()\n if (!normalized) return null\n const parsed = Number(normalized)\n if (!Number.isFinite(parsed) || parsed <= 0) return null\n return Math.floor(parsed)\n}\n\nexport const getChunkSizeBytes = (): number => {\n const configured = parseOptionalPositiveInt(process.env.RB_UPLOAD_CHUNK_SIZE_BYTES)\n const resolved = configured ?? DEFAULT_CHUNK_SIZE_BYTES\n return Math.min(MAX_CHUNK_SIZE_BYTES, resolved)\n}\n\nexport const getMaxClientUploadBytesPerSecond = (): number | null => {\n const configured = parseOptionalPositiveInt(process.env.RB_UPLOAD_MAX_CLIENT_BYTES_PER_SECOND)\n return configured ?? DEFAULT_MAX_CLIENT_BYTES_PER_SECOND\n}\n\nexport const getSessionTtlMs = (): number => {\n const ttlSeconds = parseOptionalPositiveInt(process.env.RB_UPLOAD_SESSION_TTL_S) ?? DEFAULT_SESSION_TTL_S\n return ttlSeconds * 1000\n}\n\nexport const getRawBodyLimitBytes = (chunkSizeBytes: number): number => chunkSizeBytes + 1024 * 1024\n\nexport const getBucketName = (): string => (process.env.RB_FILESYSTEM_BUCKET_NAME ?? \"\").trim() || \"fs\"\n\nexport const getUserId = (ctx: Ctx<SessionUser>): string | null => {\n const raw = ctx.req.session?.user?.id\n if (typeof raw !== \"string\") return null\n const normalized = raw.trim()\n return normalized ? normalized : null\n}\n\nexport const getTenantId = (ctx: Ctx<SessionUser>): string | null => {\n const rawSession = ctx.req.session?.user?.currentTenantId\n const sessionTenantId = typeof rawSession === \"string\" ? rawSession.trim() : \"\"\n\n const userId = getUserId(ctx)\n const rawQuery = ctx.req.query?.[\"rb-tenant-id\"]\n const queryTenantId = Array.isArray(rawQuery) ? rawQuery[0] : rawQuery\n const queryValue = typeof queryTenantId === \"string\" && queryTenantId.trim() ? queryTenantId.trim() : null\n\n if (!userId && queryValue) return queryValue\n\n if (userId) return sessionTenantId || null\n\n if (sessionTenantId) return sessionTenantId\n\n return queryValue\n}\n\nexport const computeSha256Hex = (data: Buffer): string => createHash(\"sha256\").update(data).digest(\"hex\")\n\nexport const normalizeSha256Hex = (value: string): string => value.trim().toLowerCase()\n\nexport const getModelCtx = (_ctx: Ctx<SessionUser>, tenantId: string): LoadModelCtx => ({\n req: {\n session: {\n user: {\n currentTenantId: tenantId,\n },\n },\n },\n})\n\nexport const toBufferPayload = (payload: unknown): Buffer | null => {\n if (Buffer.isBuffer(payload)) return payload\n if (payload instanceof Uint8Array) return Buffer.from(payload)\n return null\n}\n\nexport const ensureUploadIndexes = async (\n UploadSession: Model<UploadSessionDoc>,\n UploadChunk: Model<UploadChunkDoc>,\n): Promise<void> => {\n const dbName = String((UploadSession as unknown as { db?: { name?: unknown } })?.db?.name ?? \"\")\n if (dbName && ensuredIndexDbNames.has(dbName)) return\n\n await Promise.all([\n UploadSession.createIndexes(),\n UploadChunk.createIndexes(),\n ])\n\n if (dbName) ensuredIndexDbNames.add(dbName)\n}\n\nconst normalizeUploadKey = (raw: unknown): string | null => {\n if (typeof raw !== \"string\") return null\n const normalized = raw.trim()\n return normalized ? normalized : null\n}\n\nexport const getUploadKeyHash = (ctx: Ctx<SessionUser>): string | null => {\n const uploadKey = normalizeUploadKey(ctx.req.get(\"X-Upload-Key\"))\n if (!uploadKey) return null\n return computeSha256Hex(Buffer.from(uploadKey))\n}\n\nexport const buildUploadsAbility = (ctx: Ctx<SessionUser>, tenantId: string): AppAbility => {\n const uploadKeyHash = getUploadKeyHash(ctx)\n const claims = uploadKeyHash ? { uploadKeyHash } : undefined\n return buildAbilityFromSession({ tenantId, session: ctx.req.session, claims })\n}\n\nexport const getUploadSessionAccessQuery = (\n ability: AppAbility,\n action: \"read\" | \"update\" | \"delete\",\n): Record<string, unknown> => getAccessibleByQuery(ability, action, \"RBUploadSession\")\n\nconst timingSafeEqualHex = (left: string, right: string): boolean => {\n if (left.length !== right.length) return false\n try {\n return timingSafeEqual(Buffer.from(left, \"hex\"), Buffer.from(right, \"hex\"))\n } catch {\n return false\n }\n}\n\nexport const getOwnershipSelector = (\n ctx: Ctx<SessionUser>,\n session: Pick<UploadSessionDoc, \"userId\" | \"ownerKeyHash\">,\n): { userId?: string; ownerKeyHash?: string } | null => {\n if (session.userId) {\n const userId = getUserId(ctx)\n if (!userId || userId !== session.userId) return null\n return { userId: session.userId }\n }\n\n if (session.ownerKeyHash) {\n const uploadKeyHash = getUploadKeyHash(ctx)\n if (!uploadKeyHash) return null\n if (!timingSafeEqualHex(session.ownerKeyHash, uploadKeyHash)) return null\n return { ownerKeyHash: session.ownerKeyHash }\n }\n\n return null\n}\n"],"names":[],"mappings":"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"}
package/dist/uploads.js CHANGED
@@ -7,3 +7,4 @@ const routes = Object.entries({
7
7
  export {
8
8
  routes
9
9
  };
10
+ //# sourceMappingURL=uploads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploads.js","sources":["../src/uploads/routes.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/uploads/api/\")] = mod\n return acc\n}, {})\n\n"],"names":[],"mappings":"AAAO,MAAM,SAAS,OAAO,QAAQ;AAAA,EACnC,GAAG,uBAAA,OAAA,EAAA,iCAAA,MAAA,OAAA,uBAAA,GAAA,0BAAA,MAAA,OAAA,uBAAA,EAAA,CAAA;AACL,CAAC,EAAE,OAAgC,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM;AACvD,MAAI,KAAK,QAAQ,UAAU,8BAA8B,CAAC,IAAI;AAC9D,SAAO;AACT,GAAG,CAAA,CAAE;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpcbase/server",
3
- "version": "0.486.0",
3
+ "version": "0.487.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"