@s-hirano-ist/s-scripts 1.26.8 → 4.0.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.
Files changed (180) hide show
  1. package/README.md +2 -0
  2. package/dist/chunk-5OXUZW2Q.js +590 -0
  3. package/dist/chunk-7PVU2Z32.js +71 -0
  4. package/dist/chunk-AJMTRCPJ.js +15 -0
  5. package/dist/chunk-AZF2R2BF.js +85 -0
  6. package/dist/chunk-F4J6JBR4.js +115 -0
  7. package/dist/chunk-HDYQV2Y2.js +58 -0
  8. package/dist/chunk-HIK7SX34.js +85 -0
  9. package/dist/chunk-IWNZQXDG.js +7660 -0
  10. package/dist/chunk-J4VLH4VA.js +267 -0
  11. package/dist/chunk-JZOR5GA2.js +42 -0
  12. package/dist/chunk-KKZP7ZF3.js +77 -0
  13. package/dist/chunk-N6TZF755.js +102 -0
  14. package/dist/chunk-S5WJ5VXW.js +85 -0
  15. package/dist/chunk-UXGLXHQ6.js +40 -0
  16. package/dist/cleanup-minio-images.js +115 -92
  17. package/dist/enrich-books.js +87 -78
  18. package/dist/fetch-articles.js +101 -90
  19. package/dist/fetch-books.js +147 -128
  20. package/dist/fetch-images.js +88 -72
  21. package/dist/fetch-notes.js +66 -54
  22. package/dist/find-duplicate-json-articles.js +100 -97
  23. package/dist/fix-book-frontmatter.js +111 -99
  24. package/dist/fix-image-formats.js +81 -65
  25. package/dist/ingest-articles.js +30 -249
  26. package/dist/ingest-books.js +61 -318
  27. package/dist/ingest-images.js +33 -184
  28. package/dist/ingest-notes.js +41 -171
  29. package/dist/rag/delete-collection.js +32 -22
  30. package/dist/rag/ingest.js +371 -92
  31. package/dist/rag/search.js +80 -76
  32. package/dist/reset-articles.js +57 -60
  33. package/dist/reset-books.js +57 -59
  34. package/dist/reset-images.js +57 -59
  35. package/dist/reset-notes.js +57 -59
  36. package/dist/revert-articles.js +53 -55
  37. package/dist/revert-books.js +53 -53
  38. package/dist/revert-images.js +53 -53
  39. package/dist/revert-notes.js +53 -53
  40. package/dist/s-content-format.js +103 -13
  41. package/dist/update-json-articles.js +101 -97
  42. package/dist/update-raw-articles.js +130 -130
  43. package/dist/validate-json-articles.js +129 -130
  44. package/package.json +13 -50
  45. package/dist/cleanup-minio-images.d.ts +0 -3
  46. package/dist/cleanup-minio-images.d.ts.map +0 -1
  47. package/dist/cleanup-minio-images.js.map +0 -1
  48. package/dist/enrich-books.d.ts +0 -3
  49. package/dist/enrich-books.d.ts.map +0 -1
  50. package/dist/enrich-books.js.map +0 -1
  51. package/dist/fetch-articles.d.ts +0 -3
  52. package/dist/fetch-articles.d.ts.map +0 -1
  53. package/dist/fetch-articles.js.map +0 -1
  54. package/dist/fetch-books.d.ts +0 -3
  55. package/dist/fetch-books.d.ts.map +0 -1
  56. package/dist/fetch-books.js.map +0 -1
  57. package/dist/fetch-images.d.ts +0 -3
  58. package/dist/fetch-images.d.ts.map +0 -1
  59. package/dist/fetch-images.js.map +0 -1
  60. package/dist/fetch-notes.d.ts +0 -3
  61. package/dist/fetch-notes.d.ts.map +0 -1
  62. package/dist/fetch-notes.js.map +0 -1
  63. package/dist/find-duplicate-json-articles.d.ts +0 -3
  64. package/dist/find-duplicate-json-articles.d.ts.map +0 -1
  65. package/dist/find-duplicate-json-articles.js.map +0 -1
  66. package/dist/fix-book-frontmatter.d.ts +0 -3
  67. package/dist/fix-book-frontmatter.d.ts.map +0 -1
  68. package/dist/fix-book-frontmatter.js.map +0 -1
  69. package/dist/fix-image-formats.d.ts +0 -3
  70. package/dist/fix-image-formats.d.ts.map +0 -1
  71. package/dist/fix-image-formats.js.map +0 -1
  72. package/dist/infrastructures/articles-command-repository.d.ts +0 -57
  73. package/dist/infrastructures/articles-command-repository.d.ts.map +0 -1
  74. package/dist/infrastructures/articles-command-repository.js +0 -68
  75. package/dist/infrastructures/articles-command-repository.js.map +0 -1
  76. package/dist/infrastructures/books-command-repository.d.ts +0 -34
  77. package/dist/infrastructures/books-command-repository.d.ts.map +0 -1
  78. package/dist/infrastructures/books-command-repository.js +0 -53
  79. package/dist/infrastructures/books-command-repository.js.map +0 -1
  80. package/dist/infrastructures/cache-invalidation-client.d.ts +0 -9
  81. package/dist/infrastructures/cache-invalidation-client.d.ts.map +0 -1
  82. package/dist/infrastructures/cache-invalidation-client.js +0 -21
  83. package/dist/infrastructures/cache-invalidation-client.js.map +0 -1
  84. package/dist/infrastructures/cache-invalidation-client.test.d.ts +0 -2
  85. package/dist/infrastructures/cache-invalidation-client.test.d.ts.map +0 -1
  86. package/dist/infrastructures/cache-invalidation-client.test.js +0 -25
  87. package/dist/infrastructures/cache-invalidation-client.test.js.map +0 -1
  88. package/dist/infrastructures/images-command-repository.d.ts +0 -34
  89. package/dist/infrastructures/images-command-repository.d.ts.map +0 -1
  90. package/dist/infrastructures/images-command-repository.js +0 -53
  91. package/dist/infrastructures/images-command-repository.js.map +0 -1
  92. package/dist/infrastructures/notes-command-repository.d.ts +0 -34
  93. package/dist/infrastructures/notes-command-repository.d.ts.map +0 -1
  94. package/dist/infrastructures/notes-command-repository.js +0 -53
  95. package/dist/infrastructures/notes-command-repository.js.map +0 -1
  96. package/dist/ingest-articles.d.ts +0 -3
  97. package/dist/ingest-articles.d.ts.map +0 -1
  98. package/dist/ingest-articles.js.map +0 -1
  99. package/dist/ingest-books.d.ts +0 -3
  100. package/dist/ingest-books.d.ts.map +0 -1
  101. package/dist/ingest-books.js.map +0 -1
  102. package/dist/ingest-images.d.ts +0 -3
  103. package/dist/ingest-images.d.ts.map +0 -1
  104. package/dist/ingest-images.js.map +0 -1
  105. package/dist/ingest-notes.d.ts +0 -3
  106. package/dist/ingest-notes.d.ts.map +0 -1
  107. package/dist/ingest-notes.js.map +0 -1
  108. package/dist/lib/glob-paths.d.ts +0 -6
  109. package/dist/lib/glob-paths.d.ts.map +0 -1
  110. package/dist/lib/glob-paths.js +0 -9
  111. package/dist/lib/glob-paths.js.map +0 -1
  112. package/dist/lib/glob-paths.test.d.ts +0 -2
  113. package/dist/lib/glob-paths.test.d.ts.map +0 -1
  114. package/dist/lib/glob-paths.test.js +0 -30
  115. package/dist/lib/glob-paths.test.js.map +0 -1
  116. package/dist/lib/html-charset.d.ts +0 -4
  117. package/dist/lib/html-charset.d.ts.map +0 -1
  118. package/dist/lib/html-charset.js +0 -33
  119. package/dist/lib/html-charset.js.map +0 -1
  120. package/dist/lib/html-charset.test.d.ts +0 -2
  121. package/dist/lib/html-charset.test.d.ts.map +0 -1
  122. package/dist/lib/html-charset.test.js +0 -27
  123. package/dist/lib/html-charset.test.js.map +0 -1
  124. package/dist/rag/delete-collection.d.ts +0 -3
  125. package/dist/rag/delete-collection.d.ts.map +0 -1
  126. package/dist/rag/delete-collection.js.map +0 -1
  127. package/dist/rag/ingest-config.d.ts +0 -8
  128. package/dist/rag/ingest-config.d.ts.map +0 -1
  129. package/dist/rag/ingest-config.js +0 -8
  130. package/dist/rag/ingest-config.js.map +0 -1
  131. package/dist/rag/ingest.d.ts +0 -3
  132. package/dist/rag/ingest.d.ts.map +0 -1
  133. package/dist/rag/ingest.js.map +0 -1
  134. package/dist/rag/search.d.ts +0 -3
  135. package/dist/rag/search.d.ts.map +0 -1
  136. package/dist/rag/search.js.map +0 -1
  137. package/dist/reset-articles.d.ts +0 -3
  138. package/dist/reset-articles.d.ts.map +0 -1
  139. package/dist/reset-articles.js.map +0 -1
  140. package/dist/reset-books.d.ts +0 -3
  141. package/dist/reset-books.d.ts.map +0 -1
  142. package/dist/reset-books.js.map +0 -1
  143. package/dist/reset-images.d.ts +0 -3
  144. package/dist/reset-images.d.ts.map +0 -1
  145. package/dist/reset-images.js.map +0 -1
  146. package/dist/reset-notes.d.ts +0 -3
  147. package/dist/reset-notes.d.ts.map +0 -1
  148. package/dist/reset-notes.js.map +0 -1
  149. package/dist/revert-articles.d.ts +0 -3
  150. package/dist/revert-articles.d.ts.map +0 -1
  151. package/dist/revert-articles.js.map +0 -1
  152. package/dist/revert-books.d.ts +0 -3
  153. package/dist/revert-books.d.ts.map +0 -1
  154. package/dist/revert-books.js.map +0 -1
  155. package/dist/revert-images.d.ts +0 -3
  156. package/dist/revert-images.d.ts.map +0 -1
  157. package/dist/revert-images.js.map +0 -1
  158. package/dist/revert-notes.d.ts +0 -3
  159. package/dist/revert-notes.d.ts.map +0 -1
  160. package/dist/revert-notes.js.map +0 -1
  161. package/dist/s-content-format-runner.d.ts +0 -10
  162. package/dist/s-content-format-runner.d.ts.map +0 -1
  163. package/dist/s-content-format-runner.js +0 -76
  164. package/dist/s-content-format-runner.js.map +0 -1
  165. package/dist/s-content-format.d.ts +0 -3
  166. package/dist/s-content-format.d.ts.map +0 -1
  167. package/dist/s-content-format.js.map +0 -1
  168. package/dist/s-content-format.test.d.ts +0 -2
  169. package/dist/s-content-format.test.d.ts.map +0 -1
  170. package/dist/s-content-format.test.js +0 -99
  171. package/dist/s-content-format.test.js.map +0 -1
  172. package/dist/update-json-articles.d.ts +0 -3
  173. package/dist/update-json-articles.d.ts.map +0 -1
  174. package/dist/update-json-articles.js.map +0 -1
  175. package/dist/update-raw-articles.d.ts +0 -3
  176. package/dist/update-raw-articles.d.ts.map +0 -1
  177. package/dist/update-raw-articles.js.map +0 -1
  178. package/dist/validate-json-articles.d.ts +0 -3
  179. package/dist/validate-json-articles.d.ts.map +0 -1
  180. package/dist/validate-json-articles.js.map +0 -1
@@ -0,0 +1,267 @@
1
+ import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
2
+
3
+ // ../search/dist/qdrant-client.js
4
+ import { QdrantClient } from "@qdrant/js-client-rest";
5
+
6
+ // ../search/dist/config.js
7
+ function requireEnv(name) {
8
+ const value = process.env[name];
9
+ if (!value)
10
+ throw new Error(`${name} environment variable is required`);
11
+ return value;
12
+ }
13
+ function envWithDefault(name, defaultValue) {
14
+ return process.env[name] || defaultValue;
15
+ }
16
+ var RAG_CONFIG = {
17
+ qdrant: {
18
+ collectionName: requireEnv("QDRANT_COLLECTION_NAME"),
19
+ vectorSize: Number(envWithDefault("EMBEDDING_VECTOR_SIZE", "384")),
20
+ distance: "Cosine"
21
+ },
22
+ embedding: {
23
+ // Deployed services and ingestion jobs always use Qdrant Cloud Inference.
24
+ // Keep TEI only for explicitly enabled, non-production local development.
25
+ url: process.env.LOCAL_DEV_MODE === "true" && process.env.VERCEL !== "1" && process.env.NODE_ENV !== "production" ? process.env.EMBEDDING_URL : void 0,
26
+ model: envWithDefault("EMBEDDING_MODEL", "intfloat/multilingual-e5-small")
27
+ },
28
+ chunking: { maxChunkLength: 2e3, headingLevels: [2, 3] }
29
+ };
30
+
31
+ // ../search/dist/qdrant-client.js
32
+ var client = null;
33
+ async function embedLocally(texts) {
34
+ const { url } = RAG_CONFIG.embedding;
35
+ if (!url)
36
+ throw new Error("EMBEDDING_URL environment variable is required");
37
+ const response = await fetch(`${url.replace(/\/$/u, "")}/embed`, {
38
+ method: "POST",
39
+ headers: { "content-type": "application/json" },
40
+ body: JSON.stringify({ inputs: texts, normalize: true, truncate: true })
41
+ });
42
+ if (!response.ok) {
43
+ throw new Error(`Embedding request failed (${response.status}): ${await response.text()}`);
44
+ }
45
+ const embeddings = await response.json();
46
+ if (!Array.isArray(embeddings) || embeddings.length !== texts.length || embeddings.some((embedding) => !Array.isArray(embedding) || embedding.some((value) => typeof value !== "number"))) {
47
+ throw new TypeError("Embedding API returned an invalid response");
48
+ }
49
+ return embeddings;
50
+ }
51
+ function getQdrantClient(config) {
52
+ if (!client) {
53
+ const url = config?.url ?? process.env.QDRANT_URL;
54
+ const apiKey = config?.apiKey ?? process.env.QDRANT_API_KEY;
55
+ if (!url) {
56
+ throw new Error("QDRANT_URL environment variable is required");
57
+ }
58
+ client = new QdrantClient({
59
+ url,
60
+ apiKey
61
+ });
62
+ }
63
+ return client;
64
+ }
65
+ async function deleteCollection(collectionName) {
66
+ const qdrant = getQdrantClient();
67
+ await qdrant.deleteCollection(collectionName);
68
+ }
69
+ async function ensureCollection() {
70
+ const qdrant = getQdrantClient();
71
+ const { collectionName, vectorSize, distance } = RAG_CONFIG.qdrant;
72
+ const collections = await qdrant.getCollections();
73
+ const exists = collections.collections.some((c) => c.name === collectionName);
74
+ if (exists) {
75
+ console.log(`Collection ${collectionName} already exists.`);
76
+ } else {
77
+ console.log(`Creating collection: ${collectionName}`);
78
+ await qdrant.createCollection(collectionName, {
79
+ vectors: {
80
+ size: vectorSize,
81
+ distance
82
+ }
83
+ });
84
+ console.log(`Collection ${collectionName} created successfully.`);
85
+ }
86
+ await qdrant.createPayloadIndex(collectionName, {
87
+ field_name: "type",
88
+ field_schema: "keyword",
89
+ wait: true
90
+ });
91
+ await qdrant.createPayloadIndex(collectionName, {
92
+ field_name: "top_heading",
93
+ field_schema: "keyword",
94
+ wait: true
95
+ });
96
+ await qdrant.createPayloadIndex(collectionName, {
97
+ field_name: "content_type",
98
+ field_schema: "keyword",
99
+ wait: true
100
+ });
101
+ console.log("Payload indexes ensured for: type, top_heading, content_type");
102
+ }
103
+ async function upsertPoints(points) {
104
+ const qdrant = getQdrantClient();
105
+ const { collectionName } = RAG_CONFIG.qdrant;
106
+ const localEmbeddings = RAG_CONFIG.embedding.url ? await embedLocally(points.map((point) => point.text)) : void 0;
107
+ const qdrantPoints = points.map((p, index) => ({
108
+ id: hashToUint(p.id),
109
+ vector: localEmbeddings?.[index] ?? {
110
+ text: p.text,
111
+ model: RAG_CONFIG.embedding.model
112
+ },
113
+ payload: p.payload
114
+ }));
115
+ await qdrant.upsert(collectionName, {
116
+ wait: true,
117
+ points: qdrantPoints
118
+ });
119
+ }
120
+ async function getExistingHashes(chunkIds) {
121
+ const qdrant = getQdrantClient();
122
+ const { collectionName } = RAG_CONFIG.qdrant;
123
+ const hashMap = /* @__PURE__ */ new Map();
124
+ if (chunkIds.length === 0)
125
+ return hashMap;
126
+ const numericIds = chunkIds.map((id) => hashToUint(id));
127
+ try {
128
+ const result = await qdrant.retrieve(collectionName, {
129
+ ids: numericIds,
130
+ with_payload: ["chunk_id", "content_hash"]
131
+ });
132
+ for (const point of result) {
133
+ const payload = point.payload;
134
+ if (payload?.chunk_id && payload?.content_hash) {
135
+ hashMap.set(payload.chunk_id, payload.content_hash);
136
+ }
137
+ }
138
+ } catch {
139
+ }
140
+ return hashMap;
141
+ }
142
+ async function search(queryText, options = {}) {
143
+ const qdrant = getQdrantClient();
144
+ const { collectionName } = RAG_CONFIG.qdrant;
145
+ const { topK = 10, filter } = options;
146
+ const filterConditions = [];
147
+ if (filter?.type) {
148
+ filterConditions.push({
149
+ key: "type",
150
+ match: { value: filter.type }
151
+ });
152
+ }
153
+ if (filter?.top_heading) {
154
+ filterConditions.push({
155
+ key: "top_heading",
156
+ match: { value: filter.top_heading }
157
+ });
158
+ }
159
+ if (filter?.content_type) {
160
+ if (Array.isArray(filter.content_type)) {
161
+ filterConditions.push({
162
+ key: "content_type",
163
+ match: { any: filter.content_type }
164
+ });
165
+ } else {
166
+ filterConditions.push({
167
+ key: "content_type",
168
+ match: { value: filter.content_type }
169
+ });
170
+ }
171
+ }
172
+ const query = RAG_CONFIG.embedding.url ? (await embedLocally([queryText]))[0] : { text: queryText, model: RAG_CONFIG.embedding.model };
173
+ const result = await qdrant.query(collectionName, {
174
+ query,
175
+ limit: topK,
176
+ with_payload: true,
177
+ filter: filterConditions.length > 0 ? { must: filterConditions } : void 0
178
+ });
179
+ return result.points.map((r) => {
180
+ const payload = r.payload;
181
+ return {
182
+ score: r.score,
183
+ text: payload.text,
184
+ title: payload.title,
185
+ url: payload.url,
186
+ heading_path: payload.heading_path,
187
+ type: payload.type,
188
+ content_type: payload.content_type,
189
+ doc_id: payload.doc_id
190
+ };
191
+ });
192
+ }
193
+ async function listAllChunkIds() {
194
+ const qdrant = getQdrantClient();
195
+ const { collectionName } = RAG_CONFIG.qdrant;
196
+ const chunkIds = [];
197
+ const pageSize = 256;
198
+ let nextOffset;
199
+ try {
200
+ do {
201
+ const result = await qdrant.scroll(collectionName, {
202
+ limit: pageSize,
203
+ offset: typeof nextOffset === "string" || typeof nextOffset === "number" ? nextOffset : void 0,
204
+ with_payload: ["chunk_id"],
205
+ with_vector: false
206
+ });
207
+ for (const point of result.points) {
208
+ const payload = point.payload;
209
+ if (payload?.chunk_id) {
210
+ chunkIds.push(payload.chunk_id);
211
+ }
212
+ }
213
+ nextOffset = result.next_page_offset;
214
+ } while (nextOffset !== null && nextOffset !== void 0);
215
+ } catch {
216
+ }
217
+ return chunkIds;
218
+ }
219
+ async function deletePointsByChunkIds(chunkIds) {
220
+ if (chunkIds.length === 0)
221
+ return;
222
+ const qdrant = getQdrantClient();
223
+ const { collectionName } = RAG_CONFIG.qdrant;
224
+ const numericIds = chunkIds.map((id) => hashToUint(id));
225
+ await qdrant.delete(collectionName, {
226
+ wait: true,
227
+ points: numericIds
228
+ });
229
+ }
230
+ async function getCollectionStats() {
231
+ const qdrant = getQdrantClient();
232
+ const { collectionName } = RAG_CONFIG.qdrant;
233
+ try {
234
+ const info = await qdrant.getCollection(collectionName);
235
+ return {
236
+ pointsCount: info.points_count ?? 0,
237
+ status: info.status
238
+ };
239
+ } catch {
240
+ return {
241
+ pointsCount: 0,
242
+ status: "not_found"
243
+ };
244
+ }
245
+ }
246
+ function hashToUint(str) {
247
+ let hash = 0;
248
+ for (let i = 0; i < str.length; i++) {
249
+ const char = str.codePointAt(i) ?? 0;
250
+ hash = (hash << 5) - hash + char;
251
+ hash = hash & hash;
252
+ }
253
+ return Math.abs(hash);
254
+ }
255
+
256
+ export {
257
+ RAG_CONFIG,
258
+ getQdrantClient,
259
+ deleteCollection,
260
+ ensureCollection,
261
+ upsertPoints,
262
+ getExistingHashes,
263
+ search,
264
+ listAllChunkIds,
265
+ deletePointsByChunkIds,
266
+ getCollectionStats
267
+ };
@@ -0,0 +1,42 @@
1
+ import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
+ }) : x)(function(x) {
11
+ if (typeof require !== "undefined") return require.apply(this, arguments);
12
+ throw Error('Dynamic require of "' + x + '" is not supported');
13
+ });
14
+ var __commonJS = (cb, mod) => function __require2() {
15
+ try {
16
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
17
+ } catch (e) {
18
+ throw mod = 0, e;
19
+ }
20
+ };
21
+ var __copyProps = (to, from, except, desc) => {
22
+ if (from && typeof from === "object" || typeof from === "function") {
23
+ for (let key of __getOwnPropNames(from))
24
+ if (!__hasOwnProp.call(to, key) && key !== except)
25
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
26
+ }
27
+ return to;
28
+ };
29
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
30
+ // If the importer is in node compatibility mode or this is not an ESM
31
+ // file that has been converted to a CommonJS file using a Babel-
32
+ // compatible transform (i.e. "__esModule" has not been set), then set
33
+ // "default" to the CommonJS "module.exports" for node compatibility.
34
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
35
+ mod
36
+ ));
37
+
38
+ export {
39
+ __require,
40
+ __commonJS,
41
+ __toESM
42
+ };
@@ -0,0 +1,77 @@
1
+ import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
2
+
3
+ // ../image-processing/dist/node.js
4
+ import sharp from "sharp";
5
+
6
+ // ../image-processing/dist/shared.js
7
+ var WEBP_HEADER_OFFSET = 8;
8
+ var RIFF_HEADER_LENGTH = 12;
9
+ function startsWithBytes(bytes, signature) {
10
+ return bytes.length >= signature.length && signature.every((byte, index) => bytes[index] === byte);
11
+ }
12
+ function startsWithAscii(bytes, value) {
13
+ if (bytes.length < value.length) {
14
+ return false;
15
+ }
16
+ for (let index = 0; index < value.length; index++) {
17
+ if (bytes[index] !== value.codePointAt(index)) {
18
+ return false;
19
+ }
20
+ }
21
+ return true;
22
+ }
23
+ function detectSupportedImageFormat(bytes) {
24
+ if (startsWithBytes(bytes, [255, 216, 255])) {
25
+ return "jpeg";
26
+ }
27
+ if (startsWithBytes(bytes, [137, 80, 78, 71, 13, 10, 26, 10])) {
28
+ return "png";
29
+ }
30
+ if (bytes.length >= RIFF_HEADER_LENGTH && startsWithAscii(bytes, "RIFF") && bytes[WEBP_HEADER_OFFSET] === "W".codePointAt(0) && bytes[WEBP_HEADER_OFFSET + 1] === "E".codePointAt(0) && bytes[WEBP_HEADER_OFFSET + 2] === "B".codePointAt(0) && bytes[WEBP_HEADER_OFFSET + 3] === "P".codePointAt(0)) {
31
+ return "webp";
32
+ }
33
+ return void 0;
34
+ }
35
+
36
+ // ../image-processing/dist/node.js
37
+ var DEFAULT_WEBP_QUALITY = 80;
38
+ function assertSupportedImage(bytes) {
39
+ const format = detectSupportedImageFormat(bytes);
40
+ if (format === void 0) {
41
+ throw new Error("Unsupported image signature.");
42
+ }
43
+ return format;
44
+ }
45
+ async function readImageMetadata(bytes) {
46
+ const format = assertSupportedImage(bytes);
47
+ const metadata = await sharp(bytes).metadata();
48
+ const dimensions = metadata.autoOrient;
49
+ if (!dimensions.width || !dimensions.height) {
50
+ throw new Error("Image dimensions could not be determined.");
51
+ }
52
+ return {
53
+ format,
54
+ height: dimensions.height,
55
+ width: dimensions.width
56
+ };
57
+ }
58
+ async function createWebpThumbnail(bytes, options) {
59
+ assertSupportedImage(bytes);
60
+ const output = await sharp(bytes).autoOrient().resize(options.width, options.height, {
61
+ fit: "cover",
62
+ kernel: sharp.kernel.lanczos3,
63
+ position: "centre"
64
+ }).webp({ quality: DEFAULT_WEBP_QUALITY }).toBuffer();
65
+ return new Uint8Array(output);
66
+ }
67
+ async function convertToWebp(bytes, options) {
68
+ assertSupportedImage(bytes);
69
+ const output = await sharp(bytes).autoOrient().webp({ quality: options?.quality ?? DEFAULT_WEBP_QUALITY }).toBuffer();
70
+ return new Uint8Array(output);
71
+ }
72
+
73
+ export {
74
+ readImageMetadata,
75
+ createWebpThumbnail,
76
+ convertToWebp
77
+ };
@@ -0,0 +1,102 @@
1
+ import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
2
+ import {
3
+ makeExportedAt,
4
+ withTransactionRetry
5
+ } from "./chunk-IWNZQXDG.js";
6
+
7
+ // ../application/dist/batch/articles-batch-service.js
8
+ var ArticlesBatchApplicationService = class {
9
+ commandRepository;
10
+ /**
11
+ * Creates a new ArticlesBatchApplicationService instance.
12
+ *
13
+ * @param commandRepository - The command repository for batch updates
14
+ */
15
+ constructor(commandRepository) {
16
+ this.commandRepository = commandRepository;
17
+ }
18
+ /**
19
+ * Resets articles for a new batch export.
20
+ *
21
+ * @param userId - The user ID for tenant isolation
22
+ * @returns The result containing finalized and marked counts
23
+ *
24
+ * @remarks
25
+ * Delegates to repository's resetStatus which performs two operations
26
+ * atomically within a single transaction:
27
+ * 1. Finalize: LAST_UPDATED -> EXPORTED (complete previous batch)
28
+ * 2. Mark: UNEXPORTED -> LAST_UPDATED (prepare current batch)
29
+ */
30
+ async resetArticles(userId) {
31
+ return this.commandRepository.resetStatus(userId, makeExportedAt());
32
+ }
33
+ /**
34
+ * Reverts articles from LAST_UPDATED back to UNEXPORTED.
35
+ *
36
+ * @param userId - The user ID for tenant isolation
37
+ * @returns The result containing the count of reverted articles
38
+ *
39
+ * @remarks
40
+ * Used to undo a batch marking if something went wrong.
41
+ * Only affects articles in LAST_UPDATED status.
42
+ */
43
+ async revertArticles(userId) {
44
+ return this.commandRepository.bulkUpdateStatus({
45
+ userId,
46
+ fromStatus: "LAST_UPDATED",
47
+ toStatus: "UNEXPORTED"
48
+ });
49
+ }
50
+ };
51
+
52
+ // ../infrastructure/dist/batch/articles-command-repository.js
53
+ function createArticlesCommandRepository(prisma) {
54
+ return {
55
+ async bulkUpdateStatus(params) {
56
+ const { userId, fromStatus, toStatus, exportedAt } = params;
57
+ const result = await prisma.article.updateMany({
58
+ where: {
59
+ userId,
60
+ status: fromStatus
61
+ },
62
+ data: {
63
+ status: toStatus,
64
+ ...exportedAt && { exportedAt }
65
+ }
66
+ });
67
+ return { count: result.count };
68
+ },
69
+ async resetStatus(userId, exportedAt) {
70
+ const [finalized, marked] = await withTransactionRetry(() => prisma.$transaction([
71
+ prisma.article.updateMany({
72
+ where: {
73
+ userId,
74
+ status: "LAST_UPDATED"
75
+ },
76
+ data: {
77
+ status: "EXPORTED",
78
+ exportedAt
79
+ }
80
+ }),
81
+ prisma.article.updateMany({
82
+ where: {
83
+ userId,
84
+ status: "UNEXPORTED"
85
+ },
86
+ data: {
87
+ status: "LAST_UPDATED"
88
+ }
89
+ })
90
+ ]));
91
+ return {
92
+ finalized: { count: finalized.count },
93
+ marked: { count: marked.count }
94
+ };
95
+ }
96
+ };
97
+ }
98
+
99
+ export {
100
+ ArticlesBatchApplicationService,
101
+ createArticlesCommandRepository
102
+ };
@@ -0,0 +1,85 @@
1
+ import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
2
+ import {
3
+ makeExportedAt,
4
+ withTransactionRetry
5
+ } from "./chunk-IWNZQXDG.js";
6
+
7
+ // ../application/dist/batch/notes-batch-service.js
8
+ var NotesBatchApplicationService = class {
9
+ commandRepository;
10
+ constructor(commandRepository) {
11
+ this.commandRepository = commandRepository;
12
+ }
13
+ /**
14
+ * Resets notes for a new batch export.
15
+ *
16
+ * @remarks
17
+ * Delegates to repository's resetStatus which performs two operations
18
+ * atomically within a single transaction.
19
+ */
20
+ async resetNotes(userId) {
21
+ return this.commandRepository.resetStatus(userId, makeExportedAt());
22
+ }
23
+ /**
24
+ * Reverts notes from LAST_UPDATED back to UNEXPORTED.
25
+ */
26
+ async revertNotes(userId) {
27
+ return this.commandRepository.bulkUpdateStatus({
28
+ userId,
29
+ fromStatus: "LAST_UPDATED",
30
+ toStatus: "UNEXPORTED"
31
+ });
32
+ }
33
+ };
34
+
35
+ // ../infrastructure/dist/batch/notes-command-repository.js
36
+ function createNotesCommandRepository(prisma) {
37
+ return {
38
+ async bulkUpdateStatus(params) {
39
+ const { userId, fromStatus, toStatus, exportedAt } = params;
40
+ const result = await prisma.note.updateMany({
41
+ where: {
42
+ userId,
43
+ status: fromStatus
44
+ },
45
+ data: {
46
+ status: toStatus,
47
+ ...exportedAt && { exportedAt }
48
+ }
49
+ });
50
+ return { count: result.count };
51
+ },
52
+ async resetStatus(userId, exportedAt) {
53
+ const [finalized, marked] = await withTransactionRetry(() => prisma.$transaction([
54
+ prisma.note.updateMany({
55
+ where: {
56
+ userId,
57
+ status: "LAST_UPDATED"
58
+ },
59
+ data: {
60
+ status: "EXPORTED",
61
+ exportedAt
62
+ }
63
+ }),
64
+ prisma.note.updateMany({
65
+ where: {
66
+ userId,
67
+ status: "UNEXPORTED"
68
+ },
69
+ data: {
70
+ status: "LAST_UPDATED"
71
+ }
72
+ })
73
+ ]));
74
+ return {
75
+ finalized: { count: finalized.count },
76
+ marked: { count: marked.count }
77
+ };
78
+ }
79
+ };
80
+ }
81
+
82
+ export {
83
+ NotesBatchApplicationService,
84
+ createNotesCommandRepository
85
+ };
@@ -0,0 +1,40 @@
1
+ import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
2
+
3
+ // src/lib/html-charset.ts
4
+ var CHARSET_ALIASES = {
5
+ eucjp: "euc-jp",
6
+ sjis: "shift_jis",
7
+ shiftjis: "shift_jis",
8
+ utf8: "utf-8",
9
+ xeucjp: "euc-jp",
10
+ xsjis: "shift_jis"
11
+ };
12
+ function normalizeCharset(charset) {
13
+ const trimmed = charset.trim().replaceAll(/^['"]|['"]$/gu, "");
14
+ const normalized = trimmed.toLowerCase().replaceAll(/[^a-z0-9]/gu, "");
15
+ return CHARSET_ALIASES[normalized] ?? trimmed.toLowerCase();
16
+ }
17
+ function detectHtmlCharset(headers, bytes) {
18
+ const contentType = headers.get("content-type") ?? "";
19
+ const headerMatch = /charset\s*=\s*["']?([^"'\s;]+)/iu.exec(contentType);
20
+ if (headerMatch) return normalizeCharset(headerMatch[1]);
21
+ const preview = new TextDecoder("windows-1252").decode(
22
+ bytes.subarray(0, Math.min(4096, bytes.length))
23
+ );
24
+ const metaCharset = /<meta\s{1,200}charset=["']?([^"'\s>]+)/iu.exec(preview);
25
+ if (metaCharset) return normalizeCharset(metaCharset[1]);
26
+ const metaHttpEquiv = /<meta[^>]{1,500}http-equiv=["']?Content-Type["']?[^>]{1,500}content=["'][^"']{0,500}charset=([^"'\s;]+)/iu.exec(
27
+ preview
28
+ );
29
+ if (metaHttpEquiv) return normalizeCharset(metaHttpEquiv[1]);
30
+ return "utf-8";
31
+ }
32
+ function decodeHtml(headers, bytes) {
33
+ return new TextDecoder(detectHtmlCharset(headers, bytes), {
34
+ fatal: false
35
+ }).decode(bytes);
36
+ }
37
+
38
+ export {
39
+ decodeHtml
40
+ };