Skip to content

About

Type-safe Parse Server SDK for Dart and Flutter, built on code generation

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

parse_next

A type-safe Parse Server SDK for Dart and Flutter. Models are plain immutable classes, a code generator writes their query fields and update ops, and the compiler rejects a wrong field or type before a request is sent. Pure Dart: it runs in Flutter, on the web and on the server, and depends only on http and web_socket_channel.

Tested against parse-server 9.9.0 on MongoDB and on Postgres.

Package What it is
parse_next The SDK. Start with its README.
parse_next_generator The build_runner generator, a dev dependency.
parse_next_it Integration tests against real servers; not published.

What it supports

Grouped as the Parse JavaScript guide is, so it can be read side by side with it. Checked items are in the SDK and tested, most against both databases in parse_next_it and the rest, such as the distance helpers, in unit tests. Unchecked items are not in the SDK, with the reason.

The master key is for Dart code that runs on a server; an app must not hold it.

Getting started

  • Connect with server URL, app id and client key — ParseClient(server:, appId:, clientKey:)
  • An http.Client of your own, a request timeout
  • One installation id per install — ParseClient(installationId:)
  • Retries, opt-in — attempts for reads; idempotency for writes, which uses the server's idempotencyOptions
  • Master key, for code on a server — ParseClient(masterKey:), sent on every request and on the live query connection

Objects

  • Typed classes — @ParseClass('Post') and the generator
  • Save a new object — collection.create(value, acl:)
  • Your own object id — create(value, id:), with the server's allowCustomObjectId
  • Cloud Code context — context: on create, update, replace, delete and batch.send
  • Retrieve by id — collection.get(id), or query().include(...).get(id)
  • Update with Parse ops, applied atomically — collection.update(id, (o) => [...])
  • The object as the server has it after an update, trigger changes included — UpdateResult.applyTo(doc)
  • Overwrite every field — collection.replace(id, value)
  • Counters — o.likes.increment(n), negative to decrement
  • Arrays — set, add, addUnique, remove
  • One key of an object field — o.meta.setKey, incrementKey, unsetKey: one top-level key, once per map in an update
  • Remove a field — o.field.unset()
  • Delete — collection.delete(id)
  • Several writes in one request — client.batch(), 20 to a request, or atomically with send(transaction: true)
  • Save nested new objects in one call — save each, then point to it
  • Save offline (saveEventually, destroyEventually) — no local store

Data types

  • String, Number (int, double, num), Boolean, Date, enum (stored by name)
  • Array of String, Number or Boolean, and of Pointers
  • Array of Dates, GeoPoints, Files or Objects — List<DateTime>, List<GeoPoint>, List<ParseFile>, List<Map<String, Object?>>. In a list, a file is matched by its URL too, and on Mongo an object matches only with its keys in the same order
  • Object — Map<String, Object?>, holding DateTime, GeoPoint, Polygon and ParseFile at any depth
  • Pointer, Relation, File, GeoPoint, Polygon, Bytes (Uint8List)
  • Null and absent — a nullable field
  • An Array of embedded typed classes, such as List<Bucket> — use List<Map<String, Object?>>

Queries

  • Find, first, count, find and count in one request — find, first, count, findAndCount
  • equalTo, notEqualTo, lessThan(OrEqualTo), greaterThan(OrEqualTo) — eq, ne, lt, lte, gt, gte
  • containedIn, notContainedIn — oneOf, noneOf
  • exists, doesNotExist — exists(), exists(false)
  • limit, skip, ascending, descending over several keys — limit, skip, orderBy(f.x.asc)
  • Every match, page by page (each, eachBatch, findAll) — stream()
  • Rows that fail to decode, without failing the page — tryFind()
  • select, exclude — partial: raw(keys:, exclude:) returns untyped rows, since a partial object does not decode into a model with required fields
  • Conditions inside an Object field — f.meta.at('a.b')
  • hint, comment, readPreference
  • More than ~1,000 ids in a condition — queries are sent in the body, never in the URL
  • explain, aggregate, distinct — query.explain(), collection.aggregate(pipeline), query.distinct((f) => f.x), undecoded; need the master key
  • Local vs. server data — no local store

Queries on arrays

  • equalTo on an array, containsAll, containedIn on an array — contains, containsAll, containsAny
  • containedBy, containsAllStartingWith

Queries on strings

  • startsWith, endsWith, contains (escaped), matches (regex, case-insensitive)
  • Full-text search with language, case and diacritic options — fullText
  • Sort by text score — orderByTextScore()

Relational queries

  • On a pointer: equalTo, notEqualTo, containedIn, notContainedIn — eq, ne, oneOf, noneOf, isId
  • matchesQuery, doesNotMatchQuery
  • matchesKeyInQuery, doesNotMatchKeyInQuery — matchesKey, notMatchesKey
  • Objects related through a Relation — relationField.of(ownerId)
  • Objects whose Relation holds a member — relationField.has(pointer)
  • include, nested includes, includeAll — include((f) => f.author.then((a) => a.team))

Compound queries

  • OR, AND, NOR — |, &, ~

Live queries

  • Subscribe — query.live(), a Stream
  • create, update, enter, leave and delete events — Created, Updated, Entered, Left, Deleted
  • Open and close — Subscribed after every (re)connect, Disconnected when the connection drops
  • Errors — a server error for the subscription ends the stream with its ParseException
  • Unsubscribe — cancel the stream; close the socket — client.close()
  • A LiveQuery server on another URL — ParseClient(liveQueryServer:)
  • Reconnect — automatic, with backoff; never ends the stream
  • Session token — the session at the time of listening
  • Only updates to some fields (watch) — live(watch: (f) => [...])
  • Only some fields in events (keys) — a partial object does not decode into the model

Users

  • Sign up, log in, log out — auth.signUp, logIn, logOut
  • Current user — auth.current(), auth.me(), and auth.changes
  • Set the current user from a session token — auth.become
  • Keep the session between runs — your SessionStore, such as secure storage, which covers encrypting it
  • Verify emails — auth.requestEmailVerification, and emailVerified as a model field
  • Reset password — auth.requestPasswordReset
  • Change password, keeping this session — auth.updatePassword
  • Check a password without logging in — auth.verifyPassword
  • Query users and point to them — a _User model in a collection
  • Log in with a provider (Google, Apple, Facebook, anonymous, ...) — auth.logInWith(provider, authData)
  • Link and unlink a provider — auth.linkWith, auth.unlink
  • Provider SDK modules that fetch authData for you — get it from the provider's own package

Sessions

  • The current session's properties — auth.session(): id, expiresAt, installationId, createdWith
  • Invalid session token — InvalidSession (209); the stored session is cleared

Roles

  • Roles as a model — @ParseClass('_Role') with users and roles relations
  • Role-based access — Acl().withRole(name)
  • Role hierarchy — the roles relation

Files

  • Upload — client.uploadFile(bytes, name:, contentType:), with progress and cancel
  • Store in an object field — ParseFile
  • Download — client.downloadFile(file)
  • Metadata and tags — uploadFile(metadata:, tags:)
  • Delete — client.deleteFile(file); needs the master key

GeoPoints

  • GeoPoint, Polygon, distances — radiansTo, kilometersTo, milesTo, Polygon.containsPoint
  • near, with a maximum distance — near(point, maxKm:)
  • withinKilometers without sorting — withinKm; miles and radians: convert
  • withinGeoBox, withinPolygon, polygonContains — withinBox, withinPolygon, containsPoint
  • The device's location (GeoPoint.current) — use a location package

Local datastore

  • Pinning, local queries, cached results — not supported

Push notifications

  • An installation id on every request
  • Send a push to channels or to an _Installation query — client.push(data, channels:), push(data, where:); needs the master key and a push adapter on the server
  • An _Installation type — model it with @ParseClass('_Installation')
  • Scheduled pushes and expiry (push_time, expiration_time) — not supported

Config

  • Retrieve config — client.config(); masterKeyOnly params are sent only with the master key
  • Save config — client.saveConfig(params, masterKeyOnly:); needs the master key
  • A cached current config — keep the result yourself

Analytics

  • Custom events — not supported

Cloud Code

  • Call functions with typed parameters and results — @ParseFunctions
  • Start a job — client.startJob(name, params), returning its _JobStatus id; needs the master key

Schema

  • Schemas, indexes, class-level permissions, purge — client.schemas(), client.schema(name) with get, create, update, delete, purge; untyped; need the master key

Relations

  • One-to-one and one-to-many — Pointer<T>
  • Many-to-many — Relation<T>, an array of pointers, or a join class

Errors

  • Every failure is a sealed ParseException: ObjectNotFound, DuplicateValue, ScriptFailed, UsernameTaken, EmailTaken, InvalidCredentials, InvalidSession, Unauthorized, NetworkError, DecodeError, OtherParseError with the code
  • Values the server would store wrongly are rejected before sending — ArgumentError

Security

  • Access control lists — Acl: public, user and role read and write

Class-level permissions, pointer permissions, cloud-code validation and rate limits are server configuration; the SDK reports their errors.

Data

Storage, the data browser, import and export are server and dashboard features.

Development

The integration tests need Docker: Compose starts parse-server 9.9.0 on MongoDB (port 1337) and on Postgres (port 1338).

tool/ci.sh

runs every check: dependency and code rules, generated code and fixtures, analyze, format, unit tests (VM, and Chrome for the platform-dependent ones), generator tests, then the integration and live-query suites. PLAN.md records the design, the rules and every server behavior the SDK relies on.

License

Apache 2.0.

About

Type-safe Parse Server SDK for Dart and Flutter, built on code generation

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages