mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-28 00:47:07 +00:00
1178 lines
45 KiB
TypeScript
1178 lines
45 KiB
TypeScript
/*
|
|
* Copyright (C) 2024-present Puter Technologies Inc.
|
|
*
|
|
* This file is part of Puter.
|
|
*
|
|
* Puter is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import type { PuterRouter } from './core/http/PuterRouter';
|
|
|
|
export interface IAWSCredentials {
|
|
access_key?: string;
|
|
secret_key?: string;
|
|
region?: string;
|
|
}
|
|
|
|
export interface IDynamoConfig {
|
|
aws?: IAWSCredentials;
|
|
endpoint?: string;
|
|
/**
|
|
* Filesystem path for the local dynalite store. Defaults to
|
|
* `./volatile/runtime/puter-ddb`. Pass `':memory:'` (or set `inMemory:
|
|
* true`) to run dynalite without persistence — the recommended setup for
|
|
* unit/integration tests.
|
|
*/
|
|
path?: string;
|
|
/**
|
|
* Run dynalite in-memory with no on-disk state. Equivalent to `path:
|
|
* ':memory:'`. Intended for tests so each suite gets a pristine in-process
|
|
* DynamoDB.
|
|
*/
|
|
inMemory?: boolean;
|
|
/**
|
|
* Create required tables on startup if they don't exist. Off by default
|
|
* because real-AWS deployments provision tables externally (Terraform /
|
|
* IaC). Set to `true` when pointing at a local DynamoDB emulator so
|
|
* self-hosters don't have to bootstrap by hand.
|
|
*/
|
|
bootstrapTables?: boolean;
|
|
}
|
|
|
|
export interface IRedisConfig {
|
|
startupNodes?: Array<{
|
|
host: string;
|
|
port: number;
|
|
}>;
|
|
/**
|
|
* Use TLS for cluster connections. Defaults to `true` (matches prod
|
|
* ElastiCache). Set `false` for self-host plain-TCP Valkey/Redis.
|
|
*/
|
|
tls?: boolean;
|
|
/**
|
|
* Use ioredis-mock instead of a real Redis cluster — fully in-process, no
|
|
* network. Defaults to `true` when `startupNodes` is empty (so tests with
|
|
* no redis config get a mock for free). Intended for unit/integration
|
|
* tests.
|
|
*/
|
|
useMock?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Redis-backed read cache in front of the KV store's point reads (`get`, and
|
|
* the per-key half of a batch `get`). Off unless `enabled` is set.
|
|
*
|
|
* Only user/app namespaces are cached; internal state under the system
|
|
* namespace is always read through, as are consistent reads.
|
|
*
|
|
* Turning the cache off does not clear what it already holds. Entries stop
|
|
* being read but also stop being invalidated, so a disable followed by a
|
|
* re-enable inside `ttlSeconds` can serve values written in between — wait out
|
|
* `ttlSeconds` before switching back on.
|
|
*/
|
|
export interface IKvCacheConfig {
|
|
/** Master switch. Default false. */
|
|
enabled?: boolean;
|
|
/** Seconds a cached value is served for. Default 60. */
|
|
ttlSeconds?: number;
|
|
/**
|
|
* Seconds a cached absence is served for. Shorter than `ttlSeconds` because
|
|
* a key that doesn't exist yet is the one most likely to appear. Default
|
|
* 10.
|
|
*/
|
|
missTtlSeconds?: number;
|
|
/**
|
|
* Seconds after a write during which that key's reads bypass the cache.
|
|
* Must comfortably exceed how long a mutation takes to become visible to
|
|
* every reader, or a read that raced the write can re-cache the old value.
|
|
* Default 5.
|
|
*/
|
|
blockSeconds?: number;
|
|
/**
|
|
* Largest cached entry, in bytes of serialized envelope. Bigger values are
|
|
* read through — they earn the least per byte of cache memory. Default
|
|
* 32768.
|
|
*/
|
|
maxEntryBytes?: number;
|
|
/**
|
|
* Milliseconds invalidations accumulate for before one broadcast carries
|
|
* them all. Local invalidation is always immediate; this only batches the
|
|
* message to peers. 0 sends one per write. Default 250.
|
|
*/
|
|
broadcastCoalesceMs?: number;
|
|
}
|
|
|
|
/**
|
|
* Alert severity. Ordered `info` < `warning` < `error` < `critical`; each alert
|
|
* transport takes everything at or above its own `minSeverity`, so the severity
|
|
* a call site picks is what decides where the alarm lands.
|
|
*/
|
|
export type PagerSeverity = 'critical' | 'error' | 'warning' | 'info';
|
|
|
|
/** A severity, or `mute` to drop the alarm before any transport sees it. */
|
|
export type SeverityRule = PagerSeverity | 'mute';
|
|
|
|
export interface IPagerDutyConfig {
|
|
enabled?: boolean;
|
|
routingKey?: string;
|
|
/**
|
|
* Lowest severity that reaches PagerDuty. Default `warning`, which keeps
|
|
* `info` alarms out of the paging system entirely.
|
|
*/
|
|
minSeverity?: PagerSeverity;
|
|
}
|
|
|
|
export interface ISlackAlertConfig {
|
|
enabled?: boolean;
|
|
/** Incoming-webhook URL to post alerts to. */
|
|
webhookUrl?: string;
|
|
/** Channel override (e.g. `#alerts`). Defaults to the webhook's own. */
|
|
channel?: string;
|
|
/** Bot display name on the posted message. */
|
|
username?: string;
|
|
/** Lowest severity posted to Slack. Default `info`. */
|
|
minSeverity?: PagerSeverity;
|
|
/**
|
|
* Highest severity posted to Slack. Defaults to `info` when PagerDuty is
|
|
* configured — anything that pages belongs in the paging system, not in
|
|
* chat — and to `critical` (everything) when Slack is the only transport.
|
|
*/
|
|
maxSeverity?: PagerSeverity;
|
|
/**
|
|
* Don't repost the same alarm id within this window. The first occurrence
|
|
* always posts; repeats inside the window only bump the occurrence count
|
|
* that the next post reports. Default 15 minutes; `0` disables throttling.
|
|
*/
|
|
repeatThrottleMs?: number;
|
|
}
|
|
|
|
export interface IPagerConfig {
|
|
/** Severity used when a call site doesn't pass one. Default `critical`. */
|
|
defaultSeverity?: PagerSeverity;
|
|
/**
|
|
* Operator overrides keyed by alarm id, or by prefix with a trailing `*`
|
|
* (`cronMonitor:*`). Exact ids beat patterns and the longest matching
|
|
* prefix wins. Applied after the call site's severity and any known-error
|
|
* rule, so this is the final say — it can retier or mute a noisy alarm
|
|
* without a deploy.
|
|
*/
|
|
severityOverrides?: Record<string, SeverityRule>;
|
|
pagerduty?: IPagerDutyConfig;
|
|
slack?: ISlackAlertConfig;
|
|
}
|
|
|
|
export interface ICfFileCacheConfig {
|
|
/**
|
|
* POST endpoint that accepts batched `{ site, path }[]` invalidation
|
|
* payloads.
|
|
*/
|
|
endpoint: string;
|
|
/** Flush cadence in ms. Default 500. */
|
|
throttle_ms?: number;
|
|
}
|
|
|
|
export interface IClickhouseConfig {
|
|
url: string;
|
|
username?: string;
|
|
password?: string;
|
|
/** Milliseconds. Default 15000. */
|
|
request_timeout?: number;
|
|
/** Max pending rows before backpressure drops oldest. Default 100000. */
|
|
max_buffer_size?: number;
|
|
/** Rows per flush. Default 500. */
|
|
batch_size?: number;
|
|
/** Flush cadence in ms. Default 5000. */
|
|
flush_interval_ms?: number;
|
|
}
|
|
|
|
export interface IEmailConfig {
|
|
/** "From" address used when callers don't override. */
|
|
from?: string;
|
|
// nodemailer transport options (passed through as-is)
|
|
host?: string;
|
|
port?: number;
|
|
secure?: boolean;
|
|
auth?: {
|
|
user?: string;
|
|
pass?: string;
|
|
};
|
|
service?: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
/** Prelude (https://prelude.so) Verify v2 — SMS phone verification provider. */
|
|
export interface IPreludeConfig {
|
|
/** Prelude v2 API key (sent as `Authorization: Bearer <apiKey>`). */
|
|
apiKey?: string;
|
|
/** Default region for parsing local-format phone numbers (e.g. 'US'). */
|
|
defaultCountry?: string;
|
|
/** Per-SMS cost ceiling in EUR. */
|
|
maxSmsCostEur?: number;
|
|
/**
|
|
* Verification template id (from the Prelude dashboard) that controls the
|
|
* SMS wording — e.g. a "Your Puter verification code is {{code}}" template.
|
|
* The message text itself is authored in Prelude, not here; this just
|
|
* selects it. Omit to use the dashboard default.
|
|
*/
|
|
templateId?: string;
|
|
/**
|
|
* Alphanumeric Sender ID to brand who the SMS is "from" (e.g. "Puter").
|
|
* Must be pre-enabled by Prelude and isn't supported by all
|
|
* carriers/regions (notably US long/short codes). Omit to use Prelude's
|
|
* default sender.
|
|
*/
|
|
senderId?: string;
|
|
/**
|
|
* Channel Prelude prioritizes for delivery. Defaults to 'rcs' (much cheaper
|
|
* than SMS); Prelude falls back to SMS when RCS isn't reachable. Requires
|
|
* an RCS agent provisioned in the Prelude account to actually use RCS.
|
|
*/
|
|
preferredChannel?:
|
|
| 'sms'
|
|
| 'rcs'
|
|
| 'whatsapp'
|
|
| 'viber'
|
|
| 'zalo'
|
|
| 'telegram';
|
|
}
|
|
|
|
/**
|
|
* S3-compatible bucket the thumbnails extension uses for storing generated
|
|
* thumbnails. When unset, the extension falls back to the main `S3Client`
|
|
* (fauxqs locally, real S3 in prod) and writes into the default bucket.
|
|
*/
|
|
export interface IThumbnailStoreConfig {
|
|
/** Bucket name. Default: `puter-local`. */
|
|
name?: string;
|
|
/** Endpoint URL — unset forces the fallback. */
|
|
endpoint?: string;
|
|
credentials?: {
|
|
accessKeyId: string;
|
|
secretAccessKey: string;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Shape of an entry under `config.providers.*` — each AI / integration driver
|
|
* reads a slightly different subset of these keys. Kept permissive so new
|
|
* providers don't have to touch the root type.
|
|
*/
|
|
export interface IAIProviderConfig {
|
|
/**
|
|
* API key. Sole canonical name — drivers no longer accept
|
|
* `secret_key`/`api_key`/`key` aliases.
|
|
*/
|
|
apiKey?: string;
|
|
/**
|
|
* Cloudflare API token (semantically distinct from a regular key).
|
|
* Cloudflare-only.
|
|
*/
|
|
apiToken?: string;
|
|
/**
|
|
* Override the provider's HTTP base URL (OpenRouter, Cloudflare,
|
|
* ElevenLabs, Ollama).
|
|
*/
|
|
apiBaseUrl?: string;
|
|
/**
|
|
* Azure AI Foundry deployment endpoint (azure-openai). Required alongside
|
|
* `apiKey`.
|
|
*/
|
|
apiURL?: string;
|
|
/** Cloudflare account id. */
|
|
accountId?: string;
|
|
/** ElevenLabs default voice id. */
|
|
defaultVoiceId?: string;
|
|
/** ElevenLabs speech-to-speech model id. */
|
|
speechToSpeechModelId?: string;
|
|
/** Ollama toggle — defaults true; set `false` to disable. */
|
|
enabled?: boolean;
|
|
/** AWS credentials for AWS-backed providers (Polly, Textract). */
|
|
aws?: IAWSCredentials;
|
|
/** Escape hatch — providers often expose additional tuning knobs. */
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
/**
|
|
* OIDC provider sub-config (google, custom, …). `google` uses discovery, so
|
|
* only `client_id` + `client_secret` are required; custom providers must also
|
|
* supply the three endpoint URLs explicitly.
|
|
*/
|
|
export interface IOIDCProviderConfig {
|
|
client_id?: string;
|
|
client_secret?: string;
|
|
authorization_endpoint?: string;
|
|
token_endpoint?: string;
|
|
userinfo_endpoint?: string;
|
|
/** Space-separated OAuth scopes. Default depends on provider. */
|
|
scopes?: string;
|
|
/** Apple Developer Team ID (apple provider only). */
|
|
team_id?: string;
|
|
/** Key ID for the Sign in with Apple private key (apple provider only). */
|
|
key_id?: string;
|
|
/** PKCS#8 PEM private key content from Apple (apple provider only). */
|
|
private_key?: string;
|
|
/** Azure AD tenant ID (microsoft provider only). Defaults to "common". */
|
|
tenant_id?: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export interface IOIDCConfig {
|
|
providers?: Record<string, IOIDCProviderConfig>;
|
|
}
|
|
|
|
export interface IPeersConfig {
|
|
/** WebRTC signaller URL returned to clients. */
|
|
signaller_url?: string;
|
|
/** Fallback ICE server list when TURN credential generation fails. */
|
|
fallback_ice?: unknown[];
|
|
/** TURN credential generation config (Cloudflare-backed). */
|
|
turn?: {
|
|
cloudflare_turn_service_id?: string;
|
|
cloudflare_turn_api_token?: string;
|
|
/** Credential TTL in seconds. Default 86400. */
|
|
ttl?: number;
|
|
};
|
|
/**
|
|
* Relay access for guests of an authenticated host, who mint credentials
|
|
* against a signed grant instead of an account of their own.
|
|
*/
|
|
guest_turn?: {
|
|
/**
|
|
* HMAC key for guest grants. Absent disables the guest routes with a
|
|
* 503 — a deployment opts into guest relay access by setting this. Must
|
|
* not be shared with any other secret.
|
|
*/
|
|
grant_secret?: string;
|
|
/** Grant lifetime in seconds. Default 3600. */
|
|
grant_ttl?: number;
|
|
/** Guest credential TTL in seconds, clamped to `turn.ttl`. Default 3600. */
|
|
credential_ttl?: number;
|
|
};
|
|
/** Shared secret for the internal `/turn/ingest-usage` endpoint. */
|
|
internal_auth_secret?: string;
|
|
}
|
|
|
|
export interface IBroadcastPeerConfig {
|
|
/** Stable id of the peer (also sent as `X-Broadcast-Peer-Id`). */
|
|
peerId?: string;
|
|
/** Whether this peer should receive webhooks. Non-webhook peers are skipped. */
|
|
webhook?: boolean;
|
|
/** HTTPS endpoint to POST broadcast events to. */
|
|
webhook_url?: string;
|
|
/** HMAC-SHA256 secret shared with the peer for signing. */
|
|
webhook_secret?: string;
|
|
}
|
|
|
|
export interface IBroadcastConfig {
|
|
peers?: IBroadcastPeerConfig[];
|
|
webhook?: {
|
|
/**
|
|
* This server's peerId, sent in outbound POSTs as
|
|
* `X-Broadcast-Peer-Id`.
|
|
*/
|
|
peerId?: string;
|
|
/** Secret used to sign OUTBOUND POSTs. */
|
|
secret?: string;
|
|
};
|
|
/**
|
|
* Reject webhooks whose timestamp is more than this many seconds in the
|
|
* past. Default 300.
|
|
*/
|
|
webhook_replay_window_seconds?: number;
|
|
/**
|
|
* Time to wait coalescing outbound events into a single peer POST. Default
|
|
* 2000ms.
|
|
*/
|
|
outbound_flush_ms?: number;
|
|
}
|
|
|
|
/** Cloudflare Workers deployment config used by `WorkerDriver`. */
|
|
export interface IWorkersConfig {
|
|
XAUTHKEY?: string;
|
|
ACCOUNTID?: string;
|
|
/**
|
|
* Optional dispatch namespace — when set, scripts deploy under
|
|
* `/dispatch/namespaces/<ns>`.
|
|
*/
|
|
namespace?: string;
|
|
/**
|
|
* Base URL included as the `puter_endpoint` binding. Default
|
|
* `https://api.puter.com`.
|
|
*/
|
|
internetExposedUrl?: string;
|
|
/**
|
|
* URL returned by `getLoggingUrl()` — surfaced to clients that render
|
|
* worker logs.
|
|
*/
|
|
loggingUrl?: string;
|
|
[key: string]: string | undefined;
|
|
}
|
|
|
|
/**
|
|
* Optional outbound-fetch proxy used by `secureFetch()` when the backend has to
|
|
* fetch a user-supplied URL (e.g. image-gen `input_image`). Requests get
|
|
* prefixed with `url` and sent through the Worker with `x-cors-proxy-auth-
|
|
* secret: <secret>`; the Worker authenticates the secret, fetches the real URL,
|
|
* and strips CORS on the response. Unset → fetches go direct (still guarded by
|
|
* the URL/redirect/DNS checks in secureFetch).
|
|
*/
|
|
export interface ISecureCorsProxyConfig {
|
|
url: string;
|
|
secret: string;
|
|
}
|
|
|
|
export interface IWispConfig {
|
|
/** WISP relay server address returned to clients on token create. */
|
|
server?: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export interface IServerHealthConfig {
|
|
/** DB liveness latency threshold (ms). Default 1500. */
|
|
db_liveness_latency_fail_ms?: number;
|
|
/**
|
|
* Latency threshold for the primary-pinned probe (ms). Separate from
|
|
* `db_liveness_latency_fail_ms` because that one measures the local read
|
|
* path while this one crosses to whichever region holds the primary.
|
|
* Default 3000. Values at or above the 4000ms per-check timeout are moot —
|
|
* the check runner gives up first.
|
|
*/
|
|
db_primary_liveness_latency_fail_ms?: number;
|
|
/**
|
|
* Consecutive over-threshold runs before the primary probe reports
|
|
* unhealthy. Default 2, i.e. sustained slowness rather than one slow round
|
|
* trip. A primary that errors or hangs fails on the first run regardless.
|
|
*/
|
|
db_primary_liveness_breaches_to_fail?: number;
|
|
/** Staleness threshold for the health-check loop itself (ms). */
|
|
stale_health_loop_fail_ms?: number;
|
|
/**
|
|
* Cadence for the external-dependency probes (redis, dynamo, object store,
|
|
* primary database). Deliberately slower than the 5s check loop so probing
|
|
* a paid, rate-limited backing service stays a rounding error against real
|
|
* traffic. Default 30000.
|
|
*/
|
|
dependency_check_interval_ms?: number;
|
|
/** Redis liveness latency threshold (ms). Default 1000. */
|
|
redis_liveness_latency_fail_ms?: number;
|
|
/** Dynamo liveness latency threshold (ms). Default 1500. */
|
|
dynamo_liveness_latency_fail_ms?: number;
|
|
/** Object-store liveness latency threshold (ms). Default 2000. */
|
|
s3_liveness_latency_fail_ms?: number;
|
|
/**
|
|
* Check names to skip registering entirely — an operator kill switch for a
|
|
* probe that turns out to be noisy, without waiting on a deploy.
|
|
*/
|
|
disabled_checks?: string[];
|
|
}
|
|
|
|
export interface IS3LocalConfig {
|
|
/**
|
|
* Run fauxqs entirely in-memory: random port on `127.0.0.1`, no `dataDir` /
|
|
* `s3StorageDir`. Intended for tests so each suite gets a pristine
|
|
* in-process S3.
|
|
*/
|
|
inMemory?: boolean;
|
|
host?: string;
|
|
port?: number;
|
|
dataDir?: string;
|
|
s3StorageDir?: string;
|
|
}
|
|
|
|
export interface IS3RemoteConfig {
|
|
useCredentialChain?: boolean;
|
|
endpoint: string;
|
|
/**
|
|
* Endpoint used when generating presigned URLs handed to clients (browser
|
|
* uploads/downloads). Defaults to `endpoint`. Set this when the server-side
|
|
* S3 endpoint isn't reachable from the browser — e.g. self-host with
|
|
* `endpoint: http://s3:9000` (docker-internal) and `publicEndpoint:
|
|
* http://localhost:9000` (host-published port).
|
|
*/
|
|
publicEndpoint?: string;
|
|
accessKeyId: string;
|
|
secretAccessKey: string;
|
|
region?: string;
|
|
/**
|
|
* Use path-style URLs (`<endpoint>/<bucket>`) instead of virtual-hosted
|
|
* style (`<bucket>.<endpoint>`). Defaults to AWS SDK's default (virtual-
|
|
* hosted, which only works on real AWS S3). Set `true` for S3-compatible
|
|
* servers (RustFS, MinIO, fauxqs) where DNS-style addressing fails.
|
|
*/
|
|
forcePathStyle?: boolean;
|
|
}
|
|
|
|
export interface IS3Config {
|
|
localConfig?: IS3LocalConfig;
|
|
s3Config?: IS3RemoteConfig;
|
|
}
|
|
|
|
export interface IDatabaseConfig {
|
|
engine: 'sqlite' | 'mysql' | 'postgres';
|
|
// sqlite
|
|
/**
|
|
* SQLite database file path. Defaults to `':memory:'` (the better-sqlite3
|
|
* in-memory mode), which is also what tests should use. `inMemory: true` is
|
|
* an explicit alias for the same.
|
|
*/
|
|
path?: string;
|
|
/**
|
|
* Force in-memory SQLite (ignores `path`). Equivalent to `path:
|
|
* ':memory:'`. Intended for tests so each suite gets a pristine in-process
|
|
* database. Test utilities also use `engine: 'postgres'` with `inMemory:
|
|
* true` to run against pgmock.
|
|
*/
|
|
inMemory?: boolean;
|
|
targetVersion?: number;
|
|
// mysql
|
|
host?: string;
|
|
port?: number;
|
|
user?: string;
|
|
password?: string;
|
|
database?: string;
|
|
connectionString?: string;
|
|
url?: string;
|
|
replica?: {
|
|
host?: string;
|
|
port?: number;
|
|
user?: string;
|
|
password?: string;
|
|
database?: string;
|
|
connectionString?: string;
|
|
url?: string;
|
|
};
|
|
/**
|
|
* Server-side execution cap for SELECT statements in ms (mysql engine;
|
|
* applies to both pools — MySQL only enforces it on SELECTs, so writes are
|
|
* unaffected). 0 disables. Default 30000.
|
|
*/
|
|
selectTimeoutMs?: number;
|
|
/**
|
|
* Max time to wait for a pooled connection before the query batcher treats
|
|
* acquisition as failed, in ms (mysql engine). 0 disables the bound.
|
|
* Default 5000.
|
|
*/
|
|
acquireTimeoutMs?: number;
|
|
/**
|
|
* Ordered list of directories whose `.sql` files are run sequentially at
|
|
* server start (mysql/postgres engines). Numbered migration filenames sort
|
|
* numerically; directories are processed in array order. Files MUST be
|
|
* idempotent — there is no per-file applied-state tracking. Relative paths
|
|
* resolve from `process.cwd()`.
|
|
*/
|
|
migrationPaths?: string[];
|
|
}
|
|
|
|
/**
|
|
* Bucket of pass-through values surfaced to the client-side `gui()` boot
|
|
* function. Known fields are declared for lookup hygiene; unknown keys are
|
|
* still tolerated so product teams can add one-off flags without churn.
|
|
*/
|
|
export interface IGuiParams {
|
|
title?: string;
|
|
short_description?: string;
|
|
social_media_image?: string;
|
|
/**
|
|
* Public SDK key for Prelude's JS Signals SDK. When set, the phone-
|
|
* verification window collects a `dispatch_id` (browser signals) and
|
|
* forwards it to /send-confirm-phone, which passes it to Prelude's Verify
|
|
* API so its abuse model can weigh the device. Omit to disable collection.
|
|
*/
|
|
preludeSdkKey?: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export interface IDevWatcherConfig {
|
|
/** Force-enable/disable the dev watcher. Defaults to enabled in dev. */
|
|
enabled?: boolean;
|
|
/** Root path for watcher entries. Relative paths resolve from package root. */
|
|
root?: string;
|
|
/** Delay after watcher startup before boot continues. Default: 5000. */
|
|
ready_delay_ms?: number;
|
|
/** Optional extra child processes to start with the dev watcher. */
|
|
commands?: Array<{
|
|
name: string;
|
|
directory: string;
|
|
command: string;
|
|
args?: string[];
|
|
env?: Record<string, string>;
|
|
}>;
|
|
/**
|
|
* Optional webpack watcher entries. Omit to use the built-in GUI/puter.js
|
|
* watchers.
|
|
*/
|
|
webpack?: Array<{
|
|
name?: string;
|
|
directory: string;
|
|
env?: Record<string, string>;
|
|
}>;
|
|
}
|
|
|
|
/**
|
|
* Complete shape of Puter's root config. Everything is optional here —
|
|
* mandatory fields (only `port` + `extensions`) are pulled out of the
|
|
* `Partial<...>` below and listed after it.
|
|
*
|
|
* When adding a new config field, declare it here with a doc comment so there's
|
|
* a single discoverable reference for every config-driven switch.
|
|
*
|
|
* One value, one location: each setting lives at exactly one key. There are no
|
|
* legacy aliases or fallback paths — older configs that relied on them need to
|
|
* migrate.
|
|
*/
|
|
interface IConfigOptional {
|
|
// -- Environment / identity --------------------------------------
|
|
|
|
/**
|
|
* Environment marker. `dev` disables blocked-email checks, opens
|
|
* auto-browser, etc.
|
|
*/
|
|
env: 'dev' | 'prod';
|
|
/**
|
|
* Free-form name of the config profile (e.g. `oss-default`). Surfaced in
|
|
* logs.
|
|
*/
|
|
config_name: string;
|
|
/**
|
|
* Console output format. `json` replaces the global console so every call
|
|
* emits one structured JSON line (`level`, `timestamp`, `msg`, and the
|
|
* active `traceId`) — one event per call, so a line-oriented log collector
|
|
* can't split stack traces across events, and level filtering works. `text`
|
|
* (the default) leaves console output human-readable for local/dev.
|
|
*/
|
|
log_format: 'json' | 'text';
|
|
/**
|
|
* Keep serving after an uncaught exception instead of exiting. Uncaught
|
|
* exceptions are always logged either way; this only decides whether one
|
|
* ends the process. Default: false, matching Node's own behavior. Set it
|
|
* where losing the node costs more than running a possibly-degraded one — a
|
|
* small pool behind a health check that replaces bad nodes anyway.
|
|
*/
|
|
keep_alive_on_uncaught: boolean;
|
|
/** Server version. Falls back to `npm_package_version`. */
|
|
version: string;
|
|
/**
|
|
* Stable identity for this server node. Enables pager alerts + graceful
|
|
* shutdown delay.
|
|
*/
|
|
serverId: string;
|
|
|
|
// -- Networking / URLs -------------------------------------------
|
|
|
|
/**
|
|
* Protocol used for the externally-visible origin ('http' or 'https').
|
|
* Default: 'http'.
|
|
*/
|
|
protocol: string;
|
|
/** Primary domain for Puter (e.g., `puter.localhost`, `puter.com`). */
|
|
domain: string;
|
|
/**
|
|
* Externally-visible port. Defaults to `port`. Behind a reverse proxy, set
|
|
* this to the public port.
|
|
*/
|
|
pub_port: number;
|
|
/**
|
|
* Fully-qualified externally-visible URL (protocol + domain + port).
|
|
* Computed from `protocol`/`domain`/`pub_port` if unset.
|
|
*/
|
|
origin: string;
|
|
/**
|
|
* Public base URL for the API subdomain, e.g. `https://api.puter.com`. Used
|
|
* to build signed URLs.
|
|
*/
|
|
api_base_url: string;
|
|
/** Static hosting domain for user sites (e.g., `puter.site`). */
|
|
static_hosting_domain: string;
|
|
/** Alt static hosting domain. */
|
|
static_hosting_domain_alt: string;
|
|
/** Private app hosting domain (e.g., `app.puter.localhost`). */
|
|
private_app_hosting_domain: string;
|
|
/** Alt private app hosting domain. */
|
|
private_app_hosting_domain_alt: string;
|
|
/**
|
|
* Groups of equivalent app index_url hosts. Each group lists hosts that
|
|
* should resolve to the same canonical app: `appUidFromOrigin` looks up any
|
|
* DB row whose `index_url` is one of the group's hosts and returns that
|
|
* row's UID for every host in the group.
|
|
*
|
|
* Hosts listed here are also reserved — `apps.create` / `apps.update`
|
|
* reject any attempt to register a different app under one of these hosts,
|
|
* so the group is owned by exactly one app row.
|
|
*
|
|
* Entries are bare hosts (no scheme), lowercased. Example: [
|
|
* ["camera.puter.com", "camera.puter.site", "camera.ca"],
|
|
* ["player.puter.com", "player.puter.site"], ]
|
|
*/
|
|
app_origin_aliases?: string[][];
|
|
/** When true, accept any Host header value. Dev/testing only. */
|
|
allow_all_host_values: boolean;
|
|
/** When true, accept requests without a Host header. */
|
|
allow_no_host_header: boolean;
|
|
/** When true, allow nip.io wildcard domains. */
|
|
allow_nipio_domains: boolean;
|
|
/** When true, support custom domain resolution for hosted sites. */
|
|
custom_domains_enabled: boolean;
|
|
/** When true, enable IP validation via event bus. */
|
|
enable_ip_validation: boolean;
|
|
/**
|
|
* Express `trust proxy` setting — controls how `req.ip` is derived from
|
|
* `X-Forwarded-For`. Set to the number of reverse-proxy hops in front of
|
|
* the server (e.g. `1` for a single Cloudflare or nginx hop, `2` for
|
|
* Cloudflare → ALB → app), or to a CIDR / IP / list of trusted proxy
|
|
* addresses. `false` (default) disables XFF parsing — `req.ip` returns the
|
|
* direct socket peer, which is the safe choice when no proxy is in front.
|
|
* Never set to `true` in production: it trusts _every_ hop and makes XFF
|
|
* forgeable. See https://expressjs.com/en/guide/behind-proxies.html.
|
|
*/
|
|
trust_proxy: boolean | number | string | string[];
|
|
/** Don't launch browser when starting. */
|
|
no_browser_launch: boolean;
|
|
/** Disable dev-time frontend webpack watchers. */
|
|
no_devwatch: boolean;
|
|
/**
|
|
* Skip first-boot bootstrap of the `admin` user and the credentials banner
|
|
* that DefaultUserService prints. Intended for tests.
|
|
*/
|
|
no_default_user: boolean;
|
|
/**
|
|
* Import `.ts` extension sources instead of built `.js`. Only for
|
|
* transform-capable runtimes (the test harness sets this); plain node
|
|
* cannot execute the TypeScript sources.
|
|
*/
|
|
import_ts_extensions?: boolean;
|
|
/** Optional dev-time frontend watcher overrides. */
|
|
devwatch: IDevWatcherConfig;
|
|
|
|
// -- Auth / session ----------------------------------------------
|
|
|
|
/** HMAC secret used to sign and verify auth JWTs (`kid: 'v2'`). */
|
|
jwt_secret_v2: string;
|
|
/**
|
|
* Optional extra `Origin` header values allowed to call the routes that
|
|
* hand back a session credential (`/login`, `/signup`,
|
|
* `/session/sync-cookie` — see `guiOriginGate`). The main `origin` is
|
|
* always allowed, and callers with no `Origin` at all (CLI, mobile,
|
|
* server-side, tests) are never gated.
|
|
*
|
|
* Only for deployments that genuinely serve their GUI from a different
|
|
* origin than `config.origin`. Do NOT list loopback origins in production:
|
|
* `http://localhost:4000` is not an authenticatable origin, it's whatever
|
|
* is listening on that port on the visitor's machine. A local GUI should
|
|
* obtain its token via the AuthMe flow instead.
|
|
*/
|
|
allow_gui_origins?: string[];
|
|
/** HMAC secret for signed file URLs (/file, /writeFile, /sign). */
|
|
url_signature_secret: string;
|
|
/** Name of the session cookie the auth probe reads. */
|
|
cookie_name: string;
|
|
/** Minimum password length for login/signup validation. */
|
|
min_pass_length: number;
|
|
/** When true, allow the 'system' user to log in. */
|
|
allow_system_login: boolean;
|
|
/**
|
|
* When true, anonymous users cannot create new accounts or temporary
|
|
* sessions. Existing accounts can still log in, and pre-existing
|
|
* placeholder rows may still be claimed.
|
|
*/
|
|
disable_user_signup: boolean;
|
|
/** Reject auth-gated routes unless the user has confirmed their email. */
|
|
strict_email_verification_required: boolean;
|
|
/**
|
|
* Force SMS phone verification on every new signup, regardless of abuse
|
|
* reputation. Off by default; mainly a test/QA switch so the phone gate can
|
|
* be exercised on demand (it otherwise only triggers for low-reputation
|
|
* signups). Requires `prelude.apiKey` to actually deliver codes.
|
|
*/
|
|
always_require_phone_verification: boolean;
|
|
/**
|
|
* Force credit-card verification on every new signup, regardless of abuse
|
|
* reputation. Off by default; mainly a test/QA switch so the card gate can
|
|
* be exercised on demand (it otherwise only triggers for low-reputation
|
|
* signups). Requires a payments extension to actually run the $0 auth.
|
|
*/
|
|
always_require_card_verification: boolean;
|
|
/**
|
|
* Let a user who keeps getting blocked on SMS phone verification fall back
|
|
* to credit-card verification, which clears the phone gate (and the card
|
|
* gate too, when one is set).
|
|
*
|
|
* The fallback opens once the user has made `after_attempts` SMS _send_
|
|
* attempts inside the send rate-limit window, which by default means only
|
|
* after they have used up the window's entire send allowance — the card
|
|
* option is an escape hatch for a phone that isn't working, not a choice
|
|
* offered alongside a working SMS flow. Successful sends count too, so this
|
|
* trades the phone signal for a card signal; it does NOT guarantee SMS
|
|
* actually failed. Once open, the fallback stays open for 24 hours so the
|
|
* user can finish the card flow. Requires a payments extension to run the
|
|
* actual card check.
|
|
*/
|
|
phone_verification_card_fallback: {
|
|
/**
|
|
* Tri-state. Set it and that wins, either way — this is the opt-out.
|
|
* Omit it and the fallback is on wherever both gates it bridges
|
|
* actually work: an SMS provider is configured _and_ an installed
|
|
* extension reports card verification enabled. On a build with no card
|
|
* gate behind it the fallback stays off, since taking the offer there
|
|
* could only strand the user.
|
|
*/
|
|
enabled?: boolean;
|
|
/**
|
|
* SMS send attempts (within the send rate-limit window) before the card
|
|
* fallback opens. Defaults to the send route's full rate limit
|
|
* (10/hour), i.e. the fallback appears only once the user is out of SMS
|
|
* attempts. Values above that limit are clamped down to it — requests
|
|
* past the route limit never reach the attempt counter, so a higher
|
|
* threshold could never be crossed. Lower it (e.g. 2) to reach the card
|
|
* path without burning the whole allowance, which is mainly useful for
|
|
* QA.
|
|
*/
|
|
after_attempts?: number;
|
|
};
|
|
/** Captcha configuration. */
|
|
captcha: { enabled: boolean; difficulty?: 'easy' | 'medium' | 'hard' };
|
|
/** OIDC / OAuth2 providers (google + custom). */
|
|
oidc: IOIDCConfig;
|
|
|
|
// -- Groups / provisioning ---------------------------------------
|
|
|
|
/**
|
|
* UID of the persistent group that non-temp users are enrolled in at
|
|
* signup.
|
|
*/
|
|
default_user_group: string;
|
|
/**
|
|
* UID of the persistent group that temporary users are enrolled in at
|
|
* signup.
|
|
*/
|
|
default_temp_group: string;
|
|
/** When true, ACL grants read/list/see on `/<user>/Public` to any actor. */
|
|
enable_public_folders: boolean;
|
|
|
|
/**
|
|
* Whether a recipient who already has an account is emailed about a share
|
|
* as well as notified in-app. **On unless set to false**; recipients
|
|
* decline with the unsubscribe link the mail carries, or by blocking a
|
|
* sender. An invite to an address with no account is emailed regardless.
|
|
*/
|
|
share_email_notifications?: boolean;
|
|
|
|
/**
|
|
* Ceiling on how many shares one user may create per UTC day. An abuse
|
|
* bound, not an accounting one — it exists so a script can't blanket other
|
|
* accounts with unwanted items and the notifications that follow. Omit to
|
|
* use the built-in default.
|
|
*/
|
|
share_daily_limit?: number;
|
|
|
|
/**
|
|
* How often a share may interrupt its recipient — the notification pushed
|
|
* to their screen and the email that goes with it. The share itself is
|
|
* never refused for being over budget; only the announcement is dropped.
|
|
*
|
|
* Both axes are needed: the pair bounds hold one sharer back, and the
|
|
* recipient bounds are what stop many senders from burying one person
|
|
* between them. Omit any field for the built-in default; a non-positive
|
|
* value removes that bound.
|
|
*/
|
|
share_notify_limits?: {
|
|
/**
|
|
* Quiet period after one sharer reaches a recipient, in seconds. Also
|
|
* how long their notification keeps absorbing new shares.
|
|
*/
|
|
pairWindowSeconds?: number;
|
|
/** Interruptions one sharer may cause a recipient per day. */
|
|
pairDaily?: number;
|
|
/** Interruptions a recipient may receive per hour, from anyone. */
|
|
recipientHourly?: number;
|
|
/** Same, per day. */
|
|
recipientDaily?: number;
|
|
/**
|
|
* How long emails to one recipient are held and merged into a single
|
|
* digest, in seconds. Default 90; non-positive sends immediately.
|
|
*/
|
|
emailBatchSeconds?: number;
|
|
};
|
|
|
|
/**
|
|
* Ceiling on recipients, and on items, in a single share request. Bounds
|
|
* the fan-out one call can trigger; the daily limit bounds the total.
|
|
*/
|
|
share_max_recipients?: number;
|
|
share_max_items?: number;
|
|
|
|
// -- Storage / S3 ------------------------------------------------
|
|
|
|
/** S3 storage config (local fauxqs or remote). */
|
|
s3: IS3Config;
|
|
/** Default S3 bucket for file storage. */
|
|
s3_bucket: string;
|
|
/** Default S3 region. */
|
|
s3_region: string;
|
|
/** Fallback AWS region. */
|
|
region: string;
|
|
/** Default storage capacity per user (bytes). */
|
|
storage_capacity: number;
|
|
/** When false, storage is effectively unlimited (bounded by device space). */
|
|
is_storage_limited: boolean;
|
|
/** Bytes of device storage available (used when is_storage_limited=false). */
|
|
available_device_storage: number;
|
|
/** Optional dedicated S3-compatible bucket used by the thumbnails extension. */
|
|
thumbnailStore: IThumbnailStoreConfig;
|
|
|
|
// -- Database ----------------------------------------------------
|
|
|
|
database: IDatabaseConfig;
|
|
|
|
// -- Clients / infra ---------------------------------------------
|
|
|
|
dynamo: IDynamoConfig;
|
|
redis: IRedisConfig;
|
|
/** Read cache in front of KV point reads. Off unless `enabled` is set. */
|
|
kvCache: IKvCacheConfig;
|
|
pager: IPagerConfig;
|
|
email: IEmailConfig;
|
|
/** Optional — only set when SMS phone verification (Prelude) is wired in. */
|
|
prelude: IPreludeConfig;
|
|
/** Optional — only set when a ClickHouse analytics client is wired in. */
|
|
clickhouse?: IClickhouseConfig;
|
|
cf_file_cache: ICfFileCacheConfig;
|
|
|
|
// -- Rate limiting -----------------------------------------------
|
|
|
|
rate_limit: {
|
|
/**
|
|
* Rate limiter backend selection.
|
|
*
|
|
* - `memory`: per-node in-memory counters.
|
|
* - `redis`: sorted-sets in Redis — shared state across nodes (default).
|
|
* - `kv`: per-hit rows in the system KV store (DynamoDB), with TTL.
|
|
*/
|
|
backend?: 'memory' | 'redis' | 'kv';
|
|
};
|
|
|
|
// -- AI / integration providers ----------------------------------
|
|
//
|
|
// All AI providers — chat, image, video, TTS, OCR, speech-to-text,
|
|
// speech-to-speech — are configured under `providers[<provider-id>]`.
|
|
// Provider ids match the driver-side identifier (e.g. `claude`,
|
|
// `openai-image-generation`, `aws-textract`). There is no `services`
|
|
// bag and no top-level `openai`/`gemini`/`mistral`/`elevenlabs`/`aws`
|
|
// shortcut.
|
|
providers: Record<string, IAIProviderConfig | undefined>;
|
|
|
|
// -- Cross-node / external integrations --------------------------
|
|
|
|
/** Cross-node event replication config. */
|
|
broadcast: IBroadcastConfig;
|
|
/** WebRTC signalling + TURN. */
|
|
peers: IPeersConfig;
|
|
/** WISP relay proxy. */
|
|
wisp: IWispConfig;
|
|
/** Cloudflare Workers driver config. */
|
|
workers: IWorkersConfig;
|
|
/** Optional CORS-stripping signed-Worker proxy used by `secureFetch`. */
|
|
secureCorsProxy: ISecureCorsProxyConfig;
|
|
/** Legacy Stripe billing extension. */
|
|
|
|
// -- GUI / static mounts -----------------------------------------
|
|
|
|
/** Absolute path to the GUI assets root. */
|
|
gui_assets_root: string;
|
|
/** Which profile in `puter-gui.json` to load. Default: `development`. */
|
|
gui_profile: string;
|
|
/** Map of built-in app name → local directory served at `/builtin/<name>`. */
|
|
builtin_apps: Record<string, string>;
|
|
/** Force the bundled GUI even in dev. Default: false. */
|
|
use_bundled_gui: boolean;
|
|
/** Override the GUI bundle JS path. Default: `/dist/bundle.min.js`. */
|
|
gui_bundle: string;
|
|
/** Override the GUI CSS path when bundled. Default: `/dist/bundle.min.css`. */
|
|
gui_css: string;
|
|
/**
|
|
* Override the puter.js preload URL when bundled. Default:
|
|
* `https://js.puter.com/v2/`.
|
|
*/
|
|
gui_puterjs_bundle: string;
|
|
/**
|
|
* Free-form bag of values passed through to the client-side `gui()`
|
|
* function.
|
|
*/
|
|
gui_params: IGuiParams;
|
|
/**
|
|
* Absolute path to the directory holding native app bundles, each in a
|
|
* subdirectory matching its subdomain (e.g. `<root>/editor/`).
|
|
*/
|
|
native_apps_root: string;
|
|
/**
|
|
* Absolute path to a directory holding `puter.js`/`putility.js` version
|
|
* bundles.
|
|
*/
|
|
client_libs_root: string;
|
|
/** Path to the puter-js SDK root (serves `/sdk/*` and `/puter.js/v{1,2}`). */
|
|
puterjs_root: string;
|
|
|
|
// -- Extension-specific ------------------------------------------
|
|
|
|
/**
|
|
* Flat `{ flag_name: boolean }` bag of feature toggles. Non-boolean values
|
|
* are coerced before use.
|
|
*
|
|
* Server-only by default. Flags are surfaced to clients via `/whoami` only
|
|
* if their key is on the allowlist in `extensions/whoami.ts`
|
|
* (`CLIENT_VISIBLE_FEATURE_FLAGS`). New flags should be assumed internal —
|
|
* add them to the allowlist explicitly if (and only if) the client needs to
|
|
* read them.
|
|
*/
|
|
feature_flags: Record<string, boolean | string | number>;
|
|
/** Blocked email TLDs / domains — checked in `prod` only. */
|
|
blockedEmailDomains: string[];
|
|
/** Contact-form recipient. Default `support@puter.com`. */
|
|
support_email: string;
|
|
/** Worker / subdomain names that cannot be allocated by users. */
|
|
reserved_words: string[];
|
|
/** Max subdomains a single user may own. Default 10. */
|
|
max_subdomains_per_user: number;
|
|
/** Health-check tuning. */
|
|
server_health: IServerHealthConfig;
|
|
|
|
//Metering
|
|
unlimitedMetering?: boolean;
|
|
/**
|
|
* Fleet-wide spend rate, in micro-cents per minute, past which the metering
|
|
* service raises an alarm. There is no defensible default here — the right
|
|
* number is a multiple of what this deployment's own traffic normally
|
|
* costs, so set it from observed rate and revisit it as traffic grows. An
|
|
* unset or non-positive value turns the check off rather than guessing.
|
|
*/
|
|
maxGlobalUsagePerMinute?: number;
|
|
/**
|
|
* How long per-request usage (response bytes, object-store requests) is
|
|
* held in memory before being written, in milliseconds. This is the dial
|
|
* between how promptly that usage lands and how many writes it costs: every
|
|
* actor active in a window settles once per window, so halving it doubles
|
|
* the write rate. Unset uses the service default; a non-positive value is
|
|
* ignored.
|
|
*/
|
|
meteringUsageBufferFlushMs?: number;
|
|
/**
|
|
* Whether recorded usage is also enforced: an account with nothing left of
|
|
* its budget is turned away from the operations that spend it (file
|
|
* transfers, KV calls) with a 402. Metadata reads and deletions stay open,
|
|
* as does serving a hosted site — those bytes are billed to the account
|
|
* hosting it but requested by visitors who have no say in its balance.
|
|
*
|
|
* - `enabled` — defaults to on, and covers the plan gates below as well. The
|
|
* switch to reach for if enforcement is turning away traffic it
|
|
* shouldn't; usage is still recorded either way.
|
|
* - `workers` — extend enforcement to worker-driven calls. Off by default: a
|
|
* worker has no prompt to show and nobody watching, so being cut off
|
|
* presents as a program that started failing.
|
|
* - `subscriptions` — whether surfaces that declare a plan requirement
|
|
* (`requireSubscription`) enforce it. Defaults to on, and off wherever no
|
|
* paid plan exists to be on (self-hosted installs ship it off). Turning
|
|
* it off opens those surfaces to every account without unpicking the
|
|
* declarations; `enabled: false` turns it off along with everything
|
|
* else.
|
|
*/
|
|
meteringEnforcement?: {
|
|
enabled?: boolean;
|
|
workers?: boolean;
|
|
subscriptions?: boolean;
|
|
};
|
|
|
|
/**
|
|
* Reputation gating: what the tiers named by `requireReputation` on a route
|
|
* or a driver method actually take.
|
|
*
|
|
* - `enabled` — the one switch that stops every declared gate enforcing,
|
|
* without unpicking the declarations. Defaults to on, which by itself
|
|
* gates nothing: a tier only bites once `tiers` gives it a score.
|
|
* - `tiers` — tier name → the minimum score (0-100) an account needs to pass
|
|
* it. The numbers live here rather than in the declarations because what
|
|
* counts as trusted enough is a per-deployment call, retuned far more
|
|
* often than the surfaces it protects. A tier with no entry is inert: an
|
|
* install that doesn't score its accounts must not turn traffic away on a
|
|
* score it never computed.
|
|
*/
|
|
reputationGate?: {
|
|
enabled?: boolean;
|
|
tiers?: Record<string, number>;
|
|
};
|
|
|
|
/**
|
|
* Display multiplier converting metered amounts into the "credits" clients
|
|
* show. Applied server-side by the usage-reporting endpoints, so raw
|
|
* metered amounts never leave the API; purely presentational, so it can
|
|
* change without a data migration. No default — when unset, the endpoints
|
|
* report raw amounts and clients render dollars.
|
|
*/
|
|
creditMultiplier?: number;
|
|
}
|
|
|
|
/**
|
|
* Extension-augmentable config surface. Extensions add their own config keys
|
|
* via TypeScript declaration merging:
|
|
*
|
|
* declare module '@heyputer/backend/types' {
|
|
* interface IExtensionConfig {
|
|
* myExtension?: { foo: string };
|
|
* }
|
|
* }
|
|
*
|
|
* Augmentations flow into `IConfig`, which is what `this.config` /
|
|
* `extension.config` are typed as everywhere.
|
|
*/
|
|
export interface IExtensionConfig {
|
|
/**
|
|
* Open index signature so config reads of extension-only keys return
|
|
* `unknown` (not a type error). Extensions that declare-merge concrete keys
|
|
* (`myExt?: { foo: string }`) override this for the named key — the
|
|
* concrete property type wins over the index signature.
|
|
*/
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export type IConfig = Partial<IConfigOptional> & {
|
|
extensions: string[];
|
|
port: number;
|
|
} & IExtensionConfig;
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-wrapper-object-types
|
|
export interface WithLifecycle extends Object {
|
|
onServerStart?: () => Promise<void> | void;
|
|
onServerShutdown?: () => Promise<void> | void;
|
|
onServerPrepareShutdown?: () => Promise<void> | void;
|
|
}
|
|
|
|
export interface WithCostsReporting extends WithLifecycle {
|
|
getReportedCosts?: () => // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
| Promise<Record<string, any>[]>
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
| Record<string, any>[];
|
|
}
|
|
|
|
export interface WithControllerRegistration extends WithCostsReporting {
|
|
registerRoutes: (router: PuterRouter) => void;
|
|
}
|
|
|
|
export type LayerInstances<
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
T extends Record<string, (new (...args: any[]) => any) | any>,
|
|
> = {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
[K in keyof T]: T[K] extends new (...args: any[]) => any
|
|
? InstanceType<T[K]>
|
|
: T[K];
|
|
};
|