@tensorix/metahub 0.4.0 → 0.4.1
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/cli.js +165 -15
- package/dist/cli.js.map +5 -5
- package/dist/core/properties.d.ts +8 -0
- package/dist/core/properties.d.ts.map +1 -1
- package/dist/db-worker.js +56 -56
- package/dist/edge-worker.js +84 -6
- package/dist/index.js +159 -13
- package/dist/index.js.map +4 -4
- package/dist/metahub-sdk.js +6 -6
- package/dist/sw.js +4 -4
- package/dist/webui/server/routes.d.ts.map +1 -1
- package/dist/webui.js +122 -116
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -807,7 +807,7 @@ var package_default;
|
|
|
807
807
|
var init_package = __esm(() => {
|
|
808
808
|
package_default = {
|
|
809
809
|
name: "@tensorix/metahub",
|
|
810
|
-
version: "0.4.
|
|
810
|
+
version: "0.4.1",
|
|
811
811
|
description: "Metahub core library and CLI",
|
|
812
812
|
license: "AGPL-3.0-only",
|
|
813
813
|
type: "module",
|
|
@@ -847,6 +847,7 @@ var init_package = __esm(() => {
|
|
|
847
847
|
"@codemirror/commands": "6.10.4",
|
|
848
848
|
"@codemirror/state": "6.7.0",
|
|
849
849
|
"@codemirror/view": "6.43.4",
|
|
850
|
+
"@joplin/turndown-plugin-gfm": "^1.0.67",
|
|
850
851
|
"@sqlite.org/sqlite-wasm": "^3.53.0-build1",
|
|
851
852
|
aws4fetch: "^1.0.20",
|
|
852
853
|
citty: "^0.1.6",
|
|
@@ -855,7 +856,6 @@ var init_package = __esm(() => {
|
|
|
855
856
|
preact: "^10.29.2",
|
|
856
857
|
"qrcode-generator": "^2.0.4",
|
|
857
858
|
turndown: "^7.2.4",
|
|
858
|
-
"turndown-plugin-gfm": "^1.0.2",
|
|
859
859
|
zod: "^4.4.3"
|
|
860
860
|
},
|
|
861
861
|
devDependencies: {
|
|
@@ -17558,7 +17558,15 @@ function setPropertyWidth(db, id, width) {
|
|
|
17558
17558
|
const w2 = Math.max(80, Math.min(2000, Math.round(width)));
|
|
17559
17559
|
return updateProperty(db, id, { config: { ...cur.config ?? {}, width: w2 } });
|
|
17560
17560
|
}
|
|
17561
|
-
|
|
17561
|
+
function getSelectProperty(db, id) {
|
|
17562
|
+
const prop = getProperty(db, id);
|
|
17563
|
+
if (!prop)
|
|
17564
|
+
throw new MhError("not_found", `no such property: ${id}`);
|
|
17565
|
+
if (prop.type !== "select" && prop.type !== "multi_select")
|
|
17566
|
+
throw new MhError("invalid_input", `${prop.type} property has no options`);
|
|
17567
|
+
return { prop, options: prop.config?.options ?? [] };
|
|
17568
|
+
}
|
|
17569
|
+
var PROP_TYPES, PROPERTY_COLS, PROPERTY_SELECT, addProperty, updateProperty, renameSelectOption, removeSelectOption, removeProperty;
|
|
17562
17570
|
var init_properties = __esm(() => {
|
|
17563
17571
|
init_ids();
|
|
17564
17572
|
init_crdt();
|
|
@@ -17601,16 +17609,25 @@ var init_properties = __esm(() => {
|
|
|
17601
17609
|
if (fields.type !== undefined && !PROP_TYPES.has(fields.type))
|
|
17602
17610
|
throw new MhError("invalid_input", `unknown property type: ${fields.type}`);
|
|
17603
17611
|
const effectiveType = fields.type ?? cur.type;
|
|
17604
|
-
|
|
17605
|
-
|
|
17606
|
-
|
|
17607
|
-
|
|
17612
|
+
let mergedConfig;
|
|
17613
|
+
if (fields.config !== undefined) {
|
|
17614
|
+
const merged = { ...cur.config ?? {} };
|
|
17615
|
+
for (const [k2, v2] of Object.entries(fields.config)) {
|
|
17616
|
+
if (v2 === null)
|
|
17617
|
+
delete merged[k2];
|
|
17618
|
+
else
|
|
17619
|
+
merged[k2] = v2;
|
|
17620
|
+
}
|
|
17621
|
+
mergedConfig = merged;
|
|
17622
|
+
validateConfig(effectiveType, mergedConfig);
|
|
17623
|
+
} else if (typeChanged)
|
|
17624
|
+
validateConfig(fields.type, cur.config ?? undefined);
|
|
17608
17625
|
if (fields.name !== undefined)
|
|
17609
17626
|
emit(db, "properties", id, "name", fields.name);
|
|
17610
17627
|
if (fields.type !== undefined)
|
|
17611
17628
|
emit(db, "properties", id, "type", fields.type);
|
|
17612
|
-
if (
|
|
17613
|
-
emit(db, "properties", id, "config",
|
|
17629
|
+
if (mergedConfig !== undefined)
|
|
17630
|
+
emit(db, "properties", id, "config", mergedConfig);
|
|
17614
17631
|
if (fields.position !== undefined)
|
|
17615
17632
|
emit(db, "properties", id, "position", fields.position);
|
|
17616
17633
|
if (typeChanged) {
|
|
@@ -17620,6 +17637,67 @@ var init_properties = __esm(() => {
|
|
|
17620
17637
|
}
|
|
17621
17638
|
return getProperty(db, id);
|
|
17622
17639
|
});
|
|
17640
|
+
renameSelectOption = grouped(function renameSelectOption2(db, id, from, to) {
|
|
17641
|
+
const { prop, options } = getSelectProperty(db, id);
|
|
17642
|
+
to = to.trim();
|
|
17643
|
+
if (!to)
|
|
17644
|
+
throw new MhError("invalid_input", "option name must not be empty");
|
|
17645
|
+
if (!options.includes(from))
|
|
17646
|
+
throw new MhError("not_found", `no such option: ${from}`);
|
|
17647
|
+
if (to === from)
|
|
17648
|
+
return { property: prop, renamed: 0 };
|
|
17649
|
+
if (options.includes(to))
|
|
17650
|
+
throw new MhError("conflict", `option already exists: ${to}`);
|
|
17651
|
+
emit(db, "properties", id, "config", {
|
|
17652
|
+
...prop.config ?? {},
|
|
17653
|
+
options: options.map((o3) => o3 === from ? to : o3)
|
|
17654
|
+
});
|
|
17655
|
+
let renamed = 0;
|
|
17656
|
+
if (prop.type === "select") {
|
|
17657
|
+
const rows = db.query("SELECT id FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? = ?").all(prop.database_id, id, from);
|
|
17658
|
+
for (const r3 of rows)
|
|
17659
|
+
emit(db, "records", r3.id, id, to);
|
|
17660
|
+
renamed = rows.length;
|
|
17661
|
+
} else {
|
|
17662
|
+
const rows = db.query("SELECT id, data ->> ? AS v FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? IS NOT NULL").all(id, prop.database_id, id);
|
|
17663
|
+
for (const r3 of rows) {
|
|
17664
|
+
const vals = JSON.parse(r3.v);
|
|
17665
|
+
if (!Array.isArray(vals) || !vals.includes(from))
|
|
17666
|
+
continue;
|
|
17667
|
+
emit(db, "records", r3.id, id, [...new Set(vals.map((v2) => v2 === from ? to : v2))]);
|
|
17668
|
+
renamed++;
|
|
17669
|
+
}
|
|
17670
|
+
}
|
|
17671
|
+
return { property: getProperty(db, id), renamed };
|
|
17672
|
+
});
|
|
17673
|
+
removeSelectOption = grouped(function removeSelectOption2(db, id, name) {
|
|
17674
|
+
const { prop, options } = getSelectProperty(db, id);
|
|
17675
|
+
if (!options.includes(name))
|
|
17676
|
+
throw new MhError("not_found", `no such option: ${name}`);
|
|
17677
|
+
if (options.length === 1)
|
|
17678
|
+
throw new MhError("invalid_input", "cannot remove the last option; delete the property instead");
|
|
17679
|
+
emit(db, "properties", id, "config", {
|
|
17680
|
+
...prop.config ?? {},
|
|
17681
|
+
options: options.filter((o3) => o3 !== name)
|
|
17682
|
+
});
|
|
17683
|
+
let cleared = 0;
|
|
17684
|
+
if (prop.type === "select") {
|
|
17685
|
+
const rows = db.query("SELECT id FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? = ?").all(prop.database_id, id, name);
|
|
17686
|
+
for (const r3 of rows)
|
|
17687
|
+
emit(db, "records", r3.id, id, null);
|
|
17688
|
+
cleared = rows.length;
|
|
17689
|
+
} else {
|
|
17690
|
+
const rows = db.query("SELECT id, data ->> ? AS v FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? IS NOT NULL").all(id, prop.database_id, id);
|
|
17691
|
+
for (const r3 of rows) {
|
|
17692
|
+
const vals = JSON.parse(r3.v);
|
|
17693
|
+
if (!Array.isArray(vals) || !vals.includes(name))
|
|
17694
|
+
continue;
|
|
17695
|
+
emit(db, "records", r3.id, id, vals.filter((v2) => v2 !== name));
|
|
17696
|
+
cleared++;
|
|
17697
|
+
}
|
|
17698
|
+
}
|
|
17699
|
+
return { property: getProperty(db, id), cleared };
|
|
17700
|
+
});
|
|
17623
17701
|
removeProperty = grouped(function removeProperty2(db, id) {
|
|
17624
17702
|
const prop = getProperty(db, id);
|
|
17625
17703
|
if (!prop)
|
|
@@ -28576,9 +28654,15 @@ var update = defineCommand({
|
|
|
28576
28654
|
args: {
|
|
28577
28655
|
id: { type: "positional", required: true, description: "Property ref (id/prefix/name)" },
|
|
28578
28656
|
name: { type: "string" },
|
|
28579
|
-
options: {
|
|
28657
|
+
options: {
|
|
28658
|
+
type: "string",
|
|
28659
|
+
description: "Comma list for select/multi_select; replaces the option set without migrating cell values"
|
|
28660
|
+
},
|
|
28580
28661
|
target: { type: "string", description: "Target database ref for relation" },
|
|
28581
|
-
config: {
|
|
28662
|
+
config: {
|
|
28663
|
+
type: "string",
|
|
28664
|
+
description: "Config JSON patch, merged key-wise; a null key is removed (@file/@- ok)"
|
|
28665
|
+
},
|
|
28582
28666
|
position: { type: "string" }
|
|
28583
28667
|
},
|
|
28584
28668
|
run: guard(async (args) => {
|
|
@@ -37961,15 +38045,57 @@ var styles_default = ` :root {
|
|
|
37961
38045
|
.pop .typegrid .item .lico { margin:0 auto; }
|
|
37962
38046
|
.pop input.field { width:100%; border:1px solid var(--line-strong); border-radius:var(--radius-sm); padding:7px 9px; outline:none; background:var(--surface); margin:2px; }
|
|
37963
38047
|
.pop input.field:focus { border-color:var(--accent); }
|
|
38048
|
+
/* select-cell picker: search/create input at the top of the popover */
|
|
38049
|
+
.pop .selsearch { display:flex; align-items:center; gap:7px; margin:2px 2px 6px; padding:6px 9px;
|
|
38050
|
+
border:1px solid var(--line-strong); border-radius:var(--radius-sm); background:var(--surface); color:var(--muted); }
|
|
38051
|
+
.pop .selsearch:focus-within { border-color:var(--accent); }
|
|
38052
|
+
.pop .selsearch input { flex:1; width:0; min-width:0; border:0; background:transparent; outline:none;
|
|
38053
|
+
font:inherit; font-size:var(--fs-ui); color:var(--fg); }
|
|
38054
|
+
.pop .selsearch input::placeholder { color:var(--muted); }
|
|
37964
38055
|
.optrow { display:flex; align-items:center; gap:6px; padding:3px 4px; }
|
|
37965
38056
|
.optrow .chip { flex:1; }
|
|
37966
|
-
.optrow .
|
|
37967
|
-
.optrow .
|
|
38057
|
+
.optrow .grip { width:18px; height:22px; display:grid; place-items:center; color:var(--muted); cursor:grab; touch-action:none; flex:none; }
|
|
38058
|
+
.optrow .grip.dim { color:color-mix(in srgb,var(--muted) 40%,transparent); cursor:default; }
|
|
38059
|
+
.optrow .optlabel { flex:1; display:flex; min-width:0; text-align:left; cursor:pointer; }
|
|
38060
|
+
.optrow .x { width:22px; height:22px; display:grid; place-items:center; color:var(--muted); border-radius:5px; flex:none; }
|
|
38061
|
+
.optrow .x:hover { background:var(--hover-2); color:var(--fg); }
|
|
38062
|
+
.optrow .x.del:hover { color:var(--danger); }
|
|
38063
|
+
.optrow .x.ok:hover { color:var(--success); }
|
|
38064
|
+
/* edit-state buttons fade in alongside the pill's ring */
|
|
38065
|
+
.optrow .x.ok, .optrow .x.cancel { animation:optbtn-in .15s ease; }
|
|
38066
|
+
@keyframes optbtn-in { from { opacity:0; } }
|
|
38067
|
+
.optrow.drop-before { box-shadow:inset 0 2px 0 0 var(--accent); }
|
|
38068
|
+
.optrow.drop-after { box-shadow:inset 0 -2px 0 0 var(--accent); }
|
|
38069
|
+
/* In-place pill editor: mirrors .chip metrics exactly (padding/radius/type)
|
|
38070
|
+
so entering edit never changes the row's size \u2014 only a color-matched ring
|
|
38071
|
+
appears. Caret and selection reuse the option's own hue. */
|
|
38072
|
+
/* width:0 kills the input's intrinsic width (browser default ~20ch) so the
|
|
38073
|
+
popover doesn't widen when the row enters edit \u2014 flex:1 does the sizing.
|
|
38074
|
+
The entry animation only fades the ring in: the pill background must stay
|
|
38075
|
+
put or the swap from the chip reads as a flash. */
|
|
38076
|
+
.optrow input.optedit { flex:1; width:0; min-width:0; margin:0; border:0; font:inherit; font-size:var(--fs-sm); font-weight:500;
|
|
38077
|
+
padding:2px 8px; border-radius:5px; background:color-mix(in srgb,var(--c) 15%,transparent); color:var(--c);
|
|
38078
|
+
caret-color:var(--c); outline:1.5px solid color-mix(in srgb,var(--c) 50%,transparent); outline-offset:0;
|
|
38079
|
+
animation:optedit-in .18s ease; }
|
|
38080
|
+
@keyframes optedit-in { from { outline-color:transparent; } }
|
|
38081
|
+
.optrow input.optedit::selection { background:color-mix(in srgb,var(--c) 28%,transparent); }
|
|
38082
|
+
:root[data-resolved="dark"] .optrow input.optedit { background:color-mix(in srgb,var(--c) 22%,transparent);
|
|
38083
|
+
color:color-mix(in srgb,var(--c) 75%,#fff); caret-color:color-mix(in srgb,var(--c) 75%,#fff); }
|
|
38084
|
+
/* Ghost add-row: quiet until it's needed \u2014 plus icon in the grip slot, a
|
|
38085
|
+
bare borderless input; hover/focus tints the row like a menu item. */
|
|
38086
|
+
.optrow.optadd { border-radius:var(--radius-sm); }
|
|
38087
|
+
.optrow.optadd:hover, .optrow.optadd:focus-within { background:var(--hover-2); }
|
|
38088
|
+
.optrow.optadd input { flex:1; width:0; min-width:0; margin:0; border:0; background:transparent; font:inherit;
|
|
38089
|
+
font-size:var(--fs-sm); padding:2px 8px; color:var(--fg); outline:none; }
|
|
38090
|
+
.optrow.optadd input::placeholder { color:var(--muted); }
|
|
37968
38091
|
|
|
37969
38092
|
/* modal */
|
|
37970
38093
|
.modal-scrim { position:fixed; inset:0; background:rgba(15,15,25,.4); z-index:100; display:flex; align-items:center; justify-content:center;
|
|
37971
38094
|
opacity:0; pointer-events:none; transition:opacity .15s; padding:20px; }
|
|
37972
38095
|
.modal-scrim.open { opacity:1; pointer-events:auto; }
|
|
38096
|
+
/* opt-in (openModal aboveMenus): stack one dialog over the menu layer (115)
|
|
38097
|
+
so a confirm can appear without dismissing an open popover */
|
|
38098
|
+
.modal-scrim.above-menus { z-index:130; }
|
|
37973
38099
|
.modal { background:var(--bg); border-radius:var(--radius-xl); box-shadow:var(--shadow-lg); width:440px; max-width:100%; max-height:100%; display:flex; flex-direction:column; overflow:hidden; }
|
|
37974
38100
|
.modal-head { padding:20px 22px 0; }
|
|
37975
38101
|
.modal-head h3 { margin:0; font-size:18px; font-weight:700; letter-spacing:-.01em; }
|
|
@@ -40018,6 +40144,8 @@ var UpdatePropertyReq = exports_external.object({
|
|
|
40018
40144
|
position: exports_external.number().optional()
|
|
40019
40145
|
});
|
|
40020
40146
|
var SetWidthReq = exports_external.object({ width: exports_external.number() });
|
|
40147
|
+
var RenameOptionReq = exports_external.object({ from: exports_external.string(), to: exports_external.string() });
|
|
40148
|
+
var RemoveOptionReq = exports_external.object({ name: exports_external.string() });
|
|
40021
40149
|
var RecordValuesReq = exports_external.record(exports_external.string(), exports_external.any()).describe("Flat { column: value } cell patch (keyed by property name).");
|
|
40022
40150
|
var CreateDocumentReq = exports_external.object({
|
|
40023
40151
|
title: exports_external.string(),
|
|
@@ -40329,7 +40457,7 @@ var webuiRoutes = [
|
|
|
40329
40457
|
{
|
|
40330
40458
|
method: "PATCH",
|
|
40331
40459
|
path: "/api/property",
|
|
40332
|
-
summary: "Update a property: rename, change type, edit config
|
|
40460
|
+
summary: "Update a property: rename, change type, edit config, reorder. `config` is merged key-wise into the current config (set a key to null to remove it). Query: ?id=<id>",
|
|
40333
40461
|
request: UpdatePropertyReq,
|
|
40334
40462
|
response: PropertySchema,
|
|
40335
40463
|
handler: handle3(async (req, { db }) => {
|
|
@@ -40337,6 +40465,28 @@ var webuiRoutes = [
|
|
|
40337
40465
|
return updateProperty(db, need3(req, "id"), body);
|
|
40338
40466
|
})
|
|
40339
40467
|
},
|
|
40468
|
+
{
|
|
40469
|
+
method: "POST",
|
|
40470
|
+
path: "/api/property/option/rename",
|
|
40471
|
+
summary: "Rename a select/multi_select option and rewrite every cell using the old name. Query: ?id=<id>",
|
|
40472
|
+
request: RenameOptionReq,
|
|
40473
|
+
response: exports_external.object({ property: PropertySchema, renamed: exports_external.number() }),
|
|
40474
|
+
handler: handle3(async (req, { db }) => {
|
|
40475
|
+
const body = await req.json();
|
|
40476
|
+
return renameSelectOption(db, need3(req, "id"), body.from, body.to);
|
|
40477
|
+
})
|
|
40478
|
+
},
|
|
40479
|
+
{
|
|
40480
|
+
method: "POST",
|
|
40481
|
+
path: "/api/property/option/remove",
|
|
40482
|
+
summary: "Remove a select/multi_select option and clear it from every cell using it. Query: ?id=<id>",
|
|
40483
|
+
request: RemoveOptionReq,
|
|
40484
|
+
response: exports_external.object({ property: PropertySchema, cleared: exports_external.number() }),
|
|
40485
|
+
handler: handle3(async (req, { db }) => {
|
|
40486
|
+
const body = await req.json();
|
|
40487
|
+
return removeSelectOption(db, need3(req, "id"), body.name);
|
|
40488
|
+
})
|
|
40489
|
+
},
|
|
40340
40490
|
{
|
|
40341
40491
|
method: "PATCH",
|
|
40342
40492
|
path: "/api/property/width",
|
|
@@ -41302,4 +41452,4 @@ if (scopedConfigHelp) {
|
|
|
41302
41452
|
runMain(main, { showUsage: showUsage2 });
|
|
41303
41453
|
}
|
|
41304
41454
|
|
|
41305
|
-
//# debugId=
|
|
41455
|
+
//# debugId=0FE630E325CE9F8064756E2164756E21
|