@awesomeness-js/server 1.1.10 → 1.1.11

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 (52) hide show
  1. package/build/build.js +4 -1
  2. package/build/postBuild.js +4 -1
  3. package/index.js +6 -4
  4. package/package.json +1 -1
  5. package/src/componentAndPageMemory.js +269 -0
  6. package/src/componentDependencies.js +9 -19
  7. package/src/extractUiComponentRefs.js +55 -0
  8. package/src/fetchPage.js +79 -4
  9. package/src/resolveRealCasePath.js +0 -4
  10. package/tests/componentAndPageMemory.test.js +171 -0
  11. package/tests/fetchPage.test.js +257 -0
  12. package/tests/fixtures/site-and-components/components/card/index.js +88 -0
  13. package/tests/fixtures/site-and-components/components/cardMain/index.js +86 -0
  14. package/tests/fixtures/site-and-components/components/cardMount/index.js +86 -0
  15. package/tests/fixtures/site-and-components/components/pageInit/index.js +86 -0
  16. package/tests/fixtures/site-and-components/components/pageScript/index.js +86 -0
  17. package/tests/fixtures/site-and-components/components/pageWidget/index.js +86 -0
  18. package/tests/fixtures/site-and-components/sites/site-a/pages/home/init.js +78 -0
  19. package/tests/fixtures/site-and-components/sites/site-a/pages/home/js/index.js +60 -0
  20. package/tests/setup.js +1 -0
  21. package/tsconfig.json +18 -0
  22. package/types/errors.d.ts +0 -0
  23. package/types/index.d.ts +109 -0
  24. package/types/src/applicationMap.d.ts +1 -0
  25. package/types/src/awesomenessNormalizeRequest.d.ts +27 -0
  26. package/types/src/brotliJsonResponse.d.ts +10 -0
  27. package/types/src/checkAccess.d.ts +5 -0
  28. package/types/src/componentAndPageMemory.d.ts +75 -0
  29. package/types/src/componentDependencies.d.ts +6 -0
  30. package/types/src/config.d.ts +7 -0
  31. package/types/src/extractUiComponentRefs.d.ts +5 -0
  32. package/types/src/fetchPage.d.ts +4 -0
  33. package/types/src/getConfig.d.ts +2 -0
  34. package/types/src/getMD.d.ts +1 -0
  35. package/types/src/init.d.ts +2 -0
  36. package/types/src/koa/attachAwesomenessRequest.d.ts +1 -0
  37. package/types/src/koa/cors.d.ts +1 -0
  38. package/types/src/koa/errorHandler.d.ts +1 -0
  39. package/types/src/koa/finalFormat.d.ts +1 -0
  40. package/types/src/koa/jsonBodyParser.d.ts +1 -0
  41. package/types/src/koa/routeRequest.d.ts +1 -0
  42. package/types/src/koa/serverUp.d.ts +1 -0
  43. package/types/src/koa/staticFiles.d.ts +1 -0
  44. package/types/src/koa/timeout.d.ts +1 -0
  45. package/types/src/pageInfo.d.ts +8 -0
  46. package/types/src/reRoute.d.ts +7 -0
  47. package/types/src/resolveRealCasePath.d.ts +1 -0
  48. package/types/src/specialPaths.d.ts +3 -0
  49. package/types/src/start.d.ts +1 -0
  50. package/types/src/validateRequest.d.ts +2 -0
  51. package/types/src/ws/handlers.d.ts +4 -0
  52. package/types/src/ws/index.d.ts +1 -0
package/build/build.js CHANGED
@@ -3,5 +3,8 @@ import { build } from '@awesomeness-js/utils';
3
3
  build({
4
4
  src: './src',
5
5
  dest: './index.js',
6
- dts: false
6
+ dts: false,
7
+ ignore: [
8
+ '*.test.js',
9
+ ]
7
10
  });
@@ -3,5 +3,8 @@ import { build } from '@awesomeness-js/utils';
3
3
  build({
4
4
  src: './src',
5
5
  dest: './types/index.d.ts',
6
- dts: true
6
+ dts: true,
7
+ ignore: [
8
+ '*.test.js',
9
+ ]
7
10
  });
package/index.js CHANGED
@@ -7,8 +7,10 @@ import _applicationMap from './src/applicationMap.js';
7
7
  import _awesomenessNormalizeRequest from './src/awesomenessNormalizeRequest.js';
8
8
  import _brotliJsonResponse from './src/brotliJsonResponse.js';
9
9
  import _checkAccess from './src/checkAccess.js';
10
+ import _componentAndPageMemory from './src/componentAndPageMemory.js';
10
11
  import _componentDependencies from './src/componentDependencies.js';
11
12
  import _config from './src/config.js';
13
+ import _extractUiComponentRefs from './src/extractUiComponentRefs.js';
12
14
  import _fetchPage from './src/fetchPage.js';
13
15
  import _getConfig from './src/getConfig.js';
14
16
  import _getMD from './src/getMD.js';
@@ -35,8 +37,10 @@ export { _applicationMap as applicationMap };
35
37
  export { _awesomenessNormalizeRequest as awesomenessNormalizeRequest };
36
38
  export { _brotliJsonResponse as brotliJsonResponse };
37
39
  export { _checkAccess as checkAccess };
40
+ export { _componentAndPageMemory as componentAndPageMemory };
38
41
  export { _componentDependencies as componentDependencies };
39
42
  export { _config as config };
43
+ export { _extractUiComponentRefs as extractUiComponentRefs };
40
44
  export { _fetchPage as fetchPage };
41
45
  export { _getConfig as getConfig };
42
46
  export { _getMD as getMD };
@@ -71,18 +75,16 @@ export default {
71
75
  awesomenessNormalizeRequest: _awesomenessNormalizeRequest,
72
76
  brotliJsonResponse: _brotliJsonResponse,
73
77
  checkAccess: _checkAccess,
78
+ componentAndPageMemory: _componentAndPageMemory,
74
79
  componentDependencies: _componentDependencies,
75
80
  config: _config,
81
+ extractUiComponentRefs: _extractUiComponentRefs,
76
82
  fetchPage: _fetchPage,
77
83
  getConfig: _getConfig,
78
84
  getMD: _getMD,
79
85
  init: _init,
80
86
  pageInfo: _pageInfo,
81
87
  reRoute: _reRoute,
82
- /**
83
- * Case-insensitive real path resolver.
84
- * Works correctly on Windows, macOS, and Linux/Docker.
85
- */
86
88
  resolveRealCasePath: _resolveRealCasePath,
87
89
  specialPaths: _specialPaths,
88
90
  start: _start,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awesomeness-js/server",
3
- "version": "1.1.10",
3
+ "version": "1.1.11",
4
4
  "description": "Awesomeness Multi-Site Server",
5
5
  "author": "Scott Forte",
6
6
  "type": "module",
@@ -0,0 +1,269 @@
1
+ import { createHash } from "crypto";
2
+ import { readFileSync, statSync } from "fs";
3
+ import extractUiComponentRefs from "./extractUiComponentRefs.js";
4
+
5
+ const FILE_CACHE_LIMIT = 2000;
6
+ const REFS_CACHE_LIMIT = 5000;
7
+
8
+ const fileCache = new Map();
9
+ const refsCache = new Map();
10
+
11
+ function toMB(bytes) {
12
+
13
+ return Number((bytes / (1024 * 1024)).toFixed(6));
14
+
15
+ }
16
+
17
+ function toGB(bytes) {
18
+
19
+ return Number((bytes / (1024 * 1024 * 1024)).toFixed(6));
20
+
21
+ }
22
+
23
+ function withMBGB(bytes) {
24
+
25
+ return {
26
+ bytes,
27
+ mb: toMB(bytes),
28
+ gb: toGB(bytes),
29
+ };
30
+
31
+ }
32
+
33
+ function pruneCache(cache, limit) {
34
+
35
+ if (cache.size <= limit) {
36
+
37
+ return;
38
+
39
+ }
40
+
41
+ const deleteCount = cache.size - limit;
42
+ let i = 0;
43
+
44
+ for (const key of cache.keys()) {
45
+
46
+ cache.delete(key);
47
+ i += 1;
48
+
49
+ if (i >= deleteCount) {
50
+
51
+ break;
52
+
53
+ }
54
+
55
+ }
56
+
57
+ }
58
+
59
+ function hashContent(content) {
60
+
61
+ return createHash("sha1").update(content).digest("hex");
62
+
63
+ }
64
+
65
+ function refsCacheKey(content, options) {
66
+
67
+ const {
68
+ namespace = "ui",
69
+ includeCall = true,
70
+ includeDotAccess = false,
71
+ cacheContext = "",
72
+ } = options || {};
73
+ const contentHash = hashContent(content);
74
+ const contextPart = cacheContext ? `${cacheContext}|` : "";
75
+
76
+ return `${contextPart}${namespace}|${includeCall}|${includeDotAccess}|${contentHash}`;
77
+
78
+ }
79
+
80
+ function refsCacheKeyFromFile(filePath, fileMeta, options = {}) {
81
+
82
+ const {
83
+ namespace = "ui",
84
+ includeCall = true,
85
+ includeDotAccess = false,
86
+ cacheContext = `file:${filePath}`,
87
+ } = options || {};
88
+ const contextPart = cacheContext ? `${cacheContext}|` : "";
89
+
90
+ return `${contextPart}${namespace}|${includeCall}|${includeDotAccess}|mtime:${fileMeta.mtimeMs}|size:${fileMeta.size}`;
91
+
92
+ }
93
+
94
+ function getFileCacheEntry(filePath) {
95
+
96
+ const stat = statSync(filePath);
97
+ const cacheEntry = fileCache.get(filePath);
98
+
99
+ if (
100
+ cacheEntry &&
101
+ cacheEntry.mtimeMs === stat.mtimeMs &&
102
+ cacheEntry.size === stat.size
103
+ ) {
104
+
105
+ return cacheEntry;
106
+
107
+ }
108
+
109
+ const content = readFileSync(filePath, "utf-8");
110
+ const nextEntry = {
111
+ mtimeMs: stat.mtimeMs,
112
+ size: stat.size,
113
+ content,
114
+ };
115
+
116
+ fileCache.set(filePath, nextEntry);
117
+ pruneCache(fileCache, FILE_CACHE_LIMIT);
118
+
119
+ return nextEntry;
120
+
121
+ }
122
+
123
+ export function readFileMemoized(filePath) {
124
+
125
+ return getFileCacheEntry(filePath).content;
126
+
127
+ }
128
+
129
+ export function extractUiRefsMemoized(content, options = {}) {
130
+
131
+ if (typeof content !== "string" || !content.length) {
132
+
133
+ return [];
134
+
135
+ }
136
+
137
+ const key = refsCacheKey(content, options);
138
+ const existing = refsCache.get(key);
139
+
140
+ if (existing) {
141
+
142
+ return existing;
143
+
144
+ }
145
+
146
+ const refs = extractUiComponentRefs(content, options);
147
+
148
+ refsCache.set(key, refs);
149
+ pruneCache(refsCache, REFS_CACHE_LIMIT);
150
+
151
+ return refs;
152
+
153
+ }
154
+
155
+ export function extractUiRefsFromFileMemoized(filePath, options = {}) {
156
+
157
+ const fileEntry = getFileCacheEntry(filePath);
158
+ const extractionOptions = options.cacheContext
159
+ ? options
160
+ : {
161
+ ...options,
162
+ cacheContext: `file:${filePath}`,
163
+ };
164
+ const key = refsCacheKeyFromFile(filePath, fileEntry, extractionOptions);
165
+ const existing = refsCache.get(key);
166
+
167
+ if (existing) {
168
+
169
+ return existing;
170
+
171
+ }
172
+
173
+ const refs = extractUiComponentRefs(fileEntry.content, extractionOptions);
174
+
175
+ refsCache.set(key, refs);
176
+ pruneCache(refsCache, REFS_CACHE_LIMIT);
177
+
178
+ return refs;
179
+
180
+ }
181
+
182
+ export function clearComponentAndPageMemory() {
183
+
184
+ fileCache.clear();
185
+ refsCache.clear();
186
+
187
+ }
188
+
189
+ export function getComponentAndPageMemoryStatus({
190
+ includeKeys = true,
191
+ sampleSize = 25,
192
+ } = {}) {
193
+
194
+ let fileCacheBytes = 0;
195
+
196
+ for (const value of fileCache.values()) {
197
+
198
+ fileCacheBytes += Buffer.byteLength(value.content || "", "utf-8");
199
+
200
+ }
201
+
202
+ let refsCacheBytes = 0;
203
+
204
+ for (const [ key, value ] of refsCache.entries()) {
205
+
206
+ refsCacheBytes += Buffer.byteLength(key, "utf-8");
207
+ refsCacheBytes += Buffer.byteLength(JSON.stringify(value || []), "utf-8");
208
+
209
+ }
210
+
211
+ const processMemoryUsage = typeof process?.memoryUsage === "function"
212
+ ? process.memoryUsage()
213
+ : null;
214
+ const totalCacheBytes = fileCacheBytes + refsCacheBytes;
215
+ const processMemoryUsageWithUnits = processMemoryUsage
216
+ ? {
217
+ rss: withMBGB(processMemoryUsage.rss || 0),
218
+ heapTotal: withMBGB(processMemoryUsage.heapTotal || 0),
219
+ heapUsed: withMBGB(processMemoryUsage.heapUsed || 0),
220
+ external: withMBGB(processMemoryUsage.external || 0),
221
+ arrayBuffers: withMBGB(processMemoryUsage.arrayBuffers || 0),
222
+ }
223
+ : null;
224
+
225
+ const out = {
226
+ limits: {
227
+ fileCacheLimit: FILE_CACHE_LIMIT,
228
+ refsCacheLimit: REFS_CACHE_LIMIT,
229
+ },
230
+ counts: {
231
+ fileCacheEntries: fileCache.size,
232
+ refsCacheEntries: refsCache.size,
233
+ },
234
+ approximateBytes: {
235
+ fileCacheBytes,
236
+ refsCacheBytes,
237
+ totalCacheBytes,
238
+ },
239
+ approximateMemory: {
240
+ fileCache: withMBGB(fileCacheBytes),
241
+ refsCache: withMBGB(refsCacheBytes),
242
+ totalCache: withMBGB(totalCacheBytes),
243
+ },
244
+ processMemoryUsage,
245
+ processMemoryUsageWithUnits,
246
+ };
247
+
248
+ if (includeKeys) {
249
+
250
+ out.keys = {
251
+ fileCacheKeys: [ ...fileCache.keys() ].slice(0, sampleSize),
252
+ refsCacheKeys: [ ...refsCache.keys() ].slice(0, sampleSize),
253
+ };
254
+
255
+ }
256
+
257
+ return out;
258
+
259
+ }
260
+
261
+ const componentAndPageMemory = {
262
+ readFileMemoized,
263
+ extractUiRefsMemoized,
264
+ extractUiRefsFromFileMemoized,
265
+ clearComponentAndPageMemory,
266
+ getComponentAndPageMemoryStatus,
267
+ };
268
+
269
+ export default componentAndPageMemory;
@@ -1,8 +1,11 @@
1
1
  import path from "path";
2
2
  import { fileURLToPath } from "url";
3
3
  import { each, getAllFiles } from "@awesomeness-js/utils";
4
- import { readFileSync } from "fs";
5
4
  import getConfig from "./getConfig.js";
5
+ import {
6
+ extractUiRefsFromFileMemoized,
7
+ readFileMemoized,
8
+ } from "./componentAndPageMemory.js";
6
9
 
7
10
  function urlToFsPath(u) {
8
11
 
@@ -34,22 +37,6 @@ function urlToFsPath(u) {
34
37
 
35
38
  }
36
39
 
37
- function extractUiFirstParts(str) {
38
-
39
- const regex = /ui\.([a-zA-Z0-9_]+)(?:\.[a-zA-Z0-9_.]*)?\(/g;
40
- const matches = new Set();
41
- let match;
42
-
43
- while ((match = regex.exec(str)) !== null) {
44
-
45
- matches.add(match[1]);
46
-
47
- }
48
-
49
- return [ ...matches ];
50
-
51
- }
52
-
53
40
  export default function componentDependencies(
54
41
  allComponents,
55
42
  {
@@ -186,14 +173,17 @@ export default function componentDependencies(
186
173
 
187
174
  // readFileSync must use chosenRoot + relative file path
188
175
  const filePath = path.isAbsolute(file) ? file : path.join(chosenRoot, file);
189
- const fileContent = readFileSync(filePath, "utf-8");
176
+ const fileContent = readFileMemoized(filePath);
190
177
 
191
178
  const lines = fileContent.split("\n");
192
179
  let fileWithImportsStripped = "";
193
180
 
194
181
  try {
195
182
 
196
- const newTest = extractUiFirstParts(fileContent);
183
+ const newTest = extractUiRefsFromFileMemoized(filePath, {
184
+ namespace,
185
+ cacheContext: `component:${component}|file:${filePath}`,
186
+ });
197
187
 
198
188
  if (newTest.length > 0) {
199
189
 
@@ -0,0 +1,55 @@
1
+ function escapeRegex(input) {
2
+
3
+ return String(input).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4
+
5
+ }
6
+
7
+ export default function extractUiComponentRefs(
8
+ str,
9
+ {
10
+ namespace = "ui",
11
+ includeCall = true,
12
+ includeDotAccess = false,
13
+ } = {}
14
+ ) {
15
+
16
+ if (typeof str !== "string" || !str.length) {
17
+
18
+ return [];
19
+
20
+ }
21
+
22
+ const ns = escapeRegex(namespace);
23
+ const lookaheads = [];
24
+
25
+ if (includeDotAccess) {
26
+
27
+ lookaheads.push("\\.");
28
+
29
+ }
30
+
31
+ if (includeCall) {
32
+
33
+ lookaheads.push("\\(");
34
+
35
+ }
36
+
37
+ if (!lookaheads.length) {
38
+
39
+ return [];
40
+
41
+ }
42
+
43
+ const regex = new RegExp(`${ns}\\.([a-zA-Z0-9_]+)(?=${lookaheads.join("|")})`, "g");
44
+ const matches = new Set();
45
+ let match;
46
+
47
+ while ((match = regex.exec(str)) !== null) {
48
+
49
+ matches.add(match[1]);
50
+
51
+ }
52
+
53
+ return [ ...matches ];
54
+
55
+ }
package/src/fetchPage.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import path from "path";
2
2
  import { fileURLToPath } from "url";
3
+ import { existsSync } from "fs";
3
4
 
4
5
  import componentDependencies from "./componentDependencies.js";
5
6
  import pageInfo from "./pageInfo.js";
6
7
  import { each, md5, combineFiles } from "@awesomeness-js/utils";
7
8
  import getConfig from "./getConfig.js";
9
+ import { extractUiRefsFromFileMemoized, extractUiRefsMemoized } from "./componentAndPageMemory.js";
8
10
 
9
11
  const componentNamespace = "ui";
10
12
  const pageNamespaceBase = `app.pages`;
@@ -68,6 +70,63 @@ export default async function fetchPage(
68
70
  return inits.join("\n");
69
71
 
70
72
  }
73
+
74
+ function collectComponentsFromPageFiles() {
75
+
76
+ const found = new Set();
77
+ const initPath = path.join(path.dirname(jsPath), "init.js");
78
+ const cacheScope = `site:${awesomenessRequest.site}|page:${page}`;
79
+
80
+ if (existsSync(initPath)) {
81
+
82
+ try {
83
+
84
+
85
+ extractUiRefsFromFileMemoized(initPath, {
86
+ namespace: componentNamespace,
87
+ includeDotAccess: true,
88
+ }).forEach((name) => found.add(name));
89
+
90
+ } catch (err) {
91
+
92
+ console.log("failed to infer components from page init.js", {
93
+ page,
94
+ initPath,
95
+ err,
96
+ });
97
+
98
+ }
99
+
100
+ }
101
+
102
+ try {
103
+
104
+ combineFiles(jsPath, "js", {
105
+ processContent: ({ content }) => {
106
+
107
+ extractUiRefsMemoized(content, {
108
+ namespace: componentNamespace,
109
+ includeDotAccess: true,
110
+ cacheContext: `${cacheScope}|js-content`,
111
+ }).forEach((name) => found.add(name));
112
+
113
+ return content;
114
+
115
+ },
116
+ });
117
+
118
+ } catch (err) {
119
+
120
+ console.log("failed to infer components from page js", {
121
+ page,
122
+ err,
123
+ });
124
+
125
+ }
126
+
127
+ return [ ...found ];
128
+
129
+ }
71
130
 
72
131
 
73
132
  // initialize if not already available
@@ -189,7 +248,8 @@ export default async function fetchPage(
189
248
 
190
249
  }
191
250
 
192
- return content;
251
+ return content;
252
+
193
253
 
194
254
  },
195
255
  });
@@ -218,9 +278,24 @@ export default async function fetchPage(
218
278
 
219
279
  }
220
280
 
221
- if (about?.components?.length) {
222
-
223
- const allDependencies = componentDependencies(about.components, {
281
+ const explicitComponents = Array.isArray(about?.components) ? about.components : [];
282
+ const inferredComponents = [
283
+ ...new Set([
284
+ ...collectComponentsFromPageFiles(),
285
+ ...(meta.pages[page]?.js
286
+ ? extractUiRefsMemoized(meta.pages[page].js, {
287
+ namespace: componentNamespace,
288
+ includeDotAccess: true,
289
+ cacheContext: `site:${awesomenessRequest.site}|page:${page}|bundled-js`,
290
+ })
291
+ : []),
292
+ ]),
293
+ ];
294
+ const pageComponents = [ ...new Set([ ...explicitComponents, ...inferredComponents ]) ];
295
+
296
+ if (pageComponents.length) {
297
+
298
+ const allDependencies = componentDependencies(pageComponents, {
224
299
  componentLocations: awesomenessConfig.componentLocations(awesomenessRequest),
225
300
  namespace: componentNamespace,
226
301
  showDetails,
@@ -1,10 +1,6 @@
1
1
  import { readdirSync } from "fs";
2
2
  import path from "path";
3
3
 
4
- /**
5
- * Case-insensitive real path resolver.
6
- * Works correctly on Windows, macOS, and Linux/Docker.
7
- */
8
4
  export default function resolveRealCasePath(inputPath, returnAbsolute = false) {
9
5
 
10
6
  if (!inputPath) return null;