Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/tomevault-io/skills-registry --skill graplix명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SKILL.md 표시 중
SOC 직업 분류 기준
| name | graplix |
| description | > Use when this capability is needed. |
Build relation-based access control with Graplix. This skill teaches you how to find current documentation and write correct Graplix code.
Everything you know about Graplix is likely outdated or wrong. Never rely on memory. Your training data may contain obsolete APIs. Always verify against the documentation referenced in this skill.
Before writing code, verify package installation:
ls node_modules/@graplix/engine/
references/quick-start.md| Question | Resource | Purpose |
|---|---|---|
| Project setup / installation | references/quick-start.md | Installation and first permission check guide |
| Schema syntax / keywords | references/schema-syntax.md | .graplix file syntax and relation expressions |
| API usage / type signatures | references/embedded-docs.md | Look up via installed package docs and type declarations |
| Error resolution | references/common-errors.md | Troubleshooting solutions |
Embedded docs (if @graplix/engine is installed)
node_modules/@graplix/engine/dist/docs/*.mdnode_modules/@graplix/engine/dist/index.d.mtsSource code (if packages installed)
node_modules/@graplix/engine/dist/index.mjsGraplix has two runtime components:
.graplix schema → buildEngine() → engine.check() / engine.explain()
(relation model) (async factory) (permission evaluation)
Data flow for a permission check:
engine.check({ user, object, relation, context })
→ resolveType(user) → EntityRef
→ resolveType(object) → EntityRef
→ evaluate relation graph (resolver.relations callbacks)
→ resolver.load() for any entity IDs encountered
→ true | false
Graplix schemas are .graplix text files. See references/schema-syntax.md for the full reference.
type user
type repository
relations
define owner: [user]
define member: [user]
define admin: owner or member
define can_delete: owner from organization
Key rules:
snake_case[TypeA, TypeB] — direct relation (user must be one of these types)relation from source — transitive via another relation on sourceterm or term — union of multiple termsdefine = type with no relations (still valid)import { buildEngine } from "@graplix/engine";
// 1. Define your entity types
type User = { id: string };
type Repository = { id: string; ownerIds: string[] };
// 2. Set up data (replace with your real data source)
const users = new Map<string, User>([
["user-1", { id: "user-1" }],
["user-2", { id: "user-2" }],
]);
const repos = new Map<string, Repository>([
["repo-1", { id: "repo-1", ownerIds: ["user-1"] }],
]);
// 3. Write your schema
const schema = `
type user
type repository
relations
define owner: [user]
define can_delete: owner
`;
// 4. Build the engine (async — validates schema eagerly)
const engine = await buildEngine<object, User | Repository>({
schema,
resolveType: (value) => {
if (typeof value !== || value === ) ;
( value) ;
;
},
: {
: {
: user.,
() {
users.(id) ?? ;
},
},
: {
: repo.,
() {
repos.(id) ?? ;
},
: {
() {
repo.
.( users.(id))
.((u): u is => u !== );
},
},
},
},
});
engine.({
: users.()!,
: repos.()!,
: ,
: {},
});
result = engine.({
: users.()!,
: repos.()!,
: ,
: {},
});
result.;
result.;
result.;
buildEngine Optionsconst engine = await buildEngine<TContext, TEntityInput>({
schema, // string — raw .graplix schema text (required)
resolvers, // Resolvers<TContext> — keyed by type name (required)
resolveType, // ResolveType<TContext> — entity type discriminator (required)
resolverTimeoutMs: 3000, // timeout (ms) for load and relation resolvers
maxCacheSize: 1000, // per-request LRU cache size (default: 500)
onError: (err) => { // called when entity resolution silently fails
console.error(err); // throw here to escalate to a hard failure
},
});
buildEngine is async. Schema validation happens at construction time — an invalid schema rejects immediately.
resolveTypetype ResolveType<TContext> = (value: unknown, context: TContext) => string | null;
null if unknownquery.user and query.object — must return the correct typenull is acceptable (engine uses schema hints)// ✅ Structural field discrimination
const resolveType: ResolveType<MyContext> = (value) => {
if (typeof value !== "object" || value === null) return null;
const v = value as Record<string, unknown>;
if ("adminIds" in v) return "organization";
if ("ownerIds" in v && "organizationId" in v) return "repository";
if ("ownerIds" in v) return "team";
return "user";
};
// ✅ instanceof checks (class-based models)
const resolveType: ResolveType<MyContext> = (value) => {
if (value instanceof Organization) return "organization";
if (value instanceof Repository) return ;
(value ) ;
;
};
Resolver Interfaceinterface Resolver<TEntity, TContext> {
id(entity: TEntity): string;
load(
id: string,
context: TContext,
info: ResolverInfo, // info.signal for timeout cancellation
): Promise<TEntity | null>;
relations?: {
[relation: string]: (
entity: TEntity,
context: TContext,
info: ResolverInfo,
) => TEntity | TEntity[] | null | Promise<TEntity | TEntity[] | null>;
};
}
contextPassed to every check/explain call and forwarded to all resolver functions. Use for request-scoped data: database connections, auth info, tenant IDs, etc.
type MyContext = { db: DB; userId: string };
const engine = await buildEngine<MyContext, User | Repo>({ ... });
await engine.check({
user: currentUser,
object: targetRepo,
relation: "owner",
context: { db, userId: "user-1" }, // required on every call
});
If resolvers need no context, use object and pass {}.
@graplix/codegen generates a fully-typed buildEngine wrapper from your schema:
npx @graplix/codegen ./schema.graplix
The generated file provides typed GraplixResolvers<TContext> and GraplixEntityInput so TypeScript enforces exhaustiveness:
import { buildEngine } from "./schema.generated";
const engine = await buildEngine({
resolvers: { ... }, // typed per schema + mappers
resolveType: (value) => { ... },
});
EntityRef directly to check/explain. query.user and query.object accept TEntityInput (your domain types) only.EntityRef as a type only when working with CheckEdge.from/to in explain results.null) — not IDs, not EntityRef instances.resolveType (or uses schema hints) to determine the returned entity's type.resolver.load() is never called inside toEntityRef — it is only called when an entity needs to be loaded by ID.resolveType PrecedenceresolveType(value) is always tried first.null, schema type hints (from relation definitions) are used as fallback.query.user and query.object, resolveType must return the correct type — no fallback.maxCacheSize (default: 500).// Runtime engine
import { buildEngine } from "@graplix/engine";
import type {
BuildEngineOptions,
GraplixEngine,
Query,
Resolver,
Resolvers,
ResolverInfo,
ResolveType,
CheckEdge,
CheckExplainResult,
EntityRef, // for CheckEdge.from/to type annotations
} from "@graplix/engine";
// Schema parsing (lower-level)
import { parse } from "@graplix/language";
// Codegen (CLI or programmatic)
import { generateTypeScript } from "@graplix/codegen";
Type errors often signal outdated knowledge. Common indicators: "Property X does not exist," module not found, incorrect generic parameters.
Response approach:
references/common-errors.mdnode_modules/@graplix/engine/dist/docs/)Source: daangn/graplix — distributed by TomeVault.