@volter/twin-pinecone 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.
@@ -0,0 +1,96 @@
1
+ // Pinecone CONNECTOR — the live-vendor pull path over an INJECTED client (the auth boundary; a
2
+ // fake in tests, the real `@pinecone-database/pinecone` SDK in prod). The pack imports NO SDK and
3
+ // holds NO key at runtime — only this file's *type* shapes describe what a real client call would
4
+ // return; the actual SDK is a devDependency imported ONLY in *.test.ts (architecture.test.ts).
5
+ //
6
+ // PULL (real -> twin): fetch real indexes + vectors via the injected client, map them to
7
+ // SyncResource[], and fold them into the event log via `syncPull` (shadow-diff dedup, so a
8
+ // re-pull of identical state is a no-op). Kernel subject ids are TYPE-PREFIXED to stay
9
+ // collision-safe (mirrors pinecone-twin.ts).
10
+ //
11
+ // Vectors are genuinely HANDLE-DRIVEN, the same honest shape fal's connector uses (see fal-
12
+ // connector.ts / pull-audit.json's fal notes): Pinecone's real SDK has NO cross-index "list every
13
+ // vector in my project" endpoint — `Index.listPaginated()` is scoped to one already-known index +
14
+ // namespace. So `PineconeLikeClient.listVectors` is this connector's OWN pull-source shape (a
15
+ // real production implementation loops `pc.listIndexes()` then `pc.index(name).listPaginated()`
16
+ // per namespace and flattens the result into exactly this shape) rather than a literal single
17
+ // vendor endpoint — same honesty framing as fal's `syncFalFromReal`.
18
+ //
19
+ // A pulled index is re-minted a LOCAL data-plane host (`mintHost`) rather than keeping whatever
20
+ // host the real vendor reported — the twin's own data-plane router only ever recognizes its own
21
+ // minted hosts (pinecone-twin.ts's `recoverIndexFromHost`), so a pulled index must get one too to
22
+ // stay queryable through this twin.
23
+ import { syncPull } from '@volter/world-core';
24
+ import { mintHost } from "./pinecone-twin.js";
25
+ //
26
+ // ── The client-side RATE BUDGET is not optional here ────────────────────────────────────────
27
+ // Pinecone publishes a detailed DATA-plane limits table but NO row for the CONTROL-plane listIndexes
28
+ // this connector calls, and `listVectors` here is a cross-index, per-namespace paginated aggregation
29
+ // rather than one vendor endpoint.
30
+ // Every entrypoint below GUARDS the injected client before touching it (`guardPineconeClient`, which is
31
+ // idempotent — a caller who already wrapped is not double-charged, a caller who forgot is protected
32
+ // anyway); there is deliberately no option that turns the budget off. See pinecone-budget.ts.
33
+ import { pineconeBudgetOf, guardPineconeClient } from "./pinecone-budget.js";
34
+ const SERVICE = 'pinecone';
35
+ function indexKey(name) {
36
+ return `index:${name}`;
37
+ }
38
+ function vectorKey(index, namespace, id) {
39
+ return `vector:${index}::${namespace}::${id}`;
40
+ }
41
+ // ── PULL mappers (real Pinecone object → SyncResource) ─────────────────────────────────────
42
+ export function mapIndex(m) {
43
+ return {
44
+ type: 'index',
45
+ id: indexKey(m.name),
46
+ fields: {
47
+ name: m.name,
48
+ dimension: m.dimension ?? null,
49
+ metric: m.metric ?? 'cosine',
50
+ vectorType: m.vectorType ?? 'dense',
51
+ spec: m.spec ?? { serverless: { cloud: 'aws', region: 'us-east-1' } },
52
+ deletionProtection: m.deletionProtection ?? 'disabled',
53
+ tags: m.tags ?? {},
54
+ host: mintHost(m.name),
55
+ ready: true,
56
+ state: 'Ready',
57
+ },
58
+ };
59
+ }
60
+ export function mapVector(v) {
61
+ const namespace = v.namespace ?? '';
62
+ return {
63
+ type: 'vector',
64
+ id: vectorKey(v.index, namespace, v.id),
65
+ fields: { values: v.values ?? [], metadata: v.metadata ?? {} },
66
+ };
67
+ }
68
+ export async function pullPineconeIndexes(rawClient, opts = {}) {
69
+ const client = guardPineconeClient(rawClient, pineconeBudgetOf(opts));
70
+ if (!client.listIndexes)
71
+ return [];
72
+ return (await client.listIndexes()).indexes.map((m) => mapIndex(m));
73
+ }
74
+ export async function pullPineconeVectors(rawClient, opts = {}) {
75
+ const client = guardPineconeClient(rawClient, opts);
76
+ if (!client.listVectors)
77
+ return [];
78
+ return (await client.listVectors()).vectors.map(mapVector);
79
+ }
80
+ /**
81
+ * D7 entry point: pull from real Pinecone (indexes + vectors) and fold EVERY domain into the twin
82
+ * via ONE syncPull (shadow-diff dedup). Returns { observed, deltasAppended } — a re-pull of
83
+ * identical state appends ZERO deltas (observed stays, deltasAppended drops to 0).
84
+ */
85
+ export async function syncPineconeFromReal(rawClient, opts = {}) {
86
+ // Guard ONCE here and hand the guarded client down: this is the entrypoint whose vector
87
+ // aggregation fans out, so it is the one that must be unable to run unbudgeted.
88
+ const client = guardPineconeClient(rawClient, pineconeBudgetOf(opts));
89
+ const occurredAt = opts.occurredAt ?? new Date().toISOString();
90
+ const resources = [
91
+ ...(await pullPineconeIndexes(client, opts)),
92
+ ...(await pullPineconeVectors(client)),
93
+ ];
94
+ const result = syncPull({ service: SERVICE, resources, occurredAt, ...(opts.root !== undefined ? { root: opts.root } : {}) });
95
+ return { observed: result.observed, deltasAppended: result.deltasAppended };
96
+ }
@@ -0,0 +1,3 @@
1
+ export type PineconeFilter = Record<string, unknown>;
2
+ /** Evaluate a Pinecone metadata filter (Mongo-style) against a stored metadata object. An absent/empty filter matches everything. */
3
+ export declare function matchesFilter(metadata: Record<string, unknown> | undefined, filter: PineconeFilter | undefined | null): boolean;
@@ -0,0 +1,69 @@
1
+ const COMPARISON_OPERATORS = new Set(['$eq', '$ne', '$gt', '$gte', '$lt', '$lte', '$in', '$nin']);
2
+ function isPlainObject(v) {
3
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
4
+ }
5
+ function valuesEqual(a, b) {
6
+ if (Array.isArray(a) && Array.isArray(b)) {
7
+ return a.length === b.length && a.every((x, i) => valuesEqual(x, b[i]));
8
+ }
9
+ return a === b;
10
+ }
11
+ function evalComparison(op, fieldValue, operand) {
12
+ switch (op) {
13
+ case '$eq':
14
+ return valuesEqual(fieldValue, operand);
15
+ case '$ne':
16
+ return !valuesEqual(fieldValue, operand);
17
+ case '$in':
18
+ return Array.isArray(operand) && operand.some((o) => valuesEqual(fieldValue, o));
19
+ case '$nin':
20
+ return Array.isArray(operand) && !operand.some((o) => valuesEqual(fieldValue, o));
21
+ case '$gt':
22
+ case '$gte':
23
+ case '$lt':
24
+ case '$lte': {
25
+ if (typeof fieldValue !== 'number' || typeof operand !== 'number')
26
+ return false;
27
+ if (op === '$gt')
28
+ return fieldValue > operand;
29
+ if (op === '$gte')
30
+ return fieldValue >= operand;
31
+ if (op === '$lt')
32
+ return fieldValue < operand;
33
+ return fieldValue <= operand;
34
+ }
35
+ default:
36
+ return false;
37
+ }
38
+ }
39
+ /** Evaluate a single field's clause: either an operator object `{$gt: 5}` or a bare `$eq` literal. */
40
+ function evalFieldClause(fieldValue, clause) {
41
+ if (isPlainObject(clause)) {
42
+ const keys = Object.keys(clause);
43
+ if (keys.length > 0 && keys.every((k) => COMPARISON_OPERATORS.has(k))) {
44
+ return keys.every((op) => evalComparison(op, fieldValue, clause[op]));
45
+ }
46
+ }
47
+ return valuesEqual(fieldValue, clause); // bare-literal shorthand for $eq
48
+ }
49
+ /** Evaluate a Pinecone metadata filter (Mongo-style) against a stored metadata object. An absent/empty filter matches everything. */
50
+ export function matchesFilter(metadata, filter) {
51
+ if (!filter || Object.keys(filter).length === 0)
52
+ return true;
53
+ const md = metadata ?? {};
54
+ for (const [key, clause] of Object.entries(filter)) {
55
+ if (key === '$and') {
56
+ if (!Array.isArray(clause) || !clause.every((c) => matchesFilter(md, c)))
57
+ return false;
58
+ continue;
59
+ }
60
+ if (key === '$or') {
61
+ if (!Array.isArray(clause) || clause.length === 0 || !clause.some((c) => matchesFilter(md, c)))
62
+ return false;
63
+ continue;
64
+ }
65
+ if (!evalFieldClause(md[key], clause))
66
+ return false;
67
+ }
68
+ return true;
69
+ }
@@ -0,0 +1,14 @@
1
+ /** Options every Pinecone-twin HTTP surface needs, independent of who owns the socket. */
2
+ export interface PineconeTwinFetchOptions {
3
+ root?: string;
4
+ readOnly?: boolean;
5
+ }
6
+ export declare function createPineconeTwinFetch(options: PineconeTwinFetchOptions): (request: Request) => Promise<Response>;
7
+ export declare function createPineconeTwinServer(options?: {
8
+ root?: string;
9
+ port?: number;
10
+ readOnly?: boolean;
11
+ }): Promise<{
12
+ port: number;
13
+ stop: () => void;
14
+ }>;
@@ -0,0 +1,67 @@
1
+ // Pinecone twin HTTP server — ONE server hosting BOTH the control plane (api.pinecone.io) and
2
+ // every index's data plane (a per-index minted host, see pinecone-twin.ts's `mintHost`), so an
3
+ // unmodified `@pinecone-database/pinecone` SDK works end-to-end against a single local process.
4
+ //
5
+ // THE ROUTING TRICK (verified against the installed 8.0.0 package's own source — pinecone-sdk.
6
+ // integration.test.ts's header has the full trail): `PineconeConfiguration.fetchApi` is a
7
+ // genuine full-fetch override — BOTH `indexOperationsBuilder` (control plane) and
8
+ // `VectorOperationsProvider`/etc (data plane) read `getFetch(config)`, which prefers
9
+ // `config.fetchApi` over the ambient global `fetch` — so ONE override at `new Pinecone({
10
+ // fetchApi })` covers every request the SDK makes, control AND data plane alike. The twin's own
11
+ // `mintHost()` output is a synthetic, non-resolvable hostname; a caller (real SDK or otherwise)
12
+ // that supplies a `fetchApi` rewriting the URL's host to this server's real `127.0.0.1:<port>`
13
+ // while stashing the ORIGINAL host in the `x-pinecone-target-host` header (exactly what
14
+ // pinecone-sdk.integration.test.ts's `fetchApi` does) reaches this ONE process for every call —
15
+ // the real network is never touched, keeping every capability verify offline + deterministic.
16
+ // Without that header (a plain caller hitting this server directly), the server falls back to
17
+ // the literal HTTP `Host` header, so a caller who already points requests straight at THIS
18
+ // server's address (e.g. `controllerHostUrl: 'http://127.0.0.1:<port>'` for the control plane,
19
+ // `pc.index({ name, host: 'http://127.0.0.1:<port>' })` for the data plane — both real,
20
+ // documented SDK config fields) also works without any header trick at all; the twin's own
21
+ // `pinecone-twin.test.ts` HTTP-server tests drive it exactly that way.
22
+ //
23
+ // Writable by default; pass readOnly to reject writes (per-operation — see pinecone-twin.ts).
24
+ //
25
+ // FETCH-FIRST (runtime contract R12b): the surface is the plain `createPineconeTwinFetch` and the
26
+ // SERVER is one line of `Bun.serve` around it. This is a CUSTOM fetch, not the kernel adapter
27
+ // (`createTwinFetchFromHandler`): the 202/204 replies are body-suppressed by STATUS whatever the
28
+ // handler returned, which the adapter's body-shaped rule would change.
29
+ import { serveHttp } from '@volter/world-core';
30
+ import { handlePineconeTwinRequest } from "./pinecone-twin.js";
31
+ import { worldNow, statefulTwinManifest } from '@volter/world-core';
32
+ export function createPineconeTwinFetch(options) {
33
+ const readOnly = options.readOnly ?? false;
34
+ return async function pineconeTwinFetch(request) {
35
+ const url = new URL(request.url);
36
+ // GET /twin — the discovery manifest (education inside the twin).
37
+ if (request.method === 'GET' && url.pathname.replace(/\/+$/, '') === '/twin') {
38
+ return Response.json(statefulTwinManifest({ vendor: 'pinecone', twinOf: 'the Pinecone vector database', stores: 'indexes, collections and vectors, searched by a real brute-force scorer' }));
39
+ }
40
+ const headers = {};
41
+ request.headers.forEach((value, key) => { headers[key.toLowerCase()] = value; });
42
+ const host = headers['x-pinecone-target-host'] || headers['host'] || url.host;
43
+ const body = request.method !== 'GET' && request.method !== 'HEAD' ? await request.text() : '';
44
+ const { status, body: out, headers: outHeaders } = await handlePineconeTwinRequest({
45
+ method: request.method,
46
+ path: url.pathname + (url.search || ''),
47
+ body,
48
+ headers,
49
+ host,
50
+ readOnly,
51
+ occurredAt: worldNow(),
52
+ ...(options.root !== undefined ? { root: options.root } : {}),
53
+ });
54
+ if (status === 202 || status === 204 || out === null) {
55
+ return new Response(null, { status, headers: { ...(outHeaders ?? {}) } });
56
+ }
57
+ return new Response(JSON.stringify(out), { status, headers: { 'content-type': 'application/json', ...(outHeaders ?? {}) } });
58
+ };
59
+ }
60
+ export async function createPineconeTwinServer(options = {}) {
61
+ const server = await serveHttp({
62
+ port: options.port ?? 0,
63
+ idleTimeout: 60,
64
+ fetch: createPineconeTwinFetch(options),
65
+ });
66
+ return { port: server.port ?? options.port ?? 0, stop: () => server.stop(true) };
67
+ }
@@ -0,0 +1,15 @@
1
+ export type PineconeMetric = 'cosine' | 'dotproduct' | 'euclidean';
2
+ export declare function dotProduct(a: readonly number[], b: readonly number[]): number;
3
+ export declare function magnitude(v: readonly number[]): number;
4
+ /** Cosine similarity in [-1, 1]; 0 if either vector has zero magnitude (undefined direction). */
5
+ export declare function cosineSimilarity(a: readonly number[], b: readonly number[]): number;
6
+ /** Squared L2 distance (sum of squared differences). Mismatched lengths pad the shorter with 0. */
7
+ export declare function squaredEuclideanDistance(a: readonly number[], b: readonly number[]): number;
8
+ /** Score one candidate against a query vector for the given metric. */
9
+ export declare function scoreVector(metric: PineconeMetric, query: readonly number[], candidate: readonly number[]): number;
10
+ /** True when a HIGHER score means more similar (false only for euclidean: lower/ascending). */
11
+ export declare function higherIsBetter(metric: PineconeMetric): boolean;
12
+ /** Sort scored candidates best-match-first for the given metric (stable). */
13
+ export declare function sortByScore<T extends {
14
+ score: number;
15
+ }>(metric: PineconeMetric, scored: readonly T[]): T[];
@@ -0,0 +1,48 @@
1
+ export function dotProduct(a, b) {
2
+ const n = Math.min(a.length, b.length);
3
+ let sum = 0;
4
+ for (let i = 0; i < n; i++)
5
+ sum += a[i] * b[i];
6
+ return sum;
7
+ }
8
+ export function magnitude(v) {
9
+ let sum = 0;
10
+ for (const x of v)
11
+ sum += x * x;
12
+ return Math.sqrt(sum);
13
+ }
14
+ /** Cosine similarity in [-1, 1]; 0 if either vector has zero magnitude (undefined direction). */
15
+ export function cosineSimilarity(a, b) {
16
+ const magA = magnitude(a);
17
+ const magB = magnitude(b);
18
+ if (magA === 0 || magB === 0)
19
+ return 0;
20
+ return dotProduct(a, b) / (magA * magB);
21
+ }
22
+ /** Squared L2 distance (sum of squared differences). Mismatched lengths pad the shorter with 0. */
23
+ export function squaredEuclideanDistance(a, b) {
24
+ const n = Math.max(a.length, b.length);
25
+ let sum = 0;
26
+ for (let i = 0; i < n; i++) {
27
+ const d = (a[i] ?? 0) - (b[i] ?? 0);
28
+ sum += d * d;
29
+ }
30
+ return sum;
31
+ }
32
+ /** Score one candidate against a query vector for the given metric. */
33
+ export function scoreVector(metric, query, candidate) {
34
+ if (metric === 'cosine')
35
+ return cosineSimilarity(query, candidate);
36
+ if (metric === 'dotproduct')
37
+ return dotProduct(query, candidate);
38
+ return squaredEuclideanDistance(query, candidate);
39
+ }
40
+ /** True when a HIGHER score means more similar (false only for euclidean: lower/ascending). */
41
+ export function higherIsBetter(metric) {
42
+ return metric !== 'euclidean';
43
+ }
44
+ /** Sort scored candidates best-match-first for the given metric (stable). */
45
+ export function sortByScore(metric, scored) {
46
+ const ascending = !higherIsBetter(metric);
47
+ return [...scored].sort((a, b) => (ascending ? a.score - b.score : b.score - a.score));
48
+ }
@@ -0,0 +1,52 @@
1
+ export type PineconeRequest = {
2
+ method: string;
3
+ path: string;
4
+ body?: string;
5
+ headers?: Record<string, string>;
6
+ /** Effective request host (real Host header, or the SDK-integration-test's stashed original
7
+ * target host — see pinecone-server.ts). Drives routePineconeSurface + data-plane index
8
+ * recovery. */
9
+ host?: string;
10
+ /** Explicit data-plane index-name override — the twin's OWN verifies use this directly instead
11
+ * of round-tripping a minted host through recoverIndexFromHost (spec §6's documented fallback). */
12
+ index?: string;
13
+ occurredAt?: string;
14
+ root?: string;
15
+ readOnly?: boolean;
16
+ };
17
+ export type PineconeResponse = {
18
+ status: number;
19
+ body: unknown;
20
+ headers?: Record<string, string>;
21
+ };
22
+ export declare const PINECONE_RESOURCE_TYPES: readonly ["index", "collection", "vector"];
23
+ export type PineconeResourceType = typeof PINECONE_RESOURCE_TYPES[number];
24
+ /** Deterministic per-INDEX synthetic data-plane host — stable across calls, never a real
25
+ * resolvable DNS name (this twin is only ever reached via pinecone-server.ts's fetchApi-level
26
+ * redirect, so real-network resolvability is never required — see file header).
27
+ *
28
+ * R9 (serve-path determinism) AT THE RESOURCE LEVEL: the minted host is a function of the INDEX
29
+ * NAME and of nothing else. It deliberately does NOT hash the world `root`: the root is a
30
+ * filesystem PATH, so folding it in made `IndexModel.host` — a field every list/describe serves
31
+ * and every SDK dials — a function of WHERE the world happens to live, and two identical worlds
32
+ * under different directories served different hosts (found 2026-09-02 by the R9 replay sweep,
33
+ * the algolia `mintObjectId` defect in its second form). Uniqueness per root was never the
34
+ * property that mattered: a host only ever has to round-trip back to its index name through
35
+ * `recoverIndexFromHost` within ONE world, and index names are already unique within one. */
36
+ export declare function mintHost(indexName: string): string;
37
+ /** Recover an index name from a minted data-plane host (strip scheme/port, then the known
38
+ * suffix and trailing `-<hash8>`). Returns undefined for the control host or an unrecognized one. */
39
+ export declare function recoverIndexFromHost(host: string | undefined): string | undefined;
40
+ export type PineconeSurface = 'control' | 'data';
41
+ /** Decide control- vs data-plane purely from the effective host (path is a fallback for the
42
+ * twin's own verifies that don't bother setting a host at all). */
43
+ export declare function routePineconeSurface(req: {
44
+ host?: string;
45
+ path: string;
46
+ }): PineconeSurface;
47
+ export declare function handlePineconeTwinRequest(req: PineconeRequest): Promise<PineconeResponse>;
48
+ export type PineconeTwinSnapshot = {
49
+ resourceTypes: readonly PineconeResourceType[];
50
+ implementedEndpoints: readonly string[];
51
+ };
52
+ export declare function pineconeTwinSnapshot(): PineconeTwinSnapshot;