Skip to content

Commit

Permalink
Sync changes
Browse files Browse the repository at this point in the history
  • Loading branch information
JadenSimon committed Jun 30, 2024
1 parent ca77e7b commit b1ff93b
Show file tree
Hide file tree
Showing 50 changed files with 1,115 additions and 579 deletions.
17 changes: 14 additions & 3 deletions .github/workflows/build-synapse.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,26 @@ on:
- test/**

jobs:
run_test:
runs-on:
- ubuntu-latest
run_test:
strategy:
matrix:
include:
- os: macos-13
- os: macos-14
- os: ubuntu-latest
- os: windows-2022
runs-on: ${{ matrix.os }}
permissions:
contents: read
steps:
- uses: actions/checkout@v3

- run: curl -fsSL https://synap.sh/install | bash
if: matrix.os != windows-2022
- run: irm https://synap.sh/install.ps1 | iex
if: matrix.os == windows-2022
- run: synapse --version

- run: synapse compile
- run: synapse run testFixtures
- run: synapse build
Expand Down
82 changes: 42 additions & 40 deletions integrations/aws/src/services/api-gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { LambdaFunction } from './lambda'
import { signRequest } from '../sigv4'
import { NodeHttpHandler } from '@smithy/node-http-handler'
import { HostedZone } from './route53'
import { HttpHandler, Middleware, RouteRegexp, buildRouteRegexp, matchRoutes, HttpResponse, HttpError, HttpRoute, PathArgs, createPathBindings, applyRoute, compareRoutes, HttpRequest } from 'synapse:http'
import { HttpHandler, Middleware, RouteRegexp, buildRouteRegexp, matchRoutes, HttpResponse, HttpError, HttpRoute, PathArgs, createPathBindings, applyRoute, compareRoutes, HttpRequest, RequestHandler, RequestHandlerWithBody, PathArgsWithBody } from 'synapse:http'
import { createSerializedPolicy } from './iam'
import { generateIdentifier } from 'synapse:lib'
import * as net from 'synapse:srl/net'
Expand Down Expand Up @@ -66,17 +66,11 @@ export class Gateway {
this.region = region.name
}

public addRoute<P extends string = string, U = any, R = HttpResponse>(
route: P,
handler: HttpHandler<P, U, R> | HttpHandler<P, string, R>,
): HttpRoute<[...PathArgs<P>, U], R> {
const [method, path] = route.split(' ')
if (path === undefined) {
throw new Error(`Missing method in route: ${route}`)
}

private _addRoute(method: string, path: string, handler: RequestHandler | RequestHandlerWithBody) {
const authHandler = typeof this.props?.auth === 'function' ? this.props.auth : undefined
const wrapped = wrapHandler(handler as any, authHandler as any, this.middleware, this.props?.allowedOrigins)
const wrapped = wrapRequestHandler(handler, authHandler)

const route = `${method} ${path}`
const mergeHandlers = this.props?.mergeHandlers ?? true
if (mergeHandlers) {
if (!this.requestRouter) {
Expand All @@ -89,7 +83,7 @@ export class Gateway {

this.requestRouter.addRoute(route, wrapped)

core.getPermissionsLater(wrapped, statements => {
core.getPermissionsLater(handler, statements => {
this.requestRouter!.fn.principal.addPolicy({
// [\w+=,.@-]+{1,128}
name: `Route-${route.replace(/[\s\/]+/g, '_').replace(/[{}]/g, '')}`,
Expand All @@ -116,7 +110,15 @@ export class Gateway {
}
}

private addRouteInfra(route: string, handler: ReturnType<typeof wrapHandler>) {
public route<P extends string = string, U = unknown, R = unknown>(
method: string,
path: P,
handler: RequestHandler<`${string} ${P}`, R> | RequestHandlerWithBody<`${string} ${P}`, U, R>
): HttpRoute<PathArgsWithBody<P, U>, R> {
return this._addRoute(method, path, handler)
}

private addRouteInfra(route: string, handler: ApiGatewayHandler) {
const fn = new LambdaFunction(handler)

const integration = new aws.Apigatewayv2Integration({
Expand Down Expand Up @@ -366,11 +368,9 @@ function isJsonRequest(headers: Record<string, string>) {
return !!contentType.match(/application\/(?:([^+\s]+)\+)?json/)
}

function wrapHandler(
handler: HttpHandler,
authHandler?: HttpHandler,
middleware: Middleware[] = [],
allowedOrigins?: string[]
function wrapRequestHandler(
handler: RequestHandler | RequestHandlerWithBody,
authHandler?: RequestHandler | RequestHandlerWithBody,
) {
async function handleRequest(request: ApiGatewayRequestPayloadV2) {
const decoded = (request.body !== undefined && request.isBase64Encoded)
Expand All @@ -379,35 +379,37 @@ function wrapHandler(
const body = (decoded && isJsonRequest(request.headers)) ? JSON.parse(decoded) : decoded
const stage = request.requestContext.stage
const trimmedPath = request.rawPath.replace(`/${stage}`, '')
const queryString = request.rawQueryString
const url = new URL(`${trimmedPath}${queryString ? `?${queryString}` : ''}`, `https://${request.headers['host']}`)
const headers = new Headers(request.headers)
const method = request.requestContext.http.method
const reqBody = method === 'GET' || method === 'HEAD' ? undefined : body

const mappedRequest = {
path: trimmedPath,
queryString: request.rawQueryString,
headers: new Headers(request.headers),
context: request.requestContext,
method: request.requestContext.http.method,
pathParameters: request.pathParameters,

// Why is `cookies` an array?
cookies: request.cookies,
}

return runHandler(async () => {
if (authHandler) {
const resp = await authHandler(mappedRequest, body)
if (resp !== undefined) {
return resp
}
const newReq = new Request(url, {
headers,
method,
body: reqBody,
duplex: 'half', // specific to node
} as RequestInit)

;(newReq as any).cookeis = request.cookies
;(newReq as any).context = request.requestContext
;(newReq as any).pathParameters = request.pathParameters

if (authHandler) {
const resp = await authHandler(newReq as any, body)
if (resp !== undefined) {
return resp
}

return handler(mappedRequest, body)
})
}

return handler(newReq as any, body)
}

return handleRequest
}

type ApiGatewayHandler = ReturnType<typeof wrapHandler>
type ApiGatewayHandler = ReturnType<typeof wrapRequestHandler>
function createRequestRouter() {
interface RouteEntry {
readonly route: string
Expand Down
2 changes: 1 addition & 1 deletion integrations/aws/src/services/kinesis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ function createIngestionService(consumer: (data: string) => Promise<void> | void
})


const route = service.addRoute('POST /ingest', async (req, body: FirehosePutRequest) => {
const route = service.route('POST', '/ingest', async (req, body: FirehosePutRequest) => {
try {
await Promise.all(body.records.map(r => consumer(r.data)))

Expand Down
22 changes: 15 additions & 7 deletions integrations/aws/src/services/s3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,21 @@ export class Bucket implements storage.Bucket {
}

// IMPORTANT: S3 returns a 403 instead of a 404 when the user doesn't have permission to list the bucket contents.
public async get(key: string): Promise<Uint8Array>
public async get(key: string, encoding: storage.Encoding): Promise<string>
public async get(key: string, encoding?: storage.Encoding): Promise<Uint8Array | string> {
const resp = await this.client.getObject({ Bucket: this.name, Key: key })
const bytes = await resp.Body!.transformToByteArray()

return !encoding ? bytes : Buffer.from(bytes).toString(encoding)
public async get(key: string): Promise<Blob | undefined>
public async get(key: string, encoding: storage.Encoding): Promise<string | undefined>
public async get(key: string, encoding?: storage.Encoding): Promise<Blob | string | undefined> {
try {
const resp = await this.client.getObject({ Bucket: this.name, Key: key })
const bytes = await resp.Body!.transformToByteArray()

// TODO: need way to convert web stream to blob
return !encoding ? new Blob([bytes]) : Buffer.from(bytes).toString(encoding)
} catch (e) {
if (!(e instanceof Error) || e.name !== 'NoSuchKey') {
throw e
}
return undefined
}
}

public async put(key: string, blob: string | Uint8Array): Promise<void> {
Expand Down
29 changes: 15 additions & 14 deletions integrations/local/src/bucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ import { getContentType } from 'synapse:http'
import { createHash } from 'node:crypto'
import { getLocalPath } from './provider'

export class NoSuchKey extends Error {
public readonly name = 'NoSuchKey'
}

function isFileNotFoundError(e: unknown) {
return (e as any).code === 'ENOENT'
}
Expand Down Expand Up @@ -46,12 +42,19 @@ export class LocalKVStore extends core.defineResource({
await fs.writeFile(p, value)
}

async get(key: string) {
return fs.readFile(path.resolve(this.filePath, key)).catch(e => {
async get(key: string): Promise<Blob | undefined>
async get(key: string, encoding: storage.Encoding): Promise<string | undefined>
async get(key: string, encoding?: storage.Encoding): Promise<string | Blob | undefined> {
const data = await fs.readFile(path.resolve(this.filePath, key), encoding).catch(e => {
throwIfNotFileNotFoundError(e)

throw new NoSuchKey(`Key not found: ${key}`)
return undefined
})

if (encoding) {
return data
}

return data !== undefined ? new Blob([data]) : undefined
}

async stat(key: string) {
Expand Down Expand Up @@ -119,12 +122,10 @@ export function getStorePath() {
export class Bucket implements storage.Bucket {
private readonly resource = new LocalKVStore(getStorePath())

public async get(key: string): Promise<Uint8Array>
public async get(key: string, encoding: storage.Encoding): Promise<string>
public async get(key: string, encoding?: storage.Encoding): Promise<Uint8Array | string> {
const data = await this.resource.get(key)

return encoding ? Buffer.from(data).toString(encoding) : data
public get(key: string): Promise<Blob | undefined>
public get(key: string, encoding: storage.Encoding): Promise<string | undefined>
public get(key: string, encoding?: storage.Encoding): Promise<Blob | string | undefined> {
return this.resource.get(key, encoding)
}

public async put(key: string, blob: string | Uint8Array): Promise<void> {
Expand Down
2 changes: 1 addition & 1 deletion integrations/local/src/cdn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export class CDN implements storage.CDN {

constructor(props: { bucket: storage.Bucket }) {
const service = new compute.HttpService({ auth: 'none' })
service.addRoute('GET /{key+}', async req => {
service.route('GET', '/{key+}', async req => {
const key = req.pathParameters.key
const [data, metadata] = await Promise.all([
props.bucket.get(key),
Expand Down
8 changes: 2 additions & 6 deletions integrations/local/src/counter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,9 @@ export class Counter implements storage.Counter {
}

async get(): Promise<number> {
try {
const d = JSON.parse(await this.bucket.get('counter', 'utf-8'))
const data = await this.bucket.get('counter', 'utf-8')

return d
} catch {
return this.init
}
return data !== undefined ? JSON.parse(data) : this.init
}

async set(amount: number): Promise<number> {
Expand Down
Loading

0 comments on commit b1ff93b

Please sign in to comment.