@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.
- package/README.md +1551 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +455 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.mjs +430 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/client-O_JEOzfx.d.mts +491 -0
- package/dist/client-O_JEOzfx.d.ts +491 -0
- package/dist/index.d.mts +124 -0
- package/dist/index.d.ts +124 -0
- package/dist/index.js +1429 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1409 -0
- package/dist/index.mjs.map +1 -0
- package/dist/node.d.mts +25 -0
- package/dist/node.d.ts +25 -0
- package/dist/node.js +28 -0
- package/dist/node.js.map +1 -0
- package/dist/node.mjs +25 -0
- package/dist/node.mjs.map +1 -0
- package/dist/nuxt.d.mts +19 -0
- package/dist/nuxt.d.ts +19 -0
- package/dist/nuxt.js +473 -0
- package/dist/nuxt.js.map +1 -0
- package/dist/nuxt.mjs +470 -0
- package/dist/nuxt.mjs.map +1 -0
- package/dist/react.d.mts +28 -0
- package/dist/react.d.ts +28 -0
- package/dist/react.js +1389 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +1386 -0
- package/dist/react.mjs.map +1 -0
- package/dist/runtime/nuxt.plugin.client.d.mts +43 -0
- package/dist/runtime/nuxt.plugin.client.d.ts +43 -0
- package/dist/runtime/nuxt.plugin.client.js +1137 -0
- package/dist/runtime/nuxt.plugin.client.js.map +1 -0
- package/dist/runtime/nuxt.plugin.client.mjs +1135 -0
- package/dist/runtime/nuxt.plugin.client.mjs.map +1 -0
- package/dist/runtime/nuxt.types.d.ts +15 -0
- package/dist/svelte.d.mts +48 -0
- package/dist/svelte.d.ts +48 -0
- package/dist/svelte.js +1398 -0
- package/dist/svelte.js.map +1 -0
- package/dist/svelte.mjs +1391 -0
- package/dist/svelte.mjs.map +1 -0
- package/dist/vite.d.mts +37 -0
- package/dist/vite.d.ts +37 -0
- package/dist/vite.js +346 -0
- package/dist/vite.js.map +1 -0
- package/dist/vite.mjs +341 -0
- package/dist/vite.mjs.map +1 -0
- package/dist/vue.d.mts +49 -0
- package/dist/vue.d.ts +49 -0
- package/dist/vue.js +1388 -0
- package/dist/vue.js.map +1 -0
- package/dist/vue.mjs +1385 -0
- package/dist/vue.mjs.map +1 -0
- package/package.json +121 -0
package/README.md
ADDED
|
@@ -0,0 +1,1551 @@
|
|
|
1
|
+
# @haex-space/sdk
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@haex-space/sdk)
|
|
4
|
+
[](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
|