Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
Improve naming
Browse files Browse the repository at this point in the history
- Replace term/type `Historical` with more precise `Record` and
`SavedRecord`:
  - Define Record to contain the Pouch-specific `_id` field
  - Define SavedRecord to contain other Pouch-specifics, like `_rev`

- Document Adapter interface

- Update variable identifiers to align with new terminology
  • Loading branch information
g. nicholas d'andrea committed May 19, 2021
1 parent 6a03931 commit e0b5051
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 130 deletions.
160 changes: 78 additions & 82 deletions packages/db/src/meta/pouch/adapters/base.ts
Expand Up @@ -9,7 +9,9 @@ import type { CollectionName, Collections } from "@truffle/db/meta/collections";
import * as Id from "@truffle/db/meta/id";

import type {
Historical,
Record,
SavedRecord,
RecordReference,
Adapter,
Definition,
Definitions
Expand Down Expand Up @@ -78,60 +80,59 @@ export abstract class Databases<C extends Collections> implements Adapter<C> {
}
}

public async every<
N extends CollectionName<C>,
I extends PouchDB.Core.IdMeta
>(collectionName: N): Promise<Historical<I>[]> {
public async every<N extends CollectionName<C>, I>(
collectionName: N
): Promise<SavedRecord<I>[]> {
await this.ready;

const { rows }: any = await this.collections[collectionName].allDocs({
include_docs: true
});

const result = rows
.map(({ doc }) => doc)
// filter out any views
.filter(({ views }) => !views);

return result;
return (
rows
.map(({ doc }) => doc)
// filter out any views
.filter(({ views }) => !views)
);
}

public async retrieve<
N extends CollectionName<C>,
I extends PouchDB.Core.IdMeta
>(collectionName: N, references: (Pick<I, "_id"> | undefined)[]) {
public async retrieve<N extends CollectionName<C>, I>(
collectionName: N,
records: (Pick<Record<I>, "_id"> | undefined)[]
) {
await this.ready;

const unordered = await this.search<N, I>(collectionName, {
const unorderedSavedRecords = await this.search<N, I>(collectionName, {
selector: {
_id: {
$in: references
.filter((obj): obj is Pick<I, "_id"> => !!obj)
$in: records
.filter((obj): obj is Pick<Record<I>, "_id"> => !!obj)
.map(({ _id }) => _id)
}
}
});

const byId = unordered.reduce(
(byId, savedInput) =>
savedInput
const savedRecordById = unorderedSavedRecords.reduce(
(byId, savedRecord) =>
savedRecord
? {
...byId,
[savedInput._id as string]: savedInput
[savedRecord._id as string]: savedRecord
}
: byId,
{} as { [id: string]: Historical<I> }
{} as { [id: string]: SavedRecord<I> }
);

return references.map(reference =>
reference ? byId[reference._id] : undefined
return records.map(record =>
record ? savedRecordById[record._id] : undefined
);
}

public async search<
N extends CollectionName<C>,
I extends PouchDB.Core.IdMeta
>(collectionName: N, options: PouchDB.Find.FindRequest<{}>) {
public async search<N extends CollectionName<C>, I>(
collectionName: N,
options: PouchDB.Find.FindRequest<{}>
) {
await this.ready;

// allows searching with `id` instead of pouch's internal `_id`,
Expand All @@ -150,108 +151,103 @@ export abstract class Databases<C extends Collections> implements Adapter<C> {
selector: fixIdSelector(options.selector)
});

const result: Historical<I>[] = docs;
const savedRecords: SavedRecord<I>[] = docs;

return result;
return savedRecords;
}

public async record<
N extends CollectionName<C>,
I extends PouchDB.Core.IdMeta
>(
public async record<N extends CollectionName<C>, I>(
collectionName: N,
inputs: (I | undefined)[],
records: (Record<I> | undefined)[],
options: { overwrite?: boolean } = {}
) {
await this.ready;

const { overwrite = false } = options;

const inputsById: {
[id: string]: I;
} = inputs
.filter((input): input is I => !!input)
.map(input => ({
[input._id]: input
const recordsById: {
[id: string]: Record<I>;
} = records
.filter((record): record is Record<I> => !!record)
.map(record => ({
[record._id]: record
}))
.reduce((a, b) => ({ ...a, ...b }), {} as { [id: string]: I });
.reduce((a, b) => ({ ...a, ...b }), {} as { [id: string]: Record<I> });

const currentInputsById: {
[id: string]: Historical<I>;
const existingSavedRecordById: {
[id: string]: SavedRecord<I>;
} = (
await this.retrieve<N, I>(
collectionName,
Object.keys(inputsById).map(_id => ({ _id }))
Object.keys(recordsById).map(_id => ({ _id } as Pick<Record<I>, "_id">))
)
)
.filter((currentInput): currentInput is Historical<I> => !!currentInput)
.map(currentInput => ({ [currentInput._id]: currentInput }))
.filter(
(existingSavedRecord): existingSavedRecord is SavedRecord<I> =>
!!existingSavedRecord
)
.map(existingSavedRecord => ({
[existingSavedRecord._id]: existingSavedRecord
}))
.reduce(
(a, b) => ({ ...a, ...b }),
{} as { [id: string]: Historical<I> }
{} as { [id: string]: SavedRecord<I> }
);

const savedInputsById: {
[id: string]: Historical<I>;
const savedRecordById: {
[id: string]: SavedRecord<I>;
} = (
await Promise.all(
Object.entries(inputsById).map(async ([_id, input]) => {
const currentInput = currentInputsById[_id];
Object.entries(recordsById).map(async ([_id, record]) => {
const existingSavedRecord = existingSavedRecordById[_id];

if (currentInput && !overwrite) {
return currentInput;
if (existingSavedRecord && !overwrite) {
return existingSavedRecord;
}

const { _rev = undefined } = currentInput || {};
const { _rev = undefined } = existingSavedRecord || {};

const { rev } = await this.collections[collectionName].put({
...input,
_rev,
_id
...record,
_rev
});

return {
...input,
_rev: rev,
_id
} as Historical<I>;
...record,
_rev: rev
} as SavedRecord<I>;
})
)
)
.map(savedInput => ({ [savedInput._id]: savedInput }))
.map(savedRecord => ({ [savedRecord._id]: savedRecord }))
.reduce(
(a, b) => ({ ...a, ...b }),
{} as { [id: string]: Historical<I> }
{} as { [id: string]: SavedRecord<I> }
);

return inputs.map(input => input && savedInputsById[input._id]);
return records.map(record => record && savedRecordById[record._id]);
}

public async forget<
N extends CollectionName<C>,
I extends PouchDB.Core.IdMeta
>(collectionName: N, references: (Pick<I, "_id"> | undefined)[]) {
public async forget<N extends CollectionName<C>, T>(
collectionName: N,
references: (RecordReference<T> | undefined)[]
) {
await this.ready;

const retrievedInputs = await this.retrieve<N, I>(
collectionName,
references
);
const savedRecords = await this.retrieve<N, T>(collectionName, references);

const retrievedInputsById = retrievedInputs
.filter(
(retrievedInput): retrievedInput is Historical<I> => !!retrievedInput
)
.map(retrievedInput => ({
[retrievedInput._id]: retrievedInput
const savedRecordById = savedRecords
.filter((savedRecord): savedRecord is SavedRecord<T> => !!savedRecord)
.map(savedRecord => ({
[savedRecord._id]: savedRecord
}))
.reduce(
(a, b) => ({ ...a, ...b }),
{} as { [id: string]: Historical<I> }
{} as { [id: string]: SavedRecord<T> }
);

await Promise.all(
Object.values(retrievedInputsById).map(async ({ _id, _rev }) => {
Object.values(savedRecordById).map(async ({ _id, _rev }) => {
await this.collections[collectionName].put({
_rev,
_id,
Expand Down
72 changes: 58 additions & 14 deletions packages/db/src/meta/pouch/types.ts
Expand Up @@ -6,34 +6,78 @@ import PouchDB from "pouchdb";
import type { Collections, CollectionName } from "@truffle/db/meta/collections";
import type * as Id from "@truffle/db/meta/id";

export type History = PouchDB.Core.IdMeta & PouchDB.Core.GetMeta;
type Identified = PouchDB.Core.IdMeta;
type Retrieved = PouchDB.Core.GetMeta;

export type Historical<T> = T & History;
/**
* An object is a Record<T> if it has identification info
* (i.e., `{ _id: string }`)
*/
export type Record<T> = T & Identified;

/**
* SavedRecord<T> objects additionally contain PouchDB revision info, etc.
*/
export type SavedRecord<T> = Record<T> & Retrieved;

/**
* A reference has only identification information
*/
export type RecordReference<T> = Pick<Record<T>, "_id">;

/**
* Low-level interface for PouchDB adapters - allows basic CRUD operations
* for arbitrary (weakly-typed) data types.
*/
export interface Adapter<C extends Collections> {
every<N extends CollectionName<C>, I extends PouchDB.Core.IdMeta>(
/**
* Retrieve and return every saved record for a given collection name
*/
every<N extends CollectionName<C>, T>(
collectionName: N
): Promise<Historical<I>[]>;
): Promise<SavedRecord<T>[]>;

retrieve<N extends CollectionName<C>, I extends PouchDB.Core.IdMeta>(
/**
* Retrieve specific saved records of a given collection name by list of
* references.
*
* @param references - Can be sparse
* @return - Items in list correspond to `references` by index (also sparse)
*/
retrieve<N extends CollectionName<C>, T>(
collectionName: N,
references: (Pick<I, "_id"> | undefined)[]
): Promise<(Historical<I> | undefined)[]>;
references: (RecordReference<T> | undefined)[]
): Promise<(SavedRecord<T> | undefined)[]>;

search<N extends CollectionName<C>, I extends PouchDB.Core.IdMeta>(
/**
* Search for saved records of a given collection name by PouchDB find
* syntax
*/
search<N extends CollectionName<C>, T>(
collectionName: N,
options: PouchDB.Find.FindRequest<{}>
): Promise<Historical<I>[]>;
): Promise<SavedRecord<T>[]>;

record<N extends CollectionName<C>, I extends PouchDB.Core.IdMeta>(
/**
* Create or update saved records of a given collection name.
*
* @param records - Can be sparse
* @return - Items in list correspond to `records` by index (also sparse)
*/
record<N extends CollectionName<C>, T>(
collectionName: N,
inputs: (I | undefined)[],
records: (Record<T> | undefined)[],
options: { overwrite?: boolean }
): Promise<(Historical<I> | undefined)[]>;
): Promise<(SavedRecord<T> | undefined)[]>;

forget<N extends CollectionName<C>, I extends PouchDB.Core.IdMeta>(
/**
* Delete saved records
*
* @param references - Can be sparse
*/
forget<N extends CollectionName<C>, T>(
collectionName: N,
references: (Pick<I, "_id"> | undefined)[]
references: (RecordReference<T> | undefined)[]
): Promise<void>;
}

Expand Down

0 comments on commit e0b5051

Please sign in to comment.