@dontcode2/backend 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/dist/chunk-2OGEV57K.js +850 -0
- package/dist/chunk-2OGEV57K.js.map +1 -0
- package/dist/index.cjs +261 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +196 -3
- package/dist/index.d.ts +196 -3
- package/dist/index.js +253 -36
- package/dist/index.js.map +1 -1
- package/dist/mock/cli.cjs +956 -0
- package/dist/mock/cli.cjs.map +1 -0
- package/dist/mock/cli.d.cts +1 -0
- package/dist/mock/cli.d.ts +1 -0
- package/dist/mock/cli.js +90 -0
- package/dist/mock/cli.js.map +1 -0
- package/dist/mock/index.cjs +886 -0
- package/dist/mock/index.cjs.map +1 -0
- package/dist/mock/index.d.cts +38 -0
- package/dist/mock/index.d.ts +38 -0
- package/dist/mock/index.js +7 -0
- package/dist/mock/index.js.map +1 -0
- package/package.json +14 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mock/db-query.ts","../src/mock/server.ts"],"sourcesContent":["/**\n * Structured-query executor for the local mock's /api/v1/db endpoint.\n *\n * This is a faithful port of the platform gateway's executor: it speaks the\n * exact `{ operation, tableName, options }` wire protocol and produces the same\n * `{ data }` envelopes and status codes (notably 409 on unique/FK conflict, the\n * idempotency signal the SDK relies on). Keeping the SQL generation identical to\n * production is the whole point — a query that works against the mock works\n * against the real gateway and vice versa.\n *\n * The only difference from the platform copy is the database handle: instead of\n * a pooled `pg` connection it runs against any `Queryable` (the mock backs this\n * with in-process Postgres via PGlite), so there are no external services.\n */\n\nexport type WhereOperator = {\n equals?: unknown\n not?: unknown\n gt?: unknown\n gte?: unknown\n lt?: unknown\n lte?: unknown\n in?: unknown[]\n notIn?: unknown[]\n contains?: string\n startsWith?: string\n endsWith?: string\n mode?: 'default' | 'insensitive'\n}\n\nexport type WhereClause = {\n [key: string]: unknown\n AND?: WhereClause[]\n OR?: WhereClause[]\n NOT?: WhereClause\n}\n\nexport type OrderByClause = Record<string, 'asc' | 'desc'>\n\nexport interface QueryOptions {\n where?: WhereClause\n select?: string[]\n orderBy?: OrderByClause\n limit?: number\n offset?: number\n include?: unknown\n data?: Record<string, unknown>\n}\n\n/** The subset of a Postgres driver this executor needs. PGlite satisfies it. */\nexport interface Queryable {\n query(\n sql: string,\n params?: unknown[]\n ): Promise<{ rows: Array<Record<string, unknown>>; affectedRows?: number }>\n}\n\nexport type DbResult =\n | { status: number; body: { data: unknown } }\n | { status: number; body: { error: string } }\n\nclass QueryValidationError extends Error {}\n\n/** Identifiers (tables, columns) — never parameterizable, so strictly validated. */\nfunction ident(name: string): string {\n if (typeof name !== 'string' || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {\n throw new QueryValidationError(`Invalid identifier: ${JSON.stringify(name)}`)\n }\n return `\"${name}\"`\n}\n\nfunction buildWhereClause(\n where: WhereClause,\n startParamIndex = 1\n): { clause: string; values: unknown[] } {\n const conditions: string[] = []\n const values: unknown[] = []\n let paramIndex = startParamIndex\n\n for (const [key, value] of Object.entries(where)) {\n if (key === 'AND' && Array.isArray(value)) {\n const subClauses = value.map((subWhere) => {\n const result = buildWhereClause(subWhere, paramIndex)\n paramIndex += result.values.length\n values.push(...result.values)\n return result.clause.replace('WHERE ', '')\n })\n if (subClauses.length > 0) conditions.push(`(${subClauses.join(' AND ')})`)\n continue\n }\n\n if (key === 'OR' && Array.isArray(value)) {\n const subClauses = value.map((subWhere) => {\n const result = buildWhereClause(subWhere, paramIndex)\n paramIndex += result.values.length\n values.push(...result.values)\n return result.clause.replace('WHERE ', '')\n })\n if (subClauses.length > 0) conditions.push(`(${subClauses.join(' OR ')})`)\n continue\n }\n\n if (key === 'NOT' && value && typeof value === 'object') {\n const result = buildWhereClause(value as WhereClause, paramIndex)\n paramIndex += result.values.length\n values.push(...result.values)\n const notClause = result.clause.replace('WHERE ', '')\n if (notClause) conditions.push(`NOT (${notClause})`)\n continue\n }\n\n const column = ident(key)\n\n if (value === null) {\n conditions.push(`${column} IS NULL`)\n continue\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) {\n conditions.push('FALSE')\n } else {\n const placeholders = value.map(() => `$${paramIndex++}`).join(', ')\n conditions.push(`${column} IN (${placeholders})`)\n values.push(...value)\n }\n continue\n }\n\n if (value && typeof value === 'object' && !(value instanceof Date)) {\n const operators = value as WhereOperator\n const like = operators.mode === 'insensitive' ? 'ILIKE' : 'LIKE'\n\n for (const [operator, operatorValue] of Object.entries(operators)) {\n if (operator === 'mode') continue\n\n switch (operator) {\n case 'equals':\n case 'eq':\n if (operatorValue === null) {\n conditions.push(`${column} IS NULL`)\n } else {\n conditions.push(`${column} = $${paramIndex++}`)\n values.push(operatorValue)\n }\n break\n\n case 'not':\n if (operatorValue === null) {\n conditions.push(`${column} IS NOT NULL`)\n } else if (Array.isArray(operatorValue)) {\n if (operatorValue.length === 0) {\n conditions.push('TRUE')\n } else {\n const placeholders = operatorValue\n .map(() => `$${paramIndex++}`)\n .join(', ')\n conditions.push(`${column} NOT IN (${placeholders})`)\n values.push(...operatorValue)\n }\n } else {\n conditions.push(`${column} != $${paramIndex++}`)\n values.push(operatorValue)\n }\n break\n\n case 'gt':\n conditions.push(`${column} > $${paramIndex++}`)\n values.push(operatorValue)\n break\n\n case 'gte':\n conditions.push(`${column} >= $${paramIndex++}`)\n values.push(operatorValue)\n break\n\n case 'lt':\n conditions.push(`${column} < $${paramIndex++}`)\n values.push(operatorValue)\n break\n\n case 'lte':\n conditions.push(`${column} <= $${paramIndex++}`)\n values.push(operatorValue)\n break\n\n case 'in':\n if (!Array.isArray(operatorValue) || operatorValue.length === 0) {\n conditions.push('FALSE')\n } else {\n const placeholders = operatorValue\n .map(() => `$${paramIndex++}`)\n .join(', ')\n conditions.push(`${column} IN (${placeholders})`)\n values.push(...operatorValue)\n }\n break\n\n case 'notIn':\n if (!Array.isArray(operatorValue) || operatorValue.length === 0) {\n conditions.push('TRUE')\n } else {\n const placeholders = operatorValue\n .map(() => `$${paramIndex++}`)\n .join(', ')\n conditions.push(`${column} NOT IN (${placeholders})`)\n values.push(...operatorValue)\n }\n break\n\n case 'contains':\n conditions.push(`${column} ${like} $${paramIndex++}`)\n values.push(`%${operatorValue}%`)\n break\n\n case 'startsWith':\n conditions.push(`${column} ${like} $${paramIndex++}`)\n values.push(`${operatorValue}%`)\n break\n\n case 'endsWith':\n conditions.push(`${column} ${like} $${paramIndex++}`)\n values.push(`%${operatorValue}`)\n break\n\n default:\n throw new QueryValidationError(`Unsupported operator: ${operator}`)\n }\n }\n continue\n }\n\n conditions.push(`${column} = $${paramIndex++}`)\n values.push(value)\n }\n\n return {\n clause: conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '',\n values,\n }\n}\n\nfunction buildOrderByClause(orderBy?: OrderByClause): string {\n if (!orderBy) return ''\n const orders = Object.entries(orderBy).map(([col, dir]) => {\n if (dir !== 'asc' && dir !== 'desc') {\n throw new QueryValidationError(`Invalid sort direction: ${JSON.stringify(dir)}`)\n }\n return `${ident(col)} ${dir.toUpperCase()}`\n })\n return `ORDER BY ${orders.join(', ')}`\n}\n\nfunction buildSelectColumns(select?: string[]): string {\n if (!select || select.length === 0) return '*'\n return select.map(ident).join(', ')\n}\n\nfunction clampInt(value: unknown, max: number): number | null {\n const n = Number(value)\n if (!Number.isFinite(n) || n < 0) return null\n return Math.min(Math.floor(n), max)\n}\n\nconst MAX_LIMIT = 1000\n\ninterface PgError {\n code?: string\n message?: string\n}\n\nfunction errorResult(err: unknown): DbResult {\n if (err instanceof QueryValidationError) {\n return { status: 400, body: { error: err.message } }\n }\n\n const pgErr = err as PgError\n const message = pgErr.message ?? 'Database error'\n\n // Constraint conflicts get 409 so idempotent insert patterns\n // (insert → on 409 fetch existing) work without raw-SQL upserts.\n if (pgErr.code === '23505' || pgErr.code === '23503') {\n return { status: 409, body: { error: message } }\n }\n // Undefined table/column — the caller's own schema mistake.\n if (pgErr.code === '42P01' || pgErr.code === '42703') {\n return { status: 400, body: { error: message } }\n }\n\n return { status: 500, body: { error: message } }\n}\n\nfunction rowCount(result: { rows: unknown[]; affectedRows?: number }): number {\n // RETURNING * makes rows reflect the affected set; affectedRows is the\n // authoritative driver count when present.\n return result.affectedRows ?? result.rows.length\n}\n\n/**\n * Execute one structured-protocol operation against the mock's schema. Returns\n * a status + JSON body; never throws on user-caused failures.\n */\nexport async function executeDbOperation(\n db: Queryable,\n schema: string,\n operation: string,\n tableName: string,\n options: QueryOptions\n): Promise<DbResult> {\n try {\n const table = `${ident(schema)}.${ident(tableName)}`\n\n if (\n options.include &&\n (!Array.isArray(options.include) || options.include.length > 0) &&\n Object.keys(options.include).length > 0\n ) {\n return {\n status: 400,\n body: { error: 'include is not yet supported on the public API' },\n }\n }\n\n switch (operation) {\n case 'find':\n case 'findMany': {\n const { where, select, orderBy, limit, offset } = options\n const whereClause = where ? buildWhereClause(where) : { clause: '', values: [] }\n const limitValue = limit !== undefined ? clampInt(limit, MAX_LIMIT) : MAX_LIMIT\n const offsetValue = offset !== undefined ? clampInt(offset, 1e9) : null\n\n const query = [\n `SELECT ${buildSelectColumns(select)} FROM ${table}`,\n whereClause.clause,\n buildOrderByClause(orderBy),\n limitValue !== null ? `LIMIT ${limitValue}` : '',\n offsetValue !== null && offsetValue > 0 ? `OFFSET ${offsetValue}` : '',\n ]\n .filter(Boolean)\n .join(' ')\n\n const result = await db.query(query, whereClause.values)\n return { status: 200, body: { data: result.rows } }\n }\n\n case 'findFirst':\n case 'findOne': {\n const { where, select, orderBy } = options\n const whereClause = where ? buildWhereClause(where) : { clause: '', values: [] }\n\n const query = [\n `SELECT ${buildSelectColumns(select)} FROM ${table}`,\n whereClause.clause,\n buildOrderByClause(orderBy),\n 'LIMIT 1',\n ]\n .filter(Boolean)\n .join(' ')\n\n const result = await db.query(query, whereClause.values)\n return { status: 200, body: { data: result.rows[0] ?? null } }\n }\n\n case 'insert': {\n const { data } = options\n if (!data || typeof data !== 'object' || Object.keys(data).length === 0) {\n return { status: 400, body: { error: 'Insert requires a data object' } }\n }\n\n const columns = Object.keys(data).map(ident)\n const values = Object.values(data)\n const placeholders = values.map((_, i) => `$${i + 1}`).join(', ')\n\n const query = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${placeholders}) RETURNING *`\n const result = await db.query(query, values)\n const row = result.rows[0]\n return { status: 200, body: { data: { id: row?.id ?? row } } }\n }\n\n case 'update': {\n const { where, data } = options\n if (!data || typeof data !== 'object' || Object.keys(data).length === 0) {\n return { status: 400, body: { error: 'Update requires a data object' } }\n }\n if (!where || Object.keys(where).length === 0) {\n return {\n status: 400,\n body: {\n error: 'Update requires a WHERE clause to prevent accidental updates of all records',\n },\n }\n }\n\n const setClauses: string[] = []\n const values: unknown[] = []\n let paramIndex = 1\n for (const [key, value] of Object.entries(data)) {\n setClauses.push(`${ident(key)} = $${paramIndex++}`)\n values.push(value)\n }\n\n const whereClause = buildWhereClause(where, paramIndex)\n values.push(...whereClause.values)\n\n const query = `UPDATE ${table} SET ${setClauses.join(', ')} ${whereClause.clause} RETURNING *`\n const result = await db.query(query, values)\n return { status: 200, body: { data: { count: rowCount(result) } } }\n }\n\n case 'delete': {\n const { where } = options\n const whereClause = where ? buildWhereClause(where) : { clause: '', values: [] }\n if (!whereClause.clause) {\n return {\n status: 400,\n body: {\n error: 'Delete requires a WHERE clause to prevent accidental deletion of all records',\n },\n }\n }\n\n const query = `DELETE FROM ${table} ${whereClause.clause} RETURNING *`\n const result = await db.query(query, whereClause.values)\n return { status: 200, body: { data: { count: rowCount(result) } } }\n }\n\n case 'count': {\n const { where } = options\n const whereClause = where ? buildWhereClause(where) : { clause: '', values: [] }\n const query = [`SELECT COUNT(*) as count FROM ${table}`, whereClause.clause]\n .filter(Boolean)\n .join(' ')\n\n const result = await db.query(query, whereClause.values)\n return { status: 200, body: { data: parseInt(String(result.rows[0].count), 10) } }\n }\n\n default:\n return { status: 400, body: { error: 'Invalid operation' } }\n }\n } catch (err) {\n return errorResult(err)\n }\n}\n","/**\n * A local, zero-dependency-to-run mock of the DontCode v1 gateway.\n *\n * The SDK is a thin, typed proxy over a fixed HTTP wire protocol, so the\n * cleanest way to develop against it offline is to stand up a server that\n * speaks that same protocol. Point the SDK at it with a single env var and\n * everything else — auth, database, storage — works unchanged:\n *\n * DONTCODE_API_URL=http://localhost:4000\n * DONTCODE_API_KEY=dc_local_dev # any dc_… value is accepted by default\n *\n * Fidelity comes from reusing production's exact pieces where it matters:\n * - DB runs the real structured-query executor against in-process Postgres\n * (PGlite), so the generated SQL, the `{ data }` envelopes, and the 409\n * conflict signal all match the gateway.\n * - `db.migrate` executes your real DDL against that same Postgres.\n * - Auth issues real JWT-shaped tokens whose claims `decodeAccessToken`\n * (and therefore `getSession`) reads exactly as it would in production.\n * - Storage reads and writes real files and serves them over this server.\n *\n * It is a DEV tool: passwords are stored in plaintext, tokens are unsigned,\n * and there is no rate limiting. Never expose it to a network you don't trust.\n */\nimport { executeDbOperation, type Queryable } from './db-query'\nimport { randomUUID } from 'node:crypto'\nimport { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http'\nimport { tmpdir } from 'node:os'\nimport { dirname, join } from 'node:path'\n\nexport interface MockServerOptions {\n /** Port to listen on. Default 4000. */\n port?: number\n /** Host/interface to bind. Default '127.0.0.1' (loopback only). */\n host?: string\n /**\n * Directory for persisted state (Postgres data, uploaded files, auth users).\n * Relative paths resolve from the cwd. Pass `null` for an ephemeral, in-memory\n * instance that starts empty every time (good for tests). Default\n * `.dontcode-mock`.\n */\n dataDir?: string | null\n /**\n * If set, only this exact API key is accepted (sent as `Authorization:\n * Bearer <key>`). If omitted, any `dc_…` bearer is accepted — the friendliest\n * default for local dev. A request with no bearer always gets a 401, matching\n * the real gateway's \"Missing API key\".\n */\n apiKey?: string\n /** Postgres schema the structured queries run against. Default 'public'. */\n schema?: string\n /** Suppress the startup banner and request logging. Default false. */\n quiet?: boolean\n}\n\nexport interface MockServer {\n /** The base URL to put in `DONTCODE_API_URL`. */\n url: string\n port: number\n /** Stop the server and release its resources. */\n close(): Promise<void>\n}\n\ninterface MockUser {\n id: string\n email: string\n password: string\n role?: string\n claims?: Record<string, unknown>\n verified: boolean\n}\n\nconst ACCESS_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 7 // 7 days, matches the default cookie\n\n// ── small helpers ────────────────────────────────────────────────────────────\n\nfunction b64url(value: string | object): string {\n const str = typeof value === 'string' ? value : JSON.stringify(value)\n return Buffer.from(str).toString('base64url')\n}\n\n/** A JWT-shaped, UNSIGNED token. `decodeAccessToken` reads `parts[1]` as the\n * claims and never checks the signature, so this is a faithful stand-in. */\nfunction mintToken(user: MockUser): { AccessToken: string; ExpiresIn: number } {\n const now = Math.floor(Date.now() / 1000)\n const header = { alg: 'none', typ: 'JWT' }\n const payload = {\n sub: user.id,\n email: user.email,\n role: user.role,\n claims: user.claims,\n iat: now,\n exp: now + ACCESS_TOKEN_TTL_SECONDS,\n }\n return {\n AccessToken: `${b64url(header)}.${b64url(payload)}.${b64url('dontcode-mock')}`,\n ExpiresIn: ACCESS_TOKEN_TTL_SECONDS,\n }\n}\n\nfunction decodeToken(token: string): { sub: string; exp?: number } | null {\n const parts = token.split('.')\n if (parts.length < 2) return null\n try {\n const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'))\n return typeof payload?.sub === 'string' ? payload : null\n } catch {\n return null\n }\n}\n\nfunction fileName(path: string): string {\n return path.split('/').filter(Boolean).pop() ?? path\n}\n\n/** Mirror of the gateway's path guard: no leading slashes, no traversal. */\nfunction normalizePath(value: unknown, { allowEmpty = false } = {}): string | null {\n if (typeof value !== 'string') return allowEmpty && value === undefined ? '' : null\n const path = value.replace(/^\\/+/, '').replace(/\\/+$/, '')\n if (!allowEmpty && path.length === 0) return null\n if (path.split('/').some((segment) => segment === '..' || segment === '.')) return null\n // eslint-disable-next-line no-control-regex\n if (/[\\x00-\\x1f]/.test(path)) return null\n return path\n}\n\nfunction readBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n req.on('data', (c: Buffer) => chunks.push(c))\n req.on('end', () => resolve(Buffer.concat(chunks)))\n req.on('error', reject)\n })\n}\n\n// ── the server ────────────────────────────────────────────────────────────────\n\n/**\n * Build and start a mock gateway. Resolves once it is listening; the resolved\n * handle's `url` is what goes in `DONTCODE_API_URL`.\n */\nexport async function startMockServer(options: MockServerOptions = {}): Promise<MockServer> {\n const port = options.port ?? 4000\n const host = options.host ?? '127.0.0.1'\n const schema = options.schema ?? 'public'\n const quiet = options.quiet ?? false\n const ephemeral = options.dataDir === null\n const dataDir = ephemeral\n ? mkdtempSync(join(tmpdir(), 'dontcode-mock-'))\n : (options.dataDir ?? '.dontcode-mock')\n\n if (!ephemeral) mkdirSync(dataDir, { recursive: true })\n\n // ── Postgres (PGlite), lazily imported so the core SDK stays dependency-free.\n const pglite = await loadPGlite()\n const pgDir = ephemeral ? undefined : join(dataDir, 'pgdata')\n const pg = pgDir ? new pglite.PGlite(pgDir) : new pglite.PGlite()\n await pg.query('SELECT 1') // force ready\n const db: Queryable = {\n query: async (sql, params) => {\n const r = await pg.query(sql, params as unknown[] | undefined)\n return { rows: r.rows as Array<Record<string, unknown>>, affectedRows: r.affectedRows }\n },\n }\n\n // ── Auth store (users), persisted to disk unless ephemeral.\n const authFile = join(dataDir, 'auth.json')\n const users = new Map<string, MockUser>()\n if (!ephemeral && existsSync(authFile)) {\n try {\n const saved: MockUser[] = JSON.parse(readFileSync(authFile, 'utf8'))\n for (const u of saved) users.set(u.email.toLowerCase(), u)\n } catch {\n /* corrupt store — start fresh */\n }\n }\n const persistUsers = () => {\n if (ephemeral) return\n writeFileSync(authFile, JSON.stringify([...users.values()], null, 2))\n }\n\n // ── Storage (filesystem + a content-type manifest).\n const storageDir = join(dataDir, 'storage')\n mkdirSync(storageDir, { recursive: true })\n const manifestFile = join(storageDir, 'manifest.json')\n const manifest = new Map<string, { contentType: string; size: number; lastModified: string }>()\n if (existsSync(manifestFile)) {\n try {\n for (const [k, v] of Object.entries(\n JSON.parse(readFileSync(manifestFile, 'utf8')) as Record<string, never>\n ))\n manifest.set(k, v)\n } catch {\n /* ignore */\n }\n }\n const persistManifest = () =>\n writeFileSync(manifestFile, JSON.stringify(Object.fromEntries(manifest), null, 2))\n const objKey = (bucket: string, path: string) => `${bucket}/${path}`\n const objFile = (bucket: string, path: string) => join(storageDir, bucket, path)\n\n // Resolved to the real value once the server is listening (port may be 0 =\n // \"pick any free port\"). Handlers read it at request time, so it is set by then.\n let baseUrl = `http://localhost:${port}`\n\n function objectShape(bucket: string, path: string) {\n const meta = manifest.get(objKey(bucket, path))\n return {\n key: path,\n name: fileName(path),\n size: meta?.size ?? 0,\n contentType: meta?.contentType ?? 'application/octet-stream',\n lastModified: meta?.lastModified ?? new Date().toISOString(),\n isFolder: false,\n }\n }\n\n function writeObject(bucket: string, path: string, body: Buffer, contentType: string) {\n const file = objFile(bucket, path)\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, body)\n manifest.set(objKey(bucket, path), {\n contentType,\n size: body.length,\n lastModified: new Date().toISOString(),\n })\n persistManifest()\n }\n\n // ── HTTP handler ──────────────────────────────────────────────────────────\n\n const server = createServer((req, res) => {\n handle(req, res).catch((err) => {\n if (!quiet) console.error('[dontcode-mock] handler error:', err)\n sendJson(res, 500, { error: err instanceof Error ? err.message : 'Internal error' })\n })\n })\n\n function sendJson(res: ServerResponse, status: number, body: unknown) {\n const json = JSON.stringify(body)\n res.writeHead(status, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n })\n res.end(json)\n }\n\n function checkApiKey(req: IncomingMessage): boolean {\n const header = req.headers['authorization']\n if (typeof header !== 'string' || !header.startsWith('Bearer ')) return false\n const key = header.slice(7).trim()\n if (options.apiKey) return key === options.apiKey\n return key.startsWith('dc_')\n }\n\n async function handle(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const url = new URL(req.url ?? '/', baseUrl)\n const path = url.pathname\n const method = req.method ?? 'GET'\n\n if (method === 'OPTIONS') {\n res.writeHead(204, {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET,POST,PUT,OPTIONS',\n 'Access-Control-Allow-Headers': 'Authorization,Content-Type,X-Access-Token',\n })\n res.end()\n return\n }\n\n if (!quiet) console.log(`[dontcode-mock] ${method} ${path}`)\n\n // Health check / friendly root.\n if (path === '/' && method === 'GET') {\n sendJson(res, 200, { service: 'dontcode-mock', ok: true })\n return\n }\n\n // Serving + presigned-PUT of stored files. No API key — the URL is the cap.\n if (path.startsWith('/__storage/')) {\n await handleFileEndpoint(req, res, url, method)\n return\n }\n\n if (!path.startsWith('/api/v1/')) {\n sendJson(res, 404, { error: 'Not found' })\n return\n }\n\n // Every /api/v1 call needs a project API key.\n if (!checkApiKey(req)) {\n sendJson(res, 401, {\n error: 'Missing API key. Send it as: Authorization: Bearer <key>',\n })\n return\n }\n\n const raw = await readBody(req)\n\n if (path === '/api/v1/db' && method === 'POST') {\n const body = parseJson(raw)\n const {\n operation,\n tableName,\n options: opts,\n } = body as {\n operation?: string\n tableName?: string\n options?: Record<string, unknown>\n }\n if (!operation || !tableName) {\n sendJson(res, 400, { error: 'Operation and tableName are required' })\n return\n }\n const result = await executeDbOperation(db, schema, operation, tableName, opts ?? {})\n sendJson(res, result.status, result.body)\n return\n }\n\n if (path === '/api/v1/db/migrate' && method === 'POST') {\n const { sql } = parseJson(raw) as { sql?: string }\n if (!sql || typeof sql !== 'string') {\n sendJson(res, 400, { error: 'sql is required' })\n return\n }\n try {\n const results = await pg.exec(sql)\n sendJson(res, 200, {\n success: true,\n executedStatements: Array.isArray(results) ? results.length : 1,\n warnings: [],\n })\n } catch (err) {\n sendJson(res, 400, {\n success: false,\n error: err instanceof Error ? err.message : 'Migration failed',\n })\n }\n return\n }\n\n if (path.startsWith('/api/v1/auth/') && method === 'POST') {\n await handleAuth(req, res, path.slice('/api/v1/auth/'.length), raw)\n return\n }\n\n if (path === '/api/v1/storage') {\n if (method === 'POST') {\n await handleStorageJson(res, parseJson(raw) as Record<string, unknown>)\n return\n }\n if (method === 'PUT') {\n await handleStorageUpload(req, res, raw)\n return\n }\n }\n\n sendJson(res, 404, { error: 'Unknown endpoint' })\n }\n\n // ── Auth ────────────────────────────────────────────────────────────────\n\n async function handleAuth(\n req: IncomingMessage,\n res: ServerResponse,\n endpoint: string,\n raw: Buffer\n ): Promise<void> {\n const body = parseJson(raw) as Record<string, unknown>\n const accessToken =\n typeof req.headers['x-access-token'] === 'string'\n ? (req.headers['x-access-token'] as string)\n : undefined\n\n switch (endpoint) {\n case 'signup': {\n const email = String(body.email ?? '').trim()\n const password = String(body.password ?? '')\n if (!email || !password) {\n sendJson(res, 400, { error: 'Email and password are required' })\n return\n }\n if (users.has(email.toLowerCase())) {\n sendJson(res, 409, { error: 'Email already registered', code: 'EmailExists' })\n return\n }\n const user: MockUser = {\n id: randomUUID(),\n email,\n password,\n role: typeof body.role === 'string' ? body.role : undefined,\n verified: true,\n }\n users.set(email.toLowerCase(), user)\n persistUsers()\n // No email-verification step in the mock: the account is ready to use.\n sendJson(res, 200, { success: true, userId: user.id, verified: true })\n return\n }\n\n case 'login': {\n const email = String(body.email ?? '').trim()\n const password = String(body.password ?? '')\n const user = users.get(email.toLowerCase())\n if (!user || user.password !== password) {\n sendJson(res, 401, { error: 'Invalid email or password' })\n return\n }\n sendJson(res, 200, { success: true, userId: user.id, tokens: mintToken(user) })\n return\n }\n\n case 'me': {\n const decoded = accessToken ? decodeToken(accessToken) : null\n if (!decoded || (decoded.exp && Date.now() / 1000 >= decoded.exp)) {\n sendJson(res, 200, { user: null })\n return\n }\n const user = [...users.values()].find((u) => u.id === decoded.sub)\n if (!user) {\n sendJson(res, 200, { user: null })\n return\n }\n sendJson(res, 200, {\n user: { id: user.id, email: user.email, role: user.role, claims: user.claims },\n })\n return\n }\n\n case 'verify-email':\n case 'forgot-password':\n case 'reset-password':\n sendJson(res, 200, { success: true })\n return\n\n case 'mfa/enroll':\n sendJson(res, 200, {\n success: true,\n secret: 'MOCKMFASECRET',\n otpauth_url: 'otpauth://totp/DontCodeMock?secret=MOCKMFASECRET',\n })\n return\n\n case 'mfa/enroll/confirm':\n sendJson(res, 200, { success: true, recovery_codes: ['mock-recovery-0001'] })\n return\n\n case 'mfa/disable':\n sendJson(res, 200, { success: true })\n return\n\n case 'mfa/challenge':\n // The mock's login never demands a second factor, so a challenge\n // should not occur. Be explicit rather than silently succeed.\n sendJson(res, 400, {\n error: 'MFA is not enabled in the mock',\n code: 'MfaNotOffered',\n })\n return\n\n default:\n sendJson(res, 404, { error: 'Unknown auth endpoint' })\n }\n }\n\n // ── Storage (JSON operations) ─────────────────────────────────────────────\n\n async function handleStorageJson(res: ServerResponse, body: Record<string, unknown>) {\n const operation = body.operation\n const bucket = body.bucket === 'public' || body.bucket === 'private' ? body.bucket : null\n if (!bucket) {\n sendJson(res, 400, { error: 'bucket must be \"public\" or \"private\"' })\n return\n }\n\n const bad = (msg: string) => sendJson(res, 400, { error: msg })\n\n switch (operation) {\n case 'list': {\n const prefix = normalizePath(body.prefix, { allowEmpty: true })\n if (prefix === null) return bad('Invalid prefix')\n const objects: ReturnType<typeof objectShape>[] = []\n for (const key of manifest.keys()) {\n if (!key.startsWith(`${bucket}/`)) continue\n const path = key.slice(bucket.length + 1)\n if (prefix && !path.startsWith(`${prefix}/`) && path !== prefix) continue\n objects.push(objectShape(bucket, path))\n }\n sendJson(res, 200, {\n objects,\n folders: [],\n prefix: prefix ? `${prefix}/` : '',\n truncated: false,\n continuationToken: null,\n })\n return\n }\n\n case 'remove': {\n const paths = Array.isArray(body.paths)\n ? body.paths.map((p) => normalizePath(p))\n : null\n if (!paths || paths.length === 0 || paths.some((p) => p === null)) {\n return bad('paths must be a non-empty array of valid paths')\n }\n for (const p of paths as string[]) {\n const file = objFile(bucket, p)\n if (existsSync(file)) rmSync(file)\n manifest.delete(objKey(bucket, p))\n }\n persistManifest()\n sendJson(res, 200, { deleted: paths.length })\n return\n }\n\n case 'move': {\n const from = normalizePath(body.from)\n const to = normalizePath(body.to)\n if (!from || !to) return bad('from and to are required')\n const fromFile = objFile(bucket, from)\n if (!existsSync(fromFile)) return sendJson(res, 404, { error: 'File not found' })\n const buf = readFileSync(fromFile)\n const meta = manifest.get(objKey(bucket, from))\n writeObject(bucket, to, buf, meta?.contentType ?? 'application/octet-stream')\n rmSync(fromFile)\n manifest.delete(objKey(bucket, from))\n persistManifest()\n sendJson(res, 200, { object: objectShape(bucket, to) })\n return\n }\n\n case 'createFolder': {\n const path = normalizePath(body.path)\n if (!path) return bad('path is required')\n mkdirSync(join(storageDir, bucket, path), { recursive: true })\n sendJson(res, 200, { created: `${path}/` })\n return\n }\n\n case 'download': {\n const path = normalizePath(body.path)\n if (!path) return bad('path is required')\n const file = objFile(bucket, path)\n if (!existsSync(file)) return sendJson(res, 404, { error: 'File not found' })\n const buf = readFileSync(file)\n if (buf.length > 8 * 1024 * 1024) {\n return bad('File is too large to download inline; use getTemporaryUrl instead')\n }\n const meta = manifest.get(objKey(bucket, path))\n sendJson(res, 200, {\n body: buf.toString('base64'),\n contentType: meta?.contentType ?? 'application/octet-stream',\n size: buf.length,\n })\n return\n }\n\n case 'getUrl': {\n if (bucket !== 'public') return bad('getUrl is only available on the public bucket')\n const path = normalizePath(body.path)\n if (!path) return bad('path is required')\n sendJson(res, 200, { url: `${baseUrl}/__storage/public/${path}` })\n return\n }\n\n case 'getTemporaryUrl': {\n const path = normalizePath(body.path)\n if (!path) return bad('path is required')\n const requested = Number(body.expiresIn)\n const expiresIn =\n Number.isFinite(requested) && requested > 0\n ? Math.min(Math.floor(requested), 7 * 24 * 60 * 60)\n : 300\n sendJson(res, 200, {\n url: `${baseUrl}/__storage/${bucket}/${path}?expires=${expiresIn}`,\n expiresIn,\n })\n return\n }\n\n case 'presignUpload': {\n const path = normalizePath(body.path)\n if (!path) return bad('path is required')\n const contentType =\n typeof body.contentType === 'string' && body.contentType.length > 0\n ? body.contentType\n : 'application/octet-stream'\n sendJson(res, 200, {\n url: `${baseUrl}/__storage/${bucket}/${path}?upload=1&contentType=${encodeURIComponent(contentType)}`,\n key: path,\n expiresIn: 600,\n })\n return\n }\n\n default:\n bad('Invalid operation')\n }\n }\n\n // ── Storage (multipart upload) ────────────────────────────────────────────\n\n async function handleStorageUpload(req: IncomingMessage, res: ServerResponse, raw: Buffer) {\n const webReq = new Request(`${baseUrl}/api/v1/storage`, {\n method: 'PUT',\n headers: nodeHeaders(req),\n body: new Uint8Array(raw),\n })\n let form: FormData\n try {\n form = await webReq.formData()\n } catch {\n sendJson(res, 400, { error: 'Upload requires multipart/form-data with a file field' })\n return\n }\n const file = form.get('file')\n if (!(file instanceof File)) {\n sendJson(res, 400, { error: 'file and path are required' })\n return\n }\n const bucket = form.get('bucket')\n if (bucket !== 'public' && bucket !== 'private') {\n sendJson(res, 400, { error: 'bucket must be \"public\" or \"private\"' })\n return\n }\n const path = normalizePath(form.get('path'))\n if (!path) {\n sendJson(res, 400, { error: 'file and path are required' })\n return\n }\n const ctField = form.get('contentType')\n const contentType =\n typeof ctField === 'string' && ctField.length > 0\n ? ctField\n : file.type || 'application/octet-stream'\n const buf = Buffer.from(await file.arrayBuffer())\n writeObject(bucket, path, buf, contentType)\n sendJson(res, 200, { object: objectShape(bucket, path) })\n }\n\n // ── Direct file GET/PUT (public URLs, signed URLs, presigned uploads) ───────\n\n async function handleFileEndpoint(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL,\n method: string\n ) {\n const rest = url.pathname.slice('/__storage/'.length)\n const slash = rest.indexOf('/')\n const bucket = slash === -1 ? rest : rest.slice(0, slash)\n const path = slash === -1 ? '' : rest.slice(slash + 1)\n if ((bucket !== 'public' && bucket !== 'private') || !path) {\n sendJson(res, 404, { error: 'Not found' })\n return\n }\n\n if (method === 'PUT' && url.searchParams.get('upload') === '1') {\n const buf = await readBody(req)\n const contentType =\n url.searchParams.get('contentType') ||\n (typeof req.headers['content-type'] === 'string'\n ? req.headers['content-type']\n : 'application/octet-stream')\n writeObject(bucket, decodeURIComponent(path), buf, contentType)\n res.writeHead(200, { 'Access-Control-Allow-Origin': '*' })\n res.end()\n return\n }\n\n const file = objFile(bucket, decodeURIComponent(path))\n if (!existsSync(file)) {\n sendJson(res, 404, { error: 'File not found' })\n return\n }\n const meta = manifest.get(objKey(bucket, decodeURIComponent(path)))\n const buf = readFileSync(file)\n res.writeHead(200, {\n 'Content-Type': meta?.contentType ?? 'application/octet-stream',\n 'Content-Length': buf.length,\n 'Access-Control-Allow-Origin': '*',\n })\n res.end(buf)\n }\n\n // ── Listen ──────────────────────────────────────────────────────────────\n\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject)\n server.listen(port, host, () => {\n server.off('error', reject)\n resolve()\n })\n })\n\n const address = server.address()\n const actualPort = address && typeof address === 'object' ? address.port : port\n baseUrl = `http://localhost:${actualPort}`\n\n if (!quiet) {\n const where = ephemeral ? 'ephemeral (in-memory)' : dataDir\n console.log(\n `\\n DontCode mock gateway listening on ${baseUrl}\\n` +\n ` data: ${where}\\n\\n` +\n ` Point your app at it:\\n` +\n ` DONTCODE_API_URL=${baseUrl}\\n` +\n ` DONTCODE_API_KEY=dc_local_dev\\n`\n )\n }\n\n return {\n url: baseUrl,\n port: actualPort,\n close: async () => {\n await new Promise<void>((resolve) => server.close(() => resolve()))\n await pg.close?.()\n if (ephemeral) rmSync(dataDir, { recursive: true, force: true })\n },\n }\n}\n\nfunction parseJson(raw: Buffer): Record<string, unknown> {\n if (raw.length === 0) return {}\n try {\n const parsed = JSON.parse(raw.toString('utf8'))\n return parsed && typeof parsed === 'object' ? parsed : {}\n } catch {\n return {}\n }\n}\n\nfunction nodeHeaders(req: IncomingMessage): Headers {\n const headers = new Headers()\n for (const [key, value] of Object.entries(req.headers)) {\n if (Array.isArray(value)) headers.set(key, value.join(', '))\n else if (typeof value === 'string') headers.set(key, value)\n }\n return headers\n}\n\n// ── PGlite loader ──────────────────────────────────────────────────────────────\n\ninterface PGliteModule {\n PGlite: new (dataDir?: string) => {\n query(sql: string, params?: unknown[]): Promise<{ rows: unknown[]; affectedRows?: number }>\n exec(sql: string): Promise<unknown[]>\n close?(): Promise<void>\n }\n}\n\nasync function loadPGlite(): Promise<PGliteModule> {\n try {\n return (await import('@electric-sql/pglite')) as unknown as PGliteModule\n } catch {\n throw new Error(\n 'The DontCode mock needs an in-process Postgres engine that is not installed.\\n' +\n ' Install it with: pnpm add -D @electric-sql/pglite (or npm i -D @electric-sql/pglite)\\n' +\n ' It ships as an optional dependency of @dontcode2/backend, so this usually means\\n' +\n ' it was skipped (e.g. an install run with --no-optional).'\n )\n }\n}\n"],"mappings":"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","names":["buf"]}
|
package/dist/index.cjs
CHANGED
|
@@ -22,16 +22,182 @@ var src_exports = {};
|
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
AuthApi: () => AuthApi,
|
|
24
24
|
BucketClient: () => BucketClient,
|
|
25
|
+
DEFAULT_SESSION_COOKIE_NAME: () => DEFAULT_SESSION_COOKIE_NAME,
|
|
25
26
|
DontCodeError: () => DontCodeError,
|
|
27
|
+
InMemorySessionCache: () => InMemorySessionCache,
|
|
26
28
|
MfaApi: () => MfaApi,
|
|
27
29
|
PublicBucketClient: () => PublicBucketClient,
|
|
28
30
|
TableQuery: () => TableQuery,
|
|
31
|
+
clearSessionCookie: () => clearSessionCookie,
|
|
29
32
|
createStorage: () => createStorage,
|
|
33
|
+
decodeAccessToken: () => decodeAccessToken,
|
|
30
34
|
dontcode: () => dontcode,
|
|
31
|
-
isDontCodeError: () => isDontCodeError
|
|
35
|
+
isDontCodeError: () => isDontCodeError,
|
|
36
|
+
isSessionExpired: () => isSessionExpired,
|
|
37
|
+
readSessionToken: () => readSessionToken,
|
|
38
|
+
serializeSessionCookie: () => serializeSessionCookie
|
|
32
39
|
});
|
|
33
40
|
module.exports = __toCommonJS(src_exports);
|
|
34
41
|
|
|
42
|
+
// src/cookies.ts
|
|
43
|
+
var DEFAULT_SESSION_COOKIE_NAME = "dc_access_token";
|
|
44
|
+
var DEFAULT_MAX_AGE_SECONDS = 60 * 60 * 24 * 7;
|
|
45
|
+
function serialize(name, value, options, maxAge) {
|
|
46
|
+
const sameSite = options.sameSite ?? "lax";
|
|
47
|
+
const secure = sameSite === "none" ? true : options.secure ?? true;
|
|
48
|
+
const httpOnly = options.httpOnly ?? true;
|
|
49
|
+
const path = options.path ?? "/";
|
|
50
|
+
const parts = [`${name}=${encodeURIComponent(value)}`, `Path=${path}`, `Max-Age=${maxAge}`];
|
|
51
|
+
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
52
|
+
parts.push(`SameSite=${sameSite.charAt(0).toUpperCase()}${sameSite.slice(1)}`);
|
|
53
|
+
if (httpOnly) parts.push("HttpOnly");
|
|
54
|
+
if (secure) parts.push("Secure");
|
|
55
|
+
return parts.join("; ");
|
|
56
|
+
}
|
|
57
|
+
function serializeSessionCookie(token, options = {}) {
|
|
58
|
+
const name = options.name ?? DEFAULT_SESSION_COOKIE_NAME;
|
|
59
|
+
const maxAge = options.maxAge ?? DEFAULT_MAX_AGE_SECONDS;
|
|
60
|
+
return serialize(name, token, options, maxAge);
|
|
61
|
+
}
|
|
62
|
+
function clearSessionCookie(options = {}) {
|
|
63
|
+
const name = options.name ?? DEFAULT_SESSION_COOKIE_NAME;
|
|
64
|
+
return serialize(name, "", options, 0);
|
|
65
|
+
}
|
|
66
|
+
function readSessionToken(cookieHeader, name = DEFAULT_SESSION_COOKIE_NAME) {
|
|
67
|
+
if (!cookieHeader) return null;
|
|
68
|
+
for (const pair of cookieHeader.split(";")) {
|
|
69
|
+
const eq = pair.indexOf("=");
|
|
70
|
+
if (eq === -1) continue;
|
|
71
|
+
if (pair.slice(0, eq).trim() !== name) continue;
|
|
72
|
+
const raw = pair.slice(eq + 1).trim();
|
|
73
|
+
if (!raw) return null;
|
|
74
|
+
try {
|
|
75
|
+
return decodeURIComponent(raw);
|
|
76
|
+
} catch {
|
|
77
|
+
return raw;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/errors.ts
|
|
84
|
+
var DontCodeError = class extends Error {
|
|
85
|
+
constructor(status, body) {
|
|
86
|
+
const message = typeof body?.error === "string" && body.error.length > 0 ? body.error : `DontCode request failed with status ${status}`;
|
|
87
|
+
super(message);
|
|
88
|
+
this.name = "DontCodeError";
|
|
89
|
+
this.status = status;
|
|
90
|
+
this.code = typeof body?.code === "string" ? body.code : void 0;
|
|
91
|
+
this.body = body ?? {};
|
|
92
|
+
}
|
|
93
|
+
/** True when the request was rejected by the per-key rate limiter. */
|
|
94
|
+
get rateLimited() {
|
|
95
|
+
return this.status === 429;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
function isDontCodeError(err) {
|
|
99
|
+
if (err instanceof DontCodeError) return true;
|
|
100
|
+
return typeof err === "object" && err !== null && err.name === "DontCodeError";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/session.ts
|
|
104
|
+
var DEFAULT_TTL_MS = 6e4;
|
|
105
|
+
var DEFAULT_VERIFY_TIMEOUT_MS = 5e3;
|
|
106
|
+
var InMemorySessionCache = class {
|
|
107
|
+
constructor() {
|
|
108
|
+
this.store = /* @__PURE__ */ new Map();
|
|
109
|
+
}
|
|
110
|
+
get(token) {
|
|
111
|
+
const hit = this.store.get(token);
|
|
112
|
+
if (!hit) return void 0;
|
|
113
|
+
if (Date.now() >= hit.expiresAtMs) {
|
|
114
|
+
this.store.delete(token);
|
|
115
|
+
return void 0;
|
|
116
|
+
}
|
|
117
|
+
return hit.value;
|
|
118
|
+
}
|
|
119
|
+
set(token, value, ttlMs) {
|
|
120
|
+
this.store.set(token, { value, expiresAtMs: Date.now() + ttlMs });
|
|
121
|
+
}
|
|
122
|
+
delete(token) {
|
|
123
|
+
this.store.delete(token);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
function base64UrlDecode(segment) {
|
|
127
|
+
const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
|
|
128
|
+
const padded = base64.length % 4 === 0 ? base64 : base64 + "=".repeat(4 - base64.length % 4);
|
|
129
|
+
if (typeof atob === "function") {
|
|
130
|
+
const binary = atob(padded);
|
|
131
|
+
const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
|
|
132
|
+
return new TextDecoder().decode(bytes);
|
|
133
|
+
}
|
|
134
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
135
|
+
}
|
|
136
|
+
function decodeAccessToken(token) {
|
|
137
|
+
if (!token || typeof token !== "string") return null;
|
|
138
|
+
const parts = token.split(".");
|
|
139
|
+
if (parts.length < 2) return null;
|
|
140
|
+
try {
|
|
141
|
+
const payload = JSON.parse(base64UrlDecode(parts[1]));
|
|
142
|
+
if (!payload || typeof payload.sub !== "string") return null;
|
|
143
|
+
return payload;
|
|
144
|
+
} catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function isSessionExpired(input, opts = {}) {
|
|
149
|
+
const decoded = typeof input === "string" ? decodeAccessToken(input) : input;
|
|
150
|
+
if (!decoded || typeof decoded.exp !== "number") return false;
|
|
151
|
+
const nowSeconds = Date.now() / 1e3;
|
|
152
|
+
return nowSeconds >= decoded.exp - (opts.skewSeconds ?? 0);
|
|
153
|
+
}
|
|
154
|
+
function userFromClaims(decoded) {
|
|
155
|
+
return {
|
|
156
|
+
id: decoded.sub,
|
|
157
|
+
email: typeof decoded.email === "string" ? decoded.email : "",
|
|
158
|
+
role: decoded.role,
|
|
159
|
+
claims: decoded.claims
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
var SessionVerifier = class {
|
|
163
|
+
constructor(auth, options = {}) {
|
|
164
|
+
this.auth = auth;
|
|
165
|
+
this.cache = options.cache ?? new InMemorySessionCache();
|
|
166
|
+
this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
167
|
+
this.verifyTimeoutMs = options.verifyTimeoutMs ?? DEFAULT_VERIFY_TIMEOUT_MS;
|
|
168
|
+
}
|
|
169
|
+
async getSession({ accessToken, mode = "optimistic" }) {
|
|
170
|
+
const decoded = decodeAccessToken(accessToken);
|
|
171
|
+
if (!decoded) return { status: "anonymous", user: null, verified: false };
|
|
172
|
+
if (isSessionExpired(decoded)) {
|
|
173
|
+
return { status: "expired", user: null, verified: false, expiresAt: decoded.exp };
|
|
174
|
+
}
|
|
175
|
+
const optimistic = {
|
|
176
|
+
status: "active",
|
|
177
|
+
user: userFromClaims(decoded),
|
|
178
|
+
verified: false,
|
|
179
|
+
expiresAt: decoded.exp
|
|
180
|
+
};
|
|
181
|
+
if (mode === "optimistic") return optimistic;
|
|
182
|
+
const cached = this.cache.get(accessToken);
|
|
183
|
+
if (cached) return cached;
|
|
184
|
+
try {
|
|
185
|
+
const { user } = await this.auth.me({
|
|
186
|
+
accessToken,
|
|
187
|
+
timeoutMs: this.verifyTimeoutMs
|
|
188
|
+
});
|
|
189
|
+
const result = user ? { status: "active", user, verified: true, expiresAt: decoded.exp } : { status: "anonymous", user: null, verified: true };
|
|
190
|
+
this.cache.set(accessToken, result, this.ttlMs);
|
|
191
|
+
return result;
|
|
192
|
+
} catch (err) {
|
|
193
|
+
if (isDontCodeError(err) && err.status === 401) {
|
|
194
|
+
return { status: "anonymous", user: null, verified: true };
|
|
195
|
+
}
|
|
196
|
+
return { ...optimistic, status: "unavailable" };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
35
201
|
// src/auth.ts
|
|
36
202
|
var AUTH_BASE = "/api/v1/auth";
|
|
37
203
|
var MfaApi = class {
|
|
@@ -76,9 +242,10 @@ var MfaApi = class {
|
|
|
76
242
|
}
|
|
77
243
|
};
|
|
78
244
|
var AuthApi = class {
|
|
79
|
-
constructor(transport) {
|
|
245
|
+
constructor(transport, sessionOptions) {
|
|
80
246
|
this.transport = transport;
|
|
81
247
|
this.mfa = new MfaApi(transport);
|
|
248
|
+
this.sessions = new SessionVerifier(this, sessionOptions);
|
|
82
249
|
}
|
|
83
250
|
/** Create an account. If the project requires email verification the
|
|
84
251
|
* response has `verification_required: true` and NO tokens; collect a
|
|
@@ -100,9 +267,46 @@ var AuthApi = class {
|
|
|
100
267
|
password: input.password
|
|
101
268
|
});
|
|
102
269
|
}
|
|
103
|
-
/** Resolve the signed-in user from their access token, or `{ user: null }`.
|
|
270
|
+
/** Resolve the signed-in user from their access token, or `{ user: null }`.
|
|
271
|
+
* This is a network round-trip; for a per-navigation guard prefer
|
|
272
|
+
* `getSession`, which can answer offline and caches verified results. */
|
|
104
273
|
me(input) {
|
|
105
|
-
return this.transport.json(
|
|
274
|
+
return this.transport.json(
|
|
275
|
+
`${AUTH_BASE}/me`,
|
|
276
|
+
{},
|
|
277
|
+
{ accessToken: input.accessToken, timeoutMs: input.timeoutMs }
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Resolve an access token into a session for a route guard, the one call
|
|
282
|
+
* that replaces "hit `me` on every navigation". Two modes:
|
|
283
|
+
*
|
|
284
|
+
* - `'optimistic'` (default): decode the token locally and trust its
|
|
285
|
+
* claims. Zero network, zero stall. The right default for gating page
|
|
286
|
+
* loads. It does NOT verify the signature and will not notice a
|
|
287
|
+
* server-side revocation until the token's own `exp`.
|
|
288
|
+
* - `'verified'`: confirm against the gateway's `me`, cached for a short
|
|
289
|
+
* TTL with a hard timeout. Use it before sensitive actions. On a
|
|
290
|
+
* timeout/outage it returns `status: 'unavailable'` with the optimistic
|
|
291
|
+
* user, so you choose whether to fail open rather than the SDK guessing.
|
|
292
|
+
*
|
|
293
|
+
* See the BYOC docs ("Sessions") for the full reasoning and best practices.
|
|
294
|
+
*/
|
|
295
|
+
getSession(input) {
|
|
296
|
+
return this.sessions.getSession(input);
|
|
297
|
+
}
|
|
298
|
+
/** Read the access token from a `Cookie` request header and resolve it, in
|
|
299
|
+
* one call. `name` defaults to `dc_access_token`. Returns the anonymous
|
|
300
|
+
* session when no cookie is present. */
|
|
301
|
+
sessionFromCookies(cookieHeader, options = {}) {
|
|
302
|
+
const token = readSessionToken(cookieHeader, options.cookieName);
|
|
303
|
+
if (!token) return Promise.resolve({ status: "anonymous", user: null, verified: false });
|
|
304
|
+
return this.sessions.getSession({ accessToken: token, mode: options.mode });
|
|
305
|
+
}
|
|
306
|
+
/** Decode an access token's claims locally without a network call or any
|
|
307
|
+
* signature check. Convenience re-export of `decodeAccessToken`. */
|
|
308
|
+
decodeToken(token) {
|
|
309
|
+
return decodeAccessToken(token);
|
|
106
310
|
}
|
|
107
311
|
/** Confirm the 6-digit code emailed at signup. */
|
|
108
312
|
verifyEmail(input) {
|
|
@@ -192,27 +396,8 @@ function createDb(transport) {
|
|
|
192
396
|
});
|
|
193
397
|
}
|
|
194
398
|
|
|
195
|
-
// src/errors.ts
|
|
196
|
-
var DontCodeError = class extends Error {
|
|
197
|
-
constructor(status, body) {
|
|
198
|
-
const message = typeof body?.error === "string" && body.error.length > 0 ? body.error : `DontCode request failed with status ${status}`;
|
|
199
|
-
super(message);
|
|
200
|
-
this.name = "DontCodeError";
|
|
201
|
-
this.status = status;
|
|
202
|
-
this.code = typeof body?.code === "string" ? body.code : void 0;
|
|
203
|
-
this.body = body ?? {};
|
|
204
|
-
}
|
|
205
|
-
/** True when the request was rejected by the per-key rate limiter. */
|
|
206
|
-
get rateLimited() {
|
|
207
|
-
return this.status === 429;
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
function isDontCodeError(err) {
|
|
211
|
-
if (err instanceof DontCodeError) return true;
|
|
212
|
-
return typeof err === "object" && err !== null && err.name === "DontCodeError";
|
|
213
|
-
}
|
|
214
|
-
|
|
215
399
|
// src/http.ts
|
|
400
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
216
401
|
var Transport = class {
|
|
217
402
|
constructor(config) {
|
|
218
403
|
this.config = config;
|
|
@@ -226,22 +411,54 @@ var Transport = class {
|
|
|
226
411
|
url(path) {
|
|
227
412
|
return `${this.config.baseUrl}${path}`;
|
|
228
413
|
}
|
|
414
|
+
timeout(opts) {
|
|
415
|
+
const value = opts?.timeoutMs ?? this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
416
|
+
return value > 0 ? value : 0;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* One fetch, with a timeout that turns "hung socket" into a fast, typed
|
|
420
|
+
* failure. A timeout surfaces as `DontCodeError` with status 408 / code
|
|
421
|
+
* `Timeout`; any other transport failure (DNS, refused, offline) as status
|
|
422
|
+
* 0 / code `NetworkError`. Both are distinct from a real `401`, so an auth
|
|
423
|
+
* guard can tell "backend is down" apart from "user is signed out".
|
|
424
|
+
*/
|
|
425
|
+
async send(path, init, opts) {
|
|
426
|
+
const timeoutMs = this.timeout(opts);
|
|
427
|
+
const controller = timeoutMs > 0 ? new AbortController() : void 0;
|
|
428
|
+
const timer = controller ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
|
|
429
|
+
try {
|
|
430
|
+
return await fetch(this.url(path), { ...init, signal: controller?.signal });
|
|
431
|
+
} catch (err) {
|
|
432
|
+
if (controller?.signal.aborted) {
|
|
433
|
+
throw new DontCodeError(408, {
|
|
434
|
+
error: `Request to ${path} timed out after ${timeoutMs}ms`,
|
|
435
|
+
code: "Timeout"
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
throw new DontCodeError(0, {
|
|
439
|
+
error: err instanceof Error ? err.message : "Network request failed",
|
|
440
|
+
code: "NetworkError"
|
|
441
|
+
});
|
|
442
|
+
} finally {
|
|
443
|
+
if (timer) clearTimeout(timer);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
229
446
|
/** POST a JSON body and parse the JSON response. */
|
|
230
447
|
async json(path, body, opts) {
|
|
231
|
-
const res = await
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
448
|
+
const res = await this.send(
|
|
449
|
+
path,
|
|
450
|
+
{
|
|
451
|
+
method: "POST",
|
|
452
|
+
headers: { ...this.headers(opts), "Content-Type": "application/json" },
|
|
453
|
+
body: JSON.stringify(body ?? {})
|
|
454
|
+
},
|
|
455
|
+
opts
|
|
456
|
+
);
|
|
236
457
|
return this.parse(res);
|
|
237
458
|
}
|
|
238
459
|
/** PUT a multipart form (file uploads). The runtime sets the boundary. */
|
|
239
460
|
async multipart(path, form, opts) {
|
|
240
|
-
const res = await
|
|
241
|
-
method: "PUT",
|
|
242
|
-
headers: this.headers(opts),
|
|
243
|
-
body: form
|
|
244
|
-
});
|
|
461
|
+
const res = await this.send(path, { method: "PUT", headers: this.headers(opts), body: form }, opts);
|
|
245
462
|
return this.parse(res);
|
|
246
463
|
}
|
|
247
464
|
async parse(res) {
|
|
@@ -351,9 +568,9 @@ function dontcode(options = {}) {
|
|
|
351
568
|
/\/+$/,
|
|
352
569
|
""
|
|
353
570
|
);
|
|
354
|
-
const transport = new Transport({ apiKey, baseUrl });
|
|
571
|
+
const transport = new Transport({ apiKey, baseUrl, timeoutMs: options.timeoutMs });
|
|
355
572
|
return {
|
|
356
|
-
auth: new AuthApi(transport),
|
|
573
|
+
auth: new AuthApi(transport, options.session),
|
|
357
574
|
db: createDb(transport),
|
|
358
575
|
storage: createStorage(transport)
|
|
359
576
|
};
|
|
@@ -362,12 +579,19 @@ function dontcode(options = {}) {
|
|
|
362
579
|
0 && (module.exports = {
|
|
363
580
|
AuthApi,
|
|
364
581
|
BucketClient,
|
|
582
|
+
DEFAULT_SESSION_COOKIE_NAME,
|
|
365
583
|
DontCodeError,
|
|
584
|
+
InMemorySessionCache,
|
|
366
585
|
MfaApi,
|
|
367
586
|
PublicBucketClient,
|
|
368
587
|
TableQuery,
|
|
588
|
+
clearSessionCookie,
|
|
369
589
|
createStorage,
|
|
590
|
+
decodeAccessToken,
|
|
370
591
|
dontcode,
|
|
371
|
-
isDontCodeError
|
|
592
|
+
isDontCodeError,
|
|
593
|
+
isSessionExpired,
|
|
594
|
+
readSessionToken,
|
|
595
|
+
serializeSessionCookie
|
|
372
596
|
});
|
|
373
597
|
//# sourceMappingURL=index.cjs.map
|