mikser-io-sdk-api 3.4.0 → 3.5.1

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
@@ -453,6 +453,8 @@ For framework integration, prefer `live()` below — it handles the list+watch c
453
453
 
454
454
  The higher-level real-time primitive. Calls `onChange(items)` with the initial snapshot, then again with the patched array on every create/update/delete event. Returns a dispose function.
455
455
 
456
+ The initial snapshot is the **complete** matching set — `live()` paginates to the end, it doesn't stop at the server's default page size. This keeps the snapshot consistent with the SSE stream that follows: `watch()` subscribes by filter with no limit, so a create/update/delete to *any* matching entity is delivered; if the snapshot were capped at page one, updates to entities past it would silently no-op (they aren't in `items`) and the view would drift. Pass `limit` (or `skip`) only when you want a bounded window — a "latest N" feed — and accept that updates outside that window won't be reflected.
457
+
456
458
  ```js
457
459
  const dispose = docs.live(
458
460
  { 'meta.published': true, type: 'document' },
@@ -476,7 +478,8 @@ dispose()
476
478
  Equivalent to:
477
479
 
478
480
  ```js
479
- // 1. await list({ filter, sort, fields, limit, skip })
481
+ // 1. paginate list({ filter, sort, fields }) to the end (or one bounded
482
+ // list() when limit/skip is set)
480
483
  // 2. onChange(items)
481
484
  // 3. for await (event of watch({ filter })) patch + onChange
482
485
  // 4. abort on dispose
package/index.d.ts CHANGED
@@ -302,31 +302,34 @@ export function createHrefIndex(
302
302
 
303
303
  export interface AssetRecord {
304
304
  url: string
305
- width?: number
306
- height?: number
307
- srcset?: string
308
- alt?: string
305
+ /** Raw entity meta block — opaque (mime, dimensions, duration, …). */
309
306
  meta?: Record<string, unknown>
310
307
  }
311
308
 
312
- export interface ImageProps {
313
- src: string
314
- width?: number
315
- height?: number
316
- srcset?: string
317
- alt?: string
318
- }
319
-
320
309
  export interface AssetIndex {
321
310
  asset(ref: string): AssetRecord | null
322
- image(ref: string): ImageProps | null
323
311
  map: Record<string, AssetRecord>
324
312
  }
325
313
 
314
+ export interface AssetUrlOptions {
315
+ /** Origin of the mikser server; omit for a root-relative URL. */
316
+ baseUrl?: string
317
+ /** Preset output format — replaces the source extension (.mp4 → .jpg). */
318
+ ext?: string
319
+ }
320
+
326
321
  /**
327
- * Build an asset metadata lookup from a snapshot of asset entities.
328
- * Pure data transformation — wrap in a framework-specific reactive
329
- * shell to drive `useAsset` composables.
322
+ * URL of a transcoded derivative, by the assets() plugin convention:
323
+ * `<baseUrl>/assets/<preset>/<source>`. Format-neutral (video, image,
324
+ * pdf, audio, …) mikser's assets() is a preset transcoder, not an
325
+ * image pipeline.
326
+ */
327
+ export function assetUrl(source: string, preset: string, options?: AssetUrlOptions): string
328
+
329
+ /**
330
+ * Build a format-neutral lookup (id → { url, meta }) from a snapshot of
331
+ * managed asset entities. Pure data transformation — wrap in a
332
+ * framework-specific reactive shell to drive `useAsset` composables.
330
333
  */
331
334
  export function createAssetIndex(
332
335
  assets: Array<{ id: string; meta?: Record<string, unknown> }>,
package/index.js CHANGED
@@ -65,4 +65,4 @@ export function createClient({
65
65
  export { MikserError }
66
66
  export { generateMikserRoutes } from './src/routes.js'
67
67
  export { createHrefIndex } from './src/href.js'
68
- export { createAssetIndex } from './src/asset.js'
68
+ export { createAssetIndex, assetUrl } from './src/asset.js'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikser-io-sdk-api",
3
- "version": "3.4.0",
3
+ "version": "3.5.1",
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",
package/src/asset.js CHANGED
@@ -1,65 +1,53 @@
1
- // Asset metadata index pure data version. Framework SDKs wrap this
2
- // in their own reactivity primitives and expose useAsset on top.
3
- //
4
- // When assets carry metadata the template needs (dimensions, srcset,
5
- // alt text), looking them up by reference is cleaner than re-fetching
6
- // per render. The convention is that asset entities have an `id`
7
- // (used as the reference key) plus a `meta` block with the metadata.
1
+ // Asset URLs + metadata — format-neutral. mikser's assets() plugin is a
2
+ // preset transcoder (video, image, pdf, audio, …), not an image pipeline,
3
+ // so neither is this: it models a (source, preset) → derivative-URL
4
+ // convention plus an opaque metadata lookup. Image-specific concerns
5
+ // (srcset, dimensions, <img> props) are a consumer concern build them
6
+ // on top of `meta` where you actually know an asset is an image.
7
+ import { joinUrl } from './url.js'
8
8
 
9
9
  /**
10
- * @param {Array<{id: string, meta?: object}>} assets Asset entities.
11
- * @returns {{
12
- * asset: (ref: string) => AssetRecord|null,
13
- * image: (ref: string) => ImageProps|null,
14
- * map: Record<string, AssetRecord>,
15
- * }}
10
+ * URL of a transcoded derivative, by the assets() plugin convention:
16
11
  *
17
- * @typedef {Object} AssetRecord
18
- * @property {string} url
19
- * @property {number|undefined} width
20
- * @property {number|undefined} height
21
- * @property {string|undefined} srcset
22
- * @property {string|undefined} alt
23
- * @property {object|undefined} meta The raw meta block, for downstream use.
12
+ * <baseUrl>/assets/<preset>/<source>
24
13
  *
25
- * @typedef {Object} ImageProps
26
- * @property {string} src
27
- * @property {number|undefined} width
28
- * @property {number|undefined} height
29
- * @property {string|undefined} srcset
30
- * @property {string|undefined} alt
14
+ * `source` is the source ref, e.g. `/media/bg/clip.mp4`. `ext`, when
15
+ * given, is the preset's output format and REPLACES the source extension
16
+ * (a poster preset turns .mp4 → .jpg); omit it to keep the source ext.
17
+ * `baseUrl` is optional — omit for a same-origin, root-relative URL.
18
+ *
19
+ * @param {string} source
20
+ * @param {string} preset
21
+ * @param {{ baseUrl?: string, ext?: string }} [options]
22
+ * @returns {string}
23
+ */
24
+ export function assetUrl(source, preset, { baseUrl = '', ext } = {}) {
25
+ if (!source || !preset) return ''
26
+ const file = ext ? source.replace(/\.[^./]+$/, `.${ext}`) : source
27
+ const path = `/assets/${preset}/${file.replace(/^\/+/, '')}`
28
+ return baseUrl ? joinUrl(baseUrl, path) : path
29
+ }
30
+
31
+ /**
32
+ * Format-neutral lookup for managed asset entities that carry their own
33
+ * URL/metadata. `asset(ref)` → `{ url, meta }` | null, keyed by entity
34
+ * `id`. `meta` is the entity's raw meta block, opaque — mime, dimensions,
35
+ * duration, whatever the preset emitted. No image semantics: a consumer
36
+ * that knows an asset is an image reads `meta.width`/`meta.srcset` itself.
37
+ *
38
+ * @param {Array<{id: string, meta?: object}>} assets
39
+ * @returns {{ asset: (ref: string) => ({url: string, meta?: object}|null), map: Record<string, {url: string, meta?: object}> }}
31
40
  */
32
41
  export function createAssetIndex(assets) {
33
42
  const map = {}
34
43
  if (Array.isArray(assets)) {
35
44
  for (const a of assets) {
36
45
  if (!a?.id) continue
37
- map[a.id] = {
38
- url: a.meta?.destination ?? a.meta?.url ?? a.id,
39
- width: a.meta?.width,
40
- height: a.meta?.height,
41
- srcset: a.meta?.srcset,
42
- alt: a.meta?.alt,
43
- meta: a.meta,
44
- }
46
+ map[a.id] = { url: a.meta?.destination ?? a.meta?.url ?? a.id, meta: a.meta }
45
47
  }
46
48
  }
47
-
48
- function asset(ref) {
49
- return map[ref] ?? null
49
+ return {
50
+ asset: (ref) => map[ref] ?? null,
51
+ map,
50
52
  }
51
-
52
- function image(ref) {
53
- const a = map[ref]
54
- if (!a) return null
55
- return {
56
- src: a.url,
57
- width: a.width,
58
- height: a.height,
59
- srcset: a.srcset,
60
- alt: a.alt,
61
- }
62
- }
63
-
64
- return { asset, image, map }
65
53
  }
package/src/entities.js CHANGED
@@ -556,19 +556,45 @@ export function createEntitiesClient({ baseUrl, basePath, fetch: doFetch, header
556
556
  }
557
557
 
558
558
  if (!usedFastPath) {
559
- // Pass { quiet } so list()'s wide-warning honors
560
- // the live() caller's quiet opt.
559
+ // The initial snapshot must be the COMPLETE matching
560
+ // set, not a single server page. watch() below
561
+ // subscribes by filter with no limit, so create /
562
+ // update / delete events arrive for ANY matching
563
+ // entity. If the initial load stopped at the server's
564
+ // default page size, an update to an entity past page 1
565
+ // would silently no-op (it isn't in `items`) and a
566
+ // create could append out of sort order — the live view
567
+ // would drift out of sync with its own change stream.
568
+ // So paginate to completion, unless the caller opted
569
+ // into a bounded window via `limit` or `skip`.
561
570
  //
562
- // `expand` flows through to the initial snapshot
563
- // call AND to watch() below, so the server's
564
- // graph-subscription path (runtime.refs) emits
565
- // already-expanded entities on every mutation
566
- // within the expansion graph. Update events
567
- // replace items in place with the new expanded
568
- // shape, keeping the consumer's view consistent.
569
- const env = await list({ filter, sort, fields, limit, skip, expand }, { quiet })
570
- if (disposed || ac.signal.aborted) return
571
- items = env.items
571
+ // Pass { quiet } so list()'s wide-warning honors the
572
+ // live() caller's quiet opt.
573
+ //
574
+ // `expand` flows through to the initial snapshot call
575
+ // AND to watch() below, so the server's graph-
576
+ // subscription path (runtime.refs) emits already-
577
+ // expanded entities on every mutation within the
578
+ // expansion graph. Update events replace items in place
579
+ // with the new expanded shape, keeping the consumer's
580
+ // view consistent.
581
+ const bounded = limit != null || skip != null
582
+ if (bounded) {
583
+ const env = await list({ filter, sort, fields, limit, skip, expand }, { quiet })
584
+ if (disposed || ac.signal.aborted) return
585
+ items = env.items
586
+ } else {
587
+ const collected = []
588
+ let page = 1
589
+ while (true) {
590
+ const env = await list({ filter, sort, fields, expand, limit: 1000, page }, { quiet })
591
+ if (disposed || ac.signal.aborted) return
592
+ collected.push(...env.items)
593
+ if (!env.hasNext) break
594
+ page = env.page + 1
595
+ }
596
+ items = collected
597
+ }
572
598
  onChange(items)
573
599
  }
574
600
 
@@ -776,7 +802,7 @@ export function createEntitiesClient({ baseUrl, basePath, fetch: doFetch, header
776
802
  return api
777
803
  }
778
804
 
779
- return { list, listAll, urlFor, cacheKeyFor, pages, paginator, watch, live, update, delete: remove, render }
805
+ return { list, listAll, urlFor, cacheKeyFor, pages, paginator, watch, live, update, delete: remove, render, baseUrl }
780
806
  }
781
807
  }
782
808