@gaganref/confect-test 10.3.1-fork.91 → 10.3.1-fork.92

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.
@@ -1 +1 @@
1
- {"version":3,"file":"KernelQuery.js","names":["KernelRefusal.requiresBinary","ReadSet.keyOf"],"sources":["../../src/internal/KernelQuery.ts"],"sourcesContent":["// Derived from convex-test 0.0.65 (index.ts, `DatabaseFake`), Copyright Convex,\n// Inc., licensed under the Apache License, Version 2.0\n// (https://www.apache.org/licenses/LICENSE-2.0). The ordering, index range and\n// pagination rules are convex-test's. They read a table through the attempt\n// that runs the query instead of convex-test's database, and record the\n// interval each query examined.\n\nimport type {\n GenericDocument,\n GenericSchema,\n SchemaDefinition,\n} from \"convex/server\";\nimport {\n compareValues,\n convexToJson,\n getDocumentSize,\n jsonToConvex,\n} from \"convex/values\";\nimport * as KernelRefusal from \"./KernelRefusal.js\";\nimport * as ReadSet from \"./ReadSet.js\";\nimport type {\n FilterJson,\n Index,\n SerializedQuery,\n SerializedRangeExpression,\n StoredDocument,\n ValidatorJSON,\n} from \"./KernelValues.js\";\nimport {\n evaluateFieldPath,\n evaluateFilter,\n evaluateRangeFilter,\n isValidIdentifier,\n resolvePendingValues,\n validateFieldNames,\n validateIndexRangeExpression,\n validateValidator,\n} from \"./KernelValues.js\";\n\n/**\n * A component's tables as its schema declares them.\n */\nexport interface KernelSchema {\n readonly schemaValidation: boolean;\n readonly tables: ReadonlyMap<\n string,\n {\n readonly indexes: ReadonlyArray<Index>;\n readonly documentType: ValidatorJSON;\n }\n >;\n}\n\nexport const exportSchema = (\n schema: SchemaDefinition<GenericSchema, boolean> | null | undefined,\n): KernelSchema | null => {\n if (schema === null || schema === undefined) return null;\n const exported: KernelSchema = {\n schemaValidation: (schema as unknown as { schemaValidation: boolean })\n .schemaValidation,\n tables: new Map(\n Object.entries(schema.tables).map(([name, table]) => [\n name,\n (table as unknown as { export: () => any }).export(),\n ]),\n ),\n };\n exported.tables.forEach((table, tableName) => {\n if (!isValidIdentifier(tableName)) {\n throw new Error(\n `Table names must be valid identifiers, got \"${tableName}\"`,\n );\n }\n validateFieldNames(table.documentType);\n table.indexes.forEach(({ indexDescriptor }) => {\n if (!isValidIdentifier(indexDescriptor)) {\n throw new Error(\n `Index names must be valid identifiers, got \"${indexDescriptor}\"`,\n );\n }\n });\n });\n return exported;\n};\n\n/**\n * Check a document against its table's declared type, as the backend does\n * before a write.\n */\nexport const validateDocument = (\n schema: KernelSchema | null,\n tableName: string,\n doc: Record<string, unknown>,\n) => {\n if (schema === null || !schema.schemaValidation) return;\n const validator = schema.tables.get(tableName)?.documentType;\n if (validator === undefined) return;\n validateValidator(validator, resolvePendingValues(doc as GenericDocument));\n};\n\n/**\n * An interval a query reads, before it says where it starts and how far it got.\n */\nexport type Interval = Omit<\n ReadSet.IntervalRead,\n \"componentPath\" | \"after\" | \"through\"\n>;\n\n/**\n * What a query reads: one table of one component, as the running attempt sees\n * it. `read` records an interval in the attempt's read set and returns how to\n * advance it.\n */\nexport interface TableView {\n readonly schema: KernelSchema | null;\n readonly scan: (tableName: string) => ReadonlyArray<StoredDocument>;\n readonly read: (\n interval: Interval,\n after: ReadSet.Key | null,\n ) => (through: ReadSet.Key | \"end\") => void;\n}\n\ninterface ResolvedSource {\n readonly sortedDocs: ReadonlyArray<GenericDocument>;\n readonly filterFn: (doc: GenericDocument) => boolean;\n readonly limit: number | null;\n readonly order: \"asc\" | \"desc\";\n readonly interval: Interval;\n}\n\nconst resolveSource = (\n view: TableView,\n query: SerializedQuery,\n): ResolvedSource => {\n const source = query.source;\n let docs: Array<GenericDocument>;\n let fieldPathsToSortBy: Array<string>;\n let order: \"asc\" | \"desc\";\n let table: string;\n let range: ReadonlyArray<SerializedRangeExpression> = [];\n switch (source.type) {\n case \"FullTableScan\": {\n table = source.tableName;\n docs = [...view.scan(source.tableName)];\n order = source.order ?? \"asc\";\n fieldPathsToSortBy = [\"_creationTime\"];\n break;\n }\n case \"IndexRange\": {\n const [tableName, indexName] = source.indexName.split(\".\") as [\n string,\n string,\n ];\n table = tableName;\n range = source.range;\n order = source.order ?? \"asc\";\n let fields: Array<string>;\n if (indexName === \"by_creation_time\") {\n fields = [\"_creationTime\", \"_id\"];\n } else if (indexName === \"by_id\") {\n fields = [\"_id\"];\n } else {\n const index = view.schema?.tables\n .get(tableName)\n ?.indexes.find(\n ({ indexDescriptor }) => indexDescriptor === indexName,\n );\n if (index === undefined) {\n throw new Error(\n `Cannot use index \"${indexName}\" for table \"${tableName}\" because ` +\n `it is not declared in the schema.`,\n );\n }\n fields = index.fields.concat([\"_creationTime\", \"_id\"]);\n }\n fieldPathsToSortBy = fields;\n validateIndexRangeExpression(source, fields);\n docs = view\n .scan(tableName)\n .filter((doc) =>\n source.range.every((filter) => evaluateRangeFilter(doc, filter)),\n );\n break;\n }\n case \"Search\": {\n throw KernelRefusal.requiresBinary(\"textSearch\");\n }\n }\n\n const filters = query.operators\n .filter(\n (operator): operator is { filter: FilterJson } => \"filter\" in operator,\n )\n .map((operator) => operator.filter);\n const limit =\n query.operators.find(\n (operator): operator is { limit: number } => \"limit\" in operator,\n ) ?? null;\n\n const orderMultiplier = order === \"asc\" ? 1 : -1;\n docs.sort((a, b) => {\n for (const fp of fieldPathsToSortBy) {\n const v = compareValues(\n evaluateFieldPath(fp, a),\n evaluateFieldPath(fp, b),\n );\n if (v !== 0) return v * orderMultiplier;\n }\n return 0;\n });\n\n return {\n sortedDocs: docs,\n filterFn: (doc) => filters.every((f) => evaluateFilter(doc, f)),\n limit: limit?.limit ?? null,\n order,\n interval: {\n kind: \"interval\",\n table,\n fields: fieldPathsToSortBy.includes(\"_id\")\n ? fieldPathsToSortBy\n : [...fieldPathsToSortBy, \"_id\"],\n order,\n range,\n },\n };\n};\n\n/**\n * A query's documents in order. The source is read when the query starts, as a\n * snapshot, and consumed lazily. The read set holds the prefix the stream has\n * examined, filtered documents included, or the whole range once it ends.\n */\nexport const stream = (\n view: TableView,\n query: SerializedQuery,\n): Iterator<GenericDocument> => {\n const { sortedDocs, filterFn, limit, interval } = resolveSource(view, query);\n const examined = view.read(interval, null);\n return (function* () {\n let returned = 0;\n for (const doc of sortedDocs) {\n if (limit !== null && returned >= limit) return;\n examined(ReadSet.keyOf(interval.fields, doc as StoredDocument));\n if (filterFn(doc)) {\n returned++;\n yield doc;\n }\n }\n examined(\"end\");\n })();\n};\n\nconst encodeCursor = (\n doc: GenericDocument,\n fieldPaths: ReadonlyArray<string>,\n): string =>\n JSON.stringify(\n fieldPaths.map((fp) => {\n const k = evaluateFieldPath(fp, doc);\n return k === undefined ? null : JSON.stringify(convexToJson(k));\n }),\n );\n\nconst parseCursor = (c: string | null | undefined) =>\n c\n ? (JSON.parse(c) as Array<string | null>).map((k) =>\n k === null ? undefined : jsonToConvex(JSON.parse(k)),\n )\n : null;\n\nconst compareToCursor = (\n doc: GenericDocument,\n cursorKeys: ReadonlyArray<unknown>,\n fieldPaths: ReadonlyArray<string>,\n order: \"asc\" | \"desc\",\n): number => {\n const orderMultiplier = order === \"asc\" ? 1 : -1;\n for (let i = 0; i < fieldPaths.length; i++) {\n const cmp = compareValues(\n evaluateFieldPath(fieldPaths[i]!, doc),\n cursorKeys[i] as any,\n );\n if (cmp !== 0) return cmp * orderMultiplier;\n }\n return 0;\n};\n\nexport interface PageRequest {\n readonly query: SerializedQuery;\n readonly cursor: string | null;\n readonly endCursor?: string | null;\n readonly pageSize: number;\n readonly maximumRowsRead?: number | null;\n readonly maximumBytesRead?: number | null;\n}\n\nexport const paginate = (\n view: TableView,\n {\n query,\n cursor,\n endCursor,\n pageSize,\n maximumRowsRead,\n maximumBytesRead,\n }: PageRequest,\n) => {\n const { sortedDocs, filterFn, order, interval } = resolveSource(view, query);\n // A page from the terminal cursor reads the empty interval past the end, so\n // it depends on nothing.\n if (cursor === \"_end_cursor\") {\n return {\n page: [],\n isDone: true,\n continueCursor: \"_end_cursor\",\n splitCursor: null,\n pageStatus: null,\n };\n }\n const cursorFields = interval.fields;\n const cursorKeys = parseCursor(cursor);\n const hasEndBoundary = !!endCursor && endCursor !== \"_end_cursor\";\n const endCursorKeys = hasEndBoundary ? parseCursor(endCursor) : null;\n const examined = view.read(interval, cursorKeys);\n let reachedEnd = true;\n\n const page: Array<GenericDocument> = [];\n let isInPage = cursor === null;\n let continueCursor = \"_end_cursor\";\n let splitCursor: string | null = null;\n let pageStatus: \"SplitRecommended\" | \"SplitRequired\" | null = null;\n let rowsRead = 0;\n let bytesRead = 0;\n const readDocCursors: Array<string> = [];\n\n for (let i = 0; i < sortedDocs.length; i++) {\n const doc = sortedDocs[i]!;\n const isLastDoc = i === sortedDocs.length - 1;\n if (!isInPage) {\n if (cursorKeys) {\n if (compareToCursor(doc, cursorKeys, cursorFields, order) > 0) {\n isInPage = true;\n } else {\n continue;\n }\n }\n }\n if (hasEndBoundary && endCursorKeys) {\n const cmp = compareToCursor(doc, endCursorKeys, cursorFields, order);\n if (cmp >= 0) {\n reachedEnd = false;\n examined(endCursorKeys);\n if (cmp === 0) {\n rowsRead += 1;\n bytesRead += getDocumentSize(doc);\n readDocCursors.push(encodeCursor(doc, cursorFields));\n if (filterFn(doc)) page.push(doc);\n continueCursor = encodeCursor(doc, cursorFields);\n }\n break;\n }\n }\n rowsRead += 1;\n bytesRead += getDocumentSize(doc);\n readDocCursors.push(encodeCursor(doc, cursorFields));\n examined(ReadSet.keyOf(cursorFields, doc as StoredDocument));\n const hitLimit =\n (!!maximumRowsRead && rowsRead >= maximumRowsRead) ||\n (!!maximumBytesRead && bytesRead >= maximumBytesRead);\n if (filterFn(doc)) page.push(doc);\n if (hitLimit) {\n reachedEnd = false;\n pageStatus = \"SplitRequired\";\n continueCursor = encodeCursor(doc, cursorFields);\n break;\n }\n if (!endCursor && page.length >= pageSize) {\n reachedEnd = isLastDoc;\n if (!isLastDoc) continueCursor = encodeCursor(doc, cursorFields);\n break;\n }\n }\n // A page bounded by an end cursor examined the range only up to it.\n if (reachedEnd) examined(endCursorKeys ?? \"end\");\n\n if (pageStatus === \"SplitRequired\" && readDocCursors.length >= 2) {\n splitCursor = readDocCursors[Math.floor((readDocCursors.length - 1) / 2)]!;\n } else if (\n pageStatus === null &&\n rowsRead > pageSize + 1 &&\n readDocCursors.length >= 2\n ) {\n pageStatus = \"SplitRecommended\";\n splitCursor = readDocCursors[Math.floor((readDocCursors.length - 1) / 2)]!;\n }\n\n return {\n page,\n isDone: continueCursor === \"_end_cursor\",\n continueCursor,\n splitCursor,\n pageStatus,\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"KernelQuery.js","names":["KernelRefusal.requiresBinary","ReadSet.keyOf"],"sources":["../../src/internal/KernelQuery.ts"],"sourcesContent":["// Derived from convex-test 0.0.65 (index.ts, `DatabaseFake`), Copyright Convex,\n// Inc., licensed under the Apache License, Version 2.0\n// (https://www.apache.org/licenses/LICENSE-2.0). The ordering, index range and\n// pagination rules are convex-test's. They read a table through the attempt\n// that runs the query instead of convex-test's database, and record the\n// interval each query examined.\n\nimport type {\n GenericDocument,\n GenericSchema,\n SchemaDefinition,\n} from \"convex/server\";\nimport {\n compareValues,\n convexToJson,\n getDocumentSize,\n jsonToConvex,\n} from \"convex/values\";\nimport * as KernelRefusal from \"./KernelRefusal.js\";\nimport * as ReadSet from \"./ReadSet.js\";\nimport type {\n FilterJson,\n Index,\n SerializedQuery,\n SerializedRangeExpression,\n StoredDocument,\n ValidatorJSON,\n} from \"./KernelValues.js\";\nimport {\n evaluateFieldPath,\n evaluateFilter,\n evaluateRangeFilter,\n isValidIdentifier,\n resolvePendingValues,\n validateFieldNames,\n validateIndexRangeExpression,\n validateValidator,\n} from \"./KernelValues.js\";\n\n/**\n * A component's tables as its schema declares them.\n */\nexport interface KernelSchema {\n readonly schemaValidation: boolean;\n readonly tables: ReadonlyMap<\n string,\n {\n readonly indexes: ReadonlyArray<Index>;\n readonly documentType: ValidatorJSON;\n }\n >;\n}\n\nexport const exportSchema = (\n schema: SchemaDefinition<GenericSchema, boolean> | null | undefined,\n): KernelSchema | null => {\n if (schema === null || schema === undefined) return null;\n const exported: KernelSchema = {\n schemaValidation: (schema as unknown as { schemaValidation: boolean })\n .schemaValidation,\n tables: new Map(\n Object.entries(schema.tables).map(([name, table]) => [\n name,\n (table as unknown as { export: () => any }).export(),\n ]),\n ),\n };\n exported.tables.forEach((table, tableName) => {\n if (!isValidIdentifier(tableName)) {\n throw new Error(\n `Table names must be valid identifiers, got \"${tableName}\"`,\n );\n }\n validateFieldNames(table.documentType);\n table.indexes.forEach(({ indexDescriptor }) => {\n if (!isValidIdentifier(indexDescriptor)) {\n throw new Error(\n `Index names must be valid identifiers, got \"${indexDescriptor}\"`,\n );\n }\n });\n });\n return exported;\n};\n\n/**\n * Check a document against its table's declared type, as the backend does\n * before a write.\n */\nexport const validateDocument = (\n schema: KernelSchema | null,\n tableName: string,\n doc: Record<string, unknown>,\n) => {\n if (schema === null || !schema.schemaValidation) return;\n const validator = schema.tables.get(tableName)?.documentType;\n if (validator === undefined) return;\n validateValidator(validator, resolvePendingValues(doc as GenericDocument));\n};\n\n/**\n * An interval a query reads, before it says where it starts and how far it got,\n * and the index it reads it from.\n */\nexport type Interval = Omit<\n ReadSet.IntervalRead,\n \"componentPath\" | \"after\" | \"through\"\n> & { readonly index: string };\n\n/**\n * What a query reads: one table of one component, as the running attempt sees\n * it. `read` records an interval in the attempt's read set and returns how to\n * advance it. `examine` charges one document the query examined, before the\n * query's filters see it.\n */\nexport interface TableView {\n readonly schema: KernelSchema | null;\n readonly scan: (tableName: string) => ReadonlyArray<StoredDocument>;\n readonly read: (\n interval: Interval,\n after: ReadSet.Key | null,\n ) => (through: ReadSet.Key | \"end\") => void;\n readonly examine: (table: string, doc: GenericDocument) => void;\n}\n\ninterface ResolvedSource {\n readonly sortedDocs: ReadonlyArray<GenericDocument>;\n readonly filterFn: (doc: GenericDocument) => boolean;\n readonly limit: number | null;\n readonly order: \"asc\" | \"desc\";\n readonly interval: Interval;\n}\n\nconst resolveSource = (\n view: TableView,\n query: SerializedQuery,\n): ResolvedSource => {\n const source = query.source;\n let docs: Array<GenericDocument>;\n let fieldPathsToSortBy: Array<string>;\n let order: \"asc\" | \"desc\";\n let table: string;\n let range: ReadonlyArray<SerializedRangeExpression> = [];\n let index: string;\n switch (source.type) {\n case \"FullTableScan\": {\n table = source.tableName;\n // The binary scans a whole table through its creation-time index.\n index = \"by_creation_time\";\n docs = [...view.scan(source.tableName)];\n order = source.order ?? \"asc\";\n fieldPathsToSortBy = [\"_creationTime\"];\n break;\n }\n case \"IndexRange\": {\n const [tableName, indexName] = source.indexName.split(\".\") as [\n string,\n string,\n ];\n table = tableName;\n index = indexName;\n range = source.range;\n order = source.order ?? \"asc\";\n let fields: Array<string>;\n if (indexName === \"by_creation_time\") {\n fields = [\"_creationTime\", \"_id\"];\n } else if (indexName === \"by_id\") {\n fields = [\"_id\"];\n } else {\n const declared = view.schema?.tables\n .get(tableName)\n ?.indexes.find(\n ({ indexDescriptor }) => indexDescriptor === indexName,\n );\n if (declared === undefined) {\n throw new Error(\n `Cannot use index \"${indexName}\" for table \"${tableName}\" because ` +\n `it is not declared in the schema.`,\n );\n }\n fields = declared.fields.concat([\"_creationTime\", \"_id\"]);\n }\n fieldPathsToSortBy = fields;\n validateIndexRangeExpression(source, fields);\n docs = view\n .scan(tableName)\n .filter((doc) =>\n source.range.every((filter) => evaluateRangeFilter(doc, filter)),\n );\n break;\n }\n case \"Search\": {\n throw KernelRefusal.requiresBinary(\"textSearch\");\n }\n }\n\n const filters = query.operators\n .filter(\n (operator): operator is { filter: FilterJson } => \"filter\" in operator,\n )\n .map((operator) => operator.filter);\n const limit =\n query.operators.find(\n (operator): operator is { limit: number } => \"limit\" in operator,\n ) ?? null;\n\n const orderMultiplier = order === \"asc\" ? 1 : -1;\n docs.sort((a, b) => {\n for (const fp of fieldPathsToSortBy) {\n const v = compareValues(\n evaluateFieldPath(fp, a),\n evaluateFieldPath(fp, b),\n );\n if (v !== 0) return v * orderMultiplier;\n }\n return 0;\n });\n\n return {\n sortedDocs: docs,\n filterFn: (doc) => filters.every((f) => evaluateFilter(doc, f)),\n limit: limit?.limit ?? null,\n order,\n interval: {\n kind: \"interval\",\n index,\n table,\n fields: fieldPathsToSortBy.includes(\"_id\")\n ? fieldPathsToSortBy\n : [...fieldPathsToSortBy, \"_id\"],\n order,\n range,\n },\n };\n};\n\n/**\n * A query's documents in order. The source is read when the query starts, as a\n * snapshot, and consumed lazily. The read set holds the prefix the stream has\n * examined, filtered documents included, or the whole range once it ends.\n */\nexport const stream = (\n view: TableView,\n query: SerializedQuery,\n): Iterator<GenericDocument> => {\n const { sortedDocs, filterFn, limit, interval } = resolveSource(view, query);\n const examined = view.read(interval, null);\n return (function* () {\n let returned = 0;\n for (const doc of sortedDocs) {\n if (limit !== null && returned >= limit) return;\n view.examine(interval.table, doc);\n examined(ReadSet.keyOf(interval.fields, doc as StoredDocument));\n if (filterFn(doc)) {\n returned++;\n yield doc;\n }\n }\n examined(\"end\");\n })();\n};\n\nconst encodeCursor = (\n doc: GenericDocument,\n fieldPaths: ReadonlyArray<string>,\n): string =>\n JSON.stringify(\n fieldPaths.map((fp) => {\n const k = evaluateFieldPath(fp, doc);\n return k === undefined ? null : JSON.stringify(convexToJson(k));\n }),\n );\n\nconst parseCursor = (c: string | null | undefined) =>\n c\n ? (JSON.parse(c) as Array<string | null>).map((k) =>\n k === null ? undefined : jsonToConvex(JSON.parse(k)),\n )\n : null;\n\nconst compareToCursor = (\n doc: GenericDocument,\n cursorKeys: ReadonlyArray<unknown>,\n fieldPaths: ReadonlyArray<string>,\n order: \"asc\" | \"desc\",\n): number => {\n const orderMultiplier = order === \"asc\" ? 1 : -1;\n for (let i = 0; i < fieldPaths.length; i++) {\n const cmp = compareValues(\n evaluateFieldPath(fieldPaths[i]!, doc),\n cursorKeys[i] as any,\n );\n if (cmp !== 0) return cmp * orderMultiplier;\n }\n return 0;\n};\n\nexport interface PageRequest {\n readonly query: SerializedQuery;\n readonly cursor: string | null;\n readonly endCursor?: string | null;\n readonly pageSize: number;\n readonly maximumRowsRead?: number | null;\n readonly maximumBytesRead?: number | null;\n}\n\nexport const paginate = (\n view: TableView,\n {\n query,\n cursor,\n endCursor,\n pageSize,\n maximumRowsRead,\n maximumBytesRead,\n }: PageRequest,\n) => {\n const { sortedDocs, filterFn, order, interval } = resolveSource(view, query);\n // A page from the terminal cursor reads the empty interval past the end, so\n // it depends on nothing.\n if (cursor === \"_end_cursor\") {\n return {\n page: [],\n isDone: true,\n continueCursor: \"_end_cursor\",\n splitCursor: null,\n pageStatus: null,\n };\n }\n const cursorFields = interval.fields;\n const cursorKeys = parseCursor(cursor);\n const hasEndBoundary = !!endCursor && endCursor !== \"_end_cursor\";\n const endCursorKeys = hasEndBoundary ? parseCursor(endCursor) : null;\n const examined = view.read(interval, cursorKeys);\n let reachedEnd = true;\n\n const page: Array<GenericDocument> = [];\n let isInPage = cursor === null;\n let continueCursor = \"_end_cursor\";\n let splitCursor: string | null = null;\n let pageStatus: \"SplitRecommended\" | \"SplitRequired\" | null = null;\n let rowsRead = 0;\n let bytesRead = 0;\n const readDocCursors: Array<string> = [];\n\n for (let i = 0; i < sortedDocs.length; i++) {\n const doc = sortedDocs[i]!;\n const isLastDoc = i === sortedDocs.length - 1;\n if (!isInPage) {\n if (cursorKeys) {\n if (compareToCursor(doc, cursorKeys, cursorFields, order) > 0) {\n isInPage = true;\n } else {\n continue;\n }\n }\n }\n if (hasEndBoundary && endCursorKeys) {\n const cmp = compareToCursor(doc, endCursorKeys, cursorFields, order);\n if (cmp >= 0) {\n reachedEnd = false;\n examined(endCursorKeys);\n if (cmp === 0) {\n view.examine(interval.table, doc);\n rowsRead += 1;\n bytesRead += getDocumentSize(doc);\n readDocCursors.push(encodeCursor(doc, cursorFields));\n if (filterFn(doc)) page.push(doc);\n continueCursor = encodeCursor(doc, cursorFields);\n }\n break;\n }\n }\n view.examine(interval.table, doc);\n rowsRead += 1;\n bytesRead += getDocumentSize(doc);\n readDocCursors.push(encodeCursor(doc, cursorFields));\n examined(ReadSet.keyOf(cursorFields, doc as StoredDocument));\n const hitLimit =\n (!!maximumRowsRead && rowsRead >= maximumRowsRead) ||\n (!!maximumBytesRead && bytesRead >= maximumBytesRead);\n if (filterFn(doc)) page.push(doc);\n if (hitLimit) {\n reachedEnd = false;\n pageStatus = \"SplitRequired\";\n continueCursor = encodeCursor(doc, cursorFields);\n break;\n }\n if (!endCursor && page.length >= pageSize) {\n reachedEnd = isLastDoc;\n if (!isLastDoc) continueCursor = encodeCursor(doc, cursorFields);\n break;\n }\n }\n // A page bounded by an end cursor examined the range only up to it.\n if (reachedEnd) examined(endCursorKeys ?? \"end\");\n\n if (pageStatus === \"SplitRequired\" && readDocCursors.length >= 2) {\n splitCursor = readDocCursors[Math.floor((readDocCursors.length - 1) / 2)]!;\n } else if (\n pageStatus === null &&\n rowsRead > pageSize + 1 &&\n readDocCursors.length >= 2\n ) {\n pageStatus = \"SplitRecommended\";\n splitCursor = readDocCursors[Math.floor((readDocCursors.length - 1) / 2)]!;\n }\n\n return {\n page,\n isDone: continueCursor === \"_end_cursor\",\n continueCursor,\n splitCursor,\n pageStatus,\n };\n};\n"],"mappings":"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"}
@@ -0,0 +1,186 @@
1
+ import type { TransactionMetrics } from "convex/server";
2
+ import { type Value } from "convex/values";
3
+ import * as Schema from "effect/Schema";
4
+ import type { SerializedRangeExpression } from "./KernelValues.js";
5
+ /**
6
+ * The ceilings one function execution runs under, the binary's
7
+ * `TransactionLimits` (`crates/database/src/execution_size.rs:62-95`).
8
+ */
9
+ export type Limits = {
10
+ readonly [K in keyof TransactionMetrics]: number;
11
+ };
12
+ /**
13
+ * The binary's default ceilings (`crates/common/src/knobs.rs`).
14
+ */
15
+ export declare const defaults: Limits;
16
+ /**
17
+ * What a nested call may use on top of what its transaction has used, as
18
+ * `ctx.runQuery` and `ctx.runMutation` take it in `transactionLimits`.
19
+ */
20
+ export type Budget = Partial<Limits>;
21
+ /**
22
+ * A nested call's ceilings: each one what the transaction has used plus the
23
+ * call's budget, never above the caller's (`TransactionLimits::from_budget`,
24
+ * execution_size.rs:97-160). An unset field leaves the caller's ceiling.
25
+ */
26
+ export declare const fromBudget: (budget: Budget, used: Limits, ceiling: Limits) => Limits;
27
+ declare const TransactionLimitExceeded_base: Schema.Class<TransactionLimitExceeded, Schema.TaggedStruct<"TransactionLimitExceeded", {
28
+ readonly code: Schema.Literals<readonly ["TooManyReads", "TooManyDocumentsRead", "TooManyBytesRead", "TooManyWrites", "TooManyBytesWritten", "TooManyFunctionsScheduled", "ScheduledFunctionsArgumentsTooLarge"]>;
29
+ readonly limit: Schema.Int;
30
+ readonly message: Schema.String;
31
+ }>, import("effect/Cause").YieldableError>;
32
+ /**
33
+ * A function execution crossed one of its ceilings. `code` is the binary's
34
+ * short message and `message` its text, which the function receives as a
35
+ * catchable error.
36
+ */
37
+ export declare class TransactionLimitExceeded extends TransactionLimitExceeded_base {
38
+ }
39
+ /**
40
+ * `common::fmt::format_bytes` (`crates/common/src/fmt.rs:29-47`).
41
+ */
42
+ export declare const formatBytes: (n: number) => string;
43
+ /**
44
+ * A position in an index's key space: just before, or just after, every key
45
+ * that starts with `key`.
46
+ */
47
+ interface Bound {
48
+ readonly key: ReadonlyArray<Value | undefined>;
49
+ readonly side: -1 | 1;
50
+ }
51
+ /**
52
+ * The keys from `start` up to `end`.
53
+ */
54
+ export interface KeyInterval {
55
+ readonly start: Bound;
56
+ readonly end: Bound;
57
+ }
58
+ /**
59
+ * The one key `key`, as a read or write of a document by ID records it.
60
+ */
61
+ export declare const point: (key: ReadonlyArray<Value | undefined>) => KeyInterval;
62
+ /**
63
+ * Every key of an index.
64
+ */
65
+ export declare const everything: KeyInterval;
66
+ /**
67
+ * The part of an index range a query has examined: from where it starts, past
68
+ * the cursor `after`, through `through` in its order. `null` when it has
69
+ * examined nothing.
70
+ */
71
+ export declare const examined: (range: ReadonlyArray<SerializedRangeExpression>, order: "asc" | "desc", after: ReadonlyArray<Value | undefined> | null, through: ReadonlyArray<Value | undefined> | "end") => KeyInterval | null;
72
+ /**
73
+ * The intervals read from one index, overlapping and adjacent ones merged
74
+ * (`IntervalSet::add`, `crates/common/src/interval/interval_set.rs:150-230`).
75
+ * They stay sorted and disjoint, so an add touches only its neighbours.
76
+ */
77
+ declare class IntervalSet {
78
+ private readonly intervals;
79
+ get size(): number;
80
+ add(interval: KeyInterval): void;
81
+ }
82
+ /**
83
+ * The counters every frame of one attempt shares: reads and schedules stay when
84
+ * a nested mutation fails, as the binary keeps them outside its subtransaction
85
+ * token (`crates/database/src/transaction.rs:418-454`).
86
+ */
87
+ declare class Shared {
88
+ readonly indexes: Map<string, IntervalSet>;
89
+ intervals: number;
90
+ documentsRead: number;
91
+ bytesRead: number;
92
+ functionsScheduled: number;
93
+ scheduledFunctionArgsBytes: number;
94
+ readonly filesRead = 0;
95
+ readonly fileReadBytes = 0;
96
+ /**
97
+ * A stale-snapshot query's counters: the scheduling and file-read counters
98
+ * carried over, reads and intervals from zero (`clone_for_snapshot_query`,
99
+ * `crates/database/src/transaction.rs:1460-1485`).
100
+ */
101
+ forStaleSnapshot(): Shared;
102
+ }
103
+ /**
104
+ * One frame's share of an attempt's accounting. The outermost frame belongs to
105
+ * the top-level function and each nested mutation gets a child. A child's
106
+ * writes count toward its caller's once it merges and are forgotten when it
107
+ * fails, as the binary's nested write set rolls its sizes back
108
+ * (`crates/database/src/writes.rs:96-136`).
109
+ */
110
+ export declare class Meter {
111
+ private readonly shared;
112
+ private readonly parent?;
113
+ private documentsWritten;
114
+ private bytesWritten;
115
+ private readonly filesWritten;
116
+ private readonly fileWriteBytes;
117
+ constructor(shared?: Shared, parent?: Meter | undefined);
118
+ child(): Meter;
119
+ /**
120
+ * The meter of a query run on a stale snapshot. The binary runs it on a clone
121
+ * of the transaction that keeps the scheduling counters and starts reads and
122
+ * writes from zero, then drops the clone, so nothing it counts reaches this
123
+ * meter (`clone_for_snapshot_query`,
124
+ * `crates/database/src/transaction.rs:1460-1485`; `run_udf`,
125
+ * `crates/isolate/src/environment/udf/async_syscall.rs:695-776`).
126
+ */
127
+ forStaleSnapshot(): Meter;
128
+ /**
129
+ * The child's writes become its caller's.
130
+ */
131
+ merge(): void;
132
+ private chain;
133
+ /**
134
+ * How much of each limit the execution has used.
135
+ */
136
+ used(): Limits;
137
+ /**
138
+ * What `getTransactionMetrics` answers (`tx_metrics`,
139
+ * `crates/isolate/src/environment/udf/async_syscall.rs:947-993`).
140
+ */
141
+ metrics(limits: Limits): TransactionMetrics;
142
+ private set;
143
+ /**
144
+ * A read the function made of `interval` in `index`, counted as the binary
145
+ * counts it: the total drops by the index's interval count before the read,
146
+ * saturating at zero, and rises by its count after
147
+ * (`record_indexed_directly`, `crates/database/src/reads.rs:447-480`). The
148
+ * index's count includes the intervals of IDs this execution inserted, which
149
+ * joined it without counting (`deriveInterval`), so a read that covers them
150
+ * can move the total by more than the read itself. The binary starts the
151
+ * total at the system-table intervals it reads to run the function, which the
152
+ * kernel does not simulate. While neither total saturates they differ by
153
+ * exactly that share. A read that saturates only the kernel's lower total
154
+ * narrows the gap, and it never widens or turns negative (see the
155
+ * `SystemTableReadIntervals` boundary).
156
+ */
157
+ readInterval(index: string, interval: KeyInterval, limits: Limits): TransactionLimitExceeded | undefined;
158
+ /**
159
+ * A read the binary takes for a write rather than for the function: a new
160
+ * document's ID (`register_new_id`, writes.rs:426-441). It joins the index's
161
+ * intervals and leaves the total as it was (`record_indexed_derived` calls
162
+ * `_record_indexed` and ignores its counts, reads.rs:333-344), so a later
163
+ * read of that index starts from a count that includes it.
164
+ */
165
+ deriveInterval(index: string, interval: KeyInterval): void;
166
+ /**
167
+ * A document the function read. A system table's document counts toward no
168
+ * limit (`record_read_document`, reads.rs:365-445).
169
+ */
170
+ readDocument(document: Value, system: boolean, limits: Limits): TransactionLimitExceeded | undefined;
171
+ /**
172
+ * A write to a user table: the new document, or `null` for a delete. Each
173
+ * write counts, even to a document this execution already wrote
174
+ * (`Writes::update`, writes.rs:241-310).
175
+ */
176
+ write(document: Value | null, limits: Limits): TransactionLimitExceeded | undefined;
177
+ /**
178
+ * A function scheduled with `args`. The binary checks before it counts, so a
179
+ * refused schedule counts nothing (`check_scheduling_limits`,
180
+ * `crates/model/src/scheduled_jobs/mod.rs:162-196`). Its arguments are sized
181
+ * as the one-element array the scheduler stores.
182
+ */
183
+ schedule(args: Value, limits: Limits): TransactionLimitExceeded | undefined;
184
+ }
185
+ export {};
186
+ //# sourceMappingURL=Metering.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Metering.d.ts","sourceRoot":"","sources":["../../src/internal/Metering.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,401 @@
1
+ import { evaluateQueryValue } from "./KernelValues.js";
2
+ import * as Schema from "effect/Schema";
3
+ import { compareValues, getConvexSize } from "convex/values";
4
+ //#region src/internal/Metering.ts
5
+ /**
6
+ * The binary's default ceilings (`crates/common/src/knobs.rs`).
7
+ */
8
+ const defaults = {
9
+ bytesRead: 1 << 24,
10
+ documentsRead: 32e3,
11
+ databaseQueries: 4096,
12
+ documentsWritten: 16e3,
13
+ bytesWritten: 1 << 24,
14
+ functionsScheduled: 1e3,
15
+ scheduledFunctionArgsBytes: 1 << 24,
16
+ filesWritten: 10,
17
+ fileWriteBytes: 1 << 24,
18
+ filesRead: 10,
19
+ fileReadBytes: 1 << 24
20
+ };
21
+ /**
22
+ * A nested call's ceilings: each one what the transaction has used plus the
23
+ * call's budget, never above the caller's (`TransactionLimits::from_budget`,
24
+ * execution_size.rs:97-160). An unset field leaves the caller's ceiling.
25
+ */
26
+ const fromBudget = (budget, used, ceiling) => Object.fromEntries(Object.keys(ceiling).map((metric) => [metric, Math.min(used[metric] + (budget[metric] ?? ceiling[metric]), ceiling[metric])]));
27
+ const overLimitHelp = "Consider using smaller limits in your queries, paginating your queries, or using indexed queries with a selective index range expressions.";
28
+ /**
29
+ * A function execution crossed one of its ceilings. `code` is the binary's
30
+ * short message and `message` its text, which the function receives as a
31
+ * catchable error.
32
+ */
33
+ var TransactionLimitExceeded = class extends Schema.TaggedError()("TransactionLimitExceeded", {
34
+ code: Schema.Literals([
35
+ "TooManyReads",
36
+ "TooManyDocumentsRead",
37
+ "TooManyBytesRead",
38
+ "TooManyWrites",
39
+ "TooManyBytesWritten",
40
+ "TooManyFunctionsScheduled",
41
+ "ScheduledFunctionsArgumentsTooLarge"
42
+ ]),
43
+ limit: Schema.Int,
44
+ message: Schema.String
45
+ }) {};
46
+ const byteUnits = [
47
+ [2 ** 30, "GiB"],
48
+ [1e9, "GB"],
49
+ [2 ** 20, "MiB"],
50
+ [1e6, "MB"],
51
+ [1024, "KiB"],
52
+ [1e3, "KB"]
53
+ ];
54
+ /**
55
+ * `common::fmt::format_bytes` (`crates/common/src/fmt.rs:29-47`).
56
+ */
57
+ const formatBytes = (n) => {
58
+ if (n === 0) return "0 bytes";
59
+ for (const [unit, name] of byteUnits) {
60
+ if (n < unit) continue;
61
+ if (n % unit === 0) return `${n / unit} ${name}`;
62
+ if (n * 10 % unit === 0) return `${Math.floor(n / unit)}.${Math.floor(n * 10 / unit) % 10} ${name}`;
63
+ }
64
+ return `${n} bytes`;
65
+ };
66
+ const compareBounds = (a, b) => {
67
+ const shared = Math.min(a.key.length, b.key.length);
68
+ for (let i = 0; i < shared; i++) {
69
+ const cmp = compareValues(a.key[i], b.key[i]);
70
+ if (cmp !== 0) return cmp;
71
+ }
72
+ if (a.key.length === b.key.length) return a.side - b.side;
73
+ return a.key.length < b.key.length ? a.side : -b.side;
74
+ };
75
+ const maxBound = (a, b) => compareBounds(a, b) >= 0 ? a : b;
76
+ const minBound = (a, b) => compareBounds(a, b) <= 0 ? a : b;
77
+ /**
78
+ * The one key `key`, as a read or write of a document by ID records it.
79
+ */
80
+ const point = (key) => ({
81
+ start: {
82
+ key,
83
+ side: -1
84
+ },
85
+ end: {
86
+ key,
87
+ side: 1
88
+ }
89
+ });
90
+ /**
91
+ * Every key of an index.
92
+ */
93
+ const everything = point([]);
94
+ /**
95
+ * The part of an index range a query has examined: from where it starts, past
96
+ * the cursor `after`, through `through` in its order. `null` when it has
97
+ * examined nothing.
98
+ */
99
+ const examined = (range, order, after, through) => {
100
+ const equal = [];
101
+ let start;
102
+ let end;
103
+ for (const expression of range) {
104
+ const value = evaluateQueryValue(expression.value);
105
+ switch (expression.type) {
106
+ case "Eq":
107
+ equal.push(value);
108
+ break;
109
+ case "Gt":
110
+ case "Gte":
111
+ start = {
112
+ key: [...equal, value],
113
+ side: expression.type === "Gt" ? 1 : -1
114
+ };
115
+ break;
116
+ case "Lt":
117
+ case "Lte": end = {
118
+ key: [...equal, value],
119
+ side: expression.type === "Lt" ? -1 : 1
120
+ };
121
+ }
122
+ }
123
+ start ??= {
124
+ key: equal,
125
+ side: -1
126
+ };
127
+ end ??= {
128
+ key: equal,
129
+ side: 1
130
+ };
131
+ if (after !== null) {
132
+ if (order === "asc") start = maxBound(start, {
133
+ key: after,
134
+ side: 1
135
+ });
136
+ else end = minBound(end, {
137
+ key: after,
138
+ side: -1
139
+ });
140
+ }
141
+ if (through !== "end") {
142
+ if (order === "asc") end = minBound(end, {
143
+ key: through,
144
+ side: 1
145
+ });
146
+ else start = maxBound(start, {
147
+ key: through,
148
+ side: -1
149
+ });
150
+ }
151
+ return compareBounds(start, end) < 0 ? {
152
+ start,
153
+ end
154
+ } : null;
155
+ };
156
+ /**
157
+ * The intervals read from one index, overlapping and adjacent ones merged
158
+ * (`IntervalSet::add`, `crates/common/src/interval/interval_set.rs:150-230`).
159
+ * They stay sorted and disjoint, so an add touches only its neighbours.
160
+ */
161
+ var IntervalSet = class {
162
+ intervals = [];
163
+ get size() {
164
+ return this.intervals.length;
165
+ }
166
+ add(interval) {
167
+ const intervals = this.intervals;
168
+ let low = 0;
169
+ let high = intervals.length;
170
+ while (low < high) {
171
+ const middle = low + high >> 1;
172
+ if (compareBounds(intervals[middle].end, interval.start) < 0) low = middle + 1;
173
+ else high = middle;
174
+ }
175
+ let last = low;
176
+ let merged = interval;
177
+ while (last < intervals.length && compareBounds(intervals[last].start, interval.end) <= 0) {
178
+ const other = intervals[last];
179
+ merged = {
180
+ start: minBound(merged.start, other.start),
181
+ end: maxBound(merged.end, other.end)
182
+ };
183
+ last++;
184
+ }
185
+ intervals.splice(low, last - low, merged);
186
+ }
187
+ };
188
+ /**
189
+ * The counters every frame of one attempt shares: reads and schedules stay when
190
+ * a nested mutation fails, as the binary keeps them outside its subtransaction
191
+ * token (`crates/database/src/transaction.rs:418-454`).
192
+ */
193
+ var Shared = class Shared {
194
+ indexes = /* @__PURE__ */ new Map();
195
+ intervals = 0;
196
+ documentsRead = 0;
197
+ bytesRead = 0;
198
+ functionsScheduled = 0;
199
+ scheduledFunctionArgsBytes = 0;
200
+ filesRead = 0;
201
+ fileReadBytes = 0;
202
+ /**
203
+ * A stale-snapshot query's counters: the scheduling and file-read counters
204
+ * carried over, reads and intervals from zero (`clone_for_snapshot_query`,
205
+ * `crates/database/src/transaction.rs:1460-1485`).
206
+ */
207
+ forStaleSnapshot() {
208
+ const copy = new Shared();
209
+ copy.functionsScheduled = this.functionsScheduled;
210
+ copy.scheduledFunctionArgsBytes = this.scheduledFunctionArgsBytes;
211
+ return copy;
212
+ }
213
+ };
214
+ /**
215
+ * `a - b`, or 0 when `b` is larger: Rust's `usize::saturating_sub`.
216
+ */
217
+ const saturatingSub = (a, b) => a > b ? a - b : 0;
218
+ /**
219
+ * One frame's share of an attempt's accounting. The outermost frame belongs to
220
+ * the top-level function and each nested mutation gets a child. A child's
221
+ * writes count toward its caller's once it merges and are forgotten when it
222
+ * fails, as the binary's nested write set rolls its sizes back
223
+ * (`crates/database/src/writes.rs:96-136`).
224
+ */
225
+ var Meter = class Meter {
226
+ shared;
227
+ parent;
228
+ documentsWritten = 0;
229
+ bytesWritten = 0;
230
+ filesWritten = 0;
231
+ fileWriteBytes = 0;
232
+ constructor(shared = new Shared(), parent) {
233
+ this.shared = shared;
234
+ this.parent = parent;
235
+ }
236
+ child() {
237
+ return new Meter(this.shared, this);
238
+ }
239
+ /**
240
+ * The meter of a query run on a stale snapshot. The binary runs it on a clone
241
+ * of the transaction that keeps the scheduling counters and starts reads and
242
+ * writes from zero, then drops the clone, so nothing it counts reaches this
243
+ * meter (`clone_for_snapshot_query`,
244
+ * `crates/database/src/transaction.rs:1460-1485`; `run_udf`,
245
+ * `crates/isolate/src/environment/udf/async_syscall.rs:695-776`).
246
+ */
247
+ forStaleSnapshot() {
248
+ return new Meter(this.shared.forStaleSnapshot());
249
+ }
250
+ /**
251
+ * The child's writes become its caller's.
252
+ */
253
+ merge() {
254
+ if (this.parent === void 0) throw new Error("The outermost meter of an attempt has no caller.");
255
+ this.parent.documentsWritten += this.documentsWritten;
256
+ this.parent.bytesWritten += this.bytesWritten;
257
+ }
258
+ chain() {
259
+ return this.parent === void 0 ? [this] : [...this.parent.chain(), this];
260
+ }
261
+ /**
262
+ * How much of each limit the execution has used.
263
+ */
264
+ used() {
265
+ const frames = this.chain();
266
+ const sum = (of) => frames.reduce((total, meter) => total + of(meter), 0);
267
+ const shared = this.shared;
268
+ return {
269
+ bytesRead: shared.bytesRead,
270
+ documentsRead: shared.documentsRead,
271
+ databaseQueries: shared.intervals,
272
+ documentsWritten: sum((meter) => meter.documentsWritten),
273
+ bytesWritten: sum((meter) => meter.bytesWritten),
274
+ functionsScheduled: shared.functionsScheduled,
275
+ scheduledFunctionArgsBytes: shared.scheduledFunctionArgsBytes,
276
+ filesWritten: sum((meter) => meter.filesWritten),
277
+ fileWriteBytes: sum((meter) => meter.fileWriteBytes),
278
+ filesRead: shared.filesRead,
279
+ fileReadBytes: shared.fileReadBytes
280
+ };
281
+ }
282
+ /**
283
+ * What `getTransactionMetrics` answers (`tx_metrics`,
284
+ * `crates/isolate/src/environment/udf/async_syscall.rs:947-993`).
285
+ */
286
+ metrics(limits) {
287
+ const used = this.used();
288
+ return Object.fromEntries(Object.keys(limits).map((metric) => [metric, {
289
+ used: used[metric],
290
+ remaining: limits[metric] - used[metric]
291
+ }]));
292
+ }
293
+ set(index) {
294
+ const set = this.shared.indexes.get(index) ?? new IntervalSet();
295
+ this.shared.indexes.set(index, set);
296
+ return set;
297
+ }
298
+ /**
299
+ * A read the function made of `interval` in `index`, counted as the binary
300
+ * counts it: the total drops by the index's interval count before the read,
301
+ * saturating at zero, and rises by its count after
302
+ * (`record_indexed_directly`, `crates/database/src/reads.rs:447-480`). The
303
+ * index's count includes the intervals of IDs this execution inserted, which
304
+ * joined it without counting (`deriveInterval`), so a read that covers them
305
+ * can move the total by more than the read itself. The binary starts the
306
+ * total at the system-table intervals it reads to run the function, which the
307
+ * kernel does not simulate. While neither total saturates they differ by
308
+ * exactly that share. A read that saturates only the kernel's lower total
309
+ * narrows the gap, and it never widens or turns negative (see the
310
+ * `SystemTableReadIntervals` boundary).
311
+ */
312
+ readInterval(index, interval, limits) {
313
+ const set = this.set(index);
314
+ const before = set.size;
315
+ set.add(interval);
316
+ const shared = this.shared;
317
+ shared.intervals = saturatingSub(shared.intervals, before) + set.size;
318
+ if (!Number.isSafeInteger(shared.intervals) || shared.intervals < 0) throw new Error(`The kernel counted ${shared.intervals} intervals read, which is no count.`);
319
+ return shared.intervals > limits.databaseQueries ? new TransactionLimitExceeded({
320
+ code: "TooManyReads",
321
+ limit: limits.databaseQueries,
322
+ message: `Too many reads in a single function execution (limit: ${limits.databaseQueries}). ${overLimitHelp}`
323
+ }) : void 0;
324
+ }
325
+ /**
326
+ * A read the binary takes for a write rather than for the function: a new
327
+ * document's ID (`register_new_id`, writes.rs:426-441). It joins the index's
328
+ * intervals and leaves the total as it was (`record_indexed_derived` calls
329
+ * `_record_indexed` and ignores its counts, reads.rs:333-344), so a later
330
+ * read of that index starts from a count that includes it.
331
+ */
332
+ deriveInterval(index, interval) {
333
+ this.set(index).add(interval);
334
+ }
335
+ /**
336
+ * A document the function read. A system table's document counts toward no
337
+ * limit (`record_read_document`, reads.rs:365-445).
338
+ */
339
+ readDocument(document, system, limits) {
340
+ if (system) return void 0;
341
+ const shared = this.shared;
342
+ shared.documentsRead += 1;
343
+ shared.bytesRead += getConvexSize(document);
344
+ if (shared.documentsRead > limits.documentsRead) return new TransactionLimitExceeded({
345
+ code: "TooManyDocumentsRead",
346
+ limit: limits.documentsRead,
347
+ message: `Too many documents read in a single function execution (limit: ${limits.documentsRead}). ${overLimitHelp}`
348
+ });
349
+ if (shared.bytesRead > limits.bytesRead) return new TransactionLimitExceeded({
350
+ code: "TooManyBytesRead",
351
+ limit: limits.bytesRead,
352
+ message: `Too many bytes read in a single function execution (limit: ${limits.bytesRead} bytes). ${overLimitHelp}`
353
+ });
354
+ }
355
+ /**
356
+ * A write to a user table: the new document, or `null` for a delete. Each
357
+ * write counts, even to a document this execution already wrote
358
+ * (`Writes::update`, writes.rs:241-310).
359
+ */
360
+ write(document, limits) {
361
+ this.documentsWritten += 1;
362
+ this.bytesWritten += document === null ? 0 : getConvexSize(document);
363
+ const used = this.used();
364
+ if (used.documentsWritten > limits.documentsWritten) return new TransactionLimitExceeded({
365
+ code: "TooManyWrites",
366
+ limit: limits.documentsWritten,
367
+ message: `Too many writes in a single function execution (limit: ${limits.documentsWritten})`
368
+ });
369
+ if (used.bytesWritten > limits.bytesWritten) return new TransactionLimitExceeded({
370
+ code: "TooManyBytesWritten",
371
+ limit: limits.bytesWritten,
372
+ message: `Too many bytes written in a single function execution (limit: ${formatBytes(limits.bytesWritten)})`
373
+ });
374
+ }
375
+ /**
376
+ * A function scheduled with `args`. The binary checks before it counts, so a
377
+ * refused schedule counts nothing (`check_scheduling_limits`,
378
+ * `crates/model/src/scheduled_jobs/mod.rs:162-196`). Its arguments are sized
379
+ * as the one-element array the scheduler stores.
380
+ */
381
+ schedule(args, limits) {
382
+ const shared = this.shared;
383
+ const size = getConvexSize([args]);
384
+ if (shared.functionsScheduled >= limits.functionsScheduled) return new TransactionLimitExceeded({
385
+ code: "TooManyFunctionsScheduled",
386
+ limit: limits.functionsScheduled,
387
+ message: `Too many functions scheduled by this mutation (limit: ${limits.functionsScheduled})`
388
+ });
389
+ if (shared.scheduledFunctionArgsBytes + size > limits.scheduledFunctionArgsBytes) return new TransactionLimitExceeded({
390
+ code: "ScheduledFunctionsArgumentsTooLarge",
391
+ limit: limits.scheduledFunctionArgsBytes,
392
+ message: `Too large total size of the arguments of scheduled functions from this mutation (limit: ${limits.scheduledFunctionArgsBytes} bytes)`
393
+ });
394
+ shared.functionsScheduled += 1;
395
+ shared.scheduledFunctionArgsBytes += size;
396
+ }
397
+ };
398
+ //#endregion
399
+ export { Meter, TransactionLimitExceeded, defaults, everything, examined, formatBytes, fromBudget, point };
400
+
401
+ //# sourceMappingURL=Metering.js.map