Skip to content
Open
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
37 changes: 28 additions & 9 deletions src/core/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,34 @@ export class HttpHandler {
this.#baseUrl = props.urlEndpoint ?? DefaultEndpoint
}

async get<T = unknown>(path: string, data?: unknown): Promise<T> {
async get<T = unknown>(path: string, data?: unknown): Promise<T | undefined> {
return this.#request<T>('GET', path, data)
}

async post<T = unknown>(path: string, data?: unknown): Promise<T> {
async post<T = unknown>(path: string, data?: unknown): Promise<T | undefined> {
return this.#request<T>('POST', path, data)
}

async delete<T = unknown>(path: string, data?: unknown): Promise<T> {
async delete<T = unknown>(path: string, data?: unknown): Promise<T | undefined> {
return this.#request<T>('DELETE', path, data)
}

async #request<T>(method: HttpMethod, path: string, data?: unknown): Promise<T> {
const url = `${this.#baseUrl}/client/${path}`
async #request<T>(method: HttpMethod, path: string, data?: unknown): Promise<T | undefined> {
let url = `${this.#baseUrl}/client/${path}`
let body: string | undefined

if (method === 'GET' && data) {
const mapped = mapKeys(data) as Record<string, unknown>
const params = new URLSearchParams()
for (const [key, value] of Object.entries(mapped)) {
if (value !== null && value !== undefined) {
params.set(key, String(value))
}
}
url = `${url}?${params.toString()}`
} else if (data) {
body = JSON.stringify(mapKeys(data))
}

try {
const response = await fetch(url, {
Expand All @@ -51,7 +65,7 @@ export class HttpHandler {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.#apiKey}`,
},
body: data ? JSON.stringify(mapKeys(data)) : undefined,
body,
})

return this.#handleResponse<T>(response)
Expand All @@ -65,17 +79,22 @@ export class HttpHandler {
}
}

async #handleResponse<T>(response: Response): Promise<T> {
async #handleResponse<T>(response: Response): Promise<T | undefined> {
if (!response.ok) {
throw await this.#mapError(response)
}

const contentLength = response.headers.get('content-length')
if (contentLength === '0') {
return undefined
}

const contentType = response.headers.get('content-type')
if (!contentType || !contentType.includes('application/json')) {
return undefined as T
return undefined
}

return response.json() as Promise<T>
return response.json()
}

async #mapError(response: Response): Promise<LunogramError> {
Expand Down
8 changes: 4 additions & 4 deletions src/core/resources/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ export abstract class BaseResource {

protected abstract readonly endpoint: string

protected async get<T = unknown>(data?: unknown): Promise<T> {
return this.#http.get<T>(this.endpoint, data)
protected async get<T = unknown>(data?: unknown, pathOverride?: string): Promise<T | undefined> {
return this.#http.get<T>(pathOverride ?? this.endpoint, data)
}

protected async post<T = unknown>(data?: unknown, pathOverride?: string): Promise<T> {
protected async post<T = unknown>(data?: unknown, pathOverride?: string): Promise<T | undefined> {
return this.#http.post<T>(pathOverride ?? this.endpoint, data)
}

protected async remove<T = unknown>(data?: unknown, pathOverride?: string): Promise<T> {
protected async remove<T = unknown>(data?: unknown, pathOverride?: string): Promise<T | undefined> {
return this.#http.delete<T>(pathOverride ?? this.endpoint, data)
}
}
4 changes: 2 additions & 2 deletions src/core/resources/organizations/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class OrganizationEventsResource extends BaseResource {
* Posts organization events for asynchronous processing.
* @param data - Array of organization events
*/
async post<T = unknown>(data: OrganizationEvent[]): Promise<T> {
return super.post(data)
async post<T = unknown>(data: OrganizationEvent[]): Promise<T | undefined> {
return super.post<T>(data)
}
}
48 changes: 48 additions & 0 deletions src/core/resources/organizations/inbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { BaseResource } from '../base'
import {
PostInboxMessagesRequest,
InboxMessageEvents,
GetInboxParams,
GetInboxCountParams,
InboxMessageList,
InboxCount,
} from '../../../types'

export class OrganizationInboxResource extends BaseResource {
readonly endpoint = 'organizations/inbox'

/**
* Creates one or more inbox messages for organizations. Processed asynchronously.
*/
async create(data: PostInboxMessagesRequest): Promise<void> {
return super.post<void>(data)
}

/**
* Returns visible, non-expired inbox messages for an organization.
*/
async list(params: GetInboxParams): Promise<InboxMessageList | undefined> {
return super.get<InboxMessageList>(params)
}

/**
* Returns unread and total inbox message counts for an organization.
*/
async count(params: GetInboxCountParams): Promise<InboxCount | undefined> {
return super.get<InboxCount>(params, 'organizations/inbox/count')
}

/**
* Marks one or more inbox messages as opened. Processed asynchronously.
*/
async opened(data: InboxMessageEvents): Promise<void> {
return super.post<void>(data, 'organizations/inbox/opened')
}

/**
* Marks one or more inbox messages as archived. Processed asynchronously.
*/
async archived(data: InboxMessageEvents): Promise<void> {
return super.post<void>(data, 'organizations/inbox/archived')
}
}
3 changes: 2 additions & 1 deletion src/core/resources/organizations/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { OrganizationResource } from './organization'
export { OrganizationScheduledResource } from './scheduled'
export { OrganizationEventsResource } from './events'
export { OrganizationEventsResource } from './events'
export { OrganizationInboxResource } from './inbox'
13 changes: 8 additions & 5 deletions src/core/resources/organizations/organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,28 @@ import { BaseResource } from "../base"
import { HttpHandler } from "../../http"
import { OrganizationScheduledResource } from "./scheduled"
import { OrganizationEventsResource } from "./events"
import { OrganizationInboxResource } from "./inbox"

export class OrganizationResource extends BaseResource {
readonly endpoint = 'organizations'
readonly schedule: OrganizationScheduledResource
readonly events: OrganizationEventsResource
readonly inbox: OrganizationInboxResource

constructor(http: HttpHandler) {
super(http)
this.schedule = new OrganizationScheduledResource(http)
this.events = new OrganizationEventsResource(http)
this.inbox = new OrganizationInboxResource(http)
}

/**
* Creates or updates an organization.
* @param data - Organization data including identifier, name, data, etc.
* @returns Promise resolving to the created/updated organization
*/
async upsert(data: OrganizationRequest): Promise<OrganizationResponse> {
return this.post(data)
async upsert(data: OrganizationRequest): Promise<OrganizationResponse | undefined> {
return this.post<OrganizationResponse>(data)
}

/**
Expand All @@ -36,7 +39,7 @@ export class OrganizationResource extends BaseResource {
* @returns Promise resolving when organization is deleted
*/
async delete(data: DeleteOrganizationRequest): Promise<void> {
return this.remove(data)
return this.remove<void>(data)
}

/**
Expand All @@ -45,7 +48,7 @@ export class OrganizationResource extends BaseResource {
* @returns Promise resolving when user is added
*/
async addUser(data: OrganizationUserRequest): Promise<void> {
return this.post(data, 'organizations/users')
return this.post<void>(data, 'organizations/users')
}

/**
Expand All @@ -54,6 +57,6 @@ export class OrganizationResource extends BaseResource {
* @returns Promise resolving when user is removed
*/
async removeUser(data: RemoveOrganizationUserRequest): Promise<void> {
return this.remove(data, 'organizations/users')
return this.remove<void>(data, 'organizations/users')
}
}
6 changes: 3 additions & 3 deletions src/core/resources/organizations/scheduled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export class OrganizationScheduledResource extends BaseResource {
* @param data - Scheduled resource data including name, identifier, scheduledAt, interval, etc.
* @returns Promise resolving to the accepted scheduled resource
*/
async upsert(data: UpsertOrganizationScheduledRequest): Promise<ScheduledAcceptedResponse> {
return this.post(data)
async upsert(data: UpsertOrganizationScheduledRequest): Promise<ScheduledAcceptedResponse | undefined> {
return this.post<ScheduledAcceptedResponse>(data)
}

/**
Expand All @@ -26,6 +26,6 @@ export class OrganizationScheduledResource extends BaseResource {
* @returns Promise resolving when scheduled resource is deleted
*/
async delete(data: DeleteOrganizationScheduledRequest): Promise<void> {
return this.remove(data)
return this.remove<void>(data)
}
}
4 changes: 2 additions & 2 deletions src/core/resources/users/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class UserEventsResource extends BaseResource {
* Posts user events for asynchronous processing.
* @param data - Array of user events
*/
async post<T = unknown>(data: UserEvent[]): Promise<T> {
return super.post(data)
async post<T = unknown>(data: UserEvent[]): Promise<T | undefined> {
return super.post<T>(data)
}
}
48 changes: 48 additions & 0 deletions src/core/resources/users/inbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { BaseResource } from '../base'
import {
PostInboxMessagesRequest,
InboxMessageEvents,
GetInboxParams,
GetInboxCountParams,
InboxMessageList,
InboxCount,
} from '../../../types'

export class UserInboxResource extends BaseResource {
readonly endpoint = 'users/inbox'

/**
* Creates one or more inbox messages for users. Processed asynchronously.
*/
async create(data: PostInboxMessagesRequest): Promise<void> {
return super.post<void>(data)
}

/**
* Returns visible, non-expired inbox messages for a user.
*/
async list(params: GetInboxParams): Promise<InboxMessageList | undefined> {
return super.get<InboxMessageList>(params)
}

/**
* Returns unread and total inbox message counts for a user.
*/
async count(params: GetInboxCountParams): Promise<InboxCount | undefined> {
return super.get<InboxCount>(params, 'users/inbox/count')
}

/**
* Marks one or more inbox messages as opened. Processed asynchronously.
*/
async opened(data: InboxMessageEvents): Promise<void> {
return super.post<void>(data, 'users/inbox/opened')
}

/**
* Marks one or more inbox messages as archived. Processed asynchronously.
*/
async archived(data: InboxMessageEvents): Promise<void> {
return super.post<void>(data, 'users/inbox/archived')
}
}
3 changes: 2 additions & 1 deletion src/core/resources/users/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { UserResource } from './user'
export { UserScheduledResource } from './scheduled'
export { UserEventsResource } from './events'
export { UserEventsResource } from './events'
export { UserInboxResource } from './inbox'
6 changes: 3 additions & 3 deletions src/core/resources/users/scheduled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export class UserScheduledResource extends BaseResource {
* @param data - Scheduled resource data including name, identifier, scheduledAt, interval, etc.
* @returns Promise resolving to the accepted scheduled resource
*/
async upsert(data: UpsertUserScheduledRequest): Promise<ScheduledAcceptedResponse> {
return this.post(data)
async upsert(data: UpsertUserScheduledRequest): Promise<ScheduledAcceptedResponse | undefined> {
return this.post<ScheduledAcceptedResponse>(data)
}

/**
Expand All @@ -26,6 +26,6 @@ export class UserScheduledResource extends BaseResource {
* @returns Promise resolving when scheduled resource is deleted
*/
async delete(data: DeleteUserScheduledRequest): Promise<void> {
return this.remove(data)
return this.remove<void>(data)
}
}
9 changes: 6 additions & 3 deletions src/core/resources/users/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,28 @@ import {
} from '../../../types'
import { UserScheduledResource } from './scheduled'
import { UserEventsResource } from './events'
import { UserInboxResource } from './inbox'

export class UserResource extends BaseResource {
readonly endpoint = 'users'
readonly schedule: UserScheduledResource
readonly events: UserEventsResource
readonly inbox: UserInboxResource

constructor(http: HttpHandler) {
super(http)
this.schedule = new UserScheduledResource(http)
this.events = new UserEventsResource(http)
this.inbox = new UserInboxResource(http)
}

/**
* Creates or updates a user.
* @param data - User data including identifier, email, phone, etc.
* @returns Promise resolving to the created/updated user
*/
async upsert(data: UpsertUserRequest): Promise<UserResponse> {
return this.post(data)
async upsert(data: UpsertUserRequest): Promise<UserResponse | undefined> {
return this.post<UserResponse>(data)
}

/**
Expand All @@ -34,6 +37,6 @@ export class UserResource extends BaseResource {
* @returns Promise resolving when user is deleted
*/
async delete(data: DeleteUserRequest): Promise<void> {
return this.remove(data)
return this.remove<void>(data)
}
}
8 changes: 4 additions & 4 deletions src/platform/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ class BrowserUserEventsResource extends UserEventsResource {
this.#getIdentifier = getIdentifier
}

async post<T = unknown>(data: UserEvent[]): Promise<T> {
async post<T = unknown>(data: UserEvent[]): Promise<T | undefined> {
const identifier = this.#getIdentifier()
const injected = data.map((event) => ({
...event,
// Do not inject identifier when `match` is used (they are mutually exclusive)
identifier: event.match ? event.identifier : (event.identifier ?? identifier),
}))
return super.post(injected) as Promise<T>
return super.post<T>(injected)
}
}

Expand All @@ -43,7 +43,7 @@ class BrowserUserScheduledResource extends UserScheduledResource {
this.#getIdentifier = getIdentifier
}

async upsert(data: UpsertUserScheduledRequest): Promise<ScheduledAcceptedResponse> {
async upsert(data: UpsertUserScheduledRequest): Promise<ScheduledAcceptedResponse | undefined> {
return super.upsert({
...data,
identifier: data.identifier ?? this.#getIdentifier(),
Expand Down Expand Up @@ -113,7 +113,7 @@ class BrowserUserResource extends UserResource {
return identifier
}

async upsert(data: UpsertUserRequest): Promise<UserResponse> {
async upsert(data: UpsertUserRequest): Promise<UserResponse | undefined> {
const identifier = this.#buildIdentifier(data.identifier)
return super.upsert({ ...data, identifier })
}
Expand Down
Loading
Loading