create-noy-db 0.1.0-pre.4
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/LICENSE +21 -0
- package/README.md +283 -0
- package/dist/bin/create.d.ts +1 -0
- package/dist/bin/create.js +815 -0
- package/dist/bin/create.js.map +1 -0
- package/dist/bin/noy-db.d.ts +1 -0
- package/dist/bin/noy-db.js +555 -0
- package/dist/bin/noy-db.js.map +1 -0
- package/dist/index.d.ts +697 -0
- package/dist/index.js +967 -0
- package/dist/index.js.map +1 -0
- package/package.json +74 -0
- package/templates/electron/README.md +85 -0
- package/templates/electron/_gitignore +31 -0
- package/templates/electron/electron/main.ts +51 -0
- package/templates/electron/index.html +12 -0
- package/templates/electron/package.json +32 -0
- package/templates/electron/src/App.vue +75 -0
- package/templates/electron/src/components/AddInvoiceForm.vue +39 -0
- package/templates/electron/src/components/InvoiceTable.vue +41 -0
- package/templates/electron/src/main.ts +48 -0
- package/templates/electron/src/stores/invoices.ts +35 -0
- package/templates/electron/src/style.css +119 -0
- package/templates/electron/tsconfig.json +19 -0
- package/templates/electron/vite.config.ts +21 -0
- package/templates/nuxt-default/README.md +38 -0
- package/templates/nuxt-default/_gitignore +32 -0
- package/templates/nuxt-default/app/app.vue +37 -0
- package/templates/nuxt-default/app/pages/index.vue +21 -0
- package/templates/nuxt-default/app/pages/invoices.vue +62 -0
- package/templates/nuxt-default/app/stores/invoices.ts +23 -0
- package/templates/nuxt-default/nuxt.config.ts +30 -0
- package/templates/nuxt-default/package.json +28 -0
- package/templates/nuxt-default/tsconfig.json +3 -0
- package/templates/vanilla/README.md +77 -0
- package/templates/vanilla/_gitignore +29 -0
- package/templates/vanilla/index.html +54 -0
- package/templates/vanilla/package.json +21 -0
- package/templates/vanilla/src/main.ts +153 -0
- package/templates/vanilla/src/style.css +127 -0
- package/templates/vanilla/tsconfig.json +17 -0
- package/templates/vanilla/vite.config.ts +10 -0
- package/templates/vite-vue/README.md +95 -0
- package/templates/vite-vue/_gitignore +29 -0
- package/templates/vite-vue/index.html +12 -0
- package/templates/vite-vue/package.json +28 -0
- package/templates/vite-vue/src/App.vue +75 -0
- package/templates/vite-vue/src/components/AddInvoiceForm.vue +39 -0
- package/templates/vite-vue/src/components/InvoiceTable.vue +41 -0
- package/templates/vite-vue/src/main.ts +55 -0
- package/templates/vite-vue/src/stores/invoices.ts +35 -0
- package/templates/vite-vue/src/style.css +119 -0
- package/templates/vite-vue/tsconfig.json +18 -0
- package/templates/vite-vue/vite.config.ts +12 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { Invoice } from '../stores/invoices'
|
|
3
|
+
|
|
4
|
+
defineProps<{ invoices: Invoice[] }>()
|
|
5
|
+
const emit = defineEmits<{ (e: 'remove', id: string): void }>()
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<section class="invoices">
|
|
10
|
+
<h2>Invoices</h2>
|
|
11
|
+
<table v-if="invoices.length > 0">
|
|
12
|
+
<thead>
|
|
13
|
+
<tr>
|
|
14
|
+
<th>ID</th>
|
|
15
|
+
<th>Client</th>
|
|
16
|
+
<th>Amount</th>
|
|
17
|
+
<th>Status</th>
|
|
18
|
+
<th>Issued</th>
|
|
19
|
+
<th></th>
|
|
20
|
+
</tr>
|
|
21
|
+
</thead>
|
|
22
|
+
<tbody>
|
|
23
|
+
<tr v-for="inv in invoices" :key="inv.id">
|
|
24
|
+
<td>{{ inv.id }}</td>
|
|
25
|
+
<td>{{ inv.client }}</td>
|
|
26
|
+
<td>{{ inv.amount.toLocaleString() }}</td>
|
|
27
|
+
<td>
|
|
28
|
+
<span :class="`status-pill status-${inv.status}`">{{ inv.status }}</span>
|
|
29
|
+
</td>
|
|
30
|
+
<td>{{ inv.issueDate }}</td>
|
|
31
|
+
<td>
|
|
32
|
+
<button class="danger" @click="emit('remove', inv.id)">×</button>
|
|
33
|
+
</td>
|
|
34
|
+
</tr>
|
|
35
|
+
</tbody>
|
|
36
|
+
</table>
|
|
37
|
+
<p v-else class="empty">
|
|
38
|
+
No invoices yet — add one above.
|
|
39
|
+
</p>
|
|
40
|
+
</section>
|
|
41
|
+
</template>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{PROJECT_NAME}} — renderer bootstrap.
|
|
3
|
+
*
|
|
4
|
+
* Opens a Noydb vault backed by `@noy-db/to-file`, writing JSON to
|
|
5
|
+
* a directory under the user's data folder. Perfect for the
|
|
6
|
+
* USB-stick workflow: point the `dir` at an external mount and
|
|
7
|
+
* the vault travels with you.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createApp } from 'vue'
|
|
11
|
+
import { createPinia } from 'pinia'
|
|
12
|
+
import { createNoydb } from '@noy-db/hub'
|
|
13
|
+
import { jsonFile } from '@noy-db/to-file'
|
|
14
|
+
import { setActiveNoydb } from '@noy-db/in-pinia'
|
|
15
|
+
import path from 'node:path'
|
|
16
|
+
import App from './App.vue'
|
|
17
|
+
import './style.css'
|
|
18
|
+
|
|
19
|
+
async function bootstrap(): Promise<void> {
|
|
20
|
+
const pinia = createPinia()
|
|
21
|
+
|
|
22
|
+
// Default directory: `<userData>/{{PROJECT_NAME}}/vault`. For a
|
|
23
|
+
// USB-stick workflow, prompt the user for a path and use that
|
|
24
|
+
// instead (e.g. `/Volumes/USB/myapp`).
|
|
25
|
+
const baseDir = process.env.VITE_DEV_SERVER_URL
|
|
26
|
+
? path.resolve('./.{{PROJECT_NAME}}-dev-vault')
|
|
27
|
+
: path.resolve(process.resourcesPath ?? '.', '{{PROJECT_NAME}}-vault')
|
|
28
|
+
|
|
29
|
+
const passphrase = prompt(
|
|
30
|
+
'Enter passphrase for {{PROJECT_NAME}}\n\n' +
|
|
31
|
+
'Master encryption key — lose it and the data is unrecoverable.',
|
|
32
|
+
) ?? ''
|
|
33
|
+
if (passphrase.length === 0) {
|
|
34
|
+
document.body.textContent = 'Cancelled — reload to try again.'
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const db = await createNoydb({
|
|
39
|
+
store: jsonFile({ dir: baseDir }),
|
|
40
|
+
user: 'owner',
|
|
41
|
+
secret: passphrase,
|
|
42
|
+
})
|
|
43
|
+
await db.openVault('demo')
|
|
44
|
+
setActiveNoydb(db)
|
|
45
|
+
createApp(App).use(pinia).mount('#app')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
void bootstrap()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Invoice store — reactive, encrypted, backed by a noy-db collection.
|
|
3
|
+
*
|
|
4
|
+
* `defineNoydbStore` wraps `defineStore` from Pinia and wires the
|
|
5
|
+
* store to a vault + collection. All writes encrypt before the
|
|
6
|
+
* IndexedDB adapter sees them; all reads decrypt into the reactive
|
|
7
|
+
* `items` array.
|
|
8
|
+
*
|
|
9
|
+
* Use from components:
|
|
10
|
+
* const invoices = useInvoices()
|
|
11
|
+
* await invoices.$ready
|
|
12
|
+
* await invoices.add(id, record)
|
|
13
|
+
* invoices.items // reactive array
|
|
14
|
+
* invoices.count // reactive getter
|
|
15
|
+
*/
|
|
16
|
+
import { defineNoydbStore } from '@noy-db/in-pinia'
|
|
17
|
+
|
|
18
|
+
export interface Invoice {
|
|
19
|
+
id: string
|
|
20
|
+
client: string
|
|
21
|
+
amount: number
|
|
22
|
+
status: 'draft' | 'open' | 'paid' | 'overdue'
|
|
23
|
+
issueDate: string // ISO-8601
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const useInvoices = defineNoydbStore<Invoice>('invoices', {
|
|
27
|
+
vault: 'demo',
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
/** Optional seed set rendered on first load when the collection is empty. */
|
|
31
|
+
export const DEFAULT_INVOICES: Invoice[] = [
|
|
32
|
+
{ id: 'inv-001', client: 'Acme Corp', amount: 1_200, status: 'paid', issueDate: '2026-01-15' },
|
|
33
|
+
{ id: 'inv-002', client: 'Globex', amount: 2_400, status: 'open', issueDate: '2026-02-01' },
|
|
34
|
+
{ id: 'inv-003', client: 'Initech', amount: 800, status: 'draft', issueDate: '2026-02-20' },
|
|
35
|
+
]
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light;
|
|
3
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
4
|
+
color: #1f2937;
|
|
5
|
+
background: #f9fafb;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
* { box-sizing: border-box; }
|
|
9
|
+
|
|
10
|
+
body { margin: 0; padding: 1rem; }
|
|
11
|
+
|
|
12
|
+
.app { max-width: 900px; margin: 0 auto; }
|
|
13
|
+
|
|
14
|
+
header {
|
|
15
|
+
border-bottom: 1px solid #e5e7eb;
|
|
16
|
+
padding-bottom: 1rem;
|
|
17
|
+
margin-bottom: 1.5rem;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
h1 { font-size: 1.5rem; margin: 0 0 0.5rem 0; }
|
|
21
|
+
|
|
22
|
+
.subtitle { color: #6b7280; font-size: 0.9rem; margin: 0; }
|
|
23
|
+
|
|
24
|
+
.loading {
|
|
25
|
+
background: white;
|
|
26
|
+
border: 1px solid #e5e7eb;
|
|
27
|
+
border-radius: 0.5rem;
|
|
28
|
+
padding: 1.5rem;
|
|
29
|
+
text-align: center;
|
|
30
|
+
color: #6b7280;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.summary {
|
|
34
|
+
display: flex;
|
|
35
|
+
gap: 1rem;
|
|
36
|
+
margin-bottom: 1.5rem;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.stat {
|
|
40
|
+
flex: 1;
|
|
41
|
+
background: white;
|
|
42
|
+
border: 1px solid #e5e7eb;
|
|
43
|
+
border-radius: 0.5rem;
|
|
44
|
+
padding: 1rem;
|
|
45
|
+
}
|
|
46
|
+
.stat .label { color: #6b7280; font-size: 0.75rem; text-transform: uppercase; }
|
|
47
|
+
.stat .value { font-size: 1.5rem; font-weight: 600; margin-top: 0.25rem; }
|
|
48
|
+
|
|
49
|
+
.add-form {
|
|
50
|
+
display: flex;
|
|
51
|
+
gap: 0.5rem;
|
|
52
|
+
margin-bottom: 1.5rem;
|
|
53
|
+
}
|
|
54
|
+
.add-form input, .add-form select {
|
|
55
|
+
padding: 0.5rem;
|
|
56
|
+
border: 1px solid #d1d5db;
|
|
57
|
+
border-radius: 0.25rem;
|
|
58
|
+
font-size: 0.875rem;
|
|
59
|
+
}
|
|
60
|
+
.add-form input[placeholder='Client name'] { flex: 2; }
|
|
61
|
+
.add-form input[type='number'] { flex: 1; }
|
|
62
|
+
|
|
63
|
+
button {
|
|
64
|
+
padding: 0.5rem 1rem;
|
|
65
|
+
border: 1px solid #2563eb;
|
|
66
|
+
background: #2563eb;
|
|
67
|
+
color: white;
|
|
68
|
+
border-radius: 0.25rem;
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
font-size: 0.875rem;
|
|
71
|
+
}
|
|
72
|
+
button:hover { background: #1d4ed8; }
|
|
73
|
+
button.danger {
|
|
74
|
+
background: #dc2626;
|
|
75
|
+
border-color: #dc2626;
|
|
76
|
+
padding: 0.125rem 0.5rem;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.invoices h2 { font-size: 1.1rem; margin-top: 0; }
|
|
80
|
+
|
|
81
|
+
table {
|
|
82
|
+
width: 100%;
|
|
83
|
+
border-collapse: collapse;
|
|
84
|
+
background: white;
|
|
85
|
+
border: 1px solid #e5e7eb;
|
|
86
|
+
border-radius: 0.5rem;
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
}
|
|
89
|
+
th, td {
|
|
90
|
+
padding: 0.5rem 0.75rem;
|
|
91
|
+
text-align: left;
|
|
92
|
+
border-bottom: 1px solid #e5e7eb;
|
|
93
|
+
font-size: 0.875rem;
|
|
94
|
+
}
|
|
95
|
+
th { background: #f3f4f6; font-weight: 600; }
|
|
96
|
+
tr:last-child td { border-bottom: 0; }
|
|
97
|
+
|
|
98
|
+
.empty { color: #6b7280; text-align: center; font-style: italic; padding: 1rem; }
|
|
99
|
+
|
|
100
|
+
.status-pill {
|
|
101
|
+
display: inline-block;
|
|
102
|
+
padding: 0.125rem 0.5rem;
|
|
103
|
+
border-radius: 0.25rem;
|
|
104
|
+
font-size: 0.75rem;
|
|
105
|
+
text-transform: uppercase;
|
|
106
|
+
font-weight: 600;
|
|
107
|
+
}
|
|
108
|
+
.status-draft { background: #f3f4f6; color: #4b5563; }
|
|
109
|
+
.status-open { background: #dbeafe; color: #1e40af; }
|
|
110
|
+
.status-paid { background: #d1fae5; color: #065f46; }
|
|
111
|
+
.status-overdue { background: #fee2e2; color: #b91c1c; }
|
|
112
|
+
|
|
113
|
+
footer {
|
|
114
|
+
margin-top: 3rem;
|
|
115
|
+
padding-top: 1rem;
|
|
116
|
+
border-top: 1px solid #e5e7eb;
|
|
117
|
+
color: #6b7280;
|
|
118
|
+
font-size: 0.85rem;
|
|
119
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"isolatedModules": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"useDefineForClassFields": true,
|
|
15
|
+
"jsx": "preserve",
|
|
16
|
+
"types": ["node"]
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*.ts", "src/**/*.vue", "electron/**/*.ts"]
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import vue from '@vitejs/plugin-vue'
|
|
3
|
+
import electron from 'vite-plugin-electron'
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [
|
|
7
|
+
vue(),
|
|
8
|
+
electron({
|
|
9
|
+
entry: 'electron/main.ts',
|
|
10
|
+
vite: {
|
|
11
|
+
build: {
|
|
12
|
+
outDir: 'dist-electron',
|
|
13
|
+
target: 'es2022',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
}),
|
|
17
|
+
],
|
|
18
|
+
build: {
|
|
19
|
+
target: 'es2022',
|
|
20
|
+
},
|
|
21
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A Nuxt 4 + Pinia + [noy-db](https://github.com/vLannaAi/noy-db) starter, scaffolded by `create-noy-db`.
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
- **Nuxt 4** — fullstack Vue framework
|
|
8
|
+
- **Pinia** — reactive state management
|
|
9
|
+
- **@noy-db/in-nuxt** — Nuxt module for noy-db (auto-imports, SSR-safe runtime, devtools tab)
|
|
10
|
+
- **@noy-db/in-pinia** — `defineNoydbStore` — drop-in `defineStore` replacement that wires a Pinia store to an encrypted compartment + collection
|
|
11
|
+
- **@noy-db/{{ADAPTER}}** — storage adapter
|
|
12
|
+
|
|
13
|
+
Everything stored is encrypted with AES-256-GCM before it touches the adapter. The adapter only ever sees ciphertext.
|
|
14
|
+
|
|
15
|
+
## Getting started
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm install # or npm/yarn/bun
|
|
19
|
+
pnpm dev # nuxt dev on http://localhost:3000
|
|
20
|
+
pnpm build # production build
|
|
21
|
+
pnpm preview # preview the production build
|
|
22
|
+
pnpm verify # run the noy-db integrity check
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Adding a collection
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx noy-db add clients
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This scaffolds `app/stores/clients.ts` and `app/pages/clients.vue`. Edit the generated `Client` interface to match your domain, then visit `/clients` in your dev server.
|
|
32
|
+
|
|
33
|
+
## Documentation
|
|
34
|
+
|
|
35
|
+
- [noy-db getting started](https://github.com/vLannaAi/noy-db/blob/main/docs/quickstart.md)
|
|
36
|
+
- [End-user features](https://github.com/vLannaAi/noy-db/blob/main/docs/features.md)
|
|
37
|
+
- [Architecture](https://github.com/vLannaAi/noy-db/blob/main/docs/architecture.md)
|
|
38
|
+
- [Roadmap](https://github.com/vLannaAi/noy-db/blob/main/ROADMAP.md)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Nuxt build outputs
|
|
2
|
+
.nuxt
|
|
3
|
+
.output
|
|
4
|
+
.data
|
|
5
|
+
.cache
|
|
6
|
+
.vite
|
|
7
|
+
|
|
8
|
+
# Node
|
|
9
|
+
node_modules
|
|
10
|
+
*.log
|
|
11
|
+
.npm
|
|
12
|
+
|
|
13
|
+
# Env files
|
|
14
|
+
.env
|
|
15
|
+
.env.local
|
|
16
|
+
.env.*.local
|
|
17
|
+
|
|
18
|
+
# OS
|
|
19
|
+
.DS_Store
|
|
20
|
+
Thumbs.db
|
|
21
|
+
|
|
22
|
+
# IDE
|
|
23
|
+
.vscode/*
|
|
24
|
+
!.vscode/extensions.json
|
|
25
|
+
.idea
|
|
26
|
+
*.suo
|
|
27
|
+
*.ntvs*
|
|
28
|
+
*.njsproj
|
|
29
|
+
*.sln
|
|
30
|
+
|
|
31
|
+
# Test coverage
|
|
32
|
+
coverage
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Top-level layout. Add navigation here as you grow the app.
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<div>
|
|
7
|
+
<header>
|
|
8
|
+
<nav>
|
|
9
|
+
<NuxtLink to="/">Home</NuxtLink>
|
|
10
|
+
<NuxtLink to="/invoices">Invoices</NuxtLink>
|
|
11
|
+
</nav>
|
|
12
|
+
</header>
|
|
13
|
+
<main>
|
|
14
|
+
<NuxtPage />
|
|
15
|
+
</main>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<style>
|
|
20
|
+
nav {
|
|
21
|
+
display: flex;
|
|
22
|
+
gap: 1rem;
|
|
23
|
+
padding: 1rem;
|
|
24
|
+
border-bottom: 1px solid #e5e5e5;
|
|
25
|
+
}
|
|
26
|
+
nav a {
|
|
27
|
+
text-decoration: none;
|
|
28
|
+
color: #0070f3;
|
|
29
|
+
}
|
|
30
|
+
nav a.router-link-active {
|
|
31
|
+
font-weight: bold;
|
|
32
|
+
}
|
|
33
|
+
main {
|
|
34
|
+
padding: 1rem;
|
|
35
|
+
font-family: system-ui, sans-serif;
|
|
36
|
+
}
|
|
37
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Landing page for the generated noy-db app.
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<section>
|
|
7
|
+
<h1>{{PROJECT_NAME}}</h1>
|
|
8
|
+
<p>
|
|
9
|
+
Encrypted, offline-first, zero-knowledge document store powered by
|
|
10
|
+
<a href="https://github.com/vLannaAi/noy-db">noy-db</a>.
|
|
11
|
+
</p>
|
|
12
|
+
<p>
|
|
13
|
+
Visit <NuxtLink to="/invoices">/invoices</NuxtLink> to see the demo
|
|
14
|
+
invoices store.
|
|
15
|
+
</p>
|
|
16
|
+
<p>
|
|
17
|
+
Run <code>noy-db verify</code> at any time to check the integrity of
|
|
18
|
+
your noy-db install.
|
|
19
|
+
</p>
|
|
20
|
+
</section>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { DEFAULT_INVOICES } from '~/stores/invoices'
|
|
3
|
+
|
|
4
|
+
const invoices = useInvoices()
|
|
5
|
+
await invoices.$ready
|
|
6
|
+
|
|
7
|
+
// Seed on first run: if the store is empty and there are defaults, populate.
|
|
8
|
+
// This runs once on component mount; later visits see whatever the user
|
|
9
|
+
// has edited.
|
|
10
|
+
if (invoices.items.length === 0 && DEFAULT_INVOICES.length > 0) {
|
|
11
|
+
for (const inv of DEFAULT_INVOICES) {
|
|
12
|
+
invoices.add(inv)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Reactive query: re-runs whenever the underlying collection changes.
|
|
17
|
+
const open = invoices.query()
|
|
18
|
+
.where('status', '==', 'open')
|
|
19
|
+
.live()
|
|
20
|
+
|
|
21
|
+
function addDraft() {
|
|
22
|
+
invoices.add({
|
|
23
|
+
id: crypto.randomUUID(),
|
|
24
|
+
client: 'New Client',
|
|
25
|
+
amount: Math.round(Math.random() * 10000),
|
|
26
|
+
status: 'draft',
|
|
27
|
+
dueDate: new Date().toISOString().slice(0, 10),
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function remove(id: string) {
|
|
32
|
+
invoices.remove(id)
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<section>
|
|
38
|
+
<h1>Invoices</h1>
|
|
39
|
+
<button @click="addDraft">+ New draft</button>
|
|
40
|
+
<p><strong>{{ open.length }}</strong> open invoice(s)</p>
|
|
41
|
+
<ul>
|
|
42
|
+
<li v-for="inv in invoices.items" :key="inv.id">
|
|
43
|
+
<strong>{{ inv.client }}</strong> — {{ inv.amount }} ({{ inv.status }})
|
|
44
|
+
<button @click="remove(inv.id)">Delete</button>
|
|
45
|
+
</li>
|
|
46
|
+
</ul>
|
|
47
|
+
<p v-if="invoices.items.length === 0">
|
|
48
|
+
No invoices yet — click "New draft" to add one.
|
|
49
|
+
</p>
|
|
50
|
+
</section>
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
<style scoped>
|
|
54
|
+
button {
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
padding: 0.25rem 0.75rem;
|
|
57
|
+
margin: 0.25rem;
|
|
58
|
+
}
|
|
59
|
+
li {
|
|
60
|
+
padding: 0.5rem 0;
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Generated by `create-noy-db`.
|
|
2
|
+
//
|
|
3
|
+
// `defineNoydbStore` is auto-imported by @noy-db/in-nuxt. The store wires
|
|
4
|
+
// a Pinia store to a noy-db compartment + collection. Encryption,
|
|
5
|
+
// keyring management, and adapter wiring are invisible to components.
|
|
6
|
+
|
|
7
|
+
export interface Invoice {
|
|
8
|
+
id: string
|
|
9
|
+
client: string
|
|
10
|
+
amount: number
|
|
11
|
+
status: 'draft' | 'open' | 'paid' | 'overdue'
|
|
12
|
+
dueDate: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const useInvoices = defineNoydbStore<Invoice>('invoices', {
|
|
16
|
+
vault: 'demo-co',
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Default seed records rendered into the store on first mount (see
|
|
21
|
+
* `app/pages/invoices.vue`). An empty array means the page starts empty.
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_INVOICES: Invoice[] = {{SEED_INVOICES}}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Generated by `create-noy-db`.
|
|
2
|
+
//
|
|
3
|
+
// The `noydb:` key is fully typed via @noy-db/in-nuxt's module augmentation
|
|
4
|
+
// of @nuxt/schema. Hover any field for documentation.
|
|
5
|
+
|
|
6
|
+
export default defineNuxtConfig({
|
|
7
|
+
compatibilityDate: '2026-04-01',
|
|
8
|
+
|
|
9
|
+
modules: [
|
|
10
|
+
'@pinia/nuxt',
|
|
11
|
+
'@noy-db/in-nuxt',
|
|
12
|
+
],
|
|
13
|
+
|
|
14
|
+
noydb: {
|
|
15
|
+
adapter: '{{ADAPTER}}',
|
|
16
|
+
pinia: true,
|
|
17
|
+
devtools: {{DEVTOOLS}},
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
devtools: {
|
|
21
|
+
enabled: process.env['NODE_ENV'] !== 'production',
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
telemetry: false,
|
|
25
|
+
|
|
26
|
+
typescript: {
|
|
27
|
+
strict: true,
|
|
28
|
+
typeCheck: false,
|
|
29
|
+
},
|
|
30
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "nuxt dev",
|
|
8
|
+
"build": "nuxt build",
|
|
9
|
+
"preview": "nuxt preview",
|
|
10
|
+
"verify": "noy-db verify"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@noy-db/to-browser-idb": "^0.11.0",
|
|
14
|
+
"@noy-db/hub": "^0.11.0",
|
|
15
|
+
"@noy-db/to-file": "^0.11.0",
|
|
16
|
+
"@noy-db/to-memory": "^0.11.0",
|
|
17
|
+
"@noy-db/in-nuxt": "^0.11.0",
|
|
18
|
+
"@noy-db/in-pinia": "^0.11.0",
|
|
19
|
+
"@noy-db/in-vue": "^0.11.0",
|
|
20
|
+
"@pinia/nuxt": "^0.11.0",
|
|
21
|
+
"nuxt": "^4.4.2",
|
|
22
|
+
"pinia": "^3.0.1",
|
|
23
|
+
"vue": "^3.5.32"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"create-noy-db": "^0.5.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A Vite + TypeScript + [noy-db](https://github.com/vLannaAi/noy-db)
|
|
4
|
+
starter with **no framework** — just the browser, the hub, and the
|
|
5
|
+
IndexedDB store. Scaffolded by `create-noy-db`.
|
|
6
|
+
|
|
7
|
+
## Stack
|
|
8
|
+
|
|
9
|
+
- **Vite** — dev server + build
|
|
10
|
+
- **@noy-db/hub** — zero-knowledge encrypted document store
|
|
11
|
+
- **@noy-db/to-browser-idb** — IndexedDB adapter (atomic CAS, larger
|
|
12
|
+
quota than localStorage)
|
|
13
|
+
|
|
14
|
+
Everything stored is encrypted with AES-256-GCM before it touches
|
|
15
|
+
IndexedDB. The adapter only ever sees ciphertext.
|
|
16
|
+
|
|
17
|
+
## When to pick this template
|
|
18
|
+
|
|
19
|
+
- You want the **smallest possible** noy-db starting point.
|
|
20
|
+
- You're integrating noy-db into an existing non-framework app (hand-rolled
|
|
21
|
+
DOM, web components, a library).
|
|
22
|
+
- You want to learn the hub API without a framework abstraction layer in
|
|
23
|
+
the way.
|
|
24
|
+
|
|
25
|
+
If you want Vue, Pinia, or Nuxt, pick the `nuxt-default` template
|
|
26
|
+
instead (`npm create noy-db@latest -- --template nuxt-default`).
|
|
27
|
+
|
|
28
|
+
## Getting started
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm install # or npm/yarn/bun
|
|
32
|
+
pnpm dev # vite dev on http://localhost:5173
|
|
33
|
+
pnpm build # production build
|
|
34
|
+
pnpm preview # preview the production build
|
|
35
|
+
pnpm verify # run the noy-db integrity check
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
First time the page loads it will prompt for a **passphrase** — that
|
|
39
|
+
passphrase derives the master key. Lose it and the data is
|
|
40
|
+
unrecoverable (by design: the library cannot help you).
|
|
41
|
+
|
|
42
|
+
## What's in `src/main.ts`
|
|
43
|
+
|
|
44
|
+
The starter walks through the full lifecycle:
|
|
45
|
+
|
|
46
|
+
1. `createNoydb({ store: browserIdbStore(), secret: passphrase })` —
|
|
47
|
+
open the encrypted store.
|
|
48
|
+
2. `await db.openVault('demo')` — create or open a vault (tenant).
|
|
49
|
+
3. `vault.collection<Invoice>('invoices')` — typed collection.
|
|
50
|
+
4. CRUD operations — `.put()` / `.get()` / `.delete()` / `.list()`.
|
|
51
|
+
5. `db.close()` — clears the master key from memory.
|
|
52
|
+
|
|
53
|
+
The UI is intentionally plain HTML — swap in your framework of choice
|
|
54
|
+
once you've understood the flow.
|
|
55
|
+
|
|
56
|
+
## Verifying zero-knowledge
|
|
57
|
+
|
|
58
|
+
Open DevTools → Application → IndexedDB → your origin → `noydb_demo`
|
|
59
|
+
(or whatever vault name you used). Every record shows `{ _noydb: 1,
|
|
60
|
+
_v, _ts, _iv, _data }`. The `_data` blob is AES-GCM ciphertext — the
|
|
61
|
+
literal record contents never appear.
|
|
62
|
+
|
|
63
|
+
## Adding a collection
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx noy-db add clients
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This scaffolds `src/clients.ts` with a typed Client interface +
|
|
70
|
+
CRUD helpers. Edit the generated interface to match your domain.
|
|
71
|
+
|
|
72
|
+
## Documentation
|
|
73
|
+
|
|
74
|
+
- [noy-db getting started](https://github.com/vLannaAi/noy-db/blob/main/docs/quickstart.md)
|
|
75
|
+
- [Topology matrix](https://github.com/vLannaAi/noy-db/blob/main/docs/topologies.md) — pick the right stack
|
|
76
|
+
- [Architecture](https://github.com/vLannaAi/noy-db/blob/main/docs/architecture.md)
|
|
77
|
+
- [Roadmap](https://github.com/vLannaAi/noy-db/blob/main/ROADMAP.md)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Vite / build output
|
|
2
|
+
dist
|
|
3
|
+
.vite
|
|
4
|
+
|
|
5
|
+
# Node
|
|
6
|
+
node_modules
|
|
7
|
+
*.log
|
|
8
|
+
.npm
|
|
9
|
+
|
|
10
|
+
# Env files
|
|
11
|
+
.env
|
|
12
|
+
.env.local
|
|
13
|
+
.env.*.local
|
|
14
|
+
|
|
15
|
+
# OS
|
|
16
|
+
.DS_Store
|
|
17
|
+
Thumbs.db
|
|
18
|
+
|
|
19
|
+
# IDE
|
|
20
|
+
.vscode/*
|
|
21
|
+
!.vscode/extensions.json
|
|
22
|
+
.idea
|
|
23
|
+
*.suo
|
|
24
|
+
*.ntvs*
|
|
25
|
+
*.njsproj
|
|
26
|
+
*.sln
|
|
27
|
+
|
|
28
|
+
# Test coverage
|
|
29
|
+
coverage
|