blogwright-analytics 0.3.3
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 +162 -0
- package/dist/adapters/duckdb-ingest.d.ts +76 -0
- package/dist/adapters/duckdb-ingest.js +173 -0
- package/dist/adapters/duckdb-query.d.ts +56 -0
- package/dist/adapters/duckdb-query.js +80 -0
- package/dist/adapters/duckdb-session.d.ts +168 -0
- package/dist/adapters/duckdb-session.js +330 -0
- package/dist/app/_app/immutable/assets/0.BTQrrh5B.css +1 -0
- package/dist/app/_app/immutable/assets/2.CZSK3rT8.css +1 -0
- package/dist/app/_app/immutable/assets/BrushContext.D7c8UPey.css +1 -0
- package/dist/app/_app/immutable/assets/ChartAnnotations.CPxIG7Mw.css +1 -0
- package/dist/app/_app/immutable/assets/Circle.C5MKzgk2.css +1 -0
- package/dist/app/_app/immutable/assets/DefaultTooltip.C5-uctZ7.css +1 -0
- package/dist/app/_app/immutable/assets/Group.DV48xipa.css +1 -0
- package/dist/app/_app/immutable/assets/Labels.BxZ4NUVz.css +1 -0
- package/dist/app/_app/immutable/assets/Legend.CxnrE4Ye.css +1 -0
- package/dist/app/_app/immutable/assets/Line.fkmsECm9.css +1 -0
- package/dist/app/_app/immutable/assets/Path.CvpwNZ6g.css +1 -0
- package/dist/app/_app/immutable/assets/Rect.CtRaGMmQ.css +1 -0
- package/dist/app/_app/immutable/assets/Text.j9l35qB0.css +1 -0
- package/dist/app/_app/immutable/assets/TransformContext.Bs_HkpAk.css +1 -0
- package/dist/app/_app/immutable/assets/Voronoi.ce7atosu.css +1 -0
- package/dist/app/_app/immutable/chunks/-aNGNaBT.js +1 -0
- package/dist/app/_app/immutable/chunks/6djn-yLs.js +1 -0
- package/dist/app/_app/immutable/chunks/B1amyutE.js +1 -0
- package/dist/app/_app/immutable/chunks/B3vZDoek.js +1 -0
- package/dist/app/_app/immutable/chunks/B5KRA4hC.js +1 -0
- package/dist/app/_app/immutable/chunks/BClnVG6H.js +1 -0
- package/dist/app/_app/immutable/chunks/BID1NNRh.js +1 -0
- package/dist/app/_app/immutable/chunks/BR2LaRms.js +1 -0
- package/dist/app/_app/immutable/chunks/Bd1gDe3Y.js +1 -0
- package/dist/app/_app/immutable/chunks/Bjy-W4x2.js +81 -0
- package/dist/app/_app/immutable/chunks/Bl052uUt.js +1 -0
- package/dist/app/_app/immutable/chunks/Bye3lL0c.js +1 -0
- package/dist/app/_app/immutable/chunks/C58PZtCD.js +4 -0
- package/dist/app/_app/immutable/chunks/CAzydqEO.js +1 -0
- package/dist/app/_app/immutable/chunks/CCch3uox.js +1 -0
- package/dist/app/_app/immutable/chunks/CIlSMUH9.js +1 -0
- package/dist/app/_app/immutable/chunks/CO1vUXfR.js +1 -0
- package/dist/app/_app/immutable/chunks/CPbD8C65.js +5 -0
- package/dist/app/_app/immutable/chunks/CRTcXoMo.js +1 -0
- package/dist/app/_app/immutable/chunks/CjjyIQAO.js +1 -0
- package/dist/app/_app/immutable/chunks/CuXAxjvF.js +1 -0
- package/dist/app/_app/immutable/chunks/CvyVA_jC.js +1 -0
- package/dist/app/_app/immutable/chunks/CxGCFVdy.js +1 -0
- package/dist/app/_app/immutable/chunks/D0Ty6LN0.js +1 -0
- package/dist/app/_app/immutable/chunks/D2AaQUUW.js +1 -0
- package/dist/app/_app/immutable/chunks/D2BnX0Uk.js +3 -0
- package/dist/app/_app/immutable/chunks/DJc8C0NK.js +1 -0
- package/dist/app/_app/immutable/chunks/DKMlMI4a.js +1 -0
- package/dist/app/_app/immutable/chunks/DVXZkpbf.js +1 -0
- package/dist/app/_app/immutable/chunks/DVt8ukQ_.js +1 -0
- package/dist/app/_app/immutable/chunks/DZPlYdq_.js +1 -0
- package/dist/app/_app/immutable/chunks/Db0q5_zr.js +1 -0
- package/dist/app/_app/immutable/chunks/Dfvzj6n2.js +1 -0
- package/dist/app/_app/immutable/chunks/Dh958be7.js +1 -0
- package/dist/app/_app/immutable/chunks/DjKLLdnY.js +15 -0
- package/dist/app/_app/immutable/chunks/Doz7YX1W.js +1 -0
- package/dist/app/_app/immutable/chunks/DthYhn6Y.js +2 -0
- package/dist/app/_app/immutable/chunks/DtuTIrAM.js +1 -0
- package/dist/app/_app/immutable/chunks/HclGiUj8.js +1 -0
- package/dist/app/_app/immutable/chunks/Hx0TNsV3.js +1 -0
- package/dist/app/_app/immutable/chunks/RobXhXPM.js +1 -0
- package/dist/app/_app/immutable/chunks/V9ZjaxiY.js +1 -0
- package/dist/app/_app/immutable/chunks/Y5urAfNy.js +1 -0
- package/dist/app/_app/immutable/chunks/caXkbKD3.js +1 -0
- package/dist/app/_app/immutable/chunks/devYm2ud.js +1 -0
- package/dist/app/_app/immutable/chunks/mtZWP0zR.js +1 -0
- package/dist/app/_app/immutable/chunks/vDgBJUjM.js +1 -0
- package/dist/app/_app/immutable/chunks/xIq_fFFM.js +1 -0
- package/dist/app/_app/immutable/chunks/xihTtKlq.js +1 -0
- package/dist/app/_app/immutable/chunks/z05MoCFz.js +1 -0
- package/dist/app/_app/immutable/entry/app.CLAerUAN.js +2 -0
- package/dist/app/_app/immutable/entry/start.D3MqnNci.js +1 -0
- package/dist/app/_app/immutable/nodes/0.UTMEigHJ.js +1 -0
- package/dist/app/_app/immutable/nodes/1.Cn4f11bT.js +1 -0
- package/dist/app/_app/immutable/nodes/2.B39cIcr2.js +6 -0
- package/dist/app/_app/version.json +1 -0
- package/dist/app/index.html +82 -0
- package/dist/aws/clients.d.ts +70 -0
- package/dist/aws/clients.js +52 -0
- package/dist/aws/errors.d.ts +41 -0
- package/dist/aws/errors.js +70 -0
- package/dist/aws/firehose.d.ts +228 -0
- package/dist/aws/firehose.js +347 -0
- package/dist/aws/glue.d.ts +103 -0
- package/dist/aws/glue.js +225 -0
- package/dist/aws/lambda.d.ts +132 -0
- package/dist/aws/lambda.js +339 -0
- package/dist/aws/s3tables.d.ts +120 -0
- package/dist/aws/s3tables.js +281 -0
- package/dist/backfill.d.ts +100 -0
- package/dist/backfill.js +294 -0
- package/dist/commands.d.ts +124 -0
- package/dist/commands.js +336 -0
- package/dist/config.d.ts +162 -0
- package/dist/config.js +317 -0
- package/dist/fixture-ingest.d.ts +49 -0
- package/dist/fixture-ingest.js +43 -0
- package/dist/fixture-query.d.ts +39 -0
- package/dist/fixture-query.js +70 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +35 -0
- package/dist/nodes.d.ts +404 -0
- package/dist/nodes.js +2708 -0
- package/dist/paths.d.ts +45 -0
- package/dist/paths.js +47 -0
- package/dist/plugin.d.ts +102 -0
- package/dist/plugin.js +248 -0
- package/dist/ports.d.ts +113 -0
- package/dist/ports.js +35 -0
- package/dist/queries.d.ts +301 -0
- package/dist/queries.js +414 -0
- package/dist/schema.d.ts +240 -0
- package/dist/schema.js +154 -0
- package/dist/server.d.ts +150 -0
- package/dist/server.js +499 -0
- package/dist/transform/bots.d.ts +47 -0
- package/dist/transform/bots.js +73 -0
- package/dist/transform/handler.d.ts +135 -0
- package/dist/transform/handler.js +177 -0
- package/dist/transform/map-record.d.ts +110 -0
- package/dist/transform/map-record.js +275 -0
- package/dist/transform/visitor-key.d.ts +83 -0
- package/dist/transform/visitor-key.js +120 -0
- package/dist/transform-bundle/index.mjs +21456 -0
- package/dist/transform-bundle/transform-manifest.json +4 -0
- package/dist/transform-hash.d.ts +135 -0
- package/dist/transform-hash.js +186 -0
- package/dist/write-transform-manifest.mjs +365 -0
- package/package.json +59 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One attached DuckDB session, shared by the two adapters that need one: the
|
|
3
|
+
* read path behind `AnalyticsQuery` (`duckdb-query.ts`) and the write path
|
|
4
|
+
* behind `AnalyticsIngest` (`duckdb-ingest.ts`). The change spec asks for the
|
|
5
|
+
* ingest port to be "implemented by the same DuckDB adapter" as the query
|
|
6
|
+
* port; two files with one session module between them is what that means in
|
|
7
|
+
* practice, and the alternative - a second copy of the credential secret, the
|
|
8
|
+
* attach, the quoting and the error translation - is the duplication
|
|
9
|
+
* DEVELOPMENT.md §Definition of done bans, in the one module where a
|
|
10
|
+
* divergence would be a security difference rather than a style one.
|
|
11
|
+
*
|
|
12
|
+
* **This is the only module in the repo that imports the DuckDB node-api
|
|
13
|
+
* package.** (Named in prose rather than spelled as the specifier, so a check
|
|
14
|
+
* for that specifier over this tree is not satisfied by a comment about it.)
|
|
15
|
+
* Neither adapter names a vendor type, and no module outside `adapters/` names
|
|
16
|
+
* DuckDB at all - which is what `ports.ts` promises and what the change spec's
|
|
17
|
+
* §Ports → `AnalyticsQuery` requires: DuckDB's iceberg extension is documented
|
|
18
|
+
* as *preview*, so its attach syntax may move, and when it does the edit lands
|
|
19
|
+
* here rather than across the dashboard and the backfill.
|
|
20
|
+
*
|
|
21
|
+
* **Credentials are injected, never resolved by DuckDB.** A session takes a
|
|
22
|
+
* `CredentialProvider` - core's own chain, the one `deploy` already signs with
|
|
23
|
+
* (`createCredentialProvider`, `packages/core/src/aws/credentials.ts`) -
|
|
24
|
+
* resolves it, and names the secret it builds in the `ATTACH`. DuckDB
|
|
25
|
+
* therefore has exactly one credential source and no reason to consult its
|
|
26
|
+
* own; a session that works for `deploy` works for the dashboard and for a
|
|
27
|
+
* backfill, which is the spec's §Analytics dashboard → Credentials decision.
|
|
28
|
+
*
|
|
29
|
+
* **The credential values are bound, not spelled.** `CREATE SECRET` accepts
|
|
30
|
+
* `$name` placeholders (verified against DuckDB 1.5.5, 2026-08-30), so the
|
|
31
|
+
* statement this module builds is a constant and the access key, secret and
|
|
32
|
+
* session token travel as bind values. That is not a style preference: DuckDB's
|
|
33
|
+
* parser echoes the offending line back in a syntax error (`LINE 1: ...`), so a
|
|
34
|
+
* credential spliced into the statement text could reach an error message, a
|
|
35
|
+
* log, or a thrown stack. Bound, it cannot. {@link redactorFor} is the second
|
|
36
|
+
* half of the same property, covering a message DuckDB builds for itself.
|
|
37
|
+
*
|
|
38
|
+
* **No vendor error object escapes.** Every DuckDB call runs inside
|
|
39
|
+
* {@link DuckDbSession.step}, and what leaves an adapter is always a plain repo
|
|
40
|
+
* `Error` naming the operation, the attach target and the step that failed.
|
|
41
|
+
* Unlike `createNodeFileSystem`'s `contextualise`
|
|
42
|
+
* (`packages/core/src/adapters/node-fs.ts`) nothing is attached as `cause`: a
|
|
43
|
+
* `cause` keeps the vendor error reachable, and `err.cause.message` is exactly
|
|
44
|
+
* the string the redaction above exists to control. The vendor's own words are
|
|
45
|
+
* preserved - redacted - inside the raised message instead.
|
|
46
|
+
*
|
|
47
|
+
* **The relation is bound here because only here is there a context to bind it
|
|
48
|
+
* from.** `queries.ts` reads one fixed relation, `PAGE_VIEWS_RELATION`, because
|
|
49
|
+
* SQL binds values and not identifiers and that module exists to make splicing
|
|
50
|
+
* impossible. A session holds the plugin context, so it takes
|
|
51
|
+
* `resolveAnalyticsConfig(ctx)` - never a caller-supplied bucket name, so
|
|
52
|
+
* task 44's `ENV_DERIVED` seal is honoured and staging cannot resolve to
|
|
53
|
+
* production's Iceberg table - and offers the configured
|
|
54
|
+
* `<catalog>.<namespace>.<table>` triple as {@link DuckDbSession.relation}.
|
|
55
|
+
*/
|
|
56
|
+
import type { CredentialProvider, PluginContext } from 'blogwright-core';
|
|
57
|
+
import { type AnalyticsConfig, type ResolvedAnalyticsConfig } from '../config.js';
|
|
58
|
+
import type { QueryRow } from '../ports.js';
|
|
59
|
+
/**
|
|
60
|
+
* Values bound to a statement's placeholders. Wider than the read path's own
|
|
61
|
+
* bindings, which are days and flags: an inserted row carries the column types
|
|
62
|
+
* `schema.ts` declares, so a number, a boolean and an absent (null) value all
|
|
63
|
+
* have to travel.
|
|
64
|
+
*/
|
|
65
|
+
export type DuckDbBindings = Readonly<Record<string, string | number | boolean | null>>;
|
|
66
|
+
/**
|
|
67
|
+
* The DuckDB surface the adapters use, narrowed to the two operations they
|
|
68
|
+
* need. Declared here rather than in `ports.ts` because it is not a domain
|
|
69
|
+
* port - no domain module may name it - but the seam a test substitutes at to
|
|
70
|
+
* observe the statements an adapter issues without an AWS account.
|
|
71
|
+
*/
|
|
72
|
+
export interface DuckDbConnection {
|
|
73
|
+
/** Run one statement, binding `$name` placeholders from `bindings`. */
|
|
74
|
+
run(sql: string, bindings: DuckDbBindings): Promise<readonly QueryRow[]>;
|
|
75
|
+
/** Release the connection. Called when a session's setup failed. */
|
|
76
|
+
close(): void;
|
|
77
|
+
}
|
|
78
|
+
/** How a {@link DuckDbConnection} is obtained. Defaults to {@link connectDuckDb}. */
|
|
79
|
+
export type DuckDbConnect = () => Promise<DuckDbConnection>;
|
|
80
|
+
/**
|
|
81
|
+
* The slice of a plugin context a session reads. `env`, `config` and
|
|
82
|
+
* `pluginConfig` are what `resolveAnalyticsConfig` needs; `accountId` and
|
|
83
|
+
* `config.region` are what the table bucket's ARN needs. Taken as a `Pick` of
|
|
84
|
+
* core's own `PluginContext` rather than a restatement, so the four members
|
|
85
|
+
* cannot drift from the SPI and a plugin command passes `ctx` straight
|
|
86
|
+
* through.
|
|
87
|
+
*/
|
|
88
|
+
export type DuckDbSessionContext = Pick<PluginContext<AnalyticsConfig>, 'env' | 'config' | 'pluginConfig' | 'accountId'>;
|
|
89
|
+
/** What {@link createDuckDbSession} is built from. */
|
|
90
|
+
export interface DuckDbSessionOptions {
|
|
91
|
+
/**
|
|
92
|
+
* The plugin context. The session resolves the analytics config from it
|
|
93
|
+
* itself: `tableBucket` is sealed under task 44's `ENV_DERIVED` symbol and
|
|
94
|
+
* `resolveAnalyticsConfig` is the only way to it, so no caller can hand a
|
|
95
|
+
* session a bucket name that dropped the environment.
|
|
96
|
+
*/
|
|
97
|
+
readonly ctx: DuckDbSessionContext;
|
|
98
|
+
/**
|
|
99
|
+
* Credentials for the catalog, resolved through core's provider chain
|
|
100
|
+
* (`createCredentialProvider`). A test passes `staticCredentials`.
|
|
101
|
+
*/
|
|
102
|
+
readonly credentials: CredentialProvider;
|
|
103
|
+
/**
|
|
104
|
+
* Whether the catalog is attached read-only. The dashboard's read path
|
|
105
|
+
* attaches read-only and the backfill's write path does not; nothing else
|
|
106
|
+
* about the two sessions differs.
|
|
107
|
+
*/
|
|
108
|
+
readonly readOnly: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* How a DuckDB connection is obtained. Defaults to {@link connectDuckDb};
|
|
111
|
+
* a test substitutes a recording connection here.
|
|
112
|
+
*/
|
|
113
|
+
readonly connect?: DuckDbConnect | undefined;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Quote an identifier for the statement text. `validateAnalyticsConfig` already
|
|
117
|
+
* holds `namespace` and `table` to `^[a-z0-9_]+$`, so there is no quote
|
|
118
|
+
* character to escape and this adds no second validation of a rule config owns.
|
|
119
|
+
* What it does add is reserved words: `web` and `page_views` need no quoting,
|
|
120
|
+
* but `order` and `table` match that same pattern and an operator may configure
|
|
121
|
+
* either.
|
|
122
|
+
*/
|
|
123
|
+
export declare function quoteIdentifier(identifier: string): string;
|
|
124
|
+
/**
|
|
125
|
+
* The configured `<catalog>.<namespace>.<table>` triple every statement reads
|
|
126
|
+
* or writes, inside the attached catalog. This is the binding task 45
|
|
127
|
+
* delegated to the adapter layer.
|
|
128
|
+
*/
|
|
129
|
+
export declare function pageViewsRelation(config: ResolvedAnalyticsConfig): string;
|
|
130
|
+
/**
|
|
131
|
+
* Open a real DuckDB connection - the default {@link DuckDbConnect}, and the
|
|
132
|
+
* one place the vendor package is called. Exported so the test suite can run
|
|
133
|
+
* statements against a real local table without naming the vendor package
|
|
134
|
+
* itself, which is what keeps DuckDB confined to this file.
|
|
135
|
+
*/
|
|
136
|
+
export declare const connectDuckDb: DuckDbConnect;
|
|
137
|
+
/** An attached DuckDB session, and the vocabulary an adapter reports failures in. */
|
|
138
|
+
export interface DuckDbSession {
|
|
139
|
+
/** The resolved analytics config the session was built against. */
|
|
140
|
+
readonly config: ResolvedAnalyticsConfig;
|
|
141
|
+
/** The S3 Tables bucket ARN the catalog is attached from. */
|
|
142
|
+
readonly attachTarget: string;
|
|
143
|
+
/** The `<catalog>.<namespace>.<table>` triple statements name. */
|
|
144
|
+
readonly relation: string;
|
|
145
|
+
/**
|
|
146
|
+
* The open connection, attached and ready. Opened lazily on the first call
|
|
147
|
+
* and then reused, so building a session touches neither the network nor the
|
|
148
|
+
* native library. A session whose setup failed is closed and forgotten
|
|
149
|
+
* rather than cached, so a dashboard left open across an expired SSO session
|
|
150
|
+
* recovers on the next request instead of repeating the first failure
|
|
151
|
+
* forever.
|
|
152
|
+
*/
|
|
153
|
+
open(): Promise<DuckDbConnection>;
|
|
154
|
+
/** Run one step, so no vendor error object escapes it. */
|
|
155
|
+
step<T>(description: string, run: () => Promise<T>): Promise<T>;
|
|
156
|
+
/**
|
|
157
|
+
* The words that follow "failed while" in an adapter's message: the failing
|
|
158
|
+
* step's own description when the failure came from {@link step}, and
|
|
159
|
+
* `fallback` with the redacted vendor text otherwise.
|
|
160
|
+
*/
|
|
161
|
+
detail(err: unknown, fallback: string): string;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Build a session. Nothing here connects: the connection is opened on the
|
|
165
|
+
* first {@link DuckDbSession.open}, so a command that never reaches the table
|
|
166
|
+
* starts nothing and binds its own listener first.
|
|
167
|
+
*/
|
|
168
|
+
export declare function createDuckDbSession(opts: DuckDbSessionOptions): DuckDbSession;
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One attached DuckDB session, shared by the two adapters that need one: the
|
|
3
|
+
* read path behind `AnalyticsQuery` (`duckdb-query.ts`) and the write path
|
|
4
|
+
* behind `AnalyticsIngest` (`duckdb-ingest.ts`). The change spec asks for the
|
|
5
|
+
* ingest port to be "implemented by the same DuckDB adapter" as the query
|
|
6
|
+
* port; two files with one session module between them is what that means in
|
|
7
|
+
* practice, and the alternative - a second copy of the credential secret, the
|
|
8
|
+
* attach, the quoting and the error translation - is the duplication
|
|
9
|
+
* DEVELOPMENT.md §Definition of done bans, in the one module where a
|
|
10
|
+
* divergence would be a security difference rather than a style one.
|
|
11
|
+
*
|
|
12
|
+
* **This is the only module in the repo that imports the DuckDB node-api
|
|
13
|
+
* package.** (Named in prose rather than spelled as the specifier, so a check
|
|
14
|
+
* for that specifier over this tree is not satisfied by a comment about it.)
|
|
15
|
+
* Neither adapter names a vendor type, and no module outside `adapters/` names
|
|
16
|
+
* DuckDB at all - which is what `ports.ts` promises and what the change spec's
|
|
17
|
+
* §Ports → `AnalyticsQuery` requires: DuckDB's iceberg extension is documented
|
|
18
|
+
* as *preview*, so its attach syntax may move, and when it does the edit lands
|
|
19
|
+
* here rather than across the dashboard and the backfill.
|
|
20
|
+
*
|
|
21
|
+
* **Credentials are injected, never resolved by DuckDB.** A session takes a
|
|
22
|
+
* `CredentialProvider` - core's own chain, the one `deploy` already signs with
|
|
23
|
+
* (`createCredentialProvider`, `packages/core/src/aws/credentials.ts`) -
|
|
24
|
+
* resolves it, and names the secret it builds in the `ATTACH`. DuckDB
|
|
25
|
+
* therefore has exactly one credential source and no reason to consult its
|
|
26
|
+
* own; a session that works for `deploy` works for the dashboard and for a
|
|
27
|
+
* backfill, which is the spec's §Analytics dashboard → Credentials decision.
|
|
28
|
+
*
|
|
29
|
+
* **The credential values are bound, not spelled.** `CREATE SECRET` accepts
|
|
30
|
+
* `$name` placeholders (verified against DuckDB 1.5.5, 2026-08-30), so the
|
|
31
|
+
* statement this module builds is a constant and the access key, secret and
|
|
32
|
+
* session token travel as bind values. That is not a style preference: DuckDB's
|
|
33
|
+
* parser echoes the offending line back in a syntax error (`LINE 1: ...`), so a
|
|
34
|
+
* credential spliced into the statement text could reach an error message, a
|
|
35
|
+
* log, or a thrown stack. Bound, it cannot. {@link redactorFor} is the second
|
|
36
|
+
* half of the same property, covering a message DuckDB builds for itself.
|
|
37
|
+
*
|
|
38
|
+
* **No vendor error object escapes.** Every DuckDB call runs inside
|
|
39
|
+
* {@link DuckDbSession.step}, and what leaves an adapter is always a plain repo
|
|
40
|
+
* `Error` naming the operation, the attach target and the step that failed.
|
|
41
|
+
* Unlike `createNodeFileSystem`'s `contextualise`
|
|
42
|
+
* (`packages/core/src/adapters/node-fs.ts`) nothing is attached as `cause`: a
|
|
43
|
+
* `cause` keeps the vendor error reachable, and `err.cause.message` is exactly
|
|
44
|
+
* the string the redaction above exists to control. The vendor's own words are
|
|
45
|
+
* preserved - redacted - inside the raised message instead.
|
|
46
|
+
*
|
|
47
|
+
* **The relation is bound here because only here is there a context to bind it
|
|
48
|
+
* from.** `queries.ts` reads one fixed relation, `PAGE_VIEWS_RELATION`, because
|
|
49
|
+
* SQL binds values and not identifiers and that module exists to make splicing
|
|
50
|
+
* impossible. A session holds the plugin context, so it takes
|
|
51
|
+
* `resolveAnalyticsConfig(ctx)` - never a caller-supplied bucket name, so
|
|
52
|
+
* task 44's `ENV_DERIVED` seal is honoured and staging cannot resolve to
|
|
53
|
+
* production's Iceberg table - and offers the configured
|
|
54
|
+
* `<catalog>.<namespace>.<table>` triple as {@link DuckDbSession.relation}.
|
|
55
|
+
*/
|
|
56
|
+
import { DuckDBInstance } from '@duckdb/node-api';
|
|
57
|
+
import { resolveAnalyticsConfig, } from '../config.js';
|
|
58
|
+
/**
|
|
59
|
+
* The name the attached catalog takes inside a DuckDB session. A module
|
|
60
|
+
* constant and not derived from configuration: it is private to one
|
|
61
|
+
* connection, so nothing is gained by letting an operator name it, and a
|
|
62
|
+
* fixed name keeps one more configured string out of the statement text.
|
|
63
|
+
*/
|
|
64
|
+
const CATALOG_ALIAS = 'analytics';
|
|
65
|
+
/**
|
|
66
|
+
* The name of the secret the credentials land in. The `ATTACH` names it
|
|
67
|
+
* explicitly rather than letting DuckDB pick a secret by scope, which is what
|
|
68
|
+
* makes "an adapter never lets DuckDB resolve its own chain" a property of
|
|
69
|
+
* the statement rather than of the ambient secret table.
|
|
70
|
+
*/
|
|
71
|
+
const SECRET_NAME = 'blogwright_analytics';
|
|
72
|
+
/**
|
|
73
|
+
* The extensions the S3 Tables attach needs: `iceberg` for the catalog itself,
|
|
74
|
+
* `httpfs` for the `s3` secret type and the object reads behind it. Loaded
|
|
75
|
+
* explicitly rather than left to DuckDB's autoloading, so a machine that
|
|
76
|
+
* cannot reach the extension repository fails at a named setup step instead of
|
|
77
|
+
* midway through a statement.
|
|
78
|
+
*/
|
|
79
|
+
const REQUIRED_EXTENSIONS = ['httpfs', 'iceberg'];
|
|
80
|
+
/** What replaces a credential value in a message that came back carrying one. */
|
|
81
|
+
const REDACTED = '<redacted>';
|
|
82
|
+
/**
|
|
83
|
+
* A failed DuckDB step, carrying the words that follow "failed while" in the
|
|
84
|
+
* message a caller sees. Module-private and always caught by an adapter's own
|
|
85
|
+
* error translation, so it is a way of passing the step description outwards,
|
|
86
|
+
* never a type any caller can observe.
|
|
87
|
+
*/
|
|
88
|
+
class DuckDbStepFailure extends Error {
|
|
89
|
+
}
|
|
90
|
+
/** What an error says for itself, whether or not it is an `Error`. */
|
|
91
|
+
function describeError(err) {
|
|
92
|
+
return err instanceof Error ? err.message : String(err);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Quote an identifier for the statement text. `validateAnalyticsConfig` already
|
|
96
|
+
* holds `namespace` and `table` to `^[a-z0-9_]+$`, so there is no quote
|
|
97
|
+
* character to escape and this adds no second validation of a rule config owns.
|
|
98
|
+
* What it does add is reserved words: `web` and `page_views` need no quoting,
|
|
99
|
+
* but `order` and `table` match that same pattern and an operator may configure
|
|
100
|
+
* either.
|
|
101
|
+
*/
|
|
102
|
+
export function quoteIdentifier(identifier) {
|
|
103
|
+
return `"${identifier}"`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Quote a value as a SQL string literal, doubling every `'` so the literal
|
|
107
|
+
* cannot be closed from inside it.
|
|
108
|
+
*
|
|
109
|
+
* This is deliberately not conditional on the value having been validated.
|
|
110
|
+
* `ATTACH` takes its target as a literal and DuckDB accepts no placeholder
|
|
111
|
+
* there, so this is the one place in this module where a configured value is
|
|
112
|
+
* spelled into statement text rather than bound - and `runAndReadAll` executes
|
|
113
|
+
* *every* statement in the string it is handed, after `CREATE SECRET` has
|
|
114
|
+
* already put the operator's real AWS credentials into the session. A single
|
|
115
|
+
* unescaped quote in the attach target is therefore arbitrary SQL execution
|
|
116
|
+
* under those credentials, with `httpfs` loaded.
|
|
117
|
+
*
|
|
118
|
+
* Of the ARN's three components `tableBucket` is held to `^[0-9a-z-]{3,63}$`
|
|
119
|
+
* and `accountId` comes from STS, but `config.region` is checked only for
|
|
120
|
+
* truthiness (`packages/core/src/config.ts`), so today a region *is* a way in.
|
|
121
|
+
* That gap is being closed upstream; this escape stays regardless. An adapter
|
|
122
|
+
* must not be one upstream change - a relaxed rule, a new field spliced into
|
|
123
|
+
* the ARN, a caller that assembles a context by hand - away from executing
|
|
124
|
+
* arbitrary SQL. The escape is local to the quote it protects and holds
|
|
125
|
+
* whatever validation elsewhere does or does not do.
|
|
126
|
+
*/
|
|
127
|
+
function quoteLiteral(value) {
|
|
128
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The configured `<catalog>.<namespace>.<table>` triple every statement reads
|
|
132
|
+
* or writes, inside the attached catalog. This is the binding task 45
|
|
133
|
+
* delegated to the adapter layer.
|
|
134
|
+
*/
|
|
135
|
+
export function pageViewsRelation(config) {
|
|
136
|
+
return [CATALOG_ALIAS, config.namespace, config.table].map(quoteIdentifier).join('.');
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The S3 Tables bucket's ARN - the attach target, and the string every error
|
|
140
|
+
* an adapter raises names, because it is what an operator checks permissions
|
|
141
|
+
* and region against. The `s3tables` ARN form is the one
|
|
142
|
+
* `S3TablesClient.createTableBucket` (`packages/analytics/src/aws/s3tables.ts`)
|
|
143
|
+
* documents.
|
|
144
|
+
*/
|
|
145
|
+
function tableBucketArn(ctx, config) {
|
|
146
|
+
return `arn:aws:s3tables:${ctx.config.region}:${ctx.accountId}:bucket/${config.tableBucket}`;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The `CREATE SECRET` statement and the values it binds. The statement is a
|
|
150
|
+
* constant apart from whether the credentials carry a session token: an absent
|
|
151
|
+
* token drops the clause rather than binding a null, so DuckDB is never handed
|
|
152
|
+
* an empty `x-amz-security-token` to sign with.
|
|
153
|
+
*/
|
|
154
|
+
function secretStatement(credentials, region) {
|
|
155
|
+
const clauses = [
|
|
156
|
+
'TYPE s3',
|
|
157
|
+
'PROVIDER config',
|
|
158
|
+
'KEY_ID $access_key_id',
|
|
159
|
+
'SECRET $secret_access_key',
|
|
160
|
+
'REGION $region',
|
|
161
|
+
];
|
|
162
|
+
const bindings = {
|
|
163
|
+
access_key_id: credentials.accessKeyId,
|
|
164
|
+
secret_access_key: credentials.secretAccessKey,
|
|
165
|
+
region,
|
|
166
|
+
};
|
|
167
|
+
if (credentials.sessionToken !== undefined) {
|
|
168
|
+
clauses.push('SESSION_TOKEN $session_token');
|
|
169
|
+
bindings.session_token = credentials.sessionToken;
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
sql: `CREATE OR REPLACE SECRET ${SECRET_NAME} (${clauses.join(', ')})`,
|
|
173
|
+
bindings,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The `ATTACH` statement. `READ_ONLY` is an option the iceberg catalog accepts
|
|
178
|
+
* rather than one it ignores - DuckDB rejects an unrecognised attach option
|
|
179
|
+
* outright ("Unhandled options found"), so an attach that parses is an attach
|
|
180
|
+
* whose mode was understood - and on the read path it is the spec's "The
|
|
181
|
+
* dashboard's own attach stays read-only" as a property of the connection, not
|
|
182
|
+
* merely of the port's shape. The backfill's session omits it, because
|
|
183
|
+
* `INSERT` is the whole point of that session; nothing else about the two
|
|
184
|
+
* statements differs, so the difference is one clause rather than two
|
|
185
|
+
* statements that could drift.
|
|
186
|
+
*/
|
|
187
|
+
function attachStatement(target, readOnly) {
|
|
188
|
+
const options = [
|
|
189
|
+
'TYPE iceberg',
|
|
190
|
+
'ENDPOINT_TYPE s3_tables',
|
|
191
|
+
`SECRET ${SECRET_NAME}`,
|
|
192
|
+
...(readOnly ? ['READ_ONLY'] : []),
|
|
193
|
+
];
|
|
194
|
+
return `ATTACH ${quoteLiteral(target)} AS ${quoteIdentifier(CATALOG_ALIAS)} (${options.join(', ')})`;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Replace every credential value in `text`. DuckDB builds some of its own
|
|
198
|
+
* messages out of the values it was handed, so a bound credential can still
|
|
199
|
+
* reach a message an adapter is about to raise; this is what keeps it from
|
|
200
|
+
* leaving. Empty values are skipped - replacing the empty string matches
|
|
201
|
+
* everywhere.
|
|
202
|
+
*/
|
|
203
|
+
function redactorFor(credentials) {
|
|
204
|
+
const secrets = [
|
|
205
|
+
credentials.accessKeyId,
|
|
206
|
+
credentials.secretAccessKey,
|
|
207
|
+
credentials.sessionToken ?? '',
|
|
208
|
+
].filter((secret) => secret.length > 0);
|
|
209
|
+
return (text) => secrets.reduce((redacted, secret) => redacted.replaceAll(secret, REDACTED), text);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* One `page_views` result cell, in this package's own vocabulary. A SQL NULL
|
|
213
|
+
* becomes `undefined` so the caller sees an absent key, which is `QueryRow`'s
|
|
214
|
+
* rule. Counts arrive as `BIGINT`, and a range total as `HUGEINT`, so both
|
|
215
|
+
* arrive as `bigint`; anything past `Number.MAX_SAFE_INTEGER` raises rather
|
|
216
|
+
* than rounding, because a silently rounded view count is worse than no chart.
|
|
217
|
+
* DATE and the other typed columns come back as DuckDB value objects whose
|
|
218
|
+
* `toString` is the SQL rendering - a `day` reads back as `2026-08-01`, with
|
|
219
|
+
* no `Date` and so no time zone anywhere near it.
|
|
220
|
+
*/
|
|
221
|
+
function toQueryValue(column, value) {
|
|
222
|
+
if (value === null)
|
|
223
|
+
return undefined;
|
|
224
|
+
if (typeof value === 'bigint') {
|
|
225
|
+
const asNumber = Number(value);
|
|
226
|
+
if (!Number.isSafeInteger(asNumber)) {
|
|
227
|
+
throw new Error(`analytics result column "${column}" is ${value}, past the largest integer JavaScript counts exactly`);
|
|
228
|
+
}
|
|
229
|
+
return asNumber;
|
|
230
|
+
}
|
|
231
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
232
|
+
return value;
|
|
233
|
+
}
|
|
234
|
+
return String(value);
|
|
235
|
+
}
|
|
236
|
+
/** One result row, with every SQL NULL dropped rather than carried as a null. */
|
|
237
|
+
function toQueryRow(row) {
|
|
238
|
+
const converted = {};
|
|
239
|
+
for (const [column, value] of Object.entries(row)) {
|
|
240
|
+
const cell = toQueryValue(column, value);
|
|
241
|
+
if (cell !== undefined)
|
|
242
|
+
converted[column] = cell;
|
|
243
|
+
}
|
|
244
|
+
return converted;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Open a real DuckDB connection - the default {@link DuckDbConnect}, and the
|
|
248
|
+
* one place the vendor package is called. Exported so the test suite can run
|
|
249
|
+
* statements against a real local table without naming the vendor package
|
|
250
|
+
* itself, which is what keeps DuckDB confined to this file.
|
|
251
|
+
*/
|
|
252
|
+
export const connectDuckDb = async () => {
|
|
253
|
+
const instance = await DuckDBInstance.create(':memory:');
|
|
254
|
+
const connection = await instance.connect();
|
|
255
|
+
return {
|
|
256
|
+
async run(sql, bindings) {
|
|
257
|
+
const reader = await connection.runAndReadAll(sql, { ...bindings });
|
|
258
|
+
return reader.getRowObjects().map(toQueryRow);
|
|
259
|
+
},
|
|
260
|
+
close() {
|
|
261
|
+
connection.closeSync();
|
|
262
|
+
instance.closeSync();
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
};
|
|
266
|
+
/**
|
|
267
|
+
* Build a session. Nothing here connects: the connection is opened on the
|
|
268
|
+
* first {@link DuckDbSession.open}, so a command that never reaches the table
|
|
269
|
+
* starts nothing and binds its own listener first.
|
|
270
|
+
*/
|
|
271
|
+
export function createDuckDbSession(opts) {
|
|
272
|
+
const config = resolveAnalyticsConfig(opts.ctx);
|
|
273
|
+
const attachTarget = tableBucketArn(opts.ctx, config);
|
|
274
|
+
const relation = pageViewsRelation(config);
|
|
275
|
+
const connect = opts.connect ?? connectDuckDb;
|
|
276
|
+
/**
|
|
277
|
+
* Redaction needs the resolved credentials, and they are resolved inside the
|
|
278
|
+
* session; this holds the redactor the current session installed so a failure
|
|
279
|
+
* raised while executing a statement is scrubbed by the same rule a failure
|
|
280
|
+
* raised while creating the secret is.
|
|
281
|
+
*/
|
|
282
|
+
let redact = (text) => text;
|
|
283
|
+
let pending;
|
|
284
|
+
async function step(description, run) {
|
|
285
|
+
try {
|
|
286
|
+
return await run();
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
throw new DuckDbStepFailure(`${description}: ${redact(describeError(err))}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async function openSession() {
|
|
293
|
+
const credentials = await step('resolving AWS credentials', () => opts.credentials());
|
|
294
|
+
redact = redactorFor(credentials);
|
|
295
|
+
const connection = await step('opening a DuckDB connection', connect);
|
|
296
|
+
try {
|
|
297
|
+
for (const extension of REQUIRED_EXTENSIONS) {
|
|
298
|
+
await step(`installing the ${extension} extension`, () => connection.run(`INSTALL ${extension}`, {}));
|
|
299
|
+
await step(`loading the ${extension} extension`, () => connection.run(`LOAD ${extension}`, {}));
|
|
300
|
+
}
|
|
301
|
+
const secret = secretStatement(credentials, opts.ctx.config.region);
|
|
302
|
+
await step('creating the credentials secret', () => connection.run(secret.sql, secret.bindings));
|
|
303
|
+
await step(opts.readOnly ? 'attaching the catalog read-only' : 'attaching the catalog', () => connection.run(attachStatement(attachTarget, opts.readOnly), {}));
|
|
304
|
+
}
|
|
305
|
+
catch (err) {
|
|
306
|
+
connection.close();
|
|
307
|
+
throw err;
|
|
308
|
+
}
|
|
309
|
+
return connection;
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
config,
|
|
313
|
+
attachTarget,
|
|
314
|
+
relation,
|
|
315
|
+
step,
|
|
316
|
+
open() {
|
|
317
|
+
const current = (pending ??= openSession());
|
|
318
|
+
return current.catch((err) => {
|
|
319
|
+
if (pending === current)
|
|
320
|
+
pending = undefined;
|
|
321
|
+
throw err;
|
|
322
|
+
});
|
|
323
|
+
},
|
|
324
|
+
detail(err, fallback) {
|
|
325
|
+
return err instanceof DuckDbStepFailure
|
|
326
|
+
? err.message
|
|
327
|
+
: `${fallback}: ${redact(describeError(err))}`;
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer theme,base,components,utilities;:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;--color-surface-100:var(--lightningcss-light,#fff)var(--lightningcss-dark,#16181d);--color-surface-200:var(--lightningcss-light,#f4f5f7)var(--lightningcss-dark,#1d2026);--color-surface-content:var(--lightningcss-light,#1b1f27)var(--lightningcss-dark,#e6e8ee);--color-surface-muted:var(--lightningcss-light,#5b6473)var(--lightningcss-dark,#98a1b2);--color-surface-border:var(--lightningcss-light,#dfe2e8)var(--lightningcss-dark,#2c313a);--color-primary:var(--lightningcss-light,#2f5fd0)var(--lightningcss-dark,#7aa2f7);--radius:8px;--gap:16px}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}*{box-sizing:border-box}body{background:var(--color-surface-200);color:var(--color-surface-content);margin:0;font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,sans-serif;font-size:14px;line-height:1.5}.page{max-width:1200px;padding:var(--gap);margin:0 auto}.page-header{justify-content:space-between;align-items:flex-end;gap:var(--gap);margin-bottom:var(--gap);flex-wrap:wrap;display:flex}.page-header h1{margin:0;font-size:20px;font-weight:600}.page-header p{color:var(--color-surface-muted);margin:4px 0 0}.controls{flex-wrap:wrap;align-items:flex-end;gap:12px;display:flex}.control{flex-direction:column;gap:4px;display:flex}.control span{color:var(--color-surface-muted);font-size:12px}.control input,.control select{background:var(--color-surface-100);border:1px solid var(--color-surface-border);border-radius:var(--radius);color:inherit;font:inherit;padding:6px 8px}.range-error{border:1px solid var(--color-surface-border);border-radius:var(--radius);background:var(--color-surface-100);margin-bottom:var(--gap);border-left:3px solid #d04a4a;padding:10px 12px}.panels{gap:var(--gap);grid-template-columns:repeat(auto-fit,minmax(360px,1fr));display:grid}.panel{background:var(--color-surface-100);border:1px solid var(--color-surface-border);border-radius:var(--radius);flex-direction:column;gap:8px;padding:14px 16px 16px;display:flex}.panel h2{margin:0;font-size:15px;font-weight:600}.panel-meaning{color:var(--color-surface-muted);margin:0;font-size:12px}.panel-state{color:var(--color-surface-muted);text-align:center;justify-content:center;align-items:center;height:220px;display:flex}.panel-error{color:#c0392b}.panel-chart{height:220px}.panel-total{border-top:1px solid var(--color-surface-border);justify-content:space-between;gap:8px;margin:0;padding-top:8px;display:flex}.panel-total dt{color:var(--color-surface-muted)}.panel-total dd{font-variant-numeric:tabular-nums;margin:0;font-weight:600}.panel-note{color:var(--color-surface-muted);margin:0;font-size:12px}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer base{:where(.lc-layout-svg.svelte-4rngwg){position:absolute;inset:0;overflow:visible}:where(.lc-layout-svg.svelte-4rngwg).disablePointerEvents{pointer-events:none}:where(.lc-layout-svg.svelte-4rngwg).clip{overflow:hidden}:where(.lc-layout-canvas.svelte-dr1886){position:absolute;inset:0}:where(.lc-layout-canvas.svelte-dr1886).disablePointerEvents{pointer-events:none}:where(.lc-hit-canvas.svelte-dr1886){pointer-events:none;image-rendering:pixelated;opacity:0;position:absolute;inset:0}:where(.lc-hit-canvas.svelte-dr1886).debug{border:1px solid var(--color-danger,red);opacity:1}:where(.lc-layout-html.svelte-xy3xrb){position:absolute;inset:0}:where(.lc-layout-html.svelte-xy3xrb).disablePointerEvents{pointer-events:none}:where(.lc-layout-html.svelte-xy3xrb).clip{overflow:hidden}}@layer components{:where(.lc-tooltip-context-container.svelte-1xyc5r3){position:absolute}:where(.lc-tooltip-context.svelte-1xyc5r3){touch-action:var(--touch-action);position:absolute}:where(.lc-tooltip-context.svelte-1xyc5r3).debug{outline:1px solid var(--color-danger);background-color:color-mix(in oklab, var(--color-danger) 10%, transparent)}:where(.lc-tooltip-voronoi-path).debug{stroke:var(--color-danger);fill:color-mix(in oklab, var(--color-danger) 10%, transparent)}:where(.lc-tooltip-rect.svelte-1xyc5r3){fill:#0000}:where(.lc-tooltip-rect.svelte-1xyc5r3).debug{stroke:var(--color-danger);fill:color-mix(in oklab, var(--color-danger) 10%, transparent)}:where(.lc-tooltip-quadtree-rect.svelte-1xyc5r3){fill:#0000}:where(.lc-tooltip-quadtree-rect.svelte-1xyc5r3).debug{stroke:var(--color-danger);fill:color-mix(in oklab, var(--color-danger) 10%, transparent)}:where(.lc-debug-frame){--fill-color:color-mix(in oklab, var(--color-danger) 10%, transparent)}:where(.lc-axis-rule),:where(.lc-axis-tick){--stroke-color:color-mix(in oklab, var(--color-surface-content,currentColor) 50%, transparent)}:where(.lc-axis-grid){--stroke-color:color-mix(in oklab, var(--color-surface-content,currentColor) 10%, transparent)}:where(.lc-axis-label,.lc-axis-tick-label){stroke:var(--color-surface-100,var(--lightningcss-light,#fff)var(--lightningcss-dark,#000));stroke-width:2px;font-size:10px;font-weight:300}:where(.lc-rule-x-line,.lc-rule-y-line,.lc-rule-x-radial-line,.lc-rule-y-radial-circle):not([class*=lc-axis],[class*=lc-grid]){--stroke-color:color-mix(in oklab, var(--color-surface-content,currentColor) 50%, transparent)}:where(.lc-rule-y-radial-circle){--fill-color:none}:where(.lc-grid-x-rule,.lc-grid-x-end-rule,.lc-grid-x-radial-line,.lc-grid-y-rule,.lc-grid-y-end-rule,.lc-grid-y-radial-line){--stroke-color:color-mix(in oklab, var(--color-surface-content,currentColor) 10%, transparent)}:where(.lc-grid-y-radial-circle){--fill-color:none;--stroke-color:color-mix(in oklab, var(--color-surface-content,currentColor) 10%, transparent)}:where(.lc-highlight-area){--fill-color:color-mix(in oklab, var(--color-surface-content,currentColor) 5%, transparent)}:where(.lc-highlight-bar){--fill-color:var(--color-primary,currentColor)}:where(.lc-highlight-line){--stroke-color:color-mix(in oklab, var(--color-surface-content,currentColor) 20%, transparent);stroke-width:2px;stroke-dasharray:2 2;pointer-events:none}:where(.lc-highlight-point){--stroke-color:white;--fill-color:var(--color-primary,currentColor);paint-order:stroke;filter:drop-shadow(var(--drop-shadow-sm,0 1px 2px #00000026))}}.lc-root-container.svelte-17r60jn,.lc-root-container.svelte-17r60jn *{box-sizing:border-box}.lc-root-container.svelte-17r60jn{-webkit-user-select:var(--lc-user-select,none);user-select:var(--lc-user-select,none)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer base{:where(.lc-brush-context.svelte-3rn8dw){touch-action:none;position:absolute}:where(.lc-brush-container.svelte-3rn8dw){position:absolute}:where(.lc-brush-range.svelte-3rn8dw){cursor:move;-webkit-user-select:none;user-select:none;z-index:10;background:color-mix(in oklab, var(--color-surface-content,currentColor) 10%, transparent);position:absolute}:where(.lc-brush-handle.svelte-3rn8dw){-webkit-user-select:none;user-select:none;z-index:10;position:absolute}:where(.lc-brush-handle.svelte-3rn8dw)[data-position=top],:where(.lc-brush-handle.svelte-3rn8dw)[data-position=bottom]{cursor:ns-resize}:where(.lc-brush-handle.svelte-3rn8dw)[data-position=left],:where(.lc-brush-handle.svelte-3rn8dw)[data-position=right]{cursor:ew-resize}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer components{:where(.lc-annotation-line){--stroke-color:var(--color-surface-content,currentColor)}:where(.lc-annotation-line-label),:where(.lc-annotation-point-label){pointer-events:none;font-size:12px}:where(.lc-annotation-point-ring){--fill-color:none;--stroke-color:var(--color-surface-content,currentColor)}:where(.lc-annotation-point-link){--stroke-color:var(--color-surface-content,currentColor);fill:none;pointer-events:none}:where(.lc-annotation-range-label){pointer-events:none;font-size:12px}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer base{:where(.lc-layout-svg .lc-circle,svg.lc-circle):not([fill]){fill:var(--fill-color)}:where(.lc-layout-svg .lc-circle,svg.lc-circle):not([stroke]){stroke:var(--stroke-color)}:where(.lc-circle){--fill-color:var(--color-surface-content,currentColor);--stroke-color:initial}:where(.lc-layout-html .lc-circle){box-sizing:border-box}:where(.lc-layout-html .lc-circle):not([background]){background:var(--fill-color)}:where(.lc-layout-html .lc-circle):not([border-color]){border-color:var(--stroke-color)}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer components{:where(.lc-tooltip-header.svelte-bwtgwj){white-space:nowrap;border-bottom:1px solid color-mix(in oklab, var(--color-surface-content,currentColor) 20%, transparent);align-items:center;gap:8px;margin-bottom:4px;padding-bottom:4px;font-weight:600;display:flex}:where(.lc-tooltip-header-color.svelte-bwtgwj){background-color:var(--color);border-radius:9999px;flex-shrink:0;width:8px;height:8px;display:inline-block}:where(.lc-tooltip-item-root.svelte-mbbltt){display:contents}:where(.lc-tooltip-item-root[data-highlighted].svelte-mbbltt)>:where(.lc-tooltip-item-label.svelte-mbbltt,.lc-tooltip-item-value.svelte-mbbltt){opacity:1;transition:opacity .15s}:where(.lc-tooltip-item-root[data-highlighted=false].svelte-mbbltt)>:where(.lc-tooltip-item-label.svelte-mbbltt,.lc-tooltip-item-value.svelte-mbbltt){opacity:.1}:where(.lc-tooltip-item-color.svelte-mbbltt){background-color:var(--color);border-radius:9999px;width:8px;height:8px;display:inline-block}:where(.lc-tooltip-item-label.svelte-mbbltt){white-space:nowrap;align-items:center;gap:8px;display:flex}:where(.lc-tooltip-item-value.svelte-mbbltt){font-variant-numeric:tabular-nums}:where(.lc-tooltip-item-value.svelte-mbbltt)[data-align=right]{text-align:right}:where(.lc-tooltip-item-value.svelte-mbbltt)[data-align=center]{text-align:center}:where(.lc-tooltip-list.svelte-1i64ej2){grid-template-columns:1fr auto;align-items:start;gap:4px 8px;display:grid}:where(.lc-tooltip-separator.svelte-13b11yj){background-color:color-mix(in oklab, var(--color-surface-content,currentColor) 20%, transparent);border-radius:4px;grid-column:1/-1;height:1px;margin-top:4px;margin-bottom:4px}:where(.lc-tooltip-root.svelte-1019wmy){z-index:50;-webkit-user-select:none;user-select:none;position:absolute}:where(.lc-tooltip-root.svelte-1019wmy).portaled{position:fixed}:where(.lc-tooltip-root.svelte-1019wmy).disablePointerEvents{pointer-events:none}:where(.lc-tooltip-container.svelte-1019wmy):not([data-variant=none]){-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);border-radius:.25rem;height:100%;padding:4px 8px;font-size:.875rem;line-height:1.25rem;box-shadow:0 2px 1px -1px #0003,0 1px 1px #00000024,0 1px 3px #0000001f}:where(.lc-tooltip-container.svelte-1019wmy)[data-variant=default]{color:var(--color-surface-content,currentColor);background-color:color-mix(in oklab, var(--lightningcss-light,var(--color-surface-100,white))var(--lightningcss-dark,var(--color-surface-300,black)) 90%, transparent)}:where(.lc-tooltip-container.svelte-1019wmy)[data-variant=default] .label{color:color-mix(in oklab, var(--color-surface-content,currentColor) 75%, transparent)}:where(.lc-tooltip-container.svelte-1019wmy)[data-variant=invert]{color:var(--color-surface-100,var(--lightningcss-light,#fff)var(--lightningcss-dark,#000));background-color:color-mix(in oklab, var(--color-surface-content,currentColor) 90%, transparent)}:where(.lc-tooltip-container.svelte-1019wmy)[data-variant=invert] .label{color:color-mix(in oklab, var(--color-surface-100,var(--lightningcss-light,#fff)var(--lightningcss-dark,#000)) 50%, transparent)}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer base{:where(.lc-group-div.svelte-yxzquv){position:absolute}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer components{:where(.lc-labels-text){--fill-color:var(--color-surface-content,currentColor);--stroke-color:var(--color-surface-100,var(--lightningcss-light,#fff)var(--lightningcss-dark,#000));font-size:12px}:where(.lc-labels-text)[data-placement=inside],:where(.lc-labels-text)[data-placement=center]{--fill-color:var(--color-surface-100,var(--lightningcss-light,#fff)var(--lightningcss-dark,#000));--stroke-color:var(--color-surface-content,currentColor)}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer components{:where(.lc-legend-container.svelte-1s27lqi){z-index:1;display:inline-block}:where(.lc-legend-container.svelte-1s27lqi)[data-placement]{position:absolute}:where(.lc-legend-container.svelte-1s27lqi)[data-placement=top-left]{top:0;left:0}:where(.lc-legend-container.svelte-1s27lqi)[data-placement=top]{top:0;left:50%;transform:translate(-50%)}:where(.lc-legend-container.svelte-1s27lqi)[data-placement=top-right]{top:0;right:0}:where(.lc-legend-container.svelte-1s27lqi)[data-placement=left]{top:50%;left:0;transform:translateY(-50%)}:where(.lc-legend-container.svelte-1s27lqi)[data-placement=center]{top:50%;left:50%;transform:translate(-50%,-50%)}:where(.lc-legend-container.svelte-1s27lqi)[data-placement=right]{top:50%;right:0;transform:translateY(-50%)}:where(.lc-legend-container.svelte-1s27lqi)[data-placement=bottom-left]{bottom:0;left:0}:where(.lc-legend-container.svelte-1s27lqi)[data-placement=bottom]{bottom:0;left:50%;transform:translate(-50%)}:where(.lc-legend-container.svelte-1s27lqi)[data-placement=bottom-right]{bottom:0;right:0}:where(.lc-legend-title.svelte-1s27lqi){font-size:10px;font-weight:600}:where(.lc-legend-ramp-svg.svelte-1s27lqi){overflow:visible}:where(.lc-legend-tick-text.svelte-1s27lqi){fill:var(--color-surface-content,currentColor);font-size:10px}:where(.lc-legend-tick-line.svelte-1s27lqi){stroke:var(--color-surface-content,currentColor)}:where(.lc-legend-indicator.svelte-1s27lqi){fill:var(--color-surface-content,currentColor)}:where(.lc-legend-swatch-group.svelte-1s27lqi){gap:.25rem 1rem;display:flex}:where(.lc-legend-swatch-group.svelte-1s27lqi)[data-orientation=vertical]{flex-direction:column}:where(.lc-legend-swatch-button.svelte-1s27lqi){white-space:nowrap;text-overflow:ellipsis;align-items:center;gap:.25rem;display:flex;overflow:hidden}:where(.lc-legend-swatch.svelte-1s27lqi){border-radius:9999px;flex-shrink:0;width:16px;height:16px}:where(.lc-legend-swatch-label.svelte-1s27lqi){color:var(--color-surface-content,currentColor);text-overflow:ellipsis;white-space:nowrap;font-size:.75rem;line-height:1.33333;overflow:hidden}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer base{:where(.lc-layout-svg .lc-line,svg.lc-line):not([stroke]){stroke:var(--stroke-color)}:where(.lc-line){--stroke-color:var(--color-surface-content,currentColor)}:where(.lc-layout-html .lc-line):not([background-color]){background-color:var(--stroke-color)}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer base{:where(.lc-marker){overflow:visible}:where(.lc-marker)[data-type=arrow],:where(.lc-marker)[data-type=circle-stroke],:where(.lc-marker)[data-type=square-stroke],:where(.lc-marker)[data-type=line]{fill:none;stroke:context-stroke;stroke-linecap:round;stroke-linejoin:round}:where(.lc-marker)[data-type=circle]{stroke:var(--color-surface-100,var(--lightningcss-light,#fff)var(--lightningcss-dark,#000))}:where(.lc-marker)[data-type=triangle],:where(.lc-marker)[data-type=dot],:where(.lc-marker)[data-type=circle],:where(.lc-marker)[data-type=square]{fill:context-stroke}:where(.lc-marker)[data-type=circle-stroke],:where(.lc-marker)[data-type=square-stroke]{fill:var(--color-surface-100,var(--lightningcss-light,#fff)var(--lightningcss-dark,#000))}:where(.lc-path){--fill-color:transparent;--stroke-color:var(--color-surface-content,currentColor)}:where(.lc-layout-svg .lc-path,svg.lc-path):not([fill]){fill:var(--fill-color)}:where(.lc-layout-svg .lc-path,svg.lc-path):not([stroke]){stroke:var(--stroke-color)}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer base{:where(.lc-layout-svg .lc-rect,svg.lc-rect):not([fill]){fill:var(--fill-color)}:where(.lc-layout-svg .lc-rect,svg.lc-rect):not([stroke]){stroke:var(--stroke-color)}:where(.lc-rect){--fill-color:var(--color-surface-content,currentColor);--stroke-color:initial}:where(.lc-layout-html .lc-rect){box-sizing:border-box}:where(.lc-layout-html .lc-rect):not([background]){background:var(--fill-color)}:where(.lc-layout-html .lc-rect):not([border-color]){border-color:var(--stroke-color)}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer base{:where(.lc-text-svg){paint-order:stroke;overflow:visible}:where(.lc-layout-svg .lc-text,svg.lc-text):not([fill]){color:var(--fill-color);fill:currentColor}:where(.lc-layout-svg .lc-text,svg.lc-text):not([stroke]){stroke:var(--stroke-color)}:where(.lc-text){--fill-color:var(--color-surface-content,currentColor);--stroke-color:initial}:where(.lc-layout-html .lc-text):not([background-color]){color:var(--fill-color)}:where(.lc-layout-html .lc-text):not([border-color]){border-color:var(--stroke-color)}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer base{:where(.lc-transform-context.svelte-1t4nypg){height:100%}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer components{:where(.lc-voronoi-path,.lc-voronoi-geo-path){fill:#0000;stroke:#0000}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{G as e,dt as t,lt as n,st as r}from"./D2BnX0Uk.js";import{f as i}from"./Bd1gDe3Y.js";import{t as a}from"./Db0q5_zr.js";import{t as o}from"./CCch3uox.js";import{t as s}from"./RobXhXPM.js";var c=typeof window<`u`,l=[`TRACE`,`DEBUG`,`INFO`,`WARN`,`ERROR`],u=class{name;constructor(e){this.name=e}trace(...e){this.log(`TRACE`,...e)}debug(...e){this.log(`DEBUG`,...e)}info(...e){this.log(`INFO`,...e)}warn(...e){this.log(`WARN`,...e)}error(...e){this.log(`ERROR`,...e)}log(e,...t){let n=(c?localStorage.getItem(`logger`)?.split(`,`).map(e=>e.split(`:`))??[]:[]).find(e=>e[0]===this.name);if(n!=null&&l.indexOf(e)>=l.indexOf(n[1]??`DEBUG`))switch(e){case`TRACE`:console.trace(`%c${this.name} %c${e}`,`color: hsl(200deg, 10%, 50%)`,`color: hsl(200deg, 40%, 50%)`,...t);break;case`DEBUG`:console.log(`%c${this.name} %c${e}`,`color: hsl(200deg, 10%, 50%)`,`color: hsl(200deg, 40%, 50%)`,...t);break;case`INFO`:console.log(`%c${this.name} %c${e}`,`color: hsl(200deg, 10%, 50%)`,`color: hsl(60deg, 100%, 50%)`,...t);break;case`WARN`:console.warn(`%c${this.name} %c${e}`,`color: hsl(200deg, 10%, 50%)`,`color: hsl(30deg, 100%, 50%)`,...t);break;case`ERROR`:console.warn(`%c${this.name} %c${e}`,`color: hsl(200deg, 10%, 50%)`,`color: hsl(0deg, 100%, 50%)`,...t)}}};function d(e,t){if(e[0]==null||e[1]==null||typeof e[0]!=`string`)return e;let n=t.indexOf(e[0]),r=t.indexOf(e[1]);return n===-1||r===-1?e:t.slice(n,r+1)}function f(e){return e.length>0&&typeof e[0]==`string`}function p(e,t,n){return n.indexOf(e)<=n.indexOf(t)?e:t}function m(e,t,n){return n.indexOf(e)>=n.indexOf(t)?e:t}function h(e,t,n,r){let i=r.indexOf(e),a=r.indexOf(t),o=r.indexOf(n);return i===-1||i<a?t:i>o?n:e}var g=class{ctx;#e=t(r([null,null]));get x(){return e(this.#e)}set x(e){n(this.#e,e,!0)}#t=t(r([null,null]));get y(){return e(this.#t)}set y(e){n(this.#t,e,!0)}#n=t();get active(){return e(this.#n)}set active(e){n(this.#n,e,!0)}#r=t(`x`);get axis(){return e(this.#r)}set axis(e){n(this.#r,e,!0)}#i=t(0);get handleSize(){return e(this.#i)}set handleSize(e){n(this.#i,e,!0)}#a=t();get minExtent(){return e(this.#a)}set minExtent(e){n(this.#a,e,!0)}#o=t();get maxExtent(){return e(this.#o)}set maxExtent(e){n(this.#o,e,!0)}#s=t();get constrain(){return e(this.#s)}set constrain(e){n(this.#s,e,!0)}#c=t(!0);get constrainToDomain(){return e(this.#c)}set constrainToDomain(e){n(this.#c,e,!0)}constructor(e,t){this.ctx=e,this.x=t?.x??[null,null],this.y=t?.y??[null,null],this.active=t?.active,this.axis=t?.axis??`x`,this.minExtent=t?.minExtent,this.maxExtent=t?.maxExtent,this.constrain=t?.constrain,this.constrainToDomain=t?.constrainToDomain??!0}get xDomainMin(){return this.ctx?.baseXScale.domain()[0]}get xDomainMax(){return this.ctx?.baseXScale.domain().at(-1)}get yDomainMin(){return this.ctx?.baseYScale.domain()[0]}get yDomainMax(){return this.ctx?.baseYScale.domain().at(-1)}get range(){if(!this.ctx)return{x:0,y:0,width:0,height:0};let e=this.ctx.xScale.bandwidth?.()??0,t=this.ctx.yScale.bandwidth?.()??0,n=this.ctx.xScale(this.x?.[0]),r=this.ctx.xScale(this.x?.[1])+e,i=this.ctx.yScale(this.y?.[1]),a=this.ctx.yScale(this.y?.[0])+t;return{x:this.axis===`both`||this.axis===`x`?n:0,y:this.axis===`both`||this.axis===`y`?i:0,width:this.axis===`both`||this.axis===`x`?r-n:this.ctx.width,height:this.axis===`both`||this.axis===`y`?a-i:this.ctx.height}}reset(){this.active=!1,this.x=[null,null],this.y=[null,null]}selectAll(){this.active=!0,this.x=[this.xDomainMin,this.xDomainMax],this.y=[this.yDomainMin,this.yDomainMax],this._applyConstraints({x:this.xDomainMin,y:this.yDomainMin})}move(e){`x`in e&&(this.x=e.x??[null,null]),`y`in e&&(this.y=e.y??[null,null]),this._applyConstraints({x:this.x[0],y:this.y[0]});let t=this.x[0]!=null&&this.x[1]!=null,n=this.y[0]!=null&&this.y[1]!=null;this.active=this.axis===`x`?t:this.axis===`y`?n:t||n}setRange(e,t){this.active=!0;let n=this.ctx?.baseXScale.domain()??[],r=this.ctx?.baseYScale.domain()??[];this.x=f(n)?[h(p(e.x,t.x,n),this.xDomainMin,this.xDomainMax,n),h(m(e.x,t.x,n),this.xDomainMin,this.xDomainMax,n)]:[i(o([e.x,t.x]),this.xDomainMin,this.xDomainMax),i(a([e.x,t.x]),this.xDomainMin,this.xDomainMax)],this.y=f(r)?[h(p(e.y,t.y,r),this.yDomainMin,this.yDomainMax,r),h(m(e.y,t.y,r),this.yDomainMin,this.yDomainMax,r)]:[i(o([e.y,t.y]),this.yDomainMin,this.yDomainMax),i(a([e.y,t.y]),this.yDomainMin,this.yDomainMax)],this._applyConstraints({x:e.x,y:e.y})}moveRange(e,t){let n=this.ctx?.baseXScale.domain()??[],r=this.ctx?.baseYScale.domain()??[];if(f(n)){let r=n.indexOf(e.value.x),i=n.indexOf(t.x),a=n.indexOf(e.x[0]),o=n.indexOf(e.x[1]),s=Math.max(-a,Math.min(n.length-1-o,i-r));this.x=[n[a+s],n[o+s]]}else{let n=i(t.x-e.value.x,this.xDomainMin-+e.x[0],this.xDomainMax-+e.x[1]);this.x=[s(e.x[0],n),s(e.x[1],n)]}if(f(r)){let n=r.indexOf(e.value.y),i=r.indexOf(t.y),a=r.indexOf(e.y[0]),o=r.indexOf(e.y[1]),s=Math.max(-a,Math.min(r.length-1-o,i-n));this.y=[r[a+s],r[o+s]]}else{let n=i(t.y-e.value.y,this.yDomainMin-+e.y[0],this.yDomainMax-+e.y[1]);this.y=[s(e.y[0],n),s(e.y[1],n)]}this._applyConstraints({x:null,y:null})}adjustEdge(e,t,n){let r=this.ctx?.baseXScale.domain()??[],a=this.ctx?.baseYScale.domain()??[],o=f(r),s=f(a),c=e=>o?h(e,this.xDomainMin,this.xDomainMax,r):i(e,this.xDomainMin,this.xDomainMax),l=e=>s?h(e,this.yDomainMin,this.yDomainMax,a):i(e,this.yDomainMin,this.yDomainMax),u=(e,t)=>o?r.indexOf(e)<r.indexOf(t):e<+t,d=(e,t)=>o?r.indexOf(e)>r.indexOf(t):e>+t,p=(e,t)=>s?a.indexOf(e)<a.indexOf(t):e<+t,m=(e,t)=>s?a.indexOf(e)>a.indexOf(t):e>+t;switch(e){case`top`:this.y=[l(p(n.y,t.y[0])?n.y:t.y[0]),l(p(n.y,t.y[0])?t.y[0]:n.y)];break;case`bottom`:this.y=[l(m(n.y,t.y[1])?t.y[1]:n.y),l(m(n.y,t.y[1])?n.y:t.y[1])];break;case`left`:this.x=[c(d(n.x,t.x[1])?t.x[1]:n.x),c(d(n.x,t.x[1])?n.x:t.x[1])];break;case`right`:this.x=[c(u(n.x,t.x[0])?n.x:t.x[0]),c(u(n.x,t.x[0])?t.x[0]:n.x)]}switch(e){case`left`:this._applyConstraints({x:t.x[1]});break;case`right`:this._applyConstraints({x:t.x[0]});break;case`top`:this._applyConstraints({y:t.y[0]});break;case`bottom`:this._applyConstraints({y:t.y[1]})}}_anchorIndex(e,t){if(t==null)return null;let n=t?.valueOf?.()??t;return(e[0]?.valueOf?.()??e[0])===n?0:(e[1]?.valueOf?.()??e[1])===n?1:null}_clampExtent(e,t,n){let r=this.minExtent?.[e],a=this.maxExtent?.[e];if(r==null&&a==null||t[0]==null||t[1]==null)return t;let o=(e===`x`?this.ctx?.baseXScale:this.ctx?.baseYScale)?.domain()??[],c=e===`x`?this.xDomainMin:this.yDomainMin,l=e===`x`?this.xDomainMax:this.yDomainMax,u=this._anchorIndex(t,n);if(f(o)){let e=o.indexOf(t[0]),n=o.indexOf(t[1]);if(e===-1||n===-1)return t;e>n&&([e,n]=[n,e]);let i=n-e+1;if(a!=null&&i>a){if(u===1)e=n-(a-1);else if(u===0)n=e+(a-1);else{let t=i-a;e+=Math.ceil(t/2),n-=Math.floor(t/2)}}else if(r!=null&&i<r){if(u===1)e=n-(r-1);else if(u===0)n=e+(r-1);else{let t=r-i;e-=Math.floor(t/2),n+=Math.ceil(t/2)}}let s=o.length-1;return e<0&&(n+=-e,e=0),n>s&&(e-=n-s,n=s),e=Math.max(0,e),n=Math.min(s,n),[o[e],o[n]]}let d=t[0],p=t[1],m=+p-d;if(a!=null&&m>a){if(u===1)d=s(p,-a);else if(u===0)p=s(d,a);else{let e=(m-a)/2;d=s(d,e),p=s(p,-e)}}else if(r!=null&&m<r){if(u===1)d=s(p,-r);else if(u===0)p=s(d,r);else{let e=(r-m)/2;d=s(d,-e),p=s(p,e)}c!=null&&+d<+c&&(p=s(p,+c-d),d=c),l!=null&&+p>+l&&(d=s(d,+l-p),p=l),d=i(d,c,l),p=i(p,c,l)}return[d,p]}_clampToDomain(e,t){if(t[0]==null||t[1]==null)return t;let n=(e===`x`?this.ctx?.baseXScale:this.ctx?.baseYScale)?.domain()??[],r=e===`x`?this.xDomainMin:this.yDomainMin,a=e===`x`?this.xDomainMax:this.yDomainMax;return f(n)?[h(t[0],r,a,n),h(t[1],r,a,n)]:[i(t[0],r,a),i(t[1],r,a)]}_applyConstraints(e){if(`x`in e&&this.x[0]!=null&&this.x[1]!=null&&(this.x=this._clampExtent(`x`,[this.x[0],this.x[1]],e.x)),`y`in e&&this.y[0]!=null&&this.y[1]!=null&&(this.y=this._clampExtent(`y`,[this.y[0],this.y[1]],e.y)),this.constrain){let e=this.constrain({x:this.x,y:this.y});this.x=e.x,this.y=e.y}this.constrainToDomain&&(this.x=this._clampToDomain(`x`,this.x),this.y=this._clampToDomain(`y`,this.y))}syncFromExternal(e,t){let n=e??[null,null],r=t??[null,null];(this.x[0]?.valueOf()!==n[0]?.valueOf()||this.x[1]?.valueOf()!==n[1]?.valueOf())&&(this.x=n),(this.y[0]?.valueOf()!==r[0]?.valueOf()||this.y[1]?.valueOf()!==r[1]?.valueOf())&&(this.y=r);let i=e!=null&&e[0]!=null&&e[1]!=null&&(e[0].valueOf()!==this.xDomainMin?.valueOf()||e[1].valueOf()!==this.xDomainMax?.valueOf()),a=t!=null&&t[0]!=null&&t[1]!=null&&(t[0].valueOf()!==this.yDomainMin?.valueOf()||t[1].valueOf()!==this.yDomainMax?.valueOf()),o=this.axis===`x`?i:this.axis===`y`?a:i||a;this.active!==o&&(this.active=o)}};export{d as n,u as r,g as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,Ct as t,D as n,Dt as r,F as i,G as a,H as o,I as s,J as c,L as l,V as u,_ as d,_t as f,d as p,dt as m,et as h,f as g,gt as _,i as v,it as y,j as b,lt as x,ot as S,rt as C,s as w,tt as T,v as E,y as D}from"./D2BnX0Uk.js";import"./xihTtKlq.js";import{t as O}from"./Bd1gDe3Y.js";import{s as k}from"./DKMlMI4a.js";import{c as A}from"./V9ZjaxiY.js";import{t as j}from"./BID1NNRh.js";import{r as M,t as N}from"./-aNGNaBT.js";var P=l(`<div></div> <div></div>`,1),F=l(`<div></div> <!> <!>`,1),I=l(`<div><div><!></div> <!></div>`);function L(l,u){f(u,!0);let L=O(),R=v(u,`state`,15),z=v(u,`axis`,3,`x`),B=v(u,`handleSize`,3,5),ee=v(u,`clickToReset`,3,!0),te=v(u,`disabled`,3,!1),V=v(u,`constrainToDomain`,3,!0),H=v(u,`range`,19,()=>({})),U=v(u,`handle`,19,()=>({})),W=v(u,`classes`,19,()=>({})),G=v(u,`onBrushEnd`,3,()=>{}),K=v(u,`onBrushStart`,3,()=>{}),q=v(u,`onChange`,3,()=>{}),J=m(void 0),Y=new N(L,{x:u.x,y:u.y,axis:z(),minExtent:u.minExtent,maxExtent:u.maxExtent,constrain:u.constrain,constrainToDomain:V()});R(Y),h(()=>{Y.handleSize=B()}),h(()=>{Y.minExtent=u.minExtent,Y.maxExtent=u.maxExtent,Y.constrain=u.constrain,Y.constrainToDomain=V()});let X=new M(`BrushContext`);function Z(e){return t=>{X.debug(`drag start`),t.stopPropagation();let n=j(t,a(J));if(n&&(n.x<0||n.x>L.width||n.y<0||n.y>L.height)){X.debug(`ignoring click as outside of chart bounds`,{startPoint:n,width:L.width,height:L.height});return}let r={x:[Y.x[0]??Y.xDomainMin,Y.x[1]??Y.xDomainMax],y:[Y.y[0]??Y.yDomainMin,Y.y[1]??Y.yDomainMax],value:{x:A(L.xScale,n?.x??0),y:A(L.yScale,n?.y??0)}};K()({brush:Y});let i=t=>{let n=j(t,a(J));e(r,{x:A(L.xScale,n?.x??0),y:A(L.yScale,n?.y??0)}),q()({brush:Y})},o=e=>{let t=j(e,a(J)),r=Math.abs((n?.x??0)-(t?.x??0)),s=Math.abs((n?.y??0)-(t?.y??0));!Array.from(e.target.classList).some(e=>[`range`,`handle`].includes(e))&&r<1&&s<1||Y.range.width<1||Y.range.height<1?ee()?(X.debug(`resetting due to frame click`),Y.reset(),q()({brush:Y})):X.debug(`ignoring frame click reset (clickToReset: false)`):X.debug(`drag end`,{target:e.target,xPointDelta:r,yPointDelta:s,rangeWidth:Y.range.width,rangeHeight:Y.range.height}),G()({brush:Y}),window.removeEventListener(`pointermove`,i),window.removeEventListener(`pointerup`,o)};window.addEventListener(`pointermove`,i),window.addEventListener(`pointerup`,o)}}let ne=Z((e,t)=>{X.debug(`createRange`),Y.setRange(e.value,t)}),re=Z((e,t)=>{X.debug(`adjustRange`),Y.moveRange(e,t)}),ie=Z((e,t)=>{X.debug(`adjustTop`),Y.adjustEdge(`top`,e,t)}),ae=Z((e,t)=>{X.debug(`adjustBottom`),Y.adjustEdge(`bottom`,e,t)}),oe=Z((e,t)=>{X.debug(`adjustLeft`),Y.adjustEdge(`left`,e,t)}),se=Z((e,t)=>{X.debug(`adjustRight`),Y.adjustEdge(`right`,e,t)});T(()=>{if(u.x!==void 0||u.y!==void 0){let e=u.x,t=u.y;c(()=>Y.syncFromExternal(e,t))}});var Q=s(),ce=y(Q),le=e=>{var t=s(),a=y(t);n(a,()=>u.children??r,()=>({state:Y})),i(e,t)},$=s=>{var c=I();let l;var f=C(c);let m;var h=C(f);n(h,()=>u.children??r,()=>({state:Y})),t(f);var _=S(f,2),v=e=>{var t=F(),n=y(t),r=()=>{Y.reset(),q()({brush:Y})};g(n,e=>({...H(),class:e,onpointerdown:re,ondblclick:r,[p]:{left:`${Y.range.x??``}px`,top:`${Y.range.y??``}px`,width:`${Y.range.width??``}px`,height:`${Y.range.height??``}px`}}),[()=>k(`lc-brush-range`,W().range,H()?.class)],void 0,void 0,`svelte-3rn8dw`);var a=S(n,2),o=e=>{var t=P(),n=y(t),r=e=>{e.stopPropagation(),Y.y[0]&&(Y.y[0]=Y.yDomainMin,q()({brush:Y}))};g(n,e=>({...U(),"data-position":`top`,class:e,onpointerdown:ie,ondblclick:r,[p]:{left:`${Y.range.x??``}px`,top:`${Y.range.y??``}px`,width:`${Y.range.width??``}px`,height:`${B()??``}px`}}),[()=>k(`lc-brush-handle`,W().handle,U()?.class)],void 0,void 0,`svelte-3rn8dw`);var a=S(n,2),o=e=>{e.stopPropagation(),Y.y[1]&&(Y.y[1]=Y.yDomainMax,q()({brush:Y}))};g(a,e=>({...U(),"data-position":`bottom`,class:e,onpointerdown:ae,ondblclick:o,[p]:{left:`${Y.range.x??``}px`,top:`${Y.range.y+Y.range.height-B()}px`,width:`${Y.range.width??``}px`,height:`${B()??``}px`}}),[()=>k(`lc-brush-handle`,W().handle,U()?.class)],void 0,void 0,`svelte-3rn8dw`),i(e,t)};b(a,e=>{(z()===`both`||z()===`y`)&&e(o)});var s=S(a,2),c=e=>{var t=P(),n=y(t),r=e=>{e.stopPropagation(),Y.x[0]&&(Y.x[0]=Y.xDomainMin,q()({brush:Y}))};g(n,e=>({...U(),"data-position":`left`,class:e,onpointerdown:oe,ondblclick:r,[p]:{left:`${Y.range.x??``}px`,top:`${Y.range.y??``}px`,width:`${B()??``}px`,height:`${Y.range.height??``}px`}}),[()=>k(`lc-brush-handle`,W().handle,U()?.class)],void 0,void 0,`svelte-3rn8dw`);var a=S(n,2),o=e=>{e.stopPropagation(),Y.x[1]&&(Y.x[1]=Y.xDomainMax,q()({brush:Y}))};g(a,e=>({...U(),"data-position":`right`,class:e,onpointerdown:se,ondblclick:o,[p]:{left:`${Y.range.x+Y.range.width-B()+1}px`,top:`${Y.range.y??``}px`,width:`${B()??``}px`,height:`${Y.range.height??``}px`}}),[()=>k(`lc-brush-handle`,W().handle,U()?.class)],void 0,void 0,`svelte-3rn8dw`),i(e,t)};b(s,e=>{(z()===`both`||z()===`x`)&&e(c)}),i(e,t)};b(_,e=>{Y.active&&e(v)}),t(c),w(c,e=>x(J,e),()=>a(J)),e((e,t)=>{E(c,1,e,`svelte-3rn8dw`),l=d(c,``,l,{top:`${L.padding.top??``}px`,left:`${L.padding.left??``}px`,width:`${L.width??``}px`,height:`${L.height??``}px`}),E(f,1,t,`svelte-3rn8dw`),m=d(f,``,m,{top:`-${L.padding.top??0??``}px`,left:`-${L.padding.left??0??``}px`,width:`${L.containerWidth??``}px`,height:`${L.containerHeight??``}px`})},[()=>D(k(`lc-brush-context`)),()=>D(k(`lc-brush-container`))]),o(`pointerdown`,c,ne),o(`dblclick`,c,e=>{Y.selectAll(),e.stopPropagation()}),i(s,c)};b(ce,e=>{te()?e(le):e($,-1)}),i(l,Q),_()}u([`pointerdown`,`dblclick`]);export{L as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{E as e,F as t,G as n,I as r,O as i,_t as a,a as o,ft as s,gt as c,it as l,j as u,k as d,lt as f,o as p}from"./D2BnX0Uk.js";import"./xihTtKlq.js";import{t as m}from"./Bd1gDe3Y.js";import{t as h}from"./Y5urAfNy.js";import{t as g}from"./Db0q5_zr.js";import{t as _}from"./DKMlMI4a.js";import{i as v}from"./V9ZjaxiY.js";import{C as y,o as b,r as x,x as S}from"./Dh958be7.js";import{a as C,r as w}from"./caXkbKD3.js";import{n as T,r as E,t as D}from"./DthYhn6Y.js";import{n as O,t as k}from"./CO1vUXfR.js";var A=class{#e=()=>({});ctx=m();geo=y();#t;constructor(e){this.#e=e,e(),this.ctx.registerComponent({name:`Spline`,kind:`mark`,markInfo:()=>{let t=e();return{data:t.data,x:t.x,y:t.y,seriesKey:t.seriesKey,color:typeof t.stroke==`string`?t.stroke:void 0}}}),this.#t=w(this.#p(),()=>this.d,{type:`tween`,interpolate:E})}#n(e,t,n){let r=n(e);return Array.isArray(r)&&(r=g(r)),t.domain().length?t(r):r}#r=s(()=>this.ctx.series.series.find(e=>e.key===this.#e().seriesKey));get series(){return n(this.#r)}set series(e){f(this.#r,e)}#i=s(()=>this.series?.value??(this.series?.data?void 0:this.series?.key));get seriesAccessor(){return n(this.#i)}set seriesAccessor(e){f(this.#i,e)}#a=s(()=>_(this.#e().x??(this.ctx.valueAxis===`x`?this.seriesAccessor:void 0)??this.ctx.x));get xAccessor(){return n(this.#a)}set xAccessor(e){f(this.#a,e)}#o=s(()=>_(this.#e().y??(this.ctx.valueAxis===`y`?this.seriesAccessor:void 0)??this.ctx.y));get yAccessor(){return n(this.#o)}set yAccessor(e){f(this.#o,e)}#s=s(()=>v(this.ctx.xScale)?this.ctx.xScale.bandwidth()/2:0);get xOffset(){return n(this.#s)}set xOffset(e){f(this.#s,e)}#c=s(()=>v(this.ctx.yScale)?this.ctx.yScale.bandwidth()/2:0);get yOffset(){return n(this.#c)}set yOffset(e){f(this.#c,e)}#l(e){let t=this.#e(),n=this.ctx.radial?k().angle(e=>this.#n(e,this.ctx.xScale,this.xAccessor)+0).radius(e=>this.#n(e,this.ctx.yScale,this.yAccessor)+this.yOffset):O().x(e=>this.#n(e,this.ctx.xScale,this.xAccessor)+this.xOffset).y(e=>this.#n(e,this.ctx.yScale,this.yAccessor)+this.yOffset);return n.defined(t.defined??(e=>this.xAccessor(e)!=null&&this.yAccessor(e)!=null)),t.curve&&n.curve(t.curve),n(e)??``}#u=s(()=>{let e=this.#e();return typeof e.stroke==`function`||typeof e.fill==`function`||typeof e.opacity==`function`});get hasAnyStyleFn(){return n(this.#u)}set hasAnyStyleFn(e){f(this.#u,e)}#d=s(()=>{let e=this.#e();if(this.hasAnyStyleFn&&!this.geo.projection)return``;let t=e.data??this.series?.data??this.ctx.data;if(this.geo.projection){let n={type:`LineString`,coordinates:t.filter(n=>e.defined?e.defined(n,0,t):this.xAccessor(n)!=null&&this.yAccessor(n)!=null).map(e=>[this.xAccessor(e),this.yAccessor(e)])};return h(this.geo.projection)(n)??``}return this.#l(t)});get d(){return n(this.#d)}set d(e){f(this.#d,e)}#f=s(()=>{if(!this.hasAnyStyleFn)return null;let e=this.#e(),t=e.data??this.series?.data??this.ctx.data;return this.geo.projection?null:j(t,(t,n,r)=>{let i=x(e.stroke,t,this.ctx.cScale,n,r),a=x(e.fill,t,this.ctx.cScale,n,r),o=b(e.opacity,t,n,r);return{key:`${i}\0${a}\0${o}`,style:{stroke:i,fill:a,opacity:o}}}).map(e=>({...e.style,d:this.#l(e.data)}))});get segments(){return n(this.#f)}set segments(e){f(this.#f,e)}#p(){let e=this.#e();if(!C(e.motion))return``;if(this.ctx.config.x){let t=e.data??this.series?.data??this.ctx.data,n=Math.min(this.ctx.yScale(0)??this.ctx.yRange[0],this.ctx.yRange[0]),r=this.ctx.radial?k().angle(e=>this.#n(e,this.ctx.xScale,this.xAccessor)+0).radius(()=>n):O().x(e=>this.#n(e,this.ctx.xScale,this.xAccessor)+this.xOffset).y(()=>n);return r.defined(e.defined??(e=>this.xAccessor(e)!=null&&this.yAccessor(e)!=null)),e.curve&&r.curve(e.curve),r(t)??``}return``}#m=s(()=>C(this.#e().motion)!=null);get isTweened(){return n(this.#m)}set isTweened(e){f(this.#m,e)}get tweenedPath(){return this.#t.current}#h=s(()=>this.series?.key==null||this.ctx.series.visibleSeries.length<=1||this.ctx.series.isHighlighted(this.series.key,!0)?1:.1);get seriesOpacity(){return n(this.#h)}set seriesOpacity(e){f(this.#h,e)}};function j(e,t){if(e.length<2)return[];let n=[],r=t(e[0],0,e),i=0;for(let a=1;a<e.length;a++){let o=t(e[a],a,e);o.key!==r.key&&(n.push({style:r.style,data:e.slice(i,a+1)}),i=a,r=o)}return e.length-i>=2&&n.push({style:r.style,data:e.slice(i)}),n}var M=new Set([`$$slots`,`$$events`,`$$legacy`,`Path`,`data`,`x`,`y`,`seriesKey`,`defined`,`curve`,`stroke`,`fill`,`opacity`,`motion`]);function N(f,m){a(m,!0);let h=o(m,M),g=new A(()=>({data:m.data,x:m.x,y:m.y,seriesKey:m.seriesKey,defined:m.defined,curve:m.curve,stroke:m.stroke,fill:m.fill,opacity:m.opacity,motion:m.motion}));var _=r(),v=l(_),y=a=>{var o=r(),c=l(o);i(c,17,()=>g.segments,d,(i,a)=>{var o=r(),c=l(o);{let t=s(()=>n(a).opacity??(g.seriesOpacity===1?void 0:g.seriesOpacity));e(c,()=>m.Path,(e,r)=>{r(e,p({get pathData(){return n(a).d},get stroke(){return n(a).stroke},get fill(){return n(a).fill},get opacity(){return n(t)}},()=>g.series?.props,()=>h))})}t(i,o)}),t(a,o)},b=i=>{var a=r(),o=l(a);{let t=s(()=>g.isTweened?g.tweenedPath:g.d),r=s(()=>(typeof m.stroke==`string`?m.stroke:void 0)??g.series?.color),i=s(()=>typeof m.fill==`string`?m.fill:void 0),a=s(()=>(typeof m.opacity==`number`?m.opacity:void 0)??(g.seriesOpacity===1?void 0:g.seriesOpacity));e(o,()=>m.Path,(e,o)=>{o(e,p({get pathData(){return n(t)},get stroke(){return n(r)},get fill(){return n(i)},get opacity(){return n(a)}},()=>g.series?.props,()=>h))})}t(i,a)};u(v,e=>{g.segments?e(y):e(b,-1)}),t(f,_),c()}var P=new Set([`$$slots`,`$$events`,`$$legacy`]);function F(e,t){let n=o(t,P);N(e,p({get Path(){return T}},()=>n))}var I=new Set([`$$slots`,`$$events`,`$$legacy`]);function L(e,t){let n=o(t,I);N(e,p({get Path(){return D}},()=>n))}var R=new Set([`$$slots`,`$$events`,`$$legacy`]);function z(e,n){a(n,!0);let i=S(),s=o(n,R);var d=r(),f=l(d),m=e=>{F(e,p(()=>s))},h=e=>{L(e,p(()=>s))};u(f,e=>{i===`svg`?e(m):i===`canvas`&&e(h,1)}),t(e,d),c()}export{z as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./D2BnX0Uk.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Array.prototype.slice;function t(e){return typeof e==`object`&&`length`in e?e:Array.from(e)}export{e as n,t};
|