UNPKG

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
// 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