backd-js 0.3.0 → 0.4.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/package.json +1 -1
- package/src/admin.js +12 -6
- package/src/auth.js +23 -7
- package/src/client.js +13 -3
- package/src/data.js +26 -5
- package/src/index.js +1 -0
- package/src/storage.js +7 -0
- package/types/admin.d.ts +20 -5
- package/types/auth.d.ts +13 -4
- package/types/client.d.ts +8 -0
- package/types/data.d.ts +18 -5
- package/types/index.d.ts +2 -0
- package/types/storage.d.ts +6 -0
package/package.json
CHANGED
package/src/admin.js
CHANGED
|
@@ -27,6 +27,7 @@ import { Job } from './functions.js'
|
|
|
27
27
|
* @property {number} limit
|
|
28
28
|
* @property {number} skip
|
|
29
29
|
* @property {boolean} has_more
|
|
30
|
+
* @property {string} [next_cursor] With `has_more`: pass it as `after` for the next page.
|
|
30
31
|
*/
|
|
31
32
|
|
|
32
33
|
/**
|
|
@@ -65,6 +66,7 @@ import { Job } from './functions.js'
|
|
|
65
66
|
* @property {'data' | 'admin'} role
|
|
66
67
|
* @property {string} prefix First characters of the key.
|
|
67
68
|
* @property {string[]} networks Where it may be used from; empty: anywhere.
|
|
69
|
+
* @property {string[]} scopes What it reaches (`read:blog/posts`, `call:main/export`…); empty: everything.
|
|
68
70
|
* @property {string} created_at
|
|
69
71
|
* @property {string | null} last_used_at
|
|
70
72
|
* @property {string | null} expires_at
|
|
@@ -225,13 +227,14 @@ class AdminUsers {
|
|
|
225
227
|
}
|
|
226
228
|
|
|
227
229
|
/**
|
|
228
|
-
* A page of users, sorted by email.
|
|
229
|
-
*
|
|
230
|
+
* A page of users, sorted by email. `after` is the `next_cursor` of the
|
|
231
|
+
* previous page (not combinable with `skip`).
|
|
232
|
+
* @param {{ limit?: number, skip?: number, after?: string }} [params]
|
|
230
233
|
* @param {RequestOptions} [opts]
|
|
231
234
|
* @returns {Promise<UserPage>}
|
|
232
235
|
*/
|
|
233
236
|
async list(params = {}, opts) {
|
|
234
|
-
return (await this.admin._request({ method: 'GET', path: ['users'], query: { limit: params.limit, skip: params.skip }, ...opts })).data
|
|
237
|
+
return (await this.admin._request({ method: 'GET', path: ['users'], query: { limit: params.limit, skip: params.skip, after: params.after }, ...opts })).data
|
|
235
238
|
}
|
|
236
239
|
|
|
237
240
|
/**
|
|
@@ -388,17 +391,20 @@ class AdminAPIKeys {
|
|
|
388
391
|
|
|
389
392
|
/**
|
|
390
393
|
* Creates a key; store its `key` now, it is never shown again.
|
|
391
|
-
* @param {{ name: string, role?: 'data' | 'admin', expiresIn?: string, networks?: string[] }} input
|
|
392
|
-
* `role` defaults to data; `expiresIn`: days (`90d`) or Go durations (`12h`).
|
|
394
|
+
* @param {{ name: string, role?: 'data' | 'admin', expiresIn?: string, networks?: string[], scopes?: string[] }} input
|
|
395
|
+
* `role` defaults to data; `expiresIn`: days (`90d`) or Go durations (`12h`). `scopes` limit what a
|
|
396
|
+
* data key reaches: `read`, `write` or `call`, optionally followed by `:<database>` or
|
|
397
|
+
* `:<database>/<collection or function>`; none means everything.
|
|
393
398
|
* @param {RequestOptions} [opts]
|
|
394
399
|
* @returns {Promise<NewAPIKey>}
|
|
395
400
|
*/
|
|
396
|
-
async create({ name, role, expiresIn, networks }, opts) {
|
|
401
|
+
async create({ name, role, expiresIn, networks, scopes }, opts) {
|
|
397
402
|
/** @type {Record<string, unknown>} */
|
|
398
403
|
const body = { name }
|
|
399
404
|
if (role !== undefined) body.role = role
|
|
400
405
|
if (expiresIn !== undefined) body.expires_in = expiresIn
|
|
401
406
|
if (networks !== undefined) body.networks = networks
|
|
407
|
+
if (scopes !== undefined) body.scopes = scopes
|
|
402
408
|
return (await this.admin._request({ method: 'POST', path: ['apikeys'], body, ...opts })).data
|
|
403
409
|
}
|
|
404
410
|
|
package/src/auth.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AuthenticationError, VerificationRequiredError } from './errors.js'
|
|
2
|
+
import { COOKIE_SESSION } from './storage.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @typedef {import('./client.js').Client} Client
|
|
@@ -19,8 +20,9 @@ import { AuthenticationError, VerificationRequiredError } from './errors.js'
|
|
|
19
20
|
/**
|
|
20
21
|
* A new session.
|
|
21
22
|
* @typedef {object} Session
|
|
22
|
-
* @property {string} token
|
|
23
|
-
*
|
|
23
|
+
* @property {string} [token] Session token (`bds_…`); already stored by the client. Absent with `cookies: true`:
|
|
24
|
+
* the session is in an HttpOnly cookie, out of reach of scripts.
|
|
25
|
+
* @property {'Bearer'} [token_type]
|
|
24
26
|
* @property {string} session_id
|
|
25
27
|
* @property {string} expires_at RFC3339; pushed forward as the session is used.
|
|
26
28
|
* @property {User} user
|
|
@@ -81,8 +83,9 @@ export class Auth {
|
|
|
81
83
|
* @returns {Promise<Session>}
|
|
82
84
|
*/
|
|
83
85
|
async signup({ email, password, invitation, locale, redirectTo }, opts) {
|
|
84
|
-
/** @type {Record<string, string>} */
|
|
86
|
+
/** @type {Record<string, string | boolean>} */
|
|
85
87
|
const body = { email, password }
|
|
88
|
+
if (this.client.cookies) body.cookie = true
|
|
86
89
|
if (invitation) body.invitation = invitation
|
|
87
90
|
if (locale) body.locale = locale
|
|
88
91
|
if (redirectTo) body.redirect_to = redirectTo
|
|
@@ -100,7 +103,8 @@ export class Auth {
|
|
|
100
103
|
* @returns {Promise<Session>}
|
|
101
104
|
*/
|
|
102
105
|
async login({ email, password }, opts) {
|
|
103
|
-
const
|
|
106
|
+
const body = this.client.cookies ? { email, password, cookie: true } : { email, password }
|
|
107
|
+
const { data } = await this.client.request({ method: 'POST', path: ['_auth', 'login'], body, auth: false, ...opts })
|
|
104
108
|
return this.signedIn(data)
|
|
105
109
|
}
|
|
106
110
|
|
|
@@ -311,11 +315,23 @@ export class Auth {
|
|
|
311
315
|
}
|
|
312
316
|
|
|
313
317
|
/**
|
|
314
|
-
* The stored session token, if any.
|
|
318
|
+
* The stored session token, if any. Always null with `cookies: true`: the
|
|
319
|
+
* token is in a cookie that scripts can't read.
|
|
315
320
|
* @returns {Promise<string | null>}
|
|
316
321
|
*/
|
|
317
322
|
async token() {
|
|
318
|
-
|
|
323
|
+
const token = (await this.client.storage.get()) ?? null
|
|
324
|
+
return token === COOKIE_SESSION ? null : token
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Whether this client believes it is signed in: it holds a token, or (with
|
|
329
|
+
* `cookies: true`) it signed in and hasn't signed out or been refused since.
|
|
330
|
+
* A page that has just loaded knows nothing: ask the server with {@link Auth#me}.
|
|
331
|
+
* @returns {Promise<boolean>}
|
|
332
|
+
*/
|
|
333
|
+
async hasSession() {
|
|
334
|
+
return Boolean(await this.client.storage.get())
|
|
319
335
|
}
|
|
320
336
|
|
|
321
337
|
/**
|
|
@@ -334,7 +350,7 @@ export class Auth {
|
|
|
334
350
|
* @returns {Promise<Session>}
|
|
335
351
|
*/
|
|
336
352
|
async signedIn(session) {
|
|
337
|
-
await this.client.storage.set(session.token)
|
|
353
|
+
await this.client.storage.set(session.token ?? COOKIE_SESSION)
|
|
338
354
|
this.emit('SIGNED_IN', session)
|
|
339
355
|
return session
|
|
340
356
|
}
|
package/src/client.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Admin } from './admin.js'
|
|
|
2
2
|
import { Auth } from './auth.js'
|
|
3
3
|
import { Database } from './data.js'
|
|
4
4
|
import { NetworkError, RetryableError, errorFromResponse } from './errors.js'
|
|
5
|
-
import { memoryStorage } from './storage.js'
|
|
5
|
+
import { COOKIE_SESSION, memoryStorage } from './storage.js'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @typedef {import('./storage.js').TokenStorage} TokenStorage
|
|
@@ -23,6 +23,9 @@ import { memoryStorage } from './storage.js'
|
|
|
23
23
|
* @property {string} [apiKey] Server-side only: an API key (`bdk_…`). Full access to the realm.
|
|
24
24
|
* @property {boolean} [dangerouslyAllowBrowser] Allow `apiKey` in a browser. Anyone who loads the page gets the key.
|
|
25
25
|
* @property {TokenStorage} [storage] Where the session token lives; memory by default.
|
|
26
|
+
* @property {boolean} [cookies] Browser apps: keep the session in an HttpOnly cookie that scripts can't read,
|
|
27
|
+
* instead of a token. The realm must enable `sessions.cookie` and list the app's origin in `cors.origins`; the
|
|
28
|
+
* app's page (or the API) must be on a site the cookie's SameSite setting allows. The admin API doesn't take cookies.
|
|
26
29
|
* @property {RetryOptions} [retry] Retries for 429/503; off by default.
|
|
27
30
|
* @property {typeof fetch} [fetch] A fetch implementation; the global one by default.
|
|
28
31
|
* @property {Record<string, string>} [headers] Extra headers for every request.
|
|
@@ -81,11 +84,16 @@ export class Client {
|
|
|
81
84
|
'`dangerouslyAllowBrowser: true` if you really mean it.',
|
|
82
85
|
)
|
|
83
86
|
}
|
|
87
|
+
if (options.cookies && options.apiKey) {
|
|
88
|
+
throw new TypeError('createClient: `cookies` is for sessions in a browser; an API key is sent in a header')
|
|
89
|
+
}
|
|
84
90
|
/** @readonly */
|
|
85
91
|
this.url = options.url.replace(/\/+$/, '')
|
|
86
92
|
/** @readonly */
|
|
87
93
|
this.realm = options.realm
|
|
88
94
|
/** @internal */
|
|
95
|
+
this.cookies = Boolean(options.cookies)
|
|
96
|
+
/** @internal */
|
|
89
97
|
this.apiKey = options.apiKey
|
|
90
98
|
/** @internal */
|
|
91
99
|
this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis)
|
|
@@ -138,14 +146,15 @@ export class Client {
|
|
|
138
146
|
} else {
|
|
139
147
|
const token = await this.storage.get()
|
|
140
148
|
if (token) {
|
|
141
|
-
headers.Authorization = 'Bearer ' + token
|
|
149
|
+
if (token !== COOKIE_SESSION) headers.Authorization = 'Bearer ' + token // a cookie session is sent by the browser
|
|
142
150
|
sentSession = true
|
|
143
151
|
}
|
|
144
152
|
}
|
|
145
153
|
}
|
|
146
154
|
|
|
147
155
|
const retry = req.retry ?? this.retry
|
|
148
|
-
|
|
156
|
+
// Safe to repeat: reads, writes conditional on a version, and requests the server deduplicates by key.
|
|
157
|
+
const safe = req.method === 'GET' || Object.keys(headers).some((h) => ['if-match', 'idempotency-key'].includes(h.toLowerCase()))
|
|
149
158
|
const attempts = safe ? Math.max(0, retry.attempts) : 0
|
|
150
159
|
for (let attempt = 0; ; attempt++) {
|
|
151
160
|
/** @type {Response} */
|
|
@@ -156,6 +165,7 @@ export class Client {
|
|
|
156
165
|
headers,
|
|
157
166
|
body: req.body === undefined ? undefined : JSON.stringify(req.body),
|
|
158
167
|
signal: req.signal,
|
|
168
|
+
credentials: this.cookies ? 'include' : undefined,
|
|
159
169
|
})
|
|
160
170
|
} catch (cause) {
|
|
161
171
|
throw new NetworkError({
|
package/src/data.js
CHANGED
|
@@ -54,6 +54,15 @@ import { Job } from './functions.js'
|
|
|
54
54
|
* @typedef {RequestOptions & { ifMatch?: number | string }} WriteOptions
|
|
55
55
|
*/
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Options for creates and batches. `idempotencyKey` makes a retry safe: the
|
|
59
|
+
* server remembers the answer of the first request with a key for 24 hours and
|
|
60
|
+
* returns it again for the same key and body, instead of creating a second
|
|
61
|
+
* document. The client also retries such a request itself after a network
|
|
62
|
+
* error, `429` or `503` (see `retry`).
|
|
63
|
+
* @typedef {RequestOptions & { idempotencyKey?: string }} CreateOptions
|
|
64
|
+
*/
|
|
65
|
+
|
|
57
66
|
/**
|
|
58
67
|
* One write for `db.batch(...)`, applied atomically with the others.
|
|
59
68
|
* `create` and `replace` need `document`; `patch` needs `patch` (a JSON
|
|
@@ -99,14 +108,14 @@ export class Database {
|
|
|
99
108
|
* mismatch (`ifMatch`) is a `VersionMismatchError`, same as a single
|
|
100
109
|
* write.
|
|
101
110
|
* @param {BatchOperation[]} operations
|
|
102
|
-
* @param {
|
|
111
|
+
* @param {CreateOptions} [opts]
|
|
103
112
|
* @returns {Promise<(Doc | { id: string })[]>} one entry per operation, in order
|
|
104
113
|
*/
|
|
105
114
|
async batch(operations, opts) {
|
|
106
115
|
const body = {
|
|
107
116
|
operations: operations.map(({ ifMatch, ...op }) => (ifMatch === undefined ? op : { ...op, if_match: ifMatchValue(ifMatch) })),
|
|
108
117
|
}
|
|
109
|
-
const { data } = await this.client.request({ method: 'POST', path: [this.name, '_batch'], body, ...opts })
|
|
118
|
+
const { data } = await this.client.request({ method: 'POST', path: [this.name, '_batch'], body, ...keyed(opts) })
|
|
110
119
|
return data.results
|
|
111
120
|
}
|
|
112
121
|
|
|
@@ -208,13 +217,15 @@ export class Collection {
|
|
|
208
217
|
}
|
|
209
218
|
|
|
210
219
|
/**
|
|
211
|
-
* Creates a document. `id` and `_meta` are set by the server.
|
|
220
|
+
* Creates a document. `id` and `_meta` are set by the server. With an
|
|
221
|
+
* `idempotencyKey`, a repeated request returns the document created by the
|
|
222
|
+
* first one.
|
|
212
223
|
* @param {T} doc
|
|
213
|
-
* @param {
|
|
224
|
+
* @param {CreateOptions} [opts]
|
|
214
225
|
* @returns {Promise<Doc<T>>}
|
|
215
226
|
*/
|
|
216
227
|
async create(doc, opts) {
|
|
217
|
-
return (await this.client.request({ method: 'POST', path: this.path, body: doc, ...opts })).data
|
|
228
|
+
return (await this.client.request({ method: 'POST', path: this.path, body: doc, ...keyed(opts) })).data
|
|
218
229
|
}
|
|
219
230
|
|
|
220
231
|
/**
|
|
@@ -274,6 +285,16 @@ function listQuery(p) {
|
|
|
274
285
|
}
|
|
275
286
|
}
|
|
276
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Turns `idempotencyKey` into an Idempotency-Key header.
|
|
290
|
+
* @param {CreateOptions} [opts]
|
|
291
|
+
* @returns {RequestOptions}
|
|
292
|
+
*/
|
|
293
|
+
function keyed(opts = {}) {
|
|
294
|
+
const { idempotencyKey, ...rest } = opts
|
|
295
|
+
return idempotencyKey === undefined ? rest : { ...rest, headers: { ...rest.headers, 'Idempotency-Key': idempotencyKey } }
|
|
296
|
+
}
|
|
297
|
+
|
|
277
298
|
/**
|
|
278
299
|
* Turns `ifMatch` into an If-Match header.
|
|
279
300
|
* @param {WriteOptions} opts
|
package/src/index.js
CHANGED
|
@@ -30,6 +30,7 @@ export { memoryStorage, localStorageStorage } from './storage.js'
|
|
|
30
30
|
* @typedef {import('./data.js').Meta} Meta
|
|
31
31
|
* @typedef {import('./data.js').ListParams} ListParams
|
|
32
32
|
* @typedef {import('./data.js').WriteOptions} WriteOptions
|
|
33
|
+
* @typedef {import('./data.js').CreateOptions} CreateOptions
|
|
33
34
|
* @typedef {import('./admin.js').AdminUser} AdminUser
|
|
34
35
|
* @typedef {import('./admin.js').UserPage} UserPage
|
|
35
36
|
* @typedef {import('./admin.js').Invitation} Invitation
|
package/src/storage.js
CHANGED
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
* @property {() => void | Promise<void>} remove
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* What the client stores, instead of a token, when the session lives in an
|
|
11
|
+
* HttpOnly cookie (`cookies: true`): a hint that there is a session, with
|
|
12
|
+
* nothing secret in it.
|
|
13
|
+
*/
|
|
14
|
+
export const COOKIE_SESSION = 'cookie-session'
|
|
15
|
+
|
|
9
16
|
/**
|
|
10
17
|
* Keeps the token in memory: it is lost on reload, and scripts on the page
|
|
11
18
|
* can't read it from storage. The default.
|
package/types/admin.d.ts
CHANGED
|
@@ -28,6 +28,10 @@ export type UserPage = {
|
|
|
28
28
|
limit: number;
|
|
29
29
|
skip: number;
|
|
30
30
|
has_more: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* With `has_more`: pass it as `after` for the next page.
|
|
33
|
+
*/
|
|
34
|
+
next_cursor?: string;
|
|
31
35
|
};
|
|
32
36
|
export type Invitation = {
|
|
33
37
|
id: string;
|
|
@@ -79,6 +83,10 @@ export type APIKeyInfo = {
|
|
|
79
83
|
* Where it may be used from; empty: anywhere.
|
|
80
84
|
*/
|
|
81
85
|
networks: string[];
|
|
86
|
+
/**
|
|
87
|
+
* What it reaches (`read:blog/posts`, `call:main/export`…); empty: everything.
|
|
88
|
+
*/
|
|
89
|
+
scopes: string[];
|
|
82
90
|
created_at: string;
|
|
83
91
|
last_used_at: string | null;
|
|
84
92
|
expires_at: string | null;
|
|
@@ -241,6 +249,7 @@ export type JobsPage = {
|
|
|
241
249
|
* @property {number} limit
|
|
242
250
|
* @property {number} skip
|
|
243
251
|
* @property {boolean} has_more
|
|
252
|
+
* @property {string} [next_cursor] With `has_more`: pass it as `after` for the next page.
|
|
244
253
|
*/
|
|
245
254
|
/**
|
|
246
255
|
* @typedef {object} Invitation
|
|
@@ -274,6 +283,7 @@ export type JobsPage = {
|
|
|
274
283
|
* @property {'data' | 'admin'} role
|
|
275
284
|
* @property {string} prefix First characters of the key.
|
|
276
285
|
* @property {string[]} networks Where it may be used from; empty: anywhere.
|
|
286
|
+
* @property {string[]} scopes What it reaches (`read:blog/posts`, `call:main/export`…); empty: everything.
|
|
277
287
|
* @property {string} created_at
|
|
278
288
|
* @property {string | null} last_used_at
|
|
279
289
|
* @property {string | null} expires_at
|
|
@@ -408,14 +418,16 @@ declare class AdminUsers {
|
|
|
408
418
|
/** @param {Admin} admin */
|
|
409
419
|
constructor(admin: Admin);
|
|
410
420
|
/**
|
|
411
|
-
* A page of users, sorted by email.
|
|
412
|
-
*
|
|
421
|
+
* A page of users, sorted by email. `after` is the `next_cursor` of the
|
|
422
|
+
* previous page (not combinable with `skip`).
|
|
423
|
+
* @param {{ limit?: number, skip?: number, after?: string }} [params]
|
|
413
424
|
* @param {RequestOptions} [opts]
|
|
414
425
|
* @returns {Promise<UserPage>}
|
|
415
426
|
*/
|
|
416
427
|
list(params?: {
|
|
417
428
|
limit?: number;
|
|
418
429
|
skip?: number;
|
|
430
|
+
after?: string;
|
|
419
431
|
}, opts?: RequestOptions): Promise<UserPage>;
|
|
420
432
|
/**
|
|
421
433
|
* The user with this email (case-insensitive), or null.
|
|
@@ -536,16 +548,19 @@ declare class AdminAPIKeys {
|
|
|
536
548
|
list(opts?: RequestOptions): Promise<APIKeyInfo[]>;
|
|
537
549
|
/**
|
|
538
550
|
* Creates a key; store its `key` now, it is never shown again.
|
|
539
|
-
* @param {{ name: string, role?: 'data' | 'admin', expiresIn?: string, networks?: string[] }} input
|
|
540
|
-
* `role` defaults to data; `expiresIn`: days (`90d`) or Go durations (`12h`).
|
|
551
|
+
* @param {{ name: string, role?: 'data' | 'admin', expiresIn?: string, networks?: string[], scopes?: string[] }} input
|
|
552
|
+
* `role` defaults to data; `expiresIn`: days (`90d`) or Go durations (`12h`). `scopes` limit what a
|
|
553
|
+
* data key reaches: `read`, `write` or `call`, optionally followed by `:<database>` or
|
|
554
|
+
* `:<database>/<collection or function>`; none means everything.
|
|
541
555
|
* @param {RequestOptions} [opts]
|
|
542
556
|
* @returns {Promise<NewAPIKey>}
|
|
543
557
|
*/
|
|
544
|
-
create({ name, role, expiresIn, networks }: {
|
|
558
|
+
create({ name, role, expiresIn, networks, scopes }: {
|
|
545
559
|
name: string;
|
|
546
560
|
role?: 'data' | 'admin';
|
|
547
561
|
expiresIn?: string;
|
|
548
562
|
networks?: string[];
|
|
563
|
+
scopes?: string[];
|
|
549
564
|
}, opts?: RequestOptions): Promise<NewAPIKey>;
|
|
550
565
|
/**
|
|
551
566
|
* Revokes a key: it stops working at once.
|
package/types/auth.d.ts
CHANGED
|
@@ -16,10 +16,11 @@ export type User = {
|
|
|
16
16
|
};
|
|
17
17
|
export type Session = {
|
|
18
18
|
/**
|
|
19
|
-
* Session token (`bds_…`); already stored by the client.
|
|
19
|
+
* Session token (`bds_…`); already stored by the client. Absent with `cookies: true`:
|
|
20
|
+
* the session is in an HttpOnly cookie, out of reach of scripts.
|
|
20
21
|
*/
|
|
21
|
-
token
|
|
22
|
-
token_type
|
|
22
|
+
token?: string;
|
|
23
|
+
token_type?: 'Bearer';
|
|
23
24
|
session_id: string;
|
|
24
25
|
/**
|
|
25
26
|
* RFC3339; pushed forward as the session is used.
|
|
@@ -246,10 +247,18 @@ export declare class Auth {
|
|
|
246
247
|
*/
|
|
247
248
|
revokeSession(id: string, opts?: RequestOptions): Promise<void>;
|
|
248
249
|
/**
|
|
249
|
-
* The stored session token, if any.
|
|
250
|
+
* The stored session token, if any. Always null with `cookies: true`: the
|
|
251
|
+
* token is in a cookie that scripts can't read.
|
|
250
252
|
* @returns {Promise<string | null>}
|
|
251
253
|
*/
|
|
252
254
|
token(): Promise<string | null>;
|
|
255
|
+
/**
|
|
256
|
+
* Whether this client believes it is signed in: it holds a token, or (with
|
|
257
|
+
* `cookies: true`) it signed in and hasn't signed out or been refused since.
|
|
258
|
+
* A page that has just loaded knows nothing: ask the server with {@link Auth#me}.
|
|
259
|
+
* @returns {Promise<boolean>}
|
|
260
|
+
*/
|
|
261
|
+
hasSession(): Promise<boolean>;
|
|
253
262
|
/**
|
|
254
263
|
* Calls listener on sign-in, sign-out and session expiry.
|
|
255
264
|
* @param {AuthListener} listener
|
package/types/client.d.ts
CHANGED
|
@@ -33,6 +33,12 @@ export type ClientOptions = {
|
|
|
33
33
|
* Where the session token lives; memory by default.
|
|
34
34
|
*/
|
|
35
35
|
storage?: TokenStorage;
|
|
36
|
+
/**
|
|
37
|
+
* Browser apps: keep the session in an HttpOnly cookie that scripts can't read,
|
|
38
|
+
* instead of a token. The realm must enable `sessions.cookie` and list the app's origin in `cors.origins`; the
|
|
39
|
+
* app's page (or the API) must be on a site the cookie's SameSite setting allows. The admin API doesn't take cookies.
|
|
40
|
+
*/
|
|
41
|
+
cookies?: boolean;
|
|
36
42
|
/**
|
|
37
43
|
* Retries for 429/503; off by default.
|
|
38
44
|
*/
|
|
@@ -98,6 +104,8 @@ export declare class Client {
|
|
|
98
104
|
/** @readonly */
|
|
99
105
|
realm: string;
|
|
100
106
|
/** @internal */
|
|
107
|
+
cookies: boolean;
|
|
108
|
+
/** @internal */
|
|
101
109
|
apiKey: string | undefined;
|
|
102
110
|
/** @internal */
|
|
103
111
|
fetchImpl: typeof fetch;
|
package/types/data.d.ts
CHANGED
|
@@ -75,6 +75,9 @@ export type Page<T extends object = Record<string, any>> = {
|
|
|
75
75
|
export type WriteOptions = RequestOptions & {
|
|
76
76
|
ifMatch?: number | string;
|
|
77
77
|
};
|
|
78
|
+
export type CreateOptions = RequestOptions & {
|
|
79
|
+
idempotencyKey?: string;
|
|
80
|
+
};
|
|
78
81
|
export type BatchOperation = {
|
|
79
82
|
op: 'create' | 'replace' | 'patch' | 'delete';
|
|
80
83
|
collection: string;
|
|
@@ -131,6 +134,14 @@ export type BatchOperation = {
|
|
|
131
134
|
* VersionMismatchError unless the document is still at that version.
|
|
132
135
|
* @typedef {RequestOptions & { ifMatch?: number | string }} WriteOptions
|
|
133
136
|
*/
|
|
137
|
+
/**
|
|
138
|
+
* Options for creates and batches. `idempotencyKey` makes a retry safe: the
|
|
139
|
+
* server remembers the answer of the first request with a key for 24 hours and
|
|
140
|
+
* returns it again for the same key and body, instead of creating a second
|
|
141
|
+
* document. The client also retries such a request itself after a network
|
|
142
|
+
* error, `429` or `503` (see `retry`).
|
|
143
|
+
* @typedef {RequestOptions & { idempotencyKey?: string }} CreateOptions
|
|
144
|
+
*/
|
|
134
145
|
/**
|
|
135
146
|
* One write for `db.batch(...)`, applied atomically with the others.
|
|
136
147
|
* `create` and `replace` need `document`; `patch` needs `patch` (a JSON
|
|
@@ -170,10 +181,10 @@ export declare class Database {
|
|
|
170
181
|
* mismatch (`ifMatch`) is a `VersionMismatchError`, same as a single
|
|
171
182
|
* write.
|
|
172
183
|
* @param {BatchOperation[]} operations
|
|
173
|
-
* @param {
|
|
184
|
+
* @param {CreateOptions} [opts]
|
|
174
185
|
* @returns {Promise<(Doc | { id: string })[]>} one entry per operation, in order
|
|
175
186
|
*/
|
|
176
|
-
batch(operations: BatchOperation[], opts?:
|
|
187
|
+
batch(operations: BatchOperation[], opts?: CreateOptions): Promise<(Doc | {
|
|
177
188
|
id: string;
|
|
178
189
|
})[]>;
|
|
179
190
|
/**
|
|
@@ -239,12 +250,14 @@ export declare class Collection<T extends object = Record<string, any>> {
|
|
|
239
250
|
*/
|
|
240
251
|
get(id: string, opts?: RequestOptions): Promise<Doc<T>>;
|
|
241
252
|
/**
|
|
242
|
-
* Creates a document. `id` and `_meta` are set by the server.
|
|
253
|
+
* Creates a document. `id` and `_meta` are set by the server. With an
|
|
254
|
+
* `idempotencyKey`, a repeated request returns the document created by the
|
|
255
|
+
* first one.
|
|
243
256
|
* @param {T} doc
|
|
244
|
-
* @param {
|
|
257
|
+
* @param {CreateOptions} [opts]
|
|
245
258
|
* @returns {Promise<Doc<T>>}
|
|
246
259
|
*/
|
|
247
|
-
create(doc: T, opts?:
|
|
260
|
+
create(doc: T, opts?: CreateOptions): Promise<Doc<T>>;
|
|
248
261
|
/**
|
|
249
262
|
* Replaces the whole document: fields not in `doc` are removed.
|
|
250
263
|
* @param {string} id
|
package/types/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export type ErrorDetail = import('./errors.js').ErrorDetail;
|
|
|
17
17
|
export type Meta = import('./data.js').Meta;
|
|
18
18
|
export type ListParams = import('./data.js').ListParams;
|
|
19
19
|
export type WriteOptions = import('./data.js').WriteOptions;
|
|
20
|
+
export type CreateOptions = import('./data.js').CreateOptions;
|
|
20
21
|
export type AdminUser = import('./admin.js').AdminUser;
|
|
21
22
|
export type UserPage = import('./admin.js').UserPage;
|
|
22
23
|
export type Invitation = import('./admin.js').Invitation;
|
|
@@ -40,6 +41,7 @@ export type Page<T extends object = Record<string, any>> = import('./data.js').P
|
|
|
40
41
|
* @typedef {import('./data.js').Meta} Meta
|
|
41
42
|
* @typedef {import('./data.js').ListParams} ListParams
|
|
42
43
|
* @typedef {import('./data.js').WriteOptions} WriteOptions
|
|
44
|
+
* @typedef {import('./data.js').CreateOptions} CreateOptions
|
|
43
45
|
* @typedef {import('./admin.js').AdminUser} AdminUser
|
|
44
46
|
* @typedef {import('./admin.js').UserPage} UserPage
|
|
45
47
|
* @typedef {import('./admin.js').Invitation} Invitation
|
package/types/storage.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ export type TokenStorage = {
|
|
|
10
10
|
set: (token: string) => void | Promise<void>;
|
|
11
11
|
remove: () => void | Promise<void>;
|
|
12
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* What the client stores, instead of a token, when the session lives in an
|
|
15
|
+
* HttpOnly cookie (`cookies: true`): a hint that there is a session, with
|
|
16
|
+
* nothing secret in it.
|
|
17
|
+
*/
|
|
18
|
+
export declare const COOKIE_SESSION = "cookie-session";
|
|
13
19
|
/**
|
|
14
20
|
* Keeps the token in memory: it is lost on reload, and scripts on the page
|
|
15
21
|
* can't read it from storage. The default.
|