kintone-as-code
Version:
A CLI tool for managing kintone applications as code with type-safe TypeScript schemas
476 lines (368 loc) • 13.6 kB
Markdown
# kintone-as-code
[](https://badge.fury.io/js/kintone-as-code)
[](https://github.com/kimotokensei/kintone-as-code/actions/workflows/ci.yml)
[](https://opensource.org/licenses/MIT)
[日本語](README.ja.md)
Manage kintone app configurations as code with type safety using Effect-TS.
## Architecture (Summary)
- Functional Core, Imperative Shell
- Core (pure functions in `src/query/*`): expressions, fields, FP builder, validation
- Shell (side effects): CLI/commands and code generator
- Public API is FP-only. No OO facade is provided. The method-chaining `createQuery()` is a helper emitted in generated files `apps/{name}.query.ts`, not a public package API.
## Features
- 🔒 **Type-safe** - Full TypeScript support with kintone-effect-schema
- 📝 **Export from kintone** - Generate schema files from existing apps
- 🚀 **Apply changes** - Update existing apps with schema changes and automatically add new fields
- ✨ **Create apps** - Create new kintone apps from schema definitions
- 🔧 **Environment management** - Support multiple kintone environments
- 🎯 **Effect-TS powered** - Leverage the power of Effect-TS for schema validation
- 📋 **Record Schema Generation** - Generates static, copy-paste-friendly record schemas for customization
## Installation
```bash
npm install -g kintone-as-code
```
## Prerequisites
- Node.js 18 or higher
- kintone environment with API access
## Quick Start
### 1. Initialize project
```bash
kintone-as-code init
```
This creates a `kintone-as-code.config.js` file with your environment settings.
### 2. Export an existing app
```bash
# Default: generates query and record-schema as well
kintone-as-code export --app-id 123 --name customer-app
# Suppress generation (backward-compatible --with-* also supported)
kintone-as-code export --app-id 123 --name customer-app --no-query
kintone-as-code export --app-id 123 --name customer-app --no-record-schema
# Include related/subtable fields as dot-notation (minimal: in/not in only)
kintone-as-code export --app-id 123 --name customer-app \
--include-related \
--include-subtable
```
This generates:
- `apps/customer-app.schema.ts` - Fully typed field definitions
- `apps/customer-app.record-schema.ts` - Static, type-safe record validation schema (copy-paste friendly)
- `apps/customer-app.query.ts` - Type-safe query builder for kintone API (generated by default)
- With `--include-related`, `REFERENCE_TABLE` displayFields are exposed minimally via `createTableSubField('parent.child')` (in/not in only)
- With `--include-subtable`, subtable child fields are exposed with the same minimal API
### 3. Apply changes to existing app
```bash
kintone-as-code apply --app-id 123 --schema apps/customer-app.schema.ts
```
This updates the app with any schema changes and automatically adds new fields.
### 4. Create a new app from schema
```bash
kintone-as-code create --schema apps/customer-app.schema.ts --name "Customer App Copy"
```
This creates a new app with all fields defined in the schema.
### 5. Define app schema
The exported schema uses kintone-effect-schema for complete type safety:
```typescript
import { defineAppSchema } from 'kintone-as-code';
import { APP_IDS } from './utils/app-ids';
import type {
SingleLineTextFieldProperties,
NumberFieldProperties,
SubtableFieldProperties,
} from 'kintone-effect-schema';
// Individual field definitions with complete type information
export const companyNameField: SingleLineTextFieldProperties = {
type: 'SINGLE_LINE_TEXT',
code: '会社名',
label: '会社名',
required: true,
unique: true,
maxLength: '100',
};
export const revenueField: NumberFieldProperties = {
type: 'NUMBER',
code: '売上高',
label: '年間売上高',
unit: '円',
unitPosition: 'AFTER',
};
// Subtable with nested fields
export const productsField: SubtableFieldProperties = {
type: 'SUBTABLE',
code: 'products',
fields: {
productName: {
type: 'SINGLE_LINE_TEXT',
code: 'productName',
label: '商品名',
required: true,
},
price: {
type: 'NUMBER',
code: 'price',
label: '単価',
unit: '円',
},
},
};
// App fields configuration
export const appFieldsConfig = {
properties: {
会社名: companyNameField,
売上高: revenueField,
products: productsField,
},
};
// App schema definition
export default defineAppSchema({
// APP_IDS central registry (recommended to align with generated files)
appId: APP_IDS.CUSTOMER_APP,
name: 'Customer Management',
description: 'Customer information management app',
fieldsConfig: appFieldsConfig,
});
```
## Configuration
### App ID management
Use `utils/app-ids.ts` to centrally manage app IDs. It is automatically updated by the `export` command.
### Configuration File
`kintone-as-code.config.js`:
```javascript
export default {
default: 'production',
environments: {
production: {
auth: {
baseUrl: process.env.KINTONE_BASE_URL,
username: process.env.KINTONE_USERNAME,
password: process.env.KINTONE_PASSWORD,
},
},
development: {
auth: {
baseUrl: process.env.KINTONE_BASE_URL,
username: process.env.KINTONE_USERNAME,
password: process.env.KINTONE_PASSWORD,
},
},
},
};
```
## Integration with kintone-effect-schema
This tool is designed to work seamlessly with [kintone-effect-schema](https://github.com/Kensei-Kimoto/kintone-effect-schema), which provides:
- Complete type definitions for all kintone field types
- Runtime validation using Effect-TS
- Support for Japanese field codes
- Automatic handling of empty values
## Commands
### Docs index
- Overview (IaC): `docs/overview.ja.md`
- Config: `docs/config.ja.md`
- Converter & Schemas: `docs/converter-and-schemas.ja.md`
- Export/Apply/Create: `docs/export-apply-create.ja.md`
- Query Builder: `docs/query-builder.ja.md`
- Query Cookbook: `docs/query-cookbook.ja.md`
- Architecture: `docs/architecture.ja.md`
### init
Initialize a new kintone-as-code project:
```bash
kintone-as-code init [options]
Options:
-f, --force Force overwrite existing files
```
### export
Export kintone app configuration to TypeScript:
```bash
kintone-as-code export [options]
Options:
--app-id <id> App ID to export (required)
--name <name> Schema file name (required)
--env <env> Environment name
--output <dir> Output directory (default: "apps")
--with-record-schema Generate record schema file (default: true)
--no-record-schema Skip record schema generation
--with-query Generate query builder file (default: true)
--no-query Skip query builder generation
```
The export command now generates three files by default:
1. **Field Schema** (`{name}.schema.ts`) - Field definitions and configurations
2. **Record Schema** (`{name}.record-schema.ts`) - Type-safe record validation with Effect Schema
3. **Query Builder** (`{name}.query.ts`) - Type-safe query builder for kintone REST API
### apply
Apply schema changes to an existing kintone app:
```bash
kintone-as-code apply [options]
Options:
--app-id <id> App ID to update (optional; if omitted, uses appId from schema)
--schema <path> Path to schema file (required)
--env <env> Environment name
```
Features:
- Updates existing fields with type-safe validation
- Automatically detects and adds new fields
- Deploys changes after successful update
### create
Create a new kintone app from a schema file:
```bash
kintone-as-code create [options]
Options:
--schema <path> Path to schema file (required)
--name <name> Override app name from schema
--space <id> Create app in specific space
--thread <id> Thread ID in the space (when creating inside a space)
--env <env> Environment name
```
Features:
- Creates new app with all fields defined in schema
- Supports creating apps in specific spaces
- Automatically deploys the app after creation
## Record Schema Usage
The generated record schema provides type-safe validation for kintone records with automatic normalization:
```typescript
import { KintoneRestAPIClient } from '@kintone/rest-api-client';
import {
validateRecord,
type AppRecord,
} from './apps/customer-app.record-schema';
// Initialize client
const client = new KintoneRestAPIClient({
baseUrl: process.env.KINTONE_BASE_URL!,
auth: {
username: process.env.KINTONE_USERNAME!,
password: process.env.KINTONE_PASSWORD!,
},
});
// Fetch and validate record with automatic normalization
const response = await client.record.getRecord({
app: 123,
id: 1,
});
const validatedRecord: AppRecord = validateRecord(response.record);
// validatedRecord is fully typed and normalized (no type assertions needed)
// Empty strings in number fields → null, undefined → '', etc.
```
### Example of generated Record Schema (simple)
```ts
import { Schema } from 'effect';
import {
SingleLineTextFieldSchema,
NumberFieldSchema,
decodeKintoneRecord,
} from 'kintone-effect-schema';
// Static output example
export const RecordSchema = Schema.Struct({
title: SingleLineTextFieldSchema,
amount: NumberFieldSchema,
});
export type AppRecord = Schema.Schema.Type<typeof RecordSchema>;
export const validateRecord = (record: Record<string, unknown>): AppRecord => {
const normalized = decodeKintoneRecord(record);
return Schema.decodeUnknownSync(RecordSchema)(normalized);
};
```
### JavaScript API Usage (Customization)
```typescript
import {
validateRecord,
type AppRecord,
} from './apps/customer-app.record-schema';
kintone.events.on('app.record.detail.show', (event) => {
// Same function works for JavaScript API
const validatedRecord: AppRecord = validateRecord(event.record);
// Handles all empty value inconsistencies automatically
return event;
});
```
## Query Builder Usage
The generated query builder provides type-safe query construction with IDE auto-completion:
```typescript
import { QueryFields, createQuery } from './apps/customer-app.query';
import { and, or } from 'kintone-as-code';
// All field names are auto-completed
const { 会社名, ステータス, 売上高, 担当者 } = QueryFields;
// Build type-safe queries
// OO facade (method-chain)
const query = createQuery()
.where(
and(
会社名.like('*サイボウズ*'),
売上高.greaterThan(1000000),
ステータス.in(['商談中', '受注'])
)
)
.orderBy('売上高', 'desc')
.limit(100)
.build();
// Use with kintone REST API
const client = new KintoneRestAPIClient({
/* ... */
});
// Functional API (pure functions)
import {
createQueryState,
setWhere,
appendOrder,
withLimit,
build,
} from 'kintone-as-code';
const query2 = build(
withLimit(100)(
appendOrder(
'売上高',
'desc'
)(
setWhere(
and(
会社名.like('*サイボウズ*'),
売上高.greaterThan(1000000),
ステータス.in(['商談中', '受注'])
)
)(createQueryState())
)
)
);
const records = await client.record.getRecords({
app: 123,
query: query,
});
```
### Helper methods
- Strings: `contains()/startsWith()/endsWith()`
- Numbers, Date, DateTime, and Time: `between(min, max)`
- Custom function names: `customDateFunction(name, ...args)` / `customUserFunction(name, ...args)`
### Query Features
- **Type-safe field access**: All fields are typed based on their kintone field types
- **Field-specific operators**: Only valid operators for each field type are available
- **kintone functions**: Support for `TODAY()`, `LOGINUSER()`, `THIS_MONTH()`, etc.
- **Complex conditions**: Combine with `and()`, `or()`, `not()`
- **Auto-completion**: IDE provides suggestions for fields and methods
Note: The query builder is not exposed as a public API. Internally we follow FP design; if we expose it in the future, the FP API will be the only supported style.
### Note: No `raw()` escape hatch
Direct raw query insertion via `raw()` is not provided. Instead, use
`contains/startsWith/endsWith`, `between(min, max)`, and
`customDateFunction/customUserFunction` as escape hatches.
### Field Type Examples
```typescript
// String fields support like/not like
会社名.like('*株式会社*');
会社名.notLike('*test*');
// Number fields support comparison operators
売上高.greaterThan(1000000);
売上高.lessThanOrEqual(5000000);
// Dropdown fields use in/not in
ステータス.in(['商談中', '受注']);
ステータス.notIn(['失注', 'キャンセル']);
// Date fields support date functions
契約日.equals(TODAY());
期限日.lessThan(FROM_TODAY(7, 'DAYS'));
登録日.in([THIS_MONTH()]);
// User fields support user functions
担当者.equals(LOGINUSER());
作成者.in(['user1', 'user2']);
```
## Best Practices
1. **Version Control**: Commit your schema files to track app configuration changes
2. **Centralized APP_IDS**: Manage app IDs in `utils/app-ids.ts` (kept up-to-date by export)
3. **Type Safety**: Leverage TypeScript's type checking to catch configuration errors early
4. **Code Review**: Review schema changes as part of your development process
5. **Record Validation**: Use generated record schemas in your customization code for type-safe data handling
## License
MIT