0.1.1 • Published 1 year ago
@caffedpkg/microcore v0.1.1
microcore
A 'framework' framework.
About
- Why?
- I prefer smaller frameworks that allow for open ended extension with less dependencies to use for my own projects.
- Also, why not? 🤷
- What?
- This project follows the 80/20 adage in supporting 20% of the features you use 80% of the time and will never be as featureful as dedicated projects for specific domains.
- Most modules provide an API with middleware for extension.
- How?
- Currently, the best documentation is the source and tests in this package. As time permits, I will add more verbose documentation and examples.
Documentation
- This is a collection of APIs mostly designed to be used together. The two main sources of documentation are this README.md and the Typedoc generated documentation.
- This readme is meant to be an overview of this project while the documentation will be expanded at a later date.
Goals/Caveats:
- The goal of this project is to be as small as possible.
- API cronstructors provide a standard API and middleware extension framework. Specific use case functionality is supposed to use 'userland' middleware for implementation.
- The targeted interpreters are node, browsers and Electron. Deno will be verified in the future.
Modules
Cache
- The cache module provides the
Cache<K, V>interface and two implementations:MapCache<K, V>class andcreateMapCache<K, V>constructor. - The
MapCacheclass provides a simple implementation of the interface using aMap<K, V>to store values. - The
createMapCacheconstructor implements middleware so that each method, except the name getter, can be extended via middleware. The last middleware in the chain is./cache/middleware.tsand provides baseline cache method functionality. - For more detail, see source, middleware and tests.
MapCache Class Example:
// Not meant to be a complete example
import { MapCache } from '@caffedpkg/microcore';
type CustomType = {
// typedef here
};
const mapCacheClassInstance = new MapCache<string, CustomType>();
const key = 'key';
const valueOne: CustomType = {};
mapCacheClassInstance.setValue(key, valueOne);
mapCacheClassInstance.hasValue(key) // truecreateMapCache Constructor Example:
// Not meant to be a complete example
import { CACHE_ACTIONS, createMapCache } from '@caffedpkg/microcore';
type CustomType = {
// typedef here
};
const myHashingFunction = (input) => {
// logic here: MD5, SHA256 etc
};
const cacheKey = (hashingFunction: typeof Function) => {
return (api: Record<any, any>) => {
const { type } = api?.action;
return (middleware: any) => {
return (params: Record<any, any>) => {
if (type === CACHE_ACTIONS.CACHE_SET_VALUE) {
const hashKey = hashingFunction(value);
return middleware({ key: `${key}-${hashkey}`, value });
}
return middleware(params);
};
};
};
};
const mapCacheInstance = createMapCache<string, CustomType>({
middleware: [{ middleware: cacheKey(myHashingFunction) }],
});
const key = 'key';
const valueOne: CustomType = {};
mapCacheInstance.setValue(key, valueOne);Combinators
- This is a collection of common functional combinators and are still heavily WIP.
- Not yet recommended for use.
- These will eventually allow for easy typing and code block integration.
- For more detail, see source and tests.
Decorators
- This is a convenience API based off of the TypeScript handbook documentation for decorators.
- This implementation provides a
Decorator<T, V, R>, type as well as a universalcreateDecorator<T, V, R>constructor with convenience constructors for each type of decorator. - This decorator API supports middleware for constructors so that functionality can be easily reused.
- For more detailed examples, see decorator test specs.
- For detailed parameter decorator implementations, see TypeScript's Handbook.
- For more detail, see source and tests.
Class Decorator Example:
// Not meant to be a complete example
import { createClassDecorator } from '@caffedpkg/microcore';
const myDecoratorMiddleware = () => {} // see middleware docs
const middleware = [{
middleware: myDecoratorMiddleware,
}];
const callback = (params: Record<any, any>) => {
const {
api, // the middleware api
target, // the class prototype
} = params;
// decorator code here
// target is mutated and there is no return
};
const MyClassDecorator = createClassDecorator({
callback,
middleware,
});
@MyClassDecorator
class Test {
constructor() {}
}Class Method Decorator Example:
// Not meant to be a complete example
import { createClassMemberDecorator } from '@caffedpkg/microcore';
const myDecoratorMiddleware = () => {} // see middleware docs
const middleware = [{
middleware: myDecoratorMiddleware,
}];
const callback = (params: Record<any, any>) => {
const {
api, // the middleware api
target, // the class prototype
propertyKey, // the name of the method
descriptor, // The property descriptor of method definition
} = params;
// decorator code here
// descriptor is mutated and there is no return
};
const MyClassMethodDecorator = createClassMemberDecorator({
callback,
middleware,
});
class Test {
constructor() {}
@MyClassMethodDecorator
method() {}
}Class Property Decorator Example:
// Not meant to be a complete example
import { createClassMemberDecorator } from '@caffedpkg/microcore';
const myDecoratorMiddleware = () => {} // see middleware docs
const middleware = [{
middleware: myDecoratorMiddleware,
}];
const callback = (params: Record<any, any>) => {
const {
api, // the middleware api
target, // the class prototype
propertyKey, // the name of the method
} = params;
// decorator code here
// property accessors are mutated and there is no return
// Easiest approach is using `Reflect.defineProperty(target, propertyKey, { get, set })`
};
const MyClassPropertyDecorator = createClassMemberDecorator({
callback,
middleware,
});
class Test {
// Initialization with assignment is currently not supported with TypeScript property decorators
@MyClassPropertyDecorator
declare val : string;
}Class Method Parameter Decorator Example:
// Not meant to be a complete example
import { createClassMemberDecorator } from '@caffedpkg/microcore';
const myDecoratorMiddleware = () => {} // see middleware docs
const middleware = [{
middleware: myDecoratorMiddleware,
}];
const callback = (params: Record<any, any>) => {
const {
api, // the middleware api
parameterIndex, // parameter arity index
propertyKey, // the name of the method
} = params;
// decorator code here
// no return
// see https://www.typescriptlang.org/docs/handbook/decorators.html#parameter-decorators
};
const MyClassParameterDecorator = createClassMemberDecorator({
callback,
middleware,
});
class Test {
method(@MyClassParameterDecorator param) {}
}HTTTPClient
- The
HTTPClient<R>constructor uses types made from MDN and RFC documentation (see source for links) and simplifies creatingfetchbased requests with middleware support for request and response handling. - There are additional convenience constructors for creating APIs based on
HTTPClient:clientFactory(scoped by HTTP method) andcreateHttpClient(all HTTP methods). - For more detail, see source and tests.
HTTPClient Request Handling Example:
// Not meant to be a complete example
import { HttpClient } from '@caffedpkg/microcore';
const myTokenGenerator = () => {};
const auth = (getToken: typeof Function) => {
return (api: Record<any, any>) => {
const { type } = api?.action;
return (middleware: any) => {
return (params: Record<any, any>) => {
const {
requestOptions,
} = params;
if (type === 'HTTP_REQUEST') {
const token = getToken(requestOptions);
const init = requestOptions[1] || {};
if (init.headers) {
init.headers.set('Authorization', `Bearer: ${token}`);
} else {
init.headers = new Headers([['Authorization', `Bearer: ${token}`]]);;
}
requestOptions[1] = init;
}
return middleware(params);
};
};
};
};
const client = HttpClient({
middleware: [{ middleware: auth(myTokenGenerator) }],
requestInitOptions: {
method: 'GET',
},
request: 'https://url',
});
await client()
.then(resp => {
console.log(resp.headers.get('Authorization')); // `Bearer: ${token}`
});HTTPClient Response Handling Example:
// Not meant to be a complete example
import { HttpClient } from '@caffedpkg/microcore';
const myResponseParser = () => {};
const responseHandler = (api: Record<any, any>) => {
const { type } = api?.action;
return (middleware: any) => {
return (params: Record<any, any>) => {
const {
result,
} = params;
if (type === 'HTTP_RESPONSE') {
return myResponseParser(result);
}
return middleware(params);
};
};
};
const client = HttpClient({
middleware: [{ middleware: responseHandler }],
requestInitOptions: {
method: 'GET',
},
request: 'https://url',
});
await client()
.then(resp => {
console.log(resp); // response was automatically handled by myResponseParser
});Logger
- This API provides the
createLogger<F>constructor with middleware support for line creation and line logging. - The default middleware provides JSON and string formatted support with
console.logfunctionality. ThemakeJsonLinemiddleware automatically creates a SQLite compatible DATATIME timestamp for each logged line.
[${YEAR}-${DAY}-${MONTH}T${HOUR}:${MINUTE}:${SECOND}:${MS} | ${LEVEL} | ${PREFIX}]: ${DATA}
- Middleware can be written to support custom telemetry workflows as well.
- For more detail, see source, middleware and tests.
Logger Example:
// Not meant to be a complete example
import { createLogger } from '@caffedpkg/microcore';
const myFormatter = () => {}; // see ./logger/middlerware.ts
const logger = createLogger<string>({
formatter: myFormatter,
prefix: 'Custom Prefix',
});
logger.log('test')
> [2049-04-4T12:00:00:000 | INFO | Custom Prefix]: testMatcher
- This API provides the
createMatcher<A, B>constructor with middleware support for extendable assertions. The intended use case is for data validation eg: form fields, etc. - The
createProxiedMatcher<A, B>allows for a flexible micro-assertion library extendable via middleware. This throws for any failure. These use cases might be less universally needed. - For more detail, see source and tests.
Matcher Example:
// Not meant to be a complete example
import { createMatcher } from '@caffedpkg/microcore';
const assertTrue = (_: any) => {
return (_: any) => {
return (...params: any[]) => {
return params[0] === params[1];
};
}
};
type MyInputTypes = string | number;
const assertTrueMatcher = createMatcher<number | string>({
middleware: [{ middleware: assertTrue }],
});
assertTrueMatcher('test', 'test') // true
assertTrueMatcher(false, 'false') // type error and also false Proxied Matcher Example:
// Not meant to be a complete example
import { createProxiedMatcher } from '@caffedpkg/microcore';
const assertTrue = (_: any) => {
return (_: any) => {
return (...params: any[]) => {
return params[0] === params[1];
};
}
};
const proxiedMatcher = createProxiedMatcher<number | string>({
defaultTarget: {},
methodName,
middleware: [{ middleware: assertTrue }],
});
// matcher
proxiedMatcher.match(true, true);
// seperate expect() chain added, see source for features
proxiedMatcher.expect(true).to.eq(true);
proxiedMatcher.expect(true).to.be.true;Middleware
- This API provides a generic way to add middleware to any project. It is based on the apply/A+compose/B combinator approach that Redux uses.
- This main entrypoint for this API is
createMiddlewareApi<T, R>as it provides defaults for all parameters not passed in. If you want more control or a simpler implementation, useapplyMiddleware<T, R>instead. - For more detail, see source and tests.
- NOTE: The types/generics in this API might change in the future.
createMiddlewareApi Example:
// Not meant to be a complete example
import { createMiddlewareApi } from '@caffedpkg/microcore';
// API object constructor
const createApi = (...args) => {
return {
myOtherFunction() {},
middleware() {}, // added to middleware chain
};
};
// Array of middleware
const middleware = [{
data: {}, // to be used in transform for sorting, reshaping etc
middleware: myMiddleWare,
}];
// compare function: (a, b) => -1, 0, 1
const sort = () => {};
// An Array.prorotype.reduce callback function
// allows for arbitrary reshaping of the array of middleware.
const transform = (accumulator, current, index, array) => {};
// - Each one of these parameters has a corresponding
// passthrough default if not provided
// - `middleware` is the only one really needed for most use cases
const api = createMiddlewareApi({
createApi,
middleware,
sort,
transform,
});
const result = api.middleware(args);React utils
- The one thing currently provided in
mountRootComponentwhich handles bootstrapping the root React component into thedocument.body. - For more detail, see source and tests.
mountRootComponent Example:
// Not meant to be a complete example
import { mountRootComponent } from '@caffedpkg/microcore';
import { App } from './App';
const logger = () => {};
const renderFunc = () => {};
mountRootComponent({
Component: App,
logger, // optional. for error reporting. console.warn is default.
renderFunc, // optional. handles mounting root component. defaults to React 18.x boilerplate.
rootElement: '#content-root', // CSS selector string or HTMLElement reference of root HTMLElement
});Reducer
- There are three APIs:
createHistory,createReducer, andcreateStore. The history and reducer APIs are self contained while the store API combines them together. - The
createHistoryController<T, S>API manages aHistory<T, S>instance with storing states in thepast,currentandfuture. - The
createReducer<T, S>API manages aReducerAction<T, S, A>collection. This can be reused in different stores for composable functionality. - The
createStore<T, S, A>API combines a history controller and reducer collection to dispatch actions to. By defaultundo,redoandjumpToRevisionare supported. - States have at least a
revisionIdandvalueproperty and actions have at least atype. - For more detail, see
Store actions Example:
const reducers = {
math: {
add: numberAdd(state, action) => state,
},
strings: {
add: stringAdd(state, action) => state,
}
}
// results in actionOne = { type: 'math/add' }, and actionTwo = { type: 'strings/add' }createHistoryController Example:
// Not meant to be a complete example
import { createHistoryController } from '@caffedpkg/microcore';
// can be pre-seeded with data
const history = {
past: [],
current: null,
future: [],
};
const historyController = createHistoryController({ history });createReducer Example:
// Not meant to be a complete example
import { createReducer } from '@caffedpkg/microcore';
const addAction = (state, action) => state;
const actions = {
'ADD': addAction,
};
type MyStateValueType = {
// fields
};
const reducer = createReducer<MyStateValueType>({
actions,
});createStore Example:
// Not meant to be a complete example
import { createStore } from '@caffedpkg/microcore';
const reducers = {
math: {
add: numberAdd, // (state, action) => state,
},
strings: {
add: stringAdd, // (state, action) => state,
}
}
const reducer = createStore({
reducers,
});
const result = store.dispatch({
type: 'math/add',
data: {},
});Resource
- The resource API is for creating dynamically generated resource URLs for use with Workers and other frameworks that require URLs for construction.
- There are two stand alone constructors
createTextResourceandcreateResourcewith the main difference in thatcreateTextResourceis restricted to all 'text/*' mime types and returns a tag function for string template setup andcreateResourcereturns a curried function for default plus instance param merging. - There is also a
ResourceControllerwhich is a singleton controller designed to manage all resources per JavaScript realm. - For more detail, see source and tests.
createTextResource Example:
// Not meant to be a complete example
import { createTextResource } from '@caffedpkg/microcore';
const factory = x => x;
const name = 'test';
const textResource = createTextResource({
factory, // optional. used for any custom resource content formatting.
mimeType: 'text/javascript',
name, // optional. the name of the resource.
})`
self.addEventListener('message', () => { console.log('i am being used for a Worker.'); });
`;
const worker = new Worker(textResource.url);createResource Example:
// Not meant to be a complete example
import { createResource } from '@caffedpkg/microcore';
const factory = x => x;
const name = 'test';
const data = `self.addEventListener('message', () => { console.log('i am being used for a Worker.');`;
// Passing default parameters
const workerScript = createResource({
factory, // optional. used for any custom resource content formatting.
mimeType: 'text/javascript',
name, // optional. the name of the resource.
});
const resource = workerScript({ data });
const worker = new Worker(resource.url);ResourceController Example:
// Not meant to be a complete example
import { ResourceController } from '@caffedpkg/microcore';
const factory = x => x;
const name = 'test';
const data = `self.addEventListener('message', () => { console.log('I am being used for a Worker.');`;
const resourceController = new ResourceController();
const resource = resourceController.create({
data,
factory,
mimeType: 'text/javascript',
name,
});
const worker = new Worker(resource.url);
resourceController.delete(name);
const workerTwo = new Worker(resource.url); // will now failTransaction
- This API is inspired by the combination of state reducer and database transaction patterns. Each stage in the transaction chain is handled by a middleware handler with data flowing throw them like a state reducer - state and action. Commit processes the steps in forward order while rollback() processes in reverse order to return related entities back into the origin state.
- Each middleware should account for handling both
TRANSACTION_COMMITandTRANSACTION_ROLLBACKmiddleware actions. - For more detail, see source and tests.
Transaction Example:
// Not meant to be a complete example
import {
type Action,
type State,
createTransaction,
} from '@caffedpkg/microcore';
const state: State<T> = {};
const action: Action<T> = {};
const middleware = []; // your transaction handling middleware
const createTxRunner = createTransaction({
middleware,
});
const runner = createTxRunner(state, action);
try {
runner.commit();
} catch(_: any) {
runner.rollback();
}URLManager
- The URLManager class is a small extension of the existing URL interface to track generated object URLs from
URL.createObjectUrl. - It also adds a convenience get/set for search params as
Record<string, string>. - There is also a convenience static property
currentUrlwhich returns aURLManagerinstance ofglobalThis.location.href. - For more detail, see source and tests.
URLManager Examples:
// Not meant to be a complete example
import { URLManager } from '@caffedpkg/microcore';
const blob = new Blob(['test'], { type 'text/plain' });
const url = URLManager.createObjectUrl(blob);
URLManager.objectUrls.includes(url); // true
// currentUrl
const currentUrl = URLManager.currentUrl;
// params
const url = new URLManager('https://domain?key=val');
console.log(url.params);
> {
> key: 'val',
> }
url.params = { key: 'valTwo' };
console.log(url.href);
> 'https://dommain?key=valTwo'Utils
- This is a collection of functions mostly to support other APIs. These are tested but highly in flux. The rest will be documented in future versions when they are stabilized.
- For more detail, see source and tests.
Utils Examples:
// Not meant to be a complete example
import { randomInteger, randomString } from '@caffedpkg/microcore';
const num = randomInteger(); // default 4 digits
typeof num === 'number';
const numTwo = randomInteger(20, { bigint: true });
typeof numTwo === 'bigint';
const str = randomString(); // default 10 characters
typeof str === 'string';
const strTwo = randomString(100);
typeof strTwo === 'string';
strTwo.length === 100; // trueTypes
- These are convenience types to support APIs here and in other projects.
- For more detail, see source.
// Not meant to be a complete example
import type {
AnyObject,
ArrowFunction,
Callable,
Constructable,
ConstructorFunction,
MimeType,
} from '@caffedpkg/microcore';
// AnyObject<any> is essentially the same as Record<any, any>
const obj: AnyObject = {};
// ArrowFunction is a union type of all arrow function signatures
const myFunc = (callback: ArrowFunction): void => {};
// Callable is a conditional type used for defining arrow function signatures
// Constructable is a conditional type used for defining function constructor signatures
type MyType<R> = {
callback: Callable<[first: FirstParamType, second: SecondParamType], any>;
constructor: Constructable<any, R>;
}
// ConstructorFunction is a union type of all constructor function signatures
const myFunc = (constructor: ConstructorFunction): void => new constructor();
// MimeType is a string template type that constrains mime type strings to mostly valid
const mimeType: MimeType = 'image/png';
const badMimeType: MimeType = 'string/text';Roadmap
NOTE: This project will follow semver guidelines
- 0.1.0
- First alpha release
- 0.2.0
- Bug fixes.
- The rest of the roadmap completed
Contributing
Currently, this package is for my project needs so large changes will be ignored.
Please see the contributing guidelines and the code of conduct for more information regarding submitting feature requests and bugs.
License
Licensed under the MIT License, Copyright © 2022-present Carlo Mogavero.
See LICENSE for more information.