@iglo.tech/iglo.base 0.0.0-stage → 1.0.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/CHANGELOG.md +18 -0
- package/LICENSE +18 -0
- package/README.md +778 -2
- package/dist/iglo-base.es.mjs +4 -0
- package/dist/iglo-base.es.mjs.map +34 -0
- package/dist/ssr-astro.es.mjs +4 -0
- package/dist/ssr-astro.es.mjs.map +12 -0
- package/dist/ssr-browser-client.es.mjs +4 -0
- package/dist/ssr-browser-client.es.mjs.map +13 -0
- package/dist/ssr-browser.es.mjs +4 -0
- package/dist/ssr-browser.es.mjs.map +10 -0
- package/dist/ssr-next.es.mjs +4 -0
- package/dist/ssr-next.es.mjs.map +12 -0
- package/dist/ssr-nuxt.es.mjs +4 -0
- package/dist/ssr-nuxt.es.mjs.map +12 -0
- package/dist/ssr-react-router.es.mjs +4 -0
- package/dist/ssr-react-router.es.mjs.map +12 -0
- package/dist/ssr-sveltekit.es.mjs +4 -0
- package/dist/ssr-sveltekit.es.mjs.map +12 -0
- package/dist/ssr.es.mjs +4 -0
- package/dist/ssr.es.mjs.map +22 -0
- package/dist/types/src/Client.d.ts +233 -0
- package/dist/types/src/ClientResponseError.d.ts +22 -0
- package/dist/types/src/index.d.ts +18 -0
- package/dist/types/src/services/BackupService.d.ts +59 -0
- package/dist/types/src/services/BaseService.d.ts +8 -0
- package/dist/types/src/services/BatchService.d.ts +56 -0
- package/dist/types/src/services/CollectionService.d.ts +47 -0
- package/dist/types/src/services/CronService.d.ts +20 -0
- package/dist/types/src/services/CrudService.d.ts +85 -0
- package/dist/types/src/services/FileService.d.ts +19 -0
- package/dist/types/src/services/HealthService.d.ts +16 -0
- package/dist/types/src/services/LogService.d.ts +35 -0
- package/dist/types/src/services/RealtimeService.d.ts +124 -0
- package/dist/types/src/services/RecordService.d.ts +374 -0
- package/dist/types/src/services/SQLService.d.ts +21 -0
- package/dist/types/src/services/SettingsService.d.ts +46 -0
- package/dist/types/src/ssr/adapter.d.ts +133 -0
- package/dist/types/src/ssr/astro.d.ts +27 -0
- package/dist/types/src/ssr/browser-client.d.ts +100 -0
- package/dist/types/src/ssr/browser-token.d.ts +29 -0
- package/dist/types/src/ssr/browser.d.ts +1 -0
- package/dist/types/src/ssr/cache.d.ts +9 -0
- package/dist/types/src/ssr/cookies.d.ts +68 -0
- package/dist/types/src/ssr/csrf.d.ts +38 -0
- package/dist/types/src/ssr/index.d.ts +12 -0
- package/dist/types/src/ssr/next.d.ts +53 -0
- package/dist/types/src/ssr/no-store-fetch.d.ts +7 -0
- package/dist/types/src/ssr/nuxt.d.ts +41 -0
- package/dist/types/src/ssr/oauth2.d.ts +56 -0
- package/dist/types/src/ssr/popup.d.ts +14 -0
- package/dist/types/src/ssr/react-router.d.ts +53 -0
- package/dist/types/src/ssr/renew.d.ts +25 -0
- package/dist/types/src/ssr/server-client.d.ts +83 -0
- package/dist/types/src/ssr/superuser-client.d.ts +24 -0
- package/dist/types/src/ssr/sveltekit.d.ts +44 -0
- package/dist/types/src/ssr/types.d.ts +118 -0
- package/dist/types/src/stores/AsyncAuthStore.d.ts +52 -0
- package/dist/types/src/stores/BaseAuthStore.d.ts +104 -0
- package/dist/types/src/stores/LocalAuthStore.d.ts +48 -0
- package/dist/types/src/tools/cookie.d.ts +31 -0
- package/dist/types/src/tools/dtos.d.ts +122 -0
- package/dist/types/src/tools/formdata.d.ts +30 -0
- package/dist/types/src/tools/jwt.d.ts +14 -0
- package/dist/types/src/tools/legacy.d.ts +2 -0
- package/dist/types/src/tools/loose.d.ts +5 -0
- package/dist/types/src/tools/options.d.ts +91 -0
- package/dist/types/src/tools/refresh.d.ts +3 -0
- package/package.json +73 -4
package/README.md
CHANGED
|
@@ -1,3 +1,779 @@
|
|
|
1
|
-
|
|
1
|
+
iglo.base JavaScript SDK
|
|
2
|
+
======================================================================
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
JavaScript SDK (browser, Node, Deno, Bun and React Native) for the [iglo.base](https://github.com/iglo-tech/iglo.base) API.
|
|
5
|
+
|
|
6
|
+
This is a fork of the [PocketBase JavaScript SDK](https://github.com/pocketbase/js-sdk) v0.28.1 (MIT), maintained with iglo.base. It keeps the `pocketbase` package's API (same default export and named exports) and works with PocketBase v0.41.0 too, but ships ES modules only (no `./cjs`, `./umd` or script-tag build).
|
|
7
|
+
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Usage](#usage)
|
|
10
|
+
- [Caveats](#caveats)
|
|
11
|
+
- [Binding filter parameters](#binding-filter-parameters)
|
|
12
|
+
- [File upload](#file-upload)
|
|
13
|
+
- [Error handling](#error-handling)
|
|
14
|
+
- [Auth store](#auth-store)
|
|
15
|
+
- [LocalAuthStore (default)](#localauthstore-default)
|
|
16
|
+
- [AsyncAuthStore (_usually used with React Native_)](#asyncauthstore)
|
|
17
|
+
- [Custom auth store](#custom-auth-store)
|
|
18
|
+
- [Common auth store fields and methods](#common-auth-store-fields-and-methods)
|
|
19
|
+
- [Auto cancellation](#auto-cancellation)
|
|
20
|
+
- [Specify TypeScript definitions](#specify-typescript-definitions)
|
|
21
|
+
- [Custom request options](#custom-request-options)
|
|
22
|
+
- [Send hooks](#send-hooks)
|
|
23
|
+
- [Server-side rendering](#server-side-rendering)
|
|
24
|
+
- [Security](#security)
|
|
25
|
+
- [Definitions](#definitions)
|
|
26
|
+
- [Development](#development)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
### Browser (ES modules)
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<script type="module">
|
|
35
|
+
import PocketBase from '/path/to/dist/iglo-base.es.mjs'
|
|
36
|
+
|
|
37
|
+
const pb = new PocketBase("https://example.com")
|
|
38
|
+
...
|
|
39
|
+
</script>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Node.js / Bun (via npm)
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
npm install @iglo.tech/iglo.base --save
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
import PocketBase from '@iglo.tech/iglo.base'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
> 🔧 For **Node < 17** you'll need to load a `fetch()` polyfill.
|
|
53
|
+
> I recommend [lquixada/cross-fetch](https://github.com/lquixada/cross-fetch):
|
|
54
|
+
> ```js
|
|
55
|
+
> // npm install cross-fetch --save
|
|
56
|
+
> import 'cross-fetch/polyfill';
|
|
57
|
+
> ```
|
|
58
|
+
---
|
|
59
|
+
> 🔧 Node doesn't have native `EventSource` implementation, so in order to use the realtime subscriptions you'll need to load a `EventSource` polyfill.
|
|
60
|
+
> ```js
|
|
61
|
+
> // for server: npm install eventsource --save
|
|
62
|
+
> import { EventSource } from "eventsource";
|
|
63
|
+
>
|
|
64
|
+
> // for React Native: npm install react-native-sse --save
|
|
65
|
+
> import EventSource from "react-native-sse";
|
|
66
|
+
>
|
|
67
|
+
> global.EventSource = EventSource;
|
|
68
|
+
> ```
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
import PocketBase from '@iglo.tech/iglo.base';
|
|
75
|
+
|
|
76
|
+
const pb = new PocketBase('http://127.0.0.1:8090');
|
|
77
|
+
|
|
78
|
+
...
|
|
79
|
+
|
|
80
|
+
// authenticate as auth collection record
|
|
81
|
+
const userData = await pb.collection('users').authWithPassword('test@example.com', '123456');
|
|
82
|
+
|
|
83
|
+
// list and filter "example" collection records
|
|
84
|
+
const result = await pb.collection('example').getList(1, 20, {
|
|
85
|
+
filter: 'status = true && created > "2022-08-01 10:00:00"'
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// and much more...
|
|
89
|
+
```
|
|
90
|
+
> More detailed API docs and copy-paste examples could be found in the [API documentation for each service](https://pocketbase.io/docs/api-records/).
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
## Caveats
|
|
94
|
+
|
|
95
|
+
### Binding filter parameters
|
|
96
|
+
|
|
97
|
+
The SDK comes with a helper `pb.filter(expr, params)` method to generate a filter string with placeholder parameters (`{:paramName}`) populated from an object.
|
|
98
|
+
|
|
99
|
+
**This method is also recommended when using the SDK in Node/Deno/Bun server-side list queries and accepting untrusted user input as `filter` string arguments, because it will take care to properly escape the generated string expression, avoiding eventual string injection attacks** (_on the client-side this is not much of an issue_).
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
const records = await pb.collection("example").getList(1, 20, {
|
|
103
|
+
// the same as: "title ~ 'te\\'st' && (totalA = 123 || totalB = 123)"
|
|
104
|
+
filter: pb.filter("title ~ {:title} && (totalA = {:num} || totalB = {:num})", { title: "te'st", num: 123 })
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The supported placeholder parameter values are:
|
|
109
|
+
|
|
110
|
+
- `string`
|
|
111
|
+
- `number`
|
|
112
|
+
- `boolean`
|
|
113
|
+
- `null`
|
|
114
|
+
- `undefined` (stringified as `null`)
|
|
115
|
+
- `Date` object (stringified into the format expected by PocketBase)
|
|
116
|
+
- everything else is converted to a string using `JSON.stringify()`
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
### File upload
|
|
120
|
+
|
|
121
|
+
PocketBase Web API supports file upload via `multipart/form-data` requests,
|
|
122
|
+
which means that to upload a file it is enough to provide either a [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) instance OR plain object with `File`/`Blob` prop values.
|
|
123
|
+
|
|
124
|
+
- Using plain object as body _(this is the same as above and it will be converted to `FormData` behind the scenes)_:
|
|
125
|
+
```js
|
|
126
|
+
const data = {
|
|
127
|
+
'title': 'lorem ipsum...',
|
|
128
|
+
'document': new File(...),
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
await pb.collection('example').create(data);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
- Using `FormData` as body:
|
|
135
|
+
```js
|
|
136
|
+
// the standard way to create multipart/form-data body
|
|
137
|
+
const data = new FormData();
|
|
138
|
+
data.set('title', 'lorem ipsum...')
|
|
139
|
+
data.set('document', new File(...))
|
|
140
|
+
|
|
141
|
+
await pb.collection('example').create(data);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Error handling
|
|
145
|
+
|
|
146
|
+
All services return a standard [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)-based response, so the error handling is straightforward:
|
|
147
|
+
```js
|
|
148
|
+
pb.collection('example').getList(1, 50).then((result) => {
|
|
149
|
+
// success...
|
|
150
|
+
console.log('Result:', result);
|
|
151
|
+
}).catch((error) => {
|
|
152
|
+
// error...
|
|
153
|
+
console.log('Error:', error);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// OR if you are using the async/await syntax:
|
|
157
|
+
try {
|
|
158
|
+
const result = await pb.collection('example').getList(1, 50);
|
|
159
|
+
console.log('Result:', result);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.log('Error:', error);
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The response error is normalized and always returned as `ClientResponseError` object with the following public fields that you could use:
|
|
166
|
+
```js
|
|
167
|
+
ClientResponseError {
|
|
168
|
+
url: string, // requested url
|
|
169
|
+
status: number, // response status code
|
|
170
|
+
response: { ... }, // the API JSON error response
|
|
171
|
+
isAbort: boolean, // is abort/cancellation error
|
|
172
|
+
originalError: Error|null, // the original non-normalized error
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Auth store
|
|
177
|
+
|
|
178
|
+
The SDK keeps track of the authenticated token and auth model for you via the `pb.authStore` instance.
|
|
179
|
+
|
|
180
|
+
##### LocalAuthStore (default)
|
|
181
|
+
|
|
182
|
+
The default [`LocalAuthStore`](https://github.com/iglo-tech/iglo.base/blob/main/sdk/src/stores/LocalAuthStore.ts) uses the browser's `LocalStorage` if available, otherwise - will fallback to runtime/memory (aka. on page refresh or service restart you'll have to authenticate again).
|
|
183
|
+
|
|
184
|
+
Conveniently, the default store also takes care to automatically sync the auth store state between multiple tabs.
|
|
185
|
+
|
|
186
|
+
> _**NB!** Deno also supports `LocalStorage` but keep in mind that, unlike in browsers where the client is the only user, by default Deno `LocalStorage` will be shared by all clients making requests to your server!_
|
|
187
|
+
|
|
188
|
+
##### AsyncAuthStore
|
|
189
|
+
|
|
190
|
+
The SDK comes also with a helper [`AsyncAuthStore`](https://github.com/iglo-tech/iglo.base/blob/main/sdk/src/stores/AsyncAuthStore.ts) that you can use to integrate with any 3rd party async storage implementation (_usually this is needed when working with React Native_):
|
|
191
|
+
```js
|
|
192
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
193
|
+
import PocketBase, { AsyncAuthStore } from '@iglo.tech/iglo.base';
|
|
194
|
+
|
|
195
|
+
const store = new AsyncAuthStore({
|
|
196
|
+
save: async (serialized) => AsyncStorage.setItem('pb_auth', serialized),
|
|
197
|
+
initial: AsyncStorage.getItem('pb_auth'),
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const pb = new PocketBase('http://127.0.0.1:8090', store)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
##### Custom auth store
|
|
204
|
+
|
|
205
|
+
In some situations it could be easier to create your own custom auth store. For this you can extend [`BaseAuthStore`](https://github.com/iglo-tech/iglo.base/blob/main/sdk/src/stores/BaseAuthStore.ts) and pass the new custom instance as constructor argument to the client:
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
import PocketBase, { BaseAuthStore } from '@iglo.tech/iglo.base';
|
|
209
|
+
|
|
210
|
+
class CustomAuthStore extends BaseAuthStore {
|
|
211
|
+
save(token, model) {
|
|
212
|
+
super.save(token, model);
|
|
213
|
+
|
|
214
|
+
// your custom business logic...
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const pb = new PocketBase('http://127.0.0.1:8090', new CustomAuthStore());
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
##### Common auth store fields and methods
|
|
222
|
+
|
|
223
|
+
The default `pb.authStore` extends [`BaseAuthStore`](https://github.com/iglo-tech/iglo.base/blob/main/sdk/src/stores/BaseAuthStore.ts) and has the following public members that you can use:
|
|
224
|
+
|
|
225
|
+
```js
|
|
226
|
+
BaseAuthStore {
|
|
227
|
+
// base fields
|
|
228
|
+
record: RecordModel|null // the authenticated auth record
|
|
229
|
+
token: string // the authenticated token
|
|
230
|
+
isValid: boolean // checks if the store has existing and unexpired token
|
|
231
|
+
isSuperuser: boolean // checks if the store state is for superuser
|
|
232
|
+
|
|
233
|
+
// main methods
|
|
234
|
+
clear() // "logout" the authenticated record
|
|
235
|
+
save(token, record) // update the store with the new auth data
|
|
236
|
+
onChange(callback, fireImmediately = false) // register a callback that will be called on store change
|
|
237
|
+
|
|
238
|
+
// cookie parse and serialize helpers
|
|
239
|
+
loadFromCookie(cookieHeader, key = 'pb_auth')
|
|
240
|
+
exportToCookie(options = {}, key = 'pb_auth')
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
To _"logout"_ the authenticated record you can call `pb.authStore.clear()`.
|
|
245
|
+
|
|
246
|
+
To _"listen"_ for changes in the auth store, you can register a new listener via `pb.authStore.onChange`, eg:
|
|
247
|
+
```js
|
|
248
|
+
// triggered everytime on store change
|
|
249
|
+
const removeListener1 = pb.authStore.onChange((token, record) => {
|
|
250
|
+
console.log('New store data 1:', token, record)
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// triggered once right after registration and everytime on store change
|
|
254
|
+
const removeListener2 = pb.authStore.onChange((token, record) => {
|
|
255
|
+
console.log('New store data 2:', token, record)
|
|
256
|
+
}, true);
|
|
257
|
+
|
|
258
|
+
// (optional) removes the attached listeners
|
|
259
|
+
removeListener1();
|
|
260
|
+
removeListener2();
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
### Auto cancellation
|
|
265
|
+
|
|
266
|
+
The SDK client will auto cancel duplicated pending requests for you.
|
|
267
|
+
For example, if you have the following 3 duplicated endpoint calls, only the last one will be executed, while the first 2 will be cancelled with `ClientResponseError` error:
|
|
268
|
+
|
|
269
|
+
```js
|
|
270
|
+
pb.collection('example').getList(1, 20) // cancelled
|
|
271
|
+
pb.collection('example').getList(2, 20) // cancelled
|
|
272
|
+
pb.collection('example').getList(3, 20) // executed
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
To change this behavior per request basis, you can adjust the `requestKey: null|string` special query parameter.
|
|
276
|
+
Set it to `null` to unset the default request identifier and to disable auto cancellation for the specific request.
|
|
277
|
+
Or set it to a unique string that will be used as request identifier and based on which pending requests will be matched (default to `HTTP_METHOD + path`, eg. "GET /api/users")
|
|
278
|
+
|
|
279
|
+
If you want to globally disable the auto cancellation behavior, you could set `pb.autoCancellation(false)`.
|
|
280
|
+
|
|
281
|
+
Examples:
|
|
282
|
+
|
|
283
|
+
```js
|
|
284
|
+
pb.collection('example').getList(1, 20); // cancelled
|
|
285
|
+
pb.collection('example').getList(1, 20); // executed
|
|
286
|
+
pb.collection('example').getList(1, 20, { requestKey: "test" }) // cancelled
|
|
287
|
+
pb.collection('example').getList(1, 20, { requestKey: "test" }) // executed
|
|
288
|
+
pb.collection('example').getList(1, 20, { requestKey: null }) // executed
|
|
289
|
+
pb.collection('example').getList(1, 20, { requestKey: null }) // executed
|
|
290
|
+
|
|
291
|
+
// globally disable auto cancellation
|
|
292
|
+
pb.autoCancellation(false);
|
|
293
|
+
|
|
294
|
+
pb.collection('example').getList(1, 20); // executed
|
|
295
|
+
pb.collection('example').getList(1, 20); // executed
|
|
296
|
+
pb.collection('example').getList(1, 20); // executed
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
If you want to manually cancel pending requests, you could use `pb.cancelAllRequests()` or `pb.cancelRequest(requestKey)`.
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
### Specify TypeScript definitions
|
|
303
|
+
|
|
304
|
+
You could specify custom TypeScript definitions for your Record models using generics:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
interface Task {
|
|
308
|
+
// type the collection fields you want to use...
|
|
309
|
+
id: string;
|
|
310
|
+
name: string;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
pb.collection('tasks').getList<Task>(1, 20) // -> results in Promise<ListResult<Task>>
|
|
314
|
+
pb.collection('tasks').getOne<Task>("RECORD_ID") // -> results in Promise<Task>
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Alternatively, if you don't want to type the generic argument every time you can define a global PocketBase type using type assertion:
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
interface Task {
|
|
321
|
+
id: string;
|
|
322
|
+
name: string;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
interface Post {
|
|
326
|
+
id: string;
|
|
327
|
+
title: string;
|
|
328
|
+
active: boolean;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
interface TypedPocketBase extends PocketBase {
|
|
332
|
+
collection(idOrName: string): RecordService // default fallback for any other collection
|
|
333
|
+
collection(idOrName: 'tasks'): RecordService<Task>
|
|
334
|
+
collection(idOrName: 'posts'): RecordService<Post>
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
...
|
|
338
|
+
|
|
339
|
+
const pb = new PocketBase("http://127.0.0.1:8090") as TypedPocketBase;
|
|
340
|
+
|
|
341
|
+
pb.collection('tasks').getOne("RECORD_ID") // -> results in Promise<Task>
|
|
342
|
+
pb.collection('posts').getOne("RECORD_ID") // -> results in Promise<Post>
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
### Custom request options
|
|
347
|
+
|
|
348
|
+
All API services accept an optional `options` argument (usually the last one and of type [`SendOptions`](https://github.com/iglo-tech/iglo.base/blob/main/sdk/src/tools/options.ts)), that can be used to provide:
|
|
349
|
+
|
|
350
|
+
- custom headers for a single request
|
|
351
|
+
- custom fetch options
|
|
352
|
+
- or even your own `fetch` implementation
|
|
353
|
+
|
|
354
|
+
For example:
|
|
355
|
+
|
|
356
|
+
```js
|
|
357
|
+
pb.collection('example').getList(1, 20, {
|
|
358
|
+
expand: 'someRel',
|
|
359
|
+
otherQueryParam: '123',
|
|
360
|
+
|
|
361
|
+
// custom headers
|
|
362
|
+
headers: {
|
|
363
|
+
'X-Custom-Header': 'example',
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
// custom fetch options
|
|
367
|
+
keepalive: false,
|
|
368
|
+
cache: 'no-store',
|
|
369
|
+
|
|
370
|
+
// or custom fetch implementation
|
|
371
|
+
fetch: async (url, config) => { ... },
|
|
372
|
+
})
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
_Note that for backward compatability and to minimize the verbosity, any "unknown" top-level field will be treated as query parameter._
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
### Send hooks
|
|
379
|
+
|
|
380
|
+
Sometimes you may want to modify the request data globally or to customize the response.
|
|
381
|
+
|
|
382
|
+
To accomplish this, the SDK provides 2 function hooks:
|
|
383
|
+
|
|
384
|
+
- `beforeSend` - triggered right before sending the `fetch` request, allowing you to inspect/modify the request config.
|
|
385
|
+
```js
|
|
386
|
+
const pb = new PocketBase('http://127.0.0.1:8090');
|
|
387
|
+
|
|
388
|
+
pb.beforeSend = function (url, options) {
|
|
389
|
+
// For list of the possible request options properties check
|
|
390
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/fetch#options
|
|
391
|
+
options.headers = Object.assign({}, options.headers, {
|
|
392
|
+
'X-Custom-Header': 'example',
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
return { url, options };
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// use the created client as usual...
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
- `afterSend` - triggered after successfully sending the `fetch` request, allowing you to inspect/modify the response object and its parsed data.
|
|
402
|
+
```js
|
|
403
|
+
const pb = new PocketBase('http://127.0.0.1:8090');
|
|
404
|
+
|
|
405
|
+
pb.afterSend = function (response, data) {
|
|
406
|
+
// do something with the response state
|
|
407
|
+
console.log(response.status);
|
|
408
|
+
|
|
409
|
+
return Object.assign(data, {
|
|
410
|
+
// extend the data...
|
|
411
|
+
"additionalField": 123,
|
|
412
|
+
});
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// use the created client as usual...
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Server-side rendering
|
|
419
|
+
|
|
420
|
+
`@iglo.tech/iglo.base/ssr` lets your server render pages for signed-in users. Each request gets its own client, the session lives in an HttpOnly cookie, and the browser receives only a short token for realtime. The SDK also provides the browser entry `@iglo.tech/iglo.base/ssr/browser` (`createBrowserClient`), which is safe to bundle for the browser.
|
|
421
|
+
|
|
422
|
+
`@iglo.tech/iglo.base/ssr` is server-only: import it only in server code. Its exports include `createServerClient`, `createSuperuserClient`, `createSessionCache`, `createRequestCookieAdapter`, `mintBrowserToken`, `handleBrowserTokenRenew`, `handlePopupHandOff`, `checkCsrf`, `checkOrigin` and `SSR_NO_CACHE_HEADERS`. The framework adapters are `@iglo.tech/iglo.base/ssr/next`, `/sveltekit`, `/nuxt`, `/astro` and `/react-router`.
|
|
423
|
+
|
|
424
|
+
`createSessionCache({ ttl, maxEntries })` takes two optional options. `ttl` is in milliseconds (default 300000, five minutes), and `maxEntries` defaults to 10000. A session served from the cache can stay signed in for up to `ttl` after iglo.base revokes it; the guide's [session cache](https://github.com/iglo-tech/iglo.base/blob/main/docs/ssr.md#the-session-cache) section covers the trade-off.
|
|
425
|
+
|
|
426
|
+
For a provider that returns by POST, such as Sign in with Apple, use the popup flow (`authWithOAuth2()` in the browser, then `handOff()`). Register `<PB_PUBLIC_URL>/api/oauth2-redirect` with that provider, not the app's callback URL; the guide's [OAuth2 providers](https://github.com/iglo-tech/iglo.base/blob/main/docs/ssr.md#oauth2-providers) section explains both flows.
|
|
427
|
+
|
|
428
|
+
For setup, the framework sections, caching headers, security notes and troubleshooting, read the **[server-side rendering guide](https://github.com/iglo-tech/iglo.base/blob/main/docs/ssr.md)**. The example apps are in [`examples/nextjs`](https://github.com/iglo-tech/iglo.base/tree/main/examples/nextjs) and [`examples/sveltekit`](https://github.com/iglo-tech/iglo.base/tree/main/examples/sveltekit).
|
|
429
|
+
|
|
430
|
+
### Security
|
|
431
|
+
|
|
432
|
+
The most common frontend related vulnerability is XSS (and CSRF when dealing with cookies).
|
|
433
|
+
Fortunately, modern browsers can detect and mitigate most of this type of attacks if [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) is provided.
|
|
434
|
+
|
|
435
|
+
**To prevent a malicious user or 3rd party script to steal your PocketBase auth token, it is recommended to configure a basic CSP for your application (either as `meta` tag or HTTP header).**
|
|
436
|
+
|
|
437
|
+
This is out of the scope of the SDK, but you could find more resources about CSP at:
|
|
438
|
+
|
|
439
|
+
- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
|
|
440
|
+
- https://content-security-policy.com
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
**Depending on how and where you use the JS SDK, it is also recommended to use the helper `pb.filter(expr, params)` when constructing filter strings with untrusted user input to avoid eventual string injection attacks (see [Binding filter parameters](#binding-filter-parameters)).**
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
## Definitions
|
|
447
|
+
|
|
448
|
+
### Creating new client instance
|
|
449
|
+
|
|
450
|
+
```js
|
|
451
|
+
const pb = new PocketBase(baseURL = '/', authStore = LocalAuthStore);
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Instance methods
|
|
455
|
+
|
|
456
|
+
> Each instance method returns the `PocketBase` instance allowing chaining.
|
|
457
|
+
|
|
458
|
+
| Method | Description |
|
|
459
|
+
|:----------------------------------|:------------------------------------------------------------------------------|
|
|
460
|
+
| `pb.send(path, sendOptions = {})` | Sends an api http request. |
|
|
461
|
+
| `pb.autoCancellation(enable)` | Globally enable or disable auto cancellation for pending duplicated requests. |
|
|
462
|
+
| `pb.cancelAllRequests()` | Cancels all pending requests. |
|
|
463
|
+
| `pb.cancelRequest(cancelKey)` | Cancels single request by its cancellation token key. |
|
|
464
|
+
| `pb.buildURL(path)` | Builds a full client url by safely concatenating the provided path. |
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
### Services
|
|
468
|
+
|
|
469
|
+
> Each service call returns a `Promise` object with the API response.
|
|
470
|
+
|
|
471
|
+
##### RecordService
|
|
472
|
+
|
|
473
|
+
###### _Crud handlers_
|
|
474
|
+
|
|
475
|
+
```js
|
|
476
|
+
// Returns a paginated records list.
|
|
477
|
+
🔓 pb.collection(collectionIdOrName).getList(page = 1, perPage = 30, options = {});
|
|
478
|
+
|
|
479
|
+
// Returns a list with all records batch fetched at once
|
|
480
|
+
// (by default 1000 items per request; to change it set the `batch` param).
|
|
481
|
+
🔓 pb.collection(collectionIdOrName).getFullList(options = {});
|
|
482
|
+
|
|
483
|
+
// Returns the first found record matching the specified filter.
|
|
484
|
+
🔓 pb.collection(collectionIdOrName).getFirstListItem(filter, options = {});
|
|
485
|
+
|
|
486
|
+
// Returns a single record by its id.
|
|
487
|
+
🔓 pb.collection(collectionIdOrName).getOne(recordId, options = {});
|
|
488
|
+
|
|
489
|
+
// Creates (aka. register) a new record.
|
|
490
|
+
🔓 pb.collection(collectionIdOrName).create(bodyParams = {}, options = {});
|
|
491
|
+
|
|
492
|
+
// Updates an existing record by its id.
|
|
493
|
+
🔓 pb.collection(collectionIdOrName).update(recordId, bodyParams = {}, options = {});
|
|
494
|
+
|
|
495
|
+
// Deletes a single record by its id.
|
|
496
|
+
🔓 pb.collection(collectionIdOrName).delete(recordId, options = {});
|
|
497
|
+
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
###### _Realtime handlers_
|
|
501
|
+
|
|
502
|
+
```js
|
|
503
|
+
// Subscribe to realtime changes to the specified topic ("*" or recordId).
|
|
504
|
+
//
|
|
505
|
+
// It is safe to subscribe multiple times to the same topic.
|
|
506
|
+
//
|
|
507
|
+
// You can use the returned UnsubscribeFunc to remove a single registered subscription.
|
|
508
|
+
// If you want to remove all subscriptions related to the topic use unsubscribe(topic).
|
|
509
|
+
🔓 pb.collection(collectionIdOrName).subscribe(topic, callback, options = {});
|
|
510
|
+
|
|
511
|
+
// Unsubscribe from all registered subscriptions to the specified topic ("*" or recordId).
|
|
512
|
+
// If topic is not set, then it will remove all registered collection subscriptions.
|
|
513
|
+
🔓 pb.collection(collectionIdOrName).unsubscribe([topic]);
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
###### _Auth handlers_
|
|
517
|
+
|
|
518
|
+
> Available only for "auth" type collections.
|
|
519
|
+
|
|
520
|
+
```js
|
|
521
|
+
// Returns all available application auth methods.
|
|
522
|
+
🔓 pb.collection(collectionIdOrName).listAuthMethods(options = {});
|
|
523
|
+
|
|
524
|
+
// Authenticates a record with their username/email and password.
|
|
525
|
+
🔓 pb.collection(collectionIdOrName).authWithPassword(usernameOrEmail, password, options = {});
|
|
526
|
+
|
|
527
|
+
// Authenticates a record with an OTP.
|
|
528
|
+
🔓 pb.collection(collectionIdOrName).authWithOTP(otpId, password, options = {});
|
|
529
|
+
|
|
530
|
+
// Authenticates a record with OAuth2 provider without custom redirects, deeplinks or even page reload.
|
|
531
|
+
🔓 pb.collection(collectionIdOrName).authWithOAuth2(authConfig);
|
|
532
|
+
|
|
533
|
+
// Authenticates a record with OAuth2 code.
|
|
534
|
+
🔓 pb.collection(collectionIdOrName).authWithOAuth2Code(provider, code, codeVerifier, redirectUrl, createData = {}, options = {});
|
|
535
|
+
|
|
536
|
+
// Refreshes the current authenticated record and auth token.
|
|
537
|
+
🔐 pb.collection(collectionIdOrName).authRefresh(options = {});
|
|
538
|
+
|
|
539
|
+
// Sends a record OTP email request.
|
|
540
|
+
🔓 pb.collection(collectionIdOrName).requestOTP(email, options = {});
|
|
541
|
+
|
|
542
|
+
// Sends a record password reset email.
|
|
543
|
+
🔓 pb.collection(collectionIdOrName).requestPasswordReset(email, options = {});
|
|
544
|
+
|
|
545
|
+
// Confirms a record password reset request.
|
|
546
|
+
🔓 pb.collection(collectionIdOrName).confirmPasswordReset(resetToken, newPassword, newPasswordConfirm, options = {});
|
|
547
|
+
|
|
548
|
+
// Sends a record verification email request.
|
|
549
|
+
🔓 pb.collection(collectionIdOrName).requestVerification(email, options = {});
|
|
550
|
+
|
|
551
|
+
// Confirms a record email verification request.
|
|
552
|
+
🔓 pb.collection(collectionIdOrName).confirmVerification(verificationToken, options = {});
|
|
553
|
+
|
|
554
|
+
// Sends a record email change request to the provider email.
|
|
555
|
+
🔐 pb.collection(collectionIdOrName).requestEmailChange(newEmail, options = {});
|
|
556
|
+
|
|
557
|
+
// Confirms record new email address.
|
|
558
|
+
🔓 pb.collection(collectionIdOrName).confirmEmailChange(emailChangeToken, userPassword, options = {});
|
|
559
|
+
|
|
560
|
+
// Lists all linked external auth providers for the specified record.
|
|
561
|
+
🔐 pb.collection(collectionIdOrName).listExternalAuths(recordId, options = {});
|
|
562
|
+
|
|
563
|
+
// Unlinks a single external auth provider relation from the specified record.
|
|
564
|
+
🔐 pb.collection(collectionIdOrName).unlinkExternalAuth(recordId, provider, options = {});
|
|
565
|
+
|
|
566
|
+
// Impersonate authenticates with the specified recordId and returns a new client with the received auth token in a memory store.
|
|
567
|
+
🔐 pb.collection(collectionIdOrName).impersonate(recordId, duration, options = {});
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
#### BatchService
|
|
573
|
+
|
|
574
|
+
```js
|
|
575
|
+
// create a new batch instance
|
|
576
|
+
const batch = pb.createBatch();
|
|
577
|
+
|
|
578
|
+
// register create/update/delete/upsert requests to the created batch
|
|
579
|
+
batch.collection('example1').create({ ... });
|
|
580
|
+
batch.collection('example2').update('RECORD_ID', { ... });
|
|
581
|
+
batch.collection('example3').delete('RECORD_ID');
|
|
582
|
+
batch.collection('example4').upsert({ ... });
|
|
583
|
+
|
|
584
|
+
// send the batch request
|
|
585
|
+
const result = await batch.send()
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
##### FileService
|
|
591
|
+
|
|
592
|
+
```js
|
|
593
|
+
// Builds and returns an absolute record file url for the provided filename.
|
|
594
|
+
🔓 pb.files.getURL(record, filename, options = {});
|
|
595
|
+
|
|
596
|
+
// Requests a new private file access token for the current authenticated record.
|
|
597
|
+
🔐 pb.files.getToken(options = {});
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
##### CollectionService
|
|
603
|
+
|
|
604
|
+
```js
|
|
605
|
+
// Returns a paginated collections list.
|
|
606
|
+
🔐 pb.collections.getList(page = 1, perPage = 30, options = {});
|
|
607
|
+
|
|
608
|
+
// Returns a list with all collections batch fetched at once
|
|
609
|
+
// (by default 200 items per request; to change it set the `batch` query param).
|
|
610
|
+
🔐 pb.collections.getFullList(options = {});
|
|
611
|
+
|
|
612
|
+
// Returns the first found collection matching the specified filter.
|
|
613
|
+
🔐 pb.collections.getFirstListItem(filter, options = {});
|
|
614
|
+
|
|
615
|
+
// Returns a single collection by its id or name.
|
|
616
|
+
🔐 pb.collections.getOne(idOrName, options = {});
|
|
617
|
+
|
|
618
|
+
// Creates (aka. register) a new collection.
|
|
619
|
+
🔐 pb.collections.create(bodyParams = {}, options = {});
|
|
620
|
+
|
|
621
|
+
// Updates an existing collection by its id or name.
|
|
622
|
+
🔐 pb.collections.update(idOrName, bodyParams = {}, options = {});
|
|
623
|
+
|
|
624
|
+
// Deletes a single collection by its id or name.
|
|
625
|
+
🔐 pb.collections.delete(idOrName, options = {});
|
|
626
|
+
|
|
627
|
+
// Deletes all records associated with the specified collection.
|
|
628
|
+
🔐 pb.collections.truncate(idOrName, options = {});
|
|
629
|
+
|
|
630
|
+
// Imports the provided collections.
|
|
631
|
+
🔐 pb.collections.import(collections, deleteMissing = false, options = {});
|
|
632
|
+
|
|
633
|
+
// Returns type indexed map with scaffolded collection models populated with their default field values.
|
|
634
|
+
🔐 pb.collections.getScaffolds(options = {});
|
|
635
|
+
|
|
636
|
+
// Returns a list with all configurable OAuth2 providers.
|
|
637
|
+
🔐 pb.collections.getAllOAuth2Providers(options = {});
|
|
638
|
+
|
|
639
|
+
// Tests the specified view query and returns a sample of the resulting records.
|
|
640
|
+
🔐 pb.collections.dryRunViewQuery(query, options = {});
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
##### LogService
|
|
646
|
+
|
|
647
|
+
```js
|
|
648
|
+
// Returns a paginated logs list.
|
|
649
|
+
🔐 pb.logs.getList(page = 1, perPage = 30, options = {});
|
|
650
|
+
|
|
651
|
+
// Returns a single log by its id.
|
|
652
|
+
🔐 pb.logs.getOne(id, options = {});
|
|
653
|
+
|
|
654
|
+
// Returns logs statistics.
|
|
655
|
+
🔐 pb.logs.getStats(options = {});
|
|
656
|
+
|
|
657
|
+
// Delete all logs.
|
|
658
|
+
🔐 pb.logs.truncate(options = {});
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
##### SettingsService
|
|
664
|
+
|
|
665
|
+
```js
|
|
666
|
+
// Returns a map with all available app settings.
|
|
667
|
+
🔐 pb.settings.getAll(options = {});
|
|
668
|
+
|
|
669
|
+
// Bulk updates app settings.
|
|
670
|
+
🔐 pb.settings.update(bodyParams = {}, options = {});
|
|
671
|
+
|
|
672
|
+
// Performs a S3 storage connection test.
|
|
673
|
+
🔐 pb.settings.testS3(filesystem = "storage", options = {});
|
|
674
|
+
|
|
675
|
+
// Sends a test email (verification, password-reset, email-change).
|
|
676
|
+
🔐 pb.settings.testEmail(collectionIdOrName, toEmail, template, options = {});
|
|
677
|
+
|
|
678
|
+
// Generates a new Apple OAuth2 client secret.
|
|
679
|
+
🔐 pb.settings.generateAppleClientSecret(clientId, teamId, keyId, privateKey, duration, options = {});
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
---
|
|
683
|
+
|
|
684
|
+
##### RealtimeService
|
|
685
|
+
|
|
686
|
+
> This service is usually used with custom realtime actions.
|
|
687
|
+
> For records realtime subscriptions you can use the subscribe/unsubscribe
|
|
688
|
+
> methods available in the `pb.collection()` RecordService.
|
|
689
|
+
|
|
690
|
+
```js
|
|
691
|
+
// Initialize the realtime connection (if not already) and register the subscription listener.
|
|
692
|
+
//
|
|
693
|
+
// You can subscribe to the `PB_CONNECT` event if you want to listen to the realtime connection connect/reconnect events.
|
|
694
|
+
🔓 pb.realtime.subscribe(topic, callback, options = {});
|
|
695
|
+
|
|
696
|
+
// Unsubscribe from all subscription listeners with the specified topic.
|
|
697
|
+
🔓 pb.realtime.unsubscribe(topic?);
|
|
698
|
+
|
|
699
|
+
// Unsubscribe from all subscription listeners starting with the specified topic prefix.
|
|
700
|
+
🔓 pb.realtime.unsubscribeByPrefix(topicPrefix);
|
|
701
|
+
|
|
702
|
+
// Unsubscribe from all subscriptions matching the specified topic and listener function.
|
|
703
|
+
🔓 pb.realtime.unsubscribeByTopicAndListener(topic, callback);
|
|
704
|
+
|
|
705
|
+
// Getter that checks whether the realtime connection has been established.
|
|
706
|
+
pb.realtime.isConnected
|
|
707
|
+
|
|
708
|
+
// An optional hook that is invoked when the realtime client disconnects
|
|
709
|
+
// either when unsubscribing from all subscriptions or when the connection
|
|
710
|
+
// was interrupted or closed by the server.
|
|
711
|
+
//
|
|
712
|
+
// Note that the realtime client autoreconnect on its own and this hook is
|
|
713
|
+
// useful only for the cases where you want to apply a special behavior on
|
|
714
|
+
// server error or after closing the realtime connection.
|
|
715
|
+
pb.realtime.onDisconnect = function(activeSubscriptions)
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
---
|
|
719
|
+
|
|
720
|
+
##### BackupService
|
|
721
|
+
|
|
722
|
+
```js
|
|
723
|
+
// Returns list with all available backup files.
|
|
724
|
+
🔐 pb.backups.getFullList(options = {});
|
|
725
|
+
|
|
726
|
+
// Initializes a new backup.
|
|
727
|
+
🔐 pb.backups.create(basename = "", options = {});
|
|
728
|
+
|
|
729
|
+
// Upload an existing app data backup.
|
|
730
|
+
🔐 pb.backups.upload({ file: File/Blob }, options = {});
|
|
731
|
+
|
|
732
|
+
// Deletes a single backup by its name.
|
|
733
|
+
🔐 pb.backups.delete(key, options = {});
|
|
734
|
+
|
|
735
|
+
// Initializes an app data restore from an existing backup.
|
|
736
|
+
🔐 pb.backups.restore(key, options = {});
|
|
737
|
+
|
|
738
|
+
// Builds a download url for a single existing backup using a
|
|
739
|
+
// superuser file token and the backup file key.
|
|
740
|
+
🔐 pb.backups.getDownloadURL(token, key);
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
##### CronService
|
|
744
|
+
|
|
745
|
+
```js
|
|
746
|
+
// Returns list with all available cron jobs.
|
|
747
|
+
🔐 pb.crons.getFullList(options = {});
|
|
748
|
+
|
|
749
|
+
// Runs the specified cron job.
|
|
750
|
+
🔐 pb.crons.run(jobId, options = {});
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
##### SQLService
|
|
754
|
+
|
|
755
|
+
```js
|
|
756
|
+
// Runs the specified raw SQL query.
|
|
757
|
+
🔐 pb.sql.run(query, options = {});
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
---
|
|
761
|
+
|
|
762
|
+
##### HealthService
|
|
763
|
+
|
|
764
|
+
```js
|
|
765
|
+
// Checks the health status of the api.
|
|
766
|
+
🔓 pb.health.check(options = {});
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
## Development
|
|
771
|
+
|
|
772
|
+
Run from the repository root of [iglo.base](https://github.com/iglo-tech/iglo.base):
|
|
773
|
+
|
|
774
|
+
```sh
|
|
775
|
+
bun install
|
|
776
|
+
bun test sdk # unit tests
|
|
777
|
+
bun run sdk:build # build into sdk/dist
|
|
778
|
+
bun run check # lint, format, typecheck, tests and build (server and SDK)
|
|
779
|
+
```
|