@coffer-org/plugin-documents 2.0.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/index.js +6 -1
- package/dist/person_identity/index.js +1 -1
- package/dist/personal_document/index.js +7 -3
- package/dist/schema.js +239 -53
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -8,7 +8,12 @@ export default definePlugin({
|
|
|
8
8
|
dependsOn: ['people'],
|
|
9
9
|
libraries: [
|
|
10
10
|
{
|
|
11
|
-
meta: defineLibrary({
|
|
11
|
+
meta: defineLibrary({
|
|
12
|
+
id: 'documents',
|
|
13
|
+
label: 'documents.library.label',
|
|
14
|
+
icon: 'lucide:id-card',
|
|
15
|
+
agent: 'Personal documents — IDs, passports, licences (personal_document); tracks expiry.',
|
|
16
|
+
}),
|
|
12
17
|
shelves: [personal_document],
|
|
13
18
|
},
|
|
14
19
|
],
|
|
@@ -6,7 +6,7 @@ export default defineExtend({
|
|
|
6
6
|
icon: 'lucide:globe',
|
|
7
7
|
attachTo: [{ library: 'people', shelf: 'person' }],
|
|
8
8
|
claude: `Canonical person identity (not tied to a physical document).
|
|
9
|
-
nationality — ISO alpha-2. variants — given/family name per language (field.
|
|
9
|
+
nationality — ISO alpha-2. variants — given/family name per language (field.keyed by lang: name+surname).`,
|
|
10
10
|
fields: [
|
|
11
11
|
field.select({ key: 'nationality', label: 'documents.person_identity.fields.nationality', options: 'countries' }),
|
|
12
12
|
field.keyed({
|
|
@@ -6,7 +6,7 @@ export default defineShelf({
|
|
|
6
6
|
label: 'documents.personal_document.label',
|
|
7
7
|
icon: 'lucide:id-card',
|
|
8
8
|
views: {
|
|
9
|
-
list:
|
|
9
|
+
list: ['owner', 'type', 'surname', 'expires'],
|
|
10
10
|
},
|
|
11
11
|
fields: [
|
|
12
12
|
field.title({ key: 'name', label: 'core.fields.name' }),
|
|
@@ -29,7 +29,11 @@ export default defineShelf({
|
|
|
29
29
|
field.date({ key: 'issued', label: 'documents.personal_document.fields.issued' }),
|
|
30
30
|
field.reminder({ key: 'expires', label: 'documents.personal_document.fields.expires', rules: { lead: 60 } }),
|
|
31
31
|
field.string({ key: 'authority', label: 'documents.personal_document.fields.authority', rules: { max: 200 } }),
|
|
32
|
-
field.relation({
|
|
32
|
+
field.relation({
|
|
33
|
+
key: 'storage',
|
|
34
|
+
label: 'documents.personal_document.fields.storage',
|
|
35
|
+
options: { library: 'things', shelf: 'storage_location' },
|
|
36
|
+
}),
|
|
33
37
|
field.string({ key: 'mrz', label: 'documents.personal_document.fields.mrz', rules: { max: 200 } }),
|
|
34
38
|
field.relation({
|
|
35
39
|
key: 'owner',
|
|
@@ -37,7 +41,6 @@ export default defineShelf({
|
|
|
37
41
|
options: { library: 'people', shelf: 'person' },
|
|
38
42
|
}),
|
|
39
43
|
field.image({ key: 'photos', label: 'documents.personal_document.fields.photos', multiple: true }),
|
|
40
|
-
field.text({ key: 'notes', label: 'documents.personal_document.fields.notes', rules: { max: 1000 } }),
|
|
41
44
|
field.select({
|
|
42
45
|
key: 'type',
|
|
43
46
|
label: 'core.fields.type',
|
|
@@ -53,6 +56,7 @@ export default defineShelf({
|
|
|
53
56
|
{ value: 'OT', title: 'documents.personal_document.options.type.OT' },
|
|
54
57
|
],
|
|
55
58
|
}),
|
|
59
|
+
field.text({ key: 'notes', label: 'documents.personal_document.fields.notes', rules: { max: 1000 } }),
|
|
56
60
|
field.tags({ key: 'tags', label: 'documents.personal_document.fields.tags' }),
|
|
57
61
|
],
|
|
58
62
|
});
|
package/dist/schema.js
CHANGED
|
@@ -4391,11 +4391,11 @@ function resolveUnits(u) {
|
|
|
4391
4391
|
//#endregion
|
|
4392
4392
|
//#region ../sdk/src/currencies.ts
|
|
4393
4393
|
/**
|
|
4394
|
-
*
|
|
4395
|
-
*
|
|
4394
|
+
* Currencies from built-in `Intl` (ISO 4217), with NO hand list or npm dependency.
|
|
4395
|
+
* Codes come from `Intl.supportedValuesOf('currency')`; symbols/names are localized through
|
|
4396
4396
|
* `Intl.NumberFormat`/`Intl.DisplayNames`.
|
|
4397
4397
|
*/
|
|
4398
|
-
/**
|
|
4398
|
+
/** All ISO 4217 codes. Fallback: several major codes for older runtimes. */
|
|
4399
4399
|
var CURRENCY_CODES = (() => {
|
|
4400
4400
|
try {
|
|
4401
4401
|
return Intl.supportedValuesOf("currency");
|
|
@@ -4410,26 +4410,26 @@ var CURRENCY_CODES = (() => {
|
|
|
4410
4410
|
}
|
|
4411
4411
|
})();
|
|
4412
4412
|
var CODE_SET = new Set(CURRENCY_CODES);
|
|
4413
|
-
/**
|
|
4413
|
+
/** Whether an ISO 4217 code is valid. */
|
|
4414
4414
|
var isCurrencyCode = (c) => CODE_SET.has(c);
|
|
4415
4415
|
//#endregion
|
|
4416
4416
|
//#region ../sdk/src/fields/validation.ts
|
|
4417
|
-
/**
|
|
4417
|
+
/** Structured message for zod: JSON {code, params}. Decoded by mutate.ts. */
|
|
4418
4418
|
function vmsg(code, params) {
|
|
4419
4419
|
return JSON.stringify(params ? {
|
|
4420
4420
|
code,
|
|
4421
4421
|
params
|
|
4422
4422
|
} : { code });
|
|
4423
4423
|
}
|
|
4424
|
-
/** v4 error-map:
|
|
4424
|
+
/** v4 error-map: message for a missing value (formerly required_error). */
|
|
4425
4425
|
function reqErr(code = "required") {
|
|
4426
4426
|
return { error: (iss) => iss.input === void 0 ? vmsg(code) : void 0 };
|
|
4427
4427
|
}
|
|
4428
|
-
/** v4 error-map:
|
|
4428
|
+
/** v4 error-map: message for an invalid type (formerly invalid_type_error). */
|
|
4429
4429
|
function typeErr(code = "invalid_type") {
|
|
4430
4430
|
return { error: (iss) => iss.code === "invalid_type" ? vmsg(code) : void 0 };
|
|
4431
4431
|
}
|
|
4432
|
-
/** v4 error-map: required + invalid_type
|
|
4432
|
+
/** v4 error-map: required + invalid_type together (formerly required_error + invalid_type_error). */
|
|
4433
4433
|
function reqTypeErr() {
|
|
4434
4434
|
return { error: (iss) => iss.code === "invalid_type" ? iss.input === void 0 ? vmsg("required") : vmsg("invalid_type") : void 0 };
|
|
4435
4435
|
}
|
|
@@ -4447,10 +4447,10 @@ function jsonValue(raw) {
|
|
|
4447
4447
|
return raw;
|
|
4448
4448
|
}
|
|
4449
4449
|
/**
|
|
4450
|
-
*
|
|
4451
|
-
*
|
|
4452
|
-
* Single
|
|
4453
|
-
*
|
|
4450
|
+
* Factory for fields that store JSON and validate it with a nested zod schema.
|
|
4451
|
+
* Accepts a native object/array (native form state) OR a JSON string (legacy).
|
|
4452
|
+
* Single parse through jsonValue → inner.safeParse → issue `code`; an unparseable
|
|
4453
|
+
* string remains a string → code 'json'.
|
|
4454
4454
|
*/
|
|
4455
4455
|
function jsonRefined(inner, code) {
|
|
4456
4456
|
return unknown().superRefine((raw, ctx) => {
|
|
@@ -4502,6 +4502,8 @@ function normalizeOpts(rawIn) {
|
|
|
4502
4502
|
unique: r.unique,
|
|
4503
4503
|
fixed: r.fixed,
|
|
4504
4504
|
exts: r.exts,
|
|
4505
|
+
ext: r.ext,
|
|
4506
|
+
maxBytes: r.maxBytes,
|
|
4505
4507
|
lead: r.lead,
|
|
4506
4508
|
by: r.by,
|
|
4507
4509
|
min: r.min,
|
|
@@ -4522,16 +4524,16 @@ function normalizeOpts(rawIn) {
|
|
|
4522
4524
|
//#endregion
|
|
4523
4525
|
//#region ../sdk/src/field-presets.ts
|
|
4524
4526
|
/**
|
|
4525
|
-
*
|
|
4527
|
+
* Field presets are thin wrappers around the primitives in fields.ts.
|
|
4526
4528
|
*
|
|
4527
|
-
*
|
|
4528
|
-
*
|
|
4529
|
+
* Each preset = one kind (one widget). Presets do not accept `format`;
|
|
4530
|
+
* they are semantic types themselves. min/max/step go through `config`.
|
|
4529
4531
|
*
|
|
4530
|
-
*
|
|
4531
|
-
*
|
|
4532
|
+
* Presets are added to `f` through `composeF`, which guarantees no preset
|
|
4533
|
+
* overrides a primitive.
|
|
4532
4534
|
*
|
|
4533
|
-
*
|
|
4534
|
-
*
|
|
4535
|
+
* The cyclic import from fields.ts is safe: factories/helpers are hoisted declarations,
|
|
4536
|
+
* and presets call them only inside their function bodies.
|
|
4535
4537
|
*/
|
|
4536
4538
|
function email(raw) {
|
|
4537
4539
|
const o = normalizeOpts(raw);
|
|
@@ -4705,9 +4707,9 @@ function link(raw) {
|
|
|
4705
4707
|
}, o.multiple ?? false));
|
|
4706
4708
|
}
|
|
4707
4709
|
var TEL_RE = /^\+?[\d\s()-]{4,}$/;
|
|
4708
|
-
/** Loose URL:
|
|
4710
|
+
/** Loose URL: any scheme:// OR dotted host (optional port/path). No spaces. */
|
|
4709
4711
|
var LINK_RE = /^([a-z][a-z0-9+.-]*:\/\/\S+|[\w-]+(\.[\w-]+)+(:\d+)?(\/\S*)?)$/i;
|
|
4710
|
-
/** CSS named colors (CSS Color Module L4)
|
|
4712
|
+
/** CSS named colors (CSS Color Module L4) for f.colorname. */
|
|
4711
4713
|
var CSS_COLOR_NAMES = /* @__PURE__ */ new Set([
|
|
4712
4714
|
"aliceblue",
|
|
4713
4715
|
"antiquewhite",
|
|
@@ -4913,6 +4915,42 @@ function snippet(raw) {
|
|
|
4913
4915
|
}
|
|
4914
4916
|
});
|
|
4915
4917
|
}
|
|
4918
|
+
/** A NUL byte, or U+FFFD left behind by decoding non-UTF-8 bytes as text. */
|
|
4919
|
+
function isBinaryText(v) {
|
|
4920
|
+
return v.includes("\0") || v.includes("�");
|
|
4921
|
+
}
|
|
4922
|
+
function source(raw) {
|
|
4923
|
+
const o = normalizeOpts(raw);
|
|
4924
|
+
const required = o.required ?? false;
|
|
4925
|
+
const ext = o.ext ?? "txt";
|
|
4926
|
+
const maxBytes = o.maxBytes ?? 262144;
|
|
4927
|
+
const s = string$1(reqTypeErr()).superRefine((v, ctx) => {
|
|
4928
|
+
if (isBinaryText(v)) {
|
|
4929
|
+
ctx.addIssue({
|
|
4930
|
+
code: ZodIssueCode.custom,
|
|
4931
|
+
message: vmsg("source_binary")
|
|
4932
|
+
});
|
|
4933
|
+
return;
|
|
4934
|
+
}
|
|
4935
|
+
if (new TextEncoder().encode(v).length > maxBytes) ctx.addIssue({
|
|
4936
|
+
code: ZodIssueCode.custom,
|
|
4937
|
+
message: vmsg("source_too_large", { maxBytes })
|
|
4938
|
+
});
|
|
4939
|
+
});
|
|
4940
|
+
return wrapKey(o, {
|
|
4941
|
+
kind: "source",
|
|
4942
|
+
label: o.label ?? "",
|
|
4943
|
+
required,
|
|
4944
|
+
prim: "text",
|
|
4945
|
+
column: "text",
|
|
4946
|
+
hints: {
|
|
4947
|
+
ext,
|
|
4948
|
+
maxBytes,
|
|
4949
|
+
noEditControl: true
|
|
4950
|
+
},
|
|
4951
|
+
zod: optionalize(s, required)
|
|
4952
|
+
});
|
|
4953
|
+
}
|
|
4916
4954
|
function rating(raw) {
|
|
4917
4955
|
const o = normalizeOpts(raw);
|
|
4918
4956
|
const required = o.required ?? false;
|
|
@@ -4966,7 +5004,7 @@ function reminder(raw) {
|
|
|
4966
5004
|
}
|
|
4967
5005
|
var _real = real;
|
|
4968
5006
|
var _int = int;
|
|
4969
|
-
/**
|
|
5007
|
+
/** Percentage 0..100 — real with rules:{min:0,max:100}. */
|
|
4970
5008
|
function percent(o) {
|
|
4971
5009
|
return _real({
|
|
4972
5010
|
...o,
|
|
@@ -4977,7 +5015,7 @@ function percent(o) {
|
|
|
4977
5015
|
}
|
|
4978
5016
|
});
|
|
4979
5017
|
}
|
|
4980
|
-
/**
|
|
5018
|
+
/** Year — int with rules:{min:1900,max:2100}; bounds can be overridden via rules.min/max. */
|
|
4981
5019
|
function year(o) {
|
|
4982
5020
|
return _int({
|
|
4983
5021
|
...o,
|
|
@@ -5049,6 +5087,7 @@ var presets = {
|
|
|
5049
5087
|
tags,
|
|
5050
5088
|
markdown,
|
|
5051
5089
|
snippet,
|
|
5090
|
+
source,
|
|
5052
5091
|
rating,
|
|
5053
5092
|
duration,
|
|
5054
5093
|
reminder,
|
|
@@ -5974,6 +6013,7 @@ function keyed(o) {
|
|
|
5974
6013
|
};
|
|
5975
6014
|
}
|
|
5976
6015
|
var isCollectionGroup = (g) => g.key != null && g.multiple === true;
|
|
6016
|
+
var isEmbeddedGroup = (g) => g.key != null && g.multiple !== true;
|
|
5977
6017
|
/** Horizontal divider. */
|
|
5978
6018
|
function divider() {
|
|
5979
6019
|
return { el: "divider" };
|
|
@@ -6030,13 +6070,6 @@ function wrapKey(opts, meta) {
|
|
|
6030
6070
|
role: opts.role
|
|
6031
6071
|
}
|
|
6032
6072
|
};
|
|
6033
|
-
if (opts.pinned) m = {
|
|
6034
|
-
...m,
|
|
6035
|
-
hints: {
|
|
6036
|
-
...m.hints,
|
|
6037
|
-
pinned: true
|
|
6038
|
-
}
|
|
6039
|
-
};
|
|
6040
6073
|
if (opts.default !== void 0) m = {
|
|
6041
6074
|
...m,
|
|
6042
6075
|
default: opts.default
|
|
@@ -6045,6 +6078,15 @@ function wrapKey(opts, meta) {
|
|
|
6045
6078
|
...m,
|
|
6046
6079
|
hidden: opts.hidden
|
|
6047
6080
|
};
|
|
6081
|
+
if (opts.derive) m = {
|
|
6082
|
+
...m,
|
|
6083
|
+
derive: opts.derive,
|
|
6084
|
+
derived: true,
|
|
6085
|
+
hints: {
|
|
6086
|
+
...m.hints,
|
|
6087
|
+
noEditControl: true
|
|
6088
|
+
}
|
|
6089
|
+
};
|
|
6048
6090
|
if (opts.key) return {
|
|
6049
6091
|
key: opts.key,
|
|
6050
6092
|
type: m
|
|
@@ -6359,11 +6401,18 @@ function triState(raw) {
|
|
|
6359
6401
|
zod: optionalize(s, required)
|
|
6360
6402
|
});
|
|
6361
6403
|
}
|
|
6404
|
+
/** Inline option entry → OptionItem (plain string = value and label at once). */
|
|
6405
|
+
function toOptionItem(o) {
|
|
6406
|
+
return typeof o === "string" ? {
|
|
6407
|
+
value: o,
|
|
6408
|
+
title: o
|
|
6409
|
+
} : o;
|
|
6410
|
+
}
|
|
6362
6411
|
function select(raw) {
|
|
6363
6412
|
const o = normalizeOpts(raw);
|
|
6364
6413
|
const required = o.required ?? false;
|
|
6365
6414
|
const source = typeof o.options === "string" ? o.options : null;
|
|
6366
|
-
const inlineOpts = Array.isArray(o.options) ? o.options : [];
|
|
6415
|
+
const inlineOpts = Array.isArray(o.options) ? o.options.map(toOptionItem) : [];
|
|
6367
6416
|
const s = inlineOpts.length > 0 ? _enum(inlineOpts.map((x) => x.value), { error: () => vmsg("enum") }) : string$1(reqErr());
|
|
6368
6417
|
return wrapKey(o, applyMultiple({
|
|
6369
6418
|
kind: "select",
|
|
@@ -6436,15 +6485,23 @@ function check(raw) {
|
|
|
6436
6485
|
const required = o.required ?? false;
|
|
6437
6486
|
const multiple = o.multiple ?? false;
|
|
6438
6487
|
const slots = o.slots && o.slots.length > 0 ? o.slots : [""];
|
|
6488
|
+
const contentFields = o.fields?.length ? o.fields : [string({
|
|
6489
|
+
key: "text",
|
|
6490
|
+
label: "core.fields.text"
|
|
6491
|
+
})];
|
|
6492
|
+
const contentKeys = /* @__PURE__ */ new Set();
|
|
6493
|
+
for (const f of contentFields) {
|
|
6494
|
+
if (contentKeys.has(f.key)) throw new Error(`[field.check] duplicate content field '${f.key}'`);
|
|
6495
|
+
if (/^check\d+$/.test(f.key)) throw new Error(`[field.check] content field '${f.key}' is reserved for checkbox slots`);
|
|
6496
|
+
if (!multiple && (f.type.virtual || f.type.columns)) throw new Error(`[field.check] single content field '${f.key}' must be a scalar stored field`);
|
|
6497
|
+
contentKeys.add(f.key);
|
|
6498
|
+
}
|
|
6499
|
+
const checkFields = slots.map((label, i) => boolean({
|
|
6500
|
+
key: `check${i}`,
|
|
6501
|
+
label: label || "core.fields.check"
|
|
6502
|
+
}));
|
|
6439
6503
|
if (multiple) {
|
|
6440
|
-
const fields =
|
|
6441
|
-
key: `check${i}`,
|
|
6442
|
-
label: label || "core.fields.check"
|
|
6443
|
-
}));
|
|
6444
|
-
fields.push(string({
|
|
6445
|
-
key: "text",
|
|
6446
|
-
label: "core.fields.text"
|
|
6447
|
-
}));
|
|
6504
|
+
const fields = [...checkFields, ...contentFields];
|
|
6448
6505
|
return group({
|
|
6449
6506
|
key: o.key,
|
|
6450
6507
|
label: o.label ?? o.key,
|
|
@@ -6454,8 +6511,12 @@ function check(raw) {
|
|
|
6454
6511
|
view: { kind: "checklist" }
|
|
6455
6512
|
});
|
|
6456
6513
|
}
|
|
6457
|
-
const shape = {
|
|
6458
|
-
const columns = {
|
|
6514
|
+
const shape = {};
|
|
6515
|
+
const columns = {};
|
|
6516
|
+
for (const f of contentFields) {
|
|
6517
|
+
shape[f.key] = f.type.zod;
|
|
6518
|
+
columns[f.key] = f.type.column;
|
|
6519
|
+
}
|
|
6459
6520
|
slots.forEach((_, i) => {
|
|
6460
6521
|
shape[`check${i}`] = boolean$1();
|
|
6461
6522
|
columns[`check${i}`] = "boolean";
|
|
@@ -6481,7 +6542,13 @@ function check(raw) {
|
|
|
6481
6542
|
required,
|
|
6482
6543
|
prim: "check",
|
|
6483
6544
|
column: "text",
|
|
6484
|
-
hints: {
|
|
6545
|
+
hints: {
|
|
6546
|
+
slots,
|
|
6547
|
+
fields: contentFields.map((f) => ({
|
|
6548
|
+
key: f.key,
|
|
6549
|
+
...toClient(f.type)
|
|
6550
|
+
}))
|
|
6551
|
+
},
|
|
6485
6552
|
columns,
|
|
6486
6553
|
zod: optionalize(s, required)
|
|
6487
6554
|
});
|
|
@@ -6542,6 +6609,47 @@ function measured(raw) {
|
|
|
6542
6609
|
zod: optionalize(s, required)
|
|
6543
6610
|
});
|
|
6544
6611
|
}
|
|
6612
|
+
function unit(raw) {
|
|
6613
|
+
const o = normalizeOpts(raw);
|
|
6614
|
+
const required = o.required ?? false;
|
|
6615
|
+
const units = resolveUnits(o.options);
|
|
6616
|
+
const s = string$1(reqErr()).refine((v) => units.some((u) => u.value === v), { message: vmsg("measured_unit") });
|
|
6617
|
+
return wrapKey(o, {
|
|
6618
|
+
kind: "unit",
|
|
6619
|
+
label: o.label ?? "",
|
|
6620
|
+
required,
|
|
6621
|
+
prim: "select",
|
|
6622
|
+
column: "text",
|
|
6623
|
+
hints: { source: typeof o.options === "string" ? o.options : null },
|
|
6624
|
+
options: units.map((u) => ({
|
|
6625
|
+
value: u.value,
|
|
6626
|
+
title: u.label
|
|
6627
|
+
})),
|
|
6628
|
+
zod: optionalize(s, required)
|
|
6629
|
+
});
|
|
6630
|
+
}
|
|
6631
|
+
function amount(raw) {
|
|
6632
|
+
const o = normalizeOpts(raw);
|
|
6633
|
+
const required = o.required ?? false;
|
|
6634
|
+
const cfg = o.config ?? {};
|
|
6635
|
+
let s = number(typeErr());
|
|
6636
|
+
if (cfg.min != null) s = s.min(cfg.min, { message: vmsg("min", { min: cfg.min }) });
|
|
6637
|
+
if (cfg.max != null) s = s.max(cfg.max, { message: vmsg("max", { max: cfg.max }) });
|
|
6638
|
+
return wrapKey(o, {
|
|
6639
|
+
kind: "amount",
|
|
6640
|
+
label: o.label ?? "",
|
|
6641
|
+
required,
|
|
6642
|
+
prim: "number",
|
|
6643
|
+
column: "real",
|
|
6644
|
+
hints: {
|
|
6645
|
+
unitFrom: o.unitFrom,
|
|
6646
|
+
min: cfg.min,
|
|
6647
|
+
max: cfg.max,
|
|
6648
|
+
step: cfg.step ?? "any"
|
|
6649
|
+
},
|
|
6650
|
+
zod: optionalize(s, required)
|
|
6651
|
+
});
|
|
6652
|
+
}
|
|
6545
6653
|
function money(raw) {
|
|
6546
6654
|
const o = normalizeOpts(raw);
|
|
6547
6655
|
const required = o.required ?? false;
|
|
@@ -7019,6 +7127,8 @@ var PRIMITIVES = {
|
|
|
7019
7127
|
json,
|
|
7020
7128
|
check,
|
|
7021
7129
|
measured,
|
|
7130
|
+
unit,
|
|
7131
|
+
amount,
|
|
7022
7132
|
money,
|
|
7023
7133
|
code,
|
|
7024
7134
|
geo,
|
|
@@ -7064,7 +7174,7 @@ var field = new Proxy({}, {
|
|
|
7064
7174
|
has: (_t, k) => k in composedField()
|
|
7065
7175
|
});
|
|
7066
7176
|
function toClient(field) {
|
|
7067
|
-
const { kind, label, required, prim, hints, options, relation, virtual, json, hidden } = field;
|
|
7177
|
+
const { kind, label, required, prim, hints, options, relation, virtual, json, hidden, derived } = field;
|
|
7068
7178
|
return {
|
|
7069
7179
|
kind,
|
|
7070
7180
|
label,
|
|
@@ -7075,7 +7185,8 @@ function toClient(field) {
|
|
|
7075
7185
|
relation,
|
|
7076
7186
|
virtual,
|
|
7077
7187
|
json,
|
|
7078
|
-
hidden
|
|
7188
|
+
hidden,
|
|
7189
|
+
derived
|
|
7079
7190
|
};
|
|
7080
7191
|
}
|
|
7081
7192
|
//#endregion
|
|
@@ -7753,6 +7864,41 @@ var createDefaultQueryTester = function(query, options) {
|
|
|
7753
7864
|
};
|
|
7754
7865
|
createDefaultQueryTester.createEqualsOperation;
|
|
7755
7866
|
//#endregion
|
|
7867
|
+
//#region ../sdk/src/derive.ts
|
|
7868
|
+
/**
|
|
7869
|
+
* Derived fields: a value the SERVER computes on every write and stores in a real
|
|
7870
|
+
* column. Unlike a computed StaticEl (`value: fn`), which the browser renders and
|
|
7871
|
+
* nothing persists, a derived field keeps its column — so list views, filters,
|
|
7872
|
+
* sorting, field.lookup and MCP consumers see it like any other field.
|
|
7873
|
+
*
|
|
7874
|
+
* The function is pure, synchronous and isomorphic: it receives the fully hydrated
|
|
7875
|
+
* record (base scalars, embedded groups, collections, extends) and no database
|
|
7876
|
+
* handle. That is what lets the same declaration run on the server at write time
|
|
7877
|
+
* and in the browser for live preview.
|
|
7878
|
+
*
|
|
7879
|
+
* Scope: top-level fields of the record only. A derived field inside a collection
|
|
7880
|
+
* row is not supported.
|
|
7881
|
+
*/
|
|
7882
|
+
/** [key, spec] for every top-level derived field. Layout groups are flattened. */
|
|
7883
|
+
function derivedEntries(fields) {
|
|
7884
|
+
const out = [];
|
|
7885
|
+
for (const it of fields) if (isField(it)) {
|
|
7886
|
+
const spec = it.type.derive;
|
|
7887
|
+
if (spec) out.push([it.key, spec]);
|
|
7888
|
+
} else if (isGroup(it) && !it.key) out.push(...derivedEntries(it.fields));
|
|
7889
|
+
return out;
|
|
7890
|
+
}
|
|
7891
|
+
/** Keys a shelf's derive specs may reference: its own fields plus its collections. */
|
|
7892
|
+
function derivableKeys(fields) {
|
|
7893
|
+
const keys = /* @__PURE__ */ new Set();
|
|
7894
|
+
for (const it of fields) if (isField(it)) keys.add(it.key);
|
|
7895
|
+
else if (isGroup(it)) {
|
|
7896
|
+
if (it.key) keys.add(it.key);
|
|
7897
|
+
if (!it.key) for (const k of derivableKeys(it.fields)) keys.add(k);
|
|
7898
|
+
}
|
|
7899
|
+
return keys;
|
|
7900
|
+
}
|
|
7901
|
+
//#endregion
|
|
7756
7902
|
//#region ../sdk/src/shelf.ts
|
|
7757
7903
|
/** Flat [storageKey, FieldMeta] pairs of the parent table's columns.
|
|
7758
7904
|
* layout group → flattened; embedded group → prefix `key__`; collection → skipped. */
|
|
@@ -7770,9 +7916,49 @@ function defineShelf(m) {
|
|
|
7770
7916
|
const keys = fieldEntries(m.fields).map(([k]) => k);
|
|
7771
7917
|
const dup = keys.find((k, i) => keys.indexOf(k) !== i);
|
|
7772
7918
|
if (dup) throw new Error(`[shelf] ${m.library}/${m.shelf}: duplicate key '${dup}'`);
|
|
7773
|
-
if (m.
|
|
7919
|
+
if (m.single && !m.claude) throw new Error(`[shelf] ${m.library}/${m.shelf}: single shelf requires \`claude\` — the agent cannot find it otherwise`);
|
|
7920
|
+
if (m.views?.list !== void 0 && !Array.isArray(m.views.list)) throw new Error(`[shelf] ${m.library}/${m.shelf}: views.list is a flat array of field keys — the { kind, fields } form is gone; use views.list: [...] plus views.listKind`);
|
|
7921
|
+
if (m.standalone !== false && !m.single && !m.views?.list?.length) console.warn(`[shelf] ${m.library}/${m.shelf}: standalone shelf without an explicit views.list`);
|
|
7922
|
+
if (m.views?.title && !ownFieldEntries(m.fields).some(([k]) => k === m.views.title)) throw new Error(`[shelf] ${m.library}/${m.shelf}: views.title '${m.views.title}' is not a field`);
|
|
7923
|
+
if (m.views?.list?.includes(titleKey(m))) console.warn(`[shelf] ${m.library}/${m.shelf}: views.list repeats the title '${titleKey(m)}' — the title is declared separately`);
|
|
7924
|
+
const titles = ownFieldEntries(m.fields).filter(([, f]) => f.kind === "title");
|
|
7925
|
+
if (titles.length > 1) console.warn(`[shelf] ${m.library}/${m.shelf}: ${titles.length} title fields (${titles.map(([k]) => k).join(", ")}) — '${titles[0][0]}' wins`);
|
|
7926
|
+
if (m.standalone !== false && !m.single && titleKey(m) === "id") console.warn(`[shelf] ${m.library}/${m.shelf}: no title declared (field.title() or views.title) — the record renders no heading`);
|
|
7927
|
+
const known = derivableKeys(m.fields);
|
|
7928
|
+
for (const [key, spec] of derivedEntries(m.fields)) for (const dep of spec.deps) if (!known.has(dep)) throw new Error(`[defineShelf] ${m.library}/${m.shelf}: derive on '${key}' depends on unknown key '${dep}'`);
|
|
7774
7929
|
return m;
|
|
7775
7930
|
}
|
|
7931
|
+
/**
|
|
7932
|
+
* Contract of a real entity:
|
|
7933
|
+
* id — uuid (the ONLY required system field, auto).
|
|
7934
|
+
* The rest (including name) are regular fields in ShelfDef.fields.
|
|
7935
|
+
* created_at/updated_at — system infrastructure (outside fields, auto-managed).
|
|
7936
|
+
*/
|
|
7937
|
+
/**
|
|
7938
|
+
* Keyed fields of the record itself, in declaration order: top level and plain
|
|
7939
|
+
* layout groups. An embedded group or a collection describes a sub-record, so its
|
|
7940
|
+
* fields are not candidates for the record's own heading.
|
|
7941
|
+
*/
|
|
7942
|
+
function ownFieldEntries(items) {
|
|
7943
|
+
const out = [];
|
|
7944
|
+
for (const it of items) if (isField(it)) out.push([it.key, it.type]);
|
|
7945
|
+
else if (isGroup(it) && !isCollectionGroup(it) && !isEmbeddedGroup(it)) out.push(...ownFieldEntries(it.fields));
|
|
7946
|
+
return out;
|
|
7947
|
+
}
|
|
7948
|
+
/**
|
|
7949
|
+
* Key of the record's title field (lists, relation pickers, inlines, record heading).
|
|
7950
|
+
* Priority: views.title → the first keyed kind:'title' field → 'id'.
|
|
7951
|
+
*
|
|
7952
|
+
* The title is declared, never guessed: a shelf that wants a heading marks the field
|
|
7953
|
+
* with field.title(), or names another field through views.title when the heading is
|
|
7954
|
+
* not a text field (a date, a virtual value). A shelf that declares nothing resolves
|
|
7955
|
+
* to 'id' and renders no heading — every field stays in the body with its own label.
|
|
7956
|
+
*/
|
|
7957
|
+
function titleKey(m) {
|
|
7958
|
+
if (m.views?.title) return m.views.title;
|
|
7959
|
+
const declared = ownFieldEntries(m.fields).find(([, f]) => f.kind === "title");
|
|
7960
|
+
return declared ? declared[0] : "id";
|
|
7961
|
+
}
|
|
7776
7962
|
//#endregion
|
|
7777
7963
|
//#region src/personal_document/index.ts
|
|
7778
7964
|
var personal_document_default = defineShelf({
|
|
@@ -7780,12 +7966,12 @@ var personal_document_default = defineShelf({
|
|
|
7780
7966
|
library: "documents",
|
|
7781
7967
|
label: "documents.personal_document.label",
|
|
7782
7968
|
icon: "lucide:id-card",
|
|
7783
|
-
views: { list:
|
|
7969
|
+
views: { list: [
|
|
7784
7970
|
"owner",
|
|
7785
7971
|
"type",
|
|
7786
7972
|
"surname",
|
|
7787
7973
|
"expires"
|
|
7788
|
-
] }
|
|
7974
|
+
] },
|
|
7789
7975
|
fields: [
|
|
7790
7976
|
field.title({
|
|
7791
7977
|
key: "name",
|
|
@@ -7880,11 +8066,6 @@ var personal_document_default = defineShelf({
|
|
|
7880
8066
|
label: "documents.personal_document.fields.photos",
|
|
7881
8067
|
multiple: true
|
|
7882
8068
|
}),
|
|
7883
|
-
field.text({
|
|
7884
|
-
key: "notes",
|
|
7885
|
-
label: "documents.personal_document.fields.notes",
|
|
7886
|
-
rules: { max: 1e3 }
|
|
7887
|
-
}),
|
|
7888
8069
|
field.select({
|
|
7889
8070
|
key: "type",
|
|
7890
8071
|
label: "core.fields.type",
|
|
@@ -7924,6 +8105,11 @@ var personal_document_default = defineShelf({
|
|
|
7924
8105
|
}
|
|
7925
8106
|
]
|
|
7926
8107
|
}),
|
|
8108
|
+
field.text({
|
|
8109
|
+
key: "notes",
|
|
8110
|
+
label: "documents.personal_document.fields.notes",
|
|
8111
|
+
rules: { max: 1e3 }
|
|
8112
|
+
}),
|
|
7927
8113
|
field.tags({
|
|
7928
8114
|
key: "tags",
|
|
7929
8115
|
label: "documents.personal_document.fields.tags"
|
|
@@ -7954,7 +8140,7 @@ var person_identity_default = defineExtend({
|
|
|
7954
8140
|
shelf: "person"
|
|
7955
8141
|
}],
|
|
7956
8142
|
claude: `Canonical person identity (not tied to a physical document).
|
|
7957
|
-
nationality — ISO alpha-2. variants — given/family name per language (field.
|
|
8143
|
+
nationality — ISO alpha-2. variants — given/family name per language (field.keyed by lang: name+surname).`,
|
|
7958
8144
|
fields: [field.select({
|
|
7959
8145
|
key: "nationality",
|
|
7960
8146
|
label: "documents.person_identity.fields.nationality",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coffer-org/plugin-documents",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24"
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"postpack": "node ../../scripts/swap-exports.mjs src"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@coffer-org/plugin-people": "^
|
|
29
|
-
"@coffer-org/sdk": "^
|
|
30
|
-
"@coffer-org/server": "^
|
|
28
|
+
"@coffer-org/plugin-people": "^3.0.0",
|
|
29
|
+
"@coffer-org/sdk": "^3.0.0",
|
|
30
|
+
"@coffer-org/server": "^3.0.0"
|
|
31
31
|
},
|
|
32
32
|
"coffer": {
|
|
33
33
|
"schema": "dist/schema.js"
|