@things-factory/shell 10.1.165 → 10.1.178
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.
- package/client/bootstrap.ts +10 -0
- package/dist-server/server-dev.js +2 -7
- package/dist-server/server-dev.js.map +1 -1
- package/dist-server/server.js +2 -7
- package/dist-server/server.js.map +1 -1
- package/dist-server/service/file-upload/file-upload-option.d.ts +10 -0
- package/dist-server/service/file-upload/file-upload-option.js +20 -0
- package/dist-server/service/file-upload/file-upload-option.js.map +1 -0
- package/dist-server/service/file-upload/file-upload-query.d.ts +7 -0
- package/dist-server/service/file-upload/file-upload-query.js +36 -0
- package/dist-server/service/file-upload/file-upload-query.js.map +1 -0
- package/dist-server/service/file-upload/index.d.ts +4 -0
- package/dist-server/service/file-upload/index.js +9 -0
- package/dist-server/service/file-upload/index.js.map +1 -0
- package/dist-server/service/index.d.ts +2 -1
- package/dist-server/service/index.js +4 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/helps/about/framework.ko.md +3 -3
- package/package.json +4 -4
- package/tests/client-upload-limits.test.ts +38 -0
- package/tests/file-upload-limits.test.ts +48 -0
- package/tests/one-file-picker.test.ts +52 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
### 1. **클라우드 및 인프라 지원**
|
|
6
6
|
|
|
7
|
-
Things-Factory 프레임워크는
|
|
7
|
+
Things-Factory 프레임워크는 클라우드 인프라에서 애플리케이션을 배포하고 관리할 수 있도록 지원합니다. Edge Computing 기능으로 현장 데이터를 처리하고, 다양한 파일 스토리지 옵션을 통해 데이터를 관리할 수 있습니다. 실제 처리 성능과 배포 방식은 구성한 인프라에서 확인해야 합니다.
|
|
8
8
|
|
|
9
9
|
- **클라우드 서비스 구조 준비**: Things-Factory는 AWS, GCP, Azure 등 주요 클라우드 플랫폼과 Kubernetes(K8s)와의 원활한 통합을 제공합니다. 이를 통해 사용자는 클라우드 환경에서 애플리케이션을 손쉽게 배포하고 관리할 수 있습니다. 예를 들어, 자동 확장 및 로드 밸런싱 기능을 통해 트래픽 급증 시에도 안정적인 서비스를 유지할 수 있으며, IaC(Infrastructure as Code) 지원으로 클라우드 리소스를 코드로 관리할 수 있습니다.
|
|
10
10
|
- **Edge Computing**: 클라우드에서 모델링한 데이터를 Edge에서 실행하여, 실시간 데이터 처리와 빠른 응답성을 제공합니다. 예를 들어, 제조 현장에서 발생하는 대량의 센서 데이터를 실시간으로 처리하여, 클라우드에 전송하기 전에 필요한 데이터를 선별하고 필터링할 수 있습니다. 이 기능은 분산된 환경에서도 높은 성능을 유지하며, 네트워크 지연을 최소화할 수 있습니다.
|
|
@@ -43,9 +43,9 @@ Things-Factory 프레임워크는 현대적인 클라우드 인프라와의 완
|
|
|
43
43
|
|
|
44
44
|
### 5. **개발 생산성 및 관리**
|
|
45
45
|
|
|
46
|
-
효율적인 개발과 관리 도구는 Things-Factory 프레임워크의 핵심입니다. 모듈 구조의 재활용성, 계층적 도메인 구조, 사용자 정의 메뉴 시스템, 다국어 기능 등을 통해 개발 생산성과 관리 효율성을
|
|
46
|
+
효율적인 개발과 관리 도구는 Things-Factory 프레임워크의 핵심입니다. 모듈 구조의 재활용성, 계층적 도메인 구조, 사용자 정의 메뉴 시스템, 다국어 기능 등을 통해 개발 생산성과 관리 효율성을 높입니다. 이를 통해 개발자는 더 빠르고 효율적으로 애플리케이션을 구축할 수 있습니다.
|
|
47
47
|
|
|
48
|
-
- **모듈 구조의 재활용성**: 모듈 구조를 통해, 코드를 재활용하고 유지보수하기 쉽게 만듭니다. 예를 들어, 공통 기능을 모듈로 분리하여 여러 프로젝트에서 재사용할 수 있으며, 새로운 기능 추가 시 기존 코드를 수정하지 않고도 쉽게 확장할 수 있습니다. 이를 통해 개발자는 반복적인 작업을 줄이고, 생산성을
|
|
48
|
+
- **모듈 구조의 재활용성**: 모듈 구조를 통해, 코드를 재활용하고 유지보수하기 쉽게 만듭니다. 예를 들어, 공통 기능을 모듈로 분리하여 여러 프로젝트에서 재사용할 수 있으며, 새로운 기능 추가 시 기존 코드를 수정하지 않고도 쉽게 확장할 수 있습니다. 이를 통해 개발자는 반복적인 작업을 줄이고, 생산성을 높일 수 있습니다. 모듈 구조는 애플리케이션의 확장성을 높이고, 품질을 유지합니다.
|
|
49
49
|
- **계층적 도메인 구조**: 상속 가능한 테넌시 구조를 지원하여, 복잡한 다중 도메인 환경에서도 효율적인 관리가 가능합니다. 예를 들어, 여러 하위 도메인이 상위 도메인의 설정과 권한을 상속받아, 일관된 정책을 유지할 수 있습니다. 이를 통해 다양한 비즈니스 요구에 유연하게 대응할 수 있으며, 테넌트 간의 데이터와 설정을 효율적으로 관리할 수 있습니다. 계층적 구조는 관리 효율성을 높입니다.
|
|
50
50
|
- **사용자 정의 메뉴 시스템 제공**: 각기 다른 사용자 요구에 맞춰 메뉴 시스템을 사용자 정의할 수 있습니다. 예를 들어, 관리자와 일반 사용자에게 각각 다른 메뉴를 제공하여, 필요 없는 기능을 숨기고 중요한 기능을 강조할 수 있습니다. 이를 통해 사용자는 자신에게 최적화된 인터페이스를 만들 수 있으며, 관리자는 다양한 설정을 유연하게 조정할 수 있습니다. 사용자 정의 메뉴 시스템은 사용자 경험을 향상시킵니다.
|
|
51
51
|
- **다국어 기능**: 테넌시별 사용자 정의 다국어 기능을 통해, 글로벌 비즈니스 환경에서도 원활한 애플리케이션 사용을 보장합니다. 예를 들어, 각 국가별로 언어와 지역 설정을 적용하여, 사용자에게 최적화된 콘텐츠를 제공할 수 있습니다. 이를 통해 각기 다른 언어를 사용하는 사용자들에게 일관된 경험을 제공합니다. 다국어 기능은 글로벌 시장 진출을 용이하게 합니다.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/shell",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.178",
|
|
4
4
|
"description": "Core module for framework",
|
|
5
5
|
"bin": {
|
|
6
6
|
"things-factory": "bin/things-factory",
|
|
@@ -67,13 +67,13 @@
|
|
|
67
67
|
"@operato/layout": "^10.0.0",
|
|
68
68
|
"@operato/shell": "^10.0.0",
|
|
69
69
|
"@operato/typeorm-history": "^10.0.0",
|
|
70
|
-
"@operato/utils": "^10.
|
|
70
|
+
"@operato/utils": "^10.29.36",
|
|
71
71
|
"@reduxjs/toolkit": "^2.2.5",
|
|
72
72
|
"@things-factory/ejs-remote": "^10.1.20",
|
|
73
73
|
"@things-factory/env": "^10.1.149",
|
|
74
74
|
"@things-factory/operato-license-checker": "^4.0.4",
|
|
75
75
|
"@things-factory/styles": "^10.1.123",
|
|
76
|
-
"@things-factory/utils": "^10.1.
|
|
76
|
+
"@things-factory/utils": "^10.1.178",
|
|
77
77
|
"@types/koa": "^3.0.1",
|
|
78
78
|
"@types/koa-bodyparser": "^4.3.13",
|
|
79
79
|
"@webcomponents/scoped-custom-element-registry": "^0.0.9",
|
|
@@ -142,5 +142,5 @@
|
|
|
142
142
|
"pg": "^8.7.3",
|
|
143
143
|
"sqlite3": "^5.0.8"
|
|
144
144
|
},
|
|
145
|
-
"gitHead": "
|
|
145
|
+
"gitHead": "d57448d3f3045f72a9f24b877a3e306eb34cbaf1"
|
|
146
146
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shell tells every file picker the server's upload limits.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this is tested
|
|
5
|
+
*
|
|
6
|
+
* `ox-input-file` refuses before the upload only when someone registered where the limits come from
|
|
7
|
+
* (@operato/utils `registerFileUploadLimits`). The shell is that someone: on bootstrap it registers a
|
|
8
|
+
* reader of the `fileUploadLimits` query. If the registration is dropped, every picker quietly takes a
|
|
9
|
+
* file the server then refuses after the whole upload.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const mockRegistered: Array<() => Promise<any>> = []
|
|
13
|
+
const mockQueries: string[] = []
|
|
14
|
+
|
|
15
|
+
jest.mock('@operato/utils', () => ({ registerFileUploadLimits: (reader: any) => mockRegistered.push(reader) }))
|
|
16
|
+
jest.mock('@operato/layout', () => ({ updateLayout: () => {} }))
|
|
17
|
+
jest.mock('@operato/graphql', () => ({
|
|
18
|
+
client: {
|
|
19
|
+
query: async ({ query }: any) => {
|
|
20
|
+
mockQueries.push(query.loc.source.body)
|
|
21
|
+
return { data: { fileUploadLimits: { maxFileSize: 1024, maxFiles: 3 } } }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
describe('client bootstrap', () => {
|
|
27
|
+
it('registers the server upload limits for every file picker', async () => {
|
|
28
|
+
/* The module writes its version on window when loaded. */
|
|
29
|
+
;(global as any).window = { matchMedia: () => ({ matches: true, addEventListener: () => {} }) }
|
|
30
|
+
const bootstrap = require('../client/bootstrap').default
|
|
31
|
+
|
|
32
|
+
bootstrap()
|
|
33
|
+
|
|
34
|
+
expect(mockRegistered.length).toBe(1)
|
|
35
|
+
expect(await mockRegistered[0]()).toEqual({ maxFileSize: 1024, maxFiles: 3 })
|
|
36
|
+
expect(mockQueries[0]).toMatch(/fileUploadLimits\s*\{\s*maxFileSize\s+maxFiles\s*\}/)
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The upload limits, in one place.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this is tested
|
|
5
|
+
*
|
|
6
|
+
* The upload middleware enforces `fileUpload.maxFileSize` / `maxFiles`; the `fileUploadLimits`
|
|
7
|
+
* query hands the same numbers to every file picker so it refuses before uploading. Both servers
|
|
8
|
+
* (server.ts, server-dev.ts) once carried their own copy of the reading. If the query and the
|
|
9
|
+
* middleware drift, a picker takes a file the server then refuses.
|
|
10
|
+
*/
|
|
11
|
+
import 'reflect-metadata'
|
|
12
|
+
|
|
13
|
+
const mockConfig: Record<string, any> = {}
|
|
14
|
+
|
|
15
|
+
jest.mock('@things-factory/env', () => ({ config: { get: (key: string) => mockConfig[key] } }))
|
|
16
|
+
|
|
17
|
+
import { fileUploadOption } from '../server/service/file-upload/file-upload-option'
|
|
18
|
+
import { FileUploadQuery } from '../server/service/file-upload/file-upload-query'
|
|
19
|
+
|
|
20
|
+
describe('upload limits', () => {
|
|
21
|
+
afterEach(() => delete mockConfig.fileUpload)
|
|
22
|
+
|
|
23
|
+
it('falls back to 10mb and 10 files', () => {
|
|
24
|
+
expect(fileUploadOption()).toEqual({ maxFileSize: 10 * 1024 * 1024, maxFiles: 10 })
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('reads the configured limits', () => {
|
|
28
|
+
mockConfig.fileUpload = { maxFileSize: '25mb', maxFiles: 3 }
|
|
29
|
+
expect(fileUploadOption()).toEqual({ maxFileSize: 25 * 1024 * 1024, maxFiles: 3 })
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('answers the picker with the numbers the middleware enforces', () => {
|
|
33
|
+
mockConfig.fileUpload = { maxFileSize: '2mb', maxFiles: 4 }
|
|
34
|
+
expect(new FileUploadQuery().fileUploadLimits()).toEqual(fileUploadOption())
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('both servers', () => {
|
|
39
|
+
const fs = require('fs')
|
|
40
|
+
const path = require('path')
|
|
41
|
+
|
|
42
|
+
it.each(['server.ts', 'server-dev.ts'])('%s reads the limits from the one place', name => {
|
|
43
|
+
const source = fs.readFileSync(path.join(__dirname, '../server', name), 'utf8')
|
|
44
|
+
|
|
45
|
+
expect(source).toContain('graphqlUploadKoa(fileUploadOption())')
|
|
46
|
+
expect(source).not.toContain("config.get('fileUpload')")
|
|
47
|
+
})
|
|
48
|
+
})
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One file picker.
|
|
3
|
+
*
|
|
4
|
+
* Picking a file was written eight times across operato and things-factory. `ox-input-file`
|
|
5
|
+
* (@operato/input) is now the one picker and `FileDropHelper` (@operato/utils) the one drop helper.
|
|
6
|
+
* This counts that the retired copies stay gone from things-factory client code: the form-ui
|
|
7
|
+
* `file-uploader`, the image-uploader-ui `image-uploader` and the second `FileDropHelper`.
|
|
8
|
+
*
|
|
9
|
+
* A new need goes into ox-input-file, not into a new element here.
|
|
10
|
+
*
|
|
11
|
+
* It also asserts how many files it read, so a wrong path cannot pass by reading nothing.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, readdirSync, statSync } from 'fs'
|
|
15
|
+
import path from 'path'
|
|
16
|
+
|
|
17
|
+
const PACKAGES = path.resolve(__dirname, '../..')
|
|
18
|
+
const SKIP = new Set(['node_modules', 'dist', 'dist-client', 'dist-server', 'coverage'])
|
|
19
|
+
|
|
20
|
+
function sources(dir: string, found: string[] = []): string[] {
|
|
21
|
+
for (const name of readdirSync(dir)) {
|
|
22
|
+
if (SKIP.has(name) || name.startsWith('.')) continue
|
|
23
|
+
const full = path.join(dir, name)
|
|
24
|
+
const stat = statSync(full)
|
|
25
|
+
if (stat.isDirectory()) sources(full, found)
|
|
26
|
+
else if (/\.(ts|js)$/.test(name)) found.push(full)
|
|
27
|
+
}
|
|
28
|
+
return found
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const RETIRED: [string, RegExp][] = [
|
|
32
|
+
['form-ui file-uploader', /customElements\.define\(\s*['"]file-uploader['"]|<file-uploader[\s>]/],
|
|
33
|
+
['image-uploader-ui image-uploader', /customElements\.define\(\s*['"]image-uploader['"]|<image-uploader[\s>]/],
|
|
34
|
+
['a second FileDropHelper', /class\s+FileDropHelper\b/]
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
describe('one file picker', () => {
|
|
38
|
+
const files = readdirSync(PACKAGES)
|
|
39
|
+
.map(one => path.join(PACKAGES, one))
|
|
40
|
+
.filter(dir => statSync(dir).isDirectory())
|
|
41
|
+
.flatMap(dir => ['client', 'src'].map(sub => path.join(dir, sub)).filter(sub => statSync(sub, { throwIfNoEntry: false })?.isDirectory()))
|
|
42
|
+
.flatMap(dir => sources(dir))
|
|
43
|
+
|
|
44
|
+
it('reads the client code of every package', () => {
|
|
45
|
+
expect(files.length).toBeGreaterThan(1000)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it.each(RETIRED)('%s is gone', (_, pattern) => {
|
|
49
|
+
const found = files.filter(file => pattern.test(readFileSync(file, 'utf8'))).map(file => path.relative(PACKAGES, file))
|
|
50
|
+
expect(found).toEqual([])
|
|
51
|
+
})
|
|
52
|
+
})
|