mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
* initial refactor * fixes * test secrets extraction * updated lock * fix secret schema * updated schemas, fixed tests, skipping flow tests for now * added validator for rjsf, hook form via standard schema * removed @sinclair/typebox * remove unneeded vite dep * fix jsonv literal on Field.tsx * fix schema import path * fix schema modals * fix schema modals * fix json field form, replaced auth form * initial waku * finalize waku example * fix jsonv-ts version * fix schema updates with falsy values * fix media api to respect options' init, improve types * checking media controller test * checking media controller test * checking media controller test * clean up mediacontroller test * added cookie option `partitioned`, as well as cors `origin` to be array, option to enable `credentials` (#214) * added cookie option `partitioned`, as well as cors `origin` to be array, option to enable `credentials` * fix server test * fix data api (updated jsonv-ts) * enhance cloudflare image optimization plugin with new options and explain endpoint (#215) * feat: add ability to serve static by using dynamic imports (#197) * feat: add ability to serve static by using dynamic imports * serveStaticViaImport: make manifest optional * serveStaticViaImport: add error log * refactor/imports (#217) * refactored core and core/utils imports * refactored core and core/utils imports * refactored media imports * refactored auth imports * refactored data imports * updated package json exports, fixed mm config * fix tests * feat/deno (#219) * update bun version * fix module manager's em reference * add basic deno example * finalize * docs: fumadocs migration (#185) * feat(docs): initialize documentation structure with Fumadocs * feat(docs): remove home route and move /docs route to /route * feat(docs): add redirect to /start page * feat(docs): migrate Getting Started chapters * feat(docs): migrate Usage and Extending chapters * feat(callout): add CalloutCaution, CalloutDanger, CalloutInfo, and CalloutPositive * feat(layout): add Discord and GitHub links to documentation layout * feat(docs): add integration chapters draft * feat(docs): add modules chapters draft * refactor(mdx-components): remove unused Icon import * refactor(StackBlitz): enhance type safety by using unknown instead of any * refactor(layout): update navigation mode to 'top' in layout configuration * feat(docs): add @iconify/react package * docs(mdx-components): add Icon component to MDX components list * feat(docs): update Next.js integration guide * feat(docs): update React Router integration guide * feat(docs): update Astro integration guide * feat(docs): update Vite integration guide * fix(docs): update package manager initialization commands * feat(docs): migrate Modules chapters * chore(docs): update package.json with new devDependencies * feat(docs): migrate Integration Runtimes chapters * feat(docs): update Database usage chapter * feat(docs): restructure documentation paths * chore(docs): clean up unused imports and files in documentation * style(layout): revert navigation mode to previous state * fix(docs): routing for documentation structure * feat(openapi): add API documentation generation from OpenAPI schema * feat(docs): add icons to documentation pages * chore(dependencies): remove unused content-collections packages * fix(types): fix type error for attachFile in source.ts * feat(redirects): update root redirect destination to '/start' * feat(search): add static search functionality * chore(dependencies): update fumadocs-core and fumadocs-ui to latest versions * feat(search): add Powered by Orama link * feat(generate-openapi): add error handling for missing OpenAPI schema * feat(scripts): add OpenAPI generation to build process * feat(config): enable dynamic redirects and rewrites in development mode * feat(layout): add GitHub token support for improved API rate limits * feat(redirects): add 301 redirects for cloudflare pages * feat(docs): add Vercel redirects configuration * feat(config): enable standalone output for development environment * chore(layout): adjust layout settings * refactor(package): clean up ajv dependency versions * feat(docs): add twoslash support * refactor(layout): update DocsLayout import and navigation configuration * chore(layout): clean up layout.tsx by commenting out GithubInfo * fix(Search): add locale to search initialization * chore(package): update fumadocs and orama to latest versions * docs: add menu items descriptions * feat(layout): add GitHub URL to the layout component * feat(docs): add AutoTypeTable component to MDX components * feat(app): implement AutoTypeTable rendering for AppEvents type * docs(layout): switch callouts back to default components * fix(config): use __filename and __dirname for module paths * docs: add note about node.js 22 requirement * feat(styles): add custom color variables for light and dark themes * docs: add S3 setup instructions for media module * docs: fix typos and indentation in media module docs * docs: add local media adapter example for Node.js * docs(media): add S3/R2 URL format examples and fix typo * docs: add cross-links to initial config and seeding sections * indent numbered lists content, clarified media serve locations * fix mediacontroller tests * feat(layout): add AnimatedGridPattern component for dynamic background * style(layout): configure fancy ToC style ('clerk') * fix(AnimatedGridPattern): correct strokeDasharray type * docs: actualize docs * feat: add favicon * style(cloudflare): format code examples * feat(layout): add Github and Discord footer icons * feat(footer): add SVG social media icons for GitHub and Discord * docs: adjusted auto type table, added llm functions * added static deployment to cloudflare workers * docs: change cf redirects to proxy *.mdx instead of redirecting --------- Co-authored-by: dswbx <dennis.senn@gmx.ch> Co-authored-by: cameronapak <cameronandrewpak@gmail.com> * build: improve build script * add missing exports, fix EntityTypescript imports * media: Dropzone: add programmatic upload, additional events, loading state * schema object: disable extended defaults to allow empty config values * Feat/new docs deploy (#224) * test * try fixing pm * try fixing pm * fix docs on imports, export events correctly --------- Co-authored-by: Tim Seriakov <59409712+timseriakov@users.noreply.github.com> Co-authored-by: cameronapak <cameronandrewpak@gmail.com>
438 lines
13 KiB
TypeScript
438 lines
13 KiB
TypeScript
import { pascalToKebab } from "./strings";
|
|
|
|
export function _jsonp(obj: any, space = 2): string {
|
|
return JSON.stringify(obj, null, space);
|
|
}
|
|
|
|
export function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
return Object.prototype.toString.call(value) === "[object Object]";
|
|
}
|
|
|
|
export function isObject(value: unknown): value is Record<string, unknown> {
|
|
return value !== null && typeof value === "object";
|
|
}
|
|
|
|
export function omitKeys<T extends object, K extends keyof T>(
|
|
obj: T,
|
|
keys_: readonly K[],
|
|
): Omit<T, Extract<K, keyof T>> {
|
|
const keys = new Set(keys_);
|
|
const result = {} as Omit<T, Extract<K, keyof T>>;
|
|
for (const [key, value] of Object.entries(obj) as [keyof T, T[keyof T]][]) {
|
|
if (!keys.has(key as K)) {
|
|
(result as any)[key] = value;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function safelyParseObjectValues<T extends { [key: string]: any }>(obj: T): T {
|
|
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
try {
|
|
// @ts-ignore
|
|
acc[key] = JSON.parse(value);
|
|
} catch (error) {
|
|
// @ts-ignore
|
|
acc[key] = value;
|
|
}
|
|
return acc;
|
|
}, {} as T);
|
|
}
|
|
|
|
export function keepChanged<T extends object>(origin: T, updated: T): Partial<T> {
|
|
return Object.keys(updated).reduce(
|
|
(acc, key) => {
|
|
if (updated[key] !== origin[key]) {
|
|
acc[key] = updated[key];
|
|
}
|
|
return acc;
|
|
},
|
|
{} as Partial<T>,
|
|
);
|
|
}
|
|
|
|
export function objectKeysPascalToKebab(obj: any, ignoreKeys: string[] = []): any {
|
|
if (obj === null || typeof obj !== "object") {
|
|
return obj;
|
|
}
|
|
|
|
if (Array.isArray(obj)) {
|
|
return obj.map((item) => objectKeysPascalToKebab(item, ignoreKeys));
|
|
}
|
|
|
|
return Object.keys(obj).reduce(
|
|
(acc, key) => {
|
|
const kebabKey = ignoreKeys.includes(key) ? key : pascalToKebab(key);
|
|
acc[kebabKey] = objectKeysPascalToKebab(obj[key], ignoreKeys);
|
|
return acc;
|
|
},
|
|
{} as Record<string, any>,
|
|
);
|
|
}
|
|
|
|
export function filterKeys<Object extends { [key: string]: any }>(
|
|
obj: Object,
|
|
keysToFilter: string[],
|
|
): Object {
|
|
const result = {} as Object;
|
|
|
|
for (const key in obj) {
|
|
const shouldFilter = keysToFilter.some((filterKey) => key.includes(filterKey));
|
|
if (!shouldFilter) {
|
|
if (typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key])) {
|
|
result[key] = filterKeys(obj[key], keysToFilter);
|
|
} else {
|
|
result[key] = obj[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
export function transformObject<T extends Record<string, any>, U>(
|
|
object: T,
|
|
transform: (value: T[keyof T], key: keyof T) => U | undefined,
|
|
): { [K in keyof T]: U } {
|
|
const result = {} as { [K in keyof T]: U };
|
|
for (const [key, value] of Object.entries(object) as [keyof T, T[keyof T]][]) {
|
|
const t = transform(value, key);
|
|
if (typeof t !== "undefined") {
|
|
result[key] = t;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
export const objectTransform = transformObject;
|
|
|
|
export function objectEach<T extends Record<string, any>, U>(
|
|
object: T,
|
|
each: (value: T[keyof T], key: keyof T) => U,
|
|
): void {
|
|
Object.entries(object).forEach(
|
|
([key, value]) => {
|
|
each(value, key);
|
|
},
|
|
{} as { [K in keyof T]: U },
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Deep merge two objects.
|
|
* @param target
|
|
* @param ...sources
|
|
*/
|
|
export function mergeDeep(target, ...sources) {
|
|
if (!sources.length) return target;
|
|
const source = sources.shift();
|
|
|
|
if (isObject(target) && isObject(source)) {
|
|
for (const key in source) {
|
|
if (isObject(source[key])) {
|
|
if (!target[key]) Object.assign(target, { [key]: {} });
|
|
mergeDeep(target[key], source[key]);
|
|
} else {
|
|
Object.assign(target, { [key]: source[key] });
|
|
}
|
|
}
|
|
}
|
|
|
|
return mergeDeep(target, ...sources);
|
|
}
|
|
|
|
export function getFullPathKeys(obj: any, parentPath: string = ""): string[] {
|
|
let keys: string[] = [];
|
|
|
|
for (const key in obj) {
|
|
const fullPath = parentPath ? `${parentPath}.${key}` : key;
|
|
keys.push(fullPath);
|
|
|
|
if (typeof obj[key] === "object" && obj[key] !== null) {
|
|
keys = keys.concat(getFullPathKeys(obj[key], fullPath));
|
|
}
|
|
}
|
|
|
|
return keys;
|
|
}
|
|
|
|
export function flattenObject(obj: any, parentKey = "", result: any = {}): any {
|
|
for (const key in obj) {
|
|
if (key in obj) {
|
|
const newKey = parentKey ? `${parentKey}.${key}` : key;
|
|
if (typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key])) {
|
|
flattenObject(obj[key], newKey, result);
|
|
} else if (Array.isArray(obj[key])) {
|
|
obj[key].forEach((item, index) => {
|
|
const arrayKey = `${newKey}.${index}`;
|
|
if (typeof item === "object" && item !== null) {
|
|
flattenObject(item, arrayKey, result);
|
|
} else {
|
|
result[arrayKey] = item;
|
|
}
|
|
});
|
|
} else {
|
|
result[newKey] = obj[key];
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function objectDepth(object: object): number {
|
|
let level = 1;
|
|
for (const key in object) {
|
|
if (typeof object[key] === "object") {
|
|
const depth = objectDepth(object[key]) + 1;
|
|
level = Math.max(depth, level);
|
|
}
|
|
}
|
|
return level;
|
|
}
|
|
|
|
export function objectCleanEmpty<Obj extends { [key: string]: any }>(obj: Obj): Obj {
|
|
if (!obj) return obj;
|
|
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
if (value && Array.isArray(value) && value.some((v) => typeof v === "object")) {
|
|
const nested = value.map(objectCleanEmpty);
|
|
if (nested.length > 0) {
|
|
acc[key] = nested;
|
|
}
|
|
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
const nested = objectCleanEmpty(value);
|
|
if (Object.keys(nested).length > 0) {
|
|
acc[key] = nested;
|
|
}
|
|
} else if (value !== "" && value !== null && value !== undefined) {
|
|
acc[key] = value;
|
|
}
|
|
return acc;
|
|
}, {} as any);
|
|
}
|
|
|
|
/**
|
|
* Lodash's merge implementation caused issues in Next.js environments
|
|
* From: https://thescottyjam.github.io/snap.js/#!/nolodash/merge
|
|
* NOTE: This mutates `object`. It also may mutate anything that gets attached to `object` during the merge.
|
|
* @param object
|
|
* @param sources
|
|
*/
|
|
export function mergeObject(object, ...sources) {
|
|
for (const source of sources) {
|
|
for (const [key, value] of Object.entries(source)) {
|
|
if (value === undefined) {
|
|
continue;
|
|
}
|
|
|
|
// These checks are a week attempt at mimicking the various edge-case behaviors
|
|
// that Lodash's `_.merge()` exhibits. Feel free to simplify and
|
|
// remove checks that you don't need.
|
|
if (!isPlainObject(value) && !Array.isArray(value)) {
|
|
object[key] = value;
|
|
} else if (Array.isArray(value) && !Array.isArray(object[key])) {
|
|
object[key] = value;
|
|
} else if (!isObject(object[key])) {
|
|
object[key] = value;
|
|
} else {
|
|
mergeObject(object[key], value);
|
|
}
|
|
}
|
|
}
|
|
|
|
return object;
|
|
}
|
|
|
|
/**
|
|
* Lodash's mergeWith implementation caused issues in Next.js environments
|
|
* From: https://thescottyjam.github.io/snap.js/#!/nolodash/mergeWith
|
|
* NOTE: This mutates `object`. It also may mutate anything that gets attached to `object` during the merge.
|
|
* @param object
|
|
* @param sources
|
|
* @param customizer
|
|
*/
|
|
export function mergeObjectWith(object, source, customizer) {
|
|
for (const [key, value] of Object.entries(source)) {
|
|
const mergedValue = customizer(object[key], value, key, object, source);
|
|
if (mergedValue !== undefined) {
|
|
object[key] = mergedValue;
|
|
continue;
|
|
}
|
|
// Otherwise, fall back to default behavior
|
|
|
|
if (value === undefined) {
|
|
continue;
|
|
}
|
|
|
|
// These checks are a week attempt at mimicking the various edge-case behaviors
|
|
// that Lodash's `_.merge()` exhibits. Feel free to simplify and
|
|
// remove checks that you don't need.
|
|
if (!isPlainObject(value) && !Array.isArray(value)) {
|
|
object[key] = value;
|
|
} else if (Array.isArray(value) && !Array.isArray(object[key])) {
|
|
object[key] = value;
|
|
} else if (!isObject(object[key])) {
|
|
object[key] = value;
|
|
} else {
|
|
mergeObjectWith(object[key], value, customizer);
|
|
}
|
|
}
|
|
|
|
return object;
|
|
}
|
|
|
|
export function isEqual(value1: any, value2: any): boolean {
|
|
// Each type corresponds to a particular comparison algorithm
|
|
const getType = (value: any) => {
|
|
if (value !== Object(value)) return "primitive";
|
|
if (Array.isArray(value)) return "array";
|
|
if (value instanceof Map) return "map";
|
|
if (value != null && [null, Object.prototype].includes(Object.getPrototypeOf(value)))
|
|
return "plainObject";
|
|
if (value instanceof Function) return "function";
|
|
throw new Error(
|
|
`deeply comparing an instance of type ${value1.constructor?.name} is not supported.`,
|
|
);
|
|
};
|
|
|
|
const type = getType(value1);
|
|
if (type !== getType(value2)) {
|
|
return false;
|
|
}
|
|
|
|
if (type === "primitive") {
|
|
return value1 === value2 || (Number.isNaN(value1) && Number.isNaN(value2));
|
|
} else if (type === "array") {
|
|
return (
|
|
value1.length === value2.length &&
|
|
value1.every((iterValue: any, i: number) => isEqual(iterValue, value2[i]))
|
|
);
|
|
} else if (type === "map") {
|
|
// In this particular implementation, map keys are not
|
|
// being deeply compared, only map values.
|
|
return (
|
|
value1.size === value2.size &&
|
|
[...value1].every(([iterKey, iterValue]) => {
|
|
return value2.has(iterKey) && isEqual(iterValue, value2.get(iterKey));
|
|
})
|
|
);
|
|
} else if (type === "plainObject") {
|
|
const value1AsMap = new Map(Object.entries(value1));
|
|
const value2AsMap = new Map(Object.entries(value2));
|
|
return (
|
|
value1AsMap.size === value2AsMap.size &&
|
|
[...value1AsMap].every(([iterKey, iterValue]) => {
|
|
return value2AsMap.has(iterKey) && isEqual(iterValue, value2AsMap.get(iterKey));
|
|
})
|
|
);
|
|
} else if (type === "function") {
|
|
// just check signature
|
|
return value1.toString() === value2.toString();
|
|
} else {
|
|
throw new Error("Unreachable");
|
|
}
|
|
}
|
|
|
|
export function getPath(
|
|
object: object,
|
|
_path: string | (string | number)[],
|
|
defaultValue = undefined,
|
|
): any {
|
|
const path = typeof _path === "string" ? _path.split(/[.\[\]\"]+/).filter((x) => x) : _path;
|
|
|
|
if (path.length === 0) {
|
|
return object;
|
|
}
|
|
|
|
try {
|
|
const [head, ...tail] = path;
|
|
if (!head || !(head in object)) {
|
|
return defaultValue;
|
|
}
|
|
|
|
return getPath(object[head], tail, defaultValue);
|
|
} catch (error) {
|
|
if (typeof defaultValue !== "undefined") {
|
|
return defaultValue;
|
|
}
|
|
|
|
throw new Error(`Invalid path: ${path.join(".")}`);
|
|
}
|
|
}
|
|
|
|
export function objectToJsLiteral(value: object, indent: number = 0, _level: number = 0): string {
|
|
const nl = indent ? "\n" : "";
|
|
const pad = (lvl: number) => (indent ? " ".repeat(indent * lvl) : "");
|
|
const openPad = pad(_level + 1);
|
|
const closePad = pad(_level);
|
|
|
|
// primitives
|
|
if (value === null) return "null";
|
|
if (value === undefined) return "undefined";
|
|
const t = typeof value;
|
|
if (t === "string") return JSON.stringify(value); // handles escapes
|
|
if (t === "number" || t === "boolean") return String(value);
|
|
|
|
// arrays
|
|
if (Array.isArray(value)) {
|
|
const out = value
|
|
.map((v) => objectToJsLiteral(v, indent, _level + 1))
|
|
.join(", " + (indent ? nl + openPad : ""));
|
|
return (
|
|
"[" +
|
|
(indent && value.length ? nl + openPad : "") +
|
|
out +
|
|
(indent && value.length ? nl + closePad : "") +
|
|
"]"
|
|
);
|
|
}
|
|
|
|
// objects
|
|
if (t === "object") {
|
|
const entries = Object.entries(value).map(([k, v]) => {
|
|
const idOk = /^[A-Za-z_$][\w$]*$/.test(k); // valid identifier?
|
|
const key = idOk ? k : JSON.stringify(k); // quote if needed
|
|
return key + ": " + objectToJsLiteral(v, indent, _level + 1);
|
|
});
|
|
const out = entries.join(", " + (indent ? nl + openPad : ""));
|
|
return (
|
|
"{" +
|
|
(indent && entries.length ? nl + openPad : "") +
|
|
out +
|
|
(indent && entries.length ? nl + closePad : "") +
|
|
"}"
|
|
);
|
|
}
|
|
|
|
throw new TypeError(`Unsupported data type: ${t}`);
|
|
}
|
|
|
|
// lodash-es compatible `pick` with perfect type inference
|
|
export function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
|
|
return keys.reduce(
|
|
(acc, key) => {
|
|
if (key in obj) {
|
|
acc[key] = obj[key];
|
|
}
|
|
return acc;
|
|
},
|
|
{} as Pick<T, K>,
|
|
);
|
|
}
|
|
|
|
export function deepFreeze<T extends object>(object: T): T {
|
|
if (Object.isFrozen(object)) return object;
|
|
|
|
// Retrieve the property names defined on object
|
|
const propNames = Reflect.ownKeys(object);
|
|
|
|
// Freeze properties before freezing self
|
|
for (const name of propNames) {
|
|
const value = object[name];
|
|
|
|
if ((value && typeof value === "object") || typeof value === "function") {
|
|
deepFreeze(value);
|
|
}
|
|
}
|
|
|
|
return Object.freeze(object);
|
|
}
|