datanotes 2.0.0 → 2.0.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/SECURITY.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  ## Reporting a vulnerability
4
4
 
5
- Please report vulnerabilities privately, through GitHub's **Report a vulnerability** button on this
6
- repository (Security → Advisories), not in a public issue. You will get an answer within a week.
5
+ Please report vulnerabilities privately, by email to the maintainer listed on the npm package
6
+ page ([npmjs.com/package/datanotes](https://www.npmjs.com/package/datanotes)), not in public. You will get an answer within a week.
7
7
  Include the version, how to reproduce it and what an attacker gains.
8
8
 
9
9
  ## Security model
@@ -7649,7 +7649,7 @@ var OPERATIONS = [
7649
7649
  },
7650
7650
  {
7651
7651
  "name": "infer_table",
7652
- "description": "Read-only schema inference from notes' frontmatter.\n- `folder` (recursive; \"\" = whole vault) and/or `paths`: notes not yet in a table. Returns\n `columns` (type, enum `values`, link `ref` to the table most targets belong to, `required`,\n `unique`, `dense`, `from` when the property name is not a valid column name, `stats`),\n `proposal` ready for create_table (`name`, `options.filename` or `timeColumn`, `columns`) and\n `adopt` ready for adopt_rows (`paths`, `rename`, `relocate: false`), plus `notes` to read.\n Review the proposal (names, enums, required) before creating; ask the user when unsure.\n- `table`: keys the table's rows use but the table does not declare (candidates for add_column,\n or keys to drop from rows).\nA string column becomes an enum with at most `max_enum_values` distinct values (default 12).",
7652
+ "description": "Read-only schema inference from notes' frontmatter.\n- `folder` (recursive; \"\" = whole vault) and/or `paths`: notes not yet in a table. Returns\n `columns` (type, enum `values`, link `ref` to the table most targets belong to, `required`,\n `unique`, `dense`, `from` when the property name is not a valid column name, `stats`),\n `proposal` ready for create_table (`name`, `options.filename` or `timeColumn`, `columns`) and\n `adopt` ready for adopt_rows (`paths`, `rename`, `relocate: false`), plus `notes` to read.\n Review the proposal (names, enums, required) before creating; ask the user when unsure.\n- `table`: keys the table's rows use but the table does not declare (candidates for add_column,\n or keys to drop from rows).\nA string column becomes an enum when its values repeat: at least 3 notes, at most `max_enum_values`\ndistinct values (default 12), each used twice on average, short texts.",
7653
7653
  "inputSchema": {
7654
7654
  "additionalProperties": false,
7655
7655
  "properties": {
@@ -12331,7 +12331,7 @@ function describeRefusal(data) {
12331
12331
  const op = data.op ?? "operation";
12332
12332
  const failed = (data.failed ?? []).length > 0;
12333
12333
  let head = failed ? `${op} failed` : `${op} refused \u2014 nothing was written`;
12334
- if (data.migrationId) head += ` (journal ${String(data.migrationId)}; use undo_migration to revert what was applied)`;
12334
+ if (failed && data.migrationId) head += ` (journal ${String(data.migrationId)}; use undo_migration to revert what was applied)`;
12335
12335
  return head + (shown.length ? `:
12336
12336
  ${shown.join("\n")}` : "");
12337
12337
  }
package/dist/api/index.js CHANGED
@@ -7607,7 +7607,7 @@ var OPERATIONS = [
7607
7607
  },
7608
7608
  {
7609
7609
  "name": "infer_table",
7610
- "description": "Read-only schema inference from notes' frontmatter.\n- `folder` (recursive; \"\" = whole vault) and/or `paths`: notes not yet in a table. Returns\n `columns` (type, enum `values`, link `ref` to the table most targets belong to, `required`,\n `unique`, `dense`, `from` when the property name is not a valid column name, `stats`),\n `proposal` ready for create_table (`name`, `options.filename` or `timeColumn`, `columns`) and\n `adopt` ready for adopt_rows (`paths`, `rename`, `relocate: false`), plus `notes` to read.\n Review the proposal (names, enums, required) before creating; ask the user when unsure.\n- `table`: keys the table's rows use but the table does not declare (candidates for add_column,\n or keys to drop from rows).\nA string column becomes an enum with at most `max_enum_values` distinct values (default 12).",
7610
+ "description": "Read-only schema inference from notes' frontmatter.\n- `folder` (recursive; \"\" = whole vault) and/or `paths`: notes not yet in a table. Returns\n `columns` (type, enum `values`, link `ref` to the table most targets belong to, `required`,\n `unique`, `dense`, `from` when the property name is not a valid column name, `stats`),\n `proposal` ready for create_table (`name`, `options.filename` or `timeColumn`, `columns`) and\n `adopt` ready for adopt_rows (`paths`, `rename`, `relocate: false`), plus `notes` to read.\n Review the proposal (names, enums, required) before creating; ask the user when unsure.\n- `table`: keys the table's rows use but the table does not declare (candidates for add_column,\n or keys to drop from rows).\nA string column becomes an enum when its values repeat: at least 3 notes, at most `max_enum_values`\ndistinct values (default 12), each used twice on average, short texts.",
7611
7611
  "inputSchema": {
7612
7612
  "additionalProperties": false,
7613
7613
  "properties": {
@@ -12289,7 +12289,7 @@ function describeRefusal(data) {
12289
12289
  const op = data.op ?? "operation";
12290
12290
  const failed = (data.failed ?? []).length > 0;
12291
12291
  let head = failed ? `${op} failed` : `${op} refused \u2014 nothing was written`;
12292
- if (data.migrationId) head += ` (journal ${String(data.migrationId)}; use undo_migration to revert what was applied)`;
12292
+ if (failed && data.migrationId) head += ` (journal ${String(data.migrationId)}; use undo_migration to revert what was applied)`;
12293
12293
  return head + (shown.length ? `:
12294
12294
  ${shown.join("\n")}` : "");
12295
12295
  }
@@ -10310,6 +10310,25 @@ function resolveDateTokens(where, tz, now) {
10310
10310
  const walk = (v) => Array.isArray(v) ? v.map(walk) : v && typeof v === "object" ? Object.fromEntries(Object.entries(v).map(([k, x]) => [k, walk(x)])) : resolveDateToken(v, tz, now);
10311
10311
  return walk(where);
10312
10312
  }
10313
+ function wallClock(now, tz) {
10314
+ const parts = new Intl.DateTimeFormat("en-CA", {
10315
+ timeZone: tz || "UTC",
10316
+ year: "numeric",
10317
+ month: "2-digit",
10318
+ day: "2-digit",
10319
+ hour: "2-digit",
10320
+ minute: "2-digit",
10321
+ second: "2-digit",
10322
+ hourCycle: "h23"
10323
+ }).formatToParts(now);
10324
+ const g = (type) => parts.find((p) => p.type === type)?.value ?? "00";
10325
+ return `${g("year")}-${g("month")}-${g("day")}T${g("hour")}:${g("minute")}:${g("second")}`;
10326
+ }
10327
+ function resolveDefault(value, now, tz) {
10328
+ if (value === DEFAULT_NOW) return tz === "" ? `${now.toISOString().slice(0, 19)}Z` : normalizeDatetime(wallClock(now, tz), tz);
10329
+ if (value === DEFAULT_TODAY) return wallClock(now, tz).slice(0, 10);
10330
+ return value;
10331
+ }
10313
10332
 
10314
10333
  // src/core/database.ts
10315
10334
  var DatabaseConfigError = class extends Error {
@@ -10914,7 +10933,8 @@ async function createTableImpl(app, settings, input, opts = {}) {
10914
10933
  throw new OperationInputError(`"time_column" must be a date or datetime column of the table`);
10915
10934
  }
10916
10935
  if (cfg.default_partition !== "") extra.partition = `{${tc.name}:${cfg.default_partition}}`;
10917
- extra.filename = `{${tc.name}:${cfg.filename}}`;
10936
+ const format = tc.type === "date" ? cfg.filename.replace(/\s*(\[[^\]]*\])?\s*[Hh]{1,2}.*$/, "") || "YYYY-MM-DD" : cfg.filename;
10937
+ extra.filename = `{${tc.name}:${format}}`;
10918
10938
  }
10919
10939
  const given = { ...input.options ?? {} };
10920
10940
  if ("path" in given) throw new OperationInputError(`database tables take "partition", not "path"`);
@@ -10991,10 +11011,11 @@ async function addColumnImpl(app, settings, input, opts = {}) {
10991
11011
  const cs = emptyChangeSet("add_column", `add column ${spec.name} to ${t.def.name}`, t.file.path);
10992
11012
  addSchemaChange(cs, t, after, settings);
10993
11013
  const violations = [];
11014
+ const fill = resolveDefault(spec.default, /* @__PURE__ */ new Date(), (await readDatabaseConfig(app, settings)).timezone);
10994
11015
  for (const r of await loadRows(app, settings, t)) {
10995
11016
  const cur = fmGet(r.ordered, spec.name);
10996
11017
  const has = r.ordered.some(([k]) => k === spec.name);
10997
- const value = has ? cur : spec.default;
11018
+ const value = has ? cur : fill;
10998
11019
  if (isEmptyValue(value)) {
10999
11020
  if (spec.required) {
11000
11021
  violations.push({
@@ -11009,7 +11030,7 @@ async function addColumnImpl(app, settings, input, opts = {}) {
11009
11030
  cs.edits.push({
11010
11031
  path: r.file.path,
11011
11032
  before: r.ordered,
11012
- after: fmAdd(r.ordered, spec.name, spec.default ?? null)
11033
+ after: fmAdd(r.ordered, spec.name, fill ?? null)
11013
11034
  });
11014
11035
  }
11015
11036
  }
@@ -11128,6 +11149,7 @@ async function alterColumnImpl(app, settings, input, opts = {}) {
11128
11149
  const widen = old.type === "date" && spec.type === "datetime";
11129
11150
  const tz = widen ? (await readDatabaseConfig(app, settings)).timezone : "";
11130
11151
  const violations = spec.unique ? uniqueViolations([spec], existing.map((r) => ({ path: r.file.path, ordered: r.ordered }))) : [];
11152
+ const fill = makeDense ? resolveDefault(spec.default, /* @__PURE__ */ new Date(), (await readDatabaseConfig(app, settings)).timezone) : void 0;
11131
11153
  for (const r of existing) {
11132
11154
  const has = r.ordered.some(([k]) => k === spec.name);
11133
11155
  let v = fmGet(r.ordered, spec.name);
@@ -11144,7 +11166,7 @@ async function alterColumnImpl(app, settings, input, opts = {}) {
11144
11166
  violations.push({ path: r.file.path, violations: validateValue(spec, v, validationContextFor(app, settings, r.file.path)) });
11145
11167
  }
11146
11168
  if (makeDense && !has) {
11147
- cs.edits.push({ path: r.file.path, before: r.ordered, after: fmAdd(r.ordered, spec.name, spec.default ?? null) });
11169
+ cs.edits.push({ path: r.file.path, before: r.ordered, after: fmAdd(r.ordered, spec.name, fill ?? null) });
11148
11170
  }
11149
11171
  }
11150
11172
  return finish(app, settings, cs, [], violations, opts);
@@ -11896,9 +11918,11 @@ async function planInheritedRows(app, settings, model, text2, refs, rewrite, cs)
11896
11918
  checkColumnRefs(model, ref2.full, effectiveColumns(model, ref2, t.catalog.entry).filter((c) => !own.has(String(c.name))), ref2.full);
11897
11919
  const old = await loadTable(app, settings, ref2.full);
11898
11920
  const dense = t.def.columns.filter((c) => c.dense);
11921
+ const now = /* @__PURE__ */ new Date();
11922
+ const tz = (await readDatabaseConfig(app, settings)).timezone;
11899
11923
  for (const r of await loadRows(app, settings, old)) {
11900
11924
  let after = rewrite(r.ordered);
11901
- for (const c of dense) if (!after.some(([k]) => k === c.name)) after = fmSet(after, c.name, c.default ?? null);
11925
+ for (const c of dense) if (!after.some(([k]) => k === c.name)) after = fmSet(after, c.name, resolveDefault(c.default, now, tz) ?? null);
11902
11926
  after = await fillIdentities(app, settings, t, after, r.file.path);
11903
11927
  const v = validateRow(t.def, after, { tableProperty: settings.tableProperty }, validationContextFor(app, settings, r.file.path));
11904
11928
  if (v.length > 0) violations.push({ path: r.file.path, violations: v });
@@ -12157,25 +12181,6 @@ function renderPathTemplate(template, values) {
12157
12181
  return sanitizeFileName(scalarForPath(v));
12158
12182
  });
12159
12183
  }
12160
- function wallClock(now, tz) {
12161
- const parts = new Intl.DateTimeFormat("en-CA", {
12162
- timeZone: tz || "UTC",
12163
- year: "numeric",
12164
- month: "2-digit",
12165
- day: "2-digit",
12166
- hour: "2-digit",
12167
- minute: "2-digit",
12168
- second: "2-digit",
12169
- hourCycle: "h23"
12170
- }).formatToParts(now);
12171
- const g = (type) => parts.find((p) => p.type === type)?.value ?? "00";
12172
- return `${g("year")}-${g("month")}-${g("day")}T${g("hour")}:${g("minute")}:${g("second")}`;
12173
- }
12174
- function resolveDefault(value, now, tz) {
12175
- if (value === DEFAULT_NOW) return tz === "" ? `${now.toISOString().slice(0, 19)}Z` : normalizeDatetime(wallClock(now, tz), tz);
12176
- if (value === DEFAULT_TODAY) return wallClock(now, tz).slice(0, 10);
12177
- return value;
12178
- }
12179
12184
  function computedColumnNames(t) {
12180
12185
  return new Set(t.def.columns.filter((c) => c.generated !== void 0 || c.identity !== void 0).map((c) => c.name));
12181
12186
  }
@@ -12811,6 +12816,7 @@ function planInsert(app, settings, t, input, env) {
12811
12816
  if (!known.has(k)) throw new OperationInputError(`unknown column "${k}" for table "${t.def.name}"`);
12812
12817
  }
12813
12818
  const computed = computedColumnNames(t);
12819
+ for (const c of t.def.columns) if (c.identity === "filename") computed.delete(c.name);
12814
12820
  for (const k of Object.keys(input.values)) {
12815
12821
  if (computed.has(k)) throw new OperationInputError(`"${k}" is a generated column: its value is computed`);
12816
12822
  }
@@ -13223,12 +13229,23 @@ async function deleteRowsImpl(app, settings, input, opts = {}) {
13223
13229
  sources.forEach((s) => referrers.add(s));
13224
13230
  }
13225
13231
  if (policy === "set_null") {
13232
+ const model = cachedMetastore(app, settings);
13226
13233
  for (const src of [...referrers].sort()) {
13227
13234
  const f = app.vault.getAbstractFileByPath(src);
13228
13235
  if (!f || !("extension" in f) || f.extension !== "md") continue;
13229
13236
  const text2 = await app.vault.read(f);
13230
13237
  const before = readOrderedFromMarkdown(text2).ordered;
13231
- cs.edits.push({ path: src, before, after: clearLinksTo(app, src, before, doomed) });
13238
+ const after = clearLinksTo(app, src, before, doomed);
13239
+ const owner = model ? rowTableFull(model, fmGet(after, settings.tableProperty)) : null;
13240
+ const ot = owner ? await loadTable(app, settings, owner) : null;
13241
+ if (ot && !src.startsWith(`${archiveFolder(ot.def)}/`)) {
13242
+ for (const c of ot.def.columns) {
13243
+ if (c.required && !isEmptyValue(fmGet(before, c.name)) && isEmptyValue(fmGet(after, c.name))) {
13244
+ conflicts.push({ path: src, message: `"${c.name}" is required and would be left empty: change or delete this row first, or use on_referenced "ignore"` });
13245
+ }
13246
+ }
13247
+ }
13248
+ cs.edits.push({ path: src, before, after });
13232
13249
  }
13233
13250
  }
13234
13251
  const folder = archiveFolder(t.def);
@@ -13304,6 +13321,9 @@ async function retargetRows(app, settings, dst, notes, r, cs, opts) {
13304
13321
  }
13305
13322
  try {
13306
13323
  const values = new Map(after);
13324
+ for (const c of dst.def.columns) {
13325
+ if (c.identity === "filename" && isEmptyValue(values.get(c.name))) values.set(c.name, basenameOf(n.path));
13326
+ }
13307
13327
  const base = `${renderPathTemplate(dst.def.layout.rowPath, values)}/${renderPathTemplate(dst.def.options.filename, values)}`;
13308
13328
  to = `${base}.md`;
13309
13329
  for (let k = 2; to !== n.path && (taken.has(to) || app.vault.getAbstractFileByPath(to) != null) && k <= MAX_NAME_SUFFIX; k++) {
@@ -13647,6 +13667,10 @@ async function adoptCreatedNote(app, settings, file) {
13647
13667
  const present = new Set(ordered.map(([k]) => k));
13648
13668
  const values = Object.fromEntries(Object.entries(target.values).filter(([k]) => !present.has(k)));
13649
13669
  for (const c of (await loadTable(app, settings, target.table)).def.columns) {
13670
+ if (c.generated !== void 0 || c.identity !== void 0) {
13671
+ delete values[c.name];
13672
+ continue;
13673
+ }
13650
13674
  if (c.external_default !== void 0 && !present.has(c.name) && values[c.name] === void 0) values[c.name] = c.external_default;
13651
13675
  }
13652
13676
  const r = await adoptRows(app, settings, { table: target.table, paths: [file.path], perRow: { [file.path]: values }, relocate: false });
@@ -23348,7 +23372,7 @@ var OPERATIONS = [
23348
23372
  },
23349
23373
  {
23350
23374
  "name": "infer_table",
23351
- "description": "Read-only schema inference from notes' frontmatter.\n- `folder` (recursive; \"\" = whole vault) and/or `paths`: notes not yet in a table. Returns\n `columns` (type, enum `values`, link `ref` to the table most targets belong to, `required`,\n `unique`, `dense`, `from` when the property name is not a valid column name, `stats`),\n `proposal` ready for create_table (`name`, `options.filename` or `timeColumn`, `columns`) and\n `adopt` ready for adopt_rows (`paths`, `rename`, `relocate: false`), plus `notes` to read.\n Review the proposal (names, enums, required) before creating; ask the user when unsure.\n- `table`: keys the table's rows use but the table does not declare (candidates for add_column,\n or keys to drop from rows).\nA string column becomes an enum with at most `max_enum_values` distinct values (default 12).",
23375
+ "description": "Read-only schema inference from notes' frontmatter.\n- `folder` (recursive; \"\" = whole vault) and/or `paths`: notes not yet in a table. Returns\n `columns` (type, enum `values`, link `ref` to the table most targets belong to, `required`,\n `unique`, `dense`, `from` when the property name is not a valid column name, `stats`),\n `proposal` ready for create_table (`name`, `options.filename` or `timeColumn`, `columns`) and\n `adopt` ready for adopt_rows (`paths`, `rename`, `relocate: false`), plus `notes` to read.\n Review the proposal (names, enums, required) before creating; ask the user when unsure.\n- `table`: keys the table's rows use but the table does not declare (candidates for add_column,\n or keys to drop from rows).\nA string column becomes an enum when its values repeat: at least 3 notes, at most `max_enum_values`\ndistinct values (default 12), each used twice on average, short texts.",
23352
23376
  "inputSchema": {
23353
23377
  "additionalProperties": false,
23354
23378
  "properties": {
@@ -30682,6 +30706,16 @@ var NodeVault = class {
30682
30706
  this.files = /* @__PURE__ */ new Map();
30683
30707
  this.folders = /* @__PURE__ */ new Map();
30684
30708
  this.root = new NodeFolder("");
30709
+ /**
30710
+ * Bring one path of the index in line with the disk (for the watcher). Emits create / modify /
30711
+ * delete when something changed; returns what happened.
30712
+ */
30713
+ /**
30714
+ * Paths the vault is writing right now (and the folders above them). Between the change on disk
30715
+ * and the index update there is an `await`: a rescan landing there must not take the vault's own
30716
+ * write for a change made outside (a moved file would be dropped as deleted and added again).
30717
+ */
30718
+ this.busy = /* @__PURE__ */ new Map();
30685
30719
  this.adapter = new NodeAdapter(basePath);
30686
30720
  this.events.setMaxListeners(0);
30687
30721
  this.folders.set("", this.root);
@@ -30767,12 +30801,25 @@ var NodeVault = class {
30767
30801
  async refreshStat(f) {
30768
30802
  f.stat = statsOf(await import_promises.default.stat(this.abs(f.path)));
30769
30803
  }
30770
- /**
30771
- * Bring one path of the index in line with the disk (for the watcher). Emits create / modify /
30772
- * delete when something changed; returns what happened.
30773
- */
30804
+ async guarded(paths, fn) {
30805
+ for (const p of paths) this.busy.set(p, (this.busy.get(p) ?? 0) + 1);
30806
+ try {
30807
+ return await fn();
30808
+ } finally {
30809
+ for (const p of paths) {
30810
+ const n = (this.busy.get(p) ?? 1) - 1;
30811
+ if (n <= 0) this.busy.delete(p);
30812
+ else this.busy.set(p, n);
30813
+ }
30814
+ }
30815
+ }
30816
+ isBusy(rel) {
30817
+ for (const p of this.busy.keys()) if (rel === p || rel.startsWith(`${p}/`) || p.startsWith(`${rel}/`)) return true;
30818
+ return false;
30819
+ }
30774
30820
  syncPath(rel) {
30775
30821
  if (rel === "" || hidden(rel)) return null;
30822
+ if (this.isBusy(rel)) return null;
30776
30823
  let st = null;
30777
30824
  try {
30778
30825
  st = import_node_fs3.default.statSync(this.abs(rel));
@@ -30864,8 +30911,10 @@ var NodeVault = class {
30864
30911
  }
30865
30912
  /** Write a file's text (internal: also used by the file manager). */
30866
30913
  async writeText(file, data) {
30867
- await atomicWrite(this.abs(file.path), data);
30868
- await this.refreshStat(file);
30914
+ await this.guarded([file.path], async () => {
30915
+ await atomicWrite(this.abs(file.path), data);
30916
+ await this.refreshStat(file);
30917
+ });
30869
30918
  this.emit("modify", file);
30870
30919
  }
30871
30920
  async process(file, fn) {
@@ -30878,9 +30927,11 @@ var NodeVault = class {
30878
30927
  async create(p, data) {
30879
30928
  const rel = p.replace(/^\/+/, "");
30880
30929
  if (import_node_fs3.default.existsSync(this.abs(rel))) throw new Error(`File already exists: ${rel}`);
30881
- await this.ensureFolder(dirname(rel));
30882
- await atomicWrite(this.abs(rel), data);
30883
- const f = this.addFile(rel, statsOf(await import_promises.default.stat(this.abs(rel))));
30930
+ const f = await this.guarded([rel], async () => {
30931
+ await this.ensureFolder(dirname(rel));
30932
+ await atomicWrite(this.abs(rel), data);
30933
+ return this.addFile(rel, statsOf(await import_promises.default.stat(this.abs(rel))));
30934
+ });
30884
30935
  this.emit("create", f);
30885
30936
  return f;
30886
30937
  }
@@ -30897,6 +30948,9 @@ var NodeVault = class {
30897
30948
  return this.folders.get(rel);
30898
30949
  }
30899
30950
  async delete(file, force) {
30951
+ return this.guarded([file.path], () => this.deleteNow(file, force));
30952
+ }
30953
+ async deleteNow(file, force) {
30900
30954
  const rel = file.path;
30901
30955
  if (this.folders.has(rel)) {
30902
30956
  const gone = [...this.files.values()].filter((f2) => f2.path.startsWith(`${rel}/`));
@@ -30911,8 +30965,11 @@ var NodeVault = class {
30911
30965
  if (f) this.emit("delete", f);
30912
30966
  }
30913
30967
  /** Move to `.trash/` (Obsidian's local trash; also for "system", which Node cannot reach) or delete. */
30914
- async trash(file, _system) {
30968
+ async trash(file, system) {
30915
30969
  if (this.config.trashOption === "none") return this.delete(file, true);
30970
+ return this.guarded([file.path], () => this.trashNow(file, system));
30971
+ }
30972
+ async trashNow(file, _system) {
30916
30973
  const target = await this.freeName(`.trash/${basename2(file.path)}`);
30917
30974
  const isFolder = this.folders.has(file.path);
30918
30975
  const gone = isFolder ? [...this.files.values()].filter((f) => f.path.startsWith(`${file.path}/`)) : [];
@@ -30944,6 +31001,9 @@ var NodeVault = class {
30944
31001
  const from = item.path;
30945
31002
  const to = newPath.replace(/^\/+|\/+$/g, "");
30946
31003
  if (from === to) return /* @__PURE__ */ new Map();
31004
+ return this.guarded([from, to], () => this.moveNow(item, from, to));
31005
+ }
31006
+ async moveNow(item, from, to) {
30947
31007
  if (import_node_fs3.default.existsSync(this.abs(to)) && from.toLowerCase() !== to.toLowerCase()) throw new Error(`Destination exists: ${to}`);
30948
31008
  await this.ensureFolder(dirname(to));
30949
31009
  await import_promises.default.rename(this.abs(from), this.abs(to));
@@ -31264,7 +31324,7 @@ var NodeWatcher = class {
31264
31324
  };
31265
31325
 
31266
31326
  // src/node/service.ts
31267
- var VERSION = true ? "2.0.0" : "dev";
31327
+ var VERSION = true ? "2.0.1" : "dev";
31268
31328
  function parseArgs(argv, env = process.env) {
31269
31329
  const get = (name) => {
31270
31330
  const i = argv.indexOf(`--${name}`);
@@ -31439,7 +31499,7 @@ Docs: https://datanotes.dev`;
31439
31499
  }
31440
31500
 
31441
31501
  // src/node/cli.ts
31442
- var VERSION2 = true ? "2.0.0" : "dev";
31502
+ var VERSION2 = true ? "2.0.1" : "dev";
31443
31503
  async function main2(argv = process.argv.slice(2)) {
31444
31504
  const [command, ...rest] = argv;
31445
31505
  if (command === "--version" || command === "-v" || command === "version") return void process.stdout.write(`${VERSION2}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datanotes",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "The engine of your second brain: Markdown notes as database tables, for you and your agents.",
5
5
  "keywords": [
6
6
  "markdown",
@@ -65,7 +65,8 @@
65
65
  "build:service": "node scripts/check-layers.mjs && tsc -noEmit -skipLibCheck && node esbuild.service.mjs && npm run build:api",
66
66
  "prepack": "npm run build:service",
67
67
  "build:api": "tsc -p tsconfig.api.json && node esbuild.api.mjs",
68
- "build": "npm run build:service"
68
+ "build": "npm run build:service",
69
+ "prepublishOnly": "node scripts/release-window.mjs"
69
70
  },
70
71
  "devDependencies": {
71
72
  "@types/node": "^22.10.0",