@schoolai/spicedb-zed-schema-parser
Version:
SpiceDB .zed file format parser and analyzer written in Typescript
817 lines (807 loc) • 25 kB
JavaScript
"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