Core SDK (arkveil)
Runtime-agnostic core SDK — the Arkveil client, permission checks, and row-level data condition builders.
Install
npm install arkveilarkveil has zero runtime dependencies and ships dual ESM/CJS builds with type declarations.
Creating a client
import { Arkveil } from "arkveil";
const arkveil = new Arkveil({
serviceUrl: "https://api.arkveil.com",
apiKey: "your-api-key",
});Constructor options
| Option | Type | Default | Description |
|---|---|---|---|
serviceUrl | string | — | Required. Base URL of Arkveil Cloud or a self-hosted arkveil-runtime sidecar. |
apiKey | string | — | Required. Workspace API key, sent as the x-api-key header. |
version | "v1" | "v1" | API version. |
timeout | number | 5000 | Request timeout in milliseconds. |
retryAttempts | number | 3 | Retry attempts for 429/5xx/network errors. |
logger | Logger | — | Logger with error/warn methods, used for fail-closed diagnostics. |
getUserAttributes | (req: any) => TUser | Promise<TUser> | — | Extracts user attributes from a framework request object. Used by platform SDKs. |
getContextAttributes | (req: any) => TContext | Promise<TContext> | — | Extracts context attributes from a framework request object. |
onDenied | (req, res, reason?) => void | Promise<void> | — | Custom handler invoked on denial by platform SDKs. |
Arkveil is generic over <TCode, TUser, TContext>. Leave these unset to fall back to string/Record<string, any>, or bind them to generated types — see Typed codes & attributes.
Permission checks
const result = await arkveil.checkPermission({
actionCode: "content-service.article-delete",
user: { id: "user-123", role: "admin" },
context: { region: "EU" },
});
// result: { granted: boolean }checkPermission(request)POSTs to${serviceUrl}/api/${version}/abac/permissions/check.- It is fail-closed: any network error, timeout, or non-OK response is caught, logged, and resolved to
{ granted: false }. It never throws. buildPermissionRequest(code, req)resolvesuser/contextattributes from a framework request viagetUserAttributes/getContextAttributes, returning aPermissionCheckRequest. This is used internally by@arkveil/nodeand@arkveil/nest.
Row-level data protection
Two helpers turn an ABAC policy into SQL you execute directly against your own database. The SDK never sees your data — only the rendered condition or check SQL.
buildReadCondition
const { readCondition, mode } = await arkveil.buildReadCondition({
datasetCode: "billing.public.payments",
user: { id: "user-123", role: "manager" },
context: {},
alias: "p",
});
const rows = await db.query(
`SELECT * FROM payments p WHERE p.tenant_id = $1 AND (${readCondition})`,
[tenantId],
);- POSTs to
.../abac/conditions/read. datasetCodeis the canonical dataset code,datasource.schema.table(see dataset codes).- Pass
aliaswhen the table is aliased or joined, so the condition qualifies columns as"alias"."column"instead of"schema"."table"."column". readCondition: "FALSE"is a normal response meaning "no policy grants access to any rows" — not an error.- Fails closed to
{ readCondition: "FALSE", mode: "UNAVAILABLE" }on any error.
buildWriteChecks
const { writeSql, invariantSql } = await arkveil.buildWriteChecks({
datasetCode: "billing.public.payments",
user,
context: {},
ids: [42, 7],
});
const [{ allowed }] = await tx.query(`${writeSql} AS allowed`);
if (!allowed) throw rollback();- POSTs to
.../abac/conditions/write. writeSqlis a single statement returning a boolean; run it inside the same transaction as the mutation:- CREATE — after insert, with the new row ids.
- UPDATE — before, with the targeted ids.
- DELETE — before, with the targeted ids.
idsare sent as strings; the server casts them to the dataset's primary key type. Omittingidsleaves a{{ids}}placeholder inwriteSql, which you can fill later withsubstituteIds.reason: "METADATA_MISSING"means the dataset isn't registered in Arkveil — a configuration gap, not a policy denial.- Fails closed to
{ writeSql: "SELECT FALSE", invariantSql: [], mode: "UNAVAILABLE" }on any error.
Dataset codes
Dataset codes identify a table for row-level protection and follow a strict shape: datasource.schema.table, three lowercase, dot-separated identifier segments.
import { normalizeDatasetCode } from "arkveil";
normalizeDatasetCode("Billing.Public.Payments");
// "billing.public.payments"normalizeDatasetCode trims and lowercases a dataset code, and throws if it isn't exactly three dot-separated identifier segments. This is the one place the SDK throws instead of failing closed, because a malformed code is a configuration bug, not a runtime access decision.
Helper exports
import {
normalizeDatasetCode,
substituteIds,
IDS_PLACEHOLDER,
METADATA_MISSING,
MODE_UNAVAILABLE,
} from "arkveil";| Export | Description |
|---|---|
normalizeDatasetCode(code: string): string | Validates and normalizes a dataset code. Throws on malformed input. |
substituteIds(writeSql: string, ids: readonly (string | number | bigint)[]): string | Replaces the {{ids}} placeholder in writeSql with escaped SQL literals. Throws if ids is empty or no placeholder is present. |
IDS_PLACEHOLDER | "{{ids}}" |
METADATA_MISSING | "METADATA_MISSING" |
MODE_UNAVAILABLE | "UNAVAILABLE" |
Types
import type {
ArkveilParams,
ArkveilCode,
ArkveilCodeRegistry,
ArkveilUser,
ArkveilUserRegistry,
ArkveilContext,
ArkveilContextRegistry,
PermissionCheckRequest,
PermissionCheckResponse,
ReadConditionRequest,
ReadConditionResponse,
WriteChecksRequest,
WriteChecksResponse,
WriteCheckId,
} from "arkveil";The *Registry interfaces are empty by default and are augmented by generated code — see Typed codes & attributes.