/*!
 * I'm Queue Software Project
 * Copyright (C) 2025  imqueue.com <support@imqueue.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * If you want to use this code in a closed source (commercial) project, you can
 * purchase a proprietary commercial license. Please contact us at
 * <support@imqueue.com> to get commercial licensing options.
 */
import { type PgCacheable } from './PgCache.js';
/**
 * Minimal logger interface accepted by this package. Structurally
 * compatible with the console object and with `@imqueue` loggers, so any of
 * them can be passed without depending on `@imqueue/core.`
 */
export interface ILogger {
    /** General-purpose message, equivalent to `console.log`. */
    log(...args: unknown[]): void;
    /**
     * Informational message. Used for cache hits, misses and trigger
     * installation, and only emitted when `PG_CACHE_DEBUG` is on.
     */
    info(...args: unknown[]): void;
    /**
     * Recoverable problem. Every swallowed cache error is reported at this
     * level, so a redis outage shows up here rather than as a thrown error.
     */
    warn(...args: unknown[]): void;
    /** Unrecoverable problem. */
    error(...args: unknown[]): void;
}
/**
 * A dual-mode class decorator: called as `(constructor)` by legacy
 * (`experimentalDecorators`) TypeScript and as `(value, context)` by standard
 * (TC39) decorators. In both forms the first argument is the class, and the
 * result is the class augmented with {@link PgCacheable}.
 *
 * Supporting both is what lets this package decorate `@imqueue` services compiled
 * in either mode, the same way `@imqueue/rpc` and `@imqueue/core` decorators do.
 */
export type ClassDecorator = <T extends new (...args: any[]) => {}>(constructor: T, context?: unknown) => T & PgCacheable;
/**
 * A dual-mode method decorator: called as `(target, propertyKey, descriptor)` by
 * legacy (`experimentalDecorators`) TypeScript and as `(value, context)` by
 * standard (TC39) decorators.
 *
 * Use {@link isStandardDecorator} on the second argument to tell the two apart.
 */
export type MethodDecorator = (target: any, context: any, descriptor?: TypedPropertyDescriptor<(...args: any[]) => any>) => any;
/**
 * Returns true if the decorator was invoked in standard (TC39) mode, i.e.
 * its second argument is a decorator context object carrying a `kind`.
 *
 * @param context - the decorator's second argument
 */
export declare function isStandardDecorator(context: unknown): boolean;
/**
 * Walks up from a constructed instance to the prototype that actually
 * declares the given method, mirroring legacy decoration where the decorator
 * target is the declaring prototype. Falls back to the instance's own
 * prototype.
 *
 * @param instance - `this` inside a standard decorator initializer
 * @param methodName - method to locate on the prototype chain
 * @returns the declaring prototype
 */
export declare function declaringPrototype(instance: any, methodName: string): any;
/**
 * Registers pg-cache channel entries for a method on the given prototype
 * exactly once, even when called from a per-construction initializer.
 *
 * @param proto - declaring prototype to attach channel metadata to
 * @param methodName - decorated method name (dedup key)
 * @param register - pushes entries
 */
export declare function registerChannelsOnce(proto: any, methodName: string, register: (channels: Record<string, unknown[]>) => void): void;
/**
 * Default lifetime of a cached entry, in milliseconds — 24 hours.
 *
 * A TTL is a backstop, not the primary invalidation mechanism: entries are
 * normally dropped by a PostgreSQL change notification long before it expires.
 * It exists so an entry cannot outlive its data indefinitely if a notification
 * is ever missed.
 */
export declare const DEFAULT_CACHE_TTL = 86400000;
/**
 * Reads a boolean environment variable, accepting the human-friendly
 * spellings 1/true/yes/on and 0/false/no/off (case-insensitive). The
 * previous `!!+value` idiom parsed values like `true` as NaN, i.e. `false`.
 *
 * @param name - environment variable name
 * @param defaultValue - used when unset or unrecognized
 */
export declare function envBool(name: string, defaultValue?: boolean): boolean;
/**
 * Whether verbose cache tracing is on, read once from the `PG_CACHE_DEBUG`
 * environment variable at import time.
 *
 * When enabled, cache saves, fetches and trigger installation are logged at info
 * level. Warnings are logged regardless. Because it is read at import time,
 * changing the variable afterwards has no effect.
 *
 * @see {@link envBool} for the accepted spellings
 */
export declare const PG_CACHE_DEBUG: boolean;
/**
 * Default PL/pgSQL trigger function installed on every watched table.
 *
 * It builds a JSON payload of the changed row and issues `PG_NOTIFY` on a channel
 * named after the table. The payload shape is {@link ChannelPayload}: timestamp,
 * operation, schema, table and the row itself — `NEW` for inserts and updates,
 * `OLD` for deletes.
 *
 * Column values are read out of `information_schema` and cast to TEXT, so every
 * field arrives as a string regardless of its SQL type.
 *
 * Note PostgreSQL caps a NOTIFY payload at 8000 bytes; a change to a very wide
 * row can exceed that and the notification will be rejected. Override with
 * `PgCacheOptions.triggerDefinition` if the default does not suit — see
 * {@link PgCacheOptions}.
 */
export declare const PG_CACHE_TRIGGER = "CREATE FUNCTION post_change_notify_trigger()\nRETURNS TRIGGER\nLANGUAGE plpgsql\nAS $$\nDECLARE\n    rec RECORD;\n    payload TEXT;\n    payload_items TEXT[];\n    column_names TEXT[];\n    column_name TEXT;\n    column_value TEXT;\n    channel CHARACTER VARYING(255);\nBEGIN\n    channel := TG_TABLE_NAME;\n\n    CASE TG_OP\n        WHEN 'INSERT', 'UPDATE' THEN rec := NEW;\n        WHEN 'DELETE' THEN rec := OLD;\n        ELSE RAISE EXCEPTION 'NOTIFY: Invalid operation \"%\"!',\n            TG_OP;\n    END CASE;\n\n    SELECT array_agg(\"c\".\"column_name\"::TEXT)\n    INTO column_names\n    FROM \"information_schema\".\"columns\" AS \"c\"\n    WHERE \"c\".\"table_name\" = TG_TABLE_NAME;\n\n    FOREACH column_name IN ARRAY column_names\n    LOOP\n        EXECUTE FORMAT('SELECT $1.%I::TEXT', column_name)\n            INTO column_value\n            USING rec;\n\n        payload_items := ARRAY_CAT(\n            payload_items,\n            ARRAY [column_name, column_value]\n        );\n    END LOOP;\n\n    payload := json_build_object(\n        'timestamp', CURRENT_TIMESTAMP,\n        'operation', TG_OP,\n        'schema', TG_TABLE_SCHEMA,\n        'table', TG_TABLE_NAME,\n        'record', TO_JSON(JSON_OBJECT(payload_items))\n    );\n\n    PERFORM PG_NOTIFY(channel, payload);\n\n    RETURN rec;\nEND;\n$$;\n";
/**
 * Reports a successful cache write and passes the value straight through, so it
 * can be used inline in a return position. Logs only when
 * {@link PG_CACHE_DEBUG} is on.
 *
 * @param logger - logger to report through
 * @param res - value that was cached; returned unchanged
 * @param key - redis key it was stored under
 * @param decorator - decorator that performed the write, named in the message
 * @returns `res`, unchanged
 */
export declare function setInfo(logger: ILogger, res: any, key: string, decorator: Function): any;
/**
 * Reports a failed cache write at warning level. Always logs: a write failure
 * matters even when tracing is off.
 *
 * @param logger - logger to report through
 * @param err - error redis raised
 * @param key - redis key the write targeted
 * @param decorator - decorator that attempted the write
 */
export declare function setError(logger: ILogger, err: any, key: string, decorator: Function): void;
/**
 * Reports a failed cache read at warning level. The caller then falls through to
 * the real method, so a read failure costs latency rather than correctness.
 *
 * @param logger - logger to report through
 * @param err - error redis raised
 * @param key - redis key the read targeted
 * @param decorator - decorator that attempted the read
 */
export declare function fetchError(logger: ILogger, err: any, key: string, decorator: Function): void;
/**
 * Reports that a cached method ran while the cache was absent — either `start()`
 * has not completed, or invalidation could not be established and caching was
 * therefore left off. The method still executes; it is simply not cached.
 *
 * @param logger - logger to report through
 * @param className - service class whose cache is missing
 * @param methodName - cached method that was called too early
 * @param decorator - decorator that found the cache absent
 */
export declare function initError(logger: ILogger, className: string, methodName: string, decorator: Function): void;
/**
 * Default time `start()` waits for the change-notify triggers and the channel
 * subscriptions to be confirmed, in milliseconds.
 *
 * @remarks
 * Reaching it means the database accepted a connection but the invalidation
 * setup never finished, which is a broken deployment rather than a slow one:
 * long enough not to trip on a loaded database, short enough that a service
 * cannot sit in `start()` indefinitely.
 */
export declare const DEFAULT_INVALIDATION_TIMEOUT = 30000;
/**
 * Waits for invalidation to be confirmed, but never longer than `timeout`.
 *
 * @remarks
 * This is what keeps `start()` honest. The triggers are installed and the
 * channels subscribed from a `connect` event handler, so without waiting for
 * that work `start()` resolves while the cache is already live and nothing can
 * invalidate it. Waiting forever is not an option either — a database that
 * connects but never confirms would hang start-up — so an expired wait reports
 * itself through `onTimeout` and lets the caller continue with caching off.
 *
 * The timer is always cleared, so a confirmed subscription never leaves a
 * pending timeout behind.
 *
 * @param ready - resolves once invalidation is established; it must never
 *  reject, because a rejection here would escape the caller's `start()`
 * @param timeout - milliseconds to wait; a non-positive value falls back to
 *  {@link DEFAULT_INVALIDATION_TIMEOUT}
 * @param onTimeout - called if the wait expires first, to report it
 * @returns true if invalidation was confirmed, false if the wait expired
 */
export declare function awaitInvalidation(ready: Promise<void>, timeout: number, onTimeout: () => void): Promise<boolean>;
