@forumulate/sdk 0.5.0 → 0.7.0

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,180 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.positionalHelpers = positionalHelpers;
4
+ const webhooks_1 = require("./webhooks");
5
+ function positionalHelpers(request) {
6
+ return {
7
+ // ── Identity ──────────────────────────────────────────────────────────────
8
+ auth: {
9
+ // `register` and `login` were removed in 0.6: the Atlas cutover removed `/auth/register`
10
+ // and `/auth/login`, so both failed for every caller. Sign-in is Atlas (a browser
11
+ // flow); an integration authenticates with a Personal Access Token.
12
+ me: () => request('GET', '/auth/me'),
13
+ /** Mint a Personal Access Token (requires an authenticated session). */
14
+ createToken: (input) => request('POST', '/pats', {
15
+ body: input,
16
+ }),
17
+ },
18
+ // ── Organizations ─────────────────────────────────────────────────────────
19
+ orgs: {
20
+ list: () => request('GET', '/orgs'),
21
+ create: (input) => request('POST', '/orgs', { body: input }),
22
+ },
23
+ // ── Spaces & content ───────────────────────────────────────────────────────
24
+ spaces: {
25
+ list: (orgId) => request('GET', `/orgs/${orgId}/spaces`),
26
+ create: (orgId, input) => request('POST', `/orgs/${orgId}/spaces`, { body: input }),
27
+ createCollection: (orgId, spaceId, input) => request('POST', `/orgs/${orgId}/spaces/${spaceId}/collections`, { body: input }),
28
+ createPage: (orgId, spaceId, collectionId, input) => request('POST', `/orgs/${orgId}/spaces/${spaceId}/collections/${collectionId}/pages`, { body: input }),
29
+ setPageContent: (orgId, spaceId, pageId, input) => request('PUT', `/orgs/${orgId}/spaces/${spaceId}/pages/${pageId}/content`, {
30
+ body: input,
31
+ }),
32
+ publishPage: (orgId, spaceId, pageId) => request('POST', `/orgs/${orgId}/spaces/${spaceId}/pages/${pageId}/publish`, { body: {} }),
33
+ },
34
+ /**
35
+ * Shapes and pieces — the surface an agent works through.
36
+ *
37
+ * `shape` says what one piece of a space is made of and hands back JSON
38
+ * Schema; `items` writes one, whatever a piece happens to be in that space:
39
+ * a documentation page, a blog post, a release, an incident. The reply says
40
+ * which required sections are still empty, so an agent can create a piece,
41
+ * ask what it forgot, and close the gap.
42
+ */
43
+ shapes: {
44
+ get: (orgId, spaceId) => request('GET', `/orgs/${orgId}/spaces/${spaceId}/shape`),
45
+ set: (orgId, spaceId, patch) => request('PUT', `/orgs/${orgId}/spaces/${spaceId}/shape`, {
46
+ body: patch,
47
+ }),
48
+ applyPreset: (orgId, spaceId, name) => request('POST', `/orgs/${orgId}/spaces/${spaceId}/shape/presets/${name}`, {
49
+ body: {},
50
+ }),
51
+ },
52
+ items: {
53
+ create: (orgId, spaceId, input) => request('POST', `/orgs/${orgId}/spaces/${spaceId}/items`, {
54
+ body: input,
55
+ }),
56
+ /**
57
+ * What is in a space: how something finds a piece it has no words for.
58
+ *
59
+ * Pass `include: ['body']` to get content. Asking for `sections` ALONE is
60
+ * the trap: it is populated only for pieces written against a shape, so a
61
+ * space of free-form pages answers with empty objects and a sync carries
62
+ * nothing while reporting success. Every row says which field holds its
63
+ * content in `contentIn`.
64
+ *
65
+ * Any option here puts the response in its paged form, so `page` and
66
+ * `sync` come back — `page.total` is what a walk should check its own
67
+ * count against, and `sync.checkpoint` is what an incremental sync stores.
68
+ */
69
+ list: (orgId, spaceId, opts = {}) => {
70
+ // A bare number used to be the whole signature; keep it working.
71
+ const o = typeof opts === 'number' ? { limit: opts } : opts;
72
+ const query = {};
73
+ if (o.limit)
74
+ query.limit = o.limit;
75
+ if (o.include?.length)
76
+ query.include = o.include.join(',');
77
+ if (o.cursor)
78
+ query.cursor = o.cursor;
79
+ if (o.updatedSince)
80
+ query.updated_since = o.updatedSince;
81
+ if (o.includeDeleted)
82
+ query.include_deleted = 'true';
83
+ if (o.fields?.length)
84
+ query.fields = o.fields.join(',');
85
+ return request('GET', `/orgs/${orgId}/spaces/${spaceId}/items`, { query });
86
+ },
87
+ get: (orgId, spaceId, itemId) => request('GET', `/orgs/${orgId}/spaces/${spaceId}/items/${itemId}`),
88
+ /**
89
+ * Make it readable. Kinds that are live on creation say so and change nothing.
90
+ *
91
+ * `conformance` describes the piece AFTER publishing — which declared slots
92
+ * are filled and which are still missing — so an agent that has just made
93
+ * its work readable can tell a complete piece from one with holes in it
94
+ * without fetching it again. The server has always sent it and this type
95
+ * omitted it, so a TypeScript caller could not see the field the docs
96
+ * promise (E-11).
97
+ */
98
+ publish: (orgId, spaceId, itemId) => request('POST', `/orgs/${orgId}/spaces/${spaceId}/items/${itemId}/publish`, { body: {} }),
99
+ /**
100
+ * Replace a piece's whole body — as markdown, or as blocks.
101
+ *
102
+ * The create door has always taken markdown and this one took blocks only,
103
+ * so "make it from this file" worked and "update it from the same file" did
104
+ * not. Both now; blocks win if a caller sends both.
105
+ */
106
+ setContent: (orgId, spaceId, itemId, input) => request('PUT', `/orgs/${orgId}/spaces/${spaceId}/items/${itemId}/content`, { body: input }),
107
+ /** Replace one named section. Idempotent — sending it twice writes it once. */
108
+ setSection: (orgId, spaceId, itemId, key, markdown) => request('PUT', `/orgs/${orgId}/spaces/${spaceId}/items/${itemId}/sections/${key}`, { body: { markdown } }),
109
+ },
110
+ /**
111
+ * The addresses a space answers at.
112
+ *
113
+ * Published because the question a customer asks about a custom domain —
114
+ * "why is it not live yet" — is answerable only from `verified`, `active` and
115
+ * the setup records, and the SDK could not reach any of them (DX-2). A failed
116
+ * verification is a 200 with `verified: false`: read the body, not the status.
117
+ */
118
+ domains: {
119
+ list: (orgId, spaceId) => request('GET', `/orgs/${orgId}/spaces/${spaceId}/domains`),
120
+ add: (orgId, spaceId, input) => request('POST', `/orgs/${orgId}/spaces/${spaceId}/domains`, {
121
+ body: input,
122
+ }),
123
+ /** Re-run the ownership check. `activate: false` proves it without switching traffic. */
124
+ verify: (orgId, spaceId, domainId, activate = true) => request('POST', `/orgs/${orgId}/spaces/${spaceId}/domains/${domainId}/verify`, { body: { activate } }),
125
+ /** Switch traffic to an address whose ownership is already proven. */
126
+ activate: (orgId, spaceId, domainId) => request('POST', `/orgs/${orgId}/spaces/${spaceId}/domains/${domainId}/activate`, { body: {} }),
127
+ /** What to configure, and what is still missing. */
128
+ setup: (orgId, spaceId, domainId) => request('GET', `/orgs/${orgId}/spaces/${spaceId}/domains/${domainId}/setup`),
129
+ },
130
+ // ── Webhooks ───────────────────────────────────────────────────────────────
131
+ webhooks: {
132
+ list: (orgId) => request('GET', `/orgs/${orgId}/webhooks`),
133
+ create: (orgId, input) => request('POST', `/orgs/${orgId}/webhooks`, { body: input }),
134
+ /** Check a delivery's `X-Forumulate-Signature`; see {@link verifyWebhookSignature}. */
135
+ verifySignature: webhooks_1.verifyWebhookSignature,
136
+ /** Verify a delivery and parse it; throws when the signature does not hold. */
137
+ constructEvent: webhooks_1.constructWebhookEvent,
138
+ },
139
+ // ── Space snapshots (version history) ───────────────────────────────────────
140
+ snapshots: {
141
+ list: (orgId, spaceId) => request('GET', `/orgs/${orgId}/spaces/${spaceId}/snapshots`),
142
+ create: (orgId, spaceId, label) => request('POST', `/orgs/${orgId}/spaces/${spaceId}/snapshots`, {
143
+ body: { label },
144
+ }),
145
+ restore: (orgId, spaceId, id) => request('POST', `/orgs/${orgId}/spaces/${spaceId}/snapshots/${id}/restore`, { body: {} }),
146
+ },
147
+ // ── Community (forum topics) ────────────────────────────────────────────────
148
+ forum: {
149
+ listTopics: (orgId, spaceId, query = {}) => request('GET', `/orgs/${orgId}/spaces/${spaceId}/forum`, { query }),
150
+ createTopic: (orgId, spaceId, input) => request('POST', `/orgs/${orgId}/spaces/${spaceId}/forum`, {
151
+ body: input,
152
+ }),
153
+ reply: (orgId, spaceId, topicId, content) => request('POST', `/orgs/${orgId}/spaces/${spaceId}/forum/${topicId}/replies`, { body: { content } }),
154
+ },
155
+ // ── Search & analytics (org-level) ──────────────────────────────────────────
156
+ search: {
157
+ unified: (orgId, q, opts = {}) => request('GET', `/orgs/${orgId}/search`, {
158
+ query: { q, ...opts },
159
+ }),
160
+ },
161
+ analytics: {
162
+ orgEvents: (orgId, days) => request('GET', `/orgs/${orgId}/analytics/events`, {
163
+ query: days ? { days } : {},
164
+ }),
165
+ },
166
+ // ── Public reader (no auth required) ────────────────────────────────────────
167
+ public: {
168
+ getPage: (orgId, spaceId, pageId) => request('GET', `/public/orgs/${orgId}/spaces/${spaceId}/pages/${pageId}`),
169
+ search: (orgId, spaceId, query) => request('GET', `/public/orgs/${orgId}/spaces/${spaceId}/search`, {
170
+ query: { q: query },
171
+ }),
172
+ listComments: (orgId, spaceId, pageId) => request('GET', `/public/orgs/${orgId}/spaces/${spaceId}/pages/${pageId}/comments`),
173
+ postComment: (orgId, spaceId, pageId, input) => request('POST', `/public/orgs/${orgId}/spaces/${spaceId}/pages/${pageId}/comments`, { body: input }),
174
+ reactToComment: (orgId, spaceId, commentId, emoji) => request('POST', `/public/orgs/${orgId}/spaces/${spaceId}/comments/${commentId}/react`, { body: { emoji } }),
175
+ /** Download the whole space as a PDF. Returns the raw `Response` (read `.blob()` / `.arrayBuffer()`). */
176
+ pdf: (orgId, spaceId) => request('GET', `/public/orgs/${orgId}/spaces/${spaceId}/pdf`, { raw: true }),
177
+ },
178
+ };
179
+ }
180
+ //# sourceMappingURL=helpers.js.map
package/dist/http.d.ts ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The one place a request leaves the SDK: headers, body encoding, retries,
3
+ * response decoding and the error type. Every surface — the positional
4
+ * helpers, `typed()`, `request(operationId)` and the generated namespaces —
5
+ * goes through `send`, so a retry or a header rule cannot apply to one door
6
+ * and not another.
7
+ */
8
+ /** Kept in step with package.json by `coverage.test.ts`. */
9
+ export declare const SDK_VERSION = "0.7.0";
10
+ /** The API's error envelope: `{ error: { code, message, details? }, requestId? }`. */
11
+ export interface ForumulateErrorInit {
12
+ /** Field-level detail, e.g. `{ title: ['Required'] }` for `validation.failed`. */
13
+ details?: unknown;
14
+ /** The id to quote in a support request; the API puts it on every error. */
15
+ requestId?: string;
16
+ /** Seconds the API asked us to wait (`Retry-After`), when it said. */
17
+ retryAfter?: number;
18
+ /** The parsed body, whole, for anything the fields above do not carry. */
19
+ body?: unknown;
20
+ /** The response headers of the failed request. */
21
+ headers?: Headers;
22
+ cause?: unknown;
23
+ }
24
+ /**
25
+ * Thrown for any non-2xx API response, and for a network failure that
26
+ * outlived its retries (`status: 0`, `code: 'network.error'`). Mirrors the
27
+ * API's error envelope.
28
+ */
29
+ export declare class ForumulateError extends Error {
30
+ readonly status: number;
31
+ readonly code: string;
32
+ readonly details?: unknown;
33
+ readonly requestId?: string;
34
+ readonly retryAfter?: number;
35
+ readonly body?: unknown;
36
+ readonly headers?: Headers;
37
+ constructor(status: number, code: string, message: string, init?: ForumulateErrorInit);
38
+ }
39
+ export type Query = Record<string, string | number | boolean | readonly (string | number)[] | undefined | null>;
40
+ /** Per-call options every surface accepts. */
41
+ export interface CallOptions {
42
+ /** Extra headers for this request; they win over the client's. */
43
+ headers?: Record<string, string>;
44
+ /**
45
+ * Makes a write safe to repeat: the API runs the first request with a key
46
+ * and replays its answer to any repeat for 24 hours. POST and PATCH get a
47
+ * generated one by default (reused across this call's own retries); pass
48
+ * yours to dedupe across processes, e.g. a queue message id.
49
+ */
50
+ idempotencyKey?: string;
51
+ signal?: AbortSignal;
52
+ /** Overrides the client's `maxRetries` for this call. */
53
+ maxRetries?: number;
54
+ }
55
+ export interface SendInit extends CallOptions {
56
+ method: string;
57
+ path: string;
58
+ query?: Query;
59
+ body?: unknown;
60
+ /** Return the untouched `Response` (binary downloads, streams). */
61
+ raw?: boolean;
62
+ /** How to read a 2xx body when it is not JSON. */
63
+ kind?: 'json' | 'text' | 'binary';
64
+ /** `Forumulate-Version` to send unless the caller already set one. */
65
+ version?: string;
66
+ }
67
+ export interface Transport {
68
+ baseUrl: string;
69
+ token?: string;
70
+ credentials: boolean;
71
+ headers: Record<string, string>;
72
+ fetch: typeof fetch;
73
+ maxRetries: number;
74
+ autoIdempotencyKey: boolean;
75
+ }
76
+ /**
77
+ * How long to wait before retry number `attempt` (0-based), or `null` to stop.
78
+ *
79
+ * `Retry-After` wins when present — seconds or an HTTP date — because the
80
+ * API sends it on purpose (a 429's window, a 503's batch interruption). Without
81
+ * it: exponential from 0.5s, capped at 8s, with jitter so a fleet of clients
82
+ * that failed together does not retry together.
83
+ */
84
+ export declare function retryDelayMs(attempt: number, retryAfter: string | null | undefined, now?: number): number | null;
85
+ export declare function send<T>(t: Transport, init: SendInit): Promise<T>;
86
+ //# sourceMappingURL=http.d.ts.map
package/dist/http.js ADDED
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ /**
3
+ * The one place a request leaves the SDK: headers, body encoding, retries,
4
+ * response decoding and the error type. Every surface — the positional
5
+ * helpers, `typed()`, `request(operationId)` and the generated namespaces —
6
+ * goes through `send`, so a retry or a header rule cannot apply to one door
7
+ * and not another.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.ForumulateError = exports.SDK_VERSION = void 0;
11
+ exports.retryDelayMs = retryDelayMs;
12
+ exports.send = send;
13
+ /** Kept in step with package.json by `coverage.test.ts`. */
14
+ exports.SDK_VERSION = '0.7.0';
15
+ /**
16
+ * Thrown for any non-2xx API response, and for a network failure that
17
+ * outlived its retries (`status: 0`, `code: 'network.error'`). Mirrors the
18
+ * API's error envelope.
19
+ */
20
+ class ForumulateError extends Error {
21
+ status;
22
+ code;
23
+ details;
24
+ requestId;
25
+ retryAfter;
26
+ body;
27
+ headers;
28
+ constructor(status, code, message, init = {}) {
29
+ super(message, init.cause === undefined ? undefined : { cause: init.cause });
30
+ this.status = status;
31
+ this.code = code;
32
+ this.name = 'ForumulateError';
33
+ this.details = init.details;
34
+ this.requestId = init.requestId;
35
+ this.retryAfter = init.retryAfter;
36
+ this.body = init.body;
37
+ this.headers = init.headers;
38
+ }
39
+ }
40
+ exports.ForumulateError = ForumulateError;
41
+ /** 408, 429 and the gateway family. A 500 is not here: the API means it ("retrying will not help"). */
42
+ const RETRY_STATUS = new Set([408, 429, 502, 503, 504]);
43
+ const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE']);
44
+ /** A Retry-After longer than this is a "come back later", not a retry; surface it. */
45
+ const MAX_RETRY_AFTER_MS = 60_000;
46
+ const hasHeader = (h, name) => Object.keys(h).some((k) => k.toLowerCase() === name.toLowerCase());
47
+ const isRawBody = (b) => (typeof FormData !== 'undefined' && b instanceof FormData) ||
48
+ (typeof Blob !== 'undefined' && b instanceof Blob) ||
49
+ (typeof URLSearchParams !== 'undefined' && b instanceof URLSearchParams) ||
50
+ (typeof ReadableStream !== 'undefined' && b instanceof ReadableStream) ||
51
+ b instanceof ArrayBuffer ||
52
+ ArrayBuffer.isView(b);
53
+ function newIdempotencyKey() {
54
+ const c = globalThis.crypto;
55
+ if (c?.randomUUID)
56
+ return c.randomUUID();
57
+ return 'sdk-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 12);
58
+ }
59
+ /**
60
+ * How long to wait before retry number `attempt` (0-based), or `null` to stop.
61
+ *
62
+ * `Retry-After` wins when present — seconds or an HTTP date — because the
63
+ * API sends it on purpose (a 429's window, a 503's batch interruption). Without
64
+ * it: exponential from 0.5s, capped at 8s, with jitter so a fleet of clients
65
+ * that failed together does not retry together.
66
+ */
67
+ function retryDelayMs(attempt, retryAfter, now = Date.now()) {
68
+ if (retryAfter != null && retryAfter.trim() !== '') {
69
+ const secs = Number(retryAfter);
70
+ const ms = Number.isFinite(secs) ? secs * 1000 : Date.parse(retryAfter) - now;
71
+ if (Number.isFinite(ms))
72
+ return ms > MAX_RETRY_AFTER_MS ? null : Math.max(0, ms);
73
+ }
74
+ return Math.min(500 * 2 ** attempt, 8000) * (1 - Math.random() * 0.25);
75
+ }
76
+ function sleep(ms, signal) {
77
+ return new Promise((resolve, reject) => {
78
+ if (signal?.aborted)
79
+ return reject(signal.reason);
80
+ const t = setTimeout(resolve, ms);
81
+ signal?.addEventListener?.('abort', () => {
82
+ clearTimeout(t);
83
+ reject(signal.reason);
84
+ }, { once: true });
85
+ });
86
+ }
87
+ function buildUrl(base, path, query) {
88
+ const url = new URL(base + path);
89
+ for (const [k, v] of Object.entries(query ?? {})) {
90
+ if (v === undefined || v === null)
91
+ continue;
92
+ // Lists are comma-joined: `include=sections,body` is how the API reads them.
93
+ url.searchParams.set(k, Array.isArray(v) ? v.join(',') : String(v));
94
+ }
95
+ return url.toString();
96
+ }
97
+ async function decode(res, init) {
98
+ if (init.raw || init.kind === 'binary')
99
+ return res;
100
+ const text = await res.text();
101
+ if (init.kind === 'text')
102
+ return text;
103
+ if (!text)
104
+ return undefined;
105
+ const type = res.headers?.get?.('content-type') ?? '';
106
+ // No content type (a test double, an odd proxy) is read as JSON, as it always was.
107
+ if (!type || /json/i.test(type))
108
+ return JSON.parse(text);
109
+ return text;
110
+ }
111
+ async function toError(res) {
112
+ let body;
113
+ try {
114
+ const text = await res.text();
115
+ body = text ? JSON.parse(text) : undefined;
116
+ }
117
+ catch {
118
+ /* non-JSON body (an edge block page) */
119
+ }
120
+ const env = (body ?? {});
121
+ const after = res.headers?.get?.('retry-after');
122
+ const retryAfter = after != null && Number.isFinite(Number(after)) ? Number(after) : undefined;
123
+ return new ForumulateError(res.status, env.error?.code ?? 'error', env.error?.message ?? res.statusText, {
124
+ details: env.error?.details,
125
+ requestId: env.requestId ?? res.headers?.get?.('x-request-id') ?? undefined,
126
+ retryAfter,
127
+ body,
128
+ headers: res.headers,
129
+ });
130
+ }
131
+ async function send(t, init) {
132
+ const method = init.method.toUpperCase();
133
+ const url = buildUrl(t.baseUrl, init.path, init.query);
134
+ /*
135
+ * Ours first, the caller's last, so a per-request header wins.
136
+ *
137
+ * `Idempotency-Key` has to be per-REQUEST (a key reused across two
138
+ * different writes is worse than none), and `Forumulate-Version` is
139
+ * naturally per-client — so both layers exist and the narrower one
140
+ * overrides. `Content-Type` is set before the caller's so a multipart or
141
+ * ndjson body can correct it rather than be forced to JSON.
142
+ */
143
+ const headers = { ...t.headers };
144
+ if (t.token)
145
+ headers.Authorization = `Bearer ${t.token}`;
146
+ const raw = init.body !== undefined && isRawBody(init.body);
147
+ if (init.body !== undefined && !raw)
148
+ headers['Content-Type'] = 'application/json';
149
+ if (init.version &&
150
+ !hasHeader(headers, 'forumulate-version') &&
151
+ !hasHeader(init.headers ?? {}, 'forumulate-version')) {
152
+ headers['Forumulate-Version'] = init.version;
153
+ }
154
+ Object.assign(headers, init.headers ?? {});
155
+ if (init.idempotencyKey)
156
+ headers['Idempotency-Key'] = init.idempotencyKey;
157
+ else if (t.autoIdempotencyKey &&
158
+ (method === 'POST' || method === 'PATCH') &&
159
+ !hasHeader(headers, 'idempotency-key')) {
160
+ // Generated ONCE per call, so every retry of this call carries the same key.
161
+ headers['Idempotency-Key'] = newIdempotencyKey();
162
+ }
163
+ const retryable = SAFE_METHODS.has(method) || hasHeader(headers, 'idempotency-key');
164
+ const maxRetries = retryable ? Math.max(0, init.maxRetries ?? t.maxRetries) : 0;
165
+ const body = init.body === undefined ? undefined : raw ? init.body : JSON.stringify(init.body);
166
+ for (let attempt = 0;; attempt++) {
167
+ let res;
168
+ try {
169
+ res = await t.fetch(url, {
170
+ method,
171
+ headers,
172
+ credentials: t.credentials ? 'include' : 'same-origin',
173
+ body,
174
+ signal: init.signal,
175
+ });
176
+ }
177
+ catch (err) {
178
+ if (err?.name === 'AbortError' || init.signal?.aborted)
179
+ throw err;
180
+ const wait = attempt < maxRetries ? retryDelayMs(attempt, null) : null;
181
+ if (wait === null) {
182
+ throw new ForumulateError(0, 'network.error', err?.message ?? 'Network request failed', { cause: err });
183
+ }
184
+ await sleep(wait, init.signal);
185
+ continue;
186
+ }
187
+ if (res.ok)
188
+ return (await decode(res, init));
189
+ if (attempt < maxRetries && RETRY_STATUS.has(res.status)) {
190
+ const wait = retryDelayMs(attempt, res.headers?.get?.('retry-after'));
191
+ if (wait !== null) {
192
+ await res.text?.().catch(() => undefined);
193
+ await sleep(wait, init.signal);
194
+ continue;
195
+ }
196
+ }
197
+ throw await toError(res);
198
+ }
199
+ }
200
+ //# sourceMappingURL=http.js.map