mikser-io-sdk-api 3.0.0 → 3.1.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/index.d.ts +83 -0
- package/index.js +6 -0
- package/package.json +1 -1
- package/src/asset.js +65 -0
- package/src/href.js +110 -0
- package/src/routes.js +34 -0
package/index.d.ts
CHANGED
|
@@ -187,3 +187,86 @@ export declare class MikserError extends Error {
|
|
|
187
187
|
status: number
|
|
188
188
|
body: { error?: string } | undefined
|
|
189
189
|
}
|
|
190
|
+
|
|
191
|
+
// ────────────────────────────────────────────────────────────────────
|
|
192
|
+
// Pure utilities — framework SDKs wrap these in their own reactivity.
|
|
193
|
+
// ────────────────────────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
export interface GenerateRoutesOptions<TRoute = unknown> {
|
|
196
|
+
/** A mikser entities client (the result of createClient(...).entities(name)). */
|
|
197
|
+
client: { listAll(query: ListQuery): Promise<Array<{ id: string; meta?: Record<string, unknown> }>> }
|
|
198
|
+
/** Sift filter — defaults to "published documents that declare meta.route". */
|
|
199
|
+
filter?: Filter
|
|
200
|
+
/** Maps a catalog document to a route descriptor (or null to skip). */
|
|
201
|
+
mapRoute: (document: { id: string; meta?: Record<string, unknown> }) => TRoute | null
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Build-time route enumeration. Auto-paginates via listAll() and
|
|
206
|
+
* applies `mapRoute` to every catalog entity that matches the filter.
|
|
207
|
+
* `null` returns from `mapRoute` are dropped.
|
|
208
|
+
*/
|
|
209
|
+
export function generateMikserRoutes<TRoute = unknown>(
|
|
210
|
+
options: GenerateRoutesOptions<TRoute>,
|
|
211
|
+
): Promise<TRoute[]>
|
|
212
|
+
|
|
213
|
+
export interface HrefIndexOptions {
|
|
214
|
+
/** Fallback language tag for documents that don't declare meta.lang. Default 'default'. */
|
|
215
|
+
defaultLang?: string
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface HrefIndex {
|
|
219
|
+
/** Resolve a logical reference (`/about`) to a deployed URL for the given language. */
|
|
220
|
+
href(ref: string, lang?: string): string
|
|
221
|
+
/** Reverse — given a deployed URL, return the logical reference it belongs to. */
|
|
222
|
+
refFor(url: string | null): string | null
|
|
223
|
+
/** Alternates for a deployed URL — `current` plus the alternate-language URLs. */
|
|
224
|
+
alternates(options: { route: string | null; languages?: string[] }): {
|
|
225
|
+
current: { lang: string | null; url: string; ref: string } | null
|
|
226
|
+
alternates: Array<{ lang: string; url: string }>
|
|
227
|
+
}
|
|
228
|
+
/** Raw `ref → { lang → url }` map, for inspection / debugging. */
|
|
229
|
+
map: Record<string, Record<string, string>>
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Build a multilingual href lookup from a snapshot of catalog documents.
|
|
234
|
+
* Pure data transformation — wrap in a framework-specific reactive
|
|
235
|
+
* shell to drive `useHref` / `useAlternates` composables.
|
|
236
|
+
*/
|
|
237
|
+
export function createHrefIndex(
|
|
238
|
+
documents: Array<{ meta?: Record<string, unknown> }>,
|
|
239
|
+
options?: HrefIndexOptions,
|
|
240
|
+
): HrefIndex
|
|
241
|
+
|
|
242
|
+
export interface AssetRecord {
|
|
243
|
+
url: string
|
|
244
|
+
width?: number
|
|
245
|
+
height?: number
|
|
246
|
+
srcset?: string
|
|
247
|
+
alt?: string
|
|
248
|
+
meta?: Record<string, unknown>
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface ImageProps {
|
|
252
|
+
src: string
|
|
253
|
+
width?: number
|
|
254
|
+
height?: number
|
|
255
|
+
srcset?: string
|
|
256
|
+
alt?: string
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export interface AssetIndex {
|
|
260
|
+
asset(ref: string): AssetRecord | null
|
|
261
|
+
image(ref: string): ImageProps | null
|
|
262
|
+
map: Record<string, AssetRecord>
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Build an asset metadata lookup from a snapshot of asset entities.
|
|
267
|
+
* Pure data transformation — wrap in a framework-specific reactive
|
|
268
|
+
* shell to drive `useAsset` composables.
|
|
269
|
+
*/
|
|
270
|
+
export function createAssetIndex(
|
|
271
|
+
assets: Array<{ id: string; meta?: Record<string, unknown> }>,
|
|
272
|
+
): AssetIndex
|
package/index.js
CHANGED
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
// src/url.js — URL building (joinUrl, sortToParam, filterToParams)
|
|
28
28
|
// src/sse.js — SSE event parser
|
|
29
29
|
// src/entities.js — per-endpoint entities client (list / watch / live / ...)
|
|
30
|
+
// src/routes.js — generateMikserRoutes (build-time route enumeration)
|
|
31
|
+
// src/href.js — createHrefIndex (multilingual reference → URL lookup)
|
|
32
|
+
// src/asset.js — createAssetIndex (asset metadata lookup)
|
|
30
33
|
import { MikserError } from './src/error.js'
|
|
31
34
|
import { createEntitiesClient } from './src/entities.js'
|
|
32
35
|
|
|
@@ -60,3 +63,6 @@ export function createClient({
|
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
export { MikserError }
|
|
66
|
+
export { generateMikserRoutes } from './src/routes.js'
|
|
67
|
+
export { createHrefIndex } from './src/href.js'
|
|
68
|
+
export { createAssetIndex } from './src/asset.js'
|
package/package.json
CHANGED
package/src/asset.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
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.
|
|
8
|
+
|
|
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
|
+
* }}
|
|
16
|
+
*
|
|
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.
|
|
24
|
+
*
|
|
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
|
|
31
|
+
*/
|
|
32
|
+
export function createAssetIndex(assets) {
|
|
33
|
+
const map = {}
|
|
34
|
+
if (Array.isArray(assets)) {
|
|
35
|
+
for (const a of assets) {
|
|
36
|
+
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
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function asset(ref) {
|
|
49
|
+
return map[ref] ?? null
|
|
50
|
+
}
|
|
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
|
+
}
|
package/src/href.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Multilingual href resolution — pure data version. Framework SDKs
|
|
2
|
+
// (Vue/React/Svelte) wrap this in their own reactivity primitives and
|
|
3
|
+
// expose useHref / useAlternates composables on top.
|
|
4
|
+
//
|
|
5
|
+
// The "logical reference → deployed URL" mapping lets the consumer
|
|
6
|
+
// link to a content-shaped identifier (`/about`) and have it resolve
|
|
7
|
+
// to whatever URL the current locale serves it at (`/en/about`,
|
|
8
|
+
// `/fr/a-propos`, etc.). Convention is three meta fields:
|
|
9
|
+
//
|
|
10
|
+
// meta.href: '/about' (logical reference)
|
|
11
|
+
// meta.lang: 'en' (language this doc represents)
|
|
12
|
+
// meta.route: '/en/about' (deployed URL)
|
|
13
|
+
//
|
|
14
|
+
// Anything missing `meta.href` is excluded from the index.
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {Array<{meta?: object}>} documents Catalog documents with meta.
|
|
18
|
+
* @param {Object} [options]
|
|
19
|
+
* @param {string} [options.defaultLang='default'] Fallback language tag
|
|
20
|
+
* for documents that don't declare meta.lang. The literal string
|
|
21
|
+
* `'default'` is treated as a fallback bucket in lookups.
|
|
22
|
+
* @returns {{
|
|
23
|
+
* href: (ref: string, lang?: string) => string,
|
|
24
|
+
* refFor: (url: string|null) => string|null,
|
|
25
|
+
* alternates: (opts: { route: string|null, languages?: string[] }) => { current: {lang, url, ref}|null, alternates: Array<{lang, url}> },
|
|
26
|
+
* map: Record<string, Record<string, string>>,
|
|
27
|
+
* }}
|
|
28
|
+
*/
|
|
29
|
+
export function createHrefIndex(documents, { defaultLang = 'default' } = {}) {
|
|
30
|
+
const map = {}
|
|
31
|
+
if (Array.isArray(documents)) {
|
|
32
|
+
for (const document of documents) {
|
|
33
|
+
const ref = document?.meta?.href
|
|
34
|
+
if (!ref) continue
|
|
35
|
+
const lang = document.meta?.lang ?? defaultLang
|
|
36
|
+
const url = document.meta?.route ?? document.meta?.destination ?? ref
|
|
37
|
+
if (!map[ref]) map[ref] = {}
|
|
38
|
+
map[ref][lang] = url
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a logical reference to a deployed URL.
|
|
44
|
+
*
|
|
45
|
+
* Fallback chain: requested lang → `'default'` bucket → any
|
|
46
|
+
* available language → the input reference unchanged (so broken
|
|
47
|
+
* references stay visible rather than silently becoming undefined).
|
|
48
|
+
*/
|
|
49
|
+
function href(ref, lang) {
|
|
50
|
+
const target = lang ?? defaultLang
|
|
51
|
+
const entry = map[ref]
|
|
52
|
+
if (!entry) return ref
|
|
53
|
+
return entry[target]
|
|
54
|
+
?? entry['default']
|
|
55
|
+
?? Object.values(entry)[0]
|
|
56
|
+
?? ref
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Reverse lookup — given a deployed URL, return the logical
|
|
61
|
+
* reference it belongs to (or null if it's not in the index).
|
|
62
|
+
*/
|
|
63
|
+
function refFor(url) {
|
|
64
|
+
if (url == null) return null
|
|
65
|
+
for (const [ref, byLang] of Object.entries(map)) {
|
|
66
|
+
if (Object.values(byLang).includes(url)) return ref
|
|
67
|
+
}
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Alternates for a deployed URL — useful for hreflang tags and
|
|
73
|
+
* language switchers.
|
|
74
|
+
*
|
|
75
|
+
* `languages` controls the alternate set:
|
|
76
|
+
* - omitted: only return languages that actually exist in the
|
|
77
|
+
* catalog for this ref. Right shape for hreflang (don't
|
|
78
|
+
* advertise translations that don't exist).
|
|
79
|
+
* - provided as an array: return one entry per requested
|
|
80
|
+
* language, using href()'s fallback chain when a translation
|
|
81
|
+
* doesn't exist. Right shape for language switchers (show
|
|
82
|
+
* every locale the app supports).
|
|
83
|
+
*
|
|
84
|
+
* The current page's own language is excluded from `alternates` —
|
|
85
|
+
* it's what `current` is for. Callers that want it included can
|
|
86
|
+
* prepend `current` themselves.
|
|
87
|
+
*/
|
|
88
|
+
function alternates({ route, languages } = {}) {
|
|
89
|
+
if (route == null) return { current: null, alternates: [] }
|
|
90
|
+
const ref = refFor(route)
|
|
91
|
+
if (ref == null) return { current: null, alternates: [] }
|
|
92
|
+
const entry = map[ref] ?? {}
|
|
93
|
+
const currentLang = Object.entries(entry).find(([, url]) => url === route)?.[0] ?? null
|
|
94
|
+
const current = { lang: currentLang, url: route, ref }
|
|
95
|
+
|
|
96
|
+
let list
|
|
97
|
+
if (Array.isArray(languages)) {
|
|
98
|
+
list = languages
|
|
99
|
+
.filter(lang => lang !== currentLang)
|
|
100
|
+
.map(lang => ({ lang, url: href(ref, lang) }))
|
|
101
|
+
} else {
|
|
102
|
+
list = Object.entries(entry)
|
|
103
|
+
.filter(([lang]) => lang !== currentLang && lang !== 'default')
|
|
104
|
+
.map(([lang, url]) => ({ lang, url }))
|
|
105
|
+
}
|
|
106
|
+
return { current, alternates: list }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { href, refFor, alternates, map }
|
|
110
|
+
}
|
package/src/routes.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Build-time route enumeration. Given a mikser entities client and a
|
|
2
|
+
// mapRoute function, return the array of route descriptors produced by
|
|
3
|
+
// applying `mapRoute` to every catalog entity that matches the filter.
|
|
4
|
+
//
|
|
5
|
+
// Auto-paginates via the client's listAll() under the hood — no manual
|
|
6
|
+
// limit, no silent truncation on large catalogs.
|
|
7
|
+
//
|
|
8
|
+
// Framework-agnostic: the `mapRoute` return shape is whatever your build
|
|
9
|
+
// pipeline expects (vite-ssg routes, Next pages, SvelteKit entries, etc.).
|
|
10
|
+
// Framework SDKs re-export this with their own typed mapRoute signatures.
|
|
11
|
+
|
|
12
|
+
const DEFAULT_FILTER = { 'meta.published': true, 'meta.route': { $exists: true } }
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} options
|
|
16
|
+
* @param {Object} options.client A mikser entities client (the result of
|
|
17
|
+
* createClient(...).entities(name)).
|
|
18
|
+
* @param {Object} [options.filter] Sift filter — defaults to "published
|
|
19
|
+
* documents that declare meta.route".
|
|
20
|
+
* @param {Function} options.mapRoute (document) => routeDescriptor | null.
|
|
21
|
+
* Null returns are dropped.
|
|
22
|
+
* @returns {Promise<Array>} The mapped route descriptors.
|
|
23
|
+
*/
|
|
24
|
+
export async function generateMikserRoutes({
|
|
25
|
+
client,
|
|
26
|
+
filter = DEFAULT_FILTER,
|
|
27
|
+
mapRoute,
|
|
28
|
+
} = {}) {
|
|
29
|
+
if (!client) throw new Error('generateMikserRoutes: { client } is required')
|
|
30
|
+
if (!mapRoute) throw new Error('generateMikserRoutes: { mapRoute } is required')
|
|
31
|
+
|
|
32
|
+
const items = await client.listAll({ filter, fields: ['id', 'meta'] })
|
|
33
|
+
return items.map(mapRoute).filter(r => r != null)
|
|
34
|
+
}
|