Skip to content

Commit

Permalink
Misc QOL (#97)
Browse files Browse the repository at this point in the history
  • Loading branch information
zoe-codez authored Oct 19, 2024
1 parent 63439c1 commit 2424317
Show file tree
Hide file tree
Showing 16 changed files with 579 additions and 174 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"repository": {
"url": "git+https://github.com/Digital-Alchemy-TS/core"
},
"version": "24.10.4",
"version": "24.10.5",
"author": {
"url": "https://github.com/zoe-codez",
"name": "Zoe Codez"
Expand Down
30 changes: 21 additions & 9 deletions src/helpers/config-environment-loader.helper.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable unicorn/prevent-abbreviations */
import minimist from "minimist";
import { env } from "process";

Expand All @@ -6,6 +7,7 @@ import {
AbstractConfig,
ConfigLoaderParams,
ConfigLoaderReturn,
DataTypes,
findKey,
iSearchKey,
loadDotenv,
Expand All @@ -20,6 +22,13 @@ export async function ConfigLoaderEnvironment<
const CLI_SWITCHES = minimist(process.argv);
const switchKeys = Object.keys(CLI_SWITCHES);
const out: Partial<AbstractConfig> = {};
const canEnvironment = internal.boot.options?.configSources?.env ?? true;
const canArgv = internal.boot.options?.configSources?.argv ?? true;

const shouldArgv = (source: DataTypes[]) =>
canArgv && (!is.array(source) || source.includes("argv"));
const shouldEnv = (source: DataTypes[]) =>
canEnvironment && (!is.array(source) || source.includes("env"));

// * merge dotenv into local vars
// accounts for `--env-file` switches, and whatever is passed in via bootstrap
Expand All @@ -32,6 +41,7 @@ export async function ConfigLoaderEnvironment<

// * run through each config for module
Object.keys(configuration).forEach(key => {
const { source } = configs.get(project)[key];
// > things to search for
// - MODULE_NAME_CONFIG_KEY (module + key, ex: app_NODE_ENV)
// - CONFIG_KEY (only key, ex: NODE_ENV)
Expand All @@ -41,14 +51,14 @@ export async function ConfigLoaderEnvironment<

// * (preferred) Find an applicable cli switch
const flag = findKey(search, switchKeys);
if (flag) {
if (flag && shouldArgv(source)) {
const formattedFlag = iSearchKey(flag, switchKeys);
internal.utils.object.set(
out,
configPath,
parseConfig(configuration[key], CLI_SWITCHES[formattedFlag]),
);
logger.trace(
logger.debug(
{
flag: formattedFlag,
name: ConfigLoaderEnvironment,
Expand All @@ -61,14 +71,16 @@ export async function ConfigLoaderEnvironment<

// * (fallback) Find an environment variable
const environment = findKey(search, environmentKeys);
if (!is.empty(environment)) {
if (!is.empty(environment) && shouldEnv(source)) {
const environmentName = iSearchKey(environment, environmentKeys);
internal.utils.object.set(
out,
configPath,
parseConfig(configuration[key], env[environmentName]),
);
logger.trace(
if (!is.string(env[environmentName]) || !is.empty(env[environmentName])) {
internal.utils.object.set(
out,
configPath,
parseConfig(configuration[key], env[environmentName]),
);
}
logger.debug(
{
name: ConfigLoaderEnvironment,
path: configPath,
Expand Down
24 changes: 23 additions & 1 deletion src/helpers/config.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ import {
TInjectedConfig,
} from "./wiring.helper";

export interface ConfigLoaderSource {
/**
* will be checked for values unless `sources` is defined without argv
*/
argv: true;
/**
* will be checked for values unless `env` is defined without argv
*/
env: true;
/**
* will be checked for values unless `file` is defined without argv
*/
file: true;
}

export type CodeConfigDefinition = Record<string, AnyConfig>;
export type ProjectConfigTypes =
| "string"
Expand Down Expand Up @@ -52,6 +67,11 @@ export interface BaseConfig {
required?: boolean;

type: ProjectConfigTypes;

/**
* Where this can be loaded from
*/
source?: (keyof ConfigLoaderSource)[];
}
export type KnownConfigs = Map<string, CodeConfigDefinition>;
export interface StringConfig<STRING extends string> extends BaseConfig {
Expand Down Expand Up @@ -272,9 +292,10 @@ export type DigitalAlchemyConfiguration = {
[INITIALIZE]: <S extends ServiceMap, C extends OptionalModuleConfiguration>(
application: ApplicationDefinition<S, C>,
) => Promise<string>;
[INJECTED_DEFINITIONS]: () => TInjectedConfig;
[INJECTED_DEFINITIONS]: TInjectedConfig;
[LOAD_PROJECT]: (library: string, definitions: CodeConfigDefinition) => void;
getDefinitions: () => KnownConfigs;
registerLoader: (loader: ConfigLoader, type: DataTypes) => void;
merge: (incoming: Partial<PartialConfiguration>) => PartialConfiguration;
/**
* Not a replacement for `onPostConfig`
Expand Down Expand Up @@ -310,3 +331,4 @@ export type OnConfigUpdateCallback<
Project extends keyof TInjectedConfig,
Property extends keyof TInjectedConfig[Project],
> = (project: Project, property: Property) => TBlackHole;
export type DataTypes = keyof ConfigLoaderSource;
9 changes: 7 additions & 2 deletions src/helpers/cron.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,16 @@ export type SchedulerSlidingOptions = SchedulerOptions & {
next: () => Dayjs | string | number | Date | undefined;
};

export type ScheduleRemove = () => TBlackHole;
export type ScheduleRemove = (() => TBlackHole) & { remove: () => TBlackHole };

export const makeRemover = (remover: () => TBlackHole): ScheduleRemove => {
const cast = remover as ScheduleRemove;
cast.remove = remover;
return cast;
};

export type DigitalAlchemyScheduler = {
cron: (options: SchedulerCronOptions) => ScheduleRemove;
interval: (options: SchedulerIntervalOptions) => ScheduleRemove;
sliding: (options: SchedulerSlidingOptions) => ScheduleRemove;
setInterval: (callback: () => TBlackHole, ms: number) => ScheduleRemove;
setTimeout: (callback: () => TBlackHole, ms: number) => ScheduleRemove;
Expand Down
26 changes: 16 additions & 10 deletions src/helpers/module.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type ExtendOptions = {
keepConfiguration?: boolean;
};

// #MARK: DigitalAlchemyModule
export type DigitalAlchemyModule<S extends ServiceMap, C extends OptionalModuleConfiguration> = {
services: S;
configuration: C;
Expand All @@ -31,6 +32,7 @@ export type DigitalAlchemyModule<S extends ServiceMap, C extends OptionalModuleC
extend: (options?: ExtendOptions) => ModuleExtension<S, C>;
};

// #MARK: CreateModuleOptions
export type CreateModuleOptions<S extends ServiceMap, C extends OptionalModuleConfiguration> = {
services: S;
configuration: C;
Expand All @@ -43,6 +45,7 @@ export type CreateModuleOptions<S extends ServiceMap, C extends OptionalModuleCo
/**
* commands mutate module
*/
// #MARK: ModuleExtension
export type ModuleExtension<S extends ServiceMap, C extends OptionalModuleConfiguration> = {
appendLibrary: (library: TLibrary) => ModuleExtension<S, C>;
appendService: (name: string, target: ServiceFunction) => ModuleExtension<S, C>;
Expand Down Expand Up @@ -93,6 +96,7 @@ export type ModuleExtension<S extends ServiceMap, C extends OptionalModuleConfig
toTest: () => iTestRunner<S, C>;
};

// #MARK: createModule
export function createModule<S extends ServiceMap, C extends OptionalModuleConfiguration>(
options: CreateModuleOptions<S, C>,
): DigitalAlchemyModule<S, C> {
Expand Down Expand Up @@ -164,7 +168,7 @@ export function createModule<S extends ServiceMap, C extends OptionalModuleConfi
},
toApplication: () => {
const depends = {} as Record<string, TLibrary>;
workingModule.depends.forEach(i => (depends[i.name] = i));
workingModule.depends?.forEach(i => (depends[i.name] = i));
appendLibrary.forEach((value, key) => (depends[key] = value));

return CreateApplication({
Expand All @@ -173,13 +177,13 @@ export function createModule<S extends ServiceMap, C extends OptionalModuleConfi
// @ts-expect-error wrapper problems
name: extendOptions?.name || options.name,
// @ts-expect-error wrapper problems
priorityInit: [...workingModule.priorityInit],
priorityInit: [...(workingModule.priorityInit ?? [])],
services,
});
},
toLibrary: () => {
const depends = {} as Record<string, TLibrary>;
workingModule.depends.forEach(i => (depends[i.name] = i));
workingModule.depends?.forEach(i => (depends[i.name] = i));
appendLibrary.forEach((value, key) => (depends[key] = value));

return CreateLibrary({
Expand Down Expand Up @@ -212,28 +216,30 @@ export function createModule<S extends ServiceMap, C extends OptionalModuleConfi
return workingModule;
}

// #MARK: fromApplication
createModule.fromApplication = <S extends ServiceMap, C extends OptionalModuleConfiguration>(
application: ApplicationDefinition<S, C>,
) => {
return createModule<S, C>({
configuration: application.configuration || ({} as C),
depends: application.libraries || [],
configuration: application.configuration,
depends: application.libraries,
name: application.name,
optionalDepends: [],
priorityInit: application.priorityInit || [],
priorityInit: application.priorityInit,
services: application.services,
});
};

// #MARK: fromLibrary
createModule.fromLibrary = <S extends ServiceMap, C extends OptionalModuleConfiguration>(
library: LibraryDefinition<S, C>,
) => {
return createModule<S, C>({
configuration: library.configuration || ({} as C),
depends: library.depends || [],
configuration: library.configuration,
depends: library.depends,
name: library.name,
optionalDepends: library.optionalDepends || [],
priorityInit: library.priorityInit || [],
optionalDepends: library.optionalDepends,
priorityInit: library.priorityInit,
services: library.services,
});
};
33 changes: 18 additions & 15 deletions src/helpers/wiring.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import {
AnyConfig,
BooleanConfig,
ConfigLoader,
DataTypes,
InternalConfig,
NumberConfig,
OptionalModuleConfiguration,
Expand All @@ -39,7 +39,6 @@ export type ApplicationConfigurationOptions<
S extends ServiceMap,
C extends OptionalModuleConfiguration,
> = {
configurationLoaders?: ConfigLoader[];
name: keyof LoadedModules;
services: S;
libraries?: LibraryDefinition<ServiceMap, OptionalModuleConfiguration>[];
Expand Down Expand Up @@ -98,16 +97,6 @@ export type TScheduler = {
schedule: Schedule | Schedule[];
},
) => ScheduleRemove;
/**
* Run code on a regular periodic interval
*
* @deprecated use `scheduler.setInterval`
*/
interval: (
options: SchedulerOptions & {
interval: number;
},
) => ScheduleRemove;
/**
* Run code at a different time every {period}
*
Expand Down Expand Up @@ -158,7 +147,7 @@ export type TInjectedConfig = {
[ModuleName in keyof ModuleConfigs]: ConfigTypes<ModuleConfigs[ModuleName]>;
};

// #region
// #region Special
// SEE DOCS http://docs.digital-alchemy.app/docs/core/declaration-merging
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface AsyncLogData {
Expand Down Expand Up @@ -309,6 +298,7 @@ export type LibraryConfigurationOptions<
priorityInit?: Extract<keyof S, string>[];
};

// #MARK: PartialConfiguration
export type PartialConfiguration = Partial<{
[ModuleName in keyof ModuleConfigs]: Partial<ConfigTypes<ModuleConfigs[ModuleName]>>;
}>;
Expand Down Expand Up @@ -372,15 +362,22 @@ export type BootstrapOptions = {
* Default: `.env`
*/
envFile?: string;

/**
* all properties default true if not provided
*/
configSources?: Partial<Record<DataTypes, boolean>>;
};

// #MARK: LoggerOptions
export type LoggerOptions = {
/**
* Generic data to include as data payload for all logs
*
* Can be used to provide application tags when using a log aggregator
*/
mergeData?: object;

/**
* Adjust the format of the timestamp at the start of the log
*
Expand Down Expand Up @@ -444,6 +441,7 @@ type Wire = {
) => Promise<TLifecycleBase>;
};

// #MARK: LibraryDefinition
export type LibraryDefinition<
S extends ServiceMap,
C extends OptionalModuleConfiguration,
Expand All @@ -452,6 +450,7 @@ export type LibraryDefinition<
type: "library";
};

// #MARK: ApplicationDefinition
export type ApplicationDefinition<
S extends ServiceMap,
C extends OptionalModuleConfiguration,
Expand All @@ -460,11 +459,12 @@ export type ApplicationDefinition<
logger: ILogger;
type: "application";
booted: boolean;
bootstrap: (options?: BootstrapOptions) => Promise<void>;
bootstrap: (options?: BootstrapOptions) => Promise<TServiceParams>;
teardown: () => Promise<void>;
};
export type TLibrary = LibraryDefinition<ServiceMap, OptionalModuleConfiguration>;

// #MARK: buildSortOrder
export function buildSortOrder<S extends ServiceMap, C extends OptionalModuleConfiguration>(
app: ApplicationDefinition<S, C>,
logger: ILogger,
Expand Down Expand Up @@ -499,7 +499,7 @@ export function buildSortOrder<S extends ServiceMap, C extends OptionalModuleCon
// just "are they the same object reference?" as the test
// you get a warning, and the one the app asks for
// hopefully there is no breaking changes
if (loaded !== item && !process.env.NODE_ENV.startsWith("test")) {
if (loaded !== item && !process.env.NODE_ENV?.startsWith("test")) {
logger.warn(
{ name: buildSortOrder },
"[%s] depends different version {%s}",
Expand Down Expand Up @@ -540,6 +540,7 @@ export function buildSortOrder<S extends ServiceMap, C extends OptionalModuleCon
export const COERCE_CONTEXT = (context: string): TContext => context as TContext;
export const WIRING_CONTEXT = COERCE_CONTEXT("boilerplate:wiring");

// #MARK: validateLibrary
export function validateLibrary<S extends ServiceMap>(
project: string,
serviceList: S,
Expand All @@ -565,6 +566,7 @@ export function validateLibrary<S extends ServiceMap>(
}
}

// #MARK: wireOrder
export function wireOrder<T extends string>(priority: T[], list: T[]): T[] {
const out = [...(priority || [])];
if (!is.empty(priority)) {
Expand All @@ -581,6 +583,7 @@ export function wireOrder<T extends string>(priority: T[], list: T[]): T[] {
return temporary;
}

// #MARK: CreateLibrary
export function CreateLibrary<S extends ServiceMap, C extends OptionalModuleConfiguration>({
name: libraryName,
configuration = {} as C,
Expand Down
Loading

0 comments on commit 2424317

Please sign in to comment.