@seletech/sele 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.enc = exports.Http = exports.SeleError = void 0;
4
+ /** Thrown for every non-2xx response. `code` is the machine-readable error code from the API. */
5
+ class SeleError extends Error {
6
+ status;
7
+ code;
8
+ requestId;
9
+ body;
10
+ constructor(status, code, message, body, requestId) {
11
+ super(message);
12
+ this.name = 'SeleError';
13
+ this.status = status;
14
+ this.code = code;
15
+ this.body = body;
16
+ this.requestId = requestId;
17
+ }
18
+ }
19
+ exports.SeleError = SeleError;
20
+ class Http {
21
+ baseUrl;
22
+ apiKey;
23
+ fetchFn;
24
+ timeoutMs;
25
+ constructor(defaults, o = {}) {
26
+ this.baseUrl = (o.baseUrl ?? defaults.baseUrl).replace(/\/+$/, '');
27
+ this.apiKey = o.apiKey;
28
+ this.fetchFn = o.fetch ?? ((i, init) => fetch(i, init));
29
+ this.timeoutMs = o.timeoutMs ?? 15000;
30
+ }
31
+ async raw(method, path, init = {}) {
32
+ const q = init.query ? Object.entries(init.query).filter(([, v]) => v !== undefined).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join('&') : '';
33
+ const headers = { ...init.headers };
34
+ if (this.apiKey && !headers.authorization)
35
+ headers.authorization = `Bearer ${this.apiKey}`;
36
+ const res = await this.fetchFn(`${this.baseUrl}${path}${q ? `?${q}` : ''}`, { method, headers, body: init.body ?? undefined, signal: AbortSignal.timeout(this.timeoutMs) });
37
+ if (!res.ok)
38
+ throw await toError(res);
39
+ return res;
40
+ }
41
+ async json(method, path, body, query) {
42
+ const res = await this.raw(method, path, { headers: body === undefined ? {} : { 'content-type': 'application/json' }, body: body === undefined ? undefined : JSON.stringify(body), query });
43
+ return (await res.json());
44
+ }
45
+ }
46
+ exports.Http = Http;
47
+ async function toError(res) {
48
+ let body = null;
49
+ try {
50
+ body = await res.json();
51
+ }
52
+ catch { /* not JSON */ }
53
+ const e = body?.error;
54
+ const code = typeof e === 'string' ? e : typeof e?.code === 'string' ? e.code : `http_${res.status}`;
55
+ const message = typeof e?.message === 'string' ? e.message : typeof body?.error_description === 'string' ? body.error_description : `Request failed with status ${res.status}`;
56
+ return new SeleError(res.status, code, message, body, typeof e?.request_id === 'string' ? e.request_id : undefined);
57
+ }
58
+ const enc = (s) => s.split('/').map(encodeURIComponent).join('/');
59
+ exports.enc = enc;
@@ -0,0 +1,7 @@
1
+ export { SeleError, type ClientOptions, type FetchLike } from './http.ts';
2
+ export { SeleIdAuth, createPkce, buildAuthorizeUrl, ISSUER, type Tokens, type UserInfo, type PkcePair, type AuthorizeRequest, type AuthorizeResult } from './auth.ts';
3
+ export { SeleStorage, type StoredObject, type ObjectList, type SignedUrl } from './storage.ts';
4
+ export { SeleDb, SeleDb as SeleBase, type Param, type QueryResult } from './db.ts';
5
+ export { seleIdAccessToken, type SeleIdTokenSource } from './supabase.ts';
6
+ export { SeleEvents, verifyWebhook } from './events.ts';
7
+ export { SeleAi, type ChatMessage, type ChatResponse } from './ai.ts';
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SeleAi = exports.verifyWebhook = exports.SeleEvents = exports.seleIdAccessToken = exports.SeleBase = exports.SeleDb = exports.SeleStorage = exports.ISSUER = exports.buildAuthorizeUrl = exports.createPkce = exports.SeleIdAuth = exports.SeleError = void 0;
4
+ var http_ts_1 = require("./http.js");
5
+ Object.defineProperty(exports, "SeleError", { enumerable: true, get: function () { return http_ts_1.SeleError; } });
6
+ var auth_ts_1 = require("./auth.js");
7
+ Object.defineProperty(exports, "SeleIdAuth", { enumerable: true, get: function () { return auth_ts_1.SeleIdAuth; } });
8
+ Object.defineProperty(exports, "createPkce", { enumerable: true, get: function () { return auth_ts_1.createPkce; } });
9
+ Object.defineProperty(exports, "buildAuthorizeUrl", { enumerable: true, get: function () { return auth_ts_1.buildAuthorizeUrl; } });
10
+ Object.defineProperty(exports, "ISSUER", { enumerable: true, get: function () { return auth_ts_1.ISSUER; } });
11
+ var storage_ts_1 = require("./storage.js");
12
+ Object.defineProperty(exports, "SeleStorage", { enumerable: true, get: function () { return storage_ts_1.SeleStorage; } });
13
+ var db_ts_1 = require("./db.js");
14
+ Object.defineProperty(exports, "SeleDb", { enumerable: true, get: function () { return db_ts_1.SeleDb; } });
15
+ Object.defineProperty(exports, "SeleBase", { enumerable: true, get: function () { return db_ts_1.SeleDb; } });
16
+ var supabase_ts_1 = require("./supabase.js");
17
+ Object.defineProperty(exports, "seleIdAccessToken", { enumerable: true, get: function () { return supabase_ts_1.seleIdAccessToken; } });
18
+ var events_ts_1 = require("./events.js");
19
+ Object.defineProperty(exports, "SeleEvents", { enumerable: true, get: function () { return events_ts_1.SeleEvents; } });
20
+ Object.defineProperty(exports, "verifyWebhook", { enumerable: true, get: function () { return events_ts_1.verifyWebhook; } });
21
+ var ai_ts_1 = require("./ai.js");
22
+ Object.defineProperty(exports, "SeleAi", { enumerable: true, get: function () { return ai_ts_1.SeleAi; } });
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,41 @@
1
+ import { type ClientOptions } from './http.ts';
2
+ export interface StoredObject {
3
+ key: string;
4
+ size: number;
5
+ uploaded: string | null;
6
+ }
7
+ export interface ObjectList {
8
+ objects: StoredObject[];
9
+ truncated: boolean;
10
+ cursor: string | null;
11
+ }
12
+ export interface SignedUrl {
13
+ url: string;
14
+ method: 'GET' | 'PUT';
15
+ expires_at: string;
16
+ }
17
+ export declare class SeleStorage {
18
+ private readonly http;
19
+ constructor(o: ClientOptions);
20
+ put(key: string, body: Uint8Array | string, contentType?: string): Promise<{
21
+ key: string;
22
+ size: number;
23
+ }>;
24
+ get(key: string): Promise<Uint8Array>;
25
+ getText(key: string): Promise<string>;
26
+ delete(key: string): Promise<{
27
+ deleted: string;
28
+ }>;
29
+ list(o?: {
30
+ prefix?: string;
31
+ limit?: number;
32
+ cursor?: string;
33
+ }): Promise<ObjectList>;
34
+ /** A URL that lets a browser upload or download one object without your key. Relative to the storage base URL. */
35
+ signedUrl(o: {
36
+ method: 'GET' | 'PUT';
37
+ key: string;
38
+ expiresIn?: number;
39
+ }): Promise<SignedUrl>;
40
+ usage(): Promise<Record<string, unknown>>;
41
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SeleStorage = void 0;
4
+ const http_ts_1 = require("./http.js");
5
+ class SeleStorage {
6
+ http;
7
+ constructor(o) { this.http = new http_ts_1.Http({ baseUrl: 'https://storage.seleid.dev' }, o); }
8
+ async put(key, body, contentType = 'application/octet-stream') {
9
+ const bytes = typeof body === 'string' ? new TextEncoder().encode(body) : body;
10
+ const res = await this.http.raw('PUT', `/v1/objects/${(0, http_ts_1.enc)(key)}`, { headers: { 'content-type': contentType, 'content-length': String(bytes.byteLength) }, body: bytes });
11
+ return (await res.json());
12
+ }
13
+ async get(key) { return new Uint8Array(await (await this.http.raw('GET', `/v1/objects/${(0, http_ts_1.enc)(key)}`)).arrayBuffer()); }
14
+ async getText(key) { return new TextDecoder().decode(await this.get(key)); }
15
+ delete(key) { return this.http.json('DELETE', `/v1/objects/${(0, http_ts_1.enc)(key)}`); }
16
+ list(o = {}) { return this.http.json('GET', '/v1/objects', undefined, o); }
17
+ /** A URL that lets a browser upload or download one object without your key. Relative to the storage base URL. */
18
+ signedUrl(o) {
19
+ return this.http.json('POST', '/v1/signed-urls', { method: o.method, key: o.key, expires_in: o.expiresIn });
20
+ }
21
+ usage() { return this.http.json('GET', '/v1/usage'); }
22
+ }
23
+ exports.SeleStorage = SeleStorage;
@@ -0,0 +1,18 @@
1
+ import type { Tokens } from './auth.ts';
2
+ export interface SeleIdTokenSource {
3
+ /** Current session tokens, or null when signed out. Must include `id_token` (request the `openid` scope). */
4
+ getTokens(): Promise<Tokens | null> | Tokens | null;
5
+ /** Called with the refresh token when the ID token is (almost) expired. Typically `(rt) => auth.refresh(rt)`. */
6
+ refresh(refreshToken: string): Promise<Tokens>;
7
+ /** Called with the fresh tokens so your app can persist the rotated refresh token. */
8
+ onTokens?(tokens: Tokens): void | Promise<void>;
9
+ /** Refresh this many seconds before expiry. Default 60. */
10
+ skewSeconds?: number;
11
+ /** Clock override for tests (ms). */
12
+ now?: () => number;
13
+ }
14
+ /**
15
+ * Returns the callback for `createClient(url, anonKey, { accessToken })`. It returns a valid SeleID ID token,
16
+ * refreshing it (once, even for concurrent calls) when it is within `skewSeconds` of expiry, and null when signed out.
17
+ */
18
+ export declare function seleIdAccessToken(src: SeleIdTokenSource): () => Promise<string | null>;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.seleIdAccessToken = seleIdAccessToken;
4
+ function expOf(jwt) {
5
+ try {
6
+ const part = jwt.split('.')[1]?.replace(/-/g, '+').replace(/_/g, '/');
7
+ if (!part)
8
+ return null;
9
+ const json = JSON.parse(atob(part.padEnd(Math.ceil(part.length / 4) * 4, '=')));
10
+ return typeof json.exp === 'number' ? json.exp : null;
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
16
+ /**
17
+ * Returns the callback for `createClient(url, anonKey, { accessToken })`. It returns a valid SeleID ID token,
18
+ * refreshing it (once, even for concurrent calls) when it is within `skewSeconds` of expiry, and null when signed out.
19
+ */
20
+ function seleIdAccessToken(src) {
21
+ const skew = (src.skewSeconds ?? 60) * 1000;
22
+ const now = src.now ?? (() => Date.now());
23
+ let inflight = null;
24
+ const fresh = (t) => {
25
+ const exp = t?.id_token ? expOf(t.id_token) : null;
26
+ return t?.id_token && exp !== null && exp * 1000 - now() > skew ? t.id_token : null;
27
+ };
28
+ return async () => {
29
+ const tokens = await src.getTokens();
30
+ if (!tokens)
31
+ return null;
32
+ const ok = fresh(tokens);
33
+ if (ok)
34
+ return ok;
35
+ if (!tokens.refresh_token)
36
+ return null;
37
+ inflight ??= (async () => {
38
+ try {
39
+ const next = await src.refresh(tokens.refresh_token);
40
+ await src.onTokens?.(next);
41
+ return fresh(next);
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ finally {
47
+ inflight = null;
48
+ }
49
+ })();
50
+ return inflight;
51
+ };
52
+ }
@@ -0,0 +1,37 @@
1
+ import { type ClientOptions } from './http.ts';
2
+ export interface ChatMessage {
3
+ role: 'system' | 'user' | 'assistant';
4
+ content: string;
5
+ }
6
+ export interface ChatResponse {
7
+ id: string;
8
+ model: string;
9
+ message: {
10
+ role: 'assistant';
11
+ content: string;
12
+ };
13
+ usage: {
14
+ input_tokens: number;
15
+ output_tokens: number;
16
+ attempts: number;
17
+ charge_micro_usd: number;
18
+ };
19
+ }
20
+ export declare class SeleAi {
21
+ private readonly http;
22
+ constructor(o: ClientOptions);
23
+ models(): Promise<{
24
+ models: Array<{
25
+ id: string;
26
+ context_tokens: number;
27
+ max_output_tokens: number;
28
+ }>;
29
+ }>;
30
+ usage(): Promise<Record<string, number>>;
31
+ /** Throws SeleError with code `spend_cap_reached` (HTTP 402) when the cap or credit is used up. */
32
+ chat(r: {
33
+ model: string;
34
+ messages: ChatMessage[];
35
+ maxTokens?: number;
36
+ }): Promise<ChatResponse>;
37
+ }
package/dist/esm/ai.js ADDED
@@ -0,0 +1,11 @@
1
+ import { Http } from "./http.js";
2
+ export class SeleAi {
3
+ http;
4
+ constructor(o) { this.http = new Http({ baseUrl: 'https://api.seleid.dev' }, o); }
5
+ models() { return this.http.json('GET', '/v1/ai/models'); }
6
+ usage() { return this.http.json('GET', '/v1/ai/usage'); }
7
+ /** Throws SeleError with code `spend_cap_reached` (HTTP 402) when the cap or credit is used up. */
8
+ chat(r) {
9
+ return this.http.json('POST', '/v1/ai/chat', { model: r.model, messages: r.messages, max_tokens: r.maxTokens });
10
+ }
11
+ }
@@ -0,0 +1,61 @@
1
+ import { type FetchLike } from './http.ts';
2
+ export declare const ISSUER = "https://auth.seleid.dev";
3
+ export interface PkcePair {
4
+ codeVerifier: string;
5
+ codeChallenge: string;
6
+ }
7
+ /** Generates a 64 character verifier and its S256 challenge. */
8
+ export declare function createPkce(): Promise<PkcePair>;
9
+ export interface AuthorizeRequest {
10
+ clientId: string;
11
+ redirectUri: string;
12
+ /** Default: openid profile email. Must include openid. */
13
+ scope?: string;
14
+ codeChallenge: string;
15
+ state?: string;
16
+ nonce?: string;
17
+ issuer?: string;
18
+ }
19
+ export interface AuthorizeResult {
20
+ url: string;
21
+ state: string;
22
+ nonce: string;
23
+ }
24
+ /** Builds the /authorize URL. Store `state`, `nonce` and the verifier with the session; check `state` on return. */
25
+ export declare function buildAuthorizeUrl(r: AuthorizeRequest): AuthorizeResult;
26
+ export interface Tokens {
27
+ access_token: string;
28
+ token_type: 'Bearer';
29
+ expires_in: number;
30
+ refresh_token?: string;
31
+ id_token?: string;
32
+ scope?: string;
33
+ }
34
+ export interface UserInfo {
35
+ sub: string;
36
+ name?: string;
37
+ email?: string;
38
+ email_verified?: boolean;
39
+ }
40
+ export declare class SeleIdAuth {
41
+ private readonly http;
42
+ private readonly o;
43
+ constructor(o: {
44
+ clientId: string;
45
+ clientSecret?: string;
46
+ issuer?: string;
47
+ fetch?: FetchLike;
48
+ });
49
+ private form;
50
+ /** Exchange the `code` from the redirect. Check `state` yourself before calling this. */
51
+ exchangeCode(p: {
52
+ code: string;
53
+ redirectUri: string;
54
+ codeVerifier: string;
55
+ }): Promise<Tokens>;
56
+ /** Refresh tokens rotate: store the new refresh_token from the response. */
57
+ refresh(refreshToken: string): Promise<Tokens>;
58
+ userInfo(accessToken: string): Promise<UserInfo>;
59
+ revoke(token: string): Promise<void>;
60
+ discovery(): Promise<Record<string, unknown>>;
61
+ }
@@ -0,0 +1,56 @@
1
+ // Continue with SeleID: authorization code flow with PKCE (S256). Safe for public clients (no secret needed).
2
+ import { Http, SeleError } from "./http.js";
3
+ export const ISSUER = 'https://auth.seleid.dev';
4
+ const b64url = (bytes) => btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
5
+ const random = (n) => b64url(crypto.getRandomValues(new Uint8Array(n)));
6
+ /** Generates a 64 character verifier and its S256 challenge. */
7
+ export async function createPkce() {
8
+ const codeVerifier = random(48);
9
+ const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier)));
10
+ return { codeVerifier, codeChallenge: b64url(digest) };
11
+ }
12
+ /** Builds the /authorize URL. Store `state`, `nonce` and the verifier with the session; check `state` on return. */
13
+ export function buildAuthorizeUrl(r) {
14
+ const scope = r.scope ?? 'openid profile email';
15
+ if (!scope.split(' ').includes('openid'))
16
+ throw new Error('scope must include openid');
17
+ const state = r.state ?? random(24);
18
+ const nonce = r.nonce ?? random(24);
19
+ const u = new URL(`${(r.issuer ?? ISSUER).replace(/\/+$/, '')}/authorize`);
20
+ u.search = new URLSearchParams({ client_id: r.clientId, redirect_uri: r.redirectUri, response_type: 'code', scope, state, nonce, code_challenge: r.codeChallenge, code_challenge_method: 'S256' }).toString();
21
+ return { url: u.toString(), state, nonce };
22
+ }
23
+ export class SeleIdAuth {
24
+ http;
25
+ o;
26
+ constructor(o) {
27
+ this.o = o;
28
+ this.http = new Http({ baseUrl: ISSUER }, { baseUrl: o.issuer, fetch: o.fetch });
29
+ }
30
+ async form(path, params) {
31
+ const res = await this.http.raw('POST', path, { headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams(params).toString() });
32
+ return (await res.json());
33
+ }
34
+ /** Exchange the `code` from the redirect. Check `state` yourself before calling this. */
35
+ exchangeCode(p) {
36
+ return this.form('/oauth/token', { grant_type: 'authorization_code', code: p.code, redirect_uri: p.redirectUri, client_id: this.o.clientId, code_verifier: p.codeVerifier, ...(this.o.clientSecret ? { client_secret: this.o.clientSecret } : {}) });
37
+ }
38
+ /** Refresh tokens rotate: store the new refresh_token from the response. */
39
+ refresh(refreshToken) {
40
+ return this.form('/oauth/token', { grant_type: 'refresh_token', refresh_token: refreshToken, client_id: this.o.clientId, ...(this.o.clientSecret ? { client_secret: this.o.clientSecret } : {}) });
41
+ }
42
+ async userInfo(accessToken) {
43
+ const res = await this.http.raw('GET', '/oauth/userinfo', { headers: { authorization: `Bearer ${accessToken}` } });
44
+ return (await res.json());
45
+ }
46
+ async revoke(token) {
47
+ try {
48
+ await this.form('/oauth/revoke', { token, client_id: this.o.clientId });
49
+ }
50
+ catch (e) {
51
+ if (!(e instanceof SeleError) || e.status >= 500)
52
+ throw e;
53
+ }
54
+ }
55
+ discovery() { return this.http.json('GET', '/.well-known/openid-configuration'); }
56
+ }
@@ -0,0 +1,29 @@
1
+ import { type ClientOptions } from './http.ts';
2
+ export type Param = string | number | boolean | null;
3
+ export interface QueryResult {
4
+ [k: string]: unknown;
5
+ }
6
+ export declare class SeleDb {
7
+ private readonly http;
8
+ constructor(o: ClientOptions);
9
+ listDatabases(): Promise<{
10
+ databases: Array<{
11
+ name: string;
12
+ sizeBytes: number;
13
+ createdAt: string;
14
+ }>;
15
+ }>;
16
+ createDatabase(name: string): Promise<{
17
+ name: string;
18
+ createdAt: string;
19
+ }>;
20
+ deleteDatabase(name: string): Promise<{
21
+ deleted: string;
22
+ }>;
23
+ /** One statement per call. Always pass user input through `params`, never string-concatenate it into `sql`. */
24
+ query(database: string, sql: string, params?: Param[]): Promise<QueryResult>;
25
+ schema(database: string): Promise<{
26
+ schema: unknown;
27
+ }>;
28
+ usage(): Promise<Record<string, unknown>>;
29
+ }
package/dist/esm/db.js ADDED
@@ -0,0 +1,14 @@
1
+ import { Http } from "./http.js";
2
+ export class SeleDb {
3
+ http;
4
+ constructor(o) { this.http = new Http({ baseUrl: 'https://api.seleid.dev' }, o); }
5
+ listDatabases() { return this.http.json('GET', '/v1/databases'); }
6
+ createDatabase(name) { return this.http.json('POST', '/v1/databases', { name }); }
7
+ deleteDatabase(name) { return this.http.json('DELETE', `/v1/databases/${encodeURIComponent(name)}`); }
8
+ /** One statement per call. Always pass user input through `params`, never string-concatenate it into `sql`. */
9
+ query(database, sql, params = []) {
10
+ return this.http.json('POST', `/v1/databases/${encodeURIComponent(database)}/query`, { sql, params });
11
+ }
12
+ schema(database) { return this.http.json('GET', `/v1/databases/${encodeURIComponent(database)}/schema`); }
13
+ usage() { return this.http.json('GET', '/v1/usage'); }
14
+ }
@@ -0,0 +1,51 @@
1
+ import { type ClientOptions } from './http.ts';
2
+ export declare class SeleEvents {
3
+ private readonly http;
4
+ constructor(o: ClientOptions);
5
+ createWebhookEndpoint(url: string, events: string[]): Promise<{
6
+ id: string;
7
+ secret: string;
8
+ }>;
9
+ listWebhookEndpoints(): Promise<{
10
+ endpoints: unknown[];
11
+ }>;
12
+ deleteWebhookEndpoint(id: string): Promise<{
13
+ deleted: string;
14
+ }>;
15
+ rotateWebhookSecret(id: string): Promise<{
16
+ secret: string;
17
+ }>;
18
+ emit(type: string, data: unknown): Promise<{
19
+ deliveries: unknown[];
20
+ }>;
21
+ deliveries(endpoint?: string): Promise<{
22
+ deliveries: unknown[];
23
+ }>;
24
+ replay(deliveryId: string): Promise<unknown>;
25
+ sendMail(m: {
26
+ from: string;
27
+ to: string;
28
+ subject: string;
29
+ text?: string;
30
+ html?: string;
31
+ }): Promise<unknown>;
32
+ registerDevice(d: {
33
+ userId: string;
34
+ platform: 'android' | 'ios' | 'web';
35
+ token: string;
36
+ }): Promise<unknown>;
37
+ sendPush(p: {
38
+ userId: string;
39
+ title: string;
40
+ body: string;
41
+ data?: unknown;
42
+ }): Promise<unknown>;
43
+ }
44
+ /**
45
+ * Verify a webhook. Pass the RAW request body string, the `seleid-signature` header and your endpoint secret.
46
+ * Header format: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">. Rejects timestamps older than `toleranceSec` (default 300).
47
+ */
48
+ export declare function verifyWebhook(secret: string, rawBody: string, header: string | null | undefined, opts?: {
49
+ nowSec?: number;
50
+ toleranceSec?: number;
51
+ }): Promise<boolean>;
@@ -0,0 +1,36 @@
1
+ import { Http } from "./http.js";
2
+ export class SeleEvents {
3
+ http;
4
+ constructor(o) { this.http = new Http({ baseUrl: 'https://api.seleid.dev' }, o); }
5
+ createWebhookEndpoint(url, events) { return this.http.json('POST', '/v1/webhooks/endpoints', { url, events }); }
6
+ listWebhookEndpoints() { return this.http.json('GET', '/v1/webhooks/endpoints'); }
7
+ deleteWebhookEndpoint(id) { return this.http.json('DELETE', `/v1/webhooks/endpoints/${encodeURIComponent(id)}`); }
8
+ rotateWebhookSecret(id) { return this.http.json('POST', `/v1/webhooks/endpoints/${encodeURIComponent(id)}/rotate`); }
9
+ emit(type, data) { return this.http.json('POST', '/v1/events', { type, data }); }
10
+ deliveries(endpoint) { return this.http.json('GET', '/v1/webhooks/deliveries', undefined, { endpoint }); }
11
+ replay(deliveryId) { return this.http.json('POST', `/v1/webhooks/deliveries/${encodeURIComponent(deliveryId)}/replay`); }
12
+ sendMail(m) { return this.http.json('POST', '/v1/mail/send', m); }
13
+ registerDevice(d) { return this.http.json('POST', '/v1/push/devices', d); }
14
+ sendPush(p) { return this.http.json('POST', '/v1/push/send', p); }
15
+ }
16
+ const hex = (b) => [...new Uint8Array(b)].map((x) => x.toString(16).padStart(2, '0')).join('');
17
+ /**
18
+ * Verify a webhook. Pass the RAW request body string, the `seleid-signature` header and your endpoint secret.
19
+ * Header format: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">. Rejects timestamps older than `toleranceSec` (default 300).
20
+ */
21
+ export async function verifyWebhook(secret, rawBody, header, opts = {}) {
22
+ const parts = Object.fromEntries(String(header ?? '').split(',').map((p) => { const i = p.indexOf('='); return [p.slice(0, i), p.slice(i + 1)]; }));
23
+ const t = Number(parts.t);
24
+ if (!Number.isInteger(t) || typeof parts.v1 !== 'string')
25
+ return false;
26
+ if (Math.abs((opts.nowSec ?? Date.now() / 1000) - t) > (opts.toleranceSec ?? 300))
27
+ return false;
28
+ const key = await crypto.subtle.importKey('raw', new TextEncoder().encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
29
+ const want = hex(await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(`${t}.${rawBody}`)));
30
+ if (want.length !== parts.v1.length)
31
+ return false;
32
+ let diff = 0;
33
+ for (let i = 0; i < want.length; i++)
34
+ diff |= want.charCodeAt(i) ^ parts.v1.charCodeAt(i);
35
+ return diff === 0;
36
+ }
@@ -0,0 +1,33 @@
1
+ export type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
2
+ export interface ClientOptions {
3
+ /** API key from the developer console. Keep it on the server; never ship it in a browser or mobile app. */
4
+ apiKey?: string;
5
+ baseUrl?: string;
6
+ fetch?: FetchLike;
7
+ /** Request timeout in milliseconds. Default 15000. */
8
+ timeoutMs?: number;
9
+ }
10
+ /** Thrown for every non-2xx response. `code` is the machine-readable error code from the API. */
11
+ export declare class SeleError extends Error {
12
+ readonly status: number;
13
+ readonly code: string;
14
+ readonly requestId?: string;
15
+ readonly body: unknown;
16
+ constructor(status: number, code: string, message: string, body: unknown, requestId?: string);
17
+ }
18
+ export declare class Http {
19
+ readonly baseUrl: string;
20
+ private readonly apiKey?;
21
+ private readonly fetchFn;
22
+ private readonly timeoutMs;
23
+ constructor(defaults: {
24
+ baseUrl: string;
25
+ }, o?: ClientOptions);
26
+ raw(method: string, path: string, init?: {
27
+ headers?: Record<string, string>;
28
+ body?: BodyInit | null;
29
+ query?: Record<string, string | number | undefined>;
30
+ }): Promise<Response>;
31
+ json<T>(method: string, path: string, body?: unknown, query?: Record<string, string | number | undefined>): Promise<T>;
32
+ }
33
+ export declare const enc: (s: string) => string;
@@ -0,0 +1,53 @@
1
+ /** Thrown for every non-2xx response. `code` is the machine-readable error code from the API. */
2
+ export class SeleError extends Error {
3
+ status;
4
+ code;
5
+ requestId;
6
+ body;
7
+ constructor(status, code, message, body, requestId) {
8
+ super(message);
9
+ this.name = 'SeleError';
10
+ this.status = status;
11
+ this.code = code;
12
+ this.body = body;
13
+ this.requestId = requestId;
14
+ }
15
+ }
16
+ export class Http {
17
+ baseUrl;
18
+ apiKey;
19
+ fetchFn;
20
+ timeoutMs;
21
+ constructor(defaults, o = {}) {
22
+ this.baseUrl = (o.baseUrl ?? defaults.baseUrl).replace(/\/+$/, '');
23
+ this.apiKey = o.apiKey;
24
+ this.fetchFn = o.fetch ?? ((i, init) => fetch(i, init));
25
+ this.timeoutMs = o.timeoutMs ?? 15000;
26
+ }
27
+ async raw(method, path, init = {}) {
28
+ const q = init.query ? Object.entries(init.query).filter(([, v]) => v !== undefined).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join('&') : '';
29
+ const headers = { ...init.headers };
30
+ if (this.apiKey && !headers.authorization)
31
+ headers.authorization = `Bearer ${this.apiKey}`;
32
+ const res = await this.fetchFn(`${this.baseUrl}${path}${q ? `?${q}` : ''}`, { method, headers, body: init.body ?? undefined, signal: AbortSignal.timeout(this.timeoutMs) });
33
+ if (!res.ok)
34
+ throw await toError(res);
35
+ return res;
36
+ }
37
+ async json(method, path, body, query) {
38
+ const res = await this.raw(method, path, { headers: body === undefined ? {} : { 'content-type': 'application/json' }, body: body === undefined ? undefined : JSON.stringify(body), query });
39
+ return (await res.json());
40
+ }
41
+ }
42
+ async function toError(res) {
43
+ let body = null;
44
+ try {
45
+ body = await res.json();
46
+ }
47
+ catch { /* not JSON */ }
48
+ const e = body?.error;
49
+ const code = typeof e === 'string' ? e : typeof e?.code === 'string' ? e.code : `http_${res.status}`;
50
+ const message = typeof e?.message === 'string' ? e.message : typeof body?.error_description === 'string' ? body.error_description : `Request failed with status ${res.status}`;
51
+ return new SeleError(res.status, code, message, body, typeof e?.request_id === 'string' ? e.request_id : undefined);
52
+ }
53
+ export const enc = (s) => s.split('/').map(encodeURIComponent).join('/');
@@ -0,0 +1,7 @@
1
+ export { SeleError, type ClientOptions, type FetchLike } from './http.ts';
2
+ export { SeleIdAuth, createPkce, buildAuthorizeUrl, ISSUER, type Tokens, type UserInfo, type PkcePair, type AuthorizeRequest, type AuthorizeResult } from './auth.ts';
3
+ export { SeleStorage, type StoredObject, type ObjectList, type SignedUrl } from './storage.ts';
4
+ export { SeleDb, SeleDb as SeleBase, type Param, type QueryResult } from './db.ts';
5
+ export { seleIdAccessToken, type SeleIdTokenSource } from './supabase.ts';
6
+ export { SeleEvents, verifyWebhook } from './events.ts';
7
+ export { SeleAi, type ChatMessage, type ChatResponse } from './ai.ts';
@@ -0,0 +1,7 @@
1
+ export { SeleError } from "./http.js";
2
+ export { SeleIdAuth, createPkce, buildAuthorizeUrl, ISSUER } from "./auth.js";
3
+ export { SeleStorage } from "./storage.js";
4
+ export { SeleDb, SeleDb as SeleBase } from "./db.js";
5
+ export { seleIdAccessToken } from "./supabase.js";
6
+ export { SeleEvents, verifyWebhook } from "./events.js";
7
+ export { SeleAi } from "./ai.js";