mirror of
https://github.com/trailbaseio/trailbase.git
synced 2026-02-22 18:58:48 -06:00
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:
@@ -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}) {
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
{
|
||||
|
||||
3
docs/examples/record_api_dart/.gitignore
vendored
Normal file
3
docs/examples/record_api_dart/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://dart.dev/guides/libraries/private-files
|
||||
# Created by `dart pub`
|
||||
.dart_tool/
|
||||
18
docs/examples/record_api_dart/analysis_options.yaml
Normal file
18
docs/examples/record_api_dart/analysis_options.yaml
Normal 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
|
||||
5
docs/examples/record_api_dart/lib/record_api.dart
Normal file
5
docs/examples/record_api_dart/lib/record_api.dart
Normal 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';
|
||||
5
docs/examples/record_api_dart/lib/src/create.dart
Normal file
5
docs/examples/record_api_dart/lib/src/create.dart
Normal 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'});
|
||||
4
docs/examples/record_api_dart/lib/src/delete.dart
Normal file
4
docs/examples/record_api_dart/lib/src/delete.dart
Normal 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);
|
||||
4
docs/examples/record_api_dart/lib/src/read.dart
Normal file
4
docs/examples/record_api_dart/lib/src/read.dart
Normal 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);
|
||||
9
docs/examples/record_api_dart/lib/src/subscribe.dart
Normal file
9
docs/examples/record_api_dart/lib/src/subscribe.dart
Normal 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();
|
||||
5
docs/examples/record_api_dart/lib/src/update.dart
Normal file
5
docs/examples/record_api_dart/lib/src/update.dart
Normal 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'});
|
||||
433
docs/examples/record_api_dart/pubspec.lock
Normal file
433
docs/examples/record_api_dart/pubspec.lock
Normal 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"
|
||||
15
docs/examples/record_api_dart/pubspec.yaml
Normal file
15
docs/examples/record_api_dart/pubspec.yaml
Normal 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
|
||||
74
docs/examples/record_api_dart/test/record_api_test.dart
Normal file
74
docs/examples/record_api_dart/test/record_api_test.dart
Normal 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));
|
||||
});
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@assets/*": ["../assets/*"]
|
||||
"@assets/*": ["../assets/*"],
|
||||
"@examples/*": ["./examples/*"],
|
||||
"@root/*": ["../*"]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
|
||||
Reference in New Issue
Block a user