authz-schema-sync-check
June 3, 2025 ยท View on GitHub
A pre-commit hook for generating and syncing type definitions from SpiceDB schema.
Overview
This package provides a pre-commit hook that generates type definitions from a SpiceDB schema file (schema.zed) and ensures they are in sync with output files. It supports generating both Python and TypeScript type definitions, and can be extended to support other languages through custom templates. It uses Git to compare the generated code with the existing code and can automatically apply changes.
Installation
This package is built with Poetry and should be used with Poetry:
# For development
git clone https://github.com/yahiaosama/authz-schema-sync-check.git
cd authz-schema-sync-check
poetry install
Usage
As a Pre-commit Hook
Add the following to your .pre-commit-config.yaml file to use it exactly as in the backend repository:
repos:
- repo: https://github.com/yahiaosama/authz-schema-sync-check
rev: v0.2.0
hooks:
- id: authz-schema-sync-check
args: [
"--schema", "backend/app/infra/authz/schema.zed",
"--outputs",
"backend/app/infra/authz/resources.py",
"frontend-apps/packages/shared/src/authz/resources.ts",
"--verbose",
"--colorized-diff=true",
"--auto-fix"
]
files: 'backend/app/infra/authz/schema\.zed$|backend/app/infra/authz/resources\.py$|frontend-apps/packages/shared/src/authz/resources\.ts$'
pass_filenames: false
Available options:
--schema: Path to the schema.zed file (default:schema.zed)--outputs: Output paths, optionally with template names in format 'output_path[:template_name]' (required)- For
.pyfiles, the default template isdefault_types.py.jinja - For
.tsfiles, the default template isdefault_types.ts.jinja - For other file types, you must specify the template explicitly
- For
--auto-fix: Automatically apply changes if out of sync--verbose: Enable verbose output--colorized-diff: Enable or disable colorized diff output (true/false, default: true)
The files pattern determines when the hook runs. In the example above, it will run whenever:
- The
schema.zedfile is modified - The
resources.pyfile is modified - The
resources.tsfile is modified
Important Note: The hook will fail if any output file doesn't exist, even with --auto-fix enabled. With --auto-fix, it will create the file but still fail, requiring you to review and commit the newly created file in a separate step. This is an intentional security feature to ensure that generated files are always explicitly committed by the user, preventing accidental inclusion of unreviewed generated code.
As a Command-line Tool
You can also use the package as a command-line tool:
# Generate a single output with default template (inferred from file extension)
poetry run authz-schema-sync-check --schema path/to/schema.zed --outputs "path/to/resources.py"
# Generate multiple outputs with default templates
poetry run authz-schema-sync-check --schema path/to/schema.zed --outputs \
"path/to/resources.py" \
"path/to/resources.ts"
# Generate outputs with explicit templates
poetry run authz-schema-sync-check --schema path/to/schema.zed --outputs \
"path/to/resources.py:default_types.py.jinja" \
"path/to/resources.ts:default_types.ts.jinja"
# Automatically apply changes
poetry run authz-schema-sync-check --schema path/to/schema.zed --outputs \
"path/to/resources.py" \
--auto-fix
# Check with colorized diff disabled
poetry run authz-schema-sync-check --schema path/to/schema.zed --outputs \
"path/to/resources.py" \
--colorized-diff=false
Options:
--schema: Path to the schema.zed file (default:schema.zed)--outputs: Output paths, optionally with template names in format 'output_path[:template_name]' (required)- For
.pyfiles, the default template isdefault_types.py.jinja - For
.tsfiles, the default template isdefault_types.ts.jinja - For other file types, you must specify the template explicitly
- For
--auto-fix: Automatically apply changes if out of sync--verbose: Enable verbose output--colorized-diff: Enable or disable colorized diff output (true/false, default: true)
Generated Type Definitions
The package can generate type definitions in multiple languages based on the schema. The available templates are:
default_types.py.jinja: Generates Python type definitionsdefault_types.ts.jinja: Generates TypeScript type definitions
You can also create your own templates in the templates directory.
Python Type Definitions
The Python template generates the following:
- Permission Type Aliases: Type literals for permissions specific to each resource type.
- Resource Base Class: A generic base class for all resources.
- Resource Classes: Classes for each object type defined in the schema, with a
permission_typeclass variable.
Example
schema.zed
definition user {
relation organization: organization
relation self: user
permission read = self
permission update = self
permission make_admin = organization->administrate
permission revoke_admin = organization->administrate
}
definition group {
relation organization: organization
relation member: user | group#member
permission edit_members = organization->administrate
}
definition organization {
relation admin: user
relation member: user | group#member
permission administrate = admin
permission read = member
}
Generated Python Output (resources.py)
"""
GENERATED CODE - DO NOT EDIT MANUALLY
This file is generated from schema.zed and should not be modified directly.
"""
from typing import Generic, Literal, TypeVar
# Permission type aliases for each resource type
UserPermission = Literal["read", "update", "make_admin", "revoke_admin"]
GroupPermission = Literal["edit_members"]
OrganizationPermission = Literal["administrate", "read"]
# Type variable for permission literals
P = TypeVar("P", bound=str)
# Base resource class
class Resource(Generic[P]):
"""Base class for all resources with typed permissions."""
def __init__(self, id: str, resource_type: str):
self.id = id
self.type = resource_type
# Resource classes with their specific permission types
class User(Resource[UserPermission]):
"""User resource from schema.zed"""
# Set the permission type for this resource
permission_type = UserPermission
def __init__(self, id: str):
super().__init__(id, "user")
class Group(Resource[GroupPermission]):
"""Group resource from schema.zed"""
# Set the permission type for this resource
permission_type = GroupPermission
def __init__(self, id: str):
super().__init__(id, "group")
class Organization(Resource[OrganizationPermission]):
"""Organization resource from schema.zed"""
# Set the permission type for this resource
permission_type = OrganizationPermission
def __init__(self, id: str):
super().__init__(id, "organization")
# Note: snake_case resource names are converted to CamelCase
# For example, table_view would become TableView
Development
Setup
-
Clone the repository:
git clone https://github.com/yahiaosama/authz-schema-sync-check.git cd authz-schema-sync-check -
Install dependencies with Poetry:
poetry install
Running Tests
poetry run pytest
TypeScript Type Definitions
The TypeScript template generates a discriminated union type that represents all valid resource-permission combinations from the schema.
Generated TypeScript Output (resources.ts)
/**
* GENERATED CODE - DO NOT EDIT MANUALLY
* This file is generated from schema.zed and should not be modified directly.
*/
/**
* Type representing all valid resource-permission combinations
* from the SpiceDB schema.
*/
export type ResourcePermission =
| { resource: "user"; permission: "read" | "update" | "make_admin" | "revoke_admin"; resourceId: string | number }
| { resource: "group"; permission: "edit_members"; resourceId: string | number }
| { resource: "organization"; permission: "administrate" | "read"; resourceId: string | number };
This TypeScript type ensures that only valid resource-permission combinations can be used at compile time. For example:
// Valid combinations
const valid1: ResourcePermission = { resource: "user", permission: "read", resourceId: "user-123" };
const valid2: ResourcePermission = { resource: "organization", permission: "administrate", resourceId: 456 };
// Invalid combinations - TypeScript error
const invalid1: ResourcePermission = { resource: "user", permission: "administrate", resourceId: "user-123" }; // Error
const invalid2: ResourcePermission = { resource: "invalid", permission: "read", resourceId: 789 }; // Error
Troubleshooting
Missing Output File
If you see an error like:
Error processing path/to/resources.py: Output file does not exist
This means the output file doesn't exist yet. Run the hook with --auto-fix to create it:
poetry run authz-schema-sync-check --schema path/to/schema.zed --outputs "path/to/resources.py" --auto-fix
The hook will create the file but still fail with an error like:
Error processing path/to/resources.py: Output file did not exist but has been created
Please review and commit the newly created file: path/to/resources.py
This is intentional - you need to review the generated file and commit it manually. This ensures that generated files are always explicitly reviewed before being committed.
Files Out of Sync
If you see an error like:
Error processing path/to/resources.py: File is out of sync with schema
This means you've modified the schema.zed file but haven't updated the output file. Run the hook with --auto-fix to update it:
poetry run authz-schema-sync-check --schema path/to/schema.zed --outputs "path/to/resources.py" --auto-fix
Then review the changes and commit them.
Template Not Found
If you see an error like:
Error processing path/to/resources.py: Template 'nonexistent.jinja' not found
This means the specified template doesn't exist. Make sure the template exists in the templates directory.
License
MIT