@yuneta/gobj-ui 7.23.169 → 7.23.170
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/gobj-ui.cjs.js +394 -386
- package/dist/gobj-ui.es.js +394 -386
- package/package.json +2 -2
- package/src/c_yui_treedb_topic_with_form.js +5 -39
- package/src/treedb_write_plan.js +47 -1
- package/src/treedb_write_plan.test.js +53 -1
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -25643,6 +25643,361 @@ function row_matches(row, term) {
|
|
|
25643
25643
|
});
|
|
25644
25644
|
}
|
|
25645
25645
|
//#endregion
|
|
25646
|
+
//#region src/schema_model.js
|
|
25647
|
+
/***********************************************************************
|
|
25648
|
+
* schema_model.js
|
|
25649
|
+
*
|
|
25650
|
+
* A SCHEMA out of the three meta-topics that store it.
|
|
25651
|
+
*
|
|
25652
|
+
* `treedb_system_schema` keeps every schema of a yuno as DATA, in
|
|
25653
|
+
* three flat topics — `treedbs`, `topics`, `cols` — linked by
|
|
25654
|
+
* fkeys. That is the right STORAGE and the wrong thing to put in
|
|
25655
|
+
* front of an operator: adding one column to one topic means
|
|
25656
|
+
* finding it in a table holding every column of every topic of
|
|
25657
|
+
* every treedb the yuno has.
|
|
25658
|
+
*
|
|
25659
|
+
* This module turns those three lists back into what they
|
|
25660
|
+
* describe: treedb -> topics -> columns, each in its declared
|
|
25661
|
+
* `order`. Pure, so the editor's model can be tested with no DOM
|
|
25662
|
+
* and no backend — the gclass fetches and draws, this decides
|
|
25663
|
+
* what the records MEAN.
|
|
25664
|
+
*
|
|
25665
|
+
* TWO THINGS IT EXISTS TO STATE ONCE:
|
|
25666
|
+
*
|
|
25667
|
+
* 1. A record is grouped by its FKEY, not by its id. The id is
|
|
25668
|
+
* qualified (`<treedb>.<topic>.<column>`) and reading it back
|
|
25669
|
+
* with a split on '.' works right up to the first treedb or
|
|
25670
|
+
* topic whose name carries one. The fkey is the link the store
|
|
25671
|
+
* itself uses; the id is only the fallback for a record whose
|
|
25672
|
+
* fkey never arrived.
|
|
25673
|
+
*
|
|
25674
|
+
* 2. A fkey reaches this code in FOUR shapes, because `nodes`
|
|
25675
|
+
* answers differently depending on the options it was called
|
|
25676
|
+
* with: the bare ref string, the expanded object, a list of
|
|
25677
|
+
* either, or a dict keyed by the ref. All four are the same
|
|
25678
|
+
* fact and parse_fkey_ref() flattens them to it.
|
|
25679
|
+
*
|
|
25680
|
+
* Copyright (c) 2026, ArtGins.
|
|
25681
|
+
* All Rights Reserved.
|
|
25682
|
+
***********************************************************************/
|
|
25683
|
+
var DEFAULT_ORDER = 9999;
|
|
25684
|
+
/***************************************************************
|
|
25685
|
+
* parse_fkey_ref(value) -> [{topic_name, id, hook_name}, ...]
|
|
25686
|
+
*
|
|
25687
|
+
* Every shape a fkey column can arrive in, flattened to the
|
|
25688
|
+
* refs it names. Anything unrecognizable contributes nothing —
|
|
25689
|
+
* a schema drawn without one link beats a screen that throws.
|
|
25690
|
+
***************************************************************/
|
|
25691
|
+
function parse_fkey_ref(value) {
|
|
25692
|
+
let refs = [];
|
|
25693
|
+
let push_one = (v) => {
|
|
25694
|
+
if (typeof v === "string") {
|
|
25695
|
+
let parts = v.split("^");
|
|
25696
|
+
if (parts.length !== 3) return;
|
|
25697
|
+
refs.push({
|
|
25698
|
+
topic_name: parts[0],
|
|
25699
|
+
id: parts[1],
|
|
25700
|
+
hook_name: parts[2]
|
|
25701
|
+
});
|
|
25702
|
+
return;
|
|
25703
|
+
}
|
|
25704
|
+
if (v && typeof v === "object") {
|
|
25705
|
+
let id = v.id;
|
|
25706
|
+
if (typeof id !== "string" || id.length === 0) return;
|
|
25707
|
+
refs.push({
|
|
25708
|
+
topic_name: v.topic_name || "",
|
|
25709
|
+
id,
|
|
25710
|
+
hook_name: v.hook_name || ""
|
|
25711
|
+
});
|
|
25712
|
+
}
|
|
25713
|
+
};
|
|
25714
|
+
if (value === null || value === void 0) return refs;
|
|
25715
|
+
if (typeof value === "string") {
|
|
25716
|
+
push_one(value);
|
|
25717
|
+
return refs;
|
|
25718
|
+
}
|
|
25719
|
+
if (Array.isArray(value)) {
|
|
25720
|
+
for (let v of value) push_one(v);
|
|
25721
|
+
return refs;
|
|
25722
|
+
}
|
|
25723
|
+
if (typeof value === "object") for (let [key, v] of Object.entries(value)) if (v && typeof v === "object" && typeof v.id === "string") push_one(v);
|
|
25724
|
+
else push_one(key);
|
|
25725
|
+
return refs;
|
|
25726
|
+
}
|
|
25727
|
+
/***************************************************************
|
|
25728
|
+
* The id of the FIRST parent a record names, or "" when it
|
|
25729
|
+
* names none. A record has one parent here — a column belongs to
|
|
25730
|
+
* one topic, a topic to one treedb — and a second ref would be a
|
|
25731
|
+
* store this editor cannot draw anyway.
|
|
25732
|
+
***************************************************************/
|
|
25733
|
+
function parent_id(record, fkey_col) {
|
|
25734
|
+
let refs = parse_fkey_ref(record ? record[fkey_col] : null);
|
|
25735
|
+
return refs.length > 0 ? refs[0].id : "";
|
|
25736
|
+
}
|
|
25737
|
+
/***************************************************************
|
|
25738
|
+
* The name a record is KNOWN by, which is not what it is keyed
|
|
25739
|
+
* by: `topics` and `cols` key by the qualified id and carry the
|
|
25740
|
+
* bare name in `value` (its pkey2). An older store that still
|
|
25741
|
+
* keyed by rowid has no `value` worth showing, so the id stands
|
|
25742
|
+
* in — the same fallback as node_label().
|
|
25743
|
+
***************************************************************/
|
|
25744
|
+
function record_name(record) {
|
|
25745
|
+
if (!record) return "";
|
|
25746
|
+
if (typeof record.value === "string" && record.value.length > 0) return record.value;
|
|
25747
|
+
return typeof record.id === "string" ? record.id : "";
|
|
25748
|
+
}
|
|
25749
|
+
/***************************************************************
|
|
25750
|
+
* The `order` of a record as a NUMBER, whatever the store put
|
|
25751
|
+
* there: an integer column read back from JSON may arrive as a
|
|
25752
|
+
* string, and comparing "10" with 9 sorts the schema wrong.
|
|
25753
|
+
***************************************************************/
|
|
25754
|
+
function record_order(record) {
|
|
25755
|
+
let order = record ? record.order : void 0;
|
|
25756
|
+
if (typeof order === "number" && isFinite(order)) return order;
|
|
25757
|
+
if (typeof order === "string" && order.trim().length > 0) {
|
|
25758
|
+
let n = Number(order);
|
|
25759
|
+
if (isFinite(n)) return n;
|
|
25760
|
+
}
|
|
25761
|
+
return DEFAULT_ORDER;
|
|
25762
|
+
}
|
|
25763
|
+
/***************************************************************
|
|
25764
|
+
* Sort in place by `order`, ties broken by name so a schema
|
|
25765
|
+
* whose records all default to 9999 still draws the same way
|
|
25766
|
+
* twice.
|
|
25767
|
+
***************************************************************/
|
|
25768
|
+
function sort_by_order(list) {
|
|
25769
|
+
list.sort((a, b) => {
|
|
25770
|
+
if (a.order !== b.order) return a.order - b.order;
|
|
25771
|
+
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
25772
|
+
});
|
|
25773
|
+
return list;
|
|
25774
|
+
}
|
|
25775
|
+
/***************************************************************
|
|
25776
|
+
* build_schema_model({treedbs, topics, cols}) -> model
|
|
25777
|
+
*
|
|
25778
|
+
* model = {
|
|
25779
|
+
* treedbs: [{
|
|
25780
|
+
* id, name, record,
|
|
25781
|
+
* schema_version, c_schema_version, system_schema_version,
|
|
25782
|
+
* topics: [{
|
|
25783
|
+
* id, name, order, record,
|
|
25784
|
+
* pkey, topic_version, system_topic, main_topic,
|
|
25785
|
+
* cols: [{id, name, order, record}]
|
|
25786
|
+
* }]
|
|
25787
|
+
* }],
|
|
25788
|
+
* orphan_topics: [...], topics whose treedb is not in the store
|
|
25789
|
+
* orphan_cols: [...] columns whose topic is not in the store
|
|
25790
|
+
* }
|
|
25791
|
+
*
|
|
25792
|
+
* An orphan is NOT dropped. A column whose topic was deleted is
|
|
25793
|
+
* exactly the kind of leftover an operator opens this editor to
|
|
25794
|
+
* find, and a model that hides it makes the editor lie.
|
|
25795
|
+
***************************************************************/
|
|
25796
|
+
function build_schema_model(records) {
|
|
25797
|
+
let r = records || {};
|
|
25798
|
+
let treedb_rows = Array.isArray(r.treedbs) ? r.treedbs : [];
|
|
25799
|
+
let topic_rows = Array.isArray(r.topics) ? r.topics : [];
|
|
25800
|
+
let col_rows = Array.isArray(r.cols) ? r.cols : [];
|
|
25801
|
+
let treedbs = [];
|
|
25802
|
+
let by_treedb_id = {};
|
|
25803
|
+
for (let record of treedb_rows) {
|
|
25804
|
+
if (!record || typeof record.id !== "string") continue;
|
|
25805
|
+
let entry = {
|
|
25806
|
+
id: record.id,
|
|
25807
|
+
name: record.id,
|
|
25808
|
+
record,
|
|
25809
|
+
schema_version: record.schema_version,
|
|
25810
|
+
c_schema_version: record.c_schema_version,
|
|
25811
|
+
system_schema_version: record.system_schema_version,
|
|
25812
|
+
topics: []
|
|
25813
|
+
};
|
|
25814
|
+
treedbs.push(entry);
|
|
25815
|
+
by_treedb_id[entry.id] = entry;
|
|
25816
|
+
}
|
|
25817
|
+
let orphan_topics = [];
|
|
25818
|
+
let by_topic_id = {};
|
|
25819
|
+
for (let record of topic_rows) {
|
|
25820
|
+
if (!record || typeof record.id !== "string") continue;
|
|
25821
|
+
let entry = {
|
|
25822
|
+
id: record.id,
|
|
25823
|
+
name: record_name(record),
|
|
25824
|
+
order: record_order(record),
|
|
25825
|
+
record,
|
|
25826
|
+
treedb_id: parent_id(record, "treedbs"),
|
|
25827
|
+
pkey: record.pkey || "",
|
|
25828
|
+
pkey2s: record.pkey2s,
|
|
25829
|
+
system_flag: record.system_flag || "",
|
|
25830
|
+
tkey: record.tkey || "",
|
|
25831
|
+
topic_version: record.topic_version,
|
|
25832
|
+
system_topic: !!record.system_topic,
|
|
25833
|
+
main_topic: !!record.main_topic,
|
|
25834
|
+
cols: []
|
|
25835
|
+
};
|
|
25836
|
+
by_topic_id[entry.id] = entry;
|
|
25837
|
+
let parent = by_treedb_id[entry.treedb_id];
|
|
25838
|
+
if (parent) parent.topics.push(entry);
|
|
25839
|
+
else orphan_topics.push(entry);
|
|
25840
|
+
}
|
|
25841
|
+
let orphan_cols = [];
|
|
25842
|
+
for (let record of col_rows) {
|
|
25843
|
+
if (!record || typeof record.id !== "string") continue;
|
|
25844
|
+
let entry = {
|
|
25845
|
+
id: record.id,
|
|
25846
|
+
name: record_name(record),
|
|
25847
|
+
order: record_order(record),
|
|
25848
|
+
record,
|
|
25849
|
+
topic_id: parent_id(record, "topics")
|
|
25850
|
+
};
|
|
25851
|
+
let parent = by_topic_id[entry.topic_id];
|
|
25852
|
+
if (parent) parent.cols.push(entry);
|
|
25853
|
+
else orphan_cols.push(entry);
|
|
25854
|
+
}
|
|
25855
|
+
for (let treedb of treedbs) {
|
|
25856
|
+
sort_by_order(treedb.topics);
|
|
25857
|
+
for (let topic of treedb.topics) sort_by_order(topic.cols);
|
|
25858
|
+
}
|
|
25859
|
+
for (let topic of orphan_topics) sort_by_order(topic.cols);
|
|
25860
|
+
treedbs.sort((a, b) => {
|
|
25861
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
25862
|
+
});
|
|
25863
|
+
sort_by_order(orphan_topics);
|
|
25864
|
+
sort_by_order(orphan_cols);
|
|
25865
|
+
return {
|
|
25866
|
+
treedbs,
|
|
25867
|
+
orphan_topics,
|
|
25868
|
+
orphan_cols
|
|
25869
|
+
};
|
|
25870
|
+
}
|
|
25871
|
+
/***************************************************************
|
|
25872
|
+
* Is there a value here at all?
|
|
25873
|
+
*
|
|
25874
|
+
* The store answers a `blob` column that was never set with an
|
|
25875
|
+
* EMPTY COLLECTION, not with nothing: `enum: {}`, `hook: {}`,
|
|
25876
|
+
* `default: {}`. Read as "a value", those turn every export into
|
|
25877
|
+
* a schema full of empty objects — and `'hook': {}` in a literal
|
|
25878
|
+
* is a hook with no mapping, which is a link the treedb builds
|
|
25879
|
+
* and nothing writes.
|
|
25880
|
+
***************************************************************/
|
|
25881
|
+
function is_empty_value(value) {
|
|
25882
|
+
if (value === null || value === void 0 || value === "") return true;
|
|
25883
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
25884
|
+
if (typeof value === "object") return Object.keys(value).length === 0;
|
|
25885
|
+
return false;
|
|
25886
|
+
}
|
|
25887
|
+
/***************************************************************
|
|
25888
|
+
* The fields of a column that are DECLARED as one thing and
|
|
25889
|
+
* STORED as another.
|
|
25890
|
+
*
|
|
25891
|
+
* `hook`, `enum`, `default`, `pkey2s` and `properties` are `blob`
|
|
25892
|
+
* columns of the `cols` topic (treedb_system_schema.c), so what
|
|
25893
|
+
* comes back may be the value or the JSON text of the value,
|
|
25894
|
+
* depending on how it was written — a projection writes the
|
|
25895
|
+
* value, a hand edit through a plain text field writes the text.
|
|
25896
|
+
* `flag` is an array that a single-flag edit can leave as a bare
|
|
25897
|
+
* string. Reading these without saying so is how a hook drawn
|
|
25898
|
+
* from `{"users":"department"}` becomes no hook at all.
|
|
25899
|
+
***************************************************************/
|
|
25900
|
+
function as_json(value) {
|
|
25901
|
+
if (typeof value !== "string") return value;
|
|
25902
|
+
let text = value.trim();
|
|
25903
|
+
if (text.length === 0) return null;
|
|
25904
|
+
try {
|
|
25905
|
+
return JSON.parse(text);
|
|
25906
|
+
} catch (e) {
|
|
25907
|
+
return value;
|
|
25908
|
+
}
|
|
25909
|
+
}
|
|
25910
|
+
function col_flags(record) {
|
|
25911
|
+
let flag = record ? record.flag : null;
|
|
25912
|
+
if (Array.isArray(flag)) return flag.filter((f) => typeof f === "string" && f.length > 0);
|
|
25913
|
+
if (typeof flag === "string" && flag.length > 0) {
|
|
25914
|
+
let parsed = as_json(flag);
|
|
25915
|
+
if (Array.isArray(parsed)) return parsed.filter((f) => typeof f === "string" && f.length > 0);
|
|
25916
|
+
return [flag];
|
|
25917
|
+
}
|
|
25918
|
+
return [];
|
|
25919
|
+
}
|
|
25920
|
+
function col_hook(record) {
|
|
25921
|
+
let hook = as_json(record ? record.hook : null);
|
|
25922
|
+
if (hook && typeof hook === "object" && !Array.isArray(hook)) return hook;
|
|
25923
|
+
return null;
|
|
25924
|
+
}
|
|
25925
|
+
function col_enum(record) {
|
|
25926
|
+
let e = as_json(record ? record.enum : null);
|
|
25927
|
+
if (Array.isArray(e)) return e;
|
|
25928
|
+
return [];
|
|
25929
|
+
}
|
|
25930
|
+
function topic_pkey2s(record) {
|
|
25931
|
+
let pkey2s = as_json(record ? record.pkey2s : null);
|
|
25932
|
+
if (Array.isArray(pkey2s)) return pkey2s.filter((k) => typeof k === "string" && k.length > 0);
|
|
25933
|
+
if (typeof pkey2s === "string" && pkey2s.length > 0) return [pkey2s];
|
|
25934
|
+
return [];
|
|
25935
|
+
}
|
|
25936
|
+
/***************************************************************
|
|
25937
|
+
* Lookups by id. The editor addresses a position by NAME (the
|
|
25938
|
+
* url carries `<treedb>/<topic>`), so both are offered.
|
|
25939
|
+
***************************************************************/
|
|
25940
|
+
function find_treedb(model, treedb_id) {
|
|
25941
|
+
if (!model || !Array.isArray(model.treedbs)) return null;
|
|
25942
|
+
for (let treedb of model.treedbs) if (treedb.id === treedb_id) return treedb;
|
|
25943
|
+
return null;
|
|
25944
|
+
}
|
|
25945
|
+
function find_topic(model, treedb_id, topic_name) {
|
|
25946
|
+
let treedb = find_treedb(model, treedb_id);
|
|
25947
|
+
if (!treedb) return null;
|
|
25948
|
+
for (let topic of treedb.topics) if (topic.name === topic_name || topic.id === topic_name) return topic;
|
|
25949
|
+
return null;
|
|
25950
|
+
}
|
|
25951
|
+
function find_col(model, treedb_id, topic_name, col_name) {
|
|
25952
|
+
let topic = find_topic(model, treedb_id, topic_name);
|
|
25953
|
+
if (!topic) return null;
|
|
25954
|
+
for (let col of topic.cols) if (col.name === col_name || col.id === col_name) return col;
|
|
25955
|
+
return null;
|
|
25956
|
+
}
|
|
25957
|
+
/***************************************************************
|
|
25958
|
+
* The fkey a NEW record must carry to belong to `parent_id`.
|
|
25959
|
+
* Written here because the editor is the only writer that has to
|
|
25960
|
+
* compose one, and getting the hook name wrong links the record
|
|
25961
|
+
* to nothing while the write still succeeds.
|
|
25962
|
+
***************************************************************/
|
|
25963
|
+
function fkey_ref(parent_topic, parent_id_, hook_name) {
|
|
25964
|
+
return `${parent_topic}^${parent_id_}^${hook_name}`;
|
|
25965
|
+
}
|
|
25966
|
+
/***************************************************************
|
|
25967
|
+
* Where a new sibling goes: after the last one. The store's own
|
|
25968
|
+
* default (9999) would put every hand-made column in the same
|
|
25969
|
+
* place and let their names decide the schema's order.
|
|
25970
|
+
***************************************************************/
|
|
25971
|
+
function next_order(siblings) {
|
|
25972
|
+
let max = 0;
|
|
25973
|
+
if (Array.isArray(siblings)) for (let s of siblings) {
|
|
25974
|
+
let order = s && typeof s.order === "number" ? s.order : DEFAULT_ORDER;
|
|
25975
|
+
if (order < 9999 && order > max) max = order;
|
|
25976
|
+
}
|
|
25977
|
+
return max + 1;
|
|
25978
|
+
}
|
|
25979
|
+
/***************************************************************
|
|
25980
|
+
* Renumber a list AFTER a move, as the writes it takes: only the
|
|
25981
|
+
* records whose order actually changed, so a drag of one row in a
|
|
25982
|
+
* 40-column topic is not 40 writes.
|
|
25983
|
+
*
|
|
25984
|
+
* moved_orders(list) -> [{id, order}, ...]
|
|
25985
|
+
***************************************************************/
|
|
25986
|
+
function moved_orders(list) {
|
|
25987
|
+
let writes = [];
|
|
25988
|
+
if (!Array.isArray(list)) return writes;
|
|
25989
|
+
for (let i = 0; i < list.length; i++) {
|
|
25990
|
+
let entry = list[i];
|
|
25991
|
+
let order = i + 1;
|
|
25992
|
+
if (!entry || typeof entry.id !== "string") continue;
|
|
25993
|
+
if (entry.order !== order) writes.push({
|
|
25994
|
+
id: entry.id,
|
|
25995
|
+
order
|
|
25996
|
+
});
|
|
25997
|
+
}
|
|
25998
|
+
return writes;
|
|
25999
|
+
}
|
|
26000
|
+
//#endregion
|
|
25646
26001
|
//#region src/treedb_write_plan.js
|
|
25647
26002
|
/***********************************************************************
|
|
25648
26003
|
* treedb_write_plan.js
|
|
@@ -25704,6 +26059,43 @@ function plan_treedb_writes(flags) {
|
|
|
25704
26059
|
form_toolbar: null
|
|
25705
26060
|
};
|
|
25706
26061
|
}
|
|
26062
|
+
/************************************************************
|
|
26063
|
+
* Does column `col` travel back to treedb on a write?
|
|
26064
|
+
*
|
|
26065
|
+
* The form SHOWS every field and sends back only what the topic
|
|
26066
|
+
* accepts: the writable cols, the fkeys (a link is edited by
|
|
26067
|
+
* linking) and the pkey, which is not written but is what
|
|
26068
|
+
* ADDRESSES the record.
|
|
26069
|
+
*
|
|
26070
|
+
* It matters because `treedb_update_node()` does not check
|
|
26071
|
+
* `writable` -- it writes any col it is handed -- so a read-only
|
|
26072
|
+
* field travelling back is written with whatever the form made of
|
|
26073
|
+
* it, and the fields that describe a record are exactly the ones
|
|
26074
|
+
* that do not survive a round trip through a widget: a `time` is an
|
|
26075
|
+
* integer rendered as a `datetime-local`, with no seconds, so every
|
|
26076
|
+
* save moved it back up to 59 s. Nothing looked wrong until the
|
|
26077
|
+
* stored timestamp had moved.
|
|
26078
|
+
*
|
|
26079
|
+
* `is_file` and not the type: a `file` column IS an fkey (it is
|
|
26080
|
+
* flagged ['fkey','file']) but answers `type: "file"`, and the write
|
|
26081
|
+
* goes out with `autolink`, which rebuilds the links from what the
|
|
26082
|
+
* record carries. Dropped, a read-only `file` column -- the one only
|
|
26083
|
+
* a load fills -- was UNLINKED by every save of any other field.
|
|
26084
|
+
*
|
|
26085
|
+
* The pkey2s go back for the same reason as the pkey: a secondary key
|
|
26086
|
+
* names the INSTANCE the update is for (`yunos.yuno_release` in the
|
|
26087
|
+
* agent), and it is `persistent, required`, not `writable`. Dropped,
|
|
26088
|
+
* the update reached the backend with no pkey2 and C_NODE resolved it
|
|
26089
|
+
* to the PRIMARY instance -- right by chance while the table lists
|
|
26090
|
+
* primaries, wrong from a form opened on a row of `instances`.
|
|
26091
|
+
************************************************************/
|
|
26092
|
+
function col_goes_back_to_treedb(desc, col) {
|
|
26093
|
+
let d = desc || {};
|
|
26094
|
+
let pkey = d.pkey || "id";
|
|
26095
|
+
const field_desc = (0, _yuneta_gobj_js.treedb_get_field_desc)(col);
|
|
26096
|
+
if (field_desc.is_writable || field_desc.type === "fkey" || field_desc.is_file || col.id === pkey) return true;
|
|
26097
|
+
return topic_pkey2s(d).includes(col.id);
|
|
26098
|
+
}
|
|
25707
26099
|
//#endregion
|
|
25708
26100
|
//#region src/delete_impact.js
|
|
25709
26101
|
/***********************************************************************
|
|
@@ -28051,35 +28443,6 @@ function get_schema_col(gobj, id) {
|
|
|
28051
28443
|
return null;
|
|
28052
28444
|
}
|
|
28053
28445
|
/************************************************************
|
|
28054
|
-
* Does column `col` travel back to treedb on a write?
|
|
28055
|
-
*
|
|
28056
|
-
* The form SHOWS every field and sends back only what the topic
|
|
28057
|
-
* accepts: the writable cols, the fkeys (a link is edited by
|
|
28058
|
-
* linking) and the pkey, which is not written but is what
|
|
28059
|
-
* ADDRESSES the record.
|
|
28060
|
-
*
|
|
28061
|
-
* It matters because `treedb_update_node()` does not check
|
|
28062
|
-
* `writable` -- it writes any col it is handed -- so a read-only
|
|
28063
|
-
* field travelling back is written with whatever the form made of
|
|
28064
|
-
* it, and the fields that describe a record are exactly the ones
|
|
28065
|
-
* that do not survive a round trip through a widget: a `time` is an
|
|
28066
|
-
* integer rendered as a `datetime-local`, with no seconds, so every
|
|
28067
|
-
* save moved it back up to 59 s. Nothing looked wrong until the
|
|
28068
|
-
* stored timestamp had moved.
|
|
28069
|
-
*
|
|
28070
|
-
* `is_file` and not the type: a `file` column IS an fkey (it is
|
|
28071
|
-
* flagged ['fkey','file']) but answers `type: "file"`, and the write
|
|
28072
|
-
* goes out with `autolink`, which rebuilds the links from what the
|
|
28073
|
-
* record carries. Dropped, a read-only `file` column -- the one only
|
|
28074
|
-
* a load fills -- was UNLINKED by every save of any other field.
|
|
28075
|
-
************************************************************/
|
|
28076
|
-
function col_goes_back_to_treedb(gobj, col) {
|
|
28077
|
-
let pkey = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "desc").pkey || "id";
|
|
28078
|
-
const field_desc = (0, _yuneta_gobj_js.treedb_get_field_desc)(col);
|
|
28079
|
-
if (field_desc.is_writable || field_desc.type === "fkey" || field_desc.is_file || col.id === pkey) return true;
|
|
28080
|
-
return false;
|
|
28081
|
-
}
|
|
28082
|
-
/************************************************************
|
|
28083
28446
|
* The record the FORM hands over, without the columns that do
|
|
28084
28447
|
* not go back to treedb (see col_goes_back_to_treedb()). The
|
|
28085
28448
|
* form has already encoded the values, so only the columns are
|
|
@@ -28089,7 +28452,7 @@ function strip_read_only_cols(gobj, record) {
|
|
|
28089
28452
|
let row = {};
|
|
28090
28453
|
for (let field_name of Object.keys(record || {})) {
|
|
28091
28454
|
let col = get_schema_col(gobj, field_name);
|
|
28092
|
-
if (col && !col_goes_back_to_treedb(gobj, col)) continue;
|
|
28455
|
+
if (col && !col_goes_back_to_treedb((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "desc"), col)) continue;
|
|
28093
28456
|
row[field_name] = record[field_name];
|
|
28094
28457
|
}
|
|
28095
28458
|
return row;
|
|
@@ -28105,7 +28468,7 @@ function transform__form_record_2_treedb_record(gobj, kw, operation) {
|
|
|
28105
28468
|
if ((0, _yuneta_gobj_js.empty_string)(field_name)) continue;
|
|
28106
28469
|
let col = get_schema_col(gobj, field_name);
|
|
28107
28470
|
if (!col) continue;
|
|
28108
|
-
if (!col_goes_back_to_treedb(gobj, col)) continue;
|
|
28471
|
+
if (!col_goes_back_to_treedb((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "desc"), col)) continue;
|
|
28109
28472
|
let value = kw[field_name];
|
|
28110
28473
|
row[field_name] = transform__form_value_2_treedb_value(gobj, col, value, operation);
|
|
28111
28474
|
}
|
|
@@ -69023,361 +69386,6 @@ function register_c_yui_treedb_schema() {
|
|
|
69023
69386
|
return create_gclass$2(GCLASS_NAME$2);
|
|
69024
69387
|
}
|
|
69025
69388
|
//#endregion
|
|
69026
|
-
//#region src/schema_model.js
|
|
69027
|
-
/***********************************************************************
|
|
69028
|
-
* schema_model.js
|
|
69029
|
-
*
|
|
69030
|
-
* A SCHEMA out of the three meta-topics that store it.
|
|
69031
|
-
*
|
|
69032
|
-
* `treedb_system_schema` keeps every schema of a yuno as DATA, in
|
|
69033
|
-
* three flat topics — `treedbs`, `topics`, `cols` — linked by
|
|
69034
|
-
* fkeys. That is the right STORAGE and the wrong thing to put in
|
|
69035
|
-
* front of an operator: adding one column to one topic means
|
|
69036
|
-
* finding it in a table holding every column of every topic of
|
|
69037
|
-
* every treedb the yuno has.
|
|
69038
|
-
*
|
|
69039
|
-
* This module turns those three lists back into what they
|
|
69040
|
-
* describe: treedb -> topics -> columns, each in its declared
|
|
69041
|
-
* `order`. Pure, so the editor's model can be tested with no DOM
|
|
69042
|
-
* and no backend — the gclass fetches and draws, this decides
|
|
69043
|
-
* what the records MEAN.
|
|
69044
|
-
*
|
|
69045
|
-
* TWO THINGS IT EXISTS TO STATE ONCE:
|
|
69046
|
-
*
|
|
69047
|
-
* 1. A record is grouped by its FKEY, not by its id. The id is
|
|
69048
|
-
* qualified (`<treedb>.<topic>.<column>`) and reading it back
|
|
69049
|
-
* with a split on '.' works right up to the first treedb or
|
|
69050
|
-
* topic whose name carries one. The fkey is the link the store
|
|
69051
|
-
* itself uses; the id is only the fallback for a record whose
|
|
69052
|
-
* fkey never arrived.
|
|
69053
|
-
*
|
|
69054
|
-
* 2. A fkey reaches this code in FOUR shapes, because `nodes`
|
|
69055
|
-
* answers differently depending on the options it was called
|
|
69056
|
-
* with: the bare ref string, the expanded object, a list of
|
|
69057
|
-
* either, or a dict keyed by the ref. All four are the same
|
|
69058
|
-
* fact and parse_fkey_ref() flattens them to it.
|
|
69059
|
-
*
|
|
69060
|
-
* Copyright (c) 2026, ArtGins.
|
|
69061
|
-
* All Rights Reserved.
|
|
69062
|
-
***********************************************************************/
|
|
69063
|
-
var DEFAULT_ORDER = 9999;
|
|
69064
|
-
/***************************************************************
|
|
69065
|
-
* parse_fkey_ref(value) -> [{topic_name, id, hook_name}, ...]
|
|
69066
|
-
*
|
|
69067
|
-
* Every shape a fkey column can arrive in, flattened to the
|
|
69068
|
-
* refs it names. Anything unrecognizable contributes nothing —
|
|
69069
|
-
* a schema drawn without one link beats a screen that throws.
|
|
69070
|
-
***************************************************************/
|
|
69071
|
-
function parse_fkey_ref(value) {
|
|
69072
|
-
let refs = [];
|
|
69073
|
-
let push_one = (v) => {
|
|
69074
|
-
if (typeof v === "string") {
|
|
69075
|
-
let parts = v.split("^");
|
|
69076
|
-
if (parts.length !== 3) return;
|
|
69077
|
-
refs.push({
|
|
69078
|
-
topic_name: parts[0],
|
|
69079
|
-
id: parts[1],
|
|
69080
|
-
hook_name: parts[2]
|
|
69081
|
-
});
|
|
69082
|
-
return;
|
|
69083
|
-
}
|
|
69084
|
-
if (v && typeof v === "object") {
|
|
69085
|
-
let id = v.id;
|
|
69086
|
-
if (typeof id !== "string" || id.length === 0) return;
|
|
69087
|
-
refs.push({
|
|
69088
|
-
topic_name: v.topic_name || "",
|
|
69089
|
-
id,
|
|
69090
|
-
hook_name: v.hook_name || ""
|
|
69091
|
-
});
|
|
69092
|
-
}
|
|
69093
|
-
};
|
|
69094
|
-
if (value === null || value === void 0) return refs;
|
|
69095
|
-
if (typeof value === "string") {
|
|
69096
|
-
push_one(value);
|
|
69097
|
-
return refs;
|
|
69098
|
-
}
|
|
69099
|
-
if (Array.isArray(value)) {
|
|
69100
|
-
for (let v of value) push_one(v);
|
|
69101
|
-
return refs;
|
|
69102
|
-
}
|
|
69103
|
-
if (typeof value === "object") for (let [key, v] of Object.entries(value)) if (v && typeof v === "object" && typeof v.id === "string") push_one(v);
|
|
69104
|
-
else push_one(key);
|
|
69105
|
-
return refs;
|
|
69106
|
-
}
|
|
69107
|
-
/***************************************************************
|
|
69108
|
-
* The id of the FIRST parent a record names, or "" when it
|
|
69109
|
-
* names none. A record has one parent here — a column belongs to
|
|
69110
|
-
* one topic, a topic to one treedb — and a second ref would be a
|
|
69111
|
-
* store this editor cannot draw anyway.
|
|
69112
|
-
***************************************************************/
|
|
69113
|
-
function parent_id(record, fkey_col) {
|
|
69114
|
-
let refs = parse_fkey_ref(record ? record[fkey_col] : null);
|
|
69115
|
-
return refs.length > 0 ? refs[0].id : "";
|
|
69116
|
-
}
|
|
69117
|
-
/***************************************************************
|
|
69118
|
-
* The name a record is KNOWN by, which is not what it is keyed
|
|
69119
|
-
* by: `topics` and `cols` key by the qualified id and carry the
|
|
69120
|
-
* bare name in `value` (its pkey2). An older store that still
|
|
69121
|
-
* keyed by rowid has no `value` worth showing, so the id stands
|
|
69122
|
-
* in — the same fallback as node_label().
|
|
69123
|
-
***************************************************************/
|
|
69124
|
-
function record_name(record) {
|
|
69125
|
-
if (!record) return "";
|
|
69126
|
-
if (typeof record.value === "string" && record.value.length > 0) return record.value;
|
|
69127
|
-
return typeof record.id === "string" ? record.id : "";
|
|
69128
|
-
}
|
|
69129
|
-
/***************************************************************
|
|
69130
|
-
* The `order` of a record as a NUMBER, whatever the store put
|
|
69131
|
-
* there: an integer column read back from JSON may arrive as a
|
|
69132
|
-
* string, and comparing "10" with 9 sorts the schema wrong.
|
|
69133
|
-
***************************************************************/
|
|
69134
|
-
function record_order(record) {
|
|
69135
|
-
let order = record ? record.order : void 0;
|
|
69136
|
-
if (typeof order === "number" && isFinite(order)) return order;
|
|
69137
|
-
if (typeof order === "string" && order.trim().length > 0) {
|
|
69138
|
-
let n = Number(order);
|
|
69139
|
-
if (isFinite(n)) return n;
|
|
69140
|
-
}
|
|
69141
|
-
return DEFAULT_ORDER;
|
|
69142
|
-
}
|
|
69143
|
-
/***************************************************************
|
|
69144
|
-
* Sort in place by `order`, ties broken by name so a schema
|
|
69145
|
-
* whose records all default to 9999 still draws the same way
|
|
69146
|
-
* twice.
|
|
69147
|
-
***************************************************************/
|
|
69148
|
-
function sort_by_order(list) {
|
|
69149
|
-
list.sort((a, b) => {
|
|
69150
|
-
if (a.order !== b.order) return a.order - b.order;
|
|
69151
|
-
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
69152
|
-
});
|
|
69153
|
-
return list;
|
|
69154
|
-
}
|
|
69155
|
-
/***************************************************************
|
|
69156
|
-
* build_schema_model({treedbs, topics, cols}) -> model
|
|
69157
|
-
*
|
|
69158
|
-
* model = {
|
|
69159
|
-
* treedbs: [{
|
|
69160
|
-
* id, name, record,
|
|
69161
|
-
* schema_version, c_schema_version, system_schema_version,
|
|
69162
|
-
* topics: [{
|
|
69163
|
-
* id, name, order, record,
|
|
69164
|
-
* pkey, topic_version, system_topic, main_topic,
|
|
69165
|
-
* cols: [{id, name, order, record}]
|
|
69166
|
-
* }]
|
|
69167
|
-
* }],
|
|
69168
|
-
* orphan_topics: [...], topics whose treedb is not in the store
|
|
69169
|
-
* orphan_cols: [...] columns whose topic is not in the store
|
|
69170
|
-
* }
|
|
69171
|
-
*
|
|
69172
|
-
* An orphan is NOT dropped. A column whose topic was deleted is
|
|
69173
|
-
* exactly the kind of leftover an operator opens this editor to
|
|
69174
|
-
* find, and a model that hides it makes the editor lie.
|
|
69175
|
-
***************************************************************/
|
|
69176
|
-
function build_schema_model(records) {
|
|
69177
|
-
let r = records || {};
|
|
69178
|
-
let treedb_rows = Array.isArray(r.treedbs) ? r.treedbs : [];
|
|
69179
|
-
let topic_rows = Array.isArray(r.topics) ? r.topics : [];
|
|
69180
|
-
let col_rows = Array.isArray(r.cols) ? r.cols : [];
|
|
69181
|
-
let treedbs = [];
|
|
69182
|
-
let by_treedb_id = {};
|
|
69183
|
-
for (let record of treedb_rows) {
|
|
69184
|
-
if (!record || typeof record.id !== "string") continue;
|
|
69185
|
-
let entry = {
|
|
69186
|
-
id: record.id,
|
|
69187
|
-
name: record.id,
|
|
69188
|
-
record,
|
|
69189
|
-
schema_version: record.schema_version,
|
|
69190
|
-
c_schema_version: record.c_schema_version,
|
|
69191
|
-
system_schema_version: record.system_schema_version,
|
|
69192
|
-
topics: []
|
|
69193
|
-
};
|
|
69194
|
-
treedbs.push(entry);
|
|
69195
|
-
by_treedb_id[entry.id] = entry;
|
|
69196
|
-
}
|
|
69197
|
-
let orphan_topics = [];
|
|
69198
|
-
let by_topic_id = {};
|
|
69199
|
-
for (let record of topic_rows) {
|
|
69200
|
-
if (!record || typeof record.id !== "string") continue;
|
|
69201
|
-
let entry = {
|
|
69202
|
-
id: record.id,
|
|
69203
|
-
name: record_name(record),
|
|
69204
|
-
order: record_order(record),
|
|
69205
|
-
record,
|
|
69206
|
-
treedb_id: parent_id(record, "treedbs"),
|
|
69207
|
-
pkey: record.pkey || "",
|
|
69208
|
-
pkey2s: record.pkey2s,
|
|
69209
|
-
system_flag: record.system_flag || "",
|
|
69210
|
-
tkey: record.tkey || "",
|
|
69211
|
-
topic_version: record.topic_version,
|
|
69212
|
-
system_topic: !!record.system_topic,
|
|
69213
|
-
main_topic: !!record.main_topic,
|
|
69214
|
-
cols: []
|
|
69215
|
-
};
|
|
69216
|
-
by_topic_id[entry.id] = entry;
|
|
69217
|
-
let parent = by_treedb_id[entry.treedb_id];
|
|
69218
|
-
if (parent) parent.topics.push(entry);
|
|
69219
|
-
else orphan_topics.push(entry);
|
|
69220
|
-
}
|
|
69221
|
-
let orphan_cols = [];
|
|
69222
|
-
for (let record of col_rows) {
|
|
69223
|
-
if (!record || typeof record.id !== "string") continue;
|
|
69224
|
-
let entry = {
|
|
69225
|
-
id: record.id,
|
|
69226
|
-
name: record_name(record),
|
|
69227
|
-
order: record_order(record),
|
|
69228
|
-
record,
|
|
69229
|
-
topic_id: parent_id(record, "topics")
|
|
69230
|
-
};
|
|
69231
|
-
let parent = by_topic_id[entry.topic_id];
|
|
69232
|
-
if (parent) parent.cols.push(entry);
|
|
69233
|
-
else orphan_cols.push(entry);
|
|
69234
|
-
}
|
|
69235
|
-
for (let treedb of treedbs) {
|
|
69236
|
-
sort_by_order(treedb.topics);
|
|
69237
|
-
for (let topic of treedb.topics) sort_by_order(topic.cols);
|
|
69238
|
-
}
|
|
69239
|
-
for (let topic of orphan_topics) sort_by_order(topic.cols);
|
|
69240
|
-
treedbs.sort((a, b) => {
|
|
69241
|
-
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
69242
|
-
});
|
|
69243
|
-
sort_by_order(orphan_topics);
|
|
69244
|
-
sort_by_order(orphan_cols);
|
|
69245
|
-
return {
|
|
69246
|
-
treedbs,
|
|
69247
|
-
orphan_topics,
|
|
69248
|
-
orphan_cols
|
|
69249
|
-
};
|
|
69250
|
-
}
|
|
69251
|
-
/***************************************************************
|
|
69252
|
-
* Is there a value here at all?
|
|
69253
|
-
*
|
|
69254
|
-
* The store answers a `blob` column that was never set with an
|
|
69255
|
-
* EMPTY COLLECTION, not with nothing: `enum: {}`, `hook: {}`,
|
|
69256
|
-
* `default: {}`. Read as "a value", those turn every export into
|
|
69257
|
-
* a schema full of empty objects — and `'hook': {}` in a literal
|
|
69258
|
-
* is a hook with no mapping, which is a link the treedb builds
|
|
69259
|
-
* and nothing writes.
|
|
69260
|
-
***************************************************************/
|
|
69261
|
-
function is_empty_value(value) {
|
|
69262
|
-
if (value === null || value === void 0 || value === "") return true;
|
|
69263
|
-
if (Array.isArray(value)) return value.length === 0;
|
|
69264
|
-
if (typeof value === "object") return Object.keys(value).length === 0;
|
|
69265
|
-
return false;
|
|
69266
|
-
}
|
|
69267
|
-
/***************************************************************
|
|
69268
|
-
* The fields of a column that are DECLARED as one thing and
|
|
69269
|
-
* STORED as another.
|
|
69270
|
-
*
|
|
69271
|
-
* `hook`, `enum`, `default`, `pkey2s` and `properties` are `blob`
|
|
69272
|
-
* columns of the `cols` topic (treedb_system_schema.c), so what
|
|
69273
|
-
* comes back may be the value or the JSON text of the value,
|
|
69274
|
-
* depending on how it was written — a projection writes the
|
|
69275
|
-
* value, a hand edit through a plain text field writes the text.
|
|
69276
|
-
* `flag` is an array that a single-flag edit can leave as a bare
|
|
69277
|
-
* string. Reading these without saying so is how a hook drawn
|
|
69278
|
-
* from `{"users":"department"}` becomes no hook at all.
|
|
69279
|
-
***************************************************************/
|
|
69280
|
-
function as_json(value) {
|
|
69281
|
-
if (typeof value !== "string") return value;
|
|
69282
|
-
let text = value.trim();
|
|
69283
|
-
if (text.length === 0) return null;
|
|
69284
|
-
try {
|
|
69285
|
-
return JSON.parse(text);
|
|
69286
|
-
} catch (e) {
|
|
69287
|
-
return value;
|
|
69288
|
-
}
|
|
69289
|
-
}
|
|
69290
|
-
function col_flags(record) {
|
|
69291
|
-
let flag = record ? record.flag : null;
|
|
69292
|
-
if (Array.isArray(flag)) return flag.filter((f) => typeof f === "string" && f.length > 0);
|
|
69293
|
-
if (typeof flag === "string" && flag.length > 0) {
|
|
69294
|
-
let parsed = as_json(flag);
|
|
69295
|
-
if (Array.isArray(parsed)) return parsed.filter((f) => typeof f === "string" && f.length > 0);
|
|
69296
|
-
return [flag];
|
|
69297
|
-
}
|
|
69298
|
-
return [];
|
|
69299
|
-
}
|
|
69300
|
-
function col_hook(record) {
|
|
69301
|
-
let hook = as_json(record ? record.hook : null);
|
|
69302
|
-
if (hook && typeof hook === "object" && !Array.isArray(hook)) return hook;
|
|
69303
|
-
return null;
|
|
69304
|
-
}
|
|
69305
|
-
function col_enum(record) {
|
|
69306
|
-
let e = as_json(record ? record.enum : null);
|
|
69307
|
-
if (Array.isArray(e)) return e;
|
|
69308
|
-
return [];
|
|
69309
|
-
}
|
|
69310
|
-
function topic_pkey2s(record) {
|
|
69311
|
-
let pkey2s = as_json(record ? record.pkey2s : null);
|
|
69312
|
-
if (Array.isArray(pkey2s)) return pkey2s.filter((k) => typeof k === "string" && k.length > 0);
|
|
69313
|
-
if (typeof pkey2s === "string" && pkey2s.length > 0) return [pkey2s];
|
|
69314
|
-
return [];
|
|
69315
|
-
}
|
|
69316
|
-
/***************************************************************
|
|
69317
|
-
* Lookups by id. The editor addresses a position by NAME (the
|
|
69318
|
-
* url carries `<treedb>/<topic>`), so both are offered.
|
|
69319
|
-
***************************************************************/
|
|
69320
|
-
function find_treedb(model, treedb_id) {
|
|
69321
|
-
if (!model || !Array.isArray(model.treedbs)) return null;
|
|
69322
|
-
for (let treedb of model.treedbs) if (treedb.id === treedb_id) return treedb;
|
|
69323
|
-
return null;
|
|
69324
|
-
}
|
|
69325
|
-
function find_topic(model, treedb_id, topic_name) {
|
|
69326
|
-
let treedb = find_treedb(model, treedb_id);
|
|
69327
|
-
if (!treedb) return null;
|
|
69328
|
-
for (let topic of treedb.topics) if (topic.name === topic_name || topic.id === topic_name) return topic;
|
|
69329
|
-
return null;
|
|
69330
|
-
}
|
|
69331
|
-
function find_col(model, treedb_id, topic_name, col_name) {
|
|
69332
|
-
let topic = find_topic(model, treedb_id, topic_name);
|
|
69333
|
-
if (!topic) return null;
|
|
69334
|
-
for (let col of topic.cols) if (col.name === col_name || col.id === col_name) return col;
|
|
69335
|
-
return null;
|
|
69336
|
-
}
|
|
69337
|
-
/***************************************************************
|
|
69338
|
-
* The fkey a NEW record must carry to belong to `parent_id`.
|
|
69339
|
-
* Written here because the editor is the only writer that has to
|
|
69340
|
-
* compose one, and getting the hook name wrong links the record
|
|
69341
|
-
* to nothing while the write still succeeds.
|
|
69342
|
-
***************************************************************/
|
|
69343
|
-
function fkey_ref(parent_topic, parent_id_, hook_name) {
|
|
69344
|
-
return `${parent_topic}^${parent_id_}^${hook_name}`;
|
|
69345
|
-
}
|
|
69346
|
-
/***************************************************************
|
|
69347
|
-
* Where a new sibling goes: after the last one. The store's own
|
|
69348
|
-
* default (9999) would put every hand-made column in the same
|
|
69349
|
-
* place and let their names decide the schema's order.
|
|
69350
|
-
***************************************************************/
|
|
69351
|
-
function next_order(siblings) {
|
|
69352
|
-
let max = 0;
|
|
69353
|
-
if (Array.isArray(siblings)) for (let s of siblings) {
|
|
69354
|
-
let order = s && typeof s.order === "number" ? s.order : DEFAULT_ORDER;
|
|
69355
|
-
if (order < 9999 && order > max) max = order;
|
|
69356
|
-
}
|
|
69357
|
-
return max + 1;
|
|
69358
|
-
}
|
|
69359
|
-
/***************************************************************
|
|
69360
|
-
* Renumber a list AFTER a move, as the writes it takes: only the
|
|
69361
|
-
* records whose order actually changed, so a drag of one row in a
|
|
69362
|
-
* 40-column topic is not 40 writes.
|
|
69363
|
-
*
|
|
69364
|
-
* moved_orders(list) -> [{id, order}, ...]
|
|
69365
|
-
***************************************************************/
|
|
69366
|
-
function moved_orders(list) {
|
|
69367
|
-
let writes = [];
|
|
69368
|
-
if (!Array.isArray(list)) return writes;
|
|
69369
|
-
for (let i = 0; i < list.length; i++) {
|
|
69370
|
-
let entry = list[i];
|
|
69371
|
-
let order = i + 1;
|
|
69372
|
-
if (!entry || typeof entry.id !== "string") continue;
|
|
69373
|
-
if (entry.order !== order) writes.push({
|
|
69374
|
-
id: entry.id,
|
|
69375
|
-
order
|
|
69376
|
-
});
|
|
69377
|
-
}
|
|
69378
|
-
return writes;
|
|
69379
|
-
}
|
|
69380
|
-
//#endregion
|
|
69381
69389
|
//#region src/schema_validate.js
|
|
69382
69390
|
/***********************************************************************
|
|
69383
69391
|
* schema_validate.js
|