generated from ubiquity/ts-template
-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/proxy callbacks #191
Closed
Closed
Feat/proxy callbacks #191
Changes from 6 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
883078f
feat: CallbackBuilder
Keyrxng 4014532
chore: pass CallbackBuilder as createPlugin param
Keyrxng 4f8aca9
chore: types, exports
Keyrxng 8883bbf
feat: sdk post-build package.json script
Keyrxng d1ba37b
chore: format, post-build pkg.json dir fix
Keyrxng bec5cde
Merge branch 'development' into feat/proxy-callbacks
Keyrxng 5115477
chore: no proxy, PluginCallbacks
Keyrxng File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import PACKAGE from "./package.json"; | ||
import fs from "fs/promises"; | ||
|
||
type Exports = Record<string, { import?: string; require?: string; types: string }>; | ||
|
||
interface PackageJson { | ||
name: string; | ||
version: string; | ||
description: string; | ||
main: string; | ||
module: string; | ||
types: string; | ||
author: string; | ||
license: string; | ||
keywords: string[]; | ||
exports: Exports; | ||
} | ||
|
||
export async function createCleanPackageJson(dirName: string, base = false, dirs?: string[]) { | ||
console.log(`Creating package.json for ${!dirName ? "index" : dirName}...`); | ||
let exports: Exports; | ||
|
||
const packageJson = { | ||
name: PACKAGE.name, | ||
version: PACKAGE.version, | ||
description: PACKAGE.description, | ||
main: "./", | ||
module: "./", | ||
types: "./", | ||
author: PACKAGE.author, | ||
license: PACKAGE.license, | ||
keywords: PACKAGE.keywords, | ||
exports: {} as Exports, | ||
} as unknown as PackageJson; | ||
|
||
if (base && dirs) { | ||
exports = { | ||
".": { | ||
types: `./sdk/index.d.ts`, | ||
import: `./sdk/index.mjs`, | ||
require: `./sdk/index.js`, | ||
}, | ||
}; | ||
|
||
for (const dir of dirs) { | ||
exports[`./${dir}`] = { | ||
import: `./${dir}/index.mjs`, | ||
require: `./${dir}/index.js`, | ||
types: `./${dir}/index.d.ts`, | ||
}; | ||
} | ||
|
||
packageJson.exports = exports; | ||
packageJson.types = `./sdk/index.d.ts`; | ||
packageJson.main = `./sdk/index.js`; | ||
packageJson.module = `./sdk/index.mjs`; | ||
|
||
await writeDirPackageJson("dist", packageJson); | ||
} else { | ||
exports = { | ||
[`.`]: { | ||
import: `./index.mjs`, | ||
require: `./index.js`, | ||
types: `./index.d.ts`, | ||
}, | ||
}; | ||
|
||
packageJson.exports = exports; | ||
packageJson.types = `./index.d.ts`; | ||
packageJson.main = `./index.js`; | ||
packageJson.module = `./index.mjs`; | ||
|
||
await writeDirPackageJson(dirName, packageJson); | ||
} | ||
} | ||
|
||
async function writeDirPackageJson(dirName: string, packageJson: PackageJson) { | ||
const path = dirName === "dist" ? "./dist/package.json" : `./dist/${dirName}/package.json`; | ||
await fs.writeFile(path, JSON.stringify(packageJson, null, 2)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { LogReturn } from "@ubiquity-os/ubiquity-os-logger"; | ||
import { Context } from "./context"; | ||
import { sanitizeMetadata } from "./util"; | ||
|
||
export async function postWorkerErrorComment(context: Context, error: LogReturn) { | ||
if ("issue" in context.payload && context.payload.repository?.owner?.login) { | ||
await context.octokit.rest.issues.createComment({ | ||
owner: context.payload.repository.owner.login, | ||
repo: context.payload.repository.name, | ||
issue_number: context.payload.issue.number, | ||
body: `${error.logMessage.diff}\n<!--\n${sanitizeMetadata(error.metadata)}\n-->`, | ||
}); | ||
} else { | ||
context.logger.info("Cannot post comment because issue is not found in the payload"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { Context, SupportedEventsU } from "../sdk/context"; | ||
import { CallbackFunction, ProxyCallbacks } from "../types/helpers"; | ||
import { postWorkerErrorComment } from "./errors"; | ||
|
||
/** | ||
* Build your callbacks first and pass `CallbackBuilder` directly to `createPlugin`. | ||
* | ||
* @example | ||
|
||
```ts | ||
const builder = new CallbackBuilder() | ||
.addCallback("issue_comment.created", <CallbackFunction<"issue_comment.created">>helloWorld) | ||
.addCallback("issue_comment.deleted", <CallbackFunction<"issue_comment.deleted">>goodbyeCruelWorld); | ||
``` | ||
*/ | ||
export class CallbackBuilder { | ||
private _callbacks: ProxyCallbacks = {} as ProxyCallbacks; | ||
|
||
/** | ||
* Add a callback for the given event. | ||
* | ||
* @param event The event to add a callback for. | ||
* @param callback The callback to add. | ||
*/ | ||
addCallback<TEvent extends SupportedEventsU>(event: TEvent, callback: CallbackFunction<TEvent>) { | ||
this._callbacks[event] ??= []; | ||
this._callbacks[event].push(callback); | ||
return this; | ||
} | ||
|
||
/** | ||
* This simply returns the callbacks object. | ||
*/ | ||
build() { | ||
return this._callbacks; | ||
} | ||
} | ||
|
||
export function proxyCallbacks(context: Context, callbackBuilder: CallbackBuilder): ProxyCallbacks { | ||
return new Proxy(callbackBuilder.build(), { | ||
Keyrxng marked this conversation as resolved.
Show resolved
Hide resolved
|
||
get(target, prop: SupportedEventsU) { | ||
if (!target[prop]) { | ||
context.logger.info(`No callbacks found for event ${prop}`); | ||
return { status: 204, reason: "skipped" }; | ||
} | ||
return (async () => { | ||
try { | ||
const res = await Promise.all(target[prop].map((callback) => handleCallback(callback, context))); | ||
context.logger.info(`${prop} callbacks completed`, { res }); | ||
let hasFailed = false; | ||
for (const r of res) { | ||
if (r.status === 500) { | ||
/** | ||
* Once https://github.com/ubiquity-os/ubiquity-os-kernel/pull/169 is merged, | ||
* we'll be able to detect easily if it's a worker or an action using the new context var | ||
* `pluginDeploymentDetails` which is just `inputs.ref` essentially. | ||
*/ | ||
await postWorkerErrorComment(context, context.logger.error(r.reason, { content: r.content })); | ||
hasFailed = true; | ||
} else if (r.status === 404) { | ||
context.logger.error(r.reason, { content: r.content }); | ||
} else if (r.status === 204) { | ||
context.logger.info(r.reason, { content: r.content }); | ||
} else { | ||
context.logger.ok(r.reason, { content: r.content }); | ||
} | ||
} | ||
|
||
if (hasFailed) { | ||
return { status: 500, reason: `One or more callbacks failed for event ${prop}` }; | ||
} | ||
return { status: 200, reason: "success" }; | ||
} catch (er) { | ||
console.log("Error in proxyCallbacks", er); | ||
await postWorkerErrorComment(context, context.logger.fatal("Error in proxyCallbacks", { er })); | ||
return { status: 500, reason: "error", content: String(er) }; | ||
} | ||
})(); | ||
}, | ||
}); | ||
} | ||
|
||
/** | ||
* Helper for awaiting proxyCallbacks | ||
*/ | ||
export async function handleProxyCallbacks(proxyCallbacks: ProxyCallbacks, context: Context) { | ||
return proxyCallbacks[context.eventName]; | ||
} | ||
|
||
/** | ||
* Why do we need this wrapper function? | ||
* | ||
* By using a generic `Function` type for the callback parameter, we bypass strict type | ||
* checking temporarily. This allows us to pass a standard `Context` object, which we know | ||
* contains the correct event and payload types, to the callback safely. | ||
* | ||
* We can trust that the `ProxyCallbacks` type has already ensured that each callback function | ||
* matches the expected event and payload types, so this function provides a safe and | ||
* flexible way to handle callbacks without introducing type or logic errors. | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/ban-types | ||
function handleCallback(callback: Function, context: Context) { | ||
return callback(context); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { Context } from "../sdk"; | ||
import { SupportedEventsU } from "../sdk/context"; | ||
|
||
export type CallbackResult = { status: 200 | 201 | 204 | 404 | 500; reason: string; content?: string | Record<string, unknown> }; | ||
export type CallbackFunction<TEvent extends SupportedEventsU, TConfig = Record<string, unknown>, TEnv = Record<string, unknown>> = ( | ||
context: Context<TConfig, TEnv, TEvent> | ||
) => Promise<CallbackResult>; | ||
/** | ||
* The `Context` type is a generic type defined as `Context<TEvent, TPayload>`, | ||
* where `TEvent` is a string representing the event name (e.g., "issues.labeled") | ||
* and `TPayload` is the webhook payload type for that event, derived from | ||
* the `SupportedEvents` type map. | ||
* | ||
* The `ProxyCallbacks` object is cast to allow optional callbacks | ||
* for each event type. This is useful because not all events may have associated callbacks. | ||
* As opposed to Partial<ProxyCallbacks> which could mean an undefined object. | ||
* | ||
* The expected function signature for callbacks looks like this: | ||
* | ||
* ```typescript | ||
* fn(context: Context<"issues.labeled", SupportedEvents["issues.labeled"]>): Promise<Result> | ||
* ``` | ||
*/ | ||
|
||
export type ProxyCallbacks = { | ||
[K in SupportedEventsU]: Array<CallbackFunction<K>>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { Manifest as M } from "./manifest"; | ||
export type { M as Manifest }; | ||
export type { CallbackFunction, CallbackResult } from "./helpers"; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed since we moved the SDK out?