mega-framework 0.1.11 → 0.1.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mega-framework",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Node.js 마이크로프레임웍 + CLI — Slim의 가벼움 + Rails의 관례",
5
5
  "type": "module",
6
6
  "engines": {
@@ -802,12 +802,68 @@ export class User extends MegaModel {
802
802
 
803
803
  - **옵트인**이다 — `static schema` 가 없는 모델(레거시 raw SQL 운용)은 그냥 무시된다.
804
804
  - 같은 `static schema` 선언은 **공통 CRUD([§2-1](#2-1-공통-crud-static-schema-opt-in-adr-212))도 함께 켠다**(SQL 어댑터) — "스키마 1선언 = 마이그레이션 + CRUD".
805
- - 타입: `serial/bigSerial/integer/bigInteger/smallInteger/real/doublePrecision/decimal(p,s)/varchar(n)/
806
- text/char(n)/boolean/timestamp/timestamptz/date/time/uuid/json/jsonb/enum(values)/bytea`.
807
- - 체인: `.primary() .notNull() .unique({name?}) .default(v | {raw}) .defaultNow() .check(expr,{name?})
808
- .references(model, col, {onDelete?, onUpdate?, name?}) .comment(text)`. 복합 PK `t.primary(['a','b'])`.
809
- - FK 대상은 **모델 이름**(file-scan 범위 안에 있어야 함 — 없으면 fail-fast). 관계는 FK 까지만 —
810
- 조인 헬퍼/lazy load 같은 ORM 기능은 없다(ADR-009).
805
+
806
+ #### 컬럼 타입 — `t.<type>()`
807
+
808
+ 타입 메서드는 `ColumnBuilder` 반환하므로 아래 **수식어를 체인**할 수 있다(`src/core/migration/schema-builder.js`).
809
+
810
+ | 분류 | 메서드 | 비고 |
811
+ | --- | --- | --- |
812
+ | 정수 | `t.serial()` · `t.bigSerial()` | 자동증가(postgres SERIAL / BIGSERIAL) |
813
+ | 정수 | `t.integer()` · `t.bigInteger()` · `t.smallInteger()` | |
814
+ | 실수 | `t.real()` · `t.doublePrecision()` | |
815
+ | 정밀수 | `t.decimal(precision, scale)` | NUMERIC(p,s) |
816
+ | 문자열 | `t.varchar(maxLength)` · `t.char(length)` · `t.text()` | |
817
+ | 논리 | `t.boolean()` | |
818
+ | 시간 | `t.timestamp()` · `t.timestamptz()` · `t.date()` · `t.time()` | |
819
+ | 기타 | `t.uuid()` · `t.json()` · `t.jsonb()` · `t.bytea()` | |
820
+ | 열거 | `t.enum(values, { name? })` | postgres 는 `TEXT + CHECK (col IN (...))` 로 렌더(native enum 미사용 — 값 추가/삭제가 제약 교체로 단순) |
821
+ | **mongo 전용** | `t.objectId()` · `t.object(shape?)` · `t.array(items?, { uniqueItems? })` | SQL dialect 는 렌더 시 거부. `object`/`array` 의 중첩 필드는 타입·길이·enum·notNull(→required)만 — unique/primary/references/check 는 **최상위 전용** |
822
+
823
+ #### 컬럼 수식어 (체인) — `ColumnBuilder`
824
+
825
+ | 메서드 | 뜻 |
826
+ | --- | --- |
827
+ | `.primary()` | 단일 컬럼 PRIMARY KEY. 복합 PK 는 `t.primary(['a','b'])`(아래) |
828
+ | `.notNull()` | NOT NULL |
829
+ | `.nullable()` | 명시 null 허용. SQL 은 기본 nullable 이라 선언적 표시(no-op), mongo 는 `['type','null']` 유니온. `.notNull()` 과 **동시 사용 불가** |
830
+ | `.unique({ name? })` | UNIQUE 제약. 이름 미지정 시 `uniq_<table>_<col>` |
831
+ | `.default(value)` | DEFAULT. literal(string/number/boolean/null) 또는 `{ raw: 'expr' }`(raw SQL 식) |
832
+ | `.defaultNow()` | `DEFAULT CURRENT_TIMESTAMP` 단축 |
833
+ | `.check(expr, { name? })` | CHECK 제약. 이름 미지정 시 `chk_<table>_<col>` |
834
+ | `.references(model, col, { onDelete?, onUpdate?, name? })` | FK. 대상은 **모델 이름**(테이블명 아님 — 스냅샷 시 해석). `onDelete`/`onUpdate` ∈ `cascade · set null · set default · restrict · no action` |
835
+ | `.comment(text)` | `COMMENT ON COLUMN` |
836
+
837
+ - 테이블 레벨: `t.primary(['a','b'])` — **복합 PRIMARY KEY**(반환값 없음, 컬럼 맵엔 안 들어감). 단일 PK 는 컬럼 체인 `.primary()`.
838
+ - FK 대상 모델은 **file-scan 범위 안에 있어야** 한다(없으면 fail-fast). 관계는 FK 까지만 — 조인 헬퍼/lazy load 같은 ORM 기능은 없다(ADR-009).
839
+
840
+ #### 인덱스 — `static indexes = (t) => [ t.index(...) ]`
841
+
842
+ ```js
843
+ static indexes = (t) => [
844
+ t.index(['role', 'createdAt']), // 복합. 이름 자동: idx_<table>_<col…> (= idx_users_role_createdAt)
845
+ t.index(['email'], { unique: true }), // unique 인덱스
846
+ t.index({ expression: 'lower(email)' }, { name: 'idx_users_email_lower' }), // 표현식 인덱스
847
+ t.index(['org_id'], { where: 'deleted_at IS NULL' }), // 부분 인덱스(조건부)
848
+ t.index(['payload'], { using: 'gin' }), // 인덱스 방법(postgres USING)
849
+ ]
850
+ ```
851
+
852
+ | `t.index(컬럼들|식, opts)` 인자 | 뜻 |
853
+ | --- | --- |
854
+ | 1번째: `'col'` / `['a','b']` / `{ expression: 'lower(x)' }` | 단일·복합 컬럼 또는 표현식 인덱스 |
855
+ | `opts.name` | 인덱스 이름 override(기본 dialect 표준명) |
856
+ | `opts.unique` | `true` 면 UNIQUE 인덱스 |
857
+ | `opts.where` | **부분 인덱스** WHERE 조건(예: `'deleted_at IS NULL'`) |
858
+ | `opts.using` | 인덱스 방법(postgres `USING` — 예: `gin`/`gist`/`hash`) |
859
+
860
+ > 컬럼에 직접 `.unique()` 를 거는 것(=UNIQUE **제약**)과 `t.index([...], { unique: true })`(=UNIQUE **인덱스**)는
861
+ > 비슷하나, 후자는 복합·표현식·부분 인덱스를 지원한다. 단일 컬럼 유일성은 `.unique()` 가 간단하다.
862
+
863
+ **자동 이름 규칙**(postgres dialect, `{ name }` 미지정 시): 인덱스 `idx_<table>_<cols>` · UNIQUE 인덱스/제약
864
+ `uniq_<table>_<cols>` · FK `fk_<table>_<col>_<reftable>` · CHECK `chk_<table>_<col>`. 63byte 초과 시 거부되니
865
+ 길면 `{ name }` 으로 짧게 지정한다.
866
+
811
867
  - 제외 규칙: `_` 로 시작하는 파일/폴더, `*.test.js`, `static skip = true`.
812
868
  - 식별자는 63byte(postgres 한도)를 넘으면 거부된다 — 합성 이름(`fk_…`)이 걸리면 `{name}` 으로
813
869
  짧은 이름을 명시하면 된다.
@@ -17,19 +17,11 @@ export class {{Name}} extends MegaModel {
17
17
  static table = '{{table}}'
18
18
 
19
19
  // 자동 마이그레이션 스키마(ADR-209) — 빌더 API 는 docs/guide/03-service-model-db.md §5 참조.
20
- static schema = (t) => ({
20
+ static schema = /** @param {any} t */ (t) => ({
21
21
  name: t.varchar(100).notNull(),
22
22
  createdAt: t.timestamptz().notNull(), // 생성 시 앱에서 new Date() 로 채운다(mongo 는 default 미지원)
23
23
  })
24
24
 
25
25
  // 인덱스(선택): static indexes = (t) => [t.index(['name'], { unique: true })]
26
26
 
27
- /**
28
- * _id 로 1건 조회 — `this.db` 는 native mongodb `Db`(ADR-009), 도큐먼트 API 직접 사용.
29
- * @param {import('mongodb').ObjectId} id
30
- * @returns {Promise<object|null>}
31
- */
32
- static async findById(id) {
33
- return this.db.collection(this.table).findOne({ _id: id })
34
- }
35
27
  }
@@ -15,7 +15,7 @@ export class {{Name}} extends MegaModel {
15
15
  static table = '{{table}}'
16
16
 
17
17
  // 자동 마이그레이션 스키마(ADR-204) — 빌더 API 는 docs/guide/03-service-model-db.md §5 참조.
18
- static schema = (t) => ({
18
+ static schema = /** @param {any} t */ (t) => ({
19
19
  id: t.serial().primary(),
20
20
  name: t.varchar(100).notNull(),
21
21
  createdAt: t.timestamptz().defaultNow(),
@@ -23,13 +23,4 @@ export class {{Name}} extends MegaModel {
23
23
 
24
24
  // 인덱스(선택): static indexes = (t) => [t.index(['name'], { unique: true })]
25
25
 
26
- /**
27
- * id 로 1건 조회. `this.query` 는 계측된 어댑터 query 위임(ADR-138).
28
- * @param {string|number} id
29
- * @returns {Promise<object|null>}
30
- */
31
- static async findById(id) {
32
- const { rows } = await this.query('select * from {{table}} where id = $1', [id])
33
- return rows[0] ?? null
34
- }
35
26
  }
@@ -12,27 +12,5 @@ describe('{{Name}} model (mongodb)', () => {
12
12
 
13
13
  test('schema 빌더 선언 — 자동 마이그레이션 트랙 옵트인(ADR-209)', () => {
14
14
  expect(typeof {{Name}}.schema).toBe('function')
15
- })
16
-
17
- test('findById — native Db 의 도큐먼트 API 에 위임', async () => {
18
- /** @type {any[]} */
19
- const calls = []
20
- const fakeDb = {
21
- collection: (/** @type {string} */ name) => ({
22
- findOne: async (/** @type {any} */ filter) => {
23
- calls.push({ name, filter })
24
- return { _id: filter._id }
25
- },
26
- }),
27
- }
28
- // MegaModel.db 는 getter 라 서브클래스 own property 로 가린다(테스트 한정).
29
- Object.defineProperty({{Name}}, 'db', { value: fakeDb, configurable: true })
30
- try {
31
- const doc = await {{Name}}.findById(/** @type {any} */ ('id-1'))
32
- expect(doc).toEqual({ _id: 'id-1' })
33
- expect(calls[0].name).toBe('{{table}}')
34
- } finally {
35
- delete /** @type {any} */ ({{Name}}).db
36
- }
37
- })
15
+ })
38
16
  })
@@ -13,21 +13,4 @@ describe('{{Name}} model', () => {
13
13
  test('schema 빌더 선언 — 자동 마이그레이션 트랙 옵트인(ADR-204)', () => {
14
14
  expect(typeof {{Name}}.schema).toBe('function')
15
15
  })
16
-
17
- test('findById — 어댑터 query 에 위임', async () => {
18
- const orig = {{Name}}.query
19
- /** @type {any[]} */
20
- const calls = []
21
- {{Name}}.query = async (/** @type {string} */ sql, /** @type {any[]} */ params) => {
22
- calls.push({ sql, params })
23
- return { rows: [{ id: params[0] }] }
24
- }
25
- try {
26
- const row = await {{Name}}.findById(7)
27
- expect(row).toEqual({ id: 7 })
28
- expect(calls[0].params).toEqual([7])
29
- } finally {
30
- {{Name}}.query = orig
31
- }
32
- })
33
16
  })