@fitzzero/quickdraw-core 5.0.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +343 -49
- package/dist/{admin-BiU4LRZS.d.ts → admin-DKwkIh5i.d.ts} +25 -10
- package/dist/{adminSchemas-k5a1Ov7b.d.ts → adminSchemas-2DUA_hZ8.d.ts} +15 -9
- package/dist/{auth-CMyRkfjZ.d.ts → auth-BhWqdyb-.d.ts} +2 -2
- package/dist/{caller-DHzK3oiI.d.ts → caller-CXTFswBO.d.ts} +28 -6
- package/dist/{chunk-2XJFNVHW.js → chunk-2PWMUVC7.js} +1 -1
- package/dist/{chunk-2XJFNVHW.js.map → chunk-2PWMUVC7.js.map} +1 -1
- package/dist/{chunk-5IC5VZ7T.js → chunk-2UELNHDD.js} +3 -3
- package/dist/{chunk-XFDOGTMC.js → chunk-3674EIRT.js} +4 -4
- package/dist/{chunk-DNCAI3KY.js → chunk-4DIQHASK.js} +1 -1
- package/dist/chunk-4DIQHASK.js.map +1 -0
- package/dist/{chunk-LLLYILEP.js → chunk-A6DJBGOG.js} +411 -43
- package/dist/chunk-A6DJBGOG.js.map +1 -0
- package/dist/{chunk-B2UQ27GH.js → chunk-CDKG4TFA.js} +25 -5
- package/dist/{chunk-B2UQ27GH.js.map → chunk-CDKG4TFA.js.map} +1 -1
- package/dist/{chunk-IRGAGRVG.js → chunk-JZBCLNDU.js} +4 -4
- package/dist/{chunk-XUSN5WKU.js → chunk-KNDDCVQJ.js} +4 -4
- package/dist/{chunk-5YRQ26LF.js → chunk-RT6OILOI.js} +1 -1
- package/dist/chunk-RT6OILOI.js.map +1 -0
- package/dist/{chunk-7QIY2DDX.js → chunk-VJTFXHOK.js} +2 -2
- package/dist/{chunk-7QIY2DDX.js.map → chunk-VJTFXHOK.js.map} +1 -1
- package/dist/chunk-XNLWBZIG.js +9 -0
- package/dist/chunk-XNLWBZIG.js.map +1 -0
- package/dist/cli/quickdraw-docs.js +241 -27
- package/dist/cli/quickdraw-docs.js.map +1 -1
- package/dist/client/index.d.ts +7 -7
- package/dist/client/index.js +5 -5
- package/dist/{clientTypes-B3o_99Xq.d.ts → clientTypes-Bc-G8n-U.d.ts} +3 -3
- package/dist/{connection-DuN8GvcD.d.ts → connection-Cr1BU07W.d.ts} +2 -2
- package/dist/{createServer-DUacdfKl.d.ts → createServer-CP9eDHZZ.d.ts} +30 -9
- package/dist/{createTestApp-DFLXoTKm.d.ts → createTestApp-UKVGEmOf.d.ts} +6 -6
- package/dist/{envelope-nf5eGB-a.d.ts → envelope-Cp3YcVxC.d.ts} +1 -1
- package/dist/{http--uoooIH5.d.ts → http-Djj2Inuu.d.ts} +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +27 -7
- package/dist/index.js.map +1 -1
- package/dist/{infer-Dw21kmLw.d.ts → infer-Teo1xpsd.d.ts} +17 -0
- package/dist/{metrics-C0WvB7-I.d.ts → metrics-BdBu5o8G.d.ts} +2 -2
- package/dist/prisma/index.d.ts +4 -4
- package/dist/prisma/index.js +1 -1
- package/dist/server/auth/index.d.ts +14 -5
- package/dist/server/auth/index.js +0 -5
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/index.d.ts +120 -33
- package/dist/server/index.js +146 -193
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/index.d.ts +51 -11
- package/dist/server/mcp/index.js +129 -8
- package/dist/server/mcp/index.js.map +1 -1
- package/dist/server/otel.d.ts +3 -3
- package/dist/{sharing-n4ss-ONM.d.ts → sharing-DVa8W-yR.d.ts} +3 -3
- package/dist/testing/client.d.ts +13 -13
- package/dist/testing/client.js +6 -6
- package/dist/testing/index.d.ts +156 -12
- package/dist/testing/index.js +1037 -22
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/mock.d.ts +5 -5
- package/dist/testing/mock.js +5 -5
- package/dist/{types-Bwg3kIEc.d.ts → types-Cu0vmx7c.d.ts} +8 -2
- package/dist/utils/index.d.ts +1 -1
- package/package.json +1 -1
- package/dist/chunk-5YRQ26LF.js.map +0 -1
- package/dist/chunk-DNCAI3KY.js.map +0 -1
- package/dist/chunk-LLLYILEP.js.map +0 -1
- /package/dist/{chunk-5IC5VZ7T.js.map → chunk-2UELNHDD.js.map} +0 -0
- /package/dist/{chunk-XFDOGTMC.js.map → chunk-3674EIRT.js.map} +0 -0
- /package/dist/{chunk-IRGAGRVG.js.map → chunk-JZBCLNDU.js.map} +0 -0
- /package/dist/{chunk-XUSN5WKU.js.map → chunk-KNDDCVQJ.js.map} +0 -0
package/dist/testing/mock.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { A as AccessLevel, ae as ContractMap, J as AnyContract, ap as MethodName, al as InputOf, ar as OutputOf, af as EntityOf, ac as CollectionName, aw as ScopeOf, an as ItemOf, az as StreamName, am as IsScopedStream, ay as StreamItemOf, aa as ChannelName, a9 as ChannelInputOf, ag as EventName, ah as EventPayloadOf } from '../infer-
|
|
1
|
+
import { A as AccessLevel, ae as ContractMap, J as AnyContract, ap as MethodName, al as InputOf, ar as OutputOf, af as EntityOf, ac as CollectionName, aw as ScopeOf, an as ItemOf, az as StreamName, am as IsScopedStream, ay as StreamItemOf, aa as ChannelName, a9 as ChannelInputOf, ag as EventName, ah as EventPayloadOf } from '../infer-Teo1xpsd.js';
|
|
2
2
|
import { QueryClient } from '@tanstack/react-query';
|
|
3
3
|
import { ReactNode, ReactElement } from 'react';
|
|
4
|
-
import { i as MethodMember, E as EntityMembers, C as CollectionMember, y as StreamMember, u as ChannelMember, v as EventMember, n as QuickdrawInvalidate } from '../clientTypes-
|
|
5
|
-
import { H as AdminMethodsOf } from '../admin-
|
|
4
|
+
import { i as MethodMember, E as EntityMembers, C as CollectionMember, y as StreamMember, u as ChannelMember, v as EventMember, n as QuickdrawInvalidate } from '../clientTypes-Bc-G8n-U.js';
|
|
5
|
+
import { H as AdminMethodsOf } from '../admin-DKwkIh5i.js';
|
|
6
6
|
import { Q as QuickdrawError } from '../errors-S-bxEIBC.js';
|
|
7
7
|
import '../keys-CqKkljVN.js';
|
|
8
|
-
import '../adminSchemas-
|
|
9
|
-
import '../envelope-
|
|
8
|
+
import '../adminSchemas-2DUA_hZ8.js';
|
|
9
|
+
import '../envelope-Cp3YcVxC.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* How a mocked method answers. Until one of the `mock*` setters runs, every
|
package/dist/testing/mock.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createMockClient
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
7
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-3674EIRT.js";
|
|
4
|
+
import "../chunk-KNDDCVQJ.js";
|
|
5
|
+
import "../chunk-CDKG4TFA.js";
|
|
6
|
+
import "../chunk-VJTFXHOK.js";
|
|
7
|
+
import "../chunk-RT6OILOI.js";
|
|
8
8
|
import "../chunk-47AEXVDM.js";
|
|
9
9
|
import "../chunk-Z2LZ7IZ7.js";
|
|
10
10
|
import "../chunk-YSWH776Y.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as AccessLevel, ae as ContractMap } from './infer-
|
|
1
|
+
import { A as AccessLevel, ae as ContractMap } from './infer-Teo1xpsd.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Who is calling. A transport authenticates the connection or request and
|
|
@@ -48,6 +48,12 @@ type DbOf<T extends QuickdrawTypes> = T extends {
|
|
|
48
48
|
type PrincipalOf<T extends QuickdrawTypes> = T extends {
|
|
49
49
|
readonly principal: infer P extends Principal;
|
|
50
50
|
} ? P : Principal;
|
|
51
|
+
/**
|
|
52
|
+
* The kinds of the app's principal (`"user" | "agent"` when it declares
|
|
53
|
+
* `kind: "user" | "agent"`): what a `kinds` list names. Any string with the
|
|
54
|
+
* base {@link Principal}.
|
|
55
|
+
*/
|
|
56
|
+
type PrincipalKindOf<T extends QuickdrawTypes> = NonNullable<PrincipalOf<T>["kind"]>;
|
|
51
57
|
/** The fields `initQuickdraw({ context })` adds to every handler's `ctx`. */
|
|
52
58
|
type ContextExtensionOf<T extends QuickdrawTypes> = T extends {
|
|
53
59
|
readonly context: infer Extension extends object;
|
|
@@ -63,4 +69,4 @@ type Transport = "socket" | "http" | "mcp" | "internal" | "legacy";
|
|
|
63
69
|
/** A value, or a promise of it. */
|
|
64
70
|
type MaybePromise<T> = T | PromiseLike<T>;
|
|
65
71
|
|
|
66
|
-
export type { ContextExtensionOf as C, DbOf as D, MaybePromise as M, Principal as P, QuickdrawTypes as Q, Transport as T, McpContext as a, McpContextOf as b, PrincipalOf as c };
|
|
72
|
+
export type { ContextExtensionOf as C, DbOf as D, MaybePromise as M, Principal as P, QuickdrawTypes as Q, Transport as T, McpContext as a, McpContextOf as b, PrincipalOf as c, PrincipalKindOf as d };
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { M as MethodQueryKey } from '../keys-CqKkljVN.js';
|
|
2
2
|
export { C as CollectionQueryKey, E as EntityQueryKey, K as KEY_ROOT, b as MethodKeyPrefix, S as ServiceKeyPrefix, c as collectionKey, e as entityKey, m as methodKey, a as methodKeyPrefix, s as serviceKeyPrefix } from '../keys-CqKkljVN.js';
|
|
3
3
|
import { QueryClient } from '@tanstack/react-query';
|
|
4
|
-
import { ae as ContractMap, ap as MethodName, J as AnyContract, ao as KindOf, al as InputOf, ar as OutputOf } from '../infer-
|
|
4
|
+
import { ae as ContractMap, ap as MethodName, J as AnyContract, ao as KindOf, al as InputOf, ar as OutputOf } from '../infer-Teo1xpsd.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Format a currency value for display.
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/contract/collections.ts","../src/contract/realtime.ts"],"sourcesContent":["// Collection declarations for a contract (RFC 0003 section 7.1). A collection\n// is the rows of one service grouped by a scope value. Membership is declared\n// (a scope column or a junction table, plus an equality filter), never\n// computed by a function, so the framework knows which writes can move a row.\n// This replaces 4.1's `defineCollection({ resolveScopeId, checkScopeAccess,\n// snapshot, toItem })`.\n\nimport type { AccessLevel } from \"./access\";\n\n/** Page size of a collection snapshot when the collection sets no `limit`. */\nexport const DEFAULT_COLLECTION_LIMIT = 100;\n\n/** Largest page a client may ask for when the collection sets no `maxLimit`. */\nexport const DEFAULT_COLLECTION_MAX_LIMIT = 500;\n\n/**\n * A scope that comes from a junction table instead of a column on the row:\n * the rows of `model` link an entry (`entry` holds the row's id) to a scope\n * value (`scope`). For example, a chat appears in each member's list through\n * `via({ model: \"chatMember\", entry: \"chatId\", scope: \"userId\" })`.\n */\nexport interface ViaScope<\n Model extends string = string,\n Entry extends string = string,\n Scope extends string = string,\n> {\n readonly kind: \"via\";\n readonly model: Model;\n readonly entry: Entry;\n readonly scope: Scope;\n /**\n * The item is computed from the junction (a member count): every junction\n * write sends its entry again, whole, to each scope that still holds it.\n * Without it a junction write only adds or removes the entry in the scope\n * it links, so a count over the junction goes stale everywhere else.\n */\n readonly refreshEntry?: boolean;\n}\n\n/** What `via(...)` takes: the junction model, its entry and scope columns, and `refreshEntry`. */\nexport interface ViaOptions<\n Model extends string = string,\n Entry extends string = string,\n Scope extends string = string,\n> {\n /** The junction model, as the database client names it: `\"chatMember\"`. */\n readonly model: Model;\n /** The junction column holding the entry's id: `\"chatId\"`. */\n readonly entry: Entry;\n /** The junction column holding the scope value: `\"userId\"`. */\n readonly scope: Scope;\n /**\n * Set when the item reads the junction, as a `memberCount` mapped from a\n * relation count does: a junction create, update or delete then sends the\n * entry again, as `updated`, to every scope that still holds it (one read\n * of the entry's item per flush). Default `false`: a junction write adds\n * or removes the entry for the scope it links, and nothing else.\n */\n readonly refreshEntry?: boolean;\n}\n\n/**\n * Declares a collection scope that comes from a junction table.\n *\n * @example\n * myChats: {\n * scope: via({ model: \"chatMember\", entry: \"chatId\", scope: \"userId\", refreshEntry: true }),\n * item: \"listItem\",\n * order: [[\"id\", \"asc\"]],\n * }\n */\nexport function via<\n const Model extends string,\n const Entry extends string,\n const Scope extends string,\n>(junction: ViaOptions<Model, Entry, Scope>): ViaScope<Model, Entry, Scope> {\n return Object.freeze({\n kind: \"via\",\n model: junction.model,\n entry: junction.entry,\n scope: junction.scope,\n ...(junction.refreshEntry === undefined ? {} : { refreshEntry: junction.refreshEntry }),\n });\n}\n\nexport type SortDirection = \"asc\" | \"desc\";\n\n/**\n * Sort columns for the keyset cursor, ending in `id` so every row has a\n * unique position: `[[\"ordinal\", \"asc\"], [\"id\", \"asc\"]]`.\n */\nexport type OrderBy<Column extends string = string> = readonly [\n ...(readonly [Column, SortDirection])[],\n readonly [\"id\", SortDirection],\n];\n\n/** Who a view is evaluated for, on the client. */\nexport interface Viewer {\n readonly userId: string;\n}\n\n/**\n * A view: a named pure predicate evaluated on the client over the index rows\n * of a scope (RFC 0003 section 7.5). `row` holds only `id` and the\n * collection's `index` fields, which the type enforces.\n */\nexport type ViewPredicate<Row> = (row: Row, who: Viewer) => boolean;\n\n/** The equality filter that decides membership, for example `{ archived: false }`. */\nexport interface CollectionWhere {\n readonly [column: string]: string | number | boolean | null;\n}\n\n/** Any collection of any contract, as `defineContract` stores it. */\nexport interface CollectionDef {\n /** A string column of the entity, or `via(...)` for a junction table. */\n readonly scope: string | ViaScope;\n /** The projection each item is sent as: `\"entity\"` or a named projection. */\n readonly item: string;\n /** Sort columns for the keyset cursor; the last one is `id`. */\n readonly order: OrderBy;\n /** Equality filter that decides membership. */\n readonly where?: CollectionWhere | undefined;\n /** Page size. Default {@link DEFAULT_COLLECTION_LIMIT}. */\n readonly limit?: number | undefined;\n /** Largest page a client may ask for. Default {@link DEFAULT_COLLECTION_MAX_LIMIT}. */\n readonly maxLimit?: number | undefined;\n /** Item fields sent for every member of the scope (RFC 0003 section 7.4). */\n readonly index?: readonly string[] | undefined;\n /** Named predicates over index rows. Requires `index`. */\n readonly views?: { readonly [view: string]: ViewPredicate<never> } | undefined;\n /** Level required on the scope's anchor row. Default `\"Read\"`, through the anchor's policy. */\n readonly access?: AccessLevel | undefined;\n}\n","// Streams, channels and typed room events in a contract (RFC 0003 section\n// 12.5). A stream is server-to-client append-only data a subscriber receives\n// with a seed of recent items; a channel is fire-and-forget client-to-server\n// input (cursors, typing) behind a per-socket token bucket; an event is a\n// typed custom frame a handler sends to a room. 4.1 declared channels on the\n// service class (4.1 `src/server/BaseService.ts:915-942`) and typed room\n// events through the augmentable `QuickdrawEventMap`; here all three live in\n// the contract, so the client is typed from it.\n//\n// Methods, collections, streams, channels and events share one namespace:\n// the client exposes each as `qd.<service>.<name>`.\n\nimport type { AccessLevel } from \"./access\";\nimport type { AnyContract } from \"./defineContract\";\nimport type { StandardSchemaV1 } from \"./standardSchema\";\n\n/** The `scope` of a stream with one feed for the whole service: the default. */\nexport const GLOBAL_STREAM = \"global\";\n\n/** The most items a stream may keep per scope as the seed for new subscribers. */\nexport const STREAM_MAX_SEED = 1000;\n\n/** A channel's sustained rate when it declares none: messages per second per socket. */\nexport const CHANNEL_DEFAULT_RATE = 30;\n\n/**\n * The app room a stream's subscriber must be in (`access: { room }`): the\n * room's name, `{ prefix }` for any room whose name starts with it, or for a\n * scoped stream a function of the scope (`(worldId) => \\`world:${worldId}\\``).\n */\nexport type StreamRoom = string | RoomPrefix | ((scope: string) => string | null | undefined);\n\n/**\n * Who may subscribe to a stream (RFC 0003 section 4.1's forms, as data). A\n * scoped stream's scope value is the row an `entry` or `scope` form is about:\n * `{ entry: L }` needs level `L` on the row of this service whose id is the\n * scope, `{ scope: L, of }` level `L` on that row of `of`'s service.\n * `{ room }` opens it to the sockets in an app room a method joined them to\n * (signed in or not), as a channel's `requires: { room }` does its messages:\n * a socket that leaves the room, or is taken out of it, leaves the feed too.\n * A stream that declares none is closed: no client may subscribe.\n */\nexport type StreamAccess =\n | \"public\"\n | \"authenticated\"\n | {\n readonly service: AccessLevel;\n readonly entry?: undefined;\n readonly scope?: undefined;\n readonly of?: undefined;\n readonly room?: undefined;\n }\n | {\n readonly entry: AccessLevel;\n readonly service?: AccessLevel;\n readonly scope?: undefined;\n readonly of?: undefined;\n readonly room?: undefined;\n }\n | {\n readonly scope: AccessLevel;\n readonly of: AnyContract;\n readonly service?: undefined;\n readonly entry?: undefined;\n readonly room?: undefined;\n }\n | {\n readonly room: StreamRoom;\n readonly service?: undefined;\n readonly entry?: undefined;\n readonly scope?: undefined;\n readonly of?: undefined;\n };\n\n/** A server-to-client stream of append-only items (RFC 0003 section 12.5). */\nexport interface StreamDef<Item extends StandardSchemaV1 = StandardSchemaV1> {\n /** One item's schema. Pushed items are checked against it before they are sent. */\n readonly item: Item;\n /**\n * What a scope value is (`\"taskId\"`): the stream has one feed per scope.\n * `\"global\"`, the default, makes one feed for the whole service.\n */\n readonly scope?: string;\n /**\n * How many of the latest items the server keeps per scope, in memory on\n * each process, and sends a new subscriber first. Default 0; at most 1,000.\n */\n readonly seed?: number;\n /** Send items volatile: dropped for a client whose connection is backed up. Default `false`. */\n readonly volatile?: boolean;\n /** Who may subscribe. Without it the stream is closed. */\n readonly access?: StreamAccess;\n}\n\n/**\n * Where a channel requirement finds its row id or scope value in the parsed\n * payload: the name of a key holding a string, or a function of the payload.\n */\nexport type PayloadSelector = string | ((payload: never) => string | null | undefined);\n\n/**\n * Any app room whose name starts with `prefix` (`{ prefix: \"world:\" }`, a\n * game of many worlds): the sending socket passes when it is in one, and\n * the channel's handler gets the one it matched as `ctx.room`, so the\n * payload need not repeat which room it is for.\n */\nexport interface RoomPrefix {\n readonly prefix: string;\n}\n\n/**\n * The app room a channel requirement names: the room itself (`\"world\"`, a\n * game's one world), a function of the parsed payload that returns it\n * (`(payload) => \\`lobby:${payload.lobbyId}\\``), or `{ prefix }`, any room\n * whose name starts with it (`{ prefix: \"world:\" }`). Unlike a\n * {@link PayloadSelector}, a string here is the room's name, not a payload\n * key. A name starting with `qd:` or `user:` is never an app room: a literal\n * one (or such a prefix) is refused when the contract is defined, and a\n * computed one drops the message.\n */\nexport type RoomSelector = string | ((payload: never) => string | null | undefined) | RoomPrefix;\n\n/**\n * What a channel message requires of the socket that sends it: a live\n * subscription (`qd:sub`) to the row of this service `entity` names, or\n * (`qd:col:sub`) to the scope of `collection` that `scope` names; or that\n * the socket is in the app room `room` names (or, for `{ prefix }`, in any\n * app room whose name starts with it), which a method called over that\n * same socket joined with `ctx.rooms.join` (a room another socket of the\n * user joined does not count, and a reconnected socket is in none until it\n * joins again). The handler gets the room that matched as `ctx.room`. Each\n * is checked in memory against the sending socket's own records, on the\n * node it is connected to. A message whose payload names none is dropped.\n */\nexport type ChannelRequires =\n | {\n readonly entity: PayloadSelector;\n readonly collection?: undefined;\n readonly scope?: undefined;\n readonly room?: undefined;\n }\n | {\n readonly collection: string;\n readonly scope: PayloadSelector;\n readonly entity?: undefined;\n readonly room?: undefined;\n }\n | {\n readonly room: RoomSelector;\n readonly entity?: undefined;\n readonly collection?: undefined;\n readonly scope?: undefined;\n };\n\n/** A client-to-server fire-and-forget channel (RFC 0003 section 12.5). */\nexport interface ChannelDef<Payload extends StandardSchemaV1 = StandardSchemaV1> {\n /** One message's schema. A message that fails it is dropped. */\n readonly payload: Payload;\n /** Messages one socket may send per second, sustained. Default 30. */\n readonly ratePerSecond?: number;\n /** How many messages one socket may send at once. Default twice `ratePerSecond`. */\n readonly burst?: number;\n /** What the sender must already be subscribed to, or the app room it must be in. */\n readonly requires?: ChannelRequires;\n}\n\n/** A typed custom room event, delivered as `qd:event` (RFC 0003 section 8.3). */\nexport interface EventDef<Payload extends StandardSchemaV1 = StandardSchemaV1> {\n readonly payload: Payload;\n}\n\n/** True when a stream has one feed per scope value; false for a global stream. */\nexport function isScopedStream(def: Pick<StreamDef, \"scope\">): boolean {\n return def.scope !== undefined && def.scope !== GLOBAL_STREAM;\n}\n"],"mappings":";AAUO,IAAM,2BAA2B;AAGjC,IAAM,+BAA+B;AA0DrC,SAAS,IAId,UAA0E;AAC1E,SAAO,OAAO,OAAO;AAAA,IACnB,MAAM;AAAA,IACN,OAAO,SAAS;AAAA,IAChB,OAAO,SAAS;AAAA,IAChB,OAAO,SAAS;AAAA,IAChB,GAAI,SAAS,iBAAiB,SAAY,CAAC,IAAI,EAAE,cAAc,SAAS,aAAa;AAAA,EACvF,CAAC;AACH;;;AClEO,IAAM,gBAAgB;AAGtB,IAAM,kBAAkB;AAGxB,IAAM,uBAAuB;AAqJ7B,SAAS,eAAe,KAAwC;AACrE,SAAO,IAAI,UAAU,UAAa,IAAI,UAAU;AAClD;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/devWarnings.ts","../src/server/rev.ts","../src/server/uow/unitOfWork.ts","../src/server/uow/flush.ts","../src/server/uow/flushSink.ts","../src/server/uow/records.ts","../src/server/uow/statementChecks.ts"],"sourcesContent":["// Development warnings (RFC 0003 section 13): the slow or untracked patterns\n// a lint rule cannot see, because they only show while the code runs,\n// reported as they happen. Every warning has one format, names the call it\n// happened in, and is logged once per kind, service, method and subject:\n//\n// [quickdraw:n-plus-one] taskService.board: task.findUnique by id ran 10 times in one call...\n//\n// | Kind | Raised when | Subject |\n// |----------------------|--------------------------------------------------------------------|--------------------------|\n// | `n-plus-one` | a call ran 10 statements of one shape (model, operation, `where` | none |\n// | | keys), outside an array-form `$transaction` | |\n// | `unbounded-read` | a call ran `findMany` with neither `take` nor ids to read (`id`, | none |\n// | | `{ in }`, `{ equals }`) | |\n// | `oversized-response` | a reply was larger than `maxResponseBytes` | none |\n// | `nested-write` | a write's `data` wrote a related row, which is not tracked | model, field, operation |\n// | `ambient-write` | a tracked write ran outside any unit of work | model |\n// | `batch-read` | a write in an array-form `$transaction` read its rows outside it | model, operation |\n// | `batch-create-many` | a `createMany` in an array-form `$transaction` could not be traced | model |\n// | `repeated-call` | one connection called a method with the same input more than 10 | connection |\n// | | times within a second, or was refused `RATE_LIMITED` more than 30 | (`refused <connection>`) |\n// | | times within a minute: a client in a loop (`createLoopWatch`) | |\n// | `tiered-field-in- | a method's own output schema declares a field the contract tiers, | the field |\n// | output` | at any depth, which such an output never strips | |\n// | | (`emit/tieredOutputs.ts`); raised when a dispatcher is made, so a | |\n// | | strict test app fails to start; for an output without JSON Schema, | |\n// | | raised by the first reply that carries the field | |\n//\n// `nested-write`, `ambient-write`, `batch-read` and `batch-create-many` are\n// the write tracker's (`uow/unitOfWork.ts`), raised exactly where they were\n// before; they gained the format and the call. Only an app's\n// own statements are checked for the first two: the framework's reads through\n// the storage adapter, the tracker's own reads and the kits' handlers run\n// `quietly`, while the app's callbacks a kit calls (`prepare`, `onChange`,\n// `resolveUser`, a search strategy) run `checked`. A query by an `id` list\n// (`{ id: { in: ids } }`) is one query per list, not per row, so it never\n// counts toward an N+1. Warnings are off when NODE_ENV is \"production\". In a test app\n// made with `strictWarnings` (under vitest or jest) every warning raised in\n// one of its method calls throws a `DevWarningError` where it is raised\n// instead, so the test fails; warnings outside its calls (an ambient write\n// while seeding, say) are logged as usual.\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { createHash } from \"node:crypto\";\nimport { consoleLogger, type Logger } from \"../contract/logger\";\nimport type { CallOutcome } from \"./pipeline/metrics\";\n\n/** What a development warning is about. */\nexport type DevWarningKind =\n | \"n-plus-one\"\n | \"unbounded-read\"\n | \"oversized-response\"\n | \"nested-write\"\n | \"ambient-write\"\n | \"batch-read\"\n | \"batch-create-many\"\n | \"repeated-call\"\n | \"tiered-field-in-output\";\n\n/** One development warning. */\nexport interface DevWarning {\n readonly kind: DevWarningKind;\n /** The service whose method call raised it; absent outside a call (a job, a script). */\n readonly service?: string;\n readonly method?: string;\n /**\n * What the warning is about within its call, for the once-only rule: the\n * model of an ambient write, the model, field and operation of a nested\n * write, the connection of a repeated call. Empty for the kinds that are\n * once per call site.\n */\n readonly subject?: string;\n /** What happened and what to do instead. */\n readonly message: string;\n /** Logged with the warning. */\n readonly meta?: Readonly<Record<string, unknown>>;\n}\n\n/** The statements of one shape a call may run before it is an N+1. */\nexport const N_PLUS_ONE_STATEMENTS = 10;\n\n/** The text a warning is logged and thrown with: `[quickdraw:<kind>] <service>.<method>: <message>`. */\nexport function formatDevWarning(warning: DevWarning): string {\n const where =\n warning.service === undefined || warning.method === undefined\n ? \"\"\n : ` ${warning.service}.${warning.method}:`;\n return `[quickdraw:${warning.kind}]${where} ${warning.message}`;\n}\n\n/** A development warning, thrown instead of logged by a test app made with `strictWarnings`. */\nexport class DevWarningError extends Error {\n override readonly name = \"DevWarningError\";\n readonly warning: DevWarning;\n\n constructor(warning: DevWarning) {\n super(formatDevWarning(warning));\n this.warning = warning;\n }\n}\n\n/**\n * Where development warnings go: one per dispatcher, which the write tracker\n * reports a call's warnings to as well.\n */\nexport interface DevWarnings {\n /** False when warnings are off: NODE_ENV is \"production\" and nothing made them strict. */\n readonly enabled: boolean;\n /** True when every warning throws: a strict test app's dispatcher. */\n readonly strict: boolean;\n /** Logs `warning` the first time its kind, service, method and subject come up; strict, throws it every time. */\n warn(warning: DevWarning): void;\n}\n\n/** Options of {@link createDevWarnings}. */\nexport interface DevWarningsOptions {\n readonly logger?: Logger;\n /** Default: on unless NODE_ENV is \"production\". */\n readonly development?: boolean;\n /** Throw every warning as a `DevWarningError` instead of logging it. */\n readonly strict?: boolean;\n}\n\nfunction onceKey(warning: DevWarning): string {\n return [warning.kind, warning.service ?? \"\", warning.method ?? \"\", warning.subject ?? \"\"].join(\n \"\\u0000\",\n );\n}\n\n/** Creates the development warnings of one dispatcher (or of a write tracker before one attaches). */\nexport function createDevWarnings(options: DevWarningsOptions = {}): DevWarnings {\n const logger = options.logger ?? consoleLogger;\n const strict = options.strict === true;\n const enabled = strict || (options.development ?? process.env.NODE_ENV !== \"production\");\n const warned = new Set<string>();\n return Object.freeze({\n enabled,\n strict,\n warn(warning: DevWarning): void {\n if (strict) {\n throw new DevWarningError(warning);\n }\n const key = onceKey(warning);\n if (!enabled || warned.has(key)) {\n return;\n }\n warned.add(key);\n logger.warn(formatDevWarning(warning), {\n category: \"quickdraw.dev\",\n warning: warning.kind,\n ...(warning.service === undefined ? {} : { service: warning.service }),\n ...(warning.method === undefined ? {} : { method: warning.method }),\n ...warning.meta,\n });\n },\n });\n}\n\n/**\n * The option `createTestApp({ strictWarnings })` sets on the server options\n * it passes on, so the dispatcher makes its warnings strict. A symbol, so it\n * survives the options being copied and is no option of the public types.\n */\nexport const STRICT_WARNINGS: unique symbol = Symbol(\"quickdraw.strictWarnings\");\n\n/** Whether `options` carry {@link STRICT_WARNINGS}. */\nexport function strictWarningsOf(options: object): boolean {\n return Reflect.get(options, STRICT_WARNINGS) === true;\n}\n\nconst QUIET = new AsyncLocalStorage<boolean>();\n\n/**\n * Runs `fn` with the development checks of statements off, awaiting its\n * result inside (a Prisma promise runs where it is awaited): the framework's\n * own reads and the kits' handlers, which an app cannot change.\n */\nexport async function quietly<T>(fn: () => T | PromiseLike<T>): Promise<T> {\n return await QUIET.run(true, async () => await fn());\n}\n\n/**\n * Runs `fn` with the development checks of statements on again, inside a\n * kit's quiet handler: the app's own callbacks a kit calls (`prepare`,\n * `onChange`, `resolveUser`, a search strategy) are the app's code, checked\n * like a handler's.\n */\nexport async function checked<T>(fn: () => T | PromiseLike<T>): Promise<T> {\n return await QUIET.run(false, async () => await fn());\n}\n\n/** True inside {@link quietly}, and not inside a {@link checked} within it. */\nexport function isQuiet(): boolean {\n return QUIET.getStore() === true;\n}\n\n// Loop warnings (`repeated-call`). Without them, a client re-issuing one\n// call (a mutation fired from an effect that its own result re-runs, a\n// refetch that triggers itself) is seen only as `RATE_LIMITED` once the\n// socket's 600-per-minute limiter trips, which says nothing about why. The\n// dispatcher's loop watch says it instead:\n//\n// - one connection (a socket, an MCP session) calls the same method with an\n// identical input more than 10 times within a second: once per\n// connection, service and method;\n// - one connection is refused `RATE_LIMITED` more than 30 times within a\n// minute, by the socket rate limiter or by a full query queue: once per\n// connection, at warn where a refusal on its own logs at debug.\n//\n// A test app made with `strictWarnings` throws them from the call that\n// crossed the line, once its reply was sent; a refusal outside any call is\n// logged even there. Calls without a connection (in-process, HTTP) are not\n// counted. Memory is bounded: the 10,000 most recently seen keys are kept.\n\n/** Identical calls one connection may make within {@link REPEATED_CALL_WINDOW_MS}. */\nexport const REPEATED_CALL_LIMIT = 10;\nexport const REPEATED_CALL_WINDOW_MS = 1_000;\n\n/** `RATE_LIMITED` refusals one connection may get within {@link REFUSAL_WINDOW_MS}. */\nexport const REFUSAL_LIMIT = 30;\nexport const REFUSAL_WINDOW_MS = 60_000;\n\nconst MAX_KEYS = 10_000;\n\n/** Inputs longer than this are keyed by their hash. */\nconst MAX_INPUT_KEY = 256;\n\n/** What a finished call is counted by. */\nexport interface CountedCall {\n readonly service: string;\n readonly method: string;\n readonly input: unknown;\n readonly connectionId?: string;\n}\n\n/** Counts each connection's calls and refusals, and warns when they look like a loop. */\nexport interface LoopWatch {\n /**\n * Counts one finished call and how it ended. Throws the warning in a test\n * app made with `strictWarnings`; call it once the reply was sent.\n */\n call(call: CountedCall, outcome: CallOutcome): void;\n /** Counts an event the socket rate limiter refused; never throws. */\n refused(connectionId: string, event: string): void;\n}\n\n/**\n * Counts events per key: returns how many fell within `windowMs` up to now,\n * once that is more than `limit`, else `undefined`. Keeps at most `limit + 1`\n * times per key and drops the least recently seen key past `MAX_KEYS`.\n */\nfunction createCounter(\n limit: number,\n windowMs: number,\n now: () => number,\n): (key: string) => number | undefined {\n const times = new Map<string, number[]>();\n return (key) => {\n const at = now();\n const recent = (times.get(key) ?? []).filter((time) => at - time < windowMs);\n recent.push(at);\n if (recent.length > limit + 1) {\n recent.shift();\n }\n times.delete(key);\n times.set(key, recent);\n if (times.size > MAX_KEYS) {\n const [oldest] = times.keys();\n times.delete(oldest ?? key);\n }\n return recent.length > limit ? recent.length : undefined;\n };\n}\n\n/** The input as a key: its JSON, hashed when long; `undefined` for an input JSON cannot write. */\nfunction inputKey(input: unknown): string | undefined {\n let text: string | undefined;\n try {\n text = JSON.stringify(input) ?? \"undefined\";\n } catch {\n return undefined;\n }\n return text.length <= MAX_INPUT_KEY ? text : createHash(\"sha1\").update(text).digest(\"base64url\");\n}\n\nconst OFF: LoopWatch = Object.freeze({\n call: () => undefined,\n refused: () => undefined,\n});\n\n/**\n * The loop watch of one dispatcher: it raises its `repeated-call` warnings\n * through `warnings`, and through a lenient copy logging to `logger` for\n * refusals outside any call.\n */\nexport function createLoopWatch(\n warnings: DevWarnings,\n logger: Logger,\n now: () => number = Date.now,\n): LoopWatch {\n if (!warnings.enabled) {\n return OFF;\n }\n const repeats = createCounter(REPEATED_CALL_LIMIT, REPEATED_CALL_WINDOW_MS, now);\n const refusals = createCounter(REFUSAL_LIMIT, REFUSAL_WINDOW_MS, now);\n const outside = warnings.strict ? createDevWarnings({ logger, development: true }) : warnings;\n const refusal = (sink: DevWarnings, connectionId: string, what: string): void => {\n const count = refusals(connectionId);\n if (count !== undefined) {\n sink.warn({\n kind: \"repeated-call\",\n subject: `refused ${connectionId}`,\n message:\n `connection ${connectionId} was refused RATE_LIMITED ${String(count)} times within a minute (the last: ${what}): ` +\n \"its client keeps calling while it is rate limited, as a loop does. The quickdraw client backs off on RATE_LIMITED; \" +\n \"find the call that repeats (a repeated-call warning names it) and stop the loop\",\n meta: { connectionId, refusals: count, windowMs: REFUSAL_WINDOW_MS },\n });\n }\n };\n return Object.freeze({\n call(call: CountedCall, outcome: CallOutcome): void {\n const { connectionId } = call;\n if (connectionId === undefined) {\n return;\n }\n if (outcome === \"RATE_LIMITED\") {\n refusal(warnings, connectionId, `${call.service}.${call.method}`);\n }\n const key = inputKey(call.input);\n const count =\n key === undefined\n ? undefined\n : repeats([connectionId, call.service, call.method, key].join(\"\\u0000\"));\n if (count === undefined) {\n return;\n }\n warnings.warn({\n kind: \"repeated-call\",\n service: call.service,\n method: call.method,\n subject: connectionId,\n message:\n `called ${String(count)} times within a second with the same input, on one connection (${connectionId}): ` +\n \"the client calls it in a loop, as a mutation fired from an effect or from render does, or a refetch that triggers itself. \" +\n \"Call it from an event handler, or guard the effect so it runs once per change\",\n meta: { connectionId, calls: count, windowMs: REPEATED_CALL_WINDOW_MS },\n });\n },\n refused(connectionId: string, event: string): void {\n refusal(outside, connectionId, event);\n },\n });\n}\n","// Revisions (RFC 0003 section 5.3, step 2): one per flush, per-process\n// monotonic, `max(Date.now() * 1000, last + 1)`: microseconds since the\n// epoch. A revision is taken before any row is read, so a payload is never\n// older than the revision it carries. Clients drop frames older than what\n// they hold, so two flushes in one millisecond must still get different\n// revisions, and a clock that steps back must not reuse one. In\n// microseconds a process takes a thousand revisions a millisecond and stays\n// on the clock, which `versionColumn` times are compared with (a time is its\n// milliseconds times 1,000: `emit/hub.ts`); revisions stay safe integers\n// (about 1.8e15, under 2^53).\n//\n// Behind a cluster adapter with a shared counter (`cluster/revisions.ts`),\n// flushes take their revisions from the counter instead, which keeps to\n// Valkey's clock in microseconds; the revisions it hands out are observed\n// here, so a revision this process takes later is never below one it\n// already sent.\n\nimport type { Revision } from \"../protocol/envelope\";\n\n/** Microseconds per millisecond: a revision is a time in microseconds. */\nexport const MICROS_PER_MS = 1000;\n\nlet last = 0;\n\n/** The clock as a revision: `Date.now()` in microseconds. */\nexport function clockRev(): Revision {\n return Date.now() * MICROS_PER_MS;\n}\n\n/** The next revision: the current time in microseconds, or one more than the last revision when that is later. */\nexport function nextRev(): Revision {\n last = Math.max(clockRev(), last + 1);\n return last;\n}\n\n/**\n * The revision a read made from now on is no older than, without taking a\n * new one: the last revision taken (one is taken when none was yet). Every\n * write whose flush took a revision up to it had committed before that\n * revision was taken, and any later flush takes a greater one. A read that\n * is not a flush (a subscription's rows) claims it rather than taking its\n * own, so reads do not push revisions ahead of the clock that\n * `versionColumn` times are compared with.\n */\nexport function currentRev(): Revision {\n return last === 0 ? nextRev() : last;\n}\n\n/** Records a revision taken elsewhere (the cluster's counter): later ones taken here are above it. */\nexport function observeRev(rev: Revision): void {\n last = Math.max(last, rev);\n}\n\n/**\n * The time a version column holds as a revision (microseconds since the\n * epoch: its milliseconds times 1,000), or `undefined` when it holds none. A\n * column holds a `Date`, a date string, or a number of milliseconds since\n * the epoch.\n */\nexport function versionTime(value: unknown): Revision | undefined {\n if (value instanceof Date) {\n const time = value.getTime();\n return Number.isNaN(time) ? undefined : time * MICROS_PER_MS;\n }\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return value * MICROS_PER_MS;\n }\n if (typeof value === \"string\") {\n const time = Date.parse(value);\n return Number.isNaN(time) ? undefined : time * MICROS_PER_MS;\n }\n return undefined;\n}\n","// The tracked unit of work (RFC 0003 section 5.1). A write tracker holds the\n// writes of every unit of work in `AsyncLocalStorage` frames: a database\n// adapter (`trackPrisma` on `./prisma`) records each write into the frame\n// that is active where the write runs, and the dispatcher opens a unit per\n// handler run and flushes it after the response.\n//\n// Frames nest:\n//\n// - a unit's frame buffers the unit's writes until it flushes;\n// - a transaction's frame buffers its writes separately, and they join the\n// enclosing buffer only when the transaction commits; a rollback discards\n// them; an interactive transaction's frame also holds its transaction\n// client, so the adapter's own reads see the transaction's writes;\n// - a unit begun inside another open frame (an in-process call made from a\n// handler, `qd.run` inside a transaction) joins it: its writes go where\n// the enclosing frame's go, and its own flush does nothing; a detached\n// unit (`qd.run(fn, { detached: true })`, a handler's background work)\n// never joins: it starts a stack of its own and flushes on its own;\n// - `countStatements` frames only count.\n//\n// A write with no open buffer above it (a job that did not use `qd.run`, or\n// a handler's background work that outlived its unit's flush) is ambient: it\n// flushes on its own on the next tick, with a development warning.\n//\n// In development a method call's unit also checks the statements run in it\n// for N+1 shapes and unbounded reads (`statementChecks.ts`), and every\n// warning goes out in the shared format of `../devWarnings.ts`, naming the\n// call it happened in. A warning raised inside a method call goes to the\n// warnings of the dispatcher that runs the call (a strict test app's throw);\n// one raised elsewhere goes to the attached dispatcher's, never strictly.\n//\n// Dispatchers attach to the tracker as a stack: the last one attached\n// receives ambient writes and the warnings outside calls, and detaching it\n// (a server's `close()`) restores the one attached before.\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { randomUUID } from \"node:crypto\";\nimport { consoleLogger, type Logger } from \"../../contract/logger\";\nimport type { RunContext, RunOptions, TouchOptions } from \"../context\";\nimport { createDevWarnings, isQuiet, type DevWarning, type DevWarnings } from \"../devWarnings\";\nimport { flushWrites } from \"./flush\";\nimport { noFlushSink, type FlushSink } from \"./flushSink\";\nimport {\n createStatementChecks,\n type CallSite,\n type Statement,\n type StatementCheck,\n type StatementPlace,\n} from \"./statementChecks\";\nimport {\n ANY_FIELD,\n type UnitOfWork,\n type UnitOfWorkFactory,\n type UnitOfWorkScope,\n type WriteRecord,\n} from \"./types\";\n\n/** One level of the frame stack. Mutable flags close it once its unit flushed or its transaction settled. */\ninterface Frame {\n readonly parent: Frame | undefined;\n /** Where writes made here go; a frame without a buffer passes them up. */\n readonly buffer: WriteRecord[] | undefined;\n /** Writes in a unit's buffer are durable; a transaction's wait for its commit. */\n readonly durable: boolean;\n /** An interactive transaction's client, for the adapter's own reads. */\n tx: unknown;\n /** An array-form transaction: no client, and its operations cannot be rewritten. */\n readonly batch: boolean;\n /** Counts the statements issued in this frame and every frame inside it. */\n readonly count: ((statements: number) => void) | undefined;\n /** A method call's unit: the call warnings made inside it are about. */\n readonly call: CallSite | undefined;\n /** A method call's unit: its dispatcher's warnings, which those warnings go to. */\n readonly warnings: DevWarnings | undefined;\n /** A method call's unit in development: checks the statements run inside it. */\n readonly check: StatementCheck | undefined;\n open: boolean;\n}\n\n/** A warning the tracker raises; it adds the call it was raised in. */\nexport type TrackerWarning = Omit<DevWarning, \"service\" | \"method\">;\n\n/** An open transaction, from {@link WriteTracker.openTransaction}. */\nexport interface TrackedTransaction {\n /** Sets the interactive transaction's client, once the database has opened it. */\n setClient(tx: unknown): void;\n /** Runs `fn` inside the transaction's frame, awaiting its result there. */\n run<T>(fn: () => T | PromiseLike<T>): Promise<T>;\n /** The transaction committed: its writes join the enclosing buffer. */\n commit(): void;\n /** The transaction rolled back: its writes are dropped. */\n rollback(): void;\n}\n\n/** A statement count, from {@link WriteTracker.countStatements}. */\nexport interface StatementCount<T> {\n readonly value: T;\n readonly statements: number;\n}\n\n/** Options of {@link createWriteTracker}. */\nexport interface WriteTrackerOptions {\n /** Receives warnings until a dispatcher attaches its logger. Default: the console logger. */\n readonly logger?: Logger;\n /**\n * Raise development warnings: ambient, nested and batched writes, and the\n * checks of each method call's statements. Default: on unless `NODE_ENV`\n * is `\"production\"`.\n */\n readonly development?: boolean;\n}\n\n/**\n * Where a database adapter records writes, and the unit of work factory the\n * dispatcher opens units with. One tracker belongs to one tracked client.\n */\nexport interface WriteTracker {\n /** The units of work of handler runs and `qd.run` blocks: give it to the dispatcher. */\n readonly unitOfWork: UnitOfWorkFactory;\n /** Records writes where they ran: an open transaction, a unit of work, or the next tick's ambient flush. */\n record(writes: readonly WriteRecord[]): void;\n /** Records `ids` of `model` as changed, or as deleted with `removed`: `ctx.touch`. */\n touch(model: string, ids: readonly string[], options?: TouchOptions): void;\n /** Counts one statement in every open frame where it runs. */\n countStatement(): void;\n /** Opens a transaction frame inside the active one. */\n openTransaction(kind: \"interactive\" | \"batch\"): TrackedTransaction;\n /** The client of the open interactive transaction where this runs, or `undefined`. */\n transactionClient(): unknown;\n /** True inside an array-form transaction. */\n inBatch(): boolean;\n /** Runs `fn` and counts the statements it issued. */\n countStatements<T>(fn: () => T | PromiseLike<T>): Promise<StatementCount<T>>;\n /** Calls `listener` with each write once it is durable: in a unit of work, or flushing on its own. */\n onWrite(listener: (write: WriteRecord) => void): () => void;\n /**\n * Raises a development warning about the method call this runs in (none\n * outside a call): logged once, in the shared format, by the attached\n * dispatcher's warnings. Nothing outside development.\n */\n warn(warning: TrackerWarning): void;\n /**\n * Hands a statement the database client is about to run to the checks of\n * the method call it runs in (development only; nothing outside a call,\n * nor inside `quietly`).\n */\n observe(statement: Statement): void;\n /** The logger warnings go to: the attached dispatcher's, or the one given at creation. */\n readonly logger: Logger;\n}\n\n/** Where ambient writes flush and warnings outside a call go: the attached dispatcher's. */\ninterface Attachment {\n readonly sink: FlushSink;\n readonly logger: Logger;\n /** Never strict: warnings outside a method call only log. */\n readonly warnings: DevWarnings;\n}\n\ninterface TrackerState {\n readonly als: AsyncLocalStorage<Frame>;\n readonly listeners: Set<(write: WriteRecord) => void>;\n readonly development: boolean;\n /** Before any dispatcher attaches, and once every one detached. */\n readonly base: Attachment;\n /** The dispatchers attached, the current one last. */\n readonly attached: Attachment[];\n logger: Logger;\n warnings: DevWarnings;\n sink: FlushSink;\n ambient: WriteRecord[];\n /** Every statement counted since the tracker was created, wherever it ran. */\n issued: number;\n}\n\nconst ISSUED = new WeakMap<object, () => number>();\n\n/**\n * How many statements the tracked client of `unitOfWork` (a tracker's\n * `unitOfWork`, as `storage.unitOfWork` carries it) has counted since it was\n * created, in every async context: what `expectBudget` measures a test step\n * with. `undefined` for units of work no tracker made.\n */\nexport function statementsIssued(unitOfWork: object): number | undefined {\n return ISSUED.get(unitOfWork)?.();\n}\n\nfunction frameWhere(state: TrackerState, test: (frame: Frame) => boolean): Frame | undefined {\n for (let frame = state.als.getStore(); frame !== undefined; frame = frame.parent) {\n if (frame.open && test(frame)) {\n return frame;\n }\n }\n return undefined;\n}\n\nfunction warn(state: TrackerState, warning: TrackerWarning): void {\n if (!state.development) {\n return;\n }\n const frame = frameWhere(state, (candidate) => candidate.call !== undefined);\n if (frame?.call === undefined) {\n state.warnings.warn(warning);\n return;\n }\n (frame.warnings ?? state.warnings).warn({ ...warning, ...frame.call });\n}\n\n/** Where a statement runs: the innermost open buffer is a unit's, a batch's or an interactive transaction's. */\nfunction placeOf(state: TrackerState): StatementPlace {\n const open = frameWhere(state, (candidate) => candidate.buffer !== undefined);\n if (open?.batch === true) {\n return \"batch\";\n }\n return open?.tx === undefined ? \"unit\" : \"interactive\";\n}\n\nfunction observe(state: TrackerState, statement: Statement): void {\n if (!state.development || isQuiet()) {\n return;\n }\n const frame = frameWhere(state, (candidate) => candidate.check !== undefined);\n frame?.check?.(statement, placeOf(state));\n}\n\nfunction notify(state: TrackerState, writes: readonly WriteRecord[]): void {\n for (const listener of state.listeners) {\n for (const write of writes) {\n try {\n listener(write);\n } catch (error) {\n state.logger.error(\"An onWrite listener threw\", {\n category: \"quickdraw.writes\",\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n }\n}\n\nfunction flushAmbient(state: TrackerState): void {\n const writes = state.ambient;\n state.ambient = [];\n const scope: UnitOfWorkScope = {\n requestId: randomUUID(),\n transport: \"internal\",\n sink: state.sink,\n };\n void flushWrites(writes, scope, state.logger);\n}\n\nfunction recordAmbient(state: TrackerState, writes: readonly WriteRecord[]): void {\n for (const model of new Set(writes.map((write) => write.model))) {\n warn(state, {\n kind: \"ambient-write\",\n subject: model,\n message: `A tracked write to ${model} ran outside any unit of work, so it flushes on its own; run jobs and scripts inside qd.run(...)`,\n meta: { model },\n });\n }\n if (state.ambient.length === 0) {\n setImmediate(() => flushAmbient(state));\n }\n state.ambient.push(...writes);\n notify(state, writes);\n}\n\n/**\n * `qd.run(fn)` made before any dispatcher exists (a boot-time seed written\n * before `createServer`): a unit of work no dispatcher opened, shared by\n * every tracked client. The writes each client makes in it are kept apart\n * from ambient writes (so they raise no warning) and flush once `fn`\n * settles, to the dispatcher that client is attached to by then: none\n * before any server exists, so they reach no one, as no socket could be\n * subscribed yet.\n */\ninterface LooseRun {\n /** The writes of each tracked client, until the run settles. */\n readonly writes: Map<TrackerState, WriteRecord[]>;\n /** False once the run settled: work it started and did not await writes as ambient then. */\n open: boolean;\n}\n\nconst looseRuns = new AsyncLocalStorage<LooseRun>();\n\n/** Keeps `writes` in the loose run this runs in, if any; false outside one. */\nfunction keptLoose(state: TrackerState, writes: readonly WriteRecord[]): boolean {\n const run = looseRuns.getStore();\n if (run === undefined || !run.open) {\n return false;\n }\n run.writes.set(state, [...(run.writes.get(state) ?? []), ...writes]);\n notify(state, writes);\n return true;\n}\n\n/**\n * `qd.run(fn)` before its instance created any dispatcher: `fn` runs in a\n * unit of work no dispatcher opened, with a context that records nothing\n * (no tracked client is known to `ctx.touch`) and logs to the console.\n */\nexport async function runBeforeAnyDispatcher<R>(\n fn: (ctx: RunContext) => R | PromiseLike<R>,\n options?: RunOptions,\n): Promise<R> {\n if (typeof fn !== \"function\") {\n throw new TypeError(\"run: pass the function to run inside a unit of work\");\n }\n if (options !== undefined && (typeof options !== \"object\" || options === null)) {\n throw new TypeError(\"run: options must be an object, { detached?: boolean }\");\n }\n const ctx: RunContext = Object.freeze({\n touch: () => undefined,\n log: consoleLogger.child({ requestId: randomUUID() }),\n principal: null,\n });\n return await runLoose(() => fn(ctx));\n}\n\n/**\n * Runs `fn` in a unit of work no dispatcher opened (`qd.run` before any\n * exists): every tracked client's writes in it flush once `fn` settles,\n * whether it resolved or threw, each to the dispatcher its client is\n * attached to then, which before any server is none.\n */\nasync function runLoose<T>(fn: () => T | PromiseLike<T>): Promise<T> {\n const run: LooseRun = { writes: new Map(), open: true };\n try {\n return await looseRuns.run(run, async () => await fn());\n } finally {\n run.open = false;\n const flushed = [...run.writes].map(async ([state, writes]) => {\n const scope: UnitOfWorkScope = {\n requestId: randomUUID(),\n transport: \"internal\",\n sink: state.sink,\n };\n await flushWrites(writes, scope, state.logger);\n });\n run.writes.clear();\n await Promise.all(flushed);\n }\n}\n\n/** Puts `writes` in the first open buffer at or above `from`, or flushes them on their own. */\nfunction deliver(\n state: TrackerState,\n writes: readonly WriteRecord[],\n from: Frame | undefined,\n): void {\n if (writes.length === 0) {\n return;\n }\n for (let frame = from; frame !== undefined; frame = frame.parent) {\n if (frame.open && frame.buffer !== undefined) {\n frame.buffer.push(...writes);\n if (frame.durable) {\n notify(state, writes);\n }\n return;\n }\n }\n if (!keptLoose(state, writes)) {\n recordAmbient(state, writes);\n }\n}\n\nfunction openTransaction(state: TrackerState, kind: \"interactive\" | \"batch\"): TrackedTransaction {\n const parent = state.als.getStore();\n const frame: Frame = {\n parent,\n buffer: [],\n durable: false,\n tx: undefined,\n batch: kind === \"batch\",\n count: undefined,\n call: undefined,\n warnings: undefined,\n check: undefined,\n open: true,\n };\n return {\n setClient: (tx) => {\n frame.tx = tx;\n },\n run: (fn) => state.als.run(frame, async () => await fn()),\n commit: () => {\n frame.open = false;\n deliver(state, frame.buffer ?? [], parent);\n },\n rollback: () => {\n frame.open = false;\n },\n };\n}\n\nasync function countStatements<T>(\n state: TrackerState,\n fn: () => T | PromiseLike<T>,\n): Promise<StatementCount<T>> {\n let statements = 0;\n const frame: Frame = {\n parent: state.als.getStore(),\n buffer: undefined,\n durable: false,\n tx: undefined,\n batch: false,\n count: (n) => {\n statements += n;\n },\n call: undefined,\n warnings: undefined,\n check: undefined,\n open: true,\n };\n try {\n const value = await state.als.run(frame, async () => await fn());\n return { value, statements };\n } finally {\n frame.open = false;\n }\n}\n\n/**\n * The method call a unit's scope names, the warnings of the dispatcher that\n * runs it (the tracker's own when that dispatcher's are off, as for a\n * production dispatcher over a development tracker), and in development the\n * checks of its statements.\n */\nfunction callOf(\n state: TrackerState,\n scope: UnitOfWorkScope,\n): Pick<Frame, \"call\" | \"warnings\" | \"check\"> {\n if (scope.service === undefined || scope.method === undefined) {\n return { call: undefined, warnings: undefined, check: undefined };\n }\n const call: CallSite = { service: scope.service, method: scope.method };\n const warnings = scope.warnings?.enabled === true ? scope.warnings : state.warnings;\n if (!state.development) {\n return { call, warnings, check: undefined };\n }\n return {\n call,\n warnings,\n check: createStatementChecks((warning) => warnings.warn({ ...warning, ...call })),\n };\n}\n\n/** A unit of work over the tracker's frames. */\nfunction createUnit(state: TrackerState, scope: UnitOfWorkScope): UnitOfWork {\n let statements = 0;\n let frame: Frame | undefined;\n let flushed = false;\n const writes: WriteRecord[] = [];\n return {\n get sqlStatements() {\n return statements;\n },\n run<T>(fn: () => T | PromiseLike<T>): Promise<T> {\n // Inside an open buffer (a transaction, another unit), this unit joins it, unless it is\n // detached: then it stands alone, outside every frame open where it was begun.\n const detached = scope.detached === true;\n const joins =\n !detached && frameWhere(state, (candidate) => candidate.buffer !== undefined) !== undefined;\n const own: Frame = {\n parent: detached ? undefined : state.als.getStore(),\n buffer: joins ? undefined : writes,\n durable: true,\n tx: undefined,\n batch: false,\n count: (n) => {\n statements += n;\n },\n ...callOf(state, scope),\n open: true,\n };\n frame = own;\n return state.als.run(own, async () => await fn());\n },\n async flush(): Promise<void> {\n if (flushed) {\n return;\n }\n flushed = true;\n if (frame !== undefined) {\n frame.open = false;\n }\n await flushWrites(writes.splice(0), scope, state.logger);\n },\n };\n}\n\nfunction touchWrites(\n model: string,\n ids: readonly string[],\n options: TouchOptions | undefined,\n): WriteRecord[] {\n const removed = options?.removed === true;\n return [...new Set(ids)].map((id) => ({\n model,\n id,\n op: removed ? \"delete\" : \"update\",\n fields: removed ? [] : [ANY_FIELD],\n }));\n}\n\n/**\n * The warnings an attaching dispatcher's give the tracker for warnings\n * outside its calls: its own, unless they are off (a tracker made with\n * `development: true` keeps warning under a production dispatcher, as it\n * always did) or strict (only a strict dispatcher's calls throw).\n */\nfunction outsideCalls(\n state: TrackerState,\n logger: Logger,\n warnings: DevWarnings | undefined,\n): DevWarnings {\n return warnings?.enabled === true && !warnings.strict\n ? warnings\n : createDevWarnings({ logger, development: state.development });\n}\n\n/** Makes `attachment` the current one, until the returned function detaches it. */\nfunction attach(state: TrackerState, attachment: Attachment): () => void {\n const follow = (): void => {\n const current = state.attached.at(-1) ?? state.base;\n state.sink = current.sink;\n state.logger = current.logger;\n state.warnings = current.warnings;\n };\n state.attached.push(attachment);\n follow();\n return () => {\n const index = state.attached.indexOf(attachment);\n if (index !== -1) {\n state.attached.splice(index, 1);\n follow();\n }\n };\n}\n\n/** Creates the write tracker of one tracked database client. */\nexport function createWriteTracker(options: WriteTrackerOptions = {}): WriteTracker {\n const development = options.development ?? process.env.NODE_ENV !== \"production\";\n const logger = options.logger ?? consoleLogger;\n const base: Attachment = {\n sink: noFlushSink,\n logger,\n warnings: createDevWarnings({ logger, development }),\n };\n const state: TrackerState = {\n als: new AsyncLocalStorage<Frame>(),\n listeners: new Set(),\n development,\n base,\n attached: [],\n ...base,\n ambient: [],\n issued: 0,\n };\n const touch = (model: string, ids: readonly string[], touchOptions?: TouchOptions): void =>\n deliver(state, touchWrites(model, ids, touchOptions), state.als.getStore());\n const unitOfWork: UnitOfWorkFactory = Object.freeze({\n begin: (scope: UnitOfWorkScope) => createUnit(state, scope),\n touch,\n attach: (sink: FlushSink, attached: Logger, warnings?: DevWarnings) =>\n attach(state, { sink, logger: attached, warnings: outsideCalls(state, attached, warnings) }),\n });\n ISSUED.set(unitOfWork, () => state.issued);\n return Object.freeze({\n unitOfWork,\n record: (writes: readonly WriteRecord[]) => deliver(state, writes, state.als.getStore()),\n touch,\n countStatement(): void {\n state.issued += 1;\n for (let frame = state.als.getStore(); frame !== undefined; frame = frame.parent) {\n if (frame.open) {\n frame.count?.(1);\n }\n }\n },\n openTransaction: (kind: \"interactive\" | \"batch\") => openTransaction(state, kind),\n transactionClient: () => frameWhere(state, (frame) => frame.tx !== undefined)?.tx,\n inBatch: () => frameWhere(state, (frame) => frame.buffer !== undefined)?.batch === true,\n countStatements: <T>(fn: () => T | PromiseLike<T>) => countStatements(state, fn),\n onWrite(listener: (write: WriteRecord) => void): () => void {\n state.listeners.add(listener);\n return () => {\n state.listeners.delete(listener);\n };\n },\n warn: (warning: TrackerWarning) => warn(state, warning),\n observe: (statement: Statement) => observe(state, statement),\n get logger() {\n return state.logger;\n },\n });\n}\n","// The flush (RFC 0003 section 5.3): once a handler has settled and its\n// response was sent, its unit of work merges the recorded writes per row,\n// takes one revision, and hands the batch to the dispatcher's sinks in the\n// order they were given. A sink that throws is logged with the rows it\n// failed on; every other sink then hears about it through `onFlushError`.\n// Nothing here rejects: the caller's response was already sent.\n//\n// A dispatcher runs its flushes one after another, in revision order\n// (`inRevisionOrder`): a flush's sinks run once every flush with a lower\n// revision has finished its own, so frames leave in revision order and a\n// client that merges a patch only \"when rev is newer\" never drops one.\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { Logger } from \"../../contract/logger\";\nimport { describeError } from \"../pipeline/metrics\";\nimport { nextRev } from \"../rev\";\nimport { noFlushSink, type FlushInfo, type FlushSink } from \"./flushSink\";\nimport { mergeRecords } from \"./records\";\nimport type { UnitOfWorkScope, WriteRecord } from \"./types\";\n\n/** How many rows a failure's log entry names. */\nconst LOGGED_ROWS = 20;\n\nfunction logFailure(\n logger: Logger,\n message: string,\n writes: readonly WriteRecord[],\n info: FlushInfo,\n error: unknown,\n): void {\n logger.error(message, {\n category: \"quickdraw.flush\",\n requestId: info.requestId,\n service: info.service,\n method: info.method,\n rev: info.rev,\n rowCount: writes.length,\n rows: writes.slice(0, LOGGED_ROWS).map((write) => `${write.op} ${write.model} ${write.id}`),\n error: describeError(error),\n });\n}\n\n/**\n * Merges `records`, takes one revision and hands the batch to `scope.sink`.\n * With nothing recorded, or nothing left once merged (rows the unit created\n * and deleted again), no revision is taken and the sink is not called.\n * Never rejects; a sink's failure is logged.\n */\nexport async function flushWrites(\n records: readonly WriteRecord[],\n scope: UnitOfWorkScope,\n logger: Logger,\n): Promise<void> {\n const writes = mergeRecords(records);\n if (writes.length === 0) {\n return;\n }\n // The call's warnings stay with the unit: sinks see where the writes came from.\n const { sink, warnings: _warnings, detached: _detached, ...origin } = scope;\n const info: FlushInfo = { ...origin, rev: nextRev() };\n try {\n await sink.flush(writes, info);\n } catch (error) {\n logFailure(\n logger,\n \"Flushing writes failed; the response was already sent\",\n writes,\n info,\n error,\n );\n }\n}\n\ninterface Failure {\n readonly sink: FlushSink;\n readonly error: unknown;\n}\n\n/** Tells every sink but the one that failed, after all of them ran. */\nasync function reportFailures(\n sinks: readonly FlushSink[],\n failures: readonly Failure[],\n writes: readonly WriteRecord[],\n info: FlushInfo,\n logger: Logger,\n): Promise<void> {\n for (const sink of sinks) {\n const failure = failures.find((candidate) => candidate.sink !== sink);\n if (failure === undefined || sink.onFlushError === undefined) {\n continue;\n }\n try {\n await sink.onFlushError(writes, info, failure.error);\n } catch (error) {\n logFailure(\n logger,\n \"A flush sink failed to handle another sink's failure\",\n writes,\n info,\n error,\n );\n }\n }\n}\n\n/**\n * Where a dispatcher's flushes take their revision when it is not the one\n * `flushWrites` took: a cluster's shared counter (`../cluster/revisions.ts`).\n */\nexport interface FlushRevisions {\n /**\n * Called when a flush arrives: `undefined` to keep the flush's own\n * revision, or a function that resolves with the revision to use instead,\n * called when the flush's turn comes.\n */\n forFlush(): (() => Promise<number>) | undefined;\n}\n\n/**\n * A sink that runs the flushes it receives one at a time, in the order they\n * arrive, which is revision order: `flushWrites` takes a flush's revision\n * and hands the batch over in one synchronous step. A flush waits until the\n * flush before it has finished every sink; one that fails does not hold up\n * the next. A flush started from inside one of these runs (a sink that\n * writes through `qd.run`) runs at once instead: waiting for the run that\n * started it would never end.\n *\n * With `revisions` that hand out their own (a cluster's shared counter), a\n * flush asks for its revision when it arrives, and its sinks run with it\n * once its turn comes and the answer is in: still in arrival order, which\n * the revisions keep increasing.\n */\nexport function inRevisionOrder(sink: FlushSink, revisions?: FlushRevisions): FlushSink {\n const running = new AsyncLocalStorage<true>();\n let last: Promise<void> = Promise.resolve();\n return Object.freeze({\n flush(writes: readonly WriteRecord[], info: FlushInfo): Promise<void> {\n const shared = revisions?.forFlush();\n const flushOne =\n shared === undefined\n ? () => sink.flush(writes, info)\n : async () => await sink.flush(writes, { ...info, rev: await shared() });\n if (running.getStore() === true) {\n return flushOne();\n }\n const run = last.then(() => running.run(true, flushOne));\n last = run.catch(() => undefined);\n return run;\n },\n });\n}\n\n/**\n * One sink made of several: each batch goes to every sink in order, a sink\n * that throws is logged and the others still run, and once all have run\n * every sink but a failed one receives `onFlushError`. It never rejects. A\n * lone sink is returned as it is, and no sinks make one that does nothing.\n */\nexport function combineSinks(sinks: readonly FlushSink[], logger: Logger): FlushSink {\n const all = [...sinks];\n if (all.length === 0) {\n return noFlushSink;\n }\n if (all.length === 1 && all[0] !== undefined) {\n return all[0];\n }\n return Object.freeze({\n async flush(writes: readonly WriteRecord[], info: FlushInfo): Promise<void> {\n const failures: Failure[] = [];\n for (const sink of all) {\n try {\n await sink.flush(writes, info);\n } catch (error) {\n failures.push({ sink, error });\n logFailure(\n logger,\n \"A flush sink failed; the response was already sent\",\n writes,\n info,\n error,\n );\n }\n }\n if (failures.length > 0) {\n await reportFailures(all, failures, writes, info, logger);\n }\n },\n });\n}\n","// The flush sink seam (RFC 0003 section 5.3): where a unit of work's writes\n// go once the response is sent. The unit merges its writes per row and takes\n// one revision (`flush.ts`); each sink then turns the batch into what it\n// emits: entity frames, collection deltas, change signals, cache evictions.\n\nimport type { Revision } from \"../../protocol/envelope\";\nimport type { UnitOfWorkScope, WriteRecord } from \"./types\";\n\n/** What a sink learns about a flush besides its writes: where they came from, and the flush's revision. */\nexport interface FlushInfo extends Omit<UnitOfWorkScope, \"sink\" | \"warnings\" | \"detached\"> {\n /**\n * The flush's revision (RFC 0003 section 5.3, step 2), taken once for the\n * whole batch before any sink runs, so before any row is read.\n */\n readonly rev: Revision;\n}\n\n/**\n * Receives the writes of one unit of work after the response has been sent:\n * one record per row, merged. A failure is logged with the affected rows and\n * never fails the response.\n */\nexport interface FlushSink {\n flush(writes: readonly WriteRecord[], info: FlushInfo): Promise<void>;\n /**\n * Called when another sink failed to flush this batch, after every sink\n * ran, so this one can make up for what that one did not send (the\n * collections sink sends a `reset`). Its own failure is logged.\n */\n onFlushError?(\n writes: readonly WriteRecord[],\n info: FlushInfo,\n error: unknown,\n ): void | Promise<void>;\n}\n\n/** The default sink: there is nothing to emit until writes are tracked. */\nexport const noFlushSink: FlushSink = Object.freeze({\n flush: (): Promise<void> => Promise.resolve(),\n});\n","// Merging a unit of work's writes per row (RFC 0003 section 5.3, step 1).\n// A handler that updates one row ten times flushes one record, so a sink\n// reads that row once and a subscriber receives one frame.\n//\n// Per row, in the order the writes happened:\n//\n// - A row the unit created and then deleted is dropped: it never existed\n// outside the unit, so no sink, change log or topic hears of it. Its create\n// must be certain: an `upsert` that read nothing (`mayHaveExisted`) may\n// have updated a row that existed, whose delete is kept, with the values\n// the writes carry.\n// - Otherwise `delete` beats `create` beats `update`, read as \"the row's\n// final state\": a row whose last write deleted it is a `delete`; a row\n// that was created, or deleted and created again, is a `create`, which\n// sinks send whole; anything else is an `update`.\n// - `fields` are unioned.\n// - `before` keeps the earliest value of each column: the row as subscribers\n// last saw it, since nothing was emitted during the unit. A row the unit\n// created has none.\n// - `after` keeps the latest value of each column written since the row was\n// last deleted; a deleted row has none.\n\nimport { ANY_FIELD, type WriteRecord } from \"./types\";\n\ntype Values = Readonly<Record<string, unknown>>;\n\n/** One row's writes, oldest first. */\ntype History = readonly [WriteRecord, ...WriteRecord[]];\n\nfunction mergeFields(history: History): readonly string[] {\n const fields = new Set<string>();\n for (const record of history) {\n for (const field of record.fields) {\n fields.add(field);\n }\n }\n return fields.has(ANY_FIELD) ? [ANY_FIELD] : [...fields];\n}\n\n/** Merges value maps; with `keepFirst`, the first value of a key wins, otherwise the last. */\nfunction mergeValues(\n maps: readonly (Values | undefined)[],\n keepFirst: boolean,\n): Values | undefined {\n let merged: Record<string, unknown> | undefined;\n for (const values of maps) {\n if (values === undefined) {\n continue;\n }\n merged ??= {};\n for (const [key, value] of Object.entries(values)) {\n if (!keepFirst || !Object.hasOwn(merged, key)) {\n merged[key] = value;\n }\n }\n }\n return merged;\n}\n\nfunction finalOp(history: History): WriteRecord[\"op\"] {\n if (history[history.length - 1]?.op === \"delete\") {\n return \"delete\";\n }\n return history.some((record) => record.op === \"create\") ? \"create\" : \"update\";\n}\n\n/** The writes since the row was last deleted: what its `after` values describe. */\nfunction sinceLastDelete(history: History): readonly WriteRecord[] {\n let start = 0;\n history.forEach((record, index) => {\n if (record.op === \"delete\") {\n start = index + 1;\n }\n });\n return history.slice(start);\n}\n\n/** True when the row's first write certainly created it: not an upsert that read nothing. */\nfunction created(first: WriteRecord): boolean {\n return first.op === \"create\" && first.mayHaveExisted !== true;\n}\n\n/** The row's one merged record, or `undefined` for a row the unit created and deleted again. */\nfunction mergeRow(history: History): WriteRecord | undefined {\n const [first] = history;\n if (created(first) && history[history.length - 1]?.op === \"delete\") {\n return undefined;\n }\n if (history.length === 1) {\n return first;\n }\n const op = finalOp(history);\n const before = created(first)\n ? undefined\n : mergeValues(\n history.map((record) => record.before),\n true,\n );\n const after =\n op === \"delete\"\n ? undefined\n : mergeValues(\n sinceLastDelete(history).map((record) => record.after),\n false,\n );\n return {\n model: first.model,\n id: first.id,\n op,\n fields: mergeFields(history),\n ...(before === undefined ? {} : { before }),\n ...(after === undefined ? {} : { after }),\n };\n}\n\n/**\n * Merges a unit of work's writes into one record per row, in the order each\n * row was first written; a row the unit created and deleted again has none.\n * See the rules at the top of this file.\n */\nexport function mergeRecords(records: readonly WriteRecord[]): WriteRecord[] {\n const rows = new Map<string, [WriteRecord, ...WriteRecord[]]>();\n for (const record of records) {\n const key = `${record.model}\\u0000${record.id}`;\n const history = rows.get(key);\n if (history === undefined) {\n rows.set(key, [record]);\n } else {\n history.push(record);\n }\n }\n return [...rows.values()].flatMap((history) => mergeRow(history) ?? []);\n}\n","// The development checks of a method call's statements (`../devWarnings.ts`):\n// N+1 statements and unbounded reads. The tracked database client tells the\n// write tracker about each statement before it runs (`trackPrisma`'s hook),\n// and the tracker hands it to the checks of the innermost method call it\n// runs in. A statement outside a method call (a `qd.run` job, a flush, a\n// subscription) is not checked, and neither is one the framework runs\n// `quietly`.\n\nimport { N_PLUS_ONE_STATEMENTS, type DevWarning } from \"../devWarnings\";\n\n/** One statement the tracked database client is about to run. */\nexport interface Statement {\n /** The model, named as the client names it: `\"task\"`. */\n readonly model: string;\n /** The client operation: `\"findMany\"`. */\n readonly operation: string;\n readonly args: Readonly<Record<string, unknown>> | undefined;\n}\n\n/** The method call a unit of work runs. */\nexport interface CallSite {\n readonly service: string;\n readonly method: string;\n}\n\n/**\n * Where a statement runs: in the call's unit, inside an array-form\n * `$transaction([...])` (`\"batch\"`), or inside an interactive\n * `$transaction(async (tx) => ...)` (`\"interactive\"`).\n */\nexport type StatementPlace = \"unit\" | \"batch\" | \"interactive\";\n\n/** Checks one statement of a call, run at `place`. */\nexport type StatementCheck = (statement: Statement, place: StatementPlace) => void;\n\n/** Raises a warning about the call the checks belong to. */\nexport type RaiseWarning = (warning: Omit<DevWarning, \"service\" | \"method\">) => void;\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** The keys a statement filters by, sorted: its shape besides the model and operation. */\nfunction whereKeys(statement: Statement): string[] {\n const where = statement.args?.where;\n return isRecord(where) ? Object.keys(where).sort() : [];\n}\n\n/**\n * Whether an `id` filter names the rows it reads: a value (equality), or\n * `{ in }` or `{ equals }`. `{ not }`, `{ notIn }` and comparisons leave\n * every other row.\n */\nfunction namesIds(filter: unknown): boolean {\n if (filter === undefined) {\n return false;\n }\n if (!isRecord(filter)) {\n return true;\n }\n return filter.in !== undefined || filter.equals !== undefined;\n}\n\n/**\n * A `findMany` that can return every row of its table: no `take`, and no\n * `id` filter that names its rows (a read by ids is bounded by the ids it\n * names). The same test as the lint rule `no-unbounded-read`, made on the\n * arguments the statement actually ran with.\n */\nexport function isUnboundedRead(statement: Statement): boolean {\n if (statement.operation !== \"findMany\") {\n return false;\n }\n const { take, where } = statement.args ?? {};\n if (take !== undefined && take !== null) {\n return false;\n }\n return !(isRecord(where) && namesIds(where.id));\n}\n\n/**\n * An update or delete of one row by its id inside an interactive\n * transaction: the sanctioned form of per-row writes whose data differs per\n * row (one that moves a row or changes who may see it must read the row\n * first, which an array-form `$transaction` cannot do inside the batch).\n */\nfunction isRowWriteInTransaction(statement: Statement, place: StatementPlace): boolean {\n if (place !== \"interactive\") {\n return false;\n }\n if (statement.operation !== \"update\" && statement.operation !== \"delete\") {\n return false;\n }\n const keys = whereKeys(statement);\n return keys.length === 1 && keys[0] === \"id\";\n}\n\n/**\n * A statement filtered by an `id` list (`{ id: { in: ids } }`): one query\n * per list, as in a loop over chunks of ids, so never an N+1. The lint rule\n * `no-db-call-in-loop` makes the same exception.\n */\nfunction readsIdList(statement: Statement): boolean {\n const where = statement.args?.where;\n const id = isRecord(where) ? where.id : undefined;\n return isRecord(id) && Array.isArray(id.in);\n}\n\nfunction nPlusOne(\n statement: Statement,\n keys: readonly string[],\n): Omit<DevWarning, \"service\" | \"method\"> {\n const label = `${statement.model}.${statement.operation}${keys.length === 0 ? \"\" : ` by ${keys.join(\", \")}`}`;\n return {\n kind: \"n-plus-one\",\n message:\n `${label} ran ${N_PLUS_ONE_STATEMENTS} times in one call, once per item (N+1); ` +\n \"read the rows in one query (findMany({ where: { id: { in: ids } } })), write them in one when every row gets the same data \" +\n \"(updateMany, createMany), or write each row by id inside an interactive transaction (db.$transaction(async (tx) => ...))\",\n meta: { model: statement.model, operation: statement.operation, where: keys },\n };\n}\n\nfunction unboundedRead(statement: Statement): Omit<DevWarning, \"service\" | \"method\"> {\n return {\n kind: \"unbounded-read\",\n message:\n `${statement.model}.findMany() without take reads every matching row, however many there are; ` +\n \"add take (with a cursor to page), or serve the list as a collection or the read/write kit's list\",\n meta: { model: statement.model },\n };\n}\n\n/**\n * The checks of one method call: warns once its statements of one shape\n * (model, operation and `where` keys) reach {@link N_PLUS_ONE_STATEMENTS}, and\n * at an unbounded read. Statements batched in an array-form `$transaction`\n * are sent together, which is the fix for an N+1, so they are not counted;\n * neither are updates and deletes by id inside an interactive transaction,\n * the form per-row writes take when each row's data differs, nor statements\n * filtered by an `id` list (one per chunk of ids).\n */\nexport function createStatementChecks(raise: RaiseWarning): StatementCheck {\n const shapes = new Map<string, number>();\n return (statement, place) => {\n if (isUnboundedRead(statement)) {\n raise(unboundedRead(statement));\n }\n if (place === \"batch\" || isRowWriteInTransaction(statement, place) || readsIdList(statement)) {\n return;\n }\n const keys = whereKeys(statement);\n const shape = `${statement.model}.${statement.operation}(${keys.join(\",\")})`;\n const seen = (shapes.get(shape) ?? 0) + 1;\n shapes.set(shape, seen);\n if (seen === N_PLUS_ONE_STATEMENTS) {\n raise(nPlusOne(statement, keys));\n }\n };\n}\n"],"mappings":"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","names":["AsyncLocalStorage","AsyncLocalStorage","AsyncLocalStorage","last","AsyncLocalStorage"]}
|