UNPKG

@schoolai/spicedb-zed-schema-parser

Version:

SpiceDB .zed file format parser and analyzer written in Typescript

817 lines (807 loc) 25 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); // src/builder/index.ts var builder_exports = {}; __export(builder_exports, { BoundCheckOperation: () => BoundCheckOperation, BoundDeleteOperation: () => BoundDeleteOperation, BoundLookupOperation: () => BoundLookupOperation, BoundQueryOperation: () => BoundQueryOperation, BoundTransaction: () => BoundTransaction, BoundWriteOperation: () => BoundWriteOperation, CheckOperation: () => CheckOperation, DeleteOperation: () => DeleteOperation, LookupOperation: () => LookupOperation, Operations: () => Operations, PermissionOperations: () => PermissionOperations, Permissions: () => Permissions, QueryOperation: () => QueryOperation, Transaction: () => Transaction, TransactionWriteOperation: () => TransactionWriteOperation, WriteOperation: () => WriteOperation, createPermissions: () => createPermissions, parseReference: () => parseReference }); module.exports = __toCommonJS(builder_exports); // src/builder/check.ts var import_authzed_node = require("@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 = import_authzed_node.v1.Consistency.create({ requirement: { oneofKind: "atLeastAsFresh", atLeastAsFresh: import_authzed_node.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 = import_authzed_node.v1.CheckPermissionRequest.create({ resource: import_authzed_node.v1.ObjectReference.create({ objectType: resourceType, objectId: resourceId }), permission: this.permission, subject: import_authzed_node.v1.SubjectReference.create({ object: import_authzed_node.v1.ObjectReference.create({ objectType: subjectType, objectId: subjectId }) }), consistency: this.consistency }); const response = await client.checkPermission(request); return response.permissionship === import_authzed_node.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 var import_authzed_node2 = require("@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 = import_authzed_node2.v1.SubjectFilter.create(subjectFilter); } const request = import_authzed_node2.v1.DeleteRelationshipsRequest.create({ relationshipFilter: import_authzed_node2.v1.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 var import_authzed_node3 = require("@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 = import_authzed_node3.v1.Consistency.create({ requirement: { oneofKind: "atLeastAsFresh", atLeastAsFresh: import_authzed_node3.v1.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 = import_authzed_node3.v1.LookupResourcesRequest.create({ resourceObjectType: this.resourceFilter?.type || "document", permission: this.permission, subject: import_authzed_node3.v1.SubjectReference.create({ object: import_authzed_node3.v1.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 = import_authzed_node3.v1.LookupSubjectsRequest.create({ resource: import_authzed_node3.v1.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 = import_authzed_node3.v1.LookupSubjectsRequest.create({ resource: import_authzed_node3.v1.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 var import_authzed_node4 = require("@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 = import_authzed_node4.v1.Consistency.create({ requirement: { oneofKind: "atLeastAsFresh", atLeastAsFresh: import_authzed_node4.v1.ZedToken.create({ token }) } }); return this; } async execute(client) { if (this.queryType === "subjects" && this.filter.resourceType && this.filter.resourceId && this.permission) { const request2 = import_authzed_node4.v1.LookupSubjectsRequest.create({ resource: import_authzed_node4.v1.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 === import_authzed_node4.v1.LookupPermissionship.HAS_PERMISSION ? this.permission : void 0 }); } } return results2; } if (this.queryType === "resources" && this.filter.subjectType && this.filter.subjectId && this.permission) { const request2 = import_authzed_node4.v1.LookupResourcesRequest.create({ resourceObjectType: this.filter.resourceType || "document", permission: this.permission, subject: import_authzed_node4.v1.SubjectReference.create({ object: import_authzed_node4.v1.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 = import_authzed_node4.v1.SubjectFilter.create(subjectFilter); } const request = import_authzed_node4.v1.ReadRelationshipsRequest.create({ relationshipFilter: import_authzed_node4.v1.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 var import_authzed_node5 = require("@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 = import_authzed_node5.v1.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 = import_authzed_node5.v1.Relationship.create({ resource: import_authzed_node5.v1.ObjectReference.create({ objectType: resourceType, objectId: resourceId }), relation: this.relation, subject: import_authzed_node5.v1.SubjectReference.create({ object: import_authzed_node5.v1.ObjectReference.create({ objectType: subjectType, objectId: subjectId }) }) }); return import_authzed_node5.v1.RelationshipUpdate.create({ relationship, operation: this.operation === "grant" ? import_authzed_node5.v1.RelationshipUpdate_Operation.TOUCH : import_authzed_node5.v1.RelationshipUpdate_Operation.DELETE }); }); } } return this.transaction; } }; // src/builder/write.ts var import_authzed_node6 = require("@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 = import_authzed_node6.v1.Consistency.create({ requirement: { oneofKind: "atLeastAsFresh", atLeastAsFresh: import_authzed_node6.v1.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 = import_authzed_node6.v1.Relationship.create({ resource: import_authzed_node6.v1.ObjectReference.create({ objectType: resourceType, objectId: resourceId }), relation: this.relation, subject: import_authzed_node6.v1.SubjectReference.create({ object: import_authzed_node6.v1.ObjectReference.create({ objectType: subjectType, objectId: subjectId }) }) }); updates.push( import_authzed_node6.v1.RelationshipUpdate.create({ relationship, operation: this.operation === "grant" ? import_authzed_node6.v1.RelationshipUpdate_Operation.TOUCH : import_authzed_node6.v1.RelationshipUpdate_Operation.DELETE }) ); } } const request = import_authzed_node6.v1.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; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { BoundCheckOperation, BoundDeleteOperation, BoundLookupOperation, BoundQueryOperation, BoundTransaction, BoundWriteOperation, CheckOperation, DeleteOperation, LookupOperation, Operations, PermissionOperations, Permissions, QueryOperation, Transaction, TransactionWriteOperation, WriteOperation, createPermissions, parseReference }); //# sourceMappingURL=index.cjs.map