@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.
- package/README.md +94 -0
- package/dist/adaptive.d.ts +27 -0
- package/dist/adaptive.d.ts.map +1 -0
- package/dist/adaptive.js +25 -0
- package/dist/adaptive.js.map +1 -0
- package/dist/bounded.d.ts +314 -0
- package/dist/bounded.d.ts.map +1 -0
- package/dist/bounded.js +39 -0
- package/dist/bounded.js.map +1 -0
- package/dist/cluster-ring.d.ts +25 -0
- package/dist/cluster-ring.d.ts.map +1 -0
- package/dist/cluster-ring.js +16 -0
- package/dist/cluster-ring.js.map +1 -0
- package/dist/css-color.d.ts +11 -0
- package/dist/css-color.d.ts.map +1 -0
- package/dist/css-color.js +23 -0
- package/dist/css-color.js.map +1 -0
- package/dist/duck-source.d.ts +182 -0
- package/dist/duck-source.d.ts.map +1 -0
- package/dist/duck-source.js +413 -0
- package/dist/duck-source.js.map +1 -0
- package/dist/graph-canvas.d.ts +50 -0
- package/dist/graph-canvas.d.ts.map +1 -0
- package/dist/graph-canvas.js +35 -0
- package/dist/graph-canvas.js.map +1 -0
- package/dist/graph-looks.d.ts +146 -0
- package/dist/graph-looks.d.ts.map +1 -0
- package/dist/graph-looks.js +44 -0
- package/dist/graph-looks.js.map +1 -0
- package/dist/graph-model.d.ts +130 -0
- package/dist/graph-model.d.ts.map +1 -0
- package/dist/graph-model.js +99 -0
- package/dist/graph-model.js.map +1 -0
- package/dist/graph-sim.d.ts +40 -0
- package/dist/graph-sim.d.ts.map +1 -0
- package/dist/graph-sim.js +20 -0
- package/dist/graph-sim.js.map +1 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/memory-source.d.ts +32 -0
- package/dist/memory-source.d.ts.map +1 -0
- package/dist/memory-source.js +134 -0
- package/dist/memory-source.js.map +1 -0
- package/dist/obligations.d.ts +62 -0
- package/dist/obligations.d.ts.map +1 -0
- package/dist/resident.d.ts +79 -0
- package/dist/resident.d.ts.map +1 -0
- package/dist/resident.js +44 -0
- package/dist/resident.js.map +1 -0
- package/dist/section.d.ts +82 -0
- package/dist/section.d.ts.map +1 -0
- package/dist/section.js +142 -0
- package/dist/section.js.map +1 -0
- package/dist/slice-client.d.ts +78 -0
- package/dist/slice-client.d.ts.map +1 -0
- package/dist/slice-client.js +98 -0
- package/dist/slice-client.js.map +1 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/use-graph-look.d.ts +28 -0
- package/dist/use-graph-look.d.ts.map +1 -0
- package/dist/use-graph-look.js +28 -0
- package/dist/use-graph-look.js.map +1 -0
- package/dist/use-graph-overlays.d.ts +53 -0
- package/dist/use-graph-overlays.d.ts.map +1 -0
- package/dist/use-graph-overlays.js +96 -0
- package/dist/use-graph-overlays.js.map +1 -0
- package/dist/use-graph-selection.d.ts +59 -0
- package/dist/use-graph-selection.d.ts.map +1 -0
- package/dist/use-graph-selection.js +101 -0
- package/dist/use-graph-selection.js.map +1 -0
- package/dist/use-graph.d.ts +177 -0
- package/dist/use-graph.d.ts.map +1 -0
- package/dist/use-graph.js +101 -0
- package/dist/use-graph.js.map +1 -0
- package/dist/use-query-loop.d.ts +84 -0
- package/dist/use-query-loop.d.ts.map +1 -0
- package/dist/use-query-loop.js +151 -0
- package/dist/use-query-loop.js.map +1 -0
- package/dist/use-renderer.d.ts +86 -0
- package/dist/use-renderer.d.ts.map +1 -0
- package/dist/use-renderer.js +185 -0
- package/dist/use-renderer.js.map +1 -0
- package/dist/when-ready.d.ts +35 -0
- package/dist/when-ready.d.ts.map +1 -0
- package/dist/when-ready.js +17 -0
- package/dist/when-ready.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type Rgba = [number, number, number, number];
|
|
2
|
+
/**
|
|
3
|
+
* Any theme colour — a `var(--token)` or a literal — as GPU floats, resolved against `host`.
|
|
4
|
+
*
|
|
5
|
+
* `host` matters and is not ceremony: resolving against the element the canvas actually sits in is
|
|
6
|
+
* what lets a scoped theme override win, which resolving against `<html>` would quietly lose.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveToken(host: Element, value: string, fallback?: Rgba): Rgba;
|
|
9
|
+
/** cosmos.gl's *config* colours go through d3-color, so they have to be hex. */
|
|
10
|
+
export declare function toHex([r, g, b, a]: Rgba): string;
|
|
11
|
+
//# sourceMappingURL=css-color.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"css-color.d.ts","sourceRoot":"","sources":["../src/css-color.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AA0BpD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAE,IAAe,GAAG,IAAI,CAK1F;AAOD,gFAAgF;AAChF,wBAAgB,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM,CAEhD"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { resolveTokenColor as u } from "@kanzo-tech/ui";
|
|
2
|
+
const a = [0.7, 0.7, 0.7, 1], p = /(-?[\d.]+)/g;
|
|
3
|
+
function d(r, n) {
|
|
4
|
+
var c;
|
|
5
|
+
const t = (c = r.match(p)) == null ? void 0 : c.map(Number);
|
|
6
|
+
if (!t || t.length < 3 || t.some(Number.isNaN)) return n;
|
|
7
|
+
const [o = 0, s = 0, i = 0, m = 1] = t;
|
|
8
|
+
return [o / 255, s / 255, i / 255, m];
|
|
9
|
+
}
|
|
10
|
+
function g(r, n, t = a) {
|
|
11
|
+
if (typeof document > "u" || !n) return t;
|
|
12
|
+
const o = n.trim().replace(/^var\(\s*|\s*\)$/g, ""), s = u(r, o.startsWith("--") ? o : n);
|
|
13
|
+
return d(s, t);
|
|
14
|
+
}
|
|
15
|
+
const e = (r) => Math.round(Math.min(1, Math.max(0, r)) * 255).toString(16).padStart(2, "0");
|
|
16
|
+
function h([r, n, t, o]) {
|
|
17
|
+
return `#${e(r)}${e(n)}${e(t)}${o >= 1 ? "" : e(o)}`;
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
g as resolveToken,
|
|
21
|
+
h as toHex
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=css-color.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"css-color.js","sources":["../src/css-color.ts"],"sourcesContent":["// Theme colours as numbers a WebGL renderer can take.\n//\n// Resolution is the library's — `resolveTokenColor` from `@kanzo-tech/ui`. This file used\n// to carry its own: a 1×1 canvas painted with the colour and read back through `getImageData`. Two\n// implementations of one job, in two packages, sharing nothing and failing differently, for the\n// same underlying reason — the theme is written in `oklch()` and `color-mix()`, cosmos.gl resolves\n// through d3-color and Observable Plot through its own `isColor`, and neither knows either syntax.\n// One browser probe, one place.\n//\n// What stays here is the part that is genuinely the graph's: the library answers `rgb(r, g, b)`\n// because that is what Plot eats, and `setPointColors` wants four floats in 0..1.\n\nimport { resolveTokenColor } from \"@kanzo-tech/ui\";\n\nexport type Rgba = [number, number, number, number];\n\n/**\n * What a point wears when there is no token to resolve — not a colour this package chose.\n *\n * It cannot be `var(--muted-foreground)`: this is the path taken precisely when the token layer is\n * unreachable — `document` is undefined (a server render), or the browser handed back something\n * `parseRgb` could not read. Pointing it at another token would be pointing at the thing that just\n * failed to answer.\n *\n * So it is a mid grey, and mid is the whole of the reasoning: 0.7 sits between the light page and\n * the dark one, so a point wearing it is visible against either rather than invisible against one.\n * It is a *nothing-resolved* signal, and if it ever reaches a real screen that is a bug in the\n * caller's `host`, not a colour to tune.\n */\nconst FALLBACK: Rgba = [0.7, 0.7, 0.7, 1];\nconst CHANNELS = /(-?[\\d.]+)/g;\n\n/** `rgb(37, 99, 235)` / `rgba(37, 99, 235, 0.5)` → four floats. */\nfunction parseRgb(css: string, fallback: Rgba): Rgba {\n const parts = css.match(CHANNELS)?.map(Number);\n if (!parts || parts.length < 3 || parts.some(Number.isNaN)) return fallback;\n const [r = 0, g = 0, b = 0, a = 1] = parts;\n return [r / 255, g / 255, b / 255, a];\n}\n\n/**\n * Any theme colour — a `var(--token)` or a literal — as GPU floats, resolved against `host`.\n *\n * `host` matters and is not ceremony: resolving against the element the canvas actually sits in is\n * what lets a scoped theme override win, which resolving against `<html>` would quietly lose.\n */\nexport function resolveToken(host: Element, value: string, fallback: Rgba = FALLBACK): Rgba {\n if (typeof document === \"undefined\" || !value) return fallback;\n const token = value.trim().replace(/^var\\(\\s*|\\s*\\)$/g, \"\");\n const resolved = resolveTokenColor(host, token.startsWith(\"--\") ? token : value);\n return parseRgb(resolved, fallback);\n}\n\nconst byte = (n: number) =>\n Math.round(Math.min(1, Math.max(0, n)) * 255)\n .toString(16)\n .padStart(2, \"0\");\n\n/** cosmos.gl's *config* colours go through d3-color, so they have to be hex. */\nexport function toHex([r, g, b, a]: Rgba): string {\n return `#${byte(r)}${byte(g)}${byte(b)}${a >= 1 ? \"\" : byte(a)}`;\n}\n"],"names":["FALLBACK","CHANNELS","parseRgb","css","fallback","parts","_a","r","g","b","a","resolveToken","host","value","token","resolved","resolveTokenColor","byte","n","toHex"],"mappings":";AA6BA,MAAMA,IAAiB,CAAC,KAAK,KAAK,KAAK,CAAC,GAClCC,IAAW;AAGjB,SAASC,EAASC,GAAaC,GAAsB;;AACnD,QAAMC,KAAQC,IAAAH,EAAI,MAAMF,CAAQ,MAAlB,gBAAAK,EAAqB,IAAI;AACvC,MAAI,CAACD,KAASA,EAAM,SAAS,KAAKA,EAAM,KAAK,OAAO,KAAK,EAAG,QAAOD;AACnE,QAAM,CAACG,IAAI,GAAGC,IAAI,GAAGC,IAAI,GAAGC,IAAI,CAAC,IAAIL;AACrC,SAAO,CAACE,IAAI,KAAKC,IAAI,KAAKC,IAAI,KAAKC,CAAC;AACtC;AAQO,SAASC,EAAaC,GAAeC,GAAeT,IAAiBJ,GAAgB;AAC1F,MAAI,OAAO,WAAa,OAAe,CAACa,EAAO,QAAOT;AACtD,QAAMU,IAAQD,EAAM,KAAA,EAAO,QAAQ,qBAAqB,EAAE,GACpDE,IAAWC,EAAkBJ,GAAME,EAAM,WAAW,IAAI,IAAIA,IAAQD,CAAK;AAC/E,SAAOX,EAASa,GAAUX,CAAQ;AACpC;AAEA,MAAMa,IAAO,CAACC,MACZ,KAAK,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,GAAGA,CAAC,CAAC,IAAI,GAAG,EACzC,SAAS,EAAE,EACX,SAAS,GAAG,GAAG;AAGb,SAASC,EAAM,CAAC,GAAGX,GAAGC,GAAGC,CAAC,GAAiB;AAChD,SAAO,IAAIO,EAAK,CAAC,CAAC,GAAGA,EAAKT,CAAC,CAAC,GAAGS,EAAKR,CAAC,CAAC,GAAGC,KAAK,IAAI,KAAKO,EAAKP,CAAC,CAAC;AAChE;"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { Coordinator, Selection } from '@kanzo-tech/mosaic';
|
|
2
|
+
import { BoundedSource } from './bounded';
|
|
3
|
+
import { VertexId } from './resident';
|
|
4
|
+
/**
|
|
5
|
+
* A `BoundedSource` over two ordinary relations in DuckDB.
|
|
6
|
+
*
|
|
7
|
+
* On a subpath because Mosaic is an optional peer and this is the half that needs it: a host drawing
|
|
8
|
+
* arrays it already holds takes `memorySource` and pays for no database. Splitting them is what lets
|
|
9
|
+
* that promise be true rather than merely stated.
|
|
10
|
+
*
|
|
11
|
+
* **Neutral about storage, and that is the point.** The neutrality let bounded be measured against
|
|
12
|
+
* unbounded before anything committed to a layout on disk — and it is the reason this file survived
|
|
13
|
+
* a decision on the other side of the seam. The verb it was written to sit beside never landed:
|
|
14
|
+
* `viewport` was dropped and GraphAr with it, because the camera is addressed rather than queried.
|
|
15
|
+
* What replaces it is a tile fetched by a computed URL, which is another source.
|
|
16
|
+
*
|
|
17
|
+
* **Every query in this file goes through a `SliceRead`, and there is no other path.** `onceQuery`
|
|
18
|
+
* was the other one — a throwaway client per query, on this subpath, re-exported for the two
|
|
19
|
+
* showcases that also read a relation directly. It is gone: a read the page's filters cannot reach
|
|
20
|
+
* is a picture that disagrees with the page, and `slice-client.ts` carries what that cost.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* A DuckDB-backed source, and the one thing it can do that the render contract knows nothing about.
|
|
24
|
+
*
|
|
25
|
+
* `BoundedSource` says what a renderer needs: answer a bounded question. Publishing a selection is
|
|
26
|
+
* the other direction of the same seam and it is Mosaic's, not the renderer's — so it lives on the
|
|
27
|
+
* concrete type rather than on the contract, beside `watch`, which is on the contract because the
|
|
28
|
+
* query loop is what has to act on it.
|
|
29
|
+
*/
|
|
30
|
+
export interface DuckSource extends BoundedSource {
|
|
31
|
+
/**
|
|
32
|
+
* The reader's own selection, as a clause the rest of the page filters by. `null` retracts it.
|
|
33
|
+
*
|
|
34
|
+
* **The graph is exempt from its own clause, and that is the whole difference from the greyout it
|
|
35
|
+
* replaces.** While the canvas *faded* excluded rows it could take its own clause too — the row was
|
|
36
|
+
* still drawn and still selectable, and the fade was the brush. A canvas that now draws what
|
|
37
|
+
* survives would answer a lasso by deleting everything the reader did not lasso, which is not a
|
|
38
|
+
* selection, it is a filter nobody asked for.
|
|
39
|
+
*/
|
|
40
|
+
publish(vertices: readonly VertexId[] | null): void;
|
|
41
|
+
}
|
|
42
|
+
export interface DuckSourceOptions {
|
|
43
|
+
coordinator: Coordinator;
|
|
44
|
+
/**
|
|
45
|
+
* The crossfilter this graph draws inside.
|
|
46
|
+
*
|
|
47
|
+
* Given, the page's predicate rides in the slice query and the canvas draws **what survives**.
|
|
48
|
+
* Omitted, the source is a reader of a relation and nothing else — which is what a graph with no
|
|
49
|
+
* charts beside it is.
|
|
50
|
+
*/
|
|
51
|
+
filterBy?: Selection;
|
|
52
|
+
/** The node relation. */
|
|
53
|
+
nodes: string;
|
|
54
|
+
/** The edge relation, as pairs of node ids. */
|
|
55
|
+
edges: string;
|
|
56
|
+
/**
|
|
57
|
+
* Which vertex type this relation is.
|
|
58
|
+
*
|
|
59
|
+
* Required, and with no default, because the source is the only thing that knows: a `dense_id`
|
|
60
|
+
* numbers within one type, so the identity a slice carries is only completed here. A corpus of one
|
|
61
|
+
* type is type `0` and has to say so — a defaulted `0` would let a second relation ship the same
|
|
62
|
+
* identities as the first with nothing raised.
|
|
63
|
+
*/
|
|
64
|
+
typeIndex: number;
|
|
65
|
+
/**
|
|
66
|
+
* A **dense** integer id — `0..n-1`, no gaps.
|
|
67
|
+
*
|
|
68
|
+
* Dense because `links` refers to positions rather than to ids, so a consumer never pays for an
|
|
69
|
+
* id→index map. GraphAr's `dense_id` is this column by another name.
|
|
70
|
+
*/
|
|
71
|
+
idField?: string;
|
|
72
|
+
/**
|
|
73
|
+
* The identity column — the subject IRI. **Omitted, a slice carries addresses only.**
|
|
74
|
+
*
|
|
75
|
+
* A `dense_id` says where a vertex is; the IRI says which vertex it is, and only the second
|
|
76
|
+
* survives the layout being redone. The corpus writes it as `subject`, non-null and unique within
|
|
77
|
+
* a type, which is why that is the name here — but it stays opt-in rather than defaulted, because
|
|
78
|
+
* reading it costs 1.87× the drawing tile and most points are painted rather than named.
|
|
79
|
+
*
|
|
80
|
+
* Set it when something outlives a session: a bookmark, a link out, a selection that has to mean
|
|
81
|
+
* the same thing after the next `fossil run`.
|
|
82
|
+
*/
|
|
83
|
+
subjectField?: string;
|
|
84
|
+
xField?: string;
|
|
85
|
+
yField?: string;
|
|
86
|
+
/**
|
|
87
|
+
* **What colours and what sizes are not here**, and their absence is the shape rather than an
|
|
88
|
+
* omission: they are `fill` and `r` on the request, because a channel is what the caller wants
|
|
89
|
+
* drawn now and this object is where the bytes are. Given here too, recolouring meant building a
|
|
90
|
+
* second source — and two places deciding one colour is the state that move ended.
|
|
91
|
+
*/
|
|
92
|
+
sourceField?: string;
|
|
93
|
+
targetField?: string;
|
|
94
|
+
}
|
|
95
|
+
export declare function duckBoundedSource(options: DuckSourceOptions): DuckSource;
|
|
96
|
+
/**
|
|
97
|
+
* A corpus that fossil wrote, read by address.
|
|
98
|
+
*
|
|
99
|
+
* **The five things a call site used to know, and now does not.** Drawing a corpus meant deriving
|
|
100
|
+
* the chunk URLs from a `chunk_size` copied by hand, knowing how a tile is named, knowing what the
|
|
101
|
+
* edge directory is called, knowing GraphAr's column names, and knowing that a glob cannot work over
|
|
102
|
+
* a plain HTTP origin because there is no listing. Five conventions and about forty lines, none of
|
|
103
|
+
* it the business of something that wants to draw a graph. `/docs/design/graph` carries the
|
|
104
|
+
* argument; the copied `chunk_size` carries the evidence, because it went stale and read
|
|
105
|
+
* a fraction of a corpus in silence for as long as it did.
|
|
106
|
+
*
|
|
107
|
+
* The consumer knows one thing: **where the corpus is.**
|
|
108
|
+
*
|
|
109
|
+
* ```ts
|
|
110
|
+
* const { source } = await openCorpus({ coordinator, dest: "/bench/1000000" });
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* **Addressed, not queried.** The manifest and the per-tile boxes are read once and kept; after that
|
|
114
|
+
* a camera move is arithmetic over boxes and a list of URLs. There is deliberately no request on the
|
|
115
|
+
* path between the camera moving and a URL being computable — the moment there is one, this has
|
|
116
|
+
* become the `viewport` verb fossil deleted.
|
|
117
|
+
*
|
|
118
|
+
* **What it is not.** It takes no column names and no type index. Those come from the manifest or
|
|
119
|
+
* they do not come: a corpus reader that also accepts `idField` is `duckBoundedSource` with extra
|
|
120
|
+
* steps, and there is already one of those for the case this is not — an arbitrary relation with
|
|
121
|
+
* `x`/`y` that nobody wrote as a corpus.
|
|
122
|
+
*/
|
|
123
|
+
export interface OpenCorpusOptions {
|
|
124
|
+
coordinator: Coordinator;
|
|
125
|
+
/**
|
|
126
|
+
* The crossfilter this graph draws inside — the same `Selection` the page's charts filter by.
|
|
127
|
+
*
|
|
128
|
+
* Given, the predicate rides in the slice query and the canvas draws what survives.
|
|
129
|
+
*/
|
|
130
|
+
filterBy?: Selection;
|
|
131
|
+
/** Where the corpus lives, without a trailing slash — the directory holding `graph.graph.yml`. */
|
|
132
|
+
dest: string;
|
|
133
|
+
/**
|
|
134
|
+
* Which vertex type to draw, when a corpus carries more than one.
|
|
135
|
+
*
|
|
136
|
+
* Defaults to the first the manifest names. A corpus of one type never passes it; a corpus of
|
|
137
|
+
* several has to, because *which graph do you mean* is not a question a reader can answer.
|
|
138
|
+
*/
|
|
139
|
+
vertexType?: string;
|
|
140
|
+
/**
|
|
141
|
+
* Read the identity column as well. **Off by default, and the same trade as everywhere else:**
|
|
142
|
+
* `subject` costs about twice the drawing tile, so the drawing path carries addresses and a host
|
|
143
|
+
* asks for names when something has to be *named* rather than painted.
|
|
144
|
+
*/
|
|
145
|
+
subjects?: boolean;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* An opened corpus: the half that **draws** and the half that **answers**.
|
|
149
|
+
*
|
|
150
|
+
* A host needs both over the same bytes and they are not the same access. The canvas reads tiles by
|
|
151
|
+
* address — a handful of files per camera move, chosen from the footer's boxes, with no query. A
|
|
152
|
+
* chart, a crossfilter clause or a verb reads the *relation*: every row, by column name, in SQL.
|
|
153
|
+
* Hiding the URLs behind `source` is right for the first and leaves the second with nothing to
|
|
154
|
+
* query, so opening a corpus registers views for it.
|
|
155
|
+
*
|
|
156
|
+
* This is the other side's own shape. `fossil-mcp` describes itself as opening a dataset,
|
|
157
|
+
* *registering views over the Parquet the corpus already holds*, and dispatching a verb — the same
|
|
158
|
+
* two halves, named the same way, one call apart.
|
|
159
|
+
*/
|
|
160
|
+
export interface OpenedCorpus {
|
|
161
|
+
/**
|
|
162
|
+
* For the canvas: `<GraphCanvas source={…}>`. Reads tiles, never the whole relation.
|
|
163
|
+
*
|
|
164
|
+
* A `DuckSource`, and `CorpusSource` is gone with the reason it existed: `extent()` was declared
|
|
165
|
+
* there because a source over an unlaid-out relation has no answer to it, and *optional on the
|
|
166
|
+
* base contract* says that better than a second interface — a relation with `x`/`y` has an extent
|
|
167
|
+
* too, and it was the one host that could not frame its opening view.
|
|
168
|
+
*/
|
|
169
|
+
source: DuckSource;
|
|
170
|
+
/**
|
|
171
|
+
* The vertex relation, registered and ready to query by name.
|
|
172
|
+
*
|
|
173
|
+
* Every column the manifest declares, including the corpus' own properties — so a clause a chart
|
|
174
|
+
* publishes over `kind` or `region` lands here with no translation, which is what makes one
|
|
175
|
+
* crossfilter serve the canvas and the charts.
|
|
176
|
+
*/
|
|
177
|
+
nodes: string;
|
|
178
|
+
/** The source-ordered edge relation, or `undefined` when the corpus declares no edge for this type. */
|
|
179
|
+
edges: string | undefined;
|
|
180
|
+
}
|
|
181
|
+
export declare function openCorpus(options: OpenCorpusOptions): Promise<OpenedCorpus>;
|
|
182
|
+
//# sourceMappingURL=duck-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duck-source.d.ts","sourceRoot":"","sources":["../src/duck-source.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAc,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC7E,OAAO,EAAgC,KAAK,aAAa,EAAgD,MAAM,WAAW,CAAC;AAE3H,OAAO,EAA6B,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtE;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,UAAW,SAAQ,aAAa;IAC/C;;;;;;;;OAQG;IACH,OAAO,CAAC,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,WAAW,CAAC;IACzB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAuFD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAmExE;AAmdD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,WAAW,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,kGAAkG;IAClG,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAyCD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;OAOG;IACH,MAAM,EAAE,UAAU,CAAC;IACnB;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,uGAAuG;IACvG,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,YAAY,CAAC,CA+WlF"}
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { numbers as k, clausePoints as yt, fillColumn as _, column as $t } from "@kanzo-tech/mosaic";
|
|
3
|
+
import { SUPERSEDED as pt, BOUNDED_DEFAULTS as xt } from "./bounded.js";
|
|
4
|
+
import { SliceRead as z } from "./slice-client.js";
|
|
5
|
+
import { typeOf as St, denseOf as Z, vertexId as At } from "./resident.js";
|
|
6
|
+
function tt(t, e) {
|
|
7
|
+
return {
|
|
8
|
+
points: new z(t, e),
|
|
9
|
+
links: new z(t, e),
|
|
10
|
+
meta: new z(t)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function et(t) {
|
|
14
|
+
let e = Promise.resolve();
|
|
15
|
+
return (n) => {
|
|
16
|
+
const s = e.then(() => t.ask(() => n));
|
|
17
|
+
return e = s.catch(() => {
|
|
18
|
+
}), s;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function K(t) {
|
|
22
|
+
if (t == null) return "";
|
|
23
|
+
const n = (Array.isArray(t) ? t : [t]).filter((s) => s != null).map((s) => String(s));
|
|
24
|
+
return n.length > 0 ? n.map((s) => `(${s})`).join(" AND ") : "";
|
|
25
|
+
}
|
|
26
|
+
function nt(t, e) {
|
|
27
|
+
return t ? e ? `(${t}) AND (${e})` : t : e || "TRUE";
|
|
28
|
+
}
|
|
29
|
+
function _t(t) {
|
|
30
|
+
const { coordinator: e, edges: n, filterBy: s, nodes: i, typeIndex: r } = t, c = {
|
|
31
|
+
id: t.idField ?? "id",
|
|
32
|
+
subject: t.subjectField,
|
|
33
|
+
x: t.xField ?? "x",
|
|
34
|
+
y: t.yField ?? "y",
|
|
35
|
+
source: t.sourceField ?? "source",
|
|
36
|
+
target: t.targetField ?? "target"
|
|
37
|
+
}, l = tt(e, s), m = et(l.meta), S = rt(l);
|
|
38
|
+
return {
|
|
39
|
+
...S.api,
|
|
40
|
+
publish(y) {
|
|
41
|
+
it(l, s, c.id, y);
|
|
42
|
+
},
|
|
43
|
+
async total() {
|
|
44
|
+
const y = await m(`SELECT count(*) AS n FROM ${i}`);
|
|
45
|
+
return Number(k(y, "n")[0] ?? 0);
|
|
46
|
+
},
|
|
47
|
+
/**
|
|
48
|
+
* Four aggregates, so the canvas can frame what is actually there.
|
|
49
|
+
*
|
|
50
|
+
* One scan of two columns, once — against a first paint that otherwise opens on the renderer's
|
|
51
|
+
* default box and finds the corpus occupying a corner of it.
|
|
52
|
+
*/
|
|
53
|
+
async extent() {
|
|
54
|
+
const y = await m(
|
|
55
|
+
`SELECT min(${c.x}) AS x0, max(${c.x}) AS x1,
|
|
56
|
+
min(${c.y}) AS y0, max(${c.y}) AS y1
|
|
57
|
+
FROM ${i}`
|
|
58
|
+
), p = (A) => Number(k(y, A)[0] ?? 0);
|
|
59
|
+
return { xMin: p("x0"), yMin: p("y0"), xMax: p("x1"), yMax: p("y1") };
|
|
60
|
+
},
|
|
61
|
+
/**
|
|
62
|
+
* Regions only, and it says so by **not having** `explore`.
|
|
63
|
+
*
|
|
64
|
+
* This source is two relations and a spatial predicate — it has no adjacency index, so a
|
|
65
|
+
* neighbourhood query would mean recursive joins over the whole edge table, which is the
|
|
66
|
+
* unbounded pattern wearing a bounded interface. It used to say that with a predicate and a
|
|
67
|
+
* throw; now the absence is the statement, and asking is a compile error.
|
|
68
|
+
*/
|
|
69
|
+
async slice(y) {
|
|
70
|
+
const { fill: p, limit: A, perPixel: h, pinned: x, r: g, view: f } = y, L = { ...c, category: p, size: g };
|
|
71
|
+
return S.run(
|
|
72
|
+
at(i, n, L, f, A, r, x, h, void 0)
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function ht(t, e) {
|
|
78
|
+
const s = [
|
|
79
|
+
[t.x, e.xMin, ">="],
|
|
80
|
+
[t.x, e.xMax, "<="],
|
|
81
|
+
[t.y, e.yMin, ">="],
|
|
82
|
+
[t.y, e.yMax, "<="]
|
|
83
|
+
].filter(([, i]) => Number.isFinite(i)).map(([i, r, c]) => `${i} ${c} ${r}`);
|
|
84
|
+
return s.length > 0 ? s.join(" AND ") : "TRUE";
|
|
85
|
+
}
|
|
86
|
+
const ft = (t) => `greatest(1, CAST(ceil(matched / ${t}.0) AS BIGINT))`;
|
|
87
|
+
function W(t, e, n, s, i = !0) {
|
|
88
|
+
const r = e.size ? `, ${e.size} AS size` : "", c = e.subject ? `, ${e.subject} AS subject` : "", l = e.category ? "(dense_rank() OVER (ORDER BY cat) - 1)::INTEGER AS category" : "0::INTEGER AS category";
|
|
89
|
+
return `WITH pool AS (
|
|
90
|
+
SELECT ${e.id} AS id, ${e.x} AS x, ${e.y} AS y${r}${c}${e.category ? `, ${e.category} AS cat` : ""},
|
|
91
|
+
count(*) OVER () AS matched
|
|
92
|
+
FROM ${t}
|
|
93
|
+
WHERE ${n}
|
|
94
|
+
), vis AS (
|
|
95
|
+
SELECT id, x, y${e.size ? ", size" : ""}${e.subject ? ", subject" : ""}${i ? ", matched" : ""},
|
|
96
|
+
${l},
|
|
97
|
+
(row_number() OVER (ORDER BY id) - 1)::INTEGER AS local
|
|
98
|
+
FROM pool
|
|
99
|
+
WHERE id % ${ft(s)} = 0
|
|
100
|
+
LIMIT ${s}
|
|
101
|
+
)`;
|
|
102
|
+
}
|
|
103
|
+
function st(t, e, n) {
|
|
104
|
+
if (n === void 0 || !Number.isFinite(n) || n <= 0) return "";
|
|
105
|
+
const s = xt.minLinkPixels * n;
|
|
106
|
+
return `(${t}.x - ${e}.x) * (${t}.x - ${e}.x) + (${t}.y - ${e}.y) * (${t}.y - ${e}.y) >= ${s * s}`;
|
|
107
|
+
}
|
|
108
|
+
function Nt(t, e, n, s, i, r) {
|
|
109
|
+
const c = nt(`NOT (${s})`, i), l = st("a", "b", r);
|
|
110
|
+
return `, out AS (
|
|
111
|
+
SELECT ${n.id} AS id, ${n.x} AS x, ${n.y} AS y FROM ${t} WHERE ${c}
|
|
112
|
+
), reach AS (
|
|
113
|
+
SELECT id, x, y FROM vis UNION ALL SELECT id, x, y FROM out
|
|
114
|
+
), span AS (
|
|
115
|
+
SELECT sv.local AS src, tv.local AS dst, a.id AS src_id, b.id AS dst_id
|
|
116
|
+
FROM ${e} e
|
|
117
|
+
JOIN reach a ON e.${n.source} = a.id
|
|
118
|
+
JOIN reach b ON e.${n.target} = b.id
|
|
119
|
+
LEFT JOIN vis sv ON sv.id = a.id
|
|
120
|
+
LEFT JOIN vis tv ON tv.id = b.id
|
|
121
|
+
WHERE (sv.id IS NOT NULL OR tv.id IS NOT NULL)${l ? ` AND ${l}` : ""}
|
|
122
|
+
), anchor AS (
|
|
123
|
+
SELECT o.id, o.x, o.y,
|
|
124
|
+
((SELECT count(*) FROM vis) + row_number() OVER (ORDER BY o.id) - 1)::INTEGER AS local
|
|
125
|
+
FROM out o
|
|
126
|
+
WHERE o.id IN (SELECT src_id FROM span WHERE src IS NULL
|
|
127
|
+
UNION SELECT dst_id FROM span WHERE dst IS NULL)
|
|
128
|
+
)`;
|
|
129
|
+
}
|
|
130
|
+
function at(t, e, n, s, i, r, c, l, m) {
|
|
131
|
+
const S = ht(n, s), y = (c ?? []).filter((u) => St(u) === r).map(Z), p = y.length > 0 ? ` OR ${n.id} IN (${y.join(",")})` : "", A = `(${S})${p}`, h = (u) => nt(A, K(u)), x = n.size ? ", size" : "", g = n.subject ? ", subject" : "", f = st("s", "t", l), L = (u) => m ? Nt(m, e, n, A, K(u), l) : "";
|
|
132
|
+
return {
|
|
133
|
+
/**
|
|
134
|
+
* The marks, then the anchors, in one answer — because they are one buffer.
|
|
135
|
+
*
|
|
136
|
+
* `ORDER BY local` is load-bearing rather than tidy: `local` runs `0..marks-1` over the sample
|
|
137
|
+
* and continues past it over the anchors, so ordering by it puts every mark before every anchor
|
|
138
|
+
* and makes `marks` a prefix length. `matched` is then still read off row zero.
|
|
139
|
+
*/
|
|
140
|
+
points: (u) => `${W(t, n, h(u), i)}${L(u)}
|
|
141
|
+
SELECT local, id, x, y, category, matched, TRUE AS mark${x}${g} FROM vis${m ? `
|
|
142
|
+
UNION ALL
|
|
143
|
+
SELECT local, id, x, y, 0::INTEGER, NULL::BIGINT, FALSE AS mark${n.size ? ", CAST(NULL AS DOUBLE)" : ""}${n.subject ? ", CAST(NULL AS VARCHAR)" : ""} FROM anchor` : ""}
|
|
144
|
+
ORDER BY local`,
|
|
145
|
+
/**
|
|
146
|
+
* One end drawn and both ends positioned — or, where nothing is held, both ends drawn.
|
|
147
|
+
*
|
|
148
|
+
* The second form is what a source over an ordinary relation gets, and it is the old query plus
|
|
149
|
+
* the length predicate. It still drops an edge that leaves the window, and the reason is now
|
|
150
|
+
* exact rather than a limitation of the reader: no bytes beyond the rectangle were fetched, so
|
|
151
|
+
* there is no position to draw the far end at. A corpus fetches tiles and therefore has some.
|
|
152
|
+
*/
|
|
153
|
+
links: (u) => m ? `${W(t, n, h(u), i, !1)}${L(u)}
|
|
154
|
+
SELECT coalesce(sp.src, sa.local) AS src, coalesce(sp.dst, da.local) AS dst
|
|
155
|
+
FROM span sp
|
|
156
|
+
LEFT JOIN anchor sa ON sa.id = sp.src_id
|
|
157
|
+
LEFT JOIN anchor da ON da.id = sp.dst_id` : `${W(t, n, h(u), i, !1)}
|
|
158
|
+
SELECT s.local AS src, t.local AS dst
|
|
159
|
+
FROM ${e} e
|
|
160
|
+
JOIN vis s ON e.${n.source} = s.id
|
|
161
|
+
JOIN vis t ON e.${n.target} = t.id${f ? `
|
|
162
|
+
WHERE ${f}` : ""}`,
|
|
163
|
+
assemble: (u, C) => ({
|
|
164
|
+
/**
|
|
165
|
+
* How many matched, separately from how many came back.
|
|
166
|
+
*
|
|
167
|
+
* Without it the view cannot tell a reader "there is more here than I am showing you", and a
|
|
168
|
+
* truncated slice looks exactly like a complete one — which is the failure this whole branch
|
|
169
|
+
* has been about. Read off the first row rather than asked for: `matched` is constant down the
|
|
170
|
+
* column, and an empty answer has no row and no matches, which agree.
|
|
171
|
+
*/
|
|
172
|
+
n: Number(k(u, "matched")[0] ?? 0),
|
|
173
|
+
...gt(u, C, r, n.size ? "size" : void 0, n.subject !== void 0)
|
|
174
|
+
})
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function rt(t) {
|
|
178
|
+
let e = null, n = null;
|
|
179
|
+
const s = /* @__PURE__ */ new Map(), i = (r) => (c) => {
|
|
180
|
+
if (!e || !n || (s.set(r, c), s.size < 2)) return;
|
|
181
|
+
const l = s.get("points"), m = s.get("links");
|
|
182
|
+
s.clear(), n(e.assemble(l, m));
|
|
183
|
+
};
|
|
184
|
+
return {
|
|
185
|
+
api: {
|
|
186
|
+
/**
|
|
187
|
+
* Say when the answer changes for a reason the camera cannot see.
|
|
188
|
+
*
|
|
189
|
+
* The reason it hands over a whole `Slice` rather than a nudge to ask again: the coordinator
|
|
190
|
+
* has *already* re-run both reads with the new predicate by the time we hear about it. Asking
|
|
191
|
+
* again would run the same two queries a second time to learn what is in hand.
|
|
192
|
+
*/
|
|
193
|
+
watch(r) {
|
|
194
|
+
return n = r, t.points.onAnswer = i("points"), t.links.onAnswer = i("links"), () => {
|
|
195
|
+
n = null, s.clear(), e = null, t.points.release(), t.links.release(), t.meta.release();
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
async run(r) {
|
|
200
|
+
e = r, s.clear();
|
|
201
|
+
const [c, l] = await Promise.all([
|
|
202
|
+
t.points.ask(r.points),
|
|
203
|
+
t.links.ask(r.links)
|
|
204
|
+
]);
|
|
205
|
+
return r.assemble(c, l);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function it(t, e, n, s) {
|
|
210
|
+
e && e.update(
|
|
211
|
+
yt([n], s == null ? void 0 : s.map((i) => [Z(i)]), {
|
|
212
|
+
source: t.points,
|
|
213
|
+
clients: /* @__PURE__ */ new Set([t.points, t.links])
|
|
214
|
+
})
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
function gt(t, e, n, s, i = !1) {
|
|
218
|
+
const r = Q(t, "x"), c = new Float32Array(r * 2), l = _(t, "x", c, 0, 2);
|
|
219
|
+
_(t, "y", c, 1, 2);
|
|
220
|
+
const m = new Int32Array(r);
|
|
221
|
+
_(t, "mark", m);
|
|
222
|
+
let S = 0;
|
|
223
|
+
for (; S < l && m[S] !== 0; ) S++;
|
|
224
|
+
const y = new Float64Array(l);
|
|
225
|
+
_(t, "id", y);
|
|
226
|
+
const p = new BigUint64Array(l);
|
|
227
|
+
for (let u = 0; u < l; u++) p[u] = At(n, y[u]);
|
|
228
|
+
const A = new Uint16Array(l);
|
|
229
|
+
_(t, "category", A);
|
|
230
|
+
const h = i ? $t(t, "subject") : void 0;
|
|
231
|
+
let x;
|
|
232
|
+
s && (x = new Float32Array(l), _(t, s, x));
|
|
233
|
+
const g = Q(e, "src"), f = new Float32Array(g * 2), L = _(e, "src", f, 0, 2);
|
|
234
|
+
return _(e, "dst", f, 1, 2), {
|
|
235
|
+
marks: S,
|
|
236
|
+
vertices: p,
|
|
237
|
+
subjects: h,
|
|
238
|
+
positions: c.subarray(0, l * 2),
|
|
239
|
+
links: f.subarray(0, L * 2),
|
|
240
|
+
categories: A,
|
|
241
|
+
sizes: x
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function Q(t, e) {
|
|
245
|
+
var i;
|
|
246
|
+
const n = t == null ? void 0 : t.numRows;
|
|
247
|
+
if (typeof n == "number") return n;
|
|
248
|
+
const s = (i = t == null ? void 0 : t.getChild) == null ? void 0 : i.call(t, e);
|
|
249
|
+
return s ? s.length : Array.from(t).length;
|
|
250
|
+
}
|
|
251
|
+
function I(t, e) {
|
|
252
|
+
const n = t.split(`
|
|
253
|
+
`).find((s) => s.startsWith(`${e}:`));
|
|
254
|
+
return n == null ? void 0 : n.slice(e.length + 1).trim().replace(/^['"]|['"]$/g, "");
|
|
255
|
+
}
|
|
256
|
+
function X(t, e) {
|
|
257
|
+
const n = t.split(`
|
|
258
|
+
`), s = n.findIndex((r) => r.startsWith(`${e}:`));
|
|
259
|
+
if (s < 0) return [];
|
|
260
|
+
const i = [];
|
|
261
|
+
for (const r of n.slice(s + 1)) {
|
|
262
|
+
if (!r.startsWith("- ")) break;
|
|
263
|
+
i.push(r.slice(2).trim());
|
|
264
|
+
}
|
|
265
|
+
return i;
|
|
266
|
+
}
|
|
267
|
+
async function q(t, e) {
|
|
268
|
+
const n = await fetch(`${t}/${e}`);
|
|
269
|
+
if (!n.ok) throw new Error(`corpus: ${e} is not readable (${n.status})`);
|
|
270
|
+
return n.text();
|
|
271
|
+
}
|
|
272
|
+
async function Lt(t) {
|
|
273
|
+
const { coordinator: e, dest: n, filterBy: s, subjects: i = !1, vertexType: r } = t, c = tt(e, s), l = et(c.meta), m = rt(c), S = await q(n, "graph.graph.yml"), y = X(S, "vertices"), p = X(S, "edges"), A = await Promise.all(y.map((a) => q(n, a))), h = r === void 0 ? A[0] : A.find((a) => I(a, "type") === r);
|
|
274
|
+
if (!h)
|
|
275
|
+
throw new Error(
|
|
276
|
+
`corpus: no vertex type ${r ?? "(none declared)"} in ${n}/graph.graph.yml`
|
|
277
|
+
);
|
|
278
|
+
const x = I(h, "type"), g = I(h, "prefix"), f = Number(I(h, "chunk_size"));
|
|
279
|
+
if (!x || !g || !Number.isFinite(f) || f <= 0)
|
|
280
|
+
throw new Error(`corpus: ${n} declares no usable type, prefix and chunk_size`);
|
|
281
|
+
const u = (await Promise.all(p.map((a) => q(n, a)))).find((a) => I(a, "src_type") === x), C = u ? I(u, "prefix") : void 0;
|
|
282
|
+
let P = null, G;
|
|
283
|
+
const U = (a) => `'${n}/${g}chunk${a}.parquet'`, ot = (a) => `'${n}/${C}by_source/tile${a}.parquet'`, ct = async () => {
|
|
284
|
+
var o;
|
|
285
|
+
const a = (o = e.databaseConnector) == null ? void 0 : o.call(e);
|
|
286
|
+
if (!(a != null && a.getDuckDB)) return null;
|
|
287
|
+
try {
|
|
288
|
+
return await a.getDuckDB();
|
|
289
|
+
} catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}, D = /* @__PURE__ */ new Map();
|
|
293
|
+
let H = 0;
|
|
294
|
+
const lt = 64 * 1024 * 1024, ut = 256 * 1024;
|
|
295
|
+
async function V(a, o) {
|
|
296
|
+
const d = a === "vertex" ? U : ot, M = await ct();
|
|
297
|
+
if (!M) return o.map(d);
|
|
298
|
+
const v = await Promise.all(
|
|
299
|
+
o.map(async (E) => {
|
|
300
|
+
const $ = `corpus_${x}_${a}_${E}.parquet`;
|
|
301
|
+
if (D.has($)) return $;
|
|
302
|
+
const F = d(E).slice(1, -1), w = await fetch(F, { method: "HEAD" }), T = Number(w.headers.get("content-length"));
|
|
303
|
+
if (!w.ok || !Number.isFinite(T) || T > ut) return d(E);
|
|
304
|
+
const R = await fetch(F);
|
|
305
|
+
if (!R.ok) return d(E);
|
|
306
|
+
const O = new Uint8Array(await R.arrayBuffer());
|
|
307
|
+
return await M.registerFileBuffer($, O), D.set($, O.byteLength), H += O.byteLength, $;
|
|
308
|
+
})
|
|
309
|
+
);
|
|
310
|
+
for (; H > lt && D.size > 0; ) {
|
|
311
|
+
const [E, $] = D.entries().next().value;
|
|
312
|
+
if (v.includes(E)) break;
|
|
313
|
+
D.delete(E), H -= $, await M.dropFile(E);
|
|
314
|
+
}
|
|
315
|
+
return v.map((E) => E.startsWith("'") ? E : `'${E}'`);
|
|
316
|
+
}
|
|
317
|
+
function j() {
|
|
318
|
+
return P ?? (P = (async () => {
|
|
319
|
+
const a = async (N) => (await fetch(`${n}/${g}chunk${N}.parquet`, { method: "HEAD" })).ok;
|
|
320
|
+
if (!await a(0)) throw new Error(`corpus: ${n}/${g} holds no chunk0`);
|
|
321
|
+
let o = 0, d = 1;
|
|
322
|
+
for (; await a(d); )
|
|
323
|
+
o = d, d *= 2;
|
|
324
|
+
for (; d - o > 1; ) {
|
|
325
|
+
const N = Math.floor((o + d) / 2);
|
|
326
|
+
await a(N) ? o = N : d = N;
|
|
327
|
+
}
|
|
328
|
+
const M = o + 1, v = await l(
|
|
329
|
+
`SELECT num_rows AS n FROM parquet_file_metadata('${U(o).slice(1, -1)}')`
|
|
330
|
+
);
|
|
331
|
+
G = o * f + Number(k(v, "n")[0] ?? 0);
|
|
332
|
+
const E = Array.from({ length: M }, (N, b) => U(b)).join(", "), $ = await l(
|
|
333
|
+
`SELECT CAST(regexp_extract(file_name, 'chunk(\\d+)', 1) AS INTEGER) AS tile,
|
|
334
|
+
min(CASE WHEN path_in_schema = 'x' THEN coalesce(stats_min_value, stats_min)::DOUBLE END) AS x0,
|
|
335
|
+
max(CASE WHEN path_in_schema = 'x' THEN coalesce(stats_max_value, stats_max)::DOUBLE END) AS x1,
|
|
336
|
+
min(CASE WHEN path_in_schema = 'y' THEN coalesce(stats_min_value, stats_min)::DOUBLE END) AS y0,
|
|
337
|
+
max(CASE WHEN path_in_schema = 'y' THEN coalesce(stats_max_value, stats_max)::DOUBLE END) AS y1
|
|
338
|
+
FROM parquet_metadata([${E}])
|
|
339
|
+
WHERE path_in_schema IN ('x', 'y') GROUP BY 1 ORDER BY 1`
|
|
340
|
+
), F = k($, "tile"), [w, T, R, O] = ["x0", "x1", "y0", "y1"].map((N) => k($, N));
|
|
341
|
+
return F.map((N, b) => ({
|
|
342
|
+
tile: N,
|
|
343
|
+
x0: w == null ? void 0 : w[b],
|
|
344
|
+
x1: T == null ? void 0 : T[b],
|
|
345
|
+
y0: R == null ? void 0 : R[b],
|
|
346
|
+
y1: O == null ? void 0 : O[b]
|
|
347
|
+
}));
|
|
348
|
+
})()), P;
|
|
349
|
+
}
|
|
350
|
+
function dt(a, o) {
|
|
351
|
+
return a.filter((d) => d.x1 >= o.xMin && d.x0 <= o.xMax && d.y1 >= o.yMin && d.y0 <= o.yMax).map((d) => d.tile);
|
|
352
|
+
}
|
|
353
|
+
const J = {
|
|
354
|
+
id: "dense_id",
|
|
355
|
+
subject: i ? "subject" : void 0,
|
|
356
|
+
x: "x",
|
|
357
|
+
y: "y",
|
|
358
|
+
source: "src_dense",
|
|
359
|
+
target: "dst_dense"
|
|
360
|
+
}, Y = `corpus_${x}`, B = C ? `corpus_${x}_edges` : void 0, Et = async () => (await j()).map((o) => U(o.tile)).join(", ");
|
|
361
|
+
return await e.exec(
|
|
362
|
+
`CREATE OR REPLACE VIEW ${Y} AS SELECT * FROM read_parquet([${await Et()}])`
|
|
363
|
+
), B && await e.exec(
|
|
364
|
+
`CREATE OR REPLACE VIEW ${B} AS
|
|
365
|
+
SELECT * FROM read_parquet('${n}/${C}by_source.parquet')`
|
|
366
|
+
), { source: {
|
|
367
|
+
...m.api,
|
|
368
|
+
publish(a) {
|
|
369
|
+
it(c, s, J.id, a);
|
|
370
|
+
},
|
|
371
|
+
async total() {
|
|
372
|
+
return await j(), G ?? 0;
|
|
373
|
+
},
|
|
374
|
+
async extent() {
|
|
375
|
+
const a = await j();
|
|
376
|
+
return {
|
|
377
|
+
xMin: Math.min(...a.map((o) => o.x0)),
|
|
378
|
+
yMin: Math.min(...a.map((o) => o.y0)),
|
|
379
|
+
xMax: Math.max(...a.map((o) => o.x1)),
|
|
380
|
+
yMax: Math.max(...a.map((o) => o.y1))
|
|
381
|
+
};
|
|
382
|
+
},
|
|
383
|
+
// Regions only, said the same way `duckBoundedSource` says it: no `explore`. A neighbourhood
|
|
384
|
+
// needs adjacency this source does not index, and fossil's `expand` is what answers it —
|
|
385
|
+
// `ExploringSource` is the shape waiting for whoever writes that one.
|
|
386
|
+
async slice(a) {
|
|
387
|
+
const { fill: o, limit: d, perPixel: M, pinned: v, r: E, signal: $, view: F } = a, w = { ...J, category: o, size: E }, T = await j(), R = dt(T, F);
|
|
388
|
+
if (R.length === 0)
|
|
389
|
+
return {
|
|
390
|
+
n: 0,
|
|
391
|
+
marks: 0,
|
|
392
|
+
vertices: new BigUint64Array(0),
|
|
393
|
+
positions: new Float32Array(0),
|
|
394
|
+
links: new Float32Array(0),
|
|
395
|
+
categories: new Uint16Array(0)
|
|
396
|
+
};
|
|
397
|
+
const [O, N] = await Promise.all([
|
|
398
|
+
V("vertex", R),
|
|
399
|
+
V("edge", C ? R : [])
|
|
400
|
+
]);
|
|
401
|
+
if ($ != null && $.aborted) throw pt;
|
|
402
|
+
const b = `read_parquet([${O.join(", ")}])`, mt = `read_parquet([${N.join(", ")}])`;
|
|
403
|
+
return m.run(
|
|
404
|
+
at(b, mt, w, F, d, 0, v, M, b)
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
}, nodes: Y, edges: B };
|
|
408
|
+
}
|
|
409
|
+
export {
|
|
410
|
+
_t as duckBoundedSource,
|
|
411
|
+
Lt as openCorpus
|
|
412
|
+
};
|
|
413
|
+
//# sourceMappingURL=duck-source.js.map
|