Fliplet.DataSources
Fliplet.DataSources methods, query options, return values and errors for connecting to a data source and reading and writing its entries.
Fliplet.DataSources lets screen code list, create and change data sources, and connect to one to read and write its entries. Most methods return a promise; the ones that return a value directly or throw are marked.
Applies to: Fliplet JavaScript SDK, library fliplet-datasources version 1.0, in web and native app screens. Role: developers writing screen code. Server: App Actions can list the library as a dependency, but how it runs in an App Action that runs on the server is [UNVERIFIED: server App Action runtime not traced].
Where it runs and authentication
- The library is the
fliplet-datasourcesasset, version1.0. It loadsvendor/sift.js,vendor/mingo.js,database.jsanddatasources.js, and depends onfliplet-coreandfliplet-utils. fliplet-corecreatesFliplet.DataSourcesand adds the offline queue methods.fliplet-datasourcesadds everything else to the same object.- An App Action whose code uses
Fliplet.DataSourcesfails validation when saved unlessfliplet-datasourcesis in itsdependencies. An App Action'senvironmentisserver,clientorany, and defaults toserver. - Online calls are HTTP requests to the
v1/data-sourcesendpoints of the Fliplet API, sent withFliplet.API.request. Connection calls add theappIdandpageIdfromFliplet.Envto the query string. - Which credentials a call sends is [UNVERIFIED: Fliplet.API.request credentials not traced]. See Fliplet.API.
- The server checks the data source's access rules on every query. A role without query permission gets
Your role does not allow advanced querying from this data source. - When
Fliplet.Env.get('development')is true, for example in local CLI development, methods return built-in sample data instead of calling the API.
Connect to a data source
Fliplet.DataSources.connect(dataSourceId, options)
Fliplet.DataSources.connectByName(name, options)
Both resolve with a connection object, used by every method under Read entries and Write entries.
| Parameter | Type | Required | Description |
|---|---|---|---|
dataSourceId | number | Yes | The data source ID. An object as the first argument uses its name instead. |
name | string | Yes, for connectByName | The data source name. connectByName is connect with options.name set. |
options.offline | boolean | No | On native apps, unless set to false, reads use the on-device database. Web is always online. |
- In Fliplet Studio, find the ID in the ID column of the Data Sources dialog. Open it with the Data Sources button in the project sidebar. The same dialog has an Available offline column.
- With no ID,
connectrejects with the stringData source ID is required. - If the on-device database can't be opened, the connection falls back to the online API for that call and later ones.
- A second
connectwith the same ID or name and the sameofflinevalue resolves with the cached connection.Fliplet.DataSources.clearActiveConnections()empties the cache and returns nothing.
Read entries
| Method | Resolves with |
|---|---|
connection.find(query, options) | An array of entries. |
connection.query(query, options) | Same as find. |
connection.findOne(query, options) | The first entry, or undefined. Runs find with limit: 1. |
connection.findById(id) | One entry. |
connection.findWithCursor(query, options) | A cursor (see Paging with a cursor). |
connection.getIndex(column) | An array of the unique values in one column. |
connection.getIndexes(columns) | An object of unique values, keyed by column. |
Each entry has an id and a data object of column values. The other fields the server returns are [UNVERIFIED: full entry fields not traced].
find sends the query to POST v1/data-sources/:id/data/query. query can also be a JSON string.
Find options
| Option | Type | Default | Description |
|---|---|---|---|
cache | boolean | none | Passed to the request. |
authenticate | boolean | true on screens | Passes every value in entry.data through Fliplet.Media.authenticate. Applied on on-device reads; online reads without type return entries as the server sent them. Turned off for type: 'delete'. |
returnAsObject | boolean | false | Online only. Resolves with { entries, deletedEntries }. |
ack | boolean | false | With type: 'delete', also removes the deleted entries from the on-device database. The result shape with ack is [UNVERIFIED: differs on web, see review]. |
On-device reads (native)
On native with the on-device database, where is matched on the device with the same Sift filter. Then only offset, limit, attributes, aggregate and join are applied, and the result is always a plain array. order, distinct, includePagination and returnAsObject don't apply on the device. To use them on native, connect with offline: false. type: 'delete' queries always go online. The order in which limit and aggregate apply differs between the device and the server [UNVERIFIED: order of operations not verified].
Paging with a cursor
findWithCursor resolves with a cursor: an array of the current page's entries with these members.
| Member | Returns | Description |
|---|---|---|
currentPage, isFirstPage, isLastPage, query, firstRecordInNextPage | value | The cursor's state. |
containsId(id) | boolean | Whether the page holds that entry. |
next(), prev(), setPage(page) | the cursor | Move offset by limit. They don't fetch. |
update({ keepExisting }) | a promise | Runs the query, fetching one extra entry to set isLastPage, and replaces the entries unless keepExisting is true. |
const connection = await Fliplet.DataSources.connect(123, { offline: false });
const cursor = await connection.findWithCursor({ limit: 20 });
if (!cursor.isLastPage) {
await cursor.next().update();
}
Query options
| Key | Type | Description |
|---|---|---|
where | object | Filter on column values (see Where operators). |
order | array | [column, direction] pairs (see Sorting). |
limit | number | 0 or more. 0 or no value means no limit. |
offset | number | 0 or more. |
attributes | array | Column names to keep in each entry's data. |
distinct | string | A column name. Keeps the first entry for each value. |
aggregate | array | A Mingo aggregation pipeline, run after where. |
join | object | Named joins (see Joins). |
includePagination | boolean | Online only. find resolves with the whole response (see Pagination response). |
id, createdAt, updatedAt | any | Filter on those entry fields. |
type | string | select (default), delete or update (see Delete and update queries). |
Invalid limit or offset values fail with Invalid limit value. Must be a positive number. or Invalid offset value. Must be a non-negative number.
Where operators
where is a Mongo-style filter on column values, matched against each entry's data with the bundled Sift library. On the server, some conditions are first turned into SQL [UNVERIFIED: SQL and Sift results not compared].
Operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $mod, $like (case-sensitive substring), $iLike (case-insensitive substring), $regex with $options, $not, $type, $all, $size, $or, $nor, $and, $elemMatch, $exists, $match, $where and $filters.
The server rejects a string $where with Invalid "where" query. A column holding a password or login code can only be filtered by an exact value: This column can only be filtered by an exact value.
const entries = await connection.find({
where: {
Office: 'London',
Name: { $iLike: 'smith' }
}
});
$filters conditions
$filters is an array of { column, condition, value } objects. An entry must pass all of them.
| Condition | Value |
|---|---|
==, !=, >, >=, <, <= | A value to compare. |
contains, notcontain, notcontains, regex | A string or pattern. |
between | { from, to } |
oneof, notoneof, notin | A list, or comma-separated values. |
empty, notempty | No value needed. |
dateis, datebefore, dateafter | A date, with an optional unit. |
datebetween | from: { value, unit } and to: { value, unit } |
none | Matches every entry. |
- An unknown condition matches no entries.
column: 'none'matches every entry. - For
==,!=,>,>=,<,<=,contains,notcontain,notcontainsandregex, a string value and the entry value are lowercased before comparing.
const entries = await connection.find({
where: {
$filters: [
{ column: 'Status', condition: '==', value: 'Active' },
{ column: 'Score', condition: 'between', value: { from: 5, to: 10 } }
]
}
});
Sorting
order is an array of [column, direction] pairs.
- Column is
data.followed by a column name, or one ofid,order,createdAt,deletedAtorupdatedAt. - For
data.columns, direction isASC(default) orDESC. Other directions fail withInvalid order direction: {direction}. - An unknown entry field, a name with characters other than letters, digits, space,
_or-, or a password or login-code column fails withInvalid order column name: {column}. - A column the data source doesn't have fails with
Column '{column}' does not exists[UNVERIFIED: only when the data source has a column list]. - Sorting by a
data.column puts numbers and text in separate groups, text first whenASC. - Without
order, entries are sorted byorderascending, theniddescending.
Joins
Each named join needs dataSourceId or dataSourceName. Otherwise the request fails with One or more of your joins has errors: {names}. Online joins run on the server, and their options and result shape are [UNVERIFIED: server joins not traced].
On the device, a join accepts where, on (a map of this entry's key to the joined entry's key), count, has, sum (a column to add up), limit, attributes and order, and the result goes in entry.joins[name] [UNVERIFIED: behavior with several joins or without on].
Pagination response
With includePagination: true, find resolves with:
{
"entries": [],
"dataSourceId": 123,
"count": 0,
"source": "writer",
"pagination": { "total": 0, "limit": 20, "offset": 0 },
"indexes": {},
"deletedEntries": []
}
Delete and update queries
type: 'delete'needs awhere, otherwiseA "where" condition is required to delete data. The server answers{ entries: [], count, deleted }with the deleted IDs, andfindresolves with itsentries, an empty array.type: 'update'needs adataobject, applied to the matching entries. Whatfindresolves with is [UNVERIFIED: update response not traced].
Write entries
insert
connection.insert(data, options)
Adds one entry. Throws Data is required when data is missing.
| Option | Description |
|---|---|
offline | false refuses to queue the entry while the device is offline. |
folderId, public, source | Sent in the query string. |
flUserToken | Sent in the query string [UNVERIFIED: what flUserToken does]. |
ack | Accepted. The device copy is only written when the on-device database is in use. |
- Sends
PUT v1/data-sources/:id/data?appId=with the app ID (:idis the ID or name you connected with).FileorBlobvalues and base64 data URLs are sent as multipart form data. - Resolves with the saved entry. On native with the on-device database, a queued insert resolves with a temporary entry whose
idstarts withtemp_. Otherwise a queued insert resolves with the result of saving the queue, not an entry. - If the request fails with anything but 401 or 422, the entry is queued in device storage under
flDataSourcesQueueand sent when the device is back online. - 401 and 422 reject with
{ code: 2, message }, wheremessageis the parsed server error. datagiven as aFormDataobject can't be queued and rejects with{ code: 1, message }, wheremessageis the original error orCannot send the data. A plain object holding files is queued.- With
offline: falseand no connection, it rejects with{ code: 2, message: 'Entry does not support being saved offline.' }. - When the temporary entry is replaced by the saved one, the hook
dataSourceEntryReconciledruns with{ dataSourceId, tempId, realId, entry }.Fliplet.DataSources.isTempId(id)returnstruefor a temporary ID. - Inserting through a connection made with
connectByNamesends the name in the URL [UNVERIFIED: server lookup by name on insert not traced].
update and removeById
| Method | Description | Resolves with |
|---|---|---|
connection.update(id, data, options) | PUT v1/data-sources/:id/data/:entryId. Files in data are sent as multipart. | The updated entry, or the result of the device write when the on-device database holds the entry. |
connection.removeById(id, options) | DELETE v1/data-sources/:id/data/:entryId. Throws Data source entry ID is required without id. | The API response, or the result of the device write when the on-device database holds the entry. |
update options: folderId, public, source, ack (accepted, no extra effect), and allowOffline. With allowOffline, a failed request resolves with { id, dataSourceId, data, offline: true } instead of rejecting. removeById accepts ack with no extra effect.
Bulk writes
| Method | Description | Resolves with |
|---|---|---|
connection.append(entries, options) | Adds an array of entries. options.runHooks defaults to true. | The API response. |
connection.replaceWith(entries) | Replaces the data source's entries with entries. | The API response. |
connection.commit(data, columns) | Sends several changes in one request to POST .../commit. | [UNVERIFIED: commit response not traced] |
connection.import(formData) | Posts a file of entries as multipart. | The API response. |
> [!CAUTION] > This cannot be undone. replaceWith, and commit without delete or append: true, remove every entry you don't send.
commit takes an array of entries (sent as { entries, columns }) or an object. Entries with an id are updated and entries without one are inserted. delete is an array of entry IDs. With append: true and no delete, other entries are kept. With neither, entries not listed are removed. [UNVERIFIED: delete-by-ID handling outside the checked code]
Commit errors:
The "entries" attribute is required to be an array.
You must provide either the "entries" or "delete" attribute.
Your role does not allow writing to this data source.
Replacing every entry is not allowed on this data source. Send "append": true to add entries.
Subscribe to changes
const subscription = connection.subscribe(options, callback);
subscribe returns the subscription directly, not a promise. options can be left out.
| Option | Description |
|---|---|
events | Any of insert, update, delete. Defaults to all three. |
cursor | Limit to entries in a findWithCursor cursor. |
id | One ID, an array of IDs or a Sift query [UNVERIFIED: id filtering may not work as intended]. |
filter | A function that returns true for entries to include. |
- The callback receives
{ inserted, updated, deleted }. Each key is present only when it has entries. An entry counts as inserted when itscreatedAtequals itsupdatedAt. - Changes arrive through the
dataSourceUpdatedhook. Without the on-device database, the connection also checks the server every 30 seconds. - The subscription has
status()(activeorpaused),pause(),resume(),unsubscribe(),updateOptions(options)andisInScope(entry). subscribethrowsIncremental updates must be enabled in this app to subscribe to data source updates.when the app doesn't support incremental updates, andA callback function must be provided for subscription.without a callback.
Manage data sources
| Method | Description | Resolves with |
|---|---|---|
Fliplet.DataSources.get(params, options) | GET v1/data-sources. params.attributes (array or comma list) limits the fields. An object params.definition is sent as JSON and turns off caching. options.cache defaults to true. | An array of data sources. |
Fliplet.DataSources.getById(id, options) | Options attributes, params (extra query parameters) and cache (default true). | One data source. |
Fliplet.DataSources.create(options) | Posts a new data source with no access rules (accessRules: []) unless you pass them. | The new data source. |
Fliplet.DataSources.update(id, data) | Also accepts update({ id, ...data }). Clears the API cache. Throws Data source ID is required without an ID. | The updated data source. |
Fliplet.DataSources.delete(id) | DELETE v1/data-sources/:id. | The API response. |
Fliplet.DataSources.fetchWithOptions(options) | See the next table. | { dataSource, dataSourceEntries } |
fetchWithOptions connects online and runs find(query).
| Option | Description |
|---|---|
dataSourceId | Required. Without it, rejects with the string Data source ID is required. |
query | Passed to find. With query.aggregate, resolves with the raw find result instead. |
columns | Picks or renames columns: 'Name' or { newName: 'Column' }. |
compact | Defaults to true. Drops empty values. |
cache | Defaults to false. |
Hooks, users, validation and the offline queue
Fliplet.DataSources.bindHooks(id, hooks)registers client-side functions keyed by method (get,insert,update,append,commit) that run on entries before they're used or sent. It returns directly. Binding twice throwsHooks have already been bound to data source {{id}}, and a non-object throwsAn object is required.Fliplet.DataSources.hasHook(id)returns a boolean directly.Fliplet.DataSources.applyHooks(id, method, entries)resolves straight away when no hook is bound, otherwise runs the hook on each entry.connection.getUsers()resolves with the data source's user roles,connection.addUserRole(data)with the new role, andconnection.removeUserRole(userId)with the API response. They usev1/data-sources/:id/users.connection.sendValidation(data)andconnection.validate(data)post tosend-validationandvalidateand resolve with the API response.validaterefreshes the cached user session first.Fliplet.DataSources.getQueue(),clearQueue({ appId }),processQueue()andhasFiles(data)read, clear, send and inspect the offline insert queue.enqueueandsaveQueueare also exposed [UNVERIFIED: enqueue and saveQueue behavior not described].
Errors
| Message | Form | When |
|---|---|---|
Data source ID is required | Rejected string | connect or fetchWithOptions without an ID. |
Data source ID is required | Thrown Error | Fliplet.DataSources.update without an ID. |
Data is required | Thrown Error | insert without data. |
Data source entry ID is required | Thrown Error | removeById without an ID. |
Entry does not support being saved offline. | { code: 2, message } | insert with offline: false while offline. |
Cannot send the data | { code: 1, message } | insert with FormData that fails, when there is no original error. |
Data source not found | Rejection | getById in development mode only. |
The query is not allowed | Server error | The query isn't allowed for this role. |
Your role does not allow reading from this data source. | Server error | A read without read permission. |
Your role does not allow advanced querying from this data source. | Server error | A query without query permission. |
You cannot delete this entry as it belongs to a different user. | Server error | Deleting another user's entry. |
subscribe and bindHooks also throw Error synchronously, with the messages in their sections. How a server error reaches your code is [UNVERIFIED: shape of server errors not traced].
Example
const connection = await Fliplet.DataSources.connect(123, { offline: false });
const people = await connection.find({
where: { Office: 'London' },
order: [['data.Name', 'ASC']],
limit: 50
});
const entry = await connection.insert({
Name: 'Alex Morgan',
Office: 'London'
});
See also
- Data Sources: the feature reference this page belongs to.
- How to use Data Sources: create a data source and use it from screen code.
- Secure Data Sources: the access rules that decide which calls succeed.
- Data Source entry endpoints: the REST endpoints these methods call.
- Data Source access and permission errors: fix the server errors on this page.