@coffer-org/sdk 2.2.0 → 3.0.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/countries.js +6 -1
- package/dist/derive.d.ts +12 -0
- package/dist/derive.js +42 -0
- package/dist/field-presets.js +150 -23
- package/dist/fields.d.ts +30 -2
- package/dist/fields.js +70 -10
- package/dist/shelf.d.ts +2 -5
- package/dist/shelf.js +40 -15
- package/dist/users-shelf.js +1 -1
- package/package.json +5 -1
package/dist/countries.js
CHANGED
|
@@ -144,7 +144,12 @@ export const COUNTRIES = [
|
|
|
144
144
|
{ code: 'RW', name: 'Руанда', nameRu: 'Руанда', nameEn: 'Rwanda' },
|
|
145
145
|
{ code: 'KN', name: 'Сент-Кітс і Невіс', nameRu: 'Сент-Китс и Невис', nameEn: 'St. Kitts & Nevis' },
|
|
146
146
|
{ code: 'LC', name: 'Сент-Люсія', nameRu: 'Сент-Люсия', nameEn: 'St. Lucia' },
|
|
147
|
-
{
|
|
147
|
+
{
|
|
148
|
+
code: 'VC',
|
|
149
|
+
name: 'Сент-Вінсент і Гренадіни',
|
|
150
|
+
nameRu: 'Сент-Винсент и Гренадины',
|
|
151
|
+
nameEn: 'St. Vincent & Grenadines',
|
|
152
|
+
},
|
|
148
153
|
{ code: 'WS', name: 'Самоа', nameRu: 'Самоа', nameEn: 'Samoa' },
|
|
149
154
|
{ code: 'SM', name: 'Сан-Марино', nameRu: 'Сан-Марино', nameEn: 'San Marino' },
|
|
150
155
|
{ code: 'ST', name: 'Сан-Томе і Принсіпі', nameRu: 'Сан-Томе и Принсипи', nameEn: 'São Tomé & Príncipe' },
|
package/dist/derive.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type LayoutEl } from './fields.ts';
|
|
2
|
+
export interface DeriveCtx {
|
|
3
|
+
record: Record<string, unknown>;
|
|
4
|
+
}
|
|
5
|
+
export type DeriveFn = (ctx: DeriveCtx) => unknown;
|
|
6
|
+
export interface DeriveSpec {
|
|
7
|
+
deps: string[];
|
|
8
|
+
fn: DeriveFn;
|
|
9
|
+
}
|
|
10
|
+
export declare function derivedEntries(fields: LayoutEl[]): [string, DeriveSpec][];
|
|
11
|
+
export declare function applyDerived(fields: LayoutEl[], record: Record<string, unknown>): Record<string, unknown>;
|
|
12
|
+
export declare function derivableKeys(fields: LayoutEl[]): Set<string>;
|
package/dist/derive.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { isField, isGroup } from "./fields.js";
|
|
2
|
+
export function derivedEntries(fields) {
|
|
3
|
+
const out = [];
|
|
4
|
+
for (const it of fields) {
|
|
5
|
+
if (isField(it)) {
|
|
6
|
+
const spec = it.type.derive;
|
|
7
|
+
if (spec)
|
|
8
|
+
out.push([it.key, spec]);
|
|
9
|
+
}
|
|
10
|
+
else if (isGroup(it) && !it.key) {
|
|
11
|
+
out.push(...derivedEntries(it.fields));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
export function applyDerived(fields, record) {
|
|
17
|
+
const out = {};
|
|
18
|
+
for (const [key, spec] of derivedEntries(fields)) {
|
|
19
|
+
try {
|
|
20
|
+
out[key] = spec.fn({ record });
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
console.warn(`[derive] '${key}' threw`, e);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
export function derivableKeys(fields) {
|
|
29
|
+
const keys = new Set();
|
|
30
|
+
for (const it of fields) {
|
|
31
|
+
if (isField(it))
|
|
32
|
+
keys.add(it.key);
|
|
33
|
+
else if (isGroup(it)) {
|
|
34
|
+
if (it.key)
|
|
35
|
+
keys.add(it.key);
|
|
36
|
+
if (!it.key)
|
|
37
|
+
for (const k of derivableKeys(it.fields))
|
|
38
|
+
keys.add(k);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return keys;
|
|
42
|
+
}
|
package/dist/field-presets.js
CHANGED
|
@@ -71,11 +71,7 @@ export function internalOauthGrants(o) {
|
|
|
71
71
|
label: o.label ?? o.key,
|
|
72
72
|
multiple: true,
|
|
73
73
|
view: { kind: 'internalOauthGrants' },
|
|
74
|
-
fields: [
|
|
75
|
-
string({ key: 'clientName' }),
|
|
76
|
-
string({ key: 'createdAt' }),
|
|
77
|
-
string({ key: 'lastUsedAt' }),
|
|
78
|
-
],
|
|
74
|
+
fields: [string({ key: 'clientName' }), string({ key: 'createdAt' }), string({ key: 'lastUsedAt' })],
|
|
79
75
|
});
|
|
80
76
|
}
|
|
81
77
|
const SLUG_RE = /^[a-z0-9-]+$/;
|
|
@@ -164,24 +160,155 @@ export function link(raw) {
|
|
|
164
160
|
const TEL_RE = /^\+?[\d\s()-]{4,}$/;
|
|
165
161
|
const LINK_RE = /^([a-z][a-z0-9+.-]*:\/\/\S+|[\w-]+(\.[\w-]+)+(:\d+)?(\/\S*)?)$/i;
|
|
166
162
|
const CSS_COLOR_NAMES = new Set([
|
|
167
|
-
'aliceblue',
|
|
168
|
-
'
|
|
169
|
-
'
|
|
170
|
-
'
|
|
171
|
-
'
|
|
172
|
-
'
|
|
173
|
-
'
|
|
174
|
-
'
|
|
175
|
-
'
|
|
176
|
-
'
|
|
177
|
-
'
|
|
178
|
-
'
|
|
179
|
-
'
|
|
180
|
-
'
|
|
181
|
-
'
|
|
182
|
-
'
|
|
183
|
-
'
|
|
184
|
-
'
|
|
163
|
+
'aliceblue',
|
|
164
|
+
'antiquewhite',
|
|
165
|
+
'aqua',
|
|
166
|
+
'aquamarine',
|
|
167
|
+
'azure',
|
|
168
|
+
'beige',
|
|
169
|
+
'bisque',
|
|
170
|
+
'black',
|
|
171
|
+
'blanchedalmond',
|
|
172
|
+
'blue',
|
|
173
|
+
'blueviolet',
|
|
174
|
+
'brown',
|
|
175
|
+
'burlywood',
|
|
176
|
+
'cadetblue',
|
|
177
|
+
'chartreuse',
|
|
178
|
+
'chocolate',
|
|
179
|
+
'coral',
|
|
180
|
+
'cornflowerblue',
|
|
181
|
+
'cornsilk',
|
|
182
|
+
'crimson',
|
|
183
|
+
'cyan',
|
|
184
|
+
'darkblue',
|
|
185
|
+
'darkcyan',
|
|
186
|
+
'darkgoldenrod',
|
|
187
|
+
'darkgray',
|
|
188
|
+
'darkgreen',
|
|
189
|
+
'darkgrey',
|
|
190
|
+
'darkkhaki',
|
|
191
|
+
'darkmagenta',
|
|
192
|
+
'darkolivegreen',
|
|
193
|
+
'darkorange',
|
|
194
|
+
'darkorchid',
|
|
195
|
+
'darkred',
|
|
196
|
+
'darksalmon',
|
|
197
|
+
'darkseagreen',
|
|
198
|
+
'darkslateblue',
|
|
199
|
+
'darkslategray',
|
|
200
|
+
'darkslategrey',
|
|
201
|
+
'darkturquoise',
|
|
202
|
+
'darkviolet',
|
|
203
|
+
'deeppink',
|
|
204
|
+
'deepskyblue',
|
|
205
|
+
'dimgray',
|
|
206
|
+
'dimgrey',
|
|
207
|
+
'dodgerblue',
|
|
208
|
+
'firebrick',
|
|
209
|
+
'floralwhite',
|
|
210
|
+
'forestgreen',
|
|
211
|
+
'fuchsia',
|
|
212
|
+
'gainsboro',
|
|
213
|
+
'ghostwhite',
|
|
214
|
+
'gold',
|
|
215
|
+
'goldenrod',
|
|
216
|
+
'gray',
|
|
217
|
+
'green',
|
|
218
|
+
'greenyellow',
|
|
219
|
+
'grey',
|
|
220
|
+
'honeydew',
|
|
221
|
+
'hotpink',
|
|
222
|
+
'indianred',
|
|
223
|
+
'indigo',
|
|
224
|
+
'ivory',
|
|
225
|
+
'khaki',
|
|
226
|
+
'lavender',
|
|
227
|
+
'lavenderblush',
|
|
228
|
+
'lawngreen',
|
|
229
|
+
'lemonchiffon',
|
|
230
|
+
'lightblue',
|
|
231
|
+
'lightcoral',
|
|
232
|
+
'lightcyan',
|
|
233
|
+
'lightgoldenrodyellow',
|
|
234
|
+
'lightgray',
|
|
235
|
+
'lightgreen',
|
|
236
|
+
'lightgrey',
|
|
237
|
+
'lightpink',
|
|
238
|
+
'lightsalmon',
|
|
239
|
+
'lightseagreen',
|
|
240
|
+
'lightskyblue',
|
|
241
|
+
'lightslategray',
|
|
242
|
+
'lightslategrey',
|
|
243
|
+
'lightsteelblue',
|
|
244
|
+
'lightyellow',
|
|
245
|
+
'lime',
|
|
246
|
+
'limegreen',
|
|
247
|
+
'linen',
|
|
248
|
+
'magenta',
|
|
249
|
+
'maroon',
|
|
250
|
+
'mediumaquamarine',
|
|
251
|
+
'mediumblue',
|
|
252
|
+
'mediumorchid',
|
|
253
|
+
'mediumpurple',
|
|
254
|
+
'mediumseagreen',
|
|
255
|
+
'mediumslateblue',
|
|
256
|
+
'mediumspringgreen',
|
|
257
|
+
'mediumturquoise',
|
|
258
|
+
'mediumvioletred',
|
|
259
|
+
'midnightblue',
|
|
260
|
+
'mintcream',
|
|
261
|
+
'mistyrose',
|
|
262
|
+
'moccasin',
|
|
263
|
+
'navajowhite',
|
|
264
|
+
'navy',
|
|
265
|
+
'oldlace',
|
|
266
|
+
'olive',
|
|
267
|
+
'olivedrab',
|
|
268
|
+
'orange',
|
|
269
|
+
'orangered',
|
|
270
|
+
'orchid',
|
|
271
|
+
'palegoldenrod',
|
|
272
|
+
'palegreen',
|
|
273
|
+
'paleturquoise',
|
|
274
|
+
'palevioletred',
|
|
275
|
+
'papayawhip',
|
|
276
|
+
'peachpuff',
|
|
277
|
+
'peru',
|
|
278
|
+
'pink',
|
|
279
|
+
'plum',
|
|
280
|
+
'powderblue',
|
|
281
|
+
'purple',
|
|
282
|
+
'rebeccapurple',
|
|
283
|
+
'red',
|
|
284
|
+
'rosybrown',
|
|
285
|
+
'royalblue',
|
|
286
|
+
'saddlebrown',
|
|
287
|
+
'salmon',
|
|
288
|
+
'sandybrown',
|
|
289
|
+
'seagreen',
|
|
290
|
+
'seashell',
|
|
291
|
+
'sienna',
|
|
292
|
+
'silver',
|
|
293
|
+
'skyblue',
|
|
294
|
+
'slateblue',
|
|
295
|
+
'slategray',
|
|
296
|
+
'slategrey',
|
|
297
|
+
'snow',
|
|
298
|
+
'springgreen',
|
|
299
|
+
'steelblue',
|
|
300
|
+
'tan',
|
|
301
|
+
'teal',
|
|
302
|
+
'thistle',
|
|
303
|
+
'tomato',
|
|
304
|
+
'turquoise',
|
|
305
|
+
'violet',
|
|
306
|
+
'wheat',
|
|
307
|
+
'white',
|
|
308
|
+
'whitesmoke',
|
|
309
|
+
'yellow',
|
|
310
|
+
'yellowgreen',
|
|
311
|
+
'transparent',
|
|
185
312
|
]);
|
|
186
313
|
export function tag(raw) {
|
|
187
314
|
const o = normalizeOpts(raw);
|
package/dist/fields.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
|
|
2
|
+
import { type UnitsSpec } from './units.ts';
|
|
3
|
+
export { vmsg, reqErr, typeErr, reqTypeErr, jsonRefined, optionalize, jsonValue, decodeVmsg, validateField, } from './fields/validation.ts';
|
|
3
4
|
import type { Condition } from './condition.ts';
|
|
4
5
|
export type { Rules, View, FieldConfig } from './fields/normalize.ts';
|
|
5
6
|
export { normalizeOpts } from './fields/normalize.ts';
|
|
@@ -99,6 +100,7 @@ export interface FieldClient {
|
|
|
99
100
|
virtual?: boolean;
|
|
100
101
|
default?: string | number | boolean | null;
|
|
101
102
|
json?: true;
|
|
103
|
+
derived?: true;
|
|
102
104
|
columns?: Record<string, ColumnType>;
|
|
103
105
|
hidden?: boolean | Condition;
|
|
104
106
|
}
|
|
@@ -180,6 +182,7 @@ export interface FieldCoreOpts {
|
|
|
180
182
|
emphasis?: 'hero' | 'muted';
|
|
181
183
|
noLabel?: boolean;
|
|
182
184
|
role?: 'cover' | 'avatar';
|
|
185
|
+
derive?: import('./derive.ts').DeriveSpec;
|
|
183
186
|
}
|
|
184
187
|
export declare function wrapKey(opts: {
|
|
185
188
|
key?: string;
|
|
@@ -190,10 +193,12 @@ export declare function wrapKey(opts: {
|
|
|
190
193
|
emphasis?: 'hero' | 'muted';
|
|
191
194
|
noLabel?: boolean;
|
|
192
195
|
role?: 'cover' | 'avatar';
|
|
196
|
+
derive?: import('./derive.ts').DeriveSpec;
|
|
193
197
|
}, meta: FieldMeta): FieldItem | StaticEl | FieldMeta;
|
|
194
198
|
export interface FieldMeta extends FieldClient {
|
|
195
199
|
column: ColumnType;
|
|
196
200
|
zod: z.ZodTypeAny;
|
|
201
|
+
derive?: import('./derive.ts').DeriveSpec;
|
|
197
202
|
}
|
|
198
203
|
export declare function isJsonStored(field: FieldMeta | FieldClient): boolean;
|
|
199
204
|
export { LANGUAGES, SEX_OPTIONS, WEEKDAY_OPTIONS } from './fields/constants.ts';
|
|
@@ -355,7 +360,7 @@ export declare function triState(o: TriStateOpts & {
|
|
|
355
360
|
value?: undefined;
|
|
356
361
|
}): FieldMeta;
|
|
357
362
|
export interface SelectOpts extends FieldCoreOpts {
|
|
358
|
-
options?:
|
|
363
|
+
options?: string | (OptionItem | string)[];
|
|
359
364
|
}
|
|
360
365
|
export declare function select(o: SelectOpts & {
|
|
361
366
|
key: string;
|
|
@@ -427,6 +432,27 @@ export declare function measured(o: MeasuredOpts & {
|
|
|
427
432
|
key?: undefined;
|
|
428
433
|
value?: undefined;
|
|
429
434
|
}): FieldMeta;
|
|
435
|
+
export type UnitScope = string;
|
|
436
|
+
export interface UnitOpts extends FieldCoreOpts {
|
|
437
|
+
options: UnitsSpec;
|
|
438
|
+
}
|
|
439
|
+
export declare function unit(o: UnitOpts & {
|
|
440
|
+
key: string;
|
|
441
|
+
}): FieldItem;
|
|
442
|
+
export declare function unit(o: UnitOpts & {
|
|
443
|
+
key?: undefined;
|
|
444
|
+
value?: undefined;
|
|
445
|
+
}): FieldMeta;
|
|
446
|
+
export interface AmountOpts extends FieldCoreOpts {
|
|
447
|
+
unitFrom: UnitScope;
|
|
448
|
+
}
|
|
449
|
+
export declare function amount(o: AmountOpts & {
|
|
450
|
+
key: string;
|
|
451
|
+
}): FieldItem;
|
|
452
|
+
export declare function amount(o: AmountOpts & {
|
|
453
|
+
key?: undefined;
|
|
454
|
+
value?: undefined;
|
|
455
|
+
}): FieldMeta;
|
|
430
456
|
export type MoneyOpts = FieldCoreOpts;
|
|
431
457
|
export declare function money(o: MoneyOpts & {
|
|
432
458
|
key: string;
|
|
@@ -667,6 +693,8 @@ declare const PRIMITIVES: {
|
|
|
667
693
|
json: typeof json;
|
|
668
694
|
check: typeof check;
|
|
669
695
|
measured: typeof measured;
|
|
696
|
+
unit: typeof unit;
|
|
697
|
+
amount: typeof amount;
|
|
670
698
|
money: typeof money;
|
|
671
699
|
code: typeof code;
|
|
672
700
|
geo: typeof geo;
|
package/dist/fields.js
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { resolveUnits } from "./units.js";
|
|
3
3
|
import { isCurrencyCode } from "./currencies.js";
|
|
4
4
|
import { vmsg, reqErr, typeErr, reqTypeErr, jsonRefined, optionalize, jsonValue } from "./fields/validation.js";
|
|
5
|
-
export { vmsg, reqErr, typeErr, reqTypeErr, jsonRefined, optionalize, jsonValue, decodeVmsg, validateField } from "./fields/validation.js";
|
|
5
|
+
export { vmsg, reqErr, typeErr, reqTypeErr, jsonRefined, optionalize, jsonValue, decodeVmsg, validateField, } from "./fields/validation.js";
|
|
6
6
|
export { normalizeOpts } from "./fields/normalize.js";
|
|
7
7
|
import { normalizeOpts } from "./fields/normalize.js";
|
|
8
8
|
import { presets } from "./field-presets.js";
|
|
@@ -127,6 +127,8 @@ export function wrapKey(opts, meta) {
|
|
|
127
127
|
m = { ...m, default: opts.default };
|
|
128
128
|
if (opts.hidden !== undefined)
|
|
129
129
|
m = { ...m, hidden: opts.hidden };
|
|
130
|
+
if (opts.derive)
|
|
131
|
+
m = { ...m, derive: opts.derive, derived: true, hints: { ...m.hints, noEditControl: true } };
|
|
130
132
|
if (opts.key)
|
|
131
133
|
return { key: opts.key, type: m };
|
|
132
134
|
if ('value' in opts && opts.value !== undefined)
|
|
@@ -185,7 +187,13 @@ export function applyMultiple(base, multiple) {
|
|
|
185
187
|
if (!multiple)
|
|
186
188
|
return base;
|
|
187
189
|
const s = multipleZod(base.zod);
|
|
188
|
-
return {
|
|
190
|
+
return {
|
|
191
|
+
...base,
|
|
192
|
+
column: 'text',
|
|
193
|
+
hints: { ...base.hints, multiple: true },
|
|
194
|
+
json: true,
|
|
195
|
+
zod: optionalize(s, base.required),
|
|
196
|
+
};
|
|
189
197
|
}
|
|
190
198
|
export function string(raw) {
|
|
191
199
|
const o = normalizeOpts(raw);
|
|
@@ -421,11 +429,14 @@ export function triState(raw) {
|
|
|
421
429
|
};
|
|
422
430
|
return wrapKey(o, meta);
|
|
423
431
|
}
|
|
432
|
+
function toOptionItem(o) {
|
|
433
|
+
return typeof o === 'string' ? { value: o, title: o } : o;
|
|
434
|
+
}
|
|
424
435
|
export function select(raw) {
|
|
425
436
|
const o = normalizeOpts(raw);
|
|
426
437
|
const required = o.required ?? false;
|
|
427
438
|
const source = typeof o.options === 'string' ? o.options : null;
|
|
428
|
-
const inlineOpts = Array.isArray(o.options) ? o.options : [];
|
|
439
|
+
const inlineOpts = Array.isArray(o.options) ? o.options.map(toOptionItem) : [];
|
|
429
440
|
const s = inlineOpts.length > 0
|
|
430
441
|
? z.enum(inlineOpts.map((x) => x.value), { error: () => vmsg('enum') })
|
|
431
442
|
: z.string(reqErr());
|
|
@@ -455,7 +466,10 @@ export function relation(raw) {
|
|
|
455
466
|
});
|
|
456
467
|
}
|
|
457
468
|
else {
|
|
458
|
-
s = z.coerce
|
|
469
|
+
s = z.coerce
|
|
470
|
+
.number()
|
|
471
|
+
.int()
|
|
472
|
+
.positive({ message: vmsg('int') });
|
|
459
473
|
}
|
|
460
474
|
const meta = {
|
|
461
475
|
kind: 'relation',
|
|
@@ -509,9 +523,7 @@ export function check(raw) {
|
|
|
509
523
|
const required = o.required ?? false;
|
|
510
524
|
const multiple = o.multiple ?? false;
|
|
511
525
|
const slots = o.slots && o.slots.length > 0 ? o.slots : [''];
|
|
512
|
-
const contentFields = o.fields?.length
|
|
513
|
-
? o.fields
|
|
514
|
-
: [string({ key: 'text', label: 'core.fields.text' })];
|
|
526
|
+
const contentFields = o.fields?.length ? o.fields : [string({ key: 'text', label: 'core.fields.text' })];
|
|
515
527
|
const contentKeys = new Set();
|
|
516
528
|
for (const f of contentFields) {
|
|
517
529
|
if (contentKeys.has(f.key))
|
|
@@ -525,7 +537,14 @@ export function check(raw) {
|
|
|
525
537
|
const checkFields = slots.map((label, i) => boolean({ key: `check${i}`, label: label || 'core.fields.check' }));
|
|
526
538
|
if (multiple) {
|
|
527
539
|
const fields = [...checkFields, ...contentFields];
|
|
528
|
-
return group({
|
|
540
|
+
return group({
|
|
541
|
+
key: o.key,
|
|
542
|
+
label: o.label ?? o.key,
|
|
543
|
+
multiple: true,
|
|
544
|
+
required: required,
|
|
545
|
+
fields,
|
|
546
|
+
view: { kind: 'checklist' },
|
|
547
|
+
});
|
|
529
548
|
}
|
|
530
549
|
const shape = {};
|
|
531
550
|
const columns = {};
|
|
@@ -618,6 +637,45 @@ export function measured(raw) {
|
|
|
618
637
|
};
|
|
619
638
|
return wrapKey(o, meta);
|
|
620
639
|
}
|
|
640
|
+
export function unit(raw) {
|
|
641
|
+
const o = normalizeOpts(raw);
|
|
642
|
+
const required = o.required ?? false;
|
|
643
|
+
const units = resolveUnits(o.options);
|
|
644
|
+
const s = z
|
|
645
|
+
.string(reqErr())
|
|
646
|
+
.refine((v) => units.some((u) => u.value === v), { message: vmsg('measured_unit') });
|
|
647
|
+
const meta = {
|
|
648
|
+
kind: 'unit',
|
|
649
|
+
label: o.label ?? '',
|
|
650
|
+
required,
|
|
651
|
+
prim: 'select',
|
|
652
|
+
column: 'text',
|
|
653
|
+
hints: { source: typeof o.options === 'string' ? o.options : null },
|
|
654
|
+
options: units.map((u) => ({ value: u.value, title: u.label })),
|
|
655
|
+
zod: optionalize(s, required),
|
|
656
|
+
};
|
|
657
|
+
return wrapKey(o, meta);
|
|
658
|
+
}
|
|
659
|
+
export function amount(raw) {
|
|
660
|
+
const o = normalizeOpts(raw);
|
|
661
|
+
const required = o.required ?? false;
|
|
662
|
+
const cfg = (o.config ?? {});
|
|
663
|
+
let s = z.coerce.number(typeErr());
|
|
664
|
+
if (cfg.min != null)
|
|
665
|
+
s = s.min(cfg.min, { message: vmsg('min', { min: cfg.min }) });
|
|
666
|
+
if (cfg.max != null)
|
|
667
|
+
s = s.max(cfg.max, { message: vmsg('max', { max: cfg.max }) });
|
|
668
|
+
const meta = {
|
|
669
|
+
kind: 'amount',
|
|
670
|
+
label: o.label ?? '',
|
|
671
|
+
required,
|
|
672
|
+
prim: 'number',
|
|
673
|
+
column: 'real',
|
|
674
|
+
hints: { unitFrom: o.unitFrom, min: cfg.min, max: cfg.max, step: cfg.step ?? 'any' },
|
|
675
|
+
zod: optionalize(s, required),
|
|
676
|
+
};
|
|
677
|
+
return wrapKey(o, meta);
|
|
678
|
+
}
|
|
621
679
|
export function money(raw) {
|
|
622
680
|
const o = normalizeOpts(raw);
|
|
623
681
|
const required = o.required ?? false;
|
|
@@ -1005,6 +1063,8 @@ const PRIMITIVES = {
|
|
|
1005
1063
|
json,
|
|
1006
1064
|
check,
|
|
1007
1065
|
measured,
|
|
1066
|
+
unit,
|
|
1067
|
+
amount,
|
|
1008
1068
|
money,
|
|
1009
1069
|
code,
|
|
1010
1070
|
geo,
|
|
@@ -1048,6 +1108,6 @@ export const field = new Proxy({}, {
|
|
|
1048
1108
|
has: (_t, k) => k in composedField(),
|
|
1049
1109
|
});
|
|
1050
1110
|
export function toClient(field) {
|
|
1051
|
-
const { kind, label, required, prim, hints, options, relation, virtual, json, hidden } = field;
|
|
1052
|
-
return { kind, label, required, prim, hints, options, relation, virtual, json, hidden };
|
|
1111
|
+
const { kind, label, required, prim, hints, options, relation, virtual, json, hidden, derived } = field;
|
|
1112
|
+
return { kind, label, required, prim, hints, options, relation, virtual, json, hidden, derived };
|
|
1053
1113
|
}
|
package/dist/shelf.d.ts
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { FieldMeta, LayoutEl, ColumnType } from './fields.ts';
|
|
3
3
|
import type { FieldClient, GroupEl, PseudoEl } from './fields.ts';
|
|
4
|
-
export interface ShelfListView {
|
|
5
|
-
kind?: string;
|
|
6
|
-
fields: string[];
|
|
7
|
-
}
|
|
8
4
|
export interface ShelfViews {
|
|
9
5
|
title?: string;
|
|
10
6
|
inline?: string[];
|
|
11
|
-
list?:
|
|
7
|
+
list?: string[];
|
|
8
|
+
listKind?: string;
|
|
12
9
|
groupBy?: string[];
|
|
13
10
|
defaultGroupBy?: string;
|
|
14
11
|
select?: {
|
package/dist/shelf.js
CHANGED
|
@@ -2,6 +2,8 @@ import { z } from 'zod';
|
|
|
2
2
|
import { toClient, isField, isGroup, isStatic, isCollectionGroup, isEmbeddedGroup } from "./fields.js";
|
|
3
3
|
import { resolveFlag } from "./condition.js";
|
|
4
4
|
import { parseUrl } from "./fields/url.js";
|
|
5
|
+
import { vmsg } from "./fields/validation.js";
|
|
6
|
+
import { derivedEntries, derivableKeys } from "./derive.js";
|
|
5
7
|
export function fieldEntries(items) {
|
|
6
8
|
const out = [];
|
|
7
9
|
for (const it of items) {
|
|
@@ -70,24 +72,45 @@ export function defineShelf(m) {
|
|
|
70
72
|
throw new Error(`[shelf] ${m.library}/${m.shelf}: duplicate key '${dup}'`);
|
|
71
73
|
if (m.single && !m.claude)
|
|
72
74
|
throw new Error(`[shelf] ${m.library}/${m.shelf}: single shelf requires \`claude\` — the agent cannot find it otherwise`);
|
|
73
|
-
if (m.
|
|
75
|
+
if (m.views?.list !== undefined && !Array.isArray(m.views.list))
|
|
76
|
+
throw new Error(`[shelf] ${m.library}/${m.shelf}: views.list is a flat array of field keys — ` +
|
|
77
|
+
`the { kind, fields } form is gone; use views.list: [...] plus views.listKind`);
|
|
78
|
+
if (m.standalone !== false && !m.single && !m.views?.list?.length)
|
|
74
79
|
console.warn(`[shelf] ${m.library}/${m.shelf}: standalone shelf without an explicit views.list`);
|
|
80
|
+
if (m.views?.title && !ownFieldEntries(m.fields).some(([k]) => k === m.views.title))
|
|
81
|
+
throw new Error(`[shelf] ${m.library}/${m.shelf}: views.title '${m.views.title}' is not a field`);
|
|
82
|
+
if (m.views?.list?.includes(titleKey(m)))
|
|
83
|
+
console.warn(`[shelf] ${m.library}/${m.shelf}: views.list repeats the title '${titleKey(m)}' — the title is declared separately`);
|
|
84
|
+
const titles = ownFieldEntries(m.fields).filter(([, f]) => f.kind === 'title');
|
|
85
|
+
if (titles.length > 1)
|
|
86
|
+
console.warn(`[shelf] ${m.library}/${m.shelf}: ${titles.length} title fields (${titles.map(([k]) => k).join(', ')}) — '${titles[0][0]}' wins`);
|
|
87
|
+
if (m.standalone !== false && !m.single && titleKey(m) === 'id')
|
|
88
|
+
console.warn(`[shelf] ${m.library}/${m.shelf}: no title declared (field.title() or views.title) — the record renders no heading`);
|
|
89
|
+
const known = derivableKeys(m.fields);
|
|
90
|
+
for (const [key, spec] of derivedEntries(m.fields)) {
|
|
91
|
+
for (const dep of spec.deps) {
|
|
92
|
+
if (!known.has(dep))
|
|
93
|
+
throw new Error(`[defineShelf] ${m.library}/${m.shelf}: derive on '${key}' depends on unknown key '${dep}'`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
75
96
|
return m;
|
|
76
97
|
}
|
|
77
98
|
export const SYSTEM_FIELDS = ['id', 'createdAt', 'updatedAt'];
|
|
99
|
+
function ownFieldEntries(items) {
|
|
100
|
+
const out = [];
|
|
101
|
+
for (const it of items) {
|
|
102
|
+
if (isField(it))
|
|
103
|
+
out.push([it.key, it.type]);
|
|
104
|
+
else if (isGroup(it) && !isCollectionGroup(it) && !isEmbeddedGroup(it))
|
|
105
|
+
out.push(...ownFieldEntries(it.fields));
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
78
109
|
export function titleKey(m) {
|
|
79
110
|
if (m.views?.title)
|
|
80
111
|
return m.views.title;
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
if (inlineNonImage)
|
|
84
|
-
return inlineNonImage;
|
|
85
|
-
if (fm['name'])
|
|
86
|
-
return 'name';
|
|
87
|
-
const firstText = fieldEntries(m.fields).find(([, f]) => !f.virtual && isTextPrim(f.prim));
|
|
88
|
-
if (firstText)
|
|
89
|
-
return firstText[0];
|
|
90
|
-
return 'id';
|
|
112
|
+
const declared = ownFieldEntries(m.fields).find(([, f]) => f.kind === 'title');
|
|
113
|
+
return declared ? declared[0] : 'id';
|
|
91
114
|
}
|
|
92
115
|
export function recordTitle(m, record) {
|
|
93
116
|
return String(record[titleKey(m)] ?? record['id'] ?? '');
|
|
@@ -128,13 +151,11 @@ export function inlineText(m, row) {
|
|
|
128
151
|
}
|
|
129
152
|
export function viewKeys(m) {
|
|
130
153
|
const tk = titleKey(m);
|
|
131
|
-
const rest = m.views?.list?.
|
|
132
|
-
? realKeys(m, m.views.list.fields.filter((k) => k !== tk))
|
|
133
|
-
: [];
|
|
154
|
+
const rest = m.views?.list?.length ? realKeys(m, m.views.list.filter((k) => k !== tk)) : [];
|
|
134
155
|
return [tk, ...rest];
|
|
135
156
|
}
|
|
136
157
|
export function listKind(m) {
|
|
137
|
-
return m.views?.
|
|
158
|
+
return m.views?.listKind ?? 'table';
|
|
138
159
|
}
|
|
139
160
|
const GROUPABLE_PRIMS = new Set(['select', 'checkbox', 'relation']);
|
|
140
161
|
export function groupableFields(m) {
|
|
@@ -198,6 +219,10 @@ export function buildShape(fields, partial) {
|
|
|
198
219
|
if (isField(it)) {
|
|
199
220
|
if (it.type.virtual)
|
|
200
221
|
continue;
|
|
222
|
+
if (it.type.derive) {
|
|
223
|
+
shape[it.key] = z.never({ error: () => vmsg('derived') }).optional();
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
201
226
|
shape[it.key] = partial ? it.type.zod.optional() : it.type.zod;
|
|
202
227
|
}
|
|
203
228
|
else if (isGroup(it)) {
|
package/dist/users-shelf.js
CHANGED
|
@@ -5,7 +5,7 @@ export const USERS_SHELF = defineShelf({
|
|
|
5
5
|
library: '',
|
|
6
6
|
label: 'auth.usersTitle',
|
|
7
7
|
icon: 'lucide:users',
|
|
8
|
-
views: {
|
|
8
|
+
views: { title: 'login', list: ['displayName', 'role', 'disabled'] },
|
|
9
9
|
fields: [
|
|
10
10
|
field.string({ key: 'login', label: 'auth.fieldLogin' }),
|
|
11
11
|
field.string({ key: 'displayName', label: 'auth.fieldDisplayName' }),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coffer-org/sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24"
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"default": "./dist/index.js"
|
|
15
15
|
},
|
|
16
|
+
"./derive": {
|
|
17
|
+
"types": "./dist/derive.d.ts",
|
|
18
|
+
"default": "./dist/derive.js"
|
|
19
|
+
},
|
|
16
20
|
"./*": {
|
|
17
21
|
"types": "./dist/*.d.ts",
|
|
18
22
|
"default": "./dist/*.js"
|