@things-factory/shell 10.1.122 → 10.1.141

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.
@@ -0,0 +1,34 @@
1
+ import { ColumnOptions, ValueTransformer } from 'typeorm';
2
+ /**
3
+ * `decimal` 컬럼을 숫자로 읽고 쓰는 변환기.
4
+ *
5
+ * Postgres·MySQL 은 `decimal` 을 **문자열**로 돌려준다(정밀도를 지키려고). 그대로 두면
6
+ * GraphQL `Float` 필드와 합산 코드가 `'1200.00' + 300` 같은 글자 이어붙이기를 한다.
7
+ * 그래서 읽을 때 숫자로 바꾼다. 비어 있음(`null`·`undefined`)은 그대로 둔다 — 0 으로 만들지 않는다.
8
+ */
9
+ export declare const decimalTransformer: ValueTransformer;
10
+ /**
11
+ * 금액 컬럼 — `decimal(18, 2)`.
12
+ *
13
+ * ## 왜 `decimal` 인가
14
+ *
15
+ * - 형식을 적지 않은 `number` 컬럼은 32비트 정수가 된다. 원 단위로는 정확하지만 한 칸에
16
+ * 약 21억까지이고 소수(센트)를 못 담는다.
17
+ * - `float` 은 0.1 을 정확히 담지 못한다. 수천 줄을 더하면 은행 잔액과 어긋난다.
18
+ * - `decimal` 은 SQL 표준이라 다섯 드라이버(postgres·mysql/mariadb·sqlite·mssql·oracle)가 모두 받는다.
19
+ * 18자리 중 소수 2자리 — 원화 금액 전부와 외화 센트를 담는다.
20
+ *
21
+ * sqlite 는 자릿수를 강제하지 않는다(NUMERIC 친화형). 정수 원화는 정확하지만 소수 금액은
22
+ * 내부에서 실수로 담길 수 있다 — 소수 금액의 정확성은 서버 DB 를 전제로 한다.
23
+ *
24
+ * ## 쓰는 법
25
+ *
26
+ * ```ts
27
+ * @Column({ nullable: true, ...moneyColumn() })
28
+ * @Field(type => Float, { nullable: true })
29
+ * supplyAmount?: number
30
+ * ```
31
+ *
32
+ * `nullable`·`default` 같은 나머지 옵션은 호출하는 쪽이 정한다. GraphQL 필드는 `Float` 로 둔다.
33
+ */
34
+ export declare function moneyColumn(options?: ColumnOptions): ColumnOptions;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decimalTransformer = void 0;
4
+ exports.moneyColumn = moneyColumn;
5
+ /**
6
+ * `decimal` 컬럼을 숫자로 읽고 쓰는 변환기.
7
+ *
8
+ * Postgres·MySQL 은 `decimal` 을 **문자열**로 돌려준다(정밀도를 지키려고). 그대로 두면
9
+ * GraphQL `Float` 필드와 합산 코드가 `'1200.00' + 300` 같은 글자 이어붙이기를 한다.
10
+ * 그래서 읽을 때 숫자로 바꾼다. 비어 있음(`null`·`undefined`)은 그대로 둔다 — 0 으로 만들지 않는다.
11
+ */
12
+ exports.decimalTransformer = {
13
+ to: (value) => (value === undefined ? undefined : value === null || value === '' ? null : Number(value)),
14
+ from: (value) => (value === null || value === undefined ? value : Number(value))
15
+ };
16
+ /**
17
+ * 금액 컬럼 — `decimal(18, 2)`.
18
+ *
19
+ * ## 왜 `decimal` 인가
20
+ *
21
+ * - 형식을 적지 않은 `number` 컬럼은 32비트 정수가 된다. 원 단위로는 정확하지만 한 칸에
22
+ * 약 21억까지이고 소수(센트)를 못 담는다.
23
+ * - `float` 은 0.1 을 정확히 담지 못한다. 수천 줄을 더하면 은행 잔액과 어긋난다.
24
+ * - `decimal` 은 SQL 표준이라 다섯 드라이버(postgres·mysql/mariadb·sqlite·mssql·oracle)가 모두 받는다.
25
+ * 18자리 중 소수 2자리 — 원화 금액 전부와 외화 센트를 담는다.
26
+ *
27
+ * sqlite 는 자릿수를 강제하지 않는다(NUMERIC 친화형). 정수 원화는 정확하지만 소수 금액은
28
+ * 내부에서 실수로 담길 수 있다 — 소수 금액의 정확성은 서버 DB 를 전제로 한다.
29
+ *
30
+ * ## 쓰는 법
31
+ *
32
+ * ```ts
33
+ * @Column({ nullable: true, ...moneyColumn() })
34
+ * @Field(type => Float, { nullable: true })
35
+ * supplyAmount?: number
36
+ * ```
37
+ *
38
+ * `nullable`·`default` 같은 나머지 옵션은 호출하는 쪽이 정한다. GraphQL 필드는 `Float` 로 둔다.
39
+ */
40
+ function moneyColumn(options = {}) {
41
+ return { type: 'decimal', precision: 18, scale: 2, transformer: exports.decimalTransformer, ...options };
42
+ }
43
+ //# sourceMappingURL=money-column.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"money-column.js","sourceRoot":"","sources":["../../server/typeorm/money-column.ts"],"names":[],"mappings":";;;AAsCA,kCAEC;AAtCD;;;;;;GAMG;AACU,QAAA,kBAAkB,GAAqB;IAClD,EAAE,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACjH,IAAI,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;CAC1F,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,SAAgB,WAAW,CAAC,UAAyB,EAAE;IACrD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,0BAAkB,EAAE,GAAG,OAAO,EAAE,CAAA;AAClG,CAAC","sourcesContent":["import { ColumnOptions, ValueTransformer } from 'typeorm'\n\n/**\n * `decimal` 컬럼을 숫자로 읽고 쓰는 변환기.\n *\n * Postgres·MySQL 은 `decimal` 을 **문자열**로 돌려준다(정밀도를 지키려고). 그대로 두면\n * GraphQL `Float` 필드와 합산 코드가 `'1200.00' + 300` 같은 글자 이어붙이기를 한다.\n * 그래서 읽을 때 숫자로 바꾼다. 비어 있음(`null`·`undefined`)은 그대로 둔다 — 0 으로 만들지 않는다.\n */\nexport const decimalTransformer: ValueTransformer = {\n to: (value: unknown) => (value === undefined ? undefined : value === null || value === '' ? null : Number(value)),\n from: (value: unknown) => (value === null || value === undefined ? value : Number(value))\n}\n\n/**\n * 금액 컬럼 — `decimal(18, 2)`.\n *\n * ## 왜 `decimal` 인가\n *\n * - 형식을 적지 않은 `number` 컬럼은 32비트 정수가 된다. 원 단위로는 정확하지만 한 칸에\n * 약 21억까지이고 소수(센트)를 못 담는다.\n * - `float` 은 0.1 을 정확히 담지 못한다. 수천 줄을 더하면 은행 잔액과 어긋난다.\n * - `decimal` 은 SQL 표준이라 다섯 드라이버(postgres·mysql/mariadb·sqlite·mssql·oracle)가 모두 받는다.\n * 18자리 중 소수 2자리 — 원화 금액 전부와 외화 센트를 담는다.\n *\n * sqlite 는 자릿수를 강제하지 않는다(NUMERIC 친화형). 정수 원화는 정확하지만 소수 금액은\n * 내부에서 실수로 담길 수 있다 — 소수 금액의 정확성은 서버 DB 를 전제로 한다.\n *\n * ## 쓰는 법\n *\n * ```ts\n * @Column({ nullable: true, ...moneyColumn() })\n * @Field(type => Float, { nullable: true })\n * supplyAmount?: number\n * ```\n *\n * `nullable`·`default` 같은 나머지 옵션은 호출하는 쪽이 정한다. GraphQL 필드는 `Float` 로 둔다.\n */\nexport function moneyColumn(options: ColumnOptions = {}): ColumnOptions {\n return { type: 'decimal', precision: 18, scale: 2, transformer: decimalTransformer, ...options }\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import type { EntityManager } from 'typeorm';
2
+ /**
3
+ * **Can this database hold a row for us while we decide?** — `SELECT … FOR UPDATE` where it exists.
4
+ *
5
+ * True only inside an open transaction on a driver that has row locks. sqlite has none, and TypeORM throws
6
+ * `LockNotSupportedOnGivenDriverError` if asked; it does not need one, because a single writer makes a second
7
+ * writing transaction wait for the first as a whole. Every other driver this platform runs on (postgres,
8
+ * mysql, mssql, oracle) takes the lock.
9
+ *
10
+ * Outside a transaction a lock would be released as soon as the statement ends, so it is not asked for.
11
+ *
12
+ * ```ts
13
+ * repository.findOne({ where, ...(writeLockSupported(tx) ? { lock: { mode: 'pessimistic_write' } } : {}) })
14
+ * ```
15
+ */
16
+ export declare function writeLockSupported(manager: Pick<EntityManager, 'connection' | 'queryRunner'>): boolean;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.writeLockSupported = writeLockSupported;
4
+ /**
5
+ * **Can this database hold a row for us while we decide?** — `SELECT … FOR UPDATE` where it exists.
6
+ *
7
+ * True only inside an open transaction on a driver that has row locks. sqlite has none, and TypeORM throws
8
+ * `LockNotSupportedOnGivenDriverError` if asked; it does not need one, because a single writer makes a second
9
+ * writing transaction wait for the first as a whole. Every other driver this platform runs on (postgres,
10
+ * mysql, mssql, oracle) takes the lock.
11
+ *
12
+ * Outside a transaction a lock would be released as soon as the statement ends, so it is not asked for.
13
+ *
14
+ * ```ts
15
+ * repository.findOne({ where, ...(writeLockSupported(tx) ? { lock: { mode: 'pessimistic_write' } } : {}) })
16
+ * ```
17
+ */
18
+ function writeLockSupported(manager) {
19
+ const type = manager.connection?.options?.type;
20
+ return Boolean(manager.queryRunner?.isTransactionActive) && !SINGLE_WRITER_DRIVERS.includes(type ?? '');
21
+ }
22
+ const SINGLE_WRITER_DRIVERS = ['sqlite', 'better-sqlite3', 'sqljs'];
23
+ //# sourceMappingURL=write-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-lock.js","sourceRoot":"","sources":["../../server/typeorm/write-lock.ts"],"names":[],"mappings":";;AAgBA,gDAGC;AAjBD;;;;;;;;;;;;;GAaG;AACH,SAAgB,kBAAkB,CAAC,OAA0D;IAC3F,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,EAAE,OAAO,EAAE,IAA0B,CAAA;IACpE,OAAO,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,mBAAmB,CAAC,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,CAAA;AACzG,CAAC;AAED,MAAM,qBAAqB,GAAG,CAAC,QAAQ,EAAE,gBAAgB,EAAE,OAAO,CAAC,CAAA","sourcesContent":["import type { EntityManager } from 'typeorm'\n\n/**\n * **Can this database hold a row for us while we decide?** — `SELECT … FOR UPDATE` where it exists.\n *\n * True only inside an open transaction on a driver that has row locks. sqlite has none, and TypeORM throws\n * `LockNotSupportedOnGivenDriverError` if asked; it does not need one, because a single writer makes a second\n * writing transaction wait for the first as a whole. Every other driver this platform runs on (postgres,\n * mysql, mssql, oracle) takes the lock.\n *\n * Outside a transaction a lock would be released as soon as the statement ends, so it is not asked for.\n *\n * ```ts\n * repository.findOne({ where, ...(writeLockSupported(tx) ? { lock: { mode: 'pessimistic_write' } } : {}) })\n * ```\n */\nexport function writeLockSupported(manager: Pick<EntityManager, 'connection' | 'queryRunner'>): boolean {\n const type = manager.connection?.options?.type as string | undefined\n return Boolean(manager.queryRunner?.isTransactionActive) && !SINGLE_WRITER_DRIVERS.includes(type ?? '')\n}\n\nconst SINGLE_WRITER_DRIVERS = ['sqlite', 'better-sqlite3', 'sqljs']\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/shell",
3
- "version": "10.1.122",
3
+ "version": "10.1.141",
4
4
  "description": "Core module for framework",
5
5
  "bin": {
6
6
  "things-factory": "bin/things-factory",
@@ -56,7 +56,7 @@
56
56
  "@things-factory/ejs-remote": "^10.1.20",
57
57
  "@things-factory/env": "^10.1.20",
58
58
  "@things-factory/operato-license-checker": "^4.0.4",
59
- "@things-factory/styles": "^10.1.122",
59
+ "@things-factory/styles": "^10.1.123",
60
60
  "@things-factory/utils": "^10.1.20",
61
61
  "@types/koa": "^3.0.1",
62
62
  "@types/koa-bodyparser": "^4.3.13",
@@ -126,5 +126,5 @@
126
126
  "pg": "^8.7.3",
127
127
  "sqlite3": "^5.0.8"
128
128
  },
129
- "gitHead": "a66715e94ef6064cade05229de57d0fe5798d1fb"
129
+ "gitHead": "931866a13b9149be42a294f207e245aba11e57f8"
130
130
  }
@@ -0,0 +1,34 @@
1
+ import { decimalTransformer, moneyColumn } from '../server/typeorm/money-column'
2
+
3
+ /**
4
+ * Money is a `decimal(18, 2)` column read back as a number.
5
+ *
6
+ * Postgres and MySQL hand `decimal` back as a string. Left alone, a sum over rows becomes string
7
+ * concatenation ('1200.00' + 300 = '1200.00300') and the GraphQL Float field receives text.
8
+ */
9
+ describe('a money column', () => {
10
+ it('is decimal(18, 2) with the number transformer', () => {
11
+ expect(moneyColumn()).toEqual({ type: 'decimal', precision: 18, scale: 2, transformer: decimalTransformer })
12
+ })
13
+
14
+ it('keeps the options the caller gives', () => {
15
+ expect(moneyColumn({ nullable: true, default: 0 })).toMatchObject({ type: 'decimal', nullable: true, default: 0 })
16
+ })
17
+
18
+ it('reads a driver string as a number', () => {
19
+ expect(decimalTransformer.from('1200.00')).toBe(1200)
20
+ expect(decimalTransformer.from('-71200000.50')).toBe(-71200000.5)
21
+ })
22
+
23
+ it('leaves an empty value empty, never 0', () => {
24
+ expect(decimalTransformer.from(null)).toBeNull()
25
+ expect(decimalTransformer.from(undefined)).toBeUndefined()
26
+ expect(decimalTransformer.to(null)).toBeNull()
27
+ expect(decimalTransformer.to('')).toBeNull()
28
+ expect(decimalTransformer.to(undefined)).toBeUndefined()
29
+ })
30
+
31
+ it('writes a number given as text as a number', () => {
32
+ expect(decimalTransformer.to('658630')).toBe(658630)
33
+ })
34
+ })
@@ -0,0 +1,27 @@
1
+ import { writeLockSupported } from '../server/typeorm/write-lock'
2
+
3
+ /**
4
+ * A row lock is asked for only where the driver has one and a transaction will hold it.
5
+ *
6
+ * Asking sqlite throws `LockNotSupportedOnGivenDriverError`; asking outside a transaction locks nothing.
7
+ */
8
+ const manager = (type: string, isTransactionActive?: boolean) =>
9
+ ({
10
+ connection: { options: { type } },
11
+ queryRunner: isTransactionActive === undefined ? undefined : { isTransactionActive }
12
+ }) as any
13
+
14
+ describe('writeLockSupported', () => {
15
+ it.each(['postgres', 'mysql', 'mariadb', 'mssql', 'oracle'])('locks on %s inside a transaction', type => {
16
+ expect(writeLockSupported(manager(type, true))).toBe(true)
17
+ })
18
+
19
+ it.each(['sqlite', 'better-sqlite3', 'sqljs'])('does not lock on %s, which has a single writer', type => {
20
+ expect(writeLockSupported(manager(type, true))).toBe(false)
21
+ })
22
+
23
+ it('does not lock outside a transaction', () => {
24
+ expect(writeLockSupported(manager('postgres', false))).toBe(false)
25
+ expect(writeLockSupported(manager('postgres'))).toBe(false)
26
+ })
27
+ })