diff --git a/src/Document.lua b/src/Document.lua index 7f4e902..c56a567 100644 --- a/src/Document.lua +++ b/src/Document.lua @@ -15,6 +15,7 @@ function Document.new(collection, key, validate, lockId, data) lockId = lockId, data = data, closed = false, + callingBeforeClose = false, }, Document) end @@ -55,7 +56,7 @@ end @return Promise<()> ]=] function Document:save() - assert(not self.closed, "Cannot save a closed document") + assert(not self.closed and not self.callingBeforeClose, "Cannot save a closed document") return self.collection.data:save(self.collection.dataStore, self.key, function(value) if value.lockId ~= self.lockId then @@ -79,16 +80,37 @@ end Throws an error if the document was closed. ::: + :::warning + Throws an error or yields if the beforeClose callback does. + ::: + @return Promise<()> ]=] function Document:close() - assert(not self.closed, "Cannot close a closed document") + assert(not self.closed and not self.callingBeforeClose, "Cannot close a closed document") + + local function close() + self.closed = true + + self.collection.openDocuments[self.key] = nil - self.closed = true + self.collection.autoSave:removeDocument(self) + end + + if self.beforeCloseCallback ~= nil then + self.callingBeforeClose = true + + local ok, err = pcall(self.beforeCloseCallback) + + if not ok then + close() + error(`beforeClose callback threw error: {tostring(err)}`) + end - self.collection.openDocuments[self.key] = nil + self.callingBeforeClose = false + end - self.collection.autoSave:removeDocument(self) + close() return self.collection.data:save(self.collection.dataStore, self.key, function(value) if value.lockId ~= self.lockId then @@ -105,4 +127,20 @@ function Document:close() end) end +--[=[ + Sets a callback that is run inside `document:close` before it saves. The document can be read and written to in the + callback. + + :::warning + Throws an error if it was called previously. + ::: + + @param callback () -> () +]=] +function Document:beforeClose(callback) + assert(self.beforeCloseCallback == nil, "Document:beforeClose can only be called once") + + self.beforeCloseCallback = callback +end + return Document diff --git a/src/Document.test.lua b/src/Document.test.lua index 6cde4a7..a6ecfbf 100644 --- a/src/Document.test.lua +++ b/src/Document.test.lua @@ -10,6 +10,7 @@ local DEFAULT_OPTIONS = { } return function(x) + local assertEqual = x.assertEqual local shouldThrow = x.shouldThrow x.test("it should not merge close into save when save is running", function(context) @@ -200,4 +201,75 @@ return function(x) promise:expect() end) + + x.nested("Document:beforeClose", function() + x.test("throws when setting twice", function(context) + local document = context.lapis.createCollection("collection", DEFAULT_OPTIONS):load("document"):expect() + + document:beforeClose(function() end) + + shouldThrow(function() + document:beforeClose(function() end) + end, "Document:beforeClose can only be called once") + end) + + x.test("throws when calling close in callback", function(context) + local document = context.lapis.createCollection("collection", DEFAULT_OPTIONS):load("document"):expect() + + document:beforeClose(function() + document:close() + end) + + shouldThrow(function() + document:close() + end, "beforeClose callback threw error") + end) + + x.test("throws when calling save in callback", function(context) + local document = context.lapis.createCollection("collection", DEFAULT_OPTIONS):load("document"):expect() + + document:beforeClose(function() + document:save() + end) + + shouldThrow(function() + document:close() + end, "beforeClose callback threw error") + end) + + x.test("closes document even if document error", function(context) + local collection = context.lapis.createCollection("collection", DEFAULT_OPTIONS) + + local promise = collection:load("document") + local document = promise:expect() + + document:beforeClose(function() + error("error") + end) + + shouldThrow(function() + document:close() + end) + + assert(collection:load("document") ~= promise, "collection:load should return a new promise") + + shouldThrow(function() + document:write({ foo = "baz" }) + end, "Cannot write to a closed document") + end) + + x.test("saves new data", function(context) + local document = context.lapis.createCollection("collection", DEFAULT_OPTIONS):load("document"):expect() + + document:beforeClose(function() + document:read() -- This checks that read doesn't error in the callback. + + document:write({ foo = "new" }) + end) + + document:close():expect() + + assertEqual(context.read("collection", "document").data.foo, "new") + end) + end) end diff --git a/src/init.lua b/src/init.lua index be73f57..845b1d6 100644 --- a/src/init.lua +++ b/src/init.lua @@ -33,6 +33,7 @@ export type Document = { write: (self: Document, T) -> (), save: (self: Document) -> PromiseTypes.TypedPromise<()>, close: (self: Document) -> PromiseTypes.TypedPromise<()>, + beforeClose: (self: Document, callback: () -> ()) -> (), } --[=[ diff --git a/wally.lock b/wally.lock index f109279..92ee559 100644 --- a/wally.lock +++ b/wally.lock @@ -14,10 +14,10 @@ dependencies = [] [[package]] name = "nezuo/lapis" -version = "0.2.4" -dependencies = [["Promise", "evaera/promise@4.0.0"], ["DataStoreServiceMock", "nezuo/data-store-service-mock@0.3.0"], ["Midori", "nezuo/midori@0.1.2"]] +version = "0.2.5" +dependencies = [["Promise", "evaera/promise@4.0.0"], ["DataStoreServiceMock", "nezuo/data-store-service-mock@0.3.0"], ["Midori", "nezuo/midori@0.1.3"]] [[package]] name = "nezuo/midori" -version = "0.1.2" +version = "0.1.3" dependencies = [] diff --git a/wally.toml b/wally.toml index 1b80dfa..23a108d 100644 --- a/wally.toml +++ b/wally.toml @@ -10,5 +10,5 @@ realm = "server" Promise = "evaera/promise@4.0.0" [dev-dependencies] -Midori = "nezuo/midori@0.1.2" +Midori = "nezuo/midori@0.1.3" DataStoreServiceMock = "nezuo/data-store-service-mock@0.3.0"