UNPKG

lemon-devkit

Version:

Lemon Serverless Micro-Service Platform for local development

160 lines 7.08 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.assertFieldRegistry = exports.validateFieldRegistry = void 0; /** * `fields/validate.ts` * - generated field registry의 runtime 무결성 검증 헬퍼. * * **NOTE** * - artifact integrity만 검증한다. source↔generated drift는 `gen --check`(CI) 책임. * - `ts-morph`, `fs`, `path`를 import 하지 않는다. * - 모든 issue를 모아서 한 번에 반환한다 (short-circuit 없음). * * @author Claire <claire@lemoncloud.io> * @date 2026-04-21 added runtime field registry validator. * @copyright (C) lemoncloud.io 2026 - All Rights Reserved. */ const crypto = __importStar(require("crypto")); /** `fieldRegistryMeta`가 유효한 shape인지 확인 */ const isMeta = (v) => typeof v === 'object' && v !== null && typeof v.kind === 'string' && typeof v.schemaVersion === 'number' && typeof v.entryCount === 'number' && typeof v.checksum === 'string' && v.generatedBy === 'lemon-fields'; const issue = (code, message, entryKey) => entryKey !== undefined ? { code, message, entryKey } : { code, message }; const sha256First16 = (input) => crypto.createHash('sha256').update(input).digest('hex').slice(0, 16); /** * live registry에서 checksum 입력용 canonical string을 만든다. * * generator의 `canonicalEntries`와 동일한 규칙: * `Object.keys(fieldKeys).sort().map(k => JSON.stringify([k, fn()])).join('\n')` * * 실패한 entry는 canonical input에서 제외하고 별도 issue로 보고한다. */ const canonicalFromLiveRegistry = (fieldKeys, issues) => { const pairs = []; for (const key of Object.keys(fieldKeys).sort()) { const fn = fieldKeys[key]; if (typeof fn !== 'function') continue; // NON_FUNCTION_ENTRY가 이미 추가됨 let result; try { result = fn(); } catch (e) { issues.push(issue('ENTRY_EVAL_FAILED', `entry "${key}" threw on evaluation: ${e}`, key)); continue; } if (!Array.isArray(result) || !result.every(x => typeof x === 'string')) { issues.push(issue('INVALID_FIELD_LIST', `entry "${key}" did not return string[], got: ${JSON.stringify(result)}`, key)); continue; } pairs.push(JSON.stringify([key, result])); } return pairs.join('\n'); }; /** * generated field registry의 artifact 무결성을 검증한다. * * 검증 범위: * - meta shape 존재 여부 / schemaVersion / kind * - entry count와 meta.entryCount 일치 * - checksum 재계산 일치 * - 각 entry가 callable하고 string[] 반환 여부 * - concrete registry가 0 entry인 비정상 상태 * * 검증 범위 외: * - source와 generated registry 간 drift (CI에서 `gen --check`로 보장) */ const validateFieldRegistry = (input) => { const issues = []; // meta shape 확인 if (!isMeta(input.fieldRegistryMeta)) { issues.push(issue('META_MISSING', 'fieldRegistryMeta is missing or has invalid shape — run `lemon-fields gen` to regenerate')); return { ok: false, issues }; } const meta = input.fieldRegistryMeta; // schemaVersion 확인 if (meta.schemaVersion !== 1) { issues.push(issue('UNSUPPORTED_SCHEMA', `unsupported schemaVersion ${meta.schemaVersion}, expected 1`)); return { ok: false, issues }; } // bootstrap stub 확인 if (meta.kind === 'bootstrap') { issues.push(issue('BOOTSTRAP_STUB', 'registry is a bootstrap stub — run `lemon-fields gen` to generate a concrete registry')); return { ok: false, issues }; } const keys = Object.keys(input.fieldKeys); // 각 entry가 function인지 먼저 확인 (canonical 계산 전에 수집) for (const key of keys) { if (typeof input.fieldKeys[key] !== 'function') { issues.push(issue('NON_FUNCTION_ENTRY', `entry "${key}" is not a function`, key)); } } // entry count 확인 (meta.entryCount vs 실제 key 개수) if (keys.length !== meta.entryCount) { issues.push(issue('ENTRY_COUNT_MISMATCH', `registry has ${keys.length} entries but meta.entryCount is ${meta.entryCount}`)); } // 0 entry concrete registry는 비정상 상태 (generator는 이 경우 bootstrap을 내므로) if (meta.kind === 'concrete' && keys.length === 0) { issues.push(issue('EMPTY_CONCRETE_REGISTRY', 'concrete registry has 0 entries — this is unexpected')); } // canonical 재계산 및 checksum 비교 (NON_FUNCTION_ENTRY가 있는 entry는 canonical에서 제외됨) const canonical = canonicalFromLiveRegistry(input.fieldKeys, issues); // ENTRY_EVAL_FAILED/INVALID_FIELD_LIST가 이미 있으면 checksum은 무의미할 수 있으나 계속 비교 const computedChecksum = sha256First16(canonical); if (computedChecksum !== meta.checksum) { issues.push(issue('CHECKSUM_MISMATCH', `registry checksum mismatch — expected "${meta.checksum}", got "${computedChecksum}"`)); } return { ok: issues.length === 0, issues }; }; exports.validateFieldRegistry = validateFieldRegistry; /** * 검증 실패 시 모든 issue를 합쳐 `Error`를 throw한다. * * 앱 bootstrap에서 한 번만 호출한다 — import side effect가 아님. */ const assertFieldRegistry = (input) => { const result = (0, exports.validateFieldRegistry)(input); if (!result.ok) { const summary = result.issues.map(i => ` [${i.code}] ${i.message}`).join('\n'); throw new Error(`field registry validation failed:\n${summary}`); } }; exports.assertFieldRegistry = assertFieldRegistry; //# sourceMappingURL=validate.js.map