From 2e50f0604f24a74c2b167d1412ec525dc2d5b084 Mon Sep 17 00:00:00 2001 From: Maria Ines Parnisari Date: Fri, 13 Mar 2026 15:55:17 -0700 Subject: [PATCH] feat: support composable schemas --- build-panels.sh | 2 +- package.json | 10 +++++ src/binary.ts | 15 ++++++- src/extension.ts | 75 +-------------------------------- syntaxes/spicedb.tmGrammar.json | 26 ++++++++++++ syntaxes/test/full-standard.zed | 14 ++++-- syntaxes/test/imported.zed | 13 ++++++ 7 files changed, 75 insertions(+), 80 deletions(-) create mode 100644 syntaxes/test/imported.zed diff --git a/build-panels.sh b/build-panels.sh index ef8f8db..9cd879f 100755 --- a/build-panels.sh +++ b/build-panels.sh @@ -1,7 +1,7 @@ set -e WASM_FILE="src/check-watch-panel/public/main.wasm" -WASM_URL="https://github.com/authzed/spicedb/releases/download/v1.49.2/development.wasm" +WASM_URL="https://github.com/authzed/spicedb/releases/download/v1.51.0/development.wasm" WASM_EXEC_FILE="src/check-watch-panel/public/wasm_exec.js" WASM_EXEC_URL="https://raw.githubusercontent.com/golang/go/c61e5e72447b568dd25367f592962c7ebf28b1c7/lib/wasm/wasm_exec.js" diff --git a/package.json b/package.json index 6d5d4d2..3d87caf 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,16 @@ "icon": "$(watch-expressions-add)" } ], + "configuration": { + "title": "SpiceDB", + "properties": { + "spicedb.binaryPath": { + "type": "string", + "default": "/Users/user/Documents/GitHub/spicedb/dist/main", + "description": "Absolute path to a custom SpiceDB binary. If empty, the extension will look for 'spicedb' on your PATH." + } + } + }, "menus": { "view/title": [ { diff --git a/src/binary.ts b/src/binary.ts index 75eb059..72a88a3 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -1,8 +1,19 @@ import * as vscode from 'vscode'; import commandExists from 'command-exists'; +import * as fs from 'fs'; export async function languageServerBinaryPath(_context: vscode.ExtensionContext): Promise { + const config = vscode.workspace.getConfiguration('spicedb'); + const customPath = config.get('binaryPath'); + if (customPath) { + if (fs.existsSync(customPath)) { + vscode.window.showInformationMessage(`Using custom SpiceDB binary found at configured path: ${customPath}`); + return customPath; + } + vscode.window.showInformationMessage(`Custom SpiceDB binary specified but not found: ${customPath}`); + } + try { return await commandExists('spicedb'); } catch (_e) { @@ -13,7 +24,7 @@ export async function languageServerBinaryPath(_context: vscode.ExtensionContext const INSTALL_COMMANDS = { darwin: 'brew install spicedb', linux: '', - win32: '', + win32: 'choco install spicedb', aix: '', android: '', freebsd: '', @@ -26,5 +37,5 @@ const INSTALL_COMMANDS = { export function getInstallCommand() { const platform = process.platform; - return INSTALL_COMMANDS[platform] || 'https://authzed.com/docs/spicedb/getting-started/installing-spicedb'; + return INSTALL_COMMANDS[platform] || 'https://authzed.com/docs/spicedb/getting-started/install/macos'; } diff --git a/src/extension.ts b/src/extension.ts index 58105e1..8cfac2b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node'; -import { type ResolvedReference, Resolver, findReferenceNode, parse } from '@authzed/spicedb-parser-js'; +import { type ResolvedReference, Resolver, parse } from '@authzed/spicedb-parser-js'; import { getInstallCommand, languageServerBinaryPath } from './binary'; import { CheckWatchProvider } from './checkwatchprovider'; @@ -43,79 +43,6 @@ export function activate(context: vscode.ExtensionContext) { }), ); - // TODO: Move this into the language server. - vscode.languages.registerDefinitionProvider('spicedb', { - provideDefinition: function ( - document: vscode.TextDocument, - position: vscode.Position, - _token: vscode.CancellationToken, - ): vscode.ProviderResult { - const text = document.getText(); - const parserResult = parse(text); - if (parserResult.error) { - return; - } - - // NOTE: the indexes from VSCode are 0-based, but the parser is 1-based. - const found = findReferenceNode(parserResult.schema!, position.line + 1, position.character + 1); - if (!found) { - return; - } - - const resolution = new Resolver(parserResult.schema!); - switch (found.node?.kind) { - case 'typeref': { - const def = resolution.lookupDefinition(found.node.path); - if (def) { - if (found.node.relationName) { - const relation = def.lookupRelationOrPermission(found.node.relationName); - if (relation) { - return { - uri: document.uri, - range: new vscode.Range( - relation.range.startIndex.line - 1, - relation.range.startIndex.column - 1, - relation.range.startIndex.line - 1, - relation.range.startIndex.column - 1, - ), - }; - } - } else { - return { - uri: document.uri, - range: new vscode.Range( - def.definition.range.startIndex.line - 1, - def.definition.range.startIndex.column - 1, - def.definition.range.startIndex.line - 1, - def.definition.range.startIndex.column - 1, - ), - }; - } - } - break; - } - - case 'relationref': { - const relation = resolution.resolveRelationOrPermission(found.node, found.def); - if (relation) { - return { - uri: document.uri, - range: new vscode.Range( - relation.range.startIndex.line - 1, - relation.range.startIndex.column - 1, - relation.range.startIndex.line - 1, - relation.range.startIndex.column - 1, - ), - }; - } - break; - } - } - - return undefined; - }, - }); - // TODO: Move this into the language server. vscode.languages.registerDocumentSemanticTokensProvider( 'spicedb', diff --git a/syntaxes/spicedb.tmGrammar.json b/syntaxes/spicedb.tmGrammar.json index e61991b..5607938 100644 --- a/syntaxes/spicedb.tmGrammar.json +++ b/syntaxes/spicedb.tmGrammar.json @@ -7,6 +7,8 @@ "patterns": [ { "include": "#comment" }, { "include": "#use" }, + { "include": "#import" }, + { "include": "#partial" }, { "include": "#definition" }, { "include": "#caveat" }, { "include": "#relation" }, @@ -288,6 +290,30 @@ } } }, + "import": { + "comment": "import", + "match": "\\s*(import)\\s*([\"a-zA-Z_]\\w*)\\s*", + "captures": { + "1": { + "name": "keyword.class.definition" + }, + "2": { + "name": "entity.name.function" + } + } + }, + "partial": { + "comment": "partial", + "match": "\\s*(partial)\\s*([\"a-zA-Z_]\\w*)\\s*", + "captures": { + "1": { + "name": "keyword.class.definition" + }, + "2": { + "name": "entity.name.function" + } + } + }, "with_caveat": { "comment": "with_caveat", "match": "\\s*(with)\\s*(([a-z][\\w]{1,62}[a-z0-9]\\/)*[a-z][\\w]{1,62}[a-z0-9])\\s*", diff --git a/syntaxes/test/full-standard.zed b/syntaxes/test/full-standard.zed index ca5b32a..f15d217 100644 --- a/syntaxes/test/full-standard.zed +++ b/syntaxes/test/full-standard.zed @@ -1,11 +1,15 @@ // this is a standard schema that should have every available feature (wildcards, caveats, etc) use expiration +use partial +use import -definition user {} +import "imported.zed" definition group { - relation member: user with non_expired_grant | user with expiration + relation member: user with is_raining | user with expiration relation member2: user:* with non_expired_grant + ...secret + permission supersecret = secretview // defined in the partial } definition document { @@ -15,7 +19,7 @@ definition document { relation bbb: user:* relation ccc: user:* relation ddd: user:* - + ...view_secret // a comment permission view = aaa @@ -34,3 +38,7 @@ definition document { caveat non_expired_grant(name string, description string) { name.matches("-$test") || size(description) >= 100 } + +partial view_secret { + relation secret: user +} diff --git a/syntaxes/test/imported.zed b/syntaxes/test/imported.zed new file mode 100644 index 0000000..5c63ddb --- /dev/null +++ b/syntaxes/test/imported.zed @@ -0,0 +1,13 @@ +use partial + +definition mycustomtype {} + +definition user {} + +partial secret { + relation secretview: user | mycustomtype +} + +caveat is_raining(day string) { + day == "tues" || day == "mon" +} \ No newline at end of file