Skip to main content

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.

ParameterDescription
appThe raw Jazz app returned by s.defineApp.
templatesAt 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).
rawOptional 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.

ParameterDescription
configThe 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.

ParameterDescription
configThe 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.

ParameterDescription
configThe 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).

ParameterDescription
configThe 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.