Changelog
New features, API changes, and important fixes in the Primitive platform libraries.
js-bao-wss-client v1.4.5 — 2026-05-07
client.documents.updatePermissions({ email, sendEmail: true, ... })now actually delivers a share email when the recipient is not yet an app member. The deferred-grant branch previously dropped the email; the flag is now honored end-to-end so the samesendEmailtoggle works for both existing-member and pending-signup recipients.- New
document-share-deferredemail type — non-members invited via document share now receive a dedicated template that carries the tokenized accept URL alongside document context, separate from thedocument-sharetemplate used for existing members. Customize it the same way as other built-in templates (primitive email-templates set document-share-deferred ...). - New precondition for
sendEmail: trueonclient.documents.updatePermissions: the app must havebaseUrlconfigured. Both branches now share the same precondition (the deferred branch needs it to compose the accept URL); requests without it return400 "Cannot send share email: app baseUrl is not configured". Direct-only callers that previously sent share emails on apps withoutbaseUrlmust now configure one. - Repeated
client.documents.updatePermissions(documentId, { email, ... })calls for the same(documentId, email)no longer create duplicateDeferredDocumentPermissionrows. The pending grant is updated in place, so the latestpermissionvalue wins at signup-time resolution andclient.documents.listPendingInvitations(documentId)no longer surfaces stale duplicates. - Pending-invitation reuse is now safe across expiry — when a deferred grant or group add reuses an existing pending
AppInvitation, the platform renews theinvitationTokenand pushesexpiresAtout 30 days if the invitation has already lapsed. TheinvitationIdis preserved, so existing deferred rows linked to it keep resolving correctly.
js-bao-wss-client v1.4.4 — 2026-05-06
- Default group / collection create is now permissive. With no custom
GroupTypeConfig/CollectionTypeConfig, any signed-in member can create their own group or collection;editanddeleteremain creator-only. Apps that need admin-gated creation must now opt in by attaching a rule set whosecreaterule checkshasRole('admin'). - Group rule-set operation
group.listis renamed togroup.get(single-resource read), parallel to the earliercollection.list→collection.getrename. Existing rule sets keep working via a backward-compat alias, but newly authored TOML rule sets must usegetfor thegroupcategory. Themember.listkey is unchanged. - New
client.collectionTypeConfigsandclient.databaseTypeConfigsSDK sub-APIs give surface parity with the existingclient.groupTypeConfigsfor managing per-type rule-set bindings. client.collections.listDocuments(collectionId)now returns full document metadata (title, createdBy, lastModified, tags, effective permission) in a single round-trip — no more N+1 follow-updocuments.getper row.- Record-route hardening: the 14 direct record HTTP routes (
POST records/{save,query,patch,delete,count,batch,aggregate,increment,stringset/*}plus 4 GET introspection routes) are now manager+ only. Member-role callers must go throughdatabases.executeOperation(databaseId, operationName, params)— the operation'saccessCEL is the authorization point. Apps already using the registered-operations pattern need no change. - Collection member removal now broadcasts a
docMetadataaccess-lost frame, so open WebSocket sessions evict immediately instead of silently retaining the YjsRoom connection. - Accept-invite failures (bad / expired / already-accepted token) collapse to a uniform
401 INVITE_TOKEN_INVALIDresponse so the response shape no longer leaks whether a token exists.
js-bao v0.4.2 — 2026-05-06
- v2 codegen now auto-promotes
auto_assign = trueidfields torequired = trueso the generated attribute interface merges cleanly withBaseModel(no more TS2320 declaration-merge clashes). - The
allModelsarray exported by the generatedindex.tsbarrel is now typed asArray<typeof BaseModel>instead ofunknown[], so you can pass it straight toJsBaoClient.modelswithout anascast.
primitive-app v2.1.7 (patch) — 2026-05-04
- The project template now uses a TOML-based model definition approach: declare models in
src/models/models.tomland runnpx js-bao-codegen-v2to generate typed TypeScript classes — no manualdefineModelSchemaorgetJsBaoConfigwiring needed. - TOML
[models.X.relationships.Y]sections now generate typed traversal methods on model classes (e.g.post.author()returnsPromise<Author | null>,author.posts()returnsPromise<PaginatedResult<Post>>). - The generated barrel
src/models/index.tsnow validates bidirectional sync betweenmodels.tomland generated classes — a mismatch at startup throws with a clear error pointing tonpx js-bao-codegen-v2. - Updated peer dependency to
js-bao-wss-client@^1.4.4.
js-bao v0.4.0 — 2026-05-01
- New
js-bao-codegen-v2CLI — TOML-based code generation. Define models inmodels.tomland runjs-bao-codegen-v2 generateto produce strongly-typed TypeScript classes with auto-registration. Replaces the marker-comment approach of v1 codegen. js-bao-codegen-v2 migrate— one-shot migration tool that scans a v1-codegen project, generatesmodels.toml, and produces a migration report classifying each model assafe-to-deleteorneeds-manual-migration(custom methods, function defaults, etc.).upsertOnin registered database operations — pass"upsertOn": "$params.fieldName"in asaveoperation definition to create-or-update a record by a unique field value instead of requiring an explicitid. Enables idempotent write patterns without client-side lookup.database.celContext.*CEL alias — the database CEL context (formerly called "metadata") is now also accessible asdatabase.celContext.*in access expressions, triggers, and filters.database.metadata.*continues to work as a legacy alias. The TOML field is nowcelContextAccess(metadataAccessstill accepted). The CLI command is nowprimitive databases cel-context get/update(primitive databases metadatastill works).
primitive-app v2.1.7 — 2026-04-30
- Models are now defined in
src/models/models.toml(TOML schema) and auto-generated into*.generated.tsTypeScript classes — runpnpm codegenafter editing the TOML to regenerate - The
allModelsbarrel insrc/models/index.tsis fully auto-generated; adding a model tomodels.tomland running codegen is all that's needed to register it with js-bao - Demo app playground pages have been reorganized under a unified
playground/route structure, making it easier to see all API features in one place useApiLogcomposable andApiPlaygroundwrapper component extracted for consistent API call logging across playground pages
primitive-app v2.1.7 — 2026-04-28
- Models are now defined in
src/models/models.tomland generated into TypeScript viapnpm models:gen— you no longer writedefineModelSchema()by hand; the generated.generated.tsfiles produce the attribute interface, class declaration, and model-name constant automatically - Model classes are now pure data containers (no instance methods or getters); business logic belongs in a controller module in
src/lib/as free functions — call sites useisOverdue(task)instead oftask.isOverdue - The
src/models/index.tsbarrel is now also auto-generated and handlesattachAndRegisterModelregistration for all models; always import models from@/modelsso registration runs exactly once pnpm models:genreplacespnpm codegenfor model generation; re-run it after any change tomodels.toml
primitive-app v2.1.7 — 2026-04-27
- New
InviteAcceptPagecomponent (src/pages/InviteAcceptPage.vue, routed at/invite/accept) handles the full invitation acceptance flow — signed-in confirmation, signed-out stash-and-redirect, and all error states (INVITE_TOKEN_EXPIRED,INVITE_ALREADY_ACCEPTED,INVITE_TOKEN_INVALID) - New
inviteToken.tsutility (src/lib/inviteToken.ts) persists invite tokens insessionStorageacross auth round-trips, including cross-tab magic-link flows where the token also rides the OAuth state parameter - All auth paths in
userStore(login,verifyOtp, magic-link callback,registerPasskey) now automatically read and forward the pending invite token so deferred grants resolve in a single server round-trip at sign-in - Sidebar and user-menu components updated to support collapsible icon mode via
group-data-[collapsible=icon]Tailwind classes
js-bao-wss-client v1.4.3 — 2026-04-24
A large cumulative release across invitations, sharing, server-side automation, databases, storage, and CLI. Highlights:
Invitations and group membership
- Custom invitation email CTAs —
invitations.create()(and the deferred branches ofdocuments.setPermissions({ email })andgroups.addMember({ email })) returninvitationId+inviteToken.invitations.getAcceptToken(id)retrieves the token for any existing invitation. - Token-based acceptance —
invitations.accept(inviteToken)lets an authenticated caller redeem an invitation under a different identity than the email it was sent to. Resolves all linked deferred grants to the caller.AppInvitation.acceptedByUserIdrecords the accepting user. groups.addMemberreturns a discriminated union —DirectGroupAdd | DeferredGroupAdd. Branch onstatus: "added" | "already_member" | "pending_signup"."already_member"replaces the prior409. The"pending_signup"branch carriesinvitationId+inviteToken.groups.removeMember(groupType, groupId, { email })removes the membership if one exists, or cancels the pendingDeferredGroupAddif not — a single call handles both the "already a member" and "pending invite" cases.documents.listPendingInvitations(documentId)andgroups.listPendingInvitations(groupType, groupId)return the deferred grants scoped to one resource.groups.listUserMemberships(userId)results includename(joined fromAppGroup) and optionaldescription; orphan rows are skipped. Accepts{ groupType }for server-side push-down filtering.- Member invitations with quota — set
memberInvitationsEnabled: trueon an app to let regular members invite others, capped bymemberInvitationLimit. Admins and owners are exempt. NewGET /invitations/quotaendpoint. - Document access requests — Google-Docs-style "request access" flow. A 403 on
documents.getreturns acanRequestAccesshint when applicable. Client methods:documents.requestAccess,listAccessRequests,approveAccessRequest,denyAccessRequest. Owners receive WS + email notifications. - Bookmarks — generic bookmark model for organizing references to documents, databases, or any target type.
client.me.bookmarks.add/remove/rename/listwith prefix queries. Documents are auto-bookmarked on creation. - Email-based document sharing — pass
emailtoclient.documents.setPermissions(orsetGroupPermission) and the grant resolves automatically when the recipient signs up. Non-members receive an invitation email. - Invitation acceptance WebSocket event — the
invitation/acceptedevent fires reliably from the GET document path (previously silently dropped on that branch).
Collections
DocumentCollectionexposescollectionTypeandcontextId(both nullable, immutable after create), mirroringAppGroup.groupType+groupId. Collection rule sets referencecollection.contextIdto express "caller is a member of the group this collection belongs to." Collection rule sets see a dedicatedcollection.*CEL namespace, separate from thegroup.*namespace used by group rule sets.- Member invitations with quota — set
memberInvitationsEnabled: trueon an app to let regular members invite others, capped bymemberInvitationLimit. Admins and owners are exempt from the quota. NewGET /invitations/quotaendpoint. - Document access requests — Google Docs-style "request access" flow. A 403 on
documents.getnow returns acanRequestAccesshint. New client methods:documents.requestAccess,listAccessRequests,approveAccessRequest,denyAccessRequest. Owners receive WS + email notifications. - Bookmarks — new generic bookmark model for organizing references to documents, databases, or any target type.
client.me.bookmarks.add/remove/rename/listwith prefix queries. Documents are auto-bookmarked on creation. - Invitation acceptance WebSocket event — the
invitation/acceptedevent now fires reliably from the GET document path (previously silently dropped on that branch).
Server-side automation
- Real-time database subscriptions —
db.subscribe/db.unsubscribeover WebSocket. Server-side subscriptions use parameterized CEL filters to broadcastdb.changeframes to matching connections. Mutations (including those from workflows) automatically trigger subscriptions. Writer's connection is excluded. - Cron-triggered workflows — new
CronTriggermodel and CLI (primitive cron-triggers …). IANA timezone support, overlap skip policy, per-app cap of 50. applyToQuerydatabase operation — server-side query+mutate in one request with safe-looping truncation signal.analytics.queryworkflow step — all 16 analytics query types available from workflows. Default deny, admin/owner bypass, per-run cap of 50.- Custom email template types — register templates with arbitrary kebab-case names (e.g.
"order-confirmation") via admin API, CLI, or web-admin. - Inline email mode — the
email.sendworkflow step can now specifysubject/htmlBody/textBodydirectly in the step config, bypassing templates. - Workflow run steps endpoint — new app-level API exposes persisted step-level data for debugging.
Databases
executeBatchoperation type — apply many individual writes in a single request with CEL access checked per-item.- Database group permissions — new
DatabaseGroupPermissionmodel.databases.get()resolves group access;databases.list()stays direct-only (matches document semantics). Newcollections.listAll()admin method to retrieve the full-app view.
Deprecations
importBulkoperation type is deprecated. Rename existing configs toexecuteBatchand review per-item access rules — the new operation type enforces CEL per item rather than across the whole batch.
Storage
- General-purpose blob buckets — new
BlobBucketmodel for non-document blob storage. Bucket-based storage with TTL tiers, signed URLs, optional CEL access rules, R2-only tracking, full CLI + sync. Newblobworkflow step.
CLI
- Project-scoped config with named environments —
.primitive/config.json(committed) declares per-environmentapiUrl+appIdpairs and an optionaldefaultEnvironment;.primitive/credentials.json(gitignored) holds per-environment tokens. Newprimitive envcommands (add,list,show,use,remove) for dev/staging/prod, with active-env resolution via--env→PRIMITIVE_ENV→defaultEnvironment. - Test user sign-in —
+primitivetest<suffix>OTP bypass for automated testing, gated by a per-apptestAccountBaseEmailswhitelist (primitive apps update --test-account-bases …, max 50). Verify with the magic code000000to receive a 30-minute token with aprimitiveBypassclaim that is re-checked on every request. The derived account must already exist as anAppUser;+primitivetest*is reserved at admin/role/invitation boundaries.
Fixes and performance
collections.list()returns only direct-access collections (adminlistAll()preserves full-app view).primitive apps list/primitive useno longer silently truncate at 25 apps.- WebSocket fanout pagination — removes the ~100-subscriber silent ceiling; adds bounded parallel fanout.
See Sharing and Invitations, Scheduled and Real-Time Automation, and Blob Buckets for full walkthroughs.
js-bao v0.3.1 — 2026-04-13
- New
upsertOnoption forsave()— passupsertOn: "fieldName"to create-or-update a record by a unique field value instead of requiring an explicit ID, with transactional lookup to prevent race conditions - Schema discovery and TOML utilities (
discoverSchema,discoverModelNames,schemaToToml,loadSchemaFromTomlString,loadSchemaFromToml) are now re-exported from all bundle entry points (browser, node, cloudflare) - New YDoc dump utilities for debugging:
dumpYDocToPlain()andsummarizePlainYDoc()let you inspect document structure as plain JavaScript objects (node bundle) - Fixed schema discovery failing when YDoc share entries weren't materialized before instanceof checks
js-bao-wss — 2026-04-10
- Model-less documents — you can now work with Yjs documents without pre-defining TypeScript model classes; the server auto-discovers document schema at runtime
- New
getDocumentSchema()API andschema-discoveredevent for client-side schema introspection; passschemaTomltoJsBaoClientfor client-side model validation - Workflow runs triggered via webhooks and
workflow.startare now visible through the API (previously hidden) upsertOnsupport for conditionally creating or updating records based on field values- Fixed awareness state keying —
sendAwarenessStateand_broadcastAwarenessnow consistently useconnectionId, preventing duplicate awareness entries - Fixed HTTP 400 errors when syncing database records with no top-level field changes
primitive-app v2.1.7 — 2026-04-13
- New
getOrCreateWithAlias()API replaces the old multi-step document alias flow — handles race conditions server-side, eliminating the need for client-side retry logic - Fixed passkey credential prompt unexpectedly appearing after OTP sign-in — auth flow now properly cancels pending passkey ceremonies
- New
VITE_APP_NAMEenvironment variable to customize the browser tab title