@secondlayer/shared 4.3.4 → 5.0.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.
- package/dist/src/crypto/hmac.js +3 -3
- package/dist/src/crypto/hmac.js.map +3 -3
- package/dist/src/crypto/secrets.js.map +2 -2
- package/dist/src/db/index.js.map +2 -2
- package/dist/src/db/queries/account-usage.js +137 -29
- package/dist/src/db/queries/account-usage.js.map +3 -3
- package/dist/src/db/queries/subscriptions.js +3 -3
- package/dist/src/db/queries/subscriptions.js.map +5 -5
- package/dist/src/db/queries/tenants.js.map +2 -2
- package/dist/src/errors.d.ts +1 -2
- package/dist/src/errors.js +3 -2
- package/dist/src/errors.js.map +3 -3
- package/dist/src/index.d.ts +51 -3
- package/dist/src/index.js +351 -4
- package/dist/src/index.js.map +9 -8
- package/dist/src/logger.js.map +2 -2
- package/dist/src/node/archive-client.js.map +2 -2
- package/dist/src/node/hiro-client.js +27 -23
- package/dist/src/node/hiro-client.js.map +4 -4
- package/dist/src/node/hiro-pg-client.js +2 -2
- package/dist/src/node/hiro-pg-client.js.map +3 -3
- package/dist/src/node/local-client.js.map +2 -2
- package/dist/src/pricing.d.ts +51 -7
- package/dist/src/pricing.js +143 -28
- package/dist/src/pricing.js.map +3 -3
- package/dist/src/schemas/filters.js.map +2 -2
- package/dist/src/schemas/index.d.ts +9 -0
- package/dist/src/schemas/index.js.map +3 -3
- package/dist/src/schemas/subgraphs.d.ts +9 -0
- package/dist/src/schemas/subgraphs.js.map +1 -1
- package/dist/src/subgraphs/spec.d.ts +104 -0
- package/dist/src/subgraphs/spec.js +366 -0
- package/dist/src/subgraphs/spec.js.map +10 -0
- package/migrations/0001_initial.ts +2 -0
- package/migrations/0002_api_keys.ts +2 -0
- package/migrations/0003_tenant_isolation.ts +5 -3
- package/migrations/0004_accounts_and_usage.ts +2 -0
- package/migrations/0005_sessions.ts +2 -0
- package/migrations/0006_tx_index.ts +2 -0
- package/migrations/0007_contracts.ts +2 -0
- package/migrations/0008_drop_contracts.ts +2 -0
- package/migrations/0009_waitlist.ts +2 -0
- package/migrations/0010_waitlist_status.ts +2 -0
- package/migrations/0011_account_insights.ts +2 -0
- package/migrations/0012_view_health_snapshots.ts +2 -0
- package/migrations/0013_view_processing_stats.ts +2 -0
- package/migrations/0014_view_table_snapshots.ts +2 -0
- package/migrations/0015_rename_views_to_subgraphs.ts +2 -0
- package/migrations/0016_rename_webhook_to_endpoint.ts +2 -0
- package/migrations/0017_security_hardening.ts +2 -0
- package/migrations/0018_subgraph_gaps.ts +2 -0
- package/migrations/0021_tx_function_args_result.ts +3 -4
- package/migrations/0022_marketplace.ts +4 -5
- package/migrations/0023_projects.ts +3 -1
- package/migrations/0024_chat_sessions.ts +3 -1
- package/migrations/0025_chat_session_summary.ts +3 -4
- package/migrations/0026_workflows.ts +4 -5
- package/migrations/0027_workflow_cursors.ts +3 -1
- package/migrations/0028_subgraph_account_scoping.ts +7 -2
- package/migrations/0029_subgraph_handler_code.ts +3 -4
- package/migrations/0030_workflow_source_code.ts +2 -0
- package/migrations/0031_subgraph_source_code.ts +2 -0
- package/migrations/0032_drop_streams_tables.ts +9 -9
- package/package.json +6 -2
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
"sources": ["../src/mode.ts", "../src/crypto/secrets.ts", "../src/db/queries/tenants.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * Instance modes for the Secondlayer platform.\n *\n * - `oss`: self-hosted, single-tenant. No auth middleware, no platform routes\n * (projects, admin, tenants). Everything runs against a single\n * `DATABASE_URL`. Intended for `docker compose up`.\n *\n * - `dedicated`: per-customer managed instance. JWT-based auth (anon =\n * read-only, service = full). Dual-DB mode — shared source indexer DB for\n * block reads, per-tenant target DB for subgraph data. No platform-wide\n * routes mounted (no cross-tenant accounts).\n *\n * - `platform`: control-plane mode. Magic-link auth, API keys, projects,\n * tenants, admin. Serves the dashboard + CLI against a single shared DB.\n */\n\nexport type InstanceMode = \"oss\" | \"dedicated\" | \"platform\";\n\nconst VALID_MODES: readonly InstanceMode[] = [\"oss\", \"dedicated\", \"platform\"];\n\n/**\n * Resolve the active instance mode from `process.env.INSTANCE_MODE`.\n * Defaults to `\"oss\"` — the safest default for self-hosters who deploy\n * without setting the variable.\n */\nexport function getInstanceMode(): InstanceMode {\n\tconst raw = process.env.INSTANCE_MODE?.trim().toLowerCase();\n\tif (raw && (VALID_MODES as readonly string[]).includes(raw)) {\n\t\treturn raw as InstanceMode;\n\t}\n\treturn \"oss\";\n}\n\n/** True when the active mode is `\"platform\"` (shared multi-tenant). */\nexport function isPlatformMode(): boolean {\n\treturn getInstanceMode() === \"platform\";\n}\n\n/** True when the active mode is `\"oss\"` (self-hosted). */\nexport function isOssMode(): boolean {\n\treturn getInstanceMode() === \"oss\";\n}\n\n/** True when the active mode is `\"dedicated\"` (per-tenant managed). */\nexport function isDedicatedMode(): boolean {\n\treturn getInstanceMode() === \"dedicated\";\n}\n",
|
|
6
|
-
"import { createCipheriv, createDecipheriv, randomBytes } from \"node:crypto\";\nimport {\n\tappendFileSync,\n\tcloseSync,\n\texistsSync,\n\topenSync,\n\treadFileSync,\n\tunlinkSync,\n} from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { getInstanceMode } from \"../mode.ts\";\n\n/**\n * AES-256-GCM symmetric envelope for encrypted secrets at rest (tenant keys,\n * subscription signing secrets, etc.).\n *\n * Ciphertext layout: `iv (12 bytes) || authTag (16 bytes) || ciphertext`\n *\n * The key comes from `SECONDLAYER_SECRETS_KEY` — 32 bytes hex. In OSS mode,\n * if the env var is unset on first use we autogenerate a key and persist it\n * to `.env.local` in the current working directory so subsequent restarts\n * pick it up without user intervention. Dedicated/platform modes throw —\n * those runtimes must provision the key explicitly.\n *\n * Rotation strategy: re-encrypt all rows with the new key and swap the env\n * var. Not zero-downtime, but acceptable at v2 scale. For real KMS (AWS\n * KMS, Vault, GCP KMS), wrap the same byte layout behind an\n * `EncryptSecret`/`DecryptSecret` interface and swap at startup.\n */\n\nconst KEY_ENV = \"SECONDLAYER_SECRETS_KEY\";\nconst IV_LEN = 12;\nconst TAG_LEN = 16;\n\nfunction readExistingKey(envPath: string): string | null {\n\tif (!existsSync(envPath)) return null;\n\tconst contents = readFileSync(envPath, \"utf8\");\n\tconst match = contents.match(/^SECONDLAYER_SECRETS_KEY=([a-fA-F0-9]{64})/m);\n\treturn match ? match[1]! : null;\n}\n\n/**\n * Atomic file lock via `openSync(..., \"wx\")` — O_CREAT | O_EXCL. If two\n * processes race on cold-compose start, exactly one creates the lock\n * file; the loser polls until the winner finishes writing `.env.local`,\n * then reads the winner's key. Stale locks (process crashed mid-write)\n * are cleaned after `STALE_LOCK_MS`.\n */\nconst STALE_LOCK_MS = 10_000;\nconst POLL_MS = 25;\n\nfunction bootstrapOssKey(): string {\n\tconst envPath = resolve(process.cwd(), \".env.local\");\n\n\t// Fast path — key already on disk from a prior run.\n\tconst existing = readExistingKey(envPath);\n\tif (existing) {\n\t\tprocess.env[KEY_ENV] = existing;\n\t\treturn existing;\n\t}\n\n\tconst lockPath = `${envPath}.secret-bootstrap.lock`;\n\tlet lockFd: number | null = null;\n\ttry {\n\t\tlockFd = openSync(lockPath, \"wx\", 0o600);\n\t} catch (err) {\n\t\tconst e = err as NodeJS.ErrnoException;\n\t\tif (e.code !== \"EEXIST\") throw err;\n\t}\n\n\tif (lockFd === null) {\n\t\t// Another process is bootstrapping. Poll for its result.\n\t\tconst deadline = Date.now() + STALE_LOCK_MS;\n\t\twhile (Date.now() < deadline) {\n\t\t\tconst key = readExistingKey(envPath);\n\t\t\tif (key) {\n\t\t\t\tprocess.env[KEY_ENV] = key;\n\t\t\t\treturn key;\n\t\t\t}\n\t\t\tBun.sleepSync(POLL_MS);\n\t\t}\n\t\t// Lock holder died mid-write — force-clean and retry once.\n\t\ttry {\n\t\t\tunlinkSync(lockPath);\n\t\t} catch {}\n\t\treturn bootstrapOssKey();\n\t}\n\n\ttry {\n\t\tconst hex = randomBytes(32).toString(\"hex\");\n\t\tconst line = `${existsSync(envPath) ? \"\\n\" : \"\"}${KEY_ENV}=${hex}\\n`;\n\t\tappendFileSync(envPath, line, { mode: 0o600 });\n\t\tprocess.env[KEY_ENV] = hex;\n\t\tconsole.log(\n\t\t\t`[secondlayer] generated ${KEY_ENV}; saved to ${envPath} (mode 0600)`,\n\t\t);\n\t\treturn hex;\n\t} finally {\n\t\tcloseSync(lockFd);\n\t\ttry {\n\t\t\tunlinkSync(lockPath);\n\t\t} catch {}\n\t}\n}\n\nfunction loadKey(): Buffer {\n\tlet hex = process.env[KEY_ENV];\n\tif (!hex) {\n\t\tif (getInstanceMode() === \"oss\") {\n\t\t\thex = bootstrapOssKey();\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t`${KEY_ENV} not set. Generate one with: openssl rand -hex 32`,\n\t\t\t);\n\t\t}\n\t}\n\tconst key = Buffer.from(hex, \"hex\");\n\tif (key.length !== 32) {\n\t\tthrow new Error(`${KEY_ENV} must be 32 bytes hex (got ${key.length})`);\n\t}\n\treturn key;\n}\n\nlet _cachedKey: Buffer | null = null;\nfunction getKey(): Buffer {\n\tif (!_cachedKey) _cachedKey = loadKey();\n\treturn _cachedKey;\n}\n\nexport function encryptSecret(plaintext: string): Buffer {\n\tconst key = getKey();\n\tconst iv = randomBytes(IV_LEN);\n\tconst cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n\tconst ciphertext = Buffer.concat([\n\t\tcipher.update(plaintext, \"utf8\"),\n\t\tcipher.final(),\n\t]);\n\tconst tag = cipher.getAuthTag();\n\treturn Buffer.concat([iv, tag, ciphertext]);\n}\n\nexport function decryptSecret(envelope: Buffer): string {\n\tconst key = getKey();\n\tconst iv = envelope.subarray(0, IV_LEN);\n\tconst tag = envelope.subarray(IV_LEN, IV_LEN + TAG_LEN);\n\tconst ciphertext = envelope.subarray(IV_LEN + TAG_LEN);\n\tconst decipher = createDecipheriv(\"aes-256-gcm\", key, iv);\n\tdecipher.setAuthTag(tag);\n\treturn decipher.update(ciphertext).toString(\"utf8\") + decipher.final(\"utf8\");\n}\n\n/** Generate a fresh 32-byte hex key suitable for `SECONDLAYER_SECRETS_KEY`. */\nexport function generateSecretsKey(): string {\n\treturn randomBytes(32).toString(\"hex\");\n}\n",
|
|
6
|
+
"import { createCipheriv, createDecipheriv, randomBytes } from \"node:crypto\";\nimport {\n\tappendFileSync,\n\tcloseSync,\n\texistsSync,\n\topenSync,\n\treadFileSync,\n\tunlinkSync,\n} from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { getInstanceMode } from \"../mode.ts\";\n\n/**\n * AES-256-GCM symmetric envelope for encrypted secrets at rest (tenant keys,\n * subscription signing secrets, etc.).\n *\n * Ciphertext layout: `iv (12 bytes) || authTag (16 bytes) || ciphertext`\n *\n * The key comes from `SECONDLAYER_SECRETS_KEY` — 32 bytes hex. In OSS mode,\n * if the env var is unset on first use we autogenerate a key and persist it\n * to `.env.local` in the current working directory so subsequent restarts\n * pick it up without user intervention. Dedicated/platform modes throw —\n * those runtimes must provision the key explicitly.\n *\n * Rotation strategy: re-encrypt all rows with the new key and swap the env\n * var. Not zero-downtime, but acceptable at v2 scale. For real KMS (AWS\n * KMS, Vault, GCP KMS), wrap the same byte layout behind an\n * `EncryptSecret`/`DecryptSecret` interface and swap at startup.\n */\n\nconst KEY_ENV = \"SECONDLAYER_SECRETS_KEY\";\nconst IV_LEN = 12;\nconst TAG_LEN = 16;\n\nfunction readExistingKey(envPath: string): string | null {\n\tif (!existsSync(envPath)) return null;\n\tconst contents = readFileSync(envPath, \"utf8\");\n\tconst match = contents.match(/^SECONDLAYER_SECRETS_KEY=([a-fA-F0-9]{64})/m);\n\t// biome-ignore lint/style/noNonNullAssertion: value is non-null after preceding check or by construction; TS narrowing limitation\n\treturn match ? match[1]! : null;\n}\n\n/**\n * Atomic file lock via `openSync(..., \"wx\")` — O_CREAT | O_EXCL. If two\n * processes race on cold-compose start, exactly one creates the lock\n * file; the loser polls until the winner finishes writing `.env.local`,\n * then reads the winner's key. Stale locks (process crashed mid-write)\n * are cleaned after `STALE_LOCK_MS`.\n */\nconst STALE_LOCK_MS = 10_000;\nconst POLL_MS = 25;\n\nfunction bootstrapOssKey(): string {\n\tconst envPath = resolve(process.cwd(), \".env.local\");\n\n\t// Fast path — key already on disk from a prior run.\n\tconst existing = readExistingKey(envPath);\n\tif (existing) {\n\t\tprocess.env[KEY_ENV] = existing;\n\t\treturn existing;\n\t}\n\n\tconst lockPath = `${envPath}.secret-bootstrap.lock`;\n\tlet lockFd: number | null = null;\n\ttry {\n\t\tlockFd = openSync(lockPath, \"wx\", 0o600);\n\t} catch (err) {\n\t\tconst e = err as NodeJS.ErrnoException;\n\t\tif (e.code !== \"EEXIST\") throw err;\n\t}\n\n\tif (lockFd === null) {\n\t\t// Another process is bootstrapping. Poll for its result.\n\t\tconst deadline = Date.now() + STALE_LOCK_MS;\n\t\twhile (Date.now() < deadline) {\n\t\t\tconst key = readExistingKey(envPath);\n\t\t\tif (key) {\n\t\t\t\tprocess.env[KEY_ENV] = key;\n\t\t\t\treturn key;\n\t\t\t}\n\t\t\tBun.sleepSync(POLL_MS);\n\t\t}\n\t\t// Lock holder died mid-write — force-clean and retry once.\n\t\ttry {\n\t\t\tunlinkSync(lockPath);\n\t\t} catch {}\n\t\treturn bootstrapOssKey();\n\t}\n\n\ttry {\n\t\tconst hex = randomBytes(32).toString(\"hex\");\n\t\tconst line = `${existsSync(envPath) ? \"\\n\" : \"\"}${KEY_ENV}=${hex}\\n`;\n\t\tappendFileSync(envPath, line, { mode: 0o600 });\n\t\tprocess.env[KEY_ENV] = hex;\n\t\tconsole.log(\n\t\t\t`[secondlayer] generated ${KEY_ENV}; saved to ${envPath} (mode 0600)`,\n\t\t);\n\t\treturn hex;\n\t} finally {\n\t\tcloseSync(lockFd);\n\t\ttry {\n\t\t\tunlinkSync(lockPath);\n\t\t} catch {}\n\t}\n}\n\nfunction loadKey(): Buffer {\n\tlet hex = process.env[KEY_ENV];\n\tif (!hex) {\n\t\tif (getInstanceMode() === \"oss\") {\n\t\t\thex = bootstrapOssKey();\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t`${KEY_ENV} not set. Generate one with: openssl rand -hex 32`,\n\t\t\t);\n\t\t}\n\t}\n\tconst key = Buffer.from(hex, \"hex\");\n\tif (key.length !== 32) {\n\t\tthrow new Error(`${KEY_ENV} must be 32 bytes hex (got ${key.length})`);\n\t}\n\treturn key;\n}\n\nlet _cachedKey: Buffer | null = null;\nfunction getKey(): Buffer {\n\tif (!_cachedKey) _cachedKey = loadKey();\n\treturn _cachedKey;\n}\n\nexport function encryptSecret(plaintext: string): Buffer {\n\tconst key = getKey();\n\tconst iv = randomBytes(IV_LEN);\n\tconst cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n\tconst ciphertext = Buffer.concat([\n\t\tcipher.update(plaintext, \"utf8\"),\n\t\tcipher.final(),\n\t]);\n\tconst tag = cipher.getAuthTag();\n\treturn Buffer.concat([iv, tag, ciphertext]);\n}\n\nexport function decryptSecret(envelope: Buffer): string {\n\tconst key = getKey();\n\tconst iv = envelope.subarray(0, IV_LEN);\n\tconst tag = envelope.subarray(IV_LEN, IV_LEN + TAG_LEN);\n\tconst ciphertext = envelope.subarray(IV_LEN + TAG_LEN);\n\tconst decipher = createDecipheriv(\"aes-256-gcm\", key, iv);\n\tdecipher.setAuthTag(tag);\n\treturn decipher.update(ciphertext).toString(\"utf8\") + decipher.final(\"utf8\");\n}\n\n/** Generate a fresh 32-byte hex key suitable for `SECONDLAYER_SECRETS_KEY`. */\nexport function generateSecretsKey(): string {\n\treturn randomBytes(32).toString(\"hex\");\n}\n",
|
|
7
7
|
"import { type Kysely, sql } from \"kysely\";\nimport { decryptSecret, encryptSecret } from \"../../crypto/secrets.ts\";\nimport type { Database, InsertTenant, Tenant, TenantStatus } from \"../types.ts\";\n\n/**\n * Tenant registry queries. Encrypted columns are stored as `bytea` and\n * transparently encrypted/decrypted via `encryptSecret`/`decryptSecret`.\n *\n * Never return decrypted values from listTenants — only `getTenantCredentials`\n * surfaces plaintext, and only when explicitly called by a caller that\n * needs to hand creds to a CLI or dashboard session.\n */\n\nexport interface NewTenantInput {\n\taccountId: string;\n\tslug: string;\n\tplan: string;\n\tcpus: number;\n\tmemoryMb: number;\n\tstorageLimitMb: number;\n\tpgContainerId: string;\n\tapiContainerId: string;\n\tprocessorContainerId: string;\n\ttargetDatabaseUrl: string;\n\ttenantJwtSecret: string;\n\tanonKey: string;\n\tserviceKey: string;\n\tapiUrlInternal: string;\n\tapiUrlPublic: string;\n\tprojectId?: string;\n}\n\nexport async function insertTenant(\n\tdb: Kysely<Database>,\n\tinput: NewTenantInput,\n): Promise<Tenant> {\n\tconst row: InsertTenant = {\n\t\taccount_id: input.accountId,\n\t\tslug: input.slug,\n\t\tstatus: \"active\",\n\t\tplan: input.plan,\n\t\tcpus: input.cpus,\n\t\tmemory_mb: input.memoryMb,\n\t\tstorage_limit_mb: input.storageLimitMb,\n\t\tpg_container_id: input.pgContainerId,\n\t\tapi_container_id: input.apiContainerId,\n\t\tprocessor_container_id: input.processorContainerId,\n\t\ttarget_database_url_enc: encryptSecret(input.targetDatabaseUrl),\n\t\ttenant_jwt_secret_enc: encryptSecret(input.tenantJwtSecret),\n\t\tanon_key_enc: encryptSecret(input.anonKey),\n\t\tservice_key_enc: encryptSecret(input.serviceKey),\n\t\tapi_url_internal: input.apiUrlInternal,\n\t\tapi_url_public: input.apiUrlPublic,\n\t\tproject_id: input.projectId ?? null,\n\t};\n\treturn db\n\t\t.insertInto(\"tenants\")\n\t\t.values(row)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getTenantByAccount(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<Tenant | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"status\", \"<>\", \"deleted\")\n\t\t.orderBy(\"created_at\", \"desc\")\n\t\t.executeTakeFirst();\n\treturn row ?? null;\n}\n\nexport async function getTenantBySlug(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<Tenant | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\treturn row ?? null;\n}\n\nexport async function listTenantsByStatus(\n\tdb: Kysely<Database>,\n\tstatus: TenantStatus,\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"=\", status)\n\t\t.execute();\n}\n\n/**\n * Tenants considered \"idle\" for auto-pause on the Hobby tier. Active =\n * any successful tenant-API request bumped `last_active_at` within the\n * threshold.\n */\nexport async function listIdleHobbyTenants(\n\tdb: Kysely<Database>,\n\tidleSince: Date,\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"=\", \"active\")\n\t\t.where(\"plan\", \"=\", \"hobby\")\n\t\t.where(\"last_active_at\", \"<\", idleSince)\n\t\t.execute();\n}\n\n/**\n * Bump `last_active_at` for a tenant. Callers are expected to throttle\n * (don't hammer on every request) — the tenant-API activity middleware\n * enforces a 60s per-tenant min between writes.\n */\nexport async function bumpTenantActivity(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"tenants\")\n\t\t.set({ last_active_at: new Date() })\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.execute();\n}\n\nexport async function listSuspendedOlderThan(\n\tdb: Kysely<Database>,\n\tolderThan: Date,\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"=\", \"suspended\")\n\t\t.where(\"suspended_at\", \"<\", olderThan)\n\t\t.execute();\n}\n\nexport async function setTenantStatus(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tstatus: TenantStatus,\n): Promise<void> {\n\tconst patch: Record<string, unknown> = {\n\t\tstatus,\n\t\tupdated_at: new Date(),\n\t};\n\tif (status === \"suspended\") patch.suspended_at = new Date();\n\tif (status === \"active\") patch.suspended_at = null;\n\tawait db.updateTable(\"tenants\").set(patch).where(\"slug\", \"=\", slug).execute();\n}\n\nexport async function recordHealthCheck(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tstorageUsedMb: number | null,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"tenants\")\n\t\t.set({\n\t\t\tlast_health_check_at: new Date(),\n\t\t\tstorage_used_mb: storageUsedMb,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.execute();\n}\n\n/**\n * Record a storage measurement into the current calendar month's bucket.\n * Maintains peak, running average, and the most recent value in a single\n * upsert. Billing will consume this later; for now the table just gives\n * us evidence of usage over time.\n */\nexport async function recordMonthlyUsage(\n\tdb: Kysely<Database>,\n\ttenantId: string,\n\tstorageMb: number,\n): Promise<void> {\n\t// Bucket is the first day of the current month (UTC), so the unique\n\t// (tenant_id, period_month) constraint groups all samples cleanly.\n\tconst now = new Date();\n\tconst periodMonth = new Date(\n\t\tDate.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1),\n\t);\n\n\t// Running mean: avg_new = (avg_old * n + x) / (n + 1). Doing it in SQL\n\t// keeps the write atomic — no read-modify-write race between ticks.\n\tawait sql`\n\t\tINSERT INTO tenant_usage_monthly (\n\t\t\ttenant_id, period_month,\n\t\t\tstorage_peak_mb, storage_avg_mb, storage_last_mb,\n\t\t\tmeasurements, first_at, last_at\n\t\t) VALUES (\n\t\t\t${tenantId}, ${periodMonth},\n\t\t\t${storageMb}, ${storageMb}, ${storageMb},\n\t\t\t1, now(), now()\n\t\t)\n\t\tON CONFLICT (tenant_id, period_month) DO UPDATE SET\n\t\t\tstorage_peak_mb = GREATEST(tenant_usage_monthly.storage_peak_mb, EXCLUDED.storage_last_mb),\n\t\t\tstorage_avg_mb = (\n\t\t\t\t(tenant_usage_monthly.storage_avg_mb * tenant_usage_monthly.measurements + EXCLUDED.storage_last_mb)\n\t\t\t\t/ (tenant_usage_monthly.measurements + 1)\n\t\t\t),\n\t\t\tstorage_last_mb = EXCLUDED.storage_last_mb,\n\t\t\tmeasurements = tenant_usage_monthly.measurements + 1,\n\t\t\tlast_at = now()\n\t`.execute(db);\n}\n\nexport async function updateTenantPlan(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tplan: string,\n\tcpus: number,\n\tmemoryMb: number,\n\tstorageLimitMb: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"tenants\")\n\t\t.set({\n\t\t\tplan,\n\t\t\tcpus,\n\t\t\tmemory_mb: memoryMb,\n\t\t\tstorage_limit_mb: storageLimitMb,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.execute();\n}\n\nexport type RotateType = \"service\" | \"anon\" | \"both\";\n\n/**\n * Bump the selected gen counter(s) by 1 and return the new values.\n * Used by the key-rotate endpoint to force the tenant API to reject\n * previously-issued tokens of the rotated role(s).\n */\nexport async function bumpTenantKeyGen(\n\tdb: Kysely<Database>,\n\tslug: string,\n\ttype: RotateType,\n): Promise<{ serviceGen: number; anonGen: number }> {\n\tconst bumpService = type === \"service\" || type === \"both\";\n\tconst bumpAnon = type === \"anon\" || type === \"both\";\n\tconst row = await db\n\t\t.updateTable(\"tenants\")\n\t\t.set((eb) => ({\n\t\t\tservice_gen: bumpService\n\t\t\t\t? eb(\"service_gen\", \"+\", 1)\n\t\t\t\t: eb.ref(\"service_gen\"),\n\t\t\tanon_gen: bumpAnon ? eb(\"anon_gen\", \"+\", 1) : eb.ref(\"anon_gen\"),\n\t\t\tupdated_at: new Date(),\n\t\t}))\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.returning([\"service_gen\", \"anon_gen\"])\n\t\t.executeTakeFirstOrThrow();\n\treturn { serviceGen: row.service_gen, anonGen: row.anon_gen };\n}\n\n/**\n * Replace the encrypted key columns after a successful rotate. Only the\n * rotated column(s) are written — the other stays untouched.\n */\nexport async function updateTenantKeys(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tkeys: { serviceKey?: string; anonKey?: string },\n): Promise<void> {\n\tconst patch: Record<string, unknown> = { updated_at: new Date() };\n\tif (keys.serviceKey) patch.service_key_enc = encryptSecret(keys.serviceKey);\n\tif (keys.anonKey) patch.anon_key_enc = encryptSecret(keys.anonKey);\n\tif (Object.keys(patch).length === 1) return; // only updated_at — nothing to write\n\tawait db.updateTable(\"tenants\").set(patch).where(\"slug\", \"=\", slug).execute();\n}\n\n/**\n * Hard-delete a tenant row. Call only AFTER the provisioner has torn down\n * containers + volume; otherwise orphaned resources linger. Returns whether\n * a row was actually deleted.\n */\nexport async function deleteTenant(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<boolean> {\n\tconst res = await db\n\t\t.deleteFrom(\"tenants\")\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\treturn (res.numDeletedRows ?? 0n) > 0n;\n}\n\nexport interface TenantCredentials {\n\tslug: string;\n\ttargetDatabaseUrl: string;\n\ttenantJwtSecret: string;\n\tanonKey: string;\n\tserviceKey: string;\n\tapiUrlInternal: string;\n\tapiUrlPublic: string;\n}\n\n/**\n * Decrypts the four encrypted columns and returns them plaintext. Call\n * this only when surfacing credentials to an authorized caller (dashboard,\n * CLI). Never log the returned object.\n */\nexport async function getTenantCredentials(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<TenantCredentials | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.select([\n\t\t\t\"slug\",\n\t\t\t\"target_database_url_enc\",\n\t\t\t\"tenant_jwt_secret_enc\",\n\t\t\t\"anon_key_enc\",\n\t\t\t\"service_key_enc\",\n\t\t\t\"api_url_internal\",\n\t\t\t\"api_url_public\",\n\t\t])\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\tif (!row) return null;\n\treturn {\n\t\tslug: row.slug,\n\t\ttargetDatabaseUrl: decryptSecret(row.target_database_url_enc),\n\t\ttenantJwtSecret: decryptSecret(row.tenant_jwt_secret_enc),\n\t\tanonKey: decryptSecret(row.anon_key_enc),\n\t\tserviceKey: decryptSecret(row.service_key_enc),\n\t\tapiUrlInternal: row.api_url_internal,\n\t\tapiUrlPublic: row.api_url_public,\n\t};\n}\n"
|
|
8
8
|
],
|
|
9
|
-
"mappings": ";;;;;;;;;;;;;;;;;AAkBA,IAAM,cAAuC,CAAC,OAAO,aAAa,UAAU;AAOrE,SAAS,eAAe,GAAiB;AAAA,EAC/C,MAAM,MAAM,QAAQ,IAAI,eAAe,KAAK,EAAE,YAAY;AAAA,EAC1D,IAAI,OAAQ,YAAkC,SAAS,GAAG,GAAG;AAAA,IAC5D,OAAO;AAAA,EACR;AAAA,EACA,OAAO;AAAA;AAID,SAAS,cAAc,GAAY;AAAA,EACzC,OAAO,gBAAgB,MAAM;AAAA;AAIvB,SAAS,SAAS,GAAY;AAAA,EACpC,OAAO,gBAAgB,MAAM;AAAA;AAIvB,SAAS,eAAe,GAAY;AAAA,EAC1C,OAAO,gBAAgB,MAAM;AAAA;;;AC7C9B;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA;AAqBA,IAAM,UAAU;AAChB,IAAM,SAAS;AACf,IAAM,UAAU;AAEhB,SAAS,eAAe,CAAC,SAAgC;AAAA,EACxD,IAAI,CAAC,WAAW,OAAO;AAAA,IAAG,OAAO;AAAA,EACjC,MAAM,WAAW,aAAa,SAAS,MAAM;AAAA,EAC7C,MAAM,QAAQ,SAAS,MAAM,6CAA6C;AAAA,
|
|
9
|
+
"mappings": "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",
|
|
10
10
|
"debugId": "ACAC3D9239633C2164756E2164756E21",
|
|
11
11
|
"names": []
|
|
12
12
|
}
|
package/dist/src/errors.d.ts
CHANGED
|
@@ -60,7 +60,6 @@ declare class TenantSuspendedError extends SecondLayerError {
|
|
|
60
60
|
}
|
|
61
61
|
/** Error code → HTTP status. Used by API middleware for code-based matching
|
|
62
62
|
* (avoids cross-bundle instanceof failures from bunup class duplication). */
|
|
63
|
-
|
|
64
|
-
declare const CODE_TO_STATUS: Record<MappedCode, 400 | 401 | 403 | 404 | 409 | 423 | 429>;
|
|
63
|
+
declare const CODE_TO_STATUS: Record<string, 400 | 401 | 403 | 404 | 409 | 423 | 429>;
|
|
65
64
|
declare function getErrorMessage(err: unknown): string;
|
|
66
65
|
export { getErrorMessage, VersionConflictError, ValidationError, TenantSuspendedError, SecondLayerError, RateLimitError, NotFoundError, KeyRotatedError, ForbiddenError, ErrorCodes, ErrorCode, DatabaseError, CODE_TO_STATUS, AuthorizationError, AuthenticationError };
|
package/dist/src/errors.js
CHANGED
|
@@ -124,7 +124,8 @@ var CODE_TO_STATUS = {
|
|
|
124
124
|
KEY_ROTATED: 401,
|
|
125
125
|
TENANT_SUSPENDED: 423,
|
|
126
126
|
NO_TENANT_FOR_PROJECT: 404,
|
|
127
|
-
INSTANCE_EXISTS: 409
|
|
127
|
+
INSTANCE_EXISTS: 409,
|
|
128
|
+
SUBGRAPH_NOT_FOUND: 404
|
|
128
129
|
};
|
|
129
130
|
function getErrorMessage(err) {
|
|
130
131
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -146,5 +147,5 @@ export {
|
|
|
146
147
|
AuthenticationError
|
|
147
148
|
};
|
|
148
149
|
|
|
149
|
-
//# debugId=
|
|
150
|
+
//# debugId=686075FF797C527764756E2164756E21
|
|
150
151
|
//# sourceMappingURL=errors.js.map
|
package/dist/src/errors.js.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/errors.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"export const ErrorCodes = {\n\tVALIDATION_ERROR: \"VALIDATION_ERROR\",\n\tDATABASE_ERROR: \"DATABASE_ERROR\",\n\tAUTHENTICATION_ERROR: \"AUTHENTICATION_ERROR\",\n\tAUTHORIZATION_ERROR: \"AUTHORIZATION_ERROR\",\n\tRATE_LIMIT_ERROR: \"RATE_LIMIT_ERROR\",\n\tFORBIDDEN: \"FORBIDDEN\",\n\tVERSION_CONFLICT: \"VERSION_CONFLICT\",\n\tNOT_FOUND: \"NOT_FOUND\",\n\t// Tenant lifecycle (CLI surfaces these verbatim)\n\tKEY_ROTATED: \"KEY_ROTATED\",\n\tTENANT_SUSPENDED: \"TENANT_SUSPENDED\",\n\tNO_TENANT_FOR_PROJECT: \"NO_TENANT_FOR_PROJECT\",\n\tINSTANCE_EXISTS: \"INSTANCE_EXISTS\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\n/** Base error class for all Secondlayer errors. */\nexport class SecondLayerError extends Error {\n\tpublic code: ErrorCode;\n\tpublic override cause?: unknown;\n\n\tconstructor(code: ErrorCode, message: string, cause?: unknown) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\t\tthis.cause = cause;\n\t\tthis.name = this.constructor.name;\n\t\tError.captureStackTrace?.(this, this.constructor);\n\t}\n\n\ttoJSON(): {\n\t\tname: string;\n\t\tcode: string;\n\t\tmessage: string;\n\t\tstack: string | undefined;\n\t\tcause: unknown;\n\t} {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tcode: this.code,\n\t\t\tmessage: this.message,\n\t\t\tstack: this.stack,\n\t\t\tcause: this.cause,\n\t\t};\n\t}\n}\n\nexport class NotFoundError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"NOT_FOUND\", message);\n\t}\n}\n\nexport class ValidationError extends SecondLayerError {\n\tconstructor(message: string, cause?: unknown) {\n\t\tsuper(\"VALIDATION_ERROR\", message, cause);\n\t}\n}\n\nexport class DatabaseError extends SecondLayerError {\n\tconstructor(message: string, cause?: unknown) {\n\t\tsuper(\"DATABASE_ERROR\", message, cause);\n\t}\n}\n\nexport class AuthenticationError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"AUTHENTICATION_ERROR\", message);\n\t}\n}\n\nexport class AuthorizationError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"AUTHORIZATION_ERROR\", message);\n\t}\n}\n\nexport class RateLimitError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"RATE_LIMIT_ERROR\", message);\n\t}\n}\n\nexport class ForbiddenError extends SecondLayerError {\n\tconstructor(message = \"Forbidden\") {\n\t\tsuper(\"FORBIDDEN\", message);\n\t}\n}\n\nexport class VersionConflictError extends SecondLayerError {\n\tpublic currentVersion: string;\n\tpublic expectedVersion: string;\n\n\tconstructor(currentVersion: string, expectedVersion: string) {\n\t\tsuper(\n\t\t\t\"VERSION_CONFLICT\",\n\t\t\t`Version conflict: expected ${expectedVersion}, current ${currentVersion}`,\n\t\t);\n\t\tthis.currentVersion = currentVersion;\n\t\tthis.expectedVersion = expectedVersion;\n\t}\n}\n\nexport class KeyRotatedError extends SecondLayerError {\n\tconstructor(message = \"Token has been rotated\") {\n\t\tsuper(\"KEY_ROTATED\", message);\n\t}\n}\n\nexport class TenantSuspendedError extends SecondLayerError {\n\tconstructor(message = \"Instance is suspended\") {\n\t\tsuper(\"TENANT_SUSPENDED\", message);\n\t}\n}\n\n/** Error code → HTTP status. Used by API middleware for code-based matching\n * (avoids cross-bundle instanceof failures from bunup class duplication). */\
|
|
5
|
+
"export const ErrorCodes = {\n\tVALIDATION_ERROR: \"VALIDATION_ERROR\",\n\tDATABASE_ERROR: \"DATABASE_ERROR\",\n\tAUTHENTICATION_ERROR: \"AUTHENTICATION_ERROR\",\n\tAUTHORIZATION_ERROR: \"AUTHORIZATION_ERROR\",\n\tRATE_LIMIT_ERROR: \"RATE_LIMIT_ERROR\",\n\tFORBIDDEN: \"FORBIDDEN\",\n\tVERSION_CONFLICT: \"VERSION_CONFLICT\",\n\tNOT_FOUND: \"NOT_FOUND\",\n\t// Tenant lifecycle (CLI surfaces these verbatim)\n\tKEY_ROTATED: \"KEY_ROTATED\",\n\tTENANT_SUSPENDED: \"TENANT_SUSPENDED\",\n\tNO_TENANT_FOR_PROJECT: \"NO_TENANT_FOR_PROJECT\",\n\tINSTANCE_EXISTS: \"INSTANCE_EXISTS\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\n/** Base error class for all Secondlayer errors. */\nexport class SecondLayerError extends Error {\n\tpublic code: ErrorCode;\n\tpublic override cause?: unknown;\n\n\tconstructor(code: ErrorCode, message: string, cause?: unknown) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\t\tthis.cause = cause;\n\t\tthis.name = this.constructor.name;\n\t\tError.captureStackTrace?.(this, this.constructor);\n\t}\n\n\ttoJSON(): {\n\t\tname: string;\n\t\tcode: string;\n\t\tmessage: string;\n\t\tstack: string | undefined;\n\t\tcause: unknown;\n\t} {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tcode: this.code,\n\t\t\tmessage: this.message,\n\t\t\tstack: this.stack,\n\t\t\tcause: this.cause,\n\t\t};\n\t}\n}\n\nexport class NotFoundError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"NOT_FOUND\", message);\n\t}\n}\n\nexport class ValidationError extends SecondLayerError {\n\tconstructor(message: string, cause?: unknown) {\n\t\tsuper(\"VALIDATION_ERROR\", message, cause);\n\t}\n}\n\nexport class DatabaseError extends SecondLayerError {\n\tconstructor(message: string, cause?: unknown) {\n\t\tsuper(\"DATABASE_ERROR\", message, cause);\n\t}\n}\n\nexport class AuthenticationError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"AUTHENTICATION_ERROR\", message);\n\t}\n}\n\nexport class AuthorizationError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"AUTHORIZATION_ERROR\", message);\n\t}\n}\n\nexport class RateLimitError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"RATE_LIMIT_ERROR\", message);\n\t}\n}\n\nexport class ForbiddenError extends SecondLayerError {\n\tconstructor(message = \"Forbidden\") {\n\t\tsuper(\"FORBIDDEN\", message);\n\t}\n}\n\nexport class VersionConflictError extends SecondLayerError {\n\tpublic currentVersion: string;\n\tpublic expectedVersion: string;\n\n\tconstructor(currentVersion: string, expectedVersion: string) {\n\t\tsuper(\n\t\t\t\"VERSION_CONFLICT\",\n\t\t\t`Version conflict: expected ${expectedVersion}, current ${currentVersion}`,\n\t\t);\n\t\tthis.currentVersion = currentVersion;\n\t\tthis.expectedVersion = expectedVersion;\n\t}\n}\n\nexport class KeyRotatedError extends SecondLayerError {\n\tconstructor(message = \"Token has been rotated\") {\n\t\tsuper(\"KEY_ROTATED\", message);\n\t}\n}\n\nexport class TenantSuspendedError extends SecondLayerError {\n\tconstructor(message = \"Instance is suspended\") {\n\t\tsuper(\"TENANT_SUSPENDED\", message);\n\t}\n}\n\n/** Error code → HTTP status. Used by API middleware for code-based matching\n * (avoids cross-bundle instanceof failures from bunup class duplication). */\n// String literal map — codes don't have to be in the central ErrorCode\n// enum (route-local error classes can supply any code; we just map the\n// HTTP status here). This keeps cross-bundle instanceof failures out of\n// the equation.\nexport const CODE_TO_STATUS: Record<\n\tstring,\n\t400 | 401 | 403 | 404 | 409 | 423 | 429\n> = {\n\tAUTHENTICATION_ERROR: 401,\n\tAUTHORIZATION_ERROR: 403,\n\tRATE_LIMIT_ERROR: 429,\n\tFORBIDDEN: 403,\n\tNOT_FOUND: 404,\n\tVALIDATION_ERROR: 400,\n\tKEY_ROTATED: 401,\n\tTENANT_SUSPENDED: 423,\n\tNO_TENANT_FOR_PROJECT: 404,\n\tINSTANCE_EXISTS: 409,\n\tSUBGRAPH_NOT_FOUND: 404,\n} as const;\n\nexport function getErrorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;AAAO,IAAM,aAAa;AAAA,EACzB,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,sBAAsB;AAAA,EACtB,qBAAqB;AAAA,EACrB,kBAAkB;AAAA,EAClB,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB,WAAW;AAAA,EAEX,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,uBAAuB;AAAA,EACvB,iBAAiB;AAClB;AAAA;AAKO,MAAM,yBAAyB,MAAM;AAAA,EACpC;AAAA,EACS;AAAA,EAEhB,WAAW,CAAC,MAAiB,SAAiB,OAAiB;AAAA,IAC9D,MAAM,OAAO;AAAA,IACb,KAAK,OAAO;AAAA,IACZ,KAAK,QAAQ;AAAA,IACb,KAAK,OAAO,KAAK,YAAY;AAAA,IAC7B,MAAM,oBAAoB,MAAM,KAAK,WAAW;AAAA;AAAA,EAGjD,MAAM,GAMJ;AAAA,IACD,OAAO;AAAA,MACN,MAAM,KAAK;AAAA,MACX,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,OAAO,KAAK;AAAA,MACZ,OAAO,KAAK;AAAA,IACb;AAAA;AAEF;AAAA;AAEO,MAAM,sBAAsB,iBAAiB;AAAA,EACnD,WAAW,CAAC,SAAiB;AAAA,IAC5B,MAAM,aAAa,OAAO;AAAA;AAE5B;AAAA;AAEO,MAAM,wBAAwB,iBAAiB;AAAA,EACrD,WAAW,CAAC,SAAiB,OAAiB;AAAA,IAC7C,MAAM,oBAAoB,SAAS,KAAK;AAAA;AAE1C;AAAA;AAEO,MAAM,sBAAsB,iBAAiB;AAAA,EACnD,WAAW,CAAC,SAAiB,OAAiB;AAAA,IAC7C,MAAM,kBAAkB,SAAS,KAAK;AAAA;AAExC;AAAA;AAEO,MAAM,4BAA4B,iBAAiB;AAAA,EACzD,WAAW,CAAC,SAAiB;AAAA,IAC5B,MAAM,wBAAwB,OAAO;AAAA;AAEvC;AAAA;AAEO,MAAM,2BAA2B,iBAAiB;AAAA,EACxD,WAAW,CAAC,SAAiB;AAAA,IAC5B,MAAM,uBAAuB,OAAO;AAAA;AAEtC;AAAA;AAEO,MAAM,uBAAuB,iBAAiB;AAAA,EACpD,WAAW,CAAC,SAAiB;AAAA,IAC5B,MAAM,oBAAoB,OAAO;AAAA;AAEnC;AAAA;AAEO,MAAM,uBAAuB,iBAAiB;AAAA,EACpD,WAAW,CAAC,UAAU,aAAa;AAAA,IAClC,MAAM,aAAa,OAAO;AAAA;AAE5B;AAAA;AAEO,MAAM,6BAA6B,iBAAiB;AAAA,EACnD;AAAA,EACA;AAAA,EAEP,WAAW,CAAC,gBAAwB,iBAAyB;AAAA,IAC5D,MACC,oBACA,8BAA8B,4BAA4B,gBAC3D;AAAA,IACA,KAAK,iBAAiB;AAAA,IACtB,KAAK,kBAAkB;AAAA;AAEzB;AAAA;AAEO,MAAM,wBAAwB,iBAAiB;AAAA,EACrD,WAAW,CAAC,UAAU,0BAA0B;AAAA,IAC/C,MAAM,eAAe,OAAO;AAAA;AAE9B;AAAA;AAEO,MAAM,6BAA6B,iBAAiB;AAAA,EAC1D,WAAW,CAAC,UAAU,yBAAyB;AAAA,IAC9C,MAAM,oBAAoB,OAAO;AAAA;AAEnC;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;AAAO,IAAM,aAAa;AAAA,EACzB,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,sBAAsB;AAAA,EACtB,qBAAqB;AAAA,EACrB,kBAAkB;AAAA,EAClB,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB,WAAW;AAAA,EAEX,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,uBAAuB;AAAA,EACvB,iBAAiB;AAClB;AAAA;AAKO,MAAM,yBAAyB,MAAM;AAAA,EACpC;AAAA,EACS;AAAA,EAEhB,WAAW,CAAC,MAAiB,SAAiB,OAAiB;AAAA,IAC9D,MAAM,OAAO;AAAA,IACb,KAAK,OAAO;AAAA,IACZ,KAAK,QAAQ;AAAA,IACb,KAAK,OAAO,KAAK,YAAY;AAAA,IAC7B,MAAM,oBAAoB,MAAM,KAAK,WAAW;AAAA;AAAA,EAGjD,MAAM,GAMJ;AAAA,IACD,OAAO;AAAA,MACN,MAAM,KAAK;AAAA,MACX,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,OAAO,KAAK;AAAA,MACZ,OAAO,KAAK;AAAA,IACb;AAAA;AAEF;AAAA;AAEO,MAAM,sBAAsB,iBAAiB;AAAA,EACnD,WAAW,CAAC,SAAiB;AAAA,IAC5B,MAAM,aAAa,OAAO;AAAA;AAE5B;AAAA;AAEO,MAAM,wBAAwB,iBAAiB;AAAA,EACrD,WAAW,CAAC,SAAiB,OAAiB;AAAA,IAC7C,MAAM,oBAAoB,SAAS,KAAK;AAAA;AAE1C;AAAA;AAEO,MAAM,sBAAsB,iBAAiB;AAAA,EACnD,WAAW,CAAC,SAAiB,OAAiB;AAAA,IAC7C,MAAM,kBAAkB,SAAS,KAAK;AAAA;AAExC;AAAA;AAEO,MAAM,4BAA4B,iBAAiB;AAAA,EACzD,WAAW,CAAC,SAAiB;AAAA,IAC5B,MAAM,wBAAwB,OAAO;AAAA;AAEvC;AAAA;AAEO,MAAM,2BAA2B,iBAAiB;AAAA,EACxD,WAAW,CAAC,SAAiB;AAAA,IAC5B,MAAM,uBAAuB,OAAO;AAAA;AAEtC;AAAA;AAEO,MAAM,uBAAuB,iBAAiB;AAAA,EACpD,WAAW,CAAC,SAAiB;AAAA,IAC5B,MAAM,oBAAoB,OAAO;AAAA;AAEnC;AAAA;AAEO,MAAM,uBAAuB,iBAAiB;AAAA,EACpD,WAAW,CAAC,UAAU,aAAa;AAAA,IAClC,MAAM,aAAa,OAAO;AAAA;AAE5B;AAAA;AAEO,MAAM,6BAA6B,iBAAiB;AAAA,EACnD;AAAA,EACA;AAAA,EAEP,WAAW,CAAC,gBAAwB,iBAAyB;AAAA,IAC5D,MACC,oBACA,8BAA8B,4BAA4B,gBAC3D;AAAA,IACA,KAAK,iBAAiB;AAAA,IACtB,KAAK,kBAAkB;AAAA;AAEzB;AAAA;AAEO,MAAM,wBAAwB,iBAAiB;AAAA,EACrD,WAAW,CAAC,UAAU,0BAA0B;AAAA,IAC/C,MAAM,eAAe,OAAO;AAAA;AAE9B;AAAA;AAEO,MAAM,6BAA6B,iBAAiB;AAAA,EAC1D,WAAW,CAAC,UAAU,yBAAyB;AAAA,IAC9C,MAAM,oBAAoB,OAAO;AAAA;AAEnC;AAQO,IAAM,iBAGT;AAAA,EACH,sBAAsB;AAAA,EACtB,qBAAqB;AAAA,EACrB,kBAAkB;AAAA,EAClB,WAAW;AAAA,EACX,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,uBAAuB;AAAA,EACvB,iBAAiB;AAAA,EACjB,oBAAoB;AACrB;AAEO,SAAS,eAAe,CAAC,KAAsB;AAAA,EACrD,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA;",
|
|
8
|
+
"debugId": "686075FF797C527764756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -639,8 +639,7 @@ declare class TenantSuspendedError extends SecondLayerError {
|
|
|
639
639
|
}
|
|
640
640
|
/** Error code → HTTP status. Used by API middleware for code-based matching
|
|
641
641
|
* (avoids cross-bundle instanceof failures from bunup class duplication). */
|
|
642
|
-
|
|
643
|
-
declare const CODE_TO_STATUS: Record<MappedCode, 400 | 401 | 403 | 404 | 409 | 423 | 429>;
|
|
642
|
+
declare const CODE_TO_STATUS: Record<string, 400 | 401 | 403 | 404 | 409 | 423 | 429>;
|
|
644
643
|
declare function getErrorMessage(err: unknown): string;
|
|
645
644
|
declare class Logger {
|
|
646
645
|
private _level?;
|
|
@@ -841,8 +840,12 @@ interface SubgraphSyncInfo {
|
|
|
841
840
|
interface SubgraphDetail {
|
|
842
841
|
name: string;
|
|
843
842
|
version: string;
|
|
843
|
+
schemaHash?: string;
|
|
844
844
|
status: string;
|
|
845
845
|
lastProcessedBlock: number;
|
|
846
|
+
description?: string;
|
|
847
|
+
sources?: Record<string, unknown>;
|
|
848
|
+
definition?: Record<string, unknown>;
|
|
846
849
|
health: {
|
|
847
850
|
totalProcessed: number
|
|
848
851
|
totalErrors: number
|
|
@@ -856,9 +859,14 @@ interface SubgraphDetail {
|
|
|
856
859
|
columns: Record<string, {
|
|
857
860
|
type: string
|
|
858
861
|
nullable?: boolean
|
|
862
|
+
indexed?: boolean
|
|
863
|
+
searchable?: boolean
|
|
864
|
+
default?: string | number | boolean
|
|
859
865
|
}>
|
|
860
866
|
rowCount: number
|
|
861
867
|
example: string
|
|
868
|
+
indexes?: string[][]
|
|
869
|
+
uniqueKeys?: string[][]
|
|
862
870
|
}>;
|
|
863
871
|
createdAt: string;
|
|
864
872
|
updatedAt: string;
|
|
@@ -1036,6 +1044,46 @@ declare function validateSubscriptionFilterForTable(input: {
|
|
|
1036
1044
|
filter?: unknown
|
|
1037
1045
|
tables: SubscriptionSchemaTables
|
|
1038
1046
|
}): string[];
|
|
1047
|
+
type SubgraphSpecFormat = "openapi" | "agent" | "markdown";
|
|
1048
|
+
interface SubgraphSpecOptions {
|
|
1049
|
+
serverUrl?: string;
|
|
1050
|
+
generatedAt?: string;
|
|
1051
|
+
}
|
|
1052
|
+
interface SubgraphAgentSchema {
|
|
1053
|
+
name: string;
|
|
1054
|
+
version: string;
|
|
1055
|
+
description?: string;
|
|
1056
|
+
schemaHash?: string;
|
|
1057
|
+
generatedAt: string;
|
|
1058
|
+
serverUrl: string;
|
|
1059
|
+
sources?: Record<string, unknown>;
|
|
1060
|
+
tables: Record<string, {
|
|
1061
|
+
endpoint: string
|
|
1062
|
+
countEndpoint: string
|
|
1063
|
+
rowCount: number
|
|
1064
|
+
columns: SubgraphDetail["tables"][string]["columns"]
|
|
1065
|
+
indexes?: string[][]
|
|
1066
|
+
uniqueKeys?: string[][]
|
|
1067
|
+
query: {
|
|
1068
|
+
parameters: string[]
|
|
1069
|
+
sortable: string[]
|
|
1070
|
+
selectable: string[]
|
|
1071
|
+
searchable: string[]
|
|
1072
|
+
filters: string[]
|
|
1073
|
+
}
|
|
1074
|
+
examples: {
|
|
1075
|
+
list: Record<string, unknown>
|
|
1076
|
+
count: {
|
|
1077
|
+
count: number
|
|
1078
|
+
}
|
|
1079
|
+
curl: string
|
|
1080
|
+
}
|
|
1081
|
+
}>;
|
|
1082
|
+
}
|
|
1083
|
+
declare function generateSubgraphAgentSchema(detail: SubgraphDetail, options?: SubgraphSpecOptions): SubgraphAgentSchema;
|
|
1084
|
+
declare function generateSubgraphOpenApi(detail: SubgraphDetail, options?: SubgraphSpecOptions): Record<string, unknown>;
|
|
1085
|
+
declare function generateSubgraphMarkdown(detail: SubgraphDetail, options?: SubgraphSpecOptions): string;
|
|
1086
|
+
declare function generateSubgraphSpec(detail: SubgraphDetail, format: SubgraphSpecFormat, options?: SubgraphSpecOptions): Record<string, unknown> | SubgraphAgentSchema | string;
|
|
1039
1087
|
declare namespace exports_hmac {
|
|
1040
1088
|
export { verifySignatureHeader, verifySignature, signPayload, generateSecret, createSignatureHeader };
|
|
1041
1089
|
}
|
|
@@ -1064,4 +1112,4 @@ declare function createSignatureHeader(payload: string, secret: string, timestam
|
|
|
1064
1112
|
* Returns true if valid, false otherwise
|
|
1065
1113
|
*/
|
|
1066
1114
|
declare function verifySignatureHeader(payload: string, header: string, secret: string, toleranceSeconds?: number): boolean;
|
|
1067
|
-
export { validateSubscriptionFilterForTable, sql, parseJsonb, logger, jsonb, getTargetDb, getSourceDb, getRawClient, getErrorMessage, getEnv, getDb, formatSubscriptionSchemaErrors, exports_hmac as crypto, closeDb, WaitlistTable, VersionConflictError, ValidationError, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateTransaction, UpdateTenantUsageMonthly, UpdateTenantComputeAddon, UpdateTenant, UpdateSubscriptionRequestSchema, UpdateSubscriptionRequest, UpdateSubscriptionOutbox, UpdateSubscription, UpdateSubgraphOperation, UpdateSubgraph, UpdateProject, UpdateProfileRequestSchema, UpdateProfileRequest, UpdateIndexProgress, UpdateEvent, UpdateChatSession, UpdateBlock, UpdateApiKey, UpdateAccountSpendCap, TransactionsTable, Transaction, TenantsTable, TenantUsageMonthlyTable, TenantUsageMonthly, TenantSuspendedError, TenantStatus, TenantComputeAddonsTable, TenantComputeAddon, Tenant, TeamMembersTable, TeamMember, TeamInvitationsTable, TeamInvitation, SubscriptionsTable, SubscriptionSummary, SubscriptionStatusSchema, SubscriptionStatus, SubscriptionSchemaTables, SubscriptionSchemaTable, SubscriptionSchemaColumn, SubscriptionRuntimeSchema, SubscriptionRuntime, SubscriptionOutboxTable, SubscriptionOutbox, SubscriptionFormatSchema, SubscriptionFormat, SubscriptionFilterSchema, SubscriptionFilterPrimitiveSchema, SubscriptionFilterPrimitive, SubscriptionFilterOperatorSchema, SubscriptionFilterOperator, SubscriptionFilterClauseSchema, SubscriptionFilterClause, SubscriptionFilter, SubscriptionDetail, SubscriptionDelivery, SubscriptionDeliveriesTable, Subscription, SubgraphsTable, SubgraphUsageDailyTable, SubgraphUsageDaily, SubgraphTableSnapshotsTable, SubgraphSyncInfo, SubgraphSummary, SubgraphQueryParams, SubgraphProcessingStatsTable, SubgraphOperationsTable, SubgraphOperationStatus, SubgraphOperationKind, SubgraphOperation, SubgraphHealthSnapshotsTable, SubgraphHealthSnapshot, SubgraphGapsTable, SubgraphGapsResponse, SubgraphGapRange, SubgraphGapEntry, SubgraphGap, SubgraphDetail, Subgraph, StxTransferFilterSchema, StxTransferFilter, StxMintFilterSchema, StxMintFilter, StxLockFilterSchema, StxLockFilter, StxBurnFilterSchema, StxBurnFilter, SessionsTable, Session, SecondLayerError, SUBSCRIPTION_STATUSES, SUBSCRIPTION_RUNTIMES, SUBSCRIPTION_FORMATS, SUBSCRIPTION_FILTER_OPERATORS, RotateSecretResponse, ReplaySubscriptionRequestSchema, ReplaySubscriptionRequest, ReplayResult, ReindexResponse, RateLimitError, ProvisioningAuditStatus, ProvisioningAuditLogTable, ProvisioningAuditLog, ProvisioningAuditEvent, ProjectsTable, Project, PrintEventFilterSchema, PrintEventFilter, ParsedUpdateSubscriptionRequest, ParsedReplaySubscriptionRequest, ParsedCreateSubscriptionRequest, OutboxStatus, NotFoundError, NftTransferFilterSchema, NftTransferFilter, NftMintFilterSchema, NftMintFilter, NftBurnFilterSchema, NftBurnFilter, MagicLinksTable, MagicLink, KeyRotatedError, InsertTransaction, InsertTenantUsageMonthly, InsertTenantComputeAddon, InsertTenant, InsertTeamMember, InsertTeamInvitation, InsertSubscriptionOutbox, InsertSubscriptionDelivery, InsertSubscription, InsertSubgraphUsageDaily, InsertSubgraphOperation, InsertSubgraphHealthSnapshot, InsertSubgraphGap, InsertSubgraph, InsertSession, InsertProvisioningAuditLog, InsertProject, InsertMagicLink, InsertIndexProgress, InsertEvent, InsertChatSession, InsertChatMessage, InsertBlock, InsertApiKey, InsertAccountSpendCap, InsertAccountInsight, InsertAccountAgentRun, InsertAccount, IndexProgressTable, IndexProgress, FtTransferFilterSchema, FtTransferFilter, FtMintFilterSchema, FtMintFilter, FtBurnFilterSchema, FtBurnFilter, ForbiddenError, EventsTable, EventFilterSchema, EventFilter, Event, ErrorCodes, ErrorCode, Env, DeploySubgraphResponse, DeploySubgraphRequestSchema, DeploySubgraphRequest, DeliveryRow, DeadRow, DatabaseError, Database, CreateSubscriptionResponse, CreateSubscriptionRequestSchema, CreateSubscriptionRequest, ContractDeployFilterSchema, ContractDeployFilter, ContractCallFilterSchema, ContractCallFilter, ChatSessionsTable, ChatSession, ChatMessagesTable, ChatMessage, CODE_TO_STATUS, BlocksTable, Block, AuthorizationError, AuthenticationError, ApiKeysTable, ApiKey, AccountsTable, AccountSpendCapsTable, AccountSpendCap, AccountInsightsTable, AccountInsight, AccountAgentRunsTable, AccountAgentRun, Account };
|
|
1115
|
+
export { validateSubscriptionFilterForTable, sql, parseJsonb, logger, jsonb, getTargetDb, getSourceDb, getRawClient, getErrorMessage, getEnv, getDb, generateSubgraphSpec, generateSubgraphOpenApi, generateSubgraphMarkdown, generateSubgraphAgentSchema, formatSubscriptionSchemaErrors, exports_hmac as crypto, closeDb, WaitlistTable, VersionConflictError, ValidationError, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateTransaction, UpdateTenantUsageMonthly, UpdateTenantComputeAddon, UpdateTenant, UpdateSubscriptionRequestSchema, UpdateSubscriptionRequest, UpdateSubscriptionOutbox, UpdateSubscription, UpdateSubgraphOperation, UpdateSubgraph, UpdateProject, UpdateProfileRequestSchema, UpdateProfileRequest, UpdateIndexProgress, UpdateEvent, UpdateChatSession, UpdateBlock, UpdateApiKey, UpdateAccountSpendCap, TransactionsTable, Transaction, TenantsTable, TenantUsageMonthlyTable, TenantUsageMonthly, TenantSuspendedError, TenantStatus, TenantComputeAddonsTable, TenantComputeAddon, Tenant, TeamMembersTable, TeamMember, TeamInvitationsTable, TeamInvitation, SubscriptionsTable, SubscriptionSummary, SubscriptionStatusSchema, SubscriptionStatus, SubscriptionSchemaTables, SubscriptionSchemaTable, SubscriptionSchemaColumn, SubscriptionRuntimeSchema, SubscriptionRuntime, SubscriptionOutboxTable, SubscriptionOutbox, SubscriptionFormatSchema, SubscriptionFormat, SubscriptionFilterSchema, SubscriptionFilterPrimitiveSchema, SubscriptionFilterPrimitive, SubscriptionFilterOperatorSchema, SubscriptionFilterOperator, SubscriptionFilterClauseSchema, SubscriptionFilterClause, SubscriptionFilter, SubscriptionDetail, SubscriptionDelivery, SubscriptionDeliveriesTable, Subscription, SubgraphsTable, SubgraphUsageDailyTable, SubgraphUsageDaily, SubgraphTableSnapshotsTable, SubgraphSyncInfo, SubgraphSummary, SubgraphSpecOptions, SubgraphSpecFormat, SubgraphQueryParams, SubgraphProcessingStatsTable, SubgraphOperationsTable, SubgraphOperationStatus, SubgraphOperationKind, SubgraphOperation, SubgraphHealthSnapshotsTable, SubgraphHealthSnapshot, SubgraphGapsTable, SubgraphGapsResponse, SubgraphGapRange, SubgraphGapEntry, SubgraphGap, SubgraphDetail, SubgraphAgentSchema, Subgraph, StxTransferFilterSchema, StxTransferFilter, StxMintFilterSchema, StxMintFilter, StxLockFilterSchema, StxLockFilter, StxBurnFilterSchema, StxBurnFilter, SessionsTable, Session, SecondLayerError, SUBSCRIPTION_STATUSES, SUBSCRIPTION_RUNTIMES, SUBSCRIPTION_FORMATS, SUBSCRIPTION_FILTER_OPERATORS, RotateSecretResponse, ReplaySubscriptionRequestSchema, ReplaySubscriptionRequest, ReplayResult, ReindexResponse, RateLimitError, ProvisioningAuditStatus, ProvisioningAuditLogTable, ProvisioningAuditLog, ProvisioningAuditEvent, ProjectsTable, Project, PrintEventFilterSchema, PrintEventFilter, ParsedUpdateSubscriptionRequest, ParsedReplaySubscriptionRequest, ParsedCreateSubscriptionRequest, OutboxStatus, NotFoundError, NftTransferFilterSchema, NftTransferFilter, NftMintFilterSchema, NftMintFilter, NftBurnFilterSchema, NftBurnFilter, MagicLinksTable, MagicLink, KeyRotatedError, InsertTransaction, InsertTenantUsageMonthly, InsertTenantComputeAddon, InsertTenant, InsertTeamMember, InsertTeamInvitation, InsertSubscriptionOutbox, InsertSubscriptionDelivery, InsertSubscription, InsertSubgraphUsageDaily, InsertSubgraphOperation, InsertSubgraphHealthSnapshot, InsertSubgraphGap, InsertSubgraph, InsertSession, InsertProvisioningAuditLog, InsertProject, InsertMagicLink, InsertIndexProgress, InsertEvent, InsertChatSession, InsertChatMessage, InsertBlock, InsertApiKey, InsertAccountSpendCap, InsertAccountInsight, InsertAccountAgentRun, InsertAccount, IndexProgressTable, IndexProgress, FtTransferFilterSchema, FtTransferFilter, FtMintFilterSchema, FtMintFilter, FtBurnFilterSchema, FtBurnFilter, ForbiddenError, EventsTable, EventFilterSchema, EventFilter, Event, ErrorCodes, ErrorCode, Env, DeploySubgraphResponse, DeploySubgraphRequestSchema, DeploySubgraphRequest, DeliveryRow, DeadRow, DatabaseError, Database, CreateSubscriptionResponse, CreateSubscriptionRequestSchema, CreateSubscriptionRequest, ContractDeployFilterSchema, ContractDeployFilter, ContractCallFilterSchema, ContractCallFilter, ChatSessionsTable, ChatSession, ChatMessagesTable, ChatMessage, CODE_TO_STATUS, BlocksTable, Block, AuthorizationError, AuthenticationError, ApiKeysTable, ApiKey, AccountsTable, AccountSpendCapsTable, AccountSpendCap, AccountInsightsTable, AccountInsight, AccountAgentRunsTable, AccountAgentRun, Account };
|
package/dist/src/index.js
CHANGED
|
@@ -329,7 +329,8 @@ var CODE_TO_STATUS = {
|
|
|
329
329
|
KEY_ROTATED: 401,
|
|
330
330
|
TENANT_SUSPENDED: 423,
|
|
331
331
|
NO_TENANT_FOR_PROJECT: 404,
|
|
332
|
-
INSTANCE_EXISTS: 409
|
|
332
|
+
INSTANCE_EXISTS: 409,
|
|
333
|
+
SUBGRAPH_NOT_FOUND: 404
|
|
333
334
|
};
|
|
334
335
|
function getErrorMessage(err) {
|
|
335
336
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -596,6 +597,348 @@ function validateSubscriptionFilterForTable(input) {
|
|
|
596
597
|
}
|
|
597
598
|
return errors;
|
|
598
599
|
}
|
|
600
|
+
// src/subgraphs/spec.ts
|
|
601
|
+
var SYSTEM_COLUMNS = ["_id", "_block_height", "_tx_id", "_created_at"];
|
|
602
|
+
var BASE_QUERY_PARAMS = ["_limit", "_offset", "_sort", "_order", "_fields"];
|
|
603
|
+
var COMPARISON_OPS = ["neq", "gt", "gte", "lt", "lte"];
|
|
604
|
+
function generatedAt(options) {
|
|
605
|
+
return options.generatedAt ?? new Date().toISOString();
|
|
606
|
+
}
|
|
607
|
+
function normalizeServerUrl(serverUrl) {
|
|
608
|
+
return (serverUrl ?? "https://api.secondlayer.tools").replace(/\/+$/, "");
|
|
609
|
+
}
|
|
610
|
+
function tablePath(subgraphName, tableName) {
|
|
611
|
+
return `/api/subgraphs/${subgraphName}/${tableName}`;
|
|
612
|
+
}
|
|
613
|
+
function countPath(subgraphName, tableName) {
|
|
614
|
+
return `${tablePath(subgraphName, tableName)}/count`;
|
|
615
|
+
}
|
|
616
|
+
function isTextLike(type) {
|
|
617
|
+
return type === "text" || type === "principal" || type === "timestamp";
|
|
618
|
+
}
|
|
619
|
+
function isComparable(type) {
|
|
620
|
+
return type === "uint" || type === "int" || type === "bigint" || type === "serial" || type === "timestamp";
|
|
621
|
+
}
|
|
622
|
+
function exampleForColumn(type) {
|
|
623
|
+
switch (type) {
|
|
624
|
+
case "uint":
|
|
625
|
+
case "int":
|
|
626
|
+
case "bigint":
|
|
627
|
+
return "1000";
|
|
628
|
+
case "serial":
|
|
629
|
+
return 1;
|
|
630
|
+
case "principal":
|
|
631
|
+
return "SP000000000000000000002Q6VF78";
|
|
632
|
+
case "timestamp":
|
|
633
|
+
return "2026-01-01T00:00:00.000Z";
|
|
634
|
+
case "boolean":
|
|
635
|
+
return true;
|
|
636
|
+
case "jsonb":
|
|
637
|
+
return { example: true };
|
|
638
|
+
default:
|
|
639
|
+
return "example";
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
function openApiSchemaForColumn(col) {
|
|
643
|
+
let schema;
|
|
644
|
+
switch (col.type) {
|
|
645
|
+
case "uint":
|
|
646
|
+
case "int":
|
|
647
|
+
case "bigint":
|
|
648
|
+
schema = { type: "string", pattern: "^-?\\d+(\\.\\d+)?$" };
|
|
649
|
+
break;
|
|
650
|
+
case "serial":
|
|
651
|
+
schema = { type: "integer" };
|
|
652
|
+
break;
|
|
653
|
+
case "principal":
|
|
654
|
+
case "text":
|
|
655
|
+
schema = { type: "string" };
|
|
656
|
+
break;
|
|
657
|
+
case "timestamp":
|
|
658
|
+
schema = { type: "string", format: "date-time" };
|
|
659
|
+
break;
|
|
660
|
+
case "boolean":
|
|
661
|
+
schema = { type: "boolean" };
|
|
662
|
+
break;
|
|
663
|
+
case "jsonb":
|
|
664
|
+
schema = { type: "object", additionalProperties: true };
|
|
665
|
+
break;
|
|
666
|
+
default:
|
|
667
|
+
schema = {};
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
670
|
+
if (col.nullable) {
|
|
671
|
+
const type = schema.type;
|
|
672
|
+
if (typeof type === "string")
|
|
673
|
+
schema.type = [type, "null"];
|
|
674
|
+
}
|
|
675
|
+
return schema;
|
|
676
|
+
}
|
|
677
|
+
function columnEntries(table) {
|
|
678
|
+
return Object.entries(table.columns);
|
|
679
|
+
}
|
|
680
|
+
function selectableColumns(table) {
|
|
681
|
+
return columnEntries(table).map(([name2]) => name2);
|
|
682
|
+
}
|
|
683
|
+
function searchableColumns(table) {
|
|
684
|
+
return columnEntries(table).filter(([, col]) => col.searchable).map(([name2]) => name2);
|
|
685
|
+
}
|
|
686
|
+
function filterNames(table) {
|
|
687
|
+
const result = [];
|
|
688
|
+
for (const [name2, col] of columnEntries(table)) {
|
|
689
|
+
result.push(name2);
|
|
690
|
+
result.push(`${name2}.neq`);
|
|
691
|
+
if (isComparable(col.type)) {
|
|
692
|
+
for (const op of COMPARISON_OPS.filter((op2) => op2 !== "neq")) {
|
|
693
|
+
result.push(`${name2}.${op}`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
if (isTextLike(col.type))
|
|
697
|
+
result.push(`${name2}.like`);
|
|
698
|
+
}
|
|
699
|
+
return result;
|
|
700
|
+
}
|
|
701
|
+
function queryParameters(table) {
|
|
702
|
+
const params = [...BASE_QUERY_PARAMS];
|
|
703
|
+
if (searchableColumns(table).length > 0)
|
|
704
|
+
params.push("_search");
|
|
705
|
+
return params;
|
|
706
|
+
}
|
|
707
|
+
function rowExample(table) {
|
|
708
|
+
const row = {};
|
|
709
|
+
for (const [name2, col] of columnEntries(table)) {
|
|
710
|
+
row[name2] = exampleForColumn(col.type);
|
|
711
|
+
}
|
|
712
|
+
return row;
|
|
713
|
+
}
|
|
714
|
+
function openApiParameter(name2, description, schema = { type: "string" }) {
|
|
715
|
+
return {
|
|
716
|
+
name: name2,
|
|
717
|
+
in: "query",
|
|
718
|
+
required: false,
|
|
719
|
+
description,
|
|
720
|
+
schema
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
function tableParameters(table) {
|
|
724
|
+
const parameters = [
|
|
725
|
+
openApiParameter("_limit", "Maximum rows to return.", {
|
|
726
|
+
type: "integer",
|
|
727
|
+
default: 50,
|
|
728
|
+
minimum: 1,
|
|
729
|
+
maximum: 1000
|
|
730
|
+
}),
|
|
731
|
+
openApiParameter("_offset", "Rows to skip for pagination.", {
|
|
732
|
+
type: "integer",
|
|
733
|
+
default: 0,
|
|
734
|
+
minimum: 0
|
|
735
|
+
}),
|
|
736
|
+
openApiParameter("_sort", "Column to sort by.", {
|
|
737
|
+
type: "string",
|
|
738
|
+
enum: selectableColumns(table)
|
|
739
|
+
}),
|
|
740
|
+
openApiParameter("_order", "Sort direction.", {
|
|
741
|
+
type: "string",
|
|
742
|
+
enum: ["asc", "desc"],
|
|
743
|
+
default: "asc"
|
|
744
|
+
}),
|
|
745
|
+
openApiParameter("_fields", "Comma-separated columns to include.", {
|
|
746
|
+
type: "string"
|
|
747
|
+
})
|
|
748
|
+
];
|
|
749
|
+
if (searchableColumns(table).length > 0) {
|
|
750
|
+
parameters.push(openApiParameter("_search", "Search across searchable columns.", {
|
|
751
|
+
type: "string"
|
|
752
|
+
}));
|
|
753
|
+
}
|
|
754
|
+
for (const [name2, col] of columnEntries(table)) {
|
|
755
|
+
parameters.push(openApiParameter(name2, `Filter ${name2} by equality.`, {
|
|
756
|
+
type: "string"
|
|
757
|
+
}));
|
|
758
|
+
parameters.push(openApiParameter(`${name2}.neq`, `Filter ${name2} by inequality.`, {
|
|
759
|
+
type: "string"
|
|
760
|
+
}));
|
|
761
|
+
if (isComparable(col.type)) {
|
|
762
|
+
for (const op of ["gt", "gte", "lt", "lte"]) {
|
|
763
|
+
parameters.push(openApiParameter(`${name2}.${op}`, `Filter ${name2} with ${op}.`, {
|
|
764
|
+
type: "string"
|
|
765
|
+
}));
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if (isTextLike(col.type)) {
|
|
769
|
+
parameters.push(openApiParameter(`${name2}.like`, `Case-insensitive contains filter for ${name2}.`, {
|
|
770
|
+
type: "string"
|
|
771
|
+
}));
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return parameters;
|
|
775
|
+
}
|
|
776
|
+
function generateSubgraphAgentSchema(detail, options = {}) {
|
|
777
|
+
const serverUrl = normalizeServerUrl(options.serverUrl);
|
|
778
|
+
const tables = {};
|
|
779
|
+
for (const [tableName, table] of Object.entries(detail.tables)) {
|
|
780
|
+
const path = tablePath(detail.name, tableName);
|
|
781
|
+
tables[tableName] = {
|
|
782
|
+
endpoint: `${serverUrl}${path}`,
|
|
783
|
+
countEndpoint: `${serverUrl}${countPath(detail.name, tableName)}`,
|
|
784
|
+
rowCount: table.rowCount,
|
|
785
|
+
columns: table.columns,
|
|
786
|
+
...table.indexes ? { indexes: table.indexes } : {},
|
|
787
|
+
...table.uniqueKeys ? { uniqueKeys: table.uniqueKeys } : {},
|
|
788
|
+
query: {
|
|
789
|
+
parameters: queryParameters(table),
|
|
790
|
+
sortable: selectableColumns(table),
|
|
791
|
+
selectable: selectableColumns(table),
|
|
792
|
+
searchable: searchableColumns(table),
|
|
793
|
+
filters: filterNames(table)
|
|
794
|
+
},
|
|
795
|
+
examples: {
|
|
796
|
+
list: rowExample(table),
|
|
797
|
+
count: { count: table.rowCount },
|
|
798
|
+
curl: `curl '${serverUrl}${path}?_limit=10&_sort=_block_height&_order=desc'`
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
return {
|
|
803
|
+
name: detail.name,
|
|
804
|
+
version: detail.version,
|
|
805
|
+
...detail.description ? { description: detail.description } : {},
|
|
806
|
+
...detail.schemaHash ? { schemaHash: detail.schemaHash } : {},
|
|
807
|
+
generatedAt: generatedAt(options),
|
|
808
|
+
serverUrl,
|
|
809
|
+
...detail.sources ? { sources: detail.sources } : {},
|
|
810
|
+
tables
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
function generateSubgraphOpenApi(detail, options = {}) {
|
|
814
|
+
const serverUrl = normalizeServerUrl(options.serverUrl);
|
|
815
|
+
const paths = {};
|
|
816
|
+
const schemas = {};
|
|
817
|
+
for (const [tableName, table] of Object.entries(detail.tables)) {
|
|
818
|
+
const schemaName = `${tableName}Row`;
|
|
819
|
+
const properties = {};
|
|
820
|
+
const required = [];
|
|
821
|
+
for (const [columnName, column] of columnEntries(table)) {
|
|
822
|
+
properties[columnName] = openApiSchemaForColumn(column);
|
|
823
|
+
if (!column.nullable)
|
|
824
|
+
required.push(columnName);
|
|
825
|
+
}
|
|
826
|
+
schemas[schemaName] = {
|
|
827
|
+
type: "object",
|
|
828
|
+
properties,
|
|
829
|
+
required,
|
|
830
|
+
example: rowExample(table)
|
|
831
|
+
};
|
|
832
|
+
paths[tablePath(detail.name, tableName)] = {
|
|
833
|
+
get: {
|
|
834
|
+
summary: `Query ${detail.name}.${tableName}`,
|
|
835
|
+
operationId: `query_${detail.name.replace(/-/g, "_")}_${tableName}`,
|
|
836
|
+
parameters: tableParameters(table),
|
|
837
|
+
responses: {
|
|
838
|
+
"200": {
|
|
839
|
+
description: "Rows returned from the subgraph table.",
|
|
840
|
+
content: {
|
|
841
|
+
"application/json": {
|
|
842
|
+
schema: {
|
|
843
|
+
type: "object",
|
|
844
|
+
properties: {
|
|
845
|
+
data: {
|
|
846
|
+
type: "array",
|
|
847
|
+
items: { $ref: `#/components/schemas/${schemaName}` }
|
|
848
|
+
},
|
|
849
|
+
meta: {
|
|
850
|
+
type: "object",
|
|
851
|
+
properties: {
|
|
852
|
+
total: { type: "integer" },
|
|
853
|
+
limit: { type: "integer" },
|
|
854
|
+
offset: { type: "integer" }
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
paths[countPath(detail.name, tableName)] = {
|
|
866
|
+
get: {
|
|
867
|
+
summary: `Count ${detail.name}.${tableName}`,
|
|
868
|
+
operationId: `count_${detail.name.replace(/-/g, "_")}_${tableName}`,
|
|
869
|
+
parameters: tableParameters(table),
|
|
870
|
+
responses: {
|
|
871
|
+
"200": {
|
|
872
|
+
description: "Row count for the filtered table query.",
|
|
873
|
+
content: {
|
|
874
|
+
"application/json": {
|
|
875
|
+
schema: {
|
|
876
|
+
type: "object",
|
|
877
|
+
properties: { count: { type: "integer" } },
|
|
878
|
+
required: ["count"],
|
|
879
|
+
example: { count: table.rowCount }
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
return {
|
|
889
|
+
openapi: "3.1.0",
|
|
890
|
+
info: {
|
|
891
|
+
title: `${detail.name} Subgraph API`,
|
|
892
|
+
version: detail.version,
|
|
893
|
+
...detail.description ? { description: detail.description } : {}
|
|
894
|
+
},
|
|
895
|
+
servers: [{ url: serverUrl }],
|
|
896
|
+
paths,
|
|
897
|
+
components: { schemas },
|
|
898
|
+
"x-secondlayer-subgraph": detail.name,
|
|
899
|
+
"x-secondlayer-version": detail.version,
|
|
900
|
+
"x-secondlayer-schema-hash": detail.schemaHash,
|
|
901
|
+
"x-secondlayer-generated-at": generatedAt(options),
|
|
902
|
+
"x-secondlayer-sources": detail.sources ?? {},
|
|
903
|
+
"x-secondlayer-tables": Object.keys(detail.tables)
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
function generateSubgraphMarkdown(detail, options = {}) {
|
|
907
|
+
const agent = generateSubgraphAgentSchema(detail, options);
|
|
908
|
+
const lines = [
|
|
909
|
+
`# ${detail.name} Subgraph API`,
|
|
910
|
+
"",
|
|
911
|
+
`Version: ${detail.version}`,
|
|
912
|
+
detail.schemaHash ? `Schema hash: ${detail.schemaHash}` : undefined,
|
|
913
|
+
`Server: ${agent.serverUrl}`,
|
|
914
|
+
"",
|
|
915
|
+
detail.description
|
|
916
|
+
].filter((line) => line !== undefined && line !== "");
|
|
917
|
+
for (const [tableName, table] of Object.entries(agent.tables)) {
|
|
918
|
+
lines.push("", `## ${tableName}`, "", `GET ${table.endpoint}`, `GET ${table.countEndpoint}`, "", `Rows: ${table.rowCount}`, "", "### Columns", "", "| Column | Type | Attributes |", "| --- | --- | --- |");
|
|
919
|
+
for (const [columnName, col] of Object.entries(table.columns)) {
|
|
920
|
+
const attrs = [
|
|
921
|
+
SYSTEM_COLUMNS.includes(columnName) ? "system" : undefined,
|
|
922
|
+
col.nullable ? "nullable" : undefined,
|
|
923
|
+
col.indexed ? "indexed" : undefined,
|
|
924
|
+
col.searchable ? "searchable" : undefined
|
|
925
|
+
].filter(Boolean).join(", ");
|
|
926
|
+
lines.push(`| \`${columnName}\` | \`${col.type}\` | ${attrs || "-"} |`);
|
|
927
|
+
}
|
|
928
|
+
lines.push("", "### Query", "", `Parameters: ${table.query.parameters.map((p) => `\`${p}\``).join(", ")}`, `Filters: ${table.query.filters.map((p) => `\`${p}\``).join(", ")}`, "", "### Example", "", "```bash", table.examples.curl, "```");
|
|
929
|
+
}
|
|
930
|
+
return `${lines.join(`
|
|
931
|
+
`)}
|
|
932
|
+
`;
|
|
933
|
+
}
|
|
934
|
+
function generateSubgraphSpec(detail, format, options = {}) {
|
|
935
|
+
if (format === "openapi")
|
|
936
|
+
return generateSubgraphOpenApi(detail, options);
|
|
937
|
+
if (format === "agent")
|
|
938
|
+
return generateSubgraphAgentSchema(detail, options);
|
|
939
|
+
return generateSubgraphMarkdown(detail, options);
|
|
940
|
+
}
|
|
941
|
+
|
|
599
942
|
// src/crypto/hmac.ts
|
|
600
943
|
var exports_hmac = {};
|
|
601
944
|
__export(exports_hmac, {
|
|
@@ -605,7 +948,7 @@ __export(exports_hmac, {
|
|
|
605
948
|
generateSecret: () => generateSecret,
|
|
606
949
|
createSignatureHeader: () => createSignatureHeader
|
|
607
950
|
});
|
|
608
|
-
import { createHmac, randomBytes } from "crypto";
|
|
951
|
+
import { createHmac, randomBytes } from "node:crypto";
|
|
609
952
|
function generateSecret() {
|
|
610
953
|
return randomBytes(32).toString("hex");
|
|
611
954
|
}
|
|
@@ -639,7 +982,7 @@ function verifySignatureHeader(payload, header, secret, toleranceSeconds = 300)
|
|
|
639
982
|
return false;
|
|
640
983
|
}
|
|
641
984
|
const ts = Number.parseInt(timestamp, 10);
|
|
642
|
-
if (isNaN(ts)) {
|
|
985
|
+
if (Number.isNaN(ts)) {
|
|
643
986
|
return false;
|
|
644
987
|
}
|
|
645
988
|
const now = Math.floor(Date.now() / 1000);
|
|
@@ -661,6 +1004,10 @@ export {
|
|
|
661
1004
|
getErrorMessage,
|
|
662
1005
|
getEnv,
|
|
663
1006
|
getDb,
|
|
1007
|
+
generateSubgraphSpec,
|
|
1008
|
+
generateSubgraphOpenApi,
|
|
1009
|
+
generateSubgraphMarkdown,
|
|
1010
|
+
generateSubgraphAgentSchema,
|
|
664
1011
|
formatSubscriptionSchemaErrors,
|
|
665
1012
|
exports_hmac as crypto,
|
|
666
1013
|
closeDb,
|
|
@@ -709,5 +1056,5 @@ export {
|
|
|
709
1056
|
AuthenticationError
|
|
710
1057
|
};
|
|
711
1058
|
|
|
712
|
-
//# debugId=
|
|
1059
|
+
//# debugId=B69B5B4AFAFF189E64756E2164756E21
|
|
713
1060
|
//# sourceMappingURL=index.js.map
|