@contentrain/query 7.0.15 → 7.2.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 +92 -6
- package/dist/astro/index.cjs +142 -0
- package/dist/astro/index.d.cts +81 -0
- package/dist/astro/index.d.cts.map +1 -0
- package/dist/astro/index.d.mts +81 -0
- package/dist/astro/index.d.mts.map +1 -0
- package/dist/astro/index.mjs +143 -0
- package/dist/astro/index.mjs.map +1 -0
- package/dist/cdn/index.cjs +2 -1
- package/dist/cdn/index.d.cts +2 -2
- package/dist/cdn/index.d.mts +2 -2
- package/dist/cdn/index.mjs +2 -2
- package/dist/{cdn-C8dqHhdG.mjs → cdn-C5yiVdfQ.mjs} +102 -27
- package/dist/cdn-C5yiVdfQ.mjs.map +1 -0
- package/dist/{cdn-DrQCLLj6.cjs → cdn-DJw9oksO.cjs} +106 -25
- package/dist/cli.cjs +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/frontmatter-CVjPyhOq.mjs +248 -0
- package/dist/frontmatter-CVjPyhOq.mjs.map +1 -0
- package/dist/frontmatter-DSFLOTwS.cjs +287 -0
- package/dist/{generate-mpKW9Y8v.mjs → generate-B7jooE0f.mjs} +4 -244
- package/dist/generate-B7jooE0f.mjs.map +1 -0
- package/dist/{generate-DN3mAbvC.cjs → generate-D3C6itur.cjs} +14 -254
- package/dist/generator/generate.cjs +1 -1
- package/dist/generator/generate.mjs +1 -1
- package/dist/{index-TgljULTn.d.mts → index-BFurv7WX.d.cts} +134 -28
- package/dist/index-BFurv7WX.d.cts.map +1 -0
- package/dist/{index-mxo0dHvb.d.cts → index-VXFz-DDN.d.mts} +134 -28
- package/dist/index-VXFz-DDN.d.mts.map +1 -0
- package/dist/index.cjs +2 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +39 -15
- package/dist/cdn-C8dqHhdG.mjs.map +0 -1
- package/dist/generate-mpKW9Y8v.mjs.map +0 -1
- package/dist/index-TgljULTn.d.mts.map +0 -1
- package/dist/index-mxo0dHvb.d.cts.map +0 -1
|
@@ -428,38 +428,110 @@ var MediaAccessor = class {
|
|
|
428
428
|
}
|
|
429
429
|
};
|
|
430
430
|
//#endregion
|
|
431
|
+
//#region src/cdn/public-request.ts
|
|
432
|
+
async function errorMessage(res, fallback) {
|
|
433
|
+
const text = await res.text().catch(() => "");
|
|
434
|
+
try {
|
|
435
|
+
const parsed = JSON.parse(text);
|
|
436
|
+
const message = parsed.message ?? parsed.statusMessage;
|
|
437
|
+
if (typeof message === "string" && message) return message;
|
|
438
|
+
} catch {}
|
|
439
|
+
return text || fallback;
|
|
440
|
+
}
|
|
441
|
+
async function publicGet(url) {
|
|
442
|
+
const res = await globalThis.fetch(url);
|
|
443
|
+
if (!res.ok) throw new ContentrainError(res.status, await errorMessage(res, "Request failed"));
|
|
444
|
+
return await res.json();
|
|
445
|
+
}
|
|
446
|
+
async function publicPost(url, body) {
|
|
447
|
+
const res = await globalThis.fetch(url, {
|
|
448
|
+
method: "POST",
|
|
449
|
+
headers: { "Content-Type": "application/json" },
|
|
450
|
+
body: JSON.stringify(body)
|
|
451
|
+
});
|
|
452
|
+
if (!res.ok) throw new ContentrainError(res.status, await errorMessage(res, "Request failed"));
|
|
453
|
+
return await res.json();
|
|
454
|
+
}
|
|
455
|
+
/** `baseUrl` without a trailing slash, path segments URL-encoded. */
|
|
456
|
+
function publicUrl(baseUrl, segments, query) {
|
|
457
|
+
const path = segments.map((s) => encodeURIComponent(s)).join("/");
|
|
458
|
+
const params = new URLSearchParams();
|
|
459
|
+
for (const [key, value] of Object.entries(query ?? {})) if (value !== void 0) params.set(key, String(value));
|
|
460
|
+
const qs = params.toString();
|
|
461
|
+
return `${baseUrl.replace(/\/+$/, "")}/${path}${qs ? `?${qs}` : ""}`;
|
|
462
|
+
}
|
|
463
|
+
//#endregion
|
|
431
464
|
//#region src/cdn/forms-client.ts
|
|
432
465
|
var FormsClient = class {
|
|
433
466
|
_baseUrl;
|
|
434
467
|
_projectId;
|
|
435
|
-
_apiKey;
|
|
436
468
|
constructor(config) {
|
|
437
469
|
this._baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
438
470
|
this._projectId = config.projectId;
|
|
439
|
-
this._apiKey = config.apiKey;
|
|
440
471
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
return await res.json();
|
|
472
|
+
config(modelId) {
|
|
473
|
+
return publicGet(publicUrl(this._baseUrl, [
|
|
474
|
+
this._projectId,
|
|
475
|
+
modelId,
|
|
476
|
+
"config"
|
|
477
|
+
]));
|
|
448
478
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
479
|
+
/**
|
|
480
|
+
* Resolves with the server's verdict for 2xx responses — including
|
|
481
|
+
* `{ success: false, errors }` for validation failures, which are not
|
|
482
|
+
* exceptions. Rejects with `ContentrainError` on 403/404/429/5xx.
|
|
483
|
+
*/
|
|
484
|
+
submit(modelId, data, options) {
|
|
485
|
+
const body = { data };
|
|
486
|
+
if (options?.captchaToken) body.captchaToken = options.captchaToken;
|
|
487
|
+
if (options?.honeypot !== void 0) body._hp = options.honeypot;
|
|
488
|
+
return publicPost(publicUrl(this._baseUrl, [
|
|
489
|
+
this._projectId,
|
|
490
|
+
modelId,
|
|
491
|
+
"submit"
|
|
492
|
+
]), body);
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region src/cdn/comments-client.ts
|
|
497
|
+
var CommentsClient = class {
|
|
498
|
+
_baseUrl;
|
|
499
|
+
_projectId;
|
|
500
|
+
constructor(config) {
|
|
501
|
+
this._baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
502
|
+
this._projectId = config.projectId;
|
|
503
|
+
}
|
|
504
|
+
thread(modelId, entryId, query) {
|
|
505
|
+
return publicGet(publicUrl(this._baseUrl, [
|
|
506
|
+
this._projectId,
|
|
507
|
+
modelId,
|
|
508
|
+
entryId
|
|
509
|
+
], {
|
|
510
|
+
locale: query?.locale,
|
|
511
|
+
page: query?.page,
|
|
512
|
+
limit: query?.limit,
|
|
513
|
+
sort: query?.sort
|
|
514
|
+
}));
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Resolves with the server's verdict for 2xx responses — including
|
|
518
|
+
* `{ success: false, errors }`. Rejects with `ContentrainError` on
|
|
519
|
+
* 403 (thread closed / plan), 404, 429 and 5xx.
|
|
520
|
+
*/
|
|
521
|
+
submit(modelId, entryId, submission, options) {
|
|
522
|
+
const url = publicUrl(this._baseUrl, [
|
|
523
|
+
this._projectId,
|
|
524
|
+
modelId,
|
|
525
|
+
entryId
|
|
526
|
+
], { locale: options?.locale });
|
|
527
|
+
const body = {
|
|
528
|
+
author: submission.author,
|
|
529
|
+
body: submission.body
|
|
530
|
+
};
|
|
531
|
+
if (submission.parentId) body.parentId = submission.parentId;
|
|
532
|
+
if (submission.captchaToken) body.captchaToken = submission.captchaToken;
|
|
533
|
+
if (submission.honeypot !== void 0) body._hp = submission.honeypot;
|
|
534
|
+
return publicPost(url, body);
|
|
463
535
|
}
|
|
464
536
|
};
|
|
465
537
|
//#endregion
|
|
@@ -517,8 +589,11 @@ function createContentrain(config) {
|
|
|
517
589
|
media: () => new MediaAccessor(transport),
|
|
518
590
|
form: () => new FormsClient({
|
|
519
591
|
baseUrl: (config.baseUrl ?? "https://studio.contentrain.io/api/cdn/v1").replace("/cdn/v1", "/forms/v1"),
|
|
520
|
-
projectId: config.projectId
|
|
521
|
-
|
|
592
|
+
projectId: config.projectId
|
|
593
|
+
}),
|
|
594
|
+
comments: () => new CommentsClient({
|
|
595
|
+
baseUrl: (config.baseUrl ?? "https://studio.contentrain.io/api/cdn/v1").replace("/cdn/v1", "/comments/v1"),
|
|
596
|
+
projectId: config.projectId
|
|
522
597
|
}),
|
|
523
598
|
conversation: () => new ConversationClient({
|
|
524
599
|
baseUrl: (config.baseUrl ?? "https://studio.contentrain.io/api/cdn/v1").replace("/cdn/v1", "/conversation/v1"),
|
|
@@ -532,6 +607,6 @@ function createContentrain(config) {
|
|
|
532
607
|
};
|
|
533
608
|
}
|
|
534
609
|
//#endregion
|
|
535
|
-
export {
|
|
610
|
+
export { MediaAccessor as a, CdnSingletonAccessor as c, ContentrainError as d, applyWhere as f, FormsClient as i, CdnCollectionQuery as l, ConversationClient as n, CdnDocumentQuery as o, CommentsClient as r, CdnDictionaryAccessor as s, createContentrain as t, HttpTransport as u };
|
|
536
611
|
|
|
537
|
-
//# sourceMappingURL=cdn-
|
|
612
|
+
//# sourceMappingURL=cdn-C5yiVdfQ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdn-C5yiVdfQ.mjs","names":[],"sources":["../src/shared/where.ts","../src/cdn/errors.ts","../src/cdn/http-transport.ts","../src/cdn/collection-query.ts","../src/cdn/singleton-accessor.ts","../src/cdn/dictionary-accessor.ts","../src/cdn/document-query.ts","../src/cdn/media-accessor.ts","../src/cdn/public-request.ts","../src/cdn/forms-client.ts","../src/cdn/comments-client.ts","../src/cdn/conversation-client.ts","../src/cdn/index.ts"],"sourcesContent":["export type WhereOp = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'contains'\n\nexport interface WhereClause {\n field: string\n op: WhereOp\n value: unknown\n}\n\nexport function applyWhere<T>(item: T, clause: WhereClause): boolean {\n const val = (item as Record<string, unknown>)[clause.field]\n switch (clause.op) {\n case 'eq': {\n if (Array.isArray(val)) return val.includes(clause.value)\n return val === clause.value\n }\n case 'ne': {\n if (Array.isArray(val)) return !val.includes(clause.value)\n return val !== clause.value\n }\n case 'gt': return (val as number) > (clause.value as number)\n case 'gte': return (val as number) >= (clause.value as number)\n case 'lt': return (val as number) < (clause.value as number)\n case 'lte': return (val as number) <= (clause.value as number)\n case 'in': return Array.isArray(clause.value) && (clause.value as unknown[]).includes(val)\n case 'contains': {\n if (typeof val === 'string') return val.includes(clause.value as string)\n if (Array.isArray(val)) return val.includes(clause.value)\n return false\n }\n default: return true\n }\n}\n","export class ContentrainError extends Error {\n constructor(\n public status: number,\n message: string,\n ) {\n super(message)\n this.name = 'ContentrainError'\n }\n}\n","import { ContentrainError } from './errors.js'\nimport type { CollectionDataSource, SingletonDataSource, DictionaryDataSource, DocumentDataSource, DocumentIndexEntry } from './data-source.js'\n\ninterface CacheEntry {\n data: unknown\n etag: string\n}\n\nexport interface TransportConfig {\n baseUrl: string\n projectId: string\n apiKey: string\n /** Bundle preload mode. `true` → `{ revalidateMs: 60_000 }` */\n bundle?: boolean | { revalidateMs?: number }\n defaultLocale?: string\n}\n\ninterface BundleState {\n fetchedAt: number\n paths: Set<string>\n missing: boolean\n inflight: Promise<void> | null\n}\n\ninterface BundlePayload {\n version?: string\n paths?: Record<string, unknown>\n}\n\n/** Paths eligible for bundle serving — everything else (manifests, models/*) has no locale segment. */\nconst BUNDLE_SCOPE = /^(content|documents|meta)\\//\n\nexport class HttpTransport {\n private _baseUrl: string\n private _projectId: string\n private _apiKey: string\n private _cache = new Map<string, CacheEntry>()\n private _bundleMode: boolean\n private _revalidateMs: number\n private _defaultLocale: string\n private _primed = new Map<string, unknown>()\n private _bundles = new Map<string, BundleState>()\n\n constructor(config: TransportConfig) {\n this._baseUrl = config.baseUrl.replace(/\\/+$/, '')\n this._projectId = config.projectId\n this._apiKey = config.apiKey\n const b = config.bundle\n this._bundleMode = !!b\n this._revalidateMs = typeof b === 'object' && typeof b.revalidateMs === 'number' ? b.revalidateMs : 60_000\n this._defaultLocale = config.defaultLocale ?? 'en'\n }\n\n buildUrl(path: string): string {\n return `${this._baseUrl}/${this._projectId}/${path}`\n }\n\n /** Eager warmup (e.g. SSR boot). Resolves `true` when a bundle was found and primed. */\n async preload(locale?: string): Promise<boolean> {\n if (!this._bundleMode) return false\n const l = locale ?? this._defaultLocale\n await this._ensureBundle(l)\n return !this._bundles.get(l)?.missing\n }\n\n async fetch<T>(path: string): Promise<T> {\n if (this._bundleMode && BUNDLE_SCOPE.test(path)) {\n await this._ensureBundle(this._localeOf(path))\n if (this._primed.has(path)) return this._primed.get(path) as T\n // out-of-scope path (doc body, meta, ...) → regular per-path fetch\n }\n return this._networkFetch<T>(path)\n }\n\n /** Locale from a path's last segment (sans `.json`). `data` (non-i18n) → defaultLocale bundle. */\n private _localeOf(path: string): string {\n const last = path.slice(path.lastIndexOf('/') + 1).replace(/\\.json$/, '')\n return last === 'data' ? this._defaultLocale : last\n }\n\n private _ensureBundle(locale: string): Promise<void> | void {\n const state = this._bundles.get(locale)\n if (state?.inflight) return state.inflight\n if (state && Date.now() - state.fetchedAt < this._revalidateMs) return\n\n const next: BundleState = state ?? { fetchedAt: 0, paths: new Set(), missing: false, inflight: null }\n this._bundles.set(locale, next)\n next.inflight = (async () => {\n try {\n // _networkFetch is conditional: unchanged bundle → 304 → cached payload (cheap)\n const bundle = await this._networkFetch<BundlePayload>(`_bundle/${locale}.json`)\n if (bundle?.version !== '1' || !bundle.paths || typeof bundle.paths !== 'object') {\n next.missing = true\n return\n }\n // Evict primed paths absent from the fresh bundle (deleted models must not serve stale)\n const fresh = new Set(Object.keys(bundle.paths))\n for (const p of next.paths) {\n if (!fresh.has(p)) this._primed.delete(p)\n }\n for (const [p, body] of Object.entries(bundle.paths)) this._primed.set(p, body)\n next.paths = fresh\n next.missing = false\n }\n catch {\n // 404 (bundle not built yet) or network error → per-path fallback until next revalidate\n next.missing = true\n }\n finally {\n next.fetchedAt = Date.now()\n next.inflight = null\n }\n })()\n return next.inflight\n }\n\n private async _networkFetch<T>(path: string): Promise<T> {\n const url = `${this._baseUrl}/${this._projectId}/${path}`\n const cached = this._cache.get(path)\n\n const headers: Record<string, string> = {\n 'Authorization': `Bearer ${this._apiKey}`,\n }\n if (cached?.etag) {\n headers['If-None-Match'] = cached.etag\n }\n\n const res = await globalThis.fetch(url, { headers })\n\n if (res.status === 304 && cached) return cached.data as T\n if (!res.ok) throw new ContentrainError(res.status, await res.text())\n\n const data = (await res.json()) as T\n const etag = res.headers.get('etag') ?? ''\n if (etag) {\n this._cache.set(path, { data, etag })\n }\n return data\n }\n\n collection<T>(modelId: string): CollectionDataSource<T> {\n return {\n getAll: async (locale) => {\n const map = await this.fetch<Record<string, T>>(`content/${modelId}/${locale}.json`)\n return Object.entries(map).map(([id, entry]) => Object.assign({ id }, entry as object) as T)\n },\n getOne: async (id, locale) => {\n const map = await this.fetch<Record<string, T>>(`content/${modelId}/${locale}.json`)\n const entry = map[id]\n return entry ? { id, ...entry as object } as T : null\n },\n }\n }\n\n singleton<T>(modelId: string): SingletonDataSource<T> {\n return {\n get: (locale) => this.fetch<T>(`content/${modelId}/${locale}.json`),\n }\n }\n\n dictionary(modelId: string): DictionaryDataSource {\n return {\n get: (locale) => this.fetch<Record<string, string>>(`content/${modelId}/${locale}.json`),\n }\n }\n\n document<T>(modelId: string): DocumentDataSource<T> {\n return {\n // `_index` carries frontmatter only — the body is in the per-slug document.\n getIndex: (locale) => this.fetch<DocumentIndexEntry<T>[]>(`documents/${modelId}/_index/${locale}.json`),\n getBySlug: (slug, locale) => this.fetch(`documents/${modelId}/${slug}/${locale}.json`),\n }\n }\n}\n","import type { CollectionDataSource } from './data-source.js'\nimport type { HttpTransport } from './http-transport.js'\nimport type { WhereOp, WhereClause } from '../shared/where.js'\nimport { applyWhere } from '../shared/where.js'\n\nexport interface EntryMeta {\n status?: string\n publish_at?: string\n expire_at?: string\n updated_by?: string\n approved_by?: string\n [key: string]: unknown\n}\n\nexport class CdnCollectionQuery<T extends object> {\n private _transport: HttpTransport\n private _source: CollectionDataSource<T>\n private _modelId: string\n private _locale: string = 'en'\n private _filters: WhereClause[] = []\n private _sortField: string | null = null\n private _sortOrder: 'asc' | 'desc' = 'asc'\n private _limit: number | null = null\n private _offset = 0\n private _includes: string[] = []\n private _withMeta = false\n\n constructor(transport: HttpTransport, modelId: string, defaultLocale?: string) {\n this._transport = transport\n this._source = transport.collection<T>(modelId)\n this._modelId = modelId\n if (defaultLocale) this._locale = defaultLocale\n }\n\n locale(lang: string): this {\n this._locale = lang\n return this\n }\n\n where(field: string, op: WhereOp, value: unknown): this {\n this._filters.push({ field, op, value })\n return this\n }\n\n sort(field: string, order: 'asc' | 'desc' = 'asc'): this {\n this._sortField = field\n this._sortOrder = order\n return this\n }\n\n limit(n: number): this {\n this._limit = n\n return this\n }\n\n offset(n: number): this {\n this._offset = n\n return this\n }\n\n include(...fields: string[]): this {\n this._includes.push(...fields)\n return this\n }\n\n withMeta(): this {\n this._withMeta = true\n return this\n }\n\n async all(): Promise<T[]> {\n let items = await this._source.getAll(this._locale)\n\n // Filter\n for (const clause of this._filters) {\n items = items.filter(item => applyWhere(item, clause))\n }\n\n // Sort\n if (this._sortField) {\n const field = this._sortField\n const dir = this._sortOrder === 'asc' ? 1 : -1\n items = items.toSorted((a, b) => {\n const va = (a as Record<string, unknown>)[field]\n const vb = (b as Record<string, unknown>)[field]\n if (va == null && vb == null) return 0\n if (va == null) return dir\n if (vb == null) return -dir\n if (va < vb) return -dir\n if (va > vb) return dir\n return 0\n })\n }\n\n // Pagination\n if (this._offset > 0 || this._limit !== null) {\n const end = this._limit !== null ? this._offset + this._limit : undefined\n items = items.slice(this._offset, end)\n }\n\n // Resolve relations\n if (this._includes.length > 0) {\n items = await this._resolveIncludes(items)\n }\n\n // Enrich with entry metadata\n if (this._withMeta) {\n items = await this._enrichMeta(items)\n }\n\n return items\n }\n\n async count(): Promise<number> {\n const items = await this.all()\n return items.length\n }\n\n async first(): Promise<T | undefined> {\n const items = await this.all()\n return items[0]\n }\n\n private async _resolveIncludes(items: T[]): Promise<T[]> {\n // Prefetch related collections to avoid N+1\n const cache = new Map<string, Map<string, Record<string, unknown>>>()\n\n for (const field of this._includes) {\n // Collect all IDs for this field\n for (const item of items) {\n const val = (item as Record<string, unknown>)[field]\n const ids = Array.isArray(val) ? val : val ? [val] : []\n for (const id of ids) {\n if (typeof id !== 'string') continue\n // We don't know the target model from here, so we use field name as hint\n // In production, model metadata would provide this mapping\n if (!cache.has(field)) {\n try {\n const related = await this._transport.fetch<Record<string, unknown>>(`content/${field}/${this._locale}.json`)\n const map = new Map<string, Record<string, unknown>>()\n for (const [entryId, entry] of Object.entries(related)) {\n map.set(entryId, { id: entryId, ...entry as object })\n }\n cache.set(field, map)\n } catch {\n cache.set(field, new Map())\n }\n break\n }\n }\n }\n }\n\n return items.map(item => {\n const resolved = { ...item }\n const dst = resolved as Record<string, unknown>\n for (const field of this._includes) {\n const related = cache.get(field)\n if (!related) continue\n const val = (item as Record<string, unknown>)[field]\n if (Array.isArray(val)) {\n dst[field] = val.map(id => typeof id === 'string' ? (related.get(id) ?? id) : id)\n } else if (typeof val === 'string') {\n dst[field] = related.get(val) ?? val\n }\n }\n return resolved\n })\n }\n\n private async _enrichMeta(items: T[]): Promise<T[]> {\n let metaMap: Record<string, EntryMeta> = {}\n try {\n metaMap = await this._transport.fetch<Record<string, EntryMeta>>(`meta/${this._modelId}/${this._locale}.json`)\n } catch {\n // No meta available — return items unchanged\n return items\n }\n return items.map(item => {\n const id = (item as Record<string, unknown>).id as string\n const meta = id ? metaMap[id] : undefined\n if (meta) {\n return { ...item, _meta: meta }\n }\n return item\n })\n }\n}\n\n","import type { SingletonDataSource } from './data-source.js'\n\nexport class CdnSingletonAccessor<T extends Record<string, unknown>> {\n private _source: SingletonDataSource<T>\n private _locale: string = 'en'\n\n constructor(source: SingletonDataSource<T>, defaultLocale?: string) {\n this._source = source\n if (defaultLocale) this._locale = defaultLocale\n }\n\n locale(lang: string): this {\n this._locale = lang\n return this\n }\n\n async get(): Promise<T> {\n return this._source.get(this._locale)\n }\n}\n","import type { DictionaryDataSource } from './data-source.js'\n\nexport class CdnDictionaryAccessor {\n private _source: DictionaryDataSource\n private _locale: string = 'en'\n\n constructor(source: DictionaryDataSource, defaultLocale?: string) {\n this._source = source\n if (defaultLocale) this._locale = defaultLocale\n }\n\n locale(lang: string): this {\n this._locale = lang\n return this\n }\n\n async get(): Promise<Record<string, string>>\n async get(key: string): Promise<string | undefined>\n async get(key: string, params: Record<string, string | number>): Promise<string>\n async get(key?: string, params?: Record<string, string | number>): Promise<Record<string, string> | string | undefined> {\n const dict = await this._source.get(this._locale)\n if (key === undefined) return dict\n const value = dict[key]\n if (value === undefined) return undefined\n if (params) return interpolate(value, params)\n return value\n }\n}\n\nfunction interpolate(template: string, params: Record<string, string | number>): string {\n return template.replace(/\\{(\\w+)\\}/g, (match, key: string) => {\n const val = params[key]\n return val !== undefined ? String(val) : match\n })\n}\n","import type { DocumentDataSource, DocumentIndexEntry } from './data-source.js'\nimport type { WhereOp, WhereClause } from '../shared/where.js'\nimport { applyWhere } from '../shared/where.js'\n\nexport class CdnDocumentQuery<T extends object> {\n private _source: DocumentDataSource<T>\n private _locale: string = 'en'\n private _filters: WhereClause[] = []\n private _sortField: string | null = null\n private _sortOrder: 'asc' | 'desc' = 'asc'\n\n constructor(source: DocumentDataSource<T>, defaultLocale?: string) {\n this._source = source\n if (defaultLocale) this._locale = defaultLocale\n }\n\n locale(lang: string): this {\n this._locale = lang\n return this\n }\n\n where(field: string, op: WhereOp, value: unknown): this {\n this._filters.push({ field, op, value })\n return this\n }\n\n sort(field: string, order: 'asc' | 'desc' = 'asc'): this {\n this._sortField = field\n this._sortOrder = order\n return this\n }\n\n /**\n * Every entry's frontmatter, from the model's `_index`.\n *\n * Bodies are not included — fetch one with {@link bySlug}. See\n * {@link DocumentIndexEntry}.\n */\n async all(): Promise<DocumentIndexEntry<T>[]> {\n let items = await this._source.getIndex(this._locale)\n\n for (const clause of this._filters) {\n items = items.filter(item => applyWhere(item, clause))\n }\n\n if (this._sortField) {\n const sf = this._sortField\n const dir = this._sortOrder === 'asc' ? 1 : -1\n items = items.toSorted((a, b) => {\n const va = (a as Record<string, unknown>)[sf] as number | string | null | undefined\n const vb = (b as Record<string, unknown>)[sf] as number | string | null | undefined\n if (va == null && vb == null) return 0\n if (va == null) return dir\n if (vb == null) return -dir\n return va < vb ? -dir : va > vb ? dir : 0\n })\n }\n\n return items\n }\n\n async count(): Promise<number> {\n const items = await this.all()\n return items.length\n }\n\n async first(): Promise<DocumentIndexEntry<T> | undefined> {\n const items = await this.all()\n return items[0]\n }\n\n /** The full document: frontmatter plus its rendered body. */\n async bySlug(slug: string): Promise<{ frontmatter: T; body: string; html: string } | null> {\n return this._source.getBySlug(slug, this._locale)\n }\n}\n","import type { HttpTransport } from './http-transport.js'\n\nexport interface MediaAssetMeta {\n width?: number\n height?: number\n format?: string\n size?: number\n blurhash?: string | null\n alt?: string | null\n}\n\nexport interface MediaAsset {\n original: string\n variants: Record<string, string>\n meta: MediaAssetMeta\n}\n\nexport interface MediaManifest {\n version: string\n assets: Record<string, MediaAsset>\n}\n\nexport class MediaAccessor {\n private _transport: HttpTransport\n private _manifest: MediaManifest | null = null\n\n constructor(transport: HttpTransport) {\n this._transport = transport\n }\n\n async manifest(): Promise<MediaManifest> {\n if (!this._manifest) {\n this._manifest = await this._transport.fetch<MediaManifest>('_media_manifest.json')\n }\n return this._manifest\n }\n\n async assets(): Promise<Record<string, MediaAsset>> {\n const m = await this.manifest()\n return m.assets\n }\n\n async asset(path: string): Promise<MediaAsset | null> {\n const all = await this.assets()\n return all[path] ?? null\n }\n\n async list(): Promise<Array<{ path: string } & MediaAsset>> {\n const all = await this.assets()\n return Object.entries(all).map(([path, asset]) =>\n Object.assign({ path }, asset),\n )\n }\n\n resolve(asset: MediaAsset, variant?: string): string {\n if (variant && asset.variants[variant]) {\n return asset.variants[variant]\n }\n return asset.original\n }\n\n url(asset: MediaAsset, variant?: string): string {\n const path = this.resolve(asset, variant)\n return this._transport.buildUrl(path)\n }\n}\n","// Shared transport for the PUBLIC embed endpoints (forms, comments).\n//\n// These are called from a visitor's browser on someone else's site: no\n// session, no API key — a page cannot keep a secret, and Studio's CORS for\n// these routes allows only `Content-Type`, so an `Authorization` header would\n// fail the preflight before the request is even sent. Errors come back as\n// h3 error bodies (`{ statusCode, message }`) or plain text; both become a\n// `ContentrainError` carrying the HTTP status.\n\nimport { ContentrainError } from './errors.js'\n\nasync function errorMessage(res: Response, fallback: string): Promise<string> {\n const text = await res.text().catch(() => '')\n try {\n const parsed = JSON.parse(text) as { message?: unknown; statusMessage?: unknown }\n const message = parsed.message ?? parsed.statusMessage\n if (typeof message === 'string' && message) return message\n } catch {\n // not JSON — fall through to the raw text\n }\n return text || fallback\n}\n\nexport async function publicGet<T>(url: string): Promise<T> {\n const res = await globalThis.fetch(url)\n if (!res.ok) throw new ContentrainError(res.status, await errorMessage(res, 'Request failed'))\n return (await res.json()) as T\n}\n\nexport async function publicPost<T>(url: string, body: unknown): Promise<T> {\n const res = await globalThis.fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (!res.ok) throw new ContentrainError(res.status, await errorMessage(res, 'Request failed'))\n return (await res.json()) as T\n}\n\n/** `baseUrl` without a trailing slash, path segments URL-encoded. */\nexport function publicUrl(baseUrl: string, segments: string[], query?: Record<string, string | number | undefined>): string {\n const path = segments.map((s) => encodeURIComponent(s)).join('/')\n const params = new URLSearchParams()\n for (const [key, value] of Object.entries(query ?? {})) {\n if (value !== undefined) params.set(key, String(value))\n }\n const qs = params.toString()\n return `${baseUrl.replace(/\\/+$/, '')}/${path}${qs ? `?${qs}` : ''}`\n}\n","// Public forms client — the browser side of Studio's `/api/forms/v1`.\n//\n// Contract (Studio docs/FORMS.md):\n// GET {base}/{projectId}/{modelId}/config → FormConfig\n// POST {base}/{projectId}/{modelId}/submit { data, captchaToken?, _hp? } → FormSubmitResult\n//\n// `fields` is a map keyed by field id (the model's own FieldDef shape), not a\n// list; the captcha comes back as `captcha` + `captchaSiteKey`; the honeypot\n// input's name is `honeypotField`. Submissions wrap the values in `data` so\n// the control fields (`captchaToken`, `_hp`) can never collide with a model\n// field. No credential is sent: the endpoints are public by design.\n\nimport type { FieldDef } from '@contentrain/types'\nimport { publicGet, publicPost, publicUrl } from './public-request.js'\n\n/** One exposed field, as the model defines it. */\nexport type FormFieldConfig = FieldDef\n\nexport interface FormConfig {\n modelId: string\n /** The project's default locale — what a submission is validated against and written to. */\n locale: string\n /** Exposed fields only, keyed by field id. */\n fields: Record<string, FormFieldConfig>\n captcha: 'turnstile' | null\n /** Turnstile widget key, when `captcha` is set and the operator configured one. */\n captchaSiteKey: string | null\n successMessage?: string\n /** Name of the hidden honeypot input to render (and leave empty), or null. */\n honeypotField: string | null\n}\n\nexport interface FormFieldError {\n field: string\n message: string\n}\n\nexport interface FormSubmitResult {\n success: boolean\n /** The model's `successMessage` on success. */\n message?: string\n /** Validation errors against the exposed fields, or `captcha`. */\n errors?: FormFieldError[]\n}\n\nexport interface FormSubmitOptions {\n /** Turnstile token, when the config's `captcha` is set. */\n captchaToken?: string\n /** Value of the honeypot input. A human leaves it empty; a filled one is silently dropped server-side. */\n honeypot?: string\n}\n\nexport interface FormsClientConfig {\n /** Public API root — `https://studio.contentrain.io/api/forms/v1`. */\n baseUrl: string\n projectId: string\n}\n\nexport class FormsClient {\n private _baseUrl: string\n private _projectId: string\n\n constructor(config: FormsClientConfig) {\n this._baseUrl = config.baseUrl.replace(/\\/+$/, '')\n this._projectId = config.projectId\n }\n\n config(modelId: string): Promise<FormConfig> {\n return publicGet<FormConfig>(publicUrl(this._baseUrl, [this._projectId, modelId, 'config']))\n }\n\n /**\n * Resolves with the server's verdict for 2xx responses — including\n * `{ success: false, errors }` for validation failures, which are not\n * exceptions. Rejects with `ContentrainError` on 403/404/429/5xx.\n */\n submit(modelId: string, data: Record<string, unknown>, options?: FormSubmitOptions): Promise<FormSubmitResult> {\n const body: { data: Record<string, unknown>; captchaToken?: string; _hp?: string } = { data }\n if (options?.captchaToken) body.captchaToken = options.captchaToken\n if (options?.honeypot !== undefined) body._hp = options.honeypot\n return publicPost<FormSubmitResult>(publicUrl(this._baseUrl, [this._projectId, modelId, 'submit']), body)\n }\n}\n","// Public comments client — the browser side of Studio's `/api/comments/v1`.\n//\n// Contract (Studio docs/COMMENTS.md):\n// GET {base}/{projectId}/{modelId}/{entryId}?locale&page&limit&sort → CommentThread\n// POST {base}/{projectId}/{modelId}/{entryId}?locale\n// { author: { name, email?, url? }, body, parentId?, captchaToken?, _hp? } → CommentSubmitResult\n//\n// Only approved comments come back, nested under their roots; email, IP,\n// user agent and referrer never leave the server. `body` is plain text —\n// render it escaped. No credential is sent: the endpoints are public.\n\nimport { publicGet, publicPost, publicUrl } from './public-request.js'\n\nexport type CommentType = 'comment' | 'pingback' | 'trackback'\n\nexport interface CommentAuthor {\n name: string\n url: string | null\n /** Written from Studio by a workspace member. */\n isModerator: boolean\n}\n\nexport interface PublicComment {\n id: string\n parentId: string | null\n depth: number\n author: CommentAuthor\n /** Plain text. */\n body: string\n type: CommentType\n /** ISO 8601. */\n createdAt: string\n replies: PublicComment[]\n}\n\nexport interface CommentThreadConfig {\n /** Thread closed → render the comments, hide the form. */\n closed: boolean\n requireApproval: boolean\n requireEmail: boolean\n /** Reply nesting cap for new submissions (0 = flat). */\n maxDepth: number\n maxBodyLength: number\n captcha: 'turnstile' | null\n captchaSiteKey: string | null\n honeypotField: string | null\n}\n\nexport interface CommentThread {\n entry: { modelId: string; entryId: string; locale: string }\n config: CommentThreadConfig\n /** Root comments of this page, replies nested. */\n comments: PublicComment[]\n /** Root comments in total. */\n total: number\n page: number\n limit: number\n}\n\nexport interface CommentThreadQuery {\n locale?: string\n page?: number\n limit?: number\n sort?: 'oldest' | 'newest'\n}\n\nexport interface CommentSubmission {\n author: { name: string; email?: string; url?: string }\n body: string\n /** Reply target — an approved comment's id. */\n parentId?: string\n captchaToken?: string\n /** Value of the honeypot input (`config.honeypotField`); a human leaves it empty. */\n honeypot?: string\n}\n\nexport interface CommentFieldError {\n /** `author.name` · `author.email` · `author.url` · `body` · `parentId` · `captcha` */\n field: string\n message: string\n}\n\nexport interface CommentSubmitResult {\n success: boolean\n /** `approved` renders now; `pending` is only echoed back to its author. */\n status?: 'pending' | 'approved'\n /** The stored comment, in the public shape. Absent when the honeypot swallowed the post. */\n comment?: PublicComment\n errors?: CommentFieldError[]\n}\n\nexport interface CommentsClientConfig {\n /** Public API root — `https://studio.contentrain.io/api/comments/v1`. */\n baseUrl: string\n projectId: string\n}\n\nexport class CommentsClient {\n private _baseUrl: string\n private _projectId: string\n\n constructor(config: CommentsClientConfig) {\n this._baseUrl = config.baseUrl.replace(/\\/+$/, '')\n this._projectId = config.projectId\n }\n\n thread(modelId: string, entryId: string, query?: CommentThreadQuery): Promise<CommentThread> {\n const url = publicUrl(this._baseUrl, [this._projectId, modelId, entryId], {\n locale: query?.locale,\n page: query?.page,\n limit: query?.limit,\n sort: query?.sort,\n })\n return publicGet<CommentThread>(url)\n }\n\n /**\n * Resolves with the server's verdict for 2xx responses — including\n * `{ success: false, errors }`. Rejects with `ContentrainError` on\n * 403 (thread closed / plan), 404, 429 and 5xx.\n */\n submit(\n modelId: string,\n entryId: string,\n submission: CommentSubmission,\n options?: { locale?: string },\n ): Promise<CommentSubmitResult> {\n const url = publicUrl(this._baseUrl, [this._projectId, modelId, entryId], { locale: options?.locale })\n const body: Record<string, unknown> = { author: submission.author, body: submission.body }\n if (submission.parentId) body.parentId = submission.parentId\n if (submission.captchaToken) body.captchaToken = submission.captchaToken\n if (submission.honeypot !== undefined) body._hp = submission.honeypot\n return publicPost<CommentSubmitResult>(url, body)\n }\n}\n","import { ContentrainError } from './errors.js'\n\nexport interface ConversationContext {\n activeModelId?: string | null\n activeLocale?: string\n activeEntryId?: string | null\n panelState?: string\n activeBranch?: string | null\n}\n\nexport interface ConversationSendOptions {\n conversationId?: string\n context?: ConversationContext\n}\n\nexport interface ConversationToolResult {\n id: string\n name: string\n result: unknown\n}\n\nexport interface ConversationUsage {\n inputTokens: number\n outputTokens: number\n}\n\nexport interface ConversationResponse {\n conversationId: string\n message: string\n toolResults?: ConversationToolResult[]\n usage: ConversationUsage\n}\n\nexport interface ConversationMessage {\n id: string\n role: 'user' | 'assistant'\n content: string | unknown[]\n toolCalls?: unknown[]\n model?: string\n usage?: ConversationUsage\n createdAt: string\n}\n\nexport interface ConversationHistory {\n conversationId: string\n messages: ConversationMessage[]\n}\n\nexport interface ConversationClientConfig {\n baseUrl: string\n projectId: string\n apiKey: string\n}\n\nexport class ConversationClient {\n private _baseUrl: string\n private _projectId: string\n private _apiKey: string\n\n constructor(config: ConversationClientConfig) {\n this._baseUrl = config.baseUrl.replace(/\\/+$/, '')\n this._projectId = config.projectId\n this._apiKey = config.apiKey\n }\n\n async send(message: string, options?: ConversationSendOptions): Promise<ConversationResponse> {\n const url = `${this._baseUrl}/${this._projectId}/message`\n\n const body: Record<string, unknown> = { message }\n if (options?.conversationId) body.conversationId = options.conversationId\n if (options?.context) body.context = options.context\n\n const res = await globalThis.fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this._apiKey}`,\n },\n body: JSON.stringify(body),\n })\n\n if (!res.ok) {\n throw new ContentrainError(res.status, await res.text())\n }\n\n return (await res.json()) as ConversationResponse\n }\n\n async history(conversationId: string, options?: { limit?: number }): Promise<ConversationHistory> {\n const params = new URLSearchParams({ conversationId })\n if (options?.limit) params.set('limit', String(options.limit))\n\n const url = `${this._baseUrl}/${this._projectId}/history?${params}`\n\n const res = await globalThis.fetch(url, {\n headers: {\n 'Authorization': `Bearer ${this._apiKey}`,\n },\n })\n\n if (!res.ok) {\n throw new ContentrainError(res.status, await res.text())\n }\n\n return (await res.json()) as ConversationHistory\n }\n}\n","import { HttpTransport } from './http-transport.js'\nimport { CdnCollectionQuery } from './collection-query.js'\nimport { CdnSingletonAccessor } from './singleton-accessor.js'\nimport { CdnDictionaryAccessor } from './dictionary-accessor.js'\nimport { CdnDocumentQuery } from './document-query.js'\nimport { MediaAccessor } from './media-accessor.js'\nimport { FormsClient } from './forms-client.js'\nimport { CommentsClient } from './comments-client.js'\nimport { ConversationClient } from './conversation-client.js'\n\nexport interface ContentrainCDNConfig {\n projectId: string\n apiKey: string\n baseUrl?: string\n defaultLocale?: string\n /** Bundle preload mode. `true` → `{ revalidateMs: 60_000 }` */\n bundle?: boolean | { revalidateMs?: number }\n}\n\nexport type ContentrainCDNClient = ReturnType<typeof createContentrain>\n\nexport function createContentrain(config: ContentrainCDNConfig) {\n const transport = new HttpTransport({\n baseUrl: config.baseUrl ?? 'https://studio.contentrain.io/api/cdn/v1',\n projectId: config.projectId,\n apiKey: config.apiKey,\n bundle: config.bundle,\n defaultLocale: config.defaultLocale,\n })\n const defaultLocale = config.defaultLocale\n\n return {\n collection: <T extends object = Record<string, unknown>>(modelId: string) =>\n new CdnCollectionQuery<T>(transport, modelId, defaultLocale),\n\n singleton: <T extends Record<string, unknown> = Record<string, unknown>>(modelId: string) =>\n new CdnSingletonAccessor<T>(transport.singleton<T>(modelId), defaultLocale),\n\n dictionary: (modelId: string) =>\n new CdnDictionaryAccessor(transport.dictionary(modelId), defaultLocale),\n\n document: <T extends object = Record<string, unknown>>(modelId: string) =>\n new CdnDocumentQuery<T>(transport.document<T>(modelId), defaultLocale),\n\n media: () => new MediaAccessor(transport),\n\n // Public embed surfaces: no API key travels with them — a visitor's\n // browser cannot keep a secret, and the endpoints are unauthenticated by design.\n form: () => new FormsClient({\n baseUrl: (config.baseUrl ?? 'https://studio.contentrain.io/api/cdn/v1').replace('/cdn/v1', '/forms/v1'),\n projectId: config.projectId,\n }),\n\n comments: () => new CommentsClient({\n baseUrl: (config.baseUrl ?? 'https://studio.contentrain.io/api/cdn/v1').replace('/cdn/v1', '/comments/v1'),\n projectId: config.projectId,\n }),\n\n conversation: () => new ConversationClient({\n baseUrl: (config.baseUrl ?? 'https://studio.contentrain.io/api/cdn/v1').replace('/cdn/v1', '/conversation/v1'),\n projectId: config.projectId,\n apiKey: config.apiKey,\n }),\n\n manifest: () => transport.fetch<unknown>('_manifest.json'),\n models: () => transport.fetch<unknown[]>('models/_index.json'),\n model: (id: string) => transport.fetch<unknown>(`models/${id}.json`),\n\n preload: (locale?: string) => transport.preload(locale),\n }\n}\n\n// Re-exports\nexport { ContentrainError } from './errors.js'\nexport type { CollectionDataSource, SingletonDataSource, DictionaryDataSource, DocumentDataSource, DocumentIndexEntry } from './data-source.js'\nexport { HttpTransport } from './http-transport.js'\nexport type { TransportConfig } from './http-transport.js'\nexport { CdnCollectionQuery } from './collection-query.js'\nexport { CdnSingletonAccessor } from './singleton-accessor.js'\nexport { CdnDictionaryAccessor } from './dictionary-accessor.js'\nexport { CdnDocumentQuery } from './document-query.js'\nexport { MediaAccessor } from './media-accessor.js'\nexport type { MediaAsset, MediaAssetMeta, MediaManifest } from './media-accessor.js'\nexport { FormsClient } from './forms-client.js'\nexport type {\n FormConfig,\n FormFieldConfig,\n FormFieldError,\n FormSubmitOptions,\n FormSubmitResult,\n FormsClientConfig,\n} from './forms-client.js'\nexport { CommentsClient } from './comments-client.js'\nexport type {\n CommentAuthor,\n CommentFieldError,\n CommentSubmission,\n CommentSubmitResult,\n CommentThread,\n CommentThreadConfig,\n CommentThreadQuery,\n CommentType,\n CommentsClientConfig,\n PublicComment,\n} from './comments-client.js'\nexport { ConversationClient } from './conversation-client.js'\nexport type {\n ConversationClientConfig,\n ConversationContext,\n ConversationSendOptions,\n ConversationResponse,\n ConversationToolResult,\n ConversationUsage,\n ConversationMessage,\n ConversationHistory,\n} from './conversation-client.js'\n"],"mappings":"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"}
|
|
@@ -428,38 +428,110 @@ var MediaAccessor = class {
|
|
|
428
428
|
}
|
|
429
429
|
};
|
|
430
430
|
//#endregion
|
|
431
|
+
//#region src/cdn/public-request.ts
|
|
432
|
+
async function errorMessage(res, fallback) {
|
|
433
|
+
const text = await res.text().catch(() => "");
|
|
434
|
+
try {
|
|
435
|
+
const parsed = JSON.parse(text);
|
|
436
|
+
const message = parsed.message ?? parsed.statusMessage;
|
|
437
|
+
if (typeof message === "string" && message) return message;
|
|
438
|
+
} catch {}
|
|
439
|
+
return text || fallback;
|
|
440
|
+
}
|
|
441
|
+
async function publicGet(url) {
|
|
442
|
+
const res = await globalThis.fetch(url);
|
|
443
|
+
if (!res.ok) throw new ContentrainError(res.status, await errorMessage(res, "Request failed"));
|
|
444
|
+
return await res.json();
|
|
445
|
+
}
|
|
446
|
+
async function publicPost(url, body) {
|
|
447
|
+
const res = await globalThis.fetch(url, {
|
|
448
|
+
method: "POST",
|
|
449
|
+
headers: { "Content-Type": "application/json" },
|
|
450
|
+
body: JSON.stringify(body)
|
|
451
|
+
});
|
|
452
|
+
if (!res.ok) throw new ContentrainError(res.status, await errorMessage(res, "Request failed"));
|
|
453
|
+
return await res.json();
|
|
454
|
+
}
|
|
455
|
+
/** `baseUrl` without a trailing slash, path segments URL-encoded. */
|
|
456
|
+
function publicUrl(baseUrl, segments, query) {
|
|
457
|
+
const path = segments.map((s) => encodeURIComponent(s)).join("/");
|
|
458
|
+
const params = new URLSearchParams();
|
|
459
|
+
for (const [key, value] of Object.entries(query ?? {})) if (value !== void 0) params.set(key, String(value));
|
|
460
|
+
const qs = params.toString();
|
|
461
|
+
return `${baseUrl.replace(/\/+$/, "")}/${path}${qs ? `?${qs}` : ""}`;
|
|
462
|
+
}
|
|
463
|
+
//#endregion
|
|
431
464
|
//#region src/cdn/forms-client.ts
|
|
432
465
|
var FormsClient = class {
|
|
433
466
|
_baseUrl;
|
|
434
467
|
_projectId;
|
|
435
|
-
_apiKey;
|
|
436
468
|
constructor(config) {
|
|
437
469
|
this._baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
438
470
|
this._projectId = config.projectId;
|
|
439
|
-
this._apiKey = config.apiKey;
|
|
440
471
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
return await res.json();
|
|
472
|
+
config(modelId) {
|
|
473
|
+
return publicGet(publicUrl(this._baseUrl, [
|
|
474
|
+
this._projectId,
|
|
475
|
+
modelId,
|
|
476
|
+
"config"
|
|
477
|
+
]));
|
|
448
478
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
479
|
+
/**
|
|
480
|
+
* Resolves with the server's verdict for 2xx responses — including
|
|
481
|
+
* `{ success: false, errors }` for validation failures, which are not
|
|
482
|
+
* exceptions. Rejects with `ContentrainError` on 403/404/429/5xx.
|
|
483
|
+
*/
|
|
484
|
+
submit(modelId, data, options) {
|
|
485
|
+
const body = { data };
|
|
486
|
+
if (options?.captchaToken) body.captchaToken = options.captchaToken;
|
|
487
|
+
if (options?.honeypot !== void 0) body._hp = options.honeypot;
|
|
488
|
+
return publicPost(publicUrl(this._baseUrl, [
|
|
489
|
+
this._projectId,
|
|
490
|
+
modelId,
|
|
491
|
+
"submit"
|
|
492
|
+
]), body);
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region src/cdn/comments-client.ts
|
|
497
|
+
var CommentsClient = class {
|
|
498
|
+
_baseUrl;
|
|
499
|
+
_projectId;
|
|
500
|
+
constructor(config) {
|
|
501
|
+
this._baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
502
|
+
this._projectId = config.projectId;
|
|
503
|
+
}
|
|
504
|
+
thread(modelId, entryId, query) {
|
|
505
|
+
return publicGet(publicUrl(this._baseUrl, [
|
|
506
|
+
this._projectId,
|
|
507
|
+
modelId,
|
|
508
|
+
entryId
|
|
509
|
+
], {
|
|
510
|
+
locale: query?.locale,
|
|
511
|
+
page: query?.page,
|
|
512
|
+
limit: query?.limit,
|
|
513
|
+
sort: query?.sort
|
|
514
|
+
}));
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Resolves with the server's verdict for 2xx responses — including
|
|
518
|
+
* `{ success: false, errors }`. Rejects with `ContentrainError` on
|
|
519
|
+
* 403 (thread closed / plan), 404, 429 and 5xx.
|
|
520
|
+
*/
|
|
521
|
+
submit(modelId, entryId, submission, options) {
|
|
522
|
+
const url = publicUrl(this._baseUrl, [
|
|
523
|
+
this._projectId,
|
|
524
|
+
modelId,
|
|
525
|
+
entryId
|
|
526
|
+
], { locale: options?.locale });
|
|
527
|
+
const body = {
|
|
528
|
+
author: submission.author,
|
|
529
|
+
body: submission.body
|
|
530
|
+
};
|
|
531
|
+
if (submission.parentId) body.parentId = submission.parentId;
|
|
532
|
+
if (submission.captchaToken) body.captchaToken = submission.captchaToken;
|
|
533
|
+
if (submission.honeypot !== void 0) body._hp = submission.honeypot;
|
|
534
|
+
return publicPost(url, body);
|
|
463
535
|
}
|
|
464
536
|
};
|
|
465
537
|
//#endregion
|
|
@@ -517,8 +589,11 @@ function createContentrain(config) {
|
|
|
517
589
|
media: () => new MediaAccessor(transport),
|
|
518
590
|
form: () => new FormsClient({
|
|
519
591
|
baseUrl: (config.baseUrl ?? "https://studio.contentrain.io/api/cdn/v1").replace("/cdn/v1", "/forms/v1"),
|
|
520
|
-
projectId: config.projectId
|
|
521
|
-
|
|
592
|
+
projectId: config.projectId
|
|
593
|
+
}),
|
|
594
|
+
comments: () => new CommentsClient({
|
|
595
|
+
baseUrl: (config.baseUrl ?? "https://studio.contentrain.io/api/cdn/v1").replace("/cdn/v1", "/comments/v1"),
|
|
596
|
+
projectId: config.projectId
|
|
522
597
|
}),
|
|
523
598
|
conversation: () => new ConversationClient({
|
|
524
599
|
baseUrl: (config.baseUrl ?? "https://studio.contentrain.io/api/cdn/v1").replace("/cdn/v1", "/conversation/v1"),
|
|
@@ -556,6 +631,12 @@ Object.defineProperty(exports, "CdnSingletonAccessor", {
|
|
|
556
631
|
return CdnSingletonAccessor;
|
|
557
632
|
}
|
|
558
633
|
});
|
|
634
|
+
Object.defineProperty(exports, "CommentsClient", {
|
|
635
|
+
enumerable: true,
|
|
636
|
+
get: function() {
|
|
637
|
+
return CommentsClient;
|
|
638
|
+
}
|
|
639
|
+
});
|
|
559
640
|
Object.defineProperty(exports, "ContentrainError", {
|
|
560
641
|
enumerable: true,
|
|
561
642
|
get: function() {
|
package/dist/cli.cjs
CHANGED
package/dist/cli.mjs
CHANGED