mikser-io-sdk-api 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,14 @@ For semantic search against the `vector` plugin, install [mikser-io-sdk-vector](
8
8
 
9
9
  Zero dependencies. Runs anywhere `fetch` is available (modern browsers, Node 18+, Deno, Bun, Workers).
10
10
 
11
+ > **Using Vue, React, or Svelte?** You probably want one of the framework SDKs — they wrap this package in framework-idiomatic primitives (`useDocument` / `useDocuments`, multilingual `useHref`, live SSE-driven updates) so you don't write a watch loop or lifecycle plumbing yourself:
12
+ >
13
+ > - [`mikser-io-sdk-vue`](https://github.com/almero-digital-marketing/mikser-io-sdk-vue) — Vue 3 composables + vue-router integration
14
+ > - [`mikser-io-sdk-react`](https://github.com/almero-digital-marketing/mikser-io-sdk-react) — React 18+ / 19+ hooks + React Router v6+
15
+ > - [`mikser-io-sdk-svelte`](https://github.com/almero-digital-marketing/mikser-io-sdk-svelte) — Svelte 5 (runes) + SvelteKit
16
+ >
17
+ > Use **this** package directly when you're writing a custom adapter for another framework (Solid, Qwik, vanilla JS, server-side Node) or when you need the lower-level surface (`list`, `urlFor`, `watch`, `render`).
18
+
11
19
  ## Install
12
20
 
13
21
  ```bash
@@ -206,9 +214,9 @@ Equivalent to:
206
214
  // 4. abort on dispose
207
215
  ```
208
216
 
209
- …but with race-safe cleanup (no `mounted` flag needed in caller code), unified error routing via `onError`, and a single dispose path. This is the building block the framework adapters in [**Recipes**](#recipes--composing-real-time-and-search) use.
217
+ …but with race-safe cleanup (no `mounted` flag needed in caller code), unified error routing via `onError`, and a single dispose path. This is the building block the [framework SDKs](#framework-integration) (Vue / React / Svelte) consume internally — and the surface to use directly if you're writing a custom adapter.
210
218
 
211
- `live()` keeps an internal `items` array, patches it on each event, and hands the whole array to `onChange` every time. That's the simplest contract for React-style frameworks (the callback can replace state). If you need per-event deltas — animated reveals, audit logs, derived counters — use `watch()` directly.
219
+ `live()` keeps an internal `items` array, patches it on each event, and hands the whole array to `onChange` every time. That's the simplest contract for frameworks with state-replace semantics (the callback just overwrites state). If you need per-event deltas — animated reveals, audit logs, derived counters — use `watch()` directly.
212
220
 
213
221
  ### `update(payload)` / `delete(payload)` — writes
214
222
 
@@ -443,142 +451,32 @@ Two SDKs, one mental model, one server. The vector store gives you ranked semant
443
451
 
444
452
  ### Framework integration
445
453
 
446
- All the boilerplate (initial fetch, watch loop, race-safe cleanup) lives inside `docs.live()`. The framework adapters are ~5 lines each they just give the SDK a callback and call dispose on unmount.
454
+ All the boilerplate (initial fetch, watch loop, race-safe cleanup) lives inside `live()`. A framework adapter is ~5 lines — it gives the SDK a callback and calls dispose on unmount. For the three major frameworks, those adapters are already published:
447
455
 
448
- The shared module used by every variant below — wires the client once:
456
+ | Framework | Package | Primitive |
457
+ |---|---|---|
458
+ | Vue 3 | [`mikser-io-sdk-vue`](https://github.com/almero-digital-marketing/mikser-io-sdk-vue) | `useDocument(id)` / `useDocuments(query)` returning Vue refs; vue-router integration via `createMikserRouter` |
459
+ | React 18+ / 19+ | [`mikser-io-sdk-react`](https://github.com/almero-digital-marketing/mikser-io-sdk-react) | `useDocument(id)` / `useDocuments(query)` hooks; React Router integration via `useMikserRoutes` → `useRoutes` |
460
+ | Svelte 5 | [`mikser-io-sdk-svelte`](https://github.com/almero-digital-marketing/mikser-io-sdk-svelte) | `useDocument(() => id)` / `useDocuments(() => query)` runes-backed reactives; SvelteKit `entries()` integration via `generateMikserRoutes` |
449
461
 
450
- ```js
451
- // mikser.js — single source of truth for the configured client
452
- import { createClient } from 'mikser-io-sdk-api'
453
- export const docs = createClient({ baseUrl: 'https://cms.example.com' })
454
- .entities('public')
455
- ```
462
+ All three share the same conceptual surface — single-document subscription, list subscription, multilingual `useHref` / `useAlternates`, asset resolution via `useAsset` — wrapped in each framework's idiomatic shape. They all peer-depend on this package and consume `live()` internally; nothing about their behaviour is duplicated logic. If you have one of those three frameworks, prefer the matching SDK over hand-rolling against `live()`.
456
463
 
457
- #### React (hook)
464
+ The shape adapts to **any** framework with a setup-and-cleanup lifecycle — Solid (`createSignal` + `onCleanup`), Qwik (`useTask$`), Lit, or vanilla JS. For those, the adapter pattern is the same five lines: instantiate state, call `live(filter, setState)`, store the returned `dispose`, call it on teardown.
458
465
 
459
466
  ```js
460
- // useLiveEntities.js
461
- import { useEffect, useState } from 'react'
462
- import { docs } from './mikser'
463
-
464
- export function useLiveEntities(filter, options) {
465
- const [items, setItems] = useState([])
466
- useEffect(
467
- () => docs.live(filter, setItems, options), // returns dispose
468
- [JSON.stringify(filter)],
469
- )
470
- return items
471
- }
472
- ```
467
+ // vanilla adapter shape — works in any environment
468
+ const documents = createClient({ baseUrl }).entities('public')
473
469
 
474
- ```jsx
475
- // ArticleList.jsx
476
- import { useLiveEntities } from './useLiveEntities'
477
-
478
- export function ArticleList() {
479
- const articles = useLiveEntities(
480
- { type: 'document', 'meta.collection': 'articles', 'meta.published': true },
481
- { sort: { 'meta.date': -1 }, limit: 20 },
482
- )
483
- return (
484
- <ul>
485
- {articles.map(a => <li key={a.id}>{a.meta.title}</li>)}
486
- </ul>
487
- )
488
- }
489
- ```
490
-
491
- #### Vue 3 (composable, Composition API)
492
-
493
- ```js
494
- // useLiveEntities.js
495
- import { ref, onMounted, onUnmounted } from 'vue'
496
- import { docs } from './mikser'
497
-
498
- export function useLiveEntities(filter, options) {
499
- const items = ref([])
500
- let dispose
501
- onMounted (() => { dispose = docs.live(filter, v => items.value = v, options) })
502
- onUnmounted(() => dispose?.())
503
- return { items }
504
- }
505
- ```
506
-
507
- ```vue
508
- <!-- ArticleList.vue -->
509
- <script setup>
510
- import { useLiveEntities } from './useLiveEntities'
511
-
512
- const { items: articles } = useLiveEntities(
513
- { type: 'document', 'meta.collection': 'articles', 'meta.published': true },
470
+ const dispose = documents.live(
471
+ { 'meta.published': true },
472
+ (items) => render(items), // your update callback
514
473
  { sort: { 'meta.date': -1 }, limit: 20 },
515
474
  )
516
- </script>
517
475
 
518
- <template>
519
- <ul>
520
- <li v-for="a in articles" :key="a.id">{{ a.meta.title }}</li>
521
- </ul>
522
- </template>
523
- ```
524
-
525
- #### Svelte (writable store — works in Svelte 3, 4, and 5)
526
-
527
- Svelte's `writable(initial, start)` pattern is a perfect fit: `start` runs when the store gains its first subscriber and the returned `stop` runs when the last one disappears. The store lifecycle and `live()`'s dispose function line up exactly.
528
-
529
- ```js
530
- // liveEntities.js
531
- import { writable } from 'svelte/store'
532
- import { docs } from './mikser'
533
-
534
- export function liveEntities(filter, options) {
535
- return writable([], (set) => docs.live(filter, set, options))
536
- }
537
- ```
538
-
539
- ```svelte
540
- <!-- ArticleList.svelte -->
541
- <script>
542
- import { liveEntities } from './liveEntities'
543
-
544
- const articles = liveEntities(
545
- { type: 'document', 'meta.collection': 'articles', 'meta.published': true },
546
- { sort: { 'meta.date': -1 }, limit: 20 },
547
- )
548
- </script>
549
-
550
- <ul>
551
- {#each $articles as a (a.id)}
552
- <li>{a.meta.title}</li>
553
- {/each}
554
- </ul>
555
- ```
556
-
557
- If you're on Svelte 5 and prefer runes over stores:
558
-
559
- ```svelte
560
- <!-- ArticleList.svelte (Svelte 5 runes) -->
561
- <script>
562
- import { onMount } from 'svelte'
563
- import { docs } from './mikser'
564
-
565
- let articles = $state([])
566
- const filter = { type: 'document', 'meta.collection': 'articles', 'meta.published': true }
567
-
568
- onMount(() => docs.live(filter, v => articles = v, {
569
- sort: { 'meta.date': -1 }, limit: 20,
570
- }))
571
- </script>
572
-
573
- <ul>
574
- {#each articles as a (a.id)}
575
- <li>{a.meta.title}</li>
576
- {/each}
577
- </ul>
476
+ // later, on teardown
477
+ dispose()
578
478
  ```
579
479
 
580
- The same shape adapts to Solid (`createSignal` + `onCleanup`), Qwik (`useTask$`), or vanilla JS — anywhere with a setup-and-cleanup lifecycle. The SDK doesn't care.
581
-
582
480
  ## Configure
583
481
 
584
482
  ```js
package/index.d.ts CHANGED
@@ -115,6 +115,13 @@ export interface EntitiesClient {
115
115
  urlFor(query?: ListQuery): string
116
116
  /** Iterate result pages — yields each envelope until hasNext is false. */
117
117
  pages<T = unknown>(query?: ListQuery): AsyncGenerator<ListEnvelope<T>>
118
+ /**
119
+ * One-shot: fetch every matching entity into a flat array.
120
+ * Auto-paginates internally; `limit` controls per-page batch size
121
+ * (default 1000), not total cap. Right for SSG enumeration; wrong
122
+ * for catalogs too large to hold in memory.
123
+ */
124
+ listAll<T = unknown>(query?: ListQuery): Promise<T[]>
118
125
  /**
119
126
  * Open an SSE stream and yield events as matching entities change.
120
127
  * Compose with list() for initial state, then watch() for updates.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikser-io-sdk-api",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Client SDK for mikser-io's api plugin — query the document catalog from the browser or Node",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -18,7 +18,13 @@
18
18
  "README.md",
19
19
  "LICENSE"
20
20
  ],
21
- "scripts": {},
21
+ "scripts": {
22
+ "test": "vitest",
23
+ "test:run": "vitest run"
24
+ },
25
+ "devDependencies": {
26
+ "vitest": "^2.0.0"
27
+ },
22
28
  "repository": {
23
29
  "type": "git",
24
30
  "url": "git+https://github.com/almero-digital-marketing/mikser-io-sdk-api.git"
package/src/entities.js CHANGED
@@ -64,6 +64,27 @@ export function createEntitiesClient({ baseUrl, basePath, fetch: doFetch, header
64
64
  }
65
65
  }
66
66
 
67
+ /**
68
+ * One-shot: fetch every entity matching the filter into a flat
69
+ * array. Auto-paginates internally via pages() — `limit` in the
70
+ * query controls the per-page batch size (default 1000), not the
71
+ * total cap.
72
+ *
73
+ * Right when: SSG route enumeration, sitemap generation, build-
74
+ * time indexing — anything that needs the whole filtered set in
75
+ * memory at once.
76
+ *
77
+ * Wrong when: the catalog is large enough that loading all of it
78
+ * is wasteful. Use pages() directly and stream-process there.
79
+ */
80
+ async function listAll(query = {}) {
81
+ const items = []
82
+ for await (const env of pages({ limit: 1000, ...query })) {
83
+ items.push(...env.items)
84
+ }
85
+ return items
86
+ }
87
+
67
88
  /**
68
89
  * Subscribe to changes — opens an SSE stream and yields events
69
90
  * for each matching entity change (CREATE / UPDATE / DELETE).
@@ -264,6 +285,6 @@ export function createEntitiesClient({ baseUrl, basePath, fetch: doFetch, header
264
285
  return res.arrayBuffer()
265
286
  }
266
287
 
267
- return { list, urlFor, pages, watch, live, update, delete: remove, render }
288
+ return { list, listAll, urlFor, pages, watch, live, update, delete: remove, render }
268
289
  }
269
290
  }