Skip to content

Commit a3ea19d

Browse files
committed
Rename library
1 parent 248d748 commit a3ea19d

File tree

16 files changed

+205
-34
lines changed

16 files changed

+205
-34
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pubspec_overrides.yaml
99
.flutter-plugins-dependencies
1010
.flutter-plugins
1111
build
12+
**/doc/api
13+
1214

1315
# Shared assets
1416
assets/*

demos/supabase-todolist/lib/attachments/photo_capture_widget.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class _TakePhotoWidgetState extends State<TakePhotoWidget> {
5454
return;
5555
}
5656

57-
final photoData = await photoFile.readAsBytes();
57+
final photoData = photoFile.openRead();
5858

5959
// Save the photo attachment with the byte data
6060
final attachment = await savePhotoAttachment(photoData, widget.todoId);

demos/supabase-todolist/lib/attachments/queue.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import 'dart:io';
44
import 'package:logging/logging.dart';
55
import 'package:path_provider/path_provider.dart';
66
import 'package:powersync/powersync.dart';
7-
import 'package:powersync_core/attachments.dart';
7+
import 'package:powersync_core/attachments/attachments.dart';
88
import 'package:powersync_core/attachments/io.dart';
99

1010
import 'package:powersync_flutter_demo/attachments/remote_storage_adapter.dart';
@@ -38,7 +38,8 @@ Future<void> initializeAttachmentQueue(PowerSyncDatabase db) async {
3838
await attachmentQueue.startSync();
3939
}
4040

41-
Future<Attachment> savePhotoAttachment(List<int> photoData, String todoId,
41+
Future<Attachment> savePhotoAttachment(
42+
Stream<List<int>> photoData, String todoId,
4243
{String mediaType = 'image/jpeg'}) async {
4344
// Save the file using the AttachmentQueue API
4445
return await attachmentQueue.saveFile(

demos/supabase-todolist/lib/attachments/remote_storage_adapter.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import 'dart:io';
22
import 'dart:typed_data';
33

4-
import 'package:powersync_core/attachments.dart';
4+
import 'package:powersync_core/attachments/attachments.dart';
55
import 'package:powersync_flutter_demo/app_config.dart';
66
import 'package:supabase_flutter/supabase_flutter.dart';
77
import 'package:logging/logging.dart';
88

9-
class SupabaseStorageAdapter implements RemoteAttachmentStorage {
9+
class SupabaseStorageAdapter implements RemoteStorageAdapter {
1010
static final _log = Logger('SupabaseStorageAdapter');
1111

1212
@override

demos/supabase-todolist/lib/models/schema.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import 'package:powersync/powersync.dart';
2-
import 'package:powersync_core/attachments.dart';
2+
import 'package:powersync_core/attachments/attachments.dart';
33

44
const todosTable = 'todos';
55

6-
Schema schema = Schema(([
6+
Schema schema = Schema([
77
const Table(todosTable, [
88
Column.text('list_id'),
99
Column.text('photo_id'),
@@ -23,4 +23,4 @@ Schema schema = Schema(([
2323
Column.text('owner_id')
2424
]),
2525
AttachmentsQueueTable()
26-
]));
26+
]);
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
dartdoc:
22
categories:
33
attachments:
4-
displayName: Attachments
4+
name: Attachments
55
markdown: doc/attachments.md
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
## Attachments
2+
3+
In many cases, you might want to sync large binary data (like images) along with the data synced by
4+
PowerSync.
5+
Embedding this data directly in your source databases is [inefficient and not recommended](https://docs.powersync.com/usage/use-case-examples/attachments).
6+
7+
Instead, the PowerSync SDK for Dart and Flutter provides utilities you can use to _reference_ this binary data
8+
in your primary data model, and then download it from a secondary data store such as S3.
9+
Because binary data is not directly stored in the source database in this model, we call these files _attachments_.
10+
11+
## Alpha release
12+
13+
The attachment helpers described in this document are currently in an alpha state, intended for testing.
14+
Expect breaking changes and instability as development continues.
15+
The attachments API is marked as `@experimental` for this reason.
16+
17+
Do not rely on these libraries for production use.
18+
19+
## Usage
20+
21+
An `AttachmentQueue` instance is used to manage and sync attachments in your app.
22+
The attachments' state is stored in a local-only attachments table.
23+
24+
### Key assumptions
25+
26+
- Each attachment is identified by a unique id.
27+
- Attachments are immutable once created.
28+
- Relational data should reference attachments using a foreign key column.
29+
- Relational data should reflect the holistic state of attachments at any given time. Any existing local attachment
30+
will be deleted locally if no relational data references it.
31+
32+
### Example implementation
33+
34+
See the [supabase todolist](https://github.com/powersync-ja/powersync.dart/tree/main/demos/supabase-todolist) demo for
35+
a basic example of attachment syncing.
36+
37+
### Setup
38+
39+
First, add a table storing local attachment state to your database schema.
40+
41+
```dart
42+
final schema = Schema([
43+
AttachmentsQueueTable(),
44+
// In this document, we assume the photo_id column of the todos table references an optional photo
45+
// stored as an attachment.
46+
Table('todos', [
47+
Column.text('list_id'),
48+
Column.text('photo_id'),
49+
Column.text('description'),
50+
Column.integer('completed'),
51+
]),
52+
]);
53+
```
54+
55+
Next, create an `AttachmentQueue` instance. This class provides default syncing utilities and implements a default
56+
sync strategy. This class can be extended for custom functionality, if needed.
57+
58+
```dart
59+
final directory = await getApplicationDocumentsDirectory();
60+
61+
final attachmentQueue = AttachmentQueue(
62+
db: db,
63+
remoteStorage: SupabaseStorageAdapter(), // instance responsible for uploads and downloads
64+
logger: logger,
65+
localStorage: IOLocalStorage(appDocDir), // IOLocalStorage requires `dart:io` and is not available on the web
66+
watchAttachments: () => db.watch('''
67+
SELECT photo_id as id FROM todos WHERE photo_id IS NOT NULL
68+
''').map((results) => [
69+
for (final row in results)
70+
WatchedAttachmentItem(
71+
id: row['id'] as String,
72+
fileExtension: 'jpg',
73+
)
74+
],
75+
),
76+
);
77+
```
78+
79+
Here,
80+
81+
- An instance of `LocalStorageAdapter`, such as the `IOLocalStorage` provided by the SDK, is responsible for storing
82+
attachment contents locally.
83+
- An instance of `RemoteStorageAdapter` is responsible for downloading and uploading attachment contents to the secondary
84+
service, such as S3, Firebase cloud storage or Supabase storage.
85+
- `watchAttachments` is a function emitting a stream of attachment items that are considered to be referenced from
86+
the current database state. In this example, `todos.photo_id` is the only column referencing attachments.
87+
88+
Next, start the sync process by calling `attachmentQueue.startSync()`.
89+
90+
## Storing attachments
91+
92+
To create a new attachment locally, call `AttachmentQueue.saveFile`. To represent the attachment, this method takes
93+
the contents to store, the media type, an optional file extension and id.
94+
The queue will store the contents in a local file and mark is as queued for uploads. It also invokes a callback
95+
responsible for referencing the id of the generated attachment in the primary data model:
96+
97+
```dart
98+
Future<Attachment> savePhotoAttachment(
99+
Stream<List<int>> photoData, String todoId,
100+
{String mediaType = 'image/jpeg'}) async {
101+
// Save the file using the AttachmentQueue API
102+
return await attachmentQueue.saveFile(
103+
data: photoData,
104+
mediaType: mediaType,
105+
fileExtension: 'jpg',
106+
metaData: 'Photo attachment for todo: $todoId',
107+
updateHook: (context, attachment) async {
108+
// Update the todo item to reference this attachment
109+
await context.execute(
110+
'UPDATE todos SET photo_id = ? WHERE id = ?',
111+
[attachment.id, todoId],
112+
);
113+
},
114+
);
115+
}
116+
```
117+
118+
## Deleting attachments
119+
120+
To delete attachments, it is sufficient to stop referencing them in the data model, e.g. via
121+
`UPDATE todos SET photo_id = NULL` in this example. The attachment sync implementation will eventually
122+
delete orphaned attachments from the local storage.

packages/powersync_core/lib/attachments.dart

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// Imports for attachments that are available on all platforms.
2+
///
3+
/// For more details on using attachments, see the documentation for the topic.
4+
///
5+
/// {@category attachments}
6+
library;
7+
8+
export '../src/attachments/attachment.dart';
9+
export '../src/attachments/attachment_queue_service.dart';
10+
export '../src/attachments/local_storage.dart';
11+
export '../src/attachments/remote_storage.dart';

packages/powersync_core/lib/src/attachments/attachment.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import 'package:powersync_core/sqlite3_common.dart' show Row;
99
import 'package:powersync_core/powersync_core.dart';
1010

1111
/// Represents the state of an attachment.
12+
///
13+
/// {@category attachments}
1214
@experimental
1315
enum AttachmentState {
1416
/// The attachment is queued for download from the remote storage.
@@ -54,6 +56,8 @@ enum AttachmentState {
5456
/// - [size]: Size of the attachment in bytes, if available.
5557
/// - [hasSynced]: Indicates whether the attachment has been synced locally before.
5658
/// - [metaData]: Additional metadata associated with the attachment.
59+
///
60+
/// {@category attachments}
5761
@experimental
5862
final class Attachment {
5963
/// Unique identifier for the attachment.
@@ -141,6 +145,11 @@ final class Attachment {
141145
}
142146

143147
/// Table definition for the attachments queue.
148+
///
149+
/// The columns in this table are used by the attachments implementation to
150+
/// store which attachments have been download and tracks metadata for state.
151+
///
152+
/// {@category attachments}
144153
@experimental
145154
final class AttachmentsQueueTable extends Table {
146155
AttachmentsQueueTable({

0 commit comments

Comments
 (0)