diff --git a/package.json b/package.json index f58fd95..927848f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "", "main": "dist/server.js", "scripts": { - "generate": "tsc && node --inspect ./dist/app.js", + "generate": "npm run build-generator && node --inspect ./dist/app.js", + "build-generator": "tsc && node dist/CopyCodeFile.js && tsc", "debug": "tsc && node --inspect=9229 ./dist/server.js", "start": "npm run build && npm run watch", "build": "npm run build-ts && npm run tslint", diff --git a/package.json b/package.json index f58fd95..927848f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "", "main": "dist/server.js", "scripts": { - "generate": "tsc && node --inspect ./dist/app.js", + "generate": "npm run build-generator && node --inspect ./dist/app.js", + "build-generator": "tsc && node dist/CopyCodeFile.js && tsc", "debug": "tsc && node --inspect=9229 ./dist/server.js", "start": "npm run build && npm run watch", "build": "npm run build-ts && npm run tslint", diff --git a/src/CopyCodeFile.ts b/src/CopyCodeFile.ts new file mode 100644 index 0000000..d56f2d4 --- /dev/null +++ b/src/CopyCodeFile.ts @@ -0,0 +1,10 @@ +import * as fs from "fs"; +import { join } from "path"; + +const codeFileName = join(__dirname, "..", "src", "ExampleOutput", "util.ts"); +let code = fs.readFileSync(codeFileName).toString(); +code = code.replace(/`/g, "\\`").replace("$", "\\$"); +const utilGenerator = `export const UtilCode = \`${code}\`;\n`; +const outputFileName = join(__dirname, "..", "src", "UtilGenerator.ts"); +fs.writeFileSync(outputFileName, utilGenerator); +console.log(`Generated UtilGenerator at ${outputFileName}`); diff --git a/package.json b/package.json index f58fd95..927848f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "", "main": "dist/server.js", "scripts": { - "generate": "tsc && node --inspect ./dist/app.js", + "generate": "npm run build-generator && node --inspect ./dist/app.js", + "build-generator": "tsc && node dist/CopyCodeFile.js && tsc", "debug": "tsc && node --inspect=9229 ./dist/server.js", "start": "npm run build && npm run watch", "build": "npm run build-ts && npm run tslint", diff --git a/src/CopyCodeFile.ts b/src/CopyCodeFile.ts new file mode 100644 index 0000000..d56f2d4 --- /dev/null +++ b/src/CopyCodeFile.ts @@ -0,0 +1,10 @@ +import * as fs from "fs"; +import { join } from "path"; + +const codeFileName = join(__dirname, "..", "src", "ExampleOutput", "util.ts"); +let code = fs.readFileSync(codeFileName).toString(); +code = code.replace(/`/g, "\\`").replace("$", "\\$"); +const utilGenerator = `export const UtilCode = \`${code}\`;\n`; +const outputFileName = join(__dirname, "..", "src", "UtilGenerator.ts"); +fs.writeFileSync(outputFileName, utilGenerator); +console.log(`Generated UtilGenerator at ${outputFileName}`); diff --git a/src/ExampleOutput/Middleware.ts b/src/ExampleOutput/Middleware.ts index bfd59d5..006a148 100644 --- a/src/ExampleOutput/Middleware.ts +++ b/src/ExampleOutput/Middleware.ts @@ -1,80 +1,17 @@ import express from "express"; import * as DataAccess from "./DataAccess"; import { append, findSingle, GraphNodeInput, toPayload } from "./Model"; -import { Result } from "../ExampleOutput/Result"; +import { andThen, compose, parseNotNull, ParseObject, parseObject, parseRequiredMember, parseString, resolve, Result } from "./util"; -function parseNotNull(data: any): Result { - if (data == null) return { isSuccessful: false, errorMessage: "Input is null." }; - else return { isSuccessful: true, value: data }; -} -function parseObject(data: any): Result { - if (typeof data === "object") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Input is not an object." }; -} - -const parseRequiredMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: false, errorMessage: `Required member "${fieldName}" could not be found.` }; - } -} - -const parseNullableMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: true, value: null }; - } -} - -function parseString(data: any): Result { - if (data == null || typeof data === "string") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Value is not a string." }; -} - -function andThen(result: Result, nextFn: (data: T) => Result): Result { - if (!result.isSuccessful) return result; - return nextFn(result.value); -} - -type ParseObject = { - [P in keyof T]: Result -}; - -function compose(first: (obj:R) => Result, second: (obj: S) => Result): (obj:R) => Result { - return (obj:R): Result => { - const firstResult = first(obj); - if(!firstResult.isSuccessful) return firstResult; - return second(firstResult.value); - }; -} - -function resolve(members: ParseObject): Result { - const obj: Partial = {}; - for (const key in members) { - if (Object.prototype.hasOwnProperty.call(members, key)) { - const member: Result]> = members[key]; - if (!member.isSuccessful) { - return member; - } - obj[key] = member.value; - } - } - return { isSuccessful: true, value: (obj as T) }; -} - -const parseName = compose(parseRequiredMember("name"), parseString); -const parseDescription = compose(parseRequiredMember("description"), parseString); +const parseName = andThen(compose(parseRequiredMember("name"), parseString)); +const parseDescription = andThen(compose(parseRequiredMember("description"), parseString)); function parseGraphNodeInput(data: any): Result { - const obj = andThen(parseNotNull(data), parseObject); + const obj = andThen(parseObject)(parseNotNull(data)); const members: ParseObject = { - name: andThen(obj, parseName), - description: andThen(obj, parseDescription), + name: parseName(obj), + description: parseDescription(obj), }; return resolve(members); } @@ -84,7 +21,7 @@ const route = express.Router(); - // TODO get/post collection, get/post/delete single entity + // TODO post/delete single entity route.get("/nodes/:id(\\d+)", (req, res) => { const id = Number.parseInt(req.params.id, undefined); const node = findSingle(graph, id); diff --git a/package.json b/package.json index f58fd95..927848f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "", "main": "dist/server.js", "scripts": { - "generate": "tsc && node --inspect ./dist/app.js", + "generate": "npm run build-generator && node --inspect ./dist/app.js", + "build-generator": "tsc && node dist/CopyCodeFile.js && tsc", "debug": "tsc && node --inspect=9229 ./dist/server.js", "start": "npm run build && npm run watch", "build": "npm run build-ts && npm run tslint", diff --git a/src/CopyCodeFile.ts b/src/CopyCodeFile.ts new file mode 100644 index 0000000..d56f2d4 --- /dev/null +++ b/src/CopyCodeFile.ts @@ -0,0 +1,10 @@ +import * as fs from "fs"; +import { join } from "path"; + +const codeFileName = join(__dirname, "..", "src", "ExampleOutput", "util.ts"); +let code = fs.readFileSync(codeFileName).toString(); +code = code.replace(/`/g, "\\`").replace("$", "\\$"); +const utilGenerator = `export const UtilCode = \`${code}\`;\n`; +const outputFileName = join(__dirname, "..", "src", "UtilGenerator.ts"); +fs.writeFileSync(outputFileName, utilGenerator); +console.log(`Generated UtilGenerator at ${outputFileName}`); diff --git a/src/ExampleOutput/Middleware.ts b/src/ExampleOutput/Middleware.ts index bfd59d5..006a148 100644 --- a/src/ExampleOutput/Middleware.ts +++ b/src/ExampleOutput/Middleware.ts @@ -1,80 +1,17 @@ import express from "express"; import * as DataAccess from "./DataAccess"; import { append, findSingle, GraphNodeInput, toPayload } from "./Model"; -import { Result } from "../ExampleOutput/Result"; +import { andThen, compose, parseNotNull, ParseObject, parseObject, parseRequiredMember, parseString, resolve, Result } from "./util"; -function parseNotNull(data: any): Result { - if (data == null) return { isSuccessful: false, errorMessage: "Input is null." }; - else return { isSuccessful: true, value: data }; -} -function parseObject(data: any): Result { - if (typeof data === "object") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Input is not an object." }; -} - -const parseRequiredMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: false, errorMessage: `Required member "${fieldName}" could not be found.` }; - } -} - -const parseNullableMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: true, value: null }; - } -} - -function parseString(data: any): Result { - if (data == null || typeof data === "string") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Value is not a string." }; -} - -function andThen(result: Result, nextFn: (data: T) => Result): Result { - if (!result.isSuccessful) return result; - return nextFn(result.value); -} - -type ParseObject = { - [P in keyof T]: Result -}; - -function compose(first: (obj:R) => Result, second: (obj: S) => Result): (obj:R) => Result { - return (obj:R): Result => { - const firstResult = first(obj); - if(!firstResult.isSuccessful) return firstResult; - return second(firstResult.value); - }; -} - -function resolve(members: ParseObject): Result { - const obj: Partial = {}; - for (const key in members) { - if (Object.prototype.hasOwnProperty.call(members, key)) { - const member: Result]> = members[key]; - if (!member.isSuccessful) { - return member; - } - obj[key] = member.value; - } - } - return { isSuccessful: true, value: (obj as T) }; -} - -const parseName = compose(parseRequiredMember("name"), parseString); -const parseDescription = compose(parseRequiredMember("description"), parseString); +const parseName = andThen(compose(parseRequiredMember("name"), parseString)); +const parseDescription = andThen(compose(parseRequiredMember("description"), parseString)); function parseGraphNodeInput(data: any): Result { - const obj = andThen(parseNotNull(data), parseObject); + const obj = andThen(parseObject)(parseNotNull(data)); const members: ParseObject = { - name: andThen(obj, parseName), - description: andThen(obj, parseDescription), + name: parseName(obj), + description: parseDescription(obj), }; return resolve(members); } @@ -84,7 +21,7 @@ const route = express.Router(); - // TODO get/post collection, get/post/delete single entity + // TODO post/delete single entity route.get("/nodes/:id(\\d+)", (req, res) => { const id = Number.parseInt(req.params.id, undefined); const node = findSingle(graph, id); diff --git a/src/ExampleOutput/Result.ts b/src/ExampleOutput/Result.ts deleted file mode 100644 index cc34875..0000000 --- a/src/ExampleOutput/Result.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - TOO MUCH OVERENGINEERING -*/ - - -export interface Ok { - isSuccessful: true; - value: T; -} - -export interface Err { - isSuccessful: false; - errorMessage: string; -} - -export type Result = Ok | Err; - -export const andThen = (fn: (a: T) => Result) => (result: Result) => { - if (result.isSuccessful) { - return fn(result.value); - } - else { - return result; - } -} \ No newline at end of file diff --git a/package.json b/package.json index f58fd95..927848f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "", "main": "dist/server.js", "scripts": { - "generate": "tsc && node --inspect ./dist/app.js", + "generate": "npm run build-generator && node --inspect ./dist/app.js", + "build-generator": "tsc && node dist/CopyCodeFile.js && tsc", "debug": "tsc && node --inspect=9229 ./dist/server.js", "start": "npm run build && npm run watch", "build": "npm run build-ts && npm run tslint", diff --git a/src/CopyCodeFile.ts b/src/CopyCodeFile.ts new file mode 100644 index 0000000..d56f2d4 --- /dev/null +++ b/src/CopyCodeFile.ts @@ -0,0 +1,10 @@ +import * as fs from "fs"; +import { join } from "path"; + +const codeFileName = join(__dirname, "..", "src", "ExampleOutput", "util.ts"); +let code = fs.readFileSync(codeFileName).toString(); +code = code.replace(/`/g, "\\`").replace("$", "\\$"); +const utilGenerator = `export const UtilCode = \`${code}\`;\n`; +const outputFileName = join(__dirname, "..", "src", "UtilGenerator.ts"); +fs.writeFileSync(outputFileName, utilGenerator); +console.log(`Generated UtilGenerator at ${outputFileName}`); diff --git a/src/ExampleOutput/Middleware.ts b/src/ExampleOutput/Middleware.ts index bfd59d5..006a148 100644 --- a/src/ExampleOutput/Middleware.ts +++ b/src/ExampleOutput/Middleware.ts @@ -1,80 +1,17 @@ import express from "express"; import * as DataAccess from "./DataAccess"; import { append, findSingle, GraphNodeInput, toPayload } from "./Model"; -import { Result } from "../ExampleOutput/Result"; +import { andThen, compose, parseNotNull, ParseObject, parseObject, parseRequiredMember, parseString, resolve, Result } from "./util"; -function parseNotNull(data: any): Result { - if (data == null) return { isSuccessful: false, errorMessage: "Input is null." }; - else return { isSuccessful: true, value: data }; -} -function parseObject(data: any): Result { - if (typeof data === "object") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Input is not an object." }; -} - -const parseRequiredMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: false, errorMessage: `Required member "${fieldName}" could not be found.` }; - } -} - -const parseNullableMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: true, value: null }; - } -} - -function parseString(data: any): Result { - if (data == null || typeof data === "string") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Value is not a string." }; -} - -function andThen(result: Result, nextFn: (data: T) => Result): Result { - if (!result.isSuccessful) return result; - return nextFn(result.value); -} - -type ParseObject = { - [P in keyof T]: Result -}; - -function compose(first: (obj:R) => Result, second: (obj: S) => Result): (obj:R) => Result { - return (obj:R): Result => { - const firstResult = first(obj); - if(!firstResult.isSuccessful) return firstResult; - return second(firstResult.value); - }; -} - -function resolve(members: ParseObject): Result { - const obj: Partial = {}; - for (const key in members) { - if (Object.prototype.hasOwnProperty.call(members, key)) { - const member: Result]> = members[key]; - if (!member.isSuccessful) { - return member; - } - obj[key] = member.value; - } - } - return { isSuccessful: true, value: (obj as T) }; -} - -const parseName = compose(parseRequiredMember("name"), parseString); -const parseDescription = compose(parseRequiredMember("description"), parseString); +const parseName = andThen(compose(parseRequiredMember("name"), parseString)); +const parseDescription = andThen(compose(parseRequiredMember("description"), parseString)); function parseGraphNodeInput(data: any): Result { - const obj = andThen(parseNotNull(data), parseObject); + const obj = andThen(parseObject)(parseNotNull(data)); const members: ParseObject = { - name: andThen(obj, parseName), - description: andThen(obj, parseDescription), + name: parseName(obj), + description: parseDescription(obj), }; return resolve(members); } @@ -84,7 +21,7 @@ const route = express.Router(); - // TODO get/post collection, get/post/delete single entity + // TODO post/delete single entity route.get("/nodes/:id(\\d+)", (req, res) => { const id = Number.parseInt(req.params.id, undefined); const node = findSingle(graph, id); diff --git a/src/ExampleOutput/Result.ts b/src/ExampleOutput/Result.ts deleted file mode 100644 index cc34875..0000000 --- a/src/ExampleOutput/Result.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - TOO MUCH OVERENGINEERING -*/ - - -export interface Ok { - isSuccessful: true; - value: T; -} - -export interface Err { - isSuccessful: false; - errorMessage: string; -} - -export type Result = Ok | Err; - -export const andThen = (fn: (a: T) => Result) => (result: Result) => { - if (result.isSuccessful) { - return fn(result.value); - } - else { - return result; - } -} \ No newline at end of file diff --git a/src/ExampleOutput/util.ts b/src/ExampleOutput/util.ts new file mode 100644 index 0000000..7f9648c --- /dev/null +++ b/src/ExampleOutput/util.ts @@ -0,0 +1,79 @@ +export interface Ok { + isSuccessful: true; + value: T; +} + +export interface Err { + isSuccessful: false; + errorMessage: string; +} + +export type Result = Ok | Err; + +export const andThen = (fn: (a: T) => Result) => (result: Result) => { + if (result.isSuccessful) { + return fn(result.value); + } + else { + return result; + } +} + +export function parseNotNull(data: any): Result { + if (data == null) return { isSuccessful: false, errorMessage: "Input is null." }; + else return { isSuccessful: true, value: data }; +} + +export function parseObject(data: any): Result { + if (typeof data === "object") return { isSuccessful: true, value: data }; + else return { isSuccessful: false, errorMessage: "Input is not an object." }; +} + +export const parseRequiredMember = (fieldName: keyof T) => (data: any): Result => { + if (fieldName in data) { + return { isSuccessful: true, value: data[fieldName] }; + } + else { + return { isSuccessful: false, errorMessage: `Required member "${fieldName}" could not be found.` }; + } +} + +export const parseNullableMember = (fieldName: keyof T) => (data: any): Result => { + if (fieldName in data) { + return { isSuccessful: true, value: data[fieldName] }; + } + else { + return { isSuccessful: true, value: null }; + } +} + +export function parseString(data: any): Result { + if (data == null || typeof data === "string") return { isSuccessful: true, value: data }; + else return { isSuccessful: false, errorMessage: "Value is not a string." }; +} + +export type ParseObject = { + [P in keyof T]: Result +}; + +export function compose(first: (obj:R) => Result, second: (obj: S) => Result): (obj:R) => Result { + return (obj:R): Result => { + const firstResult = first(obj); + if(!firstResult.isSuccessful) return firstResult; + return second(firstResult.value); + }; +} + +export function resolve(members: ParseObject): Result { + const obj: Partial = {}; + for (const key in members) { + if (Object.prototype.hasOwnProperty.call(members, key)) { + const member: Result]> = members[key]; + if (!member.isSuccessful) { + return member; + } + obj[key] = member.value; + } + } + return { isSuccessful: true, value: (obj as T) }; +} diff --git a/package.json b/package.json index f58fd95..927848f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "", "main": "dist/server.js", "scripts": { - "generate": "tsc && node --inspect ./dist/app.js", + "generate": "npm run build-generator && node --inspect ./dist/app.js", + "build-generator": "tsc && node dist/CopyCodeFile.js && tsc", "debug": "tsc && node --inspect=9229 ./dist/server.js", "start": "npm run build && npm run watch", "build": "npm run build-ts && npm run tslint", diff --git a/src/CopyCodeFile.ts b/src/CopyCodeFile.ts new file mode 100644 index 0000000..d56f2d4 --- /dev/null +++ b/src/CopyCodeFile.ts @@ -0,0 +1,10 @@ +import * as fs from "fs"; +import { join } from "path"; + +const codeFileName = join(__dirname, "..", "src", "ExampleOutput", "util.ts"); +let code = fs.readFileSync(codeFileName).toString(); +code = code.replace(/`/g, "\\`").replace("$", "\\$"); +const utilGenerator = `export const UtilCode = \`${code}\`;\n`; +const outputFileName = join(__dirname, "..", "src", "UtilGenerator.ts"); +fs.writeFileSync(outputFileName, utilGenerator); +console.log(`Generated UtilGenerator at ${outputFileName}`); diff --git a/src/ExampleOutput/Middleware.ts b/src/ExampleOutput/Middleware.ts index bfd59d5..006a148 100644 --- a/src/ExampleOutput/Middleware.ts +++ b/src/ExampleOutput/Middleware.ts @@ -1,80 +1,17 @@ import express from "express"; import * as DataAccess from "./DataAccess"; import { append, findSingle, GraphNodeInput, toPayload } from "./Model"; -import { Result } from "../ExampleOutput/Result"; +import { andThen, compose, parseNotNull, ParseObject, parseObject, parseRequiredMember, parseString, resolve, Result } from "./util"; -function parseNotNull(data: any): Result { - if (data == null) return { isSuccessful: false, errorMessage: "Input is null." }; - else return { isSuccessful: true, value: data }; -} -function parseObject(data: any): Result { - if (typeof data === "object") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Input is not an object." }; -} - -const parseRequiredMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: false, errorMessage: `Required member "${fieldName}" could not be found.` }; - } -} - -const parseNullableMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: true, value: null }; - } -} - -function parseString(data: any): Result { - if (data == null || typeof data === "string") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Value is not a string." }; -} - -function andThen(result: Result, nextFn: (data: T) => Result): Result { - if (!result.isSuccessful) return result; - return nextFn(result.value); -} - -type ParseObject = { - [P in keyof T]: Result -}; - -function compose(first: (obj:R) => Result, second: (obj: S) => Result): (obj:R) => Result { - return (obj:R): Result => { - const firstResult = first(obj); - if(!firstResult.isSuccessful) return firstResult; - return second(firstResult.value); - }; -} - -function resolve(members: ParseObject): Result { - const obj: Partial = {}; - for (const key in members) { - if (Object.prototype.hasOwnProperty.call(members, key)) { - const member: Result]> = members[key]; - if (!member.isSuccessful) { - return member; - } - obj[key] = member.value; - } - } - return { isSuccessful: true, value: (obj as T) }; -} - -const parseName = compose(parseRequiredMember("name"), parseString); -const parseDescription = compose(parseRequiredMember("description"), parseString); +const parseName = andThen(compose(parseRequiredMember("name"), parseString)); +const parseDescription = andThen(compose(parseRequiredMember("description"), parseString)); function parseGraphNodeInput(data: any): Result { - const obj = andThen(parseNotNull(data), parseObject); + const obj = andThen(parseObject)(parseNotNull(data)); const members: ParseObject = { - name: andThen(obj, parseName), - description: andThen(obj, parseDescription), + name: parseName(obj), + description: parseDescription(obj), }; return resolve(members); } @@ -84,7 +21,7 @@ const route = express.Router(); - // TODO get/post collection, get/post/delete single entity + // TODO post/delete single entity route.get("/nodes/:id(\\d+)", (req, res) => { const id = Number.parseInt(req.params.id, undefined); const node = findSingle(graph, id); diff --git a/src/ExampleOutput/Result.ts b/src/ExampleOutput/Result.ts deleted file mode 100644 index cc34875..0000000 --- a/src/ExampleOutput/Result.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - TOO MUCH OVERENGINEERING -*/ - - -export interface Ok { - isSuccessful: true; - value: T; -} - -export interface Err { - isSuccessful: false; - errorMessage: string; -} - -export type Result = Ok | Err; - -export const andThen = (fn: (a: T) => Result) => (result: Result) => { - if (result.isSuccessful) { - return fn(result.value); - } - else { - return result; - } -} \ No newline at end of file diff --git a/src/ExampleOutput/util.ts b/src/ExampleOutput/util.ts new file mode 100644 index 0000000..7f9648c --- /dev/null +++ b/src/ExampleOutput/util.ts @@ -0,0 +1,79 @@ +export interface Ok { + isSuccessful: true; + value: T; +} + +export interface Err { + isSuccessful: false; + errorMessage: string; +} + +export type Result = Ok | Err; + +export const andThen = (fn: (a: T) => Result) => (result: Result) => { + if (result.isSuccessful) { + return fn(result.value); + } + else { + return result; + } +} + +export function parseNotNull(data: any): Result { + if (data == null) return { isSuccessful: false, errorMessage: "Input is null." }; + else return { isSuccessful: true, value: data }; +} + +export function parseObject(data: any): Result { + if (typeof data === "object") return { isSuccessful: true, value: data }; + else return { isSuccessful: false, errorMessage: "Input is not an object." }; +} + +export const parseRequiredMember = (fieldName: keyof T) => (data: any): Result => { + if (fieldName in data) { + return { isSuccessful: true, value: data[fieldName] }; + } + else { + return { isSuccessful: false, errorMessage: `Required member "${fieldName}" could not be found.` }; + } +} + +export const parseNullableMember = (fieldName: keyof T) => (data: any): Result => { + if (fieldName in data) { + return { isSuccessful: true, value: data[fieldName] }; + } + else { + return { isSuccessful: true, value: null }; + } +} + +export function parseString(data: any): Result { + if (data == null || typeof data === "string") return { isSuccessful: true, value: data }; + else return { isSuccessful: false, errorMessage: "Value is not a string." }; +} + +export type ParseObject = { + [P in keyof T]: Result +}; + +export function compose(first: (obj:R) => Result, second: (obj: S) => Result): (obj:R) => Result { + return (obj:R): Result => { + const firstResult = first(obj); + if(!firstResult.isSuccessful) return firstResult; + return second(firstResult.value); + }; +} + +export function resolve(members: ParseObject): Result { + const obj: Partial = {}; + for (const key in members) { + if (Object.prototype.hasOwnProperty.call(members, key)) { + const member: Result]> = members[key]; + if (!member.isSuccessful) { + return member; + } + obj[key] = member.value; + } + } + return { isSuccessful: true, value: (obj as T) }; +} diff --git a/src/MiddlewareGenerator.ts b/src/MiddlewareGenerator.ts index 23d5a4b..96e0704 100644 --- a/src/MiddlewareGenerator.ts +++ b/src/MiddlewareGenerator.ts @@ -16,7 +16,6 @@ const definitions = [ defineImports(collection), defineCreateMiddlewareFunction(collection), - defineGeneralFunctions, defineInputParsers(collection.entities), ]; return { @@ -29,7 +28,7 @@ return `import express from "express"; import * as DataAccess from "./${dataAccessModuleName(collection)}"; import { append, findSingle, ${collection.entities.name}Input, toPayload } from "./${modelModuleName(collection)}"; -import { Result } from "../ExampleOutput/Result";`; +import { andThen, compose, parseNotNull, ParseObject, parseObject, parseRequiredMember, parseString, resolve, Result } from "./util";`; } function defineCreateMiddlewareFunction(collection: Collection): string { @@ -41,7 +40,7 @@ const route = express.Router(); - // TODO get/post collection, get/post/delete single entity + // TODO post/delete single entity route.get("${route}/:${entityId}(\\\\d+)", (req, res) => { const id = Number.parseInt(req.params.${entityId}, undefined); const item = findSingle(${col}, id); @@ -80,79 +79,14 @@ }`; } -const defineGeneralFunctions: string = - `function parseNotNull(data: any): Result { - if (data == null) return { isSuccessful: false, errorMessage: "Input is null." }; - else return { isSuccessful: true, value: data }; -} - -function parseObject(data: any): Result { - if (typeof data === "object") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Input is not an object." }; -} - -const parseRequiredMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: false, errorMessage: \`Required member "\${fieldName}" could not be found.\` }; - } -} - -const parseNullableMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: true, value: null }; - } -} - -function parseString(data: any): Result { - if (data == null || typeof data === "string") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Value is not a string." }; -} - -function andThen(result: Result, nextFn: (data: T) => Result): Result { - if (!result.isSuccessful) return result; - return nextFn(result.value); -} - -type ParseObject = { - [P in keyof T]: Result -}; - -function compose(first: (obj:R) => Result, second: (obj: S) => Result): (obj:R) => Result { - return (obj:R): Result => { - const firstResult = first(obj); - if(!firstResult.isSuccessful) return firstResult; - return second(firstResult.value); - }; -} - -function resolve(members: ParseObject): Result { - const obj: Partial = {}; - for (const key in members) { - if (Object.prototype.hasOwnProperty.call(members, key)) { - const member: Result]> = members[key]; - if (!member.isSuccessful) { - return member; - } - obj[key] = member.value; - } - } - return { isSuccessful: true, value: (obj as T) }; -}`; - function defineInputParsers(entity: Entity): string { const propParsers = entity.properties.map(p => definePropertyParser(entity, p)).join("\n"); return `${propParsers} function parse${entity.name}Input(data: any): Result<${entity.name}Input> { - const obj = andThen(parseNotNull(data), parseObject); + const obj = andThen(parseObject)(parseNotNull(data)); const members: ParseObject<${entity.name}Input> = { - ${entity.properties.map(p => `${p.key}: andThen(obj, parse${p.key}),`).join("\n\t\t")} + ${entity.properties.map(p => `${p.key}: parse${p.key}(obj),`).join("\n\t\t")} }; return resolve(members); }`; @@ -169,5 +103,5 @@ function defineStringPropertyParser(typeName: string, propName: string, isNullable: boolean): string { const parser = isNullable ? "parseNullableMember" : "parseRequiredMember"; - return `const parse${propName} = compose(${parser}<${typeName}Input>("${propName}"), parseString);`; -} \ No newline at end of file + return `const parse${propName} = andThen(compose(${parser}<${typeName}Input>("${propName}"), parseString));`; +} diff --git a/package.json b/package.json index f58fd95..927848f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "", "main": "dist/server.js", "scripts": { - "generate": "tsc && node --inspect ./dist/app.js", + "generate": "npm run build-generator && node --inspect ./dist/app.js", + "build-generator": "tsc && node dist/CopyCodeFile.js && tsc", "debug": "tsc && node --inspect=9229 ./dist/server.js", "start": "npm run build && npm run watch", "build": "npm run build-ts && npm run tslint", diff --git a/src/CopyCodeFile.ts b/src/CopyCodeFile.ts new file mode 100644 index 0000000..d56f2d4 --- /dev/null +++ b/src/CopyCodeFile.ts @@ -0,0 +1,10 @@ +import * as fs from "fs"; +import { join } from "path"; + +const codeFileName = join(__dirname, "..", "src", "ExampleOutput", "util.ts"); +let code = fs.readFileSync(codeFileName).toString(); +code = code.replace(/`/g, "\\`").replace("$", "\\$"); +const utilGenerator = `export const UtilCode = \`${code}\`;\n`; +const outputFileName = join(__dirname, "..", "src", "UtilGenerator.ts"); +fs.writeFileSync(outputFileName, utilGenerator); +console.log(`Generated UtilGenerator at ${outputFileName}`); diff --git a/src/ExampleOutput/Middleware.ts b/src/ExampleOutput/Middleware.ts index bfd59d5..006a148 100644 --- a/src/ExampleOutput/Middleware.ts +++ b/src/ExampleOutput/Middleware.ts @@ -1,80 +1,17 @@ import express from "express"; import * as DataAccess from "./DataAccess"; import { append, findSingle, GraphNodeInput, toPayload } from "./Model"; -import { Result } from "../ExampleOutput/Result"; +import { andThen, compose, parseNotNull, ParseObject, parseObject, parseRequiredMember, parseString, resolve, Result } from "./util"; -function parseNotNull(data: any): Result { - if (data == null) return { isSuccessful: false, errorMessage: "Input is null." }; - else return { isSuccessful: true, value: data }; -} -function parseObject(data: any): Result { - if (typeof data === "object") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Input is not an object." }; -} - -const parseRequiredMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: false, errorMessage: `Required member "${fieldName}" could not be found.` }; - } -} - -const parseNullableMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: true, value: null }; - } -} - -function parseString(data: any): Result { - if (data == null || typeof data === "string") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Value is not a string." }; -} - -function andThen(result: Result, nextFn: (data: T) => Result): Result { - if (!result.isSuccessful) return result; - return nextFn(result.value); -} - -type ParseObject = { - [P in keyof T]: Result -}; - -function compose(first: (obj:R) => Result, second: (obj: S) => Result): (obj:R) => Result { - return (obj:R): Result => { - const firstResult = first(obj); - if(!firstResult.isSuccessful) return firstResult; - return second(firstResult.value); - }; -} - -function resolve(members: ParseObject): Result { - const obj: Partial = {}; - for (const key in members) { - if (Object.prototype.hasOwnProperty.call(members, key)) { - const member: Result]> = members[key]; - if (!member.isSuccessful) { - return member; - } - obj[key] = member.value; - } - } - return { isSuccessful: true, value: (obj as T) }; -} - -const parseName = compose(parseRequiredMember("name"), parseString); -const parseDescription = compose(parseRequiredMember("description"), parseString); +const parseName = andThen(compose(parseRequiredMember("name"), parseString)); +const parseDescription = andThen(compose(parseRequiredMember("description"), parseString)); function parseGraphNodeInput(data: any): Result { - const obj = andThen(parseNotNull(data), parseObject); + const obj = andThen(parseObject)(parseNotNull(data)); const members: ParseObject = { - name: andThen(obj, parseName), - description: andThen(obj, parseDescription), + name: parseName(obj), + description: parseDescription(obj), }; return resolve(members); } @@ -84,7 +21,7 @@ const route = express.Router(); - // TODO get/post collection, get/post/delete single entity + // TODO post/delete single entity route.get("/nodes/:id(\\d+)", (req, res) => { const id = Number.parseInt(req.params.id, undefined); const node = findSingle(graph, id); diff --git a/src/ExampleOutput/Result.ts b/src/ExampleOutput/Result.ts deleted file mode 100644 index cc34875..0000000 --- a/src/ExampleOutput/Result.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - TOO MUCH OVERENGINEERING -*/ - - -export interface Ok { - isSuccessful: true; - value: T; -} - -export interface Err { - isSuccessful: false; - errorMessage: string; -} - -export type Result = Ok | Err; - -export const andThen = (fn: (a: T) => Result) => (result: Result) => { - if (result.isSuccessful) { - return fn(result.value); - } - else { - return result; - } -} \ No newline at end of file diff --git a/src/ExampleOutput/util.ts b/src/ExampleOutput/util.ts new file mode 100644 index 0000000..7f9648c --- /dev/null +++ b/src/ExampleOutput/util.ts @@ -0,0 +1,79 @@ +export interface Ok { + isSuccessful: true; + value: T; +} + +export interface Err { + isSuccessful: false; + errorMessage: string; +} + +export type Result = Ok | Err; + +export const andThen = (fn: (a: T) => Result) => (result: Result) => { + if (result.isSuccessful) { + return fn(result.value); + } + else { + return result; + } +} + +export function parseNotNull(data: any): Result { + if (data == null) return { isSuccessful: false, errorMessage: "Input is null." }; + else return { isSuccessful: true, value: data }; +} + +export function parseObject(data: any): Result { + if (typeof data === "object") return { isSuccessful: true, value: data }; + else return { isSuccessful: false, errorMessage: "Input is not an object." }; +} + +export const parseRequiredMember = (fieldName: keyof T) => (data: any): Result => { + if (fieldName in data) { + return { isSuccessful: true, value: data[fieldName] }; + } + else { + return { isSuccessful: false, errorMessage: `Required member "${fieldName}" could not be found.` }; + } +} + +export const parseNullableMember = (fieldName: keyof T) => (data: any): Result => { + if (fieldName in data) { + return { isSuccessful: true, value: data[fieldName] }; + } + else { + return { isSuccessful: true, value: null }; + } +} + +export function parseString(data: any): Result { + if (data == null || typeof data === "string") return { isSuccessful: true, value: data }; + else return { isSuccessful: false, errorMessage: "Value is not a string." }; +} + +export type ParseObject = { + [P in keyof T]: Result +}; + +export function compose(first: (obj:R) => Result, second: (obj: S) => Result): (obj:R) => Result { + return (obj:R): Result => { + const firstResult = first(obj); + if(!firstResult.isSuccessful) return firstResult; + return second(firstResult.value); + }; +} + +export function resolve(members: ParseObject): Result { + const obj: Partial = {}; + for (const key in members) { + if (Object.prototype.hasOwnProperty.call(members, key)) { + const member: Result]> = members[key]; + if (!member.isSuccessful) { + return member; + } + obj[key] = member.value; + } + } + return { isSuccessful: true, value: (obj as T) }; +} diff --git a/src/MiddlewareGenerator.ts b/src/MiddlewareGenerator.ts index 23d5a4b..96e0704 100644 --- a/src/MiddlewareGenerator.ts +++ b/src/MiddlewareGenerator.ts @@ -16,7 +16,6 @@ const definitions = [ defineImports(collection), defineCreateMiddlewareFunction(collection), - defineGeneralFunctions, defineInputParsers(collection.entities), ]; return { @@ -29,7 +28,7 @@ return `import express from "express"; import * as DataAccess from "./${dataAccessModuleName(collection)}"; import { append, findSingle, ${collection.entities.name}Input, toPayload } from "./${modelModuleName(collection)}"; -import { Result } from "../ExampleOutput/Result";`; +import { andThen, compose, parseNotNull, ParseObject, parseObject, parseRequiredMember, parseString, resolve, Result } from "./util";`; } function defineCreateMiddlewareFunction(collection: Collection): string { @@ -41,7 +40,7 @@ const route = express.Router(); - // TODO get/post collection, get/post/delete single entity + // TODO post/delete single entity route.get("${route}/:${entityId}(\\\\d+)", (req, res) => { const id = Number.parseInt(req.params.${entityId}, undefined); const item = findSingle(${col}, id); @@ -80,79 +79,14 @@ }`; } -const defineGeneralFunctions: string = - `function parseNotNull(data: any): Result { - if (data == null) return { isSuccessful: false, errorMessage: "Input is null." }; - else return { isSuccessful: true, value: data }; -} - -function parseObject(data: any): Result { - if (typeof data === "object") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Input is not an object." }; -} - -const parseRequiredMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: false, errorMessage: \`Required member "\${fieldName}" could not be found.\` }; - } -} - -const parseNullableMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: true, value: null }; - } -} - -function parseString(data: any): Result { - if (data == null || typeof data === "string") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Value is not a string." }; -} - -function andThen(result: Result, nextFn: (data: T) => Result): Result { - if (!result.isSuccessful) return result; - return nextFn(result.value); -} - -type ParseObject = { - [P in keyof T]: Result -}; - -function compose(first: (obj:R) => Result, second: (obj: S) => Result): (obj:R) => Result { - return (obj:R): Result => { - const firstResult = first(obj); - if(!firstResult.isSuccessful) return firstResult; - return second(firstResult.value); - }; -} - -function resolve(members: ParseObject): Result { - const obj: Partial = {}; - for (const key in members) { - if (Object.prototype.hasOwnProperty.call(members, key)) { - const member: Result]> = members[key]; - if (!member.isSuccessful) { - return member; - } - obj[key] = member.value; - } - } - return { isSuccessful: true, value: (obj as T) }; -}`; - function defineInputParsers(entity: Entity): string { const propParsers = entity.properties.map(p => definePropertyParser(entity, p)).join("\n"); return `${propParsers} function parse${entity.name}Input(data: any): Result<${entity.name}Input> { - const obj = andThen(parseNotNull(data), parseObject); + const obj = andThen(parseObject)(parseNotNull(data)); const members: ParseObject<${entity.name}Input> = { - ${entity.properties.map(p => `${p.key}: andThen(obj, parse${p.key}),`).join("\n\t\t")} + ${entity.properties.map(p => `${p.key}: parse${p.key}(obj),`).join("\n\t\t")} }; return resolve(members); }`; @@ -169,5 +103,5 @@ function defineStringPropertyParser(typeName: string, propName: string, isNullable: boolean): string { const parser = isNullable ? "parseNullableMember" : "parseRequiredMember"; - return `const parse${propName} = compose(${parser}<${typeName}Input>("${propName}"), parseString);`; -} \ No newline at end of file + return `const parse${propName} = andThen(compose(${parser}<${typeName}Input>("${propName}"), parseString));`; +} diff --git a/src/UtilGenerator.ts b/src/UtilGenerator.ts new file mode 100644 index 0000000..0c14092 --- /dev/null +++ b/src/UtilGenerator.ts @@ -0,0 +1,80 @@ +export const UtilCode = `export interface Ok { + isSuccessful: true; + value: T; +} + +export interface Err { + isSuccessful: false; + errorMessage: string; +} + +export type Result = Ok | Err; + +export const andThen = (fn: (a: T) => Result) => (result: Result) => { + if (result.isSuccessful) { + return fn(result.value); + } + else { + return result; + } +} + +export function parseNotNull(data: any): Result { + if (data == null) return { isSuccessful: false, errorMessage: "Input is null." }; + else return { isSuccessful: true, value: data }; +} + +export function parseObject(data: any): Result { + if (typeof data === "object") return { isSuccessful: true, value: data }; + else return { isSuccessful: false, errorMessage: "Input is not an object." }; +} + +export const parseRequiredMember = (fieldName: keyof T) => (data: any): Result => { + if (fieldName in data) { + return { isSuccessful: true, value: data[fieldName] }; + } + else { + return { isSuccessful: false, errorMessage: \`Required member "\${fieldName}" could not be found.\` }; + } +} + +export const parseNullableMember = (fieldName: keyof T) => (data: any): Result => { + if (fieldName in data) { + return { isSuccessful: true, value: data[fieldName] }; + } + else { + return { isSuccessful: true, value: null }; + } +} + +export function parseString(data: any): Result { + if (data == null || typeof data === "string") return { isSuccessful: true, value: data }; + else return { isSuccessful: false, errorMessage: "Value is not a string." }; +} + +export type ParseObject = { + [P in keyof T]: Result +}; + +export function compose(first: (obj:R) => Result, second: (obj: S) => Result): (obj:R) => Result { + return (obj:R): Result => { + const firstResult = first(obj); + if(!firstResult.isSuccessful) return firstResult; + return second(firstResult.value); + }; +} + +export function resolve(members: ParseObject): Result { + const obj: Partial = {}; + for (const key in members) { + if (Object.prototype.hasOwnProperty.call(members, key)) { + const member: Result]> = members[key]; + if (!member.isSuccessful) { + return member; + } + obj[key] = member.value; + } + } + return { isSuccessful: true, value: (obj as T) }; +} +`; diff --git a/package.json b/package.json index f58fd95..927848f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "", "main": "dist/server.js", "scripts": { - "generate": "tsc && node --inspect ./dist/app.js", + "generate": "npm run build-generator && node --inspect ./dist/app.js", + "build-generator": "tsc && node dist/CopyCodeFile.js && tsc", "debug": "tsc && node --inspect=9229 ./dist/server.js", "start": "npm run build && npm run watch", "build": "npm run build-ts && npm run tslint", diff --git a/src/CopyCodeFile.ts b/src/CopyCodeFile.ts new file mode 100644 index 0000000..d56f2d4 --- /dev/null +++ b/src/CopyCodeFile.ts @@ -0,0 +1,10 @@ +import * as fs from "fs"; +import { join } from "path"; + +const codeFileName = join(__dirname, "..", "src", "ExampleOutput", "util.ts"); +let code = fs.readFileSync(codeFileName).toString(); +code = code.replace(/`/g, "\\`").replace("$", "\\$"); +const utilGenerator = `export const UtilCode = \`${code}\`;\n`; +const outputFileName = join(__dirname, "..", "src", "UtilGenerator.ts"); +fs.writeFileSync(outputFileName, utilGenerator); +console.log(`Generated UtilGenerator at ${outputFileName}`); diff --git a/src/ExampleOutput/Middleware.ts b/src/ExampleOutput/Middleware.ts index bfd59d5..006a148 100644 --- a/src/ExampleOutput/Middleware.ts +++ b/src/ExampleOutput/Middleware.ts @@ -1,80 +1,17 @@ import express from "express"; import * as DataAccess from "./DataAccess"; import { append, findSingle, GraphNodeInput, toPayload } from "./Model"; -import { Result } from "../ExampleOutput/Result"; +import { andThen, compose, parseNotNull, ParseObject, parseObject, parseRequiredMember, parseString, resolve, Result } from "./util"; -function parseNotNull(data: any): Result { - if (data == null) return { isSuccessful: false, errorMessage: "Input is null." }; - else return { isSuccessful: true, value: data }; -} -function parseObject(data: any): Result { - if (typeof data === "object") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Input is not an object." }; -} - -const parseRequiredMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: false, errorMessage: `Required member "${fieldName}" could not be found.` }; - } -} - -const parseNullableMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: true, value: null }; - } -} - -function parseString(data: any): Result { - if (data == null || typeof data === "string") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Value is not a string." }; -} - -function andThen(result: Result, nextFn: (data: T) => Result): Result { - if (!result.isSuccessful) return result; - return nextFn(result.value); -} - -type ParseObject = { - [P in keyof T]: Result -}; - -function compose(first: (obj:R) => Result, second: (obj: S) => Result): (obj:R) => Result { - return (obj:R): Result => { - const firstResult = first(obj); - if(!firstResult.isSuccessful) return firstResult; - return second(firstResult.value); - }; -} - -function resolve(members: ParseObject): Result { - const obj: Partial = {}; - for (const key in members) { - if (Object.prototype.hasOwnProperty.call(members, key)) { - const member: Result]> = members[key]; - if (!member.isSuccessful) { - return member; - } - obj[key] = member.value; - } - } - return { isSuccessful: true, value: (obj as T) }; -} - -const parseName = compose(parseRequiredMember("name"), parseString); -const parseDescription = compose(parseRequiredMember("description"), parseString); +const parseName = andThen(compose(parseRequiredMember("name"), parseString)); +const parseDescription = andThen(compose(parseRequiredMember("description"), parseString)); function parseGraphNodeInput(data: any): Result { - const obj = andThen(parseNotNull(data), parseObject); + const obj = andThen(parseObject)(parseNotNull(data)); const members: ParseObject = { - name: andThen(obj, parseName), - description: andThen(obj, parseDescription), + name: parseName(obj), + description: parseDescription(obj), }; return resolve(members); } @@ -84,7 +21,7 @@ const route = express.Router(); - // TODO get/post collection, get/post/delete single entity + // TODO post/delete single entity route.get("/nodes/:id(\\d+)", (req, res) => { const id = Number.parseInt(req.params.id, undefined); const node = findSingle(graph, id); diff --git a/src/ExampleOutput/Result.ts b/src/ExampleOutput/Result.ts deleted file mode 100644 index cc34875..0000000 --- a/src/ExampleOutput/Result.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - TOO MUCH OVERENGINEERING -*/ - - -export interface Ok { - isSuccessful: true; - value: T; -} - -export interface Err { - isSuccessful: false; - errorMessage: string; -} - -export type Result = Ok | Err; - -export const andThen = (fn: (a: T) => Result) => (result: Result) => { - if (result.isSuccessful) { - return fn(result.value); - } - else { - return result; - } -} \ No newline at end of file diff --git a/src/ExampleOutput/util.ts b/src/ExampleOutput/util.ts new file mode 100644 index 0000000..7f9648c --- /dev/null +++ b/src/ExampleOutput/util.ts @@ -0,0 +1,79 @@ +export interface Ok { + isSuccessful: true; + value: T; +} + +export interface Err { + isSuccessful: false; + errorMessage: string; +} + +export type Result = Ok | Err; + +export const andThen = (fn: (a: T) => Result) => (result: Result) => { + if (result.isSuccessful) { + return fn(result.value); + } + else { + return result; + } +} + +export function parseNotNull(data: any): Result { + if (data == null) return { isSuccessful: false, errorMessage: "Input is null." }; + else return { isSuccessful: true, value: data }; +} + +export function parseObject(data: any): Result { + if (typeof data === "object") return { isSuccessful: true, value: data }; + else return { isSuccessful: false, errorMessage: "Input is not an object." }; +} + +export const parseRequiredMember = (fieldName: keyof T) => (data: any): Result => { + if (fieldName in data) { + return { isSuccessful: true, value: data[fieldName] }; + } + else { + return { isSuccessful: false, errorMessage: `Required member "${fieldName}" could not be found.` }; + } +} + +export const parseNullableMember = (fieldName: keyof T) => (data: any): Result => { + if (fieldName in data) { + return { isSuccessful: true, value: data[fieldName] }; + } + else { + return { isSuccessful: true, value: null }; + } +} + +export function parseString(data: any): Result { + if (data == null || typeof data === "string") return { isSuccessful: true, value: data }; + else return { isSuccessful: false, errorMessage: "Value is not a string." }; +} + +export type ParseObject = { + [P in keyof T]: Result +}; + +export function compose(first: (obj:R) => Result, second: (obj: S) => Result): (obj:R) => Result { + return (obj:R): Result => { + const firstResult = first(obj); + if(!firstResult.isSuccessful) return firstResult; + return second(firstResult.value); + }; +} + +export function resolve(members: ParseObject): Result { + const obj: Partial = {}; + for (const key in members) { + if (Object.prototype.hasOwnProperty.call(members, key)) { + const member: Result]> = members[key]; + if (!member.isSuccessful) { + return member; + } + obj[key] = member.value; + } + } + return { isSuccessful: true, value: (obj as T) }; +} diff --git a/src/MiddlewareGenerator.ts b/src/MiddlewareGenerator.ts index 23d5a4b..96e0704 100644 --- a/src/MiddlewareGenerator.ts +++ b/src/MiddlewareGenerator.ts @@ -16,7 +16,6 @@ const definitions = [ defineImports(collection), defineCreateMiddlewareFunction(collection), - defineGeneralFunctions, defineInputParsers(collection.entities), ]; return { @@ -29,7 +28,7 @@ return `import express from "express"; import * as DataAccess from "./${dataAccessModuleName(collection)}"; import { append, findSingle, ${collection.entities.name}Input, toPayload } from "./${modelModuleName(collection)}"; -import { Result } from "../ExampleOutput/Result";`; +import { andThen, compose, parseNotNull, ParseObject, parseObject, parseRequiredMember, parseString, resolve, Result } from "./util";`; } function defineCreateMiddlewareFunction(collection: Collection): string { @@ -41,7 +40,7 @@ const route = express.Router(); - // TODO get/post collection, get/post/delete single entity + // TODO post/delete single entity route.get("${route}/:${entityId}(\\\\d+)", (req, res) => { const id = Number.parseInt(req.params.${entityId}, undefined); const item = findSingle(${col}, id); @@ -80,79 +79,14 @@ }`; } -const defineGeneralFunctions: string = - `function parseNotNull(data: any): Result { - if (data == null) return { isSuccessful: false, errorMessage: "Input is null." }; - else return { isSuccessful: true, value: data }; -} - -function parseObject(data: any): Result { - if (typeof data === "object") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Input is not an object." }; -} - -const parseRequiredMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: false, errorMessage: \`Required member "\${fieldName}" could not be found.\` }; - } -} - -const parseNullableMember = (fieldName: keyof T) => (data: any): Result => { - if (fieldName in data) { - return { isSuccessful: true, value: data[fieldName] }; - } - else { - return { isSuccessful: true, value: null }; - } -} - -function parseString(data: any): Result { - if (data == null || typeof data === "string") return { isSuccessful: true, value: data }; - else return { isSuccessful: false, errorMessage: "Value is not a string." }; -} - -function andThen(result: Result, nextFn: (data: T) => Result): Result { - if (!result.isSuccessful) return result; - return nextFn(result.value); -} - -type ParseObject = { - [P in keyof T]: Result -}; - -function compose(first: (obj:R) => Result, second: (obj: S) => Result): (obj:R) => Result { - return (obj:R): Result => { - const firstResult = first(obj); - if(!firstResult.isSuccessful) return firstResult; - return second(firstResult.value); - }; -} - -function resolve(members: ParseObject): Result { - const obj: Partial = {}; - for (const key in members) { - if (Object.prototype.hasOwnProperty.call(members, key)) { - const member: Result]> = members[key]; - if (!member.isSuccessful) { - return member; - } - obj[key] = member.value; - } - } - return { isSuccessful: true, value: (obj as T) }; -}`; - function defineInputParsers(entity: Entity): string { const propParsers = entity.properties.map(p => definePropertyParser(entity, p)).join("\n"); return `${propParsers} function parse${entity.name}Input(data: any): Result<${entity.name}Input> { - const obj = andThen(parseNotNull(data), parseObject); + const obj = andThen(parseObject)(parseNotNull(data)); const members: ParseObject<${entity.name}Input> = { - ${entity.properties.map(p => `${p.key}: andThen(obj, parse${p.key}),`).join("\n\t\t")} + ${entity.properties.map(p => `${p.key}: parse${p.key}(obj),`).join("\n\t\t")} }; return resolve(members); }`; @@ -169,5 +103,5 @@ function defineStringPropertyParser(typeName: string, propName: string, isNullable: boolean): string { const parser = isNullable ? "parseNullableMember" : "parseRequiredMember"; - return `const parse${propName} = compose(${parser}<${typeName}Input>("${propName}"), parseString);`; -} \ No newline at end of file + return `const parse${propName} = andThen(compose(${parser}<${typeName}Input>("${propName}"), parseString));`; +} diff --git a/src/UtilGenerator.ts b/src/UtilGenerator.ts new file mode 100644 index 0000000..0c14092 --- /dev/null +++ b/src/UtilGenerator.ts @@ -0,0 +1,80 @@ +export const UtilCode = `export interface Ok { + isSuccessful: true; + value: T; +} + +export interface Err { + isSuccessful: false; + errorMessage: string; +} + +export type Result = Ok | Err; + +export const andThen = (fn: (a: T) => Result) => (result: Result) => { + if (result.isSuccessful) { + return fn(result.value); + } + else { + return result; + } +} + +export function parseNotNull(data: any): Result { + if (data == null) return { isSuccessful: false, errorMessage: "Input is null." }; + else return { isSuccessful: true, value: data }; +} + +export function parseObject(data: any): Result { + if (typeof data === "object") return { isSuccessful: true, value: data }; + else return { isSuccessful: false, errorMessage: "Input is not an object." }; +} + +export const parseRequiredMember = (fieldName: keyof T) => (data: any): Result => { + if (fieldName in data) { + return { isSuccessful: true, value: data[fieldName] }; + } + else { + return { isSuccessful: false, errorMessage: \`Required member "\${fieldName}" could not be found.\` }; + } +} + +export const parseNullableMember = (fieldName: keyof T) => (data: any): Result => { + if (fieldName in data) { + return { isSuccessful: true, value: data[fieldName] }; + } + else { + return { isSuccessful: true, value: null }; + } +} + +export function parseString(data: any): Result { + if (data == null || typeof data === "string") return { isSuccessful: true, value: data }; + else return { isSuccessful: false, errorMessage: "Value is not a string." }; +} + +export type ParseObject = { + [P in keyof T]: Result +}; + +export function compose(first: (obj:R) => Result, second: (obj: S) => Result): (obj:R) => Result { + return (obj:R): Result => { + const firstResult = first(obj); + if(!firstResult.isSuccessful) return firstResult; + return second(firstResult.value); + }; +} + +export function resolve(members: ParseObject): Result { + const obj: Partial = {}; + for (const key in members) { + if (Object.prototype.hasOwnProperty.call(members, key)) { + const member: Result]> = members[key]; + if (!member.isSuccessful) { + return member; + } + obj[key] = member.value; + } + } + return { isSuccessful: true, value: (obj as T) }; +} +`; diff --git a/src/app.ts b/src/app.ts index 4abe7c3..8ab72b8 100644 --- a/src/app.ts +++ b/src/app.ts @@ -5,6 +5,7 @@ import { generateDataAccess } from "./DataAccessGenerator"; import { generateOpenAPI } from "./OpenApiGenerator"; import { generateMiddlewares } from "./MiddlewareGenerator"; +import { UtilCode } from "./UtilGenerator"; interface Options { typeScriptOutputFolder: string; @@ -57,6 +58,11 @@ const middlewareWrites = generateMiddlewares(description); middlewareWrites.forEach(w => { w.location = join(options.typeScriptOutputFolder, w.location) }); + const utilWrite: FileWrite = { + location: join(options.typeScriptOutputFolder, "util.ts"), + content: UtilCode, + }; + // TODO create json schema file for the storage return [ @@ -64,6 +70,7 @@ ...dataAccessWrites, ...middlewareWrites, openapiWrites, + utilWrite, ]; }