Datatypes, not tables
AppEngine does not hand out a fixed set of tables. Every record has a datatype —
sf_product, reservation, customer, or one you define — and the same handful
of calls work on all of them. That is the part worth understanding first: you are
not limited to the datatypes the platform ships with.
Records come back as plain maps with the fields under data. The client does not
impose models, because your app knows its own shapes better than a generic
client can.
Reading
final page = await appmint.repository.find(
'sf_order',
filter: {'data.status': 'open'},
pageSize: 20,
sort: {'createdate': -1},
);
page.items; // List<Map<String, dynamic>>
page.total;
page.hasMore;filter is passed through as-is, so it takes the query shape the server
understands, including operators:
filter: {'data.total': {r'$gt': 100}}One record by id:
final order = await appmint.repository.findById('sf_order', id);It can return records that merely resemble the value you passed. Read what
comes back and confirm each record is the one you meant before acting on it —
and never pipe its results straight into delete. That mistake has destroyed
live records.
Writing
final created = await appmint.repository.create('my_datatype', {'title': 'Hello'});
await appmint.repository.update('my_datatype', id, {'title': 'Replaced'});
await appmint.repository.updateFields('my_datatype', id, {
'data.title': 'Renamed',
'data.settings.theme': 'dark',
});
await appmint.repository.delete('my_datatype', id);Two things that cost people an afternoon if nobody says them:
updateFields takes dot paths, not a nested map. Passing
{'data': {'settings': {'theme': 'dark'}}} replaces that whole branch and drops
its other keys, quietly.
Create is a PUT on the server, not a POST. The client handles that; you
only meet it if you call the endpoint yourself and get a 404.
Everything else
The package wraps the calls every app needs. Anything else is one line away, with headers and tokens still handled:
await appmint.http.post('/storefront/pos/tab/$id/settle', body: {...});
await appmint.http.get('/events/tickets', query: {'eventId': id});
await appmint.http.upload('/file/upload', bytes: bytes, fileName: 'photo.jpg');Pass sendUserToken: false for a call that should go out with app
authentication alone.
Watching what it does
appmint.http.onCall = (call) {
print('${call.method} ${call.path} → ${call.status} '
'(${call.took.inMilliseconds}ms) '
'app:${call.appAuthenticated} user:${call.sentUserToken}');
};AppmintCall carries no token values — only whether each was attached, which is
the genuinely confusing part of this API. The authentication example
puts this on screen beside the app.
Set logRequests: true on the config for the same thing in the console.
Errors
Failures arrive as AppmintException, and message is safe to show — AppEngine
writes its refusals in plain language and the client passes them through rather
than replacing them.
| Type | Means |
|---|---|
AppmintException | The server answered and refused. statusCode, and reason where it gives one — invalid_code and challenge_expired need different cures |
AppmintAppAuthException | The app itself could not authenticate — wrong appId/key/secret, or wrong org. No request can succeed until it is fixed |
AppmintSessionExpiredException | The session is gone and could not be renewed. Send them back to sign-in |
AppmintNetworkException | The request never reached the server. Distinct from a 5xx, which means the server answered and failed |
That last distinction is deliberate: a 400 is an answer, and calling it a network error sends people to check their wifi instead of their password.