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 +4 -1
- package/index.d.ts +19 -16
- package/index.js +1 -1
- package/package.json +1 -1
- package/src/asset.js +39 -51
- package/src/entities.js +39 -13
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.
|
|
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
|
-
|
|
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
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
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 }
|
|
68
|
+
export { createAssetIndex, assetUrl } from './src/asset.js'
|
package/package.json
CHANGED
package/src/asset.js
CHANGED
|
@@ -1,65 +1,53 @@
|
|
|
1
|
-
// Asset metadata
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* @
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
//
|
|
560
|
-
//
|
|
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
|
-
//
|
|
563
|
-
//
|
|
564
|
-
//
|
|
565
|
-
//
|
|
566
|
-
//
|
|
567
|
-
//
|
|
568
|
-
//
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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
|
|