toride 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ // src/client.ts
2
+ var CLIENT_VERSION = "0.0.1";
3
+ var TorideClient = class {
4
+ permissions;
5
+ constructor(snapshot) {
6
+ this.permissions = /* @__PURE__ */ new Map();
7
+ for (const [key, actions] of Object.entries(snapshot)) {
8
+ this.permissions.set(key, new Set(actions));
9
+ }
10
+ }
11
+ /**
12
+ * Synchronous permission check.
13
+ * Returns true if the action is permitted for the resource, false otherwise.
14
+ * Unknown resources return false (default-deny).
15
+ */
16
+ can(action, resource) {
17
+ const key = `${resource.type}:${resource.id}`;
18
+ const actions = this.permissions.get(key);
19
+ if (!actions) return false;
20
+ return actions.has(action);
21
+ }
22
+ /**
23
+ * Return the list of permitted actions for a resource.
24
+ * Returns empty array for unknown resources.
25
+ */
26
+ permittedActions(resource) {
27
+ const key = `${resource.type}:${resource.id}`;
28
+ const actions = this.permissions.get(key);
29
+ if (!actions) return [];
30
+ return [...actions];
31
+ }
32
+ };
33
+
34
+ export {
35
+ CLIENT_VERSION,
36
+ TorideClient
37
+ };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ /** Main CLI entry point. */
3
+ declare function main(args?: string[]): Promise<number>;
4
+
5
+ export { main };
package/dist/cli.js ADDED
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ PolicySchema,
4
+ ValidationError,
5
+ parseInlineTests,
6
+ parseTestFile,
7
+ runTestCases,
8
+ validatePolicyResult,
9
+ validatePolicyStrict
10
+ } from "./chunk-24PMDTLE.js";
11
+
12
+ // src/cli.ts
13
+ import { readFileSync, readdirSync, statSync } from "fs";
14
+ import { resolve, dirname, join } from "path";
15
+ import * as YAML from "yaml";
16
+ import * as v from "valibot";
17
+ function loadPolicyFile(filePath) {
18
+ const absPath = resolve(filePath);
19
+ let content;
20
+ try {
21
+ content = readFileSync(absPath, "utf-8");
22
+ } catch {
23
+ throw new Error(`Cannot read file: ${absPath}`);
24
+ }
25
+ let raw;
26
+ if (absPath.endsWith(".json")) {
27
+ try {
28
+ raw = JSON.parse(content);
29
+ } catch (err) {
30
+ throw new ValidationError(
31
+ `JSON parse error: ${err instanceof Error ? err.message : String(err)}`,
32
+ ""
33
+ );
34
+ }
35
+ } else {
36
+ try {
37
+ raw = YAML.parse(content, { prettyErrors: true });
38
+ } catch (err) {
39
+ throw new ValidationError(
40
+ `YAML parse error: ${err instanceof Error ? err.message : String(err)}`,
41
+ ""
42
+ );
43
+ }
44
+ }
45
+ const result = v.safeParse(PolicySchema, raw);
46
+ if (!result.success) {
47
+ const issue = result.issues[0];
48
+ const path = issue?.path?.map((p) => String(p.key)).join(".") ?? "";
49
+ throw new ValidationError(
50
+ `Policy validation failed: ${issue?.message ?? "unknown error"}${path ? ` at ${path}` : ""}`,
51
+ path
52
+ );
53
+ }
54
+ return result.output;
55
+ }
56
+ function expandGlob(pattern) {
57
+ const absPattern = resolve(pattern);
58
+ if (!pattern.includes("*")) {
59
+ return [absPattern];
60
+ }
61
+ const parts = absPattern.split("/");
62
+ const baseParts = [];
63
+ for (const part of parts) {
64
+ if (part.includes("*")) break;
65
+ baseParts.push(part);
66
+ }
67
+ const baseDir = baseParts.join("/") || "/";
68
+ const regexStr = absPattern.replace(/\./g, "\\.").replace(/\*\*/g, "___GLOBSTAR___").replace(/\*/g, "[^/]*").replace(/___GLOBSTAR___/g, ".*");
69
+ const regex = new RegExp(`^${regexStr}$`);
70
+ const results = [];
71
+ function walkDir(dir) {
72
+ let entries;
73
+ try {
74
+ entries = readdirSync(dir);
75
+ } catch {
76
+ return;
77
+ }
78
+ for (const entry of entries) {
79
+ const fullPath = join(dir, entry);
80
+ try {
81
+ const stat = statSync(fullPath);
82
+ if (stat.isDirectory()) {
83
+ walkDir(fullPath);
84
+ } else if (regex.test(fullPath)) {
85
+ results.push(fullPath);
86
+ }
87
+ } catch {
88
+ }
89
+ }
90
+ }
91
+ walkDir(baseDir);
92
+ return results.sort();
93
+ }
94
+ async function runTestFile(filePath) {
95
+ const absPath = resolve(filePath);
96
+ const content = readFileSync(absPath, "utf-8");
97
+ if (absPath.endsWith(".test.yaml") || absPath.endsWith(".test.yml")) {
98
+ const testFile = parseTestFile(content);
99
+ const policyPath = resolve(dirname(absPath), testFile.policyPath);
100
+ const policy = loadPolicyFile(policyPath);
101
+ const results = await runTestCases(policy, testFile.tests);
102
+ return { results, file: absPath };
103
+ } else {
104
+ const policy = loadPolicyFile(absPath);
105
+ const { tests } = parseInlineTests(policy);
106
+ if (tests.length === 0) {
107
+ return { results: [], file: absPath };
108
+ }
109
+ const results = await runTestCases(policy, tests);
110
+ return { results, file: absPath };
111
+ }
112
+ }
113
+ async function main(args = process.argv.slice(2)) {
114
+ const command = args[0];
115
+ if (command !== "validate" && command !== "test") {
116
+ console.error(`Usage: toride <validate|test> [options] <file(s)>`);
117
+ return 1;
118
+ }
119
+ if (command === "test") {
120
+ return handleTestCommand(args.slice(1));
121
+ }
122
+ const isStrict = args.includes("--strict");
123
+ const fileArg = args.filter((a) => a !== "validate" && a !== "--strict")[0];
124
+ if (!fileArg) {
125
+ console.error("Error: No policy file specified");
126
+ console.error("Usage: toride validate [--strict] <policy-file>");
127
+ return 1;
128
+ }
129
+ let policy;
130
+ try {
131
+ policy = loadPolicyFile(fileArg);
132
+ } catch (err) {
133
+ if (err instanceof ValidationError) {
134
+ console.error(`Error: ${err.message}`);
135
+ } else if (err instanceof Error) {
136
+ console.error(`Error: ${err.message}`);
137
+ }
138
+ return 1;
139
+ }
140
+ if (isStrict) {
141
+ const result = validatePolicyStrict(policy);
142
+ for (const error of result.errors) {
143
+ console.error(`Error: ${error.message}`);
144
+ }
145
+ for (const warning of result.warnings) {
146
+ console.warn(`Warning: ${warning.message}`);
147
+ }
148
+ if (result.errors.length > 0) {
149
+ return 1;
150
+ }
151
+ if (result.errors.length === 0 && result.warnings.length === 0) {
152
+ console.log("Policy is valid.");
153
+ } else {
154
+ console.log("Policy is valid (with warnings).");
155
+ }
156
+ return 0;
157
+ } else {
158
+ const result = validatePolicyResult(policy);
159
+ for (const error of result.errors) {
160
+ console.error(`Error: ${error.message}`);
161
+ }
162
+ if (result.errors.length > 0) {
163
+ return 1;
164
+ }
165
+ console.log("Policy is valid.");
166
+ return 0;
167
+ }
168
+ }
169
+ async function handleTestCommand(args) {
170
+ const fileArgs = args.filter((a) => !a.startsWith("-"));
171
+ if (fileArgs.length === 0) {
172
+ console.error("Error: No test file(s) specified");
173
+ console.error("Usage: toride test <file-or-glob> [...]");
174
+ return 1;
175
+ }
176
+ const allFiles = [];
177
+ for (const arg of fileArgs) {
178
+ const expanded = expandGlob(arg);
179
+ if (expanded.length === 0) {
180
+ console.error(`Warning: No files matched pattern "${arg}"`);
181
+ }
182
+ allFiles.push(...expanded);
183
+ }
184
+ if (allFiles.length === 0) {
185
+ console.error("Error: No test files found");
186
+ return 1;
187
+ }
188
+ let totalPassed = 0;
189
+ let totalFailed = 0;
190
+ for (const file of allFiles) {
191
+ try {
192
+ const { results } = await runTestFile(file);
193
+ for (const result of results) {
194
+ if (result.passed) {
195
+ console.log(` \u2713 ${result.name}`);
196
+ totalPassed++;
197
+ } else {
198
+ console.log(` \u2717 ${result.name}`);
199
+ console.log(` Expected: ${result.expected}`);
200
+ console.log(` Got: ${result.actual}`);
201
+ totalFailed++;
202
+ }
203
+ }
204
+ } catch (err) {
205
+ console.error(`Error processing ${file}: ${err instanceof Error ? err.message : String(err)}`);
206
+ totalFailed++;
207
+ }
208
+ }
209
+ console.log("");
210
+ if (totalFailed === 0) {
211
+ console.log(`${totalPassed} tests passed`);
212
+ return 0;
213
+ } else {
214
+ console.log(`${totalPassed} passed, ${totalFailed} failed`);
215
+ return 1;
216
+ }
217
+ }
218
+ var isDirectExecution = typeof process !== "undefined" && process.argv[1] && (process.argv[1].endsWith("/cli.js") || process.argv[1].endsWith("/cli.mjs"));
219
+ if (isDirectExecution) {
220
+ main().then((code) => process.exit(code));
221
+ }
222
+ export {
223
+ main
224
+ };
@@ -0,0 +1,368 @@
1
+ /**
2
+ * Shape constraint for Toride's type parameter.
3
+ * Each property is a union or mapped type that codegen fills with literals.
4
+ */
5
+ interface TorideSchema {
6
+ /** Union of all resource type names (e.g., "Document" | "Organization") */
7
+ resources: string;
8
+ /** Global union of all action/permission names across all resources */
9
+ actions: string;
10
+ /** Union of all actor type names (e.g., "User" | "ServiceAccount") */
11
+ actorTypes: string;
12
+ /** Per-resource permission unions: { Document: "read" | "write"; ... } */
13
+ permissionMap: {
14
+ [R in string]: string;
15
+ };
16
+ /** Per-resource role unions: { Document: "admin" | "editor"; ... } */
17
+ roleMap: {
18
+ [R in string]: string;
19
+ };
20
+ /** Per-resource attribute shapes: { Document: { status: string; ownerId: string }; ... } */
21
+ resourceAttributeMap: {
22
+ [R in string]: Record<string, unknown>;
23
+ };
24
+ /** Per-actor attribute shapes: { User: { email: string; is_admin: boolean }; ... } */
25
+ actorAttributeMap: {
26
+ [A in string]: Record<string, unknown>;
27
+ };
28
+ /** Per-resource relation maps: { Document: { org: "Organization" }; ... } */
29
+ relationMap: {
30
+ [R in string]: Record<string, string>;
31
+ };
32
+ }
33
+ /**
34
+ * Default schema where everything is string / Record<string, unknown>.
35
+ * Used when Toride is instantiated without a type parameter.
36
+ * Provides full backward compatibility with the current untyped API.
37
+ */
38
+ interface DefaultSchema extends TorideSchema {
39
+ resources: string;
40
+ actions: string;
41
+ actorTypes: string;
42
+ permissionMap: Record<string, string>;
43
+ roleMap: Record<string, string>;
44
+ resourceAttributeMap: Record<string, Record<string, unknown>>;
45
+ actorAttributeMap: Record<string, Record<string, unknown>>;
46
+ relationMap: Record<string, Record<string, string>>;
47
+ }
48
+ /**
49
+ * Represents an entity performing actions.
50
+ * Generic discriminated union over actor types in S.
51
+ * When S = DefaultSchema, collapses to the original untyped shape.
52
+ */
53
+ type ActorRef<S extends TorideSchema = DefaultSchema> = {
54
+ [A in S["actorTypes"]]: {
55
+ readonly type: A;
56
+ readonly id: string;
57
+ readonly attributes: S["actorAttributeMap"][A];
58
+ };
59
+ }[S["actorTypes"]];
60
+ /**
61
+ * Represents a protected entity being accessed.
62
+ * Generic over schema S and resource type R.
63
+ * When S = DefaultSchema, collapses to the original untyped shape.
64
+ */
65
+ type ResourceRef<S extends TorideSchema = DefaultSchema, R extends S["resources"] = S["resources"]> = {
66
+ readonly type: R;
67
+ readonly id: string;
68
+ /** Pre-fetched attributes. Inline values take precedence over resolver results. */
69
+ readonly attributes?: S["resourceAttributeMap"][R];
70
+ };
71
+ /** Optional per-check configuration. */
72
+ interface CheckOptions {
73
+ readonly env?: Record<string, unknown>;
74
+ }
75
+ /** A single item in a canBatch() call. Action narrowed to global actions union. */
76
+ interface BatchCheckItem<S extends TorideSchema = DefaultSchema> {
77
+ readonly action: S["actions"];
78
+ readonly resource: ResourceRef<S>;
79
+ }
80
+ /**
81
+ * Per-type resolver function.
82
+ * Called when the engine needs attributes not available inline.
83
+ * Called at most once per unique resource per evaluation (cached).
84
+ *
85
+ * Registering a resolver is **optional** per resource type. When no resolver is
86
+ * registered, inline {@link ResourceRef.attributes} are used as the sole data
87
+ * source — this is referred to as "default resolver" behavior (analogous to
88
+ * GraphQL's default field resolver, which returns `parent[fieldName]`).
89
+ *
90
+ * A resolver is only needed when attributes must be fetched from an external
91
+ * source (e.g., a database). When both inline attributes and a resolver are
92
+ * present, inline attributes take precedence field-by-field over resolver
93
+ * results.
94
+ */
95
+ type ResourceResolver<S extends TorideSchema = DefaultSchema, R extends S["resources"] = S["resources"]> = (ref: ResourceRef<S, R>) => Promise<Record<string, unknown>>;
96
+ /**
97
+ * Map of resource type names to their resolver functions.
98
+ *
99
+ * Not all types need resolvers. Types without a registered resolver use
100
+ * **default resolver** behavior (also called "trivial resolution"): the engine
101
+ * reads attribute values directly from the inline {@link ResourceRef.attributes}
102
+ * passed at the call site. Fields not present inline resolve to `undefined`,
103
+ * causing conditions that reference them to fail (default-deny).
104
+ *
105
+ * This mirrors GraphQL's default field resolver pattern, where an unresolved
106
+ * field simply returns `parent[fieldName]` — here, inline attributes play the
107
+ * role of the `parent` object.
108
+ */
109
+ type Resolvers<S extends TorideSchema = DefaultSchema> = {
110
+ [R in S["resources"]]?: ResourceResolver<S, R>;
111
+ };
112
+ /** Custom evaluator function signature. */
113
+ type EvaluatorFn = (actor: ActorRef, resource: ResourceRef, env: Record<string, unknown>) => Promise<boolean>;
114
+ /** Engine construction options. */
115
+ interface TorideOptions<S extends TorideSchema = DefaultSchema> {
116
+ readonly policy: Policy;
117
+ /**
118
+ * Per-type resolver map.
119
+ *
120
+ * Optional — the engine works without any resolvers when all required data is
121
+ * provided inline via {@link ResourceRef.attributes}. This "default resolver"
122
+ * mode is the simplest way to use toride and requires no async data fetching.
123
+ *
124
+ * When both inline attributes and a resolver are present for the same resource
125
+ * type, **inline attributes take precedence** over resolver results on a
126
+ * field-by-field basis.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * // Inline-only mode — no resolvers needed
131
+ * const toride = new Toride({ policy });
132
+ *
133
+ * const allowed = await toride.can(actor, "read", {
134
+ * type: "Document",
135
+ * id: "doc-1",
136
+ * attributes: { status: "published", ownerId: "user-42" },
137
+ * });
138
+ * ```
139
+ */
140
+ readonly resolvers?: Resolvers<S>;
141
+ readonly maxConditionDepth?: number;
142
+ readonly maxDerivedRoleDepth?: number;
143
+ readonly customEvaluators?: Record<string, EvaluatorFn>;
144
+ readonly onDecision?: (event: DecisionEvent) => void;
145
+ readonly onQuery?: (event: QueryEvent) => void;
146
+ }
147
+ /** Attribute type for actor declarations. */
148
+ type AttributeType = "string" | "number" | "boolean";
149
+ /** Actor type declaration with attribute schema. */
150
+ interface ActorDeclaration {
151
+ readonly attributes: Record<string, AttributeType>;
152
+ }
153
+ /** Global role definition derived from actor attributes. */
154
+ interface GlobalRole {
155
+ readonly actor_type: string;
156
+ readonly when: ConditionExpression;
157
+ }
158
+ /**
159
+ * Derived role entry. Exactly one derivation pattern per entry.
160
+ * Patterns:
161
+ * 1. from_global_role
162
+ * 2. from_role + on_relation
163
+ * 3. from_relation
164
+ * 4. actor_type + when (conditional)
165
+ * 5. when only
166
+ */
167
+ interface DerivedRoleEntry {
168
+ readonly role: string;
169
+ readonly from_global_role?: string;
170
+ readonly from_role?: string;
171
+ readonly on_relation?: string;
172
+ readonly from_relation?: string;
173
+ readonly actor_type?: string;
174
+ readonly when?: ConditionExpression;
175
+ }
176
+ /** Conditional rule (permit or forbid). */
177
+ interface Rule {
178
+ readonly effect: "permit" | "forbid";
179
+ readonly roles?: string[];
180
+ readonly permissions: string[];
181
+ readonly when: ConditionExpression;
182
+ }
183
+ /** Field-level access control definition. */
184
+ interface FieldAccessDef {
185
+ readonly read?: string[];
186
+ readonly update?: string[];
187
+ }
188
+ /** Resource block definition. */
189
+ interface ResourceBlock {
190
+ readonly roles: string[];
191
+ readonly permissions: string[];
192
+ /** Optional typed attribute declarations for this resource type. */
193
+ readonly attributes?: Record<string, AttributeType>;
194
+ /** Relations map field names to target resource type names (simplified). */
195
+ readonly relations?: Record<string, string>;
196
+ readonly grants?: Record<string, string[]>;
197
+ readonly derived_roles?: DerivedRoleEntry[];
198
+ readonly rules?: Rule[];
199
+ readonly field_access?: Record<string, FieldAccessDef>;
200
+ }
201
+ /** Operator-based condition value. */
202
+ type ConditionOperator = {
203
+ readonly eq: unknown;
204
+ } | {
205
+ readonly neq: unknown;
206
+ } | {
207
+ readonly gt: unknown;
208
+ } | {
209
+ readonly gte: unknown;
210
+ } | {
211
+ readonly lt: unknown;
212
+ } | {
213
+ readonly lte: unknown;
214
+ } | {
215
+ readonly in: unknown[] | string;
216
+ } | {
217
+ readonly includes: unknown;
218
+ } | {
219
+ readonly exists: boolean;
220
+ } | {
221
+ readonly startsWith: string;
222
+ } | {
223
+ readonly endsWith: string;
224
+ } | {
225
+ readonly contains: string;
226
+ } | {
227
+ readonly custom: string;
228
+ };
229
+ /**
230
+ * Condition value: either a primitive (equality shorthand),
231
+ * a cross-reference string ($actor.x, $resource.x, $env.x),
232
+ * or an operator object.
233
+ */
234
+ type ConditionValue = string | number | boolean | ConditionOperator;
235
+ /** Simple conditions: all key-value pairs ANDed together. */
236
+ type SimpleConditions = Record<string, ConditionValue>;
237
+ /**
238
+ * Recursive condition expression.
239
+ * Either simple conditions (Record<string, ConditionValue>),
240
+ * or a logical combinator ({ any: ... } or { all: ... }).
241
+ */
242
+ type ConditionExpression = SimpleConditions | {
243
+ readonly any: ConditionExpression[];
244
+ } | {
245
+ readonly all: ConditionExpression[];
246
+ };
247
+ /** Test case for declarative YAML tests. */
248
+ interface TestCase {
249
+ readonly name: string;
250
+ readonly actor: ActorRef;
251
+ /** Mock resolver data: keyed by "Type:id", values are attribute objects. */
252
+ readonly resolvers?: Record<string, Record<string, unknown>>;
253
+ readonly action: string;
254
+ readonly resource: ResourceRef;
255
+ readonly expected: "allow" | "deny";
256
+ }
257
+ /** Top-level policy object. */
258
+ interface Policy {
259
+ readonly version: "1";
260
+ readonly actors: Record<string, ActorDeclaration>;
261
+ readonly global_roles?: Record<string, GlobalRole>;
262
+ readonly resources: Record<string, ResourceBlock>;
263
+ readonly tests?: TestCase[];
264
+ }
265
+ /** Trace for a derived role showing derivation path. */
266
+ interface DerivedRoleTrace {
267
+ readonly role: string;
268
+ readonly via: string;
269
+ }
270
+ /** Resolved roles detail with direct and derived breakdown. */
271
+ interface ResolvedRolesDetail {
272
+ readonly direct: string[];
273
+ readonly derived: DerivedRoleTrace[];
274
+ }
275
+ /** A matched rule with evaluation context. */
276
+ interface MatchedRule {
277
+ readonly effect: "permit" | "forbid";
278
+ readonly matched: boolean;
279
+ readonly rule: Rule;
280
+ readonly resolvedValues: Record<string, unknown>;
281
+ }
282
+ /** Full decision trace from explain(). */
283
+ interface ExplainResult<S extends TorideSchema = DefaultSchema, R extends S["resources"] = S["resources"]> {
284
+ readonly allowed: boolean;
285
+ readonly resolvedRoles: ResolvedRolesDetail;
286
+ readonly grantedPermissions: S["permissionMap"][R][];
287
+ readonly matchedRules: MatchedRule[];
288
+ readonly finalDecision: string;
289
+ }
290
+ /** Audit event for authorization checks. */
291
+ interface DecisionEvent {
292
+ readonly actor: ActorRef;
293
+ readonly action: string;
294
+ readonly resource: ResourceRef;
295
+ readonly allowed: boolean;
296
+ readonly resolvedRoles: string[];
297
+ readonly matchedRules: {
298
+ effect: string;
299
+ matched: boolean;
300
+ }[];
301
+ readonly timestamp: Date;
302
+ }
303
+ /** Audit event for constraint queries. */
304
+ interface QueryEvent {
305
+ readonly actor: ActorRef;
306
+ readonly action: string;
307
+ readonly resourceType: string;
308
+ readonly resultType: "unrestricted" | "forbidden" | "constrained";
309
+ readonly timestamp: Date;
310
+ }
311
+ /** Thrown when policy validation fails. */
312
+ declare class ValidationError extends Error {
313
+ readonly path: string;
314
+ constructor(message: string, path: string);
315
+ }
316
+ /** Thrown when a cycle is detected in relation traversal. */
317
+ declare class CycleError extends Error {
318
+ readonly path: string[];
319
+ constructor(message: string, path: string[]);
320
+ }
321
+ /** Thrown when depth limit is exceeded. */
322
+ declare class DepthLimitError extends Error {
323
+ readonly limit: number;
324
+ readonly limitType: "condition" | "derivation";
325
+ constructor(message: string, limit: number, limitType: "condition" | "derivation");
326
+ }
327
+
328
+ /**
329
+ * A serializable map of permissions keyed by "Type:id".
330
+ * Values are arrays of permitted action strings.
331
+ * Suitable for JSON transport to client-side TorideClient.
332
+ */
333
+ type PermissionSnapshot = Record<string, string[]>;
334
+
335
+ declare const CLIENT_VERSION = "0.0.1";
336
+
337
+ /** Minimal resource reference for client-side lookups. Generic over S for type narrowing. */
338
+ interface ClientResourceRef<S extends TorideSchema = DefaultSchema> {
339
+ readonly type: S["resources"];
340
+ readonly id: string;
341
+ }
342
+ /**
343
+ * Client-side permission checker that provides instant synchronous checks
344
+ * against a PermissionSnapshot received from the server.
345
+ *
346
+ * Generic over TorideSchema so that action names and resource types
347
+ * are validated at compile time when a concrete schema is provided.
348
+ *
349
+ * Default-deny: unknown resources or actions return false.
350
+ * The snapshot is defensively copied to prevent external mutation.
351
+ */
352
+ declare class TorideClient<S extends TorideSchema = DefaultSchema> {
353
+ private readonly permissions;
354
+ constructor(snapshot: PermissionSnapshot);
355
+ /**
356
+ * Synchronous permission check.
357
+ * Returns true if the action is permitted for the resource, false otherwise.
358
+ * Unknown resources return false (default-deny).
359
+ */
360
+ can(action: S["actions"], resource: ClientResourceRef<S>): boolean;
361
+ /**
362
+ * Return the list of permitted actions for a resource.
363
+ * Returns empty array for unknown resources.
364
+ */
365
+ permittedActions(resource: ClientResourceRef<S>): S["actions"][];
366
+ }
367
+
368
+ export { type ActorRef as A, type BatchCheckItem as B, type CheckOptions as C, type DefaultSchema as D, type ExplainResult as E, type FieldAccessDef as F, type GlobalRole as G, type MatchedRule as M, type Policy as P, type QueryEvent as Q, type ResourceRef as R, type SimpleConditions as S, type TorideSchema as T, ValidationError as V, type TorideOptions as a, type PermissionSnapshot as b, type TestCase as c, type Resolvers as d, type ActorDeclaration as e, type AttributeType as f, type ClientResourceRef as g, type ConditionExpression as h, type ConditionOperator as i, type ConditionValue as j, CycleError as k, type DecisionEvent as l, DepthLimitError as m, type DerivedRoleEntry as n, type DerivedRoleTrace as o, type EvaluatorFn as p, type ResolvedRolesDetail as q, type ResourceBlock as r, type ResourceResolver as s, type Rule as t, TorideClient as u, CLIENT_VERSION as v };
@@ -0,0 +1 @@
1
+ export { v as CLIENT_VERSION, g as ClientResourceRef, b as PermissionSnapshot, u as TorideClient } from './client-RqwW0K_-.js';
package/dist/client.js ADDED
@@ -0,0 +1,8 @@
1
+ import {
2
+ CLIENT_VERSION,
3
+ TorideClient
4
+ } from "./chunk-475CNU63.js";
5
+ export {
6
+ CLIENT_VERSION,
7
+ TorideClient
8
+ };