agentfootprint 9.2.0 → 9.4.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.
Files changed (183) hide show
  1. package/CLAUDE.md +3 -1
  2. package/dist/adapters/identity/agentcore.js +94 -26
  3. package/dist/adapters/identity/agentcore.js.map +1 -1
  4. package/dist/adapters/llm/BedrockProvider.js +34 -9
  5. package/dist/adapters/llm/BedrockProvider.js.map +1 -1
  6. package/dist/adapters/memory/pgVector.js +731 -0
  7. package/dist/adapters/memory/pgVector.js.map +1 -0
  8. package/dist/adapters/memory/s3Vectors.js +780 -0
  9. package/dist/adapters/memory/s3Vectors.js.map +1 -0
  10. package/dist/adapters/memory/sqliteVector.js +16 -79
  11. package/dist/adapters/memory/sqliteVector.js.map +1 -1
  12. package/dist/adapters/observability/cloudwatch.js +13 -8
  13. package/dist/adapters/observability/cloudwatch.js.map +1 -1
  14. package/dist/adapters/observability/xray.js +13 -8
  15. package/dist/adapters/observability/xray.js.map +1 -1
  16. package/dist/adapters/security/agentcore.js +0 -0
  17. package/dist/adapters/security/agentcore.js.map +1 -1
  18. package/dist/bridge/eventMeta.js +3 -0
  19. package/dist/bridge/eventMeta.js.map +1 -1
  20. package/dist/core/Agent.js +11 -0
  21. package/dist/core/Agent.js.map +1 -1
  22. package/dist/core/agent/stages/toolCalls.js +80 -7
  23. package/dist/core/agent/stages/toolCalls.js.map +1 -1
  24. package/dist/doors/security.js +3 -2
  25. package/dist/doors/security.js.map +1 -1
  26. package/dist/embedders/index.js +240 -76
  27. package/dist/embedders/index.js.map +1 -1
  28. package/dist/esm/adapters/identity/agentcore.d.ts +39 -9
  29. package/dist/esm/adapters/identity/agentcore.js +94 -26
  30. package/dist/esm/adapters/identity/agentcore.js.map +1 -1
  31. package/dist/esm/adapters/llm/BedrockProvider.d.ts +24 -0
  32. package/dist/esm/adapters/llm/BedrockProvider.js +34 -9
  33. package/dist/esm/adapters/llm/BedrockProvider.js.map +1 -1
  34. package/dist/esm/adapters/memory/pgVector.d.ts +243 -0
  35. package/dist/esm/adapters/memory/pgVector.js +726 -0
  36. package/dist/esm/adapters/memory/pgVector.js.map +1 -0
  37. package/dist/esm/adapters/memory/s3Vectors.d.ts +240 -0
  38. package/dist/esm/adapters/memory/s3Vectors.js +776 -0
  39. package/dist/esm/adapters/memory/s3Vectors.js.map +1 -0
  40. package/dist/esm/adapters/memory/sqliteVector.d.ts +5 -27
  41. package/dist/esm/adapters/memory/sqliteVector.js +10 -73
  42. package/dist/esm/adapters/memory/sqliteVector.js.map +1 -1
  43. package/dist/esm/adapters/observability/cloudwatch.d.ts +20 -0
  44. package/dist/esm/adapters/observability/cloudwatch.js +13 -8
  45. package/dist/esm/adapters/observability/cloudwatch.js.map +1 -1
  46. package/dist/esm/adapters/observability/xray.d.ts +18 -0
  47. package/dist/esm/adapters/observability/xray.js +13 -8
  48. package/dist/esm/adapters/observability/xray.js.map +1 -1
  49. package/dist/esm/adapters/security/agentcore.d.ts +81 -107
  50. package/dist/esm/adapters/security/agentcore.js +0 -0
  51. package/dist/esm/adapters/security/agentcore.js.map +1 -1
  52. package/dist/esm/bridge/eventMeta.d.ts +3 -0
  53. package/dist/esm/bridge/eventMeta.js +3 -0
  54. package/dist/esm/bridge/eventMeta.js.map +1 -1
  55. package/dist/esm/core/Agent.d.ts +16 -0
  56. package/dist/esm/core/Agent.js +11 -0
  57. package/dist/esm/core/Agent.js.map +1 -1
  58. package/dist/esm/core/agent/stages/toolCalls.js +80 -7
  59. package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
  60. package/dist/esm/doors/security.d.ts +3 -2
  61. package/dist/esm/doors/security.js +3 -2
  62. package/dist/esm/doors/security.js.map +1 -1
  63. package/dist/esm/embedders/index.d.ts +99 -19
  64. package/dist/esm/embedders/index.js +240 -76
  65. package/dist/esm/embedders/index.js.map +1 -1
  66. package/dist/esm/events/dispatcher.d.ts +1 -1
  67. package/dist/esm/events/dispatcher.js.map +1 -1
  68. package/dist/esm/events/payloads.d.ts +23 -1
  69. package/dist/esm/events/types.d.ts +17 -0
  70. package/dist/esm/hosting/standingAgent.js +14 -1
  71. package/dist/esm/hosting/standingAgent.js.map +1 -1
  72. package/dist/esm/lib/embedderMismatch.d.ts +67 -0
  73. package/dist/esm/lib/embedderMismatch.js +96 -0
  74. package/dist/esm/lib/embedderMismatch.js.map +1 -0
  75. package/dist/esm/lib/rag/defineRAG.d.ts +28 -6
  76. package/dist/esm/lib/rag/defineRAG.js +39 -5
  77. package/dist/esm/lib/rag/defineRAG.js.map +1 -1
  78. package/dist/esm/memory/define.js +31 -1
  79. package/dist/esm/memory/define.js.map +1 -1
  80. package/dist/esm/memory/define.types.d.ts +13 -2
  81. package/dist/esm/memory/define.types.js.map +1 -1
  82. package/dist/esm/memory/embedding/loadRelevant.d.ts +10 -2
  83. package/dist/esm/memory/embedding/loadRelevant.js +36 -18
  84. package/dist/esm/memory/embedding/loadRelevant.js.map +1 -1
  85. package/dist/esm/memory/pipeline/semantic.d.ts +13 -2
  86. package/dist/esm/memory/pipeline/semantic.js +11 -5
  87. package/dist/esm/memory/pipeline/semantic.js.map +1 -1
  88. package/dist/esm/memory/store/capability.d.ts +25 -6
  89. package/dist/esm/memory/store/capability.js +68 -3
  90. package/dist/esm/memory/store/capability.js.map +1 -1
  91. package/dist/esm/memory/store/index.d.ts +1 -0
  92. package/dist/esm/memory/store/index.js +4 -0
  93. package/dist/esm/memory/store/index.js.map +1 -1
  94. package/dist/esm/memory/store/types.d.ts +40 -0
  95. package/dist/esm/memory-providers.d.ts +7 -4
  96. package/dist/esm/memory-providers.js +18 -4
  97. package/dist/esm/memory-providers.js.map +1 -1
  98. package/dist/esm/recorders/core/CredentialRecorder.d.ts +30 -0
  99. package/dist/esm/recorders/core/CredentialRecorder.js +35 -0
  100. package/dist/esm/recorders/core/CredentialRecorder.js.map +1 -0
  101. package/dist/esm/security/index.d.ts +7 -3
  102. package/dist/esm/security/index.js +12 -7
  103. package/dist/esm/security/index.js.map +1 -1
  104. package/dist/events/dispatcher.js.map +1 -1
  105. package/dist/hosting/standingAgent.js +14 -1
  106. package/dist/hosting/standingAgent.js.map +1 -1
  107. package/dist/lib/embedderMismatch.js +103 -0
  108. package/dist/lib/embedderMismatch.js.map +1 -0
  109. package/dist/lib/rag/defineRAG.js +39 -5
  110. package/dist/lib/rag/defineRAG.js.map +1 -1
  111. package/dist/memory/define.js +31 -1
  112. package/dist/memory/define.js.map +1 -1
  113. package/dist/memory/define.types.js.map +1 -1
  114. package/dist/memory/embedding/loadRelevant.js +36 -18
  115. package/dist/memory/embedding/loadRelevant.js.map +1 -1
  116. package/dist/memory/pipeline/semantic.js +11 -5
  117. package/dist/memory/pipeline/semantic.js.map +1 -1
  118. package/dist/memory/store/capability.js +70 -4
  119. package/dist/memory/store/capability.js.map +1 -1
  120. package/dist/memory/store/index.js +7 -1
  121. package/dist/memory/store/index.js.map +1 -1
  122. package/dist/memory-providers.js +22 -5
  123. package/dist/memory-providers.js.map +1 -1
  124. package/dist/recorders/core/CredentialRecorder.js +39 -0
  125. package/dist/recorders/core/CredentialRecorder.js.map +1 -0
  126. package/dist/security/index.js +13 -7
  127. package/dist/security/index.js.map +1 -1
  128. package/dist/types/adapters/identity/agentcore.d.ts +39 -9
  129. package/dist/types/adapters/identity/agentcore.d.ts.map +1 -1
  130. package/dist/types/adapters/llm/BedrockProvider.d.ts +24 -0
  131. package/dist/types/adapters/llm/BedrockProvider.d.ts.map +1 -1
  132. package/dist/types/adapters/memory/pgVector.d.ts +244 -0
  133. package/dist/types/adapters/memory/pgVector.d.ts.map +1 -0
  134. package/dist/types/adapters/memory/s3Vectors.d.ts +241 -0
  135. package/dist/types/adapters/memory/s3Vectors.d.ts.map +1 -0
  136. package/dist/types/adapters/memory/sqliteVector.d.ts +5 -27
  137. package/dist/types/adapters/memory/sqliteVector.d.ts.map +1 -1
  138. package/dist/types/adapters/observability/cloudwatch.d.ts +20 -0
  139. package/dist/types/adapters/observability/cloudwatch.d.ts.map +1 -1
  140. package/dist/types/adapters/observability/xray.d.ts +18 -0
  141. package/dist/types/adapters/observability/xray.d.ts.map +1 -1
  142. package/dist/types/adapters/security/agentcore.d.ts +81 -107
  143. package/dist/types/adapters/security/agentcore.d.ts.map +1 -1
  144. package/dist/types/bridge/eventMeta.d.ts +3 -0
  145. package/dist/types/bridge/eventMeta.d.ts.map +1 -1
  146. package/dist/types/core/Agent.d.ts +16 -0
  147. package/dist/types/core/Agent.d.ts.map +1 -1
  148. package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
  149. package/dist/types/doors/security.d.ts +3 -2
  150. package/dist/types/doors/security.d.ts.map +1 -1
  151. package/dist/types/embedders/index.d.ts +99 -19
  152. package/dist/types/embedders/index.d.ts.map +1 -1
  153. package/dist/types/events/dispatcher.d.ts +1 -1
  154. package/dist/types/events/dispatcher.d.ts.map +1 -1
  155. package/dist/types/events/payloads.d.ts +23 -1
  156. package/dist/types/events/payloads.d.ts.map +1 -1
  157. package/dist/types/events/types.d.ts +17 -0
  158. package/dist/types/events/types.d.ts.map +1 -1
  159. package/dist/types/hosting/standingAgent.d.ts.map +1 -1
  160. package/dist/types/lib/embedderMismatch.d.ts +68 -0
  161. package/dist/types/lib/embedderMismatch.d.ts.map +1 -0
  162. package/dist/types/lib/rag/defineRAG.d.ts +28 -6
  163. package/dist/types/lib/rag/defineRAG.d.ts.map +1 -1
  164. package/dist/types/memory/define.d.ts.map +1 -1
  165. package/dist/types/memory/define.types.d.ts +13 -2
  166. package/dist/types/memory/define.types.d.ts.map +1 -1
  167. package/dist/types/memory/embedding/loadRelevant.d.ts +10 -2
  168. package/dist/types/memory/embedding/loadRelevant.d.ts.map +1 -1
  169. package/dist/types/memory/pipeline/semantic.d.ts +13 -2
  170. package/dist/types/memory/pipeline/semantic.d.ts.map +1 -1
  171. package/dist/types/memory/store/capability.d.ts +25 -6
  172. package/dist/types/memory/store/capability.d.ts.map +1 -1
  173. package/dist/types/memory/store/index.d.ts +1 -0
  174. package/dist/types/memory/store/index.d.ts.map +1 -1
  175. package/dist/types/memory/store/types.d.ts +40 -0
  176. package/dist/types/memory/store/types.d.ts.map +1 -1
  177. package/dist/types/memory-providers.d.ts +7 -4
  178. package/dist/types/memory-providers.d.ts.map +1 -1
  179. package/dist/types/recorders/core/CredentialRecorder.d.ts +31 -0
  180. package/dist/types/recorders/core/CredentialRecorder.d.ts.map +1 -0
  181. package/dist/types/security/index.d.ts +7 -3
  182. package/dist/types/security/index.d.ts.map +1 -1
  183. package/package.json +9 -1
@@ -0,0 +1,731 @@
1
+ "use strict";
2
+ /**
3
+ * adapters/memory/pgVector — the target our own port has named since 2.x.
4
+ *
5
+ * `MemoryStore`'s docstring has listed the backends it was designed for from
6
+ * the beginning — *"Every storage backend (InMemory, Redis, DynamoDB,
7
+ * **Postgres**, Bedrock AgentCore) implements this interface"* — and named the
8
+ * query, twice, in the places an implementer would look: *"**Postgres**:
9
+ * multi-row INSERT … ON CONFLICT DO UPDATE"* for `putMany`, *"**pgvector**:
10
+ * `ORDER BY embedding <=> query LIMIT k`"* for `search`. Every one of those
11
+ * sentences was true about the design and false about the shipped package.
12
+ * This is the adapter that makes them the same sentence.
13
+ *
14
+ * It matters more than one row in a table of backends. Postgres is the database
15
+ * most teams already run, `pgvector` is an extension away, and a corpus that
16
+ * lives beside the application's own data inherits its backups, its failover,
17
+ * its access control and its migrations. `sqliteVectorStore` is one machine;
18
+ * `s3VectorsStore` is serverless and eventually consistent; this is the one for
19
+ * a fleet that already has a database.
20
+ *
21
+ * ── The table, and why this does not create it ──────────────────────────────
22
+ * A `vector(N)` column fixes N at creation, and N is a fact about your
23
+ * embedder. Creating the table implicitly would pick that number — and the
24
+ * index type, and the operator class — on your behalf, in a migration you never
25
+ * reviewed, in a database whose DDL is usually somebody's job. So the schema is
26
+ * yours to run, and this store REFUSES a table that is missing rather than
27
+ * silently answering "no matches" against nothing:
28
+ *
29
+ * ```sql
30
+ * CREATE EXTENSION IF NOT EXISTS vector;
31
+ *
32
+ * -- 1024 = your embedder's dimensions. bedrockEmbedder() default: 1024.
33
+ * -- openaiEmbedder() default: 1536. staticEmbedder(): 256.
34
+ * CREATE TABLE af_vectors (
35
+ * namespace TEXT NOT NULL,
36
+ * id TEXT NOT NULL,
37
+ * value JSONB NOT NULL,
38
+ * metadata JSONB,
39
+ * embedding vector(1024),
40
+ * embedder_fp TEXT,
41
+ * version INTEGER NOT NULL,
42
+ * created_at BIGINT NOT NULL,
43
+ * updated_at BIGINT NOT NULL,
44
+ * last_accessed_at BIGINT NOT NULL,
45
+ * access_count INTEGER NOT NULL,
46
+ * ttl BIGINT,
47
+ * tier TEXT,
48
+ * source JSONB,
49
+ * embedding_model TEXT,
50
+ * PRIMARY KEY (namespace, id)
51
+ * );
52
+ *
53
+ * -- Cosine, because that is the score this port reports and every threshold
54
+ * -- in this library is calibrated on. Match the operator class to the metric.
55
+ * CREATE INDEX af_vectors_hnsw ON af_vectors
56
+ * USING hnsw (embedding vector_cosine_ops);
57
+ * CREATE INDEX af_vectors_ns ON af_vectors (namespace);
58
+ *
59
+ * -- Recognition (`seen`/`recordSignature`), usefulness feedback, and the
60
+ * -- per-namespace embedder fingerprint. Small, and each one is a port method
61
+ * -- that would otherwise have to be refused.
62
+ * CREATE TABLE af_signatures (
63
+ * namespace TEXT NOT NULL, signature TEXT NOT NULL,
64
+ * PRIMARY KEY (namespace, signature)
65
+ * );
66
+ * CREATE TABLE af_feedback (
67
+ * namespace TEXT NOT NULL, id TEXT NOT NULL,
68
+ * total DOUBLE PRECISION NOT NULL, count INTEGER NOT NULL,
69
+ * PRIMARY KEY (namespace, id)
70
+ * );
71
+ * CREATE TABLE af_index_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
72
+ * ```
73
+ *
74
+ * Every table and column name above is an OPTION with that value as its
75
+ * default, so this drops into a schema that already has naming conventions —
76
+ * see {@link PgVectorStoreOptions}. Identifiers are validated and quoted; a
77
+ * name that is not a plain SQL identifier is refused rather than interpolated.
78
+ *
79
+ * ── Cosine, and only cosine ─────────────────────────────────────────────────
80
+ * `search` is `1 - (embedding <=> $query::vector)` — pgvector's cosine
81
+ * DISTANCE, converted to the cosine SIMILARITY the port reports and
82
+ * `defineRAG`'s 0.7 default is calibrated on. `<->` (L2) and `<#>` (inner
83
+ * product) are deliberately not options: their ranges are not that range, and a
84
+ * number that reads like a cosine and is not one is the failure mode the whole
85
+ * fingerprint machinery exists to prevent. Build the HNSW index with
86
+ * `vector_cosine_ops` so the operator and the index agree — with the wrong
87
+ * operator class the query still returns the right answer, slowly, by scanning.
88
+ *
89
+ * ── One statement at a time, on purpose ─────────────────────────────────────
90
+ * The client here is anything with `query()` — a `pg.Pool` is the expected one,
91
+ * and a Pool hands each `query()` its own connection. `BEGIN` on one and the
92
+ * next statement on another is a transaction that silently is not one, so this
93
+ * adapter never writes multi-statement transactions. Everything that must be
94
+ * atomic is ONE statement: `putMany` is one multi-row upsert, `putIfVersion` is
95
+ * one conditional upsert, `forget` is one statement with CTEs across all four
96
+ * tables. That is a constraint that made the code better.
97
+ *
98
+ * ── Lazy peer dependency ────────────────────────────────────────────────────
99
+ * `pg` is an OPTIONAL peer dependency, required at construction time. Importing
100
+ * `agentfootprint/memory` costs nothing for consumers who never build one of
101
+ * these. Pass `client` to reuse the pool your app already has — which is the
102
+ * recommended shape, because a second pool to the same database is a second set
103
+ * of connections nobody counted.
104
+ */
105
+ Object.defineProperty(exports, "__esModule", { value: true });
106
+ exports.pgVectorStore = exports.PgVectorSchemaError = void 0;
107
+ const index_js_1 = require("../../memory/identity/index.js");
108
+ const lazyRequire_js_1 = require("../../lib/lazyRequire.js");
109
+ const embedderMismatch_js_1 = require("../../lib/embedderMismatch.js");
110
+ /**
111
+ * Raised when the database is reachable but its schema is not this store's.
112
+ *
113
+ * The law `sqliteVectorStore` states for a file, one backend over: **an
114
+ * unreadable index and an empty one are different facts, and only one of them
115
+ * is safe to answer with "no matches".** A store that treated a missing table
116
+ * as an empty corpus would answer every question from the model's own weights
117
+ * and log nothing.
118
+ */
119
+ class PgVectorSchemaError extends Error {
120
+ code = 'ERR_PGVECTOR_SCHEMA';
121
+ /** The schema-qualified table that could not be used. */
122
+ table;
123
+ /** Columns this store needs and did not find. Empty when the table is absent entirely. */
124
+ missingColumns;
125
+ constructor(table, missingColumns, detail) {
126
+ super(`[memory] the pgVectorStore table ${table} cannot be used: ${detail}\n` +
127
+ ` A missing table and an empty corpus are different facts, and only one of them is ` +
128
+ `safe to answer with "no matches" — so this refuses rather than quietly answering ` +
129
+ `every question from the model alone.\n` +
130
+ ` Fix: run the CREATE TABLE from the pgVectorStore docs (it also needs ` +
131
+ `CREATE EXTENSION vector), or point \`table\`/\`columns\` at the schema you have.`);
132
+ this.name = 'PgVectorSchemaError';
133
+ this.table = table;
134
+ this.missingColumns = missingColumns;
135
+ }
136
+ }
137
+ exports.PgVectorSchemaError = PgVectorSchemaError;
138
+ const DEFAULT_COLUMNS = {
139
+ namespace: 'namespace',
140
+ id: 'id',
141
+ value: 'value',
142
+ metadata: 'metadata',
143
+ embedding: 'embedding',
144
+ embedderFp: 'embedder_fp',
145
+ version: 'version',
146
+ createdAt: 'created_at',
147
+ updatedAt: 'updated_at',
148
+ lastAccessedAt: 'last_accessed_at',
149
+ accessCount: 'access_count',
150
+ ttl: 'ttl',
151
+ tier: 'tier',
152
+ source: 'source',
153
+ embeddingModel: 'embedding_model',
154
+ };
155
+ /**
156
+ * Open a `MemoryStore` over an existing Postgres + pgvector table.
157
+ *
158
+ * @throws when `pg` is absent and no `client` was passed.
159
+ * @throws PgVectorSchemaError on the first call, when the table or a column it
160
+ * needs is not there.
161
+ * @throws EmbedderMismatchError from `put`/`putMany`/`search` when a vector
162
+ * meets a namespace built by a different embedder.
163
+ *
164
+ * @example A corpus beside the application's own data
165
+ * ```ts
166
+ * import { Pool } from 'pg';
167
+ * import { defineRAG, indexDocuments } from 'agentfootprint';
168
+ * import { pgVectorStore } from 'agentfootprint/memory';
169
+ * import { openaiEmbedder } from 'agentfootprint/providers';
170
+ *
171
+ * const store = pgVectorStore({ client: new Pool({ connectionString: process.env.DATABASE_URL }) });
172
+ * const embedder = openaiEmbedder();
173
+ *
174
+ * await indexDocuments(store, embedder, docs, { embedderId: embedder.id });
175
+ *
176
+ * const agent = Agent.create({ provider })
177
+ * .rag(defineRAG({ id: 'docs', store, embedder, embedderId: embedder.id }))
178
+ * .build();
179
+ * ```
180
+ */
181
+ function pgVectorStore(options = {}) {
182
+ const schema = ident(options.schema ?? 'public', 'schema');
183
+ const vectorsTable = qualified(schema, ident(options.table ?? 'af_vectors', 'table'));
184
+ const signaturesTable = qualified(schema, ident(options.signaturesTable ?? 'af_signatures', 'signaturesTable'));
185
+ const feedbackTable = qualified(schema, ident(options.feedbackTable ?? 'af_feedback', 'feedbackTable'));
186
+ const metaTable = qualified(schema, ident(options.metaTable ?? 'af_index_meta', 'metaTable'));
187
+ const rawTableName = options.table ?? 'af_vectors';
188
+ const rawSchemaName = options.schema ?? 'public';
189
+ const col = resolveColumns(options.columns);
190
+ const batchSize = Math.max(1, Math.floor(options.batchSize ?? 500));
191
+ /** The 15 columns a row is written with and read back through, in one order. */
192
+ const WRITE_COLUMNS = [
193
+ col.namespace,
194
+ col.id,
195
+ col.value,
196
+ col.metadata,
197
+ col.embedding,
198
+ col.embedderFp,
199
+ col.version,
200
+ col.createdAt,
201
+ col.updatedAt,
202
+ col.lastAccessedAt,
203
+ col.accessCount,
204
+ col.ttl,
205
+ col.tier,
206
+ col.source,
207
+ col.embeddingModel,
208
+ ];
209
+ const READ_COLUMNS = [
210
+ col.id,
211
+ col.value,
212
+ col.metadata,
213
+ col.version,
214
+ col.createdAt,
215
+ col.updatedAt,
216
+ col.lastAccessedAt,
217
+ col.accessCount,
218
+ col.ttl,
219
+ col.tier,
220
+ col.source,
221
+ col.embeddingModel,
222
+ ]
223
+ .map(quote)
224
+ .join(', ');
225
+ let owned = false;
226
+ let client;
227
+ const connect = () => {
228
+ if (client)
229
+ return client;
230
+ if (options._client) {
231
+ client = options._client;
232
+ return client;
233
+ }
234
+ if (options.client) {
235
+ client = options.client;
236
+ return client;
237
+ }
238
+ const pg = options._pg ?? loadPg();
239
+ if (typeof pg.Pool !== 'function') {
240
+ throw new Error('pgVectorStore: `pg` is installed but `Pool` was not found. Update the driver, or ' +
241
+ 'pass `client` with a pre-built pool.');
242
+ }
243
+ if (!options.connectionString) {
244
+ throw new Error('pgVectorStore: pass `client` (recommended — reuse the pool your app already has) ' +
245
+ 'or `connectionString` for this store to build one.');
246
+ }
247
+ client = new pg.Pool({ connectionString: options.connectionString });
248
+ owned = true;
249
+ return client;
250
+ };
251
+ let closed = false;
252
+ const open = (verb) => {
253
+ if (closed) {
254
+ throw new Error(`[memory] the pgVectorStore for ${vectorsTable} is closed, so it cannot ${verb}. ` +
255
+ `close() is final by design — reopening the pool behind you would hide a ` +
256
+ `shutdown-ordering bug rather than surface it. Build a new store if you need one ` +
257
+ `after closing this.`);
258
+ }
259
+ };
260
+ /**
261
+ * Verify the schema ONCE, on the first call, and refuse by name.
262
+ *
263
+ * Not at construction: building a store must not require a live connection
264
+ * (a module that connects on import is a module that fails at import). Not on
265
+ * every call either — the answer cannot change under a running process
266
+ * without a migration, and paying an `information_schema` read per retrieval
267
+ * would be a tax on the hot path for a fact checked at boot.
268
+ */
269
+ let verified;
270
+ const ensureSchema = () => {
271
+ return (verified ??= (async () => {
272
+ const db = connect();
273
+ const result = await db.query(`SELECT column_name FROM information_schema.columns ` +
274
+ `WHERE table_schema = $1 AND table_name = $2`, [rawSchemaName, rawTableName]);
275
+ const found = new Set(result.rows.map((r) => String(r['column_name'])));
276
+ if (found.size === 0) {
277
+ throw new PgVectorSchemaError(vectorsTable, [], `no such table (looked in schema '${rawSchemaName}').`);
278
+ }
279
+ const missing = WRITE_COLUMNS.filter((name) => !found.has(name));
280
+ if (missing.length > 0) {
281
+ throw new PgVectorSchemaError(vectorsTable, missing, `it exists but is missing ${missing.join(', ')} — this is a table of that name ` +
282
+ `belonging to something else, or a schema from a different release.`);
283
+ }
284
+ })());
285
+ };
286
+ const query = async (text, params) => {
287
+ await ensureSchema();
288
+ const result = await connect().query(text, params);
289
+ return result.rows ?? [];
290
+ };
291
+ // ── Fingerprints ──────────────────────────────────────────────────
292
+ const fingerprints = new Map();
293
+ const readFingerprint = async (ns) => {
294
+ const held = fingerprints.get(ns);
295
+ if (held !== undefined)
296
+ return held;
297
+ const rows = await query(`SELECT "value" FROM ${metaTable} WHERE "key" = $1`, [`fp:${ns}`]);
298
+ const value = rows[0]?.['value'];
299
+ if (typeof value !== 'string')
300
+ return undefined;
301
+ fingerprints.set(ns, value);
302
+ return value;
303
+ };
304
+ const recordFingerprint = async (ns, fp) => {
305
+ await query(`INSERT INTO ${metaTable} ("key", "value") VALUES ($1, $2) ` +
306
+ `ON CONFLICT ("key") DO UPDATE SET "value" = EXCLUDED."value"`, [`fp:${ns}`, fp]);
307
+ fingerprints.set(ns, fp);
308
+ };
309
+ /** The `sqliteVectorStore` rule, one backend over: dimensions always decide. */
310
+ const reconcile = async (ns, incoming, operation) => {
311
+ const storedText = await readFingerprint(ns);
312
+ if (storedText === undefined) {
313
+ if (operation === 'write to')
314
+ await recordFingerprint(ns, (0, embedderMismatch_js_1.fingerprintText)(incoming));
315
+ return;
316
+ }
317
+ const stored = (0, embedderMismatch_js_1.parseFingerprint)(storedText);
318
+ const conflict = (0, embedderMismatch_js_1.fingerprintConflict)(stored, incoming);
319
+ if (conflict !== null) {
320
+ throw new embedderMismatch_js_1.EmbedderMismatchError(ns, storedText, (0, embedderMismatch_js_1.fingerprintText)(incoming), conflict, operation, 'point this store at a different table');
321
+ }
322
+ if (operation === 'write to' && stored.id === undefined && incoming.id !== undefined) {
323
+ await recordFingerprint(ns, (0, embedderMismatch_js_1.fingerprintText)(incoming));
324
+ }
325
+ };
326
+ /** The 15 bound values one row is written with, in `WRITE_COLUMNS` order. */
327
+ const rowValues = (ns, entry) => {
328
+ const embedding = entry.embedding;
329
+ const fp = embedding && embedding.length > 0
330
+ ? (0, embedderMismatch_js_1.fingerprintText)({
331
+ ...(entry.embeddingModel !== undefined && { id: entry.embeddingModel }),
332
+ dims: embedding.length,
333
+ })
334
+ : null;
335
+ return [
336
+ ns,
337
+ entry.id,
338
+ JSON.stringify(entry.value ?? null),
339
+ entry.metadata === undefined ? null : JSON.stringify(entry.metadata),
340
+ // pgvector reads its own literal form, `'[1,2,3]'`, cast to `vector`.
341
+ embedding && embedding.length > 0 ? `[${embedding.join(',')}]` : null,
342
+ fp,
343
+ entry.version,
344
+ entry.createdAt,
345
+ entry.updatedAt,
346
+ entry.lastAccessedAt,
347
+ entry.accessCount,
348
+ entry.ttl ?? null,
349
+ entry.tier ?? null,
350
+ entry.source === undefined ? null : JSON.stringify(entry.source),
351
+ entry.embeddingModel ?? null,
352
+ ];
353
+ };
354
+ /**
355
+ * The SQL type of each bound value, in `WRITE_COLUMNS` order.
356
+ *
357
+ * Every parameter is cast EXPLICITLY rather than left to inference. In an
358
+ * `INSERT … VALUES` Postgres can infer from the target column, but in the
359
+ * `INSERT … SELECT` form `putIfVersion` needs it cannot, and an uncast
360
+ * parameter arrives as text — which fails on the first integer column with
361
+ * an error about a type nobody wrote. One list, both statements, no
362
+ * inference to reason about.
363
+ */
364
+ const CASTS = [
365
+ 'text', // namespace
366
+ 'text', // id
367
+ 'jsonb', // value
368
+ 'jsonb', // metadata
369
+ 'vector', // embedding
370
+ 'text', // embedder_fp
371
+ 'int', // version
372
+ 'bigint', // created_at
373
+ 'bigint', // updated_at
374
+ 'bigint', // last_accessed_at
375
+ 'int', // access_count
376
+ 'bigint', // ttl
377
+ 'text', // tier
378
+ 'jsonb', // source
379
+ 'text', // embedding_model
380
+ ];
381
+ /** `$1::text, $2::text, $3::jsonb, …` for one row, offset into the batch. */
382
+ const rowParams = (offset) => CASTS.map((cast, i) => `$${offset + i + 1}::${cast}`).join(', ');
383
+ const upsertSet = WRITE_COLUMNS.slice(2)
384
+ .map((name) => `${quote(name)} = EXCLUDED.${quote(name)}`)
385
+ .join(', ');
386
+ const insertColumns = WRITE_COLUMNS.map(quote).join(', ');
387
+ const conflictTarget = `(${quote(col.namespace)}, ${quote(col.id)})`;
388
+ const store = {
389
+ // Vectors in, ranked vectors out — `ORDER BY embedding <=> $query` ranks
390
+ // the embeddings this store was handed.
391
+ supportsVectorSearch: true,
392
+ ranksBy: 'vector',
393
+ async fingerprintOf(identity) {
394
+ open('report a fingerprint');
395
+ return readFingerprint((0, index_js_1.identityNamespace)(identity));
396
+ },
397
+ async get(identity, id) {
398
+ open('read an entry');
399
+ const ns = (0, index_js_1.identityNamespace)(identity);
400
+ const rows = await query(`SELECT ${READ_COLUMNS} FROM ${vectorsTable} ` +
401
+ `WHERE ${quote(col.namespace)} = $1 AND ${quote(col.id)} = $2`, [ns, id]);
402
+ const row = rows[0];
403
+ if (row === undefined)
404
+ return null;
405
+ const entry = rowToEntry(row, col);
406
+ if (entry.ttl !== undefined && entry.ttl <= Date.now())
407
+ return null;
408
+ // Decay signals, the same side effect the port documents for `get`.
409
+ await query(`UPDATE ${vectorsTable} SET ${quote(col.lastAccessedAt)} = $1, ` +
410
+ `${quote(col.accessCount)} = ${quote(col.accessCount)} + 1 ` +
411
+ `WHERE ${quote(col.namespace)} = $2 AND ${quote(col.id)} = $3`, [Date.now(), ns, id]);
412
+ return entry;
413
+ },
414
+ async put(identity, entry) {
415
+ await this.putMany(identity, [entry]);
416
+ },
417
+ async putMany(identity, entries) {
418
+ open('write entries');
419
+ // The port requires an empty batch to be a no-op — callers rely on it to
420
+ // skip a round-trip on a turn that produced nothing.
421
+ if (entries.length === 0)
422
+ return;
423
+ const ns = (0, index_js_1.identityNamespace)(identity);
424
+ for (const entry of entries) {
425
+ const embedding = entry.embedding;
426
+ if (embedding && embedding.length > 0) {
427
+ await reconcile(ns, {
428
+ ...(entry.embeddingModel !== undefined && { id: entry.embeddingModel }),
429
+ dims: embedding.length,
430
+ }, 'write to');
431
+ }
432
+ }
433
+ // ONE multi-row upsert per chunk — the statement the port's own docstring
434
+ // has named for this backend since 2.x. One statement is one implicit
435
+ // transaction, so a chunk lands whole or not at all: a HALF-INDEXED
436
+ // corpus keeps answering and quietly cannot see what did not land.
437
+ for (let i = 0; i < entries.length; i += batchSize) {
438
+ const chunk = entries.slice(i, i + batchSize);
439
+ const params = [];
440
+ const tuples = [];
441
+ for (const entry of chunk) {
442
+ tuples.push(`(${rowParams(params.length)})`);
443
+ params.push(...rowValues(ns, entry));
444
+ }
445
+ await query(`INSERT INTO ${vectorsTable} (${insertColumns}) VALUES ${tuples.join(', ')} ` +
446
+ `ON CONFLICT ${conflictTarget} DO UPDATE SET ${upsertSet}`, params);
447
+ }
448
+ },
449
+ async putIfVersion(identity, entry, expectedVersion) {
450
+ open('write an entry');
451
+ const ns = (0, index_js_1.identityNamespace)(identity);
452
+ const embedding = entry.embedding;
453
+ if (embedding && embedding.length > 0) {
454
+ await reconcile(ns, {
455
+ ...(entry.embeddingModel !== undefined && { id: entry.embeddingModel }),
456
+ dims: embedding.length,
457
+ }, 'write to');
458
+ }
459
+ // ONE statement, so the check and the write cannot be separated by
460
+ // another writer — which is the entire point of a compare-and-set, and
461
+ // is why this is not a SELECT followed by an UPDATE (a Pool would happily
462
+ // run those two on different connections).
463
+ //
464
+ // The `WHERE` on the source SELECT is what makes the absent-row case
465
+ // correct: with `expectedVersion !== 0` and no existing row, no source
466
+ // row is produced, nothing is inserted, and nothing comes back. With a
467
+ // row present, the source row IS produced, the conflict fires, and the
468
+ // `DO UPDATE … WHERE version = expected` decides.
469
+ const params = rowValues(ns, entry);
470
+ const expectedParam = `$${params.length + 1}`;
471
+ params.push(expectedVersion);
472
+ const rows = await query(`INSERT INTO ${vectorsTable} AS af_target (${insertColumns}) ` +
473
+ `SELECT ${rowParams(0)} ` +
474
+ `WHERE ${expectedParam}::int = 0 OR EXISTS (SELECT 1 FROM ${vectorsTable} AS af_probe ` +
475
+ `WHERE af_probe.${quote(col.namespace)} = $1 AND af_probe.${quote(col.id)} = $2) ` +
476
+ `ON CONFLICT ${conflictTarget} DO UPDATE SET ${upsertSet} ` +
477
+ `WHERE af_target.${quote(col.version)} = ${expectedParam}::int ` +
478
+ `RETURNING af_target.${quote(col.version)}`, params);
479
+ if (rows.length > 0)
480
+ return { applied: true };
481
+ // Advisory, and read AFTER the failed attempt rather than inside it:
482
+ // the port describes `currentVersion` as what the caller decides a retry
483
+ // against, not as a value it may assume is still current.
484
+ const current = await query(`SELECT ${quote(col.version)} FROM ${vectorsTable} ` +
485
+ `WHERE ${quote(col.namespace)} = $1 AND ${quote(col.id)} = $2`, [ns, entry.id]);
486
+ const version = current[0]?.[col.version];
487
+ return version === undefined || version === null
488
+ ? { applied: false }
489
+ : { applied: false, currentVersion: num(version) };
490
+ },
491
+ async list(identity, listOptions) {
492
+ open('list entries');
493
+ const ns = (0, index_js_1.identityNamespace)(identity);
494
+ const limit = Math.max(1, Math.floor(listOptions?.limit ?? 100));
495
+ const after = listOptions?.cursor ?? '';
496
+ // Keyset pagination on the primary key: stable under concurrent writes,
497
+ // unlike OFFSET, and the cursor is the last id rather than a position.
498
+ const rows = await query(`SELECT ${READ_COLUMNS} FROM ${vectorsTable} ` +
499
+ `WHERE ${quote(col.namespace)} = $1 AND ${quote(col.id)} > $2 ` +
500
+ `ORDER BY ${quote(col.id)} LIMIT $3`, [ns, after, limit + 1]);
501
+ const tierFilter = listOptions?.tiers ? new Set(listOptions.tiers) : undefined;
502
+ const page = [];
503
+ let cursor;
504
+ for (const row of rows) {
505
+ if (page.length === limit) {
506
+ // The extra row only ever exists to prove there IS a next page.
507
+ cursor = page[page.length - 1]?.id;
508
+ break;
509
+ }
510
+ const entry = rowToEntry(row, col);
511
+ if (entry.ttl !== undefined && entry.ttl <= Date.now())
512
+ continue;
513
+ if (tierFilter && (entry.tier === undefined || !tierFilter.has(entry.tier)))
514
+ continue;
515
+ page.push(entry);
516
+ }
517
+ return { entries: page, ...(cursor !== undefined && { cursor }) };
518
+ },
519
+ async delete(identity, id) {
520
+ open('delete an entry');
521
+ await query(`DELETE FROM ${vectorsTable} WHERE ${quote(col.namespace)} = $1 AND ${quote(col.id)} = $2`, [(0, index_js_1.identityNamespace)(identity), id]);
522
+ },
523
+ async seen(identity, signature) {
524
+ open('check a signature');
525
+ const rows = await query(`SELECT 1 FROM ${signaturesTable} WHERE "namespace" = $1 AND "signature" = $2`, [(0, index_js_1.identityNamespace)(identity), signature]);
526
+ return rows.length > 0;
527
+ },
528
+ async recordSignature(identity, signature) {
529
+ open('record a signature');
530
+ await query(`INSERT INTO ${signaturesTable} ("namespace", "signature") VALUES ($1, $2) ` +
531
+ `ON CONFLICT ("namespace", "signature") DO NOTHING`, [(0, index_js_1.identityNamespace)(identity), signature]);
532
+ },
533
+ async feedback(identity, id, usefulness) {
534
+ open('record feedback');
535
+ // Non-finite values poison the aggregate; the port says adapters must
536
+ // reject them, and clamp the rest.
537
+ if (!Number.isFinite(usefulness))
538
+ return;
539
+ const clamped = Math.max(-1, Math.min(1, usefulness));
540
+ await query(`INSERT INTO ${feedbackTable} AS af_fb ("namespace", "id", "total", "count") ` +
541
+ `VALUES ($1::text, $2::text, $3::double precision, 1) ` +
542
+ `ON CONFLICT ("namespace", "id") DO UPDATE SET ` +
543
+ `"total" = af_fb."total" + EXCLUDED."total", "count" = af_fb."count" + 1`, [(0, index_js_1.identityNamespace)(identity), id, clamped]);
544
+ },
545
+ async getFeedback(identity, id) {
546
+ open('read feedback');
547
+ const rows = await query(`SELECT "total", "count" FROM ${feedbackTable} WHERE "namespace" = $1 AND "id" = $2`, [(0, index_js_1.identityNamespace)(identity), id]);
548
+ const row = rows[0];
549
+ const count = row === undefined ? 0 : num(row['count']);
550
+ if (count === 0)
551
+ return null;
552
+ return { average: num(row?.['total']) / count, count };
553
+ },
554
+ async forget(identity) {
555
+ open('forget a namespace');
556
+ const ns = (0, index_js_1.identityNamespace)(identity);
557
+ // ONE statement across all four tables. A GDPR erasure that removed the
558
+ // vectors and left the signatures behind would be a deletion that is not
559
+ // one, and a Pool cannot be trusted to keep four statements on one
560
+ // connection — so they are one statement, and therefore one transaction.
561
+ await query(`WITH cleared_vectors AS (DELETE FROM ${vectorsTable} WHERE ${quote(col.namespace)} = $1), ` +
562
+ `cleared_signatures AS (DELETE FROM ${signaturesTable} WHERE "namespace" = $1), ` +
563
+ `cleared_feedback AS (DELETE FROM ${feedbackTable} WHERE "namespace" = $1) ` +
564
+ `DELETE FROM ${metaTable} WHERE "key" = $2`, [ns, `fp:${ns}`]);
565
+ fingerprints.delete(ns);
566
+ },
567
+ async search(identity, queryVector, searchOptions) {
568
+ open('search');
569
+ const ns = (0, index_js_1.identityNamespace)(identity);
570
+ const k = Math.max(1, Math.floor(searchOptions?.k ?? 10));
571
+ if (queryVector.length === 0)
572
+ return [];
573
+ // pgvector's cosine distance is undefined for a zero vector (it divides
574
+ // by the norm) and comes back as NaN, which sorts unpredictably. An
575
+ // unscoreable query is empty rather than arbitrary.
576
+ if (queryVector.every((v) => v === 0))
577
+ return [];
578
+ // Refused BEFORE the scan, on both halves of the fingerprint. A swapped
579
+ // embedder must not score against the old vectors — the numbers come
580
+ // back in the same range as real ones and no threshold separates them.
581
+ await reconcile(ns, {
582
+ ...(searchOptions?.embedderId !== undefined && { id: searchOptions.embedderId }),
583
+ dims: queryVector.length,
584
+ }, 'search');
585
+ const params = [ns, `[${queryVector.join(',')}]`];
586
+ const where = [
587
+ `${quote(col.namespace)} = $1`,
588
+ `${quote(col.embedding)} IS NOT NULL`,
589
+ `(${quote(col.ttl)} IS NULL OR ${quote(col.ttl)} > ${bind(params, Date.now())})`,
590
+ ];
591
+ const tiers = searchOptions?.tiers;
592
+ if (tiers && tiers.length > 0) {
593
+ where.push(`${quote(col.tier)} = ANY(${bind(params, [...tiers])}::text[])`);
594
+ }
595
+ if (searchOptions?.embedderId !== undefined) {
596
+ // A row that never named its embedder is not evidence of a different
597
+ // one — the same asymmetry the fingerprint rule uses.
598
+ const p = bind(params, searchOptions.embedderId);
599
+ where.push(`(${quote(col.embeddingModel)} IS NULL OR ${quote(col.embeddingModel)} = ${p})`);
600
+ }
601
+ if (searchOptions?.minScore !== undefined) {
602
+ const p = bind(params, searchOptions.minScore);
603
+ where.push(`1 - (${quote(col.embedding)} <=> $2::vector) >= ${p}`);
604
+ }
605
+ const limitParam = bind(params, k);
606
+ // The query the port's docstring has named for this backend since 2.x —
607
+ // `ORDER BY embedding <=> query LIMIT k` — with the distance turned into
608
+ // the cosine SIMILARITY the port reports.
609
+ const rows = await query(`SELECT ${READ_COLUMNS}, 1 - (${quote(col.embedding)} <=> $2::vector) AS af_score ` +
610
+ `FROM ${vectorsTable} WHERE ${where.join(' AND ')} ` +
611
+ `ORDER BY ${quote(col.embedding)} <=> $2::vector, ${quote(col.id)} ` +
612
+ `LIMIT ${limitParam}`, params);
613
+ return rows.map((row) => ({
614
+ entry: rowToEntry(row, col),
615
+ score: num(row['af_score']),
616
+ }));
617
+ },
618
+ async close() {
619
+ if (closed)
620
+ return;
621
+ closed = true;
622
+ if (owned)
623
+ await client?.end?.();
624
+ },
625
+ };
626
+ return store;
627
+ }
628
+ exports.pgVectorStore = pgVectorStore;
629
+ // ─── Identifiers ─────────────────────────────────────────────────────
630
+ /**
631
+ * Validate a caller-supplied identifier, or refuse it.
632
+ *
633
+ * Table and column names are the one part of a query that cannot be a bound
634
+ * parameter — they are interpolated into the SQL text. So they are checked
635
+ * against a plain-identifier shape and refused otherwise, rather than escaped
636
+ * and hoped for. A store configured from an environment variable is one
637
+ * `DB_TABLE` away from being an injection point, and "we quoted it" is not the
638
+ * answer that lets you stop thinking about it.
639
+ */
640
+ function ident(name, option) {
641
+ if (!/^[A-Za-z_][A-Za-z0-9_$]*$/.test(name)) {
642
+ throw new TypeError(`pgVectorStore: \`${option}\` must be a plain SQL identifier — letters, digits and ` +
643
+ `underscores, not starting with a digit. Received ${JSON.stringify(name)}.\n` +
644
+ ` Table and column names cannot be bound parameters; they are interpolated into the ` +
645
+ `statement, so anything else is refused rather than escaped and hoped for.`);
646
+ }
647
+ return name;
648
+ }
649
+ function quote(name) {
650
+ return `"${name}"`;
651
+ }
652
+ function qualified(schema, table) {
653
+ return `${quote(schema)}.${quote(table)}`;
654
+ }
655
+ function resolveColumns(overrides) {
656
+ const out = { ...DEFAULT_COLUMNS };
657
+ for (const key of Object.keys(DEFAULT_COLUMNS)) {
658
+ const value = overrides?.[key];
659
+ if (value !== undefined)
660
+ out[key] = ident(value, `columns.${key}`);
661
+ }
662
+ return out;
663
+ }
664
+ /** Append a value to the parameter list and return its `$n` placeholder. */
665
+ function bind(params, value) {
666
+ params.push(value);
667
+ return `$${params.length}`;
668
+ }
669
+ // ─── Row mapping ─────────────────────────────────────────────────────
670
+ /**
671
+ * `BIGINT` comes back from `pg` as a STRING by default (it does not fit in a
672
+ * JavaScript number in general), and every timestamp here is one. Coerced in
673
+ * one place, so a `createdAt` never reaches a consumer as `'1754640000000'`.
674
+ */
675
+ function num(value) {
676
+ if (typeof value === 'number')
677
+ return value;
678
+ if (typeof value === 'string') {
679
+ const n = Number(value);
680
+ return Number.isFinite(n) ? n : 0;
681
+ }
682
+ return 0;
683
+ }
684
+ /** `JSONB` arrives already parsed from `pg`; a mock or a `TEXT` column may not. */
685
+ function json(value) {
686
+ if (typeof value !== 'string')
687
+ return value;
688
+ try {
689
+ return JSON.parse(value);
690
+ }
691
+ catch {
692
+ return value;
693
+ }
694
+ }
695
+ function rowToEntry(row, col) {
696
+ const metadata = row[col.metadata];
697
+ const source = row[col.source];
698
+ const ttl = row[col.ttl];
699
+ const tier = row[col.tier];
700
+ const model = row[col.embeddingModel];
701
+ return {
702
+ id: String(row[col.id]),
703
+ value: json(row[col.value]),
704
+ ...(metadata !== null &&
705
+ metadata !== undefined && { metadata: json(metadata) }),
706
+ version: num(row[col.version]),
707
+ createdAt: num(row[col.createdAt]),
708
+ updatedAt: num(row[col.updatedAt]),
709
+ lastAccessedAt: num(row[col.lastAccessedAt]),
710
+ accessCount: num(row[col.accessCount]),
711
+ ...(ttl !== null && ttl !== undefined && { ttl: num(ttl) }),
712
+ ...(typeof tier === 'string' && { tier: tier }),
713
+ ...(source !== null &&
714
+ source !== undefined && { source: json(source) }),
715
+ ...(typeof model === 'string' && { embeddingModel: model }),
716
+ };
717
+ }
718
+ // ─── Driver loading ──────────────────────────────────────────────────
719
+ function loadPg() {
720
+ try {
721
+ return (0, lazyRequire_js_1.lazyRequire)('pg');
722
+ }
723
+ catch {
724
+ throw new Error('pgVectorStore requires the `pg` peer dependency.\n' +
725
+ ' Install: npm install pg\n' +
726
+ ' And in the database: CREATE EXTENSION IF NOT EXISTS vector;\n' +
727
+ ' Or pass `client` with a pre-built pool — recommended, so one pool serves the ' +
728
+ 'whole app.');
729
+ }
730
+ }
731
+ //# sourceMappingURL=pgVector.js.map