@hasna/shortlinks 0.1.24 → 0.2.1

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.
@@ -4943,8 +4943,8 @@ var require_lib2 = __commonJS((exports, module) => {
4943
4943
  var require_package = __commonJS((exports, module) => {
4944
4944
  module.exports = {
4945
4945
  name: "@hasna/shortlinks",
4946
- version: "0.1.24",
4947
- description: "CLI-only shortlink manager for custom domains, click tracking, Cloudflare setup, and app-owned Postgres runtime support",
4946
+ version: "0.2.1",
4947
+ description: "Shortlink manager for custom domains and click tracking, with local SQLite or self-hosted cloud (/v1 API + bearer key) storage and Cloudflare setup helpers",
4948
4948
  type: "module",
4949
4949
  main: "dist/index.js",
4950
4950
  types: "dist/index.d.ts",
@@ -4970,10 +4970,6 @@ var require_package = __commonJS((exports, module) => {
4970
4970
  types: "./dist/pg-store.d.ts",
4971
4971
  import: "./dist/pg-store.js"
4972
4972
  },
4973
- "./runtime": {
4974
- types: "./dist/runtime.d.ts",
4975
- import: "./dist/runtime.js"
4976
- },
4977
4973
  "./sdk": {
4978
4974
  types: "./dist/sdk/index.d.ts",
4979
4975
  import: "./dist/sdk/index.js"
@@ -4988,7 +4984,7 @@ var require_package = __commonJS((exports, module) => {
4988
4984
  "SECURITY.md"
4989
4985
  ],
4990
4986
  scripts: {
4991
- build: "rm -rf dist && bun build src/cli/index.ts src/mcp/index.ts src/serve/index.ts src/sdk/index.ts src/index.ts src/server.ts src/cloudflare.ts src/runtime.ts src/pg-store.ts --root src --outdir dist --target bun --external @modelcontextprotocol/sdk && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir dist",
4987
+ build: "rm -rf dist && bun build src/cli/index.ts src/mcp/index.ts src/serve/index.ts src/sdk/index.ts src/index.ts src/server.ts src/cloudflare.ts src/pg-store.ts --root src --outdir dist --target bun --external @modelcontextprotocol/sdk && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir dist",
4992
4988
  "sdk:generate": "bun run scripts/generate-sdk.ts",
4993
4989
  typecheck: "tsc --noEmit",
4994
4990
  test: "bun test",
@@ -5027,7 +5023,6 @@ var require_package = __commonJS((exports, module) => {
5027
5023
  author: "Andrei Hasna <andrei@hasna.com>",
5028
5024
  license: "Apache-2.0",
5029
5025
  dependencies: {
5030
- "@hasna/contracts": "0.4.1",
5031
5026
  "@hasna/events": "^0.1.6",
5032
5027
  "@modelcontextprotocol/sdk": "^1.27.1",
5033
5028
  chalk: "^5.4.1",
@@ -5036,6 +5031,7 @@ var require_package = __commonJS((exports, module) => {
5036
5031
  pg: "^8.13.3"
5037
5032
  },
5038
5033
  devDependencies: {
5034
+ "@hasna/contracts": "^0.5.2",
5039
5035
  "@types/pg": "^8.11.11",
5040
5036
  "@types/bun": "^1.2.4",
5041
5037
  typescript: "^5.7.3"
@@ -5043,170 +5039,6 @@ var require_package = __commonJS((exports, module) => {
5043
5039
  };
5044
5040
  });
5045
5041
 
5046
- // src/runtime.ts
5047
- var SHORTLINKS_RUNTIME_ENV = {
5048
- store: "HASNA_SHORTLINKS_STORE",
5049
- databaseUrl: "HASNA_SHORTLINKS_DATABASE_URL",
5050
- databaseSsl: "HASNA_SHORTLINKS_DATABASE_SSL"
5051
- };
5052
- var SHORTLINKS_RUNTIME_FALLBACK_ENV = {
5053
- store: "SHORTLINKS_STORE",
5054
- databaseUrl: "SHORTLINKS_DATABASE_URL",
5055
- databaseSsl: "SHORTLINKS_DATABASE_SSL"
5056
- };
5057
- var CANONICAL_SHORTLINKS_POSTGRES_CLUSTER = "hasna-xyz-infra-apps-prod-postgres";
5058
- var CANONICAL_SHORTLINKS_POSTGRES_DATABASE = "shortlinks";
5059
- var CANONICAL_SHORTLINKS_RUNTIME_SECRET_PATH = "hasna/xyz/opensource/shortlinks/prod/postgres";
5060
- function getCanonicalShortlinksPostgresConfig() {
5061
- return {
5062
- cluster: CANONICAL_SHORTLINKS_POSTGRES_CLUSTER,
5063
- database: CANONICAL_SHORTLINKS_POSTGRES_DATABASE,
5064
- runtimeSecretPath: CANONICAL_SHORTLINKS_RUNTIME_SECRET_PATH,
5065
- primaryEnv: SHORTLINKS_RUNTIME_ENV.databaseUrl,
5066
- fallbackEnv: SHORTLINKS_RUNTIME_FALLBACK_ENV.databaseUrl
5067
- };
5068
- }
5069
- function parseShortlinksStoreMode(value) {
5070
- const normalized = clean(value)?.toLowerCase();
5071
- if (!normalized)
5072
- return "local";
5073
- if (normalized === "local" || normalized === "postgres")
5074
- return normalized;
5075
- if (normalized === "pg")
5076
- return "postgres";
5077
- throw new Error(`${SHORTLINKS_RUNTIME_ENV.store} must be local or postgres`);
5078
- }
5079
- function getShortlinksStoreMode(env = process.env) {
5080
- return parseShortlinksStoreMode(readRuntimeEnv(env, "store").value);
5081
- }
5082
- function getShortlinksDatabaseUrl(env = process.env) {
5083
- return readRuntimeEnv(env, "databaseUrl").value;
5084
- }
5085
- function getShortlinksDatabaseSsl(env = process.env) {
5086
- return parseBoolean(readRuntimeEnv(env, "databaseSsl").value, true);
5087
- }
5088
- function getShortlinksRuntimeEnvName(env, key) {
5089
- const primary = SHORTLINKS_RUNTIME_ENV[key];
5090
- if (clean(env[primary]))
5091
- return primary;
5092
- return SHORTLINKS_RUNTIME_FALLBACK_ENV[key];
5093
- }
5094
- function loadShortlinksRuntimeConfig(env = process.env) {
5095
- const mode = getShortlinksStoreMode(env);
5096
- const databaseUrl = getShortlinksDatabaseUrl(env);
5097
- return {
5098
- service: "shortlinks",
5099
- mode,
5100
- ...databaseUrl ? {
5101
- database: {
5102
- provider: "postgres",
5103
- url: databaseUrl,
5104
- ssl: getShortlinksDatabaseSsl(env)
5105
- }
5106
- } : {}
5107
- };
5108
- }
5109
- function assertShortlinksPostgresConfig(config) {
5110
- if (config.mode !== "postgres")
5111
- return;
5112
- if (!config.database?.url) {
5113
- throw new Error(`${SHORTLINKS_RUNTIME_ENV.databaseUrl} is required when ${SHORTLINKS_RUNTIME_ENV.store}=postgres`);
5114
- }
5115
- }
5116
- function getShortlinksRuntimeStatus(env = process.env) {
5117
- const issues = [];
5118
- const warnings = [];
5119
- let config;
5120
- try {
5121
- config = loadShortlinksRuntimeConfig(env);
5122
- } catch (error) {
5123
- issues.push(error instanceof Error ? error.message : String(error));
5124
- config = { service: "shortlinks", mode: "local" };
5125
- }
5126
- try {
5127
- assertShortlinksPostgresConfig(config);
5128
- } catch (error) {
5129
- issues.push(error instanceof Error ? error.message : String(error));
5130
- }
5131
- if (config.mode === "local" && config.database?.url) {
5132
- warnings.push(`${SHORTLINKS_RUNTIME_ENV.store}=local ignores configured Postgres database settings`);
5133
- }
5134
- return {
5135
- ok: issues.length === 0,
5136
- service: "shortlinks",
5137
- mode: config.mode,
5138
- local_default: config.mode === "local",
5139
- postgres_enabled: config.mode === "postgres",
5140
- database: {
5141
- configured: Boolean(config.database?.url),
5142
- provider: config.database?.provider ?? null,
5143
- redacted_url: redactDatabaseUrl(config.database?.url),
5144
- ssl: config.database?.ssl ?? null
5145
- },
5146
- env: runtimeEnvStatus(env),
5147
- canonical: getCanonicalShortlinksPostgresConfig(),
5148
- issues,
5149
- warnings,
5150
- no_network: true
5151
- };
5152
- }
5153
- function redactDatabaseUrl(value) {
5154
- if (!value)
5155
- return null;
5156
- try {
5157
- const url = new URL(value);
5158
- if (url.username)
5159
- url.username = "***";
5160
- if (url.password)
5161
- url.password = "***";
5162
- for (const key of Array.from(url.searchParams.keys())) {
5163
- if (isSensitiveQueryKey(key))
5164
- url.searchParams.set(key, "***");
5165
- }
5166
- return url.toString();
5167
- } catch {
5168
- return "(redacted)";
5169
- }
5170
- }
5171
- function runtimeEnvStatus(env) {
5172
- return Object.fromEntries(Object.entries(SHORTLINKS_RUNTIME_ENV).map(([key, name]) => {
5173
- const activeName = getShortlinksRuntimeEnvName(env, key);
5174
- return [
5175
- key,
5176
- {
5177
- name,
5178
- active_name: activeName,
5179
- configured: Boolean(clean(env[activeName]))
5180
- }
5181
- ];
5182
- }));
5183
- }
5184
- function readRuntimeEnv(env, key) {
5185
- const primary = SHORTLINKS_RUNTIME_ENV[key];
5186
- const primaryValue = clean(env[primary]);
5187
- if (primaryValue)
5188
- return { name: primary, value: primaryValue };
5189
- const fallback = SHORTLINKS_RUNTIME_FALLBACK_ENV[key];
5190
- return { name: fallback, value: clean(env[fallback]) };
5191
- }
5192
- function parseBoolean(value, fallback) {
5193
- const normalized = clean(value)?.toLowerCase();
5194
- if (!normalized)
5195
- return fallback;
5196
- if (["1", "true", "yes", "on"].includes(normalized))
5197
- return true;
5198
- if (["0", "false", "no", "off"].includes(normalized))
5199
- return false;
5200
- throw new Error(`${SHORTLINKS_RUNTIME_ENV.databaseSsl} must be true or false`);
5201
- }
5202
- function isSensitiveQueryKey(key) {
5203
- return /(?:^|[_-])(pass(?:word)?|pwd|secret|token|credential|auth|api[_-]?key|access[_-]?key)(?:$|[_-])/i.test(key);
5204
- }
5205
- function clean(value) {
5206
- const trimmed = value?.trim();
5207
- return trimmed ? trimmed : undefined;
5208
- }
5209
-
5210
5042
  // src/pg-store.ts
5211
5043
  import { createHash } from "crypto";
5212
5044
 
@@ -5508,47 +5340,10 @@ function parseJsonObject(value) {
5508
5340
  return {};
5509
5341
  }
5510
5342
  }
5511
- async function loadPgPool() {
5512
- const importer = new Function("specifier", "return import(specifier)");
5513
- const module = await importer("pg");
5514
- return module.Pool;
5515
- }
5516
5343
  function toPostgresSql(sql) {
5517
5344
  let index = 0;
5518
5345
  return sql.replace(/\?/g, () => `$${++index}`);
5519
5346
  }
5520
- function createPgPoolConfig(connectionString, options = {}) {
5521
- const ssl = options.ssl ?? true;
5522
- return {
5523
- connectionString,
5524
- ...ssl ? { ssl: { rejectUnauthorized: true } } : { ssl: false }
5525
- };
5526
- }
5527
-
5528
- class PgPoolAdapter {
5529
- pool;
5530
- constructor(pool) {
5531
- this.pool = pool;
5532
- }
5533
- static async create(connectionString, options = {}) {
5534
- const Pool = await loadPgPool();
5535
- return new PgPoolAdapter(new Pool(createPgPoolConfig(connectionString, options)));
5536
- }
5537
- async get(sql, ...params) {
5538
- const result = await this.pool.query(toPostgresSql(sql), params);
5539
- return result.rows[0] ?? null;
5540
- }
5541
- async all(sql, ...params) {
5542
- const result = await this.pool.query(toPostgresSql(sql), params);
5543
- return result.rows;
5544
- }
5545
- async run(sql, ...params) {
5546
- return this.pool.query(toPostgresSql(sql), params);
5547
- }
5548
- async close() {
5549
- await this.pool.end();
5550
- }
5551
- }
5552
5347
  function toIsoString(value) {
5553
5348
  if (value instanceof Date)
5554
5349
  return value.toISOString();
@@ -5630,19 +5425,9 @@ class PgShortlinksStore {
5630
5425
  constructor(pg) {
5631
5426
  this.pg = pg;
5632
5427
  }
5633
- static async fromConnectionString(connectionString, options = {}) {
5634
- return new PgShortlinksStore(await PgPoolAdapter.create(connectionString, options));
5635
- }
5636
5428
  static fromQueryClient(client) {
5637
5429
  return new PgShortlinksStore(createKitPgAdapter(client));
5638
5430
  }
5639
- static async fromEnv(env = process.env) {
5640
- const connectionString = getShortlinksDatabaseUrl(env);
5641
- if (!connectionString) {
5642
- throw new Error("HASNA_SHORTLINKS_DATABASE_URL is required when shortlinks uses the postgres store");
5643
- }
5644
- return PgShortlinksStore.fromConnectionString(connectionString, { ssl: getShortlinksDatabaseSsl(env) });
5645
- }
5646
5431
  async close() {
5647
5432
  await this.pg.close?.();
5648
5433
  }
@@ -5696,6 +5481,13 @@ class PgShortlinksStore {
5696
5481
  `);
5697
5482
  return row ? domainFromRow(row) : null;
5698
5483
  }
5484
+ async deleteDomain(hostnameOrId) {
5485
+ const domain = await this.getDomain(hostnameOrId);
5486
+ if (!domain)
5487
+ throw new Error("Domain not found.");
5488
+ await this.pg.run("DELETE FROM domains WHERE id = ?", domain.id);
5489
+ return domain;
5490
+ }
5699
5491
  async createLink(input) {
5700
5492
  const domain = input.domain ? await this.getDomain(input.domain) : await this.getDefaultDomain();
5701
5493
  if (!domain) {
@@ -5871,52 +5663,6 @@ class PgShortlinksStore {
5871
5663
  throw new Error("Could not generate an unused slug after 32 attempts.");
5872
5664
  }
5873
5665
  }
5874
- async function applyPostgresMigrations(connectionString, migrations, options = {}) {
5875
- const Pool = await loadPgPool();
5876
- const pool = new Pool(createPgPoolConfig(connectionString, options));
5877
- const client = await pool.connect();
5878
- const run = (sql, ...params) => client.query(toPostgresSql(sql), params);
5879
- const get = async (sql, ...params) => {
5880
- const result = await run(sql, ...params);
5881
- return result.rows[0] ?? null;
5882
- };
5883
- const applied = [];
5884
- const skipped = [];
5885
- try {
5886
- await run("BEGIN");
5887
- await run(`
5888
- SELECT pg_advisory_xact_lock(hashtext(?))
5889
- `, "shortlinks:migrations");
5890
- await run(`
5891
- CREATE TABLE IF NOT EXISTS _shortlinks_migrations (
5892
- id INTEGER PRIMARY KEY,
5893
- service TEXT NOT NULL DEFAULT 'shortlinks',
5894
- applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
5895
- )
5896
- `);
5897
- for (let i = 0;i < migrations.length; i += 1) {
5898
- const id = i + 1;
5899
- const existing = await get("SELECT id FROM _shortlinks_migrations WHERE id = ? LIMIT 1", id);
5900
- if (existing) {
5901
- skipped.push(id);
5902
- continue;
5903
- }
5904
- await run(migrations[i]);
5905
- await run("INSERT INTO _shortlinks_migrations (id, service, applied_at) VALUES (?, ?, now())", id, "shortlinks");
5906
- applied.push(id);
5907
- }
5908
- await run("COMMIT");
5909
- return { service: "shortlinks", applied, skipped };
5910
- } catch (error) {
5911
- try {
5912
- await run("ROLLBACK");
5913
- } catch {}
5914
- throw error;
5915
- } finally {
5916
- client.release();
5917
- await pool.end();
5918
- }
5919
- }
5920
5666
 
5921
5667
  // node_modules/.pnpm/pg@8.22.0/node_modules/pg/esm/index.mjs
5922
5668
  var import_lib = __toESM(require_lib2(), 1);
@@ -6241,7 +5987,7 @@ class MigrationLedger {
6241
5987
  }
6242
5988
  }
6243
5989
 
6244
- // node_modules/.pnpm/@hasna+contracts@0.4.1/node_modules/@hasna/contracts/dist/auth/index.js
5990
+ // node_modules/.pnpm/@hasna+contracts@0.5.2/node_modules/@hasna/contracts/dist/auth/index.js
6245
5991
  import { createHash as createHash3, createHmac, randomBytes as randomBytes3, timingSafeEqual } from "crypto";
6246
5992
  var API_KEY_TOKEN_VERSION = 1;
6247
5993
  var API_KEY_NAMESPACE = "hasna";
@@ -8362,6 +8108,11 @@ function buildOpenApiDocument(version) {
8362
8108
  properties: { deleted: { type: "boolean" }, slug: { type: "string" } },
8363
8109
  required: ["deleted"]
8364
8110
  },
8111
+ DomainDeleteResponse: {
8112
+ type: "object",
8113
+ properties: { deleted: { type: "boolean" }, hostname: { type: "string" } },
8114
+ required: ["deleted"]
8115
+ },
8365
8116
  HealthStatus: probe({ db_latency_ms: { type: "integer" } }),
8366
8117
  ReadyStatus: probe({ pending_migrations: { type: "array", items: { type: "string" } } }),
8367
8118
  VersionInfo: probe({ name: { type: "string" } }),
@@ -8432,6 +8183,19 @@ function buildOpenApiDocument(version) {
8432
8183
  }
8433
8184
  }
8434
8185
  },
8186
+ "/v1/domains/{hostname}": {
8187
+ delete: {
8188
+ operationId: "deleteDomain",
8189
+ summary: "Delete a domain and all of its links and clicks.",
8190
+ security: [{ apiKey: [] }],
8191
+ parameters: [
8192
+ { name: "hostname", in: "path", required: true, schema: { type: "string" } }
8193
+ ],
8194
+ responses: {
8195
+ "200": { content: { "application/json": { schema: { $ref: "#/components/schemas/DomainDeleteResponse" } } } }
8196
+ }
8197
+ }
8198
+ },
8435
8199
  "/v1/links": {
8436
8200
  get: {
8437
8201
  operationId: "listLinks",
@@ -8633,6 +8397,18 @@ function createServeApp(deps) {
8633
8397
  return handleError(c, error);
8634
8398
  }
8635
8399
  });
8400
+ app.delete("/v1/domains/:hostname", async (c) => {
8401
+ const denied = await requireScopes(c, [`${APP_SLUG}:write`]);
8402
+ if (denied)
8403
+ return denied;
8404
+ const hostname2 = c.req.param("hostname");
8405
+ try {
8406
+ const domain = await store.deleteDomain(hostname2);
8407
+ return c.json({ deleted: true, hostname: domain.hostname });
8408
+ } catch (error) {
8409
+ return handleError(c, error);
8410
+ }
8411
+ });
8636
8412
  app.get("/v1/links", async (c) => {
8637
8413
  const denied = await requireScopes(c, [`${APP_SLUG}:read`]);
8638
8414
  if (denied)
package/dist/server.js CHANGED
@@ -430,6 +430,17 @@ class ShortlinksStore {
430
430
  `).get(normalized, hostnameOrId);
431
431
  return row ? domainFromRow(row) : null;
432
432
  }
433
+ deleteDomain(hostnameOrId) {
434
+ const domain = this.getDomain(hostnameOrId);
435
+ if (!domain)
436
+ throw new Error("Domain not found.");
437
+ this.database.db.query("DELETE FROM domains WHERE id = ?").run(domain.id);
438
+ const config = loadConfig();
439
+ if (config.defaultDomain && normalizeHostname(config.defaultDomain) === domain.hostname) {
440
+ updateConfig({ defaultDomain: undefined, publicBaseUrl: undefined });
441
+ }
442
+ return domain;
443
+ }
433
444
  getDefaultDomain() {
434
445
  const config = loadConfig();
435
446
  if (config.defaultDomain) {
@@ -0,0 +1,42 @@
1
+ import type { AddDomainInput, Click, ClickInput, CreateLinkInput, Domain, Link, LinkStats } from "./types.js";
2
+ export interface ListLinksOptions {
3
+ domain?: string;
4
+ activeOnly?: boolean;
5
+ limit?: number;
6
+ }
7
+ export interface TotalStats {
8
+ domains: number;
9
+ links: number;
10
+ clicks: number;
11
+ }
12
+ /**
13
+ * The single storage surface shared by LocalStore and ApiStore. All methods are
14
+ * async so one call site works against either transport with no mode branching.
15
+ */
16
+ export interface Store {
17
+ /** Which transport backs this store (for banners/diagnostics only). */
18
+ readonly kind: "local" | "cloud-http";
19
+ addDomain(input: AddDomainInput): Promise<Domain>;
20
+ listDomains(): Promise<Domain[]>;
21
+ getDomain(hostnameOrId: string): Promise<Domain | null>;
22
+ getDefaultDomain(): Promise<Domain | null>;
23
+ /**
24
+ * Delete a domain (by hostname or id) and, via ON DELETE CASCADE, all of its
25
+ * links and clicks. Returns the deleted domain. Throws when not found.
26
+ */
27
+ deleteDomain(hostnameOrId: string): Promise<Domain>;
28
+ createLink(input: CreateLinkInput): Promise<Link>;
29
+ listLinks(options?: ListLinksOptions): Promise<Link[]>;
30
+ getLink(domainOrSlug: string, maybeSlug?: string): Promise<Link | null>;
31
+ resolve(hostname: string, slug: string): Promise<Link | null>;
32
+ setLinkActive(domainOrSlug: string, slugOrActive: string | boolean, active?: boolean): Promise<Link>;
33
+ deleteLink(domainOrSlug: string, maybeSlug?: string): Promise<Link>;
34
+ /**
35
+ * Record a click. Only the on-box redirect server does this against a LocalStore;
36
+ * the cloud API records clicks server-side, so ApiStore rejects this call.
37
+ */
38
+ recordClick(link: Link, input?: ClickInput): Promise<Click>;
39
+ getStats(domainOrSlug: string, maybeSlug?: string): Promise<LinkStats>;
40
+ totalStats(): Promise<TotalStats>;
41
+ close(): Promise<void>;
42
+ }
package/dist/store.d.ts CHANGED
@@ -7,6 +7,7 @@ export declare class ShortlinksStore {
7
7
  addDomain(input: AddDomainInput): Domain;
8
8
  listDomains(): Domain[];
9
9
  getDomain(hostnameOrId: string): Domain | null;
10
+ deleteDomain(hostnameOrId: string): Domain;
10
11
  getDefaultDomain(): Domain | null;
11
12
  createLink(input: CreateLinkInput): Link;
12
13
  listLinks(options?: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hasna/shortlinks",
3
- "version": "0.1.24",
4
- "description": "CLI-only shortlink manager for custom domains, click tracking, Cloudflare setup, and app-owned Postgres runtime support",
3
+ "version": "0.2.1",
4
+ "description": "Shortlink manager for custom domains and click tracking, with local SQLite or self-hosted cloud (/v1 API + bearer key) storage and Cloudflare setup helpers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -27,10 +27,6 @@
27
27
  "types": "./dist/pg-store.d.ts",
28
28
  "import": "./dist/pg-store.js"
29
29
  },
30
- "./runtime": {
31
- "types": "./dist/runtime.d.ts",
32
- "import": "./dist/runtime.js"
33
- },
34
30
  "./sdk": {
35
31
  "types": "./dist/sdk/index.d.ts",
36
32
  "import": "./dist/sdk/index.js"
@@ -45,7 +41,7 @@
45
41
  "SECURITY.md"
46
42
  ],
47
43
  "scripts": {
48
- "build": "rm -rf dist && bun build src/cli/index.ts src/mcp/index.ts src/serve/index.ts src/sdk/index.ts src/index.ts src/server.ts src/cloudflare.ts src/runtime.ts src/pg-store.ts --root src --outdir dist --target bun --external @modelcontextprotocol/sdk && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir dist",
44
+ "build": "rm -rf dist && bun build src/cli/index.ts src/mcp/index.ts src/serve/index.ts src/sdk/index.ts src/index.ts src/server.ts src/cloudflare.ts src/pg-store.ts --root src --outdir dist --target bun --external @modelcontextprotocol/sdk && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir dist",
49
45
  "sdk:generate": "bun run scripts/generate-sdk.ts",
50
46
  "typecheck": "tsc --noEmit",
51
47
  "test": "bun test",
@@ -84,7 +80,6 @@
84
80
  "author": "Andrei Hasna <andrei@hasna.com>",
85
81
  "license": "Apache-2.0",
86
82
  "dependencies": {
87
- "@hasna/contracts": "0.4.1",
88
83
  "@hasna/events": "^0.1.6",
89
84
  "@modelcontextprotocol/sdk": "^1.27.1",
90
85
  "chalk": "^5.4.1",
@@ -93,6 +88,7 @@
93
88
  "pg": "^8.13.3"
94
89
  },
95
90
  "devDependencies": {
91
+ "@hasna/contracts": "^0.5.2",
96
92
  "@types/pg": "^8.11.11",
97
93
  "@types/bun": "^1.2.4",
98
94
  "typescript": "^5.7.3"
@@ -1 +0,0 @@
1
- export declare const PG_MIGRATIONS: string[];
package/dist/runtime.d.ts DELETED
@@ -1,65 +0,0 @@
1
- export type ShortlinksStoreMode = "local" | "postgres";
2
- export type ShortlinksRuntimeEnv = Record<string, string | undefined>;
3
- export interface ShortlinksPostgresConfig {
4
- provider: "postgres";
5
- url: string;
6
- ssl: boolean;
7
- }
8
- export interface ShortlinksRuntimeConfig {
9
- service: "shortlinks";
10
- mode: ShortlinksStoreMode;
11
- database?: ShortlinksPostgresConfig;
12
- }
13
- export declare const SHORTLINKS_RUNTIME_ENV: {
14
- readonly store: "HASNA_SHORTLINKS_STORE";
15
- readonly databaseUrl: "HASNA_SHORTLINKS_DATABASE_URL";
16
- readonly databaseSsl: "HASNA_SHORTLINKS_DATABASE_SSL";
17
- };
18
- export declare const SHORTLINKS_RUNTIME_FALLBACK_ENV: {
19
- readonly store: "SHORTLINKS_STORE";
20
- readonly databaseUrl: "SHORTLINKS_DATABASE_URL";
21
- readonly databaseSsl: "SHORTLINKS_DATABASE_SSL";
22
- };
23
- export declare const CANONICAL_SHORTLINKS_POSTGRES_CLUSTER = "hasna-xyz-infra-apps-prod-postgres";
24
- export declare const CANONICAL_SHORTLINKS_POSTGRES_DATABASE = "shortlinks";
25
- export declare const CANONICAL_SHORTLINKS_RUNTIME_SECRET_PATH = "hasna/xyz/opensource/shortlinks/prod/postgres";
26
- export interface CanonicalShortlinksPostgresConfig {
27
- cluster: typeof CANONICAL_SHORTLINKS_POSTGRES_CLUSTER;
28
- database: typeof CANONICAL_SHORTLINKS_POSTGRES_DATABASE;
29
- runtimeSecretPath: typeof CANONICAL_SHORTLINKS_RUNTIME_SECRET_PATH;
30
- primaryEnv: typeof SHORTLINKS_RUNTIME_ENV.databaseUrl;
31
- fallbackEnv: typeof SHORTLINKS_RUNTIME_FALLBACK_ENV.databaseUrl;
32
- }
33
- export interface RuntimeEnvStatus {
34
- name: string;
35
- active_name: string;
36
- configured: boolean;
37
- }
38
- export interface ShortlinksRuntimeStatus {
39
- ok: boolean;
40
- service: "shortlinks";
41
- mode: ShortlinksStoreMode;
42
- local_default: boolean;
43
- postgres_enabled: boolean;
44
- database: {
45
- configured: boolean;
46
- provider: "postgres" | null;
47
- redacted_url: string | null;
48
- ssl: boolean | null;
49
- };
50
- env: Record<keyof typeof SHORTLINKS_RUNTIME_ENV, RuntimeEnvStatus>;
51
- canonical: CanonicalShortlinksPostgresConfig;
52
- issues: string[];
53
- warnings: string[];
54
- no_network: true;
55
- }
56
- export declare function getCanonicalShortlinksPostgresConfig(): CanonicalShortlinksPostgresConfig;
57
- export declare function parseShortlinksStoreMode(value: string | undefined): ShortlinksStoreMode;
58
- export declare function getShortlinksStoreMode(env?: ShortlinksRuntimeEnv): ShortlinksStoreMode;
59
- export declare function getShortlinksDatabaseUrl(env?: ShortlinksRuntimeEnv): string | undefined;
60
- export declare function getShortlinksDatabaseSsl(env?: ShortlinksRuntimeEnv): boolean;
61
- export declare function getShortlinksRuntimeEnvName(env: ShortlinksRuntimeEnv, key: keyof typeof SHORTLINKS_RUNTIME_ENV): string;
62
- export declare function loadShortlinksRuntimeConfig(env?: ShortlinksRuntimeEnv): ShortlinksRuntimeConfig;
63
- export declare function assertShortlinksPostgresConfig(config: ShortlinksRuntimeConfig): void;
64
- export declare function getShortlinksRuntimeStatus(env?: ShortlinksRuntimeEnv): ShortlinksRuntimeStatus;
65
- export declare function redactDatabaseUrl(value: string | undefined): string | null;