Skip to content
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

Add HTTP response Helper & Make initNammatham create Azure Functions for default #131

Merged
merged 5 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,5 @@ inversify-express-utils

.azurite
tmp
.nx
.nx
sample_code
23 changes: 11 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Type-safe Serverless Library for Azure Functions and friends
>
> Note: [Nammatham v1](https://www.npmjs.com/package/nammatham) is currently in maintenance mode. no new features are actively being developed

You're reading v2 docs


| Version | Status | Azure Functions <br>Node.js Lib | Branch | Build Status |
| ------- | ----------- | ----------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
Expand All @@ -37,31 +39,28 @@ Nammatham (นามธรรม in Thai, pronounced `/naam ma tham/`, means **a
npm install nammatham@alpha
```

You can also install independently
```bash
npm install @nammatham/core @nammatham/azure-functions @nammatham/express
```

### Example

You can see [examples](examples) or follow the minimal app getting started below:

> `initNammatham.create()` is a factory function for creating Nammatham App, it's a wrapper for Azure Functions App.

```typescript
import { initNammatham, AzureFunctionsAdapter, expressPlugin } from "nammatham";
import { initNammatham, expressPlugin } from 'nammatham';

const n = initNammatham.create(new AzureFunctionsAdapter());
const n = initNammatham.create();
const func = n.func;
const app = n.app;

const helloFunction = func
.httpGet('hello', {
route: 'hello-world',
})
.handler(async ({trigger, context}) => {
context.log('HTTP trigger function processed a request.');
context.debug(`Http function processed request for url "${trigger.url}"`);
const name = trigger.query.get('name') || (await trigger.text()) || 'world';
return { body: `Hello, ${name}!` };
.handler(async c => {
c.context.log('HTTP trigger function processed a request.');
c.context.debug(`Http function processed request for url "${c.trigger.url}"`);
const name = c.trigger.query.get('name') || (await c.trigger.text()) || 'world';
return c.text(`Hello, ${name}!`);
});

app.addFunctions(helloFunction);
Expand Down
14 changes: 7 additions & 7 deletions examples/azure-functions-minimal/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AzureFunctionsAdapter, initNammatham, expressPlugin } from 'nammatham';
import { initNammatham, expressPlugin } from 'nammatham';

const n = initNammatham.create(new AzureFunctionsAdapter());
const n = initNammatham.create();
const func = n.func;
const app = n.app;

Expand All @@ -9,11 +9,11 @@ app.addFunctions(
.httpGet('hello', {
route: 'hello-world',
})
.handler(async ({ trigger, context }) => {
context.log('HTTP trigger function processed a request.');
context.debug(`Http function processed request for url "${trigger.url}"`);
const name = trigger.query.get('name') || (await trigger.text()) || 'world';
return { body: `Hello, ${name}!` };
.handler(async c => {
c.context.log('HTTP trigger function processed a request.');
c.context.debug(`Http function processed request for url "${c.trigger.url}"`);
const name = c.trigger.query.get('name') || (await c.trigger.text()) || 'world';
return c.text(`Hello, ${name}!`);
})
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export default func
.timer('watcher', {
schedule: '*/5 * * * * *',
})
.handler(async ({trigger, context}) => {
context.info('Timer triggered!');
.handler(async ({ trigger, context }) => {
context.info('Timer triggered!');
trigger.isPastDue ? context.info('Timer is past due!') : null;
});
4 changes: 2 additions & 2 deletions examples/azure-functions-timer-trigger/src/nammatham.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { initNammatham, AzureFunctionsAdapter } from 'nammatham';
import { initNammatham } from 'nammatham';

const n = initNammatham.create(new AzureFunctionsAdapter());
const n = initNammatham.create();
n.func;
// ^?
export const func = n.func;
Expand Down
12 changes: 5 additions & 7 deletions examples/azure-functions-with-inversify/src/functions/hello.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@ export default func
.setContext({
services,
})
.handler(async ({ trigger, context, services }) => {
context.log('HTTP trigger function processed a request.');
.handler(async c => {
c.context.log('HTTP trigger function processed a request.');

return {
jsonBody: {
data: 'hello world' + services.dataService.getData(),
},
};
return c.json({
data: 'hello world' + services.dataService.getData(),
});
});
4 changes: 2 additions & 2 deletions examples/azure-functions-with-inversify/src/nammatham.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { initNammatham, AzureFunctionsAdapter } from 'nammatham';
import { initNammatham } from 'nammatham';

const n = initNammatham.create(new AzureFunctionsAdapter());
const n = initNammatham.create();
n.func;
// ^?
export const func = n.func;
Expand Down
19 changes: 9 additions & 10 deletions examples/azure-functions-with-test/__test__/helloFunction.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { expect, test } from 'vitest';
import helloFunc from '../src/functions/hello';
import { HttpRequest, InvocationContext } from '@azure/functions';
import { NammathamContext } from 'nammatham';

test('helloFunc', async () => {
const handler = helloFunc.getHandler();
const result = await handler({
context: new InvocationContext(),
trigger: new HttpRequest({
method: 'GET',
url: 'http://localhost:3000/api/hello-world',
query: {
name: 'world',
},
}),
});
const nammathamConext = new NammathamContext(new InvocationContext(), new HttpRequest({
method: 'GET',
url: 'http://localhost:3000/api/hello-world',
query: {
name: 'world',
},
}))
const result = await handler(nammathamConext);

expect(result).toEqual({
jsonBody: {
Expand Down
19 changes: 8 additions & 11 deletions examples/azure-functions-with-test/src/functions/hello.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,17 @@ export default func
.httpGet('hello', {
route: 'hello-world',
})
.handler(async ({ trigger, context }) => {
context.log('HTTP trigger function processed a request.');
context.debug(`Http function processed request for url "${trigger.url}"`);
const name = trigger.query.get('name') || (await trigger.text()) || 'world';
.handler(async c => {
c.context.log('HTTP trigger function processed a request.');
c.context.debug(`Http function processed request for url "${c.trigger.url}"`);
const name = c.trigger.query.get('name') || (await c.trigger.text()) || 'world';
if (name === 'error') {
throw new Error('this is an error');
}
const result = {
return c.json({
data: {
name: name,
message: `Hello, ${name}!`
}
}
return {
jsonBody: result,
}
message: `Hello, ${name}!`,
},
});
});
3 changes: 1 addition & 2 deletions examples/azure-functions-with-test/src/nammatham.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { initNammatham } from 'nammatham';
import { AzureFunctionsAdapter } from 'nammatham';

const n = initNammatham.create(new AzureFunctionsAdapter());
const n = initNammatham.create();

export const func = n.func;
export const app = n.app;
4 changes: 2 additions & 2 deletions examples/azure-functions-with-trpc/src/nammatham.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { initNammatham, AzureFunctionsAdapter } from 'nammatham';
import { initNammatham } from 'nammatham';

const n = initNammatham.create(new AzureFunctionsAdapter());
const n = initNammatham.create();

export const func = n.func;
export const app = n.app;
12 changes: 5 additions & 7 deletions examples/azure-functions/src/functions/blob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ export default func
extraInputs: [blobInput],
extraOutputs: [blobOutput],
})
.handler(({ trigger, context }) => {
context.log('function processed work item:', trigger);
const blobInputValue = context.extraInputs.get(blobOutput);
.handler(c => {
c.context.log('function processed work item:', c.trigger);
const blobInputValue = c.context.extraInputs.get(blobOutput);

context.extraOutputs.set(blobOutput, blobInputValue);
return {
body: `Hello ${blobInputValue}`,
};
c.context.extraOutputs.set(blobOutput, blobInputValue);
return c.text(`Hello ${blobInputValue}`);
});
19 changes: 8 additions & 11 deletions examples/azure-functions/src/functions/hello.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,17 @@ export default func
.httpGet('hello', {
route: 'hello-world',
})
.handler(async ({ trigger, context }) => {
context.log('HTTP trigger function processed a request.');
context.debug(`Http function processed request for url "${trigger.url}"`);
const name = trigger.query.get('name') || (await trigger.text()) || 'world';
.handler(async c => {
c.context.log('HTTP trigger function processed a request.');
c.context.debug(`Http function processed request for url "${c.trigger.url}"`);
const name = c.trigger.query.get('name') || (await c.trigger.text()) || 'world';
if (name === 'error') {
throw new Error('this is an error');
}
const result = {
return c.json({
data: {
name: name,
message: `Hello, ${name}!`
}
}
return {
jsonBody: result,
}
message: `Hello, ${name}!`,
},
});
});
4 changes: 2 additions & 2 deletions examples/azure-functions/src/nammatham.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { initNammatham, AzureFunctionsAdapter } from 'nammatham';
import { initNammatham } from 'nammatham';

const n = initNammatham.create(new AzureFunctionsAdapter());
const n = initNammatham.create();
n.func;
// ^?
export const func = n.func;
Expand Down
7 changes: 4 additions & 3 deletions packages/azure-functions/src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class AzureFunctionsHandler<
// eslint-disable-next-line @typescript-eslint/ban-types
ExtraContext extends Record<string, unknown> = {}
> extends BaseHandler<HandlerFunction<TTriggerType, TReturnType, ExtraContext>> {
context: ExtraContext = {} as ExtraContext;
extraContext: ExtraContext = {} as ExtraContext;
protected funcHandler!: HandlerFunction<TTriggerType, TReturnType, ExtraContext>;

constructor(
Expand All @@ -31,7 +31,8 @@ export class AzureFunctionsHandler<
build(): AzureFunctionsEndpoint<TTriggerType, TReturnType> {
const invokeHandler = (triggerInput: TTriggerType, innocationContext: InvocationContext) => {
const nammathamContext = new NammathamContext(innocationContext, triggerInput);
return this.funcHandler({ ...nammathamContext, ...this.context });
Object.assign(nammathamContext, this.extraContext);
return this.funcHandler(nammathamContext as NammathamContext<TTriggerType> & ExtraContext);
};
return {
...this.functionOption,
Expand All @@ -43,7 +44,7 @@ export class AzureFunctionsHandler<
}

setContext<NewItem extends Record<string, unknown>>(context: NewItem) {
this.context = { ...this.context, ...context };
this.extraContext = { ...this.extraContext, ...context };
return this as AzureFunctionsHandler<TTriggerType, TReturnType, ExtraContext & NewItem>;
}

Expand Down
73 changes: 73 additions & 0 deletions packages/azure-functions/src/http/HttpResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as type from '@azure/functions';

/**
* Re-export from Azure Functions;
*/
export type AzureHttpResponse = type.HttpResponseInit | type.HttpResponse;
export type Request = type.HttpRequest;

/**
* Http Response Builder wrapping around azure/functions
*/
export type Header = Record<string, string>;
export class HttpResponse {
protected _headers: Header;
protected _cookies: NonNullable<type.HttpResponseInit['cookies']> = [];
protected _httpResponse: type.HttpResponseInit;

constructor(responseInit?: Omit<type.HttpResponseInit, 'headers'> & { headers: Header }) {
this._httpResponse = {
...responseInit,
};
this._headers = responseInit?.headers ?? {};
}

private build(): type.HttpResponseInit {
const result = {
...this._httpResponse,
};
if (Object.values(this._headers).length > 0) {
result.headers = this._headers;
}

if (this._cookies.length > 0) {
result.cookies = this._cookies;
}
return result;
}

public text(body?: string) {
return new type.HttpResponse({
...this.build(),
body,
});
}

public json<T extends object>(jsonBody: T): AzureHttpResponse {
return {
...this.build(),
jsonBody,
};
}

public httpResponse(responseInit?: type.HttpResponseInit) {
return new type.HttpResponse(responseInit);
}

public header(key: string, value: string) {
this._headers[key] = value;
return this;
}

public headers(headers: Header) {
for (const [key, value] of Object.entries(headers)) {
this._headers[key] = value;
}
return this;
}

public status(status: number) {
this._httpResponse.status = status;
return this;
}
}
6 changes: 3 additions & 3 deletions packages/azure-functions/src/nammatham-context.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { InvocationContext } from '@azure/functions';
import { type InvocationContext } from '@azure/functions';

import { NammathamContextBase } from '@nammatham/core';
import { HttpResponse } from './http/HttpResponse';

export class NammathamContext<TTriggerType> extends NammathamContextBase {
export class NammathamContext<TTriggerType> extends HttpResponse {
constructor(public readonly context: InvocationContext, public readonly trigger: TTriggerType) {
super();
}
Expand Down
Loading
Loading