{"_id":"@arcadedb/driver-grpc","name":"@arcadedb/driver-grpc","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@arcadedb/driver-grpc","version":"0.1.0","description":"TypeScript/JavaScript gRPC client for ArcadeDB, generated from ArcadeDB's Protobuf contract.","type":"module","sideEffects":false,"license":"Apache-2.0","keywords":["arcadedb","database","graph-database","multi-model","grpc-client"],"homepage":"https://github.com/ArcadeData/arcadedb-drivers/tree/main/typescript/packages/driver-grpc#readme","bugs":{"url":"https://github.com/ArcadeData/arcadedb-drivers/issues"},"repository":{"type":"git","url":"git+https://github.com/ArcadeData/arcadedb-drivers.git","directory":"typescript/packages/driver-grpc"},"engines":{"node":">=20"},"arcadedb":{"serverVersion":"26.9.1"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"main":"./dist/index.js","types":"./dist/index.d.ts","scripts":{"prepack":"tsc --build"},"dependencies":{"@bufbuild/protobuf":"2.14.0","@connectrpc/connect":"2.1.2","@connectrpc/connect-node":"2.1.2"},"_id":"@arcadedb/driver-grpc@0.1.0","gitHead":"3c7ba9d8e04eb331e7cc7aba9ad29962aea3dd1e","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-bYOzn9PBUxZD++V/Fp9c04LarlGbsrqOUzA3IIrY60vYZTfP486E2ZoQEAY6eJoVr51PiAZ5Sg0OUv7/QYon4Q==","shasum":"05e0cd6f97df5164335f73f6ea3ef3d06159c935","tarball":"https://registry.npmjs.org/@arcadedb/driver-grpc/-/driver-grpc-0.1.0.tgz","fileCount":23,"unpackedSize":342228,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arcadedb%2fdriver-grpc@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIBm5PkE8RCR3wCB5oSJBlUaQAhTWUxFq8qefcMTR1ofBAiEAqrbocEC7HMrHDMlaUsMkxXTPCDOYgRQqeTZ14zAeUtE="}]},"_npmUser":{"name":"robfrank","email":"ro.franchini@gmail.com"},"directories":{},"maintainers":[{"name":"robfrank","email":"ro.franchini@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/driver-grpc_0.1.0_1788528943999_0.5465521999100007"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-04T13:35:43.741Z","0.1.0":"2026-09-04T13:35:44.184Z","modified":"2026-09-04T13:35:44.627Z"},"maintainers":[{"name":"robfrank","email":"ro.franchini@gmail.com"}],"description":"TypeScript/JavaScript gRPC client for ArcadeDB, generated from ArcadeDB's Protobuf contract.","homepage":"https://github.com/ArcadeData/arcadedb-drivers/tree/main/typescript/packages/driver-grpc#readme","keywords":["arcadedb","database","graph-database","multi-model","grpc-client"],"repository":{"type":"git","url":"git+https://github.com/ArcadeData/arcadedb-drivers.git","directory":"typescript/packages/driver-grpc"},"bugs":{"url":"https://github.com/ArcadeData/arcadedb-drivers/issues"},"license":"Apache-2.0","readme":"# @arcadedb/driver-grpc\n\nA TypeScript/JavaScript gRPC client for ArcadeDB's data plane, generated from ArcadeDB's Protobuf\ncontract with [Connect-ES](https://connectrpc.com/).\n\n**This package is not yet published to npm**, though the path to publish it now exists:\n`publish.yml` takes a `package` input, and dispatching it with `package=driver-grpc` publishes\nthis one. What it still needs is npm-side setup — a first publish under the `@arcadedb` scope, and\nits own trusted publisher afterwards, neither of which `@arcadedb/driver` having them does for it.\nUntil that runs, consume it from this repository (workspace link or `npm pack`).\n\nIf you want an HTTP client instead - including from a browser - see\n[`@arcadedb/driver`](../driver/README.md).\n\n## Requirements\n\n- Node.js `>=20`, Bun, or Deno. See \"Runtime targets\" below for what that means in practice.\n- ESM only. The package has no CommonJS build and no `require()` entry point; import it with\n  `import`, not `require`.\n\n## Installation\n\n```bash\nnpm install @arcadedb/driver-grpc\n```\n\n## Runtime targets: Node, Bun, Deno - not browsers\n\nThis package targets Node, Bun, and Deno. The underlying transport,\n[`@connectrpc/connect-node`](https://www.npmjs.com/package/@connectrpc/connect-node), is built on\nNode's `node:http2` module, which Bun and Deno both also implement well enough to run it. Node is\nwhat this repository's CI actually exercises today; Bun and Deno are intended targets that have\nnot (yet) got a CI job of their own, so treat them as likely-to-work rather than verified.\n\nThere is no browser build, and there will not be one until the server changes. This is a server\ncapability question, not a packaging one: ArcadeDB's `GrpcServerPlugin` is plain grpc-java over\nHTTP/2, built on Netty's `NettyServerBuilder`, with no gRPC-Web handler, no Connect protocol, and\nno servlet adapter in front of it. A browser cannot speak raw HTTP/2 gRPC framing at all - there\nis no protocol translation layer for it to go through - so no client library, however written,\ncan reach this server from a browser. Anyone who needs a browser client uses\n[`@arcadedb/driver`](../driver/README.md) over HTTP instead.\n\n## Quick start\n\n```ts\nimport { createClient, passwordAuth } from \"@arcadedb/driver-grpc\";\n\nconst grpc = createClient({\n  baseUrl: \"https://localhost:50051\",\n  auth: passwordAuth(\"root\", \"playwithdata\", \"mydb\"),\n});\n\nconst response = await grpc.raw.executeQuery({\n  database: \"mydb\",\n  query: \"SELECT FROM Person WHERE age > 21\",\n  language: \"sql\",\n});\n```\n\n`raw` is the generated Connect client for `ArcadeDbService` (the data plane) - every RPC the\n`.proto` contract declares is callable through it. `createClient` adds three ergonomic wrappers\non top for the RPCs the generated client alone handles badly: `streamQuery`, `insertStream`, and\n`transaction`. Everything else - the unary CRUD calls, `insertBidirectional`, `graphBatchLoad` -\nis used directly through `raw`.\n\n## Authentication\n\nTwo helpers build an `Interceptor` to pass as `auth`:\n\n```ts\nimport { bearerAuth, passwordAuth } from \"@arcadedb/driver-grpc\";\n\nbearerAuth(\"AU-...\"); // sets `authorization: Bearer <token>` metadata\npasswordAuth(\"root\", \"playwithdata\", \"mydb\"); // sets x-arcade-user / x-arcade-password / x-arcade-database metadata\n```\n\n`passwordAuth` sends the password in plaintext gRPC metadata, so `createClient` **refuses** to\npair it with a non-TLS (`http://`) `baseUrl` unless you pass `insecure: true` explicitly:\n\n```ts\ncreateClient({ baseUrl: \"http://localhost:50051\", auth: passwordAuth(\"root\", \"pw\") });\n// throws: refusing to send a plaintext password over insecure baseUrl \"http://localhost:50051\"\n\ncreateClient({ baseUrl: \"http://localhost:50051\", auth: passwordAuth(\"root\", \"pw\"), insecure: true });\n// fine - you opted in\n```\n\nBe aware of the limits of this check: it recognizes only the exact `Interceptor` value\n`passwordAuth` itself returned (via an internal marker on that value), not any interceptor that\nhappens to set the same headers - and not any interceptor other than the one `passwordAuth`\nreturned, including a wrapper around it. Composing `passwordAuth(...)` with another interceptor\n(logging, retry, call-recording) produces a new function value that does not carry the marker, so\nthe refusal is silently skipped even though a real `passwordAuth` password is still being sent in\nplaintext underneath. A hand-rolled interceptor that sets `x-arcade-password` directly is not\ncaught either. This check is a safety net for callers who pass `passwordAuth(...)` straight\nthrough as `auth`, not a general scan of outgoing metadata.\n\n## Streaming queries: `streamQuery`\n\n```ts\nfor await (const row of grpc.streamQuery({\n  database: \"mydb\",\n  query: \"SELECT FROM Person\",\n  language: \"sql\",\n})) {\n  console.log(row.rid, row.properties);\n}\n```\n\n`streamQuery` flattens the server's stream of row batches into one row at a time, so the calling\ncode never has to unwrap `QueryResult.records` itself. That is the only thing it does - it does\nnot choose `retrievalMode` or `batchSize` for you. `retrievalMode` is deliberately the caller's\nchoice, because the three modes the `.proto` contract defines differ materially in memory and\nconsistency behavior:\n\n- `CURSOR` (the default) - runs the query once and streams results as you iterate.\n- `MATERIALIZE_ALL` - loads the entire result set on the server first, then emits it in batches.\n- `PAGED` - re-issues the query with `LIMIT`/`SKIP` per batch.\n\nPick `CURSOR` for a large result set you want to bound memory on; `MATERIALIZE_ALL` when you need\na stable snapshot and can afford to hold it server-side; `PAGED` when you want each batch's\nconsistency independent of the others. This wrapper does not, and should not, guess which one a\ngiven query needs.\n\n## Streaming inserts: `insertStream`\n\n```ts\nasync function* rows() {\n  yield [{ type: \"Person\", properties: { name: { kind: { case: \"stringValue\", value: \"Alice\" } } } }];\n  yield [{ type: \"Person\", properties: { name: { kind: { case: \"stringValue\", value: \"Bob\" } } } }];\n}\n\nconst summary = await grpc.insertStream({\n  database: \"mydb\",\n  options: { targetClass: \"Person\" },\n  chunks: rows(),\n});\n\nconsole.log(summary.inserted, summary.failed);\n```\n\n`chunks` is an `AsyncIterable` of row batches - one element becomes exactly one wire\n`InsertChunk`. The caller decides how many rows go in each batch and when to yield the next one;\n`insertStream` owns only the envelope bookkeeping around those batches, which it would otherwise\nbe easy to get wrong by hand:\n\n- one `session_id` (a fresh UUID), stable for the whole stream\n- `chunk_seq` starting at 1 and incrementing by 1 per chunk\n- `database` set on the first chunk only, per the `.proto` contract\n- `last: true` on the final chunk only\n\nAn empty `chunks` iterable is not an error. A filter that matched nothing is a legitimate reason\nto have zero rows to insert, and this package should not turn that into an exception - the same\nprinciple `@arcadedb/driver`'s README documents for `truncated`. `insertStream` sends a single\nchunk with zero rows and `last: true`, and returns whatever `InsertSummary` the server gives back\nfor it (verified against a real server: this is accepted cleanly, in under 100ms, and comes back\nas an all-zero summary) - it does not invent a summary itself.\n\n### The `InsertOptions.database` workaround\n\n`insertStream` also sets `options.database` to the same value as the first chunk's `database`.\nThis is a compatibility workaround for servers older than the fix for\n[ArcadeData/arcadedb#6597](https://github.com/ArcadeData/arcadedb/issues/6597) (merged in\n`7ccade7348`, not yet in a release as of this writing): `InsertChunk.database` is marked\n`// REQUIRED` on the first chunk in `arcadedb-server.proto`, but on 26.9.1 and every earlier\nrelease the server's `InsertContext` construction only reads `InsertOptions.database` - it never\nlooks at `InsertChunk.database` at all. Without this mirroring, every stream against such a server\nfails at the deferred commit with `Invalid database name: name is required`, even though `database`\nwas sent exactly as the contract specifies. A server carrying the #6597 fix prefers a non-empty\n`InsertChunk.database` and falls back to `InsertOptions.database`, so setting both to the same\nvalue here can never disagree - this mirroring is safe to keep sending even after the fix ships,\nand is what makes `insertStream` work against every server this package supports, fixed or not.\n\n## Transactions: `transaction`\n\n```ts\nconst totalRow = await grpc.transaction(\"mydb\", async (tx) => {\n  await tx.executeCommand({ command: \"INSERT INTO Account SET balance = 100\", language: \"sql\" });\n  const { results } = await tx.executeQuery({ query: \"SELECT sum(balance) as total FROM Account\", language: \"sql\" });\n  return results[0]?.records[0];\n});\n```\n\n`transaction` begins a server-side transaction, hands the callback a `TransactionHandle` whose\ncalls (`executeQuery`, `executeCommand`, `createRecord`, `updateRecord`, `deleteRecord`,\n`lookupByRid`, `streamQuery`) all carry the transaction's id automatically, and ends the\ntransaction on both the success and failure paths: the callback resolving commits, the callback\nthrowing or rejecting rolls back and re-throws the callback's own error. This is the safety net\nagainst forgetting, dropping, or mismatching a transaction id by hand - the exact class of defect\na 2026 gRPC audit found three times in ad hoc transaction code.\n\nA resolved `commitTransaction` call is not, by itself, proof of a commit: the server answers a\ntransaction id it no longer recognises (for example, one reaped after sitting idle past the\nserver's idle timeout) with `success=true, committed=false` and no error status at all. `transaction`\nreads `committed` and throws - including the server's own message - rather than reporting success\nfor a transaction whose writes were silently lost. `beginTransaction`'s response is checked the\nsame way: a missing or blank transaction id throws immediately instead of running the callback\nagainst a handle that would silently auto-commit every statement.\n\n`beginTransaction`, `commitTransaction`, and `rollbackTransaction` are the calls `transaction`\nmanages for you; the `.proto` contract also lets a request carry inline `begin` / `commit` flags\non individual RPCs, so a call can begin or end a transaction as a side effect without a separate\n`BeginTransaction`/`CommitTransaction` round trip. This wrapper deliberately does not wrap those\nflags - they are reachable through `grpc.raw` for callers who want that shape, but `transaction`\nonly ever manages transactions the explicit way.\n\n### `bulkInsert` and `insertStream` cannot join a `transaction()` on this server\n\n`TransactionHandle` deliberately does **not** include `bulkInsert` or `insertStream`. On this\nserver, `ArcadeDbGrpcService#bulkInsert` and `#insertStream` never read the request's transaction\ncontext at all: each builds its own `InsertContext`, which resolves its own `Database` and commits\nindependently, regardless of any `BeginTransaction`/`CommitTransaction`/`RollbackTransaction` the\ncaller issued around it. Binding them into a `TransactionHandle` would silently lie about this:\ntheir writes are **not** part of the transaction, they commit even when the transaction's callback\nthrows, and they survive a rollback. Both remain available outside a transaction -\n`grpc.insertStream`/`grpc.raw.insertStream` and `grpc.raw.bulkInsert` - but never through `tx`. See\n[ArcadeData/arcadedb#6607](https://github.com/ArcadeData/arcadedb/issues/6607), filed against this\ngap; this restriction is removable once that lands server-side.\n\n## The admin service is not a supported path\n\nThe `.proto` contract also defines `ArcadeDbAdminService` (`Ping`, `GetServerInfo`,\n`ListDatabases`, `ExistsDatabase`, `CreateDatabase`, `DropDatabase`, `GetDatabaseInfo`,\n`CreateUser`, `DeleteUser`). `createClient` does not wire up a client for it, and this package\ndoes not export one. There is no deep import that gets you one either: the `exports` map in\n`package.json` exposes only this package's own entry point, so `@arcadedb/driver-grpc/gen/...` is\nnot a reachable path for an installed copy. If you need it, generate your own Connect client\nagainst `contracts/arcadedb-server-<version>.proto` the same way this package's own `raw` client\nis generated - the `.proto` is a plain source file, not something only this package can read.\n\nThe reason is its auth model, not an oversight: every `ArcadeDbAdminService` RPC authenticates\nfrom a `credentials` field inside the request message itself, rather than from gRPC metadata the\nway every data-plane call in this package does. Wrapping it here would mean this package's\n`auth` option meant one thing for `raw` and `streamQuery`/`insertStream`/`transaction`, and\nsomething else again for admin calls. `@arcadedb/driver` already covers the admin service's\nactual job - server discovery and database lifecycle (`listDatabases`, `exists`, create/drop) -\nover HTTP, so there is no gap this package needs to fill.\n\n## Errors: `ConnectError`, not `ArcadeDBError`\n\nA failed call throws Connect's own `ConnectError`, not the `ArcadeDBError` that\n`@arcadedb/driver`'s facade methods throw:\n\n```ts\nimport { ConnectError } from \"@connectrpc/connect\";\n\ntry {\n  await grpc.raw.executeQuery({ database: \"mydb\", query: \"SELECT FROM NoSuchType\", language: \"sql\" });\n} catch (err) {\n  if (err instanceof ConnectError) {\n    console.error(err.code, err.message, err.details);\n  }\n}\n```\n\nThis is a deliberate asymmetry with `@arcadedb/driver`, not an inconsistency to be fixed later.\nThe two transports carry genuinely different error information - a gRPC status code and details\nmessage versus an HTTP status and a JSON error body - and translating one into the other's shape\nwould either drop information or invent fields the underlying transport never provided. Each\nclient surfaces the error its own transport actually gives it.\n\n## Contract version and compatibility\n\nThis package was generated from `contracts/arcadedb-server-26.9.1.proto`, recorded in\n`package.json` as `arcadedb.serverVersion`:\n\n```json\n{\n  \"arcadedb\": {\n    \"serverVersion\": \"26.9.1\"\n  }\n}\n```\n\n| `@arcadedb/driver-grpc` | ArcadeDB server |\n| --- | --- |\n| 0.1.0 | 26.9.1 |\n\nPointing it at a server on a materially different release may work for the RPCs both versions\nshare, but is not tested or supported.\n\n## License\n\nApache-2.0.\n","readmeFilename":"README.md","_rev":"1-dfb25ab46184e3b7f7030acc6c6bebe4"}