|
| 1 | +import type { SqliteRuntimeDatabase } from "./sqlite-runtime"; |
| 2 | + |
| 3 | +// Keep these limits in sync with engine/packages/pegboard/src/actor_kv/mod.rs. |
| 4 | +const KV_MAX_KEY_SIZE = 2 * 1024; |
| 5 | +const KV_MAX_VALUE_SIZE = 128 * 1024; |
| 6 | +const KV_MAX_KEYS = 128; |
| 7 | +const KV_MAX_PUT_PAYLOAD_SIZE = 976 * 1024; |
| 8 | +const KV_MAX_STORAGE_SIZE = 1024 * 1024 * 1024; |
| 9 | +const KV_KEY_WRAPPER_OVERHEAD_SIZE = 2; |
| 10 | + |
| 11 | +export function estimateKvSize(db: SqliteRuntimeDatabase): number { |
| 12 | + const row = db.get<{ total: number | bigint | null }>( |
| 13 | + "SELECT COALESCE(SUM(LENGTH(key) + LENGTH(value)), 0) AS total FROM kv", |
| 14 | + ); |
| 15 | + return row ? Number(row.total ?? 0) : 0; |
| 16 | +} |
| 17 | + |
| 18 | +export function validateKvKey( |
| 19 | + key: Uint8Array, |
| 20 | + keyLabel: "key" | "prefix key" = "key", |
| 21 | +): void { |
| 22 | + if (key.byteLength + KV_KEY_WRAPPER_OVERHEAD_SIZE > KV_MAX_KEY_SIZE) { |
| 23 | + throw new Error(`${keyLabel} is too long (max 2048 bytes)`); |
| 24 | + } |
| 25 | +} |
| 26 | + |
| 27 | +export function validateKvKeys(keys: Uint8Array[]): void { |
| 28 | + if (keys.length > KV_MAX_KEYS) { |
| 29 | + throw new Error("a maximum of 128 keys is allowed"); |
| 30 | + } |
| 31 | + |
| 32 | + for (const key of keys) { |
| 33 | + validateKvKey(key); |
| 34 | + } |
| 35 | +} |
| 36 | + |
| 37 | +export function validateKvEntries( |
| 38 | + entries: [Uint8Array, Uint8Array][], |
| 39 | + totalSize: number, |
| 40 | +): void { |
| 41 | + if (entries.length > KV_MAX_KEYS) { |
| 42 | + throw new Error("A maximum of 128 key-value entries is allowed"); |
| 43 | + } |
| 44 | + |
| 45 | + let payloadSize = 0; |
| 46 | + for (const [key, value] of entries) { |
| 47 | + payloadSize += |
| 48 | + key.byteLength + KV_KEY_WRAPPER_OVERHEAD_SIZE + value.byteLength; |
| 49 | + } |
| 50 | + |
| 51 | + if (payloadSize > KV_MAX_PUT_PAYLOAD_SIZE) { |
| 52 | + throw new Error("total payload is too large (max 976 KiB)"); |
| 53 | + } |
| 54 | + |
| 55 | + const storageRemaining = Math.max(0, KV_MAX_STORAGE_SIZE - totalSize); |
| 56 | + if (payloadSize > storageRemaining) { |
| 57 | + throw new Error( |
| 58 | + `not enough space left in storage (${storageRemaining} bytes remaining, current payload is ${payloadSize} bytes)`, |
| 59 | + ); |
| 60 | + } |
| 61 | + |
| 62 | + for (const [key, value] of entries) { |
| 63 | + validateKvKey(key); |
| 64 | + if (value.byteLength > KV_MAX_VALUE_SIZE) { |
| 65 | + throw new Error( |
| 66 | + `value is too large (max ${KV_MAX_VALUE_SIZE / 1024} KiB)`, |
| 67 | + ); |
| 68 | + } |
| 69 | + } |
| 70 | +} |
0 commit comments