-
Notifications
You must be signed in to change notification settings - Fork 3
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: custom resource #384
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { Execution } from "./execution.js"; | ||
|
||
export interface Resource<ID extends string, Attributes, Client> { | ||
kind: "Resource"; | ||
id: ID; | ||
client: Client; | ||
attributes: Attributes; | ||
} | ||
|
||
export function resource< | ||
const ID extends string, | ||
const Properties, | ||
const Attributes, | ||
const Client | ||
>( | ||
id: ID, | ||
options: { | ||
create(request: Properties): Promise<Execution<Attributes> | Attributes>; | ||
update(request: { | ||
oldResourceProperties: Properties; | ||
newResourceProperties: Properties; | ||
attributes: Serialized<Attributes>; | ||
}): Promise<Execution<Attributes> | Attributes>; | ||
delete(request: { | ||
properties: Properties; | ||
attributes: Serialized<Attributes>; | ||
}): Promise<Execution<void> | void>; | ||
init(output: Serialized<Attributes>): Promise<Client>; | ||
} | ||
): (id: string, props: Properties) => Resource<ID, Attributes, Client> { | ||
return { | ||
kind: "Resource", | ||
id, | ||
options, | ||
} as any; | ||
} | ||
|
||
export type Serialized<T> = T extends | ||
| undefined | ||
| null | ||
| boolean | ||
| number | ||
| string | ||
? T | ||
: T extends readonly any[] | ||
? { | ||
[i in keyof T]: i extends number ? Serialized<T[i]> : T[i]; | ||
} | ||
: T extends Record<string, any> | ||
? Omit< | ||
{ | ||
[k in keyof T]: T[k] extends (...args: any[]) => any | ||
? never | ||
: Serialized<T[k]>; | ||
}, | ||
{ | ||
[k in keyof T]: T[k] extends (...args: any[]) => any ? k : never; | ||
}[keyof T] | ||
> | ||
: never; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"extends": "../../../../tsconfig-base.cjs", | ||
"include": ["src"], | ||
"exclude": ["lib", "node_modules", "src/package.json"], | ||
"compilerOptions": { | ||
"outDir": "lib/cjs", | ||
"rootDir": "src" | ||
}, | ||
"references": [] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
{ | ||
"extends": "../../../tsconfig-base", | ||
"extends": "../../../../tsconfig-base", | ||
"include": ["src", "src/package.json"], | ||
"exclude": ["lib", "node_modules"], | ||
"compilerOptions": { | ||
"outDir": "lib/esm", | ||
"rootDir": "src" | ||
}, | ||
"references": [{ "path": "../core" }] | ||
"references": [{ "path": "../../core" }] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
{ | ||
"name": "@eventual/twilio", | ||
"version": "0.41.0", | ||
"exports": { | ||
".": { | ||
"import": "./lib/esm/index.js", | ||
"require": "./lib/cjs/index.js" | ||
} | ||
}, | ||
"main": "./lib/cjs/index.js", | ||
"module": "./lib/esm/index.js", | ||
"files": [ | ||
"lib" | ||
], | ||
"dependencies": { | ||
"tsscmp": "^1.0.6", | ||
"twilio": "^4.11.1" | ||
}, | ||
"peerDependencies": { | ||
"@eventual/core": "workspace:^", | ||
"itty-router": "^2.6.6" | ||
}, | ||
"devDependencies": { | ||
"@eventual/core": "workspace:^", | ||
"@types/jest": "^29.5.1", | ||
"@types/node": "^18", | ||
"@types/tsscmp": "^1.0.0", | ||
"itty-router": "2.6.6", | ||
"jest": "^29", | ||
"ts-jest": "^29.1.0", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^5", | ||
"ulidx": "^0.3.0" | ||
}, | ||
"jest": { | ||
"extensionsToTreatAsEsm": [ | ||
".ts" | ||
], | ||
"moduleNameMapper": { | ||
"^(\\.{1,2}/.*)\\.js$": "$1" | ||
}, | ||
"transform": { | ||
"^.+\\.(t|j)sx?$": [ | ||
"ts-jest", | ||
{ | ||
"tsconfig": "tsconfig.test.json", | ||
"useESM": true | ||
} | ||
] | ||
} | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import twilio from "twilio"; | ||
import { HttpResponse, api, resource } from "@eventual/core"; | ||
import type { AddressListInstanceCreateOptions } from "twilio/lib/rest/api/v2010/account/address"; | ||
import type { IncomingPhoneNumberListInstanceCreateOptions } from "twilio/lib/rest/api/v2010/account/incomingPhoneNumber"; | ||
|
||
const client = twilio( | ||
process.env.TWILIO_ACCOUNT_SID, | ||
process.env.TWILIO_AUTH_TOKEN | ||
); | ||
|
||
export const Address = resource("twilio.Address", { | ||
async create(input: AddressListInstanceCreateOptions) { | ||
return { | ||
sid: (await client.addresses.create(input)).sid, | ||
}; | ||
}, | ||
async update({ newResourceProperties, attributes: address }) { | ||
const updatedAddress = await client | ||
.addresses(address.sid) | ||
.update(newResourceProperties); | ||
return { | ||
sid: updatedAddress.sid, | ||
}; | ||
}, | ||
async delete({ attributes: address }) { | ||
await client.addresses(address.sid).remove(); | ||
}, | ||
async init(address) { | ||
return client.addresses.get(address.sid).fetch(); | ||
}, | ||
}); | ||
|
||
export const PhoneNumber = resource("twilio.PhoneNumber", { | ||
async create(input: IncomingPhoneNumberListInstanceCreateOptions) { | ||
return { | ||
sid: (await client.incomingPhoneNumbers.create(input)).sid, | ||
}; | ||
}, | ||
async update({ newResourceProperties, attributes: address }) { | ||
const updatedAddress = await client | ||
.incomingPhoneNumbers(address.sid) | ||
.update(newResourceProperties); | ||
return { | ||
sid: updatedAddress.sid, | ||
}; | ||
}, | ||
async delete({ attributes: address }) { | ||
await client.incomingPhoneNumbers(address.sid).remove(); | ||
}, | ||
async init(address) { | ||
return client.incomingPhoneNumbers.get(address.sid).fetch(); | ||
}, | ||
}); | ||
|
||
export const samGoodwin = Address("Sam Goodwin", { | ||
customerName: "Sam Goodwin", | ||
city: "Seattle", | ||
region: "Seattle", | ||
postalCode: "98109", | ||
isoCountry: "US", | ||
street: "560 Highland Drive", | ||
}); | ||
|
||
export const samGoodwinCell = PhoneNumber("Sam Goodwin Cell", { | ||
// PROBLEM: attributes.sid won't exist during infer/synth | ||
// can use a Proxy to intercept these references, not sure if good idea | ||
addressSid: samGoodwin.attributes.sid, | ||
// TODO: where to get this from? | ||
// This will need to be a ngrok URL when running locally | ||
// And then the API Gateway URL when deployed | ||
Comment on lines
+69
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In export const serviceUrl = () => tryGetEnv<string>(ENV_NAMES.SERVICE_URL); |
||
smsUrl: process.env.SERVER_URL, | ||
}); | ||
|
||
export const sms = api.post("/sms", (request) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is triggering this webhook? Is twillio asking for the SMS contents? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Twilio will call this when a message is received. It needs to return the response |
||
const response = new twilio.twiml.MessagingResponse().message("Hello World"); | ||
return new HttpResponse(response.toString(), { | ||
status: 200, | ||
headers: { | ||
"Content-Type": "text/xml", | ||
}, | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"extends": "../../../../tsconfig-base", | ||
"include": ["src", "src/package.json"], | ||
"exclude": ["lib", "node_modules"], | ||
"compilerOptions": { | ||
"outDir": "lib/esm", | ||
"rootDir": "src", | ||
"esModuleInterop": true | ||
}, | ||
"references": [{ "path": "../../core" }] | ||
} |
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.
Yeah, you'd need to load the data into an env variable and then pull the values out via a proxy. And then we'd either need to always inject every custom resource value into all functions or compute a dependency map.
Or you could do runtime lookups on demand to a SSM parameter, app config, or secret.