Skip to content

Instantly share code, notes, and snippets.

@aral
Last active May 31, 2023 13:16
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aral/fc4115fdf338e02d735ae58e245817ce to your computer and use it in GitHub Desktop.
Save aral/fc4115fdf338e02d735ae58e245817ce to your computer and use it in GitHub Desktop.
WhatDB? Proposed syntax.

WhatDB?

Proposed syntax.

Preparing a query

Internally, this prepares the predicate for an array.filter() operation.

.where( propertyName )               : Start query. Returns query object.
.and( propertyName )                 : Continues query with logical AND. Returns query object.
.or ( propertyName )                 : Continues query with logical OR. Returns query object.
.startsWith(), .endsWith()           : Adds string start/end match comparison to the query. Returns query object.
.includes()                          : Adds substring match to the query. Returns query object.
.is()|.isGreaterThan()|.isLessThan() : Adds value comparison to the query. Returns query object.

Executing a query.

Internally, this executes and returns the result of an array.filter() operation. If a specific set of properties is requested, an array.map() is also performed.

.get ([property1, property2, …])     : Executes query. If properties are specified, returns only those properties.

Sorting.

Internally, these result in an array.sort() operation.

.sortBy()                            : Sorts by given property in ascending order.
.sortAscendingBy()                   : Alias for sortBy()
.sortDescendingBy()                  : Sorts by given property in descending order.

Grouping and aggregation.

Internally, these result in an array.reduce() operation.

.groupBy()                           : Groups the result by the passed property. Returns array.
.sum()                               : Reduces to the sum of the given property. Returns a numerical value.
.average()                           : Reduces to the average of the given property. Returns a numerical value.

Result values and assertions.

.expectOne()                         : Returns the only item in the array. If array has more than one item, throws.
.expectMany()                        : Returns array. If array has no items or one item, throws.
.expectNone()                        : Returns null. If array is not empty, throws.
.expect(n)                           : Returns the array with N items. If array.length !== n, throws.

Example:

require('@small-tech/whatdb')

const people = [
  { name: 'Aral', pet: {type: 'dog', name: 'Oskar'}, likesToPlay: 'RimWorld' },
  { name: 'Laura', pet: {type: 'dog', name: 'Oskar'}, likesToPlay: 'Dishonored 2'}
]

let person

person = people
  .where('name')
  .is('Laura')
  .and('pet.name')
  .is('Oskar')
  .get()

// Returns [ { name: 'Laura', pet: {type: 'dog', name: 'Oskar'}, likesToPlay: 'Dishonored 2'} ]

try {
  person = people.where('pet.type').is('dog').and('likesToPlay').includes('World').get('name').expectOne()
} catch (error) {
  // e.g., if there is more than one.
  console.log(`Query error: ${error}`)
}

// Returns: {name: 'Aral'}
// With get('name.value') instead, would return the string value: 'Aral'
@aschrijver
Copy link

aschrijver commented Sep 11, 2020

Negations?

not()
isNot('cat')
andNot('likesToPurr')

Are these aliases? Not sure I get exactly how each would work.

Yea, me neither.. exactly. There should be some way to exclude from a query, that is still intuitive wrt query syntax already adopted. I typed this just off the top of my head. Let's see..

legalPetOwner = people.where('pet.type').isNot('jaguar')
legalPetOwner = people.where('pet.type').not().in(['jaguar', 'lion', 'chimpansee'])
decentBoss = people.where('company.minWage').greaterThan(60000).andNot('name').is('Jeff')
decentBoss = people.where(company).isNot(null).and('lastname').not().startsWith('Zuck)

This can certainly be improved.. looks a bit contrived. Note the isNot(null) to check for existence of a nested object, where in 3rd example it is unclear what a missing where('company.minWage') indicates. You might need a .exists() in that case, maybe. I thought of the not() as just a general way to negate whatever comes after it.

The andNot().is() could also be a and().isNot(), but andNot() leaves more options for what comes after it and might improve readability in some cases. The last not() certainly looks contrived, and should be an andNot('lastname').

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment