@dontcode2/backend 0.2.0 → 0.2.2
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 +99 -2
- package/dist/auth-device.d.ts +43 -0
- package/dist/auth.d.ts +102 -0
- package/dist/cache.d.ts +36 -0
- package/dist/chunk-6DFTM26Y.js +448 -0
- package/dist/chunk-6DFTM26Y.js.map +1 -0
- package/dist/chunk-LBN4R3GH.js +716 -0
- package/dist/chunk-LBN4R3GH.js.map +1 -0
- package/dist/cli.cjs +1206 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/{mock/cli.d.cts → cli.d.ts} +1 -0
- package/dist/cli.js +95 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +40 -0
- package/dist/cookies.d.ts +36 -0
- package/dist/credentials.d.ts +36 -0
- package/dist/db.d.ts +48 -0
- package/dist/errors.d.ts +38 -0
- package/dist/http.d.ts +52 -0
- package/dist/index.cjs +164 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +15 -588
- package/dist/index.js +26 -536
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.cjs +1116 -0
- package/dist/mcp/index.cjs.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +10 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +6 -0
- package/dist/mock/cli.d.ts +1 -0
- package/dist/mock/db-query.d.ts +67 -0
- package/dist/mock/index.d.ts +17 -36
- package/dist/mock/{index.d.cts → server.d.ts} +3 -5
- package/dist/node.cjs +1160 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.ts +8 -0
- package/dist/node.js +28 -0
- package/dist/node.js.map +1 -0
- package/dist/realtime.d.ts +29 -0
- package/dist/session.d.ts +115 -0
- package/dist/storage.d.ts +46 -0
- package/dist/types.d.ts +184 -0
- package/package.json +19 -2
- package/dist/index.d.cts +0 -588
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# @dontcode2/backend
|
|
2
2
|
|
|
3
3
|
The public SDK for the [DontCode](https://www.dontcode.co) backend: a thin, typed
|
|
4
|
-
proxy over the v1 HTTP gateway. Auth, database,
|
|
5
|
-
yourself ("bring your own code"), behind a single
|
|
4
|
+
proxy over the v1 HTTP gateway. Auth, database, file storage, a key-value cache, and
|
|
5
|
+
realtime pub/sub for an app you host yourself ("bring your own code"), behind a single
|
|
6
|
+
project API key.
|
|
6
7
|
|
|
7
8
|
It speaks the exact wire protocol of the gateway, so you can move between the SDK and
|
|
8
9
|
raw HTTP at any time without platform-side changes.
|
|
@@ -98,6 +99,47 @@ const client = dontcode({ baseUrl: mock.url, apiKey: 'dc_test' })
|
|
|
98
99
|
await mock.close()
|
|
99
100
|
```
|
|
100
101
|
|
|
102
|
+
## MCP server (Claude Code and other AI tools)
|
|
103
|
+
|
|
104
|
+
This package ships an MCP server so an AI agent can work on a DontCode project
|
|
105
|
+
directly: sign in from the terminal, query and update the database, run
|
|
106
|
+
migrations, and manage files. It runs over stdio, so the tool launches it for
|
|
107
|
+
you.
|
|
108
|
+
|
|
109
|
+
```jsonc
|
|
110
|
+
// .mcp.json (Claude Code)
|
|
111
|
+
{
|
|
112
|
+
"mcpServers": {
|
|
113
|
+
"dontcode": {
|
|
114
|
+
"command": "npx",
|
|
115
|
+
"args": ["-y", "-p", "@dontcode2/backend", "dontcode", "mcp"]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
On first use the agent calls the `auth_login` tool, which opens your browser.
|
|
122
|
+
You confirm a short code, pick a project, and approve; the browser hands a
|
|
123
|
+
short-lived, project-scoped token back to the terminal (cached under
|
|
124
|
+
`~/.dontcode`). The token is bound to your user, and the gateway enforces your
|
|
125
|
+
project role on every request, so the agent can never do more than you can.
|
|
126
|
+
|
|
127
|
+
CLI equivalents:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx -p @dontcode2/backend dontcode login # browser sign-in
|
|
131
|
+
npx -p @dontcode2/backend dontcode status # project + role + capabilities
|
|
132
|
+
npx -p @dontcode2/backend dontcode logout # forget the cached token
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
For non-interactive use (CI), set `DONTCODE_API_KEY` to a `dc_` project key and
|
|
136
|
+
skip `login`. Set `DONTCODE_API_URL` to target a non-default gateway.
|
|
137
|
+
|
|
138
|
+
Tools: `auth_login`, `auth_wait`, `auth_status`, `auth_logout`, `db_query`,
|
|
139
|
+
`db_insert`, `db_update`, `db_delete`, `db_migrate`, `storage_list`,
|
|
140
|
+
`storage_get_url`, `storage_temporary_url`, `storage_upload`, `storage_remove`,
|
|
141
|
+
`storage_move`. Writes and deletes are annotated so the agent confirms first.
|
|
142
|
+
|
|
101
143
|
## Auth
|
|
102
144
|
|
|
103
145
|
The API key (`Authorization: Bearer dc_…`) identifies your **project** and is sent on
|
|
@@ -187,6 +229,61 @@ await priv.move('a.pdf', 'archive/a.pdf')
|
|
|
187
229
|
const { url: putUrl } = await priv.presignUpload('big.zip', 'application/zip')
|
|
188
230
|
```
|
|
189
231
|
|
|
232
|
+
## Cache
|
|
233
|
+
|
|
234
|
+
A key-value cache for ephemeral, high-churn, or session-scoped state, with optional TTL
|
|
235
|
+
expiry. Keys are scoped to your project automatically. This is a cache, not a database:
|
|
236
|
+
values may be evicted and are not durable, so keep your system of record in `db`.
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
const c = client.cache
|
|
240
|
+
|
|
241
|
+
await c.set('session:42', { step: 2 }, { ttl: 3600 }) // ttl in seconds
|
|
242
|
+
const session = await c.get<{ step: number }>('session:42') // null on miss or expiry
|
|
243
|
+
await c.set('lock:job', '1', { nx: true }) // false if it already existed
|
|
244
|
+
await c.expire('session:42', 600) // or null to clear the TTL
|
|
245
|
+
await c.del('session:42')
|
|
246
|
+
|
|
247
|
+
// hashes
|
|
248
|
+
await c.hset('profile:9', { name: 'Zed', level: 7 })
|
|
249
|
+
const profile = await c.hgetAll<{ name: string; level: number }>('profile:9') // null on miss
|
|
250
|
+
|
|
251
|
+
// sets (string members)
|
|
252
|
+
await c.sAdd('online', 'u1', 'u2')
|
|
253
|
+
const online = await c.sMembers('online') // string[] ([] on miss)
|
|
254
|
+
await c.sRem('online', 'u1')
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
A miss (or expiry) reads back as `null` for `get`/`hgetAll` and `[]` for `sMembers`,
|
|
258
|
+
not an error.
|
|
259
|
+
|
|
260
|
+
## Realtime
|
|
261
|
+
|
|
262
|
+
Realtime pub/sub over WebSockets for live features (chat, presence, live updates). The
|
|
263
|
+
SDK is the **server side**: mint a connection token for a browser, publish messages, and
|
|
264
|
+
read presence. The browser opens the socket itself with the scoped token, so it never
|
|
265
|
+
holds your API key. Delivery is fire-and-forget (no history/replay), so persist anything
|
|
266
|
+
that needs durability to `db`.
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
const rt = client.realtime
|
|
270
|
+
|
|
271
|
+
// In your token endpoint, after you've authenticated the user:
|
|
272
|
+
const conn = await rt.mintToken({ channels: [`room:${id}`], identity: userId })
|
|
273
|
+
// → send `conn` to the browser; it connects to `${conn.url}?token=${conn.token}`
|
|
274
|
+
// (use @dontcode/realtime's client `connect()` to handle the socket + reconnect)
|
|
275
|
+
|
|
276
|
+
// From anywhere on your backend:
|
|
277
|
+
const delivered = await rt.publish(`room:${id}`, { text: 'hello' })
|
|
278
|
+
const members = await rt.presence(`room:${id}`) // [{ id, identity? }]
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
A browser connection may only use the channels named when its token was minted.
|
|
282
|
+
|
|
283
|
+
> The local mock gateway (`dontcode-mock`) and the MCP server currently cover auth,
|
|
284
|
+
> database, and storage. Cache and realtime are available on the hosted gateway; point
|
|
285
|
+
> `DONTCODE_API_URL` at it to use them.
|
|
286
|
+
|
|
190
287
|
## Errors
|
|
191
288
|
|
|
192
289
|
Every non-2xx response throws a `DontCodeError`:
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type StoredCredential } from './credentials';
|
|
2
|
+
export interface DeviceStartResponse {
|
|
3
|
+
device_code: string;
|
|
4
|
+
user_code: string;
|
|
5
|
+
verification_uri: string;
|
|
6
|
+
verification_uri_complete: string;
|
|
7
|
+
interval: number;
|
|
8
|
+
expires_in: number;
|
|
9
|
+
}
|
|
10
|
+
export interface DeviceTokenResponse {
|
|
11
|
+
access_token: string;
|
|
12
|
+
token_type: string;
|
|
13
|
+
expires_in: number;
|
|
14
|
+
project_id: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function startDeviceAuth(baseUrl: string, clientName?: string): Promise<DeviceStartResponse>;
|
|
17
|
+
export interface PollOptions {
|
|
18
|
+
onPending?: () => void;
|
|
19
|
+
/** Stop polling after this many ms (throws code `WaitTimeout`), so a caller
|
|
20
|
+
* like an MCP tool can poll in bounded slices instead of blocking for the
|
|
21
|
+
* full 10-minute window. Defaults to the request's own expiry. */
|
|
22
|
+
maxWaitMs?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Poll until the request is approved (token), denied/expired (throws), or the
|
|
26
|
+
* wait budget closes (throws 408). Honors the server's interval and `slow_down`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function pollDeviceToken(baseUrl: string, start: DeviceStartResponse, opts?: PollOptions): Promise<DeviceTokenResponse>;
|
|
29
|
+
/** Best-effort: open the verification URL in the user's browser. */
|
|
30
|
+
export declare function openBrowser(url: string): Promise<void>;
|
|
31
|
+
export interface LoginOptions {
|
|
32
|
+
baseUrl: string;
|
|
33
|
+
clientName?: string;
|
|
34
|
+
/** Open the browser automatically. Default true. */
|
|
35
|
+
open?: boolean;
|
|
36
|
+
/** Where human-facing prompts go. Default: no-op. */
|
|
37
|
+
log?: (message: string) => void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Run the full device flow and cache the resulting credential. Returns the
|
|
41
|
+
* stored credential (token, project, expiry).
|
|
42
|
+
*/
|
|
43
|
+
export declare function login(options: LoginOptions): Promise<StoredCredential>;
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Transport } from './http';
|
|
2
|
+
import { type DecodedSession, type GetSessionInput, type SessionOptions, type SessionResult } from './session';
|
|
3
|
+
import type { ForgotPasswordInput, LoginInput, LoginResult, MeResult, MfaChallengeInput, MfaDisableInput, MfaEnrollConfirmInput, MfaEnrollResult, ResetPasswordInput, SignupInput, SignupResult, SimpleResult, VerifyEmailInput } from './types';
|
|
4
|
+
/** Shape of `GET /api/v1/info`: validates the credential and reports what it
|
|
5
|
+
* can do. For device tokens, capabilities follow the signed-in user's role. */
|
|
6
|
+
export interface InfoResult {
|
|
7
|
+
project: {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string | null;
|
|
10
|
+
};
|
|
11
|
+
credential: {
|
|
12
|
+
type: 'api_key' | 'device';
|
|
13
|
+
role: string | null;
|
|
14
|
+
user_id: string | null;
|
|
15
|
+
};
|
|
16
|
+
capabilities: Record<string, boolean>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* MFA is per-user and opt-in. `enroll`/`enrollConfirm`/`disable` act as the
|
|
20
|
+
* signed-in user, so they need the end-user access token. `challenge` does
|
|
21
|
+
* not; it completes a login that returned `mfa_required`, exchanging the
|
|
22
|
+
* short-lived challenge token for real session tokens.
|
|
23
|
+
*/
|
|
24
|
+
export declare class MfaApi {
|
|
25
|
+
private readonly transport;
|
|
26
|
+
constructor(transport: Transport);
|
|
27
|
+
/** Complete an MFA login. Pass the `challenge_token` from `login`, plus
|
|
28
|
+
* either the authenticator `code` or a `recoveryCode`. */
|
|
29
|
+
challenge(input: MfaChallengeInput): Promise<LoginResult>;
|
|
30
|
+
/** Begin enrollment. Render the returned `otpauth_url` as a QR code.
|
|
31
|
+
* Enrollment stays pending until `enrollConfirm`. */
|
|
32
|
+
enroll(input: {
|
|
33
|
+
accessToken: string;
|
|
34
|
+
}): Promise<MfaEnrollResult>;
|
|
35
|
+
/** Confirm enrollment with the first authenticator code. The returned
|
|
36
|
+
* `recovery_codes` are shown once and never again. */
|
|
37
|
+
enrollConfirm(input: MfaEnrollConfirmInput): Promise<SimpleResult>;
|
|
38
|
+
/** Turn MFA off. Proves possession of the second factor via `code` or
|
|
39
|
+
* `recoveryCode`. */
|
|
40
|
+
disable(input: MfaDisableInput): Promise<SimpleResult>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Fronts DontCode Auth with the same shapes as the gateway. Two behaviours are
|
|
44
|
+
* project settings (not API flags) and your code must handle both states:
|
|
45
|
+
* email verification (signup may not return tokens) and MFA (login may be two
|
|
46
|
+
* steps). Branch on the resolved value; never assume one round-trip.
|
|
47
|
+
*/
|
|
48
|
+
export declare class AuthApi {
|
|
49
|
+
private readonly transport;
|
|
50
|
+
readonly mfa: MfaApi;
|
|
51
|
+
private readonly sessions;
|
|
52
|
+
constructor(transport: Transport, sessionOptions?: SessionOptions);
|
|
53
|
+
/** Create an account. If the project requires email verification the
|
|
54
|
+
* response has `verification_required: true` and NO tokens; collect a
|
|
55
|
+
* code and call `verifyEmail`, then `login`. */
|
|
56
|
+
signup(input: SignupInput): Promise<SignupResult>;
|
|
57
|
+
/** Authenticate. Branch on `mfa_required`: when true you hold only a
|
|
58
|
+
* challenge (finish via `mfa.challenge`); otherwise `tokens` is your
|
|
59
|
+
* session. A 403 `EmailNotVerified` means the email step isn't done. */
|
|
60
|
+
login(input: LoginInput): Promise<LoginResult>;
|
|
61
|
+
/** Validate the current credential (API key or device token) and report the
|
|
62
|
+
* project, the caller's role, and which capabilities that role grants.
|
|
63
|
+
* Backs the MCP "is my session still good" check. */
|
|
64
|
+
info(): Promise<InfoResult>;
|
|
65
|
+
/** Resolve the signed-in user from their access token, or `{ user: null }`.
|
|
66
|
+
* This is a network round-trip; for a per-navigation guard prefer
|
|
67
|
+
* `getSession`, which can answer offline and caches verified results. */
|
|
68
|
+
me(input: {
|
|
69
|
+
accessToken: string;
|
|
70
|
+
timeoutMs?: number;
|
|
71
|
+
}): Promise<MeResult>;
|
|
72
|
+
/**
|
|
73
|
+
* Resolve an access token into a session for a route guard, the one call
|
|
74
|
+
* that replaces "hit `me` on every navigation". Two modes:
|
|
75
|
+
*
|
|
76
|
+
* - `'optimistic'` (default): decode the token locally and trust its
|
|
77
|
+
* claims. Zero network, zero stall. The right default for gating page
|
|
78
|
+
* loads. It does NOT verify the signature and will not notice a
|
|
79
|
+
* server-side revocation until the token's own `exp`.
|
|
80
|
+
* - `'verified'`: confirm against the gateway's `me`, cached for a short
|
|
81
|
+
* TTL with a hard timeout. Use it before sensitive actions. On a
|
|
82
|
+
* timeout/outage it returns `status: 'unavailable'` with the optimistic
|
|
83
|
+
* user, so you choose whether to fail open rather than the SDK guessing.
|
|
84
|
+
*
|
|
85
|
+
* See the BYOC docs ("Sessions") for the full reasoning and best practices.
|
|
86
|
+
*/
|
|
87
|
+
getSession(input: GetSessionInput): Promise<SessionResult>;
|
|
88
|
+
/** Read the access token from a `Cookie` request header and resolve it, in
|
|
89
|
+
* one call. `name` defaults to `dc_access_token`. Returns the anonymous
|
|
90
|
+
* session when no cookie is present. */
|
|
91
|
+
sessionFromCookies(cookieHeader: string | null | undefined, options?: {
|
|
92
|
+
mode?: GetSessionInput['mode'];
|
|
93
|
+
cookieName?: string;
|
|
94
|
+
}): Promise<SessionResult>;
|
|
95
|
+
/** Decode an access token's claims locally without a network call or any
|
|
96
|
+
* signature check. Convenience re-export of `decodeAccessToken`. */
|
|
97
|
+
decodeToken(token: string): DecodedSession | null;
|
|
98
|
+
/** Confirm the 6-digit code emailed at signup. */
|
|
99
|
+
verifyEmail(input: VerifyEmailInput): Promise<SimpleResult>;
|
|
100
|
+
forgotPassword(input: ForgotPasswordInput): Promise<SimpleResult>;
|
|
101
|
+
resetPassword(input: ResetPasswordInput): Promise<SimpleResult>;
|
|
102
|
+
}
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Transport } from './http';
|
|
2
|
+
import type { CacheSetOptions } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Key-value cache: a typed proxy over `/api/v1/cache`. Keys are namespaced to
|
|
5
|
+
* your project by the gateway; values are JSON. This is ephemeral storage —
|
|
6
|
+
* use `db` for anything that must be durable.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* await client.cache.set('session:42', { step: 2 }, { ttl: 3600 })
|
|
10
|
+
* const s = await client.cache.get<{ step: number }>('session:42') // null if expired
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare class CacheClient {
|
|
14
|
+
private readonly transport;
|
|
15
|
+
constructor(transport: Transport);
|
|
16
|
+
/** Read a value. Returns `null` on a miss or expiry. */
|
|
17
|
+
get<T = unknown>(key: string): Promise<T | null>;
|
|
18
|
+
/** Set a value, optionally with a TTL (seconds) or set-if-absent (`nx`).
|
|
19
|
+
* Returns `false` when `nx` is set and the key already existed. */
|
|
20
|
+
set(key: string, value: unknown, options?: CacheSetOptions): Promise<boolean>;
|
|
21
|
+
/** Delete a key. Returns whether it existed. */
|
|
22
|
+
del(key: string): Promise<boolean>;
|
|
23
|
+
/** Set or clear (`null`) the TTL on an existing key. Returns `false` if absent. */
|
|
24
|
+
expire(key: string, ttl: number | null): Promise<boolean>;
|
|
25
|
+
/** Set fields on a hash. Returns the number of fields written. */
|
|
26
|
+
hset(key: string, fields: Record<string, unknown>): Promise<number>;
|
|
27
|
+
/** Read a whole hash. Returns `null` on a miss. */
|
|
28
|
+
hgetAll<T = Record<string, unknown>>(key: string): Promise<T | null>;
|
|
29
|
+
/** Add members to a set. Returns the number newly added. */
|
|
30
|
+
sAdd(key: string, ...members: string[]): Promise<number>;
|
|
31
|
+
/** List set members. Returns `[]` on a miss. */
|
|
32
|
+
sMembers(key: string): Promise<string[]>;
|
|
33
|
+
/** Remove members from a set. Returns the number removed. */
|
|
34
|
+
sRem(key: string, ...members: string[]): Promise<number>;
|
|
35
|
+
}
|
|
36
|
+
export declare function createCache(transport: Transport): CacheClient;
|