MCPcopy
hub / github.com/pocketbase/js-sdk

github.com/pocketbase/js-sdk @v0.27.0 sqlite

repository ↗ · DeepWiki ↗ · release v0.27.0 ↗
259 symbols 636 edges 47 files 115 documented · 44%
README

PocketBase JavaScript SDK

Official JavaScript SDK (browser and node) for interacting with the PocketBase API.

Installation

Browser (manually via script tag)

<script src="https://github.com/pocketbase/js-sdk/raw/v0.27.0/path/to/dist/pocketbase.umd.js"></script>
<script type="text/javascript">
    const pb = new PocketBase("https://example.com")
    ...
</script>

OR if you are using ES modules:

<script type="module">
    import PocketBase from '/path/to/dist/pocketbase.es.mjs'

    const pb = new PocketBase("https://example.com")
    ...
</script>

Node.js (via npm)

npm install pocketbase --save
// Using ES modules (default)
import PocketBase from 'pocketbase'

// OR if you are using CommonJS modules
const PocketBase = require('pocketbase/cjs')

🔧 For Node < 17 you'll need to load a fetch() polyfill. I recommend lquixada/cross-fetch: js // npm install cross-fetch --save import 'cross-fetch/polyfill';


🔧 Node doesn't have native EventSource implementation, so in order to use the realtime subscriptions you'll need to load a EventSource polyfill. ```js // for server: npm install eventsource --save import { EventSource } from "eventsource";

// for React Native: npm install react-native-sse --save import EventSource from "react-native-sse";

global.EventSource = EventSource; ```

Usage

import PocketBase from 'pocketbase';

const pb = new PocketBase('http://127.0.0.1:8090');

...

// authenticate as auth collection record
const userData = await pb.collection('users').authWithPassword('test@example.com', '123456');

// list and filter "example" collection records
const result = await pb.collection('example').getList(1, 20, {
    filter: 'status = true && created > "2022-08-01 10:00:00"'
});

// and much more...

More detailed API docs and copy-paste examples could be found in the API documentation for each service.

Caveats

Binding filter parameters

The SDK comes with a helper pb.filter(expr, params) method to generate a filter string with placeholder parameters ({:paramName}) populated from an object.

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).

const records = await pb.collection("example").getList(1, 20, {
  // the same as: "title ~ 'te\\'st' && (totalA = 123 || totalB = 123)"
  filter: pb.filter("title ~ {:title} && (totalA = {:num} || totalB = {:num})", { title: "te'st", num: 123 })
})

The supported placeholder parameter values are:

  • string (single quotes are autoescaped)
  • number
  • boolean
  • Date object (will be stringified into the format expected by PocketBase)
  • null
  • everything else is converted to a string using JSON.stringify()

File upload

PocketBase Web API supports file upload via multipart/form-data requests, which means that to upload a file it is enough to provide either a FormData instance OR plain object with File/Blob prop values.

  • Using plain object as body (this is the same as above and it will be converted to FormData behind the scenes): ```js const data = { 'title': 'lorem ipsum...', 'document': new File(...), };

    await pb.collection('example').create(data); ```

  • Using FormData as body: ```js // the standard way to create multipart/form-data body const data = new FormData(); data.set('title', 'lorem ipsum...') data.set('document', new File(...))

    await pb.collection('example').create(data); ```

Error handling

All services return a standard Promise-based response, so the error handling is straightforward:

pb.collection('example').getList(1, 50).then((result) => {
  // success...
  console.log('Result:', result);
}).catch((error) => {
  // error...
  console.log('Error:', error);
});

// OR if you are using the async/await syntax:
try {
  const result = await pb.collection('example').getList(1, 50);
  console.log('Result:', result);
} catch (error) {
  console.log('Error:', error);
}

The response error is normalized and always returned as ClientResponseError object with the following public fields that you could use:

ClientResponseError {
    url:           string,     // requested url
    status:        number,     // response status code
    response:      { ... },    // the API JSON error response
    isAbort:       boolean,    // is abort/cancellation error
    originalError: Error|null, // the original non-normalized error
}

Auth store

The SDK keeps track of the authenticated token and auth model for you via the pb.authStore instance.

LocalAuthStore (default)

The default LocalAuthStore 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).

Conveniently, the default store also takes care to automatically sync the auth store state between multiple tabs.

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!

AsyncAuthStore

The SDK comes also with a helper AsyncAuthStore that you can use to integrate with any 3rd party async storage implementation (usually this is needed when working with React Native):

import AsyncStorage from '@react-native-async-storage/async-storage';
import PocketBase, { AsyncAuthStore } from 'pocketbase';

const store = new AsyncAuthStore({
    save:    async (serialized) => AsyncStorage.setItem('pb_auth', serialized),
    initial: AsyncStorage.getItem('pb_auth'),
});

const pb = new PocketBase('http://127.0.0.1:8090', store)
Custom auth store

In some situations it could be easier to create your own custom auth store. For this you can extend BaseAuthStore and pass the new custom instance as constructor argument to the client:

import PocketBase, { BaseAuthStore } from 'pocketbase';

class CustomAuthStore extends BaseAuthStore {
    save(token, model) {
        super.save(token, model);

        // your custom business logic...
    }
}

const pb = new PocketBase('http://127.0.0.1:8090', new CustomAuthStore());
Common auth store fields and methods

The default pb.authStore extends BaseAuthStore and has the following public members that you can use:

BaseAuthStore {
    // base fields
    record:       RecordModel|null // the authenticated auth record
    token:        string  // the authenticated token
    isValid:      boolean // checks if the store has existing and unexpired token
    isSuperuser:  boolean // checks if the store state is for superuser

    // main methods
    clear()             // "logout" the authenticated record
    save(token, record) // update the store with the new auth data
    onChange(callback, fireImmediately = false) // register a callback that will be called on store change

    // cookie parse and serialize helpers
    loadFromCookie(cookieHeader, key = 'pb_auth')
    exportToCookie(options = {}, key = 'pb_auth')
}

To "logout" the authenticated record you can call pb.authStore.clear().

To "listen" for changes in the auth store, you can register a new listener via pb.authStore.onChange, eg:

// triggered everytime on store change
const removeListener1 = pb.authStore.onChange((token, record) => {
    console.log('New store data 1:', token, record)
});

// triggered once right after registration and everytime on store change
const removeListener2 = pb.authStore.onChange((token, record) => {
    console.log('New store data 2:', token, record)
}, true);

// (optional) removes the attached listeners
removeListener1();
removeListener2();

Auto cancellation

The SDK client will auto cancel duplicated pending requests for you. 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:

pb.collection('example').getList(1, 20) // cancelled
pb.collection('example').getList(2, 20) // cancelled
pb.collection('example').getList(3, 20) // executed

To change this behavior per request basis, you can adjust the requestKey: null|string special query parameter. Set it to null to unset the default request identifier and to disable auto cancellation for the specific request. 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")

If you want to globally disable the auto cancellation behavior, you could set pb.autoCancellation(false).

Examples:

pb.collection('example').getList(1, 20);                        // cancelled
pb.collection('example').getList(1, 20);                        // executed
pb.collection('example').getList(1, 20, { requestKey: "test" }) // cancelled
pb.collection('example').getList(1, 20, { requestKey: "test" }) // executed
pb.collection('example').getList(1, 20, { requestKey: null })   // executed
pb.collection('example').getList(1, 20, { requestKey: null })   // executed

// globally disable auto cancellation
pb.autoCancellation(false);

pb.collection('example').getList(1, 20); // executed
pb.collection('example').getList(1, 20); // executed
pb.collection('example').getList(1, 20); // executed

If you want to manually cancel pending requests, you could use pb.cancelAllRequests() or pb.cancelRequest(requestKey).

Specify TypeScript definitions

You could specify custom TypeScript definitions for your Record models using generics:

interface Task {
  // type the collection fields you want to use...
  id:   string;
  name: string;
}

pb.collection('tasks').getList<Task>(1, 20) // -> results in Promise<ListResult<Task>>
pb.collection('tasks').getOne<Task>("RECORD_ID")  // -> results in Promise<Task>

Alternatively, if you don't want to type the generic argument every time you can define a global PocketBase type using type assertion:

interface Task {
  id:   string;
  name: string;
}

interface Post {
  id:     string;
  title:  string;
  active: boolean;
}

interface TypedPocketBase extends PocketBase {
  collection(idOrName: string): RecordService // default fallback for any other collection
  collection(idOrName: 'tasks'): RecordService<Task>
  collection(idOrName: 'posts'): RecordService<Post>
}

...

const pb = new PocketBase("http://127.0.0.1:8090") as TypedPocketBase;

pb.collection('tasks').getOne("RECORD_ID") // -> results in Promise<Task>
pb.collection('posts').getOne("RECORD_ID") // -> results in Promise<Post>

Custom request options

All API services accept an optional options argument (usually the last one and of type SendOptions), that can be used to provide:

  • custom headers for a single request
  • custom fetch options
  • or even your own fetch implementation

For example:

pb.collection('example').getList(1, 20, {
    expand:          'someRel',
    otherQueryParam: '123',

    // custom headers
    headers: {
        'X-Custom-Header': 'example',
    },

    // custom fetch options
    keepalive: false,
    cache:     'no-store',

    // or custom fetch implementation
    fetch: async (url, config) => { ... },
})

Note that for backward compatability and to minimize the verbosity, any "unknown" top-level field will be treated as query parameter.

Send hooks

Sometimes you may want to modify the request data globally or to customize the response.

To accomplish this, the SDK provides 2 function hooks:

  • beforeSend - triggered right before sending the fetch request, allowing you to inspect/modify the request config. ```js const pb = new PocketBase('http://127.0.0.1:8090');

    pb.beforeSend = function (url, options) { // For list of the possible request options properties check // https://developer.mozilla.org/en-US/docs/Web/API/fetch#options options.headers = Object.assign({}, options.headers, { 'X-Custom-Header': 'example',

Extension points exported contracts — how you extend this code

BeforeSendResult (Interface)
(no doc)
src/Client.ts
SendOptions (Interface)
(no doc)
src/tools/options.ts
HourlyStats (Interface)
(no doc)
src/services/LogService.ts
CommonOptions (Interface)
(no doc)
src/tools/options.ts
SQLResult (Interface)
(no doc)
src/services/SQLService.ts
ListOptions (Interface)
(no doc)
src/tools/options.ts
promiseCallbacks (Interface)
(no doc)
src/services/RealtimeService.ts
FullListOptions (Interface)
(no doc)
src/tools/options.ts

Core symbols most depended-on inside this repo

on
called by 95
tests/mocks.ts
buildURL
called by 92
src/Client.ts
save
called by 44
src/stores/BaseAuthStore.ts
send
called by 41
src/services/BatchService.ts
send
called by 40
src/Client.ts
getAll
called by 14
src/services/SettingsService.ts
restore
called by 13
tests/mocks.ts
init
called by 12
tests/mocks.ts

Shape

Method 139
Interface 47
Class 38
Function 35

Languages

TypeScript100%

Modules by API surface

src/services/RecordService.ts41 symbols
src/services/RealtimeService.ts23 symbols
src/tools/dtos.ts18 symbols
src/Client.ts18 symbols
src/stores/BaseAuthStore.ts15 symbols
src/tools/options.ts14 symbols
src/services/BatchService.ts14 symbols
src/stores/LocalAuthStore.ts12 symbols
src/services/CrudService.ts11 symbols
src/services/BackupService.ts10 symbols
src/stores/AsyncAuthStore.ts8 symbols
src/services/SettingsService.ts8 symbols

Used by 2 indexed graphs manifest dependencies, hub-wide

Dependencies from manifests, versioned

@rollup/plugin-terser0.4.3 · 1×
prettier3.2.4 · 1×
rollup4.0.0 · 1×
rollup-plugin-ts3.0.0 · 1×
typescript5.1.6 · 1×
vitest2.0.0 · 1×

For agents

$ claude mcp add js-sdk \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact