From 09f1f2d921fecd37587037363ee10ca3bcfa5510 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Wed, 13 May 2026 13:25:57 -0400 Subject: [PATCH 1/6] Rename lsp namespace to langservice; split cursor functions into their own namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'lsp:' XQuery namespace and '/api/lsp/*' URL prefix were inherited from the migrated exist-lsp package and implied a protocol claim that isn't true — exist-api is an HTTP/JSON backend with LSP-inspired data shapes, not an LSP server. See #7 for the architecture discussion that drove this rename. This commit also splits the server-side cursor functions (eval / fetch / close) out of the language-services namespace, since they are query-execution primitives, not language services. Naming changes: - URL prefix: /api/lsp/* -> /api/langservice/* - XQuery: 'lsp:' -> 'lang:' (language services) and new 'cursor:' - XQuery URIs: http://exist-db.org/xquery/{langservice,cursor} - Java packages: org.exist.xquery.modules.api.lsp.* -> org.exist.xquery.modules.openapi.{langservice,cursor}.* - New module classes: LangServiceModule, CursorModule (replacing the combined LspModule) Other changes: - ApiModule deleted; its cursor-store configuration role moves to CursorModule (which is what actually uses the store) - New endpoint: GET /api/langservice/capabilities returns the set of language-service features the running instance supports — replaces existdb-langserver's current probe hack (closes #17) - MIGRATION.md documents the rename for downstream consumers; removed once eXide / monex / existdb-langserver / notebook are updated No backward-compatibility aliases. All four known consumers are pre-release and will be updated in lockstep. REST request/response shapes are unchanged in this PR; shape tightening to LSP 3.17 isomorphism is tracked separately in #16. Closes #7 Closes #15 Closes #17 Co-Authored-By: Claude Opus 4.7 (1M context) --- MIGRATION.md | 71 ++++++++++++++ modules/api.json | 92 ++++++++++++------- modules/api.xq | 2 +- modules/{lsp.xqm => langservice.xqm} | 73 +++++++++------ modules/query.xqm | 16 ++-- pom.xml | 12 +-- .../{api/lsp => openapi/cursor}/Close.java | 6 +- .../cursor/CursorModule.java} | 41 ++++----- .../lsp => openapi/cursor}/CursorStore.java | 4 +- .../{api/lsp => openapi/cursor}/Eval.java | 10 +- .../{api/lsp => openapi/cursor}/Fetch.java | 12 +-- .../langservice}/Completions.java | 4 +- .../langservice}/Definition.java | 4 +- .../langservice}/Diagnostics.java | 4 +- .../lsp => openapi/langservice}/Hover.java | 4 +- .../langservice/LangServiceModule.java} | 29 +++--- .../langservice}/References.java | 4 +- .../lsp => openapi/langservice}/Symbols.java | 4 +- src/test/resources/conf.xml | 8 +- xar-assembly.xml | 8 +- 20 files changed, 258 insertions(+), 150 deletions(-) create mode 100644 MIGRATION.md rename modules/{lsp.xqm => langservice.xqm} (51%) rename src/main/java/org/exist/xquery/modules/{api/lsp => openapi/cursor}/Close.java (89%) rename src/main/java/org/exist/xquery/modules/{api/ApiModule.java => openapi/cursor/CursorModule.java} (67%) rename src/main/java/org/exist/xquery/modules/{api/lsp => openapi/cursor}/CursorStore.java (97%) rename src/main/java/org/exist/xquery/modules/{api/lsp => openapi/cursor}/Eval.java (94%) rename src/main/java/org/exist/xquery/modules/{api/lsp => openapi/cursor}/Fetch.java (97%) rename src/main/java/org/exist/xquery/modules/{api/lsp => openapi/langservice}/Completions.java (99%) rename src/main/java/org/exist/xquery/modules/{api/lsp => openapi/langservice}/Definition.java (98%) rename src/main/java/org/exist/xquery/modules/{api/lsp => openapi/langservice}/Diagnostics.java (98%) rename src/main/java/org/exist/xquery/modules/{api/lsp => openapi/langservice}/Hover.java (98%) rename src/main/java/org/exist/xquery/modules/{api/lsp/LspModule.java => openapi/langservice/LangServiceModule.java} (58%) rename src/main/java/org/exist/xquery/modules/{api/lsp => openapi/langservice}/References.java (99%) rename src/main/java/org/exist/xquery/modules/{api/lsp => openapi/langservice}/Symbols.java (98%) diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..9443385 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,71 @@ +# Migration: `lsp` → `langservice` + `cursor` (pre-1.0.0) + +This document describes the breaking rename landed in PR for issue #15. It applies to consumers that integrated against pre-release versions of exist-api (formerly the `lsp:` namespace inherited from `exist-lsp`). + +This file will be removed once all known consumers (eXide, monex, existdb-langserver, notebook) have been updated. If you reach this section after that point, the names below are settled. + +## Why + +The previous `lsp:` namespace and `/api/lsp/*` URL prefix were inherited from the migrated `exist-lsp` standalone package. They implied that exist-api speaks the Language Server Protocol — it does not. exist-api is an HTTP/JSON backend whose data shapes are *inspired by* LSP types but consumed via REST, not JSON-RPC. See issue #7 for the full discussion. + +The rename also splits server-side cursor functions (`eval` / `fetch` / `close`) out of the language-services namespace, since they are query-execution primitives, not language services. + +## What changed + +### URL paths + +| Before | After | +|---|---| +| `POST /api/lsp/diagnostics` | `POST /api/langservice/diagnostics` | +| `POST /api/lsp/completions` | `POST /api/langservice/completions` | +| `POST /api/lsp/hover` | `POST /api/langservice/hover` | +| `POST /api/lsp/definition` | `POST /api/langservice/definition` | +| `POST /api/lsp/references` | `POST /api/langservice/references` | +| `POST /api/lsp/symbols` | `POST /api/langservice/symbols` | +| — | `GET /api/langservice/capabilities` *(new)* | + +`POST /api/eval`, `GET /api/query/{id}/results`, `DELETE /api/query/{id}` are unchanged externally. Internally they now delegate to `cursor:eval` / `cursor:fetch` / `cursor:close` instead of `lsp:eval` / `lsp:fetch` / `lsp:close`. + +### XQuery namespaces + +| Before | After | +|---|---| +| `http://exist-db.org/xquery/lsp` (prefix `lsp`) | `http://exist-db.org/xquery/langservice` (prefix `lang`) for language-service functions | +| — | `http://exist-db.org/xquery/cursor` (prefix `cursor`) *(new)* for cursor functions | +| `http://exist-db.org/xquery/api` (prefix `api`, empty module) | removed | + +Function renames: + +| Before | After | +|---|---| +| `lsp:diagnostics()` | `lang:diagnostics()` | +| `lsp:completions()` | `lang:completions()` | +| `lsp:hover()` | `lang:hover()` | +| `lsp:definition()` | `lang:definition()` | +| `lsp:references()` | `lang:references()` | +| `lsp:symbols()` | `lang:symbols()` | +| `lsp:eval()` | `cursor:eval()` | +| `lsp:fetch()` | `cursor:fetch()` | +| `lsp:close()` | `cursor:close()` | + +### Java packages + +| Before | After | +|---|---| +| `org.exist.xquery.modules.api.ApiModule` | removed | +| `org.exist.xquery.modules.api.lsp.LspModule` | `org.exist.xquery.modules.openapi.langservice.LangServiceModule` | +| `org.exist.xquery.modules.api.lsp.{Completions,Definition,Diagnostics,Hover,References,Symbols}` | `org.exist.xquery.modules.openapi.langservice.{...}` | +| `org.exist.xquery.modules.api.lsp.{Eval,Fetch,Close,CursorStore}` | `org.exist.xquery.modules.openapi.cursor.{...}` | +| — | `org.exist.xquery.modules.openapi.cursor.CursorModule` *(new)* | + +`CursorModule` takes over the cursor store configuration role that previously lived in the (now-removed) `ApiModule`. The configuration parameter names (`cursor.maximumSize`, `cursor.expireAfterAccess`, `cursor.maximumWeight`) are unchanged. + +### No backward-compatibility aliases + +None of the previous names are kept as aliases. All four known consumers — eXide, existdb-langserver, monex, notebook — are pre-release and updated in lockstep with this change. + +## What did NOT change + +- The `/api/*` URL prefix and the OpenAPI surface as a whole. +- HTTP request/response *shapes* for the language-service endpoints. Those will be tightened to LSP-isomorphic shapes in a follow-up PR (issue #16) — separate from this rename. +- The `repoxml` package name / abbrev (still `exist-api` until the repo rename in #13 lands). diff --git a/modules/api.json b/modules/api.json index 96a9e50..9f91895 100644 --- a/modules/api.json +++ b/modules/api.json @@ -25,8 +25,8 @@ "description": "XQuery execution with cursor-based result pagination" }, { - "name": "LSP", - "description": "Language Server Protocol services for XQuery (diagnostics, completions, hover, etc.)" + "name": "Language Service", + "description": "Language services for XQuery (diagnostics, completions, hover, etc.). Data shapes are inspired by the Language Server Protocol but consumed over HTTP/JSON." }, { "name": "Database", @@ -314,7 +314,7 @@ } }, "400": { - "description": "Bad request \u2014 missing or invalid source parameter", + "description": "Bad request — missing or invalid source parameter", "content": { "application/json": { "schema": { @@ -420,7 +420,7 @@ } }, "400": { - "description": "Bad request \u2014 invalid or missing query", + "description": "Bad request — invalid or missing query", "content": { "application/json": { "schema": { @@ -596,7 +596,7 @@ "post": { "summary": "Cancel running query", "operationId": "query:cancel", - "description": "Attempts to cancel a currently running query. Not yet implemented \u2014 returns an error message.", + "description": "Attempts to cancel a currently running query. Not yet implemented — returns an error message.", "tags": [ "Query" ], @@ -633,13 +633,13 @@ } } }, - "/api/lsp/diagnostics": { + "/api/langservice/diagnostics": { "post": { "summary": "Compile check", - "operationId": "lspapi:diagnostics", + "operationId": "langservice:diagnostics", "description": "Compiles an XQuery expression and returns any syntax or static errors as diagnostics. Used by IDE integrations for real-time error checking.", "tags": [ - "LSP" + "Language Service" ], "requestBody": { "required": true, @@ -712,13 +712,13 @@ } } }, - "/api/lsp/completions": { + "/api/langservice/completions": { "post": { "summary": "Code completions", - "operationId": "lspapi:completions", + "operationId": "langservice:completions", "description": "Returns completion suggestions for an XQuery expression at the current cursor position. Includes functions, variables, and namespace prefixes.", "tags": [ - "LSP" + "Language Service" ], "requestBody": { "required": true, @@ -791,13 +791,13 @@ } } }, - "/api/lsp/hover": { + "/api/langservice/hover": { "post": { "summary": "Hover info", - "operationId": "lspapi:hover", + "operationId": "langservice:hover", "description": "Returns hover information (signature and documentation) for the symbol at the given line and column in the XQuery expression.", "tags": [ - "LSP" + "Language Service" ], "requestBody": { "required": true, @@ -860,13 +860,13 @@ } } }, - "/api/lsp/definition": { + "/api/langservice/definition": { "post": { "summary": "Go to definition", - "operationId": "lspapi:definition", + "operationId": "langservice:definition", "description": "Resolves the definition location for the symbol at the given line and column. Returns the file URI, line, and column of the definition.", "tags": [ - "LSP" + "Language Service" ], "requestBody": { "required": true, @@ -944,13 +944,13 @@ } } }, - "/api/lsp/references": { + "/api/langservice/references": { "post": { "summary": "Find all references", - "operationId": "lspapi:references", + "operationId": "langservice:references", "description": "Finds all references to the symbol at the given line and column within the current expression.", "tags": [ - "LSP" + "Language Service" ], "requestBody": { "required": true, @@ -1028,13 +1028,13 @@ } } }, - "/api/lsp/symbols": { + "/api/langservice/symbols": { "post": { "summary": "Document symbols", - "operationId": "lspapi:symbols", + "operationId": "langservice:symbols", "description": "Returns all symbols (functions, variables) defined in the given XQuery expression. Used for document outline views in IDEs.", "tags": [ - "LSP" + "Language Service" ], "requestBody": { "required": true, @@ -1408,7 +1408,7 @@ } }, "400": { - "description": "Bad request \u2014 missing path or content", + "description": "Bad request — missing path or content", "content": { "application/json": { "schema": { @@ -1528,7 +1528,7 @@ } }, "400": { - "description": "Bad request \u2014 missing or invalid path", + "description": "Bad request — missing or invalid path", "content": { "application/json": { "schema": { @@ -1677,7 +1677,7 @@ } }, "400": { - "description": "Bad request \u2014 missing source or target", + "description": "Bad request — missing source or target", "content": { "application/json": { "schema": { @@ -1757,7 +1757,7 @@ } }, "400": { - "description": "Bad request \u2014 missing source or target", + "description": "Bad request — missing source or target", "content": { "application/json": { "schema": { @@ -1898,7 +1898,7 @@ "schema": { "type": "string" }, - "description": "ISO datetime \u2014 only return resources modified after this time" + "description": "ISO datetime — only return resources modified after this time" } ], "responses": { @@ -2033,7 +2033,7 @@ } }, "400": { - "description": "Bad request \u2014 missing path or invalid parameters", + "description": "Bad request — missing path or invalid parameters", "content": { "application/json": { "schema": { @@ -2321,7 +2321,7 @@ } }, "400": { - "description": "Bad request \u2014 missing required fields or user already exists", + "description": "Bad request — missing required fields or user already exists", "content": { "application/json": { "schema": { @@ -2495,7 +2495,7 @@ } }, "400": { - "description": "Bad request \u2014 invalid parameters", + "description": "Bad request — invalid parameters", "content": { "application/json": { "schema": { @@ -2694,7 +2694,7 @@ } }, "400": { - "description": "Bad request \u2014 missing name or group already exists", + "description": "Bad request — missing name or group already exists", "content": { "application/json": { "schema": { @@ -3115,7 +3115,7 @@ } }, "400": { - "description": "Bad request \u2014 missing package name/URL or invalid .xar file", + "description": "Bad request — missing package name/URL or invalid .xar file", "content": { "application/json": { "schema": { @@ -3327,7 +3327,7 @@ } }, "409": { - "description": "Other packages depend on this one \u2014 use force=true to override", + "description": "Other packages depend on this one — use force=true to override", "content": { "application/json": { "schema": { @@ -3728,6 +3728,28 @@ } } } + }, + "/api/langservice/capabilities": { + "get": { + "summary": "Capability discovery", + "description": "Returns the set of language-service features the running exist-api instance supports.", + "operationId": "langservice:capabilities", + "tags": [ + "Language Service" + ], + "responses": { + "200": { + "description": "Capability map", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } } } -} \ No newline at end of file +} diff --git a/modules/api.xq b/modules/api.xq index 29ea21b..cc0e07e 100644 --- a/modules/api.xq +++ b/modules/api.xq @@ -21,7 +21,7 @@ import module namespace rutil="http://e-editiones.org/roaster/util"; (: Import API modules — each handles a group of endpoints :) import module namespace system-api="http://exist-db.org/api/system" at "system.xqm"; import module namespace query="http://exist-db.org/api/query" at "query.xqm"; -import module namespace lspapi="http://exist-db.org/api/lsp" at "lsp.xqm"; +import module namespace langservice="http://exist-db.org/api/langservice" at "langservice.xqm"; import module namespace db="http://exist-db.org/api/db" at "db.xqm"; import module namespace users="http://exist-db.org/api/users" at "users.xqm"; import module namespace packages="http://exist-db.org/api/packages" at "packages.xqm"; diff --git a/modules/lsp.xqm b/modules/langservice.xqm similarity index 51% rename from modules/lsp.xqm rename to modules/langservice.xqm index 359c897..0d7752e 100644 --- a/modules/lsp.xqm +++ b/modules/langservice.xqm @@ -6,11 +6,11 @@ xquery version "3.1"; (:~ : Language services endpoints. - : Wraps LSP Java functions as REST endpoints. + : Wraps lang:* Java functions as REST endpoints at /api/langservice/*. :) -module namespace lspapi="http://exist-db.org/api/lsp"; +module namespace langservice="http://exist-db.org/api/langservice"; -import module namespace lsp="http://exist-db.org/xquery/lsp"; +import module namespace lang="http://exist-db.org/xquery/langservice"; declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization"; @@ -19,9 +19,9 @@ declare option output:media-type "application/json"; (:~ : Compile check — returns diagnostics. - : POST /api/lsp/diagnostics + : POST /api/langservice/diagnostics :) -declare function lspapi:diagnostics($request as map(*)) { +declare function langservice:diagnostics($request as map(*)) { let $body := $request?body let $expression := $body?expression let $module-load-path := $body?module-load-path @@ -29,15 +29,15 @@ declare function lspapi:diagnostics($request as map(*)) { if (empty($expression)) then map { "error": "Missing required field: expression" } else if ($module-load-path) - then lsp:diagnostics($expression, $module-load-path) - else lsp:diagnostics($expression) + then lang:diagnostics($expression, $module-load-path) + else lang:diagnostics($expression) }; (:~ : Code completions. - : POST /api/lsp/completions + : POST /api/langservice/completions :) -declare function lspapi:completions($request as map(*)) { +declare function langservice:completions($request as map(*)) { let $body := $request?body let $expression := $body?expression let $module-load-path := $body?module-load-path @@ -45,15 +45,15 @@ declare function lspapi:completions($request as map(*)) { if (empty($expression)) then map { "error": "Missing required field: expression" } else if ($module-load-path) - then lsp:completions($expression, $module-load-path) - else lsp:completions($expression) + then lang:completions($expression, $module-load-path) + else lang:completions($expression) }; (:~ : Hover info. - : POST /api/lsp/hover + : POST /api/langservice/hover :) -declare function lspapi:hover($request as map(*)) { +declare function langservice:hover($request as map(*)) { let $body := $request?body let $expression := $body?expression let $line := $body?line @@ -63,15 +63,15 @@ declare function lspapi:hover($request as map(*)) { if (empty($expression) or empty($line) or empty($column)) then map { "error": "Missing required fields: expression, line, column" } else if ($module-load-path) - then lsp:hover($expression, $line, $column, $module-load-path) - else lsp:hover($expression, $line, $column) + then lang:hover($expression, $line, $column, $module-load-path) + else lang:hover($expression, $line, $column) }; (:~ : Go to definition. - : POST /api/lsp/definition + : POST /api/langservice/definition :) -declare function lspapi:definition($request as map(*)) { +declare function langservice:definition($request as map(*)) { let $body := $request?body let $expression := $body?expression let $line := $body?line @@ -81,15 +81,15 @@ declare function lspapi:definition($request as map(*)) { if (empty($expression) or empty($line) or empty($column)) then map { "error": "Missing required fields: expression, line, column" } else if ($module-load-path) - then lsp:definition($expression, $line, $column, $module-load-path) - else lsp:definition($expression, $line, $column) + then lang:definition($expression, $line, $column, $module-load-path) + else lang:definition($expression, $line, $column) }; (:~ : Find all references. - : POST /api/lsp/references + : POST /api/langservice/references :) -declare function lspapi:references($request as map(*)) { +declare function langservice:references($request as map(*)) { let $body := $request?body let $expression := $body?expression let $line := $body?line @@ -99,15 +99,15 @@ declare function lspapi:references($request as map(*)) { if (empty($expression) or empty($line) or empty($column)) then map { "error": "Missing required fields: expression, line, column" } else if ($module-load-path) - then lsp:references($expression, $line, $column, $module-load-path) - else lsp:references($expression, $line, $column) + then lang:references($expression, $line, $column, $module-load-path) + else lang:references($expression, $line, $column) }; (:~ : Document symbols. - : POST /api/lsp/symbols + : POST /api/langservice/symbols :) -declare function lspapi:symbols($request as map(*)) { +declare function langservice:symbols($request as map(*)) { let $body := $request?body let $expression := $body?expression let $module-load-path := $body?module-load-path @@ -115,6 +115,25 @@ declare function lspapi:symbols($request as map(*)) { if (empty($expression)) then map { "error": "Missing required field: expression" } else if ($module-load-path) - then lsp:symbols($expression, $module-load-path) - else lsp:symbols($expression) + then lang:symbols($expression, $module-load-path) + else lang:symbols($expression) +}; + +(:~ + : Capability discovery — returns the set of language-service features + : the running exist-api instance supports. Modeled loosely on LSP's + : ServerCapabilities, but for the REST surface. + : GET /api/langservice/capabilities + :) +declare function langservice:capabilities($request as map(*)) { + map { + "diagnostics": map { "available": true, "provider": "exist-xquery-parser" }, + "completions": map { "available": true, "positional": false }, + "hover": map { "available": true, "markupKinds": [ "plaintext" ] }, + "definition": map { "available": true, "multiTarget": false }, + "references": map { "available": true, "includeDeclaration": false }, + "symbols": map { "available": true, "hierarchical": false }, + "cursor": map { "available": true }, + "version": "0.9.0-SNAPSHOT" + } }; diff --git a/modules/query.xqm b/modules/query.xqm index fc1f6fb..2df6eb9 100644 --- a/modules/query.xqm +++ b/modules/query.xqm @@ -6,11 +6,11 @@ xquery version "3.1"; (:~ : Query execution endpoints. - : Wraps lsp:eval(), lsp:fetch(), lsp:close() Java functions as REST endpoints. + : Wraps cursor:eval(), cursor:fetch(), cursor:close() Java functions as REST endpoints. :) module namespace query="http://exist-db.org/api/query"; -import module namespace lsp="http://exist-db.org/xquery/lsp"; +import module namespace cursor="http://exist-db.org/xquery/cursor"; declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization"; @@ -24,7 +24,7 @@ declare option output:media-type "application/json"; : defaults (adaptive method, indented, XML declaration omitted). : : @param $params map of parameter names to values (strings or booleans) - : @return serialization parameters map suitable for serialize() or lsp:fetch() + : @return serialization parameters map suitable for serialize() or cursor:fetch() :) declare %private function query:serialization-params($params as map(*)) as map(*) { (: serialize() requires xs:boolean for boolean parameters, not "yes"/"no" strings :) @@ -61,8 +61,8 @@ declare function query:execute($request as map(*)) { else let $result := if ($module-load-path) - then lsp:eval($expression, $module-load-path) - else lsp:eval($expression) + then cursor:eval($expression, $module-load-path) + else cursor:eval($expression) return $result }; @@ -77,13 +77,13 @@ declare function query:fetch($request as map(*)) { let $cursor := $request?parameters?id let $start := ($request?parameters?start, 1)[1] cast as xs:integer let $count := ($request?parameters?count, 10)[1] cast as xs:integer - (: lsp:fetch() expects string values for boolean params — keep as-is :) + (: cursor:fetch() expects string values for boolean params — keep as-is :) let $ser := map { "method": ($request?parameters?method, "adaptive")[1], "indent": ($request?parameters?indent, "yes")[1] } return - lsp:fetch($cursor, $start, $count, $ser) + cursor:fetch($cursor, $start, $count, $ser) }; (:~ @@ -95,7 +95,7 @@ declare function query:fetch($request as map(*)) { :) declare function query:close($request as map(*)) { let $cursor := $request?parameters?id - let $closed := lsp:close($cursor) + let $closed := cursor:close($cursor) return map { "closed": $closed } }; diff --git a/pom.xml b/pom.xml index 37e780e..4b5a644 100644 --- a/pom.xml +++ b/pom.xml @@ -53,13 +53,13 @@ http://exist-db.org/pkg/api exist-api - - http://exist-db.org/xquery/api - org.exist.xquery.modules.api.ApiModule + + http://exist-db.org/xquery/langservice + org.exist.xquery.modules.openapi.langservice.LangServiceModule - - http://exist-db.org/xquery/lsp - org.exist.xquery.modules.api.lsp.LspModule + + http://exist-db.org/xquery/cursor + org.exist.xquery.modules.openapi.cursor.CursorModule diff --git a/src/main/java/org/exist/xquery/modules/api/lsp/Close.java b/src/main/java/org/exist/xquery/modules/openapi/cursor/Close.java similarity index 89% rename from src/main/java/org/exist/xquery/modules/api/lsp/Close.java rename to src/main/java/org/exist/xquery/modules/openapi/cursor/Close.java index 13d16b9..e0598a3 100644 --- a/src/main/java/org/exist/xquery/modules/api/lsp/Close.java +++ b/src/main/java/org/exist/xquery/modules/openapi/cursor/Close.java @@ -2,7 +2,7 @@ * SPDX LGPL-2.1-or-later * Copyright (C) 2026 The eXist-db Authors */ -package org.exist.xquery.modules.api.lsp; +package org.exist.xquery.modules.openapi.cursor; import org.exist.xquery.BasicFunction; import org.exist.xquery.FunctionSignature; @@ -24,11 +24,11 @@ public class Close extends BasicFunction { private static final String FS_CLOSE_NAME = "close"; private static final String FS_CLOSE_DESCRIPTION = """ - Closes a server-side cursor created by lsp:eval(), releasing the held result sequence. \ + Closes a server-side cursor created by cursor:eval(), releasing the held result sequence. \ Returns true if the cursor was found and removed, false if it had already expired."""; public static final FunctionSignature[] FS_CLOSE = functionSignatures( - LspModule.qname(FS_CLOSE_NAME), + CursorModule.qname(FS_CLOSE_NAME), FS_CLOSE_DESCRIPTION, returns(Type.BOOLEAN, "true if cursor was closed, false if not found"), arities( diff --git a/src/main/java/org/exist/xquery/modules/api/ApiModule.java b/src/main/java/org/exist/xquery/modules/openapi/cursor/CursorModule.java similarity index 67% rename from src/main/java/org/exist/xquery/modules/api/ApiModule.java rename to src/main/java/org/exist/xquery/modules/openapi/cursor/CursorModule.java index 6c556d9..efbe9f8 100644 --- a/src/main/java/org/exist/xquery/modules/api/ApiModule.java +++ b/src/main/java/org/exist/xquery/modules/openapi/cursor/CursorModule.java @@ -2,14 +2,13 @@ * SPDX LGPL-2.1-or-later * Copyright (C) 2026 The eXist-db Authors */ -package org.exist.xquery.modules.api; +package org.exist.xquery.modules.openapi.cursor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.exist.dom.QName; import org.exist.xquery.AbstractInternalModule; import org.exist.xquery.FunctionDef; -import org.exist.xquery.modules.api.lsp.CursorStore; import java.util.List; import java.util.Map; @@ -17,41 +16,41 @@ import static org.exist.xquery.FunctionDSL.functionDefs; /** - * XQuery function module providing the unified eXist-db Platform API. + * XQuery function module exposing server-side cursors for paginated query execution. * - *

Registers functions under {@code http://exist-db.org/xquery/api} (prefix: {@code api}). - * Currently configures the shared cursor store; new API-namespaced functions will be - * added in future versions.

- * - *

LSP functions remain available under the original {@code http://exist-db.org/xquery/lsp} - * namespace via the companion {@link org.exist.xquery.modules.api.lsp.LspModule}.

+ *

Registers functions under {@code http://exist-db.org/xquery/cursor} + * (prefix: {@code cursor}). Provides {@code cursor:eval()} to evaluate an + * expression into a server-held cursor, {@code cursor:fetch()} to retrieve + * pages, and {@code cursor:close()} to release.

* *

Cursor store configuration

- *

The cursor store used by {@code api:eval}/{@code api:fetch} can be configured - * via module parameters in {@code exist.xml}:

+ *

The cursor store used by these functions can be configured via module + * parameters in {@code exist.xml}:

*
    *
  • {@code cursor.maximumSize} — max concurrent cursors (default: 100, LRU eviction)
  • *
  • {@code cursor.expireAfterAccess} — inactivity timeout in ms (default: 300000 = 5 min)
  • *
  • {@code cursor.maximumWeight} — max total estimated memory in bytes (default: 0 = unlimited)
  • *
*/ -public class ApiModule extends AbstractInternalModule { +public class CursorModule extends AbstractInternalModule { - private static final Logger logger = LogManager.getLogger(ApiModule.class); + private static final Logger logger = LogManager.getLogger(CursorModule.class); - public static final String NAMESPACE_URI = "http://exist-db.org/xquery/api"; - public static final String PREFIX = "api"; + public static final String NAMESPACE_URI = "http://exist-db.org/xquery/cursor"; + public static final String PREFIX = "cursor"; public static final String RELEASE = "0.9.0-SNAPSHOT"; public static final String PARAM_CURSOR_MAXIMUM_SIZE = "cursor.maximumSize"; public static final String PARAM_CURSOR_EXPIRE_AFTER_ACCESS = "cursor.expireAfterAccess"; public static final String PARAM_CURSOR_MAXIMUM_WEIGHT = "cursor.maximumWeight"; - // No functions registered yet — LSP functions use the lsp namespace. - // Future platform API functions will be added here. - public static final FunctionDef[] functions = functionDefs(); + public static final FunctionDef[] functions = functionDefs( + functionDefs(Eval.class, Eval.FS_EVAL), + functionDefs(Fetch.class, Fetch.FS_FETCH), + functionDefs(Close.class, Close.FS_CLOSE) + ); - public ApiModule(final Map> parameters) { + public CursorModule(final Map> parameters) { super(functions, parameters, true); final long maxSize = getLongParam(parameters, PARAM_CURSOR_MAXIMUM_SIZE, 100); @@ -59,7 +58,7 @@ public ApiModule(final Map> parameters) { final long maxWeight = getLongParam(parameters, PARAM_CURSOR_MAXIMUM_WEIGHT, 0); CursorStore.configure(maxSize, expireMs, maxWeight); - logger.info("API cursor store: maximumSize={}, expireAfterAccess={}ms, maximumWeight={}", + logger.info("Cursor store: maximumSize={}, expireAfterAccess={}ms, maximumWeight={}", maxSize, expireMs, maxWeight > 0 ? maxWeight : "unlimited"); } @@ -83,7 +82,7 @@ private static long getLongParam(final Map> parameters, @Override public String getDescription() { - return "Unified platform API for eXist-db"; + return "Server-side cursors for paginated XQuery execution"; } @Override diff --git a/src/main/java/org/exist/xquery/modules/api/lsp/CursorStore.java b/src/main/java/org/exist/xquery/modules/openapi/cursor/CursorStore.java similarity index 97% rename from src/main/java/org/exist/xquery/modules/api/lsp/CursorStore.java rename to src/main/java/org/exist/xquery/modules/openapi/cursor/CursorStore.java index 0765b7c..a494c8e 100644 --- a/src/main/java/org/exist/xquery/modules/api/lsp/CursorStore.java +++ b/src/main/java/org/exist/xquery/modules/openapi/cursor/CursorStore.java @@ -2,7 +2,7 @@ * SPDX LGPL-2.1-or-later * Copyright (C) 2026 The eXist-db Authors */ -package org.exist.xquery.modules.api.lsp; +package org.exist.xquery.modules.openapi.cursor; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; @@ -63,7 +63,7 @@ public static CursorStore getInstance() { } /** - * (Re)configure the cursor store. Called from {@link LspModule} constructor + * (Re)configure the cursor store. Called from {@link CursorModule} constructor * when module parameters are available. * * @param maximumSize max concurrent cursors (0 = unlimited count) diff --git a/src/main/java/org/exist/xquery/modules/api/lsp/Eval.java b/src/main/java/org/exist/xquery/modules/openapi/cursor/Eval.java similarity index 94% rename from src/main/java/org/exist/xquery/modules/api/lsp/Eval.java rename to src/main/java/org/exist/xquery/modules/openapi/cursor/Eval.java index ecc5d7f..bf139b4 100644 --- a/src/main/java/org/exist/xquery/modules/api/lsp/Eval.java +++ b/src/main/java/org/exist/xquery/modules/openapi/cursor/Eval.java @@ -2,7 +2,7 @@ * SPDX LGPL-2.1-or-later * Copyright (C) 2026 The eXist-db Authors */ -package org.exist.xquery.modules.api.lsp; +package org.exist.xquery.modules.openapi.cursor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -29,7 +29,7 @@ * *

Returns a map with:

*
    - *
  • {@code cursor} — cursor ID for use with {@code lsp:fetch()} and {@code lsp:close()}
  • + *
  • {@code cursor} — cursor ID for use with {@code cursor:fetch()} and {@code cursor:close()}
  • *
  • {@code items} — total number of items in the result sequence
  • *
  • {@code elapsed} — execution time in milliseconds
  • *
@@ -45,12 +45,12 @@ public class Eval extends BasicFunction { private static final String FS_EVAL_NAME = "eval"; private static final String FS_EVAL_DESCRIPTION = """ Evaluates an XQuery expression and stores the result in a server-side cursor. \ - Returns a map with keys: cursor (xs:string, cursor ID for lsp:fetch/lsp:close), \ + Returns a map with keys: cursor (xs:string, cursor ID for cursor:fetch/cursor:close), \ items (xs:integer, total result count), and elapsed (xs:integer, execution time in ms). \ The cursor holds live node references and expires after 5 minutes of inactivity."""; public static final FunctionSignature[] FS_EVAL = functionSignatures( - LspModule.qname(FS_EVAL_NAME), + CursorModule.qname(FS_EVAL_NAME), FS_EVAL_DESCRIPTION, returns(Type.MAP_ITEM, "a map with cursor ID, item count, and elapsed time"), arities( @@ -113,7 +113,7 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro final String cursorId = UUID.randomUUID().toString(); CursorStore.getInstance().put(cursorId, result, itemCount, evalContext); - logger.debug("lsp:eval cursor={} items={} compile={}ms eval={}ms total={}ms", + logger.debug("cursor:eval cursor={} items={} compile={}ms eval={}ms total={}ms", cursorId, itemCount, compileTime, evalTime, totalTime); // Return metadata with timing breakdown diff --git a/src/main/java/org/exist/xquery/modules/api/lsp/Fetch.java b/src/main/java/org/exist/xquery/modules/openapi/cursor/Fetch.java similarity index 97% rename from src/main/java/org/exist/xquery/modules/api/lsp/Fetch.java rename to src/main/java/org/exist/xquery/modules/openapi/cursor/Fetch.java index 326493e..4084182 100644 --- a/src/main/java/org/exist/xquery/modules/api/lsp/Fetch.java +++ b/src/main/java/org/exist/xquery/modules/openapi/cursor/Fetch.java @@ -2,7 +2,7 @@ * SPDX LGPL-2.1-or-later * Copyright (C) 2026 The eXist-db Authors */ -package org.exist.xquery.modules.api.lsp; +package org.exist.xquery.modules.openapi.cursor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -56,7 +56,7 @@ public class Fetch extends BasicFunction { private static final String FS_FETCH_NAME = "fetch"; private static final String FS_FETCH_DESCRIPTION = """ - Retrieves a page of results from a cursor created by lsp:eval(). \ + Retrieves a page of results from a cursor created by cursor:eval(). \ Only the requested items are serialized; the rest remain as live references. \ Returns an array of maps with keys: value (xs:string, serialized item), \ type (xs:string, XDM type), documentURI (xs:string, source document path or ""), \ @@ -66,17 +66,17 @@ and nodeId (xs:string, internal node ID or ""). \ Defaults to method=adaptive, indent=yes."""; public static final FunctionSignature[] FS_FETCH = functionSignatures( - LspModule.qname(FS_FETCH_NAME), + CursorModule.qname(FS_FETCH_NAME), FS_FETCH_DESCRIPTION, returns(Type.ARRAY_ITEM, "an array of result item maps"), arities( arity( - param("cursor", Type.STRING, "The cursor ID returned by lsp:eval()."), + param("cursor", Type.STRING, "The cursor ID returned by cursor:eval()."), param("start", Type.INTEGER, "1-based start position."), param("count", Type.INTEGER, "Number of items to retrieve.") ), arity( - param("cursor", Type.STRING, "The cursor ID returned by lsp:eval()."), + param("cursor", Type.STRING, "The cursor ID returned by cursor:eval()."), param("start", Type.INTEGER, "1-based start position."), param("count", Type.INTEGER, "Number of items to retrieve."), param("serialization", Type.MAP_ITEM, """ @@ -128,7 +128,7 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro final int actualStart = Math.max(1, start); final int actualEnd = Math.min(actualStart + count - 1, total); - logger.debug("lsp:fetch cursor={} start={} count={} (total={})", cursorId, actualStart, count, total); + logger.debug("cursor:fetch cursor={} start={} count={} (total={})", cursorId, actualStart, count, total); final List items = new ArrayList<>(); diff --git a/src/main/java/org/exist/xquery/modules/api/lsp/Completions.java b/src/main/java/org/exist/xquery/modules/openapi/langservice/Completions.java similarity index 99% rename from src/main/java/org/exist/xquery/modules/api/lsp/Completions.java rename to src/main/java/org/exist/xquery/modules/openapi/langservice/Completions.java index e01874a..56d47f9 100644 --- a/src/main/java/org/exist/xquery/modules/api/lsp/Completions.java +++ b/src/main/java/org/exist/xquery/modules/openapi/langservice/Completions.java @@ -2,7 +2,7 @@ * SPDX LGPL-2.1-or-later * Copyright (C) 2026 The eXist-db Authors */ -package org.exist.xquery.modules.api.lsp; +package org.exist.xquery.modules.openapi.langservice; import java.io.StringReader; import java.util.ArrayList; @@ -77,7 +77,7 @@ public class Completions extends BasicFunction { included if the expression compiles successfully."""; public static final FunctionSignature[] FS_COMPLETIONS = functionSignatures( - LspModule.qname(FS_COMPLETIONS_NAME), + LangServiceModule.qname(FS_COMPLETIONS_NAME), FS_COMPLETIONS_DESCRIPTION, returns(Type.ARRAY_ITEM, "an array of completion item maps"), arities( diff --git a/src/main/java/org/exist/xquery/modules/api/lsp/Definition.java b/src/main/java/org/exist/xquery/modules/openapi/langservice/Definition.java similarity index 98% rename from src/main/java/org/exist/xquery/modules/api/lsp/Definition.java rename to src/main/java/org/exist/xquery/modules/openapi/langservice/Definition.java index 98dc231..1fd6e02 100644 --- a/src/main/java/org/exist/xquery/modules/api/lsp/Definition.java +++ b/src/main/java/org/exist/xquery/modules/openapi/langservice/Definition.java @@ -2,7 +2,7 @@ * SPDX LGPL-2.1-or-later * Copyright (C) 2026 The eXist-db Authors */ -package org.exist.xquery.modules.api.lsp; +package org.exist.xquery.modules.openapi.langservice; import java.io.StringReader; @@ -72,7 +72,7 @@ public class Definition extends BasicFunction { sequence if no user-declared definition is found."""; public static final FunctionSignature[] FS_DEFINITION = functionSignatures( - LspModule.qname(FS_DEFINITION_NAME), + LangServiceModule.qname(FS_DEFINITION_NAME), FS_DEFINITION_DESCRIPTION, returns(Type.MAP_ITEM, "a definition location map, or empty sequence"), arities( diff --git a/src/main/java/org/exist/xquery/modules/api/lsp/Diagnostics.java b/src/main/java/org/exist/xquery/modules/openapi/langservice/Diagnostics.java similarity index 98% rename from src/main/java/org/exist/xquery/modules/api/lsp/Diagnostics.java rename to src/main/java/org/exist/xquery/modules/openapi/langservice/Diagnostics.java index d2fdfca..2ca3fcb 100644 --- a/src/main/java/org/exist/xquery/modules/api/lsp/Diagnostics.java +++ b/src/main/java/org/exist/xquery/modules/openapi/langservice/Diagnostics.java @@ -2,7 +2,7 @@ * SPDX LGPL-2.1-or-later * Copyright (C) 2026 The eXist-db Authors */ -package org.exist.xquery.modules.api.lsp; +package org.exist.xquery.modules.openapi.langservice; import java.io.StringReader; import java.util.ArrayList; @@ -66,7 +66,7 @@ public class Diagnostics extends BasicFunction { message (xs:string). Returns an empty array if compilation succeeds."""; public static final FunctionSignature[] FS_DIAGNOSTICS = functionSignatures( - LspModule.qname(FS_DIAGNOSTICS_NAME), + LangServiceModule.qname(FS_DIAGNOSTICS_NAME), FS_DIAGNOSTICS_DESCRIPTION, returns(Type.ARRAY_ITEM, "an array of diagnostic maps"), arities( diff --git a/src/main/java/org/exist/xquery/modules/api/lsp/Hover.java b/src/main/java/org/exist/xquery/modules/openapi/langservice/Hover.java similarity index 98% rename from src/main/java/org/exist/xquery/modules/api/lsp/Hover.java rename to src/main/java/org/exist/xquery/modules/openapi/langservice/Hover.java index 836581d..1e238c2 100644 --- a/src/main/java/org/exist/xquery/modules/api/lsp/Hover.java +++ b/src/main/java/org/exist/xquery/modules/openapi/langservice/Hover.java @@ -2,7 +2,7 @@ * SPDX LGPL-2.1-or-later * Copyright (C) 2026 The eXist-db Authors */ -package org.exist.xquery.modules.api.lsp; +package org.exist.xquery.modules.openapi.langservice; import java.io.StringReader; import java.util.Iterator; @@ -62,7 +62,7 @@ public class Hover extends BasicFunction { symbol is found at the position."""; public static final FunctionSignature[] FS_HOVER = functionSignatures( - LspModule.qname(FS_HOVER_NAME), + LangServiceModule.qname(FS_HOVER_NAME), FS_HOVER_DESCRIPTION, returns(Type.MAP_ITEM, "a hover info map, or empty sequence"), arities( diff --git a/src/main/java/org/exist/xquery/modules/api/lsp/LspModule.java b/src/main/java/org/exist/xquery/modules/openapi/langservice/LangServiceModule.java similarity index 58% rename from src/main/java/org/exist/xquery/modules/api/lsp/LspModule.java rename to src/main/java/org/exist/xquery/modules/openapi/langservice/LangServiceModule.java index d10eadf..254e262 100644 --- a/src/main/java/org/exist/xquery/modules/api/lsp/LspModule.java +++ b/src/main/java/org/exist/xquery/modules/openapi/langservice/LangServiceModule.java @@ -2,7 +2,7 @@ * SPDX LGPL-2.1-or-later * Copyright (C) 2026 The eXist-db Authors */ -package org.exist.xquery.modules.api.lsp; +package org.exist.xquery.modules.openapi.langservice; import org.exist.dom.QName; import org.exist.xquery.AbstractInternalModule; @@ -14,35 +14,32 @@ import static org.exist.xquery.FunctionDSL.functionDefs; /** - * Backward-compatibility module that registers LSP functions under the - * original {@code http://exist-db.org/xquery/lsp} namespace. + * XQuery function module providing language-services capabilities for XQuery editors. * - *

This allows existing code (e.g., eXide) that imports {@code lsp:eval}, - * {@code lsp:fetch}, etc. to continue working without changes after - * migrating from the standalone exist-lsp package to exist-api.

+ *

Registers functions under {@code http://exist-db.org/xquery/langservice} + * (prefix: {@code lang}). The functions emit data shapes inspired by the + * Language Server Protocol (LSP) but are consumed over HTTP/JSON, not the + * LSP wire protocol.

* - *

All functions delegate to the same implementations used by the - * {@code api} namespace module.

+ *

Server-side cursor functions for paginated query execution live in + * {@link org.exist.xquery.modules.openapi.cursor.CursorModule}, not here.

*/ -public class LspModule extends AbstractInternalModule { +public class LangServiceModule extends AbstractInternalModule { - public static final String NAMESPACE_URI = "http://exist-db.org/xquery/lsp"; - public static final String PREFIX = "lsp"; + public static final String NAMESPACE_URI = "http://exist-db.org/xquery/langservice"; + public static final String PREFIX = "lang"; public static final String RELEASE = "0.9.0-SNAPSHOT"; public static final FunctionDef[] functions = functionDefs( - functionDefs(Close.class, Close.FS_CLOSE), functionDefs(Completions.class, Completions.FS_COMPLETIONS), functionDefs(Definition.class, Definition.FS_DEFINITION), functionDefs(Diagnostics.class, Diagnostics.FS_DIAGNOSTICS), - functionDefs(Eval.class, Eval.FS_EVAL), - functionDefs(Fetch.class, Fetch.FS_FETCH), functionDefs(Hover.class, Hover.FS_HOVER), functionDefs(References.class, References.FS_REFERENCES), functionDefs(Symbols.class, Symbols.FS_SYMBOLS) ); - public LspModule(final Map> parameters) { + public LangServiceModule(final Map> parameters) { super(functions, parameters, true); } @@ -54,7 +51,7 @@ public LspModule(final Map> parameters) { @Override public String getDescription() { - return "Backward-compatible LSP functions (use http://exist-db.org/xquery/api for new code)"; + return "Language services for XQuery (diagnostics, completions, hover, definition, references, symbols)"; } @Override diff --git a/src/main/java/org/exist/xquery/modules/api/lsp/References.java b/src/main/java/org/exist/xquery/modules/openapi/langservice/References.java similarity index 99% rename from src/main/java/org/exist/xquery/modules/api/lsp/References.java rename to src/main/java/org/exist/xquery/modules/openapi/langservice/References.java index 9c61bc1..6de5764 100644 --- a/src/main/java/org/exist/xquery/modules/api/lsp/References.java +++ b/src/main/java/org/exist/xquery/modules/openapi/langservice/References.java @@ -2,7 +2,7 @@ * SPDX LGPL-2.1-or-later * Copyright (C) 2026 The eXist-db Authors */ -package org.exist.xquery.modules.api.lsp; +package org.exist.xquery.modules.openapi.langservice; import java.io.StringReader; import java.util.ArrayList; @@ -69,7 +69,7 @@ public class References extends BasicFunction { array if no symbol is found at the position."""; public static final FunctionSignature[] FS_REFERENCES = functionSignatures( - LspModule.qname(FS_REFERENCES_NAME), + LangServiceModule.qname(FS_REFERENCES_NAME), FS_REFERENCES_DESCRIPTION, returns(Type.ARRAY_ITEM, "an array of reference location maps"), arities( diff --git a/src/main/java/org/exist/xquery/modules/api/lsp/Symbols.java b/src/main/java/org/exist/xquery/modules/openapi/langservice/Symbols.java similarity index 98% rename from src/main/java/org/exist/xquery/modules/api/lsp/Symbols.java rename to src/main/java/org/exist/xquery/modules/openapi/langservice/Symbols.java index 3a37d3d..5eddef9 100644 --- a/src/main/java/org/exist/xquery/modules/api/lsp/Symbols.java +++ b/src/main/java/org/exist/xquery/modules/openapi/langservice/Symbols.java @@ -2,7 +2,7 @@ * SPDX LGPL-2.1-or-later * Copyright (C) 2026 The eXist-db Authors */ -package org.exist.xquery.modules.api.lsp; +package org.exist.xquery.modules.openapi.langservice; import java.io.StringReader; import java.util.ArrayList; @@ -70,7 +70,7 @@ public class Symbols extends BasicFunction { Returns an empty array if the expression cannot be compiled."""; public static final FunctionSignature[] FS_SYMBOLS = functionSignatures( - LspModule.qname(FS_SYMBOLS_NAME), + LangServiceModule.qname(FS_SYMBOLS_NAME), FS_SYMBOLS_DESCRIPTION, returns(Type.ARRAY_ITEM, "an array of document symbol maps"), arities( diff --git a/src/test/resources/conf.xml b/src/test/resources/conf.xml index 2b6f4ad..dbad181 100644 --- a/src/test/resources/conf.xml +++ b/src/test/resources/conf.xml @@ -16,10 +16,10 @@ backwardCompatible="no" enforce-index-use="always" raise-error-on-failed-retrieval="no"> - - + + diff --git a/xar-assembly.xml b/xar-assembly.xml index a7b2f44..42b05ff 100644 --- a/xar-assembly.xml +++ b/xar-assembly.xml @@ -56,12 +56,12 @@ - ${api.module.namespace} - urn:java:class:${api.module.java.classname} + ${langservice.module.namespace} + urn:java:class:${langservice.module.java.classname} - ${lsp.module.namespace} - urn:java:class:${lsp.module.java.classname} + ${cursor.module.namespace} + urn:java:class:${cursor.module.java.classname} From 5a56e5efb8fe24f5e7a9cd3ac854de9f157cddb0 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Sat, 16 May 2026 15:05:30 -0400 Subject: [PATCH 2/6] Rename project from exist-api to existdb-openapi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renames the Maven artifactId, XAR target, EXPath package URI, and related coordinates to match the GitHub repo rename in #13. The repo itself was renamed from eXist-db/exist-api to eXist-db/existdb-openapi on GitHub (auto-redirect in place). Per #13, "existdb-openapi" matches what this project actually is — an OpenAPI-described HTTP surface, distinct from RESTXQ, the legacy REST Server, and XML:DB. The "exist-api" name was too generic given how many APIs eXist already has. Coordinates changed: - artifactId: exist-api -> existdb-openapi - project name: "Platform API Module" -> "eXist-db OpenAPI" - scm/url: github.com/eXist-db/exist-api -> .../existdb-openapi - package-name (EXPath URI): /pkg/api -> /pkg/openapi - package-abbrev: exist-api -> existdb-openapi - XAR target: exist-api -> existdb-openapi - XAR tags: exist-api/platform-api -> existdb-openapi/openapi MIGRATION.md updated to reflect the rename and document the coordinate changes for downstream consumers. Closes #13 Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build.yml | 8 ++++---- MIGRATION.md | 20 ++++++++++++++++---- controller.xq | 2 +- cypress.config.cjs | 2 +- modules/api.json | 20 ++++++++++---------- modules/langservice.xqm | 2 +- package-lock.json | 4 ++-- package.json | 4 ++-- pom.xml | 18 +++++++++--------- repo.xml | 6 +++--- src/test/cypress/e2e/db.cy.js | 4 ++-- src/test/cypress/e2e/packages.cy.js | 8 ++++---- src/test/cypress/e2e/parity.cy.js | 10 +++++----- src/test/cypress/e2e/site.cy.js | 8 ++++---- xar-assembly.xml | 4 ++-- 15 files changed, 66 insertions(+), 54 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f6a42c3..89ecc2a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -78,7 +78,7 @@ jobs: echo "eXist did not become ready in time" exit 1 - - name: Install roaster (XQuery dependency of exist-api) + - name: Install roaster (XQuery dependency of existdb-openapi) run: | npx --yes @existdb/xst@^4 package install from-registry \ http://e-editiones.org/roaster \ @@ -88,9 +88,9 @@ jobs: EXISTDB_USER: admin EXISTDB_PASS: "" - - name: Deploy exist-api XAR + - name: Deploy existdb-openapi XAR run: | - npx --yes @existdb/xst@^4 package install local target/exist-api-*.xar + npx --yes @existdb/xst@^4 package install local target/existdb-openapi-*.xar env: EXISTDB_SERVER: http://localhost:8080 EXISTDB_USER: admin @@ -106,7 +106,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: exist-api-xar + name: existdb-openapi-xar path: target/*.xar if-no-files-found: warn diff --git a/MIGRATION.md b/MIGRATION.md index 9443385..e7151b0 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,12 +1,14 @@ -# Migration: `lsp` → `langservice` + `cursor` (pre-1.0.0) +# Migration: `lsp` → `langservice` + `cursor`, repo `exist-api` → `existdb-openapi` (pre-1.0.0) -This document describes the breaking rename landed in PR for issue #15. It applies to consumers that integrated against pre-release versions of exist-api (formerly the `lsp:` namespace inherited from `exist-lsp`). +This document describes the breaking renames landed in the PR closing issues #7, #13, #15, and #17. It applies to consumers that integrated against pre-release versions of this project (formerly `exist-api`, with the `lsp:` namespace inherited from `exist-lsp`). This file will be removed once all known consumers (eXide, monex, existdb-langserver, notebook) have been updated. If you reach this section after that point, the names below are settled. ## Why -The previous `lsp:` namespace and `/api/lsp/*` URL prefix were inherited from the migrated `exist-lsp` standalone package. They implied that exist-api speaks the Language Server Protocol — it does not. exist-api is an HTTP/JSON backend whose data shapes are *inspired by* LSP types but consumed via REST, not JSON-RPC. See issue #7 for the full discussion. +The previous `lsp:` namespace and `/api/lsp/*` URL prefix were inherited from the migrated `exist-lsp` standalone package. They implied that this project speaks the Language Server Protocol — it does not. The project is an HTTP/JSON backend whose data shapes are *inspired by* LSP types but consumed via REST, not JSON-RPC. See issue #7 for the full discussion. + +The repo itself was also renamed from `exist-api` to `existdb-openapi` to match what the project actually is — an OpenAPI-described HTTP surface, distinct from RESTXQ, the legacy REST Server, and XML:DB. See issue #13. The rename also splits server-side cursor functions (`eval` / `fetch` / `close`) out of the language-services namespace, since they are query-execution primitives, not language services. @@ -64,8 +66,18 @@ Function renames: None of the previous names are kept as aliases. All four known consumers — eXide, existdb-langserver, monex, notebook — are pre-release and updated in lockstep with this change. +## Repo / package coordinates + +| Coordinate | Before | After | +|---|---|---| +| GitHub repo | `eXist-db/exist-api` | `eXist-db/existdb-openapi` | +| Maven `artifactId` | `exist-api` | `existdb-openapi` | +| XAR `target` / `package-abbrev` | `exist-api` | `existdb-openapi` | +| EXPath `package-name` URI | `http://exist-db.org/pkg/api` | `http://exist-db.org/pkg/openapi` | + +GitHub auto-redirects the old repo URL. + ## What did NOT change - The `/api/*` URL prefix and the OpenAPI surface as a whole. - HTTP request/response *shapes* for the language-service endpoints. Those will be tightened to LSP-isomorphic shapes in a follow-up PR (issue #16) — separate from this rename. -- The `repoxml` package name / abbrev (still `exist-api` until the repo rename in #13 lands). diff --git a/controller.xq b/controller.xq index 00848d1..54fe98b 100644 --- a/controller.xq +++ b/controller.xq @@ -5,7 +5,7 @@ xquery version "3.1"; (:~ - : URL rewriting controller for the exist-api package. + : URL rewriting controller for the existdb-openapi package. : Routes /api/* requests to the Roaster-based API entry point. :) diff --git a/cypress.config.cjs b/cypress.config.cjs index 8ccec25..3299592 100644 --- a/cypress.config.cjs +++ b/cypress.config.cjs @@ -6,7 +6,7 @@ module.exports = defineConfig({ videosFolder: 'target/cypress/videos', downloadsFolder: 'target/cypress/downloads', e2e: { - baseUrl: 'http://localhost:8080/exist/apps/exist-api', + baseUrl: 'http://localhost:8080/exist/apps/existdb-openapi', supportFile: false, specPattern: 'src/test/cypress/e2e/**/*.cy.js' } diff --git a/modules/api.json b/modules/api.json index 9f91895..c9f932e 100644 --- a/modules/api.json +++ b/modules/api.json @@ -11,7 +11,7 @@ }, "servers": [ { - "url": "/exist/apps/exist-api", + "url": "/exist/apps/existdb-openapi", "description": "Local eXist-db instance" } ], @@ -2143,7 +2143,7 @@ "get": { "summary": "Get current user identity", "operationId": "users:whoami", - "description": "Returns the real and effective user identities and group memberships. Also serves as a capability probe: a 200 response indicates exist-api is installed. Replaces xst's whoami.xq.", + "description": "Returns the real and effective user identities and group memberships. Also serves as a capability probe: a 200 response indicates existdb-openapi is installed. Replaces xst's whoami.xq.", "tags": [ "Users" ], @@ -2993,7 +2993,7 @@ { "uri": "http://exist-db.org/pkg/api", "name": "http://exist-db.org/pkg/api", - "abbrev": "exist-api", + "abbrev": "existdb-openapi", "version": "0.9.0-SNAPSHOT", "title": "Platform API Module", "processor": [ @@ -3010,7 +3010,7 @@ "xquery": [], "xslt": [], "jar": [ - "exist-api-0.9.0-SNAPSHOT.jar" + "existdb-openapi-0.9.0-SNAPSHOT.jar" ] }, "website": "", @@ -3020,7 +3020,7 @@ "eXist-db" ], "type": "application", - "target": "exist-api", + "target": "existdb-openapi", "date": "2024-01-01T00:00:00Z" } ] @@ -3143,7 +3143,7 @@ "schema": { "type": "string" }, - "description": "Package abbreviation (e.g. 'exist-api')" + "description": "Package abbreviation (e.g. 'existdb-openapi')" } ], "responses": { @@ -3213,7 +3213,7 @@ "example": { "uri": "http://exist-db.org/pkg/api", "name": "http://exist-db.org/pkg/api", - "abbrev": "exist-api", + "abbrev": "existdb-openapi", "version": "0.9.0-SNAPSHOT", "title": "Platform API Module", "processor": [ @@ -3230,7 +3230,7 @@ "xquery": [], "xslt": [], "jar": [ - "exist-api-0.9.0-SNAPSHOT.jar" + "existdb-openapi-0.9.0-SNAPSHOT.jar" ] }, "website": "", @@ -3240,7 +3240,7 @@ "eXist-db" ], "type": "application", - "target": "exist-api", + "target": "existdb-openapi", "date": "2024-01-01T00:00:00Z" } } @@ -3732,7 +3732,7 @@ "/api/langservice/capabilities": { "get": { "summary": "Capability discovery", - "description": "Returns the set of language-service features the running exist-api instance supports.", + "description": "Returns the set of language-service features the running existdb-openapi instance supports.", "operationId": "langservice:capabilities", "tags": [ "Language Service" diff --git a/modules/langservice.xqm b/modules/langservice.xqm index 0d7752e..07d9baa 100644 --- a/modules/langservice.xqm +++ b/modules/langservice.xqm @@ -121,7 +121,7 @@ declare function langservice:symbols($request as map(*)) { (:~ : Capability discovery — returns the set of language-service features - : the running exist-api instance supports. Modeled loosely on LSP's + : the running existdb-openapi instance supports. Modeled loosely on LSP's : ServerCapabilities, but for the REST surface. : GET /api/langservice/capabilities :) diff --git a/package-lock.json b/package-lock.json index 50a4693..fb99a67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,10 +1,10 @@ { - "name": "exist-api", + "name": "existdb-openapi", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "exist-api", + "name": "existdb-openapi", "devDependencies": { "@existdb/xst": "^4.1.2", "cypress": "^15.13.0" diff --git a/package.json b/package.json index e3f56be..961a708 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "exist-api", + "name": "existdb-openapi", "private": true, "scripts": { - "deploy": "xst package install target/exist-api-*.xar", + "deploy": "xst package install target/existdb-openapi-*.xar", "test": "cypress run", "test:open": "cypress open" }, diff --git a/pom.xml b/pom.xml index 4b5a644..ee2b2c1 100644 --- a/pom.xml +++ b/pom.xml @@ -12,12 +12,12 @@ org.exist-db - exist-api + existdb-openapi 0.9.0-SNAPSHOT - Platform API Module - Unified platform API for eXist-db: database management, query execution, language services, user management, package management, search, and cross-app linking - https://github.com/eXist-db/exist-api + eXist-db OpenAPI + OpenAPI-described HTTP surface for eXist-db: database management, query execution, language services, user management, package management, search, and cross-app linking + https://github.com/eXist-db/existdb-openapi The eXist-db Authors @@ -32,9 +32,9 @@ - https://github.com/eXist-db/exist-api.git - scm:git:https://github.com/eXist-db/exist-api.git - scm:git:https://github.com/eXist-db/exist-api.git + https://github.com/eXist-db/existdb-openapi.git + scm:git:https://github.com/eXist-db/existdb-openapi.git + scm:git:https://github.com/eXist-db/existdb-openapi.git @@ -50,8 +50,8 @@ 11.8.0 - http://exist-db.org/pkg/api - exist-api + http://exist-db.org/pkg/openapi + existdb-openapi http://exist-db.org/xquery/langservice diff --git a/repo.xml b/repo.xml index 0a7fb45..08bdeb7 100644 --- a/repo.xml +++ b/repo.xml @@ -1,13 +1,13 @@ - Unified platform API for eXist-db + OpenAPI-described HTTP surface for eXist-db eXist-db - https://github.com/eXist-db/exist-api + https://github.com/eXist-db/existdb-openapi stable GNU Lesser General Public License, version 2.1 true application - exist-api + existdb-openapi diff --git a/src/test/cypress/e2e/db.cy.js b/src/test/cypress/e2e/db.cy.js index b2ccf23..4213bdf 100644 --- a/src/test/cypress/e2e/db.cy.js +++ b/src/test/cypress/e2e/db.cy.js @@ -42,7 +42,7 @@ describe('/api/db', () => { it('supports glob filter', () => { cy.request({ - url: '/api/db?path=/db/apps/exist-api/modules&glob=*.xqm', + url: '/api/db?path=/db/apps/existdb-openapi/modules&glob=*.xqm', auth }).then(response => { const resources = response.body.children.filter(c => c.type === 'resource'); @@ -54,7 +54,7 @@ describe('/api/db', () => { it('supports recursive listing', () => { cy.request({ - url: '/api/db?path=/db/apps/exist-api&recursive=true&depth=1', + url: '/api/db?path=/db/apps/existdb-openapi&recursive=true&depth=1', auth }).then(response => { const collections = response.body.children.filter(c => c.type === 'collection'); diff --git a/src/test/cypress/e2e/packages.cy.js b/src/test/cypress/e2e/packages.cy.js index 1056556..fb85335 100644 --- a/src/test/cypress/e2e/packages.cy.js +++ b/src/test/cypress/e2e/packages.cy.js @@ -21,10 +21,10 @@ describe('/api/packages', () => { describe('GET /api/packages/{name}', () => { it('gets package details by abbreviation with full metadata', () => { cy.request({ - url: '/api/packages/exist-api', + url: '/api/packages/existdb-openapi', auth }).then(response => { - expect(response.body.abbrev).to.eq('exist-api'); + expect(response.body.abbrev).to.eq('existdb-openapi'); expect(response.body).to.have.property('components'); expect(response.body).to.have.property('version'); expect(response.body).to.have.property('name'); @@ -33,10 +33,10 @@ describe('/api/packages', () => { it('gets package details by abbreviation', () => { cy.request({ - url: '/api/packages/exist-api', + url: '/api/packages/existdb-openapi', auth }).then(response => { - expect(response.body.abbrev).to.eq('exist-api'); + expect(response.body.abbrev).to.eq('existdb-openapi'); }); }); diff --git a/src/test/cypress/e2e/parity.cy.js b/src/test/cypress/e2e/parity.cy.js index 6c80c25..13ba236 100644 --- a/src/test/cypress/e2e/parity.cy.js +++ b/src/test/cypress/e2e/parity.cy.js @@ -184,7 +184,7 @@ describe('Priority 3: atom-editor-support endpoints', () => { describe('GET /api/db/sync', () => { it('returns sync tree', () => { cy.request({ - url: '/api/db/sync?root=/db/apps/exist-api/modules', + url: '/api/db/sync?root=/db/apps/existdb-openapi/modules', auth }).then(response => { expect(response.status).to.eq(200); @@ -202,7 +202,7 @@ describe('Priority 3: atom-editor-support endpoints', () => { it('supports timestamp filter', () => { // Use a future timestamp — should return empty children cy.request({ - url: '/api/db/sync?root=/db/apps/exist-api/modules×tamp=2099-01-01T00:00:00Z', + url: '/api/db/sync?root=/db/apps/existdb-openapi/modules×tamp=2099-01-01T00:00:00Z', auth }).then(response => { // Resources should be filtered out (all older than 2099) @@ -215,7 +215,7 @@ describe('Priority 3: atom-editor-support endpoints', () => { describe('GET /api/modules', () => { it('returns importable modules', () => { cy.request({ - url: '/api/modules?path=/db/apps/exist-api/modules/db.xqm', + url: '/api/modules?path=/db/apps/existdb-openapi/modules/db.xqm', auth }).then(response => { expect(response.status).to.eq(200); @@ -233,7 +233,7 @@ describe('Priority 3: atom-editor-support endpoints', () => { describe('Priority 4: ACL support', () => { it('db:list includes acl field', () => { cy.request({ - url: '/api/db?path=/db/apps/exist-api/modules&glob=api.xq', + url: '/api/db?path=/db/apps/existdb-openapi/modules&glob=api.xq', auth }).then(response => { expect(response.body).to.have.property('acl'); @@ -247,7 +247,7 @@ describe('Priority 4: ACL support', () => { it('db:properties includes acl field', () => { cy.request({ - url: '/api/db/properties?path=/db/apps/exist-api/modules/api.xq', + url: '/api/db/properties?path=/db/apps/existdb-openapi/modules/api.xq', auth }).then(response => { expect(response.body).to.have.property('acl'); diff --git a/src/test/cypress/e2e/site.cy.js b/src/test/cypress/e2e/site.cy.js index a302a1b..2d7dc8f 100644 --- a/src/test/cypress/e2e/site.cy.js +++ b/src/test/cypress/e2e/site.cy.js @@ -17,10 +17,10 @@ describe('/api/site', () => { }); }); - it('includes exist-api itself', () => { + it('includes existdb-openapi itself', () => { cy.request({ url: '/api/site/apps', auth }).then(response => { const abbrevs = response.body.map(a => a.abbrev); - expect(abbrevs).to.include('exist-api'); + expect(abbrevs).to.include('existdb-openapi'); }); }); }); @@ -28,11 +28,11 @@ describe('/api/site', () => { describe('GET /api/site/resolve', () => { it('resolves link for installed app', () => { cy.request({ - url: '/api/site/resolve?app=exist-api&path=/api/system/info', + url: '/api/site/resolve?app=existdb-openapi&path=/api/system/info', auth }).then(response => { expect(response.body).to.have.property('url'); - expect(response.body.url).to.include('/exist/apps/exist-api/'); + expect(response.body.url).to.include('/exist/apps/existdb-openapi/'); }); }); diff --git a/xar-assembly.xml b/xar-assembly.xml index 42b05ff..c581fde 100644 --- a/xar-assembly.xml +++ b/xar-assembly.xml @@ -15,8 +15,8 @@ stable - exist-api - platform-api + existdb-openapi + openapi rest xquery Libraries From c4f4916d19836a43a2419643d1f76e3dea5003be Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Sat, 16 May 2026 15:30:29 -0400 Subject: [PATCH 3/6] Remove orphan test/xqs fileSet from xar-assembly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fileSet pointed to a non-existent directory and caused mvn package to fail. Pre-existing issue, not related to the rename, but blocking local XAR builds — removing as part of this PR so the rename is properly buildable. XQSuite tests can be added back later under a real directory when there are tests to ship. Co-Authored-By: Claude Opus 4.7 (1M context) --- xar-assembly.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/xar-assembly.xml b/xar-assembly.xml index c581fde..2778812 100644 --- a/xar-assembly.xml +++ b/xar-assembly.xml @@ -38,10 +38,6 @@ ${basedir}/modules modules - - ${basedir}/test/xqs - test/xqs - From b588b2459a7df60ac0376f375a04f4ac2dc7631f Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Sat, 16 May 2026 22:30:31 -0400 Subject: [PATCH 4/6] Fix XQuery booleans in capabilities; drop ordered=true from small modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes from smoke-testing on eXist 7.0-SNAPSHOT: 1. langservice:capabilities used bare true / false in map literals. XQuery has no boolean literals — they're functions (true(), false()). Bare identifiers were being parsed as path steps, raising err:XPDY0002 at runtime. Fixed. 2. Drop ordered=true from CursorModule and LangServiceModule super(...) calls. AbstractInternalModule with ordered=true uses binary search over the FunctionDef[] for call dispatch, which requires alphabetical sorting of the array. CursorModule's lifecycle-ordered declaration (eval/fetch/close) violated that invariant, making cursor:close() silently unparseable at the call site even though inspect:inspect-module-uri showed the signature registered fine. The error message ("Unexpectedly received N parameter(s)") was misleading. ordered=true is a performance optimization for modules with many functions (FnModule has ~500). For modules with a handful of functions, linear scan via the 2-arg super() is strictly better: no sort invariant to violate, no need to declare functions alphabetically vs. semantically. Refs eXist-db/exist#6378. 3. Renamed local $cursor variable to $cursor-id in modules/query.xqm to avoid visual confusion with the cursor: namespace prefix. Co-Authored-By: Claude Opus 4.7 (1M context) --- modules/langservice.xqm | 14 +++++++------- modules/query.xqm | 8 ++++---- .../modules/openapi/cursor/CursorModule.java | 2 +- .../openapi/langservice/LangServiceModule.java | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/modules/langservice.xqm b/modules/langservice.xqm index 07d9baa..6227a99 100644 --- a/modules/langservice.xqm +++ b/modules/langservice.xqm @@ -127,13 +127,13 @@ declare function langservice:symbols($request as map(*)) { :) declare function langservice:capabilities($request as map(*)) { map { - "diagnostics": map { "available": true, "provider": "exist-xquery-parser" }, - "completions": map { "available": true, "positional": false }, - "hover": map { "available": true, "markupKinds": [ "plaintext" ] }, - "definition": map { "available": true, "multiTarget": false }, - "references": map { "available": true, "includeDeclaration": false }, - "symbols": map { "available": true, "hierarchical": false }, - "cursor": map { "available": true }, + "diagnostics": map { "available": true(), "provider": "exist-xquery-parser" }, + "completions": map { "available": true(), "positional": false() }, + "hover": map { "available": true(), "markupKinds": [ "plaintext" ] }, + "definition": map { "available": true(), "multiTarget": false() }, + "references": map { "available": true(), "includeDeclaration": false() }, + "symbols": map { "available": true(), "hierarchical": false() }, + "cursor": map { "available": true() }, "version": "0.9.0-SNAPSHOT" } }; diff --git a/modules/query.xqm b/modules/query.xqm index 2df6eb9..b415aea 100644 --- a/modules/query.xqm +++ b/modules/query.xqm @@ -74,7 +74,7 @@ declare function query:execute($request as map(*)) { : @return array of result item maps :) declare function query:fetch($request as map(*)) { - let $cursor := $request?parameters?id + let $cursor-id := $request?parameters?id let $start := ($request?parameters?start, 1)[1] cast as xs:integer let $count := ($request?parameters?count, 10)[1] cast as xs:integer (: cursor:fetch() expects string values for boolean params — keep as-is :) @@ -83,7 +83,7 @@ declare function query:fetch($request as map(*)) { "indent": ($request?parameters?indent, "yes")[1] } return - cursor:fetch($cursor, $start, $count, $ser) + cursor:fetch($cursor-id, $start, $count, $ser) }; (:~ @@ -94,8 +94,8 @@ declare function query:fetch($request as map(*)) { : @return map with status :) declare function query:close($request as map(*)) { - let $cursor := $request?parameters?id - let $closed := cursor:close($cursor) + let $cursor-id := $request?parameters?id + let $closed := cursor:close($cursor-id) return map { "closed": $closed } }; diff --git a/src/main/java/org/exist/xquery/modules/openapi/cursor/CursorModule.java b/src/main/java/org/exist/xquery/modules/openapi/cursor/CursorModule.java index efbe9f8..97f763a 100644 --- a/src/main/java/org/exist/xquery/modules/openapi/cursor/CursorModule.java +++ b/src/main/java/org/exist/xquery/modules/openapi/cursor/CursorModule.java @@ -51,7 +51,7 @@ public class CursorModule extends AbstractInternalModule { ); public CursorModule(final Map> parameters) { - super(functions, parameters, true); + super(functions, parameters); final long maxSize = getLongParam(parameters, PARAM_CURSOR_MAXIMUM_SIZE, 100); final long expireMs = getLongParam(parameters, PARAM_CURSOR_EXPIRE_AFTER_ACCESS, 300_000); diff --git a/src/main/java/org/exist/xquery/modules/openapi/langservice/LangServiceModule.java b/src/main/java/org/exist/xquery/modules/openapi/langservice/LangServiceModule.java index 254e262..2b960b5 100644 --- a/src/main/java/org/exist/xquery/modules/openapi/langservice/LangServiceModule.java +++ b/src/main/java/org/exist/xquery/modules/openapi/langservice/LangServiceModule.java @@ -40,7 +40,7 @@ public class LangServiceModule extends AbstractInternalModule { ); public LangServiceModule(final Map> parameters) { - super(functions, parameters, true); + super(functions, parameters); } @Override From ddf52b731580116cb56e10effe25966f78379a8f Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Sat, 16 May 2026 22:30:31 -0400 Subject: [PATCH 5/6] Untrack tooling dirs: .claude/, .codacy/ Local tooling artifacts (Claude Code session state, Codacy analyzer config) that crept in during the rename PR via 'git add -A'. Adding to .gitignore so they stay out. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 2c578f9..06ba419 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ node_modules/ #Ignore vscode AI rules .github/instructions/codacy.instructions.md +.claude/ +.codacy/ From a9b10e4b7b8943ce8deebca77e48367df0cf2044 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Sun, 17 May 2026 03:37:04 -0400 Subject: [PATCH 6/6] Add comprehensive README Covers what existdb-openapi is (and isn't), how it fits among eXist's several historical HTTP surfaces, a quick-start with curl, an endpoint index grouped by concern (db, query, langservice, users/groups/packages, search, system), the two internal XQuery namespaces (lang:* and cursor:*), the cursor lifecycle, configuration knobs (cursor.maximumSize etc.), build instructions, project layout, versioning policy, and pointers to consumers (eXide, existdb-langserver, monex, notebook) and the open 1.0 tracking issues (#2, #15, #16, #17, #13). The XAR assembly already declared README.md as a fileSet include, so the README ships with the package. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 353 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 353 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..60f73cc --- /dev/null +++ b/README.md @@ -0,0 +1,353 @@ +# existdb-openapi + +An OpenAPI-described HTTP API for [eXist-db](https://exist-db.org/) — a single, +versioned REST surface for database management, query execution, language +services, user/group/package administration, search, and cross-app linking. + +**Status:** pre-1.0 (`0.9.x` releases). The 1.0.0 milestone tracks API +stabilization. See [issue #2](https://github.com/eXist-db/existdb-openapi/issues/2). + +--- + +## What this is (and isn't) + +eXist-db ships several HTTP surfaces accumulated over its history: + +- **RESTXQ** — declarative URL routing for XQuery (annotation-based) +- **REST Server** (`/exist/rest/`) — the legacy XML:DB-flavored interface +- **XML:DB / XML-RPC** — the older Java/RPC era +- the **Roaster** framework — the toolkit this project is built on + +`existdb-openapi` is **none of the above** — it's a new, OpenAPI-described +surface that consolidates the operations modern eXist apps actually need (admin +endpoints, query execution, language services for editor integration) under a +coherent versioned contract. It's defined by [`modules/api.json`](modules/api.json), +served by the [Roaster](https://github.com/eeditiones/roaster) routing layer, +and backed by a mix of XQuery handlers and Java-implemented XQuery functions +for the performance-sensitive paths (cursor store, language analysis). + +It is **not** a Language Server Protocol implementation. The `/api/langservice/*` +endpoints return data shapes inspired by LSP types, but the wire protocol is +plain HTTP/JSON, not JSON-RPC. See [#7](https://github.com/eXist-db/existdb-openapi/issues/7) +for the scope discussion. + +--- + +## Quick start + +### Install on a running eXist-db 7.0+ + +1. Download the latest XAR from [Releases](https://github.com/eXist-db/existdb-openapi/releases) + (or build from source — see below). +2. Drop it into eXist's autodeploy directory (or install via the Dashboard's + Package Manager). Dependencies: + - eXist-db 7.0+ + - [Roaster](https://github.com/eeditiones/roaster) ≥ 1.8.0 +3. The API is then reachable at `/exist/apps/existdb-openapi/api/*`. + +### Smoke test + +```bash +# Capability discovery (no auth required) +curl http://localhost:8080/exist/apps/existdb-openapi/api/langservice/capabilities + +# Run a query (admin auth) +curl -u admin: \ + -X POST -H "Content-Type: application/json" \ + -d '{"query":"(1, 2, 3, 4, 5)"}' \ + http://localhost:8080/exist/apps/existdb-openapi/api/query + +# Returns: { "cursor": "", "items": 5, "elapsed": 1, ... } +# Then: +curl -u admin: \ + "http://localhost:8080/exist/apps/existdb-openapi/api/query//results?start=1&count=3" + +# Done with the cursor: +curl -u admin: -X DELETE \ + "http://localhost:8080/exist/apps/existdb-openapi/api/query/" +``` + +--- + +## API surface + +The full spec lives in [`modules/api.json`](modules/api.json) (OpenAPI 3.0). A +non-exhaustive index of what's available, grouped by concern: + +### Database + +| Method | Path | Purpose | +|---|---|---| +| `GET` | `/api/db` | List collection contents | +| `POST` / `DELETE` | `/api/db/collection` | Create / remove collection | +| `POST` | `/api/db/copy` | Copy resource or collection | +| `POST` | `/api/db/move` | Move resource or collection (with optional rename) | +| `POST` | `/api/db/permissions` | Set permissions | +| `GET` | `/api/db/properties` | Resource / collection properties | +| `GET` / `PUT` / `DELETE` | `/api/db/resource` | Get / store / remove a resource | +| `GET` | `/api/db/sync` | Sync a tree with timestamps | +| `GET` | `/api/modules` | Discover importable XQuery modules | + +### Query execution + +Two flavors: + +| Method | Path | Purpose | +|---|---|---| +| `POST` | `/api/eval` | One-shot: evaluate and return serialized result as text | +| `POST` | `/api/query` | Cursor-based: returns a `{cursor, items, elapsed}` handle | +| `GET` | `/api/query/{id}/results?start=…&count=…` | Fetch a page from an open cursor | +| `POST` | `/api/query/{id}/cancel` | Cancel a running query | +| `DELETE` | `/api/query/{id}` | Close the cursor, release server-side resources | + +`/api/eval` is for simple try-it widgets; the `/api/query/…` family is for +paginated UIs (browser-based editors, dashboards). See +[Cursor lifecycle](#cursor-lifecycle) below. + +### Language services + +For editor / IDE integration — analyze XQuery without running it. Request +bodies take `{expression, …}` plus per-endpoint extras (e.g. `line` / `column` +for positional queries). + +| Method | Path | Purpose | +|---|---|---| +| `GET` | `/api/langservice/capabilities` | Feature discovery (which langservice features the server supports) | +| `POST` | `/api/langservice/diagnostics` | Compile-check; structured error list | +| `POST` | `/api/langservice/completions` | Identifier completions in context | +| `POST` | `/api/langservice/hover` | Symbol info at a position | +| `POST` | `/api/langservice/definition` | Go-to-definition target(s) | +| `POST` | `/api/langservice/references` | Find references to a symbol | +| `POST` | `/api/langservice/symbols` | Document symbol outline | + +Response shapes are inspired by LSP types (`CompletionItemKind`, `SymbolKind`, +`DiagnosticSeverity` integers are reused) but the transport is plain +HTTP/JSON. Isomorphic-to-LSP shape tightening is tracked in +[#16](https://github.com/eXist-db/existdb-openapi/issues/16). + +### Users, groups, packages, search, system + +Standard admin surface — see the OpenAPI spec for full operation details. + +| Method | Path | Purpose | +|---|---|---| +| `GET` / `POST` | `/api/users` | List / create | +| `GET` / `PUT` / `DELETE` | `/api/users/{name}` | Get / update / remove | +| `GET` | `/api/users/whoami` | Current authenticated user | +| `GET` / `POST` | `/api/groups` | List / create | +| `GET` / `DELETE` | `/api/groups/{name}` | Get / remove | +| `GET` | `/api/packages` | List installed packages | +| `POST` | `/api/packages/install` | Install a XAR | +| `POST` | `/api/packages/update-check` | Check upstream for newer versions | +| `DELETE` | `/api/packages/{name}` | Remove a package | +| `GET` | `/api/search` | Sitewide search (Lucene) | +| `GET` | `/api/site/apps` | List installed apps | +| `GET` | `/api/site/resolve` | Resolve cross-app link | +| `GET` | `/api/system/info` | System info | +| `GET` | `/api/system/scheduler` | Scheduled jobs | +| `POST` | `/api/test` | Run XQSuite tests | + +--- + +## XQuery namespaces + +The internal XQuery functions surface two namespaces (these are Java-backed +internal modules, not REST endpoints): + +| Prefix | URI | Backed by | What's in it | +|---|---|---|---| +| `lang` | `http://exist-db.org/xquery/langservice` | `org.exist.xquery.modules.openapi.langservice.LangServiceModule` | `lang:diagnostics`, `lang:completions`, `lang:hover`, `lang:definition`, `lang:references`, `lang:symbols` | +| `cursor` | `http://exist-db.org/xquery/cursor` | `org.exist.xquery.modules.openapi.cursor.CursorModule` | `cursor:eval`, `cursor:fetch`, `cursor:close` | + +The REST routes in `modules/langservice.xqm` and `modules/query.xqm` are thin +wrappers over these functions. + +--- + +## Cursor lifecycle + +The `/api/query/*` family implements server-side cursor pagination. The Java +side holds the result sequence (with live node references), allowing lazy +serialization and per-page document-URI lookup: + +``` +client server + │ │ + │ POST /api/query { query, … } │ + │ ───────────────────────────────► │ cursor:eval → stash result sequence + │ ◄─── { cursor: "", items, elapsed } + │ │ + │ GET /api/query//results?… │ + │ ───────────────────────────────► │ cursor:fetch → serialize requested page + │ ◄─── [ items… ] │ + │ │ + │ DELETE /api/query/ │ + │ ───────────────────────────────► │ cursor:close → release the sequence + │ ◄─── { closed: true } │ +``` + +Cursors are also released automatically by the cache: + +- **Time-based**: 5 min of inactivity by default +- **Count-based**: LRU eviction at 100 concurrent cursors by default +- **Weight-based**: configurable optional memory budget + +See [Configuration](#configuration) for tuning. + +--- + +## Configuration + +`CursorModule` accepts three parameters via `exist.xml` (set on the module +declaration): + +| Parameter | Default | Meaning | +|---|---|---| +| `cursor.maximumSize` | `100` | Max concurrent cursors (LRU eviction). `0` = unlimited count. | +| `cursor.expireAfterAccess` | `300000` (5 min) | Inactivity timeout in milliseconds. | +| `cursor.maximumWeight` | `0` (unlimited) | Max estimated total memory in bytes across all cursors. | + +### Connection credentials + +For local development tooling (the `xst` CLI, gulp-exist, the Cypress test +runner, etc.), this repo supports both conventions used in the eXist-db +ecosystem: + +- **`.env` file** — the default for `xst` and `gulp-exist` +- **`.existdb.json` file** — an alternate format also recognized by both tools + +Templates are included as `.env.example` and `.existdb.json.example`; pick the +one that matches your tooling. + +--- + +## Build from source + +### Requirements + +- JDK 21+ (the project uses `--release 21`) +- Maven 3.9+ +- Node.js (for the Cypress test suite — optional) + +### Build + +```bash +mvn package -DskipTests +# → target/existdb-openapi-.xar +``` + +Install the resulting XAR into eXist via the Dashboard's Package Manager, or +drop it in `autodeploy/`. + +### Project layout + +``` +existdb-openapi/ +├── controller.xq # Top-level URL router (delegates to modules/api.xq) +├── modules/ +│ ├── api.json # The OpenAPI 3.0 spec — single source of truth +│ ├── api.xq # Roaster entry-point; loads api.json and dispatches +│ ├── langservice.xqm # /api/langservice/* handlers (wraps lang:*) +│ ├── query.xqm # /api/query, /api/eval handlers (wraps cursor:*) +│ ├── db.xqm, dbutils.xqm # /api/db/* — collections, resources, sync +│ ├── users.xqm # /api/users/*, /api/groups/* +│ ├── packages.xqm # /api/packages/* +│ ├── search.xqm, site.xqm # search + cross-app linking +│ └── system.xqm # /api/system/*, /api/test +├── src/main/java/org/exist/xquery/modules/openapi/ +│ ├── langservice/ # Java XQuery functions for lang:* (LangServiceModule) +│ └── cursor/ # Java XQuery functions for cursor:* + CursorStore +├── repo.xml, expath-pkg.xml.tmpl # EXPath package descriptors +├── xar-assembly.xml # Maven plugin XAR assembly config +└── pom.xml # Maven build +``` + +The XQuery handlers in `modules/*.xqm` are thin — they validate request bodies +and call into the Java-backed `lang:*` / `cursor:*` functions for the actual +work. The split keeps performance-sensitive paths (parsing, cursor storage, +serialization) in Java while leaving the URL routing in XQuery where it's +easier to evolve. + +### Running tests + +```bash +# Unit / XQSuite tests (none yet — placeholder) +mvn test + +# Cypress browser tests against a running eXist +npm install +npm test +``` + +--- + +## Versioning and compatibility + +Pre-1.0 releases (`0.9.x`) are **not** API-stable; breaking changes may land +between minor versions as we converge on the 1.0 contract. Once 1.0 ships: + +- The OpenAPI surface at `/api/*` becomes the stable contract. +- Breaking changes require a major version bump. +- The shape-tightening work (real `Range` end-positions, hierarchical + `DocumentSymbol[]`, `MarkupContent` on hover, positional completions) tracked + in [#16](https://github.com/eXist-db/existdb-openapi/issues/16) is targeted + for 1.0. + +A real LSP wire-protocol transport (JSON-RPC, LSP-over-WebSocket) is +**explicitly out of scope** for 1.0 — a separate process (e.g., +[existdb-langserver](https://github.com/wolfgangmm/existdb-langserver)) +consumes the REST API and translates to LSP for editor clients. See +[#7](https://github.com/eXist-db/existdb-openapi/issues/7). + +--- + +## Consumers + +Known clients of this API (each may be in flight or already merged): + +- **[eXide](https://github.com/eXist-db/eXide)** (browser-based XQuery IDE) — + uses `/api/langservice/*` for diagnostics, completions, hover, definition, + references, and `/api/query/*` for paginated query results. +- **[existdb-langserver](https://github.com/wolfgangmm/existdb-langserver)** + (VS Code / Atom Language Server Protocol server) — same `/api/langservice/*` + + `/api/query/*` surface, translated to LSP/JSON-RPC for editor clients. +- **[monex](https://github.com/eXist-db/monex)** (monitoring app) — + WebSocket-based monitoring + cursor-paginated query console. +- **[notebook](https://github.com/joewiz/notebook)** (Jupyter-style XQuery + notebook) — completions + hover via `/api/langservice/*`. + +--- + +## Contributing + +Issues and pull requests welcome at +[github.com/eXist-db/existdb-openapi](https://github.com/eXist-db/existdb-openapi). + +Open issues coordinating the road to 1.0: + +- [#2](https://github.com/eXist-db/existdb-openapi/issues/2) — Initial release tracker +- [#7](https://github.com/eXist-db/existdb-openapi/issues/7) — Langservice / LSP scope (closed; superseded by #15/#16/#17) +- [#15](https://github.com/eXist-db/existdb-openapi/issues/15) — `lsp` → `langservice` rename (this PR) +- [#16](https://github.com/eXist-db/existdb-openapi/issues/16) — Tighten REST shapes to LSP 3.17 isomorphism +- [#17](https://github.com/eXist-db/existdb-openapi/issues/17) — `/api/langservice/capabilities` endpoint +- [#13](https://github.com/eXist-db/existdb-openapi/issues/13) — Repo rename (closed; renamed from `exist-api`) + +--- + +## History + +This project absorbs and replaces the prior standalone `exist-lsp` package. +The XQuery namespace and URL prefix were originally `lsp` / `/api/lsp/*`; both +were renamed to `langservice` / `lang` (for language-service endpoints) plus a +new `cursor` namespace (for paginated query execution) once it became clear +that this surface is *inspired by* LSP types but does not implement the LSP +wire protocol. The repo itself was renamed from `exist-api` to +`existdb-openapi` to name what it actually is — an OpenAPI-described HTTP +surface — without conflicting with eXist's several other "API" identifiers. +See [`MIGRATION.md`](MIGRATION.md) for the rename details. + +--- + +## License + +GNU Lesser General Public License, version 2.1 (or later). See [`LICENSE`](LICENSE).