Fliplet Help

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-datasources asset, version 1.0. It loads vendor/sift.js, vendor/mingo.js, database.js and datasources.js, and depends on fliplet-core and fliplet-utils.
  • fliplet-core creates Fliplet.DataSources and adds the offline queue methods. fliplet-datasources adds everything else to the same object.
  • An App Action whose code uses Fliplet.DataSources fails validation when saved unless fliplet-datasources is in its dependencies. An App Action's environment is server, client or any, and defaults to server.
  • Online calls are HTTP requests to the v1/data-sources endpoints of the Fliplet API, sent with Fliplet.API.request. Connection calls add the appId and pageId from Fliplet.Env to 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.

ParameterTypeRequiredDescription
dataSourceIdnumberYesThe data source ID. An object as the first argument uses its name instead.
namestringYes, for connectByNameThe data source name. connectByName is connect with options.name set.
options.offlinebooleanNoOn 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, connect rejects with the string Data 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 connect with the same ID or name and the same offline value resolves with the cached connection. Fliplet.DataSources.clearActiveConnections() empties the cache and returns nothing.

Read entries

MethodResolves 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

OptionTypeDefaultDescription
cachebooleannonePassed to the request.
authenticatebooleantrue on screensPasses 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'.
returnAsObjectbooleanfalseOnline only. Resolves with { entries, deletedEntries }.
ackbooleanfalseWith 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.

MemberReturnsDescription
currentPage, isFirstPage, isLastPage, query, firstRecordInNextPagevalueThe cursor's state.
containsId(id)booleanWhether the page holds that entry.
next(), prev(), setPage(page)the cursorMove offset by limit. They don't fetch.
update({ keepExisting })a promiseRuns 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

KeyTypeDescription
whereobjectFilter on column values (see Where operators).
orderarray[column, direction] pairs (see Sorting).
limitnumber0 or more. 0 or no value means no limit.
offsetnumber0 or more.
attributesarrayColumn names to keep in each entry's data.
distinctstringA column name. Keeps the first entry for each value.
aggregatearrayA Mingo aggregation pipeline, run after where.
joinobjectNamed joins (see Joins).
includePaginationbooleanOnline only. find resolves with the whole response (see Pagination response).
id, createdAt, updatedAtanyFilter on those entry fields.
typestringselect (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.

ConditionValue
==, !=, >, >=, <, <=A value to compare.
contains, notcontain, notcontains, regexA string or pattern.
between{ from, to }
oneof, notoneof, notinA list, or comma-separated values.
empty, notemptyNo value needed.
dateis, datebefore, dateafterA date, with an optional unit.
datebetweenfrom: { value, unit } and to: { value, unit }
noneMatches every entry.
  • An unknown condition matches no entries. column: 'none' matches every entry.
  • For ==, !=, >, >=, <, <=, contains, notcontain, notcontains and regex, 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 of id, order, createdAt, deletedAt or updatedAt.
  • For data. columns, direction is ASC (default) or DESC. Other directions fail with Invalid 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 with Invalid 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 when ASC.
  • Without order, entries are sorted by order ascending, then id descending.

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 a where, otherwise A "where" condition is required to delete data. The server answers { entries: [], count, deleted } with the deleted IDs, and find resolves with its entries, an empty array.
  • type: 'update' needs a data object, applied to the matching entries. What find resolves 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.

OptionDescription
offlinefalse refuses to queue the entry while the device is offline.
folderId, public, sourceSent in the query string.
flUserTokenSent in the query string [UNVERIFIED: what flUserToken does].
ackAccepted. 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 (:id is the ID or name you connected with). File or Blob values 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 id starts with temp_. 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 flDataSourcesQueue and sent when the device is back online.
  • 401 and 422 reject with { code: 2, message }, where message is the parsed server error.
  • data given as a FormData object can't be queued and rejects with { code: 1, message }, where message is the original error or Cannot send the data. A plain object holding files is queued.
  • With offline: false and 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 dataSourceEntryReconciled runs with { dataSourceId, tempId, realId, entry }. Fliplet.DataSources.isTempId(id) returns true for a temporary ID.
  • Inserting through a connection made with connectByName sends the name in the URL [UNVERIFIED: server lookup by name on insert not traced].

update and removeById

MethodDescriptionResolves 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

MethodDescriptionResolves 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.

OptionDescription
eventsAny of insert, update, delete. Defaults to all three.
cursorLimit to entries in a findWithCursor cursor.
idOne ID, an array of IDs or a Sift query [UNVERIFIED: id filtering may not work as intended].
filterA 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 its createdAt equals its updatedAt.
  • Changes arrive through the dataSourceUpdated hook. Without the on-device database, the connection also checks the server every 30 seconds.
  • The subscription has status() (active or paused), pause(), resume(), unsubscribe(), updateOptions(options) and isInScope(entry).
  • subscribe throws Incremental updates must be enabled in this app to subscribe to data source updates. when the app doesn't support incremental updates, and A callback function must be provided for subscription. without a callback.

Manage data sources

MethodDescriptionResolves 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).

OptionDescription
dataSourceIdRequired. Without it, rejects with the string Data source ID is required.
queryPassed to find. With query.aggregate, resolves with the raw find result instead.
columnsPicks or renames columns: 'Name' or { newName: 'Column' }.
compactDefaults to true. Drops empty values.
cacheDefaults 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 throws Hooks have already been bound to data source {{id}}, and a non-object throws An 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, and connection.removeUserRole(userId) with the API response. They use v1/data-sources/:id/users.
  • connection.sendValidation(data) and connection.validate(data) post to send-validation and validate and resolve with the API response. validate refreshes the cached user session first.
  • Fliplet.DataSources.getQueue(), clearQueue({ appId }), processQueue() and hasFiles(data) read, clear, send and inspect the offline insert queue. enqueue and saveQueue are also exposed [UNVERIFIED: enqueue and saveQueue behavior not described].

Errors

MessageFormWhen
Data source ID is requiredRejected stringconnect or fetchWithOptions without an ID.
Data source ID is requiredThrown ErrorFliplet.DataSources.update without an ID.
Data is requiredThrown Errorinsert without data.
Data source entry ID is requiredThrown ErrorremoveById 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 foundRejectiongetById in development mode only.
The query is not allowedServer errorThe query isn't allowed for this role.
Your role does not allow reading from this data source.Server errorA read without read permission.
Your role does not allow advanced querying from this data source.Server errorA query without query permission.
You cannot delete this entry as it belongs to a different user.Server errorDeleting 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

Open this page in the app: /developer/javascript/data-sources