UNPKG

@schoolai/spicedb-zed-schema-parser

Version:

SpiceDB .zed file format parser and analyzer written in Typescript

775 lines (766 loc) 22.2 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); // src/builder/check.ts import { v1 } from "@authzed/authzed-node"; // src/builder/types.ts function parseReference(ref) { const [part1, part2] = ref.split(":"); if (!part1 || !part2) { throw new Error( `Invalid reference format: ${ref}. Expected format: type:id` ); } return [part1, part2]; } // src/builder/check.ts var CheckOperation = class { constructor(permission) { this.permission = permission; __publicField(this, "subjectRef"); __publicField(this, "resourceRef"); __publicField(this, "consistency"); } subject(ref) { this.subjectRef = ref; return this; } resource(ref) { this.resourceRef = ref; return this; } withConsistency(token) { this.consistency = v1.Consistency.create({ requirement: { oneofKind: "atLeastAsFresh", atLeastAsFresh: v1.ZedToken.create({ token }) } }); return this; } async execute(client) { if (!this.subjectRef || !this.resourceRef) { throw new Error("Check operation requires both subject and resource"); } const [subjectType, subjectId] = parseReference(this.subjectRef); const [resourceType, resourceId] = parseReference(this.resourceRef); const request = v1.CheckPermissionRequest.create({ resource: v1.ObjectReference.create({ objectType: resourceType, objectId: resourceId }), permission: this.permission, subject: v1.SubjectReference.create({ object: v1.ObjectReference.create({ objectType: subjectType, objectId: subjectId }) }), consistency: this.consistency }); const response = await client.checkPermission(request); return response.permissionship === v1.CheckPermissionResponse_Permissionship.HAS_PERMISSION; } toJSON() { return { permission: this.permission, subject: this.subjectRef, resource: this.resourceRef, consistency: this.consistency }; } }; var BoundCheckOperation = class extends CheckOperation { constructor(client, permission) { super(permission); this.client = client; } async execute() { return super.execute(this.client); } }; // src/builder/delete.ts import { v1 as v12 } from "@authzed/authzed-node"; var DeleteOperation = class { constructor() { __publicField(this, "filter", {}); } subject(ref) { const [type, id] = parseReference(ref); this.filter.subjectType = type; this.filter.subjectId = id; return this; } relation(rel) { this.filter.relation = rel; return this; } resource(ref) { const [type, id] = parseReference(ref); this.filter.resourceType = type; this.filter.resourceId = id; return this; } where(filter) { this.filter = { ...this.filter, ...filter }; return this; } async execute(client) { const relationshipFilter = {}; if (this.filter.resourceType) { relationshipFilter.resourceType = this.filter.resourceType; } if (this.filter.resourceId) { relationshipFilter.optionalResourceId = this.filter.resourceId; } if (this.filter.relation) { relationshipFilter.optionalRelation = this.filter.relation; } if (this.filter.subjectType || this.filter.subjectId) { const subjectFilter = {}; if (this.filter.subjectType) { subjectFilter.subjectType = this.filter.subjectType; } if (this.filter.subjectId) { subjectFilter.optionalSubjectId = this.filter.subjectId; } relationshipFilter.optionalSubjectFilter = v12.SubjectFilter.create(subjectFilter); } const request = v12.DeleteRelationshipsRequest.create({ relationshipFilter: v12.RelationshipFilter.create(relationshipFilter) }); const response = await client.deleteRelationships(request); return response.deletedAt?.token || null; } toJSON() { return { filter: this.filter }; } }; var BoundDeleteOperation = class extends DeleteOperation { constructor(client) { super(); this.client = client; } async execute() { return super.execute(this.client); } }; // src/builder/lookup.ts import { v1 as v13 } from "@authzed/authzed-node"; var LookupOperation = class { constructor() { __publicField(this, "lookupType"); __publicField(this, "resourceFilter"); __publicField(this, "subjectFilter"); __publicField(this, "permission"); __publicField(this, "consistency"); } resourcesAccessibleBy(subjectRef) { this.lookupType = "resources"; const [type, id] = parseReference(subjectRef); this.subjectFilter = { type, id }; return this; } subjectsWithAccessTo(resourceRef) { this.lookupType = "subjects"; const [type, id] = parseReference(resourceRef); this.resourceFilter = { type, id }; return this; } ofType(type) { if (this.lookupType === "resources") { this.resourceFilter = { type }; } else if (this.lookupType === "subjects") { this.subjectFilter = { type }; } return this; } withPermission(permission) { this.permission = permission; return this; } withConsistency(token) { this.consistency = v13.Consistency.create({ requirement: { oneofKind: "atLeastAsFresh", atLeastAsFresh: v13.ZedToken.create({ token }) } }); return this; } async execute(client) { if (!this.permission) { throw new Error("Lookup operation requires permission"); } if (this.lookupType === "resources" && this.subjectFilter) { const request = v13.LookupResourcesRequest.create({ resourceObjectType: this.resourceFilter?.type || "document", permission: this.permission, subject: v13.SubjectReference.create({ object: v13.ObjectReference.create({ objectType: this.subjectFilter.type, objectId: this.subjectFilter.id }) }), consistency: this.consistency }); const stream = await client.lookupResources(request); const results = []; for (const result of stream) { if (result.resourceObjectId) { results.push({ type: this.resourceFilter?.type || "document", id: result.resourceObjectId, permissionship: result.permissionship }); } } return results; } if (this.lookupType === "subjects" && this.resourceFilter?.id) { const request = v13.LookupSubjectsRequest.create({ resource: v13.ObjectReference.create({ objectType: this.resourceFilter.type, objectId: this.resourceFilter.id }), permission: this.permission, subjectObjectType: this.subjectFilter?.type || "user", consistency: this.consistency }); const stream = await client.lookupSubjects(request); const results = []; for (const result of stream) { if (result.subject?.subjectObjectId) { results.push({ type: this.subjectFilter?.type || "user", id: result.subject.subjectObjectId, permissionship: result.subject.permissionship }); } } return results; } throw new Error("Invalid lookup configuration"); } /** * Special helper for looking up subjects with multiple permission levels */ async withPermissions(permissions, client) { if (!this.resourceFilter?.id) { throw new Error("Multiple permission lookup requires a specific resource"); } if (!client) { throw new Error( "withPermissions requires a client. Use execute(client) or pass client as second parameter." ); } const resultMap = /* @__PURE__ */ new Map(); for (const permission of permissions) { const request = v13.LookupSubjectsRequest.create({ resource: v13.ObjectReference.create({ objectType: this.resourceFilter.type, objectId: this.resourceFilter.id }), permission, subjectObjectType: this.subjectFilter?.type || "user", consistency: this.consistency }); const stream = await client.lookupSubjects(request); for (const result of stream) { if (result.subject?.subjectObjectId) { if (!resultMap.has(result.subject.subjectObjectId)) { resultMap.set(result.subject.subjectObjectId, permission); } } } } return resultMap; } toJSON() { return { lookupType: this.lookupType, resourceFilter: this.resourceFilter, subjectFilter: this.subjectFilter, permission: this.permission, consistency: this.consistency }; } }; var BoundLookupOperation = class extends LookupOperation { constructor(client) { super(); this.client = client; } async execute() { return super.execute(this.client); } async withPermissions(permissions) { return super.withPermissions(permissions, this.client); } }; // src/builder/query.ts import { v1 as v14 } from "@authzed/authzed-node"; var QueryOperation = class { constructor() { __publicField(this, "filter", {}); __publicField(this, "queryType"); __publicField(this, "permission"); __publicField(this, "consistency"); } subjects(type) { this.queryType = "subjects"; if (type) this.filter.subjectType = type; return this; } resources(type) { this.queryType = "resources"; if (type) this.filter.resourceType = type; return this; } subject(ref) { if (ref.includes("*")) { const [type] = parseReference(ref); this.filter.subjectType = type; } else { const [type, id] = parseReference(ref); this.filter.subjectType = type; this.filter.subjectId = id; } return this; } relation(rel) { if (rel !== "*") { this.filter.relation = rel; } return this; } resource(ref) { if (ref.includes("*")) { const [type] = parseReference(ref); this.filter.resourceType = type; } else { const [type, id] = parseReference(ref); this.filter.resourceType = type; this.filter.resourceId = id; } return this; } withPermission(permission) { this.permission = permission; return this; } withConsistency(token) { this.consistency = v14.Consistency.create({ requirement: { oneofKind: "atLeastAsFresh", atLeastAsFresh: v14.ZedToken.create({ token }) } }); return this; } async execute(client) { if (this.queryType === "subjects" && this.filter.resourceType && this.filter.resourceId && this.permission) { const request2 = v14.LookupSubjectsRequest.create({ resource: v14.ObjectReference.create({ objectType: this.filter.resourceType, objectId: this.filter.resourceId }), permission: this.permission, subjectObjectType: this.filter.subjectType || "user", consistency: this.consistency }); const stream2 = await client.lookupSubjects(request2); const results2 = []; for (const result of stream2) { if (result.subject?.subjectObjectId) { results2.push({ type: this.filter.subjectType || "user", id: result.subject.subjectObjectId, relation: result.subject.permissionship === v14.LookupPermissionship.HAS_PERMISSION ? this.permission : void 0 }); } } return results2; } if (this.queryType === "resources" && this.filter.subjectType && this.filter.subjectId && this.permission) { const request2 = v14.LookupResourcesRequest.create({ resourceObjectType: this.filter.resourceType || "document", permission: this.permission, subject: v14.SubjectReference.create({ object: v14.ObjectReference.create({ objectType: this.filter.subjectType, objectId: this.filter.subjectId }) }), consistency: this.consistency }); const stream2 = await client.lookupResources(request2); const results2 = []; for (const result of stream2) { if (result.resourceObjectId) { results2.push({ type: this.filter.resourceType || "document", id: result.resourceObjectId, permissionship: result.permissionship }); } } return results2; } const filter = {}; if (this.filter.resourceType) { filter.resourceType = this.filter.resourceType; } if (this.filter.resourceId) { filter.optionalResourceId = this.filter.resourceId; } if (this.filter.relation) { filter.optionalRelation = this.filter.relation; } if (this.filter.subjectType || this.filter.subjectId) { const subjectFilter = {}; if (this.filter.subjectType) { subjectFilter.subjectType = this.filter.subjectType; } if (this.filter.subjectId) { subjectFilter.optionalSubjectId = this.filter.subjectId; } filter.optionalSubjectFilter = v14.SubjectFilter.create(subjectFilter); } const request = v14.ReadRelationshipsRequest.create({ relationshipFilter: v14.RelationshipFilter.create(filter), consistency: this.consistency }); const stream = await client.readRelationships(request); const results = []; for (const result of stream) { if (result.relationship) { results.push({ type: result.relationship.resource?.objectType || "", id: result.relationship.resource?.objectId || "", relation: result.relationship.relation, subjectType: result.relationship.subject?.object?.objectType || "", subjectId: result.relationship.subject?.object?.objectId || "" }); } } return results; } toJSON() { return { queryType: this.queryType, filter: this.filter, permission: this.permission, consistency: this.consistency }; } }; var BoundQueryOperation = class extends QueryOperation { constructor(client) { super(); this.client = client; } async execute() { return super.execute(this.client); } }; // src/builder/transaction.ts import { v1 as v15 } from "@authzed/authzed-node"; var Transaction = class { constructor() { __publicField(this, "operations", []); } grant(relation) { return new TransactionWriteOperation(this, "grant", relation); } revoke(relation) { return new TransactionWriteOperation(this, "revoke", relation); } add(operation) { this.operations.push(operation); return this; } async execute(client) { const updates = this.operations.map((op) => op()); const request = v15.WriteRelationshipsRequest.create({ updates }); const response = await client.writeRelationships(request); return { token: response.writtenAt?.token || null, succeeded: true, operationCount: updates.length }; } toJSON() { return { operationCount: this.operations.length }; } }; var BoundTransaction = class extends Transaction { constructor(client) { super(); this.client = client; } grant(relation) { return new TransactionWriteOperation(this, "grant", relation); } revoke(relation) { return new TransactionWriteOperation(this, "revoke", relation); } async execute() { return super.execute(this.client); } async commit() { return this.execute(); } }; var TransactionWriteOperation = class { constructor(transaction, operation, relation) { this.transaction = transaction; this.operation = operation; this.relation = relation; __publicField(this, "subjects", []); __publicField(this, "resources", []); } subject(ref) { this.subjects = Array.isArray(ref) ? ref : [ref]; return this; } resource(ref) { this.resources = Array.isArray(ref) ? ref : [ref]; return this; } and() { for (const subjectRef of this.subjects) { for (const resourceRef of this.resources) { this.transaction.add(() => { const [subjectType, subjectId] = parseReference(subjectRef); const [resourceType, resourceId] = parseReference(resourceRef); const relationship = v15.Relationship.create({ resource: v15.ObjectReference.create({ objectType: resourceType, objectId: resourceId }), relation: this.relation, subject: v15.SubjectReference.create({ object: v15.ObjectReference.create({ objectType: subjectType, objectId: subjectId }) }) }); return v15.RelationshipUpdate.create({ relationship, operation: this.operation === "grant" ? v15.RelationshipUpdate_Operation.TOUCH : v15.RelationshipUpdate_Operation.DELETE }); }); } } return this.transaction; } }; // src/builder/write.ts import { v1 as v16 } from "@authzed/authzed-node"; var WriteOperation = class { constructor(operation, relation) { this.operation = operation; this.relation = relation; __publicField(this, "subjects", []); __publicField(this, "resources", []); __publicField(this, "consistency"); } subject(ref) { this.subjects = Array.isArray(ref) ? ref : [ref]; return this; } resource(ref) { this.resources = Array.isArray(ref) ? ref : [ref]; return this; } withConsistency(token) { this.consistency = v16.Consistency.create({ requirement: { oneofKind: "atLeastAsFresh", atLeastAsFresh: v16.ZedToken.create({ token }) } }); return this; } async execute(client) { const updates = []; for (const subjectRef of this.subjects) { for (const resourceRef of this.resources) { const [subjectType, subjectId] = parseReference(subjectRef); const [resourceType, resourceId] = parseReference(resourceRef); const relationship = v16.Relationship.create({ resource: v16.ObjectReference.create({ objectType: resourceType, objectId: resourceId }), relation: this.relation, subject: v16.SubjectReference.create({ object: v16.ObjectReference.create({ objectType: subjectType, objectId: subjectId }) }) }); updates.push( v16.RelationshipUpdate.create({ relationship, operation: this.operation === "grant" ? v16.RelationshipUpdate_Operation.TOUCH : v16.RelationshipUpdate_Operation.DELETE }) ); } } const request = v16.WriteRelationshipsRequest.create({ updates }); const response = await client.writeRelationships(request); return response.writtenAt?.token || null; } /** * Convert to a plain object for serialization */ toJSON() { return { operation: this.operation, relation: this.relation, subjects: this.subjects, resources: this.resources, consistency: this.consistency }; } }; var BoundWriteOperation = class extends WriteOperation { constructor(client, operation, relation) { super(operation, relation); this.client = client; } async execute() { return super.execute(this.client); } }; // src/builder/operations.ts var PermissionOperations = class { /** * Create a grant operation */ static grant(relation) { return new WriteOperation("grant", relation); } /** * Create a revoke operation */ static revoke(relation) { return new WriteOperation("revoke", relation); } /** * Create a check operation */ static check(permission) { return new CheckOperation(permission); } /** * Create a find/query operation */ static find() { return new QueryOperation(); } /** * Create a delete operation */ static delete() { return new DeleteOperation(); } /** * Create a batch transaction */ static batch() { return new Transaction(); } /** * Create a lookup operation */ static lookup() { return new LookupOperation(); } }; var Permissions = class { constructor(client) { this.client = client; } /** * Grant a relation between subjects and resources */ grant(relation) { return new BoundWriteOperation(this.client, "grant", relation); } /** * Revoke a relation between subjects and resources */ revoke(relation) { return new BoundWriteOperation(this.client, "revoke", relation); } /** * Check if a subject has a permission on a resource */ check(permission) { return new BoundCheckOperation(this.client, permission); } /** * Find subjects or resources matching criteria */ find() { return new BoundQueryOperation(this.client); } /** * Delete relationships matching a filter */ delete() { return new BoundDeleteOperation(this.client); } /** * Create a batch transaction */ batch() { return new BoundTransaction(this.client); } /** * Lookup resources accessible to a subject */ lookup() { return new BoundLookupOperation(this.client); } /** * Execute a pure operation with this instance's client */ async execute(operation) { return operation.execute(this.client); } }; function createPermissions(client) { return new Permissions(client); } var Operations = PermissionOperations; export { BoundCheckOperation, BoundDeleteOperation, BoundLookupOperation, BoundQueryOperation, BoundTransaction, BoundWriteOperation, CheckOperation, DeleteOperation, LookupOperation, Operations, PermissionOperations, Permissions, QueryOperation, Transaction, TransactionWriteOperation, WriteOperation, createPermissions, parseReference }; //# sourceMappingURL=index.js.map