@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.
Files changed (69) hide show
  1. package/dist/color-names.d.ts +0 -1
  2. package/dist/color-names.js +0 -1
  3. package/dist/condition.d.ts +3 -19
  4. package/dist/condition.js +50 -69
  5. package/dist/countries.d.ts +0 -2
  6. package/dist/countries.js +0 -1
  7. package/dist/currencies.d.ts +0 -10
  8. package/dist/currencies.js +0 -10
  9. package/dist/extend.d.ts +3 -22
  10. package/dist/extend.js +2 -6
  11. package/dist/field-helpers.d.ts +0 -7
  12. package/dist/field-helpers.js +0 -16
  13. package/dist/field-presets.d.ts +55 -87
  14. package/dist/field-presets.js +116 -108
  15. package/dist/fields/constants.d.ts +36 -0
  16. package/dist/fields/constants.js +18 -0
  17. package/dist/fields/normalize.d.ts +84 -0
  18. package/dist/fields/normalize.js +47 -0
  19. package/dist/fields/url.d.ts +13 -0
  20. package/dist/fields/url.js +49 -0
  21. package/dist/fields/validation.d.ts +30 -0
  22. package/dist/fields/validation.js +57 -0
  23. package/dist/fields.d.ts +222 -332
  24. package/dist/fields.js +435 -351
  25. package/dist/index.d.ts +14 -16
  26. package/dist/index.js +14 -16
  27. package/dist/module.d.ts +13 -103
  28. package/dist/module.js +51 -83
  29. package/dist/package.d.ts +3 -20
  30. package/dist/package.js +14 -19
  31. package/dist/plugin.d.ts +5 -45
  32. package/dist/plugin.js +5 -19
  33. package/dist/settings.d.ts +1 -2
  34. package/dist/settings.js +0 -1
  35. package/dist/units.d.ts +0 -1
  36. package/dist/units.js +0 -5
  37. package/dist/vault.d.ts +3 -8
  38. package/dist/vault.js +4 -1
  39. package/package.json +5 -1
  40. package/dist/color-names.d.ts.map +0 -1
  41. package/dist/color-names.js.map +0 -1
  42. package/dist/condition.d.ts.map +0 -1
  43. package/dist/condition.js.map +0 -1
  44. package/dist/countries.d.ts.map +0 -1
  45. package/dist/countries.js.map +0 -1
  46. package/dist/currencies.d.ts.map +0 -1
  47. package/dist/currencies.js.map +0 -1
  48. package/dist/extend.d.ts.map +0 -1
  49. package/dist/extend.js.map +0 -1
  50. package/dist/field-helpers.d.ts.map +0 -1
  51. package/dist/field-helpers.js.map +0 -1
  52. package/dist/field-presets.d.ts.map +0 -1
  53. package/dist/field-presets.js.map +0 -1
  54. package/dist/fields.d.ts.map +0 -1
  55. package/dist/fields.js.map +0 -1
  56. package/dist/index.d.ts.map +0 -1
  57. package/dist/index.js.map +0 -1
  58. package/dist/module.d.ts.map +0 -1
  59. package/dist/module.js.map +0 -1
  60. package/dist/package.d.ts.map +0 -1
  61. package/dist/package.js.map +0 -1
  62. package/dist/plugin.d.ts.map +0 -1
  63. package/dist/plugin.js.map +0 -1
  64. package/dist/settings.d.ts.map +0 -1
  65. package/dist/settings.js.map +0 -1
  66. package/dist/units.d.ts.map +0 -1
  67. package/dist/units.js.map +0 -1
  68. package/dist/vault.d.ts.map +0 -1
  69. package/dist/vault.js.map +0 -1
package/dist/index.d.ts CHANGED
@@ -1,16 +1,14 @@
1
- /** @coffer-org/sdk ізоморфний контракт плагінів (типи + фабрики + довідкові дані). */
2
- export * from './field-helpers.js';
3
- export * from './fields.js';
4
- export * from './field-presets.js';
5
- export * from './module.js';
6
- export * from './vault.js';
7
- export * from './extend.js';
8
- export * from './package.js';
9
- export * from './plugin.js';
10
- export * from './condition.js';
11
- export * from './countries.js';
12
- export * from './color-names.js';
13
- export * from './units.js';
14
- export * from './currencies.js';
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
- /** @coffer-org/sdk ізоморфний контракт плагінів (типи + фабрики + довідкові дані). */
2
- export * from './field-helpers.js';
3
- export * from './fields.js';
4
- export * from './field-presets.js';
5
- export * from './module.js';
6
- export * from './vault.js';
7
- export * from './extend.js';
8
- export * from './package.js';
9
- export * from './plugin.js';
10
- export * from './condition.js';
11
- export * from './countries.js';
12
- export * from './color-names.js';
13
- export * from './units.js';
14
- export * from './currencies.js';
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.js';
3
- import type { FieldClient, GroupEl, PseudoEl } from './fields.js';
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.ZodObject<z.ZodRawShape>;
139
- export declare function buildZodObjectPartial(m: ModuleDef): z.ZodObject<z.ZodRawShape>;
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 LayoutElClient = {
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
- }) | StaticElClient | PseudoEl;
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 './fields.js';
3
- // ─── Helpers доступу до полів ────────────────────────────────────────────────
4
- /** Плоскі пари [storageKey, FieldMeta] колонок батьківської таблиці.
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; // живе в child-таблиці
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
- /** Усі collection-групи (рекурсивно крізь layout та embedded). Top-level зверху. */
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}: дубльований key '${dup}'`);
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-модуль без явного views.table`);
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 && (GROUPABLE_PRIMS.has(f.prim) || isTextPrim(f.prim)))
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
- const inner = z.object(buildShape(it.fields, partial)).passthrough();
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((it) => {
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
- // Збираємо власні vault'и в Map (копія, щоб не мутувати оригінал)
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
- vaultMap.set(bundle.meta.id, { ...bundle, modules: [...bundle.modules] });
6
+ if (!metaMap.has(bundle.meta.id))
7
+ metaMap.set(bundle.meta.id, { ...bundle.meta });
12
8
  }
13
9
  }
14
- // Ін'єкція vaultModules в існуючі vault'и
10
+ const modules = [];
15
11
  for (const pkg of packages) {
16
- for (const mod of pkg.vaultModules ?? []) {
17
- const bundle = vaultMap.get(mod.vault);
18
- if (bundle) {
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 vaults = [...vaultMap.values()];
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: vaults.flatMap((v) => v.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