@jpmorganchase/mosaic-core 0.1.0-beta.3

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.
@@ -0,0 +1,57 @@
1
+ import type IFileAccess from '@jpmorganchase/mosaic-types/dist/IFileAccess';
2
+ import type { IVolumePartiallyMutable } from '@jpmorganchase/mosaic-types/dist/Volume';
3
+
4
+ import ImmutableFileSystem from './ImmutableVolume';
5
+ import { create } from 'lodash';
6
+
7
+ /**
8
+ * Restricted filesystems can be written to, but cannot be frozen, updated or reset
9
+ */
10
+ class RestrictedVolume extends ImmutableFileSystem implements IVolumePartiallyMutable {
11
+ #vfs: IFileAccess;
12
+
13
+ constructor(vfs, namespace) {
14
+ super(vfs, namespace);
15
+ this.#vfs = vfs;
16
+ }
17
+
18
+ promises = create(this.promises, {
19
+ unlink: target => {
20
+ return this.#vfs.unlink(target);
21
+ },
22
+ symlink: (target, alias, type) => {
23
+ return this.#vfs.symlink(target, alias, type);
24
+ },
25
+ writeFile: (file, data) => {
26
+ return this.#vfs.writeFile(file, data);
27
+ },
28
+ mkdir: (dir, options) => {
29
+ return this.#vfs.mkdir(dir, options);
30
+ }
31
+ });
32
+
33
+ /**
34
+ * Do not use this method on restricted volumes, as it may disrupt the read/cache flow of files.
35
+ */
36
+ __internal_do_not_use_addReadFileHook(readFileHook) {
37
+ return this.#vfs.$$addReadFileHook(readFileHook);
38
+ }
39
+
40
+ symlinksToJSON(): { [key: string]: { target: string; type: string }[] } {
41
+ return this.#vfs.symlinksToJSON();
42
+ }
43
+
44
+ toJSON() {
45
+ return this.#vfs.toJSON();
46
+ }
47
+
48
+ get frozen() {
49
+ return this.#vfs.$$frozen;
50
+ }
51
+
52
+ asReadOnly() {
53
+ return new ImmutableFileSystem(this.#vfs, super.namespace);
54
+ }
55
+ }
56
+
57
+ export default RestrictedVolume;
@@ -0,0 +1,77 @@
1
+ import { IUnionFs, Union } from 'unionfs';
2
+ import merge from 'lodash/merge';
3
+
4
+ import type { IVolumeMutable } from '@jpmorganchase/mosaic-types/dist/Volume';
5
+
6
+ import FileAccess from './FileAccess';
7
+ import { IFS } from 'unionfs/lib/fs';
8
+
9
+ export default class UnionFileAccess extends FileAccess {
10
+ #ufs: IUnionFs & { fss: IVolumeMutable[] };
11
+
12
+ constructor(ufs) {
13
+ super(ufs);
14
+ this.#ufs = ufs;
15
+ }
16
+ // We have to override this here, so we can hand-hold the glob and make sure it fires on all filesystems
17
+ async glob(pattern, options) {
18
+ return this.#ufs.fss.reduce(
19
+ async (globResults, childFs) => [
20
+ ...(await globResults),
21
+ ...(await childFs.promises.glob(pattern, options))
22
+ ],
23
+ Promise.resolve([])
24
+ );
25
+ }
26
+ /**
27
+ * Scopes a filesystem to specific namespaces
28
+ * @param {string[]} namespaces Array of namespaces to limit this filesystem to
29
+ * @returns A new immutable union filesystem
30
+ */
31
+ scope(namespaces: string[]) {
32
+ const scopedUnion: IUnionFs & { fss: IVolumeMutable[] } = new Union() as IUnionFs & {
33
+ fss: IVolumeMutable[];
34
+ };
35
+ const matchingNamespaceFSs = this.#ufs.fss.filter(fss => namespaces.includes(fss.namespace));
36
+
37
+ if (!matchingNamespaceFSs.length) {
38
+ throw new Error(
39
+ `Scoping failed. No matching filesystems for ${namespaces
40
+ .map(namespace => `"${namespace}"`)
41
+ .join(', ')}.`
42
+ );
43
+ }
44
+ matchingNamespaceFSs.forEach(fss => scopedUnion.use(fss as unknown as IFS));
45
+ return new UnionFileAccess(scopedUnion);
46
+ }
47
+ /**
48
+ * Reads 1 or more files (if multiple files exist at the same location within the union filesystem)
49
+ * @param file Path
50
+ * @param options.includeConflicts If multiple files exist at the same location in the filesystem, return them all as an array
51
+ * @returns Promise<TDataOut[]>
52
+ */
53
+ readFile(file, options?: { includeConflicts?: boolean }) {
54
+ if (!options?.includeConflicts) {
55
+ return super.readFile(file);
56
+ }
57
+ return this.#ufs.fss.reduce(
58
+ async (fileResults, childFs) => [
59
+ ...(await fileResults),
60
+ ...((await childFs.promises.exists(file)) ? [await childFs.promises.readFile(file)] : [])
61
+ ],
62
+ Promise.resolve([])
63
+ );
64
+ }
65
+ symlinksToJSON() {
66
+ return this.#ufs.fss.reduce(
67
+ (merged, filesystem) => merge(merged, filesystem.symlinksToJSON()),
68
+ {}
69
+ );
70
+ }
71
+ toJSON() {
72
+ return this.#ufs.fss.reduce(
73
+ (merged, filesystem) => ({ ...merged, ...filesystem.toJSON() }),
74
+ {}
75
+ );
76
+ }
77
+ }
@@ -0,0 +1,28 @@
1
+ import type { IUnionVolume } from '@jpmorganchase/mosaic-types/dist/Volume';
2
+
3
+ import type UnionFileAccess from './UnionFileAccess';
4
+ import { create } from 'lodash';
5
+ import { TDataOut } from 'memfs/lib/encoding';
6
+ import MutableVolume from './MutableVolume';
7
+
8
+ export default class UnionVolume extends MutableVolume implements IUnionVolume {
9
+ #vfs: UnionFileAccess;
10
+
11
+ constructor(vfs, namespace) {
12
+ super(vfs, namespace);
13
+ this.#vfs = vfs;
14
+ }
15
+
16
+ promises = create(this.promises, {
17
+ readFile: (file, options) => {
18
+ if (options?.includeConflicts) {
19
+ return this.#vfs.readFile(file, options) as Promise<TDataOut[]>;
20
+ }
21
+ return this.#vfs.readFile(file) as Promise<TDataOut>;
22
+ }
23
+ });
24
+
25
+ scope(namespaces: string[]) {
26
+ return new UnionVolume(this.#vfs.scope(namespaces), super.namespace);
27
+ }
28
+ }
@@ -0,0 +1,52 @@
1
+ import type MutableData from '@jpmorganchase/mosaic-types/dist/MutableData';
2
+ import { ImmutableData } from '@jpmorganchase/mosaic-types/dist/MutableData';
3
+ import { merge } from 'lodash';
4
+ import path from 'path';
5
+
6
+ export default function createConfig<T = {}>(initialData: Partial<T> = {}): MutableData<T> {
7
+ let data: { refs?: {}; globalRefs?: {}; aliases?: {} } = { refs: {}, globalRefs: {}, aliases: {}, ...initialData };
8
+
9
+ const configReadOnly = {
10
+ get data() {
11
+ return Object.freeze({ ...data });
12
+ }
13
+ } as ImmutableData<T>;
14
+
15
+ return {
16
+ asReadOnly() {
17
+ return configReadOnly;
18
+ },
19
+ setTags(fullPath: string, tags: string[]) {
20
+ data.aliases[fullPath] = new Set<string>(data.aliases[fullPath] || []);
21
+ tags.forEach(tag => data.aliases[fullPath].add(path.join('/.tags', tag, fullPath)));
22
+ },
23
+ setGlobalRef(targetPath, targetPropPath, refValue) {
24
+ data.globalRefs[targetPath] = data.globalRefs[targetPath] || [];
25
+ data.globalRefs[targetPath].push({
26
+ $$path: targetPropPath,
27
+ $$value: refValue
28
+ });
29
+ },
30
+ setRef(targetPath, targetPropPath, refValue) {
31
+ data.refs[targetPath] = data.refs[targetPath] || [];
32
+ data.refs[targetPath].push({
33
+ $$path: targetPropPath,
34
+ $$value: refValue
35
+ });
36
+ },
37
+ setAliases(fullPath: string, aliases: string[]) {
38
+ data.aliases[fullPath] = new Set<string>(data.aliases[fullPath] || []);
39
+ aliases.forEach(alias => data.aliases[fullPath].add(alias));
40
+ },
41
+ setData(value, overwrite = false) {
42
+ if (overwrite) {
43
+ data = value;
44
+ } else {
45
+ merge(data, value);
46
+ }
47
+ },
48
+ get data() {
49
+ return configReadOnly.data;
50
+ }
51
+ };
52
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { default } from './PullDocs';
@@ -0,0 +1,24 @@
1
+ import { Worker } from 'worker_threads';
2
+
3
+ import { Observable } from 'rxjs';
4
+
5
+ import type WorkerData from '@jpmorganchase/mosaic-types/dist/WorkerData';
6
+
7
+ export function from<T>(workerData: WorkerData) {
8
+ return new Observable<T>(observer => {
9
+ const worker = new Worker(require.resolve('../worker/Source.worker'), { workerData });
10
+ worker.on('message', message => observer.next(message));
11
+ worker.once('error', error => observer.error(error));
12
+ worker.once('exit', code => {
13
+ if (code !== 0) {
14
+ observer.error(new Error(`mosaic source stopped with exit code ${code}`));
15
+ } else {
16
+ observer.complete();
17
+ }
18
+ });
19
+
20
+ return () => {
21
+ worker.terminate();
22
+ };
23
+ });
24
+ }
@@ -0,0 +1,75 @@
1
+ import type { LoadedPlugin } from '@jpmorganchase/mosaic-types/dist/Plugin';
2
+ import type { LoadedSerialiser } from '@jpmorganchase/mosaic-types/dist/Serialiser';
3
+ import type Plugin from '@jpmorganchase/mosaic-types/dist/Plugin';
4
+ import type PluginModuleDefinition from '@jpmorganchase/mosaic-types/dist/PluginModuleDefinition';
5
+ import type Serialiser from '@jpmorganchase/mosaic-types/dist/Serialiser';
6
+ import type Page from '@jpmorganchase/mosaic-types/dist/Page';
7
+
8
+ import loadDefinitionModules from './loadDefinitionModules';
9
+ import serialiserRunner from './serialiserRunner';
10
+ import pluginRunner from './pluginRunner';
11
+
12
+ function createProxyBaseSerialiserAPI(): Serialiser {
13
+ return {
14
+ serialise() {
15
+ throw new Error('This is just for the interface on the Proxy and should never be invoked.');
16
+ },
17
+ deserialise() {
18
+ throw new Error('This is just for the interface on the Proxy and should never be invoked.');
19
+ }
20
+ };
21
+ }
22
+
23
+ function createProxyBaseAPI<ConfigData>(): Plugin<ConfigData> {
24
+ return {
25
+ $afterSource() {
26
+ throw new Error('This is just for the interface on the Proxy and should never be invoked.');
27
+ },
28
+ shouldClearCache() {
29
+ throw new Error('This is just for the interface on the Proxy and should never be invoked.');
30
+ },
31
+ afterUpdate() {
32
+ throw new Error('This is just for the interface on the Proxy and should never be invoked.');
33
+ },
34
+ $beforeSend() {
35
+ throw new Error('This is just for the interface on the Proxy and should never be invoked.');
36
+ }
37
+ };
38
+ }
39
+
40
+ export default async function createPluginAPI<PluginInput, ConfigData = {}>(
41
+ plugins: PluginModuleDefinition[]
42
+ ): Promise<Plugin<ConfigData>> {
43
+ const loadedPlugins: LoadedPlugin[] | LoadedSerialiser[] = await loadDefinitionModules(plugins);
44
+ const baseObj = createProxyBaseAPI<ConfigData>();
45
+ const baseSerialiserObj = createProxyBaseSerialiserAPI();
46
+
47
+ return new Proxy(baseObj, {
48
+ get(obj, propOrLifecycleName) {
49
+ if (baseSerialiserObj.hasOwnProperty(propOrLifecycleName)) {
50
+ return async (pagePath, ...args: [string | Page, {}]) =>
51
+ await serialiserRunner(
52
+ {
53
+ serialiserMethod: String(propOrLifecycleName),
54
+ loadedPlugins: loadedPlugins as LoadedSerialiser[]
55
+ },
56
+ pagePath,
57
+ ...args
58
+ );
59
+ }
60
+ if (baseObj.hasOwnProperty(propOrLifecycleName)) {
61
+ return async (...args: [PluginInput, {}]) =>
62
+ await pluginRunner(
63
+ {
64
+ loadedPlugins: loadedPlugins as LoadedPlugin[],
65
+ lifecycleName: String(propOrLifecycleName)
66
+ },
67
+ ...args
68
+ );
69
+ }
70
+ if (typeof obj[propOrLifecycleName] !== 'undefined') {
71
+ return obj[propOrLifecycleName];
72
+ }
73
+ }
74
+ });
75
+ }
@@ -0,0 +1,41 @@
1
+ import type { TDataOut } from 'memfs/lib/encoding';
2
+
3
+ import type Page from '@jpmorganchase/mosaic-types/dist/Page';
4
+ import type Serialiser from '@jpmorganchase/mosaic-types/dist/Serialiser';
5
+ import type Plugin from '@jpmorganchase/mosaic-types/dist/Plugin';
6
+ import type { IVolumeMutable } from '@jpmorganchase/mosaic-types/dist/Volume';
7
+ import type PluginModuleDefinition from '@jpmorganchase/mosaic-types/dist/PluginModuleDefinition';
8
+
9
+ import loadPlugins from './createPluginAPI';
10
+
11
+ export async function bindSerialiser(serialisers): Promise<Serialiser> {
12
+ const pluginApi = (await loadPlugins<Page>(serialisers)) as Serialiser;
13
+
14
+ return {
15
+ async serialise(fullPath, page) {
16
+ return await pluginApi.serialise(fullPath, page);
17
+ },
18
+ async deserialise(fullPath, data) {
19
+ return await pluginApi.deserialise(fullPath, data);
20
+ }
21
+ };
22
+ }
23
+
24
+ export async function bindPluginMethods(plugins: PluginModuleDefinition[]): Promise<Plugin> {
25
+ const pluginApi = await loadPlugins<Page[] | TDataOut | IVolumeMutable>(plugins);
26
+
27
+ return {
28
+ async $afterSource(pages, args) {
29
+ return await pluginApi.$afterSource(pages, args);
30
+ },
31
+ async shouldClearCache(lastAfterUpdateReturn, args) {
32
+ return await pluginApi.shouldClearCache(lastAfterUpdateReturn, args);
33
+ },
34
+ async afterUpdate(mutableFilesystem, args) {
35
+ return await pluginApi.afterUpdate(mutableFilesystem, args);
36
+ },
37
+ async $beforeSend(mutableFilesystem, args) {
38
+ await pluginApi.$beforeSend(mutableFilesystem, args);
39
+ }
40
+ };
41
+ }
@@ -0,0 +1,58 @@
1
+ import type PluginModuleDefinition from '@jpmorganchase/mosaic-types/dist/PluginModuleDefinition';
2
+ import type SerialiserModuleDefinition from '@jpmorganchase/mosaic-types/dist/SerialiserModuleDefinition';
3
+ import type Plugin from '@jpmorganchase/mosaic-types/dist/Plugin';
4
+ import type { LoadedSerialiser, Serialiser } from '@jpmorganchase/mosaic-types/dist/Serialiser';
5
+ import type { LoadedPlugin } from '@jpmorganchase/mosaic-types/dist/Plugin';
6
+
7
+ const loadedPluginsAndSerialisers: { [key: string]: LoadedPlugin | LoadedSerialiser } = {};
8
+
9
+ export default async function loadDefinitionModules(
10
+ plugins: (PluginModuleDefinition | SerialiserModuleDefinition)[]
11
+ ): Promise<LoadedPlugin[] | LoadedSerialiser[]> {
12
+ const results = [];
13
+
14
+ try {
15
+ for (const plugin of plugins) {
16
+ const { modulePath }: { modulePath: string } = plugin;
17
+ if (!loadedPluginsAndSerialisers[modulePath]) {
18
+ const {
19
+ default: definitionExports
20
+ }: {
21
+ default: Partial<Plugin | Serialiser> | { __esModule: boolean; default: Partial<Plugin | Serialiser> };
22
+ } = await import(modulePath);
23
+ const pluginApi: Partial<Plugin | Serialiser> =
24
+ '__esModule' in definitionExports && 'default' in definitionExports
25
+ ? definitionExports.default
26
+ : definitionExports;
27
+ if (!pluginApi) {
28
+ throw new Error(`Plugin or serialiser '${modulePath}' did not have a default export.`);
29
+ }
30
+
31
+ loadedPluginsAndSerialisers[modulePath] = createInvoker(plugin, pluginApi);
32
+ }
33
+ results.push(loadedPluginsAndSerialisers[modulePath]);
34
+ }
35
+ return results;
36
+ } catch (e) {
37
+ throw new Error(`Could not load plugin/serialiser modules due to error.
38
+ ${e}`);
39
+ }
40
+ }
41
+
42
+ function createInvoker(plugin, api) {
43
+ return new Proxy(plugin, {
44
+ get(obj, prop) {
45
+ if (typeof obj[prop] !== 'undefined') {
46
+ return obj[prop];
47
+ }
48
+
49
+ if (typeof api === 'function') {
50
+ return api;
51
+ }
52
+
53
+ if (prop in api) {
54
+ return api[prop];
55
+ }
56
+ }
57
+ });
58
+ }
@@ -0,0 +1,52 @@
1
+ import type { LoadedPlugin } from '@jpmorganchase/mosaic-types/dist/Plugin';
2
+
3
+ import PluginError from '../PluginError';
4
+
5
+ export default async function pluginRunner(
6
+ { loadedPlugins, lifecycleName }: { loadedPlugins: LoadedPlugin[]; lifecycleName: string },
7
+ input,
8
+ ...args
9
+ ) {
10
+ let transformedInput = input;
11
+
12
+ for (const plugin of loadedPlugins) {
13
+ try {
14
+ if (typeof input === 'undefined') {
15
+ throw new Error('Input was undefined.');
16
+ }
17
+
18
+ if (typeof plugin[lifecycleName] !== 'function') {
19
+ continue;
20
+ }
21
+
22
+ // console.debug(
23
+ // `[PullDocs] Applying plugin method \`${lifecycleName}\`${lifecycleName.startsWith('$') ? ' (in a child worker)' : ''} for '${plugin.modulePath}'.`
24
+ // );
25
+
26
+ const result = await plugin[lifecycleName](
27
+ lifecycleName === '$afterSource' ? transformedInput : input,
28
+ ...args,
29
+ plugin.options
30
+ );
31
+
32
+ if (result && lifecycleName !== '$afterSource' && lifecycleName !== 'shouldClearCache') {
33
+ console.warn(
34
+ `[PullDocs] \`${lifecycleName}\` plugin should not return a value - this lifecycle phase expects mutation to occur directly on the filesystem instance. This will be ignored.`
35
+ );
36
+ }
37
+
38
+ transformedInput = result;
39
+ } catch (e) {
40
+ // This check will stop nested errors from ending up with multiple 'Plugin X threw an exception' headers from
41
+ // being prefixed to the messages
42
+ if (e instanceof PluginError) {
43
+ throw e;
44
+ }
45
+ throw new PluginError(
46
+ `Plugin '${plugin.modulePath}' threw an exception running \`${lifecycleName}\`. See below:
47
+ ${e.stack}`
48
+ );
49
+ }
50
+ }
51
+ return transformedInput;
52
+ }
@@ -0,0 +1,36 @@
1
+ import type { LoadedSerialiser } from '@jpmorganchase/mosaic-types/dist/Serialiser';
2
+
3
+ import PluginError from '../PluginError';
4
+
5
+ export default async function serialiserRunner(
6
+ { loadedPlugins, serialiserMethod }: { serialiserMethod: string; loadedPlugins: LoadedSerialiser[] },
7
+ fullPath: string,
8
+ ...args
9
+ ) {
10
+ if (typeof fullPath !== 'string' || !/\/[^/]+\.\w{1,4}$/.test(fullPath)) {
11
+ throw new Error(`Invalid serialiser input. '${fullPath}' provided but a path string (with a valid file extension) was expected.`);
12
+ }
13
+ for (const serialiser of loadedPlugins) {
14
+ try {
15
+ if (!serialiser.filter.test(String(fullPath))) {
16
+ // Serialiser failed the path filter test
17
+ continue;
18
+ }
19
+ const result = await serialiser[serialiserMethod](fullPath, ...args, serialiser.options);
20
+ return result;
21
+ } catch (e) {
22
+ // This check will stop nested errors from ending up with multiple 'Plugin X threw an exception' headers from
23
+ // being prefixed to the messages
24
+ if (e instanceof PluginError) {
25
+ throw e;
26
+ }
27
+ throw new PluginError(
28
+ `Serialiser '${serialiser.modulePath}' threw an exception calling \`serialiser.${serialiserMethod}\` on '${fullPath}'.
29
+
30
+ Is this the correct serialiser to use for this file type? See error below:
31
+ ${e.stack}`
32
+ );
33
+ }
34
+ }
35
+ throw new Error(`Could not find a suitable serialiser for file '${fullPath}'.`);
36
+ }
@@ -0,0 +1,124 @@
1
+ import { parentPort, isMainThread, workerData as unTypedWorkerData } from 'worker_threads';
2
+
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+ import { map, tap, switchMap } from 'rxjs';
6
+ import { DirectoryJSON, Volume } from 'memfs';
7
+
8
+ import type WorkerData from '@jpmorganchase/mosaic-types/dist/WorkerData';
9
+ import type Page from '@jpmorganchase/mosaic-types/dist/Page';
10
+
11
+ import createSourceObservable from './helpers/createSourceObservable';
12
+ import { bindSerialiser, bindPluginMethods } from '../plugin';
13
+ import createConfig from '../helpers/createConfig';
14
+ import FileAccess from '../filesystems/FileAccess';
15
+ import MutableVolume from '../filesystems/MutableVolume';
16
+
17
+ const workerData: WorkerData<{ cache: boolean }> = unTypedWorkerData;
18
+
19
+ if (isMainThread) {
20
+ throw new Error('This module can only be called from a child process.');
21
+ }
22
+ (async () => {
23
+ let config;
24
+ let filesystem;
25
+ const serialiser = await bindSerialiser(workerData.serialisers);
26
+ const pluginApi = await bindPluginMethods(workerData.plugins);
27
+ const cachePath = path.join(process.cwd(), '.tmp', '.cache', `${workerData.name}.json`);
28
+
29
+ if (workerData.options.cache !== false) {
30
+ await fs.promises.mkdir(path.dirname(cachePath), { recursive: true });
31
+ }
32
+
33
+ (await createSourceObservable(workerData, serialiser))
34
+ .pipe(
35
+ tap(() => {
36
+ config = createConfig();
37
+ }),
38
+ switchMap((pages: Page[]) =>
39
+ pluginApi.$afterSource(pages, {
40
+ config,
41
+ serialiser,
42
+ ignorePages: workerData.ignorePages,
43
+ pageExtensions: workerData.pageExtensions
44
+ })
45
+ ),
46
+ switchMap((pages: Page[]) =>
47
+ pages.reduce(async (mergedPagesPromise, page) => {
48
+ const mergedPages = await mergedPagesPromise;
49
+ mergedPages[page.fullPath] = String(await serialiser.serialise(page.fullPath, page));
50
+ return mergedPages;
51
+ }, Promise.resolve({}))
52
+ ),
53
+ switchMap(async (mergedPages: DirectoryJSON) => {
54
+ filesystem = new MutableVolume(
55
+ new FileAccess(Volume.fromJSON(mergedPages)),
56
+ workerData.namespace
57
+ );
58
+ await pluginApi.$beforeSend(filesystem.asRestricted(), {
59
+ config,
60
+ ignorePages: workerData.ignorePages,
61
+ pageExtensions: workerData.pageExtensions,
62
+ serialiser
63
+ });
64
+ // In the main thread we would freeze the filesystem here, but since we throw it away after sending it to the parent process,
65
+ // we don't bother freezing
66
+ // Turn data into buffer
67
+ return Buffer.from(JSON.stringify({ pages: filesystem.toJSON(), data: config.data, symlinks: filesystem.symlinksToJSON() }));
68
+ }),
69
+ tap(() => {
70
+ // Reset filesystem and config memory
71
+ config = null;
72
+ filesystem.reset();
73
+ filesystem = null;
74
+ })
75
+ )
76
+ .subscribe(async (pagesAndSymlinks: Buffer) => {
77
+ if (workerData.options.cache !== false) {
78
+ console.info(`[PullDocs] Saving cached filesystem of ${workerData.name}`);
79
+ await fs.promises.writeFile(
80
+ cachePath,
81
+ pagesAndSymlinks
82
+ );
83
+ }
84
+
85
+ parentPort.postMessage({
86
+ type: 'message',
87
+ data: pagesAndSymlinks
88
+ }, /* transferList */ [pagesAndSymlinks.buffer]);
89
+ });
90
+
91
+ if (workerData.options.cache !== false) {
92
+ try {
93
+ if (await fs.promises.stat(cachePath)) {
94
+ const data = await fs.promises.readFile(cachePath);
95
+ console.info(`Restoring cached filesystem for ${workerData.name}`);
96
+ parentPort.postMessage({
97
+ type: 'init',
98
+ data
99
+ }, /* transferList */ [data.buffer]);
100
+ }
101
+ // Important: Return to avoid sending another init signal on L107
102
+ return;
103
+ } catch (e) {
104
+ // Does not exist
105
+ }
106
+ }
107
+ parentPort.postMessage({ type: 'init' });
108
+ })();
109
+
110
+ // Catch any unhandled errors and re-throw them
111
+ process.once('uncaughtException', e => {
112
+ throw e;
113
+ });
114
+
115
+ // async function getPagesWithoutContent(pages: DirectoryJSON, serialiser: Serialiser): Promise<DirectoryJSON> {
116
+ // const pagesWithoutContent = {};
117
+ // for (const fullPath in pages) {
118
+ // const serialisedPage = pages[fullPath];
119
+ // const page = await serialiser.deserialise(fullPath, serialisedPage);
120
+ // page.content = '';
121
+ // pagesWithoutContent[fullPath] = String(await serialiser.serialise(fullPath, page));
122
+ // }
123
+ // return pagesWithoutContent;
124
+ // }