Security News
JSR Working Group Kicks Off with Ambitious Roadmap and Plans for Open Governance
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
Aggregation utility for objects like in MongoDB
npm install aggio --save # Put latest version in your package.json
import { aggio, createDB, DB } from 'aggio';
type UserWithAddress = { name: string; address?: { street: string } };
describe('DB', () => {
let db: DB<{ name: string }>;
beforeEach(async () => (db = createDB()));
const Antonio = { name: 'Antonio' };
const Rafaela = { name: 'Rafaela' };
const users = [Antonio, Rafaela];
const usersWithAddress: UserWithAddress[] = [
{
name: 'Antonio',
address: {
street: 'Rua',
},
},
{
name: 'Rafaela',
address: {
street: 'Avenida',
},
},
{
name: 'Goat',
},
];
const account = {
username: 'antonio',
firstName: 'antonio',
lastName: 'Silva',
access: [
{
kind: 'email',
value: 'antonio@example.com',
updatedAt: '2022-10-17T02:09:47.948Z',
createdAt: '2022-10-17T02:09:47.948Z',
verified: false,
},
{
kind: 'phone',
value: '+5511999988888',
updatedAt: '2022-10-17T02:09:47.948Z',
createdAt: '2022-10-17T02:09:47.948Z',
verified: false,
},
],
};
describe('aggio', () => {
test('$groupBy accessKind', () => {
const res = aggio(
[account],
[
//
{ $pick: 'access' },
{ $groupBy: 'kind' },
]
);
expect(res).toEqual({
email: [
{
createdAt: '2022-10-17T02:09:47.948Z',
kind: 'email',
updatedAt: '2022-10-17T02:09:47.948Z',
value: 'antonio@example.com',
verified: false,
},
],
phone: [
{
createdAt: '2022-10-17T02:09:47.948Z',
kind: 'phone',
updatedAt: '2022-10-17T02:09:47.948Z',
value: '+5511999988888',
verified: false,
},
],
});
});
test('$pick email', () => {
const res = aggio(
[account],
[
//
{ $pick: 'access' },
{ $matchOne: { kind: 'email' } },
{ $pick: 'value' },
]
);
expect(res).toEqual('antonio@example.com');
});
test('$keyBy accessKind', () => {
const res = aggio(
[account],
[
//
{ $pick: 'access' },
{ $keyBy: { $template: '{kind}#{value}' } },
]
);
expect(res).toEqual({
'email#antonio@example.com': {
createdAt: '2022-10-17T02:09:47.948Z',
kind: 'email',
updatedAt: '2022-10-17T02:09:47.948Z',
value: 'antonio@example.com',
verified: false,
},
'phone#+5511999988888': {
createdAt: '2022-10-17T02:09:47.948Z',
kind: 'phone',
updatedAt: '2022-10-17T02:09:47.948Z',
value: '+5511999988888',
verified: false,
},
});
});
test('$matchOne', () => {
const sut = aggio(users, [{ $matchOne: { name: 'Antonio' } }]);
expect(sut).toMatchObject(Antonio);
});
test('$template', () => {
const sut = aggio<{ name: string; address?: { street: string } }>(
[
{
name: 'Antonio',
address: {
street: 'Rua',
},
},
{
name: 'Rafaela',
address: {
street: 'Avenida',
},
},
],
[
{ $sort: { name: -1 } }, //
{ $template: '{name}#{lowercase(address.street)}' },
{ $first: true },
{ $limit: 10 },
]
);
expect(sut).toEqual('Rafaela#av');
});
test('$keyBy: field.subField', () => {
const sut = aggio<UserWithAddress>(usersWithAddress, [
{ $keyBy: 'address.street' },
{ $sort: { name: -1 } }, //
{ $matchOne: {} },
]);
expect(sut).toEqual({
Avenida: {
address: {
street: 'Avenida',
},
name: 'Rafaela',
},
Rua: {
address: {
street: 'Rua',
},
name: 'Antonio',
},
});
});
test('$groupBy: field.subField', () => {
const sut = aggio<UserWithAddress>(usersWithAddress, [
{ $groupBy: 'address.street' },
{ $sort: { name: -1 } }, //
{ $matchOne: {} },
]);
expect(sut).toEqual({
Avenida: [
{
address: {
street: 'Avenida',
},
name: 'Rafaela',
},
],
Rua: [
{
address: {
street: 'Rua',
},
name: 'Antonio',
},
],
});
});
test('$keyBy:{ $pick }', () => {
const sut = aggio<{ name: string }>(users, [
{ $keyBy: { $pick: 'name' } },
{ $sort: { name: -1 } }, //
{ $matchOne: {} },
]);
expect(sut).toMatchObject({
Antonio,
Rafaela,
});
});
test('$keyBy:{ $pick: `field.subField` }', () => {
const sut = aggio<UserWithAddress>(
[
{
name: 'Antonio',
address: {
street: 'Rua',
},
},
{
name: 'Rafaela',
address: {
street: 'Avenida',
},
},
{
name: 'Goat',
},
],
[
{ $keyBy: { $pick: { $join: ['name', '##', 'address.street'], $stringify: 'snakeCase' } } },
{ $sort: { name: -1 } }, //
{ $matchOne: {} },
]
);
expect(sut).toEqual({
'rafaela#avenida': {
address: {
street: 'Avenida',
},
name: 'Rafaela',
},
'antonio#rua': {
address: {
street: 'Rua',
},
name: 'Antonio',
},
});
});
test('$keyBy:{ $pick: $template }', () => {
const sut = aggio<{ name: string; address?: { street: string } }>(
[
{
name: 'Antonio',
address: {
street: 'Rua',
},
},
{
name: 'Rafaela',
address: {
street: 'Avenida',
},
},
{
name: 'Goat',
},
],
[
{ $match: { 'address.street': { $exists: true } } },
{
$keyBy: {
$pick: { $join: ['address'], $stringify: { $template: `{uppercase(name)}#{lowercase(street)}` } },
},
},
{ $sort: { name: -1 } }, //
{ $matchOne: {} },
]
);
expect(sut).toEqual({
'ANTONIO#rua': {
address: {
street: 'Rua',
},
name: 'Antonio',
},
'RAFAELA#avenida': {
address: {
street: 'Avenida',
},
name: 'Rafaela',
},
});
});
test('$groupBy with $sort and $update', () => {
const sut = aggio<{ name: string; age?: number }>(
[
...users,
{
name: 'Antonio',
age: 55,
},
],
[
{
$update: {
$match: { age: { $exists: false } },
$inc: { age: 20 },
},
},
{ $sort: { name: -1, age: -1 } },
{
$groupBy: { name: { $exists: true } },
},
{ $matchOne: {} },
]
);
expect(sut).toEqual({
Antonio: [
{
age: 55,
name: 'Antonio',
},
{
age: 20,
name: 'Antonio',
},
],
Rafaela: [
{
age: 20,
name: 'Rafaela',
},
],
});
});
test('$pick with $sort and $update', () => {
const sut = aggio<{ name: string; age?: number }>(
[
...users,
{
name: 'Antonio',
age: 55,
},
],
[
{
$update: {
$match: { age: { $exists: false } },
$inc: { age: 20 },
},
},
{ $sort: { name: -1, age: -1 } },
{ $pick: 'name' },
]
);
expect(sut).toEqual('Rafaela');
});
test('$pick $join', () => {
const sut = aggio<{ name: string; age?: number; address?: { street?: string } }>(
[
{
name: 'Antonio',
address: {
street: 'Rua',
},
},
{
name: 'Rafaela',
address: {
street: 'Avenida',
},
},
],
[
{ $match: { 'address.street': { $exists: true } } }, //
{ $sort: { name: -1, age: -1 } }, //
{ $pick: { $join: ['name', '##', 'address.street'] } },
]
);
expect(sut).toEqual('Rafaela#Avenida');
});
test('$pick $joinEach', () => {
const sut = aggio<{ name: string; age?: number; address?: { street?: string } }>(
[
{
name: 'Antonio',
address: {
street: 'Rua',
},
},
{
name: 'Rafaela',
address: {
street: 'Avenida',
},
},
],
[
{ $match: { 'address.street': { $exists: true } } }, //
{ $sort: { name: -1, age: -1 } }, //
{ $pick: { $joinEach: ['name', '##', 'address.street'] } },
]
);
expect(sut).toEqual(['Rafaela#Avenida', 'Antonio#Rua']);
});
test('$pick $each', () => {
const sut = aggio<{ name: string; age?: number; address?: { street?: string } }>(
[
...users,
{
name: 'Antonio',
age: 55,
address: {
street: 'Rua',
},
},
],
[
{
$update: {
$match: { age: { $exists: false } },
$inc: { age: 20 },
},
},
{ $sort: { name: -1, age: -1 } },
{ $pick: { $each: 'name' } },
]
);
expect(sut).toEqual(['Rafaela', 'Antonio', 'Antonio']);
});
test('$match with $sort', () => {
const sut = aggio(users, [{ $match: { name: { $exists: true } } }, { $sort: { name: 1 } }]);
expect(sut).toMatchObject([{ name: 'Antonio' }, { name: 'Rafaela' }]);
});
test('$keyBy with $sort', () => {
const sut = aggio<{ name: string }>(users, [
{ $keyBy: { name: { $exists: true } } },
{ $sort: { name: -1 } }, //
{ $matchOne: {} },
]);
expect(sut).toMatchObject({
Antonio,
Rafaela,
});
});
});
describe('DB methods', () => {
test('db.insert', async () => {
const sut = db.insert(users);
expect(sut).toEqual([
{
_id: expect.any(String),
name: 'Antonio',
},
{
_id: expect.any(String),
name: 'Rafaela',
},
]);
});
test('db.update', async () => {
db.insert(users);
const sut = db.update({ name: /ant/i }, { $inc: { age: 1 } });
expect(sut).toEqual({
numAffected: 1,
updated: expect.objectContaining({
...Antonio,
age: 1,
}),
upsert: false,
});
});
test('db.count', async () => {
db.insert(users);
const sut = db.count({ name: /ant/i });
expect(sut).toEqual(1);
});
test('db.find', async () => {
db.insert(users);
const sut = db.find({ name: /ant/i }).exec();
expect(sut).toEqual([expect.objectContaining(Antonio)]);
});
test('db.findOne', async () => {
db.insert(users);
const sut = db.findOne({ name: /ant/i }).exec();
expect(sut).toMatchObject(Antonio);
});
test('db.remove', async () => {
db.insert(users);
const sut = db.remove({ name: /ant/i });
expect(sut).toEqual(1);
});
});
});
export type AggregationOperatorKeys = typeof aggregationOperatorKeys.enum;
export type Aggregation<TSchema> = AggregationOperator<TSchema>[];
export type AggregationOperatorKey = AggregationOperator<any> extends infer R
? R extends unknown
? keyof R
: never
: never;
export type TemplateDefinition = { $template: string; options?: TemplateOptions };
export type StringifyDefinition = keyof typeof stringCase | TemplateDefinition;
export type PickDefinition<TSchema> = {
$pick:
| DotNotations<TSchema>
| { $join: (DotNotations<TSchema> | `#${string | number}`)[]; $stringify?: StringifyDefinition }
| { $joinEach: (DotNotations<TSchema> | `#${string | number}`)[]; $stringify?: StringifyDefinition }
| { $each: DotNotations<TSchema> | DotNotations<TSchema>[]; $stringify?: StringifyDefinition };
};
export type AggregationOperator<TSchema> =
| { $first: true | 1 }
| { $last: true | 1 }
| { $update: UpdateDefinition<TSchema> & { $match?: Query<TSchema>; $multi?: boolean; $upsert?: boolean } }
| { $matchOne: Query<TSchema> }
| { $limit: number }
| { $sort: Sort }
| { $match: Query<TSchema> }
| { $project: TDocument }
| { $groupBy: GroupByDefinition<TSchema> }
| { $keyBy: KeyByDefinition<TSchema> }
| PickDefinition<TSchema>
| TemplateDefinition;
export type GroupByDefinition<TSchema> =
| {
[Property in Join<NestedPaths<WithId<TSchema>>, '.'> as PropertyType<TSchema, Property> extends number | string
? Property
: never]?: PropertyType<WithId<TSchema>, Property> | Condition<PropertyType<WithId<TSchema>, Property>>;
}
| Join<NestedPaths<WithId<TSchema>>, '.'>;
export type KeyByDefinition<TSchema extends any = { _id?: string }> =
| ((
| {
[Property in Join<NestedPaths<WithId<TSchema>>, '.'> as PropertyType<TSchema, Property> extends
| number
| string
? Property
: never]?: PropertyType<WithId<TSchema>, Property> | Condition<PropertyType<WithId<TSchema>, Property>>;
}
| PickDefinition<TSchema>
) & {
$onMany?: 'first' | 'last' | 'error' | 'warn' | 'list';
})
| Join<NestedPaths<WithId<TSchema>>, '.'>;
// Some Types from The official MongoDB driver for Node.js
export type Query<TSchema = TDocument> =
| Partial<TSchema>
| ({
[Property in Join<NestedPaths<WithId<TSchema>>, '.'>]?: Condition<PropertyType<WithId<TSchema>, Property>>;
} & RootFilterOperators<WithId<TSchema>>);
export type Join<T extends unknown[], D extends string> = T extends []
? ''
: T extends [string | number]
? `${T[0]}`
: T extends [string | number, ...infer R]
? `${T[0]}${D}${Join<R, D>}`
: string;
export interface TDocument {
[key: string]: any;
}
export declare type NestedPaths<Type> = Type extends string | number | boolean | Date | RegExp
? []
: Type extends ReadonlyArray<infer ArrayType>
? [] | [number, ...NestedPaths<ArrayType>]
: Type extends object
? {
[Key in Extract<keyof Type, string>]: Type[Key] extends Type
? [Key]
: Type extends Type[Key]
? [Key]
: Type[Key] extends ReadonlyArray<infer ArrayType>
? Type extends ArrayType
? [Key]
: ArrayType extends Type
? [Key]
: [Key, ...NestedPaths<Type[Key]>] // child is not structured the same as the parent
: [Key, ...NestedPaths<Type[Key]>] | [Key];
}[Extract<keyof Type, string>]
: [];
export type DotNotations<T> = Join<NestedPaths<T>, '.'>;
export type PropertyType<Type, Property extends string> = string extends Property
? unknown
: Property extends keyof Type
? Type[Property]
: Property extends `${number}`
? Type extends ReadonlyArray<infer ArrayType>
? ArrayType
: unknown
: Property extends `${infer Key}.${infer Rest}`
? Key extends `${number}`
? Type extends ReadonlyArray<infer ArrayType>
? PropertyType<ArrayType, Rest>
: unknown
: Key extends keyof Type
? Type[Key] extends Map<string, infer MapType>
? MapType
: PropertyType<Type[Key], Rest>
: unknown
: unknown;
export interface RootFilterOperators<TSchema> extends TDocument {
$and?: Query<TSchema>[];
$or?: Query<TSchema>[];
$not?: Query<TSchema>;
}
export type Condition<T> = AlternativeType<T> | Query<AlternativeType<T>>;
export type AlternativeType<T> = T extends ReadonlyArray<infer U> ? T | RegExpOrString<U> : RegExpOrString<T>;
export type RegExpOrString<T> = T extends string ? RegExp | T : T;
export type EnhancedOmit<TRecordOrUnion, KeyUnion> = string extends keyof TRecordOrUnion
? TRecordOrUnion
: TRecordOrUnion extends any
? Pick<TRecordOrUnion, Exclude<keyof TRecordOrUnion, KeyUnion>>
: never;
export type WithId<TSchema> = EnhancedOmit<TSchema, '_id'> & {
_id: string;
};
export interface RootFilterOperators<TSchema> extends TDocument {
$and?: Query<TSchema>[];
$or?: Query<TSchema>[];
$not?: Query<TSchema>;
}
export declare type UpdateDefinition<TSchema> = {
$inc?: OnlyFieldsOfType<TSchema, NumericType | undefined>;
$min?: MatchKeysAndValues<TSchema>;
$max?: MatchKeysAndValues<TSchema>;
$set?: MatchKeysAndValues<TSchema>;
$unset?: OnlyFieldsOfType<TSchema, any, '' | true | 1>;
$addToSet?: SetFields<TSchema>;
$pop?: OnlyFieldsOfType<TSchema, ReadonlyArray<any>, 1 | -1>;
$pull?: PullOperator<TSchema>;
$push?: PushOperator<TSchema>;
} & TDocument;
export type OnlyFieldsOfType<TSchema, FieldType = any, AssignableType = FieldType> = IfAny<
TSchema[keyof TSchema],
Record<string, FieldType>,
AcceptedFields<TSchema, FieldType, AssignableType> &
NotAcceptedFields<TSchema, FieldType> &
Record<string, AssignableType>
>;
export type AcceptedFields<TSchema, FieldType, AssignableType> = {
readonly [key in KeysOfAType<TSchema, FieldType>]?: AssignableType;
};
type KeysOfAType<TSchema, Type> = {
[key in keyof TSchema]: NonNullable<TSchema[key]> extends Type ? key : never;
}[keyof TSchema];
export declare type NotAcceptedFields<TSchema, FieldType> = {
readonly [key in KeysOfOtherType<TSchema, FieldType>]?: never;
};
export type IfAny<Type, ResultIfAny, ResultIfNotAny> = true extends false & Type ? ResultIfAny : ResultIfNotAny;
export type PullOperator<TSchema> = ({
readonly [key in KeysOfAType<TSchema, ReadonlyArray<any>>]?:
| Partial<Flatten<TSchema[key]>>
| FilterOperations<Flatten<TSchema[key]>>;
} & NotAcceptedFields<TSchema, ReadonlyArray<any>>) & {
readonly [key: string]: Query<any> | any;
};
export type Flatten<Type> = Type extends ReadonlyArray<infer Item> ? Item : Type;
export type FilterOperations<T> = T extends Record<string, any>
? {
[key in keyof T]?: Query<T[key]>;
}
: Query<T>;
export type MatchKeysAndValues<TSchema> = Readonly<
{
[Property in Join<NestedPaths<TSchema>, '.'>]?: PropertyType<TSchema, Property>;
} & {
[Property in `${NestedPathsOfType<TSchema, any[]>}.$${`[${string}]` | ''}`]?: ArrayElement<
PropertyType<TSchema, Property extends `${infer Key}.$${string}` ? Key : never>
>;
} & {
[Property in `${NestedPathsOfType<TSchema, Record<string, any>[]>}.$${`[${string}]` | ''}.${string}`]?: any;
}
>;
export type ArrayElement<Type> = Type extends ReadonlyArray<infer Item> ? Item : never;
export type NestedPathsOfType<TSchema, Type> = KeysOfAType<
{
[Property in Join<NestedPaths<TSchema>, '.'>]: PropertyType<TSchema, Property>;
},
Type
>;
// export type PullAllOperator<TSchema> = ({
// readonly [key in KeysOfAType<TSchema, ReadonlyArray<any>>]?: TSchema[key];
// } & NotAcceptedFields<TSchema, ReadonlyArray<any>>) & {
// readonly [key: string]: ReadonlyArray<any>;
// };
export type PushOperator<TSchema> = ({
readonly [key in KeysOfAType<TSchema, ReadonlyArray<any>>]?:
| Flatten<TSchema[key]>
| ArrayOperator<Array<Flatten<TSchema[key]>>>;
} & NotAcceptedFields<TSchema, ReadonlyArray<any>>) & {
readonly [key: string]: ArrayOperator<any> | any;
};
// @ts-ignore
export type ArrayOperator<Type> = {
// $each?: Array<Flatten<Type>>;
// $slice?: number;
// $position?: number;
// $sort?: Sort; // TODO
};
export type KeysOfOtherType<TSchema, Type> = {
[key in keyof TSchema]: NonNullable<TSchema[key]> extends Type ? never : key;
}[keyof TSchema];
export type NumericType = number;
export type SetFields<TSchema> = ({
readonly [key in KeysOfAType<TSchema, ReadonlyArray<any> | undefined>]?:
| OptionalId<Flatten<TSchema[key]>>
| AddToSetOperators<Array<OptionalId<Flatten<TSchema[key]>>>>;
} & NotAcceptedFields<TSchema, ReadonlyArray<any> | undefined>) & {
readonly [key: string]: AddToSetOperators<any> | any;
};
export type OptionalId<TSchema> = EnhancedOmit<TSchema, '_id'> & {
_id?: InferIdType<TSchema>;
};
// @ts-ignore
export type InferIdType<TSchema> = string;
// @ts-ignore
export type AddToSetOperators<Type> = {
// $each?: Array<Flatten<Type>>;
};
export type Sort =
| string
| Exclude<
SortDirection,
{
$meta: string;
}
>
| string[]
| {
[key: string]: SortDirection;
}
| [string, SortDirection][]
| [string, SortDirection];
export type SortDirection = 1 | -1 | 'asc' | 'desc' | 'ascending' | 'descending';
See License
FAQs
In memory database with subset of MongoDB's API and plenty fast.
The npm package aggio receives a total of 69 weekly downloads. As such, aggio popularity was classified as not popular.
We found that aggio demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
Security News
Research
An advanced npm supply chain attack is leveraging Ethereum smart contracts for decentralized, persistent malware control, evading traditional defenses.
Security News
Research
Attackers are impersonating Sindre Sorhus on npm with a fake 'chalk-node' package containing a malicious backdoor to compromise developers' projects.