@kanzo-tech/graph 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +94 -0
  2. package/dist/adaptive.d.ts +27 -0
  3. package/dist/adaptive.d.ts.map +1 -0
  4. package/dist/adaptive.js +25 -0
  5. package/dist/adaptive.js.map +1 -0
  6. package/dist/bounded.d.ts +314 -0
  7. package/dist/bounded.d.ts.map +1 -0
  8. package/dist/bounded.js +39 -0
  9. package/dist/bounded.js.map +1 -0
  10. package/dist/cluster-ring.d.ts +25 -0
  11. package/dist/cluster-ring.d.ts.map +1 -0
  12. package/dist/cluster-ring.js +16 -0
  13. package/dist/cluster-ring.js.map +1 -0
  14. package/dist/css-color.d.ts +11 -0
  15. package/dist/css-color.d.ts.map +1 -0
  16. package/dist/css-color.js +23 -0
  17. package/dist/css-color.js.map +1 -0
  18. package/dist/duck-source.d.ts +182 -0
  19. package/dist/duck-source.d.ts.map +1 -0
  20. package/dist/duck-source.js +413 -0
  21. package/dist/duck-source.js.map +1 -0
  22. package/dist/graph-canvas.d.ts +50 -0
  23. package/dist/graph-canvas.d.ts.map +1 -0
  24. package/dist/graph-canvas.js +35 -0
  25. package/dist/graph-canvas.js.map +1 -0
  26. package/dist/graph-looks.d.ts +146 -0
  27. package/dist/graph-looks.d.ts.map +1 -0
  28. package/dist/graph-looks.js +44 -0
  29. package/dist/graph-looks.js.map +1 -0
  30. package/dist/graph-model.d.ts +130 -0
  31. package/dist/graph-model.d.ts.map +1 -0
  32. package/dist/graph-model.js +99 -0
  33. package/dist/graph-model.js.map +1 -0
  34. package/dist/graph-sim.d.ts +40 -0
  35. package/dist/graph-sim.d.ts.map +1 -0
  36. package/dist/graph-sim.js +20 -0
  37. package/dist/graph-sim.js.map +1 -0
  38. package/dist/index.d.ts +72 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +53 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/memory-source.d.ts +32 -0
  43. package/dist/memory-source.d.ts.map +1 -0
  44. package/dist/memory-source.js +134 -0
  45. package/dist/memory-source.js.map +1 -0
  46. package/dist/obligations.d.ts +62 -0
  47. package/dist/obligations.d.ts.map +1 -0
  48. package/dist/resident.d.ts +79 -0
  49. package/dist/resident.d.ts.map +1 -0
  50. package/dist/resident.js +44 -0
  51. package/dist/resident.js.map +1 -0
  52. package/dist/section.d.ts +82 -0
  53. package/dist/section.d.ts.map +1 -0
  54. package/dist/section.js +142 -0
  55. package/dist/section.js.map +1 -0
  56. package/dist/slice-client.d.ts +78 -0
  57. package/dist/slice-client.d.ts.map +1 -0
  58. package/dist/slice-client.js +98 -0
  59. package/dist/slice-client.js.map +1 -0
  60. package/dist/types.d.ts +57 -0
  61. package/dist/types.d.ts.map +1 -0
  62. package/dist/use-graph-look.d.ts +28 -0
  63. package/dist/use-graph-look.d.ts.map +1 -0
  64. package/dist/use-graph-look.js +28 -0
  65. package/dist/use-graph-look.js.map +1 -0
  66. package/dist/use-graph-overlays.d.ts +53 -0
  67. package/dist/use-graph-overlays.d.ts.map +1 -0
  68. package/dist/use-graph-overlays.js +96 -0
  69. package/dist/use-graph-overlays.js.map +1 -0
  70. package/dist/use-graph-selection.d.ts +59 -0
  71. package/dist/use-graph-selection.d.ts.map +1 -0
  72. package/dist/use-graph-selection.js +101 -0
  73. package/dist/use-graph-selection.js.map +1 -0
  74. package/dist/use-graph.d.ts +177 -0
  75. package/dist/use-graph.d.ts.map +1 -0
  76. package/dist/use-graph.js +101 -0
  77. package/dist/use-graph.js.map +1 -0
  78. package/dist/use-query-loop.d.ts +84 -0
  79. package/dist/use-query-loop.d.ts.map +1 -0
  80. package/dist/use-query-loop.js +151 -0
  81. package/dist/use-query-loop.js.map +1 -0
  82. package/dist/use-renderer.d.ts +86 -0
  83. package/dist/use-renderer.d.ts.map +1 -0
  84. package/dist/use-renderer.js +185 -0
  85. package/dist/use-renderer.js.map +1 -0
  86. package/dist/when-ready.d.ts +35 -0
  87. package/dist/when-ready.d.ts.map +1 -0
  88. package/dist/when-ready.js +17 -0
  89. package/dist/when-ready.js.map +1 -0
  90. package/package.json +69 -0
@@ -0,0 +1,79 @@
1
+ import { Slice } from './bounded';
2
+ /**
3
+ * Who is drawn, and where the GPU is drawing them.
4
+ *
5
+ * **A buffer index numbers the answer, not the corpus.** cosmos.gl addresses every point by its
6
+ * position in the arrays it was last handed, so index 7 is whatever the current answer put seventh —
7
+ * and a resident set that comes and goes reuses every index while the vertices behind them change.
8
+ * Anything that outlives one answer — a selection, a label, a hover, a pin — therefore has to be
9
+ * held as an identity and re-resolved against whatever is drawn now.
10
+ *
11
+ * **A vertex is the pair `(type_idx, dense_id)`.** Not `dense_id` alone: it numbers
12
+ * within one vertex type, so a union of two types repeats every value and the same number names two
13
+ * different vertices. A `LIMIT` breaks the correspondence between an id and a position regardless of
14
+ * how many types there are.
15
+ */
16
+ /**
17
+ * A vertex identity — the pair, packed into one 64-bit integer so it can key a `Map` and a `Set`.
18
+ *
19
+ * **A `bigint`, which is strictly stronger than the brand it also wears.** A buffer index is a
20
+ * `number`; an identity is a `bigint`. Confusing the two therefore stops being a branding
21
+ * convention that holds while everyone remembers it and becomes a *primitive* type error — and no
22
+ * cast quietly launders one: `7 as VertexId` was legal against `number & brand` and is a compile
23
+ * error against `bigint & brand`. Getting one wrong now costs `as unknown as`, which is loud.
24
+ *
25
+ * **The evidence this is worth the friction, because it is not a precaution we invented.** Across
26
+ * every multi-language format surveyed — Arrow, Parquet, Iceberg, Delta, MVT, PMTiles, Zarr,
27
+ * GraphAr, H3, S2 — the failure that recurs is a 64-bit value crossing into JavaScript.
28
+ * `mapbox/node-s2` is a **binding**, not a port: it calls the same C++ the reference implementation
29
+ * does, and it still returned wrong cell ids — issue #92, open since 2017, `1152921504606847000`
30
+ * where Java and Go both give `1152921504606846977`. JavaScript's `number` is 53 bits and a cell id
31
+ * is 64, and a binding cannot protect a language boundary that cannot hold the value. H3 settled it
32
+ * by decree before anyone could get it wrong: `h3-js` types `H3Index` as a string. This is the same
33
+ * decree with the type JavaScript grew for it. See rmlext ADR-0045.
34
+ *
35
+ * **And `>>` means three different things across our three layers.** In JavaScript it converts its
36
+ * operand to *32 bits* and takes the shift count modulo 32, so the obvious `dense | (type << 32)`
37
+ * is not a lost high word — it is `type | dense`, silently. The packing below cannot be written on
38
+ * `number` at all and be right.
39
+ *
40
+ * What goes to the GPU does not change: positions and buffer indices stay `number` and
41
+ * `Float32Array`. The conversion happens at the edge, which is where it belongs.
42
+ */
43
+ export type VertexId = bigint & {
44
+ readonly vertex: unique symbol;
45
+ };
46
+ export declare function vertexId(type: number, dense: number): VertexId;
47
+ /** Both halves come back as `number`: each is 32 bits, and a `number` holds those exactly. */
48
+ export declare function typeOf(vertex: VertexId): number;
49
+ export declare function denseOf(vertex: VertexId): number;
50
+ /**
51
+ * The identity↔index map for whatever is drawn right now.
52
+ *
53
+ * Built once per residency change and never mutated, because that is what a residency change *is*: a
54
+ * different set of vertices, in a different order. It belongs to whoever owns residency — that is
55
+ * `useQueryLoop`, which holds the answer — and is read from there by everything else, because a
56
+ * second copy is a second copy that can disagree with the buffers on screen.
57
+ */
58
+ export interface Resident {
59
+ /** How many points are drawn. */
60
+ readonly size: number;
61
+ /** Where a vertex is drawn, or `undefined` when it is not resident. */
62
+ indexOf(vertex: VertexId): number | undefined;
63
+ /** Which vertex is drawn at a buffer index, or `undefined` past the end of the answer. */
64
+ at(index: number): VertexId | undefined;
65
+ /** Where these vertices are drawn, skipping every one that is not resident. */
66
+ indicesOf(vertices: Iterable<VertexId>): number[];
67
+ /** Which vertices are drawn at these buffer indices, skipping any the answer does not hold. */
68
+ verticesAt(indices: Iterable<number>): VertexId[];
69
+ }
70
+ /**
71
+ * The map an answer implies. `null` — before the first answer — is nobody resident, not an error.
72
+ *
73
+ * Keyed by the identity itself. `Map` and `Set` compare keys by SameValueZero, which for a `bigint`
74
+ * is *value* equality and not reference equality — two separately-constructed `vertexId(0, 5)`
75
+ * reach the same entry. That is asserted rather than assumed in `resident.test.ts`, because the
76
+ * whole file rests on it and BigInt being an object-shaped primitive makes it a fair thing to doubt.
77
+ */
78
+ export declare function residentOf(slice: Slice | null): Resident;
79
+ //# sourceMappingURL=resident.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resident.d.ts","sourceRoot":"","sources":["../src/resident.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAEvC;;;;;;;;;;;;;GAaG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,MAAM,CAAA;CAAE,CAAC;AAYnE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,CAE9D;AAED,8FAA8F;AAC9F,wBAAgB,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAE/C;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAEhD;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ;IACvB,iCAAiC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,OAAO,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;IAC9C,0FAA0F;IAC1F,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;IACxC,+EAA+E;IAC/E,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,CAAC;IAClD,+FAA+F;IAC/F,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,QAAQ,EAAE,CAAC;CACnD;AAID;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,GAAG,QAAQ,CAwCxD"}
@@ -0,0 +1,44 @@
1
+ const v = 32n, x = 0xffffffffn;
2
+ function a(t, o) {
3
+ return BigInt(t) << v | BigInt(o);
4
+ }
5
+ function g(t) {
6
+ return Number(t >> v);
7
+ }
8
+ function h(t) {
9
+ return Number(t & x);
10
+ }
11
+ const O = new BigUint64Array(0);
12
+ function p(t) {
13
+ const o = (t == null ? void 0 : t.vertices) ?? O, f = Math.min((t == null ? void 0 : t.marks) ?? o.length, o.length), u = o, i = /* @__PURE__ */ new Map();
14
+ for (let n = 0; n < f; n++) i.set(u[n], n);
15
+ const c = (n) => n >= 0 && n < f ? u[n] : void 0, d = (n) => i.get(n);
16
+ return {
17
+ size: f,
18
+ indexOf: d,
19
+ at: c,
20
+ indicesOf(n) {
21
+ const e = [];
22
+ for (const s of n) {
23
+ const r = d(s);
24
+ r !== void 0 && e.push(r);
25
+ }
26
+ return e;
27
+ },
28
+ verticesAt(n) {
29
+ const e = [];
30
+ for (const s of n) {
31
+ const r = c(s);
32
+ r !== void 0 && e.push(r);
33
+ }
34
+ return e;
35
+ }
36
+ };
37
+ }
38
+ export {
39
+ h as denseOf,
40
+ p as residentOf,
41
+ g as typeOf,
42
+ a as vertexId
43
+ };
44
+ //# sourceMappingURL=resident.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resident.js","sources":["../src/resident.ts"],"sourcesContent":["import type { Slice } from \"./bounded\";\n\n/**\n * Who is drawn, and where the GPU is drawing them.\n *\n * **A buffer index numbers the answer, not the corpus.** cosmos.gl addresses every point by its\n * position in the arrays it was last handed, so index 7 is whatever the current answer put seventh —\n * and a resident set that comes and goes reuses every index while the vertices behind them change.\n * Anything that outlives one answer — a selection, a label, a hover, a pin — therefore has to be\n * held as an identity and re-resolved against whatever is drawn now.\n *\n * **A vertex is the pair `(type_idx, dense_id)`.** Not `dense_id` alone: it numbers\n * within one vertex type, so a union of two types repeats every value and the same number names two\n * different vertices. A `LIMIT` breaks the correspondence between an id and a position regardless of\n * how many types there are.\n */\n\n/**\n * A vertex identity — the pair, packed into one 64-bit integer so it can key a `Map` and a `Set`.\n *\n * **A `bigint`, which is strictly stronger than the brand it also wears.** A buffer index is a\n * `number`; an identity is a `bigint`. Confusing the two therefore stops being a branding\n * convention that holds while everyone remembers it and becomes a *primitive* type error — and no\n * cast quietly launders one: `7 as VertexId` was legal against `number & brand` and is a compile\n * error against `bigint & brand`. Getting one wrong now costs `as unknown as`, which is loud.\n *\n * **The evidence this is worth the friction, because it is not a precaution we invented.** Across\n * every multi-language format surveyed — Arrow, Parquet, Iceberg, Delta, MVT, PMTiles, Zarr,\n * GraphAr, H3, S2 — the failure that recurs is a 64-bit value crossing into JavaScript.\n * `mapbox/node-s2` is a **binding**, not a port: it calls the same C++ the reference implementation\n * does, and it still returned wrong cell ids — issue #92, open since 2017, `1152921504606847000`\n * where Java and Go both give `1152921504606846977`. JavaScript's `number` is 53 bits and a cell id\n * is 64, and a binding cannot protect a language boundary that cannot hold the value. H3 settled it\n * by decree before anyone could get it wrong: `h3-js` types `H3Index` as a string. This is the same\n * decree with the type JavaScript grew for it. See rmlext ADR-0045.\n *\n * **And `>>` means three different things across our three layers.** In JavaScript it converts its\n * operand to *32 bits* and takes the shift count modulo 32, so the obvious `dense | (type << 32)`\n * is not a lost high word — it is `type | dense`, silently. The packing below cannot be written on\n * `number` at all and be right.\n *\n * What goes to the GPU does not change: positions and buffer indices stay `number` and\n * `Float32Array`. The conversion happens at the edge, which is where it belongs.\n */\nexport type VertexId = bigint & { readonly vertex: unique symbol };\n\n/**\n * One type's worth of dense ids.\n *\n * `dense_id` is a `UInt32`, so the type index occupies everything above bit 32 — exactly, for all\n * 2³² of them, which is the whole point of the width. The old packing was `type * 2**32 + dense` in\n * `float64` and ran out of exactness at type index 2²¹.\n */\nconst TYPE_SHIFT = 32n;\nconst DENSE_MASK = 0xffff_ffffn;\n\nexport function vertexId(type: number, dense: number): VertexId {\n return ((BigInt(type) << TYPE_SHIFT) | BigInt(dense)) as VertexId;\n}\n\n/** Both halves come back as `number`: each is 32 bits, and a `number` holds those exactly. */\nexport function typeOf(vertex: VertexId): number {\n return Number(vertex >> TYPE_SHIFT);\n}\n\nexport function denseOf(vertex: VertexId): number {\n return Number(vertex & DENSE_MASK);\n}\n\n/**\n * The identity↔index map for whatever is drawn right now.\n *\n * Built once per residency change and never mutated, because that is what a residency change *is*: a\n * different set of vertices, in a different order. It belongs to whoever owns residency — that is\n * `useQueryLoop`, which holds the answer — and is read from there by everything else, because a\n * second copy is a second copy that can disagree with the buffers on screen.\n */\nexport interface Resident {\n /** How many points are drawn. */\n readonly size: number;\n /** Where a vertex is drawn, or `undefined` when it is not resident. */\n indexOf(vertex: VertexId): number | undefined;\n /** Which vertex is drawn at a buffer index, or `undefined` past the end of the answer. */\n at(index: number): VertexId | undefined;\n /** Where these vertices are drawn, skipping every one that is not resident. */\n indicesOf(vertices: Iterable<VertexId>): number[];\n /** Which vertices are drawn at these buffer indices, skipping any the answer does not hold. */\n verticesAt(indices: Iterable<number>): VertexId[];\n}\n\nconst NOBODY = new BigUint64Array(0);\n\n/**\n * The map an answer implies. `null` — before the first answer — is nobody resident, not an error.\n *\n * Keyed by the identity itself. `Map` and `Set` compare keys by SameValueZero, which for a `bigint`\n * is *value* equality and not reference equality — two separately-constructed `vertexId(0, 5)`\n * reach the same entry. That is asserted rather than assumed in `resident.test.ts`, because the\n * whole file rests on it and BigInt being an object-shaped primitive makes it a fair thing to doubt.\n */\nexport function residentOf(slice: Slice | null): Resident {\n const all = slice?.vertices ?? NOBODY;\n /**\n * The marks, and not the anchors past them.\n *\n * An anchor is a real vertex in the buffers at its real coordinates, there so an edge leaving the\n * window has an end — and it is never drawn. Residency is *what is drawn*, so it stops here: a\n * hover, a selection or a frame that could land on an anchor would be pointing at a vertex with no\n * ink, off screen, that the window deliberately did not return.\n */\n const size = Math.min(slice?.marks ?? all.length, all.length);\n const vertices = all;\n const index = new Map<bigint, number>();\n for (let i = 0; i < size; i++) index.set(vertices[i] as bigint, i);\n\n const at = (i: number): VertexId | undefined =>\n i >= 0 && i < size ? (vertices[i] as VertexId) : undefined;\n const indexOf = (vertex: VertexId): number | undefined => index.get(vertex);\n\n return {\n size,\n indexOf,\n at,\n indicesOf(wanted) {\n const found: number[] = [];\n for (const vertex of wanted) {\n const i = indexOf(vertex);\n if (i !== undefined) found.push(i);\n }\n return found;\n },\n verticesAt(indices) {\n const found: VertexId[] = [];\n for (const i of indices) {\n const vertex = at(i);\n if (vertex !== undefined) found.push(vertex);\n }\n return found;\n },\n };\n}\n"],"names":["TYPE_SHIFT","DENSE_MASK","vertexId","type","dense","typeOf","vertex","denseOf","NOBODY","residentOf","slice","all","size","vertices","index","i","at","indexOf","wanted","found","indices"],"mappings":"AAqDA,MAAMA,IAAa,KACbC,IAAa;AAEZ,SAASC,EAASC,GAAcC,GAAyB;AAC9D,SAAS,OAAOD,CAAI,KAAKH,IAAc,OAAOI,CAAK;AACrD;AAGO,SAASC,EAAOC,GAA0B;AAC/C,SAAO,OAAOA,KAAUN,CAAU;AACpC;AAEO,SAASO,EAAQD,GAA0B;AAChD,SAAO,OAAOA,IAASL,CAAU;AACnC;AAuBA,MAAMO,IAAS,IAAI,eAAe,CAAC;AAU5B,SAASC,EAAWC,GAA+B;AACxD,QAAMC,KAAMD,KAAA,gBAAAA,EAAO,aAAYF,GASzBI,IAAO,KAAK,KAAIF,KAAA,gBAAAA,EAAO,UAASC,EAAI,QAAQA,EAAI,MAAM,GACtDE,IAAWF,GACXG,wBAAY,IAAA;AAClB,WAASC,IAAI,GAAGA,IAAIH,GAAMG,OAAW,IAAIF,EAASE,CAAC,GAAaA,CAAC;AAEjE,QAAMC,IAAK,CAACD,MACVA,KAAK,KAAKA,IAAIH,IAAQC,EAASE,CAAC,IAAiB,QAC7CE,IAAU,CAACX,MAAyCQ,EAAM,IAAIR,CAAM;AAE1E,SAAO;AAAA,IACL,MAAAM;AAAA,IACA,SAAAK;AAAA,IACA,IAAAD;AAAA,IACA,UAAUE,GAAQ;AAChB,YAAMC,IAAkB,CAAA;AACxB,iBAAWb,KAAUY,GAAQ;AAC3B,cAAMH,IAAIE,EAAQX,CAAM;AACxB,QAAIS,MAAM,UAAWI,EAAM,KAAKJ,CAAC;AAAA,MACnC;AACA,aAAOI;AAAA,IACT;AAAA,IACA,WAAWC,GAAS;AAClB,YAAMD,IAAoB,CAAA;AAC1B,iBAAWJ,KAAKK,GAAS;AACvB,cAAMd,IAASU,EAAGD,CAAC;AACnB,QAAIT,MAAW,UAAWa,EAAM,KAAKb,CAAM;AAAA,MAC7C;AACA,aAAOa;AAAA,IACT;AAAA,EAAA;AAEJ;"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The manifest shape, re-declared structurally rather than imported from `@kanzo-tech/theme`.
3
+ *
4
+ * Third instance of the same call in this repository, and the same reasoning each time: the type is
5
+ * a *shape*, the value is data, and importing it would add a dependency to carry no code. Here it
6
+ * also keeps the arrow pointing one way — a section contributes to the core, and the core does not
7
+ * know it exists, so neither package should have to name the other to make that true.
8
+ *
9
+ * `packages/theme/src/sections.ts` is the definition this conforms to. Two structural declarations
10
+ * can drift; what stops it is that a mismatch is a `tsc` error the moment a host passes this to
11
+ * `validateSection`, which `docs/` does.
12
+ */
13
+ type PrefCommon_ = {
14
+ default: string;
15
+ doc: string;
16
+ label?: string;
17
+ };
18
+ type SectionPrefDecl_ = (PrefCommon_ & {
19
+ kind: "choice";
20
+ options: readonly {
21
+ value: string;
22
+ label: string;
23
+ }[];
24
+ }) | (PrefCommon_ & {
25
+ kind: "toggle";
26
+ }) | (PrefCommon_ & {
27
+ kind: "range";
28
+ min: number;
29
+ max: number;
30
+ step: number;
31
+ });
32
+ interface SectionManifest {
33
+ namespace: string;
34
+ version: number;
35
+ tokens?: Readonly<Record<string, {
36
+ default: string;
37
+ doc: string;
38
+ }>>;
39
+ prefs?: Readonly<Record<string, SectionPrefDecl_>>;
40
+ }
41
+ /**
42
+ * The graph's contribution to a host's preferences — its tokens and its choices.
43
+ *
44
+ * **It was `LOOK_SECTION`, and the name was half of it.** The look is five of the sixteen
45
+ * preferences below; the rest are the edge layer, the dot grid and the six force coefficients, which
46
+ * lived in a `Display` interface and a `Sim` interface with their own defaults and their own
47
+ * hand-rolled sliders. One section, one storage shape, one resolution, one renderer.
48
+ *
49
+ * Reached by subpath — `@kanzo-tech/graph/section` — and never imported by
50
+ * `@kanzo-tech/theme`. That direction is the whole design: contributing is *using* a namespace, not
51
+ * registering a type in the core, so a consumer who never installs this package pays nothing and
52
+ * `packages/ui` gains no reference to the graph.
53
+ *
54
+ * ## Four colour tokens are gone, and the rule that removed them is the same one
55
+ *
56
+ * A name is worth having when a theme would plausibly give it a value **different from the token it
57
+ * comes out of**. If it can only ever repeat one, it is not a name, it is a use. Applied here:
58
+ *
59
+ * · `vignette` defaulted to `--background`, and the rim fades *toward the page* — any other value
60
+ * breaks it. It could never differ, so the canvas reads `--background` directly.
61
+ * · `marquee-edge` and `point-ring-focus` both defaulted to `--primary`. A selection outline IS the
62
+ * brand; two names for "the brand, solid" is the duplication, not the flexibility.
63
+ * · `marquee` was `(brand, alpha 5)` — a *dilution*, which is a derivation and not a name. It is
64
+ * `color-mix` where it is drawn.
65
+ *
66
+ * What that costs is the capacity for a tenant to redirect them, and measured against the rule
67
+ * nobody wants it: redirecting `vignette` is breaking it. It is the same rule that removed seventeen
68
+ * level-names from the core's vocabulary, applied to a section rather than to the core — which is
69
+ * the half that had not been done.
70
+ *
71
+ * `grid` survives, and it is the only one that is arguable: a dot grid is decorative and drawn at a
72
+ * different weight from a border, so a theme might plausibly want it fainter. If no theme ever does,
73
+ * it goes too.
74
+ *
75
+ * **A default is a CSS value now, not an object.** It was `{kind, ramp, step}` in three shapes, two
76
+ * of which named a reference tier that no longer exists; `var(--border)` says the same thing with
77
+ * nothing between it and the browser, and the type it mirrored no longer has to be declared
78
+ * structurally in two packages at once.
79
+ */
80
+ export declare const GRAPH_SECTION: SectionManifest;
81
+ export {};
82
+ //# sourceMappingURL=section.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section.d.ts","sourceRoot":"","sources":["../src/section.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,KAAK,WAAW,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACpE,KAAK,gBAAgB,GACjB,CAAC,WAAW,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,SAAS;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,CAAC,GACxF,CAAC,WAAW,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,GAClC,CAAC,WAAW,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE9E,UAAU,eAAe;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IACpE,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;CACpD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,aAAa,EAAE,eA0I3B,CAAC"}
@@ -0,0 +1,142 @@
1
+ const e = {
2
+ namespace: "graph",
3
+ version: 1,
4
+ tokens: {
5
+ grid: {
6
+ default: "var(--border)",
7
+ doc: "The dot grid behind the canvas. Decorative, and WCAG 1.4.11 exempts a non-interactive separator by name."
8
+ },
9
+ "point-ring-hover": {
10
+ default: "var(--primary)",
11
+ doc: "The ring drawn around a hovered point."
12
+ }
13
+ },
14
+ prefs: {
15
+ marks: {
16
+ kind: "choice",
17
+ label: "Marks",
18
+ default: "dense",
19
+ doc: "How much ink a point spends. The legible mark is the one to pair the shape channel with — its radius floor is what keeps shape from corrupting the size ramp beside it.",
20
+ options: [
21
+ { value: "dense", label: "Dense" },
22
+ { value: "legible", label: "Legible" }
23
+ ]
24
+ },
25
+ "additive-links": {
26
+ kind: "toggle",
27
+ label: "Additive links",
28
+ default: "false",
29
+ doc: "Links add where they overlap instead of compositing over one another. Additive light is what makes a dense graph read as flow — and what made 4,280 links at 0.45 swallow 1,543 points on the archive."
30
+ },
31
+ "bowed-links": {
32
+ kind: "toggle",
33
+ label: "Bowed links",
34
+ default: "true",
35
+ doc: "Links bow off the straight line by a hint, which is enough to tell two parallel edges apart. Every link curves the same way, so more than a hint reads as a pinwheel."
36
+ },
37
+ labels: {
38
+ kind: "range",
39
+ label: "Labels",
40
+ default: "26",
41
+ doc: "How many of the highest-degree nodes carry a standing label. Zero draws none.",
42
+ min: 0,
43
+ max: 60,
44
+ step: 2
45
+ },
46
+ vignette: {
47
+ kind: "toggle",
48
+ label: "Vignette",
49
+ default: "false",
50
+ doc: "A darkened rim. Mood rather than a reading aid, which is why it is a preference and not a display control."
51
+ },
52
+ links: {
53
+ kind: "toggle",
54
+ label: "Show links",
55
+ default: "true",
56
+ doc: "Draw the edge layer at all. Past a few hundred thousand links it is fog that costs a draw call a frame, and `adaptive` says where that is."
57
+ },
58
+ grid: {
59
+ kind: "toggle",
60
+ label: "Dot grid",
61
+ default: "true",
62
+ doc: "The dot grid behind the graph. It pans and subdivides with the camera, which is what makes a pan read as motion rather than as a redraw."
63
+ },
64
+ /**
65
+ * The force coefficients, which are preferences and were a second vocabulary.
66
+ *
67
+ * **They are not appearance, and this manifest holds them anyway.** A section's `tokens` are the
68
+ * colours a document may move; its `prefs` are *what the person on the screen decides*, and a
69
+ * reader tuning a layout until it settles is deciding something. They lived in a TS interface
70
+ * with a `DEFAULT_SIM` beside it and six hand-rolled sliders in one showcase's dock — the fourth
71
+ * declaration style in a census of four, for a quarter of the knobs.
72
+ *
73
+ * The bounds are cosmos.gl's useful range rather than its legal one, and a stored value outside
74
+ * them is declined: a slider that used to run to 5 and now stops at 3 must not paint 5 because
75
+ * storage remembers it.
76
+ *
77
+ * **`adaptive(nodes)` still exists and still knows better.** What a corpus of this size wants is
78
+ * computed, not chosen, and these defaults are tuned for a few hundred nodes. A host with a
79
+ * large corpus should start its users at `adaptive`'s answer through the tenant policy — which
80
+ * is the mechanism's own way of saying "start somewhere else" — rather than by writing values
81
+ * into storage nobody can then reset.
82
+ */
83
+ gravity: {
84
+ kind: "range",
85
+ label: "Gravity",
86
+ default: "0.14",
87
+ doc: "Pull toward the centre. It is what stops a disconnected component drifting off the canvas.",
88
+ min: 0,
89
+ max: 0.5,
90
+ step: 0.01
91
+ },
92
+ repulsion: {
93
+ kind: "range",
94
+ label: "Repulsion",
95
+ default: "1.1",
96
+ doc: "How hard every point pushes every other. Bigger graphs need less of it, or they never settle.",
97
+ min: 0,
98
+ max: 2,
99
+ step: 0.05
100
+ },
101
+ "link-spring": {
102
+ kind: "range",
103
+ label: "Link spring",
104
+ default: "0.6",
105
+ doc: "How hard a link pulls its two ends together.",
106
+ min: 0,
107
+ max: 2,
108
+ step: 0.05
109
+ },
110
+ "link-distance": {
111
+ kind: "range",
112
+ label: "Link distance",
113
+ default: "18",
114
+ doc: "The length a link is happy at, in simulation units.",
115
+ min: 2,
116
+ max: 60,
117
+ step: 1
118
+ },
119
+ friction: {
120
+ kind: "range",
121
+ label: "Friction",
122
+ default: "0.86",
123
+ doc: "How fast motion decays. Under about 0.7 the layout twitches; near 1 it never stops.",
124
+ min: 0.5,
125
+ max: 0.99,
126
+ step: 0.01
127
+ },
128
+ cluster: {
129
+ kind: "range",
130
+ label: "Cluster pull",
131
+ default: "0.1",
132
+ doc: "Pull toward the node's group position on the cluster ring. Zero lets the links decide alone.",
133
+ min: 0,
134
+ max: 1,
135
+ step: 0.05
136
+ }
137
+ }
138
+ };
139
+ export {
140
+ e as GRAPH_SECTION
141
+ };
142
+ //# sourceMappingURL=section.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section.js","sources":["../src/section.ts"],"sourcesContent":["/**\n * The manifest shape, re-declared structurally rather than imported from `@kanzo-tech/theme`.\n *\n * Third instance of the same call in this repository, and the same reasoning each time: the type is\n * a *shape*, the value is data, and importing it would add a dependency to carry no code. Here it\n * also keeps the arrow pointing one way — a section contributes to the core, and the core does not\n * know it exists, so neither package should have to name the other to make that true.\n *\n * `packages/theme/src/sections.ts` is the definition this conforms to. Two structural declarations\n * can drift; what stops it is that a mismatch is a `tsc` error the moment a host passes this to\n * `validateSection`, which `docs/` does.\n */\ntype PrefCommon_ = { default: string; doc: string; label?: string };\ntype SectionPrefDecl_ =\n | (PrefCommon_ & { kind: \"choice\"; options: readonly { value: string; label: string }[] })\n | (PrefCommon_ & { kind: \"toggle\" })\n | (PrefCommon_ & { kind: \"range\"; min: number; max: number; step: number });\n\ninterface SectionManifest {\n namespace: string;\n version: number;\n tokens?: Readonly<Record<string, { default: string; doc: string }>>;\n prefs?: Readonly<Record<string, SectionPrefDecl_>>;\n}\n\n/**\n * The graph's contribution to a host's preferences — its tokens and its choices.\n *\n * **It was `LOOK_SECTION`, and the name was half of it.** The look is five of the sixteen\n * preferences below; the rest are the edge layer, the dot grid and the six force coefficients, which\n * lived in a `Display` interface and a `Sim` interface with their own defaults and their own\n * hand-rolled sliders. One section, one storage shape, one resolution, one renderer.\n *\n * Reached by subpath — `@kanzo-tech/graph/section` — and never imported by\n * `@kanzo-tech/theme`. That direction is the whole design: contributing is *using* a namespace, not\n * registering a type in the core, so a consumer who never installs this package pays nothing and\n * `packages/ui` gains no reference to the graph.\n *\n * ## Four colour tokens are gone, and the rule that removed them is the same one\n *\n * A name is worth having when a theme would plausibly give it a value **different from the token it\n * comes out of**. If it can only ever repeat one, it is not a name, it is a use. Applied here:\n *\n * · `vignette` defaulted to `--background`, and the rim fades *toward the page* — any other value\n * breaks it. It could never differ, so the canvas reads `--background` directly.\n * · `marquee-edge` and `point-ring-focus` both defaulted to `--primary`. A selection outline IS the\n * brand; two names for \"the brand, solid\" is the duplication, not the flexibility.\n * · `marquee` was `(brand, alpha 5)` — a *dilution*, which is a derivation and not a name. It is\n * `color-mix` where it is drawn.\n *\n * What that costs is the capacity for a tenant to redirect them, and measured against the rule\n * nobody wants it: redirecting `vignette` is breaking it. It is the same rule that removed seventeen\n * level-names from the core's vocabulary, applied to a section rather than to the core — which is\n * the half that had not been done.\n *\n * `grid` survives, and it is the only one that is arguable: a dot grid is decorative and drawn at a\n * different weight from a border, so a theme might plausibly want it fainter. If no theme ever does,\n * it goes too.\n *\n * **A default is a CSS value now, not an object.** It was `{kind, ramp, step}` in three shapes, two\n * of which named a reference tier that no longer exists; `var(--border)` says the same thing with\n * nothing between it and the browser, and the type it mirrored no longer has to be declared\n * structurally in two packages at once.\n */\nexport const GRAPH_SECTION: SectionManifest = {\n namespace: \"graph\",\n version: 1,\n tokens: {\n grid: {\n default: \"var(--border)\",\n doc: \"The dot grid behind the canvas. Decorative, and WCAG 1.4.11 exempts a non-interactive separator by name.\",\n },\n \"point-ring-hover\": {\n default: \"var(--primary)\",\n doc: \"The ring drawn around a hovered point.\",\n },\n },\n prefs: {\n marks: {\n kind: \"choice\",\n label: \"Marks\",\n default: \"dense\",\n doc: \"How much ink a point spends. The legible mark is the one to pair the shape channel with — its radius floor is what keeps shape from corrupting the size ramp beside it.\",\n options: [\n { value: \"dense\", label: \"Dense\" },\n { value: \"legible\", label: \"Legible\" },\n ],\n },\n \"additive-links\": {\n kind: \"toggle\",\n label: \"Additive links\",\n default: \"false\",\n doc: \"Links add where they overlap instead of compositing over one another. Additive light is what makes a dense graph read as flow — and what made 4,280 links at 0.45 swallow 1,543 points on the archive.\",\n },\n \"bowed-links\": {\n kind: \"toggle\",\n label: \"Bowed links\",\n default: \"true\",\n doc: \"Links bow off the straight line by a hint, which is enough to tell two parallel edges apart. Every link curves the same way, so more than a hint reads as a pinwheel.\",\n },\n labels: {\n kind: \"range\",\n label: \"Labels\",\n default: \"26\",\n doc: \"How many of the highest-degree nodes carry a standing label. Zero draws none.\",\n min: 0,\n max: 60,\n step: 2,\n },\n vignette: {\n kind: \"toggle\",\n label: \"Vignette\",\n default: \"false\",\n doc: \"A darkened rim. Mood rather than a reading aid, which is why it is a preference and not a display control.\",\n },\n links: {\n kind: \"toggle\",\n label: \"Show links\",\n default: \"true\",\n doc: \"Draw the edge layer at all. Past a few hundred thousand links it is fog that costs a draw call a frame, and `adaptive` says where that is.\",\n },\n grid: {\n kind: \"toggle\",\n label: \"Dot grid\",\n default: \"true\",\n doc: \"The dot grid behind the graph. It pans and subdivides with the camera, which is what makes a pan read as motion rather than as a redraw.\",\n },\n\n /**\n * The force coefficients, which are preferences and were a second vocabulary.\n *\n * **They are not appearance, and this manifest holds them anyway.** A section's `tokens` are the\n * colours a document may move; its `prefs` are *what the person on the screen decides*, and a\n * reader tuning a layout until it settles is deciding something. They lived in a TS interface\n * with a `DEFAULT_SIM` beside it and six hand-rolled sliders in one showcase's dock — the fourth\n * declaration style in a census of four, for a quarter of the knobs.\n *\n * The bounds are cosmos.gl's useful range rather than its legal one, and a stored value outside\n * them is declined: a slider that used to run to 5 and now stops at 3 must not paint 5 because\n * storage remembers it.\n *\n * **`adaptive(nodes)` still exists and still knows better.** What a corpus of this size wants is\n * computed, not chosen, and these defaults are tuned for a few hundred nodes. A host with a\n * large corpus should start its users at `adaptive`'s answer through the tenant policy — which\n * is the mechanism's own way of saying \"start somewhere else\" — rather than by writing values\n * into storage nobody can then reset.\n */\n gravity: {\n kind: \"range\",\n label: \"Gravity\",\n default: \"0.14\",\n doc: \"Pull toward the centre. It is what stops a disconnected component drifting off the canvas.\",\n min: 0,\n max: 0.5,\n step: 0.01,\n },\n repulsion: {\n kind: \"range\",\n label: \"Repulsion\",\n default: \"1.1\",\n doc: \"How hard every point pushes every other. Bigger graphs need less of it, or they never settle.\",\n min: 0,\n max: 2,\n step: 0.05,\n },\n \"link-spring\": {\n kind: \"range\",\n label: \"Link spring\",\n default: \"0.6\",\n doc: \"How hard a link pulls its two ends together.\",\n min: 0,\n max: 2,\n step: 0.05,\n },\n \"link-distance\": {\n kind: \"range\",\n label: \"Link distance\",\n default: \"18\",\n doc: \"The length a link is happy at, in simulation units.\",\n min: 2,\n max: 60,\n step: 1,\n },\n friction: {\n kind: \"range\",\n label: \"Friction\",\n default: \"0.86\",\n doc: \"How fast motion decays. Under about 0.7 the layout twitches; near 1 it never stops.\",\n min: 0.5,\n max: 0.99,\n step: 0.01,\n },\n cluster: {\n kind: \"range\",\n label: \"Cluster pull\",\n default: \"0.1\",\n doc: \"Pull toward the node's group position on the cluster ring. Zero lets the links decide alone.\",\n min: 0,\n max: 1,\n step: 0.05,\n },\n },\n};\n"],"names":["GRAPH_SECTION"],"mappings":"AAgEO,MAAMA,IAAiC;AAAA,EAC5C,WAAW;AAAA,EACX,SAAS;AAAA,EACT,QAAQ;AAAA,IACN,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,KAAK;AAAA,IAAA;AAAA,IAEP,oBAAoB;AAAA,MAClB,SAAS;AAAA,MACT,KAAK;AAAA,IAAA;AAAA,EACP;AAAA,EAEF,OAAO;AAAA,IACL,OAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,MACT,KAAK;AAAA,MACL,SAAS;AAAA,QACP,EAAE,OAAO,SAAS,OAAO,QAAA;AAAA,QACzB,EAAE,OAAO,WAAW,OAAO,UAAA;AAAA,MAAU;AAAA,IACvC;AAAA,IAEF,kBAAkB;AAAA,MAChB,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,MACT,KAAK;AAAA,IAAA;AAAA,IAEP,eAAe;AAAA,MACb,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,MACT,KAAK;AAAA,IAAA;AAAA,IAEP,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,MAAM;AAAA,IAAA;AAAA,IAER,UAAU;AAAA,MACR,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,MACT,KAAK;AAAA,IAAA;AAAA,IAEP,OAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,MACT,KAAK;AAAA,IAAA;AAAA,IAEP,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,MACT,KAAK;AAAA,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAsBP,SAAS;AAAA,MACP,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,MAAM;AAAA,IAAA;AAAA,IAER,WAAW;AAAA,MACT,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,MAAM;AAAA,IAAA;AAAA,IAER,eAAe;AAAA,MACb,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,MAAM;AAAA,IAAA;AAAA,IAER,iBAAiB;AAAA,MACf,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,MAAM;AAAA,IAAA;AAAA,IAER,UAAU;AAAA,MACR,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,MAAM;AAAA,IAAA;AAAA,IAER,SAAS;AAAA,MACP,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,MAAM;AAAA,IAAA;AAAA,EACR;AAEJ;"}
@@ -0,0 +1,78 @@
1
+ import { MosaicClient, Coordinator, FilterExpr, Selection } from '@kanzo-tech/mosaic';
2
+ /**
3
+ * One read of a corpus, as a client of the page's coordinator.
4
+ *
5
+ * **A source used to query outside the protocol, and that was the whole defect.** `onceQuery`
6
+ * connected a throwaway `MosaicClient` per query, took the answer and disconnected — a second query
7
+ * path beside the one every chart on the page uses, and one that no selection could reach. So a
8
+ * graph inside a crossfilter had to be joined to it from outside: something else asked which ids
9
+ * survived, and the canvas painted grey over the ones that had not. Two round trips, a full scan and
10
+ * a texture upload, to express a `WHERE` clause.
11
+ *
12
+ * A read is the same protocol a histogram uses, in the two directions it runs:
13
+ *
14
+ * `query(filter)` — the coordinator hands us the page's predicate and we build the SQL around it,
15
+ * so what comes back is *what survives* rather than everything with a mask beside it.
16
+ * `queryResult(data)` — the answer, whether we asked for it or the page's filters moved.
17
+ *
18
+ * **The second direction is the one that pays.** A selection change re-runs this read with the new
19
+ * predicate and no camera involvement at all: the coordinator already walks its clients on every
20
+ * selection update, so a graph that is one of them is re-queried the way a plot is. Nothing
21
+ * subscribes to anything, and there is no window where the picture and the page's filters disagree.
22
+ *
23
+ * **Not `makeClient`.** That helper takes a `query` and a `queryResult` as options and is right for a
24
+ * client with one standing question. A slice is two reads that have to arrive together, over a
25
+ * question the camera rewrites — so what is needed is a handle the source can re-aim, which is a
26
+ * class with a method rather than a closure fixed at construction.
27
+ */
28
+ export declare class SliceRead extends MosaicClient {
29
+ #private;
30
+ /**
31
+ * @param filterBy The crossfilter this read lives inside, if any. The unfiltered reads — how big
32
+ * the corpus is, where it is, what its tile footers say — take none: those are facts about the
33
+ * corpus rather than about what the page is looking at, and filtering them would make
34
+ * "20,000 of 1,000,000" a fraction of itself.
35
+ */
36
+ constructor(coordinator: Coordinator, filterBy?: Selection);
37
+ /**
38
+ * Pre-aggregation cannot help here, and saying so costs a getter instead of a query.
39
+ *
40
+ * `preaggColumns` reaches the same "no" by calling `query()` and finding a string where it wanted a
41
+ * `SelectQuery` — a slice is a CTE over window functions and is not expressible in the builder —
42
+ * but it calls `query()` to find out, on every selection change. The claim is true rather than
43
+ * defensive: the filter decides which rows are numbered, so it moves the groupby domain outright,
44
+ * which is exactly what this flag is asked about.
45
+ */
46
+ get filterStable(): boolean;
47
+ /**
48
+ * What the rest of the page is filtering by, right now.
49
+ *
50
+ * The same value the coordinator would hand `query()`. A source that needs the predicate *before*
51
+ * it can build SQL — because it has to know which relation to point at, or because it is assembling
52
+ * one answer out of two reads — asks here rather than reaching into the selection and re-deriving
53
+ * the client exemption by hand.
54
+ */
55
+ get predicate(): FilterExpr;
56
+ /**
57
+ * Ask, and *keep* the question. The coordinator issues, consolidates, caches and re-runs it.
58
+ *
59
+ * `build` is retained rather than consumed, because it is the standing question: after the page
60
+ * filters something the camera is still over the same rectangle, so the coordinator re-running this
61
+ * with a new predicate is precisely the query anybody would have written by hand.
62
+ */
63
+ ask(build: (filter: FilterExpr) => string): Promise<unknown>;
64
+ /**
65
+ * Where an answer goes when nobody asked for it — which is the page's filters moving.
66
+ *
67
+ * Told apart from a pull by which of the two is outstanding rather than by a flag, so the two
68
+ * cannot disagree: an answer settles the promise when one is waiting, and is reported here when
69
+ * none is.
70
+ */
71
+ set onAnswer(handle: ((data: unknown) => void) | null);
72
+ /** Let go: no more queries, and the coordinator stops walking us on every selection change. */
73
+ release(): void;
74
+ query(filter?: FilterExpr): string | null;
75
+ queryResult(data: unknown): this;
76
+ queryError(error: Error): this;
77
+ }
78
+ //# sourceMappingURL=slice-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slice-client.d.ts","sourceRoot":"","sources":["../src/slice-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAG7E;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,SAAU,SAAQ,YAAY;;IAOzC;;;;;OAKG;gBACS,WAAW,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,SAAS;IAK1D;;;;;;;;OAQG;IACH,IAAa,YAAY,IAAI,OAAO,CAEnC;IAED;;;;;;;OAOG;IACH,IAAI,SAAS,IAAI,UAAU,CAE1B;IAED;;;;;;OAMG;IACH,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAuB5D;;;;;;OAMG;IACH,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,IAAI,EAEpD;IAED,+FAA+F;IAC/F,OAAO,IAAI,IAAI;IAWN,KAAK,CAAC,MAAM,GAAE,UAAe,GAAG,MAAM,GAAG,IAAI;IAI7C,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAQhC,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;CAMxC"}
@@ -0,0 +1,98 @@
1
+ "use client";
2
+ var p = (e) => {
3
+ throw TypeError(e);
4
+ };
5
+ var m = (e, i, t) => i.has(e) || p("Cannot " + t);
6
+ var l = (e, i, t) => (m(e, i, "read from private field"), t ? t.call(e) : i.get(e)), u = (e, i, t) => i.has(e) ? p("Cannot add the same private member more than once") : i instanceof WeakSet ? i.add(e) : i.set(e, t), r = (e, i, t, s) => (m(e, i, "write to private field"), s ? s.call(e, t) : i.set(e, t), t);
7
+ import { MosaicClient as w } from "@kanzo-tech/mosaic";
8
+ import { SUPERSEDED as y } from "./bounded.js";
9
+ var o, c, n, a, h;
10
+ class R extends w {
11
+ /**
12
+ * @param filterBy The crossfilter this read lives inside, if any. The unfiltered reads — how big
13
+ * the corpus is, where it is, what its tile footers say — take none: those are facts about the
14
+ * corpus rather than about what the page is looking at, and filtering them would make
15
+ * "20,000 of 1,000,000" a fraction of itself.
16
+ */
17
+ constructor(t, s) {
18
+ super(s);
19
+ u(this, o);
20
+ u(this, c, null);
21
+ u(this, n, null);
22
+ u(this, a, null);
23
+ u(this, h, !1);
24
+ r(this, o, t);
25
+ }
26
+ /**
27
+ * Pre-aggregation cannot help here, and saying so costs a getter instead of a query.
28
+ *
29
+ * `preaggColumns` reaches the same "no" by calling `query()` and finding a string where it wanted a
30
+ * `SelectQuery` — a slice is a CTE over window functions and is not expressible in the builder —
31
+ * but it calls `query()` to find out, on every selection change. The claim is true rather than
32
+ * defensive: the filter decides which rows are numbered, so it moves the groupby domain outright,
33
+ * which is exactly what this flag is asked about.
34
+ */
35
+ get filterStable() {
36
+ return !1;
37
+ }
38
+ /**
39
+ * What the rest of the page is filtering by, right now.
40
+ *
41
+ * The same value the coordinator would hand `query()`. A source that needs the predicate *before*
42
+ * it can build SQL — because it has to know which relation to point at, or because it is assembling
43
+ * one answer out of two reads — asks here rather than reaching into the selection and re-deriving
44
+ * the client exemption by hand.
45
+ */
46
+ get predicate() {
47
+ var t;
48
+ return ((t = this.filterBy) == null ? void 0 : t.predicate(this)) ?? [];
49
+ }
50
+ /**
51
+ * Ask, and *keep* the question. The coordinator issues, consolidates, caches and re-runs it.
52
+ *
53
+ * `build` is retained rather than consumed, because it is the standing question: after the page
54
+ * filters something the camera is still over the same rectangle, so the coordinator re-running this
55
+ * with a new predicate is precisely the query anybody would have written by hand.
56
+ */
57
+ ask(t) {
58
+ r(this, c, t);
59
+ const s = new Promise((f, q) => {
60
+ var d;
61
+ (d = l(this, n)) == null || d.reject(y), r(this, n, { resolve: f, reject: q });
62
+ });
63
+ return l(this, h) ? this.requestQuery() : (r(this, h, !0), l(this, o).connect(this)), s;
64
+ }
65
+ /**
66
+ * Where an answer goes when nobody asked for it — which is the page's filters moving.
67
+ *
68
+ * Told apart from a pull by which of the two is outstanding rather than by a flag, so the two
69
+ * cannot disagree: an answer settles the promise when one is waiting, and is reported here when
70
+ * none is.
71
+ */
72
+ set onAnswer(t) {
73
+ r(this, a, t);
74
+ }
75
+ /** Let go: no more queries, and the coordinator stops walking us on every selection change. */
76
+ release() {
77
+ var t;
78
+ (t = l(this, n)) == null || t.reject(y), r(this, n, null), r(this, a, null), r(this, c, null), l(this, h) && (r(this, h, !1), l(this, o).disconnect(this));
79
+ }
80
+ query(t = []) {
81
+ var s;
82
+ return ((s = l(this, c)) == null ? void 0 : s.call(this, t)) ?? null;
83
+ }
84
+ queryResult(t) {
85
+ var f;
86
+ const s = l(this, n);
87
+ return r(this, n, null), s ? s.resolve(t) : (f = l(this, a)) == null || f.call(this, t), this;
88
+ }
89
+ queryError(t) {
90
+ const s = l(this, n);
91
+ return r(this, n, null), s && s.reject(t), this;
92
+ }
93
+ }
94
+ o = new WeakMap(), c = new WeakMap(), n = new WeakMap(), a = new WeakMap(), h = new WeakMap();
95
+ export {
96
+ R as SliceRead
97
+ };
98
+ //# sourceMappingURL=slice-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slice-client.js","sources":["../src/slice-client.ts"],"sourcesContent":["\"use client\";\n\nimport { MosaicClient } from \"@kanzo-tech/mosaic\";\nimport type { Coordinator, FilterExpr, Selection } from \"@kanzo-tech/mosaic\";\nimport { SUPERSEDED } from \"./bounded\";\n\n/**\n * One read of a corpus, as a client of the page's coordinator.\n *\n * **A source used to query outside the protocol, and that was the whole defect.** `onceQuery`\n * connected a throwaway `MosaicClient` per query, took the answer and disconnected — a second query\n * path beside the one every chart on the page uses, and one that no selection could reach. So a\n * graph inside a crossfilter had to be joined to it from outside: something else asked which ids\n * survived, and the canvas painted grey over the ones that had not. Two round trips, a full scan and\n * a texture upload, to express a `WHERE` clause.\n *\n * A read is the same protocol a histogram uses, in the two directions it runs:\n *\n * `query(filter)` — the coordinator hands us the page's predicate and we build the SQL around it,\n * so what comes back is *what survives* rather than everything with a mask beside it.\n * `queryResult(data)` — the answer, whether we asked for it or the page's filters moved.\n *\n * **The second direction is the one that pays.** A selection change re-runs this read with the new\n * predicate and no camera involvement at all: the coordinator already walks its clients on every\n * selection update, so a graph that is one of them is re-queried the way a plot is. Nothing\n * subscribes to anything, and there is no window where the picture and the page's filters disagree.\n *\n * **Not `makeClient`.** That helper takes a `query` and a `queryResult` as options and is right for a\n * client with one standing question. A slice is two reads that have to arrive together, over a\n * question the camera rewrites — so what is needed is a handle the source can re-aim, which is a\n * class with a method rather than a closure fixed at construction.\n */\nexport class SliceRead extends MosaicClient {\n #coordinator: Coordinator;\n #build: ((filter: FilterExpr) => string) | null = null;\n #settle: { resolve: (data: unknown) => void; reject: (error: unknown) => void } | null = null;\n #onAnswer: ((data: unknown) => void) | null = null;\n #connected = false;\n\n /**\n * @param filterBy The crossfilter this read lives inside, if any. The unfiltered reads — how big\n * the corpus is, where it is, what its tile footers say — take none: those are facts about the\n * corpus rather than about what the page is looking at, and filtering them would make\n * \"20,000 of 1,000,000\" a fraction of itself.\n */\n constructor(coordinator: Coordinator, filterBy?: Selection) {\n super(filterBy);\n this.#coordinator = coordinator;\n }\n\n /**\n * Pre-aggregation cannot help here, and saying so costs a getter instead of a query.\n *\n * `preaggColumns` reaches the same \"no\" by calling `query()` and finding a string where it wanted a\n * `SelectQuery` — a slice is a CTE over window functions and is not expressible in the builder —\n * but it calls `query()` to find out, on every selection change. The claim is true rather than\n * defensive: the filter decides which rows are numbered, so it moves the groupby domain outright,\n * which is exactly what this flag is asked about.\n */\n override get filterStable(): boolean {\n return false;\n }\n\n /**\n * What the rest of the page is filtering by, right now.\n *\n * The same value the coordinator would hand `query()`. A source that needs the predicate *before*\n * it can build SQL — because it has to know which relation to point at, or because it is assembling\n * one answer out of two reads — asks here rather than reaching into the selection and re-deriving\n * the client exemption by hand.\n */\n get predicate(): FilterExpr {\n return this.filterBy?.predicate(this) ?? [];\n }\n\n /**\n * Ask, and *keep* the question. The coordinator issues, consolidates, caches and re-runs it.\n *\n * `build` is retained rather than consumed, because it is the standing question: after the page\n * filters something the camera is still over the same rectangle, so the coordinator re-running this\n * with a new predicate is precisely the query anybody would have written by hand.\n */\n ask(build: (filter: FilterExpr) => string): Promise<unknown> {\n this.#build = build;\n const answer = new Promise<unknown>((resolve, reject) => {\n // A superseded question is settled rather than dropped. The camera moves faster than DuckDB\n // answers, so the previous promise has a caller awaiting it; leaving it unsettled leaves that\n // caller's `finally` unrun and the loop reporting a query in flight for the rest of the session.\n this.#settle?.reject(SUPERSEDED);\n this.#settle = { resolve, reject };\n });\n if (this.#connected) {\n this.requestQuery();\n } else {\n // Connected on first use rather than at construction, and that is what keeps a null question\n // out of the protocol: a connected client is one the coordinator may re-query on any selection\n // change, and before the first camera question there is nothing to re-query it *with*.\n // `connect` initializes, and initializing requests a query — so the opening read is issued by\n // the coordinator's own lifecycle rather than beside it.\n this.#connected = true;\n this.#coordinator.connect(this);\n }\n return answer;\n }\n\n /**\n * Where an answer goes when nobody asked for it — which is the page's filters moving.\n *\n * Told apart from a pull by which of the two is outstanding rather than by a flag, so the two\n * cannot disagree: an answer settles the promise when one is waiting, and is reported here when\n * none is.\n */\n set onAnswer(handle: ((data: unknown) => void) | null) {\n this.#onAnswer = handle;\n }\n\n /** Let go: no more queries, and the coordinator stops walking us on every selection change. */\n release(): void {\n this.#settle?.reject(SUPERSEDED);\n this.#settle = null;\n this.#onAnswer = null;\n this.#build = null;\n if (this.#connected) {\n this.#connected = false;\n this.#coordinator.disconnect(this);\n }\n }\n\n override query(filter: FilterExpr = []): string | null {\n return this.#build?.(filter) ?? null;\n }\n\n override queryResult(data: unknown): this {\n const settle = this.#settle;\n this.#settle = null;\n if (settle) settle.resolve(data);\n else this.#onAnswer?.(data);\n return this;\n }\n\n override queryError(error: Error): this {\n const settle = this.#settle;\n this.#settle = null;\n if (settle) settle.reject(error);\n return this;\n }\n}\n"],"names":["_coordinator"],"mappings":";;;;;;;;;AAgCO;AAAqC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcxC;AAbF;AACA;AACA;AACA;AACA;AAUE;AAAoB;AACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYE;AAAO;AACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAWE;AAAyC;AAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUE;AACA;;AAIE;AAC0B;AAE5B;AAWO;AACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUE;AAAiB;AACnB;AAAA;;AAIE;AAMmC;AAErC;;AAGE;AAAgC;AAClC;;AAGE;AACA;AAGO;AACT;AAGE;AACA;AAEO;AAEX;AAjHEA;;;;"}