@noy-db/in-pinia 0.2.0-pre.9 → 0.3.0-pre.2
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 +1 -1
- package/dist/index.d.ts +16 -2
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/package.json +6 -13
- package/dist/index.cjs +0 -606
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -608
package/dist/index.d.cts
DELETED
|
@@ -1,608 +0,0 @@
|
|
|
1
|
-
import * as pinia from 'pinia';
|
|
2
|
-
import { StateTree, PiniaPlugin } from 'pinia';
|
|
3
|
-
import { ShallowRef, Ref, ComputedRef } from 'vue';
|
|
4
|
-
import { Query, Noydb, StandardSchemaV1, Vault, I18nTextDescriptor, DictKeyDescriptor, NoydbStore as NoydbStore$1, NoydbOptions, Role } from '@noy-db/hub';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* i18n resolution mode for a store's reads.
|
|
8
|
-
* - `'raw'` (default) — items keep `{ [locale]: string }` maps (today's behavior).
|
|
9
|
-
* - `'follow'` — resolve to the global `useNoydbI18n` locale; re-read on flip.
|
|
10
|
-
* - `{ locale, fallback? }` — pin to a fixed locale or own ref.
|
|
11
|
-
*/
|
|
12
|
-
type NoydbStoreI18nMode = 'raw' | 'follow' | {
|
|
13
|
-
locale: string | Ref<string>;
|
|
14
|
-
fallback?: string | readonly string[];
|
|
15
|
-
};
|
|
16
|
-
/**
|
|
17
|
-
* Reactive handle returned by `store.liveQuery(fn)`. Mirrors a hub
|
|
18
|
-
* `LiveQuery<R>` into Vue refs; `items` updates on every left- or
|
|
19
|
-
* joined-right-side mutation, `error` carries re-run errors as state
|
|
20
|
-
* (a `DanglingReferenceError` in strict join mode is the common case),
|
|
21
|
-
* `stop()` tears down upstream subscriptions. Auto-disposed on scope
|
|
22
|
-
* teardown when called inside a Vue setup / Pinia store body.
|
|
23
|
-
*/
|
|
24
|
-
interface NoydbLiveQuery<R> {
|
|
25
|
-
items: ShallowRef<readonly R[]>;
|
|
26
|
-
error: Ref<Error | null>;
|
|
27
|
-
stop(): void;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Options accepted by `defineNoydbStore`.
|
|
31
|
-
*
|
|
32
|
-
* Generic `T` is the record shape — defaults to `unknown` if the caller
|
|
33
|
-
* doesn't supply a type. Use `defineNoydbStore<Invoice>('invoices', {...})`
|
|
34
|
-
* for full type safety.
|
|
35
|
-
*/
|
|
36
|
-
interface NoydbStoreOptions<T> {
|
|
37
|
-
/** Vault (tenant) name. */
|
|
38
|
-
vault: string;
|
|
39
|
-
/** Collection name within the vault. Defaults to the store id. */
|
|
40
|
-
collection?: string;
|
|
41
|
-
/**
|
|
42
|
-
* Optional explicit Noydb instance. If omitted, the store resolves the
|
|
43
|
-
* globally bound instance via `getActiveNoydb()`.
|
|
44
|
-
*/
|
|
45
|
-
noydb?: Noydb | null;
|
|
46
|
-
/**
|
|
47
|
-
* If true (default), hydration kicks off immediately when the store is
|
|
48
|
-
* first instantiated. If false, hydration is deferred until the first
|
|
49
|
-
* call to `refresh()` or any read accessor.
|
|
50
|
-
*/
|
|
51
|
-
prefetch?: boolean;
|
|
52
|
-
/**
|
|
53
|
-
* Optional schema validator.
|
|
54
|
-
*
|
|
55
|
-
* Accepts any [Standard Schema v1](https://standardschema.dev) validator
|
|
56
|
-
* — Zod, Valibot, ArkType, Effect Schema, etc. The same validator is
|
|
57
|
-
* installed on the underlying `Collection`, so every `put()` is
|
|
58
|
-
* validated **before encryption** and every read is validated **after
|
|
59
|
-
* decryption**. The store's `add`/`update` methods inherit this
|
|
60
|
-
* validation automatically; no duplicate `.parse()` call is needed.
|
|
61
|
-
*
|
|
62
|
-
* Schema-less stores behave exactly as before (no validation, no
|
|
63
|
-
* perf cost, backwards compatible with usage).
|
|
64
|
-
*/
|
|
65
|
-
schema?: StandardSchemaV1<unknown, T>;
|
|
66
|
-
/**
|
|
67
|
-
* Optional per-field attestation schema. When set, it's installed on the
|
|
68
|
-
* underlying `Collection` (alongside `schema`) so `vault.issueAttestation(name, id)`
|
|
69
|
-
* can commit the declared fields against the firm's signing key — see
|
|
70
|
-
* `@noy-db/attestation` `AttestationFieldSchema`. Stores without it behave as before.
|
|
71
|
-
*/
|
|
72
|
-
attestation?: NonNullable<Parameters<Vault['collection']>[1]>['attestation'];
|
|
73
|
-
/**
|
|
74
|
-
* If true, the collection persists a JSON Schema baseline of `schema` so the
|
|
75
|
-
* schema-update protocol can detect drift on later opens. Forwarded as-is to
|
|
76
|
-
* the underlying `Collection`. Required for `schemaUpdate` to take effect.
|
|
77
|
-
*/
|
|
78
|
-
persistJsonSchema?: NonNullable<Parameters<Vault['collection']>[1]>['persistJsonSchema'];
|
|
79
|
-
/**
|
|
80
|
-
* Ordered schema-update strategies (e.g. `coordinatedCutover`, `additiveOnly`,
|
|
81
|
-
* `lockSchema`) applied when a stored baseline differs from the current
|
|
82
|
-
* `schema`. Forwarded as-is to the underlying `Collection`. Requires
|
|
83
|
-
* `persistJsonSchema: true` (drift detection needs the persisted baseline).
|
|
84
|
-
* Lets a `defineNoydbStore`-defined collection opt into migration tracking
|
|
85
|
-
* declaratively, without a pre-registration `vault.collection(...)` call.
|
|
86
|
-
*/
|
|
87
|
-
schemaUpdate?: NonNullable<Parameters<Vault['collection']>[1]>['schemaUpdate'];
|
|
88
|
-
/**
|
|
89
|
-
* Per-field `i18nText()` descriptors. Forwarded to the underlying
|
|
90
|
-
* `Collection` so locale resolution and required-translation validation
|
|
91
|
-
* run declaratively without a separate `vault.collection(name, { i18nFields })`
|
|
92
|
-
* pre-registration call.
|
|
93
|
-
*/
|
|
94
|
-
i18nFields?: Record<string, I18nTextDescriptor>;
|
|
95
|
-
/**
|
|
96
|
-
* Per-field `dictKey()` descriptors. Forwarded to the underlying
|
|
97
|
-
* `Collection` so dictionary label resolution runs declaratively.
|
|
98
|
-
*/
|
|
99
|
-
dictKeyFields?: Record<string, DictKeyDescriptor>;
|
|
100
|
-
/**
|
|
101
|
-
* How the store resolves i18nText/dictKey fields on read.
|
|
102
|
-
* Default `'raw'` — items keep `{ [locale]: string }` maps (today's
|
|
103
|
-
* behavior; zero breaking change). `'follow'` resolves to the global
|
|
104
|
-
* `useNoydbI18n` locale and re-reads when it changes. `{ locale }`
|
|
105
|
-
* pins to a fixed locale or own ref. Set `'raw'` for stores whose maps
|
|
106
|
-
* feed identity/export reads or a per-cell bilingual toggle.
|
|
107
|
-
*/
|
|
108
|
-
i18n?: NoydbStoreI18nMode;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* The runtime shape of the store returned by `defineNoydbStore`.
|
|
112
|
-
* Exposed as a public type so consumers can write `useStore: ReturnType<typeof useInvoices>`.
|
|
113
|
-
*/
|
|
114
|
-
interface NoydbStore<T> {
|
|
115
|
-
items: Ref<T[]>;
|
|
116
|
-
count: ComputedRef<number>;
|
|
117
|
-
$ready: Promise<void>;
|
|
118
|
-
byId(id: string): T | undefined;
|
|
119
|
-
add(id: string, record: T): Promise<void>;
|
|
120
|
-
update(id: string, record: T): Promise<void>;
|
|
121
|
-
remove(id: string): Promise<void>;
|
|
122
|
-
refresh(): Promise<void>;
|
|
123
|
-
query(): Query<T>;
|
|
124
|
-
liveQuery<R = T>(build: (q: Query<T>) => Query<R>): NoydbLiveQuery<R>;
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Define a Pinia store that's wired to a NOYDB collection.
|
|
128
|
-
*
|
|
129
|
-
* Generic T defaults to `unknown` — pass `<MyType>` for full type inference.
|
|
130
|
-
*
|
|
131
|
-
* @example
|
|
132
|
-
* ```ts
|
|
133
|
-
* import { defineNoydbStore } from '@noy-db/in-pinia';
|
|
134
|
-
*
|
|
135
|
-
* export const useInvoices = defineNoydbStore<Invoice>('invoices', {
|
|
136
|
-
* vault: 'C101',
|
|
137
|
-
* schema: InvoiceSchema, // optional
|
|
138
|
-
* });
|
|
139
|
-
* ```
|
|
140
|
-
*/
|
|
141
|
-
declare function defineNoydbStore<T>(id: string, options: NoydbStoreOptions<T>): pinia.StoreDefinition<string, Pick<{
|
|
142
|
-
items: Ref<T[], T[]>;
|
|
143
|
-
count: ComputedRef<number>;
|
|
144
|
-
$ready: Promise<void>;
|
|
145
|
-
byId: (id: string) => T | undefined;
|
|
146
|
-
add: (id: string, record: T) => Promise<void>;
|
|
147
|
-
update: (id: string, record: T) => Promise<void>;
|
|
148
|
-
remove: (id: string) => Promise<void>;
|
|
149
|
-
refresh: () => Promise<void>;
|
|
150
|
-
query: () => Query<T>;
|
|
151
|
-
liveQuery: <R = T>(build: (q: Query<T>) => Query<R>) => NoydbLiveQuery<R>;
|
|
152
|
-
}, "items" | "$ready">, Pick<{
|
|
153
|
-
items: Ref<T[], T[]>;
|
|
154
|
-
count: ComputedRef<number>;
|
|
155
|
-
$ready: Promise<void>;
|
|
156
|
-
byId: (id: string) => T | undefined;
|
|
157
|
-
add: (id: string, record: T) => Promise<void>;
|
|
158
|
-
update: (id: string, record: T) => Promise<void>;
|
|
159
|
-
remove: (id: string) => Promise<void>;
|
|
160
|
-
refresh: () => Promise<void>;
|
|
161
|
-
query: () => Query<T>;
|
|
162
|
-
liveQuery: <R = T>(build: (q: Query<T>) => Query<R>) => NoydbLiveQuery<R>;
|
|
163
|
-
}, "count">, Pick<{
|
|
164
|
-
items: Ref<T[], T[]>;
|
|
165
|
-
count: ComputedRef<number>;
|
|
166
|
-
$ready: Promise<void>;
|
|
167
|
-
byId: (id: string) => T | undefined;
|
|
168
|
-
add: (id: string, record: T) => Promise<void>;
|
|
169
|
-
update: (id: string, record: T) => Promise<void>;
|
|
170
|
-
remove: (id: string) => Promise<void>;
|
|
171
|
-
refresh: () => Promise<void>;
|
|
172
|
-
query: () => Query<T>;
|
|
173
|
-
liveQuery: <R = T>(build: (q: Query<T>) => Query<R>) => NoydbLiveQuery<R>;
|
|
174
|
-
}, "byId" | "add" | "update" | "remove" | "refresh" | "query" | "liveQuery">>;
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Active NOYDB instance binding.
|
|
178
|
-
*
|
|
179
|
-
* `defineNoydbStore` resolves the `Noydb` instance from one of three places,
|
|
180
|
-
* in priority order:
|
|
181
|
-
*
|
|
182
|
-
* 1. The store options' explicit `noydb:` field (highest precedence — useful
|
|
183
|
-
* for tests and multi-database apps).
|
|
184
|
-
* 2. A globally bound instance set via `setActiveNoydb()` — this is what the
|
|
185
|
-
* Nuxt module's runtime plugin and playground apps use.
|
|
186
|
-
* 3. Throws a clear error if neither is set.
|
|
187
|
-
*
|
|
188
|
-
* Keeping the binding pluggable means tests can pass an instance directly
|
|
189
|
-
* without polluting global state.
|
|
190
|
-
*/
|
|
191
|
-
|
|
192
|
-
/** Bind a Noydb instance globally. Called by the Nuxt module / app plugin. */
|
|
193
|
-
declare function setActiveNoydb(instance: Noydb | null): void;
|
|
194
|
-
/** Returns the globally bound Noydb instance, or null if none. */
|
|
195
|
-
declare function getActiveNoydb(): Noydb | null;
|
|
196
|
-
/**
|
|
197
|
-
* Resolve the Noydb instance to use for a store. Throws if no instance is
|
|
198
|
-
* bound — the error message points the developer at the three options.
|
|
199
|
-
*/
|
|
200
|
-
declare function resolveNoydb(explicit?: Noydb | null): Noydb;
|
|
201
|
-
|
|
202
|
-
/** Minimal vault shape needed to sync an ambient locale. */
|
|
203
|
-
interface LocaleSyncable {
|
|
204
|
-
setLocale(locale: string | undefined): void;
|
|
205
|
-
}
|
|
206
|
-
interface SetLocaleOptions {
|
|
207
|
-
/**
|
|
208
|
-
* Vault(s) to ALSO update via `vault.setLocale` (opt-in). Omit to keep
|
|
209
|
-
* the operation state-only — the safe default for locale-less vaults.
|
|
210
|
-
*/
|
|
211
|
-
readonly syncVault?: LocaleSyncable | readonly LocaleSyncable[];
|
|
212
|
-
}
|
|
213
|
-
declare const useNoydbI18n: pinia.StoreDefinition<"noydb-i18n", Pick<{
|
|
214
|
-
locale: Ref<string, string>;
|
|
215
|
-
fallback: Ref<string[], string[]>;
|
|
216
|
-
setLocale: (l: string, opts?: SetLocaleOptions) => void;
|
|
217
|
-
setFallback: (chain: string[]) => void;
|
|
218
|
-
bindTo: (source: Ref<string>, opts?: {
|
|
219
|
-
immediate?: boolean;
|
|
220
|
-
}) => () => void;
|
|
221
|
-
}, "locale" | "fallback">, Pick<{
|
|
222
|
-
locale: Ref<string, string>;
|
|
223
|
-
fallback: Ref<string[], string[]>;
|
|
224
|
-
setLocale: (l: string, opts?: SetLocaleOptions) => void;
|
|
225
|
-
setFallback: (chain: string[]) => void;
|
|
226
|
-
bindTo: (source: Ref<string>, opts?: {
|
|
227
|
-
immediate?: boolean;
|
|
228
|
-
}) => () => void;
|
|
229
|
-
}, never>, Pick<{
|
|
230
|
-
locale: Ref<string, string>;
|
|
231
|
-
fallback: Ref<string[], string[]>;
|
|
232
|
-
setLocale: (l: string, opts?: SetLocaleOptions) => void;
|
|
233
|
-
setFallback: (chain: string[]) => void;
|
|
234
|
-
bindTo: (source: Ref<string>, opts?: {
|
|
235
|
-
immediate?: boolean;
|
|
236
|
-
}) => () => void;
|
|
237
|
-
}, "setLocale" | "setFallback" | "bindTo">>;
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* `useI18nField` — a reactive `pickLang` for a single i18nText map.
|
|
241
|
-
*
|
|
242
|
-
* Resolves a `{ [locale]: string }` map to the active locale, recomputing
|
|
243
|
-
* when the locale (or a reactive source) changes. Uses hub's
|
|
244
|
-
* `resolveI18nText` with `policy: 'null'`, so it never throws and never
|
|
245
|
-
* yields `undefined` — `null` when no locale (and no fallback) resolves.
|
|
246
|
-
*
|
|
247
|
-
* Follows the global `useNoydbI18n` locale/fallback unless overridden.
|
|
248
|
-
* Ideal for resolving one field at the edge while siblings stay raw
|
|
249
|
-
* (e.g. a bilingual section reading raw maps).
|
|
250
|
-
*
|
|
251
|
-
* @public
|
|
252
|
-
*/
|
|
253
|
-
|
|
254
|
-
type MapSource = Record<string, string> | (() => Record<string, string> | undefined | null);
|
|
255
|
-
interface UseI18nFieldOptions {
|
|
256
|
-
/** Override the active locale (string or ref). Defaults to the global. */
|
|
257
|
-
readonly locale?: string | Ref<string>;
|
|
258
|
-
/** Override the fallback chain. Defaults to the global. */
|
|
259
|
-
readonly fallback?: string | readonly string[];
|
|
260
|
-
}
|
|
261
|
-
declare function useI18nField(source: MapSource, opts?: UseI18nFieldOptions): Ref<string | null>;
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* `useDictLabel(name, options)` — Vue composable for rendering
|
|
265
|
-
* `dictKey` fields at template time.
|
|
266
|
-
*
|
|
267
|
-
* The i18n boundary pattern has records store a **stable key**; the
|
|
268
|
-
* label lives in a reserved `_dict_<name>` collection and is resolved
|
|
269
|
-
* at render time. Every Vue / Nuxt consumer ends up writing the same
|
|
270
|
-
* wrapper — this composable replaces that boilerplate.
|
|
271
|
-
*
|
|
272
|
-
* ```vue
|
|
273
|
-
* <script setup lang="ts">
|
|
274
|
-
* import { useDictLabel } from '@noy-db/in-pinia'
|
|
275
|
-
* const label = useDictLabel('invoiceStatus')
|
|
276
|
-
* </script>
|
|
277
|
-
*
|
|
278
|
-
* <template>
|
|
279
|
-
* <td v-for="inv in invoices" :key="inv.id">
|
|
280
|
-
* {{ label(inv.status).value }}
|
|
281
|
-
* </td>
|
|
282
|
-
* </template>
|
|
283
|
-
* ```
|
|
284
|
-
*
|
|
285
|
-
* ## Reactivity
|
|
286
|
-
*
|
|
287
|
-
* `label(key)` returns a `Ref<string>` that updates when the locale
|
|
288
|
-
* changes (via the passed-in `locale` ref).
|
|
289
|
-
*
|
|
290
|
-
* **Known limitation:** mutations via `vault.dictionary(name).put()`
|
|
291
|
-
* bypass the Collection emitter — the hub's `DictionaryHandle` writes
|
|
292
|
-
* through the adapter directly, so labels cached by this composable
|
|
293
|
-
* won't refresh for those writes until either (a) the locale changes
|
|
294
|
-
* or (b) the caller re-creates the composable. Tracked as a
|
|
295
|
-
* hub follow-up (route dict writes through the Collection emitter).
|
|
296
|
-
*
|
|
297
|
-
* The underlying fetch is async, so a newly-constructed label starts
|
|
298
|
-
* at its "missing" sentinel (the key itself by default) and updates
|
|
299
|
-
* to the resolved string within one tick.
|
|
300
|
-
*
|
|
301
|
-
* @module
|
|
302
|
-
*/
|
|
303
|
-
|
|
304
|
-
interface UseDictLabelOptions {
|
|
305
|
-
/**
|
|
306
|
-
* Explicit vault. Either a `Vault` instance or its name. When a
|
|
307
|
-
* name is provided the composable calls `db.vault(name)` — which
|
|
308
|
-
* requires the vault to already be open via
|
|
309
|
-
* `await db.openVault(name)` elsewhere.
|
|
310
|
-
*
|
|
311
|
-
* When omitted, uses the currently-active vault on the global
|
|
312
|
-
* Noydb instance set via `setActiveNoydb()`. If no vault is open
|
|
313
|
-
* and none is provided, setup throws.
|
|
314
|
-
*/
|
|
315
|
-
readonly vault?: Vault | string;
|
|
316
|
-
/**
|
|
317
|
-
* Active locale. Pass a `Ref<string>` for live reactivity
|
|
318
|
-
* (e.g. `useI18n().locale` from vue-i18n, or `useLocale()` from a
|
|
319
|
-
* Nuxt module). A bare string is wrapped in a static ref.
|
|
320
|
-
* Defaults to `'en'`.
|
|
321
|
-
*/
|
|
322
|
-
readonly locale?: Ref<string> | string;
|
|
323
|
-
/**
|
|
324
|
-
* Fallback locale chain — evaluated in order when the primary
|
|
325
|
-
* locale has no translation. Use `'any'` as the final entry to
|
|
326
|
-
* accept any available locale. Defaults to `['en', 'any']`.
|
|
327
|
-
*/
|
|
328
|
-
readonly fallback?: string | readonly string[];
|
|
329
|
-
/**
|
|
330
|
-
* What to render when the key is absent from the dictionary.
|
|
331
|
-
*
|
|
332
|
-
* - `'key'` (default) — return the key itself. Matches the hub's
|
|
333
|
-
* stable-key invariant and surfaces typos during development.
|
|
334
|
-
* - `'empty'` — return `''`. Best for cells where a missing
|
|
335
|
-
* value should render blank.
|
|
336
|
-
* - `'placeholder'` — return `⟨missing:{key}⟩` for visible
|
|
337
|
-
* audit during QA.
|
|
338
|
-
*/
|
|
339
|
-
readonly onMissing?: 'key' | 'empty' | 'placeholder';
|
|
340
|
-
}
|
|
341
|
-
/**
|
|
342
|
-
* Build a reactive label lookup. Returns a factory `(key) => Ref<string>`.
|
|
343
|
-
*/
|
|
344
|
-
declare function useDictLabel(dictionaryName: string, options?: UseDictLabelOptions): (key: string) => Ref<string>;
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* `createNoydbPiniaPlugin` — augmentation path for existing Pinia stores.
|
|
348
|
-
*
|
|
349
|
-
* Lets a developer take any existing `defineStore()` call and opt into NOYDB
|
|
350
|
-
* persistence by adding a single `noydb:` option, without touching component
|
|
351
|
-
* code. The plugin watches the chosen state key(s), encrypts on change, syncs
|
|
352
|
-
* to a NOYDB collection, and rehydrates on store init.
|
|
353
|
-
*
|
|
354
|
-
* @example
|
|
355
|
-
* ```ts
|
|
356
|
-
* import { createPinia } from 'pinia';
|
|
357
|
-
* import { createNoydbPiniaPlugin } from '@noy-db/in-pinia';
|
|
358
|
-
* import { jsonFile } from '@noy-db/to-file';
|
|
359
|
-
*
|
|
360
|
-
* const pinia = createPinia();
|
|
361
|
-
* pinia.use(createNoydbPiniaPlugin({
|
|
362
|
-
* adapter: jsonFile({ dir: './data' }),
|
|
363
|
-
* user: 'owner-01',
|
|
364
|
-
* secret: () => promptPassphrase(),
|
|
365
|
-
* }));
|
|
366
|
-
*
|
|
367
|
-
* // existing store — add one option, no component changes:
|
|
368
|
-
* export const useClients = defineStore('clients', {
|
|
369
|
-
* state: () => ({ list: [] as Client[] }),
|
|
370
|
-
* noydb: { vault: 'C101', collection: 'clients', persist: 'list' },
|
|
371
|
-
* });
|
|
372
|
-
* ```
|
|
373
|
-
*
|
|
374
|
-
* Design notes
|
|
375
|
-
* ------------
|
|
376
|
-
* - Each augmented store persists a SINGLE document at id `__state__`
|
|
377
|
-
* containing the picked keys. We don't try to map state arrays onto
|
|
378
|
-
* per-element records — that's `defineNoydbStore`'s territory.
|
|
379
|
-
* - The Noydb instance is constructed lazily on first store-with-noydb
|
|
380
|
-
* instantiation, then memoized for the lifetime of the Pinia app.
|
|
381
|
-
* This means apps that don't actually use any noydb-augmented stores
|
|
382
|
-
* pay zero crypto cost.
|
|
383
|
-
* - `secret` is a function so the passphrase can come from a prompt,
|
|
384
|
-
* biometric unlock, or session token — never stored in config.
|
|
385
|
-
* - The plugin sets `store.$noydbReady` (a `Promise<void>`) and
|
|
386
|
-
* `store.$noydbError` (an `Error | null`) on every augmented store
|
|
387
|
-
* so components can await hydration and surface failures.
|
|
388
|
-
*/
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Per-store NOYDB configuration. Attached to a Pinia store via the `noydb`
|
|
392
|
-
* option inside `defineStore({ ..., noydb: {...} })`.
|
|
393
|
-
*
|
|
394
|
-
* `persist` selects which top-level state keys to mirror into NOYDB.
|
|
395
|
-
* Pass a single key, an array of keys, or `'*'` to mirror the entire state.
|
|
396
|
-
*/
|
|
397
|
-
interface StoreNoydbOptions<S extends StateTree = StateTree> {
|
|
398
|
-
/** Vault (tenant) name. */
|
|
399
|
-
vault: string;
|
|
400
|
-
/** Collection name within the vault. */
|
|
401
|
-
collection: string;
|
|
402
|
-
/**
|
|
403
|
-
* Which state keys to persist. Defaults to `'*'` (the entire state object).
|
|
404
|
-
* Pass a string or string[] to scope to specific keys.
|
|
405
|
-
*/
|
|
406
|
-
persist?: keyof S | (keyof S)[] | '*';
|
|
407
|
-
/**
|
|
408
|
-
* Optional schema validator applied at the document level (the persisted
|
|
409
|
-
* subset of state, not individual records). Throws if validation fails on
|
|
410
|
-
* hydration — the store stays at its initial state and `$noydbError` is set.
|
|
411
|
-
*/
|
|
412
|
-
schema?: {
|
|
413
|
-
parse: (input: unknown) => unknown;
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
/**
|
|
417
|
-
* Configuration for `createNoydbPiniaPlugin`. Mirrors `NoydbOptions` but
|
|
418
|
-
* makes `secret` a function so the passphrase can come from a prompt
|
|
419
|
-
* rather than being stored in config.
|
|
420
|
-
*/
|
|
421
|
-
interface NoydbPiniaPluginOptions {
|
|
422
|
-
/** The NOYDB store to use for persistence. */
|
|
423
|
-
adapter: NoydbStore$1;
|
|
424
|
-
/** User identifier (matches the keyring file). */
|
|
425
|
-
user: string;
|
|
426
|
-
/**
|
|
427
|
-
* Passphrase provider. Called once on first noydb-augmented store
|
|
428
|
-
* instantiation. Return a string or a Promise that resolves to one.
|
|
429
|
-
*/
|
|
430
|
-
secret: () => string | Promise<string>;
|
|
431
|
-
/** Optional Noydb open-options forwarded to `createNoydb`. */
|
|
432
|
-
noydbOptions?: Partial<Omit<NoydbOptions, 'store' | 'user' | 'secret'>>;
|
|
433
|
-
}
|
|
434
|
-
/**
|
|
435
|
-
* Create a Pinia plugin that wires NOYDB persistence into any store
|
|
436
|
-
* declaring a `noydb:` option.
|
|
437
|
-
*
|
|
438
|
-
* Returns a `PiniaPlugin` directly usable with `pinia.use(...)`.
|
|
439
|
-
*/
|
|
440
|
-
declare function createNoydbPiniaPlugin(opts: NoydbPiniaPluginOptions): PiniaPlugin;
|
|
441
|
-
declare module 'pinia' {
|
|
442
|
-
interface DefineStoreOptionsBase<S extends StateTree, Store> {
|
|
443
|
-
/**
|
|
444
|
-
* Opt this store into NOYDB persistence via the
|
|
445
|
-
* `createNoydbPiniaPlugin` augmentation plugin.
|
|
446
|
-
*
|
|
447
|
-
* The chosen state keys are encrypted and persisted to the configured
|
|
448
|
-
* vault + collection on every mutation, and rehydrated on first
|
|
449
|
-
* store access.
|
|
450
|
-
*/
|
|
451
|
-
noydb?: StoreNoydbOptions<S>;
|
|
452
|
-
}
|
|
453
|
-
interface PiniaCustomProperties {
|
|
454
|
-
/**
|
|
455
|
-
* Resolves once this store has finished its initial hydration from
|
|
456
|
-
* NOYDB. `undefined` for stores that don't declare a `noydb:` option.
|
|
457
|
-
*/
|
|
458
|
-
$noydbReady?: Promise<void>;
|
|
459
|
-
/**
|
|
460
|
-
* Set when hydration or persistence fails. `null` while healthy.
|
|
461
|
-
* Plugins (and devtools) can poll this to surface storage errors.
|
|
462
|
-
*/
|
|
463
|
-
$noydbError?: Error | null;
|
|
464
|
-
/**
|
|
465
|
-
* `true` if this store opted into NOYDB persistence via the `noydb:`
|
|
466
|
-
* option, `false` otherwise. Useful for debugging and devtools.
|
|
467
|
-
*/
|
|
468
|
-
$noydbAugmented?: boolean;
|
|
469
|
-
/**
|
|
470
|
-
* Wait for all in-flight encrypted persistence puts to complete.
|
|
471
|
-
* Useful in tests for deterministic flushing, and in app code before
|
|
472
|
-
* unmounting components that just mutated the store.
|
|
473
|
-
*/
|
|
474
|
-
$noydbFlush?: () => Promise<void>;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* `useCapabilityGrant` — Vue/Pinia composable for time-boxed
|
|
480
|
-
* capability approval flows.
|
|
481
|
-
*
|
|
482
|
-
* The composable orchestrates a request → approve → expire / release
|
|
483
|
-
* lifecycle for a session-scoped capability. It manages UI state,
|
|
484
|
-
* persists the request to a reserved `_capability_requests` collection
|
|
485
|
-
* so a separate approver session can see it, and tracks TTL with
|
|
486
|
-
* auto-revert.
|
|
487
|
-
*
|
|
488
|
-
* **It does NOT itself flip capability bits.** Capability mechanisms
|
|
489
|
-
* vary across adopters: tier-based deployments wire `onGrant` to
|
|
490
|
-
* `vault.elevate(tier, opts)`; keyring-based deployments wire it to
|
|
491
|
-
* `db.grant(...)`; custom deployments do their own thing. Keeping the
|
|
492
|
-
* actual flip behind a callback avoids introducing a parallel
|
|
493
|
-
* "capability elevation" primitive in hub when the existing
|
|
494
|
-
* `vault.elevate()` already covers the time-boxed-grant pattern.
|
|
495
|
-
*
|
|
496
|
-
* ## State machine
|
|
497
|
-
*
|
|
498
|
-
* idle ─── .request() ───────► requested
|
|
499
|
-
* requested ─── .approve() ──► granted
|
|
500
|
-
* granted ─── TTL expires ─► idle
|
|
501
|
-
* granted ─── .release() ──► idle
|
|
502
|
-
*
|
|
503
|
-
* @module
|
|
504
|
-
*/
|
|
505
|
-
|
|
506
|
-
/** Reserved internal collection that holds capability-grant lifecycle records. */
|
|
507
|
-
declare const CAPABILITY_REQUESTS_COLLECTION = "_capability_requests";
|
|
508
|
-
type CapabilityGrantState = 'idle' | 'requested' | 'granted' | 'expired';
|
|
509
|
-
/**
|
|
510
|
-
* On-disk shape of a capability-grant lifecycle record. Persisted in
|
|
511
|
-
* the reserved {@link CAPABILITY_REQUESTS_COLLECTION}. Encrypted with
|
|
512
|
-
* that collection's DEK at the storage layer; the in-memory shape
|
|
513
|
-
* here is plaintext.
|
|
514
|
-
*
|
|
515
|
-
* The audit trail invariant: this record carries metadata only —
|
|
516
|
-
* capability name, roles, ttl, reason. Never plaintext payload.
|
|
517
|
-
*/
|
|
518
|
-
interface CapabilityGrantRecord {
|
|
519
|
-
readonly id: string;
|
|
520
|
-
readonly capability: string;
|
|
521
|
-
readonly requestedBy: string;
|
|
522
|
-
readonly approverRole: Role;
|
|
523
|
-
readonly reason: string;
|
|
524
|
-
readonly ttlMs: number;
|
|
525
|
-
readonly status: 'requested' | 'granted' | 'released' | 'expired';
|
|
526
|
-
readonly requestedAt: string;
|
|
527
|
-
readonly approvedBy?: string;
|
|
528
|
-
readonly approvedAt?: string;
|
|
529
|
-
readonly expiresAt?: string;
|
|
530
|
-
}
|
|
531
|
-
interface UseCapabilityGrantOptions {
|
|
532
|
-
/** TTL in milliseconds for the granted window. */
|
|
533
|
-
readonly ttlMs: number;
|
|
534
|
-
/** Role required to call `.approve()`. Mismatch throws on `.approve()`. */
|
|
535
|
-
readonly approver: Role;
|
|
536
|
-
/** Audit-ledger string. Stamped on the request record; no plaintext payload. */
|
|
537
|
-
readonly reason: string;
|
|
538
|
-
/**
|
|
539
|
-
* Optional explicit vault. Either a `Vault` instance or its name.
|
|
540
|
-
* When omitted, resolves the active Noydb instance via
|
|
541
|
-
* `setActiveNoydb()` and opens the first vault the caller has
|
|
542
|
-
* already loaded.
|
|
543
|
-
*/
|
|
544
|
-
readonly vault: Vault | string;
|
|
545
|
-
/**
|
|
546
|
-
* Called on the approver's session when `.approve()` succeeds. Wire
|
|
547
|
-
* this to whatever capability flip your codebase uses —
|
|
548
|
-
* `vault.elevate(tier, opts)` for tier-based deployments,
|
|
549
|
-
* `db.grant(...)` for keyring-based, custom for custom.
|
|
550
|
-
*
|
|
551
|
-
* The composable does NOT enforce that the capability was actually
|
|
552
|
-
* granted — it just tracks the lifecycle. The post-expiry "gated
|
|
553
|
-
* call throws" contract comes from the underlying mechanism the
|
|
554
|
-
* callback wires up (e.g., `ElevationExpiredError` from
|
|
555
|
-
* `vault.elevate`'s lazy TTL check).
|
|
556
|
-
*/
|
|
557
|
-
readonly onGrant?: (ctx: {
|
|
558
|
-
record: CapabilityGrantRecord;
|
|
559
|
-
vault: Vault;
|
|
560
|
-
}) => void | Promise<void>;
|
|
561
|
-
/**
|
|
562
|
-
* Called when the grant ends (TTL expiry OR voluntary release).
|
|
563
|
-
* Mirror of `onGrant`. Idempotent — may be called twice if release
|
|
564
|
-
* and expiry race; callers should no-op on the second invocation.
|
|
565
|
-
*/
|
|
566
|
-
readonly onRelease?: (ctx: {
|
|
567
|
-
record: CapabilityGrantRecord;
|
|
568
|
-
vault: Vault;
|
|
569
|
-
cause: 'released' | 'expired';
|
|
570
|
-
}) => void | Promise<void>;
|
|
571
|
-
}
|
|
572
|
-
interface UseCapabilityGrantReturn {
|
|
573
|
-
readonly state: Ref<CapabilityGrantState>;
|
|
574
|
-
/** Milliseconds remaining on the granted window; 0 outside `granted`. */
|
|
575
|
-
readonly timeRemaining: ComputedRef<number>;
|
|
576
|
-
/** Most recent error from request/approve/release (resets on next op). */
|
|
577
|
-
readonly error: Ref<Error | null>;
|
|
578
|
-
/** Issue a request. State must be `idle`. */
|
|
579
|
-
request(): Promise<void>;
|
|
580
|
-
/** Approve a pending request. State must be `requested`. */
|
|
581
|
-
approve(): Promise<void>;
|
|
582
|
-
/** Voluntarily revoke an active grant. State must be `granted`. */
|
|
583
|
-
release(): Promise<void>;
|
|
584
|
-
}
|
|
585
|
-
/**
|
|
586
|
-
* Build a reactive capability-grant lifecycle handle.
|
|
587
|
-
*
|
|
588
|
-
* @example Tier-based capability flip
|
|
589
|
-
* ```ts
|
|
590
|
-
* let elevated: ElevatedHandle | null = null
|
|
591
|
-
* const grant = useCapabilityGrant('canExportPlaintext', {
|
|
592
|
-
* vault: 'V1',
|
|
593
|
-
* ttlMs: 15 * 60_000,
|
|
594
|
-
* approver: 'admin',
|
|
595
|
-
* reason: 'bulk export',
|
|
596
|
-
* onGrant: async ({ vault, record }) => {
|
|
597
|
-
* elevated = await vault.elevate(2, {
|
|
598
|
-
* ttlMs: record.ttlMs,
|
|
599
|
-
* reason: record.reason,
|
|
600
|
-
* })
|
|
601
|
-
* },
|
|
602
|
-
* onRelease: async () => { await elevated?.release(); elevated = null },
|
|
603
|
-
* })
|
|
604
|
-
* ```
|
|
605
|
-
*/
|
|
606
|
-
declare function useCapabilityGrant(capability: string, options: UseCapabilityGrantOptions): UseCapabilityGrantReturn;
|
|
607
|
-
|
|
608
|
-
export { CAPABILITY_REQUESTS_COLLECTION, type CapabilityGrantRecord, type CapabilityGrantState, type LocaleSyncable, type NoydbLiveQuery, type NoydbPiniaPluginOptions, type NoydbStore, type NoydbStoreOptions, type SetLocaleOptions, type StoreNoydbOptions, type UseCapabilityGrantOptions, type UseCapabilityGrantReturn, type UseDictLabelOptions, type UseI18nFieldOptions, createNoydbPiniaPlugin, defineNoydbStore, getActiveNoydb, resolveNoydb, setActiveNoydb, useCapabilityGrant, useDictLabel, useI18nField, useNoydbI18n };
|