@jsenv/core 23.7.0 → 23.8.2

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.
@@ -1,265 +1,268 @@
1
- import { urlToFileSystemPath } from "@jsenv/filesystem"
2
- import { createDetailedMessage } from "@jsenv/logger"
3
- import { timeStart, timeFunction } from "@jsenv/server"
4
- import { readFileContent } from "./fs-optimized-for-cache.js"
5
- import { validateCache } from "./validateCache.js"
6
- import { getMetaJsonFileUrl } from "./compile-asset.js"
7
- import { createLockRegistry } from "./createLockRegistry.js"
8
-
9
- const { lockForRessource } = createLockRegistry()
10
-
11
- export const getOrGenerateCompiledFile = async ({
12
- logger,
13
-
14
- projectDirectoryUrl,
15
- originalFileUrl,
16
- compiledFileUrl = originalFileUrl,
17
- compileCacheStrategy,
18
- compileCacheSourcesValidation,
19
- compileCacheAssetsValidation,
20
- fileContentFallback,
21
- request,
22
- compile,
23
- }) => {
24
- if (typeof projectDirectoryUrl !== "string") {
25
- throw new TypeError(
26
- `projectDirectoryUrl must be a string, got ${projectDirectoryUrl}`,
27
- )
28
- }
29
- if (typeof originalFileUrl !== "string") {
30
- throw new TypeError(
31
- `originalFileUrl must be a string, got ${originalFileUrl}`,
32
- )
33
- }
34
- if (!originalFileUrl.startsWith(projectDirectoryUrl)) {
35
- throw new Error(
36
- createDetailedMessage(`origin file must be inside project`, {
37
- ["original file url"]: originalFileUrl,
38
- ["project directory url"]: projectDirectoryUrl,
39
- }),
40
- )
41
- }
42
- if (typeof compiledFileUrl !== "string") {
43
- throw new TypeError(
44
- `compiledFileUrl must be a string, got ${compiledFileUrl}`,
45
- )
46
- }
47
- if (!compiledFileUrl.startsWith(projectDirectoryUrl)) {
48
- throw new Error(
49
- createDetailedMessage(`compiled file must be inside project`, {
50
- ["compiled file url"]: compiledFileUrl,
51
- ["project directory url"]: projectDirectoryUrl,
52
- }),
53
- )
54
- }
55
- if (typeof compile !== "function") {
56
- throw new TypeError(`compile must be a function, got ${compile}`)
57
- }
58
-
59
- const lockTimeEnd = timeStart("lock")
60
- return startAsap(
61
- async () => {
62
- const lockTiming = lockTimeEnd()
63
- const { meta, compileResult, compileResultStatus, timing } =
64
- await computeCompileReport({
65
- originalFileUrl,
66
- compiledFileUrl,
67
- compile,
68
- fileContentFallback,
69
- compileCacheStrategy,
70
- compileCacheSourcesValidation,
71
- compileCacheAssetsValidation,
72
- request,
73
- logger,
74
- })
75
-
76
- return {
77
- meta,
78
- compileResult,
79
- compileResultStatus,
80
- timing: {
81
- ...lockTiming,
82
- ...timing,
83
- },
84
- }
85
- },
86
- {
87
- compiledFileUrl,
88
- logger,
89
- },
90
- )
91
- }
92
-
93
- const computeCompileReport = async ({
94
- originalFileUrl,
95
- compiledFileUrl,
96
- compile,
97
- fileContentFallback,
98
- compileCacheStrategy,
99
- compileCacheSourcesValidation,
100
- compileCacheAssetsValidation,
101
- request,
102
- logger,
103
- }) => {
104
- const [readCacheTiming, cacheValidity] = await timeFunction(
105
- "read cache",
106
- () => {
107
- // if (!useFilesystemAsCache) {
108
- // return {
109
- // isValid: false,
110
- // code: "META_FILE_NOT_FOUND",
111
- // meta: {
112
- // isValid: false,
113
- // code: "META_FILE_NOT_FOUND",
114
- // },
115
- // }
116
- // }
117
- return validateCache({
118
- logger,
119
- compiledFileUrl,
120
- compileCacheStrategy,
121
- compileCacheSourcesValidation,
122
- compileCacheAssetsValidation,
123
- request,
124
- })
125
- },
126
- )
127
-
128
- if (!cacheValidity.isValid) {
129
- if (cacheValidity.code === "SOURCES_EMPTY") {
130
- logger.warn(`WARNING: meta.sources is empty for ${compiledFileUrl}`)
131
- }
132
-
133
- const metaIsValid = cacheValidity.meta.isValid
134
-
135
- const [compileTiming, compileResult] = await timeFunction("compile", () =>
136
- callCompile({
137
- logger,
138
- originalFileUrl,
139
- fileContentFallback,
140
- compile,
141
- }),
142
- )
143
-
144
- return {
145
- meta: metaIsValid ? cacheValidity.meta.data : null,
146
- compileResult,
147
- compileResultStatus: metaIsValid ? "updated" : "created",
148
- timing: {
149
- ...readCacheTiming,
150
- ...compileTiming,
151
- },
152
- }
153
- }
154
-
155
- const meta = cacheValidity.meta.data
156
- const { contentType, sources, assets } = meta
157
- return {
158
- meta,
159
- compileResult: {
160
- compiledSource: String(
161
- cacheValidity.compiledFile.data.compiledSourceBuffer,
162
- ),
163
- compiledEtag: cacheValidity.compiledFile.data.compiledEtag,
164
- compiledMtime: cacheValidity.compiledFile.data.compiledMtime,
165
- contentType,
166
- sources,
167
- assets,
168
- },
169
- compileResultStatus: "cached",
170
- timing: {
171
- ...readCacheTiming,
172
- },
173
- }
174
- }
175
-
176
- const callCompile = async ({
177
- logger,
178
- originalFileUrl,
179
- fileContentFallback,
180
- compile,
181
- }) => {
182
- logger.debug(`compile ${originalFileUrl}`)
183
-
184
- const codeBeforeCompile =
185
- compile.length === 0
186
- ? ""
187
- : await getCodeToCompile({ originalFileUrl, fileContentFallback })
188
-
189
- const compileReturnValue = await compile({
190
- code: codeBeforeCompile,
191
- map: undefined,
192
- })
193
- if (typeof compileReturnValue !== "object" || compileReturnValue === null) {
194
- throw new TypeError(
195
- `compile must return an object, got ${compileReturnValue}`,
196
- )
197
- }
198
- const {
199
- contentType,
200
- compiledSource,
201
- sources = [],
202
- sourcesContent = [],
203
- assets = [],
204
- assetsContent = [],
205
- responseHeaders,
206
- } = compileReturnValue
207
- if (typeof contentType !== "string") {
208
- throw new TypeError(
209
- `compile must return a contentType string, got ${contentType}`,
210
- )
211
- }
212
- if (typeof compiledSource !== "string") {
213
- throw new TypeError(
214
- `compile must return a compiledSource string, got ${compiledSource}`,
215
- )
216
- }
217
-
218
- return {
219
- contentType,
220
- compiledSource,
221
- sources,
222
- sourcesContent,
223
- assets,
224
- assetsContent,
225
- responseHeaders,
226
- }
227
- }
228
-
229
- const getCodeToCompile = async ({ originalFileUrl, fileContentFallback }) => {
230
- let fileContent
231
- if (fileContentFallback) {
232
- try {
233
- fileContent = await readFileContent(originalFileUrl)
234
- } catch (e) {
235
- if (e.code === "ENOENT") {
236
- fileContent = await fileContentFallback()
237
- } else {
238
- throw e
239
- }
240
- }
241
- } else {
242
- fileContent = await readFileContent(originalFileUrl)
243
- }
244
- return fileContent
245
- }
246
-
247
- const startAsap = async (fn, { logger, compiledFileUrl }) => {
248
- const metaJsonFileUrl = getMetaJsonFileUrl(compiledFileUrl)
249
- const metaJsonFilePath = urlToFileSystemPath(metaJsonFileUrl)
250
-
251
- logger.debug(`lock ${metaJsonFilePath}`)
252
- // in case this process try to concurrently access meta we wait for previous to be done
253
- const unlockLocal = await lockForRessource(metaJsonFilePath)
254
-
255
- let unlockInterProcessLock = () => {}
256
-
257
- try {
258
- return await fn()
259
- } finally {
260
- // we want to unlock in case of error too
261
- logger.debug(`unlock ${metaJsonFilePath}`)
262
- unlockLocal()
263
- unlockInterProcessLock()
264
- }
265
- }
1
+ import { urlToFileSystemPath } from "@jsenv/filesystem"
2
+ import { createDetailedMessage } from "@jsenv/logger"
3
+ import { timeStart, timeFunction } from "@jsenv/server"
4
+ import { readFileContent } from "./fs-optimized-for-cache.js"
5
+ import { validateCache } from "./validateCache.js"
6
+ import { getMetaJsonFileUrl } from "./compile-asset.js"
7
+ import { createLockRegistry } from "./createLockRegistry.js"
8
+
9
+ const { lockForRessource } = createLockRegistry()
10
+
11
+ export const getOrGenerateCompiledFile = async ({
12
+ logger,
13
+
14
+ projectDirectoryUrl,
15
+ originalFileUrl,
16
+ compiledFileUrl = originalFileUrl,
17
+ compileCacheStrategy,
18
+ compileCacheSourcesValidation,
19
+ compileCacheAssetsValidation,
20
+ fileContentFallback,
21
+ request,
22
+ compile,
23
+ }) => {
24
+ if (typeof projectDirectoryUrl !== "string") {
25
+ throw new TypeError(
26
+ `projectDirectoryUrl must be a string, got ${projectDirectoryUrl}`,
27
+ )
28
+ }
29
+ if (typeof originalFileUrl !== "string") {
30
+ throw new TypeError(
31
+ `originalFileUrl must be a string, got ${originalFileUrl}`,
32
+ )
33
+ }
34
+ if (!originalFileUrl.startsWith(projectDirectoryUrl)) {
35
+ throw new Error(
36
+ createDetailedMessage(`origin file must be inside project`, {
37
+ ["original file url"]: originalFileUrl,
38
+ ["project directory url"]: projectDirectoryUrl,
39
+ }),
40
+ )
41
+ }
42
+ if (typeof compiledFileUrl !== "string") {
43
+ throw new TypeError(
44
+ `compiledFileUrl must be a string, got ${compiledFileUrl}`,
45
+ )
46
+ }
47
+ if (!compiledFileUrl.startsWith(projectDirectoryUrl)) {
48
+ throw new Error(
49
+ createDetailedMessage(`compiled file must be inside project`, {
50
+ ["compiled file url"]: compiledFileUrl,
51
+ ["project directory url"]: projectDirectoryUrl,
52
+ }),
53
+ )
54
+ }
55
+ if (typeof compile !== "function") {
56
+ throw new TypeError(`compile must be a function, got ${compile}`)
57
+ }
58
+
59
+ const lockTimeEnd = timeStart("lock")
60
+ return startAsap(
61
+ async () => {
62
+ const lockTiming = lockTimeEnd()
63
+ const { meta, compileResult, compileResultStatus, timing } =
64
+ await computeCompileReport({
65
+ originalFileUrl,
66
+ compiledFileUrl,
67
+ compile,
68
+ fileContentFallback,
69
+ compileCacheStrategy,
70
+ compileCacheSourcesValidation,
71
+ compileCacheAssetsValidation,
72
+ request,
73
+ logger,
74
+ })
75
+
76
+ return {
77
+ meta,
78
+ compileResult,
79
+ compileResultStatus,
80
+ timing: {
81
+ ...lockTiming,
82
+ ...timing,
83
+ },
84
+ }
85
+ },
86
+ {
87
+ compiledFileUrl,
88
+ logger,
89
+ },
90
+ )
91
+ }
92
+
93
+ const computeCompileReport = async ({
94
+ originalFileUrl,
95
+ compiledFileUrl,
96
+ compile,
97
+ fileContentFallback,
98
+ compileCacheStrategy,
99
+ compileCacheSourcesValidation,
100
+ compileCacheAssetsValidation,
101
+ request,
102
+ logger,
103
+ }) => {
104
+ const [readCacheTiming, cacheValidity] = await timeFunction(
105
+ "read cache",
106
+ () => {
107
+ // if (!useFilesystemAsCache) {
108
+ // return {
109
+ // isValid: false,
110
+ // code: "META_FILE_NOT_FOUND",
111
+ // meta: {
112
+ // isValid: false,
113
+ // code: "META_FILE_NOT_FOUND",
114
+ // },
115
+ // }
116
+ // }
117
+ return validateCache({
118
+ logger,
119
+ compiledFileUrl,
120
+ compileCacheStrategy,
121
+ compileCacheSourcesValidation,
122
+ compileCacheAssetsValidation,
123
+ request,
124
+ })
125
+ },
126
+ )
127
+
128
+ if (!cacheValidity.isValid) {
129
+ if (cacheValidity.code === "SOURCES_EMPTY") {
130
+ logger.warn(`WARNING: meta.sources is empty for ${compiledFileUrl}`)
131
+ }
132
+
133
+ const metaIsValid = cacheValidity.meta.isValid
134
+
135
+ const [compileTiming, compileResult] = await timeFunction("compile", () =>
136
+ callCompile({
137
+ logger,
138
+ originalFileUrl,
139
+ fileContentFallback,
140
+ compile,
141
+ }),
142
+ )
143
+
144
+ return {
145
+ meta: metaIsValid ? cacheValidity.meta.data : null,
146
+ compileResult,
147
+ compileResultStatus: metaIsValid ? "updated" : "created",
148
+ timing: {
149
+ ...readCacheTiming,
150
+ ...compileTiming,
151
+ },
152
+ }
153
+ }
154
+
155
+ const meta = cacheValidity.meta.data
156
+ const { contentType, sources, assets, dependencies } = meta
157
+ return {
158
+ meta,
159
+ compileResult: {
160
+ compiledSource: String(
161
+ cacheValidity.compiledFile.data.compiledSourceBuffer,
162
+ ),
163
+ compiledEtag: cacheValidity.compiledFile.data.compiledEtag,
164
+ compiledMtime: cacheValidity.compiledFile.data.compiledMtime,
165
+ contentType,
166
+ sources,
167
+ assets,
168
+ dependencies,
169
+ },
170
+ compileResultStatus: "cached",
171
+ timing: {
172
+ ...readCacheTiming,
173
+ },
174
+ }
175
+ }
176
+
177
+ const callCompile = async ({
178
+ logger,
179
+ originalFileUrl,
180
+ fileContentFallback,
181
+ compile,
182
+ }) => {
183
+ logger.debug(`compile ${originalFileUrl}`)
184
+
185
+ const codeBeforeCompile =
186
+ compile.length === 0
187
+ ? ""
188
+ : await getCodeToCompile({ originalFileUrl, fileContentFallback })
189
+
190
+ const compileReturnValue = await compile({
191
+ code: codeBeforeCompile,
192
+ map: undefined,
193
+ })
194
+ if (typeof compileReturnValue !== "object" || compileReturnValue === null) {
195
+ throw new TypeError(
196
+ `compile must return an object, got ${compileReturnValue}`,
197
+ )
198
+ }
199
+ const {
200
+ contentType,
201
+ compiledSource,
202
+ sources = [],
203
+ sourcesContent = [],
204
+ assets = [],
205
+ assetsContent = [],
206
+ dependencies = [],
207
+ responseHeaders,
208
+ } = compileReturnValue
209
+ if (typeof contentType !== "string") {
210
+ throw new TypeError(
211
+ `compile must return a contentType string, got ${contentType}`,
212
+ )
213
+ }
214
+ if (typeof compiledSource !== "string") {
215
+ throw new TypeError(
216
+ `compile must return a compiledSource string, got ${compiledSource}`,
217
+ )
218
+ }
219
+
220
+ return {
221
+ contentType,
222
+ compiledSource,
223
+ sources,
224
+ sourcesContent,
225
+ assets,
226
+ assetsContent,
227
+ dependencies,
228
+ responseHeaders,
229
+ }
230
+ }
231
+
232
+ const getCodeToCompile = async ({ originalFileUrl, fileContentFallback }) => {
233
+ let fileContent
234
+ if (fileContentFallback) {
235
+ try {
236
+ fileContent = await readFileContent(originalFileUrl)
237
+ } catch (e) {
238
+ if (e.code === "ENOENT") {
239
+ fileContent = await fileContentFallback()
240
+ } else {
241
+ throw e
242
+ }
243
+ }
244
+ } else {
245
+ fileContent = await readFileContent(originalFileUrl)
246
+ }
247
+ return fileContent
248
+ }
249
+
250
+ const startAsap = async (fn, { logger, compiledFileUrl }) => {
251
+ const metaJsonFileUrl = getMetaJsonFileUrl(compiledFileUrl)
252
+ const metaJsonFilePath = urlToFileSystemPath(metaJsonFileUrl)
253
+
254
+ logger.debug(`lock ${metaJsonFilePath}`)
255
+ // in case this process try to concurrently access meta we wait for previous to be done
256
+ const unlockLocal = await lockForRessource(metaJsonFilePath)
257
+
258
+ let unlockInterProcessLock = () => {}
259
+
260
+ try {
261
+ return await fn()
262
+ } finally {
263
+ // we want to unlock in case of error too
264
+ logger.debug(`unlock ${metaJsonFilePath}`)
265
+ unlockLocal()
266
+ unlockInterProcessLock()
267
+ }
268
+ }