@persistmemory/sdk 0.1.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.
- package/LICENSE +21 -0
- package/README.md +520 -0
- package/dist/backoff.d.ts +73 -0
- package/dist/client.d.ts +46 -0
- package/dist/errors.d.ts +159 -0
- package/dist/http.d.ts +114 -0
- package/dist/index.cjs +1038 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +1015 -0
- package/dist/index.js.map +7 -0
- package/dist/pagination.d.ts +45 -0
- package/dist/query.d.ts +35 -0
- package/dist/resources/conversations.d.ts +38 -0
- package/dist/resources/health.d.ts +20 -0
- package/dist/resources/ingestion.d.ts +37 -0
- package/dist/resources/integrations.d.ts +48 -0
- package/dist/resources/knowledge.d.ts +70 -0
- package/dist/resources/memories.d.ts +40 -0
- package/dist/resources/search.d.ts +40 -0
- package/dist/resources/spaces.d.ts +55 -0
- package/dist/types.d.ts +500 -0
- package/package.json +59 -0
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What went wrong, in a shape a caller can branch on.
|
|
3
|
+
*
|
|
4
|
+
* One class per thing a caller can DO about it, which is not the same as one
|
|
5
|
+
* class per status code. `RateLimited` says wait, `Validation` says fix the
|
|
6
|
+
* request, `NotFound` says the id is wrong or gone, `Conflict` says re-read
|
|
7
|
+
* and try again. A code nobody branches on is a string that only looks like an
|
|
8
|
+
* API, so 402 and 418 and anything else unmapped land on the base class rather
|
|
9
|
+
* than growing a name each.
|
|
10
|
+
*
|
|
11
|
+
* The API's own error envelope is
|
|
12
|
+
*
|
|
13
|
+
* { error: { code, message, fields?, requestId } }
|
|
14
|
+
*
|
|
15
|
+
* and `code` is the stable part. Branch on the class or on `code`, never on
|
|
16
|
+
* `message`: messages get rewritten for clarity, translated, and deliberately
|
|
17
|
+
* made vaguer for security, and a client keyed to message text breaks silently
|
|
18
|
+
* when any of that happens.
|
|
19
|
+
*
|
|
20
|
+
* NOTHING in here ever holds the API key. Errors are logged, serialised into
|
|
21
|
+
* bug reports and posted into issue trackers, which is exactly how a
|
|
22
|
+
* credential escapes - see `redact` at the bottom of this file, which every
|
|
23
|
+
* message this module builds passes through.
|
|
24
|
+
*/
|
|
25
|
+
/** The stable codes the API returns. Unknown strings are possible; see below. */
|
|
26
|
+
export type ErrorCode = "VALIDATION_ERROR" | "UNAUTHORIZED" | "FORBIDDEN" | "NOT_FOUND" | "CONFLICT" | "IDEMPOTENCY_MISMATCH" | "RATE_LIMITED" | "PAYLOAD_TOO_LARGE" | "DEPENDENCY_UNAVAILABLE" | "PROCESSING_FAILED" | "INTERNAL_ERROR" | (string & {});
|
|
27
|
+
export interface ApiErrorInit {
|
|
28
|
+
readonly status: number;
|
|
29
|
+
readonly code: ErrorCode;
|
|
30
|
+
readonly message: string;
|
|
31
|
+
/** Which fields were rejected, for a validation failure. */
|
|
32
|
+
readonly fields?: Readonly<Record<string, string>>;
|
|
33
|
+
/** Ties this failure to the server's log lines. Quote it in support. */
|
|
34
|
+
readonly requestId?: string;
|
|
35
|
+
/** Seconds the server asked us to wait, when it said. */
|
|
36
|
+
readonly retryAfterSeconds?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The base every failure from this SDK inherits from.
|
|
40
|
+
*
|
|
41
|
+
* `retryable` is the single question the request loop asks. It lives on the
|
|
42
|
+
* error rather than in a table beside it because the two drift: a table says
|
|
43
|
+
* 429 is retryable while the error that reaches the loop is a transport
|
|
44
|
+
* failure nobody thought to add.
|
|
45
|
+
*/
|
|
46
|
+
export declare class PersistMemoryError extends Error {
|
|
47
|
+
readonly status: number;
|
|
48
|
+
readonly code: ErrorCode;
|
|
49
|
+
readonly fields?: Readonly<Record<string, string>>;
|
|
50
|
+
readonly requestId?: string;
|
|
51
|
+
readonly retryAfterSeconds?: number;
|
|
52
|
+
/** Retrying this exact request could plausibly succeed. */
|
|
53
|
+
readonly retryable: boolean;
|
|
54
|
+
constructor(init: ApiErrorInit);
|
|
55
|
+
/**
|
|
56
|
+
* A one-line summary safe to log.
|
|
57
|
+
*
|
|
58
|
+
* Provided so callers reach for this instead of `JSON.stringify(error)`,
|
|
59
|
+
* which walks own properties and would pick up anything a future field
|
|
60
|
+
* holds. Everything here is already server-supplied and key-free.
|
|
61
|
+
*/
|
|
62
|
+
toString(): string;
|
|
63
|
+
}
|
|
64
|
+
/** 401. The key is missing, malformed, revoked, or the session expired. */
|
|
65
|
+
export declare class AuthenticationError extends PersistMemoryError {
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 403. Known caller, and this credential will never be enough.
|
|
69
|
+
*
|
|
70
|
+
* Distinct from `AuthenticationError` because the fix is different: 401 means
|
|
71
|
+
* present a credential, 403 means this key's scopes are wrong and no amount of
|
|
72
|
+
* retrying or re-signing will change it. A read-only key calling `remember`
|
|
73
|
+
* lands here.
|
|
74
|
+
*/
|
|
75
|
+
export declare class PermissionDeniedError extends PersistMemoryError {
|
|
76
|
+
}
|
|
77
|
+
/** 404. Does not exist, or is not yours - the API answers both the same way. */
|
|
78
|
+
export declare class NotFoundError extends PersistMemoryError {
|
|
79
|
+
}
|
|
80
|
+
/** 400 and 422. The request was wrong; `fields` says where. */
|
|
81
|
+
export declare class ValidationError extends PersistMemoryError {
|
|
82
|
+
}
|
|
83
|
+
/** 409. Something changed underneath. Re-read, then try again. */
|
|
84
|
+
export declare class ConflictError extends PersistMemoryError {
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 429. Slow down.
|
|
88
|
+
*
|
|
89
|
+
* `retryAfterSeconds` comes from the `Retry-After` header, which the API
|
|
90
|
+
* always sets on a 429. It is not advice - it is the only number that knows
|
|
91
|
+
* when the window resets, and computing our own backoff instead means being
|
|
92
|
+
* refused again and spending an attempt to learn what we were already told.
|
|
93
|
+
*/
|
|
94
|
+
export declare class RateLimitError extends PersistMemoryError {
|
|
95
|
+
readonly retryable = true;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 5xx. Ours, not yours.
|
|
99
|
+
*
|
|
100
|
+
* Retryable, because a 500 or a 503 is usually a dependency that has fallen
|
|
101
|
+
* over and will come back - and the alternative, giving up on the first one,
|
|
102
|
+
* turns a three-second blip into a failed job. Retryable does not mean
|
|
103
|
+
* REPEATED: a POST still needs an idempotency key before this client will send
|
|
104
|
+
* it again, because a 500 may have been raised after the work was done.
|
|
105
|
+
*/
|
|
106
|
+
export declare class ServerError extends PersistMemoryError {
|
|
107
|
+
readonly retryable = true;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The request never got an answer: DNS, connect, reset, or a body that died
|
|
111
|
+
* mid-stream.
|
|
112
|
+
*
|
|
113
|
+
* Status 0, because there was no status. Retryable in general - but see
|
|
114
|
+
* `isRetryable` in `client.ts`, which refuses to retry a POST that may already
|
|
115
|
+
* have been processed, since a connection dying after the server accepted the
|
|
116
|
+
* request looks identical from here.
|
|
117
|
+
*/
|
|
118
|
+
export declare class ConnectionError extends PersistMemoryError {
|
|
119
|
+
readonly retryable = true;
|
|
120
|
+
constructor(message: string);
|
|
121
|
+
}
|
|
122
|
+
/** The deadline passed. A distinct class because the fix is often a bigger one. */
|
|
123
|
+
export declare class TimeoutError extends PersistMemoryError {
|
|
124
|
+
readonly retryable = true;
|
|
125
|
+
constructor(message: string);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The caller's own AbortSignal fired.
|
|
129
|
+
*
|
|
130
|
+
* NOT retryable, and not a timeout: someone asked for this to stop, and
|
|
131
|
+
* retrying it is the one thing they have said they do not want.
|
|
132
|
+
*/
|
|
133
|
+
export declare class AbortError extends PersistMemoryError {
|
|
134
|
+
constructor(message?: string);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* A response the server refused, turned into the right class.
|
|
138
|
+
*
|
|
139
|
+
* Keyed on the CODE first and the status second. The code is the API's own
|
|
140
|
+
* vocabulary and is the more precise of the two - `IDEMPOTENCY_MISMATCH` is a
|
|
141
|
+
* 422 that means "you reused a key with a different body", which is a
|
|
142
|
+
* validation problem and not a generic unprocessable entity.
|
|
143
|
+
*
|
|
144
|
+
* The body is parsed defensively at every step. A 502 from a proxy in front of
|
|
145
|
+
* the API returns HTML, and an SDK that assumes JSON turns a bad gateway into
|
|
146
|
+
* an unhelpful SyntaxError thrown from inside the error handler.
|
|
147
|
+
*/
|
|
148
|
+
export declare function errorFromResponse(status: number, body: unknown, headers: {
|
|
149
|
+
get(name: string): string | null;
|
|
150
|
+
}): PersistMemoryError;
|
|
151
|
+
/**
|
|
152
|
+
* Removes anything key-shaped from a string bound for an error message.
|
|
153
|
+
*
|
|
154
|
+
* Every message this module produces goes through here, including the
|
|
155
|
+
* server's own. It should never contain a key - we never send it in a body
|
|
156
|
+
* and the API never echoes it - but "should never" is how credentials end up
|
|
157
|
+
* in issue trackers. The cost is one regex on a path that has already failed.
|
|
158
|
+
*/
|
|
159
|
+
export declare function redact(text: string): string;
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { BackoffOptions } from "./backoff.js";
|
|
2
|
+
import type { QueryParams } from "./query.js";
|
|
3
|
+
import { PersistMemoryError } from "./errors.js";
|
|
4
|
+
/**
|
|
5
|
+
* The one place a request is made, retried, timed out and turned into an error.
|
|
6
|
+
*
|
|
7
|
+
* Every resource in this package goes through `request`. That is deliberate:
|
|
8
|
+
* an SDK where each resource calls `fetch` for itself is an SDK with six
|
|
9
|
+
* slightly different retry policies, and the differences only show up during
|
|
10
|
+
* an outage, which is the moment nobody wants to be reading six files.
|
|
11
|
+
*
|
|
12
|
+
* `fetch` is injected rather than imported. It is what makes a test of this
|
|
13
|
+
* incapable of opening a socket by accident, and it is the same reason every
|
|
14
|
+
* provider client in this repo takes one.
|
|
15
|
+
*/
|
|
16
|
+
export type { QueryParams, QueryValue } from "./query.js";
|
|
17
|
+
export interface ClientOptions {
|
|
18
|
+
/**
|
|
19
|
+
* A `pm_live_...` API key, or a session JWT.
|
|
20
|
+
*
|
|
21
|
+
* Held privately and never returned, logged, stringified or put in an error.
|
|
22
|
+
* See `#apiKey` below and the `toJSON` next to it.
|
|
23
|
+
*/
|
|
24
|
+
readonly apiKey: string;
|
|
25
|
+
readonly baseUrl?: string;
|
|
26
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
27
|
+
/**
|
|
28
|
+
* Per-attempt deadline, not a budget for the whole call.
|
|
29
|
+
*
|
|
30
|
+
* Per attempt because a whole-call deadline interacts badly with backoff: a
|
|
31
|
+
* request that spent nine seconds waiting between retries would get one
|
|
32
|
+
* second to actually run, and the failure would look like a slow server.
|
|
33
|
+
*/
|
|
34
|
+
readonly timeoutMs?: number;
|
|
35
|
+
/** Attempts, not retries. 3 means the original and two more. */
|
|
36
|
+
readonly maxAttempts?: number;
|
|
37
|
+
readonly backoff?: BackoffOptions;
|
|
38
|
+
/** Injected so tests do not spend real seconds asleep. */
|
|
39
|
+
readonly sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
40
|
+
/** Sent on every request, for support and for server-side triage. */
|
|
41
|
+
readonly userAgent?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface RequestOptions {
|
|
44
|
+
/** Cancels the call. A caller's abort is never retried - they asked it to stop. */
|
|
45
|
+
readonly signal?: AbortSignal;
|
|
46
|
+
readonly timeoutMs?: number;
|
|
47
|
+
readonly maxAttempts?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Makes a POST safe to retry.
|
|
50
|
+
*
|
|
51
|
+
* The API deduplicates on this header, so a retry after a timeout finds the
|
|
52
|
+
* first result rather than doing the work twice. Without one, this client
|
|
53
|
+
* will NOT retry a POST that may already have been processed - see
|
|
54
|
+
* `mayRetry`.
|
|
55
|
+
*
|
|
56
|
+
* Give it meaning: `remember:note-42`, not a fresh random value per call. A
|
|
57
|
+
* random one makes every retry a new request, which is exactly what it
|
|
58
|
+
* exists to prevent.
|
|
59
|
+
*/
|
|
60
|
+
readonly idempotencyKey?: string;
|
|
61
|
+
}
|
|
62
|
+
interface InternalRequest {
|
|
63
|
+
readonly method: "GET" | "POST" | "PATCH" | "DELETE";
|
|
64
|
+
readonly path: string;
|
|
65
|
+
readonly query?: QueryParams;
|
|
66
|
+
readonly body?: unknown;
|
|
67
|
+
readonly options?: RequestOptions;
|
|
68
|
+
}
|
|
69
|
+
export declare class HttpClient {
|
|
70
|
+
#private;
|
|
71
|
+
constructor(options: ClientOptions);
|
|
72
|
+
/**
|
|
73
|
+
* What this object looks like when something serialises it.
|
|
74
|
+
*
|
|
75
|
+
* Both hooks return the same key-free shape. `toJSON` covers
|
|
76
|
+
* `JSON.stringify`, the inspect symbol covers `console.log` under Node, and
|
|
77
|
+
* between them they cover how a credential actually escapes: not through a
|
|
78
|
+
* deliberate log line, but through an object dumped into a bug report.
|
|
79
|
+
*/
|
|
80
|
+
toJSON(): Record<string, unknown>;
|
|
81
|
+
get<T>(path: string, query?: QueryParams, options?: RequestOptions): Promise<T>;
|
|
82
|
+
post<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
83
|
+
patch<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
84
|
+
delete<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Whether this failure is worth another attempt.
|
|
88
|
+
*
|
|
89
|
+
* Two questions, and both have to say yes. The first is whether the failure
|
|
90
|
+
* itself could clear - a 400 fails identically on every attempt, so retrying
|
|
91
|
+
* spends two more calls to learn the same thing. The second is whether
|
|
92
|
+
* repeating the REQUEST is safe, which is a different question and the one
|
|
93
|
+
* that gets skipped.
|
|
94
|
+
*
|
|
95
|
+
* For a POST the honest answer is that we usually cannot tell. A connection
|
|
96
|
+
* that died after the server accepted the request is indistinguishable from
|
|
97
|
+
* one that died before, so retrying `remember` on a timeout would queue the
|
|
98
|
+
* same note twice and the user would see it remembered twice. So a POST is
|
|
99
|
+
* retried only when:
|
|
100
|
+
*
|
|
101
|
+
* an idempotency key was given the API deduplicates on it, so the retry
|
|
102
|
+
* returns the first result rather than
|
|
103
|
+
* repeating the work
|
|
104
|
+
*
|
|
105
|
+
* the status was 429 the request was refused BEFORE it was
|
|
106
|
+
* processed. That is what a rate limit is,
|
|
107
|
+
* and it is the one case where "already
|
|
108
|
+
* succeeded" is not possible
|
|
109
|
+
*
|
|
110
|
+
* Every other retryable POST failure - a 500, a 503, a timeout, a reset - is
|
|
111
|
+
* given back to the caller, who knows whether repeating it is safe and this
|
|
112
|
+
* package does not.
|
|
113
|
+
*/
|
|
114
|
+
export declare function mayRetry(error: PersistMemoryError, request: InternalRequest): boolean;
|