@expandai/sdk 0.17.1 → 0.18.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/README.md +4 -0
- package/build/dts/Client.d.ts +10 -4
- package/build/dts/Client.d.ts.map +1 -1
- package/build/dts/Error.d.ts.map +1 -1
- package/build/dts/FetchJson.d.ts +120 -132
- package/build/dts/FetchJson.d.ts.map +1 -1
- package/build/dts/Generated.d.ts +446 -492
- package/build/dts/Generated.d.ts.map +1 -1
- package/build/dts/HttpClientHelpers.d.ts +1 -1
- package/build/dts/Internal.d.ts +4 -3
- package/build/dts/Internal.d.ts.map +1 -1
- package/build/dts/Playground.d.ts +8 -4
- package/build/dts/Playground.d.ts.map +1 -1
- package/build/dts/Service.d.ts +78 -81
- package/build/dts/Service.d.ts.map +1 -1
- package/build/dts/index.d.ts +1 -1
- package/build/dts/index.d.ts.map +1 -1
- package/build/esm/Client.js +9 -1
- package/build/esm/Client.js.map +1 -1
- package/build/esm/Error.js.map +1 -1
- package/build/esm/FetchJson.js +3 -3
- package/build/esm/FetchJson.js.map +1 -1
- package/build/esm/Generated.js +116 -94
- package/build/esm/Generated.js.map +1 -1
- package/build/esm/Internal.js +17 -7
- package/build/esm/Internal.js.map +1 -1
- package/build/esm/Playground.js +38 -18
- package/build/esm/Playground.js.map +1 -1
- package/build/esm/Service.js +23 -23
- package/build/esm/Service.js.map +1 -1
- package/build/esm/index.js.map +1 -1
- package/package.json +22 -3
- package/src/Client.ts +15 -2
- package/src/FetchJson.ts +3 -3
- package/src/Generated.ts +173 -119
- package/src/Internal.ts +29 -7
- package/src/Playground.ts +54 -20
- package/src/Service.ts +39 -24
- package/src/index.ts +2 -0
package/src/Playground.ts
CHANGED
|
@@ -3,12 +3,9 @@
|
|
|
3
3
|
// Canonical public snapshot URLs (all public, no auth, no TTL):
|
|
4
4
|
//
|
|
5
5
|
// /s/<snapshotId> — whole snapshot
|
|
6
|
-
// /s/<snapshotId
|
|
7
|
-
// /s/<snapshotId>?id=<evidenceId> — open extracted State JSON evidence
|
|
6
|
+
// /s/<snapshotId>?id=<evidenceId> — open any public evidence target
|
|
8
7
|
//
|
|
9
|
-
// A citation maps a search snippet
|
|
10
|
-
|
|
11
|
-
import { Match } from 'effect'
|
|
8
|
+
// A citation maps a search snippet location to the view that audits it.
|
|
12
9
|
|
|
13
10
|
export const DEFAULT_PLAYGROUND_HOST = 'https://expand.land'
|
|
14
11
|
|
|
@@ -16,7 +13,12 @@ export type CitationSource = 'markdown' | 'appendix' | 'statejson'
|
|
|
16
13
|
|
|
17
14
|
/** Snippet location subset needed to build a deep link. Tolerates `null` and `undefined`. */
|
|
18
15
|
export interface CitationLocation {
|
|
16
|
+
readonly evidenceId?: number | null
|
|
17
|
+
/** @deprecated Use evidenceId. */
|
|
18
|
+
readonly markdownBlockId?: string | null
|
|
19
|
+
/** @deprecated Use evidenceId for public citations. */
|
|
19
20
|
readonly nodeIds?: ReadonlyArray<number> | null
|
|
21
|
+
/** @deprecated Use evidenceId. */
|
|
20
22
|
readonly stateJsonSourceId?: number | null
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -24,6 +26,18 @@ function normalizeHost(host: string): string {
|
|
|
24
26
|
return host.replace(/\/+$/, '')
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
function publicEvidenceId(value: number | null | undefined): number | undefined {
|
|
30
|
+
return value !== null && value !== undefined && Number.isInteger(value) && value > 0 && value <= 2_147_483_647
|
|
31
|
+
? value
|
|
32
|
+
: undefined
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function legacyArchiveId(value: number | null | undefined): number | undefined {
|
|
36
|
+
return value !== null && value !== undefined && Number.isInteger(value) && value >= 0 && value <= 0xffffffff
|
|
37
|
+
? value
|
|
38
|
+
: undefined
|
|
39
|
+
}
|
|
40
|
+
|
|
27
41
|
/** Whole-snapshot URL: `${host}/s/${snapshotId}`. */
|
|
28
42
|
export function playgroundBase(snapshotId: string, host: string = DEFAULT_PLAYGROUND_HOST): string {
|
|
29
43
|
return `${normalizeHost(host)}/s/${encodeURIComponent(snapshotId)}`
|
|
@@ -51,26 +65,46 @@ export function resolvePlaygroundHost(metaPlayground: string | null | undefined,
|
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
/**
|
|
54
|
-
* Deep link auditing a single snippet
|
|
55
|
-
* - `statejson` with a source id → `?id=<stateJsonSourceId>` (opens the JSON evidence object).
|
|
56
|
-
* - `markdown`/`appendix` with a node id → `/playground#focus=<nodeIds[0]>` (highlights the block).
|
|
57
|
-
* - otherwise → the whole-snapshot base URL.
|
|
68
|
+
* Deep link auditing a single snippet by its canonical public Evidence ID.
|
|
58
69
|
*/
|
|
70
|
+
export function citationUrl(snapshotId: string, location: CitationLocation | null | undefined, host?: string): string
|
|
71
|
+
/** @deprecated Pass the location as the second argument; source is no longer needed. */
|
|
59
72
|
export function citationUrl(
|
|
60
73
|
snapshotId: string,
|
|
61
74
|
source: CitationSource,
|
|
62
75
|
location: CitationLocation | null | undefined,
|
|
63
|
-
host
|
|
76
|
+
host?: string,
|
|
77
|
+
): string
|
|
78
|
+
export function citationUrl(
|
|
79
|
+
snapshotId: string,
|
|
80
|
+
sourceOrLocation: CitationSource | CitationLocation | null | undefined,
|
|
81
|
+
locationOrHost?: CitationLocation | string | null,
|
|
82
|
+
legacyHost: string = DEFAULT_PLAYGROUND_HOST,
|
|
64
83
|
): string {
|
|
84
|
+
const legacySignature = typeof sourceOrLocation === 'string'
|
|
85
|
+
const source = legacySignature ? sourceOrLocation : undefined
|
|
86
|
+
const location = legacySignature
|
|
87
|
+
? typeof locationOrHost === 'string'
|
|
88
|
+
? undefined
|
|
89
|
+
: locationOrHost
|
|
90
|
+
: sourceOrLocation
|
|
91
|
+
const host = legacySignature
|
|
92
|
+
? legacyHost
|
|
93
|
+
: typeof locationOrHost === 'string'
|
|
94
|
+
? locationOrHost
|
|
95
|
+
: DEFAULT_PLAYGROUND_HOST
|
|
65
96
|
const base = playgroundBase(snapshotId, host)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
97
|
+
const evidenceId = publicEvidenceId(location?.evidenceId)
|
|
98
|
+
if (evidenceId !== undefined) {
|
|
99
|
+
return `${base}?id=${evidenceId}`
|
|
100
|
+
}
|
|
101
|
+
if (source === 'statejson') {
|
|
102
|
+
const legacyId = legacyArchiveId(location?.stateJsonSourceId)
|
|
103
|
+
return legacyId === undefined ? base : `${base}?id=${legacyId}`
|
|
104
|
+
}
|
|
105
|
+
if (source === 'markdown' || source === 'appendix') {
|
|
106
|
+
const nodeId = legacyArchiveId(location?.nodeIds?.[0])
|
|
107
|
+
return nodeId === undefined ? base : `${base}/playground#focus=${nodeId}`
|
|
108
|
+
}
|
|
109
|
+
return base
|
|
76
110
|
}
|
package/src/Service.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HttpClient, HttpClientRequest, HttpClientResponse } from '@effect/platform'
|
|
2
2
|
import { NodeHttpClient } from '@effect/platform-node'
|
|
3
|
-
import { Context, Duration, Effect, flow, Layer, Schedule, Schema } from 'effect'
|
|
3
|
+
import { Context, Duration, Effect, flow, Layer, Option, Random, Schedule, Schema } from 'effect'
|
|
4
4
|
import { ExpandSdkError } from './Error.js'
|
|
5
5
|
import {
|
|
6
6
|
type FetchObjectModeResultWithJson,
|
|
@@ -31,29 +31,28 @@ export class ExpandServiceOptions extends Schema.Class<ExpandServiceOptions>('Ex
|
|
|
31
31
|
}),
|
|
32
32
|
/**
|
|
33
33
|
* Total wall-clock budget in milliseconds for the operation, including retries and backoff.
|
|
34
|
-
*
|
|
34
|
+
* A successful attempt may use the remaining operation budget. Defaults to 60_000.
|
|
35
35
|
*/
|
|
36
36
|
timeoutMs: Schema.optional(Schema.Number).annotations({
|
|
37
37
|
description:
|
|
38
38
|
'Total wall-clock budget in milliseconds for the operation, including retries and backoff. ' +
|
|
39
|
-
'
|
|
39
|
+
'A successful attempt may use the remaining operation budget. Defaults to 60_000.',
|
|
40
40
|
}),
|
|
41
41
|
/**
|
|
42
42
|
* Number of retry attempts for retryable failures (timeouts, connection errors, 408/409/429/5xx).
|
|
43
|
-
* Defaults to 2.
|
|
44
|
-
* per-call to opt in.
|
|
43
|
+
* Defaults to 2.
|
|
45
44
|
*/
|
|
46
45
|
maxRetries: Schema.optional(Schema.Number).annotations({
|
|
47
46
|
description:
|
|
48
47
|
'Number of retry attempts for retryable failures (timeouts, connection errors, 408/409/429/5xx). ' +
|
|
49
|
-
'Defaults to 2.
|
|
48
|
+
'Defaults to 2.',
|
|
50
49
|
}),
|
|
51
50
|
},
|
|
52
51
|
{ description: 'Options for constructing an `ExpandService` layer or `ExpandClient` instance.' },
|
|
53
52
|
) {}
|
|
54
53
|
|
|
55
54
|
/**
|
|
56
|
-
* Per-request overrides accepted by `ExpandService.fetch`/`batched`/`getBatched` (and, transitively,
|
|
55
|
+
* Per-request overrides accepted by `ExpandService.fetch`/`batched`/`getBatched`/`cancelBatched` (and, transitively,
|
|
57
56
|
* by `ExpandClient`'s methods).
|
|
58
57
|
*/
|
|
59
58
|
export class RequestOptions extends Schema.Class<RequestOptions>('RequestOptions')(
|
|
@@ -82,8 +81,12 @@ export type FetchSearchParams = typeof Generated.FetchFetchSearchRequest.Encoded
|
|
|
82
81
|
export type FetchSearchResponse = FetchSnapshotSearchResultWithJson
|
|
83
82
|
export type BatchedParams = typeof Generated.FetchBatchedRequest.Encoded
|
|
84
83
|
export type BatchedResponse = typeof Generated.BatchedRun.Type
|
|
84
|
+
export interface BatchedRequestOptions extends RequestOptions {
|
|
85
|
+
readonly idempotencyKey?: string
|
|
86
|
+
}
|
|
85
87
|
export type GetBatchedParams = typeof Generated.FetchGetBatchedParams.Encoded
|
|
86
88
|
export type GetBatchedResponse = typeof Generated.BatchedStatus.Type
|
|
89
|
+
export type CancelBatchedResponse = typeof Generated.BatchedCancellation.Type
|
|
87
90
|
|
|
88
91
|
class ServiceConfig extends Schema.Class<ServiceConfig>('ExpandServiceConfig')({
|
|
89
92
|
apiKey: Schema.NonEmptyString,
|
|
@@ -175,20 +178,14 @@ export class ExpandService extends Effect.Service<ExpandService>()('@expandai/sd
|
|
|
175
178
|
) {
|
|
176
179
|
const totalTimeoutMs = requestOptions.timeoutMs ?? config.timeoutMs
|
|
177
180
|
const maxRetries = requestOptions.maxRetries ?? config.maxRetries
|
|
178
|
-
// `timeoutMs` is
|
|
179
|
-
//
|
|
180
|
-
// including retry backoff. Pipe ordering matters: per-attempt timeout must wrap the operation
|
|
181
|
-
// (so retry can catch its TimeoutException), and total timeout must wrap retry (so it caps
|
|
182
|
-
// backoff between attempts). Floor with a 1ms minimum so degenerate inputs don't produce a
|
|
183
|
-
// zero-millisecond budget that fires before any work happens.
|
|
184
|
-
const perAttemptTimeoutMs = Math.max(1, Math.floor(totalTimeoutMs / (maxRetries + 1)))
|
|
181
|
+
// `timeoutMs` is one wall-clock budget for the operation, including retries and backoff. A slow
|
|
182
|
+
// valid capture may use most of that budget; only failures that arrive before it expires can retry.
|
|
185
183
|
const retrySchedule = Schedule.exponential(Duration.millis(250)).pipe(
|
|
186
184
|
Schedule.jittered,
|
|
187
185
|
Schedule.compose(Schedule.recurs(maxRetries)),
|
|
188
186
|
Schedule.whileInput((error: unknown) => isRetryable(error, totalTimeoutMs)),
|
|
189
187
|
)
|
|
190
188
|
return yield* operation.pipe(
|
|
191
|
-
Effect.timeout(Duration.millis(perAttemptTimeoutMs)),
|
|
192
189
|
Effect.retry(retrySchedule),
|
|
193
190
|
Effect.timeout(Duration.millis(totalTimeoutMs)),
|
|
194
191
|
Effect.mapError((error) => toExpandError(error, totalTimeoutMs)),
|
|
@@ -226,15 +223,26 @@ export class ExpandService extends Effect.Service<ExpandService>()('@expandai/sd
|
|
|
226
223
|
|
|
227
224
|
const batched = Effect.fn('ExpandService.batched')(function* (
|
|
228
225
|
params: BatchedParams,
|
|
229
|
-
requestOptions:
|
|
226
|
+
requestOptions: BatchedRequestOptions = {},
|
|
230
227
|
) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
228
|
+
const { idempotencyKey: providedIdempotencyKey, ...retryOptions } = requestOptions
|
|
229
|
+
const idempotencyKey = yield* Option.fromNullable(providedIdempotencyKey).pipe(
|
|
230
|
+
Option.match({
|
|
231
|
+
onSome: Effect.succeed,
|
|
232
|
+
onNone: () =>
|
|
233
|
+
Effect.all([Random.nextInt, Random.nextInt, Random.nextInt]).pipe(
|
|
234
|
+
Effect.map(
|
|
235
|
+
([first, second, third]) =>
|
|
236
|
+
`sdk-${Math.abs(first).toString(36)}-${Math.abs(second).toString(36)}-${Math.abs(third).toString(36)}`,
|
|
237
|
+
),
|
|
238
|
+
),
|
|
239
|
+
}),
|
|
240
|
+
)
|
|
241
|
+
const generatedRequest: Parameters<typeof generated.fetchBatched>[0] = {
|
|
242
|
+
params: { 'x-idempotency-key': idempotencyKey },
|
|
243
|
+
payload: params,
|
|
244
|
+
}
|
|
245
|
+
return yield* withRetries(generated.fetchBatched(generatedRequest), retryOptions)
|
|
238
246
|
})
|
|
239
247
|
|
|
240
248
|
const getBatched = Effect.fn('ExpandService.getBatched')(function* (
|
|
@@ -247,7 +255,14 @@ export class ExpandService extends Effect.Service<ExpandService>()('@expandai/sd
|
|
|
247
255
|
return yield* withRetries(generated.fetchGetBatched(id, params), { timeoutMs, maxRetries })
|
|
248
256
|
})
|
|
249
257
|
|
|
250
|
-
|
|
258
|
+
const cancelBatched = Effect.fn('ExpandService.cancelBatched')(function* (
|
|
259
|
+
id: string,
|
|
260
|
+
requestOptions: RequestOptions = {},
|
|
261
|
+
) {
|
|
262
|
+
return yield* withRetries(generated.fetchCancelBatched(id), requestOptions)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
return { fetch, fetchJson, fetchSearch, batched, getBatched, cancelBatched } as const
|
|
251
266
|
}),
|
|
252
267
|
}) {
|
|
253
268
|
/**
|