@dontcode2/backend 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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(`${AUTH_BASE}/me`, {}, { accessToken: input.accessToken });
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 fetch(this.url(path), {
232
- method: "POST",
233
- headers: { ...this.headers(opts), "Content-Type": "application/json" },
234
- body: JSON.stringify(body ?? {})
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 fetch(this.url(path), {
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
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/auth.ts","../src/db.ts","../src/errors.ts","../src/http.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["export { dontcode } from './client'\nexport type { DontCodeClient, DontCodeClientOptions } from './client'\n\nexport { DontCodeError, isDontCodeError } from './errors'\nexport type { DontCodeErrorBody } from './errors'\n\nexport { AuthApi, MfaApi } from './auth'\nexport { TableQuery, type DbClient } from './db'\nexport { BucketClient, PublicBucketClient, createStorage, type StorageClient } from './storage'\n\nexport type * from './types'\n","import { Transport } from './http'\nimport type {\n ForgotPasswordInput,\n LoginInput,\n LoginResult,\n MeResult,\n MfaChallengeInput,\n MfaDisableInput,\n MfaEnrollConfirmInput,\n MfaEnrollResult,\n ResetPasswordInput,\n SignupInput,\n SignupResult,\n SimpleResult,\n VerifyEmailInput,\n} from './types'\n\nconst AUTH_BASE = '/api/v1/auth'\n\n/**\n * MFA is per-user and opt-in. `enroll`/`enrollConfirm`/`disable` act as the\n * signed-in user, so they need the end-user access token. `challenge` does\n * not; it completes a login that returned `mfa_required`, exchanging the\n * short-lived challenge token for real session tokens.\n */\nexport class MfaApi {\n constructor(private readonly transport: Transport) {}\n\n /** Complete an MFA login. Pass the `challenge_token` from `login`, plus\n * either the authenticator `code` or a `recoveryCode`. */\n challenge(input: MfaChallengeInput): Promise<LoginResult> {\n return this.transport.json<LoginResult>(`${AUTH_BASE}/mfa/challenge`, {\n challenge_token: input.challengeToken,\n code: input.code,\n recovery_code: input.recoveryCode,\n })\n }\n\n /** Begin enrollment. Render the returned `otpauth_url` as a QR code.\n * Enrollment stays pending until `enrollConfirm`. */\n enroll(input: { accessToken: string }): Promise<MfaEnrollResult> {\n return this.transport.json<MfaEnrollResult>(\n `${AUTH_BASE}/mfa/enroll`,\n {},\n { accessToken: input.accessToken }\n )\n }\n\n /** Confirm enrollment with the first authenticator code. The returned\n * `recovery_codes` are shown once and never again. */\n enrollConfirm(input: MfaEnrollConfirmInput): Promise<SimpleResult> {\n return this.transport.json<SimpleResult>(\n `${AUTH_BASE}/mfa/enroll/confirm`,\n { code: input.code },\n { accessToken: input.accessToken }\n )\n }\n\n /** Turn MFA off. Proves possession of the second factor via `code` or\n * `recoveryCode`. */\n disable(input: MfaDisableInput): Promise<SimpleResult> {\n return this.transport.json<SimpleResult>(\n `${AUTH_BASE}/mfa/disable`,\n { code: input.code, recovery_code: input.recoveryCode },\n { accessToken: input.accessToken }\n )\n }\n}\n\n/**\n * Fronts DontCode Auth with the same shapes as the gateway. Two behaviours are\n * project settings (not API flags) and your code must handle both states:\n * email verification (signup may not return tokens) and MFA (login may be two\n * steps). Branch on the resolved value; never assume one round-trip.\n */\nexport class AuthApi {\n readonly mfa: MfaApi\n\n constructor(private readonly transport: Transport) {\n this.mfa = new MfaApi(transport)\n }\n\n /** Create an account. If the project requires email verification the\n * response has `verification_required: true` and NO tokens; collect a\n * code and call `verifyEmail`, then `login`. */\n signup(input: SignupInput): Promise<SignupResult> {\n return this.transport.json<SignupResult>(`${AUTH_BASE}/signup`, {\n email: input.email,\n password: input.password,\n name: input.name,\n role: input.role,\n })\n }\n\n /** Authenticate. Branch on `mfa_required`: when true you hold only a\n * challenge (finish via `mfa.challenge`); otherwise `tokens` is your\n * session. A 403 `EmailNotVerified` means the email step isn't done. */\n login(input: LoginInput): Promise<LoginResult> {\n return this.transport.json<LoginResult>(`${AUTH_BASE}/login`, {\n email: input.email,\n password: input.password,\n })\n }\n\n /** Resolve the signed-in user from their access token, or `{ user: null }`. */\n me(input: { accessToken: string }): Promise<MeResult> {\n return this.transport.json<MeResult>(`${AUTH_BASE}/me`, {}, { accessToken: input.accessToken })\n }\n\n /** Confirm the 6-digit code emailed at signup. */\n verifyEmail(input: VerifyEmailInput): Promise<SimpleResult> {\n return this.transport.json<SimpleResult>(`${AUTH_BASE}/verify-email`, {\n code: input.code,\n email: input.email,\n })\n }\n\n forgotPassword(input: ForgotPasswordInput): Promise<SimpleResult> {\n return this.transport.json<SimpleResult>(`${AUTH_BASE}/forgot-password`, {\n email: input.email,\n })\n }\n\n resetPassword(input: ResetPasswordInput): Promise<SimpleResult> {\n return this.transport.json<SimpleResult>(`${AUTH_BASE}/reset-password`, {\n code: input.code,\n password: input.password,\n email: input.email,\n })\n }\n}\n","import { Transport } from './http'\nimport type {\n DeleteInput,\n MigrateInput,\n MigrateResult,\n QueryOptions,\n UpdateInput,\n} from './types'\n\nconst DB_PATH = '/api/v1/db'\nconst MIGRATE_PATH = '/api/v1/db/migrate'\n\n/** The gateway wraps every DB result in `{ data }`; we unwrap it. */\ninterface DbEnvelope<T> {\n data: T\n}\n\n/**\n * A handle to one table. Structured queries only; there is no raw-SQL escape\n * hatch (schema changes go through `db.migrate`). `update` and `delete`\n * require a `where` clause server-side, so the types make it mandatory.\n */\nexport class TableQuery {\n constructor(\n private readonly transport: Transport,\n private readonly tableName: string\n ) {}\n\n private async run<T>(operation: string, options: unknown): Promise<T> {\n const res = await this.transport.json<DbEnvelope<T>>(DB_PATH, {\n operation,\n tableName: this.tableName,\n options,\n })\n return res.data\n }\n\n /** Rows matching the query (max 1000 per call). */\n find<T = Record<string, unknown>>(options: QueryOptions = {}): Promise<T[]> {\n return this.run<T[]>('find', options)\n }\n\n /** Alias of `find`. */\n findMany<T = Record<string, unknown>>(options: QueryOptions = {}): Promise<T[]> {\n return this.run<T[]>('findMany', options)\n }\n\n /** The first matching row, or `null`. */\n findFirst<T = Record<string, unknown>>(options: QueryOptions = {}): Promise<T | null> {\n return this.run<T | null>('findFirst', options)\n }\n\n /** Alias of `findFirst`. */\n findOne<T = Record<string, unknown>>(options: QueryOptions = {}): Promise<T | null> {\n return this.run<T | null>('findOne', options)\n }\n\n /** Insert one row. Returns `{ id }`. Unique/FK conflicts throw a 409\n * DontCodeError, the supported idempotency signal. */\n insert(data: Record<string, unknown>): Promise<{ id: unknown }> {\n return this.run<{ id: unknown }>('insert', { data })\n }\n\n /** Update rows matching `where`. Returns `{ count }`. */\n update(input: UpdateInput): Promise<{ count: number }> {\n return this.run<{ count: number }>('update', { where: input.where, data: input.data })\n }\n\n /** Delete rows matching `where`. Returns `{ count }`. */\n delete(input: DeleteInput): Promise<{ count: number }> {\n return this.run<{ count: number }>('delete', { where: input.where })\n }\n\n /** Count matching rows. */\n count(options: Pick<QueryOptions, 'where'> = {}): Promise<number> {\n return this.run<number>('count', options)\n }\n}\n\n/**\n * `db.users.find()` and `db('users').find()` both work; the bracket/callable\n * form is there for table names that aren't valid identifiers. `db.migrate()`\n * applies schema DDL (the one place migrations enter from outside).\n */\nexport type DbClient = {\n readonly [tableName: string]: TableQuery\n} & {\n (tableName: string): TableQuery\n migrate(input: MigrateInput): Promise<MigrateResult>\n}\n\nexport function createDb(transport: Transport): DbClient {\n const table = (tableName: string): TableQuery => new TableQuery(transport, tableName)\n const migrate = (input: MigrateInput): Promise<MigrateResult> =>\n transport.json<MigrateResult>(MIGRATE_PATH, { sql: input.sql })\n\n return new Proxy(table, {\n get(target, prop, receiver) {\n if (prop === 'migrate') return migrate\n // Don't manufacture a TableQuery for symbols or promise-unwrapping\n // probes (`then`) or the function's own members.\n if (typeof prop !== 'string' || prop === 'then' || prop in target) {\n return Reflect.get(target, prop, receiver)\n }\n return new TableQuery(transport, prop)\n },\n apply(_target, _thisArg, args: [string]) {\n return table(args[0])\n },\n }) as unknown as DbClient\n}\n","/**\n * Every non-2xx response from the gateway surfaces as a DontCodeError. The\n * platform's error envelope is `{ error, ... }`, sometimes with a machine\n * `code` (e.g. `EmailNotVerified`, `ChallengeExpired`, `MfaNotOffered`) or\n * rate-limit fields. We preserve the whole body so callers can branch on it.\n *\n * Note: many \"one more step\" auth states (signup needing email verification,\n * login returning `mfa_required`) are 2xx successes, NOT errors; inspect the\n * resolved value for those. Errors are reserved for actual failures.\n */\nexport interface DontCodeErrorBody {\n error?: string\n /** Stable machine code, when the platform sends one. */\n code?: string\n /** Present on 429 responses. */\n rate_limit?: boolean\n /** Seconds until the rate limit resets, on 429 responses. */\n timeleft?: number\n [key: string]: unknown\n}\n\nexport class DontCodeError extends Error {\n /** HTTP status code of the failing response. */\n readonly status: number\n /** Stable machine code, when present (e.g. `EmailNotVerified`). */\n readonly code?: string\n /** The raw parsed response body. */\n readonly body: DontCodeErrorBody\n\n constructor(status: number, body: DontCodeErrorBody) {\n const message =\n typeof body?.error === 'string' && body.error.length > 0\n ? body.error\n : `DontCode request failed with status ${status}`\n super(message)\n this.name = 'DontCodeError'\n this.status = status\n this.code = typeof body?.code === 'string' ? body.code : undefined\n this.body = body ?? {}\n }\n\n /** True when the request was rejected by the per-key rate limiter. */\n get rateLimited(): boolean {\n return this.status === 429\n }\n}\n\n/** Cross-bundle-safe check; works even if two copies of the SDK are loaded. */\nexport function isDontCodeError(err: unknown): err is DontCodeError {\n if (err instanceof DontCodeError) return true\n return (\n typeof err === 'object' &&\n err !== null &&\n (err as { name?: unknown }).name === 'DontCodeError'\n )\n}\n","import { DontCodeError, type DontCodeErrorBody } from './errors'\n\nexport interface TransportConfig {\n /** Project API key. When absent, no Authorization header is sent and the\n * gateway responds with its own \"Missing API key\" 401. */\n apiKey?: string\n /** Gateway origin, already normalized (no trailing slash). */\n baseUrl: string\n}\n\nexport interface RequestOptions {\n /** End-user access token, sent as `X-Access-Token` (separate from the\n * project API key). Required by signed-in auth calls. */\n accessToken?: string\n}\n\n/**\n * The single place network requests are made. Everything else in the SDK is a\n * typed shape around `json()` / `multipart()`. No retries, no caching, just a\n * faithful proxy of the v1 gateway.\n */\nexport class Transport {\n constructor(private readonly config: TransportConfig) {}\n\n private headers(opts?: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = {}\n if (this.config.apiKey) headers['Authorization'] = `Bearer ${this.config.apiKey}`\n if (opts?.accessToken) headers['X-Access-Token'] = opts.accessToken\n return headers\n }\n\n private url(path: string): string {\n return `${this.config.baseUrl}${path}`\n }\n\n /** POST a JSON body and parse the JSON response. */\n async json<T>(path: string, body?: unknown, opts?: RequestOptions): Promise<T> {\n const res = await fetch(this.url(path), {\n method: 'POST',\n headers: { ...this.headers(opts), 'Content-Type': 'application/json' },\n body: JSON.stringify(body ?? {}),\n })\n return this.parse<T>(res)\n }\n\n /** PUT a multipart form (file uploads). The runtime sets the boundary. */\n async multipart<T>(path: string, form: FormData, opts?: RequestOptions): Promise<T> {\n const res = await fetch(this.url(path), {\n method: 'PUT',\n headers: this.headers(opts),\n body: form,\n })\n return this.parse<T>(res)\n }\n\n private async parse<T>(res: Response): Promise<T> {\n const raw = await res.text()\n let data: unknown = null\n if (raw) {\n try {\n data = JSON.parse(raw)\n } catch {\n data = { error: raw }\n }\n }\n if (!res.ok) {\n const body: DontCodeErrorBody =\n data && typeof data === 'object'\n ? (data as DontCodeErrorBody)\n : { error: res.statusText || 'Request failed' }\n throw new DontCodeError(res.status, body)\n }\n return data as T\n }\n}\n","import { Transport } from './http'\nimport type {\n DownloadResult,\n ListResult,\n PresignResult,\n StorageBucket,\n StorageObject,\n TemporaryUrlResult,\n UploadBody,\n} from './types'\n\nconst STORAGE_PATH = '/api/v1/storage'\n\nconst DEFAULT_CONTENT_TYPE = 'application/octet-stream'\n\n/** Normalize whatever the caller hands us into a Blob for multipart upload. */\nfunction toBlob(body: UploadBody, contentType: string): Blob {\n if (body instanceof Blob) return body\n if (typeof body === 'string') return new Blob([body], { type: contentType })\n if (body instanceof ArrayBuffer) return new Blob([body], { type: contentType })\n if (ArrayBuffer.isView(body)) {\n return new Blob([body as unknown as BlobPart], { type: contentType })\n }\n throw new TypeError('upload expects a Blob, ArrayBuffer, typed array, or string')\n}\n\nfunction fileName(path: string): string {\n return path.split('/').filter(Boolean).pop() ?? path\n}\n\n/** Operations available on both buckets. */\nexport class BucketClient {\n constructor(\n protected readonly transport: Transport,\n protected readonly bucket: StorageBucket\n ) {}\n\n protected op<T>(operation: string, params: Record<string, unknown> = {}): Promise<T> {\n return this.transport.json<T>(STORAGE_PATH, { operation, bucket: this.bucket, ...params })\n }\n\n /** List objects under `prefix`. */\n list(prefix?: string): Promise<ListResult> {\n return this.op<ListResult>('list', { prefix })\n }\n\n /** Delete one or more objects. Returns `{ deleted }`. */\n remove(paths: string[]): Promise<{ deleted: number }> {\n return this.op<{ deleted: number }>('remove', { paths })\n }\n\n /** Move/rename an object within the bucket. */\n move(from: string, to: string): Promise<{ object: StorageObject }> {\n return this.op<{ object: StorageObject }>('move', { from, to })\n }\n\n createFolder(path: string): Promise<{ created: string }> {\n return this.op<{ created: string }>('createFolder', { path })\n }\n\n /** Download an object inline (≤ 8 MB). Use `getTemporaryUrl` for larger files. */\n download(path: string): Promise<DownloadResult> {\n return this.op<DownloadResult>('download', { path })\n }\n\n /** A short-lived signed URL (default 300s, max 7 days). */\n getTemporaryUrl(path: string, expiresIn?: number): Promise<TemporaryUrlResult> {\n return this.op<TemporaryUrlResult>('getTemporaryUrl', { path, expiresIn })\n }\n\n /** A presigned PUT URL for direct, large uploads (≤ no inline limit). */\n presignUpload(path: string, contentType?: string): Promise<PresignResult> {\n return this.op<PresignResult>('presignUpload', { path, contentType })\n }\n\n /** Upload bytes directly (≤ 100 MB). For larger files, `presignUpload`\n * then PUT to the returned URL yourself. */\n upload(\n path: string,\n body: UploadBody,\n contentType: string = DEFAULT_CONTENT_TYPE\n ): Promise<{ object: StorageObject }> {\n const form = new FormData()\n form.append('file', toBlob(body, contentType), fileName(path))\n form.append('bucket', this.bucket)\n form.append('path', path)\n form.append('contentType', contentType)\n return this.transport.multipart<{ object: StorageObject }>(STORAGE_PATH, form)\n }\n}\n\n/** The public bucket additionally exposes stable public URLs. */\nexport class PublicBucketClient extends BucketClient {\n constructor(transport: Transport) {\n super(transport, 'public')\n }\n\n /** The permanent public URL for an object. */\n getUrl(path: string): Promise<{ url: string }> {\n return this.op<{ url: string }>('getUrl', { path })\n }\n}\n\nexport interface StorageClient {\n public: PublicBucketClient\n private: BucketClient\n}\n\nexport function createStorage(transport: Transport): StorageClient {\n return {\n public: new PublicBucketClient(transport),\n private: new BucketClient(transport, 'private'),\n }\n}\n","import { AuthApi } from './auth'\nimport { createDb, type DbClient } from './db'\nimport { Transport } from './http'\nimport { createStorage, type StorageClient } from './storage'\n\nconst DEFAULT_BASE_URL = 'https://backend.dontcode.co'\n\nexport interface DontCodeClientOptions {\n /** Project API key (`dc_…`). Defaults to `process.env.DONTCODE_API_KEY`.\n * If neither is set, requests fail naturally with the gateway's\n * \"Missing API key\" 401. */\n apiKey?: string\n /** Gateway origin. Defaults to `process.env.DONTCODE_API_URL`, then to\n * `https://backend.dontcode.co`. */\n baseUrl?: string\n}\n\nexport interface DontCodeClient {\n auth: AuthApi\n db: DbClient\n storage: StorageClient\n}\n\n/** Read an env var without assuming `process` exists (e.g. in the browser). */\nfunction fromEnv(name: string): string | undefined {\n if (typeof process === 'undefined' || !process.env) return undefined\n return process.env[name]\n}\n\n/**\n * Create a DontCode backend client. A thin, typed proxy over the v1 HTTP\n * gateway: auth, database, and storage. The API key scopes every request to\n * a single project; there is nothing else to configure.\n *\n * ```ts\n * import { dontcode } from '@dontcode2/backend'\n * const client = dontcode() // reads DONTCODE_API_KEY\n * await client.auth.signup({ email, password, role: 'editor' })\n * ```\n */\nexport function dontcode(options: DontCodeClientOptions = {}): DontCodeClient {\n const apiKey = options.apiKey ?? fromEnv('DONTCODE_API_KEY')\n const baseUrl = (options.baseUrl ?? fromEnv('DONTCODE_API_URL') ?? DEFAULT_BASE_URL).replace(\n /\\/+$/,\n ''\n )\n\n const transport = new Transport({ apiKey, baseUrl })\n\n return {\n auth: new AuthApi(transport),\n db: createDb(transport),\n storage: createStorage(transport),\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/cookies.ts","../src/errors.ts","../src/session.ts","../src/auth.ts","../src/db.ts","../src/http.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["export { dontcode } from './client'\nexport type { DontCodeClient, DontCodeClientOptions } from './client'\n\nexport { DontCodeError, isDontCodeError } from './errors'\nexport type { DontCodeErrorBody } from './errors'\n\nexport { AuthApi, MfaApi } from './auth'\nexport { TableQuery, type DbClient } from './db'\nexport { BucketClient, PublicBucketClient, createStorage, type StorageClient } from './storage'\n\nexport {\n decodeAccessToken,\n isSessionExpired,\n InMemorySessionCache,\n} from './session'\nexport type {\n DecodedSession,\n GetSessionInput,\n SessionCache,\n SessionOptions,\n SessionResult,\n SessionStatus,\n} from './session'\n\nexport {\n DEFAULT_SESSION_COOKIE_NAME,\n clearSessionCookie,\n readSessionToken,\n serializeSessionCookie,\n} from './cookies'\nexport type { SessionCookieOptions } from './cookies'\n\nexport type * from './types'\n","/**\n * Cookie helpers, framework-agnostic by design. They return strings: a\n * `Set-Cookie` header value to write, or a parsed token to read. Your framework\n * applies them (`headers.append('Set-Cookie', …)`, SvelteKit `cookies.set`, a\n * Next `Response` cookie, etc.). The SDK never owns a request or response.\n *\n * Defaults match how DontCode's own apps store the session: an httpOnly cookie\n * so JavaScript can't read the token, `Secure`, `SameSite=Lax`, path `/`, and a\n * 7-day max age. Cross-site setups (your app and the gateway on different sites)\n * need `sameSite: 'none'`, which forces `Secure` on.\n */\n\n/** Default cookie name for the end-user access token. */\nexport const DEFAULT_SESSION_COOKIE_NAME = 'dc_access_token'\n\n/** One week, in seconds. Matches the default session lifetime DontCode issues. */\nconst DEFAULT_MAX_AGE_SECONDS = 60 * 60 * 24 * 7\n\nexport interface SessionCookieOptions {\n /** Cookie name. Default `dc_access_token`. */\n name?: string\n /** Lifetime in seconds. Default one week. Pass the token's `ExpiresIn` to\n * keep the cookie and the token in lockstep. */\n maxAge?: number\n /** Default `/`. */\n path?: string\n domain?: string\n /** Default `true`. */\n secure?: boolean\n /** Default `true`. Keep the token unreadable from client JavaScript. */\n httpOnly?: boolean\n /** Default `'lax'`. Use `'none'` for cross-site (it forces `Secure`). */\n sameSite?: 'lax' | 'strict' | 'none'\n}\n\nfunction serialize(name: string, value: string, options: SessionCookieOptions, maxAge: number): string {\n const sameSite = options.sameSite ?? 'lax'\n // SameSite=None is meaningless without Secure (browsers drop it), so force\n // Secure on there; otherwise it defaults on and the caller can opt out.\n const secure = sameSite === 'none' ? true : (options.secure ?? true)\n const httpOnly = options.httpOnly ?? true\n const path = options.path ?? '/'\n\n const parts = [`${name}=${encodeURIComponent(value)}`, `Path=${path}`, `Max-Age=${maxAge}`]\n if (options.domain) parts.push(`Domain=${options.domain}`)\n parts.push(`SameSite=${sameSite.charAt(0).toUpperCase()}${sameSite.slice(1)}`)\n if (httpOnly) parts.push('HttpOnly')\n if (secure) parts.push('Secure')\n return parts.join('; ')\n}\n\n/** Build a `Set-Cookie` value that stores the access token. */\nexport function serializeSessionCookie(token: string, options: SessionCookieOptions = {}): string {\n const name = options.name ?? DEFAULT_SESSION_COOKIE_NAME\n const maxAge = options.maxAge ?? DEFAULT_MAX_AGE_SECONDS\n return serialize(name, token, options, maxAge)\n}\n\n/** Build a `Set-Cookie` value that clears the access token (logout). */\nexport function clearSessionCookie(options: SessionCookieOptions = {}): string {\n const name = options.name ?? DEFAULT_SESSION_COOKIE_NAME\n return serialize(name, '', options, 0)\n}\n\n/** Read the access token out of a `Cookie` request header, or `null`. Pass the\n * raw header string (`name=value; name2=value2`). */\nexport function readSessionToken(\n cookieHeader: string | null | undefined,\n name: string = DEFAULT_SESSION_COOKIE_NAME\n): string | null {\n if (!cookieHeader) return null\n for (const pair of cookieHeader.split(';')) {\n const eq = pair.indexOf('=')\n if (eq === -1) continue\n if (pair.slice(0, eq).trim() !== name) continue\n const raw = pair.slice(eq + 1).trim()\n if (!raw) return null\n try {\n return decodeURIComponent(raw)\n } catch {\n return raw\n }\n }\n return null\n}\n","/**\n * Every non-2xx response from the gateway surfaces as a DontCodeError. The\n * platform's error envelope is `{ error, ... }`, sometimes with a machine\n * `code` (e.g. `EmailNotVerified`, `ChallengeExpired`, `MfaNotOffered`) or\n * rate-limit fields. We preserve the whole body so callers can branch on it.\n *\n * Note: many \"one more step\" auth states (signup needing email verification,\n * login returning `mfa_required`) are 2xx successes, NOT errors; inspect the\n * resolved value for those. Errors are reserved for actual failures.\n *\n * Transport failures (no HTTP response at all) also surface as a DontCodeError\n * so callers have one error type: a timeout is status 408 / code `Timeout`, and\n * any other network failure is status 0 / code `NetworkError`. Neither is a\n * `401`, so a guard can distinguish \"backend unavailable\" from \"signed out\".\n */\nexport interface DontCodeErrorBody {\n error?: string\n /** Stable machine code, when the platform sends one. */\n code?: string\n /** Present on 429 responses. */\n rate_limit?: boolean\n /** Seconds until the rate limit resets, on 429 responses. */\n timeleft?: number\n [key: string]: unknown\n}\n\nexport class DontCodeError extends Error {\n /** HTTP status code of the failing response. */\n readonly status: number\n /** Stable machine code, when present (e.g. `EmailNotVerified`). */\n readonly code?: string\n /** The raw parsed response body. */\n readonly body: DontCodeErrorBody\n\n constructor(status: number, body: DontCodeErrorBody) {\n const message =\n typeof body?.error === 'string' && body.error.length > 0\n ? body.error\n : `DontCode request failed with status ${status}`\n super(message)\n this.name = 'DontCodeError'\n this.status = status\n this.code = typeof body?.code === 'string' ? body.code : undefined\n this.body = body ?? {}\n }\n\n /** True when the request was rejected by the per-key rate limiter. */\n get rateLimited(): boolean {\n return this.status === 429\n }\n}\n\n/** Cross-bundle-safe check; works even if two copies of the SDK are loaded. */\nexport function isDontCodeError(err: unknown): err is DontCodeError {\n if (err instanceof DontCodeError) return true\n return (\n typeof err === 'object' &&\n err !== null &&\n (err as { name?: unknown }).name === 'DontCodeError'\n )\n}\n","import { isDontCodeError } from './errors'\nimport type { AuthApi } from './auth'\nimport type { CurrentUser } from './types'\n\n/**\n * Session helpers, framework-agnostic by design. They never touch a request or\n * response object; they take a token (or a cookie header) and return plain\n * data, so they slot into any framework's guard (Next middleware, SvelteKit\n * hooks, Express, a worker) without an adapter.\n *\n * The point of the module: an auth guard must not make a network round-trip on\n * every navigation, or a slow gateway stalls the page and a swallowed timeout\n * reads as \"signed out\". So there are two modes:\n *\n * - optimistic — decode the token locally (no signature check, no network)\n * and trust its claims for routing. Instant. Used for the common gate.\n * - verified — call the gateway's `me` once, cache the result for a short\n * TTL, and hard-timeout the request. Used for sensitive actions.\n *\n * The trade-offs (no signature verification, revocation lag) are documented on\n * `getSession` and in the public BYOC docs; read them before choosing a mode.\n */\n\n/** A JWT payload decoded WITHOUT verifying its signature. Trust accordingly. */\nexport interface DecodedSession {\n /** Subject — the user id. */\n sub: string\n email?: string\n role?: string\n claims?: Record<string, unknown>\n /** Expiry, seconds since the epoch (standard JWT `exp`). */\n exp?: number\n /** Issued-at, seconds since the epoch (standard JWT `iat`). */\n iat?: number\n [key: string]: unknown\n}\n\nexport type SessionStatus =\n /** A usable session: a present, unexpired token (verified or optimistic). */\n | 'active'\n /** The token is present but past its `exp`. */\n | 'expired'\n /** No token, or an unparseable one. */\n | 'anonymous'\n /** Verified mode could not reach the gateway (timeout/network/5xx). The\n * optimistically-decoded `user` is still returned so the caller can choose\n * to fail open during an outage instead of logging everyone out. */\n | 'unavailable'\n\nexport interface SessionResult {\n status: SessionStatus\n /** The signed-in user, or `null` when anonymous/expired. In `verified` mode\n * this came from the gateway; in `optimistic` (or `unavailable`) it was\n * decoded from the token's own claims. */\n user: CurrentUser | null\n /** True only when `user` was confirmed by a gateway `me` call this request\n * (or from a fresh cache entry of one). False for optimistic decodes. */\n verified: boolean\n /** The token's `exp` (seconds since epoch), when present. */\n expiresAt?: number\n}\n\nexport interface GetSessionInput {\n accessToken: string\n /** `optimistic` (default): decode locally, zero network. `verified`: confirm\n * against the gateway with caching + a hard timeout. */\n mode?: 'optimistic' | 'verified'\n}\n\n/** A place to cache verified sessions. Swap the default in-memory store for a\n * shared one (Redis, KV) when running multiple instances. */\nexport interface SessionCache {\n get(token: string): SessionResult | undefined\n set(token: string, value: SessionResult, ttlMs: number): void\n delete?(token: string): void\n}\n\nexport interface SessionOptions {\n /** Cache for verified sessions. Defaults to a per-process `InMemorySessionCache`. */\n cache?: SessionCache\n /** How long a verified session stays cached. Default 60_000 (60s). Keep it\n * short: a cached session can outlive a server-side revocation by up to\n * this long. */\n ttlMs?: number\n /** Timeout for the `me` call made by `verified` mode (ms). Default 5_000. */\n verifyTimeoutMs?: number\n}\n\nconst DEFAULT_TTL_MS = 60_000\nconst DEFAULT_VERIFY_TIMEOUT_MS = 5_000\n\n/** Default cache: a `Map` with per-entry TTL. Lives for the life of the process\n * (and is shared across requests on a reused serverless instance). */\nexport class InMemorySessionCache implements SessionCache {\n private readonly store = new Map<string, { value: SessionResult; expiresAtMs: number }>()\n\n get(token: string): SessionResult | undefined {\n const hit = this.store.get(token)\n if (!hit) return undefined\n if (Date.now() >= hit.expiresAtMs) {\n this.store.delete(token)\n return undefined\n }\n return hit.value\n }\n\n set(token: string, value: SessionResult, ttlMs: number): void {\n this.store.set(token, { value, expiresAtMs: Date.now() + ttlMs })\n }\n\n delete(token: string): void {\n this.store.delete(token)\n }\n}\n\nfunction base64UrlDecode(segment: string): string {\n const base64 = segment.replace(/-/g, '+').replace(/_/g, '/')\n const padded = base64.length % 4 === 0 ? base64 : base64 + '='.repeat(4 - (base64.length % 4))\n if (typeof atob === 'function') {\n const binary = atob(padded)\n const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0))\n return new TextDecoder().decode(bytes)\n }\n // Node without a global atob (older runtimes); Buffer is always present there.\n return Buffer.from(padded, 'base64').toString('utf8')\n}\n\n/**\n * Decode a JWT access token's payload WITHOUT verifying its signature, or\n * return `null` if it is not a parseable JWT. This is deliberately cheap and\n * offline; it is not proof the token is genuine. DontCode tokens are signed\n * with a secret the gateway never shares, so the only authority on a token is\n * the gateway's `me` endpoint — use `getSession({ mode: 'verified' })` when you\n * need that authority.\n */\nexport function decodeAccessToken(token: string): DecodedSession | null {\n if (!token || typeof token !== 'string') return null\n const parts = token.split('.')\n if (parts.length < 2) return null\n try {\n const payload = JSON.parse(base64UrlDecode(parts[1])) as Record<string, unknown>\n if (!payload || typeof payload.sub !== 'string') return null\n return payload as DecodedSession\n } catch {\n return null\n }\n}\n\n/** True when a token (or already-decoded payload) is past its `exp`. A token\n * with no `exp` is treated as not expired (the caller cannot prove otherwise\n * offline). `skewSeconds` widens the window to absorb clock drift. */\nexport function isSessionExpired(\n input: string | DecodedSession | null,\n opts: { skewSeconds?: number } = {}\n): boolean {\n const decoded = typeof input === 'string' ? decodeAccessToken(input) : input\n if (!decoded || typeof decoded.exp !== 'number') return false\n const nowSeconds = Date.now() / 1000\n return nowSeconds >= decoded.exp - (opts.skewSeconds ?? 0)\n}\n\nfunction userFromClaims(decoded: DecodedSession): CurrentUser {\n return {\n id: decoded.sub,\n email: typeof decoded.email === 'string' ? decoded.email : '',\n role: decoded.role,\n claims: decoded.claims,\n }\n}\n\n/**\n * Resolves access tokens into sessions for `AuthApi`. Holds the verified-session\n * cache and timeout policy. Not exported directly; reach it via\n * `client.auth.getSession` / `client.auth.sessionFromCookies`.\n */\nexport class SessionVerifier {\n private readonly cache: SessionCache\n private readonly ttlMs: number\n private readonly verifyTimeoutMs: number\n\n constructor(\n private readonly auth: AuthApi,\n options: SessionOptions = {}\n ) {\n this.cache = options.cache ?? new InMemorySessionCache()\n this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS\n this.verifyTimeoutMs = options.verifyTimeoutMs ?? DEFAULT_VERIFY_TIMEOUT_MS\n }\n\n async getSession({ accessToken, mode = 'optimistic' }: GetSessionInput): Promise<SessionResult> {\n const decoded = decodeAccessToken(accessToken)\n if (!decoded) return { status: 'anonymous', user: null, verified: false }\n if (isSessionExpired(decoded)) {\n return { status: 'expired', user: null, verified: false, expiresAt: decoded.exp }\n }\n\n const optimistic: SessionResult = {\n status: 'active',\n user: userFromClaims(decoded),\n verified: false,\n expiresAt: decoded.exp,\n }\n if (mode === 'optimistic') return optimistic\n\n const cached = this.cache.get(accessToken)\n if (cached) return cached\n\n try {\n const { user } = await this.auth.me({\n accessToken,\n timeoutMs: this.verifyTimeoutMs,\n })\n const result: SessionResult = user\n ? { status: 'active', user, verified: true, expiresAt: decoded.exp }\n : { status: 'anonymous', user: null, verified: true }\n this.cache.set(accessToken, result, this.ttlMs)\n return result\n } catch (err) {\n // A real 401 means the gateway rejected the token: the user is out.\n if (isDontCodeError(err) && err.status === 401) {\n return { status: 'anonymous', user: null, verified: true }\n }\n // Timeout / network / 5xx: the backend is unreachable, not a verdict\n // on the user. Hand back the optimistic session marked unavailable so\n // the caller decides whether to fail open. Not cached.\n return { ...optimistic, status: 'unavailable' }\n }\n }\n}\n","import { Transport } from './http'\nimport { readSessionToken } from './cookies'\nimport {\n SessionVerifier,\n decodeAccessToken,\n type DecodedSession,\n type GetSessionInput,\n type SessionOptions,\n type SessionResult,\n} from './session'\nimport type {\n ForgotPasswordInput,\n LoginInput,\n LoginResult,\n MeResult,\n MfaChallengeInput,\n MfaDisableInput,\n MfaEnrollConfirmInput,\n MfaEnrollResult,\n ResetPasswordInput,\n SignupInput,\n SignupResult,\n SimpleResult,\n VerifyEmailInput,\n} from './types'\n\nconst AUTH_BASE = '/api/v1/auth'\n\n/**\n * MFA is per-user and opt-in. `enroll`/`enrollConfirm`/`disable` act as the\n * signed-in user, so they need the end-user access token. `challenge` does\n * not; it completes a login that returned `mfa_required`, exchanging the\n * short-lived challenge token for real session tokens.\n */\nexport class MfaApi {\n constructor(private readonly transport: Transport) {}\n\n /** Complete an MFA login. Pass the `challenge_token` from `login`, plus\n * either the authenticator `code` or a `recoveryCode`. */\n challenge(input: MfaChallengeInput): Promise<LoginResult> {\n return this.transport.json<LoginResult>(`${AUTH_BASE}/mfa/challenge`, {\n challenge_token: input.challengeToken,\n code: input.code,\n recovery_code: input.recoveryCode,\n })\n }\n\n /** Begin enrollment. Render the returned `otpauth_url` as a QR code.\n * Enrollment stays pending until `enrollConfirm`. */\n enroll(input: { accessToken: string }): Promise<MfaEnrollResult> {\n return this.transport.json<MfaEnrollResult>(\n `${AUTH_BASE}/mfa/enroll`,\n {},\n { accessToken: input.accessToken }\n )\n }\n\n /** Confirm enrollment with the first authenticator code. The returned\n * `recovery_codes` are shown once and never again. */\n enrollConfirm(input: MfaEnrollConfirmInput): Promise<SimpleResult> {\n return this.transport.json<SimpleResult>(\n `${AUTH_BASE}/mfa/enroll/confirm`,\n { code: input.code },\n { accessToken: input.accessToken }\n )\n }\n\n /** Turn MFA off. Proves possession of the second factor via `code` or\n * `recoveryCode`. */\n disable(input: MfaDisableInput): Promise<SimpleResult> {\n return this.transport.json<SimpleResult>(\n `${AUTH_BASE}/mfa/disable`,\n { code: input.code, recovery_code: input.recoveryCode },\n { accessToken: input.accessToken }\n )\n }\n}\n\n/**\n * Fronts DontCode Auth with the same shapes as the gateway. Two behaviours are\n * project settings (not API flags) and your code must handle both states:\n * email verification (signup may not return tokens) and MFA (login may be two\n * steps). Branch on the resolved value; never assume one round-trip.\n */\nexport class AuthApi {\n readonly mfa: MfaApi\n private readonly sessions: SessionVerifier\n\n constructor(\n private readonly transport: Transport,\n sessionOptions?: SessionOptions\n ) {\n this.mfa = new MfaApi(transport)\n this.sessions = new SessionVerifier(this, sessionOptions)\n }\n\n /** Create an account. If the project requires email verification the\n * response has `verification_required: true` and NO tokens; collect a\n * code and call `verifyEmail`, then `login`. */\n signup(input: SignupInput): Promise<SignupResult> {\n return this.transport.json<SignupResult>(`${AUTH_BASE}/signup`, {\n email: input.email,\n password: input.password,\n name: input.name,\n role: input.role,\n })\n }\n\n /** Authenticate. Branch on `mfa_required`: when true you hold only a\n * challenge (finish via `mfa.challenge`); otherwise `tokens` is your\n * session. A 403 `EmailNotVerified` means the email step isn't done. */\n login(input: LoginInput): Promise<LoginResult> {\n return this.transport.json<LoginResult>(`${AUTH_BASE}/login`, {\n email: input.email,\n password: input.password,\n })\n }\n\n /** Resolve the signed-in user from their access token, or `{ user: null }`.\n * This is a network round-trip; for a per-navigation guard prefer\n * `getSession`, which can answer offline and caches verified results. */\n me(input: { accessToken: string; timeoutMs?: number }): Promise<MeResult> {\n return this.transport.json<MeResult>(\n `${AUTH_BASE}/me`,\n {},\n { accessToken: input.accessToken, timeoutMs: input.timeoutMs }\n )\n }\n\n /**\n * Resolve an access token into a session for a route guard, the one call\n * that replaces \"hit `me` on every navigation\". Two modes:\n *\n * - `'optimistic'` (default): decode the token locally and trust its\n * claims. Zero network, zero stall. The right default for gating page\n * loads. It does NOT verify the signature and will not notice a\n * server-side revocation until the token's own `exp`.\n * - `'verified'`: confirm against the gateway's `me`, cached for a short\n * TTL with a hard timeout. Use it before sensitive actions. On a\n * timeout/outage it returns `status: 'unavailable'` with the optimistic\n * user, so you choose whether to fail open rather than the SDK guessing.\n *\n * See the BYOC docs (\"Sessions\") for the full reasoning and best practices.\n */\n getSession(input: GetSessionInput): Promise<SessionResult> {\n return this.sessions.getSession(input)\n }\n\n /** Read the access token from a `Cookie` request header and resolve it, in\n * one call. `name` defaults to `dc_access_token`. Returns the anonymous\n * session when no cookie is present. */\n sessionFromCookies(\n cookieHeader: string | null | undefined,\n options: { mode?: GetSessionInput['mode']; cookieName?: string } = {}\n ): Promise<SessionResult> {\n const token = readSessionToken(cookieHeader, options.cookieName)\n if (!token) return Promise.resolve({ status: 'anonymous', user: null, verified: false })\n return this.sessions.getSession({ accessToken: token, mode: options.mode })\n }\n\n /** Decode an access token's claims locally without a network call or any\n * signature check. Convenience re-export of `decodeAccessToken`. */\n decodeToken(token: string): DecodedSession | null {\n return decodeAccessToken(token)\n }\n\n /** Confirm the 6-digit code emailed at signup. */\n verifyEmail(input: VerifyEmailInput): Promise<SimpleResult> {\n return this.transport.json<SimpleResult>(`${AUTH_BASE}/verify-email`, {\n code: input.code,\n email: input.email,\n })\n }\n\n forgotPassword(input: ForgotPasswordInput): Promise<SimpleResult> {\n return this.transport.json<SimpleResult>(`${AUTH_BASE}/forgot-password`, {\n email: input.email,\n })\n }\n\n resetPassword(input: ResetPasswordInput): Promise<SimpleResult> {\n return this.transport.json<SimpleResult>(`${AUTH_BASE}/reset-password`, {\n code: input.code,\n password: input.password,\n email: input.email,\n })\n }\n}\n","import { Transport } from './http'\nimport type {\n DeleteInput,\n MigrateInput,\n MigrateResult,\n QueryOptions,\n UpdateInput,\n} from './types'\n\nconst DB_PATH = '/api/v1/db'\nconst MIGRATE_PATH = '/api/v1/db/migrate'\n\n/** The gateway wraps every DB result in `{ data }`; we unwrap it. */\ninterface DbEnvelope<T> {\n data: T\n}\n\n/**\n * A handle to one table. Structured queries only; there is no raw-SQL escape\n * hatch (schema changes go through `db.migrate`). `update` and `delete`\n * require a `where` clause server-side, so the types make it mandatory.\n */\nexport class TableQuery {\n constructor(\n private readonly transport: Transport,\n private readonly tableName: string\n ) {}\n\n private async run<T>(operation: string, options: unknown): Promise<T> {\n const res = await this.transport.json<DbEnvelope<T>>(DB_PATH, {\n operation,\n tableName: this.tableName,\n options,\n })\n return res.data\n }\n\n /** Rows matching the query (max 1000 per call). */\n find<T = Record<string, unknown>>(options: QueryOptions = {}): Promise<T[]> {\n return this.run<T[]>('find', options)\n }\n\n /** Alias of `find`. */\n findMany<T = Record<string, unknown>>(options: QueryOptions = {}): Promise<T[]> {\n return this.run<T[]>('findMany', options)\n }\n\n /** The first matching row, or `null`. */\n findFirst<T = Record<string, unknown>>(options: QueryOptions = {}): Promise<T | null> {\n return this.run<T | null>('findFirst', options)\n }\n\n /** Alias of `findFirst`. */\n findOne<T = Record<string, unknown>>(options: QueryOptions = {}): Promise<T | null> {\n return this.run<T | null>('findOne', options)\n }\n\n /** Insert one row. Returns `{ id }`. Unique/FK conflicts throw a 409\n * DontCodeError, the supported idempotency signal. */\n insert(data: Record<string, unknown>): Promise<{ id: unknown }> {\n return this.run<{ id: unknown }>('insert', { data })\n }\n\n /** Update rows matching `where`. Returns `{ count }`. */\n update(input: UpdateInput): Promise<{ count: number }> {\n return this.run<{ count: number }>('update', { where: input.where, data: input.data })\n }\n\n /** Delete rows matching `where`. Returns `{ count }`. */\n delete(input: DeleteInput): Promise<{ count: number }> {\n return this.run<{ count: number }>('delete', { where: input.where })\n }\n\n /** Count matching rows. */\n count(options: Pick<QueryOptions, 'where'> = {}): Promise<number> {\n return this.run<number>('count', options)\n }\n}\n\n/**\n * `db.users.find()` and `db('users').find()` both work; the bracket/callable\n * form is there for table names that aren't valid identifiers. `db.migrate()`\n * applies schema DDL (the one place migrations enter from outside).\n */\nexport type DbClient = {\n readonly [tableName: string]: TableQuery\n} & {\n (tableName: string): TableQuery\n migrate(input: MigrateInput): Promise<MigrateResult>\n}\n\nexport function createDb(transport: Transport): DbClient {\n const table = (tableName: string): TableQuery => new TableQuery(transport, tableName)\n const migrate = (input: MigrateInput): Promise<MigrateResult> =>\n transport.json<MigrateResult>(MIGRATE_PATH, { sql: input.sql })\n\n return new Proxy(table, {\n get(target, prop, receiver) {\n if (prop === 'migrate') return migrate\n // Don't manufacture a TableQuery for symbols or promise-unwrapping\n // probes (`then`) or the function's own members.\n if (typeof prop !== 'string' || prop === 'then' || prop in target) {\n return Reflect.get(target, prop, receiver)\n }\n return new TableQuery(transport, prop)\n },\n apply(_target, _thisArg, args: [string]) {\n return table(args[0])\n },\n }) as unknown as DbClient\n}\n","import { DontCodeError, type DontCodeErrorBody } from './errors'\n\n/** Default per-request timeout. Without one, a slow or unreachable gateway can\n * hang a request for the platform's full socket timeout (tens of seconds),\n * which is the single worst failure mode for an auth guard on the hot path. */\nexport const DEFAULT_TIMEOUT_MS = 10_000\n\nexport interface TransportConfig {\n /** Project API key. When absent, no Authorization header is sent and the\n * gateway responds with its own \"Missing API key\" 401. */\n apiKey?: string\n /** Gateway origin, already normalized (no trailing slash). */\n baseUrl: string\n /** Per-request timeout in ms. Defaults to `DEFAULT_TIMEOUT_MS`; `0` (or any\n * non-positive value) disables it. */\n timeoutMs?: number\n}\n\nexport interface RequestOptions {\n /** End-user access token, sent as `X-Access-Token` (separate from the\n * project API key). Required by signed-in auth calls. */\n accessToken?: string\n /** Override the client's timeout for this one call (ms). `0` disables it. */\n timeoutMs?: number\n}\n\n/**\n * The single place network requests are made. Everything else in the SDK is a\n * typed shape around `json()` / `multipart()`. No retries, no caching, just a\n * faithful proxy of the v1 gateway.\n */\nexport class Transport {\n constructor(private readonly config: TransportConfig) {}\n\n private headers(opts?: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = {}\n if (this.config.apiKey) headers['Authorization'] = `Bearer ${this.config.apiKey}`\n if (opts?.accessToken) headers['X-Access-Token'] = opts.accessToken\n return headers\n }\n\n private url(path: string): string {\n return `${this.config.baseUrl}${path}`\n }\n\n private timeout(opts?: RequestOptions): number {\n const value = opts?.timeoutMs ?? this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS\n return value > 0 ? value : 0\n }\n\n /**\n * One fetch, with a timeout that turns \"hung socket\" into a fast, typed\n * failure. A timeout surfaces as `DontCodeError` with status 408 / code\n * `Timeout`; any other transport failure (DNS, refused, offline) as status\n * 0 / code `NetworkError`. Both are distinct from a real `401`, so an auth\n * guard can tell \"backend is down\" apart from \"user is signed out\".\n */\n private async send(path: string, init: RequestInit, opts?: RequestOptions): Promise<Response> {\n const timeoutMs = this.timeout(opts)\n const controller = timeoutMs > 0 ? new AbortController() : undefined\n const timer = controller ? setTimeout(() => controller.abort(), timeoutMs) : undefined\n try {\n return await fetch(this.url(path), { ...init, signal: controller?.signal })\n } catch (err) {\n if (controller?.signal.aborted) {\n throw new DontCodeError(408, {\n error: `Request to ${path} timed out after ${timeoutMs}ms`,\n code: 'Timeout',\n })\n }\n throw new DontCodeError(0, {\n error: err instanceof Error ? err.message : 'Network request failed',\n code: 'NetworkError',\n })\n } finally {\n if (timer) clearTimeout(timer)\n }\n }\n\n /** POST a JSON body and parse the JSON response. */\n async json<T>(path: string, body?: unknown, opts?: RequestOptions): Promise<T> {\n const res = await this.send(\n path,\n {\n method: 'POST',\n headers: { ...this.headers(opts), 'Content-Type': 'application/json' },\n body: JSON.stringify(body ?? {}),\n },\n opts\n )\n return this.parse<T>(res)\n }\n\n /** PUT a multipart form (file uploads). The runtime sets the boundary. */\n async multipart<T>(path: string, form: FormData, opts?: RequestOptions): Promise<T> {\n const res = await this.send(path, { method: 'PUT', headers: this.headers(opts), body: form }, opts)\n return this.parse<T>(res)\n }\n\n private async parse<T>(res: Response): Promise<T> {\n const raw = await res.text()\n let data: unknown = null\n if (raw) {\n try {\n data = JSON.parse(raw)\n } catch {\n data = { error: raw }\n }\n }\n if (!res.ok) {\n const body: DontCodeErrorBody =\n data && typeof data === 'object'\n ? (data as DontCodeErrorBody)\n : { error: res.statusText || 'Request failed' }\n throw new DontCodeError(res.status, body)\n }\n return data as T\n }\n}\n","import { Transport } from './http'\nimport type {\n DownloadResult,\n ListResult,\n PresignResult,\n StorageBucket,\n StorageObject,\n TemporaryUrlResult,\n UploadBody,\n} from './types'\n\nconst STORAGE_PATH = '/api/v1/storage'\n\nconst DEFAULT_CONTENT_TYPE = 'application/octet-stream'\n\n/** Normalize whatever the caller hands us into a Blob for multipart upload. */\nfunction toBlob(body: UploadBody, contentType: string): Blob {\n if (body instanceof Blob) return body\n if (typeof body === 'string') return new Blob([body], { type: contentType })\n if (body instanceof ArrayBuffer) return new Blob([body], { type: contentType })\n if (ArrayBuffer.isView(body)) {\n return new Blob([body as unknown as BlobPart], { type: contentType })\n }\n throw new TypeError('upload expects a Blob, ArrayBuffer, typed array, or string')\n}\n\nfunction fileName(path: string): string {\n return path.split('/').filter(Boolean).pop() ?? path\n}\n\n/** Operations available on both buckets. */\nexport class BucketClient {\n constructor(\n protected readonly transport: Transport,\n protected readonly bucket: StorageBucket\n ) {}\n\n protected op<T>(operation: string, params: Record<string, unknown> = {}): Promise<T> {\n return this.transport.json<T>(STORAGE_PATH, { operation, bucket: this.bucket, ...params })\n }\n\n /** List objects under `prefix`. */\n list(prefix?: string): Promise<ListResult> {\n return this.op<ListResult>('list', { prefix })\n }\n\n /** Delete one or more objects. Returns `{ deleted }`. */\n remove(paths: string[]): Promise<{ deleted: number }> {\n return this.op<{ deleted: number }>('remove', { paths })\n }\n\n /** Move/rename an object within the bucket. */\n move(from: string, to: string): Promise<{ object: StorageObject }> {\n return this.op<{ object: StorageObject }>('move', { from, to })\n }\n\n createFolder(path: string): Promise<{ created: string }> {\n return this.op<{ created: string }>('createFolder', { path })\n }\n\n /** Download an object inline (≤ 8 MB). Use `getTemporaryUrl` for larger files. */\n download(path: string): Promise<DownloadResult> {\n return this.op<DownloadResult>('download', { path })\n }\n\n /** A short-lived signed URL (default 300s, max 7 days). */\n getTemporaryUrl(path: string, expiresIn?: number): Promise<TemporaryUrlResult> {\n return this.op<TemporaryUrlResult>('getTemporaryUrl', { path, expiresIn })\n }\n\n /** A presigned PUT URL for direct, large uploads (≤ no inline limit). */\n presignUpload(path: string, contentType?: string): Promise<PresignResult> {\n return this.op<PresignResult>('presignUpload', { path, contentType })\n }\n\n /** Upload bytes directly (≤ 100 MB). For larger files, `presignUpload`\n * then PUT to the returned URL yourself. */\n upload(\n path: string,\n body: UploadBody,\n contentType: string = DEFAULT_CONTENT_TYPE\n ): Promise<{ object: StorageObject }> {\n const form = new FormData()\n form.append('file', toBlob(body, contentType), fileName(path))\n form.append('bucket', this.bucket)\n form.append('path', path)\n form.append('contentType', contentType)\n return this.transport.multipart<{ object: StorageObject }>(STORAGE_PATH, form)\n }\n}\n\n/** The public bucket additionally exposes stable public URLs. */\nexport class PublicBucketClient extends BucketClient {\n constructor(transport: Transport) {\n super(transport, 'public')\n }\n\n /** The permanent public URL for an object. */\n getUrl(path: string): Promise<{ url: string }> {\n return this.op<{ url: string }>('getUrl', { path })\n }\n}\n\nexport interface StorageClient {\n public: PublicBucketClient\n private: BucketClient\n}\n\nexport function createStorage(transport: Transport): StorageClient {\n return {\n public: new PublicBucketClient(transport),\n private: new BucketClient(transport, 'private'),\n }\n}\n","import { AuthApi } from './auth'\nimport { createDb, type DbClient } from './db'\nimport { Transport } from './http'\nimport type { SessionOptions } from './session'\nimport { createStorage, type StorageClient } from './storage'\n\nconst DEFAULT_BASE_URL = 'https://backend.dontcode.co'\n\nexport interface DontCodeClientOptions {\n /** Project API key (`dc_…`). Defaults to `process.env.DONTCODE_API_KEY`.\n * If neither is set, requests fail naturally with the gateway's\n * \"Missing API key\" 401. */\n apiKey?: string\n /** Gateway origin. Defaults to `process.env.DONTCODE_API_URL`, then to\n * `https://backend.dontcode.co`. */\n baseUrl?: string\n /** Per-request network timeout in ms. Defaults to 10_000; `0` disables it.\n * Without one, a slow gateway can hang a request for the full socket\n * timeout, the worst case for an auth guard. */\n timeoutMs?: number\n /** Caching + timeout policy for `auth.getSession` / `auth.sessionFromCookies`. */\n session?: SessionOptions\n}\n\nexport interface DontCodeClient {\n auth: AuthApi\n db: DbClient\n storage: StorageClient\n}\n\n/** Read an env var without assuming `process` exists (e.g. in the browser). */\nfunction fromEnv(name: string): string | undefined {\n if (typeof process === 'undefined' || !process.env) return undefined\n return process.env[name]\n}\n\n/**\n * Create a DontCode backend client. A thin, typed proxy over the v1 HTTP\n * gateway: auth, database, and storage. The API key scopes every request to\n * a single project; there is nothing else to configure.\n *\n * ```ts\n * import { dontcode } from '@dontcode2/backend'\n * const client = dontcode() // reads DONTCODE_API_KEY\n * await client.auth.signup({ email, password, role: 'editor' })\n * ```\n */\nexport function dontcode(options: DontCodeClientOptions = {}): DontCodeClient {\n const apiKey = options.apiKey ?? fromEnv('DONTCODE_API_KEY')\n const baseUrl = (options.baseUrl ?? fromEnv('DONTCODE_API_URL') ?? DEFAULT_BASE_URL).replace(\n /\\/+$/,\n ''\n )\n\n const transport = new Transport({ apiKey, baseUrl, timeoutMs: options.timeoutMs })\n\n return {\n auth: new AuthApi(transport, options.session),\n db: createDb(transport),\n storage: createStorage(transport),\n }\n}\n"],"mappings":"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","names":[]}