@karmicsoft/lc-schema 0.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/LICENSE +15 -0
- package/README.md +63 -0
- package/example.config.yml +33 -0
- package/index.d.ts +56 -0
- package/index.js +85 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LightCode Platform License — Community, Educational, and Partnership Use
|
|
2
|
+
© KarmicSoft. All rights reserved.
|
|
3
|
+
|
|
4
|
+
This package (@karmicsoft/lc-schema) is part of the LightCode platform components
|
|
5
|
+
developed by KarmicSoft (Erasmus+ project "Strengthening the Digital Transformation
|
|
6
|
+
of Higher Education Through Low-Code", No. 2022-1-FR01-KA220-HED-000086863).
|
|
7
|
+
|
|
8
|
+
Free access and use are granted for educational and non-commercial academic
|
|
9
|
+
purposes, and for LightCode partner and collaboration projects. Use outside this
|
|
10
|
+
scope — commercial activities, professional services, or proprietary integrations —
|
|
11
|
+
requires a separate commercial agreement.
|
|
12
|
+
|
|
13
|
+
Full terms: see licence.md in the lightcodepedia repository
|
|
14
|
+
(https://lightcodepedia.org). Contact KarmicSoft for a partnership or commercial
|
|
15
|
+
license.
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @karmicsoft/lc-schema
|
|
2
|
+
|
|
3
|
+
Schema → **neutral IR** → widget definitions — a small **schema compiler** and a
|
|
4
|
+
**LightCode** brick. © KarmicSoft (see `LICENSE`).
|
|
5
|
+
|
|
6
|
+
## Why (the SSOT strategy)
|
|
7
|
+
|
|
8
|
+
Keep **one canonical schema** and generate the rest, instead of hand-maintaining
|
|
9
|
+
several descriptions of the same model (which drift). `lc-schema` is the compiler:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
CANONICAL SOURCE ─(reader)→ neutral IR ─(consumers/emitters)→ { lc widgets · JSON Schema · config.yml · … }
|
|
13
|
+
(your choice) (LightCode)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The **IR decouples the choice of canonical source**: switching Zod ↔ config.yml
|
|
17
|
+
later changes only the *reader*; consumers (the editor) are unaffected — **reversible,
|
|
18
|
+
no lock-in**.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npm install @karmicsoft/lc-schema
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## API (v0.1)
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
import { fromSveltiaConfig, widgets, collections } from '@karmicsoft/lc-schema';
|
|
30
|
+
|
|
31
|
+
const ir = fromSveltiaConfig(readFileSync('public/admin/config.yml', 'utf8'));
|
|
32
|
+
collections(ir); // ["persons", "events", ...]
|
|
33
|
+
widgets(ir, 'persons'); // flat widget list the lc-editor renders
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Neutral IR shape
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
{ irVersion, source, collections: [
|
|
40
|
+
{ name, label, folder, extension, identifier, fields: [ Field ] }
|
|
41
|
+
]}
|
|
42
|
+
Field = { name, label, widget, required, hint?, default?,
|
|
43
|
+
options? // select: [{label, value}]
|
|
44
|
+
collection?, multiple?, valueField?, displayField? // relation
|
|
45
|
+
item? // list of scalars
|
|
46
|
+
fields? // object / objectlist
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Supported widgets: `string · text · number · boolean · select · relation · list ·
|
|
51
|
+
objectlist · object · markdown · image · file · hidden`.
|
|
52
|
+
|
|
53
|
+
## SSOT — canonical source, your call (reversible)
|
|
54
|
+
|
|
55
|
+
- **v0.1 reader: Sveltia/Decap `config.yml`** (already in your repo).
|
|
56
|
+
- **Recommended canonical: your Zod `content.config.ts`** — it is your build authority
|
|
57
|
+
and the richest (`reference()`, refinements, `nullSafe`, `dateStr`); `config.yml` is a
|
|
58
|
+
*subset*. With a Zod reader (roadmap), `config.yml` becomes **generated** and the
|
|
59
|
+
duplication disappears.
|
|
60
|
+
- **If you keep `config.yml` canonical:** add a CI check that it stays consistent with
|
|
61
|
+
the Zod (which carries semantics `config.yml` can't).
|
|
62
|
+
|
|
63
|
+
Either way, consumers read the **IR**, so the decision is reversible.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Minimal Sveltia/Decap config excerpt — feed it to fromSveltiaConfig().
|
|
2
|
+
collections:
|
|
3
|
+
- name: persons
|
|
4
|
+
label: Personnages
|
|
5
|
+
folder: content/persons
|
|
6
|
+
extension: yaml
|
|
7
|
+
identifier_field: title
|
|
8
|
+
fields:
|
|
9
|
+
- { name: title, label: Nom affiché, widget: string }
|
|
10
|
+
- { name: gender, label: Genre, widget: select, required: false,
|
|
11
|
+
options: [masculin, féminin, non-binaire, inconnu] }
|
|
12
|
+
- name: professions
|
|
13
|
+
label: Professions
|
|
14
|
+
widget: list
|
|
15
|
+
required: false
|
|
16
|
+
field: { name: profession, widget: string }
|
|
17
|
+
- name: periods
|
|
18
|
+
label: Époques
|
|
19
|
+
widget: relation
|
|
20
|
+
collection: periods
|
|
21
|
+
multiple: true
|
|
22
|
+
search_fields: [title]
|
|
23
|
+
value_field: '{{slug}}'
|
|
24
|
+
display_fields: [title]
|
|
25
|
+
- name: addresses
|
|
26
|
+
label: Adresses
|
|
27
|
+
widget: list
|
|
28
|
+
required: false
|
|
29
|
+
fields:
|
|
30
|
+
- { name: id, label: Adresse, widget: relation, collection: addresses,
|
|
31
|
+
value_field: '{{slug}}', display_fields: [title] }
|
|
32
|
+
- { name: role, label: Rôle, widget: string, required: false }
|
|
33
|
+
- { name: body, label: Biographie, widget: markdown }
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @karmicsoft/lc-schema — schema → neutral IR → widget definitions.
|
|
3
|
+
* © KarmicSoft — LightCode.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const IR_VERSION: string;
|
|
7
|
+
|
|
8
|
+
export interface IROption { label: string; value: unknown; }
|
|
9
|
+
|
|
10
|
+
export interface IRField {
|
|
11
|
+
name: string;
|
|
12
|
+
label: string;
|
|
13
|
+
/** string | text | number | boolean | select | relation | list | objectlist | object | markdown | image | file | hidden */
|
|
14
|
+
widget: string;
|
|
15
|
+
required?: boolean;
|
|
16
|
+
hint?: string;
|
|
17
|
+
default?: unknown;
|
|
18
|
+
/** select / relation */
|
|
19
|
+
multiple?: boolean;
|
|
20
|
+
/** select */
|
|
21
|
+
options?: IROption[];
|
|
22
|
+
/** relation */
|
|
23
|
+
collection?: string;
|
|
24
|
+
valueField?: string;
|
|
25
|
+
displayField?: string;
|
|
26
|
+
searchFields?: string[];
|
|
27
|
+
/** object / objectlist */
|
|
28
|
+
fields?: IRField[];
|
|
29
|
+
/** list of scalars */
|
|
30
|
+
item?: IRField;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface IRCollection {
|
|
34
|
+
name: string;
|
|
35
|
+
label: string;
|
|
36
|
+
folder?: string;
|
|
37
|
+
extension?: string;
|
|
38
|
+
identifier?: string;
|
|
39
|
+
slug?: string;
|
|
40
|
+
fields: IRField[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface IR {
|
|
44
|
+
irVersion: string;
|
|
45
|
+
source: string;
|
|
46
|
+
collections: IRCollection[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Compile a Sveltia/Decap config.yml (string or parsed object) into the neutral IR. */
|
|
50
|
+
export function fromSveltiaConfig(config: string | object): IR;
|
|
51
|
+
|
|
52
|
+
/** The flat field list for one collection (throws if the collection is unknown). */
|
|
53
|
+
export function widgets(ir: IR, collectionName: string): IRField[];
|
|
54
|
+
|
|
55
|
+
/** All collection names in the IR. */
|
|
56
|
+
export function collections(ir: IR): string[];
|
package/index.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* @karmicsoft/lc-schema — schema → neutral IR → widget definitions.
|
|
3
|
+
* © KarmicSoft — LightCode. See LICENSE.
|
|
4
|
+
*
|
|
5
|
+
* SSOT strategy: keep ONE canonical schema and generate the rest. This is a
|
|
6
|
+
* small "schema compiler": a reader turns your canonical source into a neutral
|
|
7
|
+
* IR; the lc-* editor (and other back-ends) consume the IR. Swapping the
|
|
8
|
+
* canonical source later changes only the reader — consumers are unaffected.
|
|
9
|
+
*
|
|
10
|
+
* v0.1 ships the Sveltia/Decap `config.yml` reader (config is already in your
|
|
11
|
+
* repo). A Zod reader + projections (config.yml, JSON Schema) can be added
|
|
12
|
+
* without touching consumers — see README § SSOT.
|
|
13
|
+
*/
|
|
14
|
+
import yaml from 'js-yaml';
|
|
15
|
+
|
|
16
|
+
export const IR_VERSION = '0.1';
|
|
17
|
+
|
|
18
|
+
// Sveltia/Decap widget → neutral IR widget.
|
|
19
|
+
const WIDGET = {
|
|
20
|
+
string: 'string', text: 'text', number: 'number', boolean: 'boolean',
|
|
21
|
+
select: 'select', relation: 'relation', list: 'list', object: 'object',
|
|
22
|
+
markdown: 'markdown', image: 'image', file: 'file', hidden: 'hidden',
|
|
23
|
+
datetime: 'string', code: 'text',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** Parse a Sveltia/Decap config.yml (string or object) into the neutral IR. */
|
|
27
|
+
export function fromSveltiaConfig(config) {
|
|
28
|
+
const cfg = typeof config === 'string' ? yaml.load(config, { schema: yaml.CORE_SCHEMA }) : config;
|
|
29
|
+
if (!cfg || !Array.isArray(cfg.collections)) throw new Error('lc-schema: not a Sveltia/Decap config (missing collections[])');
|
|
30
|
+
return { irVersion: IR_VERSION, source: 'sveltia', collections: cfg.collections.map(mapCollection) };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function mapCollection(c) {
|
|
34
|
+
return {
|
|
35
|
+
name: c.name,
|
|
36
|
+
label: c.label || c.name,
|
|
37
|
+
folder: c.folder,
|
|
38
|
+
extension: c.extension || 'yaml',
|
|
39
|
+
identifier: c.identifier_field || 'title',
|
|
40
|
+
slug: c.slug,
|
|
41
|
+
fields: (c.fields || []).map(mapField),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function mapField(f) {
|
|
46
|
+
let w = WIDGET[f.widget] || 'string';
|
|
47
|
+
const out = { name: f.name, label: f.label || prettify(f.name), widget: w };
|
|
48
|
+
out.required = f.required !== false;
|
|
49
|
+
if (f.hint) out.hint = f.hint;
|
|
50
|
+
if (f.default !== undefined) out.default = f.default;
|
|
51
|
+
|
|
52
|
+
if (w === 'select') {
|
|
53
|
+
out.multiple = !!f.multiple;
|
|
54
|
+
out.options = (f.options || []).map((o) =>
|
|
55
|
+
(o && typeof o === 'object') ? { label: o.label ?? String(o.value), value: o.value }
|
|
56
|
+
: { label: String(o), value: o });
|
|
57
|
+
} else if (w === 'relation') {
|
|
58
|
+
out.collection = f.collection;
|
|
59
|
+
out.multiple = !!f.multiple;
|
|
60
|
+
out.valueField = f.value_field || '{{slug}}';
|
|
61
|
+
out.displayField = firstOf(f.display_fields) || firstOf(f.search_fields) || 'title';
|
|
62
|
+
out.searchFields = asArray(f.search_fields);
|
|
63
|
+
} else if (w === 'list') {
|
|
64
|
+
if (Array.isArray(f.fields)) { out.widget = 'objectlist'; out.fields = f.fields.map(mapField); }
|
|
65
|
+
else out.item = f.field ? mapField(f.field) : { name: 'value', widget: 'string' };
|
|
66
|
+
} else if (w === 'object') {
|
|
67
|
+
out.fields = (f.fields || []).map(mapField);
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Flat widget list for the editor, for one collection. */
|
|
73
|
+
export function widgets(ir, collectionName) {
|
|
74
|
+
const c = ir.collections.find((c) => c.name === collectionName);
|
|
75
|
+
if (!c) throw new Error(`lc-schema: unknown collection "${collectionName}"`);
|
|
76
|
+
return c.fields;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** All collection names in the IR. */
|
|
80
|
+
export function collections(ir) { return ir.collections.map((c) => c.name); }
|
|
81
|
+
|
|
82
|
+
const isArr = Array.isArray;
|
|
83
|
+
function firstOf(a) { return isArr(a) ? a[0] : a; }
|
|
84
|
+
function asArray(a) { return isArr(a) ? a : (a == null ? [] : [a]); }
|
|
85
|
+
function prettify(s) { return String(s || '').replace(/[-_]/g, ' ').replace(/\b\w/g, (m) => m.toUpperCase()); }
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@karmicsoft/lc-schema",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Schema → neutral IR → widget definitions (schema compiler) — a LightCode brick.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"import": "./index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"index.js",
|
|
16
|
+
"index.d.ts",
|
|
17
|
+
"example.config.yml",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "node test.mjs"
|
|
26
|
+
},
|
|
27
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
28
|
+
"author": "KarmicSoft",
|
|
29
|
+
"homepage": "https://lightcodepedia.org",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/michelzam/lightcodepedia",
|
|
33
|
+
"directory": "packages/lc-schema"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"js-yaml": "^4.3.0"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"schema",
|
|
43
|
+
"low-code",
|
|
44
|
+
"forms",
|
|
45
|
+
"sveltia",
|
|
46
|
+
"zod",
|
|
47
|
+
"lightcode",
|
|
48
|
+
"karmicsoft"
|
|
49
|
+
]
|
|
50
|
+
}
|