create-noy-db 0.1.0-pre.10
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,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{PROJECT_NAME}} — Vite + Vue 3 + Pinia + noy-db starter.
|
|
3
|
+
*
|
|
4
|
+
* Bootstrap order:
|
|
5
|
+
* 1. Install Pinia (required by `@noy-db/in-pinia`).
|
|
6
|
+
* 2. Open the encrypted vault via `createNoydb` + `browserIdbStore`.
|
|
7
|
+
* 3. Register the active instance with `setActiveNoydb` so any
|
|
8
|
+
* `defineNoydbStore` call can find it without manual wiring.
|
|
9
|
+
* 4. Mount the Vue app.
|
|
10
|
+
*
|
|
11
|
+
* The passphrase prompt is deliberately simple — swap in a proper
|
|
12
|
+
* modal once you've understood the flow.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createApp } from 'vue'
|
|
16
|
+
import { createPinia } from 'pinia'
|
|
17
|
+
import { createNoydb } from '@noy-db/hub'
|
|
18
|
+
import { browserIdbStore } from '@noy-db/to-browser-idb'
|
|
19
|
+
import { setActiveNoydb } from '@noy-db/in-pinia'
|
|
20
|
+
import App from './App.vue'
|
|
21
|
+
import './style.css'
|
|
22
|
+
|
|
23
|
+
async function bootstrap(): Promise<void> {
|
|
24
|
+
const pinia = createPinia()
|
|
25
|
+
|
|
26
|
+
// 1. Collect the passphrase. Reload = re-enter.
|
|
27
|
+
const passphrase =
|
|
28
|
+
prompt(
|
|
29
|
+
'Enter passphrase for {{PROJECT_NAME}}\n\n' +
|
|
30
|
+
'Derives the master encryption key. Lose it and the data is unrecoverable.',
|
|
31
|
+
) ?? ''
|
|
32
|
+
if (passphrase.length === 0) {
|
|
33
|
+
document.body.textContent = 'Cancelled — reload to try again.'
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 2. Open the encrypted vault backed by IndexedDB. The adapter sees
|
|
38
|
+
// only ciphertext (open DevTools → Application → IndexedDB to
|
|
39
|
+
// verify).
|
|
40
|
+
const db = await createNoydb({
|
|
41
|
+
store: browserIdbStore({ prefix: '{{PROJECT_NAME}}' }),
|
|
42
|
+
user: 'owner',
|
|
43
|
+
secret: passphrase,
|
|
44
|
+
})
|
|
45
|
+
await db.openVault('demo')
|
|
46
|
+
|
|
47
|
+
// 3. Register as the active instance. Pinia stores created via
|
|
48
|
+
// `defineNoydbStore` will resolve against this handle.
|
|
49
|
+
setActiveNoydb(db)
|
|
50
|
+
|
|
51
|
+
// 4. Mount.
|
|
52
|
+
createApp(App).use(pinia).mount('#app')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
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,18 @@
|
|
|
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
|
+
},
|
|
17
|
+
"include": ["src/**/*.ts", "src/**/*.vue"]
|
|
18
|
+
}
|