From 7791baff20ececf220c62fdeb864e6211951b2d4 Mon Sep 17 00:00:00 2001
From: garrettmills <shout@garrettmills.dev>
Date: Tue, 24 Dec 2024 09:43:23 -0500
Subject: [PATCH] Big bang

---
 .gitattributes     |   1 +
 .gitignore         | 175 +++++++++++++++++++++++++++++++++++++++++++++
 README.md          |  15 ++++
 bun.lockb          | Bin 0 -> 13883 bytes
 index.ts           |   4 ++
 package.json       |  16 +++++
 src/bones/index.ts |   1 +
 src/bones/types.ts |  89 +++++++++++++++++++++++
 src/config.ts      |  20 ++++++
 src/types.ts       |  23 ++++++
 tsconfig.json      |  27 +++++++
 11 files changed, 371 insertions(+)
 create mode 100644 .gitattributes
 create mode 100644 .gitignore
 create mode 100644 README.md
 create mode 100755 bun.lockb
 create mode 100644 index.ts
 create mode 100644 package.json
 create mode 100644 src/bones/index.ts
 create mode 100644 src/bones/types.ts
 create mode 100644 src/config.ts
 create mode 100644 src/types.ts
 create mode 100644 tsconfig.json

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..81c05ed
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+*.lockb binary diff=lockb
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9b1ee42
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,175 @@
+# 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*
+
+# Caches
+
+.cache
+
+# 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/)
+
+.parcel-cache
+
+# Next.js build output
+
+.next
+out
+
+# Nuxt.js build / generate output
+
+.nuxt
+dist
+
+# Gatsby files
+
+# 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
+
+# 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
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5d3181b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+# email-comments
+
+To install dependencies:
+
+```bash
+bun install
+```
+
+To run:
+
+```bash
+bun run index.ts
+```
+
+This project was created using `bun init` in bun v1.1.42. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
diff --git a/bun.lockb b/bun.lockb
new file mode 100755
index 0000000000000000000000000000000000000000..93f63c1cba7ec9eadf5c0450dbb29bef43a885ef
GIT binary patch
literal 13883
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

literal 0
HcmV?d00001

diff --git a/index.ts b/index.ts
new file mode 100644
index 0000000..9716130
--- /dev/null
+++ b/index.ts
@@ -0,0 +1,4 @@
+
+import { config } from "./src/config.ts";
+
+console.log(config)
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..218b3df
--- /dev/null
+++ b/package.json
@@ -0,0 +1,16 @@
+{
+  "name": "email-comments",
+  "module": "index.ts",
+  "type": "module",
+  "devDependencies": {
+    "@types/bun": "^1.1.14"
+  },
+  "peerDependencies": {
+    "typescript": "^5.0.0"
+  },
+  "dependencies": {
+    "@types/imapflow": "^1.0.19",
+    "imapflow": "^1.0.171",
+    "zod": "^3.24.1"
+  }
+}
\ No newline at end of file
diff --git a/src/bones/index.ts b/src/bones/index.ts
new file mode 100644
index 0000000..c9f6f04
--- /dev/null
+++ b/src/bones/index.ts
@@ -0,0 +1 @@
+export * from './types'
diff --git a/src/bones/types.ts b/src/bones/types.ts
new file mode 100644
index 0000000..f8399c4
--- /dev/null
+++ b/src/bones/types.ts
@@ -0,0 +1,89 @@
+/** Type alias for something that may or may not be wrapped in a promise. */
+export type Awaitable<T> = T | Promise<T>
+
+/** Type alias for something that may be undefined. */
+export type Maybe<T> = T | undefined
+
+export type MaybeArr<T extends [...any[]]> = {
+    [Index in keyof T]: Maybe<T[Index]>
+} & {length: T['length']}
+
+export type Either<T1, T2> = Left<T1> | Right<T2>
+
+export type Left<T> = [T, undefined]
+
+export type Right<T> = [undefined, T]
+
+export function isLeft<T1, T2>(what: Either<T1, T2>): what is Left<T1> {
+    return typeof what[1] === 'undefined'
+}
+
+export function isRight<T1, T2>(what: Either<T1, T2>): what is Right<T2> {
+    return typeof what[0] === 'undefined'
+}
+
+export function left<T>(what: T): Left<T> {
+    return [what, undefined]
+}
+
+export function right<T>(what: T): Right<T> {
+    return [undefined, what]
+}
+
+export function unleft<T>(what: Left<T>): T {
+    return what[0]
+}
+
+export function unright<T>(what: Right<T>): T {
+    return what[1]
+}
+
+/** Type alias for a callback that accepts a typed argument. */
+export type ParameterizedCallback<T> = ((arg: T) => any)
+
+/** A key-value form of a given type. */
+export type KeyValue<T> = {key: string, value: T}
+
+/** Simple helper method to verify that a key is a keyof some object. */
+export function isKeyof<T extends object>(key: unknown, obj: T): key is keyof T {
+    if ( typeof key !== 'string' && typeof key !== 'symbol' ) {
+        return false
+    }
+
+    return key in obj
+}
+
+/** A typescript-compatible version of Object.hasOwnProperty. */
+export function hasOwnProperty<X extends {}, Y extends PropertyKey>(obj: X, prop: Y): obj is X & Record<Y, unknown> {  // eslint-disable-line @typescript-eslint/ban-types
+    return Object.hasOwnProperty.call(obj, prop)
+}
+
+/**
+ * TypeScript helper for creating tagged-types.
+ */
+export interface TypeTag<S extends string> {
+    readonly __typeTag: S
+}
+
+export type PrefixTypeArray<T, TArr extends unknown[]> = [T, ...TArr]
+export type SuffixTypeArray<TArr extends unknown[], T> = [...TArr, T]
+export type TypeArraySignature<TArr extends unknown[], TReturn> = (...params: TArr) => TReturn
+
+export type MethodsOf<T, TMethod = (...args: any[]) => any> = {
+    [K in keyof T]: T[K] extends TMethod ? K : never
+}[keyof T]
+
+export type MethodType<TClass, TKey extends keyof TClass, TMethod = (...args: any[]) => any> = {
+    [K in keyof TClass]: TClass[K] extends TMethod ? TClass[K] : never
+}[TKey]
+
+export type Awaited<T> = T extends PromiseLike<infer U> ? U : T
+
+export type Integer = TypeTag<'@extollo/lib.Integer'> & number
+
+export function isInteger(num: number): num is Integer {
+    return !isNaN(num) && parseInt(String(num), 10) === num
+}
+
+export type ArrayElement<ArrayType extends readonly unknown[]> =
+    ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
diff --git a/src/config.ts b/src/config.ts
new file mode 100644
index 0000000..54afd59
--- /dev/null
+++ b/src/config.ts
@@ -0,0 +1,20 @@
+import {castCommentsConfig, type CommentsConfig} from "./types.ts";
+
+const maybeConfig: any = {
+    mail: {
+        imap: {
+            host: process.env.CHORUS_IMAP_HOST,
+            port: process.env.CHORUS_IMAP_PORT || 993,
+            auth: {
+                user: process.env.CHORUS_IMAP_USER,
+                pass: process.env.CHORUS_IMAP_PASS,
+            },
+        },
+        threads: {
+            type: 'alias',
+            template: process.env.CHORUS_THREAD_TEMPLATE,
+        },
+    },
+}
+
+export const config: CommentsConfig = castCommentsConfig(maybeConfig)
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..426d2f3
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,23 @@
+import {z} from "zod";
+
+const commentsConfigSchema = z.object({
+    mail: z.object({
+        imap: z.object({
+            host: z.string(),
+            port: z.number({ coerce: true }),
+            auth: z.object({
+                user: z.string(),
+                pass: z.string(),
+            }),
+        }),
+        threads: z.object({
+            type: z.string(),  // fixme : in validation
+            template: z.string(),
+        }),
+    }),
+})
+
+export type CommentsConfig = z.infer<typeof commentsConfigSchema>
+export const castCommentsConfig = (what: unknown): CommentsConfig => {
+    return commentsConfigSchema.parse(what)
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..238655f
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,27 @@
+{
+  "compilerOptions": {
+    // Enable latest features
+    "lib": ["ESNext", "DOM"],
+    "target": "ESNext",
+    "module": "ESNext",
+    "moduleDetection": "force",
+    "jsx": "react-jsx",
+    "allowJs": true,
+
+    // Bundler mode
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "verbatimModuleSyntax": true,
+    "noEmit": true,
+
+    // Best practices
+    "strict": true,
+    "skipLibCheck": true,
+    "noFallthroughCasesInSwitch": true,
+
+    // Some stricter flags (disabled by default)
+    "noUnusedLocals": false,
+    "noUnusedParameters": false,
+    "noPropertyAccessFromIndexSignature": false
+  }
+}