spicedb-embedded 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +146 -0
- package/dist/embedded.d.ts +45 -0
- package/dist/embedded.d.ts.map +1 -0
- package/dist/embedded.js +94 -0
- package/dist/embedded.js.map +1 -0
- package/dist/ffi.d.ts +27 -0
- package/dist/ffi.d.ts.map +1 -0
- package/dist/ffi.js +82 -0
- package/dist/ffi.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/tcp_channel.d.ts +6 -0
- package/dist/tcp_channel.d.ts.map +1 -0
- package/dist/tcp_channel.js +8 -0
- package/dist/tcp_channel.js.map +1 -0
- package/dist/unix_socket_channel.d.ts +6 -0
- package/dist/unix_socket_channel.d.ts.map +1 -0
- package/dist/unix_socket_channel.js +8 -0
- package/dist/unix_socket_channel.js.map +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# spicedb-embedded (Node.js)
|
|
2
|
+
|
|
3
|
+
Embedded [SpiceDB](https://authzed.com/spicedb) for Node.js — authorization server for tests and development. Uses the shared/c C library via koffi FFI and connects over gRPC via Unix sockets or TCP. Supports memory (default), postgres, cockroachdb, spanner, and mysql datastores. **Node.js only** (not for browser/frontend).
|
|
4
|
+
|
|
5
|
+
## Comparison with SpiceDB WASM
|
|
6
|
+
|
|
7
|
+
SpiceDB also provides a [WebAssembly (WASM) build](https://authzed.com/blog/some-assembly-required) used in the [Authzed Playground](https://play.authzed.com). This Node.js implementation differs in several important ways:
|
|
8
|
+
|
|
9
|
+
| Aspect | **spicedb-embedded** (this package) | **SpiceDB WASM** |
|
|
10
|
+
| ---------------- | -------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
11
|
+
| **Runtime** | Node.js only (native addon) | Browser + Node.js (via WASM) |
|
|
12
|
+
| **Architecture** | Spawns real SpiceDB server via C/CGO FFI; full gRPC over Unix socket | Compiled Go→WASM; callback-based development API |
|
|
13
|
+
| **API** | Full SpiceDB gRPC API (Permissions, Schema, Watch, etc.) | Development API only (check, validate, run operations) |
|
|
14
|
+
| **Use case** | Server-side tests, development, local tooling | Browser Playground, client-side validation |
|
|
15
|
+
| **Performance** | Native SpiceDB; full caching, production-grade | No Ristretto cache; simplified for WASM |
|
|
16
|
+
| **Distribution** | Requires platform-specific `libspicedb.so`/`.dylib` | Single `.wasm` file (~25MB) |
|
|
17
|
+
|
|
18
|
+
**When to use this package:** Node.js applications that need an embedded SpiceDB for integration tests, local development, or tooling. You get the real SpiceDB implementation with the full API.
|
|
19
|
+
|
|
20
|
+
**When to use WASM:** Browser-based tools (e.g., schema playground) or when you need a single portable binary.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install spicedb-embedded
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or from source:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
cd node && npm install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Prerequisites:** Go 1.23+ with CGO enabled. Build the shared library first:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd shared/c && CGO_ENABLED=1 go build -buildmode=c-shared -o libspicedb.dylib . # macOS
|
|
38
|
+
cd shared/c && CGO_ENABLED=1 go build -buildmode=c-shared -o libspicedb.so . # Linux
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The library looks for `libspicedb.dylib` (macOS) or `libspicedb.so` (Linux) in `SPICEDB_LIBRARY_PATH` or relative to the working directory. Override with `SPICEDB_LIBRARY_PATH=/path/to/shared/c`.
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { v1, EmbeddedSpiceDB } from "spicedb-embedded";
|
|
47
|
+
|
|
48
|
+
const {
|
|
49
|
+
ObjectReference,
|
|
50
|
+
Relationship,
|
|
51
|
+
SubjectReference,
|
|
52
|
+
CheckPermissionRequest,
|
|
53
|
+
Consistency,
|
|
54
|
+
CheckPermissionResponse,
|
|
55
|
+
CheckPermissionResponse_Permissionship,
|
|
56
|
+
} = v1;
|
|
57
|
+
|
|
58
|
+
const schema = `
|
|
59
|
+
definition user {}
|
|
60
|
+
|
|
61
|
+
definition document {
|
|
62
|
+
relation reader: user
|
|
63
|
+
permission read = reader
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
const rel = Relationship.create({
|
|
68
|
+
resource: ObjectReference.create({
|
|
69
|
+
objectType: "document",
|
|
70
|
+
objectId: "readme",
|
|
71
|
+
}),
|
|
72
|
+
relation: "reader",
|
|
73
|
+
subject: SubjectReference.create({
|
|
74
|
+
object: ObjectReference.create({ objectType: "user", objectId: "alice" }),
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const spicedb = await EmbeddedSpiceDB.create(schema, [rel]);
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const response = await spicedb.permissions().promises.checkPermission(
|
|
82
|
+
CheckPermissionRequest.create({
|
|
83
|
+
consistency: Consistency.create({
|
|
84
|
+
requirement: { oneofKind: "fullyConsistent", fullyConsistent: true },
|
|
85
|
+
}),
|
|
86
|
+
resource: ObjectReference.create({
|
|
87
|
+
objectType: "document",
|
|
88
|
+
objectId: "readme",
|
|
89
|
+
}),
|
|
90
|
+
permission: "read",
|
|
91
|
+
subject: SubjectReference.create({
|
|
92
|
+
object: ObjectReference.create({
|
|
93
|
+
objectType: "user",
|
|
94
|
+
objectId: "alice",
|
|
95
|
+
}),
|
|
96
|
+
}),
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const allowed =
|
|
101
|
+
response.permissionship ===
|
|
102
|
+
CheckPermissionResponse_Permissionship.HAS_PERMISSION;
|
|
103
|
+
} finally {
|
|
104
|
+
spicedb.close();
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## API
|
|
109
|
+
|
|
110
|
+
- **`EmbeddedSpiceDB.create(schema, relationships, options?)`** — Create an instance (async). Pass `[]` for no initial relationships. Pass `SpiceDBStartOptions` for datastore/transport config.
|
|
111
|
+
- **`permissions()`** — Permissions service client (CheckPermission, WriteRelationships, ReadRelationships, etc.). Use `.promises` for async/await.
|
|
112
|
+
- **`schema()`** — Schema service client (ReadSchema, WriteSchema, ReflectSchema, etc.).
|
|
113
|
+
- **`watch()`** — Watch service client for relationship changes.
|
|
114
|
+
- **`close()`** — Dispose the instance and close the channel.
|
|
115
|
+
|
|
116
|
+
Use types from `v1` (re-exported from `@authzed/authzed-node`) for `ObjectReference`, `SubjectReference`, `Relationship`, etc.
|
|
117
|
+
|
|
118
|
+
### SpiceDBStartOptions
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { EmbeddedSpiceDB, SpiceDBStartOptions } from "spicedb-embedded";
|
|
122
|
+
|
|
123
|
+
const options: SpiceDBStartOptions = {
|
|
124
|
+
datastore: "memory", // or "postgres", "cockroachdb", "spanner", "mysql"
|
|
125
|
+
grpc_transport: "unix", // or "tcp"; default by platform
|
|
126
|
+
datastore_uri: "postgres://user:pass@localhost:5432/spicedb", // required for remote
|
|
127
|
+
spanner_credentials_file: "/path/to/key.json", // Spanner only
|
|
128
|
+
spanner_emulator_host: "localhost:9010", // Spanner emulator
|
|
129
|
+
mysql_table_prefix: "spicedb_", // MySQL only (optional)
|
|
130
|
+
metrics_enabled: false, // default; set true to enable Prometheus metrics
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const spicedb = await EmbeddedSpiceDB.create(schema, [], options);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Building & Testing
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
mise run shared-c-build
|
|
140
|
+
cd node
|
|
141
|
+
npm install
|
|
142
|
+
npm run build
|
|
143
|
+
npm test
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Or from the repo root: `mise run node-test`
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedded SpiceDB instance.
|
|
3
|
+
*
|
|
4
|
+
* A thin wrapper that starts SpiceDB via the C-shared library, connects over a Unix
|
|
5
|
+
* socket, and bootstraps schema and relationships via gRPC.
|
|
6
|
+
*
|
|
7
|
+
* Use permissions(), schema(), and watch() to access the full SpiceDB API.
|
|
8
|
+
*/
|
|
9
|
+
import { v1 } from "@authzed/authzed-node";
|
|
10
|
+
import { type SpiceDBStartOptions } from "./ffi.js";
|
|
11
|
+
export declare class SpiceDBError extends Error {
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
|
14
|
+
export declare class EmbeddedSpiceDB {
|
|
15
|
+
private readonly handle;
|
|
16
|
+
private readonly client;
|
|
17
|
+
private constructor();
|
|
18
|
+
/**
|
|
19
|
+
* Create a new embedded SpiceDB instance with a schema and relationships.
|
|
20
|
+
*
|
|
21
|
+
* @param schema - The SpiceDB schema definition (ZED language)
|
|
22
|
+
* @param relationships - Initial relationships (empty array allowed)
|
|
23
|
+
* @param options - Optional configuration (datastore, grpc_transport). Omit for defaults.
|
|
24
|
+
* @returns New EmbeddedSpiceDB instance
|
|
25
|
+
*/
|
|
26
|
+
static create(schema: string, relationships?: v1.Relationship[], options?: SpiceDBStartOptions | null): Promise<EmbeddedSpiceDB>;
|
|
27
|
+
private bootstrap;
|
|
28
|
+
/**
|
|
29
|
+
* Permissions service client (CheckPermission, WriteRelationships, ReadRelationships, etc.)
|
|
30
|
+
*/
|
|
31
|
+
permissions(): v1.ZedClientInterface;
|
|
32
|
+
/**
|
|
33
|
+
* Schema service client (ReadSchema, WriteSchema, ReflectSchema, etc.)
|
|
34
|
+
*/
|
|
35
|
+
schema(): v1.ZedClientInterface;
|
|
36
|
+
/**
|
|
37
|
+
* Watch service client (watch for relationship changes)
|
|
38
|
+
*/
|
|
39
|
+
watch(): v1.ZedClientInterface;
|
|
40
|
+
/**
|
|
41
|
+
* Dispose the instance and close the channel.
|
|
42
|
+
*/
|
|
43
|
+
close(): void;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=embedded.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedded.d.ts","sourceRoot":"","sources":["../src/embedded.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,EAAE,EAAE,MAAM,uBAAuB,CAAC;AAC3C,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,UAAU,CAAC;AAYlB,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;IAE/C,OAAO;IAKP;;;;;;;OAOG;WACU,MAAM,CACjB,MAAM,EAAE,MAAM,EACd,aAAa,GAAE,EAAE,CAAC,YAAY,EAAO,EACrC,OAAO,CAAC,EAAE,mBAAmB,GAAG,IAAI,GACnC,OAAO,CAAC,eAAe,CAAC;YAyBb,SAAS;IAqBvB;;OAEG;IACH,WAAW,IAAI,EAAE,CAAC,kBAAkB;IAIpC;;OAEG;IACH,MAAM,IAAI,EAAE,CAAC,kBAAkB;IAI/B;;OAEG;IACH,KAAK,IAAI,EAAE,CAAC,kBAAkB;IAI9B;;OAEG;IACH,KAAK,IAAI,IAAI;CAId"}
|
package/dist/embedded.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedded SpiceDB instance.
|
|
3
|
+
*
|
|
4
|
+
* A thin wrapper that starts SpiceDB via the C-shared library, connects over a Unix
|
|
5
|
+
* socket, and bootstraps schema and relationships via gRPC.
|
|
6
|
+
*
|
|
7
|
+
* Use permissions(), schema(), and watch() to access the full SpiceDB API.
|
|
8
|
+
*/
|
|
9
|
+
import * as grpc from "@grpc/grpc-js";
|
|
10
|
+
import { v1 } from "@authzed/authzed-node";
|
|
11
|
+
import { spicedb_dispose, spicedb_start, } from "./ffi.js";
|
|
12
|
+
import { getTarget as getTcpTarget } from "./tcp_channel.js";
|
|
13
|
+
import { getTarget as getUnixSocketTarget } from "./unix_socket_channel.js";
|
|
14
|
+
const { NewClientWithChannelCredentials, RelationshipUpdate, RelationshipUpdate_Operation, WriteRelationshipsRequest, WriteSchemaRequest, } = v1;
|
|
15
|
+
export class SpiceDBError extends Error {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "SpiceDBError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class EmbeddedSpiceDB {
|
|
22
|
+
handle;
|
|
23
|
+
client;
|
|
24
|
+
constructor(handle, client) {
|
|
25
|
+
this.handle = handle;
|
|
26
|
+
this.client = client;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Create a new embedded SpiceDB instance with a schema and relationships.
|
|
30
|
+
*
|
|
31
|
+
* @param schema - The SpiceDB schema definition (ZED language)
|
|
32
|
+
* @param relationships - Initial relationships (empty array allowed)
|
|
33
|
+
* @param options - Optional configuration (datastore, grpc_transport). Omit for defaults.
|
|
34
|
+
* @returns New EmbeddedSpiceDB instance
|
|
35
|
+
*/
|
|
36
|
+
static async create(schema, relationships = [], options) {
|
|
37
|
+
const data = spicedb_start(options ?? undefined);
|
|
38
|
+
const { handle, grpc_transport, address } = data;
|
|
39
|
+
const target = grpc_transport === "unix"
|
|
40
|
+
? getUnixSocketTarget(address)
|
|
41
|
+
: getTcpTarget(address);
|
|
42
|
+
const creds = grpc.credentials.createInsecure();
|
|
43
|
+
const client = NewClientWithChannelCredentials(target, creds);
|
|
44
|
+
const db = new EmbeddedSpiceDB(handle, client);
|
|
45
|
+
try {
|
|
46
|
+
await db.bootstrap(schema, relationships);
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
db.close();
|
|
50
|
+
throw new SpiceDBError(`Failed to bootstrap: ${e instanceof Error ? e.message : String(e)}`);
|
|
51
|
+
}
|
|
52
|
+
return db;
|
|
53
|
+
}
|
|
54
|
+
async bootstrap(schema, relationships) {
|
|
55
|
+
const schemaRequest = WriteSchemaRequest.create({ schema });
|
|
56
|
+
await this.client.promises.writeSchema(schemaRequest);
|
|
57
|
+
if (relationships.length > 0) {
|
|
58
|
+
const updates = relationships.map((r) => RelationshipUpdate.create({
|
|
59
|
+
operation: RelationshipUpdate_Operation.TOUCH,
|
|
60
|
+
relationship: r,
|
|
61
|
+
}));
|
|
62
|
+
const writeRequest = WriteRelationshipsRequest.create({
|
|
63
|
+
updates,
|
|
64
|
+
});
|
|
65
|
+
await this.client.promises.writeRelationships(writeRequest);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Permissions service client (CheckPermission, WriteRelationships, ReadRelationships, etc.)
|
|
70
|
+
*/
|
|
71
|
+
permissions() {
|
|
72
|
+
return this.client;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Schema service client (ReadSchema, WriteSchema, ReflectSchema, etc.)
|
|
76
|
+
*/
|
|
77
|
+
schema() {
|
|
78
|
+
return this.client;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Watch service client (watch for relationship changes)
|
|
82
|
+
*/
|
|
83
|
+
watch() {
|
|
84
|
+
return this.client;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Dispose the instance and close the channel.
|
|
88
|
+
*/
|
|
89
|
+
close() {
|
|
90
|
+
this.client.close();
|
|
91
|
+
spicedb_dispose(this.handle);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=embedded.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedded.js","sourceRoot":"","sources":["../src/embedded.ts"],"names":[],"mappings":"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"}
|
package/dist/ffi.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FFI bindings to the SpiceDB C-shared library (shared/c).
|
|
3
|
+
*/
|
|
4
|
+
export interface SpiceDBStartResult {
|
|
5
|
+
handle: number;
|
|
6
|
+
grpc_transport: "unix" | "tcp";
|
|
7
|
+
address: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SpiceDBStartOptions {
|
|
10
|
+
/** Datastore: "memory" (default), "postgres", "cockroachdb", "spanner", "mysql" */
|
|
11
|
+
datastore?: string;
|
|
12
|
+
/** Connection string for remote datastores */
|
|
13
|
+
datastore_uri?: string;
|
|
14
|
+
/** gRPC transport: "unix" (default on Unix), "tcp" (default on Windows) */
|
|
15
|
+
grpc_transport?: string;
|
|
16
|
+
/** Path to Spanner service account JSON (Spanner only) */
|
|
17
|
+
spanner_credentials_file?: string;
|
|
18
|
+
/** Spanner emulator host (Spanner only) */
|
|
19
|
+
spanner_emulator_host?: string;
|
|
20
|
+
/** Prefix for all tables (MySQL only) */
|
|
21
|
+
mysql_table_prefix?: string;
|
|
22
|
+
/** Enable datastore Prometheus metrics (default: false; disabled allows multiple instances in same process) */
|
|
23
|
+
metrics_enabled?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function spicedb_start(options?: SpiceDBStartOptions | null): SpiceDBStartResult;
|
|
26
|
+
export declare function spicedb_dispose(handle: number): void;
|
|
27
|
+
//# sourceMappingURL=ffi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffi.d.ts","sourceRoot":"","sources":["../src/ffi.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,GAAG,KAAK,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,2CAA2C;IAC3C,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,yCAAyC;IACzC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,+GAA+G;IAC/G,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA8ED,wBAAgB,aAAa,CAC3B,OAAO,CAAC,EAAE,mBAAmB,GAAG,IAAI,GACnC,kBAAkB,CAapB;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAOpD"}
|
package/dist/ffi.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FFI bindings to the SpiceDB C-shared library (shared/c).
|
|
3
|
+
*/
|
|
4
|
+
import koffi from "koffi";
|
|
5
|
+
import { accessSync } from "fs";
|
|
6
|
+
import { platform } from "os";
|
|
7
|
+
import { resolve } from "path";
|
|
8
|
+
function findLibrary() {
|
|
9
|
+
const currentPlatform = platform();
|
|
10
|
+
const libName = currentPlatform === "win32"
|
|
11
|
+
? "spicedb.dll"
|
|
12
|
+
: currentPlatform === "darwin"
|
|
13
|
+
? "libspicedb.dylib"
|
|
14
|
+
: "libspicedb.so";
|
|
15
|
+
const explicit = process.env.SPICEDB_LIBRARY_PATH;
|
|
16
|
+
if (explicit) {
|
|
17
|
+
const libExtensions = [".so", ".dylib", ".dll"];
|
|
18
|
+
const candidate = libExtensions.some((e) => explicit.endsWith(e))
|
|
19
|
+
? explicit
|
|
20
|
+
: resolve(explicit, libName);
|
|
21
|
+
try {
|
|
22
|
+
accessSync(candidate);
|
|
23
|
+
return candidate;
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
const message = err instanceof Error && err.message ? err.message : String(err);
|
|
27
|
+
throw new Error(`Unable to access SpiceDB library at '${candidate}' specified by SPICEDB_LIBRARY_PATH: ${message}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const searchPaths = [
|
|
31
|
+
"shared/c",
|
|
32
|
+
resolve(process.cwd(), "shared/c"),
|
|
33
|
+
resolve(process.cwd(), "../shared/c"),
|
|
34
|
+
resolve(process.cwd(), "node/../shared/c"),
|
|
35
|
+
];
|
|
36
|
+
for (const base of searchPaths) {
|
|
37
|
+
const candidate = resolve(base, libName);
|
|
38
|
+
try {
|
|
39
|
+
accessSync(candidate);
|
|
40
|
+
return candidate;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// continue
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return libName;
|
|
47
|
+
}
|
|
48
|
+
let lib = null;
|
|
49
|
+
function getLib() {
|
|
50
|
+
if (lib)
|
|
51
|
+
return lib;
|
|
52
|
+
const path = findLibrary();
|
|
53
|
+
const loaded = koffi.load(path);
|
|
54
|
+
const spicedb_free = loaded.func("void spicedb_free(char* ptr)");
|
|
55
|
+
koffi.disposable("HeapStr", "str", (ptr) => spicedb_free(ptr));
|
|
56
|
+
lib = {
|
|
57
|
+
spicedb_start: loaded.func("HeapStr spicedb_start(const char* optionsJson)"),
|
|
58
|
+
spicedb_dispose: loaded.func("HeapStr spicedb_dispose(unsigned long long handle)"),
|
|
59
|
+
};
|
|
60
|
+
return lib;
|
|
61
|
+
}
|
|
62
|
+
export function spicedb_start(options) {
|
|
63
|
+
const l = getLib();
|
|
64
|
+
const optionsJson = options != null ? JSON.stringify(options) : null;
|
|
65
|
+
const raw = l.spicedb_start(optionsJson);
|
|
66
|
+
if (!raw)
|
|
67
|
+
throw new Error("Null response from C library");
|
|
68
|
+
const data = JSON.parse(raw);
|
|
69
|
+
if (!data.success) {
|
|
70
|
+
throw new Error(data.error ?? "Unknown error");
|
|
71
|
+
}
|
|
72
|
+
return data.data;
|
|
73
|
+
}
|
|
74
|
+
export function spicedb_dispose(handle) {
|
|
75
|
+
const l = getLib();
|
|
76
|
+
const raw = l.spicedb_dispose(handle);
|
|
77
|
+
const data = JSON.parse(raw);
|
|
78
|
+
if (!data.success) {
|
|
79
|
+
throw new Error(data.error ?? "Unknown error");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=ffi.js.map
|
package/dist/ffi.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffi.js","sourceRoot":"","sources":["../src/ffi.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedded SpiceDB for TypeScript/Node.js — in-memory authorization server for tests and development.
|
|
3
|
+
*/
|
|
4
|
+
export { EmbeddedSpiceDB, SpiceDBError } from "./embedded.js";
|
|
5
|
+
export type { SpiceDBStartOptions, SpiceDBStartResult } from "./ffi.js";
|
|
6
|
+
export { v1 } from "@authzed/authzed-node";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC9D,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAGxE,OAAO,EAAE,EAAE,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedded SpiceDB for TypeScript/Node.js — in-memory authorization server for tests and development.
|
|
3
|
+
*/
|
|
4
|
+
export { EmbeddedSpiceDB, SpiceDBError } from "./embedded.js";
|
|
5
|
+
// Re-export authzed v1 for types and client creation
|
|
6
|
+
export { v1 } from "@authzed/authzed-node";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG9D,qDAAqD;AACrD,OAAO,EAAE,EAAE,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tcp_channel.d.ts","sourceRoot":"","sources":["../src/tcp_channel.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,kFAAkF;AAClF,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tcp_channel.js","sourceRoot":"","sources":["../src/tcp_channel.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,kFAAkF;AAClF,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gRPC target for Unix domain socket connections.
|
|
3
|
+
*/
|
|
4
|
+
/** Returns the gRPC target string for a Unix domain socket (e.g. unix:///tmp/spicedb-123.sock). */
|
|
5
|
+
export declare function getTarget(address: string): string;
|
|
6
|
+
//# sourceMappingURL=unix_socket_channel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unix_socket_channel.d.ts","sourceRoot":"","sources":["../src/unix_socket_channel.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,mGAAmG;AACnG,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjD"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gRPC target for Unix domain socket connections.
|
|
3
|
+
*/
|
|
4
|
+
/** Returns the gRPC target string for a Unix domain socket (e.g. unix:///tmp/spicedb-123.sock). */
|
|
5
|
+
export function getTarget(address) {
|
|
6
|
+
return `unix://${address}`;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=unix_socket_channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unix_socket_channel.js","sourceRoot":"","sources":["../src/unix_socket_channel.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,mGAAmG;AACnG,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO,UAAU,OAAO,EAAE,CAAC;AAC7B,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "spicedb-embedded",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Embedded SpiceDB for TypeScript/Node.js — in-memory authorization using the standard gRPC API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"format": "prettier --write .",
|
|
23
|
+
"format:check": "prettier --check .",
|
|
24
|
+
"lint": "npm run format:check"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@authzed/authzed-node": "^1.5.0",
|
|
28
|
+
"@grpc/grpc-js": "^1.14.3",
|
|
29
|
+
"koffi": "^2.9.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^22.0.0",
|
|
33
|
+
"eslint": "^9.0.0",
|
|
34
|
+
"prettier": "^3.4.0",
|
|
35
|
+
"typescript": "^5.7.0",
|
|
36
|
+
"vitest": "^2.0.0"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.18.0"
|
|
40
|
+
},
|
|
41
|
+
"license": "Apache-2.0",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/borkfork/spicedb-embedded.git"
|
|
45
|
+
}
|
|
46
|
+
}
|