@walkthru-earth/objex 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +1 -1
  2. package/dist/components/browser/FileBrowser.svelte +37 -1
  3. package/dist/components/browser/FileRow.svelte +8 -3
  4. package/dist/components/browser/FileTreeSidebar.svelte +1 -3
  5. package/dist/components/layout/AboutSheet.svelte +126 -0
  6. package/dist/components/layout/AboutSheet.svelte.d.ts +6 -0
  7. package/dist/components/layout/ConnectionDialog.svelte +186 -138
  8. package/dist/components/layout/ConnectionDialog.svelte.d.ts +1 -0
  9. package/dist/components/layout/Sidebar.svelte +19 -3
  10. package/dist/components/layout/TabBar.svelte +4 -7
  11. package/dist/components/viewers/CodeViewer.svelte +17 -9
  12. package/dist/components/viewers/ImageViewer.svelte +6 -16
  13. package/dist/components/viewers/MarkdownViewer.svelte +8 -16
  14. package/dist/components/viewers/MediaViewer.svelte +6 -17
  15. package/dist/components/viewers/ModelViewer.svelte +4 -2
  16. package/dist/components/viewers/NotebookViewer.svelte +90 -40
  17. package/dist/components/viewers/PdfViewer.svelte +5 -3
  18. package/dist/components/viewers/RawViewer.svelte +4 -2
  19. package/dist/components/viewers/TableGrid.svelte +3 -2
  20. package/dist/components/viewers/ZarrMapViewer.svelte +334 -40
  21. package/dist/components/viewers/ZarrMapViewer.svelte.d.ts +3 -8
  22. package/dist/components/viewers/ZarrViewer.svelte +459 -178
  23. package/dist/components/viewers/map/AttributeTable.svelte +1 -6
  24. package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte +2 -6
  25. package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte +96 -22
  26. package/dist/constants.d.ts +28 -0
  27. package/dist/constants.js +34 -0
  28. package/dist/file-icons/index.js +6 -0
  29. package/dist/i18n/ar.js +34 -0
  30. package/dist/i18n/en.js +34 -0
  31. package/dist/index.d.ts +4 -1
  32. package/dist/index.js +7 -1
  33. package/dist/query/wasm.js +5 -4
  34. package/dist/storage/browser-cloud.d.ts +7 -0
  35. package/dist/storage/browser-cloud.js +74 -7
  36. package/dist/storage/providers.d.ts +53 -0
  37. package/dist/storage/providers.js +318 -0
  38. package/dist/stores/connections.svelte.js +5 -5
  39. package/dist/stores/query-history.svelte.js +4 -5
  40. package/dist/stores/settings.svelte.js +4 -4
  41. package/dist/types.d.ts +2 -2
  42. package/dist/utils/clipboard.d.ts +13 -0
  43. package/dist/utils/clipboard.js +38 -0
  44. package/dist/utils/error.d.ts +8 -0
  45. package/dist/utils/error.js +12 -0
  46. package/dist/utils/format.d.ts +10 -0
  47. package/dist/utils/format.js +22 -0
  48. package/dist/utils/host-detection.js +78 -18
  49. package/dist/utils/notebook.d.ts +59 -0
  50. package/dist/utils/notebook.js +211 -0
  51. package/dist/utils/parquet-metadata.js +1 -1
  52. package/dist/utils/pmtiles-tile.js +2 -1
  53. package/dist/utils/pmtiles.js +2 -1
  54. package/dist/utils/storage-url.d.ts +1 -1
  55. package/dist/utils/storage-url.js +82 -24
  56. package/dist/utils/url-state.js +2 -7
  57. package/dist/utils/url.d.ts +15 -1
  58. package/dist/utils/url.js +45 -19
  59. package/dist/utils/zarr.d.ts +60 -20
  60. package/dist/utils/zarr.js +450 -103
  61. package/package.json +64 -52
  62. package/dist/assets/favicon.svg +0 -17
  63. package/dist/components/CLAUDE.md +0 -44
  64. package/dist/components/viewers/CLAUDE.md +0 -60
  65. package/dist/file-icons/CLAUDE.md +0 -21
  66. package/dist/i18n/CLAUDE.md +0 -19
  67. package/dist/query/CLAUDE.md +0 -22
  68. package/dist/storage/CLAUDE.md +0 -23
  69. package/dist/stores/CLAUDE.md +0 -29
  70. package/dist/types/notebookjs.d.ts +0 -14
  71. package/dist/utils/CLAUDE.md +0 -54
  72. package/dist/utils/analytics.d.ts +0 -10
  73. package/dist/utils/analytics.js +0 -38
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Provider registry — single source of truth for all cloud storage providers.
3
+ *
4
+ * Centralizes endpoint patterns, regions, auth methods, and UI metadata.
5
+ * Used by ConnectionDialog, browser-cloud adapter, host-detection, url-state, etc.
6
+ */
7
+ export type ProviderId = 's3' | 'gcs' | 'r2' | 'minio' | 'azure' | 'storj' | 'b2' | 'digitalocean' | 'wasabi' | 'contabo' | 'hetzner' | 'linode' | 'ovhcloud';
8
+ export interface ProviderRegion {
9
+ code: string;
10
+ label: string;
11
+ }
12
+ export interface ProviderDef {
13
+ /** Display label in the UI. */
14
+ label: string;
15
+ /** Short description shown as helper text. */
16
+ description: string;
17
+ /** Auth method used by this provider. */
18
+ authMethod: 'sigv4' | 'sas-token';
19
+ /** Whether the region field is relevant for this provider. */
20
+ needsRegion: boolean;
21
+ /** Whether the endpoint field is required. */
22
+ needsEndpoint: boolean;
23
+ /** Default region when creating a new connection. */
24
+ defaultRegion: string;
25
+ /**
26
+ * Endpoint template with `{region}` placeholder.
27
+ * If null, the user must provide a custom endpoint (e.g. MinIO).
28
+ * If a fixed string (no `{region}`), it's always the same (e.g. GCS).
29
+ */
30
+ endpointTemplate: string | null;
31
+ /** Known regions with labels. Empty = free-form region input. */
32
+ regions: ProviderRegion[];
33
+ /** Bucket label override (e.g. Azure uses "Container"). */
34
+ bucketLabel?: string;
35
+ /** Default endpoint placeholder shown in the input. */
36
+ endpointPlaceholder: string;
37
+ /** URI schemes that map to this provider (lowercase, without "://"). */
38
+ schemes: string[];
39
+ }
40
+ export declare const PROVIDERS: Record<ProviderId, ProviderDef>;
41
+ /** All provider IDs, ordered for the UI. */
42
+ export declare const PROVIDER_IDS: ProviderId[];
43
+ /** Get provider def, falling back to S3 for unknown. */
44
+ export declare function getProvider(id: string): ProviderDef;
45
+ /** Build endpoint URL from template + region. */
46
+ export declare function buildEndpointFromTemplate(id: ProviderId, region: string): string;
47
+ /**
48
+ * Build the base URL for API requests (endpoint + bucket).
49
+ * Used by browser-cloud adapter and url-state.
50
+ */
51
+ export declare function buildProviderBaseUrl(provider: ProviderId, endpoint: string, bucket: string, region: string): string;
52
+ /** Check if a provider uses the GCS JSON API (not S3 XML). */
53
+ export declare function isGcsProvider(provider: string, endpoint: string): boolean;
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Provider registry — single source of truth for all cloud storage providers.
3
+ *
4
+ * Centralizes endpoint patterns, regions, auth methods, and UI metadata.
5
+ * Used by ConnectionDialog, browser-cloud adapter, host-detection, url-state, etc.
6
+ */
7
+ // ---------------------------------------------------------------------------
8
+ // Registry
9
+ // ---------------------------------------------------------------------------
10
+ export const PROVIDERS = {
11
+ s3: {
12
+ label: 'AWS S3',
13
+ description: 'Amazon S3 or any S3-compatible service',
14
+ authMethod: 'sigv4',
15
+ needsRegion: true,
16
+ needsEndpoint: false,
17
+ defaultRegion: 'us-east-1',
18
+ endpointTemplate: null,
19
+ regions: [
20
+ { code: 'us-east-1', label: 'US East (N. Virginia)' },
21
+ { code: 'us-east-2', label: 'US East (Ohio)' },
22
+ { code: 'us-west-1', label: 'US West (N. California)' },
23
+ { code: 'us-west-2', label: 'US West (Oregon)' },
24
+ { code: 'eu-west-1', label: 'EU (Ireland)' },
25
+ { code: 'eu-west-2', label: 'EU (London)' },
26
+ { code: 'eu-west-3', label: 'EU (Paris)' },
27
+ { code: 'eu-central-1', label: 'EU (Frankfurt)' },
28
+ { code: 'eu-central-2', label: 'EU (Zurich)' },
29
+ { code: 'eu-north-1', label: 'EU (Stockholm)' },
30
+ { code: 'eu-south-1', label: 'EU (Milan)' },
31
+ { code: 'eu-south-2', label: 'EU (Spain)' },
32
+ { code: 'ap-northeast-1', label: 'Asia Pacific (Tokyo)' },
33
+ { code: 'ap-northeast-2', label: 'Asia Pacific (Seoul)' },
34
+ { code: 'ap-northeast-3', label: 'Asia Pacific (Osaka)' },
35
+ { code: 'ap-southeast-1', label: 'Asia Pacific (Singapore)' },
36
+ { code: 'ap-southeast-2', label: 'Asia Pacific (Sydney)' },
37
+ { code: 'ap-southeast-3', label: 'Asia Pacific (Jakarta)' },
38
+ { code: 'ap-south-1', label: 'Asia Pacific (Mumbai)' },
39
+ { code: 'ap-south-2', label: 'Asia Pacific (Hyderabad)' },
40
+ { code: 'ap-east-1', label: 'Asia Pacific (Hong Kong)' },
41
+ { code: 'sa-east-1', label: 'South America (São Paulo)' },
42
+ { code: 'ca-central-1', label: 'Canada (Central)' },
43
+ { code: 'ca-west-1', label: 'Canada (Calgary)' },
44
+ { code: 'me-south-1', label: 'Middle East (Bahrain)' },
45
+ { code: 'me-central-1', label: 'Middle East (UAE)' },
46
+ { code: 'af-south-1', label: 'Africa (Cape Town)' },
47
+ { code: 'il-central-1', label: 'Israel (Tel Aviv)' }
48
+ ],
49
+ endpointPlaceholder: 'Leave empty for AWS, or enter custom S3 endpoint',
50
+ schemes: ['s3', 's3a', 's3n', 'aws']
51
+ },
52
+ gcs: {
53
+ label: 'Google Cloud',
54
+ description: 'Google Cloud Storage',
55
+ authMethod: 'sigv4',
56
+ needsRegion: false,
57
+ needsEndpoint: false,
58
+ defaultRegion: 'auto',
59
+ endpointTemplate: 'https://storage.googleapis.com',
60
+ regions: [],
61
+ endpointPlaceholder: 'https://storage.googleapis.com',
62
+ schemes: ['gs', 'gcs']
63
+ },
64
+ r2: {
65
+ label: 'Cloudflare R2',
66
+ description: 'Cloudflare R2 Storage',
67
+ authMethod: 'sigv4',
68
+ needsRegion: false,
69
+ needsEndpoint: true,
70
+ defaultRegion: 'auto',
71
+ endpointTemplate: null,
72
+ regions: [],
73
+ endpointPlaceholder: 'https://<account-id>.r2.cloudflarestorage.com',
74
+ schemes: ['r2']
75
+ },
76
+ azure: {
77
+ label: 'Azure',
78
+ description: 'Azure Blob Storage',
79
+ authMethod: 'sas-token',
80
+ needsRegion: false,
81
+ needsEndpoint: true,
82
+ defaultRegion: '',
83
+ endpointTemplate: null,
84
+ regions: [],
85
+ bucketLabel: 'Container',
86
+ endpointPlaceholder: 'https://<account>.blob.core.windows.net',
87
+ schemes: ['azure', 'az', 'abfs', 'abfss', 'wasbs', 'adl']
88
+ },
89
+ minio: {
90
+ label: 'MinIO',
91
+ description: 'Self-hosted MinIO or S3-compatible',
92
+ authMethod: 'sigv4',
93
+ needsRegion: false,
94
+ needsEndpoint: true,
95
+ defaultRegion: 'us-east-1',
96
+ endpointTemplate: null,
97
+ regions: [],
98
+ endpointPlaceholder: 'https://minio.example.com or http://localhost:9000',
99
+ schemes: []
100
+ },
101
+ storj: {
102
+ label: 'Storj',
103
+ description: 'Storj Decentralized Cloud',
104
+ authMethod: 'sigv4',
105
+ needsRegion: false,
106
+ needsEndpoint: false,
107
+ defaultRegion: 'us1',
108
+ endpointTemplate: 'https://gateway.storjshare.io',
109
+ regions: [
110
+ { code: 'us1', label: 'US1' },
111
+ { code: 'eu1', label: 'EU1' },
112
+ { code: 'ap1', label: 'AP1' }
113
+ ],
114
+ endpointPlaceholder: 'https://gateway.storjshare.io',
115
+ schemes: ['storj', 'sj']
116
+ },
117
+ b2: {
118
+ label: 'Backblaze B2',
119
+ description: 'Backblaze B2 Cloud Storage',
120
+ authMethod: 'sigv4',
121
+ needsRegion: true,
122
+ needsEndpoint: false,
123
+ defaultRegion: 'us-west-004',
124
+ endpointTemplate: 'https://s3.{region}.backblazeb2.com',
125
+ regions: [
126
+ { code: 'us-west-000', label: 'US West (Sacramento)' },
127
+ { code: 'us-west-001', label: 'US West (Stockton)' },
128
+ { code: 'us-west-002', label: 'US West (Phoenix)' },
129
+ { code: 'us-west-004', label: 'US West' },
130
+ { code: 'us-east-005', label: 'US East (Reston)' },
131
+ { code: 'eu-central-003', label: 'EU Central (Amsterdam)' },
132
+ { code: 'ca-central-001', label: 'Canada (Toronto)' }
133
+ ],
134
+ endpointPlaceholder: 'https://s3.us-west-004.backblazeb2.com',
135
+ schemes: []
136
+ },
137
+ digitalocean: {
138
+ label: 'DigitalOcean',
139
+ description: 'DigitalOcean Spaces',
140
+ authMethod: 'sigv4',
141
+ needsRegion: true,
142
+ needsEndpoint: false,
143
+ defaultRegion: 'nyc3',
144
+ endpointTemplate: 'https://{region}.digitaloceanspaces.com',
145
+ regions: [
146
+ { code: 'nyc3', label: 'New York 3' },
147
+ { code: 'sfo3', label: 'San Francisco 3' },
148
+ { code: 'ams3', label: 'Amsterdam 3' },
149
+ { code: 'sgp1', label: 'Singapore 1' },
150
+ { code: 'lon1', label: 'London 1' },
151
+ { code: 'fra1', label: 'Frankfurt 1' },
152
+ { code: 'tor1', label: 'Toronto 1' },
153
+ { code: 'blr1', label: 'Bangalore 1' },
154
+ { code: 'syd1', label: 'Sydney 1' }
155
+ ],
156
+ endpointPlaceholder: 'https://nyc3.digitaloceanspaces.com',
157
+ schemes: []
158
+ },
159
+ wasabi: {
160
+ label: 'Wasabi',
161
+ description: 'Wasabi Hot Cloud Storage',
162
+ authMethod: 'sigv4',
163
+ needsRegion: true,
164
+ needsEndpoint: false,
165
+ defaultRegion: 'us-east-1',
166
+ endpointTemplate: 'https://s3.{region}.wasabisys.com',
167
+ regions: [
168
+ { code: 'us-east-1', label: 'US East 1 (Virginia)' },
169
+ { code: 'us-east-2', label: 'US East 2 (Virginia)' },
170
+ { code: 'us-central-1', label: 'US Central 1 (Texas)' },
171
+ { code: 'us-west-1', label: 'US West 1 (Oregon)' },
172
+ { code: 'eu-central-1', label: 'EU Central 1 (Amsterdam)' },
173
+ { code: 'eu-central-2', label: 'EU Central 2 (Frankfurt)' },
174
+ { code: 'eu-west-1', label: 'EU West 1 (London)' },
175
+ { code: 'eu-west-2', label: 'EU West 2 (Paris)' },
176
+ { code: 'ap-northeast-1', label: 'AP Northeast 1 (Tokyo)' },
177
+ { code: 'ap-northeast-2', label: 'AP Northeast 2 (Osaka)' },
178
+ { code: 'ap-southeast-1', label: 'AP Southeast 1 (Singapore)' },
179
+ { code: 'ap-southeast-2', label: 'AP Southeast 2 (Sydney)' },
180
+ { code: 'ca-central-1', label: 'Canada (Toronto)' }
181
+ ],
182
+ endpointPlaceholder: 'https://s3.us-east-1.wasabisys.com',
183
+ schemes: []
184
+ },
185
+ contabo: {
186
+ label: 'Contabo',
187
+ description: 'Contabo Object Storage',
188
+ authMethod: 'sigv4',
189
+ needsRegion: true,
190
+ needsEndpoint: false,
191
+ defaultRegion: 'eu2',
192
+ endpointTemplate: 'https://{region}.contabostorage.com',
193
+ regions: [
194
+ { code: 'eu2', label: 'European Union' },
195
+ { code: 'usc1', label: 'US Central' },
196
+ { code: 'sin1', label: 'Singapore' }
197
+ ],
198
+ endpointPlaceholder: 'https://eu2.contabostorage.com',
199
+ schemes: []
200
+ },
201
+ hetzner: {
202
+ label: 'Hetzner',
203
+ description: 'Hetzner Object Storage',
204
+ authMethod: 'sigv4',
205
+ needsRegion: true,
206
+ needsEndpoint: false,
207
+ defaultRegion: 'fsn1',
208
+ endpointTemplate: 'https://{region}.your-objectstorage.com',
209
+ regions: [
210
+ { code: 'fsn1', label: 'Falkenstein, DE' },
211
+ { code: 'nbg1', label: 'Nuremberg, DE' },
212
+ { code: 'hel1', label: 'Helsinki, FI' }
213
+ ],
214
+ endpointPlaceholder: 'https://fsn1.your-objectstorage.com',
215
+ schemes: []
216
+ },
217
+ linode: {
218
+ label: 'Linode / Akamai',
219
+ description: 'Akamai / Linode Object Storage',
220
+ authMethod: 'sigv4',
221
+ needsRegion: true,
222
+ needsEndpoint: false,
223
+ defaultRegion: 'us-east-1',
224
+ endpointTemplate: 'https://{region}.linodeobjects.com',
225
+ regions: [
226
+ { code: 'us-east-1', label: 'Newark, NJ' },
227
+ { code: 'us-southeast-1', label: 'Atlanta, GA' },
228
+ { code: 'us-ord-1', label: 'Chicago, IL' },
229
+ { code: 'us-iad-1', label: 'Washington, DC' },
230
+ { code: 'us-lax-1', label: 'Los Angeles, CA' },
231
+ { code: 'us-sea-1', label: 'Seattle, WA' },
232
+ { code: 'us-mia-1', label: 'Miami, FL' },
233
+ { code: 'eu-central-1', label: 'Frankfurt, DE' },
234
+ { code: 'nl-ams-1', label: 'Amsterdam, NL' },
235
+ { code: 'gb-lon-1', label: 'London, UK' },
236
+ { code: 'fr-par-1', label: 'Paris, FR' },
237
+ { code: 'ap-south-1', label: 'Singapore' },
238
+ { code: 'jp-osa-1', label: 'Osaka, JP' },
239
+ { code: 'au-mel-1', label: 'Melbourne, AU' },
240
+ { code: 'br-gru-1', label: 'São Paulo, BR' },
241
+ { code: 'in-maa-1', label: 'Chennai, IN' },
242
+ { code: 'id-cgk-1', label: 'Jakarta, ID' },
243
+ { code: 'it-mil-1', label: 'Milan, IT' },
244
+ { code: 'se-sto-1', label: 'Stockholm, SE' }
245
+ ],
246
+ endpointPlaceholder: 'https://us-east-1.linodeobjects.com',
247
+ schemes: []
248
+ },
249
+ ovhcloud: {
250
+ label: 'OVHcloud',
251
+ description: 'OVHcloud Object Storage',
252
+ authMethod: 'sigv4',
253
+ needsRegion: true,
254
+ needsEndpoint: false,
255
+ defaultRegion: 'gra',
256
+ endpointTemplate: 'https://s3.{region}.io.cloud.ovh.net',
257
+ regions: [
258
+ { code: 'gra', label: 'Gravelines, FR' },
259
+ { code: 'sbg', label: 'Strasbourg, FR' },
260
+ { code: 'bhs', label: 'Beauharnois, CA' },
261
+ { code: 'de', label: 'Frankfurt, DE' },
262
+ { code: 'uk', label: 'London, UK' },
263
+ { code: 'waw', label: 'Warsaw, PL' }
264
+ ],
265
+ endpointPlaceholder: 'https://s3.gra.io.cloud.ovh.net',
266
+ schemes: []
267
+ }
268
+ };
269
+ // ---------------------------------------------------------------------------
270
+ // Helpers
271
+ // ---------------------------------------------------------------------------
272
+ /** All provider IDs, ordered for the UI. */
273
+ export const PROVIDER_IDS = [
274
+ 's3',
275
+ 'gcs',
276
+ 'r2',
277
+ 'azure',
278
+ 'b2',
279
+ 'digitalocean',
280
+ 'wasabi',
281
+ 'storj',
282
+ 'hetzner',
283
+ 'contabo',
284
+ 'linode',
285
+ 'ovhcloud',
286
+ 'minio'
287
+ ];
288
+ /** Get provider def, falling back to S3 for unknown. */
289
+ export function getProvider(id) {
290
+ return PROVIDERS[id] ?? PROVIDERS.s3;
291
+ }
292
+ /** Build endpoint URL from template + region. */
293
+ export function buildEndpointFromTemplate(id, region) {
294
+ const def = PROVIDERS[id];
295
+ if (!def?.endpointTemplate)
296
+ return '';
297
+ return def.endpointTemplate.replace('{region}', region);
298
+ }
299
+ /**
300
+ * Build the base URL for API requests (endpoint + bucket).
301
+ * Used by browser-cloud adapter and url-state.
302
+ */
303
+ export function buildProviderBaseUrl(provider, endpoint, bucket, region) {
304
+ if (endpoint) {
305
+ return `${endpoint.replace(/\/$/, '')}/${bucket}`;
306
+ }
307
+ const def = PROVIDERS[provider];
308
+ if (def?.endpointTemplate) {
309
+ const resolved = def.endpointTemplate.replace('{region}', region || def.defaultRegion);
310
+ return `${resolved}/${bucket}`;
311
+ }
312
+ // Fallback: AWS S3 path-style
313
+ return `https://s3.${region || 'us-east-1'}.amazonaws.com/${bucket}`;
314
+ }
315
+ /** Check if a provider uses the GCS JSON API (not S3 XML). */
316
+ export function isGcsProvider(provider, endpoint) {
317
+ return provider === 'gcs' || (!!endpoint && /storage\.googleapis\.com/i.test(endpoint));
318
+ }
@@ -1,5 +1,5 @@
1
+ import { STORAGE_KEYS } from '../constants.js';
1
2
  import { credentialStore, storeToNative } from './credentials.svelte.js';
2
- const CONNECTIONS_KEY = 'obstore-explore-connections';
3
3
  // ---------------------------------------------------------------------------
4
4
  // localStorage helpers
5
5
  // ---------------------------------------------------------------------------
@@ -7,7 +7,7 @@ function loadFromLocalStorage() {
7
7
  if (typeof window === 'undefined')
8
8
  return [];
9
9
  try {
10
- const raw = localStorage.getItem(CONNECTIONS_KEY);
10
+ const raw = localStorage.getItem(STORAGE_KEYS.CONNECTIONS);
11
11
  if (raw) {
12
12
  return JSON.parse(raw);
13
13
  }
@@ -21,7 +21,7 @@ function persistToLocalStorage(connections) {
21
21
  if (typeof window === 'undefined')
22
22
  return;
23
23
  try {
24
- localStorage.setItem(CONNECTIONS_KEY, JSON.stringify(connections));
24
+ localStorage.setItem(STORAGE_KEYS.CONNECTIONS, JSON.stringify(connections));
25
25
  }
26
26
  catch {
27
27
  // ignore storage errors
@@ -158,7 +158,7 @@ function createConnectionsStore() {
158
158
  * Test whether a connection is reachable via a lightweight list.
159
159
  */
160
160
  async test(id) {
161
- const { getAdapter } = await import(/* @vite-ignore */ '$lib/storage/index.js');
161
+ const { getAdapter } = await import('../storage/index.js');
162
162
  const adapter = getAdapter('remote', id);
163
163
  await adapter.list('');
164
164
  return true;
@@ -200,7 +200,7 @@ function createConnectionsStore() {
200
200
  }
201
201
  }
202
202
  try {
203
- const { getAdapter } = await import(/* @vite-ignore */ '$lib/storage/index.js');
203
+ const { getAdapter } = await import('../storage/index.js');
204
204
  const adapter = getAdapter('remote', tempId);
205
205
  await adapter.list(config.rootPrefix || '');
206
206
  return true;
@@ -1,10 +1,9 @@
1
- const STORAGE_KEY = 'obstore-explore-query-history';
2
- const MAX_ENTRIES = 200;
1
+ import { MAX_QUERY_HISTORY_ENTRIES, STORAGE_KEYS } from '../constants.js';
3
2
  function loadEntries() {
4
3
  if (typeof window === 'undefined')
5
4
  return [];
6
5
  try {
7
- const raw = localStorage.getItem(STORAGE_KEY);
6
+ const raw = localStorage.getItem(STORAGE_KEYS.QUERY_HISTORY);
8
7
  if (raw)
9
8
  return JSON.parse(raw);
10
9
  }
@@ -17,7 +16,7 @@ function persistEntries(entries) {
17
16
  if (typeof window === 'undefined')
18
17
  return;
19
18
  try {
20
- localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));
19
+ localStorage.setItem(STORAGE_KEYS.QUERY_HISTORY, JSON.stringify(entries));
21
20
  }
22
21
  catch {
23
22
  // ignore storage errors
@@ -37,7 +36,7 @@ function createQueryHistoryStore() {
37
36
  ...entry,
38
37
  id: crypto.randomUUID()
39
38
  };
40
- entries = [newEntry, ...entries].slice(0, MAX_ENTRIES);
39
+ entries = [newEntry, ...entries].slice(0, MAX_QUERY_HISTORY_ENTRIES);
41
40
  save();
42
41
  },
43
42
  remove(id) {
@@ -1,17 +1,17 @@
1
+ import { STORAGE_KEYS } from '../constants.js';
1
2
  import { setLocale } from '../i18n/index.svelte.js';
2
- const SETTINGS_KEY = 'obstore-explore-settings';
3
3
  function loadSettings() {
4
4
  if (typeof window === 'undefined') {
5
5
  return { theme: 'system', locale: 'en', featureLimit: 1000 };
6
6
  }
7
7
  try {
8
- const raw = localStorage.getItem(SETTINGS_KEY);
8
+ const raw = localStorage.getItem(STORAGE_KEYS.SETTINGS);
9
9
  if (raw) {
10
10
  const parsed = JSON.parse(raw);
11
11
  return {
12
12
  theme: parsed.theme ?? 'system',
13
13
  locale: parsed.locale ?? 'en',
14
- featureLimit: parsed.featureLimit ?? 100
14
+ featureLimit: parsed.featureLimit ?? 1000
15
15
  };
16
16
  }
17
17
  }
@@ -24,7 +24,7 @@ function persistSettings(settings) {
24
24
  if (typeof window === 'undefined')
25
25
  return;
26
26
  try {
27
- localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
27
+ localStorage.setItem(STORAGE_KEYS.SETTINGS, JSON.stringify(settings));
28
28
  }
29
29
  catch {
30
30
  // ignore storage errors
package/dist/types.d.ts CHANGED
@@ -9,7 +9,7 @@ export interface FileEntry {
9
9
  export interface Connection {
10
10
  id: string;
11
11
  name: string;
12
- provider: 's3' | 'gcs' | 'r2' | 'minio' | 'azure' | 'storj';
12
+ provider: string;
13
13
  endpoint: string;
14
14
  bucket: string;
15
15
  region: string;
@@ -19,7 +19,7 @@ export interface Connection {
19
19
  }
20
20
  export interface ConnectionConfig {
21
21
  name: string;
22
- provider: 's3' | 'gcs' | 'r2' | 'minio' | 'azure' | 'storj';
22
+ provider: string;
23
23
  endpoint: string;
24
24
  bucket: string;
25
25
  region: string;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Copy text to clipboard and run a feedback callback for COPY_FEEDBACK_MS.
3
+ * Silently catches clipboard errors (e.g. insecure context).
4
+ *
5
+ * @returns true if copy succeeded, false otherwise.
6
+ */
7
+ export declare function copyToClipboard(text: string, onFeedback?: (copied: boolean) => void): Promise<boolean>;
8
+ /**
9
+ * Wire click-to-copy on all elements matching `selector` inside `root`.
10
+ * Each element must have `data-code` (URI-encoded) with the text to copy.
11
+ * Adds/removes a `copied` CSS class for visual feedback.
12
+ */
13
+ export declare function wireCodeCopyButtons(root: Element, selector: string): void;
@@ -0,0 +1,38 @@
1
+ import { COPY_FEEDBACK_MS } from '../constants.js';
2
+ /**
3
+ * Copy text to clipboard and run a feedback callback for COPY_FEEDBACK_MS.
4
+ * Silently catches clipboard errors (e.g. insecure context).
5
+ *
6
+ * @returns true if copy succeeded, false otherwise.
7
+ */
8
+ export async function copyToClipboard(text, onFeedback) {
9
+ try {
10
+ await navigator.clipboard.writeText(text);
11
+ onFeedback?.(true);
12
+ setTimeout(() => onFeedback?.(false), COPY_FEEDBACK_MS);
13
+ return true;
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ }
19
+ /**
20
+ * Wire click-to-copy on all elements matching `selector` inside `root`.
21
+ * Each element must have `data-code` (URI-encoded) with the text to copy.
22
+ * Adds/removes a `copied` CSS class for visual feedback.
23
+ */
24
+ export function wireCodeCopyButtons(root, selector) {
25
+ for (const btn of root.querySelectorAll(selector)) {
26
+ btn.addEventListener('click', async () => {
27
+ const code = decodeURIComponent(btn.dataset.code ?? '');
28
+ try {
29
+ await navigator.clipboard.writeText(code);
30
+ btn.classList.add('copied');
31
+ setTimeout(() => btn.classList.remove('copied'), COPY_FEEDBACK_MS);
32
+ }
33
+ catch {
34
+ // clipboard not available
35
+ }
36
+ });
37
+ }
38
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared error handling for async viewer load operations.
3
+ */
4
+ /**
5
+ * Extract an error message from an unknown caught value.
6
+ * Returns null for AbortError (caller should silently return).
7
+ */
8
+ export declare function handleLoadError(err: unknown): string | null;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Shared error handling for async viewer load operations.
3
+ */
4
+ /**
5
+ * Extract an error message from an unknown caught value.
6
+ * Returns null for AbortError (caller should silently return).
7
+ */
8
+ export function handleLoadError(err) {
9
+ if (err instanceof DOMException && err.name === 'AbortError')
10
+ return null;
11
+ return err instanceof Error ? err.message : String(err);
12
+ }
@@ -12,3 +12,13 @@ export declare function formatDate(timestamp: number): string;
12
12
  * Returns an empty string if no extension is found.
13
13
  */
14
14
  export declare function getFileExtension(filename: string): string;
15
+ /**
16
+ * JSON replacer that converts BigInt values to strings.
17
+ * Use with `JSON.stringify(value, jsonReplacerBigInt)`.
18
+ */
19
+ export declare function jsonReplacerBigInt(_key: string, value: unknown): unknown;
20
+ /**
21
+ * Format a value for display in tables, attribute panels, and exports.
22
+ * Handles null, undefined, Date, BigInt, and objects uniformly.
23
+ */
24
+ export declare function formatValue(value: unknown): string;
@@ -54,3 +54,25 @@ export function getFileExtension(filename) {
54
54
  return '';
55
55
  return filename.slice(lastDot).toLowerCase();
56
56
  }
57
+ /**
58
+ * JSON replacer that converts BigInt values to strings.
59
+ * Use with `JSON.stringify(value, jsonReplacerBigInt)`.
60
+ */
61
+ export function jsonReplacerBigInt(_key, value) {
62
+ return typeof value === 'bigint' ? value.toString() : value;
63
+ }
64
+ /**
65
+ * Format a value for display in tables, attribute panels, and exports.
66
+ * Handles null, undefined, Date, BigInt, and objects uniformly.
67
+ */
68
+ export function formatValue(value) {
69
+ if (value === null || value === undefined)
70
+ return 'NULL';
71
+ if (value instanceof Date)
72
+ return value.toISOString();
73
+ if (typeof value === 'bigint')
74
+ return value.toString();
75
+ if (typeof value === 'object')
76
+ return JSON.stringify(value, jsonReplacerBigInt);
77
+ return String(value);
78
+ }