@noy-db/in-nuxt 0.2.0-pre.3 → 0.2.0-pre.31

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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/module.ts
2
- import { defineNuxtModule, addImports, addPlugin, addServerHandler, createResolver } from "@nuxt/kit";
2
+ import { defineNuxtModule, addImports, addPlugin, addServerHandler, createResolver, extendPages } from "@nuxt/kit";
3
3
  var module_default = defineNuxtModule({
4
4
  meta: {
5
5
  name: "@noy-db/in-nuxt",
@@ -48,6 +48,24 @@ var module_default = defineNuxtModule({
48
48
  handler: resolver.resolve("./runtime/rest")
49
49
  });
50
50
  }
51
+ if (nuxt.options.dev && options.devtools !== false) {
52
+ const panelFile = resolver.resolve("./runtime/devtools/DevtoolsPanel.vue");
53
+ extendPages((pages) => {
54
+ pages.push({
55
+ name: "noydb-devtools",
56
+ path: "/_noydb-devtools",
57
+ file: panelFile
58
+ });
59
+ });
60
+ nuxt.hook("devtools:customTabs", (tabs) => {
61
+ tabs.push({
62
+ name: "noy-db",
63
+ title: "noy-db",
64
+ icon: "i-carbon-data-base",
65
+ view: { type: "iframe", src: "/_noydb-devtools" }
66
+ });
67
+ });
68
+ }
51
69
  }
52
70
  });
53
71
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/module.ts","../src/index.ts"],"sourcesContent":["/**\n * Nuxt 4 module for noy-db.\n *\n * Built with `@nuxt/kit`'s `defineNuxtModule`. Targets Nuxt 4+ exclusively\n * (no Nuxt 3 compatibility shim — Nuxt 3 users should consume `@noy-db/vue`\n * and `@noy-db/pinia` directly with a hand-written plugin).\n *\n * Module responsibilities:\n *\n * 1. Auto-import the @noy-db/in-vue composables (`useNoydb`, `useCollection`,\n * `useSync`) and, when `pinia: true` (default), the @noy-db/in-pinia\n * helpers (`defineNoydbStore`, `createNoydbPiniaPlugin`, `setActiveNoydb`).\n *\n * 2. Expose the user's `noydb:` config through `runtimeConfig.public.noydb`\n * so the runtime plugin and downstream composables can read it\n * without re-parsing nuxt.config.ts.\n *\n * 3. Register a CLIENT-ONLY runtime plugin (`runtime/plugin.client.ts`)\n * that sets up the noydb context. The server bundle is never touched —\n * this is the load-bearing SSR-safety property.\n *\n * Deferred to follow-up issues:\n * - Devtools tab via @nuxt/devtools-kit\n * - Optional Nitro server proxy (`/api/_noydb/...`)\n * - Optional Nitro scheduled backup task\n * - `nuxi noydb` CLI extension\n * - Eager Noydb instantiation (requires the user's passphrase callback,\n * which can't be serialized through runtime config — better to let\n * users call setActiveNoydb from their own setup file)\n */\n\nimport { defineNuxtModule, addImports, addPlugin, addServerHandler, createResolver } from '@nuxt/kit'\n\n/**\n * Configuration shape for the `noydb:` key in `nuxt.config.ts`.\n *\n * Every field is optional. The defaults give a reasonable bootstrap for\n * a typical Vue/Nuxt app — Pinia helpers auto-imported, `to-browser-idb`\n * as the default store. Users override by passing the relevant fields.\n */\nexport interface ModuleOptions {\n /**\n * Which built-in store package to prefer. The runtime plugin reads this\n * and picks the matching store. Defaults to `'to-browser-idb'` because\n * Nuxt apps run in the browser at runtime.\n *\n * Note: this is just a HINT. Users can always construct their own\n * store and pass it to `createNoydb()` directly — this option exists\n * to keep simple cases simple.\n */\n store?: 'to-browser-idb' | 'to-browser-local' | 'to-memory' | 'to-file' | 'to-aws-dynamo' | 'to-aws-s3'\n\n /**\n * Auto-import the @noy-db/pinia helpers (`defineNoydbStore`,\n * `createNoydbPiniaPlugin`, `setActiveNoydb`). Defaults to `true`\n * because Pinia is the recommended state layer for.\n *\n * Set to `false` if you only want the bare @noy-db/vue composables\n * (saves ~3 KB from the auto-import metadata).\n */\n pinia?: boolean\n\n /**\n * Optional sync configuration. Currently a passthrough — the runtime\n * plugin reads it from `runtimeConfig.public.noydb.sync` and the user\n * is responsible for wiring it into their `createNoydb()` call.\n */\n sync?: {\n store?: 'to-aws-dynamo' | 'to-aws-s3'\n table?: string\n region?: string\n bucket?: string\n mode?: 'auto' | 'manual' | 'off'\n }\n\n /**\n * Optional auth configuration metadata. Same passthrough pattern as\n * `sync`. The user provides the actual passphrase / biometric callback\n * in their own setup file.\n */\n auth?: {\n mode?: 'passphrase' | 'biometric' | 'session'\n sessionTimeout?: string\n }\n\n /**\n * Whether to enable the (planned) devtools tab in `nuxi dev`. Currently\n * a passthrough — the devtools tab itself ships in a follow-up.\n */\n devtools?: boolean\n\n /**\n * Optional REST API integration. When `enabled: true`, mounts a catch-all\n * Nitro server handler at `basePath/**` using `@noy-db/in-rest`.\n *\n * The handler is scaffold-level: it reads `event.context.noydbStore` which\n * a separate Nitro plugin must populate. spec follow-up for the store\n * wiring. The module simply registers the route here.\n */\n rest?: {\n /** Enable the REST API server handler. Default: false. */\n enabled?: boolean\n /** Base path for all REST routes. Default: '/api/noydb'. */\n basePath?: string\n /** User ID forwarded to createRestHandler. */\n user?: string\n /** Session TTL in seconds. Default: 900. */\n ttlSeconds?: number\n }\n}\n\n/**\n * The exported Nuxt module factory.\n *\n * Test-friendly: `defineNuxtModule` returns a NuxtModule object whose\n * `.meta`, `.getOptions`, and `.setup` fields can be inspected without a\n * full Nuxt build. Unit tests use those introspection points instead of\n * spinning up `@nuxt/test-utils`.\n */\nexport default defineNuxtModule<ModuleOptions>({\n meta: {\n name: '@noy-db/in-nuxt',\n configKey: 'noydb',\n compatibility: {\n // Nuxt 4 only — see the module-level docstring for the rationale.\n nuxt: '^4.0.0',\n },\n },\n\n defaults: {\n store: 'to-browser-idb',\n pinia: true,\n devtools: true,\n },\n\n setup(options, nuxt) {\n const resolver = createResolver(import.meta.url)\n\n // ─── 1. Expose the user's options to runtime via runtimeConfig ───\n //\n // We stash the typed options under `runtimeConfig.public.noydb` so\n // the client plugin (and any downstream composable) can read them\n // without re-parsing nuxt.config.ts. `public` is required so the\n // values reach the browser bundle — but EVERY field is metadata\n // (store name, table name, etc.), NEVER a secret. Passphrases\n // and tokens are still provided at runtime via user callbacks.\n nuxt.options.runtimeConfig.public.noydb = {\n // The cast is necessary because Nuxt's runtimeConfig type is\n // structurally `Record<string, any>` — modules are expected to\n // own their own typing via module augmentation (which we do\n // below).\n ...(nuxt.options.runtimeConfig.public.noydb ?? {}),\n ...options,\n }\n\n // ─── 2. Auto-imports for @noy-db/vue composables ────────────────\n //\n // These are the composables shipped by @noy-db/vue. Importing\n // them automatically removes one line of boilerplate per component.\n addImports([\n { name: 'useNoydb', from: '@noy-db/in-vue' },\n { name: 'useCollection', from: '@noy-db/in-vue' },\n { name: 'useSync', from: '@noy-db/in-vue' },\n ])\n\n // ─── 3. Auto-imports for @noy-db/pinia (opt-out) ────────────────\n //\n // Most users want the Pinia helpers — `defineNoydbStore` is the\n // headline API. We default to enabling them and let users\n // opt out via `pinia: false` if they're not using Pinia at all.\n if (options.pinia !== false) {\n addImports([\n { name: 'defineNoydbStore', from: '@noy-db/in-pinia' },\n { name: 'createNoydbPiniaPlugin', from: '@noy-db/in-pinia' },\n { name: 'setActiveNoydb', from: '@noy-db/in-pinia' },\n { name: 'getActiveNoydb', from: '@noy-db/in-pinia' },\n ])\n }\n\n // ─── 4. Register the client-only runtime plugin ─────────────────\n //\n // mode: 'client' is the LOAD-BEARING SSR-safety guarantee. Nuxt\n // skips this plugin entirely on the server, so the server bundle\n // never imports any code that touches `crypto.subtle`. The CI\n // bundle assertion (planned for a follow-up) verifies this by\n // grepping the built nitro output for forbidden symbols.\n //\n // The path resolves to the COMPILED runtime file in `dist/runtime/`.\n // tsup builds this as a separate entry alongside the module index.\n addPlugin({\n src: resolver.resolve('./runtime/plugin.client.js'),\n mode: 'client',\n })\n\n // ─── 5. REST API server handler (opt-in) ────────────────────────\n //\n // When `rest.enabled: true`, mount a catch-all Nitro server handler\n // at `basePath/**`. The handler delegates to `@noy-db/in-rest` via\n // the nitroAdapter. Store wiring (populating event.context.noydbStore)\n // is a follow-up concern tracked in #273.\n if (options.rest?.enabled) {\n const basePath = options.rest.basePath ?? '/api/noydb'\n addServerHandler({\n route: `${basePath}/**`,\n handler: resolver.resolve('./runtime/rest'),\n })\n }\n },\n})\n\n/**\n * Module augmentation so the `noydb:` config key in `nuxt.config.ts`\n * is fully typed and autocompleted in the IDE.\n *\n * The augmentation is a side-effect of importing the module — once a\n * project adds `'@noy-db/in-nuxt'` to its `modules` array, TypeScript picks\n * up the typed `noydb` option without requiring an explicit import.\n */\ndeclare module '@nuxt/schema' {\n interface NuxtConfig {\n noydb?: ModuleOptions\n }\n interface NuxtOptions {\n noydb?: ModuleOptions\n }\n interface PublicRuntimeConfig {\n noydb?: ModuleOptions\n }\n}\n","/**\n * @noy-db/nuxt — Nuxt 4 module for noy-db.\n *\n * Public API:\n * - default export: the Nuxt module factory\n * - `ModuleOptions` type: shape of the `noydb:` key in `nuxt.config.ts`\n *\n * The module auto-imports the @noy-db/vue and (optionally) @noy-db/pinia\n * composables, exposes the user's options through Nuxt's runtime config,\n * and registers a CLIENT-ONLY runtime plugin so the SSR bundle never\n * touches `crypto.subtle`. Eager Noydb instantiation is intentionally\n * left to user code — see the README \"Bootstrap\" section.\n */\n\nimport noydbModule, { type ModuleOptions } from './module.js'\n\nexport default noydbModule\nexport type { ModuleOptions }\n"],"mappings":";AA+BA,SAAS,kBAAkB,YAAY,WAAW,kBAAkB,sBAAsB;AAwF1F,IAAO,iBAAQ,iBAAgC;AAAA,EAC7C,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,WAAW;AAAA,IACX,eAAe;AAAA;AAAA,MAEb,MAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,UAAU;AAAA,IACR,OAAO;AAAA,IACP,OAAO;AAAA,IACP,UAAU;AAAA,EACZ;AAAA,EAEA,MAAM,SAAS,MAAM;AACnB,UAAM,WAAW,eAAe,YAAY,GAAG;AAU/C,SAAK,QAAQ,cAAc,OAAO,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,MAKxC,GAAI,KAAK,QAAQ,cAAc,OAAO,SAAS,CAAC;AAAA,MAChD,GAAG;AAAA,IACL;AAMA,eAAW;AAAA,MACT,EAAE,MAAM,YAAY,MAAM,iBAAiB;AAAA,MAC3C,EAAE,MAAM,iBAAiB,MAAM,iBAAiB;AAAA,MAChD,EAAE,MAAM,WAAW,MAAM,iBAAiB;AAAA,IAC5C,CAAC;AAOD,QAAI,QAAQ,UAAU,OAAO;AAC3B,iBAAW;AAAA,QACT,EAAE,MAAM,oBAAoB,MAAM,mBAAmB;AAAA,QACrD,EAAE,MAAM,0BAA0B,MAAM,mBAAmB;AAAA,QAC3D,EAAE,MAAM,kBAAkB,MAAM,mBAAmB;AAAA,QACnD,EAAE,MAAM,kBAAkB,MAAM,mBAAmB;AAAA,MACrD,CAAC;AAAA,IACH;AAYA,cAAU;AAAA,MACR,KAAK,SAAS,QAAQ,4BAA4B;AAAA,MAClD,MAAM;AAAA,IACR,CAAC;AAQD,QAAI,QAAQ,MAAM,SAAS;AACzB,YAAM,WAAW,QAAQ,KAAK,YAAY;AAC1C,uBAAiB;AAAA,QACf,OAAO,GAAG,QAAQ;AAAA,QAClB,SAAS,SAAS,QAAQ,gBAAgB;AAAA,MAC5C,CAAC;AAAA,IACH;AAAA,EACF;AACF,CAAC;;;AChMD,IAAO,gBAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/module.ts","../src/index.ts"],"sourcesContent":["/**\n * Nuxt 4 module for noy-db.\n *\n * Built with `@nuxt/kit`'s `defineNuxtModule`. Targets Nuxt 4+ exclusively\n * (no Nuxt 3 compatibility shim — Nuxt 3 users should consume `@noy-db/vue`\n * and `@noy-db/pinia` directly with a hand-written plugin).\n *\n * Module responsibilities:\n *\n * 1. Auto-import the @noy-db/in-vue composables (`useNoydb`, `useCollection`,\n * `useSync`) and, when `pinia: true` (default), the @noy-db/in-pinia\n * helpers (`defineNoydbStore`, `createNoydbPiniaPlugin`, `setActiveNoydb`).\n *\n * 2. Expose the user's `noydb:` config through `runtimeConfig.public.noydb`\n * so the runtime plugin and downstream composables can read it\n * without re-parsing nuxt.config.ts.\n *\n * 3. Register a CLIENT-ONLY runtime plugin (`runtime/plugin.client.ts`)\n * that sets up the noydb context. The server bundle is never touched —\n * this is the load-bearing SSR-safety property.\n *\n * Deferred to follow-up issues:\n * - Devtools tab via @nuxt/devtools-kit\n * - Optional Nitro server proxy (`/api/_noydb/...`)\n * - Optional Nitro scheduled backup task\n * - `nuxi noydb` CLI extension\n * - Eager Noydb instantiation (requires the user's passphrase callback,\n * which can't be serialized through runtime config — better to let\n * users call setActiveNoydb from their own setup file)\n */\n\nimport { defineNuxtModule, addImports, addPlugin, addServerHandler, createResolver, extendPages } from '@nuxt/kit'\n\n/**\n * Configuration shape for the `noydb:` key in `nuxt.config.ts`.\n *\n * Every field is optional. The defaults give a reasonable bootstrap for\n * a typical Vue/Nuxt app — Pinia helpers auto-imported, `to-browser-idb`\n * as the default store. Users override by passing the relevant fields.\n */\nexport interface ModuleOptions {\n /**\n * Which built-in store package to prefer. The runtime plugin reads this\n * and picks the matching store. Defaults to `'to-browser-idb'` because\n * Nuxt apps run in the browser at runtime.\n *\n * Note: this is just a HINT. Users can always construct their own\n * store and pass it to `createNoydb()` directly — this option exists\n * to keep simple cases simple.\n */\n store?: 'to-browser-idb' | 'to-browser-local' | 'to-memory' | 'to-file' | 'to-aws-dynamo' | 'to-aws-s3'\n\n /**\n * Auto-import the @noy-db/pinia helpers (`defineNoydbStore`,\n * `createNoydbPiniaPlugin`, `setActiveNoydb`). Defaults to `true`\n * because Pinia is the recommended state layer for.\n *\n * Set to `false` if you only want the bare @noy-db/vue composables\n * (saves ~3 KB from the auto-import metadata).\n */\n pinia?: boolean\n\n /**\n * Optional sync configuration. Currently a passthrough — the runtime\n * plugin reads it from `runtimeConfig.public.noydb.sync` and the user\n * is responsible for wiring it into their `createNoydb()` call.\n */\n sync?: {\n store?: 'to-aws-dynamo' | 'to-aws-s3'\n table?: string\n region?: string\n bucket?: string\n mode?: 'auto' | 'manual' | 'off'\n }\n\n /**\n * Optional auth configuration metadata. Same passthrough pattern as\n * `sync`. The user provides the actual passphrase / biometric callback\n * in their own setup file.\n */\n auth?: {\n mode?: 'passphrase' | 'biometric' | 'session'\n sessionTimeout?: string\n }\n\n /**\n * Whether to enable the (planned) devtools tab in `nuxi dev`. Currently\n * a passthrough — the devtools tab itself ships in a follow-up.\n */\n devtools?: boolean\n\n /**\n * Optional REST API integration. When `enabled: true`, mounts a catch-all\n * Nitro server handler at `basePath/**` using `@noy-db/in-rest`.\n *\n * The handler is scaffold-level: it reads `event.context.noydbStore` which\n * a separate Nitro plugin must populate. spec follow-up for the store\n * wiring. The module simply registers the route here.\n */\n rest?: {\n /** Enable the REST API server handler. Default: false. */\n enabled?: boolean\n /** Base path for all REST routes. Default: '/api/noydb'. */\n basePath?: string\n /** User ID forwarded to createRestHandler. */\n user?: string\n /** Session TTL in seconds. Default: 900. */\n ttlSeconds?: number\n }\n}\n\n/**\n * The exported Nuxt module factory.\n *\n * Test-friendly: `defineNuxtModule` returns a NuxtModule object whose\n * `.meta`, `.getOptions`, and `.setup` fields can be inspected without a\n * full Nuxt build. Unit tests use those introspection points instead of\n * spinning up `@nuxt/test-utils`.\n */\nexport default defineNuxtModule<ModuleOptions>({\n meta: {\n name: '@noy-db/in-nuxt',\n configKey: 'noydb',\n compatibility: {\n // Nuxt 4 only — see the module-level docstring for the rationale.\n nuxt: '^4.0.0',\n },\n },\n\n defaults: {\n store: 'to-browser-idb',\n pinia: true,\n devtools: true,\n },\n\n setup(options, nuxt) {\n const resolver = createResolver(import.meta.url)\n\n // ─── 1. Expose the user's options to runtime via runtimeConfig ───\n //\n // We stash the typed options under `runtimeConfig.public.noydb` so\n // the client plugin (and any downstream composable) can read them\n // without re-parsing nuxt.config.ts. `public` is required so the\n // values reach the browser bundle — but EVERY field is metadata\n // (store name, table name, etc.), NEVER a secret. Passphrases\n // and tokens are still provided at runtime via user callbacks.\n nuxt.options.runtimeConfig.public.noydb = {\n // The cast is necessary because Nuxt's runtimeConfig type is\n // structurally `Record<string, any>` — modules are expected to\n // own their own typing via module augmentation (which we do\n // below).\n ...(nuxt.options.runtimeConfig.public.noydb ?? {}),\n ...options,\n }\n\n // ─── 2. Auto-imports for @noy-db/vue composables ────────────────\n //\n // These are the composables shipped by @noy-db/vue. Importing\n // them automatically removes one line of boilerplate per component.\n addImports([\n { name: 'useNoydb', from: '@noy-db/in-vue' },\n { name: 'useCollection', from: '@noy-db/in-vue' },\n { name: 'useSync', from: '@noy-db/in-vue' },\n ])\n\n // ─── 3. Auto-imports for @noy-db/pinia (opt-out) ────────────────\n //\n // Most users want the Pinia helpers — `defineNoydbStore` is the\n // headline API. We default to enabling them and let users\n // opt out via `pinia: false` if they're not using Pinia at all.\n if (options.pinia !== false) {\n addImports([\n { name: 'defineNoydbStore', from: '@noy-db/in-pinia' },\n { name: 'createNoydbPiniaPlugin', from: '@noy-db/in-pinia' },\n { name: 'setActiveNoydb', from: '@noy-db/in-pinia' },\n { name: 'getActiveNoydb', from: '@noy-db/in-pinia' },\n ])\n }\n\n // ─── 4. Register the client-only runtime plugin ─────────────────\n //\n // mode: 'client' is the LOAD-BEARING SSR-safety guarantee. Nuxt\n // skips this plugin entirely on the server, so the server bundle\n // never imports any code that touches `crypto.subtle`. The CI\n // bundle assertion (planned for a follow-up) verifies this by\n // grepping the built nitro output for forbidden symbols.\n //\n // The path resolves to the COMPILED runtime file in `dist/runtime/`.\n // tsup builds this as a separate entry alongside the module index.\n addPlugin({\n src: resolver.resolve('./runtime/plugin.client.js'),\n mode: 'client',\n })\n\n // ─── 5. REST API server handler (opt-in) ────────────────────────\n //\n // When `rest.enabled: true`, mount a catch-all Nitro server handler\n // at `basePath/**`. The handler delegates to `@noy-db/in-rest` via\n // the nitroAdapter. Store wiring (populating event.context.noydbStore)\n // is a follow-up concern tracked separately.\n if (options.rest?.enabled) {\n const basePath = options.rest.basePath ?? '/api/noydb'\n addServerHandler({\n route: `${basePath}/**`,\n handler: resolver.resolve('./runtime/rest'),\n })\n }\n\n // ─── 6. DevTools tab (dev mode only) ────────────────────────\n //\n // Registers a virtual Nuxt page at /_noydb-devtools and exposes it\n // as a tab in the Nuxt DevTools overlay. The page runs inside the\n // user's full Vue app context — no iframe bridge, direct access to\n // getActiveNoydb() and the inspector facade.\n //\n // Guarded on nuxt.options.dev: never ships to production builds.\n // Users can opt out with `noydb: { devtools: false }`.\n if (nuxt.options.dev && options.devtools !== false) {\n const panelFile = resolver.resolve('./runtime/devtools/DevtoolsPanel.vue')\n\n extendPages((pages) => {\n pages.push({\n name: 'noydb-devtools',\n path: '/_noydb-devtools',\n file: panelFile,\n })\n })\n\n // `devtools:customTabs` is not in Nuxt's static HookKeys type but IS\n // dispatched at runtime by @nuxt/devtools. Cast to bypass the type\n // guard while keeping the hook strictly dev-only.\n ;(nuxt as unknown as { hook(event: string, fn: (tabs: unknown[]) => void): void })\n .hook('devtools:customTabs', (tabs: unknown[]) => {\n tabs.push({\n name: 'noy-db',\n title: 'noy-db',\n icon: 'i-carbon-data-base',\n view: { type: 'iframe', src: '/_noydb-devtools' },\n })\n })\n }\n },\n})\n\n/**\n * Module augmentation so the `noydb:` config key in `nuxt.config.ts`\n * is fully typed and autocompleted in the IDE.\n *\n * The augmentation is a side-effect of importing the module — once a\n * project adds `'@noy-db/in-nuxt'` to its `modules` array, TypeScript picks\n * up the typed `noydb` option without requiring an explicit import.\n */\ndeclare module '@nuxt/schema' {\n interface NuxtConfig {\n noydb?: ModuleOptions\n }\n interface NuxtOptions {\n noydb?: ModuleOptions\n }\n interface PublicRuntimeConfig {\n noydb?: ModuleOptions\n }\n}\n","/**\n * @noy-db/nuxt — Nuxt 4 module for noy-db.\n *\n * Public API:\n * - default export: the Nuxt module factory\n * - `ModuleOptions` type: shape of the `noydb:` key in `nuxt.config.ts`\n *\n * The module auto-imports the @noy-db/vue and (optionally) @noy-db/pinia\n * composables, exposes the user's options through Nuxt's runtime config,\n * and registers a CLIENT-ONLY runtime plugin so the SSR bundle never\n * touches `crypto.subtle`. Eager Noydb instantiation is intentionally\n * left to user code — see the README \"Bootstrap\" section.\n */\n\nimport noydbModule, { type ModuleOptions } from './module.js'\n\nexport default noydbModule\nexport type { ModuleOptions }\n"],"mappings":";AA+BA,SAAS,kBAAkB,YAAY,WAAW,kBAAkB,gBAAgB,mBAAmB;AAwFvG,IAAO,iBAAQ,iBAAgC;AAAA,EAC7C,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,WAAW;AAAA,IACX,eAAe;AAAA;AAAA,MAEb,MAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,UAAU;AAAA,IACR,OAAO;AAAA,IACP,OAAO;AAAA,IACP,UAAU;AAAA,EACZ;AAAA,EAEA,MAAM,SAAS,MAAM;AACnB,UAAM,WAAW,eAAe,YAAY,GAAG;AAU/C,SAAK,QAAQ,cAAc,OAAO,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,MAKxC,GAAI,KAAK,QAAQ,cAAc,OAAO,SAAS,CAAC;AAAA,MAChD,GAAG;AAAA,IACL;AAMA,eAAW;AAAA,MACT,EAAE,MAAM,YAAY,MAAM,iBAAiB;AAAA,MAC3C,EAAE,MAAM,iBAAiB,MAAM,iBAAiB;AAAA,MAChD,EAAE,MAAM,WAAW,MAAM,iBAAiB;AAAA,IAC5C,CAAC;AAOD,QAAI,QAAQ,UAAU,OAAO;AAC3B,iBAAW;AAAA,QACT,EAAE,MAAM,oBAAoB,MAAM,mBAAmB;AAAA,QACrD,EAAE,MAAM,0BAA0B,MAAM,mBAAmB;AAAA,QAC3D,EAAE,MAAM,kBAAkB,MAAM,mBAAmB;AAAA,QACnD,EAAE,MAAM,kBAAkB,MAAM,mBAAmB;AAAA,MACrD,CAAC;AAAA,IACH;AAYA,cAAU;AAAA,MACR,KAAK,SAAS,QAAQ,4BAA4B;AAAA,MAClD,MAAM;AAAA,IACR,CAAC;AAQD,QAAI,QAAQ,MAAM,SAAS;AACzB,YAAM,WAAW,QAAQ,KAAK,YAAY;AAC1C,uBAAiB;AAAA,QACf,OAAO,GAAG,QAAQ;AAAA,QAClB,SAAS,SAAS,QAAQ,gBAAgB;AAAA,MAC5C,CAAC;AAAA,IACH;AAWA,QAAI,KAAK,QAAQ,OAAO,QAAQ,aAAa,OAAO;AAClD,YAAM,YAAY,SAAS,QAAQ,sCAAsC;AAEzE,kBAAY,CAAC,UAAU;AACrB,cAAM,KAAK;AAAA,UACT,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,QACR,CAAC;AAAA,MACH,CAAC;AAKA,MAAC,KACC,KAAK,uBAAuB,CAAC,SAAoB;AAChD,aAAK,KAAK;AAAA,UACR,MAAM;AAAA,UACN,OAAO;AAAA,UACP,MAAM;AAAA,UACN,MAAM,EAAE,MAAM,UAAU,KAAK,mBAAmB;AAAA,QAClD,CAAC;AAAA,MACH,CAAC;AAAA,IACL;AAAA,EACF;AACF,CAAC;;;AClOD,IAAO,gBAAQ;","names":[]}
@@ -0,0 +1,235 @@
1
+ <template>
2
+ <div class="noydb-panel">
3
+ <!-- no db bound -->
4
+ <div v-if="!db" class="noydb-panel__empty">
5
+ <p>No active noy-db instance.</p>
6
+ <code>Call setActiveNoydb(db) in your plugin.</code>
7
+ </div>
8
+ <!-- db bound but no vault open -->
9
+ <div v-else-if="initialized && !vaultInfo" class="noydb-panel__empty">
10
+ <p>No open vaults — unlock a vault in your app first.</p>
11
+ </div>
12
+ <!-- loading -->
13
+ <div v-else-if="!initialized" class="noydb-panel__empty">
14
+ <p>Loading…</p>
15
+ </div>
16
+ <!-- main panel -->
17
+ <template v-else>
18
+ <!-- top nav -->
19
+ <nav class="noydb-nav">
20
+ <span class="noydb-nav__logo">noy-db</span>
21
+ <button
22
+ :class="['noydb-nav__tab', { 'noydb-nav__tab--active': topTab === 'structure' }]"
23
+ @click="topTab = 'structure'"
24
+ >Structure</button>
25
+ <button
26
+ :class="['noydb-nav__tab', { 'noydb-nav__tab--active': topTab === 'monitor' }]"
27
+ @click="activateMonitor"
28
+ >Monitor</button>
29
+ <span class="noydb-nav__spacer" />
30
+ <span class="noydb-nav__status">
31
+ <span class="noydb-nav__dot" />{{ vaultInfo!.id }}
32
+ </span>
33
+ </nav>
34
+
35
+ <!-- structure tab -->
36
+ <div v-if="topTab === 'structure'" class="noydb-panel__body">
37
+ <VaultSidebar
38
+ :vault-name="vaultInfo!.id"
39
+ :vault-meta="snapshotMeta"
40
+ :collections="collections"
41
+ :selected-name="selectedCollection?.name ?? null"
42
+ @select="selectCollection"
43
+ />
44
+ <div class="noydb-panel__detail">
45
+ <div v-if="snapshotError" class="noydb-panel__error">{{ snapshotError }}</div>
46
+ <template v-else-if="selectedCollection">
47
+ <div class="noydb-detail-tabs">
48
+ <button
49
+ :class="['noydb-detail-tab', { 'noydb-detail-tab--active': detailTab === 'schema' }]"
50
+ @click="detailTab = 'schema'"
51
+ >Schema</button>
52
+ <button
53
+ :class="['noydb-detail-tab', { 'noydb-detail-tab--active': detailTab === 'records' }]"
54
+ @click="detailTab = 'records'"
55
+ >Records</button>
56
+ </div>
57
+ <SchemaPane v-if="detailTab === 'schema'" :collection="selectedCollection" />
58
+ <RecordsPane
59
+ v-else
60
+ :collection="selectedCollection"
61
+ :page="recordsPage"
62
+ :error="recordsError"
63
+ @prev="prevPage"
64
+ @next="nextPage"
65
+ />
66
+ </template>
67
+ <div v-else class="noydb-panel__empty">Select a collection</div>
68
+ </div>
69
+ </div>
70
+
71
+ <!-- monitor tab -->
72
+ <WriteMonitor v-else :rows="feed" :meter="meter" />
73
+ </template>
74
+ </div>
75
+ </template>
76
+
77
+ <script setup lang="ts">
78
+ import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
79
+ import { getActiveNoydb } from '@noy-db/in-pinia'
80
+ import { createInspector } from '@noy-db/in-devtools'
81
+ import type { Inspector, InspectorCollection, VaultInfo, RecordPage } from '@noy-db/in-devtools'
82
+ import type { Vault, VaultMeta } from '@noy-db/hub'
83
+ import type { MeterSnapshot } from '@noy-db/to-meter'
84
+ import VaultSidebar from './panes/VaultSidebar.vue'
85
+ import SchemaPane from './panes/SchemaPane.vue'
86
+ import RecordsPane from './panes/RecordsPane.vue'
87
+ import WriteMonitor from './panes/WriteMonitor.vue'
88
+ import type { FeedRow } from './panes/WriteMonitor.vue'
89
+
90
+ const PAGE = 20
91
+ const BUFFER = 200
92
+
93
+ // ── Inspector setup ─────────────────────────────────────────────
94
+ const db = ref(getActiveNoydb())
95
+ const inspector = computed<Inspector | null>(() =>
96
+ db.value ? createInspector(db.value) : null
97
+ )
98
+
99
+ // ── Vault / snapshot state ───────────────────────────────────────
100
+ const initialized = ref(false)
101
+ const vaults = ref<ReadonlyArray<VaultInfo>>([])
102
+ const vaultInfo = computed(() => vaults.value[0] ?? null)
103
+ const openVault = ref<Vault | null>(null)
104
+ const collections = ref<ReadonlyArray<InspectorCollection>>([])
105
+ const snapshotMeta = ref<VaultMeta | null>(null)
106
+ const snapshotError = ref<string | undefined>(undefined)
107
+ const selectedCollection = ref<InspectorCollection | null>(null)
108
+
109
+ // ── Detail tab state ─────────────────────────────────────────────
110
+ const topTab = ref<'structure' | 'monitor'>('structure')
111
+ const detailTab = ref<'schema' | 'records'>('schema')
112
+ const recordsOffset = ref(0)
113
+ const recordsPage = ref<RecordPage | null>(null)
114
+ const recordsError = ref<string | undefined>(undefined)
115
+
116
+ // ── Monitor state ────────────────────────────────────────────────
117
+ const feed = ref<ReadonlyArray<FeedRow>>([])
118
+ const meter = ref<MeterSnapshot | null>(null)
119
+ const conflictKeys = new Set<string>()
120
+ let offWrite: (() => void) | null = null
121
+ let offConflict: (() => void) | null = null
122
+ let meterInterval: ReturnType<typeof setInterval> | null = null
123
+ let monitorStarted = false
124
+
125
+ // ── Lifecycle ────────────────────────────────────────────────────
126
+ onMounted(async () => {
127
+ if (!inspector.value || !db.value) { initialized.value = true; return }
128
+ try {
129
+ vaults.value = await inspector.value.listVaults()
130
+ const info = vaults.value[0]
131
+ if (!info) { initialized.value = true; return }
132
+ openVault.value = await db.value.openVault(info.id)
133
+ await loadSnapshot()
134
+ } catch (e) {
135
+ snapshotError.value = e instanceof Error ? e.message : String(e)
136
+ } finally {
137
+ initialized.value = true
138
+ }
139
+ })
140
+
141
+ onUnmounted(() => {
142
+ offWrite?.()
143
+ offConflict?.()
144
+ if (meterInterval) clearInterval(meterInterval)
145
+ })
146
+
147
+ // ── Helpers ──────────────────────────────────────────────────────
148
+ async function loadSnapshot() {
149
+ if (!inspector.value || !openVault.value) return
150
+ try {
151
+ const snap = await inspector.value.snapshot(openVault.value)
152
+ collections.value = snap.collections
153
+ snapshotMeta.value = snap.meta ?? null
154
+ selectedCollection.value = snap.collections[0] ?? null
155
+ snapshotError.value = undefined
156
+ } catch (e) {
157
+ snapshotError.value = e instanceof Error ? e.message : String(e)
158
+ }
159
+ }
160
+
161
+ function selectCollection(coll: InspectorCollection) {
162
+ selectedCollection.value = coll
163
+ detailTab.value = 'schema'
164
+ recordsOffset.value = 0
165
+ recordsPage.value = null
166
+ recordsError.value = undefined
167
+ }
168
+
169
+ function prevPage() { if (recordsOffset.value >= PAGE) recordsOffset.value -= PAGE }
170
+ function nextPage() {
171
+ if (recordsPage.value && recordsOffset.value + PAGE < recordsPage.value.total)
172
+ recordsOffset.value += PAGE
173
+ }
174
+
175
+ function activateMonitor() {
176
+ topTab.value = 'monitor'
177
+ if (monitorStarted || !inspector.value) return
178
+ monitorStarted = true
179
+ offWrite = inspector.value.subscribe((e) => {
180
+ const op = e.op === 'delete' ? 'del' : ('put' as const)
181
+ const versions = e.op === 'delete' ? `${e.baseVersion}→·` : `${e.baseVersion}→${e.version}`
182
+ const baseKey = `${e.collection}/${e.docId}@${e.baseVersion}`
183
+ const row: FeedRow = {
184
+ time: new Date(e.timestamp).toTimeString().slice(0, 8),
185
+ user: e.userId,
186
+ op,
187
+ target: `${e.collection}/${e.docId}`,
188
+ versions,
189
+ baseKey,
190
+ conflict: conflictKeys.has(baseKey),
191
+ }
192
+ const prev = feed.value as FeedRow[]
193
+ if (prev.some((r) => r.baseKey === baseKey && r.user !== e.userId)) row.conflict = true
194
+ feed.value = [
195
+ row,
196
+ ...prev.map((r) => r.baseKey === baseKey && r.user !== e.userId ? { ...r, conflict: true } : r),
197
+ ].slice(0, BUFFER)
198
+ })
199
+ offConflict = inspector.value.subscribeConflicts((c) => {
200
+ const key = `${c.collection}/${c.docId}@${c.baseVersion}`
201
+ conflictKeys.add(key)
202
+ feed.value = (feed.value as FeedRow[]).map((r) => r.baseKey === key ? { ...r, conflict: true } : r)
203
+ })
204
+ }
205
+
206
+ // Latency poll — runs only while monitor tab is active
207
+ watch(topTab, (tab) => {
208
+ if (tab === 'monitor') {
209
+ meterInterval = setInterval(() => {
210
+ try { meter.value = inspector.value?.meterSnapshot() ?? null }
211
+ catch { meter.value = null }
212
+ }, 1000)
213
+ } else {
214
+ if (meterInterval) { clearInterval(meterInterval); meterInterval = null }
215
+ }
216
+ })
217
+
218
+ // Records fetch — re-runs when collection, tab, or offset changes
219
+ watch(
220
+ [selectedCollection, detailTab, recordsOffset],
221
+ async ([coll, tab]) => {
222
+ if (!inspector.value || !openVault.value || !coll || tab !== 'records') return
223
+ recordsPage.value = null
224
+ recordsError.value = undefined
225
+ try {
226
+ recordsPage.value = await inspector.value.records(
227
+ openVault.value, coll.name, { limit: PAGE, offset: recordsOffset.value }
228
+ )
229
+ } catch (e) {
230
+ recordsError.value = e instanceof Error ? e.message : String(e)
231
+ }
232
+ },
233
+ { immediate: false }
234
+ )
235
+ </script>
@@ -0,0 +1,103 @@
1
+ <template>
2
+ <div class="noydb-records">
3
+ <div class="noydb-records__header">
4
+ <template v-if="page">
5
+ rows
6
+ <strong>{{ page.total === 0 ? 0 : page.offset + 1 }}–{{ Math.min(page.offset + page.limit, page.total) }}</strong>
7
+ of <strong>{{ page.total }}</strong>
8
+ </template>
9
+ <span v-else-if="error" class="noydb-records__error">{{ error }}</span>
10
+ <span v-else class="noydb-records__loading">loading…</span>
11
+ <div class="noydb-records__nav">
12
+ <button :disabled="!canPrev" @click="$emit('prev')">◀ prev</button>
13
+ <button :disabled="!canNext" @click="$emit('next')">next ▶</button>
14
+ </div>
15
+ <button
16
+ v-if="hasSensitiveFields"
17
+ class="noydb-records__reveal-all"
18
+ data-reveal-all
19
+ @click="revealAll = !revealAll"
20
+ >{{ revealAll ? 'hide all' : 'reveal all' }}</button>
21
+ </div>
22
+ <template v-if="page && fields.length > 0">
23
+ <div class="noydb-records__cols">
24
+ <span v-for="f in fields" :key="f">{{ f }}</span>
25
+ </div>
26
+ <div v-if="page.rows.length === 0" class="noydb-records__empty">No records</div>
27
+ <div v-for="(row, i) in page.rows" :key="i" class="noydb-records__row">
28
+ <span v-for="f in fields" :key="f">
29
+ <template v-if="isVisible(f)">{{ cell(row, f) }}</template>
30
+ <button
31
+ v-else
32
+ class="noydb-records__mask"
33
+ :data-reveal="f"
34
+ @click="reveal(f)"
35
+ >••••</button>
36
+ </span>
37
+ </div>
38
+ </template>
39
+ </div>
40
+ </template>
41
+
42
+ <script setup lang="ts">
43
+ import { computed, ref, watch } from 'vue'
44
+ import type { InspectorCollection, RecordPage } from '@noy-db/in-devtools'
45
+
46
+ const props = defineProps<{
47
+ collection: InspectorCollection
48
+ page: RecordPage | null
49
+ error: string | undefined
50
+ }>()
51
+
52
+ defineEmits<{ prev: []; next: [] }>()
53
+
54
+ const fields = computed(() => Object.keys(props.collection.fields))
55
+
56
+ const canPrev = computed(() => !!props.page && props.page.offset > 0)
57
+ const canNext = computed(() => !!props.page && props.page.offset + props.page.limit < props.page.total)
58
+
59
+ // ── PII masking ────────────────────────────────────────────────────────────
60
+
61
+ /** Set of field keys that are sensitive (pii or secret) per collection.described. */
62
+ const sensitiveFields = computed((): Set<string> => {
63
+ const described = props.collection.described
64
+ if (!described) return new Set()
65
+ const s = new Set<string>()
66
+ for (const d of described) {
67
+ if (d.sensitivity !== undefined && d.sensitivity !== 'public') {
68
+ s.add(d.key)
69
+ }
70
+ }
71
+ return s
72
+ })
73
+
74
+ const hasSensitiveFields = computed(() => sensitiveFields.value.size > 0)
75
+
76
+ /** Tracks which individual fields the user has revealed. */
77
+ const revealed = ref(new Set<string>())
78
+
79
+ /** When true, all sensitive fields are shown. */
80
+ const revealAll = ref(false)
81
+
82
+ // Reset reveal state when the viewed collection changes (prevent PII from one
83
+ // collection lingering when the user navigates to another).
84
+ watch(() => props.collection, () => { revealed.value = new Set(); revealAll.value = false })
85
+
86
+ function isVisible(field: string): boolean {
87
+ if (!sensitiveFields.value.has(field)) return true
88
+ if (revealAll.value) return true
89
+ return revealed.value.has(field)
90
+ }
91
+
92
+ function reveal(field: string): void {
93
+ revealed.value = new Set([...revealed.value, field])
94
+ }
95
+
96
+ function cell(row: unknown, field: string): string {
97
+ if (row === null || row === undefined || typeof row !== 'object') return '·'
98
+ const val = (row as Record<string, unknown>)[field]
99
+ if (val === null || val === undefined) return '·'
100
+ if (typeof val === 'object') return Array.isArray(val) ? `[${(val as unknown[]).length}]` : '{…}'
101
+ return String(val)
102
+ }
103
+ </script>
@@ -0,0 +1,124 @@
1
+ <template>
2
+ <div class="noydb-schema">
3
+ <template v-if="collection">
4
+ <!-- Collection meta header -->
5
+ <div class="noydb-schema__meta-header">
6
+ <span class="noydb-schema__meta-label">{{ collectionLabel }}</span>
7
+ <span
8
+ v-if="collection.meta?.description"
9
+ class="noydb-schema__meta-desc"
10
+ :title="collection.meta.description"
11
+ >{{ collection.meta.description }}</span>
12
+ </div>
13
+
14
+ <!-- Config strip (badges for active config options) -->
15
+ <div v-if="configBadges.length" class="noydb-schema__config-strip">
16
+ <span
17
+ v-for="badge in configBadges"
18
+ :key="badge"
19
+ class="noydb-schema__config-badge"
20
+ >{{ badge }}</span>
21
+ </div>
22
+
23
+ <div class="noydb-schema__label noydb-schema__label--mt">Fields</div>
24
+
25
+ <!-- Rich rows from described (when present) -->
26
+ <template v-if="collection.described && collection.described.length">
27
+ <div
28
+ v-for="field in collection.described"
29
+ :key="field.key"
30
+ class="noydb-schema__row"
31
+ >
32
+ <span class="noydb-schema__name">{{ field.label }}</span>
33
+ <span class="noydb-schema__name noydb-schema__name--key" :title="field.key">{{ field.key }}</span>
34
+ <span class="noydb-schema__type">{{ field.type }}</span>
35
+ <span v-if="field.semanticType || field.widget" class="noydb-schema__badge noydb-schema__badge--semantic">
36
+ {{ field.semanticType ?? field.widget }}
37
+ </span>
38
+ <span v-if="field.money?.currency" class="noydb-schema__badge noydb-schema__badge--money">
39
+ {{ field.money.currency }}
40
+ </span>
41
+ <span v-if="field.dict" class="noydb-schema__badge noydb-schema__badge--dict">
42
+ dict{{ field.dict.values ? ` ×${field.dict.values.length}` : '' }}
43
+ </span>
44
+ <span v-if="field.sensitivity === 'pii'" class="noydb-schema__badge noydb-schema__badge--pii">pii</span>
45
+ <span v-if="field.sensitivity === 'secret'" class="noydb-schema__badge noydb-schema__badge--secret">secret</span>
46
+ <span v-if="field.i18n" class="noydb-schema__badge noydb-schema__badge--i18n">i18n</span>
47
+ <span v-if="field.ref" class="noydb-schema__badge noydb-schema__badge--ref" :title="`→ ${field.ref.target}`">
48
+ → {{ field.ref.target }}
49
+ </span>
50
+ <span v-if="!field.editable" class="noydb-schema__badge noydb-schema__badge--readonly">read-only</span>
51
+ <span class="noydb-schema__flag">{{ isIndexed(field.key) ? 'idx' : '' }}</span>
52
+ </div>
53
+ </template>
54
+
55
+ <!-- Fallback rows from fields (back-compat) -->
56
+ <template v-else>
57
+ <div
58
+ v-for="[name, field] in fieldEntries"
59
+ :key="name"
60
+ class="noydb-schema__row"
61
+ >
62
+ <span class="noydb-schema__name">{{ name }}</span>
63
+ <span class="noydb-schema__type">{{ (field as { type?: string }).type ?? '—' }}</span>
64
+ <span class="noydb-schema__flag">{{ isIndexed(name) ? 'idx' : '' }}</span>
65
+ </div>
66
+ </template>
67
+
68
+ <div class="noydb-schema__label noydb-schema__label--mt">Stats</div>
69
+ <div class="noydb-schema__stat">
70
+ docs <strong>{{ collection.stats?.count ?? '—' }}</strong>
71
+ · size <strong>{{ fmtBytes(collection.stats?.bytes) }}</strong>
72
+ · indexes <strong>{{ collection.indexes?.length ?? 0 }}</strong>
73
+ </div>
74
+ </template>
75
+ <div v-else class="noydb-schema__empty">Select a collection</div>
76
+ </div>
77
+ </template>
78
+
79
+ <script setup lang="ts">
80
+ import { computed } from 'vue'
81
+ import type { InspectorCollection } from '@noy-db/in-devtools'
82
+
83
+ const props = defineProps<{ collection: InspectorCollection | null }>()
84
+
85
+ const collectionLabel = computed(() =>
86
+ props.collection?.meta?.label ?? props.collection?.name ?? ''
87
+ )
88
+
89
+ const fieldEntries = computed(() =>
90
+ props.collection ? Object.entries(props.collection.fields) : []
91
+ )
92
+
93
+ /** Build small text badges for whichever config options are active. */
94
+ const configBadges = computed((): string[] => {
95
+ const cfg = props.collection?.config
96
+ if (!cfg) return []
97
+ const badges: string[] = []
98
+ if (cfg.embeddings) badges.push('embeddings')
99
+ if (cfg.textIndexes && cfg.textIndexes.length) badges.push('text-index')
100
+ if (cfg.crdt) badges.push(`crdt:${cfg.crdt}`)
101
+ if (cfg.provenance) badges.push('provenance')
102
+ if (cfg.archive) badges.push('archive')
103
+ if (cfg.tiers && cfg.tiers.length) badges.push(`tiers:${cfg.tiers.length}`)
104
+ if (cfg.perRecordKeys) badges.push('per-record-keys')
105
+ if (cfg.history) badges.push('history')
106
+ if (cfg.schemaUpdate && cfg.schemaUpdate.length) badges.push('schema-update')
107
+ return badges
108
+ })
109
+
110
+ function isIndexed(name: string): boolean {
111
+ return props.collection?.indexes?.some((idx) => {
112
+ const fields = (idx as { fields?: string | string[] }).fields
113
+ if (!fields) return false
114
+ return Array.isArray(fields) ? fields.includes(name) : fields === name
115
+ }) ?? false
116
+ }
117
+
118
+ function fmtBytes(n?: number): string {
119
+ if (n === undefined) return '—'
120
+ if (n < 1024) return `${n} B`
121
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`
122
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`
123
+ }
124
+ </script>
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <aside class="noydb-sidebar">
3
+ <div class="noydb-sidebar__header">Vault</div>
4
+ <template v-if="vaultName">
5
+ <div
6
+ class="noydb-sidebar__vault"
7
+ :title="vaultMeta?.description ?? vaultName ?? undefined"
8
+ >▸ {{ vaultMeta?.label ?? vaultName }}</div>
9
+ <button
10
+ v-for="coll in collections"
11
+ :key="coll.name"
12
+ :class="['noydb-sidebar__item', { 'noydb-sidebar__item--selected': coll.name === selectedName }]"
13
+ @click="$emit('select', coll)"
14
+ >
15
+ <span>{{ coll.meta?.label ?? coll.name }}</span>
16
+ <span v-if="coll.stats?.count" class="noydb-sidebar__badge">{{ coll.stats.count }}</span>
17
+ </button>
18
+ </template>
19
+ <div v-else class="noydb-sidebar__empty">—</div>
20
+ </aside>
21
+ </template>
22
+
23
+ <script setup lang="ts">
24
+ import type { InspectorCollection } from '@noy-db/in-devtools'
25
+ import type { VaultMeta } from '@noy-db/hub'
26
+
27
+ defineProps<{
28
+ vaultName: string | null
29
+ vaultMeta?: VaultMeta | null
30
+ collections: ReadonlyArray<InspectorCollection>
31
+ selectedName: string | null
32
+ }>()
33
+
34
+ defineEmits<{ select: [collection: InspectorCollection] }>()
35
+ </script>
@@ -0,0 +1,49 @@
1
+ <template>
2
+ <div class="noydb-monitor">
3
+ <div v-if="meter" class="noydb-monitor__latency">
4
+ put p50 {{ meter.byMethod?.['put']?.p50 ?? '—' }}ms
5
+ p99 {{ meter.byMethod?.['put']?.p99 ?? '—' }}ms
6
+ · del p50 {{ meter.byMethod?.['delete']?.p50 ?? '—' }}ms
7
+ <span v-if="meter.status === 'degraded'" class="noydb-monitor__degraded"> ⚠ degraded</span>
8
+ </div>
9
+ <div class="noydb-monitor__cols">
10
+ <span>time</span>
11
+ <span>user</span>
12
+ <span>op</span>
13
+ <span>target</span>
14
+ <span>ver</span>
15
+ </div>
16
+ <div v-if="rows.length === 0" class="noydb-monitor__empty">Waiting for writes…</div>
17
+ <div
18
+ v-for="(row, i) in rows"
19
+ :key="i"
20
+ :class="['noydb-monitor__row', { 'noydb-monitor__row--conflict': row.conflict }]"
21
+ >
22
+ <span>{{ row.time }}</span>
23
+ <span>{{ row.user }}</span>
24
+ <span>{{ row.op }}</span>
25
+ <span>{{ row.target }}</span>
26
+ <span>{{ row.versions }}</span>
27
+ <span v-if="row.conflict" class="noydb-monitor__badge">⚠ conflict</span>
28
+ </div>
29
+ </div>
30
+ </template>
31
+
32
+ <script setup lang="ts">
33
+ import type { MeterSnapshot } from '@noy-db/to-meter'
34
+
35
+ export interface FeedRow {
36
+ readonly time: string
37
+ readonly user: string
38
+ readonly op: 'put' | 'del'
39
+ readonly target: string
40
+ readonly versions: string
41
+ readonly baseKey: string
42
+ conflict: boolean
43
+ }
44
+
45
+ defineProps<{
46
+ rows: ReadonlyArray<FeedRow>
47
+ meter: MeterSnapshot | null
48
+ }>()
49
+ </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noy-db/in-nuxt",
3
- "version": "0.2.0-pre.3",
3
+ "version": "0.2.0-pre.31",
4
4
  "description": "Nuxt 4 module for noy-db — auto-imports, SSR-safe runtime plugin, and the @noy-db/in-pinia bridge",
5
5
  "license": "MIT",
6
6
  "author": "vLannaAi <vicio@lanna.ai>",
@@ -30,29 +30,36 @@
30
30
  "LICENSE"
31
31
  ],
32
32
  "engines": {
33
- "node": ">=20.0.0"
33
+ "node": ">=22.0.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "nuxt": "^4.0.0",
37
- "@noy-db/hub": "0.2.0-pre.3",
38
- "@noy-db/in-vue": "0.2.0-pre.3",
39
- "@noy-db/in-rest": "0.2.0-pre.3",
40
- "@noy-db/in-pinia": "0.2.0-pre.3"
37
+ "@noy-db/in-pinia": "0.2.0-pre.31",
38
+ "@noy-db/hub": "0.2.0-pre.31",
39
+ "@noy-db/in-vue": "0.2.0-pre.31",
40
+ "@noy-db/in-rest": "0.2.0-pre.31"
41
41
  },
42
42
  "peerDependenciesMeta": {
43
43
  "@noy-db/in-rest": {
44
44
  "optional": true
45
45
  }
46
46
  },
47
+ "dependencies": {
48
+ "@noy-db/in-devtools": "0.2.0-pre.31"
49
+ },
47
50
  "devDependencies": {
48
51
  "@nuxt/kit": "^4.4.2",
49
52
  "@nuxt/schema": "^4.4.2",
53
+ "@vitejs/plugin-vue": "^5.2.4",
54
+ "@vue/test-utils": "^2.4.6",
50
55
  "h3": "^1.13.0",
56
+ "happy-dom": "^17.4.4",
51
57
  "nuxt": "^4.4.2",
52
- "@noy-db/in-pinia": "0.2.0-pre.3",
53
- "@noy-db/in-rest": "0.2.0-pre.3",
54
- "@noy-db/hub": "0.2.0-pre.3",
55
- "@noy-db/in-vue": "0.2.0-pre.3"
58
+ "vue": "^3.5.32",
59
+ "@noy-db/hub": "0.2.0-pre.31",
60
+ "@noy-db/in-pinia": "0.2.0-pre.31",
61
+ "@noy-db/in-vue": "0.2.0-pre.31",
62
+ "@noy-db/in-rest": "0.2.0-pre.31"
56
63
  },
57
64
  "keywords": [
58
65
  "noy-db",