@mnemosyne_os/affine-reader 0.1.0 → 0.1.2

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/LICENSE-AFFiNE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022-present TOEVERYTHING PTE. LTD. and its affiliates.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE.md CHANGED
@@ -3,7 +3,10 @@
3
3
  ## AFFiNE — `src/vendor/affine/`
4
4
 
5
5
  Copyright (c) 2022-present TOEVERYTHING PTE. LTD. and its affiliates.
6
- Licensed under the **MIT License**.
6
+ Licensed under the **MIT License**, whose full text ships beside this file as
7
+ [`LICENSE-AFFiNE`](./LICENSE-AFFiNE). The MIT License asks that the copyright
8
+ notice **and the permission notice** travel with the code, so both are included
9
+ verbatim rather than merely referenced.
7
10
 
8
11
  | | |
9
12
  |---|---|
package/README.md CHANGED
@@ -46,7 +46,8 @@ answers** — nothing to read versus AFFiNE not installed. Both are reported.
46
46
 
47
47
  ### Bring your own SQLite
48
48
 
49
- `openNodeSqlite` uses Node's built-in `node:sqlite`, which needs **Node 22.5**.
49
+ `openNodeSqlite` uses Node's built-in `node:sqlite`, which runs without a flag from
50
+ **Node 22.13** and **23.4** onward. It exists from 22.5 behind `--experimental-sqlite`.
50
51
  Electron 31 ships Node 20 and has none, so a host passes its own adapter:
51
52
 
52
53
  ```ts
@@ -61,7 +62,8 @@ readWorkspace({ prepare: (sql) => raw.prepare(sql), close: () => raw.close() });
61
62
 
62
63
  | | |
63
64
  |---|---|
64
- | Location | `<app-data>/AFFiNE/<workspaces\|userspaces>/<peer>/<id>/storage.db` |
65
+ | Location | `<app-data>/AFFiNE*/<workspaces\|userspaces>/<peer>/<id>/storage.db` |
66
+ | Channels | every one you have installed. AFFiNE's build names the folder `AFFiNE` for a stable release and `AFFiNE-canary`, `AFFiNE-beta`, `AFFiNE-internal` otherwise, and most of their releases are canary |
65
67
  | Schema | nbstore `v2` (`snapshots` + `updates` + `blobs`), and the legacy `v1` shape |
66
68
  | Content | Yjs history replayed into a document, rendered by AFFiNE's own MIT parser |
67
69
 
package/dist/cli.d.ts CHANGED
@@ -8,7 +8,9 @@
8
8
  * actually live) before a byte is read, and nothing is ever written back into
9
9
  * AFFiNE's directory.
10
10
  *
11
- * ⚠️ Needs Node >= 22.5 for `node:sqlite`. It says so rather than failing with
12
- * an opaque module error.
11
+ * ⚠️ Needs `node:sqlite` without a flag, which means Node >= 22.13 (or >= 23.4).
12
+ * The module landed in 22.5 behind `--experimental-sqlite` and was unflagged in
13
+ * 22.13.0 / 23.4.0, so "22.5" as the boundary is wrong and produces a message
14
+ * that contradicts itself on 22.11: "needs >= 22.5, this is v22.11".
13
15
  */
14
16
  export declare function runCli(argv?: string[]): number;
package/dist/cli.js CHANGED
@@ -9,8 +9,10 @@
9
9
  * actually live) before a byte is read, and nothing is ever written back into
10
10
  * AFFiNE's directory.
11
11
  *
12
- * ⚠️ Needs Node >= 22.5 for `node:sqlite`. It says so rather than failing with
13
- * an opaque module error.
12
+ * ⚠️ Needs `node:sqlite` without a flag, which means Node >= 22.13 (or >= 23.4).
13
+ * The module landed in 22.5 behind `--experimental-sqlite` and was unflagged in
14
+ * 22.13.0 / 23.4.0, so "22.5" as the boundary is wrong and produces a message
15
+ * that contradicts itself on 22.11: "needs >= 22.5, this is v22.11".
14
16
  */
15
17
  Object.defineProperty(exports, "__esModule", { value: true });
16
18
  exports.runCli = runCli;
@@ -29,7 +31,11 @@ function runCli(argv = process.argv.slice(2)) {
29
31
  return outDir ? 0 : 2;
30
32
  }
31
33
  if (!(0, nodeSqlite_1.hasNodeSqlite)()) {
32
- console.error(`This tool needs Node >= 22.5 (for node:sqlite); this is ${process.version}.`);
34
+ // Says what is missing and what to do, rather than a version rule the running
35
+ // version can appear to satisfy.
36
+ console.error(`node:sqlite is not available in this runtime (${process.version}).`);
37
+ console.error('Use Node 22.13+ or 23.4+, where it needs no flag.');
38
+ console.error('On Node 22.5 to 22.12 it exists behind --experimental-sqlite.');
33
39
  return 1;
34
40
  }
35
41
  const dataDir = (0, locate_1.affineDataDir)();
@@ -37,6 +37,7 @@ export declare function fileNameFor(title: string, docId: string): string;
37
37
  export declare function workspaceFolderName(ref: {
38
38
  id: string;
39
39
  peer: string;
40
+ channel?: string;
40
41
  }): string;
41
42
  /**
42
43
  * Read `db` and write one Markdown file per doc into `outDir`.
@@ -41,7 +41,12 @@ function fileNameFor(title, docId) {
41
41
  * doors cannot resolve the same workspace to two different directories.
42
42
  */
43
43
  function workspaceFolderName(ref) {
44
- return `affine-${slugify(ref.peer, 'peer')}-${slugify(ref.id, 'id')}`;
44
+ // The channel joins the name for the same reason the peer did: a stable install
45
+ // and a canary one can both hold a workspace with this id. The stable channel
46
+ // adds nothing, so its folders keep the plain shape.
47
+ const channel = ref.channel && ref.channel !== 'AFFiNE' ? ref.channel.replace(/^AFFiNE-/, '') : '';
48
+ const parts = ['affine', channel && slugify(channel, 'channel'), slugify(ref.peer, 'peer'), slugify(ref.id, 'id')];
49
+ return parts.filter(Boolean).join('-');
45
50
  }
46
51
  function blobFileName(key, mime) {
47
52
  const ext = (mime.split('/')[1] ?? 'bin').replace('+xml', '').replace(/[^a-z0-9]/gi, '');
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * ⛔ READ ONLY, in every direction. Two processes writing one CRDT corrupt it.
9
9
  */
10
- export { affineDataDir, findWorkspaces } from './locate';
10
+ export { affineDataDir, affineDataDirs, findWorkspaces } from './locate';
11
11
  export { SIDECAR_SUFFIXES, stageDatabase } from './stage';
12
12
  export type { StagedDatabase } from './stage';
13
13
  export { detectSchema, forEachBlob, loadDoc, readBlobIndex, readDocIndex, readWorkspace, readWorkspaceId, } from './read';
package/dist/index.js CHANGED
@@ -9,9 +9,10 @@
9
9
  * ⛔ READ ONLY, in every direction. Two processes writing one CRDT corrupt it.
10
10
  */
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.openNodeSqlite = exports.hasNodeSqlite = exports.workspaceFolderName = exports.slugify = exports.fileNameFor = exports.exportWorkspace = exports.readWorkspaceId = exports.readWorkspace = exports.readDocIndex = exports.readBlobIndex = exports.loadDoc = exports.forEachBlob = exports.detectSchema = exports.stageDatabase = exports.SIDECAR_SUFFIXES = exports.findWorkspaces = exports.affineDataDir = void 0;
12
+ exports.openNodeSqlite = exports.hasNodeSqlite = exports.workspaceFolderName = exports.slugify = exports.fileNameFor = exports.exportWorkspace = exports.readWorkspaceId = exports.readWorkspace = exports.readDocIndex = exports.readBlobIndex = exports.loadDoc = exports.forEachBlob = exports.detectSchema = exports.stageDatabase = exports.SIDECAR_SUFFIXES = exports.findWorkspaces = exports.affineDataDirs = exports.affineDataDir = void 0;
13
13
  var locate_1 = require("./locate");
14
14
  Object.defineProperty(exports, "affineDataDir", { enumerable: true, get: function () { return locate_1.affineDataDir; } });
15
+ Object.defineProperty(exports, "affineDataDirs", { enumerable: true, get: function () { return locate_1.affineDataDirs; } });
15
16
  Object.defineProperty(exports, "findWorkspaces", { enumerable: true, get: function () { return locate_1.findWorkspaces; } });
16
17
  var stage_1 = require("./stage");
17
18
  Object.defineProperty(exports, "SIDECAR_SUFFIXES", { enumerable: true, get: function () { return stage_1.SIDECAR_SUFFIXES; } });
package/dist/locate.d.ts CHANGED
@@ -12,7 +12,20 @@
12
12
  * available. `layout` says which one a ref came from so a caller can tell them apart.
13
13
  */
14
14
  import type { WorkspaceRef } from './types';
15
- /** Root of AFFiNE's application data, or null when the app was never installed here. */
15
+ /**
16
+ * Every AFFiNE data directory on this machine, one per release channel.
17
+ *
18
+ * 🚨 There is more than one, and missing that is how this tool tells someone who
19
+ * clearly has AFFiNE installed that they do not. Their build script sets
20
+ * `productName = stableBuild ? 'AFFiNE' : 'AFFiNE-<buildType>'`, so a canary,
21
+ * beta or internal build keeps its workspaces in `AFFiNE-canary` and friends.
22
+ * Most of AFFiNE's own releases are canary, so that is not an edge case.
23
+ */
24
+ export declare function affineDataDirs(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): string[];
25
+ /**
26
+ * The stable channel's data directory, or the first channel found, or null when
27
+ * AFFiNE was never installed here. Kept for callers that want one path to show.
28
+ */
16
29
  export declare function affineDataDir(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): string | null;
17
30
  /**
18
31
  * Every workspace reachable on this machine. Never throws for a missing AFFiNE —
package/dist/locate.js CHANGED
@@ -13,23 +13,47 @@
13
13
  * available. `layout` says which one a ref came from so a caller can tell them apart.
14
14
  */
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.affineDataDirs = affineDataDirs;
16
17
  exports.affineDataDir = affineDataDir;
17
18
  exports.findWorkspaces = findWorkspaces;
18
19
  const node_fs_1 = require("node:fs");
19
20
  const node_path_1 = require("node:path");
20
21
  const SPACE_DIRS = ['workspaces', 'userspaces'];
21
- /** Root of AFFiNE's application data, or null when the app was never installed here. */
22
- function affineDataDir(env = process.env, platform = process.platform) {
22
+ /** Where Electron puts application data for this platform. */
23
+ function appDataRoot(env, platform) {
23
24
  const home = env.HOME ?? env.USERPROFILE ?? '';
24
25
  const base = platform === 'win32'
25
26
  ? env.APPDATA
26
27
  : platform === 'darwin'
27
28
  ? home && (0, node_path_1.join)(home, 'Library', 'Application Support')
28
29
  : (env.XDG_CONFIG_HOME ?? (home && (0, node_path_1.join)(home, '.config')));
30
+ return base || null;
31
+ }
32
+ /**
33
+ * Every AFFiNE data directory on this machine, one per release channel.
34
+ *
35
+ * 🚨 There is more than one, and missing that is how this tool tells someone who
36
+ * clearly has AFFiNE installed that they do not. Their build script sets
37
+ * `productName = stableBuild ? 'AFFiNE' : 'AFFiNE-<buildType>'`, so a canary,
38
+ * beta or internal build keeps its workspaces in `AFFiNE-canary` and friends.
39
+ * Most of AFFiNE's own releases are canary, so that is not an edge case.
40
+ */
41
+ function affineDataDirs(env = process.env, platform = process.platform) {
42
+ const base = appDataRoot(env, platform);
29
43
  if (!base)
30
- return null;
31
- const dir = (0, node_path_1.join)(base, 'AFFiNE');
32
- return (0, node_fs_1.existsSync)(dir) ? dir : null;
44
+ return [];
45
+ return safeReaddir(base)
46
+ .filter((name) => name === 'AFFiNE' || name.startsWith('AFFiNE-'))
47
+ .map((name) => (0, node_path_1.join)(base, name))
48
+ .filter(isDir)
49
+ .sort((a, b) => a.length - b.length); // stable channel first, it is the shortest
50
+ }
51
+ /**
52
+ * The stable channel's data directory, or the first channel found, or null when
53
+ * AFFiNE was never installed here. Kept for callers that want one path to show.
54
+ */
55
+ function affineDataDir(env = process.env, platform = process.platform) {
56
+ return affineDataDirs(env, platform)[0] ?? null;
33
57
  }
34
58
  function isDir(path) {
35
59
  try {
@@ -48,29 +72,34 @@ function isDir(path) {
48
72
  * is installed at all. The two are different answers and callers need both.
49
73
  */
50
74
  function findWorkspaces(opts = {}) {
51
- const dataDir = opts.dataDir === undefined ? affineDataDir() : opts.dataDir;
52
- if (!dataDir || !isDir(dataDir))
53
- return [];
75
+ // Every channel by default (AFFiNE, AFFiNE-canary, AFFiNE-beta …); one directory
76
+ // when a caller names it, which is what the tests do.
77
+ const dataDirs = opts.dataDir === undefined ? affineDataDirs() : opts.dataDir ? [opts.dataDir] : [];
54
78
  const found = [];
55
- for (const kindDir of SPACE_DIRS) {
56
- const kind = kindDir === 'workspaces' ? 'workspace' : 'userspace';
57
- const root = (0, node_path_1.join)(dataDir, kindDir);
58
- if (!isDir(root))
79
+ for (const dataDir of dataDirs) {
80
+ if (!isDir(dataDir))
59
81
  continue;
60
- for (const entry of safeReaddir(root)) {
61
- const entryPath = (0, node_path_1.join)(root, entry);
62
- if (!isDir(entryPath))
63
- continue;
64
- // v1 put storage.db directly under the workspace id; v2 inserted a <peer> level.
65
- const v1Db = (0, node_path_1.join)(entryPath, 'storage.db');
66
- if ((0, node_fs_1.existsSync)(v1Db)) {
67
- found.push({ id: entry, peer: 'local', kind, dbPath: v1Db, layout: 'v1' });
82
+ const channel = (0, node_path_1.basename)(dataDir);
83
+ for (const kindDir of SPACE_DIRS) {
84
+ const kind = kindDir === 'workspaces' ? 'workspace' : 'userspace';
85
+ const root = (0, node_path_1.join)(dataDir, kindDir);
86
+ if (!isDir(root))
68
87
  continue;
69
- }
70
- for (const workspaceId of safeReaddir(entryPath)) {
71
- const dbPath = (0, node_path_1.join)(entryPath, workspaceId, 'storage.db');
72
- if ((0, node_fs_1.existsSync)(dbPath))
73
- found.push({ id: workspaceId, peer: entry, kind, dbPath, layout: 'v2' });
88
+ for (const entry of safeReaddir(root)) {
89
+ const entryPath = (0, node_path_1.join)(root, entry);
90
+ if (!isDir(entryPath))
91
+ continue;
92
+ // v1 put storage.db directly under the workspace id; v2 inserted a <peer> level.
93
+ const v1Db = (0, node_path_1.join)(entryPath, 'storage.db');
94
+ if ((0, node_fs_1.existsSync)(v1Db)) {
95
+ found.push({ id: entry, peer: 'local', kind, dbPath: v1Db, layout: 'v1', channel });
96
+ continue;
97
+ }
98
+ for (const workspaceId of safeReaddir(entryPath)) {
99
+ const dbPath = (0, node_path_1.join)(entryPath, workspaceId, 'storage.db');
100
+ if ((0, node_fs_1.existsSync)(dbPath))
101
+ found.push({ id: workspaceId, peer: entry, kind, dbPath, layout: 'v2', channel });
102
+ }
74
103
  }
75
104
  }
76
105
  }
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * A read-only SQLite adapter backed by Node's built-in `node:sqlite`.
3
3
  *
4
- * ⚠️ `node:sqlite` landed in Node 22.5. Electron 31 ships Node 20, so the app CANNOT
5
- * use this one it passes an adapter over its own better-sqlite3. This exists for
6
- * scripts and tests, and it says so when the runtime cannot provide it instead of
7
- * failing with an opaque module error.
4
+ * ⚠️ `node:sqlite` landed in Node 22.5 behind `--experimental-sqlite` and was
5
+ * unflagged in 22.13.0 / 23.4.0, so the usable floor is 22.13. Electron 31 ships
6
+ * Node 20 and has none of it, which is why the app passes an adapter over its own
7
+ * better-sqlite3. This exists for scripts and tests, and it says what is missing
8
+ * instead of failing with an opaque module error.
8
9
  */
9
10
  import type { SqliteOpener } from './types';
10
11
  /** True when this runtime can provide `node:sqlite`. */
@@ -2,10 +2,11 @@
2
2
  /**
3
3
  * A read-only SQLite adapter backed by Node's built-in `node:sqlite`.
4
4
  *
5
- * ⚠️ `node:sqlite` landed in Node 22.5. Electron 31 ships Node 20, so the app CANNOT
6
- * use this one it passes an adapter over its own better-sqlite3. This exists for
7
- * scripts and tests, and it says so when the runtime cannot provide it instead of
8
- * failing with an opaque module error.
5
+ * ⚠️ `node:sqlite` landed in Node 22.5 behind `--experimental-sqlite` and was
6
+ * unflagged in 22.13.0 / 23.4.0, so the usable floor is 22.13. Electron 31 ships
7
+ * Node 20 and has none of it, which is why the app passes an adapter over its own
8
+ * better-sqlite3. This exists for scripts and tests, and it says what is missing
9
+ * instead of failing with an opaque module error.
9
10
  */
10
11
  Object.defineProperty(exports, "__esModule", { value: true });
11
12
  exports.openNodeSqlite = void 0;
@@ -37,8 +38,8 @@ const openNodeSqlite = (path) => {
37
38
  mod = (0, node_module_1.createRequire)(__filename)('node:sqlite');
38
39
  }
39
40
  catch (error) {
40
- throw new Error('node:sqlite is unavailable in this runtime (it needs Node >= 22.5; Electron 31 ships Node 20). ' +
41
- 'Pass your own SqliteOpener instead. Cause: ' +
41
+ throw new Error('node:sqlite is unavailable in this runtime (unflagged from Node 22.13 and 23.4; ' +
42
+ 'Electron 31 ships Node 20). Pass your own SqliteOpener instead. Cause: ' +
42
43
  (error instanceof Error ? error.message : String(error)));
43
44
  }
44
45
  const db = new mod.DatabaseSync(path);
package/dist/types.d.ts CHANGED
@@ -30,6 +30,12 @@ export interface WorkspaceRef {
30
30
  /** Absolute path to `storage.db`. */
31
31
  dbPath: string;
32
32
  layout: AffineSchema;
33
+ /**
34
+ * Which AFFiNE build this workspace belongs to: `AFFiNE` for a stable install,
35
+ * `AFFiNE-canary`, `AFFiNE-beta`, `AFFiNE-internal` for the other channels.
36
+ * Someone can have several installed at once, each with its own workspaces.
37
+ */
38
+ channel: string;
33
39
  }
34
40
  export interface AffineDoc {
35
41
  id: string;
package/package.json CHANGED
@@ -1,14 +1,13 @@
1
1
  {
2
2
  "name": "@mnemosyne_os/affine-reader",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Read a local AFFiNE workspace (SQLite + Yjs) and render its documents to Markdown. No BlockSuite at runtime, no native module, read-only.",
5
5
  "license": "MIT",
6
6
  "author": "Tony Trochet <tony@xpacegems.com> (https://xpacegems.com)",
7
7
  "homepage": "https://mnemosyne-os.io",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "git+https://github.com/Mnemosyne-OS/Mnemosyne-Neural-OS.git",
11
- "directory": "packages/affine-reader"
10
+ "url": "git+https://github.com/Mnemosyne-OS/Mnemosyne-Neural-OS.git"
12
11
  },
13
12
  "bugs": {
14
13
  "url": "https://github.com/Mnemosyne-OS/Mnemosyne-Neural-OS/issues"
@@ -39,7 +38,8 @@
39
38
  "dist",
40
39
  "README.md",
41
40
  "NOTICE.md",
42
- "LICENSE"
41
+ "LICENSE",
42
+ "LICENSE-AFFiNE"
43
43
  ],
44
44
  "sideEffects": false,
45
45
  "publishConfig": {