搜尋套件、儲存庫與 APP 分類

套件詳情v0.25.3

pocketbase

適用於 PocketBase(一個單檔案開源即時後端)的多平台 Dart SDK。

AndroidiOSWebmacOSWindowsLinux
近 30 天下載
1.9萬
喜歡數
292
Pub 分數
160

安裝套件

flutter pub add pocketbase

套件介紹

根據套件公開資料產生的選型評估,僅供參考。

PocketBase Dart SDK 是一個跨平台程式庫,可實現與 PocketBase 的無縫整合。PocketBase 是一個輕量級、開源的即時後端伺服器,包裝在單一檔案中。該 SDK 讓開發者能從 Flutter 及其他 Dart 應用程式(支援 Android、iOS、Web、macOS、Windows 和 Linux)直接存取 PocketBase 的資料庫、驗證、檔案儲存和即時訂閱功能。SDK 提供簡潔直覺的 API,簡化資料查詢、使用者管理、上傳檔案和接收即時更新等後端互動操作。非常適合快速原型開發以及需要自架設、可擴展後端但不需要複雜基礎設施的完整應用。

適用情境

  • 即時資料同步
  • 使用者身分驗證與管理
  • 檔案上傳與儲存
  • 快速應用原型設計
  • 自託管後端解決方案
  • 跨平台行動與網頁應用程式

主要特色

  • 即時訂閱
  • 內建驗證
  • 檔案儲存 API
  • 多平台支援
  • 使用一個二進制後端進行簡單設定
  • 類型安全的資料查詢

適合對象

  • 尋求輕量級後端的Flutter開發者
  • 初創公司快速建立原型
  • 希望自託管解決方案的團隊
  • 使用 Dart 的全端開發人員
  • 需要即時功能的應用程式
  • 專案避免雲供應商鎖定

使用建議

  • 需要單獨運行一個 PocketBase 伺服器
  • 受限於 PocketBase 的功能集
  • 需要手動伺服器部署
  • 不適合大規模企業後端
  • 即時性能取決於伺服器位置
  • 身分驗證安全依賴於正確的伺服器設定

原始 README

以下為關聯原始碼儲存庫的 README 原文快照,最新內容請造訪原始碼儲存庫。

PocketBase Dart SDK Pub Package

Official Multi-platform Dart SDK for interacting with the PocketBase Web API.

Installation

Add the library to your dependencies:

dart pub add pocketbase

# or with Flutter:
flutter pub add pocketbase

Import it in your Dart code:

import 'package:pocketbase/pocketbase.dart';

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

...

// authenticate as regular user
final userData = await pb.collection('users').authWithPassword('test@example.com', '123456');

// list and filter "example" collection records
final result = await pb.collection('example').getList(
  page:    1,
  perPage: 20,
  filter:  'status = true && created >= "2022-08-01"',
  sort:    '-created',
  expand:  'someRelField',
);

// subscribe to realtime "example" collection changes
pb.collection('example').subscribe("*", (e) {
  print(e.action); // create, update, delete
  print(e.record); // the changed record
}, filter: "someField > 10");

// and much more...

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

Caveats

File upload

PocketBase Dart SDK handles file upload seamlessly by using http.MultipartFile list.

Here is a simple example of uploading a single text file together with some other regular fields:

import 'package:http/http.dart' as http;
import 'package:pocketbase/pocketbase.dart';

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

pb.collection('example').create(
  body: {
    'title': 'Hello world!',
    // ... any other regular field
  },
  files: [
    http.MultipartFile.fromString(
      'document', // the name of the file field
      'example content...',
      filename: 'example_document.txt',
    ),
  ],
).then((record) {
  print(record.id);
  print(record.get<String>('title'));
});
RecordModel

The SDK comes with several helpers to make it easier working with the RecordService and RecordModel DTO. You could find more detailed documentation in the RecordModel class reference, but below is an example how to access and cast record data values:

final record = await pb.collection('example').getOne('RECORD_ID');

final options  = record.get<List<String>>('options');
final email    = record.get<String>('email');
final status   = record.get<bool>('status');
final total    = record.get<int>('total');
final price    = record.get<double>('price');
final nested1  = record.get<RecordModel>('expand.user', null);
final nested2  = record.get<String>('expand.user.title', 'N/A');

Alternatively, you can also create your own typed DTO data classes and use for example the record.toJson() to populate your object, eg:

import "package:pocketbase/pocketbase.dart";
import 'package:json_annotation/json_annotation.dart';

part 'task.g.dart';

@JsonSerializable()
class Task {
  Task({this.id = '', this.description = ''});

  // type the collection fields you want to use...
  final String id;
  final String description;

  /// Creates a new Task instance form the provided RecordModel.
  factory Task.fromRecord(RecordModel record) => Task.fromJson(record.toJson());

  /// Connect the generated [_$Task] function to the `fromJson` factory.
  factory Task.fromJson(Map<String, dynamic> json) => _$Task(json);

  /// Connect the generated [_$Task] function to the `toJson` method.
  Map<String, dynamic> toJson() => _$Task(this);
}

...

// fetch your raw record model
final record = await pb.collection('tasks').getOne('TASK_ID');

final task = Task.fromRecord(record);
Error handling

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

pb.collection('example').getList(page: 1, perPage: 50).then((result) {
  // success...
  print('Result: $result');
}).catchError((error) {
  // error...
  print('Error: $error');
});

// OR if you are using the async/await syntax:
try {
  final result = await pb.collection('example').getList(page: 1, perPage: 50);
} catch (error) {
  print('Error: $error');
}

All response errors are normalized and wrapped as ClientException with the following public members that you could use:

ClientException {
    url            Uri     // The address of the failed request
    statusCode     int     // The status code of the failed request
    response       Map     // The JSON API error response
    isAbort        bool    // Indicates whether the request was aborted/cancelled
    originalError  dynamic // The original response error
}
AuthStore

The SDK keeps track of the authenticated token and auth record for you via the pb.authStore service. The default AuthStore class has the following public members that you could use:

AuthStore {
    token:    String           // Getter for the stored auth token
    record:   RecordModel|null // Getter for the stored auth RecordModel
    isValid   bool             // Getter to loosely check if the store has an existing and unexpired token
    onChange  Stream           // Stream that gets triggered on each auth store change

    // methods
    save(token, record)        // update the store with the new auth data
    clear()                    // clears the current auth store state
}

To "logout" an authenticated record, you can just call pb.authStore.clear().

To "listen" for changes in the auth store, you can listen to the onChange broadcast stream:

pb.authStore.onChange.listen((e) {
  print(e.token);
  print(e.record);
});

The default AuthStore is not persistent!

If you want to persist the AuthStore state (eg. in case the app get closed), you can extend the default store and pass a new custom instance as constructor argument to the client. To make it slightly easier, the SDK has a builtin AsyncAuthStore that you can combine with any async persistent layer (shared_preferences, hive, local file, etc.). Here is an example using Flutter's shared_preferences:

final prefs = await SharedPreferences.getInstance();

final store = AsyncAuthStore(
 save:    (String data) async => prefs.setString('pb_auth', data),
 initial: prefs.getString('pb_auth'),
);

final pb = PocketBase('http://example.com', authStore: store);
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 a Map.

final records = await pb.collection('example').getList(filter: pb.filter(
  // the same as: "title ~ 'exa\\'mple' && created = '2023-10-18 18:20:00.123Z'"
  'title ~ {:title} && created >= {:created}',
  { "title": "exa'mple", "created": DateTime.now() },
));

The supported placeholder parameter values are:

  • String (single quotes are autoescaped)
  • DateTime
  • bool
  • num
  • null
  • everything else is converted to a string using jsonEncode()
Optional HTTP client reuse

Out of the box the SDK initializes a new one-off dart-lang/http.Client instance for each request and it takes care to close the established connection after processing the response.

The defaults should work fine for most applications but in some situations you may benefit from utilizing persistent connections when sending requests to your PocketBase server (~10% improvement on average when sending 100 requests).

This could be enabled via the reuseHTTPClient constructor option. The main drawback of this option is that you'll have to call pb.close() when done dealing with the SDK instance (e.g. before app termination).

import 'package:pocketbase/pocketbase.dart';

// enable the reuseHTTPClient option
final pb = PocketBase('http://127.0.0.1:8090', reuseHTTPClient: true);

...

// after close() no further requests can be made with this instance
pb.close();
OAuth2 and Android 15+

Android 15+ introduced major behavior changes and you may need to use a Foreground Service or WorkManager for the "All-in-one" OAuth2 flow (authWithOAuth2) to work because we rely on a realtime connection to remain active in the background while the user is authenticating in the OAuth2 provider's page (see also #71).

If that's not feasible, you can always fallback to the "Manual code exchange" OAuth2 flow (authWithOAuth2Code) combined with a deep link.

OAuth2 "all-in-one" and dual-stack IPs server setup

With PocketBase v0.38.2+ we perform extra security checks and require each realtime connection to be bound to their initialized client IP.

This unfortunatelly, when using dual-stack IPs server setup, can cause errors with the OAuth2 "all-in-one" flow (it relies on maintaining a realtime connection under the hood) due to discrepencies between the DNS resolver of dart:io and the browsers/OS (see pocketbase#7758).

The easiest workaround for the time being is to allow only IPv4 support for your PocketBase server (e.g. remove the IPv6 AAAA DNS record). Or if that's not an option for whatever reason, you can also always fallback to the "Manual code exchange" OAuth2 flow (authWithOAuth2Code) combined with a deep link.

Services

RecordService (Detailed class reference, API docs)
Crud handlers
// Returns a paginated records list.
🔓 pb.collection(collectionIdOrName).getList({page = 1, perPage = 30, filter?, sort?, expand?, fields?, query, headers});

// Returns a list with all records batch fetched at once.
🔓 pb.collection(collectionIdOrName).getFullList({batch = 1000, filter?, sort?, expand?, fields?, query, headers});

// Returns the first found record matching the specified filter.
🔓 pb.collection(collectionIdOrName).getFirstListItem(filter, {expand?, fields?, query, headers});

// Returns a single record by its id.
🔓 pb.collection(collectionIdOrName).getOne(recordId, {expand?, fields?, query, headers});

// Creates (aka. register) a new record.
🔓 pb.collection(collectionIdOrName).create({body, files, expand?, fields?, query, headers});

// Updates an existing record by its id.
🔓 pb.collection(collectionIdOrName).update(recordId, {body, files, expand?, fields?, query, headers});

// Deletes a single record by its id.
🔓 pb.collection(collectionIdOrName).delete(recordId, {query, body, headers});
Realtime handlers
// Subscribe to realtime changes to the specified topic ("*" or recordId).
//
// It is safe to subscribe multiple times to the same topic.
//
// You can use the returned UnsubscribeFunc to remove a single registered subscription.
// If you want to remove all subscriptions related to the topic use unsubscribe(topic).
🔓 pb.collection(collectionIdOrName).subscribe(topic, callback, {filter?, expand?, fields?, query, headers});

// Unsubscribe from all registered subscriptions to the specified topic ("*" or recordId).
// If topic is not set, then it will remove all registered collection subscriptions.
🔓 pb.collection(collectionIdOrName).unsubscribe([topic]);
Auth handlers

Available only for "auth" type collections.

// Returns all available application auth methods.
🔓 pb.collection(collectionIdOrName).listAuthMethods({query, headers});

// Refreshes the current authenticated record model and auth token.
🔐 pb.collection(collectionIdOrName).authRefresh({expand?, fields?, query, body, headers});

// Authenticates a record with their username/email and password.
🔓 pb.collection(collectionIdOrName).authWithPassword(usernameOrEmail, password, {expand?, fields?, query, body, headers});

// Authenticates a record with OAuth2 provider without custom redirects, deeplinks or even page reload.
🔓 pb.collection(collectionIdOrName).authWithOAuth2(provider, urlCallback {scopes, createData, expand?});

// Authenticates a record with OAuth2 code.
🔓 pb.collection(collectionIdOrName).authWithOAuth2Code(provider, code, codeVerifier, redirectURL, {createData?, expand?, fields?, query, body, headers});

// Sends auth record OTP request to the provided email.
🔓 pb.collection(collectionIdOrName).requestOTP(email, {query, body, headers});

// Authenticates a record with OTP (email code).
🔓 pb.collection(collectionIdOrName).authWithOTP(otpId, password, {expand?, fields?, query, body, headers});

// Sends a user password reset email.
🔓 pb.collection(collectionIdOrName).requestPasswordReset(email, {query, body, headers});

// Confirms a record password reset request.
🔓 pb.collection(collectionIdOrName).confirmPasswordReset(resetToken, newPassword, newPasswordConfirm, {expand?, fields?, query, body, headers});

// Sends a record verification email request.
🔓 pb.collection(collectionIdOrName).requestVerification(email, {query, body, headers});

// Confirms a record email verification request.
🔓 pb.collection(collectionIdOrName).confirmVerification(verificationToken, {expand?, fields?, query, body, headers});

// Sends a record email change request to the provider email.
🔐 pb.collection(collectionIdOrName).requestEmailChange(newEmail, {query, body, headers});

// Confirms record new email address.
🔓 pb.collection(collectionIdOrName).confirmEmailChange(emailChangeToken, userPassword, {expand?, fields?, query, body, headers});

// Impersonate authenticates with the specified recordId and returns a new client with the received auth token in a memory store.
🔐 pb.collection(collectionIdOrName).impersonate(recordId, duration, {expand?, fields?, query, body, headers});

FileService (Detailed class reference)
// Builds and returns an absolute record file url for the provided filename.
🔓 pb.files.getURL(record, filename, {thumb?, token?, query, body, headers});

// Requests a new private file access token for the current auth record.
🔐 pb.files.getToken({query, body, headers});

CollectionService (Detailed class reference, API docs)
// Returns a paginated collections list.
🔐 pb.collections.getList({page = 1, perPage = 30, filter?, sort?, query, headers});

// Returns a list with all collections batch fetched at once.
🔐 pb.collections.getFullList({batch = 100, filter?, sort?, query, headers});

// Returns the first found collection matching the specified filter.
🔐 pb.collections.getFirstListItem(filter, {query, headers});

// Returns a single collection by its id or name.
🔐 pb.collections.getOne(idOrName, {query, headers});

// Creates (aka. register) a new collection.
🔐 pb.collections.create({body, files, query, headers});

// Updates an existing collection by its id or name.
🔐 pb.collections.update(idOrName, {body, files, query, headers});

// Deletes a single collection by its id or name.
🔐 pb.collections.delete(idOrName, {query, body, headers});

// Deletes all records associated with the specified collection.
🔐 pb.collections.truncate(idOrName, {query, body, headers});

// Imports the provided collections.
🔐 pb.collections.import(collections, {deleteMissing=false, query, body, headers});

// Returns type indexed map with scaffolded collection models populated with their default field values.
🔐 pb.collections.getScaffolds({query, headers});

// Returns a list with all configurable OAuth2 providers.
🔐 pb.collections.getAllOAuth2Providers({query, headers});

// Executes the specified view query and returns a sample of the resulting records.
🔐 pb.collections.dryRunViewQuery(viewQuery, {query, body, headers});

LogService (Detailed class reference, API docs)
// Returns a paginated logs list.
🔐 pb.logs.getList({page = 1, perPage = 30, filter?, sort?, query, headers});

// Returns a single log by its id.
🔐 pb.logs.getOne(id, {query, headers});

// Returns logs statistics.
🔐 pb.logs.getStats({query, headers});

// Delete all logs.
🔐 pb.logs.truncate({query, body, headers});

SettingsService (Detailed class reference, API docs)
// Returns a map with all available app settings.
🔐 pb.settings.getAll({query, headers});

// Bulk updates app settings.
🔐 pb.settings.update({body, query, headers});

// Performs a S3 storage connection test.
🔐 pb.settings.testS3({body, query, headers});

// Sends a test email (verification, password-reset, email-change).
🔐 pb.settings.testEmail(toEmail, template, {collection, body, query, headers});

// Generates a new Apple OAuth2 client secret.
🔐 pb.settings.generateAppleClientSecret(clientId, teamId, keyId, privateKey, duration, {body, query, headers});

RealtimeService (Detailed class reference, API docs)

This service is usually used with custom realtime actions. For records realtime subscriptions you can use the subscribe/unsubscribe methods available in the pb.collection() RecordService.

// Initialize the realtime connection (if not already) and register the subscription.
//
// You can subscribe to the `PB_CONNECT` event if you want to listen to the realtime connection connect/reconnect events.
🔓 pb.realtime.subscribe(subscription, callback, {filter?, expand?, fields?, query, headers});

// Unsubscribe from a subscription (if empty - unsubscribe from all registered subscriptions).
🔓 pb.realtime.unsubscribe([subscription = '']);

// Unsubscribe from all subscriptions starting with the provided prefix.
🔓 pb.realtime.unsubscribeByPrefix(subscriptionsPrefix);

// An optional hook that is invoked when the realtime client disconnects
// either when unsubscribing from all subscriptions or when the connection
// was interrupted or closed by the server.
//
// Note that the realtime client autoreconnect on its own and this hook is
// useful only for the cases where you want to apply a special behavior on
// server error or after closing the realtime connection.
pb.realtime.onDisconnect = (subscriptions) { ... }

BackupService (Detailed class reference, API docs)
// Returns list with all available backup files.
🔐 pb.backups.getFullList({query, headers});

// Initializes a new backup.
🔐 pb.backups.create(basename, {body, query, headers});

// Uploads an existing backup file (_the multipart file key is "file"_).
🔐 pb.backups.upload(file, {body, query, headers});

// Deletes a single backup by its file key.
🔐 pb.backups.delete(key, {body, query, headers});

// Initializes an app data restore from an existing backup.
🔐 pb.backups.restore(key, {body, query, headers});

// Builds a download url for a single existing backup using an
// superuser file token and the backup file key.
🔐 pb.backups.getDownloadURL(token, key, {query});

CronService (Detailed class reference, API docs)
// Returns list with all registered app cron jobs.
🔐 pb.crons.getFullList({query, headers});

// Runs the specified cron job.
🔐 pb.crons.run(jobId, {body, query, headers});

SQLService (Detailed class reference, API docs)
// Runs the specified raw SQL query.
🔐 pb.sql.run(rawSQL, {body, query, headers});

HealthService (Detailed class reference, API docs)
// Checks the health status of the api.
🔓 pb.health.check({query, headers});

BatchService (Detailed class reference, API docs)
// create a new batch instance
final batch = pb.createBatch();

// register create/update/delete/upsert requests to the created batch
batch.collection('example1').create(body: { ... });
batch.collection('example2').update('RECORD_ID', body: { ... });
batch.collection('example3').delete('RECORD_ID');
batch.collection('example4').upsert(body: { ... });

// send the batch request
final result = await batch.send()

Limitations

PocketBase Dart SDK is built on top of the standard dart-lang/http package and inherits some of its limitations:

Development

# run the unit tests
dart test

# view dartdoc locally
dart doc

# run the example
dart run example/example.dart

# generate the DTOs json serializable artifacts
dart run build_runner build

所屬分類

依賴

依賴清單 2 項

原始碼儲存庫

使用此套件的專案