diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 12f6352..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,34 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'prettier', - ], - rules: { - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-empty-interface': 'off', - '@typescript-eslint/no-inferrable-types': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/no-object-literal-type-assertion': 'off', - '@typescript-eslint/no-triple-slash-reference': 'off', - '@typescript-eslint/no-unused-vars': 'off', - '@typescript-eslint/no-var-requires': 'off', - '@typescript-eslint/prefer-interface': 'off', - '@typescript-eslint/prefer-namespace-keyword': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - }, - env: { - browser: true, - node: true, - es6: true, - jest: true, - }, - overrides: [ - { - files: ['tests/**'], - plugins: ['jest'], - }, - ], -}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8e25e5..a5ff20c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,9 +5,9 @@ name: Node.js CI on: push: - branches: ["main"] + branches: ['main'] pull_request: - branches: ["main"] + branches: ['main'] jobs: static-check: @@ -19,8 +19,8 @@ jobs: persist-credentials: false - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: - node-version-file: ".node-version" - cache: "npm" + node-version-file: '.node-version' + cache: 'npm' - run: npm ci - name: Format Check run: npm run format @@ -28,11 +28,14 @@ jobs: run: npm run lint - name: Type Check run: npm run typecheck + - name: Schema Type Check + run: npm run typecheck:schema build-and-test: runs-on: ubuntu-slim strategy: + fail-fast: false matrix: node-version: [18.x, 20.x, 22.x, 24.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ @@ -41,13 +44,47 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - name: Use Node.js ${{ matrix.node-version }} + # Development tools require a newer Node.js than the SDK runtime. + - name: Use development Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: - node-version: ${{ matrix.node-version }} - cache: "npm" + node-version-file: '.node-version' + cache: 'npm' - run: npm ci - name: Build run: npm run build - - name: Unit Tests - run: npm run test + - name: Published Declaration Type Check + run: npm run typecheck:dist + - name: TypeScript Consumer Regression Matrix + run: npm run test:types + - name: Use runtime Node.js ${{ matrix.node-version }} + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: ${{ matrix.node-version }} + - name: Unit Tests and Coverage + run: npm run test:coverage + - name: Save coverage report + if: ${{ !cancelled() && hashFiles('coverage/coverage-summary.json') != '' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-node-${{ matrix.node-version }} + path: coverage/ + if-no-files-found: error + retention-days: 14 + + typescript-compatibility: + runs-on: ubuntu-slim + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: '.node-version' + cache: 'npm' + - run: npm ci + - run: npm run build + - name: Install minimum supported TypeScript separately + run: npm install --prefix "$RUNNER_TEMP/microcms-ts6" --no-save --package-lock=false typescript@6.0.2 + - name: TypeScript 6 consumer compatibility + run: MICROCMS_TYPESCRIPT_BINARY="$RUNNER_TEMP/microcms-ts6/node_modules/typescript/bin/tsc" npm run test:types diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a34da33..a779bae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,14 +3,13 @@ name: release on: push: tags: - - "v*" + - 'v*' permissions: - id-token: write # Required for OIDC contents: read jobs: - build: + precheck: runs-on: ubuntu-slim steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -18,14 +17,43 @@ jobs: persist-credentials: false - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: - node-version-file: ".node-version" - registry-url: "https://registry.npmjs.org" + node-version-file: '.node-version' + cache: 'npm' + - run: npm ci + - name: Lint + run: npm run lint + - name: Type Check + run: npm run typecheck + - name: Schema Type Check + run: npm run typecheck:schema + - name: Unit Tests and Coverage + run: npm run test:coverage + - name: Build + run: npm run build + - name: Published Declaration Type Check + run: npm run typecheck:dist + - name: TypeScript Consumer Regression Matrix + run: npm run test:types + - name: Install minimum supported TypeScript separately + run: npm install --prefix "$RUNNER_TEMP/microcms-ts6" --no-save --package-lock=false typescript@6.0.2 + - name: TypeScript 6 consumer compatibility + run: MICROCMS_TYPESCRIPT_BINARY="$RUNNER_TEMP/microcms-ts6/node_modules/typescript/bin/tsc" npm run test:types + + publish: + needs: precheck + runs-on: ubuntu-slim + permissions: + contents: read + id-token: write # Required for OIDC + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: '.node-version' + registry-url: 'https://registry.npmjs.org' package-manager-cache: false - - name: release on npm - run: | - npm ci - npm run lint - npm run typecheck - npm run test - npm run build + - run: npm ci + - run: npm run build - run: npm publish diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..a848202 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,98 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["typescript"], + "categories": { + "correctness": "off" + }, + "env": { + "browser": true, + "node": true, + "es6": true, + "jest": true + }, + "ignorePatterns": [ + "examples/generated/", + "tests/fixtures/typegen/generated/", + "tests/fixtures/typegen/performance/" + ], + "rules": { + "eslint/for-direction": "error", + "eslint/no-array-constructor": "error", + "eslint/no-async-promise-executor": "error", + "eslint/no-case-declarations": "error", + "eslint/no-class-assign": "error", + "eslint/no-compare-neg-zero": "error", + "eslint/no-cond-assign": "error", + "eslint/no-constant-condition": "error", + "eslint/no-control-regex": "error", + "eslint/no-debugger": "error", + "eslint/no-delete-var": "error", + "eslint/no-dupe-else-if": "error", + "eslint/no-duplicate-case": "error", + "eslint/no-empty": "error", + "eslint/no-empty-character-class": "error", + "eslint/no-empty-pattern": "error", + "eslint/no-ex-assign": "error", + "eslint/no-extra-boolean-cast": "error", + "eslint/no-fallthrough": "error", + "eslint/no-global-assign": "error", + "eslint/no-inner-declarations": "error", + "eslint/no-invalid-regexp": "error", + "eslint/no-irregular-whitespace": "error", + "eslint/no-loss-of-precision": "error", + "eslint/no-misleading-character-class": "error", + "eslint/no-nonoctal-decimal-escape": "error", + "eslint/no-prototype-builtins": "error", + "eslint/no-regex-spaces": "error", + "eslint/no-self-assign": "error", + "eslint/no-shadow-restricted-names": "error", + "eslint/no-sparse-arrays": "error", + "eslint/no-unsafe-finally": "error", + "eslint/no-unsafe-optional-chaining": "error", + "eslint/no-unused-labels": "error", + "eslint/no-useless-backreference": "error", + "eslint/no-useless-catch": "error", + "eslint/no-useless-escape": "error", + "eslint/no-var": "error", + "eslint/no-with": "error", + "eslint/prefer-const": "error", + "eslint/prefer-rest-params": "error", + "eslint/prefer-spread": "error", + "eslint/require-yield": "error", + "eslint/use-isnan": "error", + "eslint/valid-typeof": "error", + "typescript/ban-ts-comment": "error", + "typescript/ban-types": "error", + "typescript/no-duplicate-enum-values": "error", + "typescript/no-extra-non-null-assertion": "error", + "typescript/no-misused-new": "error", + "typescript/no-namespace": "error", + "typescript/no-non-null-asserted-optional-chain": "error", + "typescript/no-this-alias": "error", + "typescript/no-unnecessary-type-constraint": "error", + "typescript/no-unsafe-declaration-merging": "error", + "typescript/prefer-as-const": "error", + "typescript/triple-slash-reference": "error" + }, + "overrides": [ + { + "files": ["**/*.js", "**/*.mjs", "**/*.cjs"], + "rules": { + "eslint/constructor-super": "error", + "eslint/getter-return": "error", + "eslint/no-const-assign": "error", + "eslint/no-dupe-class-members": "error", + "eslint/no-dupe-keys": "error", + "eslint/no-func-assign": "error", + "eslint/no-import-assign": "error", + "eslint/no-obj-calls": "error", + "eslint/no-redeclare": "error", + "eslint/no-setter-return": "error", + "eslint/no-this-before-super": "error", + "eslint/no-undef": "error", + "eslint/no-unreachable": "error", + "eslint/no-unsafe-negation": "error" + } + } + ] +} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..e90fcee --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +examples/generated/ +tests/fixtures/typegen/generated/ +tests/fixtures/typegen/performance/ diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..9360950 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,93 @@ +# 開発ガイド + +利用者向けのAPI仕様は[README](README.md)、補完・推論をエディタで試す方法は[examples](examples/README.md)を参照してください。 + +## 開発・検証コマンド + +`.node-version`のNode.jsを使用し、リポジトリのルートで実行します。 + +```sh +npm ci +npm run build +``` + +| コマンド | 確認すること | +| ------------------------------------ | ----------------------------------------------------------------- | +| `npm run format` / `npm run lint` | Prettier・Oxlintによる検査。自動修正はそれぞれ`:fix` | +| `npm run typecheck` | SDKの実装・単体テストの型検査 | +| `npm test` / `npm run test:coverage` | Jestによる実行時テスト/カバレッジ下限の検査 | +| `npm run typecheck:schema` | ソースの公開APIによる型検査 | +| `npm run typecheck:dist` | ビルド済みの公開型定義による型検査 | +| `npm run typecheck:examples` | ビルド済みの公開型定義で利用例を検査 | +| `npm run test:types` | 公開API・配布型・利用例・回帰テスト・生成型との連携をまとめて検査 | +| `npm run test:performance` | 保存済み生成型による推論・型抽出の性能測定 | + +`typecheck:dist`・`typecheck:examples`・`test:types`・`test:performance`はビルド後に実行します。JavaScriptはtsupでES5・CommonJS・ES modules・ブラウザ向け形式へ、公開型定義は`tsconfig.declarations.json`と`scripts/finish-declarations.mjs`で`dist/`へ生成します。 + +開発用TypeScriptは7系、利用者向けの対応範囲は6.0以降です。CIでは6.0.2でも`test:types`を実行します。検証用コンパイラの切り替えには`MICROCMS_TYPESCRIPT_BINARY`へ`tsc`の絶対パスを指定します(ビルド・`typecheck`・エディタのコンパイラは変わりません)。 + +## 検証パターンと対応するテスト + +### 実行時の処理 + +通信は[MSWのハンドラー](tests/mocks/handlers.ts)またはテスト内の`fetch`モックを使用し、実サービスへリクエストしません。 + +| パターン | 対応するテスト | +| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| クライアントの設定検査、認証ヘッダー、HTTP/ネットワークエラー、JSON解析エラー、リトライ | [createClient.test.ts](tests/createClient.test.ts)、[fetch.test.ts](tests/lib/fetch.test.ts)、[error.test.ts](tests/lib/error.test.ts) | +| `get`・リスト・詳細・オブジェクト取得のURL・クエリ・戻り値、必須引数 | [get.test.ts](tests/get.test.ts)、[readMethods.test.ts](tests/readMethods.test.ts) | +| 全件・全ID取得、空結果、ページング途中の件数減少、`alternateField`の文字列検査 | [getAllContents.test.ts](tests/getAllContents.test.ts)、[getAllContentIds.test.ts](tests/getAllContentIds.test.ts)、[readMethods.test.ts](tests/readMethods.test.ts) | +| 作成・更新・削除、IDの有無、リスト/オブジェクト更新、公開状態 | [write.test.ts](tests/write.test.ts)、[createClient.test.ts](tests/createClient.test.ts) | +| `requestInit`の反映、クエリの配列・省略・0・特殊文字、値の判定 | [requestInit.test.ts](tests/requestInit.test.ts)、[parseQuery.test.ts](tests/utils/parseQuery.test.ts)、[isCheckValue.test.ts](tests/utils/isCheckValue.test.ts) | +| マネジメントAPIの設定・エラー、Blob/File/ストリーム/URLからのアップロード、名前・MIME・バイト列 | [createManagementClient.test.ts](tests/createManagementClient.test.ts)、[uploadMedia.test.ts](tests/uploadMedia.test.ts) | + +### 公開型・推論・入力制約 + +以下はTypeScriptによる検査です。クライアント呼び出しは実行しません。正常系は戻り値型の一致、異常系は対象の呼び出しで型エラーになることを確認します。 + +| パターン | 使用データ | 対応するテスト | +| -------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | +| エンドポイントとAPI形式、`contentId`の要否、従来genericsとの互換性 | テスト内の手書きスキーマ | [typedSchema.typecheck.ts](tests/typedSchema.typecheck.ts)、[typedSchema.matrix.ts](tests/typedSchema.matrix.ts) | +| `fields`の文字列・配列・`as const`・動的値、`depth: 0`〜`3`・省略・union、変数経由の入力、全件取得の禁止クエリ | 同上 | 同上 | +| `InferMicroCMSContent`、共通メタデータ、書き込みの未知キー・型・ネスト・unionの制約 | 同上 | 同上 | +| `fields`・`depth`による戻り値と実レスポンスの一致、選択キーの集合、抽出型との一致、不正なレスポンスの拒否 | 下表の実APIデータと生成型 | [generatedSchema.integration.mjs](tests/generatedSchema.integration.mjs) | +| 多段・循環参照の深さと選択、3層ネスト内の参照・拡張データ、選択・型抽出・書き込み | 下表の`tree`・`matrix`生成型と利用コード | [generatedRelations.integration.mjs](tests/generatedRelations.integration.mjs) | +| 利用者向けの読み取り・書き込み・型抽出・型エラーの例 | [examples](examples/README.md) | `typecheck:examples`(`test:types`にも含む) | + +`typedSchema.matrix.ts`は同じケースをソースから抽出した公開型/ビルド済み配布型と、`exactOptionalPropertyTypes`の有効/無効で検査します。`test:types`は上記すべてを選択したコンパイラで実行します。 + +### 生成型・保存データとの対応 + +下表のfixtureは`tests/fixtures/typegen/`内です。生成型は保存済みで、SDKのテストにはtypegenや隣接リポジトリは不要です。 + +| データ | 用途・対応する検証 | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [schemas.json](tests/fixtures/typegen/schemas.json)、[examplesの生成型](examples/generated/microcms-types.ts) | リスト/オブジェクトのAPI形式とフィールド定義。`generatedSchema.integration.mjs`で推論・選択キーを検査 | +| [responses.json](tests/fixtures/typegen/responses.json) | 通常取得・`fields`・`depth`別の実レスポンス、各フィールドの値あり・空値、5種類の繰り返し要素。同テストで推論型へ代入し、メタデータ・値・選択肢・未選択フィールドの改変を拒否 | +| [writes.json](tests/fixtures/typegen/writes.json)、[lifecycle.json](tests/fixtures/typegen/lifecycle.json) | 作成・更新入力と、作成・更新・削除のリクエスト・結果。同テストで書き込み型と戻り値型を検査(操作は再実行しない) | +| [generated/with-extensions.ts](tests/fixtures/typegen/generated/with-extensions.ts)、[extensions.ts](tests/fixtures/typegen/generated/extensions.ts) | ユーザー定義の拡張`data`型。同テストで実レスポンスとの一致・不正なread/writeの拒否 | +| [generated/tree.ts](tests/fixtures/typegen/generated/tree.ts)、[tree-usage.ts](tests/fixtures/typegen/generated/tree-usage.ts) | 多段・循環参照、`depth: 0`〜`3`の境界、参照の`fields`と型抽出。`generatedRelations.integration.mjs`で検査 | +| [generated/matrix.ts](tests/fixtures/typegen/generated/matrix.ts)、[matrix-usage.ts](tests/fixtures/typegen/generated/matrix-usage.ts)、[matrix-extension.ts](tests/fixtures/typegen/generated/matrix-extension.ts) | 3層の繰り返し内の単一・複数参照・拡張データ、リスト/オブジェクト、`depth: 0`〜`3`、`fields`・型抽出、実APIの書き込み入力と不正入力。同テストで検査 | +| [generated/required.ts](tests/fixtures/typegen/generated/required.ts)、[required-strict.ts](tests/fixtures/typegen/generated/required-strict.ts)、[required-usage.ts](tests/fixtures/typegen/generated/required-usage.ts) | typegenの`required-write-values.json`から生成。必須項目・ネスト・非空配列・読み取りオプション・型抽出を`generatedRelations.integration.mjs`で検査。生成ルールとAPIの例外はtypegen側で検査 | +| [generated/required-combinations.ts](tests/fixtures/typegen/generated/required-combinations.ts)、[required-combinations-usage.ts](tests/fixtures/typegen/generated/required-combinations-usage.ts) | typegenの`required-combinations.mjs`から生成。3層の異種繰り返し・必須/任意の混在・オブジェクト形式・変数経由の不正入力を同テストで検査。`exactOptionalPropertyTypes`の有無も確認 | +| [generated/read-references.ts](tests/fixtures/typegen/generated/read-references.ts)、[read-references-usage.ts](tests/fixtures/typegen/generated/read-references-usage.ts) | typegenの`read-reference-schemas.mjs`から`trustReadFieldConstraints: { trustReferenceAvailability: true }`で生成。必須・最小件数1以上の参照について、`fields`・型抽出・リスト/オブジェクト・深度0・2で非空配列と非nullを同テストで検査 | + +### 検証方針 + +`filters`・`orders`・`alternateField`のスキーマ検査と、旧リッチエディタのobject形式の自動推論は[公開仕様の対象外](README.md)です。型生成と実レスポンスの空値の一致はtypegen側で検査し、拡張フィールドUI独自のクリア値はユーザー定義の`data`型に委ねます。 + +## typegenとの分担・データ更新 + +SDKはメソッドの推論・入力制約・型抽出と通信処理、typegenはスキーマ取得と生成型の正しさを検査します。型生成・実APIデータの検証範囲は、typegenリポジトリの`DEVELOPMENT.md`を参照してください。 + +typegenの出力形式を変更した場合は、上表と利用例の生成型も更新し、`npm run build`・`npm run test:types`を実行します。保存データは入力・クエリ・レスポンスの対応を保ち、URL・メールアドレスをダミー化します。認証情報は保存しません。各リポジトリのCIは単独で動きます。 + +不具合修正では再現する異常系と近い正常系を残し、メソッド共通の制約は`typedSchema.matrix.ts`に追加します。共通処理は`tests/helpers/`にあります。 + +性能測定には[performance](tests/fixtures/typegen/performance/)の4API・50API/各100フィールド・循環参照10APIを使い、`depth`・`fields`・型抽出・書き込みを`tsc --extendedDiagnostics`で3回測定します。固定時間での合否判定には使いません。 + +## カバレッジ・CI・リリース + +- [カバレッジ設定](jest.config.js):Jestの`babel`プロバイダーでファイルごとに文・行98%、分岐90%、関数100%。型のみのファイル・再エクスポートは対象外で、公開型はコンパイルで検査します。レポートは`coverage/`のHTML・LCOV・JSONです。 +- [CI](.github/workflows/ci.yml):インストール・ビルド・静的検査は`.node-version`、実行時テストとカバレッジはNode.js18・20・22・24系。TypeScript7系と6.0.2で公開型・利用例・生成型との連携を検査し、カバレッジレポートを14日間保存します。 +- [リリース](.github/workflows/release.yml):`v*`タグで起動し、`precheck`のLint・型・実行時テスト・カバレッジ・TypeScript6互換性の検査成功後にnpmへ公開します。公開ジョブだけにOIDC権限を付与します。タグはレビューと公開準備の完了後に作成してください。通常の開発コマンド・テストCIでは公開しません。 diff --git a/README.md b/README.md index c428f0c..549c0ed 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,16 @@ JavaScriptやNode.jsのアプリケーションからmicroCMSのAPIと簡単に 詳細は[SDKの保守方針](https://document.microcms.io/manual/limitations#h8e929adf81)をご覧ください。 +## 動作環境 + +- Node.js 18+(22+を推奨、ブラウザから利用する場合は不要) +- TypeScript6.0+(型を利用する場合) + ## チュートリアル -公式ドキュメントの [チュートリアル](https://document.microcms.io/tutorial/javascript/javascript-top)をご覧ください。 +まず簡単な組み込みを試したい場合は、公式の[JavaScriptチュートリアル](https://document.microcms.io/tutorial/javascript/javascript-top)を参照してください。ブラウザとNode.jsでの基本的な利用手順を確認できます。 -## はじめに +## セットアップ ### インストール @@ -30,9 +35,6 @@ $ npm install microcms-js-sdk $ yarn add microcms-js-sdk ``` -> [!IMPORTANT] -> v3.0.0以上を使用する場合は、Node.jsのv18以上が必要です。 - #### ブラウザ(セルフホスティング) [リリースページ](https://github.com/microcmsio/microcms-js-sdk/releases)から`microcms-js-sdk-x.y.z.tgz`をダウンロードして解凍してください。その後、お好みのサーバーにアップロードして使用してください。対象ファイルは `./dist/umd/microcms-js-sdk.js` です。 @@ -93,24 +95,25 @@ const client = createClient({ }); ``` +TypeScriptでの型指定と推論は[TypeScript](#typescript)を参照してください。 + ### APIメソッド 以下の表は、microCMS JavaScript SDKの各メソッドがリスト形式のAPIまたはオブジェクト形式のAPI、どちらで使用できるかを示しています。 -| メソッド | リスト形式 | オブジェクト形式 | -|-------------------|-------------|---------------| -| getList | ✔️ | | -| getListDetail | ✔️ | | -| getObject | | ✔️ | -| getAllContentIds | ✔️ | | -| getAllContents | ✔️ | | -| create | ✔️ | | -| update | ✔️ | ✔️ | -| delete | ✔️ | | +| メソッド | リスト形式 | オブジェクト形式 | +| ---------------- | ---------- | ---------------- | +| getList | ✅ | | +| getListDetail | ✅ | | +| getObject | | ✅ | +| getAllContentIds | ✅ | | +| getAllContents | ✅ | | +| create | ✅ | | +| update | ✅ | ✅ | +| delete | ✅ | | > [!NOTE] -> - 「リスト形式」の✔️は、APIの型がリスト形式に設定されている場合に使用できるメソッドを示します。 -> - 「オブジェクト形式」の✔️は、APIの型がオブジェクト形式に設定されている場合に使用できるメソッドを示します。 +> 汎用の取得メソッド`get`は非推奨です。`getList`・`getListDetail`・`getObject`を使用してください。スキーマを指定したクライアントでも、`get`の戻り値型は自動推論されません。 ### コンテンツ一覧の取得 @@ -143,7 +146,7 @@ client ids: 'foo', filters: 'publishedAt[greater_than]2021-01-01T03:00:00.000Z', depth: 1, - } + }, }) .then((res) => console.log(res)) .catch((err) => console.error(err)); @@ -176,11 +179,10 @@ client draftKey: 'abcd', fields: 'id,title', depth: 1, - } + }, }) .then((res) => console.log(res)) .catch((err) => console.error(err)); - ``` ### オブジェクト形式のコンテンツの取得 @@ -239,7 +241,7 @@ client #### コンテンツID以外のフィールドの値を全件取得 -`alternateField`プロパティにフィールドIDを指定することで、コンテンツID以外のフィールドの値を全件取得できます。 +`alternateField`プロパティにフィールドIDを指定することで、コンテンツID以外のフィールドの値を全件取得できます。取得した値が文字列でない場合は、実行時にエラーになります。 ```javascript client @@ -272,7 +274,10 @@ client client .getAllContents({ endpoint: 'endpoint', - queries: { filters: 'createdAt[greater_than]2021-01-01T03:00:00.000Z', orders: '-createdAt' }, + queries: { + filters: 'createdAt[greater_than]2021-01-01T03:00:00.000Z', + orders: '-createdAt', + }, }) .then((res) => console.log(res)) .catch((err) => console.error(err)); @@ -453,147 +458,189 @@ client .catch((err) => console.error(err)); ``` -### TypeScript +### CustomRequestInit -`getList`メソッド、`getListDetail`メソッド、`getObject`メソッドはデフォルトのレスポンスの型を定義しています。 +#### Next.js App Router -#### getListメソッドのレスポンスの型 +Next.jsのApp Routerで利用されるfetchのcacheオプションを指定できます。 -```typescript -type Content = { - text: string, -}; -/** - * { - * contents: Content[]; // 設定したスキーマの型を格納する配列 - * totalCount: number; - * limit: number; - * offset: number; - * } - */ -client.getList({ /* その他のプロパティ */ }) -``` +指定可能なオプションは、Next.jsの公式ドキュメントを参照してください。 -#### getListDetailメソッドのレスポンスの型 +[Functions: fetch \| Next\.js](https://nextjs.org/docs/app/api-reference/functions/fetch) -```typescript -type Content = { - text: string, -}; -/** - * { - * id: string; - * createdAt: string; - * updatedAt: string; - * publishedAt?: string; - * revisedAt?: string; - * text: string; // 設定したスキーマの型 - * } - */ -client.getListDetail({ /* その他のプロパティ */ }) +```ts +const response = await client.getList({ + customRequestInit: { + next: { + revalidate: 60, + }, + }, + endpoint: 'endpoint', +}); ``` -#### getObjectメソッドのレスポンスの型 +#### AbortController: abortメソッド -```typescript -type Content = { - text: string, -}; -/** - * { - * createdAt: string; - * updatedAt: string; - * publishedAt?: string; - * revisedAt?: string; - * text: string; // 設定したスキーマの型 - * } - */ -client.getObject({ /* その他のプロパティ */ }) -``` +fetchリクエストを中断できます。 -#### getAllContentIdsメソッドのレスポンスの型 +```ts +const controller = new AbortController(); +const timeoutId = setTimeout(() => { + controller.abort(); +}, 1000); -```typescript -/** - * string[] - */ -client.getAllContentIds({ /* その他のプロパティ */ }) +try { + const response = await client.getObject({ + customRequestInit: { + signal: controller.signal, + }, + endpoint: 'config', + }); +} finally { + clearTimeout(timeoutId); +} ``` -#### 型安全なコンテンツの登録 +### TypeScript -`content`の型は`Content`であるため、型安全なコンテンツの登録が可能です。 +補完や推論をエディタで試す場合は、[型付きクライアントの利用例](examples/README.md)を参照してください。 -```typescript -type Content = { - title: string; - body?: string; -}; +コンテンツAPIの型は、サービス全体のスキーマをクライアント生成時に指定する方法と、メソッドごとに型を指定する方法があります。取得クエリに対応したコンテンツ型だけを取り出すこともできます。 -client.create({ - endpoint: 'endpoint', - content: { - title: 'タイトル', - body: '本文', - }, +#### クライアント生成時にスキーマを指定する + +[microcms-typegen](https://github.com/microcmsio/microcms-ts-typegen#readme)で作成したサービスのスキーマ型を渡すと、TypeScriptのコンパイル時に次の型検査・推論が利用できます。 + +- **入力の型検査**:登録済みのエンドポイントとAPI形式、静的に確定する`fields`のフィールドパスと`depth`の組み合わせを検査します。クエリの項目名、値の基本型、`depth`の範囲(0〜3)も検査します。 +- **戻り値型の推論**:エンドポイントと`depth`・`fields`に応じて、`getList`、`getListDetail`、`getObject`、`getAllContents`の戻り値型を推論します。 + +以下は型生成器の設定が`services.main`の場合の例です。スキーマ型の名前はサービス名に応じて変わります。 + +```ts +import { createClient } from 'microcms-js-sdk'; +import type { MainServiceSchema } from './microcms-types'; + +const client = createClient({ + serviceDomain: 'YOUR_DOMAIN', + apiKey: 'YOUR_API_KEY', +}); + +const response = await client.getList({ + endpoint: 'blogs', + queries: { depth: 2, fields: ['title', 'category.name'] }, }); ``` -#### 型安全なコンテンツの編集 +##### 型検査・推論の対象外 -`content`は`Partial`型であるため、編集したいプロパティだけを渡せます。 +以下の項目は型検査・推論には未対応です。今後対応を検討します。 -```typescript -type Content = { - title: string; - body?: string; -}; +| 項目 | 検査・推論しない内容 | +| --- | --- | +| `filters` | フィールド名・演算子・値の正しさ | +| `orders` | フィールド名・並び順指定の正しさ | +| `alternateField` | 指定先フィールドの存在と値の型 | +| `richEditorFormat: 'object'` | 戻り値型の自動推論。利用時はメソッドの型引数が必要(省略すると型エラー) | -client.update({ - endpoint: 'endpoint', - content: { - body: '本文', - }, +#### メソッドごとに型を指定する + +従来どおり、スキーマ型を渡さずにクライアントを作成し、読み取りメソッドの型引数を指定できます。型付きクライアントでも明示的なメソッド型引数を使用できます。この呼び出しではスキーマによる`depth`・`fields`の型推論より指定した型を優先します。指定した型と実際のレスポンスが一致するかは利用側で確認してください。 + +```ts +import { createClient } from 'microcms-js-sdk'; + +type Content = { title: string }; +const client = createClient({ + serviceDomain: 'YOUR_DOMAIN', + apiKey: 'YOUR_API_KEY', +}); + +const list = await client.getList({ endpoint: 'blogs' }); +const detail = await client.getListDetail({ + endpoint: 'blogs', + contentId: 'blog-id', }); +const object = await client.getObject({ endpoint: 'settings' }); ``` -### CustomRequestInit +#### クエリに応じたコンテンツ型を取り出す -#### Next.js App Router +コンポーネントのpropsなどで、取得時と同じクエリに対応したコンテンツ1件の型が必要な場合は`InferMicroCMSContent`を使います。 -Next.jsのApp Routerで利用されるfetchのcacheオプションを指定できます。 +```ts +import { createClient, type InferMicroCMSContent } from 'microcms-js-sdk'; +import type { MainServiceSchema } from './microcms-types'; -指定可能なオプションは、Next.jsの公式ドキュメントを参照してください。 +const client = createClient({ + serviceDomain: 'YOUR_DOMAIN', + apiKey: 'YOUR_API_KEY', +}); +const queries = { depth: 2, fields: ['title', 'category.name'] } as const; +const response = await client.getList({ endpoint: 'blogs', queries }); -[Functions: fetch \| Next\.js](https://nextjs.org/docs/app/api-reference/functions/fetch) +type BlogCardProps = { + blog: InferMicroCMSContent; +}; +``` + +このようにクエリを変数に分ける場合は、`as const`で`depth`と`fields`の具体的な値を保持します。`const queries = { ... }`だけでは`depth`は`number`、`fields`は`string[]`に広がります。メソッドへクエリを直接書く場合は`as const`は不要です。 + +`InferMicroCMSContent`だけで不正な`fields`の型を宣言しても、その場では型エラーにならず、結果は`unknown`です。同じクエリを型付きクライアントへ渡すと、静的に分かる誤記は呼び出し箇所で型エラーになります。 + +#### スキーマから書き込みデータの型を推論する + +型生成器で作成した`MainServiceSchema`を指定すると、`create`・`update`の`endpoint`からフィールド名と値の型を推論します。参照はコンテンツID、画像とファイルはURL文字列を指定します。`create`・`delete`はリスト形式API、`update`はリスト形式・オブジェクト形式APIで利用できます。`delete`もエンドポイントを型検査します。 ```ts -const response = await client.getList({ - customRequestInit: { - next: { - revalidate: 60, - }, - }, - endpoint: 'endpoint', +import { createClient } from 'microcms-js-sdk'; +import type { MainServiceSchema } from './microcms-types'; + +const client = createClient({ + serviceDomain: 'YOUR_DOMAIN', + apiKey: 'YOUR_API_KEY', +}); + +await client.create({ + endpoint: 'blogs', + content: { title: 'タイトル', category: 'category-id' }, +}); +await client.update({ + endpoint: 'blogs', + contentId: 'blog-id', + content: { title: '更新したタイトル' }, }); +await client.delete({ endpoint: 'blogs', contentId: 'blog-id' }); ``` -#### AbortController: abortメソッド +生成された入力型は`MainServiceSchema['blogs']['create']`や`MainServiceSchema['blogs']['update']`として取り出せます。 -fetchリクエストを中断できます。 +#### メソッドごとに書き込み型を指定する + +従来の`create`・`update`も利用できます。型付きクライアントで明示すると、その呼び出しでは生成スキーマの書き込み型より指定した型を優先します。`create`は`Content`、`update`は`Partial`として`content`を検査します。指定した型とAPIスキーマの一致は利用側で確認してください。 ```ts -const controller = new AbortController(); -const response = await client.getObject({ - customRequestInit: { - signal: controller.signal, - }, - endpoint: 'config', +type TitleOnly = { title: string }; + +await client.update({ + endpoint: 'blogs', + contentId: 'blog-id', + content: { title: '更新したタイトル' }, }); +``` -setTimeout(() => { - controller.abort(); -}, 1000); +### ヒント + +#### 読み取り用と書き込み用で別々のAPIキーを使用する + +```javascript +const readClient = createClient({ + serviceDomain: 'serviceDomain', + apiKey: 'readApiKey', +}); +const writeClient = createClient({ + serviceDomain: 'serviceDomain', + apiKey: 'writeApiKey', +}); ``` ## マネジメントAPI @@ -696,9 +743,11 @@ client ### TypeScript -#### uploadMediaメソッドのパラメータの型 +#### メディアアップロードの引数 -```typescript +マネジメントAPIの`uploadMedia`は、次の形式のデータを受け取ります。 + +```ts type UploadMediaRequest = | { data: File } | { data: Blob; name: string } @@ -713,9 +762,9 @@ function uploadMedia(params: UploadMediaRequest): Promise<{ url: string }>; ## エラーハンドリング -microCMS APIへのリクエストに失敗した場合、エラーは通常の`Error`として扱えます。`console.error(error)`でエラーメッセージとスタックトレースを出力でき、HTTPエラーの場合はHTTPステータスとAPIから返されたエラーメッセージも含まれます。 +コンテンツAPIとマネジメントAPIのどちらでも、SDKが生成したリクエストエラーは通常の`Error`として扱えます。`console.error(error)`でエラーメッセージとスタックトレースを出力でき、HTTPエラーの場合はHTTPステータスとAPIから返されたエラーメッセージも含まれます。 -さらに、`isMicroCMSRequestError`でエラーを判定すると、リクエストに関する追加情報として`status`、`url`、`originalError`を個別に参照できます。 +SDKが生成したリクエストエラーは、両クライアントで共通の`isMicroCMSRequestError`を使って判定できます。判定後は`status`、`url`、`originalError`を個別に参照できます。以下はコンテンツAPIの例です。マネジメントAPIの`uploadMedia`でも、microCMSへのリクエストでSDKが生成したエラーは同様に判定できます。 ```typescript import { createClient, isMicroCMSRequestError } from 'microcms-js-sdk'; @@ -740,11 +789,13 @@ try { } ``` -| プロパティ | HTTPエラー | ネットワークエラー | -| --------------- | ----------------------------- | --------------------------- | -| `status` | HTTPステータスコード | `undefined` | -| `url` | リクエスト先URL | リクエスト先URL | -| `originalError` | `undefined` | `fetch`が投げた元の値 | +| プロパティ | HTTPエラー | ネットワークエラー | +| --------------- | -------------------- | --------------------- | +| `status` | HTTPステータスコード | `undefined` | +| `url` | リクエスト先URL | リクエスト先URL | +| `originalError` | `undefined` | `fetch`が投げた元の値 | + +`uploadMedia`の引数検証やアップロード元URLの取得で起きたエラーは、microCMSへのリクエスト前に発生するため、`isMicroCMSRequestError`で判定できない場合があります。 `url`に`draftKey`が含まれる場合、その値は`***`にマスクされます。リクエストヘッダー、リクエストボディ、`Response`オブジェクトはエラーへ追加されません。 @@ -752,20 +803,9 @@ try { 追加されるプロパティは非列挙です。そのため、既存の`message`、`toString()`、`Object.keys()`、`JSON.stringify()`の結果には影響しません。 -## ヒント - -### 読み取り用と書き込み用で別々のAPIキーを使用する +## 開発者向け情報 -```javascript -const readClient = createClient({ - serviceDomain: 'serviceDomain', - apiKey: 'readApiKey', -}); -const writeClient = createClient({ - serviceDomain: 'serviceDomain', - apiKey: 'writeApiKey', -}); -``` +SDKの開発環境・テスト・fixture・CIについては、[開発ガイド](DEVELOPMENT.md)を参照してください。 ## ライセンス diff --git a/README_en.md b/README_en.md index 1cd0488..8cf6c92 100644 --- a/README_en.md +++ b/README_en.md @@ -12,11 +12,16 @@ The current maintenance level of this SDK is `Active`. For details, see the [SDK maintenance policy](https://document.microcms.io/en/manual/limitations#h36932e94f2). +## Requirements + +- Node.js 18+ (22+ recommended; not required when using the SDK in a browser) +- TypeScript 6.0+ (when using types) + ## Tutorial -See the [official tutorial](https://document.microcms.io/tutorial/javascript/javascript-top). +To try a basic integration first, follow the official [JavaScript tutorial](https://document.microcms.io/en/tutorial/javascript/javascript-top). It covers getting started in a browser and with Node.js. -## Getting started +## Setup ### Installation @@ -30,10 +35,7 @@ or $ yarn add microcms-js-sdk ``` -> [!IMPORTANT] -> v3.0.0 or later requires Node.js **v18 or higher**. - -#### Browser(Self-hosting) +#### Browser (self-hosting) Download and unzip `microcms-js-sdk-x.y.z.tgz` from the [releases page](https://github.com/microcmsio/microcms-js-sdk/releases). Then, host it on any server of your choice and use it. The target file is `./dist/umd/microcms-js-sdk.js`. @@ -41,7 +43,7 @@ Download and unzip `microcms-js-sdk-x.y.z.tgz` from the [releases page](https:// ``` -#### Browser(CDN) +#### Browser (CDN) Please load and use the URL provided by an external provider. @@ -74,7 +76,7 @@ or import { createClient } from 'microcms-js-sdk'; //ES6 ``` -#### Usage with a browser +#### Browser ```html