@rocicorp/zero 1.12.0-canary.10 → 1.12.0-canary.12
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/out/analyze-query/src/zql-chain.js +9 -1
- package/out/analyze-query/src/zql-chain.js.map +1 -1
- package/out/ast-to-zql/src/ast-to-zql.d.ts.map +1 -1
- package/out/ast-to-zql/src/ast-to-zql.js +61 -20
- package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
- package/out/z2s/src/compiler.d.ts +1 -1
- package/out/z2s/src/compiler.d.ts.map +1 -1
- package/out/z2s/src/compiler.js +339 -2
- package/out/z2s/src/compiler.js.map +1 -1
- package/out/z2s/src/sql.d.ts +8 -0
- package/out/z2s/src/sql.d.ts.map +1 -1
- package/out/z2s/src/sql.js +58 -11
- package/out/z2s/src/sql.js.map +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero/src/bindings.js +4 -4
- package/out/zero-cache/src/auth/write-authorizer.js +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +4 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +13 -0
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/db/create.js +1 -1
- package/out/zero-cache/src/scripts/decommission.js +1 -1
- package/out/zero-cache/src/server/replicator.js +4 -2
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/runner/run-worker.d.ts.map +1 -1
- package/out/zero-cache/src/server/runner/run-worker.js +8 -5
- package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
- package/out/zero-cache/src/server/serving-checkpointer.js +41 -0
- package/out/zero-cache/src/server/serving-checkpointer.js.map +1 -0
- package/out/zero-cache/src/server/worker-urls.d.ts +1 -0
- package/out/zero-cache/src/server/worker-urls.d.ts.map +1 -1
- package/out/zero-cache/src/server/worker-urls.js +2 -1
- package/out/zero-cache/src/server/worker-urls.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js +1 -1
- package/out/zero-cache/src/services/http-service.d.ts +6 -0
- package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/http-service.js +13 -1
- package/out/zero-cache/src/services/http-service.js.map +1 -1
- package/out/zero-cache/src/services/life-cycle.d.ts +19 -0
- package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
- package/out/zero-cache/src/services/life-cycle.js +53 -1
- package/out/zero-cache/src/services/life-cycle.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +1 -1
- package/out/zero-cache/src/services/replicator/serving-checkpointer.js +85 -0
- package/out/zero-cache/src/services/replicator/serving-checkpointer.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +101 -13
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/query-covering.js +2 -2
- package/out/zero-cache/src/services/view-syncer/query-covering.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +1 -1
- package/out/zero-cache/src/types/processes.js +4 -4
- package/out/zero-cache/src/types/processes.js.map +1 -1
- package/out/zero-cache/src/types/row-key.js +8 -1
- package/out/zero-cache/src/types/row-key.js.map +1 -1
- package/out/zero-cache/src/workers/connection.js +2 -2
- package/out/zero-cache/src/workers/replicator.js +9 -4
- package/out/zero-cache/src/workers/replicator.js.map +1 -1
- package/out/zero-client/src/client/bindings.d.ts +1 -1
- package/out/zero-client/src/client/bindings.d.ts.map +1 -1
- package/out/zero-client/src/client/bindings.js +4 -4
- package/out/zero-client/src/client/context.d.ts +7 -1
- package/out/zero-client/src/client/context.d.ts.map +1 -1
- package/out/zero-client/src/client/context.js +7 -0
- package/out/zero-client/src/client/context.js.map +1 -1
- package/out/zero-client/src/client/crud-impl.d.ts.map +1 -1
- package/out/zero-client/src/client/crud-impl.js +171 -2
- package/out/zero-client/src/client/crud-impl.js.map +1 -1
- package/out/zero-client/src/client/custom.d.ts.map +1 -1
- package/out/zero-client/src/client/custom.js +42 -3
- package/out/zero-client/src/client/custom.js.map +1 -1
- package/out/zero-client/src/client/ivm-branch.d.ts +62 -2
- package/out/zero-client/src/client/ivm-branch.d.ts.map +1 -1
- package/out/zero-client/src/client/ivm-branch.js +152 -31
- package/out/zero-client/src/client/ivm-branch.js.map +1 -1
- package/out/zero-client/src/client/keys.d.ts +33 -0
- package/out/zero-client/src/client/keys.d.ts.map +1 -1
- package/out/zero-client/src/client/keys.js +43 -4
- package/out/zero-client/src/client/keys.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero-poke-handler.d.ts.map +1 -1
- package/out/zero-client/src/client/zero-poke-handler.js +29 -1
- package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
- package/out/zero-client/src/client/zero-rep.d.ts +1 -1
- package/out/zero-client/src/client/zero-rep.d.ts.map +1 -1
- package/out/zero-client/src/client/zero-rep.js +2 -15
- package/out/zero-client/src/client/zero-rep.js.map +1 -1
- package/out/zero-client/src/client/zero.js +1 -1
- package/out/zero-client/src/mod.d.ts +3 -3
- package/out/zero-client/src/mod.d.ts.map +1 -1
- package/out/zero-protocol/src/aggregate-table.d.ts +15 -0
- package/out/zero-protocol/src/aggregate-table.d.ts.map +1 -0
- package/out/zero-protocol/src/aggregate-table.js +5 -0
- package/out/zero-protocol/src/aggregate-table.js.map +1 -0
- package/out/zero-protocol/src/ast.d.ts +141 -5
- package/out/zero-protocol/src/ast.d.ts.map +1 -1
- package/out/zero-protocol/src/ast.js +244 -18
- package/out/zero-protocol/src/ast.js.map +1 -1
- package/out/zero-protocol/src/down.d.ts +29 -29
- package/out/zero-protocol/src/inspect-down.d.ts +81 -81
- package/out/zero-protocol/src/protocol-version.d.ts +1 -1
- package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
- package/out/zero-protocol/src/protocol-version.js.map +1 -1
- package/out/zero-protocol/src/query-hash-visitor.d.ts.map +1 -1
- package/out/zero-protocol/src/query-hash-visitor.js +38 -0
- package/out/zero-protocol/src/query-hash-visitor.js.map +1 -1
- package/out/zero-react/src/bindings.js +4 -4
- package/out/zero-react/src/use-query.d.ts +3 -1
- package/out/zero-react/src/use-query.d.ts.map +1 -1
- package/out/zero-react/src/use-query.js +4 -2
- package/out/zero-react/src/use-query.js.map +1 -1
- package/out/zero-schema/src/builder/schema-builder.d.ts.map +1 -1
- package/out/zero-schema/src/builder/schema-builder.js +2 -0
- package/out/zero-schema/src/builder/schema-builder.js.map +1 -1
- package/out/zero-schema/src/name-mapper.d.ts.map +1 -1
- package/out/zero-schema/src/name-mapper.js +7 -4
- package/out/zero-schema/src/name-mapper.js.map +1 -1
- package/out/zero-server/src/custom.js +1 -1
- package/out/zero-server/src/schema.d.ts.map +1 -1
- package/out/zero-server/src/schema.js +13 -2
- package/out/zero-server/src/schema.js.map +1 -1
- package/out/zero-solid/src/bindings.js +4 -4
- package/out/zero-solid/src/solid-view.d.ts.map +1 -1
- package/out/zero-solid/src/solid-view.js +3 -3
- package/out/zero-solid/src/solid-view.js.map +1 -1
- package/out/zero-types/src/format.d.ts +13 -0
- package/out/zero-types/src/format.d.ts.map +1 -1
- package/out/zero-types/src/format.js.map +1 -1
- package/out/zero-types/src/name-mapper.d.ts +14 -0
- package/out/zero-types/src/name-mapper.d.ts.map +1 -1
- package/out/zero-types/src/name-mapper.js +12 -0
- package/out/zero-types/src/name-mapper.js.map +1 -1
- package/out/zero-types/src/schema-value.d.ts +2 -0
- package/out/zero-types/src/schema-value.d.ts.map +1 -1
- package/out/zero-types/src/schema-value.js +15 -0
- package/out/zero-types/src/schema-value.js.map +1 -0
- package/out/zero-types/src/server-schema.d.ts +18 -0
- package/out/zero-types/src/server-schema.d.ts.map +1 -1
- package/out/zql/src/builder/builder.d.ts +126 -1
- package/out/zql/src/builder/builder.d.ts.map +1 -1
- package/out/zql/src/builder/builder.js +281 -11
- package/out/zql/src/builder/builder.js.map +1 -1
- package/out/zql/src/builder/filter.d.ts +1 -1
- package/out/zql/src/builder/filter.d.ts.map +1 -1
- package/out/zql/src/builder/filter.js +41 -3
- package/out/zql/src/builder/filter.js.map +1 -1
- package/out/zql/src/ivm/aggregate.d.ts +188 -0
- package/out/zql/src/ivm/aggregate.d.ts.map +1 -0
- package/out/zql/src/ivm/aggregate.js +470 -0
- package/out/zql/src/ivm/aggregate.js.map +1 -0
- package/out/zql/src/ivm/array-view.d.ts +1 -1
- package/out/zql/src/ivm/array-view.d.ts.map +1 -1
- package/out/zql/src/ivm/array-view.js +3 -3
- package/out/zql/src/ivm/array-view.js.map +1 -1
- package/out/zql/src/ivm/cap.js +2 -2
- package/out/zql/src/ivm/constraint.d.ts.map +1 -1
- package/out/zql/src/ivm/constraint.js +2 -1
- package/out/zql/src/ivm/constraint.js.map +1 -1
- package/out/zql/src/ivm/deferred-input.js +1 -1
- package/out/zql/src/ivm/fan-in.js +1 -1
- package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
- package/out/zql/src/ivm/flipped-join.js +3 -2
- package/out/zql/src/ivm/flipped-join.js.map +1 -1
- package/out/zql/src/ivm/group-by.d.ts +98 -0
- package/out/zql/src/ivm/group-by.d.ts.map +1 -0
- package/out/zql/src/ivm/group-by.js +384 -0
- package/out/zql/src/ivm/group-by.js.map +1 -0
- package/out/zql/src/ivm/join-index.d.ts.map +1 -1
- package/out/zql/src/ivm/join-index.js +2 -1
- package/out/zql/src/ivm/join-index.js.map +1 -1
- package/out/zql/src/ivm/join-utils.d.ts.map +1 -1
- package/out/zql/src/ivm/join-utils.js +2 -1
- package/out/zql/src/ivm/join-utils.js.map +1 -1
- package/out/zql/src/ivm/join.js +1 -1
- package/out/zql/src/ivm/lift-field.d.ts +54 -0
- package/out/zql/src/ivm/lift-field.d.ts.map +1 -0
- package/out/zql/src/ivm/lift-field.js +168 -0
- package/out/zql/src/ivm/lift-field.js.map +1 -0
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.js +1 -1
- package/out/zql/src/ivm/memory-source.d.ts +2 -0
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +9 -4
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/push-accumulated.js +1 -1
- package/out/zql/src/ivm/schema.d.ts +1 -0
- package/out/zql/src/ivm/schema.d.ts.map +1 -1
- package/out/zql/src/ivm/source.d.ts +21 -0
- package/out/zql/src/ivm/source.d.ts.map +1 -1
- package/out/zql/src/ivm/source.js.map +1 -1
- package/out/zql/src/ivm/take.js +1 -1
- package/out/zql/src/ivm/union-fan-in.js +1 -1
- package/out/zql/src/ivm/view-apply-change.d.ts +5 -0
- package/out/zql/src/ivm/view-apply-change.d.ts.map +1 -1
- package/out/zql/src/ivm/view-apply-change.js +23 -3
- package/out/zql/src/ivm/view-apply-change.js.map +1 -1
- package/out/zql/src/planner/planner-debug.d.ts +1 -1
- package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
- package/out/zql/src/planner/planner-debug.js +4 -0
- package/out/zql/src/planner/planner-debug.js.map +1 -1
- package/out/zql/src/query/expression.d.ts +50 -3
- package/out/zql/src/query/expression.d.ts.map +1 -1
- package/out/zql/src/query/expression.js +62 -6
- package/out/zql/src/query/expression.js.map +1 -1
- package/out/zql/src/query/measure-push-operator.js +1 -1
- package/out/zql/src/query/query-delegate-base.d.ts +8 -0
- package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
- package/out/zql/src/query/query-delegate-base.js +13 -5
- package/out/zql/src/query/query-delegate-base.js.map +1 -1
- package/out/zql/src/query/query-impl.d.ts +17 -3
- package/out/zql/src/query/query-impl.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.js +239 -7
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query.d.ts +319 -7
- package/out/zql/src/query/query.d.ts.map +1 -1
- package/out/zqlite/src/pending-delta.d.ts.map +1 -1
- package/out/zqlite/src/pending-delta.js +4 -3
- package/out/zqlite/src/pending-delta.js.map +1 -1
- package/out/zqlite/src/query-builder.d.ts +12 -1
- package/out/zqlite/src/query-builder.d.ts.map +1 -1
- package/out/zqlite/src/query-builder.js +114 -13
- package/out/zqlite/src/query-builder.js.map +1 -1
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +37 -3
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAE,MAAM,EAAC,MAAM,mCAAmC,CAAC;AACxE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,2CAA2C,CAAC;AAC1E,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,WAAW,CAAC;AAE1C;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAA;KAAC,CAAC;IACtD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAQ3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAE,MAAM,EAAC,MAAM,mCAAmC,CAAC;AACxE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,2CAA2C,CAAC;AAC1E,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,WAAW,CAAC;AAE1C;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAA;KAAC,CAAC;IACtD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAQ3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAMrC,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC5C,CAAC"}
|
|
@@ -3,6 +3,7 @@ import type { Row } from '../../../zero-protocol/src/data.ts';
|
|
|
3
3
|
import type { TableSchema } from '../../../zero-types/src/schema.ts';
|
|
4
4
|
import type { DebugDelegate } from '../builder/debug-delegate.ts';
|
|
5
5
|
import { ChangeType } from './change-type.ts';
|
|
6
|
+
import type { Constraint } from './constraint.ts';
|
|
6
7
|
import type { Input } from './operator.ts';
|
|
7
8
|
import type { Stream } from './stream.ts';
|
|
8
9
|
export type SourceChangeAdd = [type: ChangeType.ADD, row: Row, extra: null];
|
|
@@ -75,5 +76,25 @@ export interface Source {
|
|
|
75
76
|
}
|
|
76
77
|
export interface SourceInput extends Input {
|
|
77
78
|
readonly fullyAppliedFilters: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* The number of rows a fetch with `constraint` would return, counted without
|
|
81
|
+
* reading them, or `undefined` when the source can't count right now (the
|
|
82
|
+
* caller then fetches and counts the rows itself). Optional: a source that
|
|
83
|
+
* can't count cheaply leaves it out.
|
|
84
|
+
*/
|
|
85
|
+
count?(constraint: Constraint | undefined): number | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* The distinct values of `columns` among the rows an unconstrained fetch
|
|
88
|
+
* would return, each with the number of rows that have it, in no particular
|
|
89
|
+
* order, or `undefined` when the source can't group right now (the caller
|
|
90
|
+
* then fetches the rows and groups them itself). Optional, like `count`.
|
|
91
|
+
*/
|
|
92
|
+
groupCounts?(columns: readonly string[]): GroupCount[] | undefined;
|
|
78
93
|
}
|
|
94
|
+
/** One group of {@link SourceInput.groupCounts}. */
|
|
95
|
+
export type GroupCount = {
|
|
96
|
+
/** The group's value of each of the columns, by column name. */
|
|
97
|
+
readonly row: Row;
|
|
98
|
+
readonly count: number;
|
|
99
|
+
};
|
|
79
100
|
//# sourceMappingURL=source.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,mCAAmC,CAAC;AAC3E,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,oCAAoC,CAAC;AAC5D,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mCAAmC,CAAC;AACnE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;AAC5E,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,UAAU,CAAC,MAAM;IACvB,GAAG,EAAE,GAAG;IACR,KAAK,EAAE,IAAI;CACZ,CAAC;AACF,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;AAE9E,MAAM,MAAM,YAAY,GACpB,eAAe,GACf,kBAAkB,GAClB,gBAAgB,CAAC;AAErB,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,GAAG,eAAe,CAE7D;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,GAAG,GAAG,kBAAkB,CAEnE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,gBAAgB,CAE5E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,MAAM;IACrB,IAAI,WAAW,IAAI,WAAW,CAAC;IAC/B;;;;;;;;;;;OAWG;IACH,OAAO,CACL,IAAI,EAAE,QAAQ,GAAG,SAAS,EAC1B,OAAO,CAAC,EAAE,SAAS,EACnB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAC3B,KAAK,CAAC,EAAE,aAAa,GACpB,WAAW,CAAC;IAEf;;;;;;;;OAQG;IACH,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAE5C;;;;;;;;;OASG;IACH,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,WAAY,SAAQ,KAAK;IACxC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,mCAAmC,CAAC;AAC3E,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,oCAAoC,CAAC;AAC5D,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mCAAmC,CAAC;AACnE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;AAC5E,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,UAAU,CAAC,MAAM;IACvB,GAAG,EAAE,GAAG;IACR,KAAK,EAAE,IAAI;CACZ,CAAC;AACF,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;AAE9E,MAAM,MAAM,YAAY,GACpB,eAAe,GACf,kBAAkB,GAClB,gBAAgB,CAAC;AAErB,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,GAAG,eAAe,CAE7D;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,GAAG,GAAG,kBAAkB,CAEnE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,gBAAgB,CAE5E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,MAAM;IACrB,IAAI,WAAW,IAAI,WAAW,CAAC;IAC/B;;;;;;;;;;;OAWG;IACH,OAAO,CACL,IAAI,EAAE,QAAQ,GAAG,SAAS,EAC1B,OAAO,CAAC,EAAE,SAAS,EACnB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAC3B,KAAK,CAAC,EAAE,aAAa,GACpB,WAAW,CAAC;IAEf;;;;;;;;OAQG;IACH,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAE5C;;;;;;;;;OASG;IACH,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,WAAY,SAAQ,KAAK;IACxC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IAEtC;;;;;OAKG;IACH,KAAK,CAAC,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;IAE/D;;;;;OAKG;IACH,WAAW,CAAC,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,EAAE,GAAG,SAAS,CAAC;CACpE;AAED,oDAAoD;AACpD,MAAM,MAAM,UAAU,GAAG;IACvB,gEAAgE;IAChE,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAClB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source.js","names":[],"sources":["../../../../../zql/src/ivm/source.ts"],"sourcesContent":["import type {Condition, Ordering} from '../../../zero-protocol/src/ast.ts';\nimport type {Row} from '../../../zero-protocol/src/data.ts';\nimport type {TableSchema} from '../../../zero-types/src/schema.ts';\nimport type {DebugDelegate} from '../builder/debug-delegate.ts';\nimport {ChangeType} from './change-type.ts';\nimport type {Input} from './operator.ts';\nimport type {Stream} from './stream.ts';\n\nexport type SourceChangeAdd = [type: ChangeType.ADD, row: Row, extra: null];\nexport type SourceChangeRemove = [\n type: ChangeType.REMOVE,\n row: Row,\n extra: null,\n];\nexport type SourceChangeEdit = [type: ChangeType.EDIT, row: Row, oldRow: Row];\n\nexport type SourceChange =\n | SourceChangeAdd\n | SourceChangeRemove\n | SourceChangeEdit;\n\nexport function makeSourceChangeAdd(row: Row): SourceChangeAdd {\n return [ChangeType.ADD, row, null];\n}\n\nexport function makeSourceChangeRemove(row: Row): SourceChangeRemove {\n return [ChangeType.REMOVE, row, null];\n}\n\nexport function makeSourceChangeEdit(row: Row, oldRow: Row): SourceChangeEdit {\n return [ChangeType.EDIT, row, oldRow];\n}\n\n/**\n * A source is an input that serves as the root data source of the pipeline.\n * Sources have multiple outputs. To add an output, call `connect()`, then\n * hook yourself up to the returned Connector, like:\n *\n * ```ts\n * class MyOperator implements Output {\n * constructor(input: Input) {\n * input.setOutput(this);\n * }\n *\n * push(change: Change): void {\n * // Handle change\n * }\n * }\n *\n * const connection = source.connect(ordering);\n * const myOperator = new MyOperator(connection);\n * ```\n */\nexport interface Source {\n get tableSchema(): TableSchema;\n /**\n * Creates an input that an operator can connect to. To free resources used\n * by connection, downstream operators call `destroy()` on the returned\n * input.\n *\n * @param sort The ordering of the rows. Source must return rows in this\n * order. Must include all primary keys of the table.\n * @param filters Filters to apply to the source.\n * @param splitEditKeys If an edit change modifies the values of any of the\n * keys in splitEditKeys, the source should split the edit change into\n * a remove of the old row followed by an add of the new row.\n */\n connect(\n sort: Ordering | undefined,\n filters?: Condition,\n splitEditKeys?: Set<string>,\n debug?: DebugDelegate,\n ): SourceInput;\n\n /**\n * Pushes a change into the source and into all connected outputs.\n *\n * The returned stream can yield 'yield' to yield control to the caller\n * for purposes of responsiveness.\n *\n * Once the stream is exhausted, the change will have been pushed into all\n * connected inputs and committed to the source.\n */\n push(change: SourceChange): Stream<'yield'>;\n\n /**\n * Pushes a change into the source.\n *\n * Iterating the returned stream will push the change into one connected input\n * at a time, yielding `undefined` between each, and yielding `'yield'` to\n * yield control to the caller for purposes of responsiveness.\n *\n * Once the stream is exhausted, the change will have been pushed\n * into all connected inputs and committed to the source.\n */\n genPush(change: SourceChange): Stream<'yield' | undefined>;\n}\n\nexport interface SourceInput extends Input {\n readonly fullyAppliedFilters: boolean;\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"source.js","names":[],"sources":["../../../../../zql/src/ivm/source.ts"],"sourcesContent":["import type {Condition, Ordering} from '../../../zero-protocol/src/ast.ts';\nimport type {Row} from '../../../zero-protocol/src/data.ts';\nimport type {TableSchema} from '../../../zero-types/src/schema.ts';\nimport type {DebugDelegate} from '../builder/debug-delegate.ts';\nimport {ChangeType} from './change-type.ts';\nimport type {Constraint} from './constraint.ts';\nimport type {Input} from './operator.ts';\nimport type {Stream} from './stream.ts';\n\nexport type SourceChangeAdd = [type: ChangeType.ADD, row: Row, extra: null];\nexport type SourceChangeRemove = [\n type: ChangeType.REMOVE,\n row: Row,\n extra: null,\n];\nexport type SourceChangeEdit = [type: ChangeType.EDIT, row: Row, oldRow: Row];\n\nexport type SourceChange =\n | SourceChangeAdd\n | SourceChangeRemove\n | SourceChangeEdit;\n\nexport function makeSourceChangeAdd(row: Row): SourceChangeAdd {\n return [ChangeType.ADD, row, null];\n}\n\nexport function makeSourceChangeRemove(row: Row): SourceChangeRemove {\n return [ChangeType.REMOVE, row, null];\n}\n\nexport function makeSourceChangeEdit(row: Row, oldRow: Row): SourceChangeEdit {\n return [ChangeType.EDIT, row, oldRow];\n}\n\n/**\n * A source is an input that serves as the root data source of the pipeline.\n * Sources have multiple outputs. To add an output, call `connect()`, then\n * hook yourself up to the returned Connector, like:\n *\n * ```ts\n * class MyOperator implements Output {\n * constructor(input: Input) {\n * input.setOutput(this);\n * }\n *\n * push(change: Change): void {\n * // Handle change\n * }\n * }\n *\n * const connection = source.connect(ordering);\n * const myOperator = new MyOperator(connection);\n * ```\n */\nexport interface Source {\n get tableSchema(): TableSchema;\n /**\n * Creates an input that an operator can connect to. To free resources used\n * by connection, downstream operators call `destroy()` on the returned\n * input.\n *\n * @param sort The ordering of the rows. Source must return rows in this\n * order. Must include all primary keys of the table.\n * @param filters Filters to apply to the source.\n * @param splitEditKeys If an edit change modifies the values of any of the\n * keys in splitEditKeys, the source should split the edit change into\n * a remove of the old row followed by an add of the new row.\n */\n connect(\n sort: Ordering | undefined,\n filters?: Condition,\n splitEditKeys?: Set<string>,\n debug?: DebugDelegate,\n ): SourceInput;\n\n /**\n * Pushes a change into the source and into all connected outputs.\n *\n * The returned stream can yield 'yield' to yield control to the caller\n * for purposes of responsiveness.\n *\n * Once the stream is exhausted, the change will have been pushed into all\n * connected inputs and committed to the source.\n */\n push(change: SourceChange): Stream<'yield'>;\n\n /**\n * Pushes a change into the source.\n *\n * Iterating the returned stream will push the change into one connected input\n * at a time, yielding `undefined` between each, and yielding `'yield'` to\n * yield control to the caller for purposes of responsiveness.\n *\n * Once the stream is exhausted, the change will have been pushed\n * into all connected inputs and committed to the source.\n */\n genPush(change: SourceChange): Stream<'yield' | undefined>;\n}\n\nexport interface SourceInput extends Input {\n readonly fullyAppliedFilters: boolean;\n\n /**\n * The number of rows a fetch with `constraint` would return, counted without\n * reading them, or `undefined` when the source can't count right now (the\n * caller then fetches and counts the rows itself). Optional: a source that\n * can't count cheaply leaves it out.\n */\n count?(constraint: Constraint | undefined): number | undefined;\n\n /**\n * The distinct values of `columns` among the rows an unconstrained fetch\n * would return, each with the number of rows that have it, in no particular\n * order, or `undefined` when the source can't group right now (the caller\n * then fetches the rows and groups them itself). Optional, like `count`.\n */\n groupCounts?(columns: readonly string[]): GroupCount[] | undefined;\n}\n\n/** One group of {@link SourceInput.groupCounts}. */\nexport type GroupCount = {\n /** The group's value of each of the columns, by column name. */\n readonly row: Row;\n readonly count: number;\n};\n"],"mappings":";AAsBA,SAAgB,oBAAoB,KAA2B;CAC7D,OAAO;EAAC;EAAgB;EAAK;CAAI;AACnC;AAEA,SAAgB,uBAAuB,KAA8B;CACnE,OAAO;EAAC;EAAmB;EAAK;CAAI;AACtC;AAEA,SAAgB,qBAAqB,KAAU,QAA+B;CAC5E,OAAO;EAAC;EAAiB;EAAK;CAAM;AACtC"}
|
package/out/zql/src/ivm/take.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { assert, unreachable } from "../../../shared/src/asserts.js";
|
|
2
2
|
import { hasOwn } from "../../../shared/src/has-own.js";
|
|
3
|
-
import { throwOutput } from "./operator.js";
|
|
4
3
|
import { makeAddChange, makeRemoveChange } from "./change.js";
|
|
5
4
|
import { compareValues, valueToKey } from "./data.js";
|
|
5
|
+
import { throwOutput } from "./operator.js";
|
|
6
6
|
import { assertOrderingIncludesPK } from "../query/complete-ordering.js";
|
|
7
7
|
//#region ../zql/src/ivm/take.ts
|
|
8
8
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { assert } from "../../../shared/src/asserts.js";
|
|
2
|
-
import { emptyArray } from "../../../shared/src/sentinels.js";
|
|
3
2
|
import { throwOutput } from "./operator.js";
|
|
3
|
+
import { emptyArray } from "../../../shared/src/sentinels.js";
|
|
4
4
|
import { makeAddEmptyRelationships, mergeEmpty, mergeRelationships, pushAccumulatedChanges, pushWitnessDeltas } from "./push-accumulated.js";
|
|
5
5
|
//#region ../zql/src/ivm/union-fan-in.ts
|
|
6
6
|
var UnionFanIn = class {
|
|
@@ -68,5 +68,10 @@ type EditViewChange = {
|
|
|
68
68
|
* mutate in place for the rest of the transaction. See `activeDirty` above.
|
|
69
69
|
*/
|
|
70
70
|
export declare function applyChange(parentEntry: Entry, change: ViewChange, schema: SourceSchema, relationship: string, format: Format, withIDs?: boolean, mutate?: boolean | WeakSet<object>): Entry;
|
|
71
|
+
/**
|
|
72
|
+
* The result of a view before any row arrives: the empty aggregate value for an
|
|
73
|
+
* aggregate, `undefined` for `one()`, and `[]` otherwise.
|
|
74
|
+
*/
|
|
75
|
+
export declare function emptyViewValue(format: Format): 0 | null | undefined | [];
|
|
71
76
|
export {};
|
|
72
77
|
//# sourceMappingURL=view-apply-change.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view-apply-change.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/view-apply-change.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"view-apply-change.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/view-apply-change.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,oCAAoC,CAAC;AAE5D,OAAO,EAAkB,KAAK,IAAI,EAAC,MAAM,WAAW,CAAC;AAErD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAC,KAAK,EAAE,MAAM,EAAC,MAAM,WAAW,CAAC;AAE7C,eAAO,MAAM,cAAc,eAAe,CAAC;AAC3C,eAAO,MAAM,QAAQ,eAAe,CAAC;AAwBrC;;;GAGG;AACH,KAAK,YAAY,GAAG;IAClB,GAAG,EAAE,GAAG,CAAC;IACT,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;CAC/C,CAAC;AAEF;;;;GAIG;AACH,KAAK,QAAQ,GAAG,IAAI,GAAG,YAAY,CAAC;AAEpC;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAClB,aAAa,GACb,gBAAgB,GAChB,eAAe,GACf,cAAc,CAAC;AAEnB,KAAK,WAAW,GAAG;IAAC,GAAG,EAAE,GAAG,CAAA;CAAC,CAAC;AAE9B,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,KAAK,CAAC;IACZ,IAAI,EAAE,QAAQ,CAAC;CAChB,CAAC;AAEF,KAAK,gBAAgB,GAAG;IACtB,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,QAAQ,CAAC;CAChB,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE;QACL,gBAAgB,EAAE,MAAM,CAAC;QACzB,MAAM,EAAE,UAAU,CAAC;KACpB,CAAC;CACH,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,WAAW,CAAC;CACtB,CAAC;AA8DF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CACzB,WAAW,EAAE,KAAK,EAClB,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,YAAY,EACpB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,UAAQ,EACf,MAAM,GAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAS,GACxC,KAAK,CAkBP;AA4sBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,EAAE,CAKxE"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { assert, assertNumber, unreachable } from "../../../shared/src/asserts.js";
|
|
2
2
|
import { must } from "../../../shared/src/must.js";
|
|
3
3
|
import { assignProperty } from "../../../shared/src/objects.js";
|
|
4
|
-
import { skipYields } from "./skip-yields.js";
|
|
5
4
|
import "./data.js";
|
|
5
|
+
import { skipYields } from "./skip-yields.js";
|
|
6
|
+
import { AGGREGATE_VALUE_COLUMN } from "./aggregate.js";
|
|
6
7
|
//#region ../zql/src/ivm/view-apply-change.ts
|
|
7
8
|
var refCountSymbol = Symbol("rc");
|
|
8
9
|
var idSymbol = Symbol("id");
|
|
@@ -102,6 +103,13 @@ function applyChangeInternal(parentEntry, change, schema, relationship, format,
|
|
|
102
103
|
}
|
|
103
104
|
default: unreachable(change);
|
|
104
105
|
}
|
|
106
|
+
if (format.aggregate) switch (change.type) {
|
|
107
|
+
case "add":
|
|
108
|
+
case "edit": return setProperty(mutate, parentEntry, relationship, change.node.row[AGGREGATE_VALUE_COLUMN]);
|
|
109
|
+
case "remove": return setProperty(mutate, parentEntry, relationship, emptyAggregateValue(format.aggregate));
|
|
110
|
+
case "child": return parentEntry;
|
|
111
|
+
default: unreachable(change);
|
|
112
|
+
}
|
|
105
113
|
const { singular, relationships: childFormats } = format;
|
|
106
114
|
switch (change.type) {
|
|
107
115
|
case "add": if (singular) {
|
|
@@ -216,7 +224,7 @@ function initializeRelationshipsForNewEntryIfAny(entry, node, schema, childForma
|
|
|
216
224
|
const childFormat = childFormats[relationship];
|
|
217
225
|
if (childFormat === void 0) continue;
|
|
218
226
|
if (childSchema.isHidden || childFormat.singular) {
|
|
219
|
-
result[relationship] = childFormat.singular ? void 0 : track([]);
|
|
227
|
+
result[relationship] = childFormat.aggregate ? emptyAggregateValue(childFormat.aggregate) : childFormat.singular ? void 0 : track([]);
|
|
220
228
|
for (const childNode of getChildNodes(node, relationship)) applyChangeInternal(result, {
|
|
221
229
|
type: "add",
|
|
222
230
|
node: childNode
|
|
@@ -358,6 +366,18 @@ function arrayWith(mutate, array, index, value) {
|
|
|
358
366
|
}
|
|
359
367
|
return track(array.with(index, value));
|
|
360
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* The result of a view before any row arrives: the empty aggregate value for an
|
|
371
|
+
* aggregate, `undefined` for `one()`, and `[]` otherwise.
|
|
372
|
+
*/
|
|
373
|
+
function emptyViewValue(format) {
|
|
374
|
+
if (format.aggregate) return emptyAggregateValue(format.aggregate);
|
|
375
|
+
return format.singular ? void 0 : [];
|
|
376
|
+
}
|
|
377
|
+
/** An aggregate over no rows, as in SQL: `count` is `0`, the rest `null`. */
|
|
378
|
+
function emptyAggregateValue(aggregate) {
|
|
379
|
+
return aggregate.fn === "count" ? 0 : null;
|
|
380
|
+
}
|
|
361
381
|
function setProperty(mutate, parentEntry, key, value) {
|
|
362
382
|
if (mutate || owns(parentEntry)) {
|
|
363
383
|
assignProperty(parentEntry, key, value);
|
|
@@ -370,6 +390,6 @@ function setProperty(mutate, parentEntry, key, value) {
|
|
|
370
390
|
}
|
|
371
391
|
var setRelation = setProperty;
|
|
372
392
|
//#endregion
|
|
373
|
-
export { applyChange, idSymbol, refCountSymbol };
|
|
393
|
+
export { applyChange, emptyViewValue, idSymbol, refCountSymbol };
|
|
374
394
|
|
|
375
395
|
//# sourceMappingURL=view-apply-change.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view-apply-change.js","names":[],"sources":["../../../../../zql/src/ivm/view-apply-change.ts"],"sourcesContent":["import {\n assert,\n assertNumber,\n unreachable,\n} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {assignProperty} from '../../../shared/src/objects.ts';\nimport type {Writable} from '../../../shared/src/writable.ts';\nimport type {Row} from '../../../zero-protocol/src/data.ts';\nimport {type Comparator, type Node} from './data.ts';\nimport {skipYields} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {Entry, Format} from './view.ts';\n\nexport const refCountSymbol = Symbol('rc');\nexport const idSymbol = Symbol('id');\n\ntype ReadonlyMetaEntry = Entry & {\n readonly [refCountSymbol]: number;\n readonly [idSymbol]?: string | undefined;\n};\n\ntype MutableMetaEntry = Writable<ReadonlyMetaEntry>;\n\n/**\n * MetaEntry is an Entry with internal tracking fields for reference counting\n * and stable identity. All entries in the view tree are MetaEntry instances,\n * created through makeNewMetaEntry().\n */\ntype MetaEntry<M extends Mutate> = M extends true\n ? MutableMetaEntry\n : ReadonlyMetaEntry;\n\ntype MutableArray<M extends Mutate, T> = M extends true ? T[] : readonly T[];\n\ntype MetaEntryList<M extends Mutate> = MutableArray<M, MetaEntry<M>>;\n\ntype MutableMetaEntryList = MutableMetaEntry[];\n\n/**\n * Node with eagerly-expanded relationships (arrays instead of generators).\n * Used when batching changes to capture source state at push time.\n */\ntype ExpandedNode = {\n row: Row;\n relationships: Record<string, ExpandedNode[]>;\n};\n\n/**\n * A node for view changes. Can be either:\n * - A lazy Node with generator relationships (original behavior)\n * - An ExpandedNode with pre-computed relationship arrays (for batching)\n */\ntype ViewNode = Node | ExpandedNode;\n\n/**\n * `applyChange` does not consume the `relationships` of `ChildChange#node`,\n * `EditChange#node` and `EditChange#oldNode`. The `ViewChange` type\n * documents and enforces this via the type system.\n */\nexport type ViewChange =\n | AddViewChange\n | RemoveViewChange\n | ChildViewChange\n | EditViewChange;\n\ntype RowOnlyNode = {row: Row};\n\ntype AddViewChange = {\n type: 'add';\n node: ViewNode;\n};\n\ntype RemoveViewChange = {\n type: 'remove';\n node: ViewNode;\n};\n\ntype ChildViewChange = {\n type: 'child';\n node: RowOnlyNode;\n child: {\n relationshipName: string;\n change: ViewChange;\n };\n};\n\ntype EditViewChange = {\n type: 'edit';\n node: RowOnlyNode;\n oldNode: RowOnlyNode;\n};\n\n/**\n * Get child nodes from a relationship, handling both lazy (Node) and expanded (ExpandedNode).\n */\nfunction getChildNodes(\n node: ViewNode,\n relationship: string,\n): Iterable<ViewNode> {\n const children = node.relationships[relationship];\n if (Array.isArray(children)) {\n // ExpandedNode: already an array\n return children;\n }\n // Node: lazy generator function\n return skipYields(children());\n}\n\ntype Mutate = boolean;\ntype WithIDs = boolean;\n\n/**\n * Transaction-scoped copy-on-write.\n *\n * `applyChange` is immutable: it path-copies a new spine on every call so the\n * previously-emitted snapshot is never mutated. But within a single transaction\n * (a run of `push()` calls before `flush()`) only the final state is ever\n * observed by a listener, so the intermediate spines are pure allocation/GC\n * churn — making a K-change transaction O(K * spineSize).\n *\n * When the caller passes a WeakSet as the `mutate` argument, objects and arrays\n * that were created or cloned during the current transaction are recorded in it.\n * A helper that needs to modify such an object mutates it in place (it is not\n * yet observed) instead of cloning again. The first touch of a node still\n * copies-on-write (preserving the committed snapshot's references); subsequent\n * touches in the same transaction are in-place. This brings a K-change\n * transaction to O(spineSize + K) while keeping cross-transaction reference\n * stability.\n *\n * The set is module-scoped for the duration of a single (synchronous,\n * non-re-entrant) `applyChange` call and saved/restored around it.\n *\n * It is a WeakSet on purpose: it must not keep transiently-cloned arrays/entries\n * alive. A strong Set would extend their lifetime to the end of the transaction,\n * pushing large clones out of the young generation and adding GC pressure that\n * shows up as a regression on single-change transactions over wide lists. A\n * WeakSet gives the same identity test (\"created this transaction?\") while\n * letting dead clones be collected as cheaply as in the plain immutable path.\n */\nlet activeDirty: WeakSet<object> | undefined;\n\n/** True if `o` was created/cloned during the current transaction. */\nfunction owns(o: object): boolean {\n return activeDirty !== undefined && activeDirty.has(o);\n}\n\n/** Record `o` as owned by the current transaction (no-op outside a txn). */\nfunction track<T extends object>(o: T): T {\n activeDirty?.add(o);\n return o;\n}\n\n/**\n * Immutable view update. Returns new Entry on change, same Entry if unchanged.\n * Unchanged entries keep identity, enabling shallow comparison optimizations\n * in UI frameworks (React.memo, Solid's fine-grained reactivity, etc).\n *\n * Propagation: recurse DOWN to find target, copy objects on the way UP.\n * Siblings keep original refs. Only the ancestor path is copied.\n *\n * root {users:[A,B], items:[C,D,E]} --edit C--> root' {users:[A,B], items':[C',D,E]}\n * │ same ref │ │ │\n * └─────────────────┴── unchanged ──────────────┘ new array C' new, D/E same\n *\n * `mutate` selects the update strategy:\n * - `false` (default): fully immutable; every changed node is path-copied.\n * - `true`: mutate the tree in place (only safe when it is not yet observed,\n * e.g. during initial hydration).\n * - a `WeakSet`: transaction-scoped copy-on-write — copy on first touch, then\n * mutate in place for the rest of the transaction. See `activeDirty` above.\n */\nexport function applyChange(\n parentEntry: Entry,\n change: ViewChange,\n schema: SourceSchema,\n relationship: string,\n format: Format,\n withIDs = false,\n mutate: boolean | WeakSet<object> = false,\n): Entry {\n // A WeakSet selects copy-on-write; a boolean selects full mutate / immutable.\n const inPlace = mutate === true;\n const prevDirty = activeDirty;\n activeDirty = typeof mutate === 'object' ? mutate : undefined;\n try {\n return applyChangeInternal(\n parentEntry as MetaEntry<typeof inPlace>,\n change,\n schema,\n relationship,\n format,\n withIDs,\n inPlace,\n );\n } finally {\n activeDirty = prevDirty;\n }\n}\n\nfunction applyChangeInternal<M extends Mutate>(\n parentEntry: MetaEntry<M>,\n change: ViewChange,\n schema: SourceSchema,\n relationship: string,\n format: Format,\n withIDs: WithIDs,\n mutate: M,\n): MetaEntry<M> {\n if (schema.isHidden) {\n switch (change.type) {\n case 'add':\n case 'remove': {\n let currentParent = parentEntry;\n for (const relationship of Object.keys(change.node.relationships)) {\n const childSchema = must(schema.relationships[relationship]);\n for (const node of getChildNodes(change.node, relationship)) {\n currentParent = applyChangeInternal(\n currentParent,\n {type: change.type, node},\n childSchema,\n relationship,\n format,\n withIDs,\n mutate,\n );\n }\n }\n return currentParent;\n }\n case 'edit':\n // If hidden at this level it means that the hidden row was changed. If\n // the row was changed in such a way that it would change the\n // relationships then the edit would have been split into remove and\n // add.\n return parentEntry;\n case 'child': {\n const childSchema = must(\n schema.relationships[change.child.relationshipName],\n );\n return applyChangeInternal(\n parentEntry,\n change.child.change,\n childSchema,\n relationship,\n format,\n withIDs,\n mutate,\n );\n }\n default:\n unreachable(change);\n }\n }\n\n const {singular, relationships: childFormats} = format;\n switch (change.type) {\n // ADD: Insert row (rc=1) or increment refCount if duplicate.\n // RefCount tracks when the same row is reachable via multiple edges\n // within a relationship:\n //\n // issue.labels: [L1, L2] both point to same creator\n // │ │ │\n // ▼ ▼ ▼\n // label.creator ──────────► [User:rc=2] (one row, two refs)\n //\n // add(A) add(A) remove(A) remove(A)\n // ↓ ↓ ↓ ↓\n // [A:rc=1] → [A:rc=2] → [A:rc=1] → (deleted)\n case 'add': {\n if (singular) {\n const oldEntry = getOptionalSingularEntry(parentEntry, relationship);\n if (oldEntry !== undefined) {\n // Duplicate add: increment refCount\n assert(\n schema.compareRows(oldEntry, change.node.row) === 0,\n `Singular relationship '${relationship}' should not have multiple rows. You may need to declare this relationship with the \\`many\\` helper instead of the \\`one\\` helper in your schema.`,\n );\n\n const newEntry = incRefCount(mutate, oldEntry);\n return setRelation(mutate, parentEntry, relationship, newEntry);\n } else {\n // New row: create with rc=1, initialize nested relationships\n const newEntry = makeNewMetaEntry(\n change.node.row,\n schema,\n withIDs,\n 1,\n );\n initializeRelationshipsForNewEntryIfAny(\n newEntry,\n change.node,\n schema,\n childFormats,\n withIDs,\n );\n return setRelation(mutate, parentEntry, relationship, newEntry);\n }\n } else {\n // Plural: binary search for position, insert or increment refCount\n const view = getChildEntryList(parentEntry, relationship);\n const {newEntry, newView} = add(\n change.node.row,\n view,\n schema,\n withIDs,\n mutate,\n );\n\n if (newEntry) {\n initializeRelationshipsForNewEntryIfAny(\n newEntry,\n change.node,\n schema,\n childFormats,\n withIDs,\n );\n }\n return setRelation(mutate, parentEntry, relationship, newView);\n }\n }\n // REMOVE: Decrement refCount, physically remove when rc=0.\n case 'remove': {\n if (singular) {\n const oldEntry = getSingularEntry(parentEntry, relationship);\n const rc = oldEntry[refCountSymbol];\n if (rc === 1) {\n return setRelation(mutate, parentEntry, relationship, undefined);\n }\n const newEntry = decRefCount(mutate, oldEntry);\n return setRelation(mutate, parentEntry, relationship, newEntry);\n } else {\n const view = getChildEntryList(parentEntry, relationship);\n const newView = removeAndUpdateRefCount(\n view,\n change.node.row,\n schema.compareRows,\n mutate,\n );\n return setRelation(mutate, parentEntry, relationship, newView);\n }\n }\n // CHILD: Propagate nested change up to this level (leaf-to-root pattern).\n case 'child': {\n const childSchema = must(\n schema.relationships[change.child.relationshipName],\n );\n const childFormat = format.relationships[change.child.relationshipName];\n if (childFormat === undefined) {\n return parentEntry; // Relationship not in view format\n }\n\n if (singular) {\n const existing = getSingularEntry(parentEntry, relationship);\n const newExisting = applyChangeInternal(\n existing,\n change.child.change,\n childSchema,\n change.child.relationshipName,\n childFormat,\n withIDs,\n mutate,\n );\n // Preserve identity if child didn't change (enables shallow-compare optimizations).\n if (newExisting === existing) {\n return parentEntry;\n }\n return setRelation(mutate, parentEntry, relationship, newExisting);\n } else {\n // Find the target row in the sorted array via binary search.\n const view = getChildEntryList(parentEntry, relationship);\n const pos = binarySearch(view, change.node.row, schema.compareRows);\n assert(pos >= 0, 'node does not exist');\n const existing = view[pos];\n const newExisting = applyChangeInternal(\n existing,\n change.child.change,\n childSchema,\n change.child.relationshipName,\n childFormat,\n withIDs,\n mutate,\n );\n // Preserve identity if descendant didn't change.\n if (newExisting === existing) {\n return parentEntry;\n }\n // applyChangeInternal preserves MetaEntry structure when input is MetaEntry\n assertMetaEntry(newExisting);\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n arrayWith(mutate, view, pos, newExisting),\n );\n }\n }\n // EDIT: Update row fields. If sort key changes, row may move position.\n //\n // Position change with rc>1 (two query paths reach same row):\n //\n // Before: [A, B(rc=2), C, D] After: [A, B(rc=1), C, B'(rc=1), D]\n // │ │ │\n // path1 + path2 path1 ghost path2 moved\n //\n // Why ghost stays: path1 still expects B at old position.\n // B' appears where path2's sort order places it.\n case 'edit': {\n if (singular) {\n const existing = getSingularEntry(parentEntry, relationship);\n const newEntry = applyEdit(existing, change, schema, withIDs, mutate);\n return setRelation(mutate, parentEntry, relationship, newEntry);\n } else {\n const view = getChildEntryList(parentEntry, relationship);\n // Sort key changed: row may need to move\n if (schema.compareRows(change.oldNode.row, change.node.row) !== 0) {\n const oldPos = binarySearch(\n view,\n change.oldNode.row,\n schema.compareRows,\n );\n assert(oldPos >= 0, 'old node does not exist');\n const oldEntry = view[oldPos];\n const rawPos = binarySearch(\n view,\n change.node.row,\n schema.compareRows,\n );\n const found = rawPos >= 0;\n const pos = found ? rawPos : ~rawPos;\n // A special case:\n // when refCount is 1 (so the row is being moved without leaving a\n // placeholder behind), and the new pos is the same as the old, or\n // directly after the old (so after the remove of the old it would be\n // in the same pos): the row does not need to be moved, just edited.\n if (\n oldEntry[refCountSymbol] === 1 &&\n (pos === oldPos || pos - 1 === oldPos)\n ) {\n const newEntry = applyEdit(\n oldEntry,\n change,\n schema,\n withIDs,\n mutate,\n );\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n arrayWith(mutate, view, oldPos, newEntry),\n );\n } else {\n // Move the row. If rc > 1, an edit will be received for each ref.\n // On first edit: move row, apply edit, set rc=1. Leave a copy at\n // old pos with decremented rc. As each edit arrives, old copy's rc\n // decrements and new pos rc increments. When copy rc hits 0, remove.\n const newRefCount = oldEntry[refCountSymbol] - 1;\n let newView: MutableMetaEntryList;\n let adjustedPos = pos;\n\n if (newRefCount === 0) {\n newView = removeAt(mutate, view, oldPos);\n adjustedPos = oldPos < pos ? pos - 1 : pos;\n } else {\n const oldEntryCopy = setRefCount(mutate, oldEntry, newRefCount);\n newView = arrayWith(mutate, view, oldPos, oldEntryCopy);\n }\n\n if (found) {\n // Merge with existing at new pos\n const existingEntry = newView[adjustedPos];\n const editedEntry = applyEdit(\n existingEntry,\n change,\n schema,\n withIDs,\n mutate,\n );\n const updatedEntry = setRefCount(\n mutate,\n editedEntry,\n existingEntry[refCountSymbol] + 1,\n );\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n arrayWith(mutate, newView, adjustedPos, updatedEntry),\n );\n } else {\n // Insert at new pos with rc=1\n const editedEntry = applyEdit(\n oldEntry,\n change,\n schema,\n withIDs,\n mutate,\n );\n const movedEntry = setRefCount(mutate, editedEntry, 1);\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n insertAt(mutate, newView, adjustedPos, movedEntry),\n );\n }\n }\n } else {\n // Sort key unchanged: edit in place\n const pos = binarySearch(\n view,\n change.oldNode.row,\n schema.compareRows,\n );\n assert(pos >= 0, 'node does not exist');\n const newEntry = applyEdit(\n view[pos],\n change,\n schema,\n withIDs,\n mutate,\n );\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n arrayWith(mutate, view, pos, newEntry),\n );\n }\n }\n }\n default:\n unreachable(change);\n }\n}\n\nfunction applyEdit<M extends Mutate>(\n existing: MetaEntry<M>,\n change: EditViewChange,\n schema: SourceSchema,\n withIDs: WithIDs,\n mutate: Mutate,\n): MetaEntry<M> {\n // In-place edit is safe when fully mutating or when `existing` was already\n // created/cloned in this transaction (copy-on-write). A primary-key change\n // always needs a fresh entry so identity tracks the new key.\n const canMutate = mutate || owns(existing);\n const newEntry: MutableMetaEntry =\n canMutate && schema.compareRows(change.oldNode.row, change.node.row) === 0\n ? Object.assign(existing, change.node.row)\n : track({...existing, ...change.node.row});\n\n if (withIDs) {\n return setProperty(\n mutate,\n newEntry,\n idSymbol,\n makeID(change.node.row, schema),\n );\n }\n return newEntry;\n}\n\n/**\n * Initialize child relationships on a newly-added entry by mutating it in place.\n *\n * New nodes don't exist in the view yet, so we can build in-place (no refs to preserve):\n *\n * New nodes don't exist in the view yet, so we can build in-place (no refs to preserve):\n *\n * Existing node edit (must path-copy): New node (can build in-place):\n *\n * view: [A, B, C] view: [A, B, _]\n * │ │\n * edit C.child add C with children\n * │ │\n * ▼ ▼\n * view': [A, B, C'] ◄─ path-copy view': [A, B, C{children:[...]}]\n * up the tree └─ built in-place\n *\n * Hidden schemas still use applyChange to collapse junction levels.\n */\nfunction initializeRelationshipsForNewEntryIfAny(\n entry: MutableMetaEntry,\n node: ViewNode,\n schema: SourceSchema,\n childFormats: Record<string, Format>,\n withIDs: WithIDs,\n): void {\n let result = entry;\n for (const relationship of Object.keys(node.relationships)) {\n const childSchema = must(schema.relationships[relationship]);\n const childFormat = childFormats[relationship];\n if (childFormat === undefined) {\n continue;\n }\n\n // Hidden/singular: use applyChange to handle properly\n if (childSchema.isHidden || childFormat.singular) {\n const newView = childFormat.singular\n ? undefined\n : track([] as MutableMetaEntryList);\n result[relationship] = newView;\n\n for (const childNode of getChildNodes(node, relationship)) {\n applyChangeInternal(\n result,\n {type: 'add', node: childNode},\n childSchema,\n relationship,\n childFormat,\n withIDs,\n true, // this is a new entry, so we can mutate\n );\n }\n } else {\n // Plural non-hidden: build array in-place for efficiency\n const childArray: MutableMetaEntryList = track([]);\n\n for (const childNode of getChildNodes(node, relationship)) {\n const newEntry = makeNewMetaEntry(\n childNode.row,\n childSchema,\n withIDs,\n 1,\n );\n const rawPos = binarySearch(\n childArray,\n childNode.row,\n childSchema.compareRows,\n );\n\n if (rawPos >= 0) {\n childArray[rawPos][refCountSymbol]++;\n } else {\n childArray.splice(~rawPos, 0, newEntry);\n initializeRelationshipsForNewEntryIfAny(\n newEntry,\n childNode,\n childSchema,\n childFormat.relationships,\n withIDs,\n );\n }\n }\n\n result[relationship] = childArray;\n }\n }\n}\n\nfunction add<M extends Mutate>(\n row: Row,\n view: MetaEntryList<M>,\n schema: SourceSchema,\n withIDs: WithIDs,\n mutate: M,\n): {\n newEntry: MutableMetaEntry | undefined;\n newView: MutableMetaEntryList;\n} {\n const rawPos = binarySearch(view, row, schema.compareRows);\n\n if (rawPos >= 0) {\n const existing = view[rawPos];\n\n const updated = incRefCount(mutate, existing);\n return {\n newEntry: undefined,\n newView: arrayWith(mutate, view, rawPos, updated),\n };\n }\n const newEntry = makeNewMetaEntry(row, schema, withIDs, 1);\n return {newEntry, newView: insertAt(mutate, view, ~rawPos, newEntry)};\n}\n\nfunction insertAt<M extends Mutate, T>(\n mutate: M,\n array: MutableArray<M, T>,\n index: number,\n item: T,\n): T[] {\n if (mutate || owns(array)) {\n (array as T[]).splice(index, 0, item);\n return array as T[];\n }\n return track(array.toSpliced(index, 0, item));\n}\n\nfunction removeAt<M extends Mutate, T>(\n mutate: M,\n array: MutableArray<M, T>,\n index: number,\n): T[] {\n if (mutate || owns(array)) {\n (array as T[]).splice(index, 1);\n return array as T[];\n }\n return track(array.toSpliced(index, 1));\n}\n\nfunction removeAndUpdateRefCount<M extends Mutate>(\n view: MetaEntryList<M>,\n row: Row,\n compareRows: Comparator,\n mutate: M,\n): MutableMetaEntryList {\n const pos = binarySearch(view, row, compareRows);\n assert(pos >= 0, 'node does not exist');\n const oldEntry = view[pos];\n const rc = oldEntry[refCountSymbol];\n if (rc === 1) {\n return removeAt(mutate, view, pos);\n }\n const newEntry = decRefCount(mutate, oldEntry);\n return arrayWith(mutate, view, pos, newEntry);\n}\n\n/**\n * Binary search returning a number.\n * - If found at index `i`: returns `i` (>= 0).\n * - If not found, insertion point is `low`: returns `~low` (< 0).\n * Recover insertion point with `~result`.\n */\nfunction binarySearch(\n view: MetaEntryList<Mutate>,\n target: Row,\n comparator: Comparator,\n): number {\n let high = view.length - 1;\n if (high < 0) {\n return ~0;\n }\n\n // Probe the last entry before searching. Hydration feeds the view rows in\n // the query's sort order, so every insert belongs at the end and the plain\n // search spends log2(n) comparisons to rediscover that -- about eleven per\n // row for a view of a couple of thousand. Row comparison is the single\n // largest cost in hydration on Hermes, so collapsing those eleven to one is\n // worth the one extra comparison this costs when the row does land inside\n // the view, which is a single push rather than a bulk load.\n // MetaEntry has all Row props; comparator only reads string keys\n const last = comparator(view[high] as Row, target);\n if (last < 0) {\n return ~(high + 1);\n }\n if (last === 0) {\n return high;\n }\n\n let low = 0;\n high -= 1;\n while (low <= high) {\n const mid = (low + high) >>> 1;\n const comparison = comparator(view[mid] as Row, target);\n if (comparison < 0) {\n low = mid + 1;\n } else if (comparison > 0) {\n high = mid - 1;\n } else {\n return mid;\n }\n }\n return ~low;\n}\n\n/** Assert value is MetaEntry (has refCountSymbol). */\nfunction assertMetaEntry<M extends Mutate>(\n v: unknown,\n): asserts v is MetaEntry<M> {\n assertNumber((v as Partial<MetaEntry<M>>)[refCountSymbol]);\n}\n\n/** Get singular MetaEntry, throws if missing. */\nfunction getSingularEntry<M extends Mutate>(\n parentEntry: MetaEntry<M>,\n relationship: string,\n): MetaEntry<M> {\n const entry = parentEntry[relationship];\n assert(entry !== undefined, 'node does not exist');\n assertMetaEntry(entry);\n return entry;\n}\n\n/** Get singular MetaEntry or undefined if not set. */\nfunction getOptionalSingularEntry<M extends Mutate>(\n parentEntry: MetaEntry<M>,\n relationship: string,\n): MetaEntry<M> | undefined {\n const entry = parentEntry[relationship];\n if (entry === undefined) {\n return undefined;\n }\n assertMetaEntry(entry);\n return entry;\n}\n\n/** Get child array as MetaEntryList. */\nfunction getChildEntryList<M extends Mutate>(\n parentEntry: MetaEntry<M>,\n relationship: string,\n): MetaEntryList<M> {\n const view = parentEntry[relationship];\n // `relationship` is a string key, so a name colliding with a column would\n // put a row value here; the message must not repeat it back.\n assert(Array.isArray(view), 'expected relationship array');\n return view as MetaEntryList<M>;\n}\n\n/** Create MetaEntry from row with given refCount. */\nfunction makeNewMetaEntry(\n row: Row,\n schema: SourceSchema,\n withIDs: WithIDs,\n rc: number,\n): MutableMetaEntry {\n // This creates a new MetaEntry from a Row. We never mutate Rows.\n if (withIDs) {\n return track({\n ...row,\n [refCountSymbol]: rc,\n [idSymbol]: makeID(row, schema),\n });\n }\n return track({\n ...row,\n [refCountSymbol]: rc,\n });\n}\n\nfunction makeID(row: Row, schema: SourceSchema) {\n // optimization for case of non-compound primary key\n if (schema.primaryKey.length === 1) {\n return JSON.stringify(row[schema.primaryKey[0]]);\n }\n return JSON.stringify(schema.primaryKey.map(k => row[k]));\n}\n\nfunction incRefCount<M extends Mutate>(\n mutate: M,\n entry: MetaEntry<M>,\n): MutableMetaEntry {\n return setRefCount(mutate, entry, entry[refCountSymbol] + 1);\n}\n\nfunction decRefCount<M extends Mutate>(\n mutate: M,\n entry: MetaEntry<M>,\n): MutableMetaEntry {\n return setRefCount(mutate, entry, entry[refCountSymbol] - 1);\n}\n\nfunction setRefCount<M extends Mutate>(\n mutate: M,\n entry: MetaEntry<M>,\n count: number,\n): MutableMetaEntry {\n if (mutate || owns(entry)) {\n (entry as MutableMetaEntry)[refCountSymbol] = count;\n return entry;\n }\n return track({\n ...entry,\n [refCountSymbol]: count,\n });\n}\n\nfunction arrayWith<M extends Mutate, T>(\n mutate: M,\n array: MutableArray<M, T>,\n index: number,\n value: T,\n): T[] {\n if (mutate || owns(array)) {\n (array as T[])[index] = value;\n return array as T[];\n }\n return track(array.with(index, value));\n}\n\nfunction setProperty<\n M extends Mutate,\n K extends string | keyof MetaEntry<M>,\n V,\n>(\n mutate: M,\n parentEntry: MetaEntry<M>,\n key: K,\n value: V,\n): MutableMetaEntry & {[P in K]: V} {\n if (mutate || owns(parentEntry)) {\n assignProperty(parentEntry as {[P in K]: V}, key, value);\n return parentEntry as MutableMetaEntry & {[P in K]: V};\n }\n return track({\n ...parentEntry,\n [key]: value,\n }) as MutableMetaEntry & {[P in K]: V};\n}\n\nconst setRelation: <M extends Mutate>(\n mutate: M,\n parentEntry: MetaEntry<M>,\n relationship: string,\n value: Entry | Entry[] | undefined,\n) => MutableMetaEntry = setProperty;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"view-apply-change.js","names":[],"sources":["../../../../../zql/src/ivm/view-apply-change.ts"],"sourcesContent":["import {\n assert,\n assertNumber,\n unreachable,\n} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {assignProperty} from '../../../shared/src/objects.ts';\nimport type {Writable} from '../../../shared/src/writable.ts';\nimport type {Row} from '../../../zero-protocol/src/data.ts';\nimport {AGGREGATE_VALUE_COLUMN} from './aggregate.ts';\nimport {type Comparator, type Node} from './data.ts';\nimport {skipYields} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {Entry, Format} from './view.ts';\n\nexport const refCountSymbol = Symbol('rc');\nexport const idSymbol = Symbol('id');\n\ntype ReadonlyMetaEntry = Entry & {\n readonly [refCountSymbol]: number;\n readonly [idSymbol]?: string | undefined;\n};\n\ntype MutableMetaEntry = Writable<ReadonlyMetaEntry>;\n\n/**\n * MetaEntry is an Entry with internal tracking fields for reference counting\n * and stable identity. All entries in the view tree are MetaEntry instances,\n * created through makeNewMetaEntry().\n */\ntype MetaEntry<M extends Mutate> = M extends true\n ? MutableMetaEntry\n : ReadonlyMetaEntry;\n\ntype MutableArray<M extends Mutate, T> = M extends true ? T[] : readonly T[];\n\ntype MetaEntryList<M extends Mutate> = MutableArray<M, MetaEntry<M>>;\n\ntype MutableMetaEntryList = MutableMetaEntry[];\n\n/**\n * Node with eagerly-expanded relationships (arrays instead of generators).\n * Used when batching changes to capture source state at push time.\n */\ntype ExpandedNode = {\n row: Row;\n relationships: Record<string, ExpandedNode[]>;\n};\n\n/**\n * A node for view changes. Can be either:\n * - A lazy Node with generator relationships (original behavior)\n * - An ExpandedNode with pre-computed relationship arrays (for batching)\n */\ntype ViewNode = Node | ExpandedNode;\n\n/**\n * `applyChange` does not consume the `relationships` of `ChildChange#node`,\n * `EditChange#node` and `EditChange#oldNode`. The `ViewChange` type\n * documents and enforces this via the type system.\n */\nexport type ViewChange =\n | AddViewChange\n | RemoveViewChange\n | ChildViewChange\n | EditViewChange;\n\ntype RowOnlyNode = {row: Row};\n\ntype AddViewChange = {\n type: 'add';\n node: ViewNode;\n};\n\ntype RemoveViewChange = {\n type: 'remove';\n node: ViewNode;\n};\n\ntype ChildViewChange = {\n type: 'child';\n node: RowOnlyNode;\n child: {\n relationshipName: string;\n change: ViewChange;\n };\n};\n\ntype EditViewChange = {\n type: 'edit';\n node: RowOnlyNode;\n oldNode: RowOnlyNode;\n};\n\n/**\n * Get child nodes from a relationship, handling both lazy (Node) and expanded (ExpandedNode).\n */\nfunction getChildNodes(\n node: ViewNode,\n relationship: string,\n): Iterable<ViewNode> {\n const children = node.relationships[relationship];\n if (Array.isArray(children)) {\n // ExpandedNode: already an array\n return children;\n }\n // Node: lazy generator function\n return skipYields(children());\n}\n\ntype Mutate = boolean;\ntype WithIDs = boolean;\n\n/**\n * Transaction-scoped copy-on-write.\n *\n * `applyChange` is immutable: it path-copies a new spine on every call so the\n * previously-emitted snapshot is never mutated. But within a single transaction\n * (a run of `push()` calls before `flush()`) only the final state is ever\n * observed by a listener, so the intermediate spines are pure allocation/GC\n * churn — making a K-change transaction O(K * spineSize).\n *\n * When the caller passes a WeakSet as the `mutate` argument, objects and arrays\n * that were created or cloned during the current transaction are recorded in it.\n * A helper that needs to modify such an object mutates it in place (it is not\n * yet observed) instead of cloning again. The first touch of a node still\n * copies-on-write (preserving the committed snapshot's references); subsequent\n * touches in the same transaction are in-place. This brings a K-change\n * transaction to O(spineSize + K) while keeping cross-transaction reference\n * stability.\n *\n * The set is module-scoped for the duration of a single (synchronous,\n * non-re-entrant) `applyChange` call and saved/restored around it.\n *\n * It is a WeakSet on purpose: it must not keep transiently-cloned arrays/entries\n * alive. A strong Set would extend their lifetime to the end of the transaction,\n * pushing large clones out of the young generation and adding GC pressure that\n * shows up as a regression on single-change transactions over wide lists. A\n * WeakSet gives the same identity test (\"created this transaction?\") while\n * letting dead clones be collected as cheaply as in the plain immutable path.\n */\nlet activeDirty: WeakSet<object> | undefined;\n\n/** True if `o` was created/cloned during the current transaction. */\nfunction owns(o: object): boolean {\n return activeDirty !== undefined && activeDirty.has(o);\n}\n\n/** Record `o` as owned by the current transaction (no-op outside a txn). */\nfunction track<T extends object>(o: T): T {\n activeDirty?.add(o);\n return o;\n}\n\n/**\n * Immutable view update. Returns new Entry on change, same Entry if unchanged.\n * Unchanged entries keep identity, enabling shallow comparison optimizations\n * in UI frameworks (React.memo, Solid's fine-grained reactivity, etc).\n *\n * Propagation: recurse DOWN to find target, copy objects on the way UP.\n * Siblings keep original refs. Only the ancestor path is copied.\n *\n * root {users:[A,B], items:[C,D,E]} --edit C--> root' {users:[A,B], items':[C',D,E]}\n * │ same ref │ │ │\n * └─────────────────┴── unchanged ──────────────┘ new array C' new, D/E same\n *\n * `mutate` selects the update strategy:\n * - `false` (default): fully immutable; every changed node is path-copied.\n * - `true`: mutate the tree in place (only safe when it is not yet observed,\n * e.g. during initial hydration).\n * - a `WeakSet`: transaction-scoped copy-on-write — copy on first touch, then\n * mutate in place for the rest of the transaction. See `activeDirty` above.\n */\nexport function applyChange(\n parentEntry: Entry,\n change: ViewChange,\n schema: SourceSchema,\n relationship: string,\n format: Format,\n withIDs = false,\n mutate: boolean | WeakSet<object> = false,\n): Entry {\n // A WeakSet selects copy-on-write; a boolean selects full mutate / immutable.\n const inPlace = mutate === true;\n const prevDirty = activeDirty;\n activeDirty = typeof mutate === 'object' ? mutate : undefined;\n try {\n return applyChangeInternal(\n parentEntry as MetaEntry<typeof inPlace>,\n change,\n schema,\n relationship,\n format,\n withIDs,\n inPlace,\n );\n } finally {\n activeDirty = prevDirty;\n }\n}\n\nfunction applyChangeInternal<M extends Mutate>(\n parentEntry: MetaEntry<M>,\n change: ViewChange,\n schema: SourceSchema,\n relationship: string,\n format: Format,\n withIDs: WithIDs,\n mutate: M,\n): MetaEntry<M> {\n if (schema.isHidden) {\n switch (change.type) {\n case 'add':\n case 'remove': {\n let currentParent = parentEntry;\n for (const relationship of Object.keys(change.node.relationships)) {\n const childSchema = must(schema.relationships[relationship]);\n for (const node of getChildNodes(change.node, relationship)) {\n currentParent = applyChangeInternal(\n currentParent,\n {type: change.type, node},\n childSchema,\n relationship,\n format,\n withIDs,\n mutate,\n );\n }\n }\n return currentParent;\n }\n case 'edit':\n // If hidden at this level it means that the hidden row was changed. If\n // the row was changed in such a way that it would change the\n // relationships then the edit would have been split into remove and\n // add.\n return parentEntry;\n case 'child': {\n const childSchema = must(\n schema.relationships[change.child.relationshipName],\n );\n return applyChangeInternal(\n parentEntry,\n change.child.change,\n childSchema,\n relationship,\n format,\n withIDs,\n mutate,\n );\n }\n default:\n unreachable(change);\n }\n }\n\n // An `aggregate` relationship (see aggregate.ts) materializes as the bare\n // scalar value rather than the synthetic child row. It is singular and\n // childless, so add/edit set the value, remove resets it to the empty value,\n // no child changes.\n if (format.aggregate) {\n switch (change.type) {\n case 'add':\n case 'edit':\n return setProperty(\n mutate,\n parentEntry,\n relationship,\n change.node.row[AGGREGATE_VALUE_COLUMN],\n );\n case 'remove':\n return setProperty(\n mutate,\n parentEntry,\n relationship,\n emptyAggregateValue(format.aggregate),\n );\n case 'child':\n return parentEntry;\n default:\n unreachable(change);\n }\n }\n\n const {singular, relationships: childFormats} = format;\n switch (change.type) {\n // ADD: Insert row (rc=1) or increment refCount if duplicate.\n // RefCount tracks when the same row is reachable via multiple edges\n // within a relationship:\n //\n // issue.labels: [L1, L2] both point to same creator\n // │ │ │\n // ▼ ▼ ▼\n // label.creator ──────────► [User:rc=2] (one row, two refs)\n //\n // add(A) add(A) remove(A) remove(A)\n // ↓ ↓ ↓ ↓\n // [A:rc=1] → [A:rc=2] → [A:rc=1] → (deleted)\n case 'add': {\n if (singular) {\n const oldEntry = getOptionalSingularEntry(parentEntry, relationship);\n if (oldEntry !== undefined) {\n // Duplicate add: increment refCount\n assert(\n schema.compareRows(oldEntry, change.node.row) === 0,\n `Singular relationship '${relationship}' should not have multiple rows. You may need to declare this relationship with the \\`many\\` helper instead of the \\`one\\` helper in your schema.`,\n );\n\n const newEntry = incRefCount(mutate, oldEntry);\n return setRelation(mutate, parentEntry, relationship, newEntry);\n } else {\n // New row: create with rc=1, initialize nested relationships\n const newEntry = makeNewMetaEntry(\n change.node.row,\n schema,\n withIDs,\n 1,\n );\n initializeRelationshipsForNewEntryIfAny(\n newEntry,\n change.node,\n schema,\n childFormats,\n withIDs,\n );\n return setRelation(mutate, parentEntry, relationship, newEntry);\n }\n } else {\n // Plural: binary search for position, insert or increment refCount\n const view = getChildEntryList(parentEntry, relationship);\n const {newEntry, newView} = add(\n change.node.row,\n view,\n schema,\n withIDs,\n mutate,\n );\n\n if (newEntry) {\n initializeRelationshipsForNewEntryIfAny(\n newEntry,\n change.node,\n schema,\n childFormats,\n withIDs,\n );\n }\n return setRelation(mutate, parentEntry, relationship, newView);\n }\n }\n // REMOVE: Decrement refCount, physically remove when rc=0.\n case 'remove': {\n if (singular) {\n const oldEntry = getSingularEntry(parentEntry, relationship);\n const rc = oldEntry[refCountSymbol];\n if (rc === 1) {\n return setRelation(mutate, parentEntry, relationship, undefined);\n }\n const newEntry = decRefCount(mutate, oldEntry);\n return setRelation(mutate, parentEntry, relationship, newEntry);\n } else {\n const view = getChildEntryList(parentEntry, relationship);\n const newView = removeAndUpdateRefCount(\n view,\n change.node.row,\n schema.compareRows,\n mutate,\n );\n return setRelation(mutate, parentEntry, relationship, newView);\n }\n }\n // CHILD: Propagate nested change up to this level (leaf-to-root pattern).\n case 'child': {\n const childSchema = must(\n schema.relationships[change.child.relationshipName],\n );\n const childFormat = format.relationships[change.child.relationshipName];\n if (childFormat === undefined) {\n return parentEntry; // Relationship not in view format\n }\n\n if (singular) {\n const existing = getSingularEntry(parentEntry, relationship);\n const newExisting = applyChangeInternal(\n existing,\n change.child.change,\n childSchema,\n change.child.relationshipName,\n childFormat,\n withIDs,\n mutate,\n );\n // Preserve identity if child didn't change (enables shallow-compare optimizations).\n if (newExisting === existing) {\n return parentEntry;\n }\n return setRelation(mutate, parentEntry, relationship, newExisting);\n } else {\n // Find the target row in the sorted array via binary search.\n const view = getChildEntryList(parentEntry, relationship);\n const pos = binarySearch(view, change.node.row, schema.compareRows);\n assert(pos >= 0, 'node does not exist');\n const existing = view[pos];\n const newExisting = applyChangeInternal(\n existing,\n change.child.change,\n childSchema,\n change.child.relationshipName,\n childFormat,\n withIDs,\n mutate,\n );\n // Preserve identity if descendant didn't change.\n if (newExisting === existing) {\n return parentEntry;\n }\n // applyChangeInternal preserves MetaEntry structure when input is MetaEntry\n assertMetaEntry(newExisting);\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n arrayWith(mutate, view, pos, newExisting),\n );\n }\n }\n // EDIT: Update row fields. If sort key changes, row may move position.\n //\n // Position change with rc>1 (two query paths reach same row):\n //\n // Before: [A, B(rc=2), C, D] After: [A, B(rc=1), C, B'(rc=1), D]\n // │ │ │\n // path1 + path2 path1 ghost path2 moved\n //\n // Why ghost stays: path1 still expects B at old position.\n // B' appears where path2's sort order places it.\n case 'edit': {\n if (singular) {\n const existing = getSingularEntry(parentEntry, relationship);\n const newEntry = applyEdit(existing, change, schema, withIDs, mutate);\n return setRelation(mutate, parentEntry, relationship, newEntry);\n } else {\n const view = getChildEntryList(parentEntry, relationship);\n // Sort key changed: row may need to move\n if (schema.compareRows(change.oldNode.row, change.node.row) !== 0) {\n const oldPos = binarySearch(\n view,\n change.oldNode.row,\n schema.compareRows,\n );\n assert(oldPos >= 0, 'old node does not exist');\n const oldEntry = view[oldPos];\n const rawPos = binarySearch(\n view,\n change.node.row,\n schema.compareRows,\n );\n const found = rawPos >= 0;\n const pos = found ? rawPos : ~rawPos;\n // A special case:\n // when refCount is 1 (so the row is being moved without leaving a\n // placeholder behind), and the new pos is the same as the old, or\n // directly after the old (so after the remove of the old it would be\n // in the same pos): the row does not need to be moved, just edited.\n if (\n oldEntry[refCountSymbol] === 1 &&\n (pos === oldPos || pos - 1 === oldPos)\n ) {\n const newEntry = applyEdit(\n oldEntry,\n change,\n schema,\n withIDs,\n mutate,\n );\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n arrayWith(mutate, view, oldPos, newEntry),\n );\n } else {\n // Move the row. If rc > 1, an edit will be received for each ref.\n // On first edit: move row, apply edit, set rc=1. Leave a copy at\n // old pos with decremented rc. As each edit arrives, old copy's rc\n // decrements and new pos rc increments. When copy rc hits 0, remove.\n const newRefCount = oldEntry[refCountSymbol] - 1;\n let newView: MutableMetaEntryList;\n let adjustedPos = pos;\n\n if (newRefCount === 0) {\n newView = removeAt(mutate, view, oldPos);\n adjustedPos = oldPos < pos ? pos - 1 : pos;\n } else {\n const oldEntryCopy = setRefCount(mutate, oldEntry, newRefCount);\n newView = arrayWith(mutate, view, oldPos, oldEntryCopy);\n }\n\n if (found) {\n // Merge with existing at new pos\n const existingEntry = newView[adjustedPos];\n const editedEntry = applyEdit(\n existingEntry,\n change,\n schema,\n withIDs,\n mutate,\n );\n const updatedEntry = setRefCount(\n mutate,\n editedEntry,\n existingEntry[refCountSymbol] + 1,\n );\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n arrayWith(mutate, newView, adjustedPos, updatedEntry),\n );\n } else {\n // Insert at new pos with rc=1\n const editedEntry = applyEdit(\n oldEntry,\n change,\n schema,\n withIDs,\n mutate,\n );\n const movedEntry = setRefCount(mutate, editedEntry, 1);\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n insertAt(mutate, newView, adjustedPos, movedEntry),\n );\n }\n }\n } else {\n // Sort key unchanged: edit in place\n const pos = binarySearch(\n view,\n change.oldNode.row,\n schema.compareRows,\n );\n assert(pos >= 0, 'node does not exist');\n const newEntry = applyEdit(\n view[pos],\n change,\n schema,\n withIDs,\n mutate,\n );\n return setRelation(\n mutate,\n parentEntry,\n relationship,\n arrayWith(mutate, view, pos, newEntry),\n );\n }\n }\n }\n default:\n unreachable(change);\n }\n}\n\nfunction applyEdit<M extends Mutate>(\n existing: MetaEntry<M>,\n change: EditViewChange,\n schema: SourceSchema,\n withIDs: WithIDs,\n mutate: Mutate,\n): MetaEntry<M> {\n // In-place edit is safe when fully mutating or when `existing` was already\n // created/cloned in this transaction (copy-on-write). A primary-key change\n // always needs a fresh entry so identity tracks the new key.\n const canMutate = mutate || owns(existing);\n const newEntry: MutableMetaEntry =\n canMutate && schema.compareRows(change.oldNode.row, change.node.row) === 0\n ? Object.assign(existing, change.node.row)\n : track({...existing, ...change.node.row});\n\n if (withIDs) {\n return setProperty(\n mutate,\n newEntry,\n idSymbol,\n makeID(change.node.row, schema),\n );\n }\n return newEntry;\n}\n\n/**\n * Initialize child relationships on a newly-added entry by mutating it in place.\n *\n * New nodes don't exist in the view yet, so we can build in-place (no refs to preserve):\n *\n * New nodes don't exist in the view yet, so we can build in-place (no refs to preserve):\n *\n * Existing node edit (must path-copy): New node (can build in-place):\n *\n * view: [A, B, C] view: [A, B, _]\n * │ │\n * edit C.child add C with children\n * │ │\n * ▼ ▼\n * view': [A, B, C'] ◄─ path-copy view': [A, B, C{children:[...]}]\n * up the tree └─ built in-place\n *\n * Hidden schemas still use applyChange to collapse junction levels.\n */\nfunction initializeRelationshipsForNewEntryIfAny(\n entry: MutableMetaEntry,\n node: ViewNode,\n schema: SourceSchema,\n childFormats: Record<string, Format>,\n withIDs: WithIDs,\n): void {\n let result = entry;\n for (const relationship of Object.keys(node.relationships)) {\n const childSchema = must(schema.relationships[relationship]);\n const childFormat = childFormats[relationship];\n if (childFormat === undefined) {\n continue;\n }\n\n // Hidden/singular: use applyChange to handle properly\n if (childSchema.isHidden || childFormat.singular) {\n // An aggregate starts at its empty value: a parent whose correlation\n // value is null gets no aggregate row from the Join, but SQL still gives\n // it `0` (count) or `null`.\n const newView = childFormat.aggregate\n ? emptyAggregateValue(childFormat.aggregate)\n : childFormat.singular\n ? undefined\n : track([] as MutableMetaEntryList);\n result[relationship] = newView;\n\n for (const childNode of getChildNodes(node, relationship)) {\n applyChangeInternal(\n result,\n {type: 'add', node: childNode},\n childSchema,\n relationship,\n childFormat,\n withIDs,\n true, // this is a new entry, so we can mutate\n );\n }\n } else {\n // Plural non-hidden: build array in-place for efficiency\n const childArray: MutableMetaEntryList = track([]);\n\n for (const childNode of getChildNodes(node, relationship)) {\n const newEntry = makeNewMetaEntry(\n childNode.row,\n childSchema,\n withIDs,\n 1,\n );\n const rawPos = binarySearch(\n childArray,\n childNode.row,\n childSchema.compareRows,\n );\n\n if (rawPos >= 0) {\n childArray[rawPos][refCountSymbol]++;\n } else {\n childArray.splice(~rawPos, 0, newEntry);\n initializeRelationshipsForNewEntryIfAny(\n newEntry,\n childNode,\n childSchema,\n childFormat.relationships,\n withIDs,\n );\n }\n }\n\n result[relationship] = childArray;\n }\n }\n}\n\nfunction add<M extends Mutate>(\n row: Row,\n view: MetaEntryList<M>,\n schema: SourceSchema,\n withIDs: WithIDs,\n mutate: M,\n): {\n newEntry: MutableMetaEntry | undefined;\n newView: MutableMetaEntryList;\n} {\n const rawPos = binarySearch(view, row, schema.compareRows);\n\n if (rawPos >= 0) {\n const existing = view[rawPos];\n\n const updated = incRefCount(mutate, existing);\n return {\n newEntry: undefined,\n newView: arrayWith(mutate, view, rawPos, updated),\n };\n }\n const newEntry = makeNewMetaEntry(row, schema, withIDs, 1);\n return {newEntry, newView: insertAt(mutate, view, ~rawPos, newEntry)};\n}\n\nfunction insertAt<M extends Mutate, T>(\n mutate: M,\n array: MutableArray<M, T>,\n index: number,\n item: T,\n): T[] {\n if (mutate || owns(array)) {\n (array as T[]).splice(index, 0, item);\n return array as T[];\n }\n return track(array.toSpliced(index, 0, item));\n}\n\nfunction removeAt<M extends Mutate, T>(\n mutate: M,\n array: MutableArray<M, T>,\n index: number,\n): T[] {\n if (mutate || owns(array)) {\n (array as T[]).splice(index, 1);\n return array as T[];\n }\n return track(array.toSpliced(index, 1));\n}\n\nfunction removeAndUpdateRefCount<M extends Mutate>(\n view: MetaEntryList<M>,\n row: Row,\n compareRows: Comparator,\n mutate: M,\n): MutableMetaEntryList {\n const pos = binarySearch(view, row, compareRows);\n assert(pos >= 0, 'node does not exist');\n const oldEntry = view[pos];\n const rc = oldEntry[refCountSymbol];\n if (rc === 1) {\n return removeAt(mutate, view, pos);\n }\n const newEntry = decRefCount(mutate, oldEntry);\n return arrayWith(mutate, view, pos, newEntry);\n}\n\n/**\n * Binary search returning a number.\n * - If found at index `i`: returns `i` (>= 0).\n * - If not found, insertion point is `low`: returns `~low` (< 0).\n * Recover insertion point with `~result`.\n */\nfunction binarySearch(\n view: MetaEntryList<Mutate>,\n target: Row,\n comparator: Comparator,\n): number {\n let high = view.length - 1;\n if (high < 0) {\n return ~0;\n }\n\n // Probe the last entry before searching. Hydration feeds the view rows in\n // the query's sort order, so every insert belongs at the end and the plain\n // search spends log2(n) comparisons to rediscover that -- about eleven per\n // row for a view of a couple of thousand. Row comparison is the single\n // largest cost in hydration on Hermes, so collapsing those eleven to one is\n // worth the one extra comparison this costs when the row does land inside\n // the view, which is a single push rather than a bulk load.\n // MetaEntry has all Row props; comparator only reads string keys\n const last = comparator(view[high] as Row, target);\n if (last < 0) {\n return ~(high + 1);\n }\n if (last === 0) {\n return high;\n }\n\n let low = 0;\n high -= 1;\n while (low <= high) {\n const mid = (low + high) >>> 1;\n const comparison = comparator(view[mid] as Row, target);\n if (comparison < 0) {\n low = mid + 1;\n } else if (comparison > 0) {\n high = mid - 1;\n } else {\n return mid;\n }\n }\n return ~low;\n}\n\n/** Assert value is MetaEntry (has refCountSymbol). */\nfunction assertMetaEntry<M extends Mutate>(\n v: unknown,\n): asserts v is MetaEntry<M> {\n assertNumber((v as Partial<MetaEntry<M>>)[refCountSymbol]);\n}\n\n/** Get singular MetaEntry, throws if missing. */\nfunction getSingularEntry<M extends Mutate>(\n parentEntry: MetaEntry<M>,\n relationship: string,\n): MetaEntry<M> {\n const entry = parentEntry[relationship];\n assert(entry !== undefined, 'node does not exist');\n assertMetaEntry(entry);\n return entry;\n}\n\n/** Get singular MetaEntry or undefined if not set. */\nfunction getOptionalSingularEntry<M extends Mutate>(\n parentEntry: MetaEntry<M>,\n relationship: string,\n): MetaEntry<M> | undefined {\n const entry = parentEntry[relationship];\n if (entry === undefined) {\n return undefined;\n }\n assertMetaEntry(entry);\n return entry;\n}\n\n/** Get child array as MetaEntryList. */\nfunction getChildEntryList<M extends Mutate>(\n parentEntry: MetaEntry<M>,\n relationship: string,\n): MetaEntryList<M> {\n const view = parentEntry[relationship];\n // `relationship` is a string key, so a name colliding with a column would\n // put a row value here; the message must not repeat it back.\n assert(Array.isArray(view), 'expected relationship array');\n return view as MetaEntryList<M>;\n}\n\n/** Create MetaEntry from row with given refCount. */\nfunction makeNewMetaEntry(\n row: Row,\n schema: SourceSchema,\n withIDs: WithIDs,\n rc: number,\n): MutableMetaEntry {\n // This creates a new MetaEntry from a Row. We never mutate Rows.\n if (withIDs) {\n return track({\n ...row,\n [refCountSymbol]: rc,\n [idSymbol]: makeID(row, schema),\n });\n }\n return track({\n ...row,\n [refCountSymbol]: rc,\n });\n}\n\nfunction makeID(row: Row, schema: SourceSchema) {\n // optimization for case of non-compound primary key\n if (schema.primaryKey.length === 1) {\n return JSON.stringify(row[schema.primaryKey[0]]);\n }\n return JSON.stringify(schema.primaryKey.map(k => row[k]));\n}\n\nfunction incRefCount<M extends Mutate>(\n mutate: M,\n entry: MetaEntry<M>,\n): MutableMetaEntry {\n return setRefCount(mutate, entry, entry[refCountSymbol] + 1);\n}\n\nfunction decRefCount<M extends Mutate>(\n mutate: M,\n entry: MetaEntry<M>,\n): MutableMetaEntry {\n return setRefCount(mutate, entry, entry[refCountSymbol] - 1);\n}\n\nfunction setRefCount<M extends Mutate>(\n mutate: M,\n entry: MetaEntry<M>,\n count: number,\n): MutableMetaEntry {\n if (mutate || owns(entry)) {\n (entry as MutableMetaEntry)[refCountSymbol] = count;\n return entry;\n }\n return track({\n ...entry,\n [refCountSymbol]: count,\n });\n}\n\nfunction arrayWith<M extends Mutate, T>(\n mutate: M,\n array: MutableArray<M, T>,\n index: number,\n value: T,\n): T[] {\n if (mutate || owns(array)) {\n (array as T[])[index] = value;\n return array as T[];\n }\n return track(array.with(index, value));\n}\n\n/**\n * The result of a view before any row arrives: the empty aggregate value for an\n * aggregate, `undefined` for `one()`, and `[]` otherwise.\n */\nexport function emptyViewValue(format: Format): 0 | null | undefined | [] {\n if (format.aggregate) {\n return emptyAggregateValue(format.aggregate);\n }\n return format.singular ? undefined : [];\n}\n\n/** An aggregate over no rows, as in SQL: `count` is `0`, the rest `null`. */\nfunction emptyAggregateValue(\n aggregate: NonNullable<Format['aggregate']>,\n): 0 | null {\n return aggregate.fn === 'count' ? 0 : null;\n}\n\nfunction setProperty<\n M extends Mutate,\n K extends string | keyof MetaEntry<M>,\n V,\n>(\n mutate: M,\n parentEntry: MetaEntry<M>,\n key: K,\n value: V,\n): MutableMetaEntry & {[P in K]: V} {\n if (mutate || owns(parentEntry)) {\n assignProperty(parentEntry as {[P in K]: V}, key, value);\n return parentEntry as MutableMetaEntry & {[P in K]: V};\n }\n return track({\n ...parentEntry,\n [key]: value,\n }) as MutableMetaEntry & {[P in K]: V};\n}\n\nconst setRelation: <M extends Mutate>(\n mutate: M,\n parentEntry: MetaEntry<M>,\n relationship: string,\n value: Entry | Entry[] | undefined,\n) => MutableMetaEntry = setProperty;\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as v from '../../../shared/src/valita.ts';
|
|
2
2
|
import type { attemptStartEventJSONSchema, bestPlanSelectedEventJSONSchema, connectionSelectedEventJSONSchema, nodeConstraintEventJSONSchema, PlanDebugEventJSON, planFailedEventJSONSchema } from '../../../zero-protocol/src/analyze-query-result.ts';
|
|
3
|
-
import type
|
|
3
|
+
import { type Condition, type Ordering } from '../../../zero-protocol/src/ast.ts';
|
|
4
4
|
import type { PlannerConstraint } from './planner-constraint.ts';
|
|
5
5
|
import type { PlanState } from './planner-graph.ts';
|
|
6
6
|
import type { CostEstimate, JoinType } from './planner-node.ts';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-debug.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-debug.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"planner-debug.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-debug.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { unreachable } from "../../../shared/src/asserts.js";
|
|
1
2
|
import { getOrInsertComputed } from "../../../shared/src/map.js";
|
|
2
3
|
import { newArray } from "../../../shared/src/arrays.js";
|
|
4
|
+
import { formatJsonPathReference } from "../../../zero-protocol/src/ast.js";
|
|
3
5
|
//#region ../zql/src/planner/planner-debug.ts
|
|
4
6
|
/**
|
|
5
7
|
* Simple accumulator debugger that stores all events.
|
|
@@ -41,10 +43,12 @@ function formatConstraint(constraint) {
|
|
|
41
43
|
function formatValuePosition(value) {
|
|
42
44
|
switch (value.type) {
|
|
43
45
|
case "column": return value.name;
|
|
46
|
+
case "json": return formatJsonPathReference(value);
|
|
44
47
|
case "literal":
|
|
45
48
|
if (typeof value.value === "string") return `'${value.value}'`;
|
|
46
49
|
return JSON.stringify(value.value);
|
|
47
50
|
case "static": return `@${value.anchor}.${Array.isArray(value.field) ? value.field.join(".") : value.field}`;
|
|
51
|
+
default: unreachable(value);
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
54
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-debug.js","names":[],"sources":["../../../../../zql/src/planner/planner-debug.ts"],"sourcesContent":["import {newArray} from '../../../shared/src/arrays.ts';\nimport {getOrInsertComputed} from '../../../shared/src/map.ts';\nimport type * as v from '../../../shared/src/valita.ts';\nimport type {\n attemptStartEventJSONSchema,\n bestPlanSelectedEventJSONSchema,\n connectionSelectedEventJSONSchema,\n nodeConstraintEventJSONSchema,\n PlanDebugEventJSON,\n planFailedEventJSONSchema,\n} from '../../../zero-protocol/src/analyze-query-result.ts';\nimport type {\n Condition,\n Ordering,\n ValuePosition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanState} from './planner-graph.ts';\nimport type {CostEstimate, JoinType} from './planner-node.ts';\n\n/**\n * Structured debug events emitted during query planning.\n * These events can be accumulated, printed, or analyzed to understand\n * the planner's decision-making process.\n */\n\n/**\n * Starting a new planning attempt with a different root connection.\n */\ntype AttemptStartEvent = v.Infer<typeof attemptStartEventJSONSchema>;\n\n/**\n * Snapshot of connection costs before selecting the next connection.\n */\ntype ConnectionCostsEvent = {\n type: 'connection-costs';\n attemptNumber: number;\n costs: Array<{\n connection: string;\n cost: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n pinned: boolean;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A connection was chosen and pinned.\n */\ntype ConnectionSelectedEvent = v.Infer<\n typeof connectionSelectedEventJSONSchema\n>;\n\n/**\n * Constraints have been propagated through the graph.\n */\ntype ConstraintsPropagatedEvent = {\n type: 'constraints-propagated';\n attemptNumber: number;\n connectionConstraints: Array<{\n connection: string;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A complete plan was found for this attempt.\n */\ntype PlanCompleteEvent = {\n type: 'plan-complete';\n attemptNumber: number;\n totalCost: number;\n flipPattern: number; // Bitmask indicating which joins are flipped\n joinStates: Array<{\n join: string;\n type: JoinType;\n }>;\n // Planning snapshot that can be restored and applied to AST\n planSnapshot: PlanState;\n};\n\n/**\n * Planning attempt failed (e.g., unflippable join).\n */\ntype PlanFailedEvent = v.Infer<typeof planFailedEventJSONSchema>;\n\n/**\n * The best plan across all attempts was selected.\n */\ntype BestPlanSelectedEvent = v.Infer<typeof bestPlanSelectedEventJSONSchema>;\n\n/**\n * A node computed its cost estimate during planning.\n * Emitted by nodes during estimateCost() traversal.\n * attemptNumber is added by the debugger.\n */\ntype NodeCostEvent = {\n type: 'node-cost';\n attemptNumber?: number;\n nodeType:\n | 'connection'\n | 'join'\n | 'fan-out'\n | 'fan-in'\n | 'filter'\n | 'terminus';\n node: string;\n branchPattern: number[];\n downstreamChildSelectivity: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n filters?: Condition | undefined; // Only for connections\n ordering?: Ordering | undefined; // Only for connections\n joinType?: JoinType | undefined; // Only for joins\n};\n\n/**\n * A node received constraints during constraint propagation.\n * Emitted by nodes during propagateConstraints() traversal.\n * attemptNumber is added by the debugger.\n */\ntype NodeConstraintEvent = v.Infer<typeof nodeConstraintEventJSONSchema>;\n\n/**\n * Union of all debug event types.\n */\nexport type PlanDebugEvent =\n | AttemptStartEvent\n | ConnectionCostsEvent\n | ConnectionSelectedEvent\n | ConstraintsPropagatedEvent\n | PlanCompleteEvent\n | PlanFailedEvent\n | BestPlanSelectedEvent\n | NodeCostEvent\n | NodeConstraintEvent;\n\n/**\n * Interface for objects that receive debug events during planning.\n */\nexport interface PlanDebugger {\n log(event: PlanDebugEvent): void;\n}\n\n/**\n * Simple accumulator debugger that stores all events.\n * Useful for tests and debugging.\n */\nexport class AccumulatorDebugger implements PlanDebugger {\n readonly events: PlanDebugEvent[] = [];\n private currentAttempt = 0;\n\n log(event: PlanDebugEvent): void {\n // Track current attempt number\n if (event.type === 'attempt-start') {\n this.currentAttempt = event.attemptNumber;\n }\n\n // Add attempt number to node events\n if (event.type === 'node-cost' || event.type === 'node-constraint') {\n (event as NodeCostEvent | NodeConstraintEvent).attemptNumber =\n this.currentAttempt;\n }\n\n this.events.push(event);\n }\n\n /**\n * Get all events of a specific type.\n */\n getEvents<T extends PlanDebugEvent['type']>(\n type: T,\n ): Extract<PlanDebugEvent, {type: T}>[] {\n return this.events.filter(e => e.type === type) as Extract<\n PlanDebugEvent,\n {type: T}\n >[];\n }\n\n /**\n * Format events as a human-readable string.\n */\n format(): string {\n return formatPlannerEvents(this.events);\n }\n}\n\n/**\n * Format a constraint object as a human-readable string.\n */\nfunction formatConstraint(\n constraint: PlannerConstraint | Record<string, unknown> | null | undefined,\n): string {\n if (!constraint) return '{}';\n const keys = Object.keys(constraint);\n if (keys.length === 0) return '{}';\n return '{' + keys.join(', ') + '}';\n}\n\n/**\n * Format a ValuePosition (column, literal, or static parameter) as a human-readable string.\n */\nfunction formatValuePosition(value: ValuePosition): string {\n switch (value.type) {\n case 'column':\n return value.name;\n case 'literal':\n // Format literal values with SQL-style quoting for strings\n if (typeof value.value === 'string') {\n return `'${value.value}'`;\n }\n return JSON.stringify(value.value);\n case 'static':\n return `@${value.anchor}.${Array.isArray(value.field) ? value.field.join('.') : value.field}`;\n }\n}\n\n/**\n * Format a Condition (filter) as a human-readable string.\n */\nfunction formatFilter(filter: Condition | undefined): string {\n if (!filter) return 'none';\n\n switch (filter.type) {\n case 'simple':\n return `${formatValuePosition(filter.left)} ${filter.op} ${formatValuePosition(filter.right)}`;\n case 'and':\n return `(${filter.conditions.map(formatFilter).join(' AND ')})`;\n case 'or':\n return `(${filter.conditions.map(formatFilter).join(' OR ')})`;\n case 'correlatedSubquery':\n return `EXISTS(${filter.related.subquery.table})`;\n default:\n return JSON.stringify(filter);\n }\n}\n\n/**\n * Format an Ordering as a human-readable string.\n */\nfunction formatOrdering(ordering: Ordering | undefined): string {\n if (!ordering || ordering.length === 0) return 'none';\n return ordering\n .map(([field, direction]) => `${field} ${direction}`)\n .join(', ');\n}\n\n/**\n * Format a compact summary for a single planning attempt.\n */\nfunction formatAttemptSummary(\n attemptNum: number,\n events: (PlanDebugEvent | PlanDebugEventJSON)[],\n): string[] {\n const lines: string[] = [];\n\n // Find the attempt-start event to get total attempts\n const startEvent = events.find(e => e.type === 'attempt-start') as\n | AttemptStartEvent\n | undefined;\n const totalAttempts = startEvent?.totalAttempts ?? '?';\n\n // Calculate number of bits needed for pattern\n const numBits =\n typeof totalAttempts === 'number'\n ? Math.ceil(Math.log2(totalAttempts)) || 1\n : 1;\n const bitPattern = attemptNum.toString(2).padStart(numBits, '0');\n\n lines.push(\n `[Attempt ${attemptNum + 1}/${totalAttempts}] Pattern ${attemptNum} (${bitPattern})`,\n );\n\n // Collect connection costs (use array to preserve all connections, including duplicates)\n const connectionCostEvents: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n const connectionConstraintEvents: NodeConstraintEvent[] = [];\n\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'connection') {\n connectionCostEvents.push(event);\n }\n if (event.type === 'node-constraint' && event.nodeType === 'connection') {\n connectionConstraintEvents.push(event);\n }\n }\n\n // Show connection summary\n if (connectionCostEvents.length > 0) {\n lines.push(' Connections:');\n for (const cost of connectionCostEvents) {\n // Find matching constraint event (same node name and branch pattern)\n const constraint = connectionConstraintEvents.find(\n c =>\n c.node === cost.node &&\n c.branchPattern.join(',') === cost.branchPattern.join(','),\n )?.constraint;\n\n const constraintStr = formatConstraint(constraint);\n const filterStr = formatFilter(cost.filters);\n const orderingStr = formatOrdering(cost.ordering);\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n lines.push(` constraints=${constraintStr}`);\n lines.push(` filters=${filterStr}`);\n lines.push(` ordering=${orderingStr}`);\n }\n }\n\n // Collect join costs from node-cost events\n const joinCosts: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'join') {\n joinCosts.push(event);\n }\n }\n\n if (joinCosts.length > 0) {\n lines.push(' Joins:');\n for (const cost of joinCosts) {\n const typeStr = cost.joinType ? ` (${cost.joinType})` : '';\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}${typeStr}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n }\n }\n\n // Find completion/failure events\n const completeEvent = events.find(e => e.type === 'plan-complete') as\n | PlanCompleteEvent\n | undefined;\n const failedEvent = events.find(e => e.type === 'plan-failed') as\n | PlanFailedEvent\n | undefined;\n\n // Show final status\n\n if (completeEvent) {\n lines.push(\n ` ✓ Plan complete: total cost = ${completeEvent.totalCost.toFixed(2)}`,\n );\n } else if (failedEvent) {\n lines.push(` ✗ Plan failed: ${failedEvent.reason}`);\n }\n\n return lines;\n}\n\n/**\n * Convert undefined values to null in a constraint object for JSON serialization.\n * PlannerConstraint uses Record<string, undefined> which loses keys during JSON.stringify.\n */\nfunction convertConstraintUndefinedToNull(\n constraint: PlannerConstraint | Record<string, unknown> | undefined | null,\n): Record<string, unknown> | undefined | null {\n if (constraint === undefined) {\n return undefined;\n }\n if (constraint === null) {\n return null;\n }\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(constraint)) {\n result[key] = val === undefined ? null : val;\n }\n return result;\n}\n\n/**\n * Serialize a single debug event to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n * Undefined values in constraints are converted to null for JSON serialization.\n */\nfunction serializeEvent(event: PlanDebugEvent): PlanDebugEventJSON {\n // Remove planSnapshot from plan-complete events\n if (event.type === 'plan-complete') {\n const {planSnapshot: _, ...rest} = event;\n return rest as PlanDebugEventJSON;\n }\n\n // Convert constraint undefined values to null for specific event types\n if (event.type === 'node-constraint') {\n return {\n ...event,\n constraint: convertConstraintUndefinedToNull(event.constraint),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'connection-costs') {\n return {\n ...event,\n costs: event.costs.map(cost => ({\n ...cost,\n constraints: Object.fromEntries(\n Object.entries(cost.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'constraints-propagated') {\n return {\n ...event,\n connectionConstraints: event.connectionConstraints.map(cc => ({\n ...cc,\n constraints: Object.fromEntries(\n Object.entries(cc.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n return event as PlanDebugEventJSON;\n}\n\n/**\n * Serialize an array of debug events to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n */\nexport function serializePlanDebugEvents(\n events: PlanDebugEvent[],\n): PlanDebugEventJSON[] {\n return events.map(serializeEvent);\n}\n\n/**\n * Format planner debug events as a human-readable string.\n * Works with JSON-serialized events (from inspector API) or native events (from AccumulatorDebugger).\n *\n * @param events - Array of planner debug events (either JSON or native format)\n * @returns Formatted string showing planning attempts, costs, and final plan selection\n *\n * @example\n * ```typescript\n * const result = await inspector.analyzeQuery(query, { joinPlans: true });\n * if (result.joinPlans) {\n * console.log(formatPlannerEvents(result.joinPlans));\n * }\n * ```\n */\nexport function formatPlannerEvents(\n events: PlanDebugEventJSON[] | PlanDebugEvent[],\n): string {\n const lines: string[] = [];\n\n // Group events by attempt\n const eventsByAttempt = new Map<\n number,\n (PlanDebugEventJSON | PlanDebugEvent)[]\n >();\n let bestPlanEvent:\n | {\n type: 'best-plan-selected';\n bestAttemptNumber: number;\n totalCost: number;\n flipPattern: number;\n joinStates: Array<{join: string; type: string}>;\n }\n | undefined;\n\n for (const event of events) {\n if ('attemptNumber' in event) {\n const attempt = event.attemptNumber;\n if (attempt !== undefined) {\n getOrInsertComputed(eventsByAttempt, attempt, newArray).push(event);\n }\n } else if (event.type === 'best-plan-selected') {\n // Save for displaying at the end\n bestPlanEvent = event;\n }\n }\n\n // Format each attempt as a compact summary\n for (const [attemptNum, events] of eventsByAttempt.entries()) {\n lines.push(...formatAttemptSummary(attemptNum, events));\n lines.push(''); // Blank line between attempts\n }\n\n // Show the final plan selection\n if (bestPlanEvent) {\n lines.push('─'.repeat(60));\n lines.push(\n `✓ Best plan: Attempt ${bestPlanEvent.bestAttemptNumber + 1} (cost=${bestPlanEvent.totalCost.toFixed(2)})`,\n );\n if (bestPlanEvent.joinStates.length > 0) {\n lines.push(' Join types:');\n for (const j of bestPlanEvent.joinStates) {\n lines.push(` ${j.join}: ${j.type}`);\n }\n }\n lines.push('─'.repeat(60));\n }\n\n return lines.join('\\n');\n}\n"],"mappings":";;;;;;;AAqJA,IAAa,sBAAb,MAAyD;CACvD,SAAoC,CAAC;CACrC,iBAAyB;CAEzB,IAAI,OAA6B;EAE/B,IAAI,MAAM,SAAS,iBACjB,KAAK,iBAAiB,MAAM;EAI9B,IAAI,MAAM,SAAS,eAAe,MAAM,SAAS,mBAC/C,MAA+C,gBAC7C,KAAK;EAGT,KAAK,OAAO,KAAK,KAAK;CACxB;;;;CAKA,UACE,MACsC;EACtC,OAAO,KAAK,OAAO,QAAO,MAAK,EAAE,SAAS,IAAI;CAIhD;;;;CAKA,SAAiB;EACf,OAAO,oBAAoB,KAAK,MAAM;CACxC;AACF;;;;AAKA,SAAS,iBACP,YACQ;CACR,IAAI,CAAC,YAAY,OAAO;CACxB,MAAM,OAAO,OAAO,KAAK,UAAU;CACnC,IAAI,KAAK,WAAW,GAAG,OAAO;CAC9B,OAAO,MAAM,KAAK,KAAK,IAAI,IAAI;AACjC;;;;AAKA,SAAS,oBAAoB,OAA8B;CACzD,QAAQ,MAAM,MAAd;EACE,KAAK,UACH,OAAO,MAAM;EACf,KAAK;GAEH,IAAI,OAAO,MAAM,UAAU,UACzB,OAAO,IAAI,MAAM,MAAM;GAEzB,OAAO,KAAK,UAAU,MAAM,KAAK;EACnC,KAAK,UACH,OAAO,IAAI,MAAM,OAAO,GAAG,MAAM,QAAQ,MAAM,KAAK,IAAI,MAAM,MAAM,KAAK,GAAG,IAAI,MAAM;CAC1F;AACF;;;;AAKA,SAAS,aAAa,QAAuC;CAC3D,IAAI,CAAC,QAAQ,OAAO;CAEpB,QAAQ,OAAO,MAAf;EACE,KAAK,UACH,OAAO,GAAG,oBAAoB,OAAO,IAAI,EAAE,GAAG,OAAO,GAAG,GAAG,oBAAoB,OAAO,KAAK;EAC7F,KAAK,OACH,OAAO,IAAI,OAAO,WAAW,IAAI,YAAY,CAAC,CAAC,KAAK,OAAO,EAAE;EAC/D,KAAK,MACH,OAAO,IAAI,OAAO,WAAW,IAAI,YAAY,CAAC,CAAC,KAAK,MAAM,EAAE;EAC9D,KAAK,sBACH,OAAO,UAAU,OAAO,QAAQ,SAAS,MAAM;EACjD,SACE,OAAO,KAAK,UAAU,MAAM;CAChC;AACF;;;;AAKA,SAAS,eAAe,UAAwC;CAC9D,IAAI,CAAC,YAAY,SAAS,WAAW,GAAG,OAAO;CAC/C,OAAO,SACJ,KAAK,CAAC,OAAO,eAAe,GAAG,MAAM,GAAG,WAAW,CAAC,CACpD,KAAK,IAAI;AACd;;;;AAKA,SAAS,qBACP,YACA,QACU;CACV,MAAM,QAAkB,CAAC;CAMzB,MAAM,gBAHa,OAAO,MAAK,MAAK,EAAE,SAAS,eAGzB,CAAA,EAAY,iBAAiB;CAGnD,MAAM,UACJ,OAAO,kBAAkB,WACrB,KAAK,KAAK,KAAK,KAAK,aAAa,CAAC,KAAK,IACvC;CACN,MAAM,aAAa,WAAW,SAAS,CAAC,CAAC,CAAC,SAAS,SAAS,GAAG;CAE/D,MAAM,KACJ,YAAY,aAAa,EAAE,GAAG,cAAc,YAAY,WAAW,IAAI,WAAW,EACpF;CAGA,MAAM,uBAGA,CAAC;CACP,MAAM,6BAAoD,CAAC;CAE3D,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,MAAM,SAAS,eAAe,MAAM,aAAa,cACnD,qBAAqB,KAAK,KAAK;EAEjC,IAAI,MAAM,SAAS,qBAAqB,MAAM,aAAa,cACzD,2BAA2B,KAAK,KAAK;CAEzC;CAGA,IAAI,qBAAqB,SAAS,GAAG;EACnC,MAAM,KAAK,gBAAgB;EAC3B,KAAK,MAAM,QAAQ,sBAAsB;GAEvC,MAAM,aAAa,2BAA2B,MAC5C,MACE,EAAE,SAAS,KAAK,QAChB,EAAE,cAAc,KAAK,GAAG,MAAM,KAAK,cAAc,KAAK,GAAG,CAC7D,CAAC,EAAE;GAEH,MAAM,gBAAgB,iBAAiB,UAAU;GACjD,MAAM,YAAY,aAAa,KAAK,OAAO;GAC3C,MAAM,cAAc,eAAe,KAAK,QAAQ;GAChD,MAAM,WACJ,KAAK,aAAa,UAAU,KAAA,IACxB,KAAK,aAAa,MAAM,SAAS,IACjC;GAEN,MAAM,KAAK,OAAO,KAAK,KAAK,EAAE;GAC9B,MAAM,KACJ,cAAc,KAAK,aAAa,KAAK,QAAQ,CAAC,EAAE,YAAY,KAAK,aAAa,YAAY,QAAQ,CAAC,EAAE,SAAS,KAAK,aAAa,QAAQ,QAAQ,CAAC,GACnJ;GACA,MAAM,KACJ,cAAc,KAAK,aAAa,aAAa,QAAQ,CAAC,EAAE,gBAAgB,KAAK,aAAa,YAAY,QAAQ,CAAC,EAAE,UAAU,UAC7H;GACA,MAAM,KACJ,oCAAoC,KAAK,2BAA2B,QAAQ,CAAC,GAC/E;GACA,MAAM,KAAK,qBAAqB,eAAe;GAC/C,MAAM,KAAK,iBAAiB,WAAW;GACvC,MAAM,KAAK,kBAAkB,aAAa;EAC5C;CACF;CAGA,MAAM,YAGA,CAAC;CACP,KAAK,MAAM,SAAS,QAClB,IAAI,MAAM,SAAS,eAAe,MAAM,aAAa,QACnD,UAAU,KAAK,KAAK;CAIxB,IAAI,UAAU,SAAS,GAAG;EACxB,MAAM,KAAK,UAAU;EACrB,KAAK,MAAM,QAAQ,WAAW;GAC5B,MAAM,UAAU,KAAK,WAAW,KAAK,KAAK,SAAS,KAAK;GACxD,MAAM,WACJ,KAAK,aAAa,UAAU,KAAA,IACxB,KAAK,aAAa,MAAM,SAAS,IACjC;GAEN,MAAM,KAAK,OAAO,KAAK,OAAO,QAAQ,EAAE;GACxC,MAAM,KACJ,cAAc,KAAK,aAAa,KAAK,QAAQ,CAAC,EAAE,YAAY,KAAK,aAAa,YAAY,QAAQ,CAAC,EAAE,SAAS,KAAK,aAAa,QAAQ,QAAQ,CAAC,GACnJ;GACA,MAAM,KACJ,cAAc,KAAK,aAAa,aAAa,QAAQ,CAAC,EAAE,gBAAgB,KAAK,aAAa,YAAY,QAAQ,CAAC,EAAE,UAAU,UAC7H;GACA,MAAM,KACJ,oCAAoC,KAAK,2BAA2B,QAAQ,CAAC,GAC/E;EACF;CACF;CAGA,MAAM,gBAAgB,OAAO,MAAK,MAAK,EAAE,SAAS,eAAe;CAGjE,MAAM,cAAc,OAAO,MAAK,MAAK,EAAE,SAAS,aAAa;CAM7D,IAAI,eACF,MAAM,KACJ,mCAAmC,cAAc,UAAU,QAAQ,CAAC,GACtE;MACK,IAAI,aACT,MAAM,KAAK,oBAAoB,YAAY,QAAQ;CAGrD,OAAO;AACT;;;;;AAMA,SAAS,iCACP,YAC4C;CAC5C,IAAI,eAAe,KAAA,GACjB;CAEF,IAAI,eAAe,MACjB,OAAO;CAET,MAAM,SAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,UAAU,GAChD,OAAO,OAAO,QAAQ,KAAA,IAAY,OAAO;CAE3C,OAAO;AACT;;;;;;;AAQA,SAAS,eAAe,OAA2C;CAEjE,IAAI,MAAM,SAAS,iBAAiB;EAClC,MAAM,EAAC,cAAc,GAAG,GAAG,SAAQ;EACnC,OAAO;CACT;CAGA,IAAI,MAAM,SAAS,mBACjB,OAAO;EACL,GAAG;EACH,YAAY,iCAAiC,MAAM,UAAU;CAC/D;CAGF,IAAI,MAAM,SAAS,oBACjB,OAAO;EACL,GAAG;EACH,OAAO,MAAM,MAAM,KAAI,UAAS;GAC9B,GAAG;GACH,aAAa,OAAO,YAClB,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,CACnD,KACA,iCAAiC,GAAG,CACtC,CAAC,CACH;EACF,EAAE;CACJ;CAGF,IAAI,MAAM,SAAS,0BACjB,OAAO;EACL,GAAG;EACH,uBAAuB,MAAM,sBAAsB,KAAI,QAAO;GAC5D,GAAG;GACH,aAAa,OAAO,YAClB,OAAO,QAAQ,GAAG,WAAW,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,CACjD,KACA,iCAAiC,GAAG,CACtC,CAAC,CACH;EACF,EAAE;CACJ;CAGF,OAAO;AACT;;;;;;AAOA,SAAgB,yBACd,QACsB;CACtB,OAAO,OAAO,IAAI,cAAc;AAClC;;;;;;;;;;;;;;;;AAiBA,SAAgB,oBACd,QACQ;CACR,MAAM,QAAkB,CAAC;CAGzB,MAAM,kCAAkB,IAAI,IAG1B;CACF,IAAI;CAUJ,KAAK,MAAM,SAAS,QAClB,IAAI,mBAAmB,OAAO;EAC5B,MAAM,UAAU,MAAM;EACtB,IAAI,YAAY,KAAA,GACd,oBAAoB,iBAAiB,SAAS,QAAQ,CAAC,CAAC,KAAK,KAAK;CAEtE,OAAO,IAAI,MAAM,SAAS,sBAExB,gBAAgB;CAKpB,KAAK,MAAM,CAAC,YAAY,WAAW,gBAAgB,QAAQ,GAAG;EAC5D,MAAM,KAAK,GAAG,qBAAqB,YAAY,MAAM,CAAC;EACtD,MAAM,KAAK,EAAE;CACf;CAGA,IAAI,eAAe;EACjB,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;EACzB,MAAM,KACJ,wBAAwB,cAAc,oBAAoB,EAAE,SAAS,cAAc,UAAU,QAAQ,CAAC,EAAE,EAC1G;EACA,IAAI,cAAc,WAAW,SAAS,GAAG;GACvC,MAAM,KAAK,eAAe;GAC1B,KAAK,MAAM,KAAK,cAAc,YAC5B,MAAM,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM;EAEzC;EACA,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;CAC3B;CAEA,OAAO,MAAM,KAAK,IAAI;AACxB"}
|
|
1
|
+
{"version":3,"file":"planner-debug.js","names":[],"sources":["../../../../../zql/src/planner/planner-debug.ts"],"sourcesContent":["import {newArray} from '../../../shared/src/arrays.ts';\nimport {unreachable} from '../../../shared/src/asserts.ts';\nimport {getOrInsertComputed} from '../../../shared/src/map.ts';\nimport type * as v from '../../../shared/src/valita.ts';\nimport type {\n attemptStartEventJSONSchema,\n bestPlanSelectedEventJSONSchema,\n connectionSelectedEventJSONSchema,\n nodeConstraintEventJSONSchema,\n PlanDebugEventJSON,\n planFailedEventJSONSchema,\n} from '../../../zero-protocol/src/analyze-query-result.ts';\nimport {\n formatJsonPathReference,\n type Condition,\n type Ordering,\n type ValuePosition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {PlanState} from './planner-graph.ts';\nimport type {CostEstimate, JoinType} from './planner-node.ts';\n\n/**\n * Structured debug events emitted during query planning.\n * These events can be accumulated, printed, or analyzed to understand\n * the planner's decision-making process.\n */\n\n/**\n * Starting a new planning attempt with a different root connection.\n */\ntype AttemptStartEvent = v.Infer<typeof attemptStartEventJSONSchema>;\n\n/**\n * Snapshot of connection costs before selecting the next connection.\n */\ntype ConnectionCostsEvent = {\n type: 'connection-costs';\n attemptNumber: number;\n costs: Array<{\n connection: string;\n cost: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n pinned: boolean;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A connection was chosen and pinned.\n */\ntype ConnectionSelectedEvent = v.Infer<\n typeof connectionSelectedEventJSONSchema\n>;\n\n/**\n * Constraints have been propagated through the graph.\n */\ntype ConstraintsPropagatedEvent = {\n type: 'constraints-propagated';\n attemptNumber: number;\n connectionConstraints: Array<{\n connection: string;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A complete plan was found for this attempt.\n */\ntype PlanCompleteEvent = {\n type: 'plan-complete';\n attemptNumber: number;\n totalCost: number;\n flipPattern: number; // Bitmask indicating which joins are flipped\n joinStates: Array<{\n join: string;\n type: JoinType;\n }>;\n // Planning snapshot that can be restored and applied to AST\n planSnapshot: PlanState;\n};\n\n/**\n * Planning attempt failed (e.g., unflippable join).\n */\ntype PlanFailedEvent = v.Infer<typeof planFailedEventJSONSchema>;\n\n/**\n * The best plan across all attempts was selected.\n */\ntype BestPlanSelectedEvent = v.Infer<typeof bestPlanSelectedEventJSONSchema>;\n\n/**\n * A node computed its cost estimate during planning.\n * Emitted by nodes during estimateCost() traversal.\n * attemptNumber is added by the debugger.\n */\ntype NodeCostEvent = {\n type: 'node-cost';\n attemptNumber?: number;\n nodeType:\n | 'connection'\n | 'join'\n | 'fan-out'\n | 'fan-in'\n | 'filter'\n | 'terminus';\n node: string;\n branchPattern: number[];\n downstreamChildSelectivity: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n filters?: Condition | undefined; // Only for connections\n ordering?: Ordering | undefined; // Only for connections\n joinType?: JoinType | undefined; // Only for joins\n};\n\n/**\n * A node received constraints during constraint propagation.\n * Emitted by nodes during propagateConstraints() traversal.\n * attemptNumber is added by the debugger.\n */\ntype NodeConstraintEvent = v.Infer<typeof nodeConstraintEventJSONSchema>;\n\n/**\n * Union of all debug event types.\n */\nexport type PlanDebugEvent =\n | AttemptStartEvent\n | ConnectionCostsEvent\n | ConnectionSelectedEvent\n | ConstraintsPropagatedEvent\n | PlanCompleteEvent\n | PlanFailedEvent\n | BestPlanSelectedEvent\n | NodeCostEvent\n | NodeConstraintEvent;\n\n/**\n * Interface for objects that receive debug events during planning.\n */\nexport interface PlanDebugger {\n log(event: PlanDebugEvent): void;\n}\n\n/**\n * Simple accumulator debugger that stores all events.\n * Useful for tests and debugging.\n */\nexport class AccumulatorDebugger implements PlanDebugger {\n readonly events: PlanDebugEvent[] = [];\n private currentAttempt = 0;\n\n log(event: PlanDebugEvent): void {\n // Track current attempt number\n if (event.type === 'attempt-start') {\n this.currentAttempt = event.attemptNumber;\n }\n\n // Add attempt number to node events\n if (event.type === 'node-cost' || event.type === 'node-constraint') {\n (event as NodeCostEvent | NodeConstraintEvent).attemptNumber =\n this.currentAttempt;\n }\n\n this.events.push(event);\n }\n\n /**\n * Get all events of a specific type.\n */\n getEvents<T extends PlanDebugEvent['type']>(\n type: T,\n ): Extract<PlanDebugEvent, {type: T}>[] {\n return this.events.filter(e => e.type === type) as Extract<\n PlanDebugEvent,\n {type: T}\n >[];\n }\n\n /**\n * Format events as a human-readable string.\n */\n format(): string {\n return formatPlannerEvents(this.events);\n }\n}\n\n/**\n * Format a constraint object as a human-readable string.\n */\nfunction formatConstraint(\n constraint: PlannerConstraint | Record<string, unknown> | null | undefined,\n): string {\n if (!constraint) return '{}';\n const keys = Object.keys(constraint);\n if (keys.length === 0) return '{}';\n return '{' + keys.join(', ') + '}';\n}\n\n/**\n * Format a ValuePosition (column, literal, or static parameter) as a human-readable string.\n */\nfunction formatValuePosition(value: ValuePosition): string {\n switch (value.type) {\n case 'column':\n return value.name;\n case 'json':\n return formatJsonPathReference(value);\n case 'literal':\n // Format literal values with SQL-style quoting for strings\n if (typeof value.value === 'string') {\n return `'${value.value}'`;\n }\n return JSON.stringify(value.value);\n case 'static':\n return `@${value.anchor}.${Array.isArray(value.field) ? value.field.join('.') : value.field}`;\n default:\n unreachable(value);\n }\n}\n\n/**\n * Format a Condition (filter) as a human-readable string.\n */\nfunction formatFilter(filter: Condition | undefined): string {\n if (!filter) return 'none';\n\n switch (filter.type) {\n case 'simple':\n return `${formatValuePosition(filter.left)} ${filter.op} ${formatValuePosition(filter.right)}`;\n case 'and':\n return `(${filter.conditions.map(formatFilter).join(' AND ')})`;\n case 'or':\n return `(${filter.conditions.map(formatFilter).join(' OR ')})`;\n case 'correlatedSubquery':\n return `EXISTS(${filter.related.subquery.table})`;\n default:\n return JSON.stringify(filter);\n }\n}\n\n/**\n * Format an Ordering as a human-readable string.\n */\nfunction formatOrdering(ordering: Ordering | undefined): string {\n if (!ordering || ordering.length === 0) return 'none';\n return ordering\n .map(([field, direction]) => `${field} ${direction}`)\n .join(', ');\n}\n\n/**\n * Format a compact summary for a single planning attempt.\n */\nfunction formatAttemptSummary(\n attemptNum: number,\n events: (PlanDebugEvent | PlanDebugEventJSON)[],\n): string[] {\n const lines: string[] = [];\n\n // Find the attempt-start event to get total attempts\n const startEvent = events.find(e => e.type === 'attempt-start') as\n | AttemptStartEvent\n | undefined;\n const totalAttempts = startEvent?.totalAttempts ?? '?';\n\n // Calculate number of bits needed for pattern\n const numBits =\n typeof totalAttempts === 'number'\n ? Math.ceil(Math.log2(totalAttempts)) || 1\n : 1;\n const bitPattern = attemptNum.toString(2).padStart(numBits, '0');\n\n lines.push(\n `[Attempt ${attemptNum + 1}/${totalAttempts}] Pattern ${attemptNum} (${bitPattern})`,\n );\n\n // Collect connection costs (use array to preserve all connections, including duplicates)\n const connectionCostEvents: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n const connectionConstraintEvents: NodeConstraintEvent[] = [];\n\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'connection') {\n connectionCostEvents.push(event);\n }\n if (event.type === 'node-constraint' && event.nodeType === 'connection') {\n connectionConstraintEvents.push(event);\n }\n }\n\n // Show connection summary\n if (connectionCostEvents.length > 0) {\n lines.push(' Connections:');\n for (const cost of connectionCostEvents) {\n // Find matching constraint event (same node name and branch pattern)\n const constraint = connectionConstraintEvents.find(\n c =>\n c.node === cost.node &&\n c.branchPattern.join(',') === cost.branchPattern.join(','),\n )?.constraint;\n\n const constraintStr = formatConstraint(constraint);\n const filterStr = formatFilter(cost.filters);\n const orderingStr = formatOrdering(cost.ordering);\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n lines.push(` constraints=${constraintStr}`);\n lines.push(` filters=${filterStr}`);\n lines.push(` ordering=${orderingStr}`);\n }\n }\n\n // Collect join costs from node-cost events\n const joinCosts: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'join') {\n joinCosts.push(event);\n }\n }\n\n if (joinCosts.length > 0) {\n lines.push(' Joins:');\n for (const cost of joinCosts) {\n const typeStr = cost.joinType ? ` (${cost.joinType})` : '';\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}${typeStr}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n }\n }\n\n // Find completion/failure events\n const completeEvent = events.find(e => e.type === 'plan-complete') as\n | PlanCompleteEvent\n | undefined;\n const failedEvent = events.find(e => e.type === 'plan-failed') as\n | PlanFailedEvent\n | undefined;\n\n // Show final status\n\n if (completeEvent) {\n lines.push(\n ` ✓ Plan complete: total cost = ${completeEvent.totalCost.toFixed(2)}`,\n );\n } else if (failedEvent) {\n lines.push(` ✗ Plan failed: ${failedEvent.reason}`);\n }\n\n return lines;\n}\n\n/**\n * Convert undefined values to null in a constraint object for JSON serialization.\n * PlannerConstraint uses Record<string, undefined> which loses keys during JSON.stringify.\n */\nfunction convertConstraintUndefinedToNull(\n constraint: PlannerConstraint | Record<string, unknown> | undefined | null,\n): Record<string, unknown> | undefined | null {\n if (constraint === undefined) {\n return undefined;\n }\n if (constraint === null) {\n return null;\n }\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(constraint)) {\n result[key] = val === undefined ? null : val;\n }\n return result;\n}\n\n/**\n * Serialize a single debug event to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n * Undefined values in constraints are converted to null for JSON serialization.\n */\nfunction serializeEvent(event: PlanDebugEvent): PlanDebugEventJSON {\n // Remove planSnapshot from plan-complete events\n if (event.type === 'plan-complete') {\n const {planSnapshot: _, ...rest} = event;\n return rest as PlanDebugEventJSON;\n }\n\n // Convert constraint undefined values to null for specific event types\n if (event.type === 'node-constraint') {\n return {\n ...event,\n constraint: convertConstraintUndefinedToNull(event.constraint),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'connection-costs') {\n return {\n ...event,\n costs: event.costs.map(cost => ({\n ...cost,\n constraints: Object.fromEntries(\n Object.entries(cost.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'constraints-propagated') {\n return {\n ...event,\n connectionConstraints: event.connectionConstraints.map(cc => ({\n ...cc,\n constraints: Object.fromEntries(\n Object.entries(cc.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n return event as PlanDebugEventJSON;\n}\n\n/**\n * Serialize an array of debug events to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n */\nexport function serializePlanDebugEvents(\n events: PlanDebugEvent[],\n): PlanDebugEventJSON[] {\n return events.map(serializeEvent);\n}\n\n/**\n * Format planner debug events as a human-readable string.\n * Works with JSON-serialized events (from inspector API) or native events (from AccumulatorDebugger).\n *\n * @param events - Array of planner debug events (either JSON or native format)\n * @returns Formatted string showing planning attempts, costs, and final plan selection\n *\n * @example\n * ```typescript\n * const result = await inspector.analyzeQuery(query, { joinPlans: true });\n * if (result.joinPlans) {\n * console.log(formatPlannerEvents(result.joinPlans));\n * }\n * ```\n */\nexport function formatPlannerEvents(\n events: PlanDebugEventJSON[] | PlanDebugEvent[],\n): string {\n const lines: string[] = [];\n\n // Group events by attempt\n const eventsByAttempt = new Map<\n number,\n (PlanDebugEventJSON | PlanDebugEvent)[]\n >();\n let bestPlanEvent:\n | {\n type: 'best-plan-selected';\n bestAttemptNumber: number;\n totalCost: number;\n flipPattern: number;\n joinStates: Array<{join: string; type: string}>;\n }\n | undefined;\n\n for (const event of events) {\n if ('attemptNumber' in event) {\n const attempt = event.attemptNumber;\n if (attempt !== undefined) {\n getOrInsertComputed(eventsByAttempt, attempt, newArray).push(event);\n }\n } else if (event.type === 'best-plan-selected') {\n // Save for displaying at the end\n bestPlanEvent = event;\n }\n }\n\n // Format each attempt as a compact summary\n for (const [attemptNum, events] of eventsByAttempt.entries()) {\n lines.push(...formatAttemptSummary(attemptNum, events));\n lines.push(''); // Blank line between attempts\n }\n\n // Show the final plan selection\n if (bestPlanEvent) {\n lines.push('─'.repeat(60));\n lines.push(\n `✓ Best plan: Attempt ${bestPlanEvent.bestAttemptNumber + 1} (cost=${bestPlanEvent.totalCost.toFixed(2)})`,\n );\n if (bestPlanEvent.joinStates.length > 0) {\n lines.push(' Join types:');\n for (const j of bestPlanEvent.joinStates) {\n lines.push(` ${j.join}: ${j.type}`);\n }\n }\n lines.push('─'.repeat(60));\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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"}
|