All files d1-sql.ts

100% Statements 11/11
100% Branches 2/2
100% Functions 8/8
100% Lines 11/11

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90                                        198x 1x           197x     10x 197x             197x             197x                     197x                 197x         197x           197x                          
/**
 * Validates that a table name is safe to interpolate into SQL.
 *
 * Allowed characters:
 *   - A–Z
 *   - a–z
 *   - 0–9
 *   - _ (underscore)
 *
 * Disallowed:
 *   - spaces
 *   - punctuation or symbols (.-;:!?@#$% etc.)
 *   - quotes (" ' `)
 *   - SQL injection attempts
 *   - Unicode characters (emoji, CJK, etc.)
 *
 * This ensures the table name can be safely used as a SQL identifier
 * without quoting or escaping.
 */
export function sanitizeTableName(name: string): string {
    if (!/^[A-Za-z0-9_]+$/.test(name)) {
        throw new Error(
            `Invalid table name "${name}". ` +
            `Allowed characters: A-Z, a-z, 0-9, and _. ` +
            `No spaces, punctuation, unicode, or special symbols are permitted.`
        );
    }
    return name;
}
 
export const D1_SQL = {
    get: (table: string) => /* sql */ `
        SELECT value
        FROM ${table}
        WHERE key = ?
          AND (expiration IS NULL OR expiration > unixepoch())
    `,
 
    getWithMetadata: (table: string) => /* sql */ `
        SELECT value, metadata
        FROM ${table}
        WHERE key = ?
          AND (expiration IS NULL OR expiration > unixepoch())
    `,
 
    list: (table: string) => /* sql */ `
        SELECT key, expiration, metadata
        FROM ${table}
        WHERE (expiration IS NULL OR expiration > unixepoch())
          AND key >= ?
          AND key < ?
          AND key > ?
        ORDER BY key COLLATE BINARY
        LIMIT ?
    `,
 
    put: (table: string) => /* sql */ `
        INSERT INTO ${table} (key, value, expiration, metadata)
        VALUES (?, ?, ?, ?)
        ON CONFLICT(key) DO UPDATE SET
            value      = excluded.value,
            expiration = excluded.expiration,
            metadata   = excluded.metadata
    `,
 
    delete: (table: string) => /* sql */ `
        DELETE FROM ${table}
        WHERE key = ?
    `,
 
    deleteExpired: (table: string) => /* sql */ `
        DELETE FROM ${table}
        WHERE expiration IS NOT NULL
          AND expiration <= unixepoch()
    `,
 
    ensureTable: (table: string) => /* sql */ `
        CREATE TABLE IF NOT EXISTS ${table} (
            key        TEXT    NOT NULL COLLATE BINARY PRIMARY KEY,
            value      BLOB    NOT NULL,
            expiration INTEGER,
            metadata   BLOB
        ) WITHOUT ROWID;
 
        CREATE INDEX IF NOT EXISTS ${table}_exp_idx
            ON ${table}(expiration)
            WHERE expiration IS NOT NULL;
    `,
} as const;