@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 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.0",
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
- var PROP_TYPES, PROPERTY_COLS, PROPERTY_SELECT, addProperty, updateProperty, removeProperty;
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
- if (fields.config !== undefined)
17605
- validateConfig(effectiveType, fields.config);
17606
- else if (typeChanged)
17607
- validateConfig(fields.type, undefined);
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 (fields.config !== undefined)
17613
- emit(db, "properties", id, "config", fields.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: { type: "string", description: "Comma list for select/multi_select" },
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: { type: "string", description: "Raw config JSON (@file/@- ok)" },
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 .x { width:22px; height:22px; display:grid; place-items:center; color:var(--muted); border-radius:5px; }
37967
- .optrow .x:hover { background:var(--hover-2); color:var(--danger); }
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/options, reorder. Query: ?id=<id>",
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=2746F12D819C739D64756E2164756E21
41455
+ //# debugId=0FE630E325CE9F8064756E2164756E21