diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..6581fe76
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Nullstack
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 0fb09b26..5d385b69 100644
--- a/README.md
+++ b/README.md
@@ -35,4 +35,4 @@ Get to know the [Nullstack Contributors](https://nullstack.app/contributors)
## License
-Nullstack is released under the [MIT License](https://opensource.org/licenses/MIT).
+Nullstack is released under the [MIT License](LICENSE).
diff --git a/client/instanceProxyHandler.js b/client/instanceProxyHandler.js
index 6c68ac68..fb8d9d2d 100644
--- a/client/instanceProxyHandler.js
+++ b/client/instanceProxyHandler.js
@@ -1,5 +1,5 @@
import client from './client'
-import { generateContext } from './context'
+import context, { generateContext } from './context'
import { generateObjectProxy } from './objectProxyHandler'
export const instanceProxies = new WeakMap()
@@ -15,8 +15,26 @@ const instanceProxyHandler = {
}
const { [name]: named } = {
[name]: (args) => {
- const context = generateContext({ ...target._attributes, ...args })
- return target[name].call(proxy, context)
+ const scopedContext = generateContext({ ...target._attributes, ...args })
+ let result
+ try {
+ result = target[name].call(proxy, scopedContext)
+ } catch (error) {
+ if (context.catch) {
+ context.catch(error)
+ } else {
+ throw error
+ }
+ return null
+ }
+ if (result instanceof Promise) {
+ return new Promise((resolve, reject) => {
+ result.then(resolve).catch((error) => {
+ context.catch ? context.catch(error) : reject(error)
+ })
+ })
+ }
+ return result
},
}
return named
diff --git a/loaders/add-source-to-node.js b/loaders/add-source-to-node.js
index 435306c6..c92bd5a0 100644
--- a/loaders/add-source-to-node.js
+++ b/loaders/add-source-to-node.js
@@ -13,7 +13,9 @@ module.exports = function (source) {
if (path.parent.type === 'JSXAttribute') {
if (path.node.name.startsWith('on')) {
const element = path.findParent((p) => p.type === 'JSXOpeningElement' && p.node.attributes)
- const hasSource = element.node.attributes.find((a) => a.name.name === 'source')
+ const hasSource = element.node.attributes.find((a) => {
+ return a.type === 'JSXAttribute' && a.name.name === 'source'
+ })
if (!hasSource) {
const start = element.node.attributes[0].start
uniquePositions.add(start)
diff --git a/package.json b/package.json
index 9c45b534..bf2b945e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "nullstack",
- "version": "0.17.4",
+ "version": "0.17.5",
"description": "Full Stack Javascript Components for one-dev armies",
"main": "nullstack.js",
"author": "Mortaro",
diff --git a/plugins/bindable.js b/plugins/bindable.js
index efcf938e..b3424084 100644
--- a/plugins/bindable.js
+++ b/plugins/bindable.js
@@ -9,7 +9,7 @@ function transform({ node, environment }) {
const object = node.attributes.bind.object ?? {}
const property = node.attributes.bind.property
if (node.type === 'textarea') {
- node.children = [object[property]]
+ node.children = [object[property] ?? '']
} else if (node.type === 'input' && node.attributes.type === 'checkbox') {
node.attributes.checked = object[property]
} else {
diff --git a/server/printError.js b/server/printError.js
index a2daa484..4fbd51d4 100644
--- a/server/printError.js
+++ b/server/printError.js
@@ -1,4 +1,9 @@
+import context from './context'
+
export default function (error) {
+ if (context.catch) {
+ context.catch(error)
+ }
const lines = error.stack.split(`\n`)
let initiator = lines.find((line) => line.indexOf('Proxy') > -1)
if (initiator) {
diff --git a/server/reqres.js b/server/reqres.js
index c743ad43..13cdb10c 100644
--- a/server/reqres.js
+++ b/server/reqres.js
@@ -1,2 +1,20 @@
-const reqres = {}
+class ReqRes {
+
+ request = null
+ response = null
+
+ set(request, response) {
+ this.request = request
+ this.response = response
+ }
+
+ clear() {
+ this.request = null
+ this.response = null
+ }
+
+}
+
+const reqres = new ReqRes()
+
export default reqres
diff --git a/server/server.js b/server/server.js
index dac39bb9..3f612891 100644
--- a/server/server.js
+++ b/server/server.js
@@ -40,6 +40,7 @@ for (const method of ['get', 'post', 'put', 'patch', 'delete', 'all']) {
server[method] = function (...args) {
if (typeof args[1] === 'function' && args[1].name === '_invoke') {
return original(args[0], bodyParser.text({ limit: server.maximumPayloadSize }), async (request, response) => {
+ reqres.set(request, response)
const params = {}
for (const key of Object.keys(request.params)) {
params[key] = extractParamValue(request.params[key])
@@ -48,14 +49,17 @@ for (const method of ['get', 'post', 'put', 'patch', 'delete', 'all']) {
params[key] = extractParamValue(request.query[key])
}
if (request.method !== 'GET') {
- Object.assign(params, deserialize(request.body))
+ const payload = typeof request.body === 'object' ? JSON.stringify(request.body) : request.body
+ Object.assign(params, deserialize(payload))
}
try {
const subcontext = generateContext({ request, response, ...params })
const result = await args[1](subcontext)
+ reqres.clear()
response.json(result)
} catch (error) {
printError(error)
+ reqres.clear()
response.status(500).json({})
}
})
@@ -224,6 +228,7 @@ server.start = function () {
server.all(`/${prefix}/:hash/:methodName.json`, async (request, response) => {
const payload = request.method === 'GET' ? request.query.payload : request.body
+ reqres.set(request, response)
const args = deserialize(payload)
const { hash, methodName } = request.params
const [invokerHash, boundHash] = hash.split('-')
@@ -241,12 +246,15 @@ server.start = function () {
try {
const subcontext = generateContext({ request, response, ...args })
const result = await method.call(boundKlass, subcontext)
+ reqres.clear()
response.json({ result })
} catch (error) {
printError(error)
+ reqres.clear()
response.status(500).json({})
}
} else {
+ reqres.clear()
response.status(404).json({})
}
})
@@ -255,21 +263,23 @@ server.start = function () {
if (request.originalUrl.split('?')[0].indexOf('.') > -1) {
return next()
}
- reqres.request = request
- reqres.response = response
+ reqres.set(request, response)
const scope = await prerender(request, response)
if (!response.headersSent) {
const status = scope.context.page.status
const html = template(scope)
- reqres.request = null
- reqres.response = null
+ reqres.clear()
response.status(status).send(html)
} else {
- reqres.request = null
- reqres.response = null
+ reqres.clear()
}
})
+ server.use((error, _request, response, _next) => {
+ printError(error)
+ response.status(500).json({})
+ })
+
if (!server.less) {
if (!server.port) {
console.info('\x1b[31mServer port is not defined!\x1b[0m')
diff --git a/tests/client.js b/tests/client.js
index 24b58b0e..9a0b4da5 100644
--- a/tests/client.js
+++ b/tests/client.js
@@ -1,6 +1,7 @@
import Nullstack from 'nullstack'
import Application from './src/Application'
+import CatchError from './src/CatchError.njs'
import vueable from './src/plugins/vueable'
Nullstack.use(vueable)
@@ -12,4 +13,11 @@ context.start = function () {
setTimeout(() => (context.startTimedValue = true), 1000)
}
+context.catch = async function (error) {
+ CatchError.logError({ message: error.message })
+ if (context.environment.development) {
+ console.error(error)
+ }
+}
+
export default context
diff --git a/tests/server.js b/tests/server.js
index 62df2bbb..e5428264 100644
--- a/tests/server.js
+++ b/tests/server.js
@@ -1,14 +1,17 @@
import Nullstack from 'nullstack'
import cors from 'cors'
+import express from 'express'
import Application from './src/Application'
+import CatchError from './src/CatchError'
import ContextProject from './src/ContextProject'
import ContextSecrets from './src/ContextSecrets'
import ContextSettings from './src/ContextSettings'
import ContextWorker from './src/ContextWorker'
import ExposedServerFunctions from './src/ExposedServerFunctions'
import vueable from './src/plugins/vueable'
+import ReqRes from './src/ReqRes'
Nullstack.use(vueable)
@@ -22,6 +25,7 @@ context.server.use(
optionsSuccessStatus: 200,
}),
)
+context.server.use(express.json())
context.worker.staleWhileRevalidate = [/[0-9]/]
context.worker.cacheFirst = [/[0-9]/]
@@ -40,6 +44,9 @@ context.server.put('/data/put/:param', ExposedServerFunctions.getData)
context.server.patch('/data/patch/:param', ExposedServerFunctions.getData)
context.server.delete('/data/delete/:param', ExposedServerFunctions.getData)
+context.server.get('/exposed-server-function-url.json', ReqRes.exposedServerFunction)
+context.server.get('/nested-exposed-server-function-url.json', ReqRes.nestedExposedServerFunction)
+
context.server.get('/custom-api-before-start', (request, response) => {
response.json({ startValue: context.startValue })
})
@@ -57,6 +64,10 @@ for (const method of methods) {
})
}
+context.server.get('/vaidamerdanaapi.json', (_request, response) => {
+ response.vaidamerdanaapi()
+})
+
context.startIncrementalValue = 0
context.start = async function () {
@@ -68,4 +79,11 @@ context.start = async function () {
context.startIncrementalValue++
}
+context.catch = async function (error) {
+ CatchError.logError({ message: error.message })
+ if (context.environment.development) {
+ console.error(error)
+ }
+}
+
export default context
diff --git a/tests/src/Application.njs b/tests/src/Application.njs
index 25ff8241..997742df 100644
--- a/tests/src/Application.njs
+++ b/tests/src/Application.njs
@@ -4,6 +4,7 @@ import AnchorModifiers from './AnchorModifiers'
import './Application.css'
import ArrayAttributes from './ArrayAttributes'
import BodyFragment from './BodyFragment'
+import CatchError from './CatchError'
import ChildComponent from './ChildComponent'
import ComponentTernary from './ComponentTernary'
import Context from './Context'
@@ -40,6 +41,7 @@ import PublicServerFunctions from './PublicServerFunctions.njs'
import PureComponents from './PureComponents'
import Refs from './Refs'
import RenderableComponent from './RenderableComponent'
+import ReqRes from './ReqRes'
import RoutesAndParams from './RoutesAndParams'
import RouteScroll from './RouteScroll'
import ServerFunctions from './ServerFunctions'
@@ -137,6 +139,8 @@ class Application extends Nullstack {
+
+