Skip to content

Commit

Permalink
Merge pull request #83 from doseofted/client-side-hooks
Browse files Browse the repository at this point in the history
New client-side hooks
  • Loading branch information
doseofted authored Mar 4, 2024
2 parents e461f1c + 14ee571 commit 8c5fb70
Show file tree
Hide file tree
Showing 17 changed files with 1,267 additions and 1,601 deletions.
5 changes: 5 additions & 0 deletions .changeset/brown-berries-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@doseofted/prim-rpc": patch
---

Added .preRequest and .postRequest hooks, available in client options
5 changes: 5 additions & 0 deletions .changeset/purple-oranges-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@doseofted/prim-rpc": minor
---

Pre-call and pre-request hooks can now return/resolve function calls early (and the return value from this hook must be an object consisting of .args and optionally .result)
20 changes: 10 additions & 10 deletions apps/documentation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@
},
"type": "module",
"devDependencies": {
"@ark-ui/react": "2.1.1",
"@ark-ui/react": "2.2.2",
"@astrojs/mdx": "^2.1.0",
"@astrojs/react": "^3.0.9",
"@astrojs/sitemap": "^3.0.5",
"@astrojs/tailwind": "^5.1.0",
"@astrojs/vercel": "^7.0.2",
"@astrojs/vercel": "^7.3.4",
"@doseofted/prim-example": "workspace:*",
"@doseofted/prim-rpc": "workspace:*",
"@doseofted/prim-rpc-plugins": "workspace:*",
Expand All @@ -51,40 +51,40 @@
"@fontsource-variable/plus-jakarta-sans": "^5.0.19",
"@formkit/auto-animate": "^0.8.1",
"@iconify-json/ph": "^1.1.10",
"@iconify-json/simple-icons": "^1.1.89",
"@iconify-json/simple-icons": "^1.1.93",
"@iconify/react": "^4.1.1",
"@nanostores/react": "^0.7.1",
"@studio-freight/lenis": "^1.0.34",
"@studio-freight/lenis": "^1.0.39",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",
"@tweakpane/core": "^2.0.3",
"@tweakpane/plugin-essentials": "^0.2.1",
"@types/common-tags": "^1.8.4",
"@types/react": "^18.2.48",
"@types/react": "^18.2.61",
"@types/react-dom": "^18.2.18",
"astro": "^4.4.3",
"astro": "^4.4.9",
"astro-icon": "1.1.0",
"astro-seo": "^0.8.0",
"color2k": "^2.0.3",
"common-tags": "^1.8.2",
"consola": "^3.2.3",
"daisyui": "4.7.2",
"defu": "^6.1.4",
"framer-motion": "^11.0.3",
"framer-motion": "^11.0.8",
"just-safe-get": "^4.2.0",
"just-safe-set": "^4.2.1",
"just-shuffle": "^4.2.0",
"mermaid": "^10.7.0",
"mitt": "^3.0.1",
"motion": "^10.17.0",
"nanostores": "^0.10.0",
"pts": "^0.11.5",
"query-string": "^8.1.0",
"pts": "^0.11.6",
"query-string": "^9.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"rehype-external-links": "^3.0.0",
"rehype-pretty-code": "0.13.0",
"shiki": "1.1.6",
"shiki": "1.1.7",
"tailwind-merge": "^2.2.1",
"tailwind-scrollbar": "^3.0.5",
"tailwindcss": "^3.4.1",
Expand Down
2 changes: 1 addition & 1 deletion apps/documentation/src/content/docs/learn/security.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ import { parse } from "valibot"
const prim = createPrimServer({
module,
preCall(args, func) {
if ("params" in func) return parse(func.params, args)
if ("params" in func) return { args: parse(func.params, args) }
throw new Error("Function validation is required")
},
})
Expand Down
48 changes: 47 additions & 1 deletion apps/documentation/src/content/docs/reference/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,29 @@ There are handlers already available to use but you may also create your own.
</Button>
</ButtonGroup>

## <Icon class="w-6 h-6" name="ph:brackets-curly-bold"/> Pre-Request Hook

<DataListOptions />

<CodeFile>

```typescript
import TTLCache from "@isaacs/ttlcache"

const cache = new TTLCache({ ttl: 1000 * 5 })
const prim = createPrimClient({
preRequest(args, name) {
if (cache.has(name)) return { result: cache.get(name) }
return { args }
},
})
```

</CodeFile>

The pre-request hook is optional and is executed _before_ sending an RPC to the server. It is given the arguments passed
to your function. This may be useful for logging, client-side checks, or caching.

## <Icon class="w-6 h-6" name="ph:brackets-curly-bold"/> Pre-Call Hook

<DataListOptions />
Expand All @@ -437,7 +460,7 @@ import { parse } from "valibot"

const prim = createPrimServer({
preCall(args, func) {
if ("params" in func) return parse(func.params, args)
if ("params" in func) return { args: parse(func.params, args) }
throw new Error("Argument validation is required")
},
})
Expand Down Expand Up @@ -473,6 +496,29 @@ The post-call hook is optional and is executed _after_ calling a given function.
function given to the server and the function that was called. **Do not call the function inside of the hook** as it
will be executed a second time.

## <Icon class="w-6 h-6" name="ph:brackets-curly-bold"/> Post-Request Hook

<DataListOptions />

<CodeFile>

```typescript
import TTLCache from "@isaacs/ttlcache"

const cache = new TTLCache({ ttl: 1000 * 5 })
const prim = createPrimClient({
postRequest(result, name) {
if (!cache.has(name)) cache.set(name, result)
return result
},
})
```

</CodeFile>

The post-request hook is optional and is executed _after_ receiving an RPC result from the server. It is given the
result of your function call. This may be useful for logging, client-side checks, or caching.

## <Icon class="w-6 h-6" name="ph:brackets-curly-bold"/> Flags (Experimental)

<DataListOptions synced />
Expand Down
2 changes: 1 addition & 1 deletion libs/example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
],
"devDependencies": {
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.11.8",
"@types/node": "^20.11.24",
"lodash-es": "^4.17.21"
}
}
20 changes: 10 additions & 10 deletions libs/plugins/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,26 @@
"@doseofted/prim-example": "workspace:*",
"@doseofted/prim-rpc": "workspace:*",
"@fastify/multipart": "^8.1.0",
"@hono/node-server": "^1.5.0",
"@hono/node-server": "^1.8.2",
"@msgpack/msgpack": "3.0.0-beta2",
"@remix-run/node": "^2.4.0",
"@remix-run/node": "^2.8.0",
"@types/express": "^4.17.21",
"@types/formidable": "^3.4.5",
"@types/node": "^20.11.8",
"@types/node": "^20.11.24",
"@types/supertest": "^6.0.2",
"@types/ws": "^8.5.10",
"@vitest/web-worker": "^1.2.2",
"@whatwg-node/server": "^0.9.24",
"astro": "4.4.3",
"@whatwg-node/server": "^0.9.26",
"astro": "4.4.9",
"axios": "^1.6.7",
"express": "^4.18.2",
"express": "^4.18.3",
"fastify": "^4.25.2",
"form-data": "^4.0.0",
"formidable": "^3.5.1",
"h3": "^1.10.1",
"hono": "^4.0.5",
"next": "14.1.0",
"query-string": "^8.1.0",
"h3": "^1.11.1",
"hono": "^4.0.8",
"next": "14.1.1",
"query-string": "^9.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"socket.io": "^4.7.4",
Expand Down
8 changes: 4 additions & 4 deletions libs/rpc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,18 @@
"just-safe-get": "^4.2.0",
"mitt": "^3.0.1",
"nanoid": "^5.0.4",
"proxy-deep": "^4.0.0",
"query-string": "^8.1.0",
"proxy-deep": "^4.0.1",
"query-string": "^9.0.0",
"serialize-error": "^11.0.3",
"type-fest": "^4.10.1"
},
"devDependencies": {
"@doseofted/prim-example": "workspace:*",
"@msgpack/msgpack": "3.0.0-beta2",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.11.8",
"@types/node": "^20.11.24",
"superjson": "^2.2.1",
"typedoc": "^0.25.7"
"typedoc": "^0.25.9"
},
"type": "module",
"exports": {
Expand Down
79 changes: 79 additions & 0 deletions libs/rpc/src/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,85 @@ describe("Prim Client can throw errors", () => {
})
})

describe("Prim Client can use its specific hooks", () => {
test("Pre-call hooks with local module", async () => {
const prim = createPrimClient({
module,
preRequest(args) {
if (typeof args[0] === "object" && "greeting" in args[0]) args[0].greeting = "Bye"
return { args }
},
})
const args = { greeting: "Hi", name: "Ted" }
const expectedNormally = module.sayHello(args)
const expected = module.sayHello({ ...args, greeting: "Bye" })
const result = await prim.sayHello(args)
expect(result).not.toEqual(expectedNormally)
expect(result).toEqual(expected)
})
test("Pre-call hooks with remote module", async () => {
const { callbackPlugin, methodPlugin, callbackHandler, methodHandler } = createPrimTestingPlugins()
createPrimServer({ module, callbackHandler, methodHandler })
const prim = createPrimClient<IModule>({
callbackPlugin,
methodPlugin,
preRequest(args) {
if (typeof args[0] === "object" && "greeting" in args[0]) args[0].greeting = "Bye"
return { args }
},
})
const args = { greeting: "Hi", name: "Ted" }
const expectedNormally = module.sayHello(args)
const expected = module.sayHello({ ...args, greeting: "Bye" })
const result = await prim.sayHello(args)
expect(result).not.toEqual(expectedNormally)
expect(result).toEqual(expected)
})
test("Post-call hooks with local module", async () => {
const prim = createPrimClient({
module,
postRequest(_result) {
return "Intercepted!"
},
})
const args = { greeting: "Hi", name: "Ted" }
const expectedNormally = module.sayHello(args)
const expected = "Intercepted!"
const result = await prim.sayHello(args)
expect(result).not.toEqual(expectedNormally)
expect(result).toEqual(expected)
})
test("Post-call hooks with remote module", async () => {
const { callbackPlugin, methodPlugin, callbackHandler, methodHandler } = createPrimTestingPlugins()
createPrimServer({ module, callbackHandler, methodHandler })
const prim = createPrimClient<IModule>({
callbackPlugin,
methodPlugin,
postRequest(_result) {
return "Intercepted!"
},
})
const args = { greeting: "Hi", name: "Ted" }
const expectedNormally = module.sayHello(args)
const expected = "Intercepted!"
const result = await prim.sayHello(args)
expect(result).not.toEqual(expectedNormally)
expect(result).toEqual(expected)
})
test("Post-call hooks with remote module, errored", async () => {
const { callbackPlugin, methodPlugin, callbackHandler, methodHandler } = createPrimTestingPlugins()
createPrimServer({ module, callbackHandler, methodHandler })
const prim = createPrimClient<IModule>({
callbackPlugin,
methodPlugin,
postRequest(_error) {
return "Intercepted!"
},
})
await expect(prim.oops()).rejects.toBe("Intercepted!")
})
})

describe("Prim Client can make use of callbacks", () => {
test("with local source", async () => {
const prim = createPrimClient({ module })
Expand Down
Loading

0 comments on commit 8c5fb70

Please sign in to comment.