moonflower 1.4.8 → 1.5.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 (49) hide show
  1. package/dist/openapi/analyzerModule/analyzerModule.cjs +1 -1
  2. package/dist/openapi/analyzerModule/analyzerModule.cjs.map +1 -1
  3. package/dist/openapi/analyzerModule/analyzerModule.d.ts +18 -3
  4. package/dist/openapi/analyzerModule/analyzerModule.d.ts.map +1 -1
  5. package/dist/openapi/analyzerModule/analyzerModule.mjs +168 -112
  6. package/dist/openapi/analyzerModule/analyzerModule.mjs.map +1 -1
  7. package/dist/openapi/analyzerModule/analyzerWorker.cjs +2 -0
  8. package/dist/openapi/analyzerModule/analyzerWorker.cjs.map +1 -0
  9. package/dist/openapi/analyzerModule/analyzerWorker.d.ts +2 -0
  10. package/dist/openapi/analyzerModule/analyzerWorker.d.ts.map +1 -0
  11. package/dist/openapi/analyzerModule/analyzerWorker.mjs +44 -0
  12. package/dist/openapi/analyzerModule/analyzerWorker.mjs.map +1 -0
  13. package/dist/openapi/analyzerModule/nodeParsers.cjs +1 -1
  14. package/dist/openapi/analyzerModule/nodeParsers.cjs.map +1 -1
  15. package/dist/openapi/analyzerModule/nodeParsers.d.ts.map +1 -1
  16. package/dist/openapi/analyzerModule/nodeParsers.mjs +199 -264
  17. package/dist/openapi/analyzerModule/nodeParsers.mjs.map +1 -1
  18. package/dist/openapi/analyzerModule/parseEndpoint.cjs +1 -1
  19. package/dist/openapi/analyzerModule/parseEndpoint.cjs.map +1 -1
  20. package/dist/openapi/analyzerModule/parseEndpoint.d.ts +8 -1
  21. package/dist/openapi/analyzerModule/parseEndpoint.d.ts.map +1 -1
  22. package/dist/openapi/analyzerModule/parseEndpoint.mjs +121 -106
  23. package/dist/openapi/analyzerModule/parseEndpoint.mjs.map +1 -1
  24. package/dist/openapi/analyzerModule/test/TestCase.d.ts +1 -0
  25. package/dist/openapi/analyzerModule/test/TestCase.d.ts.map +1 -1
  26. package/dist/openapi/analyzerModule/test/workerGlobalSetup.d.ts +3 -0
  27. package/dist/openapi/analyzerModule/test/workerGlobalSetup.d.ts.map +1 -0
  28. package/dist/openapi/analyzerModule/workerPaths.d.ts +2 -0
  29. package/dist/openapi/analyzerModule/workerPaths.d.ts.map +1 -0
  30. package/dist/openapi/analyzerModule/workerPool.cjs +2 -0
  31. package/dist/openapi/analyzerModule/workerPool.cjs.map +1 -0
  32. package/dist/openapi/analyzerModule/workerPool.d.ts +33 -0
  33. package/dist/openapi/analyzerModule/workerPool.d.ts.map +1 -0
  34. package/dist/openapi/analyzerModule/workerPool.mjs +46 -0
  35. package/dist/openapi/analyzerModule/workerPool.mjs.map +1 -0
  36. package/package.json +1 -1
  37. package/src/openapi/analyzerModule/analyzerModule.ts +198 -25
  38. package/src/openapi/analyzerModule/analyzerWorker.ts +73 -0
  39. package/src/openapi/analyzerModule/nodeParsers.ts +4 -104
  40. package/src/openapi/analyzerModule/parseEndpoint.ts +31 -9
  41. package/src/openapi/analyzerModule/test/TestCase.ts +1 -0
  42. package/src/openapi/analyzerModule/test/openApiAnalyzer.spec.ts +2 -2
  43. package/src/openapi/analyzerModule/test/openApiAnalyzer.zod.spec.data.ts +6 -0
  44. package/src/openapi/analyzerModule/test/openApiAnalyzer.zod.spec.ts +9 -1
  45. package/src/openapi/analyzerModule/test/workerGlobalSetup.ts +25 -0
  46. package/src/openapi/analyzerModule/workerPaths.ts +4 -0
  47. package/src/openapi/analyzerModule/workerPool.ts +92 -0
  48. package/src/test/app.spec.ts +10 -1
  49. package/vite.config.ts +5 -0
@@ -0,0 +1,25 @@
1
+ import { buildSync } from 'esbuild'
2
+ import { existsSync, unlinkSync } from 'fs'
3
+ import path from 'path'
4
+ import { fileURLToPath } from 'url'
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
+ const workerSrc = path.join(__dirname, '..', 'analyzerWorker.ts')
8
+ const workerOut = path.join(__dirname, '..', 'analyzerWorker.test.mjs')
9
+
10
+ export function setup() {
11
+ buildSync({
12
+ entryPoints: [workerSrc],
13
+ outfile: workerOut,
14
+ bundle: true,
15
+ format: 'esm',
16
+ platform: 'node',
17
+ external: ['ts-morph', '@ts-morph/common', 'typescript', 'worker_threads'],
18
+ })
19
+ }
20
+
21
+ export function teardown() {
22
+ if (existsSync(workerOut)) {
23
+ unlinkSync(workerOut)
24
+ }
25
+ }
@@ -0,0 +1,4 @@
1
+ import os from 'os'
2
+ import path from 'path'
3
+
4
+ export const TEST_WORKER_PATH = path.join(os.tmpdir(), 'moonflower-analyzerWorker.test.mjs')
@@ -0,0 +1,92 @@
1
+ import os from 'os'
2
+ import { Worker } from 'worker_threads'
3
+
4
+ import { EndpointData } from '../types'
5
+ import { SectionTiming } from './parseEndpoint'
6
+
7
+ export type WorkerTask = {
8
+ taskId: string
9
+ tsconfigPath: string
10
+ sourceFilePath: string
11
+ routerName: string
12
+ endpointIndex: number
13
+ }
14
+
15
+ export type WorkerResultSuccess = {
16
+ taskId: string
17
+ endpoint: EndpointData
18
+ sectionTimings: SectionTiming[]
19
+ timing: number
20
+ }
21
+
22
+ export type WorkerResultError = {
23
+ taskId: string
24
+ error: string
25
+ }
26
+
27
+ export type WorkerResult = WorkerResultSuccess | WorkerResultError
28
+
29
+ export class WorkerPool {
30
+ private workers: Worker[]
31
+ private idle: Worker[]
32
+ private queue: Array<{ task: WorkerTask; resolve: (r: WorkerResult) => void }> = []
33
+ private pending = new Map<string, (r: WorkerResult) => void>()
34
+
35
+ constructor(workerUrl: URL) {
36
+ const size = Math.max(1, Math.min(os.cpus().length - 1, 8))
37
+ this.workers = Array.from({ length: size }, () => {
38
+ const worker = new Worker(workerUrl)
39
+ worker.on('message', (result: WorkerResult) => {
40
+ const resolve = this.pending.get(result.taskId)
41
+ if (resolve) {
42
+ this.pending.delete(result.taskId)
43
+ resolve(result)
44
+ }
45
+ this.idle.push(worker)
46
+ this.flush()
47
+ })
48
+ worker.on('error', (err) => {
49
+ // Find any pending task for this worker and reject it
50
+ // (worker crashed — shouldn't happen, but handle gracefully)
51
+ for (const [taskId, resolve] of this.pending) {
52
+ resolve({ taskId, error: String(err) })
53
+ this.pending.delete(taskId)
54
+ break
55
+ }
56
+ this.idle.push(worker)
57
+ this.flush()
58
+ })
59
+ return worker
60
+ })
61
+ this.idle = [...this.workers]
62
+ }
63
+
64
+ run(task: WorkerTask): Promise<WorkerResult> {
65
+ return new Promise((resolve) => {
66
+ if (this.idle.length > 0) {
67
+ const worker = this.idle.pop()!
68
+ this.pending.set(task.taskId, resolve)
69
+ worker.postMessage(task)
70
+ } else {
71
+ this.queue.push({ task, resolve })
72
+ }
73
+ })
74
+ }
75
+
76
+ runAll(tasks: WorkerTask[]): Promise<WorkerResult[]> {
77
+ return Promise.all(tasks.map((t) => this.run(t)))
78
+ }
79
+
80
+ terminate() {
81
+ this.workers.forEach((w) => w.terminate())
82
+ }
83
+
84
+ private flush() {
85
+ while (this.queue.length > 0 && this.idle.length > 0) {
86
+ const { task, resolve } = this.queue.shift()!
87
+ const worker = this.idle.pop()!
88
+ this.pending.set(task.taskId, resolve)
89
+ worker.postMessage(task)
90
+ }
91
+ }
92
+ }
@@ -3,13 +3,22 @@ import Koa from 'koa'
3
3
  import * as os from 'os'
4
4
  import * as path from 'path'
5
5
  import request from 'supertest'
6
- import { describe, expect, it, vi } from 'vitest'
6
+ import { beforeAll, describe, expect, it, vi } from 'vitest'
7
7
 
8
8
  import { generateOpenApiSpec } from '../openapi/generatorModule/generatorModule'
9
9
  import { initOpenApiEngine } from '../openapi/initOpenApiEngine'
10
10
  import { OpenApiManager } from '../openapi/manager/OpenApiManager'
11
11
  import { app } from './app'
12
12
 
13
+ // The OpenAPI engine runs the TypeScript analyzer lazily on first request, and any request that
14
+ // reaches the initOpenApiEngine middleware awaits its readiness. Requests that match a route never
15
+ // reach it, but unmatched ones (e.g. the 405 test) do, so the first such request pays the full
16
+ // analyzer cold-start cost. That can exceed a single test's default 5s timeout in CI. Warm the
17
+ // engine up once here so no individual test bears that cost.
18
+ beforeAll(async () => {
19
+ await request(app.callback()).get('/api-json')
20
+ }, 60_000)
21
+
13
22
  describe('TestAppRouter', () => {
14
23
  it('includes content type header', async () => {
15
24
  const response = await request(app.callback()).get('/test/hello')
package/vite.config.ts CHANGED
@@ -24,6 +24,7 @@ const entries = {
24
24
  'validators/BuiltInValidators': resolve(__dirname, 'src/validators/BuiltInValidators.ts'),
25
25
  'validators/ParamWrappers': resolve(__dirname, 'src/validators/ParamWrappers.ts'),
26
26
  'cli/cli': resolve(__dirname, 'src/cli/cli.ts'),
27
+ 'openapi/analyzerModule/analyzerWorker': resolve(__dirname, 'src/openapi/analyzerModule/analyzerWorker.ts'),
27
28
  }
28
29
 
29
30
  export const baseViteConfig: ViteUserConfig = {
@@ -50,7 +51,10 @@ export const baseViteConfig: ViteUserConfig = {
50
51
  'fs/promises',
51
52
  'assert',
52
53
  'crypto',
54
+ 'os',
53
55
  'perf_hooks',
56
+ 'url',
57
+ 'worker_threads',
54
58
  '@ts-morph/common',
55
59
  'typescript',
56
60
  'ts-morph',
@@ -90,6 +94,7 @@ export const baseViteConfig: ViteUserConfig = {
90
94
  },
91
95
  test: {
92
96
  globals: true,
97
+ globalSetup: 'src/openapi/analyzerModule/test/workerGlobalSetup.ts',
93
98
  setupFiles: 'src/setupTests.ts',
94
99
  include: ['src/**/*.spec.ts'],
95
100
  coverage: {