@coffer-org/sdk 2.2.1 → 3.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/dist/condition.d.ts +7 -0
- package/dist/condition.js +22 -0
- package/dist/derive.d.ts +12 -0
- package/dist/derive.js +42 -0
- package/dist/field-presets.js +1 -1
- package/dist/fields.d.ts +31 -0
- package/dist/fields.js +60 -3
- package/dist/plugin.d.ts +2 -0
- package/dist/shelf.d.ts +2 -5
- package/dist/shelf.js +40 -15
- package/dist/showcase.d.ts +15 -0
- package/dist/showcase.js +35 -0
- package/dist/users-shelf.js +1 -1
- package/package.json +9 -1
package/dist/condition.d.ts
CHANGED
|
@@ -17,6 +17,13 @@ export interface Condition {
|
|
|
17
17
|
$or?: Condition[];
|
|
18
18
|
}
|
|
19
19
|
export type Values = Record<string, unknown>;
|
|
20
|
+
export declare const COMPILABLE_SCALAR_OPS: Set<string>;
|
|
21
|
+
export declare const COMPILABLE_ARRAY_OPS: Set<string>;
|
|
20
22
|
export declare function evalCondition(cond: Condition | undefined, values: Values): boolean;
|
|
21
23
|
export declare function resolveFlag(v: boolean | Condition | undefined, values: Values): boolean;
|
|
24
|
+
export declare function conditionKeys(cond: Condition | undefined): string[];
|
|
25
|
+
export declare function visibleOptions<T extends {
|
|
26
|
+
value?: string;
|
|
27
|
+
when?: Condition;
|
|
28
|
+
}>(options: T[], values: Values, keep?: string | string[]): T[];
|
|
22
29
|
export declare function describeCondition(cond: Condition | undefined, labelOf: (field: string) => string, valueLabelOf?: (field: string, value: Scalar) => string): string[];
|
package/dist/condition.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import sift from 'sift';
|
|
2
|
+
export const COMPILABLE_SCALAR_OPS = new Set(['$eq', '$ne', '$gt', '$gte', '$lt', '$lte']);
|
|
3
|
+
export const COMPILABLE_ARRAY_OPS = new Set(['$in', '$nin']);
|
|
2
4
|
const createEqualsOperation = sift
|
|
3
5
|
.createEqualsOperation;
|
|
4
6
|
const isScalar = (v) => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean';
|
|
@@ -62,6 +64,26 @@ function describePredicate(label, pred, vl) {
|
|
|
62
64
|
}
|
|
63
65
|
return out;
|
|
64
66
|
}
|
|
67
|
+
export function conditionKeys(cond) {
|
|
68
|
+
if (!cond)
|
|
69
|
+
return [];
|
|
70
|
+
const out = [];
|
|
71
|
+
for (const [key, spec] of Object.entries(cond)) {
|
|
72
|
+
if (spec === undefined)
|
|
73
|
+
continue;
|
|
74
|
+
if (key === '$and' || key === '$or') {
|
|
75
|
+
for (const c of spec)
|
|
76
|
+
out.push(...conditionKeys(c));
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
out.push(key);
|
|
80
|
+
}
|
|
81
|
+
return [...new Set(out)];
|
|
82
|
+
}
|
|
83
|
+
export function visibleOptions(options, values, keep) {
|
|
84
|
+
const kept = keep === undefined ? undefined : new Set(Array.isArray(keep) ? keep : [keep]);
|
|
85
|
+
return options.filter((o) => (kept !== undefined && o.value !== undefined && kept.has(o.value)) || (o.when ? evalCondition(o.when, values) : true));
|
|
86
|
+
}
|
|
65
87
|
export function describeCondition(cond, labelOf, valueLabelOf) {
|
|
66
88
|
if (!cond)
|
|
67
89
|
return [];
|
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
|
@@ -375,7 +375,7 @@ export function source(raw) {
|
|
|
375
375
|
required,
|
|
376
376
|
prim: 'text',
|
|
377
377
|
column: 'text',
|
|
378
|
-
hints: { ext, maxBytes, noEditControl: true },
|
|
378
|
+
hints: { ext, maxBytes, noEditControl: true, ...(o.key ? { fileName: o.key } : {}) },
|
|
379
379
|
zod: optionalize(s, required),
|
|
380
380
|
};
|
|
381
381
|
return wrapKey(o, meta);
|
package/dist/fields.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { type UnitsSpec } from './units.ts';
|
|
2
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';
|
|
@@ -23,6 +24,7 @@ export interface OptionItem {
|
|
|
23
24
|
title: string;
|
|
24
25
|
icon?: OptionIcon;
|
|
25
26
|
subtitle?: string;
|
|
27
|
+
when?: Condition;
|
|
26
28
|
}
|
|
27
29
|
declare const __fieldItemBrand: unique symbol;
|
|
28
30
|
export interface FieldItem {
|
|
@@ -95,10 +97,12 @@ export interface FieldClient {
|
|
|
95
97
|
relation?: {
|
|
96
98
|
library: string;
|
|
97
99
|
shelf: string;
|
|
100
|
+
filter?: Condition;
|
|
98
101
|
};
|
|
99
102
|
virtual?: boolean;
|
|
100
103
|
default?: string | number | boolean | null;
|
|
101
104
|
json?: true;
|
|
105
|
+
derived?: true;
|
|
102
106
|
columns?: Record<string, ColumnType>;
|
|
103
107
|
hidden?: boolean | Condition;
|
|
104
108
|
}
|
|
@@ -180,6 +184,7 @@ export interface FieldCoreOpts {
|
|
|
180
184
|
emphasis?: 'hero' | 'muted';
|
|
181
185
|
noLabel?: boolean;
|
|
182
186
|
role?: 'cover' | 'avatar';
|
|
187
|
+
derive?: import('./derive.ts').DeriveSpec;
|
|
183
188
|
}
|
|
184
189
|
export declare function wrapKey(opts: {
|
|
185
190
|
key?: string;
|
|
@@ -190,10 +195,12 @@ export declare function wrapKey(opts: {
|
|
|
190
195
|
emphasis?: 'hero' | 'muted';
|
|
191
196
|
noLabel?: boolean;
|
|
192
197
|
role?: 'cover' | 'avatar';
|
|
198
|
+
derive?: import('./derive.ts').DeriveSpec;
|
|
193
199
|
}, meta: FieldMeta): FieldItem | StaticEl | FieldMeta;
|
|
194
200
|
export interface FieldMeta extends FieldClient {
|
|
195
201
|
column: ColumnType;
|
|
196
202
|
zod: z.ZodTypeAny;
|
|
203
|
+
derive?: import('./derive.ts').DeriveSpec;
|
|
197
204
|
}
|
|
198
205
|
export declare function isJsonStored(field: FieldMeta | FieldClient): boolean;
|
|
199
206
|
export { LANGUAGES, SEX_OPTIONS, WEEKDAY_OPTIONS } from './fields/constants.ts';
|
|
@@ -372,6 +379,7 @@ export interface RelationOpts extends FieldCoreOpts {
|
|
|
372
379
|
options: {
|
|
373
380
|
library: string;
|
|
374
381
|
shelf: string;
|
|
382
|
+
filter?: Condition;
|
|
375
383
|
};
|
|
376
384
|
}
|
|
377
385
|
export declare function relation(o: RelationOpts & {
|
|
@@ -427,6 +435,27 @@ export declare function measured(o: MeasuredOpts & {
|
|
|
427
435
|
key?: undefined;
|
|
428
436
|
value?: undefined;
|
|
429
437
|
}): FieldMeta;
|
|
438
|
+
export type UnitScope = string;
|
|
439
|
+
export interface UnitOpts extends FieldCoreOpts {
|
|
440
|
+
options: UnitsSpec;
|
|
441
|
+
}
|
|
442
|
+
export declare function unit(o: UnitOpts & {
|
|
443
|
+
key: string;
|
|
444
|
+
}): FieldItem;
|
|
445
|
+
export declare function unit(o: UnitOpts & {
|
|
446
|
+
key?: undefined;
|
|
447
|
+
value?: undefined;
|
|
448
|
+
}): FieldMeta;
|
|
449
|
+
export interface AmountOpts extends FieldCoreOpts {
|
|
450
|
+
unitFrom: UnitScope;
|
|
451
|
+
}
|
|
452
|
+
export declare function amount(o: AmountOpts & {
|
|
453
|
+
key: string;
|
|
454
|
+
}): FieldItem;
|
|
455
|
+
export declare function amount(o: AmountOpts & {
|
|
456
|
+
key?: undefined;
|
|
457
|
+
value?: undefined;
|
|
458
|
+
}): FieldMeta;
|
|
430
459
|
export type MoneyOpts = FieldCoreOpts;
|
|
431
460
|
export declare function money(o: MoneyOpts & {
|
|
432
461
|
key: string;
|
|
@@ -667,6 +696,8 @@ declare const PRIMITIVES: {
|
|
|
667
696
|
json: typeof json;
|
|
668
697
|
check: typeof check;
|
|
669
698
|
measured: typeof measured;
|
|
699
|
+
unit: typeof unit;
|
|
700
|
+
amount: typeof amount;
|
|
670
701
|
money: typeof money;
|
|
671
702
|
code: typeof code;
|
|
672
703
|
geo: typeof geo;
|
package/dist/fields.js
CHANGED
|
@@ -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)
|
|
@@ -451,10 +453,20 @@ export function select(raw) {
|
|
|
451
453
|
const meta = applyMultiple(base, o.multiple ?? false);
|
|
452
454
|
return wrapKey(o, meta);
|
|
453
455
|
}
|
|
456
|
+
function assertFlatEqualityFilter(filter) {
|
|
457
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
458
|
+
if (key.startsWith('$'))
|
|
459
|
+
throw new Error(`[relation] filter key '${key}' is an operator — only flat equality is allowed here`);
|
|
460
|
+
if (value === undefined || (value !== null && typeof value === 'object'))
|
|
461
|
+
throw new Error(`[relation] filter key '${key}' has a non-scalar value — only flat equality is allowed here`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
454
464
|
export function relation(raw) {
|
|
455
465
|
const o = normalizeOpts(raw);
|
|
456
466
|
const required = o.required ?? false;
|
|
457
467
|
const multi = o.multiple ?? false;
|
|
468
|
+
if (raw.options.filter)
|
|
469
|
+
assertFlatEqualityFilter(raw.options.filter);
|
|
458
470
|
let s;
|
|
459
471
|
if (multi) {
|
|
460
472
|
s = z.unknown().superRefine((raw, ctx) => {
|
|
@@ -476,7 +488,11 @@ export function relation(raw) {
|
|
|
476
488
|
prim: 'relation',
|
|
477
489
|
column: multi ? 'text' : 'integer',
|
|
478
490
|
hints: { multi, displayKey: o.displayKey ?? 'name' },
|
|
479
|
-
relation: {
|
|
491
|
+
relation: {
|
|
492
|
+
library: raw.options.library,
|
|
493
|
+
shelf: raw.options.shelf,
|
|
494
|
+
...(raw.options.filter && { filter: raw.options.filter }),
|
|
495
|
+
},
|
|
480
496
|
...(multi && { json: true }),
|
|
481
497
|
zod: optionalize(s, required),
|
|
482
498
|
};
|
|
@@ -635,6 +651,45 @@ export function measured(raw) {
|
|
|
635
651
|
};
|
|
636
652
|
return wrapKey(o, meta);
|
|
637
653
|
}
|
|
654
|
+
export function unit(raw) {
|
|
655
|
+
const o = normalizeOpts(raw);
|
|
656
|
+
const required = o.required ?? false;
|
|
657
|
+
const units = resolveUnits(o.options);
|
|
658
|
+
const s = z
|
|
659
|
+
.string(reqErr())
|
|
660
|
+
.refine((v) => units.some((u) => u.value === v), { message: vmsg('measured_unit') });
|
|
661
|
+
const meta = {
|
|
662
|
+
kind: 'unit',
|
|
663
|
+
label: o.label ?? '',
|
|
664
|
+
required,
|
|
665
|
+
prim: 'select',
|
|
666
|
+
column: 'text',
|
|
667
|
+
hints: { source: typeof o.options === 'string' ? o.options : null },
|
|
668
|
+
options: units.map((u) => ({ value: u.value, title: u.label })),
|
|
669
|
+
zod: optionalize(s, required),
|
|
670
|
+
};
|
|
671
|
+
return wrapKey(o, meta);
|
|
672
|
+
}
|
|
673
|
+
export function amount(raw) {
|
|
674
|
+
const o = normalizeOpts(raw);
|
|
675
|
+
const required = o.required ?? false;
|
|
676
|
+
const cfg = (o.config ?? {});
|
|
677
|
+
let s = z.coerce.number(typeErr());
|
|
678
|
+
if (cfg.min != null)
|
|
679
|
+
s = s.min(cfg.min, { message: vmsg('min', { min: cfg.min }) });
|
|
680
|
+
if (cfg.max != null)
|
|
681
|
+
s = s.max(cfg.max, { message: vmsg('max', { max: cfg.max }) });
|
|
682
|
+
const meta = {
|
|
683
|
+
kind: 'amount',
|
|
684
|
+
label: o.label ?? '',
|
|
685
|
+
required,
|
|
686
|
+
prim: 'number',
|
|
687
|
+
column: 'real',
|
|
688
|
+
hints: { unitFrom: o.unitFrom, min: cfg.min, max: cfg.max, step: cfg.step ?? 'any' },
|
|
689
|
+
zod: optionalize(s, required),
|
|
690
|
+
};
|
|
691
|
+
return wrapKey(o, meta);
|
|
692
|
+
}
|
|
638
693
|
export function money(raw) {
|
|
639
694
|
const o = normalizeOpts(raw);
|
|
640
695
|
const required = o.required ?? false;
|
|
@@ -1022,6 +1077,8 @@ const PRIMITIVES = {
|
|
|
1022
1077
|
json,
|
|
1023
1078
|
check,
|
|
1024
1079
|
measured,
|
|
1080
|
+
unit,
|
|
1081
|
+
amount,
|
|
1025
1082
|
money,
|
|
1026
1083
|
code,
|
|
1027
1084
|
geo,
|
|
@@ -1065,6 +1122,6 @@ export const field = new Proxy({}, {
|
|
|
1065
1122
|
has: (_t, k) => k in composedField(),
|
|
1066
1123
|
});
|
|
1067
1124
|
export function toClient(field) {
|
|
1068
|
-
const { kind, label, required, prim, hints, options, relation, virtual, json, hidden } = field;
|
|
1069
|
-
return { kind, label, required, prim, hints, options, relation, virtual, json, hidden };
|
|
1125
|
+
const { kind, label, required, prim, hints, options, relation, virtual, json, hidden, derived } = field;
|
|
1126
|
+
return { kind, label, required, prim, hints, options, relation, virtual, json, hidden, derived };
|
|
1070
1127
|
}
|
package/dist/plugin.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { ShelfDef } from './shelf.ts';
|
|
|
3
3
|
import type { ExtendDef } from './extend.ts';
|
|
4
4
|
import type { OptionItem } from './fields.ts';
|
|
5
5
|
import type { SettingsDef } from './settings.ts';
|
|
6
|
+
import type { ShowcaseDef } from './showcase.ts';
|
|
6
7
|
export interface FieldContrib {
|
|
7
8
|
library: string;
|
|
8
9
|
shelf: string;
|
|
@@ -24,6 +25,7 @@ export interface PluginManifest {
|
|
|
24
25
|
fieldContribs?: FieldContrib[];
|
|
25
26
|
optionLists?: NamedOptionList[];
|
|
26
27
|
settings?: SettingsDef;
|
|
28
|
+
showcases?: ShowcaseDef[];
|
|
27
29
|
}
|
|
28
30
|
export declare const BASE_PLUGIN = "core";
|
|
29
31
|
export declare function definePlugin(p: PluginManifest): PluginManifest;
|
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)) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Condition } from './condition.ts';
|
|
2
|
+
export interface ShowcaseDef {
|
|
3
|
+
id: string;
|
|
4
|
+
library: string;
|
|
5
|
+
label: string;
|
|
6
|
+
icon: string;
|
|
7
|
+
source: {
|
|
8
|
+
library: string;
|
|
9
|
+
shelf: string;
|
|
10
|
+
};
|
|
11
|
+
filter: Condition;
|
|
12
|
+
columns: string[];
|
|
13
|
+
createDefaults?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
export declare function defineShowcase(s: ShowcaseDef): ShowcaseDef;
|
package/dist/showcase.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { COMPILABLE_SCALAR_OPS, COMPILABLE_ARRAY_OPS } from "./condition.js";
|
|
2
|
+
const COMPILABLE_OPS = new Set([...COMPILABLE_SCALAR_OPS, ...COMPILABLE_ARRAY_OPS]);
|
|
3
|
+
function assertCompilableOps(cond, id) {
|
|
4
|
+
for (const [key, spec] of Object.entries(cond)) {
|
|
5
|
+
if (spec === undefined)
|
|
6
|
+
continue;
|
|
7
|
+
if (key === '$and' || key === '$or') {
|
|
8
|
+
for (const c of spec)
|
|
9
|
+
assertCompilableOps(c, id);
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
if (typeof spec === 'object' && spec !== null) {
|
|
13
|
+
for (const op of Object.keys(spec)) {
|
|
14
|
+
if (!COMPILABLE_OPS.has(op)) {
|
|
15
|
+
throw new Error(`[showcase] ${id}: operator '${op}' on '${key}' does not compile to SQL`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function defineShowcase(s) {
|
|
22
|
+
if (!s.id)
|
|
23
|
+
throw new Error('[showcase] missing id');
|
|
24
|
+
if (!s.library)
|
|
25
|
+
throw new Error(`[showcase] ${s.id}: missing library`);
|
|
26
|
+
if (!s.source?.library || !s.source?.shelf)
|
|
27
|
+
throw new Error(`[showcase] ${s.id}: missing source library/shelf`);
|
|
28
|
+
if (!s.columns?.length)
|
|
29
|
+
throw new Error(`[showcase] ${s.id}: columns must not be empty`);
|
|
30
|
+
if (!s.filter || Object.keys(s.filter).length === 0) {
|
|
31
|
+
throw new Error(`[showcase] ${s.id}: filter must not be empty — an unfiltered showcase is the shelf itself`);
|
|
32
|
+
}
|
|
33
|
+
assertCompilableOps(s.filter, s.id);
|
|
34
|
+
return s;
|
|
35
|
+
}
|
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.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24"
|
|
@@ -13,6 +13,14 @@
|
|
|
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
|
+
},
|
|
20
|
+
"./showcase": {
|
|
21
|
+
"types": "./dist/showcase.d.ts",
|
|
22
|
+
"default": "./dist/showcase.js"
|
|
23
|
+
},
|
|
16
24
|
"./*": {
|
|
17
25
|
"types": "./dist/*.d.ts",
|
|
18
26
|
"default": "./dist/*.js"
|