hdoc-tools 0.60.1 → 0.62.0
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 +21 -21
- package/README.md +89 -75
- package/hdoc-build-db.js +275 -275
- package/hdoc-build-embeddings.js +202 -202
- package/hdoc-build-pdf.js +232 -232
- package/hdoc-build.js +14 -6
- package/hdoc-bump.js +4 -2
- package/hdoc-content-routes.js +143 -83
- package/hdoc-create.js +110 -108
- package/hdoc-db.js +114 -114
- package/hdoc-help.js +60 -60
- package/hdoc-init.js +103 -68
- package/hdoc-install-browser.js +145 -145
- package/hdoc-mermaid.js +204 -204
- package/hdoc-module.js +1102 -1079
- package/hdoc-serve.js +13 -7
- package/hdoc-stats.js +9 -9
- package/hdoc-validate-config.js +355 -329
- package/hdoc-validate-interbook.js +321 -0
- package/hdoc-validate.js +1231 -1158
- package/hdoc-ver.js +4 -2
- package/hdoc.js +12 -11
- package/npm-shrinkwrap.json +2 -2
- package/package.json +13 -2
- package/schemas/hdocbook-project.schema.json +20 -0
- package/schemas/hdocbook.schema.json +6 -2
- package/templates/doc-header-non-git.html +19 -19
- package/templates/doc-header.html +26 -26
- package/templates/init/.github/workflows/hdocbuild_onpull.yml +16 -16
- package/templates/init/.github/workflows/hdocbuild_onpush.yml +15 -15
- package/templates/init/LICENSE +21 -21
- package/templates/init/README.md +9 -9
- package/templates/init/_hdocbook/index.md +4 -4
- package/templates/init/gitignore +8 -8
- package/templates/init/resources/README.md +2 -2
- package/templates/pdf/css/custom-block.css +90 -90
- package/templates/pdf/css/fonts.css +221 -221
- package/templates/pdf/css/hdocs-pdf.css +495 -495
- package/templates/pdf/css/vars.css +404 -404
- package/templates/pdf/template-footer.html +19 -19
- package/templates/pdf/template-header.html +37 -37
- package/templates/pdf/template.html +20 -20
- package/templates/pdf-header-non-git.html +12 -12
- package/templates/pdf-header.html +16 -16
- package/ui/content/invalid-hdocbook-json.html +6 -6
- package/ui/content/invalid-hdocbook-json.md +7 -7
- package/ui/css/theme-default/styles/components/content.css +124 -124
- package/ui/css/theme-default/styles/components/sidebar.css +182 -182
- package/ui/css/theme-default/styles/htldoc.layouts.css +310 -310
- package/ui/index.html +419 -419
- package/ui/js/doc.hornbill.js +31 -44
- package/ui/js/mermaid-theme.json +27 -0
- package/hdoc-build-onyx.js +0 -134
- package/templates/mermaid-theme.yaml +0 -28
- package/templates/pdf/fonts/inter-cyrillic copy.woff2 +0 -0
package/hdoc-build-db.js
CHANGED
|
@@ -1,275 +1,275 @@
|
|
|
1
|
-
(() => {
|
|
2
|
-
const path = require("node:path");
|
|
3
|
-
const hdoc_index = require(path.join(__dirname, "hdoc-db.js"));
|
|
4
|
-
const database = require("better-sqlite3");
|
|
5
|
-
|
|
6
|
-
const db_schema = {
|
|
7
|
-
hdoc_index: [
|
|
8
|
-
"resource_url UNINDEXED",
|
|
9
|
-
"book_id",
|
|
10
|
-
"book_audience UNINDEXED",
|
|
11
|
-
"book_tags",
|
|
12
|
-
"doc_title",
|
|
13
|
-
"doc_content",
|
|
14
|
-
"doc_preview UNINDEXED",
|
|
15
|
-
"doc_family_id",
|
|
16
|
-
"doc_md5_hash UNINDEXED",
|
|
17
|
-
"doc_lastmod",
|
|
18
|
-
"doc_status",
|
|
19
|
-
"doc_keywords",
|
|
20
|
-
// 0=document, 1=api_ref, 2=db_ref, 3=etl_ref, 4=mcp_ref
|
|
21
|
-
"doc_type UNINDEXED",
|
|
22
|
-
// Raw markdown source of the document (HTML sources are converted)
|
|
23
|
-
"doc_md UNINDEXED",
|
|
24
|
-
],
|
|
25
|
-
hdoc_meta: [
|
|
26
|
-
"resource_url",
|
|
27
|
-
"book_id",
|
|
28
|
-
"contributor_count INTEGER UNINDEXED",
|
|
29
|
-
"edit_url UNINDEXED",
|
|
30
|
-
"last_commit UNINDEXED",
|
|
31
|
-
"pdf_size INTEGER UNINDEXED",
|
|
32
|
-
],
|
|
33
|
-
hdoc_contributors: [
|
|
34
|
-
"resource_url",
|
|
35
|
-
"book_id",
|
|
36
|
-
"login",
|
|
37
|
-
"name",
|
|
38
|
-
"avatar UNINDEXED",
|
|
39
|
-
"url UNINDEXED",
|
|
40
|
-
],
|
|
41
|
-
hdoc_redirects: [
|
|
42
|
-
"resource_url",
|
|
43
|
-
"location_url",
|
|
44
|
-
"http_code INTEGER UNINDEXED",
|
|
45
|
-
],
|
|
46
|
-
// Semantic search embeddings, generated by hdoc-build-embeddings.js.
|
|
47
|
-
// The docs service merges these into its sqlite-vec index at publish
|
|
48
|
-
hdoc_embeddings: [
|
|
49
|
-
"resource_url",
|
|
50
|
-
"chunk_seq INTEGER",
|
|
51
|
-
"chunk_text",
|
|
52
|
-
"embedding BLOB",
|
|
53
|
-
"model",
|
|
54
|
-
"dims INTEGER",
|
|
55
|
-
],
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
exports.create_table = (db, table, cols, virtual, fts5) => {
|
|
59
|
-
const table_created = hdoc_index.create_table(
|
|
60
|
-
db,
|
|
61
|
-
table,
|
|
62
|
-
cols,
|
|
63
|
-
virtual,
|
|
64
|
-
fts5,
|
|
65
|
-
);
|
|
66
|
-
if (table_created !== null) {
|
|
67
|
-
return `\nError creating table: ${table_created}`;
|
|
68
|
-
}
|
|
69
|
-
console.log(`\nTable created: ${table}`);
|
|
70
|
-
return null;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
exports.create_db = function (db_path, doc_id) {
|
|
74
|
-
const response = {
|
|
75
|
-
error: null,
|
|
76
|
-
db: null,
|
|
77
|
-
};
|
|
78
|
-
console.log("\nPerforming SQlite index creation...");
|
|
79
|
-
const db_name = path.join(db_path, doc_id, "index.db");
|
|
80
|
-
response.db = new database(db_name);
|
|
81
|
-
response.db.pragma('encoding="UTF-8"');
|
|
82
|
-
console.log(`\nDatabase created: ${db_name}`);
|
|
83
|
-
|
|
84
|
-
// Create tables
|
|
85
|
-
for (const key in db_schema) {
|
|
86
|
-
if (Object.hasOwn(db_schema, key)) {
|
|
87
|
-
const virtual = key === "hdoc_index";
|
|
88
|
-
const fts5 = key === "hdoc_index";
|
|
89
|
-
const table_created_error = this.create_table(
|
|
90
|
-
response.db,
|
|
91
|
-
key,
|
|
92
|
-
db_schema[key],
|
|
93
|
-
virtual,
|
|
94
|
-
fts5,
|
|
95
|
-
);
|
|
96
|
-
if (table_created_error !== null) {
|
|
97
|
-
console.error(table_created_error);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return response;
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
exports.populate_redirects = (db, redirect_records, _verbose = false) => {
|
|
105
|
-
const response = {
|
|
106
|
-
success: true,
|
|
107
|
-
errors: [],
|
|
108
|
-
index_success_count: 0,
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
// Prepare once, insert all in one transaction
|
|
112
|
-
const cols = db_schema.hdoc_redirects.map(c => c.replace(/\b(UNINDEXED|INTEGER)\b/g, "").trim());
|
|
113
|
-
const stmt = db.prepare(`INSERT INTO hdoc_redirects (${cols.join(", ")}) VALUES (${cols.map(() => "?").join(", ")})`);
|
|
114
|
-
|
|
115
|
-
const run_all = db.transaction(() => {
|
|
116
|
-
for (const record of redirect_records) {
|
|
117
|
-
try {
|
|
118
|
-
stmt.run(record.url, record.location ? record.location : "", record.code);
|
|
119
|
-
response.index_success_count++;
|
|
120
|
-
} catch (e) {
|
|
121
|
-
response.success = false;
|
|
122
|
-
response.errors.push(`Redirect record creation failed - ${record.url}: ${e}`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
run_all();
|
|
129
|
-
} catch (e) {
|
|
130
|
-
response.success = false;
|
|
131
|
-
response.errors.push(`Redirect index transaction failed: ${e}`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
console.log(
|
|
135
|
-
`\nRedirect Index Build Complete: ${response.index_success_count} document records created.`,
|
|
136
|
-
);
|
|
137
|
-
return response;
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
exports.populate_index = (
|
|
141
|
-
db,
|
|
142
|
-
doc_id,
|
|
143
|
-
book_config,
|
|
144
|
-
index_records,
|
|
145
|
-
verbose = false,
|
|
146
|
-
) => {
|
|
147
|
-
const response = {
|
|
148
|
-
success: false,
|
|
149
|
-
index_success_count: 0,
|
|
150
|
-
error: "",
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
if (!book_config.tags) book_config.tags = [];
|
|
154
|
-
|
|
155
|
-
// Book-level content type from hdocbook.json bookType:
|
|
156
|
-
// 0=document, 1=api_ref, 2=db_ref, 3=etl_ref, 4=mcp_ref
|
|
157
|
-
// Bound as BigInt: better-sqlite3 binds JS numbers as REAL, and FTS5
|
|
158
|
-
// columns have no affinity to coerce them back to INTEGER
|
|
159
|
-
const book_doc_type = BigInt(
|
|
160
|
-
Number.isInteger(book_config.bookType) ? book_config.bookType : 0,
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
// Build a prepared statement from a schema entry once, reusing it for every row.
|
|
164
|
-
// Previously insert_record() called db.prepare() on every single insert.
|
|
165
|
-
const make_stmt = (table) => {
|
|
166
|
-
const cols = db_schema[table].map(c => c.replace(/\b(UNINDEXED|INTEGER)\b/g, "").trim());
|
|
167
|
-
return db.prepare(`INSERT INTO ${table} (${cols.join(", ")}) VALUES (${cols.map(() => "?").join(", ")})`);
|
|
168
|
-
};
|
|
169
|
-
const stmt_index = make_stmt("hdoc_index");
|
|
170
|
-
const stmt_meta = make_stmt("hdoc_meta");
|
|
171
|
-
const stmt_contrib = make_stmt("hdoc_contributors");
|
|
172
|
-
|
|
173
|
-
// A single transaction batches all disk flushes into one — critical for
|
|
174
|
-
// FTS5 which otherwise re-indexes on every individual insert.
|
|
175
|
-
const run_all = db.transaction(() => {
|
|
176
|
-
let curr_file = "";
|
|
177
|
-
for (const file of index_records) {
|
|
178
|
-
let index_path_name = file.relative_path.replaceAll("\\", "/");
|
|
179
|
-
if (
|
|
180
|
-
index_path_name.endsWith("/index.md") ||
|
|
181
|
-
index_path_name.endsWith("/index.html") ||
|
|
182
|
-
index_path_name.endsWith("/index.htm")
|
|
183
|
-
) {
|
|
184
|
-
index_path_name = index_path_name.substring(0, index_path_name.lastIndexOf("/"));
|
|
185
|
-
}
|
|
186
|
-
index_path_name = `/${index_path_name.replace(path.extname(file.relative_path), "")}`;
|
|
187
|
-
|
|
188
|
-
let inserted_row_id = null;
|
|
189
|
-
const index_content_path = file.index_html.id !== null
|
|
190
|
-
? `${index_path_name}#${file.index_html.id}`
|
|
191
|
-
: index_path_name;
|
|
192
|
-
|
|
193
|
-
if (!file.inline) {
|
|
194
|
-
try {
|
|
195
|
-
const info = stmt_index.run(
|
|
196
|
-
index_content_path,
|
|
197
|
-
doc_id,
|
|
198
|
-
book_config.audience.join(","),
|
|
199
|
-
book_config.tags.join(","),
|
|
200
|
-
file.index_html.fm_props.title,
|
|
201
|
-
file.index_html.text,
|
|
202
|
-
file.index_html.preview,
|
|
203
|
-
book_config.productFamily,
|
|
204
|
-
file.md5,
|
|
205
|
-
file.lastmod,
|
|
206
|
-
file.status,
|
|
207
|
-
file.keywords,
|
|
208
|
-
book_doc_type,
|
|
209
|
-
file.doc_md ?? "",
|
|
210
|
-
);
|
|
211
|
-
inserted_row_id = info.lastInsertRowid;
|
|
212
|
-
} catch (e) {
|
|
213
|
-
console.error(`Index record creation failed - ${doc_id}/${file.index_html.fm_props.title}: ${e}`);
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (curr_file === index_path_name) continue;
|
|
219
|
-
curr_file = index_path_name;
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
const meta_info = stmt_meta.run(
|
|
223
|
-
index_path_name,
|
|
224
|
-
doc_id,
|
|
225
|
-
file.metadata.contributor_count,
|
|
226
|
-
file.metadata.edit_url,
|
|
227
|
-
file.metadata.last_commit,
|
|
228
|
-
file.pdf_size,
|
|
229
|
-
);
|
|
230
|
-
if (verbose) {
|
|
231
|
-
console.log(`Inserted index record ${inserted_row_id}: ${doc_id} - ${file.index_html.fm_props.title}`);
|
|
232
|
-
console.log(`Inserted index metadata record for index ID: ${meta_info.lastInsertRowid}`);
|
|
233
|
-
}
|
|
234
|
-
} catch (e) {
|
|
235
|
-
console.error(`Index metadata record creation failed - ${doc_id}/${inserted_row_id}/${file.index_html.fm_props.title}: ${e}`);
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
for (const contrib of file.contributors) {
|
|
240
|
-
try {
|
|
241
|
-
const cont_info = stmt_contrib.run(
|
|
242
|
-
index_path_name,
|
|
243
|
-
doc_id,
|
|
244
|
-
contrib.login,
|
|
245
|
-
contrib.name,
|
|
246
|
-
contrib.avatar_url,
|
|
247
|
-
contrib.html_url,
|
|
248
|
-
);
|
|
249
|
-
if (verbose) {
|
|
250
|
-
console.log(`Inserted document contributor record ${cont_info.lastInsertRowid}`);
|
|
251
|
-
}
|
|
252
|
-
} catch (e) {
|
|
253
|
-
console.error(`Index document contributor record creation failed - ${doc_id}/${inserted_row_id}/${file.index_html.fm_props.title}: ${e}`);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
response.index_success_count++;
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
try {
|
|
262
|
-
run_all();
|
|
263
|
-
} catch (e) {
|
|
264
|
-
response.error = e.message;
|
|
265
|
-
console.error(`Index build transaction failed: ${e}`);
|
|
266
|
-
return response;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
response.success = true;
|
|
270
|
-
console.log(
|
|
271
|
-
`\nIndex Build Complete: ${response.index_success_count} document index records created.`,
|
|
272
|
-
);
|
|
273
|
-
return response;
|
|
274
|
-
};
|
|
275
|
-
})();
|
|
1
|
+
(() => {
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const hdoc_index = require(path.join(__dirname, "hdoc-db.js"));
|
|
4
|
+
const database = require("better-sqlite3");
|
|
5
|
+
|
|
6
|
+
const db_schema = {
|
|
7
|
+
hdoc_index: [
|
|
8
|
+
"resource_url UNINDEXED",
|
|
9
|
+
"book_id",
|
|
10
|
+
"book_audience UNINDEXED",
|
|
11
|
+
"book_tags",
|
|
12
|
+
"doc_title",
|
|
13
|
+
"doc_content",
|
|
14
|
+
"doc_preview UNINDEXED",
|
|
15
|
+
"doc_family_id",
|
|
16
|
+
"doc_md5_hash UNINDEXED",
|
|
17
|
+
"doc_lastmod",
|
|
18
|
+
"doc_status",
|
|
19
|
+
"doc_keywords",
|
|
20
|
+
// 0=document, 1=api_ref, 2=db_ref, 3=etl_ref, 4=mcp_ref
|
|
21
|
+
"doc_type UNINDEXED",
|
|
22
|
+
// Raw markdown source of the document (HTML sources are converted)
|
|
23
|
+
"doc_md UNINDEXED",
|
|
24
|
+
],
|
|
25
|
+
hdoc_meta: [
|
|
26
|
+
"resource_url",
|
|
27
|
+
"book_id",
|
|
28
|
+
"contributor_count INTEGER UNINDEXED",
|
|
29
|
+
"edit_url UNINDEXED",
|
|
30
|
+
"last_commit UNINDEXED",
|
|
31
|
+
"pdf_size INTEGER UNINDEXED",
|
|
32
|
+
],
|
|
33
|
+
hdoc_contributors: [
|
|
34
|
+
"resource_url",
|
|
35
|
+
"book_id",
|
|
36
|
+
"login",
|
|
37
|
+
"name",
|
|
38
|
+
"avatar UNINDEXED",
|
|
39
|
+
"url UNINDEXED",
|
|
40
|
+
],
|
|
41
|
+
hdoc_redirects: [
|
|
42
|
+
"resource_url",
|
|
43
|
+
"location_url",
|
|
44
|
+
"http_code INTEGER UNINDEXED",
|
|
45
|
+
],
|
|
46
|
+
// Semantic search embeddings, generated by hdoc-build-embeddings.js.
|
|
47
|
+
// The docs service merges these into its sqlite-vec index at publish
|
|
48
|
+
hdoc_embeddings: [
|
|
49
|
+
"resource_url",
|
|
50
|
+
"chunk_seq INTEGER",
|
|
51
|
+
"chunk_text",
|
|
52
|
+
"embedding BLOB",
|
|
53
|
+
"model",
|
|
54
|
+
"dims INTEGER",
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
exports.create_table = (db, table, cols, virtual, fts5) => {
|
|
59
|
+
const table_created = hdoc_index.create_table(
|
|
60
|
+
db,
|
|
61
|
+
table,
|
|
62
|
+
cols,
|
|
63
|
+
virtual,
|
|
64
|
+
fts5,
|
|
65
|
+
);
|
|
66
|
+
if (table_created !== null) {
|
|
67
|
+
return `\nError creating table: ${table_created}`;
|
|
68
|
+
}
|
|
69
|
+
console.log(`\nTable created: ${table}`);
|
|
70
|
+
return null;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
exports.create_db = function (db_path, doc_id) {
|
|
74
|
+
const response = {
|
|
75
|
+
error: null,
|
|
76
|
+
db: null,
|
|
77
|
+
};
|
|
78
|
+
console.log("\nPerforming SQlite index creation...");
|
|
79
|
+
const db_name = path.join(db_path, doc_id, "index.db");
|
|
80
|
+
response.db = new database(db_name);
|
|
81
|
+
response.db.pragma('encoding="UTF-8"');
|
|
82
|
+
console.log(`\nDatabase created: ${db_name}`);
|
|
83
|
+
|
|
84
|
+
// Create tables
|
|
85
|
+
for (const key in db_schema) {
|
|
86
|
+
if (Object.hasOwn(db_schema, key)) {
|
|
87
|
+
const virtual = key === "hdoc_index";
|
|
88
|
+
const fts5 = key === "hdoc_index";
|
|
89
|
+
const table_created_error = this.create_table(
|
|
90
|
+
response.db,
|
|
91
|
+
key,
|
|
92
|
+
db_schema[key],
|
|
93
|
+
virtual,
|
|
94
|
+
fts5,
|
|
95
|
+
);
|
|
96
|
+
if (table_created_error !== null) {
|
|
97
|
+
console.error(table_created_error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return response;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
exports.populate_redirects = (db, redirect_records, _verbose = false) => {
|
|
105
|
+
const response = {
|
|
106
|
+
success: true,
|
|
107
|
+
errors: [],
|
|
108
|
+
index_success_count: 0,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Prepare once, insert all in one transaction
|
|
112
|
+
const cols = db_schema.hdoc_redirects.map(c => c.replace(/\b(UNINDEXED|INTEGER)\b/g, "").trim());
|
|
113
|
+
const stmt = db.prepare(`INSERT INTO hdoc_redirects (${cols.join(", ")}) VALUES (${cols.map(() => "?").join(", ")})`);
|
|
114
|
+
|
|
115
|
+
const run_all = db.transaction(() => {
|
|
116
|
+
for (const record of redirect_records) {
|
|
117
|
+
try {
|
|
118
|
+
stmt.run(record.url, record.location ? record.location : "", record.code);
|
|
119
|
+
response.index_success_count++;
|
|
120
|
+
} catch (e) {
|
|
121
|
+
response.success = false;
|
|
122
|
+
response.errors.push(`Redirect record creation failed - ${record.url}: ${e}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
run_all();
|
|
129
|
+
} catch (e) {
|
|
130
|
+
response.success = false;
|
|
131
|
+
response.errors.push(`Redirect index transaction failed: ${e}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log(
|
|
135
|
+
`\nRedirect Index Build Complete: ${response.index_success_count} document records created.`,
|
|
136
|
+
);
|
|
137
|
+
return response;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
exports.populate_index = (
|
|
141
|
+
db,
|
|
142
|
+
doc_id,
|
|
143
|
+
book_config,
|
|
144
|
+
index_records,
|
|
145
|
+
verbose = false,
|
|
146
|
+
) => {
|
|
147
|
+
const response = {
|
|
148
|
+
success: false,
|
|
149
|
+
index_success_count: 0,
|
|
150
|
+
error: "",
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
if (!book_config.tags) book_config.tags = [];
|
|
154
|
+
|
|
155
|
+
// Book-level content type from hdocbook.json bookType:
|
|
156
|
+
// 0=document, 1=api_ref, 2=db_ref, 3=etl_ref, 4=mcp_ref
|
|
157
|
+
// Bound as BigInt: better-sqlite3 binds JS numbers as REAL, and FTS5
|
|
158
|
+
// columns have no affinity to coerce them back to INTEGER
|
|
159
|
+
const book_doc_type = BigInt(
|
|
160
|
+
Number.isInteger(book_config.bookType) ? book_config.bookType : 0,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// Build a prepared statement from a schema entry once, reusing it for every row.
|
|
164
|
+
// Previously insert_record() called db.prepare() on every single insert.
|
|
165
|
+
const make_stmt = (table) => {
|
|
166
|
+
const cols = db_schema[table].map(c => c.replace(/\b(UNINDEXED|INTEGER)\b/g, "").trim());
|
|
167
|
+
return db.prepare(`INSERT INTO ${table} (${cols.join(", ")}) VALUES (${cols.map(() => "?").join(", ")})`);
|
|
168
|
+
};
|
|
169
|
+
const stmt_index = make_stmt("hdoc_index");
|
|
170
|
+
const stmt_meta = make_stmt("hdoc_meta");
|
|
171
|
+
const stmt_contrib = make_stmt("hdoc_contributors");
|
|
172
|
+
|
|
173
|
+
// A single transaction batches all disk flushes into one — critical for
|
|
174
|
+
// FTS5 which otherwise re-indexes on every individual insert.
|
|
175
|
+
const run_all = db.transaction(() => {
|
|
176
|
+
let curr_file = "";
|
|
177
|
+
for (const file of index_records) {
|
|
178
|
+
let index_path_name = file.relative_path.replaceAll("\\", "/");
|
|
179
|
+
if (
|
|
180
|
+
index_path_name.endsWith("/index.md") ||
|
|
181
|
+
index_path_name.endsWith("/index.html") ||
|
|
182
|
+
index_path_name.endsWith("/index.htm")
|
|
183
|
+
) {
|
|
184
|
+
index_path_name = index_path_name.substring(0, index_path_name.lastIndexOf("/"));
|
|
185
|
+
}
|
|
186
|
+
index_path_name = `/${index_path_name.replace(path.extname(file.relative_path), "")}`;
|
|
187
|
+
|
|
188
|
+
let inserted_row_id = null;
|
|
189
|
+
const index_content_path = file.index_html.id !== null
|
|
190
|
+
? `${index_path_name}#${file.index_html.id}`
|
|
191
|
+
: index_path_name;
|
|
192
|
+
|
|
193
|
+
if (!file.inline) {
|
|
194
|
+
try {
|
|
195
|
+
const info = stmt_index.run(
|
|
196
|
+
index_content_path,
|
|
197
|
+
doc_id,
|
|
198
|
+
book_config.audience.join(","),
|
|
199
|
+
book_config.tags.join(","),
|
|
200
|
+
file.index_html.fm_props.title,
|
|
201
|
+
file.index_html.text,
|
|
202
|
+
file.index_html.preview,
|
|
203
|
+
book_config.productFamily,
|
|
204
|
+
file.md5,
|
|
205
|
+
file.lastmod,
|
|
206
|
+
file.status,
|
|
207
|
+
file.keywords,
|
|
208
|
+
book_doc_type,
|
|
209
|
+
file.doc_md ?? "",
|
|
210
|
+
);
|
|
211
|
+
inserted_row_id = info.lastInsertRowid;
|
|
212
|
+
} catch (e) {
|
|
213
|
+
console.error(`Index record creation failed - ${doc_id}/${file.index_html.fm_props.title}: ${e}`);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (curr_file === index_path_name) continue;
|
|
219
|
+
curr_file = index_path_name;
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const meta_info = stmt_meta.run(
|
|
223
|
+
index_path_name,
|
|
224
|
+
doc_id,
|
|
225
|
+
file.metadata.contributor_count,
|
|
226
|
+
file.metadata.edit_url,
|
|
227
|
+
file.metadata.last_commit,
|
|
228
|
+
file.pdf_size,
|
|
229
|
+
);
|
|
230
|
+
if (verbose) {
|
|
231
|
+
console.log(`Inserted index record ${inserted_row_id}: ${doc_id} - ${file.index_html.fm_props.title}`);
|
|
232
|
+
console.log(`Inserted index metadata record for index ID: ${meta_info.lastInsertRowid}`);
|
|
233
|
+
}
|
|
234
|
+
} catch (e) {
|
|
235
|
+
console.error(`Index metadata record creation failed - ${doc_id}/${inserted_row_id}/${file.index_html.fm_props.title}: ${e}`);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
for (const contrib of file.contributors) {
|
|
240
|
+
try {
|
|
241
|
+
const cont_info = stmt_contrib.run(
|
|
242
|
+
index_path_name,
|
|
243
|
+
doc_id,
|
|
244
|
+
contrib.login,
|
|
245
|
+
contrib.name,
|
|
246
|
+
contrib.avatar_url,
|
|
247
|
+
contrib.html_url,
|
|
248
|
+
);
|
|
249
|
+
if (verbose) {
|
|
250
|
+
console.log(`Inserted document contributor record ${cont_info.lastInsertRowid}`);
|
|
251
|
+
}
|
|
252
|
+
} catch (e) {
|
|
253
|
+
console.error(`Index document contributor record creation failed - ${doc_id}/${inserted_row_id}/${file.index_html.fm_props.title}: ${e}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
response.index_success_count++;
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
run_all();
|
|
263
|
+
} catch (e) {
|
|
264
|
+
response.error = e.message;
|
|
265
|
+
console.error(`Index build transaction failed: ${e}`);
|
|
266
|
+
return response;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
response.success = true;
|
|
270
|
+
console.log(
|
|
271
|
+
`\nIndex Build Complete: ${response.index_success_count} document index records created.`,
|
|
272
|
+
);
|
|
273
|
+
return response;
|
|
274
|
+
};
|
|
275
|
+
})();
|