@hasna/shortlinks 0.1.23 → 0.2.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.
@@ -0,0 +1,47 @@
1
+ import { CloudShortlinksStore } from "./cloud-store.js";
2
+ import type { Env } from "@hasna/contracts/mode";
3
+ import type { ListLinksOptions, Store, TotalStats } from "./store-interface.js";
4
+ import type { AddDomainInput, Click, ClickInput, CreateLinkInput, Domain, Link, LinkStats } from "./types.js";
5
+ /** The self_hosted/cloud HTTP transport. Same client code for both tiers. */
6
+ export { CloudShortlinksStore as ApiStore } from "./cloud-store.js";
7
+ export type { Store } from "./store-interface.js";
8
+ /**
9
+ * On-box SQLite store. An async adapter over the synchronous sqlite engine
10
+ * (`ShortlinksStore`) so it satisfies the shared async {@link Store} interface.
11
+ */
12
+ export declare class LocalStore implements Store {
13
+ readonly kind: "local";
14
+ private readonly inner;
15
+ constructor(dbPath?: string);
16
+ addDomain(input: AddDomainInput): Promise<Domain>;
17
+ listDomains(): Promise<Domain[]>;
18
+ getDomain(hostnameOrId: string): Promise<Domain | null>;
19
+ getDefaultDomain(): Promise<Domain | null>;
20
+ createLink(input: CreateLinkInput): Promise<Link>;
21
+ listLinks(options?: ListLinksOptions): Promise<Link[]>;
22
+ getLink(domainOrSlug: string, maybeSlug?: string): Promise<Link | null>;
23
+ resolve(hostname: string, slug: string): Promise<Link | null>;
24
+ setLinkActive(domainOrSlug: string, slugOrActive: string | boolean, active?: boolean): Promise<Link>;
25
+ deleteLink(domainOrSlug: string, maybeSlug?: string): Promise<Link>;
26
+ recordClick(link: Link, input?: ClickInput): Promise<Click>;
27
+ getStats(domainOrSlug: string, maybeSlug?: string): Promise<LinkStats>;
28
+ totalStats(): Promise<TotalStats>;
29
+ close(): Promise<void>;
30
+ }
31
+ export interface ResolveStoreOptions {
32
+ /** Explicit local SQLite path (CLI `--db`); ignored in cloud mode. */
33
+ dbPath?: string;
34
+ /** Transport overrides for the cloud client (test injection: fetchImpl, ...). */
35
+ cloudOverrides?: Parameters<typeof CloudShortlinksStore.fromEnv>[1];
36
+ }
37
+ /**
38
+ * Resolve the active {@link Store} for the current environment. Returns the
39
+ * cloud {@link ApiStore} when the client flip is on, otherwise a
40
+ * {@link LocalStore}. Throws when cloud was requested but is misconfigured.
41
+ */
42
+ export declare function resolveStore(env?: Env, options?: ResolveStoreOptions): Store;
43
+ /**
44
+ * Run `fn` with a resolved {@link Store}, always closing it afterward. The
45
+ * canonical helper for one-shot CLI/MCP operations.
46
+ */
47
+ export declare function withStore<T>(fn: (store: Store) => T | Promise<T>, env?: Env, options?: ResolveStoreOptions): Promise<T>;
@@ -0,0 +1,46 @@
1
+ import { resolveStorageClient, type HasnaStorageClient } from "@hasna/contracts/client/storage";
2
+ import type { Env } from "@hasna/contracts/mode";
3
+ import type { Store } from "./store-interface.js";
4
+ import type { AddDomainInput, Click, ClickInput, CreateLinkInput, Domain, Link, LinkStats } from "./types.js";
5
+ /**
6
+ * Cloud-backed shortlinks store. All methods hit `/v1` over HTTPS with the
7
+ * bearer key. Mirrors the async `PgShortlinksStore` method signatures so it is a
8
+ * drop-in `RuntimeStore` for the CLI.
9
+ */
10
+ export declare class CloudShortlinksStore implements Store {
11
+ private readonly client;
12
+ readonly kind: "cloud-http";
13
+ readonly transport: HasnaStorageClient["transport"];
14
+ private constructor();
15
+ /** Base `<origin>/v1` URL this store targets (for status/diagnostics). */
16
+ get baseUrl(): string;
17
+ /**
18
+ * Resolve a cloud store from the environment. Returns the store when the
19
+ * client-flip resolves to `cloud-http` (self_hosted + API_URL + API_KEY), else
20
+ * null so the caller falls back to the local store. Throws only when cloud was
21
+ * explicitly requested but is misconfigured (never silent local drift).
22
+ */
23
+ static fromEnv(env?: Env, overrides?: Parameters<typeof resolveStorageClient>[2]): CloudShortlinksStore | null;
24
+ close(): Promise<void>;
25
+ addDomain(input: AddDomainInput): Promise<Domain>;
26
+ listDomains(): Promise<Domain[]>;
27
+ getDomain(hostnameOrId: string): Promise<Domain | null>;
28
+ getDefaultDomain(): Promise<Domain | null>;
29
+ createLink(input: CreateLinkInput): Promise<Link>;
30
+ listLinks(options?: {
31
+ domain?: string;
32
+ activeOnly?: boolean;
33
+ limit?: number;
34
+ }): Promise<Link[]>;
35
+ getLink(domainOrSlug: string, maybeSlug?: string): Promise<Link | null>;
36
+ resolve(hostname: string, slug: string): Promise<Link | null>;
37
+ setLinkActive(domainOrSlug: string, maybeSlugOrActive: string | boolean, maybeActive?: boolean): Promise<Link>;
38
+ deleteLink(domainOrSlug: string, maybeSlug?: string): Promise<Link>;
39
+ recordClick(_link: Link, _input?: ClickInput): Promise<Click>;
40
+ getStats(domainOrSlug: string, maybeSlug?: string): Promise<LinkStats>;
41
+ totalStats(): Promise<{
42
+ domains: number;
43
+ links: number;
44
+ clicks: number;
45
+ }>;
46
+ }
@@ -1,13 +1,143 @@
1
1
  // @bun
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
12
+ var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
20
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
21
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
+ for (let key of __getOwnPropNames(mod))
23
+ if (!__hasOwnProp.call(to, key))
24
+ __defProp(to, key, {
25
+ get: __accessProp.bind(mod, key),
26
+ enumerable: true
27
+ });
28
+ if (canCache)
29
+ cache.set(mod, to);
30
+ return to;
31
+ };
32
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
+ var __require = import.meta.require;
34
+
2
35
  // src/cloudflare.ts
3
- import { mkdirSync, writeFileSync } from "fs";
36
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
4
37
  import { join as join2 } from "path";
5
38
 
6
39
  // src/config.ts
40
+ import { existsSync, linkSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
41
+ import { randomBytes } from "crypto";
7
42
  import { homedir } from "os";
8
43
  import { dirname, join, resolve } from "path";
9
44
  var SERVICE_NAME = "shortlinks";
10
45
  var DEFAULT_DATA_DIR = join(homedir(), ".hasna", SERVICE_NAME);
46
+ function getDataDir() {
47
+ return resolve(process.env.SHORTLINKS_HOME || DEFAULT_DATA_DIR);
48
+ }
49
+ function ensureDataDir() {
50
+ const dir = getDataDir();
51
+ mkdirSync(dir, { recursive: true });
52
+ return dir;
53
+ }
54
+ function getConfigPath() {
55
+ return join(ensureDataDir(), "config.json");
56
+ }
57
+ function getClickSaltPath() {
58
+ return join(ensureDataDir(), "click-salt");
59
+ }
60
+ function getDatabasePath(explicitPath) {
61
+ if (explicitPath)
62
+ return resolve(explicitPath);
63
+ if (process.env.SHORTLINKS_DB)
64
+ return resolve(process.env.SHORTLINKS_DB);
65
+ return join(ensureDataDir(), `${SERVICE_NAME}.db`);
66
+ }
67
+ function readClickSaltFile(path) {
68
+ try {
69
+ const saved = readFileSync(path, "utf-8").trim();
70
+ return saved || null;
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+ function clickSaltError(path, error) {
76
+ const detail = error instanceof Error ? error.message : String(error);
77
+ return new Error(`Could not initialize click salt at ${path}. Set SHORTLINKS_CLICK_SALT or fix data directory permissions. ${detail}`);
78
+ }
79
+ function getClickSalt() {
80
+ const explicit = process.env.SHORTLINKS_CLICK_SALT?.trim();
81
+ if (explicit)
82
+ return explicit;
83
+ const path = getClickSaltPath();
84
+ const saved = readClickSaltFile(path);
85
+ if (saved)
86
+ return saved;
87
+ const generated = randomBytes(32).toString("hex");
88
+ const tempPath = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
89
+ try {
90
+ writeFileSync(tempPath, `${generated}
91
+ `, { flag: "wx", mode: 384 });
92
+ try {
93
+ linkSync(tempPath, path);
94
+ return generated;
95
+ } catch (error) {
96
+ const winner = readClickSaltFile(path);
97
+ if (winner)
98
+ return winner;
99
+ throw clickSaltError(path, error);
100
+ } finally {
101
+ try {
102
+ unlinkSync(tempPath);
103
+ } catch {}
104
+ }
105
+ } catch (error) {
106
+ const winner = readClickSaltFile(path);
107
+ if (winner)
108
+ return winner;
109
+ throw clickSaltError(path, error);
110
+ }
111
+ }
112
+ function loadConfig() {
113
+ const path = getConfigPath();
114
+ if (!existsSync(path))
115
+ return {};
116
+ try {
117
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
118
+ return parsed && typeof parsed === "object" ? parsed : {};
119
+ } catch {
120
+ return {};
121
+ }
122
+ }
123
+ function saveConfig(config) {
124
+ const path = getConfigPath();
125
+ mkdirSync(dirname(path), { recursive: true });
126
+ writeFileSync(path, `${JSON.stringify(config, null, 2)}
127
+ `);
128
+ }
129
+ function updateConfig(patch) {
130
+ const next = {
131
+ ...loadConfig(),
132
+ ...patch,
133
+ cloudflare: {
134
+ ...loadConfig().cloudflare,
135
+ ...patch.cloudflare
136
+ }
137
+ };
138
+ saveConfig(next);
139
+ return next;
140
+ }
11
141
  function normalizeHostname(input) {
12
142
  const raw = input.trim().toLowerCase();
13
143
  if (!raw)
@@ -27,6 +157,13 @@ function normalizeHostname(input) {
27
157
  }
28
158
  return hostname;
29
159
  }
160
+ function formatShortUrl(hostname, slug, publicBaseUrl) {
161
+ if (publicBaseUrl) {
162
+ const base = publicBaseUrl.endsWith("/") ? publicBaseUrl : `${publicBaseUrl}/`;
163
+ return new URL(slug, base).toString();
164
+ }
165
+ return `https://${hostname}/${slug}`;
166
+ }
30
167
 
31
168
  // src/cloudflare.ts
32
169
  function createCloudflarePlan(input) {
@@ -76,11 +213,11 @@ function generateWorkerScript() {
76
213
  function writeWorkerFiles(options = {}) {
77
214
  const outDir = options.outDir || "cloudflare";
78
215
  const workerName = options.workerName || "shortlinks";
79
- mkdirSync(outDir, { recursive: true });
216
+ mkdirSync2(outDir, { recursive: true });
80
217
  const workerPath = join2(outDir, `${workerName}.js`);
81
218
  const wranglerPath = join2(outDir, "wrangler.example.toml");
82
- writeFileSync(workerPath, generateWorkerScript());
83
- writeFileSync(wranglerPath, `name = "${workerName}"
219
+ writeFileSync2(workerPath, generateWorkerScript());
220
+ writeFileSync2(wranglerPath, `name = "${workerName}"
84
221
  main = "${workerName}.js"
85
222
  compatibility_date = "2026-05-01"
86
223
 
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Cloud Postgres migrations for the shortlinks serve service.
3
+ *
4
+ * PURE REMOTE (Amendment A1): these run against the shared RDS Postgres via the
5
+ * vendored storage kit's MigrationLedger. The service reads and writes the same
6
+ * cloud database — there is no local mirror, cache, or sync engine here.
7
+ *
8
+ * The domains/links/clicks schema uses SQLite-parity TEXT/INTEGER shapes so the
9
+ * existing SaaS-remnant tables are matched exactly
10
+ * (every statement is `IF NOT EXISTS` — applying the ledger never clobbers data).
11
+ * The api_keys table migrations come from @hasna/contracts/auth so the API-key
12
+ * middleware and the `contracts issue-key` issuer share one schema.
13
+ */
14
+ import { type Migration } from "../generated/storage-kit/migrations.js";
15
+ /**
16
+ * Ordered migrations for the shortlinks cloud schema, including the shared
17
+ * api_keys table from @hasna/contracts. Feed straight into the kit's
18
+ * MigrationLedger.
19
+ */
20
+ export declare const SHORTLINKS_MIGRATIONS: readonly Migration[];
@@ -0,0 +1,19 @@
1
+ import type { TypedQueryClient } from "./query.js";
2
+ import { type Migration, type MigrationRunnerOptions } from "./migrations.js";
3
+ export interface HealthResult {
4
+ ok: boolean;
5
+ /** Round-trip latency of the probe query, in milliseconds. */
6
+ latencyMs: number;
7
+ error?: string;
8
+ }
9
+ /** Cheap reachability probe: `SELECT 1`. Never throws — reports `ok: false`. */
10
+ export declare function checkHealth(client: TypedQueryClient): Promise<HealthResult>;
11
+ export interface ReadyResult extends HealthResult {
12
+ /** Migration ids that are defined but not yet applied. */
13
+ pendingMigrations: string[];
14
+ }
15
+ /**
16
+ * Readiness probe: reachable AND fully migrated. Reports `ok: false` with the
17
+ * list of pending migration ids when the schema is behind.
18
+ */
19
+ export declare function checkReady(client: TypedQueryClient, migrations: readonly Migration[], options?: MigrationRunnerOptions): Promise<ReadyResult>;
@@ -0,0 +1,7 @@
1
+ export declare const KIT_VERSION = "0.4.2";
2
+ export * from "./mode.js";
3
+ export * from "./tls.js";
4
+ export * from "./query.js";
5
+ export * from "./pool.js";
6
+ export * from "./migrations.js";
7
+ export * from "./health.js";
@@ -0,0 +1,47 @@
1
+ import type { TypedQueryClient } from "./query.js";
2
+ /** Default ledger table name. Override per app if a legacy name exists. */
3
+ export declare const DEFAULT_MIGRATION_LEDGER_TABLE = "schema_migrations";
4
+ export interface Migration {
5
+ readonly id: string;
6
+ readonly sql: string;
7
+ readonly checksum: string;
8
+ }
9
+ export type MigrationState = "already_applied" | "pending";
10
+ export interface MigrationPlanItem {
11
+ readonly migration: Migration;
12
+ readonly state: MigrationState;
13
+ }
14
+ export interface AppliedMigration {
15
+ readonly id: string;
16
+ readonly checksum: string;
17
+ readonly appliedAt: string;
18
+ }
19
+ export interface MigrationResult {
20
+ readonly dryRun: boolean;
21
+ readonly applied: AppliedMigration[];
22
+ readonly plan: MigrationPlanItem[];
23
+ }
24
+ /** Stable sha256 checksum for a migration's SQL text. */
25
+ export declare function checksumSql(sql: string): string;
26
+ /** Freeze a migration definition, computing its checksum from the SQL. */
27
+ export declare function defineMigration(id: string, sql: string): Migration;
28
+ export interface MigrationRunnerOptions {
29
+ ledgerTable?: string;
30
+ }
31
+ export declare class MigrationLedger {
32
+ private readonly client;
33
+ private readonly migrations;
34
+ private readonly ledgerTable;
35
+ constructor(client: TypedQueryClient, migrations: readonly Migration[], options?: MigrationRunnerOptions);
36
+ ensureLedger(): Promise<void>;
37
+ listApplied(): Promise<AppliedMigration[]>;
38
+ private readApplied;
39
+ /** Compute the migration plan and guard against drift/downgrade. */
40
+ private buildPlan;
41
+ /** Apply all pending migrations. With `dryRun`, report the plan only. */
42
+ migrate(opts?: {
43
+ dryRun?: boolean;
44
+ }): Promise<MigrationResult>;
45
+ }
46
+ /** Convenience: build a ledger and run all pending migrations. */
47
+ export declare function createMigrationLedger(client: TypedQueryClient, migrations: readonly Migration[], options?: MigrationRunnerOptions): MigrationLedger;
@@ -0,0 +1,47 @@
1
+ export declare const STORAGE_MODES: readonly ["local", "cloud"];
2
+ export type StorageMode = (typeof STORAGE_MODES)[number];
3
+ export declare const DEPRECATED_STORAGE_MODE_ALIASES: readonly ["remote", "hybrid", "self_hosted"];
4
+ export type Env = Record<string, string | undefined>;
5
+ export interface StorageModeNormalization {
6
+ mode: StorageMode;
7
+ /** The deprecated alias that was normalized to `cloud`, if any. */
8
+ deprecatedAlias: string | null;
9
+ }
10
+ /**
11
+ * Normalize a raw storage-mode string to the `local | cloud` runtime enum.
12
+ * Accepts deprecated aliases (`remote`, `hybrid`, `self_hosted`) and maps them
13
+ * to `cloud`. Throws on any other value.
14
+ */
15
+ export declare function normalizeStorageMode(value: string): StorageModeNormalization;
16
+ /** Upper-snake env token for an app name, e.g. `todos` -> `TODOS`. */
17
+ export declare function envToken(name: string): string;
18
+ export interface StorageEnvKeys {
19
+ /** `HASNA_<NAME>_STORAGE_MODE` then the optional `<NAME>_STORAGE_MODE` alias. */
20
+ modeKeys: string[];
21
+ /** `HASNA_<NAME>_DATABASE_URL` then the optional `<NAME>_DATABASE_URL` alias. */
22
+ databaseUrlKeys: string[];
23
+ }
24
+ /** Resolve the canonical env-key spec for an app's storage config. */
25
+ export declare function storageEnvKeys(name: string): StorageEnvKeys;
26
+ export interface StorageModeResolution {
27
+ mode: StorageMode;
28
+ /** Env key the mode came from, or `"default"`. */
29
+ source: string;
30
+ deprecatedAlias: string | null;
31
+ databaseUrlPresent: boolean;
32
+ /** Env key the database URL came from, or `null`. */
33
+ databaseUrlSource: string | null;
34
+ warning: string | null;
35
+ }
36
+ /**
37
+ * Resolve an app's storage mode from the environment per the contract env spec.
38
+ * Precedence: `HASNA_<NAME>_STORAGE_MODE`, then `<NAME>_STORAGE_MODE`, else
39
+ * `local`. Never reads secret values — only detects DATABASE_URL presence.
40
+ */
41
+ export declare function resolveStorageMode(name: string, env?: Env): StorageModeResolution;
42
+ /**
43
+ * Resolve the database URL value for an app, honoring the canonical then alias
44
+ * env keys. Returns `null` when unset. The caller is responsible for never
45
+ * logging the returned value.
46
+ */
47
+ export declare function resolveDatabaseUrl(name: string, env?: Env): string | null;
@@ -0,0 +1,33 @@
1
+ import type { Pool } from "pg";
2
+ import { type TlsResolveOptions } from "./tls.js";
3
+ import { type PoolQueryClient } from "./query.js";
4
+ export interface CreatePgPoolOptions extends TlsResolveOptions {
5
+ connectionString: string;
6
+ /** Max clients in the pool. Defaults to pg's default (10). */
7
+ max?: number;
8
+ /** Idle client timeout (ms). */
9
+ idleTimeoutMillis?: number;
10
+ /** Connection acquisition timeout (ms). */
11
+ connectionTimeoutMillis?: number;
12
+ /** Application name reported to Postgres (shows in pg_stat_activity). */
13
+ applicationName?: string;
14
+ }
15
+ /** Build a `pg.Pool` with fleet-standard TLS handling. */
16
+ export declare function createPgPool(options: CreatePgPoolOptions): Pool;
17
+ export interface CreateCloudPoolFromEnvOptions extends TlsResolveOptions {
18
+ max?: number;
19
+ idleTimeoutMillis?: number;
20
+ connectionTimeoutMillis?: number;
21
+ applicationName?: string;
22
+ }
23
+ export interface CloudPoolFromEnv {
24
+ client: PoolQueryClient;
25
+ connectionSource: string;
26
+ }
27
+ /**
28
+ * Resolve mode + database URL from the environment and build a cloud pool.
29
+ *
30
+ * Throws when the resolved mode is not `cloud` (PURE REMOTE has no Postgres in
31
+ * `local` mode) or when the database URL is missing. Never logs the URL.
32
+ */
33
+ export declare function createCloudPoolFromEnv(appName: string, options?: CreateCloudPoolFromEnvOptions): CloudPoolFromEnv;
@@ -0,0 +1,35 @@
1
+ import type { Pool, QueryResultRow } from "pg";
2
+ export interface QueryResult<T extends QueryResultRow> {
3
+ rows: T[];
4
+ rowCount: number;
5
+ }
6
+ /**
7
+ * Minimal executor contract. `pg.Pool` and `pg.PoolClient` both satisfy the
8
+ * `query` method; the wrapper builds the rest on top so tests can substitute a
9
+ * lightweight shim without pulling in a live Postgres.
10
+ */
11
+ export interface PgExecutor {
12
+ query<T extends QueryResultRow>(sql: string, params?: readonly unknown[]): Promise<{
13
+ rows: T[];
14
+ rowCount: number | null;
15
+ }>;
16
+ }
17
+ export interface TypedQueryClient {
18
+ query<T extends QueryResultRow>(sql: string, params?: readonly unknown[]): Promise<QueryResult<T>>;
19
+ many<T extends QueryResultRow>(sql: string, params?: readonly unknown[]): Promise<T[]>;
20
+ /** First row or `null`. Restored here after open-knowledge dropped it. */
21
+ get<T extends QueryResultRow>(sql: string, params?: readonly unknown[]): Promise<T | null>;
22
+ /** Exactly one row; throws if zero or more than one row is returned. */
23
+ one<T extends QueryResultRow>(sql: string, params?: readonly unknown[]): Promise<T>;
24
+ execute(sql: string, params?: readonly unknown[]): Promise<void>;
25
+ }
26
+ /** Wrap any `PgExecutor` (a Pool, a PoolClient, or a test shim) with the typed vocabulary. */
27
+ export declare function wrapExecutor(executor: PgExecutor): TypedQueryClient;
28
+ export interface PoolQueryClient extends TypedQueryClient {
29
+ readonly pool: Pool;
30
+ /** Run a callback inside a `BEGIN`/`COMMIT` transaction on a dedicated client. */
31
+ transaction<T>(fn: (client: TypedQueryClient) => Promise<T>): Promise<T>;
32
+ close(): Promise<void>;
33
+ }
34
+ /** Build a `PoolQueryClient` around a live `pg.Pool`. */
35
+ export declare function createQueryClient(pool: Pool): PoolQueryClient;
@@ -0,0 +1,25 @@
1
+ /** The `ssl` field shape accepted by `pg.Pool` / `pg.Client`. */
2
+ export type PgSslConfig = boolean | {
3
+ rejectUnauthorized: boolean;
4
+ ca?: string;
5
+ };
6
+ export interface TlsResolveOptions {
7
+ /** Inline CA bundle (PEM). Wins over every other CA source. */
8
+ ca?: string;
9
+ /** Path to a CA bundle PEM file, e.g. the Amazon RDS global bundle. */
10
+ caCertPath?: string;
11
+ /** Environment used to discover PGSSLROOTCERT / NODE_EXTRA_CA_CERTS. */
12
+ env?: Record<string, string | undefined>;
13
+ }
14
+ export type SslMode = "disable" | "prefer" | "require" | "verify-ca" | "verify-full";
15
+ /**
16
+ * Extract the effective `sslmode` from a Postgres connection string. Honors the
17
+ * `sslmode` query param and the legacy `ssl=true` boolean. Returns `disable`
18
+ * when TLS is not requested.
19
+ */
20
+ export declare function sslModeFromConnectionString(connectionString: string): SslMode;
21
+ /**
22
+ * Resolve the `pg` ssl config for a connection string. See the module header
23
+ * for the full mode table. Returns `undefined` when TLS should be off.
24
+ */
25
+ export declare function resolveTlsConfig(connectionString: string, options?: TlsResolveOptions): PgSslConfig | undefined;
package/dist/index.d.ts CHANGED
@@ -1,12 +1,15 @@
1
1
  export { ShortlinksDatabase, SQLITE_MIGRATIONS, makeId, now } from "./database.js";
2
2
  export { ShortlinksStore } from "./store.js";
3
- export { PgShortlinksStore, applyPostgresMigrations } from "./pg-store.js";
3
+ export { LocalStore, ApiStore, resolveStore, withStore } from "./client-store.js";
4
+ export { CloudShortlinksStore } from "./cloud-store.js";
5
+ export type { Store, ListLinksOptions, TotalStats } from "./store-interface.js";
6
+ export { PgShortlinksStore, createKitPgAdapter } from "./pg-store.js";
7
+ export { SHORTLINKS_MIGRATIONS } from "./db/migrations.js";
8
+ export { createServeApp } from "./serve/app.js";
9
+ export { buildOpenApiDocument } from "./serve/openapi.js";
4
10
  export { createShortlinksHandler, serveShortlinks } from "./server.js";
5
11
  export { createCloudflarePlan, generateWorkerScript, writeWorkerFiles, upsertCloudflareDnsRecord } from "./cloudflare.js";
6
12
  export { createLocalSetupPlan, registerMachinesDns } from "./local.js";
7
- export { PG_MIGRATIONS } from "./pg-migrations.js";
8
- export { CANONICAL_SHORTLINKS_POSTGRES_CLUSTER, CANONICAL_SHORTLINKS_POSTGRES_DATABASE, CANONICAL_SHORTLINKS_RUNTIME_SECRET_PATH, SHORTLINKS_RUNTIME_ENV, SHORTLINKS_RUNTIME_FALLBACK_ENV, assertShortlinksPostgresConfig, getCanonicalShortlinksPostgresConfig, getShortlinksDatabaseSsl, getShortlinksDatabaseUrl, getShortlinksRuntimeEnvName, getShortlinksRuntimeStatus, getShortlinksStoreMode, loadShortlinksRuntimeConfig, parseShortlinksStoreMode, redactDatabaseUrl, } from "./runtime.js";
9
13
  export { formatShortUrl, getConfigPath, getDataDir, getDatabasePath, loadConfig, normalizeHostname, saveConfig } from "./config.js";
10
14
  export { normalizeSlug, randomToken } from "./slug.js";
11
15
  export type { AddDomainInput, Click, ClickInput, CreateLinkInput, Domain, Link, LinkStats } from "./types.js";
12
- export type { CanonicalShortlinksPostgresConfig, RuntimeEnvStatus, ShortlinksPostgresConfig, ShortlinksRuntimeConfig, ShortlinksRuntimeEnv, ShortlinksRuntimeStatus, ShortlinksStoreMode, } from "./runtime.js";