Improve RecordApi docs by adding code examples (only Dart for now) and fix a small heartbeat decoding issue.

Partially addresses some of the issues with our lackluster docs: #22.
This commit is contained in:
Sebastian Jeltsch
2025-01-20 13:08:31 +01:00
parent 5144dabc68
commit 8f9d8ada9c
22 changed files with 701 additions and 62 deletions

View File

@@ -362,12 +362,14 @@ class RecordApi {
);
}
static Event _decodeEvent(Uint8List bytes) {
static List<Event> _decodeEvent(Uint8List bytes) {
final decoded = utf8.decode(bytes);
if (decoded.startsWith('data: ')) {
return Event.fromJson(jsonDecode(decoded.substring(6)));
return [Event.fromJson(jsonDecode(decoded.substring(6)))];
}
return Event.fromJson(jsonDecode(decoded));
// Heart-beat, do nothing.
return [];
}
Future<Stream<Event>> subscribe(RecordId id) async {
@@ -377,7 +379,7 @@ class RecordApi {
);
final Stream<Uint8List> stream = resp.data.stream;
return stream.asyncMap(_decodeEvent);
return stream.expand(_decodeEvent);
}
Future<Stream<Event>> subscribeAll() async {
@@ -387,7 +389,7 @@ class RecordApi {
);
final Stream<Uint8List> stream = resp.data.stream;
return stream.asyncMap(_decodeEvent);
return stream.expand(_decodeEvent);
}
Uri imageUri(RecordId id, String colName, {int? index}) {

View File

@@ -41,7 +41,7 @@ export default defineConfig({
slug: "getting-started/first-ui-app",
},
{
label: "A CRUD App",
label: "A CLI App",
slug: "getting-started/first-cli-app",
},
{

View File

@@ -0,0 +1,3 @@
# https://dart.dev/guides/libraries/private-files
# Created by `dart pub`
.dart_tool/

View File

@@ -0,0 +1,18 @@
include: package:lints/recommended.yaml
linter:
rules:
prefer_single_quotes: true
unnecessary_brace_in_string_interps: false
unawaited_futures: true
sort_child_properties_last: false
# analyzer:
# exclude:
# - path/to/excluded/files/**
# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints
# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options

View File

@@ -0,0 +1,5 @@
export 'src/create.dart';
export 'src/read.dart';
export 'src/update.dart';
export 'src/delete.dart';
export 'src/subscribe.dart';

View File

@@ -0,0 +1,5 @@
import 'package:trailbase/trailbase.dart';
Future<RecordId> create(Client client) async => await client
.records('simple_strict_table')
.create({'text_not_null': 'test'});

View File

@@ -0,0 +1,4 @@
import 'package:trailbase/trailbase.dart';
Future<void> delete(Client client, RecordId id) async =>
await client.records('simple_strict_table').delete(id);

View File

@@ -0,0 +1,4 @@
import 'package:trailbase/trailbase.dart';
Future<Map<String, dynamic>> read(Client client, RecordId id) async =>
await client.records('simple_strict_table').read(id);

View File

@@ -0,0 +1,9 @@
import 'dart:async';
import 'package:trailbase/trailbase.dart';
Future<Stream<Event>> subscribe(Client client, RecordId id) async =>
await client.records('simple_strict_table').subscribe(id);
Future<Stream<Event>> subscribeAll(Client client) async =>
await client.records('simple_strict_table').subscribeAll();

View File

@@ -0,0 +1,5 @@
import 'package:trailbase/trailbase.dart';
Future<void> update(Client client, RecordId id) async => await client
.records('simple_strict_table')
.update(id, {'text_not_null': 'updated'});

View File

@@ -0,0 +1,433 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "03f6da266a27a4538a69295ec142cb5717d7d4e5727b84658b63e1e1509bac9c"
url: "https://pub.dev"
source: hosted
version: "79.0.0"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.3"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: c9040fc56483c22a5e04a9f6a251313118b1a3c42423770623128fa484115643
url: "https://pub.dev"
source: hosted
version: "7.2.0"
args:
dependency: transitive
description:
name: args
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
url: "https://pub.dev"
source: hosted
version: "2.6.0"
async:
dependency: transitive
description:
name: async
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
url: "https://pub.dev"
source: hosted
version: "2.12.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
convert:
dependency: transitive
description:
name: convert
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.dev"
source: hosted
version: "3.1.2"
coverage:
dependency: transitive
description:
name: coverage
sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43
url: "https://pub.dev"
source: hosted
version: "1.11.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.dev"
source: hosted
version: "3.0.6"
dio:
dependency: transitive
description:
name: dio
sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260"
url: "https://pub.dev"
source: hosted
version: "5.7.0"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.dev"
source: hosted
version: "4.0.0"
glob:
dependency: transitive
description:
name: glob
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
url: "https://pub.dev"
source: hosted
version: "3.2.2"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
io:
dependency: transitive
description:
name: io
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
url: "https://pub.dev"
source: hosted
version: "1.0.5"
js:
dependency: transitive
description:
name: js
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
url: "https://pub.dev"
source: hosted
version: "0.7.1"
jwt_decoder:
dependency: transitive
description:
name: jwt_decoder
sha256: "54774aebf83f2923b99e6416b4ea915d47af3bde56884eb622de85feabbc559f"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
lints:
dependency: "direct dev"
description:
name: lints
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
url: "https://pub.dev"
source: hosted
version: "5.1.1"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
macros:
dependency: transitive
description:
name: macros
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
url: "https://pub.dev"
source: hosted
version: "0.1.3-main.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.17"
meta:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
version: "1.16.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
node_preamble:
dependency: transitive
description:
name: node_preamble
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
package_config:
dependency: transitive
description:
name: package_config
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
pool:
dependency: transitive
description:
name: pool
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.dev"
source: hosted
version: "1.5.1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
shelf:
dependency: transitive
description:
name: shelf
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
url: "https://pub.dev"
source: hosted
version: "1.4.2"
shelf_packages_handler:
dependency: transitive
description:
name: shelf_packages_handler
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
shelf_static:
dependency: transitive
description:
name: shelf_static
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
url: "https://pub.dev"
source: hosted
version: "1.1.3"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
url: "https://pub.dev"
source: hosted
version: "2.0.1"
source_map_stack_trace:
dependency: transitive
description:
name: source_map_stack_trace
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
url: "https://pub.dev"
source: hosted
version: "2.1.2"
source_maps:
dependency: transitive
description:
name: source_maps
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
url: "https://pub.dev"
source: hosted
version: "0.10.13"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test:
dependency: "direct dev"
description:
name: test
sha256: "8391fbe68d520daf2314121764d38e37f934c02fd7301ad18307bd93bd6b725d"
url: "https://pub.dev"
source: hosted
version: "1.25.14"
test_api:
dependency: transitive
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev"
source: hosted
version: "0.7.4"
test_core:
dependency: transitive
description:
name: test_core
sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa"
url: "https://pub.dev"
source: hosted
version: "0.6.8"
trailbase:
dependency: "direct main"
description:
path: "../../../client/trailbase-dart"
relative: true
source: path
version: "0.2.0"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev"
source: hosted
version: "15.0.0"
watcher:
dependency: transitive
description:
name: watcher
sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
web:
dependency: transitive
description:
name: web
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.dev"
source: hosted
version: "1.1.0"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
url: "https://pub.dev"
source: hosted
version: "0.1.6"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
webkit_inspection_protocol:
dependency: transitive
description:
name: webkit_inspection_protocol
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.6.1 <4.0.0"

View File

@@ -0,0 +1,15 @@
name: record_api
description: Example uses of record APIs for documentation purposes.
version: 0.0.1
publish_to: none
environment:
sdk: ^3.6.1
dependencies:
trailbase:
path: ../../../client/trailbase-dart
dev_dependencies:
lints: ^5.0.0
test: ^1.24.0

View File

@@ -0,0 +1,74 @@
import 'package:test/test.dart';
import 'package:trailbase/trailbase.dart';
import 'package:record_api/record_api.dart';
class SimpleStrict {
final String id;
final String? textNull;
final String textDefault;
final String textNotNull;
SimpleStrict({
required this.id,
this.textNull,
this.textDefault = '',
required this.textNotNull,
});
SimpleStrict.fromJson(Map<String, dynamic> json)
: id = json['id'],
textNull = json['text_null'],
textDefault = json['text_default'],
textNotNull = json['text_not_null'];
Map<String, dynamic> toJson() => {
'id': id,
'text_null': textNull,
'text_default': textDefault,
'text_not_null': textNotNull,
};
}
Future<Client> connect() async {
final client = Client('http://localhost:4000');
await client.login('admin@localhost', 'secret');
return client;
}
void main() {
test('Test examples', () async {
final client = await connect();
final tableStream = await subscribeAll(client);
final id = await create(client);
final recordStream = await subscribe(client, id);
{
final json = await read(client, id);
final record = SimpleStrict.fromJson(json);
expect(record.textNotNull, equals('test'));
}
{
await update(client, id);
final json = await read(client, id);
final record = SimpleStrict.fromJson(json);
expect(record.textNotNull, equals('updated'));
}
await delete(client, id);
expect(await recordStream.length, equals(2));
final tableEventList =
await tableStream.timeout(Duration(seconds: 5), onTimeout: (sink) {
print('Stream timeout');
sink.close();
}).toList();
expect(tableEventList.length, equals(3));
});
}

View File

@@ -5,7 +5,6 @@ can figure out how any feature will fit into the overall picture and minimize
friction.
For context, some larger features we have on our Roadmap:
- Realtime/notification APIs for subscribing to data changes.
- Auth: more customizable settings, more customizable UI, and multi-factor.
Also, service-accounts to auth other backends as opposed to end-users.
- Many SQLite databases: imagine a separate database by tenant or user.

View File

@@ -48,7 +48,7 @@ Likewise, TrailBase has a few nifty tricks up its sleeve:
- Language independent type-safety via JSON Schemas with strict typing
being enforced all the way down to the database level[^4].
- TrailBase's JavaScript runtime supports full ES6, TypeScript transpilation,
and is built on V8 making it [~40x faster](/reference/benchmarks/).
and is built on V8 making it [~40x faster](/reference/benchmarks/#javascript-performance).
- First-class access to all of SQLite's features and capabilities.
- A simple auth UI.
- Stateless JWT auth-tokens for simple, hermetic authentication in other

View File

@@ -3,11 +3,12 @@ title: Record APIs
---
import { Aside, Code } from "@astrojs/starlight/components";
import { Tabs, TabItem } from '@astrojs/starlight/components';
import { apiPath, recordApiNamePlaceholder, recordApiIdPlaceholder } from "./record_apis.ts";
The easiest and most type-safe path to access your `TABLE`s and `VIEW`s is to use
TrailBase's restful CRUD _Record APIs_.
The only requirements are:
TrailBase's restful CRUD _Record APIs_ allow you to access your `TABLE`s and
`VIEW`s in an easy and type-safe manner.
- Tables and views need to be `STRICT`ly[^1] typed to guarantee type-safety all the
way from your records, via JSON schema, to your client-side language bindings [^2].
@@ -15,14 +16,14 @@ The only requirements are:
and thus efficient cursor-based pagination. Either an explicit `INTEGER` or
UUIDv7 `PRIMARY KEY` will do, including `FOREIGN KEY` columns.
## Configuring APIs
## Configuration
Record APIs can be configured through the admin dashboard or immediately in
TrailBase's configuration file.
Note that there are certain features that aren't yet exposed in the dashboard,
like supporting multiple APIs based on the same table or view.
In this case you can drop down to the configuration to set up as many as you
like allowing for a lot of extra flexibility around permissions and visibility.
Record APIs can be configured via the admin dashboard or in TrailBase's
configuration file.
Not all features are yet exposed via the UI like exporting the same table/view
multiple times as different API endpoints.
Editing the configuration file directly, you can set up as many as you like
allowing for some extra flexibility around permissions and visibility.
An example API setup for managing user avatars:
@@ -60,7 +61,7 @@ A quick explanation:
match. In other words, user X cannot modify user Y's avatar.
### Access Control
### Permissions
Access can be controlled through combination of a simple ACL-based system (a
matrix of who and what) and custom SQL access rules of the nature:
@@ -108,50 +109,87 @@ appropriately tight to avoid permission escalations.
### Write-only columns
Columns with names starting with an underscore can be written on insert or
update but are hidden on reads. This is meant as a convenient convention to
updated but are hidden on reads. This is meant as a convenient convention to
allow for internal data fields, e.g hiding the record owner in an otherwise public
data set or hiding a user's internal credit rating from their profile. A
similar effect could otherwise be achieved by exposing a table for inserts and
updates only while proxying reads through a VIEW.
similar effect could otherwise be achieved by only allowing create/update
operations on the table and exposing a subset of columns through a readable
`VIEW`. However, this would lead to different API endpoints for read vs
create/update.
<Aside type="note" title="Unhiding">
Note that views can also be used to rename columns and thus expose hidden columns
in a read-only fashion.
</Aside>
## Accessing Record APIs
## Access
After configuring the APIs and setting up permissions, record APIs expose six
main endpoints[^3]:
After setting up your API, TrailBase will expose the following main endpoints[^3]:
* **C**reate: endpoint for for inserting new and potentially overriding records
depending on conflict resolution strategy.<br/>
<code>POST {apiPath({name: recordApiNamePlaceholder})}</code>
* **R**ead: endpoint for reading specific records given the record id.<br/>
<code>GET {apiPath({name: recordApiNamePlaceholder, suffix: recordApiIdPlaceholder})}</code>
* **U**pdate: partial updates to existing records given a record id and subset of fields <br/>
<code>PATCH {apiPath({name: recordApiNamePlaceholder, suffix: recordApiIdPlaceholder})}</code>
* **D**elete: endpoints for deleting record given a record id. <br/>
<code>DELETE {apiPath({name: recordApiNamePlaceholder, suffix: recordApiIdPlaceholder})}</code>
* List: endpoint for listing, filtering and sorting records based on the
configured read access rule and provided filters.<br/>
<code>GET {apiPath({name: `${recordApiNamePlaceholder}?<params>`})}</code>
* Schema: endpoint for reading the APIs JSON schema definition. Can be used for
introspection and to drive code generation.<br/>
<code>GET {apiPath({name: recordApiNamePlaceholder, suffix: "schema"})}</code>
* "realtime" subscriptions/notifications: establish a streaming HTTP connection to listen for updates via SSE:
* Specific record:<br/>
<code>GET {apiPath({name: recordApiNamePlaceholder, suffix: `subscribe/${recordApiIdPlaceholder}`})}</code>
* All record:<br/>
<code>GET {apiPath({name: recordApiNamePlaceholder, suffix: `subscribe/*`})}</code>
* **C**reate: <code>POST {apiPath({name: recordApiNamePlaceholder})}</code>
* **R**ead: <code>GET {apiPath({name: recordApiNamePlaceholder, suffix: recordApiIdPlaceholder})}</code>
* **U**pdate: <code>PATCH {apiPath({name: recordApiNamePlaceholder, suffix: recordApiIdPlaceholder})}</code>
* **D**elete: <code>DELETE {apiPath({name: recordApiNamePlaceholder, suffix: recordApiIdPlaceholder})}</code>
* List: <code>GET {apiPath({name: `${recordApiNamePlaceholder}?<params>`})}</code>
* Change Subscriptions: <br/><code>GET {apiPath({name: recordApiNamePlaceholder, suffix: `subscribe/[*|${recordApiIdPlaceholder}]`})}</code>
* Schema: <code>GET {apiPath({name: recordApiNamePlaceholder, suffix: "schema"})}</code>
All of the endpoints accept requests that are JSON encoded, url-encoded, or
`multipart/form-data` encoded, which makes them accessible via rich client-side
applications, progressive web apps, and static HTML forms alike.
### Create
The create endpoint lets you insert new records and potentially override
existing ones depending on conflict resolution strategy.
import createDartCode from "@examples/record_api_dart/lib/src/create.dart?raw";
<Tabs>
<TabItem label="Dart">
<Code lang="dart" code={createDartCode} />
</TabItem>
</Tabs>
All of the above endpoints can be interacted with through requests that are
either JSON encoded, url-encoded, or `multipart/form-data` encoded, which makes
them accessible via rich client-side applications, progressive web apps, and
static HTML forms alike.
### Read
### Listing, filtering & sorting records
The read endpoint lets you read specific records given their id.
import readDartCode from "@examples/record_api_dart/lib/src/read.dart?raw";
<Tabs>
<TabItem label="Dart">
<Code lang="dart" code={readDartCode} />
</TabItem>
</Tabs>
### Update
The update endpoint lets you modify, i.e. partially update, existing records given their id
import updateDartCode from "@examples/record_api_dart/lib/src/update.dart?raw";
<Tabs>
<TabItem label="Dart">
<Code lang="dart" code={updateDartCode} />
</TabItem>
</Tabs>
### Delete
import deleteDartCode from "@examples/record_api_dart/lib/src/update.dart?raw";
<Tabs>
<TabItem label="Dart">
<Code lang="dart" code={deleteDartCode} />
</TabItem>
</Tabs>
The delete endpoints lets you remove a record given its id.
### List: Filter, Sort and Paginate
Using the <code>GET {apiPath({name: `${recordApiNamePlaceholder}?<params>`})}</code> endpoint and given
sufficient permissions one can query records based the given `read_access_rule`
@@ -185,7 +223,27 @@ export const curlListRecords =
<Code code={curlListRecords} lang="bash" frame="terminal" />
## File Upload
### Subscribe
The streaming subscribe endpoints lets you listen for changes to tables backing
an API or specific records given their id. Change events can be insertions,
updates, and deletions.
import subscribeDartCode from "@examples/record_api_dart/lib/src/subscribe.dart?raw";
<Tabs>
<TabItem label="Dart">
<Code lang="dart" code={subscribeDartCode} />
</TabItem>
</Tabs>
### Schema
The schema endpoint allows for reading the APIs JSON schema definition. This
can be useful for introspection such as driving external code generation.
## File Uploads
Record APIs can also support file uploads and downloads. There's some special
handling in place so that only metadata is stored in the underlying table while
@@ -254,7 +312,9 @@ When generating new client-side bindings for a table or view with such nested
schemas, they will be included ensuring type-safety all the way to the
client-side APIs.
### Tangent: Querying JSON
{/*
## Tangent: Querying JSON
Independent of type-safety and Record APIs,
[SQLite](https://www.sqlite.org/json1.html) has first-class support for
@@ -286,6 +346,7 @@ on it would be a lot more efficient.
Yet, using JSON for complex structured or denormalized data can be powerful
addition to your toolbox.
*/}
<div class="h-[50px]" />
@@ -305,8 +366,8 @@ addition to your toolbox.
that you think should be supported but aren't.
[^3]:
There's also a few other endpoints, e.g. for downloading files as described
later in the document.
There's also a few other endpoints, e.g. for downloading files, which will
be described further down in the docs.
[^4]:
Record APIs only support textual JSON. Binary JSON is more compact and more

View File

@@ -4,7 +4,7 @@ type ApiOptions = {
prefix?: string;
};
export const recordApiNamePlaceholder = "<record_api_name>";
export const recordApiNamePlaceholder = "<api_name>";
export const recordApiIdPlaceholder = "<url-safe_b64_uuid_or_int>";
export function apiPath(opts: ApiOptions): string {

View File

@@ -1,5 +1,5 @@
---
title: A CRUD App
title: A CLI App
---
import { Code } from "@astrojs/starlight/components";
@@ -161,7 +161,7 @@ version and copy&paste the JSON schema from above.
With the generated types, we can use the TrailBase TypeScript client to write
the following program:
import fillCode from "../../../../../examples/tutorial/scripts/src/fill.ts?raw";
import fillCode from "@root/examples/tutorial/scripts/src/fill.ts?raw";
<Code
code={fillCode}

View File

@@ -40,7 +40,7 @@ we'll use the `sqlite3` CLI[^1] directly to import
`examples/coffeesearch/arabica_data_cleaned.csv` with the following SQL
script:
import importScript from "../../../../../examples/coffeesearch/import.sql?raw";
import importScript from "@root/examples/coffeesearch/import.sql?raw";
<Code
code={importScript}
@@ -95,7 +95,7 @@ Let's have a quick look at `examples/coffeesearch/traildepot/scripts/main.ts`,
which defines a `/search` API route we'll later use in our application to
find coffees most closely matching our desired coffee notes:
import handlerCode from "../../../../../examples/coffeesearch/traildepot/scripts/main.ts?raw";
import handlerCode from "@root/examples/coffeesearch/traildepot/scripts/main.ts?raw";
<Code
code={handlerCode}

View File

@@ -61,7 +61,7 @@ import { Duration100kInsertsChart } from "./reference/_benchmarks/benchmarks.tsx
serve millions of customers from a tiny box.
In terms of JS/TS performance, V8 is roughly
[40x faster](/reference/benchmarks#javascript-runtime-benchmarks)
[40x faster](/reference/benchmarks#javascript-performance)
than goja used by PocketBase.
</div>

View File

@@ -179,7 +179,7 @@ being roughly 5 times slower than p50.
Slower insertions can take north of 100ms. This may be related to GC pauses,
scheduling, or more generally the same CPU variability we observed earlier.
## JavaScript-Runtime Benchmarks
## JavaScript Performance
The benchmark sets up a custom HTTP endpoint `/fibonacci?n=<N>` using the same
slow recursive Fibonacci

View File

@@ -6,7 +6,9 @@
"baseUrl": "./",
"paths": {
"@/*": ["./src/*"],
"@assets/*": ["../assets/*"]
"@assets/*": ["../assets/*"],
"@examples/*": ["./examples/*"],
"@root/*": ["../*"]
}
},
"exclude": [