Initial code commit
All checks were successful
Publish to Private NPM Registry / publish (push) Successful in 56s

This commit is contained in:
Alan Bridgeman 2026-07-21 12:16:50 -05:00
commit db22585fcb
27 changed files with 2652 additions and 0 deletions

View file

@ -0,0 +1,113 @@
name: Publish to Private NPM Registry
on:
push:
branches:
- main
workflow_dispatch:
jobs:
publish:
runs-on: default
env:
PRIVATE_NPM_REGISTRY: 'https://npm.pkg.bridgemanaccessible.ca'
steps:
# Checkout the repository
- name: Checkout code
uses: actions/checkout@v3
# Set up NPM Auth Token
- name: Set up NPM Auth Token
run: echo "NODE_AUTH_TOKEN=${{ secrets.NPM_TOKEN }}" >> $GITHUB_ENV
# Set up Node.js
- name: Set up Node.js version
uses: actions/setup-node@v3
with:
# Taken from [Repo README](https://github.com/actions/setup-node#readme)
#
# > Version Spec of the version to use in SemVer notation.
# > It also admits such aliases as lts/*, latest, nightly and canary builds
# > Examples: 12.x, 10.15.1, >=10.15.0, lts/Hydrogen, 16-nightly, latest, node
node-version: '20.x'
# Taken from [Repo README](https://github.com/actions/setup-node#readme)
#
# > Optional registry to set up for auth. Will set the registry in a project level .npmrc and .yarnrc file,
# > and set up auth to read in from env.NODE_AUTH_TOKEN.
# > Default: ''
registry-url: ${{ env.PRIVATE_NPM_REGISTRY }}
# Taken from [Repo README](https://github.com/actions/setup-node#readme)
#
# > Optional scope for authenticating against scoped registries.
# > Will fall back to the repository owner when using the GitHub Packages registry (https://npm.pkg.github.com/).
scope: '@BridgemanAccessible'
#- name: Install libatomic
# run: |
# sudo apt-get update
# sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libatomic1
# Transpile/Build the package (TypeScript -> JavaScript)
- name: Transpile/Build the package (TypeScript -> JavaScript)
run: |
# Because Yarn is used locally better to install and use it than have to debug weird inconsistencies
npm install --global yarn
# Install needed dependencies
yarn install
# Build the package
yarn build
- name: Determine Version and Increment (if needed)
id: version_check
run: |
VERSION=$(node -p "require('./package.json').version")
echo "Version: $VERSION"
NAME=$(node -p "require('./package.json').name")
LATEST_VERSION=$(npm show $NAME version --registry ${{ env.PRIVATE_NPM_REGISTRY }} 2>/dev/null || echo "0.0.0")
echo "Latest version: $LATEST_VERSION"
if [ "$LATEST_VERSION" != "$VERSION" ]; then
echo "Manually updated version detected: $VERSION"
else
NEW_VERSION=$(npm version patch --no-git-tag-version)
echo "New version: $NEW_VERSION"
echo "new_version=$NEW_VERSION" >> $GITHUB_ENV
echo "version_changed=true" >> $GITHUB_OUTPUT
fi
- name: Commit Version Change (if needed)
if: steps.version_check.outputs.version_changed == 'true'
run: |
# Update remote URL to use the GITHUB_TOKEN for authentication
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@git.bridgemanaccessible.ca/${{ github.repository }}.git
# Setup git user details for committing the version change
git config user.name "Forgejo Actions"
git config user.email "actions@git.bridgemanaccessible.ca"
# Commit the version change to the `package.json` file
git add package.json
git commit -m "[Forgejo Actions] Update version to ${{ env.new_version }}"
# Push the changes to the repository
git push origin HEAD:main
# Publish to private NPM registry
- name: Publish the package
run: |
# Copy over the files to the build output (`dist`) folder
cp package.json dist/package.json
cp README.md dist/README.md
cp LICENSE dist/LICENSE
# Change directory to the build output (`dist`) folder
cd dist
# Publish the package to the private NPM registry
npm publish --registry ${{ env.PRIVATE_NPM_REGISTRY }}

139
.gitignore vendored Normal file
View file

@ -0,0 +1,139 @@
# 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
.npmrc
# 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/releases
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.yarnrc.yml
# Ignore automation scripts
*.ps1
*.sh

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Bridgeman Accessible
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

23
README.md Normal file
View file

@ -0,0 +1,23 @@
# Polyglot Persistence Utilities Library
This library is intended to provide utilities to help make creating polyglot storage / persistence implementations easier.
## Example Usage
The following shows how to use the library
```ts
import { createPolyglotRepository } from '@BridgemanAccessible/ba-polyglot-storage/repos';
import { Entity } from './entity/Entity.js';
import type { Properties } from './types/Properties.js';
// 1. Generate the custom class using the factory function
const EventRepoClass = createPolyglotRepository<Entity, Properties>(Entity);
// 2. Export the initialization wrapper
export const EntityRepo = async (
db: DB = DB.getDefaultDB(),
nosqlStorage: NoSQLStorage<any, any> = NoSQLStorage.getDefaultNoSQLStorage()
) => {
// Invoke the static method on the generated class
return await EventRepoClass.getRepo(db, nosqlStorage);
};
```

44
package.json Normal file
View file

@ -0,0 +1,44 @@
{
"name": "@BridgemanAccessible/ba-polyglot-storage",
"version": "1.0.0",
"description": "A shared utility library to support Polyglot Storage implementations",
"repository": "https://git.bridgemanaccessible.ca/Bridgeman-Accessible/ba-polyglot-storage",
"author": "Bridgeman Accessible<info@bridgemanaccessible.ca>",
"license": "MIT",
"type": "module",
"exports": {
"./db": {
"types": "./db/index.d.ts",
"default": "./db/index.js"
},
"./decorators": {
"types": "./decorators/index.d.ts",
"default": "./decorators/index.js"
},
"./nosql": {
"types": "./nosql/index.d.ts",
"default": "./nosql/index.js"
},
"./repos": {
"types": "./repos/index.d.ts",
"default": "./repos/index.js"
},
"./types": {
"types": "./types/index.d.ts",
"default": "./types/index.js"
}
},
"scripts": {
"build": "tsgo"
},
"devDependencies": {
"@types/lodash-es": "^4.17.12",
"@types/node": "^26.1.1",
"@typescript/native-preview": "^7.0.0-dev.20260707.2"
},
"dependencies": {
"@BridgemanAccessible/ba-logging": "^1.0.2",
"lodash-es": "^4.18.1",
"typeorm": "^1.1.0"
}
}

11
src/db/IDB.ts Normal file
View file

@ -0,0 +1,11 @@
import type { DataSource } from 'typeorm';
export interface IDB {
/**
* Creates a connection to the database.
*
* @param kwargs The keyword arguments to pass to the TypeORM DataSource. (ex. { logging: true })
* @returns The connection to the database.
*/
createConn(kwargs: object): Promise<DataSource>;
}

3
src/db/index.ts Normal file
View file

@ -0,0 +1,3 @@
import type { IDB } from './IDB.js';
export type { IDB };

View file

@ -0,0 +1,14 @@
import 'reflect-metadata';
/** Metadata key used to store the retrieval function of an adapter */
export const NOSQL_RETRIEVE_META = Symbol('polyglot:nosqlRetrieve');
/**
* Marks a method as the retrieval function.
* Expected signature: (partitionKey: string, rowKey: string) => Promise<TProps | null>
*/
export function NoSQLRetrieve(): MethodDecorator {
return (target, propertyKey) => {
Reflect.defineMetadata(NOSQL_RETRIEVE_META, propertyKey, target.constructor);
};
}

View file

@ -0,0 +1,14 @@
import 'reflect-metadata';
/** Metadata key used to store the save function of an adapter */
export const NOSQL_SAVE_META = Symbol('polyglot:nosqlSave');
/**
* Marks a method as the save function.
* Expected signature: (partitionKey: string, rowKey: string, properties: TProps, entity?: TEntity) => Promise<void>
*/
export function NoSQLSave(): MethodDecorator {
return (target, propertyKey) => {
Reflect.defineMetadata(NOSQL_SAVE_META, propertyKey, target.constructor);
};
}

View file

@ -0,0 +1,11 @@
import 'reflect-metadata';
/** Metadata key used to store the partition key of an entity */
export const PARTITION_KEY_META = Symbol('polyglot:partitionKey');
/** Marks a property as the partition key of the entity. */
export function PartitionKey(): PropertyDecorator {
return (target, propertyKey) => {
Reflect.defineMetadata(PARTITION_KEY_META, propertyKey, target.constructor);
};
}

View file

@ -0,0 +1,11 @@
import 'reflect-metadata';
/** Metadata key used to store the NoSQL adapter of an entity */
export const POLYGLOT_ADAPTER_META = Symbol('polyglot:adapter');
/** Links an Entity to its specific NoSQL Adapter class. */
export function PolyglotAdapter(adapterClass: any): ClassDecorator {
return (target) => {
Reflect.defineMetadata(POLYGLOT_ADAPTER_META, adapterClass, target);
};
}

11
src/decorators/RowKey.ts Normal file
View file

@ -0,0 +1,11 @@
import 'reflect-metadata';
/** Metadata key used to store the row key of an entity */
export const ROW_KEY_META = Symbol('polyglot:rowKey');
/** Marks a property as the row key of the entity. */
export function RowKey(): PropertyDecorator {
return (target, propertyKey) => {
Reflect.defineMetadata(ROW_KEY_META, propertyKey, target.constructor);
};
}

7
src/decorators/index.ts Normal file
View file

@ -0,0 +1,7 @@
import { PartitionKey } from './PartitionKey.js';
import { RowKey } from './RowKey.js';
import { PolyglotAdapter } from './PolyglotAdapter.js';
import { NoSQLRetrieve } from './NoSQLRetrieve.js';
import { NoSQLSave } from './NoSQLSave.js';
export { PartitionKey, RowKey, PolyglotAdapter, NoSQLRetrieve, NoSQLSave };

21
src/nosql/ICredentials.ts Normal file
View file

@ -0,0 +1,21 @@
/** Interface for credentials to access a NoSQL storage implementation */
export interface ICredentials<T extends ICredentials<T>> {
/** Returns the actual instance that can be used to authenticate to the NoSQL storage */
get: () => T;
/** Returns a identifiable type/name for the credential (as it relates to registration with a registry pattern class. Ex. `NoSQLStorageTypes`) */
getType: () => string;
/**
* Returns the credential as a "flat" set of values for NoSQL storage
*
* These **MUST** align with the parameters for the second type parameter for the NoSQLStorage class.
* This is because when we retrieve the credentials from the NoSQL storage we reconstruct the credentials object by:
* 1. Using the `NoSQLStorageTypes.getType(ICredentials.getType())` to get the `NoSQLStorage` object
* 2. Then we call the `NoSQLStorage.createCredentialFromArguments(...)` method with what is essentially what is returned from this method.
*
* This allows us to largely conserve the original parameter types (if `createCredentialFromArguments` is called outside this context)
* But also allows this to work properly in terms of storage and retrieval without enforcing onerous constraints on downstream implementations
*/
toNoSQLEntity: () => [string, string][] | Promise<[string, string][]>;
}

View file

@ -0,0 +1,6 @@
import type { ICredentials } from './ICredentials.js';
import type { ISearchableNoSQLRecord } from './ISearchableNoSQLRecord.js';
export interface INoSQLStorageRegistry {
getAdapter<T, U extends ICredentials<U>>(entityName: string): ISearchableNoSQLRecord<T, U>;
}

View file

@ -0,0 +1,7 @@
import type { DeepPartial } from 'typeorm';
import type { ICredentials } from './ICredentials.js';
export interface ISearchableNoSQLRecord<T, U extends ICredentials<U>> {
find(options: DeepPartial<T>, credentials?: ICredentials<U>): (T & { partitionKey: string, rowKey: string })[] | Promise<(T & { partitionKey: string, rowKey: string })[]>;
}

5
src/nosql/index.ts Normal file
View file

@ -0,0 +1,5 @@
import type { ICredentials } from './ICredentials.js';
import type { ISearchableNoSQLRecord } from './ISearchableNoSQLRecord.js';
import type { INoSQLStorageRegistry } from './INoSQLStorageRegistry.js';
export type { ICredentials, ISearchableNoSQLRecord, INoSQLStorageRegistry };

View file

@ -0,0 +1,451 @@
import type { /*FindOptionsRelationByString,*/ FindOptionsRelations } from 'typeorm';
import logMessage, { LogLevel } from '@BridgemanAccessible/ba-logging';
/** Abstract base class that provides shared polyglot hydration utilities to all extending Repository classes. */
export abstract class BasePolyglotRepo<T> {
// =======================
// Dot Notation Conversion
// =======================
/**
* Converts an array of dot notation strings into a nested object structure.
*
* @example
* Given the input:
*
* ```typescript
* ['a.b.c', 'a.b.d', 'e.f']
* ```
*
* The output will be:
*
* ```typescript
* {
* a: {
* b: {
* c: true,
* d: true
* }
* },
* e: {
* f: true
* }
* }
* ```
*
* @param keys An array of strings in dot notation format.
* @returns A nested object structure representing the dot notation keys.
*/
/*protected*/ dotNotationToObject(keys: string[]): Record<string, any> {
const result: Record<string, any> = {};
for (const key of keys) {
const parts = key.split('.');
if(parts.length === 0) {
throw new Error(`Invalid dot notation key: ${key}`);
}
let current = result;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
if(typeof part === 'undefined') {
throw new Error(`Invalid dot notation key: ${key}`);
}
if (i === parts.length - 1) {
current[part] = true;
}
else {
current[part] = current[part] || {};
current = current[part];
}
}
}
return result;
}
/**
* Converts a nested object structure into an array of dot notation strings.
*
* @example
* Given the input:
* ```typescript
* {
* a: {
* b: {
* c: true,
* d: true
* }
* },
* e: {
* f: true
* }
* }
* ```
*
* The output will be:
* ```typescript
* ['a.b.c', 'a.b.d', 'e.f']
* ```
*
* @param obj The nested object to convert.
* @param existingKeys An optional array of existing dot notation keys to include in the result.
* @returns An array of strings in dot notation format representing the keys of the nested object.
*/
/*protected*/ objectToDotNotation(obj: Record<string, any>, existingKeys: string[] = []): string[] {
// Create a copy of the existing keys to avoid mutating the original array
// Holds the list of dot notation keys that will be returned at the end of the function
let dotNotationKeys = new Set<string>([ ...existingKeys ]);
// Because we have an object, we need to loop over the keys it contains
for(const key in obj) {
// Get the value associated with the key
const keyVal = obj[key as keyof typeof obj];
if(typeof keyVal === 'string') {
// The key-value is a string so ensure it's included in the dot notation array as a string
if(!dotNotationKeys.has(keyVal)) {
dotNotationKeys.add(keyVal);
}
}
else if(typeof keyVal === 'object' && keyVal !== null) {
// Recursively process nested objects
let nestedKeys: string[] = this.objectToDotNotation(keyVal as Record<string, any>);
nestedKeys.map(nestedKey => `${key}.${nestedKey}`).forEach(nestedKey => {
if(!dotNotationKeys.has(nestedKey)) {
dotNotationKeys.add(nestedKey);
}
});
}
}
return Array.from(dotNotationKeys);
}
// ===============================
// Flags / Checks Helper Functions
// ===============================
/** Helper to recursively search for a `properties` key set to true at any depth */
/*private*/ hasDeepPropertiesKey<V>(relations: FindOptionsRelations<V>[keyof V]): boolean {
// Base failure case: Not an object we can traverse
if(typeof relations !== 'object' || relations === null || Array.isArray(relations) || Object.entries(relations).length === 0) {
return false;
}
// Base success case: Found 'properties: true' at the current depth
if('properties' in relations && typeof relations.properties === 'boolean' && relations.properties) {
return true;
}
// Recursive case: Search through all nested object keys
for (const key in relations) {
if (typeof relations[key as keyof typeof relations] === 'object' && relations[key as keyof typeof relations] !== null && !Array.isArray(relations[key as keyof typeof relations]) && Object.entries(relations[key as keyof typeof relations] as object).length > 0) {
const obj = relations[key as keyof typeof relations];
const hasNestedPropertiesKey = this.hasDeepPropertiesKey<typeof obj>(obj as FindOptionsRelations<typeof obj>[keyof typeof obj]);
if (hasNestedPropertiesKey) {
return true;
}
}
}
return false;
}
/** Determines if they want NoSQL/properties reconstruction or not */
/*private*/ wantsProperties(typeName: string, relations?: /*FindOptionsRelationByString |*/ FindOptionsRelations<T>): boolean {
logMessage(`Relations is valid object: ${typeof relations === 'object' && relations !== null}`, LogLevel.DEBUG);
// Verify Some relations are defined and that the relations object isn't just null
if(typeof relations !== 'object' || relations === null) {
logMessage(`Relations is not a valid object, so we return false.`, LogLevel.DEBUG);
return false;
}
// The type's properties relation is explicitly included (either as a string in the array form OR as a key in the object form, noting in object form we accept either the `true` boolean for `purchases` generally OR the `properties` key itself needs to be a boolean set to `true`)
// If the relations is an array (which is an array of strings) and it includes some variation of the type's properties relation as a string (`<Type Name>.*.properties`)
logMessage(
`Relations is array with ${typeName}.*.properties key: ${
Array.isArray(relations) && relations.some(
elem => elem === `${typeName}.properties` || (
typeof elem === 'string' && elem.startsWith(`${typeName}.`) && elem.endsWith('.properties')
)
)
}`,
LogLevel.DEBUG
);
if(Array.isArray(relations) && relations.some(elem => elem === `${typeName}.properties` || (typeof elem === 'string' && elem.startsWith(`${typeName}.`) && elem.endsWith('.properties')))) {
logMessage(`Relations is array with a ${typeName}.*.properties key, so we return true`, LogLevel.DEBUG);
return true;
}
// The relations is an object and it includes the type's properties (or an arbitrary depth of such) relation as a key (either as `properties` or `<Type Name>`) with a value that is either a boolean set to true or an object with a `properties` key that is a boolean set to true
logMessage(`Relations is object with ${typeName} key: ${!Array.isArray(relations) && typeName in relations}`, LogLevel.DEBUG);
if(!Array.isArray(relations) && typeName in relations) {
logMessage(`Relations[${typeName}] is boolean that is true: ${typeof relations[typeName as keyof typeof relations] === 'boolean' && relations[typeName as keyof typeof relations]}`, LogLevel.DEBUG);
// The type's properties relation is included as a key with a boolean value of true (e.g. `<Type Name>: true`)
if(typeof relations[typeName as keyof typeof relations] === 'boolean' && relations[typeName as keyof typeof relations]) {
logMessage(`Relations[${typeName}] is boolean that is true, so we return true`, LogLevel.DEBUG);
return true;
}
logMessage(`Relations[${typeName}] is object with properties key that is boolean true: ${
typeof relations[typeName as keyof typeof relations] === 'object'
&& relations[typeName as keyof typeof relations] !== null
}`, LogLevel.DEBUG);
if(typeof relations[typeName as keyof typeof relations] === 'object' && relations[typeName as keyof typeof relations] !== null) {
logMessage(`Relations[${typeName}] is object, so we check if it has a properties key that is boolean true...`, LogLevel.DEBUG);
const obj = relations[typeName as keyof typeof relations];
const hasNestedPropertiesKey = this.hasDeepPropertiesKey<typeof obj>(obj as FindOptionsRelations<typeof obj>[keyof typeof obj]);
if(hasNestedPropertiesKey) {
logMessage(`Relations[${typeName}] is object with a nested properties key that is boolean true, so we return true`, LogLevel.DEBUG);
return true;
}
}
}
logMessage(`Relations does not include a properties key for ${typeName}, so we return false.`, LogLevel.DEBUG);
return false;
}
/** Gets the `relations` parameter for nested calls */
/*private*/ getReconstructRelations<V>(typeName: string, relations?: /*FindOptionsRelationByString |*/ FindOptionsRelations<T>) {
let passedRelations: /*FindOptionsRelationByString |*/ FindOptionsRelations<V> | undefined = undefined;
if(
typeof relations === 'object'
&& relations != null
&& !Array.isArray(relations)
&& typeName in relations
&& typeof relations[typeName as keyof typeof relations] === 'object'
&& relations[typeName as keyof typeof relations] != null
) {
passedRelations = relations[typeName as keyof typeof relations] as /*FindOptionsRelationByString |*/ FindOptionsRelations<V>;
}
return passedRelations;
}
/**
* Provides Polyglot Persistence hydration support for a given property on a given entity by reconstructing the property using the appropriate repository's reconstruct method if the property is included in the relations parameter of the calling method and the property exists on the entity to be reconstructed.
*
* This is used to recursively reconstruct nested relations that aren't stored in the SQL database but instead are stored in NoSQL or another external system, such as the `properties` of various entities.
* For example, if we want to get the NoSQL properties for the `purchases` within an OrderEntity, we would call this method with:
* - `propName` set to `purchases`,
* - `reconstruct` set to the OrderEntity instance we want to reconstruct,
* - `cls` set to the `PurchasesRepo` object (which has the appropriate `reconstruct` function)
* - and `relations` set to the relations parameter passed to the calling method (so that we can check if the caller actually wants the properties reconstructed before doing the work of reconstructing them).
*
* @param propName The name of the property to potentially reconstruct on the entity
* @param reconstruct The entity instance to potentially reconstruct the property on
* @param cls The Repository instance/class that has the appropriate `reconstruct` method to reconstruct the property if needed
* @param relations The relations parameter passed to the calling method, used to determine if we actually need to reconstruct the property or not (to avoid unnecessary work)
* @param backfillFunc An optional function to backfill the entity before reconstruction (this can be important when there are dependencies such as purchases' dependency on the purchaser information from the order if we're reconstructing from the order)
* @returns The entity instance with the property potentially reconstructed if it was included in the relations parameter and exists on the entity, otherwise returns the entity instance unmodified
*/
/*protected*/ async reconstructProp<V>(
propName: keyof T,
reconstruct: T,
cls: {
reconstruct: (arg: any, relations?: /*FindOptionsRelationByString |*/ FindOptionsRelations<V>) => Promise<V>
},
relations?: /*FindOptionsRelationByString |*/ FindOptionsRelations<T>,
backfillFunc?: (obj: V) => V | Promise<V>
) {
logMessage(`Checking if we need to reconstruct property ${propName as string}...`, LogLevel.DEBUG);
if(this.wantsProperties(propName as string, relations) && typeof reconstruct[propName] !== 'undefined' && reconstruct[propName] !== null) {
logMessage(`Reconstructing property ${propName as string}...`, LogLevel.DEBUG);
if(Array.isArray(reconstruct[propName])) {
reconstruct[propName] = (
await Promise.all(
reconstruct[propName].map(
async (arrItem) => {
if(typeof backfillFunc !== 'undefined') {
arrItem = await backfillFunc(arrItem);
}
return await cls.reconstruct(arrItem, this.getReconstructRelations<V>(propName as string, relations))
}
)
)
).filter(arrItem => arrItem != null) as unknown as T[keyof T];
}
else {
if(typeof backfillFunc !== 'undefined') {
reconstruct[propName] = await backfillFunc(reconstruct[propName] as unknown as V) as unknown as T[keyof T];
}
reconstruct[propName] = await cls.reconstruct(reconstruct[propName], this.getReconstructRelations<V>(propName as string, relations)) as unknown as T[keyof T];
}
if(typeof backfillFunc !== 'undefined') {
logMessage(`Finished reconstructing property ${propName as string}. But am unable to stringify it due to potential circular references.`, LogLevel.DEBUG);
}
else {
logMessage(`Finished reconstructing property ${propName as string}: ${JSON.stringify(reconstruct[propName])}.`, LogLevel.DEBUG);
}
}
logMessage(`Finished property reconstruction (${propName as string}).`, LogLevel.DEBUG);
return reconstruct;
}
/**
* Processes the `relations` parameter passed to the calling method.
* This includes:
* - Filtering out any `properties` keys at any depth (as these are used solely for NoSQL properties and aren't actually valid TypeORM relations that can be passed to the database query methods)
* - While also ensuring that any required relations needed for reconstruction (passed as the `requiredRelations` parameter) are included in the returned relations object/array
* So that when we make database queries for sub-relations we have the appropriate relations included to ensure all necessary data is loaded from the database for proper reconstruction.
*
* @param relations The original relations parameter passed to the calling method, which may include `properties` keys that need to be filtered out and may or may not include the required relations needed for reconstruction
* @param requiredRelations The relations that are required for reconstruction that need to be included in the returned relations object/array if they aren't already included in the original relations parameter (often tied to one of the NoSQL index variables)
* @returns The processed relations object/array with any `properties` keys filtered out and any required relations included, which can then be safely passed to the database query methods without worrying about confusing TypeORM with invalid `properties` keys while also ensuring we have all necessary relations included for reconstruction
*/
/*protected*/ getDBRelations(relations?: /*FindOptionsRelationByString |*/ FindOptionsRelations<T>, requiredRelations?: /*FindOptionsRelationByString |*/ FindOptionsRelations<T>): /*FindOptionsRelationByString |*/ FindOptionsRelations<T> | undefined {
if(typeof relations === 'object' && relations != null) {
// If relations is an object (either the array form or the object form)
if(Array.isArray(relations)) {
// Relations is an array of strings
let processedRelations = [ ...relations ];
// Filter out any strings that are `properties` keys at any depth (ex. `orders.purchases.properties` or `orders.properties`)
processedRelations = processedRelations.filter(rel => rel !== 'properties' && !rel.endsWith('.properties'));
if(typeof requiredRelations !== 'undefined' && requiredRelations != null) {
let usableRequiredRelations: string[];
// Required relations is either an array of strings or an object
if(Array.isArray(requiredRelations)) {
// Required relations is already an array of strings
usableRequiredRelations = requiredRelations;
}
else {
// Because required relations is an object, we need to convert it to an array of strings in dot notation format so we can easily check if each required relation is included in the processed relations array
usableRequiredRelations = this.objectToDotNotation(requiredRelations);
/*for(const key in requiredRelations) {
// Get the value associated with the key
const reqRel = requiredRelations[key as keyof typeof requiredRelations];
if(typeof reqRel === 'string') {
// The required related key-value is string so ensure it's included in the relations array as a string
if(!processedRelations.includes(reqRel)) {
processedRelations.push(reqRel);
}
}
else if(typeof reqRel === 'object' && reqRel !== null) {
for(const nestedKey in reqRel) {
const nestedReqRel = reqRel[nestedKey as keyof typeof reqRel];
if(typeof nestedReqRel === 'string') {
if(!processedRelations.includes(nestedReqRel)) {
processedRelations.push(nestedReqRel);
}
}
}
}
}*/
}
// Loop over each required relation and verify it's included in the relations array
usableRequiredRelations.forEach(reqRel => {
if(!processedRelations.includes(reqRel)) {
processedRelations.push(reqRel);
}
});
}
return processedRelations as unknown as /*FindOptionsRelationByString |*/ FindOptionsRelations<T>;
}
else {
const recursivelyFilterPropertiesKey = (obj: FindOptionsRelations<any>) => {
const filteredObj: FindOptionsRelations<any> = {};
for(const key in obj) {
// Filter out any keys that are `properties` at arbitrary depth
if(key === 'properties') {
continue; // Skip the `properties` key and its value entirely
}
// If the key-value is an object (and not null or an array),
// we need to recursively check it for any nested `properties` keys that need to be filtered out,
// and only include it in the final relations if it has any non-`properties` keys after filtering (to avoid including empty relation objects)
if(typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
const nestedFiltered = recursivelyFilterPropertiesKey(obj[key] as FindOptionsRelations<any>);
// Make sure the object isn't blank after filtering out any nested `properties` keys
if(Object.keys(nestedFiltered).length > 0) {
filteredObj[key] = nestedFiltered;
}
else {
// This is a bit technical but because the original relation was `{ [key]: { properties: true } }`
// We want to change it to `{ [key]: true }` instead of just omitting the key entirely.
// This is because we want to convince TypeORM to load the appropriate SQL values without confusing it (hence this method)
filteredObj[key] = true;
}
// Move to the next key without adding the current key
// Which was already done if relevant.
continue;
}
// Add the key-value pair to the filtered object if it's not a `properties` key (and its value isn't an object that contains a `properties` key, which is handled by the recursive call above)
filteredObj[key] = obj[key];
}
return filteredObj;
};
let processedRelations = { ...relations };
processedRelations = recursivelyFilterPropertiesKey(processedRelations);
if(typeof requiredRelations !== 'undefined' && requiredRelations != null) {
let usableRequiredRelations: Record<string, any>;
if(Array.isArray(requiredRelations)) {
usableRequiredRelations = this.dotNotationToObject(requiredRelations);
}
else {
usableRequiredRelations = requiredRelations;
}
const recursivelyAddRequiredRelations = (obj: FindOptionsRelations<any>, requiredObj: Record<string, any>) => {
for(const key in requiredObj) {
if(!(key in obj)) {
obj[key] = requiredObj[key];
}
// Target is a boolean (true), but we have nested requirements. Upgrade it.
//
// Admittedly, there is a bit of a conflict here in terms of the boolean suggesting all default/required keys of the object.
// But what is being done here is enforcing the required schema atop the existing relations, which in rare complex situation might lead to issues / confusion.
else if (obj[key] === true && typeof requiredObj[key] === 'object' && requiredObj[key] !== null && !Array.isArray(requiredObj[key])) {
obj[key] = { ...requiredObj[key] };
}
else if(typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key]) && typeof requiredObj[key] === 'object' && requiredObj[key] !== null && !Array.isArray(requiredObj[key])) {
recursivelyAddRequiredRelations(obj[key] as FindOptionsRelations<any>, requiredObj[key]);
}
}
};
recursivelyAddRequiredRelations(processedRelations, usableRequiredRelations);
}
return processedRelations;
}
}
// Default to just the required relations if the relations parameter is not an object (or is null/undefined)
return requiredRelations;
}
}

19
src/repos/IRepoClass.ts Normal file
View file

@ -0,0 +1,19 @@
import { Repository } from 'typeorm';
import type { ObjectLiteral, DeepPartial } from 'typeorm';
import type { ISearchableNoSQLRecord } from '../nosql/ISearchableNoSQLRecord.js';
import type { ICredentials } from '../nosql/ICredentials.js';;
export interface IRepoClass<T extends ObjectLiteral, U, V extends ICredentials<V>> {
/** "Reconstruct" the object (this is mostly intended to add back NoSQL properties to a database record to create the "full" object) */
reconstruct(...args: any[]): T | Promise<T>;
/** Save the object to both the database and NoSQL storage appropriately */
save({...data}: DeepPartial<T>, ...args: any[]): T | Promise<T>;
/** Get the TypeORM repository for interacting with the database */
getRepo(): Repository<T>;
/** Get the NoSQL record for interacting with NoSQL storage */
getNoSQL(): ISearchableNoSQLRecord<U, V>;
}

234
src/repos/ManualFilterer.ts Normal file
View file

@ -0,0 +1,234 @@
import { isMatch } from 'lodash-es';
import type { FindOneOptions, FindOptionsWhere, DeepPartial } from 'typeorm';
import { Repository } from 'typeorm';
import logMessage, { LogLevel } from '@BridgemanAccessible/ba-logging';
import type { IRepoClass } from './IRepoClass.js';
export class ManualFilterer<V extends IRepoClass<T, U, any>, T extends { properties: U }, U> {
/**
* Normalize the where clause(s)
*
* Essentially, this just makes it if it's a single item that isn't a list that it becomes a list of one item.
*
* @param where The where clause(s) to normalize.
* @returns The normalized where clause(s).
*/
private normalizeWhereClause(where: FindOptionsWhere<T> | FindOptionsWhere<T>[]) {
// Because the where clause can be a list OR a single item we make it easy
// If it's a single item we make it a list with one item
let whereClause = where;
if(!Array.isArray(whereClause)) {
whereClause = [whereClause];
}
return whereClause;
}
/**
* Returns the appropriate boolean based on a where clause that is a boolean and the object has properties.
*
* @param where The where clause to check (will be some variant of `{ properties: boolean }`).
* @returns The appropriate boolean based on the where clause.
*/
private whereWhereIsBooleanAndHasProperties(where: FindOptionsWhere<T>) {
if(where.properties === true) {
// If it's just those with properties (`{ properties: true }`) than return true
return true;
}
else if(typeof where.properties === 'boolean') {
// Because we're looking for objects WITHOUT properties (`{ properties: false }`
// And this service has properties than return false
return false;
}
}
/**
* A separate function for that handles URL objects comparison correctly.
* It compares URLs by their `href` property.
*
* @param url1 The first URL object.
* @param url2 The second URL object.
* @returns `true` if URLs match, `false` if they don't, `undefined` if not URLs
*/
private urlMatcher(url1: any, url2: any): boolean | undefined {
if (url1 instanceof URL && url2 instanceof URL) {
// You can choose your comparison logic here.
// Simple `href` comparison is a great start.
return url1.href === url2.href;
}
// If one or neither are URLs, return `undefined`.
// This is so that a different comparison method can be used.
return undefined;
}
/**
* Returns the appropriate boolean if the value for the key in the object matches the value for the key in the where clause.
*
* @param key The key on both the object and the where clause to compare.
* @param object The object to check.
* @param where The where clause to check.
* @returns The appropriate boolean based on the key in the object and the where clause.
*/
private propertiesKeySearch(key: string, object: T, where: FindOptionsWhere<T>) {
// If the key in the properties of the object is undefined than return the appropriate boolean based on if the same key is undefined in the where clause
if(typeof object.properties[key as keyof U] === 'undefined') {
if(typeof (where.properties as Partial<U>)[key as keyof U] === 'undefined') {
// If the key in the properties of the object and the key in the where clause are both undefined than return true
return true;
}
else {
// If the key in the properties of the object is undefined but the key in the where clause is defined than return false
return false;
}
}
if(typeof (where.properties as Partial<U>)[key as keyof U] === 'undefined') {
// If the key in the properties of the order is defined but the key in the where clause is undefined than return false
return false;
}
//logMessage(`Key: ${key}, Object: ${JSON.stringify(object.properties[key as keyof U])}, Where: ${JSON.stringify((where.properties as Partial<U>)[key as keyof U])}, Is Match (lodash): ${isMatch(object.properties[key as keyof U] as any, (where.properties as Partial<U>)[key as keyof U] as any)}`, LogLevel.DEBUG);
if(Array.isArray(object.properties[key as keyof U])) {
// If the key in the properties of the object is an array than check if any of the values in the array match the where clause
return (object.properties[key as keyof U] as any[]).some((value) => {
return isMatch(value, (where.properties as Partial<U>)[key as keyof U] as any);
});
}
else {
// Because URLs are objects but need to be compared differently we have a separate check for them
if(object.properties[key as keyof U] instanceof URL || (where.properties as Partial<U>)[key as keyof U] instanceof URL) {
const urlMatch = this.urlMatcher(object.properties[key as keyof U], (where.properties as Partial<U>)[key as keyof U]);
logMessage(`URL Match: ${urlMatch}`, LogLevel.DEBUG);
if (urlMatch !== undefined) {
return urlMatch;
}
}
// If the key in the properties of the object matches the key in the where clause than return true
return isMatch(
object.properties[key as keyof U] as any,
(where.properties as Partial<U>)[key as keyof U] as any
);
}
}
/**
* Returns the appropriate boolean based on a where clause that is a boolean and the object has NO properties.
*
* @param where The where clause to check (will be some variant of `{ properties: boolean }`).
* @returns The appropriate boolean based on the where clause.
*/
private whereWhereIsBooleanAndNoProperties(where: FindOptionsWhere<T>) {
if(typeof where.properties === 'boolean' && where.properties === false) {
// If it's just those WITHOUT properties (`{ properties: false }`)
// And this doesn't have properties then return true
return true;
}
else {
// If the object has no properties and we're looking for something OTHER THAN those without properties than return false
return false;
}
}
/**
* Search function for finding matching objects based on the where clause.
*
* @param object The object to check/search.
* @param where The where clause to filter the results with.
* @returns A boolean that indicates if the object matches the where clause or not.
*/
private allObjectsSearch(object: T, where: FindOptionsWhere<T>) {
// Verify the object has properties
if(typeof object.properties !== 'undefined') {
// If the where clause is just looking for objects with or without properties (`{ properties: boolean }`) than return the appropriate value
if(typeof where.properties === 'boolean') {
return this.whereWhereIsBooleanAndHasProperties(where);
}
// Loop over every key in the where clause properties
return Object.keys(where.properties as Partial<U>).every((key) => this.propertiesKeySearch(key, object, where));
}
else {
// If the object has no properties and the where clause is a boolean than return the appropriate value
return this.whereWhereIsBooleanAndNoProperties(where);
}
}
/**
* Manual filtering for each individual where clause that includes properties.
*
* @param where The where clause to filter the results with.
* @param allObjects All the objects to filter.
* @returns Any matching objects that were found. Or undefined if none were found.
*/
private manualFilterEach(where: FindOptionsWhere<T>, allObjects: T[]) {
// If the properties are set in this specific where clause than we need to filter the results ourselves
if(typeof where.properties !== 'undefined') {
// Find all objects that match the properties
return allObjects.find((object) => this.allObjectsSearch(object, where));
}
}
/**
* Do manual filtering of the objects based on where clauses that include properties (NoSQL values).
*
* @param options The options to find the object with (this includes the where clauses that potentially include properties).
* @param repo The IRepoClass implementation for the given entity (used to query the database, reconstruct the "full" objects, etc...)
* @returns Any matching objects that were found. Or undefined if none were found.
*/
async manualFiltering(options: FindOneOptions<T>, repoCls: V, ...args: any[]) {
// If the where clause is set then we need to check if it's conditional on a property (because the database doesn't know about the NoSQL properties)
if(typeof options.where !== 'undefined') {
// Normalize the where clause so even if it was a single item it's now a list
const whereClause = this.normalizeWhereClause(options.where);
//logMessage(`Normalized Where Clause: ${JSON.stringify(whereClause)}`, LogLevel.DEBUG);
// Verify there is AT LEAST one where clause that is conditional on the properties
if(whereClause.some((where) => typeof where.properties !== 'undefined')) {
// Only get those where clauses that are conditional on the properties
const whereClausesWithProperties = whereClause.filter((where) => typeof where.properties !== 'undefined');
if(whereClausesWithProperties.every(where => typeof where.properties === 'boolean')) {
// If ALL the where clauses with properties are just booleans than we can short-circuit this and just return all or none based on the booleans
const wantsWithProperties = whereClausesWithProperties.some(where => where.properties === true);
const wantsWithoutProperties = whereClausesWithProperties.some(where => where.properties === false);
logMessage(`Wants With Properties: ${wantsWithProperties}, Wants Without Properties: ${wantsWithoutProperties}`, LogLevel.DEBUG);
if(wantsWithProperties && !wantsWithoutProperties) {
// If we want objects WITH properties than get all objects that have properties
const allObjects = await repoCls.getNoSQL().find({} as DeepPartial<U>);
logMessage(`All Matching Objects: ${JSON.stringify(allObjects)}`, LogLevel.DEBUG);
return allObjects;
}
}
// Create a properties filter that combines all the where clauses with properties into a single filter
// Note, using Object.assign here means that if there are conflicting keys the LAST one "wins"
const propertiesFilter = Object.assign({}, ...whereClausesWithProperties.map((where) => where.properties as DeepPartial<U>)) as DeepPartial<U>;
// Use the NoSQL repository to find/filter for us based on the properties filter we created
const allObjects = await repoCls.getNoSQL().find(propertiesFilter);
// Get the "full" objects list (that is, all objects with their NoSQL properties)
//const allObjects = await Promise.all((await repoCls.getRepo().find()).map(async (obj) => repoCls.reconstruct(obj, ...args)));
// Loop over each where clause
//return whereClause.map((where) => this.manualFilterEach(where, allObjects)).filter((object) => typeof object !== 'undefined');
logMessage(`All Matching Objects: ${JSON.stringify(allObjects)}`, LogLevel.DEBUG);
return allObjects;
}
}
}
}

View file

@ -0,0 +1,464 @@
import { Repository, In } from 'typeorm';
import type { DeepPartial, EntityTarget, FindOptionsRelations, FindOneOptions, FindOptionsWhereProperty, FindManyOptions, FindOptionsWhere } from 'typeorm';
import logMessage, { LogLevel } from '@BridgemanAccessible/ba-logging';
import { PARTITION_KEY_META } from '../decorators/PartitionKey.js'
import { ROW_KEY_META } from '../decorators/RowKey.js';
import { POLYGLOT_ADAPTER_META } from '../decorators/PolyglotAdapter.js';
import { NOSQL_RETRIEVE_META } from '../decorators/NoSQLRetrieve.js';
import { NOSQL_SAVE_META } from '../decorators/NoSQLSave.js';
import type { INoSQLStorageRegistry } from '../nosql/INoSQLStorageRegistry.js';
import type { ISearchableNoSQLRecord } from '../nosql/ISearchableNoSQLRecord.js';
import type { IDB } from '../db/IDB.js';
import type { IPolyglotEntity } from '../types/IPolyglotEntity.js';
import { BasePolyglotRepo } from './BasePolyglotRepo.js';
import type { IRepoClass } from './IRepoClass.js';
import { ManualFilterer } from './ManualFilterer.js';
import { PolyglotTransactionManager } from './PolyglotTransactionManager.js';
export function createPolyglotRepository<TEntity extends IPolyglotEntity<TProps>, TProps>(entityTarget: EntityTarget<TEntity>) {
return class extends BasePolyglotRepo<TEntity> implements IRepoClass<TEntity, TProps, any> {
/*private*/ targetConstructor;
/*private*/ partitionProp: string | undefined;
/*private*/ rowProp: string | undefined;
/*private*/ repo: Repository<TEntity>;
/*private*/ nosql: ISearchableNoSQLRecord<TProps, any>;
constructor(repo: Repository<TEntity>, nosqlStorage: INoSQLStorageRegistry) {
super();
this.targetConstructor = typeof entityTarget === 'function' ? entityTarget : (entityTarget as any).constructor;
// Extract Entity Key Metadata
this.partitionProp = Reflect.getMetadata(PARTITION_KEY_META, this.targetConstructor);
this.rowProp = Reflect.getMetadata(ROW_KEY_META, this.targetConstructor);
this.repo = repo;
// Extract Adapter Linkage
const AdapterClass = Reflect.getMetadata(POLYGLOT_ADAPTER_META, this.targetConstructor);
if(!AdapterClass) {
throw new Error(`Polyglot setup failed: ${this.targetConstructor.name} is missing @PolyglotAdapter()`);
}
this.nosql = nosqlStorage.getAdapter<TProps, any>(this.targetConstructor.name);
}
getRepo() {
return this.repo;
}
getNoSQL() {
return this.nosql;
}
// Wrapped helper functions pointing to the dynamically discovered methods
async getEntityProperties(entity: TEntity): Promise<TProps> {
const partitionValue = String(entity[this.partitionProp as keyof TEntity]);
const rowValue = String(entity[this.rowProp as keyof TEntity]);
const adapterInstance = this.getNoSQL();
const AdapterClass = adapterInstance.constructor as any;
// Extract Adapter Method Linkage
const retrieveMethodName = Reflect.getMetadata(NOSQL_RETRIEVE_META, adapterInstance);
if (!retrieveMethodName) {
throw new Error(`Polyglot setup failed: ${AdapterClass.name} must have methods decorated with @NoSQLRetrieve and @NoSQLSave`);
}
const entityProps = await (adapterInstance as Record<string, any>)[retrieveMethodName](partitionValue, rowValue);
return entityProps !== null ? entityProps : (entity.properties as TProps);
}
/**
* Reconstructs the entity with the NoSQL properties from NoSQL storage.
*
* @param entity The entity to "reconstruct".
* @returns The "reconstructed" entity.
*/
async reconstruct(entity: TEntity, relations?: /*FindOptionsRelationByString |*/ FindOptionsRelations<TEntity>): Promise<TEntity> {
logMessage(`Reconstructing Entity: ${JSON.stringify(entity)} with relations: ${JSON.stringify(relations)}`, LogLevel.DEBUG);
// Get the entity's NoSQL properties from NoSQL storage
entity.properties = await this.getEntityProperties(entity);
return entity;
}
async saveProperties(entity: TEntity, properties: TProps): Promise<TProps> {
const partitionValue = String(entity[this.partitionProp as keyof TEntity]);
const rowValue = String(entity[this.rowProp as keyof TEntity]);
const adapterInstance = this.getNoSQL();
const AdapterClass = adapterInstance.constructor as any;
// Extract Adapter Method Linkage
const saveMethodName = Reflect.getMetadata(NOSQL_SAVE_META, adapterInstance);
if (!saveMethodName) {
throw new Error(`Polyglot setup failed: ${AdapterClass.name} must have methods decorated with @NoSQLRetrieve and @NoSQLSave`);
}
await (adapterInstance as Record<string, any>)[saveMethodName](partitionValue, rowValue, properties, entity);
return properties;
}
/**
* Saves the Entity to the database (and particularly, also the properties to NoSQL storage).
*
* @param data The entity's data to save.
* @returns The saved Entity.
*/
async save({...data}: DeepPartial<TEntity>) {
// Create the new entity in the database
const entity = await this.getRepo().save({ ...data });
// If the entity's NoSQL properties were set in the provided arguments than save that to NoSQL storage
if(typeof (data as { properties?: TProps, [key: string]: any }).properties !== 'undefined' && (data as { properties?: TProps, [key: string]: any }).properties !== null) {
entity.properties = await this.saveProperties(entity, (data as { properties?: TProps, [key: string]: any }).properties as TProps);
}
return entity;
}
/**
* Gets the TypeORM repository for the Entity entity and extends it with the custom methods (`getOne` and `findOrCreate`).
*
* @returns The extended TypeORM repository for the Entity entity.
*/
static async getRepo(db: IDB, nosqlStorage: INoSQLStorageRegistry) {
// Setup the database connection options
const connOptions = {
logging: process.env.NODE_ENV !== 'production'
}
// Create the database connection
const dbConn = await db.createConn(connOptions);
// Get the TypeORM repository for the Entity entity
const entityRepo = dbConn.getRepository(entityTarget);
// Create an instance of the EntityRepoCls
const clsInstance = new this(entityRepo, nosqlStorage);
// Return an extended version of the TypeORM repository with the custom methods (`getOne` and `findOrCreate`)
return entityRepo.extend({
/**
* A wrapper around the TypeORM `findOne` method that reconstructs the entity with the NoSQL properties.
*
* @param options The options to find the entity with.
* @returns The entity with the NoSQL properties.
*/
async getOne(options: FindOneOptions<TEntity>): Promise<TEntity | null> {
// Manually filter, to filter by NoSQL properties
const manualFilterResult = await (new ManualFilterer<typeof clsInstance, TEntity, TProps>()).manualFiltering(options, clsInstance);
logMessage(`Manual Filter Result: ${JSON.stringify(manualFilterResult)}`, LogLevel.DEBUG);
// Because we've now done the manual filtering if the properties filtering is set we want to remove it so that the database query can work properly
if(typeof options.where !== 'undefined') {
// Because the where clause can be a list OR a single item we make have to deal with both
if(
Array.isArray(options.where) && // Is a list (OR'd clauses)
options.where.some((where) => typeof where.properties !== 'undefined') && // Has AT LEAST one clause with properties filtering
!options.where.some((clause) => Object.keys(clause).length === 0) // There isn't an empty clause that would return ALL results anyway (which is a weird edge case)
) {
// Because it's an array we do a map where we replace the item(s) that has properties set with a version without the properties set (and potentially with an IN operator for the IDs/app of the manual filtering)
options.where = options.where.map((where) => {
// We only want to modify the where clause(s) that have properties set
if(typeof where.properties !== 'undefined') {
// Because multiple keys under a single where clause object are AND'd together,
// if the manual filtering returned NO results than we know this entire where clause is invalidated.
// And it can't possibly match anything
if(typeof manualFilterResult === 'undefined' || manualFilterResult === null) {
return {};
}
// Make a copy of the original where clause so we can modify it
const newWhereClause = { ...where };
// Remove the properties from the where clause so the database can handle it
delete newWhereClause.properties;
// We want to set the ID to an IN operator with the manually filtered result IDs (so that we cut down our search space)
if(typeof newWhereClause.id === 'undefined') {
// If the ID isn't explicitly given in the where clause,
// than we want to set it to an IN operator with the manually filtered result IDs (so that we cut down our search space)
newWhereClause.id = In(manualFilterResult?.map((res) => res.rowKey)) as FindOptionsWhereProperty<NonNullable<TEntity["id"]>, NonNullable<TEntity["id"]>>;
}
// Use the updated where clause
return newWhereClause;
}
// Otherwise just return the where clause as-is
return where;
});
// Presumably you wouldn't intentionally have an array of all empty objects
// But it ends up that way because of the manual filtering returning no results invalidating one or all of the where clauses
// In such cases we want to short-circuit and return null (because we want to match none NOT all)
if(options.where.every((clause) => Object.keys(clause).length === 0)) {
return null;
}
// Because an empty clause object returns ALL results we just remove it.
// This is because in heterogeneous situations where invalidated clauses are mixed with valid clauses we want to still be able to search using the valid clauses
options.where = options.where.filter((clause) => Object.keys(clause).length > 0);
}
else if(Array.isArray(options.where) && options.where.some((clause) => Object.keys(clause).length === 0)) {
// Because if there exists an empty clause object in the where clause array it would return ALL results anyway
// We just remove the where clause(s) entirely.
// This deals with a very weird edge case that is more likely accident that intentional
delete options.where;
}
else if(!Array.isArray(options.where) && typeof options.where.properties !== 'undefined') {
// Because multiple keys under a single where clause object are AND'd together,
// if the manual filtering returned NO results then we know this (which is the ONLY) entire where clause is invalidated.
// And it can't possibly match anything so we short-circuit and return null
if(typeof manualFilterResult === 'undefined' || manualFilterResult === null) {
return null;
}
// Because it's a single item it's fairly straightforward
const newWhereClause = { ...options.where };
// Remove the properties from the where clause so the database can handle it
delete newWhereClause.properties;
// We want to set the ID to an IN operator with the manually filtered result IDs (so that we cut down our search space)
if(typeof newWhereClause.id === 'undefined') {
// If the ID isn't explicitly given in the where clause,
// than we want to set it to an IN operator with the manually filtered result IDs (so that we cut down our search space)
newWhereClause.id = In(manualFilterResult?.map((res) => res.rowKey)) as FindOptionsWhereProperty<NonNullable<TEntity["id"]>, NonNullable<TEntity["id"]>>;
}
options.where = newWhereClause;
}
}
// Finds the first item given the provided find options.
// If there was no matching item than it returns null.
let entity: TEntity | null;
try {
const dbRelations = clsInstance.getDBRelations(options.relations);
logMessage(`DB Relations: ${JSON.stringify(dbRelations)}`, LogLevel.DEBUG);
entity = await this.findOne({
...Object.fromEntries(
Object.entries(options).filter(([key, value]) => key !== 'relations')
),
relations: dbRelations
} as FindOneOptions<TEntity>);
}
catch(e) {
entity = null;
}
logMessage(`${entityRepo.constructor.name} (From DB): ${JSON.stringify(entity)}`, LogLevel.DEBUG);
// Verify we got a result (the entity exists)
if(entity !== null) {
// Reconstruct the entity with the NoSQL properties
return await clsInstance.reconstruct(entity, options.relations);
}
return null;
},
/**
* A wrapper around the TypeORM find method that reconstructs the entities with their NoSQL properties.
*
* @param options The options to find the entities with.
* @returns The entities with their NoSQL properties.
*/
async getMany(options: FindManyOptions<TEntity>): Promise<TEntity[]> {
// Manually filter, to filter by NoSQL properties
const manualFilterResult = await (new ManualFilterer<typeof clsInstance, TEntity, TProps>()).manualFiltering(options, clsInstance);
logMessage(`Manual Filter Result: ${JSON.stringify(manualFilterResult)}`, LogLevel.DEBUG);
// Because we've now done the manual filtering if the properties filtering is set we want to remove it so that the database query can work properly
if(typeof options.where !== 'undefined') {
// Because the where clause can be a list OR a single item we make have to deal with both
if(
Array.isArray(options.where) && // Is a list (OR'd clauses)
options.where.some((where) => typeof where.properties !== 'undefined') && // Has AT LEAST one clause with properties filtering
!options.where.some((clause) => Object.keys(clause).length === 0) // There isn't an empty clause that would return ALL results anyway (which is a weird edge case)
) {
// Because it's an array we do a map where we replace the item(s) that has properties set with a version without the properties set (and potentially with an IN operator for the IDs/app of the manual filtering)
options.where = options.where.map((where) => {
// We only want to modify the where clause(s) that have properties set
if(typeof where.properties !== 'undefined') {
// Because multiple keys under a single where clause object are AND'd together,
// if the manual filtering returned NO results than we know this entire where clause is invalidated.
// And it can't possibly match anything
if(typeof manualFilterResult === 'undefined' || manualFilterResult === null) {
return {};
}
// Make a copy of the original where clause so we can modify it
const newWhereClause = { ...where };
// Remove the properties from the where clause so the database can handle it
delete newWhereClause.properties;
// We want to set the ID and app to an IN operator with the manually filtered result IDs/apps (so that we cut down our search space)
if(typeof newWhereClause.id === 'undefined') {
// If the ID isn't explicitly given in the where clause,
// than we want to set it to an IN operator with the manually filtered result IDs (so that we cut down our search space)
newWhereClause.id = In(manualFilterResult?.map((res) => res.rowKey)) as FindOptionsWhereProperty<NonNullable<TEntity["id"]>, NonNullable<TEntity["id"]>>;
}
// Use the updated where clause
return newWhereClause;
}
// Otherwise just return the where clause as-is
return where;
});
// Presumably you wouldn't intentionally have an array of all empty objects
// But it ends up that way because of the manual filtering returning no results invalidating one or all of the where clauses
// In such cases we want to short-circuit and return an empty array (because we want to match none NOT all)
if(options.where.every((clause) => Object.keys(clause).length === 0)) {
return [];
}
// Because an empty clause object returns ALL results we just remove it.
// This is because in heterogeneous situations where invalidated clauses are mixed with valid clauses we want to still be able to search using the valid clauses
options.where = options.where.filter((clause) => Object.keys(clause).length > 0);
}
else if(Array.isArray(options.where) && options.where.some((clause) => Object.keys(clause).length === 0)) {
// Because if there exists an empty clause object in the where clause array it would return ALL results anyway
// We just remove the where clause(s) entirely.
// This deals with a very weird edge case that is more likely accident that intentional
delete options.where;
}
else if(!Array.isArray(options.where) && typeof options.where.properties !== 'undefined') {
// Because multiple keys under a single where clause object are AND'd together,
// if the manual filtering returned NO results then we know this (which is the ONLY) entire where clause is invalidated.
// And it can't possibly match anything so we short-circuit and return an empty list
if(typeof manualFilterResult === 'undefined' || manualFilterResult === null) {
return [];
}
// Because it's a single item it's fairly straightforward
const newWhereClause = { ...options.where };
// Remove the properties from the where clause so the database can handle it
delete newWhereClause.properties;
// We want to set the ID and app to an IN operator with the manually filtered result IDs/apps (so that we cut down our search space)
if(typeof newWhereClause.id === 'undefined') {
// If the ID isn't explicitly given in the where clause,
// than we want to set it to an IN operator with the manually filtered result IDs (so that we cut down our search space)
newWhereClause.id = In(manualFilterResult?.map((res) => res.rowKey)) as FindOptionsWhereProperty<NonNullable<TEntity["id"]>, NonNullable<TEntity["id"]>>;
}
options.where = newWhereClause;
}
}
// Finds the first item given the provided find options.
// If there was no matching item than it returns null.
let entities: TEntity[];
try {
const dbRelations = clsInstance.getDBRelations(options.relations);
logMessage(`DB Relations: ${JSON.stringify(dbRelations)}`, LogLevel.DEBUG);
entities = await this.find({
...Object.fromEntries(
Object.entries(options).filter(([key, value]) => key !== 'relations')
),
relations: dbRelations
} as FindManyOptions<TEntity>);
}
catch(e) {
entities = [];
}
logMessage(`${entityRepo.constructor.name} (From DB): ${JSON.stringify(entities)}`, LogLevel.DEBUG);
// Verify we got a result (the entities exists)
if(entities.length > 0) {
// Reconstruct the entity with the NoSQL properties
return await Promise.all(
entities.map(
async (entity) => await clsInstance.reconstruct(entity, options.relations)
)
);
}
return [];
},
/**
* Get or create a entity.
*
* @param entity The entity to find or create.
* @returns The entity (that was found or created)
*/
async findOrCreate({id, ...data}: DeepPartial<TEntity>, transaction?: PolyglotTransactionManager) {
// Because we want to default to creating an entity (in case an ID isn't provided) we set an initial value of null
let entity: TEntity | null = null;
// We can only try to find the entity if we have an ID
if(typeof id !== 'undefined' && id !== null) {
// Find the entity with the provided ID (if it exists)
entity = await this.findOne({ where: { id: id } as FindOptionsWhere<TEntity> });
}
// If the entity doesn't exist than create it. Otherwise, reconstruct it appropriately.
if(typeof entity === 'undefined' || entity === null) {
logMessage(`Creating/Saving ${entityRepo.constructor.name}: ${JSON.stringify({ id, ...data})}`, LogLevel.DEBUG);
if(typeof transaction !== 'undefined') {
// Queue the save operation in the provided transaction
transaction.addTask(
clsInstance.save.bind(clsInstance),
({ id, ...data }: { id?: string, }) => {
logMessage(`Rolling back Entity creation for ID: ${id}`, LogLevel.WARN);
this.delete({ id } as FindOptionsWhere<TEntity>);
// TODO: Delete NoSQL properties as well
},
{ id, ...data }
);
// Because we've queued the save operation in the transaction we need to return a "dummy" item here
entity = entityRepo.create({
id: id,
...data
} as DeepPartial<TEntity>);
}
else {
// Because no transaction was provided we can just save the item directly
entity = await clsInstance.save({ id, ...data} as DeepPartial<TEntity>);
}
}
else {
logMessage(`Found ${entityRepo.constructor.name}: ${JSON.stringify(entity)}`, LogLevel.DEBUG);
entity = await clsInstance.reconstruct(entity);
}
return entity;
},
async reconstruct(entity: TEntity, relations?: /*FindOptionsRelationByString |*/ FindOptionsRelations<TEntity>) {
return await clsInstance.reconstruct(entity, relations);
}
});
}
}
}

View file

@ -0,0 +1,144 @@
import EventEmitter from 'events';
import logMessage, { LogLevel } from '@BridgemanAccessible/ba-logging';
interface TransactionTask {
/** The callable function that "does the task" */
func: (...data: any[]) => any | Promise<any>;
/** The parameters to pass to the task and rollback functions */
params: any[];
/** The callable function that "rolls back the task" if an error occurs (with it's or any subsequent tasks) */
rollback?: (...data: any[]) => any | Promise<any>;
};
/** Events describing the lifecycle of a transaction (as it relates to the transaction manager) */
interface PolyglotTransactionManagerEvents {
/** Event for when a transaction is started */
'transactionStarted': (numTasks: number) => void;
/** Event for when a new task is queued */
'taskQueued': (task: TransactionTask) => void;
/** Event for when a task is started */
'taskStarted': (task: TransactionTask) => void;
/** Event for when a task has completed */
'taskCompleted': (task: TransactionTask) => void;
/** Event for when a task fails */
'taskFailed': (task: TransactionTask, error: any) => void;
/** Event for when rollbacks begin to be triggered (after a task fails) */
'transactionRollingBack': () => void;
/** Event for when an individual task's rollback is started */
'taskRollbackStarted': (rollback: ( (...data: any[]) => any | Promise<any>)) => void;
/** Event for when an individual task's rollback has completed */
'taskRollbackCompleted': (rollback: ( (...data: any[]) => any | Promise<any>)) => void;
/** Event for when an individual task's rollback fails */
'taskRollbackFailed': (rollback: ( (...data: any[]) => any | Promise<any>), error: any) => void;
/** Event for when a transaction has completed (Successfully) */
'transactionCompleted': () => void;
/** Event for when a transaction has failed (and rollbacks have finished) */
'transactionFailed': (error: any) => void;
};
/** A class to handle batched/delayed "transactions" with rollback support for our Polyglot Persistence layer */
export class PolyglotTransactionManager extends EventEmitter {
/** The FIFO queue of tasks to perform */
private tasks: TransactionTask[];
constructor() {
super();
this.tasks = [];
}
/**
* Add a new task to the transaction queue
*
* @param task The task function to execute
* @param rollback The rollback function to execute if the task fails
* @param params The parameters to pass to the task and rollback functions
*/
addTask(task: (...data: any[]) => any | Promise<any>, rollback: (...data: any[]) => any | Promise<any> | undefined, ...params: any[]) {
this.tasks.push({ func: task, params, rollback });
this.emit('taskQueued', this.tasks[this.tasks.length - 1] as TransactionTask);
}
/** Execute all queued tasks in order, rolling back if any task fails */
async transact() {
this.emit('transactionStarted', this.tasks.length);
let index = 0;
try {
for(;index < this.tasks.length; index++) {
const task = this.tasks[index];
this.emit('taskStarted', task as TransactionTask);
await (task as TransactionTask).func(...(task as TransactionTask).params);
this.emit('taskCompleted', task as TransactionTask);
}
this.emit('transactionCompleted');
}
catch (err) {
this.emit('taskFailed', this.tasks[index] as TransactionTask, err);
this.emit('transactionRollingBack');
// Rollback previously executed tasks in reverse order
for(let rollbackIndex = index; rollbackIndex >= 0; rollbackIndex--) {
const task = this.tasks[rollbackIndex] as TransactionTask;
if(typeof task.rollback !== 'undefined') {
try {
this.emit('taskRollbackStarted', task.rollback);
await task.rollback(...task.params);
this.emit('taskRollbackCompleted', task.rollback);
}
catch (rollbackErr) {
logMessage(`Rollback failed for task at index ${rollbackIndex}: ${rollbackErr}`, LogLevel.ERROR);
this.emit('taskRollbackFailed', task.rollback, rollbackErr);
}
}
}
this.emit('transactionFailed', err);
throw err;
}
}
// Override emit to enforce the strict types
emit<U extends keyof PolyglotTransactionManagerEvents>(
event: U, ...args: Parameters<PolyglotTransactionManagerEvents[U]>
): boolean {
return super.emit(event, ...args);
}
// Override addListener for strict types
addListener<U extends keyof PolyglotTransactionManagerEvents>(
event: U, listener: PolyglotTransactionManagerEvents[U]
): this {
return super.addListener(event, listener);
}
// Override on for strict types
on<U extends keyof PolyglotTransactionManagerEvents>(
event: U, listener: PolyglotTransactionManagerEvents[U]
): this {
return super.on(event, listener);
}
}

3
src/repos/index.ts Normal file
View file

@ -0,0 +1,3 @@
import { createPolyglotRepository } from './PolyglotRepository.js';
export { createPolyglotRepository };

View file

@ -0,0 +1,6 @@
import type { ObjectLiteral } from 'typeorm';
export interface IPolyglotEntity<TProps> extends ObjectLiteral {
id?: string;
properties: TProps;
}

3
src/types/index.ts Normal file
View file

@ -0,0 +1,3 @@
import type { IPolyglotEntity } from './IPolyglotEntity.js';
export type { IPolyglotEntity };

44
tsconfig.json Normal file
View file

@ -0,0 +1,44 @@
{
// Visit https://aka.ms/tsconfig to read more about this file
"compilerOptions": {
// File Layout
"rootDir": "./src",
"outDir": "./dist",
// Environment Settings
// See also https://aka.ms/tsconfig/module
"module": "nodenext",
"target": "esnext",
"types": ["node"],
// For nodejs:
// "lib": ["esnext"],
// "types": ["node"],
// and npm install -D @types/node
// Other Outputs
"sourceMap": true,
"declaration": true,
"declarationMap": true,
// Stricter Typechecking Options
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
// Style Options
// "noImplicitReturns": true,
// "noImplicitOverride": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
// "noFallthroughCasesInSwitch": true,
// "noPropertyAccessFromIndexSignature": true,
// Recommended Options
"strict": true,
// "jsx": "react-jsx",
"verbatimModuleSyntax": true,
"isolatedModules": true,
"noUncheckedSideEffectImports": true,
"moduleDetection": "force",
"skipLibCheck": true,
}
}

823
yarn.lock Normal file
View file

@ -0,0 +1,823 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@BridgemanAccessible/ba-logging@^1.0.2":
version "1.0.2"
resolved "https://npm.pkg.bridgemanaccessible.ca/@BridgemanAccessible/ba-logging/-/ba-logging-1.0.2.tgz#70b508b6a6e100b3b7033dd1ec4ea108484b1a6c"
integrity sha512-WL96Nk1hH6MD+CMstkrTo3gUyZA1O6lPBZL4qicdyOEHWPcW0iMAr7IExgNJGDInw/Cj6LjBAB8NtBuSI3stTg==
dependencies:
axios "^1.8.4"
express "^4.21.2"
"@sqltools/formatter@^1.2.5":
version "1.2.5"
resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12"
integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==
"@types/lodash-es@^4.17.12":
version "4.17.12"
resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.12.tgz#65f6d1e5f80539aa7cfbfc962de5def0cf4f341b"
integrity sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==
dependencies:
"@types/lodash" "*"
"@types/lodash@*":
version "4.17.24"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.24.tgz#4ae334fc62c0e915ca8ed8e35dcc6d4eeb29215f"
integrity sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==
"@types/node@^26.1.1":
version "26.1.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-26.1.1.tgz#bad758d601e97d6cf457d204ee76a35fce7bd119"
integrity sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==
dependencies:
undici-types "~8.3.0"
"@typescript/native-preview-darwin-arm64@7.0.0-dev.20260707.2":
version "7.0.0-dev.20260707.2"
resolved "https://registry.yarnpkg.com/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20260707.2.tgz#2a15a65cf2d4701225ec0f94453e126407d57a2e"
integrity sha512-wny2pgKjGbiZtnOIHVa3tXC1UfDqxNEFzyPGmiqybedG8hipG2Nfp0l5UxbaKCjkLacUpH/W5bP2hBOMVhCOzg==
"@typescript/native-preview-darwin-x64@7.0.0-dev.20260707.2":
version "7.0.0-dev.20260707.2"
resolved "https://registry.yarnpkg.com/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20260707.2.tgz#468bc5a3697925d757091d8f2314b2c1d08573a6"
integrity sha512-Afc7M5zOwo+GpfcYwz5Z8HMB2tPVsui7nNIqEuuFB73MPdVqNn/Wmpe4tP4MRri0AtJnJknoHBaTJ/VDAp/Jhw==
"@typescript/native-preview-linux-arm64@7.0.0-dev.20260707.2":
version "7.0.0-dev.20260707.2"
resolved "https://registry.yarnpkg.com/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20260707.2.tgz#702cdd2775ee98692dd368c3602bc1e25da88737"
integrity sha512-iITBa2WjjTI5N9t5l7Z4KoOSI+2zBlhbvFzsD/f8qX8QoKjz/Y4DPyBDgezYi8nkqjjksbgSOJ3/ykzhwrB9cg==
"@typescript/native-preview-linux-arm@7.0.0-dev.20260707.2":
version "7.0.0-dev.20260707.2"
resolved "https://registry.yarnpkg.com/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20260707.2.tgz#602fc487e9fc13d007aa8850470ae317830c8169"
integrity sha512-hJm/UOqZTr9FHmR7uNm8VGX4oKtfWk0Jem0zPeJFNC8ckGUfSBueyiEYMZB+XmRc1aG4x1E46y3CplP4CLHvGQ==
"@typescript/native-preview-linux-x64@7.0.0-dev.20260707.2":
version "7.0.0-dev.20260707.2"
resolved "https://registry.yarnpkg.com/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20260707.2.tgz#ae20d54c927cdf3658d7fa7ad390239e1baf7ce3"
integrity sha512-du0dzi6y97Po5vDNdPJTyyijHCpaS22JLRnKZEJXBDaO9gCIymOv/5QQokFRuOlQm0bWl3i9PF4OVdGP6uAOQA==
"@typescript/native-preview-win32-arm64@7.0.0-dev.20260707.2":
version "7.0.0-dev.20260707.2"
resolved "https://registry.yarnpkg.com/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20260707.2.tgz#649e2bd865d10601b87611f511b4fdb7754cb306"
integrity sha512-SsAwfhyHJ1akgBc+99z4+hwdbHsdWaKB8EwCNIMA6JfSLMeUjffrYvxu+vfMyxVtOVOz7RrRXRoiDiu4a2sCtg==
"@typescript/native-preview-win32-x64@7.0.0-dev.20260707.2":
version "7.0.0-dev.20260707.2"
resolved "https://registry.yarnpkg.com/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20260707.2.tgz#111929ee3876a759e10eb5da7e26ba24d8de423e"
integrity sha512-DL4u27stv0fo71sVhOzHSwE+YMZsbBijVI+kg5dLDLilSH79WFTJ8RSQ46vJrCMt+Gjlv/JOZP1PuLJDfioYeQ==
"@typescript/native-preview@^7.0.0-dev.20260707.2":
version "7.0.0-dev.20260707.2"
resolved "https://registry.yarnpkg.com/@typescript/native-preview/-/native-preview-7.0.0-dev.20260707.2.tgz#1e7c9a6f4706baad83f28676f5f08ca4ca0b9b33"
integrity sha512-oUGp+Rep/hqMhPunyinsALUwSlzHINSxitifPiSaeqoKOKD2OlR9NE3TaPqwsl4NlGslsOSUXI1JotWQzpYCPg==
optionalDependencies:
"@typescript/native-preview-darwin-arm64" "7.0.0-dev.20260707.2"
"@typescript/native-preview-darwin-x64" "7.0.0-dev.20260707.2"
"@typescript/native-preview-linux-arm" "7.0.0-dev.20260707.2"
"@typescript/native-preview-linux-arm64" "7.0.0-dev.20260707.2"
"@typescript/native-preview-linux-x64" "7.0.0-dev.20260707.2"
"@typescript/native-preview-win32-arm64" "7.0.0-dev.20260707.2"
"@typescript/native-preview-win32-x64" "7.0.0-dev.20260707.2"
accepts@~1.3.8:
version "1.3.8"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
dependencies:
mime-types "~2.1.34"
negotiator "0.6.3"
agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
dependencies:
debug "4"
ansi-regex@^6.2.2:
version "6.2.2"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1"
integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==
ansi-styles@^6.2.1:
version "6.2.3"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041"
integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==
ansis@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/ansis/-/ansis-4.3.1.tgz#2815c1ef490adaf0d612ae3a699e90cbcf70a917"
integrity sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
axios@^1.8.4:
version "1.18.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.18.1.tgz#d63f9863bcd8938815c86f9e2abd380189d96dfe"
integrity sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==
dependencies:
follow-redirects "^1.16.0"
form-data "^4.0.5"
https-proxy-agent "^5.0.1"
proxy-from-env "^2.1.0"
body-parser@~1.20.5:
version "1.20.6"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.6.tgz#60c789c78e0992d906da0a29d71ae01d15c1ed76"
integrity sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==
dependencies:
bytes "~3.1.2"
content-type "~1.0.5"
debug "2.6.9"
depd "2.0.0"
destroy "~1.2.0"
http-errors "~2.0.1"
iconv-lite "~0.4.24"
on-finished "~2.4.1"
qs "~6.15.1"
raw-body "~2.5.3"
type-is "~1.6.18"
unpipe "~1.0.0"
bytes@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
dependencies:
es-errors "^1.3.0"
function-bind "^1.1.2"
call-bound@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a"
integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==
dependencies:
call-bind-apply-helpers "^1.0.2"
get-intrinsic "^1.3.0"
cliui@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-9.0.1.tgz#6f7890f386f6f1f79953adc1f78dec46fcc2d291"
integrity sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==
dependencies:
string-width "^7.2.0"
strip-ansi "^7.1.0"
wrap-ansi "^9.0.0"
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
content-disposition@~0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
dependencies:
safe-buffer "5.2.1"
content-type@~1.0.4, content-type@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
cookie-signature@~1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.7.tgz#ab5dd7ab757c54e60f37ef6550f481c426d10454"
integrity sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==
cookie@~0.7.1:
version "0.7.2"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
dayjs@^1.11.21:
version "1.11.21"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.21.tgz#57f87562e62de76f3c704bd2b8d522fc33068eb2"
integrity sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
debug@4, debug@^4.4.3:
version "4.4.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
dependencies:
ms "^2.1.3"
dedent@^1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.2.tgz#34e2264ab538301e27cf7b07bf2369c19baa8dd9"
integrity sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
depd@2.0.0, depd@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
destroy@1.2.0, destroy@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
dunder-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
dependencies:
call-bind-apply-helpers "^1.0.1"
es-errors "^1.3.0"
gopd "^1.2.0"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
emoji-regex@^10.3.0:
version "10.6.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.6.0.tgz#bf3d6e8f7f8fd22a65d9703475bc0147357a6b0d"
integrity sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==
encodeurl@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
es-define-property@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
es-errors@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz#a2d0b373205724dfa525d23b0c3e1b1ca582c99b"
integrity sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==
dependencies:
es-errors "^1.3.0"
es-set-tostringtag@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d"
integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
dependencies:
es-errors "^1.3.0"
get-intrinsic "^1.2.6"
has-tostringtag "^1.0.2"
hasown "^2.0.2"
escalade@^3.1.1:
version "3.2.0"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
etag@~1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
express@^4.21.2:
version "4.22.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.22.2.tgz#c17ae0981e5efc24b22272f0e041c4662503b700"
integrity sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
body-parser "~1.20.5"
content-disposition "~0.5.4"
content-type "~1.0.4"
cookie "~0.7.1"
cookie-signature "~1.0.6"
debug "2.6.9"
depd "2.0.0"
encodeurl "~2.0.0"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "~1.3.1"
fresh "~0.5.2"
http-errors "~2.0.0"
merge-descriptors "1.0.3"
methods "~1.1.2"
on-finished "~2.4.1"
parseurl "~1.3.3"
path-to-regexp "~0.1.12"
proxy-addr "~2.0.7"
qs "~6.15.1"
range-parser "~1.2.1"
safe-buffer "5.2.1"
send "~0.19.0"
serve-static "~1.16.2"
setprototypeof "1.2.0"
statuses "~2.0.1"
type-is "~1.6.18"
utils-merge "1.0.1"
vary "~1.1.2"
fdir@^6.5.0:
version "6.5.0"
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
finalhandler@~1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.2.tgz#1ebc2228fc7673aac4a472c310cc05b77d852b88"
integrity sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==
dependencies:
debug "2.6.9"
encodeurl "~2.0.0"
escape-html "~1.0.3"
on-finished "~2.4.1"
parseurl "~1.3.3"
statuses "~2.0.2"
unpipe "~1.0.0"
follow-redirects@^1.16.0:
version "1.16.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc"
integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==
form-data@^4.0.5:
version "4.0.6"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.6.tgz#28e864e1b786dbebb68db1f452f9635278665827"
integrity sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
es-set-tostringtag "^2.1.0"
hasown "^2.0.4"
mime-types "^2.1.35"
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
fresh@~0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-east-asian-width@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz#216900f91df11a8b2c198c3e1d93d6c035a776b9"
integrity sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==
get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
dependencies:
call-bind-apply-helpers "^1.0.2"
es-define-property "^1.0.1"
es-errors "^1.3.0"
es-object-atoms "^1.1.1"
function-bind "^1.1.2"
get-proto "^1.0.1"
gopd "^1.2.0"
has-symbols "^1.1.0"
hasown "^2.0.2"
math-intrinsics "^1.1.0"
get-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
dependencies:
dunder-proto "^1.0.1"
es-object-atoms "^1.0.0"
gopd@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
has-symbols@^1.0.3, has-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
has-tostringtag@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
dependencies:
has-symbols "^1.0.3"
hasown@^2.0.2, hasown@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003"
integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==
dependencies:
function-bind "^1.1.2"
http-errors@~2.0.0, http-errors@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b"
integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==
dependencies:
depd "~2.0.0"
inherits "~2.0.4"
setprototypeof "~1.2.0"
statuses "~2.0.2"
toidentifier "~1.0.1"
https-proxy-agent@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
dependencies:
agent-base "6"
debug "4"
iconv-lite@~0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
inherits@~2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
ipaddr.js@1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
lodash-es@^4.18.1:
version "4.18.1"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.18.1.tgz#b962eeb80d9d983a900bf342961fb7418ca10b1d"
integrity sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==
math-intrinsics@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
merge-descriptors@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
mime@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
ms@2.1.3, ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
negotiator@0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
object-inspect@^1.13.3, object-inspect@^1.13.4:
version "1.13.4"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213"
integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
on-finished@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
dependencies:
ee-first "1.1.1"
parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
path-to-regexp@~0.1.12:
version "0.1.13"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.13.tgz#9b22ec16bc3ab88d05a0c7e369869421401ab17d"
integrity sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==
picomatch@^4.0.4:
version "4.0.5"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.5.tgz#51ea57a17d86f605f81039595fbc40ed06a55fab"
integrity sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==
proxy-addr@~2.0.7:
version "2.0.7"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
dependencies:
forwarded "0.2.0"
ipaddr.js "1.9.1"
proxy-from-env@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz#a7487568adad577cfaaa7e88c49cab3ab3081aba"
integrity sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==
qs@~6.15.1:
version "6.15.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.3.tgz#76852132a58ed5c7c0ef67e4441b9bb5d6061b3b"
integrity sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==
dependencies:
es-define-property "^1.0.1"
side-channel "^1.1.1"
range-parser@~1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@~2.5.3:
version "2.5.3"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2"
integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==
dependencies:
bytes "~3.1.2"
http-errors "~2.0.1"
iconv-lite "~0.4.24"
unpipe "~1.0.0"
reflect-metadata@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b"
integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==
safe-buffer@5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
send@~0.19.0, send@~0.19.1:
version "0.19.2"
resolved "https://registry.yarnpkg.com/send/-/send-0.19.2.tgz#59bc0da1b4ea7ad42736fd642b1c4294e114ff29"
integrity sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==
dependencies:
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
encodeurl "~2.0.0"
escape-html "~1.0.3"
etag "~1.8.1"
fresh "~0.5.2"
http-errors "~2.0.1"
mime "1.6.0"
ms "2.1.3"
on-finished "~2.4.1"
range-parser "~1.2.1"
statuses "~2.0.2"
serve-static@~1.16.2:
version "1.16.3"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.3.tgz#a97b74d955778583f3862a4f0b841eb4d5d78cf9"
integrity sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==
dependencies:
encodeurl "~2.0.0"
escape-html "~1.0.3"
parseurl "~1.3.3"
send "~0.19.1"
setprototypeof@1.2.0, setprototypeof@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
side-channel-list@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.1.tgz#c2e0b5a14a540aebee3bbc6c3f8666cc9b509127"
integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==
dependencies:
es-errors "^1.3.0"
object-inspect "^1.13.4"
side-channel-map@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42"
integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==
dependencies:
call-bound "^1.0.2"
es-errors "^1.3.0"
get-intrinsic "^1.2.5"
object-inspect "^1.13.3"
side-channel-weakmap@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea"
integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==
dependencies:
call-bound "^1.0.2"
es-errors "^1.3.0"
get-intrinsic "^1.2.5"
object-inspect "^1.13.3"
side-channel-map "^1.0.1"
side-channel@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.1.tgz#ea02c62e05dc4bea67d4442f0fb71ee192f8e0ab"
integrity sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==
dependencies:
es-errors "^1.3.0"
object-inspect "^1.13.4"
side-channel-list "^1.0.1"
side-channel-map "^1.0.1"
side-channel-weakmap "^1.0.2"
sql-highlight@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/sql-highlight/-/sql-highlight-6.1.0.tgz#e34024b4c6eac2744648771edfe3c1f894153743"
integrity sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==
statuses@~2.0.1, statuses@~2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382"
integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==
string-width@^7.0.0, string-width@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc"
integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==
dependencies:
emoji-regex "^10.3.0"
get-east-asian-width "^1.0.0"
strip-ansi "^7.1.0"
strip-ansi@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.2.0.tgz#d22a269522836a627af8d04b5c3fd2c7fa3e32e3"
integrity sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==
dependencies:
ansi-regex "^6.2.2"
tinyglobby@^0.2.17:
version "0.2.17"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.17.tgz#562a9a6c9eb2b3b123d39719f9af5bb44fcd7631"
integrity sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==
dependencies:
fdir "^6.5.0"
picomatch "^4.0.4"
toidentifier@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
tslib@^2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
dependencies:
media-typer "0.3.0"
mime-types "~2.1.24"
typeorm@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-1.1.0.tgz#cdd218532c9096e502e9ec0aa2b355ce09bcc171"
integrity sha512-iX/kvsV42/htCNAQUyElGW87E4Z12neZc6YUFBTEu0BnLiREYDNT5Wfw3wRqZw9vyOEC36zUBAY6Qvywoig06Q==
dependencies:
"@sqltools/formatter" "^1.2.5"
ansis "^4.3.1"
dayjs "^1.11.21"
debug "^4.4.3"
dedent "^1.7.2"
reflect-metadata "^0.2.2"
sql-highlight "^6.1.0"
tinyglobby "^0.2.17"
tslib "^2.8.1"
yargs "^18.0.0"
undici-types@~8.3.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-8.3.0.tgz#44e9fc9f3244648cdea35e4f9bb2d681e9410809"
integrity sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==
unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
wrap-ansi@^9.0.0:
version "9.0.2"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.2.tgz#956832dea9494306e6d209eb871643bb873d7c98"
integrity sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==
dependencies:
ansi-styles "^6.2.1"
string-width "^7.0.0"
strip-ansi "^7.1.0"
y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
yargs-parser@^22.0.0:
version "22.0.0"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-22.0.0.tgz#87b82094051b0567717346ecd00fd14804b357c8"
integrity sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==
yargs@^18.0.0:
version "18.0.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-18.0.0.tgz#6c84259806273a746b09f579087b68a3c2d25bd1"
integrity sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==
dependencies:
cliui "^9.0.1"
escalade "^3.1.1"
get-caller-file "^2.0.5"
string-width "^7.2.0"
y18n "^5.0.5"
yargs-parser "^22.0.0"