@mosetta/content 0.0.0-stage → 0.0.10
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/LICENSE +21 -0
- package/README.md +5 -2
- package/package.json +35 -3
- package/src/articleLists.ts +91 -0
- package/src/content.ts +16 -0
- package/src/editing.ts +1304 -0
- package/src/i18n.ts +177 -0
- package/src/media.ts +69 -0
- package/src/model.ts +300 -0
- package/src/names.ts +50 -0
- package/src/paths.ts +115 -0
- package/src/policy.ts +133 -0
- package/src/queryPolicy.ts +33 -0
- package/src/repository.ts +358 -0
- package/src/roots.ts +105 -0
- package/src/schemas.ts +122 -0
- package/src/transport.ts +142 -0
- package/src/units.ts +396 -0
- package/src/versions.ts +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mosetta contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @mosetta/content
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The content domain of mosetta: blueprints (pages and prefabs as JSON trees), their drafts and
|
|
4
|
+
revisions, the schemas they are checked against, the editing session, published markers and the
|
|
5
|
+
site's manifest of roots, translations (the i18n value references), media addresses and article
|
|
6
|
+
lists. Shared by the editor, the factory and the tools that write content.
|
package/package.json
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mosetta/content",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"version": "0.0.10",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "mosetta content domain: revisioned pages/prefabs model, zod schema factories, content transport (dev HTTP / prod S3 seam) and the revision state-machine repository. Web-API clean.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"src"
|
|
9
|
+
],
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/content.ts",
|
|
12
|
+
"./paths": "./src/paths.ts",
|
|
13
|
+
"./policy": "./src/policy.ts",
|
|
14
|
+
"./editing": "./src/editing.ts",
|
|
15
|
+
"./units": "./src/units.ts",
|
|
16
|
+
"./articleLists": "./src/articleLists.ts",
|
|
17
|
+
"./media": "./src/media.ts",
|
|
18
|
+
"./i18n": "./src/i18n.ts",
|
|
19
|
+
"./roots": "./src/roots.ts",
|
|
20
|
+
"./queryPolicy": "./src/queryPolicy.ts",
|
|
21
|
+
"./versions": "./src/versions.ts"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@mosetta/core": "0.0.10",
|
|
25
|
+
"@mosetta/di": "0.0.10",
|
|
26
|
+
"@mosetta/server-action": "0.0.10",
|
|
27
|
+
"@mosetta/stores": "0.0.10"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"zod": "^3"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"typecheck": "tsc --noEmit"
|
|
37
|
+
}
|
|
6
38
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// An ARTICLE LIST is a block whose data the server supplies: the page's tree names the block
|
|
2
|
+
// with a route prefix, and before a render the strategy fills its `entries` from the route table
|
|
3
|
+
// and the pages' own meta — no request from the browser, nothing the client has to fetch through
|
|
4
|
+
// a foreign proxy. The block itself only renders what it was given. A blog-kit thing by origin,
|
|
5
|
+
// a generic "settings enriched on the server" seam by shape.
|
|
6
|
+
import type { IBlueprintNodeDoc, IPageMeta } from './model';
|
|
7
|
+
|
|
8
|
+
export type { IPageMeta } from './model';
|
|
9
|
+
import { textDigest, UNIT_ID_SEPARATOR } from './units';
|
|
10
|
+
|
|
11
|
+
/** the block any distribution ships under this name receives entries */
|
|
12
|
+
export const isArticleListReference = (reference: string): boolean => /_t_component_n_articleList_v_/.test(reference);
|
|
13
|
+
|
|
14
|
+
/** pages under this prefix are never articles (layouts) */
|
|
15
|
+
export const LAYOUTS_PREFIX = '/_layouts/';
|
|
16
|
+
|
|
17
|
+
export interface IArticleEntry {
|
|
18
|
+
readonly pattern: string;
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly title: string;
|
|
21
|
+
readonly description: string;
|
|
22
|
+
readonly image: string;
|
|
23
|
+
readonly date: string;
|
|
24
|
+
readonly category: string;
|
|
25
|
+
/** the title and description in the project's other languages — the block shows the reader's */
|
|
26
|
+
readonly i18n?: IPageMeta['i18n'];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface IArticleSource {
|
|
30
|
+
routes(): Promise<readonly { pattern: string; name: string; rev: number }[]>;
|
|
31
|
+
meta(name: string, rev: number): Promise<IPageMeta | null>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// the block filters by the reader's category and caps at render; the server hands it everything
|
|
35
|
+
// under the prefix up to this bound
|
|
36
|
+
const MAX_ENTRIES = 500;
|
|
37
|
+
|
|
38
|
+
/** the entries a list with these settings may show: under the prefix, newest first */
|
|
39
|
+
export async function articleEntries(settings: Record<string, unknown> | undefined, source: IArticleSource): Promise<IArticleEntry[]> {
|
|
40
|
+
// the prefix is a FOLDER: `/blog/` holds the articles, `/blog` itself is the index and is not one
|
|
41
|
+
const raw = typeof settings?.prefix === 'string' && settings.prefix.startsWith('/') ? settings.prefix : '/';
|
|
42
|
+
const prefix = raw.endsWith('/') ? raw : `${raw}/`;
|
|
43
|
+
const routes = (await source.routes())
|
|
44
|
+
.filter((r) => r.pattern.startsWith(prefix) && !r.pattern.startsWith(LAYOUTS_PREFIX));
|
|
45
|
+
// the metas are read side by side — a revision's is immutable, a source may keep it
|
|
46
|
+
const metas = await Promise.all(routes.map((route) => source.meta(route.name, route.rev)));
|
|
47
|
+
const entries: IArticleEntry[] = [];
|
|
48
|
+
for (const [i, route] of routes.entries()) {
|
|
49
|
+
const meta = metas[i] ?? {};
|
|
50
|
+
entries.push({
|
|
51
|
+
pattern: route.pattern,
|
|
52
|
+
name: route.name,
|
|
53
|
+
title: meta.title ?? route.name,
|
|
54
|
+
description: meta.description ?? '',
|
|
55
|
+
image: meta.image ?? '',
|
|
56
|
+
date: meta.date ?? '',
|
|
57
|
+
category: meta.category ?? '',
|
|
58
|
+
...(meta.i18n && typeof meta.i18n === 'object' && Object.keys(meta.i18n).length ? { i18n: meta.i18n } : {}),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
entries.sort((a, b) => (a.date === b.date ? a.title.localeCompare(b.title) : b.date.localeCompare(a.date)));
|
|
62
|
+
return entries.slice(0, MAX_ENTRIES);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** every article-list block in the tree gets its entries — the page's own blocks and a layout's chrome alike */
|
|
66
|
+
export async function enrichArticleLists(doc: IBlueprintNodeDoc, source: IArticleSource): Promise<IBlueprintNodeDoc> {
|
|
67
|
+
const walk = async (node: IBlueprintNodeDoc): Promise<IBlueprintNodeDoc> => {
|
|
68
|
+
const children = await Promise.all((node.children ?? []).map(walk));
|
|
69
|
+
if (!isArticleListReference(node.reference)) return { ...node, children };
|
|
70
|
+
return { ...node, children, settings: { ...(node.settings ?? {}), entries: await articleEntries(node.settings, source) } };
|
|
71
|
+
};
|
|
72
|
+
return walk(doc);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* What the tree's article lists were given, as a cache-key suffix (`~l<digest>`), '' without a
|
|
77
|
+
* list: a render keyed by its page's revision alone would keep showing yesterday's articles.
|
|
78
|
+
*/
|
|
79
|
+
export function articleListsCacheSuffix(doc: IBlueprintNodeDoc): string {
|
|
80
|
+
const given: unknown[] = [];
|
|
81
|
+
const walk = (node: IBlueprintNodeDoc) => {
|
|
82
|
+
if (isArticleListReference(node.reference)) given.push(node.settings?.entries ?? []);
|
|
83
|
+
for (const child of node.children ?? []) walk(child);
|
|
84
|
+
};
|
|
85
|
+
walk(doc);
|
|
86
|
+
return given.length ? `~l${textDigest(JSON.stringify(given)).toString(36)}` : '';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// keeps the separator import honest: entries never carry chrome ids, but a list inside a layout
|
|
90
|
+
// is addressed through one — the block reads settings, not ids, so nothing here depends on it
|
|
91
|
+
void UNIT_ID_SEPARATOR;
|
package/src/content.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// @mosetta/content — the content domain: revisioned pages/prefabs model, zod schema
|
|
2
|
+
// factories (zod injected — zero runtime deps), the content transport seam (dev HTTP / prod S3) and
|
|
3
|
+
// the revision state-machine repository. Web-API clean: safe to import from an isolate, node, or the browser.
|
|
4
|
+
export * from './model';
|
|
5
|
+
export * from './paths';
|
|
6
|
+
export * from './schemas';
|
|
7
|
+
export * from './transport';
|
|
8
|
+
export * from './repository';
|
|
9
|
+
export * from './editing';
|
|
10
|
+
export * from './units';
|
|
11
|
+
export * from './articleLists';
|
|
12
|
+
export * from './media';
|
|
13
|
+
export * from './i18n';
|
|
14
|
+
export * from './versions';
|
|
15
|
+
export * from './names';
|
|
16
|
+
export * from './roots';
|