@coffer-org/plugin-transit 2.1.1 → 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/bus_route/index.js +1 -1
- package/dist/index.js +6 -1
- package/dist/schema.js +210 -17
- package/package.json +4 -4
package/dist/bus_route/index.js
CHANGED
|
@@ -6,7 +6,7 @@ export default defineShelf({
|
|
|
6
6
|
label: 'transit.bus_route.label',
|
|
7
7
|
icon: 'lucide:bus',
|
|
8
8
|
views: {
|
|
9
|
-
list:
|
|
9
|
+
list: ['route_number', 'from', 'to'],
|
|
10
10
|
},
|
|
11
11
|
fields: [
|
|
12
12
|
field.title({ key: 'name', label: 'core.fields.name' }),
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,12 @@ export default definePlugin({
|
|
|
9
9
|
dependsOn: [],
|
|
10
10
|
libraries: [
|
|
11
11
|
{
|
|
12
|
-
meta: defineLibrary({
|
|
12
|
+
meta: defineLibrary({
|
|
13
|
+
id: 'transit',
|
|
14
|
+
label: 'transit.library.label',
|
|
15
|
+
icon: 'lucide:bus',
|
|
16
|
+
agent: 'Local bus routes and their departure schedules (bus_route).',
|
|
17
|
+
}),
|
|
13
18
|
shelves: [bus_route],
|
|
14
19
|
},
|
|
15
20
|
],
|
package/dist/schema.js
CHANGED
|
@@ -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,
|
|
@@ -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;
|
|
@@ -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" };
|
|
@@ -6038,6 +6078,15 @@ function wrapKey(opts, meta) {
|
|
|
6038
6078
|
...m,
|
|
6039
6079
|
hidden: opts.hidden
|
|
6040
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
|
+
};
|
|
6041
6090
|
if (opts.key) return {
|
|
6042
6091
|
key: opts.key,
|
|
6043
6092
|
type: m
|
|
@@ -6352,11 +6401,18 @@ function triState(raw) {
|
|
|
6352
6401
|
zod: optionalize(s, required)
|
|
6353
6402
|
});
|
|
6354
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
|
+
}
|
|
6355
6411
|
function select(raw) {
|
|
6356
6412
|
const o = normalizeOpts(raw);
|
|
6357
6413
|
const required = o.required ?? false;
|
|
6358
6414
|
const source = typeof o.options === "string" ? o.options : null;
|
|
6359
|
-
const inlineOpts = Array.isArray(o.options) ? o.options : [];
|
|
6415
|
+
const inlineOpts = Array.isArray(o.options) ? o.options.map(toOptionItem) : [];
|
|
6360
6416
|
const s = inlineOpts.length > 0 ? _enum(inlineOpts.map((x) => x.value), { error: () => vmsg("enum") }) : string$1(reqErr());
|
|
6361
6417
|
return wrapKey(o, applyMultiple({
|
|
6362
6418
|
kind: "select",
|
|
@@ -6429,15 +6485,23 @@ function check(raw) {
|
|
|
6429
6485
|
const required = o.required ?? false;
|
|
6430
6486
|
const multiple = o.multiple ?? false;
|
|
6431
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
|
+
}));
|
|
6432
6503
|
if (multiple) {
|
|
6433
|
-
const fields =
|
|
6434
|
-
key: `check${i}`,
|
|
6435
|
-
label: label || "core.fields.check"
|
|
6436
|
-
}));
|
|
6437
|
-
fields.push(string({
|
|
6438
|
-
key: "text",
|
|
6439
|
-
label: "core.fields.text"
|
|
6440
|
-
}));
|
|
6504
|
+
const fields = [...checkFields, ...contentFields];
|
|
6441
6505
|
return group({
|
|
6442
6506
|
key: o.key,
|
|
6443
6507
|
label: o.label ?? o.key,
|
|
@@ -6447,8 +6511,12 @@ function check(raw) {
|
|
|
6447
6511
|
view: { kind: "checklist" }
|
|
6448
6512
|
});
|
|
6449
6513
|
}
|
|
6450
|
-
const shape = {
|
|
6451
|
-
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
|
+
}
|
|
6452
6520
|
slots.forEach((_, i) => {
|
|
6453
6521
|
shape[`check${i}`] = boolean$1();
|
|
6454
6522
|
columns[`check${i}`] = "boolean";
|
|
@@ -6474,7 +6542,13 @@ function check(raw) {
|
|
|
6474
6542
|
required,
|
|
6475
6543
|
prim: "check",
|
|
6476
6544
|
column: "text",
|
|
6477
|
-
hints: {
|
|
6545
|
+
hints: {
|
|
6546
|
+
slots,
|
|
6547
|
+
fields: contentFields.map((f) => ({
|
|
6548
|
+
key: f.key,
|
|
6549
|
+
...toClient(f.type)
|
|
6550
|
+
}))
|
|
6551
|
+
},
|
|
6478
6552
|
columns,
|
|
6479
6553
|
zod: optionalize(s, required)
|
|
6480
6554
|
});
|
|
@@ -6535,6 +6609,47 @@ function measured(raw) {
|
|
|
6535
6609
|
zod: optionalize(s, required)
|
|
6536
6610
|
});
|
|
6537
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
|
+
}
|
|
6538
6653
|
function money(raw) {
|
|
6539
6654
|
const o = normalizeOpts(raw);
|
|
6540
6655
|
const required = o.required ?? false;
|
|
@@ -7012,6 +7127,8 @@ var PRIMITIVES = {
|
|
|
7012
7127
|
json,
|
|
7013
7128
|
check,
|
|
7014
7129
|
measured,
|
|
7130
|
+
unit,
|
|
7131
|
+
amount,
|
|
7015
7132
|
money,
|
|
7016
7133
|
code,
|
|
7017
7134
|
geo,
|
|
@@ -7057,7 +7174,7 @@ var field = new Proxy({}, {
|
|
|
7057
7174
|
has: (_t, k) => k in composedField()
|
|
7058
7175
|
});
|
|
7059
7176
|
function toClient(field) {
|
|
7060
|
-
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;
|
|
7061
7178
|
return {
|
|
7062
7179
|
kind,
|
|
7063
7180
|
label,
|
|
@@ -7068,7 +7185,8 @@ function toClient(field) {
|
|
|
7068
7185
|
relation,
|
|
7069
7186
|
virtual,
|
|
7070
7187
|
json,
|
|
7071
|
-
hidden
|
|
7188
|
+
hidden,
|
|
7189
|
+
derived
|
|
7072
7190
|
};
|
|
7073
7191
|
}
|
|
7074
7192
|
//#endregion
|
|
@@ -7751,6 +7869,41 @@ var createDefaultQueryTester = function(query, options) {
|
|
|
7751
7869
|
};
|
|
7752
7870
|
createDefaultQueryTester.createEqualsOperation;
|
|
7753
7871
|
//#endregion
|
|
7872
|
+
//#region ../sdk/src/derive.ts
|
|
7873
|
+
/**
|
|
7874
|
+
* Derived fields: a value the SERVER computes on every write and stores in a real
|
|
7875
|
+
* column. Unlike a computed StaticEl (`value: fn`), which the browser renders and
|
|
7876
|
+
* nothing persists, a derived field keeps its column — so list views, filters,
|
|
7877
|
+
* sorting, field.lookup and MCP consumers see it like any other field.
|
|
7878
|
+
*
|
|
7879
|
+
* The function is pure, synchronous and isomorphic: it receives the fully hydrated
|
|
7880
|
+
* record (base scalars, embedded groups, collections, extends) and no database
|
|
7881
|
+
* handle. That is what lets the same declaration run on the server at write time
|
|
7882
|
+
* and in the browser for live preview.
|
|
7883
|
+
*
|
|
7884
|
+
* Scope: top-level fields of the record only. A derived field inside a collection
|
|
7885
|
+
* row is not supported.
|
|
7886
|
+
*/
|
|
7887
|
+
/** [key, spec] for every top-level derived field. Layout groups are flattened. */
|
|
7888
|
+
function derivedEntries(fields) {
|
|
7889
|
+
const out = [];
|
|
7890
|
+
for (const it of fields) if (isField(it)) {
|
|
7891
|
+
const spec = it.type.derive;
|
|
7892
|
+
if (spec) out.push([it.key, spec]);
|
|
7893
|
+
} else if (isGroup(it) && !it.key) out.push(...derivedEntries(it.fields));
|
|
7894
|
+
return out;
|
|
7895
|
+
}
|
|
7896
|
+
/** Keys a shelf's derive specs may reference: its own fields plus its collections. */
|
|
7897
|
+
function derivableKeys(fields) {
|
|
7898
|
+
const keys = /* @__PURE__ */ new Set();
|
|
7899
|
+
for (const it of fields) if (isField(it)) keys.add(it.key);
|
|
7900
|
+
else if (isGroup(it)) {
|
|
7901
|
+
if (it.key) keys.add(it.key);
|
|
7902
|
+
if (!it.key) for (const k of derivableKeys(it.fields)) keys.add(k);
|
|
7903
|
+
}
|
|
7904
|
+
return keys;
|
|
7905
|
+
}
|
|
7906
|
+
//#endregion
|
|
7754
7907
|
//#region ../sdk/src/shelf.ts
|
|
7755
7908
|
/** Flat [storageKey, FieldMeta] pairs of the parent table's columns.
|
|
7756
7909
|
* layout group → flattened; embedded group → prefix `key__`; collection → skipped. */
|
|
@@ -7768,9 +7921,49 @@ function defineShelf(m) {
|
|
|
7768
7921
|
const keys = fieldEntries(m.fields).map(([k]) => k);
|
|
7769
7922
|
const dup = keys.find((k, i) => keys.indexOf(k) !== i);
|
|
7770
7923
|
if (dup) throw new Error(`[shelf] ${m.library}/${m.shelf}: duplicate key '${dup}'`);
|
|
7771
|
-
if (m.
|
|
7924
|
+
if (m.single && !m.claude) throw new Error(`[shelf] ${m.library}/${m.shelf}: single shelf requires \`claude\` — the agent cannot find it otherwise`);
|
|
7925
|
+
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`);
|
|
7926
|
+
if (m.standalone !== false && !m.single && !m.views?.list?.length) console.warn(`[shelf] ${m.library}/${m.shelf}: standalone shelf without an explicit views.list`);
|
|
7927
|
+
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`);
|
|
7928
|
+
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`);
|
|
7929
|
+
const titles = ownFieldEntries(m.fields).filter(([, f]) => f.kind === "title");
|
|
7930
|
+
if (titles.length > 1) console.warn(`[shelf] ${m.library}/${m.shelf}: ${titles.length} title fields (${titles.map(([k]) => k).join(", ")}) — '${titles[0][0]}' wins`);
|
|
7931
|
+
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`);
|
|
7932
|
+
const known = derivableKeys(m.fields);
|
|
7933
|
+
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}'`);
|
|
7772
7934
|
return m;
|
|
7773
7935
|
}
|
|
7936
|
+
/**
|
|
7937
|
+
* Contract of a real entity:
|
|
7938
|
+
* id — uuid (the ONLY required system field, auto).
|
|
7939
|
+
* The rest (including name) are regular fields in ShelfDef.fields.
|
|
7940
|
+
* created_at/updated_at — system infrastructure (outside fields, auto-managed).
|
|
7941
|
+
*/
|
|
7942
|
+
/**
|
|
7943
|
+
* Keyed fields of the record itself, in declaration order: top level and plain
|
|
7944
|
+
* layout groups. An embedded group or a collection describes a sub-record, so its
|
|
7945
|
+
* fields are not candidates for the record's own heading.
|
|
7946
|
+
*/
|
|
7947
|
+
function ownFieldEntries(items) {
|
|
7948
|
+
const out = [];
|
|
7949
|
+
for (const it of items) if (isField(it)) out.push([it.key, it.type]);
|
|
7950
|
+
else if (isGroup(it) && !isCollectionGroup(it) && !isEmbeddedGroup(it)) out.push(...ownFieldEntries(it.fields));
|
|
7951
|
+
return out;
|
|
7952
|
+
}
|
|
7953
|
+
/**
|
|
7954
|
+
* Key of the record's title field (lists, relation pickers, inlines, record heading).
|
|
7955
|
+
* Priority: views.title → the first keyed kind:'title' field → 'id'.
|
|
7956
|
+
*
|
|
7957
|
+
* The title is declared, never guessed: a shelf that wants a heading marks the field
|
|
7958
|
+
* with field.title(), or names another field through views.title when the heading is
|
|
7959
|
+
* not a text field (a date, a virtual value). A shelf that declares nothing resolves
|
|
7960
|
+
* to 'id' and renders no heading — every field stays in the body with its own label.
|
|
7961
|
+
*/
|
|
7962
|
+
function titleKey(m) {
|
|
7963
|
+
if (m.views?.title) return m.views.title;
|
|
7964
|
+
const declared = ownFieldEntries(m.fields).find(([, f]) => f.kind === "title");
|
|
7965
|
+
return declared ? declared[0] : "id";
|
|
7966
|
+
}
|
|
7774
7967
|
//#endregion
|
|
7775
7968
|
//#region src/bus_route/index.ts
|
|
7776
7969
|
/**
|
|
@@ -7784,11 +7977,11 @@ var bus_route_default = defineShelf({
|
|
|
7784
7977
|
library: "transit",
|
|
7785
7978
|
label: "transit.bus_route.label",
|
|
7786
7979
|
icon: "lucide:bus",
|
|
7787
|
-
views: { list:
|
|
7980
|
+
views: { list: [
|
|
7788
7981
|
"route_number",
|
|
7789
7982
|
"from",
|
|
7790
7983
|
"to"
|
|
7791
|
-
] }
|
|
7984
|
+
] },
|
|
7792
7985
|
fields: [
|
|
7793
7986
|
field.title({
|
|
7794
7987
|
key: "name",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coffer-org/plugin-transit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24"
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"test": "node --import tsx/esm --test 'src/runtime/*.test.ts'"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@coffer-org/sdk": "^
|
|
29
|
-
"@coffer-org/server": "^
|
|
30
|
-
"@coffer-org/dav": "^
|
|
28
|
+
"@coffer-org/sdk": "^3.0.0",
|
|
29
|
+
"@coffer-org/server": "^3.0.0",
|
|
30
|
+
"@coffer-org/dav": "^3.0.0"
|
|
31
31
|
},
|
|
32
32
|
"coffer": {
|
|
33
33
|
"schema": "dist/schema.js"
|