@coffer-org/sdk 1.1.0 → 1.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/dist/color-names.d.ts +0 -1
- package/dist/color-names.js +0 -1
- package/dist/condition.d.ts +3 -19
- package/dist/condition.js +50 -69
- package/dist/countries.d.ts +0 -2
- package/dist/countries.js +0 -1
- package/dist/currencies.d.ts +0 -10
- package/dist/currencies.js +0 -10
- package/dist/extend.d.ts +3 -22
- package/dist/extend.js +2 -6
- package/dist/field-helpers.d.ts +0 -7
- package/dist/field-helpers.js +0 -16
- package/dist/field-presets.d.ts +55 -87
- package/dist/field-presets.js +116 -108
- package/dist/fields/constants.d.ts +36 -0
- package/dist/fields/constants.js +18 -0
- package/dist/fields/normalize.d.ts +84 -0
- package/dist/fields/normalize.js +47 -0
- package/dist/fields/url.d.ts +13 -0
- package/dist/fields/url.js +49 -0
- package/dist/fields/validation.d.ts +30 -0
- package/dist/fields/validation.js +57 -0
- package/dist/fields.d.ts +222 -332
- package/dist/fields.js +435 -351
- package/dist/index.d.ts +14 -16
- package/dist/index.js +14 -16
- package/dist/module.d.ts +13 -103
- package/dist/module.js +51 -83
- package/dist/package.d.ts +3 -20
- package/dist/package.js +14 -19
- package/dist/plugin.d.ts +5 -45
- package/dist/plugin.js +5 -19
- package/dist/settings.d.ts +1 -2
- package/dist/settings.js +0 -1
- package/dist/units.d.ts +0 -1
- package/dist/units.js +0 -5
- package/dist/vault.d.ts +3 -8
- package/dist/vault.js +4 -1
- package/package.json +5 -1
- package/dist/color-names.d.ts.map +0 -1
- package/dist/color-names.js.map +0 -1
- package/dist/condition.d.ts.map +0 -1
- package/dist/condition.js.map +0 -1
- package/dist/countries.d.ts.map +0 -1
- package/dist/countries.js.map +0 -1
- package/dist/currencies.d.ts.map +0 -1
- package/dist/currencies.js.map +0 -1
- package/dist/extend.d.ts.map +0 -1
- package/dist/extend.js.map +0 -1
- package/dist/field-helpers.d.ts.map +0 -1
- package/dist/field-helpers.js.map +0 -1
- package/dist/field-presets.d.ts.map +0 -1
- package/dist/field-presets.js.map +0 -1
- package/dist/fields.d.ts.map +0 -1
- package/dist/fields.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/module.d.ts.map +0 -1
- package/dist/module.js.map +0 -1
- package/dist/package.d.ts.map +0 -1
- package/dist/package.js.map +0 -1
- package/dist/plugin.d.ts.map +0 -1
- package/dist/plugin.js.map +0 -1
- package/dist/settings.d.ts.map +0 -1
- package/dist/settings.js.map +0 -1
- package/dist/units.d.ts.map +0 -1
- package/dist/units.js.map +0 -1
- package/dist/vault.d.ts.map +0 -1
- package/dist/vault.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './
|
|
6
|
-
export * from './
|
|
7
|
-
export * from './
|
|
8
|
-
export * from './
|
|
9
|
-
export * from './
|
|
10
|
-
export * from './
|
|
11
|
-
export * from './
|
|
12
|
-
export * from './
|
|
13
|
-
export * from './
|
|
14
|
-
export * from './
|
|
15
|
-
export * from './settings.js';
|
|
16
|
-
//# sourceMappingURL=index.d.ts.map
|
|
1
|
+
export * from './field-helpers.ts';
|
|
2
|
+
export * from './fields.ts';
|
|
3
|
+
export * from './field-presets.ts';
|
|
4
|
+
export * from './module.ts';
|
|
5
|
+
export * from './vault.ts';
|
|
6
|
+
export * from './extend.ts';
|
|
7
|
+
export * from './package.ts';
|
|
8
|
+
export * from './plugin.ts';
|
|
9
|
+
export * from './condition.ts';
|
|
10
|
+
export * from './countries.ts';
|
|
11
|
+
export * from './color-names.ts';
|
|
12
|
+
export * from './units.ts';
|
|
13
|
+
export * from './currencies.ts';
|
|
14
|
+
export * from './settings.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
7
|
-
export * from
|
|
8
|
-
export * from
|
|
9
|
-
export * from
|
|
10
|
-
export * from
|
|
11
|
-
export * from
|
|
12
|
-
export * from
|
|
13
|
-
export * from
|
|
14
|
-
export * from
|
|
15
|
-
export * from './settings.js';
|
|
16
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
export * from "./field-helpers.js";
|
|
2
|
+
export * from "./fields.js";
|
|
3
|
+
export * from "./field-presets.js";
|
|
4
|
+
export * from "./module.js";
|
|
5
|
+
export * from "./vault.js";
|
|
6
|
+
export * from "./extend.js";
|
|
7
|
+
export * from "./package.js";
|
|
8
|
+
export * from "./plugin.js";
|
|
9
|
+
export * from "./condition.js";
|
|
10
|
+
export * from "./countries.js";
|
|
11
|
+
export * from "./color-names.js";
|
|
12
|
+
export * from "./units.js";
|
|
13
|
+
export * from "./currencies.js";
|
|
14
|
+
export * from "./settings.js";
|
package/dist/module.d.ts
CHANGED
|
@@ -1,48 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import type { FieldMeta, LayoutEl } from './fields.
|
|
3
|
-
import type { FieldClient, GroupEl, PseudoEl } from './fields.
|
|
4
|
-
/**
|
|
5
|
-
* Ієрархія: vault → module → type (в контенті)
|
|
6
|
-
*
|
|
7
|
-
* - vault: namespace проекту ('things', 'health')
|
|
8
|
-
* - module: ідентифікатор схеми + таблиці ('item', 'vitamin') ← це поле
|
|
9
|
-
* - type: класифікація запису в контенті ('device', 'drone') ← поле в сутності
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* Три контексти відображення запису. Кожен модуль може оголосити, які поля
|
|
13
|
-
* показувати в кожному з них:
|
|
14
|
-
*
|
|
15
|
-
* 1. inline — компактне представлення ОДНОГО запису в маленькому місці:
|
|
16
|
-
* привязка людини (ім'я + прізвище), чип, елемент списку зв'язків.
|
|
17
|
-
* Може містити image/images-поле → показується як аватар + текст.
|
|
18
|
-
* Default: ['name'].
|
|
19
|
-
*
|
|
20
|
-
* 2. table — колонки таблиці-списку, де не влазять усі поля.
|
|
21
|
-
* Default: явний views.table (без евристики).
|
|
22
|
-
*
|
|
23
|
-
* 3. full — усі поля (неявно). Сторінка запису показує все.
|
|
24
|
-
*/
|
|
2
|
+
import type { FieldMeta, LayoutEl, ColumnType } from './fields.ts';
|
|
3
|
+
import type { FieldClient, GroupEl, PseudoEl } from './fields.ts';
|
|
25
4
|
export interface ModuleViews {
|
|
26
|
-
/** Явний ключ поля-заголовка запису. Якщо не заданий — резолвиться (див. titleKey). */
|
|
27
5
|
title?: string;
|
|
28
|
-
/** Компактне представлення одного запису. Напр. ['logo','name','surname']. */
|
|
29
6
|
inline?: string[];
|
|
30
|
-
/** Колонки таблиці-списку. Напр. ['name','price','inStock']. */
|
|
31
7
|
table?: string[];
|
|
32
|
-
/**
|
|
33
|
-
* Поля-кандидати для групувальної панелі списку (override авто-евристики).
|
|
34
|
-
* Список і порядок використовуються як є. Напр. ['type','brand'].
|
|
35
|
-
*/
|
|
36
8
|
groupBy?: string[];
|
|
37
|
-
/** Поле групування, обране у панелі за замовчуванням. Напр. 'type'. */
|
|
38
9
|
defaultGroupBy?: string;
|
|
39
|
-
/**
|
|
40
|
-
* Рендер запису як опції select (record-джерело). Трійка:
|
|
41
|
-
* icon — ключ поля-іконки (image→avatar, select-зі-swatch→колір, інакше icon)
|
|
42
|
-
* title — ключ або масив ключів; рендериться Inline-логікою
|
|
43
|
-
* subtitle — ключ поля-технічного значення
|
|
44
|
-
* Відсутній → fallback: title = inline-ключі, icon = inline-image, без subtitle.
|
|
45
|
-
*/
|
|
46
10
|
select?: {
|
|
47
11
|
icon?: string;
|
|
48
12
|
title: string | string[];
|
|
@@ -55,102 +19,49 @@ export interface ModuleDef {
|
|
|
55
19
|
label: string;
|
|
56
20
|
icon?: string;
|
|
57
21
|
claude?: string;
|
|
58
|
-
/**
|
|
59
|
-
* Якщо false — модуль не відображається в навігації.
|
|
60
|
-
* Використовується для sub-модулів (рейси, проживання тощо),
|
|
61
|
-
* які існують тільки як зв'язані записи батьківського модуля.
|
|
62
|
-
* За замовчуванням: true.
|
|
63
|
-
*/
|
|
64
22
|
standalone?: boolean;
|
|
65
|
-
/** Набори полів для трьох контекстів відображення (inline / table / full). */
|
|
66
23
|
views?: ModuleViews;
|
|
67
24
|
fields: LayoutEl[];
|
|
68
25
|
}
|
|
69
|
-
/** Плоскі пари [storageKey, FieldMeta] колонок батьківської таблиці.
|
|
70
|
-
* layout-група → плоско; embedded-група → префікс `key__`; collection → пропуск. */
|
|
71
26
|
export declare function fieldEntries(items: LayoutEl[]): [string, FieldMeta][];
|
|
27
|
+
export declare function storageColumns(key: string, field: FieldMeta): [string, ColumnType][];
|
|
72
28
|
export interface CollectionEntry {
|
|
73
|
-
/** Storage-ключ collection-групи (ім'я під child-таблицю). */
|
|
74
29
|
key: string;
|
|
75
30
|
group: GroupEl;
|
|
76
31
|
}
|
|
77
|
-
/** Усі collection-групи (рекурсивно крізь layout та embedded). Top-level зверху. */
|
|
78
32
|
export declare function collectionGroups(items: LayoutEl[]): CollectionEntry[];
|
|
79
|
-
/** Мапа key → FieldMeta (кеш по масиву). */
|
|
80
33
|
export declare function fieldMap(items: LayoutEl[]): Record<string, FieldMeta>;
|
|
81
34
|
export declare function defineModule(m: ModuleDef): ModuleDef;
|
|
82
35
|
export declare const SYSTEM_FIELDS: readonly ["id", "createdAt", "updatedAt"];
|
|
83
|
-
/**
|
|
84
|
-
* Контракт реальної сутності:
|
|
85
|
-
* id — uuid (ЄДИНЕ обов'язкове системне поле, авто).
|
|
86
|
-
* Решта (включно з name) — звичайні поля у ModuleDef.fields.
|
|
87
|
-
* created_at/updated_at — системна інфраструктура (поза fields, авто-керована).
|
|
88
|
-
*/
|
|
89
|
-
/**
|
|
90
|
-
* Ключ поля-заголовка запису (таблиці, пікери зв'язків, інлайни, заголовок форми).
|
|
91
|
-
* Пріоритет: views.title → перший не-image inline → 'name' (якщо є) →
|
|
92
|
-
* перше текстове поле → 'id'.
|
|
93
|
-
*/
|
|
94
36
|
export declare function titleKey(m: ModuleDef): string;
|
|
95
|
-
/** Заголовок запису (значення поля titleKey, fallback id). */
|
|
96
37
|
export declare function recordTitle(m: ModuleDef, record: Record<string, unknown>): string;
|
|
97
38
|
export declare const isImageKind: (kind: string) => boolean;
|
|
98
|
-
/** Ключі текстових колонок модуля (придатних для LIKE-пошуку). Виключає virtual, image, relation. */
|
|
99
39
|
export declare function textSearchKeys(m: ModuleDef): string[];
|
|
100
|
-
/**
|
|
101
|
-
* Поля для компактного представлення одного запису (привязка / чип / елемент списку).
|
|
102
|
-
* Пріоритет: views.inline → [titleKey(m)].
|
|
103
|
-
*/
|
|
104
40
|
export declare function inlineKeys(m: ModuleDef): string[];
|
|
105
|
-
/** Перше image-поле серед inline-полів (аватар у компактному представленні). */
|
|
106
41
|
export declare function inlineImageKey(m: ModuleDef): string | undefined;
|
|
107
|
-
/** Текстова частина компактного представлення (без зображень), напр. "Іван Петренко". */
|
|
108
42
|
export declare function inlineText(m: ModuleDef, row: Record<string, unknown>): string;
|
|
109
|
-
/**
|
|
110
|
-
* Колонки для таблиці-списку.
|
|
111
|
-
* Єдине джерело: views.table. Без евристик і showInTable.
|
|
112
|
-
*/
|
|
113
43
|
export declare function tableKeys(m: ModuleDef): string[];
|
|
114
|
-
/**
|
|
115
|
-
* Поля-кандидати для групувальної панелі списку.
|
|
116
|
-
* Пріоритет: views.groupBy (список і порядок як є, лише реальні не-virtual поля).
|
|
117
|
-
* Інакше евристика: select | checkbox | relation + текстові поля. Текстові з
|
|
118
|
-
* завеликою кардинальністю відсіює клієнт за фактичними даними (core про дані
|
|
119
|
-
* не знає).
|
|
120
|
-
*/
|
|
121
44
|
export declare function groupableFields(m: ModuleDef): string[];
|
|
122
|
-
/**
|
|
123
|
-
* Поле групування за замовчуванням (обране у панелі при відкритті).
|
|
124
|
-
* Пріоритет: views.defaultGroupBy (якщо реальне) → перший із groupableFields.
|
|
125
|
-
*/
|
|
126
45
|
export declare function defaultGroupField(m: ModuleDef): string | undefined;
|
|
127
|
-
/**
|
|
128
|
-
* Спільна побудова zod-shape з layout-полів (virtual пропускаються).
|
|
129
|
-
* partial=true → кожне поле опціональне (для PATCH-семантики оновлення).
|
|
130
|
-
*
|
|
131
|
-
* Примітка: для optional-полів zod уже опціоналізований (optionalize),
|
|
132
|
-
* для required та boolean (default) `.optional()` обов'язковий у partial-режимі;
|
|
133
|
-
* повторний `.optional()` на вже-опціональній схемі — ідемпотентний no-op,
|
|
134
|
-
* тому застосовуємо його до всіх полів у partial-режимі (поведінка незмінна).
|
|
135
|
-
*/
|
|
136
|
-
/** zod-shape запису: скаляри/layout плоско; embedded → nested object; collection → array. */
|
|
137
46
|
export declare function buildShape(fields: LayoutEl[], partial: boolean): z.ZodRawShape;
|
|
138
|
-
export declare function buildZodObject(m: ModuleDef): z.
|
|
139
|
-
export declare function buildZodObjectPartial(m: ModuleDef): z.
|
|
47
|
+
export declare function buildZodObject(m: ModuleDef): z.ZodTypeAny;
|
|
48
|
+
export declare function buildZodObjectPartial(m: ModuleDef): z.ZodTypeAny;
|
|
140
49
|
export interface StaticElClient {
|
|
141
50
|
el: 'static';
|
|
142
51
|
type: FieldClient;
|
|
143
52
|
value: unknown;
|
|
53
|
+
computed?: boolean;
|
|
144
54
|
}
|
|
145
|
-
export type
|
|
146
|
-
key: string;
|
|
147
|
-
type: FieldClient;
|
|
148
|
-
} | (Omit<GroupEl, 'fields'> & {
|
|
55
|
+
export type GroupElClient = Omit<GroupEl, 'fields'> & {
|
|
149
56
|
fields: ({
|
|
150
57
|
key: string;
|
|
151
58
|
type: FieldClient;
|
|
152
|
-
} | StaticElClient | PseudoEl)[];
|
|
153
|
-
}
|
|
59
|
+
} | StaticElClient | GroupElClient | PseudoEl)[];
|
|
60
|
+
};
|
|
61
|
+
export type LayoutElClient = {
|
|
62
|
+
key: string;
|
|
63
|
+
type: FieldClient;
|
|
64
|
+
} | GroupElClient | StaticElClient | PseudoEl;
|
|
154
65
|
export declare function layoutToClient(items: LayoutEl[]): LayoutElClient[];
|
|
155
66
|
export declare function moduleToClient(m: ModuleDef): {
|
|
156
67
|
module: string;
|
|
@@ -161,4 +72,3 @@ export declare function moduleToClient(m: ModuleDef): {
|
|
|
161
72
|
standalone: boolean | undefined;
|
|
162
73
|
fields: LayoutElClient[];
|
|
163
74
|
};
|
|
164
|
-
//# sourceMappingURL=module.d.ts.map
|
package/dist/module.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { toClient, isField, isGroup, isStatic, isCollectionGroup, isEmbeddedGroup } from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* layout-група → плоско; embedded-група → префікс `key__`; collection → пропуск. */
|
|
2
|
+
import { toClient, isField, isGroup, isStatic, isCollectionGroup, isEmbeddedGroup } from "./fields.js";
|
|
3
|
+
import { resolveFlag } from "./condition.js";
|
|
4
|
+
import { parseUrl } from "./fields/url.js";
|
|
6
5
|
export function fieldEntries(items) {
|
|
7
6
|
const out = [];
|
|
8
7
|
for (const it of items) {
|
|
@@ -11,7 +10,7 @@ export function fieldEntries(items) {
|
|
|
11
10
|
}
|
|
12
11
|
else if (isGroup(it)) {
|
|
13
12
|
if (isCollectionGroup(it))
|
|
14
|
-
continue;
|
|
13
|
+
continue;
|
|
15
14
|
const prefix = it.key ? `${it.key}__` : '';
|
|
16
15
|
for (const [sub, fm] of fieldEntries(it.fields)) {
|
|
17
16
|
out.push([prefix + sub, fm]);
|
|
@@ -20,7 +19,11 @@ export function fieldEntries(items) {
|
|
|
20
19
|
}
|
|
21
20
|
return out;
|
|
22
21
|
}
|
|
23
|
-
|
|
22
|
+
export function storageColumns(key, field) {
|
|
23
|
+
if (field.columns)
|
|
24
|
+
return Object.entries(field.columns).map(([sub, col]) => [`${key}__${sub}`, col]);
|
|
25
|
+
return [[key, field.column]];
|
|
26
|
+
}
|
|
24
27
|
export function collectionGroups(items) {
|
|
25
28
|
const out = [];
|
|
26
29
|
for (const it of items) {
|
|
@@ -30,14 +33,12 @@ export function collectionGroups(items) {
|
|
|
30
33
|
out.push({ key: it.key, group: it });
|
|
31
34
|
}
|
|
32
35
|
else {
|
|
33
|
-
// layout або embedded — спускаємось глибше шукати collection
|
|
34
36
|
out.push(...collectionGroups(it.fields));
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
return out;
|
|
38
40
|
}
|
|
39
41
|
const mapCache = new WeakMap();
|
|
40
|
-
/** Мапа key → FieldMeta (кеш по масиву). */
|
|
41
42
|
export function fieldMap(items) {
|
|
42
43
|
let m = mapCache.get(items);
|
|
43
44
|
if (!m) {
|
|
@@ -50,24 +51,12 @@ export function defineModule(m) {
|
|
|
50
51
|
const keys = fieldEntries(m.fields).map(([k]) => k);
|
|
51
52
|
const dup = keys.find((k, i) => keys.indexOf(k) !== i);
|
|
52
53
|
if (dup)
|
|
53
|
-
throw new Error(`[module] ${m.vault}/${m.module}:
|
|
54
|
-
// name — звичайне поле (оголошується у fields де треба); системне лише id.
|
|
54
|
+
throw new Error(`[module] ${m.vault}/${m.module}: duplicate key '${dup}'`);
|
|
55
55
|
if (m.standalone !== false && !m.views?.table?.length)
|
|
56
|
-
console.warn(`[module] ${m.vault}/${m.module}: standalone
|
|
56
|
+
console.warn(`[module] ${m.vault}/${m.module}: standalone module without an explicit views.table`);
|
|
57
57
|
return m;
|
|
58
58
|
}
|
|
59
59
|
export const SYSTEM_FIELDS = ['id', 'createdAt', 'updatedAt'];
|
|
60
|
-
/**
|
|
61
|
-
* Контракт реальної сутності:
|
|
62
|
-
* id — uuid (ЄДИНЕ обов'язкове системне поле, авто).
|
|
63
|
-
* Решта (включно з name) — звичайні поля у ModuleDef.fields.
|
|
64
|
-
* created_at/updated_at — системна інфраструктура (поза fields, авто-керована).
|
|
65
|
-
*/
|
|
66
|
-
/**
|
|
67
|
-
* Ключ поля-заголовка запису (таблиці, пікери зв'язків, інлайни, заголовок форми).
|
|
68
|
-
* Пріоритет: views.title → перший не-image inline → 'name' (якщо є) →
|
|
69
|
-
* перше текстове поле → 'id'.
|
|
70
|
-
*/
|
|
71
60
|
export function titleKey(m) {
|
|
72
61
|
if (m.views?.title)
|
|
73
62
|
return m.views.title;
|
|
@@ -82,40 +71,31 @@ export function titleKey(m) {
|
|
|
82
71
|
return firstText[0];
|
|
83
72
|
return 'id';
|
|
84
73
|
}
|
|
85
|
-
/** Заголовок запису (значення поля titleKey, fallback id). */
|
|
86
74
|
export function recordTitle(m, record) {
|
|
87
75
|
return String(record[titleKey(m)] ?? record['id'] ?? '');
|
|
88
76
|
}
|
|
89
|
-
/** Поля, які існують і не віртуальні (фільтр для view-наборів, заданих вручну). */
|
|
90
77
|
function realKeys(m, keys) {
|
|
91
78
|
const fm = fieldMap(m.fields);
|
|
92
79
|
return keys.filter((k) => fm[k] && !fm[k].virtual);
|
|
93
80
|
}
|
|
94
|
-
const IMAGE_KINDS = new Set(['image', 'media']);
|
|
81
|
+
const IMAGE_KINDS = new Set(['image', 'media', 'avatar', 'cover']);
|
|
95
82
|
export const isImageKind = (kind) => IMAGE_KINDS.has(kind);
|
|
96
83
|
const isTextPrim = (p) => p === 'string' || p === 'text';
|
|
97
|
-
/** Ключі текстових колонок модуля (придатних для LIKE-пошуку). Виключає virtual, image, relation. */
|
|
98
84
|
export function textSearchKeys(m) {
|
|
99
85
|
return fieldEntries(m.fields)
|
|
100
|
-
.filter(([, f]) => !f.virtual && isTextPrim(f.prim))
|
|
86
|
+
.filter(([, f]) => !f.virtual && f.kind !== 'password' && isTextPrim(f.prim))
|
|
101
87
|
.map(([k]) => k);
|
|
102
88
|
}
|
|
103
|
-
/**
|
|
104
|
-
* Поля для компактного представлення одного запису (привязка / чип / елемент списку).
|
|
105
|
-
* Пріоритет: views.inline → [titleKey(m)].
|
|
106
|
-
*/
|
|
107
89
|
export function inlineKeys(m) {
|
|
108
90
|
const ks = m.views?.inline?.length ? realKeys(m, m.views.inline) : [];
|
|
109
91
|
if (ks.length)
|
|
110
92
|
return ks;
|
|
111
93
|
return [titleKey(m)];
|
|
112
94
|
}
|
|
113
|
-
/** Перше image-поле серед inline-полів (аватар у компактному представленні). */
|
|
114
95
|
export function inlineImageKey(m) {
|
|
115
96
|
const fm = fieldMap(m.fields);
|
|
116
97
|
return inlineKeys(m).find((k) => fm[k] && isImageKind(fm[k].kind));
|
|
117
98
|
}
|
|
118
|
-
/** Текстова частина компактного представлення (без зображень), напр. "Іван Петренко". */
|
|
119
99
|
export function inlineText(m, row) {
|
|
120
100
|
const fm = fieldMap(m.fields);
|
|
121
101
|
const parts = inlineKeys(m).reduce((acc, k) => {
|
|
@@ -128,39 +108,21 @@ export function inlineText(m, row) {
|
|
|
128
108
|
}, []);
|
|
129
109
|
return parts.join(' ').trim() || recordTitle(m, row);
|
|
130
110
|
}
|
|
131
|
-
/**
|
|
132
|
-
* Колонки для таблиці-списку.
|
|
133
|
-
* Єдине джерело: views.table. Без евристик і showInTable.
|
|
134
|
-
*/
|
|
135
111
|
export function tableKeys(m) {
|
|
136
|
-
// Заголовок (titleKey) — завжди перша колонка; решта з views.table (без дублю).
|
|
137
112
|
const tk = titleKey(m);
|
|
138
113
|
const rest = m.views?.table?.length
|
|
139
114
|
? realKeys(m, m.views.table.filter((k) => k !== tk))
|
|
140
115
|
: [];
|
|
141
116
|
return [tk, ...rest];
|
|
142
117
|
}
|
|
143
|
-
// ─── Групування у панелі списку ──────────────────────────────────────────────
|
|
144
|
-
/** Примітиви, придатні для групування «з коробки» (категоріальні за типом). */
|
|
145
118
|
const GROUPABLE_PRIMS = new Set(['select', 'checkbox', 'relation']);
|
|
146
|
-
/**
|
|
147
|
-
* Поля-кандидати для групувальної панелі списку.
|
|
148
|
-
* Пріоритет: views.groupBy (список і порядок як є, лише реальні не-virtual поля).
|
|
149
|
-
* Інакше евристика: select | checkbox | relation + текстові поля. Текстові з
|
|
150
|
-
* завеликою кардинальністю відсіює клієнт за фактичними даними (core про дані
|
|
151
|
-
* не знає).
|
|
152
|
-
*/
|
|
153
119
|
export function groupableFields(m) {
|
|
154
120
|
if (m.views?.groupBy?.length)
|
|
155
121
|
return realKeys(m, m.views.groupBy);
|
|
156
122
|
return fieldEntries(m.fields)
|
|
157
|
-
.filter(([, f]) => !f.virtual &&
|
|
123
|
+
.filter(([, f]) => !f.virtual && GROUPABLE_PRIMS.has(f.prim))
|
|
158
124
|
.map(([k]) => k);
|
|
159
125
|
}
|
|
160
|
-
/**
|
|
161
|
-
* Поле групування за замовчуванням (обране у панелі при відкритті).
|
|
162
|
-
* Пріоритет: views.defaultGroupBy (якщо реальне) → перший із groupableFields.
|
|
163
|
-
*/
|
|
164
126
|
export function defaultGroupField(m) {
|
|
165
127
|
const dg = m.views?.defaultGroupBy;
|
|
166
128
|
if (dg) {
|
|
@@ -170,16 +132,6 @@ export function defaultGroupField(m) {
|
|
|
170
132
|
}
|
|
171
133
|
return groupableFields(m)[0];
|
|
172
134
|
}
|
|
173
|
-
/**
|
|
174
|
-
* Спільна побудова zod-shape з layout-полів (virtual пропускаються).
|
|
175
|
-
* partial=true → кожне поле опціональне (для PATCH-семантики оновлення).
|
|
176
|
-
*
|
|
177
|
-
* Примітка: для optional-полів zod уже опціоналізований (optionalize),
|
|
178
|
-
* для required та boolean (default) `.optional()` обов'язковий у partial-режимі;
|
|
179
|
-
* повторний `.optional()` на вже-опціональній схемі — ідемпотентний no-op,
|
|
180
|
-
* тому застосовуємо його до всіх полів у partial-режимі (поведінка незмінна).
|
|
181
|
-
*/
|
|
182
|
-
/** zod-shape запису: скаляри/layout плоско; embedded → nested object; collection → array. */
|
|
183
135
|
export function buildShape(fields, partial) {
|
|
184
136
|
const shape = {};
|
|
185
137
|
for (const it of fields) {
|
|
@@ -195,40 +147,57 @@ export function buildShape(fields, partial) {
|
|
|
195
147
|
shape[it.key] = partial ? arr.optional() : it.required ? arr : arr.optional();
|
|
196
148
|
}
|
|
197
149
|
else if (isEmbeddedGroup(it)) {
|
|
198
|
-
|
|
150
|
+
let inner = z.object(buildShape(it.fields, partial)).passthrough();
|
|
151
|
+
if (it.kind === 'url') {
|
|
152
|
+
inner = z.preprocess((v) => (typeof v === 'string' ? (parseUrl(v) ?? {}) : v), inner);
|
|
153
|
+
}
|
|
199
154
|
shape[it.key] = partial ? inner.optional() : it.required ? inner : inner.optional();
|
|
200
155
|
}
|
|
201
156
|
else {
|
|
202
|
-
// layout group — діти плоско в той самий shape
|
|
203
157
|
Object.assign(shape, buildShape(it.fields, partial));
|
|
204
158
|
}
|
|
205
159
|
}
|
|
206
160
|
}
|
|
207
161
|
return shape;
|
|
208
162
|
}
|
|
163
|
+
const isEmpty = (v) => v == null || v === '' || (Array.isArray(v) && v.length === 0);
|
|
164
|
+
function withConditionalRequired(base, m, partial) {
|
|
165
|
+
return base.superRefine((data, ctx) => {
|
|
166
|
+
for (const [key, f] of fieldEntries(m.fields)) {
|
|
167
|
+
if (f.virtual)
|
|
168
|
+
continue;
|
|
169
|
+
if (key.includes('__'))
|
|
170
|
+
continue;
|
|
171
|
+
if (partial && !(key in data))
|
|
172
|
+
continue;
|
|
173
|
+
if (f.required === true)
|
|
174
|
+
continue;
|
|
175
|
+
if (!resolveFlag(f.required, data))
|
|
176
|
+
continue;
|
|
177
|
+
if (isEmpty(data[key]))
|
|
178
|
+
ctx.addIssue({ code: 'custom', path: [key], message: JSON.stringify({ code: 'required' }) });
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
209
182
|
export function buildZodObject(m) {
|
|
210
|
-
return z.object(buildShape(m.fields, false));
|
|
183
|
+
return withConditionalRequired(z.object(buildShape(m.fields, false)), m, false);
|
|
211
184
|
}
|
|
212
185
|
export function buildZodObjectPartial(m) {
|
|
213
|
-
return z.object(buildShape(m.fields, true));
|
|
186
|
+
return withConditionalRequired(z.object(buildShape(m.fields, true)), m, true);
|
|
187
|
+
}
|
|
188
|
+
function elToClient(it) {
|
|
189
|
+
if (isField(it))
|
|
190
|
+
return { key: it.key, type: toClient(it.type) };
|
|
191
|
+
if (isStatic(it)) {
|
|
192
|
+
const fn = typeof it.value === 'function';
|
|
193
|
+
return { el: 'static', type: toClient(it.type), value: fn ? null : it.value, computed: fn || undefined };
|
|
194
|
+
}
|
|
195
|
+
if (isGroup(it))
|
|
196
|
+
return { ...it, fields: it.fields.map(elToClient) };
|
|
197
|
+
return it;
|
|
214
198
|
}
|
|
215
199
|
export function layoutToClient(items) {
|
|
216
|
-
return items.map(
|
|
217
|
-
if (isField(it))
|
|
218
|
-
return { key: it.key, type: toClient(it.type) };
|
|
219
|
-
if (isStatic(it))
|
|
220
|
-
return { el: 'static', type: toClient(it.type), value: it.value };
|
|
221
|
-
if (isGroup(it))
|
|
222
|
-
return {
|
|
223
|
-
...it,
|
|
224
|
-
fields: it.fields.map((s) => isField(s)
|
|
225
|
-
? { key: s.key, type: toClient(s.type) }
|
|
226
|
-
: isStatic(s)
|
|
227
|
-
? { el: 'static', type: toClient(s.type), value: s.value }
|
|
228
|
-
: s),
|
|
229
|
-
};
|
|
230
|
-
return it;
|
|
231
|
-
});
|
|
200
|
+
return items.map(elToClient);
|
|
232
201
|
}
|
|
233
202
|
export function moduleToClient(m) {
|
|
234
203
|
return {
|
|
@@ -241,4 +210,3 @@ export function moduleToClient(m) {
|
|
|
241
210
|
fields: layoutToClient(m.fields),
|
|
242
211
|
};
|
|
243
212
|
}
|
|
244
|
-
//# sourceMappingURL=module.js.map
|
package/dist/package.d.ts
CHANGED
|
@@ -1,30 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Кожен модуль-пакет (src/modules/<name>/index.ts) повинен виконувати default export
|
|
5
|
-
* типу ModulePackage. Реєстр збирає всі пакети через assemblePackages.
|
|
6
|
-
*
|
|
7
|
-
* Три точки розширення:
|
|
8
|
-
* vaults — нові vault'и, що визначаються цим пакетом
|
|
9
|
-
* vaultModules — ModuleDef'и, що ін'єктуються в vault'и інших пакетів
|
|
10
|
-
* extends_ — нові extends
|
|
11
|
-
*/
|
|
12
|
-
import type { VaultBundle } from './vault.js';
|
|
13
|
-
import type { ModuleDef } from './module.js';
|
|
14
|
-
import type { ExtendDef } from './extend.js';
|
|
1
|
+
import type { VaultBundle } from './vault.ts';
|
|
2
|
+
import type { ModuleDef } from './module.ts';
|
|
3
|
+
import type { ExtendDef } from './extend.ts';
|
|
15
4
|
export interface ModulePackage {
|
|
16
5
|
vaults?: VaultBundle[];
|
|
17
6
|
vaultModules?: ModuleDef[];
|
|
18
7
|
extends_?: ExtendDef[];
|
|
19
8
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Збирає пакети в єдиний реєстр.
|
|
22
|
-
* Порядок масиву важливий: vaultModules ін'єктуються тільки в vault'и,
|
|
23
|
-
* що вже оголошені в packages (попередніх або поточному).
|
|
24
|
-
*/
|
|
25
9
|
export declare function assemblePackages(packages: ModulePackage[]): {
|
|
26
10
|
vaults: VaultBundle[];
|
|
27
11
|
modules: ModuleDef[];
|
|
28
12
|
extends_: ExtendDef[];
|
|
29
13
|
};
|
|
30
|
-
//# sourceMappingURL=package.d.ts.map
|
package/dist/package.js
CHANGED
|
@@ -1,31 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
* Збирає пакети в єдиний реєстр.
|
|
3
|
-
* Порядок масиву важливий: vaultModules ін'єктуються тільки в vault'и,
|
|
4
|
-
* що вже оголошені в packages (попередніх або поточному).
|
|
5
|
-
*/
|
|
1
|
+
import { fallbackVault } from "./vault.js";
|
|
6
2
|
export function assemblePackages(packages) {
|
|
7
|
-
|
|
8
|
-
const vaultMap = new Map();
|
|
3
|
+
const metaMap = new Map();
|
|
9
4
|
for (const pkg of packages) {
|
|
10
5
|
for (const bundle of pkg.vaults ?? []) {
|
|
11
|
-
|
|
6
|
+
if (!metaMap.has(bundle.meta.id))
|
|
7
|
+
metaMap.set(bundle.meta.id, { ...bundle.meta });
|
|
12
8
|
}
|
|
13
9
|
}
|
|
14
|
-
|
|
10
|
+
const modules = [];
|
|
15
11
|
for (const pkg of packages) {
|
|
16
|
-
for (const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
bundle.modules.push(mod);
|
|
20
|
-
}
|
|
21
|
-
// Якщо vault не знайдено — пакет-залежність не підключено, ігноруємо
|
|
22
|
-
}
|
|
12
|
+
for (const bundle of pkg.vaults ?? [])
|
|
13
|
+
modules.push(...bundle.modules);
|
|
14
|
+
modules.push(...(pkg.vaultModules ?? []));
|
|
23
15
|
}
|
|
24
|
-
const
|
|
16
|
+
const ids = new Set([...metaMap.keys(), ...modules.map((m) => m.vault)]);
|
|
17
|
+
const vaults = [...ids].map((id) => ({
|
|
18
|
+
meta: metaMap.get(id) ?? fallbackVault(id),
|
|
19
|
+
modules: modules.filter((m) => m.vault === id),
|
|
20
|
+
}));
|
|
25
21
|
return {
|
|
26
22
|
vaults,
|
|
27
|
-
modules
|
|
23
|
+
modules,
|
|
28
24
|
extends_: packages.flatMap((pkg) => pkg.extends_ ?? []),
|
|
29
25
|
};
|
|
30
26
|
}
|
|
31
|
-
//# sourceMappingURL=package.js.map
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,77 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* (id / version / dependsOn + внески vaults/modules/extends). Серверні хуки
|
|
7
|
-
* життєвого циклу (міграції/seed/init/teardown) живуть окремо у server-only шарі
|
|
8
|
-
* (див. src/server/plugin-runtime.ts), щоб не тягнути ORM/node у чисте ядро.
|
|
9
|
-
*/
|
|
10
|
-
import type { VaultBundle } from './vault.js';
|
|
11
|
-
import type { ModuleDef } from './module.js';
|
|
12
|
-
import type { ExtendDef } from './extend.js';
|
|
13
|
-
import type { OptionItem } from './fields.js';
|
|
14
|
-
import type { SettingsDef } from './settings.js';
|
|
15
|
-
/**
|
|
16
|
-
* Внесок плагіна до колекцій поля іншого модуля (по vault/module/field).
|
|
17
|
-
* Вливається у саме поле при compose → form/display/table/zod бачать єдине джерело.
|
|
18
|
-
* - options: для prim:'select'
|
|
19
|
-
* - suggestions: для tags (string + suggestions)
|
|
20
|
-
*/
|
|
1
|
+
import type { VaultBundle } from './vault.ts';
|
|
2
|
+
import type { ModuleDef } from './module.ts';
|
|
3
|
+
import type { ExtendDef } from './extend.ts';
|
|
4
|
+
import type { OptionItem } from './fields.ts';
|
|
5
|
+
import type { SettingsDef } from './settings.ts';
|
|
21
6
|
export interface FieldContrib {
|
|
22
7
|
vault: string;
|
|
23
8
|
module: string;
|
|
24
|
-
/** key поля у цільовому модулі. */
|
|
25
9
|
field: string;
|
|
26
10
|
options?: OptionItem[];
|
|
27
11
|
suggestions?: string[];
|
|
28
12
|
}
|
|
29
|
-
/**
|
|
30
|
-
* Іменований список опцій — глобальна бібліотека варіантів.
|
|
31
|
-
* Плагіни реєструють списки (countries, colors, …).
|
|
32
|
-
* Поле field.select({ source: 'countries' }) отримує опції звідси, а не inline.
|
|
33
|
-
*/
|
|
34
13
|
export interface NamedOptionList {
|
|
35
14
|
name: string;
|
|
36
15
|
options: OptionItem[];
|
|
37
16
|
}
|
|
38
17
|
export interface PluginManifest {
|
|
39
|
-
/** Машинний id плагіна (= namespace журналу міграцій). */
|
|
40
18
|
id: string;
|
|
41
|
-
/** Semver плагіна (для журналу/діагностики). */
|
|
42
19
|
version: string;
|
|
43
|
-
/** Id плагінів, від яких залежить цей (мають бути зареєстровані раніше). */
|
|
44
20
|
dependsOn: string[];
|
|
45
21
|
vaults?: VaultBundle[];
|
|
46
22
|
vaultModules?: ModuleDef[];
|
|
47
23
|
extends_?: ExtendDef[];
|
|
48
|
-
/** Внески до select/type-опцій полів інших модулів. */
|
|
49
24
|
fieldContribs?: FieldContrib[];
|
|
50
|
-
/** Глобальні іменовані списки опцій (countries, colors, …). */
|
|
51
25
|
optionLists?: NamedOptionList[];
|
|
52
|
-
/** Налаштування плагіна — зберігаються як singleton-запис у _settings__<id>. */
|
|
53
26
|
settings?: SettingsDef;
|
|
54
27
|
}
|
|
55
|
-
/** Базовий плагін: неявна залежність КОЖНОГО іншого плагіна (не вказується у dependsOn). */
|
|
56
28
|
export declare const BASE_PLUGIN = "core";
|
|
57
29
|
export declare function definePlugin(p: PluginManifest): PluginManifest;
|
|
58
|
-
/**
|
|
59
|
-
* Топологічне сортування плагінів за dependsOn.
|
|
60
|
-
* Залежність від core мається на увазі за замовчуванням — її не треба вказувати
|
|
61
|
-
* у dependsOn, але core завжди сортується першим.
|
|
62
|
-
* Кидає помилку на відсутню залежність або цикл.
|
|
63
|
-
*/
|
|
64
30
|
export declare function topoSortPlugins(plugins: PluginManifest[]): PluginManifest[];
|
|
65
31
|
export interface AssembledPlugins {
|
|
66
|
-
/** Плагіни в топологічному порядку (для міграцій/seed/init). */
|
|
67
32
|
order: PluginManifest[];
|
|
68
33
|
vaults: VaultBundle[];
|
|
69
34
|
modules: ModuleDef[];
|
|
70
35
|
extends_: ExtendDef[];
|
|
71
36
|
}
|
|
72
|
-
/**
|
|
73
|
-
* Збирає плагіни в єдиний реєстр у залежнісному порядку.
|
|
74
|
-
* Порядок більше не залежить від ручного впорядкування масиву — лише від dependsOn.
|
|
75
|
-
*/
|
|
76
37
|
export declare function assemblePlugins(plugins: PluginManifest[]): AssembledPlugins;
|
|
77
|
-
//# sourceMappingURL=plugin.d.ts.map
|