Access
import * as mod from "jsr:@nzip/lofi/access";
Narrow private, direct-share, fixed-role group, and shared-field access templates over raw Jazz schemas.
Use privateAccess, sharedAccess, groupAccess, or
sharedFieldAccess to declare common authorization models. Runtime
operations require managed sync for collaboration and throw
AccessError when that precondition is not met. Raw Jazz policy
callbacks remain available through defineAccessPolicies.
Group and sharing operations raise two catchable error families:
AccessError (narrow with isAccessError) for configuration,
identity, sync, and mutation failures, and SharedFieldError (narrow
with isSharedFieldError) when shared-field key material cannot be
established or reconciled.
Shared-field hosting spans three entries. @nzip/lofi/schema declares the
encrypted columns (s.sharedEncryptedText, s.sharedEncryptedJson). This
entry compiles the directory and wrapped-key policies and runs the key
lifecycle through createGroupOperations: creation bootstraps the
first key and reconcileSharedFieldKeys repairs missing wraps. Pin
remediation for changed peer keys (trustPeerKey, with
pinnedFingerprint for inspection) lives on the main @nzip/lofi entry;
trustPeerKey is re-exported here because re-trusting a peer is an
authorization decision.
AccessError
class
class AccessError extends Error {
constructor(code: AccessErrorCode, message: string, options?: ErrorOptions);
readonly name: string;
}
Actionable failure raised by access templates and collaboration operations.
createGroupOperations
function
function createGroupOperations<Group extends Identified, GroupInit, Member extends GroupMembershipRow, MemberInit>(config: GroupOperationsConfig<Group, GroupInit, Member, MemberInit>): GroupOperations<Group, GroupInit, Member>
Creates fixed-role group membership operations for a declared table pair.
createSharingOperations
function
function createSharingOperations<Resource extends Identified, ResourceInit, Grant extends SharedGrantRow, GrantInit>(config: SharingOperationsConfig<Resource, ResourceInit, Grant, GrantInit>): SharingOperations<Resource, Grant>
Creates direct-share operations that wait for local and global durability.
decodeSharingIdentity
function
function decodeSharingIdentity(identity: string): string
Validates a sharing identity and returns its raw Jazz principal.
decodeSharingIdentityDetails
function
function decodeSharingIdentityDetails(identity: string): SharingIdentityDetails
Validates an app-scoped sharing identity and returns its parts. Both
identity versions decode: lofi1 yields the principal alone, lofi2 adds
the fingerprint. Surrounding whitespace from copy/paste is tolerated;
whitespace inside the principal is rejected — a grant for a padded
principal would look active to the owner while never matching the
recipient's actual account.
defineAccessPolicies
function
function defineAccessPolicies<TApp extends object>(app: TApp, templates: readonly AccessTemplate[], raw?: RawAccessPolicyExtension): CompiledPermissions
Compiles the four narrow templates through Jazz's own policy builder. The optional callback is the raw Jazz-policy escape hatch for app-specific rules.
| Parameter | Description |
|---|---|
app | The raw Jazz app returned by s.defineApp. |
templates | At least one privateAccess, sharedAccess, groupAccess, or sharedFieldAccess template; every table needs a policy. sharedFieldAccess compiles the key directory behind shared encrypted columns; pair it with the fieldKeys option of groupAccess, and group operations created with the same tables run the key lifecycle (bootstrap on creation, wrap delivery, lazy rotation, and reconcileSharedFieldKeys repair). |
raw | Optional raw-policy callback for app-specific rules beyond the templates. |
Returns: Compiled permissions suitable as the app's default policy export.
Example
import { defineAccessPolicies, groupAccess, sharedAccess } from "@nzip/lofi/access";
import { app } from "./schema.ts";
export default defineAccessPolicies(app, [
sharedAccess({ resource: app.notes, grants: app.noteGrants }),
groupAccess({
groups: app.workspaces,
members: app.workspaceMembers,
resources: app.documents,
groupId: "workspaceId",
}),
]);
encodeSharingIdentity
function
function encodeSharingIdentity(userId: string, fingerprint?: string): SharingIdentity
Encodes a raw Jazz principal as a versioned identity scoped to the active sync location's app. Identities are exchanged between users of the same store, so they carry the store's app id — the declared sink's when one is enrolled, the compiled managed app's otherwise. With a fingerprint, the identity also carries the account's shared-field key fingerprint.
groupAccess
function
function groupAccess(config: { groups: AccessTable; members: AccessTable; resources: AccessTable | readonly AccessTable[]; groupId: string; fieldKeys?: AccessTable }): GroupAccessTemplate
Declares fixed-role membership policy for one or more group-owned resources.
The membership table must be declared with the groupMembershipTable helper
(or match its column shape), and each resource table must carry the groupId
column referencing the group table.
Group-creator authority is permanent: the creator can always update their group row, and through it restore their own admin membership even after being demoted or removed. Choose this template only when that trust property fits — a group cannot durably expel its creator.
| Parameter | Description |
|---|---|
config | The group table, membership table, group-owned resource table(s), and the resource column that references the group. |
Returns: A template for defineAccessPolicies.
Example
import { defineAccessPolicies, groupAccess } from "@nzip/lofi/access";
import { app } from "./schema.ts";
export default defineAccessPolicies(app, [groupAccess({
groups: app.workspaces,
members: app.workspaceMembers,
resources: app.documents,
groupId: "workspaceId",
})]);
groupMembershipTable
function
function groupMembershipTable<Group extends string>(group: Group): DefinedTable<{ groupId: RefColumn<Group>; user_id: StringColumn; role: StringColumn; can_create: BooleanColumn; can_edit_any: BooleanColumn; can_manage: BooleanColumn }>
Creates the conventional relationship table used by groupAccess.
groupRoleCapabilities
function
function groupRoleCapabilities(role: GroupRole): { readonly role: GroupRole; readonly can_create: boolean; readonly can_edit_any: boolean; readonly can_manage: boolean }
Returns the persisted capability flags for a fixed group role.
groupRoles
const
const groupRoles: ("reader" | "contributor" | "writer" | "admin")[];
Fixed Wave 2 group roles. Custom role systems remain a raw Jazz escape hatch.
isAccessError
function
function isAccessError(error: unknown): error is AccessError
True when an error came from the lofi access surface.
isSharedFieldError
function
function isSharedFieldError(error: unknown): error is SharedFieldError
True when an error came from the lofi shared-field surface.
privateAccess
function
function privateAccess(config: { resource: AccessTable }): PrivateAccessTemplate
Declares owner-only read and mutation policy for one resource table.
| Parameter | Description |
|---|---|
config | The resource table whose rows are visible and mutable only to their creator. |
Returns: A template for defineAccessPolicies.
Example
import { defineAccessPolicies, privateAccess } from "@nzip/lofi/access";
import { app } from "./schema.ts";
export default defineAccessPolicies(app, [privateAccess({ resource: app.notes })]);
sharedAccess
function
function sharedAccess(config: { resource: AccessTable; grants: AccessTable }): SharedAccessTemplate
Declares owner plus explicit read/edit grants for one resource table.
The grant table must be declared with the sharedGrantTable helper (or match
its column shape) and reference the resource table.
| Parameter | Description |
|---|---|
config | The shared resource table and its grant table. |
Returns: A template for defineAccessPolicies.
Example
import { defineAccessPolicies, sharedAccess } from "@nzip/lofi/access";
import { app } from "./schema.ts";
export default defineAccessPolicies(app, [
sharedAccess({ resource: app.notes, grants: app.noteGrants }),
]);
sharedFieldAccess
function
function sharedFieldAccess(config: { directory: AccessTable }): SharedFieldAccessTemplate
Declares the shared-field key directory policy: every authenticated account reads the directory (public keys are public — integrity comes from fingerprint pinning), and each account writes only its own row, so the store cannot be used to impersonate a publisher through the policy layer.
The directory table must be declared with the sharedFieldDirectoryTable
helper (or match its column shape).
| Parameter | Description |
|---|---|
config | The key directory table. |
Returns: A template for defineAccessPolicies.
sharedFieldDirectoryTable
function
function sharedFieldDirectoryTable(): DefinedTable<{ user_id: StringColumn; algo: StringColumn; public_key: StringColumn; fingerprint: StringColumn }>
Creates the shared-field key directory table: one row per account holding
its self-published x25519 public key. Declared once per app schema and
given to sharedFieldAccess, which compiles the policy that makes it
world-readable in the store with self-only writes. Public keys are public;
integrity comes from fingerprint pinning, not from hiding the rows.
SharedFieldError
class
class SharedFieldError extends Error {
constructor(code: SharedFieldErrorCode, message: string);
readonly name: string;
readonly code: SharedFieldErrorCode;
}
Raised when shared-field material cannot be derived, wrapped, or opened.
sharedFieldKeyTable
function
function sharedFieldKeyTable<Group extends string>(group: Group): DefinedTable<{ groupId: RefColumn<Group>; recipient_user_id: StringColumn; sender_user_id: StringColumn; generation: IntColumn; wrapped_key: StringColumn; recipient_fingerprint: StringColumn; sender_fingerprint: StringColumn }>
Creates the wrapped-field-key table for one group table: one row per (recipient, generation) holding the group's field key sealed to that member's public key. Rows are ordinary synced data the server relays but cannot open; the sender's static key inside the wrap is what makes a server-minted row a detected forgery rather than a readable key.
sharedGrantTable
function
function sharedGrantTable<Resource extends string>(resource: Resource): DefinedTable<{ resourceId: RefColumn<Resource>; user_id: StringColumn; can_edit: BooleanColumn }>
Creates the conventional relationship table used by sharedAccess.
sharingIdentity
function
async function sharingIdentity(): Promise<SharingIdentity>
Returns the non-secret, app-scoped identity users may copy for shares.
trustPeerKey
function
function trustPeerKey(appId: string, userId: string, fingerprint: string): void
Replaces a peer's pin after out-of-band verification — the explicit user
action that resolves a peer-key-changed refusal.
AccessErrorCode
type
type AccessErrorCode =
| "configuration"
| "invalid-identity"
| "sync-required"
| "mutation-rejected"
| "not-found"
| "invalid-role";
Stable categories for access configuration and collaboration failures.
AccessRuntimeTable
type
type AccessRuntimeTable<Row, Init> = TableProxy<Row, Init> & { where(input: unknown): QueryBuilder<Row> };
Jazz table shape required by the access operation helpers.
AccessTable
type
type AccessTable = {
readonly _table: string;
readonly _schema: Record<string, { columns?: unknown[] }>;
};
Minimum declared Jazz table metadata consumed by access templates.
AccessTemplate
type
type AccessTemplate =
| PrivateAccessTemplate
| SharedAccessTemplate
| GroupAccessTemplate
| SharedFieldAccessTemplate;
Any built-in access policy template accepted by defineAccessPolicies.
GroupAccessTemplate
type
type GroupAccessTemplate = {
readonly kind: "group";
readonly groups: AccessTable;
readonly members: AccessTable;
readonly resources: readonly AccessTable[];
readonly groupId: string;
readonly fieldKeys?: AccessTable;
};
Fixed-role group, membership, and resource policy template.
GroupMembershipRow
type
type GroupMembershipRow = Identified & { groupId: string; user_id: string; role: GroupRole; can_create: boolean; can_edit_any: boolean; can_manage: boolean };
Conventional fixed-role group membership row.
GroupOperations
type
type GroupOperations<Group, GroupInit, Member> = {
createGroup(values: GroupInit): Promise<{ group: Group; membership: Member }>;
addMember(groupId: string, recipient: SharingIdentity | string, role: GroupRole): Promise<Member>;
changeRole(groupId: string, recipient: SharingIdentity | string, role: GroupRole): Promise<Member>;
removeMember(groupId: string, recipient: SharingIdentity | string): Promise<void>;
leaveGroup(groupId: string): Promise<void>;
listMembers(groupId: string): Promise<Member[]>;
reconcileSharedFieldKeys(groupId: string): Promise<number>;
};
Fixed-role group creation and membership operations.
GroupOperationsConfig
type
type GroupOperationsConfig<Group extends Identified, GroupInit, Member extends GroupMembershipRow, MemberInit> = {
groups: AccessRuntimeTable<Group, GroupInit>;
members: AccessRuntimeTable<Member, MemberInit>;
fieldKeys?: AccessRuntimeTable<Identified, unknown>;
directory?: AccessRuntimeTable<Identified, unknown>;
};
Tables consumed by createGroupOperations.
GroupRole
type
type GroupRole = (typeof groupRoles)[number];
Fixed group roles supported by the built-in group policy template.
Identified
type
type Identified = {
id: string;
};
Minimum row shape accepted by collaboration operations.
PrivateAccessTemplate
type
type PrivateAccessTemplate = {
readonly kind: "private";
readonly resource: AccessTable;
};
Owner-only resource policy template.
RawAccessPolicyContext
type
type RawAccessPolicyContext = {
policy: Record<string, TablePolicy>;
session: { user_id: unknown };
allowedTo: { update(fkColumn: string): unknown };
anyOf(conditions: readonly unknown[]): unknown;
allOf(conditions: readonly unknown[]): unknown;
};
Raw Jazz policy-builder context exposed to advanced policy extensions.
RawAccessPolicyExtension
type
type RawAccessPolicyExtension = (context: RawAccessPolicyContext) => void;
Callback for app-specific rules that do not fit the built-in templates.
RuleBuilder
type
type RuleBuilder = {
where(input: unknown): unknown;
always(): unknown;
};
Minimal Jazz rule builder exposed to raw access-policy extensions.
SharedAccessTemplate
type
type SharedAccessTemplate = {
readonly kind: "shared";
readonly resource: AccessTable;
readonly grants: AccessTable;
};
Direct-share resource and grant-table policy template.
SharedFieldAccessTemplate
type
type SharedFieldAccessTemplate = {
readonly kind: "shared-field";
readonly directory: AccessTable;
};
Shared-field key-directory policy template.
SharedFieldErrorCode
type
type SharedFieldErrorCode =
| "identity-missing"
| "key-pending"
| "unscoped-write"
| "corrupt"
| "peer-key-changed"
| "wrap-invalid"
| "no-directory-entry";
Stable categories for shared-field failures. identity-missing — a shared
column was touched before the runtime installed the account's x25519
identity. key-pending — no field key is installed for the value's scope
and generation yet; a normal state for a freshly added member.
unscoped-write — a write reached the column transform without the
mutation layer sealing it first. corrupt — a sealed value failed
authentication. peer-key-changed — a peer's published public key no
longer matches its pinned fingerprint. wrap-invalid — a wrapped key
failed authentication or shape checks. no-directory-entry — a wrap was
requested for an account that has not published a public key.
SharedGrantRow
type
type SharedGrantRow = Identified & { resourceId: string; user_id: string; can_edit: boolean };
Conventional direct-share grant row.
ShareLevel
type
type ShareLevel = "read" | "edit";
Access level assigned by a direct share.
SharingIdentity
type
type SharingIdentity = string & { readonly __lofiSharingIdentity: true };
App-scoped, non-secret Jazz principal identifier safe to copy between users.
SharingIdentityDetails
type
type SharingIdentityDetails = {
userId: string;
fingerprint?: string;
};
A decoded sharing identity: the raw principal, and — when the identity was minted by a shared-field-capable app — the account's public-key fingerprint. A carried fingerprint pins the peer's key out-of-band, so the sync server never gets a first-sight window for that relationship.
SharingOperations
type
type SharingOperations<Resource, Grant> = {
share(resourceId: string, recipient: SharingIdentity | string, level: ShareLevel): Promise<Grant>;
revoke(resourceId: string, recipient: SharingIdentity | string): Promise<void>;
listShares(resourceId: string): Promise<Grant[]>;
sharedWithMe(): Promise<Resource[]>;
};
Direct-share operations bound to one resource and grant table pair.
SharingOperationsConfig
type
type SharingOperationsConfig<Resource extends Identified, ResourceInit, Grant extends SharedGrantRow, GrantInit> = {
resource: AccessRuntimeTable<Resource, ResourceInit>;
grants: AccessRuntimeTable<Grant, GrantInit>;
};
Table pair consumed by createSharingOperations.
TablePolicy
type
type TablePolicy = {
allowRead: RuleBuilder;
allowInsert: RuleBuilder;
allowUpdate: RuleBuilder;
allowDelete: RuleBuilder;
exists: { where(input: unknown): unknown };
};
Read and mutation policy builders for one declared table.