vivth 0.11.2 → 1.0.1

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 (106) hide show
  1. package/README.md +2157 -69
  2. package/README.src.md +35 -0
  3. package/bun.lock +57 -3
  4. package/dev/index.mjs +24 -25
  5. package/index.mjs +51 -29
  6. package/package.json +11 -7
  7. package/src/bundler/CompileMJS.mjs +110 -0
  8. package/src/bundler/EsBundler.mjs +79 -0
  9. package/src/class/Console.mjs +62 -0
  10. package/src/class/Derived.mjs +36 -25
  11. package/src/class/Effect.mjs +106 -0
  12. package/src/class/EnvSignal.mjs +88 -0
  13. package/src/class/EventSignal.mjs +200 -0
  14. package/src/class/ListDerived.mjs +39 -0
  15. package/src/class/ListSignal.mjs +256 -0
  16. package/src/class/Paths.mjs +70 -0
  17. package/src/class/QChannel.mjs +184 -0
  18. package/src/class/SafeExit.mjs +131 -0
  19. package/src/class/Setup.mjs +73 -0
  20. package/src/class/Signal.mjs +152 -54
  21. package/src/class/WorkerMainThread.mjs +328 -0
  22. package/src/class/WorkerResult.mjs +30 -0
  23. package/src/class/WorkerThread.mjs +151 -0
  24. package/src/common/Base64URL.mjs +26 -0
  25. package/src/common/EventNameSpace.mjs +8 -0
  26. package/src/common/eventObjects.mjs +5 -0
  27. package/src/common/lazie.mjs +3 -0
  28. package/src/doc/JSautoDOC.mjs +386 -0
  29. package/src/doc/parsedFile.mjs +537 -0
  30. package/src/function/CreateImmutable.mjs +64 -0
  31. package/src/function/EventCheck.mjs +27 -0
  32. package/src/function/EventObject.mjs +21 -0
  33. package/src/function/IsAsync.mjs +23 -0
  34. package/src/function/LazyFactory.mjs +71 -0
  35. package/src/function/Timeout.mjs +23 -0
  36. package/src/function/Try.mjs +64 -0
  37. package/src/function/TryAsync.mjs +15 -4
  38. package/src/function/TrySync.mjs +9 -4
  39. package/src/function/TsToMjs.mjs +67 -0
  40. package/src/function/WriteFileSafe.mjs +37 -0
  41. package/src/types/{AnyButUndefined.type.mjs → AnyButUndefined.mjs} +1 -0
  42. package/src/types/ExtnameType.mjs +6 -0
  43. package/src/types/IsListSignal.mjs +6 -0
  44. package/src/types/ListArg.mjs +6 -0
  45. package/src/types/MutationType.mjs +8 -0
  46. package/src/types/QCBFIFOReturn.mjs +6 -0
  47. package/src/types/QCBReturn.mjs +6 -0
  48. package/tsconfig.json +3 -3
  49. package/types/dev/index.d.mts +1 -0
  50. package/types/index.d.mts +34 -8
  51. package/types/src/bundler/A.d.mts +1 -0
  52. package/types/src/bundler/CompileMJS.d.mts +8 -0
  53. package/types/src/bundler/EsBundler.d.mts +7 -0
  54. package/types/src/class/Console.d.mts +40 -0
  55. package/types/src/class/Derived.d.mts +21 -9
  56. package/types/src/class/Effect.d.mts +77 -0
  57. package/types/src/class/EnvSignal.d.mts +47 -0
  58. package/types/src/class/EventSignal.d.mts +145 -0
  59. package/types/src/class/ListDerived.d.mts +35 -0
  60. package/types/src/class/ListSignal.d.mts +150 -0
  61. package/types/src/class/Paths.d.mts +50 -0
  62. package/types/src/class/QChannel.d.mts +115 -0
  63. package/types/src/class/SafeExit.d.mts +76 -0
  64. package/types/src/class/Setup.d.mts +76 -0
  65. package/types/src/class/Signal.d.mts +105 -26
  66. package/types/src/class/WorkerMainThread.d.mts +149 -0
  67. package/types/src/class/WorkerResult.d.mts +25 -0
  68. package/types/src/class/WorkerThread.d.mts +70 -0
  69. package/types/src/common/Base64URL.d.mts +1 -0
  70. package/types/src/common/EventNameSpace.d.mts +6 -0
  71. package/types/src/common/eventObjects.d.mts +3 -0
  72. package/types/src/common/lazie.d.mts +1 -0
  73. package/types/src/doc/JSautoDOC.d.mts +76 -0
  74. package/types/src/doc/parsedFile.d.mts +154 -0
  75. package/types/src/function/CreateImmutable.d.mts +3 -0
  76. package/types/src/function/EventCheck.d.mts +2 -0
  77. package/types/src/function/EventObject.d.mts +4 -0
  78. package/types/src/function/IsAsync.d.mts +1 -0
  79. package/types/src/function/LazyFactory.d.mts +4 -0
  80. package/types/src/function/Timeout.d.mts +1 -0
  81. package/types/src/function/Try.d.mts +1 -0
  82. package/types/src/function/TsToMjs.d.mts +4 -0
  83. package/types/src/function/WriteFileSafe.d.mts +2 -0
  84. package/types/src/types/{AnyButUndefined.type.d.mts → AnyButUndefined.d.mts} +3 -0
  85. package/types/src/types/ExtnameType.d.mts +4 -0
  86. package/types/src/types/IsListSignal.d.mts +4 -0
  87. package/types/src/types/ListArg.d.mts +4 -0
  88. package/types/src/types/MutationType.d.mts +5 -0
  89. package/types/src/types/QCBFIFOReturn.d.mts +4 -0
  90. package/types/src/types/QCBReturn.d.mts +7 -0
  91. package/src/class/$.mjs +0 -68
  92. package/src/class/PingFIFO.mjs +0 -78
  93. package/src/class/PingUnique.mjs +0 -84
  94. package/src/class/Q.mjs +0 -98
  95. package/src/class/QFIFO.mjs +0 -66
  96. package/src/class/QUnique.mjs +0 -75
  97. package/src/common.mjs +0 -16
  98. package/src/function/NewQBlock.mjs +0 -39
  99. package/types/src/class/$.d.mts +0 -40
  100. package/types/src/class/PingFIFO.d.mts +0 -57
  101. package/types/src/class/PingUnique.d.mts +0 -48
  102. package/types/src/class/Q.d.mts +0 -63
  103. package/types/src/class/QFIFO.d.mts +0 -47
  104. package/types/src/class/QUnique.d.mts +0 -46
  105. package/types/src/common.d.mts +0 -2
  106. package/types/src/function/NewQBlock.d.mts +0 -1
@@ -0,0 +1,386 @@
1
+ // @ts-check
2
+
3
+ import { extname, join } from 'node:path';
4
+ import { readFile, stat } from 'node:fs/promises';
5
+
6
+ import chokidar from 'chokidar';
7
+ import { EventSignal } from '../class/EventSignal.mjs';
8
+ import { QChannel } from '../class/QChannel.mjs';
9
+ import { parsedFile } from './parsedFile.mjs';
10
+ import { SafeExit } from '../class/SafeExit.mjs';
11
+ import { Effect } from '../class/Effect.mjs';
12
+ import { Paths } from '../class/Paths.mjs';
13
+ import { Signal } from '../class/Signal.mjs';
14
+ import { LazyFactory } from '../function/LazyFactory.mjs';
15
+ import { TryAsync } from '../function/TryAsync.mjs';
16
+ import { Timeout } from '../function/Timeout.mjs';
17
+ import { Console } from '../class/Console.mjs';
18
+ import { TsToMjs } from '../function/TsToMjs.mjs';
19
+ import { WriteFileSafe } from '../function/WriteFileSafe.mjs';
20
+
21
+ /**
22
+ * @typedef {import('fs').Stats} Stats
23
+ */
24
+ /**
25
+ * @type {BufferEncoding}
26
+ */
27
+ const encoding = 'utf-8';
28
+ const readmesrcname = 'README.src.md';
29
+ /**
30
+ * @type {Set<import('../types/ExtnameType.mjs').ExtnameType>}
31
+ */
32
+ const acceptableExt = new Set(['.mjs', '.mts', '.ts']);
33
+
34
+ /**
35
+ * @description
36
+ * - class for auto documenting mjs package/project, using jsdoc;
37
+ * - this autodocumenter uses [chokidar](https://npmjs.com/package/chokidar) under the hood;
38
+ * - this class also is used to generate this `README.md`;
39
+ * - behaviours:
40
+ * >1) export all named exported 'const'|'function'|'async function'|'class', alphanumeric name, started with Capital letter, same name with fileName on `options.pahts.file`;
41
+ * >2) declare typedef of existing typedef with alphanumeric name, started with Capital letter, same name with fileName, and have no valid export like on point <sup>1</sup> on `options.pahts.file`;
42
+ * >3) create `README.md` based on, `options.paths.dir` and `README.src.md`;
43
+ * >4) extract `"at"description` jsdoc:
44
+ * >>- on static/prop that have depths, all of children should have `"at"static`/`"at"instance` `nameOfImmediateParent`, same block but before `"at"description` comment line;
45
+ * >>- `"at"description` are treated as plain `markdown`;
46
+ * >>- first `"at"${string}` after `"at"description` until `"at"example` will be treated as `javascript` comment block on the `markdown`;
47
+ * >>- `"at"example` are treated as `javascript` block on the `markdown` file, and should be placed last on the same comment block;
48
+ * >>- you can always look at `vivth/src` files to check how the source, and the `README.md` and `index.mjs` is documentation/generation results;
49
+ */
50
+ export class JSautoDOC {
51
+ /**
52
+ * @type {JSautoDOC}
53
+ */
54
+ static #instance = undefined;
55
+ /**
56
+ * @description
57
+ * @param {Object} [options]
58
+ * @param {Object} [options.paths]
59
+ * @param {string} [options.paths.file]
60
+ * - entry point;
61
+ * @param {string} [options.paths.readMe]
62
+ * - readme target;
63
+ * @param {string} [options.paths.dir]
64
+ * - source directory;
65
+ * @param {string} [options.copyright]
66
+ * @param {string} [options.tableOfContentTitle]
67
+ * @param {import('chokidar').ChokidarOptions} [options.option]
68
+ * - ChokidarOptions;
69
+ * @example
70
+ * import { Console } from '../src/class/Console.mjs';
71
+ * import { Setup } from '../src/class/Setup.mjs';
72
+ * import { JSautoDOC } from '../src/doc/JSautoDOC.mjs';
73
+ *
74
+ * const { paths, safeExit } = Setup;
75
+ *
76
+ * new paths({
77
+ * root: process?.env?.INIT_CWD ?? process?.cwd(),
78
+ * });
79
+ *
80
+ * new safeExit({
81
+ * exitEventNames: ['SIGINT', 'SIGTERM', 'exit'],
82
+ * exitCallbackListeners: (eventName) => {
83
+ * process.once(eventName, function () {
84
+ * safeExit.instance.exiting.correction(true);
85
+ * Console.log(`safe exit via "${eventName}"`);
86
+ * });
87
+ * },
88
+ * });
89
+ *
90
+ * new JSautoDOC({
91
+ * paths: { dir: 'src', file: 'index.mjs', readMe: 'README.md' },
92
+ * copyright: 'this library is made and distributed under MIT license;',
93
+ * tableOfContentTitle: 'list of exported API and typehelpers',
94
+ * });
95
+ *
96
+ */
97
+ constructor({
98
+ paths = { dir: './src', file: './index.mjs', readMe: './README.md' },
99
+ tableOfContentTitle = 'exported-api-and-type-list',
100
+ copyright = '',
101
+ option = {},
102
+ } = {}) {
103
+ if (JSautoDOC.#instance instanceof JSautoDOC) {
104
+ return this;
105
+ }
106
+ JSautoDOC.#instance = this;
107
+ this.#tableOfContentTitle = tableOfContentTitle;
108
+ this.#paths = paths;
109
+ this.#copyright = copyright;
110
+ const rootPath = Paths.root;
111
+ const watchpath = join(rootPath, this.#paths.dir);
112
+ const watcher = chokidar.watch(watchpath, option);
113
+ const watcherReadme = chokidar.watch(join(rootPath, readmesrcname), option);
114
+ /**
115
+ * @type {(eventName: 'add'|'change'|'unlink', path: string, stats?: import('fs').Stats) => void}
116
+ */
117
+ const listener = (eventName, path, stats) => {
118
+ const ext = extname(path);
119
+ if (
120
+ !acceptableExt.has(
121
+ // @ts-expect-error
122
+ ext
123
+ )
124
+ ) {
125
+ return;
126
+ }
127
+ if (ext !== '.mjs') {
128
+ TsToMjs(path, {
129
+ encoding,
130
+ });
131
+ return;
132
+ }
133
+ switch (eventName) {
134
+ case 'add':
135
+ case 'change':
136
+ this.#addHandler(eventName, path, stats);
137
+ break;
138
+ case 'unlink':
139
+ this.#removeHandler(eventName, path, stats);
140
+ break;
141
+ }
142
+ };
143
+ watcher.on('all', listener);
144
+ watcherReadme.on('all', this.#readMeListener);
145
+ SafeExit.instance.addCallback(async () => {
146
+ watcher.close();
147
+ watcherReadme.close();
148
+ watcher.removeAllListeners();
149
+ watcherReadme.removeAllListeners();
150
+ watcher.removeListener('all', listener);
151
+ watcherReadme.removeAllListeners(this.#readMeListener);
152
+ watcherReadme.removeListener('all', this.#readMeListener);
153
+ });
154
+ }
155
+ /**
156
+ * @type {string}
157
+ */
158
+ #copyright = undefined;
159
+ /**
160
+ * @type {{
161
+ * file?: string;
162
+ * readMe?: string;
163
+ * dir?: string;
164
+ * }}
165
+ */
166
+ #paths;
167
+ /**
168
+ * @type {string}
169
+ */
170
+ #tableOfContentTitle;
171
+ /**
172
+ * @param {string} _eventName
173
+ * @param {string} path_
174
+ * @param {Stats} stats
175
+ * @returns {Promise<void>}
176
+ */
177
+ #readMeListener = async (_eventName, path_, stats) => {
178
+ if (!stats.isFile()) {
179
+ return;
180
+ }
181
+ const content = await readFile(path_, { encoding });
182
+ this.#readMESRCContent.value = content;
183
+ };
184
+ /**
185
+ * @type {Signal<Set<string>>}
186
+ */
187
+ #filePaths = LazyFactory(() => new Signal(new Set()));
188
+ /**
189
+ * @type {Signal<string>}
190
+ */
191
+ #readMESRCContent = LazyFactory(() => new Signal(undefined));
192
+ /**
193
+ * @type {QChannel<JSautoDOC>}
194
+ */
195
+ #modQ = new QChannel();
196
+ #generatedREADME_md = new Effect(async ({ subscribe }) => {
197
+ this.#modQ.callback(this, async ({ isLastOnQ }) => {
198
+ if (!isLastOnQ) {
199
+ return;
200
+ }
201
+ await Timeout(1000);
202
+ const contentSRC = subscribe(this.#readMESRCContent).value;
203
+ const filepaths = subscribe(this.#filePaths).value;
204
+ if (!contentSRC || !filepaths) {
205
+ return;
206
+ }
207
+ const { readme, mjsFile } = await this.#generateFromSRC(contentSRC, filepaths);
208
+ const readmePath = join(Paths.root, this.#paths.readMe);
209
+ const mjsFilePath = join(Paths.root, this.#paths.file);
210
+ const [[, errorWriteReadme], [, errorWriteMjsFile]] = await Promise.all([
211
+ WriteFileSafe(readmePath, readme, { encoding }),
212
+ WriteFileSafe(join(Paths.root, this.#paths.file), mjsFile, { encoding }),
213
+ ]);
214
+ if (!errorWriteMjsFile) {
215
+ Console.info({ message: `successfully generate: '${mjsFilePath}'` });
216
+ } else {
217
+ Console.error({ message: `unable to generate: '${mjsFilePath}';`, errorWriteMjsFile });
218
+ }
219
+ if (!errorWriteReadme) {
220
+ Console.info({ message: `successfully generate: '${readmePath}'` });
221
+ } else {
222
+ Console.error({ message: `unable to generate: '${readmePath}';`, errorWriteReadme });
223
+ }
224
+ });
225
+ });
226
+ /**
227
+ * @param {string} string
228
+ * @returns {string}
229
+ */
230
+ #generateJSDOCFromstring = (string) => {
231
+ return `\n/**\n * automatically generated by \`vivth.MJSautoDOC\`\n * @copyright\n${string.replace(
232
+ /^/gm,
233
+ ' * '
234
+ )}\n */\n`;
235
+ };
236
+ /**
237
+ * @typedef { Object } generatedFromSRC
238
+ * @property { string } readme
239
+ * @property { string } mjsFile
240
+ */
241
+ /**
242
+ * @param {string} contentSRC
243
+ * @param {Set<string>} filepaths
244
+ * @returns {Promise<generatedFromSRC>}
245
+ */
246
+ #generateFromSRC = async (contentSRC, filepaths) => {
247
+ const tableID = this.#tableOfContentTitle.replace(/\s+/g, '-').toLowerCase();
248
+ const tableOfContent = [];
249
+ const apiDocuments = [];
250
+ const mjsMain = ['// @ts-check', this.#generateJSDOCFromstring(this.#copyright)];
251
+ const mjsTypes = [];
252
+ for await (const path_ of filepaths) {
253
+ const {
254
+ documented,
255
+ hasValidExportObject,
256
+ path: { relative: relativePath },
257
+ baseName: { noExt },
258
+ } = (await this.#parsedFilesRef.get(path_)).value;
259
+ if (hasValidExportObject) {
260
+ mjsMain.push(
261
+ `export { ${noExt} } from '${
262
+ relativePath.startsWith('.') ? relativePath : `./${relativePath}`
263
+ }';`
264
+ );
265
+ }
266
+ const currentDescription = [];
267
+ const { readme, typedef } = documented;
268
+ const [typedefString, error] = await TryAsync(async () => {
269
+ if (hasValidExportObject) {
270
+ throw new Error('');
271
+ }
272
+ const result = await typedef();
273
+ return result;
274
+ });
275
+ if (!error && typedefString) {
276
+ mjsTypes.push(typedefString.module);
277
+ const nameVarID = noExt.toLowerCase();
278
+ tableOfContent.push(`[${noExt}](#${nameVarID})`);
279
+ apiDocuments.push(
280
+ `<h2 id="${nameVarID}">${noExt}</h2>\n\n- jsdoc types:\n\n\`\`\`js\n${
281
+ typedefString.readme
282
+ }\n\`\`\`\n*) <sub>[go to ${this.#tableOfContentTitle}](#${tableID})</sub>`
283
+ );
284
+ }
285
+ readme.forEach((ref) => {
286
+ const {
287
+ // fullDescription,
288
+ // instanceOrStatic,
289
+ // namedVar,
290
+ // typeOfVar,
291
+ parsedFullDescription,
292
+ reference,
293
+ } = ref;
294
+ const { description, jsPreview } = parsedFullDescription;
295
+ currentDescription.push(`\n#### reference:${reference}\n${description}\n${jsPreview}`);
296
+ });
297
+ if (hasValidExportObject) {
298
+ const nameVarID = noExt.toLowerCase();
299
+ tableOfContent.push(`[${noExt}](#${noExt.toLowerCase()})`);
300
+ apiDocuments.push(
301
+ `<h2 id="${nameVarID}">${noExt}</h2>\n\n-current-description-to-replace-\n\n*) <sub>[go to ${
302
+ this.#tableOfContentTitle
303
+ }](#${tableID})</sub>`.replace(
304
+ '-current-description-to-replace-',
305
+ currentDescription.join('\n')
306
+ )
307
+ );
308
+ }
309
+ }
310
+ const tableOfContentString = `<h2 id="${this.#tableOfContentTitle
311
+ .replace(/\s+/g, '-')
312
+ .toLowerCase()}">${
313
+ this.#tableOfContentTitle
314
+ }</h2>\n\n - -table-of-content-to-replace-\n\n-api-document-to-replace-`
315
+ .replace('-table-of-content-to-replace-', tableOfContent.join('\n - '))
316
+ .replace('-api-document-to-replace-', apiDocuments.join('\n\n'));
317
+ return {
318
+ mjsFile: [...mjsMain, ...mjsTypes].join('\n'),
319
+ readme: `${contentSRC}\n${tableOfContentString}`,
320
+ };
321
+ };
322
+ #parsedFilesRef = LazyFactory(() => {
323
+ const prefix = 'parsedFiles:';
324
+ return {
325
+ /**
326
+ * @param {string} path__
327
+ * @returns {Promise<Signal<parsedFile>>}
328
+ */
329
+ get: async (path__) => {
330
+ const dispatch = (await EventSignal.get(`${prefix}${path__}`, false)).dispatch;
331
+ // @ts-expect-error
332
+ return dispatch;
333
+ },
334
+ /**
335
+ * @param {string} path__
336
+ * @returns {Promise<void>}
337
+ */
338
+ unRef: async (path__) => {
339
+ const parsedFile = await this.#parsedFilesRef.get(`${prefix}${path__}`);
340
+ parsedFile.remove.ref();
341
+ },
342
+ };
343
+ });
344
+ /**
345
+ * @type {(eventName: 'add'|'change', path: string, stats?: import('fs').Stats) => void}
346
+ */
347
+ #addHandler = (eventName, path__, _stats) => {
348
+ TryAsync(async () => {
349
+ const dispatch = await this.#parsedFilesRef.get(path__);
350
+ if (!(await stat(path__)).isFile()) {
351
+ return;
352
+ }
353
+ dispatch.value = new parsedFile(path__, encoding);
354
+ dispatch.subscribers.notify();
355
+ this.#filePaths.subscribers.notify(async ({ signalInstance }) => {
356
+ Console.info({ [eventName]: path__ });
357
+ signalInstance.value.add(path__);
358
+ });
359
+ }).then(([_, error]) => {
360
+ if (!error) {
361
+ return;
362
+ }
363
+ Console.error(error);
364
+ });
365
+ };
366
+ /**
367
+ * @type {(eventName: 'unlink', path: string, stats?: import('fs').Stats) => void}
368
+ */
369
+ #removeHandler = (eventName, path__, _stats) => {
370
+ TryAsync(async () => {
371
+ if (!(await stat(path__)).isFile()) {
372
+ return true;
373
+ }
374
+ return false;
375
+ }).then(([res, error]) => {
376
+ if (res && !error) {
377
+ return;
378
+ }
379
+ this.#filePaths.subscribers.notify(async ({ signalInstance }) => {
380
+ Console.warn({ [eventName]: path__ });
381
+ signalInstance.value.delete(path__);
382
+ this.#parsedFilesRef.unRef(path__);
383
+ });
384
+ });
385
+ };
386
+ }