kintone-as-code
Version:
A CLI tool for managing kintone applications as code with type-safe TypeScript schemas
248 lines (243 loc) • 8.55 kB
JavaScript
// Query generator for kintone fields
const FIELD_TYPE_MAPPINGS = {
// Text fields
SINGLE_LINE_TEXT: { factory: 'createStringField' },
MULTI_LINE_TEXT: { factory: 'createStringField' },
RICH_TEXT: { factory: 'createStringField' },
LINK: { factory: 'createStringField' },
// Number fields
NUMBER: { factory: 'createNumberField' },
CALC: { factory: 'createNumberField' },
// Selection fields with options
DROP_DOWN: { factory: 'createDropdownField', hasOptions: true },
CHECK_BOX: { factory: 'createCheckboxField', hasOptions: true },
MULTI_SELECT: { factory: 'createCheckboxField', hasOptions: true },
RADIO_BUTTON: { factory: 'createRadioButtonField', hasOptions: true },
// Date/Time fields
DATE: { factory: 'createDateField' },
TIME: { factory: 'createTimeField' },
DATETIME: { factory: 'createDateTimeField' },
// User/Organization fields
USER_SELECT: { factory: 'createUserField' },
ORGANIZATION_SELECT: { factory: 'createOrgField' },
GROUP_SELECT: { factory: 'createGroupField' },
};
// Unsupported field types for queries
const UNSUPPORTED_FIELD_TYPES = new Set([
'FILE',
'CATEGORY',
'STATUS',
'STATUS_ASSIGNEE',
'CREATED_TIME',
'UPDATED_TIME',
'CREATOR',
'MODIFIER',
'RECORD_NUMBER',
'HR',
'GROUP',
'SPACER',
'LABEL',
]);
// フィールドタイプマッピング取得
const getFieldMapping = (type) => {
if (UNSUPPORTED_FIELD_TYPES.has(type)) {
return null;
}
return FIELD_TYPE_MAPPINGS[type] || null;
};
// クエリビルダーコード生成
export const generateQueryBuilder = (formFields, appName, options) => {
const isReservedWord = (word) => {
const reserved = new Set([
'break',
'case',
'catch',
'class',
'const',
'continue',
'debugger',
'default',
'delete',
'do',
'else',
'export',
'extends',
'finally',
'for',
'function',
'if',
'import',
'in',
'instanceof',
'new',
'return',
'super',
'switch',
'this',
'throw',
'try',
'typeof',
'var',
'void',
'while',
'with',
'yield',
'enum',
'implements',
'interface',
'let',
'package',
'private',
'protected',
'public',
'static',
'await',
]);
return reserved.has(word);
};
const quoteIfNeeded = (key) => {
const unicodeIdent = /^[\p{L}\p{Nl}$_][\p{L}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}$]*$/u;
if (key.startsWith('$') || !unicodeIdent.test(key) || isReservedWord(key)) {
return JSON.stringify(key);
}
return key;
};
const fields = formFields.properties || {};
const fieldDefinitions = [];
const imports = new Set();
const warnings = [];
let hasDateFields = false;
const includeSubtable = options?.includeSubtable === true;
const includeRelated = options?.includeRelated === true;
let hasSubtableFields = false;
let hasRelatedFields = false;
// フィールドごとの処理
Object.entries(fields).forEach(([code, field]) => {
// SUBTABLE: 特別扱い(子フィールドに限定APIを提供)
if (includeSubtable && field.type === 'SUBTABLE' && field.fields) {
hasSubtableFields = true;
const childEntries = Object.entries(field.fields);
const childLines = childEntries.map(([childCode]) => {
return ` ${childCode}: createTableSubField('${code}.${childCode}')`;
});
const key = quoteIfNeeded(code);
fieldDefinitions.push(` ${key}: {\n${childLines.join(',\n')}\n }`);
return;
}
// REFERENCE_TABLE: 表示フィールドから子フィールドを限定公開(最小: in/not in)
if (includeRelated && field.type === 'REFERENCE_TABLE') {
const displayFields = field?.referenceTable
?.displayFields;
if (Array.isArray(displayFields) && displayFields.length > 0) {
hasRelatedFields = true;
const childLines = displayFields.map((childCode) => {
return ` ${childCode}: createTableSubField('${code}.${childCode}')`;
});
const key = quoteIfNeeded(code);
fieldDefinitions.push(` ${key}: {\n${childLines.join(',\n')}\n }`);
return;
}
}
const mapping = getFieldMapping(field.type);
if (!mapping) {
// SUBTABLE は includeSubtable を明示的に false 指定されたケースでは完全に無視(コメントも出さない)
const includeSubtableExplicit = options &&
Object.prototype.hasOwnProperty.call(options, 'includeSubtable');
if (field.type === 'SUBTABLE' &&
includeSubtableExplicit &&
options?.includeSubtable === false) {
return;
}
// それ以外の未サポートフィールドは警告コメントとして残す
warnings.push(`// ${code}: ${field.type} type is not supported`);
return;
}
imports.add(mapping.factory);
// Date系フィールドのチェック
if (['DATE', 'DATETIME', 'TIME'].includes(field.type)) {
hasDateFields = true;
}
if (mapping.hasOptions && field.options) {
// オプションを持つフィールド
const options = Object.keys(field.options);
const optionsStr = options.map((opt) => `'${opt}'`).join(', ');
const key = quoteIfNeeded(code);
fieldDefinitions.push(` ${key}: ${mapping.factory}('${code}', [${optionsStr}] as const)`);
}
else {
// 通常のフィールド
const key = quoteIfNeeded(code);
fieldDefinitions.push(` ${key}: ${mapping.factory}('${code}')`);
}
});
// コンポーネントの全体的な構造(改善版)
if (hasSubtableFields || hasRelatedFields) {
imports.add('createTableSubField');
}
const content = `/**
* Query builder for ${appName}
* Auto-generated by kintone-as-code
*/
import {
${Array.from(imports).sort().join(',\n ')},
// FP Query Builder
createQueryState,
setWhere,
appendOrder,
withLimit,
withOffset,
build,
and,
or,
not${hasDateFields ? ',\n // Date functions\n TODAY,\n FROM_TODAY,\n THIS_WEEK,\n THIS_MONTH,\n THIS_YEAR,\n NOW,\n LAST_WEEK,\n LAST_MONTH,\n LAST_YEAR' : ''},
type ValidationOptions,
type Expression
} from 'kintone-as-code';
${warnings.length > 0 ? warnings.join('\n') + '\n\n' : ''}/**
* Type-safe field definitions for ${appName}
*/
export const QueryFields = {
${fieldDefinitions.join(',\n')}
} as const;
/**
* Create a new query builder instance
* @example
* const query = createQuery()
* .where(QueryFields.status.equals('active'))
* .orderBy('createdAt', 'desc')
* .limit(100)
* .build();
*/
const makeBuilder = (state: ReturnType<typeof createQueryState>) => ({
where(expr: Expression) {
return makeBuilder(setWhere(expr)(state));
},
orderBy(field: string, direction: 'asc' | 'desc' = 'asc') {
return makeBuilder(appendOrder(field, direction)(state));
},
// Typed order helper (FieldNames)
orderByField(field: FieldNames, direction: 'asc' | 'desc' = 'asc') {
return makeBuilder(appendOrder(field as string, direction)(state));
},
limit(n: number) {
return makeBuilder(withLimit(n)(state));
},
offset(n: number) {
return makeBuilder(withOffset(n)(state));
},
setValidationOptions(opts: ValidationOptions) {
// Note: optional chaining since opts fields are optional
return makeBuilder((s => (opts ? { ...s, validationOptions: opts } : s))(state));
},
build() {
return build(state);
}
});
export const createQuery = () => makeBuilder(createQueryState());
// Export types for TypeScript support
export type FieldNames = keyof typeof QueryFields;
export type AppQueryFields = typeof QueryFields;
`;
return content;
};
//# sourceMappingURL=query-generator.js.map