Writing Data Source records

Writing Data Source records

Data Source connections write individual records or batches. This reference describes request shapes, results and the difference between a server response, a queued submission and a native local change.

Before you start

Load the Data Sources library and configure access rules for the intended app identity. Use a disposable source for replacement and deletion examples. An administrative request or native cached result does not establish app-user authorization.

The examples use a source named Equipment requests with Item, Quantity and Status columns. Column names do not impose a typed schema. Read IDs from actual returned records rather than guessing them.

For a write that must receive an online server response, open the connection with { offline: false }. Set insert’s separate { offline: false } option to prevent a failed submission from being queued. Leave update’s allowOffline option false. A rejected request can still follow a server-side effect, so inspect persisted state before retrying a write.

const requests = await Fliplet.DataSources.connectByName('Equipment requests', {
  offline: false
});

try {
  const created = await requests.insert({
    Item: 'Portable monitor',
    Quantity: 1,
    Status: 'Requested'
  }, { offline: false });

  if (!created || !created.id || !created.data) {
    throw new Error('The submission did not return a server record.');
  }

  const saved = await requests.findById(created.id);
  console.log('Persisted request:', saved.id, saved.data);
} catch (error) {
  console.error('Check the response and persisted state before retrying:', error);
}

The read-back requires read permission. A successful write can still be followed by a denied read; record those outcomes separately. For files, use the attachment reference.

Individual writes

insert(data, options?)

connection.insert(data, options?) submits a new record. data is a flat object of column values or a FormData object for attachments. It is not a { data: ... } record envelope. Missing/falsy data throws synchronously; validation and network failures occur asynchronously.

Option Type Default Behavior
offline Boolean Unspecified false prevents queueing a failed submission. Other values allow the insertion queue where the input can be stored. This differs from the connection’s native database option.
ack Boolean Unspecified Attempts native local reconciliation after a server result. Native database connections also attempt reconciliation automatically. Single-write reconciliation requires a working native database connection; ack: true does not enable it on an offline: false connection. It does not establish server persistence.
folderId Number Unspecified Target media folder for uploaded attachments.
flUserToken String Unspecified Existing Fliplet user-token context forwarded with the request. It is not an app role or arbitrary identity claim.
public Boolean Unspecified true requests a shared-content slug. Requires an app context and the appropriate shared-content configuration. It does not grant Data Source permissions.
source String Unspecified Context forwarded to server hooks. It is caller-supplied, not trusted attribution.

Possible resolved outcomes:

Outcome Shape or observation Meaning
Online server record { id, dataSourceId, data, ... } The server returned the saved entry. Fields can be transformed, excluded or redacted.
Native pending record { id: 'temp_...', data, _pending: true, _tempId: 'temp_...', ... } A local optimistic entry awaits a server result and ID reconciliation.
Stored queued submission Result of local queue storage; not guaranteed to contain id or data The input was accepted locally after a failed send. It is not a saved server entry.

Development sample mode returns the submitted flat data rather than a server entry. Do not use that mode to verify persistence or security.

update(id, data, options?)

connection.update(id, data, options?) updates an existing record. Online single-record updates shallowly merge the submitted column values into stored data; omitted fields remain and null is a value. Nested objects are replaced as values rather than deeply merged.

Argument or option Type Default Behavior
id Number or 'session' Required Existing entry ID. 'session' targets the authenticated row for this source when that login exists.
data Object or FormData Required Flat column values or attachment input.
allowOffline Boolean False when omitted Requests required-request handling and, on request failure, resolves an { id, dataSourceId, data, offline: true } result instead of rejecting. It does not prove the server accepted the update.
ack Boolean Unspecified Attempts local reconciliation only when native database support is active on the connection; also automatic for those connections. ack: true does not override offline: false.
folderId Number Unspecified Media folder for attachments.
public Boolean Unspecified true requests shared-content slug creation where supported.
source String Unspecified Caller-supplied server-hook context.

An ordinary online success resolves the server entry. Native reconciliation can return a local update result rather than the same complete envelope. Development sample mode resolves without a record. Read the saved entry separately when the complete persisted state matters.

const requests = await Fliplet.DataSources.connectByName('Equipment requests', {
  offline: false
});
const pending = await requests.findOne({ where: { Status: 'Requested' } });

if (pending) {
  await requests.update(pending.id, { Status: 'Collected' });
  const saved = await requests.findById(pending.id);
  console.log('Stored status:', saved.data.Status);
}

Object update requirements evaluate stored rows. They do not validate every proposed transition. Read operation-specific security before using a status field as an approval workflow.

removeById(id, options?)

connection.removeById(id, { ack? }) deletes one entry. A missing/falsy ID throws synchronously. An online missing entry rejects with HTTP 404; authorization and source permission failures can reject with HTTP 400.

Online success resolves {}. With native reconciliation enabled by ack or the connection, the result can instead be the removed local entry. This requires native database support to remain active; ack: true does not override an offline: false connection. Development sample mode resolves without a value. No connection.remove() method is provided by the online connection; the native collection API is separate.

Writes selected by a query

connection.query(data, options?) is an alias for find. For writes, data.type selects update or delete. These calls require query admission and permission for the requested write operation; they are distinct from single-record writes.

Field Type Behavior
type String 'update' or 'delete'.
where Object Filters data-column values. An omitted or broad filter can affect the whole source.
data Object Required for update; flat patch shallowly merged into each matched row.
options.returnAsObject Boolean Returns { entries, deletedEntries }; deletedEntries is not the list of IDs deleted by this request.
options.ack Boolean For query deletion, attempts removal of returned deleted IDs from a supported local database.

An online update normally resolves an array of updated entries. Online query deletion normally resolves [], not a count or removed-row list. Do not infer the deleted count from that empty array. returnAsObject changes the SDK envelope but does not expose the endpoint’s deleted ID list. With query deletion and ack: true, a local reconciliation failure can resolve { entries, deletedEntries } even when returnAsObject is omitted. Successful reconciliation returns the entries array instead of retaining an includePagination root response. Handle these branches or omit ack when consuming a predictable online response. Native local find routing is not an online query-update implementation; use an online connection for query writes.

const requests = await Fliplet.DataSources.connectByName('Equipment requests', {
  offline: false
});
const updated = await requests.query({
  type: 'update',
  where: { Status: 'Collected' },
  data: { Status: 'Archived' }
});
console.log('Updated entries:', updated.length);

Query write authorization, returned fields and hook behavior must be tested independently from update(id, ...). See testing security.

Appending and replacing a batch

Both methods accept an array of flat column objects, not record envelopes. A top-level input id is treated as an entry ID rather than a data-column value. Prefer omitting it for new rows.

Method Arguments Result and side effects
append(entries?, options?) Array; { runHooks?: Boolean } Adds rows and preserves existing rows. Omitted entries defaults to []. Online result is an array of created entry envelopes. Server runHooks defaults to true.
replaceWith(entries?) Array Removes existing rows and imports the supplied dataset. Omitted entries defaults to [], which clears the source when authorized. Online result is an array of created entries. No options argument is implemented.

These operations can expand declared column names. They do not enforce column value types. Replacement requires deletion permission in addition to insertion permission for nonempty rows. Neither method supplies an atomic rollback guarantee. A failure can follow deletion or partial mutation.

const requests = await Fliplet.DataSources.connectByName('Equipment requests', {
  offline: false
});
const created = await requests.append([
  { Item: 'USB-C adapter', Quantity: 2, Status: 'Requested' },
  { Item: 'Headset', Quantity: 1, Status: 'Requested' }
]);
console.log('Created records:', created.map(entry => entry.id));

For replacement, back up the source and verify the complete desired dataset before calling replaceWith. Development sample mode has different results and does not establish the online contract.

commit(data, columns?)

connection.commit(data, columns?) sends several changes in one request. It is not an atomic transaction: deletion, insertion, updating and hook work do not share a universal rollback boundary. Never retry the entire commit automatically after an ambiguous failure.

The entry shape differs from append/replace: commit receives { data: { ... } } for new rows and { id, data: { ... } } for updates. An array shorthand becomes { entries: data, columns: columns || [] }. The second columns argument applies only to that shorthand.

Deletion defaults are consequential. Without append: true or an explicit delete array, rows omitted from entries can be removed. An empty/default commit can clear the source. Set append: true for an insert/update-only commit, or explicitly provide the IDs to delete.

Field Type Default Behavior
entries Array [] when omitted Wrapped new/updated entries. A supplied nonarray rejects HTTP 400. Provide entries or delete.
entries[].id Number Omitted Existing entry ID for updates. Without an ID, data creates a new entry.
entries[].data Object No default Column values. Prefer complete values unless using extend.
entries[].order Number Unspecified Entry order for a supplied row.
entries[].clientId String Unspecified Caller correlation value for a new entry; returned alongside the assigned server ID.
append Boolean False when omitted true skips removal when no explicit delete array is present.
delete Array of IDs Unspecified Deletes the specified IDs instead of treating omitted rows as removed. An empty array explicitly requests no deletions.
extend Boolean False when omitted Shallowly merges update data with stored data. With update hooks enabled the handler also merges values, so use extend: true explicitly for patch semantics.
runHooks Array of strings [] Operation names such as ['insert', 'update']. It is not append’s Boolean option.
returnEntries Boolean True unless explicitly false false omits the returned dataset. It does not grant access or enforce privacy.
columns Array of strings Inferred/merged when omitted Explicit list replaces the declared column list and is augmented from row data. These names are not a typed schema.
source String Unspecified Caller-supplied context for server hooks.
normalizeOrder Object Unspecified { gap: positiveSafeInteger } renumbers existing order values. A no-entry normalization requires append: true or an explicit delete array to prevent unintended removal. Excessive gaps reject.

Use a disposable source with read/write permission for this example:

const requests = await Fliplet.DataSources.connectByName('Equipment requests', {
  offline: false
});
const result = await requests.commit({
  entries: [
    { clientId: 'monitor-request', data: {
      Item: 'Portable monitor', Quantity: 1, Status: 'Requested'
    } }
  ],
  append: true,             // preserve rows not supplied in this request
  runHooks: ['insert'],     // server hook operation names, not a Boolean
  returnEntries: false     // request only the correlation response
});
const mapping = (result.clientIds || []).find(row => row.clientId === 'monitor-request');
if (mapping) {
  const saved = await requests.findById(mapping.id);
  console.log('Persisted new row:', saved.id, saved.data);
}

The online result is { entries?, clientIds: [{ id, clientId }, ...] }. By default, entries can contain the source dataset, not only changed rows. Hashed fields are redacted, but query-read filters and projections must not be assumed to protect this write response. Test returned rows and columns as each actor before enabling commits on private data. A cooperative caller’s returnEntries: false is not authorization enforcement. See write-response tests.

Hooks and completion

Client hooks and server hooks have separate ownership:

Method Client hook Server behavior relevant to completion
insert / update insert / update Server entry hooks can run in the background after saving. A record response does not prove downstream actions completed.
append append Server operation hooks requested by Boolean runHooks, default true. Setting false still runs the client hook.
replaceWith No explicit package hook call No implemented options argument for selecting hooks.
commit commit Selected server operations from runHooks array. Update-hook work is not a guarantee that every downstream action completed before response.
query writes / removeById No equivalent insert/update client hook call Do not assume the single-record hook pipeline is reused.

Hooks can transform values; client hooks and caller-supplied source cannot establish trusted identity or universal authorization. Verify each write path the app exposes.

Failures and recovery

Failures are not uniformly HTTP 403. Source permissions, malformed batches and query failures can reject HTTP 400; missing IDs can reject 404; file validation can reject 422; throttling can reject 429. insert can wrap permanent 401/422 responses as { code: 2, message } and multipart send failures as { code: 1, message }. Preserve the actual error shape rather than assuming a status property always exists.

Show pending/queued writes as pending. Inspect server records before retrying a write after a timeout, network failure or non-atomic batch error. Prevent duplicate submissions in the UI and use application-level duplicate detection where needed. Do not use an unbounded retry loop. Keep local state, server response and persisted verification as separate observations.

Back to Data Sources