diff --git a/README.md b/README.md index d5d911c..60650a7 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,22 @@ const writtenEvents = await client.writeEvents([ *Note that according to the CloudEvents standard, event IDs must be of type string.* +#### Using the `isEventQlTrue` precondition + +If you want to write events depending on an EventQL query, import the `isEventQlTrue` function and pass it as an array of preconditions in the second argument: + +```typescript +import { isEventQlTrue } from 'eventsourcingdb'; + +const writtenEvents = await client.writeEvents([ + // events +], [ + isEventQlTrue(`FROM e IN events WHERE e.type == 'io.eventsourcingdb.library.book-borrowed' PROJECT INTO COUNT() < 10`) +]); +``` + +*Note that the query must return a single row with a single value, which is interpreted as a boolean.* + ### Reading Events To read all events of a subject, call the `readEvents` function with the subject as the first argument and an options object as the second argument. Set the `recursive` option to `false`. This ensures that only events of the given subject are returned, not events of nested subjects. diff --git a/docker/Dockerfile b/docker/Dockerfile index 7ed0ba0..2185588 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1 +1 @@ -FROM thenativeweb/eventsourcingdb:1.0.3 +FROM thenativeweb/eventsourcingdb:preview diff --git a/src/Precondition.ts b/src/Precondition.ts index f7060dd..cf4feaa 100644 --- a/src/Precondition.ts +++ b/src/Precondition.ts @@ -7,6 +7,10 @@ interface IsSubjectOnEventIdPrecondition { eventId: string; } +interface IsEventQlTruePrecondition { + query: string; +} + type Precondition = | { type: 'isSubjectPristine'; @@ -15,6 +19,10 @@ type Precondition = | { type: 'isSubjectOnEventId'; payload: IsSubjectOnEventIdPrecondition; + } + | { + type: 'isEventQlTrue'; + payload: IsEventQlTruePrecondition; }; export type { Precondition }; diff --git a/src/isEventQlTrue.ts b/src/isEventQlTrue.ts new file mode 100644 index 0000000..ad0bc6a --- /dev/null +++ b/src/isEventQlTrue.ts @@ -0,0 +1,10 @@ +import type { Precondition } from './Precondition.js'; + +const isEventQlTrue = (query: string): Precondition => { + return { + type: 'isEventQlTrue', + payload: { query }, + }; +}; + +export { isEventQlTrue }; diff --git a/src/writeEvents.test.ts b/src/writeEvents.test.ts index 8b28ed2..96e08f0 100644 --- a/src/writeEvents.test.ts +++ b/src/writeEvents.test.ts @@ -3,6 +3,7 @@ import { afterEach, beforeEach, suite, test } from 'node:test'; import { Container } from './Container.js'; import type { EventCandidate } from './EventCandidate.js'; import { getImageVersionFromDockerfile } from './getImageVersionFromDockerfile.js'; +import { isEventQlTrue } from './isEventQlTrue.js'; import { isSubjectOnEventId } from './isSubjectOnEventId.js'; import { isSubjectPristine } from './isSubjectPristine.js'; @@ -143,4 +144,45 @@ suite('writeEvents', { timeout: 30_000 }, () => { }, ); }); + + test('supports the isEventQlTrue precondition.', async (): Promise => { + const client = container.getClient(); + + const firstEvent: EventCandidate = { + source: 'https://www.eventsourcingdb.io', + subject: '/test', + type: 'io.eventsourcingdb.test', + data: { + value: 23, + }, + }; + + await client.writeEvents([firstEvent]); + + const secondEvent: EventCandidate = { + source: 'https://www.eventsourcingdb.io', + subject: '/test', + type: 'io.eventsourcingdb.test', + data: { + value: 42, + }, + }; + + await assert.rejects( + async () => { + await client.writeEvents( + [secondEvent], + [isEventQlTrue('FROM e IN events PROJECT INTO COUNT() == 0')], + ); + }, + error => { + assert.ok(error instanceof Error); + assert.equal( + error.message, + "Failed to write events, got HTTP status code '409', expected '200'.", + ); + return true; + }, + ); + }); });