refactor: collection setup
This commit is contained in:
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"biome.enabled": true,
|
||||||
"deno.enable": true,
|
"deno.enable": true,
|
||||||
"deno.lint": false,
|
"deno.lint": false,
|
||||||
|
"editor.defaultFormatter": "biomejs.biome",
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit"
|
"source.organizeImports.biome": "explicit",
|
||||||
},
|
"source.fixAll.biome": "explicit"
|
||||||
"[typescript]": {
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
biome.json
Normal file
45
biome.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"formatWithErrors": false,
|
||||||
|
"indentStyle": "space",
|
||||||
|
"indentWidth": 2,
|
||||||
|
"lineEnding": "lf",
|
||||||
|
"lineWidth": 120,
|
||||||
|
"attributePosition": "auto"
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true,
|
||||||
|
"suspicious": {
|
||||||
|
"noConfusingVoidType": "off",
|
||||||
|
"noExplicitAny": "off"
|
||||||
|
},
|
||||||
|
"complexity": {
|
||||||
|
"noBannedTypes": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"assist": {
|
||||||
|
"enabled": true,
|
||||||
|
"actions": {
|
||||||
|
"source": {
|
||||||
|
"organizeImports": {
|
||||||
|
"level": "on",
|
||||||
|
"options": {
|
||||||
|
"groups": [
|
||||||
|
[":BUN:", ":NODE:"],
|
||||||
|
":BLANK_LINE:",
|
||||||
|
":PACKAGE:",
|
||||||
|
":BLANK_LINE:",
|
||||||
|
[":ALIAS:"],
|
||||||
|
":BLANK_LINE:",
|
||||||
|
":PATH:"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
deno.json
19
deno.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@valkyr/db",
|
"name": "@valkyr/db",
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/mod.ts"
|
".": "./src/mod.ts"
|
||||||
},
|
},
|
||||||
@@ -8,12 +8,19 @@
|
|||||||
"exclude": [".github", ".vscode", ".gitignore", "tests"]
|
"exclude": [".github", ".vscode", ".gitignore", "tests"]
|
||||||
},
|
},
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"check": "deno check ./src/mod.ts",
|
"check": {
|
||||||
"lint": "npx eslint -c eslint.config.mjs --fix .",
|
"command": "deno run -A npm:@biomejs/biome check --write ./src",
|
||||||
"fmt": "npx prettier --write .",
|
"description": "Format, lint, and organize imports of the entire project."
|
||||||
"test": "deno test --allow-all",
|
},
|
||||||
|
"test": {
|
||||||
|
"command": "deno test --allow-all",
|
||||||
|
"description": "Runs all defined tests across the entire project."
|
||||||
|
},
|
||||||
"test:publish": "deno publish --dry-run",
|
"test:publish": "deno publish --dry-run",
|
||||||
"ncu": "npx ncu -u -p npm"
|
"ncu": {
|
||||||
|
"command": "npx ncu -u -p npm",
|
||||||
|
"description": "Updates all the dependencies in package.json to their latest versions."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["deno.window", "dom"]
|
"lib": ["deno.window", "dom"]
|
||||||
|
|||||||
24
package.json
24
package.json
@@ -6,24 +6,22 @@
|
|||||||
"bugs": "https://github.com/valkyrjs/db/issues",
|
"bugs": "https://github.com/valkyrjs/db/issues",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bson": "6",
|
"bson": "7.0.0",
|
||||||
"dot-prop": "9",
|
"dot-prop": "10.1.0",
|
||||||
"fast-equals": "5",
|
"fast-equals": "6.0.0",
|
||||||
"idb": "8",
|
"idb": "8.0.3",
|
||||||
"mingo": "6",
|
"mingo": "7.1.1",
|
||||||
"rfdc": "1",
|
"rfdc": "1.4.1",
|
||||||
"rxjs": "7"
|
"rxjs": "7.8.2",
|
||||||
|
"zod": "4.3.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@std/async": "npm:@jsr/std__async@1",
|
"@std/async": "npm:@jsr/std__async@1",
|
||||||
"@std/assert": "npm:@jsr/std__assert@1",
|
"@std/assert": "npm:@jsr/std__assert@1",
|
||||||
"@std/testing": "npm:@jsr/std__testing@1",
|
"@std/testing": "npm:@jsr/std__testing@1",
|
||||||
"@valkyr/testcontainers": "npm:@jsr/valkyr__testcontainers@2",
|
"@valkyr/testcontainers": "npm:@jsr/valkyr__testcontainers@2",
|
||||||
"eslint": "9",
|
"@biomejs/biome": "2.2.4",
|
||||||
"eslint-plugin-simple-import-sort": "12",
|
"expect": "30.2.0",
|
||||||
"expect": "30",
|
"fake-indexeddb": "6.2.5"
|
||||||
"fake-indexeddb": "6",
|
|
||||||
"prettier": "3",
|
|
||||||
"typescript-eslint": "8"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Document, WithId } from "./types.ts";
|
import type { AnyObject } from "mingo/types";
|
||||||
|
|
||||||
export const BroadcastChannel =
|
export const BroadcastChannel =
|
||||||
globalThis.BroadcastChannel ??
|
globalThis.BroadcastChannel ??
|
||||||
@@ -8,16 +8,16 @@ export const BroadcastChannel =
|
|||||||
close() {}
|
close() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StorageBroadcast<TSchema extends Document = Document> =
|
export type StorageBroadcast =
|
||||||
| {
|
| {
|
||||||
name: string;
|
name: string;
|
||||||
type: "insertOne" | "updateOne";
|
type: "insertOne" | "updateOne";
|
||||||
data: WithId<TSchema>;
|
data: AnyObject;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
name: string;
|
name: string;
|
||||||
type: "insertMany" | "updateMany" | "remove";
|
type: "insertMany" | "updateMany" | "remove";
|
||||||
data: WithId<TSchema>[];
|
data: AnyObject[];
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
import { UpdateOptions } from "mingo/core";
|
import type { AnyObject, Criteria } from "mingo/types";
|
||||||
import { UpdateExpression } from "mingo/updater";
|
import type { Modifier } from "mingo/updater";
|
||||||
import { Observable, Subject, Subscription } from "rxjs";
|
import { Observable, type Subject, type Subscription } from "rxjs";
|
||||||
|
import type z from "zod";
|
||||||
|
import type { ZodObject, ZodRawShape } from "zod";
|
||||||
|
|
||||||
import { observe, observeOne } from "./observe/mod.ts";
|
import { observe, observeOne } from "./observe/mod.ts";
|
||||||
import {
|
import type { ChangeEvent, InsertResult, QueryOptions, Storage, UpdateResult } from "./storage/mod.ts";
|
||||||
ChangeEvent,
|
import type { AnyDocument } from "./types.ts";
|
||||||
InsertManyResult,
|
|
||||||
InsertOneResult,
|
|
||||||
Options,
|
|
||||||
RemoveResult,
|
|
||||||
Storage,
|
|
||||||
UpdateResult,
|
|
||||||
} from "./storage/mod.ts";
|
|
||||||
import { Document, Filter, WithId } from "./types.ts";
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
@@ -20,59 +14,100 @@ import { Document, Filter, WithId } from "./types.ts";
|
|||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class Collection<TSchema extends Document = Document> {
|
export class Collection<
|
||||||
constructor(
|
TOptions extends AnyCollectionOptions = AnyCollectionOptions,
|
||||||
readonly name: string,
|
TAdapter extends Storage = TOptions["adapter"],
|
||||||
readonly storage: Storage<TSchema>,
|
TPrimaryKey extends string = TOptions["primaryKey"],
|
||||||
) {}
|
TSchema extends AnyDocument = z.output<ZodObject<TOptions["schema"]>>,
|
||||||
|
> {
|
||||||
|
declare readonly $schema: TSchema;
|
||||||
|
|
||||||
|
constructor(readonly options: TOptions) {}
|
||||||
|
|
||||||
get observable(): {
|
get observable(): {
|
||||||
change: Subject<ChangeEvent<TSchema>>;
|
change: Subject<ChangeEvent>;
|
||||||
flush: Subject<void>;
|
flush: Subject<void>;
|
||||||
} {
|
} {
|
||||||
return this.storage.observable;
|
return this.storage.observable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get storage(): TAdapter {
|
||||||
|
return this.options.adapter;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
| Mutators
|
| Mutators
|
||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async insertOne(document: Partial<WithId<TSchema>>): Promise<InsertOneResult> {
|
async insertOne(values: TSchema | Omit<TSchema, TPrimaryKey>): Promise<InsertResult> {
|
||||||
return this.storage.resolve().then((storage) => storage.insertOne(document));
|
return this.storage.resolve().then((storage) =>
|
||||||
|
storage.insertOne({
|
||||||
|
collection: this.options.name,
|
||||||
|
pkey: this.options.primaryKey,
|
||||||
|
values,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async insertMany(documents: Partial<WithId<TSchema>>[]): Promise<InsertManyResult> {
|
async insertMany(values: (TSchema | Omit<TSchema, TPrimaryKey>)[]): Promise<InsertResult> {
|
||||||
return this.storage.resolve().then((storage) => storage.insertMany(documents));
|
return this.storage.resolve().then((storage) =>
|
||||||
|
storage.insertMany({
|
||||||
|
collection: this.options.name,
|
||||||
|
pkey: this.options.primaryKey,
|
||||||
|
values,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOne(
|
async updateOne(
|
||||||
filter: Filter<WithId<TSchema>>,
|
condition: Criteria<TSchema>,
|
||||||
expr: UpdateExpression,
|
modifier: Modifier<TSchema>,
|
||||||
arrayFilters?: Filter<WithId<TSchema>>[],
|
arrayFilters?: AnyObject[],
|
||||||
condition?: Filter<WithId<TSchema>>,
|
|
||||||
options?: UpdateOptions,
|
|
||||||
): Promise<UpdateResult> {
|
): Promise<UpdateResult> {
|
||||||
return this.storage.resolve().then((storage) => storage.updateOne(filter, expr, arrayFilters, condition, options));
|
return this.storage.resolve().then((storage) =>
|
||||||
|
storage.updateOne({
|
||||||
|
collection: this.options.name,
|
||||||
|
pkey: this.options.primaryKey,
|
||||||
|
condition,
|
||||||
|
modifier,
|
||||||
|
arrayFilters,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMany(
|
async updateMany(
|
||||||
filter: Filter<WithId<TSchema>>,
|
condition: Criteria<TSchema>,
|
||||||
expr: UpdateExpression,
|
modifier: Modifier<TSchema>,
|
||||||
arrayFilters?: Filter<WithId<TSchema>>[],
|
arrayFilters?: AnyObject[],
|
||||||
condition?: Filter<WithId<TSchema>>,
|
|
||||||
options?: UpdateOptions,
|
|
||||||
): Promise<UpdateResult> {
|
): Promise<UpdateResult> {
|
||||||
return this.storage.resolve().then((storage) => storage.updateMany(filter, expr, arrayFilters, condition, options));
|
return this.storage.resolve().then((storage) =>
|
||||||
|
storage.updateMany({
|
||||||
|
collection: this.options.name,
|
||||||
|
pkey: this.options.primaryKey,
|
||||||
|
condition,
|
||||||
|
modifier,
|
||||||
|
arrayFilters,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async replaceOne(filter: Filter<WithId<TSchema>>, document: TSchema): Promise<UpdateResult> {
|
async replaceOne(condition: Criteria<TSchema>, document: TSchema): Promise<UpdateResult> {
|
||||||
return this.storage.resolve().then((storage) => storage.replace(filter, document));
|
return this.storage.resolve().then((storage) =>
|
||||||
|
storage.replace({
|
||||||
|
collection: this.options.name,
|
||||||
|
pkey: this.options.primaryKey,
|
||||||
|
condition,
|
||||||
|
document,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(filter: Filter<WithId<TSchema>>): Promise<RemoveResult> {
|
async remove(condition: Criteria<TSchema>): Promise<number> {
|
||||||
return this.storage.resolve().then((storage) => storage.remove(filter));
|
return this.storage
|
||||||
|
.resolve()
|
||||||
|
.then((storage) => storage.remove({ collection: this.options.name, pkey: this.options.primaryKey, condition }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -82,37 +117,37 @@ export class Collection<TSchema extends Document = Document> {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
subscribe(
|
subscribe(
|
||||||
filter?: Filter<WithId<TSchema>>,
|
condition?: Criteria<TSchema>,
|
||||||
options?: SubscribeToSingle,
|
options?: SubscribeToSingle,
|
||||||
next?: (document: WithId<TSchema> | undefined) => void,
|
next?: (document: TSchema | undefined) => void,
|
||||||
): Subscription;
|
): Subscription;
|
||||||
subscribe(
|
subscribe(
|
||||||
filter?: Filter<WithId<TSchema>>,
|
condition?: Criteria<TSchema>,
|
||||||
options?: SubscribeToMany,
|
options?: SubscribeToMany,
|
||||||
next?: (documents: WithId<TSchema>[], changed: WithId<TSchema>[], type: ChangeEvent["type"]) => void,
|
next?: (documents: TSchema[], changed: TSchema[], type: ChangeEvent["type"]) => void,
|
||||||
): Subscription;
|
): Subscription;
|
||||||
subscribe(filter: Filter<WithId<TSchema>> = {}, options?: Options, next?: (...args: any[]) => void): Subscription {
|
subscribe(condition: Criteria<TSchema> = {}, options?: QueryOptions, next?: (...args: any[]) => void): Subscription {
|
||||||
if (options?.limit === 1) {
|
if (options?.limit === 1) {
|
||||||
return this.#observeOne(filter).subscribe({ next });
|
return this.#observeOne(condition).subscribe({ next });
|
||||||
}
|
}
|
||||||
return this.#observe(filter, options).subscribe({
|
return this.#observe(condition, options).subscribe({
|
||||||
next: (value: [WithId<TSchema>[], WithId<TSchema>[], ChangeEvent["type"]]) => next?.(...value),
|
next: (value: [TSchema[], TSchema[], ChangeEvent["type"]]) => next?.(...value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#observe(
|
#observe(
|
||||||
filter: Filter<WithId<TSchema>> = {},
|
filter: Criteria<TSchema> = {},
|
||||||
options?: Options,
|
options?: QueryOptions,
|
||||||
): Observable<[WithId<TSchema>[], WithId<TSchema>[], ChangeEvent["type"]]> {
|
): Observable<[TSchema[], TSchema[], ChangeEvent["type"]]> {
|
||||||
return new Observable<[WithId<TSchema>[], WithId<TSchema>[], ChangeEvent["type"]]>((subscriber) => {
|
return new Observable<[TSchema[], TSchema[], ChangeEvent["type"]]>((subscriber) => {
|
||||||
return observe(this as any, filter, options, (values, changed, type) =>
|
return observe(this as any, filter, options, (values, changed, type) =>
|
||||||
subscriber.next([values, changed, type] as any),
|
subscriber.next([values, changed, type] as any),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#observeOne(filter: Filter<WithId<TSchema>> = {}): Observable<WithId<TSchema> | undefined> {
|
#observeOne(filter: Criteria<TSchema> = {}): Observable<TSchema | undefined> {
|
||||||
return new Observable<WithId<TSchema> | undefined>((subscriber) => {
|
return new Observable<TSchema | undefined>((subscriber) => {
|
||||||
return observeOne(this as any, filter, (values) => subscriber.next(values as any));
|
return observeOne(this as any, filter, (values) => subscriber.next(values as any));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -126,32 +161,34 @@ export class Collection<TSchema extends Document = Document> {
|
|||||||
/**
|
/**
|
||||||
* Retrieve a record by the document 'id' key.
|
* Retrieve a record by the document 'id' key.
|
||||||
*/
|
*/
|
||||||
async findById(id: string): Promise<WithId<TSchema> | undefined> {
|
async findById(id: string): Promise<TSchema | undefined> {
|
||||||
return this.storage.resolve().then((storage) => storage.findById(id));
|
return this.storage.resolve().then((storage) => storage.findById({ collection: this.options.name, id }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a mingo filter search over the collection data and returns
|
* Performs a mingo filter search over the collection data and returns
|
||||||
* a single document if one was found matching the filter and options.
|
* a single document if one was found matching the filter and options.
|
||||||
*/
|
*/
|
||||||
async findOne(filter: Filter<WithId<TSchema>> = {}, options?: Options): Promise<WithId<TSchema> | undefined> {
|
async findOne(condition: Criteria<TSchema> = {}, options?: QueryOptions): Promise<TSchema | undefined> {
|
||||||
return this.find(filter, options).then(([document]) => document);
|
return this.find(condition, options).then(([document]) => document);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a mingo filter search over the collection data and returns any
|
* Performs a mingo filter search over the collection data and returns any
|
||||||
* documents matching the provided filter and options.
|
* documents matching the provided filter and options.
|
||||||
*/
|
*/
|
||||||
async find(filter: Filter<WithId<TSchema>> = {}, options?: Options): Promise<WithId<TSchema>[]> {
|
async find(condition: Criteria<TSchema> = {}, options?: QueryOptions): Promise<TSchema[]> {
|
||||||
return this.storage.resolve().then((storage) => storage.find(filter, options));
|
return this.storage
|
||||||
|
.resolve()
|
||||||
|
.then((storage) => storage.find({ collection: this.options.name, condition, options }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a mingo filter search over the collection data and returns
|
* Performs a mingo filter search over the collection data and returns
|
||||||
* the count of all documents found matching the filter and options.
|
* the count of all documents found matching the filter and options.
|
||||||
*/
|
*/
|
||||||
async count(filter?: Filter<WithId<TSchema>>): Promise<number> {
|
async count(condition?: Criteria<TSchema>): Promise<number> {
|
||||||
return this.storage.resolve().then((storage) => storage.count(filter));
|
return this.storage.resolve().then((storage) => storage.count({ collection: this.options.name, condition }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -172,18 +209,32 @@ export class Collection<TSchema extends Document = Document> {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export type SubscriptionOptions = {
|
export type SubscriptionOptions = {
|
||||||
sort?: Options["sort"];
|
sort?: QueryOptions["sort"];
|
||||||
skip?: Options["skip"];
|
skip?: QueryOptions["skip"];
|
||||||
range?: Options["range"];
|
range?: QueryOptions["range"];
|
||||||
offset?: Options["offset"];
|
offset?: QueryOptions["offset"];
|
||||||
limit?: Options["limit"];
|
limit?: QueryOptions["limit"];
|
||||||
index?: Options["index"];
|
index?: QueryOptions["index"];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SubscribeToSingle = Options & {
|
export type SubscribeToSingle = QueryOptions & {
|
||||||
limit: 1;
|
limit: 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SubscribeToMany = Options & {
|
export type SubscribeToMany = QueryOptions & {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type AnyCollectionOptions = CollectionOptions<any, any, any, any>;
|
||||||
|
|
||||||
|
type CollectionOptions<
|
||||||
|
TName extends string,
|
||||||
|
TAdapter extends Storage,
|
||||||
|
TPrimaryKey extends string | number | symbol,
|
||||||
|
TSchema extends ZodRawShape,
|
||||||
|
> = {
|
||||||
|
name: TName;
|
||||||
|
adapter: TAdapter;
|
||||||
|
primaryKey: TPrimaryKey;
|
||||||
|
schema: TSchema;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { hashCodeQuery } from "../../hash.ts";
|
import { hashCodeQuery } from "../../hash.ts";
|
||||||
import { Options } from "../../storage/mod.ts";
|
import type { QueryOptions } from "../../storage/mod.ts";
|
||||||
import type { Document, Filter, WithId } from "../../types.ts";
|
import type { Document, Filter } from "../../types.ts";
|
||||||
|
|
||||||
export class IndexedDbCache<TSchema extends Document = Document> {
|
export class IndexedDBCache<TSchema extends Document = Document> {
|
||||||
readonly #cache = new Map<number, string[]>();
|
readonly #cache = new Map<number, string[]>();
|
||||||
readonly #documents = new Map<string, WithId<TSchema>>();
|
readonly #documents = new Map<string, TSchema>();
|
||||||
|
|
||||||
hash(filter: Filter<WithId<TSchema>>, options: Options): number {
|
hash(filter: Filter<TSchema>, options: QueryOptions): number {
|
||||||
return hashCodeQuery(filter, options);
|
return hashCodeQuery(filter, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
set(hashCode: number, documents: WithId<TSchema>[]) {
|
set(hashCode: number, documents: TSchema[]) {
|
||||||
this.#cache.set(
|
this.#cache.set(
|
||||||
hashCode,
|
hashCode,
|
||||||
documents.map((document) => document.id),
|
documents.map((document) => document.id),
|
||||||
@@ -20,10 +20,10 @@ export class IndexedDbCache<TSchema extends Document = Document> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get(hashCode: number): WithId<TSchema>[] | undefined {
|
get(hashCode: number): TSchema[] | undefined {
|
||||||
const ids = this.#cache.get(hashCode);
|
const ids = this.#cache.get(hashCode);
|
||||||
if (ids !== undefined) {
|
if (ids !== undefined) {
|
||||||
return ids.map((id) => this.#documents.get(id) as WithId<TSchema>);
|
return ids.map((id) => this.#documents.get(id) as TSchema);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,32 @@
|
|||||||
import { IDBPDatabase, openDB } from "idb";
|
import { type IDBPDatabase, openDB } from "idb";
|
||||||
|
|
||||||
import { Collection } from "../../collection.ts";
|
import { Collection } from "../../collection.ts";
|
||||||
import { DBLogger } from "../../logger.ts";
|
import type { DBLogger } from "../../logger.ts";
|
||||||
import { Document } from "../../types.ts";
|
import type { Document } from "../../types.ts";
|
||||||
import { Registrars } from "../registrars.ts";
|
import type { Registrars } from "../registrars.ts";
|
||||||
import { IndexedDbStorage } from "./storage.ts";
|
import { IndexedDBStorage } from "./storage.ts";
|
||||||
|
|
||||||
export class IndexedDatabase<TCollections extends StringRecord<Document>> {
|
export class IndexedDB<TCollections extends StringRecord<Document>> {
|
||||||
readonly #collections = new Map<keyof TCollections, Collection<TCollections[keyof TCollections]>>();
|
readonly #collections = new Map<keyof TCollections, Collection<TCollections[keyof TCollections]>>();
|
||||||
readonly #db: Promise<IDBPDatabase<unknown>>;
|
readonly #db: Promise<IDBPDatabase<unknown>>;
|
||||||
|
|
||||||
constructor(readonly options: Options) {
|
constructor(readonly options: Options) {
|
||||||
this.#db = openDB(options.name, options.version ?? 1, {
|
this.#db = openDB(options.name, options.version ?? 1, {
|
||||||
upgrade: (db: IDBPDatabase) => {
|
upgrade: (db: IDBPDatabase) => {
|
||||||
for (const { name, indexes = [] } of options.registrars) {
|
for (const { name, primaryKey = "id", indexes = [] } of options.registrars) {
|
||||||
const store = db.createObjectStore(name as string, { keyPath: "id" });
|
const store = db.createObjectStore(name as string, { keyPath: primaryKey });
|
||||||
store.createIndex("id", "id", { unique: true });
|
store.createIndex(primaryKey, primaryKey, { unique: true });
|
||||||
for (const [keyPath, options] of indexes) {
|
for (const [keyPath, options] of indexes) {
|
||||||
store.createIndex(keyPath, keyPath, options);
|
store.createIndex(keyPath, keyPath, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
for (const { name } of options.registrars) {
|
for (const { name, primaryKey = "id" } of options.registrars) {
|
||||||
this.#collections.set(name, new Collection(name, new IndexedDbStorage(name, this.#db, options.log ?? log)));
|
this.#collections.set(
|
||||||
|
name,
|
||||||
|
new Collection(name, new IndexedDBStorage(name, primaryKey, this.#db, options.log ?? log)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,9 +36,7 @@ export class IndexedDatabase<TCollections extends StringRecord<Document>> {
|
|||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
collection<TSchema extends TCollections[Name], Name extends keyof TCollections = keyof TCollections>(
|
collection<Name extends keyof TCollections = keyof TCollections>(name: Name) {
|
||||||
name: Name,
|
|
||||||
): Collection<TSchema> {
|
|
||||||
const collection = this.#collections.get(name);
|
const collection = this.#collections.get(name);
|
||||||
if (collection === undefined) {
|
if (collection === undefined) {
|
||||||
throw new Error(`Collection '${name as string}' not found`);
|
throw new Error(`Collection '${name as string}' not found`);
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { IDBPDatabase } from "idb";
|
import type { IDBPDatabase } from "idb";
|
||||||
import { createUpdater, Query } from "mingo";
|
import { Query, update } from "mingo";
|
||||||
import { UpdateOptions } from "mingo/core";
|
import type { Criteria, Options } from "mingo/types";
|
||||||
import { UpdateExpression } from "mingo/updater";
|
import type { CloneMode, Modifier } from "mingo/updater";
|
||||||
|
|
||||||
import { DBLogger, InsertLog, QueryLog, RemoveLog, ReplaceLog, UpdateLog } from "../../logger.ts";
|
import { type DBLogger, InsertLog, QueryLog, RemoveLog, ReplaceLog, UpdateLog } from "../../logger.ts";
|
||||||
|
import { getDocumentWithPrimaryKey } from "../../primary-key.ts";
|
||||||
import { DuplicateDocumentError } from "../../storage/errors.ts";
|
import { DuplicateDocumentError } from "../../storage/errors.ts";
|
||||||
import {
|
import {
|
||||||
getInsertManyResult,
|
getInsertManyResult,
|
||||||
@@ -13,17 +14,18 @@ import {
|
|||||||
} from "../../storage/operators/insert.ts";
|
} from "../../storage/operators/insert.ts";
|
||||||
import { RemoveResult } from "../../storage/operators/remove.ts";
|
import { RemoveResult } from "../../storage/operators/remove.ts";
|
||||||
import { UpdateResult } from "../../storage/operators/update.ts";
|
import { UpdateResult } from "../../storage/operators/update.ts";
|
||||||
import { addOptions, Index, Options, Storage } from "../../storage/storage.ts";
|
import { addOptions, type Index, type QueryOptions, Storage } from "../../storage/storage.ts";
|
||||||
import type { Document, Filter, WithId } from "../../types.ts";
|
import type { Document, Filter } from "../../types.ts";
|
||||||
import { IndexedDbCache } from "./cache.ts";
|
import { IndexedDBCache } from "./cache.ts";
|
||||||
|
|
||||||
const OBJECT_PROTOTYPE = Object.getPrototypeOf({});
|
const OBJECT_PROTOTYPE = Object.getPrototypeOf({});
|
||||||
const OBJECT_TAG = "[object Object]";
|
const OBJECT_TAG = "[object Object]";
|
||||||
|
|
||||||
const update = createUpdater({ cloneMode: "deep" });
|
export class IndexedDBStorage<TPrimaryKey extends string, TSchema extends Document = Document> extends Storage<
|
||||||
|
TPrimaryKey,
|
||||||
export class IndexedDbStorage<TSchema extends Document = Document> extends Storage<TSchema> {
|
TSchema
|
||||||
readonly #cache = new IndexedDbCache<TSchema>();
|
> {
|
||||||
|
readonly #cache = new IndexedDBCache<TSchema>();
|
||||||
|
|
||||||
readonly #promise: Promise<IDBPDatabase>;
|
readonly #promise: Promise<IDBPDatabase>;
|
||||||
|
|
||||||
@@ -31,10 +33,11 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
name: string,
|
name: string,
|
||||||
|
primaryKey: TPrimaryKey,
|
||||||
promise: Promise<IDBPDatabase>,
|
promise: Promise<IDBPDatabase>,
|
||||||
readonly log: DBLogger,
|
readonly log: DBLogger,
|
||||||
) {
|
) {
|
||||||
super(name);
|
super(name, primaryKey);
|
||||||
this.#promise = promise;
|
this.#promise = promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,11 +69,12 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async insertOne(data: Partial<TSchema>): Promise<InsertOneResult> {
|
async insertOne(values: TSchema | Omit<TSchema, TPrimaryKey>): Promise<InsertOneResult> {
|
||||||
const logger = new InsertLog(this.name);
|
const logger = new InsertLog(this.name);
|
||||||
|
|
||||||
const document = { ...data, id: data.id ?? crypto.randomUUID() } as any;
|
const document = getDocumentWithPrimaryKey(this.primaryKey, values);
|
||||||
if (await this.has(document.id)) {
|
|
||||||
|
if (await this.has(document[this.primaryKey])) {
|
||||||
throw new DuplicateDocumentError(document, this as any);
|
throw new DuplicateDocumentError(document, this as any);
|
||||||
}
|
}
|
||||||
await this.db.transaction(this.name, "readwrite", { durability: "relaxed" }).store.add(document);
|
await this.db.transaction(this.name, "readwrite", { durability: "relaxed" }).store.add(document);
|
||||||
@@ -83,15 +87,15 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
return getInsertOneResult(document);
|
return getInsertOneResult(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
async insertMany(data: Partial<TSchema>[]): Promise<InsertManyResult> {
|
async insertMany(values: (TSchema | Omit<TSchema, TPrimaryKey>)[]): Promise<InsertManyResult> {
|
||||||
const logger = new InsertLog(this.name);
|
const logger = new InsertLog(this.name);
|
||||||
|
|
||||||
const documents: WithId<TSchema>[] = [];
|
const documents: TSchema[] = [];
|
||||||
|
|
||||||
const tx = this.db.transaction(this.name, "readwrite", { durability: "relaxed" });
|
const tx = this.db.transaction(this.name, "readwrite", { durability: "relaxed" });
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
data.map((data) => {
|
values.map((values) => {
|
||||||
const document = { ...data, id: data.id ?? crypto.randomUUID() } as WithId<TSchema>;
|
const document = getDocumentWithPrimaryKey(this.primaryKey, values);
|
||||||
documents.push(document);
|
documents.push(document);
|
||||||
return tx.store.add(document);
|
return tx.store.add(document);
|
||||||
}),
|
}),
|
||||||
@@ -112,11 +116,11 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async findById(id: string): Promise<WithId<TSchema> | undefined> {
|
async findById(id: string): Promise<TSchema | undefined> {
|
||||||
return this.db.getFromIndex(this.name, "id", id);
|
return this.db.getFromIndex(this.name, "id", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async find(filter: Filter<WithId<TSchema>>, options: Options = {}): Promise<WithId<TSchema>[]> {
|
async find(filter: Filter<TSchema>, options: QueryOptions = {}): Promise<TSchema[]> {
|
||||||
const logger = new QueryLog(this.name, { filter, options });
|
const logger = new QueryLog(this.name, { filter, options });
|
||||||
|
|
||||||
const hashCode = this.#cache.hash(filter, options);
|
const hashCode = this.#cache.hash(filter, options);
|
||||||
@@ -132,7 +136,7 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
cursor = addOptions(cursor, options);
|
cursor = addOptions(cursor, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
const documents = cursor.all() as WithId<TSchema>[];
|
const documents = cursor.all() as TSchema[];
|
||||||
this.#cache.set(this.#cache.hash(filter, options), documents);
|
this.#cache.set(this.#cache.hash(filter, options), documents);
|
||||||
|
|
||||||
this.log(logger.result());
|
this.log(logger.result());
|
||||||
@@ -151,8 +155,8 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
if (indexNames.contains(key) === true) {
|
if (indexNames.contains(key) === true) {
|
||||||
let val: any;
|
let val: any;
|
||||||
if (isObject(filter[key]) === true) {
|
if (isObject(filter[key]) === true) {
|
||||||
if ((filter as any)[key]["$in"] !== undefined) {
|
if ((filter as any)[key].$in !== undefined) {
|
||||||
val = (filter as any)[key]["$in"];
|
val = (filter as any)[key].$in;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val = filter[key];
|
val = filter[key];
|
||||||
@@ -168,7 +172,7 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
async #getAll({ index, offset, range, limit }: Options) {
|
async #getAll({ index, offset, range, limit }: QueryOptions) {
|
||||||
if (index !== undefined) {
|
if (index !== undefined) {
|
||||||
return this.#getAllByIndex(index);
|
return this.#getAllByIndex(index);
|
||||||
}
|
}
|
||||||
@@ -230,34 +234,34 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
async updateOne(
|
async updateOne(
|
||||||
filter: Filter<WithId<TSchema>>,
|
filter: Filter<TSchema>,
|
||||||
expr: UpdateExpression,
|
modifier: Modifier<TSchema>,
|
||||||
arrayFilters?: Filter<WithId<TSchema>>[],
|
arrayFilters?: Filter<TSchema>[],
|
||||||
condition?: Filter<WithId<TSchema>>,
|
condition?: Criteria<TSchema>,
|
||||||
options?: UpdateOptions,
|
options: { cloneMode?: CloneMode; queryOptions?: Partial<Options> } = { cloneMode: "deep" },
|
||||||
): Promise<UpdateResult> {
|
): Promise<UpdateResult> {
|
||||||
if (typeof filter.id === "string") {
|
if (typeof filter.id === "string") {
|
||||||
return this.#update(filter.id, expr, arrayFilters, condition, options);
|
return this.#update(filter.id, modifier, arrayFilters, condition, options);
|
||||||
}
|
}
|
||||||
const documents = await this.find(filter);
|
const documents = await this.find(filter);
|
||||||
if (documents.length > 0) {
|
if (documents.length > 0) {
|
||||||
return this.#update(documents[0].id, expr, arrayFilters, condition, options);
|
return this.#update(documents[0].id, modifier, arrayFilters, condition, options);
|
||||||
}
|
}
|
||||||
return new UpdateResult(0, 0);
|
return new UpdateResult(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMany(
|
async updateMany(
|
||||||
filter: Filter<WithId<TSchema>>,
|
filter: Filter<TSchema>,
|
||||||
expr: UpdateExpression,
|
modifier: Modifier<TSchema>,
|
||||||
arrayFilters?: Filter<WithId<TSchema>>[],
|
arrayFilters?: Filter<TSchema>[],
|
||||||
condition?: Filter<WithId<TSchema>>,
|
condition?: Criteria<TSchema>,
|
||||||
options?: UpdateOptions,
|
options: { cloneMode?: CloneMode; queryOptions?: Partial<Options> } = { cloneMode: "deep" },
|
||||||
): Promise<UpdateResult> {
|
): Promise<UpdateResult> {
|
||||||
const logger = new UpdateLog(this.name, { filter, expr, arrayFilters, condition, options });
|
const logger = new UpdateLog(this.name, { filter, modifier, arrayFilters, condition, options });
|
||||||
|
|
||||||
const ids = await this.find(filter).then((data) => data.map((d) => d.id));
|
const ids = await this.find(filter).then((data) => data.map((d) => d.id));
|
||||||
|
|
||||||
const documents: WithId<TSchema>[] = [];
|
const documents: TSchema[] = [];
|
||||||
let modifiedCount = 0;
|
let modifiedCount = 0;
|
||||||
|
|
||||||
const tx = this.db.transaction(this.name, "readwrite", { durability: "relaxed" });
|
const tx = this.db.transaction(this.name, "readwrite", { durability: "relaxed" });
|
||||||
@@ -267,7 +271,7 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
if (current === undefined) {
|
if (current === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const modified = update(current, expr, arrayFilters, condition, options);
|
const modified = update(current, modifier, arrayFilters, condition, options);
|
||||||
if (modified.length > 0) {
|
if (modified.length > 0) {
|
||||||
modifiedCount += 1;
|
modifiedCount += 1;
|
||||||
documents.push(current);
|
documents.push(current);
|
||||||
@@ -287,12 +291,12 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
return new UpdateResult(ids.length, modifiedCount);
|
return new UpdateResult(ids.length, modifiedCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
async replace(filter: Filter<WithId<TSchema>>, document: WithId<TSchema>): Promise<UpdateResult> {
|
async replace(filter: Filter<TSchema>, document: TSchema): Promise<UpdateResult> {
|
||||||
const logger = new ReplaceLog(this.name, document);
|
const logger = new ReplaceLog(this.name, document);
|
||||||
|
|
||||||
const ids = await this.find(filter).then((data) => data.map((d) => d.id));
|
const ids = await this.find(filter).then((data) => data.map((d) => d.id));
|
||||||
|
|
||||||
const documents: WithId<TSchema>[] = [];
|
const documents: TSchema[] = [];
|
||||||
const count = ids.length;
|
const count = ids.length;
|
||||||
|
|
||||||
const tx = this.db.transaction(this.name, "readwrite", { durability: "relaxed" });
|
const tx = this.db.transaction(this.name, "readwrite", { durability: "relaxed" });
|
||||||
@@ -315,12 +319,12 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
|
|
||||||
async #update(
|
async #update(
|
||||||
id: string | number,
|
id: string | number,
|
||||||
expr: UpdateExpression,
|
modifier: Modifier<TSchema>,
|
||||||
arrayFilters?: Filter<WithId<TSchema>>[],
|
arrayFilters?: Filter<TSchema>[],
|
||||||
condition?: Filter<WithId<TSchema>>,
|
condition?: Criteria<TSchema>,
|
||||||
options?: UpdateOptions,
|
options: { cloneMode?: CloneMode; queryOptions?: Partial<Options> } = { cloneMode: "deep" },
|
||||||
): Promise<UpdateResult> {
|
): Promise<UpdateResult> {
|
||||||
const logger = new UpdateLog(this.name, { id, expr });
|
const logger = new UpdateLog(this.name, { id, modifier });
|
||||||
|
|
||||||
const tx = this.db.transaction(this.name, "readwrite", { durability: "relaxed" });
|
const tx = this.db.transaction(this.name, "readwrite", { durability: "relaxed" });
|
||||||
|
|
||||||
@@ -330,7 +334,7 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
return new UpdateResult(0, 0);
|
return new UpdateResult(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const modified = await update(current, expr, arrayFilters, condition, options);
|
const modified = await update(current, modifier, arrayFilters, condition, options);
|
||||||
if (modified.length > 0) {
|
if (modified.length > 0) {
|
||||||
await tx.store.put(current);
|
await tx.store.put(current);
|
||||||
}
|
}
|
||||||
@@ -352,7 +356,7 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async remove(filter: Filter<WithId<TSchema>>): Promise<RemoveResult> {
|
async remove(filter: Filter<TSchema>): Promise<RemoveResult> {
|
||||||
const logger = new RemoveLog(this.name, { filter });
|
const logger = new RemoveLog(this.name, { filter });
|
||||||
|
|
||||||
const documents = await this.find(filter);
|
const documents = await this.find(filter);
|
||||||
@@ -375,7 +379,7 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async count(filter?: Filter<WithId<TSchema>>): Promise<number> {
|
async count(filter?: Filter<TSchema>): Promise<number> {
|
||||||
if (filter !== undefined) {
|
if (filter !== undefined) {
|
||||||
return (await this.find(filter)).length;
|
return (await this.find(filter)).length;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Collection } from "../../collection.ts";
|
import { Collection } from "../../collection.ts";
|
||||||
import { Document } from "../../types.ts";
|
import type { Document } from "../../types.ts";
|
||||||
import { Registrars } from "../registrars.ts";
|
import type { Registrars } from "../registrars.ts";
|
||||||
import { MemoryStorage } from "./storage.ts";
|
import { MemoryStorage } from "./storage.ts";
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
|
|||||||
@@ -1,145 +1,155 @@
|
|||||||
import { createUpdater, Query } from "mingo";
|
import { Query, update } from "mingo";
|
||||||
import { UpdateOptions } from "mingo/core";
|
import type { AnyObject } from "mingo/types";
|
||||||
import { UpdateExpression } from "mingo/updater";
|
|
||||||
|
|
||||||
import { DuplicateDocumentError } from "../../storage/errors.ts";
|
import { getDocumentWithPrimaryKey } from "../../primary-key.ts";
|
||||||
|
import { Collections } from "../../storage/collections.ts";
|
||||||
|
import type { UpdatePayload } from "../../storage/mod.ts";
|
||||||
|
import type { InsertResult } from "../../storage/operators/insert.ts";
|
||||||
|
import type { UpdateResult } from "../../storage/operators/update.ts";
|
||||||
import {
|
import {
|
||||||
getInsertManyResult,
|
addOptions,
|
||||||
getInsertOneResult,
|
type CountPayload,
|
||||||
type InsertManyResult,
|
type FindByIdPayload,
|
||||||
type InsertOneResult,
|
type FindPayload,
|
||||||
} from "../../storage/operators/insert.ts";
|
type InsertManyPayload,
|
||||||
import { RemoveResult } from "../../storage/operators/remove.ts";
|
type InsertOnePayload,
|
||||||
import { UpdateResult } from "../../storage/operators/update.ts";
|
type RemovePayload,
|
||||||
import { addOptions, Options, Storage } from "../../storage/storage.ts";
|
type ReplacePayload,
|
||||||
import type { Document, Filter, WithId } from "../../types.ts";
|
Storage,
|
||||||
|
} from "../../storage/storage.ts";
|
||||||
|
import type { AnyDocument } from "../../types.ts";
|
||||||
|
|
||||||
const update = createUpdater({ cloneMode: "deep" });
|
export class MemoryStorage extends Storage {
|
||||||
|
readonly #collections = new Collections();
|
||||||
export class MemoryStorage<TSchema extends Document = Document> extends Storage<TSchema> {
|
|
||||||
readonly #documents = new Map<string, WithId<TSchema>>();
|
|
||||||
|
|
||||||
async resolve() {
|
async resolve() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
async has(id: string): Promise<boolean> {
|
async insertOne({ pkey, values, ...payload }: InsertOnePayload): Promise<InsertResult> {
|
||||||
return this.#documents.has(id);
|
const collection = this.#collections.get(payload.collection);
|
||||||
|
|
||||||
|
const document = getDocumentWithPrimaryKey(pkey, values);
|
||||||
|
if (collection.has(document[pkey])) {
|
||||||
|
return { insertCount: 0, insertIds: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
async insertOne(data: Partial<TSchema>): Promise<InsertOneResult> {
|
collection.set(document[pkey], document);
|
||||||
const document = { ...data, id: data.id ?? crypto.randomUUID() } as WithId<TSchema>;
|
|
||||||
if (await this.has(document.id)) {
|
|
||||||
throw new DuplicateDocumentError(document, this as any);
|
|
||||||
}
|
|
||||||
this.#documents.set(document.id, document);
|
|
||||||
this.broadcast("insertOne", document);
|
this.broadcast("insertOne", document);
|
||||||
return getInsertOneResult(document);
|
|
||||||
|
return { insertCount: 1, insertIds: [document[pkey]] };
|
||||||
}
|
}
|
||||||
|
|
||||||
async insertMany(documents: Partial<TSchema>[]): Promise<InsertManyResult> {
|
async insertMany({ pkey, values, ...payload }: InsertManyPayload): Promise<InsertResult> {
|
||||||
const result: TSchema[] = [];
|
const collection = this.#collections.get(payload.collection);
|
||||||
for (const data of documents) {
|
|
||||||
const document = { ...data, id: data.id ?? crypto.randomUUID() } as WithId<TSchema>;
|
const documents: AnyDocument[] = [];
|
||||||
result.push(document);
|
for (const insert of values) {
|
||||||
this.#documents.set(document.id, document);
|
const document = getDocumentWithPrimaryKey(pkey, insert);
|
||||||
|
if (collection.has(document[pkey])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
collection.set(document[pkey], document);
|
||||||
|
documents.push(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.broadcast("insertMany", result);
|
if (documents.length > 0) {
|
||||||
|
this.broadcast("insertMany", documents);
|
||||||
return getInsertManyResult(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async findById(id: string): Promise<WithId<TSchema> | undefined> {
|
return { insertCount: documents.length, insertIds: documents.map((document) => document[pkey]) };
|
||||||
return this.#documents.get(id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async find(filter?: Filter<WithId<TSchema>>, options?: Options): Promise<WithId<TSchema>[]> {
|
async findById({ collection, id }: FindByIdPayload): Promise<AnyObject | undefined> {
|
||||||
let cursor = new Query(filter ?? {}).find<TSchema>(Array.from(this.#documents.values()));
|
return this.#collections.get(collection).get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async find({ condition = {}, options, ...payload }: FindPayload): Promise<AnyDocument[]> {
|
||||||
|
let cursor = new Query(condition).find<AnyDocument>(this.#collections.documents(payload.collection));
|
||||||
if (options !== undefined) {
|
if (options !== undefined) {
|
||||||
cursor = addOptions(cursor, options);
|
cursor = addOptions(cursor, options);
|
||||||
}
|
}
|
||||||
return cursor.all() as WithId<TSchema>[];
|
return cursor.all();
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOne(
|
async updateOne({ pkey, condition, modifier, arrayFilters, ...payload }: UpdatePayload): Promise<UpdateResult> {
|
||||||
filter: Filter<WithId<TSchema>>,
|
const collection = this.#collections.get(payload.collection);
|
||||||
expr: UpdateExpression,
|
|
||||||
arrayFilters?: Filter<WithId<TSchema>>[],
|
let matchedCount = 0;
|
||||||
condition?: Filter<WithId<TSchema>>,
|
let modifiedCount = 0;
|
||||||
options?: UpdateOptions,
|
|
||||||
): Promise<UpdateResult> {
|
for (const document of await this.find({ collection: payload.collection, condition, options: { limit: 1 } })) {
|
||||||
for (const document of await this.find(filter)) {
|
const modified = update(document, modifier, arrayFilters, undefined, { cloneMode: "deep" });
|
||||||
const modified = update(document, expr, arrayFilters, condition, options);
|
|
||||||
if (modified.length > 0) {
|
if (modified.length > 0) {
|
||||||
this.#documents.set(document.id, document);
|
collection.set(document[pkey], document);
|
||||||
this.broadcast("updateOne", document);
|
this.broadcast("updateOne", document);
|
||||||
return new UpdateResult(1, 1);
|
modifiedCount += 1;
|
||||||
}
|
}
|
||||||
return new UpdateResult(1, 0);
|
matchedCount += 1;
|
||||||
}
|
}
|
||||||
return new UpdateResult(0, 0);
|
return { matchedCount, modifiedCount };
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMany(
|
async updateMany({ pkey, condition, modifier, arrayFilters, ...payload }: UpdatePayload): Promise<UpdateResult> {
|
||||||
filter: Filter<WithId<TSchema>>,
|
const collection = this.#collections.get(payload.collection);
|
||||||
expr: UpdateExpression,
|
|
||||||
arrayFilters?: Filter<WithId<TSchema>>[],
|
const documents: AnyDocument[] = [];
|
||||||
condition?: Filter<WithId<TSchema>>,
|
|
||||||
options?: UpdateOptions,
|
|
||||||
): Promise<UpdateResult> {
|
|
||||||
const documents: WithId<TSchema>[] = [];
|
|
||||||
|
|
||||||
let matchedCount = 0;
|
let matchedCount = 0;
|
||||||
let modifiedCount = 0;
|
let modifiedCount = 0;
|
||||||
|
|
||||||
for (const document of await this.find(filter)) {
|
for (const document of await this.find({ collection: payload.collection, condition })) {
|
||||||
matchedCount += 1;
|
matchedCount += 1;
|
||||||
const modified = update(document, expr, arrayFilters, condition, options);
|
const modified = update(document, modifier, arrayFilters, undefined, { cloneMode: "deep" });
|
||||||
if (modified.length > 0) {
|
if (modified.length > 0) {
|
||||||
modifiedCount += 1;
|
modifiedCount += 1;
|
||||||
documents.push(document);
|
documents.push(document);
|
||||||
this.#documents.set(document.id, document);
|
collection.set(document[pkey], document);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.broadcast("updateMany", documents);
|
this.broadcast("updateMany", documents);
|
||||||
|
|
||||||
return new UpdateResult(matchedCount, modifiedCount);
|
return { matchedCount, modifiedCount };
|
||||||
}
|
}
|
||||||
|
|
||||||
async replace(filter: Filter<WithId<TSchema>>, document: WithId<TSchema>): Promise<UpdateResult> {
|
async replace({ pkey, condition, document, ...payload }: ReplacePayload): Promise<UpdateResult> {
|
||||||
const documents: WithId<TSchema>[] = [];
|
const collection = this.#collections.get(payload.collection);
|
||||||
|
|
||||||
let matchedCount = 0;
|
let matchedCount = 0;
|
||||||
let modifiedCount = 0;
|
let modifiedCount = 0;
|
||||||
|
|
||||||
for (const current of await this.find(filter)) {
|
const documents: AnyDocument[] = [];
|
||||||
|
for (const current of await this.find({ collection: payload.collection, condition })) {
|
||||||
matchedCount += 1;
|
matchedCount += 1;
|
||||||
modifiedCount += 1;
|
modifiedCount += 1;
|
||||||
documents.push(document);
|
documents.push(document);
|
||||||
this.#documents.set(current.id, document);
|
collection.set(current[pkey], document);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.broadcast("updateMany", documents);
|
this.broadcast("updateMany", documents);
|
||||||
|
|
||||||
return new UpdateResult(matchedCount, modifiedCount);
|
return { matchedCount, modifiedCount };
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(filter: Filter<WithId<TSchema>>): Promise<RemoveResult> {
|
async remove({ pkey, condition, ...payload }: RemovePayload): Promise<number> {
|
||||||
const documents = await this.find(filter);
|
const collection = this.#collections.get(payload.collection);
|
||||||
|
|
||||||
|
const documents = await this.find({ collection: payload.collection, condition });
|
||||||
for (const document of documents) {
|
for (const document of documents) {
|
||||||
this.#documents.delete(document.id);
|
collection.delete(document[pkey]);
|
||||||
}
|
|
||||||
this.broadcast("remove", documents);
|
|
||||||
return new RemoveResult(documents.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async count(filter?: Filter<WithId<TSchema>>): Promise<number> {
|
this.broadcast("remove", documents);
|
||||||
return new Query(filter ?? {}).find(Array.from(this.#documents.values())).count();
|
|
||||||
|
return documents.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
async count({ collection, condition = {} }: CountPayload): Promise<number> {
|
||||||
|
return new Query(condition).find(this.#collections.documents(collection)).all().length;
|
||||||
}
|
}
|
||||||
|
|
||||||
async flush(): Promise<void> {
|
async flush(): Promise<void> {
|
||||||
this.#documents.clear();
|
this.#collections.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,16 @@
|
|||||||
import { createUpdater, Query } from "mingo";
|
import { Query, update } from "mingo";
|
||||||
import { UpdateOptions } from "mingo/core";
|
import type { Criteria, Options } from "mingo/types";
|
||||||
import { UpdateExpression } from "mingo/updater";
|
import type { CloneMode, Modifier } from "mingo/updater";
|
||||||
|
|
||||||
|
import { getDocumentWithPrimaryKey } from "../../primary-key.ts";
|
||||||
import { DuplicateDocumentError } from "../../storage/errors.ts";
|
import { DuplicateDocumentError } from "../../storage/errors.ts";
|
||||||
import {
|
import type { InsertResult } from "../../storage/operators/insert.ts";
|
||||||
getInsertManyResult,
|
|
||||||
getInsertOneResult,
|
|
||||||
InsertManyResult,
|
|
||||||
InsertOneResult,
|
|
||||||
} from "../../storage/operators/insert.ts";
|
|
||||||
import { RemoveResult } from "../../storage/operators/remove.ts";
|
|
||||||
import { UpdateResult } from "../../storage/operators/update.ts";
|
import { UpdateResult } from "../../storage/operators/update.ts";
|
||||||
import { addOptions, Options, Storage } from "../../storage/storage.ts";
|
import { addOptions, type QueryOptions, Storage } from "../../storage/storage.ts";
|
||||||
import { Document, Filter, WithId } from "../../types.ts";
|
import type { AnyDocument } from "../../types.ts";
|
||||||
|
|
||||||
const update = createUpdater({ cloneMode: "deep" });
|
export class ObserverStorage extends Storage {
|
||||||
|
readonly #documents = new Map<string, AnyDocument>();
|
||||||
export class ObserverStorage<TSchema extends Document = Document> extends Storage<TSchema> {
|
|
||||||
readonly #documents = new Map<string, WithId<TSchema>>();
|
|
||||||
|
|
||||||
async resolve() {
|
async resolve() {
|
||||||
return this;
|
return this;
|
||||||
@@ -27,48 +20,48 @@ export class ObserverStorage<TSchema extends Document = Document> extends Storag
|
|||||||
return this.#documents.has(id);
|
return this.#documents.has(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async insertOne(data: Partial<TSchema>): Promise<InsertOneResult> {
|
async insertOne(values: AnyDocument): Promise<InsertResult> {
|
||||||
const document = { ...data, id: data.id ?? crypto.randomUUID() } as WithId<TSchema>;
|
const document = getDocumentWithPrimaryKey(this.primaryKey, values);
|
||||||
if (await this.has(document.id)) {
|
if (await this.has(document[this.primaryKey])) {
|
||||||
throw new DuplicateDocumentError(document, this as any);
|
throw new DuplicateDocumentError(document, this as any);
|
||||||
}
|
}
|
||||||
this.#documents.set(document.id, document);
|
this.#documents.set(document[this.primaryKey], document);
|
||||||
return getInsertOneResult(document);
|
return getInsertOneResult(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
async insertMany(documents: Partial<TSchema>[]): Promise<InsertManyResult> {
|
async insertMany(list: TSchema[]): Promise<InsertResult> {
|
||||||
const result: TSchema[] = [];
|
const result: TSchema[] = [];
|
||||||
for (const data of documents) {
|
for (const values of list) {
|
||||||
const document = { ...data, id: data.id ?? crypto.randomUUID() } as WithId<TSchema>;
|
const document = getDocumentWithPrimaryKey(this.primaryKey, values);
|
||||||
result.push(document);
|
result.push(document);
|
||||||
this.#documents.set(document.id, document);
|
this.#documents.set(document.id, document);
|
||||||
}
|
}
|
||||||
return getInsertManyResult(result);
|
return getInsertManyResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findById(id: string): Promise<WithId<TSchema> | undefined> {
|
async findById(id: string): Promise<TSchema | undefined> {
|
||||||
return this.#documents.get(id);
|
return this.#documents.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async find(filter?: Filter<WithId<TSchema>>, options?: Options): Promise<WithId<TSchema>[]> {
|
async find(filter?: Filter<TSchema>, options?: QueryOptions): Promise<TSchema[]> {
|
||||||
let cursor = new Query(filter ?? {}).find<TSchema>(Array.from(this.#documents.values()));
|
let cursor = new Query(filter ?? {}).find<TSchema>(Array.from(this.#documents.values()));
|
||||||
if (options !== undefined) {
|
if (options !== undefined) {
|
||||||
cursor = addOptions(cursor, options);
|
cursor = addOptions(cursor, options);
|
||||||
}
|
}
|
||||||
return cursor.all() as WithId<TSchema>[];
|
return cursor.all();
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOne(
|
async updateOne(
|
||||||
filter: Filter<WithId<TSchema>>,
|
filter: Filter<TSchema>,
|
||||||
expr: UpdateExpression,
|
modifier: Modifier<TSchema>,
|
||||||
arrayFilters?: Filter<WithId<TSchema>>[],
|
arrayFilters?: Filter<TSchema>[],
|
||||||
condition?: Filter<WithId<TSchema>>,
|
condition?: Criteria<TSchema>,
|
||||||
options?: UpdateOptions,
|
options: { cloneMode?: CloneMode; queryOptions?: Partial<Options> } = { cloneMode: "deep" },
|
||||||
): Promise<UpdateResult> {
|
): Promise<UpdateResult> {
|
||||||
const query = new Query(filter);
|
const query = new Query(filter);
|
||||||
for (const document of Array.from(this.#documents.values())) {
|
for (const document of Array.from(this.#documents.values())) {
|
||||||
if (query.test(document) === true) {
|
if (query.test(document) === true) {
|
||||||
const modified = update(document, expr, arrayFilters, condition, options);
|
const modified = update(document, modifier, arrayFilters, condition, options);
|
||||||
if (modified.length > 0) {
|
if (modified.length > 0) {
|
||||||
this.#documents.set(document.id, document);
|
this.#documents.set(document.id, document);
|
||||||
this.broadcast("updateOne", document);
|
this.broadcast("updateOne", document);
|
||||||
@@ -81,15 +74,15 @@ export class ObserverStorage<TSchema extends Document = Document> extends Storag
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateMany(
|
async updateMany(
|
||||||
filter: Filter<WithId<TSchema>>,
|
filter: Filter<TSchema>,
|
||||||
expr: UpdateExpression,
|
modifier: Modifier<TSchema>,
|
||||||
arrayFilters?: Filter<WithId<TSchema>>[],
|
arrayFilters?: Filter<TSchema>[],
|
||||||
condition?: Filter<WithId<TSchema>>,
|
condition?: Criteria<TSchema>,
|
||||||
options?: UpdateOptions,
|
options: { cloneMode?: CloneMode; queryOptions?: Partial<Options> } = { cloneMode: "deep" },
|
||||||
): Promise<UpdateResult> {
|
): Promise<UpdateResult> {
|
||||||
const query = new Query(filter);
|
const query = new Query(filter);
|
||||||
|
|
||||||
const documents: WithId<TSchema>[] = [];
|
const documents: TSchema[] = [];
|
||||||
|
|
||||||
let matchedCount = 0;
|
let matchedCount = 0;
|
||||||
let modifiedCount = 0;
|
let modifiedCount = 0;
|
||||||
@@ -97,7 +90,7 @@ export class ObserverStorage<TSchema extends Document = Document> extends Storag
|
|||||||
for (const document of Array.from(this.#documents.values())) {
|
for (const document of Array.from(this.#documents.values())) {
|
||||||
if (query.test(document) === true) {
|
if (query.test(document) === true) {
|
||||||
matchedCount += 1;
|
matchedCount += 1;
|
||||||
const modified = update(filter, expr, arrayFilters, condition, options);
|
const modified = update(document, modifier, arrayFilters, condition, options);
|
||||||
if (modified.length > 0) {
|
if (modified.length > 0) {
|
||||||
modifiedCount += 1;
|
modifiedCount += 1;
|
||||||
documents.push(document);
|
documents.push(document);
|
||||||
@@ -111,10 +104,10 @@ export class ObserverStorage<TSchema extends Document = Document> extends Storag
|
|||||||
return new UpdateResult(matchedCount, modifiedCount);
|
return new UpdateResult(matchedCount, modifiedCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
async replace(filter: Filter<WithId<TSchema>>, document: WithId<TSchema>): Promise<UpdateResult> {
|
async replace(filter: Filter<TSchema>, document: TSchema): Promise<UpdateResult> {
|
||||||
const query = new Query(filter);
|
const query = new Query(filter);
|
||||||
|
|
||||||
const documents: WithId<TSchema>[] = [];
|
const documents: TSchema[] = [];
|
||||||
|
|
||||||
let matchedCount = 0;
|
let matchedCount = 0;
|
||||||
let modifiedCount = 0;
|
let modifiedCount = 0;
|
||||||
@@ -131,7 +124,7 @@ export class ObserverStorage<TSchema extends Document = Document> extends Storag
|
|||||||
return new UpdateResult(matchedCount, modifiedCount);
|
return new UpdateResult(matchedCount, modifiedCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(filter: Filter<WithId<TSchema>>): Promise<RemoveResult> {
|
async remove(filter: Filter<TSchema>): Promise<RemoveResult> {
|
||||||
const documents = Array.from(this.#documents.values());
|
const documents = Array.from(this.#documents.values());
|
||||||
const query = new Query(filter);
|
const query = new Query(filter);
|
||||||
let count = 0;
|
let count = 0;
|
||||||
@@ -144,8 +137,8 @@ export class ObserverStorage<TSchema extends Document = Document> extends Storag
|
|||||||
return new RemoveResult(count);
|
return new RemoveResult(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
async count(filter?: Filter<WithId<TSchema>>): Promise<number> {
|
async count(filter?: Filter<TSchema>): Promise<number> {
|
||||||
return new Query(filter ?? {}).find(Array.from(this.#documents.values())).count();
|
return new Query(filter ?? {}).find(Array.from(this.#documents.values())).all().length;
|
||||||
}
|
}
|
||||||
|
|
||||||
async flush(): Promise<void> {
|
async flush(): Promise<void> {
|
||||||
|
|||||||
@@ -1,6 +1,23 @@
|
|||||||
export type Registrars = {
|
export type Registrars = {
|
||||||
|
/**
|
||||||
|
* Name of the collection.
|
||||||
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the primary key of the collection.
|
||||||
|
* Default: "id"
|
||||||
|
*/
|
||||||
|
primaryKey?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of custom indexes for the collection.
|
||||||
|
*/
|
||||||
indexes?: Index[];
|
indexes?: Index[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type Index = [string, any?];
|
type Index = [IndexKey, IndexOptions?];
|
||||||
|
|
||||||
|
type IndexKey = string;
|
||||||
|
|
||||||
|
type IndexOptions = { unique: boolean };
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Collection } from "../collection.ts";
|
import type { Collection } from "../collection.ts";
|
||||||
import { Document, Filter, WithId } from "../types.ts";
|
import type { Document, Filter, WithId } from "../types.ts";
|
||||||
import { isMatch } from "./is-match.ts";
|
import { isMatch } from "./is-match.ts";
|
||||||
|
|
||||||
export function observeOne<TSchema extends Document = Document>(
|
export function observeOne<TSchema extends Document = Document>(
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
import { Query } from "mingo";
|
import { Query } from "mingo";
|
||||||
|
import type { AnyObject, Criteria } from "mingo/types";
|
||||||
|
|
||||||
import { Collection } from "../collection.ts";
|
import type { Collection } from "../collection.ts";
|
||||||
import { addOptions, ChangeEvent, Options } from "../storage/mod.ts";
|
import { addOptions, type ChangeEvent, type QueryOptions } from "../storage/mod.ts";
|
||||||
import { Document, Filter, WithId } from "../types.ts";
|
import type { AnyDocument } from "../types.ts";
|
||||||
import { Store } from "./store.ts";
|
import { Store } from "./store.ts";
|
||||||
|
|
||||||
export function observe<TSchema extends Document = Document>(
|
export function observe<TCollection extends Collection, TSchema extends AnyObject = TCollection["$schema"]>(
|
||||||
collection: Collection<TSchema>,
|
collection: TCollection,
|
||||||
filter: Filter<WithId<TSchema>>,
|
condition: Criteria<TSchema>,
|
||||||
options: Options | undefined,
|
options: QueryOptions | undefined,
|
||||||
onChange: (documents: WithId<TSchema>[], changed: WithId<TSchema>[], type: ChangeEvent<TSchema>["type"]) => void,
|
onChange: (documents: TSchema[], changed: TSchema[], type: ChangeEvent["type"]) => void,
|
||||||
): {
|
): {
|
||||||
unsubscribe: () => void;
|
unsubscribe: () => void;
|
||||||
} {
|
} {
|
||||||
const store = Store.create<TSchema>();
|
const store = Store.create();
|
||||||
|
|
||||||
let debounce: any;
|
let debounce: any;
|
||||||
|
|
||||||
collection.find(filter, options).then(async (documents) => {
|
collection.find(condition, options).then(async (documents) => {
|
||||||
const resolved = await store.resolve(documents);
|
const resolved = await store.resolve(documents);
|
||||||
onChange(resolved, resolved, "insertMany");
|
onChange(resolved, resolved, "insertMany");
|
||||||
});
|
});
|
||||||
@@ -29,17 +30,17 @@ export function observe<TSchema extends Document = Document>(
|
|||||||
onChange([], [], "remove");
|
onChange([], [], "remove");
|
||||||
}),
|
}),
|
||||||
collection.observable.change.subscribe(async ({ type, data }) => {
|
collection.observable.change.subscribe(async ({ type, data }) => {
|
||||||
let changed: WithId<TSchema>[] = [];
|
let changed: AnyObject[] = [];
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "insertOne":
|
case "insertOne":
|
||||||
case "updateOne": {
|
case "updateOne": {
|
||||||
changed = await store[type](data, filter);
|
changed = await store[type](data, condition);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "insertMany":
|
case "insertMany":
|
||||||
case "updateMany":
|
case "updateMany":
|
||||||
case "remove": {
|
case "remove": {
|
||||||
changed = await store[type](data, filter);
|
changed = await store[type](data, condition);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,12 +65,9 @@ export function observe<TSchema extends Document = Document>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyQueryOptions<TSchema extends Document = Document>(
|
function applyQueryOptions(documents: AnyDocument[], options?: QueryOptions): AnyDocument[] {
|
||||||
documents: WithId<TSchema>[],
|
|
||||||
options?: Options,
|
|
||||||
): WithId<TSchema>[] {
|
|
||||||
if (options !== undefined) {
|
if (options !== undefined) {
|
||||||
return addOptions(new Query({}).find<TSchema>(documents), options).all() as WithId<TSchema>[];
|
return addOptions(new Query({}).find<AnyDocument>(documents), options).all();
|
||||||
}
|
}
|
||||||
return documents;
|
return documents;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
import { ObserverStorage } from "../databases/observer/storage.ts";
|
import { ObserverStorage } from "../databases/observer/storage.ts";
|
||||||
import { Storage } from "../storage/mod.ts";
|
import type { Storage } from "../storage/mod.ts";
|
||||||
import { Document, Filter, WithId } from "../types.ts";
|
import type { AnyDocument } from "../types.ts";
|
||||||
import { isMatch } from "./is-match.ts";
|
import { isMatch } from "./is-match.ts";
|
||||||
|
|
||||||
export class Store<TSchema extends Document = Document> {
|
export class Store {
|
||||||
private constructor(private storage: Storage<TSchema>) {}
|
private constructor(private storage: Storage) {}
|
||||||
|
|
||||||
static create<TSchema extends Document = Document>() {
|
static create() {
|
||||||
return new Store<TSchema>(new ObserverStorage<TSchema>(`observer[${crypto.randomUUID()}]`));
|
return new Store(new ObserverStorage(`observer[${crypto.randomUUID()}]`));
|
||||||
}
|
}
|
||||||
|
|
||||||
get destroy() {
|
get destroy() {
|
||||||
return this.storage.destroy.bind(this.storage);
|
return this.storage.destroy.bind(this.storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolve(documents: WithId<TSchema>[]): Promise<WithId<TSchema>[]> {
|
async resolve(documents: AnyDocument[]): Promise<AnyDocument[]> {
|
||||||
await this.storage.insertMany(documents);
|
await this.storage.insertMany(documents);
|
||||||
return this.getDocuments();
|
return this.getDocuments();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDocuments(): Promise<WithId<TSchema>[]> {
|
async getDocuments(): Promise<AnyDocument[]> {
|
||||||
return this.storage.find();
|
return this.storage.find();
|
||||||
}
|
}
|
||||||
|
|
||||||
async insertMany(documents: WithId<TSchema>[], filter: Filter<WithId<TSchema>>): Promise<WithId<TSchema>[]> {
|
async insertMany(documents: AnyDocument[], filter: Filter<AnyDocument>): Promise<AnyDocument[]> {
|
||||||
const matched = [];
|
const matched = [];
|
||||||
for (const document of documents) {
|
for (const document of documents) {
|
||||||
matched.push(...(await this.insertOne(document, filter)));
|
matched.push(...(await this.insertOne(document, filter)));
|
||||||
@@ -31,15 +31,15 @@ export class Store<TSchema extends Document = Document> {
|
|||||||
return matched;
|
return matched;
|
||||||
}
|
}
|
||||||
|
|
||||||
async insertOne(document: WithId<TSchema>, filter: Filter<WithId<TSchema>>): Promise<WithId<TSchema>[]> {
|
async insertOne(document: AnyDocument, filter: Filter<AnyDocument>): Promise<AnyDocument[]> {
|
||||||
if (isMatch<TSchema>(document, filter)) {
|
if (isMatch<AnyDocument>(document, filter)) {
|
||||||
await this.storage.insertOne(document);
|
await this.storage.insertOne(document);
|
||||||
return [document];
|
return [document];
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMany(documents: WithId<TSchema>[], filter: Filter<WithId<TSchema>>): Promise<WithId<TSchema>[]> {
|
async updateMany(documents: AnyDocument[], filter: Filter<AnyDocument>): Promise<AnyDocument[]> {
|
||||||
const matched = [];
|
const matched = [];
|
||||||
for (const document of documents) {
|
for (const document of documents) {
|
||||||
matched.push(...(await this.updateOne(document, filter)));
|
matched.push(...(await this.updateOne(document, filter)));
|
||||||
@@ -47,33 +47,33 @@ export class Store<TSchema extends Document = Document> {
|
|||||||
return matched;
|
return matched;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOne(document: WithId<TSchema>, filter: Filter<WithId<TSchema>>): Promise<WithId<TSchema>[]> {
|
async updateOne(document: AnyDocument, filter: Filter<AnyDocument>): Promise<AnyDocument[]> {
|
||||||
if (await this.storage.has(document.id)) {
|
if (await this.storage.has(document.id)) {
|
||||||
await this.#updateOrRemove(document, filter);
|
await this.#updateOrRemove(document, filter);
|
||||||
return [document];
|
return [document];
|
||||||
} else if (isMatch<TSchema>(document, filter)) {
|
} else if (isMatch<AnyDocument>(document, filter)) {
|
||||||
await this.storage.insertOne(document);
|
await this.storage.insertOne(document);
|
||||||
return [document];
|
return [document];
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(documents: WithId<TSchema>[]): Promise<WithId<TSchema>[]> {
|
async remove(documents: AnyDocument[]): Promise<AnyDocument[]> {
|
||||||
const matched = [];
|
const matched = [];
|
||||||
for (const document of documents) {
|
for (const document of documents) {
|
||||||
if (isMatch<TSchema>(document, { id: document.id } as WithId<TSchema>)) {
|
if (isMatch<AnyDocument>(document, { id: document.id } as AnyDocument)) {
|
||||||
await this.storage.remove({ id: document.id } as WithId<TSchema>);
|
await this.storage.remove({ id: document.id } as AnyDocument);
|
||||||
matched.push(document);
|
matched.push(document);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return matched;
|
return matched;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #updateOrRemove(document: WithId<TSchema>, filter: Filter<WithId<TSchema>>): Promise<void> {
|
async #updateOrRemove(document: AnyDocument, filter: Filter<AnyDocument>): Promise<void> {
|
||||||
if (isMatch<TSchema>(document, filter)) {
|
if (isMatch<AnyDocument>(document, filter)) {
|
||||||
await this.storage.replace({ id: document.id } as WithId<TSchema>, document);
|
await this.storage.replace({ id: document.id } as AnyDocument, document);
|
||||||
} else {
|
} else {
|
||||||
await this.storage.remove({ id: document.id } as WithId<TSchema>);
|
await this.storage.remove({ id: document.id } as AnyDocument);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8
src/primary-key.ts
Normal file
8
src/primary-key.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import type { AnyDocument } from "./types.ts";
|
||||||
|
|
||||||
|
export function getDocumentWithPrimaryKey<TPKey extends string>(pkey: TPKey, document: AnyDocument): AnyDocument {
|
||||||
|
if (Object.hasOwn(document, pkey) === true) {
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
return { [pkey]: crypto.randomUUID(), ...document };
|
||||||
|
}
|
||||||
33
src/storage/collections.ts
Normal file
33
src/storage/collections.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import type { AnyObject } from "mingo/types";
|
||||||
|
|
||||||
|
import { CollectionNotFoundError } from "./errors.ts";
|
||||||
|
|
||||||
|
export class Collections {
|
||||||
|
#collections = new Map<string, Documents>();
|
||||||
|
|
||||||
|
has(name: string): boolean {
|
||||||
|
return this.#collections.has(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
documents(name: string): AnyObject[] {
|
||||||
|
return Array.from(this.get(name).values());
|
||||||
|
}
|
||||||
|
|
||||||
|
get(name: string): Documents {
|
||||||
|
const collection = this.#collections.get(name);
|
||||||
|
if (collection === undefined) {
|
||||||
|
throw new CollectionNotFoundError(name);
|
||||||
|
}
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(name: string): boolean {
|
||||||
|
return this.#collections.delete(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
flush() {
|
||||||
|
this.#collections.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Documents = Map<string, AnyObject>;
|
||||||
@@ -1,25 +1,28 @@
|
|||||||
import { RawObject } from "mingo/types";
|
import type { AnyObject } from "mingo/types";
|
||||||
|
|
||||||
import { Document } from "../types.ts";
|
|
||||||
import type { Storage } from "./storage.ts";
|
|
||||||
|
|
||||||
export class DuplicateDocumentError extends Error {
|
export class DuplicateDocumentError extends Error {
|
||||||
readonly type = "DuplicateDocumentError";
|
readonly type = "DuplicateDocumentError";
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly document: Document,
|
readonly collection: string,
|
||||||
storage: Storage,
|
readonly document: AnyObject,
|
||||||
) {
|
) {
|
||||||
super(
|
super(`Collection Insert Violation: Document '${document.id}' already exists in '${collection}' collection`);
|
||||||
`Collection Insert Violation: Document '${document.id}' already exists in ${storage.name} collection ${storage.id}`,
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
|
export class CollectionNotFoundError extends Error {
|
||||||
|
readonly type = "CollectionNotFoundError";
|
||||||
|
|
||||||
|
constructor(readonly collection: string) {
|
||||||
|
super(`Collection Retrieve Violation: Collection '${collection}' does not exist`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DocumentNotFoundError extends Error {
|
export class DocumentNotFoundError extends Error {
|
||||||
readonly type = "DocumentNotFoundError";
|
readonly type = "DocumentNotFoundError";
|
||||||
|
|
||||||
constructor(readonly criteria: RawObject) {
|
constructor(readonly criteria: AnyObject) {
|
||||||
super(`Collection Update Violation: Document matching criteria does not exists`);
|
super(`Collection Update Violation: Document matching criteria does not exists`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,4 @@
|
|||||||
import type { Document } from "../../types.ts";
|
export type InsertResult = {
|
||||||
|
insertCount: number;
|
||||||
export function getInsertManyResult(documents: Document[]): InsertManyResult {
|
insertIds: (string | number | symbol)[];
|
||||||
return {
|
|
||||||
acknowledged: true,
|
|
||||||
insertedCount: documents.length,
|
|
||||||
insertedIds: documents.reduce<{ [key: number]: string | number }>((map, document, index) => {
|
|
||||||
map[index] = document.id;
|
|
||||||
return map;
|
|
||||||
}, {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getInsertOneResult(document: Document): InsertOneResult {
|
|
||||||
return {
|
|
||||||
acknowledged: true,
|
|
||||||
insertedId: document.id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type InsertManyResult =
|
|
||||||
| {
|
|
||||||
acknowledged: false;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
acknowledged: true;
|
|
||||||
insertedCount: number;
|
|
||||||
insertedIds: {
|
|
||||||
[key: number]: string | number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type InsertOneResult =
|
|
||||||
| {
|
|
||||||
acknowledged: false;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
acknowledged: true;
|
|
||||||
insertedId: string | number;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export class RemoveResult {
|
|
||||||
constructor(readonly matched = 0) {}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
export class UpdateResult {
|
export type UpdateResult = {
|
||||||
constructor(
|
matchedCount: number;
|
||||||
readonly matched = 0,
|
modifiedCount: number;
|
||||||
readonly modified = 0,
|
};
|
||||||
) {}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
import { UpdateOptions } from "mingo/core";
|
import type { Cursor } from "mingo/cursor";
|
||||||
import { Cursor } from "mingo/cursor";
|
import type { AnyObject, Criteria } from "mingo/types";
|
||||||
import { UpdateExpression } from "mingo/updater";
|
import type { Modifier } from "mingo/updater";
|
||||||
import { Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
import { BroadcastChannel, StorageBroadcast } from "../broadcast.ts";
|
import { BroadcastChannel, type StorageBroadcast } from "../broadcast.ts";
|
||||||
import { Document, Filter, WithId } from "../types.ts";
|
import type { Prettify } from "../types.ts";
|
||||||
import { InsertManyResult, InsertOneResult } from "./operators/insert.ts";
|
import type { InsertResult } from "./operators/insert.ts";
|
||||||
import { RemoveResult } from "./operators/remove.ts";
|
import type { UpdateResult } from "./operators/update.ts";
|
||||||
import { UpdateResult } from "./operators/update.ts";
|
|
||||||
|
|
||||||
export abstract class Storage<TSchema extends Document = Document> {
|
export abstract class Storage {
|
||||||
readonly observable: {
|
readonly observable: {
|
||||||
change: Subject<ChangeEvent<TSchema>>;
|
change: Subject<ChangeEvent>;
|
||||||
flush: Subject<void>;
|
flush: Subject<void>;
|
||||||
} = {
|
} = {
|
||||||
change: new Subject<ChangeEvent<TSchema>>(),
|
change: new Subject<ChangeEvent>(),
|
||||||
flush: new Subject<void>(),
|
flush: new Subject<void>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -22,12 +21,9 @@ export abstract class Storage<TSchema extends Document = Document> {
|
|||||||
|
|
||||||
readonly #channel: BroadcastChannel;
|
readonly #channel: BroadcastChannel;
|
||||||
|
|
||||||
constructor(
|
constructor(readonly name: string) {
|
||||||
readonly name: string,
|
|
||||||
readonly id: string = crypto.randomUUID(),
|
|
||||||
) {
|
|
||||||
this.#channel = new BroadcastChannel(`valkyr:db:${name}`);
|
this.#channel = new BroadcastChannel(`valkyr:db:${name}`);
|
||||||
this.#channel.onmessage = ({ data }: MessageEvent<StorageBroadcast<TSchema>>) => {
|
this.#channel.onmessage = ({ data }: MessageEvent<StorageBroadcast>) => {
|
||||||
if (data.name !== this.name) {
|
if (data.name !== this.name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -72,7 +68,7 @@ export abstract class Storage<TSchema extends Document = Document> {
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
broadcast(type: StorageBroadcast<TSchema>["type"], data?: TSchema | TSchema[]): void {
|
broadcast(type: StorageBroadcast["type"], data?: AnyObject | AnyObject[]): void {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "flush": {
|
case "flush": {
|
||||||
this.observable.flush.next();
|
this.observable.flush.next();
|
||||||
@@ -92,37 +88,23 @@ export abstract class Storage<TSchema extends Document = Document> {
|
|||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
abstract has(id: string): Promise<boolean>;
|
abstract insertOne(payload: InsertOnePayload): Promise<InsertResult>;
|
||||||
|
|
||||||
abstract insertOne(document: Partial<WithId<TSchema>>): Promise<InsertOneResult>;
|
abstract insertMany(payload: InsertManyPayload): Promise<InsertResult>;
|
||||||
|
|
||||||
abstract insertMany(documents: Partial<WithId<TSchema>>[]): Promise<InsertManyResult>;
|
abstract findById(payload: FindByIdPayload): Promise<AnyObject | undefined>;
|
||||||
|
|
||||||
abstract findById(id: string): Promise<WithId<TSchema> | undefined>;
|
abstract find(payload: FindPayload): Promise<AnyObject[]>;
|
||||||
|
|
||||||
abstract find(filter?: Filter<WithId<TSchema>>, options?: Options): Promise<WithId<TSchema>[]>;
|
abstract updateOne(payload: UpdatePayload): Promise<UpdateResult>;
|
||||||
|
|
||||||
abstract updateOne(
|
abstract updateMany(payload: UpdatePayload): Promise<UpdateResult>;
|
||||||
filter: Filter<WithId<TSchema>>,
|
|
||||||
expr: UpdateExpression,
|
|
||||||
arrayFilters?: Filter<WithId<TSchema>>[],
|
|
||||||
condition?: Filter<WithId<TSchema>>,
|
|
||||||
options?: UpdateOptions,
|
|
||||||
): Promise<UpdateResult>;
|
|
||||||
|
|
||||||
abstract updateMany(
|
abstract replace(payload: ReplacePayload): Promise<UpdateResult>;
|
||||||
filter: Filter<WithId<TSchema>>,
|
|
||||||
expr: UpdateExpression,
|
|
||||||
arrayFilters?: Filter<WithId<TSchema>>[],
|
|
||||||
condition?: Filter<WithId<TSchema>>,
|
|
||||||
options?: UpdateOptions,
|
|
||||||
): Promise<UpdateResult>;
|
|
||||||
|
|
||||||
abstract replace(filter: Filter<WithId<TSchema>>, document: TSchema): Promise<UpdateResult>;
|
abstract remove(payload: RemovePayload): Promise<number>;
|
||||||
|
|
||||||
abstract remove(filter: Filter<WithId<TSchema>>): Promise<RemoveResult>;
|
abstract count(payload: CountPayload): Promise<number>;
|
||||||
|
|
||||||
abstract count(filter?: Filter<WithId<TSchema>>): Promise<number>;
|
|
||||||
|
|
||||||
abstract flush(): Promise<void>;
|
abstract flush(): Promise<void>;
|
||||||
|
|
||||||
@@ -143,9 +125,9 @@ export abstract class Storage<TSchema extends Document = Document> {
|
|||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function addOptions<TSchema extends Document = Document>(
|
export function addOptions<TSchema extends AnyObject = AnyObject>(
|
||||||
cursor: Cursor<TSchema>,
|
cursor: Cursor<TSchema>,
|
||||||
options: Options,
|
options: QueryOptions,
|
||||||
): Cursor<TSchema> {
|
): Cursor<TSchema> {
|
||||||
if (options.sort) {
|
if (options.sort) {
|
||||||
cursor.sort(options.sort);
|
cursor.sort(options.sort);
|
||||||
@@ -167,17 +149,17 @@ export function addOptions<TSchema extends Document = Document>(
|
|||||||
|
|
||||||
type Status = "loading" | "ready";
|
type Status = "loading" | "ready";
|
||||||
|
|
||||||
export type ChangeEvent<TSchema extends Document = Document> =
|
export type ChangeEvent =
|
||||||
| {
|
| {
|
||||||
type: "insertOne" | "updateOne";
|
type: "insertOne" | "updateOne";
|
||||||
data: WithId<TSchema>;
|
data: AnyObject;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "insertMany" | "updateMany" | "remove";
|
type: "insertMany" | "updateMany" | "remove";
|
||||||
data: WithId<TSchema>[];
|
data: AnyObject[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Options = {
|
export type QueryOptions = {
|
||||||
sort?: {
|
sort?: {
|
||||||
[key: string]: 1 | -1;
|
[key: string]: 1 | -1;
|
||||||
};
|
};
|
||||||
@@ -197,3 +179,68 @@ export type Options = {
|
|||||||
export type Index = {
|
export type Index = {
|
||||||
[index: string]: any;
|
[index: string]: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type InsertOnePayload = Prettify<
|
||||||
|
CollectionPayload &
|
||||||
|
PrimaryKeyPayload & {
|
||||||
|
values: AnyObject;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type InsertManyPayload = Prettify<
|
||||||
|
CollectionPayload &
|
||||||
|
PrimaryKeyPayload & {
|
||||||
|
values: AnyObject[];
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type FindByIdPayload = Prettify<
|
||||||
|
CollectionPayload & {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type FindPayload = Prettify<
|
||||||
|
CollectionPayload & {
|
||||||
|
condition?: Criteria<AnyObject>;
|
||||||
|
options?: QueryOptions;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type UpdatePayload = Prettify<
|
||||||
|
CollectionPayload &
|
||||||
|
PrimaryKeyPayload & {
|
||||||
|
condition: Criteria<AnyObject>;
|
||||||
|
modifier: Modifier<AnyObject>;
|
||||||
|
arrayFilters?: AnyObject[];
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type ReplacePayload = Prettify<
|
||||||
|
CollectionPayload &
|
||||||
|
PrimaryKeyPayload & {
|
||||||
|
condition: Criteria<AnyObject>;
|
||||||
|
document: AnyObject;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type RemovePayload = Prettify<
|
||||||
|
CollectionPayload &
|
||||||
|
PrimaryKeyPayload & {
|
||||||
|
condition: Criteria<AnyObject>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type CountPayload = Prettify<
|
||||||
|
CollectionPayload & {
|
||||||
|
condition?: Criteria<AnyObject>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
type CollectionPayload = {
|
||||||
|
collection: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PrimaryKeyPayload = {
|
||||||
|
pkey: string;
|
||||||
|
};
|
||||||
|
|||||||
11
src/types.ts
11
src/types.ts
@@ -1,12 +1,8 @@
|
|||||||
import type { BSONRegExp, BSONType } from "bson";
|
import type { BSONRegExp, BSONType } from "bson";
|
||||||
|
|
||||||
export type Document = {
|
export type Prettify<T> = { [K in keyof T]: T[K] } & {};
|
||||||
[key: string]: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type WithId<TSchema> = {
|
export type AnyDocument = Record<string, any>;
|
||||||
id: string;
|
|
||||||
} & TSchema;
|
|
||||||
|
|
||||||
export type Filter<TSchema> = {
|
export type Filter<TSchema> = {
|
||||||
[P in keyof TSchema]?: Condition<TSchema[P]>;
|
[P in keyof TSchema]?: Condition<TSchema[P]>;
|
||||||
@@ -129,8 +125,7 @@ type Flatten<Type> = Type extends ReadonlyArray<infer Item> ? Item : Type;
|
|||||||
|
|
||||||
type IsAny<Type, ResultIfAny, ResultIfNotAny> = true extends false & Type ? ResultIfAny : ResultIfNotAny;
|
type IsAny<Type, ResultIfAny, ResultIfNotAny> = true extends false & Type ? ResultIfAny : ResultIfNotAny;
|
||||||
|
|
||||||
type FilterOperations<T> =
|
type FilterOperations<T> = T extends Record<string, any>
|
||||||
T extends Record<string, any>
|
|
||||||
? {
|
? {
|
||||||
[key in keyof T]?: FilterOperators<T[key]>;
|
[key in keyof T]?: FilterOperators<T[key]>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, it } from "@std/testing/bdd";
|
|||||||
import { expect } from "expect";
|
import { expect } from "expect";
|
||||||
|
|
||||||
import { IndexedDbCache } from "../src/databases/indexeddb/cache.ts";
|
import { IndexedDbCache } from "../src/databases/indexeddb/cache.ts";
|
||||||
import { Options } from "../src/storage/storage.ts";
|
import type { Options } from "../src/storage/storage.ts";
|
||||||
import type { WithId } from "../src/types.ts";
|
import type { WithId } from "../src/types.ts";
|
||||||
|
|
||||||
describe("IndexedDbCache", () => {
|
describe("IndexedDbCache", () => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { expect } from "expect";
|
|||||||
|
|
||||||
import { Collection } from "../src/collection.ts";
|
import { Collection } from "../src/collection.ts";
|
||||||
import { MemoryStorage } from "../src/databases/memory/storage.ts";
|
import { MemoryStorage } from "../src/databases/memory/storage.ts";
|
||||||
import { getUsers, UserDocument } from "./users.mock.ts";
|
import { getUsers, type UserDocument } from "./users.mock.ts";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { describe, it } from "@std/testing/bdd";
|
|||||||
import { expect } from "expect";
|
import { expect } from "expect";
|
||||||
|
|
||||||
import { hashCodeQuery } from "../src/hash.ts";
|
import { hashCodeQuery } from "../src/hash.ts";
|
||||||
import { Options } from "../src/mod.ts";
|
import type { Options } from "../src/mod.ts";
|
||||||
|
|
||||||
describe("hashCodeQuery", () => {
|
describe("hashCodeQuery", () => {
|
||||||
const filter = { name: { $eq: "Document 1" } };
|
const filter = { name: { $eq: "Document 1" } };
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { describe, it } from "@std/testing/bdd";
|
import { describe, it } from "@std/testing/bdd";
|
||||||
import { expect } from "expect";
|
import { expect } from "expect";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
import { Collection } from "../src/collection.ts";
|
import { Collection } from "../src/collection.ts";
|
||||||
import { MemoryStorage } from "../src/databases/memory/storage.ts";
|
import { MemoryStorage } from "../src/databases/memory/storage.ts";
|
||||||
@@ -13,20 +14,28 @@ describe("Field Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/set/#set-top-level-fields
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/set/#set-top-level-fields
|
||||||
*/
|
*/
|
||||||
it("should set top level fields", async () => {
|
it("should set top level fields", async () => {
|
||||||
const collection = new Collection<{
|
const collection = new Collection({
|
||||||
quantity: number;
|
name: "test",
|
||||||
instock: boolean;
|
adapter: new MemoryStorage("tests"),
|
||||||
reorder: boolean;
|
primaryKey: "id" as const,
|
||||||
details: {
|
schema: {
|
||||||
model: string;
|
id: z.string(),
|
||||||
make: string;
|
quantity: z.number(),
|
||||||
};
|
instock: z.boolean(),
|
||||||
tags: string[];
|
reorder: z.boolean(),
|
||||||
ratings: {
|
details: z.object({
|
||||||
by: string;
|
model: z.string(),
|
||||||
rating: number;
|
make: z.string(),
|
||||||
}[];
|
}),
|
||||||
}>("tests", new MemoryStorage("tests"));
|
tags: z.array(z.string()),
|
||||||
|
ratings: z.array(
|
||||||
|
z.object({
|
||||||
|
by: z.string(),
|
||||||
|
rating: z.number(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({
|
await collection.insertOne({
|
||||||
id: "100",
|
id: "100",
|
||||||
@@ -73,20 +82,28 @@ describe("Field Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/set/#set-fields-in-embedded-documents
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/set/#set-fields-in-embedded-documents
|
||||||
*/
|
*/
|
||||||
it("should set fields in the embedded documents", async () => {
|
it("should set fields in the embedded documents", async () => {
|
||||||
const collection = new Collection<{
|
const collection = new Collection({
|
||||||
quantity: number;
|
name: "test",
|
||||||
instock: boolean;
|
adapter: new MemoryStorage("tests"),
|
||||||
reorder: boolean;
|
primaryKey: "id",
|
||||||
details: {
|
schema: {
|
||||||
model: string;
|
id: z.string(),
|
||||||
make: string;
|
quantity: z.number(),
|
||||||
};
|
instock: z.boolean(),
|
||||||
tags: string[];
|
reorder: z.boolean(),
|
||||||
ratings: {
|
details: z.object({
|
||||||
by: string;
|
model: z.string(),
|
||||||
rating: number;
|
make: z.string(),
|
||||||
}[];
|
}),
|
||||||
}>("tests", new MemoryStorage("tests"));
|
tags: z.array(z.string()),
|
||||||
|
ratings: z.array(
|
||||||
|
z.object({
|
||||||
|
by: z.string(),
|
||||||
|
rating: z.number(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({
|
await collection.insertOne({
|
||||||
id: "100",
|
id: "100",
|
||||||
@@ -131,20 +148,28 @@ describe("Field Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/set/#set-elements-in-arrays
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/set/#set-elements-in-arrays
|
||||||
*/
|
*/
|
||||||
it("should set elements in arrays", async () => {
|
it("should set elements in arrays", async () => {
|
||||||
const collection = new Collection<{
|
const collection = new Collection({
|
||||||
quantity: number;
|
name: "test",
|
||||||
instock: boolean;
|
adapter: new MemoryStorage("tests"),
|
||||||
reorder: boolean;
|
primaryKey: "id",
|
||||||
details: {
|
schema: {
|
||||||
model: string;
|
id: z.string(),
|
||||||
make: string;
|
quantity: z.number(),
|
||||||
};
|
instock: z.boolean(),
|
||||||
tags: string[];
|
reorder: z.boolean(),
|
||||||
ratings: {
|
details: z.object({
|
||||||
by: string;
|
model: z.string(),
|
||||||
rating: number;
|
make: z.string(),
|
||||||
}[];
|
}),
|
||||||
}>("tests", new MemoryStorage("tests"));
|
tags: z.array(z.string()),
|
||||||
|
ratings: z.array(
|
||||||
|
z.object({
|
||||||
|
by: z.string(),
|
||||||
|
rating: z.number(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({
|
await collection.insertOne({
|
||||||
id: "100",
|
id: "100",
|
||||||
@@ -189,12 +214,18 @@ describe("Field Update Operators", () => {
|
|||||||
|
|
||||||
describe("$unset", () => {
|
describe("$unset", () => {
|
||||||
it("should unset keys", async () => {
|
it("should unset keys", async () => {
|
||||||
const collection = new Collection<{
|
const collection = new Collection({
|
||||||
item: string;
|
name: "test",
|
||||||
sku: string;
|
adapter: new MemoryStorage("tests"),
|
||||||
quantity: number;
|
primaryKey: "id",
|
||||||
instock: boolean;
|
schema: {
|
||||||
}>("tests", new MemoryStorage("tests"));
|
id: z.string(),
|
||||||
|
item: z.string(),
|
||||||
|
sku: z.string(),
|
||||||
|
quantity: z.number(),
|
||||||
|
instock: z.boolean(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertMany([
|
await collection.insertMany([
|
||||||
{ id: "1", item: "chisel", sku: "C001", quantity: 4, instock: true },
|
{ id: "1", item: "chisel", sku: "C001", quantity: 4, instock: true },
|
||||||
@@ -252,10 +283,20 @@ describe("Field Update Operators", () => {
|
|||||||
describe("Array Update Operators", () => {
|
describe("Array Update Operators", () => {
|
||||||
describe("$(update)", () => {
|
describe("$(update)", () => {
|
||||||
it("should replace a object in an array", async () => {
|
it("should replace a object in an array", async () => {
|
||||||
const collection = new Collection<{ grades: { id: string; value: number }[] }>(
|
const collection = new Collection({
|
||||||
"students",
|
name: "test",
|
||||||
new MemoryStorage("students"),
|
adapter: new MemoryStorage("tests"),
|
||||||
);
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
grades: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.string(),
|
||||||
|
value: z.number(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({
|
await collection.insertOne({
|
||||||
id: "1",
|
id: "1",
|
||||||
@@ -319,7 +360,7 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/positional/#update-values-in-an-array
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/positional/#update-values-in-an-array
|
||||||
*/
|
*/
|
||||||
// it("should update values in an array", async () => {
|
// it("should update values in an array", async () => {
|
||||||
// const collection = new Collection<{ grades: number[] }>("students", new MemoryStorage("students"));
|
// const collection = new Collection<"id", { grades: number[] }>("students", new MemoryStorage("students", "id"));
|
||||||
|
|
||||||
// await collection.insertMany([
|
// await collection.insertMany([
|
||||||
// { id: "1", grades: [85, 80, 80] },
|
// { id: "1", grades: [85, 80, 80] },
|
||||||
@@ -358,9 +399,9 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/positional/#update-documents-in-an-array
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/positional/#update-documents-in-an-array
|
||||||
*/
|
*/
|
||||||
// it("should update documents in an array", async () => {
|
// it("should update documents in an array", async () => {
|
||||||
// const collection = new Collection<{ grades: { grade: number; mean: number; std: number }[] }>(
|
// const collection = new Collection<"id", { grades: { grade: number; mean: number; std: number }[] }>(
|
||||||
// "students",
|
// "students",
|
||||||
// new MemoryStorage("students"),
|
// new MemoryStorage("students", "id"),
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// await collection.insertOne({
|
// await collection.insertOne({
|
||||||
@@ -406,9 +447,9 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/positional/#update-embedded-documents-using-multiple-field-matches
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/positional/#update-embedded-documents-using-multiple-field-matches
|
||||||
*/
|
*/
|
||||||
// it("should update embedded documents using multiple field matches", async () => {
|
// it("should update embedded documents using multiple field matches", async () => {
|
||||||
// const collection = new Collection<{ grades: { grade: number; mean: number; std: number }[] }>(
|
// const collection = new Collection<"id", { grades: { grade: number; mean: number; std: number }[] }>(
|
||||||
// "students",
|
// "students",
|
||||||
// new MemoryStorage("students"),
|
// new MemoryStorage("students", "id"),
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// await collection.insertOne({
|
// await collection.insertOne({
|
||||||
@@ -464,10 +505,16 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/pull/#remove-all-items-that-equal-a-specified-value
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/pull/#remove-all-items-that-equal-a-specified-value
|
||||||
*/
|
*/
|
||||||
it("should remove all items that equal a specified value", async () => {
|
it("should remove all items that equal a specified value", async () => {
|
||||||
const collection = new Collection<{ fruits: string[]; vegetables: string[] }>(
|
const collection = new Collection({
|
||||||
"stores",
|
name: "test",
|
||||||
new MemoryStorage("stores"),
|
adapter: new MemoryStorage("tests"),
|
||||||
);
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
fruits: z.array(z.string()),
|
||||||
|
vegetables: z.array(z.string()),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertMany([
|
await collection.insertMany([
|
||||||
{
|
{
|
||||||
@@ -519,7 +566,15 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/pull/#remove-all-items-that-match-a-specified--pull-condition
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/pull/#remove-all-items-that-match-a-specified--pull-condition
|
||||||
*/
|
*/
|
||||||
it("should remove all items that match a specific $pull condition", async () => {
|
it("should remove all items that match a specific $pull condition", async () => {
|
||||||
const collection = new Collection<{ votes: number[] }>("profiles", new MemoryStorage("profiles"));
|
const collection = new Collection({
|
||||||
|
name: "test",
|
||||||
|
adapter: new MemoryStorage("tests"),
|
||||||
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
votes: z.array(z.number()),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({ id: "1", votes: [3, 5, 6, 7, 7, 8] });
|
await collection.insertOne({ id: "1", votes: [3, 5, 6, 7, 7, 8] });
|
||||||
|
|
||||||
@@ -548,10 +603,20 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/pull/#remove-items-from-an-array-of-documents
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/pull/#remove-items-from-an-array-of-documents
|
||||||
*/
|
*/
|
||||||
it("should remove items from an array of documents", async () => {
|
it("should remove items from an array of documents", async () => {
|
||||||
const collection = new Collection<{ results: { item: string; score: number }[] }>(
|
const collection = new Collection({
|
||||||
"surveys",
|
name: "test",
|
||||||
new MemoryStorage("surveys"),
|
adapter: new MemoryStorage("tests"),
|
||||||
);
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
results: z.array(
|
||||||
|
z.object({
|
||||||
|
item: z.string(),
|
||||||
|
score: z.number(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertMany([
|
await collection.insertMany([
|
||||||
{
|
{
|
||||||
@@ -596,9 +661,26 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/pull/#remove-documents-from-nested-arrays
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/pull/#remove-documents-from-nested-arrays
|
||||||
*/
|
*/
|
||||||
it("should remove documents from nested arrays", async () => {
|
it("should remove documents from nested arrays", async () => {
|
||||||
const collection = new Collection<{
|
const collection = new Collection({
|
||||||
results: { item: string; score: number; answers: { q: number; a: number }[] }[];
|
name: "test",
|
||||||
}>("surveys", new MemoryStorage("surveys"));
|
adapter: new MemoryStorage("tests"),
|
||||||
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
results: z.array(
|
||||||
|
z.object({
|
||||||
|
item: z.string(),
|
||||||
|
score: z.number(),
|
||||||
|
answers: z.array(
|
||||||
|
z.object({
|
||||||
|
q: z.number(),
|
||||||
|
a: z.number(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertMany([
|
await collection.insertMany([
|
||||||
{
|
{
|
||||||
@@ -699,7 +781,15 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/push/#append-a-value-to-an-array
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/push/#append-a-value-to-an-array
|
||||||
*/
|
*/
|
||||||
it("should append a value to an array", async () => {
|
it("should append a value to an array", async () => {
|
||||||
const collection = new Collection<{ scores: number[] }>("students", new MemoryStorage("students"));
|
const collection = new Collection({
|
||||||
|
name: "test",
|
||||||
|
adapter: new MemoryStorage("tests"),
|
||||||
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
scores: z.array(z.number()),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({ id: "1", scores: [44, 78, 38, 80] });
|
await collection.insertOne({ id: "1", scores: [44, 78, 38, 80] });
|
||||||
|
|
||||||
@@ -726,7 +816,15 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/push/#append-a-value-to-arrays-in-multiple-documents
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/push/#append-a-value-to-arrays-in-multiple-documents
|
||||||
*/
|
*/
|
||||||
it("should append a value to arrays in multiple documents", async () => {
|
it("should append a value to arrays in multiple documents", async () => {
|
||||||
const collection = new Collection<{ scores: number[] }>("students", new MemoryStorage("students"));
|
const collection = new Collection({
|
||||||
|
name: "test",
|
||||||
|
adapter: new MemoryStorage("tests"),
|
||||||
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
scores: z.array(z.number()),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertMany([
|
await collection.insertMany([
|
||||||
{ id: "1", scores: [44, 78, 38, 80, 89] },
|
{ id: "1", scores: [44, 78, 38, 80, 89] },
|
||||||
@@ -763,7 +861,16 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/push/#append-multiple-values-to-an-array
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/push/#append-multiple-values-to-an-array
|
||||||
*/
|
*/
|
||||||
it("should append multiple values to an array", async () => {
|
it("should append multiple values to an array", async () => {
|
||||||
const collection = new Collection<{ name: string; scores: number[] }>("students", new MemoryStorage("students"));
|
const collection = new Collection({
|
||||||
|
name: "test",
|
||||||
|
adapter: new MemoryStorage("tests"),
|
||||||
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
scores: z.array(z.number()),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({ id: "1", name: "Joe", scores: [44, 78] });
|
await collection.insertOne({ id: "1", name: "Joe", scores: [44, 78] });
|
||||||
|
|
||||||
@@ -792,10 +899,20 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/push/#use--push-operator-with-multiple-modifiers
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/push/#use--push-operator-with-multiple-modifiers
|
||||||
*/
|
*/
|
||||||
it("should use $push operator with multiple modifiers", async () => {
|
it("should use $push operator with multiple modifiers", async () => {
|
||||||
const collection = new Collection<{ quizzes: { wk: number; score: number }[] }>(
|
const collection = new Collection({
|
||||||
"students",
|
name: "test",
|
||||||
new MemoryStorage("students"),
|
adapter: new MemoryStorage("tests"),
|
||||||
);
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
quizzes: z.array(
|
||||||
|
z.object({
|
||||||
|
wk: z.number(),
|
||||||
|
score: z.number(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({
|
await collection.insertOne({
|
||||||
id: "5",
|
id: "5",
|
||||||
@@ -845,7 +962,15 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/slice/#slice-from-the-end-of-the-array
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/slice/#slice-from-the-end-of-the-array
|
||||||
*/
|
*/
|
||||||
it("should slice from the end of the array", async () => {
|
it("should slice from the end of the array", async () => {
|
||||||
const collection = new Collection<{ scores: number[] }>("students", new MemoryStorage("students"));
|
const collection = new Collection({
|
||||||
|
name: "test",
|
||||||
|
adapter: new MemoryStorage("tests"),
|
||||||
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
scores: z.array(z.number()),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({ id: "1", scores: [40, 50, 60] });
|
await collection.insertOne({ id: "1", scores: [40, 50, 60] });
|
||||||
|
|
||||||
@@ -875,7 +1000,15 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/slice/#slice-from-the-front-of-the-array
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/slice/#slice-from-the-front-of-the-array
|
||||||
*/
|
*/
|
||||||
it("should slice from the front of the array", async () => {
|
it("should slice from the front of the array", async () => {
|
||||||
const collection = new Collection<{ scores: number[] }>("students", new MemoryStorage("students"));
|
const collection = new Collection({
|
||||||
|
name: "test",
|
||||||
|
adapter: new MemoryStorage("tests"),
|
||||||
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
scores: z.array(z.number()),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({ id: "2", scores: [89, 90] });
|
await collection.insertOne({ id: "2", scores: [89, 90] });
|
||||||
|
|
||||||
@@ -905,7 +1038,15 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/slice/#update-array-using-slice-only
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/slice/#update-array-using-slice-only
|
||||||
*/
|
*/
|
||||||
it("should update array using slice only", async () => {
|
it("should update array using slice only", async () => {
|
||||||
const collection = new Collection<{ scores: number[] }>("students", new MemoryStorage("students"));
|
const collection = new Collection({
|
||||||
|
name: "test",
|
||||||
|
adapter: new MemoryStorage("tests"),
|
||||||
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
scores: z.array(z.number()),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({ id: "3", scores: [89, 70, 100, 20] });
|
await collection.insertOne({ id: "3", scores: [89, 70, 100, 20] });
|
||||||
|
|
||||||
@@ -935,7 +1076,15 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/position/#add-elements-at-the-start-of-the-array
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/position/#add-elements-at-the-start-of-the-array
|
||||||
*/
|
*/
|
||||||
it("should add elements to the start of the array", async () => {
|
it("should add elements to the start of the array", async () => {
|
||||||
const collection = new Collection<{ scores: number[] }>("students", new MemoryStorage("students"));
|
const collection = new Collection({
|
||||||
|
name: "test",
|
||||||
|
adapter: new MemoryStorage("tests"),
|
||||||
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
scores: z.array(z.number()),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({ id: "1", scores: [100] });
|
await collection.insertOne({ id: "1", scores: [100] });
|
||||||
|
|
||||||
@@ -965,7 +1114,15 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/position/#add-elements-to-the-middle-of-the-array
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/position/#add-elements-to-the-middle-of-the-array
|
||||||
*/
|
*/
|
||||||
it("should add elements to the middle of the array", async () => {
|
it("should add elements to the middle of the array", async () => {
|
||||||
const collection = new Collection<{ scores: number[] }>("students", new MemoryStorage("students"));
|
const collection = new Collection({
|
||||||
|
name: "test",
|
||||||
|
adapter: new MemoryStorage("tests"),
|
||||||
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
scores: z.array(z.number()),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({ id: "2", scores: [50, 60, 70, 100] });
|
await collection.insertOne({ id: "2", scores: [50, 60, 70, 100] });
|
||||||
|
|
||||||
@@ -992,7 +1149,15 @@ describe("Array Update Operators", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should use a negative index to add elements to the array", async () => {
|
it("should use a negative index to add elements to the array", async () => {
|
||||||
const collection = new Collection<{ scores: number[] }>("students", new MemoryStorage("students"));
|
const collection = new Collection({
|
||||||
|
name: "test",
|
||||||
|
adapter: new MemoryStorage("tests"),
|
||||||
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
scores: z.array(z.number()),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({ id: "3", scores: [50, 60, 20, 30, 70, 100] });
|
await collection.insertOne({ id: "3", scores: [50, 60, 20, 30, 70, 100] });
|
||||||
|
|
||||||
@@ -1022,10 +1187,20 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/sort/#sort-array-of-documents-by-a-field-in-the-documents
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/sort/#sort-array-of-documents-by-a-field-in-the-documents
|
||||||
*/
|
*/
|
||||||
it("should sort array of documents by a field in the documents", async () => {
|
it("should sort array of documents by a field in the documents", async () => {
|
||||||
const collection = new Collection<{ quizzes: { id: number; score: number }[] }>(
|
const collection = new Collection({
|
||||||
"students",
|
name: "test",
|
||||||
new MemoryStorage("students"),
|
adapter: new MemoryStorage("tests"),
|
||||||
);
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
quizzes: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.number(),
|
||||||
|
score: z.number(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({
|
await collection.insertOne({
|
||||||
id: "1",
|
id: "1",
|
||||||
@@ -1074,7 +1249,15 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/sort/#sort-array-elements-that-are-not-documents
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/sort/#sort-array-elements-that-are-not-documents
|
||||||
*/
|
*/
|
||||||
it("should sort array elements that are not documents", async () => {
|
it("should sort array elements that are not documents", async () => {
|
||||||
const collection = new Collection<{ tests: number[] }>("students", new MemoryStorage("students"));
|
const collection = new Collection({
|
||||||
|
name: "test",
|
||||||
|
adapter: new MemoryStorage("tests"),
|
||||||
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
tests: z.array(z.number()),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({ id: "2", tests: [89, 70, 89, 50] });
|
await collection.insertOne({ id: "2", tests: [89, 70, 89, 50] });
|
||||||
|
|
||||||
@@ -1106,7 +1289,15 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/sort/#update-array-using-sort-only
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/sort/#update-array-using-sort-only
|
||||||
*/
|
*/
|
||||||
it("should update array using sort only", async () => {
|
it("should update array using sort only", async () => {
|
||||||
const collection = new Collection<{ tests: number[] }>("students", new MemoryStorage("students"));
|
const collection = new Collection({
|
||||||
|
name: "test",
|
||||||
|
adapter: new MemoryStorage("tests"),
|
||||||
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
tests: z.array(z.number()),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({ id: "3", tests: [89, 70, 100, 20] });
|
await collection.insertOne({ id: "3", tests: [89, 70, 100, 20] });
|
||||||
|
|
||||||
@@ -1140,11 +1331,20 @@ describe("Array Update Operators", () => {
|
|||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/inc
|
* @see https://www.mongodb.com/docs/manual/reference/operator/update/inc
|
||||||
*/
|
*/
|
||||||
it("should increment and decrement values", async () => {
|
it("should increment and decrement values", async () => {
|
||||||
const collection = new Collection<{
|
const collection = new Collection({
|
||||||
sku: string;
|
name: "test",
|
||||||
quantity: number;
|
adapter: new MemoryStorage("tests"),
|
||||||
metrics: { orders: number; ratings: number };
|
primaryKey: "id",
|
||||||
}>("products", new MemoryStorage("products"));
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
sku: z.string(),
|
||||||
|
quantity: z.number(),
|
||||||
|
metrics: z.object({
|
||||||
|
orders: z.number(),
|
||||||
|
ratings: z.number(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({ id: "1", sku: "abc123", quantity: 10, metrics: { orders: 2, ratings: 3.5 } });
|
await collection.insertOne({ id: "1", sku: "abc123", quantity: 10, metrics: { orders: 2, ratings: 3.5 } });
|
||||||
|
|
||||||
@@ -1176,10 +1376,20 @@ describe("Array Update Operators", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should increment value of an array element with array index", async () => {
|
it("should increment value of an array element with array index", async () => {
|
||||||
const collection = new Collection<{ details: { id: number; quantity: number }[] }>(
|
const collection = new Collection({
|
||||||
"products",
|
name: "test",
|
||||||
new MemoryStorage("products"),
|
adapter: new MemoryStorage("tests"),
|
||||||
);
|
primaryKey: "id",
|
||||||
|
schema: {
|
||||||
|
id: z.string(),
|
||||||
|
details: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.number(),
|
||||||
|
quantity: z.number(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await collection.insertOne({
|
await collection.insertOne({
|
||||||
id: "3",
|
id: "3",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { clone } from "../src/clone.ts";
|
import { clone } from "../src/clone.ts";
|
||||||
import { WithId } from "../src/types.ts";
|
import type { WithId } from "../src/types.ts";
|
||||||
|
|
||||||
const users: WithId<UserDocument>[] = [
|
const users: WithId<UserDocument>[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user