moonflower 1.4.8 → 1.4.9

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.
@@ -13,7 +13,7 @@ import { ApiDocsHeader, OpenApiManager } from '../manager/OpenApiManager'
13
13
  import { EndpointData, ExposedModelData } from '../types'
14
14
  import { getSourceFileTimestamp, TimestampCache } from './getSourceFileTimestamp'
15
15
  import { getValuesOfObjectLiteral, resolveEndpointPath } from './nodeParsers'
16
- import { parseEndpoint } from './parseEndpoint'
16
+ import { parseEndpoint, SectionTiming } from './parseEndpoint'
17
17
  import { parseExposedModel, parseNamedExposedModels } from './parseExposedModels'
18
18
  import { SourceFileCache } from './sourceFileCache'
19
19
 
@@ -27,12 +27,15 @@ type Props = {
27
27
  | {
28
28
  cachePath: string
29
29
  }
30
+ profiling?: 'stats' | 'off' | 'debug'
30
31
  }
31
32
 
32
33
  type FileDiscoveryConfig = {
33
34
  rootPath: string
34
35
  }
35
36
 
37
+ type EndpointTiming = { method: string; path: string; timing: number; sectionTimings: SectionTiming[] }
38
+
36
39
  /**
37
40
  * @param tsconfigPath Path to tsconfig file relative to project root
38
41
  * @param sourceFilePaths Array of router source files relative to project root
@@ -43,6 +46,7 @@ export const prepareOpenApiSpec = ({
43
46
  sourceFilePaths,
44
47
  sourceFileDiscovery,
45
48
  incremental,
49
+ profiling = 'stats',
46
50
  }: Props) => {
47
51
  const openApiManager = OpenApiManager.getInstance()
48
52
 
@@ -81,7 +85,9 @@ export const prepareOpenApiSpec = ({
81
85
  targetPath: typeof sourceFileDiscovery === 'object' ? sourceFileDiscovery.rootPath : '.',
82
86
  tsConfigPath: tsconfigPath,
83
87
  })
84
- Logger.info(`File discovery took ${Math.round(performance.now() - startTime)}ms`)
88
+ if (profiling !== 'off') {
89
+ Logger.info(`File discovery took ${Math.round(performance.now() - startTime)}ms`)
90
+ }
85
91
  return files
86
92
  })()
87
93
 
@@ -120,6 +126,7 @@ export const prepareOpenApiSpec = ({
120
126
  incremental: incremental !== false,
121
127
  cachePath,
122
128
  timestampCache: {},
129
+ profiling,
123
130
  })
124
131
 
125
132
  openApiManager.setStats({
@@ -153,23 +160,49 @@ export const analyzeMultipleSourceFiles = (
153
160
  incremental: boolean
154
161
  cachePath: string
155
162
  timestampCache: TimestampCache
163
+ profiling?: 'stats' | 'off' | 'debug'
156
164
  },
157
165
  filterEndpointPaths?: string[],
158
166
  ): EndpointData[] => {
167
+ const profiling = config.profiling ?? 'stats'
159
168
  const startTime = performance.now()
160
169
  const analyzedFiles = files.map((file) => analyzeSourceFileWithCache(file, config, filterEndpointPaths))
161
- Logger.info(`Router analysis took ${Math.round(performance.now() - startTime)}ms`)
162
170
 
163
- analyzedFiles
164
- .map((f, index) => ({
165
- fileName: files[index].fileName,
166
- timeTaken: f.timing,
167
- }))
168
- .sort((a, b) => b.timeTaken - a.timeTaken)
169
- .filter((t) => t.timeTaken > 500)
170
- .forEach((t) => {
171
- Logger.info(`- [${t.fileName}] Took ${Math.round(t.timeTaken)}ms to analyze`)
172
- })
171
+ if (profiling !== 'off') {
172
+ Logger.info(`Router analysis took ${Math.round(performance.now() - startTime)}ms`)
173
+ }
174
+
175
+ if (profiling === 'stats') {
176
+ analyzedFiles
177
+ .map((f, index) => ({ fileName: files[index].fileName, timeTaken: f.timing }))
178
+ .sort((a, b) => b.timeTaken - a.timeTaken)
179
+ .filter((t) => t.timeTaken > 500)
180
+ .forEach((t) => {
181
+ Logger.info(`- [${t.fileName}] Took ${Math.round(t.timeTaken)}ms to analyze`)
182
+ })
183
+ } else if (profiling === 'debug') {
184
+ analyzedFiles
185
+ .map((f, index) => ({
186
+ fileName: files[index].fileName,
187
+ timeTaken: f.timing,
188
+ endpointTimings: f.endpointTimings,
189
+ }))
190
+ .sort((a, b) => b.timeTaken - a.timeTaken)
191
+ .forEach((t) => {
192
+ Logger.info(`- [${t.fileName}] Took ${Math.round(t.timeTaken)}ms to analyze`)
193
+ t.endpointTimings
194
+ .sort((a, b) => b.timing - a.timing)
195
+ .forEach((ep) => {
196
+ Logger.info(` - ${ep.method} ${ep.path} (${Math.round(ep.timing)}ms)`)
197
+ ep.sectionTimings
198
+ .filter((s) => s.timing >= 1)
199
+ .sort((a, b) => b.timing - a.timing)
200
+ .forEach((s) => {
201
+ Logger.info(` - ${s.section}: ${Math.round(s.timing)}ms`)
202
+ })
203
+ })
204
+ })
205
+ }
173
206
 
174
207
  return analyzedFiles.flatMap((f) => f.endpoints)
175
208
  }
@@ -180,31 +213,33 @@ export const analyzeSourceFileWithCache = (
180
213
  incremental: boolean
181
214
  cachePath: string
182
215
  timestampCache: TimestampCache
216
+ profiling?: 'stats' | 'off' | 'debug'
183
217
  },
184
218
  filterEndpointPaths?: string[],
185
- ): { endpoints: EndpointData[]; timing: number } => {
219
+ ): { endpoints: EndpointData[]; timing: number; endpointTimings: EndpointTiming[] } => {
186
220
  const timestamp = getSourceFileTimestamp(file.sourceFile, config.timestampCache)
187
221
  const cachedResults = SourceFileCache.getCachedResults(file.sourceFile, timestamp, config.cachePath)
188
222
 
189
223
  if (cachedResults) {
190
224
  Logger.debug(`[${file.fileName}] Found cached results`)
191
- return { endpoints: cachedResults.endpoints, timing: 0 }
225
+ return { endpoints: cachedResults.endpoints, timing: 0, endpointTimings: [] }
192
226
  }
193
227
  Logger.debug(`[${file.fileName}] Analyzing...`)
194
228
 
195
229
  const t1 = performance.now()
196
- const endpoints = analyzeSourceFileEndpoints(file, filterEndpointPaths)
230
+ const { endpoints, endpointTimings } = analyzeSourceFileEndpoints(file, filterEndpointPaths)
197
231
  const t2 = performance.now()
198
232
  Logger.debug(`[${file.fileName}] Analyzed in ${t2 - t1}ms`)
199
233
  SourceFileCache.cacheResults(file.sourceFile, timestamp, config.cachePath, endpoints)
200
- return { endpoints, timing: t2 - t1 }
234
+ return { endpoints, timing: t2 - t1, endpointTimings }
201
235
  }
202
236
 
203
237
  export const analyzeSourceFileEndpoints = (
204
238
  file: DiscoveredSourceFile,
205
239
  filterEndpointPaths?: string[],
206
- ): EndpointData[] => {
240
+ ): { endpoints: EndpointData[]; endpointTimings: EndpointTiming[] } => {
207
241
  const endpoints: EndpointData[] = []
242
+ const endpointTimings: EndpointTiming[] = []
208
243
  const operations = ['get', 'post', 'put', 'delete', 'del', 'patch']
209
244
  const joinedOperations = operations.join('|')
210
245
 
@@ -220,12 +255,20 @@ export const analyzeSourceFileEndpoints = (
220
255
  return
221
256
  }
222
257
 
223
- endpoints.push(parseEndpoint(node, file.fileName))
258
+ const t1 = performance.now()
259
+ const { endpoint, sectionTimings } = parseEndpoint(node, file.fileName)
260
+ endpointTimings.push({
261
+ method: endpoint.method,
262
+ path: endpoint.path,
263
+ timing: performance.now() - t1,
264
+ sectionTimings,
265
+ })
266
+ endpoints.push(endpoint)
224
267
  }
225
268
  })
226
269
  })
227
270
 
228
- return endpoints
271
+ return { endpoints, endpointTimings }
229
272
  }
230
273
 
231
274
  export const analyzeSourceFileApiHeader = (sourceFile: SourceFile): ApiDocsHeader | null => {
@@ -14,7 +14,12 @@ import {
14
14
  resolveEndpointPath,
15
15
  } from './nodeParsers'
16
16
 
17
- export const parseEndpoint = (node: Node<ts.Node>, sourceFilePath: string) => {
17
+ export type SectionTiming = { section: string; timing: number }
18
+
19
+ export const parseEndpoint = (
20
+ node: Node<ts.Node>,
21
+ sourceFilePath: string,
22
+ ): { endpoint: EndpointData; sectionTimings: SectionTiming[] } => {
18
23
  const parsedEndpointMethod = node
19
24
  .getFirstDescendantByKind(SyntaxKind.PropertyAccessExpression)!
20
25
  .getText()
@@ -46,11 +51,20 @@ export const parseEndpoint = (node: Node<ts.Node>, sourceFilePath: string) => {
46
51
  error: Error
47
52
  }[] = []
48
53
 
54
+ const sectionTimings: SectionTiming[] = []
55
+
56
+ const time = <T>(section: string, fn: () => T): T => {
57
+ const t1 = performance.now()
58
+ const result = fn()
59
+ sectionTimings.push({ section, timing: performance.now() - t1 })
60
+ return result
61
+ }
62
+
49
63
  const hookNodes = getHookNodes(node)
50
64
 
51
65
  // API documentation
52
66
  try {
53
- const entries = parseApiDocumentation(hookNodes.useApiEndpoint)
67
+ const entries = time('useApiEndpoint', () => parseApiDocumentation(hookNodes.useApiEndpoint))
54
68
  entries.forEach((param) => {
55
69
  endpointData[param.identifier] = param.value as string & string[]
56
70
  })
@@ -64,7 +78,9 @@ export const parseEndpoint = (node: Node<ts.Node>, sourceFilePath: string) => {
64
78
 
65
79
  // Request params
66
80
  try {
67
- endpointData.requestPathParams = parseRequestParams(hookNodes.usePathParams, endpointPath)
81
+ endpointData.requestPathParams = time('usePathParams', () =>
82
+ parseRequestParams(hookNodes.usePathParams, endpointPath),
83
+ )
68
84
  } catch (err) {
69
85
  warningData.push({
70
86
  segment: 'path',
@@ -75,7 +91,9 @@ export const parseEndpoint = (node: Node<ts.Node>, sourceFilePath: string) => {
75
91
 
76
92
  // Request query
77
93
  try {
78
- endpointData.requestQuery = parseRequestObjectInput(hookNodes.useQueryParams, 'useQueryParams')
94
+ endpointData.requestQuery = time('useQueryParams', () =>
95
+ parseRequestObjectInput(hookNodes.useQueryParams, 'useQueryParams'),
96
+ )
79
97
  } catch (err) {
80
98
  warningData.push({
81
99
  segment: 'query',
@@ -86,7 +104,9 @@ export const parseEndpoint = (node: Node<ts.Node>, sourceFilePath: string) => {
86
104
 
87
105
  // Request headers
88
106
  try {
89
- endpointData.requestHeaders = parseRequestObjectInput(hookNodes.useHeaderParams, 'useHeaderParams')
107
+ endpointData.requestHeaders = time('useHeaderParams', () =>
108
+ parseRequestObjectInput(hookNodes.useHeaderParams, 'useHeaderParams'),
109
+ )
90
110
  } catch (err) {
91
111
  warningData.push({
92
112
  segment: 'headers',
@@ -97,7 +117,7 @@ export const parseEndpoint = (node: Node<ts.Node>, sourceFilePath: string) => {
97
117
 
98
118
  // Raw request body
99
119
  try {
100
- const parsedBody = parseRequestRawBody(hookNodes.useRequestRawBody)
120
+ const parsedBody = time('useRequestRawBody', () => parseRequestRawBody(hookNodes.useRequestRawBody))
101
121
  if (parsedBody) {
102
122
  endpointData.rawBody = parsedBody
103
123
  }
@@ -111,7 +131,9 @@ export const parseEndpoint = (node: Node<ts.Node>, sourceFilePath: string) => {
111
131
 
112
132
  // Object request body
113
133
  try {
114
- endpointData.objectBody = parseRequestObjectInput(hookNodes.useRequestBody, 'useRequestBody')
134
+ endpointData.objectBody = time('useRequestBody', () =>
135
+ parseRequestObjectInput(hookNodes.useRequestBody, 'useRequestBody'),
136
+ )
115
137
  } catch (err) {
116
138
  warningData.push({
117
139
  segment: 'objectBody',
@@ -122,7 +144,7 @@ export const parseEndpoint = (node: Node<ts.Node>, sourceFilePath: string) => {
122
144
 
123
145
  // Request response
124
146
  try {
125
- endpointData.responses = parseRequestResponse(node)
147
+ endpointData.responses = time('response', () => parseRequestResponse(node))
126
148
  } catch (err) {
127
149
  warningData.push({
128
150
  segment: 'response',
@@ -131,7 +153,7 @@ export const parseEndpoint = (node: Node<ts.Node>, sourceFilePath: string) => {
131
153
  Logger.error('Error', err)
132
154
  }
133
155
 
134
- return endpointData
156
+ return { endpoint: endpointData, sectionTimings }
135
157
  }
136
158
 
137
159
  type HookName =
@@ -23,7 +23,7 @@ describe('OpenApi Analyzer', () => {
23
23
  },
24
24
  [`/test/${id}`],
25
25
  )
26
- const endpoint = analysisResult.find((endpoint) => endpoint.path.startsWith(`/test/${id}`))
26
+ const endpoint = analysisResult.endpoints.find((endpoint) => endpoint.path.startsWith(`/test/${id}`))
27
27
  if (!endpoint) {
28
28
  throw new Error(`No endpoint with id ${id} found!`)
29
29
  }
@@ -39,7 +39,7 @@ describe('OpenApi Analyzer', () => {
39
39
  },
40
40
  [`/test/${id}`],
41
41
  )
42
- const endpoints = analysisResult.filter((endpoint) => endpoint.path.startsWith(`/test/${id}`))
42
+ const endpoints = analysisResult.endpoints.filter((endpoint) => endpoint.path.startsWith(`/test/${id}`))
43
43
  if (endpoints.length === 0) {
44
44
  throw new Error(`No endpoint with id ${id} found!`)
45
45
  }
@@ -19,7 +19,7 @@ describe('OpenApi Analyzer (Zod Validator)', () => {
19
19
  },
20
20
  [`/test/${id}`],
21
21
  )
22
- const endpoint = analysisResult.find((endpoint) => endpoint.path.startsWith(`/test/${id}`))
22
+ const endpoint = analysisResult.endpoints.find((endpoint) => endpoint.path.startsWith(`/test/${id}`))
23
23
  if (!endpoint) {
24
24
  throw new Error(`No endpoint with id ${id} found!`)
25
25
  }