diff --git a/cli/cli-utils.test.ts b/cli/cli-utils.test.ts deleted file mode 100644 index 806a8ea..0000000 --- a/cli/cli-utils.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { describe, expect, test } from 'bun:test' -import { type OptionDefinition, getOptionValue, parseArgument } from './cli-utils' - -describe('getOptionValue', () => { - test('should return correct value for boolean type', () => { - const arg = '--testArg' - const nextArg = 'true' - const optionDef: OptionDefinition = { - default: false, - types: ['boolean'], - } - - const value = getOptionValue(arg, nextArg, optionDef) - expect(value).toBe(true) - }) - - test("should return default value if nextArg starts with '--'", () => { - const arg = '--testArg' - const nextArg = '--anotherArg' - const optionDef: OptionDefinition = { - default: 'default', - types: ['string'], - } - - const value = getOptionValue(arg, nextArg, optionDef) - expect(value).toBe('default') - }) -}) - -describe('parseArgument', () => { - test('should return correct key and value', () => { - const arg = '--testArg' - const nextArg = 'true' - - const { key, value } = parseArgument(arg, nextArg) - expect(key).toBe('testArg') - expect(value).toBe(true) - }) - - test("should throw error if arg does not start with '--'", async () => { - const arg = 'testArg' - const nextArg = 'true' - - let error: Error | null = null - try { - parseArgument(arg, nextArg) - } catch (e) { - if (e instanceof Error) { - error = e - } - } - - expect(error).toBeDefined() - expect(error!.message).toBe(`Invalid parameter: ${arg}`) - }) -}) diff --git a/cli/cli-utils.ts b/cli/cli-utils.ts deleted file mode 100644 index 8cfe527..0000000 --- a/cli/cli-utils.ts +++ /dev/null @@ -1,138 +0,0 @@ -import readline from 'readline' - -const cliLog = (...args: any[]) => { - console.info(...args) -} - -// Get user input asynchronously -export async function getUserInput(): Promise { - const proc = Bun.spawn([]) - return await new Response(proc.stdout).text() -} - -// Interface for parsed command line arguments -export interface ParsedArgs { - [key: string]: string | boolean | undefined -} - -export interface OptionDefinition { - default?: string | boolean - types: (string | boolean)[] -} - -const optionDefinitions: { [key: string]: OptionDefinition } = { - // Define available options here -} - -// Parse command line arguments -export function getArguments(): string[] { - return process.argv.slice(2) -} - -export function getOptionValue( - arg: string, - nextArg: string, - optionDef: OptionDefinition -): string | boolean | undefined { - let value = optionDef.default - - if (nextArg && !nextArg.startsWith('--')) { - const type = optionDef.types.find((type) => type === typeof nextArg || type === typeof value) - cliLog('Type found: ', type) // Debug log - if (type === 'boolean') { - if (nextArg.toLowerCase() === 'true') { - value = true - } else if (nextArg.toLowerCase() === 'false') { - value = false - } - } else { - value = nextArg - } - } else if (typeof value === 'boolean') { - value = true - } - - cliLog('Returned value: ', value) // Debug log - return value -} - -export function parseArgument( - arg: string, - nextArg: string -): { key: string | undefined; value: string | boolean | undefined } { - let key: string | undefined = undefined - let value: string | boolean | undefined - - if (arg.startsWith('--')) { - key = arg.slice(2) - if (optionDefinitions.hasOwnProperty(key)) { - const optionDef = optionDefinitions[key] - value = getOptionValue(arg, nextArg, optionDef) - } else { - value = true - } - } else { - throw new Error(`Invalid parameter: ${arg}`) - } - - return { key, value } -} - -export async function parseCliArgs(): Promise { - const args = getArguments() - const parsedArgs: ParsedArgs = {} - - for (let i = 0; i < args.length; i++) { - const { key, value } = parseArgument(args[i], args[i + 1]) - - if (key) { - parsedArgs[key] = value - } - } - - return parsedArgs -} - -export const getAdditionalPrompt = () => - new Promise((resolve) => { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }) - rl.question('Do you want to add anything else...', (additionalPrompt) => { - rl.close() - resolve(additionalPrompt) - }) - }) - -export const chooseActions = async (actionsConfig: Record): Promise> => { - cliLog('\nChoose actions (separated by commas):') - const actions = Object.keys(actionsConfig) - - actions.forEach((action, index) => { - cliLog(`${index + 1}. ${action}`) - }) - - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }) - - const actionIndexes = await new Promise((resolve) => { - rl.question('Enter the numbers corresponding to the actions: ', (actionIndexes) => { - rl.close() - resolve(actionIndexes) - }) - }) - - const selectedIndexes = actionIndexes.split(',').map((index) => Number.parseInt(index.trim()) - 1) - - const validSelection = selectedIndexes.every((index) => index >= 0 && index < actions.length) - - if (validSelection) { - return selectedIndexes.map((index) => actions[index] as keyof typeof actionsConfig) - } - - cliLog('Invalid input, please try again.') - return chooseActions(actionsConfig) -} diff --git a/cli/create-cli-factory.test.ts b/cli/create-cli-factory.test.ts deleted file mode 100644 index aa9ce05..0000000 --- a/cli/create-cli-factory.test.ts +++ /dev/null @@ -1 +0,0 @@ -import { describe, expect, test } from 'bun:test' diff --git a/cli/create-cli-factory.ts b/cli/create-cli-factory.ts deleted file mode 100644 index ff06aa3..0000000 --- a/cli/create-cli-factory.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { fileFactory } from '../files-folders' -import type { defaultLogger } from '../logger' -import type { BaseError } from '../utils/base-error' -import { chooseActions, getAdditionalPrompt, getUserInput, parseCliArgs } from './cli-utils' - -export type CLIOptions = { - inputPrompt?: string - actionsConfig?: Record - logger?: typeof defaultLogger - // fileConfig?: { - // filePath: string; - // fileContent: string; - // }; -} - -export function createCliFactory>({ - inputPrompt = 'Please input your command', - actionsConfig = {}, - - logger, -}: CLIOptions) { - // const actionsConfig = options.actionsConfig ?? {}; - const factory = fileFactory({ - baseDirectory: '.', // Replace with actual path - }) - - const processInput = async () => { - const commandLineArgs = await parseCliArgs() - - const userInput = await getUserInput() - - // Handle user input and command line arguments... - return { commandLineArgs, userInput } - } - - const executeActions = async () => { - const additionalPrompt = await getAdditionalPrompt() - - const chosenActions = await chooseActions(actionsConfig) - - // Execute chosen actions... - - return { additionalPrompt, chosenActions } - } - - const handleFiles = ({ filePath, fileContent }: { filePath: string; fileContent: string }) => { - factory.directoryExists({ path: filePath }) - - factory.createFile(filePath, fileContent) - } - - return { - inputPrompt, - processInput, - executeActions, - handleFiles, - } -} diff --git a/cli/index.ts b/cli/index.ts deleted file mode 100644 index 2758838..0000000 --- a/cli/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { createCliFactory } from './create-cli-factory' diff --git a/index.ts b/index.ts index 3117043..bb40ee1 100644 --- a/index.ts +++ b/index.ts @@ -1,26 +1,21 @@ -import * as auth from './auth' -import * as cli from './cli' -import * as cookies from './cookies' -import * as dataGen from './data-gen' -import * as deploy from './deploy' -import * as fetcher from './fetcher' -import * as filesFolders from './files-folders' -import * as htmlody from './htmlody' -import * as jwt from './jwt' -import * as logger from './logger' -import * as npm from './npm-release' -import * as server from './server' -import * as sqlite from './sqlite' -import * as state from './state' -import * as uuid from './uuid' -import * as validation from './validation' +import * as auth from './packages/auth' +import * as cookies from './packages/cookies' +import * as dataGen from './packages/data-gen' +import * as deploy from './packages/deploy' +import * as fetcher from './packages/fetcher' +import * as filesFolders from './packages/files-folders' +import * as htmlody from './packages/htmlody' +import * as jwt from './packages/jwt' +import * as logger from './packages/logger' +import * as npm from './packages/npm-release' +import * as state from './packages/state' +import * as uuid from './packages/uuid' // utility exports -import * as utils from './utils/classy' +import * as utils from './packages/utils/classy' export { auth, - cli, cookies, dataGen, deploy, @@ -30,10 +25,7 @@ export { jwt, logger, npm, - server, - sqlite, state, utils, uuid, - validation, } diff --git a/package.json b/package.json index 3b26045..944158e 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,9 @@ "name": "bnkit", "version": "0.5.20", "main": "index.ts", + "workspaces": [ + "packages/*" + ], "devDependencies": { "bun": "^1.1.21", "bun-types": "^1.1.21", diff --git a/auth/example/.env.example b/packages/auth/example/.env.example similarity index 100% rename from auth/example/.env.example rename to packages/auth/example/.env.example diff --git a/auth/example/google-oauth-server-example.ts b/packages/auth/example/google-oauth-server-example.ts similarity index 94% rename from auth/example/google-oauth-server-example.ts rename to packages/auth/example/google-oauth-server-example.ts index 268f07c..41ec759 100644 --- a/auth/example/google-oauth-server-example.ts +++ b/packages/auth/example/google-oauth-server-example.ts @@ -1,5 +1,5 @@ -import { oAuthFactory } from 'auth/oauth' -import { initGoogleOAuth } from 'auth/oauth-providers' +import { oAuthFactory } from 'packages/auth/oauth' +import { initGoogleOAuth } from 'packages/auth/oauth-providers' import type { Routes } from 'server' import { serverFactory } from 'server' diff --git a/auth/example/setup-oauth-providers.md b/packages/auth/example/setup-oauth-providers.md similarity index 100% rename from auth/example/setup-oauth-providers.md rename to packages/auth/example/setup-oauth-providers.md diff --git a/auth/index.ts b/packages/auth/index.ts similarity index 100% rename from auth/index.ts rename to packages/auth/index.ts diff --git a/auth/oauth-providers.ts b/packages/auth/oauth-providers.ts similarity index 100% rename from auth/oauth-providers.ts rename to packages/auth/oauth-providers.ts diff --git a/auth/oauth-types.ts b/packages/auth/oauth-types.ts similarity index 100% rename from auth/oauth-types.ts rename to packages/auth/oauth-types.ts diff --git a/auth/oauth.ts b/packages/auth/oauth.ts similarity index 100% rename from auth/oauth.ts rename to packages/auth/oauth.ts diff --git a/auth/security-token.test.ts b/packages/auth/security-token.test.ts similarity index 100% rename from auth/security-token.test.ts rename to packages/auth/security-token.test.ts diff --git a/auth/security-token.ts b/packages/auth/security-token.ts similarity index 100% rename from auth/security-token.ts rename to packages/auth/security-token.ts diff --git a/cookies/client-cookie-factory.test.ts b/packages/cookies/client-cookie-factory.test.ts similarity index 100% rename from cookies/client-cookie-factory.test.ts rename to packages/cookies/client-cookie-factory.test.ts diff --git a/cookies/client-cookie-factory.ts b/packages/cookies/client-cookie-factory.ts similarity index 100% rename from cookies/client-cookie-factory.ts rename to packages/cookies/client-cookie-factory.ts diff --git a/cookies/cookie-types.ts b/packages/cookies/cookie-types.ts similarity index 100% rename from cookies/cookie-types.ts rename to packages/cookies/cookie-types.ts diff --git a/cookies/cookie-utils.test.ts b/packages/cookies/cookie-utils.test.ts similarity index 100% rename from cookies/cookie-utils.test.ts rename to packages/cookies/cookie-utils.test.ts diff --git a/cookies/cookie-utils.ts b/packages/cookies/cookie-utils.ts similarity index 100% rename from cookies/cookie-utils.ts rename to packages/cookies/cookie-utils.ts diff --git a/cookies/index.ts b/packages/cookies/index.ts similarity index 100% rename from cookies/index.ts rename to packages/cookies/index.ts diff --git a/cookies/server-side-cookie-factory.test.ts b/packages/cookies/server-side-cookie-factory.test.ts similarity index 100% rename from cookies/server-side-cookie-factory.test.ts rename to packages/cookies/server-side-cookie-factory.test.ts diff --git a/cookies/server-side-cookie-factory.ts b/packages/cookies/server-side-cookie-factory.ts similarity index 100% rename from cookies/server-side-cookie-factory.ts rename to packages/cookies/server-side-cookie-factory.ts diff --git a/server-router/src/cors-utils.test.ts b/packages/cors/cors.test.ts similarity index 98% rename from server-router/src/cors-utils.test.ts rename to packages/cors/cors.test.ts index d7cc851..3099a41 100644 --- a/server-router/src/cors-utils.test.ts +++ b/packages/cors/cors.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; -import { addCorsHeaders, getAllowedOrigin, handleCorsPreFlight, getDefaultCorsOptions } from "./cors-utils"; -import type { CorsOptions } from "./router-types"; +import { addCorsHeaders, getAllowedOrigin, handleCorsPreFlight, getDefaultCorsOptions } from "./cors"; +import type { CorsOptions } from "./cors"; describe("cors-utils", () => { describe("addCorsHeaders", () => { diff --git a/server-router/src/cors-utils.ts b/packages/cors/cors.ts similarity index 86% rename from server-router/src/cors-utils.ts rename to packages/cors/cors.ts index e742768..f23a22d 100644 --- a/server-router/src/cors-utils.ts +++ b/packages/cors/cors.ts @@ -1,4 +1,13 @@ -import { CorsOptions } from "./router-types"; +export type HttpMethod = 'GET' | 'POST' | 'DELETE' | 'PATCH' | 'PUT' | 'OPTIONS'; + +// CORS Types +export interface CorsOptions { + origin?: string | string[] | ((origin: string) => boolean); + methods?: HttpMethod[]; + credentials?: boolean; + headers?: string[]; +} + export async function addCorsHeaders( response: Response, diff --git a/packages/cors/package.json b/packages/cors/package.json new file mode 100644 index 0000000..cb4b0bf --- /dev/null +++ b/packages/cors/package.json @@ -0,0 +1,14 @@ +{ + "name": "@bnk/cors", + "version": "1.0.1", + "main": "cors.ts", + "types": "cors.ts", + "devDependencies": { + "bun-types": "*" + }, + "scripts": { + "test": "bun test", + "prepublishOnly": "bun test", + "publish:cors": "bun publish --access public" + } +} \ No newline at end of file diff --git a/server-router/tsconfig.json b/packages/cors/tsconfig.json similarity index 100% rename from server-router/tsconfig.json rename to packages/cors/tsconfig.json diff --git a/data-gen/cities.ts b/packages/data-gen/cities.ts similarity index 100% rename from data-gen/cities.ts rename to packages/data-gen/cities.ts diff --git a/data-gen/countries.ts b/packages/data-gen/countries.ts similarity index 100% rename from data-gen/countries.ts rename to packages/data-gen/countries.ts diff --git a/data-gen/create-random-data.test.ts b/packages/data-gen/create-random-data.test.ts similarity index 100% rename from data-gen/create-random-data.test.ts rename to packages/data-gen/create-random-data.test.ts diff --git a/data-gen/create-random-data.ts b/packages/data-gen/create-random-data.ts similarity index 100% rename from data-gen/create-random-data.ts rename to packages/data-gen/create-random-data.ts diff --git a/data-gen/index.ts b/packages/data-gen/index.ts similarity index 100% rename from data-gen/index.ts rename to packages/data-gen/index.ts diff --git a/data-gen/names.ts b/packages/data-gen/names.ts similarity index 100% rename from data-gen/names.ts rename to packages/data-gen/names.ts diff --git a/data-gen/object-gen.test.ts b/packages/data-gen/object-gen.test.ts similarity index 100% rename from data-gen/object-gen.test.ts rename to packages/data-gen/object-gen.test.ts diff --git a/data-gen/object-gen.ts b/packages/data-gen/object-gen.ts similarity index 100% rename from data-gen/object-gen.ts rename to packages/data-gen/object-gen.ts diff --git a/data-gen/rand-date-range.test.ts b/packages/data-gen/rand-date-range.test.ts similarity index 100% rename from data-gen/rand-date-range.test.ts rename to packages/data-gen/rand-date-range.test.ts diff --git a/data-gen/rand-date-range.ts b/packages/data-gen/rand-date-range.ts similarity index 100% rename from data-gen/rand-date-range.ts rename to packages/data-gen/rand-date-range.ts diff --git a/data-gen/rand-num.test.ts b/packages/data-gen/rand-num.test.ts similarity index 100% rename from data-gen/rand-num.test.ts rename to packages/data-gen/rand-num.test.ts diff --git a/data-gen/rand-num.ts b/packages/data-gen/rand-num.ts similarity index 100% rename from data-gen/rand-num.ts rename to packages/data-gen/rand-num.ts diff --git a/data-gen/states.ts b/packages/data-gen/states.ts similarity index 100% rename from data-gen/states.ts rename to packages/data-gen/states.ts diff --git a/deploy/github-actions.ts b/packages/deploy/github-actions.ts similarity index 100% rename from deploy/github-actions.ts rename to packages/deploy/github-actions.ts diff --git a/deploy/index.ts b/packages/deploy/index.ts similarity index 100% rename from deploy/index.ts rename to packages/deploy/index.ts diff --git a/docs/BNK Logo.webp b/packages/docs/BNK Logo.webp similarity index 100% rename from docs/BNK Logo.webp rename to packages/docs/BNK Logo.webp diff --git a/docs/FAQs.md b/packages/docs/FAQs.md similarity index 100% rename from docs/FAQs.md rename to packages/docs/FAQs.md diff --git a/docs/Screen Recording Nov 24.gif b/packages/docs/Screen Recording Nov 24.gif similarity index 100% rename from docs/Screen Recording Nov 24.gif rename to packages/docs/Screen Recording Nov 24.gif diff --git a/docs/attachments/231109092.png b/packages/docs/attachments/231109092.png similarity index 100% rename from docs/attachments/231109092.png rename to packages/docs/attachments/231109092.png diff --git a/docs/blogs/Supercharge Your JavaScript Workflow with Bun Nook Kit.md b/packages/docs/blogs/Supercharge Your JavaScript Workflow with Bun Nook Kit.md similarity index 100% rename from docs/blogs/Supercharge Your JavaScript Workflow with Bun Nook Kit.md rename to packages/docs/blogs/Supercharge Your JavaScript Workflow with Bun Nook Kit.md diff --git a/docs/blogs/attachments/Pasted image 20231124081432.png b/packages/docs/blogs/attachments/Pasted image 20231124081432.png similarity index 100% rename from docs/blogs/attachments/Pasted image 20231124081432.png rename to packages/docs/blogs/attachments/Pasted image 20231124081432.png diff --git a/docs/blogs/attachments/Pasted image 20231124081543.png b/packages/docs/blogs/attachments/Pasted image 20231124081543.png similarity index 100% rename from docs/blogs/attachments/Pasted image 20231124081543.png rename to packages/docs/blogs/attachments/Pasted image 20231124081543.png diff --git a/docs/blogs/attachments/Pasted image 20231124081618.png b/packages/docs/blogs/attachments/Pasted image 20231124081618.png similarity index 100% rename from docs/blogs/attachments/Pasted image 20231124081618.png rename to packages/docs/blogs/attachments/Pasted image 20231124081618.png diff --git a/docs/bnk-cli/bnk-cli-readme.md b/packages/docs/bnk-cli/bnk-cli-readme.md similarity index 100% rename from docs/bnk-cli/bnk-cli-readme.md rename to packages/docs/bnk-cli/bnk-cli-readme.md diff --git a/docs/bnk-quickstart.gif b/packages/docs/bnk-quickstart.gif similarity index 100% rename from docs/bnk-quickstart.gif rename to packages/docs/bnk-quickstart.gif diff --git a/docs/modules.md b/packages/docs/modules.md similarity index 100% rename from docs/modules.md rename to packages/docs/modules.md diff --git a/docs/plugins/BNK Plugins.md b/packages/docs/plugins/BNK Plugins.md similarity index 100% rename from docs/plugins/BNK Plugins.md rename to packages/docs/plugins/BNK Plugins.md diff --git a/docs/plugins/react-server.md b/packages/docs/plugins/react-server.md similarity index 100% rename from docs/plugins/react-server.md rename to packages/docs/plugins/react-server.md diff --git a/docs/plugins/react.md b/packages/docs/plugins/react.md similarity index 100% rename from docs/plugins/react.md rename to packages/docs/plugins/react.md diff --git a/docs/readme.md b/packages/docs/readme.md similarity index 100% rename from docs/readme.md rename to packages/docs/readme.md diff --git a/docs/readmes/auth.md b/packages/docs/readmes/auth.md similarity index 100% rename from docs/readmes/auth.md rename to packages/docs/readmes/auth.md diff --git a/docs/readmes/cli.md b/packages/docs/readmes/cli.md similarity index 100% rename from docs/readmes/cli.md rename to packages/docs/readmes/cli.md diff --git a/docs/readmes/cookies.md b/packages/docs/readmes/cookies.md similarity index 100% rename from docs/readmes/cookies.md rename to packages/docs/readmes/cookies.md diff --git a/docs/readmes/data-gen.md b/packages/docs/readmes/data-gen.md similarity index 100% rename from docs/readmes/data-gen.md rename to packages/docs/readmes/data-gen.md diff --git a/docs/readmes/deploy.md b/packages/docs/readmes/deploy.md similarity index 100% rename from docs/readmes/deploy.md rename to packages/docs/readmes/deploy.md diff --git a/docs/readmes/fetcher.md b/packages/docs/readmes/fetcher.md similarity index 100% rename from docs/readmes/fetcher.md rename to packages/docs/readmes/fetcher.md diff --git a/docs/readmes/files-folder.md b/packages/docs/readmes/files-folder.md similarity index 100% rename from docs/readmes/files-folder.md rename to packages/docs/readmes/files-folder.md diff --git a/docs/readmes/htmlody-css-engine.md b/packages/docs/readmes/htmlody-css-engine.md similarity index 100% rename from docs/readmes/htmlody-css-engine.md rename to packages/docs/readmes/htmlody-css-engine.md diff --git a/docs/readmes/htmlody-plugins.md b/packages/docs/readmes/htmlody-plugins.md similarity index 100% rename from docs/readmes/htmlody-plugins.md rename to packages/docs/readmes/htmlody-plugins.md diff --git a/docs/readmes/htmlody.md b/packages/docs/readmes/htmlody.md similarity index 100% rename from docs/readmes/htmlody.md rename to packages/docs/readmes/htmlody.md diff --git a/docs/readmes/jwt.md b/packages/docs/readmes/jwt.md similarity index 100% rename from docs/readmes/jwt.md rename to packages/docs/readmes/jwt.md diff --git a/docs/readmes/logger.md b/packages/docs/readmes/logger.md similarity index 100% rename from docs/readmes/logger.md rename to packages/docs/readmes/logger.md diff --git a/docs/readmes/npm-release.md b/packages/docs/readmes/npm-release.md similarity index 100% rename from docs/readmes/npm-release.md rename to packages/docs/readmes/npm-release.md diff --git a/docs/readmes/server.md b/packages/docs/readmes/server.md similarity index 100% rename from docs/readmes/server.md rename to packages/docs/readmes/server.md diff --git a/docs/readmes/sqlite.md b/packages/docs/readmes/sqlite.md similarity index 100% rename from docs/readmes/sqlite.md rename to packages/docs/readmes/sqlite.md diff --git a/docs/readmes/state.md b/packages/docs/readmes/state.md similarity index 100% rename from docs/readmes/state.md rename to packages/docs/readmes/state.md diff --git a/docs/readmes/test-utils.md b/packages/docs/readmes/test-utils.md similarity index 100% rename from docs/readmes/test-utils.md rename to packages/docs/readmes/test-utils.md diff --git a/docs/readmes/type-utils.md b/packages/docs/readmes/type-utils.md similarity index 100% rename from docs/readmes/type-utils.md rename to packages/docs/readmes/type-utils.md diff --git a/docs/readmes/utils.md b/packages/docs/readmes/utils.md similarity index 100% rename from docs/readmes/utils.md rename to packages/docs/readmes/utils.md diff --git a/docs/readmes/uuid.md b/packages/docs/readmes/uuid.md similarity index 100% rename from docs/readmes/uuid.md rename to packages/docs/readmes/uuid.md diff --git a/docs/stacks/bnk-payments-stack.md b/packages/docs/stacks/bnk-payments-stack.md similarity index 100% rename from docs/stacks/bnk-payments-stack.md rename to packages/docs/stacks/bnk-payments-stack.md diff --git a/docs/stacks/bnk-react-ssr-client-hydration.md b/packages/docs/stacks/bnk-react-ssr-client-hydration.md similarity index 100% rename from docs/stacks/bnk-react-ssr-client-hydration.md rename to packages/docs/stacks/bnk-react-ssr-client-hydration.md diff --git a/docs/stacks/bnk-server-starter.md b/packages/docs/stacks/bnk-server-starter.md similarity index 100% rename from docs/stacks/bnk-server-starter.md rename to packages/docs/stacks/bnk-server-starter.md diff --git a/docs/usage/cli-usage.md b/packages/docs/usage/cli-usage.md similarity index 100% rename from docs/usage/cli-usage.md rename to packages/docs/usage/cli-usage.md diff --git a/docs/usage/deploy-usage.md b/packages/docs/usage/deploy-usage.md similarity index 100% rename from docs/usage/deploy-usage.md rename to packages/docs/usage/deploy-usage.md diff --git a/docs/usage/fetcher-usage.md b/packages/docs/usage/fetcher-usage.md similarity index 100% rename from docs/usage/fetcher-usage.md rename to packages/docs/usage/fetcher-usage.md diff --git a/docs/usage/files-folders-usage.md b/packages/docs/usage/files-folders-usage.md similarity index 100% rename from docs/usage/files-folders-usage.md rename to packages/docs/usage/files-folders-usage.md diff --git a/docs/usage/htmlody-usage.md b/packages/docs/usage/htmlody-usage.md similarity index 100% rename from docs/usage/htmlody-usage.md rename to packages/docs/usage/htmlody-usage.md diff --git a/docs/usage/jwt-usage.md b/packages/docs/usage/jwt-usage.md similarity index 100% rename from docs/usage/jwt-usage.md rename to packages/docs/usage/jwt-usage.md diff --git a/docs/usage/npm-release-usage.md b/packages/docs/usage/npm-release-usage.md similarity index 100% rename from docs/usage/npm-release-usage.md rename to packages/docs/usage/npm-release-usage.md diff --git a/docs/usage/sqlite-usage.md b/packages/docs/usage/sqlite-usage.md similarity index 100% rename from docs/usage/sqlite-usage.md rename to packages/docs/usage/sqlite-usage.md diff --git a/docs/usage/state-usage.md b/packages/docs/usage/state-usage.md similarity index 100% rename from docs/usage/state-usage.md rename to packages/docs/usage/state-usage.md diff --git a/docs/usage/uuid-usage.md b/packages/docs/usage/uuid-usage.md similarity index 100% rename from docs/usage/uuid-usage.md rename to packages/docs/usage/uuid-usage.md diff --git a/fetcher/create-fetch-factory.test.ts b/packages/fetcher/create-fetch-factory.test.ts similarity index 100% rename from fetcher/create-fetch-factory.test.ts rename to packages/fetcher/create-fetch-factory.test.ts diff --git a/fetcher/create-fetch-factory.ts b/packages/fetcher/create-fetch-factory.ts similarity index 100% rename from fetcher/create-fetch-factory.ts rename to packages/fetcher/create-fetch-factory.ts diff --git a/fetcher/fetch-types.ts b/packages/fetcher/fetch-types.ts similarity index 100% rename from fetcher/fetch-types.ts rename to packages/fetcher/fetch-types.ts diff --git a/fetcher/fetch-utils.ts b/packages/fetcher/fetch-utils.ts similarity index 100% rename from fetcher/fetch-utils.ts rename to packages/fetcher/fetch-utils.ts diff --git a/fetcher/index.ts b/packages/fetcher/index.ts similarity index 100% rename from fetcher/index.ts rename to packages/fetcher/index.ts diff --git a/files-folders/file-editing-utils.test.ts b/packages/files-folders/file-editing-utils.test.ts similarity index 100% rename from files-folders/file-editing-utils.test.ts rename to packages/files-folders/file-editing-utils.test.ts diff --git a/files-folders/file-editing-utils.ts b/packages/files-folders/file-editing-utils.ts similarity index 100% rename from files-folders/file-editing-utils.ts rename to packages/files-folders/file-editing-utils.ts diff --git a/files-folders/file-extension-map.ts b/packages/files-folders/file-extension-map.ts similarity index 100% rename from files-folders/file-extension-map.ts rename to packages/files-folders/file-extension-map.ts diff --git a/files-folders/file-factory.ts b/packages/files-folders/file-factory.ts similarity index 100% rename from files-folders/file-factory.ts rename to packages/files-folders/file-factory.ts diff --git a/files-folders/file-path-utils.test.ts b/packages/files-folders/file-path-utils.test.ts similarity index 100% rename from files-folders/file-path-utils.test.ts rename to packages/files-folders/file-path-utils.test.ts diff --git a/files-folders/file-path-utils.ts b/packages/files-folders/file-path-utils.ts similarity index 100% rename from files-folders/file-path-utils.ts rename to packages/files-folders/file-path-utils.ts diff --git a/files-folders/file-reading-utils.ts b/packages/files-folders/file-reading-utils.ts similarity index 100% rename from files-folders/file-reading-utils.ts rename to packages/files-folders/file-reading-utils.ts diff --git a/files-folders/file-search-utils.test.ts b/packages/files-folders/file-search-utils.test.ts similarity index 100% rename from files-folders/file-search-utils.test.ts rename to packages/files-folders/file-search-utils.test.ts diff --git a/files-folders/file-search-utils.ts b/packages/files-folders/file-search-utils.ts similarity index 100% rename from files-folders/file-search-utils.ts rename to packages/files-folders/file-search-utils.ts diff --git a/files-folders/file-types.ts b/packages/files-folders/file-types.ts similarity index 100% rename from files-folders/file-types.ts rename to packages/files-folders/file-types.ts diff --git a/files-folders/file-validation-utils.test.ts b/packages/files-folders/file-validation-utils.test.ts similarity index 100% rename from files-folders/file-validation-utils.test.ts rename to packages/files-folders/file-validation-utils.test.ts diff --git a/files-folders/file-validation-utils.ts b/packages/files-folders/file-validation-utils.ts similarity index 100% rename from files-folders/file-validation-utils.ts rename to packages/files-folders/file-validation-utils.ts diff --git a/files-folders/files-folder.test.ts b/packages/files-folders/files-folder.test.ts similarity index 100% rename from files-folders/files-folder.test.ts rename to packages/files-folders/files-folder.test.ts diff --git a/files-folders/index.ts b/packages/files-folders/index.ts similarity index 100% rename from files-folders/index.ts rename to packages/files-folders/index.ts diff --git a/htmlody/constants.ts b/packages/htmlody/constants.ts similarity index 100% rename from htmlody/constants.ts rename to packages/htmlody/constants.ts diff --git a/htmlody/css-engine.test.ts b/packages/htmlody/css-engine.test.ts similarity index 100% rename from htmlody/css-engine.test.ts rename to packages/htmlody/css-engine.test.ts diff --git a/htmlody/css-engine.ts b/packages/htmlody/css-engine.ts similarity index 100% rename from htmlody/css-engine.ts rename to packages/htmlody/css-engine.ts diff --git a/htmlody/htmlody-plugins.test.ts b/packages/htmlody/htmlody-plugins.test.ts similarity index 100% rename from htmlody/htmlody-plugins.test.ts rename to packages/htmlody/htmlody-plugins.test.ts diff --git a/htmlody/htmlody-plugins.ts b/packages/htmlody/htmlody-plugins.ts similarity index 100% rename from htmlody/htmlody-plugins.ts rename to packages/htmlody/htmlody-plugins.ts diff --git a/htmlody/htmlody-types.ts b/packages/htmlody/htmlody-types.ts similarity index 100% rename from htmlody/htmlody-types.ts rename to packages/htmlody/htmlody-types.ts diff --git a/htmlody/htmlody-utils.test.ts b/packages/htmlody/htmlody-utils.test.ts similarity index 100% rename from htmlody/htmlody-utils.test.ts rename to packages/htmlody/htmlody-utils.test.ts diff --git a/htmlody/htmlody-utils.ts b/packages/htmlody/htmlody-utils.ts similarity index 100% rename from htmlody/htmlody-utils.ts rename to packages/htmlody/htmlody-utils.ts diff --git a/htmlody/index.ts b/packages/htmlody/index.ts similarity index 100% rename from htmlody/index.ts rename to packages/htmlody/index.ts diff --git a/htmlody/json-to-html-engine.test.ts b/packages/htmlody/json-to-html-engine.test.ts similarity index 100% rename from htmlody/json-to-html-engine.test.ts rename to packages/htmlody/json-to-html-engine.test.ts diff --git a/htmlody/json-to-html-engine.ts b/packages/htmlody/json-to-html-engine.ts similarity index 98% rename from htmlody/json-to-html-engine.ts rename to packages/htmlody/json-to-html-engine.ts index 9133e87..8ae81bc 100644 --- a/htmlody/json-to-html-engine.ts +++ b/packages/htmlody/json-to-html-engine.ts @@ -1,4 +1,3 @@ -import { htmlRes, type middlewareFactory } from '../server' import { type HtmlTags, SELF_CLOSING_TAGS, htmlTags } from './constants' import { generateCSS, generateColorVariables } from './css-engine' import type { HTMLodyPlugin } from './htmlody-plugins' @@ -227,9 +226,7 @@ type HeadConfig = { scriptTags?: { src?: string; type: string; content: string }[] } -type HTMLodyOptions = { - middleware?: ReturnType -} + export const htmlodyNodeFactory = < Plugins extends HTMLodyPlugin[], @@ -260,7 +257,6 @@ export const htmlodyNodeFactory = < export const htmlodyBuilder = < Plugins extends HTMLodyPlugin[], PluginReturns extends NodePluginsMapper, - Options extends HTMLodyOptions = HTMLodyOptions, >({ plugins, options: builderOptions, @@ -452,7 +448,7 @@ export const htmlodyBuilder = < }, }) - return htmlRes(renderSingleNode(tfNode)) + return renderSingleNode(tfNode) }, docNodeMount: (content: JsonTagElNode) => { return turboFrameNode({ diff --git a/packages/htmlody/package.json b/packages/htmlody/package.json new file mode 100644 index 0000000..f6d98d1 --- /dev/null +++ b/packages/htmlody/package.json @@ -0,0 +1,14 @@ +{ + "name": "@bnk/htmlody", + "version": "1.0.0", + "workspaces": [ + "packages/*" + ], + "main": "index.ts", + "types": "index.ts", + "scripts": { + "test": "bun test", + "prepublishOnly": "bun test", + "publish:htmlody": "bun publish --access public" + } +} \ No newline at end of file diff --git a/htmlody/page-confg.ts b/packages/htmlody/page-confg.ts similarity index 100% rename from htmlody/page-confg.ts rename to packages/htmlody/page-confg.ts diff --git a/packages/htmlody/tsconfig.json b/packages/htmlody/tsconfig.json new file mode 100644 index 0000000..1cde24d --- /dev/null +++ b/packages/htmlody/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["ESNext"], + "module": "esnext", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "noEmit": true, + "strict": true, + "types": ["bun-types"], + "jsx": "react-jsx", + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src"] + } \ No newline at end of file diff --git a/jwt/index.ts b/packages/jwt/index.ts similarity index 100% rename from jwt/index.ts rename to packages/jwt/index.ts diff --git a/jwt/jwt-be.test.ts b/packages/jwt/jwt-be.test.ts similarity index 100% rename from jwt/jwt-be.test.ts rename to packages/jwt/jwt-be.test.ts diff --git a/jwt/jwt-be.ts b/packages/jwt/jwt-be.ts similarity index 100% rename from jwt/jwt-be.ts rename to packages/jwt/jwt-be.ts diff --git a/jwt/jwt-client.test.ts b/packages/jwt/jwt-client.test.ts similarity index 100% rename from jwt/jwt-client.test.ts rename to packages/jwt/jwt-client.test.ts diff --git a/jwt/jwt-client.ts b/packages/jwt/jwt-client.ts similarity index 100% rename from jwt/jwt-client.ts rename to packages/jwt/jwt-client.ts diff --git a/jwt/jwt-server-utils.test.ts b/packages/jwt/jwt-server-utils.test.ts similarity index 100% rename from jwt/jwt-server-utils.test.ts rename to packages/jwt/jwt-server-utils.test.ts diff --git a/jwt/jwt-server-utils.ts b/packages/jwt/jwt-server-utils.ts similarity index 100% rename from jwt/jwt-server-utils.ts rename to packages/jwt/jwt-server-utils.ts diff --git a/jwt/jwt-token-file-handlers.ts b/packages/jwt/jwt-token-file-handlers.ts similarity index 100% rename from jwt/jwt-token-file-handlers.ts rename to packages/jwt/jwt-token-file-handlers.ts diff --git a/jwt/jwt-types.ts b/packages/jwt/jwt-types.ts similarity index 100% rename from jwt/jwt-types.ts rename to packages/jwt/jwt-types.ts diff --git a/packages/jwt/package.json b/packages/jwt/package.json new file mode 100644 index 0000000..fbb822e --- /dev/null +++ b/packages/jwt/package.json @@ -0,0 +1,14 @@ +{ + "name": "@bnk/jwt", + "version": "1.0.0", + "workspaces": [ + "packages/*" + ], + "main": "index.ts", + "types": "index.ts", + "scripts": { + "test": "bun test", + "prepublishOnly": "bun test", + "publish:jwt": "bun publish --access public" + } +} \ No newline at end of file diff --git a/logger/client-logger.ts b/packages/logger/client-logger.ts similarity index 100% rename from logger/client-logger.ts rename to packages/logger/client-logger.ts diff --git a/logger/create-logger-factory.ts b/packages/logger/create-logger-factory.ts similarity index 100% rename from logger/create-logger-factory.ts rename to packages/logger/create-logger-factory.ts diff --git a/logger/default-logger.ts b/packages/logger/default-logger.ts similarity index 100% rename from logger/default-logger.ts rename to packages/logger/default-logger.ts diff --git a/logger/index.ts b/packages/logger/index.ts similarity index 100% rename from logger/index.ts rename to packages/logger/index.ts diff --git a/logger/server-logger.server.ts b/packages/logger/server-logger.server.ts similarity index 100% rename from logger/server-logger.server.ts rename to packages/logger/server-logger.server.ts diff --git a/npm-release/index.ts b/packages/npm-release/index.ts similarity index 100% rename from npm-release/index.ts rename to packages/npm-release/index.ts diff --git a/npm-release/mock-package.json b/packages/npm-release/mock-package.json similarity index 100% rename from npm-release/mock-package.json rename to packages/npm-release/mock-package.json diff --git a/npm-release/npm-release.test.ts b/packages/npm-release/npm-release.test.ts similarity index 100% rename from npm-release/npm-release.test.ts rename to packages/npm-release/npm-release.test.ts diff --git a/npm-release/npm-release.ts b/packages/npm-release/npm-release.ts similarity index 100% rename from npm-release/npm-release.ts rename to packages/npm-release/npm-release.ts diff --git a/npm-release/test-utils.test.ts b/packages/npm-release/test-utils.test.ts similarity index 100% rename from npm-release/test-utils.test.ts rename to packages/npm-release/test-utils.test.ts diff --git a/npm-release/test-utils.ts b/packages/npm-release/test-utils.ts similarity index 100% rename from npm-release/test-utils.ts rename to packages/npm-release/test-utils.ts diff --git a/server-router/index.ts b/packages/server-router/index.ts similarity index 88% rename from server-router/index.ts rename to packages/server-router/index.ts index 8a26c27..7f35262 100644 --- a/server-router/index.ts +++ b/packages/server-router/index.ts @@ -3,6 +3,6 @@ export * from './src/router-types' export * from './src/cors-plugin' export * from './src/response/json-response' export * from './src/router-utils' -export * from './src/cors-utils' +export * from '@bnk/cors' export * from './src/auth/auth-handler' export * from './src/auth/auth-handler' diff --git a/server-router/package.json b/packages/server-router/package.json similarity index 71% rename from server-router/package.json rename to packages/server-router/package.json index 2d27c9b..569a4fa 100644 --- a/server-router/package.json +++ b/packages/server-router/package.json @@ -1,6 +1,6 @@ { "name": "@bnk/router", - "version": "1.0.3", + "version": "1.0.4", "type": "module", "main": "index.ts", "types": "index.ts", @@ -17,12 +17,14 @@ }, "scripts": { "test": "bun test", - "prepublishOnly": "bun test" - }, - "dependencies": { - "zod": "^3.23.8" + "prepublishOnly": "bun test", + "publish:router": "bun publish --access public" }, "devDependencies": { "bun-types": "latest" + }, + "dependencies": { + "zod": "^3.23.8", + "@bnk/cors": "workspace:*" } } \ No newline at end of file diff --git a/server-router/src/README.md b/packages/server-router/src/README.md similarity index 100% rename from server-router/src/README.md rename to packages/server-router/src/README.md diff --git a/server-router/src/auth/auth-handler.test.ts b/packages/server-router/src/auth/auth-handler.test.ts similarity index 100% rename from server-router/src/auth/auth-handler.test.ts rename to packages/server-router/src/auth/auth-handler.test.ts diff --git a/server-router/src/auth/auth-handler.ts b/packages/server-router/src/auth/auth-handler.ts similarity index 100% rename from server-router/src/auth/auth-handler.ts rename to packages/server-router/src/auth/auth-handler.ts diff --git a/server-router/src/auth/auth-utils.test.ts b/packages/server-router/src/auth/auth-utils.test.ts similarity index 100% rename from server-router/src/auth/auth-utils.test.ts rename to packages/server-router/src/auth/auth-utils.test.ts diff --git a/server-router/src/auth/auth-utils.ts b/packages/server-router/src/auth/auth-utils.ts similarity index 100% rename from server-router/src/auth/auth-utils.ts rename to packages/server-router/src/auth/auth-utils.ts diff --git a/server-router/src/cors-plugin.test.ts b/packages/server-router/src/cors-plugin.test.ts similarity index 99% rename from server-router/src/cors-plugin.test.ts rename to packages/server-router/src/cors-plugin.test.ts index 0f262c7..f21456a 100644 --- a/server-router/src/cors-plugin.test.ts +++ b/packages/server-router/src/cors-plugin.test.ts @@ -1,8 +1,7 @@ import { expect, test, describe, beforeEach } from "bun:test"; import { CorsPlugin } from "./cors-plugin"; import { Router } from "./router"; -import { CorsOptions } from "./router-types"; - +import { CorsOptions } from '@bnk/cors'; describe("CorsPlugin", () => { let router: Router; let corsPlugin: CorsPlugin; diff --git a/server-router/src/cors-plugin.ts b/packages/server-router/src/cors-plugin.ts similarity index 90% rename from server-router/src/cors-plugin.ts rename to packages/server-router/src/cors-plugin.ts index 47e95a5..d374c1c 100644 --- a/server-router/src/cors-plugin.ts +++ b/packages/server-router/src/cors-plugin.ts @@ -1,6 +1,6 @@ import { Router, } from "./router"; -import { RequestWithData, CorsOptions, RouterPlugin } from "./router-types"; -import { addCorsHeaders, getDefaultCorsOptions, handleCorsPreFlight } from './cors-utils'; +import { RequestWithData, RouterPlugin } from "./router-types"; +import { addCorsHeaders, getDefaultCorsOptions, handleCorsPreFlight, CorsOptions } from '@bnk/cors'; export interface CorsPluginOptions extends CorsOptions { } diff --git a/server-router/src/response/json-response.test.ts b/packages/server-router/src/response/json-response.test.ts similarity index 100% rename from server-router/src/response/json-response.test.ts rename to packages/server-router/src/response/json-response.test.ts diff --git a/server-router/src/response/json-response.ts b/packages/server-router/src/response/json-response.ts similarity index 100% rename from server-router/src/response/json-response.ts rename to packages/server-router/src/response/json-response.ts diff --git a/server-router/src/router-types.ts b/packages/server-router/src/router-types.ts similarity index 91% rename from server-router/src/router-types.ts rename to packages/server-router/src/router-types.ts index 30fbe95..97a08e1 100644 --- a/server-router/src/router-types.ts +++ b/packages/server-router/src/router-types.ts @@ -1,8 +1,7 @@ import { z } from 'zod'; import { Router } from './router'; +import { CorsOptions, HttpMethod } from '@bnk/cors'; -// Basic Types -export type HttpMethod = 'GET' | 'POST' | 'DELETE' | 'PATCH' | 'PUT' | 'OPTIONS'; // Auth Types export type AuthData = { @@ -34,14 +33,6 @@ export interface ValidationError { errors: z.ZodError; } -// CORS Types -export interface CorsOptions { - origin?: string | string[] | ((origin: string) => boolean); - methods?: HttpMethod[]; - credentials?: boolean; - headers?: string[]; -} - // Router Types export type Middleware = ( req: RequestWithData diff --git a/server-router/src/router-utils.test.ts b/packages/server-router/src/router-utils.test.ts similarity index 99% rename from server-router/src/router-utils.test.ts rename to packages/server-router/src/router-utils.test.ts index 6108332..6fd1ee6 100644 --- a/server-router/src/router-utils.test.ts +++ b/packages/server-router/src/router-utils.test.ts @@ -5,8 +5,8 @@ import { validateRequest, ValidationFailedError, } from './router-utils'; -import { getAllowedOrigin, addCorsHeaders } from "./cors-utils"; -import { CorsOptions } from "./router-types"; +import { CorsOptions } from '@bnk/cors'; +import { getAllowedOrigin, addCorsHeaders } from '@bnk/cors'; describe("router-utils", () => { diff --git a/server-router/src/router-utils.ts b/packages/server-router/src/router-utils.ts similarity index 100% rename from server-router/src/router-utils.ts rename to packages/server-router/src/router-utils.ts diff --git a/server-router/src/router.md b/packages/server-router/src/router.md similarity index 100% rename from server-router/src/router.md rename to packages/server-router/src/router.md diff --git a/server-router/src/router.test.ts b/packages/server-router/src/router.test.ts similarity index 99% rename from server-router/src/router.test.ts rename to packages/server-router/src/router.test.ts index 231030e..a495fb0 100644 --- a/server-router/src/router.test.ts +++ b/packages/server-router/src/router.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from "bun:test"; import { Router } from "./router"; import { z } from "zod"; -import { HttpMethod, Route } from "./router-types"; import { CorsPlugin } from "./cors-plugin"; +import { HttpMethod } from '@bnk/cors'; describe("Router", () => { test("handles GET request with exact path match", async () => { @@ -646,7 +646,7 @@ describe("Router Error Handling", () => { test("handles different error types appropriately", async () => { const router = new Router(); - + router.get("/error-string", {}, () => { throw "String error"; }); @@ -770,9 +770,9 @@ describe("Router PUT method", () => { const validRes = await router.handle(validReq); expect(validRes?.status).toBe(200); const validBody = await validRes?.json(); - expect(validBody).toEqual({ - id: "550e8400-e29b-41d4-a716-446655440000", - name: "Alice" + expect(validBody).toEqual({ + id: "550e8400-e29b-41d4-a716-446655440000", + name: "Alice" }); }); }); @@ -875,7 +875,7 @@ describe("Router Path-based Middleware", () => { const router = new Router(); const executionOrder: string[] = []; - router.use(async () => { + router.use(async () => { executionOrder.push('api middleware'); return null; }, "/api"); @@ -893,7 +893,7 @@ describe("Router Path-based Middleware", () => { const router = new Router(); const executionOrder: string[] = []; - router.use(async () => { + router.use(async () => { executionOrder.push('api middleware'); return null; }, "/api"); @@ -991,7 +991,7 @@ describe("Router Plugin System", () => { await router.registerPlugin(testPlugin); await router.get("/test", {}, () => new Response("test")); - + const request = new Request("http://localhost/test"); await router.handle(request); diff --git a/server-router/src/router.ts b/packages/server-router/src/router.ts similarity index 99% rename from server-router/src/router.ts rename to packages/server-router/src/router.ts index 6fb1b45..ee91c3b 100644 --- a/server-router/src/router.ts +++ b/packages/server-router/src/router.ts @@ -6,7 +6,6 @@ import { RouteConfig, RouterOptions, Route, - HttpMethod, ValidationSchema, AuthData, RequestWithData, @@ -15,8 +14,7 @@ import { RouterPlugin, PluginHookFunction } from './router-types'; - - +import { HttpMethod } from '@bnk/cors'; export class Router { private routes: Route[] = []; diff --git a/packages/server-router/tsconfig.json b/packages/server-router/tsconfig.json new file mode 100644 index 0000000..1cde24d --- /dev/null +++ b/packages/server-router/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["ESNext"], + "module": "esnext", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "noEmit": true, + "strict": true, + "types": ["bun-types"], + "jsx": "react-jsx", + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src"] + } \ No newline at end of file diff --git a/state/create-state-dispatchers.test.ts b/packages/state/create-state-dispatchers.test.ts similarity index 100% rename from state/create-state-dispatchers.test.ts rename to packages/state/create-state-dispatchers.test.ts diff --git a/state/create-state-dispatchers.ts b/packages/state/create-state-dispatchers.ts similarity index 98% rename from state/create-state-dispatchers.ts rename to packages/state/create-state-dispatchers.ts index 99e7e58..e4e4b9f 100644 --- a/state/create-state-dispatchers.ts +++ b/packages/state/create-state-dispatchers.ts @@ -1,4 +1,4 @@ -import type { Dispatchers } from '../types' +import type { Dispatchers } from '../../types' import { isArray, isBool, isNum, isObj } from '../utils/value-checkers' export function createArrayDispatchers( diff --git a/state/index.ts b/packages/state/index.ts similarity index 100% rename from state/index.ts rename to packages/state/index.ts diff --git a/state/state-manager.test.ts b/packages/state/state-manager.test.ts similarity index 100% rename from state/state-manager.test.ts rename to packages/state/state-manager.test.ts diff --git a/state/state-manager.ts b/packages/state/state-manager.ts similarity index 97% rename from state/state-manager.ts rename to packages/state/state-manager.ts index af7ae97..11fcf8c 100644 --- a/state/state-manager.ts +++ b/packages/state/state-manager.ts @@ -1,4 +1,4 @@ -import type { FilteredKeys } from '../type-utils' +import type { FilteredKeys } from '../../type-utils' import { createStateDispatchers } from './create-state-dispatchers' export type AllowedStateKeys = boolean | string | number diff --git a/state/ws-state-manager.ts b/packages/state/ws-state-manager.ts similarity index 100% rename from state/ws-state-manager.ts rename to packages/state/ws-state-manager.ts diff --git a/utils/base-error.ts b/packages/utils/base-error.ts similarity index 100% rename from utils/base-error.ts rename to packages/utils/base-error.ts diff --git a/utils/classy.test.ts b/packages/utils/classy.test.ts similarity index 100% rename from utils/classy.test.ts rename to packages/utils/classy.test.ts diff --git a/utils/classy.ts b/packages/utils/classy.ts similarity index 100% rename from utils/classy.ts rename to packages/utils/classy.ts diff --git a/utils/http-types.ts b/packages/utils/http-types.ts similarity index 98% rename from utils/http-types.ts rename to packages/utils/http-types.ts index 826ebde..2034f76 100644 --- a/utils/http-types.ts +++ b/packages/utils/http-types.ts @@ -1,4 +1,4 @@ -import type { PartialRecord } from '../type-utils' +import type { PartialRecord } from '../../type-utils' // for improved readbility when creating server routes lower export type RouteMethods = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options' | 'head' diff --git a/utils/normalize-bytes.test.ts b/packages/utils/normalize-bytes.test.ts similarity index 100% rename from utils/normalize-bytes.test.ts rename to packages/utils/normalize-bytes.test.ts diff --git a/utils/normalize-bytes.ts b/packages/utils/normalize-bytes.ts similarity index 100% rename from utils/normalize-bytes.ts rename to packages/utils/normalize-bytes.ts diff --git a/utils/text-utils.test.ts b/packages/utils/text-utils.test.ts similarity index 100% rename from utils/text-utils.test.ts rename to packages/utils/text-utils.test.ts diff --git a/utils/text-utils.ts b/packages/utils/text-utils.ts similarity index 100% rename from utils/text-utils.ts rename to packages/utils/text-utils.ts diff --git a/utils/ulog.ts b/packages/utils/ulog.ts similarity index 100% rename from utils/ulog.ts rename to packages/utils/ulog.ts diff --git a/utils/value-checkers.test.ts b/packages/utils/value-checkers.test.ts similarity index 100% rename from utils/value-checkers.test.ts rename to packages/utils/value-checkers.test.ts diff --git a/utils/value-checkers.ts b/packages/utils/value-checkers.ts similarity index 100% rename from utils/value-checkers.ts rename to packages/utils/value-checkers.ts diff --git a/uuid/generate-uuid.test.ts b/packages/uuid/generate-uuid.test.ts similarity index 100% rename from uuid/generate-uuid.test.ts rename to packages/uuid/generate-uuid.test.ts diff --git a/uuid/generate-uuid.ts b/packages/uuid/generate-uuid.ts similarity index 100% rename from uuid/generate-uuid.ts rename to packages/uuid/generate-uuid.ts diff --git a/uuid/index.ts b/packages/uuid/index.ts similarity index 100% rename from uuid/index.ts rename to packages/uuid/index.ts diff --git a/packages/uuid/package.json b/packages/uuid/package.json new file mode 100644 index 0000000..8c4470b --- /dev/null +++ b/packages/uuid/package.json @@ -0,0 +1,14 @@ +{ + "name": "@bnk/uuid", + "version": "1.0.0", + "workspaces": [ + "packages/*" + ], + "main": "index.ts", + "types": "index.ts", + "scripts": { + "test": "bun test", + "prepublishOnly": "bun test", + "publish:uuid": "bun publish --access public" + } +} \ No newline at end of file diff --git a/packages/uuid/tsconfig.json b/packages/uuid/tsconfig.json new file mode 100644 index 0000000..1cde24d --- /dev/null +++ b/packages/uuid/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["ESNext"], + "module": "esnext", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "noEmit": true, + "strict": true, + "types": ["bun-types"], + "jsx": "react-jsx", + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src"] + } \ No newline at end of file diff --git a/plugins/react-server/.gitignore b/plugins/react-server/.gitignore deleted file mode 100644 index b3bedd8..0000000 --- a/plugins/react-server/.gitignore +++ /dev/null @@ -1,178 +0,0 @@ -# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore - -# Logs - -logs -_.log -npm-debug.log_ -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) - -report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json - -# Runtime data - -pids -_.pid -_.seed -\*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover - -lib-cov - -# Coverage directory used by tools like istanbul - -coverage -\*.lcov - -# nyc test coverage - -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) - -.grunt - -# Bower dependency directory (https://bower.io/) - -bower_components - -# node-waf configuration - -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) - -build/Release - -# Dependency directories - -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) - -web_modules/ - -# TypeScript cache - -\*.tsbuildinfo - -# Optional npm cache directory - -.npm - -# Optional eslint cache - -.eslintcache - -# Optional stylelint cache - -.stylelintcache - -# Microbundle cache - -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history - -.node_repl_history - -# Output of 'npm pack' - -\*.tgz - -# Yarn Integrity file - -.yarn-integrity - -# dotenv environment variable files - -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) - -.cache -.parcel-cache - -# Next.js build output - -.next -out - -# Nuxt.js build / generate output - -.nuxt -dist - -# Gatsby files - -.cache/ - -# Comment in the public line in if your project uses Gatsby and not Next.js - -# https://nextjs.org/blog/next-9-1#public-directory-support - -# public - -# vuepress build output - -.vuepress/dist - -# vuepress v2.x temp and cache directory - -.temp -.cache - -# Docusaurus cache and generated files - -.docusaurus - -# Serverless directories - -.serverless/ - -# FuseBox cache - -.fusebox/ - -# DynamoDB Local files - -.dynamodb/ - -# TernJS port file - -.tern-port - -# Stores VSCode versions used for testing VSCode extensions - -.vscode-test - -# yarn v2 - -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.\* - -# IntelliJ based IDEs -.idea - -# Finder (MacOS) folder config -.DS_Store - - -/build \ No newline at end of file diff --git a/plugins/react-server/.npmignore b/plugins/react-server/.npmignore deleted file mode 100644 index 76aa0df..0000000 --- a/plugins/react-server/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -bun.lockb -node_modules -app - -public -render-app.tsx \ No newline at end of file diff --git a/plugins/react-server/README.md b/plugins/react-server/README.md deleted file mode 100644 index 9b43bd4..0000000 --- a/plugins/react-server/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# react-server - -![npm bundle size](https://img.shields.io/bundlephobia/min/%40bnk%2Freact-server) - -To install dependencies: - -```bash -bun install -``` - -To run: - -```bash -bun run index.ts -``` - -This project was created using `bun init` in bun v1.0.4. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/plugins/react-server/config.ts b/plugins/react-server/config.ts deleted file mode 100644 index f7f5d05..0000000 --- a/plugins/react-server/config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as path from 'path' - -export type ENV_MODES = 'DEV' | 'PROD' - -export const PROJECT_ROOT = import.meta.dir -export const BUILD_DIR = path.resolve(PROJECT_ROOT, 'build') -export const PUBLIC_DIR = path.resolve(PROJECT_ROOT, 'public') -export const MODE: ENV_MODES = (process.env.MODE as ENV_MODES) || 'DEV' -export const JS_ENTRY_FILE = 'index.js' diff --git a/plugins/react-server/fullstack-state.tsx b/plugins/react-server/fullstack-state.tsx deleted file mode 100644 index 7917b22..0000000 --- a/plugins/react-server/fullstack-state.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React from 'react' -// this file is just an example -import { createContext, useEffect, useState } from 'react' - -export type ContextStateT = { - state: StateT - updateKey: (key: Key, value: StateT[Key]) => void -} - -export type ReactContextT = React.Context> - -const isServer = () => { - return typeof window === 'undefined' -} - -export const appState = { - count: 0, -} - -export type AppStateT = typeof appState - -const initState = async () => { - const isServerSide = isServer() - - if (isServerSide) { - return { - ...appState, - } - } - - const res = await fetch('/state', { - method: 'get', - }) - - const json = await res.json() - - return json as StateT -} - -const AppContext = createContext<{ - state: typeof appState - updateKey: ( - key: Key, - value: (typeof appState)[Key] - ) => void -}>({ - state: { count: 0 }, - updateKey: () => {}, -}) - -const clientToServerStateKeyUpdate = async ( - key: Key, - value: StateT[Key] -) => { - const res = await fetch('/state', { - method: 'post', - body: JSON.stringify({ - type: 'partial', - state: { - [key]: value, - }, - }), - }) - const json = await res.json() - return json -} - -export const StateProvider = ({ - children, - defaultState, -}: { - children: React.ReactNode - defaultState?: StateT -}) => { - const [state, setState] = useState(() => (defaultState || {}) as StateT) - - useEffect(() => { - initState().then((state) => { - setState((prev) => ({ - ...prev, - ...state, - })) - }) - }, []) - - const isServerSide = isServer() - - const updateKey = async (key: Key, value: StateT[Key]) => { - if (isServerSide) return null - - setState((prev) => ({ - ...prev, - [key]: value, - })) - clientToServerStateKeyUpdate(key, value) - } - - return {children} -} - -const useClientAppState = () => { - const { state, updateKey } = React.useContext(AppContext) - - return { - state, - updateKey, - } -} - -const Counter = () => { - const { state, updateKey } = useClientAppState() - - return ( -
-
Count: {state.count}
- -
- ) -} - -export const AppEntry = ({ defaultState }: { defaultState: StateT }) => { - return ( - - - - ) -} diff --git a/plugins/react-server/html-document.tsx b/plugins/react-server/html-document.tsx deleted file mode 100644 index 0bb53d6..0000000 --- a/plugins/react-server/html-document.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react' - -export const HtmlDocument = ({ children, entryFilePath }: { children?: React.ReactNode; entryFilePath: string }) => { - return ( - - - - - - - - - - - - - React App - - - - - -
{children}
- - - ) -} diff --git a/plugins/react-server/hydrate-client.tsx b/plugins/react-server/hydrate-client.tsx deleted file mode 100644 index a482d86..0000000 --- a/plugins/react-server/hydrate-client.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import { hydrateRoot } from 'react-dom/client' - -export const hydrateClient = ({ AppEntry }: { AppEntry: React.ReactNode }) => { - if (typeof window !== 'undefined') { - const root = typeof document !== 'undefined' && document.getElementById('root') - - if (!root) { - console.error('Root node not found') - throw new Error('Root node not found') - } - - console.log('Hydrate!') - hydrateRoot(root, {AppEntry}) - } -} diff --git a/plugins/react-server/index.ts b/plugins/react-server/index.ts deleted file mode 100644 index b7a9e2e..0000000 --- a/plugins/react-server/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { HtmlDocument } from './html-document' -export { hydrateClient } from './hydrate-client' -export { createReactStreamHandler } from './react-dom-stream-handler' -export { reactServer } from './react-server' diff --git a/plugins/react-server/package.json b/plugins/react-server/package.json deleted file mode 100644 index 0b67d4f..0000000 --- a/plugins/react-server/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "@bnk/react-server", - "version": "0.5.20", - "module": "index.ts", - "main": "index.ts", - "scripts": { - "yeet": "npm version patch && npm publish --access public", - "test": "bun test", - "test:watch": "bun test --watch", - "i": "bun install" - }, - "type": "module", - "devDependencies": { - "@types/react": "^18.2.15", - "@types/react-dom": "^18.2.7", - "bnkit": "latest", - "bun-types": "latest", - "react": "latest", - "react-dom": "^18.2.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0", - "bnkit": "^0.5.12" - } -} \ No newline at end of file diff --git a/plugins/react-server/react-dom-stream-handler.tsx b/plugins/react-server/react-dom-stream-handler.tsx deleted file mode 100644 index 6f2338e..0000000 --- a/plugins/react-server/react-dom-stream-handler.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { RouteHandler } from 'bnkit/server' -import { renderToReadableStream } from 'react-dom/server' - -export const createReactStreamHandler = async ({ - renderNode, - entryPath, -}: { - renderNode: React.ReactNode - entryPath: string -}) => { - const reactDomStreamHandler: RouteHandler = async (req) => { - const stream = await renderToReadableStream(renderNode, { - bootstrapScripts: [entryPath], - }) - - return new Response(stream, { - headers: { - 'Content-Type': 'text/html', - }, - }) - } - - return reactDomStreamHandler -} diff --git a/plugins/react-server/react-server.tsx b/plugins/react-server/react-server.tsx deleted file mode 100644 index 8897d08..0000000 --- a/plugins/react-server/react-server.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import type { MiddlewareConfigMap, Routes, serverFactory } from 'bnkit/server' -import { createReactStreamHandler } from './react-dom-stream-handler' - -export const createReactServerRoutes = async ({ - Component, - buildPath = '/build/', -}: { - Component: React.ReactNode - buildPath?: string -}) => { - // change ./ to just / for buildEntry - - const routes = { - '/': { - get: await createReactStreamHandler({ - // idea pass middleware to renderNode and access data on client - renderNode: Component, - entryPath: buildPath, - }), - }, - '^/build/.+': { - get: () => { - return new Response(Bun.file(buildPath).stream(), { - headers: { - 'Content-Type': 'application/javascript', - }, - }) - }, - }, - } - - return routes -} - -export const reactServer = async ({ - Entry, - port = 3000, - buildPath, -}: { - Entry: React.ReactNode - port?: number - buildPath?: string -}) => { - const { start } = serverFactory({ - serve: Bun.serve, - // serve, - routes: await createReactServerRoutes({ - Component: Entry, - buildPath, - }), - }) - - start(port) -} diff --git a/plugins/react-server/render-app.tsx b/plugins/react-server/render-app.tsx deleted file mode 100644 index ff03ab2..0000000 --- a/plugins/react-server/render-app.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { HtmlDocument } from './html-document' -import { AppEntry } from './fullstack-state' -import React from 'react' - -const isServer = () => { - return typeof window === 'undefined' -} - -const useIsServer = () => { - const [server, setServer] = React.useState(isServer()) - - React.useEffect(() => { - setServer(isServer()) - }, []) - - return server -} - -export const RenderApp = ({ retrieveStateFn }: { retrieveStateFn?: (...args: any[]) => State }) => { - const isServer = useIsServer() - return ( - - - {isServer} - - ) -} diff --git a/plugins/react-server/tsconfig.json b/plugins/react-server/tsconfig.json deleted file mode 100644 index 286f43e..0000000 --- a/plugins/react-server/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "lib": ["dom", "esnext"] - }, - "include": ["."], - "exclude": [] -} diff --git a/plugins/react/.gitignore b/plugins/react/.gitignore deleted file mode 100644 index f81d56e..0000000 --- a/plugins/react/.gitignore +++ /dev/null @@ -1,169 +0,0 @@ -# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore - -# Logs - -logs -_.log -npm-debug.log_ -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) - -report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json - -# Runtime data - -pids -_.pid -_.seed -\*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover - -lib-cov - -# Coverage directory used by tools like istanbul - -coverage -\*.lcov - -# nyc test coverage - -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) - -.grunt - -# Bower dependency directory (https://bower.io/) - -bower_components - -# node-waf configuration - -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) - -build/Release - -# Dependency directories - -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) - -web_modules/ - -# TypeScript cache - -\*.tsbuildinfo - -# Optional npm cache directory - -.npm - -# Optional eslint cache - -.eslintcache - -# Optional stylelint cache - -.stylelintcache - -# Microbundle cache - -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history - -.node_repl_history - -# Output of 'npm pack' - -\*.tgz - -# Yarn Integrity file - -.yarn-integrity - -# dotenv environment variable files - -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) - -.cache -.parcel-cache - -# Next.js build output - -.next -out - -# Nuxt.js build / generate output - -.nuxt -dist - -# Gatsby files - -.cache/ - -# Comment in the public line in if your project uses Gatsby and not Next.js - -# https://nextjs.org/blog/next-9-1#public-directory-support - -# public - -# vuepress build output - -.vuepress/dist - -# vuepress v2.x temp and cache directory - -.temp -.cache - -# Docusaurus cache and generated files - -.docusaurus - -# Serverless directories - -.serverless/ - -# FuseBox cache - -.fusebox/ - -# DynamoDB Local files - -.dynamodb/ - -# TernJS port file - -.tern-port - -# Stores VSCode versions used for testing VSCode extensions - -.vscode-test - -# yarn v2 - -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.\* diff --git a/plugins/react/.npmignore b/plugins/react/.npmignore deleted file mode 100644 index a82630f..0000000 --- a/plugins/react/.npmignore +++ /dev/null @@ -1,7 +0,0 @@ -bun.lockb -node_modules -app - -public -vite.config.js -index.html \ No newline at end of file diff --git a/plugins/react/README.md b/plugins/react/README.md deleted file mode 100644 index 0a98734..0000000 --- a/plugins/react/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# React Bun Nookit Plugin - -![npm bundle size](https://img.shields.io/bundlephobia/min/%40bnk%2Freact) - -## Bun Nookit Plugins - -This plugin library provides a collection of easy-to-use, modular plugins to enhance the functionality of Bun Nookit applications. These plugins are not core modules but are built upon the core modules, adding extra features and flexibility to Bun Nookit projects. - -## Overview - -The main purpose of these plugins is to extend the functionality of Bun Nookit applications and make it more versatile for developers. Some of the plugins available in this library include: - -## React-Fetcher: A plugin that provides an easy-to-use fetcher with React hooks for client-side data fetching from APIs and keeping track of the request status. If you're using React, this single plugin will handle all your data fetching needs - -Getting Started -To use a plugin in your Bun Nookit project, simply install it as a dependency and import the relevant functions or components: - -## Installation - -TBD - -Available Plugins - -### React-Fetcher - -A React-based plugin for client-side data fetching. It provides an easy-to-use fetcher with React hooks that handle fetching data from APIs and keeping track of the request status. - -Usage example: - -js - -`import { useFetcher } from '@bnkit/react';` - -`const fetch = useFetcher({ url: 'https://api.example.com' });` - -// Fetch data using the get method -`fetch.get('/resource');` - -// Fetch data using the post method with parameters -`fetch.post('/resource', { params: { test: 'test' } });` - -// Get the current status of the fetch request -`fetch.getStatus();` - -## Contributing - -Contributions to the Bun Nookit plugin library are welcome! If you have an idea for a plugin or would like to improve an existing one, please feel free to submit a pull request or open an issue for discussion. - -This should give you a good starting point for your Bun Nookit plugins README. Modify and expand it as needed to include additional plugins or details about their usage. diff --git a/plugins/react/api-hook.tsx b/plugins/react/api-hook.tsx deleted file mode 100644 index 48c077d..0000000 --- a/plugins/react/api-hook.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { createFetchFactory } from 'bnkit/fetcher' -import React, { ReactNode, createContext, useContext, useMemo, useState } from 'react' -// TODO: This hook needs some more work -type FetchFactoryReturn = ReturnType> - -const FetchContext = createContext( - createFetchFactory({ - baseUrl: '', - config: { - '/test': { - endpoint: '/test', - method: 'get', - }, - }, - }) -) - -export const FetchProvider = ({ - children, - factoryConfig, -}: { - children: ReactNode - factoryConfig: FetchConfig -}) => { - const fetchFactory = useMemo( - () => - createFetchFactory({ - config: factoryConfig, - }), - [] - ) - - return {children} -} - -export function useFetchFactory() { - const fetchFactory = useContext(FetchContext) - - if (!fetchFactory) { - throw new Error('useFetchFactory must be used within a FetchProvider') - } - - return fetchFactory -} - -type FetchState = - | { stage: 'idle'; data: null; retries: 0 } - | { stage: 'fetching'; data: null; retries: number } - | { stage: 'resolved'; data: DataT; retries: number } - | { stage: 'rejected'; data: null; retries: number } - -export function createApiHook({ - configMap, - baseUrl, -}: { - configMap: ConfigMap - baseUrl: string -}) { - return function useCustomApi(endpointKey: keyof ConfigMap) { - const { get, post, ...rest } = useFetchFactory() - const endpointConfig = configMap[endpointKey] - - if (!endpointConfig) { - throw new Error(`No configuration found for endpoint: ${configMap[typeof endpointKey].endpoint}`) - } - - const { response } = endpointConfig - type DataT = typeof response.data - - const [state, setState] = useState>({ - stage: 'idle', - data: null, - retries: 0, - }) - - const fetchWithStateMachine = (config: ConfigMap[typeof endpointKey]) => { - switch (state.stage) { - case 'idle': - setState((prev) => ({ - ...prev, - stage: 'fetching', - data: null, - retries: prev.retries, - })) - break - case 'fetching': - // TODO ensure the request config in the config map matches what the get request expects - get(config) - .then((data) => { - setState({ stage: 'resolved', data, retries: state.retries }) - }) - .catch(() => { - setState((prev) => ({ - stage: 'rejected', - data: null, - retries: prev.retries + 1, - })) - }) - break - case 'rejected': - if (state.retries <= 3) { - setState((prev) => ({ - ...prev, - stage: 'fetching', - data: null, - retries: prev.retries, - })) - } - break - default: - break - } - } - - // return { - // get: (fetchConfig: APIConfig) => - // fetchWithStateMachine(fetchConfig), - // data: state.data, - // // Include other properties/methods as needed - // }; - } -} - -// Now you can use this to create custom hooks tailored for specific APIs - -// Example usage: -// const useMyCustomApi = createApiHook({ baseUrl: 'http://localhost:3000' }); - -// const { -// response: { data }, -// get, -// post, -// ...rest -// } = useCustomApi("/get-users"); diff --git a/plugins/react/app/App.css b/plugins/react/app/App.css deleted file mode 100644 index b9d355d..0000000 --- a/plugins/react/app/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/plugins/react/app/App.tsx b/plugins/react/app/App.tsx deleted file mode 100644 index c55ba0b..0000000 --- a/plugins/react/app/App.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useFetcher } from '..' -import './App.css' - -interface DataType { - userId: number - id: number - title: string - completed: boolean -} - -function App() { - const { get, status, useData } = useFetcher({ - options: { - baseUrl: 'https://jsonplaceholder.typicode.com/', - }, - }) - - return ( -
-
{status}
- -
{JSON.stringify(useData())}
- - -
- ) -} - -export default App diff --git a/plugins/react/app/assets/react.svg b/plugins/react/app/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/plugins/react/app/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/plugins/react/app/assets/vite.svg b/plugins/react/app/assets/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/plugins/react/app/assets/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/plugins/react/app/index.css b/plugins/react/app/index.css deleted file mode 100644 index 2c3fac6..0000000 --- a/plugins/react/app/index.css +++ /dev/null @@ -1,69 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/plugins/react/app/main.tsx b/plugins/react/app/main.tsx deleted file mode 100644 index 0518519..0000000 --- a/plugins/react/app/main.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App.js' -import './index.css' - -ReactDOM.createRoot(document.getElementById('root')).render( - - - -) diff --git a/plugins/react/index.html b/plugins/react/index.html deleted file mode 100644 index d71fa02..0000000 --- a/plugins/react/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Vite + React - - -
- - - diff --git a/plugins/react/index.ts b/plugins/react/index.ts deleted file mode 100644 index eb35b22..0000000 --- a/plugins/react/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { useClipboard } from './use-clipboard' -export { useCookie } from './use-cookie' -export { useLocalStorage } from './use-local-storage' diff --git a/plugins/react/package.json b/plugins/react/package.json deleted file mode 100644 index 718cf54..0000000 --- a/plugins/react/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@bnk/react", - "version": "0.5.20", - "module": "index.ts", - "main": "index.ts", - "scripts": { - "dev": "bun vite", - "build": "bun vite build", - "preview": "bun vite preview", - "yeet": "npm version patch && npm publish --access public", - "test": "bun test", - "test:watch": "bun test --watch", - "i": "bun install" - }, - "type": "module", - "devDependencies": { - "@types/react": "^18.2.15", - "@types/react-dom": "^18.2.7", - "@vitejs/plugin-react": "^4.0.3", - "bun-types": "latest", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "vite": "^4.4.5", - "bnkit": "link:bnkit" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0", - "bnkit": "latest" - } -} \ No newline at end of file diff --git a/plugins/react/public/vite.svg b/plugins/react/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/plugins/react/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/plugins/react/tsconfig.json b/plugins/react/tsconfig.json deleted file mode 100644 index a591d59..0000000 --- a/plugins/react/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "lib": ["ESNext", "DOM", "DOM.Iterable", "es6", "dom"], - "module": "esnext", - "target": "esnext", - "moduleResolution": "bundler", - "moduleDetection": "force", - "strict": true, - "downlevelIteration": true, - "skipLibCheck": true, - "jsx": "react-jsx", - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "allowJs": false, - "types": [ - "bun-types" // add Bun global - ], - "baseUrl": "." - }, - "include": ["apps/**/*.ts", "_examples/**/*.ts", "./release.ts"] -} diff --git a/plugins/react/use-clipboard.ts b/plugins/react/use-clipboard.ts deleted file mode 100644 index 78a5782..0000000 --- a/plugins/react/use-clipboard.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { useCallback, useState } from 'react' - -export type UseClipboardRes = { - clipboardData: string | null - setClipboard: (data: string) => Promise - getClipboard: () => Promise -} - -export function useClipboard(externalValue?: string, updater?: (value: string) => void): UseClipboardRes { - const [internalClipboardData, setInternalClipboardData] = useState(externalValue || null) - - const clipboardData = externalValue !== undefined ? externalValue : internalClipboardData - - // Write to clipboard - const setClipboard = useCallback( - async (data: string) => { - try { - await navigator.clipboard.writeText(data) - if (updater) { - updater(data) - } else { - setInternalClipboardData(data) - } - } catch (error) { - console.error('Failed to write to clipboard', error) - } - }, - [updater] - ) - - // Read from clipboard - const getClipboard = useCallback(async () => { - try { - const data = await navigator.clipboard.readText() - if (updater) { - updater(data) - } else { - setInternalClipboardData(data) - } - } catch (error) { - console.error('Failed to read from clipboard', error) - } - }, [updater]) - - return { - clipboardData, - setClipboard, - getClipboard, - } -} diff --git a/plugins/react/use-cookie.ts b/plugins/react/use-cookie.ts deleted file mode 100644 index d92c1d0..0000000 --- a/plugins/react/use-cookie.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type { CookieOptions } from 'bnkit/cookies/cookie-types' -import { clientCookieFactory } from 'bnkit/cookies/create-client-side-cookie-factory' -import { useEffect, useState } from 'react' - -export function useCookie(cookieKey: string, options?: CookieOptions) { - const cookie = clientCookieFactory(cookieKey) - - const [cookieData, setCookieData] = useState<{ value: T | null }>(() => { - return { - value: cookie.getParsedCookie(), - } - }) - - const getCookie = () => { - return cookie.getRawCookie() - } - - useEffect(() => { - setCookieData({ - value: cookie.getParsedCookie(), - }) - }, []) - - const refreshCookie = () => { - setCookieData({ - value: cookie.getParsedCookie(), - }) - } - - const updateCookie = ( - value: T, - updateOptions: CookieOptions & { - cookieKey?: string // optionally override cookie key - } = options || {} - ) => { - const stringifiedValue = stringifyCookieData(value) - cookie.setCookie(stringifiedValue, { - ...options, - ...updateOptions, - }) - setCookieData({ value: value }) - } - - const removeCookie = () => { - cookie.deleteCookie() - setCookieData({ value: null }) - } - - const stringifyCookieData = (data: T): string => { - if (typeof data === 'string') { - return data - } else { - return JSON.stringify(data) - } - } - - const checkCookie = () => { - return cookie.checkCookie() - } - - return { - cookie: cookieData.value, - updateCookie, - removeCookie, - checkCookie, - getCookie, - refreshCookie, - } -} - -export default useCookie diff --git a/plugins/react/use-local-storage.ts b/plugins/react/use-local-storage.ts deleted file mode 100644 index 892eba9..0000000 --- a/plugins/react/use-local-storage.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { Dispatch, useEffect, useState } from 'react' - -export type LocalStoreConfig = { - key: string // LocalStorage key - initialState: DataT // Initial state if there's nothing in LocalStorage -} - -export type LocalStoreReturnT = [DataT, React.Dispatch>] - -export type GetLSKeyOptions = { - fallbackToInitialValOnError?: boolean -} - -export type GetLSKeyFn = (options: GetLSKeyOptions, onData?: (data: DataT) => void) => DataT | null - -export type SetLSKeyFn = (val: DataT | ((prevState: DataT) => DataT)) => void - -export type SyncLSKeyFn = (fallbackToInitialVal: boolean) => ReturnType> - -export type UseLocalStorageReturn = { - get: GetLSKeyFn - set: SetLSKeyFn - state: DataT - setState: Dispatch> - sync: SyncLSKeyFn - startSyncInterval: (syncConfig: SyncIntervalConfig) => void - stopSyncInterval: (syncConfig: SyncIntervalConfig) => void -} - -export type SyncIntervalConfig = { - interval: number // Time interval in milliseconds -} - -export function useLocalStorage(config: LocalStoreConfig): UseLocalStorageReturn { - // Initial state from local storage or fallback to initialState - const getLSKey: GetLSKeyFn = (options, onData) => { - const { fallbackToInitialValOnError: fallbackToInitialValOnErrror = true } = options - - const storedData = localStorage.getItem(config.key) - - try { - const parsedData = JSON.parse(storedData || '') as DataT - - if (onData) { - onData(parsedData) - } - return parsedData - } catch (e) { - console.error( - 'Failed to properly get Local Storage key/value, returning initial state', - config.key, - storedData, - e - ) - - if (fallbackToInitialValOnErrror) { - if (onData) { - onData(config.initialState) - } - return config.initialState - } - return null - } - } - - const getInitState = () => { - let data: DataT | null = null - - getLSKey( - { - fallbackToInitialValOnError: false, - }, - (dataCb) => { - data = dataCb - } - ) - - return data ?? config.initialState - } - - const [lsKeyState, setLsKeyState] = useState(getInitState()) - const [syncIntervalId, setSyncIntervalId] = useState(null) - - const startSyncInterval = (syncConfig: SyncIntervalConfig) => { - // Clear any existing interval - if (syncIntervalId) { - clearInterval(syncIntervalId) - } - - if (typeof window !== 'undefined') { - // Set up the new interval - const id = window?.setInterval(() => { - syncLSKeyState(true) // Assuming you want to fallback to initial value during auto-sync - }, syncConfig.interval) - - setSyncIntervalId(id) - } - } - - const stopSyncInterval = () => { - if (syncIntervalId) { - clearInterval(syncIntervalId) - } - } - - useEffect(() => { - return () => stopSyncInterval() - }, [syncIntervalId]) - - // syncs the state to local storage - const syncLSKeyState = (fallbackToInitialVal: boolean = false): DataT | null => { - return getLSKey( - { - fallbackToInitialValOnError: fallbackToInitialVal, - }, - (data) => { - setLsKeyState(data) - } - ) - } - - const setLSKey: SetLSKeyFn = (value) => { - let stringifiedVal = '' - - try { - stringifiedVal = JSON.stringify(value) - } catch (e) { - console.error('Failed to properly set Local Storage key/value', config.key, value, e) - } - - try { - localStorage.setItem(config.key, stringifiedVal) - setLsKeyState(value) - } catch (e) { - console.error('Failed to properly set Local Storage key/value', config.key, value, e) - } - } - - // Whenever key changes, update local storage - useEffect(() => { - getLSKey( - { - fallbackToInitialValOnError: true, - }, - (data) => { - setLsKeyState(data) - } - ) - }, [config.key]) - - return { - set: setLSKey, - get: getLSKey, - state: lsKeyState, - // set state directly, this is needed incase you need direct access to prevState for example - setState: setLsKeyState, - sync: syncLSKeyState, - startSyncInterval, - stopSyncInterval, - } -} diff --git a/plugins/react/use-server-state.ts b/plugins/react/use-server-state.ts deleted file mode 100644 index f6dbc9b..0000000 --- a/plugins/react/use-server-state.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { useEffect, useRef, useState } from 'react' -import { createStateDispatchers } from 'state/create-state-dispatchers' -import type { Dispatchers } from '../../types' -const MAX_RETRIES = 5 - -const getAppStateFromLocalStorage = (defaultState: State): State => { - const appStateString = localStorage.getItem('appState') - - try { - const storedState = appStateString ? JSON.parse(appStateString) : {} - // Merge the default state with the stored state. - // This will ensure that missing keys in storedState will be taken from defaultState. - return { ...defaultState, ...storedState } - } catch (error) { - return defaultState - } -} - -type OptimisticMap = Partial> - -export type DefaultOptions = { - optimistic?: boolean -} - -export function useServerState({ - defaultState, - url, - optimisticMap, -}: { - url: string - defaultState: State - optimisticMap?: OptimisticMap -}): { - state: State - control: Dispatchers - dispatch: (key: keyof State, value: State[keyof State], opts?: Options) => void -} { - const [state, setState] = useState(() => getAppStateFromLocalStorage(defaultState)) - const wsRef = useRef(null) - const initialized = useRef(false) - const prevStateRef = useRef(null) - const retryCount = useRef(0) - - useEffect(() => { - const connectToServer = () => { - const websocket = new WebSocket(url) - wsRef.current = websocket - - websocket.onopen = () => { - retryCount.current = 0 - } - - websocket.onmessage = (event) => { - if (typeof event.data !== 'string') return - const receivedData = JSON.parse(event.data) - - if (receivedData.status === 'failure' && prevStateRef.current) { - setState(prevStateRef.current) - } else if (receivedData.key && 'value' in receivedData) { - setState((prevState) => ({ - ...prevState, - [receivedData.key]: receivedData.value, - })) - } - } - - websocket.onclose = (event) => { - // Check if the WebSocket was closed unexpectedly and we haven't exceeded max retries - if (event.code !== 1000 && retryCount.current < MAX_RETRIES) { - retryCount.current += 1 - const delay = Math.min(1000 * retryCount.current, 30000) - setTimeout(connectToServer, delay) - } else { - retryCount.current = 0 // Reset retry count if we've reached max retries or if closure was normal - } - } - - return () => { - websocket?.close?.() - } - } - - connectToServer() - - // Clean up function - return () => { - wsRef.current?.close() - } - }, [url]) - - useEffect(() => { - const storedState = getAppStateFromLocalStorage(defaultState) - - if (!initialized.current) { - setState(storedState) - initialized.current = true - return - } - }, []) - - useEffect(() => { - if (initialized.current) { - localStorage.setItem('appState', JSON.stringify(state)) - } - }, [state]) - - const sendToServer = (key: keyof State, value: State[keyof State]) => { - if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { - wsRef.current.send(JSON.stringify({ key, value })) - } - } - - const dispatch = (key: keyof State, value: State[keyof State], opts?: Options) => { - const isOptimistic = opts?.optimistic ?? optimisticMap?.[key] !== false - - if (isOptimistic) { - prevStateRef.current = state - setState((prev) => ({ ...prev, [key]: value })) - } - - sendToServer(key, value) - } - - const control = createStateDispatchers({ - defaultState, - state, - updateFunction: dispatch, - }) - - return { - state, - control, - dispatch, - } -} diff --git a/plugins/react/vite.config.js b/plugins/react/vite.config.js deleted file mode 100644 index 5a33944..0000000 --- a/plugins/react/vite.config.js +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()], -}) diff --git a/plugins/react/webrtc/README.md b/plugins/react/webrtc/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/plugins/react/webrtc/index.ts b/plugins/react/webrtc/index.ts deleted file mode 100644 index 68f10f0..0000000 --- a/plugins/react/webrtc/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { createWebRTCFactory } from './webrtc-factory' diff --git a/plugins/react/webrtc/tsconfig.json b/plugins/react/webrtc/tsconfig.json deleted file mode 100644 index ba9cdd4..0000000 --- a/plugins/react/webrtc/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "lib": ["dom", "esnext"] - }, - "include": ["."] -} diff --git a/plugins/react/webrtc/webrtc-factory.ts b/plugins/react/webrtc/webrtc-factory.ts deleted file mode 100644 index 0ddcbcc..0000000 --- a/plugins/react/webrtc/webrtc-factory.ts +++ /dev/null @@ -1,71 +0,0 @@ -export type WebRTCConfig = { - iceServers?: RTCIceServer[] -} - -export type WebRTCFactoryMethods = keyof ReturnType - -/** - * Creates a WebRTC factory object with methods to create peer connections, offers, answers, and set remote descriptions. - * @param defaultConfig - Optional default configuration for the WebRTC factory. - * @returns An object with methods to create peer connections, offers, answers, and set remote descriptions. - */ -export function createWebRTCFactory({ defaultConfig }: { defaultConfig?: WebRTCConfig }) { - const configuration: RTCConfiguration = { - iceServers: defaultConfig?.iceServers || [], - } - - /** - * Creates a new RTCPeerConnection object with the given custom configuration. - * @param customConfig - Optional custom configuration for the RTCPeerConnection object. - * @returns A new RTCPeerConnection object. - */ - function createPeerConnection(customConfig?: RTCConfiguration): RTCPeerConnection { - const peerConnection = new RTCPeerConnection({ - ...configuration, - ...customConfig, - }) - return peerConnection - } - - /** - * Creates a new offer and sets it as the local description for the given RTCPeerConnection object. - * @param peerConnection - The RTCPeerConnection object to create the offer for. - * @returns A promise that resolves with the created offer. - */ - function createOffer(peerConnection: RTCPeerConnection): Promise { - return peerConnection.createOffer().then((offer) => { - return peerConnection.setLocalDescription(offer).then(() => offer) - }) - } - - /** - * Creates a new answer and sets it as the local description for the given RTCPeerConnection object. - * @param peerConnection - The RTCPeerConnection object to create the answer for. - * @returns A promise that resolves with the created answer. - */ - function createAnswer(peerConnection: RTCPeerConnection): Promise { - return peerConnection.createAnswer().then((answer) => { - return peerConnection.setLocalDescription(answer).then(() => answer) - }) - } - - /** - * Sets the remote description for the given RTCPeerConnection object. - * @param peerConnection - The RTCPeerConnection object to set the remote description for. - * @param description - The remote description to set. - * @returns A promise that resolves when the remote description has been set. - */ - function setRemoteDescription( - peerConnection: RTCPeerConnection, - description: RTCSessionDescriptionInit - ): Promise { - return peerConnection.setRemoteDescription(description) - } - - return { - createPeerConnection, - createOffer, - createAnswer, - setRemoteDescription, - } -} diff --git a/release.ts b/release.ts index f5f7ce2..90a45f9 100644 --- a/release.ts +++ b/release.ts @@ -3,7 +3,7 @@ import path from 'path' import { exit } from 'process' // import * as u from "./"; import { deploy, npm } from 'index' -import { ulog } from './utils/ulog' +import { ulog } from './packages/utils/ulog' // run bun test const testProc = Bun.spawnSync(['bun', 'test', '--coverage'], {}) @@ -37,9 +37,9 @@ const { npmPublish, setupNpmAuth, updatePackageVersion } = npm.npmReleaseFactory const corePackagePath = path.resolve(process.cwd(), 'package.json') // todo resolve all plugin paths -const pluginReactPath = path.resolve(process.cwd(), 'plugins', 'react', 'package.json') +const pluginReactPath = path.resolve(process.cwd(), 'packages', 'plugins', 'react', 'package.json') -const pluginReactServerPath = path.resolve(process.cwd(), 'plugins', 'react-server', 'package.json') +const pluginReactServerPath = path.resolve(process.cwd(), 'packages', 'plugins', 'react-server', 'package.json') ulog({ actor: e?.GITHUB_ACTOR, diff --git a/server/cors-types.ts b/server/cors-types.ts deleted file mode 100644 index 56bcfff..0000000 --- a/server/cors-types.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { CommonHttpHeaders, RouteMethods } from '../utils/http-types' - -export type CORSOptions = { - origins?: string[] - methods?: RouteMethods[] - headers?: CommonHttpHeaders[] | string[] - credentials?: boolean -} -export type ClientCORSCredentialOpts = 'omit' | 'same-origin' | 'include' diff --git a/server/create-cors-middleware.test.ts b/server/create-cors-middleware.test.ts deleted file mode 100644 index f8f2422..0000000 --- a/server/create-cors-middleware.test.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { beforeEach, describe, expect, it, jest } from 'bun:test' -import type { CORSOptions } from '../utils/http-types' -import { configCorsMiddleware } from './create-cors-middleware' - -describe('configCorsMiddleware', () => { - const mockNext = jest.fn() - - beforeEach(() => { - mockNext.mockClear() - }) - - const createMockRequest = (method: string, origin: string) => { - return { - method, - headers: new Headers({ Origin: origin }), - } as Request - } - - it('should set CORS headers for allowed origin', () => { - const options: CORSOptions = { - allowedOrigins: ['http://example.com'], - allowedMethods: ['GET', 'POST'], - } - const middleware = configCorsMiddleware(options) - const req = createMockRequest('GET', 'http://example.com') - - const headers = middleware(req, mockNext) - - expect(headers.get('Access-Control-Allow-Origin')).toBe('http://example.com') - expect(headers.get('Access-Control-Allow-Methods')).toBe('GET, POST') - expect(mockNext).toHaveBeenCalled() - }) - - it('should handle OPTIONS request', () => { - const options: CORSOptions = { - allowedOrigins: ['http://example.com'], - allowedMethods: ['GET', 'POST'], - } - const middleware = configCorsMiddleware(options) - const req = createMockRequest('OPTIONS', 'http://example.com') - req.headers.set('Access-Control-Request-Method', 'GET') - - const response = middleware(req, mockNext) - - expect(response).toBeInstanceOf(Response) - if (response instanceof Response) { - expect(response.status).toBe(204) - expect(response.headers.get('Access-Control-Allow-Origin')).toBe('http://example.com') - expect(response.headers.get('Access-Control-Allow-Methods')).toBe('GET, POST') - } - expect(mockNext).not.toHaveBeenCalled() - }) - - it('should throw error for disallowed origin', () => { - const options: CORSOptions = { - allowedOrigins: ['http://example.com'], - allowedMethods: ['GET'], - } - const middleware = configCorsMiddleware(options) - const req = createMockRequest('GET', 'http://malicious.com') - - expect(() => middleware(req, mockNext)).toThrow('Origin http://malicious.com not allowed') - expect(mockNext).not.toHaveBeenCalled() - }) - - it('should throw error for disallowed method', () => { - const options: CORSOptions = { - allowedOrigins: ['http://example.com'], - allowedMethods: ['GET'], - } - const middleware = configCorsMiddleware(options) - const req = createMockRequest('POST', 'http://example.com') - - expect(() => middleware(req, mockNext)).toThrow('Method POST not allowed') - expect(mockNext).not.toHaveBeenCalled() - }) - - it('should handle wildcard origin', () => { - const options: CORSOptions = { - allowedOrigins: ['*'], - allowedMethods: ['GET'], - } - const middleware = configCorsMiddleware(options) - const req = createMockRequest('GET', 'http://any-origin.com') - - const headers = middleware(req, mockNext) - - expect(headers.get('Access-Control-Allow-Origin')).toBe('*') - expect(mockNext).toHaveBeenCalled() - }) - - it('should set credentials header when specified', () => { - const options: CORSOptions = { - allowedOrigins: ['http://example.com'], - allowedMethods: ['GET'], - credentials: true, - } - const middleware = configCorsMiddleware(options) - const req = createMockRequest('GET', 'http://example.com') - - const headers = middleware(req, mockNext) - - expect(headers.get('Access-Control-Allow-Credentials')).toBe('true') - expect(mockNext).toHaveBeenCalled() - }) - - it('should set allowed headers when specified', () => { - const options: CORSOptions = { - allowedOrigins: ['http://example.com'], - allowedMethods: ['GET'], - allowedHeaders: ['Content-Type', 'Authorization'], - } - const middleware = configCorsMiddleware(options) - const req = createMockRequest('GET', 'http://example.com') - - const headers = middleware(req, mockNext) - - expect(headers.get('Access-Control-Allow-Headers')).toBe('Content-Type, Authorization') - expect(mockNext).toHaveBeenCalled() - }) -}) \ No newline at end of file diff --git a/server/create-cors-middleware.ts b/server/create-cors-middleware.ts deleted file mode 100644 index 2103626..0000000 --- a/server/create-cors-middleware.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { CORSOptions } from '../utils/http-types' -import type { Middleware, NextFunction } from './middleware-types' - -const setAllowOrigin = (headers: Headers, originToSet: string) => - headers.set('Access-Control-Allow-Origin', originToSet || '') - -const setAllowMethods = (headers: Headers, methods: string[]) => - headers.set('Access-Control-Allow-Methods', methods.map((method) => method).join(', ')) - -const addAllowHeader = (headers: Headers, options?: CORSOptions) => { - if (options?.allowedHeaders?.join) { - headers.set('Access-Control-Allow-Headers', options.allowedHeaders.map((header) => header).join(', ')) - } -} - -const setAllowCredentials = (headers: Headers, options?: CORSOptions) => - options?.credentials && headers.set('Access-Control-Allow-Credentials', 'true') - -export const configCorsMiddleware = (options?: CORSOptions): Middleware => { - const allowedMethods: string[] = options?.allowedMethods || [] - - return (request: Request, next: NextFunction) => { - const reqMethod = request.method.toUpperCase() - const reqOrigin = request.headers.get('Origin') - - const allowedOrigins = options?.allowedOrigins || [] - const originAllowed = allowedOrigins.includes('*') || allowedOrigins.includes(reqOrigin || '') - const methodAllowed = allowedMethods.includes(reqMethod) - - const corsHeaders = new Headers() - const originToSet = allowedOrigins.includes('*') ? '*' : reqOrigin - - if (!reqOrigin) { - throw new Error('Origin header missing') - } - - // Set CORS headers - setAllowOrigin(corsHeaders, originToSet || '') - setAllowMethods(corsHeaders, allowedMethods) - addAllowHeader(corsHeaders, options) - setAllowCredentials(corsHeaders, options) - - if (reqMethod === 'OPTIONS') { - const optionRequestMethod = request.headers.get('Access-Control-Request-Method') - - if (optionRequestMethod && !allowedMethods.includes(optionRequestMethod)) { - throw new Error(`Method ${optionRequestMethod} is not allowed`) - } - - // For OPTIONS requests, we'll return early with a 204 response - return new Response(null, { status: 204, headers: corsHeaders }) - } - - if (!originAllowed) { - throw new Error(`Origin ${reqOrigin} not allowed`) - } - - if (!methodAllowed) { - throw new Error(`Method ${reqMethod} not allowed`) - } - - // Continue to the next middleware or route handler, passing the CORS headers - next() - return corsHeaders - } -} diff --git a/server/incoming-request-handler.test.ts b/server/incoming-request-handler.test.ts deleted file mode 100644 index e25fc1e..0000000 --- a/server/incoming-request-handler.test.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { beforeEach, describe, expect, it, jest, test } from 'bun:test' -import { serverRequestHandler } from './incoming-request-handler' -import { middlewareFactory } from './middleware-factory' -import type { Routes } from './route-types' - -describe('serverRequestHandler', () => { - const mockRoutes: Routes = { - '/test': { - get: jest.fn().mockResolvedValue(new Response('Test GET')), - post: jest.fn().mockResolvedValue(new Response('Test POST')), - }, - '/regex/\\d+': { - get: jest.fn().mockResolvedValue(new Response('Regex GET')), - }, - } - - const mockMiddlewareFactory = middlewareFactory({ - test: () => ({ test: 'data' }), - }) - - const mockOptionsHandler = jest.fn().mockResolvedValue(new Response('Options')) - - beforeEach(() => { - jest.clearAllMocks() - }) - - it('should handle a simple GET request', async () => { - const req = new Request('http://example.com/test', { method: 'GET' }) - const response = await serverRequestHandler({ req, routes: mockRoutes, middlewareRet: mockMiddlewareFactory }) - - expect(response.status).toBe(200) - expect(await response.text()).toBe('Test GET') - expect(mockRoutes['/test'].get).toHaveBeenCalled() - }) - - it('should handle a POST request', async () => { - const req = new Request('http://example.com/test', { method: 'POST' }) - const response = await serverRequestHandler({ req, routes: mockRoutes, middlewareRet: mockMiddlewareFactory }) - - expect(response.status).toBe(200) - expect(await response.text()).toBe('Test POST') - expect(mockRoutes['/test'].post).toHaveBeenCalled() - }) - - it('should handle a regex route', async () => { - const req = new Request('http://example.com/regex/123', { method: 'GET' }) - const response = await serverRequestHandler({ req, routes: mockRoutes, middlewareRet: mockMiddlewareFactory }) - - expect(response.status).toBe(200) - expect(await response.text()).toBe('Regex GET') - expect(mockRoutes['/regex/\\d+'].get).toHaveBeenCalled() - }) - - it('should return 404 for unmatched routes', async () => { - const req = new Request('http://example.com/nonexistent', { method: 'GET' }) - const response = await serverRequestHandler({ req, routes: mockRoutes, middlewareRet: mockMiddlewareFactory }) - - expect(response.status).toBe(404) - expect(await response.text()).toBe('Not Found') - }) - - it('should handle OPTIONS requests', async () => { - const req = new Request('http://example.com/test', { method: 'OPTIONS' }) - const response = await serverRequestHandler({ - req, - routes: mockRoutes, - middlewareRet: mockMiddlewareFactory, - optionsHandler: mockOptionsHandler, - }) - - expect(response.status).toBe(200) - expect(await response.text()).toBe('Options') - expect(mockOptionsHandler).toHaveBeenCalled() - }) - - it('should return default OPTIONS response if no optionsHandler provided', async () => { - const req = new Request('http://example.com/test', { method: 'OPTIONS' }) - const response = await serverRequestHandler({ req, routes: mockRoutes, middlewareRet: mockMiddlewareFactory }) - - expect(response.status).toBe(204) - }) - it('should handle middleware responses', async () => { - const mockMiddleware = middlewareFactory({ - test: () => new Response('Middleware Response'), - }) - const req = new Request('http://example.com/test', { method: 'GET' }) - const response = await serverRequestHandler({ req, routes: mockRoutes, middlewareRet: mockMiddleware }) - expect(response.status).toBe(200) - const responseText = await response.text() - expect(responseText).toBe('Middleware Response') - // Ensure that the route handler wasn't called - expect(mockRoutes['/test'].get).not.toHaveBeenCalled() - }) - it('should handle errors and return 500 status', async () => { - const errorRoutes: Routes = { - '/error': { - get: jest.fn().mockRejectedValue(new Error('Test Error')), - }, - } - const req = new Request('http://example.com/error', { method: 'GET' }) - const response = await serverRequestHandler({ req, routes: errorRoutes, middlewareRet: mockMiddlewareFactory }) - - expect(response.status).toBe(500) - expect(await response.text()).toBe('Test Error') - }) - it('should handle nested regex routes', async () => { - const nestedRegexRoutes: Routes = { - '/api/users/\\d+/posts/\\d+': { - get: jest.fn().mockResolvedValue(new Response('Nested Regex GET')), - }, - } - const req = new Request('http://example.com/api/users/123/posts/456', { method: 'GET' }) - const response = await serverRequestHandler({ - req, - routes: nestedRegexRoutes, - middlewareRet: mockMiddlewareFactory, - }) - - expect(response.status).toBe(200) - expect(await response.text()).toBe('Nested Regex GET') - expect(nestedRegexRoutes['/api/users/\\d+/posts/\\d+'].get).toHaveBeenCalled() - }) - - it('should handle URLs with query parameters', async () => { - const req = new Request('http://example.com/test?param1=value1¶m2=value2', { method: 'GET' }) - const response = await serverRequestHandler({ req, routes: mockRoutes, middlewareRet: mockMiddlewareFactory }) - - console.log('Response:', { - status: response.status, - headers: response.headers.toJSON(), - bodyUsed: response.bodyUsed, - }) - - expect(response.status).toBe(200) - - if (!response.bodyUsed) { - const text = await response.text() - expect(text).toBe('Test GET') - } else { - console.warn('Response body already used') - } - - expect(mockRoutes['/test'].get).toHaveBeenCalled() - }) - - it('should return 405 Method Not Allowed for unsupported methods', async () => { - const req = new Request('http://example.com/test', { method: 'PUT' }) - const response = await serverRequestHandler({ req, routes: mockRoutes, middlewareRet: mockMiddlewareFactory }) - - expect(response.status).toBe(405) - expect(await response.text()).toBe('Method Not Allowed') - }) - - it('should handle errors in middleware', async () => { - const errorMiddleware = middlewareFactory({ - error: () => { - throw new Error('Middleware Error') - }, - }) - const req = new Request('http://example.com/test', { method: 'GET' }) - const response = await serverRequestHandler({ req, routes: mockRoutes, middlewareRet: errorMiddleware }) - - expect(response.status).toBe(500) - expect(await response.text()).toBe('Middleware Error') - }) - - it('should handle URLs with Unicode characters', async () => { - const unicodeRoutes: Routes = { - '/test/üñîçødé': { - get: jest.fn().mockResolvedValue(new Response('Unicode GET')), - }, - } - const req = new Request('http://example.com/test/üñîçødé', { method: 'GET' }) - const response = await serverRequestHandler({ req, routes: unicodeRoutes, middlewareRet: mockMiddlewareFactory }) - - expect(response.status).toBe(200) - expect(await response.text()).toBe('Unicode GET') - expect(unicodeRoutes['/test/üñîçødé'].get).toHaveBeenCalled() - }) -}) diff --git a/server/incoming-request-handler.ts b/server/incoming-request-handler.ts deleted file mode 100644 index de0c506..0000000 --- a/server/incoming-request-handler.ts +++ /dev/null @@ -1,109 +0,0 @@ -import type { InferMiddlewareDataMap, MiddlewareConfigMap } from '.' -import type { middlewareFactory } from './middleware-factory' -import type { RouteHandler, Routes } from './route-types' - -function isValidRegex(str: string): boolean { - if (str === '/') return false - - try { - new RegExp(str) - return true - } catch (e) { - return false - } -} - -export const serverRequestHandler = < - MiddlewareFactory extends ReturnType, - MiddlewareConfig extends MiddlewareConfigMap = Parameters[0], - MiddlewareDataMap extends InferMiddlewareDataMap = InferMiddlewareDataMap, ->({ - req, - routes, - middlewareRet, - optionsHandler, -}: { - req: Request - routes: Routes - middlewareRet?: MiddlewareFactory - optionsHandler?: RouteHandler -}): Promise => { - const url = new URL(req.url) - const decodedPathname = decodeURIComponent(url.pathname) - const executeMiddlewares = middlewareRet?.executeMiddlewares - - // Execute middleware first - const middlewarePromise = executeMiddlewares ? executeMiddlewares(req) : Promise.resolve({} as MiddlewareDataMap) - - console.log({ - req, - middlewarePromise, - }) - - return middlewarePromise - .then(async (middlewareResponses) => { - // Check if middleware has already handled the response - if (middlewareResponses && typeof middlewareResponses === 'object') { - for (const key in middlewareResponses) { - if (middlewareResponses[key] instanceof Response) { - return middlewareResponses[key] as Response - } - } - } - - let matchedHandler: RouteHandler | null | undefined = null - - const pathRoutes = routes[decodedPathname] - - if (pathRoutes) { - matchedHandler = pathRoutes[req.method.toLowerCase() as keyof typeof pathRoutes] - if (!matchedHandler && req.method !== 'OPTIONS') { - // Method not allowed for this path - return new Response('Method Not Allowed', { status: 405 }) - } - } - - // try regex match after direct string match - if (!matchedHandler) { - for (const pattern in routes) { - if (isValidRegex(pattern)) { - const regex = new RegExp(pattern, 'i') - if (regex.test(decodedPathname)) { - matchedHandler = routes[pattern][req.method.toLowerCase() as keyof (typeof routes)[typeof pattern]] - break - } - } - } - } - - if (req.method === 'OPTIONS') { - return optionsHandler - ? optionsHandler(req, middlewareResponses as MiddlewareDataMap) - : new Response(null, { status: 204 }) // Default OPTIONS response - } - - if (!matchedHandler) { - console.error('No match found for request', { - url: req.url, - method: req.method, - pathRoutes, - routes, - }) - return new Response('Not Found', { status: 404 }) - } - - const response = await matchedHandler(req, middlewareResponses as MiddlewareDataMap) - - const corsHeaders = middlewareResponses.cors as Headers | undefined - if (corsHeaders) { - corsHeaders.forEach((value, key) => { - response.headers.set(key, value) - }) - } - - - - return response - }) - .catch((err) => new Response(err.message, { status: 500 })) -} diff --git a/server/index.ts b/server/index.ts deleted file mode 100644 index 7dc47bb..0000000 --- a/server/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { middlewareFactory } from './middleware-factory' -export type { InferMiddlewareFromFactory } from './middleware-factory' -export { serverFactory } from './server-factory' -export type { InferMiddlewareDataMap, Middleware, MiddlewareConfigMap } from './middleware-types' -export type { RouteHandler, RouteOptionsMiddlewareManger, Routes } from './route-types' -export { htmlRes, jsonRes, redirectRes, combineResponseHeaders } from './server-utils' -export { configCorsMiddleware } from './create-cors-middleware' diff --git a/server/jwt-cookie-middleware.test.ts b/server/jwt-cookie-middleware.test.ts deleted file mode 100644 index c25152c..0000000 --- a/server/jwt-cookie-middleware.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { describe, expect, it, mock } from 'bun:test' -import { jwtBack } from '../jwt' -import { clearJwtCookie, jwtCookieMiddleware, setJwtCookie } from './jwt-cookie-middleware' - -describe('JWT Cookie Middleware', () => { - const mockOptions = { - cookieName: 'auth_token', - jwtSecret: 'test_secret', - cookieOptions: { httpOnly: true, secure: true }, - } - - const mockData = { id: 1, username: 'testuser', role: 'admin' } - - it('should set JWT cookie', () => { - const mockResponse = new Response() - setJwtCookie(mockOptions, mockData, mockResponse) - - const setCookieHeader = mockResponse.headers.get('Set-Cookie') - expect(setCookieHeader).toBeTruthy() - expect(setCookieHeader).toContain('auth_token=') - expect(setCookieHeader).toContain('httpOnly') - expect(setCookieHeader).toContain('secure') - }) - - it('should clear JWT cookie', () => { - const mockResponse = new Response() - clearJwtCookie(mockOptions, mockResponse) - - const setCookieHeader = mockResponse.headers.get('Set-Cookie') - expect(setCookieHeader).toBeTruthy() - expect(setCookieHeader).toContain('auth_token=') - expect(setCookieHeader).toContain('max-age=-1') - }) - - it('should parse valid JWT from cookie', async () => { - const middleware = jwtCookieMiddleware(mockOptions) - const jwtFactory = jwtBack({ factorySignSecret: mockOptions.jwtSecret }) - const token = jwtFactory.createJwt({ payload: mockData }) - - const mockRequest = new Request('https://example.com', { - headers: { Cookie: `${mockOptions.cookieName}=${token}` }, - }) - - const mockNext = mock(() => Promise.resolve()) - - const result = await middleware(mockRequest, mockNext) - - expect(result).toMatchObject(mockData) - expect(result).toHaveProperty('exp') - expect(typeof result.exp).toBe('number') - expect(mockNext).toHaveBeenCalled() - }) - - it('should handle invalid JWT in cookie', async () => { - const middleware = jwtCookieMiddleware(mockOptions) - - const mockRequest = new Request('https://example.com', { - headers: { Cookie: `${mockOptions.cookieName}=invalid_token` }, - }) - - const mockNext = mock(() => Promise.resolve()) - - const result = await middleware(mockRequest, mockNext) - - expect(result).toBeNull() - expect(mockNext).toHaveBeenCalled() - }) - - it('should handle missing cookie', async () => { - const middleware = jwtCookieMiddleware(mockOptions) - - const mockRequest = new Request('https://example.com') - - const mockNext = mock(() => Promise.resolve()) - - const result = await middleware(mockRequest, mockNext) - - expect(result).toBeNull() - expect(mockNext).toHaveBeenCalled() - }) - - it('should handle different types of data', async () => { - const middleware = jwtCookieMiddleware(mockOptions) - const jwtFactory = jwtBack({ factorySignSecret: mockOptions.jwtSecret }) - - const testCases = [ - { id: 1, name: 'Test User' }, - { productId: 'abc123', quantity: 5 }, - { token: 'xyz789', expiresAt: new Date().toISOString() }, - ] - - for (const testData of testCases) { - const token = jwtFactory.createJwt({ payload: testData }) - const mockRequest = new Request('https://example.com', { - headers: { Cookie: `${mockOptions.cookieName}=${token}` }, - }) - - const mockNext = mock(() => Promise.resolve()) - const result = await middleware(mockRequest, mockNext) - - expect(result).toMatchObject(testData) - expect(result).toHaveProperty('exp') - expect(typeof result.exp).toBe('number') - expect(mockNext).toHaveBeenCalled() - } - }) -}) \ No newline at end of file diff --git a/server/jwt-cookie-middleware.ts b/server/jwt-cookie-middleware.ts deleted file mode 100644 index 9059157..0000000 --- a/server/jwt-cookie-middleware.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { jwtBack } from "../jwt"; -import { createServerCookieFactory } from "../cookies"; -import type { Middleware } from "./middleware-types"; -import type { JwtPayload } from "../jwt/jwt-types"; - -export interface JwtCookieMiddlewareOptions { - cookieName: string; - jwtSecret: string; - cookieOptions?: Parameters< - ReturnType["setCookie"] - >[1]["options"]; -} - -export const jwtCookieMiddleware = ( - options: JwtCookieMiddlewareOptions, -): Middleware => { - const { cookieName, jwtSecret, cookieOptions } = options; - const cookieFactory = createServerCookieFactory(cookieName); - const jwtFactory = jwtBack({ factorySignSecret: jwtSecret }); - - return async (request, next) => { - const cookieValue = cookieFactory.getRawCookie(request); - let jwtData: T | null = null; - - if (cookieValue) { - try { - const { payload } = await jwtFactory.verifyJwt(cookieValue); - jwtData = payload as T; - } catch (error) { - // Invalid or expired token, clear the cookie - cookieFactory.deleteCookie(new Response()); - } - } - - await next(); - - return jwtData; - }; -}; - -export const setJwtCookie = ( - options: JwtCookieMiddlewareOptions, - data: T, - response: Response, -): void => { - const { cookieName, jwtSecret, cookieOptions } = options; - const cookieFactory = createServerCookieFactory(cookieName); - const jwtFactory = jwtBack({ factorySignSecret: jwtSecret }); - - const token = jwtFactory.createJwt({ payload: data as JwtPayload }); - cookieFactory.setCookie(token, { res: response, options: cookieOptions }); -}; - -export const clearJwtCookie = ( - options: JwtCookieMiddlewareOptions, - response: Response, -): void => { - const { cookieName } = options; - const cookieFactory = createServerCookieFactory(cookieName); - cookieFactory.deleteCookie(response); -}; diff --git a/server/middleware-factory.test.ts b/server/middleware-factory.test.ts deleted file mode 100644 index f6ef46b..0000000 --- a/server/middleware-factory.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { describe, expect, mock, test } from 'bun:test' -import { middlewareFactory } from './middleware-factory' -import type { Middleware } from './middleware-types' - -describe('middlewareFactory', () => { - test('should execute middlewares in order', async () => { - const mockMiddleware1: Middleware = mock(async (req, next) => { - await next() - return 'middleware1' - }) - - const mockMiddleware2: Middleware = mock(async (req, next) => { - await next() - return 42 - }) - - const factory = middlewareFactory({ - mw1: mockMiddleware1, - mw2: mockMiddleware2, - }) - - const req = new Request('https://example.com') - const results = await factory.executeMiddlewares(req) - - expect(results).toEqual({ - mw1: 'middleware1', - mw2: 42, - }) - - expect(mockMiddleware1).toHaveBeenCalledTimes(1) - expect(mockMiddleware2).toHaveBeenCalledTimes(1) - }) - - test('should handle errors in middleware', async () => { - const errorMiddleware: Middleware = mock(async (req, next) => { - throw new Error('Middleware error') - }) - - const factory = middlewareFactory({ - errorMw: errorMiddleware, - }) - - const req = new Request('https://example.com') - - await expect(factory.executeMiddlewares(req)).rejects.toThrow('Middleware error') - }) - - test('should pass control to next middleware', async () => { - const middleware1: Middleware = mock((req, next) => { - next() - return Promise.resolve('middleware1') - }) - - const middleware2: Middleware = mock((req, next) => { - return Promise.resolve('middleware2') - }) - - const factory = middlewareFactory({ - mw1: middleware1, - mw2: middleware2, - }) - - const req = new Request('https://example.com') - const results = await factory.executeMiddlewares(req) - - expect(results).toEqual({ - mw1: 'middleware1', - mw2: 'middleware2', - }) - - expect(middleware1).toHaveBeenCalledTimes(1) - expect(middleware2).toHaveBeenCalledTimes(1) - }) - - test('should infer types correctly', () => { - const factory = middlewareFactory({ - mw1: () => 'string', - mw2: () => 42, - }) - - const types = factory.inferTypes() - - // TypeScript should infer this type correctly - type InferredTypes = { - mw1: string - mw2: number - } - - // This line will cause a TypeScript error if the types are not inferred correctly - const _test: InferredTypes = types - }) -}) diff --git a/server/middleware-factory.ts b/server/middleware-factory.ts deleted file mode 100644 index 6d72c5e..0000000 --- a/server/middleware-factory.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { InferMiddlewareDataMap, MiddlewareConfigMap, NextFunction } from './middleware-types' - -export type InferMiddlewareFromFactory = ReturnType< - ReturnType['inferTypes'] -> - -export const middlewareFactory = (middlewareOptions: T) => { - const middlewares: MiddlewareConfigMap = { - ...middlewareOptions, - } - - const executeMiddlewares = async (req: Request) => { - const results: InferMiddlewareDataMap = {} as InferMiddlewareDataMap - - const executeMiddleware = async (index: number): Promise => { - if (index >= Object.keys(middlewares).length) { - return - } - - const [id, mw] = Object.entries(middlewares)[index] - const next: NextFunction = async (error?: Error) => { - if (error) { - throw error - } - await executeMiddleware(index + 1) - } - - const result = await mw(req, next) - if (result !== undefined) { - results[id as keyof T] = result - } - } - - await executeMiddleware(0) - - return results - } - - const inferTypes = () => { - return middlewares as InferMiddlewareDataMap - } - - return { executeMiddlewares, inferTypes } -} \ No newline at end of file diff --git a/server/middleware-types.ts b/server/middleware-types.ts deleted file mode 100644 index 404632a..0000000 --- a/server/middleware-types.ts +++ /dev/null @@ -1,12 +0,0 @@ -export type NextFunction = (error?: Error) => Promise - -export type Middleware = (request: Request, next: NextFunction) => Res | Promise - -export type MiddlewareConfigMap = { - [id: string]: Middleware -} -export type UnwrapPromise = T extends Promise ? U : T - -export type InferMiddlewareDataMap = { - [K in keyof T]: UnwrapPromise> -} diff --git a/server/route-types.ts b/server/route-types.ts deleted file mode 100644 index 5ac5576..0000000 --- a/server/route-types.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { RouteMethods } from '../utils/http-types' -import type { middlewareFactory } from './middleware-factory' - -import type { HTMLodyPlugin, htmlodyBuilder } from '../htmlody' -import type { InferMiddlewareDataMap, MiddlewareConfigMap } from './middleware-types' - -export type RouteHandler = (request: Request, middlewareData: M) => Response | Promise - -type RouteTypeOptsT = { - middleware?: MiddlewareConfigMap - htmlody?: { - plugins?: HTMLodyPlugin[] - builder: typeof htmlodyBuilder - } - - htmlodyBuilder?: typeof htmlodyBuilder -} - -export type Routes< - RouteType extends RouteTypeOptsT = RouteTypeOptsT, - InferredMiddlewareData = RouteType['middleware'] extends MiddlewareConfigMap - ? InferMiddlewareDataMap - : never, -> = { - [path: string]: Partial<{ - [K in RouteMethods]: RouteHandler - }> -} - -export type RouteOptionsMiddlewareManger< - MiddlewareFactory extends ReturnType, - MiddlewareDataMap = InferMiddlewareDataMap, -> = { - [path: string]: Partial<{ - [K in RouteMethods]: RouteHandler - }> -} diff --git a/server/server-factory.test.ts b/server/server-factory.test.ts deleted file mode 100644 index 42c6a7b..0000000 --- a/server/server-factory.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, jest, mock, spyOn } from 'bun:test' -import { serverRequestHandler } from './incoming-request-handler' -import { middlewareFactory } from './middleware-factory' -import { serverFactory } from './server-factory' - -describe('serverFactory', () => { - const mockServe = jest.fn() - const mockMiddleware = middlewareFactory({}) - const mockRoutes = {} - const mockFetchHandler = jest.fn() - const mockOptionsHandler = jest.fn() - const mockWebSocket = jest.fn() - - beforeEach(() => { - jest.clearAllMocks() - }) - - it('should return an object with a start function', () => { - const server = serverFactory({ - routes: mockRoutes, - }) - - expect(typeof server.start).toBe('function') - }) - - it('should call serve with correct parameters', () => { - const server = serverFactory({ - middleware: mockMiddleware, - routes: mockRoutes, - fetchHandler: mockFetchHandler, - optionsHandler: mockOptionsHandler, - serve: mockServe, - websocket: mockWebSocket, - }) - - server.start(8080) - - expect(mockServe).toHaveBeenCalledWith({ - port: 8080, - fetch: expect.any(Function), - websocket: mockWebSocket, - }) - }) - - it('should use default port 3000 if not specified', () => { - const server = serverFactory({ - routes: mockRoutes, - serve: mockServe, - }) - - server.start() - - expect(mockServe).toHaveBeenCalledWith( - expect.objectContaining({ - port: 3000, - }) - ) - }) - - it('should log starting message in development environment', () => { - const originalEnv = Bun.env.NODE_ENV - Bun.env.NODE_ENV = 'development' - const consoleSpy = spyOn(console, 'log') - - const server = serverFactory({ - routes: mockRoutes, - serve: mockServe, - }) - - server.start(5000) - - expect(consoleSpy).toHaveBeenCalledWith('Starting server on port: ', 5000) - expect(consoleSpy).toHaveBeenCalledTimes(1) - - consoleSpy.mockRestore() - Bun.env.NODE_ENV = originalEnv - }) - - it('should not log starting message in production environment', () => { - const originalEnv = Bun.env.NODE_ENV - Bun.env.NODE_ENV = 'production' - const consoleSpy = spyOn(console, 'log') - - const server = serverFactory({ - routes: mockRoutes, - serve: mockServe, - }) - - server.start(5000) - - expect(consoleSpy).not.toHaveBeenCalled() - - consoleSpy.mockRestore() - Bun.env.NODE_ENV = originalEnv - }) - - it('should use serverRequestHandler as default fetchHandler', () => { - const mockServerRequestHandler = mock(() => new Response('Mocked response')) - const originalModule = require('./incoming-request-handler') - - // Mock the module - mock.module('./incoming-request-handler', () => ({ - serverRequestHandler: mockServerRequestHandler, - })) - - // Re-import serverFactory to use the mocked version - const { serverFactory } = require('./server-factory') - - const server = serverFactory({ - routes: mockRoutes, - serve: mockServe, - }) - - server.start() - - const fetchHandler = mockServe.mock.calls[0][0].fetch - expect(fetchHandler).toBeDefined() - - const mockRequest = new Request('http://example.com') - fetchHandler(mockRequest) - - expect(mockServerRequestHandler).toHaveBeenCalledTimes(1) - expect(mockServerRequestHandler).toHaveBeenCalledWith({ - req: mockRequest, - routes: mockRoutes, - middlewareRet: undefined, - optionsHandler: undefined, - }) - - // Restore the original module - mock.restore() - }) - - it('should use custom fetchHandler when provided', () => { - const customFetchHandler = jest.fn() - const server = serverFactory({ - routes: mockRoutes, - fetchHandler: customFetchHandler, - serve: mockServe, - }) - - server.start() - - const fetchHandler = mockServe.mock.calls[0][0].fetch - expect(fetchHandler).toBeDefined() - - const mockRequest = new Request('http://example.com') - fetchHandler(mockRequest) - - expect(customFetchHandler).toHaveBeenCalledTimes(1) - expect(customFetchHandler).toHaveBeenCalledWith({ - req: mockRequest, - routes: mockRoutes, - middlewareRet: undefined, - optionsHandler: undefined, - }) - }) -}) diff --git a/server/server-factory.ts b/server/server-factory.ts deleted file mode 100644 index 84c64ad..0000000 --- a/server/server-factory.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { WebSocketHandler } from 'bun' -import { serverRequestHandler } from './incoming-request-handler' -import type { middlewareFactory } from './middleware-factory' -import type { InferMiddlewareDataMap, MiddlewareConfigMap } from './middleware-types' -import type { RouteHandler, Routes } from './route-types' - -export const serverFactory = < - MiddlewareFactory extends ReturnType, - MiddlewareConfig extends MiddlewareConfigMap = Parameters[0], - MiddlewareDataMap extends InferMiddlewareDataMap = InferMiddlewareDataMap, ->({ - middleware, - routes, - fetchHandler = serverRequestHandler, - optionsHandler, - serve = Bun.serve, - websocket, -}: { - middleware?: MiddlewareFactory - routes: Routes - fetchHandler?: typeof serverRequestHandler - optionsHandler?: RouteHandler - serve?: typeof Bun.serve - websocket?: WebSocketHandler -}) => { - const start = (port = 3000) => { - if (Bun?.env.NODE_ENV === 'development') { - console.log('Starting server on port: ', port) - } - return serve({ - port, - fetch: (req) => - fetchHandler({ - req, - routes, - middlewareRet: middleware, - optionsHandler, - }), - websocket, - }) - } - - return { - start, - } -} diff --git a/server/server-utils.test.ts b/server/server-utils.test.ts deleted file mode 100644 index 65b3df6..0000000 --- a/server/server-utils.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { describe, expect, it } from 'bun:test' -import { htmlRes, jsonRes, parseQueryParams, parseRequestHeaders, redirectRes } from './server-utils' - -describe('server-utils', () => { - describe('parseQueryParams', () => { - it('should parse query parameters correctly', () => { - const request = new Request('http://example.com?foo=bar&baz=qux') - const params = parseQueryParams(request) - expect(params).toEqual({ foo: 'bar', baz: 'qux' }) - }) - - it('should return an empty object for no query parameters', () => { - const request = new Request('http://example.com') - const params = parseQueryParams(request) - expect(params).toEqual({}) - }) - }) - - describe('parseRequestHeaders', () => { - it('should parse request headers correctly', () => { - const headers = new Headers({ - 'Content-Type': 'application/json', - 'X-Custom-Header': 'custom value' - }) - const request = new Request('http://example.com', { headers }) - const parsedHeaders = parseRequestHeaders(request) - expect(parsedHeaders).toEqual({ - 'content-type': 'application/json', - 'x-custom-header': 'custom value' - }) - }) - }) - - describe('jsonRes', () => { - it('should create a JSON response with correct content type', () => { - const body = { foo: 'bar' } - const response = jsonRes(body) - expect(response.headers.get('Content-Type')).toBe('application/json') - expect(response.status).toBe(200) - }) - - it('should merge custom options', () => { - const body = { foo: 'bar' } - const options = { status: 201, headers: { 'X-Custom-Header': 'custom value' } } - const response = jsonRes(body, options) - expect(response.status).toBe(201) - expect(response.headers.get('X-Custom-Header')).toBe('custom value') - }) - - it('should copy headers from an existing response', () => { - const body = { foo: 'bar' } - const existingResponse = new Response(null, { - headers: { 'X-Existing-Header': 'existing value' } - }) - const response = jsonRes(body, {}, existingResponse) - expect(response.headers.get('X-Existing-Header')).toBe('existing value') - }) - }) - - describe('htmlRes', () => { - it('should create an HTML response with correct content type', () => { - const body = 'Hello' - const response = htmlRes(body) - expect(response.headers.get('Content-Type')).toBe('text/html') - expect(response.status).toBe(200) - }) - - it('should merge custom options', () => { - const body = 'Hello' - const options = { status: 201, headers: { 'X-Custom-Header': 'custom value' } } - const response = htmlRes(body, options) - expect(response.status).toBe(201) - expect(response.headers.get('X-Custom-Header')).toBe('custom value') - }) - }) - - describe('redirectRes', () => { - it('should create a redirect response with correct status and location', () => { - const url = 'http://example.com/redirect' - const response = redirectRes(url) - expect(response.status).toBe(302) - expect(response.headers.get('Location')).toBe(url) - }) - - it('should allow custom status code', () => { - const url = 'http://example.com/redirect' - const response = redirectRes(url, { status: 301 }) - expect(response.status).toBe(301) - }) - - it('should set custom headers', () => { - const url = 'http://example.com/redirect' - const response = redirectRes(url, { headers: { 'X-Custom-Header': 'custom value' } }) - expect(response.headers.get('X-Custom-Header')).toBe('custom value') - }) - - it('should set cookies if provided', () => { - const url = 'http://example.com/redirect' - const response = redirectRes(url, { cookies: { session: 'abc123' } }) - expect(response.headers.get('Set-Cookie')).toBe('session=abc123') - }) - }) -}) \ No newline at end of file diff --git a/server/server-utils.ts b/server/server-utils.ts deleted file mode 100644 index bacc8c6..0000000 --- a/server/server-utils.ts +++ /dev/null @@ -1,99 +0,0 @@ -export function parseQueryParams(request: Request): ParamsType { - const url = new URL(request.url) - const params: ParamsType = {} as ParamsType - - url.searchParams.forEach((value, key) => { - // @ts-ignore - params[key] = value as any - }) - - return params -} - -export function parseRequestHeaders(request: Request): HeadersType { - return request.headers.toJSON() as unknown as HeadersType -} - -export type JSONResType = ( - body: JSONBodyGeneric, - options?: ResponseInit, - response?: Response -) => Response - -// json res creates it's own response object, but if one is passed in, it will copy headers -export const jsonRes: JSONResType = (body, options = {}, response) => { - const combinedHeaders = new Headers(options?.headers) - - if (response) { - response.headers.forEach((value, key) => { - combinedHeaders.set(key, value) - }) - } - - const headerEntries: [string, string][] = [] - combinedHeaders.forEach((value, key) => { - headerEntries.push([key, value]) - }) - - return new Response(JSON.stringify(body), { - ...options, - headers: { - ...Object.fromEntries(headerEntries), - 'Content-Type': 'application/json', - }, - }) -} - -export function htmlRes(body: string, options?: ResponseInit): Response { - return new Response(body, { - ...options, - headers: { - 'Content-Type': 'text/html', - ...options?.headers, - }, - }) -} - -type RedirectOptions = { - status?: number - statusText?: string - headers?: Record - body?: string | null - cookies?: Record -} - -export const redirectRes = (url: string, options: RedirectOptions = {}): Response => { - const defaultHeaders: Record = { - Location: url, - } - - // Merge custom headers with default headers - const headers = { ...defaultHeaders, ...options.headers } - - // Set cookies if provided - if (options.cookies) { - for (const [name, value] of Object.entries(options.cookies)) { - headers['Set-Cookie'] = `${name}=${value}` - } - } - - return new Response(options.body || null, { - status: options.status || 302, - statusText: options.statusText, - headers, - }) -} - -export function combineResponseHeaders(...responses: Response[] | [Response, Response]): Headers { - const combinedHeaders = new Headers() - - const responsesToCombine: Response[] = Array.isArray(responses[0]) ? responses[0] : responses - - for (const response of responsesToCombine) { - response.headers.forEach((value, key) => { - combinedHeaders.set(key, value) - }) - } - - return combinedHeaders -} diff --git a/sqlite/index.ts b/sqlite/index.ts deleted file mode 100644 index 61e89fc..0000000 --- a/sqlite/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { sqliteFactory as createSqliteFactory } from './sqlite-factory' -export { createItem, deleteItemById, readItems, updateItem } from './sqlite-utils/crud-fn-utils' -export { createTableQuery } from './sqlite-utils/table-query-gen' diff --git a/sqlite/sqlite-factory.test.ts b/sqlite/sqlite-factory.test.ts deleted file mode 100644 index 9fa7a31..0000000 --- a/sqlite/sqlite-factory.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import Database from 'bun:sqlite' -import { beforeEach, describe, expect, test } from 'bun:test' -import { type SchemaMap, sqliteFactory } from './sqlite-factory' - -let db = new Database(':memory:') - -const noteSchema = { - id: { type: 'TEXT' }, - text: { type: 'TEXT' }, -} satisfies SchemaMap - -describe('createSqliteFactory', () => { - beforeEach(() => { - db = new Database(':memory:') - }) - test('It should create a db factory', () => { - const { dbTableFactory } = sqliteFactory({ db }) - - expect(dbTableFactory).toBeDefined() - }) - - test('should create and read a note in sqlite', async () => { - const { dbTableFactory } = sqliteFactory({ db }) - - const notesTable = dbTableFactory({ - schema: noteSchema, - tableName: 'notes', - debug: false, - }) - - notesTable.create({ - id: '1', - text: 'some text', - }) - - const notes = notesTable.getAll() - - expect(notes).toEqual([{ id: '1', text: 'some text' }]) - }) - test('should create and read a note in sqlite and update it', () => { - const { dbTableFactory } = sqliteFactory({ db, debug: true }) - - const notesTable = dbTableFactory({ - schema: noteSchema, - tableName: 'notes', - debug: false, - }) - - notesTable.create({ - id: '1', - text: 'some text', - }) - - const notes = notesTable.getAll() - - expect(notes).toEqual([{ id: '1', text: 'some text' }]) - - notesTable.update('1', { - text: 'some text updated', - }) - - const updatedNotes = notesTable.getAll() - - expect(updatedNotes).toEqual([{ id: '1', text: 'some text updated' }]) - }) - test('should create and read a note in sqlite and delete it', () => { - const { dbTableFactory } = sqliteFactory({ db, debug: true }) - - const notesTable = dbTableFactory({ - schema: noteSchema, - tableName: 'notes', - debug: false, - }) - - notesTable.create({ - id: '1', - text: 'some text', - }) - - const notes = notesTable.getAll() - - expect(notes).toEqual([{ id: '1', text: 'some text' }]) - - notesTable.delById('1') - - const updatedNotes = notesTable.getAll() - - expect(updatedNotes).toEqual([]) - }) -}) diff --git a/sqlite/sqlite-factory.ts b/sqlite/sqlite-factory.ts deleted file mode 100644 index 241f990..0000000 --- a/sqlite/sqlite-factory.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type { Database } from 'bun:sqlite' - -import { type SqliteTableFactoryParams, sqliteTableFactory } from './sqlite-table-factory' - -type SqliteFactoryParams = { - db: Database - debug?: boolean - enableForeignKeys?: boolean -} - -type DBTableFactoryParams = Omit, 'db'> & { - debug?: boolean -} - -// Mapping of SQLite types to TypeScript types. -export type SQLiteSchemaToTSMap = { - TEXT: string - NUMERIC: number | string - INTEGER: number - REAL: number - BLOB: any - DATE: Date -} - -export type SQLiteData = keyof SQLiteSchemaToTSMap - -export type FieldDef = { - type: SQLiteData - primaryKey?: boolean - unique?: boolean - foreignKey?: string - required?: boolean - defaultValue?: string | number -} - -// Mapped type that takes a schema with SQLite types and returns a schema with TypeScript types. -export type SQLInfer = { - [K in keyof T]: T[K] extends FieldDef ? SQLiteSchemaToTSMap[T[K]['type']] : never -} - -export type SchemaMap = Partial> - -export const getType = (schema: T): SQLInfer => { - return undefined as any as SQLInfer -} - -export function sqliteFactory({ - db, - debug = false, - // because foreign keys in sqlite are disabled by default - // https://renenyffenegger.ch/notes/development/databases/SQLite/sql/pragma/foreign_keys#:~:text=pragma%20foreign_keys%20%3D%20on%20enforces%20foreign,does%20not%20enforce%20foreign%20keys.&text=Explicitly%20turning%20off%20the%20validation,dump%20'ed%20database. - // turning off foreign keys may be using when importing a .dump'ed database - enableForeignKeys = false, -}: SqliteFactoryParams) { - if (enableForeignKeys) { - // Enable foreign key constraints - db.query('PRAGMA foreign_keys = ON;').run() - } - - function dbTableFactory({ - debug: debugTable = debug || false, - schema, - tableName, - }: DBTableFactoryParams) { - return sqliteTableFactory( - { - db, - schema, - tableName, - }, - { - debug: debugTable, - enableForeignKeys: debug, - } - ) - } - - return { dbTableFactory } -} diff --git a/sqlite/sqlite-table-factory.test.ts b/sqlite/sqlite-table-factory.test.ts deleted file mode 100644 index 7b75fd6..0000000 --- a/sqlite/sqlite-table-factory.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Database } from 'bun:sqlite' -import { afterEach, describe, expect, test } from 'bun:test' -import type { SchemaMap } from './sqlite-factory' -import { sqliteTableFactory } from './sqlite-table-factory' - -const mockDb = new Database(':memory:') -const testSchema = { - id: { type: 'TEXT' }, - name: { type: 'TEXT' }, - age: { type: 'INTEGER' }, -} satisfies SchemaMap - -const factoryOptions = { - debug: true, -} - -describe('sqliteTableFactory', () => { - const factory = sqliteTableFactory( - { - db: mockDb, - schema: testSchema, - tableName: 'test', - }, - factoryOptions - ) - - afterEach(() => { - // Clean up the database after each test (for isolation purposes) - mockDb.query('delete FROM test;').run() - }) - - test('should insert an item into the database using the factory', () => { - const item = { id: '1', name: 'John', age: 30 } - factory.create(item) - const items = factory.getAll() - expect(items.length).toBe(1) - expect(items[0]).toEqual(item) - }) - - test('should read items from the database using the factory', () => { - const item = { id: '1', name: 'Jane', age: 25 } - mockDb.query('INSERT INTO test (id, name, age) VALUES (?, ?, ?)').run(item.id, item.name, item.age) - const items = factory.getAll() - expect(items.length).toBe(1) - expect(items[0]).toEqual(item) - }) - - test('should update an item in the database using the factory', () => { - const item = { id: '1', name: 'Doe', age: 35 } - mockDb.query('INSERT INTO test (id, name, age) VALUES (?, ?, ?)').run(item.id, item.name, item.age) - const updatedName = 'John Doe' - factory.update(item.id, { name: updatedName }) - const items = factory.getAll() - expect(items.length).toBe(1) - 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 } - mockDb.query('INSERT INTO test (id, name, age) VALUES (?, ?, ?)').run(item.id, item.name, item.age) - factory.delById(item.id) - const items = factory.getAll() - expect(items.length).toBe(0) - }) -}) diff --git a/sqlite/sqlite-table-factory.ts b/sqlite/sqlite-table-factory.ts deleted file mode 100644 index 0f5eb78..0000000 --- a/sqlite/sqlite-table-factory.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type Database from 'bun:sqlite' -import type { SQLInfer, SchemaMap } from './sqlite-factory' -import { - createItem, - deleteItemById, - readFirstItemByKey, - readItemById, - readItems, - readItemsWhere, - updateItem, -} from './sqlite-utils/crud-fn-utils' -import { createTableQuery } from './sqlite-utils/table-query-gen' - -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 SQLInfer = SQLInfer, ->(params: SqliteTableFactoryParams, options: SqliteTableOptions = {}) { - const { db, schema, tableName } = params - const { debug = false } = options - - db.query(createTableQuery({ tableName, schema, debug })).run() - - return { - readWhere(where: Partial) { - return readItemsWhere({ db, tableName, debug, where }) - }, - create( - item: TranslatedSchema, - createOptions?: { - returnInsertedItem?: ReturnInserted - keyForInsertLookup?: keyof SQLInfer extends string ? keyof SQLInfer : never - } - ) { - return createItem({ - db, - tableName, - debug, - item, - returnInsertedItem: createOptions?.returnInsertedItem, - keyForInsertLookup: createOptions?.keyForInsertLookup, - }) - }, - getAll(): TranslatedSchema[] { - return readItems({ db, tableName, debug }) as TranslatedSchema[] - }, - getById(id: string | number) { - return readItemById({ db, tableName, debug, id }) - }, - getByKey(key: string, value: string | number) { - return readFirstItemByKey({ - db, - tableName, - debug, - key, - value, - }) as unknown as TranslatedSchema - }, - update(id: string | number, item: Partial>) { - return updateItem({ db, tableName, debug, id, item }) - }, - delById(id: number | string) { - return deleteItemById({ db, tableName, debug, id }) - }, - infer(): TranslatedSchema { - return undefined as any as TranslatedSchema - }, - } -} diff --git a/sqlite/sqlite-utils/crud-fn-utils.test.ts b/sqlite/sqlite-utils/crud-fn-utils.test.ts deleted file mode 100644 index db48345..0000000 --- a/sqlite/sqlite-utils/crud-fn-utils.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -import Database from 'bun:sqlite' -import { beforeEach, describe, expect, it, test } from 'bun:test' -import type { SchemaMap } from '../sqlite-factory' -import { createItem, createWhereClause, deleteItemById, readItems, updateItem } from './crud-fn-utils' - -const testSchema = { - id: { type: 'TEXT' }, - name: { type: 'TEXT' }, - age: { type: 'INTEGER' }, -} satisfies SchemaMap - -let db = new Database(':memory:') - -describe('Database utility functions', () => { - beforeEach(() => { - db = new Database(':memory:') - db.query('CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)').run() - }) - - test('should create an item in the database', () => { - createItem({ - db, - tableName: 'test', - debug: false, - item: { id: '1', name: 'John', age: 25 }, - returnInsertedItem: false, - }) - const items = readItems({ db, tableName: 'test' }) - expect(items).toEqual([{ id: 1, name: 'John', age: 25 }]) - }) - - test('should read items from the database', () => { - db.query('INSERT INTO test (name, age) VALUES (?, ?)').run('Jane', 30) - const items = readItems({ db, tableName: 'test' }) - expect(items).toEqual([{ id: 1, name: 'Jane', age: 30 }]) - }) - - test('should update an item in the database', () => { - db.query('INSERT INTO test (name, age) VALUES (?, ?)').run('Doe', 35) - updateItem({ - db, - tableName: 'test', - debug: false, - id: 1, - item: { name: 'John Doe' }, - }) - const items = readItems({ db, tableName: 'test' }) - expect(items).toEqual([{ id: 1, name: 'John Doe', age: 35 }]) - }) - test('should delete an item from the database by ID', () => { - db.query('INSERT INTO test (name, age) VALUES (?, ?)').run('Alice', 40) - deleteItemById({ db, id: 1, tableName: 'test' }) - const items = readItems({ db, tableName: 'test' }) - expect(items).toEqual([]) - }) -}) - -describe('createWhereClause', () => { - it('should create a WHERE clause and parameters for a SQL query', () => { - const where = { id: 1, name: 'John' } - const result = createWhereClause(where) - - expect(result).toEqual({ - whereClause: 'id = ? AND name = ?', - parameters: [1, 'John'], - }) - }) - - it('should handle an empty object', () => { - const where = {} - const result = createWhereClause(where) - - expect(result).toEqual({ - whereClause: '', - parameters: [], - }) - }) - - it('should handle null and undefined values', () => { - const where = { id: null, name: undefined } - const result = createWhereClause(where) - - expect(result).toEqual({ - whereClause: 'id = ? AND name = ?', - parameters: [null, undefined], - }) - }) - - it('should handle more than two properties', () => { - const where = { id: 1, name: 'John', age: 30 } - const result = createWhereClause(where) - - expect(result).toEqual({ - whereClause: 'id = ? AND name = ? AND age = ?', - parameters: [1, 'John', 30], - }) - }) - - it('should handle boolean values', () => { - const where = { isActive: true } - const result = createWhereClause(where) - - expect(result).toEqual({ - whereClause: 'isActive = ?', - parameters: [true], - }) - }) - - it('should handle numeric string values', () => { - const where = { id: '1' } - const result = createWhereClause(where) - - expect(result).toEqual({ - whereClause: 'id = ?', - parameters: ['1'], - }) - }) - - it('should handle more than two properties', () => { - const where = { id: 1, name: 'John', age: 30 } - const result = createWhereClause(where) - - expect(result).toEqual({ - whereClause: 'id = ? AND name = ? AND age = ?', - parameters: [1, 'John', 30], - }) - }) - - it('should handle boolean values', () => { - const where = { isActive: true } - const result = createWhereClause(where) - - expect(result).toEqual({ - whereClause: 'isActive = ?', - parameters: [true], - }) - }) - - it('should handle numeric string values', () => { - const where = { id: '1' } - const result = createWhereClause(where) - - expect(result).toEqual({ - whereClause: 'id = ?', - parameters: ['1'], - }) - }) -}) diff --git a/sqlite/sqlite-utils/crud-fn-utils.ts b/sqlite/sqlite-utils/crud-fn-utils.ts deleted file mode 100644 index 8e4bbad..0000000 --- a/sqlite/sqlite-utils/crud-fn-utils.ts +++ /dev/null @@ -1,200 +0,0 @@ -import Database from 'bun:sqlite' -import type { SQLInfer, SchemaMap } from '../sqlite-factory' -import { deleteQueryString, insertQueryString, selectAllTableQueryString, updateQueryString } from './crud-string-utils' - -type BaseDBParams = { - db: Database - tableName: string - debug?: boolean -} - -type ParamsWithId = BaseDBParams & { id: string | number } - -type DBItem = Partial> - -export function createItem({ - db, - debug, - item, - returnInsertedItem, - tableName, - keyForInsertLookup, -}: Omit & { - item: Partial> - returnInsertedItem?: ReturnItem - keyForInsertLookup?: keyof SQLInfer extends string ? keyof SQLInfer : never -}): ReturnItem extends true ? SQLInfer : null { - const query = insertQueryString(tableName, item) - const valuesArray: any[] = [] - - for (const [key, value] of Object.entries(item)) { - const valueToInsert = value instanceof Date ? value.toISOString() : value - - if (value !== undefined) { - valuesArray.push(valueToInsert) - } - } - - if (debug) console.table({ query, valuesArray }) - - try { - // Perform the insert operation - db.query(query).run(...valuesArray) - } catch (e) { - if (debug) { - throw { - info: 'CreateItem: Error during database insert operation', - message: e.message, - query, - valuesArray, - } - } - - throw e - } - - const lookupKey = keyForInsertLookup ? keyForInsertLookup : 'id' - const lookupValue = item[lookupKey] - - if (lookupValue && lookupKey && returnInsertedItem) { - const selectQuery = `SELECT * FROM ${tableName} WHERE ${lookupKey} = ?;` - try { - const insertedItem = db.prepare(selectQuery).get(lookupValue) as SQLInfer - - if (debug) console.table({ selectQuery, lookupValue, insertedItem }) - - return insertedItem as ReturnItem extends true ? SQLInfer : null - } catch (e) { - if (debug) { - throw { - info: 'CreateItem: Error during database select operation', - message: e.message, - selectQuery, - lookupValue, - } - } - throw e - } - } - - if ((!lookupValue || !lookupKey) && returnInsertedItem) { - const errorMsg = `returnInsertedItem is true but no lookupKey or lookupValue was provided \n - lookupKey: ${lookupKey} \n - lookupValue: ${lookupValue} \n` - console.error(errorMsg) - throw new Error(errorMsg) - } - - return null as ReturnItem extends true ? SQLInfer : null -} - -export function readFirstItemByKey({ - db, - debug, - key, - tableName, - value, -}: BaseDBParams & { - key: keyof SQLInfer - value: string | number -}): SQLInfer { - const queryString = selectItemByKeyQueryString(tableName, String(key)) - if (debug) console.info(`readFirstItemByKey: ${queryString}`) - const query = db.prepare(queryString).get(value) as SQLInfer - return query -} - -// Modify the readItems function to include an optional id parameter. -export function readItemById({ db, debug, id, tableName }: ParamsWithId): SQLInfer { - const query = selectItemByKeyQueryString(tableName, 'id') - if (debug) console.info(`readItemById: ${query}`) - - const data = db.prepare(query).get(id) as SQLInfer - - return data -} - -// This type represents the shape of the 'where' parameter -type Where = Partial - -// This interface will be used to type the return value of createWhereClause -interface WhereClauseResult { - whereClause: string - parameters: { [key: string]: any } -} - -// Function to create a WHERE clause and parameters for a SQL query -export function createWhereClause>(where: Where, debug = false): WhereClauseResult { - const keys = Object.keys(where) as Array - const whereClause = keys.map((key) => `${String(key)} = ?`).join(' AND ') - const parameters = keys.map((key) => where[key]) - - if (debug) { - // create object of keys/values to be used as parameters in the query - // e.g. { $name: 'John', $age: 25 } - const debugEntries = Object.fromEntries(keys.map((key) => [`$${key as string}`, where[key]])) - - console.table(debugEntries) - } - - return { whereClause, parameters } -} - -export function readItemsWhere({ - db, - tableName, - debug, - where, -}: BaseDBParams & { - where: Where> -}): SQLInfer[] { - const { whereClause, parameters } = createWhereClause>(where, debug) - - // The query string now uses '?' placeholders for parameters - const queryString = `SELECT * FROM ${tableName} WHERE ${whereClause};` - if (debug) console.info(`readItemsWhere ${queryString}`) - - // Prepare the statement with the queryString - const statement = db.prepare(queryString) - - // Assuming the .all() method on the prepared statement executes the query - // and retrieves all the results after binding the parameters - const data = statement.all(parameters) as SQLInfer[] - - return data // Return the query results -} - -// In your crud-string-utils file, add a function to create a SQL query string to select by ID. -export function selectItemByKeyQueryString(tableName: string, key: string): string { - return `SELECT * FROM ${tableName} WHERE ${key} = ?` -} - -export function readItems({ db, debug, tableName }: BaseDBParams): SQLInfer[] { - const query = selectAllTableQueryString(tableName) - if (debug) console.info(query) - const data = db.query(query).all() as SQLInfer[] - return data -} - -export function updateItem({ - db, - debug, - id, - item, - tableName, -}: ParamsWithId & { - item: Partial, 'id'>> -}) { - const query = updateQueryString(tableName, item) - - if (debug) console.info(query) - - const params = Object.fromEntries(Object.entries(item).map(([key, value]) => [`$${key}`, value])) - db.query(query).run({ ...params, $id: id }) -} - -export function deleteItemById({ db, debug, id, tableName }: ParamsWithId) { - const query = deleteQueryString(tableName) - if (debug) console.info('deleteQueryString: ', query) - db.query(query).run({ $id: id }) -} diff --git a/sqlite/sqlite-utils/crud-string-utils.test.ts b/sqlite/sqlite-utils/crud-string-utils.test.ts deleted file mode 100644 index caac01f..0000000 --- a/sqlite/sqlite-utils/crud-string-utils.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { expect, test } from 'bun:test' -import { deleteQueryString, insertQueryString, selectAllTableQueryString, updateQueryString } from './crud-string-utils' - -// Test for insertQueryString -test('insertQueryString', () => { - const tableName = 'users' - const item = { name: 'Alice', age: 30 } - const query = insertQueryString(tableName, item) - const expectedQuery = 'INSERT INTO users (name, age) VALUES (?, ?)' - expect(query).toBe(expectedQuery) -}) - -// Test for selectAllTableQueryString -test('selectAllTableQueryString', () => { - const tableName = 'users' - const query = selectAllTableQueryString(tableName) - const expectedQuery = 'SELECT * FROM users;' - expect(query).toBe(expectedQuery) -}) - -// Test for deleteQueryString -test('deleteQueryString', () => { - const tableName = 'users' - const query = deleteQueryString(tableName) - const expectedQuery = 'delete FROM users WHERE id = $id;' - expect(query).toBe(expectedQuery) -}) - -// Test for updateQueryString -test('updateQueryString', () => { - const tableName = 'users' - const item = { name: 'John', age: 25 } // example item - const query = updateQueryString(tableName, item) - const expectedQuery = 'UPDATE users SET name = $name, age = $age WHERE id = $id;' - expect(query).toBe(expectedQuery) -}) diff --git a/sqlite/sqlite-utils/crud-string-utils.ts b/sqlite/sqlite-utils/crud-string-utils.ts deleted file mode 100644 index 24d5431..0000000 --- a/sqlite/sqlite-utils/crud-string-utils.ts +++ /dev/null @@ -1,52 +0,0 @@ -export function insertQueryString>(tableName: string, item: Item): string { - // Define a whitelist for table names if they are dynamic or ensure tableName is sanitized. - const safeTableName = escapeIdentifier(tableName) - - const definedKeys = Object.keys(item).filter((key) => item[key] !== undefined) - - // Map the defined keys to column names and placeholders - const columns = definedKeys.map((column) => escapeIdentifier(column)).join(', ') - const placeholders = definedKeys.map(() => '?').join(', ') - - // Handle the case where the item might be empty. - if (columns.length === 0 || placeholders.length === 0) { - throw new Error('No data provided for insert.') - } - - return `INSERT INTO ${safeTableName} (${columns}) VALUES (${placeholders})` -} - -function escapeIdentifier(identifier: string): string { - // This is a simplistic approach and might not cover all edge cases. - if (!identifier.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) { - throw new Error('Invalid identifier') - } - return identifier // Assuming the identifier is safe, otherwise escape it properly. -} -export function selectAllTableQueryString(tableName: string): string { - // Validate or escape the tableName to prevent SQL injection - const safeTableName = escapeIdentifier(tableName) - return `SELECT * FROM ${safeTableName};` -} - -export function deleteQueryString(tableName: string): string { - // Validate or escape the tableName to prevent SQL injection - const safeTableName = escapeIdentifier(tableName) - return `delete FROM ${safeTableName} WHERE id = $id;` -} - -export function updateQueryString(tableName: string, item: Record): string { - // Validate or escape the tableName to prevent SQL injection - const safeTableName = escapeIdentifier(tableName) - - const updateFields = Object.keys(item) - .map((key) => `${escapeIdentifier(key)} = $${key}`) - .join(', ') - - // Check if the updateFields string is empty and throw an error if it is - if (updateFields.length === 0) { - throw new Error('No fields to update were provided.') - } - - return `UPDATE ${safeTableName} SET ${updateFields} WHERE id = $id;` -} diff --git a/sqlite/sqlite-utils/format-schema.test.ts b/sqlite/sqlite-utils/format-schema.test.ts deleted file mode 100644 index e908937..0000000 --- a/sqlite/sqlite-utils/format-schema.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { expect, test } from 'bun:test' -import type { SchemaMap } from '../sqlite-factory' -import { formatSchema } from './format-schema' - -test('formatSchema formats schema correctly', () => { - const schema = { - id: { type: 'INTEGER' }, - name: { type: 'TEXT' }, - } satisfies SchemaMap - - // TODO need to fix schema type - const result = formatSchema(schema) - - expect(result[0]).toBe('id INTEGER') - expect(result[1]).toBe('name TEXT') -}) diff --git a/sqlite/sqlite-utils/format-schema.ts b/sqlite/sqlite-utils/format-schema.ts deleted file mode 100644 index d089004..0000000 --- a/sqlite/sqlite-utils/format-schema.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { SchemaMap } from '../sqlite-factory' - -export function formatSchema(schema: Schema): string[] { - return Object.entries(schema).map(([key, fieldDefinition]) => `${key} ${fieldDefinition?.type.toUpperCase()}`) -} diff --git a/sqlite/sqlite-utils/table-query-gen.test.ts b/sqlite/sqlite-utils/table-query-gen.test.ts deleted file mode 100644 index 2895b2b..0000000 --- a/sqlite/sqlite-utils/table-query-gen.test.ts +++ /dev/null @@ -1,289 +0,0 @@ -import { describe, expect, it, test } from 'bun:test' -import type { FieldDef, SchemaMap } from '../sqlite-factory' -import { - assembleCreateTableQuery, - createColumnDefinition, - createTableLevelConstraint, - createTableQuery, -} from './table-query-gen' - -test('createTableQuery constructs SQL query correctly with foreign keys', () => { - const schema = { - id: { type: 'INTEGER' }, - name: { type: 'TEXT' }, - fkTest: { type: 'TEXT', foreignKey: 'other_table(id)' }, - } satisfies SchemaMap - - const result = createTableQuery({ - schema, - tableName: 'test_table', - }) - - expect(result).toBe( - 'CREATE TABLE IF NOT EXISTS `test_table` (`id` INTEGER, `name` TEXT, `fkTest` TEXT, FOREIGN KEY (`fkTest`) REFERENCES other_table(id));' - ) -}) - -test('createTableQuery constructs SQL query correctly without foreign keys', () => { - const schema = { - id: { type: 'INTEGER' }, - name: { type: 'TEXT' }, - } satisfies SchemaMap - - const result = createTableQuery({ - schema, - tableName: 'test_table', - }) - - expect(result).toBe('CREATE TABLE IF NOT EXISTS `test_table` (`id` INTEGER, `name` TEXT);') -}) - -describe('createColumnDefinition', () => { - it('should generate a column definition for a simple TEXT field', () => { - const definition: FieldDef = { type: 'TEXT' } - const result = createColumnDefinition('name', definition) - expect(result).toBe('`name` TEXT') - }) - - it('should generate a column definition for an INTEGER field with a PRIMARY KEY', () => { - const definition: FieldDef = { type: 'INTEGER', primaryKey: true } - const result = createColumnDefinition('id', definition) - expect(result).toBe('`id` INTEGER PRIMARY KEY') - }) - - // Add tests for unique, notNull, and defaultValue... - - it('should throw an error if the definition is not provided', () => { - expect(() => { - createColumnDefinition('age', undefined as unknown as FieldDef) - }).toThrow() - }) - - it('should generate a column definition with a UNIQUE constraint', () => { - const definition: FieldDef = { type: 'TEXT', unique: true } - const result = createColumnDefinition('username', definition) - expect(result).toBe('`username` TEXT UNIQUE') - }) - - it('should generate a column definition with a NOT NULL constraint', () => { - const definition: FieldDef = { type: 'INTEGER', required: true } - const result = createColumnDefinition('age', definition) - expect(result).toBe('`age` INTEGER NOT NULL') - }) - - it('should generate a column definition with a DEFAULT value', () => { - const definition: FieldDef = { type: 'TEXT', defaultValue: 'N/A' } - const result = createColumnDefinition('status', definition) - expect(result).toBe('`status` TEXT DEFAULT N/A') - }) - - it('should correctly quote a DEFAULT string value', () => { - const definition: FieldDef = { - type: 'TEXT', - defaultValue: "'active'", - } - const result = createColumnDefinition('state', definition) - expect(result).toBe("`state` TEXT DEFAULT 'active'") - }) - - it('should generate a column definition with multiple constraints', () => { - const definition: FieldDef = { - type: 'INTEGER', - required: true, - unique: true, - defaultValue: 0, - } - const result = createColumnDefinition('count', definition) - expect(result).toContain('`count` INTEGER') - expect(result).toContain('NOT NULL') - expect(result).toContain('DEFAULT 0') - }) - - it('should not include DEFAULT when defaultValue is not provided', () => { - const definition: FieldDef = { type: 'REAL' } - const result = createColumnDefinition('price', definition) - expect(result).toBe('`price` REAL') - }) - - it('should handle numeric DEFAULT values correctly', () => { - const definition: FieldDef = { type: 'INTEGER', defaultValue: 10 } - const result = createColumnDefinition('quantity', definition) - expect(result).toBe('`quantity` INTEGER DEFAULT 10') - }) - - // Test for an edge case where type is unknown - // it("should throw an error if an invalid type is provided", () => { - // const definition = { type: "INVALID_TYPE" } as unknown as FieldDefinition; - // expect(() => { - // createColumnDefinition("invalid", definition); - // }).toThrow(); - // }); - - // Test for proper escaping of field names that are SQL keywords - it('should escape field names that are SQL keywords', () => { - const definition: FieldDef = { type: 'TEXT' } - const result = createColumnDefinition('group', definition) - expect(result).toBe('`group` TEXT') - }) -}) - -describe('createTableLevelConstraint', () => { - it('should generate a foreign key constraint', () => { - const definition: FieldDef = { - type: 'INTEGER', - foreignKey: 'other_table(id)', - } - const result = createTableLevelConstraint('fkTest', definition) - expect(result).toBe('FOREIGN KEY (`fkTest`) REFERENCES other_table(id)') - }) - - it('should return null if no foreign key is defined', () => { - const definition: FieldDef = { type: 'INTEGER' } - const result = createTableLevelConstraint('fkTest', definition) - expect(result).toBeNull() - }) - - it('should generate a foreign key constraint with a custom reference field', () => { - const definition: FieldDef = { - type: 'INTEGER', - foreignKey: 'other_table(custom_id)', - } - const result = createTableLevelConstraint('fkCustomId', definition) - expect(result).toBe('FOREIGN KEY (`fkCustomId`) REFERENCES other_table(custom_id)') - }) - - it('should properly trim the foreign key definition', () => { - const definition: FieldDef = { - type: 'INTEGER', - foreignKey: ' other_table (custom_id) ', - } - const result = createTableLevelConstraint('fkCustomId', definition) - expect(result).toBe('FOREIGN KEY (`fkCustomId`) REFERENCES other_table(custom_id)') - }) - - it('should handle foreign keys that include spaces or special characters', () => { - const definition: FieldDef = { - type: 'TEXT', - foreignKey: '`other table`(`special id`)', - } - const result = createTableLevelConstraint('fkSpecial', definition) - expect(result).toBe('FOREIGN KEY (`fkSpecial`) REFERENCES `other table`(`special id`)') - }) - - it('should return null for a malformed foreign key definition', () => { - const definition: FieldDef = { - type: 'INTEGER', - foreignKey: 'malformed', - } - expect(() => createTableLevelConstraint('fkMalformed', definition)).toThrow() - }) - - // Test for a case where the foreign key reference does not include a field - it('should return null if foreign key reference is incomplete', () => { - const definition: FieldDef = { - type: 'INTEGER', - foreignKey: 'other_table', - } - expect(() => createTableLevelConstraint('fkIncomplete', definition)).toThrow() - }) - - // Test for proper escaping of table and column names in foreign key definitions - it('should escape table and column names in foreign key definitions', () => { - const definition: FieldDef = { - type: 'INTEGER', - foreignKey: '`other-table`(`id`)', - } - const result = createTableLevelConstraint('fkEscaped', definition) - expect(result).toBe('FOREIGN KEY (`fkEscaped`) REFERENCES `other-table`(`id`)') - }) -}) - -describe('assembleCreateTableQuery', () => { - it('should assemble a create table query with a single column', () => { - const columns = ['`id` INTEGER PRIMARY KEY'] - const result = assembleCreateTableQuery('test_table', columns, []) - expect(result).toBe('CREATE TABLE IF NOT EXISTS `test_table` (`id` INTEGER PRIMARY KEY);') - }) - - it('should assemble a create table query with foreign key constraints', () => { - const columns = ['`id` INTEGER', '`name` TEXT'] - const constraints = ['FOREIGN KEY (`id`) REFERENCES other_table(id)'] - const result = assembleCreateTableQuery('test_table', columns, constraints) - expect(result).toBe( - 'CREATE TABLE IF NOT EXISTS `test_table` (`id` INTEGER, `name` TEXT, FOREIGN KEY (`id`) REFERENCES other_table(id));' - ) - }) - - it('should handle tables with multiple foreign key constraints', () => { - const columns = ['`id` INTEGER', '`parent_id` INTEGER', '`owner_id` INTEGER'] - const constraints = [ - 'FOREIGN KEY (`parent_id`) REFERENCES parents(id)', - 'FOREIGN KEY (`owner_id`) REFERENCES owners(id)', - ] - const result = assembleCreateTableQuery('test_table', columns, constraints) - expect(result).toBe( - 'CREATE TABLE IF NOT EXISTS `test_table` (`id` INTEGER, `parent_id` INTEGER, `owner_id` INTEGER, FOREIGN KEY (`parent_id`) REFERENCES parents(id), FOREIGN KEY (`owner_id`) REFERENCES owners(id));' - ) - }) - - it('should include unique constraints at the table level', () => { - const columns = ['`id` INTEGER', '`email` TEXT'] - const constraints = ['UNIQUE (`email`)'] - const result = assembleCreateTableQuery('users', columns, constraints) - expect(result).toBe('CREATE TABLE IF NOT EXISTS `users` (`id` INTEGER, `email` TEXT, UNIQUE (`email`));') - }) - - it('should return a query without any constraints if none are provided', () => { - const columns = ['`id` INTEGER', '`name` TEXT'] - const result = assembleCreateTableQuery('simple_table', columns, []) - expect(result).toBe('CREATE TABLE IF NOT EXISTS `simple_table` (`id` INTEGER, `name` TEXT);') - }) - - it('should escape table names that are SQL keywords', () => { - const columns = ['`id` INTEGER'] - const result = assembleCreateTableQuery('group', columns, []) - expect(result).toBe('CREATE TABLE IF NOT EXISTS `group` (`id` INTEGER);') - }) - - it('should throw an error if columns are empty', () => { - expect(() => { - assembleCreateTableQuery('empty_table', [], []) - }).toThrow() - }) - - it('should properly format a table with default values', () => { - const columns = ['`id` INTEGER', "`name` TEXT DEFAULT 'Unknown'"] - const result = assembleCreateTableQuery('default_values_table', columns, []) - expect(result).toBe( - "CREATE TABLE IF NOT EXISTS `default_values_table` (`id` INTEGER, `name` TEXT DEFAULT 'Unknown');" - ) - }) - - it('should assemble a create table query with check constraints', () => { - const columns = ['`age` INTEGER'] - const constraints = ['CHECK (`age` >= 18)'] - const result = assembleCreateTableQuery('check_constraints_table', columns, constraints) - expect(result).toBe('CREATE TABLE IF NOT EXISTS `check_constraints_table` (`age` INTEGER, CHECK (`age` >= 18));') - }) - - it('should handle a table with composite primary keys', () => { - const columns = ['`id` INTEGER', '`revision` INTEGER'] - const constraints = ['PRIMARY KEY (`id`, `revision`)'] - const result = assembleCreateTableQuery('composite_keys_table', columns, constraints) - expect(result).toBe( - 'CREATE TABLE IF NOT EXISTS `composite_keys_table` (`id` INTEGER, `revision` INTEGER, PRIMARY KEY (`id`, `revision`));' - ) - }) - - // Test to ensure that backticks are used consistently - it('should use backticks for all identifiers', () => { - const columns = ['`id` INTEGER', '`name` TEXT'] - const constraints = ['FOREIGN KEY (`id`) REFERENCES `other_table`(`id`)'] - const result = assembleCreateTableQuery('backtick_test', columns, constraints) - expect(result).toBe( - 'CREATE TABLE IF NOT EXISTS `backtick_test` (`id` INTEGER, `name` TEXT, FOREIGN KEY (`id`) REFERENCES `other_table`(`id`));' - ) - }) - - // Add more tests for complex tables, edge cases, etc... -}) diff --git a/sqlite/sqlite-utils/table-query-gen.ts b/sqlite/sqlite-utils/table-query-gen.ts deleted file mode 100644 index dbaa147..0000000 --- a/sqlite/sqlite-utils/table-query-gen.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { FieldDef, SchemaMap } from '../sqlite-factory' - -export function assembleCreateTableQuery( - tableName: string, - columns: string[], - tableLevelConstraints: string[] -): string { - if (columns.length === 0) throw new Error(`No columns for table ${tableName}`) - const tableDefinition = [...columns, ...tableLevelConstraints].filter(Boolean).join(', ') - return `CREATE TABLE IF NOT EXISTS \`${tableName}\` (${tableDefinition});` -} - -export function createTableLevelConstraint(fieldName: string, definition: FieldDef): string | null { - if (definition.foreignKey) { - const [referencedTable, referencedField]: string[] = definition.foreignKey.split('(') - - if (!referencedField) throw new Error(`No referenced field for foreign key ${definition.foreignKey}`) - if (!referencedTable) throw new Error(`No referenced table for foreign key ${definition.foreignKey}`) - return `FOREIGN KEY (\`${fieldName}\`) REFERENCES ${referencedTable.trim()}(${referencedField}`.trim() - } - return null -} - -export function createColumnDefinition(fieldName: string, definition: FieldDef): string { - if (!definition) throw new Error(`No definition for field ${fieldName}`) - const type = definition.type - const constraints = [] - - if (definition.primaryKey) constraints.push('PRIMARY KEY') - if (definition.unique) constraints.push('UNIQUE') - if (definition.required) constraints.push('NOT NULL') - if (definition.defaultValue !== undefined) { - constraints.push(`DEFAULT ${definition.defaultValue}`) - } - - return `\`${fieldName}\` ${type} ${constraints.join(' ')}`.trim() -} - -export function createTableQuery({ - schema, - tableName, - debug = false, -}: { - tableName: string - schema: Schema - debug?: boolean -}): string { - if (debug) { - console.info({ schema, tableName }) - } - - if (!schema) throw new Error(`No schema provided for table ${tableName}`) - - const columns = Object.keys(schema).map((fieldName) => { - return createColumnDefinition(fieldName, schema[fieldName] as FieldDef) - }) - const tableLevelConstraints = Object.keys(schema) - .map((fieldName) => createTableLevelConstraint(fieldName, schema[fieldName] as FieldDef) || '') - .filter(Boolean) - - const query = assembleCreateTableQuery(tableName, columns, tableLevelConstraints) - - if (debug) { - console.info({ query, schema, tableName }) - } - - return query -} diff --git a/scripts/starter/README.md b/starter/README.md similarity index 100% rename from scripts/starter/README.md rename to starter/README.md diff --git a/scripts/starter/index.ts b/starter/index.ts similarity index 100% rename from scripts/starter/index.ts rename to starter/index.ts diff --git a/scripts/starter/middlewares.ts b/starter/middlewares.ts similarity index 100% rename from scripts/starter/middlewares.ts rename to starter/middlewares.ts diff --git a/scripts/starter/package.json b/starter/package.json similarity index 100% rename from scripts/starter/package.json rename to starter/package.json diff --git a/scripts/starter/tsconfig.json b/starter/tsconfig.json similarity index 100% rename from scripts/starter/tsconfig.json rename to starter/tsconfig.json diff --git a/tsconfig.json b/tsconfig.json index f0fe2a8..3ed8951 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,28 +15,28 @@ "types": ["bun-types"], "baseUrl": ".", "paths": { - "cli": ["cli"], - "cookies": ["cookies"], - "data-gen": ["data-gen"], - "deploy": ["deploy"], - "fetcher": ["fetcher"], + "cli": ["packages/cli"], + "cookies": ["packages/cookies"], + "data-gen": ["packages/data-gen"], + "deploy": ["packages/deploy"], + "fetcher": ["packages/fetcher"], "files-folder": ["modules/files-folder"], - "auth": ["auth"], - "htmlody": ["htmlody"], - "jwt": ["jwt"], - "logger": ["logger"], - "npm-release": ["npm-release"], + "auth": ["packages/auth"], + "htmlody": ["packages/htmlody"], + "jwt": ["packages/jwt"], + "logger": ["packages/logger"], + "npm-release": ["packages/npm-release"], "server": ["server"], - "sqlite": ["sqlite"], - "state": ["state"], + "sqlite": ["packages/sqlite"], + "state": ["packages/state"], "test": ["test-utils"], - "utils": ["utils"], - "uuid": ["uuid"], - "validation": ["validation"], + "utils": ["packages/utils"], + "uuid": ["packages/uuid"], + "validation": ["packages/validation"], "types": ["types"], "type-utils": ["type-utils"] } }, "include": ["**/*.ts"], - "exclude": ["node_modules", "plugins/**/node_modules"] + "exclude": ["node_modules", "packages/plugins/**/node_modules"] } diff --git a/validation/index.ts b/validation/index.ts deleted file mode 100644 index 1df2f97..0000000 --- a/validation/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { createValidatorFactory } from './validation-factory' diff --git a/validation/validation-factory.ts b/validation/validation-factory.ts deleted file mode 100644 index 5d3469d..0000000 --- a/validation/validation-factory.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { ValidationResult } from '../types' - -export type ErrorT = 'ValidationError' | 'APIError' | 'JavaScriptError' - -export type CustomError = { - type: ErrorT - message: string -} - -export const apiErrorMap = { - APIError: 'API Error', - ValidationError: 'Validation Error', - JavaScriptError: 'JavaScript Error', -} - -function mapBuiltInErrorType(error: Error): ErrorT { - if (error instanceof TypeError) { - return 'JavaScriptError' - } else if (error instanceof ReferenceError) { - return 'JavaScriptError' - } else if (error instanceof SyntaxError) { - return 'JavaScriptError' - } else { - return 'JavaScriptError' - } -} - -export const getErrorType = (error: Error | CustomError): ErrorT => { - return 'type' in error ? error.type : mapBuiltInErrorType(error) -} - -export function handleError(error: Error | CustomError, throwError = false): CustomError | undefined { - const handledError: CustomError = - 'type' in error - ? error - : { - type: getErrorType(error), - message: error.message, - } - - if (throwError) { - throw handledError - } else { - return handledError - } -} - -export function createValidatorFactory>(schema: Schema) { - type SchemaKeys = keyof Schema - function validateItem(item: any): Schema { - if (typeof item !== 'object' || item === null) { - throw handleError({ type: 'ValidationError', message: 'Invalid data type' }, true) - } - - const validateSchema: Schema = {} as Schema - - const isValid = Object.keys(schema as Schema).every((key) => { - const typedKey: SchemaKeys = key as SchemaKeys - const expectedType = schema[key] - const actualType = typeof item[key] - - if (actualType !== expectedType) { - return false - } - - validateSchema[typedKey] = item[key] as Schema[keyof Schema] - return true - }) - - if (!isValid) { - throw handleError({ type: 'ValidationError', message: 'Invalid data type' }, true) - } - - return validateSchema - } - - function validateAgainstArraySchema(schema: Schema, data: unknown[]): ValidationResult { - try { - const validatedData = data.map((item) => validateItem(item)) - return { data: validatedData as Schema[] } - } catch (error) { - const handledError = handleError(error as Error) - if (handledError?.type === 'ValidationError') { - return { error: handledError.message } - } else { - throw error - } - } - } - - return { - validateAgainstArraySchema, - validateItem, - } -} - -// One export per file -export default createValidatorFactory