@crowkis/client 0.5.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 ADDED
@@ -0,0 +1,156 @@
1
+ <div align="center">
2
+
3
+ <img src="https://raw.githubusercontent.com/crowkis/crowkis-sdk/main/assets/banner.png" alt="Crowkis — the intelligent cache & memory for LLM apps, built in Rust" width="100%" />
4
+
5
+ <p>
6
+ <a href="https://www.npmjs.com/package/@crowkis/client"><img src="https://img.shields.io/npm/v/@crowkis/client?color=d62221&label=npm&logo=npm&logoColor=white" alt="npm" /></a>
7
+ <img src="https://img.shields.io/badge/TypeScript-included-d62221?logo=typescript&logoColor=white" alt="TypeScript" />
8
+ <a href="https://hub.docker.com/r/crowkis/crowkis"><img src="https://img.shields.io/docker/pulls/crowkis/crowkis?color=d62221&label=Docker&logo=docker&logoColor=white" alt="Docker" /></a>
9
+ <img src="https://img.shields.io/badge/License-Apache_2.0-d62221" alt="Apache 2.0" />
10
+ </p>
11
+
12
+ <p>
13
+ <a href="https://www.crowkis.com"><b>Website</b></a> &nbsp;·&nbsp;
14
+ <a href="https://www.crowkis.com/docs/sdk-node"><b>Documentation</b></a> &nbsp;·&nbsp;
15
+ <a href="https://hub.docker.com/r/crowkis/crowkis"><b>Docker Hub</b></a>
16
+ </p>
17
+
18
+ </div>
19
+
20
+ ## About Crowkis
21
+
22
+ Every LLM app quietly pays the same bill twice. Users ask the same questions worded a
23
+ hundred different ways, and each rewording is billed at full price. **Crowkis is an
24
+ intelligent, Redis-compatible cache and memory layer that sits between your app and your
25
+ model** — it recognises when a new question *means* the same as one it has already
26
+ answered, and serves that answer instantly, for free.
27
+
28
+ It is **model-agnostic**: you wrap the call you already make — to OpenAI, Anthropic, a
29
+ local model, or whatever comes next — and Crowkis handles the rest. It also gives your
30
+ agents **durable, semantic memory** that survives restarts and stays strictly isolated
31
+ per tenant. The engine is written in Rust and ships as a single small container.
32
+
33
+ This is the official **Node / TypeScript SDK**, with typings included.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ npm install @crowkis/client
39
+ ```
40
+
41
+ ## Run a Crowkis server
42
+
43
+ ```bash
44
+ docker run -d -p 6383:6383 -v "$(pwd)/.crow:/data/.crow" \
45
+ crowkis/crowkis:latest server --data /data/.crow
46
+ ```
47
+
48
+ The server listens on `6383` (cache/RESP), `6384` (dashboard/HTTP), and `6385` (gRPC).
49
+ Image → [hub.docker.com/r/crowkis/crowkis](https://hub.docker.com/r/crowkis/crowkis).
50
+
51
+ ## Cache any model — the wrapper
52
+
53
+ Wrap any async function whose first argument is the prompt. It matches on **meaning**, so
54
+ rephrased prompts hit too. The body can call *any* provider.
55
+
56
+ ```ts
57
+ import { Crowkis } from "@crowkis/client";
58
+
59
+ const cache = new Crowkis({ tenant: "my-app" });
60
+
61
+ const answer = cache.cached(async (prompt: string) => myModel(prompt), { ttl: 3600 });
62
+
63
+ await answer("How do refunds work?"); // miss → your model runs, result cached
64
+ await answer("What's the refund process?"); // semantic HIT → no model call, instant
65
+ ```
66
+
67
+ Prefer an inline call?
68
+
69
+ ```ts
70
+ const text = await cache.ask("How do refunds work?", async (p) => myModel(p), { ttl: 3600 });
71
+ ```
72
+
73
+ ## Read & write directly
74
+
75
+ ```ts
76
+ const hit = await cache.lookup("what's the refund timeline?");
77
+ if (hit) {
78
+ console.log(hit.text, hit.similarity, hit.confidence);
79
+ } else {
80
+ await cache.store("what's the refund timeline?", "5–7 business days.", { ttl: 3600 });
81
+ }
82
+ ```
83
+
84
+ ## Streaming
85
+
86
+ ```ts
87
+ for await (const chunk of cache.stream("Explain vector caches", async (p) => myStream(p), { ttl: 3600 })) {
88
+ process.stdout.write(typeof chunk === "string" ? chunk : chunk.toString());
89
+ }
90
+ ```
91
+
92
+ ## LangChain.js
93
+
94
+ ```ts
95
+ import { CrowkisCache } from "@crowkis/client/langchain";
96
+ import { OpenAI } from "@langchain/openai";
97
+
98
+ const llm = new OpenAI({ cache: new CrowkisCache({ tenant: "my-app", ttl: 3600 }) });
99
+ ```
100
+
101
+ ## Agent memory (LangGraph.js or any loop)
102
+
103
+ Durable, semantic, per-user memory. Every recall is scoped to its agent and user.
104
+
105
+ ```ts
106
+ import { CrowkisMemory } from "@crowkis/client/memory";
107
+
108
+ const mem = new CrowkisMemory("support-bot", { user: "alice" });
109
+ await mem.remember("Alice prefers email over phone");
110
+ await mem.recall("how should I contact Alice?"); // semantic recall
111
+ ```
112
+
113
+ ## Authentication
114
+
115
+ If your server sets an auth token (`CROWKIS_AUTH_TOKEN`), pass it from your environment —
116
+ never hard-code it. Keep it in a gitignored `.env`.
117
+
118
+ ```ts
119
+ const cache = new Crowkis({
120
+ host: process.env.CROWKIS_HOST ?? "127.0.0.1",
121
+ port: Number(process.env.CROWKIS_PORT ?? 6383),
122
+ tenant: "my-app",
123
+ authToken: process.env.CROWKIS_TOKEN, // from .env, not the code
124
+ });
125
+ ```
126
+
127
+ ## Method reference
128
+
129
+ | Group | Methods |
130
+ |-------|---------|
131
+ | **Caching** | `cached()` · `ask()` · `stream()` · `lookup()` · `store()` · `similar()` · `embed()` · `flush()` |
132
+ | **Agent memory** | `cmemset` · `cmemget` · `cmemextract` · `cmemhistory` · `cmemforget` · `cmemlink` · `cmemgraph` |
133
+ | **Sessions / docs / pins / tools** | `csession*` · `cdoc*` · `cpin*` · `ctool*` |
134
+ | **Safety / cost / compliance** | `cguard` · `coutcheck` · `cbudget*` · `ckeylimit*` · `cpii*` |
135
+ | **Evals / prompts / freshness / ops** | `ceval` · `cprompt*` · `csource*` · `cscan` · `csave` · `compact` |
136
+
137
+ Full reference and guides at **[www.crowkis.com/docs/sdk-node](https://www.crowkis.com/docs/sdk-node)**.
138
+
139
+ ## Contributing
140
+
141
+ Issues and pull requests are welcome at
142
+ [github.com/crowkis/crowkis-sdk](https://github.com/crowkis/crowkis-sdk). The client is
143
+ Apache-2.0 licensed and safe to fork, embed, and ship.
144
+
145
+ <br/>
146
+
147
+ <table>
148
+ <tr>
149
+ <td width="96"><img src="https://raw.githubusercontent.com/crowkis/crowkis-sdk/main/assets/founder.jpg" width="84" alt="Mohit Rohilla" /></td>
150
+ <td>
151
+ Built with care by <b><a href="https://github.com/itsmohitrohilla">Mohit Rohilla</a></b>, founder & creator of Crowkis — engineering the intelligent cache & memory layer for the agentic era, in Rust. 🦀
152
+ </td>
153
+ </tr>
154
+ </table>
155
+
156
+ <sub>© 2026 Crowkis · Licensed under Apache-2.0 · <a href="https://www.crowkis.com">crowkis.com</a></sub>
package/crowkis.proto ADDED
@@ -0,0 +1,76 @@
1
+ syntax = "proto3";
2
+
3
+ package crowkis.v1;
4
+
5
+ service CrowkisCache {
6
+ rpc Get(GetRequest) returns (GetResponse);
7
+ rpc Set(SetRequest) returns (SetResponse);
8
+ rpc GetStream(GetRequest) returns (stream StreamChunk);
9
+ rpc Stats(Empty) returns (StatsResponse);
10
+ rpc Invalidate(InvalidateRequest) returns (InvalidateResponse);
11
+ }
12
+
13
+ message Empty {}
14
+
15
+ message GetRequest {
16
+ bytes query = 1;
17
+ string tenant = 2;
18
+ string model = 3;
19
+ float threshold = 4;
20
+ uint32 chunk_tokens = 5;
21
+ uint32 delay_ms = 6;
22
+ string format = 7;
23
+ bytes image = 8;
24
+ }
25
+
26
+ message GetResponse {
27
+ bool hit = 1;
28
+ bytes response = 2;
29
+ float similarity = 3;
30
+ int64 ttl_remaining = 4;
31
+ bytes matched_key = 5;
32
+ float confidence = 6;
33
+ string hit_type = 7;
34
+ }
35
+
36
+ message SetRequest {
37
+ bytes query = 1;
38
+ bytes response = 2;
39
+ uint64 ttl_secs = 3;
40
+ string tenant = 4;
41
+ string model = 5;
42
+ bytes image = 6;
43
+ }
44
+
45
+ message SetResponse {
46
+ bool ok = 1;
47
+ string error = 2;
48
+ }
49
+
50
+ message StreamChunk {
51
+ bytes delta = 1;
52
+ bool is_final = 2;
53
+ string format = 3;
54
+ }
55
+
56
+ message StatsResponse {
57
+ uint64 total_requests = 1;
58
+ uint64 total_hits = 2;
59
+ uint64 total_misses = 3;
60
+ double hit_rate_pct = 4;
61
+ uint64 tokens_saved = 5;
62
+ double avg_confidence = 6;
63
+ double avg_latency_ms = 7;
64
+ uint64 vector_index_entries = 8;
65
+ uint64 memtable_bytes = 9;
66
+ uint64 memtable_keys = 10;
67
+ }
68
+
69
+ message InvalidateRequest {
70
+ string tenant = 1;
71
+ }
72
+
73
+ message InvalidateResponse {
74
+ bool ok = 1;
75
+ uint64 removed = 2;
76
+ }
package/grpc.js ADDED
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+
3
+ const path = require("node:path");
4
+
5
+ function loadCrowkisGrpc(options = {}) {
6
+ let grpc;
7
+ let protoLoader;
8
+ try {
9
+ grpc = require("@grpc/grpc-js");
10
+ protoLoader = require("@grpc/proto-loader");
11
+ } catch (error) {
12
+ throw new Error("Crowkis gRPC support requires npm packages @grpc/grpc-js and @grpc/proto-loader");
13
+ }
14
+
15
+ const protoPath = options.protoPath || path.join(__dirname, "crowkis.proto");
16
+ const definition = protoLoader.loadSync(protoPath, {
17
+ defaults: true,
18
+ enums: String,
19
+ keepCase: false,
20
+ longs: Number,
21
+ oneofs: true,
22
+ });
23
+ return grpc.loadPackageDefinition(definition).crowkis.v1;
24
+ }
25
+
26
+ class CrowkisGrpcClient {
27
+ constructor(options = {}) {
28
+ const target = options.target || "127.0.0.1:6381";
29
+ const api = loadCrowkisGrpc(options);
30
+ let credentials;
31
+ if (options.credentials) {
32
+ credentials = options.credentials;
33
+ } else {
34
+ const grpc = require("@grpc/grpc-js");
35
+ credentials = grpc.credentials.createInsecure();
36
+ }
37
+ this.client = new api.CrowkisCache(target, credentials);
38
+ this.authToken = options.authToken;
39
+ }
40
+
41
+ metadata() {
42
+ if (!this.authToken) return undefined;
43
+ const grpc = require("@grpc/grpc-js");
44
+ const metadata = new grpc.Metadata();
45
+ metadata.set("x-crowkis-auth-token", this.authToken);
46
+ return metadata;
47
+ }
48
+
49
+ get(query, options = {}) {
50
+ return unary(this.client, "Get", request(query, options), this.metadata());
51
+ }
52
+
53
+ set(query, response, options = {}) {
54
+ return unary(
55
+ this.client,
56
+ "Set",
57
+ {
58
+ query: toBuffer(query),
59
+ response: toBuffer(response),
60
+ ttlSecs: Number(options.ttlSecs || options.ttl || 0),
61
+ tenant: options.tenant || "",
62
+ model: options.model || "",
63
+ image: options.image ? toBuffer(options.image) : Buffer.alloc(0),
64
+ },
65
+ this.metadata(),
66
+ );
67
+ }
68
+
69
+ getStream(query, options = {}) {
70
+ return this.client.GetStream(request(query, options), this.metadata());
71
+ }
72
+
73
+ stats() {
74
+ return unary(this.client, "Stats", {}, this.metadata());
75
+ }
76
+
77
+ invalidate(options = {}) {
78
+ return unary(this.client, "Invalidate", { tenant: options.tenant || "" }, this.metadata());
79
+ }
80
+
81
+ close() {
82
+ this.client.close();
83
+ }
84
+ }
85
+
86
+ function unary(client, method, payload, metadata) {
87
+ return new Promise((resolve, reject) => {
88
+ const callback = (error, response) => (error ? reject(error) : resolve(response));
89
+ if (metadata) client[method](payload, metadata, callback);
90
+ else client[method](payload, callback);
91
+ });
92
+ }
93
+
94
+ function request(query, options = {}) {
95
+ const payload = {
96
+ query: toBuffer(query),
97
+ tenant: options.tenant || "",
98
+ model: options.model || "",
99
+ format: options.format || "",
100
+ };
101
+ if (options.threshold !== undefined) payload.threshold = Number(options.threshold);
102
+ if (options.chunkTokens !== undefined || options.chunk_tokens !== undefined) {
103
+ payload.chunkTokens = Number(options.chunkTokens ?? options.chunk_tokens);
104
+ }
105
+ if (options.delayMs !== undefined || options.delay_ms !== undefined) {
106
+ payload.delayMs = Number(options.delayMs ?? options.delay_ms);
107
+ }
108
+ if (options.image) payload.image = toBuffer(options.image);
109
+ return payload;
110
+ }
111
+
112
+ function toBuffer(value) {
113
+ if (Buffer.isBuffer(value)) return value;
114
+ if (value instanceof Uint8Array) return Buffer.from(value);
115
+ return Buffer.from(String(value));
116
+ }
117
+
118
+ module.exports = {
119
+ CrowkisGrpcClient,
120
+ loadCrowkisGrpc,
121
+ };
package/index.d.ts ADDED
@@ -0,0 +1,142 @@
1
+ export interface CrowkisClientOptions {
2
+ host?: string;
3
+ port?: number;
4
+ tenant?: string;
5
+ model?: string;
6
+ authToken?: string;
7
+ auth_token?: string;
8
+ timeoutMs?: number;
9
+ }
10
+
11
+ export interface CacheSetOptions {
12
+ ttl?: number;
13
+ tenant?: string;
14
+ model?: string;
15
+ modelVersion?: string;
16
+ model_version?: string;
17
+ image?: string | Buffer | Uint8Array;
18
+ }
19
+
20
+ export interface CacheGetOptions {
21
+ threshold?: number;
22
+ tenant?: string;
23
+ model?: string;
24
+ modelVersion?: string;
25
+ model_version?: string;
26
+ migrationMode?: "miss" | "refresh" | "recompute" | "force_miss" | "force-miss" | string;
27
+ migration_mode?: "miss" | "refresh" | "recompute" | "force_miss" | "force-miss" | string;
28
+ image?: string | Buffer | Uint8Array;
29
+ }
30
+
31
+ export interface StreamCacheOptions extends CacheGetOptions, CacheSetOptions {
32
+ chunkTokens?: number;
33
+ delayMs?: number;
34
+ }
35
+
36
+ export interface CacheHit {
37
+ response: Buffer;
38
+ text: string;
39
+ similarity: number;
40
+ ttlRemaining: number;
41
+ matchedKey: Buffer;
42
+ confidence: number;
43
+ hitType: string;
44
+ migrationPending: boolean;
45
+ migrationFromModelVersion: string | null;
46
+ migrationTargetModelVersion: string | null;
47
+ migrationCanaryId: string | null;
48
+ migrationPlannedAt: number | null;
49
+ }
50
+
51
+ export interface SimResult {
52
+ key: Buffer;
53
+ similarity: number;
54
+ }
55
+
56
+ export class CrowkisClient {
57
+ constructor(options?: CrowkisClientOptions);
58
+ connect(): Promise<void>;
59
+ close(): void;
60
+ execute(...args: Array<string | Buffer | Uint8Array>): Promise<unknown>;
61
+ ping(): Promise<boolean>;
62
+ get(key: string | Buffer | Uint8Array): Promise<Buffer | null>;
63
+ set(key: string | Buffer | Uint8Array, value: string | Buffer | Uint8Array, options?: { ttl?: number }): Promise<void>;
64
+ cset(query: string | Buffer | Uint8Array, response: string | Buffer | Uint8Array, options?: CacheSetOptions): Promise<void>;
65
+ cget(query: string | Buffer | Uint8Array, options?: CacheGetOptions): Promise<Buffer | null>;
66
+ cgetHit(query: string | Buffer | Uint8Array, options?: CacheGetOptions): Promise<CacheHit | null>;
67
+ cimgget(image: string | Buffer | Uint8Array, options?: CacheGetOptions): Promise<Buffer | null>;
68
+ csim(query: string | Buffer | Uint8Array, options?: { k?: number; tenant?: string }): Promise<SimResult[]>;
69
+ cflush(options?: { tenant?: string }): Promise<number>;
70
+ cvecCount(): Promise<number>;
71
+ getOrCompute(
72
+ query: string,
73
+ fn: (query: string) => Promise<string | Buffer | Uint8Array> | string | Buffer | Uint8Array,
74
+ options?: CacheGetOptions & CacheSetOptions,
75
+ ): Promise<string>;
76
+ streamGetOrCompute(
77
+ query: string,
78
+ fn: (
79
+ query: string,
80
+ ) =>
81
+ | AsyncIterable<string | Buffer | Uint8Array>
82
+ | Iterable<string | Buffer | Uint8Array>
83
+ | Promise<AsyncIterable<string | Buffer | Uint8Array> | Iterable<string | Buffer | Uint8Array>>
84
+ | string
85
+ | Buffer
86
+ | Uint8Array,
87
+ options?: StreamCacheOptions,
88
+ ): AsyncIterable<string | Buffer | Uint8Array>;
89
+ }
90
+
91
+ export interface CrowkisAdminOptions {
92
+ baseUrl?: string;
93
+ adminKey?: string;
94
+ timeoutMs?: number;
95
+ }
96
+
97
+ export class CrowkisAdmin {
98
+ constructor(options?: CrowkisAdminOptions);
99
+ health(): Promise<Record<string, unknown>>;
100
+ getStats(): Promise<Record<string, unknown>>;
101
+ updateThreshold(config: Record<string, unknown>): Promise<Record<string, unknown>>;
102
+ registerWebhook(webhook: Record<string, unknown>): Promise<Record<string, unknown>>;
103
+ invalidateSource(sourceId: string, extra?: Record<string, unknown>): Promise<Record<string, unknown>>;
104
+ flushTenant(tenantId: string): Promise<Record<string, unknown>>;
105
+ cacheEntries(options?: { tenant?: string; limit?: number }): Promise<Record<string, unknown>>;
106
+ migrationProgress(options?: {
107
+ tenant?: string;
108
+ canaryId?: string;
109
+ canary_id?: string;
110
+ fromModelVersion?: string;
111
+ from_model_version?: string;
112
+ targetModelVersion?: string;
113
+ target_model_version?: string;
114
+ limit?: number;
115
+ }): Promise<Record<string, unknown>>;
116
+ canaryMigrationProgress(id: string, options?: { tenant?: string; limit?: number }): Promise<Record<string, unknown>>;
117
+ leaseMigrationWork(options?: Record<string, unknown>): Promise<Record<string, unknown>>;
118
+ leaseCanaryMigrationWork(id: string, options?: Record<string, unknown>): Promise<Record<string, unknown>>;
119
+ completeMigrationWork(item: Record<string, unknown>): Promise<Record<string, unknown>>;
120
+ failMigrationWork(item: Record<string, unknown>): Promise<Record<string, unknown>>;
121
+ }
122
+
123
+ export class CrowkisError extends Error {}
124
+
125
+ export interface CrowkisGrpcClientOptions {
126
+ target?: string;
127
+ authToken?: string;
128
+ protoPath?: string;
129
+ credentials?: unknown;
130
+ }
131
+
132
+ export class CrowkisGrpcClient {
133
+ constructor(options?: CrowkisGrpcClientOptions);
134
+ get(query: string | Buffer | Uint8Array, options?: CacheGetOptions & StreamCacheOptions): Promise<Record<string, unknown>>;
135
+ set(query: string | Buffer | Uint8Array, response: string | Buffer | Uint8Array, options?: CacheSetOptions & { ttlSecs?: number }): Promise<Record<string, unknown>>;
136
+ getStream(query: string | Buffer | Uint8Array, options?: StreamCacheOptions): AsyncIterable<Record<string, unknown>>;
137
+ stats(): Promise<Record<string, unknown>>;
138
+ invalidate(options?: { tenant?: string }): Promise<Record<string, unknown>>;
139
+ close(): void;
140
+ }
141
+
142
+ export function loadCrowkisGrpc(options?: { protoPath?: string }): unknown;