@kanzo-tech/mosaic 0.6.0 → 0.8.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 +11 -2
- package/dist/engine.d.ts +34 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +38 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,10 +15,19 @@ Mosaic are two coordinators, and two coordinators are two crossfilters that neve
|
|
|
15
15
|
```ts
|
|
16
16
|
import { Coordinator, Selection, MosaicClient, clausePoints } from "@kanzo-tech/mosaic";
|
|
17
17
|
import { column, fillColumn, numbers, IdSetClient } from "@kanzo-tech/mosaic";
|
|
18
|
+
import { engine } from "@kanzo-tech/mosaic";
|
|
18
19
|
```
|
|
19
20
|
|
|
20
|
-
-
|
|
21
|
-
|
|
21
|
+
- **`engine()`** — the page's one DuckDB-WASM database: `{ coordinator, query, lend, hold, drop }`,
|
|
22
|
+
one per document however many callers ask. vgplot has one active coordinator, so a second boot is
|
|
23
|
+
a second database the last-mounted chart wins. `lend({ name: url })` registers a URL under a name:
|
|
24
|
+
the same URL is a no-op, a different one drops the old lease and registers the new, which is what
|
|
25
|
+
DuckDB-WASM's `File already registered` was refusing. `hold(name, bytes)` registers a copy of a
|
|
26
|
+
buffer; `drop(names)` forgets. It is fossil's `Engine` structurally, without depending on fossil.
|
|
27
|
+
|
|
28
|
+
- **Re-exports** of the coordinator, the clients, the five clause builders and the loaders — so a
|
|
29
|
+
consumer never needs a direct `@uwdata` import. The DuckDB-WASM connector is not among them:
|
|
30
|
+
`engine()` is the only boot.
|
|
22
31
|
- **`column` / `fillColumn` / `numbers`** — the half of the client protocol the protocol does not
|
|
23
32
|
give you. The coordinator answers with an Arrow table, and Arrow offers a typed column only when
|
|
24
33
|
the type allows one: an integer id gives an array, a dictionary-encoded label gives nothing
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Coordinator } from '@uwdata/mosaic-core';
|
|
2
|
+
/**
|
|
3
|
+
* The page's one database: a DuckDB-WASM instance, the Mosaic `Coordinator` over it, and the file
|
|
4
|
+
* registry, owned in one place.
|
|
5
|
+
*
|
|
6
|
+
* **One per document, because vgplot has one.** `MosaicProvider` registers its coordinator as
|
|
7
|
+
* vgplot's process-wide active one, so a second coordinator on the page is a second database the
|
|
8
|
+
* last-mounted chart wins. Every host wrote the same memoised boot to avoid it — three in these
|
|
9
|
+
* docs, one in keasy, one in fossil's playground — and none of them owned the registry beside it.
|
|
10
|
+
*
|
|
11
|
+
* **The registry is the part that was missing.** DuckDB-WASM refuses to register a name a second
|
|
12
|
+
* time under a different URL (`File already registered`), and answers only when the URL is
|
|
13
|
+
* identical. A signed URL is different on every signing, so the second visit to the same data threw.
|
|
14
|
+
* `lend` is the one place that knows what is behind a name: the same URL is a no-op, a new one drops
|
|
15
|
+
* the old lease and registers the new. That indirection is also why the registry is kept rather than
|
|
16
|
+
* reading `https://…` directly — a view names a stable file, and only the lease behind it rotates.
|
|
17
|
+
*
|
|
18
|
+
* It satisfies fossil's `Engine` structurally; this package does not depend on fossil, and does not
|
|
19
|
+
* name `@duckdb/duckdb-wasm` either — the connector hands the database over untyped.
|
|
20
|
+
*/
|
|
21
|
+
export interface Engine {
|
|
22
|
+
readonly coordinator: Coordinator;
|
|
23
|
+
/** Rows as objects, uncached: a read after a `lend` must see the new lease. */
|
|
24
|
+
query(sql: string): Promise<Record<string, unknown>[]>;
|
|
25
|
+
/** name → URL. The same URL is a no-op; a different one replaces the lease. */
|
|
26
|
+
lend(files: Record<string, string>): Promise<void>;
|
|
27
|
+
/** Registers a copy of `bytes` under `name`, replacing whatever was there. */
|
|
28
|
+
hold(name: string, bytes: Uint8Array): Promise<void>;
|
|
29
|
+
/** Unregisters the names. A name the engine does not hold is not an error. */
|
|
30
|
+
drop(names: readonly string[]): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/** The page's one engine, booted on first ask. */
|
|
33
|
+
export declare function engine(): Promise<Engine>;
|
|
34
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAiB,MAAM,qBAAqB,CAAC;AAEjE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,+EAA+E;IAC/E,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IACvD,+EAA+E;IAC/E,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,8EAA8E;IAC9E,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,8EAA8E;IAC9E,IAAI,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAwED,kDAAkD;AAClD,wBAAgB,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CAIxC"}
|
package/dist/engine.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { wasmConnector as u, Coordinator as d } from "@uwdata/mosaic-core";
|
|
2
|
+
const h = 4, w = Symbol("held");
|
|
3
|
+
async function y() {
|
|
4
|
+
const n = u(), r = new d(n), c = await n.getDuckDB();
|
|
5
|
+
await r.exec("SET enable_http_metadata_cache = true");
|
|
6
|
+
const o = /* @__PURE__ */ new Map();
|
|
7
|
+
let f = Promise.resolve();
|
|
8
|
+
const s = (e) => {
|
|
9
|
+
const t = f.then(e);
|
|
10
|
+
return f = t.catch(() => {
|
|
11
|
+
}), t;
|
|
12
|
+
}, i = async (e) => {
|
|
13
|
+
o.has(e) && (await c.dropFile(e), o.delete(e));
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
coordinator: r,
|
|
17
|
+
query: (e) => r.query(e, { type: "json", cache: !1 }),
|
|
18
|
+
lend: (e) => s(async () => {
|
|
19
|
+
for (const [t, l] of Object.entries(e))
|
|
20
|
+
o.get(t) !== l && (await i(t), await c.registerFileURL(t, l, h, !1), o.set(t, l));
|
|
21
|
+
}),
|
|
22
|
+
hold: (e, t) => s(async () => {
|
|
23
|
+
await i(e), await c.registerFileBuffer(e, t.slice()), o.set(e, w);
|
|
24
|
+
}),
|
|
25
|
+
drop: (e) => s(async () => {
|
|
26
|
+
for (const t of e) await i(t);
|
|
27
|
+
})
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const a = Symbol.for("@kanzo-tech/mosaic/engine");
|
|
31
|
+
function g() {
|
|
32
|
+
const n = globalThis;
|
|
33
|
+
return n[a] ?? (n[a] = y()), n[a];
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
g as engine
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sources":["../src/engine.ts"],"sourcesContent":["import { Coordinator, wasmConnector } from \"@uwdata/mosaic-core\";\n\n/**\n * The page's one database: a DuckDB-WASM instance, the Mosaic `Coordinator` over it, and the file\n * registry, owned in one place.\n *\n * **One per document, because vgplot has one.** `MosaicProvider` registers its coordinator as\n * vgplot's process-wide active one, so a second coordinator on the page is a second database the\n * last-mounted chart wins. Every host wrote the same memoised boot to avoid it — three in these\n * docs, one in keasy, one in fossil's playground — and none of them owned the registry beside it.\n *\n * **The registry is the part that was missing.** DuckDB-WASM refuses to register a name a second\n * time under a different URL (`File already registered`), and answers only when the URL is\n * identical. A signed URL is different on every signing, so the second visit to the same data threw.\n * `lend` is the one place that knows what is behind a name: the same URL is a no-op, a new one drops\n * the old lease and registers the new. That indirection is also why the registry is kept rather than\n * reading `https://…` directly — a view names a stable file, and only the lease behind it rotates.\n *\n * It satisfies fossil's `Engine` structurally; this package does not depend on fossil, and does not\n * name `@duckdb/duckdb-wasm` either — the connector hands the database over untyped.\n */\nexport interface Engine {\n readonly coordinator: Coordinator;\n /** Rows as objects, uncached: a read after a `lend` must see the new lease. */\n query(sql: string): Promise<Record<string, unknown>[]>;\n /** name → URL. The same URL is a no-op; a different one replaces the lease. */\n lend(files: Record<string, string>): Promise<void>;\n /** Registers a copy of `bytes` under `name`, replacing whatever was there. */\n hold(name: string, bytes: Uint8Array): Promise<void>;\n /** Unregisters the names. A name the engine does not hold is not an error. */\n drop(names: readonly string[]): Promise<void>;\n}\n\n/** The slice of DuckDB-WASM's `AsyncDuckDB` the registry uses. */\ninterface Registry {\n registerFileURL(name: string, url: string, protocol: number, directIO: boolean): Promise<void>;\n registerFileBuffer(name: string, buffer: Uint8Array): Promise<void>;\n dropFile(name: string): Promise<void>;\n}\n\n/** `DuckDBDataProtocol.HTTP` — a numeric enum, spelled here so the type never has to be imported. */\nconst HTTP = 4;\n\n/** A buffer is not a URL, so a held name never compares equal to a lent one. */\nconst HELD = Symbol(\"held\");\n\nasync function boot(): Promise<Engine> {\n const connector = wasmConnector();\n const coordinator = new Coordinator(connector);\n const db = (await connector.getDuckDB()) as unknown as Registry;\n // Lent files are read lazily by range; caching their metadata is what keeps a pan from re-probing.\n await coordinator.exec(\"SET enable_http_metadata_cache = true\");\n\n const behind = new Map<string, string | typeof HELD>();\n\n // Registry changes run one at a time: two `lend`s of one name racing each other would each see the\n // old entry, and the second register would be refused.\n let tail: Promise<unknown> = Promise.resolve();\n const serial = (step: () => Promise<void>): Promise<void> => {\n const run = tail.then(step);\n tail = run.catch(() => {});\n return run;\n };\n\n const release = async (name: string) => {\n if (!behind.has(name)) return;\n await db.dropFile(name);\n behind.delete(name);\n };\n\n return {\n coordinator,\n query: (sql) =>\n coordinator.query(sql, { type: \"json\", cache: false }) as Promise<Record<string, unknown>[]>,\n lend: (files) =>\n serial(async () => {\n for (const [name, url] of Object.entries(files)) {\n if (behind.get(name) === url) continue;\n await release(name);\n await db.registerFileURL(name, url, HTTP, false);\n behind.set(name, url);\n }\n }),\n hold: (name, bytes) =>\n serial(async () => {\n await release(name);\n // A copy, because DuckDB-WASM transfers the buffer to its worker and detaches the caller's.\n await db.registerFileBuffer(name, bytes.slice());\n behind.set(name, HELD);\n }),\n drop: (names) =>\n serial(async () => {\n for (const name of names) await release(name);\n }),\n };\n}\n\n/**\n * Keyed on the document's global rather than on this module, so two copies of the package in one\n * bundle still share one database — which is the invariant, not the module identity.\n */\nconst KEY = Symbol.for(\"@kanzo-tech/mosaic/engine\");\n\n/** The page's one engine, booted on first ask. */\nexport function engine(): Promise<Engine> {\n const scope = globalThis as { [KEY]?: Promise<Engine> };\n scope[KEY] ??= boot();\n return scope[KEY];\n}\n"],"names":["HTTP","HELD","boot","connector","wasmConnector","coordinator","Coordinator","db","behind","tail","serial","step","run","release","name","sql","files","url","bytes","names","KEY","engine","scope"],"mappings":";AAyCA,MAAMA,IAAO,GAGPC,IAAO,OAAO,MAAM;AAE1B,eAAeC,IAAwB;AACrC,QAAMC,IAAYC,EAAA,GACZC,IAAc,IAAIC,EAAYH,CAAS,GACvCI,IAAM,MAAMJ,EAAU,UAAA;AAE5B,QAAME,EAAY,KAAK,uCAAuC;AAE9D,QAAMG,wBAAa,IAAA;AAInB,MAAIC,IAAyB,QAAQ,QAAA;AACrC,QAAMC,IAAS,CAACC,MAA6C;AAC3D,UAAMC,IAAMH,EAAK,KAAKE,CAAI;AAC1B,WAAAF,IAAOG,EAAI,MAAM,MAAM;AAAA,IAAC,CAAC,GAClBA;AAAA,EACT,GAEMC,IAAU,OAAOC,MAAiB;AACtC,IAAKN,EAAO,IAAIM,CAAI,MACpB,MAAMP,EAAG,SAASO,CAAI,GACtBN,EAAO,OAAOM,CAAI;AAAA,EACpB;AAEA,SAAO;AAAA,IACL,aAAAT;AAAA,IACA,OAAO,CAACU,MACNV,EAAY,MAAMU,GAAK,EAAE,MAAM,QAAQ,OAAO,IAAO;AAAA,IACvD,MAAM,CAACC,MACLN,EAAO,YAAY;AACjB,iBAAW,CAACI,GAAMG,CAAG,KAAK,OAAO,QAAQD,CAAK;AAC5C,QAAIR,EAAO,IAAIM,CAAI,MAAMG,MACzB,MAAMJ,EAAQC,CAAI,GAClB,MAAMP,EAAG,gBAAgBO,GAAMG,GAAKjB,GAAM,EAAK,GAC/CQ,EAAO,IAAIM,GAAMG,CAAG;AAAA,IAExB,CAAC;AAAA,IACH,MAAM,CAACH,GAAMI,MACXR,EAAO,YAAY;AACjB,YAAMG,EAAQC,CAAI,GAElB,MAAMP,EAAG,mBAAmBO,GAAMI,EAAM,OAAO,GAC/CV,EAAO,IAAIM,GAAMb,CAAI;AAAA,IACvB,CAAC;AAAA,IACH,MAAM,CAACkB,MACLT,EAAO,YAAY;AACjB,iBAAWI,KAAQK,EAAO,OAAMN,EAAQC,CAAI;AAAA,IAC9C,CAAC;AAAA,EAAA;AAEP;AAMA,MAAMM,IAAM,OAAO,IAAI,2BAA2B;AAG3C,SAASC,IAA0B;AACxC,QAAMC,IAAQ;AACd,SAAAA,EAAAF,OAAAE,EAAAF,KAAelB,EAAA,IACRoB,EAAMF,CAAG;AAClB;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -20,10 +20,16 @@
|
|
|
20
20
|
* `Selection` is what makes one copy of Mosaic the easy outcome, and two copies would be two
|
|
21
21
|
* crossfilters that never hear each other.
|
|
22
22
|
*/
|
|
23
|
-
export { Coordinator, MosaicClient, Selection, makeClient, clausePoint, clausePoints, clauseInterval, clauseIntervals, clauseMatch,
|
|
23
|
+
export { Coordinator, MosaicClient, Selection, makeClient, clausePoint, clausePoints, clauseInterval, clauseIntervals, clauseMatch, type SelectionClause, } from '@uwdata/mosaic-core';
|
|
24
24
|
export { Query, asc, desc, loadCSV, loadJSON, loadObjects, loadParquet, loadSpatial, loadExtension, type ExprValue, type FilterExpr, } from '@uwdata/mosaic-sql';
|
|
25
25
|
/** Ours: turning an Arrow answer into values, which the client protocol does not do for you. */
|
|
26
26
|
export { column, fillColumn, numbers, type NumericArray } from './arrow.js';
|
|
27
27
|
/** Ours: the crossfilter adapter for a view whose positions are not in the database. */
|
|
28
28
|
export { IdSetClient, type IdSetClientOptions } from './id-set-client.js';
|
|
29
|
+
/**
|
|
30
|
+
* Ours: the page's one DuckDB-WASM engine — the coordinator and the file registry beside it. It is
|
|
31
|
+
* the only door to `wasmConnector`, which is why that is no longer re-exported: a second boot is a
|
|
32
|
+
* second database, and every host that wrote its own boot also wrote its own registry policy.
|
|
33
|
+
*/
|
|
34
|
+
export { engine, type Engine } from './engine.js';
|
|
29
35
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EACL,WAAW,EACX,YAAY,EACZ,SAAS,EACT,UAAU,EACV,WAAW,EACX,YAAY,EACZ,cAAc,EACd,eAAe,EACf,WAAW,EACX,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EACL,WAAW,EACX,YAAY,EACZ,SAAS,EACT,UAAU,EACV,WAAW,EACX,YAAY,EACZ,cAAc,EACd,eAAe,EACf,WAAW,EACX,KAAK,eAAe,GACrB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,KAAK,EACL,GAAG,EACH,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,WAAW,EACX,WAAW,EACX,WAAW,EACX,aAAa,EACb,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,oBAAoB,CAAC;AAE5B,gGAAgG;AAChG,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE5E,wFAAwF;AACxF,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE1E;;;;GAIG;AACH,OAAO,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
|
-
import { Coordinator as
|
|
2
|
-
import { Query as
|
|
3
|
-
import { column as
|
|
4
|
-
import { IdSetClient as
|
|
1
|
+
import { Coordinator as l, MosaicClient as a, Selection as t, clauseInterval as r, clauseIntervals as n, clauseMatch as s, clausePoint as c, clausePoints as i, makeClient as u } from "@uwdata/mosaic-core";
|
|
2
|
+
import { Query as m, asc as f, desc as p, loadCSV as x, loadExtension as C, loadJSON as S, loadObjects as I, loadParquet as P, loadSpatial as b } from "@uwdata/mosaic-sql";
|
|
3
|
+
import { column as M, fillColumn as O, numbers as g } from "./arrow.js";
|
|
4
|
+
import { IdSetClient as j } from "./id-set-client.js";
|
|
5
|
+
import { engine as q } from "./engine.js";
|
|
5
6
|
export {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
l as Coordinator,
|
|
8
|
+
j as IdSetClient,
|
|
9
|
+
a as MosaicClient,
|
|
10
|
+
m as Query,
|
|
10
11
|
t as Selection,
|
|
11
12
|
f as asc,
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
r as clauseInterval,
|
|
14
|
+
n as clauseIntervals,
|
|
14
15
|
s as clauseMatch,
|
|
15
16
|
c as clausePoint,
|
|
16
17
|
i as clausePoints,
|
|
17
|
-
|
|
18
|
+
M as column,
|
|
18
19
|
p as desc,
|
|
19
|
-
|
|
20
|
+
q as engine,
|
|
21
|
+
O as fillColumn,
|
|
20
22
|
x as loadCSV,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
C as loadExtension,
|
|
24
|
+
S as loadJSON,
|
|
25
|
+
I as loadObjects,
|
|
26
|
+
P as loadParquet,
|
|
27
|
+
b as loadSpatial,
|
|
26
28
|
u as makeClient,
|
|
27
|
-
|
|
28
|
-
d as wasmConnector
|
|
29
|
+
g as numbers
|
|
29
30
|
};
|
|
30
31
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kanzo-tech/mosaic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "The Mosaic conversation, without React — coordinator, clients and clauses re-exported as one surface, plus the Arrow column reader and the id-set client that every consumer of them was writing by hand.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|