@graphenedata/cli 0.0.14 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE.md +3 -3
  2. package/README.md +138 -0
  3. package/THIRD_PARTY_NOTICES.md +1 -0
  4. package/bin.js +2 -2
  5. package/dist/cli/bigQuery-I3F46SC6.js +75 -0
  6. package/dist/cli/bigQuery-I3F46SC6.js.map +7 -0
  7. package/dist/cli/chunk-OVWODUTJ.js +12849 -0
  8. package/dist/cli/chunk-OVWODUTJ.js.map +7 -0
  9. package/dist/cli/chunk-QAXEOZ43.js +53 -0
  10. package/dist/cli/chunk-QAXEOZ43.js.map +7 -0
  11. package/dist/cli/cli.js +245 -10290
  12. package/dist/cli/clickhouse-ZN5AN2UL.js +64 -0
  13. package/dist/cli/clickhouse-ZN5AN2UL.js.map +7 -0
  14. package/dist/cli/duckdb-IYBIO5KJ.js +87 -0
  15. package/dist/cli/duckdb-IYBIO5KJ.js.map +7 -0
  16. package/dist/cli/serve2-TNN5EROW.js +447 -0
  17. package/dist/cli/serve2-TNN5EROW.js.map +7 -0
  18. package/dist/cli/snowflake-MOQB5GA4.js +128 -0
  19. package/dist/cli/snowflake-MOQB5GA4.js.map +7 -0
  20. package/dist/index.d.ts +63 -0
  21. package/dist/lang/index.d.ts +63 -0
  22. package/dist/skills/graphene/SKILL.md +235 -0
  23. package/dist/skills/graphene/references/big-value.md +20 -0
  24. package/dist/skills/graphene/references/date-range.md +64 -0
  25. package/dist/skills/graphene/references/dropdown.md +62 -0
  26. package/dist/skills/graphene/references/echarts.md +162 -0
  27. package/dist/skills/graphene/references/gsql.md +393 -0
  28. package/dist/skills/graphene/references/model-gsql.md +72 -0
  29. package/dist/skills/graphene/references/table.md +143 -0
  30. package/dist/skills/graphene/references/text-input.md +29 -0
  31. package/dist/ui/app.css +263 -299
  32. package/dist/ui/component-utilities/dataShaping.ts +484 -0
  33. package/dist/ui/component-utilities/dataSummary.ts +57 -0
  34. package/dist/ui/component-utilities/enrich.ts +763 -0
  35. package/dist/ui/component-utilities/format.ts +177 -0
  36. package/dist/ui/component-utilities/inputUtils.ts +48 -9
  37. package/dist/ui/component-utilities/theme.ts +200 -0
  38. package/dist/ui/component-utilities/themeStores.ts +26 -21
  39. package/dist/ui/component-utilities/types.ts +70 -0
  40. package/dist/ui/components/AreaChart.svelte +57 -105
  41. package/dist/ui/components/BarChart.svelte +71 -129
  42. package/dist/ui/components/BigValue.svelte +24 -40
  43. package/dist/ui/components/Column.svelte +11 -19
  44. package/dist/ui/components/DateRange.svelte +71 -34
  45. package/dist/ui/components/Dropdown.svelte +82 -49
  46. package/dist/ui/components/DropdownOption.svelte +1 -2
  47. package/dist/ui/components/ECharts.svelte +179 -60
  48. package/dist/ui/components/InlineDelta.svelte +51 -32
  49. package/dist/ui/components/LineChart.svelte +54 -125
  50. package/dist/ui/components/PieChart.svelte +27 -37
  51. package/dist/ui/components/QueryLoad.svelte +78 -44
  52. package/dist/ui/components/Row.svelte +2 -1
  53. package/dist/ui/components/ScatterPlot.svelte +52 -0
  54. package/dist/ui/components/Skeleton.svelte +32 -0
  55. package/dist/ui/components/Table.svelte +3 -2
  56. package/dist/ui/components/TableGroupRow.svelte +28 -36
  57. package/dist/ui/components/TableHarness.svelte +32 -0
  58. package/dist/ui/components/TableHeader.svelte +34 -59
  59. package/dist/ui/components/TableRow.svelte +15 -39
  60. package/dist/ui/components/TableSubtotalRow.svelte +26 -21
  61. package/dist/ui/components/TableTotalRow.svelte +27 -37
  62. package/dist/ui/components/TextInput.svelte +17 -14
  63. package/dist/ui/components/Value.svelte +25 -0
  64. package/dist/ui/components/_Table.svelte +80 -76
  65. package/dist/ui/internal/ChartGallery.svelte +527 -0
  66. package/dist/ui/internal/ErrorDisplay.svelte +60 -0
  67. package/dist/ui/internal/LocalApp.svelte +87 -19
  68. package/dist/ui/internal/PageNavGroup.svelte +269 -0
  69. package/dist/ui/internal/Sidebar.svelte +178 -0
  70. package/dist/ui/internal/SidebarToggle.svelte +47 -0
  71. package/dist/ui/internal/StyleGallery.svelte +244 -0
  72. package/dist/ui/internal/clientCache.ts +15 -13
  73. package/dist/ui/internal/pageInputs.svelte.js +292 -0
  74. package/dist/ui/internal/queryEngine.ts +124 -132
  75. package/dist/ui/internal/runSocket.ts +59 -0
  76. package/dist/ui/internal/sidebar.svelte.js +18 -0
  77. package/dist/ui/internal/telemetry.ts +52 -17
  78. package/dist/ui/internal/types.d.ts +7 -0
  79. package/dist/ui/web.js +55 -13
  80. package/package.json +40 -41
  81. package/dist/docs/agent-instructions.md +0 -18
  82. package/dist/docs/base.md +0 -98
  83. package/dist/docs/cli.md +0 -22
  84. package/dist/docs/graphene.md +0 -1462
  85. package/dist/ui/component-utilities/autoFormatting.js +0 -301
  86. package/dist/ui/component-utilities/builtInFormats.js +0 -482
  87. package/dist/ui/component-utilities/chartContext.js +0 -12
  88. package/dist/ui/component-utilities/chartWindowDebug.js +0 -21
  89. package/dist/ui/component-utilities/checkInputs.js +0 -95
  90. package/dist/ui/component-utilities/convert.js +0 -15
  91. package/dist/ui/component-utilities/dateParsing.js +0 -57
  92. package/dist/ui/component-utilities/dropdownContext.ts +0 -1
  93. package/dist/ui/component-utilities/echarts.js +0 -272
  94. package/dist/ui/component-utilities/echartsThemes.js +0 -453
  95. package/dist/ui/component-utilities/formatTitle.js +0 -24
  96. package/dist/ui/component-utilities/formatting.js +0 -250
  97. package/dist/ui/component-utilities/getColumnExtents.js +0 -79
  98. package/dist/ui/component-utilities/getColumnSummary.js +0 -67
  99. package/dist/ui/component-utilities/getCompletedData.js +0 -114
  100. package/dist/ui/component-utilities/getDistinctCount.js +0 -7
  101. package/dist/ui/component-utilities/getDistinctValues.js +0 -15
  102. package/dist/ui/component-utilities/getSeriesConfig.js +0 -237
  103. package/dist/ui/component-utilities/getSortedData.js +0 -7
  104. package/dist/ui/component-utilities/getStackPercentages.js +0 -43
  105. package/dist/ui/component-utilities/getStackedData.js +0 -17
  106. package/dist/ui/component-utilities/getYAxisIndex.js +0 -15
  107. package/dist/ui/component-utilities/globalContexts.js +0 -1
  108. package/dist/ui/component-utilities/helpers/getCompletedData.helpers.js +0 -119
  109. package/dist/ui/component-utilities/replaceNulls.js +0 -14
  110. package/dist/ui/component-utilities/tableUtils.ts +0 -120
  111. package/dist/ui/components/Area.svelte +0 -214
  112. package/dist/ui/components/Bar.svelte +0 -350
  113. package/dist/ui/components/Chart.svelte +0 -989
  114. package/dist/ui/components/ErrorChart.svelte +0 -118
  115. package/dist/ui/components/Line.svelte +0 -227
  116. package/dist/ui/internal/NavSidebar.svelte +0 -396
  117. package/dist/ui/internal/PageError.svelte +0 -23
  118. package/dist/ui/internal/checkSocket.ts +0 -48
  119. package/dist/ui/internal/theme.ts +0 -88
  120. package/dist/ui/public/inter-latin-ext.woff2 +0 -0
  121. package/dist/ui/public/inter-latin.woff2 +0 -0
@@ -0,0 +1,244 @@
1
+ <script lang="ts">
2
+ import {onMount} from 'svelte'
3
+
4
+ let styleVars = [
5
+ '--font-prose',
6
+ '--font-sans',
7
+ '--font-ui',
8
+ '--font-mono',
9
+ '--color-bg',
10
+ '--color-primary-strong',
11
+ '--color-body',
12
+ '--color-muted',
13
+ '--color-tertiary',
14
+ '--color-border',
15
+ '--color-border-strong',
16
+ '--color-code-bg',
17
+ ]
18
+
19
+ let styleRows = $state([] as Array<{name: string, value: string}>)
20
+
21
+ let sampleNodes = {} as Record<string, HTMLElement>
22
+
23
+ let readCssVar = (name: string) => getComputedStyle(document.documentElement).getPropertyValue(name).trim() || '(empty)'
24
+
25
+ let refreshRows = () => {
26
+ styleRows = styleVars.map(name => ({name, value: readCssVar(name)}))
27
+ }
28
+
29
+ let registerSample = (node: HTMLElement) => {
30
+ let key = node.dataset.styleDemo
31
+ if (!key) return
32
+
33
+ sampleNodes[key] = node
34
+ refreshRows()
35
+
36
+ return {
37
+ destroy: () => {
38
+ delete sampleNodes[key]
39
+ },
40
+ }
41
+ }
42
+
43
+ onMount(() => {
44
+ refreshRows()
45
+
46
+ let cssObserver = new MutationObserver(() => refreshRows())
47
+ cssObserver.observe(document.head, {childList: true, subtree: true, attributes: true})
48
+
49
+ let frame = requestAnimationFrame(() => refreshRows())
50
+ window.addEventListener('resize', refreshRows)
51
+
52
+ return () => {
53
+ cssObserver.disconnect()
54
+ cancelAnimationFrame(frame)
55
+ window.removeEventListener('resize', refreshRows)
56
+ }
57
+ })
58
+ </script>
59
+
60
+ <div class="style-demo">
61
+ <h1 class="style-demo-title">Style Demo</h1>
62
+
63
+ <section class="sample-panel">
64
+ <p>This is a regular paragraph at the top of the post. It has a couple sentences to give it some body. The quick brown fox jumps over the lazy dog.</p>
65
+
66
+ <h1 data-style-demo="h1" use:registerSample>Heading 1</h1>
67
+ <p>This is text after an h1. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
68
+
69
+ <h2 data-style-demo="h2" use:registerSample>Heading 2</h2>
70
+ <p>This is text after an h2. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
71
+
72
+ <h3 data-style-demo="h3" use:registerSample>Heading 3</h3>
73
+ <p>This is text after an h3. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
74
+
75
+ <h2>Paragraph spacing</h2>
76
+ <p>Here is the first paragraph. It should have visible space below it before the next paragraph starts.</p>
77
+ <p>Here is the second paragraph. There should be a clear gap between this and the paragraph above. If the spacing is working correctly, you'll see breathing room between each block.</p>
78
+ <p>Here is a third paragraph to make it obvious.</p>
79
+
80
+ <h2>Unordered list</h2>
81
+ <ul>
82
+ <li>First item in the list</li>
83
+ <li>Second item with <strong>bold text</strong> inside it</li>
84
+ <li>Third item with <em>italic text</em> inside it</li>
85
+ <li>Fourth item that is a bit longer to see how it wraps across multiple lines on smaller viewports</li>
86
+ <li>Fifth and final item</li>
87
+ </ul>
88
+
89
+ <h2>Ordered list</h2>
90
+ <ol>
91
+ <li>Step one: install dependencies</li>
92
+ <li>Step two: configure your environment</li>
93
+ <li>Step three: run the dev server</li>
94
+ <li>Step four: open your browser at localhost</li>
95
+ <li>Step five: profit</li>
96
+ </ol>
97
+
98
+ <h2>Nested lists</h2>
99
+ <ul>
100
+ <li>Top level item A
101
+ <ul>
102
+ <li>Nested item A1</li>
103
+ <li>Nested item A2
104
+ <ul>
105
+ <li>Doubly nested A2a</li>
106
+ </ul>
107
+ </li>
108
+ </ul>
109
+ </li>
110
+ <li>Top level item B
111
+ <ul>
112
+ <li>Nested item B1</li>
113
+ </ul>
114
+ </li>
115
+ <li>Top level item C</li>
116
+ </ul>
117
+
118
+ <h2>Inline formatting</h2>
119
+ <p data-style-demo="body" use:registerSample>
120
+ This paragraph has <strong>bold text</strong>, <em>italic text</em>, <del>strikethrough text</del>, and <code data-style-demo="code" use:registerSample>inline code</code>. It also has a <a data-style-demo="link" use:registerSample href="/index">link to the about page</a> to test link styling.
121
+ </p>
122
+ <p>You can also combine: <strong><em>bold and italic</em></strong> together.</p>
123
+
124
+ <h2>Blockquote</h2>
125
+ <blockquote data-style-demo="blockquote" use:registerSample>
126
+ This is a blockquote. It should have a left border and be visually distinct from regular paragraph text. It might span multiple lines to show how longer quotes are handled.
127
+ </blockquote>
128
+ <blockquote>Another blockquote with <strong>bold</strong> and <em>italic</em> inside it.</blockquote>
129
+
130
+ <h2>Horizontal rule</h2>
131
+ <p>Above the rule.</p>
132
+ <hr />
133
+ <p>Below the rule.</p>
134
+
135
+ <h2>Table</h2>
136
+ <table>
137
+ <thead>
138
+ <tr>
139
+ <th>Name</th>
140
+ <th>Role</th>
141
+ <th>Location</th>
142
+ </tr>
143
+ </thead>
144
+ <tbody>
145
+ <tr><td>Kevin Marr</td><td>Co-Founder &amp; CEO</td><td>San Francisco</td></tr>
146
+ <tr><td>Grant Marvin</td><td>Co-Founder &amp; CTO</td><td>San Francisco</td></tr>
147
+ <tr><td>Dylan Scott</td><td>Founding Engineer</td><td>San Francisco</td></tr>
148
+ </tbody>
149
+ </table>
150
+ <p>A table with alignment:</p>
151
+ <table>
152
+ <thead>
153
+ <tr>
154
+ <th style="text-align:left">Metric</th>
155
+ <th style="text-align:right">Q1</th>
156
+ <th style="text-align:right">Q2</th>
157
+ <th style="text-align:right">Q3</th>
158
+ <th style="text-align:right">Change</th>
159
+ </tr>
160
+ </thead>
161
+ <tbody>
162
+ <tr>
163
+ <td style="text-align:left">Revenue</td>
164
+ <td style="text-align:right">$1.2M</td>
165
+ <td style="text-align:right">$1.8M</td>
166
+ <td style="text-align:right">$2.4M</td>
167
+ <td style="text-align:right">+33%</td>
168
+ </tr>
169
+ <tr>
170
+ <td style="text-align:left">Active users</td>
171
+ <td style="text-align:right">4,200</td>
172
+ <td style="text-align:right">6,100</td>
173
+ <td style="text-align:right">8,800</td>
174
+ <td style="text-align:right">+44%</td>
175
+ </tr>
176
+ <tr>
177
+ <td style="text-align:left">Queries / day</td>
178
+ <td style="text-align:right">12,000</td>
179
+ <td style="text-align:right">19,500</td>
180
+ <td style="text-align:right">31,000</td>
181
+ <td style="text-align:right">+59%</td>
182
+ </tr>
183
+ </tbody>
184
+ </table>
185
+
186
+ <h2>Mixed content</h2>
187
+ <p>Here's a paragraph followed by a list:</p>
188
+ <p>There are three reasons analytics should live in your repo:</p>
189
+ <ol>
190
+ <li><strong>Drift prevention.</strong> When your semantic model is versioned alongside your dbt code, column renames and schema changes surface as type errors immediately.</li>
191
+ <li><strong>Code review.</strong> Changes to metrics and dashboards go through PRs just like application code — with diffs, comments, and history.</li>
192
+ <li><strong>Agent compatibility.</strong> AI coding agents work in your repo. If your analytics layer is a GUI, agents are locked out.</li>
193
+ </ol>
194
+ <p>And then back to regular prose after the list.</p>
195
+
196
+ <h2>Final paragraph</h2>
197
+ <p>This is the last paragraph of the post. Everything above should be styled consistently and match the overall site aesthetic — using the same font, color tokens, and spacing as the rest of graphenedata.com.</p>
198
+ </section>
199
+
200
+ <h2>Style variables</h2>
201
+ <table class="token-table">
202
+ <thead>
203
+ <tr>
204
+ <th>Variable</th>
205
+ <th>Value</th>
206
+ </tr>
207
+ </thead>
208
+ <tbody>
209
+ {#each styleRows as row (row.name)}
210
+ <tr>
211
+ <td><code>{row.name}</code></td>
212
+ <td><code>{row.value}</code></td>
213
+ </tr>
214
+ {/each}
215
+ </tbody>
216
+ </table>
217
+ </div>
218
+
219
+ <style>
220
+ .style-demo {
221
+ max-width: 1200px;
222
+ margin: 0 auto;
223
+ }
224
+
225
+ .sample-panel {
226
+ margin-bottom: 1.25rem;
227
+ }
228
+
229
+ .token-table {
230
+ width: 100%;
231
+ border-collapse: collapse;
232
+ margin-bottom: 1.25rem;
233
+ table-layout: fixed;
234
+ }
235
+
236
+ .token-table th,
237
+ .token-table td {
238
+ border: 1px solid var(--color-border);
239
+ text-align: left;
240
+ padding: 8px 10px;
241
+ vertical-align: top;
242
+ overflow-wrap: anywhere;
243
+ }
244
+ </style>
@@ -5,32 +5,34 @@
5
5
  const TTL_MS = 1000 * 60 * 60 * 2
6
6
 
7
7
  let cache: Cache | null = null
8
- async function getCache () {
8
+ async function getCache() {
9
9
  cache ||= await caches.open('graphene-data')
10
10
  return cache
11
11
  }
12
12
 
13
- export async function getHashes () {
13
+ export async function getHashes(): Promise<string[]> {
14
14
  let store = await getCache()
15
15
  let keys = await store.keys()
16
- return keys.map(k => {
17
- let url = new URL(k.url)
18
- let expires = Number(url.searchParams.get('expires') || 0)
19
- if (expires < Date.now()) {
20
- store.delete(k)
21
- return null
22
- }
23
- return url.pathname.replace(/^\//, '')
24
- }).filter(h => !!h)
16
+ return keys
17
+ .map(k => {
18
+ let url = new URL(k.url)
19
+ let expires = Number(url.searchParams.get('expires') || 0)
20
+ if (expires < Date.now()) {
21
+ store.delete(k)
22
+ return null
23
+ }
24
+ return url.pathname.replace(/^\//, '')
25
+ })
26
+ .filter(Boolean) as string[]
25
27
  }
26
28
 
27
- export async function cacheRead (hash: string): Promise<any | null> {
29
+ export async function cacheRead(hash: string): Promise<any | null> {
28
30
  let store = await getCache()
29
31
  let resp = await store.match(`https://graphene-cache/${hash}`, {ignoreSearch: true})
30
32
  return await resp?.clone().json()
31
33
  }
32
34
 
33
- export async function cacheWrite (hash: string, response:Response) {
35
+ export async function cacheWrite(hash: string, response: Response) {
34
36
  if (!hash) return
35
37
  let store = await getCache()
36
38
 
@@ -0,0 +1,292 @@
1
+ import { createContext } from "svelte";
2
+ class BoundField {
3
+ constructor(controller, name, codec) {
4
+ this.controller = controller;
5
+ this.name = name;
6
+ this.codec = codec;
7
+ this.value = $state(codec.empty());
8
+ this.hasExternalValue = $state(false);
9
+ this.controller.registerField(this);
10
+ this.sync(this.controller.getParams(), "init");
11
+ }
12
+ value;
13
+ hasExternalValue;
14
+ keys() {
15
+ return this.codec.keys(this.name);
16
+ }
17
+ sync(params, source) {
18
+ let next = this.codec.read(params, this.name);
19
+ this.value = next.value;
20
+ if (source === "init") this.hasExternalValue = next.present;
21
+ else if (source === "external") this.hasExternalValue = true;
22
+ }
23
+ normalize(params) {
24
+ this.codec.normalize?.(params, this.name);
25
+ }
26
+ set(next) {
27
+ let nextParams = this.codec.write(this.name, next);
28
+ if (typeof window !== "undefined" && window.$GRAPHENE?.updateParams) window.$GRAPHENE.updateParams(nextParams);
29
+ else this.controller.updateParams(nextParams);
30
+ }
31
+ destroy() {
32
+ this.controller.unregisterField(this);
33
+ }
34
+ }
35
+ const textCodec = {
36
+ keys: (name) => [name],
37
+ empty: () => "",
38
+ read(params, name) {
39
+ let raw = params[name];
40
+ if (raw === void 0 || raw === null) return { present: false, value: "" };
41
+ if (Array.isArray(raw)) return { present: raw.length > 0, value: raw.length ? String(raw[0] ?? "") : "" };
42
+ return { present: true, value: String(raw) };
43
+ },
44
+ write(name, value) {
45
+ return { [name]: value === "" ? null : value };
46
+ }
47
+ };
48
+ const dropdownSingleCodec = {
49
+ keys: (name) => [name],
50
+ empty: () => [],
51
+ read(params, name) {
52
+ let raw = params[name];
53
+ if (raw === void 0 || raw === null) return { present: false, value: [] };
54
+ if (Array.isArray(raw)) return { present: raw.length > 0, value: raw.length ? [raw[0]] : [] };
55
+ return { present: true, value: [raw] };
56
+ },
57
+ write(name, value) {
58
+ return { [name]: value.length ? value[0] : null };
59
+ }
60
+ };
61
+ const dropdownMultiCodec = {
62
+ keys: (name) => [name],
63
+ empty: () => [],
64
+ read(params, name) {
65
+ let raw = params[name];
66
+ if (raw === void 0 || raw === null) return { present: false, value: [] };
67
+ if (Array.isArray(raw)) return { present: raw.length > 0, value: [...raw] };
68
+ return { present: true, value: [raw] };
69
+ },
70
+ write(name, value) {
71
+ return { [name]: value.length ? [...value] : null };
72
+ },
73
+ normalize(params, name) {
74
+ let raw = params[name];
75
+ if (raw === void 0 || raw === null || Array.isArray(raw)) return;
76
+ params[name] = [raw];
77
+ }
78
+ };
79
+ const dateRangeCodec = {
80
+ keys: (name) => [`${name}_start`, `${name}_end`],
81
+ empty: () => ({ start: null, end: null }),
82
+ read(params, name) {
83
+ let startRaw = params[`${name}_start`];
84
+ let endRaw = params[`${name}_end`];
85
+ let start = readScalar(startRaw);
86
+ let end = readScalar(endRaw);
87
+ return { present: start !== null || end !== null, value: { start, end } };
88
+ },
89
+ write(name, value) {
90
+ return {
91
+ [`${name}_start`]: value.start,
92
+ [`${name}_end`]: value.end
93
+ };
94
+ }
95
+ };
96
+ function readScalar(value) {
97
+ if (value === void 0 || value === null) return null;
98
+ if (Array.isArray(value)) return value.length ? String(value[0] ?? "") : null;
99
+ return String(value);
100
+ }
101
+ function cloneParamValue(value) {
102
+ if (Array.isArray(value)) return [...value];
103
+ return value;
104
+ }
105
+ function cloneParams(params) {
106
+ return Object.fromEntries(Object.entries(params).map(([name, value]) => [name, cloneParamValue(value)]));
107
+ }
108
+ function paramValueEqual(left, right) {
109
+ if (Array.isArray(left) || Array.isArray(right)) {
110
+ if (!Array.isArray(left) || !Array.isArray(right)) return false;
111
+ if (left.length !== right.length) return false;
112
+ return left.every((value, index) => value === right[index]);
113
+ }
114
+ return left === right;
115
+ }
116
+ function getChangedKeys(left, right) {
117
+ let changed = /* @__PURE__ */ new Set();
118
+ let keys = /* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)]);
119
+ keys.forEach((key) => {
120
+ if (!paramValueEqual(left[key], right[key])) changed.add(key);
121
+ });
122
+ return changed;
123
+ }
124
+ class PageInputs {
125
+ params;
126
+ subscribers;
127
+ fieldsByKey;
128
+ onPopState;
129
+ constructor() {
130
+ this.params = $state.raw({});
131
+ this.subscribers = /* @__PURE__ */ new Set();
132
+ this.fieldsByKey = /* @__PURE__ */ new Map();
133
+ this.params = cloneParams(this.readFromUrl());
134
+ if (typeof window !== "undefined") {
135
+ this.onPopState = () => this.syncFromUrl();
136
+ window.addEventListener("popstate", this.onPopState);
137
+ }
138
+ }
139
+ destroy() {
140
+ if (this.onPopState) window.removeEventListener("popstate", this.onPopState);
141
+ this.subscribers.clear();
142
+ this.fieldsByKey.clear();
143
+ }
144
+ registerField(field) {
145
+ field.keys().forEach((key) => {
146
+ let fields = this.fieldsByKey.get(key);
147
+ if (!fields) {
148
+ fields = /* @__PURE__ */ new Set();
149
+ this.fieldsByKey.set(key, fields);
150
+ }
151
+ fields.add(field);
152
+ });
153
+ }
154
+ unregisterField(field) {
155
+ field.keys().forEach((key) => {
156
+ let fields = this.fieldsByKey.get(key);
157
+ if (!fields) return;
158
+ fields.delete(field);
159
+ if (fields.size === 0) this.fieldsByKey.delete(key);
160
+ });
161
+ }
162
+ text(name) {
163
+ return new BoundField(this, name, textCodec);
164
+ }
165
+ dropdown(name, multiple) {
166
+ return new BoundField(this, name, multiple ? dropdownMultiCodec : dropdownSingleCodec);
167
+ }
168
+ dateRange(name) {
169
+ return new BoundField(this, name, dateRangeCodec);
170
+ }
171
+ getParam(name) {
172
+ return this.params[name];
173
+ }
174
+ getParams() {
175
+ return cloneParams(this.params);
176
+ }
177
+ subscribeParams(subscriber) {
178
+ this.subscribers.add(subscriber);
179
+ return () => this.subscribers.delete(subscriber);
180
+ }
181
+ updateParam(name, value) {
182
+ this.updateParams({ [name]: value });
183
+ }
184
+ updateParams(nextParams) {
185
+ let merged = { ...this.params };
186
+ Object.entries(nextParams).forEach(([name, value]) => {
187
+ merged[name] = Array.isArray(value) ? [...value] : value;
188
+ });
189
+ this.applySnapshot(merged, "local");
190
+ }
191
+ reset() {
192
+ let changed = new Set(Object.keys(this.params));
193
+ if (changed.size === 0) return;
194
+ this.params = {};
195
+ this.syncUrl();
196
+ this.syncFields(changed, "local");
197
+ let snapshot = this.getParams();
198
+ this.subscribers.forEach((subscriber) => subscriber(snapshot, { changed }));
199
+ }
200
+ syncFromUrl() {
201
+ this.applySnapshot(this.readFromUrl(), "external");
202
+ }
203
+ applySnapshot(nextParams, source) {
204
+ let cloned = cloneParams(nextParams);
205
+ this.normalizeSnapshot(cloned);
206
+ let changed = getChangedKeys(this.params, cloned);
207
+ if (changed.size === 0) return;
208
+ this.params = cloned;
209
+ if (source !== "external") this.syncUrl();
210
+ this.syncFields(changed, source);
211
+ let snapshot = this.getParams();
212
+ this.subscribers.forEach((subscriber) => subscriber(snapshot, { changed }));
213
+ window.$GRAPHENE?.rerunQueries?.();
214
+ }
215
+ syncFields(changed, source) {
216
+ let fields = /* @__PURE__ */ new Set();
217
+ changed.forEach((key) => {
218
+ this.fieldsByKey.get(key)?.forEach((field) => fields.add(field));
219
+ });
220
+ fields.forEach((field) => field.sync(this.params, source));
221
+ }
222
+ normalizeSnapshot(params) {
223
+ let fields = /* @__PURE__ */ new Set();
224
+ this.fieldsByKey.forEach((keyFields) => {
225
+ keyFields.forEach((field) => fields.add(field));
226
+ });
227
+ fields.forEach((field) => field.normalize(params));
228
+ }
229
+ syncUrl() {
230
+ if (typeof window === "undefined") return;
231
+ let search = new URLSearchParams();
232
+ Object.entries(this.params).forEach(([name, value]) => {
233
+ if (Array.isArray(value)) {
234
+ value.forEach((item) => search.append(name, String(item)));
235
+ return;
236
+ }
237
+ if (value === null || value === void 0 || value === "") return;
238
+ search.append(name, String(value));
239
+ });
240
+ let nextSearch = search.toString();
241
+ let currentSearch = window.location.search.replace(/^\?/, "");
242
+ if (nextSearch === currentSearch) return;
243
+ let nextUrl = window.location.pathname + (nextSearch ? `?${nextSearch}` : "") + window.location.hash;
244
+ window.history.replaceState(window.history.state, "", nextUrl);
245
+ }
246
+ readFromUrl() {
247
+ if (typeof window === "undefined") return {};
248
+ let next = {};
249
+ for (let [name, value] of new URLSearchParams(window.location.search).entries()) {
250
+ let existing = next[name];
251
+ if (existing === void 0) next[name] = value;
252
+ else if (Array.isArray(existing)) existing.push(value);
253
+ else next[name] = [String(existing), value];
254
+ }
255
+ return next;
256
+ }
257
+ }
258
+ const [getPageInputsContext, setPageInputsContext] = createContext();
259
+ let activePageInputs = null;
260
+ function activatePageInputs(pageInputs) {
261
+ if (activePageInputs && activePageInputs !== pageInputs) activePageInputs.destroy();
262
+ activePageInputs = pageInputs;
263
+ return pageInputs;
264
+ }
265
+ function getActivePageInputs() {
266
+ if (!activePageInputs) activePageInputs = new PageInputs();
267
+ return activePageInputs;
268
+ }
269
+ function releasePageInputs(pageInputs) {
270
+ if (activePageInputs !== pageInputs) return;
271
+ pageInputs.destroy();
272
+ activePageInputs = null;
273
+ }
274
+ function getPageInputs() {
275
+ try {
276
+ return getPageInputsContext();
277
+ } catch {
278
+ return getActivePageInputs();
279
+ }
280
+ }
281
+ function captureInitial(factory) {
282
+ return factory();
283
+ }
284
+ export {
285
+ PageInputs,
286
+ activatePageInputs,
287
+ captureInitial,
288
+ getActivePageInputs,
289
+ getPageInputs,
290
+ releasePageInputs,
291
+ setPageInputsContext
292
+ };