@dontcode2/backend 0.1.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 +157 -0
- package/dist/index.cjs +373 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +395 -0
- package/dist/index.d.ts +395 -0
- package/dist/index.js +338 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
interface TransportConfig {
|
|
2
|
+
/** Project API key. When absent, no Authorization header is sent and the
|
|
3
|
+
* gateway responds with its own "Missing API key" 401. */
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
/** Gateway origin, already normalized (no trailing slash). */
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
}
|
|
8
|
+
interface RequestOptions {
|
|
9
|
+
/** End-user access token, sent as `X-Access-Token` (separate from the
|
|
10
|
+
* project API key). Required by signed-in auth calls. */
|
|
11
|
+
accessToken?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The single place network requests are made. Everything else in the SDK is a
|
|
15
|
+
* typed shape around `json()` / `multipart()`. No retries, no caching, just a
|
|
16
|
+
* faithful proxy of the v1 gateway.
|
|
17
|
+
*/
|
|
18
|
+
declare class Transport {
|
|
19
|
+
private readonly config;
|
|
20
|
+
constructor(config: TransportConfig);
|
|
21
|
+
private headers;
|
|
22
|
+
private url;
|
|
23
|
+
/** POST a JSON body and parse the JSON response. */
|
|
24
|
+
json<T>(path: string, body?: unknown, opts?: RequestOptions): Promise<T>;
|
|
25
|
+
/** PUT a multipart form (file uploads). The runtime sets the boundary. */
|
|
26
|
+
multipart<T>(path: string, form: FormData, opts?: RequestOptions): Promise<T>;
|
|
27
|
+
private parse;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Wire shapes for the v1 gateway. These mirror the platform contract; they are
|
|
32
|
+
* intentionally loose where the platform is (claims, tokens) and additive-only.
|
|
33
|
+
*/
|
|
34
|
+
interface WhereOperator {
|
|
35
|
+
equals?: unknown;
|
|
36
|
+
not?: unknown;
|
|
37
|
+
gt?: unknown;
|
|
38
|
+
gte?: unknown;
|
|
39
|
+
lt?: unknown;
|
|
40
|
+
lte?: unknown;
|
|
41
|
+
in?: unknown[];
|
|
42
|
+
notIn?: unknown[];
|
|
43
|
+
contains?: string;
|
|
44
|
+
startsWith?: string;
|
|
45
|
+
endsWith?: string;
|
|
46
|
+
mode?: 'default' | 'insensitive';
|
|
47
|
+
}
|
|
48
|
+
interface WhereClause {
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
AND?: WhereClause[];
|
|
51
|
+
OR?: WhereClause[];
|
|
52
|
+
NOT?: WhereClause;
|
|
53
|
+
}
|
|
54
|
+
type OrderByClause = Record<string, 'asc' | 'desc'>;
|
|
55
|
+
/** Options accepted by read operations (`find`, `findFirst`, `count`). */
|
|
56
|
+
interface QueryOptions {
|
|
57
|
+
where?: WhereClause;
|
|
58
|
+
select?: string[];
|
|
59
|
+
orderBy?: OrderByClause;
|
|
60
|
+
limit?: number;
|
|
61
|
+
offset?: number;
|
|
62
|
+
}
|
|
63
|
+
interface UpdateInput {
|
|
64
|
+
where: WhereClause;
|
|
65
|
+
data: Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
interface DeleteInput {
|
|
68
|
+
where: WhereClause;
|
|
69
|
+
}
|
|
70
|
+
interface MigrateInput {
|
|
71
|
+
sql: string;
|
|
72
|
+
}
|
|
73
|
+
interface MigrateResult {
|
|
74
|
+
success: boolean;
|
|
75
|
+
executedStatements?: number;
|
|
76
|
+
warnings?: string[];
|
|
77
|
+
error?: string;
|
|
78
|
+
}
|
|
79
|
+
interface SignupInput {
|
|
80
|
+
email: string;
|
|
81
|
+
password: string;
|
|
82
|
+
name?: string;
|
|
83
|
+
role?: string;
|
|
84
|
+
}
|
|
85
|
+
interface SignupResult {
|
|
86
|
+
success: boolean;
|
|
87
|
+
userId?: string;
|
|
88
|
+
verified?: boolean;
|
|
89
|
+
verification_required?: boolean;
|
|
90
|
+
message?: string;
|
|
91
|
+
}
|
|
92
|
+
interface LoginInput {
|
|
93
|
+
email: string;
|
|
94
|
+
password: string;
|
|
95
|
+
}
|
|
96
|
+
interface AuthTokens {
|
|
97
|
+
AccessToken: string;
|
|
98
|
+
ExpiresIn: number;
|
|
99
|
+
}
|
|
100
|
+
interface LoginResult {
|
|
101
|
+
success: boolean;
|
|
102
|
+
userId?: string;
|
|
103
|
+
mfa_offered?: boolean;
|
|
104
|
+
mfa_enabled?: boolean;
|
|
105
|
+
tokens?: AuthTokens;
|
|
106
|
+
/** When true the caller holds a challenge, NOT a session; finish via mfa.challenge. */
|
|
107
|
+
mfa_required?: boolean;
|
|
108
|
+
challenge_token?: string;
|
|
109
|
+
challenge_expires_in?: number;
|
|
110
|
+
}
|
|
111
|
+
interface VerifyEmailInput {
|
|
112
|
+
code: string;
|
|
113
|
+
/** Accepted but ignored; the code alone resolves the user. */
|
|
114
|
+
email?: string;
|
|
115
|
+
}
|
|
116
|
+
interface ForgotPasswordInput {
|
|
117
|
+
email: string;
|
|
118
|
+
}
|
|
119
|
+
interface ResetPasswordInput {
|
|
120
|
+
code: string;
|
|
121
|
+
password: string;
|
|
122
|
+
email?: string;
|
|
123
|
+
}
|
|
124
|
+
interface CurrentUser {
|
|
125
|
+
id: string;
|
|
126
|
+
email: string;
|
|
127
|
+
role?: string;
|
|
128
|
+
claims?: Record<string, unknown>;
|
|
129
|
+
}
|
|
130
|
+
interface MeResult {
|
|
131
|
+
user: CurrentUser | null;
|
|
132
|
+
}
|
|
133
|
+
interface MfaChallengeInput {
|
|
134
|
+
challengeToken: string;
|
|
135
|
+
code?: string;
|
|
136
|
+
recoveryCode?: string;
|
|
137
|
+
}
|
|
138
|
+
interface MfaEnrollResult {
|
|
139
|
+
success: boolean;
|
|
140
|
+
secret?: string;
|
|
141
|
+
otpauth_url?: string;
|
|
142
|
+
}
|
|
143
|
+
interface MfaEnrollConfirmInput {
|
|
144
|
+
accessToken: string;
|
|
145
|
+
code: string;
|
|
146
|
+
}
|
|
147
|
+
interface MfaDisableInput {
|
|
148
|
+
accessToken: string;
|
|
149
|
+
code?: string;
|
|
150
|
+
recoveryCode?: string;
|
|
151
|
+
}
|
|
152
|
+
interface SimpleResult {
|
|
153
|
+
success: boolean;
|
|
154
|
+
message?: string;
|
|
155
|
+
[key: string]: unknown;
|
|
156
|
+
}
|
|
157
|
+
type StorageBucket = 'public' | 'private';
|
|
158
|
+
interface StorageObject {
|
|
159
|
+
key: string;
|
|
160
|
+
name: string;
|
|
161
|
+
size: number;
|
|
162
|
+
contentType: string;
|
|
163
|
+
lastModified: string;
|
|
164
|
+
isFolder: boolean;
|
|
165
|
+
}
|
|
166
|
+
interface ListResult {
|
|
167
|
+
objects: StorageObject[];
|
|
168
|
+
folders: string[];
|
|
169
|
+
prefix: string;
|
|
170
|
+
truncated: boolean;
|
|
171
|
+
continuationToken: string | null;
|
|
172
|
+
}
|
|
173
|
+
interface DownloadResult {
|
|
174
|
+
/** base64-encoded file contents (inline downloads are capped at 8 MB). */
|
|
175
|
+
body: string;
|
|
176
|
+
contentType: string;
|
|
177
|
+
size: number;
|
|
178
|
+
}
|
|
179
|
+
interface PresignResult {
|
|
180
|
+
url: string;
|
|
181
|
+
key: string;
|
|
182
|
+
expiresIn: number;
|
|
183
|
+
}
|
|
184
|
+
interface TemporaryUrlResult {
|
|
185
|
+
url: string;
|
|
186
|
+
expiresIn: number;
|
|
187
|
+
}
|
|
188
|
+
/** Bytes the SDK can turn into an upload body. */
|
|
189
|
+
type UploadBody = Blob | ArrayBuffer | ArrayBufferView | string;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* MFA is per-user and opt-in. `enroll`/`enrollConfirm`/`disable` act as the
|
|
193
|
+
* signed-in user, so they need the end-user access token. `challenge` does
|
|
194
|
+
* not; it completes a login that returned `mfa_required`, exchanging the
|
|
195
|
+
* short-lived challenge token for real session tokens.
|
|
196
|
+
*/
|
|
197
|
+
declare class MfaApi {
|
|
198
|
+
private readonly transport;
|
|
199
|
+
constructor(transport: Transport);
|
|
200
|
+
/** Complete an MFA login. Pass the `challenge_token` from `login`, plus
|
|
201
|
+
* either the authenticator `code` or a `recoveryCode`. */
|
|
202
|
+
challenge(input: MfaChallengeInput): Promise<LoginResult>;
|
|
203
|
+
/** Begin enrollment. Render the returned `otpauth_url` as a QR code.
|
|
204
|
+
* Enrollment stays pending until `enrollConfirm`. */
|
|
205
|
+
enroll(input: {
|
|
206
|
+
accessToken: string;
|
|
207
|
+
}): Promise<MfaEnrollResult>;
|
|
208
|
+
/** Confirm enrollment with the first authenticator code. The returned
|
|
209
|
+
* `recovery_codes` are shown once and never again. */
|
|
210
|
+
enrollConfirm(input: MfaEnrollConfirmInput): Promise<SimpleResult>;
|
|
211
|
+
/** Turn MFA off. Proves possession of the second factor via `code` or
|
|
212
|
+
* `recoveryCode`. */
|
|
213
|
+
disable(input: MfaDisableInput): Promise<SimpleResult>;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Fronts DontCode Auth with the same shapes as the gateway. Two behaviours are
|
|
217
|
+
* project settings (not API flags) and your code must handle both states:
|
|
218
|
+
* email verification (signup may not return tokens) and MFA (login may be two
|
|
219
|
+
* steps). Branch on the resolved value; never assume one round-trip.
|
|
220
|
+
*/
|
|
221
|
+
declare class AuthApi {
|
|
222
|
+
private readonly transport;
|
|
223
|
+
readonly mfa: MfaApi;
|
|
224
|
+
constructor(transport: Transport);
|
|
225
|
+
/** Create an account. If the project requires email verification the
|
|
226
|
+
* response has `verification_required: true` and NO tokens; collect a
|
|
227
|
+
* code and call `verifyEmail`, then `login`. */
|
|
228
|
+
signup(input: SignupInput): Promise<SignupResult>;
|
|
229
|
+
/** Authenticate. Branch on `mfa_required`: when true you hold only a
|
|
230
|
+
* challenge (finish via `mfa.challenge`); otherwise `tokens` is your
|
|
231
|
+
* session. A 403 `EmailNotVerified` means the email step isn't done. */
|
|
232
|
+
login(input: LoginInput): Promise<LoginResult>;
|
|
233
|
+
/** Resolve the signed-in user from their access token, or `{ user: null }`. */
|
|
234
|
+
me(input: {
|
|
235
|
+
accessToken: string;
|
|
236
|
+
}): Promise<MeResult>;
|
|
237
|
+
/** Confirm the 6-digit code emailed at signup. */
|
|
238
|
+
verifyEmail(input: VerifyEmailInput): Promise<SimpleResult>;
|
|
239
|
+
forgotPassword(input: ForgotPasswordInput): Promise<SimpleResult>;
|
|
240
|
+
resetPassword(input: ResetPasswordInput): Promise<SimpleResult>;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* A handle to one table. Structured queries only; there is no raw-SQL escape
|
|
245
|
+
* hatch (schema changes go through `db.migrate`). `update` and `delete`
|
|
246
|
+
* require a `where` clause server-side, so the types make it mandatory.
|
|
247
|
+
*/
|
|
248
|
+
declare class TableQuery {
|
|
249
|
+
private readonly transport;
|
|
250
|
+
private readonly tableName;
|
|
251
|
+
constructor(transport: Transport, tableName: string);
|
|
252
|
+
private run;
|
|
253
|
+
/** Rows matching the query (max 1000 per call). */
|
|
254
|
+
find<T = Record<string, unknown>>(options?: QueryOptions): Promise<T[]>;
|
|
255
|
+
/** Alias of `find`. */
|
|
256
|
+
findMany<T = Record<string, unknown>>(options?: QueryOptions): Promise<T[]>;
|
|
257
|
+
/** The first matching row, or `null`. */
|
|
258
|
+
findFirst<T = Record<string, unknown>>(options?: QueryOptions): Promise<T | null>;
|
|
259
|
+
/** Alias of `findFirst`. */
|
|
260
|
+
findOne<T = Record<string, unknown>>(options?: QueryOptions): Promise<T | null>;
|
|
261
|
+
/** Insert one row. Returns `{ id }`. Unique/FK conflicts throw a 409
|
|
262
|
+
* DontCodeError, the supported idempotency signal. */
|
|
263
|
+
insert(data: Record<string, unknown>): Promise<{
|
|
264
|
+
id: unknown;
|
|
265
|
+
}>;
|
|
266
|
+
/** Update rows matching `where`. Returns `{ count }`. */
|
|
267
|
+
update(input: UpdateInput): Promise<{
|
|
268
|
+
count: number;
|
|
269
|
+
}>;
|
|
270
|
+
/** Delete rows matching `where`. Returns `{ count }`. */
|
|
271
|
+
delete(input: DeleteInput): Promise<{
|
|
272
|
+
count: number;
|
|
273
|
+
}>;
|
|
274
|
+
/** Count matching rows. */
|
|
275
|
+
count(options?: Pick<QueryOptions, 'where'>): Promise<number>;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* `db.users.find()` and `db('users').find()` both work; the bracket/callable
|
|
279
|
+
* form is there for table names that aren't valid identifiers. `db.migrate()`
|
|
280
|
+
* applies schema DDL (the one place migrations enter from outside).
|
|
281
|
+
*/
|
|
282
|
+
type DbClient = {
|
|
283
|
+
readonly [tableName: string]: TableQuery;
|
|
284
|
+
} & {
|
|
285
|
+
(tableName: string): TableQuery;
|
|
286
|
+
migrate(input: MigrateInput): Promise<MigrateResult>;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/** Operations available on both buckets. */
|
|
290
|
+
declare class BucketClient {
|
|
291
|
+
protected readonly transport: Transport;
|
|
292
|
+
protected readonly bucket: StorageBucket;
|
|
293
|
+
constructor(transport: Transport, bucket: StorageBucket);
|
|
294
|
+
protected op<T>(operation: string, params?: Record<string, unknown>): Promise<T>;
|
|
295
|
+
/** List objects under `prefix`. */
|
|
296
|
+
list(prefix?: string): Promise<ListResult>;
|
|
297
|
+
/** Delete one or more objects. Returns `{ deleted }`. */
|
|
298
|
+
remove(paths: string[]): Promise<{
|
|
299
|
+
deleted: number;
|
|
300
|
+
}>;
|
|
301
|
+
/** Move/rename an object within the bucket. */
|
|
302
|
+
move(from: string, to: string): Promise<{
|
|
303
|
+
object: StorageObject;
|
|
304
|
+
}>;
|
|
305
|
+
createFolder(path: string): Promise<{
|
|
306
|
+
created: string;
|
|
307
|
+
}>;
|
|
308
|
+
/** Download an object inline (≤ 8 MB). Use `getTemporaryUrl` for larger files. */
|
|
309
|
+
download(path: string): Promise<DownloadResult>;
|
|
310
|
+
/** A short-lived signed URL (default 300s, max 7 days). */
|
|
311
|
+
getTemporaryUrl(path: string, expiresIn?: number): Promise<TemporaryUrlResult>;
|
|
312
|
+
/** A presigned PUT URL for direct, large uploads (≤ no inline limit). */
|
|
313
|
+
presignUpload(path: string, contentType?: string): Promise<PresignResult>;
|
|
314
|
+
/** Upload bytes directly (≤ 100 MB). For larger files, `presignUpload`
|
|
315
|
+
* then PUT to the returned URL yourself. */
|
|
316
|
+
upload(path: string, body: UploadBody, contentType?: string): Promise<{
|
|
317
|
+
object: StorageObject;
|
|
318
|
+
}>;
|
|
319
|
+
}
|
|
320
|
+
/** The public bucket additionally exposes stable public URLs. */
|
|
321
|
+
declare class PublicBucketClient extends BucketClient {
|
|
322
|
+
constructor(transport: Transport);
|
|
323
|
+
/** The permanent public URL for an object. */
|
|
324
|
+
getUrl(path: string): Promise<{
|
|
325
|
+
url: string;
|
|
326
|
+
}>;
|
|
327
|
+
}
|
|
328
|
+
interface StorageClient {
|
|
329
|
+
public: PublicBucketClient;
|
|
330
|
+
private: BucketClient;
|
|
331
|
+
}
|
|
332
|
+
declare function createStorage(transport: Transport): StorageClient;
|
|
333
|
+
|
|
334
|
+
interface DontCodeClientOptions {
|
|
335
|
+
/** Project API key (`dc_…`). Defaults to `process.env.DONTCODE_API_KEY`.
|
|
336
|
+
* If neither is set, requests fail naturally with the gateway's
|
|
337
|
+
* "Missing API key" 401. */
|
|
338
|
+
apiKey?: string;
|
|
339
|
+
/** Gateway origin. Defaults to `process.env.DONTCODE_API_URL`, then to
|
|
340
|
+
* `https://backend.dontcode.co`. */
|
|
341
|
+
baseUrl?: string;
|
|
342
|
+
}
|
|
343
|
+
interface DontCodeClient {
|
|
344
|
+
auth: AuthApi;
|
|
345
|
+
db: DbClient;
|
|
346
|
+
storage: StorageClient;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Create a DontCode backend client. A thin, typed proxy over the v1 HTTP
|
|
350
|
+
* gateway: auth, database, and storage. The API key scopes every request to
|
|
351
|
+
* a single project; there is nothing else to configure.
|
|
352
|
+
*
|
|
353
|
+
* ```ts
|
|
354
|
+
* import { dontcode } from '@dontcode2/backend'
|
|
355
|
+
* const client = dontcode() // reads DONTCODE_API_KEY
|
|
356
|
+
* await client.auth.signup({ email, password, role: 'editor' })
|
|
357
|
+
* ```
|
|
358
|
+
*/
|
|
359
|
+
declare function dontcode(options?: DontCodeClientOptions): DontCodeClient;
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Every non-2xx response from the gateway surfaces as a DontCodeError. The
|
|
363
|
+
* platform's error envelope is `{ error, ... }`, sometimes with a machine
|
|
364
|
+
* `code` (e.g. `EmailNotVerified`, `ChallengeExpired`, `MfaNotOffered`) or
|
|
365
|
+
* rate-limit fields. We preserve the whole body so callers can branch on it.
|
|
366
|
+
*
|
|
367
|
+
* Note: many "one more step" auth states (signup needing email verification,
|
|
368
|
+
* login returning `mfa_required`) are 2xx successes, NOT errors; inspect the
|
|
369
|
+
* resolved value for those. Errors are reserved for actual failures.
|
|
370
|
+
*/
|
|
371
|
+
interface DontCodeErrorBody {
|
|
372
|
+
error?: string;
|
|
373
|
+
/** Stable machine code, when the platform sends one. */
|
|
374
|
+
code?: string;
|
|
375
|
+
/** Present on 429 responses. */
|
|
376
|
+
rate_limit?: boolean;
|
|
377
|
+
/** Seconds until the rate limit resets, on 429 responses. */
|
|
378
|
+
timeleft?: number;
|
|
379
|
+
[key: string]: unknown;
|
|
380
|
+
}
|
|
381
|
+
declare class DontCodeError extends Error {
|
|
382
|
+
/** HTTP status code of the failing response. */
|
|
383
|
+
readonly status: number;
|
|
384
|
+
/** Stable machine code, when present (e.g. `EmailNotVerified`). */
|
|
385
|
+
readonly code?: string;
|
|
386
|
+
/** The raw parsed response body. */
|
|
387
|
+
readonly body: DontCodeErrorBody;
|
|
388
|
+
constructor(status: number, body: DontCodeErrorBody);
|
|
389
|
+
/** True when the request was rejected by the per-key rate limiter. */
|
|
390
|
+
get rateLimited(): boolean;
|
|
391
|
+
}
|
|
392
|
+
/** Cross-bundle-safe check; works even if two copies of the SDK are loaded. */
|
|
393
|
+
declare function isDontCodeError(err: unknown): err is DontCodeError;
|
|
394
|
+
|
|
395
|
+
export { AuthApi, type AuthTokens, BucketClient, type CurrentUser, type DbClient, type DeleteInput, type DontCodeClient, type DontCodeClientOptions, DontCodeError, type DontCodeErrorBody, type DownloadResult, type ForgotPasswordInput, type ListResult, type LoginInput, type LoginResult, type MeResult, MfaApi, type MfaChallengeInput, type MfaDisableInput, type MfaEnrollConfirmInput, type MfaEnrollResult, type MigrateInput, type MigrateResult, type OrderByClause, type PresignResult, PublicBucketClient, type QueryOptions, type ResetPasswordInput, type SignupInput, type SignupResult, type SimpleResult, type StorageBucket, type StorageClient, type StorageObject, TableQuery, type TemporaryUrlResult, type UpdateInput, type UploadBody, type VerifyEmailInput, type WhereClause, type WhereOperator, createStorage, dontcode, isDontCodeError };
|