@lexho111/plainblog 0.7.3 → 0.7.4

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/Article.js CHANGED
@@ -1,93 +1,88 @@
1
- export default class Article {
2
- constructor(id, title, content, createdAt) {
3
- this.id = id;
4
- this.title = title;
5
- this.content = content;
6
- this.createdAt = new Date(createdAt).getTime();
7
- }
8
-
9
- static createNew(title, content, createdAt = new Date()) {
10
- const id = this.getNextID(); // Your existing ID generator
11
- //const createdAt = new Date();
12
- return new Article(id, title, content, createdAt);
13
- }
14
-
15
- static nextID = 100;
16
- static getNextID() {
17
- // TODO fix use nextID from BlogTreeSearch!
18
- this.nextID++;
19
- return Math.floor(Math.random() * 100000000);
20
- }
21
-
22
- getContent() {
23
- return this.content;
24
- }
25
-
26
- getContentVeryShort() {
27
- if (!this.content) return "";
28
- if (this.content.length < 50) return this.content;
29
- let contentShort = this.content.substring(0, 50);
30
- contentShort += "...";
31
- return contentShort;
32
- }
33
-
34
- getContentShort() {
35
- if (!this.content) return "";
36
- if (this.content.length < 400) return this.content;
37
- let contentShort = this.content.substring(0, 400);
38
- contentShort += "...";
39
- return contentShort;
40
- }
41
-
42
- /**
43
- * Returns a JSON-serializable representation of the article.
44
- * This method is automatically called by JSON.stringify().
45
- */
46
- toJSON() {
47
- // Return a plain object with the desired properties
48
- const date = !isNaN(this.createdAt)
49
- ? new Date(this.createdAt).toISOString()
50
- : new Date().toISOString();
51
- return {
52
- id: this.id,
53
- title: this.title,
54
- content: this.content,
55
- createdAt: date,
56
- };
57
- }
58
-
59
- /**
60
- * Returns a JSON-serializable representation of the article.
61
- * This method is automatically called by JSON.stringify().
62
- */
63
- json() {
64
- return this.toJSON();
65
- }
66
-
67
- getFormattedDate() {
68
- const date = new Date(this.createdAt);
69
- const year = date.getFullYear();
70
- const month = String(date.getMonth() + 1).padStart(2, "0");
71
- const day = String(date.getDate()).padStart(2, "0");
72
- const hours = String(date.getHours()).padStart(2, "0");
73
- const minutes = String(date.getMinutes()).padStart(2, "0");
74
- return `${year}/${month}/${day} ${hours}:${minutes}`;
75
- }
76
-
77
- toHTML(loggedin = false) {
78
- if (this.content == null) return "";
79
- const moreButton = this.content.length > 400 ? `<a href="#">more</a>` : "";
80
- const buttons = loggedin // prettier-ignore
81
- ? `<div class="buttons"><div id="editButton${this.id}" class="btn edit" role="button" tabindex="0" data-action="edit" data-id="${this.id}">edit</div><div id="deleteButton${this.id}" class="btn delete" role="button" tabindex="0" data-action="delete" data-id="${this.id}">delete</div><div id="somethingelseButton${this.id}" class="btn light">something else</div></div>`
82
- : "";
83
- // prettier-ignore
84
- return `<article data-id="${this.id}" data-date="${this.createdAt}">
1
+ export default class Article {
2
+ constructor(id, title, content, createdAt) {
3
+ this.id = id;
4
+ this.title = title;
5
+ this.content = content;
6
+ this.createdAt = new Date(createdAt).getTime();
7
+ }
8
+ static createNew(title, content, createdAt = new Date()) {
9
+ const id = this.getNextID(); // Your existing ID generator
10
+ //const createdAt = new Date();
11
+ return new Article(id, title, content, createdAt);
12
+ }
13
+ static getNextID() {
14
+ // Generiert eine kollisionssichere ID basierend auf Zeitstempel + Zufall
15
+ // Passt in JS Safe Integer und SQLite Integer (64-bit)
16
+ return Date.now() * 1000 + Math.floor(Math.random() * 1000);
17
+ }
18
+ getContent() {
19
+ return this.content;
20
+ }
21
+ getContentVeryShort() {
22
+ if (!this.content)
23
+ return "";
24
+ if (this.content.length < 50)
25
+ return this.content;
26
+ let contentShort = this.content.substring(0, 50);
27
+ contentShort += "...";
28
+ return contentShort;
29
+ }
30
+ getContentShort() {
31
+ if (!this.content)
32
+ return "";
33
+ if (this.content.length < 400)
34
+ return this.content;
35
+ let contentShort = this.content.substring(0, 400);
36
+ contentShort += "...";
37
+ return contentShort;
38
+ }
39
+ /**
40
+ * Returns a JSON-serializable representation of the article.
41
+ * This method is automatically called by JSON.stringify().
42
+ */
43
+ toJSON() {
44
+ // Return a plain object with the desired properties
45
+ const date = !isNaN(this.createdAt)
46
+ ? new Date(this.createdAt).toISOString()
47
+ : new Date().toISOString();
48
+ return {
49
+ id: this.id,
50
+ title: this.title,
51
+ content: this.content,
52
+ createdAt: date,
53
+ };
54
+ }
55
+ /**
56
+ * Returns a JSON-serializable representation of the article.
57
+ * This method is automatically called by JSON.stringify().
58
+ */
59
+ json() {
60
+ return this.toJSON();
61
+ }
62
+ getFormattedDate() {
63
+ const date = new Date(this.createdAt);
64
+ const year = date.getFullYear();
65
+ const month = String(date.getMonth() + 1).padStart(2, "0");
66
+ const day = String(date.getDate()).padStart(2, "0");
67
+ const hours = String(date.getHours()).padStart(2, "0");
68
+ const minutes = String(date.getMinutes()).padStart(2, "0");
69
+ return `${year}/${month}/${day} ${hours}:${minutes}`;
70
+ }
71
+ toHTML(loggedin = false) {
72
+ if (this.content == null)
73
+ return "";
74
+ const moreButton = this.content.length > 400 ? `<a href="#">more</a>` : "";
75
+ const buttons = loggedin // prettier-ignore
76
+ ? `<div class="buttons"><div id="editButton${this.id}" class="btn edit" role="button" tabindex="0" data-action="edit" data-id="${this.id}">edit</div><div id="deleteButton${this.id}" class="btn delete" role="button" tabindex="0" data-action="delete" data-id="${this.id}">delete</div><div id="somethingelseButton${this.id}" class="btn light">something else</div></div>`
77
+ : "";
78
+ // prettier-ignore
79
+ return `<article data-id="${this.id}" data-date="${this.createdAt}">
85
80
  ${buttons}
86
81
  <h2>${this.title}</h2>
87
82
  <span class="datetime">${this.getFormattedDate()}</span>
88
83
  <p>${this.getContentShort()}</p>
89
84
  <div class="full-content" style="display: none;">${this.content}</div>
90
85
  ${moreButton}
91
- </article>`;
92
- }
93
- }
86
+ </article>`;
87
+ }
88
+ }
package/Article.ts ADDED
@@ -0,0 +1,97 @@
1
+ export default class Article {
2
+ id: number;
3
+ title: string;
4
+ content: string;
5
+ createdAt: number;
6
+
7
+ constructor(id: number, title: string, content: string, createdAt: string | number | Date) {
8
+ this.id = id;
9
+ this.title = title;
10
+ this.content = content;
11
+ this.createdAt = new Date(createdAt).getTime();
12
+ }
13
+
14
+ static createNew(title: string, content: string, createdAt: string | number | Date = new Date()): Article {
15
+ const id = this.getNextID(); // Your existing ID generator
16
+ //const createdAt = new Date();
17
+ return new Article(id, title, content, createdAt);
18
+ }
19
+
20
+ static getNextID(): number {
21
+ // Generiert eine kollisionssichere ID basierend auf Zeitstempel + Zufall
22
+ // Passt in JS Safe Integer und SQLite Integer (64-bit)
23
+ return Date.now() * 1000 + Math.floor(Math.random() * 1000);
24
+ }
25
+
26
+ getContent(): string {
27
+ return this.content;
28
+ }
29
+
30
+ getContentVeryShort(): string {
31
+ if (!this.content) return "";
32
+ if (this.content.length < 50) return this.content;
33
+ let contentShort = this.content.substring(0, 50);
34
+ contentShort += "...";
35
+ return contentShort;
36
+ }
37
+
38
+ getContentShort(): string {
39
+ if (!this.content) return "";
40
+ if (this.content.length < 400) return this.content;
41
+ let contentShort = this.content.substring(0, 400);
42
+ contentShort += "...";
43
+ return contentShort;
44
+ }
45
+
46
+ /**
47
+ * Returns a JSON-serializable representation of the article.
48
+ * This method is automatically called by JSON.stringify().
49
+ */
50
+ toJSON(): { id: number; title: string; content: string; createdAt: string } {
51
+ // Return a plain object with the desired properties
52
+ const date = !isNaN(this.createdAt)
53
+ ? new Date(this.createdAt).toISOString()
54
+ : new Date().toISOString();
55
+ return {
56
+ id: this.id,
57
+ title: this.title,
58
+ content: this.content,
59
+ createdAt: date,
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Returns a JSON-serializable representation of the article.
65
+ * This method is automatically called by JSON.stringify().
66
+ */
67
+ json(): { id: number; title: string; content: string; createdAt: string } {
68
+ return this.toJSON();
69
+ }
70
+
71
+ getFormattedDate(): string {
72
+ const date = new Date(this.createdAt);
73
+ const year = date.getFullYear();
74
+ const month = String(date.getMonth() + 1).padStart(2, "0");
75
+ const day = String(date.getDate()).padStart(2, "0");
76
+ const hours = String(date.getHours()).padStart(2, "0");
77
+ const minutes = String(date.getMinutes()).padStart(2, "0");
78
+ return `${year}/${month}/${day} ${hours}:${minutes}`;
79
+ }
80
+
81
+ toHTML(loggedin = false): string {
82
+ if (this.content == null) return "";
83
+ const moreButton = this.content.length > 400 ? `<a href="#">more</a>` : "";
84
+ const buttons = loggedin // prettier-ignore
85
+ ? `<div class="buttons"><div id="editButton${this.id}" class="btn edit" role="button" tabindex="0" data-action="edit" data-id="${this.id}">edit</div><div id="deleteButton${this.id}" class="btn delete" role="button" tabindex="0" data-action="delete" data-id="${this.id}">delete</div><div id="somethingelseButton${this.id}" class="btn light">something else</div></div>`
86
+ : "";
87
+ // prettier-ignore
88
+ return `<article data-id="${this.id}" data-date="${this.createdAt}">
89
+ ${buttons}
90
+ <h2>${this.title}</h2>
91
+ <span class="datetime">${this.getFormattedDate()}</span>
92
+ <p>${this.getContentShort()}</p>
93
+ <div class="full-content" style="display: none;">${this.content}</div>
94
+ ${moreButton}
95
+ </article>`;
96
+ }
97
+ }
package/AssetManager.js CHANGED
@@ -4,6 +4,7 @@ import path from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import { Worker } from "node:worker_threads";
6
6
  import { createDebug } from "./debug-loader.js";
7
+ import { checkSourceCSS } from "./modules/csscompiler/csscompiler.js";
7
8
 
8
9
  const debug = createDebug("plainblog:AssetManager");
9
10
  const __filename = fileURLToPath(import.meta.url);
@@ -13,27 +14,30 @@ export default class AssetManager {
13
14
  constructor() {
14
15
  this.publicDir = path.join(process.cwd(), "public");
15
16
  this.reloadStylesOnGET = false;
16
- this.stylesheetPath = null;
17
+ this._stylesheetPath = null;
17
18
  this.compilestyle = false;
18
- this.styles = "";
19
- this.compiledStyles = "";
20
- this.stylesHash = "";
19
+ this._styles = "";
21
20
  this.scriptsHash = "";
22
21
  }
23
22
 
24
- setStyle(style) {
25
- this.styles += style;
23
+ set style(value) {
24
+ debug("set style %s", value);
25
+ this._styles += value;
26
26
  this.compilestyle = true;
27
27
  }
28
28
 
29
+ set stylesheetPath(value) {
30
+ debug(`set Stylesheet path: ${value}`);
31
+ this._stylesheetPath = value;
32
+ }
33
+
34
+ get stylesheetPath() {
35
+ return this._stylesheetPath;
36
+ }
37
+
29
38
  async init() {
30
- // if there is a stylesheet path provided, process it
31
- if (this.stylesheetPath != null && this.compilestyle) {
32
- await this.processStylesheets(this.stylesheetPath);
33
- }
34
- if (!this.stylesheetPath) {
35
- await this.processDefaultStyles();
36
- }
39
+ debug("init");
40
+ await this.processStyles();
37
41
 
38
42
  // Process Scripts
39
43
  const srcScriptPath = path.join(__dirname, "src", "fetchData.js");
@@ -42,112 +46,35 @@ export default class AssetManager {
42
46
 
43
47
  async reload() {
44
48
  debug("reload");
45
- if (this.stylesheetPath != null && this.compilestyle) {
46
- await this.processStylesheets(this.stylesheetPath);
47
- }
48
- if (!this.stylesheetPath) {
49
- await this.processDefaultStyles();
50
- }
49
+ await this.processStyles();
51
50
  const srcScriptPath = path.join(__dirname, "src", "fetchData.js");
52
51
  await this.processScripts(srcScriptPath);
53
52
  }
54
53
 
55
- async processDefaultStyles() {
56
- // compile and merge hardcoded styles in "this.styles" with "src/styles.css"
57
- const srcStylePath = path.join(__dirname, "src", "styles.css");
58
- const publicStylePath = path.join(this.publicDir, "styles.min.css");
59
-
60
- let publicHash = null;
61
- let srcStyles = "";
62
-
63
- await Promise.all([
64
- fs.promises
65
- .readFile(publicStylePath, "utf8")
66
- .then((publicCSS) => {
67
- const match = publicCSS.match(
68
- /\/\* source-hash: ([a-f0-9]{64}) \*\//,
69
- );
70
- if (match) publicHash = match[1];
71
- })
72
- .catch((err) => {
73
- if (err.code !== "ENOENT" && !err.message.includes("ENOENT"))
74
- console.error(err);
75
- }),
76
- fs.promises
77
- .readFile(srcStylePath, "utf8")
78
- .then((content) => {
79
- srcStyles = content;
80
- })
81
- .catch((err) => {
82
- if (err.code !== "ENOENT" && !err.message.includes("ENOENT"))
83
- console.error(err);
84
- }),
85
- ]);
86
-
87
- const combinedStyles = srcStyles + "\n" + this.styles;
88
- const srcHash = crypto
89
- .createHash("sha256")
90
- .update(combinedStyles)
91
- .digest("hex");
92
-
93
- if (srcHash !== publicHash) {
94
- debug("Styles have changed. Recompiling in worker...");
95
- const finalStyles = await this.runWorker("mergeStyles", [
96
- srcStyles,
97
- this.styles,
98
- ]);
99
-
100
- try {
101
- await fs.promises.mkdir(path.dirname(publicStylePath), {
102
- recursive: true,
103
- });
104
- await fs.promises.writeFile(
105
- publicStylePath,
106
- finalStyles + `\n/* source-hash: ${srcHash} */`,
54
+ async processStyles(compilerOptions = {}) {
55
+ let resolvedStylesheetPath = this.stylesheetPath;
56
+ if (resolvedStylesheetPath) {
57
+ if (Array.isArray(resolvedStylesheetPath)) {
58
+ resolvedStylesheetPath = resolvedStylesheetPath.map((p) =>
59
+ path.isAbsolute(p) ? p : path.resolve(process.cwd(), p),
107
60
  );
108
- } catch (err) {
109
- console.error("Failed to write styles to public folder:", err);
110
- }
111
- }
112
- }
113
-
114
- async processStylesheets(files) {
115
- debug("process stylesheets");
116
- const fileList = Array.isArray(files) ? files : [files];
117
- const styleFiles = fileList.filter(
118
- (f) =>
119
- typeof f === "string" &&
120
- (f.endsWith(".scss") || f.endsWith(".css")) &&
121
- !f.endsWith(".min.css"),
122
- );
123
-
124
- if (styleFiles.length > 0) {
125
- const fileData = await Promise.all(
126
- styleFiles.sort().map(async (f) => {
127
- const content = await fs.promises.readFile(f, "utf-8");
128
- if (content == "") throw new Error("Invalid Filepath or empty file!");
129
- return { path: f, content };
130
- }),
131
- );
132
-
133
- const hash = crypto.createHash("sha256");
134
- for (const file of fileData) hash.update(file.content);
135
- const currentHash = hash.digest("hex");
136
-
137
- if (currentHash !== this.stylesHash && this.compilestyle) {
138
- console.log("style assets have changed. recompiling in worker...");
139
- this.stylesHash = currentHash;
140
- this.compiledStyles = await this.runWorker("styles", fileData);
141
-
142
- await fs.promises.mkdir(this.publicDir, { recursive: true });
143
- await fs.promises.writeFile(
144
- path.join(this.publicDir, "styles.min.css"),
145
- this.compiledStyles + `\n/* source-hash: ${currentHash} */`,
61
+ } else if (
62
+ typeof resolvedStylesheetPath === "string" &&
63
+ !path.isAbsolute(resolvedStylesheetPath)
64
+ ) {
65
+ resolvedStylesheetPath = path.resolve(
66
+ process.cwd(),
67
+ resolvedStylesheetPath,
146
68
  );
147
- } else {
148
- console.log("styles are up-to-date");
149
69
  }
150
70
  }
71
+ await checkSourceCSS({
72
+ stylesheetPath: resolvedStylesheetPath,
73
+ style: this._styles,
74
+ outputDir: this.publicDir,
75
+ outputPath: path.join(this.publicDir, "styles.min.css"),
76
+ ...compilerOptions,
77
+ });
151
78
  }
152
79
 
153
80
  async processScripts(files) {
@@ -184,14 +111,28 @@ export default class AssetManager {
184
111
  }
185
112
 
186
113
  if (currentHash !== this.scriptsHash) {
187
- console.log("script assets have changed. recompiling in worker...");
188
- this.scriptsHash = currentHash;
189
- const compiledScripts = await this.runWorker("scripts", fileData);
114
+ console.log("script assets have changed. recompiling...");
115
+ let compiledScripts;
116
+ let success = false;
117
+ try {
118
+ compiledScripts = await this.runWorker("scripts", fileData);
119
+ success = true;
120
+ this.scriptsHash = currentHash;
121
+ } catch (e) {
122
+ console.warn(
123
+ "Script compilation failed (using fallback):",
124
+ e.message,
125
+ );
126
+ compiledScripts = fileData.map((f) => f.content).join(";\n");
127
+ }
190
128
 
191
129
  await fs.promises.mkdir(this.publicDir, { recursive: true });
130
+ const content = success
131
+ ? compiledScripts + `\n/* source-hash: ${currentHash} */`
132
+ : compiledScripts;
192
133
  await fs.promises.writeFile(
193
134
  path.join(this.publicDir, "scripts.min.js"),
194
- compiledScripts + `\n/* source-hash: ${currentHash} */`,
135
+ content,
195
136
  );
196
137
  }
197
138
  }
@@ -205,11 +146,17 @@ export default class AssetManager {
205
146
  : { type, fileData: data };
206
147
 
207
148
  const worker = new Worker(
208
- path.resolve(__dirname, "workers", "compiler-worker.js"),
149
+ path.resolve(
150
+ __dirname,
151
+ "modules",
152
+ "jscompiler",
153
+ "compile-js-worker.js",
154
+ ),
209
155
  { workerData },
210
156
  );
211
157
  worker.on("message", (msg) => {
212
158
  if (msg.status === "success") {
159
+ console.log("compilation complete: public/styles.min.css created");
213
160
  resolve(msg.result);
214
161
  } else {
215
162
  reject(new Error(msg.error));
package/Blog.js CHANGED
@@ -123,13 +123,13 @@ export default class Blog {
123
123
  * @param {string} style - A string containing CSS rules.
124
124
  */
125
125
  set style(style) {
126
- debug("set style");
127
- this.assetManager.styles = style;
126
+ debug("set style %s", style);
127
+ this.assetManager.style = style;
128
128
  this.assetManager.compilestyle = true;
129
129
  }
130
130
 
131
131
  get style() {
132
- return this.assetManager.styles;
132
+ return this.assetManager._styles;
133
133
  }
134
134
 
135
135
  /**
@@ -137,8 +137,8 @@ export default class Blog {
137
137
  * @param {string|string[]} files - A single file path or an array of file paths.
138
138
  */
139
139
  set stylesheetPath(files) {
140
+ debug(`set stylesheet path: ${files}`);
140
141
  this.assetManager.stylesheetPath = files;
141
- console.log(`this.assetManager.stylesheetPath: ${files}`);
142
142
  this.assetManager.compilestyle = true;
143
143
  }
144
144
 
@@ -160,11 +160,18 @@ export default class Blog {
160
160
 
161
161
  /** initializes database */
162
162
  async init() {
163
- if (this.#initPromise) return this.#initPromise;
163
+ //if (this.#initPromise) return this.#initPromise;
164
164
 
165
165
  this.#initPromise = (async () => {
166
166
  try {
167
- await this.assetManager.init();
167
+ try {
168
+ await this.assetManager.init();
169
+ } catch (e) {
170
+ console.warn(
171
+ "Warning: AssetManager initialization failed. Styles may not be compiled.",
172
+ e.message,
173
+ );
174
+ }
168
175
 
169
176
  if (this.#isExternalAPI) {
170
177
  console.log("external API");
package/cluster-server.js CHANGED
@@ -1,44 +1,49 @@
1
1
  import cluster from "cluster";
2
2
  import os from "os";
3
3
  import process from "node:process";
4
+ import { fileURLToPath } from "url";
4
5
  import Blog from "./Blog.js";
5
6
 
6
- const numCPUs = os.cpus().length;
7
+ export function startCluster(port = process.env.PORT || 8080) {
8
+ const numCPUs = os.cpus().length;
7
9
 
8
- if (cluster.isPrimary) {
9
- console.log(`master ${process.pid} is running`);
10
+ if (cluster.isPrimary) {
11
+ console.log(`master ${process.pid} is running`);
10
12
 
11
- // Fork workers (one per CPU core)
12
- for (let i = 0; i < numCPUs; i++) {
13
- cluster.fork();
14
- }
13
+ // Fork workers (one per CPU core)
14
+ for (let i = 0; i < numCPUs; i++) {
15
+ cluster.fork();
16
+ }
15
17
 
16
- cluster.on("exit", (worker, code, signal) => {
17
- console.log(
18
- `worker ${worker.process.pid} died (${signal || code}). restarting...`,
19
- );
20
- cluster.fork(); // Restart dead worker
21
- });
22
- } else {
23
- // Worker process
24
- const blog = new Blog();
18
+ cluster.on("exit", (worker, code, signal) => {
19
+ console.log(
20
+ `worker ${worker.process.pid} died (${signal || code}). restarting...`,
21
+ );
22
+ cluster.fork(); // Restart dead worker
23
+ });
24
+ } else {
25
+ // Worker process
26
+ const blog = new Blog();
25
27
 
26
- const port = process.env.PORT || 8080;
28
+ blog
29
+ .startServer(port)
30
+ .then(() => {
31
+ console.log(`worker ${process.pid} started on port ${port}`);
32
+ })
33
+ .catch((err) => {
34
+ console.error(`Worker ${process.pid} failed to start:`, err);
35
+ process.exit(1);
36
+ });
27
37
 
28
- blog
29
- .startServer(port)
30
- .then(() => {
31
- console.log(`worker ${process.pid} started on port ${port}`);
32
- })
33
- .catch((err) => {
34
- console.error(`Worker ${process.pid} failed to start:`, err);
35
- process.exit(1);
38
+ // Graceful shutdown
39
+ process.on("SIGTERM", async () => {
40
+ console.log(`worker ${process.pid} shutting down...`);
41
+ await blog.closeServer();
42
+ process.exit(0);
36
43
  });
44
+ }
45
+ }
37
46
 
38
- // Graceful shutdown
39
- process.on("SIGTERM", async () => {
40
- console.log(`worker ${process.pid} shutting down...`);
41
- await blog.closeServer();
42
- process.exit(0);
43
- });
47
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
48
+ startCluster();
44
49
  }
package/index.js CHANGED
@@ -4,6 +4,7 @@ import FileAdapter from "./model/FileAdapter.js";
4
4
  import PostgresAdapter from "./model/PostgresAdapter.js";
5
5
  import SequelizeAdapter from "./model/SequelizeAdapter.js";
6
6
  import SqliteAdapter from "./model/SqliteAdapter.js";
7
+ import { startCluster } from "./cluster-server.js";
7
8
 
8
9
  export {
9
10
  Blog,
@@ -12,5 +13,6 @@ export {
12
13
  SequelizeAdapter,
13
14
  FileAdapter,
14
15
  SqliteAdapter,
16
+ startCluster,
15
17
  };
16
18
  export default Blog;
@@ -32,7 +32,7 @@ export default class SqliteAdapter {
32
32
  console.error(
33
33
  "The 'better-sqlite3' package is not installed. Please install it by running: npm install better-sqlite3",
34
34
  );
35
- throw new Error("Missing optional dependency: 'better-sqlite3'");
35
+ process.exit(1);
36
36
  } else {
37
37
  throw error;
38
38
  }