@haex-space/vault-sdk 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 (59) hide show
  1. package/README.md +1551 -0
  2. package/dist/cli/index.d.mts +1 -0
  3. package/dist/cli/index.d.ts +1 -0
  4. package/dist/cli/index.js +455 -0
  5. package/dist/cli/index.js.map +1 -0
  6. package/dist/cli/index.mjs +430 -0
  7. package/dist/cli/index.mjs.map +1 -0
  8. package/dist/client-O_JEOzfx.d.mts +491 -0
  9. package/dist/client-O_JEOzfx.d.ts +491 -0
  10. package/dist/index.d.mts +124 -0
  11. package/dist/index.d.ts +124 -0
  12. package/dist/index.js +1429 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/index.mjs +1409 -0
  15. package/dist/index.mjs.map +1 -0
  16. package/dist/node.d.mts +25 -0
  17. package/dist/node.d.ts +25 -0
  18. package/dist/node.js +28 -0
  19. package/dist/node.js.map +1 -0
  20. package/dist/node.mjs +25 -0
  21. package/dist/node.mjs.map +1 -0
  22. package/dist/nuxt.d.mts +19 -0
  23. package/dist/nuxt.d.ts +19 -0
  24. package/dist/nuxt.js +473 -0
  25. package/dist/nuxt.js.map +1 -0
  26. package/dist/nuxt.mjs +470 -0
  27. package/dist/nuxt.mjs.map +1 -0
  28. package/dist/react.d.mts +28 -0
  29. package/dist/react.d.ts +28 -0
  30. package/dist/react.js +1389 -0
  31. package/dist/react.js.map +1 -0
  32. package/dist/react.mjs +1386 -0
  33. package/dist/react.mjs.map +1 -0
  34. package/dist/runtime/nuxt.plugin.client.d.mts +43 -0
  35. package/dist/runtime/nuxt.plugin.client.d.ts +43 -0
  36. package/dist/runtime/nuxt.plugin.client.js +1137 -0
  37. package/dist/runtime/nuxt.plugin.client.js.map +1 -0
  38. package/dist/runtime/nuxt.plugin.client.mjs +1135 -0
  39. package/dist/runtime/nuxt.plugin.client.mjs.map +1 -0
  40. package/dist/runtime/nuxt.types.d.ts +15 -0
  41. package/dist/svelte.d.mts +48 -0
  42. package/dist/svelte.d.ts +48 -0
  43. package/dist/svelte.js +1398 -0
  44. package/dist/svelte.js.map +1 -0
  45. package/dist/svelte.mjs +1391 -0
  46. package/dist/svelte.mjs.map +1 -0
  47. package/dist/vite.d.mts +37 -0
  48. package/dist/vite.d.ts +37 -0
  49. package/dist/vite.js +346 -0
  50. package/dist/vite.js.map +1 -0
  51. package/dist/vite.mjs +341 -0
  52. package/dist/vite.mjs.map +1 -0
  53. package/dist/vue.d.mts +49 -0
  54. package/dist/vue.d.ts +49 -0
  55. package/dist/vue.js +1388 -0
  56. package/dist/vue.js.map +1 -0
  57. package/dist/vue.mjs +1385 -0
  58. package/dist/vue.mjs.map +1 -0
  59. package/package.json +121 -0
package/README.md ADDED
@@ -0,0 +1,1551 @@
1
+ # @haex-space/sdk
2
+
3
+ [![npm version](https://badge.fury.io/js/@haex-space%2Fsdk.svg)](https://www.npmjs.com/package/@haex-space/sdk)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@haex-space/sdk.svg)](https://www.npmjs.com/package/@haex-space/sdk)
5
+
6
+ Official SDK for building HaexVault extensions with cryptographic identity and granular permissions.
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install @haex-space/sdk
12
+ # or
13
+ pnpm add @haex-space/sdk
14
+ # or
15
+ yarn add @haex-space/sdk
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ### 1. Initialize Your Project
21
+
22
+ ```bash
23
+ # Create your project (any framework)
24
+ npm create vite@latest my-extension -- --template react-ts
25
+
26
+ # Install SDK
27
+ cd my-extension
28
+ npm install @haex-space/sdk
29
+
30
+ # Initialize extension structure
31
+ npx haex init
32
+ ```
33
+
34
+ The `haex init` command creates:
35
+ - `haextension/` directory with `manifest.json`
36
+ - Public/private keypair (`public.key`, `private.key`)
37
+ - `haextension.config.json` for development
38
+ - Updates `.gitignore` to exclude `private.key`
39
+ - Adds npm scripts (`ext:dev`, `ext:build`)
40
+
41
+ ### 2. Load the Manifest
42
+
43
+ Import the manifest in your app's entry point:
44
+
45
+ ```typescript
46
+ import manifest from './haextension/manifest.json'; // or '../haextension/manifest.json'
47
+ const { client } = useHaexVault({ manifest });
48
+ ```
49
+
50
+ ### 3. Run Your Extension
51
+
52
+ ```bash
53
+ # Development
54
+ npm run ext:dev
55
+
56
+ # Build & sign for production
57
+ npm run ext:build
58
+ ```
59
+
60
+ ## Setup Hook System
61
+
62
+ **Important:** Always use the setup hook system to initialize your extension (create tables, run migrations, etc.). This ensures all database tables are created before your app tries to query them.
63
+
64
+ ### Why Use Setup Hooks?
65
+
66
+ Without setup hooks, your app might try to query tables before they exist, causing race conditions. The setup hook system guarantees:
67
+ - ✅ Tables are created before queries run
68
+ - ✅ Migrations complete before app loads
69
+ - ✅ No race conditions on first load
70
+ - ✅ Clean separation of setup logic
71
+
72
+ ### How to Use
73
+
74
+ <details>
75
+ <summary><b>Nuxt/Vue</b> - Setup in Pinia Store (Recommended)</summary>
76
+
77
+ **⚠️ Important for All Framework Users:**
78
+
79
+ You must register your setup hook **BEFORE** calling `setupComplete()`. The recommended pattern is to register the hook at store/component initialization time, then explicitly call `setupComplete()` to execute it.
80
+
81
+ **Recommended approach for Nuxt: Register setup hook in a Pinia store:**
82
+
83
+ ```typescript
84
+ // stores/haex.ts
85
+ import { defineStore } from 'pinia';
86
+ import * as schema from '~/database/schemas';
87
+ import manifest from '../../haextension/manifest.json';
88
+
89
+ // Import migration SQL files
90
+ const migrationFiles = import.meta.glob('../database/migrations/*.sql', {
91
+ query: '?raw',
92
+ import: 'default',
93
+ eager: true,
94
+ });
95
+
96
+ export const useHaexVaultStore = defineStore('haex', () => {
97
+ const nuxtApp = useNuxtApp();
98
+ const haex = nuxtApp.$haex;
99
+
100
+ const orm = shallowRef(null);
101
+
102
+ // Step 1: Register setup hook FIRST
103
+ haex.client.onSetup(async () => {
104
+ // Convert migration files to SDK format
105
+ const migrations = Object.entries(migrationFiles).map(
106
+ ([path, content]) => {
107
+ const fileName = path.split('/').pop()?.replace('.sql', '') || '';
108
+ return { name: fileName, sql: content };
109
+ }
110
+ );
111
+
112
+ console.log(`Running ${migrations.length} migration(s)`);
113
+
114
+ // Run migrations
115
+ await haex.client.runMigrationsAsync(
116
+ manifest.public_key,
117
+ manifest.name,
118
+ migrations
119
+ );
120
+ });
121
+
122
+ // Step 2: Initialize database and trigger setup
123
+ const initializeAsync = async () => {
124
+ orm.value = haex.client.initializeDatabase(schema);
125
+
126
+ // Step 3: Call setupComplete() to execute the hook
127
+ await haex.client.setupComplete();
128
+
129
+ console.log('Database ready');
130
+ };
131
+
132
+ return {
133
+ client: haex.client,
134
+ state: haex.state,
135
+ orm,
136
+ initializeAsync,
137
+ };
138
+ });
139
+ ```
140
+
141
+ Then in your `app.vue`:
142
+
143
+ ```vue
144
+ <!-- app/app.vue -->
145
+ <template>
146
+ <div v-if="haexStore.state.isSetupComplete">
147
+ <NuxtPage />
148
+ </div>
149
+ <div v-else>
150
+ <p>Initializing extension...</p>
151
+ </div>
152
+ </template>
153
+
154
+ <script setup lang="ts">
155
+ const haexStore = useHaexVaultStore();
156
+
157
+ onMounted(async () => {
158
+ await haexStore.initializeAsync();
159
+ });
160
+ </script>
161
+ ```
162
+
163
+ **Key Points:**
164
+
165
+ 1. Register the setup hook **immediately** when creating your store/client
166
+ 2. Call `setupComplete()` explicitly when you're ready to run the setup
167
+ 3. `isSetupComplete` will only become `true` after the hook finishes executing
168
+ 4. This ensures migrations complete before any database operations
169
+
170
+ </details>
171
+
172
+ <details>
173
+ <summary><b>Vue 3 (Non-Nuxt)</b> - Setup in app.vue</summary>
174
+
175
+ ```vue
176
+ <!-- app/app.vue -->
177
+ <template>
178
+ <div v-if="isSetupComplete">
179
+ <YourApp />
180
+ </div>
181
+ <div v-else>
182
+ <p>Initializing extension...</p>
183
+ </div>
184
+ </template>
185
+
186
+ <script setup lang="ts">
187
+ const isSetupComplete = ref(false);
188
+
189
+ onMounted(async () => {
190
+ const { client } = useHaexVault();
191
+
192
+ // Register setup function (runs once after SDK initialization)
193
+ // This is where you create tables, run migrations, etc.
194
+ client.onSetup(async () => {
195
+ console.log('[Setup] Creating database tables...');
196
+
197
+ // Example: Create tables using raw SQL
198
+ const tableName = client.getTableName('demo_table');
199
+ await client.execute(`
200
+ CREATE TABLE IF NOT EXISTS ${tableName} (
201
+ id TEXT PRIMARY KEY,
202
+ name TEXT,
203
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
204
+ )
205
+ `);
206
+
207
+ // Or with Drizzle ORM:
208
+ // await createTablesAsync(client);
209
+
210
+ console.log('[Setup] Database tables created successfully');
211
+ });
212
+
213
+ // Wait for setup to complete before showing the app
214
+ console.log('[app.vue] Waiting for setup completion...');
215
+ await client.setupComplete();
216
+ console.log('[app.vue] Setup complete, app ready');
217
+
218
+ isSetupComplete.value = true;
219
+ });
220
+ </script>
221
+ ```
222
+
223
+ </details>
224
+
225
+ <details>
226
+ <summary><b>React</b> - Setup in App component</summary>
227
+
228
+ ```tsx
229
+ // src/App.tsx
230
+ import { useState, useEffect } from 'react';
231
+ import { useHaexVault } from '@haex-space/sdk/react';
232
+ import manifest from './manifest.json';
233
+
234
+ function App() {
235
+ const { client, getTableName, isSetupComplete } = useHaexVault({ manifest });
236
+
237
+ // Register setup hook to initialize database
238
+ useEffect(() => {
239
+ if (!client) return;
240
+
241
+ // Register setup function (runs once after SDK initialization)
242
+ client.onSetup(async () => {
243
+ console.log('[Setup] Creating database tables...');
244
+
245
+ // Example: Create tables using raw SQL
246
+ const tableName = getTableName('demo_table');
247
+ await client.execute(`
248
+ CREATE TABLE IF NOT EXISTS ${tableName} (
249
+ id TEXT PRIMARY KEY,
250
+ name TEXT,
251
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
252
+ )
253
+ `);
254
+
255
+ console.log('[Setup] Database tables created successfully');
256
+ });
257
+ }, [client, getTableName]);
258
+
259
+ // Show loading screen until setup completes
260
+ if (!isSetupComplete) {
261
+ return <div>Initializing extension...</div>;
262
+ }
263
+
264
+ return (
265
+ <div>
266
+ {/* Your app content */}
267
+ </div>
268
+ );
269
+ }
270
+ ```
271
+
272
+ </details>
273
+
274
+ <details>
275
+ <summary><b>Svelte</b> - Setup in root component</summary>
276
+
277
+ ```svelte
278
+ <!-- src/App.svelte -->
279
+ <script lang="ts">
280
+ import { onMount } from 'svelte';
281
+ import { initHaexVault, haexHub, isSetupComplete } from '@haex-space/sdk/svelte';
282
+ import manifest from '../haextension/manifest.json';
283
+
284
+ onMount(async () => {
285
+ // Initialize SDK with manifest
286
+ initHaexVault({ manifest });
287
+
288
+ // Register setup function (runs once after SDK initialization)
289
+ haexHub.client.onSetup(async () => {
290
+ console.log('[Setup] Creating database tables...');
291
+
292
+ const tableName = haexHub.getTableName('demo_table');
293
+ await haexHub.client.execute(`
294
+ CREATE TABLE IF NOT EXISTS ${tableName} (
295
+ id TEXT PRIMARY KEY,
296
+ name TEXT,
297
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
298
+ )
299
+ `);
300
+
301
+ console.log('[Setup] Database tables created successfully');
302
+ });
303
+ });
304
+ </script>
305
+
306
+ {#if $isSetupComplete}
307
+ <div>
308
+ <!-- Your app content -->
309
+ </div>
310
+ {:else}
311
+ <p>Initializing extension...</p>
312
+ {/if}
313
+ ```
314
+
315
+ </details>
316
+
317
+ <details>
318
+ <summary><b>Vite (Vanilla JS/TS)</b> - Setup in main.ts</summary>
319
+
320
+ ```typescript
321
+ // src/main.ts
322
+ import { createHaexVaultClient } from '@haex-space/sdk';
323
+ import manifest from '../haextension/manifest.json';
324
+
325
+ const client = createHaexVaultClient({ manifest });
326
+
327
+ // Register setup function (runs once after SDK initialization)
328
+ client.onSetup(async () => {
329
+ console.log('[Setup] Creating database tables...');
330
+
331
+ const tableName = client.getTableName('demo_table');
332
+ await client.execute(`
333
+ CREATE TABLE IF NOT EXISTS ${tableName} (
334
+ id TEXT PRIMARY KEY,
335
+ name TEXT,
336
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
337
+ )
338
+ `);
339
+
340
+ console.log('[Setup] Database tables created successfully');
341
+ });
342
+
343
+ // Wait for setup to complete before rendering app
344
+ await client.setupComplete();
345
+ console.log('[main.ts] Setup complete, rendering app');
346
+
347
+ // Now render your app
348
+ document.querySelector('#app')!.innerHTML = `
349
+ <h1>My Extension</h1>
350
+ `;
351
+ ```
352
+
353
+ </details>
354
+
355
+ ### Using with Drizzle ORM
356
+
357
+ For complex schemas, create a separate setup file:
358
+
359
+ ```typescript
360
+ // database/createTables.ts
361
+ export async function createTablesAsync(client: HaexVaultClient) {
362
+ console.log('[Setup] Creating database tables...');
363
+
364
+ const tables = [
365
+ { table: schema.users, name: 'users' },
366
+ { table: schema.posts, name: 'posts' },
367
+ // ... more tables
368
+ ];
369
+
370
+ for (const { table, name } of tables) {
371
+ const config = getTableConfig(table);
372
+ const tableName = config.name;
373
+
374
+ const createTableSQL = `CREATE TABLE IF NOT EXISTS "${tableName}" (...)`;
375
+ await client.execute(createTableSQL, []);
376
+
377
+ console.log(`[Setup] ✓ Table ${name} created/verified`);
378
+ }
379
+ }
380
+ ```
381
+
382
+ Then use it in your setup hook:
383
+
384
+ ```typescript
385
+ client.onSetup(async () => {
386
+ await createTablesAsync(client);
387
+ });
388
+ ```
389
+
390
+ ## Demo Projects
391
+
392
+ Complete working examples for each framework:
393
+
394
+ - **Nuxt**: [github.com/haex-space/haex-demo-nuxt](https://github.com/haex-space/haex-demo-nuxt)
395
+ - **React**: [github.com/haex-space/haex-demo-react](https://github.com/haex-space/haex-demo-react)
396
+ - **Svelte**: [github.com/haex-space/haex-demo-svelte](https://github.com/haex-space/haex-demo-svelte)
397
+ - **Vite**: [github.com/haex-space/haex-demo-vite](https://github.com/haex-space/haex-demo-vite)
398
+
399
+ Each demo shows:
400
+ - ✅ **Setup Hook System** - Proper initialization with table creation
401
+ - ✅ Database operations (CREATE, INSERT, SELECT)
402
+ - ✅ Application context subscription (theme & locale)
403
+ - ✅ Manifest loading
404
+ - ✅ Framework-specific best practices
405
+
406
+ ## Framework Integration
407
+
408
+ The HaexVault SDK provides **framework-specific adapters** for seamless integration with popular frameworks:
409
+
410
+ ### 🎯 Quick Start by Framework
411
+
412
+ <details>
413
+ <summary><b>Vue 3</b> - Composable with reactive refs</summary>
414
+
415
+ ```bash
416
+ npm install @haex-space/sdk
417
+ ```
418
+
419
+ ```vue
420
+ <script setup lang="ts">
421
+ import { useHaexVault } from '@haex-space/sdk/vue';
422
+ import manifest from './manifest.json';
423
+
424
+ const { client, context, getTableName } = useHaexVault({ manifest });
425
+
426
+ // Watch for context changes (theme/locale from HaexVault)
427
+ watch(() => context.value, (ctx) => {
428
+ if (ctx) {
429
+ console.log('Theme:', ctx.theme); // 'light' or 'dark'
430
+ console.log('Locale:', ctx.locale); // 'en', 'de', etc.
431
+
432
+ // Update your app's theme
433
+ document.documentElement.classList.toggle('dark', ctx.theme === 'dark');
434
+ }
435
+ }, { immediate: true });
436
+
437
+ // Create your own table - no permissions needed!
438
+ // Tables are automatically namespaced with your extension's publicKey
439
+ const tableName = getTableName('users');
440
+ await client.execute(`
441
+ CREATE TABLE IF NOT EXISTS ${tableName} (
442
+ id TEXT PRIMARY KEY,
443
+ name TEXT NOT NULL,
444
+ email TEXT UNIQUE NOT NULL
445
+ )
446
+ `);
447
+
448
+ // Full read/write access to your own tables
449
+ await client.execute(
450
+ `INSERT INTO ${tableName} (id, name, email) VALUES (?, ?, ?)`,
451
+ [crypto.randomUUID(), 'John Doe', 'john@example.com']
452
+ );
453
+
454
+ const users = await client.query<User>(`SELECT * FROM ${tableName}`);
455
+ </script>
456
+
457
+ <template>
458
+ <div>
459
+ <h1>My Extension</h1>
460
+ <p>Theme: {{ context?.theme }}</p>
461
+ <p>Locale: {{ context?.locale }}</p>
462
+ <p>Users: {{ users.length }}</p>
463
+ </div>
464
+ </template>
465
+ ```
466
+
467
+ </details>
468
+
469
+ <details>
470
+ <summary><b>React</b> - Hook with automatic state updates</summary>
471
+
472
+ ```bash
473
+ npm install @haex-space/sdk
474
+ ```
475
+
476
+ ```tsx
477
+ import { useHaexVault } from '@haex-space/sdk/react';
478
+ import { useEffect, useState } from 'react';
479
+ import manifest from './manifest.json';
480
+
481
+ interface User {
482
+ id: string;
483
+ name: string;
484
+ email: string;
485
+ }
486
+
487
+ function App() {
488
+ const { client, context, getTableName } = useHaexVault({ manifest });
489
+ const [users, setUsers] = useState<User[]>([]);
490
+
491
+ // React to context changes (theme/locale from HaexVault)
492
+ useEffect(() => {
493
+ if (context) {
494
+ console.log('Theme:', context.theme); // 'light' or 'dark'
495
+ console.log('Locale:', context.locale); // 'en', 'de', etc.
496
+
497
+ // Update your app's theme
498
+ document.documentElement.classList.toggle('dark', context.theme === 'dark');
499
+ }
500
+ }, [context]);
501
+
502
+ useEffect(() => {
503
+ async function initializeDatabase() {
504
+ // Create your own table - no permissions needed!
505
+ // Tables are automatically namespaced with your extension's publicKey
506
+ const tableName = getTableName('users');
507
+
508
+ await client.execute(`
509
+ CREATE TABLE IF NOT EXISTS ${tableName} (
510
+ id TEXT PRIMARY KEY,
511
+ name TEXT NOT NULL,
512
+ email TEXT UNIQUE NOT NULL
513
+ )
514
+ `);
515
+
516
+ // Full read/write access to your own tables
517
+ await client.execute(
518
+ `INSERT INTO ${tableName} (id, name, email) VALUES (?, ?, ?)`,
519
+ [crypto.randomUUID(), 'John Doe', 'john@example.com']
520
+ );
521
+
522
+ // Query users
523
+ const result = await client.query<User>(`SELECT * FROM ${tableName}`);
524
+ setUsers(result);
525
+ }
526
+
527
+ initializeDatabase();
528
+ }, [client, getTableName]);
529
+
530
+ return (
531
+ <div>
532
+ <h1>My Extension</h1>
533
+ <p>Theme: {context?.theme}</p>
534
+ <p>Locale: {context?.locale}</p>
535
+ <ul>
536
+ {users.map(user => (
537
+ <li key={user.id}>{user.name} - {user.email}</li>
538
+ ))}
539
+ </ul>
540
+ </div>
541
+ );
542
+ }
543
+
544
+ export default App;
545
+ ```
546
+
547
+ </details>
548
+
549
+ <details>
550
+ <summary><b>Svelte</b> - Stores with $-syntax reactivity</summary>
551
+
552
+ ```bash
553
+ npm install @haex-space/sdk
554
+ ```
555
+
556
+ ```svelte
557
+ <script lang="ts">
558
+ import { onMount } from 'svelte';
559
+ import { initHaexVault, haexHub, context } from '@haex-space/sdk/svelte';
560
+ import manifest from '../haextension/manifest.json';
561
+
562
+ let users = [];
563
+
564
+ onMount(() => {
565
+ // Initialize SDK with manifest
566
+ initHaexVault({ manifest });
567
+ });
568
+
569
+ // React to context changes (theme/locale from HaexVault)
570
+ $: if ($context) {
571
+ console.log('Theme:', $context.theme); // 'light' or 'dark'
572
+ console.log('Locale:', $context.locale); // 'en', 'de', etc.
573
+
574
+ // Update your app's theme
575
+ document.documentElement.classList.toggle('dark', $context.theme === 'dark');
576
+ }
577
+
578
+ async function loadUsers() {
579
+ // Create your own table - no permissions needed!
580
+ // Tables are automatically namespaced with your extension's publicKey
581
+ const tableName = haexHub.getTableName('users');
582
+
583
+ await haexHub.client.execute(`
584
+ CREATE TABLE IF NOT EXISTS ${tableName} (
585
+ id TEXT PRIMARY KEY,
586
+ name TEXT NOT NULL,
587
+ email TEXT UNIQUE NOT NULL
588
+ )
589
+ `);
590
+
591
+ // Full read/write access to your own tables
592
+ await haexHub.client.execute(
593
+ `INSERT INTO ${tableName} (id, name, email) VALUES (?, ?, ?)`,
594
+ [crypto.randomUUID(), 'John Doe', 'john@example.com']
595
+ );
596
+
597
+ users = await haexHub.client.query(`SELECT * FROM ${tableName}`);
598
+ }
599
+
600
+ onMount(() => {
601
+ loadUsers();
602
+ });
603
+ </script>
604
+
605
+ <!-- Automatically reactive with $ syntax! -->
606
+ <h1>My Extension</h1>
607
+ <p>Theme: {$context?.theme}</p>
608
+ <p>Locale: {$context?.locale}</p>
609
+
610
+ <ul>
611
+ {#each users as user}
612
+ <li>{user.name} - {user.email}</li>
613
+ {/each}
614
+ </ul>
615
+ ```
616
+
617
+ </details>
618
+
619
+ <details>
620
+ <summary><b>Vanilla JS / Other Frameworks</b> - Core SDK</summary>
621
+
622
+ ```bash
623
+ npm install @haex-space/sdk
624
+ ```
625
+
626
+ ```typescript
627
+ import { createHaexVaultClient } from '@haex-space/sdk';
628
+ import manifest from '../haextension/manifest.json';
629
+
630
+ const client = createHaexVaultClient({ manifest });
631
+
632
+ // Subscribe to context changes (theme/locale from HaexVault)
633
+ client.subscribe(() => {
634
+ const context = client.context;
635
+ if (context) {
636
+ console.log('Theme:', context.theme); // 'light' or 'dark'
637
+ console.log('Locale:', context.locale); // 'en', 'de', etc.
638
+
639
+ // Update your app's theme
640
+ document.documentElement.classList.toggle('dark', context.theme === 'dark');
641
+ }
642
+ });
643
+
644
+ // Create your own table - no permissions needed!
645
+ // Tables are automatically namespaced with your extension's publicKey
646
+ const tableName = client.getTableName('users');
647
+ await client.execute(`
648
+ CREATE TABLE IF NOT EXISTS ${tableName} (
649
+ id TEXT PRIMARY KEY,
650
+ name TEXT NOT NULL,
651
+ email TEXT UNIQUE NOT NULL
652
+ )
653
+ `);
654
+
655
+ // Full read/write access to your own tables
656
+ await client.execute(
657
+ `INSERT INTO ${tableName} (id, name, email) VALUES (?, ?, ?)`,
658
+ [crypto.randomUUID(), 'John Doe', 'john@example.com']
659
+ );
660
+
661
+ const users = await client.query(`SELECT * FROM ${tableName}`);
662
+ console.log(users);
663
+ ```
664
+
665
+ </details>
666
+
667
+ ### 📦 Available Adapters
668
+
669
+ | Framework | Import Path | Features |
670
+ |-----------|-------------|----------|
671
+ | **Vue 3** | `@haex-space/sdk/vue` | Composable with `ref` reactivity |
672
+ | **React** | `@haex-space/sdk/react` | Hook with state management |
673
+ | **Svelte** | `@haex-space/sdk/svelte` | Stores with `$` syntax |
674
+ | **Core** | `@haex-space/sdk` | Framework-agnostic client |
675
+
676
+ ## Built-in Polyfills
677
+
678
+ The SDK automatically includes polyfills for browser APIs that don't work in custom protocol contexts (`haex-extension://`). **You don't need to do anything** - just import the SDK and everything works!
679
+
680
+ ### What's Included
681
+
682
+ ✅ **localStorage** - In-memory fallback when blocked
683
+ ✅ **sessionStorage** - No-op implementation
684
+ ✅ **Cookies** - In-memory cookie store
685
+ ✅ **History API** - Hash-based routing fallback for SPAs
686
+
687
+ ### How It Works
688
+
689
+ When you import the SDK:
690
+
691
+ ```typescript
692
+ import { createHaexVaultClient } from '@haex-space/sdk';
693
+ // Polyfills are automatically active!
694
+ ```
695
+
696
+ The polyfills detect whether the native APIs work and only activate if needed. This means:
697
+
698
+ - **Zero configuration** - Works out of the box
699
+ - **Framework agnostic** - Works with Vue, React, Svelte, etc.
700
+ - **No performance impact** - Only activates when necessary
701
+ - **SPA-friendly** - Includes history API patches for client-side routing
702
+
703
+ ### What This Means for You
704
+
705
+ You can build your extension using **any framework and any libraries** without worrying about custom protocol restrictions. Things that "just work":
706
+
707
+ - Vuex, Pinia, Zustand (state management using localStorage)
708
+ - Vue Router, React Router (client-side routing)
709
+ - Cookie-based authentication libraries
710
+ - Any npm package that uses localStorage/cookies
711
+
712
+ ## Core Concepts
713
+
714
+ ### 1. Application Context
715
+
716
+ HaexVault provides an **Application Context** that extensions can subscribe to for reactive updates:
717
+
718
+ ```typescript
719
+ interface ApplicationContext {
720
+ theme: 'light' | 'dark'; // User's theme preference
721
+ locale: string; // User's language (e.g., 'en', 'de', 'fr')
722
+ }
723
+ ```
724
+
725
+ **Framework-specific subscription examples:**
726
+
727
+ <details>
728
+ <summary><b>Vue 3 / Nuxt</b></summary>
729
+
730
+ ```vue
731
+ <script setup lang="ts">
732
+ import { watch } from 'vue';
733
+ import { useHaexVault } from '@haex-space/sdk/vue';
734
+ import manifest from './manifest.json';
735
+
736
+ const { context } = useHaexVault({ manifest });
737
+
738
+ watch(() => context.value, (ctx) => {
739
+ if (ctx) {
740
+ // Update theme
741
+ document.documentElement.classList.toggle('dark', ctx.theme === 'dark');
742
+
743
+ // Update i18n locale
744
+ // i18n.locale.value = ctx.locale;
745
+ }
746
+ }, { immediate: true });
747
+ </script>
748
+ ```
749
+ </details>
750
+
751
+ <details>
752
+ <summary><b>React</b></summary>
753
+
754
+ ```tsx
755
+ import { useEffect } from 'react';
756
+ import { useHaexVault } from '@haex-space/sdk/react';
757
+ import manifest from './manifest.json';
758
+
759
+ function App() {
760
+ const { context } = useHaexVault({ manifest });
761
+
762
+ useEffect(() => {
763
+ if (context) {
764
+ // Update theme
765
+ document.documentElement.classList.toggle('dark', context.theme === 'dark');
766
+
767
+ // Update i18n language
768
+ // i18n.changeLanguage(context.locale);
769
+ }
770
+ }, [context]);
771
+
772
+ return <div>Theme: {context?.theme}</div>;
773
+ }
774
+ ```
775
+ </details>
776
+
777
+ <details>
778
+ <summary><b>Svelte</b></summary>
779
+
780
+ ```svelte
781
+ <script lang="ts">
782
+ import { initHaexVault, context } from '@haex-space/sdk/svelte';
783
+ import manifest from '../haextension/manifest.json';
784
+
785
+ initHaexVault({ manifest });
786
+
787
+ // Reactive statement - runs whenever $context changes
788
+ $: if ($context) {
789
+ // Update theme
790
+ document.documentElement.classList.toggle('dark', $context.theme === 'dark');
791
+ }
792
+ </script>
793
+
794
+ <p>Theme: {$context?.theme}</p>
795
+ <p>Locale: {$context?.locale}</p>
796
+ ```
797
+ </details>
798
+
799
+ <details>
800
+ <summary><b>Vanilla JS / Vite</b></summary>
801
+
802
+ ```typescript
803
+ import { createHaexVaultClient } from '@haex-space/sdk';
804
+ import manifest from '../haextension/manifest.json';
805
+
806
+ const client = createHaexVaultClient({ manifest });
807
+
808
+ // Subscribe to context changes
809
+ client.subscribe(() => {
810
+ const context = client.context;
811
+ if (context) {
812
+ // Update theme
813
+ document.documentElement.classList.toggle('dark', context.theme === 'dark');
814
+
815
+ // Update language
816
+ // updateLanguage(context.locale);
817
+ }
818
+ });
819
+ ```
820
+ </details>
821
+
822
+ ### 2. Cryptographic Identity
823
+
824
+ Each extension is identified by a **public key**, not by name or namespace.
825
+
826
+ ```typescript
827
+ // Extension ID format: {publicKey}_{name}_{version}
828
+ // Example: MCowBQYDK2VwAyEA7x8Z9Kq3mN2pL5tR8vW4yB6cE1fH3gJ9kM7nP0qS2uV_password-manager_1.0.0
829
+ ```
830
+
831
+ **Benefits:**
832
+
833
+ - ✅ Mathematically unique (no collisions)
834
+ - ✅ Tamper-proof (signed with private key)
835
+ - ✅ Registry-independent (works everywhere)
836
+
837
+ ### 2. Table Naming
838
+
839
+ All tables are automatically prefixed with your extension's identity:
840
+
841
+ ```typescript
842
+ const tableName = client.getTableName("users");
843
+ // Result: "MCowBQYDK2VwAyEA7x8Z9Kq3mN2pL5tR8vW4yB6cE1fH3gJ9kM7nP0qS2uV__my-extension__users"
844
+ ```
845
+
846
+ **Naming Rules:**
847
+ - Extension names and table names must start with a letter (a-z, A-Z)
848
+ - Can contain letters, numbers, hyphens (`-`), and underscores (`_`)
849
+ - **Cannot contain** double underscores (`__`) - reserved as separator
850
+ - **Cannot contain** dots (`.`) - causes issues with SQL schema qualification
851
+ - Must follow npm package naming conventions
852
+
853
+ **Format:** `{publicKey}__{extensionName}__{tableName}`
854
+
855
+ **Own tables:** Automatic full read/write access
856
+ **Dependency tables:** Requires explicit permission
857
+
858
+ ### 3. Permission System
859
+
860
+ HaexVault uses a **zero-trust permission model** with automatic isolation:
861
+
862
+ #### 🔓 Own Tables - Always Allowed
863
+
864
+ Your extension can **freely create, read, write, and delete** its own tables without any permissions:
865
+
866
+ ```typescript
867
+ // ✅ Always works - no permissions needed!
868
+ const myTable = client.getTableName('users');
869
+
870
+ await client.database.createTable(myTable, '...'); // ✅ Create
871
+ await client.database.query(`SELECT * FROM ${myTable}`); // ✅ Read
872
+ await client.database.insert(myTable, {...}); // ✅ Write
873
+ await client.database.delete(myTable, 'id = ?', [1]); // ✅ Delete
874
+ ```
875
+
876
+ **Why this is safe:**
877
+ - Tables are automatically prefixed with your `publicKey` (e.g., `MCowBQYDK2VwAyEA7x8Z9Kq3mN2pL5tR8vW4yB6cE1fH3gJ9kM7nP0qS2uV__myext__users`)
878
+ - Impossible to access other extensions' tables
879
+ - Complete sandbox isolation
880
+ - No manifest declarations required
881
+
882
+ #### 🔒 Dependency Tables - Explicit Permission Required
883
+
884
+ To access **another extension's tables**, you must declare it in your manifest:
885
+
886
+ **manifest.json:**
887
+ ```json
888
+ {
889
+ "dependencies": [
890
+ {
891
+ "publicKey": "MCowBQYDK2VwAyEA7x8Z9Kq3mN2pL5tR8vW4yB6cE1fH3gJ9kM7nP0qS2uV",
892
+ "name": "password-manager",
893
+ "minVersion": "1.0.0",
894
+ "reason": "Access stored credentials",
895
+ "tables": [
896
+ {
897
+ "table": "credentials",
898
+ "operations": ["read"],
899
+ "reason": "Retrieve login data for email sync"
900
+ }
901
+ ]
902
+ }
903
+ ]
904
+ }
905
+ ```
906
+
907
+ **Code:**
908
+ ```typescript
909
+ // ❌ Would fail without permission!
910
+ const depTable = client.getDependencyTableName(
911
+ 'a7f3b2c1d4e5f6a8b9c0',
912
+ 'password-manager',
913
+ 'credentials'
914
+ );
915
+
916
+ const creds = await client.database.query(`SELECT * FROM ${depTable}`);
917
+ ```
918
+
919
+ **Permission Granularity:**
920
+ - ✅ **Per-table** - Request access to specific tables only
921
+ - ✅ **Per-operation** - `["read"]` or `["read", "write"]`
922
+ - ✅ **User consent** - User approves each permission
923
+ - ✅ **Revocable** - User can revoke anytime
924
+
925
+ ## API Reference
926
+
927
+ ### Vue 3 Adapter
928
+
929
+ ```typescript
930
+ import { useHaexVault } from '@haex-space/sdk/vue';
931
+
932
+ const {
933
+ client, // Raw HaexVaultClient instance
934
+ extensionInfo, // Readonly<Ref<ExtensionInfo | null>>
935
+ context, // Readonly<Ref<ApplicationContext | null>>
936
+ db, // DatabaseAPI
937
+ storage, // StorageAPI
938
+ getTableName // (tableName: string) => string
939
+ } = useHaexVault({ debug: true });
940
+
941
+ // Use in templates or computed
942
+ watch(() => extensionInfo.value, (info) => {
943
+ console.log('Extension:', info?.name);
944
+ });
945
+ ```
946
+
947
+ ### React Adapter
948
+
949
+ ```typescript
950
+ import { useHaexVault } from '@haex-space/sdk/react';
951
+
952
+ function MyComponent() {
953
+ const {
954
+ client, // HaexVaultClient instance
955
+ extensionInfo, // ExtensionInfo | null
956
+ context, // ApplicationContext | null
957
+ db, // DatabaseAPI
958
+ storage, // StorageAPI
959
+ getTableName // (tableName: string) => string
960
+ } = useHaexVault({ debug: true });
961
+
962
+ // State automatically updates on SDK changes
963
+ return <div>{extensionInfo?.name}</div>;
964
+ }
965
+ ```
966
+
967
+ ### Svelte Adapter
968
+
969
+ ```typescript
970
+ // Initialize once in +layout.svelte
971
+ import { initHaexVault } from '@haex-space/sdk/svelte';
972
+ initHaexVault({ debug: true });
973
+
974
+ // Use stores anywhere
975
+ import { extensionInfo, context, haexHub } from '@haex-space/sdk/svelte';
976
+
977
+ // In templates with $ syntax
978
+ <h1>{$extensionInfo?.name}</h1>
979
+
980
+ // In script
981
+ const tableName = haexHub.getTableName('users');
982
+ await haexHub.database.query(`SELECT * FROM ${tableName}`);
983
+ ```
984
+
985
+ ### Core Client API
986
+
987
+ #### Client Initialization
988
+
989
+ ```typescript
990
+ import { createHaexVaultClient } from '@haex-space/sdk';
991
+
992
+ const client = createHaexVaultClient({
993
+ debug: true, // Optional: Enable debug logging
994
+ timeout: 30000 // Optional: Request timeout in ms
995
+ });
996
+ ```
997
+
998
+ #### Subscribe to Changes
999
+
1000
+ ```typescript
1001
+ // Subscribe to SDK updates
1002
+ const unsubscribe = client.subscribe(() => {
1003
+ console.log('Extension info:', client.extensionInfo);
1004
+ console.log('Context:', client.context);
1005
+ });
1006
+
1007
+ // Cleanup
1008
+ unsubscribe();
1009
+ ```
1010
+
1011
+ #### Extension Info
1012
+
1013
+ ```typescript
1014
+ // Get your extension's info
1015
+ const info = client.extensionInfo;
1016
+ // {
1017
+ // publicKey: "MCowBQYDK2VwAyEA7x8Z9Kq3mN2pL5tR8vW4yB6cE1fH3gJ9kM7nP0qS2uV",
1018
+ // name: "my-extension",
1019
+ // version: "1.0.0",
1020
+ // namespace: "johndoe" // Display only
1021
+ // }
1022
+ ```
1023
+
1024
+ #### Table Names
1025
+
1026
+ ```typescript
1027
+ // Get table name for your extension
1028
+ const myTable = client.getTableName("users");
1029
+ // → "MCowBQYDK2VwAyEA7x8Z9Kq3mN2pL5tR8vW4yB6cE1fH3gJ9kM7nP0qS2uV__my-extension__users"
1030
+
1031
+ // Get table name for a dependency
1032
+ const depTable = client.getDependencyTableName(
1033
+ "MCowBQYDK2VwAyEAp1q2r3s4t5u6v7w8x9y0z1a2b3c4d5e6f7g8h9i0j1k", // Dependency's publicKey
1034
+ "password-manager", // Dependency's name
1035
+ "credentials" // Table name
1036
+ );
1037
+ // → "MCowBQYDK2VwAyEAp1q2r3s4t5u6v7w8x9y0z1a2b3c4d5e6f7g8h9i0j1k__password-manager__credentials"
1038
+ ```
1039
+
1040
+ ### Database Operations
1041
+
1042
+ #### Query
1043
+
1044
+ ```typescript
1045
+ // SELECT queries
1046
+ const users = await client.database.query<User>(
1047
+ `SELECT * FROM ${myTable} WHERE age > ?`,
1048
+ [18]
1049
+ );
1050
+
1051
+ // Single row
1052
+ const user = await client.database.queryOne<User>(
1053
+ `SELECT * FROM ${myTable} WHERE id = ?`,
1054
+ [1]
1055
+ );
1056
+ ```
1057
+
1058
+ #### Execute
1059
+
1060
+ ```typescript
1061
+ // INSERT, UPDATE, DELETE
1062
+ const result = await client.database.execute(
1063
+ `INSERT INTO ${myTable} (name) VALUES (?)`,
1064
+ ['Alice']
1065
+ );
1066
+
1067
+ console.log(result.lastInsertId);
1068
+ console.log(result.rowsAffected);
1069
+ ```
1070
+
1071
+ #### Transactions
1072
+
1073
+ ```typescript
1074
+ await client.database.transaction([
1075
+ `INSERT INTO ${myTable} (name) VALUES ('Alice')`,
1076
+ `INSERT INTO ${myTable} (name) VALUES ('Bob')`
1077
+ ]);
1078
+ ```
1079
+
1080
+ #### Helper Methods
1081
+
1082
+ ```typescript
1083
+ // Create table
1084
+ await client.database.createTable('posts', `
1085
+ id INTEGER PRIMARY KEY,
1086
+ title TEXT NOT NULL,
1087
+ content TEXT
1088
+ `);
1089
+
1090
+ // Check existence
1091
+ const exists = await client.database.tableExists(myTable);
1092
+
1093
+ // Get table info
1094
+ const info = await client.database.getTableInfo(myTable);
1095
+
1096
+ // List all tables
1097
+ const tables = await client.database.listTables();
1098
+
1099
+ // Drop table
1100
+ await client.database.dropTable('posts');
1101
+
1102
+ // Insert
1103
+ const id = await client.database.insert(myTable, {
1104
+ name: 'John',
1105
+ email: 'john@example.com'
1106
+ });
1107
+
1108
+ // Update
1109
+ const updated = await client.database.update(
1110
+ myTable,
1111
+ { name: 'Jane' },
1112
+ 'id = ?',
1113
+ [id]
1114
+ );
1115
+
1116
+ // Delete
1117
+ const deleted = await client.database.delete(myTable, 'id = ?', [id]);
1118
+
1119
+ // Count
1120
+ const count = await client.database.count(myTable, 'age > ?', [18]);
1121
+ ```
1122
+
1123
+ ### Storage API
1124
+
1125
+ ```typescript
1126
+ // Store data
1127
+ await client.storage.setItem('theme', 'dark');
1128
+
1129
+ // Retrieve data
1130
+ const theme = await client.storage.getItem('theme');
1131
+
1132
+ // Remove data
1133
+ await client.storage.removeItem('theme');
1134
+
1135
+ // Get all keys
1136
+ const keys = await client.storage.keys();
1137
+
1138
+ // Clear all
1139
+ await client.storage.clear();
1140
+ ```
1141
+
1142
+ ### Dependencies
1143
+
1144
+ ```typescript
1145
+ // Get all dependencies
1146
+ const deps = await client.getDependencies();
1147
+
1148
+ // Each dependency has:
1149
+ // {
1150
+ // publicKey: string,
1151
+ // name: string,
1152
+ // version: string
1153
+ // }
1154
+ ```
1155
+
1156
+ ### Permissions
1157
+
1158
+ ```typescript
1159
+ // Request permission (runtime - usually done via manifest)
1160
+ const response = await client.requestDatabasePermission({
1161
+ resource: 'MCowBQYDK2VwAyEAp1q2r3s4t5u6v7w8x9y0z1a2b3c4d5e6f7g8h9i0j1k__password-manager__credentials',
1162
+ operation: 'read',
1163
+ reason: 'To retrieve email credentials'
1164
+ });
1165
+
1166
+ if (response.status === 'granted') {
1167
+ // Permission granted!
1168
+ }
1169
+
1170
+ // Check if permission exists
1171
+ const hasPermission = await client.checkDatabasePermission(
1172
+ 'MCowBQYDK2VwAyEAp1q2r3s4t5u6v7w8x9y0z1a2b3c4d5e6f7g8h9i0j1k__password-manager__credentials',
1173
+ 'read'
1174
+ );
1175
+ ```
1176
+
1177
+ ### Events
1178
+
1179
+ ```typescript
1180
+ // Listen to context changes
1181
+ client.on('context.changed', (event) => {
1182
+ console.log('Context changed:', event.data.context);
1183
+ });
1184
+
1185
+ // Listen to search requests
1186
+ client.on('search.request', (event) => {
1187
+ const { query, requestId } = event.data;
1188
+
1189
+ // Respond with search results
1190
+ await client.respondToSearch(requestId, [
1191
+ {
1192
+ id: '1',
1193
+ title: 'Result 1',
1194
+ description: 'Description',
1195
+ type: 'item',
1196
+ score: 0.9
1197
+ }
1198
+ ]);
1199
+ });
1200
+
1201
+ // Remove listener
1202
+ const callback = (event) => console.log(event);
1203
+ client.on('some-event', callback);
1204
+ client.off('some-event', callback);
1205
+ ```
1206
+
1207
+ ### Cleanup
1208
+
1209
+ ```typescript
1210
+ // Clean up when extension is destroyed
1211
+ client.destroy();
1212
+ ```
1213
+
1214
+ ## Manifest Structure
1215
+
1216
+ Your extension needs a `manifest.json` file:
1217
+
1218
+ ```json
1219
+ {
1220
+ "name": "my-extension",
1221
+ "version": "1.0.0",
1222
+ "description": "My awesome extension",
1223
+
1224
+ "publicKey": "-----BEGIN PUBLIC KEY-----\n...",
1225
+ "signature": "...",
1226
+
1227
+ "namespace": "johndoe",
1228
+ "displayName": "My Extension",
1229
+ "author": "John Doe <john@example.com>",
1230
+ "icon": "icon.png",
1231
+ "main": "index.html",
1232
+
1233
+ "permissions": ["http.fetch", "notifications.show"],
1234
+
1235
+ "dependencies": [
1236
+ {
1237
+ "publicKey": "MCowBQYDK2VwAyEAp1q2r3s4t5u6v7w8x9y0z1a2b3c4d5e6f7g8h9i0j1k",
1238
+ "name": "password-manager",
1239
+ "minVersion": "1.0.0",
1240
+ "reason": "To access stored credentials",
1241
+ "tables": [
1242
+ {
1243
+ "table": "credentials",
1244
+ "operations": ["read"],
1245
+ "reason": "Read email login credentials"
1246
+ }
1247
+ ]
1248
+ }
1249
+ ]
1250
+ }
1251
+ ```
1252
+
1253
+ ## Cross-Extension Access Example
1254
+
1255
+ ### Extension A: Password Manager
1256
+
1257
+ ```typescript
1258
+ import { useHaexVault } from '@haex-space/sdk/vue';
1259
+
1260
+ const { db, getTableName } = useHaexVault();
1261
+
1262
+ // Create credentials table - no permissions needed for own tables!
1263
+ const credentialsTable = getTableName('credentials');
1264
+ await db.createTable(credentialsTable, `
1265
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1266
+ service TEXT NOT NULL,
1267
+ username TEXT NOT NULL,
1268
+ password TEXT NOT NULL
1269
+ `);
1270
+
1271
+ // Store credentials - full access to own tables
1272
+ await db.insert(credentialsTable, {
1273
+ service: 'gmail',
1274
+ username: 'john@example.com',
1275
+ password: 'encrypted_password'
1276
+ });
1277
+ ```
1278
+
1279
+ ### Extension B: Email Client
1280
+
1281
+ **manifest.json** - Must declare dependency and request permission:
1282
+
1283
+ ```json
1284
+ {
1285
+ "name": "email-client",
1286
+ "version": "1.0.0",
1287
+
1288
+ "dependencies": [
1289
+ {
1290
+ "publicKey": "MCowBQYDK2VwAyEAp1q2r3s4t5u6v7w8x9y0z1a2b3c4d5e6f7g8h9i0j1k",
1291
+ "name": "password-manager",
1292
+ "minVersion": "1.0.0",
1293
+ "reason": "Access stored credentials for email sync",
1294
+ "tables": [
1295
+ {
1296
+ "table": "credentials",
1297
+ "operations": ["read"],
1298
+ "reason": "Retrieve Gmail login credentials"
1299
+ }
1300
+ ]
1301
+ }
1302
+ ]
1303
+ }
1304
+ ```
1305
+
1306
+ **Code:**
1307
+
1308
+ ```typescript
1309
+ import { useHaexVault } from '@haex-space/sdk/react';
1310
+
1311
+ function EmailClient() {
1312
+ const { db, client } = useHaexVault();
1313
+
1314
+ async function loadCredentials() {
1315
+ // Access Password Manager's credentials table
1316
+ // ✅ Works because we declared permission in manifest
1317
+ const credentialsTable = client.getDependencyTableName(
1318
+ 'MCowBQYDK2VwAyEAp1q2r3s4t5u6v7w8x9y0z1a2b3c4d5e6f7g8h9i0j1k', // Password Manager's publicKey
1319
+ 'password-manager', // Extension name
1320
+ 'credentials' // Table name
1321
+ );
1322
+
1323
+ // Read Gmail credentials (read permission granted via manifest)
1324
+ const creds = await db.queryOne(
1325
+ `SELECT username, password FROM ${credentialsTable} WHERE service = ?`,
1326
+ ['gmail']
1327
+ );
1328
+
1329
+ if (creds) {
1330
+ connectToGmail(creds.username, creds.password);
1331
+ }
1332
+ }
1333
+
1334
+ return <button onClick={loadCredentials}>Connect Gmail</button>;
1335
+ }
1336
+ ```
1337
+
1338
+ **User Experience:**
1339
+ 1. User installs Email Client extension
1340
+ 2. HaexVault shows permission request: "Email Client wants to **read** the **credentials** table from Password Manager"
1341
+ 3. User sees the reason: "Retrieve Gmail login credentials"
1342
+ 4. User approves or denies
1343
+ 5. Permission can be revoked anytime in settings
1344
+
1345
+ ## Extension Signing & Packaging
1346
+
1347
+ HaexVault Extensions must be cryptographically signed to ensure authenticity and prevent tampering. The SDK provides tools to generate keypairs, sign, and package your extensions.
1348
+
1349
+ ### 1. Generate a Keypair (One-time Setup)
1350
+
1351
+ Before publishing your extension, generate a keypair:
1352
+
1353
+ ```bash
1354
+ npx haex keygen
1355
+ ```
1356
+
1357
+ This creates two files:
1358
+
1359
+ - `public.key` - Include this in your repository
1360
+ - `private.key` - Keep this secret! Add to `.gitignore`
1361
+
1362
+ **Important**: **Never commit your private.key**. Anyone with this key can impersonate your extension.
1363
+
1364
+ ### 2. Add Keys to .gitignore
1365
+
1366
+ ```gitignore
1367
+ private.key
1368
+ *.key
1369
+ !public.key
1370
+ ```
1371
+
1372
+ ### 3. Build and Sign Your Extension
1373
+
1374
+ Add scripts to your `package.json`:
1375
+
1376
+ ```json
1377
+ {
1378
+ "scripts": {
1379
+ "build": "nuxt generate",
1380
+ "package": "haex sign dist -k private.key",
1381
+ "build:release": "npm run build && npm run package"
1382
+ },
1383
+ "devDependencies": {
1384
+ "@haex-space/sdk": "^0.1.0"
1385
+ }
1386
+ }
1387
+ ```
1388
+
1389
+ Then build and package:
1390
+
1391
+ ```bash
1392
+ npm run build:release
1393
+ ```
1394
+
1395
+ This creates `your-extension-1.0.0.haextension` - a signed ZIP file ready for distribution.
1396
+
1397
+ **OR** build your extension and run:
1398
+
1399
+ ```bash
1400
+ npx haex sign dist -k private.key
1401
+ ```
1402
+
1403
+ ### 4. What Gets Signed?
1404
+
1405
+ The signing process:
1406
+
1407
+ 1. Computes SHA-256 hash of all files in your extension
1408
+ 2. Signs the hash with your private key using Ed25519
1409
+ 3. Adds `public_key` and `signature` to your `manifest.json`
1410
+ 4. Creates a `.haextension` file (ZIP archive)
1411
+
1412
+ ### 5. Verification
1413
+
1414
+ When users install your extension:
1415
+
1416
+ 1. HaexVault extracts the `.haextension` file
1417
+ 2. Verifies the signature using the `public_key`
1418
+ 3. Computes the hash and checks it matches
1419
+ 4. Rejects installation if verification fails
1420
+
1421
+ This ensures the extension hasn't been modified since you signed it.
1422
+
1423
+ ### 6. Key Management Best Practices
1424
+
1425
+ - **Backup your private key** - Store it securely (password manager, encrypted backup)
1426
+ - **One key per extension** - Don't reuse keys across different extensions
1427
+ - **Rotate keys carefully** - Key changes require users to reinstall your extension
1428
+ - **Lost key = lost extension** - You cannot update an extension without the original key
1429
+
1430
+ ## Security Features
1431
+
1432
+ ### ✅ Automatic Isolation
1433
+
1434
+ - **Own tables**: Full CRUD access without permissions
1435
+ - **Table namespacing**: Automatic prefix with publicKey prevents conflicts
1436
+ - **Sandbox isolation**: Extensions cannot access each other's data by default
1437
+ - **No manifest bloat**: No need to declare own tables
1438
+
1439
+ ### ✅ Cryptographic Identity
1440
+
1441
+ - Each extension identified by public key hash
1442
+ - Impossible to impersonate another extension
1443
+ - Works across all registries
1444
+ - Tamper-proof signing with Ed25519
1445
+
1446
+ ### ✅ Granular Permissions
1447
+
1448
+ - **Per-table permissions**: Request access to specific tables only
1449
+ - **Per-operation control**: `read` and/or `write` per table
1450
+ - **Explicit manifest declarations**: Dependencies must be declared upfront
1451
+ - **User consent required**: All cross-extension access needs approval
1452
+ - **Human-readable reasons**: Users see why permission is needed
1453
+
1454
+ ### ✅ Dependency Validation
1455
+
1456
+ - Must declare dependencies in manifest
1457
+ - Extension name must match publicKey
1458
+ - Version requirements enforced (semver)
1459
+ - Missing dependencies prevent installation
1460
+
1461
+ ### ✅ Runtime Verification
1462
+
1463
+ - Every database access validated in real-time
1464
+ - Permission checks on every query
1465
+ - User can revoke permissions anytime
1466
+ - No way to bypass permission system
1467
+ - Audit trail for all cross-extension access
1468
+
1469
+ ## TypeScript Support
1470
+
1471
+ Full TypeScript support included:
1472
+
1473
+ ```typescript
1474
+ import type {
1475
+ ExtensionInfo,
1476
+ ApplicationContext,
1477
+ DatabaseQueryResult,
1478
+ PermissionStatus
1479
+ } from '@haex-space/sdk';
1480
+ ```
1481
+
1482
+ ## Error Handling
1483
+
1484
+ ```typescript
1485
+ import { ErrorCode } from '@haex-space/sdk';
1486
+
1487
+ try {
1488
+ await client.database.query(`SELECT * FROM ${someTable}`);
1489
+ } catch (error) {
1490
+ if (error.code === ErrorCode.PERMISSION_DENIED) {
1491
+ console.error('Permission denied');
1492
+ } else if (error.code === ErrorCode.TIMEOUT) {
1493
+ console.error('Request timeout');
1494
+ } else {
1495
+ console.error('Error:', error.message);
1496
+ }
1497
+ }
1498
+ ```
1499
+
1500
+ ## Development
1501
+
1502
+ ```bash
1503
+ # Install dependencies
1504
+ pnpm install
1505
+
1506
+ # Build the SDK
1507
+ pnpm build
1508
+
1509
+ # Watch mode for development
1510
+ pnpm dev
1511
+
1512
+ # Link locally for testing
1513
+ pnpm link
1514
+ ```
1515
+
1516
+ ## Release Process
1517
+
1518
+ Create a new release using the automated scripts:
1519
+
1520
+ ```bash
1521
+ # Patch release (1.2.3 → 1.2.4)
1522
+ pnpm release:patch
1523
+
1524
+ # Minor release (1.2.3 → 1.3.0)
1525
+ pnpm release:minor
1526
+
1527
+ # Major release (1.2.3 → 2.0.0)
1528
+ pnpm release:major
1529
+ ```
1530
+
1531
+ The script automatically:
1532
+ 1. Updates version in `package.json`
1533
+ 2. Creates a git commit
1534
+ 3. Creates a git tag
1535
+ 4. Pushes to remote
1536
+
1537
+ After the release, publish to npm:
1538
+
1539
+ ```bash
1540
+ pnpm publishVersion
1541
+ ```
1542
+
1543
+ ## License
1544
+
1545
+ ISC
1546
+
1547
+ ## Support
1548
+
1549
+ - Documentation: https://github.com/haex-space/sdk
1550
+ - GitHub: https://github.com/haex-space/sdk
1551
+ - Issues: https://github.com/haex-space/sdk/issues