Skip to content

Commit

Permalink
Merge pull request #158 from oliver-oloughlin/patch/semantic-API-chan…
Browse files Browse the repository at this point in the history
…ges-and-improvements

Patch/semantic api changes and improvements
  • Loading branch information
oliver-oloughlin authored Jan 13, 2024
2 parents 774a0aa + e335afd commit 25a821c
Show file tree
Hide file tree
Showing 11 changed files with 432 additions and 440 deletions.
71 changes: 38 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ _Supported Deno verisons:_ **^1.38.5**
- CRUD operations for selected and ranged documents with strong typing.
- Primary (unique) and secondary (non-unique) indexing.
- Extensible model strategy (Zod supported).
- Serialized, compressed and segmented storage for large objects that exceed the
native size limit.
- Real-time data watching.
- Serialized, compressed and segmented storage for large objects.
- Listen to real-time data updates.
- Support for pagination and filtering.
- Intervals and loops built on queues.
- Message queues at database and collection level with topics.
- Support for atomic operations.

Expand Down Expand Up @@ -45,6 +43,7 @@ _Supported Deno verisons:_ **^1.38.5**
- [updateOne()](#updateone)
- [updateOneBySecondaryIndex()](#updateonebysecondaryindex)
- [upsert()](#upsert)
- [upsertByPrimaryIndex()](#upsertbyprimaryindex)
- [delete()](#delete)
- [deleteByPrimaryIndex()](#deletebyprimaryindex)
- [deleteBySecondaryIndex()](#deletebysecondaryindex)
Expand Down Expand Up @@ -460,13 +459,10 @@ const result = await db.users.updateOneBySecondaryIndex(

### upsert()

Update an existing document by either id or primary index, or set a new document
entry if no document with matching id/index exists. When upserting by primary
index, an id can be optionally specified which will be used when setting a new
document entry, otherwise an id will be generated.
Update an existing document by id, or set a new document entry if no matching
document exists.

```ts
// Upsert by id
const result = await db.users.upsert({
id: "user_id",
update: { username: "Chris" },
Expand All @@ -482,9 +478,16 @@ const result = await db.users.upsert({
}
}
})
```

// Upsert by index
const result = await db.users.upsert({
### upsertByPrimaryIndex()

Update an existing document by a primary index, or set a new entry if no
matching document exists. An id can be optionally specified which will be used
when creating a new document entry.

```ts
const result = await db.users.upsertByPrimaryIndex({
index: ["username", "Jack"],
update: { username: "Chris" },
set: {
Expand Down Expand Up @@ -975,44 +978,46 @@ db.listenQueue(async (data) => {

### setInterval()

Create an interval built on queues that can run indefinitely or until an exit
condition is met. Interval defaults to 1 hour if not set, while there is an
enforced minimum start delay of 1 second to ensure the queue listener is
registered before the first delivery.
Create an interval built on queues that can run indefinitely or as long as a
while condition is met. Interval time is given in milliseconds, and can be set
by either a static number or dynamically by a function. There is an enforced
minimum start delay of 1 second to ensure the queue listener is registered
before the first delivery.

```ts
// Will repeat indefinitely with 1 hour interval
db.setInterval(() => console.log("Hello World!"))

// First callback is invoked after a 10 second delay, after that there is a 5 second delay between callbacks
db.setInterval(() => console.log("I terminate after running 10 times"), {
// Delay before the first callback is invoked
startDelay: 10_000,
// Will repeat indefinitely with 1 second interval
db.setInterval(() => console.log("Hello World!"), 1_000)

// Fixed interval of 5 seconds
interval: 5_000,

// ...or set a dynamic interval
interval: ({ count }) => count * 500
// First callback starts after a 10 second delay, after that there is a random delay between 0 and 5 seconds
db.setInterval(
() => console.log("I terminate after running 10 times"),
() => Math.random() * 5_000,
{
// Delay before the first callback is invoked
startDelay: 10_000,

// Count starts at 0, exitOn is run before the current callback
exitOn: ({ count }) => count === 10,
})
// Count starts at 0, exitOn is run before the current callback
while: ({ count }) => count < 10,
},
)
```

### loop()

Create a loop built on queues that can run indefinitely or until an exit
Create a loop built on queues that can run indefinitely or as long as a while
condition is met. In contrast to `setInterval()`, the callback function in a
loop is run sequentially, meaning the next callback is not enqueued until the
previous task finishes. There is an enforced minimum start delay of 1 second to
ensure the queue listener is registered before the first delivery.

```ts
// Prints "Hello World!" 10 times, with a 3 second delay between each iteration
// Sequentially prints "Hello World!" indefinitely with no delay between each iteration
db.loop(() => console.log("Hello World!"))

// Sequentially prints "Hello World!" 10 times, with a 3 second delay between each iteration
db.loop(() => console.log("Hello World!"), {
delay: 3_000,
exitOn: ({ count }) => count === 10,
while: ({ count }) => count < 10,
})
```

Expand Down
116 changes: 58 additions & 58 deletions src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
HistoryEntry,
IdempotentListener,
IdGenerator,
IdUpsertInput,
IdUpsert,
IndexDataEntry,
KvId,
KvKey,
Expand All @@ -23,7 +23,7 @@ import type {
ParseInputType,
PossibleCollectionOptions,
PrimaryIndexKeys,
PrimaryIndexUpsertInput,
PrimaryIndexUpsert,
QueueHandlers,
QueueListenerOptions,
QueueMessageHandler,
Expand All @@ -35,7 +35,6 @@ import type {
UpdateManyOptions,
UpdateOptions,
UpdateStrategy,
UpsertInput,
UpsertOptions,
WatchOptions,
} from "./types.ts"
Expand Down Expand Up @@ -845,15 +844,10 @@ export class Collection<
}

/**
* Update an existing document by either id or primary index, or set a new document
* entry if no document with matching id/index exists.
*
* When upserting by primary index, an id can be optionally specified which
* will be used when setting a new document entry, otherwise an id will be generated.
* Update an existing document by id, or set a new document entry if no matching document exists.
*
* @example
* ```ts
* // Upsert by id
* const result = await db.users.upsert({
* id: "user_id",
* update: { username: "Chris" },
Expand All @@ -871,10 +865,37 @@ export class Collection<
* })
* ```
*
* @param input - Upsert by id input.
* @param options - Upsert options.
* @returns A promise resolving to either CommitResult or CommitError.
*/
async upsert<
const TUpsertOptions extends UpsertOptions,
>(
input: IdUpsert<TInput, TOutput, TUpsertOptions["strategy"]>,
options?: TUpsertOptions,
) {
const updateCr = await this.update(input.id, input.update, options)

if (updateCr.ok) {
return updateCr
}

// Set new entry with given id
return await this.set(input.id, input.set, {
...options,
overwrite: false,
})
}

/**
* Update an existing document by a primary index, or set a new entry if no matching document exists.
*
* An id can be optionally specified which will be used when creating a new document entry.
*
* @example
* ```ts
* // Upsert by index
* const result = await db.users.upsert({
* const result = await db.users.upsertByPrimaryIndex({
* index: ["username", "Jack"],
* update: { username: "Chris" },
* set: {
Expand All @@ -891,67 +912,46 @@ export class Collection<
* })
* ```
*
* @param input - Upsert input, including id or index, update data and set data.
* @param input - Upsert by primary index input.
* @param options - Upsert options.
* @returns A promise resolving to either CommitResult or CommitError.
*/
async upsert<
async upsertByPrimaryIndex<
const TIndex extends PrimaryIndexKeys<TOutput, TOptions>,
const TUpsertOptions extends UpsertOptions,
>(
input: UpsertInput<TInput, TOutput, TIndex, TUpsertOptions["strategy"]>,
input: PrimaryIndexUpsert<
TInput,
TOutput,
TIndex,
TUpsertOptions["strategy"]
>,
options?: TUpsertOptions,
) {
// Check if is id or primary index upsert
if ((input as any).index !== undefined) {
const inp = input as PrimaryIndexUpsertInput<
TInput,
TOutput,
TIndex,
TUpsertOptions["strategy"]
>

// First attempt update
const updateCr = await this.updateByPrimaryIndex(
...inp.index,
inp.update,
options,
)

if (updateCr.ok) {
return updateCr
}

// If id is present, set new entry with given id
if (inp.id) {
return await this.set(inp.id, inp.set, {
...options,
overwrite: false,
})
}

// If no id, set new entry with generated id
return await this.add(inp.set, {
...options,
overwrite: false,
})
} else {
// First attempt update
const id =
(input as IdUpsertInput<TInput, TOutput, TUpsertOptions["strategy"]>).id

const updateCr = await this.update(id, input.update, options)
// First attempt update
const updateCr = await this.updateByPrimaryIndex(
...input.index,
input.update,
options,
)

if (updateCr.ok) {
return updateCr
}
if (updateCr.ok) {
return updateCr
}

// Set new entry with given id
return await this.set(id, input.set, {
// If id is present, set new entry with given id
if (input.id) {
return await this.set(input.id, input.set, {
...options,
overwrite: false,
})
}

// If no id, add new entry with generated id
return await this.add(input.set, {
...options,
overwrite: false,
})
}

/**
Expand Down
2 changes: 0 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ export const GET_MANY_KEY_LIMIT = 10
export const UINT8ARRAY_LENGTH_LIMIT = 65_536

// Defaults
export const DEFAULT_INTERVAL = 60 * 60 * 1_000 // 1 hour

export const DEFAULT_INTERVAL_RETRY = 10

export const DEFAULT_LOOP_RETRY = 10
Expand Down
Loading

0 comments on commit 25a821c

Please sign in to comment.