mikser-io-sdk-api 3.3.0 → 3.5.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
@@ -305,9 +305,9 @@ const { items } = await docs.list({
305
305
 
306
306
  | Cap | Default | Configured at | What triggers it |
307
307
  |---|---|---|---|
308
- | `maxDepth` | 5 | `api.expand.maxDepth` | One path is longer than this (`a.b.c.d.e.f` at default) |
309
- | `maxPaths` | 20 | `api.expand.maxPaths` | The `expand` array has more entries than this |
310
- | `maxResolved` | 100 | `api.expand.maxResolved` | Total entity lookups for the request (across all paths) exceeded |
308
+ | `maxDepth` | 5 | `catalog.expand.maxDepth` | One path is longer than this (`a.b.c.d.e.f` at default) |
309
+ | `maxPaths` | 20 | `catalog.expand.maxPaths` | The `expand` array has more entries than this |
310
+ | `maxResolved` | 100 | `catalog.expand.maxResolved` | Total entity lookups for the request (across all paths) exceeded |
311
311
 
312
312
  ```js
313
313
  try {
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.3.0",
3
+ "version": "3.5.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",
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
@@ -51,7 +51,7 @@ const GET_MAX_URL = 1800
51
51
  // console). Server-side has a matching warning that fires for all
52
52
  // clients regardless of SDK use.
53
53
  const WIDE_RESPONSE_ITEMS = 50
54
- const _warnedShapes = new Set()
54
+ const warnedShapes = new Set()
55
55
 
56
56
  function isProductionEnv() {
57
57
  try {
@@ -74,8 +74,8 @@ function maybeWarnWide({ endpoint, query, envelopeOrItems, quiet }) {
74
74
  const hasFields = Array.isArray(query?.fields) && query.fields.length > 0
75
75
  if (hasFields) return
76
76
  const shape = `${endpoint}|${JSON.stringify(query?.filter ?? null)}|${JSON.stringify(query?.sort ?? null)}`
77
- if (_warnedShapes.has(shape)) return
78
- _warnedShapes.add(shape)
77
+ if (warnedShapes.has(shape)) return
78
+ warnedShapes.add(shape)
79
79
  let sizeNote = ''
80
80
  try {
81
81
  const bytes = JSON.stringify(items).length
@@ -100,7 +100,7 @@ function maybeWarnWide({ endpoint, query, envelopeOrItems, quiet }) {
100
100
  // calls without noticing the snapshot is no longer involved. Deduped
101
101
  // per (endpoint, kind, what-was-set) so a page with 3 filtered calls
102
102
  // produces 3 warnings, not 30.
103
- const _bypassedShapes = new Set()
103
+ const bypassedShapes = new Set()
104
104
  function maybeWarnSnapshotBypass({ endpoint, kind, filter, sort, skip, quiet }) {
105
105
  if (quiet || isProductionEnv() || isQuiet()) return
106
106
  const reasons = []
@@ -110,8 +110,8 @@ function maybeWarnSnapshotBypass({ endpoint, kind, filter, sort, skip, quiet })
110
110
  if (reasons.length === 0) return
111
111
  const reasonLabel = reasons.join('+')
112
112
  const shape = `${endpoint}|${kind}|${reasonLabel}`
113
- if (_bypassedShapes.has(shape)) return
114
- _bypassedShapes.add(shape)
113
+ if (bypassedShapes.has(shape)) return
114
+ bypassedShapes.add(shape)
115
115
  const fallback = kind === 'live' ? 'live list()' : 'paginated fetch'
116
116
  console.warn(
117
117
  `[mikser-sdk] data.catalog is set on "${endpoint}" but this ${kind}() call uses ${reasonLabel} — snapshot bypassed, falling back to ${fallback}.\n` +
@@ -776,7 +776,7 @@ export function createEntitiesClient({ baseUrl, basePath, fetch: doFetch, header
776
776
  return api
777
777
  }
778
778
 
779
- return { list, listAll, urlFor, cacheKeyFor, pages, paginator, watch, live, update, delete: remove, render }
779
+ return { list, listAll, urlFor, cacheKeyFor, pages, paginator, watch, live, update, delete: remove, render, baseUrl }
780
780
  }
781
781
  }
782
782
 
package/src/href.js CHANGED
@@ -22,20 +22,31 @@
22
22
  * @returns {{
23
23
  * href: (ref: string, lang?: string) => string,
24
24
  * refFor: (url: string|null) => string|null,
25
+ * docFor: (ref: string, lang?: string) => object|null,
26
+ * metaFor: (ref: string, lang?: string) => object|null,
25
27
  * alternates: (opts: { route: string|null, languages?: string[] }) => { current: {lang, url, ref}|null, alternates: Array<{lang, url}> },
26
28
  * map: Record<string, Record<string, string>>,
27
29
  * }}
28
30
  */
29
31
  export function createHrefIndex(documents, { defaultLang = 'default' } = {}) {
30
32
  const map = {}
33
+ // ref → lang → document. The index already iterates every document
34
+ // with its full meta to build `map`; keeping the document here too
35
+ // turns the href index into a content index — `metaFor('/menu')`
36
+ // reads a known document by its logical reference, the companion to
37
+ // `href('/menu')` resolving the URL. Costs one extra object slot per
38
+ // document; the data was already in hand.
39
+ const docs = {}
31
40
  if (Array.isArray(documents)) {
32
41
  for (const document of documents) {
33
42
  const ref = document?.meta?.href
34
43
  if (!ref) continue
35
44
  const lang = document.meta?.lang ?? defaultLang
36
45
  const url = document.meta?.route ?? document.meta?.destination ?? ref
37
- if (!map[ref]) map[ref] = {}
38
- map[ref][lang] = url
46
+ if (!map[ref]) map[ref] = {}
47
+ if (!docs[ref]) docs[ref] = {}
48
+ map[ref][lang] = url
49
+ docs[ref][lang] = document
39
50
  }
40
51
  }
41
52
 
@@ -56,6 +67,32 @@ export function createHrefIndex(documents, { defaultLang = 'default' } = {}) {
56
67
  ?? ref
57
68
  }
58
69
 
70
+ /**
71
+ * Resolve a logical reference to its document — the content
72
+ * companion to href(). Same lang-fallback chain (requested lang →
73
+ * 'default' → any available). Returns null when the ref isn't in
74
+ * the index (a missing document can't be faked the way a missing
75
+ * URL falls back to the ref string).
76
+ */
77
+ function docFor(ref, lang) {
78
+ const target = lang ?? defaultLang
79
+ const entry = docs[ref]
80
+ if (!entry) return null
81
+ return entry[target]
82
+ ?? entry['default']
83
+ ?? Object.values(entry)[0]
84
+ ?? null
85
+ }
86
+
87
+ /**
88
+ * The meta of the document a logical reference resolves to — the
89
+ * 90% case (read fields off known content by its ref). Shorthand
90
+ * for `docFor(ref, lang)?.meta`.
91
+ */
92
+ function metaFor(ref, lang) {
93
+ return docFor(ref, lang)?.meta ?? null
94
+ }
95
+
59
96
  /**
60
97
  * Reverse lookup — given a deployed URL, return the logical
61
98
  * reference it belongs to (or null if it's not in the index).
@@ -106,5 +143,5 @@ export function createHrefIndex(documents, { defaultLang = 'default' } = {}) {
106
143
  return { current, alternates: list }
107
144
  }
108
145
 
109
- return { href, refFor, alternates, map }
146
+ return { href, refFor, docFor, metaFor, alternates, map }
110
147
  }