diff --git a/jwt-tokens.json b/jwt-tokens.json deleted file mode 100644 index 723856c..0000000 --- a/jwt-tokens.json +++ /dev/null @@ -1 +0,0 @@ -{"invalidTokens":["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMzQ1LCJyb2xlcyI6WyJ1c2VyIl0sImV4cCI6MTY5ODY0OTQ1M30.Z3hFdVdBbXMwS0tRN2dPVkJWaWxTeDFZT2JNcldhSytLN2tZTzQxejM3dz0","eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMzQ1LCJyb2xlcyI6WyJ1c2VyIl0sImV4cCI6MTY5ODY0OTQ1M30.Z3hFdVdBbXMwS0tRN2dPVkJWaWxTeDFZT2JNcldhSytLN2tZTzQxejM3dz0","fff7d0083ab069b37f78da2f3636577260354bd7680202b157b54c634f833c525ea57340f8334dff"],"refreshTokens":[{"token":"fff7d0083ab069b37f78da2f3636577260354bd7680202b157b54c634f833c525ea57340f8334dff","exp":1699250653}]} \ No newline at end of file diff --git a/modules/htmlody/css-engine.test.ts b/modules/htmlody/css-engine.test.ts index 8197ce4..55b678f 100644 --- a/modules/htmlody/css-engine.test.ts +++ b/modules/htmlody/css-engine.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "bun:test"; -import { JsonHtmlNodeMap, JsonTagElNode } from "."; +import { JsonHtmlNodeTree, JsonTagElNode } from "."; import { adjustBrightness, generateCSS, @@ -13,7 +13,7 @@ import { ClassRecordAttributes } from "./htmlody-plugins"; describe("generateCSS", () => { it("should generate correct CSS from nodeMap", () => { - const mockNodeMap: JsonHtmlNodeMap> = { + const mockNodeMap: JsonHtmlNodeTree> = { exampleDiv: { tag: "div", cr: { @@ -48,7 +48,7 @@ describe("generateCSS", () => { expect(result).toEqual(expectedCss); }); it("should return empty string for empty nodeMap", () => { - const mockNodeMap: JsonHtmlNodeMap> = + const mockNodeMap: JsonHtmlNodeTree> = {}; const result = generateCSS(mockNodeMap); @@ -56,7 +56,7 @@ describe("generateCSS", () => { }); it("should handle nodes without the cr property", () => { - const mockNodeMap: JsonHtmlNodeMap> = { + const mockNodeMap: JsonHtmlNodeTree> = { exampleDiv: { tag: "div", }, @@ -67,7 +67,7 @@ describe("generateCSS", () => { }); it("should ignore invalid class names", () => { - const mockNodeMap: JsonHtmlNodeMap> = { + const mockNodeMap: JsonHtmlNodeTree> = { exampleDiv: { tag: "div", cr: { @@ -83,7 +83,7 @@ describe("generateCSS", () => { }); it("should not generate CSS if all classes are set to false", () => { - const mockNodeMap: JsonHtmlNodeMap> = { + const mockNodeMap: JsonHtmlNodeTree> = { exampleDiv: { tag: "div", cr: { diff --git a/modules/htmlody/css-engine.ts b/modules/htmlody/css-engine.ts index 40da852..7011d5f 100644 --- a/modules/htmlody/css-engine.ts +++ b/modules/htmlody/css-engine.ts @@ -1,10 +1,10 @@ +import { ClassRecordAttributes } from "./htmlody-plugins"; import { ClassRecord, - JsonHtmlNodeMap, + JsonHtmlNodeTree, JsonTagElNode, ResponsiveClassRecord, -} from "./html-type-engine"; -import { ClassRecordAttributes } from "./htmlody-plugins"; +} from "./htmlody-types"; const fractionPercentMap = { "1/2": 50, @@ -134,26 +134,23 @@ export function processNode( if (node.cr) { const classRecords = processClassRecords(node.cr, usedClasses); - if (classRecords) cssStr += classRecords; } if (node.children) { Object.values(node.children).forEach((childNode) => { const childNodeStr = processNode(childNode, usedClasses); - if (childNodeStr) cssStr += childNodeStr; }); } - if (!cssStr) return null; - return cssStr; + return cssStr || null; } export function generateCSS< - NodeMap extends JsonHtmlNodeMap< + NodeMap extends JsonHtmlNodeTree< JsonTagElNode - > = JsonHtmlNodeMap> + > = JsonHtmlNodeTree> >(nodeMap: NodeMap): string | null { const usedClasses = new Set(); let cssStr = ""; @@ -168,32 +165,6 @@ export function generateCSS< return cssStr; } -// export const cssFactory = < -// NodeMap extends JsonHtmlNodeMap< -// JsonTagElNode -// > = JsonHtmlNodeMap> -// >( -// nodeMap: NodeMap -// ) => { -// const usedClasses = new Set(); -// const cssStrings: string[] = []; - -// const generateCSS = () => { -// Object.values(nodeMap).forEach((node) => { -// cssStrings.push(processNode(node, usedClasses)); -// }); -// }; - -// const getCSS = () => { -// return cssStrings.join("\n"); -// }; - -// return { -// generateCSS, -// getCSS, -// }; -// }; - export const createKeyVal = ( key: Key, val: Val @@ -839,10 +810,36 @@ export const CSS_MAP = { "items-start": "align-items: flex-start;", "items-center": "align-items: center;", "items-end": "align-items: flex-end;", + "items-stretch": "align-items: stretch;", + "items-baseline": "align-items: baseline;", + "items-auto": "align-items: auto;", + "items-normal": "align-items: normal;", "justify-start": "justify-content: flex-start;", "justify-center": "justify-content: center;", "justify-end": "justify-content: flex-end;", + "justify-between": "justify-content: space-between;", + "justify-around": "justify-content: space-around;", + "justify-evenly": "justify-content: space-evenly;", + + "justify-items-start": "justify-items: flex-start;", + "justify-items-center": "justify-items: center;", + "justify-items-end": "justify-items: flex-end;", + "justify-items-stretch": "justify-items: stretch;", + "justify-items-baseline": "justify-items: baseline;", + "justify-items-auto": "justify-items: auto;", + + "justify-self-auto": "justify-self: auto;", + "justify-self-start": "justify-self: flex-start;", + "justify-self-center": "justify-self: center;", + "justify-self-end": "justify-self: flex-end;", + "justify-self-stretch": "justify-self: stretch;", + "justify-self-baseline": "justify-self: baseline;", + "justify-self-normal": "justify-self: normal;", + "justify-self-left": "justify-self: left;", + "justify-self-right": "justify-self: right;", + "justify-self-safe": "justify-self: safe;", + "justify-self-unsafe": "justify-self: unsafe;", // Grid Utilities "grid-cols-1": "grid-template-columns: repeat(1, minmax(0, 1fr));", diff --git a/modules/htmlody/html-factory.test.ts b/modules/htmlody/html-factory.test.ts deleted file mode 100644 index c2f2260..0000000 --- a/modules/htmlody/html-factory.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { describe, expect, it } from "bun:test"; -import { CRNode } from "."; -import { cc } from "./css-engine"; -import { pageGenerator } from "./html-factory"; -import type { JsonHtmlNodeMap } from "./html-type-engine"; -import { htmlBody } from "./html-type-engine.test"; - -describe("htmlFactory", () => { - const mockHeadConfig = { title: "Test Title" }; - const mockBodyConfig: JsonHtmlNodeMap = { - divId: { - tag: "div", - content: "Test Content", - cr: cc(["bg-blue-500"]), - children: { - spanId: { - tag: "span", - content: "Child Content", - }, - }, - }, - }; - - it("renders basic elements correctly", () => { - const factory = pageGenerator(mockHeadConfig); - const htmlOut = factory.buildHtml(mockBodyConfig); - - expect(htmlOut).toContain('
'); - expect(htmlOut).toContain("Test Content"); - expect(htmlOut).toContain("
"); - }); - - it("renders attributes correctly", () => { - const factory = pageGenerator(mockHeadConfig); - const htmlOut = factory.buildHtml(mockBodyConfig); - - expect(htmlOut).toContain('class="bg-blue-500"'); - }); - - it("renders nested children correctly", () => { - const factory = pageGenerator(mockHeadConfig); - const htmlOut = factory.buildHtml(mockBodyConfig); - - expect(htmlOut).toContain(""); - expect(htmlOut).toContain("Child Content"); - expect(htmlOut).toContain(""); - }); - - it("handles optional configurations correctly", () => { - const factory = pageGenerator(mockHeadConfig, { - useHtmx: true, - }); - const htmlOut = factory.buildHtml(mockBodyConfig); - - expect(htmlOut).toContain( - '' - ); - }); -}); - -const createPageFactory = () => { - return pageGenerator( - { title: "My Title" }, - { - useHtmx: true, - } - ); -}; - -describe("htmlFactory", () => { - it("renders complete HTML structure based on provided config", () => { - const factory = createPageFactory(); - const htmlOut = factory.buildHtml(htmlBody); - - expect(htmlOut).toContain( - '

Hello World

' - ); - expect(htmlOut).toContain("

This is a description

"); - expect(htmlOut).toContain('Click Me'); - expect(htmlOut).toContain('
'); - expect(htmlOut).toContain( - '' - ); - expect(htmlOut).toContain("
Hello World
"); - }); - - it("includes optional configurations like Tailwind and htmx", () => { - const factory = createPageFactory(); - const htmlOut = factory.buildHtml(htmlBody); - - expect(htmlOut).toContain( - '' - ); - }); -}); diff --git a/modules/htmlody/html-factory.ts b/modules/htmlody/html-factory.ts deleted file mode 100644 index 7fc08e4..0000000 --- a/modules/htmlody/html-factory.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { cc, generateCSS, generateColorVariables } from "./css-engine"; -import type { - FunctionMap, - JsonHtmlHead, - JsonHtmlNodeMap, -} from "./html-type-engine"; -import { CRNode, classRecordPlugin } from "./htmlody-plugins"; -import { jsonToHtml } from "./json-to-html-engine"; - -const functionMap = { - generateTable: () => { - return "
Sample Table
"; - }, -} satisfies FunctionMap; - -export const pageGenerator = ( - headConfig: Head, - { - useHtmx, - }: { - useHtmx?: boolean | string; - } = {} -) => { - let htmxScript = ""; - - if (useHtmx === true) { - htmxScript = ``; - } else if (typeof useHtmx === "string") { - htmxScript = ``; - } - - // const infer = (): RecursiveConstructHtmlTag => { - // return {} as RecursiveConstructHtmlTag; - // }; - - const headConfigToHtml = () => { - return ` - ${headConfig.title} - - - - -`; - }; - - // TODO add second optional to configure, head title and thigns like that - - const buildHtml = (config: JsonHtmlNodeMap) => { - return /* html */ ` - - - - - ${headConfigToHtml()} - - - - ${jsonToHtml(config, [classRecordPlugin])} - - - ${htmxScript} - - -`; - }; - - const response = (config: JsonHtmlNodeMap) => { - return new Response(buildHtml(config), { - headers: { - "Content-Type": "text/html", - }, - }); - }; - - return { - buildHtml, - response, - }; -}; diff --git a/modules/htmlody/html-type-engine.test.ts b/modules/htmlody/html-type-engine.test.ts deleted file mode 100644 index 39613d1..0000000 --- a/modules/htmlody/html-type-engine.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { expectType } from "tsd"; -import { RecursiveConstructHtmlTag } from "./html-type-engine"; -import { htmxButton } from "./page-confg"; - -type TSStringValidator = T extends Expectation ? true : false; -type IsNever = [T] extends [never] ? true : false; -type IsValid = IsNever extends true - ? false - : TSStringValidator; - -export const htmlBody = { - h1id: { - tag: "h1", - content: "Hello World", - attributes: { - class: "bg-blue-500", - id: "title-id", - }, - }, - pid: { - tag: "p", - content: "This is a description", - }, - aid: { - tag: "a", - content: "Click Me", - attributes: { - href: "https://www.example.com", - }, - }, - div1id: { - tag: "div", - children: { - div2id: { - tag: "div", - children: { - div3id: { - tag: "div", - content: "Hello World", - }, - }, - }, - }, - }, - red_div: { - tag: "div", - attributes: { - class: "bg-red-500", - }, - }, - htmxButton, -} as const; - -type ConvertedHtml = RecursiveConstructHtmlTag; - -type H1 = ConvertedHtml["h1id"]; - -// checks -type Results = { - h1: IsValid< - ConvertedHtml["h1id"], - '

Hello World

' - >; - p: IsValidThis is a description

">; - a: IsValid< - ConvertedHtml["aid"], - 'Click Me' - >; - div: IsValid
Hello World
">; -}; - -expectType( - '

Hello World

' -); -expectType("

This is a description

"); -expectType( - 'Click Me' -); -expectType("
Hello World
"); diff --git a/modules/htmlody/html-type-engine.ts b/modules/htmlody/html-type-engine.ts deleted file mode 100644 index ce87c5a..0000000 --- a/modules/htmlody/html-type-engine.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type { CSSMapKeys } from "./css-engine"; - -export type Attributes = Record; - -export type ClassRecord = Partial<{ - [key in CSSMapKeys]: boolean; -}>; - -export type ResponsiveClassRecord = { - "*"?: ClassRecord; - sm?: ClassRecord; - md?: ClassRecord; - lg?: ClassRecord; - xl?: ClassRecord; -}; - -export type ExtensionRec = Record; - -export type JsonTagElNode = { - content?: string; - children?: JsonHtmlNodeMap>; - attributes?: Attributes; - tag: string; -} & Omit; - -export type JsonHtmlNodeMap = { - [id: string]: JsonTagElNode; -}; - -/********* TYPE ENGINE (lots of work needs to be done done here still*/ -type ConvertAttributesToHtmlString = { - [Key in keyof Attrs]: `${Key}="${Attrs[Key]}"`; -}[keyof Attrs]; - -type ContentOfChildrenHelper< - Node extends JsonHtmlNodeMap, - TagKey extends keyof Node = keyof Node -> = Node extends JsonHtmlNodeMap - ? { - [InnerTagKey in keyof Node]: ConstructHtmlTag; - }[TagKey] - : never; - -type OpenTag< - ParentNode extends JsonHtmlNodeMap, - TagKey extends keyof ParentNode -> = `${"<"}${TagKey}${ParentNode[TagKey]["attributes"] extends Attributes - ? ` ${ConvertAttributesToHtmlString}` - : ""}${">"}`; - -type CloseTag = ``; - -export type TagContent< - ParentNode extends JsonHtmlNodeMap, - TagKey extends keyof ParentNode -> = ParentNode[TagKey]["children"] extends JsonHtmlNodeMap - ? ContentOfChildrenHelper< - ParentNode[TagKey]["children"], - keyof ParentNode[TagKey]["children"] - > - : ParentNode[TagKey]["content"]; - -export type ConstructHtmlTag< - ParentNode extends JsonHtmlNodeMap, - TagKey extends keyof ParentNode = keyof ParentNode -> = `${OpenTag}${TagContent< - ParentNode, - TagKey ->}${CloseTag}`; - -export type RecursiveConstructHtmlTag = { - [TagKey in keyof HtmlInput]: ConstructHtmlTag; -}; - -export type JsonHtmlHead = { - title: string; -}; - -export type FullJsonHtmlDocStructure< - Head extends JsonHtmlHead, - Body extends JsonHtmlNodeMap -> = { - html: { - head: Head; - body: Body; - }; -}; - -export type FunctionMap = { - [key: string]: () => string; -}; diff --git a/modules/htmlody/htmlody-plugins.test.ts b/modules/htmlody/htmlody-plugins.test.ts index d63f927..f0ef05c 100644 --- a/modules/htmlody/htmlody-plugins.test.ts +++ b/modules/htmlody/htmlody-plugins.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "bun:test"; -import { JsonHtmlNodeMap, JsonTagElNode, jsonToHtml } from "."; +import { JsonHtmlNodeTree, JsonTagElNode, jsonToHtml } from "."; import { CRNode, ClassRecordAttributes, @@ -42,7 +42,7 @@ describe("classRecordPluginHandler", () => { }); describe("classRecordPlugin", () => { - const sampleNodeMap: JsonHtmlNodeMap = + const sampleNodeMap: JsonHtmlNodeTree = { div1: { tag: "div", diff --git a/modules/htmlody/htmlody-plugins.ts b/modules/htmlody/htmlody-plugins.ts index 99602e1..5f4bcdd 100644 --- a/modules/htmlody/htmlody-plugins.ts +++ b/modules/htmlody/htmlody-plugins.ts @@ -1,9 +1,9 @@ -import { convertMarkdownToHTML } from "mod/utils/text-utils"; +import { convertMarkdownToHTML } from "../utils/text-utils"; import { ExtensionRec, JsonTagElNode, ResponsiveClassRecord, -} from "./html-type-engine"; +} from "./htmlody-types"; // this will be the node that will be attached to our json node export type ClassRecordAttributes = { @@ -78,4 +78,3 @@ export const markdownPluginHandler = ( export const markdownPlugin: HTMLodyPlugin = { processNode: markdownPluginHandler, }; - diff --git a/modules/htmlody/htmlody-test-utils.ts b/modules/htmlody/htmlody-test-utils.ts deleted file mode 100644 index 06d769d..0000000 --- a/modules/htmlody/htmlody-test-utils.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Attributes, JsonTagElNode } from "."; - -export function randomAttributes(): Attributes { - return { - id: `id-${Math.floor(Math.random() * 1000)}`, - class: `class-${Math.floor(Math.random() * 1000)}`, - }; -} - -export function randomTagElement(): JsonTagElNode { - return { - tag: `tag-${Math.floor(Math.random() * 1000)}`, - attributes: randomAttributes(), - content: `content-${Math.floor(Math.random() * 1000)}`, - }; -} diff --git a/modules/htmlody/htmlody-types.ts b/modules/htmlody/htmlody-types.ts new file mode 100644 index 0000000..e387d26 --- /dev/null +++ b/modules/htmlody/htmlody-types.ts @@ -0,0 +1,28 @@ +import type { CSSMapKeys } from "./css-engine"; + +export type Attributes = Record; + +export type ClassRecord = Partial<{ + [key in CSSMapKeys]: boolean; +}>; + +export type ResponsiveClassRecord = { + "*"?: ClassRecord; + sm?: ClassRecord; + md?: ClassRecord; + lg?: ClassRecord; + xl?: ClassRecord; +}; + +export type ExtensionRec = Record; + +export type JsonTagElNode = { + content?: string; + children?: JsonHtmlNodeTree>; + attributes?: Attributes; + tag: string; +} & Omit; + +export type JsonHtmlNodeTree = { + [id: string]: JsonTagElNode; +}; diff --git a/modules/htmlody/htmlody-utils.test.ts b/modules/htmlody/htmlody-utils.test.ts index 6dadb88..c2d4e6d 100644 --- a/modules/htmlody/htmlody-utils.test.ts +++ b/modules/htmlody/htmlody-utils.test.ts @@ -1,6 +1,6 @@ import { JsonTagElNode } from "@bnk/core/modules/htmlody"; import { describe, expect, it } from "bun:test"; -import { Attributes } from "./html-type-engine"; +import { Attributes } from "./htmlody-types"; import { collectClassNames, formatAttributes, diff --git a/modules/htmlody/htmlody-utils.ts b/modules/htmlody/htmlody-utils.ts index 23dcef4..f0c84c3 100644 --- a/modules/htmlody/htmlody-utils.ts +++ b/modules/htmlody/htmlody-utils.ts @@ -1,14 +1,8 @@ import { VALID_HTML_TAGS } from "./constants"; -import { - Attributes, - FullJsonHtmlDocStructure, - JsonHtmlHead, - JsonHtmlNodeMap, - JsonTagElNode, -} from "./html-type-engine"; import { CRNode } from "./htmlody-plugins"; +import { Attributes, JsonHtmlNodeTree, JsonTagElNode } from "./htmlody-types"; -export const retrieveElement = ( +export const retrieveElement = ( JsonHtmlNodeMap: Structure, element: keyof Structure ) => { @@ -34,21 +28,6 @@ export const nodeFactory = < }; }; -export const buildPageConfig = < - Head extends JsonHtmlHead, - Body extends JsonHtmlNodeMap ->( - head: Head, - body: Body -): FullJsonHtmlDocStructure => { - return { - html: { - head, - body, - }, - }; -}; - export function formatAttributes(attributes: Attributes): string { return Object.entries(attributes) .map(([key, value]) => `${key}="${value}"`) @@ -75,7 +54,7 @@ export function collectClassNames( } } export const children = (children: JsonTagElNode[]) => { - const returnChildren: JsonHtmlNodeMap = {}; + const returnChildren: JsonHtmlNodeTree = {}; for (let i = 0; i < children.length; i++) { returnChildren[i] = children[i]; diff --git a/modules/htmlody/index.ts b/modules/htmlody/index.ts index e9163e0..513d2f2 100644 --- a/modules/htmlody/index.ts +++ b/modules/htmlody/index.ts @@ -1,22 +1,20 @@ -export { pageGenerator } from "./html-factory"; export type { Attributes, ClassRecord, ExtensionRec, - JsonHtmlNodeMap, + JsonHtmlNodeTree as JsonHtmlNodeTree, JsonTagElNode, - TagContent -} from "./html-type-engine"; -export { buildPageConfig, children } from "./htmlody-utils"; +} from "./htmlody-types"; +export { children } from "./htmlody-utils"; -export { jsonToHtml, renderChildrenNodes as renderChildren } from "./json-to-html-engine"; +export { jsonToHtml,htmlodyBuilder} from "./json-to-html-engine"; export { classRecordPlugin, markdownPlugin } from "./htmlody-plugins"; export type { CRNode, ClassRecordAttributes, HTMLodyPlugin, - MDNode + MDNode, } from "./htmlody-plugins"; export { cc, uClass } from "./css-engine"; diff --git a/modules/htmlody/json-to-html-engine.test.ts b/modules/htmlody/json-to-html-engine.test.ts index 28354d5..ad239a8 100644 --- a/modules/htmlody/json-to-html-engine.test.ts +++ b/modules/htmlody/json-to-html-engine.test.ts @@ -1,26 +1,30 @@ import { describe, expect, it } from "bun:test"; -import { JsonHtmlNodeMap } from "./html-type-engine"; import { HTMLodyPlugin, classRecordPlugin, markdownPlugin, } from "./htmlody-plugins"; -import { randomAttributes } from "./htmlody-test-utils"; +import { Attributes, JsonHtmlNodeTree } from "./htmlody-types"; +import { formatAttributes, isValidAttributesString } from "./htmlody-utils"; import { - formatAttributes, - isValidAttributesString -} from "./htmlody-utils"; -import { - createNodeFactory, getHtmlTags, getValidatedAttributesStr, getValidatedTagName, + htmlodyBuilder, jsonToHtml, renderChildrenNodes, renderHtmlTag, renderNodeToHtml, + renderNodeWithPlugins, } from "./json-to-html-engine"; +export function randomAttributes(): Attributes { + return { + id: `id-${Math.floor(Math.random() * 1000)}`, + class: `class-${Math.floor(Math.random() * 1000)}`, + }; +} + function expectHtmlToMatch(html: string, expected: string) { expect(html.replace(/\s+/g, "")).toBe(expected.replace(/\s+/g, "")); } @@ -57,7 +61,7 @@ describe("renderHtmlTag", () => { }); }); it("should handle missing content and children", () => { - const nodeMap: JsonHtmlNodeMap = { + const nodeMap: JsonHtmlNodeTree = { div_id1: { tag: "div", attributes: randomAttributes(), @@ -117,7 +121,7 @@ describe("jsonToHtml", () => { }); it("should render entire HTML structure from node map", () => { - const nodeMap: JsonHtmlNodeMap = { + const nodeMap: JsonHtmlNodeTree = { div_id1: { tag: "div", content: "Sample Content", @@ -144,7 +148,7 @@ describe("jsonToHtml", () => { describe("renderChildren", () => { it("should render children recursively", () => { - const children: JsonHtmlNodeMap = { + const children: JsonHtmlNodeTree = { div_id: { tag: "div", content: `content-${Math.floor(Math.random() * 1000)}`, @@ -263,8 +267,8 @@ describe("createNodeFactory", () => { markdownPlugin, ] satisfies HTMLodyPlugin[]; - const { createNode, renderHtml, renderSingleNode, renderChildren } = - createNodeFactory(plugins); + const { createNode, renderNodeTreeToHtml, renderSingleNode, renderChildren } = + htmlodyBuilder(plugins); describe("createNode", () => { it("should createa default node with a div tag", () => { @@ -276,7 +280,7 @@ describe("createNodeFactory", () => { //renderChildren describe("renderChildren", () => { it("should render children recursively", () => { - const children: JsonHtmlNodeMap = { + const children: JsonHtmlNodeTree = { div_id: { tag: "div", content: `content-${Math.floor(Math.random() * 1000)}`, @@ -306,7 +310,7 @@ describe("createNodeFactory", () => { }, }; - const html = renderHtml(nodeMap); + const html = renderNodeTreeToHtml(nodeMap); expectHtmlToMatch( html, '
Sample ContentChild Content
' @@ -355,3 +359,18 @@ describe("createNodeFactory", () => { ); }); }); + +describe("renderNodeWithPlugins", () => { + it("should throw an error if tag name is not provided", () => { + const node = { + attributes: { + id: "sample-id", + }, + content: "Sample Content", + }; + const plugins = []; + expect(() => renderNodeWithPlugins(node, plugins)).toThrow( + 'Tag name not provided for node. \n ID: id="sample-id"\n Content: Sample Content\n ' + ); + }); +}); diff --git a/modules/htmlody/json-to-html-engine.ts b/modules/htmlody/json-to-html-engine.ts index f8d405d..8e35825 100644 --- a/modules/htmlody/json-to-html-engine.ts +++ b/modules/htmlody/json-to-html-engine.ts @@ -1,10 +1,7 @@ import { SELF_CLOSING_TAGS } from "./constants"; -import { - ExtensionRec, - JsonHtmlNodeMap, - JsonTagElNode, -} from "./html-type-engine"; +import { generateCSS, generateColorVariables } from "./css-engine"; import { HTMLodyPlugin } from "./htmlody-plugins"; +import { ExtensionRec, JsonHtmlNodeTree, JsonTagElNode } from "./htmlody-types"; import { formatAttributes, isValidAttributesString, @@ -62,7 +59,7 @@ export function renderHtmlTag( } export function renderChildrenNodes[]>( - children: JsonHtmlNodeMap, + children: JsonHtmlNodeTree, plugins: Plugins ): string { return Object.entries(children) @@ -116,7 +113,7 @@ export function jsonToHtml< Plugins extends HTMLodyPlugin[], PluginProps extends ExtensionRec, Node extends JsonTagElNode = JsonTagElNode, - NodeMap extends JsonHtmlNodeMap = JsonHtmlNodeMap + NodeMap extends JsonHtmlNodeTree = JsonHtmlNodeTree >(nodeMap: NodeMap, plugins: Plugins): string { return Object.keys(nodeMap) .map((id) => renderNodeToHtml(nodeMap[id], plugins)) @@ -150,14 +147,73 @@ export function renderNodeWithPlugins< return renderHtmlTag(tagName, attributesStr, content, childrenHtml); } -export type NodePluginsReturn[]> = +const generateTitleNode = (title: string): JsonTagElNode => { + return { + tag: "title", + content: title, + }; +}; + +const generateMetaTagNode = (meta: { + name: string; + content: string; +}): JsonTagElNode => { + return { + tag: "meta", + attributes: { name: meta.name, content: meta.content }, + }; +}; + +const generateLinkTagNode = (link: { + rel: string; + href: string; +}): JsonTagElNode => { + return { + tag: "link", + attributes: { rel: link.rel, href: link.href }, + }; +}; + +const generateScriptTagNode = (src: string): JsonTagElNode => { + return { + tag: "script", + attributes: { src }, + }; +}; + +const generateStyleTagNode = (content: string): JsonTagElNode => { + return { + tag: "style", + content, + }; +}; + +export type NodePluginsMapper[]> = ReturnType; -export const createNodeFactory = < +// type CRNodeTest = NodePluginsReturn<[typeof classRecordPlugin]> +// const tester: CRNodeTest = { + +// } + +type HeadConfig = { + title: string; + metaTags?: { name: string; content: string }[]; + linkTags?: { rel: string; href: string }[]; + styleTags?: { content: string }[]; +}; + +type ScriptLoadingOptions = { + useHtmx?: boolean | string; + // Add other script loading options as needed +}; + +export const htmlodyBuilder = < Plugins extends HTMLodyPlugin[], - PluginReturns extends NodePluginsReturn + PluginReturns extends NodePluginsMapper >( - plugins: Plugins + plugins: Plugins, + headConfig?: HeadConfig ) => { const effectivePlugins = plugins; @@ -170,8 +226,8 @@ export const createNodeFactory = < } as JsonTagElNode; }; - const renderHtml = ( - nodeMap: JsonHtmlNodeMap, + const renderNodeTreeToHtml = ( + nodeMap: JsonHtmlNodeTree, pluginsOverride?: Plugins ): string => { const activePlugins = pluginsOverride || effectivePlugins; @@ -189,7 +245,7 @@ export const createNodeFactory = < }; const renderChildren = ( - children: JsonHtmlNodeMap, + children: JsonHtmlNodeTree, pluginsOverride?: Plugins ): string => { const activePlugins = pluginsOverride || effectivePlugins; @@ -200,10 +256,95 @@ export const createNodeFactory = < .join(""); }; + const buildHtmlDoc = ( + bodyConfig: JsonHtmlNodeTree, + options: { + headConfig?: HeadConfig; + scriptOptions?: ScriptLoadingOptions; + } = { + headConfig: headConfig || { + title: "HTMLody", + }, + scriptOptions: { + useHtmx: false, + }, + } + ) => { + const headNodes: JsonHtmlNodeTree = {}; + + if (options?.headConfig?.title) { + headNodes["title"] = generateTitleNode(options.headConfig.title); + } + + if (options?.headConfig?.metaTags) { + options.headConfig.metaTags.forEach((meta, index) => { + headNodes[`meta${index}`] = generateMetaTagNode(meta); + }); + } + + if (options?.headConfig?.linkTags) { + options.headConfig.linkTags.forEach((link, index) => { + headNodes[`link${index}`] = generateLinkTagNode(link); + }); + } + + if (options?.headConfig?.styleTags) { + options.headConfig.styleTags.forEach((style, index) => { + headNodes[`style${index}`] = generateStyleTagNode(style.content); + }); + } + + if (options.scriptOptions?.useHtmx) { + const htmxSrc = + typeof options.scriptOptions.useHtmx === "string" + ? options.scriptOptions.useHtmx + : "https://unpkg.com/htmx.org"; + headNodes["htmxScript"] = generateScriptTagNode(htmxSrc); + } + + const headHtml = jsonToHtml(headNodes, []); + const bodyHtml = renderNodeTreeToHtml(bodyConfig); + + // todo this needs to be externalized since this is specific to the class records plugin + const css = generateCSS(bodyConfig); + const colorVariables = generateColorVariables(); + + return ` + + + + ${headHtml} + + + + + ${bodyHtml} + + + `; + }; + + const response = ( + bodyConfig: JsonHtmlNodeTree, + options: { + headConfig?: HeadConfig; + scriptOptions?: ScriptLoadingOptions; + } = {} + ) => { + const html = buildHtmlDoc(bodyConfig, options); + return new Response(html, { + headers: { + "Content-Type": "text/html", + }, + }); + }; + return { createNode, - renderHtml, + renderNodeTreeToHtml, renderSingleNode, renderChildren, + buildHtmlDoc, + response, }; }; diff --git a/modules/htmlody/page-confg.ts b/modules/htmlody/page-confg.ts index f90361f..1ba6691 100644 --- a/modules/htmlody/page-confg.ts +++ b/modules/htmlody/page-confg.ts @@ -1,4 +1,4 @@ -import { JsonHtmlNodeMap, JsonTagElNode } from "."; +import { JsonHtmlNodeTree, JsonTagElNode } from "."; import { CRNode, MDNode } from "./htmlody-plugins"; // import { pageFactory } from "./html-generator"; @@ -15,7 +15,7 @@ export const htmxButton: JsonTagElNode = { tag: "button", }; -export const htmlBody: JsonHtmlNodeMap = { +export const htmlBody: JsonHtmlNodeTree = { h1: { content: "Hello World", attributes: { @@ -50,4 +50,3 @@ export const htmlBody: JsonHtmlNodeMap = { }, }; -// type HtmlTypeRes = ReturnType; diff --git a/modules/sqlite/README.md b/modules/sqlite/README.md index 0efe42a..0361924 100644 --- a/modules/sqlite/README.md +++ b/modules/sqlite/README.md @@ -2,31 +2,32 @@ This module aids in creating SQLite tables, managing schemas, and handling CRUD operations on database entries. -## Features: +## Features - Helps create tables in SQLite - Manages schemas in an efficient way - Handles CRUD operations on SQLite tables -## Importing the Dependencies: +## Importing the Dependencies + ```javascript import Database from "bun:sqlite"; import type { SchemaType, SchemaTypeInference } from "@bnk/core/modules/types"; import { createTableQuery, createItem, deleteItemById, readItems, updateItem } from "@bnk/core/sqlite-factory"; - ``` -Next, define your schema and use `createSqliteTableFactory` to generate your table. +Next, define your schema and use `sqliteTableFactory` to generate your table. + ```javascript const userSchema = { - id: "string", - name: "string", - email: "string", + id: "TEXT", + name: "TEXT", + email: "TEXT", } const db = new Database({filename: "./mydb.sqlite"}) -const userTableFactory = createSqliteTableFactory({ +const userTableFactory = sqliteTableFactory({ db, tableName: "users", schema: userSchema @@ -53,6 +54,6 @@ userTableFactory.update("1", {name: "John Updated"}); userTableFactory.deleteById("1"); ``` -And that's it! We have successfully generated a table and performed some CRUD operations on it. +And that's it! We have successfully generated a table and performed some CRUD operations on it. -Please note for actual coding, make sure to handle database errors and edge cases. The examples given are purely for demonstration and simplicity purposes. \ No newline at end of file +Please note for actual coding, make sure to handle database errors and edge cases. The examples given are purely for demonstration and simplicity purposes. diff --git a/modules/sqlite/create-sqlite-table-factory.ts b/modules/sqlite/create-sqlite-table-factory.ts deleted file mode 100644 index b726ecc..0000000 --- a/modules/sqlite/create-sqlite-table-factory.ts +++ /dev/null @@ -1,70 +0,0 @@ -import Database from "bun:sqlite"; -import { SchemaT, SchemaTInference } from "../types"; -import { createTableQuery } from "./sqlite-utils/create-table-query-string"; -import { - createItem, - deleteItemById, - readItems, - updateItem, -} from "./sqlite-utils/crud-fn-utils"; - -export type ForeignKeysT = - | { column: keyof Schema; references: string }[] - | null; - -export type CreateSqliteTableFactoryParams = { - db: Database; - tableName: string; - schema: Schema; -}; - -export type CreateSqliteTableOptions = { - debug?: boolean; - enableForeignKeys?: boolean; - foreignKeys?: ForeignKeysT; -}; - -// Logger utility -function logger(debug: boolean) { - return (...args: any[]) => { - if (debug) { - console.info(...args); - } - }; -} - -export function createSqliteTableFactory( - params: CreateSqliteTableFactoryParams, - options: CreateSqliteTableOptions = {} -) { - const { db, schema, tableName } = params; - const { debug = false, foreignKeys = null } = options; - - const log = logger(debug); - - db.query(createTableQuery({ tableName, schema, foreignKeys, debug })).run(); - - // Pass necessary context to external CRUD functions - function create(item: SchemaTInference) { - return createItem(db, tableName, log, item); - } - - function read(): Schema[] { - return readItems(db, tableName, log); - } - - function update(id: string | number, item: Partial>) { - updateItem(db, tableName, log, id, item); - } - - function deleteById(id: number) { - deleteItemById(db, tableName, log, id); - } - - return { - create, - read, - update, - deleteById, - }; -} diff --git a/modules/sqlite/index.ts b/modules/sqlite/index.ts index 33c9f25..549fdef 100644 --- a/modules/sqlite/index.ts +++ b/modules/sqlite/index.ts @@ -1,9 +1,9 @@ -export { createSqliteFactory } from "./create-sqlite-factory"; -export { createTableQuery } from "./sqlite-utils/create-table-query-string"; +export { createSqliteFactory } from "./sqlite-factory"; export { - createItem, - deleteItemById, - readItems, - updateItem + createItem, + deleteItemById, + readItems, + updateItem } from "./sqlite-utils/crud-fn-utils"; +export { createTableQuery } from "./sqlite-utils/table-query-string"; diff --git a/modules/sqlite/create-sqlite-factory.test.ts b/modules/sqlite/sqlite-factory.test.ts similarity index 64% rename from modules/sqlite/create-sqlite-factory.test.ts rename to modules/sqlite/sqlite-factory.test.ts index 1bbea55..dcbe329 100644 --- a/modules/sqlite/create-sqlite-factory.test.ts +++ b/modules/sqlite/sqlite-factory.test.ts @@ -1,14 +1,13 @@ import Database from "bun:sqlite"; import { beforeEach, describe, expect, test } from "bun:test"; -import { SchemaT } from "mod/types"; -import { createSqliteFactory } from "./create-sqlite-factory"; +import { SchemaMap, createSqliteFactory } from "./sqlite-factory"; let db = new Database(":memory:"); -const noteSchema: SchemaT = { - id: "string", - text: "string", -}; +const noteSchema = { + id: "TEXT", + text: "TEXT", +} satisfies SchemaMap; describe("createSqliteFactory", () => { beforeEach(() => { @@ -30,15 +29,15 @@ describe("createSqliteFactory", () => { }); notesTable.create({ - id: 1, + id: "1", text: "some text", }); - const notes = await notesTable.read(); + const notes = notesTable.read(); - expect(notes).toEqual([{ id: 1, text: "some text" }]); + expect(notes).toEqual([{ id: "1", text: "some text" }]); }); - test("should create and read a note in sqlite and update it", async () => { + test("should create and read a note in sqlite and update it", () => { const { dbTableFactory } = createSqliteFactory({ db, debug: true }); const notesTable = dbTableFactory({ @@ -48,23 +47,23 @@ describe("createSqliteFactory", () => { }); notesTable.create({ - id: 1, + id: "1", text: "some text", }); - const notes = await notesTable.read(); + const notes = notesTable.read(); - expect(notes).toEqual([{ id: 1, text: "some text" }]); + expect(notes).toEqual([{ id: "1", text: "some text" }]); - await notesTable.update(1, { + notesTable.update("1", { text: "some text updated", }); - const updatedNotes = await notesTable.read(); + const updatedNotes = notesTable.read(); - expect(updatedNotes).toEqual([{ id: 1, text: "some text updated" }]); + expect(updatedNotes).toEqual([{ id: "1", text: "some text updated" }]); }); - test("should create and read a note in sqlite and delete it", async () => { + test("should create and read a note in sqlite and delete it", () => { const { dbTableFactory } = createSqliteFactory({ db, debug: true }); const notesTable = dbTableFactory({ @@ -74,17 +73,17 @@ describe("createSqliteFactory", () => { }); notesTable.create({ - id: 1, + id: "1", text: "some text", }); - const notes = await notesTable.read(); + const notes = notesTable.read(); - expect(notes).toEqual([{ id: 1, text: "some text" }]); + expect(notes).toEqual([{ id: "1", text: "some text" }]); - await notesTable.deleteById(1); + notesTable.deleteById("1"); - const updatedNotes = await notesTable.read(); + const updatedNotes = notesTable.read(); expect(updatedNotes).toEqual([]); }); diff --git a/modules/sqlite/create-sqlite-factory.ts b/modules/sqlite/sqlite-factory.ts similarity index 60% rename from modules/sqlite/create-sqlite-factory.ts rename to modules/sqlite/sqlite-factory.ts index 17c7098..886750b 100644 --- a/modules/sqlite/create-sqlite-factory.ts +++ b/modules/sqlite/sqlite-factory.ts @@ -1,17 +1,16 @@ import { Database } from "bun:sqlite"; -import { SchemaT, SchemaTInference } from "../types"; import { - CreateSqliteTableFactoryParams, - createSqliteTableFactory, -} from "./create-sqlite-table-factory"; + SqliteTableFactoryParams, + sqliteTableFactory, +} from "./sqlite-table-factory"; -export type CreateSqliteFactory = { - create: (item: SchemaTInference) => Promise; - read: () => Promise[]>; +export type CreateSqliteFactory = { + create: (item: SQLiteSchemaToTypeScript) => Promise; + read: () => Promise[]>; update: ( id: number, - item: Partial> + item: Partial> ) => Promise; deleteById: (id: number) => Promise; }; @@ -22,15 +21,15 @@ type CreateSqliteFactoryParams = { enableForeignKeys?: boolean; }; -type DBTableFactoryParams = Omit< - CreateSqliteTableFactoryParams, +type DBTableFactoryParams = Omit< + SqliteTableFactoryParams, "db" > & { debug: boolean; }; // Mapping of SQLite types to TypeScript types. -type SQLiteToTypeScriptTypes = { +export type SQLiteToTypeScriptTypes = { TEXT: string; NUMERIC: number | string; INTEGER: number; @@ -39,11 +38,21 @@ type SQLiteToTypeScriptTypes = { DATE: Date; }; +export type SchemaKeys = keyof SQLiteToTypeScriptTypes; + +export type SchemaMap = Partial>; + +export const createTableSchema = ( + schema: Schema +): string => { + return undefined as any as string; +}; + // Mapped type that takes a schema with SQLite types and returns a schema with TypeScript types. -type SQLiteSchemaToTypeScript< - T extends { [K in keyof T]: keyof SQLiteToTypeScriptTypes } -> = { - [K in keyof T]: SQLiteToTypeScriptTypes[T[K]]; +export type SQLiteSchemaToTypeScript = { + [K in keyof T]: T[K] extends SchemaKeys + ? SQLiteToTypeScriptTypes[T[K]] + : never; }; // Example usage. @@ -52,7 +61,16 @@ const sqlitePersonTableSchema = { age: "INTEGER", name: "TEXT", createdAt: "DATE", -} as const; +} satisfies SchemaMap; + +export const getType = ( + schema: T +): SQLiteSchemaToTypeScript => { + return undefined as any as SQLiteSchemaToTypeScript; +}; + +type Person = SQLiteSchemaToTypeScript; +// => schema; // TODO implement into the sqlite factory type PersonTableSchema = SQLiteSchemaToTypeScript< @@ -80,12 +98,12 @@ export function createSqliteFactory({ db.query("PRAGMA foreign_keys = ON;").run(); } - function dbTableFactory({ + function dbTableFactory({ debug: debugTable = debug || false, schema, tableName, }: DBTableFactoryParams) { - return createSqliteTableFactory( + return sqliteTableFactory( { db, schema, diff --git a/modules/sqlite/create-sqlite-table-factory.test.ts b/modules/sqlite/sqlite-table-factory.test.ts similarity index 78% rename from modules/sqlite/create-sqlite-table-factory.test.ts rename to modules/sqlite/sqlite-table-factory.test.ts index 117b33f..2cb1ee8 100644 --- a/modules/sqlite/create-sqlite-table-factory.test.ts +++ b/modules/sqlite/sqlite-table-factory.test.ts @@ -1,20 +1,21 @@ import { Database } from "bun:sqlite"; import { afterEach, describe, expect, test } from "bun:test"; -import { createSqliteTableFactory } from "./create-sqlite-table-factory"; +import { SchemaMap } from "./sqlite-factory"; +import { sqliteTableFactory } from "./sqlite-table-factory"; const mockDb = new Database(":memory:"); const testSchema = { - id: "number", - name: "string", - age: "number", -} as const; + id: "TEXT", + name: "TEXT", + age: "INTEGER", +} satisfies SchemaMap; const factoryOptions = { debug: true, }; -describe("createSqliteTableFactory", () => { - const factory = createSqliteTableFactory( +describe("sqliteTableFactory", () => { + const factory = sqliteTableFactory( { db: mockDb, schema: testSchema, @@ -29,7 +30,7 @@ describe("createSqliteTableFactory", () => { }); test("should insert an item into the database using the factory", () => { - const item = { id: 1, name: "John", age: 30 }; + const item = { id: "1", name: "John", age: 30 }; factory.create(item); const items = factory.read(); expect(items.length).toBe(1); @@ -37,7 +38,7 @@ describe("createSqliteTableFactory", () => { }); test("should read items from the database using the factory", () => { - const item = { id: 1, name: "Jane", age: 25 }; + const item = { id: "1", name: "Jane", age: 25 }; mockDb .query("INSERT INTO test (id, name, age) VALUES (?, ?, ?)") .run(item.id, item.name, item.age); @@ -47,7 +48,7 @@ describe("createSqliteTableFactory", () => { }); test("should update an item in the database using the factory", () => { - const item = { id: 1, name: "Doe", age: 35 }; + const item = { id: "1", name: "Doe", age: 35 }; mockDb .query("INSERT INTO test (id, name, age) VALUES (?, ?, ?)") .run(item.id, item.name, item.age); @@ -58,7 +59,7 @@ describe("createSqliteTableFactory", () => { expect(items[0]).toEqual({ ...item, name: updatedName }); }); test("should delete an item from the database using the factory", () => { - const item = { id: 1, name: "Alice", age: 40 }; + const item = { id: "1", name: "Alice", age: 40 }; mockDb .query("INSERT INTO test (id, name, age) VALUES (?, ?, ?)") .run(item.id, item.name, item.age); diff --git a/modules/sqlite/sqlite-table-factory.ts b/modules/sqlite/sqlite-table-factory.ts new file mode 100644 index 0000000..271b8d7 --- /dev/null +++ b/modules/sqlite/sqlite-table-factory.ts @@ -0,0 +1,85 @@ +import Database from "bun:sqlite"; +import { + CreateSqliteFactory, + SQLiteSchemaToTypeScript, + SchemaMap, +} from "./sqlite-factory"; +import { + createItem, + deleteItemById, + readItems, + updateItem, +} from "./sqlite-utils/crud-fn-utils"; +import { createTableQuery } from "./sqlite-utils/table-query-string"; + +export type ForeignKeysT = + | { column: keyof Schema; references: string }[] + | null; + +export type SqliteTableFactoryParams = { + db: Database; + tableName: string; + schema: Schema; +}; + +export type SqliteTableOptions = { + debug?: boolean; + enableForeignKeys?: boolean; + foreignKeys?: ForeignKeysT; +}; + +// Logger utility +function logger(debug: boolean) { + return (...args: any[]) => { + if (debug) { + console.info(...args); + } + }; +} + +export function sqliteTableFactory< + Schema extends SchemaMap, + TranslatedSchema extends SQLiteSchemaToTypeScript = SQLiteSchemaToTypeScript +>( + params: SqliteTableFactoryParams, + options: SqliteTableOptions = {} +) { + const { db, schema, tableName } = params; + const { debug = false, foreignKeys = null } = options; + + const log = logger(debug); + + db.query(createTableQuery({ tableName, schema, foreignKeys, debug })).run(); + + // Pass necessary context to external CRUD functions + function create(item: TranslatedSchema) { + return createItem(db, tableName, log, item); + } + + function read(): TranslatedSchema[] { + return readItems(db, tableName, log) as TranslatedSchema[]; + } + + function update( + id: string | number, + item: Partial> + ) { + updateItem(db, tableName, log, id, item); + } + + function deleteById(id: number| string) { + deleteItemById(db, tableName, log, id); + } + + function infer(): CreateSqliteFactory { + return undefined as any as CreateSqliteFactory; + } + + return { + create, + read, + update, + deleteById, + infer, + }; +} diff --git a/modules/sqlite/sqlite-utils/create-table-query-string.test.ts b/modules/sqlite/sqlite-utils/create-table-query-string.test.ts deleted file mode 100644 index 7864c0d..0000000 --- a/modules/sqlite/sqlite-utils/create-table-query-string.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { expect, test } from "bun:test"; -import { createTableQuery } from "./create-table-query-string"; - -test("createTableQuery constructs SQL query correctly", () => { - const schema = { - id: "integer", - name: "string", - }; - - const foreignKeys = [ - { - column: "id", - references: "other_table(id)", - }, - ]; - - const result = createTableQuery({ - schema, - tableName: "test_table", - foreignKeys, - }); - - expect(result).toBe( - "CREATE TABLE IF NOT EXISTS test_table (id INTEGER, name STRING, FOREIGN KEY (id) REFERENCES other_table(id));" - ); -}); diff --git a/modules/sqlite/sqlite-utils/crud-fn-utils.test.ts b/modules/sqlite/sqlite-utils/crud-fn-utils.test.ts index 04260bd..35f973c 100644 --- a/modules/sqlite/sqlite-utils/crud-fn-utils.test.ts +++ b/modules/sqlite/sqlite-utils/crud-fn-utils.test.ts @@ -1,17 +1,18 @@ import Database from "bun:sqlite"; import { beforeEach, describe, expect, test } from "bun:test"; +import { SchemaMap } from "../sqlite-factory"; import { - createItem, - deleteItemById, - readItems, - updateItem, + createItem, + deleteItemById, + readItems, + updateItem, } from "./crud-fn-utils"; // replace with the path to your file const testSchema = { - id: "number", - name: "string", - age: "number", -}; + id: "TEXT", + name: "TEXT", + age: "INTEGER", +} satisfies SchemaMap; let db = new Database(":memory:"); @@ -30,7 +31,11 @@ describe("Database utility functions", () => { }); test("should create an item in the database", () => { - createItem(db, "test", log, { id: 1, name: "John", age: 25 }); + createItem(db, "test", log, { + id: "1", + name: "John", + age: 25, + }); const items = readItems(db, "test", log); expect(items).toEqual([{ id: 1, name: "John", age: 25 }]); }); @@ -43,7 +48,7 @@ describe("Database utility functions", () => { test("should update an item in the database", () => { db.query("INSERT INTO test (name, age) VALUES (?, ?)").run("Doe", 35); - updateItem(db, "test", log, 1, { name: "John Doe" }); + updateItem(db, "test", log, 1, { name: "John Doe" }); const items = readItems(db, "test", log); expect(items).toEqual([{ id: 1, name: "John Doe", age: 35 }]); }); diff --git a/modules/sqlite/sqlite-utils/crud-fn-utils.ts b/modules/sqlite/sqlite-utils/crud-fn-utils.ts index 65d635a..97edc8a 100644 --- a/modules/sqlite/sqlite-utils/crud-fn-utils.ts +++ b/modules/sqlite/sqlite-utils/crud-fn-utils.ts @@ -1,17 +1,20 @@ import Database from "bun:sqlite"; -import { SchemaT, SchemaTInference } from "mod/types"; +import { SchemaMap, SQLiteSchemaToTypeScript } from "../sqlite-factory"; import { - deleteQueryString, - insertQueryString, - selectAllTableQueryString, - updateQueryString, + deleteQueryString, + insertQueryString, + selectAllTableQueryString, + updateQueryString, } from "./crud-string-utils"; -export function createItem( +export function createItem< + Schema extends SchemaMap, + TranslatedSchema extends SQLiteSchemaToTypeScript = SQLiteSchemaToTypeScript +>( db: Database, tableName: string, log: (msg: any) => void, - item: SchemaTInference + item: TranslatedSchema ) { const query = insertQueryString(tableName, item); const valuesArray = Object.values(item); @@ -20,23 +23,29 @@ export function createItem( return []; } -export function readItems( +export function readItems< + Schema extends SchemaMap, + TranslatedSchema extends SQLiteSchemaToTypeScript = SQLiteSchemaToTypeScript +>( db: Database, tableName: string, log: (msg: any) => void -): Schema[] { +): TranslatedSchema[] { const query = selectAllTableQueryString(tableName); log(query); - const data = db.query(query).all() as Schema[]; + const data = db.query(query).all() as TranslatedSchema[]; return data; } -export function updateItem( +export function updateItem< + Schema extends SchemaMap, + TranslatedSchema extends SQLiteSchemaToTypeScript = SQLiteSchemaToTypeScript +>( db: Database, tableName: string, log: (msg: any) => void, id: string | number, - item: Partial> + item: Partial> ) { const query = updateQueryString(tableName, item); log(query); @@ -50,7 +59,7 @@ export function deleteItemById( db: Database, tableName: string, log: (msg: any) => void, - id: number + id: number | string ) { const query = deleteQueryString(tableName); log(query); diff --git a/modules/sqlite/sqlite-utils/format-foreign-keys.ts b/modules/sqlite/sqlite-utils/format-foreign-keys.ts index 4368417..d7ca142 100644 --- a/modules/sqlite/sqlite-utils/format-foreign-keys.ts +++ b/modules/sqlite/sqlite-utils/format-foreign-keys.ts @@ -1,9 +1,9 @@ -import { TypeMapping } from "mod/types"; -import { ForeignKeysT } from "../create-sqlite-table-factory"; +import { SQLiteSchemaToTypeScript } from "../sqlite-factory"; +import { ForeignKeysT } from "../sqlite-table-factory"; -export function formatForeignKeys< - Schema extends Record ->(foreignKeys: ForeignKeysT | undefined): string { +export function formatForeignKeys>( + foreignKeys: ForeignKeysT | undefined +): string { if (!foreignKeys) return ""; return foreignKeys .map( diff --git a/modules/sqlite/sqlite-utils/format-schema.test.ts b/modules/sqlite/sqlite-utils/format-schema.test.ts index fcab2df..3716934 100644 --- a/modules/sqlite/sqlite-utils/format-schema.test.ts +++ b/modules/sqlite/sqlite-utils/format-schema.test.ts @@ -1,13 +1,14 @@ import { expect, test } from "bun:test"; +import { SchemaMap } from "../sqlite-factory"; import { formatSchema } from "./format-schema"; test("formatSchema formats schema correctly", () => { const schema = { - id: "integer", - name: "string", - }; + id: "INTEGER", + name: "TEXT", + } satisfies SchemaMap; // TODO need to fix schema type const result = formatSchema(schema); - expect(result).toBe("id INTEGER, name STRING"); + expect(result).toBe("id INTEGER, name TEXT"); }); diff --git a/modules/sqlite/sqlite-utils/format-schema.ts b/modules/sqlite/sqlite-utils/format-schema.ts index d8707f2..bb634f8 100644 --- a/modules/sqlite/sqlite-utils/format-schema.ts +++ b/modules/sqlite/sqlite-utils/format-schema.ts @@ -1,10 +1,10 @@ -import { TypeMapping } from "mod/types"; +import { SchemaMap } from "../sqlite-factory"; -export function formatSchema>( +export function formatSchema( schema: Schema ): string { return Object.entries(schema) - .map(([key, type]) => `${key} ${type.toUpperCase()}`) + .map(([key, type]) => `${key} ${type?.toUpperCase()}`) .join(", "); } \ No newline at end of file diff --git a/modules/sqlite/sqlite-utils/table-query-string.test.ts b/modules/sqlite/sqlite-utils/table-query-string.test.ts new file mode 100644 index 0000000..d7c0fd3 --- /dev/null +++ b/modules/sqlite/sqlite-utils/table-query-string.test.ts @@ -0,0 +1,41 @@ +import { expect, test } from "bun:test"; +import { SchemaMap } from "../sqlite-factory"; +import { createTableQuery } from "./table-query-string"; + +test("createTableQuery constructs SQL query correctly with foreign keys", () => { + const schema = { + id: "INTEGER", + name: "TEXT", + } satisfies SchemaMap; + + const result = createTableQuery({ + schema, + tableName: "test_table", + foreignKeys: [ + { + column: "id", + references: "other_table(id)", + }, + ], + }); + + expect(result).toBe( + "CREATE TABLE IF NOT EXISTS test_table (id INTEGER, name TEXT, FOREIGN KEY (id) REFERENCES other_table(id));" + ); +}); + +test("createTableQuery constructs SQL query correctly without foreign keys", () => { + const schema = { + id: "INTEGER", + name: "TEXT", + } satisfies SchemaMap; + + const result = createTableQuery({ + schema, + tableName: "test_table", + }); + + expect(result).toBe( + "CREATE TABLE IF NOT EXISTS test_table (id INTEGER, name TEXT);" + ); +}); diff --git a/modules/sqlite/sqlite-utils/create-table-query-string.ts b/modules/sqlite/sqlite-utils/table-query-string.ts similarity index 71% rename from modules/sqlite/sqlite-utils/create-table-query-string.ts rename to modules/sqlite/sqlite-utils/table-query-string.ts index ced1cf9..7c8c207 100644 --- a/modules/sqlite/sqlite-utils/create-table-query-string.ts +++ b/modules/sqlite/sqlite-utils/table-query-string.ts @@ -1,10 +1,11 @@ -import { TypeMapping } from "mod/types"; -import { ForeignKeysT } from "../create-sqlite-table-factory"; +import { SQLiteSchemaToTypeScript, SchemaMap } from "../sqlite-factory"; +import { ForeignKeysT } from "../sqlite-table-factory"; import { formatForeignKeys } from "./format-foreign-keys"; import { formatSchema } from "./format-schema"; export function createTableQuery< - Schema extends Record + Schema extends SchemaMap, + TranslatedSchema extends SQLiteSchemaToTypeScript = SQLiteSchemaToTypeScript >({ schema, tableName, @@ -14,7 +15,7 @@ export function createTableQuery< tableName: string; schema: Schema; debug?: boolean; - foreignKeys?: ForeignKeysT; + foreignKeys?: ForeignKeysT; }): string { if (debug) { console.info({ schema, tableName }); diff --git a/package.json b/package.json index b0d87c0..d0ffe88 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "version": "0.2.18", "main": "index.ts", "devDependencies": { - "bun": "^1.0.6", - "bun-types": "^1.0.6", + "bun": "^1.0.8", + "bun-types": "^1.0.8", "tsd": "^0.29.0" }, "description": "Bun Nook Kit is a simple zero-dependency, fast, and lightweight toolkit for Bun", @@ -17,9 +17,5 @@ "tsc": "tsc --project tsconfig.json", "tsc:noEmit": "tsc --noEmit --project tsconfig-noEmit.json", "validate": "bun run tsc:noEmite && bun run test" - }, - "dependencies": { - "@bnk/core": "0.2.17", - "stripe-event-types": "^3.1.0" } -} \ No newline at end of file +}