@vrtmrz/livesync-commonlib 0.1.39 → 0.1.40

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.
@@ -20,5 +20,5 @@ export declare const readline: typeof nodeReadlinePromises;
20
20
  export declare const builtinModules: readonly string[];
21
21
  /** Returns whether a module specifier refers to a Node built-in. */
22
22
  export declare const isBuiltin: typeof nodeIsBuiltin;
23
- export { NodeStorageAdapter, createNodeStorage, validateStoragePath, type CreateNodeStorageOptions, type NodeStorageStat, } from "./storage.js";
23
+ export { NodeStorageAdapter, createNodeStorage, getNodeFileCreationTime, validateStoragePath, type CreateNodeStorageOptions, type NodeStorageStat, } from "./storage.js";
24
24
  export { createNodeStandardIo, type CreateNodeStandardIoOptions, type NodeStandardInput, type NodeStandardOutput, } from "./standardIo.js";
@@ -15,6 +15,7 @@ const isBuiltin = nodeIsBuiltin;
15
15
  import {
16
16
  NodeStorageAdapter,
17
17
  createNodeStorage,
18
+ getNodeFileCreationTime,
18
19
  validateStoragePath
19
20
  } from "./storage.js";
20
21
  import {
@@ -28,6 +29,7 @@ export {
28
29
  fileURLToPath,
29
30
  fs,
30
31
  fsPromises,
32
+ getNodeFileCreationTime,
31
33
  isBuiltin,
32
34
  os,
33
35
  path,
@@ -1,6 +1,9 @@
1
+ import type { Stats } from "node:fs";
1
2
  import type { UXDataWriteOptions, UXStat } from "../../common/types.js";
2
3
  import type { IStorageAdapter } from "../../serviceModules/adapters/IStorageAdapter.js";
3
4
  export type NodeStorageStat = UXStat;
5
+ /** Read creation time in integer milliseconds, using change time when birth time is unavailable. */
6
+ export declare function getNodeFileCreationTime(stat: Pick<Stats, "birthtimeMs" | "ctimeMs">): number;
4
7
  export interface CreateNodeStorageOptions {
5
8
  /**
6
9
  * Host-selected root directory. Adapter paths are resolved relative to this root.
@@ -2,6 +2,9 @@ import { constants as nodeFsConstants } from "node:fs";
2
2
  import * as nodeFsPromises from "node:fs/promises";
3
3
  import * as nodePath from "node:path";
4
4
  import { validateStoragePath } from "../storagePath.js";
5
+ function getNodeFileCreationTime(stat) {
6
+ return Math.floor(stat.birthtimeMs > 0 ? stat.birthtimeMs : stat.ctimeMs);
7
+ }
5
8
  const NO_FOLLOW = nodeFsConstants.O_NOFOLLOW ?? 0;
6
9
  class SymbolicLinkStoragePathError extends Error {
7
10
  constructor(path) {
@@ -48,7 +51,7 @@ class NodeStorageAdapter {
48
51
  return {
49
52
  size: stat.size,
50
53
  mtime: Math.floor(stat.mtimeMs),
51
- ctime: Math.floor(stat.ctimeMs),
54
+ ctime: getNodeFileCreationTime(stat),
52
55
  type: stat.isDirectory() ? "folder" : "file"
53
56
  };
54
57
  } catch (error) {
@@ -161,5 +164,6 @@ import { validateStoragePath as validateStoragePath2 } from "../storagePath.js";
161
164
  export {
162
165
  NodeStorageAdapter,
163
166
  createNodeStorage,
167
+ getNodeFileCreationTime,
164
168
  validateStoragePath2 as validateStoragePath
165
169
  };
@@ -39,6 +39,8 @@ await storage.write('notes/example.md', '# Example');
39
39
 
40
40
  `rootPath` may be absolute or process-relative. The host should normally resolve and validate configuration before constructing the adapter.
41
41
 
42
+ Node storage reports creation time from a positive `birthtimeMs`, falling back to `ctimeMs` when birth time is unavailable. Timestamps are rounded down to integer milliseconds. Hosts converting native Node stats can use `getNodeFileCreationTime` from the `/node` entry for the same conversion. Some platforms already return change time as birth time, so this fallback cannot guarantee a stable creation time there. Replacing a file can also change its birth time. Write options preserve modification time; the Node adapter does not restore creation time from `ctime`.
43
+
42
44
  The Node adapter rejects symbolic-link components which it observes in an adapter path before an operation. File opens also use `O_NOFOLLOW` for the final component where the platform exposes that flag. These checks protect against invalid paths and symbolic links which already exist when an operation begins. Its concrete `rename(sourcePath, targetPath)` operation validates both paths before providing the atomic rename expected by Node hosts.
43
45
 
44
46
  The host-selected `rootPath` is a trust boundary, not an operating-system filesystem sandbox. The adapter assumes that an untrusted local process cannot replace that root or its descendants concurrently with an operation. A host which grants another local actor write access to the tree must not rely on the adapter as a privilege boundary.
@@ -86,6 +86,8 @@ The CLI extends the same neutral context with its database path and injected `St
86
86
 
87
87
  The CLI filesystem composition receives a configured base directory and constructs the rooted Node storage adapter in `src/apps/cli/adapters/NodeFileSystemAdapter.ts`. Commonlib's shared storage result table checks the focused root contract; the consumer adds its file cache, Vault-shaped operations, case handling, and command policy.
88
88
 
89
+ Unreleased Node timestamp tests verify that `stat` and `trystat` retain an available birth time when change time advances, fall back when birth time is zero, and return integer milliseconds. Packed-package checks exercise the shared `getNodeFileCreationTime` export. With the exact locally packed candidate installed, downstream CLI tests verify that an in-place edit retains creation time in local Metadata. The CLI-to-real-Obsidian scenario additionally verifies the edited content and creation time in received Metadata. These checks do not establish the reporter's ZFS behaviour or physical-file creation times in Obsidian.
90
+
89
91
  The maintained CLI checks include unit tests, Deno-driven CLI E2E, Compose-based compatibility runs, and the CLI-to-real-Obsidian synchronisation scenario in `test/e2e-obsidian/scripts/cli-to-obsidian-sync.ts`. The latter is the strongest current evidence that the CLI and plug-in compose compatible Commonlib behaviour across two hosts.
90
92
 
91
93
  The CLI mirror command returns Commonlib's full-scan aggregate, and daemon start-up stops when that aggregate reports an actual pair failure. Deliberately skipped pairs remain a successful scan result. Commonlib owns the pair classification and aggregate; the CLI owns its command and process policy.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vrtmrz/livesync-commonlib",
3
- "version": "0.1.39",
3
+ "version": "0.1.40",
4
4
  "description": "Shared data, storage, and replication primitives for Self-hosted LiveSync clients.",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",