@mastra/pg 1.25.0 → 1.26.0-alpha.1
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/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/reference-memory-memory-class.md +2 -0
- package/dist/docs/references/reference-rag-vector-databases.md +39 -0
- package/dist/index.cjs +193 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +193 -3
- package/dist/index.js.map +1 -1
- package/dist/storage/db/index.d.ts +5 -0
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts +4 -2
- package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts +5 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
- package/dist/storage/factory-storage.d.ts.map +1 -1
- package/dist/storage/test-utils.d.ts.map +1 -1
- package/package.json +6 -6
package/dist/docs/SKILL.md
CHANGED
|
@@ -41,6 +41,8 @@ export const agent = new Agent({
|
|
|
41
41
|
|
|
42
42
|
**options.lastMessages** (`number | false`): Number of most recent messages to include in context. Set to false to disable the message history feature entirely (messages are not loaded into context or saved). Use Number.MAX\_SAFE\_INTEGER to retrieve all messages with no limit. To load messages without saving new ones, use the readOnly option. The window slides forward on every request, so once a thread exceeds the limit, each turn invalidates the provider prompt cache. For long-running conversations, use Observational Memory instead.
|
|
43
43
|
|
|
44
|
+
**options.messageHistory** (`{ maxTokens: number; atMaxRemoveTokens?: number }`): Token budget for the complete prompt, including remembered history, system instructions, context, and the current turn. When the prompt exceeds maxTokens, the oldest remembered messages are dropped until the prompt is at most maxTokens - atMaxRemoveTokens (defaults to 25% of maxTokens). Protected content is never removed. Linked tool calls and results are removed together. During agent runs, trimmed history is excluded from later turns via a persisted per-thread boundary, but the messages themselves stay in storage. When set without an explicit lastMessages, the default 10-message cap is not applied. Set maxTokens to 0 to disable message history. Prefer this over lastMessages, since message count is a poor proxy for context size.
|
|
45
|
+
|
|
44
46
|
**options.readOnly** (`boolean`): When true, prevents memory from saving new messages and provides working memory as read-only context (without the updateWorkingMemory tool). Useful for read-only operations like previews, internal routing agents, or sub agents that should reference but not modify memory.
|
|
45
47
|
|
|
46
48
|
**options.semanticRecall** (`boolean | { topK: number; messageRange: number | { before: number; after: number }; scope?: 'thread' | 'resource' }`): Enable semantic search in message history. Can be a boolean or an object with configuration options. When enabled, requires both vector store and embedder to be configured. Default topK is 4, default messageRange is {before: 1, after: 1}.
|
|
@@ -248,6 +248,34 @@ await store.upsert({
|
|
|
248
248
|
})
|
|
249
249
|
```
|
|
250
250
|
|
|
251
|
+
**Weaviate**:
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
import { WeaviateVector } from '@mastra/weaviate'
|
|
255
|
+
|
|
256
|
+
const store = new WeaviateVector({
|
|
257
|
+
id: 'weaviate-vector',
|
|
258
|
+
httpHost: process.env.WEAVIATE_HOST,
|
|
259
|
+
httpPort: 443,
|
|
260
|
+
httpSecure: true,
|
|
261
|
+
grpcHost: process.env.WEAVIATE_GRPC_HOST,
|
|
262
|
+
grpcPort: 443,
|
|
263
|
+
grpcSecure: true,
|
|
264
|
+
apiKey: process.env.WEAVIATE_API_KEY,
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
await store.createIndex({
|
|
268
|
+
indexName: 'myCollection',
|
|
269
|
+
dimension: 1536,
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
await store.upsert({
|
|
273
|
+
indexName: 'myCollection',
|
|
274
|
+
vectors: embeddings,
|
|
275
|
+
metadata: chunks.map(chunk => ({ text: chunk.text })),
|
|
276
|
+
})
|
|
277
|
+
```
|
|
278
|
+
|
|
251
279
|
**Cloudflare**:
|
|
252
280
|
|
|
253
281
|
```ts
|
|
@@ -534,6 +562,17 @@ Namespace names must:
|
|
|
534
562
|
|
|
535
563
|
- Example: `_namespace` isn't valid (starts with underscore)
|
|
536
564
|
|
|
565
|
+
**Weaviate**:
|
|
566
|
+
|
|
567
|
+
Index names map to Weaviate collections, which:
|
|
568
|
+
|
|
569
|
+
- Are capitalized by Weaviate (the first letter is upper-cased)
|
|
570
|
+
- Should contain only letters, numbers, and the `_` character
|
|
571
|
+
- Must start with a letter, since only the first character is upper-cased (a leading digit or symbol stays invalid)
|
|
572
|
+
- Preserve the original Mastra index name in the collection description, so `listIndexes()` and `describeIndex()` return the name you supplied
|
|
573
|
+
- Example: `my_collection` is stored as `My_collection` and returned as `my_collection`
|
|
574
|
+
- Example: `123_collection` isn't valid (starts with a digit)
|
|
575
|
+
|
|
537
576
|
**Cloudflare**:
|
|
538
577
|
|
|
539
578
|
Index names must:
|
package/dist/index.cjs
CHANGED
|
@@ -2484,6 +2484,7 @@ async function loadSchemaSnapshot(client, schemaName) {
|
|
|
2484
2484
|
}
|
|
2485
2485
|
//#endregion
|
|
2486
2486
|
//#region src/storage/db/index.ts
|
|
2487
|
+
const POSTGRES_MAX_BIND_PARAMETERS$1 = 65535;
|
|
2487
2488
|
/**
|
|
2488
2489
|
* Resolves PgDomainConfig to a database client and schema.
|
|
2489
2490
|
* Handles creating a new pool if config is provided.
|
|
@@ -3037,6 +3038,90 @@ var PgDB = class extends _mastra_core_base.MastraBase {
|
|
|
3037
3038
|
ON CONFLICT ("traceId", "spanId") DO NOTHING`, values);
|
|
3038
3039
|
} else await client.none(`INSERT INTO ${fullTableName} (${columnList}) VALUES (${placeholders})`, values);
|
|
3039
3040
|
}
|
|
3041
|
+
getChunkRowLimit(columnCount) {
|
|
3042
|
+
if (columnCount === 0) return 0;
|
|
3043
|
+
return Math.max(1, Math.floor(POSTGRES_MAX_BIND_PARAMETERS$1 / columnCount));
|
|
3044
|
+
}
|
|
3045
|
+
getSpanConflictIdentifier(record) {
|
|
3046
|
+
const traceId = record.traceId;
|
|
3047
|
+
const spanId = record.spanId;
|
|
3048
|
+
if (traceId === void 0 || spanId === void 0) return;
|
|
3049
|
+
return `${String(traceId)}|${String(spanId)}`;
|
|
3050
|
+
}
|
|
3051
|
+
async normalizeForInsert(tableName, record) {
|
|
3052
|
+
this.addTimestampZColumns(record);
|
|
3053
|
+
const filteredRecord = await this.filterRecordToKnownColumns(tableName, record);
|
|
3054
|
+
return {
|
|
3055
|
+
columns: Object.keys(filteredRecord).map((column) => (0, _mastra_core_utils.parseSqlIdentifier)(column, "column name")),
|
|
3056
|
+
values: this.prepareValuesForInsert(filteredRecord, tableName),
|
|
3057
|
+
conflictKey: tableName === _mastra_core_storage.TABLE_SPANS ? this.getSpanConflictIdentifier(filteredRecord) : void 0
|
|
3058
|
+
};
|
|
3059
|
+
}
|
|
3060
|
+
buildMultiRowInsertStatement({ tableName, columns, rows }) {
|
|
3061
|
+
const fullTableName = getTableName$6({
|
|
3062
|
+
indexName: tableName,
|
|
3063
|
+
schemaName: getSchemaName$6(this.schemaName)
|
|
3064
|
+
});
|
|
3065
|
+
const columnList = columns.map((column) => `"${column}"`).join(", ");
|
|
3066
|
+
const bindParams = [];
|
|
3067
|
+
const values = [];
|
|
3068
|
+
let bindIndex = 1;
|
|
3069
|
+
for (const rowValues of rows) {
|
|
3070
|
+
const placeholders = rowValues.map(() => `$${bindIndex++}`);
|
|
3071
|
+
bindParams.push(`(${placeholders.join(", ")})`);
|
|
3072
|
+
values.push(...rowValues);
|
|
3073
|
+
}
|
|
3074
|
+
let query = `INSERT INTO ${fullTableName} (${columnList}) VALUES ${bindParams.join(", ")}`;
|
|
3075
|
+
if (tableName === _mastra_core_storage.TABLE_SPANS) {
|
|
3076
|
+
const updateColumns = columns.filter((column) => column !== "traceId" && column !== "spanId");
|
|
3077
|
+
if (updateColumns.length > 0) {
|
|
3078
|
+
const updateClause = updateColumns.map((column) => `"${column}" = EXCLUDED."${column}"`).join(", ");
|
|
3079
|
+
query += ` ON CONFLICT ("traceId", "spanId") DO UPDATE SET ${updateClause}`;
|
|
3080
|
+
} else query += ` ON CONFLICT ("traceId", "spanId") DO NOTHING`;
|
|
3081
|
+
}
|
|
3082
|
+
return {
|
|
3083
|
+
query,
|
|
3084
|
+
values
|
|
3085
|
+
};
|
|
3086
|
+
}
|
|
3087
|
+
async executeBatchInsert(client, { tableName, records }) {
|
|
3088
|
+
const preparedRecords = [];
|
|
3089
|
+
for (const record of records) preparedRecords.push(await this.normalizeForInsert(tableName, record));
|
|
3090
|
+
let pendingColumns;
|
|
3091
|
+
let pendingConflictKeys = /* @__PURE__ */ new Set();
|
|
3092
|
+
let pendingRows = [];
|
|
3093
|
+
let pendingLimit = 0;
|
|
3094
|
+
const flush = async () => {
|
|
3095
|
+
if (!pendingColumns || pendingRows.length === 0) return;
|
|
3096
|
+
const statement = this.buildMultiRowInsertStatement({
|
|
3097
|
+
tableName,
|
|
3098
|
+
columns: pendingColumns,
|
|
3099
|
+
rows: pendingRows
|
|
3100
|
+
});
|
|
3101
|
+
await client.none(statement.query, statement.values);
|
|
3102
|
+
pendingColumns = void 0;
|
|
3103
|
+
pendingRows = [];
|
|
3104
|
+
pendingConflictKeys = /* @__PURE__ */ new Set();
|
|
3105
|
+
pendingLimit = 0;
|
|
3106
|
+
};
|
|
3107
|
+
for (const { columns, values, conflictKey } of preparedRecords) {
|
|
3108
|
+
if (columns.length === 0) continue;
|
|
3109
|
+
const columnsSignature = columns.join("\0");
|
|
3110
|
+
const currentPendingColumns = pendingColumns;
|
|
3111
|
+
const isSpans = tableName === _mastra_core_storage.TABLE_SPANS;
|
|
3112
|
+
const conflictDuplicate = isSpans && conflictKey !== void 0 && pendingConflictKeys.has(conflictKey);
|
|
3113
|
+
const exceedsLimit = pendingRows.length >= pendingLimit;
|
|
3114
|
+
if (currentPendingColumns === void 0 || columnsSignature !== currentPendingColumns.join("\0") || conflictDuplicate || exceedsLimit) {
|
|
3115
|
+
await flush();
|
|
3116
|
+
pendingColumns = columns;
|
|
3117
|
+
pendingLimit = this.getChunkRowLimit(columns.length);
|
|
3118
|
+
pendingRows = [values];
|
|
3119
|
+
pendingConflictKeys = /* @__PURE__ */ new Set();
|
|
3120
|
+
} else pendingRows.push(values);
|
|
3121
|
+
if (isSpans && conflictKey !== void 0) pendingConflictKeys.add(conflictKey);
|
|
3122
|
+
}
|
|
3123
|
+
await flush();
|
|
3124
|
+
}
|
|
3040
3125
|
async insert({ tableName, record }) {
|
|
3041
3126
|
try {
|
|
3042
3127
|
await this.executeInsert(this.client, {
|
|
@@ -3485,9 +3570,9 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
3485
3570
|
async batchInsert({ tableName, records }) {
|
|
3486
3571
|
try {
|
|
3487
3572
|
await this.client.tx(async (tx) => {
|
|
3488
|
-
|
|
3573
|
+
await this.executeBatchInsert(tx, {
|
|
3489
3574
|
tableName,
|
|
3490
|
-
|
|
3575
|
+
records
|
|
3491
3576
|
});
|
|
3492
3577
|
});
|
|
3493
3578
|
} catch (error) {
|
|
@@ -17631,6 +17716,75 @@ LIMIT $${values.length}`,
|
|
|
17631
17716
|
values
|
|
17632
17717
|
};
|
|
17633
17718
|
}
|
|
17719
|
+
function discoveryRegistry(scope) {
|
|
17720
|
+
if (scope === "trace") return TRACE_FIELDS;
|
|
17721
|
+
if (scope === "spans") return SPAN_FIELDS;
|
|
17722
|
+
if (scope === "scores") return SCORE_FIELDS;
|
|
17723
|
+
return FEEDBACK_FIELDS;
|
|
17724
|
+
}
|
|
17725
|
+
function discoverySource(scope) {
|
|
17726
|
+
if (scope === "trace") return "root_scope r";
|
|
17727
|
+
if (scope === "spans") return "current_spans s";
|
|
17728
|
+
if (scope === "scores") return "current_scores s";
|
|
17729
|
+
return "current_feedback s";
|
|
17730
|
+
}
|
|
17731
|
+
function discoveryCollections(scope) {
|
|
17732
|
+
return scope === "trace" ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([scope]);
|
|
17733
|
+
}
|
|
17734
|
+
function compilePostgresTraceQueryObservedFields(schema, plan) {
|
|
17735
|
+
const { ctes, values } = compilePostgresTraceScope(schema, plan, /* @__PURE__ */ new Set());
|
|
17736
|
+
const searchParameter = values.length + 1;
|
|
17737
|
+
const search = plan.search ? `AND strpos(lower('metadata.' || entry.key), lower($${searchParameter})) > 0` : "";
|
|
17738
|
+
if (plan.search) values.push(plan.search);
|
|
17739
|
+
values.push(plan.limit + 1);
|
|
17740
|
+
return {
|
|
17741
|
+
text: `WITH ${ctes.join(",\n")}
|
|
17742
|
+
SELECT 'metadata.' || entry.key AS path, count(*)::bigint AS occurrences
|
|
17743
|
+
FROM root_scope r
|
|
17744
|
+
CROSS JOIN LATERAL jsonb_each(CASE WHEN jsonb_typeof(r."metadataRaw") = 'object' THEN r."metadataRaw" ELSE '{}'::jsonb END) entry
|
|
17745
|
+
WHERE jsonb_typeof(entry.value) = 'string'
|
|
17746
|
+
AND btrim(entry.value #>> '{}') <> ''
|
|
17747
|
+
AND entry.key <> ''
|
|
17748
|
+
AND strpos(entry.key, '.') = 0
|
|
17749
|
+
AND octet_length('metadata.' || entry.key) <= ${_mastra_core_storage.TRACE_QUERY_MAX_PATH_BYTES}
|
|
17750
|
+
AND octet_length(entry.value #>> '{}') <= ${_mastra_core_storage.TRACE_QUERY_MAX_STRING_BYTES}
|
|
17751
|
+
${search}
|
|
17752
|
+
GROUP BY entry.key
|
|
17753
|
+
ORDER BY occurrences DESC, ('metadata.' || entry.key) COLLATE "C" ASC
|
|
17754
|
+
LIMIT $${values.length}`,
|
|
17755
|
+
values
|
|
17756
|
+
};
|
|
17757
|
+
}
|
|
17758
|
+
function compilePostgresTraceQueryValues(schema, plan) {
|
|
17759
|
+
const { ctes, values } = compilePostgresTraceScope(schema, plan, discoveryCollections(plan.predicateScope));
|
|
17760
|
+
let field;
|
|
17761
|
+
if (plan.predicateScope === "trace" && plan.path.startsWith("metadata.")) {
|
|
17762
|
+
const keyParameter = `$${values.length + 1}`;
|
|
17763
|
+
field = `COALESCE(
|
|
17764
|
+
CASE WHEN jsonb_typeof(r."metadataSearch" -> ${keyParameter}) = 'string' THEN r."metadataSearch" ->> ${keyParameter} END,
|
|
17765
|
+
CASE WHEN jsonb_typeof(r."metadataRaw" -> ${keyParameter}) = 'string' THEN NULLIF(btrim(r."metadataRaw" ->> ${keyParameter}), '') END
|
|
17766
|
+
)`;
|
|
17767
|
+
values.push(plan.path.slice(9));
|
|
17768
|
+
} else field = fieldSql(discoveryRegistry(plan.predicateScope), plan.path);
|
|
17769
|
+
const searchParameter = values.length + 1;
|
|
17770
|
+
const search = plan.search ? `AND strpos(lower(value), lower($${searchParameter})) > 0` : "";
|
|
17771
|
+
if (plan.search) values.push(plan.search);
|
|
17772
|
+
values.push(plan.limit + 1);
|
|
17773
|
+
return {
|
|
17774
|
+
text: `WITH ${ctes.join(",\n")}, extracted AS (
|
|
17775
|
+
SELECT ${field}::text AS value FROM ${discoverySource(plan.predicateScope)}
|
|
17776
|
+
)
|
|
17777
|
+
SELECT value, count(*)::bigint AS count
|
|
17778
|
+
FROM extracted
|
|
17779
|
+
WHERE value IS NOT NULL
|
|
17780
|
+
AND octet_length(value) <= ${_mastra_core_storage.TRACE_QUERY_MAX_STRING_BYTES}
|
|
17781
|
+
${search}
|
|
17782
|
+
GROUP BY value
|
|
17783
|
+
ORDER BY count DESC, value COLLATE "C" ASC
|
|
17784
|
+
LIMIT $${values.length}`,
|
|
17785
|
+
values
|
|
17786
|
+
};
|
|
17787
|
+
}
|
|
17634
17788
|
function asIsoTimestamp$1(value) {
|
|
17635
17789
|
if (value === null || value === void 0) throw new Error("Trace query returned a null timestamp");
|
|
17636
17790
|
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
|
@@ -17652,6 +17806,29 @@ async function runWithPostgresTraceQueryTimeout(client, timeoutMs, execute) {
|
|
|
17652
17806
|
throw error;
|
|
17653
17807
|
}
|
|
17654
17808
|
}
|
|
17809
|
+
async function getTraceQueryObservedFields(client, schema, plan, timeoutMs) {
|
|
17810
|
+
if (plan.predicateScope !== "trace") return {
|
|
17811
|
+
observedFields: [],
|
|
17812
|
+
observedFieldsTruncated: false
|
|
17813
|
+
};
|
|
17814
|
+
const query = compilePostgresTraceQueryObservedFields(schema, plan);
|
|
17815
|
+
const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(query.text, query.values));
|
|
17816
|
+
return {
|
|
17817
|
+
observedFields: rows.slice(0, plan.limit).map((row) => _mastra_core_storage.createTraceQueryObservedFieldDescriptor(String(row.path), Number(row.occurrences))),
|
|
17818
|
+
observedFieldsTruncated: rows.length > plan.limit
|
|
17819
|
+
};
|
|
17820
|
+
}
|
|
17821
|
+
async function getTraceQueryValues(client, schema, plan, timeoutMs) {
|
|
17822
|
+
const query = compilePostgresTraceQueryValues(schema, plan);
|
|
17823
|
+
const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(query.text, query.values));
|
|
17824
|
+
return _mastra_core_storage.getTraceQueryValuesResponseSchema.parse({
|
|
17825
|
+
values: rows.slice(0, plan.limit).map((row) => ({
|
|
17826
|
+
value: String(row.value),
|
|
17827
|
+
count: Number(row.count)
|
|
17828
|
+
})),
|
|
17829
|
+
valuesTruncated: rows.length > plan.limit
|
|
17830
|
+
});
|
|
17831
|
+
}
|
|
17655
17832
|
async function queryTraces(client, schema, plan, timeoutMs) {
|
|
17656
17833
|
const query = compilePostgresTraceQuery(schema, plan);
|
|
17657
17834
|
const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(query.text, query.values));
|
|
@@ -18598,6 +18775,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
18598
18775
|
"metrics",
|
|
18599
18776
|
"logs",
|
|
18600
18777
|
"trace-query",
|
|
18778
|
+
"trace-query-discovery",
|
|
18601
18779
|
"thread-query"
|
|
18602
18780
|
];
|
|
18603
18781
|
return [
|
|
@@ -18605,6 +18783,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
18605
18783
|
"logs",
|
|
18606
18784
|
"delta-polling",
|
|
18607
18785
|
"trace-query",
|
|
18786
|
+
"trace-query-discovery",
|
|
18608
18787
|
"thread-query"
|
|
18609
18788
|
];
|
|
18610
18789
|
}
|
|
@@ -18651,6 +18830,12 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
18651
18830
|
async queryTraces(plan) {
|
|
18652
18831
|
return this.#run("QUERY_TRACES", () => queryTraces(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
|
|
18653
18832
|
}
|
|
18833
|
+
async getTraceQueryObservedFields(plan) {
|
|
18834
|
+
return this.#run("GET_TRACE_QUERY_OBSERVED_FIELDS", () => getTraceQueryObservedFields(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
|
|
18835
|
+
}
|
|
18836
|
+
async getTraceQueryValues(plan) {
|
|
18837
|
+
return this.#run("GET_TRACE_QUERY_VALUES", () => getTraceQueryValues(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
|
|
18838
|
+
}
|
|
18654
18839
|
async queryThreads(plan) {
|
|
18655
18840
|
return this.#run("QUERY_THREADS", () => queryThreads(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
|
|
18656
18841
|
}
|
|
@@ -23430,7 +23615,12 @@ var PgFactoryStorageOps = class {
|
|
|
23430
23615
|
const assignments = columns.map((column, i) => `"${column}" = $${i + 1}`).join(", ");
|
|
23431
23616
|
const filter = this.#buildWhere(schema, where, columns.length + 1);
|
|
23432
23617
|
const args = [...columns.map((column) => this.#serialize(this.#column(schema, column), set[column])), ...filter.args];
|
|
23433
|
-
|
|
23618
|
+
try {
|
|
23619
|
+
return (await queryable.query(`UPDATE "${schema.name}" SET ${assignments} WHERE ${filter.sql}`, args)).rowCount ?? 0;
|
|
23620
|
+
} catch (error) {
|
|
23621
|
+
if (isUniqueViolation(error)) throw new _mastra_core_storage.UniqueViolationError(collection, { cause: error });
|
|
23622
|
+
throw error;
|
|
23623
|
+
}
|
|
23434
23624
|
}
|
|
23435
23625
|
async deleteMany(collection, where) {
|
|
23436
23626
|
const schema = this.#schema(collection);
|