From be301a9bbe565d3956ac74787408713ca164cef6 Mon Sep 17 00:00:00 2001 From: Hunter Perrin Date: Fri, 14 Jun 2024 13:08:11 -0700 Subject: [PATCH] Add more information on properly using transactions in Nymph. --- .../user-guide/creating-entities/+page.svelte | 6 +- .../user-guide/entity-class/+page.svelte | 5 +- .../user-guide/entity-querying/+page.svelte | 13 +- .../user-guide/transactions/+page.svelte | 154 ++++++++++++++++++ 4 files changed, 164 insertions(+), 14 deletions(-) diff --git a/src/routes/user-guide/creating-entities/+page.svelte b/src/routes/user-guide/creating-entities/+page.svelte index 0b49b69..581ec5d 100644 --- a/src/routes/user-guide/creating-entities/+page.svelte +++ b/src/routes/user-guide/creating-entities/+page.svelte @@ -39,7 +39,7 @@ if (someBlogPost.guid == null) { await blogPost.$save(); let superPosts = await nymph.getEntities( - { class: BlogPost.class }, + { class: BlogPost }, { type: '&', tag: 'super-post' } ); @@ -106,8 +106,8 @@ console.log(entity.foo.bar); // Outputs undefined.`} Nymph. Instead, it creates instances without data called "sleeping references". You can use `$wake` on the entity or `$wakeAll` on the parent entity to get the entity's data from the DB. The $wakeAll - method will awaken all sleeping references in the entity's data. You can - call $clearCache + method will awaken all sleeping references in the entity's data. You can call + $clearCache in Node.js or $refresh in the client to turn all the entities back into sleeping references.

diff --git a/src/routes/user-guide/entity-class/+page.svelte b/src/routes/user-guide/entity-class/+page.svelte index f2fa28f..b978e52 100644 --- a/src/routes/user-guide/entity-class/+page.svelte +++ b/src/routes/user-guide/entity-class/+page.svelte @@ -179,8 +179,9 @@ await nymph.deleteEntities([entity]);`} if it's true, the function uses $equals.
  • - $arraySearch - Search an array for the entity and return the - corresponding key. Takes two arguments, the array and a boolean + $arraySearch - Search an array for the entity and return its + index, or -1 if it's not found. Takes two arguments, the + array and a boolean strict. If strict is false or undefined, the function uses $is diff --git a/src/routes/user-guide/entity-querying/+page.svelte b/src/routes/user-guide/entity-querying/+page.svelte index cbec977..cdc9a9e 100644 --- a/src/routes/user-guide/entity-querying/+page.svelte +++ b/src/routes/user-guide/entity-querying/+page.svelte @@ -25,13 +25,12 @@ class typeof Entity Entity - The class used to create each entity. It must have a factory static method that returns a new instance. + The Entity class to query. limit @@ -247,7 +242,7 @@ if (cronUser.guid == null) { >The named property contains the value (its JSON string is found within the property's JSON string). - {"{type: '&', array: ['foo', 'bar']}"} + {"{type: '&', contain: ['foo', 'bar']}"} {"entity.foo = ['bar', 'baz']"} diff --git a/src/routes/user-guide/transactions/+page.svelte b/src/routes/user-guide/transactions/+page.svelte index b8759d6..d0f873d 100644 --- a/src/routes/user-guide/transactions/+page.svelte +++ b/src/routes/user-guide/transactions/+page.svelte @@ -1,5 +1,6 @@ Transactions - User Guide - Nymph.js + {@html github}
    @@ -41,6 +42,12 @@ instance. It will be tied to a specific connection to the database.

    +

    + The transaction instance of Nymph has its own set of classes. You can use + its getEntityClass method to get the proper classes for that instance + of Nymph. +

    +

    When you start a new transaction, entities retrieved from that transaction's Nymph instance will have that instance within their static nymph + +

    + Here is an example of a class that uses a transaction to delete all of its + children when it is deleted. If any of its children cannot be deleted, then + the transaction is rolled back, meaning none of its children get deleted. +

    + + { + static ETYPE = 'todo'; + static class = 'Todo'; + + protected $clientEnabledMethods = []; + protected $allowlistData? = ['name', 'done', 'parent']; + protected $protectedTags = []; + protected $allowlistTags? = []; + + constructor() { + super(); + + this.$data.name = ''; + this.$data.done = false; + } + + async $getUniques() { + return [ + \`\${this.$data.user?.guid}:\${this.$data.parent?.guid}:\${this.$data.name}\`, + ]; + } + + /** + * Set a new Nymph instance on this and all contained entities. + */ + $setNymph(nymph: Nymph) { + this.$nymph = nymph; + if (!this.$asleep()) { + if (this.$data.user) { + this.$data.user.$nymph = nymph; + } + if (this.$data.group) { + this.$data.group.$nymph = nymph; + } + if (this.$data.parent) { + this.$data.parent.$setNymph(nymph); + } + } + } + + async $save() { + const tilmeld = enforceTilmeld(this); + if (!tilmeld.gatekeeper()) { + // Only allow logged in users to save. + throw new Error('You are not logged in.'); + } + + // Validate the entity's data. + Joi.attempt( + this.$getValidatable(), + Joi.object().keys({ + ...nymphJoiProps, + ...tilmeldJoiProps, + + name: Joi.string().trim(false).max(500, 'utf8').required(), + done: Joi.boolean().required(), + parent: Joi.object().instance(Todo), + }), + 'Invalid Todo: ', + ); + + try { + return await super.$save(); + } catch (e: any) { + if (e instanceof EntityUniqueConstraintError) { + throw new Error('There is already a todo for that.'); + } + throw e; + } + } + + async $delete() { + const transaction = 'todo-delete-' + this.guid; + const nymph = this.$nymph; + const tnymph = await nymph.startTransaction(transaction); + this.$setNymph(tnymph); + + try { + // Delete this todo's children. + const children = await tnymph.getEntities( + { + class: tnymph.getEntityClass(Todo), + skipAc: true, + }, + { + type: '&', + ref: ['parent', this], + }, + ); + + for (let child of children) { + if (!(await child.$delete())) { + throw new Error("Couldn't delete child todo."); + } + } + + // Delete todo. + let success = await super.$delete(); + if (success) { + success = await tnymph.commit(transaction); + } else { + await tnymph.rollback(transaction); + } + this.$setNymph(nymph); + return success; + } catch (e: any) { + await tnymph.rollback(transaction); + this.$setNymph(nymph); + throw e; + } + } +} +`} + /> + +

    + The $setNymph method is used to make sure the entity and all + referenced entities use the transactional Nymph instance. The + tnymph Nymph instance is used during the transaction, and the + children are retrieved using the proper class with + tnymph.getEntityClass(Todo). +

    @@ -70,5 +220,9 @@